/**
 * EventHandle manages the binding and unbinding of event listeners. It provides a convenient way
 * to add, remove, and invoke event handlers associated with specific event names. Each EventHandle
 * is linked to an 'owner' object, typically an instance of the Events class, allowing for elegant
 * event management and chaining.
 */
let EventHandle$1 = class EventHandle {
    /**
     * Creates an instance of EventHandle.
     *
     * @param owner - Owner
     * @param name - Name
     * @param fn - Callback function
     */
    constructor(owner, name, fn) {
        this.owner = owner;
        this.name = name;
        this.fn = fn;
    }
    /**
     * Unbinds the event handle from the owner, effectively removing the event listener. After
     * calling this method, the event handle will no longer trigger the callback function when the
     * event is emitted.
     */
    unbind() {
        if (!this.owner) {
            return;
        }
        this.owner.unbind(this.name, this.fn);
        this.owner = null;
        this.name = null;
        this.fn = null;
    }
    /**
     * Invokes the callback function associated with the event handle. This method directly
     * triggers the event's callback without the event being emitted by the event system.
     */
    call(_events, ..._args) {
        if (!this.fn) {
            return;
        }
        this.fn.call(this.owner, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7]);
    }
    /**
     * Registers a new event listener on the same owner as the EventHandle. This method allows
     * chaining additional event listeners to the owner of this event handle.
     *
     * @param name - Name
     * @param fn - Callback function
     * @returns EventHandle
     */
    on(name, fn) {
        return this.owner.on(name, fn);
    }
};

/**
 * Base class for event handling, providing mechanisms to register, emit, and unbind events. This
 * class supports adding event listeners, emitting events with up to 8 arguments, and managing
 * multiple emitters.
 *
 * @example
 * // Create an instance of the Events class
 * const events = new Events();
 *
 * // Register an event listener
 * events.on('testEvent', (arg1, arg2) => {
 *     console.log('Event triggered with arguments:', arg1, arg2);
 * });
 *
 * // Emit the event
 * events.emit('testEvent', 'value1', 'value2');
 *
 * // Unbind the event listener
 * events.unbind('testEvent');
 */
let Events$1 = class Events {
    /**
     * Creates a new Events instance.
     */
    constructor() {
        this._suspendEvents = false;
        this._additionalEmitters = [];
        this._events = {};
        // _world
        Object.defineProperty(this, '_events', {
            enumerable: false,
            configurable: false,
            writable: true,
            value: {}
        });
    }
    /**
     * Sets whether events are suspended. If true, the observer will not emit events when values
     * are set.
     */
    set suspendEvents(value) {
        this._suspendEvents = !!value;
    }
    /**
     * Gets whether events are suspended.
     */
    get suspendEvents() {
        return this._suspendEvents;
    }
    /**
     * Registers an event listener for the specified event name. If the event is emitted,
     * the callback function is executed with up to 8 arguments.
     *
     * @param name - The name of the event to listen for.
     * @param fn - The callback function to be executed when the event is emitted.
     * @returns An EventHandle object that can be used to unbind the event listener.
     *
     * @example
     * // Register an event listener
     * events.on('testEvent', (arg1, arg2) => {
     *     console.log('Event triggered with arguments:', arg1, arg2);
     * });
     *
     * // Emit the event
     * events.emit('testEvent', 'value1', 'value2');
     */
    on(name, fn) {
        const events = this._events[name];
        if (events === undefined) {
            this._events[name] = [fn];
        }
        else {
            if (events.indexOf(fn) === -1) {
                events.push(fn);
            }
        }
        return new EventHandle$1(this, name, fn);
    }
    /**
     * Registers a one-time event listener for the specified event name. The callback function is
     * executed the next time the event is emitted, and then automatically unbound.
     *
     * @param name - The name of the event to listen for.
     * @param fn - The callback function to be executed once when the event is emitted.
     * @returns An EventHandle object that can be used to unbind the event listener
     * before it is triggered.
     *
     * @example
     * // Register a one-time event listener
     * events.once('testEvent', (arg1, arg2) => {
     *     console.log('Event triggered once with arguments:', arg1, arg2);
     * });
     *
     * // Emit the event
     * events.emit('testEvent', 'value1', 'value2'); // The callback will be called and then unbound.
     *
     * // Emit the event again
     * events.emit('testEvent', 'value1', 'value2'); // The callback will not be called this time.
     */
    once(name, fn) {
        const evt = this.on(name, (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) => {
            fn.call(this, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
            evt.unbind();
        });
        return evt;
    }
    /**
     * Emits the specified event, executing all registered listeners for that event with the
     * provided arguments. If events are suspended, the emit operation will be ignored.
     *
     * @param name - The name of the event to emit.
     * @param arg0 - The first argument to pass to the event listeners.
     * @param arg1 - The second argument to pass to the event listeners.
     * @param arg2 - The third argument to pass to the event listeners.
     * @param arg3 - The fourth argument to pass to the event listeners.
     * @param arg4 - The fifth argument to pass to the event listeners.
     * @param arg5 - The sixth argument to pass to the event listeners.
     * @param arg6 - The seventh argument to pass to the event listeners.
     * @param arg7 - The eighth argument to pass to the event listeners.
     * @returns The current instance for chaining.
     *
     * @example
     * // Register an event listener
     * events.on('testEvent', (arg1, arg2) => {
     *     console.log('Event triggered with arguments:', arg1, arg2);
     * });
     *
     * // Emit the event
     * events.emit('testEvent', 'value1', 'value2');
     *
     * // Emit the event with more arguments
     * events.emit('testEvent', 'value1', 'value2', 'value3', 'value4');
     */
    emit(name, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
        if (this._suspendEvents)
            return this;
        let events = this._events[name];
        if (events && events.length) {
            events = events.slice(0);
            for (let i = 0; i < events.length; i++) {
                if (!events[i]) {
                    continue;
                }
                try {
                    events[i].call(this, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
                }
                catch (ex) {
                    console.info('%c%s %c(event error)', 'color: #06f', name, 'color: #f00');
                    console.log(ex.stack);
                }
            }
        }
        if (this._additionalEmitters.length) {
            const emitters = this._additionalEmitters.slice();
            emitters.forEach((emitter) => {
                emitter.emit(name, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
            });
        }
        return this;
    }
    /**
     * Unbinds an event listener for the specified event name. If a callback function is provided,
     * only that specific listener is removed. If no callback is provided, all listeners for the
     * event are removed. If no event name is provided, all listeners for all events are removed.
     *
     * @param name - The name of the event to unbind. If not provided, all events are
     * unbound.
     * @param fn - The specific callback function to remove. If not provided, all
     * listeners for the event are removed.
     * @returns The current instance for chaining.
     *
     * @example
     * // Register an event listener
     * const callback = (arg1, arg2) => {
     *     console.log('Event triggered with arguments:', arg1, arg2);
     * };
     * events.on('testEvent', callback);
     *
     * // Unbind the specific event listener
     * events.unbind('testEvent', callback);
     *
     * // Unbind all listeners for a specific event
     * events.unbind('testEvent');
     *
     * // Unbind all listeners for all events
     * events.unbind();
     */
    unbind(name, fn) {
        if (name) {
            const events = this._events[name];
            if (!events) {
                return this;
            }
            if (fn) {
                const i = events.indexOf(fn);
                if (i !== -1) {
                    if (events.length === 1) {
                        delete this._events[name];
                    }
                    else {
                        events.splice(i, 1);
                    }
                }
            }
            else {
                delete this._events[name];
            }
        }
        else {
            this._events = {};
        }
        return this;
    }
    /**
     * Adds another emitter. Any events fired by this instance will also be fired on the additional
     * emitter.
     *
     * @param emitter - The emitter
     */
    addEmitter(emitter) {
        if (!this._additionalEmitters.includes(emitter)) {
            this._additionalEmitters.push(emitter);
        }
    }
    /**
     * Removes emitter.
     *
     * @param emitter - The emitter
     */
    removeEmitter(emitter) {
        const idx = this._additionalEmitters.indexOf(emitter);
        if (idx !== -1) {
            this._additionalEmitters.splice(idx, 1);
        }
    }
};

/**
 * Determines whether two arrays are deeply equal. Two arrays are considered equal if they have the
 * same length and corresponding elements are equal. This function also supports nested arrays,
 * comparing them recursively.
 *
 * @param a - The first array to compare.
 * @param b - The second array to compare.
 * @returns Returns `true` if the arrays are deeply equal, otherwise `false`.
 *
 * @example
 * arrayEquals([1, 2, 3], [1, 2, 3]); // true
 * arrayEquals([1, 2, 3], [3, 2, 1]); // false
 * arrayEquals([1, [2, 3]], [1, [2, 3]]); // true
 * arrayEquals([1, [2, 3]], [1, [3, 2]]); // false
 * arrayEquals([1, 2, 3], null); // false
 * arrayEquals(null, null); // false
 */
const arrayEquals = (a, b) => {
    if (!a || !b) {
        return false;
    }
    const l = a.length;
    if (l !== b.length) {
        return false;
    }
    for (let i = 0; i < l; i++) {
        if (a[i] instanceof Array && b[i] instanceof Array) {
            if (!arrayEquals(a[i], b[i])) {
                return false;
            }
        }
        else if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
};

/**
 * The Observer class is used to observe and manage changes to an object. It allows for tracking
 * modifications to nested properties, emitting events on changes, and maintaining state
 * consistency. This is particularly useful in applications where state management and change
 * tracking are critical, such as in data-driven interfaces or collaborative applications.
 *
 * @example
 * const data = {
 *   name: 'John',
 *   age: 30,
 *   address: {
 *     city: 'New York',
 *     zip: '10001'
 *   }
 * };
 *
 * const observer = new Observer(data);
 *
 * observer.on('name:set', (newValue, oldValue) => {
 *   console.log(`Name changed from ${oldValue} to ${newValue}`);
 * });
 *
 * observer.set('name', 'Jane'); // Logs: Name changed from John to Jane
 */
class Observer extends Events$1 {
    /**
     * Creates a new Observer instance.
     *
     * @param data - The initial data to observe.
     * @param options - Additional options for the observer.
     */
    constructor(data, options = {}) {
        super();
        this._destroyed = false;
        this._path = '';
        this._keys = [];
        this._data = {};
        this._pathsWithDuplicates = null;
        this._parent = null;
        this._parentPath = '';
        this._parentField = null;
        this._parentKey = null;
        this._latestFn = null;
        this._silent = false;
        // array paths where duplicate entries are allowed - normally
        // we check if an array already has a value before inserting it
        // but if the array path is in here we'll allow it
        this._pathsWithDuplicates = null;
        if (options.pathsWithDuplicates) {
            this._pathsWithDuplicates = {};
            for (let i = 0; i < options.pathsWithDuplicates.length; i++) {
                this._pathsWithDuplicates[options.pathsWithDuplicates[i]] = true;
            }
        }
        this.patch(data);
        this._parent = options.parent || null;
        this._parentPath = options.parentPath || '';
        this._parentField = options.parentField || null;
        this._parentKey = options.parentKey || null;
        this._latestFn = options.latestFn || null;
        this._silent = false;
        const propagate = function (evt) {
            return function (path, arg1, arg2, arg3) {
                if (!this._parent) {
                    return;
                }
                let key = this._parentKey;
                if (!key && (this._parentField instanceof Array)) {
                    key = this._parentField.indexOf(this);
                    if (key === -1) {
                        return;
                    }
                }
                path = `${this._parentPath}.${key}.${path}`;
                let state;
                if (this._silent) {
                    state = this._parent.silence();
                }
                this._parent.emit(`${path}:${evt}`, arg1, arg2, arg3);
                this._parent.emit(`*:${evt}`, path, arg1, arg2, arg3);
                if (this._silent) {
                    this._parent.silenceRestore(state);
                }
            };
        };
        // propagate set
        this.on('*:set', propagate('set'));
        this.on('*:unset', propagate('unset'));
        this.on('*:insert', propagate('insert'));
        this.on('*:remove', propagate('remove'));
        this.on('*:move', propagate('move'));
    }
    static _splitPath(path) {
        const cache = Observer._splitPathsCache;
        let result = cache[path];
        if (!result) {
            result = path.split('.');
            cache[path] = result;
        }
        else {
            result = result.slice();
        }
        return result;
    }
    silence() {
        this._silent = true;
        // history hook to prevent array values to be recorded
        const historyState = this.history && this.history.enabled;
        if (historyState) {
            this.history.enabled = false;
        }
        // sync hook to prevent array values to be recorded as array root already did
        const syncState = this.sync && this.sync.enabled;
        if (syncState) {
            this.sync.enabled = false;
        }
        return [historyState, syncState];
    }
    silenceRestore(state) {
        this._silent = false;
        if (state[0]) {
            this.history.enabled = true;
        }
        if (state[1]) {
            this.sync.enabled = true;
        }
    }
    _prepare(target, key, value, silent = false, remote = false) {
        let i;
        let state;
        const path = (target._path ? (`${target._path}.`) : '') + key;
        const type = typeof value;
        target._keys.push(key);
        if (type === 'object' && (value instanceof Array)) {
            target._data[key] = value.slice(0);
            for (i = 0; i < target._data[key].length; i++) {
                if (typeof target._data[key][i] === 'object' && target._data[key][i] !== null) {
                    if (target._data[key][i] instanceof Array) {
                        target._data[key][i].slice(0);
                    }
                    else {
                        target._data[key][i] = new Observer(target._data[key][i], {
                            parent: this,
                            parentPath: path,
                            parentField: target._data[key],
                            parentKey: null
                        });
                    }
                }
                else {
                    state = this.silence();
                    this.emit(`${path}.${i}:set`, target._data[key][i], null, remote);
                    this.emit('*:set', `${path}.${i}`, target._data[key][i], null, remote);
                    this.silenceRestore(state);
                }
            }
            if (silent) {
                state = this.silence();
            }
            this.emit(`${path}:set`, target._data[key], null, remote);
            this.emit('*:set', path, target._data[key], null, remote);
            if (silent) {
                this.silenceRestore(state);
            }
        }
        else if (type === 'object' && (value instanceof Object)) {
            if (typeof target._data[key] !== 'object') {
                target._data[key] = {
                    _path: path,
                    _keys: [],
                    _data: {}
                };
            }
            for (i in value) {
                if (typeof value[i] === 'object') {
                    this._prepare(target._data[key], i, value[i], true, remote);
                }
                else {
                    state = this.silence();
                    target._data[key]._data[i] = value[i];
                    target._data[key]._keys.push(i);
                    this.emit(`${path}.${i}:set`, value[i], null, remote);
                    this.emit('*:set', `${path}.${i}`, value[i], null, remote);
                    this.silenceRestore(state);
                }
            }
            if (silent) {
                state = this.silence();
            }
            // passing undefined as valueOld here
            // but we should get the old value to be consistent
            this.emit(`${path}:set`, value, undefined, remote);
            this.emit('*:set', path, value, undefined, remote);
            if (silent) {
                this.silenceRestore(state);
            }
        }
        else {
            if (silent) {
                state = this.silence();
            }
            target._data[key] = value;
            this.emit(`${path}:set`, value, undefined, remote);
            this.emit('*:set', path, value, undefined, remote);
            if (silent) {
                this.silenceRestore(state);
            }
        }
        return true;
    }
    /**
     * @param path - Path to the property in the object.
     * @param value - Value to set.
     * @param silent - If true, the change will not be recorded in history.
     * @param remote - State value passed to the set event used to disable remote event emission.
     * @param force - If true, the value will be set even if it is the same as the current value.
     * @returns Returns true if the value was successfully set and false otherwise.
     */
    set(path, value, silent = false, remote = false, force = false) {
        let i;
        let valueOld;
        let keys = Observer._splitPath(path);
        const length = keys.length;
        const key = keys[length - 1];
        let node = this;
        let nodePath = '';
        let obj = this;
        let state;
        for (i = 0; i < length - 1; i++) {
            if (node instanceof Array) {
                node = node[keys[i]];
                if (node instanceof Observer) {
                    path = keys.slice(i + 1).join('.');
                    obj = node;
                }
            }
            else {
                if (i < length && typeof node._data[keys[i]] !== 'object') {
                    if (node._data[keys[i]]) {
                        obj.unset((node._path ? `${node._path}.` : '') + keys[i]);
                    }
                    node._data[keys[i]] = {
                        _path: path,
                        _keys: [],
                        _data: {}
                    };
                    node._keys.push(keys[i]);
                }
                if (i === length - 1 && node._path) {
                    nodePath = `${node._path}.${keys[i]}`;
                }
                node = node._data[keys[i]];
            }
        }
        if (node instanceof Array) {
            const ind = parseInt(key, 10);
            if (node[ind] === value && !force) {
                return false;
            }
            valueOld = node[ind];
            if (valueOld instanceof Observer) {
                valueOld = valueOld.json();
            }
            else {
                valueOld = obj.json(valueOld);
            }
            node[ind] = value;
            if (value instanceof Observer) {
                value._parent = obj;
                value._parentPath = nodePath;
                value._parentField = node;
                value._parentKey = null;
            }
            if (silent) {
                state = obj.silence();
            }
            obj.emit(`${path}:set`, value, valueOld, remote);
            obj.emit('*:set', path, value, valueOld, remote);
            if (silent) {
                obj.silenceRestore(state);
            }
            return true;
        }
        else if (node._data && !node._data.hasOwnProperty(key)) {
            if (typeof value === 'object') {
                return obj._prepare(node, key, value, false, remote);
            }
            node._data[key] = value;
            node._keys.push(key);
            if (silent) {
                state = obj.silence();
            }
            obj.emit(`${path}:set`, value, null, remote);
            obj.emit('*:set', path, value, null, remote);
            if (silent) {
                obj.silenceRestore(state);
            }
            return true;
        }
        if (typeof value === 'object' && (value instanceof Array)) {
            if (arrayEquals(value, node._data[key]) && !force) {
                return false;
            }
            valueOld = node._data[key];
            if (!(valueOld instanceof Observer)) {
                valueOld = obj.json(valueOld);
            }
            if (node._data[key] && node._data[key].length === value.length) {
                state = obj.silence();
                // handle new array instance
                if (value.length === 0) {
                    node._data[key] = value;
                }
                for (i = 0; i < node._data[key].length; i++) {
                    if (node._data[key][i] instanceof Observer) {
                        node._data[key][i].patch(value[i], true);
                    }
                    else if (node._data[key][i] !== value[i]) {
                        node._data[key][i] = value[i];
                        obj.emit(`${path}.${i}:set`, node._data[key][i], valueOld && valueOld[i] || null, remote);
                        obj.emit('*:set', `${path}.${i}`, node._data[key][i], valueOld && valueOld[i] || null, remote);
                    }
                }
                obj.silenceRestore(state);
            }
            else {
                node._data[key] = [];
                value.forEach((val) => {
                    this._doInsert(node, key, val, undefined, true);
                });
                state = obj.silence();
                for (i = 0; i < node._data[key].length; i++) {
                    obj.emit(`${path}.${i}:set`, node._data[key][i], valueOld && valueOld[i] || null, remote);
                    obj.emit('*:set', `${path}.${i}`, node._data[key][i], valueOld && valueOld[i] || null, remote);
                }
                obj.silenceRestore(state);
            }
            if (silent) {
                state = obj.silence();
            }
            obj.emit(`${path}:set`, value, valueOld, remote);
            obj.emit('*:set', path, value, valueOld, remote);
            if (silent) {
                obj.silenceRestore(state);
            }
            return true;
        }
        else if (typeof value === 'object' && (value instanceof Object)) {
            let changed = false;
            valueOld = node._data[key];
            if (!(valueOld instanceof Observer)) {
                valueOld = obj.json(valueOld);
            }
            keys = Object.keys(value);
            if (!node._data[key] || !node._data[key]._data) {
                if (node._data[key]) {
                    obj.unset((node._path ? `${node._path}.` : '') + key);
                }
                else {
                    changed = true;
                }
                node._data[key] = {
                    _path: path,
                    _keys: [],
                    _data: {}
                };
            }
            let c;
            for (const n in node._data[key]._data) {
                if (!value.hasOwnProperty(n)) {
                    c = obj.unset(`${path}.${n}`, true);
                    if (c)
                        changed = true;
                }
                else if (node._data[key]._data.hasOwnProperty(n)) {
                    if (!obj._equals(node._data[key]._data[n], value[n])) {
                        c = obj.set(`${path}.${n}`, value[n], true);
                        if (c)
                            changed = true;
                    }
                }
                else {
                    c = obj._prepare(node._data[key], n, value[n], true, remote);
                    if (c)
                        changed = true;
                }
            }
            for (i = 0; i < keys.length; i++) {
                if (value[keys[i]] === undefined && node._data[key]._data.hasOwnProperty(keys[i])) {
                    c = obj.unset(`${path}.${keys[i]}`, true);
                    if (c)
                        changed = true;
                }
                else if (typeof value[keys[i]] === 'object') {
                    if (node._data[key]._data.hasOwnProperty(keys[i])) {
                        c = obj.set(`${path}.${keys[i]}`, value[keys[i]], true);
                        if (c)
                            changed = true;
                    }
                    else {
                        c = obj._prepare(node._data[key], keys[i], value[keys[i]], true, remote);
                        if (c)
                            changed = true;
                    }
                }
                else if (!obj._equals(node._data[key]._data[keys[i]], value[keys[i]])) {
                    if (typeof value[keys[i]] === 'object') {
                        c = obj.set(`${node._data[key]._path}.${keys[i]}`, value[keys[i]], true);
                        if (c)
                            changed = true;
                    }
                    else if (node._data[key]._data[keys[i]] !== value[keys[i]]) {
                        changed = true;
                        if (node._data[key]._keys.indexOf(keys[i]) === -1) {
                            node._data[key]._keys.push(keys[i]);
                        }
                        node._data[key]._data[keys[i]] = value[keys[i]];
                        state = obj.silence();
                        obj.emit(`${node._data[key]._path}.${keys[i]}:set`, node._data[key]._data[keys[i]], null, remote);
                        obj.emit('*:set', `${node._data[key]._path}.${keys[i]}`, node._data[key]._data[keys[i]], null, remote);
                        obj.silenceRestore(state);
                    }
                }
            }
            if (changed) {
                if (silent) {
                    state = obj.silence();
                }
                const val = obj.json(node._data[key]);
                obj.emit(`${node._data[key]._path}:set`, val, valueOld, remote);
                obj.emit('*:set', node._data[key]._path, val, valueOld, remote);
                if (silent) {
                    obj.silenceRestore(state);
                }
                return true;
            }
            return false;
        }
        let data;
        if (!node.hasOwnProperty('_data') && node.hasOwnProperty(key)) {
            data = node;
        }
        else {
            data = node._data;
        }
        if (data[key] === value && !force) {
            return false;
        }
        if (silent) {
            state = obj.silence();
        }
        valueOld = data[key];
        if (!(valueOld instanceof Observer)) {
            valueOld = obj.json(valueOld);
        }
        data[key] = value;
        obj.emit(`${path}:set`, value, valueOld, remote);
        obj.emit('*:set', path, value, valueOld, remote);
        if (silent) {
            obj.silenceRestore(state);
        }
        return true;
    }
    /**
     * Query whether the object has the specified property.
     *
     * @param path - Path to the value.
     * @returns Returns true if the value is present and false otherwise.
     */
    has(path) {
        const keys = Observer._splitPath(path);
        let node = this;
        for (let i = 0, len = keys.length; i < len; i++) {
            // eslint-disable-next-line eqeqeq
            if (node == undefined) {
                return undefined;
            }
            if (node._data) {
                node = node._data[keys[i]];
            }
            else {
                node = node[keys[i]];
            }
        }
        return node !== undefined;
    }
    /**
     * @param path - Path to the value.
     * @param raw - Retrieve the observer object without converting it to JSON.
     * @returns The value at the specified path.
     */
    get(path, raw = false) {
        const keys = Observer._splitPath(path);
        let node = this;
        for (let i = 0; i < keys.length; i++) {
            // eslint-disable-next-line eqeqeq
            if (node == undefined) {
                return undefined;
            }
            if (node._data) {
                node = node._data[keys[i]];
            }
            else {
                node = node[keys[i]];
            }
        }
        if (raw) {
            return node;
        }
        if (node == null) {
            return null;
        }
        return this.json(node);
    }
    getRaw(path) {
        return this.get(path, true);
    }
    _equals(a, b) {
        if (a === b) {
            return true;
        }
        else if (a instanceof Array && b instanceof Array && arrayEquals(a, b)) {
            return true;
        }
        return false;
    }
    /**
     * @param path - Path to the value.
     * @param silent - If true, the change will not be recorded in history.
     * @param remote - State value passed to the set event used to disable remote event emission.
     * @returns Returns true if the value was successfully unset and false otherwise.
     */
    unset(path, silent = false, remote = false) {
        let i;
        const keys = Observer._splitPath(path);
        const key = keys[keys.length - 1];
        let node = this;
        let obj = this;
        for (i = 0; i < keys.length - 1; i++) {
            if (node instanceof Array) {
                node = node[keys[i]];
                if (node instanceof Observer) {
                    path = keys.slice(i + 1).join('.');
                    obj = node;
                }
            }
            else {
                node = node._data[keys[i]];
            }
        }
        if (!node._data || !node._data.hasOwnProperty(key)) {
            return false;
        }
        let valueOld = node._data[key];
        if (!(valueOld instanceof Observer)) {
            valueOld = obj.json(valueOld);
        }
        // recursive
        if (node._data[key] && node._data[key]._data) {
            // do this in reverse order because node._data[key]._keys gets
            // modified as we loop
            for (i = node._data[key]._keys.length - 1; i >= 0; i--) {
                obj.unset(`${path}.${node._data[key]._keys[i]}`, true);
            }
        }
        node._keys.splice(node._keys.indexOf(key), 1);
        delete node._data[key];
        let state;
        if (silent) {
            state = obj.silence();
        }
        obj.emit(`${path}:unset`, valueOld, remote);
        obj.emit('*:unset', path, valueOld, remote);
        if (silent) {
            obj.silenceRestore(state);
        }
        return true;
    }
    /**
     * @param path - Path to the value.
     * @param ind - Index of the value.
     * @param silent - If true, the remove event will not be emitted.
     * @param remote - State value passed to the set event used to disable remote event emission.
     * @returns Returns true if the value was successfully removed and false otherwise.
     */
    remove(path, ind, silent = false, remote = false) {
        const keys = Observer._splitPath(path);
        const key = keys[keys.length - 1];
        let node = this;
        let obj = this;
        for (let i = 0; i < keys.length - 1; i++) {
            if (node instanceof Array) {
                node = node[parseInt(keys[i], 10)];
                if (node instanceof Observer) {
                    path = keys.slice(i + 1).join('.');
                    obj = node;
                }
            }
            else if (node._data && node._data.hasOwnProperty(keys[i])) {
                node = node._data[keys[i]];
            }
            else {
                return false;
            }
        }
        if (!node._data || !node._data.hasOwnProperty(key) || !(node._data[key] instanceof Array)) {
            return false;
        }
        const arr = node._data[key];
        if (arr.length < ind) {
            return false;
        }
        let value = arr[ind];
        if (value instanceof Observer) {
            value._parent = null;
        }
        else {
            value = obj.json(value);
        }
        arr.splice(ind, 1);
        let state;
        if (silent) {
            state = obj.silence();
        }
        obj.emit(`${path}:remove`, value, ind, remote);
        obj.emit('*:remove', path, value, ind, remote);
        if (silent) {
            obj.silenceRestore(state);
        }
        return true;
    }
    /**
     * @param path - Path to the value.
     * @param value - Value to remove.
     * @param silent - If true, the remove event will not be emitted.
     * @param remote - State value passed to the set event used to disable remote event emission.
     * @returns Returns true if the value was successfully removed and false otherwise.
     */
    removeValue(path, value, silent = false, remote = false) {
        const keys = Observer._splitPath(path);
        const key = keys[keys.length - 1];
        let node = this;
        let obj = this;
        for (let i = 0; i < keys.length - 1; i++) {
            if (node instanceof Array) {
                node = node[parseInt(keys[i], 10)];
                if (node instanceof Observer) {
                    path = keys.slice(i + 1).join('.');
                    obj = node;
                }
            }
            else if (node._data && node._data.hasOwnProperty(keys[i])) {
                node = node._data[keys[i]];
            }
            else {
                return;
            }
        }
        if (!node._data || !node._data.hasOwnProperty(key) || !(node._data[key] instanceof Array)) {
            return;
        }
        const arr = node._data[key];
        const ind = arr.indexOf(value);
        if (ind === -1) {
            return;
        }
        if (arr.length < ind) {
            return;
        }
        value = arr[ind];
        if (value instanceof Observer) {
            value._parent = null;
        }
        else {
            value = obj.json(value);
        }
        arr.splice(ind, 1);
        let state;
        if (silent) {
            state = obj.silence();
        }
        obj.emit(`${path}:remove`, value, ind, remote);
        obj.emit('*:remove', path, value, ind, remote);
        if (silent) {
            obj.silenceRestore(state);
        }
        return true;
    }
    /**
     * @param path - Path to the value.
     * @param value - Value to insert.
     * @param ind - Index to insert the value at.
     * @param silent - If true, the insert event will not be emitted.
     * @param remote - State value passed to the set event used to disable remote event emission.
     * @returns Returns true if the value was successfully inserted and false otherwise.
     */
    insert(path, value, ind, silent = false, remote = false) {
        const keys = Observer._splitPath(path);
        const key = keys[keys.length - 1];
        let node = this;
        let obj = this;
        for (let i = 0; i < keys.length - 1; i++) {
            if (node instanceof Array) {
                node = node[parseInt(keys[i], 10)];
                if (node instanceof Observer) {
                    path = keys.slice(i + 1).join('.');
                    obj = node;
                }
            }
            else if (node._data && node._data.hasOwnProperty(keys[i])) {
                node = node._data[keys[i]];
            }
            else {
                return;
            }
        }
        if (!node._data || !node._data.hasOwnProperty(key) || !(node._data[key] instanceof Array)) {
            return;
        }
        const arr = node._data[key];
        value = obj._doInsert(node, key, value, ind);
        if (ind === undefined) {
            ind = arr.length - 1;
        }
        let state;
        if (silent) {
            state = obj.silence();
        }
        obj.emit(`${path}:insert`, value, ind, remote);
        obj.emit('*:insert', path, value, ind, remote);
        if (silent) {
            obj.silenceRestore(state);
        }
        return true;
    }
    _doInsert(node, key, value, ind, allowDuplicates = false) {
        const arr = node._data[key];
        if (typeof value === 'object' && !(value instanceof Observer) && value !== null) {
            if (value instanceof Array) {
                value = value.slice(0);
            }
            else {
                value = new Observer(value);
            }
        }
        const path = node._path ? `${node._path}.${key}` : key;
        if (value !== null && !allowDuplicates && (!this._pathsWithDuplicates || !this._pathsWithDuplicates[path])) {
            if (arr.indexOf(value) !== -1) {
                return;
            }
        }
        if (ind === undefined) {
            arr.push(value);
        }
        else {
            arr.splice(ind, 0, value);
        }
        if (value instanceof Observer) {
            value._parent = this;
            value._parentPath = path;
            value._parentField = arr;
            value._parentKey = null;
        }
        else {
            value = this.json(value);
        }
        return value;
    }
    /**
     * @param path - Path to the value.
     * @param indOld - Index of the value to move.
     * @param indNew - Index to move the value to.
     * @param silent - If true, the move event will not be emitted.
     * @param remote - State value passed to the set event used to disable remote event emission.
     * @returns Returns true if the value was successfully moved and false otherwise.
     */
    move(path, indOld, indNew, silent = false, remote = false) {
        const keys = Observer._splitPath(path);
        const key = keys[keys.length - 1];
        let node = this;
        let obj = this;
        for (let i = 0; i < keys.length - 1; i++) {
            if (node instanceof Array) {
                node = node[parseInt(keys[i], 10)];
                if (node instanceof Observer) {
                    path = keys.slice(i + 1).join('.');
                    obj = node;
                }
            }
            else if (node._data && node._data.hasOwnProperty(keys[i])) {
                node = node._data[keys[i]];
            }
            else {
                return;
            }
        }
        if (!node._data || !node._data.hasOwnProperty(key) || !(node._data[key] instanceof Array)) {
            return;
        }
        const arr = node._data[key];
        if (arr.length < indOld || arr.length < indNew || indOld === indNew) {
            return;
        }
        let value = arr[indOld];
        arr.splice(indOld, 1);
        if (indNew === -1) {
            indNew = arr.length;
        }
        arr.splice(indNew, 0, value);
        if (!(value instanceof Observer)) {
            value = obj.json(value);
        }
        let state;
        if (silent) {
            state = obj.silence();
        }
        obj.emit(`${path}:move`, value, indNew, indOld, remote);
        obj.emit('*:move', path, value, indNew, indOld, remote);
        if (silent) {
            obj.silenceRestore(state);
        }
        return true;
    }
    patch(data, removeMissingKeys = false) {
        if (typeof data !== 'object') {
            return;
        }
        for (const key in data) {
            if (typeof data[key] === 'object' && !this._data.hasOwnProperty(key)) {
                this._prepare(this, key, data[key]);
            }
            else if (this._data[key] !== data[key]) {
                this.set(key, data[key]);
            }
        }
        if (removeMissingKeys) {
            for (const key in this._data) {
                if (!data.hasOwnProperty(key)) {
                    this.unset(key);
                }
            }
        }
    }
    /**
     * @param target - The object to JSONify.
     * @returns The current state of the object tracked by the observer.
     */
    json(target) {
        let key, n;
        let obj = {};
        const node = target === undefined ? this : target;
        let len, nlen;
        if (node instanceof Object && node._keys) {
            len = node._keys.length;
            for (let i = 0; i < len; i++) {
                key = node._keys[i];
                const value = node._data[key];
                const type = typeof value;
                if (type === 'object' && (value instanceof Array)) {
                    obj[key] = value.slice(0);
                    nlen = obj[key].length;
                    for (n = 0; n < nlen; n++) {
                        if (typeof obj[key][n] === 'object') {
                            obj[key][n] = this.json(obj[key][n]);
                        }
                    }
                }
                else if (type === 'object' && (value instanceof Object)) {
                    obj[key] = this.json(value);
                }
                else {
                    obj[key] = value;
                }
            }
        }
        else {
            if (node === null) {
                return null;
            }
            else if (typeof node === 'object' && (node instanceof Array)) {
                obj = node.slice(0);
                len = obj.length;
                for (n = 0; n < len; n++) {
                    obj[n] = this.json(obj[n]);
                }
            }
            else if (typeof node === 'object') {
                for (key in node) {
                    if (node.hasOwnProperty(key)) {
                        obj[key] = node[key];
                    }
                }
            }
            else {
                obj = node;
            }
        }
        return obj;
    }
    forEach(fn, target, path = '') {
        const node = target || this;
        for (let i = 0; i < node._keys.length; i++) {
            const key = node._keys[i];
            const value = node._data[key];
            const type = (this.schema && this.schema.has(path + key) && this.schema.get(path + key).type.name.toLowerCase()) || typeof value;
            if (type === 'object' && (value instanceof Array)) {
                fn(path + key, 'array', value, key);
            }
            else if (type === 'object' && (value instanceof Object)) {
                fn(path + key, 'object', value, key);
                this.forEach(fn, value, `${path + key}.`);
            }
            else {
                fn(path + key, type, value, key);
            }
        }
    }
    /**
     * Returns the latest observer instance. This is important when
     * dealing with undo / redo where the observer might have been deleted
     * and/or possibly re-created.
     *
     * @returns The latest instance of the observer.
     */
    latest() {
        return this._latestFn ? this._latestFn() : this;
    }
    /**
     * Destroys the observer instance.
     */
    destroy() {
        if (this._destroyed)
            return;
        this._destroyed = true;
        this.emit('destroy');
        this.unbind();
    }
    set latestFn(value) {
        this._latestFn = value;
    }
    get latestFn() {
        return this._latestFn;
    }
}
// cache calls to path.split(path, '.')
// as they take considerable time especially during loading
// if there are a lot of observers like entities, assets etc.
Observer._splitPathsCache = {};

const CLASS_COLLAPSED = 'pcui-collapsed';
const CLASS_COLLAPSIBLE = 'pcui-collapsible';
const CLASS_DEFAULT_MOUSEDOWN = 'pcui-default-mousedown';
const CLASS_DISABLED = 'pcui-disabled';
const CLASS_ERROR = 'pcui-error';
const CLASS_FLASH = 'flash';
const CLASS_FLEX = 'pcui-flex';
const CLASS_FOCUS = 'pcui-focus';
const CLASS_FONT_REGULAR = 'font-regular';
const CLASS_FONT_BOLD = 'font-bold';
const CLASS_GRID = 'pcui-grid';
const CLASS_HIDDEN = 'pcui-hidden';
const CLASS_MULTIPLE_VALUES = 'pcui-multiple-values';
const CLASS_NOT_FLEXIBLE = 'pcui-not-flexible';
const CLASS_READONLY = 'pcui-readonly';
const CLASS_RESIZABLE = 'pcui-resizable';
const CLASS_SCROLLABLE = 'pcui-scrollable';

const CLASS_ELEMENT = 'pcui-element';
// these are properties that are
// available as Element properties and
// can also be set through the Element constructor
const SIMPLE_CSS_PROPERTIES = [
    'flexDirection',
    'flexGrow',
    'flexBasis',
    'flexShrink',
    'flexWrap',
    'alignItems',
    'alignSelf',
    'justifyContent',
    'justifySelf'
];
/**
 * The base class for all UI elements.
 */
let Element$1 = class Element extends Events$1 {
    /**
     * Creates a new Element.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b, _c, _d;
        super();
        this._destroyed = false;
        this._parent = null; // eslint-disable-line no-use-before-define
        this._eventsParent = [];
        this._flashTimeout = null;
        this._suppressChange = false;
        this._onMouseOver = (evt) => {
            this.emit('hover', evt);
        };
        this._onMouseOut = (evt) => {
            this.emit('hoverend', evt);
        };
        if (typeof args.dom === 'string') {
            this._dom = document.createElement(args.dom);
        }
        else if (args.dom instanceof Node) {
            this._dom = args.dom;
        }
        else {
            this._dom = document.createElement('div');
        }
        if (args.id !== undefined) {
            this._dom.id = args.id;
        }
        // add ui reference
        this._dom.ui = this;
        this._onClickEvt = this._onClick.bind(this);
        // add event listeners
        this._dom.addEventListener('click', this._onClickEvt);
        this._dom.addEventListener('mouseover', this._onMouseOver);
        this._dom.addEventListener('mouseout', this._onMouseOut);
        // add css classes
        this._dom.classList.add(CLASS_ELEMENT, CLASS_FONT_REGULAR);
        // add user classes
        if (args.class) {
            const classes = Array.isArray(args.class) ? args.class : [args.class];
            for (const cls of classes) {
                this._dom.classList.add(cls);
            }
        }
        this.enabled = (_a = args.enabled) !== null && _a !== void 0 ? _a : true;
        this._hiddenParents = !args.isRoot;
        this.hidden = (_b = args.hidden) !== null && _b !== void 0 ? _b : false;
        this.readOnly = (_c = args.readOnly) !== null && _c !== void 0 ? _c : false;
        this.ignoreParent = (_d = args.ignoreParent) !== null && _d !== void 0 ? _d : false;
        if (args.width !== undefined) {
            this.width = args.width;
        }
        if (args.height !== undefined) {
            this.height = args.height;
        }
        if (args.tabIndex !== undefined) {
            this.tabIndex = args.tabIndex;
        }
        // copy CSS properties from args
        for (const key in args) {
            // @ts-ignore
            if (args[key] === undefined)
                continue;
            if (SIMPLE_CSS_PROPERTIES.indexOf(key) !== -1) {
                // @ts-ignore
                this[key] = args[key];
            }
        }
        // set the binding object
        if (args.binding) {
            this.binding = args.binding;
        }
    }
    /**
     * Destroys the Element and its events.
     */
    destroy() {
        if (this._destroyed)
            return;
        this._destroyed = true;
        if (this.binding) {
            this.binding = null;
        }
        else {
            this.unlink();
        }
        if (this.parent) {
            const parent = this.parent;
            for (const event of this._eventsParent) {
                event.unbind();
            }
            this._eventsParent.length = 0;
            // remove element from parent
            // check if parent has been destroyed already
            // because we do not want to be emitting events
            // on a destroyed parent after it's been destroyed
            // as it is easy to lead to null exceptions
            // @ts-ignore
            if (parent.remove && !parent._destroyed) {
                // @ts-ignore
                parent.remove(this);
            }
            // set parent to null and remove from
            // parent dom just in case parent.remove above
            // didn't work because of an override or other condition
            this._parent = null;
            // Do not manually call removeChild for elements whose parent has already been destroyed.
            // For example when we destroy a TreeViewItem that has many child nodes, that will trigger every child Element to call dom.parentElement.removeChild(dom).
            // But we don't need to remove all these DOM elements from their parents since the root DOM element is destroyed anyway.
            // This has a big impact on destroy speed in certain cases.
            if (!parent._destroyed && this._dom && this._dom.parentElement) {
                this._dom.parentElement.removeChild(this._dom);
            }
        }
        const dom = this._dom;
        if (dom) {
            // remove event listeners
            dom.removeEventListener('click', this._onClickEvt);
            dom.removeEventListener('mouseover', this._onMouseOver);
            dom.removeEventListener('mouseout', this._onMouseOut);
            // remove ui reference
            delete dom.ui;
            this._dom = null;
        }
        if (this._flashTimeout) {
            window.clearTimeout(this._flashTimeout);
        }
        this.emit('destroy', dom, this);
        this.unbind();
    }
    /**
     * Links the specified observers and paths to the Element's data binding.
     *
     * @param observers - An array of observers or a single observer.
     * @param paths - A path for the observer(s) or an array of paths that maps to each separate observer.
     */
    link(observers, paths) {
        if (this._binding) {
            this._binding.link(observers, paths);
        }
    }
    /**
     * Unlinks the Element from its observers.
     */
    unlink() {
        if (this._binding) {
            this._binding.unlink();
        }
    }
    /**
     * Triggers a flash animation on the Element.
     */
    flash() {
        if (this._flashTimeout)
            return;
        this.class.add(CLASS_FLASH);
        this._flashTimeout = window.setTimeout(() => {
            this._flashTimeout = null;
            this.class.remove(CLASS_FLASH);
        }, 200);
    }
    _onClick(evt) {
        if (this.enabled) {
            this.emit('click', evt);
        }
    }
    _onHiddenToRootChange(hiddenToRoot) {
        this.emit(hiddenToRoot ? 'hideToRoot' : 'showToRoot');
    }
    _onEnabledChange(enabled) {
        if (enabled) {
            this.class.remove(CLASS_DISABLED);
        }
        else {
            this.class.add(CLASS_DISABLED);
        }
        this.emit(enabled ? 'enable' : 'disable');
    }
    _onParentDestroy() {
        this.destroy();
    }
    _onParentDisable() {
        if (this._ignoreParent)
            return;
        if (this._enabled) {
            this._onEnabledChange(false);
        }
    }
    _onParentEnable() {
        if (this._ignoreParent)
            return;
        if (this._enabled) {
            this._onEnabledChange(true);
        }
    }
    _onParentShowToRoot() {
        const oldHiddenToRoot = this.hiddenToRoot;
        this._hiddenParents = false;
        if (oldHiddenToRoot !== this.hiddenToRoot) {
            this._onHiddenToRootChange(this.hiddenToRoot);
        }
    }
    _onParentHideToRoot() {
        const oldHiddenToRoot = this.hiddenToRoot;
        this._hiddenParents = true;
        if (oldHiddenToRoot !== this.hiddenToRoot) {
            this._onHiddenToRootChange(this.hiddenToRoot);
        }
    }
    _onReadOnlyChange(readOnly) {
        if (readOnly) {
            this.class.add(CLASS_READONLY);
        }
        else {
            this.class.remove(CLASS_READONLY);
        }
        this.emit('readOnly', readOnly);
    }
    _onParentReadOnlyChange(readOnly) {
        if (this._ignoreParent)
            return;
        if (readOnly) {
            if (!this._readOnly) {
                this._onReadOnlyChange(true);
            }
        }
        else {
            if (!this._readOnly) {
                this._onReadOnlyChange(false);
            }
        }
    }
    /**
     * @param type - The type we want to reference this Element by.
     * @param cls - The actual class of the Element.
     * @param defaultArguments - Default arguments when creating this type.
     */
    static register(type, cls, defaultArguments) {
        Element.registry.set(type, { cls, defaultArguments });
    }
    /**
     * @param type - The type we want to unregister.
     */
    static unregister(type) {
        Element.registry.delete(type);
    }
    /**
     * Creates a new Element of the desired type.
     *
     * @param type - The type of the Element (registered by Element#register).
     * @param args - Arguments for the Element.
     * @returns The new Element or undefined if type is not found.
     */
    static create(type, args) {
        const entry = Element.registry.get(type);
        if (!entry) {
            console.error('Invalid type passed to Element.create:', type);
            return undefined;
        }
        const cls = entry.cls;
        const clsArgs = Object.assign(Object.assign({}, entry.defaultArguments), args);
        return new cls(clsArgs);
    }
    /**
     * Sets whether the Element or its parent chain is enabled or not. Defaults to `true`.
     */
    set enabled(value) {
        if (this._enabled === value)
            return;
        // remember if enabled in hierarchy
        const enabled = this.enabled;
        this._enabled = value;
        // only fire event if hierarchy state changed
        if (enabled !== value) {
            this._onEnabledChange(value);
        }
    }
    /**
     * Gets whether the Element or its parent chain is enabled or not.
     */
    get enabled() {
        if (this._ignoreParent)
            return this._enabled;
        return this._enabled && (!this._parent || this._parent.enabled);
    }
    /**
     * Sets whether the Element will ignore parent events & variable states.
     */
    set ignoreParent(value) {
        this._ignoreParent = value;
        this._onEnabledChange(this.enabled);
        this._onReadOnlyChange(this.readOnly);
    }
    /**
     * Gets whether the Element will ignore parent events & variable states.
     */
    get ignoreParent() {
        return this._ignoreParent;
    }
    /**
     * Gets the root DOM node for this Element.
     */
    get dom() {
        return this._dom;
    }
    /**
     * Sets the parent Element.
     */
    set parent(value) {
        if (value === this._parent)
            return;
        const oldEnabled = this.enabled;
        const oldReadonly = this.readOnly;
        const oldHiddenToRoot = this.hiddenToRoot;
        if (this._parent) {
            for (let i = 0; i < this._eventsParent.length; i++) {
                this._eventsParent[i].unbind();
            }
            this._eventsParent.length = 0;
        }
        this._parent = value;
        if (this._parent) {
            this._eventsParent.push(this._parent.once('destroy', this._onParentDestroy.bind(this)));
            this._eventsParent.push(this._parent.on('disable', this._onParentDisable.bind(this)));
            this._eventsParent.push(this._parent.on('enable', this._onParentEnable.bind(this)));
            this._eventsParent.push(this._parent.on('readOnly', this._onParentReadOnlyChange.bind(this)));
            this._eventsParent.push(this._parent.on('showToRoot', this._onParentShowToRoot.bind(this)));
            this._eventsParent.push(this._parent.on('hideToRoot', this._onParentHideToRoot.bind(this)));
            this._hiddenParents = this._parent.hiddenToRoot;
        }
        else {
            this._hiddenParents = true;
        }
        this.emit('parent', this._parent);
        const newEnabled = this.enabled;
        if (newEnabled !== oldEnabled) {
            this._onEnabledChange(newEnabled);
        }
        const newReadonly = this.readOnly;
        if (newReadonly !== oldReadonly) {
            this._onReadOnlyChange(newReadonly);
        }
        const hiddenToRoot = this.hiddenToRoot;
        if (hiddenToRoot !== oldHiddenToRoot) {
            this._onHiddenToRootChange(hiddenToRoot);
        }
    }
    /**
     * Gets the parent Element.
     */
    get parent() {
        return this._parent;
    }
    /**
     * Sets whether the Element is hidden.
     */
    set hidden(value) {
        if (value === this._hidden)
            return;
        const oldHiddenToRoot = this.hiddenToRoot;
        this._hidden = value;
        if (value) {
            this.class.add(CLASS_HIDDEN);
        }
        else {
            this.class.remove(CLASS_HIDDEN);
        }
        this.emit(value ? 'hide' : 'show');
        if (this.hiddenToRoot !== oldHiddenToRoot) {
            this._onHiddenToRootChange(this.hiddenToRoot);
        }
    }
    /**
     * Gets whether the Element is hidden.
     */
    get hidden() {
        return this._hidden;
    }
    /**
     * Gets whether the Element is hidden all the way up to the root. If the Element itself or any of its parents are hidden then this is true.
     */
    get hiddenToRoot() {
        return this._hidden || this._hiddenParents;
    }
    /**
     * Sets whether the Element is read only.
     */
    set readOnly(value) {
        if (this._readOnly === value)
            return;
        this._readOnly = value;
        this._onReadOnlyChange(value);
    }
    /**
     * Gets whether the Element is read only.
     */
    get readOnly() {
        if (this._ignoreParent)
            return this._readOnly;
        return this._readOnly || !!(this._parent && this._parent.readOnly);
    }
    /**
     * Sets whether the Element is in an error state.
     */
    set error(value) {
        if (this._hasError === value)
            return;
        this._hasError = value;
        if (value) {
            this.class.add(CLASS_ERROR);
        }
        else {
            this.class.remove(CLASS_ERROR);
        }
    }
    /**
     * Gets whether the Element is in an error state.
     */
    get error() {
        return this._hasError;
    }
    /**
     * Shortcut to Element.dom.style.
     */
    get style() {
        return this._dom.style;
    }
    /**
     * Get the `DOMTokenList` of the underlying DOM element. This is essentially a shortcut to
     * `element.dom.classList`.
     */
    get class() {
        return this._dom.classList;
    }
    /**
     * Sets the width of the Element in pixels. Can also be an empty string to remove it.
     */
    set width(value) {
        this.style.width = typeof value === 'number' ? `${value}px` : value;
    }
    /**
     * Gets the width of the Element in pixels.
     */
    get width() {
        return this._dom.clientWidth;
    }
    /**
     * Sets the height of the Element in pixels. Can also be an empty string to remove it.
     */
    set height(value) {
        this.style.height = typeof value === 'number' ? `${value}px` : value;
    }
    /**
     * Gets the height of the Element in pixels.
     */
    get height() {
        return this._dom.clientHeight;
    }
    /**
     * Sets the tabIndex of the Element.
     */
    set tabIndex(value) {
        this._dom.tabIndex = value;
    }
    /**
     * Gets the tabIndex of the Element.
     */
    get tabIndex() {
        return this._dom.tabIndex;
    }
    /**
     * Sets the Binding object for the element.
     */
    set binding(value) {
        if (this._binding === value)
            return;
        let prevObservers;
        let prevPaths;
        if (this._binding) {
            prevObservers = this._binding.observers;
            prevPaths = this._binding.paths;
            this.unlink();
            this._binding.element = null;
            this._binding = null;
        }
        this._binding = value;
        if (this._binding) {
            // @ts-ignore
            this._binding.element = this;
            if (prevObservers && prevPaths) {
                this.link(prevObservers, prevPaths);
            }
        }
    }
    /**
     * Gets the Binding object for the element.
     */
    get binding() {
        return this._binding;
    }
    get destroyed() {
        return this._destroyed;
    }
    // CSS proxy accessors
    /**
     * Sets the flex-direction CSS property.
     */
    set flexDirection(value) {
        this.style.flexDirection = value;
    }
    /**
     * Gets the flex-direction CSS property.
     */
    get flexDirection() {
        return this.style.flexDirection;
    }
    /**
     * Sets the flex-grow CSS property.
     */
    set flexGrow(value) {
        this.style.flexGrow = value;
    }
    /**
     * Gets the flex-grow CSS property.
     */
    get flexGrow() {
        return this.style.flexGrow;
    }
    /**
     * Sets the flex-basis CSS property.
     */
    set flexBasis(value) {
        this.style.flexBasis = value;
    }
    /**
     * Gets the flex-basis CSS property.
     */
    get flexBasis() {
        return this.style.flexBasis;
    }
    /**
     * Sets the flex-shrink CSS property.
     */
    set flexShrink(value) {
        this.style.flexShrink = value;
    }
    /**
     * Gets the flex-shrink CSS property.
     */
    get flexShrink() {
        return this.style.flexShrink;
    }
    /**
     * Sets the flex-wrap CSS property.
     */
    set flexWrap(value) {
        this.style.flexWrap = value;
    }
    /**
     * Gets the flex-wrap CSS property.
     */
    get flexWrap() {
        return this.style.flexWrap;
    }
    /**
     * Sets the align-items CSS property.
     */
    set alignItems(value) {
        this.style.alignItems = value;
    }
    /**
     * Gets the align-items CSS property.
     */
    get alignItems() {
        return this.style.alignItems;
    }
    /**
     * Sets the align-self CSS property.
     */
    set alignSelf(value) {
        this.style.alignSelf = value;
    }
    /**
     * Gets the align-self CSS property.
     */
    get alignSelf() {
        return this.style.alignSelf;
    }
    /**
     * Sets the justify-content CSS property.
     */
    set justifyContent(value) {
        this.style.justifyContent = value;
    }
    /**
     * Gets the justify-content CSS property.
     */
    get justifyContent() {
        return this.style.justifyContent;
    }
    /**
     * Sets the justify-self CSS property.
     */
    set justifySelf(value) {
        this.style.justifySelf = value;
    }
    /**
     * Gets the justify-self CSS property.
     */
    get justifySelf() {
        return this.style.justifySelf;
    }
    /*  Backwards Compatibility */
    // we should remove those after we migrate
    /** @ignore */
    set disabled(value) {
        this.enabled = !value;
    }
    /** @ignore */
    get disabled() {
        return !this.enabled;
    }
    /** @ignore */
    set element(value) {
        this._dom = value;
    }
    /** @ignore */
    get element() {
        return this.dom;
    }
    /** @ignore */
    set innerElement(value) {
        this._domContent = value;
    }
    /** @ignore */
    get innerElement() {
        return this._domContent;
    }
};
/**
 * Fired when the Element gets enabled.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('enable', () => {
 *     console.log('Element enabled');
 * });
 * ```
 */
Element$1.EVENT_ENABLE = 'enable';
/**
 * Fired when the Element gets disabled.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('disable', () => {
 *     console.log('Element disabled');
 * });
 * ```
 */
Element$1.EVENT_DISABLE = 'disable';
/**
 * Fired when the Element gets hidden.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('hide', () => {
 *     console.log('Element hidden');
 * });
 * ```
 */
Element$1.EVENT_HIDE = 'hide';
/**
 * Fired when the Element or any of its parent get hidden.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('hideToRoot', () => {
 *     console.log('Element or one of its parents hidden');
 * });
 * ```
 */
Element$1.EVENT_HIDE_TO_ROOT = 'hideToRoot';
/**
 * Fired when the Element stops being hidden.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('show', () => {
 *     console.log('Element shown');
 * });
 * ```
 */
Element$1.EVENT_SHOW = 'show';
/**
 * Fired when the Element and all of its parents become visible.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('showToRoot', () => {
 *     console.log('Element and all of its parents shown');
 * });
 * ```
 */
Element$1.EVENT_SHOW_TO_ROOT = 'showToRoot';
/**
 * Fired when the readOnly property of an Element changes.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('readOnly', (readOnly: boolean) => {
 *     console.log(`Element is now ${readOnly ? 'read only' : 'editable'}`);
 * });
 * ```
 */
Element$1.EVENT_READ_ONLY = 'readOnly';
/**
 * Fired when the Element's parent gets set.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('parent', (parent: Element) => {
 *     console.log(`Element's parent is now ${parent}`);
 * });
 * ```
 */
Element$1.EVENT_PARENT = 'parent';
/**
 * Fired when the mouse is clicked on the Element but only if the Element is enabled. The
 * native DOM MouseEvent is passed as a parameter to the event handler.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('click', (evt: MouseEvent) => {
 *     console.log('Element clicked');
 * });
 * ```
 */
Element$1.EVENT_CLICK = 'click';
/**
 * Fired when the mouse starts hovering on the Element. The native DOM MouseEvent is passed as a
 * parameter to the event handler.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('hover', (evt: MouseEvent) => {
 *     console.log('Element hovered');
 * });
 * ```
 */
Element$1.EVENT_HOVER = 'hover';
/**
 * Fired when the mouse stops hovering on the Element. The native DOM MouseEvent is passed as a
 * parameter to the event handler.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('hoverend', (evt: MouseEvent) => {
 *     console.log('Element hover ended');
 * });
 * ```
 */
Element$1.EVENT_HOVER_END = 'hoverend';
/**
 * Fired after the element has been destroyed. Both the DOM element and the owner Element
 * instance are passed as parameters to the event handler.
 *
 * @event
 * @example
 * ```ts
 * const element = new Element();
 * element.on('destroy', (dom: HTMLElement, element: Element) => {
 *     console.log('Element destroyed');
 * });
 * ```
 */
Element$1.EVENT_DESTROY = 'destroy';
/**
 * Stores Element types by name and default arguments.
 */
Element$1.registry = new Map();

const CLASS_BUTTON = 'pcui-button';
/**
 * User input with click interaction.
 */
class Button extends Element$1 {
    /**
     * Creates a new Button.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        super(Object.assign({ dom: 'button' }, args));
        this._onKeyDown = (evt) => {
            if (evt.key === 'Escape') {
                this.blur();
            }
            else if (evt.key === 'Enter') {
                this._onClick(evt);
            }
        };
        this.class.add(CLASS_BUTTON);
        this._unsafe = args.unsafe;
        this.text = args.text;
        this.size = args.size;
        this.icon = args.icon;
        this.dom.addEventListener('keydown', this._onKeyDown);
    }
    destroy() {
        if (this._destroyed)
            return;
        this.dom.removeEventListener('keydown', this._onKeyDown);
        super.destroy();
    }
    _onClick(evt) {
        this.blur();
        if (this.readOnly)
            return;
        super._onClick(evt);
    }
    focus() {
        this.dom.focus();
    }
    blur() {
        this.dom.blur();
    }
    /**
     * Sets the text of the button.
     */
    set text(value) {
        if (this._text === value)
            return;
        this._text = value;
        if (this._unsafe) {
            this.dom.innerHTML = value;
        }
        else {
            this.dom.textContent = value;
        }
    }
    /**
     * Gets the text of the button.
     */
    get text() {
        return this._text;
    }
    /**
     * Sets the CSS code for an icon for the button. e.g. 'E401' (notice we omit the '\\' character).
     */
    set icon(value) {
        if (this._icon === value || !value.match(/^E\d{0,4}$/))
            return;
        this._icon = value;
        if (value) {
            // set data-icon attribute but first convert the value to a code point
            this.dom.setAttribute('data-icon', String.fromCodePoint(parseInt(value, 16)));
        }
        else {
            this.dom.removeAttribute('data-icon');
        }
    }
    /**
     * Gets the CSS code for an icon for the button.
     */
    get icon() {
        return this._icon;
    }
    /**
     * Sets the 'size' type of the button. Can be null or 'small'.
     */
    set size(value) {
        if (this._size === value)
            return;
        if (this._size) {
            this.class.remove(`pcui-${this._size}`);
            this._size = null;
        }
        this._size = value;
        if (this._size) {
            this.class.add(`pcui-${this._size}`);
        }
    }
    /**
     * Gets the 'size' type of the button.
     */
    get size() {
        return this._size;
    }
}
Element$1.register('button', Button);

const RESIZE_HANDLE_SIZE = 4;
const VALID_RESIZABLE_VALUES = [
    null,
    'top',
    'right',
    'bottom',
    'left'
];
const CLASS_RESIZING = `${CLASS_RESIZABLE}-resizing`;
const CLASS_RESIZABLE_HANDLE = 'pcui-resizable-handle';
const CLASS_CONTAINER = 'pcui-container';
const CLASS_DRAGGED = `${CLASS_CONTAINER}-dragged`;
const CLASS_DRAGGED_CHILD = `${CLASS_DRAGGED}-child`;
/**
 * A container is the basic building block for {@link Element}s that are grouped together. A
 * container can contain any other element including other containers.
 */
class Container extends Element$1 {
    /**
     * Creates a new Container.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a;
        super(args);
        this._scrollable = false;
        this._flex = false;
        this._grid = false;
        this._domResizeHandle = null;
        this._resizePointerId = null;
        this._resizeData = null;
        this._resizeHorizontally = true;
        this._resizeMin = 100;
        this._resizeMax = 300;
        this._draggedStartIndex = -1;
        this._onScroll = (evt) => {
            this.emit('scroll', evt);
        };
        this._onResizeStart = (evt) => {
            if (this._resizePointerId !== null)
                return;
            evt.preventDefault();
            evt.stopPropagation();
            this._domResizeHandle.setPointerCapture(evt.pointerId);
            this._resizePointerId = evt.pointerId;
            this._resizeStart();
        };
        this._onResizeMove = (evt) => {
            if (this._resizePointerId !== evt.pointerId)
                return;
            evt.preventDefault();
            evt.stopPropagation();
            this._resizeMove(evt.clientX, evt.clientY);
        };
        this._onResizeEnd = (evt) => {
            if (this._resizePointerId !== evt.pointerId)
                return;
            evt.preventDefault();
            evt.stopPropagation();
            this._resizeEnd();
            this._domResizeHandle.releasePointerCapture(evt.pointerId);
            this._resizePointerId = null;
        };
        this.class.add(CLASS_CONTAINER);
        this.domContent = this._dom;
        // scroll
        if (args.scrollable) {
            this.scrollable = true;
        }
        // flex
        this.flex = !!args.flex;
        // grid
        let grid = !!args.grid;
        if (grid) {
            if (this.flex) {
                console.error('Invalid Container arguments: "grid" and "flex" cannot both be true.');
                grid = false;
            }
        }
        this.grid = grid;
        // resize related
        this.resizable = (_a = args.resizable) !== null && _a !== void 0 ? _a : null;
        if (args.resizeMin !== undefined) {
            this.resizeMin = args.resizeMin;
        }
        if (args.resizeMax !== undefined) {
            this.resizeMax = args.resizeMax;
        }
    }
    destroy() {
        if (this._destroyed)
            return;
        this.domContent = null;
        if (this._domResizeHandle) {
            this._domResizeHandle.removeEventListener('pointerdown', this._onResizeStart);
            this._domResizeHandle.removeEventListener('pointermove', this._onResizeMove);
            this._domResizeHandle.removeEventListener('pointerup', this._onResizeEnd);
        }
        super.destroy();
    }
    /**
     * Appends an element to the container.
     *
     * @param {Element} element - The element to append.
     */
    append(element) {
        const dom = this._getDomFromElement(element);
        this._domContent.appendChild(dom);
        this._onAppendChild(element);
    }
    /**
     * Appends an element to the container before the specified reference element.
     *
     * @param {Element} element - The element to append.
     * @param {Element} referenceElement - The element before which the element will be appended.
     */
    appendBefore(element, referenceElement) {
        const dom = this._getDomFromElement(element);
        this._domContent.appendChild(dom);
        const referenceDom = referenceElement && this._getDomFromElement(referenceElement);
        this._domContent.insertBefore(dom, referenceDom);
        this._onAppendChild(element);
    }
    /**
     * Appends an element to the container just after the specified reference element.
     *
     * @param {Element} element - The element to append.
     * @param {Element} referenceElement - The element after which the element will be appended.
     */
    appendAfter(element, referenceElement) {
        const dom = this._getDomFromElement(element);
        const referenceDom = referenceElement && this._getDomFromElement(referenceElement);
        const elementBefore = referenceDom ? referenceDom.nextSibling : null;
        if (elementBefore) {
            this._domContent.insertBefore(dom, elementBefore);
        }
        else {
            this._domContent.appendChild(dom);
        }
        this._onAppendChild(element);
    }
    /**
     * Inserts an element in the beginning of the container.
     *
     * @param {Element} element - The element to prepend.
     */
    prepend(element) {
        const dom = this._getDomFromElement(element);
        const first = this._domContent.firstChild;
        if (first) {
            this._domContent.insertBefore(dom, first);
        }
        else {
            this._domContent.appendChild(dom);
        }
        this._onAppendChild(element);
    }
    /**
     * Removes the specified child element from the container.
     *
     * @param element - The element to remove.
     */
    remove(element) {
        if (element.parent !== this)
            return;
        const dom = this._getDomFromElement(element);
        this._domContent.removeChild(dom);
        this._onRemoveChild(element);
    }
    /**
     * Moves the specified child at the specified index.
     *
     * @param element - The element to move.
     * @param index - The index to move the element to.
     */
    move(element, index) {
        const idx = Array.prototype.indexOf.call(this.dom.childNodes, element.dom);
        if (idx === -1) {
            this.appendBefore(element, this.dom.childNodes[index]);
        }
        else if (index !== idx) {
            this.remove(element);
            if (index < idx) {
                this.appendBefore(element, this.dom.childNodes[index]);
            }
            else {
                this.appendAfter(element, this.dom.childNodes[index - 1]);
            }
        }
    }
    /**
     * Clears all children from the container.
     */
    clear() {
        let i = this._domContent.childNodes.length;
        while (i--) {
            const node = this._domContent.childNodes[i];
            if (node.ui && node.ui !== this) {
                node.ui.destroy();
            }
        }
        if (this._domResizeHandle) {
            this._domResizeHandle.removeEventListener('pointerdown', this._onResizeStart);
            this._domResizeHandle.removeEventListener('pointermove', this._onResizeMove);
            this._domResizeHandle.removeEventListener('pointerup', this._onResizeEnd);
            this._domResizeHandle = null;
        }
        this._domContent.innerHTML = '';
        if (this.resizable) {
            this._createResizeHandle();
            this._dom.appendChild(this._domResizeHandle);
        }
    }
    // Used for backwards compatibility with the legacy ui framework
    _getDomFromElement(element) {
        if (element.dom) {
            return element.dom;
        }
        if (element.element) {
            // console.log('Legacy ui.Element passed to Container', this.class, element.class);
            return element.element;
        }
        return element;
    }
    _onAppendChild(element) {
        element.parent = this;
        this.emit('append', element);
    }
    _onRemoveChild(element) {
        element.parent = null;
        this.emit('remove', element);
    }
    _createResizeHandle() {
        const handle = document.createElement('div');
        handle.classList.add(CLASS_RESIZABLE_HANDLE);
        handle.ui = this;
        handle.addEventListener('pointerdown', this._onResizeStart);
        handle.addEventListener('pointermove', this._onResizeMove);
        handle.addEventListener('pointerup', this._onResizeEnd);
        this._domResizeHandle = handle;
    }
    _resizeStart() {
        this.class.add(CLASS_RESIZING);
    }
    _resizeMove(x, y) {
        // if we haven't initialized resizeData do so now
        if (!this._resizeData) {
            this._resizeData = {
                x: x,
                y: y,
                width: this.dom.clientWidth,
                height: this.dom.clientHeight
            };
            return;
        }
        if (this._resizeHorizontally) {
            // horizontal resizing
            let offsetX = this._resizeData.x - x;
            if (this._resizable === 'right') {
                offsetX = -offsetX;
            }
            this.width = RESIZE_HANDLE_SIZE + Math.max(this._resizeMin, Math.min(this._resizeMax, (this._resizeData.width + offsetX)));
        }
        else {
            // vertical resizing
            let offsetY = this._resizeData.y - y;
            if (this._resizable === 'bottom') {
                offsetY = -offsetY;
            }
            this.height = Math.max(this._resizeMin, Math.min(this._resizeMax, (this._resizeData.height + offsetY)));
        }
        this.emit('resize');
    }
    _resizeEnd() {
        this._resizeData = null;
        this.class.remove(CLASS_RESIZING);
    }
    /**
     * Resize the container.
     *
     * @param x - The number of pixels to resize the width.
     * @param y - The number of pixels to resize the height.
     */
    resize(x = 0, y = 0) {
        this._resizeStart();
        this._resizeMove(0, 0);
        this._resizeMove(-x + RESIZE_HANDLE_SIZE, -y);
        this._resizeEnd();
    }
    _getDraggedChildIndex(draggedChild) {
        for (let i = 0; i < this.dom.childNodes.length; i++) {
            if (this.dom.childNodes[i].ui === draggedChild) {
                return i;
            }
        }
        return -1;
    }
    _onChildDragStart(evt, childPanel) {
        this.class.add(CLASS_DRAGGED_CHILD);
        this._draggedStartIndex = this._getDraggedChildIndex(childPanel);
        childPanel.class.add(CLASS_DRAGGED);
        this.emit('child:dragstart', childPanel, this._draggedStartIndex);
    }
    _onChildDragMove(evt, childPanel) {
        const rect = this.dom.getBoundingClientRect();
        const dragOut = (evt.clientX < rect.left || evt.clientX > rect.right || evt.clientY < rect.top || evt.clientY > rect.bottom);
        const childPanelIndex = this._getDraggedChildIndex(childPanel);
        if (dragOut) {
            childPanel.class.remove(CLASS_DRAGGED);
            if (this._draggedStartIndex !== childPanelIndex) {
                this.remove(childPanel);
                if (this._draggedStartIndex < childPanelIndex) {
                    this.appendBefore(childPanel, this.dom.childNodes[this._draggedStartIndex]);
                }
                else {
                    this.appendAfter(childPanel, this.dom.childNodes[this._draggedStartIndex - 1]);
                }
            }
            return;
        }
        childPanel.class.add(CLASS_DRAGGED);
        const y = evt.clientY - rect.top;
        let ind = null;
        // hovered script
        for (let i = 0; i < this.dom.childNodes.length; i++) {
            const otherPanel = this.dom.childNodes[i].ui;
            const otherTop = otherPanel.dom.offsetTop;
            if (i < childPanelIndex) {
                if (y <= otherTop + otherPanel.header.height) {
                    ind = i;
                    break;
                }
            }
            else if (i > childPanelIndex) {
                if (y + childPanel.height >= otherTop + otherPanel.height) {
                    ind = i;
                    break;
                }
            }
        }
        if (ind !== null && childPanelIndex !== ind) {
            this.remove(childPanel);
            if (ind < childPanelIndex) {
                this.appendBefore(childPanel, this.dom.childNodes[ind]);
            }
            else {
                this.appendAfter(childPanel, this.dom.childNodes[ind - 1]);
            }
        }
    }
    _onChildDragEnd(evt, childPanel) {
        this.class.remove(CLASS_DRAGGED_CHILD);
        childPanel.class.remove(CLASS_DRAGGED);
        const index = this._getDraggedChildIndex(childPanel);
        this.emit('child:dragend', childPanel, index, this._draggedStartIndex);
        this._draggedStartIndex = -1;
    }
    /**
     * Iterate over each child element using the supplied function. To early out of the iteration,
     * return `false` from the function.
     *
     * @param fn - The function to call for each child element.
     */
    forEachChild(fn) {
        for (let i = 0; i < this.dom.childNodes.length; i++) {
            const node = this.dom.childNodes[i].ui;
            if (node) {
                const result = fn(node, i);
                if (result === false) {
                    // early out
                    break;
                }
            }
        }
    }
    /**
     * If the current node contains a root, recursively append its children to this node
     * and return it. Otherwise return the current node. Also add each child to the parent
     * under its keyed name.
     *
     * @param node - The current element in the dom structure which must be recursively
     * traversed and appended to its parent.
     * @param node.root - The root node of the dom structure.
     * @param node.children - The children of the root node.
     * @returns The recursively appended element node.
     */
    _buildDomNode(node) {
        const keys = Object.keys(node);
        let rootNode;
        if (keys.includes('root')) {
            rootNode = this._buildDomNode(node.root);
            node.children.forEach((childNode) => {
                const childNodeElement = this._buildDomNode(childNode);
                if (childNodeElement !== null) {
                    rootNode.append(childNodeElement);
                }
            });
        }
        else {
            rootNode = node[keys[0]];
            // @ts-ignore
            this[`_${keys[0]}`] = rootNode;
        }
        return rootNode;
    }
    /**
     * Takes an array of pcui elements, each of which can contain their own child elements, and
     * appends them to this container. These child elements are traversed recursively using
     * _buildDomNode.
     *
     * @param dom - An array of child pcui elements to append to this container.
     *
     * @example
     * buildDom([
     *     {
     *         child1: pcui.Label()
     *     },
     *     {
     *         root: {
     *             container1: pcui.Container()
     *         },
     *         children: [
     *             {
     *                 child2: pcui.Label()
     *             },
     *             {
     *                 child3: pcui.Label()
     *             }
     *         ]
     *     }
     * ]);
     */
    buildDom(dom) {
        dom.forEach((node) => {
            const builtNode = this._buildDomNode(node);
            this.append(builtNode);
        });
    }
    /**
     * Sets whether the Element supports flex layout.
     */
    set flex(value) {
        if (value === this._flex)
            return;
        this._flex = value;
        if (value) {
            this.class.add(CLASS_FLEX);
        }
        else {
            this.class.remove(CLASS_FLEX);
        }
    }
    /**
     * Gets whether the Element supports flex layout.
     */
    get flex() {
        return this._flex;
    }
    /**
     * Sets whether the Element supports the grid layout.
     */
    set grid(value) {
        if (value === this._grid)
            return;
        this._grid = value;
        if (value) {
            this.class.add(CLASS_GRID);
        }
        else {
            this.class.remove(CLASS_GRID);
        }
    }
    /**
     * Gets whether the Element supports the grid layout.
     */
    get grid() {
        return this._grid;
    }
    /**
     * Sets whether the Element should be scrollable.
     */
    set scrollable(value) {
        if (this._scrollable === value)
            return;
        this._scrollable = value;
        if (value) {
            this.class.add(CLASS_SCROLLABLE);
        }
        else {
            this.class.remove(CLASS_SCROLLABLE);
        }
    }
    /**
     * Gets whether the Element should be scrollable.
     */
    get scrollable() {
        return this._scrollable;
    }
    /**
     * Sets whether the Element is resizable and where the resize handle is located. Can be one of
     * 'top', 'bottom', 'right', 'left'. Set to null to disable resizing.
     */
    set resizable(value) {
        if (value === this._resizable)
            return;
        if (VALID_RESIZABLE_VALUES.indexOf(value) === -1) {
            console.error(`Invalid resizable value: must be one of ${VALID_RESIZABLE_VALUES.join(',')}`);
            return;
        }
        // remove old class
        if (this._resizable) {
            this.class.remove(`${CLASS_RESIZABLE}-${this._resizable}`);
        }
        this._resizable = value;
        this._resizeHorizontally = (value === 'right' || value === 'left');
        if (value) {
            // add resize class and create / append resize handle
            this.class.add(CLASS_RESIZABLE);
            this.class.add(`${CLASS_RESIZABLE}-${value}`);
            if (!this._domResizeHandle) {
                this._createResizeHandle();
            }
            this._dom.appendChild(this._domResizeHandle);
        }
        else {
            // remove resize class and resize handle
            this.class.remove(CLASS_RESIZABLE);
            if (this._domResizeHandle) {
                this._dom.removeChild(this._domResizeHandle);
            }
        }
    }
    /**
     * Gets whether the Element is resizable and where the resize handle is located.
     */
    get resizable() {
        return this._resizable;
    }
    /**
     * Sets the minimum size the Element can take when resized in pixels.
     */
    set resizeMin(value) {
        this._resizeMin = Math.max(0, Math.min(value, this._resizeMax));
    }
    /**
     * Gets the minimum size the Element can take when resized in pixels.
     */
    get resizeMin() {
        return this._resizeMin;
    }
    /**
     * Sets the maximum size the Element can take when resized in pixels.
     */
    set resizeMax(value) {
        this._resizeMax = Math.max(this._resizeMin, value);
    }
    /**
     * Gets the maximum size the Element can take when resized in pixels.
     */
    get resizeMax() {
        return this._resizeMax;
    }
    /**
     * Sets the internal DOM element used as a the container of all children. Can be overridden by
     * derived classes.
     */
    set domContent(value) {
        if (this._domContent === value)
            return;
        if (this._domContent) {
            this._domContent.removeEventListener('scroll', this._onScroll);
        }
        this._domContent = value;
        if (this._domContent) {
            this._domContent.addEventListener('scroll', this._onScroll);
        }
    }
    /**
     * Gets the internal DOM element used as a the container of all children.
     */
    get domContent() {
        return this._domContent;
    }
}
/**
 * Fired when a child Element gets added to the Container.
 *
 * @event
 * @example
 * ```ts
 * const container = new Container();
 * container.on('append', (element: Element) => {
 *     console.log('Element added to container:', element);
 * });
 * ```
 */
Container.EVENT_APPEND = 'append';
/**
 * Fired when a child Element gets removed from the Container.
 *
 * @event
 * @example
 * ```ts
 * const container = new Container();
 * container.on('remove', (element: Element) => {
 *     console.log('Element removed from container:', element);
 * });
 * ```
 */
Container.EVENT_REMOVE = 'remove';
/**
 * Fired when the container is scrolled. The native DOM scroll event is passed to the event handler.
 *
 * @event
 * @example
 * ```ts
 * const container = new Container();
 * container.on('scroll', (event: Event) => {
 *     console.log('Container scrolled:', event);
 * });
 * ```
 */
Container.EVENT_SCROLL = 'scroll';
/**
 * Fired when the container gets resized using the resize handle.
 *
 * @event
 * @example
 * ```ts
 * const container = new Container();
 * container.on('resize', () => {
 *     console.log('Container resized to:', container.width, container.height, 'px');
 * });
 * ```
 */
Container.EVENT_RESIZE = 'resize';
Element$1.register('container', Container);

const CLASS_INPUT_ELEMENT = 'pcui-input-element';
/**
 * The InputElement is an abstract class that manages an input DOM element. It is the superclass of
 * {@link TextInput} and {@link NumericInput}. It is not intended to be used directly.
 */
class InputElement extends Element$1 {
    constructor(args = {}) {
        var _a, _b, _c, _d, _e;
        super(args);
        /**
         * Determines whether the input should blur when the enter key is pressed.
         */
        this.blurOnEnter = true;
        /**
         * Determines whether the input should blur when the escape key is pressed.
         */
        this.blurOnEscape = true;
        this._onInputFocus = (evt) => {
            this.class.add(CLASS_FOCUS);
            this.emit('focus', evt);
            this._prevValue = this._domInput.value;
        };
        this._onInputBlur = (evt) => {
            this.class.remove(CLASS_FOCUS);
            this.emit('blur', evt);
        };
        this._onInputKeyUp = (evt) => {
            if (evt.key !== 'Escape') {
                this._onInputChange(evt);
            }
            this.emit('keyup', evt);
        };
        this._onInputCtxMenu = (evt) => {
            this._domInput.select();
        };
        this._updateInputReadOnly = () => {
            const readOnly = !this.enabled || this.readOnly;
            if (readOnly) {
                this._domInput.setAttribute('readonly', 'true');
            }
            else {
                this._domInput.removeAttribute('readonly');
            }
        };
        this.class.add(CLASS_INPUT_ELEMENT);
        let input = args.input;
        if (!input) {
            input = document.createElement('input');
            input.type = 'text';
        }
        input.ui = this;
        input.tabIndex = 0;
        input.autocomplete = 'off';
        this._onInputKeyDownEvt = this._onInputKeyDown.bind(this);
        this._onInputChangeEvt = this._onInputChange.bind(this);
        input.addEventListener('change', this._onInputChangeEvt);
        input.addEventListener('keydown', this._onInputKeyDownEvt);
        input.addEventListener('focus', this._onInputFocus);
        input.addEventListener('blur', this._onInputBlur);
        input.addEventListener('contextmenu', this._onInputCtxMenu, false);
        this.dom.appendChild(input);
        this._domInput = input;
        this._suspendInputChangeEvt = false;
        if (args.value !== undefined) {
            this._domInput.value = args.value;
        }
        this.placeholder = (_a = args.placeholder) !== null && _a !== void 0 ? _a : '';
        this.renderChanges = (_b = args.renderChanges) !== null && _b !== void 0 ? _b : false;
        this.blurOnEnter = (_c = args.blurOnEnter) !== null && _c !== void 0 ? _c : true;
        this.blurOnEscape = (_d = args.blurOnEscape) !== null && _d !== void 0 ? _d : true;
        this.keyChange = (_e = args.keyChange) !== null && _e !== void 0 ? _e : false;
        this._prevValue = null;
        this.on('change', () => {
            if (this.renderChanges) {
                this.flash();
            }
        });
        this.on('disable', this._updateInputReadOnly);
        this.on('enable', this._updateInputReadOnly);
        this.on('readOnly', this._updateInputReadOnly);
        this._updateInputReadOnly();
    }
    destroy() {
        if (this._destroyed)
            return;
        const input = this._domInput;
        input.removeEventListener('change', this._onInputChangeEvt);
        input.removeEventListener('keydown', this._onInputKeyDownEvt);
        input.removeEventListener('focus', this._onInputFocus);
        input.removeEventListener('blur', this._onInputBlur);
        input.removeEventListener('keyup', this._onInputKeyUp);
        input.removeEventListener('contextmenu', this._onInputCtxMenu);
        super.destroy();
    }
    _onInputKeyDown(evt) {
        if (evt.key === 'Enter' && this.blurOnEnter) {
            // do not fire input change event on blur
            // if keyChange is true (because a change event)
            // will have already been fired before for the current
            // value
            this._suspendInputChangeEvt = this.keyChange;
            this._domInput.blur();
            this._suspendInputChangeEvt = false;
        }
        else if (evt.key === 'Escape') {
            this._suspendInputChangeEvt = true;
            const prev = this._domInput.value;
            this._domInput.value = this._prevValue;
            this._suspendInputChangeEvt = false;
            // manually fire change event
            if (this.keyChange && prev !== this._prevValue) {
                this._onInputChange(evt);
            }
            if (this.blurOnEscape) {
                this._domInput.blur();
            }
        }
        this.emit('keydown', evt);
    }
    _onInputChange(evt) { }
    focus(select) {
        this._domInput.focus();
        if (select) {
            this._domInput.select();
        }
    }
    blur() {
        this._domInput.blur();
    }
    set placeholder(value) {
        if (value) {
            this.dom.setAttribute('placeholder', value);
        }
        else {
            this.dom.removeAttribute('placeholder');
        }
    }
    get placeholder() {
        var _a;
        return (_a = this.dom.getAttribute('placeholder')) !== null && _a !== void 0 ? _a : '';
    }
    /**
     * Sets the method to call when keyup is called on the input DOM element.
     */
    set keyChange(value) {
        if (this._keyChange === value)
            return;
        this._keyChange = value;
        if (value) {
            this._domInput.addEventListener('keyup', this._onInputKeyUp);
        }
        else {
            this._domInput.removeEventListener('keyup', this._onInputKeyUp);
        }
    }
    /**
     * Gets the method to call when keyup is called on the input DOM element.
     */
    get keyChange() {
        return this._keyChange;
    }
    /**
     * Gets the input DOM element.
     */
    get input() {
        return this._domInput;
    }
    set renderChanges(value) {
        this._renderChanges = value;
    }
    get renderChanges() {
        return this._renderChanges;
    }
}

const CLASS_NUMERIC_INPUT = 'pcui-numeric-input';
const CLASS_NUMERIC_INPUT_SLIDER_CONTROL = `${CLASS_NUMERIC_INPUT}-slider-control`;
const CLASS_NUMERIC_INPUT_SLIDER_CONTROL_ACTIVE = `${CLASS_NUMERIC_INPUT_SLIDER_CONTROL}-active`;
const CLASS_NUMERIC_INPUT_SLIDER_CONTROL_HIDDEN = `${CLASS_NUMERIC_INPUT_SLIDER_CONTROL}-hidden`;
const REGEX_COMMA = /,/g;
/**
 * The NumericInput represents an input element that holds numbers.
 *
 * NumericInput accepts `number` values. It also accepts strings that contain mathematical
 * expressions which are then evaluated to a number. Here are some examples:
 *
 * | Input String       | Evaluated Number Value |
 * | ------------------ | ---------------------- |
 * | `"10 + 20"`        | `30`                   |
 * | `"10 - 20"`        | `-10`                  |
 * | `"10 * 20"`        | `200`                  |
 * | `"10 / 20"`        | `0.5`                  |
 * | `"10 * (20 + 30)"` | `500`                  |
 *
 * By default, a NumericInput displays a slider input that can be used to quickly change the value
 * via a click and drag. The slider can be disabled by setting the `hideSlider` argument to `true`.
 */
class NumericInput extends InputElement {
    /**
     * Creates a new NumericInput.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b, _c, _d, _e;
        const textInputArgs = Object.assign({}, args);
        // delete value because we want to set it after the other arguments
        delete textInputArgs.value;
        delete textInputArgs.renderChanges;
        super(textInputArgs);
        this._sliderUsed = false;
        this._onSliderMouseWheel = (evt) => {
            this._updatePosition(evt.deltaY, evt.shiftKey);
        };
        this._onSliderMouseMove = (evt) => {
            this._updatePosition(evt.movementX, evt.shiftKey);
        };
        this._onSliderMouseDown = (evt) => {
            this._sliderControl.dom.requestPointerLock();
            this._sliderMovement = 0.0;
            this._sliderPrevValue = this.value;
            this._sliderUsed = true;
            if (this.binding) {
                this._historyCombine = this.binding.historyCombine;
                this._historyPostfix = this.binding.historyPostfix;
                this.binding.historyCombine = true;
                this.binding.historyPostfix = `(${Date.now()})`;
            }
            this.emit('slider:mousedown', evt);
        };
        this._onSliderMouseUp = () => {
            document.exitPointerLock();
            if (!this._sliderUsed)
                return;
            this._sliderUsed = false;
            this.value = this._sliderPrevValue + this._sliderMovement;
            if (this.binding) {
                this.binding.historyCombine = this._historyCombine;
                this.binding.historyPostfix = this._historyPostfix;
                this._historyCombine = false;
                this._historyPostfix = null;
            }
            this.focus();
            this.emit('slider:mouseup');
        };
        this._onPointerLockChange = () => {
            if (this._isScrolling()) {
                this._sliderControl.dom.addEventListener('mousemove', this._onSliderMouseMove, false);
                this._sliderControl.dom.addEventListener('wheel', this._onSliderMouseWheel, { passive: true });
                this._sliderControl.class.add(CLASS_NUMERIC_INPUT_SLIDER_CONTROL_ACTIVE);
            }
            else {
                this._sliderControl.dom.removeEventListener('mousemove', this._onSliderMouseMove, false);
                this._sliderControl.dom.removeEventListener('wheel', this._onSliderMouseWheel);
                this._sliderControl.class.remove(CLASS_NUMERIC_INPUT_SLIDER_CONTROL_ACTIVE);
            }
        };
        this.class.add(CLASS_NUMERIC_INPUT);
        this._min = (_a = args.min) !== null && _a !== void 0 ? _a : null;
        this._max = (_b = args.max) !== null && _b !== void 0 ? _b : null;
        this._allowNull = (_c = args.allowNull) !== null && _c !== void 0 ? _c : false;
        this._precision = (_d = args.precision) !== null && _d !== void 0 ? _d : 7;
        if (Number.isFinite(args.step)) {
            this._step = args.step;
        }
        else if (args.precision) {
            this._step = 10 / Math.pow(10, args.precision);
        }
        else {
            this._step = 1;
        }
        if (Number.isFinite(args.stepPrecision)) {
            this._stepPrecision = args.stepPrecision;
        }
        else {
            this._stepPrecision = this._step * 0.1;
        }
        this._oldValue = undefined;
        if (Number.isFinite(args.value)) {
            this.value = args.value;
        }
        else if (!this._allowNull) {
            this.value = 0;
        }
        this._historyCombine = false;
        this._historyPostfix = null;
        this._sliderPrevValue = 0;
        this.renderChanges = (_e = args.renderChanges) !== null && _e !== void 0 ? _e : false;
        if (!args.hideSlider) {
            this._sliderControl = new Element$1();
            this._sliderControl.class.add(CLASS_NUMERIC_INPUT_SLIDER_CONTROL);
            this.dom.append(this._sliderControl.dom);
            this._sliderControl.dom.addEventListener('mousedown', this._onSliderMouseDown);
            this._sliderControl.dom.addEventListener('mouseup', this._onSliderMouseUp);
            document.addEventListener('pointerlockchange', this._onPointerLockChange, false);
        }
    }
    destroy() {
        if (this.destroyed)
            return;
        if (this._sliderControl) {
            this._sliderControl.dom.removeEventListener('mousedown', this._onSliderMouseDown);
            this._sliderControl.dom.removeEventListener('mouseup', this._onSliderMouseUp);
            this._sliderControl.dom.removeEventListener('mousemove', this._onSliderMouseMove, false);
            this._sliderControl.dom.removeEventListener('wheel', this._onSliderMouseWheel);
            document.removeEventListener('pointerlockchange', this._onPointerLockChange, false);
        }
        super.destroy();
    }
    _updatePosition(movement, shiftKey) {
        // move one step or stepPrecision every 100 pixels
        this._sliderMovement += movement / 100 * (shiftKey ? this._stepPrecision : this._step);
        this.value = this._sliderPrevValue + this._sliderMovement;
    }
    _onInputChange(evt) {
        // get the content of the input, normalize it and set it as the current value
        this.value = this._normalizeValue(this._domInput.value);
    }
    _onInputKeyDown(evt) {
        if (!this.enabled || this.readOnly)
            return;
        // increase / decrease value with arrow keys
        if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown') {
            const inc = evt.key === 'ArrowDown' ? -1 : 1;
            this.value += (evt.shiftKey ? this._stepPrecision : this._step) * inc;
        }
        super._onInputKeyDown(evt);
    }
    _getPointerLockElementByShadowRoot(pointerLockElement) {
        const shadowRoot = pointerLockElement.shadowRoot;
        if (shadowRoot) {
            const pointerLockElement = shadowRoot.pointerLockElement;
            return this._getPointerLockElementByShadowRoot(pointerLockElement);
        }
        return pointerLockElement === this._sliderControl.dom;
    }
    _isScrolling() {
        if (!this._sliderControl)
            return false;
        if (document.pointerLockElement && document.pointerLockElement.shadowRoot) {
            return this._getPointerLockElementByShadowRoot(document.pointerLockElement);
        }
        return document.pointerLockElement === this._sliderControl.dom;
    }
    _normalizeValue(value) {
        try {
            if (typeof value === 'string') {
                // check for 0
                if (value === '0')
                    return 0;
                // replace commas with dots (for some international keyboards)
                value = value.replace(REGEX_COMMA, '.');
                // remove spaces
                value = value.replace(/\s/g, '');
                // sanitize input to only allow short mathematical expressions
                value = value.match(/^[*/+\-0-9().]+$/);
                if (value !== null && value[0].length < 20) {
                    let expression = value[0];
                    const operators = ['+', '-', '/', '*'];
                    operators.forEach((operator) => {
                        const expressionArr = expression.split(operator);
                        expressionArr.forEach((_, i) => {
                            expressionArr[i] = expressionArr[i].replace(/^0+/, '');
                        });
                        expression = expressionArr.join(operator);
                    });
                    // eslint-disable-next-line no-new-func
                    value = Function(`"use strict";return (${expression})`)();
                }
            }
            if (value === null || value === undefined || value === '') {
                if (this._allowNull) {
                    return null;
                }
                value = 0;
            }
            value = Number(value);
            if (isNaN(value)) {
                if (this._allowNull) {
                    return null;
                }
                value = 0;
            }
            // clamp between min max
            if (this.min !== null && value < this.min) {
                value = this.min;
            }
            if (this.max !== null && value > this.max) {
                value = this.max;
            }
            // fix precision
            if (this.precision !== null) {
                value = parseFloat(Number(value).toFixed(this.precision));
            }
            return value;
        }
        catch (error) {
            if (this._allowNull) {
                return null;
            }
            return 0;
        }
    }
    _updateValue(value, force) {
        const different = (value !== this._oldValue || force);
        // always set the value to the input because
        // we always want it to show an actual number or nothing
        this._oldValue = value;
        if (value === null) {
            this._domInput.value = '';
        }
        else {
            this._domInput.value = String(value);
        }
        this.class.remove(CLASS_MULTIPLE_VALUES);
        if (different) {
            this.emit('change', value);
        }
        return different;
    }
    set value(value) {
        value = this._normalizeValue(value);
        const forceUpdate = this.class.contains(CLASS_MULTIPLE_VALUES) && value === null && this._allowNull;
        const changed = this._updateValue(value, forceUpdate);
        if (changed && this.binding) {
            this.binding.setValue(value);
        }
        if (this._sliderControl) {
            this._sliderControl.class.remove(CLASS_NUMERIC_INPUT_SLIDER_CONTROL_HIDDEN);
        }
    }
    get value() {
        const val = this._domInput.value;
        return val !== '' ? parseFloat(val) : null;
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        const normalizedValues = values.map(v => this._normalizeValue(v));
        const different = normalizedValues.some(v => v !== normalizedValues[0]);
        if (different) {
            this._updateValue(null);
            this.class.add(CLASS_MULTIPLE_VALUES);
            if (this._sliderControl) {
                this._sliderControl.class.add(CLASS_NUMERIC_INPUT_SLIDER_CONTROL_HIDDEN);
            }
        }
        else {
            this._updateValue(normalizedValues[0]);
            if (this._sliderControl) {
                this._sliderControl.class.remove(CLASS_NUMERIC_INPUT_SLIDER_CONTROL_HIDDEN);
            }
        }
    }
    /**
     * Sets the minimum value this field can take.
     */
    set min(value) {
        if (this._min === value)
            return;
        this._min = value;
        // reset value
        if (this._min !== null) {
            this.value = this.value; // eslint-disable-line no-self-assign
        }
    }
    /**
     * Gets the minimum value this field can take.
     */
    get min() {
        return this._min;
    }
    /**
     * Sets the maximum value this field can take.
     */
    set max(value) {
        if (this._max === value)
            return;
        this._max = value;
        // reset value
        if (this._max !== null) {
            this.value = this.value; // eslint-disable-line no-self-assign
        }
    }
    /**
     * Gets the maximum value this field can take.
     */
    get max() {
        return this._max;
    }
    /**
     * Sets the precision of the input.
     */
    set precision(value) {
        if (this._precision === value)
            return;
        this._precision = value;
        // reset value
        if (this._precision !== null) {
            this.value = this.value; // eslint-disable-line no-self-assign
        }
    }
    /**
     * Gets the precision of the input.
     */
    get precision() {
        return this._precision;
    }
    /**
     * Sets the amount that the value will be increased or decreased when using the arrow keys and the slider input.
     */
    set step(value) {
        this._step = value;
    }
    /**
     * Gets the amount that the value will be increased or decreased when using the arrow keys and the slider input.
     */
    get step() {
        return this._step;
    }
}
Element$1.register('number', NumericInput, { renderChanges: true });

const CLASS_LABEL = 'pcui-label';
/**
 * The Label is a simple span element that displays some text.
 */
class Label extends Element$1 {
    /**
     * Creates a new Label.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b, _c, _d;
        super(Object.assign({ dom: 'span' }, args));
        this.class.add(CLASS_LABEL);
        this._unsafe = (_a = args.unsafe) !== null && _a !== void 0 ? _a : false;
        this.text = (_c = (_b = args.text) !== null && _b !== void 0 ? _b : args.value) !== null && _c !== void 0 ? _c : '';
        if (args.allowTextSelection) {
            this.class.add(CLASS_DEFAULT_MOUSEDOWN);
        }
        if (args.nativeTooltip) {
            this.dom.title = this.text;
        }
        this.placeholder = args.placeholder;
        this.renderChanges = (_d = args.renderChanges) !== null && _d !== void 0 ? _d : false;
        this.on('change', () => {
            if (this.renderChanges) {
                this.flash();
            }
        });
    }
    _updateText(value) {
        this.class.remove(CLASS_MULTIPLE_VALUES);
        if (this._text === value)
            return false;
        this._text = value;
        if (this._unsafe) {
            this._dom.innerHTML = value;
        }
        else {
            this._dom.textContent = value;
        }
        this.emit('change', value);
        return true;
    }
    /**
     * Sets the text of the Label.
     */
    set text(value) {
        if (value === undefined || value === null) {
            value = '';
        }
        const changed = this._updateText(value);
        if (changed && this._binding) {
            this._binding.setValue(value);
        }
    }
    /**
     * Gets the text of the Label.
     */
    get text() {
        return this._text;
    }
    set value(value) {
        this.text = value;
    }
    get value() {
        return this.text;
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        const different = values.some(v => v !== values[0]);
        if (different) {
            this._updateText('');
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            this._updateText(values[0]);
        }
    }
    set placeholder(value) {
        if (value) {
            this.dom.setAttribute('placeholder', value);
        }
        else {
            this.dom.removeAttribute('placeholder');
        }
    }
    get placeholder() {
        return this.dom.getAttribute('placeholder');
    }
    set renderChanges(value) {
        this._renderChanges = value;
    }
    get renderChanges() {
        return this._renderChanges;
    }
}
Element$1.register('label', Label);

const CLASS_PANEL = 'pcui-panel';
const CLASS_PANEL_HEADER = `${CLASS_PANEL}-header`;
const CLASS_PANEL_HEADER_TITLE = `${CLASS_PANEL_HEADER}-title`;
const CLASS_PANEL_CONTENT = `${CLASS_PANEL}-content`;
const CLASS_PANEL_HORIZONTAL = `${CLASS_PANEL}-horizontal`;
const CLASS_PANEL_SORTABLE_ICON = `${CLASS_PANEL}-sortable-icon`;
const CLASS_PANEL_REMOVE = `${CLASS_PANEL}-remove`;
/**
 * The Panel is a {@link Container} that itself contains a header container and a content
 * container. The respective Container functions work using the content container. One can also
 * append elements to the header of the Panel.
 */
class Panel extends Container {
    /**
     * Creates a new Panel.
     *
     * @param args - The arguments. Extends the Container constructor arguments. All settable
     * properties can also be set through the constructor.
     */
    constructor(args = {}) {
        var _a, _b, _c, _d, _e;
        const containerArgs = Object.assign(Object.assign({}, args), { flex: true });
        delete containerArgs.grid;
        delete containerArgs.flexDirection;
        delete containerArgs.scrollable;
        super(containerArgs);
        this._reflowTimeout = null;
        this._widthBeforeCollapse = null;
        this._heightBeforeCollapse = null;
        this._iconSort = null;
        this._btnRemove = null;
        this._onHeaderClick = (evt) => {
            if (!this._collapsible)
                return;
            if (evt.target !== this.header.dom && evt.target !== this._labelTitle.dom)
                return;
            // toggle collapsed
            this.collapsed = !this.collapsed;
        };
        this._onDragStart = (evt) => {
            if (!this.enabled || this.readOnly)
                return;
            evt.stopPropagation();
            evt.preventDefault();
            window.addEventListener('mouseup', this._onDragEndEvt);
            window.addEventListener('mouseleave', this._onDragEndEvt);
            window.addEventListener('mousemove', this._onDragMove);
            this.emit('dragstart');
            // @ts-ignore accessing protected methods
            if (this.parent && this.parent._onChildDragStart) {
                // @ts-ignore accessing protected methods
                this.parent._onChildDragStart(evt, this);
            }
        };
        this._onDragMove = (evt) => {
            this.emit('dragmove');
            // @ts-ignore accessing protected methods
            if (this.parent && this.parent._onChildDragStart) {
                // @ts-ignore accessing protected methods
                this.parent._onChildDragMove(evt, this);
            }
        };
        this.class.add(CLASS_PANEL);
        if (args.panelType) {
            this.class.add(`${CLASS_PANEL}-${args.panelType}`);
        }
        // do not call reflow on every update while
        // we are initializing
        this._suspendReflow = true;
        // initialize header container
        this._initializeHeader(args);
        // initialize content container
        this._initializeContent(args);
        // header size
        this.headerSize = (_a = args.headerSize) !== null && _a !== void 0 ? _a : 32;
        // collapse related
        this.collapsible = (_b = args.collapsible) !== null && _b !== void 0 ? _b : false;
        this.collapsed = (_c = args.collapsed) !== null && _c !== void 0 ? _c : false;
        this.collapseHorizontally = (_d = args.collapseHorizontally) !== null && _d !== void 0 ? _d : false;
        this.sortable = (_e = args.sortable) !== null && _e !== void 0 ? _e : false;
        this.removable = args.removable || !!args.onRemove || false;
        // Set the contents container to be the content DOM element. From now on, calling append
        // functions on the panel will append the elements to the contents container.
        this.domContent = this._containerContent.dom;
        // execute reflow now after all fields have been initialized
        this._suspendReflow = false;
        this._reflow();
        this._onDragEndEvt = this._onDragEnd.bind(this);
    }
    destroy() {
        if (this._destroyed)
            return;
        if (this._reflowTimeout) {
            cancelAnimationFrame(this._reflowTimeout);
            this._reflowTimeout = null;
        }
        window.removeEventListener('mouseup', this._onDragEndEvt);
        window.removeEventListener('mouseleave', this._onDragEndEvt);
        window.removeEventListener('mousemove', this._onDragMove);
        super.destroy();
    }
    _initializeHeader(args) {
        // header container
        this._containerHeader = new Container({
            flex: true,
            flexDirection: 'row',
            class: [CLASS_PANEL_HEADER, CLASS_FONT_BOLD]
        });
        // header title
        this._labelTitle = new Label({
            text: args.headerText,
            class: [CLASS_PANEL_HEADER_TITLE, CLASS_FONT_BOLD]
        });
        this._containerHeader.append(this._labelTitle);
        // use native click listener because the Element#click event is only fired if the element
        // is enabled. However we still want to catch header click events in order to collapse them
        this._containerHeader.dom.addEventListener('click', this._onHeaderClick);
        this.append(this._containerHeader);
    }
    _onClickRemove(evt) {
        evt.preventDefault();
        evt.stopPropagation();
        this.emit('click:remove');
    }
    _initializeContent(args) {
        // containers container
        this._containerContent = new Container({
            class: CLASS_PANEL_CONTENT,
            grid: args.grid,
            flex: args.flex,
            flexDirection: args.flexDirection,
            scrollable: args.scrollable,
            dom: args.content
        });
        this.append(this._containerContent);
    }
    // Collapses or expands the panel as needed
    _reflow() {
        if (this._suspendReflow) {
            return;
        }
        if (this._reflowTimeout) {
            cancelAnimationFrame(this._reflowTimeout);
            this._reflowTimeout = null;
        }
        if (this.hidden || !this.collapsible)
            return;
        if (this.collapsed && this.collapseHorizontally) {
            this._containerHeader.style.top = `${-this.headerSize}px`;
        }
        else {
            this._containerHeader.style.top = '';
        }
        // we rely on the content width / height and we have to
        // wait for 1 frame before we can get the final values back
        this._reflowTimeout = requestAnimationFrame(() => {
            this._reflowTimeout = null;
            if (this.collapsed) {
                // remember size before collapse
                if (!this._widthBeforeCollapse) {
                    this._widthBeforeCollapse = this.style.width;
                }
                if (!this._heightBeforeCollapse) {
                    this._heightBeforeCollapse = this.style.height;
                }
                if (this._collapseHorizontally) {
                    this.height = '';
                    this.width = this.headerSize;
                }
                else {
                    this.height = this.headerSize;
                }
                // add collapsed class after getting the width and height
                // because if we add it before then because of overflow:hidden
                // we might get inaccurate width/heights.
                this.class.add(CLASS_COLLAPSED);
            }
            else {
                // remove collapsed class first and the restore width and height
                // (opposite order of collapsing)
                this.class.remove(CLASS_COLLAPSED);
                if (this._collapseHorizontally) {
                    this.height = '';
                    if (this._widthBeforeCollapse !== null) {
                        this.width = this._widthBeforeCollapse;
                    }
                }
                else {
                    if (this._heightBeforeCollapse !== null) {
                        this.height = this._heightBeforeCollapse;
                    }
                }
                // reset before collapse vars
                this._widthBeforeCollapse = null;
                this._heightBeforeCollapse = null;
            }
        });
    }
    _onDragEnd(evt) {
        window.removeEventListener('mouseup', this._onDragEndEvt);
        window.removeEventListener('mouseleave', this._onDragEndEvt);
        window.removeEventListener('mousemove', this._onDragMove);
        if (this._draggedChild === this) {
            this._draggedChild = null;
        }
        this.emit('dragend');
        // @ts-ignore accessing protected methods
        if (this.parent && this.parent._onChildDragStart) {
            // @ts-ignore accessing protected methods
            this.parent._onChildDragEnd(evt, this);
        }
    }
    /**
     * Sets whether the Element is collapsible.
     */
    set collapsible(value) {
        if (value === this._collapsible)
            return;
        this._collapsible = value;
        if (value) {
            this.class.add(CLASS_COLLAPSIBLE);
        }
        else {
            this.class.remove(CLASS_COLLAPSIBLE);
        }
        this._reflow();
        if (this.collapsed) {
            this.emit(value ? 'collapse' : 'expand');
        }
    }
    /**
     * Gets whether the Element is collapsible.
     */
    get collapsible() {
        return this._collapsible;
    }
    /**
     * Sets whether the Element should be collapsed.
     */
    set collapsed(value) {
        if (this._collapsed === value)
            return;
        this._collapsed = value;
        this._reflow();
        if (this.collapsible) {
            this.emit(value ? 'collapse' : 'expand');
        }
    }
    /**
     * Gets whether the Element should be collapsed.
     */
    get collapsed() {
        return this._collapsed;
    }
    /**
     * Sets whether the panel can be reordered.
     */
    set sortable(value) {
        if (this._sortable === value)
            return;
        this._sortable = value;
        if (value) {
            this._iconSort = new Label({
                class: CLASS_PANEL_SORTABLE_ICON
            });
            this._iconSort.dom.addEventListener('mousedown', this._onDragStart);
            this.header.prepend(this._iconSort);
        }
        else if (this._iconSort) {
            this._iconSort.destroy();
            this._iconSort = null;
        }
    }
    /**
     * Gets whether the panel can be reordered.
     */
    get sortable() {
        return this._sortable;
    }
    /**
     * Sets whether the panel can be removed
     */
    set removable(value) {
        if (this.removable === value)
            return;
        if (value) {
            this._btnRemove = new Button({
                icon: 'E289',
                class: CLASS_PANEL_REMOVE
            });
            this._btnRemove.on('click', this._onClickRemove.bind(this));
            this.header.append(this._btnRemove);
        }
        else {
            this._btnRemove.destroy();
            this._btnRemove = null;
        }
    }
    /**
     * Gets whether the panel can be removed
     */
    get removable() {
        return !!this._btnRemove;
    }
    /**
     * Sets whether the panel collapses horizontally. This would be the case for side panels. Defaults to `false`.
     */
    set collapseHorizontally(value) {
        if (this._collapseHorizontally === value)
            return;
        this._collapseHorizontally = value;
        if (value) {
            this.class.add(CLASS_PANEL_HORIZONTAL);
        }
        else {
            this.class.remove(CLASS_PANEL_HORIZONTAL);
        }
        this._reflow();
    }
    /**
     * Gets whether the panel collapses horizontally.
     */
    get collapseHorizontally() {
        return this._collapseHorizontally;
    }
    /**
     * Gets the content container.
     */
    get content() {
        return this._containerContent;
    }
    /**
     * Gets the header container.
     */
    get header() {
        return this._containerHeader;
    }
    /**
     * Sets the header text of the panel. Defaults to the empty string.
     */
    set headerText(value) {
        this._labelTitle.text = value;
    }
    /**
     * Gets the header text of the panel.
     */
    get headerText() {
        return this._labelTitle.text;
    }
    /**
     * Sets the height of the header in pixels. Defaults to 32.
     */
    set headerSize(value) {
        this._headerSize = value;
        const style = this._containerHeader.dom.style;
        style.height = `${Math.max(0, value)}px`;
        style.lineHeight = style.height;
        this._reflow();
    }
    /**
     * Gets the height of the header in pixels.
     */
    get headerSize() {
        return this._headerSize;
    }
}
/**
 * Fired when the panel gets collapsed.
 *
 * @event
 * @example
 * ```ts
 * const panel = new Panel();
 * panel.on('collapse', () => {
 *     console.log('Panel collapsed');
 * });
 * ```
 */
Panel.EVENT_COLLAPSE = 'collapse';
/**
 * Fired when the panel gets expanded.
 *
 * @event
 * @example
 * ```ts
 * const panel = new Panel();
 * panel.on('expand', () => {
 *     console.log('Panel expanded');
 * });
 * ```
 */
Panel.EVENT_EXPAND = 'expand';
Element$1.register('panel', Panel);

const CLASS_BOOLEAN_INPUT = 'pcui-boolean-input';
const CLASS_BOOLEAN_INPUT_TICKED = `${CLASS_BOOLEAN_INPUT}-ticked`;
const CLASS_BOOLEAN_INPUT_TOGGLE = `${CLASS_BOOLEAN_INPUT}-toggle`;
/**
 * A checkbox element.
 */
class BooleanInput extends Element$1 {
    /**
     * Creates a new BooleanInput.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a;
        super(Object.assign({ tabIndex: 0 }, args));
        this._onKeyDown = (evt) => {
            if (evt.key === 'Escape') {
                this.blur();
                return;
            }
            if (!this.enabled || this.readOnly)
                return;
            if (evt.key === ' ') {
                evt.stopPropagation();
                evt.preventDefault();
                this.value = !this.value;
            }
        };
        this._onFocus = () => {
            this.emit('focus');
        };
        this._onBlur = () => {
            this.emit('blur');
        };
        if (args.type === 'toggle') {
            this.class.add(CLASS_BOOLEAN_INPUT_TOGGLE);
        }
        else {
            this.class.add(CLASS_BOOLEAN_INPUT);
        }
        this.class.add(CLASS_NOT_FLEXIBLE);
        this.dom.addEventListener('keydown', this._onKeyDown);
        this.dom.addEventListener('focus', this._onFocus);
        this.dom.addEventListener('blur', this._onBlur);
        this._value = null;
        if (args.value !== undefined) {
            this.value = args.value;
        }
        this.renderChanges = (_a = args.renderChanges) !== null && _a !== void 0 ? _a : false;
    }
    destroy() {
        if (this._destroyed)
            return;
        this.dom.removeEventListener('keydown', this._onKeyDown);
        this.dom.removeEventListener('focus', this._onFocus);
        this.dom.removeEventListener('blur', this._onBlur);
        super.destroy();
    }
    _onClick(evt) {
        if (this.enabled) {
            this.focus();
        }
        if (this.enabled && !this.readOnly) {
            this.value = !this.value;
        }
        return super._onClick(evt);
    }
    _updateValue(value) {
        this.class.remove(CLASS_MULTIPLE_VALUES);
        if (value === this.value)
            return false;
        this._value = value;
        if (value) {
            this.class.add(CLASS_BOOLEAN_INPUT_TICKED);
        }
        else {
            this.class.remove(CLASS_BOOLEAN_INPUT_TICKED);
        }
        if (this.renderChanges) {
            this.flash();
        }
        this.emit('change', value);
        return true;
    }
    focus() {
        this.dom.focus();
    }
    blur() {
        this.dom.blur();
    }
    set value(value) {
        const changed = this._updateValue(value);
        if (changed && this._binding) {
            this._binding.setValue(value);
        }
    }
    get value() {
        return this._value;
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        const different = values.some(v => v !== values[0]);
        if (different) {
            this._updateValue(null);
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            this._updateValue(values[0]);
        }
    }
    set renderChanges(value) {
        this._renderChanges = value;
    }
    get renderChanges() {
        return this._renderChanges;
    }
}
Element$1.register('boolean', BooleanInput, { renderChanges: true });

function _rgb2hsv(rgb) {
    const r = rgb[0] / 255, g = rgb[1] / 255, b = rgb[2] / 255;
    let h, s;
    const v = Math.max(r, g, b);
    const diff = v - Math.min(r, g, b);
    const diffc = (c) => (v - c) / 6 / diff + 1 / 2;
    if (diff === 0) {
        h = s = 0;
        return [h, s, v];
    }
    s = diff / v;
    const rr = diffc(r);
    const gg = diffc(g);
    const bb = diffc(b);
    if (r === v) {
        h = bb - gg;
    }
    else if (g === v) {
        h = (1 / 3) + rr - bb;
    }
    else if (b === v) {
        h = (2 / 3) + gg - rr;
    }
    if (h < 0) {
        h += 1;
    }
    else if (h > 1) {
        h -= 1;
    }
    return [h, s, v];
}
function _hsv2rgb(hsv) {
    const h = hsv[0];
    const s = hsv[1];
    const v = hsv[2];
    let r, g, b;
    const i = Math.floor(h * 6);
    const f = h * 6 - i;
    const p = v * (1 - s);
    const q = v * (1 - f * s);
    const t = v * (1 - (1 - f) * s);
    switch (i % 6) {
        case 0:
            r = v;
            g = t;
            b = p;
            break;
        case 1:
            r = q;
            g = v;
            b = p;
            break;
        case 2:
            r = p;
            g = v;
            b = t;
            break;
        case 3:
            r = p;
            g = q;
            b = v;
            break;
        case 4:
            r = t;
            g = p;
            b = v;
            break;
        case 5:
            r = v;
            g = p;
            b = q;
            break;
    }
    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}

const CLASS_OVERLAY = 'pcui-overlay';
const CLASS_OVERLAY_INNER = `${CLASS_OVERLAY}-inner`;
const CLASS_OVERLAY_CLICKABLE = `${CLASS_OVERLAY}-clickable`;
const CLASS_OVERLAY_TRANSPARENT = `${CLASS_OVERLAY}-transparent`;
const CLASS_OVERLAY_CONTENT = `${CLASS_OVERLAY}-content`;
/**
 * An overlay element.
 */
class Overlay extends Container {
    /**
     * Creates a new Overlay.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b;
        super(args);
        this._onPointerDown = (evt) => {
            if (!this.clickable || this.domContent.contains(evt.target)) {
                return;
            }
            // some field might be in focus
            document.body.blur();
            // wait till blur is done
            requestAnimationFrame(() => {
                this.hidden = true;
            });
            evt.preventDefault();
            evt.stopPropagation();
        };
        this.class.add(CLASS_OVERLAY);
        this._domClickableOverlay = document.createElement('div');
        this._domClickableOverlay.ui = this;
        this._domClickableOverlay.classList.add(CLASS_OVERLAY_INNER);
        this.dom.appendChild(this._domClickableOverlay);
        this.on('show', () => {
            document.body.addEventListener('pointerdown', this._onPointerDown, true);
        });
        this.on('hide', () => {
            document.body.removeEventListener('pointerdown', this._onPointerDown, true);
        });
        this.domContent = document.createElement('div');
        this.domContent.ui = this;
        this.domContent.classList.add(CLASS_OVERLAY_CONTENT);
        this.dom.appendChild(this.domContent);
        this.clickable = (_a = args.clickable) !== null && _a !== void 0 ? _a : false;
        this.transparent = (_b = args.transparent) !== null && _b !== void 0 ? _b : false;
    }
    destroy() {
        if (this._destroyed)
            return;
        super.destroy();
    }
    /**
     * Position the overlay at specific x, y coordinates.
     *
     * @param x - The x coordinate.
     * @param y - The y coordinate.
     */
    position(x, y) {
        const area = this._domClickableOverlay.getBoundingClientRect();
        const rect = this.domContent.getBoundingClientRect();
        x = Math.max(0, Math.min(area.width - rect.width, x));
        y = Math.max(0, Math.min(area.height - rect.height, y));
        this.domContent.style.position = 'absolute';
        this.domContent.style.left = `${x}px`;
        this.domContent.style.top = `${y}px`;
    }
    /**
     * Sets whether the overlay can be hidden by clicking on it.
     */
    set clickable(value) {
        if (value) {
            this.class.add(CLASS_OVERLAY_CLICKABLE);
        }
        else {
            this.class.remove(CLASS_OVERLAY_CLICKABLE);
        }
    }
    /**
     * Gets whether the overlay can be hidden by clicking on it.
     */
    get clickable() {
        return this.class.contains(CLASS_OVERLAY_CLICKABLE);
    }
    /**
     * Sets whether the overlay is transparent or not.
     */
    set transparent(value) {
        if (value) {
            this.class.add(CLASS_OVERLAY_TRANSPARENT);
        }
        else {
            this.class.remove(CLASS_OVERLAY_TRANSPARENT);
        }
    }
    /**
     * Gets whether the overlay is transparent or not.
     */
    get transparent() {
        return this.class.contains(CLASS_OVERLAY_TRANSPARENT);
    }
}
Element$1.register('overlay', Overlay);

const CLASS_TEXT_INPUT = 'pcui-text-input';
/**
 * The TextInput is an input element of type text.
 */
class TextInput extends InputElement {
    /**
     * Creates a new TextInput.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        super(args);
        this.class.add(CLASS_TEXT_INPUT);
        if (args.onValidate) {
            this.onValidate = args.onValidate;
        }
    }
    _onInputChange(evt) {
        if (this._suspendInputChangeEvt)
            return;
        if (this.onValidate) {
            const error = !this.onValidate(this.value);
            this.error = error;
            if (error) {
                return;
            }
        }
        else {
            this.error = false;
        }
        this.emit('change', this.value);
        if (this._binding) {
            this._binding.setValue(this.value);
        }
    }
    _updateValue(value) {
        this.class.remove(CLASS_MULTIPLE_VALUES);
        if (value && typeof (value) === 'object') {
            if (Array.isArray(value)) {
                let isObject = false;
                for (let i = 0; i < value.length; i++) {
                    if (value[i] && typeof value[i] === 'object') {
                        isObject = true;
                        break;
                    }
                }
                value = isObject ? '[Not available]' : value.map((val) => {
                    return val === null ? 'null' : val;
                }).join(',');
            }
            else {
                value = '[Not available]';
            }
        }
        if (value === this.value)
            return false;
        this._suspendInputChangeEvt = true;
        this._domInput.value = (value === null || value === undefined) ? '' : String(value);
        this._suspendInputChangeEvt = false;
        this.emit('change', value);
        return true;
    }
    set value(value) {
        const changed = this._updateValue(value);
        if (changed) {
            // reset error
            this.error = false;
        }
        if (changed && this._binding) {
            this._binding.setValue(value);
        }
    }
    get value() {
        return this._domInput.value;
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        const different = values.some(v => v !== values[0]);
        if (different) {
            this._updateValue(null);
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            this._updateValue(values[0]);
        }
    }
}
Element$1.register('string', TextInput, { renderChanges: true });

const CLASS_ROOT = 'pcui-color-input';
/**
 * Represents a color picker.
 */
class ColorPicker extends Element$1 {
    /**
     * Creates a new ColorPicker.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b, _c;
        super(args);
        this._historyCombine = false;
        this._historyPostfix = null;
        this._size = 144;
        this._directInput = true;
        this._colorHSV = [0, 0, 0];
        this._pickerChannels = [];
        this._channelsNumber = 4;
        this._changing = false;
        this._dragging = false;
        this._callingCallback = false;
        this._pickRectPointerId = null;
        this._pickHuePointerId = null;
        this._pickOpacityPointerId = null;
        this._evtColorPick = null;
        this._evtColorToPicker = null;
        this._evtColorPickStart = null;
        this._evtColorPickEnd = null;
        this._onKeyDown = (evt) => {
            // escape blurs the field
            if (evt.key === 'Escape') {
                this.blur();
            }
            // enter opens the color picker
            if (evt.key !== 'Enter' || !this.enabled || this.readOnly) {
                return;
            }
            evt.stopPropagation();
            evt.preventDefault();
        };
        this._onFocus = (evt) => {
            this.emit('focus');
        };
        this._onBlur = (evt) => {
            this.emit('blur');
        };
        // rect drag start
        this._pickRectPointerDown = (event) => {
            if (this._pickRectPointerId !== null)
                return;
            this._pickRect.setPointerCapture(event.pointerId);
            this._pickRectPointerId = event.pointerId;
            event.preventDefault();
            event.stopPropagation();
            this.emit('picker:color:start');
            this._pickRectPointerMove(event);
        };
        // rect drag
        this._pickRectPointerMove = (event) => {
            if (this._pickRectPointerId !== event.pointerId)
                return;
            this._changing = true;
            // Get the pointer position relative to the element
            const rect = this._pickRect.getBoundingClientRect();
            const x = Math.max(0, Math.min(this._size, Math.floor(event.clientX - rect.left)));
            const y = Math.max(0, Math.min(this._size, Math.floor(event.clientY - rect.top)));
            this._colorHSV[1] = x / this._size;
            this._colorHSV[2] = 1.0 - (y / this._size);
            this._directInput = false;
            const rgb = _hsv2rgb(this._colorHSV);
            for (let i = 0; i < 3; i++) {
                this._pickerChannels[i].value = rgb[i];
            }
            this._fieldHex.value = this._getHex();
            this._directInput = true;
            this._pickRectHandle.style.left = `${Math.max(4, Math.min(this._size - 4, x))}px`;
            this._pickRectHandle.style.top = `${Math.max(4, Math.min(this._size - 4, y))}px`;
            this._changing = false;
        };
        // rect drag stop
        this._pickRectPointerUp = (event) => {
            if (this._pickRectPointerId !== event.pointerId)
                return;
            this.emit('picker:color:end');
            // Release the pointer
            this._pickRect.releasePointerCapture(event.pointerId);
            this._pickRectPointerId = null;
        };
        // hue drag start
        this._pickHuePointerDown = (event) => {
            if (this._pickHuePointerId !== null)
                return;
            this._pickHue.setPointerCapture(event.pointerId);
            this._pickHuePointerId = event.pointerId;
            event.preventDefault();
            event.stopPropagation();
            this.emit('picker:color:start');
            this._pickHuePointerMove(event);
        };
        // hue drag
        this._pickHuePointerMove = (event) => {
            if (this._pickHuePointerId !== event.pointerId)
                return;
            this._changing = true;
            const rect = this._pickHue.getBoundingClientRect();
            const y = Math.max(0, Math.min(this._size, Math.floor(event.clientY - rect.top)));
            const h = y / this._size;
            const rgb = _hsv2rgb([h, this._colorHSV[1], this._colorHSV[2]]);
            this._colorHSV[0] = h;
            this._directInput = false;
            for (let i = 0; i < 3; i++) {
                this._pickerChannels[i].value = rgb[i];
            }
            this._fieldHex.value = this._getHex();
            this._onChangeRgb();
            this._directInput = true;
            this._changing = false;
        };
        // hue drag stop
        this._pickHuePointerUp = (event) => {
            if (this._pickHuePointerId !== event.pointerId)
                return;
            this.emit('picker:color:end');
            // Release the pointer
            this._pickHue.releasePointerCapture(event.pointerId);
            this._pickHuePointerId = null;
        };
        // opacity drag start
        this._pickOpacityPointerDown = (event) => {
            if (this._pickOpacityPointerId !== null)
                return;
            this._pickOpacity.setPointerCapture(event.pointerId);
            this._pickOpacityPointerId = event.pointerId;
            event.preventDefault();
            event.stopPropagation();
            this.emit('picker:color:start');
            this._pickOpacityPointerMove(event);
        };
        // opacity drag
        this._pickOpacityPointerMove = (event) => {
            if (this._pickOpacityPointerId !== event.pointerId)
                return;
            this._changing = true;
            const rect = this._pickOpacity.getBoundingClientRect();
            const y = Math.max(0, Math.min(this._size, Math.floor(event.clientY - rect.top)));
            const o = 1.0 - y / this._size;
            this._directInput = false;
            this._pickerChannels[3].value = Math.max(0, Math.min(255, Math.round(o * 255)));
            this._fieldHex.value = this._getHex();
            this._directInput = true;
            this._changing = false;
        };
        // opacity drag stop
        this._pickOpacityPointerUp = (event) => {
            if (this._pickOpacityPointerId !== event.pointerId)
                return;
            this.emit('picker:color:end');
            // Release the pointer
            this._pickOpacity.releasePointerCapture(event.pointerId);
            this._pickOpacityPointerId = null;
        };
        this.class.add(CLASS_ROOT, CLASS_NOT_FLEXIBLE);
        // this element shows the actual color. The
        // parent element shows the checkerboard pattern
        this._domColor = document.createElement('div');
        this.dom.appendChild(this._domColor);
        this.dom.addEventListener('keydown', this._onKeyDown);
        this.dom.addEventListener('focus', this._onFocus);
        this.dom.addEventListener('blur', this._onBlur);
        this.dom.addEventListener('pointerdown', (evt) => {
            if (this.enabled && !this.readOnly && this._overlay.hidden) {
                this._openColorPicker();
                evt.stopPropagation();
                evt.preventDefault();
            }
        });
        this._value = (_a = args.value) !== null && _a !== void 0 ? _a : [0, 0, 255, 1];
        this._channels = (_b = args.channels) !== null && _b !== void 0 ? _b : 3;
        this._setValue(this._value);
        this.renderChanges = (_c = args.renderChanges) !== null && _c !== void 0 ? _c : false;
        this.on('change', () => {
            if (this.renderChanges) {
                this.flash();
            }
        });
        // overlay
        this._overlay = new Overlay({
            class: 'picker-color',
            clickable: true,
            hidden: true,
            transparent: true
        });
        this.dom.appendChild(this._overlay.dom);
        // rectangular picker
        this._pickRect = document.createElement('div');
        this._pickRect.classList.add('pick-rect');
        this._overlay.append(this._pickRect);
        // color drag events
        this._pickRect.addEventListener('pointerdown', this._pickRectPointerDown);
        this._pickRect.addEventListener('pointermove', this._pickRectPointerMove);
        this._pickRect.addEventListener('pointerup', this._pickRectPointerUp);
        // white
        this._pickRectWhite = document.createElement('div');
        this._pickRectWhite.classList.add('white');
        this._pickRect.appendChild(this._pickRectWhite);
        // black
        this._pickRectBlack = document.createElement('div');
        this._pickRectBlack.classList.add('black');
        this._pickRect.appendChild(this._pickRectBlack);
        // handle
        this._pickRectHandle = document.createElement('div');
        this._pickRectHandle.classList.add('handle');
        this._pickRect.appendChild(this._pickRectHandle);
        // hue (rainbow) picker
        this._pickHue = document.createElement('div');
        this._pickHue.classList.add('pick-hue');
        this._overlay.append(this._pickHue);
        // hue drag events
        this._pickHue.addEventListener('pointerdown', this._pickHuePointerDown);
        this._pickHue.addEventListener('pointermove', this._pickHuePointerMove);
        this._pickHue.addEventListener('pointerup', this._pickHuePointerUp);
        // handle
        this._pickHueHandle = document.createElement('div');
        this._pickHueHandle.classList.add('handle');
        this._pickHue.appendChild(this._pickHueHandle);
        // opacity (gradient) picker
        this._pickOpacity = document.createElement('div');
        this._pickOpacity.classList.add('pick-opacity');
        this._overlay.append(this._pickOpacity);
        // opacity drag events
        this._pickOpacity.addEventListener('pointerdown', this._pickOpacityPointerDown);
        this._pickOpacity.addEventListener('pointermove', this._pickOpacityPointerMove);
        this._pickOpacity.addEventListener('pointerup', this._pickOpacityPointerUp);
        // handle
        this._pickOpacityHandle = document.createElement('div');
        this._pickOpacityHandle.classList.add('handle');
        this._pickOpacity.appendChild(this._pickOpacityHandle);
        // fields
        this._panelFields = document.createElement('div');
        this._panelFields.classList.add('fields');
        this._overlay.append(this._panelFields);
        this._overlay.on('hide', () => {
            this._evtColorPick.unbind();
            this._evtColorPick = null;
            this._evtColorToPicker.unbind();
            this._evtColorToPicker = null;
            this._evtColorPickStart.unbind();
            this._evtColorPickStart = null;
            this._evtColorPickEnd.unbind();
            this._evtColorPickEnd = null;
        });
        const createChannelInput = (channel) => {
            return new NumericInput({
                class: ['field', `field-${channel}`],
                precision: 0,
                step: 1,
                min: 0,
                max: 255,
                placeholder: channel
            });
        };
        // R
        const fieldR = createChannelInput('r');
        fieldR.on('change', () => {
            this._onChangeRgb();
        });
        this._pickerChannels.push(fieldR);
        this._panelFields.appendChild(fieldR.dom);
        // G
        const fieldG = createChannelInput('g');
        fieldG.on('change', () => {
            this._onChangeRgb();
        });
        this._pickerChannels.push(fieldG);
        this._panelFields.appendChild(fieldG.dom);
        // B
        const fieldB = createChannelInput('b');
        fieldB.on('change', () => {
            this._onChangeRgb();
        });
        this._pickerChannels.push(fieldB);
        this._panelFields.appendChild(fieldB.dom);
        // A
        const fieldA = createChannelInput('a');
        fieldA.on('change', (value) => {
            this._onChangeAlpha(value);
        });
        this._pickerChannels.push(fieldA);
        this._panelFields.appendChild(fieldA.dom);
        // HEX
        this._fieldHex = new TextInput({
            class: ['field', 'field-hex'],
            placeholder: '#'
        });
        this._fieldHex.on('change', () => {
            this._onChangeHex();
        });
        this._panelFields.appendChild(this._fieldHex.dom);
    }
    destroy() {
        if (this._destroyed)
            return;
        this.dom.removeEventListener('keydown', this._onKeyDown);
        this.dom.removeEventListener('focus', this._onFocus);
        this.dom.removeEventListener('blur', this._onBlur);
        this._pickRect.removeEventListener('pointerdown', this._pickRectPointerDown);
        this._pickRect.removeEventListener('pointermove', this._pickRectPointerMove);
        this._pickRect.removeEventListener('pointerup', this._pickRectPointerUp);
        this._pickHue.removeEventListener('pointerdown', this._pickHuePointerDown);
        this._pickHue.removeEventListener('pointermove', this._pickHuePointerMove);
        this._pickHue.removeEventListener('pointerup', this._pickHuePointerUp);
        this._pickOpacity.removeEventListener('pointerdown', this._pickOpacityPointerDown);
        this._pickOpacity.removeEventListener('pointermove', this._pickOpacityPointerMove);
        this._pickOpacity.removeEventListener('pointerup', this._pickOpacityPointerUp);
        super.destroy();
    }
    focus() {
        this.dom.focus();
    }
    blur() {
        this.dom.blur();
    }
    _setColorPickerPosition(x, y) {
        this._overlay.position(x, y);
    }
    _setPickerColor(color) {
        if (this._changing || this._dragging) {
            return;
        }
        if (this._channelsNumber >= 3) {
            const hsv = _rgb2hsv(color);
            this._colorHSV[0] = hsv[0];
            this._colorHSV[1] = hsv[1];
            this._colorHSV[2] = hsv[2];
        }
        // set fields
        this._directInput = false;
        for (let i = 0; i < color.length; i++) {
            this._pickerChannels[i].value = color[i];
        }
        this._fieldHex.value = this._getHex();
        this._directInput = true;
    }
    _openColorPicker() {
        // open color picker
        this._callPicker(this.value.map(c => Math.floor(c * 255)));
        // picked color
        this._evtColorPick = this.on('picker:color', (color) => {
            this.value = color.map((c) => c / 255);
        });
        this._evtColorPickStart = this.on('picker:color:start', () => {
            if (this.binding) {
                this._historyCombine = this.binding.historyCombine;
                this._historyPostfix = this.binding.historyPostfix;
                this.binding.historyCombine = true;
                this._binding.historyPostfix = `(${Date.now()})`;
            }
            else {
                this._historyCombine = false;
                this._historyPostfix = null;
            }
        });
        this._evtColorPickEnd = this.on('picker:color:end', () => {
            if (this.binding) {
                this.binding.historyCombine = this._historyCombine;
                this.binding.historyPostfix = this._historyPostfix;
            }
        });
        // position picker
        const rectPicker = this._overlay.dom.getBoundingClientRect();
        const rectElement = this.dom.getBoundingClientRect();
        this._setColorPickerPosition(rectElement.left - rectPicker.left, rectElement.bottom - rectPicker.top + 1);
        // color changed, update picker
        this._evtColorToPicker = this.on('change', () => {
            this._setPickerColor(this.value.map(c => Math.floor(c * 255)));
        });
    }
    _callPicker(color) {
        // class for channels
        for (let i = 0; i < 4; i++) {
            if (color.length - 1 < i) {
                this._overlay.class.remove(`c-${i + 1}`);
            }
            else {
                this._overlay.class.add(`c-${i + 1}`);
            }
        }
        // number of channels
        this._channelsNumber = color.length;
        if (this._channelsNumber >= 3) {
            const hsv = _rgb2hsv(color);
            this._colorHSV[0] = hsv[0];
            this._colorHSV[1] = hsv[1];
            this._colorHSV[2] = hsv[2];
        }
        // set fields
        this._directInput = false;
        for (let i = 0; i < color.length; i++) {
            this._pickerChannels[i].value = color[i];
        }
        this._fieldHex.value = this._getHex();
        this._directInput = true;
        // show overlay
        this._overlay.hidden = false;
        // focus on hex field
        this._fieldHex.dom.focus();
        window.setTimeout(() => {
            this._fieldHex.dom.focus();
        }, 100);
    }
    _valueToColor(value) {
        value = Math.floor(value * 255);
        return Math.max(0, Math.min(value, 255));
    }
    _setValue(value) {
        const r = this._valueToColor(value[0]);
        const g = this._valueToColor(value[1]);
        const b = this._valueToColor(value[2]);
        const a = value[3];
        if (this._channels === 1) {
            this._domColor.style.backgroundColor = `rgb(${r}, ${r}, ${r})`;
        }
        else if (this._channels === 3) {
            this._domColor.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
        }
        else if (this._channels === 4) {
            this._domColor.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${a})`;
        }
    }
    _updateValue(value) {
        let dirty = false;
        for (let i = 0; i < value.length; i++) {
            if (this._value[i] !== value[i]) {
                dirty = true;
                this._value[i] = value[i];
            }
        }
        this.class.remove(CLASS_MULTIPLE_VALUES);
        if (dirty) {
            this._setValue(value);
            this.emit('change', value);
        }
        return dirty;
    }
    // get hex from channels
    _getHex() {
        let hex = '';
        for (let i = 0; i < this._channelsNumber; i++) {
            hex += (`00${this._pickerChannels[i].value.toString(16)}`).slice(-2).toUpperCase();
        }
        return hex;
    }
    _onChangeHex() {
        if (!this._directInput) {
            return;
        }
        this._changing = true;
        const hex = this._fieldHex.value.trim().toLowerCase();
        /* eslint-disable-next-line regexp/no-unused-capturing-group */
        if (/^([0-9a-f]{2}){3,4}$/.test(hex)) {
            for (let i = 0; i < this._channelsNumber; i++) {
                this._pickerChannels[i].value = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
            }
        }
        this._changing = false;
    }
    _onChangeRgb() {
        const color = this._pickerChannels.map((channel) => {
            return channel.value || 0;
        }).slice(0, this._channelsNumber);
        const hsv = _rgb2hsv(color);
        if (this._directInput) {
            const sum = color[0] + color[1] + color[2];
            if (sum !== 765 && sum !== 0) {
                this._colorHSV[0] = hsv[0];
            }
            this._colorHSV[1] = hsv[1];
            this._colorHSV[2] = hsv[2];
            this._dragging = true;
            this.emit('picker:color:start');
            this._fieldHex.value = this._getHex();
        }
        // hue position
        this._pickHueHandle.style.top = `${Math.floor(this._size * this._colorHSV[0])}px`; // h
        // rect position
        this._pickRectHandle.style.left = `${Math.max(4, Math.min(this._size - 4, this._size * this._colorHSV[1]))}px`; // s
        this._pickRectHandle.style.top = `${Math.max(4, Math.min(this._size - 4, this._size * (1.0 - this._colorHSV[2])))}px`; // v
        if (this._channelsNumber >= 3) {
            const plainColor = _hsv2rgb([this._colorHSV[0], 1, 1]).join(',');
            // rect background color
            this._pickRect.style.backgroundColor = `rgb(${plainColor})`;
            // rect handle color
            this._pickRectHandle.style.backgroundColor = `rgb(${color.slice(0, 3).join(',')})`;
            // hue handle color
            this._pickHueHandle.style.backgroundColor = `rgb(${plainColor})`;
        }
        this.callCallback();
    }
    // update alpha handle
    _onChangeAlpha(value) {
        if (this._channelsNumber !== 4) {
            return;
        }
        // position
        this._pickOpacityHandle.style.top = `${Math.floor(this._size * (1.0 - (Math.max(0, Math.min(255, value)) / 255)))}px`;
        // color
        this._pickOpacityHandle.style.backgroundColor = `rgb(${value}, ${value}, ${value})`;
        this.callCallback();
    }
    /** @ignore */
    callbackHandle() {
        this._callingCallback = false;
        this.emit('picker:color', this._pickerChannels.map((channel) => {
            return channel.value || 0;
        }).slice(0, this._channelsNumber));
    }
    /** @ignore */
    callCallback() {
        if (this._callingCallback) {
            return;
        }
        this._callingCallback = true;
        window.setTimeout(() => {
            this.callbackHandle();
        }, 1000 / 60);
    }
    set value(value) {
        value = value || [0, 0, 0, 0];
        const changed = this._updateValue(value);
        if (changed && this._binding) {
            this._binding.setValue(value);
        }
    }
    get value() {
        return this._value.slice(0, this._channels);
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        let different = false;
        const value = values[0];
        for (let i = 1; i < values.length; i++) {
            if (Array.isArray(value)) {
                // @ts-ignore
                if (!value.equals(values[i])) { // TODO: check if this works
                    different = true;
                    break;
                }
            }
            else {
                if (value !== values[i]) {
                    different = true;
                    break;
                }
            }
        }
        if (different) {
            this.value = null;
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            // @ts-ignore
            this.value = values[0];
        }
    }
    set channels(value) {
        if (this._channels === value)
            return;
        this._channels = Math.max(0, Math.min(value, 4));
        this._setValue(this.value);
    }
    get channels() {
        return this._channels;
    }
    set renderChanges(value) {
        this._renderChanges = value;
    }
    get renderChanges() {
        return this._renderChanges;
    }
}
Element$1.register('rgb', ColorPicker);
Element$1.register('rgba', ColorPicker, { channels: 4 });

// calculate, how many string `a`
// requires edits, to become string `b`
const searchStringEditDistance = (a, b) => {
    // Levenshtein distance
    // https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#JavaScript
    if (a.length === 0)
        return b.length;
    if (b.length === 0)
        return a.length;
    if (a === b)
        return 0;
    const matrix = [];
    for (let i = 0; i <= b.length; i++) {
        matrix[i] = [i];
    }
    for (let j = 0; j <= a.length; j++) {
        matrix[0][j] = j;
    }
    for (let i = 1; i <= b.length; i++) {
        for (let j = 1; j <= a.length; j++) {
            if (b.charAt(i - 1) === a.charAt(j - 1)) {
                matrix[i][j] = matrix[i - 1][j - 1];
            }
            else {
                matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1));
            }
        }
    }
    return matrix[b.length][a.length];
};
// calculate, how many characters string `b`
// contains of a string `a`
const searchCharsContains = (a, b) => {
    if (a === b) {
        return a.length;
    }
    let contains = 0;
    const ind = new Set();
    for (let i = 0; i < b.length; i++) {
        ind.add(b.charAt(i));
    }
    for (let i = 0; i < a.length; i++) {
        if (ind.has(a.charAt(i))) {
            contains++;
        }
    }
    return contains;
};
// tokenize string into array of tokens
const searchStringTokenize = (name) => {
    const tokens = [];
    // camelCase
    // upperCASE123
    const string = name.replace(/([^A-Z])([A-Z][^A-Z])/g, '$1 $2').replace(/([A-Z0-9]{2,})/g, ' $1');
    // space notation
    // dash-notation
    // underscore_notation
    const parts = string.split(/([\s\-_])/);
    // filter valid tokens
    for (let i = 0; i < parts.length; i++) {
        parts[i] = parts[i].toLowerCase().trim();
        if (parts[i] && parts[i] !== '-' && parts[i] !== '_') {
            tokens.push(parts[i]);
        }
    }
    return tokens;
};
const _searchItems = (items, search, args) => {
    const results = [];
    for (const item of items) {
        // direct hit
        if (item.subFull !== Infinity) {
            results.push(item);
            if (item.edits === Infinity) {
                item.edits = 0;
            }
            if (item.sub === Infinity) {
                item.sub = item.subFull;
            }
            continue;
        }
        else if (item.name === search || item.name.indexOf(search) === 0) {
            results.push(item);
            if (item.edits === Infinity) {
                item.edits = 0;
            }
            if (item.sub === Infinity) {
                item.sub = 0;
            }
            continue;
        }
        // check if name contains enough of search characters
        const contains = searchCharsContains(search, item.name);
        if (contains / search.length < args.containsCharsTolerance) {
            continue;
        }
        let editsCandidate = Infinity;
        let subCandidate = Infinity;
        // for each token
        for (let t = 0; t < item.tokens.length; t++) {
            // direct token match
            if (item.tokens[t] === search) {
                editsCandidate = 0;
                subCandidate = t;
                break;
            }
            const edits = searchStringEditDistance(search, item.tokens[t]);
            if ((subCandidate === Infinity || edits < editsCandidate) && item.tokens[t].indexOf(search) !== -1) {
                // search is a substring of a token
                subCandidate = t;
                editsCandidate = edits;
                continue;
            }
            else if (subCandidate === Infinity && edits < editsCandidate) {
                // new edits candidate, not a substring of a token
                if ((edits / Math.max(search.length, item.tokens[t].length)) <= args.editsDistanceTolerance) {
                    // check if edits tolerance is satisfied
                    editsCandidate = edits;
                }
            }
        }
        // no match candidate
        if (editsCandidate === Infinity) {
            continue;
        }
        // add new result
        results.push(item);
        item.edits = item.edits === Infinity ? editsCandidate : item.edits + editsCandidate;
        item.sub = item.sub === Infinity ? subCandidate : item.sub + subCandidate;
    }
    return results;
};
/**
 * Perform search through items.
 *
 * @param items - Array of objects to search through.
 * @param searchKey - The property name to search within each item.
 * @param search - String to search for.
 * @param args - Search arguments.
 * @returns Array of found items.
 * @example
 * const items = [
 *     { text: 'Item 1', id: 1 },
 *     { text: 'Item 2', id: 2 }
 * ];
 * const results = searchItems(items, 'text', 'item');
 */
const searchItems = (items, searchKey, search = '', args = {}) => {
    var _a, _b;
    search = search.toLowerCase().trim();
    if (!search)
        return [];
    const searchTokens = searchStringTokenize(search);
    if (!searchTokens.length)
        return [];
    args.containsCharsTolerance = (_a = args.containsCharsTolerance) !== null && _a !== void 0 ? _a : 0.5;
    args.editsDistanceTolerance = (_b = args.editsDistanceTolerance) !== null && _b !== void 0 ? _b : 0.5;
    let records = items.map((item) => {
        const subInd = item[searchKey].toLowerCase().trim().indexOf(search);
        return {
            name: item[searchKey],
            item: item,
            tokens: searchStringTokenize(item[searchKey]),
            edits: Infinity,
            subFull: (subInd !== -1) ? subInd : Infinity,
            sub: Infinity
        };
    });
    // search each token
    for (let i = 0; i < searchTokens.length; i++) {
        records = _searchItems(records, searchTokens[i], args);
    }
    // sort result first by substring? then by edits number
    records.sort((a, b) => {
        if (a.subFull !== b.subFull) {
            return a.subFull - b.subFull;
        }
        else if (a.sub !== b.sub) {
            return a.sub - b.sub;
        }
        else if (a.edits !== b.edits) {
            return a.edits - b.edits;
        }
        return a.name.length - b.name.length;
    });
    // return only items without match information
    let recordItems = records.map((record) => record.item);
    // limit number of results
    if (args.hasOwnProperty('limitResults') && recordItems.length > args.limitResults) {
        recordItems = recordItems.slice(0, args.limitResults);
    }
    return recordItems;
};

const CLASS_SELECT_INPUT = 'pcui-select-input';
const CLASS_SELECT_INPUT_CONTAINER_VALUE = `${CLASS_SELECT_INPUT}-container-value`;
const CLASS_MULTI_SELECT = `${CLASS_SELECT_INPUT}-multi`;
const CLASS_DISABLED_VALUE = `${CLASS_SELECT_INPUT}-disabled-value`;
const CLASS_HAS_DISABLED_VALUE = `${CLASS_SELECT_INPUT}-has-disabled-value`;
const CLASS_ALLOW_INPUT = 'pcui-select-input-allow-input';
const CLASS_VALUE = `${CLASS_SELECT_INPUT}-value`;
const CLASS_ICON = `${CLASS_SELECT_INPUT}-icon`;
const CLASS_INPUT = `${CLASS_SELECT_INPUT}-textinput`;
const CLASS_LIST = `${CLASS_SELECT_INPUT}-list`;
const CLASS_TAGS = `${CLASS_SELECT_INPUT}-tags`;
const CLASS_TAGS_EMPTY = `${CLASS_SELECT_INPUT}-tags-empty`;
const CLASS_TAG = `${CLASS_SELECT_INPUT}-tag`;
const CLASS_TAG_NOT_EVERYWHERE = `${CLASS_SELECT_INPUT}-tag-not-everywhere`;
const CLASS_SHADOW = `${CLASS_SELECT_INPUT}-shadow`;
const CLASS_FIT_HEIGHT = `${CLASS_SELECT_INPUT}-fit-height`;
const CLASS_SELECTED = 'pcui-selected';
const CLASS_HIGHLIGHTED = `${CLASS_SELECT_INPUT}-label-highlighted`;
const CLASS_CREATE_NEW = `${CLASS_SELECT_INPUT}-create-new`;
const CLASS_OPEN = 'pcui-open';
const DEFAULT_BOTTOM_OFFSET = 25;
/**
 * An input that allows selecting from a dropdown or entering tags.
 */
class SelectInput extends Element$1 {
    /**
     * Creates a new SelectInput.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b, _c, _d, _e;
        // main container
        const container = new Container({
            dom: args.dom
        });
        const elementArgs = Object.assign(Object.assign({}, args), { dom: container.dom });
        super(elementArgs);
        this._timeoutLabelValueTabIndex = null;
        this._valueToText = {};
        this._valueToLabel = {};
        this._labelToValue = new Map();
        this._labelHighlighted = null;
        this._disabledOptions = {};
        this._prefix = '';
        this._onInputChange = (value) => {
            if (this._suspendInputChange)
                return;
            if (this._lastInputValue === value)
                return;
            this.open();
            this._lastInputValue = value;
            this._filterOptions(value);
        };
        this._onInputKeyDown = (evt) => {
            if (evt.key === 'Enter' && this.enabled && !this.readOnly) {
                evt.stopPropagation();
                evt.preventDefault();
                // on enter
                let value;
                if (this._labelHighlighted && this._labelToValue.has(this._labelHighlighted)) {
                    value = this._labelToValue.get(this._labelHighlighted);
                }
                else {
                    value = this._input.value;
                }
                if (value !== undefined) {
                    this.focus();
                    this.close();
                    if (this._valueToText[value]) {
                        this._onSelectValue(value);
                    }
                    else if (this._allowCreate) {
                        if (this._createFn) {
                            this._createFn(value);
                        }
                        else {
                            this._onSelectValue(value);
                        }
                    }
                    return;
                }
            }
            this._onKeyDown(evt);
        };
        /**
         * Handles pointer down events on the document. It has to be the document instead of the window
         * because otherwise `pointerdown` is not fired when the PCUI app is running in an iframe.
         * @param evt - Pointer event.
         */
        this._onDocumentPointerDown = (evt) => {
            // Use composedPath to handle clicks in both shadow DOM and regular DOM contexts
            if (!this.dom.contains(evt.composedPath()[0])) {
                this.close();
            }
        };
        this._onKeyDown = (evt) => {
            // close options on ESC and blur
            if (evt.key === 'Escape') {
                this.close();
                return;
            }
            if (evt.key === 'Tab') {
                this.close();
                return;
            }
            if (!this.enabled || this.readOnly)
                return;
            if (evt.key === 'Enter' && !this._allowInput) {
                if (this._labelHighlighted && this._labelToValue.has(this._labelHighlighted)) {
                    this._onSelectValue(this._labelToValue.get(this._labelHighlighted));
                    this.close();
                }
                return;
            }
            if (evt.key !== 'ArrowUp' && evt.key !== 'ArrowDown') {
                return;
            }
            evt.stopPropagation();
            evt.preventDefault();
            if ((this._allowInput || this.multiSelect) && this._containerOptions.hidden) {
                this.open();
                return;
            }
            if (this._containerOptions.hidden) {
                if (!this._options.length)
                    return;
                let index = -1;
                for (let i = 0; i < this._options.length; i++) {
                    if (this._options[i].v === this.value) {
                        index = i;
                        break;
                    }
                }
                if (evt.key === 'ArrowUp') {
                    index--;
                }
                else if (evt.key === 'ArrowDown') {
                    index++;
                }
                if (index >= 0 && index < this._options.length) {
                    this._onSelectValue(this._options[index].v);
                }
            }
            else {
                if (!this._containerOptions.dom.childNodes.length)
                    return;
                if (!this._labelHighlighted) {
                    this._highlightLabel(this._containerOptions.dom.childNodes[0].ui);
                }
                else {
                    let highlightedLabelDom = this._labelHighlighted.dom;
                    do {
                        if (evt.key === 'ArrowUp') {
                            highlightedLabelDom = highlightedLabelDom.previousSibling;
                        }
                        else if (evt.key === 'ArrowDown') {
                            highlightedLabelDom = highlightedLabelDom.nextSibling;
                        }
                    } while (highlightedLabelDom && highlightedLabelDom.ui.hidden);
                    if (highlightedLabelDom) {
                        this._highlightLabel(highlightedLabelDom.ui);
                    }
                }
            }
        };
        this._onPointerDown = () => {
            if (!this._allowInput) {
                this.focus();
            }
        };
        this._onFocus = () => {
            this.class.add(CLASS_FOCUS);
            this.emit('focus');
            if (!this._input.hidden) {
                this.open();
            }
        };
        this._onBlur = () => {
            this.class.remove(CLASS_FOCUS);
            this.emit('blur');
        };
        this._onWheel = (evt) => {
            // prevent scrolling on other stuff like the viewport
            // when we are scrolling on the select input
            evt.stopPropagation();
        };
        this._container = container;
        this._container.parent = this;
        this.class.add(CLASS_SELECT_INPUT);
        this._containerValue = new Container({
            class: CLASS_SELECT_INPUT_CONTAINER_VALUE
        });
        this._container.append(this._containerValue);
        // focus / hover shadow element
        this._domShadow = document.createElement('div');
        this._domShadow.classList.add(CLASS_SHADOW);
        this._containerValue.append(this._domShadow);
        this._allowInput = args.allowInput;
        if (this._allowInput) {
            this.class.add(CLASS_ALLOW_INPUT);
        }
        this._allowCreate = args.allowCreate;
        this._createFn = args.createFn;
        this._createLabelText = args.createLabelText;
        // displays current value
        this._labelValue = new Label({
            class: CLASS_VALUE,
            tabIndex: 0
        });
        this._labelValue.on('click', () => {
            if (this.enabled && !this.readOnly) {
                // toggle dropdown list
                this.toggle();
            }
        });
        this._containerValue.append(this._labelValue);
        // dropdown icon
        this._labelIcon = new Label({
            class: CLASS_ICON,
            hidden: args.allowInput && args.multiSelect
        });
        this._containerValue.append(this._labelIcon);
        // input for searching or adding new entries
        this._input = new TextInput({
            class: CLASS_INPUT,
            blurOnEnter: false,
            keyChange: true
        });
        this._containerValue.append(this._input);
        this._lastInputValue = '';
        this._suspendInputChange = false;
        this._input.on('change', this._onInputChange);
        this._input.on('keydown', this._onInputKeyDown);
        this._input.on('focus', this._onFocus);
        this._input.on('blur', this._onBlur);
        if (args.placeholder) {
            this.placeholder = args.placeholder;
        }
        // dropdown list
        this._containerOptions = new Container({
            class: CLASS_LIST,
            hidden: true
        });
        this._containerValue.append(this._containerOptions);
        // tags container
        this._containerTags = new Container({
            class: CLASS_TAGS,
            flex: true,
            flexDirection: 'row',
            hidden: true
        });
        this._container.append(this._containerTags);
        if (args.multiSelect) {
            this.class.add(CLASS_MULTI_SELECT);
            this._containerTags.hidden = false;
        }
        // events
        this._labelValue.dom.addEventListener('keydown', this._onKeyDown);
        this._labelValue.dom.addEventListener('focus', this._onFocus);
        this._labelValue.dom.addEventListener('blur', this._onBlur);
        this._labelValue.dom.addEventListener('pointerdown', this._onPointerDown);
        this._containerOptions.dom.addEventListener('wheel', this._onWheel, { passive: true });
        this.on('hide', () => {
            this.close();
        });
        this._type = (_a = args.type) !== null && _a !== void 0 ? _a : 'string';
        this.invalidOptions = (_b = args.invalidOptions) !== null && _b !== void 0 ? _b : [];
        this.options = (_c = args.options) !== null && _c !== void 0 ? _c : [];
        this._optionsFn = args.optionsFn;
        this._allowNull = args.allowNull;
        this._values = null;
        if (args.value !== undefined) {
            this.value = args.value;
        }
        else if (args.defaultValue) {
            this.value = args.defaultValue;
        }
        else {
            this.value = null;
        }
        this._renderChanges = (_d = args.renderChanges) !== null && _d !== void 0 ? _d : false;
        this.on('change', () => {
            this._updateInputFieldsVisibility();
            if (this.renderChanges && !this.multiSelect) {
                this._labelValue.flash();
            }
        });
        this._updateInputFieldsVisibility(false);
        this._onSelect = args.onSelect;
        this.fallbackOrder = args.fallbackOrder;
        this.disabledOptions = args.disabledOptions;
        this._prefix = (_e = args.prefix) !== null && _e !== void 0 ? _e : '';
    }
    destroy() {
        if (this._destroyed)
            return;
        this._labelValue.dom.removeEventListener('keydown', this._onKeyDown);
        this._labelValue.dom.removeEventListener('pointerdown', this._onPointerDown);
        this._labelValue.dom.removeEventListener('focus', this._onFocus);
        this._labelValue.dom.removeEventListener('blur', this._onBlur);
        this._containerOptions.dom.removeEventListener('wheel', this._onWheel);
        window.removeEventListener('keydown', this._onKeyDown);
        document.removeEventListener('pointerdown', this._onDocumentPointerDown);
        if (this._timeoutLabelValueTabIndex) {
            cancelAnimationFrame(this._timeoutLabelValueTabIndex);
            this._timeoutLabelValueTabIndex = null;
        }
        super.destroy();
    }
    _initializeCreateLabel() {
        const container = new Container({
            class: CLASS_CREATE_NEW,
            flex: true,
            flexDirection: 'row'
        });
        const label = new Label({
            text: this._input.value,
            tabIndex: -1
        });
        container.append(label);
        let evtChange = this._input.on('change', (value) => {
            // check if label is destroyed
            // during change event
            if (label.destroyed)
                return;
            label.text = value;
            if (this.invalidOptions && this.invalidOptions.indexOf(value) !== -1) {
                if (!container.hidden) {
                    container.hidden = true;
                    this._resizeShadow();
                }
            }
            else {
                if (container.hidden) {
                    container.hidden = false;
                    this._resizeShadow();
                }
            }
        });
        container.on('click', (e) => {
            e.stopPropagation();
            const text = label.text;
            this.focus();
            this.close();
            if (this._createFn) {
                this._createFn(text);
            }
            else if (text) {
                this._onSelectValue(text);
            }
        });
        label.on('destroy', () => {
            evtChange.unbind();
            evtChange = null;
        });
        const labelCreateText = new Label({
            text: this._createLabelText
        });
        container.append(labelCreateText);
        this._containerOptions.append(container);
        return container;
    }
    _convertSingleValue(value) {
        if (value === null && this._allowNull)
            return value;
        if (this._type === 'string') {
            if (!value) {
                value = '';
            }
            else {
                value = value.toString();
            }
        }
        else if (this._type === 'number') {
            if (!value) {
                value = 0;
            }
            else {
                value = parseInt(value, 10);
            }
        }
        else if (this._type === 'boolean') {
            return !!value;
        }
        return value;
    }
    _convertValue(value) {
        if (value === null && this._allowNull)
            return value;
        if (this.multiSelect) {
            if (!Array.isArray(value))
                return value;
            return value.map(val => this._convertSingleValue(val));
        }
        return this._convertSingleValue(value);
    }
    // Update our value with the specified selected option
    _onSelectValue(value) {
        value = this._convertSingleValue(value);
        if (!this.multiSelect) {
            this.value = value;
            return;
        }
        if (this._values) {
            let dirty = false;
            this._values.forEach((arr) => {
                if (!arr) {
                    arr = [value];
                    dirty = true;
                }
                else {
                    if (arr.indexOf(value) === -1) {
                        arr.push(value);
                        dirty = true;
                    }
                }
            });
            if (dirty) {
                this._onMultipleValuesChange(this._values);
                this.emit('change', this.value);
                if (this._binding) {
                    this._binding.addValues([value]);
                }
            }
        }
        else {
            if (!this._value || !Array.isArray(this._value)) {
                this.value = [value];
            }
            else {
                if (this._value.indexOf(value) === -1) {
                    this._value.push(value);
                    this._addTag(value);
                    this.emit('change', this.value);
                    if (this._binding) {
                        this._binding.addValues([value]);
                    }
                }
            }
        }
    }
    _highlightLabel(label) {
        if (this._labelHighlighted === label)
            return;
        if (this._labelHighlighted) {
            this._labelHighlighted.class.remove(CLASS_HIGHLIGHTED);
        }
        this._labelHighlighted = label;
        if (this._labelHighlighted) {
            this._labelHighlighted.class.add(CLASS_HIGHLIGHTED);
            // scroll into view if necessary
            const labelTop = this._labelHighlighted.dom.offsetTop;
            const scrollTop = this._containerOptions.dom.scrollTop;
            if (labelTop < scrollTop) {
                this._containerOptions.dom.scrollTop = labelTop;
            }
            else if (labelTop + this._labelHighlighted.height > this._containerOptions.height + scrollTop) {
                this._containerOptions.dom.scrollTop = labelTop + this._labelHighlighted.height - this._containerOptions.height;
            }
        }
    }
    // when the value is changed show the correct title
    _onValueChange(value) {
        if (!this.multiSelect) {
            this._labelValue.value = this._prefix + (this._valueToText[String(value)] || '');
            value = String(value);
            for (const key in this._valueToLabel) {
                const label = this._valueToLabel[key];
                if (key === value) {
                    label.class.add(CLASS_SELECTED);
                }
                else {
                    label.class.remove(CLASS_SELECTED);
                }
            }
        }
        else {
            this._labelValue.value = '';
            this._containerTags.clear();
            this._containerTags.class.add(CLASS_TAGS_EMPTY);
            if (value && Array.isArray(value)) {
                for (const val of value) {
                    this._addTag(val);
                    const label = this._valueToLabel[String(val)];
                    if (label) {
                        label.class.add(CLASS_SELECTED);
                    }
                }
                for (const key in this._valueToLabel) {
                    const label = this._valueToLabel[key];
                    if (value.indexOf(this._convertSingleValue(key)) !== -1) {
                        label.class.add(CLASS_SELECTED);
                    }
                    else {
                        label.class.remove(CLASS_SELECTED);
                    }
                }
            }
        }
    }
    _onMultipleValuesChange(values) {
        this._labelValue.value = '';
        this._containerTags.clear();
        this._containerTags.class.add(CLASS_TAGS_EMPTY);
        const tags = {};
        const valueCounts = {};
        values.forEach((arr) => {
            if (!arr)
                return;
            arr.forEach((val) => {
                if (!tags[val]) {
                    tags[val] = this._addTag(val);
                    valueCounts[val] = 1;
                }
                else {
                    valueCounts[val]++;
                }
            });
        });
        // add special class to tags that do not exist everywhere
        for (const val in valueCounts) {
            if (valueCounts[val] !== values.length) {
                tags[val].class.add(CLASS_TAG_NOT_EVERYWHERE);
                const label = this._valueToLabel[String(val)];
                if (label) {
                    label.class.remove(CLASS_SELECTED);
                }
            }
        }
    }
    _addTag(value) {
        const container = new Container({
            flex: true,
            flexDirection: 'row',
            class: CLASS_TAG
        });
        container.append(new Label({
            text: this._valueToText[String(value)] || String(value)
        }));
        const btnRemove = new Button({
            size: 'small',
            icon: 'E132',
            tabIndex: -1
        });
        container.append(btnRemove);
        btnRemove.on('click', () => this._removeTag(container, value));
        this._containerTags.append(container);
        this._containerTags.class.remove(CLASS_TAGS_EMPTY);
        const label = this._valueToLabel[String(value)];
        if (label) {
            label.class.add(CLASS_SELECTED);
        }
        // @ts-ignore
        container.value = value;
        return container;
    }
    _removeTag(tagElement, value) {
        tagElement.destroy();
        const label = this._valueToLabel[String(value)];
        if (label) {
            label.class.remove(CLASS_SELECTED);
        }
        if (this._values) {
            this._values.forEach((arr) => {
                if (!arr)
                    return;
                const idx = arr.indexOf(value);
                if (idx !== -1) {
                    arr.splice(idx, 1);
                }
            });
        }
        else if (this._value && Array.isArray(this._value)) {
            const idx = this._value.indexOf(value);
            if (idx !== -1) {
                this._value.splice(idx, 1);
            }
        }
        this.emit('change', this.value);
        if (this._binding) {
            this._binding.removeValues([value]);
        }
    }
    _filterOptions(filter) {
        // first remove all options
        // then search the options for best matches
        // and add them back in best match order
        const containerDom = this._containerOptions.dom;
        while (containerDom.firstChild) {
            containerDom.removeChild(containerDom.lastChild);
        }
        if (filter) {
            const searchResults = searchItems(this._options, 't', filter);
            searchResults.forEach((result) => {
                const label = this._valueToLabel[String(result.v)];
                containerDom.appendChild(label.dom);
            });
        }
        else {
            for (const option of this._options) {
                const label = this._valueToLabel[String(option.v)];
                containerDom.appendChild(label.dom);
            }
        }
        // append create label in the end
        if (this._createLabelContainer) {
            containerDom.appendChild(this._createLabelContainer.dom);
        }
        if (containerDom.firstChild) {
            this._highlightLabel(containerDom.firstChild.ui);
        }
        this._resizeShadow();
    }
    _resizeShadow() {
        this._domShadow.style.height = `${this._containerValue.height + this._containerOptions.height}px`;
    }
    _updateInputFieldsVisibility(focused) {
        let showInput = false;
        let focusInput = false;
        if (this._allowInput) {
            if (focused) {
                showInput = true;
                focusInput = true;
            }
            else {
                showInput = this.multiSelect || !this._valueToLabel[this.value];
            }
        }
        this._labelValue.hidden = showInput;
        this._labelIcon.hidden = showInput;
        this._input.hidden = !showInput;
        if (focusInput) {
            this._input.focus();
        }
        if (!this._labelValue.hidden) {
            // prevent label from being focused
            // right after input gets unfocused
            this._labelValue.tabIndex = -1;
            if (!this._timeoutLabelValueTabIndex) {
                this._timeoutLabelValueTabIndex = requestAnimationFrame(() => {
                    this._timeoutLabelValueTabIndex = null;
                    this._labelValue.tabIndex = 0;
                });
            }
        }
    }
    focus() {
        if (this._input.hidden) {
            this._labelValue.dom.focus();
        }
        else {
            this._input.focus();
        }
    }
    blur() {
        if (this._allowInput) {
            this._input.blur();
        }
        else {
            this._labelValue.dom.blur();
        }
    }
    /**
     * Opens the dropdown menu.
     */
    open() {
        if (!this._containerOptions.hidden || !this.enabled || this.readOnly)
            return;
        this._updateInputFieldsVisibility(true);
        // auto-update options if necessary
        if (this._optionsFn) {
            this.options = this._optionsFn();
        }
        if (this._containerOptions.dom.childNodes.length === 0)
            return;
        // highlight label that displays current value
        this._containerOptions.forEachChild((label) => {
            label.hidden = false;
            if (this._labelToValue.get(label) === this.value) {
                this._highlightLabel(label);
            }
        });
        if (!this._labelHighlighted) {
            this._highlightLabel(this._containerOptions.dom.childNodes[0].ui);
        }
        // show options
        this._containerOptions.hidden = false;
        this.class.add(CLASS_OPEN);
        // register keydown on entire window
        window.addEventListener('keydown', this._onKeyDown);
        document.addEventListener('pointerdown', this._onDocumentPointerDown);
        // if the dropdown list goes below the window show it above the field
        const startField = this._allowInput ? this._input.dom : this._labelValue.dom;
        const rect = startField.getBoundingClientRect();
        let fitHeight = (rect.bottom + this._containerOptions.height + DEFAULT_BOTTOM_OFFSET >= window.innerHeight);
        if (fitHeight && rect.top - this._containerOptions.height < 0) {
            // if showing it above the field means that some of it will not be visible
            // then show it below instead and adjust the max height to the maximum available space
            fitHeight = false;
            this._containerOptions.style.maxHeight = `${window.innerHeight - rect.bottom - DEFAULT_BOTTOM_OFFSET}px`;
        }
        if (fitHeight) {
            this.class.add(CLASS_FIT_HEIGHT);
        }
        else {
            this.class.remove(CLASS_FIT_HEIGHT);
        }
        // resize the outer shadow to fit the element and the dropdown list
        // we need this because the dropdown list is position: absolute
        this._resizeShadow();
    }
    /**
     * Closes the dropdown menu.
     */
    close() {
        // there is a potential bug here if the user has set a max height
        // themselves then this will be overridden
        this._containerOptions.style.maxHeight = '';
        this._highlightLabel(null);
        this._updateInputFieldsVisibility(false);
        this._suspendInputChange = true;
        this._input.value = '';
        if (this._lastInputValue) {
            this._lastInputValue = '';
            this._filterOptions(null);
        }
        this._suspendInputChange = false;
        if (this._containerOptions.hidden)
            return;
        this._containerOptions.hidden = true;
        this._domShadow.style.height = '';
        this.class.remove(CLASS_OPEN);
        window.removeEventListener('keydown', this._onKeyDown);
        document.removeEventListener('pointerdown', this._onDocumentPointerDown);
    }
    /**
     * Toggles the dropdown menu.
     */
    toggle() {
        if (this._containerOptions.hidden) {
            this.open();
        }
        else {
            this.close();
        }
    }
    unlink() {
        super.unlink();
        if (!this._containerOptions.hidden) {
            this.close();
        }
    }
    _updateValue(value) {
        if (value === this._value)
            return;
        this._value = value;
        this._onValueChange(value);
        if (!this._suppressChange) {
            this.emit('change', value);
        }
        if (this._binding) {
            this._binding.setValue(value);
        }
    }
    _updateDisabledValue(value) {
        const labels = {};
        this._containerOptions.forEachChild((label) => {
            labels[label.dom.id] = label;
            if (this._disabledOptions[label.dom.id]) {
                label.enabled = false;
                label.text = this._disabledOptions[label.dom.id];
            }
            else {
                label.enabled = true;
                label.text = this._valueToText[label.dom.id];
            }
            label.class.remove(CLASS_DISABLED_VALUE);
        });
        const disabledValue = this._disabledOptions[value] ? value : null;
        let newValue = null;
        if (disabledValue) {
            if (this._fallbackOrder) {
                for (let i = 0; i < this._fallbackOrder.length; i++) {
                    if (this._fallbackOrder[i] === value)
                        continue;
                    newValue = this._fallbackOrder[i];
                    break;
                }
            }
            this.disabledValue = disabledValue;
            labels[disabledValue].class.add(CLASS_DISABLED_VALUE);
        }
        else if (this._disabledValue) {
            newValue = this._disabledValue;
            this.disabledValue = null;
        }
        else {
            newValue = value;
            this.disabledValue = null;
        }
        return newValue;
    }
    set options(value) {
        if (this._options && JSON.stringify(this._options) === JSON.stringify(value))
            return;
        this._containerOptions.clear();
        this._labelHighlighted = null;
        this._valueToText = {};
        this._valueToLabel = {};
        this._options = value;
        // store each option value -> title pair in the optionsIndex
        for (const option of this._options) {
            this._valueToText[String(option.v)] = option.t;
            if (option.v === '')
                return;
            const label = new Label({
                text: option.t,
                tabIndex: -1,
                id: String(option.v)
            });
            this._labelToValue.set(label, option.v);
            // store labels in an index too
            this._valueToLabel[String(option.v)] = label;
            // on clicking an option set it as the value and close the dropdown list
            label.on('click', (e) => {
                e.stopPropagation();
                this._onSelectValue(option.v);
                this.close();
                if (this._onSelect) {
                    this._onSelect(this.value);
                }
            });
            this._containerOptions.append(label);
        }
        this._createLabelContainer = null;
        if (this._createLabelText) {
            this._createLabelContainer = this._initializeCreateLabel();
        }
        if (this.multiSelect && this._values) {
            this._onMultipleValuesChange(this._values);
        }
        else {
            this._onValueChange(this.value);
        }
        if (this._lastInputValue) {
            this._filterOptions(this._lastInputValue);
        }
    }
    get options() {
        return this._options.slice();
    }
    set invalidOptions(value) {
        this._invalidOptions = value || null;
    }
    get invalidOptions() {
        return this._invalidOptions;
    }
    set disabledValue(value) {
        this._disabledValue = value;
        if (this._disabledValue !== null) {
            this.class.add(CLASS_HAS_DISABLED_VALUE);
        }
        else {
            this.class.remove(CLASS_HAS_DISABLED_VALUE);
        }
    }
    set disabledOptions(value) {
        if (JSON.stringify(this._disabledOptions) === JSON.stringify(value))
            return;
        this._disabledOptions = value || {};
        const newValue = this._updateDisabledValue(this._value);
        this._updateValue(newValue);
    }
    set fallbackOrder(value) {
        this._fallbackOrder = value || null;
    }
    get multiSelect() {
        return this.class.contains(CLASS_MULTI_SELECT);
    }
    set value(value) {
        this._values = null;
        this._suspendInputChange = true;
        this._input.value = '';
        if (this._lastInputValue) {
            this._lastInputValue = '';
            this._filterOptions(null);
        }
        this._suspendInputChange = false;
        this.class.remove(CLASS_MULTIPLE_VALUES);
        value = this._convertValue(value);
        if (this._value === value || this.multiSelect && this._value && this._value.equals(value)) {
            // if the value is null because we are showing multiple values
            // but someone wants to actually set the value of all observers to null
            // then make sure we do not return early
            if (value !== null || !this._allowNull || !this.class.contains(CLASS_MULTIPLE_VALUES)) {
                return;
            }
        }
        this.disabledValue = null;
        this._updateValue(value);
    }
    get value() {
        if (!this.multiSelect) {
            return this._value;
        }
        // if multi-select then construct an array
        // value from the tags that are currently visible
        const result = [];
        this._containerTags.dom.childNodes.forEach((dom) => {
            // @ts-ignore
            result.push(dom.ui.value);
        });
        return result;
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        values = values.map((value) => {
            return this._convertValue(value);
        });
        let different = false;
        const value = values[0];
        const multiSelect = this.multiSelect;
        this._values = null;
        for (let i = 1; i < values.length; i++) {
            if (values[i] !== value && (!multiSelect || !values[i] || !values[i].equals(value))) {
                different = true;
                break;
            }
        }
        if (different) {
            this._labelValue.values = values;
            // show all different tags
            if (multiSelect) {
                this._values = values;
                this._value = null;
                this._onMultipleValuesChange(this._values);
                this.emit('change', this.value);
            }
            else {
                if (this._value !== null) {
                    this._value = null;
                    this.emit('change', null);
                }
            }
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            this.value = values[0];
        }
    }
    set placeholder(value) {
        this._input.placeholder = value;
    }
    get placeholder() {
        return this._input.placeholder;
    }
    set renderChanges(value) {
        this._renderChanges = value;
    }
    get renderChanges() {
        return this._renderChanges;
    }
}
Element$1.register('select', SelectInput, { renderChanges: true });
Element$1.register('multiselect', SelectInput, { multiSelect: true, renderChanges: true });
Element$1.register('tags', SelectInput, { allowInput: true, allowCreate: true, multiSelect: true, renderChanges: true });

var _a;
const CLASS_SLIDER = 'pcui-slider';
const CLASS_SLIDER_CONTAINER = `${CLASS_SLIDER}-container`;
const CLASS_SLIDER_BAR = `${CLASS_SLIDER}-bar`;
const CLASS_SLIDER_HANDLE = `${CLASS_SLIDER}-handle`;
const CLASS_SLIDER_ACTIVE = `${CLASS_SLIDER}-active`;
const IS_CHROME = /Chrome\//.test((_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.userAgent);
/**
 * The SliderInput shows a NumericInput and a slider widget next to it. It acts as a proxy of the
 * NumericInput.
 */
class SliderInput extends Element$1 {
    /**
     * Creates a new SliderInput.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b, _c, _d, _e;
        super(args);
        this._historyCombine = false;
        this._historyPostfix = null;
        this._cursorHandleOffset = 0;
        this._pointerId = null;
        this._onPointerDown = (evt) => {
            if ((evt.pointerType === 'mouse' && evt.button !== 0) || !this.enabled || this.readOnly || this._pointerId !== null)
                return;
            evt.stopPropagation();
            this._domSlider.setPointerCapture(evt.pointerId);
            this._pointerId = evt.pointerId;
            this._onSlideStart(evt.pageX);
        };
        this._onPointerMove = (evt) => {
            if (evt.pointerId !== this._pointerId)
                return;
            evt.stopPropagation();
            evt.preventDefault();
            this._onSlideMove(evt.pageX);
        };
        this._onPointerUp = (evt) => {
            if (evt.pointerId !== this._pointerId || this._pointerId === null)
                return;
            evt.stopPropagation();
            this._domSlider.releasePointerCapture(evt.pointerId);
            this._onSlideEnd(evt.pageX);
            this._pointerId = null;
        };
        this._onKeyDown = (evt) => {
            if (evt.key === 'Escape') {
                this.blur();
                return;
            }
            if (!this.enabled || this.readOnly)
                return;
            // move slider with left / right arrow keys
            if (evt.key !== 'ArrowLeft' && evt.key !== 'ArrowRight')
                return;
            evt.stopPropagation();
            evt.preventDefault();
            let x = evt.key === 'ArrowLeft' ? -1 : 1;
            if (evt.shiftKey) {
                x *= 10;
            }
            this.value += x * this.step;
        };
        this.class.add(CLASS_SLIDER);
        const numericInput = new NumericInput({
            allowNull: args.allowNull,
            hideSlider: true,
            min: args.min,
            max: args.max,
            keyChange: args.keyChange,
            placeholder: args.placeholder,
            precision: (_a = args.precision) !== null && _a !== void 0 ? _a : 2,
            renderChanges: args.renderChanges,
            step: args.step
        });
        // propagate change event
        numericInput.on('change', (value) => {
            this._onValueChange(value);
        });
        // propagate focus / blur events
        numericInput.on('focus', () => {
            this.emit('focus');
        });
        numericInput.on('blur', () => {
            this.emit('blur');
        });
        numericInput.parent = this;
        this.dom.appendChild(numericInput.dom);
        this._numericInput = numericInput;
        this._sliderMin = (_c = (_b = args.sliderMin) !== null && _b !== void 0 ? _b : args.min) !== null && _c !== void 0 ? _c : 0;
        this._sliderMax = (_e = (_d = args.sliderMax) !== null && _d !== void 0 ? _d : args.max) !== null && _e !== void 0 ? _e : 1;
        this._domSlider = document.createElement('div');
        this._domSlider.classList.add(CLASS_SLIDER_CONTAINER);
        this.dom.appendChild(this._domSlider);
        this._domBar = document.createElement('div');
        this._domBar.classList.add(CLASS_SLIDER_BAR);
        this._domBar.ui = this;
        this._domSlider.appendChild(this._domBar);
        this._domHandle = document.createElement('div');
        this._domHandle.ui = this;
        this._domHandle.tabIndex = 0;
        this._domHandle.classList.add(CLASS_SLIDER_HANDLE);
        this._domBar.appendChild(this._domHandle);
        this._domSlider.addEventListener('pointerdown', this._onPointerDown);
        this._domHandle.addEventListener('keydown', this._onKeyDown);
        if (args.value !== undefined) {
            this.value = args.value;
        }
        if (args.values !== undefined) {
            this.values = args.values;
        }
        // update the handle in case a 0 value has been
        // passed through the constructor
        if (this.value === 0) {
            this._updateHandle(0);
        }
    }
    destroy() {
        if (this._destroyed)
            return;
        this._domSlider.removeEventListener('pointerdown', this._onPointerDown);
        this._domHandle.removeEventListener('keydown', this._onKeyDown);
        super.destroy();
    }
    _updateHandle(value) {
        const left = Math.max(0, Math.min(1, ((value || 0) - this._sliderMin) / (this._sliderMax - this._sliderMin))) * 100;
        const handleWidth = this._domHandle.getBoundingClientRect().width;
        this._domHandle.style.left = `calc(${left}% + ${handleWidth / 2}px)`;
    }
    _onValueChange(value) {
        this._updateHandle(value);
        if (!this._suppressChange) {
            this.emit('change', value);
        }
        if (this._binding) {
            this._binding.setValue(value);
        }
    }
    // Calculates the distance in pixels between
    // the cursor x and the middle of the handle.
    // If the cursor is not on the handle sets the offset to 0
    _calculateCursorHandleOffset(pageX) {
        // not sure why but the left side needs a margin of a couple of pixels
        // to properly determine if the cursor is on the handle (in Chrome)
        const margin = IS_CHROME ? 2 : 0;
        const rect = this._domHandle.getBoundingClientRect();
        const left = rect.left - margin;
        const right = rect.right;
        if (pageX >= left && pageX <= right) {
            this._cursorHandleOffset = pageX - (left + (right - left) / 2);
        }
        else {
            this._cursorHandleOffset = 0;
        }
        return this._cursorHandleOffset;
    }
    _onSlideStart(pageX) {
        this._domHandle.focus();
        this._domSlider.addEventListener('pointermove', this._onPointerMove);
        this._domSlider.addEventListener('pointerup', this._onPointerUp);
        this.class.add(CLASS_SLIDER_ACTIVE);
        // calculate the cursor - handle offset. If there is
        // an offset that means the cursor is on the handle so
        // do not move the handle until the cursor moves.
        if (!this._calculateCursorHandleOffset(pageX)) {
            this._onSlideMove(pageX);
        }
        if (this.binding) {
            this._historyCombine = this.binding.historyCombine;
            this._historyPostfix = this.binding.historyPostfix;
            this.binding.historyCombine = true;
            this.binding.historyPostfix = `(${Date.now()})`;
        }
    }
    _onSlideMove(pageX) {
        const rect = this._domBar.getBoundingClientRect();
        // reduce pageX by the initial cursor - handle offset
        pageX -= this._cursorHandleOffset;
        const x = Math.max(0, Math.min(1, (pageX - rect.left) / rect.width));
        const range = this._sliderMax - this._sliderMin;
        let value = (x * range) + this._sliderMin;
        value = parseFloat(value.toFixed(this.precision));
        this.value = value;
    }
    _onSlideEnd(pageX) {
        // when slide ends only move the handle if the cursor is no longer
        // on the handle
        if (!this._calculateCursorHandleOffset(pageX)) {
            this._onSlideMove(pageX);
        }
        this.class.remove(CLASS_SLIDER_ACTIVE);
        this._domSlider.removeEventListener('pointermove', this._onPointerMove);
        this._domSlider.removeEventListener('pointerup', this._onPointerUp);
        if (this.binding) {
            this.binding.historyCombine = this._historyCombine;
            this.binding.historyPostfix = this._historyPostfix;
            this._historyCombine = false;
            this._historyPostfix = null;
        }
    }
    focus() {
        this._numericInput.focus();
    }
    blur() {
        this._domHandle.blur();
        this._numericInput.blur();
    }
    /**
     * Sets the minimum value that the slider field can take.
     */
    set sliderMin(value) {
        if (this._sliderMin === value)
            return;
        this._sliderMin = value;
        this._updateHandle(this.value);
    }
    /**
     * Gets the minimum value that the slider field can take.
     */
    get sliderMin() {
        return this._sliderMin;
    }
    /**
     * Sets the maximum value that the slider field can take.
     */
    set sliderMax(value) {
        if (this._sliderMax === value)
            return;
        this._sliderMax = value;
        this._updateHandle(this.value);
    }
    /**
     * Gets the maximum value that the slider field can take.
     */
    get sliderMax() {
        return this._sliderMax;
    }
    set value(value) {
        this._numericInput.value = value;
        if (this._numericInput.class.contains(CLASS_MULTIPLE_VALUES)) {
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            this.class.remove(CLASS_MULTIPLE_VALUES);
        }
    }
    get value() {
        return this._numericInput.value;
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        this._numericInput.values = values;
        if (this._numericInput.class.contains(CLASS_MULTIPLE_VALUES)) {
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            this.class.remove(CLASS_MULTIPLE_VALUES);
        }
    }
    set renderChanges(value) {
        this._numericInput.renderChanges = value;
    }
    get renderChanges() {
        return this._numericInput.renderChanges;
    }
    /**
     * Sets the minimum value that the numeric input field can take.
     */
    set min(value) {
        this._numericInput.min = value;
    }
    /**
     * Gets the minimum value that the numeric input field can take.
     */
    get min() {
        return this._numericInput.min;
    }
    /**
     * Sets the maximum value that the numeric input field can take.
     */
    set max(value) {
        this._numericInput.max = value;
    }
    /**
     * Gets the maximum value that the numeric input field can take.
     */
    get max() {
        return this._numericInput.max;
    }
    /**
     * Sets the amount that the value will be increased or decreased when using the arrow keys. Holding Shift will use 10x the step.
     */
    set step(value) {
        this._numericInput.step = value;
    }
    /**
     * Gets the amount that the value will be increased or decreased when using the arrow keys.
     */
    get step() {
        return this._numericInput.step;
    }
    /**
     * Sets the maximum number of decimals a value can take.
     */
    set precision(value) {
        this._numericInput.precision = value;
    }
    /**
     * Gets the maximum number of decimals a value can take.
     */
    get precision() {
        return this._numericInput.precision;
    }
    set keyChange(value) {
        this._numericInput.keyChange = value;
    }
    get keyChange() {
        return this._numericInput.keyChange;
    }
    set placeholder(value) {
        this._numericInput.placeholder = value;
    }
    get placeholder() {
        return this._numericInput.placeholder;
    }
}
Element$1.register('slider', SliderInput, { renderChanges: true });

const CLASS_TEXT_AREA_INPUT = 'pcui-text-area-input';
const CLASS_TEXT_AREA_INPUT_RESIZABLE = `${CLASS_TEXT_AREA_INPUT}-resizable`;
const CLASS_TEXT_AREA_INPUT_RESIZABLE_NONE = `${CLASS_TEXT_AREA_INPUT_RESIZABLE}-none`;
const CLASS_TEXT_AREA_INPUT_RESIZABLE_BOTH = `${CLASS_TEXT_AREA_INPUT_RESIZABLE}-both`;
const CLASS_TEXT_AREA_INPUT_RESIZABLE_HORIZONTAL = `${CLASS_TEXT_AREA_INPUT_RESIZABLE}-horizontal`;
const CLASS_TEXT_AREA_INPUT_RESIZABLE_VERTICAL = `${CLASS_TEXT_AREA_INPUT_RESIZABLE}-vertical`;
/**
 * The TextAreaInput wraps a textarea element. It has the same interface as {@link TextInput}.
 */
class TextAreaInput extends TextInput {
    /**
     * Creates a new TextAreaInput.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        args = Object.assign({
            input: document.createElement('textarea')
        }, args);
        super(args);
        this.class.add(CLASS_TEXT_AREA_INPUT);
        switch (args.resizable) {
            case 'both':
                this.class.add(CLASS_TEXT_AREA_INPUT_RESIZABLE_BOTH);
                break;
            case 'horizontal':
                this.class.add(CLASS_TEXT_AREA_INPUT_RESIZABLE_HORIZONTAL);
                break;
            case 'vertical':
                this.class.add(CLASS_TEXT_AREA_INPUT_RESIZABLE_VERTICAL);
                break;
            case 'none':
            default:
                this.class.add(CLASS_TEXT_AREA_INPUT_RESIZABLE_NONE);
                break;
        }
    }
    _onInputKeyDown(evt) {
        if ((evt.key === 'Escape' && this.blurOnEscape) || (evt.key === 'Enter' && this.blurOnEnter && !evt.shiftKey)) {
            this._domInput.blur();
        }
        this.emit('keydown', evt);
    }
}
Element$1.register('text', TextAreaInput, { renderChanges: true });

const CLASS_VECTOR_INPUT = 'pcui-vector-input';
/**
 * A vector input. The vector can have 2 to 4 dimensions with each dimension being a {@link NumericInput}.
 */
class VectorInput extends Element$1 {
    /**
     * Creates a new VectorInput.
     *
     * @param args - The arguments.
     */
    constructor(args = {}) {
        var _a, _b, _c;
        const elementArgs = Object.assign({}, args);
        // set binding after inputs have been created
        delete elementArgs.binding;
        super(elementArgs);
        this._inputs = [];
        this._applyingChange = false;
        this._bindAllInputs = false;
        this.class.add(CLASS_VECTOR_INPUT);
        const dimensions = Math.max(2, Math.min(4, (_a = args.dimensions) !== null && _a !== void 0 ? _a : 3));
        for (let i = 0; i < dimensions; i++) {
            const input = new NumericInput({
                min: args.min,
                max: args.max,
                precision: (_b = args.precision) !== null && _b !== void 0 ? _b : 7,
                step: (_c = args.step) !== null && _c !== void 0 ? _c : 1,
                stepPrecision: args.stepPrecision,
                renderChanges: args.renderChanges,
                placeholder: args.placeholder ? (Array.isArray(args.placeholder) ? args.placeholder[i] : args.placeholder) : null
            });
            input.on('slider:mousedown', (evt) => {
                this._bindAllInputs = !!evt.altKey;
                if (this._bindAllInputs) {
                    input.focus();
                    for (let i = 0; i < this._inputs.length; i++) {
                        this._inputs[i].class.add(CLASS_FOCUS);
                    }
                    if (this.binding) {
                        this.binding.historyCombine = true;
                    }
                }
            });
            input.on('slider:mouseup', () => {
                this._onInputChange(input);
                this._bindAllInputs = false;
                for (let i = 0; i < this._inputs.length; i++) {
                    this._inputs[i].class.remove(CLASS_FOCUS);
                }
                input.blur();
                if (this.binding) {
                    this.binding.historyCombine = false;
                }
            });
            input.on('change', () => {
                this._onInputChange(input);
            });
            input.on('focus', () => {
                this.emit('focus');
            });
            input.on('blur', () => {
                this.emit('blur');
            });
            this.dom.appendChild(input.dom);
            input.parent = this;
            this._inputs.push(input);
        }
        // set the binding after the inputs have been created
        // because we rely on them in the overridden setter
        if (args.binding) {
            this.binding = args.binding;
        }
        if (args.value !== undefined) {
            this.value = args.value;
        }
    }
    _onInputChange(input) {
        if (this._applyingChange)
            return;
        // check if any of our inputs have the MULTIPLE_VALUES class and if so inherit it for us as well
        const multipleValues = this._inputs.some(input => input.class.contains(CLASS_MULTIPLE_VALUES));
        if (multipleValues) {
            this.class.add(CLASS_MULTIPLE_VALUES);
        }
        else {
            this.class.remove(CLASS_MULTIPLE_VALUES);
        }
        if (this._bindAllInputs) {
            const value = Array(this._inputs.length).fill(input.value);
            const changed = this._updateValue(value);
            if (changed && this._binding) {
                this._binding.setValue(value);
            }
        }
        else {
            this.emit('change', this.value);
        }
    }
    _updateValue(value) {
        this.class.remove(CLASS_MULTIPLE_VALUES);
        if (JSON.stringify(this.value) === JSON.stringify(value))
            return false;
        this._applyingChange = true;
        this._inputs.forEach((input, i) => {
            // disable binding for each individual input when we use
            // the 'value' setter for the whole vector value. That is because
            // we do not want the individual inputs to emit their own binding events
            // since we are setting the whole vector value here
            const binding = input.binding;
            let applyingChange = false;
            if (binding) {
                applyingChange = binding.applyingChange;
                binding.applyingChange = true;
            }
            input.value = (value && value[i] !== undefined ? value[i] : null);
            if (binding) {
                binding.applyingChange = applyingChange;
            }
        });
        this.emit('change', this.value);
        this._applyingChange = false;
        return true;
    }
    link(observers, paths) {
        super.link(observers, paths);
        observers = Array.isArray(observers) ? observers : [observers];
        paths = Array.isArray(paths) ? paths : [paths];
        const useSinglePath = paths.length === 1 || observers.length !== paths.length;
        if (useSinglePath) {
            for (let i = 0; i < this._inputs.length; i++) {
                // link observers to path.i for each dimension
                this._inputs[i].link(observers, `${paths[0]}.${i}`);
            }
        }
        else {
            for (let i = 0; i < this._inputs.length; i++) {
                // link observers to paths[i].i for each dimension
                this._inputs[i].link(observers, paths.map(path => `${path}.${i}`));
            }
        }
    }
    unlink() {
        super.unlink();
        for (const input of this._inputs) {
            input.unlink();
        }
    }
    focus() {
        this._inputs[0].focus();
    }
    blur() {
        for (const input of this._inputs) {
            input.blur();
        }
    }
    set value(value) {
        if (typeof value === 'string') {
            try {
                // try to parse the string
                value = JSON.parse(value);
                // if the string could be converted to an array but some of its values aren't numbers
                // then use a default array also
                if (Array.isArray(value) && value.some(i => !Number.isFinite(i))) {
                    throw new Error('VectorInput value set to string which doesn\'t contain an array of numbers');
                }
            }
            catch (e) {
                console.error(e);
                value = [];
            }
        }
        if (!Array.isArray(value)) {
            value = [];
        }
        const changed = this._updateValue(value);
        if (changed && this._binding) {
            this._binding.setValue(value);
        }
    }
    get value() {
        return this._inputs.map(input => input.value);
    }
    /* eslint accessor-pairs: 0 */
    set values(values) {
        // create an array for each dimension (e.g. one array for x one for y one for z)
        values = this._inputs.map((_, i) => values.map((arr) => {
            return arr ? arr[i] : undefined;
        }));
        this._inputs.forEach((input, i) => {
            input.values = values[i];
        });
    }
    // override binding setter to set a binding clone to
    // each input
    set binding(value) {
        super.binding = value;
        for (const input of this._inputs) {
            input.binding = value ? value.clone() : null;
        }
    }
    // we have to override the getter too because
    // we have overridden the setter
    get binding() {
        return super.binding;
    }
    set placeholder(value) {
        for (let i = 0; i < this._inputs.length; i++) {
            this._inputs[i].placeholder = value[i] || value || null;
        }
    }
    get placeholder() {
        return this._inputs.map(input => input.placeholder);
    }
    /**
     * Get the array of number inputs owned by this vector.
     */
    get inputs() {
        return this._inputs.slice();
    }
    set renderChanges(value) {
        for (const input of this._inputs) {
            input.renderChanges = value;
        }
    }
    get renderChanges() {
        return this._inputs[0].renderChanges;
    }
    /**
     * Sets the minimum value accepted by all inputs of the vector.
     */
    set min(value) {
        for (const input of this._inputs) {
            input.min = value;
        }
    }
    /**
     * Gets the minimum value accepted by all inputs of the vector.
     */
    get min() {
        return this._inputs[0].min;
    }
    /**
     * Sets the maximum value accepted by all inputs of the vector.
     */
    set max(value) {
        for (const input of this._inputs) {
            input.max = value;
        }
    }
    /**
     * Gets the maximum value accepted by all inputs of the vector.
     */
    get max() {
        return this._inputs[0].max;
    }
    /**
     * Sets the maximum number of decimal places supported by all inputs of the vector.
     */
    set precision(value) {
        for (const input of this._inputs) {
            input.precision = value;
        }
    }
    /**
     * Gets the maximum number of decimal places supported by all inputs of the vector.
     */
    get precision() {
        return this._inputs[0].precision;
    }
    /**
     * Sets the amount that the value will be increased or decreased when using the arrow keys and
     * the slider input for all inputs of the vector.
     */
    set step(value) {
        for (const input of this._inputs) {
            input.step = value;
        }
    }
    /**
     * Gets the amount that the value will be increased or decreased when using the arrow keys and
     * the slider input for all inputs of the vector.
     */
    get step() {
        return this._inputs[0].step;
    }
}
Element$1.register('vec2', VectorInput, { dimensions: 2, renderChanges: true });
Element$1.register('vec3', VectorInput, { dimensions: 3, renderChanges: true });
Element$1.register('vec4', VectorInput, { dimensions: 4, renderChanges: true });

/**
 * PCUI is a front-end framework designed for creating user interfaces in web applications. It is
 * particularly well-suited for building browser-based tools. It offers a comprehensive set of UI
 * components like buttons, sliders, menus and data inputs.
 *
 * PCUI is written in TypeScript. The API can be used from both TypeScript and JavaScript. A React
 * wrapper is provided for easy integration with React applications.
 *
 * @module PCUI
 */
/**
 * The version of the PCUI library. This is a string in semantic version format of `major.minor.patch`.
 */
const version$2 = '5.2.3';
/**
 * The git revision of the PCUI library. This is a string of the git commit hash.
 */
const revision$1 = 'be45dc9';

/**
 * Logs a frame number.
 *
 * @category Debug
 */ const TRACEID_RENDER_FRAME = 'RenderFrame';
/**
 * Logs a frame time.
 *
 * @category Debug
 */ const TRACEID_RENDER_FRAME_TIME = 'RenderFrameTime';
/**
 * Logs basic information about generated render passes.
 *
 * @category Debug
 */ const TRACEID_RENDER_PASS = 'RenderPass';
/**
 * Logs additional detail for render passes.
 *
 * @category Debug
 */ const TRACEID_RENDER_PASS_DETAIL = 'RenderPassDetail';
/**
 * Logs render actions created by the layer composition. Only executes when the
 * layer composition changes.
 *
 * @category Debug
 */ const TRACEID_RENDER_ACTION = 'RenderAction';
/**
 * Logs the allocation of render targets.
 *
 * @category Debug
 */ const TRACEID_RENDER_TARGET_ALLOC = 'RenderTargetAlloc';
/**
 * Logs the allocation of textures.
 *
 * @category Debug
 */ const TRACEID_TEXTURE_ALLOC = 'TextureAlloc';
/**
 * Logs the creation of shaders.
 *
 * @category Debug
 */ const TRACEID_SHADER_ALLOC = 'ShaderAlloc';
/**
 * Logs the compilation time of shaders.
 *
 * @category Debug
 */ const TRACEID_SHADER_COMPILE = 'ShaderCompile';
/**
 * Logs the vram use by the textures.
 *
 * @category Debug
 */ const TRACEID_VRAM_TEXTURE = 'VRAM.Texture';
/**
 * Logs the vram use by the vertex buffers.
 *
 * @category Debug
 */ const TRACEID_VRAM_VB = 'VRAM.Vb';
/**
 * Logs the vram use by the index buffers.
 *
 * @category Debug
 */ const TRACEID_VRAM_IB = 'VRAM.Ib';
/**
 * Logs the vram use by the storage buffers.
 *
 * @category Debug
 */ const TRACEID_VRAM_SB = 'VRAM.Sb';
/**
 * Logs the creation of bind groups.
 *
 * @category Debug
 */ const TRACEID_BINDGROUP_ALLOC = 'BindGroupAlloc';
/**
 * Logs the creation of bind group formats.
 *
 * @category Debug
 */ const TRACEID_BINDGROUPFORMAT_ALLOC = 'BindGroupFormatAlloc';
/**
 * Logs the creation of render pipelines. WebBPU only.
 *
 * @category Debug
 */ const TRACEID_RENDERPIPELINE_ALLOC = 'RenderPipelineAlloc';
/**
 * Logs the creation of compute pipelines. WebGPU only.
 *
 * @category Debug
 */ const TRACEID_COMPUTEPIPELINE_ALLOC = 'ComputePipelineAlloc';
/**
 * Logs the creation of pipeline layouts. WebBPU only.
 *
 * @category Debug
 */ const TRACEID_PIPELINELAYOUT_ALLOC = 'PipelineLayoutAlloc';
/**
 * Logs the vram use by all textures in memory.
 *
 * @category Debug
 */ const TRACEID_TEXTURES = 'Textures';
/**
 * Logs the render queue commands.
 *
 * @category Debug
 */ const TRACEID_RENDER_QUEUE = 'RenderQueue';
/**
 * Logs the GPU timings.
 *
 * @category Debug
 */ const TRACEID_GPU_TIMINGS = 'GpuTimings';

/**
 * The engine version number. This is in semantic versioning format (MAJOR.MINOR.PATCH).
 */ const version$1 = '2.11.8';
/**
 * The engine revision number. This is the Git hash of the last commit made to the branch
 * from which the engine was built.
 */ const revision = 'd712e1a';
/**
 * Merge the contents of two objects into a single object.
 *
 * @param {object} target - The target object of the merge.
 * @param {object} ex - The object that is merged with target.
 * @returns {object} The target object.
 * @example
 * const A = {
 *     a: function () {
 *         console.log(this.a);
 *     }
 * };
 * const B = {
 *     b: function () {
 *         console.log(this.b);
 *     }
 * };
 *
 * extend(A, B);
 * A.a();
 * // logs "a"
 * A.b();
 * // logs "b"
 * @ignore
 */ function extend(target, ex) {
    for(const prop in ex){
        const copy = ex[prop];
        if (Array.isArray(copy)) {
            target[prop] = extend([], copy);
        } else if (copy && typeof copy === 'object') {
            target[prop] = extend({}, copy);
        } else {
            target[prop] = copy;
        }
    }
    return target;
}

/**
 * Basically a very large random number (128-bit) which means the probability of creating two that
 * clash is vanishingly small. GUIDs are used as the unique identifiers for Entities.
 *
 * @namespace
 */ const guid = {
    /**
     * Create an RFC4122 version 4 compliant GUID.
     *
     * @returns {string} A new GUID.
     */ create () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c)=>{
            const r = Math.random() * 16 | 0;
            const v = c === 'x' ? r : r & 0x3 | 0x8;
            return v.toString(16);
        });
    }
};

/**
 * File path API.
 *
 * @namespace
 */ const path$1 = {
    /**
     * The character that separates path segments.
     *
     * @type {string}
     */ delimiter: '/',
    /**
     * Join two or more sections of file path together, inserting a delimiter if needed.
     *
     * @param {...string} sections - Sections of the path to join.
     * @returns {string} The joined file path.
     * @example
     * const path = pc.path.join('foo', 'bar');
     * console.log(path); // Prints 'foo/bar'
     * @example
     * const path = pc.path.join('alpha', 'beta', 'gamma');
     * console.log(path); // Prints 'alpha/beta/gamma'
     */ join (...sections) {
        let result = sections[0];
        for(let i = 0; i < sections.length - 1; i++){
            const one = sections[i];
            const two = sections[i + 1];
            if (two[0] === path$1.delimiter) {
                result = two;
                continue;
            }
            if (one && two && one[one.length - 1] !== path$1.delimiter && two[0] !== path$1.delimiter) {
                result += path$1.delimiter + two;
            } else {
                result += two;
            }
        }
        return result;
    },
    /**
     * Normalize the path by removing '.' and '..' instances.
     *
     * @param {string} pathname - The path to normalize.
     * @returns {string} The normalized path.
     */ normalize (pathname) {
        const lead = pathname.startsWith(path$1.delimiter);
        const trail = pathname.endsWith(path$1.delimiter);
        const parts = pathname.split('/');
        let result = '';
        let cleaned = [];
        for(let i = 0; i < parts.length; i++){
            if (parts[i] === '') continue;
            if (parts[i] === '.') continue;
            if (parts[i] === '..' && cleaned.length > 0) {
                cleaned = cleaned.slice(0, cleaned.length - 2);
                continue;
            }
            if (i > 0) cleaned.push(path$1.delimiter);
            cleaned.push(parts[i]);
        }
        result = cleaned.join('');
        if (!lead && result[0] === path$1.delimiter) {
            result = result.slice(1);
        }
        if (trail && result[result.length - 1] !== path$1.delimiter) {
            result += path$1.delimiter;
        }
        return result;
    },
    /**
     * Split the pathname path into a pair [head, tail] where tail is the final part of the path
     * after the last delimiter and head is everything leading up to that. tail will never contain
     * a slash.
     *
     * @param {string} pathname - The path to split.
     * @returns {string[]} The split path which is an array of two strings, the path and the
     * filename.
     */ split (pathname) {
        const lastDelimiterIndex = pathname.lastIndexOf(path$1.delimiter);
        if (lastDelimiterIndex !== -1) {
            return [
                pathname.substring(0, lastDelimiterIndex),
                pathname.substring(lastDelimiterIndex + 1)
            ];
        }
        return [
            '',
            pathname
        ];
    },
    /**
     * Return the basename of the path. That is the second element of the pair returned by passing
     * path into {@link path.split}.
     *
     * @param {string} pathname - The path to process.
     * @returns {string} The basename.
     * @example
     * pc.path.getBasename("/path/to/file.txt"); // returns "file.txt"
     * pc.path.getBasename("/path/to/dir"); // returns "dir"
     */ getBasename (pathname) {
        return path$1.split(pathname)[1];
    },
    /**
     * Get the directory name from the path. This is everything up to the final instance of
     * {@link path.delimiter}.
     *
     * @param {string} pathname - The path to get the directory from.
     * @returns {string} The directory part of the path.
     */ getDirectory (pathname) {
        return path$1.split(pathname)[0];
    },
    /**
     * Return the extension of the path. Pop the last value of a list after path is split by
     * question mark and comma.
     *
     * @param {string} pathname - The path to process.
     * @returns {string} The extension.
     * @example
     * pc.path.getExtension("/path/to/file.txt"); // returns ".txt"
     * pc.path.getExtension("/path/to/file.jpg"); // returns ".jpg"
     * pc.path.getExtension("/path/to/file.txt?function=getExtension"); // returns ".txt"
     */ getExtension (pathname) {
        const ext = pathname.split('?')[0].split('.').pop();
        if (ext !== pathname) {
            return `.${ext}`;
        }
        return '';
    },
    /**
     * Check if a string s is relative path.
     *
     * @param {string} pathname - The path to process.
     * @returns {boolean} True if s doesn't start with slash and doesn't include colon and double
     * slash.
     *
     * @example
     * pc.path.isRelativePath("file.txt"); // returns true
     * pc.path.isRelativePath("path/to/file.txt"); // returns true
     * pc.path.isRelativePath("./path/to/file.txt"); // returns true
     * pc.path.isRelativePath("../path/to/file.jpg"); // returns true
     * pc.path.isRelativePath("/path/to/file.jpg"); // returns false
     * pc.path.isRelativePath("http://path/to/file.jpg"); // returns false
     */ isRelativePath (pathname) {
        return pathname.charAt(0) !== '/' && pathname.match(/:\/\//) === null;
    },
    /**
     * Return the path without file name. If path is relative path, start with period.
     *
     * @param {string} pathname - The full path to process.
     * @returns {string} The path without a last element from list split by slash.
     * @example
     * pc.path.extractPath("path/to/file.txt");    // returns "./path/to"
     * pc.path.extractPath("./path/to/file.txt");  // returns "./path/to"
     * pc.path.extractPath("../path/to/file.txt"); // returns "../path/to"
     * pc.path.extractPath("/path/to/file.txt");   // returns "/path/to"
     */ extractPath (pathname) {
        let result = '';
        const parts = pathname.split('/');
        let i = 0;
        if (parts.length > 1) {
            if (path$1.isRelativePath(pathname)) {
                if (parts[0] === '.') {
                    for(i = 0; i < parts.length - 1; ++i){
                        result += i === 0 ? parts[i] : `/${parts[i]}`;
                    }
                } else if (parts[0] === '..') {
                    for(i = 0; i < parts.length - 1; ++i){
                        result += i === 0 ? parts[i] : `/${parts[i]}`;
                    }
                } else {
                    result = '.';
                    for(i = 0; i < parts.length - 1; ++i){
                        result += `/${parts[i]}`;
                    }
                }
            } else {
                for(i = 0; i < parts.length - 1; ++i){
                    result += i === 0 ? parts[i] : `/${parts[i]}`;
                }
            }
        }
        return result;
    }
};

// detect whether passive events are supported by the browser
const detectPassiveEvents = ()=>{
    let result = false;
    try {
        const opts = Object.defineProperty({}, 'passive', {
            get: function() {
                result = true;
                return false;
            }
        });
        window.addEventListener('testpassive', null, opts);
        window.removeEventListener('testpassive', null, opts);
    } catch (e) {}
    return result;
};
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
const environment = typeof window !== 'undefined' ? 'browser' : typeof global !== 'undefined' ? 'node' : 'worker';
// detect platform
const platformName = /android/i.test(ua) ? 'android' : /ip(?:[ao]d|hone)/i.test(ua) ? 'ios' : /windows/i.test(ua) ? 'windows' : /mac os/i.test(ua) ? 'osx' : /linux/i.test(ua) ? 'linux' : /cros/i.test(ua) ? 'cros' : null;
// detect browser
const browserName = environment !== 'browser' ? null : /Chrome\/|Chromium\/|Edg.*\//.test(ua) ? 'chrome' : /Safari\//.test(ua) ? 'safari' : /Firefox\//.test(ua) ? 'firefox' : 'other';
detectPassiveEvents();
/**
 * Global namespace that stores flags regarding platform environment and features support.
 *
 * @namespace
 * @example
 * if (pc.platform.touch) {
 *     // touch is supported
 * }
 */ const platform = {
    /**
     * String identifying the current platform. Can be one of: android, ios, windows, osx, linux,
     * cros or null.
     *
     * @type {'android' | 'ios' | 'windows' | 'osx' | 'linux' | 'cros' | null}
     * @ignore
     */ name: platformName,
    /**
     * String identifying the current runtime environment. Either 'browser', 'node' or 'worker'.
     *
     * @type {'browser' | 'node' | 'worker'}
     */ environment: environment,
    /**
     * Convenience boolean indicating whether we're running in the browser.
     *
     * @type {boolean}
     */ browser: environment === 'browser',
    /**
     * True if running in a Web Worker.
     *
     * @type {boolean}
     * @ignore
     */ worker: environment === 'worker',
    /**
     * True if running on an Android device.
     *
     * @type {boolean}
     */ android: platformName === 'android',
    /**
     * Get the browser name.
     *
     * @type {'chrome' | 'safari' | 'firefox' | 'other' | null}
     * @ignore
     */ browserName: browserName
};

/**
 * Log tracing functionality, allowing for tracing of the internal functionality of the engine.
 * Note that the trace logging only takes place in the debug build of the engine and is stripped
 * out in other builds.
 *
 * @category Debug
 */ class Tracing {
    static{
        /**
     * Set storing the names of enabled trace channels.
     *
     * @type {Set<string>}
     * @private
     */ this._traceChannels = new Set();
    }
    static{
        /**
     * Enable call stack logging for trace calls. Defaults to false.
     *
     * @type {boolean}
     */ this.stack = false;
    }
    /**
     * Enable or disable a trace channel.
     *
     * @param {string} channel - Name of the trace channel. Can be:
     *
     * - {@link TRACEID_RENDER_FRAME}
     * - {@link TRACEID_RENDER_FRAME_TIME}
     * - {@link TRACEID_RENDER_PASS}
     * - {@link TRACEID_RENDER_PASS_DETAIL}
     * - {@link TRACEID_RENDER_ACTION}
     * - {@link TRACEID_RENDER_TARGET_ALLOC}
     * - {@link TRACEID_TEXTURE_ALLOC}
     * - {@link TRACEID_SHADER_ALLOC}
     * - {@link TRACEID_SHADER_COMPILE}
     * - {@link TRACEID_VRAM_TEXTURE}
     * - {@link TRACEID_VRAM_VB}
     * - {@link TRACEID_VRAM_IB}
     * - {@link TRACEID_RENDERPIPELINE_ALLOC}
     * - {@link TRACEID_COMPUTEPIPELINE_ALLOC}
     * - {@link TRACEID_PIPELINELAYOUT_ALLOC}
     * - {@link TRACEID_TEXTURES}
     * - {@link TRACEID_GPU_TIMINGS}
     *
     * @param {boolean} enabled - New enabled state for the channel.
     */ static set(channel, enabled = true) {
        if (enabled) {
            Tracing._traceChannels.add(channel);
        } else {
            Tracing._traceChannels.delete(channel);
        }
    }
    /**
     * Test if the trace channel is enabled.
     *
     * @param {string} channel - Name of the trace channel.
     * @returns {boolean} - True if the trace channel is enabled.
     */ static get(channel) {
        return Tracing._traceChannels.has(channel);
    }
}

/**
 * Engine debug log system. Note that the logging only executes in the debug build of the engine,
 * and is stripped out in other builds.
 */ class Debug {
    static{
        /**
     * Set storing already logged messages, to only print each unique message one time.
     *
     * @type {Set<string>}
     * @private
     */ this._loggedMessages = new Set();
    }
    /**
     * Deprecated warning message.
     *
     * @param {string} message - The message to log.
     */ static deprecated(message) {
        if (!Debug._loggedMessages.has(message)) {
            Debug._loggedMessages.add(message);
            console.warn(`DEPRECATED: ${message}`);
        }
    }
    /**
     * Removed warning message.
     *
     * @param {string} message - The message to log.
     */ static removed(message) {
        if (!Debug._loggedMessages.has(message)) {
            Debug._loggedMessages.add(message);
            console.error(`REMOVED: ${message}`);
        }
    }
    /**
     * Assertion deprecated message. If the assertion is false, the deprecated message is written to the log.
     *
     * @param {boolean|object} assertion - The assertion to check.
     * @param {string} message - The message to log.
     */ static assertDeprecated(assertion, message) {
        if (!assertion) {
            Debug.deprecated(message);
        }
    }
    /**
     * Assertion error message. If the assertion is false, the error message is written to the log.
     *
     * @param {boolean|object} assertion - The assertion to check.
     * @param {...*} args - The values to be written to the log.
     */ static assert(assertion, ...args) {
        if (!assertion) {
            console.error('ASSERT FAILED: ', ...args);
        }
    }
    /**
     * Assertion error message that writes an error message to the log if the object has already
     * been destroyed. To be used along setDestroyed.
     *
     * @param {object} object - The object to check.
     */ static assertDestroyed(object) {
        if (object?.__alreadyDestroyed) {
            const message = `[${object.constructor?.name}] with name [${object.name}] has already been destroyed, and cannot be used.`;
            if (!Debug._loggedMessages.has(message)) {
                Debug._loggedMessages.add(message);
                console.error('ASSERT FAILED: ', message, object);
            }
        }
    }
    /**
     * Executes a function in debug mode only.
     *
     * @param {Function} func - Function to call.
     */ static call(func) {
        func();
    }
    /**
     * Info message.
     *
     * @param {...*} args - The values to be written to the log.
     */ static log(...args) {
        console.log(...args);
    }
    /**
     * Info message logged no more than once.
     *
     * @param {string} message - The message to log.
     * @param {...*} args - The values to be written to the log.
     */ static logOnce(message, ...args) {
        if (!Debug._loggedMessages.has(message)) {
            Debug._loggedMessages.add(message);
            console.log(message, ...args);
        }
    }
    /**
     * Warning message.
     *
     * @param {...*} args - The values to be written to the log.
     */ static warn(...args) {
        console.warn(...args);
    }
    /**
     * Warning message logged no more than once.
     *
     * @param {string} message - The message to log.
     * @param {...*} args - The values to be written to the log.
     */ static warnOnce(message, ...args) {
        if (!Debug._loggedMessages.has(message)) {
            Debug._loggedMessages.add(message);
            console.warn(message, ...args);
        }
    }
    /**
     * Error message.
     *
     * @param {...*} args - The values to be written to the log.
     */ static error(...args) {
        console.error(...args);
    }
    /**
     * Error message logged no more than once.
     *
     * @param {string} message - The message to log.
     * @param {...*} args - The values to be written to the log.
     */ static errorOnce(message, ...args) {
        if (!Debug._loggedMessages.has(message)) {
            Debug._loggedMessages.add(message);
            console.error(message, ...args);
        }
    }
    /**
     * Trace message, which is logged to the console if the tracing for the channel is enabled
     *
     * @param {string} channel - The trace channel
     * @param {...*} args - The values to be written to the log.
     */ static trace(channel, ...args) {
        if (Tracing.get(channel)) {
            console.groupCollapsed(`${channel.padEnd(20, ' ')}|`, ...args);
            if (Tracing.stack) {
                console.trace();
            }
            console.groupEnd();
        }
    }
}
/**
 * A helper debug functionality.
 */ class DebugHelper {
    /**
     * Set a name to the name property of the object. Executes only in the debug build.
     *
     * @param {object} object - The object to assign the name to.
     * @param {string} name - The name to assign.
     */ static setName(object, name) {
        if (object) {
            object.name = name;
        }
    }
    /**
     * Set a label to the label property of the object. Executes only in the debug build.
     *
     * @param {object} object - The object to assign the name to.
     * @param {string} label - The label to assign.
     */ static setLabel(object, label) {
        if (object) {
            object.label = label;
        }
    }
    /**
     * Marks object as destroyed. Executes only in the debug build. To be used along assertDestroyed.
     *
     * @param {object} object - The object to mark as destroyed.
     */ static setDestroyed(object) {
        if (object) {
            object.__alreadyDestroyed = true;
        }
    }
}

/**
 * @import { EventHandler } from './event-handler.js'
 * @import { HandleEventCallback } from './event-handler.js'
 */ /**
 * Event Handle that is created by {@link EventHandler} and can be used for easier event removal
 * and management.
 *
 * @example
 * const evt = obj.on('test', (a, b) => {
 *     console.log(a + b);
 * });
 * obj.fire('test');
 *
 * evt.off(); // easy way to remove this event
 * obj.fire('test'); // this will not trigger an event
 * @example
 * // store an array of event handles
 * let events = [];
 *
 * events.push(objA.on('testA', () => {}));
 * events.push(objB.on('testB', () => {}));
 *
 * // when needed, remove all events
 * events.forEach((evt) => {
 *     evt.off();
 * });
 * events = [];
 */ class EventHandle {
    /**
     * @param {EventHandler} handler - source object of the event.
     * @param {string} name - Name of the event.
     * @param {HandleEventCallback} callback - Function that is called when event is fired.
     * @param {object} scope - Object that is used as `this` when event is fired.
     * @param {boolean} [once] - If this is a single event and will be removed after event is fired.
     */ constructor(handler, name, callback, scope, once = false){
        /**
     * True if event has been removed.
     * @type {boolean}
     * @private
     */ this._removed = false;
        this.handler = handler;
        this.name = name;
        this.callback = callback;
        this.scope = scope;
        this._once = once;
    }
    /**
     * Remove this event from its handler.
     */ off() {
        if (this._removed) return;
        this.handler.offByHandle(this);
    }
    on(name, callback, scope = this) {
        Debug.deprecated('Using chaining with EventHandler.on is deprecated, subscribe to an event from EventHandler directly instead.');
        return this.handler._addCallback(name, callback, scope, false);
    }
    once(name, callback, scope = this) {
        Debug.deprecated('Using chaining with EventHandler.once is deprecated, subscribe to an event from EventHandler directly instead.');
        return this.handler._addCallback(name, callback, scope, true);
    }
    /**
     * Mark if event has been removed.
     *
     * @type {boolean}
     * @ignore
     */ set removed(value) {
        if (!value) return;
        this._removed = true;
    }
    /**
     * True if event has been removed.
     *
     * @type {boolean}
     * @ignore
     */ get removed() {
        return this._removed;
    }
    // don't stringify EventHandle to JSON by JSON.stringify
    toJSON(key) {
        return undefined;
    }
}

/**
 * @callback HandleEventCallback
 * Callback used by {@link EventHandler} functions. Note the callback is limited to 8 arguments.
 * @param {any} [arg1] - First argument that is passed from caller.
 * @param {any} [arg2] - Second argument that is passed from caller.
 * @param {any} [arg3] - Third argument that is passed from caller.
 * @param {any} [arg4] - Fourth argument that is passed from caller.
 * @param {any} [arg5] - Fifth argument that is passed from caller.
 * @param {any} [arg6] - Sixth argument that is passed from caller.
 * @param {any} [arg7] - Seventh argument that is passed from caller.
 * @param {any} [arg8] - Eighth argument that is passed from caller.
 * @returns {void}
 */ /**
 * Abstract base class that implements functionality for event handling.
 *
 * ```javascript
 * const obj = new EventHandlerSubclass();
 *
 * // subscribe to an event
 * obj.on('hello', (str) => {
 *     console.log('event hello is fired', str);
 * });
 *
 * // fire event
 * obj.fire('hello', 'world');
 * ```
 */ class EventHandler {
    /**
     * Reinitialize the event handler.
     * @ignore
     */ initEventHandler() {
        this._callbacks = new Map();
        this._callbackActive = new Map();
    }
    /**
     * Registers a new event handler.
     *
     * @param {string} name - Name of the event to bind the callback to.
     * @param {HandleEventCallback} callback - Function that is called when event is fired. Note
     * the callback is limited to 8 arguments.
     * @param {object} scope - Object to use as 'this' when the event is fired, defaults to
     * current this.
     * @param {boolean} once - If true, the callback will be unbound after being fired once.
     * @returns {EventHandle} Created {@link EventHandle}.
     * @ignore
     */ _addCallback(name, callback, scope, once) {
        if (!name || typeof name !== 'string' || !callback) {
            console.warn(`EventHandler: subscribing to an event (${name}) with missing arguments`, callback);
        }
        if (!this._callbacks.has(name)) {
            this._callbacks.set(name, []);
        }
        // if we are adding a callback to the list that is executing right now
        // ensure we preserve initial list before modifications
        if (this._callbackActive.has(name)) {
            const callbackActive = this._callbackActive.get(name);
            if (callbackActive && callbackActive === this._callbacks.get(name)) {
                this._callbackActive.set(name, callbackActive.slice());
            }
        }
        const evt = new EventHandle(this, name, callback, scope, once);
        this._callbacks.get(name).push(evt);
        return evt;
    }
    /**
     * Attach an event handler to an event.
     *
     * @param {string} name - Name of the event to bind the callback to.
     * @param {HandleEventCallback} callback - Function that is called when event is fired. Note
     * the callback is limited to 8 arguments.
     * @param {object} [scope] - Object to use as 'this' when the event is fired, defaults to
     * current this.
     * @returns {EventHandle} Can be used for removing event in the future.
     * @example
     * obj.on('test', (a, b) => {
     *     console.log(a + b);
     * });
     * obj.fire('test', 1, 2); // prints 3 to the console
     * @example
     * const evt = obj.on('test', (a, b) => {
     *     console.log(a + b);
     * });
     * // some time later
     * evt.off();
     */ on(name, callback, scope = this) {
        return this._addCallback(name, callback, scope, false);
    }
    /**
     * Attach an event handler to an event. This handler will be removed after being fired once.
     *
     * @param {string} name - Name of the event to bind the callback to.
     * @param {HandleEventCallback} callback - Function that is called when event is fired. Note
     * the callback is limited to 8 arguments.
     * @param {object} [scope] - Object to use as 'this' when the event is fired, defaults to
     * current this.
     * @returns {EventHandle} - can be used for removing event in the future.
     * @example
     * obj.once('test', (a, b) => {
     *     console.log(a + b);
     * });
     * obj.fire('test', 1, 2); // prints 3 to the console
     * obj.fire('test', 1, 2); // not going to get handled
     */ once(name, callback, scope = this) {
        return this._addCallback(name, callback, scope, true);
    }
    /**
     * Detach an event handler from an event. If callback is not provided then all callbacks are
     * unbound from the event, if scope is not provided then all events with the callback will be
     * unbound.
     *
     * @param {string} [name] - Name of the event to unbind.
     * @param {HandleEventCallback} [callback] - Function to be unbound.
     * @param {object} [scope] - Scope that was used as the this when the event is fired.
     * @returns {EventHandler} Self for chaining.
     * @example
     * const handler = () => {};
     * obj.on('test', handler);
     *
     * obj.off(); // Removes all events
     * obj.off('test'); // Removes all events called 'test'
     * obj.off('test', handler); // Removes all handler functions, called 'test'
     * obj.off('test', handler, this); // Removes all handler functions, called 'test' with scope this
     */ off(name, callback, scope) {
        if (name) {
            // if we are removing a callback from the list that is executing right now
            // ensure we preserve initial list before modifications
            if (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) {
                this._callbackActive.set(name, this._callbackActive.get(name).slice());
            }
        } else {
            // if we are removing a callback from any list that is executing right now
            // ensure we preserve these initial lists before modifications
            for (const [key, callbacks] of this._callbackActive){
                if (!this._callbacks.has(key)) {
                    continue;
                }
                if (this._callbacks.get(key) !== callbacks) {
                    continue;
                }
                this._callbackActive.set(key, callbacks.slice());
            }
        }
        if (!name) {
            // remove all events
            for (const callbacks of this._callbacks.values()){
                for(let i = 0; i < callbacks.length; i++){
                    callbacks[i].removed = true;
                }
            }
            this._callbacks.clear();
        } else if (!callback) {
            // remove all events of a specific name
            const callbacks = this._callbacks.get(name);
            if (callbacks) {
                for(let i = 0; i < callbacks.length; i++){
                    callbacks[i].removed = true;
                }
                this._callbacks.delete(name);
            }
        } else {
            const callbacks = this._callbacks.get(name);
            if (!callbacks) {
                return this;
            }
            for(let i = 0; i < callbacks.length; i++){
                // remove all events with a specific name and a callback
                if (callbacks[i].callback !== callback) {
                    continue;
                }
                // could be a specific scope as well
                if (scope && callbacks[i].scope !== scope) {
                    continue;
                }
                callbacks[i].removed = true;
                callbacks.splice(i, 1);
                i--;
            }
            if (callbacks.length === 0) {
                this._callbacks.delete(name);
            }
        }
        return this;
    }
    /**
     * Detach an event handler from an event using EventHandle instance. More optimal remove
     * as it does not have to scan callbacks array.
     *
     * @param {EventHandle} handle - Handle of event.
     * @ignore
     */ offByHandle(handle) {
        const name = handle.name;
        handle.removed = true;
        // if we are removing a callback from the list that is executing right now
        // ensure we preserve initial list before modifications
        if (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) {
            this._callbackActive.set(name, this._callbackActive.get(name).slice());
        }
        const callbacks = this._callbacks.get(name);
        if (!callbacks) {
            return this;
        }
        const ind = callbacks.indexOf(handle);
        if (ind !== -1) {
            callbacks.splice(ind, 1);
            if (callbacks.length === 0) {
                this._callbacks.delete(name);
            }
        }
        return this;
    }
    /**
     * Fire an event, all additional arguments are passed on to the event listener.
     *
     * @param {string} name - Name of event to fire.
     * @param {any} [arg1] - First argument that is passed to the event handler.
     * @param {any} [arg2] - Second argument that is passed to the event handler.
     * @param {any} [arg3] - Third argument that is passed to the event handler.
     * @param {any} [arg4] - Fourth argument that is passed to the event handler.
     * @param {any} [arg5] - Fifth argument that is passed to the event handler.
     * @param {any} [arg6] - Sixth argument that is passed to the event handler.
     * @param {any} [arg7] - Seventh argument that is passed to the event handler.
     * @param {any} [arg8] - Eighth argument that is passed to the event handler.
     * @returns {EventHandler} Self for chaining.
     * @example
     * obj.fire('test', 'This is the message');
     */ fire(name, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) {
        if (!name) {
            return this;
        }
        const callbacksInitial = this._callbacks.get(name);
        if (!callbacksInitial) {
            return this;
        }
        let callbacks;
        if (!this._callbackActive.has(name)) {
            // when starting callbacks execution ensure we store a list of initial callbacks
            this._callbackActive.set(name, callbacksInitial);
        } else if (this._callbackActive.get(name) !== callbacksInitial) {
            // if we are trying to execute a callback while there is an active execution right now
            // and the active list has been already modified,
            // then we go to an unoptimized path and clone callbacks list to ensure execution consistency
            callbacks = callbacksInitial.slice();
        }
        // eslint-disable-next-line no-unmodified-loop-condition
        for(let i = 0; (callbacks || this._callbackActive.get(name)) && i < (callbacks || this._callbackActive.get(name)).length; i++){
            const evt = (callbacks || this._callbackActive.get(name))[i];
            if (!evt.callback) continue;
            evt.callback.call(evt.scope, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
            if (evt._once) {
                // check that callback still exists because user may have unsubscribed in the event handler
                const existingCallback = this._callbacks.get(name);
                const ind = existingCallback ? existingCallback.indexOf(evt) : -1;
                if (ind !== -1) {
                    if (this._callbackActive.get(name) === existingCallback) {
                        this._callbackActive.set(name, this._callbackActive.get(name).slice());
                    }
                    const callbacks = this._callbacks.get(name);
                    if (!callbacks) continue;
                    callbacks[ind].removed = true;
                    callbacks.splice(ind, 1);
                    if (callbacks.length === 0) {
                        this._callbacks.delete(name);
                    }
                }
            }
        }
        if (!callbacks) {
            this._callbackActive.delete(name);
        }
        return this;
    }
    /**
     * Test if there are any handlers bound to an event name.
     *
     * @param {string} name - The name of the event to test.
     * @returns {boolean} True if the object has handlers bound to the specified event name.
     * @example
     * obj.on('test', () => {}); // bind an event to 'test'
     * obj.hasEvent('test'); // returns true
     * obj.hasEvent('hello'); // returns false
     */ hasEvent(name) {
        return !!this._callbacks.get(name)?.length;
    }
    constructor(){
        /**
     * @type {Map<string,Array<EventHandle>>}
     * @private
     */ this._callbacks = new Map();
        /**
     * @type {Map<string,Array<EventHandle>>}
     * @private
     */ this._callbackActive = new Map();
    }
}

// wrapper function that caches the func result on first invocation and
// then subsequently returns the cached value
const cachedResult = (func)=>{
    const uninitToken = {};
    let result = uninitToken;
    return ()=>{
        if (result === uninitToken) {
            result = func();
        }
        return result;
    };
};
class Impl {
    static{
        this.modules = {};
    }
    static{
        // returns true if the running host supports wasm modules (all browsers except IE)
        this.wasmSupported = cachedResult(()=>{
            try {
                if (typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function') {
                    const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
                    if (module instanceof WebAssembly.Module) {
                        return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
                    }
                }
            } catch (e) {}
            return false;
        });
    }
    // load a script
    static loadScript(url, callback) {
        const s = document.createElement("script");
        s.setAttribute('src', url);
        s.onload = ()=>{
            callback(null);
        };
        s.onerror = ()=>{
            callback(`Failed to load script='${url}'`);
        };
        document.body.appendChild(s);
    }
    // load a wasm module
    static loadWasm(moduleName, config, callback) {
        const loadUrl = Impl.wasmSupported() && config.glueUrl && config.wasmUrl ? config.glueUrl : config.fallbackUrl;
        if (loadUrl) {
            Impl.loadScript(loadUrl, (err)=>{
                if (err) {
                    callback(err, null);
                } else {
                    const module = window[moduleName];
                    // clear the module from the global window since we used to store global instance here
                    window[moduleName] = undefined;
                    // instantiate the module
                    module({
                        locateFile: ()=>config.wasmUrl,
                        onAbort: ()=>{
                            callback('wasm module aborted.');
                        }
                    }).then((instance)=>{
                        callback(null, instance);
                    });
                }
            });
        } else {
            callback('No supported wasm modules found.', null);
        }
    }
    // get state object for the named module
    static getModule(name) {
        if (!Impl.modules.hasOwnProperty(name)) {
            Impl.modules[name] = {
                config: null,
                initializing: false,
                instance: null,
                callbacks: []
            };
        }
        return Impl.modules[name];
    }
    static initialize(moduleName, module) {
        if (module.initializing) {
            return;
        }
        const config = module.config;
        if (config.glueUrl || config.wasmUrl || config.fallbackUrl) {
            module.initializing = true;
            Impl.loadWasm(moduleName, config, (err, instance)=>{
                if (err) {
                    if (config.errorHandler) {
                        config.errorHandler(err);
                    } else {
                        console.error(`failed to initialize module=${moduleName} error=${err}`);
                    }
                } else {
                    module.instance = instance;
                    module.callbacks.forEach((callback)=>{
                        callback(instance);
                    });
                }
            });
        }
    }
}
/**
 * @callback ModuleErrorCallback
 * Callback used by {@link WasmModule.setConfig}.
 * @param {string} error - If the instance fails to load this will contain a description of the error.
 * @returns {void}
 */ /**
 * @callback ModuleInstanceCallback
 * Callback used by {@link WasmModule.getInstance}.
 * @param {any} moduleInstance - The module instance.
 * @returns {void}
 */ /**
 * A pure static utility class which supports immediate and lazy loading of
 * [WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly) modules. Note that you can
 * load WebAssembly modules even before instantiating your {@link AppBase} instance.
 *
 * This class is generally only needed if you are developing against the Engine directly. Editor
 * projects automatically load WebAssembly modules included in the project's assets.
 *
 * Do not use this class to load the Basis WebAssembly module. Instead, please refer to
 * {@link basisInitialize}.
 *
 * @example
 * // Load the Ammo.js physics engine
 * pc.WasmModule.setConfig('Ammo', {
 *     glueUrl: `ammo.wasm.js`,
 *     wasmUrl: `ammo.wasm.wasm`,
 *     fallbackUrl: `ammo.js`
 * });
 * await new Promise((resolve) => {
 *     pc.WasmModule.getInstance('Ammo', () => resolve());
 * });
 */ class WasmModule {
    /**
     * Set a wasm module's configuration.
     *
     * @param {string} moduleName - Name of the module.
     * @param {object} [config] - The configuration object.
     * @param {string} [config.glueUrl] - URL of glue script.
     * @param {string} [config.wasmUrl] - URL of the wasm script.
     * @param {string} [config.fallbackUrl] - URL of the fallback script to use when wasm modules
     * aren't supported.
     * @param {number} [config.numWorkers] - For modules running on worker threads, the number of
     * threads to use. Default value is based on module implementation.
     * @param {ModuleErrorCallback} [config.errorHandler] - Function to be called if the module fails
     * to download.
     */ static setConfig(moduleName, config) {
        const module = Impl.getModule(moduleName);
        module.config = config;
        if (module.callbacks.length > 0) {
            // start module initialize immediately since there are pending getInstance requests
            Impl.initialize(moduleName, module);
        }
    }
    /**
     * Get a wasm module's configuration.
     *
     * @param {string} moduleName - Name of the module.
     * @returns {object | undefined} The previously set configuration.
     */ static getConfig(moduleName) {
        return Impl.modules?.[moduleName]?.config;
    }
    /**
     * Get a wasm module instance. The instance will be created if necessary and returned
     * in the second parameter to callback.
     *
     * @param {string} moduleName - Name of the module.
     * @param {ModuleInstanceCallback} callback - The function called when the instance is
     * available.
     */ static getInstance(moduleName, callback) {
        const module = Impl.getModule(moduleName);
        if (module.instance) {
            callback(module.instance);
        } else {
            module.callbacks.push(callback);
            if (module.config) {
                // config has been provided, kick off module initialize
                Impl.initialize(moduleName, module);
            }
        }
    }
}

/**
 * Helper class for organized reading of memory.
 *
 * @ignore
 */ class ReadStream {
    /**
     * @param {ArrayBuffer} arraybuffer - The buffer to read from.
     */ constructor(arraybuffer){
        /** @type {number} */ this.offset = 0;
        this.arraybuffer = arraybuffer;
        this.dataView = new DataView(arraybuffer);
    }
    /**
     * The number of bytes remaining to be read.
     *
     * @type {number}
     */ get remainingBytes() {
        return this.dataView.byteLength - this.offset;
    }
    /**
     * Resets the offset to a given value. If no value is given, the offset is reset to 0.
     *
     * @param {number} offset - The new offset.
     */ reset(offset = 0) {
        this.offset = offset;
    }
    /**
     * Skips a number of bytes.
     *
     * @param {number} bytes - The number of bytes to skip.
     */ skip(bytes) {
        this.offset += bytes;
    }
    /**
     * Aligns the offset to a multiple of a number of bytes.
     *
     * @param {number} bytes - The number of bytes to align to.
     */ align(bytes) {
        this.offset = this.offset + bytes - 1 & ~(bytes - 1);
    }
    /**
     * Increments the offset by the specified number of bytes and returns the previous offset.
     *
     * @param {number} amount - The number of bytes to increment by.
     * @returns {number} The previous offset.
     * @private
     */ _inc(amount) {
        this.offset += amount;
        return this.offset - amount;
    }
    /**
     * Reads a single character.
     *
     * @returns {string} The character.
     */ readChar() {
        return String.fromCharCode(this.dataView.getUint8(this.offset++));
    }
    /**
     * Reads a string of a given length.
     *
     * @param {number} numChars - The number of characters to read.
     * @returns {string} The string.
     */ readChars(numChars) {
        let result = '';
        for(let i = 0; i < numChars; ++i){
            result += this.readChar();
        }
        return result;
    }
    /**
     * Read an unsigned 8-bit integer.
     *
     * @returns {number} The integer.
     */ readU8() {
        return this.dataView.getUint8(this.offset++);
    }
    /**
     * Read an unsigned 16-bit integer.
     *
     * @returns {number} The integer.
     */ readU16() {
        return this.dataView.getUint16(this._inc(2), true);
    }
    /**
     * Read an unsigned 32-bit integer.
     *
     * @returns {number} The integer.
     */ readU32() {
        return this.dataView.getUint32(this._inc(4), true);
    }
    /**
     * Read an unsigned 64-bit integer.
     *
     * @returns {number} The integer.
     */ readU64() {
        return this.readU32() + 2 ** 32 * this.readU32();
    }
    /**
     * Read a big endian unsigned 32-bit integer.
     *
     * @returns {number} The integer.
     */ readU32be() {
        return this.dataView.getUint32(this._inc(4), false);
    }
    /**
     * Read unsigned 8-bit integers into an array.
     *
     * @param {number[]} result - The array to read into.
     */ readArray(result) {
        for(let i = 0; i < result.length; ++i){
            result[i] = this.readU8();
        }
    }
    /**
     * Read a line of text from the stream.
     *
     * @returns {string} The line of text.
     */ readLine() {
        const view = this.dataView;
        let result = '';
        while(true){
            if (this.offset >= view.byteLength) {
                break;
            }
            const c = String.fromCharCode(this.readU8());
            if (c === '\n') {
                break;
            }
            result += c;
        }
        return result;
    }
}

/**
 * Tags is a powerful tag management system for categorizing and filtering objects in PlayCanvas
 * applications. It provides an efficient way to attach string identifiers to objects and query them
 * using logical operations.
 *
 * Tags are automatically available on {@link Asset}s and {@link Entity}s (see {@link Asset#tags}
 * and {@link GraphNode#tags}). You can search for specific assets via {@link AssetRegistry#findByTag}
 * and specific entities via {@link GraphNode#findByTag}.
 */ class Tags extends EventHandler {
    static{
        /**
     * Fired for each individual tag that is added.
     *
     * @event
     * @example
     * tags.on('add', (tag, parent) => {
     *    console.log(`${tag} added to ${parent.name}`);
     * });
     */ this.EVENT_ADD = 'add';
    }
    static{
        /**
     * Fired for each individual tag that is removed.
     *
     * @event
     * @example
     * tags.on('remove', (tag, parent) => {
     *   console.log(`${tag} removed from ${parent.name}`);
     * });
     */ this.EVENT_REMOVE = 'remove';
    }
    static{
        /**
     * Fired when tags have been added or removed. It will fire once on bulk changes, while `add`
     * and `remove` will fire on each tag operation.
     *
     * @event
     * @example
     * tags.on('change', (parent) => {
     *    console.log(`Tags changed on ${parent.name}`);
     * });
     */ this.EVENT_CHANGE = 'change';
    }
    /**
     * Create an instance of a Tags.
     *
     * @param {object} [parent] - Parent object who tags belong to.
     */ constructor(parent){
        super(), /** @private */ this._index = {}, /** @private */ this._list = [];
        this._parent = parent;
    }
    /**
     * Add a tag, duplicates are ignored. Can be array or comma separated arguments for multiple tags.
     *
     * @param {...*} args - Name of a tag, or array of tags.
     * @returns {boolean} True if any tag were added.
     * @example
     * tags.add('level-1');
     * @example
     * tags.add('ui', 'settings');
     * @example
     * tags.add(['level-2', 'mob']);
     */ add(...args) {
        let changed = false;
        const tags = this._processArguments(args, true);
        if (!tags.length) {
            return changed;
        }
        for(let i = 0; i < tags.length; i++){
            if (this._index[tags[i]]) {
                continue;
            }
            changed = true;
            this._index[tags[i]] = true;
            this._list.push(tags[i]);
            this.fire('add', tags[i], this._parent);
        }
        if (changed) {
            this.fire('change', this._parent);
        }
        return changed;
    }
    /**
     * Remove tag.
     *
     * @param {...*} args - Name of a tag or array of tags.
     * @returns {boolean} True if any tag were removed.
     * @example
     * tags.remove('level-1');
     * @example
     * tags.remove('ui', 'settings');
     * @example
     * tags.remove(['level-2', 'mob']);
     */ remove(...args) {
        let changed = false;
        if (!this._list.length) {
            return changed;
        }
        const tags = this._processArguments(args, true);
        if (!tags.length) {
            return changed;
        }
        for(let i = 0; i < tags.length; i++){
            if (!this._index[tags[i]]) {
                continue;
            }
            changed = true;
            delete this._index[tags[i]];
            this._list.splice(this._list.indexOf(tags[i]), 1);
            this.fire('remove', tags[i], this._parent);
        }
        if (changed) {
            this.fire('change', this._parent);
        }
        return changed;
    }
    /**
     * Remove all tags.
     *
     * @example
     * tags.clear();
     */ clear() {
        if (!this._list.length) {
            return;
        }
        const tags = this._list.slice(0);
        this._list = [];
        this._index = {};
        for(let i = 0; i < tags.length; i++){
            this.fire('remove', tags[i], this._parent);
        }
        this.fire('change', this._parent);
    }
    /**
     * Check if tags satisfy filters. Filters can be provided by simple name of tag, as well as by
     * array of tags. When an array is provided it will check if tags contain each tag within the
     * array. If any of comma separated argument is satisfied, then it will return true. Any number
     * of combinations are valid, and order is irrelevant.
     *
     * @param {...*} query - Name of a tag or array of tags.
     * @returns {boolean} True if filters are satisfied.
     * @example
     * tags.has('player'); // player
     * @example
     * tags.has('mob', 'player'); // player OR mob
     * @example
     * tags.has(['level-1', 'mob']); // monster AND level-1
     * @example
     * tags.has(['ui', 'settings'], ['ui', 'levels']); // (ui AND settings) OR (ui AND levels)
     */ has(...query) {
        if (!this._list.length) {
            return false;
        }
        return this._has(this._processArguments(query));
    }
    /**
     * @param {string[]|string[][]} tags - Array of tags.
     * @returns {boolean} True if the supplied tags are present.
     * @private
     */ _has(tags) {
        if (!this._list.length || !tags.length) {
            return false;
        }
        for(let i = 0; i < tags.length; i++){
            if (tags[i].length === 1) {
                // single occurrence
                if (this._index[tags[i][0]]) {
                    return true;
                }
            } else {
                // combined occurrence
                let multiple = true;
                for(let t = 0; t < tags[i].length; t++){
                    if (this._index[tags[i][t]]) {
                        continue;
                    }
                    multiple = false;
                    break;
                }
                if (multiple) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Returns immutable array of tags.
     *
     * @returns {string[]} Copy of tags array.
     */ list() {
        return this._list.slice(0);
    }
    /**
     * @param {Array} args - Arguments to process.
     * @param {boolean} [flat] - If true, will flatten array of tags. Defaults to false.
     * @returns {string[]|string[][]} Array of tags.
     * @private
     */ _processArguments(args, flat) {
        const tags = [];
        let tmp = [];
        if (!args || !args.length) {
            return tags;
        }
        for(let i = 0; i < args.length; i++){
            if (args[i] instanceof Array) {
                if (!flat) {
                    tmp = [];
                }
                for(let t = 0; t < args[i].length; t++){
                    if (typeof args[i][t] !== 'string') {
                        continue;
                    }
                    if (flat) {
                        tags.push(args[i][t]);
                    } else {
                        tmp.push(args[i][t]);
                    }
                }
                if (!flat && tmp.length) {
                    tags.push(tmp);
                }
            } else if (typeof args[i] === 'string') {
                if (flat) {
                    tags.push(args[i]);
                } else {
                    tags.push([
                        args[i]
                    ]);
                }
            }
        }
        return tags;
    }
    /**
     * Number of tags in set.
     *
     * @type {number}
     */ get size() {
        return this._list.length;
    }
}

/**
 * Get current time in milliseconds. Use it to measure time difference. Reference time may differ
 * on different platforms.
 *
 * @returns {number} The time in milliseconds.
 * @ignore
 */ const now = typeof window !== 'undefined' && window.performance && window.performance.now ? performance.now.bind(performance) : Date.now;

// See http://tools.ietf.org/html/rfc2396#appendix-B for details of RegExp
const re = /^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
/**
 * A URI object.
 *
 * @ignore
 */ class URI {
    /**
     * Create a new URI instance.
     *
     * @param {string} uri - URI string.
     */ constructor(uri){
        const result = uri.match(re);
        this.scheme = result[2];
        this.authority = result[4];
        this.path = result[5];
        this.query = result[7];
        this.fragment = result[9];
    }
    /**
     * Convert URI back to string.
     *
     * @returns {string} The URI as a string.
     */ toString() {
        let s = '';
        if (this.scheme) {
            s += `${this.scheme}:`;
        }
        if (this.authority) {
            s += `//${this.authority}`;
        }
        s += this.path;
        if (this.query) {
            s += `?${this.query}`;
        }
        if (this.fragment) {
            s += `#${this.fragment}`;
        }
        return s;
    }
    /**
     * Returns the query parameters as an Object.
     *
     * @returns {object} The URI's query parameters converted to an Object.
     * @example
     * const s = "http://example.com?a=1&b=2&c=3";
     * const uri = new pc.URI(s);
     * const q = uri.getQuery();
     * console.log(q.a); // logs "1"
     * console.log(q.b); // logs "2"
     * console.log(q.c); // logs "3"
     */ getQuery() {
        const result = {};
        if (this.query) {
            const queryParams = decodeURIComponent(this.query).split('&');
            for (const queryParam of queryParams){
                const pair = queryParam.split('=');
                result[pair[0]] = pair[1];
            }
        }
        return result;
    }
    /**
     * Set the query section of the URI from a Object.
     *
     * @param {object} params - Key-Value pairs to encode into the query string.
     * @example
     * const s = "http://example.com";
     * const uri = new pc.URI(s);
     * uri.setQuery({
     *     "a": 1,
     *     "b": 2
     * });
     * console.log(uri.toString()); // logs "http://example.com?a=1&b=2
     */ setQuery(params) {
        let q = '';
        for(const key in params){
            if (params.hasOwnProperty(key)) {
                if (q !== '') {
                    q += '&';
                }
                q += `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
            }
        }
        this.query = q;
    }
}

/**
 * Math API.
 *
 * @namespace
 * @category Math
 */ const math = {
    /**
     * Conversion factor between degrees and radians.
     *
     * @type {number}
     */ DEG_TO_RAD: Math.PI / 180,
    /**
     * Conversion factor between degrees and radians.
     *
     * @type {number}
     */ RAD_TO_DEG: 180 / Math.PI,
    /**
     * Clamp a number between min and max inclusive.
     *
     * @param {number} value - Number to clamp.
     * @param {number} min - Min value.
     * @param {number} max - Max value.
     * @returns {number} The clamped value.
     */ clamp (value, min, max) {
        if (value >= max) return max;
        if (value <= min) return min;
        return value;
    },
    /**
     * Convert an 24 bit integer into an array of 3 bytes.
     *
     * @param {number} i - Number holding an integer value.
     * @returns {number[]} An array of 3 bytes.
     * @example
     * // Set bytes to [0x11, 0x22, 0x33]
     * const bytes = pc.math.intToBytes24(0x112233);
     */ intToBytes24 (i) {
        const r = i >> 16 & 0xff;
        const g = i >> 8 & 0xff;
        const b = i & 0xff;
        return [
            r,
            g,
            b
        ];
    },
    /**
     * Convert an 32 bit integer into an array of 4 bytes.
     *
     * @param {number} i - Number holding an integer value.
     * @returns {number[]} An array of 4 bytes.
     * @example
     * // Set bytes to [0x11, 0x22, 0x33, 0x44]
     * const bytes = pc.math.intToBytes32(0x11223344);
     */ intToBytes32 (i) {
        const r = i >> 24 & 0xff;
        const g = i >> 16 & 0xff;
        const b = i >> 8 & 0xff;
        const a = i & 0xff;
        return [
            r,
            g,
            b,
            a
        ];
    },
    /**
     * Convert 3 8 bit Numbers into a single unsigned 24 bit Number.
     *
     * @param {number} r - A single byte (0-255).
     * @param {number} g - A single byte (0-255).
     * @param {number} b - A single byte (0-255).
     * @returns {number} A single unsigned 24 bit Number.
     * @example
     * // Set result1 to 0x112233 from an array of 3 values
     * const result1 = pc.math.bytesToInt24([0x11, 0x22, 0x33]);
     *
     * // Set result2 to 0x112233 from 3 discrete values
     * const result2 = pc.math.bytesToInt24(0x11, 0x22, 0x33);
     */ bytesToInt24 (r, g, b) {
        if (r.length) {
            b = r[2];
            g = r[1];
            r = r[0];
        }
        return r << 16 | g << 8 | b;
    },
    /**
     * Convert 4 1-byte Numbers into a single unsigned 32bit Number.
     *
     * @param {number} r - A single byte (0-255).
     * @param {number} g - A single byte (0-255).
     * @param {number} b - A single byte (0-255).
     * @param {number} a - A single byte (0-255).
     * @returns {number} A single unsigned 32bit Number.
     * @example
     * // Set result1 to 0x11223344 from an array of 4 values
     * const result1 = pc.math.bytesToInt32([0x11, 0x22, 0x33, 0x44]);
     *
     * // Set result2 to 0x11223344 from 4 discrete values
     * const result2 = pc.math.bytesToInt32(0x11, 0x22, 0x33, 0x44);
     */ bytesToInt32 (r, g, b, a) {
        if (r.length) {
            a = r[3];
            b = r[2];
            g = r[1];
            r = r[0];
        }
        // Why ((r << 24)>>>0)?
        // << operator uses signed 32 bit numbers, so 128<<24 is negative.
        // >>> used unsigned so >>>0 converts back to an unsigned.
        // See https://stackoverflow.com/questions/1908492/unsigned-integer-in-javascript
        return (r << 24 | g << 16 | b << 8 | a) >>> 0;
    },
    /**
     * Calculates the linear interpolation of two numbers.
     *
     * @param {number} a - Number to linearly interpolate from.
     * @param {number} b - Number to linearly interpolate to.
     * @param {number} alpha - The value controlling the result of interpolation. When alpha is 0,
     * a is returned. When alpha is 1, b is returned. Between 0 and 1, a linear interpolation
     * between a and b is returned. alpha is clamped between 0 and 1.
     * @returns {number} The linear interpolation of two numbers.
     */ lerp (a, b, alpha) {
        return a + (b - a) * math.clamp(alpha, 0, 1);
    },
    /**
     * Calculates the linear interpolation of two angles ensuring that interpolation is correctly
     * performed across the 360 to 0 degree boundary. Angles are supplied in degrees.
     *
     * @param {number} a - Angle (in degrees) to linearly interpolate from.
     * @param {number} b - Angle (in degrees) to linearly interpolate to.
     * @param {number} alpha - The value controlling the result of interpolation. When alpha is 0,
     * a is returned. When alpha is 1, b is returned. Between 0 and 1, a linear interpolation
     * between a and b is returned. alpha is clamped between 0 and 1.
     * @returns {number} The linear interpolation of two angles.
     */ lerpAngle (a, b, alpha) {
        if (b - a > 180) {
            b -= 360;
        }
        if (b - a < -180) {
            b += 360;
        }
        return math.lerp(a, b, math.clamp(alpha, 0, 1));
    },
    /**
     * Returns true if argument is a power-of-two and false otherwise.
     *
     * @param {number} x - Number to check for power-of-two property.
     * @returns {boolean} true if power-of-two and false otherwise.
     */ powerOfTwo (x) {
        return x !== 0 && !(x & x - 1);
    },
    /**
     * Returns the next power of 2 for the specified value.
     *
     * @param {number} val - The value for which to calculate the next power of 2.
     * @returns {number} The next power of 2.
     */ nextPowerOfTwo (val) {
        val--;
        val |= val >> 1;
        val |= val >> 2;
        val |= val >> 4;
        val |= val >> 8;
        val |= val >> 16;
        val++;
        return val;
    },
    /**
     * Returns the nearest (smaller or larger) power of 2 for the specified value.
     *
     * @param {number} val - The value for which to calculate the nearest power of 2.
     * @returns {number} The nearest power of 2.
     */ nearestPowerOfTwo (val) {
        return Math.pow(2, Math.round(Math.log2(val)));
    },
    /**
     * Return a pseudo-random number between min and max. The number generated is in the range
     * [min, max), that is inclusive of the minimum but exclusive of the maximum.
     *
     * @param {number} min - Lower bound for range.
     * @param {number} max - Upper bound for range.
     * @returns {number} Pseudo-random number between the supplied range.
     */ random (min, max) {
        const diff = max - min;
        return Math.random() * diff + min;
    },
    /**
     * The function interpolates smoothly between two input values based on a third one that should
     * be between the first two. The returned value is clamped between 0 and 1.
     *
     * The slope (i.e. derivative) of the smoothstep function starts at 0 and ends at 0. This makes
     * it easy to create a sequence of transitions using smoothstep to interpolate each segment
     * rather than using a more sophisticated or expensive interpolation technique.
     *
     * See https://en.wikipedia.org/wiki/Smoothstep for more details.
     *
     * @param {number} min - The lower bound of the interpolation range.
     * @param {number} max - The upper bound of the interpolation range.
     * @param {number} x - The value to interpolate.
     * @returns {number} The smoothly interpolated value clamped between zero and one.
     */ smoothstep (min, max, x) {
        if (x <= min) return 0;
        if (x >= max) return 1;
        x = (x - min) / (max - min);
        return x * x * (3 - 2 * x);
    },
    /**
     * An improved version of the {@link math.smoothstep} function which has zero 1st and 2nd order
     * derivatives at t=0 and t=1.
     *
     * See https://en.wikipedia.org/wiki/Smoothstep#Variations for more details.
     *
     * @param {number} min - The lower bound of the interpolation range.
     * @param {number} max - The upper bound of the interpolation range.
     * @param {number} x - The value to interpolate.
     * @returns {number} The smoothly interpolated value clamped between zero and one.
     */ smootherstep (min, max, x) {
        if (x <= min) return 0;
        if (x >= max) return 1;
        x = (x - min) / (max - min);
        return x * x * x * (x * (x * 6 - 15) + 10);
    },
    /**
     * Rounds a number up to nearest multiple.
     *
     * @param {number} numToRound - The number to round up.
     * @param {number} multiple - The multiple to round up to.
     * @returns {number} A number rounded up to nearest multiple.
     */ roundUp (numToRound, multiple) {
        if (multiple === 0) {
            return numToRound;
        }
        return Math.ceil(numToRound / multiple) * multiple;
    },
    /**
     * Checks whether a given number resides between two other given numbers.
     *
     * @param {number} num - The number to check the position of.
     * @param {number} a - The first upper or lower threshold to check between.
     * @param {number} b - The second upper or lower threshold to check between.
     * @param {boolean} inclusive - If true, a num param which is equal to a or b will return true.
     * @returns {boolean} true if between or false otherwise.
     * @ignore
     */ between (num, a, b, inclusive) {
        const min = Math.min(a, b);
        const max = Math.max(a, b);
        return inclusive ? num >= min && num <= max : num > min && num < max;
    }
};

/**
 * An RGBA color.
 *
 * Each color component is a floating point value in the range 0 to 1. The {@link r} (red),
 * {@link g} (green) and {@link b} (blue) components define a color in RGB color space. The
 * {@link a} (alpha) component defines transparency. An alpha of 1 is fully opaque. An alpha of
 * 0 is fully transparent.
 *
 * @category Math
 */ class Color {
    /**
     * Creates a new Color instance.
     *
     * @overload
     * @param {number} [r] - The r value. Defaults to 0.
     * @param {number} [g] - The g value. Defaults to 0.
     * @param {number} [b] - The b value. Defaults to 0.
     * @param {number} [a] - The a value. Defaults to 1.
     * @example
     * const c1 = new pc.Color(); // defaults to 0, 0, 0, 1
     * const c2 = new pc.Color(0.1, 0.2, 0.3, 0.4);
     */ /**
     * Creates a new Color instance.
     *
     * @overload
     * @param {number[]} arr - The array to set the color values from.
     * @example
     * const c = new pc.Color([0.1, 0.2, 0.3, 0.4]);
     */ /**
     * @param {number|number[]} [r] - The r value. Defaults to 0. If r is an array of length 3 or
     * 4, the array will be used to populate all components.
     * @param {number} [g] - The g value. Defaults to 0.
     * @param {number} [b] - The b value. Defaults to 0.
     * @param {number} [a] - The a value. Defaults to 1.
     */ constructor(r = 0, g = 0, b = 0, a = 1){
        const length = r.length;
        if (length === 3 || length === 4) {
            this.r = r[0];
            this.g = r[1];
            this.b = r[2];
            this.a = r[3] ?? 1;
        } else {
            this.r = r;
            this.g = g;
            this.b = b;
            this.a = a;
        }
    }
    /**
     * Returns a clone of the specified color.
     *
     * @returns {this} A duplicate color object.
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr(this.r, this.g, this.b, this.a);
    }
    /**
     * Copies the contents of a source color to a destination color.
     *
     * @param {Color} rhs - A color to copy to the specified color.
     * @returns {Color} Self for chaining.
     * @example
     * const src = new pc.Color(1, 0, 0, 1);
     * const dst = new pc.Color();
     *
     * dst.copy(src);
     *
     * console.log("The two colors are " + (dst.equals(src) ? "equal" : "different"));
     */ copy(rhs) {
        this.r = rhs.r;
        this.g = rhs.g;
        this.b = rhs.b;
        this.a = rhs.a;
        return this;
    }
    /**
     * Reports whether two colors are equal.
     *
     * @param {Color} rhs - The color to compare to the specified color.
     * @returns {boolean} True if the colors are equal and false otherwise.
     * @example
     * const a = new pc.Color(1, 0, 0, 1);
     * const b = new pc.Color(1, 1, 0, 1);
     * console.log("The two colors are " + (a.equals(b) ? "equal" : "different"));
     */ equals(rhs) {
        return this.r === rhs.r && this.g === rhs.g && this.b === rhs.b && this.a === rhs.a;
    }
    /**
     * Assign values to the color components, including alpha.
     *
     * @param {number} r - The value for red (0-1).
     * @param {number} g - The value for blue (0-1).
     * @param {number} b - The value for green (0-1).
     * @param {number} [a] - The value for the alpha (0-1), defaults to 1.
     * @returns {Color} Self for chaining.
     */ set(r, g, b, a = 1) {
        this.r = r;
        this.g = g;
        this.b = b;
        this.a = a;
        return this;
    }
    /**
     * Returns the result of a linear interpolation between two specified colors.
     *
     * @param {Color} lhs - The color to interpolate from.
     * @param {Color} rhs - The color to interpolate to.
     * @param {number} alpha - The value controlling the point of interpolation. Between 0 and 1,
     * the linear interpolant will occur on a straight line between lhs and rhs. Outside of this
     * range, the linear interpolant will occur on a ray extrapolated from this line.
     * @returns {Color} Self for chaining.
     * @example
     * const a = new pc.Color(0, 0, 0);
     * const b = new pc.Color(1, 1, 0.5);
     * const r = new pc.Color();
     *
     * r.lerp(a, b, 0);   // r is equal to a
     * r.lerp(a, b, 0.5); // r is 0.5, 0.5, 0.25
     * r.lerp(a, b, 1);   // r is equal to b
     */ lerp(lhs, rhs, alpha) {
        this.r = lhs.r + alpha * (rhs.r - lhs.r);
        this.g = lhs.g + alpha * (rhs.g - lhs.g);
        this.b = lhs.b + alpha * (rhs.b - lhs.b);
        this.a = lhs.a + alpha * (rhs.a - lhs.a);
        return this;
    }
    /**
     * Converts the color from gamma to linear color space.
     *
     * @param {Color} [src] - The color to convert to linear color space. If not set, the operation
     * is done in place.
     * @returns {Color} Self for chaining.
     */ linear(src = this) {
        this.r = Math.pow(src.r, 2.2);
        this.g = Math.pow(src.g, 2.2);
        this.b = Math.pow(src.b, 2.2);
        this.a = src.a;
        return this;
    }
    /**
     * Converts the color from linear to gamma color space.
     *
     * @param {Color} [src] - The color to convert to gamma color space. If not set, the operation is
     * done in place.
     * @returns {Color} Self for chaining.
     */ gamma(src = this) {
        this.r = Math.pow(src.r, 1 / 2.2);
        this.g = Math.pow(src.g, 1 / 2.2);
        this.b = Math.pow(src.b, 1 / 2.2);
        this.a = src.a;
        return this;
    }
    /**
     * Multiplies RGB elements of a Color by a number. Note that the alpha value is left unchanged.
     *
     * @param {number} scalar - The number to multiply by.
     * @returns {Color} Self for chaining.
     */ mulScalar(scalar) {
        this.r *= scalar;
        this.g *= scalar;
        this.b *= scalar;
        return this;
    }
    /**
     * Set the values of the color from a string representation '#11223344' or '#112233'.
     *
     * @param {string} hex - A string representation in the format '#RRGGBBAA' or '#RRGGBB'. Where
     * RR, GG, BB, AA are red, green, blue and alpha values. This is the same format used in
     * HTML/CSS.
     * @returns {Color} Self for chaining.
     */ fromString(hex) {
        const i = parseInt(hex.replace('#', '0x'), 16);
        let bytes;
        if (hex.length > 7) {
            bytes = math.intToBytes32(i);
        } else {
            bytes = math.intToBytes24(i);
            bytes[3] = 255;
        }
        this.set(bytes[0] / 255, bytes[1] / 255, bytes[2] / 255, bytes[3] / 255);
        return this;
    }
    /**
     * Set the values of the vector from an array.
     *
     * @param {number[]} arr - The array to set the vector values from.
     * @param {number} [offset] - The zero-based index at which to start copying elements from the
     * array. Default is 0.
     * @returns {Color} Self for chaining.
     * @example
     * const c = new pc.Color();
     * c.fromArray([1, 0, 1, 1]);
     * // c is set to [1, 0, 1, 1]
     */ fromArray(arr, offset = 0) {
        this.r = arr[offset] ?? this.r;
        this.g = arr[offset + 1] ?? this.g;
        this.b = arr[offset + 2] ?? this.b;
        this.a = arr[offset + 3] ?? this.a;
        return this;
    }
    /**
     * Converts the color to string form. The format is '#RRGGBBAA', where RR, GG, BB, AA are the
     * red, green, blue and alpha values. When the alpha value is not included (the default), this
     * is the same format as used in HTML/CSS.
     *
     * @param {boolean} alpha - If true, the output string will include the alpha value.
     * @param {boolean} [asArray] - If true, the output will be an array of numbers. Defaults to false.
     * @returns {string} The color in string form.
     * @example
     * const c = new pc.Color(1, 1, 1);
     * // Outputs #ffffffff
     * console.log(c.toString());
     */ toString(alpha, asArray) {
        const { r, g, b, a } = this;
        // If any component exceeds 1 (HDR), return the color as an array
        if (asArray || r > 1 || g > 1 || b > 1) {
            return `${r.toFixed(3)}, ${g.toFixed(3)}, ${b.toFixed(3)}, ${a.toFixed(3)}`;
        }
        let s = `#${((1 << 24) + (Math.round(r * 255) << 16) + (Math.round(g * 255) << 8) + Math.round(b * 255)).toString(16).slice(1)}`;
        if (alpha === true) {
            const aa = Math.round(a * 255).toString(16);
            if (this.a < 16 / 255) {
                s += `0${aa}`;
            } else {
                s += aa;
            }
        }
        return s;
    }
    /**
     * @overload
     * @param {number[]} [arr] - The array to populate with the color's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]} The color as an array.
     */ /**
     * @overload
     * @param {ArrayBufferView} arr - The array to populate with the color's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {ArrayBufferView} The color as an array.
     */ /**
     * Converts the color to an array.
     *
     * @param {number[]|ArrayBufferView} [arr] - The array to populate with the color's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @param {boolean} [alpha] - If true, the output array will include the alpha value.
     * @returns {number[]|ArrayBufferView} The color as an array.
     * @example
     * const c = new pc.Color(1, 1, 1);
     * // Outputs [1, 1, 1, 1]
     * console.log(c.toArray());
     */ toArray(arr = [], offset = 0, alpha = true) {
        arr[offset] = this.r;
        arr[offset + 1] = this.g;
        arr[offset + 2] = this.b;
        if (alpha) {
            arr[offset + 3] = this.a;
        }
        return arr;
    }
    static{
        /**
     * A constant color set to black [0, 0, 0, 1].
     *
     * @type {Color}
     * @readonly
     */ this.BLACK = Object.freeze(new Color(0, 0, 0, 1));
    }
    static{
        /**
     * A constant color set to blue [0, 0, 1, 1].
     *
     * @type {Color}
     * @readonly
     */ this.BLUE = Object.freeze(new Color(0, 0, 1, 1));
    }
    static{
        /**
     * A constant color set to cyan [0, 1, 1, 1].
     *
     * @type {Color}
     * @readonly
     */ this.CYAN = Object.freeze(new Color(0, 1, 1, 1));
    }
    static{
        /**
     * A constant color set to gray [0.5, 0.5, 0.5, 1].
     *
     * @type {Color}
     * @readonly
     */ this.GRAY = Object.freeze(new Color(0.5, 0.5, 0.5, 1));
    }
    static{
        /**
     * A constant color set to green [0, 1, 0, 1].
     *
     * @type {Color}
     * @readonly
     */ this.GREEN = Object.freeze(new Color(0, 1, 0, 1));
    }
    static{
        /**
     * A constant color set to magenta [1, 0, 1, 1].
     *
     * @type {Color}
     * @readonly
     */ this.MAGENTA = Object.freeze(new Color(1, 0, 1, 1));
    }
    static{
        /**
     * A constant color set to red [1, 0, 0, 1].
     *
     * @type {Color}
     * @readonly
     */ this.RED = Object.freeze(new Color(1, 0, 0, 1));
    }
    static{
        /**
     * A constant color set to white [1, 1, 1, 1].
     *
     * @type {Color}
     * @readonly
     */ this.WHITE = Object.freeze(new Color(1, 1, 1, 1));
    }
    static{
        /**
     * A constant color set to yellow [1, 1, 0, 1].
     *
     * @type {Color}
     * @readonly
     */ this.YELLOW = Object.freeze(new Color(1, 1, 0, 1));
    }
}

/**
 * @import { Color } from './color.js'
 */ const floatView = new Float32Array(1);
const int32View = new Int32Array(floatView.buffer);
/**
 * Utility static class providing functionality to pack float values to various storage
 * representations.
 *
 * @category Math
 */ class FloatPacking {
    /**
     * Packs a float to a 16-bit half-float representation used by the GPU.
     *
     * @param {number} value - The float value to pack.
     * @returns {number} The packed value.
     */ static float2Half(value) {
        // based on https://esdiscuss.org/topic/float16array
        // This method is faster than the OpenEXR implementation (very often
        // used, eg. in Ogre), with the additional benefit of rounding, inspired
        // by James Tursa?s half-precision code.
        floatView[0] = value;
        const x = int32View[0];
        let bits = x >> 16 & 0x8000; // Get the sign
        let m = x >> 12 & 0x07ff; // Keep one extra bit for rounding
        const e = x >> 23 & 0xff; // Using int is faster here
        // If zero, or denormal, or exponent underflows too much for a denormal half, return signed zero.
        if (e < 103) {
            return bits;
        }
        // If NaN, return NaN. If Inf or exponent overflow, return Inf.
        if (e > 142) {
            bits |= 0x7c00;
            // If exponent was 0xff and one mantissa bit was set, it means NaN,
            // not Inf, so make sure we set one mantissa bit too.
            bits |= (e === 255 ? 0 : 1) && x & 0x007fffff;
            return bits;
        }
        // If exponent underflows but not too much, return a denormal
        if (e < 113) {
            m |= 0x0800;
            // Extra rounding may overflow and set mantissa to 0 and exponent to 1, which is OK.
            bits |= (m >> 114 - e) + (m >> 113 - e & 1);
            return bits;
        }
        bits |= e - 112 << 10 | m >> 1;
        // Extra rounding. An overflow will set mantissa to 0 and increment the exponent, which is OK.
        bits += m & 1;
        return bits;
    }
    /**
     * Converts bits of a 32-bit float into RGBA8 format and stores the result in a provided color.
     * The float can be reconstructed in shader using the uintBitsToFloat instruction.
     *
     * @param {number} value - The float value to convert.
     * @param {Color} data - The color to store the RGBA8 packed value in.
     *
     * @ignore
     */ static float2RGBA8(value, data) {
        floatView[0] = value;
        const intBits = int32View[0];
        data.r = (intBits >> 24 & 0xFF) / 255.0;
        data.g = (intBits >> 16 & 0xFF) / 255.0;
        data.b = (intBits >> 8 & 0xFF) / 255.0;
        data.a = (intBits & 0xFF) / 255.0;
    }
}

/**
 * 3-dimensional vector.
 *
 * @category Math
 */ class Vec3 {
    /**
     * Creates a new Vec3 instance.
     *
     * @overload
     * @param {number} [x] - The x value. Defaults to 0.
     * @param {number} [y] - The y value. Defaults to 0.
     * @param {number} [z] - The z value. Defaults to 0.
     * @example
     * const v1 = new pc.Vec3(); // defaults to 0, 0, 0
     * const v2 = new pc.Vec3(1, 2, 3);
     */ /**
     * Creates a new Vec3 instance.
     *
     * @overload
     * @param {number[]} arr - The array to set the vector values from.
     * @example
     * const v = new pc.Vec3([1, 2, 3]);
     */ /**
     * @param {number|number[]} [x] - The x value. Defaults to 0. If x is an array of length 3, the
     * array will be used to populate all components.
     * @param {number} [y] - The y value. Defaults to 0.
     * @param {number} [z] - The z value. Defaults to 0.
     */ constructor(x = 0, y = 0, z = 0){
        if (x.length === 3) {
            this.x = x[0];
            this.y = x[1];
            this.z = x[2];
        } else {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }
    /**
     * Adds a 3-dimensional vector to another in place.
     *
     * @param {Vec3} rhs - The vector to add to the specified vector.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(10, 10, 10);
     * const b = new pc.Vec3(20, 20, 20);
     *
     * a.add(b);
     *
     * // Outputs [30, 30, 30]
     * console.log("The result of the addition is: " + a.toString());
     */ add(rhs) {
        this.x += rhs.x;
        this.y += rhs.y;
        this.z += rhs.z;
        return this;
    }
    /**
     * Adds two 3-dimensional vectors together and returns the result.
     *
     * @param {Vec3} lhs - The first vector operand for the addition.
     * @param {Vec3} rhs - The second vector operand for the addition.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(10, 10, 10);
     * const b = new pc.Vec3(20, 20, 20);
     * const r = new pc.Vec3();
     *
     * r.add2(a, b);
     * // Outputs [30, 30, 30]
     *
     * console.log("The result of the addition is: " + r.toString());
     */ add2(lhs, rhs) {
        this.x = lhs.x + rhs.x;
        this.y = lhs.y + rhs.y;
        this.z = lhs.z + rhs.z;
        return this;
    }
    /**
     * Adds a number to each element of a vector.
     *
     * @param {number} scalar - The number to add.
     * @returns {Vec3} Self for chaining.
     * @example
     * const vec = new pc.Vec3(3, 4, 5);
     *
     * vec.addScalar(2);
     *
     * // Outputs [5, 6, 7]
     * console.log("The result of the addition is: " + vec.toString());
     */ addScalar(scalar) {
        this.x += scalar;
        this.y += scalar;
        this.z += scalar;
        return this;
    }
    /**
     * Adds a 3-dimensional vector scaled by scalar value. Does not modify the vector being added.
     *
     * @param {Vec3} rhs - The vector to add to the specified vector.
     * @param {number} scalar - The number to multiply the added vector with.
     * @returns {Vec3} Self for chaining.
     * @example
     * const vec = new pc.Vec3(1, 2, 3);
     *
     * vec.addScaled(pc.Vec3.UP, 2);
     *
     * // Outputs [1, 4, 3]
     * console.log("The result of the addition is: " + vec.toString());
     */ addScaled(rhs, scalar) {
        this.x += rhs.x * scalar;
        this.y += rhs.y * scalar;
        this.z += rhs.z * scalar;
        return this;
    }
    /**
     * Returns an identical copy of the specified 3-dimensional vector.
     *
     * @returns {this} A 3-dimensional vector containing the result of the cloning.
     * @example
     * const v = new pc.Vec3(10, 20, 30);
     * const vclone = v.clone();
     * console.log("The result of the cloning is: " + vclone.toString());
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr(this.x, this.y, this.z);
    }
    /**
     * Copies the contents of a source 3-dimensional vector to a destination 3-dimensional vector.
     *
     * @param {Vec3} rhs - A vector to copy to the specified vector.
     * @returns {Vec3} Self for chaining.
     * @example
     * const src = new pc.Vec3(10, 20, 30);
     * const dst = new pc.Vec3();
     *
     * dst.copy(src);
     *
     * console.log("The two vectors are " + (dst.equals(src) ? "equal" : "different"));
     */ copy(rhs) {
        this.x = rhs.x;
        this.y = rhs.y;
        this.z = rhs.z;
        return this;
    }
    /**
     * Returns the result of a cross product operation performed on the two specified 3-dimensional
     * vectors.
     *
     * @param {Vec3} lhs - The first 3-dimensional vector operand of the cross product.
     * @param {Vec3} rhs - The second 3-dimensional vector operand of the cross product.
     * @returns {Vec3} Self for chaining.
     * @example
     * const back = new pc.Vec3().cross(pc.Vec3.RIGHT, pc.Vec3.UP);
     *
     * // Prints the Z axis (i.e. [0, 0, 1])
     * console.log("The result of the cross product is: " + back.toString());
     */ cross(lhs, rhs) {
        // Create temporary variables in case lhs or rhs are 'this'
        const lx = lhs.x;
        const ly = lhs.y;
        const lz = lhs.z;
        const rx = rhs.x;
        const ry = rhs.y;
        const rz = rhs.z;
        this.x = ly * rz - ry * lz;
        this.y = lz * rx - rz * lx;
        this.z = lx * ry - rx * ly;
        return this;
    }
    /**
     * Returns the distance between the two specified 3-dimensional vectors.
     *
     * @param {Vec3} rhs - The second 3-dimensional vector to test.
     * @returns {number} The distance between the two vectors.
     * @example
     * const v1 = new pc.Vec3(5, 10, 20);
     * const v2 = new pc.Vec3(10, 20, 40);
     * const d = v1.distance(v2);
     * console.log("The distance between v1 and v2 is: " + d);
     */ distance(rhs) {
        const x = this.x - rhs.x;
        const y = this.y - rhs.y;
        const z = this.z - rhs.z;
        return Math.sqrt(x * x + y * y + z * z);
    }
    /**
     * Divides a 3-dimensional vector by another in place.
     *
     * @param {Vec3} rhs - The vector to divide the specified vector by.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(4, 9, 16);
     * const b = new pc.Vec3(2, 3, 4);
     *
     * a.div(b);
     *
     * // Outputs [2, 3, 4]
     * console.log("The result of the division is: " + a.toString());
     */ div(rhs) {
        this.x /= rhs.x;
        this.y /= rhs.y;
        this.z /= rhs.z;
        return this;
    }
    /**
     * Divides one 3-dimensional vector by another and writes the result to the specified vector.
     *
     * @param {Vec3} lhs - The dividend vector (the vector being divided).
     * @param {Vec3} rhs - The divisor vector (the vector dividing the dividend).
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(4, 9, 16);
     * const b = new pc.Vec3(2, 3, 4);
     * const r = new pc.Vec3();
     *
     * r.div2(a, b);
     *
     * // Outputs [2, 3, 4]
     * console.log("The result of the division is: " + r.toString());
     */ div2(lhs, rhs) {
        this.x = lhs.x / rhs.x;
        this.y = lhs.y / rhs.y;
        this.z = lhs.z / rhs.z;
        return this;
    }
    /**
     * Divides each element of a vector by a number.
     *
     * @param {number} scalar - The number to divide by.
     * @returns {Vec3} Self for chaining.
     * @example
     * const vec = new pc.Vec3(3, 6, 9);
     *
     * vec.divScalar(3);
     *
     * // Outputs [1, 2, 3]
     * console.log("The result of the division is: " + vec.toString());
     */ divScalar(scalar) {
        this.x /= scalar;
        this.y /= scalar;
        this.z /= scalar;
        return this;
    }
    /**
     * Returns the result of a dot product operation performed on the two specified 3-dimensional
     * vectors.
     *
     * @param {Vec3} rhs - The second 3-dimensional vector operand of the dot product.
     * @returns {number} The result of the dot product operation.
     * @example
     * const v1 = new pc.Vec3(5, 10, 20);
     * const v2 = new pc.Vec3(10, 20, 40);
     * const v1dotv2 = v1.dot(v2);
     * console.log("The result of the dot product is: " + v1dotv2);
     */ dot(rhs) {
        return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;
    }
    /**
     * Reports whether two vectors are equal.
     *
     * @param {Vec3} rhs - The vector to compare to the specified vector.
     * @returns {boolean} True if the vectors are equal and false otherwise.
     * @example
     * const a = new pc.Vec3(1, 2, 3);
     * const b = new pc.Vec3(4, 5, 6);
     * console.log("The two vectors are " + (a.equals(b) ? "equal" : "different"));
     */ equals(rhs) {
        return this.x === rhs.x && this.y === rhs.y && this.z === rhs.z;
    }
    /**
     * Reports whether two vectors are equal using an absolute error tolerance.
     *
     * @param {Vec3} rhs - The vector to be compared against.
     * @param {number} [epsilon] - The maximum difference between each component of the two
     * vectors. Defaults to 1e-6.
     * @returns {boolean} True if the vectors are equal and false otherwise.
     * @example
     * const a = new pc.Vec3();
     * const b = new pc.Vec3();
     * console.log("The two vectors are approximately " + (a.equalsApprox(b, 1e-9) ? "equal" : "different"));
     */ equalsApprox(rhs, epsilon = 1e-6) {
        return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon;
    }
    /**
     * Returns the magnitude of the specified 3-dimensional vector.
     *
     * @returns {number} The magnitude of the specified 3-dimensional vector.
     * @example
     * const vec = new pc.Vec3(3, 4, 0);
     * const len = vec.length();
     * // Outputs 5
     * console.log("The length of the vector is: " + len);
     */ length() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }
    /**
     * Returns the magnitude squared of the specified 3-dimensional vector.
     *
     * @returns {number} The magnitude of the specified 3-dimensional vector.
     * @example
     * const vec = new pc.Vec3(3, 4, 0);
     * const len = vec.lengthSq();
     * // Outputs 25
     * console.log("The length squared of the vector is: " + len);
     */ lengthSq() {
        return this.x * this.x + this.y * this.y + this.z * this.z;
    }
    /**
     * Returns the result of a linear interpolation between two specified 3-dimensional vectors.
     *
     * @param {Vec3} lhs - The 3-dimensional to interpolate from.
     * @param {Vec3} rhs - The 3-dimensional to interpolate to.
     * @param {number} alpha - The value controlling the point of interpolation. Between 0 and 1,
     * the linear interpolant will occur on a straight line between lhs and rhs. Outside of this
     * range, the linear interpolant will occur on a ray extrapolated from this line.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(0, 0, 0);
     * const b = new pc.Vec3(10, 10, 10);
     * const r = new pc.Vec3();
     *
     * r.lerp(a, b, 0);   // r is equal to a
     * r.lerp(a, b, 0.5); // r is 5, 5, 5
     * r.lerp(a, b, 1);   // r is equal to b
     */ lerp(lhs, rhs, alpha) {
        this.x = lhs.x + alpha * (rhs.x - lhs.x);
        this.y = lhs.y + alpha * (rhs.y - lhs.y);
        this.z = lhs.z + alpha * (rhs.z - lhs.z);
        return this;
    }
    /**
     * Multiplies a 3-dimensional vector to another in place.
     *
     * @param {Vec3} rhs - The 3-dimensional vector used as the second multiplicand of the operation.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(2, 3, 4);
     * const b = new pc.Vec3(4, 5, 6);
     *
     * a.mul(b);
     *
     * // Outputs [8, 15, 24]
     * console.log("The result of the multiplication is: " + a.toString());
     */ mul(rhs) {
        this.x *= rhs.x;
        this.y *= rhs.y;
        this.z *= rhs.z;
        return this;
    }
    /**
     * Returns the result of multiplying the specified 3-dimensional vectors together.
     *
     * @param {Vec3} lhs - The 3-dimensional vector used as the first multiplicand of the operation.
     * @param {Vec3} rhs - The 3-dimensional vector used as the second multiplicand of the operation.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(2, 3, 4);
     * const b = new pc.Vec3(4, 5, 6);
     * const r = new pc.Vec3();
     *
     * r.mul2(a, b);
     *
     * // Outputs [8, 15, 24]
     * console.log("The result of the multiplication is: " + r.toString());
     */ mul2(lhs, rhs) {
        this.x = lhs.x * rhs.x;
        this.y = lhs.y * rhs.y;
        this.z = lhs.z * rhs.z;
        return this;
    }
    /**
     * Multiplies each element of a vector by a number.
     *
     * @param {number} scalar - The number to multiply by.
     * @returns {Vec3} Self for chaining.
     * @example
     * const vec = new pc.Vec3(3, 6, 9);
     *
     * vec.mulScalar(3);
     *
     * // Outputs [9, 18, 27]
     * console.log("The result of the multiplication is: " + vec.toString());
     */ mulScalar(scalar) {
        this.x *= scalar;
        this.y *= scalar;
        this.z *= scalar;
        return this;
    }
    /**
     * Returns this 3-dimensional vector converted to a unit vector in place. If the vector has a
     * length of zero, the vector's elements will be set to zero.
     *
     * @param {Vec3} [src] - The vector to normalize. If not set, the operation is done in place.
     * @returns {Vec3} Self for chaining.
     * @example
     * const v = new pc.Vec3(25, 0, 0);
     *
     * v.normalize();
     *
     * // Outputs [1, 0, 0]
     * console.log("The result of the vector normalization is: " + v.toString());
     */ normalize(src = this) {
        const lengthSq = src.x * src.x + src.y * src.y + src.z * src.z;
        if (lengthSq > 0) {
            const invLength = 1 / Math.sqrt(lengthSq);
            this.x = src.x * invLength;
            this.y = src.y * invLength;
            this.z = src.z * invLength;
        }
        return this;
    }
    /**
     * Each element is set to the largest integer less than or equal to its value.
     *
     * @param {Vec3} [src] - The vector to floor. If not set, the operation is done in place.
     * @returns {Vec3} Self for chaining.
     */ floor(src = this) {
        this.x = Math.floor(src.x);
        this.y = Math.floor(src.y);
        this.z = Math.floor(src.z);
        return this;
    }
    /**
     * Each element is rounded up to the next largest integer.
     *
     * @param {Vec3} [src] - The vector to ceil. If not set, the operation is done in place.
     * @returns {Vec3} Self for chaining.
     */ ceil(src = this) {
        this.x = Math.ceil(src.x);
        this.y = Math.ceil(src.y);
        this.z = Math.ceil(src.z);
        return this;
    }
    /**
     * Each element is rounded up or down to the nearest integer.
     *
     * @param {Vec3} [src] - The vector to round. If not set, the operation is done in place.
     * @returns {Vec3} Self for chaining.
     */ round(src = this) {
        this.x = Math.round(src.x);
        this.y = Math.round(src.y);
        this.z = Math.round(src.z);
        return this;
    }
    /**
     * Each element is assigned a value from rhs parameter if it is smaller.
     *
     * @param {Vec3} rhs - The 3-dimensional vector used as the source of elements to compare to.
     * @returns {Vec3} Self for chaining.
     */ min(rhs) {
        if (rhs.x < this.x) this.x = rhs.x;
        if (rhs.y < this.y) this.y = rhs.y;
        if (rhs.z < this.z) this.z = rhs.z;
        return this;
    }
    /**
     * Each element is assigned a value from rhs parameter if it is larger.
     *
     * @param {Vec3} rhs - The 3-dimensional vector used as the source of elements to compare to.
     * @returns {Vec3} Self for chaining.
     */ max(rhs) {
        if (rhs.x > this.x) this.x = rhs.x;
        if (rhs.y > this.y) this.y = rhs.y;
        if (rhs.z > this.z) this.z = rhs.z;
        return this;
    }
    /**
     * Projects this 3-dimensional vector onto the specified vector.
     *
     * @param {Vec3} rhs - The vector onto which the original vector will be projected on.
     * @returns {Vec3} Self for chaining.
     * @example
     * const v = new pc.Vec3(5, 5, 5);
     * const normal = new pc.Vec3(1, 0, 0);
     *
     * v.project(normal);
     *
     * // Outputs [5, 0, 0]
     * console.log("The result of the vector projection is: " + v.toString());
     */ project(rhs) {
        const a_dot_b = this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;
        const b_dot_b = rhs.x * rhs.x + rhs.y * rhs.y + rhs.z * rhs.z;
        const s = a_dot_b / b_dot_b;
        this.x = rhs.x * s;
        this.y = rhs.y * s;
        this.z = rhs.z * s;
        return this;
    }
    /**
     * Sets the specified 3-dimensional vector to the supplied numerical values.
     *
     * @param {number} x - The value to set on the first component of the vector.
     * @param {number} y - The value to set on the second component of the vector.
     * @param {number} z - The value to set on the third component of the vector.
     * @returns {Vec3} Self for chaining.
     * @example
     * const v = new pc.Vec3();
     * v.set(5, 10, 20);
     *
     * // Outputs [5, 10, 20]
     * console.log("The result of the vector set is: " + v.toString());
     */ set(x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
        return this;
    }
    /**
     * Subtracts a 3-dimensional vector from another in place.
     *
     * @param {Vec3} rhs - The vector to subtract from the specified vector.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(10, 10, 10);
     * const b = new pc.Vec3(20, 20, 20);
     *
     * a.sub(b);
     *
     * // Outputs [-10, -10, -10]
     * console.log("The result of the subtraction is: " + a.toString());
     */ sub(rhs) {
        this.x -= rhs.x;
        this.y -= rhs.y;
        this.z -= rhs.z;
        return this;
    }
    /**
     * Subtracts two 3-dimensional vectors from one another and returns the result.
     *
     * @param {Vec3} lhs - The first vector operand for the subtraction.
     * @param {Vec3} rhs - The second vector operand for the subtraction.
     * @returns {Vec3} Self for chaining.
     * @example
     * const a = new pc.Vec3(10, 10, 10);
     * const b = new pc.Vec3(20, 20, 20);
     * const r = new pc.Vec3();
     *
     * r.sub2(a, b);
     *
     * // Outputs [-10, -10, -10]
     * console.log("The result of the subtraction is: " + r.toString());
     */ sub2(lhs, rhs) {
        this.x = lhs.x - rhs.x;
        this.y = lhs.y - rhs.y;
        this.z = lhs.z - rhs.z;
        return this;
    }
    /**
     * Subtracts a number from each element of a vector.
     *
     * @param {number} scalar - The number to subtract.
     * @returns {Vec3} Self for chaining.
     * @example
     * const vec = new pc.Vec3(3, 4, 5);
     *
     * vec.subScalar(2);
     *
     * // Outputs [1, 2, 3]
     * console.log("The result of the subtraction is: " + vec.toString());
     */ subScalar(scalar) {
        this.x -= scalar;
        this.y -= scalar;
        this.z -= scalar;
        return this;
    }
    /**
     * Set the values of the vector from an array.
     *
     * @param {number[]|ArrayBufferView} arr - The array to set the vector values from.
     * @param {number} [offset] - The zero-based index at which to start copying elements from the
     * array. Default is 0.
     * @returns {Vec3} Self for chaining.
     * @example
     * const v = new pc.Vec3();
     * v.fromArray([20, 10, 5]);
     * // v is set to [20, 10, 5]
     */ fromArray(arr, offset = 0) {
        this.x = arr[offset] ?? this.x;
        this.y = arr[offset + 1] ?? this.y;
        this.z = arr[offset + 2] ?? this.z;
        return this;
    }
    /**
     * Converts the vector to string form.
     *
     * @returns {string} The vector in string form.
     * @example
     * const v = new pc.Vec3(20, 10, 5);
     * // Outputs [20, 10, 5]
     * console.log(v.toString());
     */ toString() {
        return `[${this.x}, ${this.y}, ${this.z}]`;
    }
    /**
     * @overload
     * @param {number[]} [arr] - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]} The vector as an array.
     */ /**
     * @overload
     * @param {ArrayBufferView} arr - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {ArrayBufferView} The vector as an array.
     */ /**
     * Converts the vector to an array.
     *
     * @param {number[]|ArrayBufferView} [arr] - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]|ArrayBufferView} The vector as an array.
     * @example
     * const v = new pc.Vec3(20, 10, 5);
     * // Outputs [20, 10, 5]
     * console.log(v.toArray());
     */ toArray(arr = [], offset = 0) {
        arr[offset] = this.x;
        arr[offset + 1] = this.y;
        arr[offset + 2] = this.z;
        return arr;
    }
    static{
        /**
     * A constant vector set to [0, 0, 0].
     *
     * @type {Vec3}
     * @readonly
     */ this.ZERO = Object.freeze(new Vec3(0, 0, 0));
    }
    static{
        /**
     * A constant vector set to [0.5, 0.5, 0.5].
     *
     * @type {Vec3}
     * @readonly
     */ this.HALF = Object.freeze(new Vec3(0.5, 0.5, 0.5));
    }
    static{
        /**
     * A constant vector set to [1, 1, 1].
     *
     * @type {Vec3}
     * @readonly
     */ this.ONE = Object.freeze(new Vec3(1, 1, 1));
    }
    static{
        /**
     * A constant vector set to [0, 1, 0].
     *
     * @type {Vec3}
     * @readonly
     */ this.UP = Object.freeze(new Vec3(0, 1, 0));
    }
    static{
        /**
     * A constant vector set to [0, -1, 0].
     *
     * @type {Vec3}
     * @readonly
     */ this.DOWN = Object.freeze(new Vec3(0, -1, 0));
    }
    static{
        /**
     * A constant vector set to [1, 0, 0].
     *
     * @type {Vec3}
     * @readonly
     */ this.RIGHT = Object.freeze(new Vec3(1, 0, 0));
    }
    static{
        /**
     * A constant vector set to [-1, 0, 0].
     *
     * @type {Vec3}
     * @readonly
     */ this.LEFT = Object.freeze(new Vec3(-1, 0, 0));
    }
    static{
        /**
     * A constant vector set to [0, 0, -1].
     *
     * @type {Vec3}
     * @readonly
     */ this.FORWARD = Object.freeze(new Vec3(0, 0, -1));
    }
    static{
        /**
     * A constant vector set to [0, 0, 1].
     *
     * @type {Vec3}
     * @readonly
     */ this.BACK = Object.freeze(new Vec3(0, 0, 1));
    }
}

/**
 * @import { Mat4 } from './mat4.js'
 * @import { Quat } from './quat.js'
 */ /**
 * A 3x3 matrix.
 *
 * @category Math
 */ class Mat3 {
    /**
     * Create a new Mat3 instance. It is initialized to the identity matrix.
     */ constructor(){
        /**
     * Matrix elements in the form of a flat array.
     *
     * @type {Float32Array}
     */ this.data = new Float32Array(9);
        // Create an identity matrix. Note that a new Float32Array has all elements set
        // to zero by default, so we only need to set the relevant elements to one.
        this.data[0] = this.data[4] = this.data[8] = 1;
    }
    /**
     * Creates a duplicate of the specified matrix.
     *
     * @returns {this} A duplicate matrix.
     * @example
     * const src = new pc.Mat3().translate(10, 20, 30);
     * const dst = src.clone();
     * console.log("The two matrices are " + (src.equals(dst) ? "equal" : "different"));
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr().copy(this);
    }
    /**
     * Copies the contents of a source 3x3 matrix to a destination 3x3 matrix.
     *
     * @param {Mat3} rhs - A 3x3 matrix to be copied.
     * @returns {Mat3} Self for chaining.
     * @example
     * const src = new pc.Mat3().translate(10, 20, 30);
     * const dst = new pc.Mat3();
     * dst.copy(src);
     * console.log("The two matrices are " + (src.equals(dst) ? "equal" : "different"));
     */ copy(rhs) {
        const src = rhs.data;
        const dst = this.data;
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
        dst[4] = src[4];
        dst[5] = src[5];
        dst[6] = src[6];
        dst[7] = src[7];
        dst[8] = src[8];
        return this;
    }
    /**
     * Copies the contents of a source array[9] to a destination 3x3 matrix.
     *
     * @param {number[]} src - An array[9] to be copied.
     * @returns {Mat3} Self for chaining.
     * @example
     * const dst = new pc.Mat3();
     * dst.set([0, 1, 2, 3, 4, 5, 6, 7, 8]);
     */ set(src) {
        const dst = this.data;
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
        dst[4] = src[4];
        dst[5] = src[5];
        dst[6] = src[6];
        dst[7] = src[7];
        dst[8] = src[8];
        return this;
    }
    /**
     * Extracts the x-axis from the specified matrix.
     *
     * @param {Vec3} [x] - The vector to receive the x axis of the matrix.
     * @returns {Vec3} The x-axis of the specified matrix.
     */ getX(x = new Vec3()) {
        return x.set(this.data[0], this.data[1], this.data[2]);
    }
    /**
     * Extracts the y-axis from the specified matrix.
     *
     * @param {Vec3} [y] - The vector to receive the y axis of the matrix.
     * @returns {Vec3} The y-axis of the specified matrix.
     */ getY(y = new Vec3()) {
        return y.set(this.data[3], this.data[4], this.data[5]);
    }
    /**
     * Extracts the z-axis from the specified matrix.
     *
     * @param {Vec3} [z] - The vector to receive the z axis of the matrix.
     * @returns {Vec3} The z-axis of the specified matrix.
     */ getZ(z = new Vec3()) {
        return z.set(this.data[6], this.data[7], this.data[8]);
    }
    /**
     * Reports whether two matrices are equal.
     *
     * @param {Mat3} rhs - The other matrix.
     * @returns {boolean} True if the matrices are equal and false otherwise.
     * @example
     * const a = new pc.Mat3().translate(10, 20, 30);
     * const b = new pc.Mat3();
     * console.log("The two matrices are " + (a.equals(b) ? "equal" : "different"));
     */ equals(rhs) {
        const l = this.data;
        const r = rhs.data;
        return l[0] === r[0] && l[1] === r[1] && l[2] === r[2] && l[3] === r[3] && l[4] === r[4] && l[5] === r[5] && l[6] === r[6] && l[7] === r[7] && l[8] === r[8];
    }
    /**
     * Reports whether the specified matrix is the identity matrix.
     *
     * @returns {boolean} True if the matrix is identity and false otherwise.
     * @example
     * const m = new pc.Mat3();
     * console.log("The matrix is " + (m.isIdentity() ? "identity" : "not identity"));
     */ isIdentity() {
        const m = this.data;
        return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 0 && m[4] === 1 && m[5] === 0 && m[6] === 0 && m[7] === 0 && m[8] === 1;
    }
    /**
     * Sets the matrix to the identity matrix.
     *
     * @returns {Mat3} Self for chaining.
     * @example
     * m.setIdentity();
     * console.log("The matrix is " + (m.isIdentity() ? "identity" : "not identity"));
     */ setIdentity() {
        const m = this.data;
        m[0] = 1;
        m[1] = 0;
        m[2] = 0;
        m[3] = 0;
        m[4] = 1;
        m[5] = 0;
        m[6] = 0;
        m[7] = 0;
        m[8] = 1;
        return this;
    }
    /**
     * Converts the matrix to string form.
     *
     * @returns {string} The matrix in string form.
     * @example
     * const m = new pc.Mat3();
     * // Outputs [1, 0, 0, 0, 1, 0, 0, 0, 1]
     * console.log(m.toString());
     */ toString() {
        return `[${this.data.join(', ')}]`;
    }
    /**
     * Generates the transpose of the specified 3x3 matrix.
     *
     * @param {Mat3} [src] - The matrix to transpose. If not set, the matrix is transposed in-place.
     * @returns {Mat3} Self for chaining.
     * @example
     * const m = new pc.Mat3();
     *
     * // Transpose in place
     * m.transpose();
     */ transpose(src = this) {
        const s = src.data;
        const t = this.data;
        if (s === t) {
            let tmp;
            tmp = s[1];
            t[1] = s[3];
            t[3] = tmp;
            tmp = s[2];
            t[2] = s[6];
            t[6] = tmp;
            tmp = s[5];
            t[5] = s[7];
            t[7] = tmp;
        } else {
            t[0] = s[0];
            t[1] = s[3];
            t[2] = s[6];
            t[3] = s[1];
            t[4] = s[4];
            t[5] = s[7];
            t[6] = s[2];
            t[7] = s[5];
            t[8] = s[8];
        }
        return this;
    }
    /**
     * Converts the specified 4x4 matrix to a Mat3.
     *
     * @param {Mat4} m - The 4x4 matrix to convert.
     * @returns {Mat3} Self for chaining.
     */ setFromMat4(m) {
        const src = m.data;
        const dst = this.data;
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[4];
        dst[4] = src[5];
        dst[5] = src[6];
        dst[6] = src[8];
        dst[7] = src[9];
        dst[8] = src[10];
        return this;
    }
    /**
     * Sets this matrix to the given quaternion rotation.
     *
     * @param {Quat} r - A quaternion rotation.
     * @returns {Mat3} Self for chaining.
     * @example
     * const r = new pc.Quat(1, 2, 3, 4).normalize();
     *
     * const m = new pc.Mat4();
     * m.setFromQuat(r);
     */ setFromQuat(r) {
        const qx = r.x;
        const qy = r.y;
        const qz = r.z;
        const qw = r.w;
        const x2 = qx + qx;
        const y2 = qy + qy;
        const z2 = qz + qz;
        const xx = qx * x2;
        const xy = qx * y2;
        const xz = qx * z2;
        const yy = qy * y2;
        const yz = qy * z2;
        const zz = qz * z2;
        const wx = qw * x2;
        const wy = qw * y2;
        const wz = qw * z2;
        const m = this.data;
        m[0] = 1 - (yy + zz);
        m[1] = xy + wz;
        m[2] = xz - wy;
        m[3] = xy - wz;
        m[4] = 1 - (xx + zz);
        m[5] = yz + wx;
        m[6] = xz + wy;
        m[7] = yz - wx;
        m[8] = 1 - (xx + yy);
        return this;
    }
    /**
     * Set the matrix to the inverse of the specified 4x4 matrix.
     *
     * @param {Mat4} src - The 4x4 matrix to invert.
     * @returns {Mat3} Self for chaining.
     *
     * @ignore
     */ invertMat4(src) {
        const s = src.data;
        const a0 = s[0];
        const a1 = s[1];
        const a2 = s[2];
        const a4 = s[4];
        const a5 = s[5];
        const a6 = s[6];
        const a8 = s[8];
        const a9 = s[9];
        const a10 = s[10];
        const b11 = a10 * a5 - a6 * a9;
        const b21 = -a10 * a1 + a2 * a9;
        const b31 = a6 * a1 - a2 * a5;
        const b12 = -a10 * a4 + a6 * a8;
        const b22 = a10 * a0 - a2 * a8;
        const b32 = -a6 * a0 + a2 * a4;
        const b13 = a9 * a4 - a5 * a8;
        const b23 = -a9 * a0 + a1 * a8;
        const b33 = a5 * a0 - a1 * a4;
        const det = a0 * b11 + a1 * b12 + a2 * b13;
        if (det === 0) {
            this.setIdentity();
        } else {
            const invDet = 1 / det;
            const t = this.data;
            t[0] = b11 * invDet;
            t[1] = b21 * invDet;
            t[2] = b31 * invDet;
            t[3] = b12 * invDet;
            t[4] = b22 * invDet;
            t[5] = b32 * invDet;
            t[6] = b13 * invDet;
            t[7] = b23 * invDet;
            t[8] = b33 * invDet;
        }
        return this;
    }
    /**
     * Transforms a 3-dimensional vector by a 3x3 matrix.
     *
     * @param {Vec3} vec - The 3-dimensional vector to be transformed.
     * @param {Vec3} [res] - An optional 3-dimensional vector to receive the result of the
     * transformation.
     * @returns {Vec3} The input vector v transformed by the current instance.
     */ transformVector(vec, res = new Vec3()) {
        const m = this.data;
        const { x, y, z } = vec;
        res.x = x * m[0] + y * m[3] + z * m[6];
        res.y = x * m[1] + y * m[4] + z * m[7];
        res.z = x * m[2] + y * m[5] + z * m[8];
        return res;
    }
    static{
        /**
     * A constant matrix set to the identity.
     *
     * @type {Mat3}
     * @readonly
     */ this.IDENTITY = Object.freeze(new Mat3());
    }
    static{
        /**
     * A constant matrix with all elements set to 0.
     *
     * @type {Mat3}
     * @readonly
     */ this.ZERO = Object.freeze(new Mat3().set([
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ]));
    }
}

/**
 * A 2-dimensional vector.
 *
 * @category Math
 */ class Vec2 {
    /**
     * Creates a new Vec2 instance.
     *
     * @overload
     * @param {number} [x] - The x value. Defaults to 0.
     * @param {number} [y] - The y value. Defaults to 0.
     * @example
     * const v1 = new pc.Vec2(); // defaults to 0, 0
     * const v2 = new pc.Vec2(1, 2);
     */ /**
     * Creates a new Vec2 instance.
     *
     * @overload
     * @param {number[]} arr - The array to set the vector values from.
     * @example
     * const v = new pc.Vec2([1, 2]);
     */ /**
     * @param {number|number[]} [x] - The x value. Defaults to 0. If x is an array of length 2, the
     * array will be used to populate all components.
     * @param {number} [y] - The y value. Defaults to 0.
     */ constructor(x = 0, y = 0){
        if (x.length === 2) {
            this.x = x[0];
            this.y = x[1];
        } else {
            this.x = x;
            this.y = y;
        }
    }
    /**
     * Adds a 2-dimensional vector to another in place.
     *
     * @param {Vec2} rhs - The vector to add to the specified vector.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(10, 10);
     * const b = new pc.Vec2(20, 20);
     *
     * a.add(b);
     *
     * // Outputs [30, 30]
     * console.log("The result of the addition is: " + a.toString());
     */ add(rhs) {
        this.x += rhs.x;
        this.y += rhs.y;
        return this;
    }
    /**
     * Adds two 2-dimensional vectors together and returns the result.
     *
     * @param {Vec2} lhs - The first vector operand for the addition.
     * @param {Vec2} rhs - The second vector operand for the addition.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(10, 10);
     * const b = new pc.Vec2(20, 20);
     * const r = new pc.Vec2();
     *
     * r.add2(a, b);
     * // Outputs [30, 30]
     *
     * console.log("The result of the addition is: " + r.toString());
     */ add2(lhs, rhs) {
        this.x = lhs.x + rhs.x;
        this.y = lhs.y + rhs.y;
        return this;
    }
    /**
     * Adds a number to each element of a vector.
     *
     * @param {number} scalar - The number to add.
     * @returns {Vec2} Self for chaining.
     * @example
     * const vec = new pc.Vec2(3, 4);
     *
     * vec.addScalar(2);
     *
     * // Outputs [5, 6]
     * console.log("The result of the addition is: " + vec.toString());
     */ addScalar(scalar) {
        this.x += scalar;
        this.y += scalar;
        return this;
    }
    /**
     * Adds a 2-dimensional vector scaled by scalar value. Does not modify the vector being added.
     *
     * @param {Vec2} rhs - The vector to add to the specified vector.
     * @param {number} scalar - The number to multiply the added vector with.
     * @returns {Vec2} Self for chaining.
     * @example
     * const vec = new pc.Vec2(1, 2);
     *
     * vec.addScaled(pc.Vec2.UP, 2);
     *
     * // Outputs [1, 4]
     * console.log("The result of the addition is: " + vec.toString());
     */ addScaled(rhs, scalar) {
        this.x += rhs.x * scalar;
        this.y += rhs.y * scalar;
        return this;
    }
    /**
     * Returns an identical copy of the specified 2-dimensional vector.
     *
     * @returns {this} A 2-dimensional vector containing the result of the cloning.
     * @example
     * const v = new pc.Vec2(10, 20);
     * const vclone = v.clone();
     * console.log("The result of the cloning is: " + vclone.toString());
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr(this.x, this.y);
    }
    /**
     * Copies the contents of a source 2-dimensional vector to a destination 2-dimensional vector.
     *
     * @param {Vec2} rhs - A vector to copy to the specified vector.
     * @returns {Vec2} Self for chaining.
     * @example
     * const src = new pc.Vec2(10, 20);
     * const dst = new pc.Vec2();
     *
     * dst.copy(src);
     *
     * console.log("The two vectors are " + (dst.equals(src) ? "equal" : "different"));
     */ copy(rhs) {
        this.x = rhs.x;
        this.y = rhs.y;
        return this;
    }
    /**
     * Returns the result of a cross product operation performed on the two specified 2-dimensional
     * vectors.
     *
     * @param {Vec2} rhs - The second 2-dimensional vector operand of the cross product.
     * @returns {number} The cross product of the two vectors.
     * @example
     * const right = new pc.Vec2(1, 0);
     * const up = new pc.Vec2(0, 1);
     * const crossProduct = right.cross(up);
     *
     * // Prints 1
     * console.log("The result of the cross product is: " + crossProduct);
     */ cross(rhs) {
        return this.x * rhs.y - this.y * rhs.x;
    }
    /**
     * Returns the distance between the two specified 2-dimensional vectors.
     *
     * @param {Vec2} rhs - The second 2-dimensional vector to test.
     * @returns {number} The distance between the two vectors.
     * @example
     * const v1 = new pc.Vec2(5, 10);
     * const v2 = new pc.Vec2(10, 20);
     * const d = v1.distance(v2);
     * console.log("The distance between v1 and v2 is: " + d);
     */ distance(rhs) {
        const x = this.x - rhs.x;
        const y = this.y - rhs.y;
        return Math.sqrt(x * x + y * y);
    }
    /**
     * Divides a 2-dimensional vector by another in place.
     *
     * @param {Vec2} rhs - The vector to divide the specified vector by.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(4, 9);
     * const b = new pc.Vec2(2, 3);
     *
     * a.div(b);
     *
     * // Outputs [2, 3]
     * console.log("The result of the division is: " + a.toString());
     */ div(rhs) {
        this.x /= rhs.x;
        this.y /= rhs.y;
        return this;
    }
    /**
     * Divides one 2-dimensional vector by another and writes the result to the specified vector.
     *
     * @param {Vec2} lhs - The dividend vector (the vector being divided).
     * @param {Vec2} rhs - The divisor vector (the vector dividing the dividend).
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(4, 9);
     * const b = new pc.Vec2(2, 3);
     * const r = new pc.Vec2();
     *
     * r.div2(a, b);
     *
     * // Outputs [2, 3]
     * console.log("The result of the division is: " + r.toString());
     */ div2(lhs, rhs) {
        this.x = lhs.x / rhs.x;
        this.y = lhs.y / rhs.y;
        return this;
    }
    /**
     * Divides each element of a vector by a number.
     *
     * @param {number} scalar - The number to divide by.
     * @returns {Vec2} Self for chaining.
     * @example
     * const vec = new pc.Vec2(3, 6);
     *
     * vec.divScalar(3);
     *
     * // Outputs [1, 2]
     * console.log("The result of the division is: " + vec.toString());
     */ divScalar(scalar) {
        this.x /= scalar;
        this.y /= scalar;
        return this;
    }
    /**
     * Returns the result of a dot product operation performed on the two specified 2-dimensional
     * vectors.
     *
     * @param {Vec2} rhs - The second 2-dimensional vector operand of the dot product.
     * @returns {number} The result of the dot product operation.
     * @example
     * const v1 = new pc.Vec2(5, 10);
     * const v2 = new pc.Vec2(10, 20);
     * const v1dotv2 = v1.dot(v2);
     * console.log("The result of the dot product is: " + v1dotv2);
     */ dot(rhs) {
        return this.x * rhs.x + this.y * rhs.y;
    }
    /**
     * Reports whether two vectors are equal.
     *
     * @param {Vec2} rhs - The vector to compare to the specified vector.
     * @returns {boolean} True if the vectors are equal and false otherwise.
     * @example
     * const a = new pc.Vec2(1, 2);
     * const b = new pc.Vec2(4, 5);
     * console.log("The two vectors are " + (a.equals(b) ? "equal" : "different"));
     */ equals(rhs) {
        return this.x === rhs.x && this.y === rhs.y;
    }
    /**
     * Reports whether two vectors are equal using an absolute error tolerance.
     *
     * @param {Vec2} rhs - The vector to be compared against.
     * @param {number} [epsilon] - The maximum difference between each component of the two
     * vectors. Defaults to 1e-6.
     * @returns {boolean} True if the vectors are equal and false otherwise.
     * @example
     * const a = new pc.Vec2();
     * const b = new pc.Vec2();
     * console.log("The two vectors are approximately " + (a.equalsApprox(b, 1e-9) ? "equal" : "different"));
     */ equalsApprox(rhs, epsilon = 1e-6) {
        return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon;
    }
    /**
     * Returns the magnitude of the specified 2-dimensional vector.
     *
     * @returns {number} The magnitude of the specified 2-dimensional vector.
     * @example
     * const vec = new pc.Vec2(3, 4);
     * const len = vec.length();
     * // Outputs 5
     * console.log("The length of the vector is: " + len);
     */ length() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
    /**
     * Returns the magnitude squared of the specified 2-dimensional vector.
     *
     * @returns {number} The magnitude of the specified 2-dimensional vector.
     * @example
     * const vec = new pc.Vec2(3, 4);
     * const len = vec.lengthSq();
     * // Outputs 25
     * console.log("The length squared of the vector is: " + len);
     */ lengthSq() {
        return this.x * this.x + this.y * this.y;
    }
    /**
     * Returns the result of a linear interpolation between two specified 2-dimensional vectors.
     *
     * @param {Vec2} lhs - The 2-dimensional to interpolate from.
     * @param {Vec2} rhs - The 2-dimensional to interpolate to.
     * @param {number} alpha - The value controlling the point of interpolation. Between 0 and 1,
     * the linear interpolant will occur on a straight line between lhs and rhs. Outside of this
     * range, the linear interpolant will occur on a ray extrapolated from this line.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(0, 0);
     * const b = new pc.Vec2(10, 10);
     * const r = new pc.Vec2();
     *
     * r.lerp(a, b, 0);   // r is equal to a
     * r.lerp(a, b, 0.5); // r is 5, 5
     * r.lerp(a, b, 1);   // r is equal to b
     */ lerp(lhs, rhs, alpha) {
        this.x = lhs.x + alpha * (rhs.x - lhs.x);
        this.y = lhs.y + alpha * (rhs.y - lhs.y);
        return this;
    }
    /**
     * Multiplies a 2-dimensional vector to another in place.
     *
     * @param {Vec2} rhs - The 2-dimensional vector used as the second multiplicand of the operation.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(2, 3);
     * const b = new pc.Vec2(4, 5);
     *
     * a.mul(b);
     *
     * // Outputs 8, 15
     * console.log("The result of the multiplication is: " + a.toString());
     */ mul(rhs) {
        this.x *= rhs.x;
        this.y *= rhs.y;
        return this;
    }
    /**
     * Returns the result of multiplying the specified 2-dimensional vectors together.
     *
     * @param {Vec2} lhs - The 2-dimensional vector used as the first multiplicand of the operation.
     * @param {Vec2} rhs - The 2-dimensional vector used as the second multiplicand of the operation.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(2, 3);
     * const b = new pc.Vec2(4, 5);
     * const r = new pc.Vec2();
     *
     * r.mul2(a, b);
     *
     * // Outputs 8, 15
     * console.log("The result of the multiplication is: " + r.toString());
     */ mul2(lhs, rhs) {
        this.x = lhs.x * rhs.x;
        this.y = lhs.y * rhs.y;
        return this;
    }
    /**
     * Multiplies each element of a vector by a number.
     *
     * @param {number} scalar - The number to multiply by.
     * @returns {Vec2} Self for chaining.
     * @example
     * const vec = new pc.Vec2(3, 6);
     *
     * vec.mulScalar(3);
     *
     * // Outputs [9, 18]
     * console.log("The result of the multiplication is: " + vec.toString());
     */ mulScalar(scalar) {
        this.x *= scalar;
        this.y *= scalar;
        return this;
    }
    /**
     * Returns this 2-dimensional vector converted to a unit vector in place. If the vector has a
     * length of zero, the vector's elements will be set to zero.
     *
     * @param {Vec2} [src] - The vector to normalize. If not set, the operation is done in place.
     * @returns {Vec2} Self for chaining.
     * @example
     * const v = new pc.Vec2(25, 0);
     *
     * v.normalize();
     *
     * // Outputs 1, 0
     * console.log("The result of the vector normalization is: " + v.toString());
     */ normalize(src = this) {
        const lengthSq = src.x * src.x + src.y * src.y;
        if (lengthSq > 0) {
            const invLength = 1 / Math.sqrt(lengthSq);
            this.x = src.x * invLength;
            this.y = src.y * invLength;
        }
        return this;
    }
    /**
     * Rotate a vector by an angle in degrees.
     *
     * @param {number} degrees - The number to degrees to rotate the vector by.
     * @returns {Vec2} Self for chaining.
     * @example
     * const v = new pc.Vec2(0, 10);
     *
     * v.rotate(45); // rotates by 45 degrees
     *
     * // Outputs [7.071068.., 7.071068..]
     * console.log("Vector after rotation is: " + v.toString());
     */ rotate(degrees) {
        const angle = Math.atan2(this.x, this.y) + degrees * math.DEG_TO_RAD;
        const len = Math.sqrt(this.x * this.x + this.y * this.y);
        this.x = Math.sin(angle) * len;
        this.y = Math.cos(angle) * len;
        return this;
    }
    /**
     * Returns the angle in degrees of the specified 2-dimensional vector.
     *
     * @returns {number} The angle in degrees of the specified 2-dimensional vector.
     * @example
     * const v = new pc.Vec2(6, 0);
     * const angle = v.angle();
     * // Outputs 90..
     * console.log("The angle of the vector is: " + angle);
     */ angle() {
        return Math.atan2(this.x, this.y) * math.RAD_TO_DEG;
    }
    /**
     * Returns the shortest Euler angle between two 2-dimensional vectors.
     *
     * @param {Vec2} rhs - The 2-dimensional vector to calculate angle to.
     * @returns {number} The shortest angle in degrees between two 2-dimensional vectors.
     * @example
     * const a = new pc.Vec2(0, 10); // up
     * const b = new pc.Vec2(1, -1); // down-right
     * const angle = a.angleTo(b);
     * // Outputs 135..
     * console.log("The angle between vectors a and b: " + angle);
     */ angleTo(rhs) {
        return Math.atan2(this.x * rhs.y + this.y * rhs.x, this.x * rhs.x + this.y * rhs.y) * math.RAD_TO_DEG;
    }
    /**
     * Each element is set to the largest integer less than or equal to its value.
     *
     * @param {Vec2} [src] - The vector to floor. If not set, the operation is done in place.
     * @returns {Vec2} Self for chaining.
     */ floor(src = this) {
        this.x = Math.floor(src.x);
        this.y = Math.floor(src.y);
        return this;
    }
    /**
     * Each element is rounded up to the next largest integer.
     *
     * @param {Vec2} [src] - The vector to ceil. If not set, the operation is done in place.
     * @returns {Vec2} Self for chaining.
     */ ceil(src = this) {
        this.x = Math.ceil(src.x);
        this.y = Math.ceil(src.y);
        return this;
    }
    /**
     * Each element is rounded up or down to the nearest integer.
     *
     * @param {Vec2} [src] - The vector to round. If not set, the operation is done in place.
     * @returns {Vec2} Self for chaining.
     */ round(src = this) {
        this.x = Math.round(src.x);
        this.y = Math.round(src.y);
        return this;
    }
    /**
     * Each element is assigned a value from rhs parameter if it is smaller.
     *
     * @param {Vec2} rhs - The 2-dimensional vector used as the source of elements to compare to.
     * @returns {Vec2} Self for chaining.
     */ min(rhs) {
        if (rhs.x < this.x) this.x = rhs.x;
        if (rhs.y < this.y) this.y = rhs.y;
        return this;
    }
    /**
     * Each element is assigned a value from rhs parameter if it is larger.
     *
     * @param {Vec2} rhs - The 2-dimensional vector used as the source of elements to compare to.
     * @returns {Vec2} Self for chaining.
     */ max(rhs) {
        if (rhs.x > this.x) this.x = rhs.x;
        if (rhs.y > this.y) this.y = rhs.y;
        return this;
    }
    /**
     * Sets the specified 2-dimensional vector to the supplied numerical values.
     *
     * @param {number} x - The value to set on the first component of the vector.
     * @param {number} y - The value to set on the second component of the vector.
     * @returns {Vec2} Self for chaining.
     * @example
     * const v = new pc.Vec2();
     * v.set(5, 10);
     *
     * // Outputs 5, 10
     * console.log("The result of the vector set is: " + v.toString());
     */ set(x, y) {
        this.x = x;
        this.y = y;
        return this;
    }
    /**
     * Subtracts a 2-dimensional vector from another in place.
     *
     * @param {Vec2} rhs - The vector to subtract from the specified vector.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(10, 10);
     * const b = new pc.Vec2(20, 20);
     *
     * a.sub(b);
     *
     * // Outputs [-10, -10]
     * console.log("The result of the subtraction is: " + a.toString());
     */ sub(rhs) {
        this.x -= rhs.x;
        this.y -= rhs.y;
        return this;
    }
    /**
     * Subtracts two 2-dimensional vectors from one another and returns the result.
     *
     * @param {Vec2} lhs - The first vector operand for the subtraction.
     * @param {Vec2} rhs - The second vector operand for the subtraction.
     * @returns {Vec2} Self for chaining.
     * @example
     * const a = new pc.Vec2(10, 10);
     * const b = new pc.Vec2(20, 20);
     * const r = new pc.Vec2();
     *
     * r.sub2(a, b);
     *
     * // Outputs [-10, -10]
     * console.log("The result of the subtraction is: " + r.toString());
     */ sub2(lhs, rhs) {
        this.x = lhs.x - rhs.x;
        this.y = lhs.y - rhs.y;
        return this;
    }
    /**
     * Subtracts a number from each element of a vector.
     *
     * @param {number} scalar - The number to subtract.
     * @returns {Vec2} Self for chaining.
     * @example
     * const vec = new pc.Vec2(3, 4);
     *
     * vec.subScalar(2);
     *
     * // Outputs [1, 2]
     * console.log("The result of the subtraction is: " + vec.toString());
     */ subScalar(scalar) {
        this.x -= scalar;
        this.y -= scalar;
        return this;
    }
    /**
     * Set the values of the vector from an array.
     *
     * @param {number[]|ArrayBufferView} arr - The array to set the vector values from.
     * @param {number} [offset] - The zero-based index at which to start copying elements from the
     * array. Default is 0.
     * @returns {Vec2} Self for chaining.
     * @example
     * const v = new pc.Vec2();
     * v.fromArray([20, 10]);
     * // v is set to [20, 10]
     */ fromArray(arr, offset = 0) {
        this.x = arr[offset] ?? this.x;
        this.y = arr[offset + 1] ?? this.y;
        return this;
    }
    /**
     * Converts the vector to string form.
     *
     * @returns {string} The vector in string form.
     * @example
     * const v = new pc.Vec2(20, 10);
     * // Outputs [20, 10]
     * console.log(v.toString());
     */ toString() {
        return `[${this.x}, ${this.y}]`;
    }
    /**
     * @overload
     * @param {number[]} [arr] - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]} The vector as an array.
     */ /**
     * @overload
     * @param {ArrayBufferView} arr - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {ArrayBufferView} The vector as an array.
     */ /**
     * Converts the vector to an array.
     *
     * @param {number[]|ArrayBufferView} [arr] - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]|ArrayBufferView} The vector as an array.
     * @example
     * const v = new pc.Vec2(20, 10);
     * // Outputs [20, 10]
     * console.log(v.toArray());
     */ toArray(arr = [], offset = 0) {
        arr[offset] = this.x;
        arr[offset + 1] = this.y;
        return arr;
    }
    /**
     * Calculates the angle between two Vec2's in radians.
     *
     * @param {Vec2} lhs - The first vector operand for the calculation.
     * @param {Vec2} rhs - The second vector operand for the calculation.
     * @returns {number} The calculated angle in radians.
     * @ignore
     */ static angleRad(lhs, rhs) {
        return Math.atan2(lhs.x * rhs.y - lhs.y * rhs.x, lhs.x * rhs.x + lhs.y * rhs.y);
    }
    static{
        /**
     * A constant vector set to [0, 0].
     *
     * @type {Vec2}
     * @readonly
     */ this.ZERO = Object.freeze(new Vec2(0, 0));
    }
    static{
        /**
     * A constant vector set to [0.5, 0.5].
     *
     * @type {Vec2}
     * @readonly
     */ this.HALF = Object.freeze(new Vec2(0.5, 0.5));
    }
    static{
        /**
     * A constant vector set to [1, 1].
     *
     * @type {Vec2}
     * @readonly
     */ this.ONE = Object.freeze(new Vec2(1, 1));
    }
    static{
        /**
     * A constant vector set to [0, 1].
     *
     * @type {Vec2}
     * @readonly
     */ this.UP = Object.freeze(new Vec2(0, 1));
    }
    static{
        /**
     * A constant vector set to [0, -1].
     *
     * @type {Vec2}
     * @readonly
     */ this.DOWN = Object.freeze(new Vec2(0, -1));
    }
    static{
        /**
     * A constant vector set to [1, 0].
     *
     * @type {Vec2}
     * @readonly
     */ this.RIGHT = Object.freeze(new Vec2(1, 0));
    }
    static{
        /**
     * A constant vector set to [-1, 0].
     *
     * @type {Vec2}
     * @readonly
     */ this.LEFT = Object.freeze(new Vec2(-1, 0));
    }
}

/**
 * A 4-dimensional vector.
 *
 * @category Math
 */ class Vec4 {
    /**
     * Creates a new Vec4 instance.
     *
     * @overload
     * @param {number} [x] - The x value. Defaults to 0.
     * @param {number} [y] - The y value. Defaults to 0.
     * @param {number} [z] - The z value. Defaults to 0.
     * @param {number} [w] - The w value. Defaults to 0.
     * @example
     * const v1 = new pc.Vec4(); // defaults to 0, 0, 0, 0
     * const v2 = new pc.Vec4(1, 2, 3, 4);
     */ /**
     * Creates a new Vec4 instance.
     *
     * @overload
     * @param {number[]} arr - The array to set the vector values from.
     * @example
     * const v = new pc.Vec4([1, 2, 3, 4]);
     */ /**
     * @param {number|number[]} [x] - The x value. Defaults to 0. If x is an array of length 4, the
     * array will be used to populate all components.
     * @param {number} [y] - The y value. Defaults to 0.
     * @param {number} [z] - The z value. Defaults to 0.
     * @param {number} [w] - The w value. Defaults to 0.
     */ constructor(x = 0, y = 0, z = 0, w = 0){
        if (x.length === 4) {
            this.x = x[0];
            this.y = x[1];
            this.z = x[2];
            this.w = x[3];
        } else {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = w;
        }
    }
    /**
     * Adds a 4-dimensional vector to another in place.
     *
     * @param {Vec4} rhs - The vector to add to the specified vector.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(10, 10, 10, 10);
     * const b = new pc.Vec4(20, 20, 20, 20);
     *
     * a.add(b);
     *
     * // Outputs [30, 30, 30]
     * console.log("The result of the addition is: " + a.toString());
     */ add(rhs) {
        this.x += rhs.x;
        this.y += rhs.y;
        this.z += rhs.z;
        this.w += rhs.w;
        return this;
    }
    /**
     * Adds two 4-dimensional vectors together and returns the result.
     *
     * @param {Vec4} lhs - The first vector operand for the addition.
     * @param {Vec4} rhs - The second vector operand for the addition.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(10, 10, 10, 10);
     * const b = new pc.Vec4(20, 20, 20, 20);
     * const r = new pc.Vec4();
     *
     * r.add2(a, b);
     * // Outputs [30, 30, 30]
     *
     * console.log("The result of the addition is: " + r.toString());
     */ add2(lhs, rhs) {
        this.x = lhs.x + rhs.x;
        this.y = lhs.y + rhs.y;
        this.z = lhs.z + rhs.z;
        this.w = lhs.w + rhs.w;
        return this;
    }
    /**
     * Adds a number to each element of a vector.
     *
     * @param {number} scalar - The number to add.
     * @returns {Vec4} Self for chaining.
     * @example
     * const vec = new pc.Vec4(3, 4, 5, 6);
     *
     * vec.addScalar(2);
     *
     * // Outputs [5, 6, 7, 8]
     * console.log("The result of the addition is: " + vec.toString());
     */ addScalar(scalar) {
        this.x += scalar;
        this.y += scalar;
        this.z += scalar;
        this.w += scalar;
        return this;
    }
    /**
     * Adds a 4-dimensional vector scaled by scalar value. Does not modify the vector being added.
     *
     * @param {Vec4} rhs - The vector to add to the specified vector.
     * @param {number} scalar - The number to multiply the added vector with.
     * @returns {Vec4} Self for chaining.
     * @example
     * const vec = new pc.Vec4(1, 2, 3, 4);
     *
     * vec.addScaled(pc.Vec4.ONE, 2);
     *
     * // Outputs [3, 4, 5, 6]
     * console.log("The result of the addition is: " + vec.toString());
     */ addScaled(rhs, scalar) {
        this.x += rhs.x * scalar;
        this.y += rhs.y * scalar;
        this.z += rhs.z * scalar;
        this.w += rhs.w * scalar;
        return this;
    }
    /**
     * Returns an identical copy of the specified 4-dimensional vector.
     *
     * @returns {this} A 4-dimensional vector containing the result of the cloning.
     * @example
     * const v = new pc.Vec4(10, 20, 30, 40);
     * const vclone = v.clone();
     * console.log("The result of the cloning is: " + vclone.toString());
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr(this.x, this.y, this.z, this.w);
    }
    /**
     * Copies the contents of a source 4-dimensional vector to a destination 4-dimensional vector.
     *
     * @param {Vec4} rhs - A vector to copy to the specified vector.
     * @returns {Vec4} Self for chaining.
     * @example
     * const src = new pc.Vec4(10, 20, 30, 40);
     * const dst = new pc.Vec4();
     *
     * dst.copy(src);
     *
     * console.log("The two vectors are " + (dst.equals(src) ? "equal" : "different"));
     */ copy(rhs) {
        this.x = rhs.x;
        this.y = rhs.y;
        this.z = rhs.z;
        this.w = rhs.w;
        return this;
    }
    /**
     * Divides a 4-dimensional vector by another in place.
     *
     * @param {Vec4} rhs - The vector to divide the specified vector by.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(4, 9, 16, 25);
     * const b = new pc.Vec4(2, 3, 4, 5);
     *
     * a.div(b);
     *
     * // Outputs [2, 3, 4, 5]
     * console.log("The result of the division is: " + a.toString());
     */ div(rhs) {
        this.x /= rhs.x;
        this.y /= rhs.y;
        this.z /= rhs.z;
        this.w /= rhs.w;
        return this;
    }
    /**
     * Divides one 4-dimensional vector by another and writes the result to the specified vector.
     *
     * @param {Vec4} lhs - The dividend vector (the vector being divided).
     * @param {Vec4} rhs - The divisor vector (the vector dividing the dividend).
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(4, 9, 16, 25);
     * const b = new pc.Vec4(2, 3, 4, 5);
     * const r = new pc.Vec4();
     *
     * r.div2(a, b);
     *
     * // Outputs [2, 3, 4, 5]
     * console.log("The result of the division is: " + r.toString());
     */ div2(lhs, rhs) {
        this.x = lhs.x / rhs.x;
        this.y = lhs.y / rhs.y;
        this.z = lhs.z / rhs.z;
        this.w = lhs.w / rhs.w;
        return this;
    }
    /**
     * Divides each element of a vector by a number.
     *
     * @param {number} scalar - The number to divide by.
     * @returns {Vec4} Self for chaining.
     * @example
     * const vec = new pc.Vec4(3, 6, 9, 12);
     *
     * vec.divScalar(3);
     *
     * // Outputs [1, 2, 3, 4]
     * console.log("The result of the division is: " + vec.toString());
     */ divScalar(scalar) {
        this.x /= scalar;
        this.y /= scalar;
        this.z /= scalar;
        this.w /= scalar;
        return this;
    }
    /**
     * Returns the result of a dot product operation performed on the two specified 4-dimensional
     * vectors.
     *
     * @param {Vec4} rhs - The second 4-dimensional vector operand of the dot product.
     * @returns {number} The result of the dot product operation.
     * @example
     * const v1 = new pc.Vec4(5, 10, 20, 40);
     * const v2 = new pc.Vec4(10, 20, 40, 80);
     * const v1dotv2 = v1.dot(v2);
     * console.log("The result of the dot product is: " + v1dotv2);
     */ dot(rhs) {
        return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z + this.w * rhs.w;
    }
    /**
     * Reports whether two vectors are equal.
     *
     * @param {Vec4} rhs - The vector to compare to the specified vector.
     * @returns {boolean} True if the vectors are equal and false otherwise.
     * @example
     * const a = new pc.Vec4(1, 2, 3, 4);
     * const b = new pc.Vec4(5, 6, 7, 8);
     * console.log("The two vectors are " + (a.equals(b) ? "equal" : "different"));
     */ equals(rhs) {
        return this.x === rhs.x && this.y === rhs.y && this.z === rhs.z && this.w === rhs.w;
    }
    /**
     * Reports whether two vectors are equal using an absolute error tolerance.
     *
     * @param {Vec4} rhs - The vector to be compared against.
     * @param {number} [epsilon] - The maximum difference between each component of the two
     * vectors. Defaults to 1e-6.
     * @returns {boolean} True if the vectors are equal and false otherwise.
     * @example
     * const a = new pc.Vec4();
     * const b = new pc.Vec4();
     * console.log("The two vectors are approximately " + (a.equalsApprox(b, 1e-9) ? "equal" : "different"));
     */ equalsApprox(rhs, epsilon = 1e-6) {
        return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon && Math.abs(this.w - rhs.w) < epsilon;
    }
    /**
     * Returns the magnitude of the specified 4-dimensional vector.
     *
     * @returns {number} The magnitude of the specified 4-dimensional vector.
     * @example
     * const vec = new pc.Vec4(3, 4, 0, 0);
     * const len = vec.length();
     * // Outputs 5
     * console.log("The length of the vector is: " + len);
     */ length() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
    }
    /**
     * Returns the magnitude squared of the specified 4-dimensional vector.
     *
     * @returns {number} The magnitude of the specified 4-dimensional vector.
     * @example
     * const vec = new pc.Vec4(3, 4, 0);
     * const len = vec.lengthSq();
     * // Outputs 25
     * console.log("The length squared of the vector is: " + len);
     */ lengthSq() {
        return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
    }
    /**
     * Returns the result of a linear interpolation between two specified 4-dimensional vectors.
     *
     * @param {Vec4} lhs - The 4-dimensional to interpolate from.
     * @param {Vec4} rhs - The 4-dimensional to interpolate to.
     * @param {number} alpha - The value controlling the point of interpolation. Between 0 and 1,
     * the linear interpolant will occur on a straight line between lhs and rhs. Outside of this
     * range, the linear interpolant will occur on a ray extrapolated from this line.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(0, 0, 0, 0);
     * const b = new pc.Vec4(10, 10, 10, 10);
     * const r = new pc.Vec4();
     *
     * r.lerp(a, b, 0);   // r is equal to a
     * r.lerp(a, b, 0.5); // r is 5, 5, 5, 5
     * r.lerp(a, b, 1);   // r is equal to b
     */ lerp(lhs, rhs, alpha) {
        this.x = lhs.x + alpha * (rhs.x - lhs.x);
        this.y = lhs.y + alpha * (rhs.y - lhs.y);
        this.z = lhs.z + alpha * (rhs.z - lhs.z);
        this.w = lhs.w + alpha * (rhs.w - lhs.w);
        return this;
    }
    /**
     * Multiplies a 4-dimensional vector to another in place.
     *
     * @param {Vec4} rhs - The 4-dimensional vector used as the second multiplicand of the operation.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(2, 3, 4, 5);
     * const b = new pc.Vec4(4, 5, 6, 7);
     *
     * a.mul(b);
     *
     * // Outputs 8, 15, 24, 35
     * console.log("The result of the multiplication is: " + a.toString());
     */ mul(rhs) {
        this.x *= rhs.x;
        this.y *= rhs.y;
        this.z *= rhs.z;
        this.w *= rhs.w;
        return this;
    }
    /**
     * Returns the result of multiplying the specified 4-dimensional vectors together.
     *
     * @param {Vec4} lhs - The 4-dimensional vector used as the first multiplicand of the operation.
     * @param {Vec4} rhs - The 4-dimensional vector used as the second multiplicand of the operation.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(2, 3, 4, 5);
     * const b = new pc.Vec4(4, 5, 6, 7);
     * const r = new pc.Vec4();
     *
     * r.mul2(a, b);
     *
     * // Outputs 8, 15, 24, 35
     * console.log("The result of the multiplication is: " + r.toString());
     */ mul2(lhs, rhs) {
        this.x = lhs.x * rhs.x;
        this.y = lhs.y * rhs.y;
        this.z = lhs.z * rhs.z;
        this.w = lhs.w * rhs.w;
        return this;
    }
    /**
     * Multiplies each element of a vector by a number.
     *
     * @param {number} scalar - The number to multiply by.
     * @returns {Vec4} Self for chaining.
     * @example
     * const vec = new pc.Vec4(3, 6, 9, 12);
     *
     * vec.mulScalar(3);
     *
     * // Outputs [9, 18, 27, 36]
     * console.log("The result of the multiplication is: " + vec.toString());
     */ mulScalar(scalar) {
        this.x *= scalar;
        this.y *= scalar;
        this.z *= scalar;
        this.w *= scalar;
        return this;
    }
    /**
     * Returns this 4-dimensional vector converted to a unit vector in place. If the vector has a
     * length of zero, the vector's elements will be set to zero.
     *
     * @param {Vec4} [src] - The vector to normalize. If not set, the operation is done in place.
     * @returns {Vec4} Self for chaining.
     * @example
     * const v = new pc.Vec4(25, 0, 0, 0);
     *
     * v.normalize();
     *
     * // Outputs 1, 0, 0, 0
     * console.log("The result of the vector normalization is: " + v.toString());
     */ normalize(src = this) {
        const lengthSq = src.x * src.x + src.y * src.y + src.z * src.z + src.w * src.w;
        if (lengthSq > 0) {
            const invLength = 1 / Math.sqrt(lengthSq);
            this.x = src.x * invLength;
            this.y = src.y * invLength;
            this.z = src.z * invLength;
            this.w = src.w * invLength;
        }
        return this;
    }
    /**
     * Each element is set to the largest integer less than or equal to its value.
     *
     * @param {Vec4} [src] - The vector to floor. If not set, the operation is done in place.
     * @returns {Vec4} Self for chaining.
     */ floor(src = this) {
        this.x = Math.floor(src.x);
        this.y = Math.floor(src.y);
        this.z = Math.floor(src.z);
        this.w = Math.floor(src.w);
        return this;
    }
    /**
     * Each element is rounded up to the next largest integer.
     *
     * @param {Vec4} [src] - The vector to ceil. If not set, the operation is done in place.
     * @returns {Vec4} Self for chaining.
     */ ceil(src = this) {
        this.x = Math.ceil(src.x);
        this.y = Math.ceil(src.y);
        this.z = Math.ceil(src.z);
        this.w = Math.ceil(src.w);
        return this;
    }
    /**
     * Each element is rounded up or down to the nearest integer.
     *
     * @param {Vec4} [src] - The vector to round. If not set, the operation is done in place.
     * @returns {Vec4} Self for chaining.
     */ round(src = this) {
        this.x = Math.round(src.x);
        this.y = Math.round(src.y);
        this.z = Math.round(src.z);
        this.w = Math.round(src.w);
        return this;
    }
    /**
     * Each element is assigned a value from rhs parameter if it is smaller.
     *
     * @param {Vec4} rhs - The 4-dimensional vector used as the source of elements to compare to.
     * @returns {Vec4} Self for chaining.
     */ min(rhs) {
        if (rhs.x < this.x) this.x = rhs.x;
        if (rhs.y < this.y) this.y = rhs.y;
        if (rhs.z < this.z) this.z = rhs.z;
        if (rhs.w < this.w) this.w = rhs.w;
        return this;
    }
    /**
     * Each element is assigned a value from rhs parameter if it is larger.
     *
     * @param {Vec4} rhs - The 4-dimensional vector used as the source of elements to compare to.
     * @returns {Vec4} Self for chaining.
     */ max(rhs) {
        if (rhs.x > this.x) this.x = rhs.x;
        if (rhs.y > this.y) this.y = rhs.y;
        if (rhs.z > this.z) this.z = rhs.z;
        if (rhs.w > this.w) this.w = rhs.w;
        return this;
    }
    /**
     * Sets the specified 4-dimensional vector to the supplied numerical values.
     *
     * @param {number} x - The value to set on the first component of the vector.
     * @param {number} y - The value to set on the second component of the vector.
     * @param {number} z - The value to set on the third component of the vector.
     * @param {number} w - The value to set on the fourth component of the vector.
     * @returns {Vec4} Self for chaining.
     * @example
     * const v = new pc.Vec4();
     * v.set(5, 10, 20, 40);
     *
     * // Outputs 5, 10, 20, 40
     * console.log("The result of the vector set is: " + v.toString());
     */ set(x, y, z, w) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        return this;
    }
    /**
     * Subtracts a 4-dimensional vector from another in place.
     *
     * @param {Vec4} rhs - The vector to add to the specified vector.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(10, 10, 10, 10);
     * const b = new pc.Vec4(20, 20, 20, 20);
     *
     * a.sub(b);
     *
     * // Outputs [-10, -10, -10, -10]
     * console.log("The result of the subtraction is: " + a.toString());
     */ sub(rhs) {
        this.x -= rhs.x;
        this.y -= rhs.y;
        this.z -= rhs.z;
        this.w -= rhs.w;
        return this;
    }
    /**
     * Subtracts two 4-dimensional vectors from one another and returns the result.
     *
     * @param {Vec4} lhs - The first vector operand for the subtraction.
     * @param {Vec4} rhs - The second vector operand for the subtraction.
     * @returns {Vec4} Self for chaining.
     * @example
     * const a = new pc.Vec4(10, 10, 10, 10);
     * const b = new pc.Vec4(20, 20, 20, 20);
     * const r = new pc.Vec4();
     *
     * r.sub2(a, b);
     *
     * // Outputs [-10, -10, -10, -10]
     * console.log("The result of the subtraction is: " + r.toString());
     */ sub2(lhs, rhs) {
        this.x = lhs.x - rhs.x;
        this.y = lhs.y - rhs.y;
        this.z = lhs.z - rhs.z;
        this.w = lhs.w - rhs.w;
        return this;
    }
    /**
     * Subtracts a number from each element of a vector.
     *
     * @param {number} scalar - The number to subtract.
     * @returns {Vec4} Self for chaining.
     * @example
     * const vec = new pc.Vec4(3, 4, 5, 6);
     *
     * vec.subScalar(2);
     *
     * // Outputs [1, 2, 3, 4]
     * console.log("The result of the subtraction is: " + vec.toString());
     */ subScalar(scalar) {
        this.x -= scalar;
        this.y -= scalar;
        this.z -= scalar;
        this.w -= scalar;
        return this;
    }
    /**
     * Set the values of the vector from an array.
     *
     * @param {number[]|ArrayBufferView} arr - The array to set the vector values from.
     * @param {number} [offset] - The zero-based index at which to start copying elements from the
     * array. Default is 0.
     * @returns {Vec4} Self for chaining.
     * @example
     * const v = new pc.Vec4();
     * v.fromArray([20, 10, 5, 0]);
     * // v is set to [20, 10, 5, 0]
     */ fromArray(arr, offset = 0) {
        this.x = arr[offset] ?? this.x;
        this.y = arr[offset + 1] ?? this.y;
        this.z = arr[offset + 2] ?? this.z;
        this.w = arr[offset + 3] ?? this.w;
        return this;
    }
    /**
     * Converts the vector to string form.
     *
     * @returns {string} The vector in string form.
     * @example
     * const v = new pc.Vec4(20, 10, 5, 0);
     * // Outputs [20, 10, 5, 0]
     * console.log(v.toString());
     */ toString() {
        return `[${this.x}, ${this.y}, ${this.z}, ${this.w}]`;
    }
    /**
     * @overload
     * @param {number[]} [arr] - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]} The vector as an array.
     */ /**
     * @overload
     * @param {ArrayBufferView} arr - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {ArrayBufferView} The vector as an array.
     */ /**
     * Converts the vector to an array.
     *
     * @param {number[]|ArrayBufferView} [arr] - The array to populate with the vector's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]|ArrayBufferView} The vector as an array.
     * @example
     * const v = new pc.Vec4(20, 10, 5, 1);
     * // Outputs [20, 10, 5, 1]
     * console.log(v.toArray());
     */ toArray(arr = [], offset = 0) {
        arr[offset] = this.x;
        arr[offset + 1] = this.y;
        arr[offset + 2] = this.z;
        arr[offset + 3] = this.w;
        return arr;
    }
    static{
        /**
     * A constant vector set to [0, 0, 0, 0].
     *
     * @type {Vec4}
     * @readonly
     */ this.ZERO = Object.freeze(new Vec4(0, 0, 0, 0));
    }
    static{
        /**
     * A constant vector set to [0.5, 0.5, 0.5, 0.5].
     *
     * @type {Vec4}
     * @readonly
     */ this.HALF = Object.freeze(new Vec4(0.5, 0.5, 0.5, 0.5));
    }
    static{
        /**
     * A constant vector set to [1, 1, 1, 1].
     *
     * @type {Vec4}
     * @readonly
     */ this.ONE = Object.freeze(new Vec4(1, 1, 1, 1));
    }
}

/**
 * @import { Quat } from './quat.js'
 */ const _halfSize$1 = new Vec2();
const x = new Vec3();
const y = new Vec3();
const z = new Vec3();
const scale = new Vec3();
/**
 * A 4x4 matrix.
 *
 * @category Math
 */ class Mat4 {
    /**
     * Create a new Mat4 instance. It is initialized to the identity matrix.
     */ constructor(){
        /**
     * Matrix elements in the form of a flat array.
     *
     * @type {Float32Array}
     */ this.data = new Float32Array(16);
        // Create an identity matrix. Note that a new Float32Array has all elements set
        // to zero by default, so we only need to set the relevant elements to one.
        this.data[0] = this.data[5] = this.data[10] = this.data[15] = 1;
    }
    // Static function which evaluates perspective projection matrix half size at the near plane
    static _getPerspectiveHalfSize(halfSize, fov, aspect, znear, fovIsHorizontal) {
        if (fovIsHorizontal) {
            halfSize.x = znear * Math.tan(fov * Math.PI / 360);
            halfSize.y = halfSize.x / aspect;
        } else {
            halfSize.y = znear * Math.tan(fov * Math.PI / 360);
            halfSize.x = halfSize.y * aspect;
        }
    }
    /**
     * Adds the specified 4x4 matrices together and stores the result in the current instance.
     *
     * @param {Mat4} lhs - The 4x4 matrix used as the first operand of the addition.
     * @param {Mat4} rhs - The 4x4 matrix used as the second operand of the addition.
     * @returns {Mat4} Self for chaining.
     * @example
     * const m = new pc.Mat4();
     *
     * m.add2(pc.Mat4.IDENTITY, pc.Mat4.ONE);
     *
     * console.log("The result of the addition is: " + m.toString());
     */ add2(lhs, rhs) {
        const a = lhs.data, b = rhs.data, r = this.data;
        r[0] = a[0] + b[0];
        r[1] = a[1] + b[1];
        r[2] = a[2] + b[2];
        r[3] = a[3] + b[3];
        r[4] = a[4] + b[4];
        r[5] = a[5] + b[5];
        r[6] = a[6] + b[6];
        r[7] = a[7] + b[7];
        r[8] = a[8] + b[8];
        r[9] = a[9] + b[9];
        r[10] = a[10] + b[10];
        r[11] = a[11] + b[11];
        r[12] = a[12] + b[12];
        r[13] = a[13] + b[13];
        r[14] = a[14] + b[14];
        r[15] = a[15] + b[15];
        return this;
    }
    /**
     * Adds the specified 4x4 matrix to the current instance.
     *
     * @param {Mat4} rhs - The 4x4 matrix used as the second operand of the addition.
     * @returns {Mat4} Self for chaining.
     * @example
     * const m = new pc.Mat4();
     *
     * m.add(pc.Mat4.ONE);
     *
     * console.log("The result of the addition is: " + m.toString());
     */ add(rhs) {
        return this.add2(this, rhs);
    }
    /**
     * Creates a duplicate of the specified matrix.
     *
     * @returns {this} A duplicate matrix.
     * @example
     * const src = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     * const dst = src.clone();
     * console.log("The two matrices are " + (src.equals(dst) ? "equal" : "different"));
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr().copy(this);
    }
    /**
     * Copies the contents of a source 4x4 matrix to a destination 4x4 matrix.
     *
     * @param {Mat4} rhs - A 4x4 matrix to be copied.
     * @returns {Mat4} Self for chaining.
     * @example
     * const src = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     * const dst = new pc.Mat4();
     * dst.copy(src);
     * console.log("The two matrices are " + (src.equals(dst) ? "equal" : "different"));
     */ copy(rhs) {
        const src = rhs.data, dst = this.data;
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
        dst[4] = src[4];
        dst[5] = src[5];
        dst[6] = src[6];
        dst[7] = src[7];
        dst[8] = src[8];
        dst[9] = src[9];
        dst[10] = src[10];
        dst[11] = src[11];
        dst[12] = src[12];
        dst[13] = src[13];
        dst[14] = src[14];
        dst[15] = src[15];
        return this;
    }
    /**
     * Reports whether two matrices are equal.
     *
     * @param {Mat4} rhs - The other matrix.
     * @returns {boolean} True if the matrices are equal and false otherwise.
     * @example
     * const a = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     * const b = new pc.Mat4();
     * console.log("The two matrices are " + (a.equals(b) ? "equal" : "different"));
     */ equals(rhs) {
        const l = this.data, r = rhs.data;
        return l[0] === r[0] && l[1] === r[1] && l[2] === r[2] && l[3] === r[3] && l[4] === r[4] && l[5] === r[5] && l[6] === r[6] && l[7] === r[7] && l[8] === r[8] && l[9] === r[9] && l[10] === r[10] && l[11] === r[11] && l[12] === r[12] && l[13] === r[13] && l[14] === r[14] && l[15] === r[15];
    }
    /**
     * Reports whether the specified matrix is the identity matrix.
     *
     * @returns {boolean} True if the matrix is identity and false otherwise.
     * @example
     * const m = new pc.Mat4();
     * console.log("The matrix is " + (m.isIdentity() ? "identity" : "not identity"));
     */ isIdentity() {
        const m = this.data;
        return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 0 && m[4] === 0 && m[5] === 1 && m[6] === 0 && m[7] === 0 && m[8] === 0 && m[9] === 0 && m[10] === 1 && m[11] === 0 && m[12] === 0 && m[13] === 0 && m[14] === 0 && m[15] === 1;
    }
    /**
     * Multiplies the specified 4x4 matrices together and stores the result in the current
     * instance.
     *
     * @param {Mat4} lhs - The 4x4 matrix used as the first multiplicand of the operation.
     * @param {Mat4} rhs - The 4x4 matrix used as the second multiplicand of the operation.
     * @returns {Mat4} Self for chaining.
     * @example
     * const a = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     * const b = new pc.Mat4().setFromAxisAngle(pc.Vec3.UP, 180);
     * const r = new pc.Mat4();
     *
     * // r = a * b
     * r.mul2(a, b);
     *
     * console.log("The result of the multiplication is: " + r.toString());
     */ mul2(lhs, rhs) {
        const a = lhs.data;
        const b = rhs.data;
        const r = this.data;
        const a00 = a[0];
        const a01 = a[1];
        const a02 = a[2];
        const a03 = a[3];
        const a10 = a[4];
        const a11 = a[5];
        const a12 = a[6];
        const a13 = a[7];
        const a20 = a[8];
        const a21 = a[9];
        const a22 = a[10];
        const a23 = a[11];
        const a30 = a[12];
        const a31 = a[13];
        const a32 = a[14];
        const a33 = a[15];
        let b0, b1, b2, b3;
        b0 = b[0];
        b1 = b[1];
        b2 = b[2];
        b3 = b[3];
        r[0] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
        r[1] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
        r[2] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
        r[3] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
        b0 = b[4];
        b1 = b[5];
        b2 = b[6];
        b3 = b[7];
        r[4] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
        r[5] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
        r[6] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
        r[7] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
        b0 = b[8];
        b1 = b[9];
        b2 = b[10];
        b3 = b[11];
        r[8] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
        r[9] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
        r[10] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
        r[11] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
        b0 = b[12];
        b1 = b[13];
        b2 = b[14];
        b3 = b[15];
        r[12] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;
        r[13] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;
        r[14] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;
        r[15] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;
        return this;
    }
    /**
     * Multiplies the specified 4x4 matrices together and stores the result in the current
     * instance. This function assumes the matrices are affine transformation matrices, where the
     * upper left 3x3 elements are a rotation matrix, and the bottom left 3 elements are
     * translation. The rightmost column is assumed to be [0, 0, 0, 1]. The parameters are not
     * verified to be in the expected format. This function is faster than general
     * {@link Mat4#mul2}.
     *
     * @param {Mat4} lhs - The affine transformation 4x4 matrix used as the first multiplicand of
     * the operation.
     * @param {Mat4} rhs - The affine transformation 4x4 matrix used as the second multiplicand of
     * the operation.
     * @returns {Mat4} Self for chaining.
     */ mulAffine2(lhs, rhs) {
        const a = lhs.data;
        const b = rhs.data;
        const r = this.data;
        const a00 = a[0];
        const a01 = a[1];
        const a02 = a[2];
        const a10 = a[4];
        const a11 = a[5];
        const a12 = a[6];
        const a20 = a[8];
        const a21 = a[9];
        const a22 = a[10];
        const a30 = a[12];
        const a31 = a[13];
        const a32 = a[14];
        let b0, b1, b2;
        b0 = b[0];
        b1 = b[1];
        b2 = b[2];
        r[0] = a00 * b0 + a10 * b1 + a20 * b2;
        r[1] = a01 * b0 + a11 * b1 + a21 * b2;
        r[2] = a02 * b0 + a12 * b1 + a22 * b2;
        r[3] = 0;
        b0 = b[4];
        b1 = b[5];
        b2 = b[6];
        r[4] = a00 * b0 + a10 * b1 + a20 * b2;
        r[5] = a01 * b0 + a11 * b1 + a21 * b2;
        r[6] = a02 * b0 + a12 * b1 + a22 * b2;
        r[7] = 0;
        b0 = b[8];
        b1 = b[9];
        b2 = b[10];
        r[8] = a00 * b0 + a10 * b1 + a20 * b2;
        r[9] = a01 * b0 + a11 * b1 + a21 * b2;
        r[10] = a02 * b0 + a12 * b1 + a22 * b2;
        r[11] = 0;
        b0 = b[12];
        b1 = b[13];
        b2 = b[14];
        r[12] = a00 * b0 + a10 * b1 + a20 * b2 + a30;
        r[13] = a01 * b0 + a11 * b1 + a21 * b2 + a31;
        r[14] = a02 * b0 + a12 * b1 + a22 * b2 + a32;
        r[15] = 1;
        return this;
    }
    /**
     * Multiplies the current instance by the specified 4x4 matrix.
     *
     * @param {Mat4} rhs - The 4x4 matrix used as the second multiplicand of the operation.
     * @returns {Mat4} Self for chaining.
     * @example
     * const a = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     * const b = new pc.Mat4().setFromAxisAngle(pc.Vec3.UP, 180);
     *
     * // a = a * b
     * a.mul(b);
     *
     * console.log("The result of the multiplication is: " + a.toString());
     */ mul(rhs) {
        return this.mul2(this, rhs);
    }
    /**
     * Transforms a 3-dimensional point by a 4x4 matrix.
     *
     * @param {Vec3} vec - The 3-dimensional point to be transformed.
     * @param {Vec3} [res] - An optional 3-dimensional point to receive the result of the
     * transformation.
     * @returns {Vec3} The input point v transformed by the current instance.
     * @example
     * // Create a 3-dimensional point
     * const v = new pc.Vec3(1, 2, 3);
     *
     * // Create a 4x4 rotation matrix
     * const m = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     *
     * const tv = m.transformPoint(v);
     */ transformPoint(vec, res = new Vec3()) {
        const m = this.data;
        const { x, y, z } = vec;
        res.x = x * m[0] + y * m[4] + z * m[8] + m[12];
        res.y = x * m[1] + y * m[5] + z * m[9] + m[13];
        res.z = x * m[2] + y * m[6] + z * m[10] + m[14];
        return res;
    }
    /**
     * Transforms a 3-dimensional vector by a 4x4 matrix.
     *
     * @param {Vec3} vec - The 3-dimensional vector to be transformed.
     * @param {Vec3} [res] - An optional 3-dimensional vector to receive the result of the
     * transformation.
     * @returns {Vec3} The input vector v transformed by the current instance.
     * @example
     * // Create a 3-dimensional vector
     * const v = new pc.Vec3(1, 2, 3);
     *
     * // Create a 4x4 rotation matrix
     * const m = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     *
     * const tv = m.transformVector(v);
     */ transformVector(vec, res = new Vec3()) {
        const m = this.data;
        const { x, y, z } = vec;
        res.x = x * m[0] + y * m[4] + z * m[8];
        res.y = x * m[1] + y * m[5] + z * m[9];
        res.z = x * m[2] + y * m[6] + z * m[10];
        return res;
    }
    /**
     * Transforms a 4-dimensional vector by a 4x4 matrix.
     *
     * @param {Vec4} vec - The 4-dimensional vector to be transformed.
     * @param {Vec4} [res] - An optional 4-dimensional vector to receive the result of the
     * transformation.
     * @returns {Vec4} The input vector v transformed by the current instance.
     * @example
     * // Create an input 4-dimensional vector
     * const v = new pc.Vec4(1, 2, 3, 4);
     *
     * // Create an output 4-dimensional vector
     * const result = new pc.Vec4();
     *
     * // Create a 4x4 rotation matrix
     * const m = new pc.Mat4().setFromEulerAngles(10, 20, 30);
     *
     * m.transformVec4(v, result);
     */ transformVec4(vec, res = new Vec4()) {
        const m = this.data;
        const { x, y, z, w } = vec;
        res.x = x * m[0] + y * m[4] + z * m[8] + w * m[12];
        res.y = x * m[1] + y * m[5] + z * m[9] + w * m[13];
        res.z = x * m[2] + y * m[6] + z * m[10] + w * m[14];
        res.w = x * m[3] + y * m[7] + z * m[11] + w * m[15];
        return res;
    }
    /**
     * Sets the specified matrix to a viewing matrix derived from an eye point, a target point and
     * an up vector. The matrix maps the target point to the negative z-axis and the eye point to
     * the origin, so that when you use a typical projection matrix, the center of the scene maps
     * to the center of the viewport. Similarly, the direction described by the up vector projected
     * onto the viewing plane is mapped to the positive y-axis so that it points upward in the
     * viewport. The up vector must not be parallel to the line of sight from the eye to the
     * reference point.
     *
     * @param {Vec3} position - 3-d vector holding view position.
     * @param {Vec3} target - 3-d vector holding reference point.
     * @param {Vec3} up - 3-d vector holding the up direction.
     * @returns {Mat4} Self for chaining.
     * @example
     * const position = new pc.Vec3(10, 10, 10);
     * const target = new pc.Vec3(0, 0, 0);
     * const up = new pc.Vec3(0, 1, 0);
     * const m = new pc.Mat4().setLookAt(position, target, up);
     */ setLookAt(position, target, up) {
        z.sub2(position, target).normalize();
        y.copy(up).normalize();
        x.cross(y, z).normalize();
        y.cross(z, x);
        const r = this.data;
        r[0] = x.x;
        r[1] = x.y;
        r[2] = x.z;
        r[3] = 0;
        r[4] = y.x;
        r[5] = y.y;
        r[6] = y.z;
        r[7] = 0;
        r[8] = z.x;
        r[9] = z.y;
        r[10] = z.z;
        r[11] = 0;
        r[12] = position.x;
        r[13] = position.y;
        r[14] = position.z;
        r[15] = 1;
        return this;
    }
    /**
     * Sets the specified matrix to a perspective projection matrix. The function's parameters
     * define the shape of a frustum.
     *
     * @param {number} left - The x-coordinate for the left edge of the camera's projection plane
     * in eye space.
     * @param {number} right - The x-coordinate for the right edge of the camera's projection plane
     * in eye space.
     * @param {number} bottom - The y-coordinate for the bottom edge of the camera's projection
     * plane in eye space.
     * @param {number} top - The y-coordinate for the top edge of the camera's projection plane in
     * eye space.
     * @param {number} znear - The near clip plane in eye coordinates.
     * @param {number} zfar - The far clip plane in eye coordinates.
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 perspective projection matrix
     * const f = pc.Mat4().setFrustum(-2, 2, -1, 1, 1, 1000);
     * @ignore
     */ setFrustum(left, right, bottom, top, znear, zfar) {
        const temp1 = 2 * znear;
        const temp2 = right - left;
        const temp3 = top - bottom;
        const temp4 = zfar - znear;
        const r = this.data;
        r[0] = temp1 / temp2;
        r[1] = 0;
        r[2] = 0;
        r[3] = 0;
        r[4] = 0;
        r[5] = temp1 / temp3;
        r[6] = 0;
        r[7] = 0;
        r[8] = (right + left) / temp2;
        r[9] = (top + bottom) / temp3;
        r[10] = (-zfar - znear) / temp4;
        r[11] = -1;
        r[12] = 0;
        r[13] = 0;
        r[14] = -temp1 * zfar / temp4;
        r[15] = 0;
        return this;
    }
    /**
     * Sets the specified matrix to a perspective projection matrix. The function's parameters
     * define the shape of a frustum.
     *
     * @param {number} fov - The frustum's field of view in degrees. The fovIsHorizontal parameter
     * controls whether this is a vertical or horizontal field of view. By default, it's a vertical
     * field of view.
     * @param {number} aspect - The aspect ratio of the frustum's projection plane
     * (width / height).
     * @param {number} znear - The near clip plane in eye coordinates.
     * @param {number} zfar - The far clip plane in eye coordinates.
     * @param {boolean} [fovIsHorizontal] - Set to true to treat the fov as horizontal (x-axis) and
     * false for vertical (y-axis). Defaults to false.
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 perspective projection matrix
     * const persp = pc.Mat4().setPerspective(45, 16 / 9, 1, 1000);
     */ setPerspective(fov, aspect, znear, zfar, fovIsHorizontal) {
        Mat4._getPerspectiveHalfSize(_halfSize$1, fov, aspect, znear, fovIsHorizontal);
        return this.setFrustum(-_halfSize$1.x, _halfSize$1.x, -_halfSize$1.y, _halfSize$1.y, znear, zfar);
    }
    /**
     * Sets the specified matrix to an orthographic projection matrix. The function's parameters
     * define the shape of a cuboid-shaped frustum.
     *
     * @param {number} left - The x-coordinate for the left edge of the camera's projection plane
     * in eye space.
     * @param {number} right - The x-coordinate for the right edge of the camera's projection plane
     * in eye space.
     * @param {number} bottom - The y-coordinate for the bottom edge of the camera's projection
     * plane in eye space.
     * @param {number} top - The y-coordinate for the top edge of the camera's projection plane in
     * eye space.
     * @param {number} near - The near clip plane in eye coordinates.
     * @param {number} far - The far clip plane in eye coordinates.
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 orthographic projection matrix
     * const ortho = pc.Mat4().ortho(-2, 2, -2, 2, 1, 1000);
     */ setOrtho(left, right, bottom, top, near, far) {
        const r = this.data;
        r[0] = 2 / (right - left);
        r[1] = 0;
        r[2] = 0;
        r[3] = 0;
        r[4] = 0;
        r[5] = 2 / (top - bottom);
        r[6] = 0;
        r[7] = 0;
        r[8] = 0;
        r[9] = 0;
        r[10] = -2 / (far - near);
        r[11] = 0;
        r[12] = -(right + left) / (right - left);
        r[13] = -(top + bottom) / (top - bottom);
        r[14] = -(far + near) / (far - near);
        r[15] = 1;
        return this;
    }
    /**
     * Sets the specified matrix to a rotation matrix equivalent to a rotation around an axis. The
     * axis must be normalized (unit length) and the angle must be specified in degrees.
     *
     * @param {Vec3} axis - The normalized axis vector around which to rotate.
     * @param {number} angle - The angle of rotation in degrees.
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 rotation matrix
     * const rm = new pc.Mat4().setFromAxisAngle(pc.Vec3.UP, 90);
     */ setFromAxisAngle(axis, angle) {
        angle *= math.DEG_TO_RAD;
        const { x, y, z } = axis;
        const c = Math.cos(angle);
        const s = Math.sin(angle);
        const t = 1 - c;
        const tx = t * x;
        const ty = t * y;
        const m = this.data;
        m[0] = tx * x + c;
        m[1] = tx * y + s * z;
        m[2] = tx * z - s * y;
        m[3] = 0;
        m[4] = tx * y - s * z;
        m[5] = ty * y + c;
        m[6] = ty * z + s * x;
        m[7] = 0;
        m[8] = tx * z + s * y;
        m[9] = ty * z - x * s;
        m[10] = t * z * z + c;
        m[11] = 0;
        m[12] = 0;
        m[13] = 0;
        m[14] = 0;
        m[15] = 1;
        return this;
    }
    /**
     * Sets the specified matrix to a translation matrix.
     *
     * @param {number} x - The x-component of the translation.
     * @param {number} y - The y-component of the translation.
     * @param {number} z - The z-component of the translation.
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 translation matrix
     * const tm = new pc.Mat4().setTranslate(10, 10, 10);
     * @ignore
     */ setTranslate(x, y, z) {
        const m = this.data;
        m[0] = 1;
        m[1] = 0;
        m[2] = 0;
        m[3] = 0;
        m[4] = 0;
        m[5] = 1;
        m[6] = 0;
        m[7] = 0;
        m[8] = 0;
        m[9] = 0;
        m[10] = 1;
        m[11] = 0;
        m[12] = x;
        m[13] = y;
        m[14] = z;
        m[15] = 1;
        return this;
    }
    /**
     * Sets the specified matrix to a scale matrix.
     *
     * @param {number} x - The x-component of the scale.
     * @param {number} y - The y-component of the scale.
     * @param {number} z - The z-component of the scale.
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 scale matrix
     * const sm = new pc.Mat4().setScale(10, 10, 10);
     * @ignore
     */ setScale(x, y, z) {
        const m = this.data;
        m[0] = x;
        m[1] = 0;
        m[2] = 0;
        m[3] = 0;
        m[4] = 0;
        m[5] = y;
        m[6] = 0;
        m[7] = 0;
        m[8] = 0;
        m[9] = 0;
        m[10] = z;
        m[11] = 0;
        m[12] = 0;
        m[13] = 0;
        m[14] = 0;
        m[15] = 1;
        return this;
    }
    /**
     * Sets the specified matrix to a matrix transforming a normalized view volume (in range of
     * -1 .. 1) to their position inside a viewport (in range of 0 .. 1). This encapsulates a
     * scaling to the size of the viewport and a translation to the position of the viewport.
     *
     * @param {number} x - The x-component of the position of the viewport (in 0..1 range).
     * @param {number} y - The y-component of the position of the viewport (in 0..1 range).
     * @param {number} width - The width of the viewport (in 0..1 range).
     * @param {number} height - The height of the viewport (in 0..1 range).
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 viewport matrix which scales normalized view volume to full texture viewport
     * const vm = new pc.Mat4().setViewport(0, 0, 1, 1);
     * @ignore
     */ setViewport(x, y, width, height) {
        const m = this.data;
        m[0] = width * 0.5;
        m[1] = 0;
        m[2] = 0;
        m[3] = 0;
        m[4] = 0;
        m[5] = height * 0.5;
        m[6] = 0;
        m[7] = 0;
        m[8] = 0;
        m[9] = 0;
        m[10] = 0.5;
        m[11] = 0;
        m[12] = x + width * 0.5;
        m[13] = y + height * 0.5;
        m[14] = 0.5;
        m[15] = 1;
        return this;
    }
    /**
     * Sets the matrix to a reflection matrix, which can be used as a mirror transformation by the
     * plane.
     *
     * @param {Vec3} normal - The normal of the plane to reflect by.
     * @param {number} distance - The distance of plane to reflect by.
     * @returns {Mat4} Self for chaining.
     */ setReflection(normal, distance) {
        const a = normal.x;
        const b = normal.y;
        const c = normal.z;
        const data = this.data;
        data[0] = 1.0 - 2 * a * a;
        data[1] = -2 * a * b;
        data[2] = -2 * a * c;
        data[3] = 0;
        data[4] = -2 * a * b;
        data[5] = 1.0 - 2 * b * b;
        data[6] = -2 * b * c;
        data[7] = 0;
        data[8] = -2 * a * c;
        data[9] = -2 * b * c;
        data[10] = 1.0 - 2 * c * c;
        data[11] = 0;
        data[12] = -2 * a * distance;
        data[13] = -2 * b * distance;
        data[14] = -2 * c * distance;
        data[15] = 1;
        return this;
    }
    /**
     * Sets the matrix to the inverse of a source matrix.
     *
     * @param {Mat4} [src] - The matrix to invert. If not set, the matrix is inverted in-place.
     * @returns {Mat4} Self for chaining.
     * @example
     * // Create a 4x4 rotation matrix of 180 degrees around the y-axis
     * const rot = new pc.Mat4().setFromAxisAngle(pc.Vec3.UP, 180);
     *
     * // Invert in place
     * rot.invert();
     */ invert(src = this) {
        const s = src.data;
        const a00 = s[0];
        const a01 = s[1];
        const a02 = s[2];
        const a03 = s[3];
        const a10 = s[4];
        const a11 = s[5];
        const a12 = s[6];
        const a13 = s[7];
        const a20 = s[8];
        const a21 = s[9];
        const a22 = s[10];
        const a23 = s[11];
        const a30 = s[12];
        const a31 = s[13];
        const a32 = s[14];
        const a33 = s[15];
        const b00 = a00 * a11 - a01 * a10;
        const b01 = a00 * a12 - a02 * a10;
        const b02 = a00 * a13 - a03 * a10;
        const b03 = a01 * a12 - a02 * a11;
        const b04 = a01 * a13 - a03 * a11;
        const b05 = a02 * a13 - a03 * a12;
        const b06 = a20 * a31 - a21 * a30;
        const b07 = a20 * a32 - a22 * a30;
        const b08 = a20 * a33 - a23 * a30;
        const b09 = a21 * a32 - a22 * a31;
        const b10 = a21 * a33 - a23 * a31;
        const b11 = a22 * a33 - a23 * a32;
        const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
        if (det === 0) {
            this.setIdentity();
        } else {
            const invDet = 1 / det;
            const t = this.data;
            t[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
            t[1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet;
            t[2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet;
            t[3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet;
            t[4] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet;
            t[5] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet;
            t[6] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet;
            t[7] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet;
            t[8] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet;
            t[9] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet;
            t[10] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet;
            t[11] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet;
            t[12] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet;
            t[13] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet;
            t[14] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet;
            t[15] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet;
        }
        return this;
    }
    /**
     * Sets matrix data from an array.
     *
     * @param {number[]} src - Source array. Must have 16 values.
     * @returns {Mat4} Self for chaining.
     */ set(src) {
        const dst = this.data;
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
        dst[4] = src[4];
        dst[5] = src[5];
        dst[6] = src[6];
        dst[7] = src[7];
        dst[8] = src[8];
        dst[9] = src[9];
        dst[10] = src[10];
        dst[11] = src[11];
        dst[12] = src[12];
        dst[13] = src[13];
        dst[14] = src[14];
        dst[15] = src[15];
        return this;
    }
    /**
     * Sets the specified matrix to the identity matrix.
     *
     * @returns {Mat4} Self for chaining.
     * @example
     * m.setIdentity();
     * console.log("The matrix is " + (m.isIdentity() ? "identity" : "not identity"));
     */ setIdentity() {
        const m = this.data;
        m[0] = 1;
        m[1] = 0;
        m[2] = 0;
        m[3] = 0;
        m[4] = 0;
        m[5] = 1;
        m[6] = 0;
        m[7] = 0;
        m[8] = 0;
        m[9] = 0;
        m[10] = 1;
        m[11] = 0;
        m[12] = 0;
        m[13] = 0;
        m[14] = 0;
        m[15] = 1;
        return this;
    }
    /**
     * Sets the specified matrix to the concatenation of a translation, a quaternion rotation and a
     * scale.
     *
     * @param {Vec3} t - A 3-d vector translation.
     * @param {Quat} r - A quaternion rotation.
     * @param {Vec3} s - A 3-d vector scale.
     * @returns {Mat4} Self for chaining.
     * @example
     * const t = new pc.Vec3(10, 20, 30);
     * const r = new pc.Quat();
     * const s = new pc.Vec3(2, 2, 2);
     *
     * const m = new pc.Mat4();
     * m.setTRS(t, r, s);
     */ setTRS(t, r, s) {
        const qx = r.x;
        const qy = r.y;
        const qz = r.z;
        const qw = r.w;
        const sx = s.x;
        const sy = s.y;
        const sz = s.z;
        const x2 = qx + qx;
        const y2 = qy + qy;
        const z2 = qz + qz;
        const xx = qx * x2;
        const xy = qx * y2;
        const xz = qx * z2;
        const yy = qy * y2;
        const yz = qy * z2;
        const zz = qz * z2;
        const wx = qw * x2;
        const wy = qw * y2;
        const wz = qw * z2;
        const m = this.data;
        m[0] = (1 - (yy + zz)) * sx;
        m[1] = (xy + wz) * sx;
        m[2] = (xz - wy) * sx;
        m[3] = 0;
        m[4] = (xy - wz) * sy;
        m[5] = (1 - (xx + zz)) * sy;
        m[6] = (yz + wx) * sy;
        m[7] = 0;
        m[8] = (xz + wy) * sz;
        m[9] = (yz - wx) * sz;
        m[10] = (1 - (xx + yy)) * sz;
        m[11] = 0;
        m[12] = t.x;
        m[13] = t.y;
        m[14] = t.z;
        m[15] = 1;
        return this;
    }
    /**
     * Sets the matrix to the transpose of a source matrix.
     *
     * @param {Mat4} [src] - The matrix to transpose. If not set, the matrix is transposed in-place.
     * @returns {Mat4} Self for chaining.
     * @example
     * const m = new pc.Mat4();
     *
     * // Transpose in place
     * m.transpose();
     */ transpose(src = this) {
        const s = src.data;
        const t = this.data;
        if (s === t) {
            let tmp;
            tmp = s[1];
            t[1] = s[4];
            t[4] = tmp;
            tmp = s[2];
            t[2] = s[8];
            t[8] = tmp;
            tmp = s[3];
            t[3] = s[12];
            t[12] = tmp;
            tmp = s[6];
            t[6] = s[9];
            t[9] = tmp;
            tmp = s[7];
            t[7] = s[13];
            t[13] = tmp;
            tmp = s[11];
            t[11] = s[14];
            t[14] = tmp;
        } else {
            t[0] = s[0];
            t[1] = s[4];
            t[2] = s[8];
            t[3] = s[12];
            t[4] = s[1];
            t[5] = s[5];
            t[6] = s[9];
            t[7] = s[13];
            t[8] = s[2];
            t[9] = s[6];
            t[10] = s[10];
            t[11] = s[14];
            t[12] = s[3];
            t[13] = s[7];
            t[14] = s[11];
            t[15] = s[15];
        }
        return this;
    }
    /**
     * Extracts the translational component from the specified 4x4 matrix.
     *
     * @param {Vec3} [t] - The vector to receive the translation of the matrix.
     * @returns {Vec3} The translation of the specified 4x4 matrix.
     * @example
     * // Create a 4x4 matrix
     * const m = new pc.Mat4();
     *
     * // Query the translation component
     * const t = new pc.Vec3();
     * m.getTranslation(t);
     */ getTranslation(t = new Vec3()) {
        return t.set(this.data[12], this.data[13], this.data[14]);
    }
    /**
     * Extracts the x-axis from the specified 4x4 matrix.
     *
     * @param {Vec3} [x] - The vector to receive the x axis of the matrix.
     * @returns {Vec3} The x-axis of the specified 4x4 matrix.
     * @example
     * // Create a 4x4 matrix
     * const m = new pc.Mat4();
     *
     * // Query the x-axis component
     * const x = new pc.Vec3();
     * m.getX(x);
     */ getX(x = new Vec3()) {
        return x.set(this.data[0], this.data[1], this.data[2]);
    }
    /**
     * Extracts the y-axis from the specified 4x4 matrix.
     *
     * @param {Vec3} [y] - The vector to receive the y axis of the matrix.
     * @returns {Vec3} The y-axis of the specified 4x4 matrix.
     * @example
     * // Create a 4x4 matrix
     * const m = new pc.Mat4();
     *
     * // Query the y-axis component
     * const y = new pc.Vec3();
     * m.getY(y);
     */ getY(y = new Vec3()) {
        return y.set(this.data[4], this.data[5], this.data[6]);
    }
    /**
     * Extracts the z-axis from the specified 4x4 matrix.
     *
     * @param {Vec3} [z] - The vector to receive the z axis of the matrix.
     * @returns {Vec3} The z-axis of the specified 4x4 matrix.
     * @example
     * // Create a 4x4 matrix
     * const m = new pc.Mat4();
     *
     * // Query the z-axis component
     * const z = new pc.Vec3();
     * m.getZ(z);
     */ getZ(z = new Vec3()) {
        return z.set(this.data[8], this.data[9], this.data[10]);
    }
    /**
     * Extracts the scale component from the specified 4x4 matrix.
     *
     * @param {Vec3} [scale] - Vector to receive the scale.
     * @returns {Vec3} The scale in X, Y and Z of the specified 4x4 matrix.
     * @example
     * // Query the scale component
     * const scale = m.getScale();
     */ getScale(scale = new Vec3()) {
        this.getX(x);
        this.getY(y);
        this.getZ(z);
        scale.set(x.length(), y.length(), z.length());
        return scale;
    }
    /**
     * -1 if the the matrix has an odd number of negative scales (mirrored); 1 otherwise.
     *
     * @type {number}
     * @ignore
     */ get scaleSign() {
        this.getX(x);
        this.getY(y);
        this.getZ(z);
        x.cross(x, y);
        return x.dot(z) < 0 ? -1 : 1;
    }
    /**
     * Sets the specified matrix to a rotation matrix defined by Euler angles. The Euler angles are
     * specified in XYZ order and in degrees.
     *
     * @param {number} ex - Angle to rotate around X axis in degrees.
     * @param {number} ey - Angle to rotate around Y axis in degrees.
     * @param {number} ez - Angle to rotate around Z axis in degrees.
     * @returns {Mat4} Self for chaining.
     * @example
     * const m = new pc.Mat4();
     * m.setFromEulerAngles(45, 90, 180);
     */ setFromEulerAngles(ex, ey, ez) {
        // http://en.wikipedia.org/wiki/Rotation_matrix#Conversion_from_and_to_axis-angle
        // The 3D space is right-handed, so the rotation around each axis will be counterclockwise
        // for an observer placed so that the axis goes in his or her direction (Right-hand rule).
        ex *= math.DEG_TO_RAD;
        ey *= math.DEG_TO_RAD;
        ez *= math.DEG_TO_RAD;
        // Solution taken from http://en.wikipedia.org/wiki/Euler_angles#Matrix_orientation
        const s1 = Math.sin(-ex);
        const c1 = Math.cos(-ex);
        const s2 = Math.sin(-ey);
        const c2 = Math.cos(-ey);
        const s3 = Math.sin(-ez);
        const c3 = Math.cos(-ez);
        const m = this.data;
        // Set rotation elements
        m[0] = c2 * c3;
        m[1] = -c2 * s3;
        m[2] = s2;
        m[3] = 0;
        m[4] = c1 * s3 + c3 * s1 * s2;
        m[5] = c1 * c3 - s1 * s2 * s3;
        m[6] = -c2 * s1;
        m[7] = 0;
        m[8] = s1 * s3 - c1 * c3 * s2;
        m[9] = c3 * s1 + c1 * s2 * s3;
        m[10] = c1 * c2;
        m[11] = 0;
        m[12] = 0;
        m[13] = 0;
        m[14] = 0;
        m[15] = 1;
        return this;
    }
    /**
     * Extracts the Euler angles equivalent to the rotational portion of the specified matrix. The
     * returned Euler angles are in XYZ order an in degrees.
     *
     * @param {Vec3} [eulers] - A 3-d vector to receive the Euler angles.
     * @returns {Vec3} A 3-d vector containing the Euler angles.
     * @example
     * // Create a 4x4 rotation matrix of 45 degrees around the y-axis
     * const m = new pc.Mat4().setFromAxisAngle(pc.Vec3.UP, 45);
     *
     * const eulers = m.getEulerAngles();
     */ getEulerAngles(eulers = new Vec3()) {
        this.getScale(scale);
        const sx = scale.x;
        const sy = scale.y;
        const sz = scale.z;
        if (sx === 0 || sy === 0 || sz === 0) {
            return eulers.set(0, 0, 0);
        }
        const m = this.data;
        const y = Math.asin(-m[2] / sx);
        const halfPi = Math.PI * 0.5;
        let x, z;
        if (y < halfPi) {
            if (y > -halfPi) {
                x = Math.atan2(m[6] / sy, m[10] / sz);
                z = Math.atan2(m[1] / sx, m[0] / sx);
            } else {
                // Not a unique solution
                z = 0;
                x = -Math.atan2(m[4] / sy, m[5] / sy);
            }
        } else {
            // Not a unique solution
            z = 0;
            x = Math.atan2(m[4] / sy, m[5] / sy);
        }
        return eulers.set(x, y, z).mulScalar(math.RAD_TO_DEG);
    }
    /**
     * Converts the specified matrix to string form.
     *
     * @returns {string} The matrix in string form.
     * @example
     * const m = new pc.Mat4();
     * // Outputs [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
     * console.log(m.toString());
     */ toString() {
        return `[${this.data.join(', ')}]`;
    }
    static{
        /**
     * A constant matrix set to the identity.
     *
     * @type {Mat4}
     * @readonly
     */ this.IDENTITY = Object.freeze(new Mat4());
    }
    static{
        /**
     * A constant matrix with all elements set to 0.
     *
     * @type {Mat4}
     * @readonly
     */ this.ZERO = Object.freeze(new Mat4().set([
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ]));
    }
}

/**
 * @import { Mat4 } from './mat4.js'
 */ /**
 * A quaternion representing rotation in 3D space. Quaternions are typically used to represent
 * rotations in 3D applications, offering advantages over Euler angles including no gimbal lock and
 * more efficient interpolation.
 *
 * @category Math
 */ class Quat {
    /**
     * Creates a new Quat instance.
     *
     * @overload
     * @param {number} [x] - The x value. Defaults to 0.
     * @param {number} [y] - The y value. Defaults to 0.
     * @param {number} [z] - The z value. Defaults to 0.
     * @param {number} [w] - The w value. Defaults to 1.
     * @example
     * const q1 = new pc.Quat(); // defaults to 0, 0, 0, 1
     * const q2 = new pc.Quat(1, 2, 3, 4);
     */ /**
     * Creates a new Quat instance.
     *
     * @overload
     * @param {number[]} arr - The array to set the vector values from.
     * @example
     * const q = new pc.Quat([1, 2, 3, 4]);
     */ /**
     * @param {number|number[]} [x] - The x value. Defaults to 0. If x is an array of length 4, the
     * array will be used to populate all components.
     * @param {number} [y] - The y value. Defaults to 0.
     * @param {number} [z] - The z value. Defaults to 0.
     * @param {number} [w] - The w value. Defaults to 1.
     */ constructor(x = 0, y = 0, z = 0, w = 1){
        if (x.length === 4) {
            this.x = x[0];
            this.y = x[1];
            this.z = x[2];
            this.w = x[3];
        } else {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = w;
        }
    }
    /**
     * Returns an identical copy of the specified quaternion.
     *
     * @returns {this} A new quaternion identical to this one.
     * @example
     * const q = new pc.Quat(-0.11, -0.15, -0.46, 0.87);
     * const qclone = q.clone();
     *
     * console.log("The result of the cloning is: " + qclone.toString());
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr(this.x, this.y, this.z, this.w);
    }
    /**
     * Conjugates a quaternion.
     *
     * @param {Quat} [src] - The quaternion to conjugate. If not set, the operation is done in place.
     * @returns {Quat} Self for chaining.
     * @example
     * const q = new pc.Quat(1, 2, 3, 4);
     * q.conjugate();
     * // q is now [-1, -2, -3, 4]
     * @ignore
     */ conjugate(src = this) {
        this.x = src.x * -1;
        this.y = src.y * -1;
        this.z = src.z * -1;
        this.w = src.w;
        return this;
    }
    /**
     * Copies the contents of a source quaternion to a destination quaternion.
     *
     * @param {Quat} rhs - The quaternion to be copied.
     * @returns {Quat} Self for chaining.
     * @example
     * const src = new pc.Quat();
     * const dst = new pc.Quat();
     * dst.copy(src);
     * console.log("The two quaternions are " + (src.equals(dst) ? "equal" : "different"));
     */ copy(rhs) {
        this.x = rhs.x;
        this.y = rhs.y;
        this.z = rhs.z;
        this.w = rhs.w;
        return this;
    }
    /**
     * Calculates the dot product of two quaternions.
     *
     * @param {Quat} other - The quaternion to calculate the dot product with.
     * @returns {number} The dot product of the two quaternions.
     * @example
     * const a = new pc.Quat(1, 0, 0, 0);
     * const b = new pc.Quat(0, 1, 0, 0);
     * console.log("Dot product: " + a.dot(b)); // Outputs 0
     */ dot(other) {
        return this.x * other.x + this.y * other.y + this.z * other.z + this.w * other.w;
    }
    /**
     * Reports whether two quaternions are equal.
     *
     * @param {Quat} rhs - The quaternion to be compared against.
     * @returns {boolean} True if the quaternions are equal and false otherwise.
     * @example
     * const a = new pc.Quat();
     * const b = new pc.Quat();
     * console.log("The two quaternions are " + (a.equals(b) ? "equal" : "different"));
     */ equals(rhs) {
        return this.x === rhs.x && this.y === rhs.y && this.z === rhs.z && this.w === rhs.w;
    }
    /**
     * Reports whether two quaternions are equal using an absolute error tolerance.
     *
     * @param {Quat} rhs - The quaternion to be compared against.
     * @param {number} [epsilon] - The maximum difference between each component of the two
     * quaternions. Defaults to 1e-6.
     * @returns {boolean} True if the quaternions are equal and false otherwise.
     * @example
     * const a = new pc.Quat();
     * const b = new pc.Quat();
     * console.log("The two quaternions are approximately " + (a.equalsApprox(b, 1e-9) ? "equal" : "different"));
     */ equalsApprox(rhs, epsilon = 1e-6) {
        return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon && Math.abs(this.w - rhs.w) < epsilon;
    }
    /**
     * Gets the rotation axis and angle for a given quaternion. If a quaternion is created with
     * `setFromAxisAngle`, this method will return the same values as provided in the original
     * parameter list OR functionally equivalent values.
     *
     * @param {Vec3} axis - The 3-dimensional vector to receive the axis of rotation.
     * @returns {number} Angle, in degrees, of the rotation.
     * @example
     * const q = new pc.Quat();
     * q.setFromAxisAngle(new pc.Vec3(0, 1, 0), 90);
     * const v = new pc.Vec3();
     * const angle = q.getAxisAngle(v);
     * // Outputs 90
     * console.log(angle);
     * // Outputs [0, 1, 0]
     * console.log(v.toString());
     */ getAxisAngle(axis) {
        let rad = Math.acos(this.w) * 2;
        const s = Math.sin(rad / 2);
        if (s !== 0) {
            axis.x = this.x / s;
            axis.y = this.y / s;
            axis.z = this.z / s;
            if (axis.x < 0 || axis.y < 0 || axis.z < 0) {
                // Flip the sign
                axis.x *= -1;
                axis.y *= -1;
                axis.z *= -1;
                rad *= -1;
            }
        } else {
            // If s is zero, return any axis (no rotation - axis does not matter)
            axis.x = 1;
            axis.y = 0;
            axis.z = 0;
        }
        return rad * math.RAD_TO_DEG;
    }
    /**
     * Converts this quaternion to Euler angles, specified in degrees. The decomposition uses an
     * **intrinsic XYZ** order, representing the angles required to achieve the quaternion's
     * orientation by rotating sequentially: first around the X-axis, then around the newly
     * transformed Y-axis, and finally around the resulting Z-axis.
     *
     * @param {Vec3} [eulers] - An optional 3-dimensional vector to receive the calculated
     * Euler angles (output parameter). If not provided, a new Vec3 object will be allocated
     * and returned.
     * @returns {Vec3} The 3-dimensional vector holding the Euler angles in degrees. This will be
     * the same object passed in as the `eulers` parameter (if one was provided).
     * @example
     * const q = new pc.Quat();
     * q.setFromAxisAngle(pc.Vec3.UP, 90);
     * const e = new pc.Vec3();
     * q.getEulerAngles(e);
     * // Outputs [0, 90, 0]
     * console.log(e.toString());
     */ getEulerAngles(eulers = new Vec3()) {
        let x, y, z;
        const qx = this.x;
        const qy = this.y;
        const qz = this.z;
        const qw = this.w;
        const a2 = 2 * (qw * qy - qx * qz);
        if (a2 <= -0.99999) {
            x = 2 * Math.atan2(qx, qw);
            y = -Math.PI / 2;
            z = 0;
        } else if (a2 >= 0.99999) {
            x = 2 * Math.atan2(qx, qw);
            y = Math.PI / 2;
            z = 0;
        } else {
            x = Math.atan2(2 * (qw * qx + qy * qz), 1 - 2 * (qx * qx + qy * qy));
            y = Math.asin(a2);
            z = Math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qy * qy + qz * qz));
        }
        return eulers.set(x, y, z).mulScalar(math.RAD_TO_DEG);
    }
    /**
     * Generates the inverse of the specified quaternion.
     *
     * @param {Quat} [src] - The quaternion to invert. If not set, the operation is done in place.
     * @returns {Quat} Self for chaining.
     * @example
     * // Create a quaternion rotated 180 degrees around the y-axis
     * const rot = new pc.Quat().setFromEulerAngles(0, 180, 0);
     *
     * // Invert in place
     * rot.invert();
     */ invert(src = this) {
        return this.conjugate(src).normalize();
    }
    /**
     * Returns the magnitude of the specified quaternion.
     *
     * @returns {number} The magnitude of the specified quaternion.
     * @example
     * const q = new pc.Quat(0, 0, 0, 5);
     * const len = q.length();
     * // Outputs 5
     * console.log("The length of the quaternion is: " + len);
     */ length() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
    }
    /**
     * Returns the magnitude squared of the specified quaternion.
     *
     * @returns {number} The magnitude squared of the quaternion.
     * @example
     * const q = new pc.Quat(3, 4, 0, 0);
     * const lenSq = q.lengthSq();
     * // Outputs 25
     * console.log("The length squared of the quaternion is: " + lenSq);
     */ lengthSq() {
        return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
    }
    /**
     * Performs a linear interpolation between two quaternions. The result of the interpolation
     * is written to the quaternion calling the function.
     *
     * @param {Quat} lhs - The quaternion to interpolate from.
     * @param {Quat} rhs - The quaternion to interpolate to.
     * @param {number} alpha - The value controlling the interpolation in relation to the two input
     * quaternions. The value is in the range 0 to 1, 0 generating q1, 1 generating q2 and anything
     * in between generating a linear interpolation between the two.
     * @returns {Quat} Self for chaining.
     * @example
     * const q1 = new pc.Quat(-0.11, -0.15, -0.46, 0.87);
     * const q2 = new pc.Quat(-0.21, -0.21, -0.67, 0.68);
     *
     * const result = new pc.Quat();
     * result.lerp(q1, q2, 0);   // Return q1
     * result.lerp(q1, q2, 0.5); // Return the midpoint interpolant
     * result.lerp(q1, q2, 1);   // Return q2
     */ lerp(lhs, rhs, alpha) {
        const omt = (1 - alpha) * (lhs.dot(rhs) < 0 ? -1 : 1);
        this.x = lhs.x * omt + rhs.x * alpha;
        this.y = lhs.y * omt + rhs.y * alpha;
        this.z = lhs.z * omt + rhs.z * alpha;
        this.w = lhs.w * omt + rhs.w * alpha;
        return this.normalize();
    }
    /**
     * Returns the result of multiplying the specified quaternions together.
     *
     * @param {Quat} rhs - The quaternion used as the second multiplicand of the operation.
     * @returns {Quat} Self for chaining.
     * @example
     * const a = new pc.Quat().setFromEulerAngles(0, 30, 0);
     * const b = new pc.Quat().setFromEulerAngles(0, 60, 0);
     *
     * // a becomes a 90 degree rotation around the Y axis
     * // In other words, a = a * b
     * a.mul(b);
     *
     * console.log("The result of the multiplication is: " + a.toString());
     */ mul(rhs) {
        const q1x = this.x;
        const q1y = this.y;
        const q1z = this.z;
        const q1w = this.w;
        const q2x = rhs.x;
        const q2y = rhs.y;
        const q2z = rhs.z;
        const q2w = rhs.w;
        this.x = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y;
        this.y = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z;
        this.z = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x;
        this.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
        return this;
    }
    /**
     * Multiplies each element of a quaternion by a number.
     *
     * @param {number} scalar - The number to multiply by.
     * @param {Quat} [src] - The quaternion to scale. If not set, the operation is done in place.
     * @returns {Quat} Self for chaining.
     * @example
     * const q = new pc.Quat(1, 2, 3, 4);
     * q.mulScalar(2);
     * // q is now [2, 4, 6, 8]
     */ mulScalar(scalar, src = this) {
        this.x = src.x * scalar;
        this.y = src.y * scalar;
        this.z = src.z * scalar;
        this.w = src.w * scalar;
        return this;
    }
    /**
     * Returns the result of multiplying the specified quaternions together.
     *
     * @param {Quat} lhs - The quaternion used as the first multiplicand of the operation.
     * @param {Quat} rhs - The quaternion used as the second multiplicand of the operation.
     * @returns {Quat} Self for chaining.
     * @example
     * const a = new pc.Quat().setFromEulerAngles(0, 30, 0);
     * const b = new pc.Quat().setFromEulerAngles(0, 60, 0);
     * const r = new pc.Quat();
     *
     * // r is set to a 90 degree rotation around the Y axis
     * // In other words, r = a * b
     * r.mul2(a, b);
     */ mul2(lhs, rhs) {
        const q1x = lhs.x;
        const q1y = lhs.y;
        const q1z = lhs.z;
        const q1w = lhs.w;
        const q2x = rhs.x;
        const q2y = rhs.y;
        const q2z = rhs.z;
        const q2w = rhs.w;
        this.x = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y;
        this.y = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z;
        this.z = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x;
        this.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
        return this;
    }
    /**
     * Normalizes the specified quaternion.
     *
     * @param {Quat} [src] - The quaternion to normalize. If not set, the operation is done in place.
     * @returns {Quat} The result of the normalization.
     * @example
     * const v = new pc.Quat(0, 0, 0, 5);
     * v.normalize();
     * // Outputs [0, 0, 0, 1]
     * console.log(v.toString());
     */ normalize(src = this) {
        let len = src.length();
        if (len === 0) {
            this.x = this.y = this.z = 0;
            this.w = 1;
        } else {
            len = 1 / len;
            this.x = src.x * len;
            this.y = src.y * len;
            this.z = src.z * len;
            this.w = src.w * len;
        }
        return this;
    }
    /**
     * Sets the specified quaternion to the supplied numerical values.
     *
     * @param {number} x - The x component of the quaternion.
     * @param {number} y - The y component of the quaternion.
     * @param {number} z - The z component of the quaternion.
     * @param {number} w - The w component of the quaternion.
     * @returns {Quat} Self for chaining.
     * @example
     * const q = new pc.Quat();
     * q.set(1, 0, 0, 0);
     *
     * // Outputs 1, 0, 0, 0
     * console.log("The result of the vector set is: " + q.toString());
     */ set(x, y, z, w) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        return this;
    }
    /**
     * Sets a quaternion from an angular rotation around an axis.
     *
     * @param {Vec3} axis - World space axis around which to rotate. Should be normalized.
     * @param {number} angle - Angle to rotate around the given axis in degrees.
     * @returns {Quat} Self for chaining.
     * @example
     * const q = new pc.Quat();
     * q.setFromAxisAngle(pc.Vec3.UP, 90);
     */ setFromAxisAngle(axis, angle) {
        angle *= 0.5 * math.DEG_TO_RAD;
        const sa = Math.sin(angle);
        const ca = Math.cos(angle);
        this.x = sa * axis.x;
        this.y = sa * axis.y;
        this.z = sa * axis.z;
        this.w = ca;
        return this;
    }
    /**
     * Sets this quaternion to represent a rotation specified by Euler angles in degrees.
     * The rotation is applied using an **intrinsic XYZ** order: first around the X-axis, then
     * around the newly transformed Y-axis, and finally around the resulting Z-axis.
     *
     * @param {number|Vec3} ex - The angle to rotate around the X-axis in degrees, or a Vec3
     * object containing the X, Y, and Z angles in degrees in its respective components (`ex.x`,
     * `ex.y`, `ex.z`).
     * @param {number} [ey] - The angle to rotate around the Y-axis in degrees. This parameter is
     * only used if `ex` is provided as a number.
     * @param {number} [ez] - The angle to rotate around the Z-axis in degrees. This parameter is
     * only used if `ex` is provided as a number.
     * @returns {Quat} The quaternion itself (this), now representing the orientation from the
     * specified XYZ Euler angles. Allows for method chaining.
     * @example
     * // Create a quaternion from 3 individual Euler angles (interpreted as X, Y, Z order)
     * const q1 = new pc.Quat();
     * q1.setFromEulerAngles(45, 90, 180); // 45 deg around X, then 90 deg around Y', then 180 deg around Z''
     * console.log("From numbers:", q1.toString());
     * @example
     * // Create the same quaternion from a Vec3 containing the angles (X, Y, Z)
     * const anglesVec = new pc.Vec3(45, 90, 180);
     * const q2 = new pc.Quat();
     * q2.setFromEulerAngles(anglesVec);
     * console.log("From Vec3:", q2.toString()); // Should match q1
     */ setFromEulerAngles(ex, ey, ez) {
        if (ex instanceof Vec3) {
            const vec = ex;
            ex = vec.x;
            ey = vec.y;
            ez = vec.z;
        }
        const halfToRad = 0.5 * math.DEG_TO_RAD;
        ex *= halfToRad;
        ey *= halfToRad;
        ez *= halfToRad;
        const sx = Math.sin(ex);
        const cx = Math.cos(ex);
        const sy = Math.sin(ey);
        const cy = Math.cos(ey);
        const sz = Math.sin(ez);
        const cz = Math.cos(ez);
        this.x = sx * cy * cz - cx * sy * sz;
        this.y = cx * sy * cz + sx * cy * sz;
        this.z = cx * cy * sz - sx * sy * cz;
        this.w = cx * cy * cz + sx * sy * sz;
        return this;
    }
    /**
     * Converts the specified 4x4 matrix to a quaternion. Note that since a quaternion is purely a
     * representation for orientation, only the rotational part of the matrix is used.
     *
     * @param {Mat4} m - The 4x4 matrix to convert.
     * @returns {Quat} Self for chaining.
     * @example
     * // Create a 4x4 rotation matrix of 180 degrees around the y-axis
     * const rot = new pc.Mat4().setFromAxisAngle(pc.Vec3.UP, 180);
     *
     * // Convert to a quaternion
     * const q = new pc.Quat().setFromMat4(rot);
     */ setFromMat4(m) {
        const d = m.data;
        let m00 = d[0];
        let m01 = d[1];
        let m02 = d[2];
        let m10 = d[4];
        let m11 = d[5];
        let m12 = d[6];
        let m20 = d[8];
        let m21 = d[9];
        let m22 = d[10];
        // if negative the space is inverted so flip X axis to restore right-handedness
        const det = m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20);
        if (det < 0) {
            m00 = -m00;
            m01 = -m01;
            m02 = -m02;
        }
        let l;
        // remove scaling from axis vectors
        l = m00 * m00 + m01 * m01 + m02 * m02;
        if (l === 0) return this.set(0, 0, 0, 1);
        l = 1 / Math.sqrt(l);
        m00 *= l;
        m01 *= l;
        m02 *= l;
        l = m10 * m10 + m11 * m11 + m12 * m12;
        if (l === 0) return this.set(0, 0, 0, 1);
        l = 1 / Math.sqrt(l);
        m10 *= l;
        m11 *= l;
        m12 *= l;
        l = m20 * m20 + m21 * m21 + m22 * m22;
        if (l === 0) return this.set(0, 0, 0, 1);
        l = 1 / Math.sqrt(l);
        m20 *= l;
        m21 *= l;
        m22 *= l;
        // https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2015/01/matrix-to-quat.pdf
        if (m22 < 0) {
            if (m00 > m11) {
                this.set(1 + m00 - m11 - m22, m01 + m10, m20 + m02, m12 - m21);
            } else {
                this.set(m01 + m10, 1 - m00 + m11 - m22, m12 + m21, m20 - m02);
            }
        } else {
            if (m00 < -m11) {
                this.set(m20 + m02, m12 + m21, 1 - m00 - m11 + m22, m01 - m10);
            } else {
                this.set(m12 - m21, m20 - m02, m01 - m10, 1 + m00 + m11 + m22);
            }
        }
        // Instead of scaling by 0.5 / Math.sqrt(t) (to match the original implementation),
        // instead we normalize the result. It costs 3 more adds and muls, but we get
        // a stable result and in some cases normalization is required anyway, see
        // https://github.com/blender/blender/blob/v4.1.1/source/blender/blenlib/intern/math_rotation.c#L368
        return this.mulScalar(1.0 / this.length());
    }
    /**
     * Set the quaternion that represents the shortest rotation from one direction to another.
     *
     * @param {Vec3} from - The direction to rotate from. It should be normalized.
     * @param {Vec3} to - The direction to rotate to. It should be normalized.
     * @returns {Quat} Self for chaining.
     * @example
     * const q = new pc.Quat();
     * const from = new pc.Vec3(0, 0, 1);
     * const to = new pc.Vec3(0, 1, 0);
     * q.setFromDirections(from, to);
     */ setFromDirections(from, to) {
        const dotProduct = 1 + from.dot(to);
        if (dotProduct < Number.EPSILON) {
            // the vectors point in opposite directions
            // so we need to rotate 180 degrees around an arbitrary orthogonal axis
            if (Math.abs(from.x) > Math.abs(from.y)) {
                this.x = -from.z;
                this.y = 0;
                this.z = from.x;
                this.w = 0;
            } else {
                this.x = 0;
                this.y = -from.z;
                this.z = from.y;
                this.w = 0;
            }
        } else {
            // cross product between the two vectors
            this.x = from.y * to.z - from.z * to.y;
            this.y = from.z * to.x - from.x * to.z;
            this.z = from.x * to.y - from.y * to.x;
            this.w = dotProduct;
        }
        return this.normalize();
    }
    /**
     * Performs a spherical interpolation between two quaternions. The result of the interpolation
     * is written to the quaternion calling the function.
     *
     * @param {Quat} lhs - The quaternion to interpolate from.
     * @param {Quat} rhs - The quaternion to interpolate to.
     * @param {number} alpha - The value controlling the interpolation in relation to the two input
     * quaternions. The value is in the range 0 to 1, 0 generating q1, 1 generating q2 and anything
     * in between generating a spherical interpolation between the two.
     * @returns {Quat} Self for chaining.
     * @example
     * const q1 = new pc.Quat(-0.11, -0.15, -0.46, 0.87);
     * const q2 = new pc.Quat(-0.21, -0.21, -0.67, 0.68);
     *
     * const result = new pc.Quat();
     * result.slerp(q1, q2, 0);   // Return q1
     * result.slerp(q1, q2, 0.5); // Return the midpoint interpolant
     * result.slerp(q1, q2, 1);   // Return q2
     */ slerp(lhs, rhs, alpha) {
        // Algorithm sourced from:
        // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
        const lx = lhs.x;
        const ly = lhs.y;
        const lz = lhs.z;
        const lw = lhs.w;
        let rx = rhs.x;
        let ry = rhs.y;
        let rz = rhs.z;
        let rw = rhs.w;
        // Calculate angle between them.
        let cosHalfTheta = lw * rw + lx * rx + ly * ry + lz * rz;
        if (cosHalfTheta < 0) {
            rw = -rw;
            rx = -rx;
            ry = -ry;
            rz = -rz;
            cosHalfTheta = -cosHalfTheta;
        }
        // If lhs == rhs or lhs == -rhs then theta == 0 and we can return lhs
        if (Math.abs(cosHalfTheta) >= 1) {
            this.w = lw;
            this.x = lx;
            this.y = ly;
            this.z = lz;
            return this;
        }
        // Calculate temporary values.
        const halfTheta = Math.acos(cosHalfTheta);
        const sinHalfTheta = Math.sqrt(1 - cosHalfTheta * cosHalfTheta);
        // If theta = 180 degrees then result is not fully defined
        // we could rotate around any axis normal to qa or qb
        if (Math.abs(sinHalfTheta) < 0.001) {
            this.w = lw * 0.5 + rw * 0.5;
            this.x = lx * 0.5 + rx * 0.5;
            this.y = ly * 0.5 + ry * 0.5;
            this.z = lz * 0.5 + rz * 0.5;
            return this;
        }
        const ratioA = Math.sin((1 - alpha) * halfTheta) / sinHalfTheta;
        const ratioB = Math.sin(alpha * halfTheta) / sinHalfTheta;
        // Calculate Quaternion.
        this.w = lw * ratioA + rw * ratioB;
        this.x = lx * ratioA + rx * ratioB;
        this.y = ly * ratioA + ry * ratioB;
        this.z = lz * ratioA + rz * ratioB;
        return this;
    }
    /**
     * Transforms a 3-dimensional vector by the specified quaternion.
     *
     * @param {Vec3} vec - The 3-dimensional vector to be transformed.
     * @param {Vec3} [res] - An optional 3-dimensional vector to receive the result of the transformation.
     * @returns {Vec3} The transformed vector (res if specified, otherwise a new Vec3).
     * @example
     * // Create a 3-dimensional vector
     * const v = new pc.Vec3(1, 2, 3);
     *
     * // Create a quaternion rotation
     * const q = new pc.Quat().setFromEulerAngles(10, 20, 30);
     *
     * const tv = q.transformVector(v);
     */ transformVector(vec, res = new Vec3()) {
        const x = vec.x, y = vec.y, z = vec.z;
        const qx = this.x, qy = this.y, qz = this.z, qw = this.w;
        // calculate quat * vec
        const ix = qw * x + qy * z - qz * y;
        const iy = qw * y + qz * x - qx * z;
        const iz = qw * z + qx * y - qy * x;
        const iw = -qx * x - qy * y - qz * z;
        // calculate result * inverse quat
        res.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
        res.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
        res.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
        return res;
    }
    /**
     * Set the values of the quaternion from an array.
     *
     * @param {number[]|ArrayBufferView} arr - The array to set the quaternion values from.
     * @param {number} [offset] - The zero-based index at which to start copying elements from the
     * array. Default is 0.
     * @returns {Quat} Self for chaining.
     * @example
     * const q = new pc.Quat();
     * q.fromArray([20, 10, 5, 0]);
     * // q is set to [20, 10, 5, 0]
     */ fromArray(arr, offset = 0) {
        this.x = arr[offset] ?? this.x;
        this.y = arr[offset + 1] ?? this.y;
        this.z = arr[offset + 2] ?? this.z;
        this.w = arr[offset + 3] ?? this.w;
        return this;
    }
    /**
     * Converts the quaternion to string form.
     *
     * @returns {string} The quaternion in string form.
     * @example
     * const q = new pc.Quat(0, 0, 0, 1);
     * // Outputs [0, 0, 0, 1]
     * console.log(q.toString());
     */ toString() {
        return `[${this.x}, ${this.y}, ${this.z}, ${this.w}]`;
    }
    /**
     * @overload
     * @param {number[]} [arr] - The array to populate with the quaternion's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]} The quaternion as an array.
     */ /**
     * @overload
     * @param {ArrayBufferView} arr - The array to populate with the quaternion's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {ArrayBufferView} The quaternion as an array.
     */ /**
     * Converts the quaternion to an array.
     *
     * @param {number[]|ArrayBufferView} [arr] - The array to populate with the quaternion's number
     * components. If not specified, a new array is created.
     * @param {number} [offset] - The zero-based index at which to start copying elements to the
     * array. Default is 0.
     * @returns {number[]|ArrayBufferView} The quaternion as an array.
     * @example
     * const q = new pc.Quat(20, 10, 5, 1);
     * // Outputs [20, 10, 5, 1]
     * console.log(q.toArray());
     */ toArray(arr = [], offset = 0) {
        arr[offset] = this.x;
        arr[offset + 1] = this.y;
        arr[offset + 2] = this.z;
        arr[offset + 3] = this.w;
        return arr;
    }
    static{
        /**
     * A constant quaternion set to [0, 0, 0, 1] (the identity). Represents no rotation.
     *
     * @type {Quat}
     * @readonly
     */ this.IDENTITY = Object.freeze(new Quat(0, 0, 0, 1));
    }
    static{
        /**
     * A constant quaternion set to [0, 0, 0, 0].
     *
     * @type {Quat}
     * @readonly
     */ this.ZERO = Object.freeze(new Quat(0, 0, 0, 0));
    }
}

/**
 * @import { BoundingSphere } from './bounding-sphere.js'
 * @import { Mat4 } from '../math/mat4.js'
 * @import { Ray } from './ray.js'
 */ const tmpVecA$1 = new Vec3();
const tmpVecB$1 = new Vec3();
const tmpVecC = new Vec3();
const tmpVecD = new Vec3();
const tmpVecE = new Vec3();
/**
 * Axis-Aligned Bounding Box.
 *
 * @category Math
 */ class BoundingBox {
    /**
     * Create a new BoundingBox instance. The bounding box is axis-aligned.
     *
     * @param {Vec3} [center] - Center of box. The constructor takes a reference of this parameter.
     * Defaults to (0, 0, 0).
     * @param {Vec3} [halfExtents] - Half the distance across the box in each axis. The constructor
     * takes a reference of this parameter. Defaults to (0.5, 0.5, 0.5).
     */ constructor(center, halfExtents){
        /**
     * Center of box.
     *
     * @type {Vec3}
     * @readonly
     */ this.center = new Vec3();
        /**
     * Half the distance across the box in each axis.
     *
     * @type {Vec3}
     * @readonly
     */ this.halfExtents = new Vec3(0.5, 0.5, 0.5);
        /**
     * @type {Vec3}
     * @private
     */ this._min = new Vec3();
        /**
     * @type {Vec3}
     * @private
     */ this._max = new Vec3();
        if (center) {
            this.center.copy(center);
        }
        if (halfExtents) {
            this.halfExtents.copy(halfExtents);
        }
    }
    /**
     * Combines two bounding boxes into one, enclosing both.
     *
     * @param {BoundingBox} other - Bounding box to add.
     */ add(other) {
        const tc = this.center;
        const tcx = tc.x;
        const tcy = tc.y;
        const tcz = tc.z;
        const th = this.halfExtents;
        const thx = th.x;
        const thy = th.y;
        const thz = th.z;
        let tminx = tcx - thx;
        let tmaxx = tcx + thx;
        let tminy = tcy - thy;
        let tmaxy = tcy + thy;
        let tminz = tcz - thz;
        let tmaxz = tcz + thz;
        const oc = other.center;
        const ocx = oc.x;
        const ocy = oc.y;
        const ocz = oc.z;
        const oh = other.halfExtents;
        const ohx = oh.x;
        const ohy = oh.y;
        const ohz = oh.z;
        const ominx = ocx - ohx;
        const omaxx = ocx + ohx;
        const ominy = ocy - ohy;
        const omaxy = ocy + ohy;
        const ominz = ocz - ohz;
        const omaxz = ocz + ohz;
        if (ominx < tminx) tminx = ominx;
        if (omaxx > tmaxx) tmaxx = omaxx;
        if (ominy < tminy) tminy = ominy;
        if (omaxy > tmaxy) tmaxy = omaxy;
        if (ominz < tminz) tminz = ominz;
        if (omaxz > tmaxz) tmaxz = omaxz;
        tc.x = (tminx + tmaxx) * 0.5;
        tc.y = (tminy + tmaxy) * 0.5;
        tc.z = (tminz + tmaxz) * 0.5;
        th.x = (tmaxx - tminx) * 0.5;
        th.y = (tmaxy - tminy) * 0.5;
        th.z = (tmaxz - tminz) * 0.5;
    }
    /**
     * Copies the contents of a source AABB.
     *
     * @param {BoundingBox} src - The AABB to copy from.
     */ copy(src) {
        this.center.copy(src.center);
        this.halfExtents.copy(src.halfExtents);
    }
    /**
     * Returns a clone of the AABB.
     *
     * @returns {BoundingBox} A duplicate AABB.
     */ clone() {
        return new BoundingBox(this.center, this.halfExtents);
    }
    /**
     * Test whether two axis-aligned bounding boxes intersect.
     *
     * @param {BoundingBox} other - Bounding box to test against.
     * @returns {boolean} True if there is an intersection.
     */ intersects(other) {
        const aMax = this.getMax();
        const aMin = this.getMin();
        const bMax = other.getMax();
        const bMin = other.getMin();
        return aMin.x <= bMax.x && aMax.x >= bMin.x && aMin.y <= bMax.y && aMax.y >= bMin.y && aMin.z <= bMax.z && aMax.z >= bMin.z;
    }
    _intersectsRay(ray, point) {
        const tMin = tmpVecA$1.copy(this.getMin()).sub(ray.origin);
        const tMax = tmpVecB$1.copy(this.getMax()).sub(ray.origin);
        const dir = ray.direction;
        // Ensure that we are not dividing it by zero
        if (dir.x === 0) {
            tMin.x = tMin.x < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;
            tMax.x = tMax.x < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;
        } else {
            tMin.x /= dir.x;
            tMax.x /= dir.x;
        }
        if (dir.y === 0) {
            tMin.y = tMin.y < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;
            tMax.y = tMax.y < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;
        } else {
            tMin.y /= dir.y;
            tMax.y /= dir.y;
        }
        if (dir.z === 0) {
            tMin.z = tMin.z < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;
            tMax.z = tMax.z < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;
        } else {
            tMin.z /= dir.z;
            tMax.z /= dir.z;
        }
        const realMin = tmpVecC.set(Math.min(tMin.x, tMax.x), Math.min(tMin.y, tMax.y), Math.min(tMin.z, tMax.z));
        const realMax = tmpVecD.set(Math.max(tMin.x, tMax.x), Math.max(tMin.y, tMax.y), Math.max(tMin.z, tMax.z));
        const minMax = Math.min(Math.min(realMax.x, realMax.y), realMax.z);
        const maxMin = Math.max(Math.max(realMin.x, realMin.y), realMin.z);
        const intersects = minMax >= maxMin && maxMin >= 0;
        if (intersects) {
            point.copy(ray.direction).mulScalar(maxMin).add(ray.origin);
        }
        return intersects;
    }
    _fastIntersectsRay(ray) {
        const diff = tmpVecA$1;
        const cross = tmpVecB$1;
        const prod = tmpVecC;
        const absDiff = tmpVecD;
        const absDir = tmpVecE;
        const rayDir = ray.direction;
        diff.sub2(ray.origin, this.center);
        absDiff.set(Math.abs(diff.x), Math.abs(diff.y), Math.abs(diff.z));
        prod.mul2(diff, rayDir);
        if (absDiff.x > this.halfExtents.x && prod.x >= 0) {
            return false;
        }
        if (absDiff.y > this.halfExtents.y && prod.y >= 0) {
            return false;
        }
        if (absDiff.z > this.halfExtents.z && prod.z >= 0) {
            return false;
        }
        absDir.set(Math.abs(rayDir.x), Math.abs(rayDir.y), Math.abs(rayDir.z));
        cross.cross(rayDir, diff);
        cross.set(Math.abs(cross.x), Math.abs(cross.y), Math.abs(cross.z));
        if (cross.x > this.halfExtents.y * absDir.z + this.halfExtents.z * absDir.y) {
            return false;
        }
        if (cross.y > this.halfExtents.x * absDir.z + this.halfExtents.z * absDir.x) {
            return false;
        }
        if (cross.z > this.halfExtents.x * absDir.y + this.halfExtents.y * absDir.x) {
            return false;
        }
        return true;
    }
    /**
     * Test if a ray intersects with the AABB.
     *
     * @param {Ray} ray - Ray to test against (direction must be normalized).
     * @param {Vec3} [point] - If there is an intersection, the intersection point will be copied
     * into here.
     * @returns {boolean} True if there is an intersection.
     */ intersectsRay(ray, point) {
        if (point) {
            return this._intersectsRay(ray, point);
        }
        return this._fastIntersectsRay(ray);
    }
    /**
     * Sets the minimum and maximum corner of the AABB. Using this function is faster than
     * assigning min and max separately.
     *
     * @param {Vec3} min - The minimum corner of the AABB.
     * @param {Vec3} max - The maximum corner of the AABB.
     */ setMinMax(min, max) {
        this.center.add2(max, min).mulScalar(0.5);
        this.halfExtents.sub2(max, min).mulScalar(0.5);
    }
    /**
     * Return the minimum corner of the AABB.
     *
     * @returns {Vec3} Minimum corner.
     */ getMin() {
        return this._min.copy(this.center).sub(this.halfExtents);
    }
    /**
     * Return the maximum corner of the AABB.
     *
     * @returns {Vec3} Maximum corner.
     */ getMax() {
        return this._max.copy(this.center).add(this.halfExtents);
    }
    /**
     * Test if a point is inside a AABB.
     *
     * @param {Vec3} point - Point to test.
     * @returns {boolean} True if the point is inside the AABB and false otherwise.
     */ containsPoint(point) {
        const min = this.getMin();
        const max = this.getMax();
        if (point.x < min.x || point.x > max.x || point.y < min.y || point.y > max.y || point.z < min.z || point.z > max.z) {
            return false;
        }
        return true;
    }
    /**
     * Set an AABB to enclose the specified AABB if it were to be transformed by the specified 4x4
     * matrix.
     *
     * @param {BoundingBox} aabb - Box to transform and enclose.
     * @param {Mat4} m - Transformation matrix to apply to source AABB.
     * @param {boolean} ignoreScale - If true is specified, a scale from the matrix is ignored. Defaults to false.
     */ setFromTransformedAabb(aabb, m, ignoreScale = false) {
        const ac = aabb.center;
        const ar = aabb.halfExtents;
        const d = m.data;
        let mx0 = d[0];
        let mx1 = d[4];
        let mx2 = d[8];
        let my0 = d[1];
        let my1 = d[5];
        let my2 = d[9];
        let mz0 = d[2];
        let mz1 = d[6];
        let mz2 = d[10];
        // renormalize axis if scale is to be ignored
        if (ignoreScale) {
            let lengthSq = mx0 * mx0 + mx1 * mx1 + mx2 * mx2;
            if (lengthSq > 0) {
                const invLength = 1 / Math.sqrt(lengthSq);
                mx0 *= invLength;
                mx1 *= invLength;
                mx2 *= invLength;
            }
            lengthSq = my0 * my0 + my1 * my1 + my2 * my2;
            if (lengthSq > 0) {
                const invLength = 1 / Math.sqrt(lengthSq);
                my0 *= invLength;
                my1 *= invLength;
                my2 *= invLength;
            }
            lengthSq = mz0 * mz0 + mz1 * mz1 + mz2 * mz2;
            if (lengthSq > 0) {
                const invLength = 1 / Math.sqrt(lengthSq);
                mz0 *= invLength;
                mz1 *= invLength;
                mz2 *= invLength;
            }
        }
        this.center.set(d[12] + mx0 * ac.x + mx1 * ac.y + mx2 * ac.z, d[13] + my0 * ac.x + my1 * ac.y + my2 * ac.z, d[14] + mz0 * ac.x + mz1 * ac.y + mz2 * ac.z);
        this.halfExtents.set(Math.abs(mx0) * ar.x + Math.abs(mx1) * ar.y + Math.abs(mx2) * ar.z, Math.abs(my0) * ar.x + Math.abs(my1) * ar.y + Math.abs(my2) * ar.z, Math.abs(mz0) * ar.x + Math.abs(mz1) * ar.y + Math.abs(mz2) * ar.z);
    }
    /**
     * Compute the min and max bounding values to encapsulate all specified vertices.
     *
     * @param {number[]|Float32Array} vertices - The vertices used to compute the new size for the
     * AABB.
     * @param {Vec3} min - Stored computed min value.
     * @param {Vec3} max - Stored computed max value.
     * @param {number} [numVerts] - Number of vertices to use from the beginning of vertices array.
     * All vertices are used if not specified.
     */ static computeMinMax(vertices, min, max, numVerts = vertices.length / 3) {
        if (numVerts > 0) {
            let minx = vertices[0];
            let miny = vertices[1];
            let minz = vertices[2];
            let maxx = minx;
            let maxy = miny;
            let maxz = minz;
            const n = numVerts * 3;
            for(let i = 3; i < n; i += 3){
                const x = vertices[i];
                const y = vertices[i + 1];
                const z = vertices[i + 2];
                if (x < minx) minx = x;
                if (y < miny) miny = y;
                if (z < minz) minz = z;
                if (x > maxx) maxx = x;
                if (y > maxy) maxy = y;
                if (z > maxz) maxz = z;
            }
            min.set(minx, miny, minz);
            max.set(maxx, maxy, maxz);
        }
    }
    /**
     * Compute the size of the AABB to encapsulate all specified vertices.
     *
     * @param {number[]|Float32Array} vertices - The vertices used to compute the new size for the
     * AABB.
     * @param {number} [numVerts] - Number of vertices to use from the beginning of vertices array.
     * All vertices are used if not specified.
     */ compute(vertices, numVerts) {
        BoundingBox.computeMinMax(vertices, tmpVecA$1, tmpVecB$1, numVerts);
        this.setMinMax(tmpVecA$1, tmpVecB$1);
    }
    /**
     * Test if a Bounding Sphere is overlapping, enveloping, or inside this AABB.
     *
     * @param {BoundingSphere} sphere - Bounding Sphere to test.
     * @returns {boolean} True if the Bounding Sphere is overlapping, enveloping, or inside the
     * AABB and false otherwise.
     */ intersectsBoundingSphere(sphere) {
        const sq = this._distanceToBoundingSphereSq(sphere);
        if (sq <= sphere.radius * sphere.radius) {
            return true;
        }
        return false;
    }
    _distanceToBoundingSphereSq(sphere) {
        const boxMin = this.getMin();
        const boxMax = this.getMax();
        let sq = 0;
        const axis = [
            'x',
            'y',
            'z'
        ];
        for(let i = 0; i < 3; ++i){
            let out = 0;
            const pn = sphere.center[axis[i]];
            const bMin = boxMin[axis[i]];
            const bMax = boxMax[axis[i]];
            let val = 0;
            if (pn < bMin) {
                val = bMin - pn;
                out += val * val;
            }
            if (pn > bMax) {
                val = pn - bMax;
                out += val * val;
            }
            sq += out;
        }
        return sq;
    }
    _expand(expandMin, expandMax) {
        tmpVecA$1.add2(this.getMin(), expandMin);
        tmpVecB$1.add2(this.getMax(), expandMax);
        this.setMinMax(tmpVecA$1, tmpVecB$1);
    }
}

/**
 * @import { Ray } from './ray.js'
 */ const tmpVecA = new Vec3();
const tmpVecB = new Vec3();
/**
 * A bounding sphere is a volume for facilitating fast intersection testing.
 *
 * @category Math
 */ class BoundingSphere {
    /**
     * Creates a new BoundingSphere instance.
     *
     * @param {Vec3} [center] - The world space coordinate marking the center of the sphere. The
     * constructor takes a reference of this parameter.
     * @param {number} [radius] - The radius of the bounding sphere. Defaults to 0.5.
     * @example
     * // Create a new bounding sphere centered on the origin with a radius of 0.5
     * const sphere = new pc.BoundingSphere();
     */ constructor(center = new Vec3(), radius = 0.5){
        Debug.assert(!Object.isFrozen(center), 'The constructor of \'BoundingSphere\' does not accept a constant (frozen) object as a \'center\' parameter');
        this.center = center;
        this.radius = radius;
    }
    containsPoint(point) {
        const lenSq = tmpVecA.sub2(point, this.center).lengthSq();
        const r = this.radius;
        return lenSq < r * r;
    }
    /**
     * Test if a ray intersects with the sphere.
     *
     * @param {Ray} ray - Ray to test against (direction must be normalized).
     * @param {Vec3} [point] - If there is an intersection, the intersection point will be copied
     * into here.
     * @returns {boolean} True if there is an intersection.
     */ intersectsRay(ray, point) {
        const m = tmpVecA.copy(ray.origin).sub(this.center);
        const b = m.dot(tmpVecB.copy(ray.direction).normalize());
        const c = m.dot(m) - this.radius * this.radius;
        // exit if ray's origin outside of sphere (c > 0) and ray pointing away from s (b > 0)
        if (c > 0 && b > 0) {
            return false;
        }
        const discr = b * b - c;
        // a negative discriminant corresponds to ray missing sphere
        if (discr < 0) {
            return false;
        }
        // ray intersects sphere, compute smallest t value of intersection
        const t = Math.abs(-b - Math.sqrt(discr));
        // if t is negative, ray started inside sphere so clamp t to zero
        if (point) {
            point.copy(ray.direction).mulScalar(t).add(ray.origin);
        }
        return true;
    }
    /**
     * Test if a Bounding Sphere is overlapping, enveloping, or inside this Bounding Sphere.
     *
     * @param {BoundingSphere} sphere - Bounding Sphere to test.
     * @returns {boolean} True if the Bounding Sphere is overlapping, enveloping, or inside this Bounding Sphere and false otherwise.
     */ intersectsBoundingSphere(sphere) {
        tmpVecA.sub2(sphere.center, this.center);
        const totalRadius = sphere.radius + this.radius;
        if (tmpVecA.lengthSq() <= totalRadius * totalRadius) {
            return true;
        }
        return false;
    }
}

/**
 * @import { Ray } from './ray.js'
 */ /**
 * An infinite plane. Internally it's represented in a parametric equation form:
 * ax + by + cz + distance = 0.
 *
 * @category Math
 */ class Plane {
    /**
     * Create a new Plane instance.
     *
     * @param {Vec3} [normal] - Normal of the plane. The constructor copies this parameter. Defaults
     * to {@link Vec3.UP}.
     * @param {number} [distance] - The distance from the plane to the origin, along its normal.
     * Defaults to 0.
     */ constructor(normal = Vec3.UP, distance = 0){
        /**
     * The normal of the plane.
     *
     * @type {Vec3}
     */ this.normal = new Vec3();
        this.normal.copy(normal);
        this.distance = distance;
    }
    /**
     * Returns a clone of the specified plane.
     *
     * @returns {this} A duplicate plane.
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr().copy(this);
    }
    /**
     * Copies the contents of a source plane to a destination plane.
     *
     * @param {Plane} src - A source plane to copy to the destination plane.
     * @returns {Plane} Self for chaining.
     */ copy(src) {
        this.normal.copy(src.normal);
        this.distance = src.distance;
        return this;
    }
    /**
     * Test if the plane intersects between two points.
     *
     * @param {Vec3} start - Start position of line.
     * @param {Vec3} end - End position of line.
     * @param {Vec3} [point] - If there is an intersection, the intersection point will be copied
     * into here.
     * @returns {boolean} True if there is an intersection.
     */ intersectsLine(start, end, point) {
        const d = this.distance;
        const d0 = this.normal.dot(start) + d;
        const d1 = this.normal.dot(end) + d;
        const t = d0 / (d0 - d1);
        const intersects = t >= 0 && t <= 1;
        if (intersects && point) {
            point.lerp(start, end, t);
        }
        return intersects;
    }
    /**
     * Test if a ray intersects with the infinite plane.
     *
     * @param {Ray} ray - Ray to test against (direction must be normalized).
     * @param {Vec3} [point] - If there is an intersection, the intersection point will be copied
     * into here.
     * @returns {boolean} True if there is an intersection.
     */ intersectsRay(ray, point) {
        const denominator = this.normal.dot(ray.direction);
        if (denominator === 0) {
            return false;
        }
        const t = -(this.normal.dot(ray.origin) + this.distance) / denominator;
        if (t >= 0 && point) {
            point.copy(ray.direction).mulScalar(t).add(ray.origin);
        }
        return t >= 0;
    }
    /**
     * Normalize the plane.
     *
     * @returns {Plane} Self for chaining.
     */ normalize() {
        const invLength = 1 / this.normal.length();
        this.normal.mulScalar(invLength);
        this.distance *= invLength;
        return this;
    }
    /**
     * Sets the plane based on a normal and a distance from the origin.
     *
     * @param {number} nx - The x-component of the normal.
     * @param {number} ny - The y-component of the normal.
     * @param {number} nz - The z-component of the normal.
     * @param {number} d - The distance from the origin.
     * @returns {Plane} Self for chaining.
     */ set(nx, ny, nz, d) {
        this.normal.set(nx, ny, nz);
        this.distance = d;
        return this;
    }
    /**
     * Sets the plane based on a specified normal and a point on the plane.
     *
     * @param {Vec3} point - The point on the plane.
     * @param {Vec3} normal - The normal of the plane.
     * @returns {Plane} Self for chaining.
     */ setFromPointNormal(point, normal) {
        this.normal.copy(normal);
        this.distance = -this.normal.dot(point);
        return this;
    }
}

/**
 * @import { BoundingSphere } from './bounding-sphere.js'
 * @import { Mat4 } from '../math/mat4.js'
 * @import { Vec3 } from '../math/vec3.js'
 */ /**
 * A frustum is a shape that defines the viewing space of a camera. It can be used to determine
 * visibility of points and bounding spheres. Typically, you would not create a Frustum shape
 * directly, but instead query {@link CameraComponent#frustum}.
 *
 * @category Math
 */ class Frustum {
    /**
     * Create a new Frustum instance.
     *
     * @example
     * const frustum = new pc.Frustum();
     */ constructor(){
        /**
     * The six planes that make up the frustum.
     *
     * @type {Plane[]}
     */ this.planes = [];
        for(let i = 0; i < 6; i++){
            this.planes[i] = new Plane();
        }
    }
    /**
     * Returns a clone of the specified frustum.
     *
     * @returns {Frustum} A duplicate frustum.
     * @example
     * const frustum = new pc.Frustum();
     * const clone = frustum.clone();
     */ clone() {
        /** @type {this} */ const cstr = this.constructor;
        return new cstr().copy(this);
    }
    /**
     * Copies the contents of a source frustum to a destination frustum.
     *
     * @param {Frustum} src - A source frustum to copy to the destination frustum.
     * @returns {Frustum} Self for chaining.
     * @example
     * const src = entity.camera.frustum;
     * const dst = new pc.Frustum();
     * dst.copy(src);
     */ copy(src) {
        for(let i = 0; i < 6; i++){
            this.planes[i].copy(src.planes[i]);
        }
        return this;
    }
    /**
     * Updates the frustum shape based on the supplied 4x4 matrix.
     *
     * @param {Mat4} matrix - The matrix describing the shape of the frustum.
     * @example
     * // Create a perspective projection matrix
     * const projection = pc.Mat4();
     * projection.setPerspective(45, 16 / 9, 1, 1000);
     *
     * // Create a frustum shape that is represented by the matrix
     * const frustum = new pc.Frustum();
     * frustum.setFromMat4(projection);
     */ setFromMat4(matrix) {
        const [m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33] = matrix.data;
        const planes = this.planes;
        planes[0].set(m03 - m00, m13 - m10, m23 - m20, m33 - m30).normalize(); // RIGHT
        planes[1].set(m03 + m00, m13 + m10, m23 + m20, m33 + m30).normalize(); // LEFT
        planes[2].set(m03 + m01, m13 + m11, m23 + m21, m33 + m31).normalize(); // BOTTOM
        planes[3].set(m03 - m01, m13 - m11, m23 - m21, m33 - m31).normalize(); // TOP
        planes[4].set(m03 - m02, m13 - m12, m23 - m22, m33 - m32).normalize(); // FAR
        planes[5].set(m03 + m02, m13 + m12, m23 + m22, m33 + m32).normalize(); // NEAR
    }
    /**
     * Tests whether a point is inside the frustum. Note that points lying in a frustum plane are
     * considered to be outside the frustum.
     *
     * @param {Vec3} point - The point to test.
     * @returns {boolean} True if the point is inside the frustum, false otherwise.
     */ containsPoint(point) {
        for(let p = 0; p < 6; p++){
            const { normal, distance } = this.planes[p];
            if (normal.dot(point) + distance <= 0) {
                return false;
            }
        }
        return true;
    }
    /**
     * Tests whether a bounding sphere intersects the frustum. If the sphere is outside the
     * frustum, zero is returned. If the sphere intersects the frustum, 1 is returned. If the
     * sphere is completely inside the frustum, 2 is returned. Note that a sphere touching a
     * frustum plane from the outside is considered to be outside the frustum.
     *
     * @param {BoundingSphere} sphere - The sphere to test.
     * @returns {number} 0 if the bounding sphere is outside the frustum, 1 if it intersects the
     * frustum and 2 if it is contained by the frustum.
     */ containsSphere(sphere) {
        const { center, radius } = sphere;
        let c = 0;
        for(let p = 0; p < 6; p++){
            const { normal, distance } = this.planes[p];
            const d = normal.dot(center) + distance;
            if (d <= -radius) {
                return 0;
            }
            if (d > radius) {
                c++;
            }
        }
        return c === 6 ? 2 : 1;
    }
}

/**
 * An infinite ray.
 *
 * @category Math
 */ class Ray {
    /**
     * Creates a new Ray instance. The ray is infinite, starting at a given origin and pointing in
     * a given direction.
     *
     * @param {Vec3} [origin] - The starting point of the ray. The constructor copies
     * this parameter. Defaults to the origin (0, 0, 0).
     * @param {Vec3} [direction] - The direction of the ray. The constructor copies
     * this parameter. Defaults to a direction down the world negative Z axis (0, 0, -1).
     * @example
     * // Create a new ray starting at the position of this entity and pointing down
     * // the entity's negative Z axis
     * const ray = new pc.Ray(this.entity.getPosition(), this.entity.forward);
     */ constructor(origin, direction){
        /**
     * The starting point of the ray.
     *
     * @readonly
     * @type {Vec3}
     */ this.origin = new Vec3();
        /**
     * The direction of the ray.
     *
     * @readonly
     * @type {Vec3}
     */ this.direction = Vec3.FORWARD.clone();
        if (origin) {
            this.origin.copy(origin);
        }
        if (direction) {
            this.direction.copy(direction);
        }
    }
    /**
     * Sets origin and direction to the supplied vector values.
     *
     * @param {Vec3} origin - The starting point of the ray.
     * @param {Vec3} direction - The direction of the ray.
     * @returns {Ray} Self for chaining.
     */ set(origin, direction) {
        this.origin.copy(origin);
        this.direction.copy(direction);
        return this;
    }
    /**
     * Copies the contents of a source Ray.
     *
     * @param {Ray} src - The Ray to copy from.
     * @returns {Ray} Self for chaining.
     */ copy(src) {
        return this.set(src.origin, src.direction);
    }
    /**
     * Returns a clone of the Ray.
     *
     * @returns {this} A duplicate Ray.
     */ clone() {
        return new this.constructor(this.origin, this.direction);
    }
}

/**
 * @import { Ray } from './ray.js'
 */ const e1 = new Vec3();
const e2 = new Vec3();
const h = new Vec3();
const s = new Vec3();
const q$4 = new Vec3();
// constants
const EPSILON = 1e-6;
/**
 * A triangle defined by three {@link Vec3} vectors.
 *
 * @category Math
 */ class Tri {
    /**
     * Creates a new Tri object.
     *
     * @param {Vec3} [v0] - The first 3-dimensional vector.
     * @param {Vec3} [v1] - The second 3-dimensional vector.
     * @param {Vec3} [v2] - The third 3-dimensional vector.
     * @example
     * const v0 = new pc.Vec3(1, 0, 0);
     * const v1 = new pc.Vec3(0, 1, 0);
     * const v2 = new pc.Vec3(2, 2, 1);
     * const t = new pc.Tri(v0, v1, v2);
     */ constructor(v0 = Vec3.ZERO, v1 = Vec3.ZERO, v2 = Vec3.ZERO){
        /**
     * The first 3-dimensional vector of the triangle.
     *
     * @readonly
     * @type {Vec3}
     */ this.v0 = new Vec3();
        /**
     * The second 3-dimensional vector of the triangle.
     *
     * @type {Vec3}
     * @readonly
     */ this.v1 = new Vec3();
        /**
     * The third 3-dimensional vector of the triangle.
     *
     * @type {Vec3}
     * @readonly
     */ this.v2 = new Vec3();
        this.set(v0, v1, v2);
    }
    /**
     * Sets the specified triangle to the supplied 3-dimensional vectors.
     *
     * @param {Vec3} v0 - The value set on the first 3-dimensional vector of the triangle.
     * @param {Vec3} v1 - The value set on the second 3-dimensional vector of the triangle.
     * @param {Vec3} v2 - The value set on the third 3-dimensional vector of the triangle.
     * @returns {Tri} Self for chaining
     * @example
     * const t = new pc.Tri(pc.Vec3.UP, pc.Vec3.RIGHT, pc.Vec3.BACK);
     * const v0 = new pc.Vec3(1, 0, 0);
     * const v1 = new pc.Vec3(0, 1, 0);
     * const v2 = new pc.Vec3(2, 2, 1);
     * t.set(v0, v1, v2);
     *
     * // Outputs [[1, 0, 0], [0, 1, 0], [2, 2, 1]]
     * console.log("The result of the triangle set is: " + t.toString());
     */ set(v0, v1, v2) {
        this.v0.copy(v0);
        this.v1.copy(v1);
        this.v2.copy(v2);
        return this;
    }
    /**
     * Test if a ray intersects with the triangle.
     *
     * @param {Ray} ray - Ray to test against (direction must be normalized).
     * @param {Vec3} [point] - If there is an intersection, the intersection point will be copied
     * into here.
     * @returns {boolean} True if there is an intersection.
     */ intersectsRay(ray, point) {
        e1.sub2(this.v1, this.v0);
        e2.sub2(this.v2, this.v0);
        h.cross(ray.direction, e2);
        const a = e1.dot(h);
        if (a > -1e-6 && a < EPSILON) {
            return false;
        }
        const f = 1 / a;
        s.sub2(ray.origin, this.v0);
        const u = f * s.dot(h);
        if (u < 0 || u > 1) {
            return false;
        }
        q$4.cross(s, e1);
        const v = f * ray.direction.dot(q$4);
        if (v < 0 || u + v > 1) {
            return false;
        }
        const t = f * e2.dot(q$4);
        if (t > EPSILON) {
            if (point instanceof Vec3) {
                point.copy(ray.direction).mulScalar(t).add(ray.origin);
            }
            return true;
        }
        return false;
    }
    /**
     * Converts the specified triangle to string form.
     *
     * @returns {string} The triangle in string form.
     * @example
     * const t = new pc.Tri(pc.Vec3.UP, pc.Vec3.RIGHT, pc.Vec3.BACK);
     * // Outputs [[0, 1, 0], [1, 0, 0], [0, 0, 1]]
     * console.log(t.toString());
     */ toString() {
        return `[${this.v0.toString()}, ${this.v1.toString()}, ${this.v2.toString()}]`;
    }
}

/**
 * Ignores the integer part of texture coordinates, using only the fractional part.
 *
 * @category Graphics
 */ const ADDRESS_REPEAT = 0;
/**
 * Clamps texture coordinate to the range 0 to 1.
 *
 * @category Graphics
 */ const ADDRESS_CLAMP_TO_EDGE = 1;
/**
 * Texture coordinate to be set to the fractional part if the integer part is even. If the integer
 * part is odd, then the texture coordinate is set to 1 minus the fractional part.
 *
 * @category Graphics
 */ const ADDRESS_MIRRORED_REPEAT = 2;
/**
 * Multiply all fragment components by zero.
 *
 * @category Graphics
 */ const BLENDMODE_ZERO = 0;
/**
 * Multiply all fragment components by one.
 *
 * @category Graphics
 */ const BLENDMODE_ONE = 1;
/**
 * Multiply all fragment components by the components of the source fragment.
 *
 * @category Graphics
 */ const BLENDMODE_SRC_COLOR = 2;
/**
 * Multiply all fragment components by the components of the destination fragment.
 *
 * @category Graphics
 */ const BLENDMODE_DST_COLOR = 4;
/**
 * Multiply all fragment components by one minus the components of the destination fragment.
 *
 * @category Graphics
 */ const BLENDMODE_ONE_MINUS_DST_COLOR = 5;
/**
 * Multiply all fragment components by the alpha value of the source fragment.
 *
 * @category Graphics
 */ const BLENDMODE_SRC_ALPHA = 6;
/**
 * Multiply all fragment components by one minus the alpha value of the source fragment.
 *
 * @category Graphics
 */ const BLENDMODE_ONE_MINUS_SRC_ALPHA = 8;
/**
 * Add the results of the source and destination fragment multiplies.
 *
 * @category Graphics
 */ const BLENDEQUATION_ADD = 0;
/**
 * Reverse and subtract the results of the source and destination fragment multiplies.
 *
 * @category Graphics
 */ const BLENDEQUATION_REVERSE_SUBTRACT = 2;
/**
 * Use the smallest value.
 *
 * @category Graphics
 */ const BLENDEQUATION_MIN = 3;
/**
 * Use the largest value.
 *
 * @category Graphics
 */ const BLENDEQUATION_MAX = 4;
/**
 * A flag utilized during the construction of a {@link StorageBuffer} to make it available for read
 * access by CPU.
 *
 * @category Graphics
 */ const BUFFERUSAGE_READ = 0x0001;
/**
 * A flag utilized during the construction of a {@link StorageBuffer} to ensure its compatibility
 * when used as a destination of a copy operation, or as a target of a write operation.
 *
 * @category Graphics
 */ const BUFFERUSAGE_COPY_DST = 0x0008;
/**
 * A flag utilized during the construction of a {@link StorageBuffer} to ensure its compatibility
 * when used as an index buffer.
 *
 * @category Graphics
 */ const BUFFERUSAGE_INDEX = 0x0010;
/**
 * A flag utilized during the construction of a {@link StorageBuffer} to ensure its compatibility
 * when used as a vertex buffer.
 *
 * @category Graphics
 */ const BUFFERUSAGE_VERTEX = 0x0020;
/**
 * A flag utilized during the construction of a {@link StorageBuffer} to ensure its compatibility
 * when used as an uniform buffer.
 *
 * @category Graphics
 */ const BUFFERUSAGE_UNIFORM = 0x0040;
/**
 * An internal flag utilized during the construction of a {@link StorageBuffer} to ensure its
 * compatibility when used as a storage buffer.
 * This flag is hidden as it's automatically used by the StorageBuffer constructor.
 *
 * @category Graphics
 * @ignore
 */ const BUFFERUSAGE_STORAGE = 0x0080;
/**
 * A flag utilized during the construction of a {@link StorageBuffer} to allow it to store indirect
 * command arguments.
 * TODO: This flag is hidden till the feature is implemented.
 *
 * @category Graphics
 * @ignore
 */ const BUFFERUSAGE_INDIRECT = 0x0100;
/**
 * The data store contents will be modified once and used many times.
 *
 * @category Graphics
 */ const BUFFER_STATIC = 0;
/**
 * The data store contents will be modified repeatedly and used many times.
 *
 * @category Graphics
 */ const BUFFER_DYNAMIC = 1;
/**
 * The data store contents will be modified once and used at most a few times.
 *
 * @category Graphics
 */ const BUFFER_STREAM = 2;
/**
 * The data store contents will be modified repeatedly on the GPU and used many times. Optimal for
 * transform feedback usage.
 *
 * @category Graphics
 */ const BUFFER_GPUDYNAMIC = 3;
/**
 * Clear the color buffer.
 *
 * @category Graphics
 */ const CLEARFLAG_COLOR = 1;
/**
 * Clear the depth buffer.
 *
 * @category Graphics
 */ const CLEARFLAG_DEPTH = 2;
/**
 * Clear the stencil buffer.
 *
 * @category Graphics
 */ const CLEARFLAG_STENCIL = 4;
/**
 * No triangles are culled.
 *
 * @category Graphics
 */ const CULLFACE_NONE = 0;
/**
 * Triangles facing away from the view direction are culled.
 *
 * @category Graphics
 */ const CULLFACE_BACK = 1;
/**
 * Triangles facing the view direction are culled.
 *
 * @category Graphics
 */ const CULLFACE_FRONT = 2;
/**
 * Point sample filtering.
 *
 * @category Graphics
 */ const FILTER_NEAREST = 0;
/**
 * Bilinear filtering.
 *
 * @category Graphics
 */ const FILTER_LINEAR = 1;
/**
 * Use the nearest neighbor in the nearest mipmap level.
 *
 * @category Graphics
 */ const FILTER_NEAREST_MIPMAP_NEAREST = 2;
/**
 * Linearly interpolate in the nearest mipmap level.
 *
 * @category Graphics
 */ const FILTER_NEAREST_MIPMAP_LINEAR = 3;
/**
 * Use the nearest neighbor after linearly interpolating between mipmap levels.
 *
 * @category Graphics
 */ const FILTER_LINEAR_MIPMAP_NEAREST = 4;
/**
 * Linearly interpolate both the mipmap levels and between texels.
 *
 * @category Graphics
 */ const FILTER_LINEAR_MIPMAP_LINEAR = 5;
/**
 * Pass if (ref & mask) < (stencil & mask).
 *
 * @category Graphics
 */ const FUNC_LESS = 1;
/**
 * Pass if (ref & mask) <= (stencil & mask).
 *
 * @category Graphics
 */ const FUNC_LESSEQUAL = 3;
/**
 * Always pass.
 *
 * @category Graphics
 */ const FUNC_ALWAYS = 7;
/**
 * 8-bit unsigned vertex indices (0 to 255).
 *
 * @category Graphics
 */ const INDEXFORMAT_UINT8 = 0;
/**
 * 16-bit unsigned vertex indices (0 to 65,535).
 *
 * @category Graphics
 */ const INDEXFORMAT_UINT16 = 1;
/**
 * 32-bit unsigned vertex indices (0 to 4,294,967,295).
 *
 * @category Graphics
 */ const INDEXFORMAT_UINT32 = 2;
const PIXELFORMAT_A8 = 0;
const PIXELFORMAT_L8 = 1;
const PIXELFORMAT_LA8 = 2;
/**
 * 16-bit RGB (5-bits for red channel, 6 for green and 5 for blue).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGB565 = 3;
/**
 * 16-bit RGBA (5-bits for red channel, 5 for green, 5 for blue with 1-bit alpha).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA5551 = 4;
/**
 * 16-bit RGBA (4-bits for red channel, 4 for green, 4 for blue with 4-bit alpha).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA4 = 5;
/**
 * 24-bit RGB (8-bits for red channel, 8 for green and 8 for blue).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGB8 = 6;
/**
 * 32-bit RGBA (8-bits for red channel, 8 for green, 8 for blue with 8-bit alpha).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA8 = 7;
/**
 * Block compressed format storing 16 input pixels in 64 bits of output, consisting of two 16-bit
 * RGB 5:6:5 color values and a 4x4 two bit lookup table.
 *
 * @category Graphics
 */ const PIXELFORMAT_DXT1 = 8;
/**
 * Block compressed format storing 16 input pixels (corresponding to a 4x4 pixel block) into 128
 * bits of output, consisting of 64 bits of alpha channel data (4 bits for each pixel) followed by
 * 64 bits of color data; encoded the same way as DXT1.
 *
 * @category Graphics
 */ const PIXELFORMAT_DXT3 = 9;
/**
 * Block compressed format storing 16 input pixels into 128 bits of output, consisting of 64 bits
 * of alpha channel data (two 8 bit alpha values and a 4x4 3 bit lookup table) followed by 64 bits
 * of color data (encoded the same way as DXT1).
 *
 * @category Graphics
 */ const PIXELFORMAT_DXT5 = 10;
/**
 * 16-bit floating point RGB (16-bit float for each red, green and blue channels).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGB16F = 11;
/**
 * 16-bit floating point RGBA (16-bit float for each red, green, blue and alpha channels).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA16F = 12;
/**
 * 32-bit floating point RGB (32-bit float for each red, green and blue channels).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGB32F = 13;
/**
 * 32-bit floating point RGBA (32-bit float for each red, green, blue and alpha channels).
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA32F = 14;
/**
 * 32-bit floating point single channel format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R32F = 15;
/**
 * A readable depth buffer format.
 *
 * @category Graphics
 */ const PIXELFORMAT_DEPTH = 16;
/**
 * A readable depth/stencil buffer format.
 *
 * @category Graphics
 */ const PIXELFORMAT_DEPTHSTENCIL = 17;
/**
 * A floating-point color-only format with 11 bits for red and green channels and 10 bits for the
 * blue channel.
 *
 * @category Graphics
 */ const PIXELFORMAT_111110F = 18;
/**
 * Color-only sRGB format.
 *
 * @category Graphics
 */ const PIXELFORMAT_SRGB8 = 19;
/**
 * Color sRGB format with additional alpha channel.
 *
 * @category Graphics
 */ const PIXELFORMAT_SRGBA8 = 20;
/**
 * ETC1 compressed format.
 *
 * @category Graphics
 */ const PIXELFORMAT_ETC1 = 21;
/**
 * ETC2 (RGB) compressed format.
 *
 * @category Graphics
 */ const PIXELFORMAT_ETC2_RGB = 22;
/**
 * ETC2 (RGBA) compressed format.
 *
 * @category Graphics
 */ const PIXELFORMAT_ETC2_RGBA = 23;
/**
 * PVRTC (2BPP RGB) compressed format.
 *
 * @category Graphics
 */ const PIXELFORMAT_PVRTC_2BPP_RGB_1 = 24;
/**
 * PVRTC (2BPP RGBA) compressed format.
 *
 * @category Graphics
 */ const PIXELFORMAT_PVRTC_2BPP_RGBA_1 = 25;
/**
 * PVRTC (4BPP RGB) compressed format.
 *
 * @category Graphics
 */ const PIXELFORMAT_PVRTC_4BPP_RGB_1 = 26;
/**
 * PVRTC (4BPP RGBA) compressed format.
 *
 * @category Graphics
 */ const PIXELFORMAT_PVRTC_4BPP_RGBA_1 = 27;
/**
 * ATC compressed format with alpha channel in blocks of 4x4.
 *
 * @category Graphics
 */ const PIXELFORMAT_ASTC_4x4 = 28;
/**
 * ATC compressed format with no alpha channel.
 *
 * @category Graphics
 */ const PIXELFORMAT_ATC_RGB = 29;
/**
 * ATC compressed format with alpha channel.
 *
 * @category Graphics
 */ const PIXELFORMAT_ATC_RGBA = 30;
/**
 * 32-bit BGRA (8-bits for blue channel, 8 for green, 8 for red with 8-bit alpha). This is an
 * internal format used by the WebGPU's backbuffer only.
 *
 * @ignore
 * @category Graphics
 */ const PIXELFORMAT_BGRA8 = 31;
/**
 * 8-bit signed integer single-channel (R) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R8I = 32;
/**
 * 8-bit unsigned integer single-channel (R) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R8U = 33;
/**
 * 16-bit signed integer single-channel (R) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R16I = 34;
/**
 * 16-bit unsigned integer single-channel (R) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R16U = 35;
/**
 * 32-bit signed integer single-channel (R) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R32I = 36;
/**
 * 32-bit unsigned integer single-channel (R) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R32U = 37;
/**
 * 8-bit per-channel signed integer (RG) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RG8I = 38;
/**
 * 8-bit per-channel unsigned integer (RG) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RG8U = 39;
/**
 * 16-bit per-channel signed integer (RG) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RG16I = 40;
/**
 * 16-bit per-channel unsigned integer (RG) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RG16U = 41;
/**
 * 32-bit per-channel signed integer (RG) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RG32I = 42;
/**
 * 32-bit per-channel unsigned integer (RG) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RG32U = 43;
/**
 * 8-bit per-channel signed integer (RGBA) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA8I = 44;
/**
 * 8-bit per-channel unsigned integer (RGBA) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA8U = 45;
/**
 * 16-bit per-channel signed integer (RGBA) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA16I = 46;
/**
 * 16-bit per-channel unsigned integer (RGBA) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA16U = 47;
/**
 * 32-bit per-channel signed integer (RGBA) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA32I = 48;
/**
 * 32-bit per-channel unsigned integer (RGBA) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RGBA32U = 49;
/**
 * 16-bit floating point R (16-bit float for red channel).
 *
 * @category Graphics
 */ const PIXELFORMAT_R16F = 50;
/**
 * 16-bit floating point RG (16-bit float for each red and green channels).
 *
 * @category Graphics
 */ const PIXELFORMAT_RG16F = 51;
/**
 * 8-bit per-channel (R) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_R8 = 52;
/**
 * 8-bit per-channel (RG) format.
 *
 * @category Graphics
 */ const PIXELFORMAT_RG8 = 53;
/**
 * Format equivalent to {@link PIXELFORMAT_DXT1} but sampled in linear color space.
 *
 * @category Graphics
 */ const PIXELFORMAT_DXT1_SRGB = 54;
/**
 * Format equivalent to {@link PIXELFORMAT_DXT3} but sampled in linear color space.
 *
 * @category Graphics
 */ const PIXELFORMAT_DXT3_SRGBA = 55;
/**
 * Format equivalent to {@link PIXELFORMAT_DXT5} but sampled in linear color space.
 *
 * @category Graphics
 */ const PIXELFORMAT_DXT5_SRGBA = 56;
/**
 * Format equivalent to {@link PIXELFORMAT_ETC2_RGB} but sampled in linear color space.
 *
 * @category Graphics
 */ const PIXELFORMAT_ETC2_SRGB = 61;
/**
 * Format equivalent to {@link PIXELFORMAT_ETC2_RGBA} but sampled in linear color space.
 *
 * @category Graphics
 */ const PIXELFORMAT_ETC2_SRGBA = 62;
/**
 * Format equivalent to {@link PIXELFORMAT_ASTC_4x4} but sampled in linear color space.
 *
 * @category Graphics
 */ const PIXELFORMAT_ASTC_4x4_SRGB = 63;
/**
 * 32-bit BGRA sRGB format. This is an internal format used by the WebGPU's backbuffer only.
 *
 * @ignore
 * @category Graphics
 */ const PIXELFORMAT_SBGRA8 = 64;
/**
 * Compressed high dynamic range signed floating point format storing RGB values.
 *
 * @category Graphics
 */ const PIXELFORMAT_BC6F = 65;
/**
 * Compressed high dynamic range unsigned floating point format storing RGB values.
 *
 * @category Graphics
 */ const PIXELFORMAT_BC6UF = 66;
/**
 * Compressed 8-bit fixed-point data. Each 4x4 block of texels consists of 128 bits of RGBA data.
 *
 * @category Graphics
 */ const PIXELFORMAT_BC7 = 67;
/**
 * Compressed 8-bit fixed-point data. Each 4x4 block of texels consists of 128 bits of SRGB_ALPHA
 * data.
 *
 * @category Graphics
 */ const PIXELFORMAT_BC7_SRGBA = 68;
/**
 * A 16-bit depth buffer format.
 *
 * @category Graphics
 */ const PIXELFORMAT_DEPTH16 = 69;
/**
 * Information about pixel formats.
 *
 * ldr: whether the format is low dynamic range (LDR), which typically means it's not HDR, and uses
 * sRGB color space to store the color values
 * srgbFormat: the corresponding sRGB format (which automatically converts the sRGB value to linear)
 *
 * @type {Map<number, { name: string, size?: number, blockSize?: number, ldr?: boolean, srgb?: boolean, srgbFormat?: number, isInt?: boolean }>}
 * @ignore
 */ const pixelFormatInfo = new Map([
    // float formats
    [
        PIXELFORMAT_A8,
        {
            name: 'A8',
            size: 1,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_R8,
        {
            name: 'R8',
            size: 1,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_L8,
        {
            name: 'L8',
            size: 1,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_LA8,
        {
            name: 'LA8',
            size: 2,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_RG8,
        {
            name: 'RG8',
            size: 2,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_RGB565,
        {
            name: 'RGB565',
            size: 2,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_RGBA5551,
        {
            name: 'RGBA5551',
            size: 2,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_RGBA4,
        {
            name: 'RGBA4',
            size: 2,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_RGB8,
        {
            name: 'RGB8',
            size: 4,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_RGBA8,
        {
            name: 'RGBA8',
            size: 4,
            ldr: true,
            srgbFormat: PIXELFORMAT_SRGBA8
        }
    ],
    [
        PIXELFORMAT_R16F,
        {
            name: 'R16F',
            size: 2
        }
    ],
    [
        PIXELFORMAT_RG16F,
        {
            name: 'RG16F',
            size: 4
        }
    ],
    [
        PIXELFORMAT_RGB16F,
        {
            name: 'RGB16F',
            size: 8
        }
    ],
    [
        PIXELFORMAT_RGBA16F,
        {
            name: 'RGBA16F',
            size: 8
        }
    ],
    [
        PIXELFORMAT_RGB32F,
        {
            name: 'RGB32F',
            size: 16
        }
    ],
    [
        PIXELFORMAT_RGBA32F,
        {
            name: 'RGBA32F',
            size: 16
        }
    ],
    [
        PIXELFORMAT_R32F,
        {
            name: 'R32F',
            size: 4
        }
    ],
    [
        PIXELFORMAT_DEPTH,
        {
            name: 'DEPTH',
            size: 4
        }
    ],
    [
        PIXELFORMAT_DEPTH16,
        {
            name: 'DEPTH16',
            size: 2
        }
    ],
    [
        PIXELFORMAT_DEPTHSTENCIL,
        {
            name: 'DEPTHSTENCIL',
            size: 4
        }
    ],
    [
        PIXELFORMAT_111110F,
        {
            name: '111110F',
            size: 4
        }
    ],
    [
        PIXELFORMAT_SRGB8,
        {
            name: 'SRGB8',
            size: 4,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_SRGBA8,
        {
            name: 'SRGBA8',
            size: 4,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_BGRA8,
        {
            name: 'BGRA8',
            size: 4,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_SBGRA8,
        {
            name: 'SBGRA8',
            size: 4,
            ldr: true,
            srgb: true
        }
    ],
    // compressed formats
    [
        PIXELFORMAT_DXT1,
        {
            name: 'DXT1',
            blockSize: 8,
            ldr: true,
            srgbFormat: PIXELFORMAT_DXT1_SRGB
        }
    ],
    [
        PIXELFORMAT_DXT3,
        {
            name: 'DXT3',
            blockSize: 16,
            ldr: true,
            srgbFormat: PIXELFORMAT_DXT3_SRGBA
        }
    ],
    [
        PIXELFORMAT_DXT5,
        {
            name: 'DXT5',
            blockSize: 16,
            ldr: true,
            srgbFormat: PIXELFORMAT_DXT5_SRGBA
        }
    ],
    [
        PIXELFORMAT_ETC1,
        {
            name: 'ETC1',
            blockSize: 8,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_ETC2_RGB,
        {
            name: 'ETC2_RGB',
            blockSize: 8,
            ldr: true,
            srgbFormat: PIXELFORMAT_ETC2_SRGB
        }
    ],
    [
        PIXELFORMAT_ETC2_RGBA,
        {
            name: 'ETC2_RGBA',
            blockSize: 16,
            ldr: true,
            srgbFormat: PIXELFORMAT_ETC2_SRGBA
        }
    ],
    [
        PIXELFORMAT_PVRTC_2BPP_RGB_1,
        {
            name: 'PVRTC_2BPP_RGB_1',
            ldr: true,
            blockSize: 8
        }
    ],
    [
        PIXELFORMAT_PVRTC_2BPP_RGBA_1,
        {
            name: 'PVRTC_2BPP_RGBA_1',
            ldr: true,
            blockSize: 8
        }
    ],
    [
        PIXELFORMAT_PVRTC_4BPP_RGB_1,
        {
            name: 'PVRTC_4BPP_RGB_1',
            ldr: true,
            blockSize: 8
        }
    ],
    [
        PIXELFORMAT_PVRTC_4BPP_RGBA_1,
        {
            name: 'PVRTC_4BPP_RGBA_1',
            ldr: true,
            blockSize: 8
        }
    ],
    [
        PIXELFORMAT_ASTC_4x4,
        {
            name: 'ASTC_4x4',
            blockSize: 16,
            ldr: true,
            srgbFormat: PIXELFORMAT_ASTC_4x4_SRGB
        }
    ],
    [
        PIXELFORMAT_ATC_RGB,
        {
            name: 'ATC_RGB',
            blockSize: 8,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_ATC_RGBA,
        {
            name: 'ATC_RGBA',
            blockSize: 16,
            ldr: true
        }
    ],
    [
        PIXELFORMAT_BC6F,
        {
            name: 'BC6H_RGBF',
            blockSize: 16
        }
    ],
    [
        PIXELFORMAT_BC6UF,
        {
            name: 'BC6H_RGBUF',
            blockSize: 16
        }
    ],
    [
        PIXELFORMAT_BC7,
        {
            name: 'BC7_RGBA',
            blockSize: 16,
            ldr: true,
            srgbFormat: PIXELFORMAT_BC7_SRGBA
        }
    ],
    // compressed sRGB formats
    [
        PIXELFORMAT_DXT1_SRGB,
        {
            name: 'DXT1_SRGB',
            blockSize: 8,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_DXT3_SRGBA,
        {
            name: 'DXT3_SRGBA',
            blockSize: 16,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_DXT5_SRGBA,
        {
            name: 'DXT5_SRGBA',
            blockSize: 16,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_ETC2_SRGB,
        {
            name: 'ETC2_SRGB',
            blockSize: 8,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_ETC2_SRGBA,
        {
            name: 'ETC2_SRGBA',
            blockSize: 16,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_ASTC_4x4_SRGB,
        {
            name: 'ASTC_4x4_SRGB',
            blockSize: 16,
            ldr: true,
            srgb: true
        }
    ],
    [
        PIXELFORMAT_BC7_SRGBA,
        {
            name: 'BC7_SRGBA',
            blockSize: 16,
            ldr: true,
            srgb: true
        }
    ],
    // integer formats
    [
        PIXELFORMAT_R8I,
        {
            name: 'R8I',
            size: 1,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_R8U,
        {
            name: 'R8U',
            size: 1,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_R16I,
        {
            name: 'R16I',
            size: 2,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_R16U,
        {
            name: 'R16U',
            size: 2,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_R32I,
        {
            name: 'R32I',
            size: 4,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_R32U,
        {
            name: 'R32U',
            size: 4,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RG8I,
        {
            name: 'RG8I',
            size: 2,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RG8U,
        {
            name: 'RG8U',
            size: 2,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RG16I,
        {
            name: 'RG16I',
            size: 4,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RG16U,
        {
            name: 'RG16U',
            size: 4,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RG32I,
        {
            name: 'RG32I',
            size: 8,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RG32U,
        {
            name: 'RG32U',
            size: 8,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RGBA8I,
        {
            name: 'RGBA8I',
            size: 4,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RGBA8U,
        {
            name: 'RGBA8U',
            size: 4,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RGBA16I,
        {
            name: 'RGBA16I',
            size: 8,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RGBA16U,
        {
            name: 'RGBA16U',
            size: 8,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RGBA32I,
        {
            name: 'RGBA32I',
            size: 16,
            isInt: true
        }
    ],
    [
        PIXELFORMAT_RGBA32U,
        {
            name: 'RGBA32U',
            size: 16,
            isInt: true
        }
    ]
]);
// update this function when exposing additional compressed pixel formats
const isCompressedPixelFormat = (format)=>{
    return pixelFormatInfo.get(format)?.blockSize !== undefined;
};
const isSrgbPixelFormat = (format)=>{
    return pixelFormatInfo.get(format)?.srgb === true;
};
const isIntegerPixelFormat = (format)=>{
    return pixelFormatInfo.get(format)?.isInt === true;
};
/**
 * Returns the srgb equivalent format for the supplied linear format. If it does not exist, the input
 * format is returned. For example for {@link PIXELFORMAT_RGBA8} the return value is
 * {@link PIXELFORMAT_SRGBA8}.
 *
 * @param {number} format - The texture format.
 * @returns {number} The format allowing linear sampling of the texture.
 * @ignore
 */ const pixelFormatLinearToGamma = (format)=>{
    return pixelFormatInfo.get(format)?.srgbFormat || format;
};
/**
 * Returns the linear equivalent format for the supplied sRGB format. If it does not exist, the input
 * format is returned. For example for {@link PIXELFORMAT_SRGBA8} the return value is
 * {@link PIXELFORMAT_RGBA8}.
 *
 * @param {number} format - The texture format.
 * @returns {number} The equivalent format without automatic sRGB conversion.
 * @ignore
 */ const pixelFormatGammaToLinear = (format)=>{
    for (const [key, value] of pixelFormatInfo){
        if (value.srgbFormat === format) {
            return key;
        }
    }
    return format;
};
/**
 * For a pixel format that stores color information, this function returns true if the texture
 * sample is in sRGB space and needs to be decoded to linear space.
 *
 * @param {number} format - The texture format.
 * @returns {boolean} Whether sampling the texture with this format returns a sRGB value.
 * @ignore
 */ const requiresManualGamma = (format)=>{
    const info = pixelFormatInfo.get(format);
    return !!(info?.ldr && !info?.srgb);
};
// get the pixel format array type
const getPixelFormatArrayType = (format)=>{
    switch(format){
        case PIXELFORMAT_R32F:
        case PIXELFORMAT_RGB32F:
        case PIXELFORMAT_RGBA32F:
            return Float32Array;
        case PIXELFORMAT_R32I:
        case PIXELFORMAT_RG32I:
        case PIXELFORMAT_RGBA32I:
            return Int32Array;
        case PIXELFORMAT_R32U:
        case PIXELFORMAT_RG32U:
        case PIXELFORMAT_RGBA32U:
            return Uint32Array;
        case PIXELFORMAT_R16I:
        case PIXELFORMAT_RG16I:
        case PIXELFORMAT_RGBA16I:
            return Int16Array;
        case PIXELFORMAT_RG8:
        case PIXELFORMAT_R16U:
        case PIXELFORMAT_RG16U:
        case PIXELFORMAT_RGBA16U:
        case PIXELFORMAT_RGB565:
        case PIXELFORMAT_RGBA5551:
        case PIXELFORMAT_RGBA4:
        case PIXELFORMAT_R16F:
        case PIXELFORMAT_RG16F:
        case PIXELFORMAT_RGB16F:
        case PIXELFORMAT_RGBA16F:
            return Uint16Array;
        case PIXELFORMAT_R8I:
        case PIXELFORMAT_RG8I:
        case PIXELFORMAT_RGBA8I:
            return Int8Array;
        default:
            return Uint8Array;
    }
};
/**
 * List of distinct points.
 *
 * @category Graphics
 */ const PRIMITIVE_POINTS = 0;
/**
 * Discrete list of line segments.
 *
 * @category Graphics
 */ const PRIMITIVE_LINES = 1;
/**
 * List of points that are linked sequentially by line segments, with a closing line segment
 * between the last and first points.
 *
 * @category Graphics
 */ const PRIMITIVE_LINELOOP = 2;
/**
 * List of points that are linked sequentially by line segments.
 *
 * @category Graphics
 */ const PRIMITIVE_LINESTRIP = 3;
/**
 * Discrete list of triangles.
 *
 * @category Graphics
 */ const PRIMITIVE_TRIANGLES = 4;
/**
 * Connected strip of triangles where a specified vertex forms a triangle using the previous two.
 *
 * @category Graphics
 */ const PRIMITIVE_TRISTRIP = 5;
/**
 * Connected fan of triangles where the first vertex forms triangles with the following pairs of vertices.
 *
 * @category Graphics
 */ const PRIMITIVE_TRIFAN = 6;
/**
 * Vertex attribute to be treated as a position.
 *
 * @category Graphics
 */ const SEMANTIC_POSITION = 'POSITION';
/**
 * Vertex attribute to be treated as a normal.
 *
 * @category Graphics
 */ const SEMANTIC_NORMAL = 'NORMAL';
/**
 * Vertex attribute to be treated as a tangent.
 *
 * @category Graphics
 */ const SEMANTIC_TANGENT = 'TANGENT';
/**
 * Vertex attribute to be treated as skin blend weights.
 *
 * @category Graphics
 */ const SEMANTIC_BLENDWEIGHT = 'BLENDWEIGHT';
/**
 * Vertex attribute to be treated as skin blend indices.
 *
 * @category Graphics
 */ const SEMANTIC_BLENDINDICES = 'BLENDINDICES';
/**
 * Vertex attribute to be treated as a color.
 *
 * @category Graphics
 */ const SEMANTIC_COLOR = 'COLOR';
// private semantic used for programmatic construction of individual texcoord semantics
const SEMANTIC_TEXCOORD = 'TEXCOORD';
/**
 * Vertex attribute to be treated as a texture coordinate (set 0).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD0 = 'TEXCOORD0';
/**
 * Vertex attribute to be treated as a texture coordinate (set 1).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD1 = 'TEXCOORD1';
/**
 * Vertex attribute to be treated as a texture coordinate (set 2).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD2 = 'TEXCOORD2';
/**
 * Vertex attribute to be treated as a texture coordinate (set 3).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD3 = 'TEXCOORD3';
/**
 * Vertex attribute to be treated as a texture coordinate (set 4).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD4 = 'TEXCOORD4';
/**
 * Vertex attribute to be treated as a texture coordinate (set 5).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD5 = 'TEXCOORD5';
/**
 * Vertex attribute to be treated as a texture coordinate (set 6).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD6 = 'TEXCOORD6';
/**
 * Vertex attribute to be treated as a texture coordinate (set 7).
 *
 * @category Graphics
 */ const SEMANTIC_TEXCOORD7 = 'TEXCOORD7';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR0 = 'ATTR0';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR1 = 'ATTR1';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR2 = 'ATTR2';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR3 = 'ATTR3';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR4 = 'ATTR4';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR5 = 'ATTR5';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR6 = 'ATTR6';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR7 = 'ATTR7';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR8 = 'ATTR8';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR9 = 'ATTR9';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR10 = 'ATTR10';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR11 = 'ATTR11';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR12 = 'ATTR12';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR13 = 'ATTR13';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR14 = 'ATTR14';
/**
 * Vertex attribute with a user defined semantic.
 *
 * @category Graphics
 */ const SEMANTIC_ATTR15 = 'ATTR15';
const SHADERTAG_MATERIAL = 1;
/**
 * Don't change the stencil buffer value.
 *
 * @category Graphics
 */ const STENCILOP_KEEP = 0;
/**
 * The texture is not in a locked state.
 *
 * @category Graphics
 */ const TEXTURELOCK_NONE = 0;
/**
 * Read only. Any changes to the locked mip level's pixels will not update the texture.
 *
 * @category Graphics
 */ const TEXTURELOCK_READ = 1;
/**
 * Write only. The contents of the specified mip level will be entirely replaced.
 *
 * @category Graphics
 */ const TEXTURELOCK_WRITE = 2;
/**
 * Texture is a default type.
 *
 * @category Graphics
 */ const TEXTURETYPE_DEFAULT = 'default';
/**
 * Texture stores high dynamic range data in RGBM format.
 *
 * @category Graphics
 */ const TEXTURETYPE_RGBM = 'rgbm';
/**
 * Texture stores high dynamic range data in RGBE format.
 *
 * @category Graphics
 */ const TEXTURETYPE_RGBE = 'rgbe';
/**
 * Texture stores high dynamic range data in RGBP encoding.
 *
 * @category Graphics
 */ const TEXTURETYPE_RGBP = 'rgbp';
/**
 * Texture stores normalmap data swizzled in GGGR format. This is used for tangent space normal
 * maps. The R component is stored in alpha and G is stored in RGB. This packing can result in
 * higher quality when the texture data is compressed.
 *
 * @category Graphics
 */ const TEXTURETYPE_SWIZZLEGGGR = 'swizzleGGGR';
const TEXHINT_SHADOWMAP = 1;
const TEXHINT_ASSET = 2;
const TEXHINT_LIGHTMAP = 3;
/**
 * Texture data is stored in a 1-dimensional texture.
 *
 * @category Graphics
 */ const TEXTUREDIMENSION_1D = '1d';
/**
 * Texture data is stored in a 2-dimensional texture.
 *
 * @category Graphics
 */ const TEXTUREDIMENSION_2D = '2d';
/**
 * Texture data is stored in an array of 2-dimensional textures.
 *
 * @category Graphics
 */ const TEXTUREDIMENSION_2D_ARRAY = '2d-array';
/**
 * Texture data is stored in a cube texture.
 *
 * @category Graphics
 */ const TEXTUREDIMENSION_CUBE = 'cube';
/**
 * Texture data is stored in an array of cube textures.
 *
 * @category Graphics
 */ const TEXTUREDIMENSION_CUBE_ARRAY = 'cube-array';
/**
 * Texture data is stored in a 3-dimensional texture.
 *
 * @category Graphics
 */ const TEXTUREDIMENSION_3D = '3d';
/**
 * A sampler type of a texture that contains floating-point data. Typically stored for color
 * textures, where data can be filtered.
 *
 * @category Graphics
 */ const SAMPLETYPE_FLOAT = 0;
/**
 * A sampler type of a texture that contains floating-point data, but cannot be filtered. Typically
 * used for textures storing data that cannot be interpolated.
 *
 * @category Graphics
 */ const SAMPLETYPE_UNFILTERABLE_FLOAT = 1;
/**
 * A sampler type of a texture that contains depth data. Typically used for depth textures.
 *
 * @category Graphics
 */ const SAMPLETYPE_DEPTH = 2;
/**
 * A sampler type of a texture that contains signed integer data.
 *
 * @category Graphics
 */ const SAMPLETYPE_INT = 3;
/**
 * A sampler type of a texture that contains unsigned integer data.
 *
 * @category Graphics
 */ const SAMPLETYPE_UINT = 4;
/**
 * Texture data is not stored a specific projection format.
 *
 * @category Graphics
 */ const TEXTUREPROJECTION_NONE = 'none';
/**
 * Texture data is stored in cubemap projection format.
 *
 * @category Graphics
 */ const TEXTUREPROJECTION_CUBE = 'cube';
/**
 * Texture data is stored in equirectangular projection format.
 *
 * @category Graphics
 */ const TEXTUREPROJECTION_EQUIRECT = 'equirect';
/**
 * Texture data is stored in octahedral projection format.
 *
 * @category Graphics
 */ const TEXTUREPROJECTION_OCTAHEDRAL = 'octahedral';
/**
 * Shader source code uses GLSL language.
 *
 * @category Graphics
 */ const SHADERLANGUAGE_GLSL = 'glsl';
/**
 * Shader source code uses WGSL language.
 *
 * @category Graphics
 */ const SHADERLANGUAGE_WGSL = 'wgsl';
/**
 * Signed byte vertex element type.
 *
 * @category Graphics
 */ const TYPE_INT8 = 0;
/**
 * Unsigned byte vertex element type.
 *
 * @category Graphics
 */ const TYPE_UINT8 = 1;
/**
 * Signed short vertex element type.
 *
 * @category Graphics
 */ const TYPE_INT16 = 2;
/**
 * Unsigned short vertex element type.
 *
 * @category Graphics
 */ const TYPE_UINT16 = 3;
/**
 * Signed integer vertex element type.
 *
 * @category Graphics
 */ const TYPE_INT32 = 4;
/**
 * Unsigned integer vertex element type.
 *
 * @category Graphics
 */ const TYPE_UINT32 = 5;
/**
 * Floating point vertex element type.
 *
 * @category Graphics
 */ const TYPE_FLOAT32 = 6;
/**
 * 16-bit floating point vertex element type.
 *
 * @category Graphics
 */ const TYPE_FLOAT16 = 7;
// ---------- Uniform types ------------
// Note: Only types which can be used in uniform buffers are exported here, others are internal.
// The arrays are exposed as a base type with number of elements, and textures are not part of the
// uniform buffers.
/**
 * Boolean uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_BOOL = 0;
/**
 * Integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_INT = 1;
/**
 * Float uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_FLOAT = 2;
/**
 * 2 x Float uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_VEC2 = 3;
/**
 * 3 x Float uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_VEC3 = 4;
/**
 * 4 x Float uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_VEC4 = 5;
/**
 * 2 x Integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_IVEC2 = 6;
/**
 * 3 x Integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_IVEC3 = 7;
/**
 * 4 x Integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_IVEC4 = 8;
/**
 * 2 x Boolean uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_BVEC2 = 9;
/**
 * 3 x Boolean uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_BVEC3 = 10;
/**
 * 4 x Boolean uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_BVEC4 = 11;
/**
 * 2 x 2 x Float uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_MAT2 = 12;
/**
 * 3 x 3 x Float uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_MAT3 = 13;
/**
 * 4 x 4 x Float uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_MAT4 = 14;
const UNIFORMTYPE_TEXTURE2D = 15;
const UNIFORMTYPE_TEXTURECUBE = 16;
const UNIFORMTYPE_FLOATARRAY = 17;
const UNIFORMTYPE_TEXTURE2D_SHADOW = 18;
const UNIFORMTYPE_TEXTURECUBE_SHADOW = 19;
const UNIFORMTYPE_TEXTURE3D = 20;
const UNIFORMTYPE_VEC2ARRAY = 21;
const UNIFORMTYPE_VEC3ARRAY = 22;
const UNIFORMTYPE_VEC4ARRAY = 23;
const UNIFORMTYPE_MAT4ARRAY = 24;
const UNIFORMTYPE_TEXTURE2D_ARRAY = 25;
// Unsigned uniform types
/**
 * Unsigned integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_UINT = 26;
/**
 * 2 x Unsigned integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_UVEC2 = 27;
/**
 * 3 x Unsigned integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_UVEC3 = 28;
/**
 * 4 x Unsigned integer uniform type.
 *
 * @category Graphics
 */ const UNIFORMTYPE_UVEC4 = 29;
// Integer uniform array types
const UNIFORMTYPE_INTARRAY = 30;
const UNIFORMTYPE_UINTARRAY = 31;
const UNIFORMTYPE_BOOLARRAY = 32;
const UNIFORMTYPE_IVEC2ARRAY = 33;
const UNIFORMTYPE_UVEC2ARRAY = 34;
const UNIFORMTYPE_BVEC2ARRAY = 35;
const UNIFORMTYPE_IVEC3ARRAY = 36;
const UNIFORMTYPE_UVEC3ARRAY = 37;
const UNIFORMTYPE_BVEC3ARRAY = 38;
const UNIFORMTYPE_IVEC4ARRAY = 39;
const UNIFORMTYPE_UVEC4ARRAY = 40;
const UNIFORMTYPE_BVEC4ARRAY = 41;
// Integer texture types
const UNIFORMTYPE_ITEXTURE2D = 42;
const UNIFORMTYPE_UTEXTURE2D = 43;
const UNIFORMTYPE_ITEXTURECUBE = 44;
const UNIFORMTYPE_UTEXTURECUBE = 45;
const UNIFORMTYPE_ITEXTURE3D = 46;
const UNIFORMTYPE_UTEXTURE3D = 47;
const UNIFORMTYPE_ITEXTURE2D_ARRAY = 48;
const UNIFORMTYPE_UTEXTURE2D_ARRAY = 49;
// ----------
// Uniform types in GLSL
const uniformTypeToName = [
    // Uniforms
    'bool',
    'int',
    'float',
    'vec2',
    'vec3',
    'vec4',
    'ivec2',
    'ivec3',
    'ivec4',
    'bvec2',
    'bvec3',
    'bvec4',
    'mat2',
    'mat3',
    'mat4',
    'sampler2D',
    'samplerCube',
    '',
    'sampler2DShadow',
    'samplerCubeShadow',
    'sampler3D',
    '',
    '',
    '',
    '',
    'sampler2DArray',
    'uint',
    'uvec2',
    'uvec3',
    'uvec4',
    '',
    '',
    '',
    '',
    '',
    '',
    '',
    '',
    '',
    '',
    '',
    '',
    'isampler2D',
    'usampler2D',
    'isamplerCube',
    'usamplerCube',
    'isampler3D',
    'usampler3D',
    'isampler2DArray',
    'usampler2DArray'
];
// Uniform types in WGSL
const uniformTypeToNameWGSL = [
    // Uniforms
    [
        'bool'
    ],
    [
        'i32'
    ],
    [
        'f32'
    ],
    [
        'vec2f',
        'vec2<f32>'
    ],
    [
        'vec3f',
        'vec3<f32>'
    ],
    [
        'vec4f',
        'vec4<f32>'
    ],
    [
        'vec2i',
        'vec2<i32>'
    ],
    [
        'vec3i',
        'vec3<i32>'
    ],
    [
        'vec4i',
        'vec4<i32>'
    ],
    [
        'vec2<bool>'
    ],
    [
        'vec3<bool>'
    ],
    [
        'vec4<bool>'
    ],
    [
        'mat2x2f',
        'mat2x2<f32>'
    ],
    [
        'mat3x3f',
        'mat3x3<f32>'
    ],
    [
        'mat4x4f',
        'mat4x4<f32>'
    ],
    [
        'texture_2d<f32>'
    ],
    [
        'texture_cube<f32>'
    ],
    [
        'array<f32>'
    ],
    [
        'texture_depth_2d'
    ],
    [
        'texture_depth_cube'
    ],
    [
        'texture_3d<f32>'
    ],
    [
        'array<vec2<f32>>'
    ],
    [
        'array<vec3<f32>>'
    ],
    [
        'array<vec4<f32>>'
    ],
    [
        'array<mat4x4<f32>>'
    ],
    [
        'texture_2d_array<f32>'
    ],
    // Unsigned integer uniforms
    [
        'u32'
    ],
    [
        'vec2u',
        'vec2<u32>'
    ],
    [
        'vec3u',
        'vec3<u32>'
    ],
    [
        'vec4u',
        'vec4<u32>'
    ],
    // Integer array uniforms
    [
        'array<i32>'
    ],
    [
        'array<u32>'
    ],
    [
        'array<bool>'
    ],
    [
        'array<vec2i>',
        'array<vec2<i32>>'
    ],
    [
        'array<vec2u>',
        'array<vec2<u32>>'
    ],
    [
        'array<vec2b>',
        'array<vec2<bool>>'
    ],
    [
        'array<vec3i>',
        'array<vec3<i32>>'
    ],
    [
        'array<vec3u>',
        'array<vec3<u32>>'
    ],
    [
        'array<vec3b>',
        'array<vec3<bool>>'
    ],
    [
        'array<vec4i>',
        'array<vec4<i32>>'
    ],
    [
        'array<vec4u>',
        'array<vec4<u32>>'
    ],
    [
        'array<vec4b>',
        'array<vec4<bool>>'
    ],
    // Integer texture types
    [
        'texture_2d<i32>'
    ],
    [
        'texture_2d<u32>'
    ],
    [
        'texture_cube<i32>'
    ],
    [
        'texture_cube<u32>'
    ],
    [
        'texture_3d<i32>'
    ],
    [
        'texture_3d<u32>'
    ],
    [
        'texture_2d_array<i32>'
    ],
    [
        'texture_2d_array<u32>'
    ] // UNIFORMTYPE_UTEXTURE2D_ARRAY
];
// map version of uniformTypeToNameMapWGSL, allowing type name lookup by type name
const uniformTypeToNameMapWGSL = new Map();
uniformTypeToNameWGSL.forEach((names, index)=>{
    names.forEach((name)=>uniformTypeToNameMapWGSL.set(name, index));
});
/**
 * A WebGL 2 device type.
 *
 * @category Graphics
 */ const DEVICETYPE_WEBGL2 = 'webgl2';
/**
 * A WebGPU device type.
 *
 * @category Graphics
 */ const DEVICETYPE_WEBGPU = 'webgpu';
/**
 * A Null device type.
 *
 * @category Graphics
 */ const DEVICETYPE_NULL = 'null';
/**
 * The resource is visible to the vertex shader.
 *
 * @category Graphics
 */ const SHADERSTAGE_VERTEX = 1;
/**
 * The resource is visible to the fragment shader.
 *
 * @category Graphics
 */ const SHADERSTAGE_FRAGMENT = 2;
/**
 * The resource is visible to the compute shader.
 *
 * @category Graphics
 */ const SHADERSTAGE_COMPUTE = 4;
/**
 * Display format for low dynamic range data. This is always supported; however, due to the cost, it
 * does not implement linear alpha blending on the main framebuffer. Instead, alpha blending occurs
 * in sRGB space.
 *
 * @category Graphics
 */ const DISPLAYFORMAT_LDR = 'ldr';
/**
 * Display format for low dynamic range data in the sRGB color space. This format correctly
 * implements linear alpha blending on the main framebuffer, with the alpha blending occurring in
 * linear space. This is currently supported on WebGPU platform only. On unsupported platforms, it
 * silently falls back to {@link DISPLAYFORMAT_LDR}.
 *
 * @category Graphics
 */ const DISPLAYFORMAT_LDR_SRGB = 'ldr_srgb';
/**
 * Display format for high dynamic range data, using 16bit floating point values.
 * Note: This is supported on WebGPU platform only, and ignored on other platforms. On displays
 * without HDR support, it silently falls back to {@link DISPLAYFORMAT_LDR}. Use
 * {@link GraphicsDevice.isHdr} to see if the HDR format is used. When it is, it's recommended to
 * use {@link TONEMAP_NONE} for the tonemapping mode, to avoid it clipping the high dynamic range.
 *
 * @category Graphics
 */ const DISPLAYFORMAT_HDR = 'hdr';
// internal flags of the texture properties
const TEXPROPERTY_MIN_FILTER = 1;
const TEXPROPERTY_MAG_FILTER = 2;
const TEXPROPERTY_ADDRESS_U = 4;
const TEXPROPERTY_ADDRESS_V = 8;
const TEXPROPERTY_ADDRESS_W = 16;
const TEXPROPERTY_COMPARE_ON_READ = 32;
const TEXPROPERTY_COMPARE_FUNC = 64;
const TEXPROPERTY_ANISOTROPY = 128;
const TEXPROPERTY_ALL = 255; // 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128
// indices of commonly used bind groups, sorted from the least commonly changing to avoid internal rebinding
const BINDGROUP_VIEW = 0; // view bind group, textures, samplers and uniforms
const BINDGROUP_MESH = 1; // mesh bind group - textures and samplers
const BINDGROUP_MESH_UB = 2; // mesh bind group - a single uniform buffer
// names of bind groups
const bindGroupNames = [
    'view',
    'mesh',
    'mesh_ub'
];
// name of the default uniform buffer slot in a bind group
const UNIFORM_BUFFER_DEFAULT_SLOT_NAME = 'default';
// WebGPU does not support empty uniform buffer, add a dummy uniform to avoid validation errors
const UNUSED_UNIFORM_NAME = '_unused_float_uniform';
// map of engine TYPE_*** enums to their corresponding typed array constructors and byte sizes
const typedArrayTypes = [
    Int8Array,
    Uint8Array,
    Int16Array,
    Uint16Array,
    Int32Array,
    Uint32Array,
    Float32Array,
    Uint16Array
];
const typedArrayTypesByteSize = [
    1,
    1,
    2,
    2,
    4,
    4,
    4,
    2
];
const vertexTypesNames = [
    'INT8',
    'UINT8',
    'INT16',
    'UINT16',
    'INT32',
    'UINT32',
    'FLOAT32',
    'FLOAT16'
];
// map of engine INDEXFORMAT_*** to their corresponding typed array constructors and byte sizes
const typedArrayIndexFormats = [
    Uint8Array,
    Uint16Array,
    Uint32Array
];
const typedArrayIndexFormatsByteSize = [
    1,
    2,
    4
];
// map of primitive GLSL types to their corresponding WGSL types
const primitiveGlslToWgslTypeMap = new Map([
    // floating-point
    [
        'float',
        'f32'
    ],
    [
        'vec2',
        'vec2f'
    ],
    [
        'vec3',
        'vec3f'
    ],
    [
        'vec4',
        'vec4f'
    ],
    // signed integer
    [
        'int',
        'i32'
    ],
    [
        'ivec2',
        'vec2i'
    ],
    [
        'ivec3',
        'vec3i'
    ],
    [
        'ivec4',
        'vec4i'
    ],
    // unsigned integer
    [
        'uint',
        'u32'
    ],
    [
        'uvec2',
        'vec2u'
    ],
    [
        'uvec3',
        'vec3u'
    ],
    [
        'uvec4',
        'vec4u'
    ]
]);
/**
 * Map of engine semantics into location on device in range 0..15 (note - semantics mapping to the
 * same location cannot be used at the same time) organized in a way that ATTR0-ATTR7 do not
 * overlap with common important semantics.
 *
 * @type {object}
 * @ignore
 * @category Graphics
 */ const semanticToLocation = {};
semanticToLocation[SEMANTIC_POSITION] = 0;
semanticToLocation[SEMANTIC_NORMAL] = 1;
semanticToLocation[SEMANTIC_BLENDWEIGHT] = 2;
semanticToLocation[SEMANTIC_BLENDINDICES] = 3;
semanticToLocation[SEMANTIC_COLOR] = 4;
semanticToLocation[SEMANTIC_TEXCOORD0] = 5;
semanticToLocation[SEMANTIC_TEXCOORD1] = 6;
semanticToLocation[SEMANTIC_TEXCOORD2] = 7;
semanticToLocation[SEMANTIC_TEXCOORD3] = 8;
semanticToLocation[SEMANTIC_TEXCOORD4] = 9;
semanticToLocation[SEMANTIC_TEXCOORD5] = 10;
semanticToLocation[SEMANTIC_TEXCOORD6] = 11;
semanticToLocation[SEMANTIC_TEXCOORD7] = 12;
semanticToLocation[SEMANTIC_TANGENT] = 13;
semanticToLocation[SEMANTIC_ATTR0] = 0;
semanticToLocation[SEMANTIC_ATTR1] = 1;
semanticToLocation[SEMANTIC_ATTR2] = 2;
semanticToLocation[SEMANTIC_ATTR3] = 3;
semanticToLocation[SEMANTIC_ATTR4] = 4;
semanticToLocation[SEMANTIC_ATTR5] = 5;
semanticToLocation[SEMANTIC_ATTR6] = 6;
semanticToLocation[SEMANTIC_ATTR7] = 7;
semanticToLocation[SEMANTIC_ATTR8] = 8;
semanticToLocation[SEMANTIC_ATTR9] = 9;
semanticToLocation[SEMANTIC_ATTR10] = 10;
semanticToLocation[SEMANTIC_ATTR11] = 11;
semanticToLocation[SEMANTIC_ATTR12] = 12;
semanticToLocation[SEMANTIC_ATTR13] = 13;
semanticToLocation[SEMANTIC_ATTR14] = 14;
semanticToLocation[SEMANTIC_ATTR15] = 15;

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 */ /**
 * Internal graphics debug system - gpu markers and similar. Note that the functions only execute
 * in the debug build, and are stripped out in other builds.
 */ class DebugGraphics {
    static{
        /**
     * An array of markers, representing a stack.
     *
     * @type {string[]}
     * @private
     */ this.markers = [];
    }
    /**
     * Clear internal stack of the GPU markers. It should be called at the start of the frame to
     * prevent the array growing if there are exceptions during the rendering.
     */ static clearGpuMarkers() {
        DebugGraphics.markers.length = 0;
    }
    /**
     * Push GPU marker to the stack on the device.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {string} name - The name of the marker.
     */ static pushGpuMarker(device, name) {
        DebugGraphics.markers.push(name);
        device.pushMarker(name);
    }
    /**
     * Pop GPU marker from the stack on the device.
     *
     * @param {GraphicsDevice} device - The graphics device.
     */ static popGpuMarker(device) {
        if (DebugGraphics.markers.length) {
            DebugGraphics.markers.pop();
        }
        device.popMarker();
    }
    /**
     * Converts current markers into a single string format.
     *
     * @returns {string} String representation of current markers.
     */ static toString() {
        return DebugGraphics.markers.join(' | ');
    }
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { ScopeId } from './scope-id.js'
 */ let id$d = 0;
/**
 * A base class to describe the format of the resource for {@link BindGroupFormat}.
 *
 * @category Graphics
 */ class BindBaseFormat {
    /**
     * Create a new instance.
     *
     * @param {string} name - The name of the resource.
     * @param {number} visibility - A bit-flag that specifies the shader stages in which the resource
     * is visible. Can be:
     *
     * - {@link SHADERSTAGE_VERTEX}
     * - {@link SHADERSTAGE_FRAGMENT}
     * - {@link SHADERSTAGE_COMPUTE}
     */ constructor(name, visibility){
        /**
     * @type {number}
     * @ignore
     */ this.slot = -1;
        /**
     * @type {ScopeId|null}
     * @ignore
     */ this.scopeId = null;
        /** @type {string} */ this.name = name;
        // SHADERSTAGE_VERTEX, SHADERSTAGE_FRAGMENT, SHADERSTAGE_COMPUTE
        this.visibility = visibility;
    }
}
/**
 * A class to describe the format of the uniform buffer for {@link BindGroupFormat}.
 *
 * @category Graphics
 */ class BindUniformBufferFormat extends BindBaseFormat {
}
/**
 * A class to describe the format of the storage buffer for {@link BindGroupFormat}.
 *
 * @category Graphics
 */ class BindStorageBufferFormat extends BindBaseFormat {
    /**
     * Create a new instance.
     *
     * @param {string} name - The name of the storage buffer.
     * @param {number} visibility - A bit-flag that specifies the shader stages in which the storage
     * buffer is visible. Can be:
     *
     * - {@link SHADERSTAGE_VERTEX}
     * - {@link SHADERSTAGE_FRAGMENT}
     * - {@link SHADERSTAGE_COMPUTE}
     *
     * @param {boolean} [readOnly] - Whether the storage buffer is read-only, or read-write. Defaults
     * to false. This has to be true for the storage buffer used in the vertex shader.
     */ constructor(name, visibility, readOnly = false){
        super(name, visibility), /**
     * Format, extracted from vertex and fragment shader.
     *
     * @type {string}
     * @ignore
     */ this.format = '';
        // whether the buffer is read-only
        this.readOnly = readOnly;
        Debug.assert(readOnly || !(visibility & SHADERSTAGE_VERTEX), 'Storage buffer can only be used in read-only mode in SHADERSTAGE_VERTEX.');
    }
}
/**
 * A class to describe the format of the texture for {@link BindGroupFormat}.
 *
 * @category Graphics
 */ class BindTextureFormat extends BindBaseFormat {
    /**
     * Create a new instance.
     *
     * @param {string} name - The name of the storage buffer.
     * @param {number} visibility - A bit-flag that specifies the shader stages in which the storage
     * buffer is visible. Can be:
     *
     * - {@link SHADERSTAGE_VERTEX}
     * - {@link SHADERSTAGE_FRAGMENT}
     * - {@link SHADERSTAGE_COMPUTE}
     *
     * @param {string} [textureDimension] - The dimension of the texture. Defaults to
     * {@link TEXTUREDIMENSION_2D}. Can be:
     *
     * - {@link TEXTUREDIMENSION_1D}
     * - {@link TEXTUREDIMENSION_2D}
     * - {@link TEXTUREDIMENSION_2D_ARRAY}
     * - {@link TEXTUREDIMENSION_CUBE}
     * - {@link TEXTUREDIMENSION_CUBE_ARRAY}
     * - {@link TEXTUREDIMENSION_3D}
     *
     * @param {number} [sampleType] - The type of the texture samples. Defaults to
     * {@link SAMPLETYPE_FLOAT}. Can be:
     *
     * - {@link SAMPLETYPE_FLOAT}
     * - {@link SAMPLETYPE_UNFILTERABLE_FLOAT}
     * - {@link SAMPLETYPE_DEPTH}
     * - {@link SAMPLETYPE_INT}
     * - {@link SAMPLETYPE_UINT}
     *
     * @param {boolean} [hasSampler] - True if the sampler for the texture is needed. Note that if the
     * sampler is used, it will take up an additional slot, directly following the texture slot.
     * Defaults to true.
     * @param {string|null} [samplerName] - Optional name of the sampler. Defaults to null.
     */ constructor(name, visibility, textureDimension = TEXTUREDIMENSION_2D, sampleType = SAMPLETYPE_FLOAT, hasSampler = true, samplerName = null){
        super(name, visibility);
        // TEXTUREDIMENSION_***
        this.textureDimension = textureDimension;
        // SAMPLETYPE_***
        this.sampleType = sampleType;
        // whether to use a sampler with this texture
        this.hasSampler = hasSampler;
        // optional name of the sampler (its automatically generated if not provided)
        this.samplerName = samplerName ?? `${name}_sampler`;
    }
}
/**
 * A class to describe the format of the storage texture for {@link BindGroupFormat}. Storage
 * texture is a texture created with the storage flag set to true, which allows it to be used as an
 * output of a compute shader.
 *
 * Note: At the current time, storage textures are only supported in compute shaders in a
 * write-only mode.
 *
 * @category Graphics
 */ class BindStorageTextureFormat extends BindBaseFormat {
    /**
     * Create a new instance.
     *
     * @param {string} name - The name of the storage buffer.
     * @param {number} [format] - The pixel format of the texture. Note that not all formats can be
     * used. Defaults to {@link PIXELFORMAT_RGBA8}.
     * @param {string} [textureDimension] - The dimension of the texture. Defaults to
     * {@link TEXTUREDIMENSION_2D}. Can be:
     *
     * - {@link TEXTUREDIMENSION_1D}
     * - {@link TEXTUREDIMENSION_2D}
     * - {@link TEXTUREDIMENSION_2D_ARRAY}
     * - {@link TEXTUREDIMENSION_3D}
     *
     * @param {boolean} [write] - Whether the storage texture is writeable. Defaults to true.
     * @param {boolean} [read] - Whether the storage texture is readable. Defaults to false. Note
     * that storage texture reads are only supported if
     * {@link GraphicsDevice#supportsStorageTextureRead} is true. Also note that only a subset of
     * pixel formats can be used for storage texture reads - as an example, PIXELFORMAT_RGBA8 is not
     * compatible, but PIXELFORMAT_R32U is.
     */ constructor(name, format = PIXELFORMAT_RGBA8, textureDimension = TEXTUREDIMENSION_2D, write = true, read = false){
        super(name, SHADERSTAGE_COMPUTE);
        // PIXELFORMAT_***
        this.format = format;
        // TEXTUREDIMENSION_***
        this.textureDimension = textureDimension;
        // whether the texture is writeable
        this.write = write;
        // whether the texture is readable
        this.read = read;
    }
}
/**
 * BindGroupFormat is a data structure that defines the layout of resources (buffers, textures,
 * samplers) used by rendering or compute shaders. It describes the binding points for each
 * resource type, and the visibility of these resources in the shader stages.
 * Currently this class is only used on WebGPU platform to specify the input and output resources
 * for vertex, fragment and compute shaders written in {@link SHADERLANGUAGE_WGSL} language.
 *
 * @category Graphics
 */ class BindGroupFormat {
    /**
     * Create a new instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this vertex format.
     * @param {(BindTextureFormat|BindStorageTextureFormat|BindUniformBufferFormat|BindStorageBufferFormat)[]} formats -
     * An array of bind formats. Note that each entry in the array uses up one slot. The exception
     * is a texture format that has a sampler, which uses up two slots. The slots are allocated
     * sequentially, starting from 0.
     */ constructor(graphicsDevice, formats){
        /**
     * @type {BindUniformBufferFormat[]}
     * @private
     */ this.uniformBufferFormats = [];
        /**
     * @type {BindTextureFormat[]}
     * @private
     */ this.textureFormats = [];
        /**
     * @type {BindStorageTextureFormat[]}
     * @private
     */ this.storageTextureFormats = [];
        /**
     * @type {BindStorageBufferFormat[]}
     * @private
     */ this.storageBufferFormats = [];
        this.id = id$d++;
        DebugHelper.setName(this, `BindGroupFormat_${this.id}`);
        Debug.assert(formats);
        let slot = 0;
        formats.forEach((format)=>{
            // Assign slot. For texture format, we also need to assign a slot for its sampler.
            format.slot = slot++;
            if (format instanceof BindTextureFormat && format.hasSampler) {
                slot++;
            }
            // split the array into separate arrays
            if (format instanceof BindUniformBufferFormat) {
                this.uniformBufferFormats.push(format);
            } else if (format instanceof BindTextureFormat) {
                this.textureFormats.push(format);
            } else if (format instanceof BindStorageTextureFormat) {
                this.storageTextureFormats.push(format);
            } else if (format instanceof BindStorageBufferFormat) {
                this.storageBufferFormats.push(format);
            } else {
                Debug.assert('Invalid bind format', format);
            }
        });
        /** @type {GraphicsDevice} */ this.device = graphicsDevice;
        const scope = graphicsDevice.scope;
        // maps a buffer format name to an index
        /** @type {Map<string, number>} */ this.bufferFormatsMap = new Map();
        this.uniformBufferFormats.forEach((bf, i)=>this.bufferFormatsMap.set(bf.name, i));
        // maps a texture format name to a slot index
        /** @type {Map<string, number>} */ this.textureFormatsMap = new Map();
        this.textureFormats.forEach((tf, i)=>{
            this.textureFormatsMap.set(tf.name, i);
            // resolve scope id
            tf.scopeId = scope.resolve(tf.name);
        });
        // maps a storage texture format name to a slot index
        /** @type {Map<string, number>} */ this.storageTextureFormatsMap = new Map();
        this.storageTextureFormats.forEach((tf, i)=>{
            this.storageTextureFormatsMap.set(tf.name, i);
            // resolve scope id
            tf.scopeId = scope.resolve(tf.name);
        });
        // maps a storage buffer format name to a slot index
        /** @type {Map<string, number>} */ this.storageBufferFormatsMap = new Map();
        this.storageBufferFormats.forEach((bf, i)=>{
            this.storageBufferFormatsMap.set(bf.name, i);
            // resolve scope id
            bf.scopeId = scope.resolve(bf.name);
        });
        this.impl = graphicsDevice.createBindGroupFormatImpl(this);
        Debug.trace(TRACEID_BINDGROUPFORMAT_ALLOC, `Alloc: Id ${this.id}, while rendering [${DebugGraphics.toString()}]`, this);
    }
    /**
     * Frees resources associated with this bind group.
     */ destroy() {
        this.impl.destroy();
    }
    /**
     * Returns format of texture with specified name.
     *
     * @param {string} name - The name of the texture slot.
     * @returns {BindTextureFormat|null} - The format.
     * @ignore
     */ getTexture(name) {
        const index = this.textureFormatsMap.get(name);
        if (index !== undefined) {
            return this.textureFormats[index];
        }
        return null;
    }
    /**
     * Returns format of storage texture with specified name.
     *
     * @param {string} name - The name of the texture slot.
     * @returns {BindStorageTextureFormat|null} - The format.
     * @ignore
     */ getStorageTexture(name) {
        const index = this.storageTextureFormatsMap.get(name);
        if (index !== undefined) {
            return this.storageTextureFormats[index];
        }
        return null;
    }
    loseContext() {
    // TODO: implement
    }
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 */ /**
 * A cache storing shared resources associated with a device. The resources are removed
 * from the cache when the device is destroyed.
 */ class DeviceCache {
    /**
     * Returns the resources for the supplied device.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {() => any} onCreate - A function that creates the resource for the device.
     * @returns {any} The resource for the device.
     */ get(device, onCreate) {
        if (!this._cache.has(device)) {
            this._cache.set(device, onCreate());
            // when the device is destroyed, destroy and remove its entry
            device.on('destroy', ()=>{
                this.remove(device);
            });
            // when the context is lost, call optional loseContext on its entry
            device.on('devicelost', ()=>{
                this._cache.get(device)?.loseContext?.(device);
            });
        }
        return this._cache.get(device);
    }
    /**
     * Destroys and removes the content of the cache associated with the device
     *
     * @param {GraphicsDevice} device - The graphics device.
     */ remove(device) {
        this._cache.get(device)?.destroy?.(device);
        this._cache.delete(device);
    }
    constructor(){
        /**
     * Cache storing the resource for each GraphicsDevice
     *
     * @type {Map<GraphicsDevice, any>}
     */ this._cache = new Map();
    }
}

/**
 * A class providing utility functions for textures.
 *
 * @ignore
 */ class TextureUtils {
    /**
     * Calculate the dimension of a texture at a specific mip level.
     *
     * @param {number} dimension - Texture dimension at level 0.
     * @param {number} mipLevel - Mip level.
     * @returns {number} The dimension of the texture at the specified mip level.
     */ static calcLevelDimension(dimension, mipLevel) {
        return Math.max(dimension >> mipLevel, 1);
    }
    /**
     * Calculate the number of mip levels for a texture with the specified dimensions.
     *
     * @param {number} width - Texture's width.
     * @param {number} height - Texture's height.
     * @param {number} [depth] - Texture's depth. Defaults to 1.
     * @returns {number} The number of mip levels required for the texture.
     */ static calcMipLevelsCount(width, height, depth = 1) {
        return 1 + Math.floor(Math.log2(Math.max(width, height, depth)));
    }
    /**
     * Calculate the size in bytes of the texture level given its format and dimensions.
     *
     * @param {number} width - Texture's width.
     * @param {number} height - Texture's height.
     * @param {number} depth - Texture's depth.
     * @param {number} format - Texture's pixel format PIXELFORMAT_***.
     * @returns {number} The number of bytes of GPU memory required for the texture.
     */ static calcLevelGpuSize(width, height, depth, format) {
        const formatInfo = pixelFormatInfo.get(format);
        Debug.assert(formatInfo !== undefined, `Invalid pixel format ${format}`);
        const pixelSize = pixelFormatInfo.get(format)?.size ?? 0;
        if (pixelSize > 0) {
            return width * height * depth * pixelSize;
        }
        const blockSize = formatInfo.blockSize ?? 0;
        let blockWidth = Math.floor((width + 3) / 4);
        const blockHeight = Math.floor((height + 3) / 4);
        const blockDepth = Math.floor((depth + 3) / 4);
        if (format === PIXELFORMAT_PVRTC_2BPP_RGB_1 || format === PIXELFORMAT_PVRTC_2BPP_RGBA_1) {
            blockWidth = Math.max(Math.floor(blockWidth / 2), 1);
        }
        return blockWidth * blockHeight * blockDepth * blockSize;
    }
    /**
     * Calculate the GPU memory required for a texture.
     *
     * @param {number} width - Texture's width.
     * @param {number} height - Texture's height.
     * @param {number} depth - Texture's depth.
     * @param {number} format - Texture's pixel format PIXELFORMAT_***.
     * @param {boolean} mipmaps - True if the texture includes mipmaps, false otherwise.
     * @param {boolean} cubemap - True is the texture is a cubemap, false otherwise.
     * @returns {number} The number of bytes of GPU memory required for the texture.
     */ static calcGpuSize(width, height, depth, format, mipmaps, cubemap) {
        let result = 0;
        while(1){
            result += TextureUtils.calcLevelGpuSize(width, height, depth, format);
            // we're done if mipmaps aren't required or we've calculated the smallest mipmap level
            if (!mipmaps || width === 1 && height === 1 && depth === 1) {
                break;
            }
            width = Math.max(width >> 1, 1);
            height = Math.max(height >> 1, 1);
            depth = Math.max(depth >> 1, 1);
        }
        return result * (cubemap ? 6 : 1);
    }
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { RenderTarget } from './render-target.js'
 */ let id$c = 0;
/**
 * Represents a texture, which is typically an image composed of pixels (texels). Textures are
 * fundamental resources for rendering graphical objects. They are commonly used by
 * {@link Material}s and sampled in {@link Shader}s (usually fragment shaders) to define the visual
 * appearance of a 3D model's surface. Beyond storing color images, textures can hold various data
 * types like normal maps, environment maps (cubemaps), or custom data for shader computations. Key
 * properties control how the texture data is sampled, including filtering modes and coordinate
 * wrapping.
 *
 * Note on **HDR texture format** support:
 * 1. **As textures**:
 *     - float (i.e. {@link PIXELFORMAT_RGBA32F}), half-float (i.e. {@link PIXELFORMAT_RGBA16F}) and
 * small-float ({@link PIXELFORMAT_111110F}) formats are always supported on both WebGL2 and WebGPU
 * with point sampling.
 *     - half-float and small-float formats are always supported on WebGL2 and WebGPU with linear
 * sampling.
 *     - float formats are supported on WebGL2 and WebGPU with linear sampling only if
 * {@link GraphicsDevice#textureFloatFilterable} is true.
 *
 * 2. **As renderable textures** that can be used as color buffers in a {@link RenderTarget}:
 *     - on WebGPU, rendering to float and half-float formats is always supported.
 *     - on WebGPU, rendering to small-float format is supported only if
 * {@link GraphicsDevice#textureRG11B10Renderable} is true.
 *     - on WebGL2, rendering to these 3 formats formats is supported only if
 * {@link GraphicsDevice#textureFloatRenderable} is true.
 *     - on WebGL2, if {@link GraphicsDevice#textureFloatRenderable} is false, but
 * {@link GraphicsDevice#textureHalfFloatRenderable} is true, rendering to half-float formats only
 * is supported. This is the case of many mobile iOS devices.
 *     - you can determine available renderable HDR format using
 * {@link GraphicsDevice#getRenderableHdrFormat}.
 * @category Graphics
 */ class Texture {
    /**
     * Create a new Texture instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this texture.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {string} [options.name] - The name of the texture. Defaults to null.
     * @param {number} [options.width] - The width of the texture in pixels. Defaults to 4.
     * @param {number} [options.height] - The height of the texture in pixels. Defaults to 4.
     * @param {number} [options.depth] - The number of depth slices in a 3D texture.
     * @param {number} [options.format] - The pixel format of the texture. Can be:
     *
     * - {@link PIXELFORMAT_R8}
     * - {@link PIXELFORMAT_RG8}
     * - {@link PIXELFORMAT_RGB565}
     * - {@link PIXELFORMAT_RGBA5551}
     * - {@link PIXELFORMAT_RGBA4}
     * - {@link PIXELFORMAT_RGB8}
     * - {@link PIXELFORMAT_RGBA8}
     * - {@link PIXELFORMAT_DXT1}
     * - {@link PIXELFORMAT_DXT3}
     * - {@link PIXELFORMAT_DXT5}
     * - {@link PIXELFORMAT_RGB16F}
     * - {@link PIXELFORMAT_RGBA16F}
     * - {@link PIXELFORMAT_RGB32F}
     * - {@link PIXELFORMAT_RGBA32F}
     * - {@link PIXELFORMAT_ETC1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGBA_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGBA_1}
     * - {@link PIXELFORMAT_111110F}
     * - {@link PIXELFORMAT_ASTC_4x4}
     * - {@link PIXELFORMAT_ATC_RGB}
     * - {@link PIXELFORMAT_ATC_RGBA}
     *
     * Defaults to {@link PIXELFORMAT_RGBA8}.
     * @param {string} [options.projection] - The projection type of the texture, used when the
     * texture represents an environment. Can be:
     *
     * - {@link TEXTUREPROJECTION_NONE}
     * - {@link TEXTUREPROJECTION_CUBE}
     * - {@link TEXTUREPROJECTION_EQUIRECT}
     * - {@link TEXTUREPROJECTION_OCTAHEDRAL}
     *
     * Defaults to {@link TEXTUREPROJECTION_CUBE} if options.cubemap is true, otherwise
     * {@link TEXTUREPROJECTION_NONE}.
     * @param {number} [options.minFilter] - The minification filter type to use. Defaults to
     * {@link FILTER_LINEAR_MIPMAP_LINEAR}.
     * @param {number} [options.magFilter] - The magnification filter type to use. Defaults to
     * {@link FILTER_LINEAR}.
     * @param {number} [options.anisotropy] - The level of anisotropic filtering to use. Defaults
     * to 1.
     * @param {number} [options.addressU] - The repeat mode to use in the U direction. Defaults to
     * {@link ADDRESS_REPEAT}.
     * @param {number} [options.addressV] - The repeat mode to use in the V direction. Defaults to
     * {@link ADDRESS_REPEAT}.
     * @param {number} [options.addressW] - The repeat mode to use in the W direction. Defaults to
     * {@link ADDRESS_REPEAT}.
     * @param {boolean} [options.mipmaps] - When enabled try to generate or use mipmaps for this
     * texture. Default is true.
     * @param {number} [options.numLevels] - Specifies the number of mip levels to generate. If not
     * specified, the number is calculated based on the texture size. When this property is set,
     * the mipmaps property is ignored.
     * @param {boolean} [options.cubemap] - Specifies whether the texture is to be a cubemap.
     * Defaults to false.
     * @param {number} [options.arrayLength] - Specifies whether the texture is to be a 2D texture array.
     * When passed in as undefined or < 1, this is not an array texture. If >= 1, this is an array texture.
     * Defaults to undefined.
     * @param {boolean} [options.volume] - Specifies whether the texture is to be a 3D volume.
     * Defaults to false.
     * @param {string} [options.type] - Specifies the texture type.  Can be:
     *
     * - {@link TEXTURETYPE_DEFAULT}
     * - {@link TEXTURETYPE_RGBM}
     * - {@link TEXTURETYPE_RGBE}
     * - {@link TEXTURETYPE_RGBP}
     * - {@link TEXTURETYPE_SWIZZLEGGGR}
     *
     * Defaults to {@link TEXTURETYPE_DEFAULT}.
     * @param {boolean} [options.flipY] - Specifies whether the texture should be flipped in the
     * Y-direction. Only affects textures with a source that is an image, canvas or video element.
     * Does not affect cubemaps, compressed textures or textures set from raw pixel data. Defaults
     * to false.
     * @param {boolean} [options.premultiplyAlpha] - If true, the alpha channel of the texture (if
     * present) is multiplied into the color channels. Defaults to false.
     * @param {boolean} [options.compareOnRead] - When enabled, and if texture format is
     * {@link PIXELFORMAT_DEPTH} or {@link PIXELFORMAT_DEPTHSTENCIL}, hardware PCF is enabled for
     * this texture, and you can get filtered results of comparison using texture() in your shader.
     * Defaults to false.
     * @param {number} [options.compareFunc] - Comparison function when compareOnRead is enabled.
     * Can be:
     *
     * - {@link FUNC_LESS}
     * - {@link FUNC_LESSEQUAL}
     * - {@link FUNC_GREATER}
     * - {@link FUNC_GREATEREQUAL}
     * - {@link FUNC_EQUAL}
     * - {@link FUNC_NOTEQUAL}
     *
     * Defaults to {@link FUNC_LESS}.
     * @param {Uint8Array[]|Uint16Array[]|Uint32Array[]|Float32Array[]|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]|Uint8Array[][]} [options.levels]
     * - Array of Uint8Array or other supported browser interface; or a two-dimensional array
     * of Uint8Array if options.arrayLength is defined and greater than zero.
     * @param {boolean} [options.storage] - Defines if texture can be used as a storage texture by
     * a compute shader. Defaults to false.
     * @example
     * // Create a 8x8x24-bit texture
     * const texture = new pc.Texture(graphicsDevice, {
     *     width: 8,
     *     height: 8,
     *     format: pc.PIXELFORMAT_RGB8
     * });
     *
     * // Fill the texture with a gradient
     * const pixels = texture.lock();
     * const count = 0;
     * for (let i = 0; i < 8; i++) {
     *     for (let j = 0; j < 8; j++) {
     *         pixels[count++] = i * 32;
     *         pixels[count++] = j * 32;
     *         pixels[count++] = 255;
     *     }
     * }
     * texture.unlock();
     */ constructor(graphicsDevice, options = {}){
        /** @ignore */ this._gpuSize = 0;
        /** @protected */ this.id = id$c++;
        /** @protected */ this._invalid = false;
        /** @protected */ this._lockedLevel = -1;
        /** @protected */ this._lockedMode = TEXTURELOCK_NONE;
        /**
     * A render version used to track the last time the texture properties requiring bind group
     * to be updated were changed.
     *
     * @type {number}
     * @ignore
     */ this.renderVersionDirty = 0;
        /** @protected */ this._storage = false;
        /** @protected */ this._numLevels = 0;
        this.device = graphicsDevice;
        Debug.assert(this.device, 'Texture constructor requires a graphicsDevice to be valid');
        Debug.assert(!options.width || Number.isInteger(options.width), 'Texture width must be an integer number, got', options);
        Debug.assert(!options.height || Number.isInteger(options.height), 'Texture height must be an integer number, got', options);
        Debug.assert(!options.depth || Number.isInteger(options.depth), 'Texture depth must be an integer number, got', options);
        this.name = options.name ?? '';
        this._width = Math.floor(options.width ?? 4);
        this._height = Math.floor(options.height ?? 4);
        this._format = options.format ?? PIXELFORMAT_RGBA8;
        this._compressed = isCompressedPixelFormat(this._format);
        this._integerFormat = isIntegerPixelFormat(this._format);
        if (this._integerFormat) {
            options.minFilter = FILTER_NEAREST;
            options.magFilter = FILTER_NEAREST;
        }
        this._volume = options.volume ?? false;
        this._depth = Math.floor(options.depth ?? 1);
        this._arrayLength = Math.floor(options.arrayLength ?? 0);
        this._storage = options.storage ?? false;
        this._cubemap = options.cubemap ?? false;
        this._flipY = options.flipY ?? false;
        this._premultiplyAlpha = options.premultiplyAlpha ?? false;
        this._mipmaps = options.mipmaps ?? true;
        this._numLevelsRequested = options.numLevels;
        if (options.numLevels !== undefined) {
            this._numLevels = options.numLevels;
        }
        this._updateNumLevel();
        this._minFilter = options.minFilter ?? FILTER_LINEAR_MIPMAP_LINEAR;
        this._magFilter = options.magFilter ?? FILTER_LINEAR;
        this._anisotropy = options.anisotropy ?? 1;
        this._addressU = options.addressU ?? ADDRESS_REPEAT;
        this._addressV = options.addressV ?? ADDRESS_REPEAT;
        this._addressW = options.addressW ?? ADDRESS_REPEAT;
        this._compareOnRead = options.compareOnRead ?? false;
        this._compareFunc = options.compareFunc ?? FUNC_LESS;
        this._type = options.type ?? TEXTURETYPE_DEFAULT;
        Debug.assert(!options.hasOwnProperty('rgbm'), 'Use options.type.');
        Debug.assert(!options.hasOwnProperty('swizzleGGGR'), 'Use options.type.');
        this.projection = TEXTUREPROJECTION_NONE;
        if (this._cubemap) {
            this.projection = TEXTUREPROJECTION_CUBE;
        } else if (options.projection && options.projection !== TEXTUREPROJECTION_CUBE) {
            this.projection = options.projection;
        }
        this.profilerHint = options.profilerHint ?? 0;
        this._levels = options.levels;
        const upload = !!options.levels;
        if (!this._levels) {
            this._clearLevels();
        }
        this.recreateImpl(upload);
        // track the texture
        graphicsDevice.textures.push(this);
        Debug.trace(TRACEID_TEXTURE_ALLOC, `Alloc: Id ${this.id} ${this.name}: ${this.width}x${this.height} [${pixelFormatInfo.get(this.format)?.name}]` + `${this.cubemap ? '[Cubemap]' : ''}` + `${this.volume ? '[Volume]' : ''}` + `${this.array ? '[Array]' : ''}` + `[MipLevels:${this.numLevels}]`, this);
    }
    /**
     * Frees resources associated with this texture.
     */ destroy() {
        Debug.trace(TRACEID_TEXTURE_ALLOC, `DeAlloc: Id ${this.id} ${this.name}`);
        const device = this.device;
        if (device) {
            // stop tracking the texture
            const idx = device.textures.indexOf(this);
            if (idx !== -1) {
                device.textures.splice(idx, 1);
            }
            // Remove texture from any uniforms
            device.scope.removeValue(this);
            // destroy implementation
            this.impl.destroy(device);
            // Update texture stats
            this.adjustVramSizeTracking(device._vram, -this._gpuSize);
            this._levels = null;
            this.device = null;
        }
    }
    recreateImpl(upload = true) {
        const { device } = this;
        // destroy existing
        this.impl?.destroy(device);
        this.impl = null;
        // create new
        this.impl = device.createTextureImpl(this);
        this.dirtyAll();
        if (upload) {
            this.upload();
        }
    }
    _clearLevels() {
        this._levels = this._cubemap ? [
            [
                null,
                null,
                null,
                null,
                null,
                null
            ]
        ] : [
            null
        ];
    }
    /**
     * Resizes the texture. This operation is supported for render target textures, and it resizes
     * the allocated buffer used for rendering, not the existing content of the texture.
     *
     * It is also supported for textures with data provided via the {@link lock} method. After
     * resizing, the appropriately sized data must be assigned by calling {@link lock} again.
     *
     * @param {number} width - The new width of the texture.
     * @param {number} height - The new height of the texture.
     * @param {number} [depth] - The new depth of the texture. Defaults to 1.
     * @ignore
     */ resize(width, height, depth = 1) {
        // destroy texture impl
        const device = this.device;
        this.adjustVramSizeTracking(device._vram, -this._gpuSize);
        this.impl.destroy(device);
        this._clearLevels();
        this._width = Math.floor(width);
        this._height = Math.floor(height);
        this._depth = Math.floor(depth);
        this._updateNumLevel();
        // re-create the implementation
        this.impl = device.createTextureImpl(this);
        this.dirtyAll();
    }
    /**
     * Called when the rendering context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        this.impl.loseContext();
        this.dirtyAll();
    }
    /**
     * Updates vram size tracking for the texture, size can be positive to add or negative to subtract
     *
     * @ignore
     */ adjustVramSizeTracking(vram, size) {
        Debug.trace(TRACEID_VRAM_TEXTURE, `${this.id} ${this.name} size: ${size} vram.texture: ${vram.tex} => ${vram.tex + size}`);
        vram.tex += size;
        if (this.profilerHint === TEXHINT_SHADOWMAP) {
            vram.texShadow += size;
        } else if (this.profilerHint === TEXHINT_ASSET) {
            vram.texAsset += size;
        } else if (this.profilerHint === TEXHINT_LIGHTMAP) {
            vram.texLightmap += size;
        }
    }
    propertyChanged(flag) {
        this.impl.propertyChanged(flag);
        this.renderVersionDirty = this.device.renderVersion;
    }
    _updateNumLevel() {
        const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1;
        const requestedLevels = this._numLevelsRequested;
        if (requestedLevels !== undefined && requestedLevels > maxLevels) {
            Debug.warn('Texture#numLevels: requested mip level count is greater than the maximum possible, will be clamped to', maxLevels, this);
        }
        this._numLevels = Math.min(requestedLevels ?? maxLevels, maxLevels);
        this._mipmaps = this._numLevels > 1;
    }
    /**
     * Returns the current lock mode. One of:
     *
     * - {@link TEXTURELOCK_NONE}
     * - {@link TEXTURELOCK_READ}
     * - {@link TEXTURELOCK_WRITE}
     *
     * @ignore
     * @type {number}
     */ get lockedMode() {
        return this._lockedMode;
    }
    /**
     * Sets the minification filter to be applied to the texture. Can be:
     *
     * - {@link FILTER_NEAREST}
     * - {@link FILTER_LINEAR}
     * - {@link FILTER_NEAREST_MIPMAP_NEAREST}
     * - {@link FILTER_NEAREST_MIPMAP_LINEAR}
     * - {@link FILTER_LINEAR_MIPMAP_NEAREST}
     * - {@link FILTER_LINEAR_MIPMAP_LINEAR}
     *
     * @type {number}
     */ set minFilter(v) {
        if (this._minFilter !== v) {
            if (isIntegerPixelFormat(this._format)) {
                Debug.warn('Texture#minFilter: minFilter property cannot be changed on an integer texture, will remain FILTER_NEAREST', this);
            } else {
                this._minFilter = v;
                this.propertyChanged(TEXPROPERTY_MIN_FILTER);
            }
        }
    }
    /**
     * Gets the minification filter to be applied to the texture.
     *
     * @type {number}
     */ get minFilter() {
        return this._minFilter;
    }
    /**
     * Sets the magnification filter to be applied to the texture. Can be:
     *
     * - {@link FILTER_NEAREST}
     * - {@link FILTER_LINEAR}
     *
     * @type {number}
     */ set magFilter(v) {
        if (this._magFilter !== v) {
            if (isIntegerPixelFormat(this._format)) {
                Debug.warn('Texture#magFilter: magFilter property cannot be changed on an integer texture, will remain FILTER_NEAREST', this);
            } else {
                this._magFilter = v;
                this.propertyChanged(TEXPROPERTY_MAG_FILTER);
            }
        }
    }
    /**
     * Gets the magnification filter to be applied to the texture.
     *
     * @type {number}
     */ get magFilter() {
        return this._magFilter;
    }
    /**
     * Sets the addressing mode to be applied to the texture horizontally. Can be:
     *
     * - {@link ADDRESS_REPEAT}
     * - {@link ADDRESS_CLAMP_TO_EDGE}
     * - {@link ADDRESS_MIRRORED_REPEAT}
     *
     * @type {number}
     */ set addressU(v) {
        if (this._addressU !== v) {
            this._addressU = v;
            this.propertyChanged(TEXPROPERTY_ADDRESS_U);
        }
    }
    /**
     * Gets the addressing mode to be applied to the texture horizontally.
     *
     * @type {number}
     */ get addressU() {
        return this._addressU;
    }
    /**
     * Sets the addressing mode to be applied to the texture vertically. Can be:
     *
     * - {@link ADDRESS_REPEAT}
     * - {@link ADDRESS_CLAMP_TO_EDGE}
     * - {@link ADDRESS_MIRRORED_REPEAT}
     *
     * @type {number}
     */ set addressV(v) {
        if (this._addressV !== v) {
            this._addressV = v;
            this.propertyChanged(TEXPROPERTY_ADDRESS_V);
        }
    }
    /**
     * Gets the addressing mode to be applied to the texture vertically.
     *
     * @type {number}
     */ get addressV() {
        return this._addressV;
    }
    /**
     * Sets the addressing mode to be applied to the 3D texture depth. Can be:
     *
     * - {@link ADDRESS_REPEAT}
     * - {@link ADDRESS_CLAMP_TO_EDGE}
     * - {@link ADDRESS_MIRRORED_REPEAT}
     *
     * @type {number}
     */ set addressW(addressW) {
        if (!this._volume) {
            Debug.warn('pc.Texture#addressW: Can\'t set W addressing mode for a non-3D texture.');
            return;
        }
        if (addressW !== this._addressW) {
            this._addressW = addressW;
            this.propertyChanged(TEXPROPERTY_ADDRESS_W);
        }
    }
    /**
     * Gets the addressing mode to be applied to the 3D texture depth.
     *
     * @type {number}
     */ get addressW() {
        return this._addressW;
    }
    /**
     * When enabled, and if texture format is {@link PIXELFORMAT_DEPTH} or
     * {@link PIXELFORMAT_DEPTHSTENCIL}, hardware PCF is enabled for this texture, and you can get
     * filtered results of comparison using texture() in your shader.
     *
     * @type {boolean}
     */ set compareOnRead(v) {
        if (this._compareOnRead !== v) {
            this._compareOnRead = v;
            this.propertyChanged(TEXPROPERTY_COMPARE_ON_READ);
        }
    }
    /**
     * Gets whether you can get filtered results of comparison using texture() in your shader.
     *
     * @type {boolean}
     */ get compareOnRead() {
        return this._compareOnRead;
    }
    /**
     * Sets the comparison function when {@link compareOnRead} is enabled. Possible values:
     *
     * - {@link FUNC_LESS}
     * - {@link FUNC_LESSEQUAL}
     * - {@link FUNC_GREATER}
     * - {@link FUNC_GREATEREQUAL}
     * - {@link FUNC_EQUAL}
     * - {@link FUNC_NOTEQUAL}
     *
     * @type {number}
     */ set compareFunc(v) {
        if (this._compareFunc !== v) {
            this._compareFunc = v;
            this.propertyChanged(TEXPROPERTY_COMPARE_FUNC);
        }
    }
    /**
     * Gets the comparison function when {@link compareOnRead} is enabled.
     *
     * @type {number}
     */ get compareFunc() {
        return this._compareFunc;
    }
    /**
     * Sets the integer value specifying the level of anisotropy to apply to the texture. The value
     * ranges from 1 (no anisotropic filtering) to the maximum anisotropy supported by the graphics
     * device (see {@link GraphicsDevice#maxAnisotropy}).
     *
     * @type {number}
     */ set anisotropy(v) {
        if (this._anisotropy !== v) {
            this._anisotropy = v;
            this.propertyChanged(TEXPROPERTY_ANISOTROPY);
        }
    }
    /**
     * Gets the integer value specifying the level of anisotropy to apply to the texture.
     *
     * @type {number}
     */ get anisotropy() {
        return this._anisotropy;
    }
    /**
     * Sets whether the texture should generate/upload mipmaps.
     *
     * @type {boolean}
     */ set mipmaps(v) {
        if (this._mipmaps !== v) {
            if (this.device.isWebGPU) {
                Debug.warn('Texture#mipmaps: mipmap property is currently not allowed to be changed on WebGPU, create the texture appropriately.', this);
            } else if (isIntegerPixelFormat(this._format)) {
                Debug.warn('Texture#mipmaps: mipmap property cannot be changed on an integer texture, will remain false', this);
            } else {
                this._mipmaps = v;
            }
            if (v) this._needsMipmapsUpload = true;
        }
    }
    /**
     * Gets whether the texture should generate/upload mipmaps.
     *
     * @type {boolean}
     */ get mipmaps() {
        return this._mipmaps;
    }
    /**
     * Gets the number of mip levels.
     *
     * @type {number}
     */ get numLevels() {
        return this._numLevels;
    }
    /**
     * Defines if texture can be used as a storage texture by a compute shader.
     *
     * @type {boolean}
     */ get storage() {
        return this._storage;
    }
    /**
     * The width of the texture in pixels.
     *
     * @type {number}
     */ get width() {
        return this._width;
    }
    /**
     * The height of the texture in pixels.
     *
     * @type {number}
     */ get height() {
        return this._height;
    }
    /**
     * The number of depth slices in a 3D texture.
     *
     * @type {number}
     */ get depth() {
        return this._depth;
    }
    /**
     * The pixel format of the texture. Can be:
     *
     * - {@link PIXELFORMAT_R8}
     * - {@link PIXELFORMAT_RG8}
     * - {@link PIXELFORMAT_RGB565}
     * - {@link PIXELFORMAT_RGBA5551}
     * - {@link PIXELFORMAT_RGBA4}
     * - {@link PIXELFORMAT_RGB8}
     * - {@link PIXELFORMAT_RGBA8}
     * - {@link PIXELFORMAT_DXT1}
     * - {@link PIXELFORMAT_DXT3}
     * - {@link PIXELFORMAT_DXT5}
     * - {@link PIXELFORMAT_RGB16F}
     * - {@link PIXELFORMAT_RGBA16F}
     * - {@link PIXELFORMAT_RGB32F}
     * - {@link PIXELFORMAT_RGBA32F}
     * - {@link PIXELFORMAT_ETC1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGBA_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGBA_1}
     * - {@link PIXELFORMAT_111110F}
     * - {@link PIXELFORMAT_ASTC_4x4}
     * - {@link PIXELFORMAT_ATC_RGB}
     * - {@link PIXELFORMAT_ATC_RGBA}
     *
     * @type {number}
     */ get format() {
        return this._format;
    }
    /**
     * Returns true if this texture is a cube map and false otherwise.
     *
     * @type {boolean}
     */ get cubemap() {
        return this._cubemap;
    }
    get gpuSize() {
        const mips = this.pot && this._mipmaps && !(this._compressed && this._levels.length === 1);
        return TextureUtils.calcGpuSize(this._width, this._height, this._depth, this._format, mips, this._cubemap);
    }
    /**
     * Returns true if this texture is a 2D texture array and false otherwise.
     *
     * @type {boolean}
     */ get array() {
        return this._arrayLength > 0;
    }
    /**
     * Returns the number of textures inside this texture if this is a 2D array texture or 0 otherwise.
     *
     * @type {number}
     */ get arrayLength() {
        return this._arrayLength;
    }
    /**
     * Returns true if this texture is a 3D volume and false otherwise.
     *
     * @type {boolean}
     */ get volume() {
        return this._volume;
    }
    /**
     * Sets the texture type.
     *
     * @type {string}
     * @ignore
     */ set type(value) {
        if (this._type !== value) {
            this._type = value;
            // update all shaders to respect the encoding of the texture (needed by the standard material)
            this.device._shadersDirty = true;
        }
    }
    /**
     * Gets the texture type.
     *
     * @type {string}
     * @ignore
     */ get type() {
        return this._type;
    }
    /**
     * Sets the texture's internal format to an sRGB or linear equivalent of its current format.
     * When set to true, the texture is stored in sRGB format and automatically converted to linear
     * space when sampled. When set to false, the texture remains in a linear format. Changing this
     * property recreates the texture on the GPU, which is an expensive operation, so it is
     * preferable to create the texture with the correct format from the start. If the texture
     * format has no sRGB variant, this operation is ignored.
     * This is not a public API and is used by Editor only to update rendering when the sRGB
     * property is changed in the inspector. The higher cost is acceptable in this case.
     *
     * @type {boolean}
     * @ignore
     */ set srgb(value) {
        const currentSrgb = isSrgbPixelFormat(this.format);
        if (value !== currentSrgb) {
            if (value) {
                // switch to sRGB
                const srgbFormat = pixelFormatLinearToGamma(this.format);
                if (this._format !== srgbFormat) {
                    Debug.warn(`Switching format of texture '${this.name}' to sRGB equivalent: ${pixelFormatInfo.get(this.format)?.name} -> ${pixelFormatInfo.get(srgbFormat)?.name}. This is an expensive operation, and the texture should be created using the right format to avoid this.`, this);
                    this._format = srgbFormat;
                    this.recreateImpl();
                    // update all shaders to respect the encoding of the texture (needed by the standard material)
                    this.device._shadersDirty = true;
                }
            } else {
                // switch to linear
                const linearFormat = pixelFormatGammaToLinear(this.format);
                if (this._format !== linearFormat) {
                    Debug.warn(`Switching format of texture '${this.name}' to linear equivalent: ${pixelFormatInfo.get(this.format)?.name} -> ${pixelFormatInfo.get(linearFormat)?.name}. This is an expensive operation, and the texture should be created using the right format to avoid this.`, this);
                    this._format = linearFormat;
                    this.recreateImpl();
                    // update all shaders to respect the encoding of the texture (needed by the standard material)
                    this.device._shadersDirty = true;
                }
            }
        }
    }
    /**
     * Returns true if the texture is stored in an sRGB format, meaning it will be converted to
     * linear space when sampled. Returns false if the texture is stored in a linear format.
     *
     * @type {boolean}
     */ get srgb() {
        return isSrgbPixelFormat(this.format);
    }
    /**
     * Sets whether the texture should be flipped in the Y-direction. Only affects textures
     * with a source that is an image, canvas or video element. Does not affect cubemaps,
     * compressed textures or textures set from raw pixel data. Defaults to true.
     *
     * @type {boolean}
     */ set flipY(flipY) {
        if (this._flipY !== flipY) {
            this._flipY = flipY;
            this._needsUpload = true;
        }
    }
    /**
     * Gets whether the texture should be flipped in the Y-direction.
     *
     * @type {boolean}
     */ get flipY() {
        return this._flipY;
    }
    set premultiplyAlpha(premultiplyAlpha) {
        if (this._premultiplyAlpha !== premultiplyAlpha) {
            this._premultiplyAlpha = premultiplyAlpha;
            this._needsUpload = true;
        }
    }
    get premultiplyAlpha() {
        return this._premultiplyAlpha;
    }
    /**
     * Returns true if all dimensions of the texture are power of two, and false otherwise.
     *
     * @type {boolean}
     */ get pot() {
        return math.powerOfTwo(this._width) && math.powerOfTwo(this._height);
    }
    // get the texture's encoding type
    get encoding() {
        switch(this.type){
            case TEXTURETYPE_RGBM:
                return 'rgbm';
            case TEXTURETYPE_RGBE:
                return 'rgbe';
            case TEXTURETYPE_RGBP:
                return 'rgbp';
        }
        // note that the srgb part only makes sense for texture storing color data
        return requiresManualGamma(this.format) ? 'srgb' : 'linear';
    }
    // Force a full resubmission of the texture to the GPU (used on a context restore event)
    dirtyAll() {
        this._levelsUpdated = this._cubemap ? [
            [
                true,
                true,
                true,
                true,
                true,
                true
            ]
        ] : [
            true
        ];
        this._needsUpload = true;
        this._needsMipmapsUpload = this._mipmaps;
        this._mipmapsUploaded = false;
        this.propertyChanged(TEXPROPERTY_ALL);
    }
    /**
     * Locks a miplevel of the texture, returning a typed array to be filled with pixel data.
     *
     * @param {object} [options] - Optional options object. Valid properties are as follows:
     * @param {number} [options.level] - The mip level to lock with 0 being the top level. Defaults
     * to 0.
     * @param {number} [options.face] - If the texture is a cubemap, this is the index of the face
     * to lock.
     * @param {number} [options.mode] - The lock mode. Can be:
     * - {@link TEXTURELOCK_READ}
     * - {@link TEXTURELOCK_WRITE}
     * Defaults to {@link TEXTURELOCK_WRITE}.
     * @returns {Uint8Array|Uint16Array|Uint32Array|Float32Array} A typed array containing the pixel data of
     * the locked mip level.
     */ lock(options = {}) {
        // Initialize options to some sensible defaults
        options.level ??= 0;
        options.face ??= 0;
        options.mode ??= TEXTURELOCK_WRITE;
        Debug.assert(this._lockedMode === TEXTURELOCK_NONE, 'The texture is already locked. Call `texture.unlock()` before attempting to lock again.', this);
        Debug.assert(options.mode === TEXTURELOCK_READ || options.mode === TEXTURELOCK_WRITE, 'Cannot lock a texture with TEXTURELOCK_NONE. To unlock a texture, call `texture.unlock()`.', this);
        this._lockedMode = options.mode;
        this._lockedLevel = options.level;
        const levels = this.cubemap ? this._levels[options.face] : this._levels;
        if (levels[options.level] === null) {
            // allocate storage for this mip level
            const width = Math.max(1, this._width >> options.level);
            const height = Math.max(1, this._height >> options.level);
            const depth = Math.max(1, this._depth >> options.level);
            const data = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, depth, this._format));
            levels[options.level] = new (getPixelFormatArrayType(this._format))(data);
        }
        return levels[options.level];
    }
    /**
     * Set the pixel data of the texture from a canvas, image, video DOM element. If the texture is
     * a cubemap, the supplied source must be an array of 6 canvases, images or videos.
     *
     * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]} source - A
     * canvas, image or video element, or an array of 6 canvas, image or video elements.
     * @param {number} [mipLevel] - A non-negative integer specifying the image level of detail.
     * Defaults to 0, which represents the base image source. A level value of N, that is greater
     * than 0, represents the image source for the Nth mipmap reduction level.
     */ setSource(source, mipLevel = 0) {
        let invalid = false;
        let width, height;
        if (this._cubemap) {
            if (source[0]) {
                // rely on first face sizes
                width = source[0].width || 0;
                height = source[0].height || 0;
                for(let i = 0; i < 6; i++){
                    const face = source[i];
                    // cubemap becomes invalid if any condition is not satisfied
                    if (!face || // face is missing
                    face.width !== width || // face is different width
                    face.height !== height || // face is different height
                    !this.device._isBrowserInterface(face)) {
                        invalid = true;
                        break;
                    }
                }
            } else {
                // first face is missing
                invalid = true;
            }
            if (!invalid) {
                // mark levels as updated
                for(let i = 0; i < 6; i++){
                    if (this._levels[mipLevel][i] !== source[i]) {
                        this._levelsUpdated[mipLevel][i] = true;
                    }
                }
            }
        } else {
            // check if source is valid type of element
            if (!this.device._isBrowserInterface(source)) {
                invalid = true;
            }
            if (!invalid) {
                // mark level as updated
                if (source !== this._levels[mipLevel]) {
                    this._levelsUpdated[mipLevel] = true;
                }
                if (source instanceof HTMLVideoElement) {
                    width = source.videoWidth;
                    height = source.videoHeight;
                } else {
                    width = source.width;
                    height = source.height;
                }
            }
        }
        if (invalid) {
            // invalid texture
            // default sizes
            this._width = 4;
            this._height = 4;
            // remove levels
            if (this._cubemap) {
                for(let i = 0; i < 6; i++){
                    this._levels[mipLevel][i] = null;
                    this._levelsUpdated[mipLevel][i] = true;
                }
            } else {
                this._levels[mipLevel] = null;
                this._levelsUpdated[mipLevel] = true;
            }
        } else {
            // valid texture
            if (mipLevel === 0) {
                this._width = width;
                this._height = height;
            }
            this._levels[mipLevel] = source;
        }
        // valid or changed state of validity
        if (this._invalid !== invalid || !invalid) {
            this._invalid = invalid;
            // reupload
            this.upload();
        }
    }
    /**
     * Get the pixel data of the texture. If this is a cubemap then an array of 6 images will be
     * returned otherwise a single image.
     *
     * @param {number} [mipLevel] - A non-negative integer specifying the image level of detail.
     * Defaults to 0, which represents the base image source. A level value of N, that is greater
     * than 0, represents the image source for the Nth mipmap reduction level.
     * @returns {HTMLImageElement} The source image of this texture. Can be null if source not
     * assigned for specific image level.
     */ getSource(mipLevel = 0) {
        return this._levels[mipLevel];
    }
    /**
     * Unlocks the currently locked mip level and uploads it to VRAM.
     */ unlock() {
        if (this._lockedMode === TEXTURELOCK_NONE) {
            Debug.warn('pc.Texture#unlock: Attempting to unlock a texture that is not locked.', this);
        }
        // Upload the new pixel data if locked in write mode (default)
        if (this._lockedMode === TEXTURELOCK_WRITE) {
            this.upload();
        }
        this._lockedLevel = -1;
        this._lockedMode = TEXTURELOCK_NONE;
    }
    /**
     * Forces a reupload of the textures pixel data to graphics memory. Ordinarily, this function
     * is called by internally by {@link setSource} and {@link unlock}. However, it still needs to
     * be called explicitly in the case where an HTMLVideoElement is set as the source of the
     * texture.  Normally, this is done once every frame before video textured geometry is
     * rendered.
     */ upload() {
        this._needsUpload = true;
        this._needsMipmapsUpload = this._mipmaps;
        this.impl.uploadImmediate?.(this.device, this);
    }
    /**
     * Download the textures data from the graphics memory to the local memory.
     *
     * Note a public API yet, as not all options are implemented on all platforms.
     *
     * @param {number} x - The left edge of the rectangle.
     * @param {number} y - The top edge of the rectangle.
     * @param {number} width - The width of the rectangle.
     * @param {number} height - The height of the rectangle.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {RenderTarget} [options.renderTarget] - The render target using the texture as a color
     * buffer. Provide as an optimization to avoid creating a new render target. Important especially
     * when this function is called with high frequency (per frame). Note that this is only utilized
     * on the WebGL platform, and ignored on WebGPU.
     * @param {number} [options.mipLevel] - The mip level to download. Defaults to 0.
     * @param {number} [options.face] - The face to download. Defaults to 0.
     * @param {Uint8Array|Uint16Array|Uint32Array|Float32Array} [options.data] - The data buffer to
     * write the pixel data to. If not provided, a new buffer will be created. The type of the buffer
     * must match the texture's format.
     * @param {boolean} [options.immediate] - If true, the read operation will be executed as soon as
     * possible. This has a performance impact, so it should be used only when necessary. Defaults
     * to false.
     * @returns {Promise<Uint8Array|Uint16Array|Uint32Array|Float32Array>} A promise that resolves
     * with the pixel data of the texture.
     * @ignore
     */ read(x, y, width, height, options = {}) {
        return this.impl.read?.(x, y, width, height, options);
    }
    /**
     * Upload texture data asynchronously to the GPU.
     *
     * @param {number} x - The left edge of the rectangle.
     * @param {number} y - The top edge of the rectangle.
     * @param {number} width - The width of the rectangle.
     * @param {number} height - The height of the rectangle.
     * @param {Uint8Array|Uint16Array|Uint32Array|Float32Array} data - The pixel data to upload. This should be a typed array.
     *
     * @returns {Promise<void>} A promise that resolves when the upload is complete.
     * @ignore
     */ write(x, y, width, height, data) {
        return this.impl.write?.(x, y, width, height, data);
    }
}

const textureData = {
    white: [
        255,
        255,
        255,
        255
    ],
    gray: [
        128,
        128,
        128,
        255
    ],
    black: [
        0,
        0,
        0,
        255
    ],
    normal: [
        128,
        128,
        255,
        255
    ],
    pink: [
        255,
        128,
        255,
        255
    ]
};
// class used to hold LUT textures in the device cache
class BuiltInTextures {
    destroy() {
        this.map.forEach((texture)=>{
            texture.destroy();
        });
    }
    constructor(){
        /** @type Map<string, Texture> */ this.map = new Map();
    }
}
// device cache storing built-in textures, taking care of their removal when the device is destroyed
const deviceCache$3 = new DeviceCache();
const getBuiltInTexture = (device, name)=>{
    const cache = deviceCache$3.get(device, ()=>{
        return new BuiltInTextures();
    });
    if (!cache.map.has(name)) {
        const texture = new Texture(device, {
            name: `built-in-texture-${name}`,
            width: 1,
            height: 1,
            format: PIXELFORMAT_RGBA8
        });
        const pixels = texture.lock();
        const data = textureData[name];
        Debug.assert(data, `Data for built-in texture '${name}' not found`);
        pixels.set(data);
        texture.unlock();
        cache.map.set(name, texture);
    }
    return cache.map.get(name);
};

/**
 * @import { BindGroupFormat } from './bind-group-format.js'
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { StorageBuffer } from './storage-buffer.js'
 * @import { Texture } from './texture.js'
 * @import { UniformBuffer } from './uniform-buffer.js'
 */ let id$b = 0;
/**
 * Data structure to hold a bind group and its offsets. This is used by {@link UniformBuffer#update}
 * to return a dynamic bind group and offset for the uniform buffer.
 *
 * @ignore
 */ class DynamicBindGroup {
    constructor(){
        this.offsets = [];
    }
}
/**
 * A bind group represents a collection of {@link UniformBuffer}, {@link Texture} and
 * {@link StorageBuffer} instanced, which can be bind on a GPU for rendering.
 *
 * @ignore
 */ class BindGroup {
    /**
     * Create a new Bind Group.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this uniform buffer.
     * @param {BindGroupFormat} format - Format of the bind group.
     * @param {UniformBuffer} [defaultUniformBuffer] - The default uniform buffer. Typically a bind
     * group only has a single uniform buffer, and this allows easier access.
     */ constructor(graphicsDevice, format, defaultUniformBuffer){
        /**
     * A render version the bind group was last updated on.
     *
     * @type {number}
     * @private
     */ this.renderVersionUpdated = -1;
        /**
     * An array of offsets for each uniform buffer in the bind group. This is the offset in the
     * buffer where the uniform buffer data starts.
     *
     * @type {number[]}
     */ this.uniformBufferOffsets = [];
        this.id = id$b++;
        this.device = graphicsDevice;
        this.format = format;
        this.dirty = true;
        this.impl = graphicsDevice.createBindGroupImpl(this);
        this.textures = [];
        this.storageTextures = [];
        this.storageBuffers = [];
        this.uniformBuffers = [];
        /** @type {UniformBuffer} */ this.defaultUniformBuffer = defaultUniformBuffer;
        if (defaultUniformBuffer) {
            this.setUniformBuffer(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, defaultUniformBuffer);
        }
        Debug.trace(TRACEID_BINDGROUP_ALLOC, `Alloc: Id ${this.id}`, this, format);
    }
    /**
     * Frees resources associated with this bind group.
     */ destroy() {
        this.impl.destroy();
        this.impl = null;
        this.format = null;
        this.defaultUniformBuffer = null;
    }
    /**
     * Assign a uniform buffer to a slot.
     *
     * @param {string} name - The name of the uniform buffer slot
     * @param {UniformBuffer} uniformBuffer - The Uniform buffer to assign to the slot.
     */ setUniformBuffer(name, uniformBuffer) {
        const index = this.format.bufferFormatsMap.get(name);
        Debug.assert(index !== undefined, `Setting a uniform [${name}] on a bind group with id ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
        if (this.uniformBuffers[index] !== uniformBuffer) {
            this.uniformBuffers[index] = uniformBuffer;
            this.dirty = true;
        }
    }
    /**
     * Assign a storage buffer to a slot.
     *
     * @param {string} name - The name of the storage buffer slot.
     * @param {StorageBuffer} storageBuffer - The storage buffer to assign to the slot.
     */ setStorageBuffer(name, storageBuffer) {
        const index = this.format.storageBufferFormatsMap.get(name);
        Debug.assert(index !== undefined, `Setting a storage buffer [${name}] on a bind group with id: ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
        if (this.storageBuffers[index] !== storageBuffer) {
            this.storageBuffers[index] = storageBuffer;
            this.dirty = true;
        }
    }
    /**
     * Assign a texture to a named slot.
     *
     * @param {string} name - The name of the texture slot.
     * @param {Texture} texture - Texture to assign to the slot.
     */ setTexture(name, texture) {
        const index = this.format.textureFormatsMap.get(name);
        Debug.assert(index !== undefined, `Setting a texture [${name}] on a bind group with id: ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
        if (this.textures[index] !== texture) {
            this.textures[index] = texture;
            this.dirty = true;
        } else if (this.renderVersionUpdated < texture.renderVersionDirty) {
            // if the texture properties have changed
            this.dirty = true;
        }
    }
    /**
     * Assign a storage texture to a named slot.
     *
     * @param {string} name - The name of the texture slot.
     * @param {Texture} texture - Texture to assign to the slot.
     */ setStorageTexture(name, texture) {
        const index = this.format.storageTextureFormatsMap.get(name);
        Debug.assert(index !== undefined, `Setting a storage texture [${name}] on a bind group with id: ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
        if (this.storageTextures[index] !== texture) {
            this.storageTextures[index] = texture;
            this.dirty = true;
        } else if (this.renderVersionUpdated < texture.renderVersionDirty) {
            // if the texture properties have changed
            this.dirty = true;
        }
    }
    /**
     * Updates the uniform buffers in this bind group.
     */ updateUniformBuffers() {
        for(let i = 0; i < this.uniformBuffers.length; i++){
            this.uniformBuffers[i].update();
        }
    }
    /**
     * Applies any changes made to the bind group's properties. Note that the content of used
     * uniform buffers needs to be updated before calling this method.
     */ update() {
        // TODO: implement faster version of this, which does not call SetTexture, which does a map lookup
        const { textureFormats, storageTextureFormats, storageBufferFormats } = this.format;
        for(let i = 0; i < textureFormats.length; i++){
            const textureFormat = textureFormats[i];
            let value = textureFormat.scopeId.value;
            // custom error handling for known global textures
            if (!value) {
                if (textureFormat.name === 'uSceneDepthMap') {
                    Debug.errorOnce(`A uSceneDepthMap texture is used by the shader but a scene depth texture is not available. Use CameraComponent.requestSceneDepthMap / enable Depth Grabpass on the Camera Component to enable it. Rendering [${DebugGraphics.toString()}]`);
                    value = getBuiltInTexture(this.device, 'white');
                }
                if (textureFormat.name === 'uSceneColorMap') {
                    Debug.errorOnce(`A uSceneColorMap texture is used by the shader but a scene color texture is not available. Use CameraComponent.requestSceneColorMap / enable Color Grabpass on the Camera Component to enable it. Rendering [${DebugGraphics.toString()}]`);
                    value = getBuiltInTexture(this.device, 'pink');
                }
                // missing generic texture
                if (!value) {
                    Debug.errorOnce(`Texture ${textureFormat.name} is required for rendering but was not set. Rendering [${DebugGraphics.toString()}]`);
                    value = getBuiltInTexture(this.device, 'pink');
                }
            }
            this.setTexture(textureFormat.name, value);
        }
        for(let i = 0; i < storageTextureFormats.length; i++){
            const storageTextureFormat = storageTextureFormats[i];
            const value = storageTextureFormat.scopeId.value;
            Debug.assert(value, `Value was not set when assigning storage texture slot [${storageTextureFormat.name}] to a bind group, while rendering [${DebugGraphics.toString()}]`, this);
            this.setStorageTexture(storageTextureFormat.name, value);
        }
        for(let i = 0; i < storageBufferFormats.length; i++){
            const storageBufferFormat = storageBufferFormats[i];
            const value = storageBufferFormat.scopeId.value;
            Debug.assert(value, `Value was not set when assigning storage buffer slot [${storageBufferFormat.name}] to a bind group, while rendering [${DebugGraphics.toString()}]`, this);
            this.setStorageBuffer(storageBufferFormat.name, value);
        }
        // update uniform buffer offsets
        this.uniformBufferOffsets.length = this.uniformBuffers.length;
        for(let i = 0; i < this.uniformBuffers.length; i++){
            const uniformBuffer = this.uniformBuffers[i];
            // offset
            this.uniformBufferOffsets[i] = uniformBuffer.offset;
            // test if any of the uniform buffers have changed (not their content, but the buffer container itself)
            if (this.renderVersionUpdated < uniformBuffer.renderVersionDirty) {
                this.dirty = true;
            }
        }
        if (this.dirty) {
            this.dirty = false;
            this.renderVersionUpdated = this.device.renderVersion;
            this.impl.update(this);
        }
    }
}

/**
 * BitPacking API - functionality for operating on values stored as bits in a number.
 *
 * @namespace
 */ const BitPacking = {
    /**
     * Sets a value to specified bits of a number.
     *
     * @param {number} storage - Number to store the bits into.
     * @param {number} value - Value to store.
     * @param {number} shift - Number of bits to shift the value.
     * @param {number} [mask] - Mask for the value to limit the number of storage bits. Defaults to 1.
     * @returns {number} Returns the storage updated with the value.
     */ set (storage, value, shift, mask = 1) {
        // clear the space
        const data = storage & ~(mask << shift);
        // set the bits
        return data | value << shift;
    },
    /**
     * Gets the value of specified bits from a number.
     *
     * @param {number} storage - Number to extract the bits from.
     * @param {number} shift - Number of bits to shift the mask.
     * @param {number} [mask] - Mask for the value to limit the number of storage bits. Defaults to 1.
     * @returns {number} Returns the extracted value.
     */ get (storage, shift, mask = 1) {
        return storage >> shift & mask;
    },
    /**
     * Tests if all specified bits are set.
     *
     * @param {number} storage - Number to test.
     * @param {number} shift - Number of bits to shift the mask.
     * @param {number} [mask] - Mask to limit the number of storage bits. Defaults to 1.
     * @returns {boolean} Returns true if all bits in the mask are set in the storage.
     */ all (storage, shift, mask = 1) {
        const shifted = mask << shift;
        return (storage & shifted) === shifted;
    },
    /**
     * Tests if any specified bits are set.
     *
     * @param {number} storage - Number to test.
     * @param {number} shift - Number of bits to shift the mask.
     * @param {number} [mask] - Mask to limit the number of storage bits. Defaults to 1.
     * @returns {boolean} Returns true if any bits in the mask are set in the storage.
     */ any (storage, shift, mask = 1) {
        return (storage & mask << shift) !== 0;
    }
};

// masks (to only keep relevant bits)
const opMask = 0b111;
const factorMask = 0b1111;
// shifts values to where individual parts are stored
const colorOpShift = 0; // 00 - 02 (3bits)
const colorSrcFactorShift = 3; // 03 - 06 (4bits)
const colorDstFactorShift = 7; // 07 - 10 (4bits)
const alphaOpShift = 11; // 11 - 13 (3bits)
const alphaSrcFactorShift = 14; // 14 - 17 (4bits)
const alphaDstFactorShift = 18; // 18 - 21 (4bits)
const redWriteShift = 22; // 22 (1 bit)
const greenWriteShift = 23; // 23 (1 bit)
const blueWriteShift = 24; // 24 (1 bit)
const alphaWriteShift = 25; // 25 (1 bit)
const blendShift = 26; // 26 (1 bit)
// combined values access
const allWriteMasks = 0b1111;
const allWriteShift = redWriteShift;
/**
 * BlendState is a descriptor that defines how output of fragment shader is written and blended
 * into render target. A blend state can be set on a material using {@link Material#blendState},
 * or in some cases on the graphics device using {@link GraphicsDevice#setBlendState}.
 *
 * For the best performance, do not modify blend state after it has been created, but create
 * multiple blend states and assign them to the material or graphics device as needed.
 *
 * @category Graphics
 */ class BlendState {
    /**
     * Create a new BlendState instance.
     *
     * All factor parameters can take the following values:
     *
     * - {@link BLENDMODE_ZERO}
     * - {@link BLENDMODE_ONE}
     * - {@link BLENDMODE_SRC_COLOR}
     * - {@link BLENDMODE_ONE_MINUS_SRC_COLOR}
     * - {@link BLENDMODE_DST_COLOR}
     * - {@link BLENDMODE_ONE_MINUS_DST_COLOR}
     * - {@link BLENDMODE_SRC_ALPHA}
     * - {@link BLENDMODE_SRC_ALPHA_SATURATE}
     * - {@link BLENDMODE_ONE_MINUS_SRC_ALPHA}
     * - {@link BLENDMODE_DST_ALPHA}
     * - {@link BLENDMODE_ONE_MINUS_DST_ALPHA}
     * - {@link BLENDMODE_CONSTANT}
     * - {@link BLENDMODE_ONE_MINUS_CONSTANT}
     *
     * All op parameters can take the following values:
     *
     * - {@link BLENDEQUATION_ADD}
     * - {@link BLENDEQUATION_SUBTRACT}
     * - {@link BLENDEQUATION_REVERSE_SUBTRACT}
     * - {@link BLENDEQUATION_MIN}
     * - {@link BLENDEQUATION_MAX}
     *
     * @param {boolean} [blend] - Enables or disables blending. Defaults to false.
     * @param {number} [colorOp] - Configures color blending operation. Defaults to
     * {@link BLENDEQUATION_ADD}.
     * @param {number} [colorSrcFactor] - Configures source color blending factor. Defaults to
     * {@link BLENDMODE_ONE}.
     * @param {number} [colorDstFactor] - Configures destination color blending factor. Defaults to
     * {@link BLENDMODE_ZERO}.
     * @param {number} [alphaOp] - Configures alpha blending operation. Defaults to
     * {@link BLENDEQUATION_ADD}.
     * @param {number} [alphaSrcFactor] - Configures source alpha blending factor. Defaults to
     * {@link BLENDMODE_ONE}.
     * @param {number} [alphaDstFactor] - Configures destination alpha blending factor. Defaults to
     * {@link BLENDMODE_ZERO}.
     * @param {boolean} [redWrite] - True to enable writing of the red channel and false otherwise.
     * Defaults to true.
     * @param {boolean} [greenWrite] - True to enable writing of the green channel and false
     * otherwise. Defaults to true.
     * @param {boolean} [blueWrite] - True to enable writing of the blue channel and false otherwise.
     * Defaults to true.
     * @param {boolean} [alphaWrite] - True to enable writing of the alpha channel and false
     * otherwise. Defaults to true.
     */ constructor(blend = false, colorOp = BLENDEQUATION_ADD, colorSrcFactor = BLENDMODE_ONE, colorDstFactor = BLENDMODE_ZERO, alphaOp, alphaSrcFactor, alphaDstFactor, redWrite = true, greenWrite = true, blueWrite = true, alphaWrite = true){
        /**
     * Bit field representing the blend state for render target 0.
     *
     * @private
     */ this.target0 = 0;
        this.setColorBlend(colorOp, colorSrcFactor, colorDstFactor);
        this.setAlphaBlend(alphaOp ?? colorOp, alphaSrcFactor ?? colorSrcFactor, alphaDstFactor ?? colorDstFactor);
        this.setColorWrite(redWrite, greenWrite, blueWrite, alphaWrite);
        this.blend = blend;
    }
    /**
     * Sets whether blending is enabled.
     *
     * @type {boolean}
     */ set blend(value) {
        this.target0 = BitPacking.set(this.target0, value ? 1 : 0, blendShift);
    }
    /**
     * Gets whether blending is enabled.
     *
     * @type {boolean}
     */ get blend() {
        return BitPacking.all(this.target0, blendShift);
    }
    setColorBlend(op, srcFactor, dstFactor) {
        this.target0 = BitPacking.set(this.target0, op, colorOpShift, opMask);
        this.target0 = BitPacking.set(this.target0, srcFactor, colorSrcFactorShift, factorMask);
        this.target0 = BitPacking.set(this.target0, dstFactor, colorDstFactorShift, factorMask);
    }
    setAlphaBlend(op, srcFactor, dstFactor) {
        this.target0 = BitPacking.set(this.target0, op, alphaOpShift, opMask);
        this.target0 = BitPacking.set(this.target0, srcFactor, alphaSrcFactorShift, factorMask);
        this.target0 = BitPacking.set(this.target0, dstFactor, alphaDstFactorShift, factorMask);
    }
    setColorWrite(redWrite, greenWrite, blueWrite, alphaWrite) {
        this.redWrite = redWrite;
        this.greenWrite = greenWrite;
        this.blueWrite = blueWrite;
        this.alphaWrite = alphaWrite;
    }
    get colorOp() {
        return BitPacking.get(this.target0, colorOpShift, opMask);
    }
    get colorSrcFactor() {
        return BitPacking.get(this.target0, colorSrcFactorShift, factorMask);
    }
    get colorDstFactor() {
        return BitPacking.get(this.target0, colorDstFactorShift, factorMask);
    }
    get alphaOp() {
        return BitPacking.get(this.target0, alphaOpShift, opMask);
    }
    get alphaSrcFactor() {
        return BitPacking.get(this.target0, alphaSrcFactorShift, factorMask);
    }
    get alphaDstFactor() {
        return BitPacking.get(this.target0, alphaDstFactorShift, factorMask);
    }
    set redWrite(value) {
        this.target0 = BitPacking.set(this.target0, value ? 1 : 0, redWriteShift);
    }
    get redWrite() {
        return BitPacking.all(this.target0, redWriteShift);
    }
    set greenWrite(value) {
        this.target0 = BitPacking.set(this.target0, value ? 1 : 0, greenWriteShift);
    }
    get greenWrite() {
        return BitPacking.all(this.target0, greenWriteShift);
    }
    set blueWrite(value) {
        this.target0 = BitPacking.set(this.target0, value ? 1 : 0, blueWriteShift);
    }
    get blueWrite() {
        return BitPacking.all(this.target0, blueWriteShift);
    }
    set alphaWrite(value) {
        this.target0 = BitPacking.set(this.target0, value ? 1 : 0, alphaWriteShift);
    }
    get alphaWrite() {
        return BitPacking.all(this.target0, alphaWriteShift);
    }
    get allWrite() {
        // return a number with all 4 bits, for fast compare
        return BitPacking.get(this.target0, allWriteShift, allWriteMasks);
    }
    /**
     * Copies the contents of a source blend state to this blend state.
     *
     * @param {BlendState} rhs - A blend state to copy from.
     * @returns {BlendState} Self for chaining.
     */ copy(rhs) {
        this.target0 = rhs.target0;
        return this;
    }
    /**
     * Returns an identical copy of the specified blend state.
     *
     * @returns {this} The result of the cloning.
     */ clone() {
        const clone = new this.constructor();
        return clone.copy(this);
    }
    get key() {
        return this.target0;
    }
    /**
     * Reports whether two BlendStates are equal.
     *
     * @param {BlendState} rhs - The blend state to compare to.
     * @returns {boolean} True if the blend states are equal and false otherwise.
     */ equals(rhs) {
        return this.target0 === rhs.target0;
    }
    static{
        /**
     * A blend state that has blending disabled and writes to all color channels.
     *
     * @type {BlendState}
     * @readonly
     */ this.NOBLEND = Object.freeze(new BlendState());
    }
    static{
        /**
     * A blend state that does not write to color channels.
     *
     * @type {BlendState}
     * @readonly
     */ this.NOWRITE = Object.freeze(new BlendState(undefined, undefined, undefined, undefined, undefined, undefined, undefined, false, false, false, false));
    }
    static{
        /**
     * A blend state that does simple translucency using alpha channel.
     *
     * @type {BlendState}
     * @readonly
     */ this.ALPHABLEND = Object.freeze(new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA));
    }
    static{
        /**
     * A blend state that does simple additive blending.
     *
     * @type {BlendState}
     * @readonly
     */ this.ADDBLEND = Object.freeze(new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE));
    }
}

/**
 * A cache for assigning unique numerical ids to strings.
 */ class StringIds {
    /**
     * Get the id for the given name. If the name has not been seen before, it will be assigned a new
     * id.
     *
     * @param {string} name - The name to get the id for.
     * @returns {number} The id for the given name.
     */ get(name) {
        let value = this.map.get(name);
        if (value === undefined) {
            value = this.id++;
            this.map.set(name, value);
        }
        return value;
    }
    constructor(){
        /** @type {Map<string, number>} */ this.map = new Map();
        /** @type {number} */ this.id = 0;
    }
}

const stringIds$4 = new StringIds();
// masks (to only keep relevant bits)
const funcMask = 0b111;
// shifts values to where individual parts are stored
const funcShift = 0; // 00 - 02 (3bits)
const writeShift = 3; // 03 - 03 (1bit)
/**
 * DepthState is a descriptor that defines how the depth value of the fragment is used by the
 * rendering pipeline. A depth state can be set on a material using {@link Material#depthState},
 * or in some cases on the graphics device using {@link GraphicsDevice#setDepthState}.
 *
 * For the best performance, do not modify depth state after it has been created, but create
 * multiple depth states and assign them to the material or graphics device as needed.
 *
 * @category Graphics
 */ class DepthState {
    /**
     * Create a new Depth State instance.
     *
     * @param {number} func - Controls how the depth of the fragment is compared against the
     * current depth contained in the depth buffer. See {@link DepthState#func} for details.
     * Defaults to {@link FUNC_LESSEQUAL}.
     * @param {boolean} write - If true, depth values are written to the depth buffer of the
     * currently active render target. Defaults to true.
     */ constructor(func = FUNC_LESSEQUAL, write = true){
        /**
     * Bit field representing the depth state.
     *
     * @private
     */ this.data = 0;
        this._depthBias = 0;
        this._depthBiasSlope = 0;
        /**
     * A unique number representing the depth state. You can use this number to quickly compare
     * two depth states for equality. The key is always maintained valid without a dirty flag,
     * to avoid condition check at runtime, considering these change rarely.
     *
     * @type {number}
     */ this.key = 0;
        this.func = func;
        this.write = write;
    }
    /**
     * Sets whether depth testing is performed. If true, a shader fragment is only written to the
     * current render target if it passes the depth test. If false, it is written regardless of
     * what is in the depth buffer. Note that when depth testing is disabled, writes to the depth
     * buffer are also disabled. Defaults to true.
     *
     * @type {boolean}
     */ set test(value) {
        this.func = value ? FUNC_LESSEQUAL : FUNC_ALWAYS;
        this.updateKey();
    }
    /**
     * Gets whether depth testing is performed.
     *
     * @type {boolean}
     */ get test() {
        return this.func !== FUNC_ALWAYS;
    }
    /**
     * Sets whether depth writing is performed. If true, shader write a depth value to the depth
     * buffer of the currently active render target. If false, no depth value is written.
     *
     * @type {boolean}
     */ set write(value) {
        this.data = BitPacking.set(this.data, value ? 1 : 0, writeShift);
        this.updateKey();
    }
    /**
     * Gets whether depth writing is performed.
     *
     * @type {boolean}
     */ get write() {
        return BitPacking.all(this.data, writeShift);
    }
    /**
     * Sets the depth testing function. Controls how the depth of the fragment is compared against
     * the current depth contained in the depth buffer. Can be:
     *
     * - {@link FUNC_NEVER}: don't draw
     * - {@link FUNC_LESS}: draw if new depth < depth buffer
     * - {@link FUNC_EQUAL}: draw if new depth == depth buffer
     * - {@link FUNC_LESSEQUAL}: draw if new depth <= depth buffer
     * - {@link FUNC_GREATER}: draw if new depth > depth buffer
     * - {@link FUNC_NOTEQUAL}: draw if new depth != depth buffer
     * - {@link FUNC_GREATEREQUAL}: draw if new depth >= depth buffer
     * - {@link FUNC_ALWAYS}: always draw
     *
     * @type {number}
     */ set func(value) {
        this.data = BitPacking.set(this.data, value, funcShift, funcMask);
        this.updateKey();
    }
    /**
     * Gets the depth testing function.
     *
     * @type {number}
     */ get func() {
        return BitPacking.get(this.data, funcShift, funcMask);
    }
    /**
     * Sets the constant depth bias added to each fragment's depth. Useful for decals to prevent
     * z-fighting. Typically a small negative value (-0.1) is used to render the mesh slightly
     * closer to the camera. Defaults to 0.
     *
     * @type {number}
     */ set depthBias(value) {
        this._depthBias = value;
        this.updateKey();
    }
    /**
     * Gets the constant depth bias added to each fragment's depth.
     *
     * @type {number}
     */ get depthBias() {
        return this._depthBias;
    }
    /**
     * Sets the depth bias that scales with the fragment's slope. Defaults to 0.
     *
     * @type {number}
     */ set depthBiasSlope(value) {
        this._depthBiasSlope = value;
        this.updateKey();
    }
    /**
     * Gets the depth bias that scales with the fragment's slope.
     *
     * @type {number}
     */ get depthBiasSlope() {
        return this._depthBiasSlope;
    }
    /**
     * Copies the contents of a source depth state to this depth state.
     *
     * @param {DepthState} rhs - A depth state to copy from.
     * @returns {DepthState} Self for chaining.
     */ copy(rhs) {
        this.data = rhs.data;
        this._depthBias = rhs._depthBias;
        this._depthBiasSlope = rhs._depthBiasSlope;
        this.key = rhs.key;
        return this;
    }
    /**
     * Returns an identical copy of the specified depth state.
     *
     * @returns {this} The result of the cloning.
     */ clone() {
        const clone = new this.constructor();
        return clone.copy(this);
    }
    updateKey() {
        const { data, _depthBias, _depthBiasSlope } = this;
        const key = `${data}-${_depthBias}-${_depthBiasSlope}`;
        // convert string to a unique number
        this.key = stringIds$4.get(key);
    }
    /**
     * Reports whether two DepthStates are equal.
     *
     * @param {DepthState} rhs - The depth state to compare to.
     * @returns {boolean} True if the depth states are equal and false otherwise.
     */ equals(rhs) {
        return this.key === rhs.key;
    }
    static{
        /**
     * A default depth state that has the depth testing function set to {@link FUNC_LESSEQUAL} and
     * depth writes enabled.
     *
     * @type {DepthState}
     * @readonly
     */ this.DEFAULT = Object.freeze(new DepthState());
    }
    static{
        /**
     * A depth state that always passes the fragment but does not write depth to the depth buffer.
     *
     * @type {DepthState}
     * @readonly
     */ this.NODEPTH = Object.freeze(new DepthState(FUNC_ALWAYS, false));
    }
    static{
        /**
     * A depth state that always passes the fragment and writes depth to the depth buffer.
     *
     * @type {DepthState}
     * @readonly
     */ this.WRITEDEPTH = Object.freeze(new DepthState(FUNC_ALWAYS, true));
    }
}

class Version {
    equals(other) {
        return this.globalId === other.globalId && this.revision === other.revision;
    }
    copy(other) {
        this.globalId = other.globalId;
        this.revision = other.revision;
    }
    reset() {
        this.globalId = 0;
        this.revision = 0;
    }
    constructor(){
        this.globalId = 0;
        this.revision = 0;
    }
}

let idCounter = 0;
class VersionedObject {
    constructor(){
        // Increment the global object ID counter
        idCounter++;
        // Create a version for this object
        this.version = new Version();
        // Set the unique object ID
        this.version.globalId = idCounter;
    }
    increment() {
        // Increment the revision number
        this.version.revision++;
    }
}

/**
 * The scope for a variable.
 *
 * @category Graphics
 */ class ScopeId {
    /**
     * Create a new ScopeId instance.
     *
     * @param {string} name - The variable name.
     */ constructor(name){
        /**
         * The variable name.
         *
         * @type {string}
         */ this.name = name;
        // Set the default value
        this.value = null;
        // Create the version object
        this.versionObject = new VersionedObject();
    }
    // Don't stringify ScopeId to JSON by JSON.stringify, as this stores 'value'
    // which is not needed. This is used when stringifying a uniform buffer format, which
    // internally stores the scope.
    toJSON(key) {
        return undefined;
    }
    /**
     * Set variable value.
     *
     * @param {*} value - The value.
     */ setValue(value) {
        // Set the new value
        this.value = value;
        // Increment the revision
        this.versionObject.increment();
    }
    /**
     * Get variable value.
     *
     * @returns {*} The value.
     */ getValue() {
        return this.value;
    }
}

/**
 * The scope for variables.
 *
 * @category Graphics
 */ class ScopeSpace {
    /**
     * Create a new ScopeSpace instance.
     *
     * @param {string} name - The scope name.
     */ constructor(name){
        /**
         * The scope name.
         *
         * @type {string}
         */ this.name = name;
        // Create map which maps a uniform name into ScopeId
        this.variables = new Map();
    }
    /**
     * Get (or create, if it doesn't already exist) a variable in the scope.
     *
     * @param {string} name - The variable name.
     * @returns {ScopeId} The variable instance.
     */ resolve(name) {
        // add new ScopeId if it does not exist yet
        if (!this.variables.has(name)) {
            this.variables.set(name, new ScopeId(name));
        }
        // return the ScopeId instance
        return this.variables.get(name);
    }
    /**
     * Clears value for any uniform with matching value (used to remove deleted textures).
     *
     * @param {*} value - The value to clear.
     * @ignore
     */ removeValue(value) {
        for(const uniformName in this.variables){
            const uniform = this.variables[uniformName];
            if (uniform.value === value) {
                uniform.value = null;
            }
        }
    }
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { VertexFormat } from './vertex-format.js'
 */ let id$a = 0;
/**
 * A vertex buffer is the mechanism via which the application specifies vertex data to the graphics
 * hardware.
 *
 * @category Graphics
 */ class VertexBuffer {
    /**
     * Create a new VertexBuffer instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this vertex
     * buffer.
     * @param {VertexFormat} format - The vertex format of this vertex buffer.
     * @param {number} numVertices - The number of vertices that this vertex buffer will hold.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {number} [options.usage] - The usage type of the vertex buffer (see BUFFER_*).
     * Defaults to BUFFER_STATIC.
     * @param {ArrayBuffer} [options.data] - Initial data.
     * @param {boolean} [options.storage] - Defines if the vertex buffer can be used as a storage
     * buffer by a compute shader. Defaults to false. Only supported on WebGPU.
     */ constructor(graphicsDevice, format, numVertices, options){
        this.usage = BUFFER_STATIC;
        Debug.assert(arguments.length <= 4 && (!options || typeof options === 'object'), 'incorrect arguments');
        // By default, vertex buffers are static (better for performance since buffer data can be cached in VRAM)
        this.usage = options?.usage ?? BUFFER_STATIC;
        this.device = graphicsDevice;
        this.format = format;
        this.numVertices = numVertices;
        this.id = id$a++;
        this.impl = graphicsDevice.createVertexBufferImpl(this, format, options);
        // Calculate the size. If format contains verticesByteSize (non-interleaved format), use it
        this.numBytes = format.verticesByteSize ? format.verticesByteSize : format.size * numVertices;
        this.adjustVramSizeTracking(graphicsDevice._vram, this.numBytes);
        // Allocate the storage
        const initialData = options?.data;
        if (initialData) {
            this.setData(initialData);
        } else {
            this.storage = new ArrayBuffer(this.numBytes);
        }
        this.device.buffers.push(this);
    }
    /**
     * Frees resources associated with this vertex buffer.
     */ destroy() {
        // stop tracking the vertex buffer
        const device = this.device;
        const idx = device.buffers.indexOf(this);
        if (idx !== -1) {
            device.buffers.splice(idx, 1);
        }
        if (this.impl.initialized) {
            this.impl.destroy(device);
            this.adjustVramSizeTracking(device._vram, -this.storage.byteLength);
        }
    }
    adjustVramSizeTracking(vram, size) {
        Debug.trace(TRACEID_VRAM_VB, `${this.id} size: ${size} vram.vb: ${vram.vb} => ${vram.vb + size}`);
        vram.vb += size;
    }
    /**
     * Called when the rendering context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        this.impl.loseContext();
    }
    /**
     * Returns the data format of the specified vertex buffer.
     *
     * @returns {VertexFormat} The data format of the specified vertex buffer.
     */ getFormat() {
        return this.format;
    }
    /**
     * Returns the usage type of the specified vertex buffer. This indicates whether the buffer can
     * be modified once and used many times {@link BUFFER_STATIC}, modified repeatedly and used
     * many times {@link BUFFER_DYNAMIC} or modified once and used at most a few times
     * {@link BUFFER_STREAM}.
     *
     * @returns {number} The usage type of the vertex buffer (see BUFFER_*).
     */ getUsage() {
        return this.usage;
    }
    /**
     * Returns the number of vertices stored in the specified vertex buffer.
     *
     * @returns {number} The number of vertices stored in the vertex buffer.
     */ getNumVertices() {
        return this.numVertices;
    }
    /**
     * Returns a mapped memory block representing the content of the vertex buffer.
     *
     * @returns {ArrayBuffer} An array containing the byte data stored in the vertex buffer.
     */ lock() {
        return this.storage;
    }
    /**
     * Notifies the graphics engine that the client side copy of the vertex buffer's memory can be
     * returned to the control of the graphics driver.
     */ unlock() {
        // Upload the new vertex data
        this.impl.unlock(this);
    }
    /**
     * Copies data into vertex buffer's memory.
     *
     * @param {ArrayBuffer} [data] - Source data to copy.
     * @returns {boolean} True if function finished successfully, false otherwise.
     */ setData(data) {
        if (data.byteLength !== this.numBytes) {
            Debug.error(`VertexBuffer: wrong initial data size: expected ${this.numBytes}, got ${data.byteLength}`);
            return false;
        }
        this.storage = data;
        this.unlock();
        return true;
    }
}

/**
 * Calculates simple hash value of a string. Designed for performance, not perfect.
 *
 * @param {string} str - String.
 * @returns {number} Hash value.
 */ function hashCode(str) {
    if (str === null || str === undefined) {
        return 0;
    }
    let hash = 0;
    for(let i = 0, len = str.length; i < len; i++){
        hash = (hash << 5) - hash + str.charCodeAt(i);
        // Convert to 32bit integer
        hash |= 0;
    }
    return hash;
}
/**
 * Calculates simple 32bit hash value of an array of 32bit integer numbers. Designed for
 * performance, but provides good distribution with small number of collisions. Based on
 * FNV-1a non-cryptographic hash function.
 *.
 * @param {number[]|Uint32Array} array - Array of 32bit integer numbers to hash.
 * @returns {number} 32bit unsigned integer hash value.
 */ function hash32Fnv1a(array) {
    const prime = 16777619;
    let hash = 2166136261;
    for(let i = 0; i < array.length; i++){
        hash ^= array[i];
        hash *= prime;
    }
    return hash >>> 0; // Ensure non-negative integer
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 */ const stringIds$3 = new StringIds();
const webgpuValidElementSizes = [
    2,
    4,
    8,
    12,
    16
];
// device cache storing the default instancing format per device
const deviceCache$2 = new DeviceCache();
/**
 * A vertex format is a descriptor that defines the layout of vertex data inside a
 * {@link VertexBuffer}.
 *
 * @property {object[]} elements The vertex attribute elements.
 * @property {string} elements[].name The meaning of the vertex element. This is used to link the
 * vertex data to a shader input. Can be:
 *
 * - {@link SEMANTIC_POSITION}
 * - {@link SEMANTIC_NORMAL}
 * - {@link SEMANTIC_TANGENT}
 * - {@link SEMANTIC_BLENDWEIGHT}
 * - {@link SEMANTIC_BLENDINDICES}
 * - {@link SEMANTIC_COLOR}
 * - {@link SEMANTIC_TEXCOORD0}
 * - {@link SEMANTIC_TEXCOORD1}
 * - {@link SEMANTIC_TEXCOORD2}
 * - {@link SEMANTIC_TEXCOORD3}
 * - {@link SEMANTIC_TEXCOORD4}
 * - {@link SEMANTIC_TEXCOORD5}
 * - {@link SEMANTIC_TEXCOORD6}
 * - {@link SEMANTIC_TEXCOORD7}
 *
 * If vertex data has a meaning other that one of those listed above, use the user-defined
 * semantics: {@link SEMANTIC_ATTR0} to {@link SEMANTIC_ATTR15}.
 * @property {number} elements[].numComponents The number of components of the vertex attribute.
 * Can be 1, 2, 3 or 4.
 * @property {number} elements[].dataType The data type of the attribute. Can be:
 *
 * - {@link TYPE_INT8}
 * - {@link TYPE_UINT8}
 * - {@link TYPE_INT16}
 * - {@link TYPE_UINT16}
 * - {@link TYPE_INT32}
 * - {@link TYPE_UINT32}
 * - {@link TYPE_FLOAT32}
 * - {@link TYPE_FLOAT16}
 * @property {boolean} elements[].normalize If true, vertex attribute data will be mapped from a 0
 * to 255 range down to 0 to 1 when fed to a shader. If false, vertex attribute data is left
 * unchanged. If this property is unspecified, false is assumed.
 * @property {number} elements[].offset The number of initial bytes at the start of a vertex that
 * are not relevant to this attribute.
 * @property {number} elements[].stride The number of total bytes that are between the start of one
 * vertex, and the start of the next.
 * @property {number} elements[].size The size of the attribute in bytes.
 * @category Graphics
 */ class VertexFormat {
    /**
     * @typedef {object} AttributeDescription
     * @property {string} semantic - The meaning of the vertex element. This is used to
     * link the vertex data to a shader input. Can be:
     *
     * - {@link SEMANTIC_POSITION}
     * - {@link SEMANTIC_NORMAL}
     * - {@link SEMANTIC_TANGENT}
     * - {@link SEMANTIC_BLENDWEIGHT}
     * - {@link SEMANTIC_BLENDINDICES}
     * - {@link SEMANTIC_COLOR}
     * - {@link SEMANTIC_TEXCOORD0}
     * - {@link SEMANTIC_TEXCOORD1}
     * - {@link SEMANTIC_TEXCOORD2}
     * - {@link SEMANTIC_TEXCOORD3}
     * - {@link SEMANTIC_TEXCOORD4}
     * - {@link SEMANTIC_TEXCOORD5}
     * - {@link SEMANTIC_TEXCOORD6}
     * - {@link SEMANTIC_TEXCOORD7}
     *
     * If vertex data has a meaning other that one of those listed above, use the user-defined
     * semantics: {@link SEMANTIC_ATTR0} to {@link SEMANTIC_ATTR15}.
     * @property {number} components - The number of components of the vertex attribute.
     * Can be 1, 2, 3 or 4.
     * @property {number} type - The data type of the attribute. Can be:
     *
     * - {@link TYPE_INT8}
     * - {@link TYPE_UINT8}
     * - {@link TYPE_INT16}
     * - {@link TYPE_UINT16}
     * - {@link TYPE_INT32}
     * - {@link TYPE_UINT32}
     * - {@link TYPE_FLOAT16}
     * - {@link TYPE_FLOAT32}
     *
     * @property {boolean} [normalize] - If true, vertex attribute data will be mapped
     * from a 0 to 255 range down to 0 to 1 when fed to a shader. If false, vertex attribute data
     * is left unchanged. If this property is unspecified, false is assumed. This property is
     * ignored when asInt is true.
     * @property {boolean} [asInt] - If true, vertex attribute data will be accessible
     * as integer numbers in shader code. Defaults to false, which means that vertex attribute data
     * will be accessible as floating point numbers. Can be only used with INT and UINT data types.
     */ /**
     * Create a new VertexFormat instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this vertex
     * format.
     * @param {AttributeDescription[]} description - An array of vertex attribute descriptions.
     * @param {number} [vertexCount] - When specified, vertex format will be set up for
     * non-interleaved format with a specified number of vertices. (example: PPPPNNNNCCCC), where
     * arrays of individual attributes will be stored one right after the other (subject to
     * alignment requirements). Note that in this case, the format depends on the number of
     * vertices, and needs to change when the number of vertices changes. When not specified,
     * vertex format will be interleaved. (example: PNCPNCPNCPNC).
     * @example
     * // Specify 3-component positions (x, y, z)
     * const vertexFormat = new pc.VertexFormat(graphicsDevice, [
     *     { semantic: pc.SEMANTIC_POSITION, components: 3, type: pc.TYPE_FLOAT32 }
     * ]);
     * @example
     * // Specify 2-component positions (x, y), a texture coordinate (u, v) and a vertex color (r, g, b, a)
     * const vertexFormat = new pc.VertexFormat(graphicsDevice, [
     *     { semantic: pc.SEMANTIC_POSITION, components: 2, type: pc.TYPE_FLOAT32 },
     *     { semantic: pc.SEMANTIC_TEXCOORD0, components: 2, type: pc.TYPE_FLOAT32 },
     *     { semantic: pc.SEMANTIC_COLOR, components: 4, type: pc.TYPE_UINT8, normalize: true }
     * ]);
     */ constructor(graphicsDevice, description, vertexCount){
        this.device = graphicsDevice;
        this._elements = [];
        this.hasUv0 = false;
        this.hasUv1 = false;
        this.hasColor = false;
        this.hasTangents = false;
        this.verticesByteSize = 0;
        this.vertexCount = vertexCount;
        this.interleaved = vertexCount === undefined;
        // true if the vertex format represents an instancing vertex buffer
        this.instancing = false;
        // calculate total size of the vertex
        this.size = description.reduce((total, desc)=>{
            return total + Math.ceil(desc.components * typedArrayTypesByteSize[desc.type] / 4) * 4;
        }, 0);
        let offset = 0, elementSize;
        for(let i = 0, len = description.length; i < len; i++){
            const elementDesc = description[i];
            elementSize = elementDesc.components * typedArrayTypesByteSize[elementDesc.type];
            // WebGPU has limited element size support (for example uint16x3 is not supported)
            Debug.assert(VertexFormat.isElementValid(graphicsDevice, elementDesc), `WebGPU does not support the format of vertex element ${elementDesc.semantic} : ${vertexTypesNames[elementDesc.type]} x ${elementDesc.components}`);
            // align up the offset to elementSize (when vertexCount is specified only - case of non-interleaved format)
            if (vertexCount) {
                offset = math.roundUp(offset, elementSize);
                // non-interleaved format with elementSize not multiple of 4 might be slower on some platforms - padding is recommended to align its size
                // example: use 4 x TYPE_UINT8 instead of 3 x TYPE_UINT8
                Debug.assert(elementSize % 4 === 0, `Non-interleaved vertex format with element size not multiple of 4 can have performance impact on some platforms. Element size: ${elementSize}`);
            }
            const asInt = elementDesc.asInt ?? false;
            const normalize = asInt ? false : elementDesc.normalize ?? false;
            const element = {
                name: elementDesc.semantic,
                offset: vertexCount ? offset : elementDesc.hasOwnProperty('offset') ? elementDesc.offset : offset,
                stride: vertexCount ? elementSize : elementDesc.hasOwnProperty('stride') ? elementDesc.stride : this.size,
                dataType: elementDesc.type,
                numComponents: elementDesc.components,
                normalize: normalize,
                size: elementSize,
                asInt: asInt
            };
            this._elements.push(element);
            if (vertexCount) {
                offset += elementSize * vertexCount;
            } else {
                offset += Math.ceil(elementSize / 4) * 4;
            }
            if (elementDesc.semantic === SEMANTIC_TEXCOORD0) {
                this.hasUv0 = true;
            } else if (elementDesc.semantic === SEMANTIC_TEXCOORD1) {
                this.hasUv1 = true;
            } else if (elementDesc.semantic === SEMANTIC_COLOR) {
                this.hasColor = true;
            } else if (elementDesc.semantic === SEMANTIC_TANGENT) {
                this.hasTangents = true;
            }
        }
        if (vertexCount) {
            this.verticesByteSize = offset;
        }
        this._evaluateHash();
    }
    get elements() {
        return this._elements;
    }
    /**
     * The {@link VertexFormat} used to store matrices of type {@link Mat4} for hardware instancing.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to create this vertex
     * format.
     * @returns {VertexFormat} The default instancing vertex format.
     */ static getDefaultInstancingFormat(graphicsDevice) {
        // get it from the device cache, or create a new one if not cached yet
        return deviceCache$2.get(graphicsDevice, ()=>{
            return new VertexFormat(graphicsDevice, [
                {
                    semantic: SEMANTIC_ATTR11,
                    components: 4,
                    type: TYPE_FLOAT32
                },
                {
                    semantic: SEMANTIC_ATTR12,
                    components: 4,
                    type: TYPE_FLOAT32
                },
                {
                    semantic: SEMANTIC_ATTR14,
                    components: 4,
                    type: TYPE_FLOAT32
                },
                {
                    semantic: SEMANTIC_ATTR15,
                    components: 4,
                    type: TYPE_FLOAT32
                }
            ]);
        });
    }
    static isElementValid(graphicsDevice, elementDesc) {
        const elementSize = elementDesc.components * typedArrayTypesByteSize[elementDesc.type];
        if (graphicsDevice.isWebGPU && !webgpuValidElementSizes.includes(elementSize)) {
            return false;
        }
        return true;
    }
    /**
     * Applies any changes made to the VertexFormat's properties.
     *
     * @private
     */ update() {
        // Note that this is used only by vertex attribute morphing on the WebGL.
        Debug.assert(!this.device.isWebGPU, 'VertexFormat#update is not supported on WebGPU and VertexFormat cannot be modified.');
        this._evaluateHash();
    }
    /**
     * Evaluates hash values for the format allowing fast compare of batching / rendering compatibility.
     *
     * @private
     */ _evaluateHash() {
        const stringElementsBatch = [];
        const stringElementsRender = [];
        const len = this._elements.length;
        for(let i = 0; i < len; i++){
            const { name, dataType, numComponents, normalize, offset, stride, size, asInt } = this._elements[i];
            // create string description of each element that is relevant for batching
            const stringElementBatch = name + dataType + numComponents + normalize + asInt;
            stringElementsBatch.push(stringElementBatch);
            // create string description of each element that is relevant for rendering
            const stringElementRender = stringElementBatch + offset + stride + size;
            stringElementsRender.push(stringElementRender);
        }
        // sort batching ones alphabetically to make the hash order independent
        stringElementsBatch.sort();
        const batchingString = stringElementsBatch.join();
        this.batchingHash = hashCode(batchingString);
        // shader processing hash - all elements that are used by the ShaderProcessor processing attributes
        // at the moment this matches the batching hash
        this.shaderProcessingHashString = batchingString;
        // rendering hash
        this.renderingHashString = stringElementsRender.join('_');
        this.renderingHash = stringIds$3.get(this.renderingHashString);
    }
}

const stringIds$2 = new StringIds();
/**
 * Holds stencil test settings.
 *
 * @category Graphics
 */ class StencilParameters {
    /**
     * Sets the comparison function that decides if the pixel should be written, based on the
     * current stencil buffer value, reference value, and mask value. Can be:
     *
     * - {@link FUNC_NEVER}: never pass
     * - {@link FUNC_LESS}: pass if (ref & mask) < (stencil & mask)
     * - {@link FUNC_EQUAL}: pass if (ref & mask) == (stencil & mask)
     * - {@link FUNC_LESSEQUAL}: pass if (ref & mask) <= (stencil & mask)
     * - {@link FUNC_GREATER}: pass if (ref & mask) > (stencil & mask)
     * - {@link FUNC_NOTEQUAL}: pass if (ref & mask) != (stencil & mask)
     * - {@link FUNC_GREATEREQUAL}: pass if (ref & mask) >= (stencil & mask)
     * - {@link FUNC_ALWAYS}: always pass
     *
     * @type {number}
     */ set func(value) {
        this._func = value;
        this._dirty = true;
    }
    /**
     * Sets the comparison function that decides if the pixel should be written.
     *
     * @type {number}
     */ get func() {
        return this._func;
    }
    /**
     * Sets the stencil test reference value used in comparisons.
     *
     * @type {number}
     */ set ref(value) {
        this._ref = value;
        this._dirty = true;
    }
    /**
     * Gets the stencil test reference value used in comparisons.
     *
     * @type {number}
     */ get ref() {
        return this._ref;
    }
    /**
     * Sets the operation to perform if stencil test is failed. Can be:
     *
     * - {@link STENCILOP_KEEP}: don't change the stencil buffer value
     * - {@link STENCILOP_ZERO}: set value to zero
     * - {@link STENCILOP_REPLACE}: replace value with the reference value.
     * - {@link STENCILOP_INCREMENT}: increment the value
     * - {@link STENCILOP_INCREMENTWRAP}: increment the value, but wrap it to zero when it's larger
     * than a maximum representable value
     * - {@link STENCILOP_DECREMENT}: decrement the value
     * - {@link STENCILOP_DECREMENTWRAP}: decrement the value, but wrap it to a maximum
     * representable value, if the current value is 0
     * - {@link STENCILOP_INVERT}: invert the value bitwise
     *
     * @type {number}
     */ set fail(value) {
        this._fail = value;
        this._dirty = true;
    }
    /**
     * Gets the operation to perform if stencil test is failed.
     *
     * @type {number}
     */ get fail() {
        return this._fail;
    }
    /**
     * Sets the operation to perform if depth test is failed. Accepts the same values as `fail`.
     *
     * @type {number}
     */ set zfail(value) {
        this._zfail = value;
        this._dirty = true;
    }
    /**
     * Gets the operation to perform if depth test is failed.
     *
     * @type {number}
     */ get zfail() {
        return this._zfail;
    }
    /**
     * Sets the operation to perform if both stencil and depth test are passed. Accepts the same
     * values as `fail`.
     *
     * @type {number}
     */ set zpass(value) {
        this._zpass = value;
        this._dirty = true;
    }
    /**
     * Gets the operation to perform if both stencil and depth test are passed.
     *
     * @type {number}
     */ get zpass() {
        return this._zpass;
    }
    /**
     * Sets the mask applied to stencil buffer value and reference value before comparison.
     *
     * @type {number}
     */ set readMask(value) {
        this._readMask = value;
        this._dirty = true;
    }
    /**
     * Gets the mask applied to stencil buffer value and reference value before comparison.
     *
     * @type {number}
     */ get readMask() {
        return this._readMask;
    }
    /**
     * Sets the bit mask applied to the stencil value when written.
     *
     * @type {number}
     */ set writeMask(value) {
        this._writeMask = value;
        this._dirty = true;
    }
    /**
     * Gets the bit mask applied to the stencil value when written.
     *
     * @type {number}
     */ get writeMask() {
        return this._writeMask;
    }
    /**
     * Create a new StencilParameters instance.
     *
     * @param {object} [options] - Options object to configure the stencil parameters.
     */ constructor(options = {}){
        /**
     * @type {boolean}
     * @private
     */ this._dirty = true;
        this._func = options.func ?? FUNC_ALWAYS;
        this._ref = options.ref ?? 0;
        this._readMask = options.readMask ?? 0xFF;
        this._writeMask = options.writeMask ?? 0xFF;
        this._fail = options.fail ?? STENCILOP_KEEP; // keep == 0
        this._zfail = options.zfail ?? STENCILOP_KEEP;
        this._zpass = options.zpass ?? STENCILOP_KEEP;
        // Evaluate key here. This evaluates the key for the DEFAULT instance, which is important,
        // as during rendering it gets copied and the key would get evaluated each time.
        this._evalKey();
    }
    _evalKey() {
        const { _func, _ref, _fail, _zfail, _zpass, _readMask, _writeMask } = this;
        const key = `${_func},${_ref},${_fail},${_zfail},${_zpass},${_readMask},${_writeMask}`;
        this._key = stringIds$2.get(key);
        this._dirty = false;
    }
    get key() {
        if (this._dirty) {
            this._evalKey();
        }
        return this._key;
    }
    /**
     * Copies the contents of a source stencil parameters to this stencil parameters.
     *
     * @param {StencilParameters} rhs - A stencil parameters to copy from.
     * @returns {StencilParameters} Self for chaining.
     */ copy(rhs) {
        this._func = rhs._func;
        this._ref = rhs._ref;
        this._readMask = rhs._readMask;
        this._writeMask = rhs._writeMask;
        this._fail = rhs._fail;
        this._zfail = rhs._zfail;
        this._zpass = rhs._zpass;
        this._dirty = rhs._dirty;
        this._key = rhs._key;
        return this;
    }
    /**
     * Clone the stencil parameters.
     *
     * @returns {StencilParameters} A cloned StencilParameters object.
     */ clone() {
        const clone = new this.constructor();
        return clone.copy(this);
    }
    static{
        /**
     * A default stencil state.
     *
     * @type {StencilParameters}
     * @readonly
     */ this.DEFAULT = Object.freeze(new StencilParameters());
    }
}

/**
 * @import { Compute } from './compute.js'
 * @import { DEVICETYPE_WEBGL2, DEVICETYPE_WEBGPU } from './constants.js'
 * @import { DynamicBuffers } from './dynamic-buffers.js'
 * @import { GpuProfiler } from './gpu-profiler.js'
 * @import { IndexBuffer } from './index-buffer.js'
 * @import { RenderTarget } from './render-target.js'
 * @import { Shader } from './shader.js'
 * @import { Texture } from './texture.js'
 * @import { StorageBuffer } from './storage-buffer.js';
 */ const _tempSet$1 = new Set();
/**
 * The graphics device manages the underlying graphics context. It is responsible for submitting
 * render state changes and graphics primitives to the hardware. A graphics device is tied to a
 * specific canvas HTML element. It is valid to have more than one canvas element per page and
 * create a new graphics device against each.
 *
 * @category Graphics
 */ class GraphicsDevice extends EventHandler {
    static{
        this.EVENT_RESIZE = 'resizecanvas';
    }
    constructor(canvas, options){
        super(), /**
     * The render target representing the main back-buffer.
     *
     * @type {RenderTarget|null}
     * @ignore
     */ this.backBuffer = null, /**
     * The dimensions of the back buffer.
     *
     * @ignore
     */ this.backBufferSize = new Vec2(), /**
     * True if the back buffer should use anti-aliasing.
     *
     * @type {boolean}
     */ this.backBufferAntialias = false, /**
     * True if the deviceType is WebGPU
     *
     * @type {boolean}
     * @readonly
     */ this.isWebGPU = false, /**
     * True if the deviceType is WebGL2
     *
     * @type {boolean}
     * @readonly
     */ this.isWebGL2 = false, /**
     * True if the back-buffer is using HDR format, which means that the browser will display the
     * rendered images in high dynamic range mode. This is true if the options.displayFormat is set
     * to {@link DISPLAYFORMAT_HDR} when creating the graphics device using
     * {@link createGraphicsDevice}, and HDR is supported by the device.
     */ this.isHdr = false, /**
     * The maximum number of indirect draw calls that can be used within a single frame. Used on
     * WebGPU only. This needs to be adjusted based on the maximum number of draw calls that can
     * be used within a single frame. Defaults to 1024.
     *
     * @type {number}
     */ this.maxIndirectDrawCount = 1024, /**
     * The maximum supported number of color buffers attached to a render target.
     *
     * @type {number}
     * @readonly
     */ this.maxColorAttachments = 1, /**
     * The maximum supported number of hardware anti-aliasing samples.
     *
     * @readonly
     * @type {number}
     */ this.maxSamples = 1, /**
     * True if the device supports compute shaders.
     *
     * @readonly
     * @type {boolean}
     */ this.supportsCompute = false, /**
     * True if the device can read from StorageTexture in the compute shader. By default, the
     * storage texture can be only used with the write operation.
     * When a shader uses this feature, it's recommended to use a `requires` directive to signal the
     * potential for non-portability at the top of the WGSL shader code:
     * ```javascript
     * requires readonly_and_readwrite_storage_textures;
     * ```
     *
     * @readonly
     * @type {boolean}
     */ this.supportsStorageTextureRead = false, /**
     * Currently active render target.
     *
     * @type {RenderTarget|null}
     * @ignore
     */ this.renderTarget = null, /**
     * Array of objects that need to be re-initialized after a context restore event
     *
     * @type {Shader[]}
     * @ignore
     */ this.shaders = [], /**
     * An array of currently created textures.
     *
     * @type {Texture[]}
     * @ignore
     */ this.textures = [], /**
     * A set of currently created render targets.
     *
     * @type {Set<RenderTarget>}
     * @ignore
     */ this.targets = new Set(), /**
     * A version number that is incremented every frame. This is used to detect if some object were
     * invalidated.
     *
     * @type {number}
     * @ignore
     */ this.renderVersion = 0, /** @type {boolean} */ this.insideRenderPass = false, /**
     * True if the device supports uniform buffers.
     *
     * @type {boolean}
     * @ignore
     */ this.supportsUniformBuffers = false, /**
     * True if the device supports clip distances (WebGPU only). Clip distances allow you to restrict
     * primitives' clip volume with user-defined half-spaces in the output of vertex stage.
     *
     * @type {boolean}
     */ this.supportsClipDistances = false, /**
     * True if small-float textures with format {@link PIXELFORMAT_111110F} can be used as a frame
     * buffer. This is always true on WebGL2, but optional on WebGPU device.
     *
     * @type {boolean}
     * @readonly
     */ this.textureRG11B10Renderable = false, /**
     * True if filtering can be applied when sampling float textures.
     *
     * @type {boolean}
     * @readonly
     */ this.textureFloatFilterable = false, /**
     * An object representing current blend state
     *
     * @ignore
     */ this.blendState = new BlendState(), /**
     * The current depth state.
     *
     * @ignore
     */ this.depthState = new DepthState(), /**
     * True if stencil is enabled and stencilFront and stencilBack are used
     *
     * @ignore
     */ this.stencilEnabled = false, /**
     * The current front stencil parameters.
     *
     * @ignore
     */ this.stencilFront = new StencilParameters(), /**
     * The current back stencil parameters.
     *
     * @ignore
     */ this.stencilBack = new StencilParameters(), this.defaultClearOptions = {
            color: [
                0,
                0,
                0,
                1
            ],
            depth: 1,
            stencil: 0,
            flags: CLEARFLAG_COLOR | CLEARFLAG_DEPTH
        }, /**
     * The current client rect.
     *
     * @type {{ width: number, height: number }}
     * @ignore
     */ this.clientRect = {
            width: 0,
            height: 0
        }, /**
     * A very heavy handed way to force all shaders to be rebuilt. Avoid using as much as possible.
     *
     * @type {boolean}
     * @ignore
     */ this._shadersDirty = false, /**
     * A list of shader defines based on the capabilities of the device.
     *
     * @type {Map<string, string>}
     * @ignore
     */ this.capsDefines = new Map(), /**
     * A set of maps to clear at the end of the frame.
     *
     * @type {Set<Map>}
     * @ignore
     */ this.mapsToClear = new Set();
        this.canvas = canvas;
        if ('setAttribute' in canvas) {
            canvas.setAttribute('data-engine', `PlayCanvas ${version$1}`);
        }
        // copy options and handle defaults
        this.initOptions = {
            ...options
        };
        this.initOptions.alpha ??= true;
        this.initOptions.depth ??= true;
        this.initOptions.stencil ??= true;
        this.initOptions.antialias ??= true;
        this.initOptions.powerPreference ??= 'high-performance';
        this.initOptions.displayFormat ??= DISPLAYFORMAT_LDR;
        // Some devices window.devicePixelRatio can be less than one
        // eg Oculus Quest 1 which returns a window.devicePixelRatio of 0.8
        this._maxPixelRatio = platform.browser ? Math.min(1, window.devicePixelRatio) : 1;
        this.buffers = [];
        this._vram = {
            texShadow: 0,
            texAsset: 0,
            texLightmap: 0,
            tex: 0,
            vb: 0,
            ib: 0,
            ub: 0,
            sb: 0
        };
        this._shaderStats = {
            vsCompiled: 0,
            fsCompiled: 0,
            linked: 0,
            materialShaders: 0,
            compileTime: 0
        };
        this.initializeContextCaches();
        // Profiler stats
        this._drawCallsPerFrame = 0;
        this._shaderSwitchesPerFrame = 0;
        this._primsPerFrame = [];
        for(let i = PRIMITIVE_POINTS; i <= PRIMITIVE_TRIFAN; i++){
            this._primsPerFrame[i] = 0;
        }
        this._renderTargetCreationTime = 0;
        // Create the ScopeNamespace for shader attributes and variables
        this.scope = new ScopeSpace('Device');
        this.textureBias = this.scope.resolve('textureBias');
        this.textureBias.setValue(0.0);
    }
    /**
     * Function that executes after the device has been created.
     */ postInit() {
        // create quad vertex buffer
        const vertexFormat = new VertexFormat(this, [
            {
                semantic: SEMANTIC_POSITION,
                components: 2,
                type: TYPE_FLOAT32
            }
        ]);
        const positions = new Float32Array([
            -1,
            -1,
            1,
            -1,
            -1,
            1,
            1,
            1
        ]);
        this.quadVertexBuffer = new VertexBuffer(this, vertexFormat, 4, {
            data: positions
        });
    }
    /**
     * Initialize the map of device capabilities, which are supplied to shaders as defines.
     *
     * @ignore
     */ initCapsDefines() {
        const { capsDefines } = this;
        capsDefines.clear();
        if (this.textureFloatFilterable) capsDefines.set('CAPS_TEXTURE_FLOAT_FILTERABLE', '');
        if (this.textureFloatRenderable) capsDefines.set('CAPS_TEXTURE_FLOAT_RENDERABLE', '');
    }
    /**
     * Destroy the graphics device.
     */ destroy() {
        // fire the destroy event.
        // textures and other device resources may destroy themselves in response.
        this.fire('destroy');
        this.quadVertexBuffer?.destroy();
        this.quadVertexBuffer = null;
        this.dynamicBuffers?.destroy();
        this.dynamicBuffers = null;
        this.gpuProfiler?.destroy();
        this.gpuProfiler = null;
    }
    onDestroyShader(shader) {
        this.fire('destroy:shader', shader);
        const idx = this.shaders.indexOf(shader);
        if (idx !== -1) {
            this.shaders.splice(idx, 1);
        }
    }
    // executes after the extended classes have executed their destroy function
    postDestroy() {
        this.scope = null;
        this.canvas = null;
    }
    /**
     * Called when the device context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        this.contextLost = true;
        // force the back-buffer to be recreated on restore
        this.backBufferSize.set(-1, -1);
        // release textures
        for (const texture of this.textures){
            texture.loseContext();
        }
        // release vertex and index buffers
        for (const buffer of this.buffers){
            buffer.loseContext();
        }
        // Reset all render targets so they'll be recreated as required.
        // TODO: a solution for the case where a render target contains something
        // that was previously generated that needs to be re-rendered.
        for (const target of this.targets){
            target.loseContext();
        }
        this.gpuProfiler?.loseContext();
    }
    /**
     * Called when the device context is restored. It reinitializes all context related resources.
     *
     * @ignore
     */ restoreContext() {
        this.contextLost = false;
        this.initializeRenderState();
        this.initializeContextCaches();
        // Recreate buffer objects and reupload buffer data to the GPU
        for (const buffer of this.buffers){
            buffer.unlock();
        }
        this.gpuProfiler?.restoreContext?.();
    }
    // don't stringify GraphicsDevice to JSON by JSON.stringify
    toJSON(key) {
        return undefined;
    }
    initializeContextCaches() {
        this.vertexBuffers = [];
        this.shader = null;
        this.shaderValid = undefined;
        this.shaderAsyncCompile = false;
        this.renderTarget = null;
    }
    initializeRenderState() {
        this.blendState = new BlendState();
        this.depthState = new DepthState();
        this.cullMode = CULLFACE_BACK;
        // Cached viewport and scissor dimensions
        this.vx = this.vy = this.vw = this.vh = 0;
        this.sx = this.sy = this.sw = this.sh = 0;
        this.blendColor = new Color(0, 0, 0, 0);
    }
    /**
     * Sets the specified stencil state. If both stencilFront and stencilBack are null, stencil
     * operation is disabled.
     *
     * @param {StencilParameters} [stencilFront] - The front stencil parameters. Defaults to
     * {@link StencilParameters.DEFAULT} if not specified.
     * @param {StencilParameters} [stencilBack] - The back stencil parameters. Defaults to
     * {@link StencilParameters.DEFAULT} if not specified.
     */ setStencilState(stencilFront, stencilBack) {
        Debug.assert(false);
    }
    /**
     * Sets the specified blend state.
     *
     * @param {BlendState} blendState - New blend state.
     */ setBlendState(blendState) {
        Debug.assert(false);
    }
    /**
     * Sets the constant blend color and alpha values used with {@link BLENDMODE_CONSTANT} and
     * {@link BLENDMODE_ONE_MINUS_CONSTANT} factors specified in {@link BlendState}. Defaults to
     * [0, 0, 0, 0].
     *
     * @param {number} r - The value for red.
     * @param {number} g - The value for green.
     * @param {number} b - The value for blue.
     * @param {number} a - The value for alpha.
     */ setBlendColor(r, g, b, a) {
        Debug.assert(false);
    }
    /**
     * Sets the specified depth state.
     *
     * @param {DepthState} depthState - New depth state.
     */ setDepthState(depthState) {
        Debug.assert(false);
    }
    /**
     * Controls how triangles are culled based on their face direction. The default cull mode is
     * {@link CULLFACE_BACK}.
     *
     * @param {number} cullMode - The cull mode to set. Can be:
     *
     * - {@link CULLFACE_NONE}
     * - {@link CULLFACE_BACK}
     * - {@link CULLFACE_FRONT}
     */ setCullMode(cullMode) {
        Debug.assert(false);
    }
    /**
     * Sets the specified render target on the device. If null is passed as a parameter, the back
     * buffer becomes the current target for all rendering operations.
     *
     * @param {RenderTarget|null} renderTarget - The render target to activate.
     * @example
     * // Set a render target to receive all rendering output
     * device.setRenderTarget(renderTarget);
     *
     * // Set the back buffer to receive all rendering output
     * device.setRenderTarget(null);
     */ setRenderTarget(renderTarget) {
        this.renderTarget = renderTarget;
    }
    /**
     * Sets the current vertex buffer on the graphics device. For subsequent draw calls, the
     * specified vertex buffer(s) will be used to provide vertex data for any primitives.
     *
     * @param {VertexBuffer} vertexBuffer - The vertex buffer to assign to the device.
     * @ignore
     */ setVertexBuffer(vertexBuffer) {
        if (vertexBuffer) {
            this.vertexBuffers.push(vertexBuffer);
        }
    }
    /**
     * Clears the vertex buffer set on the graphics device. This is called automatically by the
     * renderer.
     * @ignore
     */ clearVertexBuffer() {
        this.vertexBuffers.length = 0;
    }
    /**
     * Retrieves the available slot in the {@link indirectDrawBuffer} used for indirect rendering,
     * which can be utilized by a {@link Compute} shader to generate indirect draw parameters and by
     * {@link MeshInstance#setIndirect} to configure indirect draw calls.
     *
     * @returns {number} - The slot used for indirect rendering.
     */ getIndirectDrawSlot() {
        return 0;
    }
    /**
     * Returns the buffer used to store arguments for indirect draw calls. The size of the buffer is
     * controlled by the {@link maxIndirectDrawCount} property. This buffer can be passed to a
     * {@link Compute} shader along with a slot obtained by calling {@link getIndirectDrawSlot}, in
     * order to prepare indirect draw parameters. Also see {@link MeshInstance#setIndirect}.
     *
     * Only available on WebGPU, returns null on other platforms.
     *
     * @type {StorageBuffer|null}
     */ get indirectDrawBuffer() {
        return null;
    }
    /**
     * Queries the currently set render target on the device.
     *
     * @returns {RenderTarget} The current render target.
     * @example
     * // Get the current render target
     * const renderTarget = device.getRenderTarget();
     */ getRenderTarget() {
        return this.renderTarget;
    }
    /**
     * Initialize render target before it can be used.
     *
     * @param {RenderTarget} target - The render target to be initialized.
     * @ignore
     */ initRenderTarget(target) {
        if (target.initialized) return;
        const startTime = now();
        this.fire('fbo:create', {
            timestamp: startTime,
            target: this
        });
        target.init();
        this.targets.add(target);
        this._renderTargetCreationTime += now() - startTime;
    }
    /**
     * Submits a graphical primitive to the hardware for immediate rendering.
     *
     * @param {object} primitive - Primitive object describing how to submit current vertex/index
     * buffers.
     * @param {number} primitive.type - The type of primitive to render. Can be:
     *
     * - {@link PRIMITIVE_POINTS}
     * - {@link PRIMITIVE_LINES}
     * - {@link PRIMITIVE_LINELOOP}
     * - {@link PRIMITIVE_LINESTRIP}
     * - {@link PRIMITIVE_TRIANGLES}
     * - {@link PRIMITIVE_TRISTRIP}
     * - {@link PRIMITIVE_TRIFAN}
     *
     * @param {number} primitive.base - The offset of the first index or vertex to dispatch in the
     * draw call.
     * @param {number} primitive.count - The number of indices or vertices to dispatch in the draw
     * call.
     * @param {boolean} [primitive.indexed] - True to interpret the primitive as indexed, thereby
     * using the currently set index buffer and false otherwise.
     * @param {IndexBuffer} [indexBuffer] - The index buffer to use for the draw call.
     * @param {number} [numInstances] - The number of instances to render when using instancing.
     * Defaults to 1.
     * @param {number} [indirectSlot] - The slot of the indirect buffer to use for the draw call.
     * @param {boolean} [first] - True if this is the first draw call in a sequence of draw calls.
     * When set to true, vertex and index buffers related state is set up. Defaults to true.
     * @param {boolean} [last] - True if this is the last draw call in a sequence of draw calls.
     * When set to true, vertex and index buffers related state is cleared. Defaults to true.
     * @example
     * // Render a single, unindexed triangle
     * device.draw({
     *     type: pc.PRIMITIVE_TRIANGLES,
     *     base: 0,
     *     count: 3,
     *     indexed: false
     * });
     *
     * @ignore
     */ draw(primitive, indexBuffer, numInstances, indirectSlot, first = true, last = true) {
        Debug.assert(false);
    }
    /**
     * Reports whether a texture source is a canvas, image, video or ImageBitmap.
     *
     * @param {*} texture - Texture source data.
     * @returns {boolean} True if the texture is a canvas, image, video or ImageBitmap and false
     * otherwise.
     * @ignore
     */ _isBrowserInterface(texture) {
        return this._isImageBrowserInterface(texture) || this._isImageCanvasInterface(texture) || this._isImageVideoInterface(texture);
    }
    _isImageBrowserInterface(texture) {
        return typeof ImageBitmap !== 'undefined' && texture instanceof ImageBitmap || typeof HTMLImageElement !== 'undefined' && texture instanceof HTMLImageElement;
    }
    _isImageCanvasInterface(texture) {
        return typeof HTMLCanvasElement !== 'undefined' && texture instanceof HTMLCanvasElement;
    }
    _isImageVideoInterface(texture) {
        return typeof HTMLVideoElement !== 'undefined' && texture instanceof HTMLVideoElement;
    }
    /**
     * Sets the width and height of the canvas, then fires the `resizecanvas` event. Note that the
     * specified width and height values will be multiplied by the value of
     * {@link GraphicsDevice#maxPixelRatio} to give the final resultant width and height for the
     * canvas.
     *
     * @param {number} width - The new width of the canvas.
     * @param {number} height - The new height of the canvas.
     * @ignore
     */ resizeCanvas(width, height) {
        const pixelRatio = Math.min(this._maxPixelRatio, platform.browser ? window.devicePixelRatio : 1);
        const w = Math.floor(width * pixelRatio);
        const h = Math.floor(height * pixelRatio);
        if (w !== this.canvas.width || h !== this.canvas.height) {
            this.setResolution(w, h);
        }
    }
    /**
     * Sets the width and height of the canvas, then fires the `resizecanvas` event. Note that the
     * value of {@link GraphicsDevice#maxPixelRatio} is ignored.
     *
     * @param {number} width - The new width of the canvas.
     * @param {number} height - The new height of the canvas.
     * @ignore
     */ setResolution(width, height) {
        this.canvas.width = width;
        this.canvas.height = height;
        this.fire(GraphicsDevice.EVENT_RESIZE, width, height);
    }
    update() {
        this.updateClientRect();
    }
    updateClientRect() {
        if (platform.worker) {
            // Web Workers don't do page layout, so getBoundingClientRect is not available
            this.clientRect.width = this.canvas.width;
            this.clientRect.height = this.canvas.height;
        } else {
            const rect = this.canvas.getBoundingClientRect();
            this.clientRect.width = rect.width;
            this.clientRect.height = rect.height;
        }
    }
    /**
     * Width of the back buffer in pixels.
     *
     * @type {number}
     */ get width() {
        return this.canvas.width;
    }
    /**
     * Height of the back buffer in pixels.
     *
     * @type {number}
     */ get height() {
        return this.canvas.height;
    }
    /**
     * Sets whether the device is currently in fullscreen mode.
     *
     * @type {boolean}
     */ set fullscreen(fullscreen) {
        Debug.error('GraphicsDevice.fullscreen is not implemented on current device.');
    }
    /**
     * Gets whether the device is currently in fullscreen mode.
     *
     * @type {boolean}
     */ get fullscreen() {
        Debug.error('GraphicsDevice.fullscreen is not implemented on current device.');
        return false;
    }
    /**
     * Sets the maximum pixel ratio.
     *
     * @type {number}
     */ set maxPixelRatio(ratio) {
        this._maxPixelRatio = ratio;
    }
    /**
     * Gets the maximum pixel ratio.
     *
     * @type {number}
     */ get maxPixelRatio() {
        return this._maxPixelRatio;
    }
    /**
     * Gets the type of the device. Can be:
     *
     * - {@link DEVICETYPE_WEBGL2}
     * - {@link DEVICETYPE_WEBGPU}
     *
     * @type {DEVICETYPE_WEBGL2|DEVICETYPE_WEBGPU}
     */ get deviceType() {
        return this._deviceType;
    }
    startRenderPass(renderPass) {}
    endRenderPass(renderPass) {}
    startComputePass(name) {}
    endComputePass() {}
    /**
     * Function which executes at the start of the frame. This should not be called manually, as
     * it is handled by the AppBase instance.
     *
     * @ignore
     */ frameStart() {
        this.renderPassIndex = 0;
        this.renderVersion++;
        Debug.call(()=>{
            // log out all loaded textures, sorted by gpu memory size
            if (Tracing.get(TRACEID_TEXTURES)) {
                const textures = this.textures.slice();
                textures.sort((a, b)=>b.gpuSize - a.gpuSize);
                Debug.log(`Textures: ${textures.length}`);
                let textureTotal = 0;
                textures.forEach((texture, index)=>{
                    const textureSize = texture.gpuSize;
                    textureTotal += textureSize;
                    Debug.log(`${index}. ${texture.name} ${texture.width}x${texture.height} VRAM: ${(textureSize / 1024 / 1024).toFixed(2)} MB`);
                });
                Debug.log(`Total: ${(textureTotal / 1024 / 1024).toFixed(2)}MB`);
            }
        });
    }
    /**
     * Function which executes at the end of the frame. This should not be called manually, as it is
     * handled by the AppBase instance.
     *
     * @ignore
     */ frameEnd() {
        // clear all maps scheduled for end of frame clearing
        this.mapsToClear.forEach((map)=>map.clear());
        this.mapsToClear.clear();
    }
    /**
     * Dispatch multiple compute shaders inside a single compute shader pass.
     *
     * @param {Array<Compute>} computes - An array of compute shaders to dispatch.
     * @param {string} [name] - The name of the dispatch, used for debugging and reporting only.
     */ computeDispatch(computes, name = 'Unnamed') {}
    /**
     * Get a renderable HDR pixel format supported by the graphics device.
     *
     * Note:
     *
     * - When the `filterable` parameter is set to false, this function returns one of the supported
     * formats on the majority of devices apart from some very old iOS and Android devices (99%).
     * - When the `filterable` parameter is set to true, the function returns a format on a
     * considerably lower number of devices (70%).
     *
     * @param {number[]} [formats] - An array of pixel formats to check for support. Can contain:
     *
     * - {@link PIXELFORMAT_111110F}
     * - {@link PIXELFORMAT_RGBA16F}
     * - {@link PIXELFORMAT_RGBA32F}
     *
     * @param {boolean} [filterable] - If true, the format also needs to be filterable. Defaults to
     * true.
     * @param {number} [samples] - The number of samples to check for. Some formats are not
     * compatible with multi-sampling, for example {@link PIXELFORMAT_RGBA32F} on WebGPU platform.
     * Defaults to 1.
     * @returns {number|undefined} The first supported renderable HDR format or undefined if none is
     * supported.
     */ getRenderableHdrFormat(formats = [
        PIXELFORMAT_111110F,
        PIXELFORMAT_RGBA16F,
        PIXELFORMAT_RGBA32F
    ], filterable = true, samples = 1) {
        for(let i = 0; i < formats.length; i++){
            const format = formats[i];
            switch(format){
                case PIXELFORMAT_111110F:
                    {
                        if (this.textureRG11B10Renderable) {
                            return format;
                        }
                        break;
                    }
                case PIXELFORMAT_RGBA16F:
                    if (this.textureHalfFloatRenderable) {
                        return format;
                    }
                    break;
                case PIXELFORMAT_RGBA32F:
                    // on WebGPU platform, RGBA32F is not compatible with multi-sampling
                    if (this.isWebGPU && samples > 1) {
                        continue;
                    }
                    if (this.textureFloatRenderable && (!filterable || this.textureFloatFilterable)) {
                        return format;
                    }
                    break;
            }
        }
        return undefined;
    }
    /**
     * Validate that all attributes required by the shader are present in the currently assigned
     * vertex buffers.
     *
     * @param {Shader} shader - The shader to validate.
     * @param {VertexFormat} vb0Format - The format of the first vertex buffer.
     * @param {VertexFormat} vb1Format - The format of the second vertex buffer.
     * @protected
     */ validateAttributes(shader, vb0Format, vb1Format) {
        Debug.call(()=>{
            // add all attribute locations from vertex formats to the set
            _tempSet$1.clear();
            vb0Format?.elements.forEach((element)=>_tempSet$1.add(semanticToLocation[element.name]));
            vb1Format?.elements.forEach((element)=>_tempSet$1.add(semanticToLocation[element.name]));
            // every location shader needs must be in the vertex buffer
            for (const [location, name] of shader.attributes){
                if (!_tempSet$1.has(location)) {
                    Debug.errorOnce(`Vertex attribute [${name}] at location ${location} required by the shader is not present in the currently assigned vertex buffers, while rendering [${DebugGraphics.toString()}]`, {
                        shader,
                        vb0Format,
                        vb1Format
                    });
                }
            }
        });
    }
}

/**
 * @import { Texture } from './texture.js'
 */ let id$9 = 0;
/**
 * A render target is a rectangular rendering surface.
 *
 * @category Graphics
 */ class RenderTarget {
    /**
     * Creates a new RenderTarget instance. A color buffer or a depth buffer must be set.
     *
     * @param {object} [options] - Object for passing optional arguments.
     * @param {boolean} [options.autoResolve] - If samples > 1, enables or disables automatic MSAA
     * resolve after rendering to this RT (see {@link RenderTarget#resolve}). Defaults to true.
     * @param {Texture} [options.colorBuffer] - The texture that this render target will treat as a
     * rendering surface.
     * @param {Texture[]} [options.colorBuffers] - The textures that this render target will treat
     * as a rendering surfaces. If this option is set, the colorBuffer option is ignored.
     * @param {boolean} [options.depth] - If set to true, depth buffer will be created. Defaults to
     * true. Ignored if depthBuffer is defined.
     * @param {Texture} [options.depthBuffer] - The texture that this render target will treat as a
     * depth/stencil surface (WebGL2 only). If set, the 'depth' and 'stencil' properties are
     * ignored. Texture must have {@link PIXELFORMAT_DEPTH} or {@link PIXELFORMAT_DEPTHSTENCIL}
     * format.
     * @param {number} [options.mipLevel] - If set to a number greater than 0, the render target
     * will render to the specified mip level of the color buffer. Defaults to 0.
     * @param {number} [options.face] - If the colorBuffer parameter is a cubemap, use this option
     * to specify the face of the cubemap to render to. Can be:
     *
     * - {@link CUBEFACE_POSX}
     * - {@link CUBEFACE_NEGX}
     * - {@link CUBEFACE_POSY}
     * - {@link CUBEFACE_NEGY}
     * - {@link CUBEFACE_POSZ}
     * - {@link CUBEFACE_NEGZ}
     *
     * Defaults to {@link CUBEFACE_POSX}.
     * @param {boolean} [options.flipY] - When set to true the image will be flipped in Y. Default
     * is false.
     * @param {string} [options.name] - The name of the render target.
     * @param {number} [options.samples] - Number of hardware anti-aliasing samples. Default is 1.
     * @param {boolean} [options.stencil] - If set to true, depth buffer will include stencil.
     * Defaults to false. Ignored if depthBuffer is defined or depth is false.
     * @example
     * // Create a 512x512x24-bit render target with a depth buffer
     * const colorBuffer = new pc.Texture(graphicsDevice, {
     *     width: 512,
     *     height: 512,
     *     format: pc.PIXELFORMAT_RGB8
     * });
     * const renderTarget = new pc.RenderTarget({
     *     colorBuffer: colorBuffer,
     *     depth: true
     * });
     *
     * // Set the render target on a camera component
     * camera.renderTarget = renderTarget;
     *
     * // Destroy render target at a later stage. Note that the color buffer needs
     * // to be destroyed separately.
     * renderTarget.colorBuffer.destroy();
     * renderTarget.destroy();
     * camera.renderTarget = null;
     */ constructor(options = {}){
        Debug.assert(!(options instanceof GraphicsDevice), 'pc.RenderTarget constructor no longer accepts GraphicsDevice parameter.');
        this.id = id$9++;
        // device, from one of the buffers
        const device = options.colorBuffer?.device ?? options.colorBuffers?.[0].device ?? options.depthBuffer?.device ?? options.graphicsDevice;
        Debug.assert(device, 'Failed to obtain the device, colorBuffer nor depthBuffer store it.');
        this._device = device;
        // samples
        const { maxSamples } = this._device;
        this._samples = Math.min(options.samples ?? 1, maxSamples);
        if (device.isWebGPU) {
            // WebGPU only supports values of 1 or 4 for samples
            this._samples = this._samples > 1 ? maxSamples : 1;
        }
        // Use the single colorBuffer in the colorBuffers array. This allows us to always just use the array internally.
        this._colorBuffer = options.colorBuffer;
        if (options.colorBuffer) {
            this._colorBuffers = [
                options.colorBuffer
            ];
        }
        // Process optional arguments
        this._depthBuffer = options.depthBuffer;
        this._face = options.face ?? 0;
        if (this._depthBuffer) {
            const format = this._depthBuffer._format;
            if (format === PIXELFORMAT_DEPTH || format === PIXELFORMAT_DEPTH16) {
                this._depth = true;
                this._stencil = false;
            } else if (format === PIXELFORMAT_DEPTHSTENCIL) {
                this._depth = true;
                this._stencil = true;
            } else if (format === PIXELFORMAT_R32F && this._depthBuffer.device.isWebGPU && this._samples > 1) {
                // on WebGPU, when multisampling is enabled, we use R32F format for the specified buffer,
                // which we can resolve depth to using a shader
                this._depth = true;
                this._stencil = false;
            } else {
                Debug.warn('Incorrect depthBuffer format. Must be pc.PIXELFORMAT_DEPTH or pc.PIXELFORMAT_DEPTHSTENCIL');
                this._depth = false;
                this._stencil = false;
            }
        } else {
            this._depth = options.depth ?? true;
            this._stencil = options.stencil ?? false;
        }
        // MRT
        if (options.colorBuffers) {
            Debug.assert(!this._colorBuffers, 'When constructing RenderTarget and options.colorBuffers is used, options.colorBuffer must not be used.');
            if (!this._colorBuffers) {
                this._colorBuffers = [
                    ...options.colorBuffers
                ];
                // set the main color buffer to point to 0 index
                this._colorBuffer = options.colorBuffers[0];
            }
        }
        this.autoResolve = options.autoResolve ?? true;
        // use specified name, otherwise get one from color or depth buffer
        this.name = options.name;
        if (!this.name) {
            this.name = this._colorBuffer?.name;
        }
        if (!this.name) {
            this.name = this._depthBuffer?.name;
        }
        if (!this.name) {
            this.name = 'Untitled';
        }
        // render image flipped in Y
        this.flipY = options.flipY ?? false;
        this._mipLevel = options.mipLevel ?? 0;
        if (this._mipLevel > 0 && this._depth) {
            Debug.error(`Rendering to a mipLevel is not supported when render target uses a depth buffer. Ignoring mipLevel ${this._mipLevel} for render target ${this.name}`, {
                renderTarget: this,
                options
            });
            this._mipLevel = 0;
        }
        // if we render to a specific mipmap (even 0), do not generate mipmaps
        this._mipmaps = options.mipLevel === undefined;
        this.validateMrt();
        // device specific implementation
        this.impl = device.createRenderTargetImpl(this);
        Debug.trace(TRACEID_RENDER_TARGET_ALLOC, `Alloc: Id ${this.id} ${this.name}: ${this.width}x${this.height} ` + `[samples: ${this.samples}]` + `${this._colorBuffers?.length ? `[MRT: ${this._colorBuffers.length}]` : ''}` + `${this.colorBuffer ? '[Color]' : ''}` + `${this.depth ? '[Depth]' : ''}` + `${this.stencil ? '[Stencil]' : ''}` + `[Face:${this.face}]`);
    }
    /**
     * Frees resources associated with this render target.
     */ destroy() {
        Debug.trace(TRACEID_RENDER_TARGET_ALLOC, `DeAlloc: Id ${this.id} ${this.name}`);
        const device = this._device;
        if (device) {
            device.targets.delete(this);
            if (device.renderTarget === this) {
                device.setRenderTarget(null);
            }
            this.destroyFrameBuffers();
        }
    }
    /**
     * Free device resources associated with this render target.
     *
     * @ignore
     */ destroyFrameBuffers() {
        const device = this._device;
        if (device) {
            this.impl.destroy(device);
        }
    }
    /**
     * Free textures associated with this render target.
     *
     * @ignore
     */ destroyTextureBuffers() {
        this._depthBuffer?.destroy();
        this._depthBuffer = null;
        this._colorBuffers?.forEach((colorBuffer)=>{
            colorBuffer.destroy();
        });
        this._colorBuffers = null;
        this._colorBuffer = null;
    }
    /**
     * Resizes the render target to the specified width and height. Internally this resizes all the
     * assigned texture color and depth buffers.
     *
     * @param {number} width - The width of the render target in pixels.
     * @param {number} height - The height of the render target in pixels.
     */ resize(width, height) {
        if (this.width !== width || this.height !== height) {
            if (this.mipLevel > 0) {
                Debug.warn('Only a render target rendering to mipLevel 0 can be resized, ignoring.', this);
                return;
            }
            // release existing
            const device = this._device;
            this.destroyFrameBuffers();
            if (device.renderTarget === this) {
                device.setRenderTarget(null);
            }
            // resize textures
            this._depthBuffer?.resize(width, height);
            this._colorBuffers?.forEach((colorBuffer)=>{
                colorBuffer.resize(width, height);
            });
            // initialize again
            this.validateMrt();
            this.impl = device.createRenderTargetImpl(this);
        }
    }
    validateMrt() {
        Debug.call(()=>{
            if (this._colorBuffers) {
                const { width, height, cubemap, volume } = this._colorBuffers[0];
                for(let i = 1; i < this._colorBuffers.length; i++){
                    const colorBuffer = this._colorBuffers[i];
                    Debug.assert(colorBuffer.width === width, 'All render target color buffers must have the same width', this);
                    Debug.assert(colorBuffer.height === height, 'All render target color buffers must have the same height', this);
                    Debug.assert(colorBuffer.cubemap === cubemap, 'All render target color buffers must have the same cubemap setting', this);
                    Debug.assert(colorBuffer.volume === volume, 'All render target color buffers must have the same volume setting', this);
                }
            }
        });
    }
    /**
     * Initializes the resources associated with this render target.
     *
     * @ignore
     */ init() {
        this.impl.init(this._device, this);
    }
    /** @ignore */ get initialized() {
        return this.impl.initialized;
    }
    /** @ignore */ get device() {
        return this._device;
    }
    /**
     * Called when the device context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        this.impl.loseContext();
    }
    /**
     * If samples > 1, resolves the anti-aliased render target (WebGL2 only). When you're rendering
     * to an anti-aliased render target, pixels aren't written directly to the readable texture.
     * Instead, they're first written to a MSAA buffer, where each sample for each pixel is stored
     * independently. In order to read the results, you first need to 'resolve' the buffer - to
     * average all samples and create a simple texture with one color per pixel. This function
     * performs this averaging and updates the colorBuffer and the depthBuffer. If autoResolve is
     * set to true, the resolve will happen after every rendering to this render target, otherwise
     * you can do it manually, during the app update or similar.
     *
     * @param {boolean} [color] - Resolve color buffer. Defaults to true.
     * @param {boolean} [depth] - Resolve depth buffer. Defaults to true if the render target has a
     * depth buffer.
     */ resolve(color = true, depth = !!this._depthBuffer) {
        // TODO: consider adding support for MRT to this function.
        if (this._device && this._samples > 1) {
            DebugGraphics.pushGpuMarker(this._device, `RESOLVE-RT:${this.name}:${color ? '[color]' : ''}:${depth ? '[depth]' : ''}`);
            this.impl.resolve(this._device, this, color, depth);
            DebugGraphics.popGpuMarker(this._device);
        }
    }
    /**
     * Copies color and/or depth contents of source render target to this one. Formats, sizes and
     * anti-aliasing samples must match. Depth buffer can only be copied on WebGL 2.0.
     *
     * @param {RenderTarget} source - Source render target to copy from.
     * @param {boolean} [color] - If true, will copy the color buffer. Defaults to false.
     * @param {boolean} [depth] - If true, will copy the depth buffer. Defaults to false.
     * @returns {boolean} True if the copy was successful, false otherwise.
     */ copy(source, color, depth) {
        // TODO: consider adding support for MRT to this function.
        if (!this._device) {
            if (source._device) {
                this._device = source._device;
            } else {
                Debug.error('Render targets are not initialized');
                return false;
            }
        }
        DebugGraphics.pushGpuMarker(this._device, `COPY-RT:${source.name}->${this.name}`);
        const success = this._device.copyRenderTarget(source, this, color, depth);
        DebugGraphics.popGpuMarker(this._device);
        return success;
    }
    /**
     * Number of antialiasing samples the render target uses.
     *
     * @type {number}
     */ get samples() {
        return this._samples;
    }
    /**
     * True if the render target contains the depth attachment.
     *
     * @type {boolean}
     */ get depth() {
        return this._depth;
    }
    /**
     * True if the render target contains the stencil attachment.
     *
     * @type {boolean}
     */ get stencil() {
        return this._stencil;
    }
    /**
     * Color buffer set up on the render target.
     *
     * @type {Texture}
     */ get colorBuffer() {
        return this._colorBuffer;
    }
    /**
     * Accessor for multiple render target color buffers.
     *
     * @param {*} index - Index of the color buffer to get.
     * @returns {Texture} - Color buffer at the specified index.
     */ getColorBuffer(index) {
        return this._colorBuffers?.[index];
    }
    /**
     * Depth buffer set up on the render target. Only available, if depthBuffer was set in
     * constructor. Not available if depth property was used instead.
     *
     * @type {Texture}
     */ get depthBuffer() {
        return this._depthBuffer;
    }
    /**
     * If the render target is bound to a cubemap, this property specifies which face of the
     * cubemap is rendered to. Can be:
     *
     * - {@link CUBEFACE_POSX}
     * - {@link CUBEFACE_NEGX}
     * - {@link CUBEFACE_POSY}
     * - {@link CUBEFACE_NEGY}
     * - {@link CUBEFACE_POSZ}
     * - {@link CUBEFACE_NEGZ}
     *
     * @type {number}
     */ get face() {
        return this._face;
    }
    /**
     * Mip level of the render target.
     *
     * @type {number}
     */ get mipLevel() {
        return this._mipLevel;
    }
    /**
     * True if the mipmaps are automatically generated for the color buffer(s) if it contains
     * a mip chain.
     *
     * @type {boolean}
     */ get mipmaps() {
        return this._mipmaps;
    }
    /**
     * Width of the render target in pixels.
     *
     * @type {number}
     */ get width() {
        let width = this._colorBuffer?.width || this._depthBuffer?.width || this._device.width;
        if (this._mipLevel > 0) {
            width = TextureUtils.calcLevelDimension(width, this._mipLevel);
        }
        return width;
    }
    /**
     * Height of the render target in pixels.
     *
     * @type {number}
     */ get height() {
        let height = this._colorBuffer?.height || this._depthBuffer?.height || this._device.height;
        if (this._mipLevel > 0) {
            height = TextureUtils.calcLevelDimension(height, this._mipLevel);
        }
        return height;
    }
    /**
     * Gets whether the format of the specified color buffer is sRGB.
     *
     * @param {number} index - The index of the color buffer.
     * @returns {boolean} True if the color buffer is sRGB, false otherwise.
     * @ignore
     */ isColorBufferSrgb(index = 0) {
        if (this.device.backBuffer === this) {
            return isSrgbPixelFormat(this.device.backBufferFormat);
        }
        const colorBuffer = this.getColorBuffer(index);
        return colorBuffer ? isSrgbPixelFormat(colorBuffer.format) : false;
    }
}

/**
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 */ // Maximum number of times a duplicate error message is logged.
const MAX_DUPLICATES = 5;
/**
 * Internal WebGPU debug system. Note that the functions only execute in the debug build, and are
 * stripped out in other builds.
 */ class WebgpuDebug {
    static{
        this._scopes = [];
    }
    static{
        this._markers = [];
    }
    static{
        /** @type {Map<string,number>} */ this._loggedMessages = new Map();
    }
    /**
     * Start a validation error scope.
     *
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     */ static validate(device) {
        device.wgpu.pushErrorScope('validation');
        WebgpuDebug._scopes.push('validation');
        WebgpuDebug._markers.push(DebugGraphics.toString());
    }
    /**
     * Start an out-of-memory error scope.
     *
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     */ static memory(device) {
        device.wgpu.pushErrorScope('out-of-memory');
        WebgpuDebug._scopes.push('out-of-memory');
        WebgpuDebug._markers.push(DebugGraphics.toString());
    }
    /**
     * Start an internal error scope.
     *
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     */ static internal(device) {
        device.wgpu.pushErrorScope('internal');
        WebgpuDebug._scopes.push('internal');
        WebgpuDebug._markers.push(DebugGraphics.toString());
    }
    /**
     * End the previous error scope, and print errors if any.
     *
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     * @param {string} label - The label for the error scope.
     * @param {...any} args - Additional parameters that form the error message.
     */ static async end(device, label, ...args) {
        const header = WebgpuDebug._scopes.pop();
        const marker = WebgpuDebug._markers.pop();
        Debug.assert(header, 'Non matching end.');
        const error = await device.wgpu.popErrorScope();
        if (error) {
            const count = WebgpuDebug._loggedMessages.get(error.message) ?? 0;
            if (count < MAX_DUPLICATES) {
                const tooMany = count === MAX_DUPLICATES - 1 ? ' (Too many errors, ignoring this one from now)' : '';
                WebgpuDebug._loggedMessages.set(error.message, count + 1);
                console.error(`WebGPU ${label} ${header} error: ${error.message}`, tooMany, 'while rendering', marker, ...args);
            }
        }
    }
    /**
     * Ends the shader validation scope by retrieving and logging any compilation errors
     * or warnings from the shader module. Also handles WebGPU validation errors, while
     * avoiding duplicate error messages.
     *
     * @param {WebgpuGraphicsDevice} device - The WebGPU graphics device.
     * @param {GPUShaderModule} shaderModule - The compiled WebGPU shader module.
     * @param {string} source - The original shader source code.
     * @param {number} [contextLines] - The number of lines before and after the error to log.
     * @param {...any} args - Additional parameters providing context about the shader.
     */ static async endShader(device, shaderModule, source, contextLines = 2, ...args) {
        const header = WebgpuDebug._scopes.pop();
        const marker = WebgpuDebug._markers.pop();
        Debug.assert(header, 'Non-matching error scope end.');
        // Capture popErrorScope error (if any)
        const error = await device.wgpu.popErrorScope();
        let errorMessage = '';
        if (error) {
            errorMessage += `WebGPU ShaderModule creation ${header} error: ${error.message}`;
            errorMessage += ` - While rendering ${marker}\n`;
        }
        // Get shader compilation errors
        const compilationInfo = await shaderModule.getCompilationInfo();
        if (compilationInfo.messages.length > 0) {
            // split source into lines
            const sourceLines = source.split('\n');
            compilationInfo.messages.forEach((message, index)=>{
                const { type, lineNum, linePos, message: msg } = message;
                const lineIndex = lineNum - 1; // Convert to zero-based index
                errorMessage += `\n----- ${type.toUpperCase()} ${index + 1} context: :${lineNum}:${linePos} ${type}: ${msg}\n`;
                // Extract surrounding lines for context
                const startLine = Math.max(0, lineIndex - contextLines);
                const endLine = Math.min(sourceLines.length, lineIndex + contextLines + 1);
                for(let i = startLine; i < endLine; i++){
                    const linePrefix = i === lineIndex ? '> ' : '  ';
                    errorMessage += `${linePrefix}${i + 1}: ${sourceLines[i]}\n`;
                }
            });
        }
        // only log if there are errors or messages
        if (errorMessage) {
            console.error(errorMessage, ...args);
        }
    }
}

/**
 * @import { BindGroup } from '../bind-group.js'
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 * @import { WebgpuTexture } from './webgpu-texture.js'
 */ /**
 * A WebGPU implementation of the BindGroup, which is a wrapper over GPUBindGroup.
 *
 * @ignore
 */ class WebgpuBindGroup {
    update(bindGroup) {
        this.destroy();
        const device = bindGroup.device;
        /** @type {GPUBindGroupDescriptor} */ const desc = this.createDescriptor(device, bindGroup);
        WebgpuDebug.validate(device);
        this.bindGroup = device.wgpu.createBindGroup(desc);
        WebgpuDebug.end(device, 'BindGroup creation', {
            debugFormat: this.debugFormat,
            desc: desc,
            format: bindGroup.format,
            bindGroup: bindGroup
        });
    }
    destroy() {
        this.bindGroup = null;
    }
    /**
     * Creates a bind group descriptor in WebGPU format
     *
     * @param {WebgpuGraphicsDevice} device - Graphics device.
     * @param {BindGroup} bindGroup - Bind group to create the
     * descriptor for.
     * @returns {object} - Returns the generated descriptor of type GPUBindGroupDescriptor, which
     * can be used to create a GPUBindGroup
     */ createDescriptor(device, bindGroup) {
        // Note: This needs to match WebgpuBindGroupFormat.createDescriptor
        const entries = [];
        const format = bindGroup.format;
        Debug.call(()=>{
            this.debugFormat = '';
        });
        // uniform buffers
        const uniformBufferFormats = bindGroup.format.uniformBufferFormats;
        bindGroup.uniformBuffers.forEach((ub, i)=>{
            const slot = uniformBufferFormats[i].slot;
            const buffer = ub.persistent ? ub.impl.buffer : ub.allocation.gpuBuffer.buffer;
            Debug.assert(buffer, 'NULL uniform buffer cannot be used by the bind group');
            Debug.call(()=>{
                this.debugFormat += `${slot}: UB\n`;
            });
            entries.push({
                binding: slot,
                resource: {
                    buffer: buffer,
                    offset: 0,
                    size: ub.format.byteSize
                }
            });
        });
        // textures
        const textureFormats = bindGroup.format.textureFormats;
        bindGroup.textures.forEach((tex, textureIndex)=>{
            /** @type {WebgpuTexture} */ const wgpuTexture = tex.impl;
            const textureFormat = format.textureFormats[textureIndex];
            const slot = textureFormats[textureIndex].slot;
            // texture
            const view = wgpuTexture.getView(device);
            Debug.assert(view, 'NULL texture view cannot be used by the bind group');
            Debug.call(()=>{
                this.debugFormat += `${slot}: ${bindGroup.format.textureFormats[textureIndex].name}\n`;
            });
            entries.push({
                binding: slot,
                resource: view
            });
            // sampler
            if (textureFormat.hasSampler) {
                const sampler = wgpuTexture.getSampler(device, textureFormat.sampleType);
                Debug.assert(sampler, 'NULL sampler cannot be used by the bind group');
                Debug.call(()=>{
                    this.debugFormat += `${slot + 1}: ${sampler.label}\n`;
                });
                entries.push({
                    binding: slot + 1,
                    resource: sampler
                });
            }
        });
        // storage textures
        const storageTextureFormats = bindGroup.format.storageTextureFormats;
        bindGroup.storageTextures.forEach((tex, textureIndex)=>{
            /** @type {WebgpuTexture} */ const wgpuTexture = tex.impl;
            const slot = storageTextureFormats[textureIndex].slot;
            // texture
            const view = wgpuTexture.getView(device);
            Debug.assert(view, 'NULL texture view cannot be used by the bind group');
            Debug.call(()=>{
                this.debugFormat += `${slot}: ${bindGroup.format.storageTextureFormats[textureIndex].name}\n`;
            });
            entries.push({
                binding: slot,
                resource: view
            });
        });
        // storage buffers
        const storageBufferFormats = bindGroup.format.storageBufferFormats;
        bindGroup.storageBuffers.forEach((buffer, bufferIndex)=>{
            /** @type {GPUBuffer} */ const wgpuBuffer = buffer.impl.buffer;
            const slot = storageBufferFormats[bufferIndex].slot;
            Debug.assert(wgpuBuffer, 'NULL storage buffer cannot be used by the bind group');
            Debug.call(()=>{
                this.debugFormat += `${slot}: SB\n`;
            });
            entries.push({
                binding: slot,
                resource: {
                    buffer: wgpuBuffer
                }
            });
        });
        const desc = {
            layout: bindGroup.format.impl.bindGroupLayout,
            entries: entries
        };
        DebugHelper.setLabel(desc, bindGroup.name);
        return desc;
    }
}

class WebgpuUtils {
    // converts a combination of SHADER_STAGE_* into GPUShaderStage.*
    static shaderStage(stage) {
        let ret = 0;
        if (stage & SHADERSTAGE_VERTEX) ret |= GPUShaderStage.VERTEX;
        if (stage & SHADERSTAGE_FRAGMENT) ret |= GPUShaderStage.FRAGMENT;
        if (stage & SHADERSTAGE_COMPUTE) ret |= GPUShaderStage.COMPUTE;
        return ret;
    }
}

// map of PIXELFORMAT_*** to GPUTextureFormat
const gpuTextureFormats = [];
gpuTextureFormats[PIXELFORMAT_A8] = '';
gpuTextureFormats[PIXELFORMAT_L8] = '';
gpuTextureFormats[PIXELFORMAT_LA8] = '';
gpuTextureFormats[PIXELFORMAT_R8] = 'r8unorm';
gpuTextureFormats[PIXELFORMAT_RG8] = 'rg8unorm';
gpuTextureFormats[PIXELFORMAT_RGB565] = '';
gpuTextureFormats[PIXELFORMAT_RGBA5551] = '';
gpuTextureFormats[PIXELFORMAT_RGBA4] = '';
gpuTextureFormats[PIXELFORMAT_RGB8] = 'rgba8unorm';
gpuTextureFormats[PIXELFORMAT_RGBA8] = 'rgba8unorm';
gpuTextureFormats[PIXELFORMAT_DXT1] = 'bc1-rgba-unorm';
gpuTextureFormats[PIXELFORMAT_DXT3] = 'bc2-rgba-unorm';
gpuTextureFormats[PIXELFORMAT_DXT5] = 'bc3-rgba-unorm';
gpuTextureFormats[PIXELFORMAT_RGB16F] = '';
gpuTextureFormats[PIXELFORMAT_RGBA16F] = 'rgba16float';
gpuTextureFormats[PIXELFORMAT_R16F] = 'r16float';
gpuTextureFormats[PIXELFORMAT_RG16F] = 'rg16float';
gpuTextureFormats[PIXELFORMAT_RGB32F] = '';
gpuTextureFormats[PIXELFORMAT_RGBA32F] = 'rgba32float';
gpuTextureFormats[PIXELFORMAT_R32F] = 'r32float';
gpuTextureFormats[PIXELFORMAT_DEPTH] = 'depth32float';
gpuTextureFormats[PIXELFORMAT_DEPTH16] = 'depth16unorm';
gpuTextureFormats[PIXELFORMAT_DEPTHSTENCIL] = 'depth24plus-stencil8';
gpuTextureFormats[PIXELFORMAT_111110F] = 'rg11b10ufloat';
gpuTextureFormats[PIXELFORMAT_SRGB8] = '';
gpuTextureFormats[PIXELFORMAT_SRGBA8] = 'rgba8unorm-srgb';
gpuTextureFormats[PIXELFORMAT_ETC1] = '';
gpuTextureFormats[PIXELFORMAT_ETC2_RGB] = 'etc2-rgb8unorm';
gpuTextureFormats[PIXELFORMAT_ETC2_RGBA] = 'etc2-rgba8unorm';
gpuTextureFormats[PIXELFORMAT_PVRTC_2BPP_RGB_1] = '';
gpuTextureFormats[PIXELFORMAT_PVRTC_2BPP_RGBA_1] = '';
gpuTextureFormats[PIXELFORMAT_PVRTC_4BPP_RGB_1] = '';
gpuTextureFormats[PIXELFORMAT_PVRTC_4BPP_RGBA_1] = '';
gpuTextureFormats[PIXELFORMAT_ASTC_4x4] = 'astc-4x4-unorm';
gpuTextureFormats[PIXELFORMAT_ATC_RGB] = '';
gpuTextureFormats[PIXELFORMAT_ATC_RGBA] = '';
gpuTextureFormats[PIXELFORMAT_BGRA8] = 'bgra8unorm';
gpuTextureFormats[PIXELFORMAT_SBGRA8] = 'bgra8unorm-srgb';
gpuTextureFormats[PIXELFORMAT_R8I] = 'r8sint';
gpuTextureFormats[PIXELFORMAT_R8U] = 'r8uint';
gpuTextureFormats[PIXELFORMAT_R16I] = 'r16sint';
gpuTextureFormats[PIXELFORMAT_R16U] = 'r16uint';
gpuTextureFormats[PIXELFORMAT_R32I] = 'r32sint';
gpuTextureFormats[PIXELFORMAT_R32U] = 'r32uint';
gpuTextureFormats[PIXELFORMAT_RG8I] = 'rg8sint';
gpuTextureFormats[PIXELFORMAT_RG8U] = 'rg8uint';
gpuTextureFormats[PIXELFORMAT_RG16I] = 'rg16sint';
gpuTextureFormats[PIXELFORMAT_RG16U] = 'rg16uint';
gpuTextureFormats[PIXELFORMAT_RG32I] = 'rg32sint';
gpuTextureFormats[PIXELFORMAT_RG32U] = 'rg32uint';
gpuTextureFormats[PIXELFORMAT_RGBA8I] = 'rgba8sint';
gpuTextureFormats[PIXELFORMAT_RGBA8U] = 'rgba8uint';
gpuTextureFormats[PIXELFORMAT_RGBA16I] = 'rgba16sint';
gpuTextureFormats[PIXELFORMAT_RGBA16U] = 'rgba16uint';
gpuTextureFormats[PIXELFORMAT_RGBA32I] = 'rgba32sint';
gpuTextureFormats[PIXELFORMAT_RGBA32U] = 'rgba32uint';
gpuTextureFormats[PIXELFORMAT_BC6F] = 'bc6h-rgb-float';
gpuTextureFormats[PIXELFORMAT_BC6UF] = 'bc6h-rgb-ufloat';
gpuTextureFormats[PIXELFORMAT_BC7] = 'bc7-rgba-unorm';
// compressed sRGB formats ----
gpuTextureFormats[PIXELFORMAT_DXT1_SRGB] = 'bc1-rgba-unorm-srgb';
gpuTextureFormats[PIXELFORMAT_DXT3_SRGBA] = 'bc2-rgba-unorm-srgb';
gpuTextureFormats[PIXELFORMAT_DXT5_SRGBA] = 'bc3-rgba-unorm-srgb';
gpuTextureFormats[PIXELFORMAT_ETC2_SRGB] = 'etc2-rgb8unorm-srgb';
gpuTextureFormats[PIXELFORMAT_ETC2_SRGBA] = 'etc2-rgba8unorm-srgb';
gpuTextureFormats[PIXELFORMAT_BC7_SRGBA] = 'bc7-rgba-unorm-srgb';
gpuTextureFormats[PIXELFORMAT_ASTC_4x4_SRGB] = 'astc-4x4-unorm-srgb';

/**
 * @import { BindGroupFormat } from '../bind-group-format.js'
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 */ const samplerTypes = [];
samplerTypes[SAMPLETYPE_FLOAT] = 'filtering';
samplerTypes[SAMPLETYPE_UNFILTERABLE_FLOAT] = 'non-filtering';
samplerTypes[SAMPLETYPE_DEPTH] = 'comparison';
// Using 'comparison' instead of 'non-filtering' may seem unusual, but currently we will get a
// validation error if we use 'non-filtering' along with texelFetch/textureLoad. 'comparison' works
// very well for the most common use-case of integer textures, texelFetch. We may be able to change
// how we initialize the sampler elsewhere to support 'non-filtering' in the future.
samplerTypes[SAMPLETYPE_INT] = 'comparison';
samplerTypes[SAMPLETYPE_UINT] = 'comparison';
const sampleTypes = [];
sampleTypes[SAMPLETYPE_FLOAT] = 'float';
sampleTypes[SAMPLETYPE_UNFILTERABLE_FLOAT] = 'unfilterable-float';
sampleTypes[SAMPLETYPE_DEPTH] = 'depth';
sampleTypes[SAMPLETYPE_INT] = 'sint';
sampleTypes[SAMPLETYPE_UINT] = 'uint';
const stringIds$1 = new StringIds();
/**
 * A WebGPU implementation of the BindGroupFormat, which is a wrapper over GPUBindGroupLayout.
 *
 * @ignore
 */ class WebgpuBindGroupFormat {
    /**
     * @param {BindGroupFormat} bindGroupFormat - Bind group format.
     */ constructor(bindGroupFormat){
        /** @type {WebgpuGraphicsDevice} */ const device = bindGroupFormat.device;
        const { key, desc } = this.createDescriptor(bindGroupFormat);
        /**
         * Unique key, used for caching
         *
         * @type {number}
         */ this.key = stringIds$1.get(key);
        // keep desc in debug mode
        Debug.call(()=>{
            this.desc = desc;
        });
        /**
         * @type {GPUBindGroupLayout}
         * @private
         */ this.bindGroupLayout = device.wgpu.createBindGroupLayout(desc);
        DebugHelper.setLabel(this.bindGroupLayout, bindGroupFormat.name);
    }
    destroy() {
        this.bindGroupLayout = null;
    }
    loseContext() {
    // this.bindGroupLayout = null;
    }
    /**
     * @param {any} bindGroupFormat - The format of the bind group.
     * @returns {any} Returns the bind group descriptor.
     */ createDescriptor(bindGroupFormat) {
        // all WebGPU bindings:
        // - buffer: GPUBufferBindingLayout, resource type is GPUBufferBinding
        // - sampler: GPUSamplerBindingLayout, resource type is GPUSampler
        // - texture: GPUTextureBindingLayout, resource type is GPUTextureView
        // - storageTexture: GPUStorageTextureBindingLayout, resource type is GPUTextureView
        // - externalTexture: GPUExternalTextureBindingLayout, resource type is GPUExternalTexture
        const entries = [];
        // generate unique key
        let key = '';
        // buffers
        bindGroupFormat.uniformBufferFormats.forEach((bufferFormat)=>{
            const visibility = WebgpuUtils.shaderStage(bufferFormat.visibility);
            key += `#${bufferFormat.slot}U:${visibility}`;
            entries.push({
                binding: bufferFormat.slot,
                visibility: visibility,
                buffer: {
                    type: 'uniform',
                    // whether this binding requires a dynamic offset
                    // currently all UBs are dynamic and need the offset
                    hasDynamicOffset: true
                }
            });
        });
        // textures
        bindGroupFormat.textureFormats.forEach((textureFormat)=>{
            const visibility = WebgpuUtils.shaderStage(textureFormat.visibility);
            // texture
            const sampleType = textureFormat.sampleType;
            const viewDimension = textureFormat.textureDimension;
            const multisampled = false;
            const gpuSampleType = sampleTypes[sampleType];
            Debug.assert(gpuSampleType);
            key += `#${textureFormat.slot}T:${visibility}-${gpuSampleType}-${viewDimension}-${multisampled}`;
            // texture
            entries.push({
                binding: textureFormat.slot,
                visibility: visibility,
                texture: {
                    // Indicates the type required for texture views bound to this binding.
                    // "float", "unfilterable-float", "depth", "sint", "uint",
                    sampleType: gpuSampleType,
                    // Indicates the required dimension for texture views bound to this binding.
                    // "1d", "2d", "2d-array", "cube", "cube-array", "3d"
                    viewDimension: viewDimension,
                    // Indicates whether or not texture views bound to this binding must be multisampled
                    multisampled: multisampled
                }
            });
            // sampler
            if (textureFormat.hasSampler) {
                const gpuSamplerType = samplerTypes[sampleType];
                Debug.assert(gpuSamplerType);
                key += `#${textureFormat.slot + 1}S:${visibility}-${gpuSamplerType}`;
                entries.push({
                    binding: textureFormat.slot + 1,
                    visibility: visibility,
                    sampler: {
                        // Indicates the required type of a sampler bound to this bindings
                        // 'filtering', 'non-filtering', 'comparison'
                        type: gpuSamplerType
                    }
                });
            }
        });
        // storage textures
        bindGroupFormat.storageTextureFormats.forEach((textureFormat)=>{
            const { format, textureDimension } = textureFormat;
            const { read, write } = textureFormat;
            key += `#${textureFormat.slot}ST:${format}-${textureDimension}-${read ? 'r1' : 'r0'}-${write ? 'w1' : 'w0'}`;
            // storage texture
            entries.push({
                binding: textureFormat.slot,
                visibility: GPUShaderStage.COMPUTE,
                storageTexture: {
                    // The access mode for this binding, indicating readability and writability.
                    // 'write-only' is always support, 'read-write' and 'read-only' optionally
                    access: read ? write ? 'read-write' : 'read-only' : 'write-only',
                    // The required format of texture views bound to this binding.
                    format: gpuTextureFormats[format],
                    // Indicates the required dimension for texture views bound to this binding.
                    // "1d", "2d", "2d-array", "cube", "cube-array", "3d"
                    viewDimension: textureDimension
                }
            });
        });
        // storage buffers
        bindGroupFormat.storageBufferFormats.forEach((bufferFormat)=>{
            const readOnly = bufferFormat.readOnly;
            const visibility = WebgpuUtils.shaderStage(bufferFormat.visibility);
            key += `#${bufferFormat.slot}SB:${visibility}-${readOnly ? 'ro' : 'rw'}`;
            entries.push({
                binding: bufferFormat.slot,
                visibility: visibility,
                buffer: {
                    // "storage", "read-only-storage"
                    type: readOnly ? 'read-only-storage' : 'storage'
                }
            });
        });
        /** @type {GPUBindGroupLayoutDescriptor} */ const desc = {
            entries: entries
        };
        return {
            key,
            desc
        };
    }
}

/**
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 */ /**
 * A WebGPU implementation of the Buffer.
 *
 * @ignore
 */ class WebgpuBuffer {
    constructor(usageFlags = 0){
        /**
     * @type {GPUBuffer|null}
     * @private
     */ this.buffer = null;
        this.usageFlags = 0;
        this.usageFlags = usageFlags;
    }
    destroy(device) {
        if (this.buffer) {
            this.buffer.destroy();
            this.buffer = null;
        }
    }
    get initialized() {
        return !!this.buffer;
    }
    loseContext() {}
    allocate(device, size) {
        Debug.assert(!this.buffer, 'Buffer already allocated');
        this.buffer = device.wgpu.createBuffer({
            size,
            usage: this.usageFlags
        });
    }
    /**
     * @param {WebgpuGraphicsDevice} device - Graphics device.
     * @param {*} storage -
     */ unlock(device, storage) {
        const wgpu = device.wgpu;
        // offset of getMappedRange must me a multiple of 8
        // size of getMappedRange must be a multiple of 4
        if (!this.buffer) {
            // size needs to be a multiple of 4
            // note: based on specs, descriptor.size must be a multiple of 4 if descriptor.mappedAtCreation is true
            const size = storage.byteLength + 3 & -4;
            this.usageFlags |= GPUBufferUsage.COPY_DST;
            this.allocate(device, size);
            DebugHelper.setLabel(this.buffer, this.usageFlags & GPUBufferUsage.VERTEX ? 'VertexBuffer' : this.usageFlags & GPUBufferUsage.INDEX ? 'IndexBuffer' : this.usageFlags & GPUBufferUsage.UNIFORM ? 'UniformBuffer' : this.usageFlags & GPUBufferUsage.STORAGE ? 'StorageBuffer' : '');
        // mappedAtCreation path - this could be used when the data is provided
        // this.buffer = device.wgpu.createBuffer({
        //     size: size,
        //     usage: target,
        //     mappedAtCreation: true
        // });
        // const dest = new Uint8Array(this.buffer.getMappedRange());
        // const src = new Uint8Array(storage.buffer ? storage.buffer : storage);
        // dest.set(src);
        // this.buffer.unmap();
        }
        // src size needs to be a multiple of 4 as well
        const srcOffset = storage.byteOffset ?? 0;
        const srcData = new Uint8Array(storage.buffer ?? storage, srcOffset, storage.byteLength);
        const data = new Uint8Array(this.buffer.size);
        data.set(srcData);
        // copy data to the gpu buffer
        Debug.trace(TRACEID_RENDER_QUEUE, `writeBuffer: ${this.buffer.label}`);
        wgpu.queue.writeBuffer(this.buffer, 0, data, 0, data.length);
    }
    read(device, offset, size, data, immediate) {
        return device.readStorageBuffer(this, offset, size, data, immediate);
    }
    write(device, bufferOffset, data, dataOffset, size) {
        device.writeStorageBuffer(this, bufferOffset, data, dataOffset, size);
    }
    clear(device, offset, size) {
        device.clearStorageBuffer(this, offset, size);
    }
}

/**
 * A WebGPU implementation of the IndexBuffer.
 *
 * @ignore
 */ class WebgpuIndexBuffer extends WebgpuBuffer {
    constructor(indexBuffer, options){
        super(BUFFERUSAGE_INDEX | (options?.storage ? BUFFERUSAGE_STORAGE : 0)), this.format = null;
        Debug.assert(indexBuffer.format !== INDEXFORMAT_UINT8, 'WebGPU does not support 8-bit index buffer format');
        this.format = indexBuffer.format === INDEXFORMAT_UINT16 ? 'uint16' : 'uint32';
    }
    unlock(indexBuffer) {
        const device = indexBuffer.device;
        super.unlock(device, indexBuffer.storage);
    }
}

const array = {
    // helper function to compare two arrays for equality
    equals (arr1, arr2) {
        if (arr1.length !== arr2.length) {
            return false;
        }
        for(let i = 0; i < arr1.length; i++){
            if (arr1[i] !== arr2[i]) {
                return false;
            }
        }
        return true;
    }
};

/**
 * @import { VertexFormat } from '../vertex-format.js'
 */ // map of TYPE_*** to GPUVertexFormat
const gpuVertexFormats = [];
gpuVertexFormats[TYPE_INT8] = 'sint8';
gpuVertexFormats[TYPE_UINT8] = 'uint8';
gpuVertexFormats[TYPE_INT16] = 'sint16';
gpuVertexFormats[TYPE_UINT16] = 'uint16';
gpuVertexFormats[TYPE_INT32] = 'sint32';
gpuVertexFormats[TYPE_UINT32] = 'uint32';
gpuVertexFormats[TYPE_FLOAT32] = 'float32';
gpuVertexFormats[TYPE_FLOAT16] = 'float16';
const gpuVertexFormatsNormalized = [];
gpuVertexFormatsNormalized[TYPE_INT8] = 'snorm8';
gpuVertexFormatsNormalized[TYPE_UINT8] = 'unorm8';
gpuVertexFormatsNormalized[TYPE_INT16] = 'snorm16';
gpuVertexFormatsNormalized[TYPE_UINT16] = 'unorm16';
gpuVertexFormatsNormalized[TYPE_INT32] = 'sint32'; // there is no 32bit normalized signed int
gpuVertexFormatsNormalized[TYPE_UINT32] = 'uint32'; // there is no 32bit normalized unsigned int
gpuVertexFormatsNormalized[TYPE_FLOAT32] = 'float32'; // there is no 32bit normalized float
gpuVertexFormatsNormalized[TYPE_FLOAT16] = 'float16'; // there is no 16bit normalized half-float
class WebgpuVertexBufferLayout {
    /**
     * Obtain a vertex layout of one or two vertex formats.
     *
     * @param {VertexFormat} vertexFormat0 - The first vertex format.
     * @param {VertexFormat} [vertexFormat1] - The second vertex format.
     * @returns {any[]} - The vertex layout.
     */ get(vertexFormat0, vertexFormat1 = null) {
        const key = this.getKey(vertexFormat0, vertexFormat1);
        let layout = this.cache.get(key);
        if (!layout) {
            layout = this.create(vertexFormat0, vertexFormat1);
            this.cache.set(key, layout);
        }
        return layout;
    }
    getKey(vertexFormat0, vertexFormat1 = null) {
        return `${vertexFormat0?.renderingHashString}-${vertexFormat1?.renderingHashString}`;
    }
    /**
     * @param {VertexFormat} vertexFormat0 - The first vertex format.
     * @param {VertexFormat} vertexFormat1 - The second vertex format.
     * @returns {any[]} - The vertex buffer layout.
     */ create(vertexFormat0, vertexFormat1) {
        // type {GPUVertexBufferLayout[]}
        const layout = [];
        // Note: If the VertexFormat is interleaved, we use a single vertex buffer with multiple
        // attributes. This uses a smaller number of vertex buffers (1), which has performance
        // benefits when setting it up on the device.
        // If the VertexFormat is not interleaved, we use multiple vertex buffers, one per
        // attribute. This is less efficient, but is required as there is a pretty small
        // limit on the attribute offsets in the vertex buffer layout.
        const addFormat = (format)=>{
            const interleaved = format.interleaved;
            const stepMode = format.instancing ? 'instance' : 'vertex';
            let attributes = [];
            const elementCount = format.elements.length;
            for(let i = 0; i < elementCount; i++){
                const element = format.elements[i];
                const location = semanticToLocation[element.name];
                const formatTable = element.normalize ? gpuVertexFormatsNormalized : gpuVertexFormats;
                attributes.push({
                    shaderLocation: location,
                    offset: interleaved ? element.offset : 0,
                    format: `${formatTable[element.dataType]}${element.numComponents > 1 ? `x${element.numComponents}` : ''}`
                });
                if (!interleaved || i === elementCount - 1) {
                    layout.push({
                        attributes: attributes,
                        arrayStride: element.stride,
                        stepMode: stepMode
                    });
                    attributes = [];
                }
            }
        };
        if (vertexFormat0) {
            addFormat(vertexFormat0);
        }
        if (vertexFormat1) {
            addFormat(vertexFormat1);
        }
        return layout;
    }
    constructor(){
        /**
     * @type {Map<string, GPUVertexBufferLayout[]>}
     * @private
     */ this.cache = new Map();
    }
}

/**
 * @import { BindGroupFormat } from '../bind-group-format.js'
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 */ let _layoutId = 0;
/**
 * Base class for render and compute pipelines.
 *
 * @ignore
 */ class WebgpuPipeline {
    constructor(device){
        /** @type {WebgpuGraphicsDevice} */ this.device = device;
    }
    // TODO: this could be cached using bindGroupKey
    /**
     * @param {BindGroupFormat[]} bindGroupFormats - An array of bind group formats.
     * @returns {any} Returns the pipeline layout.
     */ getPipelineLayout(bindGroupFormats) {
        const bindGroupLayouts = [];
        bindGroupFormats.forEach((format)=>{
            bindGroupLayouts.push(format.bindGroupLayout);
        });
        const desc = {
            bindGroupLayouts: bindGroupLayouts
        };
        _layoutId++;
        DebugHelper.setLabel(desc, `PipelineLayoutDescr-${_layoutId}`);
        /** @type {GPUPipelineLayout} */ const pipelineLayout = this.device.wgpu.createPipelineLayout(desc);
        DebugHelper.setLabel(pipelineLayout, `PipelineLayout-${_layoutId}`);
        Debug.trace(TRACEID_PIPELINELAYOUT_ALLOC, `Alloc: Id ${_layoutId}`, {
            desc: desc,
            bindGroupFormats
        });
        return pipelineLayout;
    }
}

/**
 * @import { BindGroupFormat } from '../bind-group-format.js'
 * @import { BlendState } from '../blend-state.js'
 * @import { DepthState } from '../depth-state.js'
 * @import { RenderTarget } from '../render-target.js'
 * @import { Shader } from '../shader.js'
 * @import { StencilParameters } from '../stencil-parameters.js'
 * @import { VertexFormat } from '../vertex-format.js'
 * @import { WebgpuShader } from './webgpu-shader.js'
 */ let _pipelineId$1 = 0;
const _primitiveTopology = [
    'point-list',
    'line-list',
    undefined,
    'line-strip',
    'triangle-list',
    'triangle-strip',
    undefined // PRIMITIVE_TRIFAN
];
const _blendOperation = [
    'add',
    'subtract',
    'reverse-subtract',
    'min',
    'max' // BLENDEQUATION_MAX
];
const _blendFactor = [
    'zero',
    'one',
    'src',
    'one-minus-src',
    'dst',
    'one-minus-dst',
    'src-alpha',
    'src-alpha-saturated',
    'one-minus-src-alpha',
    'dst-alpha',
    'one-minus-dst-alpha',
    'constant',
    'one-minus-constant' // BLENDMODE_ONE_MINUS_CONSTANT
];
const _compareFunction = [
    'never',
    'less',
    'equal',
    'less-equal',
    'greater',
    'not-equal',
    'greater-equal',
    'always' // FUNC_ALWAYS
];
const _cullModes = [
    'none',
    'back',
    'front' // CULLFACE_FRONT
];
const _stencilOps = [
    'keep',
    'zero',
    'replace',
    'increment-clamp',
    'increment-wrap',
    'decrement-clamp',
    'decrement-wrap',
    'invert' // STENCILOP_INVERT
];
const _indexFormat = [
    '',
    'uint16',
    'uint32' // INDEXFORMAT_UINT32
];
class CacheEntry {
}
class WebgpuRenderPipeline extends WebgpuPipeline {
    constructor(device){
        super(device), this.lookupHashes = new Uint32Array(14);
        /**
         * The cache of vertex buffer layouts
         *
         * @type {WebgpuVertexBufferLayout}
         */ this.vertexBufferLayout = new WebgpuVertexBufferLayout();
        /**
         * The cache of render pipelines
         *
         * @type {Map<number, CacheEntry[]>}
         */ this.cache = new Map();
    }
    /**
     * @param {object} primitive - The primitive.
     * @param {VertexFormat} vertexFormat0 - The first vertex format.
     * @param {VertexFormat} vertexFormat1 - The second vertex format.
     * @param {number|undefined} ibFormat - The index buffer format.
     * @param {Shader} shader - The shader.
     * @param {RenderTarget} renderTarget - The render target.
     * @param {BindGroupFormat[]} bindGroupFormats - An array of bind group formats.
     * @param {BlendState} blendState - The blend state.
     * @param {DepthState} depthState - The depth state.
     * @param {number} cullMode - The cull mode.
     * @param {boolean} stencilEnabled - Whether stencil is enabled.
     * @param {StencilParameters} stencilFront - The stencil state for front faces.
     * @param {StencilParameters} stencilBack - The stencil state for back faces.
     * @returns {GPURenderPipeline} Returns the render pipeline.
     * @private
     */ get(primitive, vertexFormat0, vertexFormat1, ibFormat, shader, renderTarget, bindGroupFormats, blendState, depthState, cullMode, stencilEnabled, stencilFront, stencilBack) {
        Debug.assert(bindGroupFormats.length <= 3);
        // ibFormat is used only for stripped primitives, clear it otherwise to avoid additional render pipelines
        const primitiveType = primitive.type;
        if (ibFormat && primitiveType !== PRIMITIVE_LINESTRIP && primitiveType !== PRIMITIVE_TRISTRIP) {
            ibFormat = undefined;
        }
        // all bind groups must be set as the WebGPU layout cannot have skipped indices. Not having a bind
        // group would assign incorrect slots to the following bind groups, causing a validation errors.
        Debug.assert(bindGroupFormats[0], `BindGroup with index 0 [${bindGroupNames[0]}] is not set.`);
        Debug.assert(bindGroupFormats[1], `BindGroup with index 1 [${bindGroupNames[1]}] is not set.`);
        Debug.assert(bindGroupFormats[2], `BindGroup with index 2 [${bindGroupNames[2]}] is not set.`);
        // render pipeline unique hash
        const lookupHashes = this.lookupHashes;
        lookupHashes[0] = primitiveType;
        lookupHashes[1] = shader.id;
        lookupHashes[2] = cullMode;
        lookupHashes[3] = depthState.key;
        lookupHashes[4] = blendState.key;
        lookupHashes[5] = vertexFormat0?.renderingHash ?? 0;
        lookupHashes[6] = vertexFormat1?.renderingHash ?? 0;
        lookupHashes[7] = renderTarget.impl.key;
        lookupHashes[8] = bindGroupFormats[0]?.key ?? 0;
        lookupHashes[9] = bindGroupFormats[1]?.key ?? 0;
        lookupHashes[10] = bindGroupFormats[2]?.key ?? 0;
        lookupHashes[11] = stencilEnabled ? stencilFront.key : 0;
        lookupHashes[12] = stencilEnabled ? stencilBack.key : 0;
        lookupHashes[13] = ibFormat ?? 0;
        const hash = hash32Fnv1a(lookupHashes);
        // cached pipeline
        let cacheEntries = this.cache.get(hash);
        // if we have cache entries, find the exact match, as hash collision can occur
        if (cacheEntries) {
            for(let i = 0; i < cacheEntries.length; i++){
                const entry = cacheEntries[i];
                if (array.equals(entry.hashes, lookupHashes)) {
                    return entry.pipeline;
                }
            }
        }
        // no match or a hash collision, so create a new pipeline
        const primitiveTopology = _primitiveTopology[primitiveType];
        Debug.assert(primitiveTopology, 'Unsupported primitive topology', primitive);
        // pipeline layout
        const pipelineLayout = this.getPipelineLayout(bindGroupFormats);
        // vertex buffer layout
        const vertexBufferLayout = this.vertexBufferLayout.get(vertexFormat0, vertexFormat1);
        // pipeline
        const cacheEntry = new CacheEntry();
        cacheEntry.hashes = new Uint32Array(lookupHashes);
        cacheEntry.pipeline = this.create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack);
        // add to cache
        if (cacheEntries) {
            cacheEntries.push(cacheEntry);
        } else {
            cacheEntries = [
                cacheEntry
            ];
        }
        this.cache.set(hash, cacheEntries);
        return cacheEntry.pipeline;
    }
    getBlend(blendState) {
        // blend needs to be undefined when blending is disabled
        let blend;
        if (blendState.blend) {
            /** @type {GPUBlendState} */ blend = {
                color: {
                    operation: _blendOperation[blendState.colorOp],
                    srcFactor: _blendFactor[blendState.colorSrcFactor],
                    dstFactor: _blendFactor[blendState.colorDstFactor]
                },
                alpha: {
                    operation: _blendOperation[blendState.alphaOp],
                    srcFactor: _blendFactor[blendState.alphaSrcFactor],
                    dstFactor: _blendFactor[blendState.alphaDstFactor]
                }
            };
            // unsupported blend factors
            Debug.assert(blend.color.srcFactor !== undefined);
            Debug.assert(blend.color.dstFactor !== undefined);
            Debug.assert(blend.alpha.srcFactor !== undefined);
            Debug.assert(blend.alpha.dstFactor !== undefined);
        }
        return blend;
    }
    /**
     * @param {DepthState} depthState - The depth state.
     * @param {RenderTarget} renderTarget - The render target.
     * @param {boolean} stencilEnabled - Whether stencil is enabled.
     * @param {StencilParameters} stencilFront - The stencil state for front faces.
     * @param {StencilParameters} stencilBack - The stencil state for back faces.
     * @param {string} primitiveTopology - The primitive topology.
     * @returns {object} Returns the depth stencil state.
     * @private
     */ getDepthStencil(depthState, renderTarget, stencilEnabled, stencilFront, stencilBack, primitiveTopology) {
        /** @type {GPUDepthStencilState} */ let depthStencil;
        const { depth, stencil } = renderTarget;
        if (depth || stencil) {
            // format of depth-stencil attachment
            depthStencil = {
                format: renderTarget.impl.depthAttachment.format
            };
            // depth
            if (depth) {
                depthStencil.depthWriteEnabled = depthState.write;
                depthStencil.depthCompare = _compareFunction[depthState.func];
                const biasAllowed = primitiveTopology === 'triangle-list' || primitiveTopology === 'triangle-strip';
                depthStencil.depthBias = biasAllowed ? depthState.depthBias : 0;
                depthStencil.depthBiasSlopeScale = biasAllowed ? depthState.depthBiasSlope : 0;
            } else {
                // if render target does not have depth buffer
                depthStencil.depthWriteEnabled = false;
                depthStencil.depthCompare = 'always';
            }
            // stencil
            if (stencil && stencilEnabled) {
                // Note that WebGPU only supports a single mask, we use the one from front, but not from back.
                depthStencil.stencilReadMas = stencilFront.readMask;
                depthStencil.stencilWriteMask = stencilFront.writeMask;
                depthStencil.stencilFront = {
                    compare: _compareFunction[stencilFront.func],
                    failOp: _stencilOps[stencilFront.fail],
                    passOp: _stencilOps[stencilFront.zpass],
                    depthFailOp: _stencilOps[stencilFront.zfail]
                };
                depthStencil.stencilBack = {
                    compare: _compareFunction[stencilBack.func],
                    failOp: _stencilOps[stencilBack.fail],
                    passOp: _stencilOps[stencilBack.zpass],
                    depthFailOp: _stencilOps[stencilBack.zfail]
                };
            }
        }
        return depthStencil;
    }
    create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack) {
        const wgpu = this.device.wgpu;
        /** @type {WebgpuShader} */ const webgpuShader = shader.impl;
        /** @type {GPURenderPipelineDescriptor} */ const desc = {
            vertex: {
                module: webgpuShader.getVertexShaderModule(),
                entryPoint: webgpuShader.vertexEntryPoint,
                buffers: vertexBufferLayout
            },
            primitive: {
                topology: primitiveTopology,
                frontFace: 'ccw',
                cullMode: _cullModes[cullMode]
            },
            depthStencil: this.getDepthStencil(depthState, renderTarget, stencilEnabled, stencilFront, stencilBack, primitiveTopology),
            multisample: {
                count: renderTarget.samples
            },
            // uniform / texture binding layout
            layout: pipelineLayout
        };
        if (ibFormat) {
            desc.primitive.stripIndexFormat = _indexFormat[ibFormat];
        }
        desc.fragment = {
            module: webgpuShader.getFragmentShaderModule(),
            entryPoint: webgpuShader.fragmentEntryPoint,
            targets: []
        };
        const colorAttachments = renderTarget.impl.colorAttachments;
        if (colorAttachments.length > 0) {
            // the same write mask is used by all color buffers, to match the WebGL behavior
            let writeMask = 0;
            if (blendState.redWrite) writeMask |= GPUColorWrite.RED;
            if (blendState.greenWrite) writeMask |= GPUColorWrite.GREEN;
            if (blendState.blueWrite) writeMask |= GPUColorWrite.BLUE;
            if (blendState.alphaWrite) writeMask |= GPUColorWrite.ALPHA;
            // the same blend state is used by all color buffers, to match the WebGL behavior
            const blend = this.getBlend(blendState);
            colorAttachments.forEach((attachment)=>{
                desc.fragment.targets.push({
                    format: attachment.format,
                    writeMask: writeMask,
                    blend: blend
                });
            });
        }
        WebgpuDebug.validate(this.device);
        _pipelineId$1++;
        DebugHelper.setLabel(desc, `RenderPipelineDescr-${_pipelineId$1}`);
        const pipeline = wgpu.createRenderPipeline(desc);
        DebugHelper.setLabel(pipeline, `RenderPipeline-${_pipelineId$1}`);
        Debug.trace(TRACEID_RENDERPIPELINE_ALLOC, `Alloc: Id ${_pipelineId$1}, stack: ${DebugGraphics.toString()}`, desc);
        WebgpuDebug.end(this.device, 'RenderPipeline creation', {
            renderPipeline: this,
            desc: desc,
            shader
        });
        return pipeline;
    }
}

/**
 * @import { WebgpuShader } from './webgpu-shader.js'
 */ let _pipelineId = 0;
class WebgpuComputePipeline extends WebgpuPipeline {
    get(shader, bindGroupFormat) {
        // pipeline layout
        const pipelineLayout = this.getPipelineLayout([
            bindGroupFormat.impl
        ]);
        // TODO: this could be cached
        const pipeline = this.create(shader, pipelineLayout);
        return pipeline;
    }
    create(shader, pipelineLayout) {
        const wgpu = this.device.wgpu;
        /** @type {WebgpuShader} */ const webgpuShader = shader.impl;
        /** @type {GPUComputePipelineDescriptor} */ const desc = {
            compute: {
                module: webgpuShader.getComputeShaderModule(),
                entryPoint: webgpuShader.computeEntryPoint
            },
            // uniform / texture binding layout
            layout: pipelineLayout
        };
        WebgpuDebug.validate(this.device);
        _pipelineId++;
        DebugHelper.setLabel(desc, `ComputePipelineDescr-${_pipelineId}`);
        const pipeline = wgpu.createComputePipeline(desc);
        DebugHelper.setLabel(pipeline, `ComputePipeline-${_pipelineId}`);
        Debug.trace(TRACEID_COMPUTEPIPELINE_ALLOC, `Alloc: Id ${_pipelineId}`, desc);
        WebgpuDebug.end(this.device, 'ComputePipeline creation', {
            computePipeline: this,
            desc: desc,
            shader
        });
        return pipeline;
    }
}

/**
 * Base class that implements reference counting for objects.
 */ class RefCountedObject {
    /**
     * Increments the reference counter.
     */ incRefCount() {
        this._refCount++;
    }
    /**
     * Decrements the reference counter.
     */ decRefCount() {
        this._refCount--;
    }
    /**
     * Gets the current reference count.
     *
     * @type {number}
     */ get refCount() {
        return this._refCount;
    }
    constructor(){
        /**
     * @type {number}
     * @private
     */ this._refCount = 0;
    }
}

/**
 * An entry in the RefCountedKeyCache cache, which wraps the object with a reference count.
 */ class Entry extends RefCountedObject {
    constructor(obj){
        super();
        this.object = obj;
        this.incRefCount();
    }
}
/**
 * Class implementing reference counting cache for objects accessed by a key. Reference counting is
 * separate from the stored object.
 */ class RefCountedKeyCache {
    /**
     * Destroy all stored objects.
     */ destroy() {
        this.cache.forEach((entry)=>{
            entry.object?.destroy();
        });
        this.cache.clear();
    }
    /**
     * Clear the cache, without destroying the objects.
     */ clear() {
        this.cache.clear();
    }
    /**
     * Get the object from the cache with the given key, while incrementing the reference count. If
     * the object is not in the cache, returns null.
     *
     * @param {object} key - The key to look up the object.
     * @returns {object} The object, or null if not found.
     */ get(key) {
        const entry = this.cache.get(key);
        if (entry) {
            entry.incRefCount();
            return entry.object;
        }
        return null;
    }
    /**
     * Put the object in the cache with the given key. The object cannot be in the cache already.
     * This sets its reference count to 1.
     *
     * @param {object} key - The key to store the object under.
     * @param {object} object - The object to store.
     */ set(key, object) {
        Debug.assert(!this.cache.has(key), 'RefCountedKeyCache: Trying to put object with key that already exists in the cache', {
            key,
            object
        });
        this.cache.set(key, new Entry(object));
    }
    /**
     * Remove the object reference from the cache with the given key. If the reference count reaches
     * zero, the object is destroyed.
     *
     * @param {object} key - The key to remove the object under.
     */ release(key) {
        const entry = this.cache.get(key);
        if (entry) {
            entry.decRefCount();
            // last reference removed, destroy the object
            if (entry.refCount === 0) {
                this.cache.delete(key); // remove the entry from the cache
                entry.object?.destroy(); // destroy the object
            }
        } else {
            Debug.warn('RefCountedKeyCache: Trying to release object that is not in the cache', {
                key
            });
        }
    }
    constructor(){
        /**
     * Map storing the cache. They key is a look up key for the object, the value is an instance
     * of the Entry class, which wraps the object with a reference count.
     *
     * {@type <object, Entry>}
     * @private
     */ this.cache = new Map();
    }
}

/**
 * Reference counted cache storing multi-sampled versions of depth buffers, which are reference
 * counted and shared between render targets using the same user-specified depth-buffer. This is
 * needed for the cases where the user provided depth buffer is used for depth-pre-pass and then
 * the main render pass - those need to share the same multi-sampled depth buffer.
 */ class MultisampledTextureCache extends RefCountedKeyCache {
    loseContext(device) {
        this.clear(); // just clear the cache when the context is lost
    }
}
// a device cache storing per device instance of MultisampledTextureCache
const multisampledTextureCache = new DeviceCache();
const getMultisampledTextureCache = (device)=>{
    return multisampledTextureCache.get(device, ()=>{
        return new MultisampledTextureCache();
    });
};

/**
 * @import { RenderPass } from '../render-pass.js'
 * @import { RenderTarget } from '../render-target.js'
 * @import { WebgpuGraphicsDevice } from '../webgpu/webgpu-graphics-device.js'
 */ const stringIds = new StringIds();
/**
 * Private class storing info about color buffer.
 *
 * @private
 */ class ColorAttachment {
    destroy() {
        this.multisampledBuffer?.destroy();
        this.multisampledBuffer = null;
    }
}
/**
 * Private class storing info about depth-stencil buffer.
 *
 * @private
 */ class DepthAttachment {
    /**
     * @param {string} gpuFormat - The WebGPU format (GPUTextureFormat).
     */ constructor(gpuFormat){
        /**
     * @type {GPUTexture|null}
     * @private
     */ this.depthTexture = null;
        /**
     * True if the depthTexture is internally allocated / owned
     *
     * @type {boolean}
     */ this.depthTextureInternal = false;
        /**
     * Multi-sampled depth buffer allocated over the user provided depth buffer.
     *
     * @type {GPUTexture|null}
     * @private
     */ this.multisampledDepthBuffer = null;
        Debug.assert(gpuFormat);
        this.format = gpuFormat;
        this.hasStencil = gpuFormat === 'depth24plus-stencil8';
    }
    destroy(device) {
        if (this.depthTextureInternal) {
            this.depthTexture?.destroy();
            this.depthTexture = null;
        }
        // release multi-sampled depth buffer
        if (this.multisampledDepthBuffer) {
            this.multisampledDepthBuffer = null;
            // release reference to the texture, as its ref-counted
            getMultisampledTextureCache(device).release(this.multisampledDepthBufferKey);
        }
    }
}
/**
 * A WebGPU implementation of the RenderTarget.
 *
 * @ignore
 */ class WebgpuRenderTarget {
    /**
     * @param {RenderTarget} renderTarget - The render target owning this implementation.
     */ constructor(renderTarget){
        /** @type {boolean} */ this.initialized = false;
        /** @type {ColorAttachment[]} */ this.colorAttachments = [];
        /** @type {DepthAttachment|null} */ this.depthAttachment = null;
        /**
     * Texture assigned each frame, and not owned by this render target. This is used on the
     * framebuffer to assign per frame texture obtained from the context.
     *
     * @type {GPUTexture}
     * @private
     */ this.assignedColorTexture = null;
        /**
     * Render pass descriptor used when starting a render pass for this render target.
     *
     * @type {GPURenderPassDescriptor}
     * @private
     */ this.renderPassDescriptor = {};
        /**
     * True if this is the backbuffer of the device.
     *
     * @type {boolean}
     */ this.isBackbuffer = false;
        this.renderTarget = renderTarget;
    }
    /**
     * Release associated resources. Note that this needs to leave this instance in a state where
     * it can be re-initialized again, which is used by render target resizing.
     *
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     */ destroy(device) {
        this.initialized = false;
        this.assignedColorTexture = null;
        this.colorAttachments.forEach((colorAttachment)=>{
            colorAttachment.destroy();
        });
        this.colorAttachments.length = 0;
        this.depthAttachment?.destroy(device);
        this.depthAttachment = null;
    }
    updateKey() {
        const rt = this.renderTarget;
        // key used by render pipeline creation
        let key = `${rt.samples}:${this.depthAttachment ? this.depthAttachment.format : 'nodepth'}`;
        this.colorAttachments.forEach((colorAttachment)=>{
            key += `:${colorAttachment.format}`;
        });
        // convert string to a unique number
        this.key = stringIds.get(key);
    }
    /**
     * Assign a color buffer. This allows the color buffer of the main framebuffer
     * to be swapped each frame to a buffer provided by the context.
     *
     * @param {WebgpuGraphicsDevice} device - The WebGPU graphics device.
     * @param {any} gpuTexture - The color buffer.
     */ assignColorTexture(device, gpuTexture) {
        Debug.assert(gpuTexture);
        this.assignedColorTexture = gpuTexture;
        // create view (optionally handles srgb conversion)
        const view = gpuTexture.createView({
            format: device.backBufferViewFormat
        });
        DebugHelper.setLabel(view, 'Framebuffer.assignedColor');
        // use it as render buffer or resolve target
        const colorAttachment = this.renderPassDescriptor.colorAttachments[0];
        const samples = this.renderTarget.samples;
        if (samples > 1) {
            colorAttachment.resolveTarget = view;
        } else {
            colorAttachment.view = view;
        }
        // for main framebuffer, this is how the format is obtained
        this.setColorAttachment(0, undefined, device.backBufferViewFormat);
        this.updateKey();
    }
    setColorAttachment(index, multisampledBuffer, format) {
        if (!this.colorAttachments[index]) {
            this.colorAttachments[index] = new ColorAttachment();
        }
        if (multisampledBuffer) {
            this.colorAttachments[index].multisampledBuffer = multisampledBuffer;
        }
        if (format) {
            this.colorAttachments[index].format = format;
        }
    }
    /**
     * Initialize render target for rendering one time.
     *
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     * @param {RenderTarget} renderTarget - The render target.
     */ init(device, renderTarget) {
        const wgpu = device.wgpu;
        Debug.assert(!this.initialized);
        WebgpuDebug.memory(device);
        WebgpuDebug.validate(device);
        // initialize depth/stencil
        this.initDepthStencil(device, wgpu, renderTarget);
        // initialize color attachments
        // color formats are based on the textures
        if (renderTarget._colorBuffers) {
            renderTarget._colorBuffers.forEach((colorBuffer, index)=>{
                this.setColorAttachment(index, undefined, colorBuffer.impl.format);
            });
        }
        this.renderPassDescriptor.colorAttachments = [];
        const count = this.isBackbuffer ? 1 : renderTarget._colorBuffers?.length ?? 0;
        for(let i = 0; i < count; ++i){
            const colorAttachment = this.initColor(device, wgpu, renderTarget, i);
            // default framebuffer, buffer gets assigned later
            const isDefaultFramebuffer = i === 0 && this.colorAttachments[0]?.format;
            // if we have a color buffer, or is the default framebuffer
            if (colorAttachment.view || isDefaultFramebuffer) {
                this.renderPassDescriptor.colorAttachments.push(colorAttachment);
            }
        }
        this.updateKey();
        this.initialized = true;
        WebgpuDebug.end(device, 'RenderTarget initialization', {
            renderTarget
        });
        WebgpuDebug.end(device, 'RenderTarget initialization', {
            renderTarget
        });
    }
    initDepthStencil(device, wgpu, renderTarget) {
        const { samples, width, height, depth, depthBuffer } = renderTarget;
        // depth buffer that we render to (single or multi-sampled). We don't create resolve
        // depth buffer as we don't currently resolve it. This might need to change in the future.
        if (depth || depthBuffer) {
            // the depth texture view the rendering will write to
            let renderingView;
            // allocate depth buffer if not provided
            if (!depthBuffer) {
                // TODO: support rendering to 32bit depth without a stencil as well
                this.depthAttachment = new DepthAttachment('depth24plus-stencil8');
                /** @type {GPUTextureDescriptor} */ const depthTextureDesc = {
                    size: [
                        width,
                        height,
                        1
                    ],
                    dimension: '2d',
                    sampleCount: samples,
                    format: this.depthAttachment.format,
                    usage: GPUTextureUsage.RENDER_ATTACHMENT
                };
                if (samples > 1) {
                    // enable multi-sampled depth texture to be a source of our shader based resolver in WebgpuResolver
                    // TODO: we do not always need to resolve it, and so might consider this flag to be optional
                    depthTextureDesc.usage |= GPUTextureUsage.TEXTURE_BINDING;
                } else {
                    // single sampled depth buffer can be copied out (grab pass)
                    // TODO: we should not enable this for shadow maps, as it is not needed
                    depthTextureDesc.usage |= GPUTextureUsage.COPY_SRC;
                }
                // allocate depth buffer
                const depthTexture = wgpu.createTexture(depthTextureDesc);
                DebugHelper.setLabel(depthTexture, `${renderTarget.name}.autoDepthTexture`);
                this.depthAttachment.depthTexture = depthTexture;
                this.depthAttachment.depthTextureInternal = true;
                renderingView = depthTexture.createView();
                DebugHelper.setLabel(renderingView, `${renderTarget.name}.autoDepthView`);
            } else {
                this.depthAttachment = new DepthAttachment(depthBuffer.impl.format);
                if (samples > 1) {
                    // single-sampled depthBuffer.impl.format can be R32F in some cases, but that cannot be used as a depth
                    // buffer, only as a texture to resolve it to. We always use depth24plus-stencil8 for msaa depth buffers.
                    const depthFormat = 'depth24plus-stencil8';
                    this.depthAttachment.format = depthFormat;
                    this.depthAttachment.hasStencil = depthFormat === 'depth24plus-stencil8';
                    // key for matching multi-sampled depth buffer
                    const key = `${depthBuffer.id}:${width}:${height}:${samples}:${depthFormat}`;
                    // check if we have already allocated a multi-sampled depth buffer for the depth buffer
                    const msTextures = getMultisampledTextureCache(device);
                    let msDepthTexture = msTextures.get(key); // this incRefs it if found
                    if (!msDepthTexture) {
                        /** @type {GPUTextureDescriptor} */ const multisampledDepthDesc = {
                            size: [
                                width,
                                height,
                                1
                            ],
                            dimension: '2d',
                            sampleCount: samples,
                            format: depthFormat,
                            usage: GPUTextureUsage.RENDER_ATTACHMENT | // if msaa and resolve targets are different formats, we need to be able to bind the msaa target as a texture for manual shader resolve
                            (depthFormat !== depthBuffer.impl.format ? GPUTextureUsage.TEXTURE_BINDING : 0)
                        };
                        // allocate multi-sampled depth buffer
                        msDepthTexture = wgpu.createTexture(multisampledDepthDesc);
                        DebugHelper.setLabel(msDepthTexture, `${renderTarget.name}.multisampledDepth`);
                        // store it in the cache
                        msTextures.set(key, msDepthTexture);
                    }
                    this.depthAttachment.multisampledDepthBuffer = msDepthTexture;
                    this.depthAttachment.multisampledDepthBufferKey = key;
                    renderingView = msDepthTexture.createView();
                    DebugHelper.setLabel(renderingView, `${renderTarget.name}.multisampledDepthView`);
                } else {
                    // use provided depth buffer
                    const depthTexture = depthBuffer.impl.gpuTexture;
                    this.depthAttachment.depthTexture = depthTexture;
                    renderingView = depthTexture.createView();
                    DebugHelper.setLabel(renderingView, `${renderTarget.name}.depthView`);
                }
            }
            Debug.assert(renderingView);
            this.renderPassDescriptor.depthStencilAttachment = {
                view: renderingView
            };
        }
    }
    /**
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     * @param {GPUDevice} wgpu - The WebGPU device.
     * @param {RenderTarget} renderTarget - The render target.
     * @param {number} index - The color buffer index.
     * @returns {GPURenderPassColorAttachment} The color attachment.
     * @private
     */ initColor(device, wgpu, renderTarget, index) {
        // Single-sampled color buffer gets passed in:
        // - for normal render target, constructor takes the color buffer as an option
        // - for the main framebuffer, the device supplies the buffer each frame
        // And so we only need to create multi-sampled color buffer if needed here.
        /** @type {GPURenderPassColorAttachment} */ const colorAttachment = {};
        const { samples, width, height, mipLevel } = renderTarget;
        const colorBuffer = renderTarget.getColorBuffer(index);
        // view used to write to the color buffer (either by rendering to it, or resolving to it)
        let colorView = null;
        if (colorBuffer) {
            // render to a single mip level
            const mipLevelCount = 1;
            // cubemap face view - face is a single 2d array layer in order [+X, -X, +Y, -Y, +Z, -Z]
            if (colorBuffer.cubemap) {
                colorView = colorBuffer.impl.createView({
                    dimension: '2d',
                    baseArrayLayer: renderTarget.face,
                    arrayLayerCount: 1,
                    mipLevelCount,
                    baseMipLevel: mipLevel
                });
            } else {
                colorView = colorBuffer.impl.createView({
                    mipLevelCount,
                    baseMipLevel: mipLevel
                });
            }
        }
        // multi-sampled color buffer
        if (samples > 1) {
            const format = this.isBackbuffer ? device.backBufferViewFormat : colorBuffer.impl.format;
            /** @type {GPUTextureDescriptor} */ const multisampledTextureDesc = {
                size: [
                    width,
                    height,
                    1
                ],
                dimension: '2d',
                sampleCount: samples,
                format: format,
                usage: GPUTextureUsage.RENDER_ATTACHMENT
            };
            // allocate multi-sampled color buffer
            const multisampledColorBuffer = wgpu.createTexture(multisampledTextureDesc);
            DebugHelper.setLabel(multisampledColorBuffer, `${renderTarget.name}.multisampledColor`);
            this.setColorAttachment(index, multisampledColorBuffer, multisampledTextureDesc.format);
            colorAttachment.view = multisampledColorBuffer.createView();
            DebugHelper.setLabel(colorAttachment.view, `${renderTarget.name}.multisampledColorView`);
            colorAttachment.resolveTarget = colorView;
        } else {
            colorAttachment.view = colorView;
        }
        return colorAttachment;
    }
    /**
     * Update WebGPU render pass descriptor by RenderPass settings.
     *
     * @param {RenderPass} renderPass - The render pass to start.
     * @param {RenderTarget} renderTarget - The render target to render to.
     */ setupForRenderPass(renderPass, renderTarget) {
        Debug.assert(this.renderPassDescriptor);
        const count = this.renderPassDescriptor.colorAttachments?.length ?? 0;
        for(let i = 0; i < count; ++i){
            const colorAttachment = this.renderPassDescriptor.colorAttachments[i];
            const colorOps = renderPass.colorArrayOps[i];
            const srgb = renderTarget.isColorBufferSrgb(i);
            colorAttachment.clearValue = srgb ? colorOps.clearValueLinear : colorOps.clearValue;
            colorAttachment.loadOp = colorOps.clear ? 'clear' : 'load';
            colorAttachment.storeOp = colorOps.store ? 'store' : 'discard';
        }
        const depthAttachment = this.renderPassDescriptor.depthStencilAttachment;
        if (depthAttachment) {
            depthAttachment.depthClearValue = renderPass.depthStencilOps.clearDepthValue;
            depthAttachment.depthLoadOp = renderPass.depthStencilOps.clearDepth ? 'clear' : 'load';
            depthAttachment.depthStoreOp = renderPass.depthStencilOps.storeDepth ? 'store' : 'discard';
            depthAttachment.depthReadOnly = false;
            if (this.depthAttachment.hasStencil) {
                depthAttachment.stencilClearValue = renderPass.depthStencilOps.clearStencilValue;
                depthAttachment.stencilLoadOp = renderPass.depthStencilOps.clearStencil ? 'clear' : 'load';
                depthAttachment.stencilStoreOp = renderPass.depthStencilOps.storeStencil ? 'store' : 'discard';
                depthAttachment.stencilReadOnly = false;
            }
        }
    }
    loseContext() {
        this.initialized = false;
    }
    resolve(device, target, color, depth) {}
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { ScopeId } from './scope-id.js'
 */ // map of UNIFORMTYPE_*** to number of 32bit components
const uniformTypeToNumComponents = [];
uniformTypeToNumComponents[UNIFORMTYPE_FLOAT] = 1;
uniformTypeToNumComponents[UNIFORMTYPE_VEC2] = 2;
uniformTypeToNumComponents[UNIFORMTYPE_VEC3] = 3;
uniformTypeToNumComponents[UNIFORMTYPE_VEC4] = 4;
uniformTypeToNumComponents[UNIFORMTYPE_INT] = 1;
uniformTypeToNumComponents[UNIFORMTYPE_IVEC2] = 2;
uniformTypeToNumComponents[UNIFORMTYPE_IVEC3] = 3;
uniformTypeToNumComponents[UNIFORMTYPE_IVEC4] = 4;
uniformTypeToNumComponents[UNIFORMTYPE_BOOL] = 1;
uniformTypeToNumComponents[UNIFORMTYPE_BVEC2] = 2;
uniformTypeToNumComponents[UNIFORMTYPE_BVEC3] = 3;
uniformTypeToNumComponents[UNIFORMTYPE_BVEC4] = 4;
uniformTypeToNumComponents[UNIFORMTYPE_MAT2] = 8; // 2 x vec4
uniformTypeToNumComponents[UNIFORMTYPE_MAT3] = 12; // 3 x vec4
uniformTypeToNumComponents[UNIFORMTYPE_MAT4] = 16; // 4 x vec4
uniformTypeToNumComponents[UNIFORMTYPE_UINT] = 1;
uniformTypeToNumComponents[UNIFORMTYPE_UVEC2] = 2;
uniformTypeToNumComponents[UNIFORMTYPE_UVEC3] = 3;
uniformTypeToNumComponents[UNIFORMTYPE_UVEC4] = 4;
/**
 * A class storing description of an individual uniform, stored inside a uniform buffer.
 *
 * @category Graphics
 */ class UniformFormat {
    /**
     * True if this is an array of elements (i.e. count > 0)
     *
     * @type {boolean}
     */ get isArrayType() {
        return this.count > 0;
    }
    /**
     * Create a new UniformFormat instance.
     *
     * @param {string} name - The name of the uniform.
     * @param {number} type - The type of the uniform. One of the UNIFORMTYPE_*** constants.
     * @param {number} count - The number of elements in the array. Defaults to 0, which represents
     * a single element (not an array).
     */ constructor(name, type, count = 0){
        // just a name
        this.shortName = name;
        // name with [0] if this is an array
        this.name = count ? `${name}[0]` : name;
        this.type = type;
        this.numComponents = uniformTypeToNumComponents[type];
        Debug.assert(this.numComponents, `Unhandled uniform format ${type} used for ${name}`);
        this.updateType = type;
        if (count > 0) {
            switch(type){
                case UNIFORMTYPE_FLOAT:
                    this.updateType = UNIFORMTYPE_FLOATARRAY;
                    break;
                case UNIFORMTYPE_INT:
                    this.updateType = UNIFORMTYPE_INTARRAY;
                    break;
                case UNIFORMTYPE_UINT:
                    this.updateType = UNIFORMTYPE_UINTARRAY;
                    break;
                case UNIFORMTYPE_BOOL:
                    this.updateType = UNIFORMTYPE_BOOLARRAY;
                    break;
                case UNIFORMTYPE_VEC2:
                    this.updateType = UNIFORMTYPE_VEC2ARRAY;
                    break;
                case UNIFORMTYPE_IVEC2:
                    this.updateType = UNIFORMTYPE_IVEC2ARRAY;
                    break;
                case UNIFORMTYPE_UVEC2:
                    this.updateType = UNIFORMTYPE_UVEC2ARRAY;
                    break;
                case UNIFORMTYPE_BVEC2:
                    this.updateType = UNIFORMTYPE_BVEC2ARRAY;
                    break;
                case UNIFORMTYPE_VEC3:
                    this.updateType = UNIFORMTYPE_VEC3ARRAY;
                    break;
                case UNIFORMTYPE_IVEC3:
                    this.updateType = UNIFORMTYPE_IVEC3ARRAY;
                    break;
                case UNIFORMTYPE_UVEC3:
                    this.updateType = UNIFORMTYPE_UVEC3ARRAY;
                    break;
                case UNIFORMTYPE_BVEC3:
                    this.updateType = UNIFORMTYPE_BVEC3ARRAY;
                    break;
                case UNIFORMTYPE_VEC4:
                    this.updateType = UNIFORMTYPE_VEC4ARRAY;
                    break;
                case UNIFORMTYPE_IVEC4:
                    this.updateType = UNIFORMTYPE_IVEC4ARRAY;
                    break;
                case UNIFORMTYPE_UVEC4:
                    this.updateType = UNIFORMTYPE_UVEC4ARRAY;
                    break;
                case UNIFORMTYPE_BVEC4:
                    this.updateType = UNIFORMTYPE_BVEC4ARRAY;
                    break;
                case UNIFORMTYPE_MAT4:
                    this.updateType = UNIFORMTYPE_MAT4ARRAY;
                    break;
                default:
                    Debug.error(`Uniform array of type ${uniformTypeToName[type]} is not supported when processing uniform '${name}'.`);
                    Debug.call(()=>{
                        this.invalid = true;
                    });
                    break;
            }
        }
        this.count = count;
        Debug.assert(!isNaN(count), `Unsupported uniform: ${name}[${count}]`);
        Debug.call(()=>{
            if (isNaN(count)) {
                this.invalid = true;
            }
        });
        let componentSize = this.numComponents;
        // component size for arrays is aligned up to vec4
        if (count) {
            componentSize = math.roundUp(componentSize, 4);
        }
        this.byteSize = componentSize * 4;
        if (count) {
            this.byteSize *= count;
        }
        Debug.assert(this.byteSize, `Unknown byte size for uniform format ${type} used for ${name}`);
    }
    // std140 rules: https://registry.khronos.org/OpenGL/specs/gl/glspec45.core.pdf#page=159
    // TODO: this supports limited subset of functionality, arrays and arrays of structs are not supported.
    calculateOffset(offset) {
        // Note: vec3 has the same alignment as vec4
        let alignment = this.byteSize <= 8 ? this.byteSize : 16;
        // arrays have vec4 alignments
        if (this.count) {
            alignment = 16;
        }
        // align the start offset
        offset = math.roundUp(offset, alignment);
        this.offset = offset / 4;
    }
}
/**
 * A descriptor that defines the layout of of data inside the uniform buffer.
 *
 * @category Graphics
 */ class UniformBufferFormat {
    /**
     * Create a new UniformBufferFormat instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device.
     * @param {UniformFormat[]} uniforms - An array of uniforms to be stored in the buffer
     */ constructor(graphicsDevice, uniforms){
        /**
     * @type {number}
     * @ignore
     */ this.byteSize = 0;
        /**
     * @type {Map<string,UniformFormat>}
     * @ignore
     */ this.map = new Map();
        this.scope = graphicsDevice.scope;
        /** @type {UniformFormat[]} */ this.uniforms = uniforms;
        // TODO: optimize uniforms ordering
        let offset = 0;
        for(let i = 0; i < uniforms.length; i++){
            const uniform = uniforms[i];
            uniform.calculateOffset(offset);
            offset = uniform.offset * 4 + uniform.byteSize;
            uniform.scopeId = this.scope.resolve(uniform.name);
            this.map.set(uniform.name, uniform);
        }
        // round up buffer size
        this.byteSize = math.roundUp(offset, 16);
    }
    /**
     * Returns format of a uniform with specified name. Returns undefined if the uniform is not found.
     *
     * @param {string} name - The name of the uniform.
     * @returns {UniformFormat|undefined} - The format of the uniform.
     */ get(name) {
        return this.map.get(name);
    }
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { ShaderProcessorOptions } from './shader-processor-options.js'
 * @import { Shader } from './shader.js'
 */ // accepted keywords
// TODO: 'out' keyword is not in the list, as handling it is more complicated due
// to 'out' keyword also being used to mark output only function parameters.
const KEYWORD$2 = /[ \t]*(\battribute\b|\bvarying\b|\buniform\b)/g;
// match 'attribute' and anything else till ';'
// eslint-disable-next-line regexp/no-unused-capturing-group, regexp/no-super-linear-backtracking
const KEYWORD_LINE$1 = /(\battribute\b|\bvarying\b|\bout\b|\buniform\b)[ \t]*([^;]+)(;+)/g;
// marker for a place in the source code to be replaced by code
const MARKER$1 = '@@@';
// an array identifier, for example 'data[4]' - group 1 is 'data', group 2 is everything in brackets: '4'
const ARRAY_IDENTIFIER = /([\w-]+)\[(.*?)\]/;
const precisionQualifiers = new Set([
    'highp',
    'mediump',
    'lowp'
]);
const shadowSamplers = new Set([
    'sampler2DShadow',
    'samplerCubeShadow',
    'sampler2DArrayShadow'
]);
const textureDimensions = {
    sampler2D: TEXTUREDIMENSION_2D,
    sampler3D: TEXTUREDIMENSION_3D,
    samplerCube: TEXTUREDIMENSION_CUBE,
    samplerCubeShadow: TEXTUREDIMENSION_CUBE,
    sampler2DShadow: TEXTUREDIMENSION_2D,
    sampler2DArray: TEXTUREDIMENSION_2D_ARRAY,
    sampler2DArrayShadow: TEXTUREDIMENSION_2D_ARRAY,
    isampler2D: TEXTUREDIMENSION_2D,
    usampler2D: TEXTUREDIMENSION_2D,
    isampler3D: TEXTUREDIMENSION_3D,
    usampler3D: TEXTUREDIMENSION_3D,
    isamplerCube: TEXTUREDIMENSION_CUBE,
    usamplerCube: TEXTUREDIMENSION_CUBE,
    isampler2DArray: TEXTUREDIMENSION_2D_ARRAY,
    usampler2DArray: TEXTUREDIMENSION_2D_ARRAY
};
const textureDimensionInfo = {
    [TEXTUREDIMENSION_2D]: 'texture2D',
    [TEXTUREDIMENSION_CUBE]: 'textureCube',
    [TEXTUREDIMENSION_3D]: 'texture3D',
    [TEXTUREDIMENSION_2D_ARRAY]: 'texture2DArray'
};
let UniformLine$1 = class UniformLine {
    constructor(line, shader){
        // example: `lowp vec4 tints[2 * 4]`
        this.line = line;
        // split to words handling any number of spaces
        const words = line.trim().split(/\s+/);
        // optional precision
        if (precisionQualifiers.has(words[0])) {
            this.precision = words.shift();
        }
        // type
        this.type = words.shift();
        if (line.includes(',')) {
            Debug.error(`A comma on a uniform line is not supported, split it into multiple uniforms: ${line}`, shader);
        }
        // array of uniforms
        if (line.includes('[')) {
            const rest = words.join(' ');
            const match = ARRAY_IDENTIFIER.exec(rest);
            Debug.assert(match);
            this.name = match[1];
            this.arraySize = Number(match[2]);
            if (isNaN(this.arraySize)) {
                shader.failed = true;
                Debug.error(`Only numerically specified uniform array sizes are supported, this uniform is not supported: '${line}'`, shader);
            }
        } else {
            // simple uniform
            this.name = words.shift();
            this.arraySize = 0;
        }
        this.isSampler = this.type.indexOf('sampler') !== -1;
        this.isSignedInt = this.type.indexOf('isampler') !== -1;
        this.isUnsignedInt = this.type.indexOf('usampler') !== -1;
    }
};
/**
 * Pure static class implementing processing of GLSL shaders. It allocates fixed locations for
 * attributes, and handles conversion of uniforms to uniform buffers.
 */ class ShaderProcessorGLSL {
    /**
     * Process the shader.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {object} shaderDefinition - The shader definition.
     * @param {Shader} shader - The shader.
     * @returns {object} - The processed shader data.
     */ static run(device, shaderDefinition, shader) {
        /** @type {Map<string, number>} */ const varyingMap = new Map();
        // extract lines of interests from both shaders
        const vertexExtracted = ShaderProcessorGLSL.extract(shaderDefinition.vshader);
        const fragmentExtracted = ShaderProcessorGLSL.extract(shaderDefinition.fshader);
        // VS - convert a list of attributes to a shader block with fixed locations
        const attributesMap = new Map();
        const attributesBlock = ShaderProcessorGLSL.processAttributes(vertexExtracted.attributes, shaderDefinition.attributes, attributesMap, shaderDefinition.processingOptions);
        // VS - convert a list of varyings to a shader block
        const vertexVaryingsBlock = ShaderProcessorGLSL.processVaryings(vertexExtracted.varyings, varyingMap, true);
        // FS - convert a list of varyings to a shader block
        const fragmentVaryingsBlock = ShaderProcessorGLSL.processVaryings(fragmentExtracted.varyings, varyingMap, false);
        // FS - convert a list of outputs to a shader block
        const outBlock = ShaderProcessorGLSL.processOuts(fragmentExtracted.outs);
        // uniforms - merge vertex and fragment uniforms, and create shared uniform buffers
        // Note that as both vertex and fragment can declare the same uniform, we need to remove duplicates
        const concatUniforms = vertexExtracted.uniforms.concat(fragmentExtracted.uniforms);
        const uniforms = Array.from(new Set(concatUniforms));
        // parse uniform lines
        const parsedUniforms = uniforms.map((line)=>new UniformLine$1(line, shader));
        // validation - as uniforms go to a shared uniform buffer, vertex and fragment versions need to match
        Debug.call(()=>{
            const map = new Map();
            parsedUniforms.forEach((uni)=>{
                const existing = map.get(uni.name);
                Debug.assert(!existing, `Vertex and fragment shaders cannot use the same uniform name with different types: '${existing}' and '${uni.line}'`, shader);
                map.set(uni.name, uni.line);
            });
        });
        const uniformsData = ShaderProcessorGLSL.processUniforms(device, parsedUniforms, shaderDefinition.processingOptions, shader);
        // VS - insert the blocks to the source
        const vBlock = `${attributesBlock}\n${vertexVaryingsBlock}\n${uniformsData.code}`;
        const vshader = vertexExtracted.src.replace(MARKER$1, vBlock);
        // FS - insert the blocks to the source
        const fBlock = `${fragmentVaryingsBlock}\n${outBlock}\n${uniformsData.code}`;
        const fshader = fragmentExtracted.src.replace(MARKER$1, fBlock);
        return {
            vshader: vshader,
            fshader: fshader,
            attributes: attributesMap,
            meshUniformBufferFormat: uniformsData.meshUniformBufferFormat,
            meshBindGroupFormat: uniformsData.meshBindGroupFormat
        };
    }
    // Extract required information from the shader source code.
    static extract(src) {
        // collected data
        const attributes = [];
        const varyings = [];
        const outs = [];
        const uniforms = [];
        // replacement marker - mark a first replacement place, this is where code
        // blocks are injected later
        let replacement = `${MARKER$1}\n`;
        // extract relevant parts of the shader
        let match;
        while((match = KEYWORD$2.exec(src)) !== null){
            const keyword = match[1];
            switch(keyword){
                case 'attribute':
                case 'varying':
                case 'uniform':
                case 'out':
                    {
                        // read the line
                        KEYWORD_LINE$1.lastIndex = match.index;
                        const lineMatch = KEYWORD_LINE$1.exec(src);
                        if (keyword === 'attribute') {
                            attributes.push(lineMatch[2]);
                        } else if (keyword === 'varying') {
                            varyings.push(lineMatch[2]);
                        } else if (keyword === 'out') {
                            outs.push(lineMatch[2]);
                        } else if (keyword === 'uniform') {
                            uniforms.push(lineMatch[2]);
                        }
                        // cut it out
                        src = ShaderProcessorGLSL.cutOut(src, match.index, KEYWORD_LINE$1.lastIndex, replacement);
                        KEYWORD$2.lastIndex = match.index + replacement.length;
                        // only place a single replacement marker
                        replacement = '';
                        break;
                    }
            }
        }
        return {
            src,
            attributes,
            varyings,
            outs,
            uniforms
        };
    }
    /**
     * Process the lines with uniforms. The function receives the lines containing all uniforms,
     * both numerical as well as textures/samplers. The function also receives the format of uniform
     * buffers (numerical) and bind groups (textures) for view and material level. All uniforms that
     * match any of those are ignored, as those would be supplied by view / material level buffers.
     * All leftover uniforms create uniform buffer and bind group for the mesh itself, containing
     * uniforms that change on the level of the mesh.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {Array<UniformLine>} uniforms - Lines containing uniforms.
     * @param {ShaderProcessorOptions} processingOptions - Uniform formats.
     * @param {Shader} shader - The shader definition.
     * @returns {object} - The uniform data. Returns a shader code block containing uniforms, to be
     * inserted into the shader, as well as generated uniform format structures for the mesh level.
     */ static processUniforms(device, uniforms, processingOptions, shader) {
        // split uniform lines into samplers and the rest
        /** @type {Array<UniformLine>} */ const uniformLinesSamplers = [];
        /** @type {Array<UniformLine>} */ const uniformLinesNonSamplers = [];
        uniforms.forEach((uniform)=>{
            if (uniform.isSampler) {
                uniformLinesSamplers.push(uniform);
            } else {
                uniformLinesNonSamplers.push(uniform);
            }
        });
        // build mesh uniform buffer format
        const meshUniforms = [];
        uniformLinesNonSamplers.forEach((uniform)=>{
            // uniforms not already in supplied uniform buffers go to the mesh buffer
            if (!processingOptions.hasUniform(uniform.name)) {
                const uniformType = uniformTypeToName.indexOf(uniform.type);
                Debug.assert(uniformType >= 0, `Uniform type ${uniform.type} is not recognized on line [${uniform.line}]`);
                const uniformFormat = new UniformFormat(uniform.name, uniformType, uniform.arraySize);
                Debug.assert(!uniformFormat.invalid, `Invalid uniform line: ${uniform.line}`, shader);
                meshUniforms.push(uniformFormat);
            }
        // validate types in else
        });
        // if we don't have any uniform, add a dummy uniform to avoid empty uniform buffer - WebGPU rendering does not
        // support rendering will NULL bind group as binding a null buffer changes placement of other bindings
        if (meshUniforms.length === 0) {
            meshUniforms.push(new UniformFormat(UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT));
        }
        const meshUniformBufferFormat = meshUniforms.length ? new UniformBufferFormat(device, meshUniforms) : null;
        // build mesh bind group format - this contains the textures, but not the uniform buffer as that is a separate binding
        const textureFormats = [];
        uniformLinesSamplers.forEach((uniform)=>{
            // unmatched texture uniforms go to mesh block
            if (!processingOptions.hasTexture(uniform.name)) {
                // sample type
                // WebGpu does not currently support filtered float format textures, and so we map them to unfilterable type
                // as we sample them without filtering anyways
                let sampleType = SAMPLETYPE_FLOAT;
                if (uniform.isSignedInt) {
                    sampleType = SAMPLETYPE_INT;
                } else if (uniform.isUnsignedInt) {
                    sampleType = SAMPLETYPE_UINT;
                } else {
                    if (uniform.precision === 'highp') {
                        sampleType = SAMPLETYPE_UNFILTERABLE_FLOAT;
                    }
                    if (shadowSamplers.has(uniform.type)) {
                        sampleType = SAMPLETYPE_DEPTH;
                    }
                }
                // dimension
                const dimension = textureDimensions[uniform.type];
                // TODO: we could optimize visibility to only stages that use any of the data
                textureFormats.push(new BindTextureFormat(uniform.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, dimension, sampleType));
            }
        // validate types in else
        });
        const meshBindGroupFormat = new BindGroupFormat(device, textureFormats);
        // generate code for uniform buffers
        let code = '';
        processingOptions.uniformFormats.forEach((format, bindGroupIndex)=>{
            if (format) {
                code += ShaderProcessorGLSL.getUniformShaderDeclaration(format, bindGroupIndex, 0);
            }
        });
        // and also for generated mesh format, which is at the slot 0 of the bind group
        if (meshUniformBufferFormat) {
            code += ShaderProcessorGLSL.getUniformShaderDeclaration(meshUniformBufferFormat, BINDGROUP_MESH_UB, 0);
        }
        // generate code for textures
        processingOptions.bindGroupFormats.forEach((format, bindGroupIndex)=>{
            if (format) {
                code += ShaderProcessorGLSL.getTexturesShaderDeclaration(format, bindGroupIndex);
            }
        });
        // and also for generated mesh format
        code += ShaderProcessorGLSL.getTexturesShaderDeclaration(meshBindGroupFormat, BINDGROUP_MESH);
        return {
            code,
            meshUniformBufferFormat,
            meshBindGroupFormat
        };
    }
    static processVaryings(varyingLines, varyingMap, isVertex) {
        let block = '';
        const op = isVertex ? 'out' : 'in';
        varyingLines.forEach((line, index)=>{
            const words = ShaderProcessorGLSL.splitToWords(line);
            const type = words.slice(0, -1).join(' ');
            const name = words[words.length - 1];
            if (isVertex) {
                // store it in the map
                varyingMap.set(name, index);
            } else {
                Debug.assert(varyingMap.has(name), `Fragment shader requires varying [${name}] but vertex shader does not generate it.`);
                index = varyingMap.get(name);
            }
            // generates: 'layout(location = 0) in vec4 position;'
            block += `layout(location = ${index}) ${op} ${type} ${name};\n`;
        });
        return block;
    }
    static processOuts(outsLines) {
        let block = '';
        outsLines.forEach((line, index)=>{
            // generates: 'layout(location = 0) out vec4 gl_FragColor;'
            block += `layout(location = ${index}) out ${line};\n`;
        });
        return block;
    }
    // extract count from type ('vec3' => 3, 'float' => 1)
    static getTypeCount(type) {
        const lastChar = type.substring(type.length - 1);
        const num = parseInt(lastChar, 10);
        return isNaN(num) ? 1 : num;
    }
    static processAttributes(attributeLines, shaderDefinitionAttributes, attributesMap, processingOptions) {
        let block = '';
        const usedLocations = {};
        attributeLines.forEach((line)=>{
            const words = ShaderProcessorGLSL.splitToWords(line);
            let type = words[0];
            let name = words[1];
            if (shaderDefinitionAttributes.hasOwnProperty(name)) {
                const semantic = shaderDefinitionAttributes[name];
                const location = semanticToLocation[semantic];
                Debug.assert(location !== undefined, `Semantic ${semantic} used by the attribute ${name} is not known - make sure it's one of the supported semantics.`);
                Debug.assert(!usedLocations.hasOwnProperty(location), `WARNING: Two vertex attributes are mapped to the same location in a shader: ${usedLocations[location]} and ${semantic}`);
                usedLocations[location] = semantic;
                // build a map of used attributes
                attributesMap.set(location, name);
                // if vertex format for this attribute is not of a float type, we need to adjust the attribute format, for example we convert
                //      attribute vec4 vertex_position;
                // to
                //      attribute ivec4 _private_vertex_position;
                //      vec4 vertex_position = vec4(_private_vertex_position);
                // Note that we skip normalized elements, as shader receives them as floats already.
                let copyCode;
                const element = processingOptions.getVertexElement(semantic);
                if (element) {
                    const dataType = element.dataType;
                    if (dataType !== TYPE_FLOAT32 && dataType !== TYPE_FLOAT16 && !element.normalize && !element.asInt) {
                        const attribNumElements = ShaderProcessorGLSL.getTypeCount(type);
                        const newName = `_private_${name}`;
                        // second line of new code, copy private (u)int type into vec type
                        copyCode = `vec${attribNumElements} ${name} = vec${attribNumElements}(${newName});\n`;
                        name = newName;
                        // new attribute type, based on the vertex format element type, example: vec3 -> ivec3
                        const isSignedType = dataType === TYPE_INT8 || dataType === TYPE_INT16 || dataType === TYPE_INT32;
                        if (attribNumElements === 1) {
                            type = isSignedType ? 'int' : 'uint';
                        } else {
                            type = isSignedType ? `ivec${attribNumElements}` : `uvec${attribNumElements}`;
                        }
                    }
                }
                // generates: 'layout(location = 0) in vec4 position;'
                block += `layout(location = ${location}) in ${type} ${name};\n`;
                if (copyCode) {
                    block += copyCode;
                }
            }
        });
        return block;
    }
    static splitToWords(line) {
        // remove any double spaces
        line = line.replace(/\s+/g, ' ').trim();
        return line.split(' ');
    }
    static cutOut(src, start, end, replacement) {
        return src.substring(0, start) + replacement + src.substring(end);
    }
    static getUniformShaderDeclaration(format, bindGroup, bindIndex) {
        const name = bindGroupNames[bindGroup];
        let code = `layout(set = ${bindGroup}, binding = ${bindIndex}, std140) uniform ub_${name} {\n`;
        format.uniforms.forEach((uniform)=>{
            const typeString = uniformTypeToName[uniform.type];
            Debug.assert(typeString.length > 0, `Uniform type ${uniform.type} is not handled.`);
            code += `    ${typeString} ${uniform.shortName}${uniform.count ? `[${uniform.count}]` : ''};\n`;
        });
        return `${code}};\n`;
    }
    static getTexturesShaderDeclaration(bindGroupFormat, bindGroup) {
        let code = '';
        bindGroupFormat.textureFormats.forEach((format)=>{
            let textureType = textureDimensionInfo[format.textureDimension];
            Debug.assert(textureType, 'Unsupported texture type', format.textureDimension);
            const isArray = textureType === 'texture2DArray';
            const sampleTypePrefix = format.sampleType === SAMPLETYPE_UINT ? 'u' : format.sampleType === SAMPLETYPE_INT ? 'i' : '';
            textureType = `${sampleTypePrefix}${textureType}`;
            // handle texture2DArray by renaming the texture object and defining a replacement macro
            let namePostfix = '';
            let extraCode = '';
            if (isArray) {
                namePostfix = '_texture';
                extraCode = `#define ${format.name} ${sampleTypePrefix}sampler2DArray(${format.name}${namePostfix}, ${format.name}_sampler)\n`;
            }
            code += `layout(set = ${bindGroup}, binding = ${format.slot}) uniform ${textureType} ${format.name}${namePostfix};\n`;
            if (format.hasSampler) {
                code += `layout(set = ${bindGroup}, binding = ${format.slot + 1}) uniform sampler ${format.name}_sampler;\n`;
            }
            code += extraCode;
        });
        return code;
    }
}

/**
 * @import { GraphicsDevice } from '../graphics-device.js'
 * @import { ShaderProcessorOptions } from '../shader-processor-options.js'
 * @import { Shader } from '../shader.js'
 */ // matches lines where the keyword is the first non-whitespace content, followed by a whitespace
const KEYWORD$1 = /^[ \t]*(attribute|varying|uniform)[\t ]+/gm;
// match 'attribute' and anything else till ';'
// eslint-disable-next-line
const KEYWORD_LINE = /^[ \t]*(attribute|varying|uniform)[ \t]*([^;]+)(;+)/gm;
// match global variables
//   branch A matches: var<storage,...>
//   branch B matches: texture, storage buffer, storage texture or external texture
// eslint-disable-next-line
const KEYWORD_RESOURCE = /^[ \t]*var\s*(?:(<storage,[^>]*>)\s*([\w\d_]+)\s*:\s*(.*?)\s*;|(<(?!storage,)[^>]*>)?\s*([\w\d_]+)\s*:\s*(texture_.*|storage_texture_.*|storage\w.*|external_texture|sampler(?:_comparison)?)\s*;)\s*$/gm;
// match varying name from string like: '@interpolate(perspective, centroid) smoothColor : vec3f;'
// eslint-disable-next-line
const VARYING = /(?:@interpolate\([^)]*\)\s*)?([\w]+)\s*:/;
// marker for a place in the source code to be replaced by code
const MARKER = '@@@';
// matches vertex of fragment entry function, extracts the input name. Ends at the start of the function body '{'.
const ENTRY_FUNCTION = /(@vertex|@fragment)\s*fn\s+\w+\s*\(\s*(\w+)\s*:[\s\S]*?\{/;
const textureBaseInfo = {
    'texture_1d': {
        viewDimension: TEXTUREDIMENSION_1D,
        baseSampleType: SAMPLETYPE_FLOAT
    },
    'texture_2d': {
        viewDimension: TEXTUREDIMENSION_2D,
        baseSampleType: SAMPLETYPE_FLOAT
    },
    'texture_2d_array': {
        viewDimension: TEXTUREDIMENSION_2D_ARRAY,
        baseSampleType: SAMPLETYPE_FLOAT
    },
    'texture_3d': {
        viewDimension: TEXTUREDIMENSION_3D,
        baseSampleType: SAMPLETYPE_FLOAT
    },
    'texture_cube': {
        viewDimension: TEXTUREDIMENSION_CUBE,
        baseSampleType: SAMPLETYPE_FLOAT
    },
    'texture_cube_array': {
        viewDimension: TEXTUREDIMENSION_CUBE_ARRAY,
        baseSampleType: SAMPLETYPE_FLOAT
    },
    'texture_multisampled_2d': {
        viewDimension: TEXTUREDIMENSION_2D,
        baseSampleType: SAMPLETYPE_FLOAT
    },
    'texture_depth_2d': {
        viewDimension: TEXTUREDIMENSION_2D,
        baseSampleType: SAMPLETYPE_DEPTH
    },
    'texture_depth_2d_array': {
        viewDimension: TEXTUREDIMENSION_2D_ARRAY,
        baseSampleType: SAMPLETYPE_DEPTH
    },
    'texture_depth_cube': {
        viewDimension: TEXTUREDIMENSION_CUBE,
        baseSampleType: SAMPLETYPE_DEPTH
    },
    'texture_depth_cube_array': {
        viewDimension: TEXTUREDIMENSION_CUBE_ARRAY,
        baseSampleType: SAMPLETYPE_DEPTH
    },
    'texture_external': {
        viewDimension: TEXTUREDIMENSION_2D,
        baseSampleType: SAMPLETYPE_UNFILTERABLE_FLOAT
    }
};
// get the view dimension and sample type for a given texture type
// example: texture_2d_array<u32> -> 2d_array & uint
const getTextureInfo = (baseType, componentType)=>{
    const baseInfo = textureBaseInfo[baseType];
    Debug.assert(baseInfo);
    let finalSampleType = baseInfo.baseSampleType;
    if (baseInfo.baseSampleType === SAMPLETYPE_FLOAT && baseType !== 'texture_multisampled_2d') {
        switch(componentType){
            case 'u32':
                finalSampleType = SAMPLETYPE_UINT;
                break;
            case 'i32':
                finalSampleType = SAMPLETYPE_INT;
                break;
            case 'f32':
                finalSampleType = SAMPLETYPE_FLOAT;
                break;
            // custom 'uff' type for unfilterable float, allowing us to create correct bind, which is automatically generated based on the shader
            case 'uff':
                finalSampleType = SAMPLETYPE_UNFILTERABLE_FLOAT;
                break;
        }
    }
    return {
        viewDimension: baseInfo.viewDimension,
        sampleType: finalSampleType
    };
};
// reverse to getTextureInfo, convert view dimension and sample type to texture declaration
// example: 2d_array & float -> texture_2d_array<f32>
const getTextureDeclarationType = (viewDimension, sampleType)=>{
    // types without template specifiers
    if (sampleType === SAMPLETYPE_DEPTH) {
        switch(viewDimension){
            case TEXTUREDIMENSION_2D:
                return 'texture_depth_2d';
            case TEXTUREDIMENSION_2D_ARRAY:
                return 'texture_depth_2d_array';
            case TEXTUREDIMENSION_CUBE:
                return 'texture_depth_cube';
            case TEXTUREDIMENSION_CUBE_ARRAY:
                return 'texture_depth_cube_array';
            default:
                Debug.assert(false);
        }
    }
    // the base texture type string based on dimension
    let baseTypeString;
    switch(viewDimension){
        case TEXTUREDIMENSION_1D:
            baseTypeString = 'texture_1d';
            break;
        case TEXTUREDIMENSION_2D:
            baseTypeString = 'texture_2d';
            break;
        case TEXTUREDIMENSION_2D_ARRAY:
            baseTypeString = 'texture_2d_array';
            break;
        case TEXTUREDIMENSION_3D:
            baseTypeString = 'texture_3d';
            break;
        case TEXTUREDIMENSION_CUBE:
            baseTypeString = 'texture_cube';
            break;
        case TEXTUREDIMENSION_CUBE_ARRAY:
            baseTypeString = 'texture_cube_array';
            break;
        default:
            Debug.assert(false);
    }
    // component format string ('f32', 'u32', 'i32')
    let coreFormatString;
    switch(sampleType){
        case SAMPLETYPE_FLOAT:
        case SAMPLETYPE_UNFILTERABLE_FLOAT:
            coreFormatString = 'f32';
            break;
        case SAMPLETYPE_UINT:
            coreFormatString = 'u32';
            break;
        case SAMPLETYPE_INT:
            coreFormatString = 'i32';
            break;
        default:
            Debug.assert(false);
    }
    // final type
    return `${baseTypeString}<${coreFormatString}>`;
};
const wrappedArrayTypes = {
    'f32': 'WrappedF32',
    'i32': 'WrappedI32',
    'u32': 'WrappedU32',
    'vec2f': 'WrappedVec2F',
    'vec2i': 'WrappedVec2I',
    'vec2u': 'WrappedVec2U'
};
const splitToWords = (line)=>{
    // remove any double spaces
    line = line.replace(/\s+/g, ' ').trim();
    // Split by spaces or ':' symbol
    return line.split(/[\s:]+/);
};
// matches: array<f32, 4>;
// eslint-disable-next-line
const UNIFORM_ARRAY_REGEX = /array<([^,]+),\s*([^>]+)>/;
class UniformLine {
    constructor(line, shader){
        /**
     * A name of the ub buffer which this uniform is assigned to.
     *
     * @type {string|null}
     */ this.ubName = null;
        this.arraySize = 0;
        // Save the raw line
        this.line = line;
        // Use splitToWords to split the line into parts
        const parts = splitToWords(line);
        if (parts.length < 2) {
            Debug.error(`Invalid uniform line format: ${line}`, shader);
            shader.failed = true;
            return;
        }
        // Extract the name and type
        this.name = parts[0];
        this.type = parts.slice(1).join(' ');
        // array of uniforms (e.g. array<f32, 5>)
        if (this.type.includes('array<')) {
            const match = UNIFORM_ARRAY_REGEX.exec(this.type);
            Debug.assert(match, `Array type on line [${line}] is not supported.`);
            // array type
            this.type = match[1].trim();
            this.arraySize = Number(match[2]);
            if (isNaN(this.arraySize)) {
                shader.failed = true;
                Debug.error(`Only numerically specified uniform array sizes are supported, this uniform is not supported: '${line}'`, shader);
            }
        }
    }
}
// regex constants for resource lines, for example:
//     var diffuseTexture : texture_2d<f32>;
//     var diffuseTextures : texture_2d_array<f32>;
//     var shadowMap : texture_depth_2d;
//     var diffuseSampler : sampler;
//     var<storage, read> particles: array<Particle>;
//     var<storage, read_write> storageBuffer : Buffer;
//     var storageTexture : texture_storage_2d<rgba8unorm, write>;
//     var videoTexture : texture_external;
const TEXTURE_REGEX = /^\s*var\s+(\w+)\s*:\s*(texture_\w+)(?:<(\w+)>)?;\s*$/;
// eslint-disable-next-line
const STORAGE_TEXTURE_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*(texture_storage_2d|texture_storage_2d_array)<([\w\d_]+),\s*(\w+)>\s*;\s*$/;
// eslint-disable-next-line
const STORAGE_BUFFER_REGEX = /^\s*var\s*<storage,\s*(read|write)?>\s*([\w\d_]+)\s*:\s*(.*)\s*;\s*$/;
// eslint-disable-next-line
const EXTERNAL_TEXTURE_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*texture_external;\s*$/;
// eslint-disable-next-line
const SAMPLER_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*(sampler|sampler_comparison)\s*;\s*$/;
// ResourceLine class to parse the resource declarations
class ResourceLine {
    constructor(line, shader){
        // save the raw line
        this.originalLine = line;
        this.line = line;
        // defaults
        this.isTexture = false;
        this.isSampler = false;
        this.isStorageTexture = false;
        this.isStorageBuffer = false;
        this.isExternalTexture = false;
        this.type = '';
        this.matchedElements = [];
        // handle texture type
        const textureMatch = this.line.match(TEXTURE_REGEX);
        if (textureMatch) {
            this.name = textureMatch[1];
            this.type = textureMatch[2]; // texture type (e.g., texture_2d or texture_cube_array)
            this.textureFormat = textureMatch[3]; // texture format (e.g., f32)
            this.isTexture = true;
            this.matchedElements.push(...textureMatch);
            // get dimension and sample type
            const info = getTextureInfo(this.type, this.textureFormat);
            Debug.assert(info);
            this.textureDimension = info.viewDimension;
            this.sampleType = info.sampleType;
        }
        // storage texture (e.g., texture_storage_2d<rgba8unorm, write>)
        const storageTextureMatch = this.line.match(STORAGE_TEXTURE_REGEX);
        if (storageTextureMatch) {
            this.isStorageTexture = true;
            this.name = storageTextureMatch[1];
            this.textureType = storageTextureMatch[2]; // texture_storage_2d or texture_storage_2d_array
            this.format = storageTextureMatch[3]; // format (e.g., rgba8unorm)
            this.access = storageTextureMatch[4]; // access mode (e.g., write)
            this.matchedElements.push(...storageTextureMatch);
        }
        // storage buffer (e.g., <storage, read> particles: array<Particle>;)
        const storageBufferMatch = this.line.match(STORAGE_BUFFER_REGEX);
        if (storageBufferMatch) {
            this.isStorageBuffer = true;
            this.accessMode = storageBufferMatch[1] || 'none'; // Default to 'none' if no access mode
            this.name = storageBufferMatch[2];
            this.type = storageBufferMatch[3]; // Everything after ':' (e.g., array<Particle>)
            this.matchedElements.push(...storageBufferMatch);
        }
        // external texture (e.g., texture_external)
        const externalTextureMatch = this.line.match(EXTERNAL_TEXTURE_REGEX);
        if (externalTextureMatch) {
            this.name = externalTextureMatch[1];
            this.isExternalTexture = true;
            this.matchedElements.push(...storageBufferMatch);
        }
        // sampler
        const samplerMatch = this.line.match(SAMPLER_REGEX);
        if (samplerMatch) {
            this.name = samplerMatch[1];
            this.samplerType = samplerMatch[2]; // sampler type (e.g., sampler or sampler_comparison)
            this.isSampler = true;
            this.matchedElements.push(...samplerMatch);
        }
        if (this.matchedElements.length === 0) {
            Debug.error(`Invalid / unsupported resource line format: ${line}`, shader);
            shader.failed = true;
        }
    }
    equals(other) {
        if (this.name !== other.name) return false;
        if (this.type !== other.type) return false;
        if (this.isTexture !== other.isTexture) return false;
        if (this.isSampler !== other.isSampler) return false;
        if (this.isStorageTexture !== other.isStorageTexture) return false;
        if (this.isStorageBuffer !== other.isStorageBuffer) return false;
        if (this.isExternalTexture !== other.isExternalTexture) return false;
        if (this.textureFormat !== other.textureFormat) return false;
        if (this.textureDimension !== other.textureDimension) return false;
        if (this.sampleType !== other.sampleType) return false;
        if (this.textureType !== other.textureType) return false;
        if (this.format !== other.format) return false;
        if (this.access !== other.access) return false;
        if (this.accessMode !== other.accessMode) return false;
        if (this.samplerType !== other.samplerType) return false;
        return true;
    }
}
/**
 * Pure static class implementing processing of WGSL shaders. It allocates fixed locations for
 * attributes, and handles conversion of uniforms to uniform buffers.
 */ class WebgpuShaderProcessorWGSL {
    /**
     * Process the shader.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {object} shaderDefinition - The shader definition.
     * @param {Shader} shader - The shader.
     * @returns {object} - The processed shader data.
     */ static run(device, shaderDefinition, shader) {
        /** @type {Map<string, number>} */ const varyingMap = new Map();
        // extract lines of interests from both shaders
        const vertexExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.vshader);
        const fragmentExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.fshader);
        // VS - convert a list of attributes to a shader block with fixed locations
        const attributesMap = new Map();
        const attributesBlock = WebgpuShaderProcessorWGSL.processAttributes(vertexExtracted.attributes, shaderDefinition.attributes, attributesMap, shaderDefinition.processingOptions, shader);
        // VS - convert a list of varyings to a shader block
        const vertexVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(vertexExtracted.varyings, varyingMap, true);
        // FS - convert a list of varyings to a shader block
        const fragmentVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(fragmentExtracted.varyings, varyingMap, false);
        // uniforms - merge vertex and fragment uniforms, and create shared uniform buffers
        // Note that as both vertex and fragment can declare the same uniform, we need to remove duplicates
        const concatUniforms = vertexExtracted.uniforms.concat(fragmentExtracted.uniforms);
        const uniforms = Array.from(new Set(concatUniforms));
        // parse uniform lines
        const parsedUniforms = uniforms.map((line)=>new UniformLine(line, shader));
        // validation - as uniforms go to a shared uniform buffer, vertex and fragment versions need to match
        Debug.call(()=>{
            const map = new Map();
            parsedUniforms.forEach((uni)=>{
                const existing = map.get(uni.name);
                Debug.assert(!existing, `Vertex and fragment shaders cannot use the same uniform name with different types: '${existing}' and '${uni.line}'`, shader);
                map.set(uni.name, uni.line);
            });
        });
        const uniformsData = WebgpuShaderProcessorWGSL.processUniforms(device, parsedUniforms, shaderDefinition.processingOptions, shader);
        // rename references to uniforms to match the uniform buffer
        vertexExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(vertexExtracted.src, parsedUniforms);
        fragmentExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(fragmentExtracted.src, parsedUniforms);
        // parse resource lines
        const parsedResources = WebgpuShaderProcessorWGSL.mergeResources(vertexExtracted.resources, fragmentExtracted.resources, shader);
        const resourcesData = WebgpuShaderProcessorWGSL.processResources(device, parsedResources, shaderDefinition.processingOptions, shader);
        // generate fragment output struct
        const fOutput = WebgpuShaderProcessorWGSL.generateFragmentOutputStruct(fragmentExtracted.src, device.maxColorAttachments);
        // inject the call to the function which copies the shader input globals
        vertexExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(vertexExtracted.src, shader);
        fragmentExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(fragmentExtracted.src, shader);
        // VS - insert the blocks to the source
        const vBlock = `${attributesBlock}\n${vertexVaryingsBlock}\n${uniformsData.code}\n${resourcesData.code}\n`;
        const vshader = vertexExtracted.src.replace(MARKER, vBlock);
        // FS - insert the blocks to the source
        const fBlock = `${fragmentVaryingsBlock}\n${fOutput}\n${uniformsData.code}\n${resourcesData.code}\n`;
        const fshader = fragmentExtracted.src.replace(MARKER, fBlock);
        return {
            vshader: vshader,
            fshader: fshader,
            attributes: attributesMap,
            meshUniformBufferFormat: uniformsData.meshUniformBufferFormat,
            meshBindGroupFormat: resourcesData.meshBindGroupFormat
        };
    }
    // Extract required information from the shader source code.
    static extract(src) {
        // collected data
        const attributes = [];
        const varyings = [];
        const uniforms = [];
        const resources = [];
        // replacement marker - mark a first replacement place
        let replacement = `${MARKER}\n`;
        let match;
        // Extract uniforms, attributes, and varyings
        while((match = KEYWORD$1.exec(src)) !== null){
            const keyword = match[1];
            KEYWORD_LINE.lastIndex = match.index;
            const lineMatch = KEYWORD_LINE.exec(src);
            if (keyword === 'attribute') {
                attributes.push(lineMatch[2]);
            } else if (keyword === 'varying') {
                varyings.push(lineMatch[2]);
            } else if (keyword === 'uniform') {
                uniforms.push(lineMatch[2]);
            }
            // Remove the matched line from source
            src = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_LINE.lastIndex, replacement);
            KEYWORD$1.lastIndex = match.index + replacement.length;
            replacement = ''; // Only place a single replacement marker
        }
        // Extract resource declarations
        while((match = KEYWORD_RESOURCE.exec(src)) !== null){
            resources.push(match[0]); // Store the full line
            // Remove the matched line from source
            src = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_RESOURCE.lastIndex, replacement);
            KEYWORD_RESOURCE.lastIndex = match.index + replacement.length;
            replacement = '';
        }
        return {
            src,
            attributes,
            varyings,
            uniforms,
            resources
        };
    }
    /**
     * Process the lines with uniforms. The function receives the lines containing all numerical
     * uniforms. The function also receives the format of uniform buffers for view and material
     * level. All uniforms that match any of those are ignored, as those would be supplied by view /
     * material level buffers. All leftover uniforms create uniform buffer and bind group for the
     * mesh itself, containing uniforms that change on the level of the mesh.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {Array<UniformLine>} uniforms - Lines containing uniforms.
     * @param {ShaderProcessorOptions} processingOptions - Uniform formats.
     * @param {Shader} shader - The shader definition.
     * @returns {object} - The uniform data. Returns a shader code block containing uniforms, to be
     * inserted into the shader, as well as generated uniform format structures for the mesh level.
     */ static processUniforms(device, uniforms, processingOptions, shader) {
        // build mesh uniform buffer format
        const meshUniforms = [];
        uniforms.forEach((uniform)=>{
            // uniforms not already in supplied uniform buffers go to the mesh buffer
            if (!processingOptions.hasUniform(uniform.name)) {
                uniform.ubName = 'ub_mesh_ub';
                // Find the uniform type index in uniformTypeToNameWGSL
                const uniformType = uniformTypeToNameMapWGSL.get(uniform.type);
                Debug.assert(uniformType !== undefined, `Uniform type ${uniform.type} is not recognised on line [${uniform.line}]`);
                const uniformFormat = new UniformFormat(uniform.name, uniformType, uniform.arraySize);
                meshUniforms.push(uniformFormat);
            } else {
                // TODO: when we add material ub, this name will need to be updated
                uniform.ubName = 'ub_view';
                // Validate types here if needed
                Debug.assert(true, `Uniform ${uniform.name} already processed, skipping additional validation.`);
            }
        });
        // if we don't have any uniform, add a dummy uniform to avoid empty uniform buffer - WebGPU rendering does not
        // support rendering will NULL bind group as binding a null buffer changes placement of other bindings
        if (meshUniforms.length === 0) {
            meshUniforms.push(new UniformFormat(UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT));
        }
        const meshUniformBufferFormat = new UniformBufferFormat(device, meshUniforms);
        // generate code for uniform buffers, starts on the slot 0
        let code = '';
        processingOptions.uniformFormats.forEach((format, bindGroupIndex)=>{
            if (format) {
                code += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(format, bindGroupIndex, 0);
            }
        });
        // and also for generated mesh uniform format, which is at the slot 0 of the bind group
        if (meshUniformBufferFormat) {
            code += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(meshUniformBufferFormat, BINDGROUP_MESH_UB, 0);
        }
        return {
            code,
            meshUniformBufferFormat
        };
    }
    /**
     * Source code references uniforms as `uniform.name`, but swap those to reference the actual uniform buffer
     * the uniform was assigned to, for example `ub_view.name`.
     *
     * @param {string} source - The source code.
     * @param {Array<UniformLine>} uniforms - Lines containing uniforms.
     * @returns {string} - The source code with updated uniform references.
     */ static renameUniformAccess(source, uniforms) {
        uniforms.forEach((uniform)=>{
            const srcName = `uniform.${uniform.name}`;
            const dstName = `${uniform.ubName}.${uniform.name}`;
            // Use a regular expression to match `uniform.name` as a whole word.
            const regex = new RegExp(`\\b${srcName}\\b`, 'g');
            source = source.replace(regex, dstName);
        });
        return source;
    }
    static mergeResources(vertex, fragment, shader) {
        const resources = vertex.map((line)=>new ResourceLine(line, shader));
        const fragmentResources = fragment.map((line)=>new ResourceLine(line, shader));
        // merge fragment list to resources, removing exact duplicates
        fragmentResources.forEach((fragmentResource)=>{
            const existing = resources.find((resource)=>resource.name === fragmentResource.name);
            if (existing) {
                // if the resource is already in the list, check if it matches
                if (!existing.equals(fragmentResource)) {
                    Debug.error(`Resource '${fragmentResource.name}' is declared with different types in vertex and fragment shaders.`, {
                        vertexLine: existing.line,
                        fragmentLine: fragmentResource.line,
                        shader,
                        vertexResource: existing,
                        fragmentResource
                    });
                    shader.failed = true;
                }
            } else {
                resources.push(fragmentResource);
            }
        });
        return resources;
    }
    static processResources(device, resources, processingOptions, shader) {
        // build mesh bind group format - this contains the textures, but not the uniform buffer as that is a separate binding
        const textureFormats = [];
        for(let i = 0; i < resources.length; i++){
            const resource = resources[i];
            if (resource.isTexture) {
                // followed by optional sampler uniform
                const sampler = resources[i + 1];
                const hasSampler = sampler?.isSampler;
                // TODO: handle external, and storage types
                const sampleType = resource.sampleType;
                const dimension = resource.textureDimension;
                // TODO: we could optimize visibility to only stages that use any of the data
                textureFormats.push(new BindTextureFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, dimension, sampleType, hasSampler, hasSampler ? sampler.name : null));
                // following sampler was already handled
                if (hasSampler) i++;
            }
            if (resource.isStorageBuffer) {
                const readOnly = resource.accessMode !== 'read_write';
                const bufferFormat = new BindStorageBufferFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, readOnly);
                bufferFormat.format = resource.type;
                textureFormats.push(bufferFormat);
            }
            Debug.assert(!resource.isSampler, `Sampler uniform needs to follow a texture uniform, but does not on line [${resource.originalLine}]`);
            Debug.assert(!resource.isStorageTexture, 'TODO: add support for storage textures here');
            Debug.assert(!resource.externalTexture, 'TODO: add support for external textures here');
        }
        const meshBindGroupFormat = new BindGroupFormat(device, textureFormats);
        // generate code for textures
        let code = '';
        processingOptions.bindGroupFormats.forEach((format, bindGroupIndex)=>{
            if (format) {
                code += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(format, bindGroupIndex, 1);
            }
        });
        // and also for generated mesh format
        code += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(meshBindGroupFormat, BINDGROUP_MESH, 0);
        return {
            code,
            meshBindGroupFormat
        };
    }
    /**
     * Generates a shader code for a uniform buffer, something like:
     * ```
     *     struct ub_view { matrix_viewProjection : mat4x4f }
     *     @group(0) @binding(0) var<uniform> ubView : ub_view;
     * ```
     *
     * @param {UniformBufferFormat} ubFormat - Format of the uniform buffer.
     * @param {number} bindGroup - The bind group index.
     * @param {number} bindIndex - The bind index.
     * @returns {string} - The shader code for the uniform buffer.
     * @private
     */ static getUniformShaderDeclaration(ubFormat, bindGroup, bindIndex) {
        const name = bindGroupNames[bindGroup];
        const structName = `struct_ub_${name}`;
        let code = `struct ${structName} {\n`;
        ubFormat.uniforms.forEach((uniform)=>{
            let typeString = uniformTypeToNameWGSL[uniform.type][0];
            Debug.assert(typeString !== undefined, `Uniform type ${uniform.type} is not handled.`);
            // array uniforms
            if (uniform.count > 0) {
                // if the type is one of the ones that are not by default 16byte aligned, which is
                // a requirement for uniform buffers, we need to wrap them in a struct
                // for example: array<f32, 5> becomes  array<WrappedF32, 5>
                if (wrappedArrayTypes.hasOwnProperty(typeString)) {
                    typeString = wrappedArrayTypes[typeString];
                }
                code += `    ${uniform.shortName}: array<${typeString}, ${uniform.count}>,\n`;
            } else {
                code += `    ${uniform.shortName}: ${typeString},\n`;
            }
        });
        code += '};\n';
        code += `@group(${bindGroup}) @binding(${bindIndex}) var<uniform> ub_${name} : ${structName};\n\n`;
        return code;
    }
    /**
     * Generates a shader code for a bind group, something like:
     * ```
     *    @group(0) @binding(0) var diffuseTexture: texture_2d<f32>;
     *    @group(0) @binding(1) var diffuseTexture_sampler: sampler;  // optional
     * ```
     * @param {BindGroupFormat} format - The format of the bind group.
     * @param {number} bindGroup - The bind group index.
     * @param {number} startBindIndex - The starting bind index.
     * @returns {string} - The shader code for the bind group.
     */ static getTextureShaderDeclaration(format, bindGroup, startBindIndex) {
        let code = '';
        let bindIndex = startBindIndex;
        format.textureFormats.forEach((format)=>{
            const textureTypeName = getTextureDeclarationType(format.textureDimension, format.sampleType);
            code += `@group(${bindGroup}) @binding(${bindIndex}) var ${format.name}: ${textureTypeName};\n`;
            bindIndex++;
            if (format.hasSampler) {
                const samplerName = format.sampleType === SAMPLETYPE_DEPTH ? 'sampler_comparison' : 'sampler';
                code += `@group(${bindGroup}) @binding(${bindIndex}) var ${format.samplerName}: ${samplerName};\n`;
                bindIndex++;
            }
        });
        format.storageBufferFormats.forEach((format)=>{
            const access = format.readOnly ? 'read' : 'read_write';
            code += `@group(${bindGroup}) @binding(${bindIndex}) var<storage, ${access}> ${format.name} : ${format.format};\n`;
            bindIndex++;
        });
        Debug.assert(format.storageTextureFormats.length === 0, 'Implement support for storage textures here');
        // TODO: also add external texture support here
        return code;
    }
    static processVaryings(varyingLines, varyingMap, isVertex) {
        let block = '';
        let blockPrivates = '';
        let blockCopy = '';
        varyingLines.forEach((line, index)=>{
            const match = line.match(VARYING);
            Debug.assert(match, `Varying line is not valid: ${line}`);
            if (match) {
                const name = match[1];
                if (isVertex) {
                    // store it in the map
                    varyingMap.set(name, index);
                } else {
                    Debug.assert(varyingMap.has(name), `Fragment shader requires varying [${name}] but vertex shader does not generate it.`);
                    index = varyingMap.get(name);
                }
                // generates: `@location(0) @interpolate(perspective, centroid) smoothColor : vec3f`
                block += `    @location(${index}) ${line},\n`;
                // fragment shader inputs (varyings)
                if (!isVertex) {
                    // private global variable for fragment varying
                    blockPrivates += `    var<private> ${line};\n`;
                    // copy input variable to the private variable
                    blockCopy += `    ${name} = input.${name};\n`;
                }
            }
        });
        // add built-in varyings
        if (isVertex) {
            block += '    @builtin(position) position : vec4f,\n'; // output position
        } else {
            block += '    @builtin(position) position : vec4f,\n'; // interpolated fragment position
            block += '    @builtin(front_facing) frontFacing : bool,\n'; // front-facing
            block += '    @builtin(sample_index) sampleIndex : u32\n'; // sample index for MSAA
        }
        // global variables for build-in input into fragment shader
        const fragmentGlobals = isVertex ? '' : `
            var<private> pcPosition: vec4f;
            var<private> pcFrontFacing: bool;
            var<private> pcSampleIndex: u32;
            ${blockPrivates}
            
            // function to copy inputs (varyings) to private global variables
            fn _pcCopyInputs(input: FragmentInput) {
                ${blockCopy}
                pcPosition = input.position;
                pcFrontFacing = input.frontFacing;
                pcSampleIndex = input.sampleIndex;
            }
        `;
        const structName = isVertex ? 'VertexOutput' : 'FragmentInput';
        return `
            struct ${structName} {
                ${block}
            };
            ${fragmentGlobals}
        `;
    }
    static generateFragmentOutputStruct(src, numRenderTargets) {
        let structCode = 'struct FragmentOutput {\n';
        for(let i = 0; i < numRenderTargets; i++){
            structCode += `    @location(${i}) color${i > 0 ? i : ''} : pcOutType${i},\n`;
        }
        // find if the src contains `.fragDepth =`, ignoring whitespace before = sign
        const needsFragDepth = src.search(/\.fragDepth\s*=/) !== -1;
        if (needsFragDepth) {
            structCode += '    @builtin(frag_depth) fragDepth : f32\n';
        }
        return `${structCode}};\n`;
    }
    // convert a float attribute type to matching signed or unsigned int type
    // for example: vec4f -> vec4u, f32 -> u32
    static floatAttributeToInt(type, signed) {
        // convert any long-form type to short-form
        const longToShortMap = {
            'f32': 'f32',
            'vec2<f32>': 'vec2f',
            'vec3<f32>': 'vec3f',
            'vec4<f32>': 'vec4f'
        };
        const shortType = longToShortMap[type] || type;
        // map from float short type to int short type
        const floatToIntShort = {
            'f32': signed ? 'i32' : 'u32',
            'vec2f': signed ? 'vec2i' : 'vec2u',
            'vec3f': signed ? 'vec3i' : 'vec3u',
            'vec4f': signed ? 'vec4i' : 'vec4u'
        };
        return floatToIntShort[shortType] || null;
    }
    static processAttributes(attributeLines, shaderDefinitionAttributes = {}, attributesMap, processingOptions, shader) {
        let blockAttributes = '';
        let blockPrivates = '';
        let blockCopy = '';
        const usedLocations = {};
        attributeLines.forEach((line)=>{
            const words = splitToWords(line);
            const name = words[0];
            let type = words[1];
            const originalType = type;
            if (shaderDefinitionAttributes.hasOwnProperty(name)) {
                const semantic = shaderDefinitionAttributes[name];
                const location = semanticToLocation[semantic];
                Debug.assert(location !== undefined, `Semantic ${semantic} used by the attribute ${name} is not known - make sure it's one of the supported semantics.`);
                Debug.assert(!usedLocations.hasOwnProperty(location), `WARNING: Two vertex attributes are mapped to the same location in a shader: ${usedLocations[location]} and ${semantic}`);
                usedLocations[location] = semantic;
                // build a map of used attributes
                attributesMap.set(location, name);
                // if vertex format for this attribute is not of a float type, but shader specifies float type, convert the shader type
                // to match the vertex format type, for example: vec4f -> vec4u
                // Note that we skip normalized elements, as shader receives them as floats already.
                const element = processingOptions.getVertexElement(semantic);
                if (element) {
                    const dataType = element.dataType;
                    if (dataType !== TYPE_FLOAT32 && dataType !== TYPE_FLOAT16 && !element.normalize && !element.asInt) {
                        // new attribute type, based on the vertex format element type
                        const isSignedType = dataType === TYPE_INT8 || dataType === TYPE_INT16 || dataType === TYPE_INT32;
                        type = WebgpuShaderProcessorWGSL.floatAttributeToInt(type, isSignedType);
                        Debug.assert(type !== null, `Attribute ${name} has a type that cannot be converted to int: ${dataType}`);
                    }
                }
                // generates: @location(0) position : vec4f
                blockAttributes += `    @location(${location}) ${name}: ${type},\n`;
                // private global variable - this uses the original type
                blockPrivates += `    var<private> ${line};\n`;
                // copy input variable to the private variable - convert type if needed
                blockCopy += `    ${name} = ${originalType}(input.${name});\n`;
            } else {
                Debug.error(`Attribute ${name} is specified in the shader source, but is not defined in the shader definition, and so will be removed from the shader, as it cannot be used without a known semantic.`, {
                    shaderDefinitionAttributes,
                    shader
                });
            }
        });
        return `
            struct VertexInput {
                ${blockAttributes}
                @builtin(vertex_index) vertexIndex : u32,       // built-in vertex index
                @builtin(instance_index) instanceIndex : u32    // built-in instance index
            };

            ${blockPrivates}
            var<private> pcVertexIndex: u32;
            var<private> pcInstanceIndex: u32;

            fn _pcCopyInputs(input: VertexInput) {
                ${blockCopy}
                pcVertexIndex = input.vertexIndex;
                pcInstanceIndex = input.instanceIndex;
            }
        `;
    }
    /**
     * Injects a call to _pcCopyInputs with the function's input parameter right after the opening
     * brace of a WGSL function marked with `@vertex` or `@fragment`.
     *
     * @param {string} src - The source string containing the WGSL code.
     * @param {Shader} shader - The shader.
     * @returns {string} - The modified source string.
     */ static copyInputs(src, shader) {
        // find @vertex or @fragment followed by the function signature and capture the input parameter name
        const match = src.match(ENTRY_FUNCTION);
        // check if match exists AND the parameter name (Group 2) was captured
        if (!match || !match[2]) {
            Debug.warn('No entry function found or input parameter name not captured.', {
                shader,
                src
            });
            return src;
        }
        const inputName = match[2];
        const braceIndex = match.index + match[0].length - 1; // Calculate the index of the '{'
        // inject the line right after the opening brace
        const beginning = src.slice(0, braceIndex + 1);
        const end = src.slice(braceIndex + 1);
        const lineToInject = `\n    _pcCopyInputs(${inputName});`;
        return beginning + lineToInject + end;
    }
    static cutOut(src, start, end, replacement) {
        return src.substring(0, start) + replacement + src.substring(end);
    }
}

/**
 * @import { GraphicsDevice } from '../graphics-device.js'
 * @import { Shader } from '../shader.js'
 */ /**
 * A WebGPU implementation of the Shader.
 *
 * @ignore
 */ class WebgpuShader {
    /**
     * @param {Shader} shader - The shader.
     */ constructor(shader){
        /**
     * Transpiled vertex shader code.
     *
     * @type {string|null}
     */ this._vertexCode = null;
        /**
     * Transpiled fragment shader code.
     *
     * @type {string|null}
     */ this._fragmentCode = null;
        /**
     * Compute shader code.
     *
     * @type {string|null}
     */ this._computeCode = null;
        /**
     * Name of the vertex entry point function.
     */ this.vertexEntryPoint = 'main';
        /**
     * Name of the fragment entry point function.
     */ this.fragmentEntryPoint = 'main';
        /**
     * Name of the compute entry point function.
     */ this.computeEntryPoint = 'main';
        /** @type {Shader} */ this.shader = shader;
        const definition = shader.definition;
        Debug.assert(definition);
        if (definition.shaderLanguage === SHADERLANGUAGE_WGSL) {
            if (definition.cshader) {
                this._computeCode = definition.cshader ?? null;
                this.computeUniformBufferFormats = definition.computeUniformBufferFormats;
                this.computeBindGroupFormat = definition.computeBindGroupFormat;
            } else {
                this.vertexEntryPoint = 'vertexMain';
                this.fragmentEntryPoint = 'fragmentMain';
                if (definition.processingOptions) {
                    this.processWGSL();
                } else {
                    this._vertexCode = definition.vshader ?? null;
                    this._fragmentCode = definition.fshader ?? null;
                    shader.meshUniformBufferFormat = definition.meshUniformBufferFormat;
                    shader.meshBindGroupFormat = definition.meshBindGroupFormat;
                }
            }
            shader.ready = true;
        } else {
            if (definition.processingOptions) {
                this.processGLSL();
            }
        }
    }
    /**
     * Free the WebGPU resources associated with a shader.
     *
     * @param {Shader} shader - The shader to free.
     */ destroy(shader) {
        this._vertexCode = null;
        this._fragmentCode = null;
    }
    createShaderModule(code, shaderType) {
        const device = this.shader.device;
        const wgpu = device.wgpu;
        WebgpuDebug.validate(device);
        const shaderModule = wgpu.createShaderModule({
            code: code
        });
        DebugHelper.setLabel(shaderModule, `${shaderType}:${this.shader.label}`);
        WebgpuDebug.endShader(device, shaderModule, code, 6, {
            shaderType,
            source: code,
            shader: this.shader
        });
        return shaderModule;
    }
    getVertexShaderModule() {
        return this.createShaderModule(this._vertexCode, 'Vertex');
    }
    getFragmentShaderModule() {
        return this.createShaderModule(this._fragmentCode, 'Fragment');
    }
    getComputeShaderModule() {
        return this.createShaderModule(this._computeCode, 'Compute');
    }
    processGLSL() {
        const shader = this.shader;
        // process the shader source to allow for uniforms
        const processed = ShaderProcessorGLSL.run(shader.device, shader.definition, shader);
        // keep reference to processed shaders in debug mode
        Debug.call(()=>{
            this.processed = processed;
        });
        this._vertexCode = this.transpile(processed.vshader, 'vertex', shader.definition.vshader);
        this._fragmentCode = this.transpile(processed.fshader, 'fragment', shader.definition.fshader);
        if (!(this._vertexCode && this._fragmentCode)) {
            shader.failed = true;
        } else {
            shader.ready = true;
        }
        shader.meshUniformBufferFormat = processed.meshUniformBufferFormat;
        shader.meshBindGroupFormat = processed.meshBindGroupFormat;
        shader.attributes = processed.attributes;
    }
    processWGSL() {
        const shader = this.shader;
        // process the shader source to allow for uniforms
        const processed = WebgpuShaderProcessorWGSL.run(shader.device, shader.definition, shader);
        // keep reference to processed shaders in debug mode
        Debug.call(()=>{
            this.processed = processed;
        });
        this._vertexCode = processed.vshader;
        this._fragmentCode = processed.fshader;
        shader.meshUniformBufferFormat = processed.meshUniformBufferFormat;
        shader.meshBindGroupFormat = processed.meshBindGroupFormat;
        shader.attributes = processed.attributes;
    }
    transpile(src, shaderType, originalSrc) {
        // make sure shader transpilers are available
        const device = this.shader.device;
        if (!device.glslang || !device.twgsl) {
            console.error(`Cannot transpile shader [${this.shader.label}] - shader transpilers (glslang/twgsl) are not available. Make sure to provide glslangUrl and twgslUrl when creating the device.`, {
                shader: this.shader
            });
            return null;
        }
        // transpile
        try {
            const spirv = device.glslang.compileGLSL(src, shaderType);
            const wgsl = device.twgsl.convertSpirV2WGSL(spirv);
            return wgsl;
        } catch (err) {
            console.error(`Failed to transpile webgl ${shaderType} shader [${this.shader.label}] to WebGPU while rendering ${DebugGraphics.toString()}, error:\n [${err.stack}]`, {
                processed: src,
                original: originalSrc,
                shader: this.shader,
                error: err,
                stack: err.stack
            });
        }
    }
    get vertexCode() {
        Debug.assert(this._vertexCode);
        return this._vertexCode;
    }
    get fragmentCode() {
        Debug.assert(this._fragmentCode);
        return this._fragmentCode;
    }
    /**
     * Dispose the shader when the context has been lost.
     */ loseContext() {}
    /**
     * Restore shader after the context has been obtained.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {Shader} shader - The shader to restore.
     */ restoreContext(device, shader) {}
}

/**
 * @import { Texture } from '../texture.js'
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 */ // map of ADDRESS_*** to GPUAddressMode
const gpuAddressModes = [];
gpuAddressModes[ADDRESS_REPEAT] = 'repeat';
gpuAddressModes[ADDRESS_CLAMP_TO_EDGE] = 'clamp-to-edge';
gpuAddressModes[ADDRESS_MIRRORED_REPEAT] = 'mirror-repeat';
// map of FILTER_*** to GPUFilterMode for level and mip sampling
const gpuFilterModes = [];
gpuFilterModes[FILTER_NEAREST] = {
    level: 'nearest',
    mip: 'nearest'
};
gpuFilterModes[FILTER_LINEAR] = {
    level: 'linear',
    mip: 'nearest'
};
gpuFilterModes[FILTER_NEAREST_MIPMAP_NEAREST] = {
    level: 'nearest',
    mip: 'nearest'
};
gpuFilterModes[FILTER_NEAREST_MIPMAP_LINEAR] = {
    level: 'nearest',
    mip: 'linear'
};
gpuFilterModes[FILTER_LINEAR_MIPMAP_NEAREST] = {
    level: 'linear',
    mip: 'nearest'
};
gpuFilterModes[FILTER_LINEAR_MIPMAP_LINEAR] = {
    level: 'linear',
    mip: 'linear'
};
const dummyUse = (thingOne)=>{
// so lint thinks we're doing something with thingOne
};
/**
 * A WebGPU implementation of the Texture.
 *
 * @ignore
 */ class WebgpuTexture {
    constructor(texture){
        /**
     * An array of samplers, addressed by SAMPLETYPE_*** constant, allowing texture to be sampled
     * using different samplers. Most textures are sampled as interpolated floats, but some can
     * additionally be sampled using non-interpolated floats (raw data) or compare sampling
     * (shadow maps).
     *
     * @type {GPUSampler[]}
     * @private
     */ this.samplers = [];
        /** @type {Texture} */ this.texture = texture;
        this.format = gpuTextureFormats[texture.format];
        Debug.assert(this.format !== '', `WebGPU does not support texture format ${texture.format} [${pixelFormatInfo.get(texture.format)?.name}] for texture ${texture.name}`, texture);
        this.create(texture.device);
    }
    create(device) {
        const texture = this.texture;
        const wgpu = device.wgpu;
        const numLevels = texture.numLevels;
        Debug.assert(texture.width > 0 && texture.height > 0, `Invalid texture dimensions ${texture.width}x${texture.height} for texture ${texture.name}`, texture);
        this.desc = {
            size: {
                width: texture.width,
                height: texture.height,
                depthOrArrayLayers: texture.cubemap ? 6 : texture.array ? texture.arrayLength : 1
            },
            format: this.format,
            mipLevelCount: numLevels,
            sampleCount: 1,
            dimension: texture.volume ? '3d' : '2d',
            // TODO: use only required usage flags
            // COPY_SRC - probably only needed on render target textures, to support copyRenderTarget (grab pass needs it)
            // RENDER_ATTACHMENT - needed for mipmap generation
            usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | (isCompressedPixelFormat(texture.format) ? 0 : GPUTextureUsage.RENDER_ATTACHMENT) | (texture.storage ? GPUTextureUsage.STORAGE_BINDING : 0)
        };
        WebgpuDebug.validate(device);
        this.gpuTexture = wgpu.createTexture(this.desc);
        DebugHelper.setLabel(this.gpuTexture, `${texture.name}${texture.cubemap ? '[cubemap]' : ''}${texture.volume ? '[3d]' : ''}`);
        WebgpuDebug.end(device, 'Texture creation', {
            desc: this.desc,
            texture
        });
        // default texture view descriptor
        let viewDescr;
        // some format require custom default texture view
        if (this.texture.format === PIXELFORMAT_DEPTHSTENCIL) {
            // we expose the depth part of the format
            viewDescr = {
                format: 'depth24plus',
                aspect: 'depth-only'
            };
        }
        this.view = this.createView(viewDescr);
    }
    destroy(device) {}
    propertyChanged(flag) {
        // samplers need to be recreated
        this.samplers.length = 0;
    }
    /**
     * @param {any} device - The Graphics Device.
     * @returns {any} - Returns the view.
     */ getView(device) {
        this.uploadImmediate(device, this.texture);
        Debug.assert(this.view);
        return this.view;
    }
    createView(viewDescr) {
        const options = viewDescr ?? {};
        const textureDescr = this.desc;
        const texture = this.texture;
        // '1d', '2d', '2d-array', 'cube', 'cube-array', '3d'
        const defaultViewDimension = ()=>{
            if (texture.cubemap) return 'cube';
            if (texture.volume) return '3d';
            if (texture.array) return '2d-array';
            return '2d';
        };
        /** @type {GPUTextureViewDescriptor} */ const desc = {
            format: options.format ?? textureDescr.format,
            dimension: options.dimension ?? defaultViewDimension(),
            aspect: options.aspect ?? 'all',
            baseMipLevel: options.baseMipLevel ?? 0,
            mipLevelCount: options.mipLevelCount ?? textureDescr.mipLevelCount,
            baseArrayLayer: options.baseArrayLayer ?? 0,
            arrayLayerCount: options.arrayLayerCount ?? textureDescr.depthOrArrayLayers
        };
        const view = this.gpuTexture.createView(desc);
        DebugHelper.setLabel(view, `${viewDescr ? `CustomView${JSON.stringify(viewDescr)}` : 'DefaultView'}:${this.texture.name}`);
        return view;
    }
    // TODO: share a global map of samplers. Possibly even use shared samplers for bind group,
    // or maybe even have some attached in view bind group and use globally
    /**
     * @param {any} device - The Graphics Device.
     * @param {number} [sampleType] - A sample type for the sampler, SAMPLETYPE_*** constant. If not
     * specified, the sampler type is based on the texture format / texture sampling type.
     * @returns {any} - Returns the sampler.
     */ getSampler(device, sampleType) {
        let sampler = this.samplers[sampleType];
        if (!sampler) {
            const texture = this.texture;
            let label;
            /** @type GPUSamplerDescriptor */ const desc = {
                addressModeU: gpuAddressModes[texture.addressU],
                addressModeV: gpuAddressModes[texture.addressV],
                addressModeW: gpuAddressModes[texture.addressW]
            };
            // default for compare sampling of texture
            if (!sampleType && texture.compareOnRead) {
                sampleType = SAMPLETYPE_DEPTH;
            }
            if (sampleType === SAMPLETYPE_DEPTH || sampleType === SAMPLETYPE_INT || sampleType === SAMPLETYPE_UINT) {
                // depth compare sampling
                desc.compare = 'less';
                desc.magFilter = 'linear';
                desc.minFilter = 'linear';
                label = 'Compare';
            } else if (sampleType === SAMPLETYPE_UNFILTERABLE_FLOAT) {
                desc.magFilter = 'nearest';
                desc.minFilter = 'nearest';
                desc.mipmapFilter = 'nearest';
                label = 'Unfilterable';
            } else {
                // if the device cannot filter float textures, force nearest filtering
                const forceNearest = !device.textureFloatFilterable && (texture.format === PIXELFORMAT_RGBA32F || texture.format === PIXELFORMAT_RGBA16F);
                if (forceNearest || this.texture.format === PIXELFORMAT_DEPTHSTENCIL || isIntegerPixelFormat(this.texture.format)) {
                    desc.magFilter = 'nearest';
                    desc.minFilter = 'nearest';
                    desc.mipmapFilter = 'nearest';
                    label = 'Nearest';
                } else {
                    desc.magFilter = gpuFilterModes[texture.magFilter].level;
                    desc.minFilter = gpuFilterModes[texture.minFilter].level;
                    desc.mipmapFilter = gpuFilterModes[texture.minFilter].mip;
                    Debug.call(()=>{
                        label = `Texture:${texture.magFilter}-${texture.minFilter}-${desc.mipmapFilter}`;
                    });
                }
            }
            // ensure anisotropic filtering is only set when filtering is correctly
            // set up
            const allLinear = desc.minFilter === 'linear' && desc.magFilter === 'linear' && desc.mipmapFilter === 'linear';
            desc.maxAnisotropy = allLinear ? math.clamp(Math.round(texture._anisotropy), 1, device.maxTextureAnisotropy) : 1;
            sampler = device.wgpu.createSampler(desc);
            DebugHelper.setLabel(sampler, label);
            this.samplers[sampleType] = sampler;
        }
        return sampler;
    }
    loseContext() {}
    /**
     * @param {WebgpuGraphicsDevice} device - The graphics device.
     * @param {Texture} texture - The texture.
     */ uploadImmediate(device, texture) {
        if (texture._needsUpload || texture._needsMipmapsUpload) {
            this.uploadData(device);
            texture._needsUpload = false;
            texture._needsMipmapsUpload = false;
        }
    }
    /**
     * @param {WebgpuGraphicsDevice} device - The graphics
     * device.
     */ uploadData(device) {
        const texture = this.texture;
        if (texture._levels) {
            // upload texture data if any
            let anyUploads = false;
            let anyLevelMissing = false;
            const requiredMipLevels = texture.numLevels;
            for(let mipLevel = 0; mipLevel < requiredMipLevels; mipLevel++){
                const mipObject = texture._levels[mipLevel];
                if (mipObject) {
                    if (texture.cubemap) {
                        for(let face = 0; face < 6; face++){
                            const faceSource = mipObject[face];
                            if (faceSource) {
                                if (this.isExternalImage(faceSource)) {
                                    this.uploadExternalImage(device, faceSource, mipLevel, face);
                                    anyUploads = true;
                                } else if (ArrayBuffer.isView(faceSource)) {
                                    this.uploadTypedArrayData(device, faceSource, mipLevel, face);
                                    anyUploads = true;
                                } else {
                                    Debug.error('Unsupported texture source data for cubemap face', faceSource);
                                }
                            } else {
                                anyLevelMissing = true;
                            }
                        }
                    } else if (texture._volume) {
                        Debug.warn('Volume texture data upload is not supported yet', this.texture);
                    } else if (texture.array) {
                        if (texture.arrayLength === mipObject.length) {
                            for(let index = 0; index < texture._arrayLength; index++){
                                const arraySource = mipObject[index];
                                if (this.isExternalImage(arraySource)) {
                                    this.uploadExternalImage(device, arraySource, mipLevel, index);
                                    anyUploads = true;
                                } else if (ArrayBuffer.isView(arraySource)) {
                                    this.uploadTypedArrayData(device, arraySource, mipLevel, index);
                                    anyUploads = true;
                                } else {
                                    Debug.error('Unsupported texture source data for texture array entry', arraySource);
                                }
                            }
                        } else {
                            anyLevelMissing = true;
                        }
                    } else {
                        if (this.isExternalImage(mipObject)) {
                            this.uploadExternalImage(device, mipObject, mipLevel, 0);
                            anyUploads = true;
                        } else if (ArrayBuffer.isView(mipObject)) {
                            this.uploadTypedArrayData(device, mipObject, mipLevel, 0);
                            anyUploads = true;
                        } else {
                            Debug.error('Unsupported texture source data', mipObject);
                        }
                    }
                } else {
                    anyLevelMissing = true;
                }
            }
            if (anyUploads && anyLevelMissing && texture.mipmaps && !isCompressedPixelFormat(texture.format) && !isIntegerPixelFormat(texture.format)) {
                device.mipmapRenderer.generate(this);
            }
            // update vram stats
            if (texture._gpuSize) {
                texture.adjustVramSizeTracking(device._vram, -texture._gpuSize);
            }
            texture._gpuSize = texture.gpuSize;
            texture.adjustVramSizeTracking(device._vram, texture._gpuSize);
        }
    }
    // image types supported by copyExternalImageToTexture
    isExternalImage(image) {
        return typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap || typeof HTMLVideoElement !== 'undefined' && image instanceof HTMLVideoElement || typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas;
    }
    uploadExternalImage(device, image, mipLevel, index) {
        Debug.assert(mipLevel < this.desc.mipLevelCount, `Accessing mip level ${mipLevel} of texture with ${this.desc.mipLevelCount} mip levels`, this);
        const src = {
            source: image,
            origin: [
                0,
                0
            ],
            flipY: false
        };
        const dst = {
            texture: this.gpuTexture,
            mipLevel: mipLevel,
            origin: [
                0,
                0,
                index
            ],
            aspect: 'all' // can be: "all", "stencil-only", "depth-only"
        };
        const copySize = {
            width: this.desc.size.width,
            height: this.desc.size.height,
            depthOrArrayLayers: 1 // single layer
        };
        // submit existing scheduled commands to the queue before copying to preserve the order
        device.submit();
        // create 2d context so webgpu can upload the texture
        dummyUse(image instanceof HTMLCanvasElement && image.getContext('2d'));
        Debug.trace(TRACEID_RENDER_QUEUE, `IMAGE-TO-TEX: mip:${mipLevel} index:${index} ${this.texture.name}`);
        device.wgpu.queue.copyExternalImageToTexture(src, dst, copySize);
    }
    uploadTypedArrayData(device, data, mipLevel, index) {
        const texture = this.texture;
        const wgpu = device.wgpu;
        /** @type {GPUImageCopyTexture} */ const dest = {
            texture: this.gpuTexture,
            origin: [
                0,
                0,
                index
            ],
            mipLevel: mipLevel
        };
        // texture dimensions at the specified mip level
        const width = TextureUtils.calcLevelDimension(texture.width, mipLevel);
        const height = TextureUtils.calcLevelDimension(texture.height, mipLevel);
        // data sizes
        const byteSize = TextureUtils.calcLevelGpuSize(width, height, 1, texture.format);
        Debug.assert(byteSize === data.byteLength, `Error uploading data to texture, the data byte size of ${data.byteLength} does not match required ${byteSize}`, texture);
        const formatInfo = pixelFormatInfo.get(texture.format);
        Debug.assert(formatInfo);
        /** @type {GPUImageDataLayout} */ let dataLayout;
        let size;
        if (formatInfo.size) {
            // uncompressed format
            dataLayout = {
                offset: 0,
                bytesPerRow: formatInfo.size * width,
                rowsPerImage: height
            };
            size = {
                width: width,
                height: height
            };
        } else if (formatInfo.blockSize) {
            // compressed format
            const blockDim = (size)=>{
                return Math.floor((size + 3) / 4);
            };
            dataLayout = {
                offset: 0,
                bytesPerRow: formatInfo.blockSize * blockDim(width),
                rowsPerImage: blockDim(height)
            };
            size = {
                width: Math.max(4, width),
                height: Math.max(4, height)
            };
        } else {
            Debug.assert(false, `WebGPU does not yet support texture format ${formatInfo.name} for texture ${texture.name}`, texture);
        }
        // submit existing scheduled commands to the queue before copying to preserve the order
        device.submit();
        Debug.trace(TRACEID_RENDER_QUEUE, `WRITE-TEX: mip:${mipLevel} index:${index} ${this.texture.name}`);
        wgpu.queue.writeTexture(dest, data, dataLayout, size);
    }
    read(x, y, width, height, options) {
        const mipLevel = options.mipLevel ?? 0;
        const face = options.face ?? 0;
        const data = options.data ?? null;
        const immediate = options.immediate ?? false;
        const texture = this.texture;
        const formatInfo = pixelFormatInfo.get(texture.format);
        Debug.assert(formatInfo);
        Debug.assert(formatInfo.size);
        const bytesPerRow = width * formatInfo.size;
        // bytesPerRow must be a multiple of 256
        const paddedBytesPerRow = math.roundUp(bytesPerRow, 256);
        const size = paddedBytesPerRow * height;
        // create a temporary staging buffer
        /** @type {WebgpuGraphicsDevice} */ const device = texture.device;
        const stagingBuffer = device.createBufferImpl(BUFFERUSAGE_READ | BUFFERUSAGE_COPY_DST);
        stagingBuffer.allocate(device, size);
        const src = {
            texture: this.gpuTexture,
            mipLevel: mipLevel,
            origin: [
                x,
                y,
                face
            ]
        };
        const dst = {
            buffer: stagingBuffer.buffer,
            offset: 0,
            bytesPerRow: paddedBytesPerRow
        };
        const copySize = {
            width,
            height,
            depthOrArrayLayers: 1 // single layer
        };
        // copy the GPU texture to the staging buffer
        const commandEncoder = device.getCommandEncoder();
        commandEncoder.copyTextureToBuffer(src, dst, copySize);
        // async read data from the staging buffer to a temporary array
        return device.readBuffer(stagingBuffer, size, null, immediate).then((temp)=>{
            // remove the 256 alignment padding from the end of each row
            const target = data?.constructor === Uint8Array ? data : new Uint8Array(data?.buffer ?? height * bytesPerRow);
            for(let i = 0; i < height; i++){
                const srcOffset = i * paddedBytesPerRow;
                const dstOffset = i * bytesPerRow;
                const sub = temp.subarray(srcOffset, srcOffset + bytesPerRow);
                target.set(sub, dstOffset);
            }
            return data ?? target;
        });
    }
}

/**
 * A WebGPU implementation of the UniformBuffer.
 *
 * @ignore
 */ class WebgpuUniformBuffer extends WebgpuBuffer {
    constructor(uniformBuffer){
        super(BUFFERUSAGE_UNIFORM);
    }
    unlock(uniformBuffer) {
        const device = uniformBuffer.device;
        super.unlock(device, uniformBuffer.storageInt32.buffer);
    }
}

/**
 * A WebGPU implementation of the VertexBuffer.
 *
 * @ignore
 */ class WebgpuVertexBuffer extends WebgpuBuffer {
    constructor(vertexBuffer, format, options){
        super(BUFFERUSAGE_VERTEX | (options?.storage ? BUFFERUSAGE_STORAGE : 0));
    }
    unlock(vertexBuffer) {
        const device = vertexBuffer.device;
        super.unlock(device, vertexBuffer.storage);
    }
}

// id for debug tracing
const TRACEID = 'Preprocessor';
// accepted keywords
const KEYWORD = /[ \t]*#(ifn?def|if|endif|else|elif|define|undef|extension|include)/g;
// #define EXPRESSION
// eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/optimal-quantifier-concatenation
const DEFINE = /define[ \t]+([^\n]+)\r?(?:\n|$)/g;
// #extension IDENTIFIER : enabled
const EXTENSION = /extension[ \t]+([\w-]+)[ \t]*:[ \t]*(enable|require)/g;
// #undef EXPRESSION
// eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/optimal-quantifier-concatenation
const UNDEF = /undef[ \t]+([^\n]+)\r?(?:\n|$)/g;
// #ifdef/#ifndef SOMEDEFINE, #if EXPRESSION
// eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/no-unused-capturing-group
const IF = /(ifdef|ifndef|if)[ \t]*([^\r\n]+)\r?\n/g;
// #endif/#else or #elif EXPRESSION
const ENDIF = /(endif|else|elif)(?:[ \t]+([^\r\n]*))?\r?\n?/g;
// identifier in form of IDENTIFIER or {IDENTIFIER}
const IDENTIFIER$1 = /\{?[\w-]+\}?/;
// [!]defined(EXPRESSION)
const DEFINED = /(!|\s)?defined\(([\w-]+)\)/;
// Matches all defined(...) patterns for parentheses check
const DEFINED_PARENS = /!?defined\s*\([^)]*\)/g;
// Matches defined or !defined at the end of a string (for parentheses detection)
const DEFINED_BEFORE_PAREN = /!?defined\s*$/;
// Matches comparison operators like ==, !=, <, <=, >, >=
const COMPARISON = /([a-z_]\w*)\s*(==|!=|<|<=|>|>=)\s*([\w"']+)/i;
// currently unsupported characters in the expression: + -
const INVALID = /[+\-]/g;
// #include "identifier" or optional second identifier #include "identifier1, identifier2"
// Matches only up to the closing quote of the include directive
const INCLUDE = /include[ \t]+"([\w-]+)(?:\s*,\s*([\w-]+))?"/g;
// loop index to replace, in the format {i}
const LOOP_INDEX = /\{i\}/g;
// matches color attachments, for example: pcFragColor1
const FRAGCOLOR = /(pcFragColor[1-8])\b/g;
/**
 * Pure static class implementing subset of C-style preprocessor.
 * inspired by: https://github.com/dcodeIO/Preprocessor.js
 */ class Preprocessor {
    /**
     * Run c-like preprocessor on the source code, and resolves the code based on the defines and ifdefs
     *
     * @param {string} source - The source code to work on.
     * @param {Map<string, string>} [includes] - A map containing key-value pairs of include names
     * and their content. These are used for resolving #include directives in the source.
     * @param {object} [options] - Optional parameters.
     * @param {boolean} [options.stripUnusedColorAttachments] - If true, strips unused color attachments.
     * @param {boolean} [options.stripDefines] - If true, strips all defines from the source.
     * @param {string} [options.sourceName] - The name of the source file.
     * @returns {string|null} Returns preprocessed source code, or null in case of error.
     */ static run(source, includes = new Map(), options = {}) {
        Preprocessor.sourceName = options.sourceName;
        // strips comments, handles // and many cases of /*
        source = this.stripComments(source);
        // right trim each line
        source = source.split(/\r?\n/).map((line)=>line.trimEnd()).join('\n');
        // extracted defines
        const defines = new Map();
        // extracted defines with name in {} which are to be replaced with their values
        const injectDefines = new Map();
        // preprocess defines / ifdefs ..
        source = this._preprocess(source, defines, injectDefines, includes, options.stripDefines);
        if (source === null) return null;
        // extract defines that evaluate to an integer number
        const intDefines = new Map();
        defines.forEach((value, key)=>{
            if (Number.isInteger(parseFloat(value)) && !value.includes('.')) {
                intDefines.set(key, value);
            }
        });
        // strip comments again after the includes have been resolved
        source = this.stripComments(source);
        source = this.stripUnusedColorAttachments(source, options);
        // remove empty lines
        source = this.RemoveEmptyLines(source);
        // process array sizes
        source = this.processArraySize(source, intDefines);
        // inject defines
        source = this.injectDefines(source, injectDefines);
        return source;
    }
    static stripUnusedColorAttachments(source, options) {
        if (options.stripUnusedColorAttachments) {
            // find out how many times pcFragColorX is used (see gles3.js)
            const counts = new Map();
            const matches = source.match(FRAGCOLOR);
            matches?.forEach((match)=>{
                const index = parseInt(match.charAt(match.length - 1), 10);
                counts.set(index, (counts.get(index) ?? 0) + 1);
            });
            // if there's any attachment used only one time (only as a declaration, without actual use)
            const anySingleUse = Array.from(counts.values()).some((count)=>count === 1);
            if (anySingleUse) {
                // remove all lines that contains pcFragColorX with single usage
                const lines = source.split('\n');
                const keepLines = [];
                for(let i = 0; i < lines.length; i++){
                    const match = lines[i].match(FRAGCOLOR);
                    if (match) {
                        const index = parseInt(match[0].charAt(match[0].length - 1), 10);
                        if (index > 0 && counts.get(index) === 1) {
                            continue;
                        }
                    }
                    keepLines.push(lines[i]);
                }
                source = keepLines.join('\n');
            }
        }
        return source;
    }
    static stripComments(source) {
        return source.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
    }
    static processArraySize(source, intDefines) {
        if (source !== null) {
            // replace lines containing "[intDefine]" with their values, so that we know the array size for WebGPU uniform buffer
            // example: weight[SAMPLES] => float weight[11] in case there was a "define SAMPLES 11" in the source code
            intDefines.forEach((value, key)=>{
                source = source.replace(new RegExp(`\\[${key}\\]`, 'g'), `[${value}]`);
            });
        }
        return source;
    }
    static injectDefines(source, injectDefines) {
        if (source !== null && injectDefines.size > 0) {
            // replace all instances of the injected defines with the value itself
            const lines = source.split('\n');
            injectDefines.forEach((value, key)=>{
                const regex = new RegExp(key, 'g');
                for(let i = 0; i < lines.length; i++){
                    // replace them on lines that do not contain a preprocessor directive (the define itself for example)
                    if (!lines[i].includes('#')) {
                        lines[i] = lines[i].replace(regex, value);
                    }
                }
            });
            source = lines.join('\n');
        }
        return source;
    }
    static RemoveEmptyLines(source) {
        if (source !== null) {
            source = source.split(/\r?\n/)// convert lines with only white space into empty string
            .map((line)=>line.trim() === '' ? '' : line).join('\n');
            // remove more than 1 consecutive empty lines
            source = source.replace(/(\n\n){3,}/g, '\n\n');
        }
        return source;
    }
    /**
     * Process source code, and resolves the code based on the defines and ifdefs.
     *
     * @param {string} source - The source code to work on.
     * @param {Map<string, string>} defines - Supplied defines which are used in addition to those
     * defined in the source code. Maps a define name to its value. Note that the map is modified
     * by the function.
     * @param {Map<string, string>} injectDefines - An object to collect defines that are to be
     * replaced with their values.
     * @param {Map<string, string>} [includes] - An object containing key-value pairs of include names and their
     * content.
     * @param {boolean} [stripDefines] - If true, strips all defines from the source.
     * @returns {string|null} Returns preprocessed source code, or null if failed.
     */ static _preprocess(source, defines = new Map(), injectDefines, includes, stripDefines) {
        const originalSource = source;
        // stack, storing info about ifdef blocks
        const stack = [];
        // true if the function encounter a problem
        let error = false;
        let match;
        while((match = KEYWORD.exec(source)) !== null && !error){
            const keyword = match[1];
            switch(keyword){
                case 'define':
                    {
                        // read the rest of the define line
                        DEFINE.lastIndex = match.index;
                        const define = DEFINE.exec(source);
                        Debug.assert(define, `Invalid [${keyword}]: ${source.substring(match.index, match.index + 100)}...`);
                        error ||= define === null;
                        const expression = define[1];
                        // split it to identifier name and a value
                        IDENTIFIER$1.lastIndex = define.index;
                        const identifierValue = IDENTIFIER$1.exec(expression);
                        const identifier = identifierValue[0];
                        let value = expression.substring(identifier.length).trim();
                        if (value === '') value = 'true';
                        // are we inside if-blocks that are accepted
                        const keep = Preprocessor._keep(stack);
                        let stripThisDefine = stripDefines;
                        if (keep) {
                            // replacement identifier (inside {}) - always remove it from code
                            const replacementDefine = identifier.startsWith('{') && identifier.endsWith('}');
                            if (replacementDefine) {
                                stripThisDefine = true;
                            }
                            if (replacementDefine) {
                                injectDefines.set(identifier, value);
                            } else {
                                defines.set(identifier, value);
                            }
                            if (stripThisDefine) {
                                // cut out the define line
                                source = source.substring(0, define.index - 1) + source.substring(DEFINE.lastIndex);
                                // continue processing on the next symbol
                                KEYWORD.lastIndex = define.index - 1;
                            }
                        }
                        Debug.trace(TRACEID, `${keyword}: [${identifier}] ${value} ${keep ? '' : 'IGNORED'}`);
                        // continue on the next line
                        if (!stripThisDefine) {
                            KEYWORD.lastIndex = define.index + define[0].length;
                        }
                        break;
                    }
                case 'undef':
                    {
                        // read the rest of the define line
                        UNDEF.lastIndex = match.index;
                        const undef = UNDEF.exec(source);
                        const identifier = undef[1].trim();
                        // are we inside if-blocks that are accepted
                        const keep = Preprocessor._keep(stack);
                        // remove it from defines
                        if (keep) {
                            defines.delete(identifier);
                            if (stripDefines) {
                                // cut out the undef line
                                source = source.substring(0, undef.index - 1) + source.substring(UNDEF.lastIndex);
                                // continue processing on the next symbol
                                KEYWORD.lastIndex = undef.index - 1;
                            }
                        }
                        Debug.trace(TRACEID, `${keyword}: [${identifier}] ${keep ? '' : 'IGNORED'}`);
                        // continue on the next line
                        if (!stripDefines) {
                            KEYWORD.lastIndex = undef.index + undef[0].length;
                        }
                        break;
                    }
                case 'extension':
                    {
                        EXTENSION.lastIndex = match.index;
                        const extension = EXTENSION.exec(source);
                        Debug.assert(extension, `Invalid [${keyword}]: ${source.substring(match.index, match.index + 100)}...`);
                        error ||= extension === null;
                        if (extension) {
                            const identifier = extension[1];
                            // are we inside if-blocks that are accepted
                            const keep = Preprocessor._keep(stack);
                            if (keep) {
                                defines.set(identifier, 'true');
                            }
                            Debug.trace(TRACEID, `${keyword}: [${identifier}] ${keep ? '' : 'IGNORED'}`);
                        }
                        // continue on the next line
                        KEYWORD.lastIndex = extension.index + extension[0].length;
                        break;
                    }
                case 'ifdef':
                case 'ifndef':
                case 'if':
                    {
                        // read the if line
                        IF.lastIndex = match.index;
                        const iff = IF.exec(source);
                        const expression = iff[2];
                        // evaluate expression
                        const evaluated = Preprocessor.evaluate(expression, defines);
                        error ||= evaluated.error;
                        let result = evaluated.result;
                        if (keyword === 'ifndef') {
                            result = !result;
                        }
                        // add info to the stack (to be handled later)
                        stack.push({
                            anyKeep: result,
                            keep: result,
                            start: match.index,
                            end: IF.lastIndex // end index of IF line
                        });
                        Debug.trace(TRACEID, `${keyword}: [${expression}] => ${result}`);
                        // continue on the next line
                        KEYWORD.lastIndex = iff.index + iff[0].length;
                        break;
                    }
                case 'endif':
                case 'else':
                case 'elif':
                    {
                        // match the endif
                        ENDIF.lastIndex = match.index;
                        const endif = ENDIF.exec(source);
                        const blockInfo = stack.pop();
                        if (!blockInfo) {
                            console.error(`Shader preprocessing encountered "#${endif[1]}" without a preceding #if #ifdef #ifndef while preprocessing ${Preprocessor.sourceName} on line:\n ${source.substring(match.index, match.index + 100)}...`, {
                                source: originalSource
                            });
                            error = true;
                            continue;
                        }
                        // code between if and endif
                        const blockCode = blockInfo.keep ? source.substring(blockInfo.end, match.index) : '';
                        Debug.trace(TRACEID, `${keyword}: [previous block] => ${blockCode !== ''}`);
                        // cut out the IF and ENDIF lines, leave block if required
                        source = source.substring(0, blockInfo.start) + blockCode + source.substring(ENDIF.lastIndex);
                        KEYWORD.lastIndex = blockInfo.start + blockCode.length;
                        // handle else if
                        const endifCommand = endif[1];
                        if (endifCommand === 'else' || endifCommand === 'elif') {
                            // if any branch was already accepted, all else branches need to fail regardless of the result
                            let result = false;
                            if (!blockInfo.anyKeep) {
                                if (endifCommand === 'else') {
                                    result = !blockInfo.keep;
                                } else {
                                    const evaluated = Preprocessor.evaluate(endif[2], defines);
                                    result = evaluated.result;
                                    error ||= evaluated.error;
                                }
                            }
                            // add back to stack
                            stack.push({
                                anyKeep: blockInfo.anyKeep || result,
                                keep: result,
                                start: KEYWORD.lastIndex,
                                end: KEYWORD.lastIndex
                            });
                            Debug.trace(TRACEID, `${keyword}: [${endif[2]}] => ${result}`);
                        }
                        break;
                    }
                case 'include':
                    {
                        // match the include
                        INCLUDE.lastIndex = match.index;
                        const include = INCLUDE.exec(source);
                        error ||= include === null;
                        if (!include) {
                            Debug.assert(include, `Invalid [${keyword}] while preprocessing ${Preprocessor.sourceName}:\n${source.substring(match.index, match.index + 100)}...`);
                            error = true;
                            continue;
                        }
                        const identifier = include[1].trim();
                        const countIdentifier = include[2]?.trim();
                        // are we inside if-blocks that are accepted
                        const keep = Preprocessor._keep(stack);
                        if (keep) {
                            // cut out the include line and replace it with the included string
                            let includeSource = includes?.get(identifier);
                            if (includeSource !== undefined) {
                                includeSource = this.stripComments(includeSource);
                                // handle second identifier specifying loop count
                                if (countIdentifier) {
                                    const countString = defines.get(countIdentifier);
                                    const count = parseFloat(countString);
                                    if (Number.isInteger(count)) {
                                        // add the include count times
                                        let result = '';
                                        for(let i = 0; i < count; i++){
                                            result += includeSource.replace(LOOP_INDEX, String(i));
                                        }
                                        includeSource = result;
                                    } else {
                                        console.error(`Include Count identifier "${countIdentifier}" not resolved while preprocessing ${Preprocessor.sourceName} on line:\n ${source.substring(match.index, match.index + 100)}...`, {
                                            originalSource: originalSource,
                                            source: source
                                        });
                                        error = true;
                                    }
                                }
                                // replace the include by the included string
                                source = source.substring(0, include.index - 1) + includeSource + source.substring(INCLUDE.lastIndex);
                                // process the just included test
                                KEYWORD.lastIndex = include.index - 1;
                            } else {
                                console.error(`Include "${identifier}" not resolved while preprocessing ${Preprocessor.sourceName}`, {
                                    originalSource: originalSource,
                                    source: source
                                });
                                error = true;
                                continue;
                            }
                        }
                        Debug.trace(TRACEID, `${keyword}: [${identifier}] ${keep ? '' : 'IGNORED'}`);
                        break;
                    }
            }
        }
        if (stack.length > 0) {
            console.error(`Shader preprocessing reached the end of the file without encountering the necessary #endif to close a preceding #if, #ifdef, or #ifndef block. ${Preprocessor.sourceName}`);
            error = true;
        }
        if (error) {
            console.error('Failed to preprocess shader: ', {
                source: originalSource
            });
            return null;
        }
        return source;
    }
    // function returns true if the evaluation is inside keep branches
    static _keep(stack) {
        for(let i = 0; i < stack.length; i++){
            if (!stack[i].keep) {
                return false;
            }
        }
        return true;
    }
    /**
     * Evaluates a single atomic expression, which can be:
     * - `defined(EXPRESSION)` or `!defined(EXPRESSION)`
     * - Comparisons such as `A == B`, `A != B`, `A > B`, etc.
     * - Simple checks for the existence of a define.
     *
     * @param {string} expr - The atomic expression to evaluate.
     * @param {Map<string, string>} defines - A map containing key-value pairs of defines.
     * @returns {object} Returns an object containing the result of the evaluation and an error flag.
     */ static evaluateAtomicExpression(expr, defines) {
        let error = false;
        expr = expr.trim();
        let invert = false;
        // Handle boolean literals
        if (expr === 'true') {
            return {
                result: true,
                error
            };
        }
        if (expr === 'false') {
            return {
                result: false,
                error
            };
        }
        // Handle defined(expr) and !defined(expr)
        const definedMatch = DEFINED.exec(expr);
        if (definedMatch) {
            invert = definedMatch[1] === '!';
            expr = definedMatch[2].trim();
            const exists = defines.has(expr);
            return {
                result: invert ? !exists : exists,
                error
            };
        }
        // Handle comparisons
        const comparisonMatch = COMPARISON.exec(expr);
        if (comparisonMatch) {
            const left = defines.get(comparisonMatch[1].trim()) ?? comparisonMatch[1].trim();
            const right = defines.get(comparisonMatch[3].trim()) ?? comparisonMatch[3].trim();
            const operator = comparisonMatch[2].trim();
            let result = false;
            switch(operator){
                case '==':
                    result = left === right;
                    break;
                case '!=':
                    result = left !== right;
                    break;
                case '<':
                    result = left < right;
                    break;
                case '<=':
                    result = left <= right;
                    break;
                case '>':
                    result = left > right;
                    break;
                case '>=':
                    result = left >= right;
                    break;
                default:
                    error = true;
            }
            return {
                result,
                error
            };
        }
        // Default case: check if expression is defined
        const result = defines.has(expr);
        return {
            result,
            error
        };
    }
    /**
     * Processes parentheses in an expression by recursively evaluating subexpressions.
     * Ignores parentheses that are part of defined() calls.
     *
     * @param {string} expression - The expression to process.
     * @param {Map<string, string>} defines - A map containing key-value pairs of defines.
     * @returns {object} Returns an object containing the processed expression and an error flag.
     */ static processParentheses(expression, defines) {
        let error = false;
        let processed = expression.trim();
        // Remove outer parentheses that wrap the entire expression
        while(processed.startsWith('(') && processed.endsWith(')')){
            let depth = 0;
            let wrapsEntire = true;
            for(let i = 0; i < processed.length - 1; i++){
                if (processed[i] === '(') depth++;
                else if (processed[i] === ')') {
                    depth--;
                    if (depth === 0) {
                        wrapsEntire = false;
                        break;
                    }
                }
            }
            if (wrapsEntire) {
                processed = processed.slice(1, -1).trim();
            } else {
                break;
            }
        }
        // Keep processing until no more precedence parentheses exist
        while(true){
            let foundParen = false;
            let depth = 0;
            let maxDepth = 0;
            let deepestStart = -1;
            let deepestEnd = -1;
            // Find the deepest nested parentheses that aren't part of defined()
            let inDefinedParen = 0;
            for(let i = 0; i < processed.length; i++){
                if (processed[i] === '(') {
                    // Check if this is part of defined() - look back for "defined" or "!defined"
                    const beforeParen = processed.substring(0, i);
                    if (DEFINED_BEFORE_PAREN.test(beforeParen)) {
                        inDefinedParen++;
                    } else if (inDefinedParen === 0) {
                        depth++;
                        if (depth > maxDepth) {
                            maxDepth = depth;
                            deepestStart = i;
                        }
                        foundParen = true;
                    }
                } else if (processed[i] === ')') {
                    if (inDefinedParen > 0) {
                        inDefinedParen--;
                    } else if (depth > 0) {
                        if (depth === maxDepth && deepestStart !== -1) {
                            deepestEnd = i;
                        }
                        depth--;
                    }
                }
            }
            if (!foundParen || deepestStart === -1 || deepestEnd === -1) {
                break;
            }
            // Extract and evaluate the subexpression
            const subExpr = processed.substring(deepestStart + 1, deepestEnd);
            const { result, error: subError } = Preprocessor.evaluate(subExpr, defines);
            error = error || subError;
            // Replace the parentheses expression with its result
            processed = processed.substring(0, deepestStart) + (result ? 'true' : 'false') + processed.substring(deepestEnd + 1);
        }
        return {
            expression: processed,
            error
        };
    }
    /**
     * Evaluates a complex expression with support for `defined`, `!defined`, comparisons, `&&`,
     * `||`, and parentheses for precedence.
     *
     * @param {string} expression - The expression to evaluate.
     * @param {Map<string, string>} defines - A map containing key-value pairs of defines.
     * @returns {object} Returns an object containing the result of the evaluation and an error flag.
     */ static evaluate(expression, defines) {
        const correct = INVALID.exec(expression) === null;
        Debug.assert(correct, `Resolving expression like this is not supported: ${expression}`);
        // Process parentheses first (skip if no parentheses exist or only defined() parentheses)
        let processedExpr = expression;
        let parenError = false;
        // Quick check: remove all defined(...) patterns and see if any parentheses remain
        // If they do, process them recursively to handle nested parentheses
        const withoutDefined = expression.replace(DEFINED_PARENS, '');
        if (withoutDefined.indexOf('(') !== -1) {
            const processed = Preprocessor.processParentheses(expression, defines);
            processedExpr = processed.expression;
            parenError = processed.error;
        }
        if (parenError) {
            Debug.log(`Parenthesis parsing error in expression: "${expression}"`);
            return {
                result: false,
                error: true
            };
        }
        // Step 1: Split by "||" to handle OR conditions
        const orSegments = processedExpr.split('||');
        for (const orSegment of orSegments){
            // Step 2: Split each OR segment by "&&" to handle AND conditions
            const andSegments = orSegment.split('&&');
            // Step 3: Evaluate each AND segment
            let andResult = true;
            for (const andSegment of andSegments){
                const { result, error } = Preprocessor.evaluateAtomicExpression(andSegment.trim(), defines);
                if (!result || error) {
                    andResult = false;
                    break; // Short-circuit AND evaluation
                }
            }
            // Step 4: If any OR segment evaluates to true, short-circuit and return true
            if (andResult) {
                return {
                    result: true,
                    error: !correct
                };
            }
        }
        // If no OR segment is true, the whole expression is false
        return {
            result: false,
            error: !correct
        };
    }
}

var gles3PS = /* glsl */ `

#ifndef outType_0
#define outType_0 vec4
#endif

layout(location = 0) out highp outType_0 pcFragColor0;

#if COLOR_ATTACHMENT_1
layout(location = 1) out highp outType_1 pcFragColor1;
#endif

#if COLOR_ATTACHMENT_2
layout(location = 2) out highp outType_2 pcFragColor2;
#endif

#if COLOR_ATTACHMENT_3
layout(location = 3) out highp outType_3 pcFragColor3;
#endif

#if COLOR_ATTACHMENT_4
layout(location = 4) out highp outType_4 pcFragColor4;
#endif

#if COLOR_ATTACHMENT_5
layout(location = 5) out highp outType_5 pcFragColor5;
#endif

#if COLOR_ATTACHMENT_6
layout(location = 6) out highp outType_6 pcFragColor6;
#endif

#if COLOR_ATTACHMENT_7
layout(location = 7) out highp outType_7 pcFragColor7;
#endif

#define gl_FragColor pcFragColor0

#define varying in

#define texture2D texture
#define texture2DBias texture
#define textureCube texture
#define texture2DProj textureProj
#define texture2DLod textureLod
#define texture2DProjLod textureProjLod
#define textureCubeLod textureLod
#define texture2DGrad textureGrad
#define texture2DProjGrad textureProjGrad
#define textureCubeGrad textureGrad
#define utexture2D texture
#define itexture2D texture

// deprecated defines
#define texture2DLodEXT texture2DLodEXT_is_no_longer_supported_use_texture2DLod_instead
#define texture2DProjLodEXT texture2DProjLodEXT_is_no_longer_supported_use_texture2DProjLod
#define textureCubeLodEXT textureCubeLodEXT_is_no_longer_supported_use_textureCubeLod_instead
#define texture2DGradEXT texture2DGradEXT_is_no_longer_supported_use_texture2DGrad_instead
#define texture2DProjGradEXT texture2DProjGradEXT_is_no_longer_supported_use_texture2DProjGrad_instead
#define textureCubeGradEXT textureCubeGradEXT_is_no_longer_supported_use_textureCubeGrad_instead

// sample shadows using textureGrad to remove derivatives in the dynamic loops (which are used by
// clustered lighting) - as DirectX shader compiler tries to unroll the loops and takes long time
// to compile the shader. Using textureLod would be even better, but WebGl does not translate it to
// lod instruction for DirectX correctly and uses SampleCmp instead of SampleCmpLevelZero or similar.
#define textureShadow(res, uv) textureGrad(res, uv, vec2(1, 1), vec2(1, 1))

// pass / accept shadow map or texture as a function parameter, on webgl this is simply passed as is
// but this is needed for WebGPU
#define SHADOWMAP_PASS(name) name
#define SHADOWMAP_ACCEPT(name) sampler2DShadow name
#define TEXTURE_PASS(name) name
#define TEXTURE_ACCEPT(name) sampler2D name
#define TEXTURE_ACCEPT_HIGHP(name) highp sampler2D name

#define GL2
`;

var gles3VS = /* glsl */ `
#define attribute in
#define varying out
#define texture2D texture
#define utexture2D texture
#define itexture2D texture
#define GL2
#define VERTEXSHADER

#define TEXTURE_PASS(name) name
#define TEXTURE_ACCEPT(name) sampler2D name
#define TEXTURE_ACCEPT_HIGHP(name) highp sampler2D name
`;

var webgpuPS$1 = /* glsl */ `

// texelFetch support and others
#extension GL_EXT_samplerless_texture_functions : require

#ifndef outType_0
#define outType_0 vec4
#endif
#ifndef outType_1
#define outType_1 vec4
#endif
#ifndef outType_2
#define outType_2 vec4
#endif
#ifndef outType_3
#define outType_3 vec4
#endif
#ifndef outType_4
#define outType_4 vec4
#endif
#ifndef outType_5
#define outType_5 vec4
#endif
#ifndef outType_6
#define outType_6 vec4
#endif
#ifndef outType_7
#define outType_7 vec4
#endif

layout(location = 0) out highp outType_0 pcFragColor0;
layout(location = 1) out highp outType_1 pcFragColor1;
layout(location = 2) out highp outType_2 pcFragColor2;
layout(location = 3) out highp outType_3 pcFragColor3;
layout(location = 4) out highp outType_4 pcFragColor4;
layout(location = 5) out highp outType_5 pcFragColor5;
layout(location = 6) out highp outType_6 pcFragColor6;
layout(location = 7) out highp outType_7 pcFragColor7;

#define gl_FragColor pcFragColor0

#define texture2D(res, uv) texture(sampler2D(res, res ## _sampler), uv)
#define texture2DBias(res, uv, bias) texture(sampler2D(res, res ## _sampler), uv, bias)
#define texture2DLod(res, uv, lod) textureLod(sampler2D(res, res ## _sampler), uv, lod)
#define textureCube(res, uv) texture(samplerCube(res, res ## _sampler), uv)
#define textureCubeLod(res, uv, lod) textureLod(samplerCube(res, res ## _sampler), uv, lod)
#define textureShadow(res, uv) textureLod(sampler2DShadow(res, res ## _sampler), uv, 0.0)
#define itexture2D(res, uv) texture(isampler2D(res, res ## _sampler), uv)
#define utexture2D(res, uv) texture(usampler2D(res, res ## _sampler), uv)

// deprecated defines
#define texture2DLodEXT texture2DLodEXT_is_no_longer_supported_use_texture2DLod_instead
#define texture2DProjLodEXT texture2DProjLodEXT_is_no_longer_supported_use_texture2DProjLod
#define textureCubeLodEXT textureCubeLodEXT_is_no_longer_supported_use_textureCubeLod_instead
#define texture2DGradEXT texture2DGradEXT_is_no_longer_supported_use_texture2DGrad_instead
#define texture2DProjGradEXT texture2DProjGradEXT_is_no_longer_supported_use_texture2DProjGrad_instead
#define textureCubeGradEXT textureCubeGradEXT_is_no_longer_supported_use_textureCubeGrad_instead

// TODO: implement other texture sampling macros
// #define texture2DProj textureProj
// #define texture2DProjLod textureProjLod
// #define texture2DGrad textureGrad
// #define texture2DProjGrad textureProjGrad
// #define textureCubeGrad textureGrad

// pass / accept shadow map as a function parameter, passes both the texture as well as sampler
// as the combined sampler can be only created at a point of use
#define SHADOWMAP_PASS(name) name, name ## _sampler
#define SHADOWMAP_ACCEPT(name) texture2D name, sampler name ## _sampler
#define TEXTURE_PASS(name) name, name ## _sampler
#define TEXTURE_ACCEPT(name) texture2D name, sampler name ## _sampler
#define TEXTURE_ACCEPT_HIGHP TEXTURE_ACCEPT

#define GL2
#define WEBGPU
`;

var webgpuVS$1 = /* glsl */ `

// texelFetch support and others
#extension GL_EXT_samplerless_texture_functions : require

#define texture2D(res, uv) texture(sampler2D(res, res ## _sampler), uv)
#define itexture2D(res, uv) texture(isampler2D(res, res ## _sampler), uv)
#define utexture2D(res, uv) texture(usampler2D(res, res ## _sampler), uv)

#define TEXTURE_PASS(name) name, name ## _sampler
#define TEXTURE_ACCEPT(name) texture2D name, sampler name ## _sampler
#define TEXTURE_ACCEPT_HIGHP TEXTURE_ACCEPT

#define GL2
#define WEBGPU
#define VERTEXSHADER
#define gl_VertexID gl_VertexIndex
#define gl_InstanceID gl_InstanceIndex
`;

var webgpuPS = /* wgsl */ `
`;

var webgpuVS = /* wgsl */ `
#define VERTEXSHADER
`;

var sharedGLSL = /* glsl */ `

// convert clip space position into texture coordinates to sample scene grab textures
vec2 getGrabScreenPos(vec4 clipPos) {
    vec2 uv = (clipPos.xy / clipPos.w) * 0.5 + 0.5;

    #ifdef WEBGPU
        uv.y = 1.0 - uv.y;
    #endif

    return uv;
}

// convert uv coordinates to sample image effect texture (render target texture rendered without
// forward renderer which does the flip in the projection matrix)
vec2 getImageEffectUV(vec2 uv) {
    #ifdef WEBGPU
        uv.y = 1.0 - uv.y;
    #endif

    return uv;
}
`;

var sharedWGSL = /* glsl */ `

// convert clip space position into texture coordinates for sampling scene grab textures
fn getGrabScreenPos(clipPos: vec4<f32>) -> vec2<f32> {
    var uv: vec2<f32> = (clipPos.xy / clipPos.w) * 0.5 + vec2<f32>(0.5);
    uv.y = 1.0 - uv.y;
    return uv;
}

// convert uv coordinates to sample image effect texture (render target texture rendered without
// forward renderer which does the flip in the projection matrix)
fn getImageEffectUV(uv: vec2<f32>) -> vec2<f32> {
    var modifiedUV: vec2<f32> = uv;
    modifiedUV.y = 1.0 - modifiedUV.y;
    return modifiedUV;
}

// types wrapped in size aligned structures to ensure correct alignment in uniform buffer arrays
struct WrappedF32 { @size(16) element: f32 }
struct WrappedI32 { @size(16) element: i32 }
struct WrappedU32 { @size(16) element: u32 }
struct WrappedVec2F { @size(16) element: vec2f }
struct WrappedVec2I { @size(16) element: vec2i }
struct WrappedVec2U { @size(16) element: vec2u }
`;

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 */ const _attrib2Semantic = {
    vertex_position: SEMANTIC_POSITION,
    vertex_normal: SEMANTIC_NORMAL,
    vertex_tangent: SEMANTIC_TANGENT,
    vertex_texCoord0: SEMANTIC_TEXCOORD0,
    vertex_texCoord1: SEMANTIC_TEXCOORD1,
    vertex_texCoord2: SEMANTIC_TEXCOORD2,
    vertex_texCoord3: SEMANTIC_TEXCOORD3,
    vertex_texCoord4: SEMANTIC_TEXCOORD4,
    vertex_texCoord5: SEMANTIC_TEXCOORD5,
    vertex_texCoord6: SEMANTIC_TEXCOORD6,
    vertex_texCoord7: SEMANTIC_TEXCOORD7,
    vertex_color: SEMANTIC_COLOR,
    vertex_boneIndices: SEMANTIC_BLENDINDICES,
    vertex_boneWeights: SEMANTIC_BLENDWEIGHT
};
/**
 * A class providing utility functions for shader definition creation.
 *
 * @ignore
 */ class ShaderDefinitionUtils {
    /**
     * Creates a shader definition.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {object} options - Object for passing optional arguments.
     * @param {string} [options.name] - A name of the shader.
     * @param {object} [options.attributes] - Attributes. Will be extracted from the vertexCode if
     * not provided.
     * @param {string} options.vertexCode - The vertex shader code.
     * @param {string} [options.vertexExtensions] - The vertex shader extensions code.
     * @param {string} [options.fragmentCode] - The fragment shader code.
     * @param {string} [options.fragmentExtensions] - The fragment shader extensions code.
     * @param {string} [options.fragmentPreamble] - The preamble string for the fragment shader.
     * @param {boolean} [options.useTransformFeedback] - Whether to use transform feedback. Defaults
     * to false.
     * @param {Map<string, string>} [options.vertexIncludes] - A map containing key-value pairs of
     * include names and their content. These are used for resolving #include directives in the
     * vertex shader source.
     * @param {Map<string, string>} [options.vertexDefines] - A map containing key-value pairs of
     * define names and their values. These are used for resolving #ifdef style of directives in the
     * vertex code.
     * @param {Map<string, string>} [options.fragmentIncludes] - A map containing key-value pairs
     * of include names and their content. These are used for resolving #include directives in the
     * fragment shader source.
     * @param {Map<string, string>} [options.fragmentDefines] - A map containing key-value pairs of
     * define names and their values. These are used for resolving #ifdef style of directives in the
     * fragment code.
     * @param {string | string[]} [options.fragmentOutputTypes] - Fragment shader output types,
     * which default to vec4. Passing a string will set the output type for all color attachments.
     * Passing an array will set the output type for each color attachment.
     * @returns {object} Returns the created shader definition.
     */ static createDefinition(device, options) {
        Debug.assert(options);
        Debug.assert(!options.vertexDefines || options.vertexDefines instanceof Map);
        Debug.assert(!options.vertexIncludes || options.vertexIncludes instanceof Map);
        Debug.assert(!options.fragmentDefines || options.fragmentDefines instanceof Map);
        Debug.assert(!options.fragmentIncludes || options.fragmentIncludes instanceof Map);
        // Normalize fragmentOutputTypes to an array
        const normalizedOutputTypes = (options)=>{
            let fragmentOutputTypes = options.fragmentOutputTypes ?? 'vec4';
            if (!Array.isArray(fragmentOutputTypes)) {
                fragmentOutputTypes = [
                    fragmentOutputTypes
                ];
            }
            return fragmentOutputTypes;
        };
        const getDefines = (gpu, gl2, isVertex, options)=>{
            const deviceIntro = device.isWebGPU ? gpu : gl2;
            // a define per supported color attachment, which strips out unsupported output definitions in the deviceIntro
            let attachmentsDefine = '';
            // Define the fragment shader output type, vec4 by default
            if (!isVertex) {
                const fragmentOutputTypes = normalizedOutputTypes(options);
                for(let i = 0; i < device.maxColorAttachments; i++){
                    attachmentsDefine += `#define COLOR_ATTACHMENT_${i}\n`;
                    const outType = fragmentOutputTypes[i] ?? 'vec4';
                    attachmentsDefine += `#define outType_${i} ${outType}\n`;
                }
            }
            return attachmentsDefine + deviceIntro;
        };
        const getDefinesWgsl = (isVertex, options)=>{
            let attachmentsDefine = '';
            // Define the fragment shader output type, vec4 by default
            if (!isVertex) {
                const fragmentOutputTypes = normalizedOutputTypes(options);
                // create alias for each output type
                for(let i = 0; i < device.maxColorAttachments; i++){
                    const glslOutType = fragmentOutputTypes[i] ?? 'vec4';
                    const wgslOutType = primitiveGlslToWgslTypeMap.get(glslOutType);
                    Debug.assert(wgslOutType, `Unknown output type translation: ${glslOutType} -> ${wgslOutType}`);
                    attachmentsDefine += `alias pcOutType${i} = ${wgslOutType};\n`;
                }
            }
            return attachmentsDefine;
        };
        const name = options.name ?? 'Untitled';
        let vertCode;
        let fragCode;
        const vertexDefinesCode = ShaderDefinitionUtils.getDefinesCode(device, options.vertexDefines);
        const fragmentDefinesCode = ShaderDefinitionUtils.getDefinesCode(device, options.fragmentDefines);
        const wgsl = options.shaderLanguage === SHADERLANGUAGE_WGSL;
        if (wgsl) {
            vertCode = `
                ${getDefinesWgsl(true, options)}
                ${webgpuVS}
                ${sharedWGSL}
                ${vertexDefinesCode}
                ${options.vertexCode}
            `;
            fragCode = `
                ${getDefinesWgsl(false, options)}
                ${webgpuPS}
                ${sharedWGSL}
                ${fragmentDefinesCode}
                ${options.fragmentCode}
            `;
        } else {
            Debug.assert(options.vertexCode);
            // vertex code
            vertCode = `${ShaderDefinitionUtils.versionCode(device) + getDefines(webgpuVS$1, gles3VS, true, options) + vertexDefinesCode + ShaderDefinitionUtils.precisionCode(device)}
                ${sharedGLSL}
                ${ShaderDefinitionUtils.getShaderNameCode(name)}
                ${options.vertexCode}`;
            Debug.assert(options.fragmentCode);
            // fragment code
            fragCode = `${(options.fragmentPreamble || '') + ShaderDefinitionUtils.versionCode(device) + getDefines(webgpuPS$1, gles3PS, false, options) + fragmentDefinesCode + ShaderDefinitionUtils.precisionCode(device)}
                ${sharedGLSL}
                ${ShaderDefinitionUtils.getShaderNameCode(name)}
                ${options.fragmentCode}`;
        }
        return {
            name: name,
            shaderLanguage: options.shaderLanguage ?? SHADERLANGUAGE_GLSL,
            attributes: options.attributes,
            vshader: vertCode,
            vincludes: options.vertexIncludes,
            fincludes: options.fragmentIncludes,
            fshader: fragCode,
            useTransformFeedback: options.useTransformFeedback,
            meshUniformBufferFormat: options.meshUniformBufferFormat,
            meshBindGroupFormat: options.meshBindGroupFormat
        };
    }
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {Map<string, string>} [defines] - A map containing key-value pairs.
     * @returns {string} The shader code for the defines.
     * @private
     */ static getDefinesCode(device, defines) {
        let code = '';
        device.capsDefines.forEach((value, key)=>{
            code += `#define ${key} ${value}\n`;
        });
        code += '\n';
        defines?.forEach((value, key)=>{
            code += `#define ${key} ${value}\n`;
        });
        code += '\n';
        return code;
    }
    // SpectorJS integration
    static getShaderNameCode(name) {
        return `#define SHADER_NAME ${name}\n`;
    }
    static versionCode(device) {
        return device.isWebGPU ? '#version 450\n' : '#version 300 es\n';
    }
    static precisionCode(device, forcePrecision) {
        if (forcePrecision && forcePrecision !== 'highp' && forcePrecision !== 'mediump' && forcePrecision !== 'lowp') {
            forcePrecision = null;
        }
        if (forcePrecision) {
            if (forcePrecision === 'highp' && device.maxPrecision !== 'highp') {
                forcePrecision = 'mediump';
            }
            if (forcePrecision === 'mediump' && device.maxPrecision === 'lowp') {
                forcePrecision = 'lowp';
            }
        }
        const precision = forcePrecision ? forcePrecision : device.precision;
        const code = `
            precision ${precision} float;
            precision ${precision} int;
            precision ${precision} usampler2D;
            precision ${precision} isampler2D;
            precision ${precision} sampler2DShadow;
            precision ${precision} samplerCubeShadow;
            precision ${precision} sampler2DArray;
        `;
        return code;
    }
    /**
     * Extract the attributes specified in a vertex shader.
     *
     * @param {string} vsCode - The vertex shader code.
     * @returns {Object<string, string>} The attribute name to semantic map.
     * @ignore
     */ static collectAttributes(vsCode) {
        const attribs = {};
        let attrs = 0;
        let found = vsCode.indexOf('attribute');
        while(found >= 0){
            if (found > 0 && vsCode[found - 1] === '/') break;
            // skip the 'attribute' word inside the #define which we add to the shader
            let ignore = false;
            if (found > 0) {
                let startOfLine = vsCode.lastIndexOf('\n', found);
                startOfLine = startOfLine !== -1 ? startOfLine + 1 : 0;
                const lineStartString = vsCode.substring(startOfLine, found);
                if (lineStartString.includes('#')) {
                    ignore = true;
                }
            }
            if (!ignore) {
                const endOfLine = vsCode.indexOf(';', found);
                const startOfAttribName = vsCode.lastIndexOf(' ', endOfLine);
                const attribName = vsCode.substring(startOfAttribName + 1, endOfLine);
                // if the attribute already exists in the semantic map
                if (attribs[attribName]) {
                    Debug.warn(`Attribute [${attribName}] already exists when extracting the attributes from the vertex shader, ignoring.`, {
                        vsCode
                    });
                } else {
                    const semantic = _attrib2Semantic[attribName];
                    if (semantic !== undefined) {
                        attribs[attribName] = semantic;
                    } else {
                        attribs[attribName] = `ATTR${attrs}`;
                        attrs++;
                    }
                }
            }
            found = vsCode.indexOf('attribute', found + 1);
        }
        return attribs;
    }
}

/**
 * @import { BindGroupFormat } from './bind-group-format.js'
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { UniformBufferFormat } from './uniform-buffer-format.js'
 */ let id$8 = 0;
/**
 * A shader is a program that is responsible for rendering graphical primitives on a device's
 * graphics processor. The shader is generated from a shader definition. This shader definition
 * specifies the code for processing vertices and fragments processed by the GPU. The language of
 * the code is GLSL (or more specifically ESSL, the OpenGL ES Shading Language). The shader
 * definition also describes how the PlayCanvas engine should map vertex buffer elements onto the
 * attributes specified in the vertex shader code.
 *
 * @category Graphics
 */ class Shader {
    /**
     * Creates a new Shader instance.
     *
     * Consider {@link ShaderUtils#createShader} as a simpler and more powerful way to create
     * a shader.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this shader.
     * @param {object} definition - The shader definition from which to build the shader.
     * @param {string} [definition.name] - The name of the shader.
     * @param {Object<string, string>} [definition.attributes] - Object detailing the mapping of
     * vertex shader attribute names to semantics SEMANTIC_*. This enables the engine to match
     * vertex buffer data as inputs to the shader. When not specified, rendering without vertex
     * buffer is assumed.
     * @param {string} [definition.vshader] - Vertex shader source (GLSL code). Optional when
     * compute shader is specified.
     * @param {string} [definition.fshader] - Fragment shader source (GLSL code). Optional when
     * useTransformFeedback or compute shader is specified.
     * @param {string} [definition.cshader] - Compute shader source (WGSL code). Only supported on
     * WebGPU platform.
     * @param {Map<string, string>} [definition.vincludes] - A map containing key-value pairs of
     * include names and their content. These are used for resolving #include directives in the
     * vertex shader source.
     * @param {Map<string, string>} [definition.fincludes] - A map containing key-value pairs
     * of include names and their content. These are used for resolving #include directives in the
     * fragment shader source.
     * @param {Map<string, string>} [definition.cincludes] - A map containing key-value pairs
     * of include names and their content. These are used for resolving #include directives in the
     * compute shader source.
     * @param {boolean} [definition.useTransformFeedback] - Specifies that this shader outputs
     * post-VS data to a buffer.
     * @param {string | string[]} [definition.fragmentOutputTypes] - Fragment shader output types,
     * which default to vec4. Passing a string will set the output type for all color attachments.
     * Passing an array will set the output type for each color attachment.
     * @param {string} [definition.shaderLanguage] - Specifies the shader language of vertex and
     * fragment shaders. Defaults to {@link SHADERLANGUAGE_GLSL}.
     * @example
     * // Create a shader that renders primitives with a solid red color
     *
     * // Vertex shader
     * const vshader = `
     * attribute vec3 aPosition;
     *
     * void main(void) {
     *     gl_Position = vec4(aPosition, 1.0);
     * }
     * `;
     *
     * // Fragment shader
     * const fshader = `
     * precision ${graphicsDevice.precision} float;
     *
     * void main(void) {
     *     gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
     * }
     * `;
     *
     * const shaderDefinition = {
     *     attributes: {
     *         aPosition: pc.SEMANTIC_POSITION
     *     },
     *     vshader,
     *     fshader
     * };
     *
     * const shader = new pc.Shader(graphicsDevice, shaderDefinition);
     */ constructor(graphicsDevice, definition){
        /**
     * The attributes that this shader code uses. The location is the key, the value is the name.
     * These attributes are queried / extracted from the final shader.
     *
     * @type {Map<number, string>}
     * @ignore
     */ this.attributes = new Map();
        this.id = id$8++;
        this.device = graphicsDevice;
        this.definition = definition;
        this.name = definition.name || 'Untitled';
        this.init();
        if (definition.cshader) {
            Debug.assert(graphicsDevice.supportsCompute, 'Compute shaders are not supported on this device.');
            Debug.assert(!definition.vshader && !definition.fshader, 'Vertex and fragment shaders are not supported when creating a compute shader.');
            // pre-process compute shader source
            definition.cshader = Preprocessor.run(definition.cshader, definition.cincludes, {
                sourceName: `compute shader for ${this.label}`,
                stripDefines: true
            });
        } else {
            Debug.assert(definition.vshader, 'No vertex shader has been specified when creating a shader.');
            Debug.assert(definition.fshader, 'No fragment shader has been specified when creating a shader.');
            // keep reference to unmodified shaders in debug mode
            Debug.call(()=>{
                this.vUnmodified = definition.vshader;
                this.fUnmodified = definition.fshader;
            });
            const wgsl = definition.shaderLanguage === SHADERLANGUAGE_WGSL;
            // pre-process vertex shader source
            definition.vshader = Preprocessor.run(definition.vshader, definition.vincludes, {
                sourceName: `vertex shader for ${this.label}`,
                stripDefines: wgsl
            });
            // if no attributes are specified, try to extract the default names after the shader has been pre-processed
            if (definition.shaderLanguage === SHADERLANGUAGE_GLSL) {
                definition.attributes ??= ShaderDefinitionUtils.collectAttributes(definition.vshader);
            }
            // Strip unused color attachments from fragment shader.
            // Note: this is only needed for iOS 15 on WebGL2 where there seems to be a bug where color attachments that are not
            // written to generate metal linking errors. This is fixed on iOS 16, and iOS 14 does not support WebGL2.
            const stripUnusedColorAttachments = graphicsDevice.isWebGL2 && (platform.name === 'osx' || platform.name === 'ios');
            // pre-process fragment shader source
            definition.fshader = Preprocessor.run(definition.fshader, definition.fincludes, {
                stripUnusedColorAttachments,
                stripDefines: wgsl,
                sourceName: `fragment shader for ${this.label}`
            });
            if (!definition.vshader || !definition.fshader) {
                Debug.error(`Shader: Failed to create shader ${this.label}. Vertex or fragment shader source is empty.`, this);
                this.failed = true;
                return;
            }
        }
        this.impl = graphicsDevice.createShaderImpl(this);
        Debug.trace(TRACEID_SHADER_ALLOC, `Alloc: ${this.label}, stack: ${DebugGraphics.toString()}`, {
            instance: this
        });
    }
    /**
     * Initialize a shader back to its default state.
     *
     * @private
     */ init() {
        this.ready = false;
        this.failed = false;
    }
    /** @ignore */ get label() {
        return `Shader Id ${this.id} (${this.definition.shaderLanguage === SHADERLANGUAGE_WGSL ? 'WGSL' : 'GLSL'}) ${this.name}`;
    }
    /**
     * Frees resources associated with this shader.
     */ destroy() {
        Debug.trace(TRACEID_SHADER_ALLOC, `DeAlloc: Id ${this.id} ${this.name}`);
        this.device.onDestroyShader(this);
        this.impl.destroy(this);
    }
    /**
     * Called when the WebGL context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        this.init();
        this.impl.loseContext();
    }
    /** @ignore */ restoreContext() {
        this.impl.restoreContext(this.device, this);
    }
}

/**
 * @import { DynamicBuffer } from './dynamic-buffer.js'
 * @import { GraphicsDevice } from './graphics-device.js'
 */ /**
 * A container for storing the used areas of a pair of staging and gpu buffers.
 *
 * @ignore
 */ class UsedBuffer {
}
/**
 * A container for storing the return values of an allocation function.
 *
 * @ignore
 */ class DynamicBufferAllocation {
}
/**
 * The DynamicBuffers class provides a dynamic memory allocation system for uniform buffer data,
 * particularly for non-persistent uniform buffers. This class utilizes a bump allocator to
 * efficiently allocate aligned memory space from a set of large buffers managed internally. To
 * utilize this system, the user writes data to CPU-accessible staging buffers. When submitting
 * command buffers that require these buffers, the system automatically uploads the data to the GPU
 * buffers. This approach ensures efficient memory management and smooth data transfer between the
 * CPU and GPU.
 *
 * @ignore
 */ class DynamicBuffers {
    /**
     * Create the system of dynamic buffers.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {number} bufferSize - The size of the underlying large buffers.
     * @param {number} bufferAlignment - Alignment of each allocation.
     */ constructor(device, bufferSize, bufferAlignment){
        /**
     * Internally allocated gpu buffers.
     *
     * @type {DynamicBuffer[]}
     */ this.gpuBuffers = [];
        /**
     * Internally allocated staging buffers (CPU writable)
     *
     * @type {DynamicBuffer[]}
     */ this.stagingBuffers = [];
        /**
     * @type {UsedBuffer[]}
     */ this.usedBuffers = [];
        /**
     * @type {UsedBuffer|null}
     */ this.activeBuffer = null;
        this.device = device;
        this.bufferSize = bufferSize;
        this.bufferAlignment = bufferAlignment;
    }
    /**
     * Destroy the system of dynamic buffers.
     */ destroy() {
        this.gpuBuffers.forEach((gpuBuffer)=>{
            gpuBuffer.destroy(this.device);
        });
        this.gpuBuffers = null;
        this.stagingBuffers.forEach((stagingBuffer)=>{
            stagingBuffer.destroy(this.device);
        });
        this.stagingBuffers = null;
        this.usedBuffers = null;
        this.activeBuffer = null;
    }
    /**
     * Allocate an aligned space of the given size from a dynamic buffer.
     *
     * @param {DynamicBufferAllocation} allocation - The allocation info to fill.
     * @param {number} size - The size of the allocation.
     */ alloc(allocation, size) {
        // if we have active buffer without enough space
        if (this.activeBuffer) {
            const alignedStart = math.roundUp(this.activeBuffer.size, this.bufferAlignment);
            const space = this.bufferSize - alignedStart;
            if (space < size) {
                // we're done with this buffer, schedule it for submit
                this.scheduleSubmit();
            }
        }
        // if we don't have an active buffer, allocate new one
        if (!this.activeBuffer) {
            // gpu buffer
            let gpuBuffer = this.gpuBuffers.pop();
            if (!gpuBuffer) {
                gpuBuffer = this.createBuffer(this.device, this.bufferSize, false);
            }
            // staging buffer
            let stagingBuffer = this.stagingBuffers.pop();
            if (!stagingBuffer) {
                stagingBuffer = this.createBuffer(this.device, this.bufferSize, true);
            }
            this.activeBuffer = new UsedBuffer();
            this.activeBuffer.stagingBuffer = stagingBuffer;
            this.activeBuffer.gpuBuffer = gpuBuffer;
            this.activeBuffer.offset = 0;
            this.activeBuffer.size = 0;
        }
        // allocate from active buffer
        const activeBuffer = this.activeBuffer;
        const alignedStart = math.roundUp(activeBuffer.size, this.bufferAlignment);
        Debug.assert(alignedStart + size <= this.bufferSize, `The allocation size of ${size} is larger than the buffer size of ${this.bufferSize}`);
        allocation.gpuBuffer = activeBuffer.gpuBuffer;
        allocation.offset = alignedStart;
        allocation.storage = activeBuffer.stagingBuffer.alloc(alignedStart, size);
        // take the allocation from the buffer
        activeBuffer.size = alignedStart + size;
    }
    scheduleSubmit() {
        if (this.activeBuffer) {
            this.usedBuffers.push(this.activeBuffer);
            this.activeBuffer = null;
        }
    }
    submit() {
        // schedule currently active buffer for submit
        this.scheduleSubmit();
    }
}

/**
 * @import { DynamicBindGroup } from './bind-group.js'
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { UniformBufferFormat } from './uniform-buffer-format.js'
 * @import { UniformFormat } from './uniform-buffer-format.js'
 */ // Uniform buffer set functions - only implemented for types for which the default
// array to buffer copy does not work, or could be slower.
const _updateFunctions = [];
_updateFunctions[UNIFORMTYPE_FLOAT] = function(uniformBuffer, value, offset) {
    const dst = uniformBuffer.storageFloat32;
    dst[offset] = value;
};
_updateFunctions[UNIFORMTYPE_VEC2] = (uniformBuffer, value, offset)=>{
    const dst = uniformBuffer.storageFloat32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
};
_updateFunctions[UNIFORMTYPE_VEC3] = (uniformBuffer, value, offset)=>{
    const dst = uniformBuffer.storageFloat32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 2] = value[2];
};
_updateFunctions[UNIFORMTYPE_VEC4] = (uniformBuffer, value, offset)=>{
    const dst = uniformBuffer.storageFloat32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 2] = value[2];
    dst[offset + 3] = value[3];
};
_updateFunctions[UNIFORMTYPE_INT] = function(uniformBuffer, value, offset) {
    const dst = uniformBuffer.storageInt32;
    dst[offset] = value;
};
_updateFunctions[UNIFORMTYPE_IVEC2] = function(uniformBuffer, value, offset) {
    const dst = uniformBuffer.storageInt32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
};
_updateFunctions[UNIFORMTYPE_IVEC3] = function(uniformBuffer, value, offset) {
    const dst = uniformBuffer.storageInt32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 2] = value[2];
};
_updateFunctions[UNIFORMTYPE_IVEC4] = function(uniformBuffer, value, offset) {
    const dst = uniformBuffer.storageInt32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 2] = value[2];
    dst[offset + 3] = value[3];
};
// convert from continuous array to vec2[3] with padding to vec4[2]
_updateFunctions[UNIFORMTYPE_MAT2] = (uniformBuffer, value, offset)=>{
    const dst = uniformBuffer.storageFloat32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 4] = value[2];
    dst[offset + 5] = value[3];
    dst[offset + 8] = value[4];
    dst[offset + 9] = value[5];
};
// convert from continuous array to vec3[3] with padding to vec4[3]
_updateFunctions[UNIFORMTYPE_MAT3] = (uniformBuffer, value, offset)=>{
    const dst = uniformBuffer.storageFloat32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 2] = value[2];
    dst[offset + 4] = value[3];
    dst[offset + 5] = value[4];
    dst[offset + 6] = value[5];
    dst[offset + 8] = value[6];
    dst[offset + 9] = value[7];
    dst[offset + 10] = value[8];
};
_updateFunctions[UNIFORMTYPE_FLOATARRAY] = function(uniformBuffer, value, offset, count) {
    const dst = uniformBuffer.storageFloat32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i];
    }
};
_updateFunctions[UNIFORMTYPE_VEC2ARRAY] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageFloat32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i * 2];
        dst[offset + i * 4 + 1] = value[i * 2 + 1];
    }
};
_updateFunctions[UNIFORMTYPE_VEC3ARRAY] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageFloat32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i * 3];
        dst[offset + i * 4 + 1] = value[i * 3 + 1];
        dst[offset + i * 4 + 2] = value[i * 3 + 2];
    }
};
_updateFunctions[UNIFORMTYPE_UINT] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageUint32;
    dst[offset] = value;
};
_updateFunctions[UNIFORMTYPE_UVEC2] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageUint32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
};
_updateFunctions[UNIFORMTYPE_UVEC3] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageUint32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 2] = value[2];
};
_updateFunctions[UNIFORMTYPE_UVEC4] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageUint32;
    dst[offset] = value[0];
    dst[offset + 1] = value[1];
    dst[offset + 2] = value[2];
    dst[offset + 3] = value[3];
};
_updateFunctions[UNIFORMTYPE_INTARRAY] = function(uniformBuffer, value, offset, count) {
    const dst = uniformBuffer.storageInt32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i];
    }
};
_updateFunctions[UNIFORMTYPE_BOOLARRAY] = _updateFunctions[UNIFORMTYPE_INTARRAY];
_updateFunctions[UNIFORMTYPE_UINTARRAY] = function(uniformBuffer, value, offset, count) {
    const dst = uniformBuffer.storageUint32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i];
    }
};
_updateFunctions[UNIFORMTYPE_IVEC2ARRAY] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageInt32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i * 2];
        dst[offset + i * 4 + 1] = value[i * 2 + 1];
    }
};
_updateFunctions[UNIFORMTYPE_BVEC2ARRAY] = _updateFunctions[UNIFORMTYPE_IVEC2ARRAY];
_updateFunctions[UNIFORMTYPE_UVEC2ARRAY] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageUint32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i * 2];
        dst[offset + i * 4 + 1] = value[i * 2 + 1];
    }
};
_updateFunctions[UNIFORMTYPE_IVEC3ARRAY] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageInt32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i * 3];
        dst[offset + i * 4 + 1] = value[i * 3 + 1];
        dst[offset + i * 4 + 2] = value[i * 3 + 2];
    }
};
_updateFunctions[UNIFORMTYPE_BVEC3ARRAY] = _updateFunctions[UNIFORMTYPE_IVEC3ARRAY];
_updateFunctions[UNIFORMTYPE_UVEC3ARRAY] = (uniformBuffer, value, offset, count)=>{
    const dst = uniformBuffer.storageUint32;
    for(let i = 0; i < count; i++){
        dst[offset + i * 4] = value[i * 3];
        dst[offset + i * 4 + 1] = value[i * 3 + 1];
        dst[offset + i * 4 + 2] = value[i * 3 + 2];
    }
};
/**
 * A uniform buffer represents a GPU memory buffer storing the uniforms.
 *
 * @ignore
 */ class UniformBuffer {
    /**
     * Create a new UniformBuffer instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this uniform
     * buffer.
     * @param {UniformBufferFormat} format - Format of the uniform buffer.
     * @param {boolean} [persistent] - Whether the buffer is persistent. Defaults to true.
     */ constructor(graphicsDevice, format, persistent = true){
        /**
     * A render version used to track the last time the properties requiring bind group to be
     * updated were changed.
     *
     * @type {number}
     */ this.renderVersionDirty = 0;
        this.device = graphicsDevice;
        this.format = format;
        this.persistent = persistent;
        Debug.assert(format);
        if (persistent) {
            this.impl = graphicsDevice.createUniformBufferImpl(this);
            const storage = new ArrayBuffer(format.byteSize);
            this.assignStorage(new Int32Array(storage));
            graphicsDevice._vram.ub += this.format.byteSize;
        // TODO: register with the device and handle lost context
        // this.device.buffers.push(this);
        } else {
            this.allocation = new DynamicBufferAllocation();
        }
    }
    /**
     * Frees resources associated with this uniform buffer.
     */ destroy() {
        if (this.persistent) {
            // stop tracking the vertex buffer
            // TODO: remove the buffer from the list on the device (lost context handling)
            const device = this.device;
            this.impl.destroy(device);
            device._vram.ub -= this.format.byteSize;
        }
    }
    get offset() {
        return this.persistent ? 0 : this.allocation.offset;
    }
    /**
     * Assign a storage to this uniform buffer.
     *
     * @param {Int32Array} storage - The storage to assign to this uniform buffer.
     */ assignStorage(storage) {
        this.storageInt32 = storage;
        this.storageUint32 = new Uint32Array(storage.buffer, storage.byteOffset, storage.byteLength / 4);
        this.storageFloat32 = new Float32Array(storage.buffer, storage.byteOffset, storage.byteLength / 4);
    }
    /**
     * Called when the rendering context was lost. It releases all context related resources.
     */ loseContext() {
        this.impl?.loseContext();
    }
    /**
     * Assign a value to the uniform specified by its format. This is the fast version of assigning
     * a value to a uniform, avoiding any lookups.
     *
     * @param {UniformFormat} uniformFormat - The format of the uniform.
     * @param {any} value - The value to assign to the uniform.
     */ setUniform(uniformFormat, value) {
        Debug.assert(uniformFormat);
        const offset = uniformFormat.offset;
        if (value !== null && value !== undefined) {
            const updateFunction = _updateFunctions[uniformFormat.updateType];
            if (updateFunction) {
                updateFunction(this, value, offset, uniformFormat.count);
            } else {
                this.storageFloat32.set(value, offset);
            }
        } else {
            Debug.warnOnce(`Value was not set when assigning to uniform [${uniformFormat.name}]` + `, expected type ${uniformTypeToName[uniformFormat.type]} while rendering ${DebugGraphics.toString()}`);
        }
    }
    /**
     * Assign a value to the uniform specified by name.
     *
     * @param {string} name - The name of the uniform.
     * @param {any} value - The value to assign to the uniform.
     */ set(name, value) {
        const uniformFormat = this.format.map.get(name);
        Debug.assert(uniformFormat, `Uniform name [${name}] is not part of the Uniform buffer.`);
        if (uniformFormat) {
            this.setUniform(uniformFormat, value);
        }
    }
    startUpdate(dynamicBindGroup) {
        if (!this.persistent) {
            // allocate memory from dynamic buffer for this frame
            const allocation = this.allocation;
            const oldGpuBuffer = allocation.gpuBuffer;
            this.device.dynamicBuffers.alloc(allocation, this.format.byteSize);
            this.assignStorage(allocation.storage);
            // get info about bind group we can use for this non-persistent UB for this frame
            if (dynamicBindGroup) {
                dynamicBindGroup.bindGroup = allocation.gpuBuffer.getBindGroup(this);
                dynamicBindGroup.offsets[0] = allocation.offset;
            }
            // buffer has changed, update the render version to force bind group to be updated
            if (oldGpuBuffer !== allocation.gpuBuffer) {
                this.renderVersionDirty = this.device.renderVersion;
            }
        }
    }
    endUpdate() {
        if (this.persistent) {
            // Upload the new data
            this.impl.unlock(this);
        } else {
            this.storageFloat32 = null;
            this.storageInt32 = null;
        }
    }
    /**
     * @param {DynamicBindGroup} [dynamicBindGroup] - The function fills in the info about the
     * dynamic bind group for this frame, which uses this uniform buffer. Only used if the uniform
     * buffer is non-persistent. This allows the uniform buffer to be used without having to create
     * a bind group for it. Note that the bind group can only contains this single uniform buffer,
     * and no other resources.
     */ update(dynamicBindGroup) {
        this.startUpdate(dynamicBindGroup);
        // set new values
        const uniforms = this.format.uniforms;
        for(let i = 0; i < uniforms.length; i++){
            const value = uniforms[i].scopeId.value;
            this.setUniform(uniforms[i], value);
        }
        this.endUpdate();
    }
}

const primitive = {
    type: PRIMITIVE_TRISTRIP,
    base: 0,
    baseVertex: 0,
    count: 4,
    indexed: false
};
/**
 * A WebGPU helper class implementing a viewport clear operation. When rendering to a texture,
 * the whole surface can be cleared using loadOp, but if only a viewport needs to be cleared, or if
 * it needs to be cleared later during the rendering, this need to be achieved by rendering a quad.
 * This class renders a full-screen quad, and expects the viewport / scissor to be set up to clip
 * it to only required area.
 *
 * @ignore
 */ class WebgpuClearRenderer {
    constructor(device){
        // shader that can write out color and depth values
        const code = `

            struct ub_mesh {
                color : vec4f,
                depth: f32
            }

            @group(2) @binding(0) var<uniform> ubMesh : ub_mesh;

            var<private> pos : array<vec2f, 4> = array<vec2f, 4>(
                vec2(-1.0, 1.0), vec2(1.0, 1.0),
                vec2(-1.0, -1.0), vec2(1.0, -1.0)
            );

            struct VertexOutput {
                @builtin(position) position : vec4f
            }

            @vertex
            fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
                var output : VertexOutput;
                output.position = vec4(pos[vertexIndex], ubMesh.depth, 1.0);
                return output;
            }

            @fragment
            fn fragmentMain() -> @location(0) vec4f {
                return ubMesh.color;
            }
        `;
        this.shader = new Shader(device, {
            name: 'WebGPUClearRendererShader',
            shaderLanguage: SHADERLANGUAGE_WGSL,
            vshader: code,
            fshader: code
        });
        // uniforms
        this.uniformBuffer = new UniformBuffer(device, new UniformBufferFormat(device, [
            new UniformFormat('color', UNIFORMTYPE_VEC4),
            new UniformFormat('depth', UNIFORMTYPE_FLOAT)
        ]), false);
        this.dynamicBindGroup = new DynamicBindGroup();
        // uniform data
        this.colorData = new Float32Array(4);
    }
    destroy() {
        this.shader.destroy();
        this.shader = null;
        this.uniformBuffer.destroy();
        this.uniformBuffer = null;
    }
    clear(device, renderTarget, options, defaultOptions) {
        options = options || defaultOptions;
        const flags = options.flags ?? defaultOptions.flags;
        if (flags !== 0) {
            DebugGraphics.pushGpuMarker(device, 'CLEAR-RENDERER');
            // dynamic bind group for the UB
            const { uniformBuffer, dynamicBindGroup } = this;
            uniformBuffer.startUpdate(dynamicBindGroup);
            device.setBindGroup(BINDGROUP_MESH_UB, dynamicBindGroup.bindGroup, dynamicBindGroup.offsets);
            // not using mesh bind group
            device.setBindGroup(BINDGROUP_MESH, device.emptyBindGroup);
            // setup clear color
            if (flags & CLEARFLAG_COLOR && (renderTarget.colorBuffer || renderTarget.impl.assignedColorTexture)) {
                const color = options.color ?? defaultOptions.color;
                this.colorData.set(color);
                device.setBlendState(BlendState.NOBLEND);
            } else {
                device.setBlendState(BlendState.NOWRITE);
            }
            uniformBuffer.set('color', this.colorData);
            // setup depth clear
            if (flags & CLEARFLAG_DEPTH && renderTarget.depth) {
                const depth = options.depth ?? defaultOptions.depth;
                uniformBuffer.set('depth', depth);
                device.setDepthState(DepthState.WRITEDEPTH);
            } else {
                uniformBuffer.set('depth', 1);
                device.setDepthState(DepthState.NODEPTH);
            }
            // setup stencil clear
            if (flags & CLEARFLAG_STENCIL && renderTarget.stencil) {
                Debug.warnOnce('ClearRenderer does not support stencil clear at the moment');
            }
            uniformBuffer.endUpdate();
            device.setCullMode(CULLFACE_NONE);
            // render 4 vertices without vertex buffer
            device.setShader(this.shader);
            device.draw(primitive);
            DebugGraphics.popGpuMarker(device);
        }
    }
}

/**
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 * @import { WebgpuShader } from './webgpu-shader.js'
 * @import { WebgpuTexture } from './webgpu-texture.js'
 */ /**
 * A WebGPU helper class implementing texture mipmap generation.
 *
 * @ignore
 */ class WebgpuMipmapRenderer {
    constructor(device){
        this.device = device;
        // Shader that renders a fullscreen textured quad
        const code = `
 
            var<private> pos : array<vec2f, 4> = array<vec2f, 4>(
                vec2(-1.0, 1.0), vec2(1.0, 1.0),
                vec2(-1.0, -1.0), vec2(1.0, -1.0)
            );

            struct VertexOutput {
                @builtin(position) position : vec4f,
                @location(0) texCoord : vec2f
            };

            @vertex
            fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
              var output : VertexOutput;
              output.texCoord = pos[vertexIndex] * vec2f(0.5, -0.5) + vec2f(0.5);
              output.position = vec4f(pos[vertexIndex], 0, 1);
              return output;
            }

            @group(0) @binding(0) var imgSampler : sampler;
            @group(0) @binding(1) var img : texture_2d<f32>;

            @fragment
            fn fragmentMain(@location(0) texCoord : vec2f) -> @location(0) vec4f {
              return textureSample(img, imgSampler, texCoord);
            }
        `;
        this.shader = new Shader(device, {
            name: 'WebGPUMipmapRendererShader',
            shaderLanguage: SHADERLANGUAGE_WGSL,
            vshader: code,
            fshader: code
        });
        // using minified rendering, so that's the only filter mode we need to set.
        this.minSampler = device.wgpu.createSampler({
            minFilter: 'linear'
        });
    }
    destroy() {
        this.shader.destroy();
        this.shader = null;
    }
    /**
     * Generates mipmaps for the specified WebGPU texture.
     *
     * @param {WebgpuTexture} webgpuTexture - The texture to generate mipmaps for.
     */ generate(webgpuTexture) {
        // ignore texture with no mipmaps
        const textureDescr = webgpuTexture.desc;
        if (textureDescr.mipLevelCount <= 1) {
            return;
        }
        // not all types are currently supported
        if (webgpuTexture.texture.volume) {
            Debug.warnOnce('WebGPU mipmap generation is not supported volume texture.', webgpuTexture.texture);
            return;
        }
        const device = this.device;
        const wgpu = device.wgpu;
        /** @type {WebgpuShader} */ const webgpuShader = this.shader.impl;
        const pipeline = wgpu.createRenderPipeline({
            layout: 'auto',
            vertex: {
                module: webgpuShader.getVertexShaderModule(),
                entryPoint: webgpuShader.vertexEntryPoint
            },
            fragment: {
                module: webgpuShader.getFragmentShaderModule(),
                entryPoint: webgpuShader.fragmentEntryPoint,
                targets: [
                    {
                        format: textureDescr.format // use the same format as the texture
                    }
                ]
            },
            primitive: {
                topology: 'triangle-strip'
            }
        });
        DebugHelper.setLabel(pipeline, 'RenderPipeline-MipmapRenderer');
        const texture = webgpuTexture.texture;
        const numFaces = texture.cubemap ? 6 : texture.array ? texture.arrayLength : 1;
        const srcViews = [];
        for(let face = 0; face < numFaces; face++){
            srcViews.push(webgpuTexture.createView({
                dimension: '2d',
                baseMipLevel: 0,
                mipLevelCount: 1,
                baseArrayLayer: face
            }));
        }
        // loop through each mip level and render the previous level's contents into it.
        const commandEncoder = device.getCommandEncoder();
        DebugGraphics.pushGpuMarker(device, 'MIPMAP-RENDERER');
        for(let i = 1; i < textureDescr.mipLevelCount; i++){
            for(let face = 0; face < numFaces; face++){
                const dstView = webgpuTexture.createView({
                    dimension: '2d',
                    baseMipLevel: i,
                    mipLevelCount: 1,
                    baseArrayLayer: face
                });
                const passEncoder = commandEncoder.beginRenderPass({
                    colorAttachments: [
                        {
                            view: dstView,
                            loadOp: 'clear',
                            storeOp: 'store'
                        }
                    ]
                });
                DebugHelper.setLabel(passEncoder, `MipmapRenderer-PassEncoder_${i}`);
                const bindGroup = wgpu.createBindGroup({
                    layout: pipeline.getBindGroupLayout(0),
                    entries: [
                        {
                            binding: 0,
                            resource: this.minSampler
                        },
                        {
                            binding: 1,
                            resource: srcViews[face]
                        }
                    ]
                });
                passEncoder.setPipeline(pipeline);
                passEncoder.setBindGroup(0, bindGroup);
                passEncoder.draw(4);
                passEncoder.end();
                // next iteration
                srcViews[face] = dstView;
            }
        }
        DebugGraphics.popGpuMarker(device);
        // clear invalidated state
        device.pipeline = null;
    }
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 */ /**
 * A base class representing a single per platform buffer.
 *
 * @ignore
 */ class DynamicBuffer {
    constructor(device){
        /**
     * A cache of bind groups for each uniform buffer size, which is used to avoid creating a new
     * bind group for each uniform buffer.
     *
     * @type {Map<number, BindGroup>}
     */ this.bindGroupCache = new Map();
        this.device = device;
        // format of the bind group
        this.bindGroupFormat = new BindGroupFormat(this.device, [
            new BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)
        ]);
    }
    getBindGroup(ub) {
        const ubSize = ub.format.byteSize;
        let bindGroup = this.bindGroupCache.get(ubSize);
        if (!bindGroup) {
            // bind group
            // we pass ub to it, but internally only its size is used
            bindGroup = new BindGroup(this.device, this.bindGroupFormat, ub);
            DebugHelper.setName(bindGroup, `DynamicBuffer-BindGroup_${bindGroup.id}-${ubSize}`);
            bindGroup.update();
            this.bindGroupCache.set(ubSize, bindGroup);
        }
        return bindGroup;
    }
}

class WebgpuDynamicBuffer extends DynamicBuffer {
    constructor(device, size, isStaging){
        super(device), /**
     * @type {GPUBuffer}
     * @private
     */ this.buffer = null, /**
     * CPU access over the whole buffer.
     *
     * @type {ArrayBuffer}
     */ this.mappedRange = null;
        this.buffer = device.wgpu.createBuffer({
            size: size,
            usage: isStaging ? GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC : GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
            mappedAtCreation: isStaging
        });
        if (isStaging) {
            this.onAvailable();
        }
        // staging buffers are not stored in vram, but add them for tracking purposes anyways
        device._vram.ub += size;
        DebugHelper.setLabel(this.buffer, `DynamicBuffer-${isStaging ? 'Staging' : 'Gpu'}`);
    }
    destroy(device) {
        device._vram.ub -= this.buffer.size;
        this.buffer.destroy();
        this.buffer = null;
    }
    /**
     * Called when the staging buffer is mapped for writing.
     */ onAvailable() {
        // map the whole buffer
        this.mappedRange = this.buffer.getMappedRange();
    }
    alloc(offset, size) {
        return new Int32Array(this.mappedRange, offset, size / 4);
    }
}

class WebgpuDynamicBuffers extends DynamicBuffers {
    createBuffer(device, size, isStaging) {
        return new WebgpuDynamicBuffer(device, size, isStaging);
    }
    /**
     * Submit all used buffers to the device.
     */ submit() {
        super.submit();
        // submit all used buffers
        const count = this.usedBuffers.length;
        if (count) {
            const device = this.device;
            const gpuBuffers = this.gpuBuffers;
            // new command encoder, as buffer copies need to be submitted before the currently recorded
            // rendering encoder is submitted
            const commandEncoder = device.wgpu.createCommandEncoder();
            DebugHelper.setLabel(commandEncoder, 'DynamicBuffersSubmit');
            DebugGraphics.pushGpuMarker(device, 'DynamicBuffersSubmit');
            // run this loop backwards to preserve the order of buffers in gpuBuffers array
            for(let i = count - 1; i >= 0; i--){
                const usedBuffer = this.usedBuffers[i];
                const { stagingBuffer, gpuBuffer, offset, size } = usedBuffer;
                // unmap staging buffer (we're done writing to it on CPU)
                const src = stagingBuffer.buffer;
                src.unmap();
                // schedule data copy from staging to gpu buffer
                commandEncoder.copyBufferToBuffer(src, offset, gpuBuffer.buffer, offset, size);
                // gpu buffer can be reused immediately
                gpuBuffers.push(gpuBuffer);
            }
            DebugGraphics.popGpuMarker(device);
            // schedule the command buffer to run before all currently scheduled command buffers
            const cb = commandEncoder.finish();
            DebugHelper.setLabel(cb, 'DynamicBuffers');
            device.addCommandBuffer(cb, true);
            // keep the used staging buffers in the pending list
            for(let i = 0; i < count; i++){
                const stagingBuffer = this.usedBuffers[i].stagingBuffer;
                this.pendingStagingBuffers.push(stagingBuffer);
            }
            this.usedBuffers.length = 0;
        }
    }
    /**
     * Called when all scheduled command buffers are submitted to the device.
     */ onCommandBuffersSubmitted() {
        // map the staging buffers for write to alow them to be reused - this resolves when the CBs
        // using them are done on the GPU
        const count = this.pendingStagingBuffers.length;
        if (count) {
            for(let i = 0; i < count; i++){
                const stagingBuffer = this.pendingStagingBuffers[i];
                stagingBuffer.buffer.mapAsync(GPUMapMode.WRITE).then(()=>{
                    // the buffer can be mapped after the device has been destroyed, so test for that
                    if (this.stagingBuffers) {
                        stagingBuffer.onAvailable();
                        this.stagingBuffers.push(stagingBuffer);
                    }
                });
            }
            this.pendingStagingBuffers.length = 0;
        }
    }
    constructor(...args){
        super(...args), /**
     * Staging buffers which are getting copied over to gpu buffers in the the command buffer waiting
     * to be submitted. When those command buffers are submitted, we can mapAsync these staging
     * buffers for reuse.
     *
     * @type {WebgpuDynamicBuffer[]}
     */ this.pendingStagingBuffers = [];
    }
}

/**
 * Base class of a simple GPU profiler.
 *
 * @ignore
 */ class GpuProfiler {
    loseContext() {
        this.pastFrameAllocations.clear();
    }
    /**
     * True to enable the profiler.
     *
     * @type {boolean}
     */ set enabled(value) {
        this._enableRequest = value;
    }
    get enabled() {
        return this._enableRequest;
    }
    processEnableRequest() {
        if (this._enableRequest !== this._enabled) {
            this._enabled = this._enableRequest;
            if (!this._enabled) {
                this._frameTime = 0;
            }
        }
    }
    request(renderVersion) {
        this.pastFrameAllocations.set(renderVersion, this.frameAllocations);
        this.frameAllocations = [];
    }
    report(renderVersion, timings) {
        if (timings) {
            const allocations = this.pastFrameAllocations.get(renderVersion);
            Debug.assert(allocations.length === timings.length);
            // store frame duration
            if (timings.length > 0) {
                this._frameTime = timings.reduce((sum, t)=>sum + t, 0);
            }
            // log out timings
            if (Tracing.get(TRACEID_GPU_TIMINGS)) {
                Debug.trace(TRACEID_GPU_TIMINGS, `-- GPU timings for frame ${renderVersion} --`);
                let total = 0;
                for(let i = 0; i < allocations.length; ++i){
                    const name = allocations[i];
                    total += timings[i];
                    Debug.trace(TRACEID_GPU_TIMINGS, `${timings[i].toFixed(2)} ms ${name}`);
                }
                Debug.trace(TRACEID_GPU_TIMINGS, `${total.toFixed(2)} ms TOTAL`);
            }
        }
        // remove frame info
        this.pastFrameAllocations.delete(renderVersion);
    }
    /**
     * Allocate a slot for GPU timing during the frame. This slot is valid only for the current
     * frame. This allows multiple timers to be used during the frame, each with a unique name.
     *
     * @param {string} name - The name of the slot.
     * @returns {number} The assigned slot index.
     * @ignore
     */ getSlot(name) {
        const slot = this.frameAllocations.length;
        this.frameAllocations.push(name);
        return slot;
    }
    /**
     * Number of slots allocated during the frame.
     *
     * @ignore
     */ get slotCount() {
        return this.frameAllocations.length;
    }
    constructor(){
        /**
     * Profiling slots allocated for the current frame, storing the names of the slots.
     *
     * @type {string[]}
     * @ignore
     */ this.frameAllocations = [];
        /**
     * Map of past frame allocations, indexed by renderVersion
     *
     * @type {Map<number, string[]>}
     * @ignore
     */ this.pastFrameAllocations = new Map();
        /**
     * True if enabled in the current frame.
     *
     * @private
     */ this._enabled = false;
        /**
     * The enable request for the next frame.
     *
     * @private
     */ this._enableRequest = false;
        /**
     * The time it took to render the last frame on GPU, or 0 if the profiler is not enabled.
     *
     * @private
     */ this._frameTime = 0;
    }
}

/**
 * A wrapper over the GpuQuerySet object, allowing timestamp and occlusion queries. The results
 * are copied back using staging buffers to avoid blocking.
 */ class WebgpuQuerySet {
    constructor(device, isTimestamp, capacity){
        this.stagingBuffers = [];
        this.activeStagingBuffer = null;
        this.device = device;
        this.capacity = capacity;
        this.bytesPerSlot = isTimestamp ? 8 : 4;
        // query set
        const wgpu = device.wgpu;
        this.querySet = wgpu.createQuerySet({
            type: isTimestamp ? 'timestamp' : 'occlusion',
            count: capacity
        });
        DebugHelper.setLabel(this.querySet, `QuerySet-${isTimestamp ? 'Timestamp' : 'Occlusion'}`);
        // gpu buffer for query results GPU writes to
        this.queryBuffer = wgpu.createBuffer({
            size: this.bytesPerSlot * capacity,
            usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
        });
        DebugHelper.setLabel(this.queryBuffer, 'QueryGpuBuffer');
    }
    destroy() {
        this.querySet?.destroy();
        this.querySet = null;
        this.queryBuffer?.destroy();
        this.queryBuffer = null;
        this.activeStagingBuffer = null;
        this.stagingBuffers.forEach((stagingBuffer)=>{
            stagingBuffer.destroy();
        });
        this.stagingBuffers = null;
    }
    getStagingBuffer() {
        let stagingBuffer = this.stagingBuffers.pop();
        if (!stagingBuffer) {
            stagingBuffer = this.device.wgpu.createBuffer({
                size: this.queryBuffer.size,
                usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
            });
            DebugHelper.setLabel(this.queryBuffer, 'QueryStagingBuffer');
        }
        return stagingBuffer;
    }
    resolve(count) {
        const device = this.device;
        const commandEncoder = device.getCommandEncoder();
        // copy times to the gpu buffer
        commandEncoder.resolveQuerySet(this.querySet, 0, count, this.queryBuffer, 0);
        // copy the gpu buffer to the staging buffer
        const activeStagingBuffer = this.getStagingBuffer();
        this.activeStagingBuffer = activeStagingBuffer;
        commandEncoder.copyBufferToBuffer(this.queryBuffer, 0, activeStagingBuffer, 0, this.bytesPerSlot * count);
    }
    request(count, renderVersion) {
        const stagingBuffer = this.activeStagingBuffer;
        this.activeStagingBuffer = null;
        return stagingBuffer.mapAsync(GPUMapMode.READ).then(()=>{
            // timestamps in nanoseconds. Note that this array is valid only till we unmap the staging buffer.
            const srcTimings = new BigInt64Array(stagingBuffer.getMappedRange());
            // convert to ms per sample pair
            const timings = [];
            for(let i = 0; i < count; i++){
                timings.push(Number(srcTimings[i * 2 + 1] - srcTimings[i * 2]) * 0.000001);
            }
            stagingBuffer.unmap();
            this.stagingBuffers?.push(stagingBuffer);
            return {
                renderVersion,
                timings
            };
        });
    }
}

class WebgpuGpuProfiler extends GpuProfiler {
    constructor(device){
        super();
        this.device = device;
        // gpu timing queries
        this.timestampQueriesSet = device.supportsTimestampQuery ? new WebgpuQuerySet(device, true, 512) : null;
    }
    destroy() {
        this.timestampQueriesSet?.destroy();
        this.timestampQueriesSet = null;
    }
    frameStart() {
        this.processEnableRequest();
    }
    frameEnd() {
        if (this._enabled) {
            // schedule command buffer where timestamps are copied to CPU
            this.timestampQueriesSet?.resolve(this.slotCount * 2);
        }
    }
    request() {
        if (this._enabled) {
            // request results
            const renderVersion = this.device.renderVersion;
            this.timestampQueriesSet?.request(this.slotCount, renderVersion).then((results)=>{
                this.report(results.renderVersion, results.timings);
            });
            super.request(renderVersion);
        }
    }
}

/**
 * @import { WebgpuGraphicsDevice } from './webgpu-graphics-device.js'
 * @import { WebgpuShader } from './webgpu-shader.js'
 */ /**
 * A WebGPU helper class implementing custom resolve of multi-sampled textures.
 *
 * @ignore
 */ class WebgpuResolver {
    constructor(device){
        /**
     * Cache of render pipelines for each texture format, to avoid their per frame creation.
     *
     * @type {Map<GPUTextureFormat, GPURenderPipeline>}
     * @private
     */ this.pipelineCache = new Map();
        this.device = device;
        // Shader that renders a fullscreen textured quad and copies the depth value from sample index 0
        // TODO: could handle all sample indices and use min/max as needed
        const code = `
 
            var<private> pos : array<vec2f, 4> = array<vec2f, 4>(
                vec2(-1.0, 1.0), vec2(1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, -1.0)
            );

            struct VertexOutput {
                @builtin(position) position : vec4f,
            };

            @vertex
            fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
              var output : VertexOutput;
              output.position = vec4f(pos[vertexIndex], 0, 1);
              return output;
            }

            @group(0) @binding(0) var img : texture_depth_multisampled_2d;

            @fragment
            fn fragmentMain(@builtin(position) fragColor: vec4f) -> @location(0) vec4f {
                // load th depth value from sample index 0
                var depth = textureLoad(img, vec2i(fragColor.xy), 0u);
                return vec4f(depth, 0.0, 0.0, 0.0);
            }
        `;
        this.shader = new Shader(device, {
            name: 'WebGPUResolverDepthShader',
            shaderLanguage: SHADERLANGUAGE_WGSL,
            vshader: code,
            fshader: code
        });
    }
    destroy() {
        this.shader.destroy();
        this.shader = null;
        this.pipelineCache = null;
    }
    /**
     * @param {GPUTextureFormat} format - Texture format.
     * @returns {GPURenderPipeline} Pipeline for the given format.
     * @private
     */ getPipeline(format) {
        let pipeline = this.pipelineCache.get(format);
        if (!pipeline) {
            pipeline = this.createPipeline(format);
            this.pipelineCache.set(format, pipeline);
        }
        return pipeline;
    }
    /**
     * @param {GPUTextureFormat} format - Texture format.
     * @returns {GPURenderPipeline} Pipeline for the given format.
     * @private
     */ createPipeline(format) {
        /** @type {WebgpuShader} */ const webgpuShader = this.shader.impl;
        const pipeline = this.device.wgpu.createRenderPipeline({
            layout: 'auto',
            vertex: {
                module: webgpuShader.getVertexShaderModule(),
                entryPoint: webgpuShader.vertexEntryPoint
            },
            fragment: {
                module: webgpuShader.getFragmentShaderModule(),
                entryPoint: webgpuShader.fragmentEntryPoint,
                targets: [
                    {
                        format: format
                    }
                ]
            },
            primitive: {
                topology: 'triangle-strip'
            }
        });
        DebugHelper.setLabel(pipeline, `RenderPipeline-DepthResolver-${format}`);
        return pipeline;
    }
    /**
     * @param {GPUCommandEncoder} commandEncoder - Command encoder to use for the resolve.
     * @param {GPUTexture} sourceTexture - Source multi-sampled depth texture to resolve.
     * @param {GPUTexture} destinationTexture - Destination depth texture to resolve to.
     * @private
     */ resolveDepth(commandEncoder, sourceTexture, destinationTexture) {
        Debug.assert(sourceTexture.sampleCount > 1);
        Debug.assert(destinationTexture.sampleCount === 1);
        Debug.assert(sourceTexture.depthOrArrayLayers === destinationTexture.depthOrArrayLayers);
        const device = this.device;
        const wgpu = device.wgpu;
        // pipeline depends on the format
        const pipeline = this.getPipeline(destinationTexture.format);
        DebugGraphics.pushGpuMarker(device, 'DEPTH_RESOLVE-RENDERER');
        const numFaces = sourceTexture.depthOrArrayLayers;
        for(let face = 0; face < numFaces; face++){
            // copy depth only (not stencil)
            const srcView = sourceTexture.createView({
                dimension: '2d',
                aspect: 'depth-only',
                baseMipLevel: 0,
                mipLevelCount: 1,
                baseArrayLayer: face
            });
            const dstView = destinationTexture.createView({
                dimension: '2d',
                baseMipLevel: 0,
                mipLevelCount: 1,
                baseArrayLayer: face
            });
            const passEncoder = commandEncoder.beginRenderPass({
                colorAttachments: [
                    {
                        view: dstView,
                        loadOp: 'clear',
                        storeOp: 'store'
                    }
                ]
            });
            DebugHelper.setLabel(passEncoder, 'DepthResolve-PassEncoder');
            // no need for a sampler when using textureLoad
            const bindGroup = wgpu.createBindGroup({
                layout: pipeline.getBindGroupLayout(0),
                entries: [
                    {
                        binding: 0,
                        resource: srcView
                    }
                ]
            });
            passEncoder.setPipeline(pipeline);
            passEncoder.setBindGroup(0, bindGroup);
            passEncoder.draw(4);
            passEncoder.end();
        }
        DebugGraphics.popGpuMarker(device);
        // clear invalidated state
        device.pipeline = null;
    }
}

/**
 * A WebGPU implementation of the Compute.
 *
 * @ignore
 */ class WebgpuCompute {
    constructor(compute){
        /** @type {UniformBuffer[]} */ this.uniformBuffers = [];
        /** @type {BindGroup} */ this.bindGroup = null;
        this.compute = compute;
        const { device, shader } = compute;
        DebugGraphics.pushGpuMarker(device, `Compute:${compute.name}`);
        // create bind group
        const { computeBindGroupFormat, computeUniformBufferFormats } = shader.impl;
        Debug.assert(computeBindGroupFormat, 'Compute shader does not have computeBindGroupFormat specified', shader);
        // this.bindGroup = new BindGroup(device, computeBindGroupFormat, this.uniformBuffer);
        this.bindGroup = new BindGroup(device, computeBindGroupFormat);
        DebugHelper.setName(this.bindGroup, `Compute-BindGroup_${this.bindGroup.id}`);
        if (computeUniformBufferFormats) {
            for(const name in computeUniformBufferFormats){
                if (computeUniformBufferFormats.hasOwnProperty(name)) {
                    // TODO: investigate implications of using a non-persistent uniform buffer
                    const ub = new UniformBuffer(device, computeUniformBufferFormats[name], true);
                    this.uniformBuffers.push(ub);
                    this.bindGroup.setUniformBuffer(name, ub);
                }
            }
        }
        // pipeline
        this.pipeline = device.computePipeline.get(shader, computeBindGroupFormat);
        DebugGraphics.popGpuMarker(device);
    }
    destroy() {
        this.uniformBuffers.forEach((ub)=>ub.destroy());
        this.uniformBuffers.length = 0;
        this.bindGroup.destroy();
        this.bindGroup = null;
    }
    updateBindGroup() {
        // bind group data
        const { bindGroup } = this;
        bindGroup.updateUniformBuffers();
        bindGroup.update();
    }
    dispatch(x, y, z) {
        // bind group
        const device = this.compute.device;
        device.setBindGroup(0, this.bindGroup);
        // dispatch
        const passEncoder = device.passEncoder;
        passEncoder.setPipeline(this.pipeline);
        passEncoder.dispatchWorkgroups(x, y, z);
    }
}

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 */ let id$7 = 0;
/**
 * A storage buffer represents a memory which both the CPU and the GPU can access. Typically it is
 * used to provide data for compute shader, and to store the result of the computation.
 * Note that this class is only supported on the WebGPU platform.
 *
 * @category Graphics
 */ class StorageBuffer {
    /**
     * Create a new StorageBuffer instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this storage buffer.
     * @param {number} byteSize - The size of the storage buffer in bytes.
     * @param {number} [bufferUsage] - The usage type of the storage buffer. Can be a combination
     * of {@link BUFFERUSAGE_READ}, {@link BUFFERUSAGE_WRITE}, {@link BUFFERUSAGE_COPY_SRC} and
     * {@link BUFFERUSAGE_COPY_DST} flags. This parameter can be omitted if no special usage is
     * required.
     */ constructor(graphicsDevice, byteSize, bufferUsage = 0){
        this.id = id$7++;
        this.device = graphicsDevice;
        this.byteSize = byteSize;
        this.bufferUsage = bufferUsage;
        this.impl = graphicsDevice.createBufferImpl(BUFFERUSAGE_STORAGE | bufferUsage);
        this.impl.allocate(graphicsDevice, byteSize);
        this.device.buffers.push(this);
        this.adjustVramSizeTracking(graphicsDevice._vram, this.byteSize);
    }
    /**
     * Frees resources associated with this storage buffer.
     */ destroy() {
        // stop tracking the buffer
        const device = this.device;
        const idx = device.buffers.indexOf(this);
        if (idx !== -1) {
            device.buffers.splice(idx, 1);
        }
        this.adjustVramSizeTracking(device._vram, -this.byteSize);
        this.impl.destroy(device);
    }
    adjustVramSizeTracking(vram, size) {
        Debug.trace(TRACEID_VRAM_SB, `${this.id} size: ${size} vram.sb: ${vram.sb} => ${vram.sb + size}`);
        vram.sb += size;
    }
    /**
     * Read the contents of a storage buffer.
     *
     * @param {number} [offset] - The byte offset of data to read. Defaults to 0.
     * @param {number} [size] - The byte size of data to read. Defaults to the full size of the
     * buffer minus the offset.
     * @param {ArrayBufferView|null} [data] - Typed array to populate with the data read from the
     * storage buffer. When typed array is supplied, enough space needs to be reserved, otherwise
     * only partial data is copied. If not specified, the data is returned in an Uint8Array.
     * Defaults to null.
     * @param {boolean} [immediate] - If true, the read operation will be executed as soon as
     * possible. This has a performance impact, so it should be used only when necessary. Defaults
     * to false.
     * @returns {Promise<ArrayBufferView>} A promise that resolves with the data read from the
     * storage buffer.
     * @ignore
     */ read(offset = 0, size = this.byteSize, data = null, immediate = false) {
        return this.impl.read(this.device, offset, size, data, immediate);
    }
    /**
     * Issues a write operation of the provided data into a storage buffer.
     *
     * @param {number} bufferOffset - The offset in bytes to start writing to the storage buffer.
     * @param {ArrayBufferView} data - The data to write to the storage buffer.
     * @param {number} dataOffset - Offset in data to begin writing from. Given in elements if data
     * is a TypedArray and bytes otherwise.
     * @param {number} size - Size of content to write from data to buffer. Given in elements if
     * data is a TypedArray and bytes otherwise.
     */ write(bufferOffset = 0, data, dataOffset = 0, size) {
        this.impl.write(this.device, bufferOffset, data, dataOffset, size);
    }
    /**
     * Clear the content of a storage buffer to 0.
     *
     * @param {number} [offset] - The byte offset of data to clear. Defaults to 0.
     * @param {number} [size] - The byte size of data to clear. Defaults to the full size of the
     * buffer minus the offset.
     */ clear(offset = 0, size = this.byteSize) {
        this.impl.clear(this.device, offset, size);
    }
}

/**
 * @import { RenderPass } from '../render-pass.js'
 */ const _uniqueLocations = new Map();
// size of indirect draw entry in bytes, 5 x 32bit
const _indirectEntryByteSize = 5 * 4;
class WebgpuGraphicsDevice extends GraphicsDevice {
    constructor(canvas, options = {}){
        super(canvas, options), /**
     * Object responsible for caching and creation of render pipelines.
     */ this.renderPipeline = new WebgpuRenderPipeline(this), /**
     * Object responsible for caching and creation of compute pipelines.
     */ this.computePipeline = new WebgpuComputePipeline(this), /**
     * Buffer used to store arguments for indirect draw calls.
     *
     * @type {StorageBuffer|null}
     * @private
     */ this._indirectDrawBuffer = null, /**
     * Number of indirect draw slots allocated.
     *
     * @type {number}
     * @private
     */ this._indirectDrawBufferCount = 0, /**
     * Next unused index in indirectDrawBuffer.
     *
     * @type {number}
     * @private
     */ this._indirectDrawNextIndex = 0, /**
     * Render pipeline currently set on the device.
     *
     * @type {GPURenderPipeline|null}
     * @private
     */ this.pipeline = null, /**
     * An array of bind group formats, based on currently assigned bind groups
     *
     * @type {WebgpuBindGroupFormat[]}
     */ this.bindGroupFormats = [], /**
     * Current command buffer encoder.
     *
     * @type {GPUCommandEncoder|null}
     * @private
     */ this.commandEncoder = null, /**
     * Command buffers scheduled for execution on the GPU.
     *
     * @type {GPUCommandBuffer[]}
     * @private
     */ this.commandBuffers = [], /** GLSL to SPIR-V transpiler */ this.glslang = null, /** SPIR-V to WGSL transpiler */ this.twgsl = null;
        options = this.initOptions;
        // alpha defaults to true
        options.alpha = options.alpha ?? true;
        this.backBufferAntialias = options.antialias ?? false;
        this.isWebGPU = true;
        this._deviceType = DEVICETYPE_WEBGPU;
        this.scope.resolve(UNUSED_UNIFORM_NAME).setValue(0);
    }
    /**
     * Destroy the graphics device.
     */ destroy() {
        this.clearRenderer.destroy();
        this.clearRenderer = null;
        this.mipmapRenderer.destroy();
        this.mipmapRenderer = null;
        this.resolver.destroy();
        this.resolver = null;
        super.destroy();
    }
    initDeviceCaps() {
        const limits = this.wgpu?.limits;
        this.limits = limits;
        this.precision = 'highp';
        this.maxPrecision = 'highp';
        this.maxSamples = 4;
        this.maxTextures = 16;
        this.maxTextureSize = limits.maxTextureDimension2D;
        this.maxCubeMapSize = limits.maxTextureDimension2D;
        this.maxVolumeSize = limits.maxTextureDimension3D;
        this.maxColorAttachments = limits.maxColorAttachments;
        this.maxPixelRatio = 1;
        this.maxAnisotropy = 16;
        this.fragmentUniformsCount = limits.maxUniformBufferBindingSize / 16;
        this.vertexUniformsCount = limits.maxUniformBufferBindingSize / 16;
        this.supportsUniformBuffers = true;
        this.supportsAreaLights = true;
        this.supportsGpuParticles = true;
        this.supportsCompute = true;
        this.textureFloatRenderable = true;
        this.textureHalfFloatRenderable = true;
        this.supportsImageBitmap = true;
        // WebGPU currently only supports 1 and 4 samples
        this.samples = this.backBufferAntialias ? 4 : 1;
        // WGSL features
        const wgslFeatures = window.navigator.gpu.wgslLanguageFeatures;
        this.supportsStorageTextureRead = wgslFeatures?.has('readonly_and_readwrite_storage_textures');
        this.initCapsDefines();
    }
    async initWebGpu(glslangUrl, twgslUrl) {
        if (!window.navigator.gpu) {
            throw new Error('Unable to retrieve GPU. Ensure you are using a browser that supports WebGPU rendering.');
        }
        // temporary message to confirm Webgpu is being used
        Debug.log('WebgpuGraphicsDevice initialization ..');
        // Import shader transpilers only if both URLs are provided
        if (glslangUrl && twgslUrl) {
            // build a full URL from a relative or absolute path
            const buildUrl = (srcPath)=>{
                return new URL(srcPath, window.location.href).toString();
            };
            const results = await Promise.all([
                import(/* @vite-ignore */ /* webpackIgnore: true */ `${buildUrl(twgslUrl)}`).then((module)=>twgsl(twgslUrl.replace('.js', '.wasm'))),
                import(/* @vite-ignore */ /* webpackIgnore: true */ `${buildUrl(glslangUrl)}`).then((module)=>module.default())
            ]);
            this.twgsl = results[0];
            this.glslang = results[1];
        }
        // create the device
        return this.createDevice();
    }
    async createDevice() {
        /** @type {GPURequestAdapterOptions} */ const adapterOptions = {
            powerPreference: this.initOptions.powerPreference !== 'default' ? this.initOptions.powerPreference : undefined,
            xrCompatible: this.initOptions.xrCompatible
        };
        /**
         * @type {GPUAdapter}
         * @private
         */ this.gpuAdapter = await window.navigator.gpu.requestAdapter(adapterOptions);
        // request optional features
        const requiredFeatures = [];
        const requireFeature = (feature)=>{
            const supported = this.gpuAdapter.features.has(feature);
            if (supported) {
                requiredFeatures.push(feature);
            }
            return supported;
        };
        this.textureFloatFilterable = requireFeature('float32-filterable');
        this.textureFloatBlendable = requireFeature('float32-blendable');
        this.extCompressedTextureS3TC = requireFeature('texture-compression-bc');
        this.extCompressedTextureS3TCSliced3D = requireFeature('texture-compression-bc-sliced-3d');
        this.extCompressedTextureETC = requireFeature('texture-compression-etc2');
        this.extCompressedTextureASTC = requireFeature('texture-compression-astc');
        this.extCompressedTextureASTCSliced3D = requireFeature('texture-compression-astc-sliced-3d');
        this.supportsTimestampQuery = requireFeature('timestamp-query');
        this.supportsDepthClip = requireFeature('depth-clip-control');
        this.supportsDepth32Stencil = requireFeature('depth32float-stencil8');
        this.supportsIndirectFirstInstance = requireFeature('indirect-first-instance');
        this.supportsShaderF16 = requireFeature('shader-f16');
        this.supportsStorageRGBA8 = requireFeature('bgra8unorm-storage');
        this.textureRG11B10Renderable = requireFeature('rg11b10ufloat-renderable');
        this.supportsClipDistances = requireFeature('clip-distances');
        Debug.log(`WEBGPU features: ${requiredFeatures.join(', ')}`);
        // copy all adapter limits to the requiredLimits object - to created a device with the best feature sets available
        const adapterLimits = this.gpuAdapter?.limits;
        const requiredLimits = {};
        if (adapterLimits) {
            for(const limitName in adapterLimits){
                // skip these as they fail on Windows Chrome and are not part of spec currently
                if (limitName === 'minSubgroupSize' || limitName === 'maxSubgroupSize') {
                    continue;
                }
                requiredLimits[limitName] = adapterLimits[limitName];
            }
        }
        /** @type {GPUDeviceDescriptor} */ const deviceDescr = {
            requiredFeatures,
            requiredLimits,
            defaultQueue: {
                label: 'Default Queue'
            }
        };
        DebugHelper.setLabel(deviceDescr, 'PlayCanvasWebGPUDevice');
        /**
         * @type {GPUDevice}
         * @private
         */ this.wgpu = await this.gpuAdapter.requestDevice(deviceDescr);
        // handle lost device
        this.wgpu.lost?.then(this.handleDeviceLost.bind(this));
        this.initDeviceCaps();
        this.gpuContext = this.canvas.getContext('webgpu');
        // tonemapping, used when the backbuffer is HDR
        let canvasToneMapping = 'standard';
        // pixel format of the framebuffer that is the most efficient one on the system
        let preferredCanvasFormat = window.navigator.gpu.getPreferredCanvasFormat();
        // display format the user asked for
        const displayFormat = this.initOptions.displayFormat;
        // combine requested display format with the preferred format
        this.backBufferFormat = preferredCanvasFormat === 'rgba8unorm' ? displayFormat === DISPLAYFORMAT_LDR_SRGB ? PIXELFORMAT_SRGBA8 : PIXELFORMAT_RGBA8 : displayFormat === DISPLAYFORMAT_LDR_SRGB ? PIXELFORMAT_SBGRA8 : PIXELFORMAT_BGRA8; // (S)BGRA
        // view format for the backbuffer. Backbuffer is always allocated without srgb conversion, and
        // the view we create specifies srgb is needed to handle the conversion.
        this.backBufferViewFormat = displayFormat === DISPLAYFORMAT_LDR_SRGB ? `${preferredCanvasFormat}-srgb` : preferredCanvasFormat;
        // optional HDR display format
        if (displayFormat === DISPLAYFORMAT_HDR && this.textureFloatFilterable) {
            // if supported by the system
            const hdrMediaQuery = window.matchMedia('(dynamic-range: high)');
            if (hdrMediaQuery?.matches) {
                // configure the backbuffer to be 16 bit float
                this.backBufferFormat = PIXELFORMAT_RGBA16F;
                this.backBufferViewFormat = 'rgba16float';
                preferredCanvasFormat = 'rgba16float';
                this.isHdr = true;
                // use extended tonemapping for HDR to avoid clipping
                canvasToneMapping = 'extended';
            }
        }
        /**
         * Configuration of the main colorframebuffer we obtain using getCurrentTexture
         *
         * @type {GPUCanvasConfiguration}
         * @private
         */ this.canvasConfig = {
            device: this.wgpu,
            colorSpace: 'srgb',
            alphaMode: this.initOptions.alpha ? 'premultiplied' : 'opaque',
            // use preferred format for optimal performance on mobile
            format: preferredCanvasFormat,
            toneMapping: {
                mode: canvasToneMapping
            },
            // RENDER_ATTACHMENT is required, COPY_SRC allows scene grab to copy out from it
            usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
            // formats that views created from textures returned by getCurrentTexture may use
            // (this allows us to view the preferred format as srgb)
            viewFormats: displayFormat === DISPLAYFORMAT_LDR_SRGB ? [
                this.backBufferViewFormat
            ] : []
        };
        this.gpuContext?.configure(this.canvasConfig);
        this.createBackbuffer();
        this.clearRenderer = new WebgpuClearRenderer(this);
        this.mipmapRenderer = new WebgpuMipmapRenderer(this);
        this.resolver = new WebgpuResolver(this);
        this.postInit();
        return this;
    }
    async handleDeviceLost(info) {
        // reason is 'destroyed' if we intentionally destroy the device
        if (info.reason !== 'destroyed') {
            Debug.warn(`WebGPU device was lost: ${info.message}, this needs to be handled`);
            super.loseContext(); // 'super' works correctly here
            await this.createDevice(); // Ensure this method is defined in your class
            super.restoreContext(); // 'super' works correctly here
        }
    }
    postInit() {
        super.postInit();
        this.initializeRenderState();
        this.setupPassEncoderDefaults();
        this.gpuProfiler = new WebgpuGpuProfiler(this);
        // init dynamic buffer using 100kB allocation
        this.dynamicBuffers = new WebgpuDynamicBuffers(this, 100 * 1024, this.limits.minUniformBufferOffsetAlignment);
        // empty bind group
        this.emptyBindGroup = new BindGroup(this, new BindGroupFormat(this, []));
        this.emptyBindGroup.update();
    }
    createBackbuffer() {
        this.supportsStencil = this.initOptions.stencil;
        this.backBuffer = new RenderTarget({
            name: 'WebgpuFramebuffer',
            graphicsDevice: this,
            depth: this.initOptions.depth,
            stencil: this.supportsStencil,
            samples: this.samples
        });
        this.backBuffer.impl.isBackbuffer = true;
    }
    frameStart() {
        super.frameStart();
        this.gpuProfiler.frameStart();
        // submit any commands collected before the frame rendering
        this.submit();
        WebgpuDebug.memory(this);
        WebgpuDebug.validate(this);
        // current frame color output buffer (fallback to external backbuffer if not available)
        const outColorBuffer = this.gpuContext?.getCurrentTexture?.() ?? this.externalBackbuffer?.impl.gpuTexture;
        DebugHelper.setLabel(outColorBuffer, `${this.backBuffer.name}`);
        // reallocate framebuffer if dimensions change, to match the output texture
        if (this.backBufferSize.x !== outColorBuffer.width || this.backBufferSize.y !== outColorBuffer.height) {
            this.backBufferSize.set(outColorBuffer.width, outColorBuffer.height);
            this.backBuffer.destroy();
            this.backBuffer = null;
            this.createBackbuffer();
        }
        const rt = this.backBuffer;
        const wrt = rt.impl;
        // assign the format, allowing following init call to use it to allocate matching multisampled buffer
        wrt.setColorAttachment(0, undefined, this.backBufferViewFormat);
        this.initRenderTarget(rt);
        // assign current frame's render texture
        wrt.assignColorTexture(this, outColorBuffer);
        WebgpuDebug.end(this, 'frameStart');
        WebgpuDebug.end(this, 'frameStart');
    }
    frameEnd() {
        super.frameEnd();
        this.gpuProfiler.frameEnd();
        // submit scheduled command buffers
        this.submit();
        if (!this.contextLost) {
            this.gpuProfiler.request();
        }
        this._indirectDrawNextIndex = 0;
    }
    createBufferImpl(usageFlags) {
        return new WebgpuBuffer(usageFlags);
    }
    createUniformBufferImpl(uniformBuffer) {
        return new WebgpuUniformBuffer(uniformBuffer);
    }
    createVertexBufferImpl(vertexBuffer, format, options) {
        return new WebgpuVertexBuffer(vertexBuffer, format, options);
    }
    createIndexBufferImpl(indexBuffer, options) {
        return new WebgpuIndexBuffer(indexBuffer, options);
    }
    createShaderImpl(shader) {
        return new WebgpuShader(shader);
    }
    createTextureImpl(texture) {
        return new WebgpuTexture(texture);
    }
    createRenderTargetImpl(renderTarget) {
        return new WebgpuRenderTarget(renderTarget);
    }
    createBindGroupFormatImpl(bindGroupFormat) {
        return new WebgpuBindGroupFormat(bindGroupFormat);
    }
    createBindGroupImpl(bindGroup) {
        return new WebgpuBindGroup();
    }
    createComputeImpl(compute) {
        return new WebgpuCompute(compute);
    }
    get indirectDrawBuffer() {
        this.allocateIndirectDrawBuffer();
        return this._indirectDrawBuffer;
    }
    allocateIndirectDrawBuffer() {
        // handle reallocation
        if (this._indirectDrawNextIndex === 0 && this._indirectDrawBufferCount < this.maxIndirectDrawCount) {
            this._indirectDrawBuffer?.destroy();
            this._indirectDrawBuffer = null;
        }
        // allocate buffer
        if (this._indirectDrawBuffer === null) {
            this._indirectDrawBuffer = new StorageBuffer(this, this.maxIndirectDrawCount * 4, BUFFERUSAGE_INDIRECT | BUFFERUSAGE_COPY_DST);
            this._indirectDrawBufferCount = this.maxIndirectDrawCount;
        }
    }
    getIndirectDrawSlot() {
        // make sure the buffer is allocated
        this.allocateIndirectDrawBuffer();
        // allocate slot
        const slot = this._indirectDrawNextIndex;
        Debug.assert(slot < this.maxIndirectDrawCount, `Insufficient indirect draw slots per frame (currently ${this._indirectDrawNextIndex}), please adjust GraphicsDevice#maxIndirectDrawCount`);
        this._indirectDrawNextIndex++;
        return slot;
    }
    /**
     * @param {number} index - Index of the bind group slot
     * @param {BindGroup} bindGroup - Bind group to attach
     * @param {number[]} [offsets] - Byte offsets for all uniform buffers in the bind group.
     */ setBindGroup(index, bindGroup, offsets) {
        // TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead
        if (this.passEncoder) {
            // set it on the device
            this.passEncoder.setBindGroup(index, bindGroup.impl.bindGroup, offsets ?? bindGroup.uniformBufferOffsets);
            // store the active formats, used by the pipeline creation
            this.bindGroupFormats[index] = bindGroup.format.impl;
        }
    }
    submitVertexBuffer(vertexBuffer, slot) {
        const format = vertexBuffer.format;
        const { interleaved, elements } = format;
        const elementCount = elements.length;
        const vbBuffer = vertexBuffer.impl.buffer;
        if (interleaved) {
            // for interleaved buffers, we use a single vertex buffer, and attributes are specified using the layout
            this.passEncoder.setVertexBuffer(slot, vbBuffer);
            return 1;
        }
        // non-interleaved - vertex buffer per attribute
        for(let i = 0; i < elementCount; i++){
            this.passEncoder.setVertexBuffer(slot + i, vbBuffer, elements[i].offset);
        }
        return elementCount;
    }
    validateVBLocations(vb0, vb1) {
        // in case of multiple VBs, validate all elements use unique locations
        const validateVB = (vb)=>{
            const { elements } = vb.format;
            for(let i = 0; i < elements.length; i++){
                const name = elements[i].name;
                const location = semanticToLocation[name];
                if (_uniqueLocations.has(location)) {
                    Debug.errorOnce(`Vertex buffer element location ${location} used by [${name}] is already used by element [${_uniqueLocations.get(location)}], while rendering [${DebugGraphics.toString()}]`);
                }
                _uniqueLocations.set(location, name);
            }
        };
        validateVB(vb0);
        validateVB(vb1);
        _uniqueLocations.clear();
    }
    draw(primitive, indexBuffer, numInstances = 1, indirectSlot, first = true, last = true) {
        if (this.shader.ready && !this.shader.failed) {
            WebgpuDebug.validate(this);
            const passEncoder = this.passEncoder;
            Debug.assert(passEncoder);
            let pipeline = this.pipeline;
            // vertex buffers
            const vb0 = this.vertexBuffers[0];
            const vb1 = this.vertexBuffers[1];
            if (first) {
                if (vb0) {
                    const vbSlot = this.submitVertexBuffer(vb0, 0);
                    if (vb1) {
                        Debug.call(()=>this.validateVBLocations(vb0, vb1));
                        this.submitVertexBuffer(vb1, vbSlot);
                    }
                }
                Debug.call(()=>this.validateAttributes(this.shader, vb0?.format, vb1?.format));
                // render pipeline
                pipeline = this.renderPipeline.get(primitive, vb0?.format, vb1?.format, indexBuffer?.format, this.shader, this.renderTarget, this.bindGroupFormats, this.blendState, this.depthState, this.cullMode, this.stencilEnabled, this.stencilFront, this.stencilBack);
                Debug.assert(pipeline);
                if (this.pipeline !== pipeline) {
                    this.pipeline = pipeline;
                    passEncoder.setPipeline(pipeline);
                }
            }
            if (indexBuffer) {
                passEncoder.setIndexBuffer(indexBuffer.impl.buffer, indexBuffer.impl.format);
            }
            // draw
            if (indirectSlot !== undefined) {
                const indirectOffset = indirectSlot * _indirectEntryByteSize;
                const indirectBuffer = this.indirectDrawBuffer.impl.buffer;
                if (indexBuffer) {
                    passEncoder.drawIndexedIndirect(indirectBuffer, indirectOffset);
                } else {
                    passEncoder.drawIndirect(indirectBuffer, indirectOffset);
                }
            } else {
                if (indexBuffer) {
                    passEncoder.drawIndexed(primitive.count, numInstances, primitive.base, primitive.baseVertex ?? 0, 0);
                } else {
                    passEncoder.draw(primitive.count, numInstances, primitive.base, 0);
                }
            }
            WebgpuDebug.end(this, 'Drawing', {
                vb0,
                vb1,
                indexBuffer,
                primitive,
                numInstances,
                pipeline
            });
        }
        if (last) {
            // empty array of vertex buffers
            this.clearVertexBuffer();
            this.pipeline = null;
        }
    }
    setShader(shader, asyncCompile = false) {
        if (shader !== this.shader) {
            this.shader = shader;
            // TODO: we should probably track other stats instead, like pipeline switches
            this._shaderSwitchesPerFrame++;
        }
    }
    setBlendState(blendState) {
        this.blendState.copy(blendState);
    }
    setDepthState(depthState) {
        this.depthState.copy(depthState);
    }
    setStencilState(stencilFront, stencilBack) {
        if (stencilFront || stencilBack) {
            this.stencilEnabled = true;
            this.stencilFront.copy(stencilFront ?? StencilParameters.DEFAULT);
            this.stencilBack.copy(stencilBack ?? StencilParameters.DEFAULT);
            // ref value - based on stencil front
            const ref = this.stencilFront.ref;
            if (this.stencilRef !== ref) {
                this.stencilRef = ref;
                this.passEncoder.setStencilReference(ref);
            }
        } else {
            this.stencilEnabled = false;
        }
    }
    setBlendColor(r, g, b, a) {
        const c = this.blendColor;
        if (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {
            c.set(r, g, b, a);
            this.passEncoder.setBlendConstant(c);
        }
    }
    setCullMode(cullMode) {
        this.cullMode = cullMode;
    }
    setAlphaToCoverage(state) {}
    initializeContextCaches() {
        super.initializeContextCaches();
    }
    /**
     * Set up default values for the render pass encoder.
     */ setupPassEncoderDefaults() {
        this.pipeline = null;
        this.stencilRef = 0;
        this.blendColor.set(0, 0, 0, 0);
    }
    _uploadDirtyTextures() {
        this.textures.forEach((texture)=>{
            if (texture._needsUpload || texture._needsMipmaps) {
                texture.upload();
            }
        });
    }
    setupTimeStampWrites(passDesc, name) {
        if (this.gpuProfiler._enabled) {
            if (this.gpuProfiler.timestampQueriesSet) {
                const slot = this.gpuProfiler.getSlot(name);
                passDesc = passDesc ?? {};
                passDesc.timestampWrites = {
                    querySet: this.gpuProfiler.timestampQueriesSet.querySet,
                    beginningOfPassWriteIndex: slot * 2,
                    endOfPassWriteIndex: slot * 2 + 1
                };
            }
        }
        return passDesc;
    }
    /**
     * Start a render pass.
     *
     * @param {RenderPass} renderPass - The render pass to start.
     * @ignore
     */ startRenderPass(renderPass) {
        // upload textures that need it, to avoid them being uploaded / their mips generated during the pass
        // TODO: this needs a better solution
        this._uploadDirtyTextures();
        WebgpuDebug.internal(this);
        WebgpuDebug.validate(this);
        const rt = renderPass.renderTarget || this.backBuffer;
        this.renderTarget = rt;
        Debug.assert(rt);
        /** @type {WebgpuRenderTarget} */ const wrt = rt.impl;
        // framebuffer is initialized at the start of the frame
        if (rt !== this.backBuffer) {
            this.initRenderTarget(rt);
        }
        // set up clear / store / load settings
        wrt.setupForRenderPass(renderPass, rt);
        const renderPassDesc = wrt.renderPassDescriptor;
        // timestamp
        this.setupTimeStampWrites(renderPassDesc, renderPass.name);
        // start the pass
        const commandEncoder = this.getCommandEncoder();
        this.passEncoder = commandEncoder.beginRenderPass(renderPassDesc);
        this.passEncoder.label = `${renderPass.name}-PassEncoder RT:${rt.name}`;
        // push marker to the passEncoder
        DebugGraphics.pushGpuMarker(this, `Pass:${renderPass.name} RT:${rt.name}`);
        this.setupPassEncoderDefaults();
        // the pass always clears full target
        // TODO: avoid this setting the actual viewport/scissor on webgpu as those are automatically reset to full
        // render target. We just need to update internal state, for the get functionality to return it.
        const { width, height } = rt;
        this.setViewport(0, 0, width, height);
        this.setScissor(0, 0, width, height);
        Debug.assert(!this.insideRenderPass, 'RenderPass cannot be started while inside another render pass.');
        this.insideRenderPass = true;
    }
    /**
     * End a render pass.
     *
     * @param {RenderPass} renderPass - The render pass to end.
     * @ignore
     */ endRenderPass(renderPass) {
        // pop the marker from the passEncoder
        DebugGraphics.popGpuMarker(this);
        // end the render pass
        this.passEncoder.end();
        this.passEncoder = null;
        this.insideRenderPass = false;
        // each render pass can use different number of bind groups
        this.bindGroupFormats.length = 0;
        // resolve depth if needed after the pass has finished
        const target = this.renderTarget;
        if (target) {
            // resolve depth buffer (stencil resolve is not yet implemented)
            if (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) {
                if (renderPass.samples > 1 && target.autoResolve) {
                    const depthAttachment = target.impl.depthAttachment;
                    const destTexture = target.depthBuffer.impl.gpuTexture;
                    if (depthAttachment && destTexture) {
                        this.resolver.resolveDepth(this.commandEncoder, depthAttachment.multisampledDepthBuffer, destTexture);
                    }
                }
            }
        }
        // generate mipmaps using the same command buffer encoder
        for(let i = 0; i < renderPass.colorArrayOps.length; i++){
            const colorOps = renderPass.colorArrayOps[i];
            if (colorOps.genMipmaps) {
                this.mipmapRenderer.generate(renderPass.renderTarget._colorBuffers[i].impl);
            }
        }
        WebgpuDebug.end(this, 'RenderPass', {
            renderPass
        });
        WebgpuDebug.end(this, 'RenderPass', {
            renderPass
        });
    }
    startComputePass(name) {
        WebgpuDebug.internal(this);
        WebgpuDebug.validate(this);
        // clear cached encoder state
        this.pipeline = null;
        // timestamp
        const computePassDesc = this.setupTimeStampWrites(undefined, name);
        // start the pass
        const commandEncoder = this.getCommandEncoder();
        this.passEncoder = commandEncoder.beginComputePass(computePassDesc);
        DebugHelper.setLabel(this.passEncoder, `ComputePass-${name}`);
        Debug.assert(!this.insideRenderPass, 'ComputePass cannot be started while inside another pass.');
        this.insideRenderPass = true;
    }
    endComputePass() {
        // end the compute pass
        this.passEncoder.end();
        this.passEncoder = null;
        this.insideRenderPass = false;
        // each render pass can use different number of bind groups
        this.bindGroupFormats.length = 0;
        WebgpuDebug.end(this, 'ComputePass');
        WebgpuDebug.end(this, 'ComputePass');
    }
    computeDispatch(computes, name = 'Unnamed') {
        this.startComputePass(name);
        // update uniform buffers and bind groups
        for(let i = 0; i < computes.length; i++){
            const compute = computes[i];
            compute.applyParameters();
            compute.impl.updateBindGroup();
        }
        // dispatch
        for(let i = 0; i < computes.length; i++){
            const compute = computes[i];
            compute.impl.dispatch(compute.countX, compute.countY, compute.countZ);
        }
        this.endComputePass();
    }
    getCommandEncoder() {
        // use existing or create new encoder
        let commandEncoder = this.commandEncoder;
        if (!commandEncoder) {
            commandEncoder = this.wgpu.createCommandEncoder();
            DebugHelper.setLabel(commandEncoder, 'CommandEncoder-Shared');
            this.commandEncoder = commandEncoder;
        }
        return commandEncoder;
    }
    endCommandEncoder() {
        Debug.assert(!this.insideRenderPass, 'Attempted to finish GPUCommandEncoder while inside a pass. This will invalidate the current pass encoder and cause "Parent encoder is already finished" validation errors.');
        const { commandEncoder } = this;
        if (commandEncoder) {
            const cb = commandEncoder.finish();
            DebugHelper.setLabel(cb, 'CommandBuffer-Shared');
            this.addCommandBuffer(cb);
            this.commandEncoder = null;
        }
    }
    addCommandBuffer(commandBuffer, front = false) {
        if (front) {
            this.commandBuffers.unshift(commandBuffer);
        } else {
            this.commandBuffers.push(commandBuffer);
        }
    }
    submit() {
        Debug.assert(!this.insideRenderPass, 'Attempted to submit command buffers while inside a pass. This finishes the parent command encoder and invalidates the active pass ("Parent encoder is already finished") .');
        // end the current encoder
        this.endCommandEncoder();
        if (this.commandBuffers.length > 0) {
            // copy dynamic buffers data to the GPU (this schedules the copy CB to run before all other CBs)
            this.dynamicBuffers.submit();
            // trace all scheduled command buffers
            Debug.call(()=>{
                if (this.commandBuffers.length > 0) {
                    Debug.trace(TRACEID_RENDER_QUEUE, `SUBMIT (${this.commandBuffers.length})`);
                    for(let i = 0; i < this.commandBuffers.length; i++){
                        Debug.trace(TRACEID_RENDER_QUEUE, `  CB: ${this.commandBuffers[i].label}`);
                    }
                }
            });
            this.wgpu.queue.submit(this.commandBuffers);
            this.commandBuffers.length = 0;
            // notify dynamic buffers
            this.dynamicBuffers.onCommandBuffersSubmitted();
        }
    }
    clear(options) {
        if (options.flags) {
            this.clearRenderer.clear(this, this.renderTarget, options, this.defaultClearOptions);
        }
    }
    setViewport(x, y, w, h) {
        // TODO: only execute when it changes. Also, the viewport of encoder  matches the rendering attachments,
        // so we can skip this if fullscreen
        // TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead
        if (this.passEncoder) {
            if (!this.renderTarget.flipY) {
                y = this.renderTarget.height - y - h;
            }
            this.vx = x;
            this.vy = y;
            this.vw = w;
            this.vh = h;
            this.passEncoder.setViewport(x, y, w, h, 0, 1);
        }
    }
    setScissor(x, y, w, h) {
        // TODO: only execute when it changes. Also, the viewport of encoder  matches the rendering attachments,
        // so we can skip this if fullscreen
        // TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead
        if (this.passEncoder) {
            if (!this.renderTarget.flipY) {
                y = this.renderTarget.height - y - h;
            }
            this.sx = x;
            this.sy = y;
            this.sw = w;
            this.sh = h;
            this.passEncoder.setScissorRect(x, y, w, h);
        }
    }
    /**
     * Clear the content of a storage buffer to 0.
     *
     * @param {WebgpuBuffer} storageBuffer - The storage buffer.
     * @param {number} [offset] - The offset of data to clear. Defaults to 0.
     * @param {number} [size] - The size of data to clear. Defaults to the full size of the buffer.
     * @ignore
     */ clearStorageBuffer(storageBuffer, offset = 0, size = storageBuffer.byteSize) {
        const commandEncoder = this.getCommandEncoder();
        commandEncoder.clearBuffer(storageBuffer.buffer, offset, size);
    }
    /**
     * Read a content of a storage buffer.
     *
     * @param {WebgpuBuffer} storageBuffer - The storage buffer.
     * @param {number} [offset] - The byte offset of data to read. Defaults to 0.
     * @param {number} [size] - The byte size of data to read. Defaults to the full size of the
     * buffer minus the offset.
     * @param {ArrayBufferView} [data] - Typed array to populate with the data read from the storage
     * buffer. When typed array is supplied, enough space needs to be reserved, otherwise only
     * partial data is copied. If not specified, the data is returned in an Uint8Array. Defaults to
     * null.
     * @param {boolean} [immediate] - If true, the read operation will be executed as soon as
     * possible. This has a performance impact, so it should be used only when necessary. Defaults
     * to false.
     * @returns {Promise<ArrayBufferView>} A promise that resolves with the data read from the storage
     * buffer.
     * @ignore
     */ readStorageBuffer(storageBuffer, offset = 0, size = storageBuffer.byteSize - offset, data = null, immediate = false) {
        // create a temporary staging buffer
        const stagingBuffer = this.createBufferImpl(BUFFERUSAGE_READ | BUFFERUSAGE_COPY_DST);
        stagingBuffer.allocate(this, size);
        const destBuffer = stagingBuffer.buffer;
        // copy the GPU buffer to the staging buffer
        const commandEncoder = this.getCommandEncoder();
        commandEncoder.copyBufferToBuffer(storageBuffer.buffer, offset, destBuffer, 0, size);
        return this.readBuffer(stagingBuffer, size, data, immediate);
    }
    readBuffer(stagingBuffer, size, data = null, immediate = false) {
        const destBuffer = stagingBuffer.buffer;
        // return a promise that resolves with the data
        return new Promise((resolve, reject)=>{
            const read = ()=>{
                destBuffer?.mapAsync(GPUMapMode.READ).then(()=>{
                    // copy data to a buffer
                    data ??= new Uint8Array(size);
                    const copySrc = destBuffer.getMappedRange(0, size);
                    // use the same type as the target
                    const srcType = data.constructor;
                    data.set(new srcType(copySrc));
                    // release staging buffer
                    destBuffer.unmap();
                    stagingBuffer.destroy(this);
                    resolve(data);
                });
            };
            if (immediate) {
                // submit the command buffer immediately
                this.submit();
                read();
            } else {
                // map the buffer during the next event handling cycle, when the command buffer is submitted
                setTimeout(()=>{
                    read();
                });
            }
        });
    }
    /**
     * Issues a write operation of the provided data into a storage buffer.
     *
     * @param {WebgpuBuffer} storageBuffer - The storage buffer.
     * @param {number} bufferOffset - The offset in bytes to start writing to the storage buffer.
     * @param {ArrayBufferView} data - The data to write to the storage buffer.
     * @param {number} dataOffset - Offset in data to begin writing from. Given in elements if data
     * is a TypedArray and bytes otherwise.
     * @param {number} size - Size of content to write from data to buffer. Given in elements if
     * data is a TypedArray and bytes otherwise.
     */ writeStorageBuffer(storageBuffer, bufferOffset = 0, data, dataOffset = 0, size) {
        Debug.assert(storageBuffer.buffer);
        Debug.assert(data);
        this.wgpu.queue.writeBuffer(storageBuffer.buffer, bufferOffset, data, dataOffset, size);
    }
    /**
     * Copies source render target into destination render target. Mostly used by post-effects.
     *
     * @param {RenderTarget} [source] - The source render target. Defaults to frame buffer.
     * @param {RenderTarget} [dest] - The destination render target. Defaults to frame buffer.
     * @param {boolean} [color] - If true, will copy the color buffer. Defaults to false.
     * @param {boolean} [depth] - If true, will copy the depth buffer. Defaults to false.
     * @returns {boolean} True if the copy was successful, false otherwise.
     */ copyRenderTarget(source, dest, color, depth) {
        /** @type {GPUExtent3D} */ const copySize = {
            width: source ? source.width : dest.width,
            height: source ? source.height : dest.height,
            depthOrArrayLayers: 1
        };
        const commandEncoder = this.getCommandEncoder();
        DebugGraphics.pushGpuMarker(this, 'COPY-RT');
        if (color) {
            // read from supplied render target, or from the framebuffer
            /** @type {GPUImageCopyTexture} */ const copySrc = {
                texture: source ? source.colorBuffer.impl.gpuTexture : this.backBuffer.impl.assignedColorTexture,
                mipLevel: 0
            };
            // write to supplied render target, or to the framebuffer
            /** @type {GPUImageCopyTexture} */ const copyDst = {
                texture: dest ? dest.colorBuffer.impl.gpuTexture : this.backBuffer.impl.assignedColorTexture,
                mipLevel: 0
            };
            Debug.assert(copySrc.texture !== null && copyDst.texture !== null);
            commandEncoder.copyTextureToTexture(copySrc, copyDst, copySize);
        }
        if (depth) {
            // read from supplied render target, or from the framebuffer
            const sourceRT = source ? source : this.renderTarget;
            const sourceTexture = sourceRT.impl.depthAttachment.depthTexture;
            if (source.samples > 1) {
                // resolve the depth to a color buffer of destination render target
                const destTexture = dest.colorBuffer.impl.gpuTexture;
                this.resolver.resolveDepth(commandEncoder, sourceTexture, destTexture);
            } else {
                // write to supplied render target, or to the framebuffer
                const destTexture = dest ? dest.depthBuffer.impl.gpuTexture : this.renderTarget.impl.depthAttachment.depthTexture;
                /** @type {GPUImageCopyTexture} */ const copySrc = {
                    texture: sourceTexture,
                    mipLevel: 0
                };
                /** @type {GPUImageCopyTexture} */ const copyDst = {
                    texture: destTexture,
                    mipLevel: 0
                };
                Debug.assert(copySrc.texture !== null && copyDst.texture !== null);
                commandEncoder.copyTextureToTexture(copySrc, copyDst, copySize);
            }
        }
        DebugGraphics.popGpuMarker(this);
        return true;
    }
    pushMarker(name) {
        this.passEncoder?.pushDebugGroup(name);
    }
    popMarker() {
        this.passEncoder?.popDebugGroup();
    }
}

/**
 * A WebGL implementation of the Buffer.
 *
 * @ignore
 */ class WebglBuffer {
    destroy(device) {
        if (this.bufferId) {
            device.gl.deleteBuffer(this.bufferId);
            this.bufferId = null;
        }
    }
    get initialized() {
        return !!this.bufferId;
    }
    loseContext() {
        this.bufferId = null;
    }
    unlock(device, usage, target, storage) {
        const gl = device.gl;
        if (!this.bufferId) {
            let glUsage;
            switch(usage){
                case BUFFER_STATIC:
                    glUsage = gl.STATIC_DRAW;
                    break;
                case BUFFER_DYNAMIC:
                    glUsage = gl.DYNAMIC_DRAW;
                    break;
                case BUFFER_STREAM:
                    glUsage = gl.STREAM_DRAW;
                    break;
                case BUFFER_GPUDYNAMIC:
                    glUsage = gl.DYNAMIC_COPY;
                    break;
            }
            this.bufferId = gl.createBuffer();
            gl.bindBuffer(target, this.bufferId);
            gl.bufferData(target, storage, glUsage);
        } else {
            gl.bindBuffer(target, this.bufferId);
            gl.bufferSubData(target, 0, storage);
        }
    }
    constructor(){
        this.bufferId = null;
    }
}

/**
 * A WebGL implementation of the VertexBuffer.
 *
 * @ignore
 */ class WebglVertexBuffer extends WebglBuffer {
    destroy(device) {
        super.destroy(device);
        // clear up bound vertex buffers
        device.unbindVertexArray();
    }
    loseContext() {
        super.loseContext();
        this.vao = null;
    }
    unlock(vertexBuffer) {
        const device = vertexBuffer.device;
        super.unlock(device, vertexBuffer.usage, device.gl.ARRAY_BUFFER, vertexBuffer.storage);
    }
    constructor(...args){
        super(...args), // vertex array object
        this.vao = null;
    }
}

/**
 * A WebGL implementation of the IndexBuffer.
 *
 * @ignore
 */ class WebglIndexBuffer extends WebglBuffer {
    constructor(indexBuffer){
        super();
        const gl = indexBuffer.device.gl;
        const format = indexBuffer.format;
        if (format === INDEXFORMAT_UINT8) {
            this.glFormat = gl.UNSIGNED_BYTE;
        } else if (format === INDEXFORMAT_UINT16) {
            this.glFormat = gl.UNSIGNED_SHORT;
        } else if (format === INDEXFORMAT_UINT32) {
            this.glFormat = gl.UNSIGNED_INT;
        }
    }
    unlock(indexBuffer) {
        const device = indexBuffer.device;
        super.unlock(device, indexBuffer.usage, device.gl.ELEMENT_ARRAY_BUFFER, indexBuffer.storage);
    }
}

/**
 * @import { GraphicsDevice } from '../graphics-device.js'
 */ /**
 * Representation of a shader uniform.
 */ class WebglShaderInput {
    /**
     * Create a new WebglShaderInput instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this shader input.
     * @param {string} name - The name of the shader input.
     * @param {number} type - The type of the shader input.
     * @param {number | WebGLUniformLocation} locationId - The location id of the shader input.
     */ constructor(graphicsDevice, name, type, locationId){
        // Set the shader uniform location
        this.locationId = locationId;
        // Resolve the ScopeId for the uniform name
        this.scopeId = graphicsDevice.scope.resolve(name);
        // Create the version
        this.version = new Version();
        // custom data type for arrays
        if (name.substring(name.length - 3) === '[0]') {
            switch(type){
                case UNIFORMTYPE_FLOAT:
                    type = UNIFORMTYPE_FLOATARRAY;
                    break;
                case UNIFORMTYPE_INT:
                    type = UNIFORMTYPE_INTARRAY;
                    break;
                case UNIFORMTYPE_UINT:
                    type = UNIFORMTYPE_UINTARRAY;
                    break;
                case UNIFORMTYPE_BOOL:
                    type = UNIFORMTYPE_BOOLARRAY;
                    break;
                case UNIFORMTYPE_VEC2:
                    type = UNIFORMTYPE_VEC2ARRAY;
                    break;
                case UNIFORMTYPE_IVEC2:
                    type = UNIFORMTYPE_IVEC2ARRAY;
                    break;
                case UNIFORMTYPE_UVEC2:
                    type = UNIFORMTYPE_UVEC2ARRAY;
                    break;
                case UNIFORMTYPE_BVEC2:
                    type = UNIFORMTYPE_BVEC2ARRAY;
                    break;
                case UNIFORMTYPE_VEC3:
                    type = UNIFORMTYPE_VEC3ARRAY;
                    break;
                case UNIFORMTYPE_IVEC3:
                    type = UNIFORMTYPE_IVEC3ARRAY;
                    break;
                case UNIFORMTYPE_UVEC3:
                    type = UNIFORMTYPE_UVEC3ARRAY;
                    break;
                case UNIFORMTYPE_BVEC3:
                    type = UNIFORMTYPE_BVEC3ARRAY;
                    break;
                case UNIFORMTYPE_VEC4:
                    type = UNIFORMTYPE_VEC4ARRAY;
                    break;
                case UNIFORMTYPE_IVEC4:
                    type = UNIFORMTYPE_IVEC4ARRAY;
                    break;
                case UNIFORMTYPE_UVEC4:
                    type = UNIFORMTYPE_UVEC4ARRAY;
                    break;
                case UNIFORMTYPE_BVEC4:
                    type = UNIFORMTYPE_BVEC4ARRAY;
                    break;
            }
        }
        // Set the data dataType
        this.dataType = type;
        this.value = [
            null,
            null,
            null,
            null
        ];
        // Array to hold texture unit ids
        this.array = [];
    }
}

/**
 * @import { Shader } from '../shader.js'
 * @import { WebglGraphicsDevice } from './webgl-graphics-device.js'
 */ let _totalCompileTime = 0;
const _vertexShaderBuiltins = new Set([
    'gl_VertexID',
    'gl_InstanceID',
    'gl_DrawID',
    'gl_BaseVertex',
    'gl_BaseInstance'
]);
// class used to hold compiled WebGL vertex or fragment shaders in the device cache
class CompiledShaderCache {
    // destroy all created shaders when the device is destroyed
    destroy(device) {
        this.map.forEach((shader)=>{
            device.gl.deleteShader(shader);
        });
    }
    // just empty the cache when the context is lost
    loseContext(device) {
        this.map.clear();
    }
    constructor(){
        // maps shader source to a compiled WebGL shader
        this.map = new Map();
    }
}
const _vertexShaderCache = new DeviceCache();
const _fragmentShaderCache = new DeviceCache();
/**
 * A WebGL implementation of the Shader.
 *
 * @ignore
 */ class WebglShader {
    constructor(shader){
        this.compileDuration = 0;
        this.init();
        // kick off vertex and fragment shader compilation
        this.compile(shader.device, shader);
        // kick off linking, as this is non-blocking too
        this.link(shader.device, shader);
        // add it to a device list of all shaders
        shader.device.shaders.push(shader);
    }
    /**
     * Free the WebGL resources associated with a shader.
     *
     * @param {Shader} shader - The shader to free.
     */ destroy(shader) {
        if (this.glProgram) {
            shader.device.gl.deleteProgram(this.glProgram);
            this.glProgram = null;
        }
    }
    init() {
        this.uniforms = [];
        this.samplers = [];
        this.attributes = [];
        this.glProgram = null;
        this.glVertexShader = null;
        this.glFragmentShader = null;
    }
    /**
     * Dispose the shader when the context has been lost.
     */ loseContext() {
        this.init();
    }
    /**
     * Restore shader after the context has been obtained.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {Shader} shader - The shader to restore.
     */ restoreContext(device, shader) {
        this.compile(device, shader);
        this.link(device, shader);
    }
    /**
     * Compile shader programs.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {Shader} shader - The shader to compile.
     */ compile(device, shader) {
        const definition = shader.definition;
        this.glVertexShader = this._compileShaderSource(device, definition.vshader, true);
        this.glFragmentShader = this._compileShaderSource(device, definition.fshader, false);
    }
    /**
     * Link shader programs. This is called at a later stage, to allow many shaders to compile in parallel.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {Shader} shader - The shader to compile.
     */ link(device, shader) {
        // if the shader was already linked
        if (this.glProgram) {
            return;
        }
        // if the device is lost, silently ignore
        const gl = device.gl;
        if (gl.isContextLost()) {
            return;
        }
        let startTime = 0;
        Debug.call(()=>{
            this.compileDuration = 0;
            startTime = now();
        });
        const glProgram = gl.createProgram();
        this.glProgram = glProgram;
        gl.attachShader(glProgram, this.glVertexShader);
        gl.attachShader(glProgram, this.glFragmentShader);
        const definition = shader.definition;
        const attrs = definition.attributes;
        if (definition.useTransformFeedback) {
            // Collect all "out_" attributes and use them for output
            const outNames = [];
            for(const attr in attrs){
                if (attrs.hasOwnProperty(attr)) {
                    outNames.push(`out_${attr}`);
                }
            }
            gl.transformFeedbackVaryings(glProgram, outNames, gl.INTERLEAVED_ATTRIBS);
        }
        // map all vertex input attributes to fixed locations
        const locations = {};
        for(const attr in attrs){
            if (attrs.hasOwnProperty(attr)) {
                const semantic = attrs[attr];
                const loc = semanticToLocation[semantic];
                Debug.assert(!locations.hasOwnProperty(loc), `WARNING: Two attributes are mapped to the same location in a shader: ${locations[loc]} and ${attr}`);
                locations[loc] = attr;
                gl.bindAttribLocation(glProgram, loc, attr);
            }
        }
        gl.linkProgram(glProgram);
        Debug.call(()=>{
            this.compileDuration = now() - startTime;
        });
        device._shaderStats.linked++;
        if (definition.tag === SHADERTAG_MATERIAL) {
            device._shaderStats.materialShaders++;
        }
    }
    /**
     * Compiles an individual shader.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {string} src - The shader source code.
     * @param {boolean} isVertexShader - True if the shader is a vertex shader, false if it is a
     * fragment shader.
     * @returns {WebGLShader|null} The compiled shader, or null if the device is lost.
     * @private
     */ _compileShaderSource(device, src, isVertexShader) {
        const gl = device.gl;
        // if the device is lost, silently ignore
        if (gl.isContextLost()) {
            return null;
        }
        // device cache for current device, containing cache of compiled shaders
        const shaderDeviceCache = isVertexShader ? _vertexShaderCache : _fragmentShaderCache;
        const shaderCache = shaderDeviceCache.get(device, ()=>{
            return new CompiledShaderCache();
        });
        // try to get compiled shader from the cache
        let glShader = shaderCache.map.get(src);
        if (!glShader) {
            const startTime = now();
            device.fire('shader:compile:start', {
                timestamp: startTime,
                target: device
            });
            glShader = gl.createShader(isVertexShader ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
            gl.shaderSource(glShader, src);
            gl.compileShader(glShader);
            shaderCache.map.set(src, glShader);
            const endTime = now();
            device.fire('shader:compile:end', {
                timestamp: endTime,
                target: device
            });
            device._shaderStats.compileTime += endTime - startTime;
            if (isVertexShader) {
                device._shaderStats.vsCompiled++;
            } else {
                device._shaderStats.fsCompiled++;
            }
        }
        return glShader;
    }
    /**
     * Link the shader, and extract its attributes and uniform information.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {Shader} shader - The shader to query.
     * @returns {boolean} True if the shader was successfully queried and false otherwise.
     */ finalize(device, shader) {
        // if the device is lost, silently ignore
        const gl = device.gl;
        if (gl.isContextLost()) {
            return true;
        }
        const glProgram = this.glProgram;
        const definition = shader.definition;
        const startTime = now();
        device.fire('shader:link:start', {
            timestamp: startTime,
            target: device
        });
        // this is the main thead blocking part of the shader compilation, time it
        let linkStartTime = 0;
        Debug.call(()=>{
            linkStartTime = now();
        });
        // check the link status of a shader - this is a blocking operation waiting for the shader
        // to finish compiling and linking
        const linkStatus = gl.getProgramParameter(glProgram, gl.LINK_STATUS);
        if (!linkStatus) {
            // Check for compilation errors
            if (!this._isCompiled(device, shader, this.glVertexShader, definition.vshader, 'vertex')) {
                return false;
            }
            if (!this._isCompiled(device, shader, this.glFragmentShader, definition.fshader, 'fragment')) {
                return false;
            }
            const message = `Failed to link shader program. Error: ${gl.getProgramInfoLog(glProgram)}`;
            // log translated shaders
            definition.translatedFrag = gl.getExtension('WEBGL_debug_shaders')?.getTranslatedShaderSource(this.glFragmentShader);
            definition.translatedVert = gl.getExtension('WEBGL_debug_shaders')?.getTranslatedShaderSource(this.glVertexShader);
            console.error(message, definition);
            return false;
        }
        // Query the program for each vertex buffer input (GLSL 'attribute')
        const numAttributes = gl.getProgramParameter(glProgram, gl.ACTIVE_ATTRIBUTES);
        shader.attributes.clear();
        for(let i = 0; i < numAttributes; i++){
            const info = gl.getActiveAttrib(glProgram, i);
            const location = gl.getAttribLocation(glProgram, info.name);
            // a built-in attributes for which we do not need to provide any data
            if (_vertexShaderBuiltins.has(info.name)) {
                continue;
            }
            // Check attributes are correctly linked up
            if (definition.attributes[info.name] === undefined) {
                console.error(`Vertex shader attribute "${info.name}" is not mapped to a semantic in shader definition, shader [${shader.label}]`, shader);
                shader.failed = true;
            } else {
                shader.attributes.set(location, info.name);
            }
        }
        // Query the program for each shader state (GLSL 'uniform')
        const samplerTypes = device._samplerTypes;
        const numUniforms = gl.getProgramParameter(glProgram, gl.ACTIVE_UNIFORMS);
        for(let i = 0; i < numUniforms; i++){
            const info = gl.getActiveUniform(glProgram, i);
            const location = gl.getUniformLocation(glProgram, info.name);
            const shaderInput = new WebglShaderInput(device, info.name, device.pcUniformType[info.type], location);
            if (samplerTypes.has(info.type)) {
                this.samplers.push(shaderInput);
            } else {
                this.uniforms.push(shaderInput);
            }
        }
        shader.ready = true;
        const endTime = now();
        device.fire('shader:link:end', {
            timestamp: endTime,
            target: device
        });
        device._shaderStats.compileTime += endTime - startTime;
        Debug.call(()=>{
            const duration = now() - linkStartTime;
            this.compileDuration += duration;
            _totalCompileTime += this.compileDuration;
            Debug.trace(TRACEID_SHADER_COMPILE, `[id: ${shader.id}] ${shader.name}: ${this.compileDuration.toFixed(1)}ms, TOTAL: ${_totalCompileTime.toFixed(1)}ms`);
        });
        return true;
    }
    /**
     * Check the compilation status of a shader.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {Shader} shader - The shader to query.
     * @param {WebGLShader} glShader - The WebGL shader.
     * @param {string} source - The shader source code.
     * @param {string} shaderType - The shader type. Can be 'vertex' or 'fragment'.
     * @returns {boolean} True if the shader compiled successfully, false otherwise.
     * @private
     */ _isCompiled(device, shader, glShader, source, shaderType) {
        const gl = device.gl;
        if (!gl.getShaderParameter(glShader, gl.COMPILE_STATUS)) {
            const infoLog = gl.getShaderInfoLog(glShader);
            const [code, error] = this._processError(source, infoLog);
            const message = `Failed to compile ${shaderType} shader:\n\n${infoLog}\n${code} while rendering ${DebugGraphics.toString()}`;
            error.shader = shader;
            console.error(message, error);
            return false;
        }
        return true;
    }
    /**
     * Check the linking status of a shader.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @returns {boolean} True if the shader is already linked, false otherwise. Note that unless the
     * device supports the KHR_parallel_shader_compile extension, this will always return true.
     */ isLinked(device) {
        const { extParallelShaderCompile } = device;
        if (extParallelShaderCompile) {
            return device.gl.getProgramParameter(this.glProgram, extParallelShaderCompile.COMPLETION_STATUS_KHR);
        }
        return true;
    }
    /**
     * Truncate the WebGL shader compilation log to just include the error line plus the 5 lines
     * before and after it.
     *
     * @param {string} src - The shader source code.
     * @param {string} infoLog - The info log returned from WebGL on a failed shader compilation.
     * @returns {Array} An array where the first element is the 10 lines of code around the first
     * detected error, and the second element an object storing the error message, line number and
     * complete shader source.
     * @private
     */ _processError(src, infoLog) {
        const error = {};
        let code = '';
        if (src) {
            const lines = src.split('\n');
            let from = 0;
            let to = lines.length;
            // if error is in the code, only show nearby lines instead of whole shader code
            if (infoLog && infoLog.startsWith('ERROR:')) {
                const match = infoLog.match(/^ERROR:\s(\d+):(\d+):\s*(.+)/);
                if (match) {
                    error.message = match[3];
                    error.line = parseInt(match[2], 10);
                    from = Math.max(0, error.line - 6);
                    to = Math.min(lines.length, error.line + 5);
                }
            }
            // Chrome reports shader errors on lines indexed from 1
            for(let i = from; i < to; i++){
                const linePrefix = i + 1 === error.line ? '> ' : '  ';
                code += `${linePrefix}${i + 1}:\t${lines[i]}\n`;
            }
            error.source = src;
        }
        return [
            code,
            error
        ];
    }
}

/**
 * @import { Texture } from '../texture.js'
 * @import { WebglGraphicsDevice } from './webgl-graphics-device.js'
 */ /**
 * Checks that an image's width and height do not exceed the max texture size. If they do, it will
 * be scaled down to that maximum size and returned as a canvas element.
 *
 * @param {HTMLImageElement} image - The image to downsample.
 * @param {number} size - The maximum allowed size of the image.
 * @returns {HTMLImageElement|HTMLCanvasElement} The downsampled image.
 */ function downsampleImage(image, size) {
    const srcW = image.width;
    const srcH = image.height;
    if (srcW > size || srcH > size) {
        const scale = size / Math.max(srcW, srcH);
        const dstW = Math.floor(srcW * scale);
        const dstH = Math.floor(srcH * scale);
        Debug.warn(`Image dimensions larger than max supported texture size of ${size}. Resizing from ${srcW}, ${srcH} to ${dstW}, ${dstH}.`);
        const canvas = document.createElement('canvas');
        canvas.width = dstW;
        canvas.height = dstH;
        const context = canvas.getContext('2d');
        context.drawImage(image, 0, 0, srcW, srcH, 0, 0, dstW, dstH);
        return canvas;
    }
    return image;
}
/**
 * A WebGL implementation of the Texture.
 *
 * @ignore
 */ class WebglTexture {
    constructor(texture){
        this._glTexture = null;
        this.dirtyParameterFlags = 0;
        /** @type {Texture} */ this.texture = texture;
    }
    destroy(device) {
        if (this._glTexture) {
            // Update shadowed texture unit state to remove texture from any units
            for(let i = 0; i < device.textureUnits.length; i++){
                const textureUnit = device.textureUnits[i];
                for(let j = 0; j < textureUnit.length; j++){
                    if (textureUnit[j] === this._glTexture) {
                        textureUnit[j] = null;
                    }
                }
            }
            // release WebGL texture resource
            device.gl.deleteTexture(this._glTexture);
            this._glTexture = null;
        }
    }
    loseContext() {
        this._glTexture = null;
    }
    propertyChanged(flag) {
        this.dirtyParameterFlags |= flag;
    }
    initialize(device, texture) {
        const gl = device.gl;
        this._glTexture = gl.createTexture();
        this._glTarget = texture._cubemap ? gl.TEXTURE_CUBE_MAP : texture._volume ? gl.TEXTURE_3D : texture.array ? gl.TEXTURE_2D_ARRAY : gl.TEXTURE_2D;
        switch(texture._format){
            case PIXELFORMAT_A8:
                this._glFormat = gl.ALPHA;
                this._glInternalFormat = gl.ALPHA;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_L8:
                this._glFormat = gl.LUMINANCE;
                this._glInternalFormat = gl.LUMINANCE;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_LA8:
                this._glFormat = gl.LUMINANCE_ALPHA;
                this._glInternalFormat = gl.LUMINANCE_ALPHA;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_R8:
                this._glFormat = gl.RED;
                this._glInternalFormat = gl.R8;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_RG8:
                this._glFormat = gl.RG;
                this._glInternalFormat = gl.RG8;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_RGB565:
                this._glFormat = gl.RGB;
                this._glInternalFormat = gl.RGB565;
                this._glPixelType = gl.UNSIGNED_SHORT_5_6_5;
                break;
            case PIXELFORMAT_RGBA5551:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = gl.RGB5_A1;
                this._glPixelType = gl.UNSIGNED_SHORT_5_5_5_1;
                break;
            case PIXELFORMAT_RGBA4:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = gl.RGBA4;
                this._glPixelType = gl.UNSIGNED_SHORT_4_4_4_4;
                break;
            case PIXELFORMAT_RGB8:
                this._glFormat = gl.RGB;
                this._glInternalFormat = gl.RGB8;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_RGBA8:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = gl.RGBA8;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_BGRA8:
            case PIXELFORMAT_SBGRA8:
                Debug.error('BGRA8 and SBGRA8 texture formats are not supported by WebGL.');
                break;
            // compressed formats ----
            case PIXELFORMAT_DXT1:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT;
                break;
            case PIXELFORMAT_DXT3:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT;
                break;
            case PIXELFORMAT_DXT5:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT;
                break;
            case PIXELFORMAT_ETC1:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extCompressedTextureETC1.COMPRESSED_RGB_ETC1_WEBGL;
                break;
            case PIXELFORMAT_PVRTC_2BPP_RGB_1:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
                break;
            case PIXELFORMAT_PVRTC_2BPP_RGBA_1:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
                break;
            case PIXELFORMAT_PVRTC_4BPP_RGB_1:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
                break;
            case PIXELFORMAT_PVRTC_4BPP_RGBA_1:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
                break;
            case PIXELFORMAT_ETC2_RGB:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_RGB8_ETC2;
                break;
            case PIXELFORMAT_ETC2_RGBA:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_RGBA8_ETC2_EAC;
                break;
            case PIXELFORMAT_ASTC_4x4:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extCompressedTextureASTC.COMPRESSED_RGBA_ASTC_4x4_KHR;
                break;
            case PIXELFORMAT_ATC_RGB:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extCompressedTextureATC.COMPRESSED_RGB_ATC_WEBGL;
                break;
            case PIXELFORMAT_ATC_RGBA:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extCompressedTextureATC.COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL;
                break;
            case PIXELFORMAT_BC6F:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT;
                break;
            case PIXELFORMAT_BC6UF:
                this._glFormat = gl.RGB;
                this._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT;
                break;
            case PIXELFORMAT_BC7:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_RGBA_BPTC_UNORM_EXT;
                break;
            // compressed sRGB formats ----
            case PIXELFORMAT_DXT1_SRGB:
                this._glFormat = gl.SRGB;
                this._glInternalFormat = device.extCompressedTextureS3TC_SRGB.COMPRESSED_SRGB_S3TC_DXT1_EXT;
                break;
            case PIXELFORMAT_DXT3_SRGBA:
                this._glFormat = gl.SRGB_ALPHA;
                this._glInternalFormat = device.extCompressedTextureS3TC_SRGB.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
                break;
            case PIXELFORMAT_DXT5_SRGBA:
                this._glFormat = gl.SRGB_ALPHA;
                this._glInternalFormat = device.extCompressedTextureS3TC_SRGB.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
                break;
            case PIXELFORMAT_ETC2_SRGB:
                this._glFormat = gl.SRGB;
                this._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_SRGB8_ETC2;
                break;
            case PIXELFORMAT_ETC2_SRGBA:
                this._glFormat = gl.SRGB_ALPHA;
                this._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
                break;
            case PIXELFORMAT_ASTC_4x4_SRGB:
                this._glFormat = gl.SRGB_ALPHA;
                this._glInternalFormat = device.extCompressedTextureASTC.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR;
                break;
            case PIXELFORMAT_BC7_SRGBA:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT;
                break;
            // ------------------
            case PIXELFORMAT_R16F:
                this._glFormat = gl.RED;
                this._glInternalFormat = gl.R16F;
                this._glPixelType = gl.HALF_FLOAT;
                break;
            case PIXELFORMAT_RG16F:
                this._glFormat = gl.RG;
                this._glInternalFormat = gl.RG16F;
                this._glPixelType = gl.HALF_FLOAT;
                break;
            case PIXELFORMAT_RGB16F:
                this._glFormat = gl.RGB;
                this._glInternalFormat = gl.RGB16F;
                this._glPixelType = gl.HALF_FLOAT;
                break;
            case PIXELFORMAT_RGBA16F:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = gl.RGBA16F;
                this._glPixelType = gl.HALF_FLOAT;
                break;
            case PIXELFORMAT_RGB32F:
                this._glFormat = gl.RGB;
                this._glInternalFormat = gl.RGB32F;
                this._glPixelType = gl.FLOAT;
                break;
            case PIXELFORMAT_RGBA32F:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = gl.RGBA32F;
                this._glPixelType = gl.FLOAT;
                break;
            case PIXELFORMAT_R32F:
                this._glFormat = gl.RED;
                this._glInternalFormat = gl.R32F;
                this._glPixelType = gl.FLOAT;
                break;
            case PIXELFORMAT_DEPTH:
                this._glFormat = gl.DEPTH_COMPONENT;
                this._glInternalFormat = gl.DEPTH_COMPONENT32F;
                this._glPixelType = gl.FLOAT;
                break;
            case PIXELFORMAT_DEPTH16:
                this._glFormat = gl.DEPTH_COMPONENT;
                this._glInternalFormat = gl.DEPTH_COMPONENT16;
                this._glPixelType = gl.UNSIGNED_SHORT;
                break;
            case PIXELFORMAT_DEPTHSTENCIL:
                this._glFormat = gl.DEPTH_STENCIL;
                this._glInternalFormat = gl.DEPTH24_STENCIL8;
                this._glPixelType = gl.UNSIGNED_INT_24_8;
                break;
            case PIXELFORMAT_111110F:
                this._glFormat = gl.RGB;
                this._glInternalFormat = gl.R11F_G11F_B10F;
                this._glPixelType = gl.UNSIGNED_INT_10F_11F_11F_REV;
                break;
            case PIXELFORMAT_SRGB8:
                this._glFormat = gl.RGB;
                this._glInternalFormat = gl.SRGB8;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_SRGBA8:
                this._glFormat = gl.RGBA;
                this._glInternalFormat = gl.SRGB8_ALPHA8;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            // Integer texture formats (R)
            case PIXELFORMAT_R8I:
                this._glFormat = gl.RED_INTEGER;
                this._glInternalFormat = gl.R8I;
                this._glPixelType = gl.BYTE;
                break;
            case PIXELFORMAT_R8U:
                this._glFormat = gl.RED_INTEGER;
                this._glInternalFormat = gl.R8UI;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_R16I:
                this._glFormat = gl.RED_INTEGER;
                this._glInternalFormat = gl.R16I;
                this._glPixelType = gl.SHORT;
                break;
            case PIXELFORMAT_R16U:
                this._glFormat = gl.RED_INTEGER;
                this._glInternalFormat = gl.R16UI;
                this._glPixelType = gl.UNSIGNED_SHORT;
                break;
            case PIXELFORMAT_R32I:
                this._glFormat = gl.RED_INTEGER;
                this._glInternalFormat = gl.R32I;
                this._glPixelType = gl.INT;
                break;
            case PIXELFORMAT_R32U:
                this._glFormat = gl.RED_INTEGER;
                this._glInternalFormat = gl.R32UI;
                this._glPixelType = gl.UNSIGNED_INT;
                break;
            // Integer texture formats (RG)
            case PIXELFORMAT_RG8I:
                this._glFormat = gl.RG_INTEGER;
                this._glInternalFormat = gl.RG8I;
                this._glPixelType = gl.BYTE;
                break;
            case PIXELFORMAT_RG8U:
                this._glFormat = gl.RG_INTEGER;
                this._glInternalFormat = gl.RG8UI;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_RG16I:
                this._glFormat = gl.RG_INTEGER;
                this._glInternalFormat = gl.RG16I;
                this._glPixelType = gl.SHORT;
                break;
            case PIXELFORMAT_RG16U:
                this._glFormat = gl.RG_INTEGER;
                this._glInternalFormat = gl.RG16UI;
                this._glPixelType = gl.UNSIGNED_SHORT;
                break;
            case PIXELFORMAT_RG32I:
                this._glFormat = gl.RG_INTEGER;
                this._glInternalFormat = gl.RG32I;
                this._glPixelType = gl.INT;
                break;
            case PIXELFORMAT_RG32U:
                this._glFormat = gl.RG_INTEGER;
                this._glInternalFormat = gl.RG32UI;
                this._glPixelType = gl.UNSIGNED_INT;
                break;
            // Integer texture formats (RGBA)
            case PIXELFORMAT_RGBA8I:
                this._glFormat = gl.RGBA_INTEGER;
                this._glInternalFormat = gl.RGBA8I;
                this._glPixelType = gl.BYTE;
                break;
            case PIXELFORMAT_RGBA8U:
                this._glFormat = gl.RGBA_INTEGER;
                this._glInternalFormat = gl.RGBA8UI;
                this._glPixelType = gl.UNSIGNED_BYTE;
                break;
            case PIXELFORMAT_RGBA16I:
                this._glFormat = gl.RGBA_INTEGER;
                this._glInternalFormat = gl.RGBA16I;
                this._glPixelType = gl.SHORT;
                break;
            case PIXELFORMAT_RGBA16U:
                this._glFormat = gl.RGBA_INTEGER;
                this._glInternalFormat = gl.RGBA16UI;
                this._glPixelType = gl.UNSIGNED_SHORT;
                break;
            case PIXELFORMAT_RGBA32I:
                this._glFormat = gl.RGBA_INTEGER;
                this._glInternalFormat = gl.RGBA32I;
                this._glPixelType = gl.INT;
                break;
            case PIXELFORMAT_RGBA32U:
                this._glFormat = gl.RGBA_INTEGER;
                this._glInternalFormat = gl.RGBA32UI;
                this._glPixelType = gl.UNSIGNED_INT;
                break;
        }
        this._glCreated = false;
    }
    /**
     * @param {WebglGraphicsDevice} device - The device.
     * @param {Texture} texture - The texture to update.
     */ upload(device, texture) {
        Debug.assert(texture.device, 'Attempting to use a texture that has been destroyed.', texture);
        const gl = device.gl;
        if (!texture._needsUpload && (texture._needsMipmapsUpload && texture._mipmapsUploaded || !texture.pot)) {
            return;
        }
        let mipLevel = 0;
        let mipObject;
        let resMult;
        const requiredMipLevels = texture.numLevels;
        if (texture.array && !this._glCreated) {
            // for texture arrays we reserve the space in advance
            gl.texStorage3D(gl.TEXTURE_2D_ARRAY, requiredMipLevels, this._glInternalFormat, texture._width, texture._height, texture._arrayLength);
        }
        // Upload all existing mip levels. Initialize 0 mip anyway.
        while(texture._levels[mipLevel] || mipLevel === 0){
            if (!texture._needsUpload && mipLevel === 0) {
                mipLevel++;
                continue;
            } else if (mipLevel && (!texture._needsMipmapsUpload || !texture._mipmaps)) {
                break;
            }
            mipObject = texture._levels[mipLevel];
            resMult = 1 / Math.pow(2, mipLevel);
            if (mipLevel === 1 && !texture._compressed && !texture._integerFormat && texture._levels.length < requiredMipLevels) {
                // We have more than one mip levels we want to assign, but we need all mips to make
                // the texture complete. Therefore first generate all mip chain from 0, then assign custom mips.
                // (this implies the call to _completePartialMipLevels above was unsuccessful)
                gl.generateMipmap(this._glTarget);
                texture._mipmapsUploaded = true;
            }
            if (texture._cubemap) {
                // ----- CUBEMAP -----
                let face;
                if (device._isBrowserInterface(mipObject[0])) {
                    // Upload the image, canvas or video
                    for(face = 0; face < 6; face++){
                        if (!texture._levelsUpdated[0][face]) {
                            continue;
                        }
                        let src = mipObject[face];
                        // Downsize images that are too large to be used as cube maps
                        if (device._isImageBrowserInterface(src)) {
                            if (src.width > device.maxCubeMapSize || src.height > device.maxCubeMapSize) {
                                src = downsampleImage(src, device.maxCubeMapSize);
                                if (mipLevel === 0) {
                                    texture._width = src.width;
                                    texture._height = src.height;
                                }
                            }
                        }
                        device.setUnpackFlipY(false);
                        device.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);
                        if (this._glCreated) {
                            gl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, 0, 0, this._glFormat, this._glPixelType, src);
                        } else {
                            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, this._glInternalFormat, this._glFormat, this._glPixelType, src);
                        }
                    }
                } else {
                    // Upload the byte array
                    resMult = 1 / Math.pow(2, mipLevel);
                    for(face = 0; face < 6; face++){
                        if (!texture._levelsUpdated[0][face]) {
                            continue;
                        }
                        const texData = mipObject[face];
                        if (texture._compressed) {
                            if (this._glCreated && texData) {
                                gl.compressedTexSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, 0, 0, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), this._glInternalFormat, texData);
                            } else {
                                gl.compressedTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, texData);
                            }
                        } else {
                            device.setUnpackFlipY(false);
                            device.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);
                            if (this._glCreated && texData) {
                                gl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, 0, 0, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), this._glFormat, this._glPixelType, texData);
                            } else {
                                gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, this._glFormat, this._glPixelType, texData);
                            }
                        }
                    }
                }
            } else if (texture._volume) {
                // ----- 3D -----
                // Image/canvas/video not supported (yet?)
                // Upload the byte array
                if (texture._compressed) {
                    gl.compressedTexImage3D(gl.TEXTURE_3D, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), Math.max(texture._depth * resMult, 1), 0, mipObject);
                } else {
                    device.setUnpackFlipY(false);
                    device.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);
                    gl.texImage3D(gl.TEXTURE_3D, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), Math.max(texture._depth * resMult, 1), 0, this._glFormat, this._glPixelType, mipObject);
                }
            } else if (texture.array && typeof mipObject === 'object') {
                if (texture._arrayLength === mipObject.length) {
                    if (texture._compressed) {
                        for(let index = 0; index < texture._arrayLength; index++){
                            gl.compressedTexSubImage3D(gl.TEXTURE_2D_ARRAY, mipLevel, 0, 0, index, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), 1, this._glInternalFormat, mipObject[index]);
                        }
                    } else {
                        for(let index = 0; index < texture._arrayLength; index++){
                            gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, mipLevel, 0, 0, index, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), 1, this._glFormat, this._glPixelType, mipObject[index]);
                        }
                    }
                }
            } else {
                // ----- 2D -----
                if (device._isBrowserInterface(mipObject)) {
                    // Downsize images that are too large to be used as textures
                    if (device._isImageBrowserInterface(mipObject)) {
                        if (mipObject.width > device.maxTextureSize || mipObject.height > device.maxTextureSize) {
                            mipObject = downsampleImage(mipObject, device.maxTextureSize);
                            if (mipLevel === 0) {
                                texture._width = mipObject.width;
                                texture._height = mipObject.height;
                            }
                        }
                    }
                    const w = mipObject.width || mipObject.videoWidth;
                    const h = mipObject.height || mipObject.videoHeight;
                    // Upload the image, canvas or video
                    device.setUnpackFlipY(texture._flipY);
                    device.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);
                    // TEMP: disable fast path for video updates until
                    // https://bugs.chromium.org/p/chromium/issues/detail?id=1511207 is resolved
                    if (this._glCreated && texture._width === w && texture._height === h && !device._isImageVideoInterface(mipObject)) {
                        gl.texSubImage2D(gl.TEXTURE_2D, mipLevel, 0, 0, this._glFormat, this._glPixelType, mipObject);
                    } else {
                        gl.texImage2D(gl.TEXTURE_2D, mipLevel, this._glInternalFormat, this._glFormat, this._glPixelType, mipObject);
                        if (mipLevel === 0) {
                            texture._width = w;
                            texture._height = h;
                        }
                    }
                } else {
                    // Upload the byte array
                    resMult = 1 / Math.pow(2, mipLevel);
                    if (texture._compressed) {
                        if (this._glCreated && mipObject) {
                            gl.compressedTexSubImage2D(gl.TEXTURE_2D, mipLevel, 0, 0, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), this._glInternalFormat, mipObject);
                        } else {
                            gl.compressedTexImage2D(gl.TEXTURE_2D, mipLevel, this._glInternalFormat, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), 0, mipObject);
                        }
                    } else {
                        device.setUnpackFlipY(false);
                        device.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);
                        if (this._glCreated && mipObject) {
                            gl.texSubImage2D(gl.TEXTURE_2D, mipLevel, 0, 0, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), this._glFormat, this._glPixelType, mipObject);
                        } else {
                            gl.texImage2D(gl.TEXTURE_2D, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, this._glFormat, this._glPixelType, mipObject);
                        }
                    }
                }
                if (mipLevel === 0) {
                    texture._mipmapsUploaded = false;
                } else {
                    texture._mipmapsUploaded = true;
                }
            }
            mipLevel++;
        }
        if (texture._needsUpload) {
            if (texture._cubemap) {
                for(let i = 0; i < 6; i++){
                    texture._levelsUpdated[0][i] = false;
                }
            } else {
                texture._levelsUpdated[0] = false;
            }
        }
        if (!texture._compressed && !texture._integerFormat && texture._mipmaps && texture._needsMipmapsUpload && texture._levels.length === 1) {
            gl.generateMipmap(this._glTarget);
            texture._mipmapsUploaded = true;
        }
        // update vram stats
        if (texture._gpuSize) {
            texture.adjustVramSizeTracking(device._vram, -texture._gpuSize);
        }
        texture._gpuSize = texture.gpuSize;
        texture.adjustVramSizeTracking(device._vram, texture._gpuSize);
        this._glCreated = true;
    }
    /**
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {Texture} texture - The texture.
     */ uploadImmediate(device, texture) {
        if (texture._needsUpload || texture._needsMipmapsUpload) {
            // this uploads the texture as well
            device.setTexture(texture, 0);
            texture._needsUpload = false;
            texture._needsMipmapsUpload = false;
        }
    }
    read(x, y, width, height, options) {
        const texture = this.texture;
        /** @type {WebglGraphicsDevice} */ const device = texture.device;
        return device.readTextureAsync(texture, x, y, width, height, options);
    }
    write(x, y, width, height, data) {
        const { texture } = this;
        const { device } = texture;
        // ensure texture is created and bound
        device.setTexture(texture, 0);
        return device.writeTextureAsync(texture, x, y, width, height, data);
    }
}

/**
 * @import { RenderTarget } from '../render-target.js'
 * @import { WebglGraphicsDevice } from './webgl-graphics-device.js'
 */ /**
 * A private class representing a pair of framebuffers, when MSAA is used.
 *
 * @ignore
 */ class FramebufferPair {
    /**
     * @param {WebGLFramebuffer} msaaFB - Multi-sampled rendering framebuffer.
     * @param {WebGLFramebuffer} resolveFB - Single-sampled resolve framebuffer.
     */ constructor(msaaFB, resolveFB){
        this.msaaFB = msaaFB;
        this.resolveFB = resolveFB;
    }
    /**
     * @param {WebGLRenderingContext} gl - The WebGL rendering context.
     */ destroy(gl) {
        if (this.msaaFB) {
            gl.deleteRenderbuffer(this.msaaFB);
            this.msaaFB = null;
        }
        if (this.resolveFB) {
            gl.deleteRenderbuffer(this.resolveFB);
            this.resolveFB = null;
        }
    }
}
/**
 * A WebGL implementation of the RenderTarget.
 *
 * @ignore
 */ class WebglRenderTarget {
    destroy(device) {
        const gl = device.gl;
        this._isInitialized = false;
        if (this._glFrameBuffer) {
            if (this._glFrameBuffer !== this.suppliedColorFramebuffer) {
                gl.deleteFramebuffer(this._glFrameBuffer);
            }
            this._glFrameBuffer = null;
        }
        if (this._glDepthBuffer) {
            gl.deleteRenderbuffer(this._glDepthBuffer);
            this._glDepthBuffer = null;
        }
        if (this._glResolveFrameBuffer) {
            if (this._glResolveFrameBuffer !== this.suppliedColorFramebuffer) {
                gl.deleteFramebuffer(this._glResolveFrameBuffer);
            }
            this._glResolveFrameBuffer = null;
        }
        this._glMsaaColorBuffers.forEach((buffer)=>{
            gl.deleteRenderbuffer(buffer);
        });
        this._glMsaaColorBuffers.length = 0;
        this.colorMrtFramebuffers?.forEach((framebuffer)=>{
            framebuffer.destroy(gl);
        });
        this.colorMrtFramebuffers = null;
        if (this._glMsaaDepthBuffer) {
            this._glMsaaDepthBuffer = null;
            // release reference to the texture, as its ref-counted
            if (this.msaaDepthBufferKey) {
                getMultisampledTextureCache(device).release(this.msaaDepthBufferKey);
            }
        }
        this.suppliedColorFramebuffer = undefined;
    }
    get initialized() {
        return this._isInitialized;
    }
    init(device, target) {
        const gl = device.gl;
        Debug.assert(!this._isInitialized, 'Render target already initialized.');
        this._isInitialized = true;
        const buffers = [];
        if (this.suppliedColorFramebuffer !== undefined) {
            this._glFrameBuffer = this.suppliedColorFramebuffer;
        } else {
            Debug.call(()=>{
                if (target.width <= 0 || target.height <= 0) {
                    Debug.warnOnce(`Invalid render target size: ${target.width} x ${target.height}`, target);
                }
            });
            // ##### Create main FBO #####
            this._glFrameBuffer = gl.createFramebuffer();
            device.setFramebuffer(this._glFrameBuffer);
            // --- Init the provided color buffer (optional) ---
            const colorBufferCount = target._colorBuffers?.length ?? 0;
            const attachmentBaseConstant = gl.COLOR_ATTACHMENT0;
            for(let i = 0; i < colorBufferCount; ++i){
                const colorBuffer = target.getColorBuffer(i);
                if (colorBuffer) {
                    if (!colorBuffer.impl._glTexture) {
                        // Clamp the render buffer size to the maximum supported by the device
                        colorBuffer._width = Math.min(colorBuffer.width, device.maxRenderBufferSize);
                        colorBuffer._height = Math.min(colorBuffer.height, device.maxRenderBufferSize);
                        device.setTexture(colorBuffer, 0);
                    }
                    // Attach the color buffer
                    gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentBaseConstant + i, colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, target.mipLevel);
                    buffers.push(attachmentBaseConstant + i);
                }
            }
            gl.drawBuffers(buffers);
            const depthBuffer = target._depthBuffer;
            if (depthBuffer || target._depth) {
                const attachmentPoint = target._stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
                if (depthBuffer) {
                    // --- Init the optionally provided depth/stencil buffer ---
                    if (!depthBuffer.impl._glTexture) {
                        // Clamp the render buffer size to the maximum supported by the device
                        depthBuffer._width = Math.min(depthBuffer.width, device.maxRenderBufferSize);
                        depthBuffer._height = Math.min(depthBuffer.height, device.maxRenderBufferSize);
                        device.setTexture(depthBuffer, 0);
                    }
                    // Attach
                    gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, depthBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, target._depthBuffer.impl._glTexture, target.mipLevel);
                } else {
                    // --- Init a new depth/stencil buffer (optional) ---
                    // if device is a MSAA RT, and no buffer to resolve to, skip creating non-MSAA depth
                    const willRenderMsaa = target._samples > 1;
                    if (!willRenderMsaa) {
                        if (!this._glDepthBuffer) {
                            this._glDepthBuffer = gl.createRenderbuffer();
                        }
                        const internalFormat = target._stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT32F;
                        gl.bindRenderbuffer(gl.RENDERBUFFER, this._glDepthBuffer);
                        gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, target.width, target.height);
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentPoint, gl.RENDERBUFFER, this._glDepthBuffer);
                        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
                    }
                }
            }
            Debug.call(()=>this._checkFbo(device, target));
        }
        // ##### Create MSAA FBO #####
        if (target._samples > 1) {
            // Use previous FBO for resolves
            this._glResolveFrameBuffer = this._glFrameBuffer;
            // Actual FBO will be MSAA
            this._glFrameBuffer = gl.createFramebuffer();
            device.setFramebuffer(this._glFrameBuffer);
            // Create an optional MSAA color buffers
            const colorBufferCount = target._colorBuffers?.length ?? 0;
            if (this.suppliedColorFramebuffer !== undefined) {
                const buffer = gl.createRenderbuffer();
                this._glMsaaColorBuffers.push(buffer);
                const internalFormat = device.backBufferFormat === PIXELFORMAT_RGBA8 ? gl.RGBA8 : gl.RGB8;
                gl.bindRenderbuffer(gl.RENDERBUFFER, buffer);
                gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, internalFormat, target.width, target.height);
                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, buffer);
            } else {
                for(let i = 0; i < colorBufferCount; ++i){
                    const colorBuffer = target.getColorBuffer(i);
                    if (colorBuffer) {
                        const buffer = gl.createRenderbuffer();
                        this._glMsaaColorBuffers.push(buffer);
                        gl.bindRenderbuffer(gl.RENDERBUFFER, buffer);
                        gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, colorBuffer.impl._glInternalFormat, target.width, target.height);
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, buffer);
                    }
                }
            }
            // Optionally add a MSAA depth/stencil buffer
            if (target._depth) {
                Debug.assert(!this._glMsaaDepthBuffer);
                const internalFormat = target._stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT32F;
                const attachmentPoint = target._stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
                // for user specified depth buffer, shader multi-sampled depth buffer instead of allocating a new one
                let key;
                const depthBuffer = target._depthBuffer;
                if (depthBuffer) {
                    // key for matching multi-sampled depth buffer
                    key = `${depthBuffer.id}:${target.width}:${target.height}:${target._samples}:${internalFormat}:${attachmentPoint}`;
                    // check if we have already allocated a multi-sampled depth buffer for the depth buffer
                    this._glMsaaDepthBuffer = getMultisampledTextureCache(device).get(key); // this incRefs it if found
                }
                // if we don't have a multi-sampled depth buffer, create one
                if (!this._glMsaaDepthBuffer) {
                    this._glMsaaDepthBuffer = gl.createRenderbuffer();
                    gl.bindRenderbuffer(gl.RENDERBUFFER, this._glMsaaDepthBuffer);
                    gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, internalFormat, target.width, target.height);
                    // add 'destroy' method to the renderbuffer, allowing it to be destroyed by the cache
                    this._glMsaaDepthBuffer.destroy = function() {
                        gl.deleteRenderbuffer(this);
                    };
                    // store it in the cache
                    if (depthBuffer) {
                        getMultisampledTextureCache(device).set(key, this._glMsaaDepthBuffer);
                    }
                }
                // store the key needed to release the depth buffer from the cache
                this.msaaDepthBufferKey = key;
                // add the depth buffer to the FBO
                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentPoint, gl.RENDERBUFFER, this._glMsaaDepthBuffer);
            }
            Debug.call(()=>this._checkFbo(device, target, 'MSAA'));
            if (colorBufferCount > 1) {
                // create framebuffers allowing us to individually resolve each color buffer
                this._createMsaaMrtFramebuffers(device, target, colorBufferCount);
                // restore rendering back to the main framebuffer
                device.setFramebuffer(this._glFrameBuffer);
                gl.drawBuffers(buffers);
            }
        }
    }
    _createMsaaMrtFramebuffers(device, target, colorBufferCount) {
        const gl = device.gl;
        this.colorMrtFramebuffers = [];
        for(let i = 0; i < colorBufferCount; ++i){
            const colorBuffer = target.getColorBuffer(i);
            // src
            const srcFramebuffer = gl.createFramebuffer();
            device.setFramebuffer(srcFramebuffer);
            const buffer = this._glMsaaColorBuffers[i];
            gl.bindRenderbuffer(gl.RENDERBUFFER, buffer);
            gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, colorBuffer.impl._glInternalFormat, target.width, target.height);
            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, buffer);
            gl.drawBuffers([
                gl.COLOR_ATTACHMENT0
            ]);
            Debug.call(()=>this._checkFbo(device, target, `MSAA-MRT-src${i}`));
            // dst
            const dstFramebuffer = gl.createFramebuffer();
            device.setFramebuffer(dstFramebuffer);
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, 0);
            this.colorMrtFramebuffers[i] = new FramebufferPair(srcFramebuffer, dstFramebuffer);
            Debug.call(()=>this._checkFbo(device, target, `MSAA-MRT-dst${i}`));
        }
    }
    /**
     * Checks the completeness status of the currently bound WebGLFramebuffer object.
     *
     * @param {WebglGraphicsDevice} device - The graphics device.
     * @param {RenderTarget} target - The render target.
     * @param {string} [type] - An optional type string to append to the error message.
     * @private
     */ _checkFbo(device, target, type = '') {
        const gl = device.gl;
        const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
        let errorCode;
        switch(status){
            case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
                errorCode = 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT';
                break;
            case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
                errorCode = 'FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT';
                break;
            case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
                errorCode = 'FRAMEBUFFER_INCOMPLETE_DIMENSIONS';
                break;
            case gl.FRAMEBUFFER_UNSUPPORTED:
                errorCode = 'FRAMEBUFFER_UNSUPPORTED';
                break;
        }
        Debug.assert(!errorCode, `Framebuffer creation failed with error code ${errorCode}, render target: ${target.name} ${type}`, target);
    }
    loseContext() {
        this._glFrameBuffer = null;
        this._glDepthBuffer = null;
        this._glResolveFrameBuffer = null;
        this._glMsaaColorBuffers.length = 0;
        this._glMsaaDepthBuffer = null;
        this.msaaDepthBufferKey = undefined;
        this.colorMrtFramebuffers = null;
        this.suppliedColorFramebuffer = undefined;
        this._isInitialized = false;
    }
    internalResolve(device, src, dst, target, mask) {
        Debug.assert(src !== dst, 'Source and destination framebuffers must be different when blitting.');
        // blit is affected by scissor test, so make it full size
        device.setScissor(0, 0, target.width, target.height);
        const gl = device.gl;
        gl.bindFramebuffer(gl.READ_FRAMEBUFFER, src);
        gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst);
        gl.blitFramebuffer(0, 0, target.width, target.height, 0, 0, target.width, target.height, mask, gl.NEAREST);
    }
    resolve(device, target, color, depth) {
        const gl = device.gl;
        // if MRT is used, we need to resolve each buffer individually
        if (this.colorMrtFramebuffers) {
            // color
            if (color) {
                for(let i = 0; i < this.colorMrtFramebuffers.length; i++){
                    const fbPair = this.colorMrtFramebuffers[i];
                    DebugGraphics.pushGpuMarker(device, `RESOLVE-MRT${i}`);
                    this.internalResolve(device, fbPair.msaaFB, fbPair.resolveFB, target, gl.COLOR_BUFFER_BIT);
                    DebugGraphics.popGpuMarker(device);
                }
            }
            // depth
            if (depth) {
                DebugGraphics.pushGpuMarker(device, 'RESOLVE-MRT-DEPTH');
                this.internalResolve(device, this._glFrameBuffer, this._glResolveFrameBuffer, target, gl.DEPTH_BUFFER_BIT);
                DebugGraphics.popGpuMarker(device);
            }
        } else {
            DebugGraphics.pushGpuMarker(device, 'RESOLVE');
            this.internalResolve(device, this._glFrameBuffer, this._glResolveFrameBuffer, target, (color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0));
            DebugGraphics.popGpuMarker(device);
        }
        gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
    }
    constructor(){
        this._glFrameBuffer = null;
        this._glDepthBuffer = null;
        this._glResolveFrameBuffer = null;
        /**
     * A list of framebuffers created When MSAA and MRT are used together, one for each color buffer.
     * This allows color buffers to be resolved separately.
     *
     * @type {FramebufferPair[]}
     */ this.colorMrtFramebuffers = null;
        this._glMsaaColorBuffers = [];
        this._glMsaaDepthBuffer = null;
        this._isInitialized = false;
    }
}

/**
 * Class holding information about the queries for a single frame.
 */ class FrameQueriesInfo {
    destroy(gl) {
        this.queries.forEach((query)=>gl.deleteQuery(query));
        this.queries = null;
    }
    constructor(){
        /**
     * The queries for the frame.
     *
     * @type {WebGLQuery[]}
     */ this.queries = [];
    }
}
class WebglGpuProfiler extends GpuProfiler {
    constructor(device){
        super(), /**
     * The pool of unused queries.
     *
     * @type {WebGLQuery[]}
     */ this.freeQueries = [], /**
     * The pool of queries for the current frame.
     *
     * @type {WebGLQuery[]}
     */ this.frameQueries = [], /**
     * A list of queries from the previous frames which are waiting for results.
     *
     * @type {FrameQueriesInfo[]}
     */ this.previousFrameQueries = [], /**
     * Temporary array to storing the timings.
     *
     * @type {number[]}
     */ this.timings = [];
        this.device = device;
        this.ext = device.extDisjointTimerQuery;
    }
    destroy() {
        this.freeQueries.forEach((query)=>this.device.gl.deleteQuery(query));
        this.frameQueries.forEach((query)=>this.device.gl.deleteQuery(query));
        this.previousFrameQueries.forEach((frameQueriesInfo)=>frameQueriesInfo.destroy(this.device.gl));
        this.freeQueries = null;
        this.frameQueries = null;
        this.previousFrameQueries = null;
    }
    /**
     * Called when the WebGL context was lost. It releases all context related resources.
     */ loseContext() {
        super.loseContext();
        this.freeQueries = [];
        this.frameQueries = [];
        this.previousFrameQueries = [];
    }
    restoreContext() {
        this.ext = this.device.extDisjointTimerQuery;
    }
    getQuery() {
        return this.freeQueries.pop() ?? this.device.gl.createQuery();
    }
    start(name) {
        if (this.ext) {
            const slot = this.getSlot(name);
            const query = this.getQuery();
            this.frameQueries[slot] = query;
            this.device.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, query);
            return slot;
        }
        return undefined;
    }
    end(slot) {
        if (slot !== undefined) {
            this.device.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
        }
    }
    frameStart() {
        this.processEnableRequest();
        if (this._enabled) {
            this.frameGPUMarkerSlot = this.start('GpuFrame');
        }
    }
    frameEnd() {
        if (this._enabled) {
            this.end(this.frameGPUMarkerSlot);
        }
    }
    request() {
        if (this._enabled) {
            const ext = this.ext;
            const gl = this.device.gl;
            const renderVersion = this.device.renderVersion;
            // add current frame queries to the end of frames list
            const frameQueries = this.frameQueries;
            if (frameQueries.length > 0) {
                this.frameQueries = [];
                const frameQueriesInfo = new FrameQueriesInfo();
                frameQueriesInfo.queries = frameQueries;
                frameQueriesInfo.renderVersion = renderVersion;
                this.previousFrameQueries.push(frameQueriesInfo);
            }
            // try to resolve the oldest frame
            if (this.previousFrameQueries.length > 0) {
                const previousQueriesInfo = this.previousFrameQueries[0];
                const previousQueries = previousQueriesInfo.queries;
                const lastQuery = previousQueries[previousQueries.length - 1];
                const available = gl.getQueryParameter(lastQuery, gl.QUERY_RESULT_AVAILABLE);
                const disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT);
                // valid results
                if (available && !disjoint) {
                    // remove the oldest frame from the list
                    this.previousFrameQueries.shift();
                    // get timings
                    const timings = this.timings;
                    timings.length = 0;
                    for(let i = 0; i < previousQueries.length; i++){
                        const query = previousQueries[i];
                        const duration = gl.getQueryParameter(query, gl.QUERY_RESULT);
                        timings[i] = duration * 0.000001;
                        // return queries to the pool
                        this.freeQueries.push(query);
                    }
                    // report timings
                    this.report(previousQueriesInfo.renderVersion, timings);
                }
                // GPU was interrupted, discard all in-flight queries
                if (disjoint) {
                    this.previousFrameQueries.forEach((frameQueriesInfo)=>{
                        this.report(frameQueriesInfo.renderVersion, null);
                        frameQueriesInfo.destroy(gl);
                    });
                    this.previousFrameQueries.length = 0;
                }
            }
            super.request(renderVersion);
        }
    }
}

/**
 * @import { RenderPass } from '../render-pass.js'
 * @import { Shader } from '../shader.js'
 * @import { VertexBuffer } from '../vertex-buffer.js'
 */ const invalidateAttachments = [];
/**
 * WebglGraphicsDevice extends the base {@link GraphicsDevice} to provide rendering capabilities
 * utilizing the WebGL 2.0 specification.
 *
 * @category Graphics
 */ class WebglGraphicsDevice extends GraphicsDevice {
    /**
     * Creates a new WebglGraphicsDevice instance.
     *
     * @param {HTMLCanvasElement} canvas - The canvas to which the graphics device will render.
     * @param {object} [options] - Options passed when creating the WebGL context.
     * @param {boolean} [options.alpha] - Boolean that indicates if the canvas contains an
     * alpha buffer. Defaults to true.
     * @param {boolean} [options.depth] - Boolean that indicates that the drawing buffer is
     * requested to have a depth buffer of at least 16 bits. Defaults to true.
     * @param {boolean} [options.stencil] - Boolean that indicates that the drawing buffer is
     * requested to have a stencil buffer of at least 8 bits. Defaults to true.
     * @param {boolean} [options.antialias] - Boolean that indicates whether or not to perform
     * anti-aliasing if possible. Defaults to true.
     * @param {boolean} [options.premultipliedAlpha] - Boolean that indicates that the page
     * compositor will assume the drawing buffer contains colors with pre-multiplied alpha.
     * Defaults to true.
     * @param {boolean} [options.preserveDrawingBuffer] - If the value is true the buffers will not
     * be cleared and will preserve their values until cleared or overwritten by the author.
     * Defaults to false.
     * @param {'default'|'high-performance'|'low-power'} [options.powerPreference] - A hint to the
     * user agent indicating what configuration of GPU is suitable for the WebGL context. Possible
     * values are:
     *
     * - 'default': Let the user agent decide which GPU configuration is most suitable. This is the
     * default value.
     * - 'high-performance': Prioritizes rendering performance over power consumption.
     * - 'low-power': Prioritizes power saving over rendering performance.
     *
     * Defaults to 'default'.
     * @param {boolean} [options.failIfMajorPerformanceCaveat] - Boolean that indicates if a
     * context will be created if the system performance is low or if no hardware GPU is available.
     * Defaults to false.
     * @param {boolean} [options.desynchronized] - Boolean that hints the user agent to reduce the
     * latency by desynchronizing the canvas paint cycle from the event loop. Defaults to false.
     * @param {boolean} [options.xrCompatible] - Boolean that hints to the user agent to use a
     * compatible graphics adapter for an immersive XR device.
     * @param {WebGL2RenderingContext} [options.gl] - The rendering context
     * to use. If not specified, a new context will be created.
     */ constructor(canvas, options = {}){
        super(canvas, options), /**
     * WebGLFramebuffer object that represents the backbuffer of the device for a rendering frame.
     * When null, this is a framebuffer created when the device was created, otherwise it is a
     * framebuffer supplied by the XR session.
     *
     * @ignore
     */ this._defaultFramebuffer = null, /**
     * True if the default framebuffer has changed since the last frame.
     *
     * @ignore
     */ this._defaultFramebufferChanged = false;
        options = this.initOptions;
        this.updateClientRect();
        // initialize this before registering lost context handlers to avoid undefined access when the device is created lost.
        this.initTextureUnits();
        // Add handlers for when the WebGL context is lost or restored
        this.contextLost = false;
        this._contextLostHandler = (event)=>{
            event.preventDefault();
            this.loseContext();
            Debug.log('pc.GraphicsDevice: WebGL context lost.');
            this.fire('devicelost');
        };
        this._contextRestoredHandler = ()=>{
            Debug.log('pc.GraphicsDevice: WebGL context restored.');
            this.restoreContext();
            this.fire('devicerestored');
        };
        // #4136 - turn off antialiasing on AppleWebKit browsers 15.4
        const ua = typeof navigator !== 'undefined' && navigator.userAgent;
        this.forceDisableMultisampling = ua && ua.includes('AppleWebKit') && (ua.includes('15.4') || ua.includes('15_4'));
        if (this.forceDisableMultisampling) {
            options.antialias = false;
            Debug.log('Antialiasing has been turned off due to rendering issues on AppleWebKit 15.4');
        }
        // #5856 - turn off antialiasing on Firefox running on Windows / Android
        if (platform.browserName === 'firefox') {
            const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
            const match = ua.match(/Firefox\/(\d+(\.\d+)*)/);
            const firefoxVersion = match ? match[1] : null;
            if (firefoxVersion) {
                const version = parseFloat(firefoxVersion);
                const disableAntialias = platform.name === 'windows' && (version >= 120 || version === 115) || platform.name === 'android' && version >= 132;
                if (disableAntialias) {
                    options.antialias = false;
                    Debug.log(`Antialiasing has been turned off due to rendering issues on Firefox ${platform.name} platform version ${firefoxVersion}`);
                }
            }
        }
        // we always allocate the default framebuffer without antialiasing, so remove that option
        this.backBufferAntialias = options.antialias ?? false;
        options.antialias = false;
        // Retrieve the WebGL context
        /** @type {WebGL2RenderingContext} */ const gl = options.gl ?? canvas.getContext('webgl2', options);
        if (!gl) {
            throw new Error('WebGL not supported');
        }
        this.gl = gl;
        this.isWebGL2 = true;
        this._deviceType = DEVICETYPE_WEBGL2;
        // pixel format of the framebuffer
        this.updateBackbufferFormat(null);
        const isChrome = platform.browserName === 'chrome';
        const isSafari = platform.browserName === 'safari';
        const isMac = platform.browser && navigator.appVersion.indexOf('Mac') !== -1;
        // enable temporary texture unit workaround on desktop safari
        this._tempEnableSafariTextureUnitWorkaround = isSafari;
        // enable temporary workaround for glBlitFramebuffer failing on Mac Chrome (#2504)
        this._tempMacChromeBlitFramebufferWorkaround = isMac && isChrome && !options.alpha;
        canvas.addEventListener('webglcontextlost', this._contextLostHandler, false);
        canvas.addEventListener('webglcontextrestored', this._contextRestoredHandler, false);
        this.initializeExtensions();
        this.initializeCapabilities();
        this.initializeRenderState();
        this.initializeContextCaches();
        this.createBackbuffer(null);
        // only enable ImageBitmap on chrome
        this.supportsImageBitmap = !isSafari && typeof ImageBitmap !== 'undefined';
        // supported sampler types
        this._samplerTypes = new Set([
            gl.SAMPLER_2D,
            gl.SAMPLER_CUBE,
            gl.UNSIGNED_INT_SAMPLER_2D,
            gl.INT_SAMPLER_2D,
            gl.SAMPLER_2D_SHADOW,
            gl.SAMPLER_CUBE_SHADOW,
            gl.SAMPLER_3D,
            gl.INT_SAMPLER_3D,
            gl.UNSIGNED_INT_SAMPLER_3D,
            gl.SAMPLER_2D_ARRAY,
            gl.INT_SAMPLER_2D_ARRAY,
            gl.UNSIGNED_INT_SAMPLER_2D_ARRAY
        ]);
        this.glAddress = [
            gl.REPEAT,
            gl.CLAMP_TO_EDGE,
            gl.MIRRORED_REPEAT
        ];
        this.glBlendEquation = [
            gl.FUNC_ADD,
            gl.FUNC_SUBTRACT,
            gl.FUNC_REVERSE_SUBTRACT,
            gl.MIN,
            gl.MAX
        ];
        this.glBlendFunctionColor = [
            gl.ZERO,
            gl.ONE,
            gl.SRC_COLOR,
            gl.ONE_MINUS_SRC_COLOR,
            gl.DST_COLOR,
            gl.ONE_MINUS_DST_COLOR,
            gl.SRC_ALPHA,
            gl.SRC_ALPHA_SATURATE,
            gl.ONE_MINUS_SRC_ALPHA,
            gl.DST_ALPHA,
            gl.ONE_MINUS_DST_ALPHA,
            gl.CONSTANT_COLOR,
            gl.ONE_MINUS_CONSTANT_COLOR
        ];
        this.glBlendFunctionAlpha = [
            gl.ZERO,
            gl.ONE,
            gl.SRC_COLOR,
            gl.ONE_MINUS_SRC_COLOR,
            gl.DST_COLOR,
            gl.ONE_MINUS_DST_COLOR,
            gl.SRC_ALPHA,
            gl.SRC_ALPHA_SATURATE,
            gl.ONE_MINUS_SRC_ALPHA,
            gl.DST_ALPHA,
            gl.ONE_MINUS_DST_ALPHA,
            gl.CONSTANT_ALPHA,
            gl.ONE_MINUS_CONSTANT_ALPHA
        ];
        this.glComparison = [
            gl.NEVER,
            gl.LESS,
            gl.EQUAL,
            gl.LEQUAL,
            gl.GREATER,
            gl.NOTEQUAL,
            gl.GEQUAL,
            gl.ALWAYS
        ];
        this.glStencilOp = [
            gl.KEEP,
            gl.ZERO,
            gl.REPLACE,
            gl.INCR,
            gl.INCR_WRAP,
            gl.DECR,
            gl.DECR_WRAP,
            gl.INVERT
        ];
        this.glClearFlag = [
            0,
            gl.COLOR_BUFFER_BIT,
            gl.DEPTH_BUFFER_BIT,
            gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT,
            gl.STENCIL_BUFFER_BIT,
            gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT,
            gl.STENCIL_BUFFER_BIT | gl.DEPTH_BUFFER_BIT,
            gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT
        ];
        this.glCull = [
            0,
            gl.BACK,
            gl.FRONT,
            gl.FRONT_AND_BACK
        ];
        this.glFilter = [
            gl.NEAREST,
            gl.LINEAR,
            gl.NEAREST_MIPMAP_NEAREST,
            gl.NEAREST_MIPMAP_LINEAR,
            gl.LINEAR_MIPMAP_NEAREST,
            gl.LINEAR_MIPMAP_LINEAR
        ];
        this.glPrimitive = [
            gl.POINTS,
            gl.LINES,
            gl.LINE_LOOP,
            gl.LINE_STRIP,
            gl.TRIANGLES,
            gl.TRIANGLE_STRIP,
            gl.TRIANGLE_FAN
        ];
        this.glType = [
            gl.BYTE,
            gl.UNSIGNED_BYTE,
            gl.SHORT,
            gl.UNSIGNED_SHORT,
            gl.INT,
            gl.UNSIGNED_INT,
            gl.FLOAT,
            gl.HALF_FLOAT
        ];
        this.pcUniformType = {};
        this.pcUniformType[gl.BOOL] = UNIFORMTYPE_BOOL;
        this.pcUniformType[gl.INT] = UNIFORMTYPE_INT;
        this.pcUniformType[gl.FLOAT] = UNIFORMTYPE_FLOAT;
        this.pcUniformType[gl.FLOAT_VEC2] = UNIFORMTYPE_VEC2;
        this.pcUniformType[gl.FLOAT_VEC3] = UNIFORMTYPE_VEC3;
        this.pcUniformType[gl.FLOAT_VEC4] = UNIFORMTYPE_VEC4;
        this.pcUniformType[gl.INT_VEC2] = UNIFORMTYPE_IVEC2;
        this.pcUniformType[gl.INT_VEC3] = UNIFORMTYPE_IVEC3;
        this.pcUniformType[gl.INT_VEC4] = UNIFORMTYPE_IVEC4;
        this.pcUniformType[gl.BOOL_VEC2] = UNIFORMTYPE_BVEC2;
        this.pcUniformType[gl.BOOL_VEC3] = UNIFORMTYPE_BVEC3;
        this.pcUniformType[gl.BOOL_VEC4] = UNIFORMTYPE_BVEC4;
        this.pcUniformType[gl.FLOAT_MAT2] = UNIFORMTYPE_MAT2;
        this.pcUniformType[gl.FLOAT_MAT3] = UNIFORMTYPE_MAT3;
        this.pcUniformType[gl.FLOAT_MAT4] = UNIFORMTYPE_MAT4;
        this.pcUniformType[gl.SAMPLER_2D] = UNIFORMTYPE_TEXTURE2D;
        this.pcUniformType[gl.SAMPLER_CUBE] = UNIFORMTYPE_TEXTURECUBE;
        this.pcUniformType[gl.UNSIGNED_INT] = UNIFORMTYPE_UINT;
        this.pcUniformType[gl.UNSIGNED_INT_VEC2] = UNIFORMTYPE_UVEC2;
        this.pcUniformType[gl.UNSIGNED_INT_VEC3] = UNIFORMTYPE_UVEC3;
        this.pcUniformType[gl.UNSIGNED_INT_VEC4] = UNIFORMTYPE_UVEC4;
        this.pcUniformType[gl.SAMPLER_2D_SHADOW] = UNIFORMTYPE_TEXTURE2D_SHADOW;
        this.pcUniformType[gl.SAMPLER_CUBE_SHADOW] = UNIFORMTYPE_TEXTURECUBE_SHADOW;
        this.pcUniformType[gl.SAMPLER_2D_ARRAY] = UNIFORMTYPE_TEXTURE2D_ARRAY;
        this.pcUniformType[gl.SAMPLER_3D] = UNIFORMTYPE_TEXTURE3D;
        this.pcUniformType[gl.INT_SAMPLER_2D] = UNIFORMTYPE_ITEXTURE2D;
        this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURE2D;
        this.pcUniformType[gl.INT_SAMPLER_CUBE] = UNIFORMTYPE_ITEXTURECUBE;
        this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURECUBE;
        this.pcUniformType[gl.INT_SAMPLER_3D] = UNIFORMTYPE_ITEXTURE3D;
        this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_3D] = UNIFORMTYPE_UTEXTURE3D;
        this.pcUniformType[gl.INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_ITEXTURE2D_ARRAY;
        this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_UTEXTURE2D_ARRAY;
        this.targetToSlot = {};
        this.targetToSlot[gl.TEXTURE_2D] = 0;
        this.targetToSlot[gl.TEXTURE_CUBE_MAP] = 1;
        this.targetToSlot[gl.TEXTURE_3D] = 2;
        // Define the uniform commit functions
        let scopeX, scopeY, scopeZ, scopeW;
        let uniformValue;
        this.commitFunction = [];
        this.commitFunction[UNIFORMTYPE_BOOL] = function(uniform, value) {
            if (uniform.value !== value) {
                gl.uniform1i(uniform.locationId, value);
                uniform.value = value;
            }
        };
        this.commitFunction[UNIFORMTYPE_INT] = this.commitFunction[UNIFORMTYPE_BOOL];
        this.commitFunction[UNIFORMTYPE_FLOAT] = function(uniform, value) {
            if (uniform.value !== value) {
                gl.uniform1f(uniform.locationId, value);
                uniform.value = value;
            }
        };
        this.commitFunction[UNIFORMTYPE_VEC2] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
                gl.uniform2fv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
            }
        };
        this.commitFunction[UNIFORMTYPE_VEC3] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            scopeZ = value[2];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
                gl.uniform3fv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
                uniformValue[2] = scopeZ;
            }
        };
        this.commitFunction[UNIFORMTYPE_VEC4] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            scopeZ = value[2];
            scopeW = value[3];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
                gl.uniform4fv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
                uniformValue[2] = scopeZ;
                uniformValue[3] = scopeW;
            }
        };
        this.commitFunction[UNIFORMTYPE_IVEC2] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
                gl.uniform2iv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
            }
        };
        this.commitFunction[UNIFORMTYPE_BVEC2] = this.commitFunction[UNIFORMTYPE_IVEC2];
        this.commitFunction[UNIFORMTYPE_IVEC3] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            scopeZ = value[2];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
                gl.uniform3iv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
                uniformValue[2] = scopeZ;
            }
        };
        this.commitFunction[UNIFORMTYPE_BVEC3] = this.commitFunction[UNIFORMTYPE_IVEC3];
        this.commitFunction[UNIFORMTYPE_IVEC4] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            scopeZ = value[2];
            scopeW = value[3];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
                gl.uniform4iv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
                uniformValue[2] = scopeZ;
                uniformValue[3] = scopeW;
            }
        };
        this.commitFunction[UNIFORMTYPE_BVEC4] = this.commitFunction[UNIFORMTYPE_IVEC4];
        this.commitFunction[UNIFORMTYPE_MAT2] = function(uniform, value) {
            gl.uniformMatrix2fv(uniform.locationId, false, value);
        };
        this.commitFunction[UNIFORMTYPE_MAT3] = function(uniform, value) {
            gl.uniformMatrix3fv(uniform.locationId, false, value);
        };
        this.commitFunction[UNIFORMTYPE_MAT4] = function(uniform, value) {
            gl.uniformMatrix4fv(uniform.locationId, false, value);
        };
        this.commitFunction[UNIFORMTYPE_FLOATARRAY] = function(uniform, value) {
            gl.uniform1fv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_VEC2ARRAY] = function(uniform, value) {
            gl.uniform2fv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_VEC3ARRAY] = function(uniform, value) {
            gl.uniform3fv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_VEC4ARRAY] = function(uniform, value) {
            gl.uniform4fv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_UINT] = function(uniform, value) {
            if (uniform.value !== value) {
                gl.uniform1ui(uniform.locationId, value);
                uniform.value = value;
            }
        };
        this.commitFunction[UNIFORMTYPE_UVEC2] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
                gl.uniform2uiv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
            }
        };
        this.commitFunction[UNIFORMTYPE_UVEC3] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            scopeZ = value[2];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
                gl.uniform3uiv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
                uniformValue[2] = scopeZ;
            }
        };
        this.commitFunction[UNIFORMTYPE_UVEC4] = function(uniform, value) {
            uniformValue = uniform.value;
            scopeX = value[0];
            scopeY = value[1];
            scopeZ = value[2];
            scopeW = value[3];
            if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
                gl.uniform4uiv(uniform.locationId, value);
                uniformValue[0] = scopeX;
                uniformValue[1] = scopeY;
                uniformValue[2] = scopeZ;
                uniformValue[3] = scopeW;
            }
        };
        this.commitFunction[UNIFORMTYPE_INTARRAY] = function(uniform, value) {
            gl.uniform1iv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_UINTARRAY] = function(uniform, value) {
            gl.uniform1uiv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_BOOLARRAY] = this.commitFunction[UNIFORMTYPE_INTARRAY];
        this.commitFunction[UNIFORMTYPE_IVEC2ARRAY] = function(uniform, value) {
            gl.uniform2iv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_UVEC2ARRAY] = function(uniform, value) {
            gl.uniform2uiv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_BVEC2ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC2ARRAY];
        this.commitFunction[UNIFORMTYPE_IVEC3ARRAY] = function(uniform, value) {
            gl.uniform3iv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_UVEC3ARRAY] = function(uniform, value) {
            gl.uniform3uiv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_BVEC3ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC3ARRAY];
        this.commitFunction[UNIFORMTYPE_IVEC4ARRAY] = function(uniform, value) {
            gl.uniform4iv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_UVEC4ARRAY] = function(uniform, value) {
            gl.uniform4uiv(uniform.locationId, value);
        };
        this.commitFunction[UNIFORMTYPE_BVEC4ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC4ARRAY];
        this.commitFunction[UNIFORMTYPE_MAT4ARRAY] = function(uniform, value) {
            gl.uniformMatrix4fv(uniform.locationId, false, value);
        };
        this.constantTexSource = this.scope.resolve('source');
        this.postInit();
    }
    postInit() {
        super.postInit();
        this.gpuProfiler = new WebglGpuProfiler(this);
    }
    /**
     * Destroy the graphics device.
     */ destroy() {
        super.destroy();
        const gl = this.gl;
        if (this.feedback) {
            gl.deleteTransformFeedback(this.feedback);
        }
        this.clearVertexArrayObjectCache();
        this.canvas.removeEventListener('webglcontextlost', this._contextLostHandler, false);
        this.canvas.removeEventListener('webglcontextrestored', this._contextRestoredHandler, false);
        this._contextLostHandler = null;
        this._contextRestoredHandler = null;
        this.gl = null;
        super.postDestroy();
    }
    createBackbuffer(frameBuffer) {
        this.supportsStencil = this.initOptions.stencil;
        this.backBuffer = new RenderTarget({
            name: 'WebglFramebuffer',
            graphicsDevice: this,
            depth: this.initOptions.depth,
            stencil: this.supportsStencil,
            samples: this.samples
        });
        // use the default WebGL framebuffer for rendering
        this.backBuffer.impl.suppliedColorFramebuffer = frameBuffer;
    }
    // Update framebuffer format based on the current framebuffer, as this is use to create matching multi-sampled framebuffer
    updateBackbufferFormat(framebuffer) {
        const gl = this.gl;
        gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        const alphaBits = this.gl.getParameter(this.gl.ALPHA_BITS);
        this.backBufferFormat = alphaBits ? PIXELFORMAT_RGBA8 : PIXELFORMAT_RGB8;
    }
    updateBackbuffer() {
        const resolutionChanged = this.canvas.width !== this.backBufferSize.x || this.canvas.height !== this.backBufferSize.y;
        if (this._defaultFramebufferChanged || resolutionChanged) {
            // if the default framebuffer changes (entering or exiting XR for example)
            if (this._defaultFramebufferChanged) {
                this.updateBackbufferFormat(this._defaultFramebuffer);
            }
            this._defaultFramebufferChanged = false;
            this.backBufferSize.set(this.canvas.width, this.canvas.height);
            // recreate the backbuffer with newly supplied framebuffer
            this.backBuffer.destroy();
            this.createBackbuffer(this._defaultFramebuffer);
        }
    }
    // provide webgl implementation for the vertex buffer
    createVertexBufferImpl(vertexBuffer, format) {
        return new WebglVertexBuffer();
    }
    // provide webgl implementation for the index buffer
    createIndexBufferImpl(indexBuffer) {
        return new WebglIndexBuffer(indexBuffer);
    }
    createShaderImpl(shader) {
        return new WebglShader(shader);
    }
    createTextureImpl(texture) {
        return new WebglTexture(texture);
    }
    createRenderTargetImpl(renderTarget) {
        return new WebglRenderTarget();
    }
    pushMarker(name) {
        if (platform.browser && window.spector) {
            const label = DebugGraphics.toString();
            window.spector.setMarker(`${label} #`);
        }
    }
    popMarker() {
        if (platform.browser && window.spector) {
            const label = DebugGraphics.toString();
            if (label.length) {
                window.spector.setMarker(`${label} #`);
            } else {
                window.spector.clearMarker();
            }
        }
    }
    /**
     * Query the precision supported by ints and floats in vertex and fragment shaders. Note that
     * getShaderPrecisionFormat is not guaranteed to be present (such as some instances of the
     * default Android browser). In this case, assume highp is available.
     *
     * @returns {"highp"|"mediump"|"lowp"} The highest precision supported by the WebGL context.
     * @ignore
     */ getPrecision() {
        const gl = this.gl;
        let precision = 'highp';
        if (gl.getShaderPrecisionFormat) {
            const vertexShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT);
            const vertexShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT);
            const fragmentShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
            const fragmentShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
            if (vertexShaderPrecisionHighpFloat && vertexShaderPrecisionMediumpFloat && fragmentShaderPrecisionHighpFloat && fragmentShaderPrecisionMediumpFloat) {
                const highpAvailable = vertexShaderPrecisionHighpFloat.precision > 0 && fragmentShaderPrecisionHighpFloat.precision > 0;
                const mediumpAvailable = vertexShaderPrecisionMediumpFloat.precision > 0 && fragmentShaderPrecisionMediumpFloat.precision > 0;
                if (!highpAvailable) {
                    if (mediumpAvailable) {
                        precision = 'mediump';
                        Debug.warn('WARNING: highp not supported, using mediump');
                    } else {
                        precision = 'lowp';
                        Debug.warn('WARNING: highp and mediump not supported, using lowp');
                    }
                }
            }
        }
        return precision;
    }
    getExtension() {
        for(let i = 0; i < arguments.length; i++){
            if (this.supportedExtensions.indexOf(arguments[i]) !== -1) {
                return this.gl.getExtension(arguments[i]);
            }
        }
        return null;
    }
    get extDisjointTimerQuery() {
        // lazy evaluation as this is not typically used
        if (!this._extDisjointTimerQuery) {
            // Note that Firefox exposes EXT_disjoint_timer_query under WebGL2 rather than EXT_disjoint_timer_query_webgl2
            this._extDisjointTimerQuery = this.getExtension('EXT_disjoint_timer_query_webgl2', 'EXT_disjoint_timer_query');
        }
        return this._extDisjointTimerQuery;
    }
    /**
     * Initialize the extensions provided by the WebGL context.
     *
     * @ignore
     */ initializeExtensions() {
        const gl = this.gl;
        this.supportedExtensions = gl.getSupportedExtensions() ?? [];
        this._extDisjointTimerQuery = null;
        this.textureRG11B10Renderable = true;
        // In WebGL2 float texture renderability is dictated by the EXT_color_buffer_float extension
        this.extColorBufferFloat = this.getExtension('EXT_color_buffer_float');
        this.textureFloatRenderable = !!this.extColorBufferFloat;
        // iOS exposes this for half precision render targets on WebGL2 from iOS v 14.5beta
        this.extColorBufferHalfFloat = this.getExtension('EXT_color_buffer_half_float');
        // render to half float buffers support - either of these two extensions
        this.textureHalfFloatRenderable = !!this.extColorBufferHalfFloat || !!this.extColorBufferFloat;
        this.extDebugRendererInfo = this.getExtension('WEBGL_debug_renderer_info');
        this.extTextureFloatLinear = this.getExtension('OES_texture_float_linear');
        this.textureFloatFilterable = !!this.extTextureFloatLinear;
        this.extFloatBlend = this.getExtension('EXT_float_blend');
        this.extTextureFilterAnisotropic = this.getExtension('EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic');
        this.extParallelShaderCompile = this.getExtension('KHR_parallel_shader_compile');
        // compressed textures
        this.extCompressedTextureETC1 = this.getExtension('WEBGL_compressed_texture_etc1');
        this.extCompressedTextureETC = this.getExtension('WEBGL_compressed_texture_etc');
        this.extCompressedTexturePVRTC = this.getExtension('WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc');
        this.extCompressedTextureS3TC = this.getExtension('WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc');
        this.extCompressedTextureS3TC_SRGB = this.getExtension('WEBGL_compressed_texture_s3tc_srgb');
        this.extCompressedTextureATC = this.getExtension('WEBGL_compressed_texture_atc');
        this.extCompressedTextureASTC = this.getExtension('WEBGL_compressed_texture_astc');
        this.extTextureCompressionBPTC = this.getExtension('EXT_texture_compression_bptc');
    }
    /**
     * Query the capabilities of the WebGL context.
     *
     * @ignore
     */ initializeCapabilities() {
        const gl = this.gl;
        let ext;
        const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';
        this.maxPrecision = this.precision = this.getPrecision();
        const contextAttribs = gl.getContextAttributes();
        this.supportsMsaa = contextAttribs?.antialias ?? false;
        this.supportsStencil = contextAttribs?.stencil ?? false;
        // Query parameter values from the WebGL context
        this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
        this.maxCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
        this.maxRenderBufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
        this.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
        this.maxCombinedTextures = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
        this.maxVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
        this.vertexUniformsCount = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
        this.fragmentUniformsCount = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
        this.maxColorAttachments = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
        this.maxVolumeSize = gl.getParameter(gl.MAX_3D_TEXTURE_SIZE);
        ext = this.extDebugRendererInfo;
        this.unmaskedRenderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : '';
        this.unmaskedVendor = ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : '';
        // Mali-G52 has rendering issues with GPU particles including
        // SM-A225M, M2003J15SC and KFRAWI (Amazon Fire HD 8 2022)
        const maliRendererRegex = /\bMali-G52+/;
        // Samsung devices with Exynos (ARM) either crash or render incorrectly when using GPU for particles. See:
        // https://github.com/playcanvas/engine/issues/3967
        // https://github.com/playcanvas/engine/issues/3415
        // https://github.com/playcanvas/engine/issues/4514
        // Example UA matches: Starting 'SM' and any combination of letters or numbers:
        // Mozilla/5.0 (Linux, Android 12; SM-G970F Build/SP1A.210812.016; wv)
        const samsungModelRegex = /SM-[a-zA-Z0-9]+/;
        this.supportsGpuParticles = !(this.unmaskedVendor === 'ARM' && userAgent.match(samsungModelRegex)) && !this.unmaskedRenderer.match(maliRendererRegex);
        ext = this.extTextureFilterAnisotropic;
        this.maxAnisotropy = ext ? gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1;
        const antialiasSupported = !this.forceDisableMultisampling;
        this.maxSamples = antialiasSupported ? gl.getParameter(gl.MAX_SAMPLES) : 1;
        // some devices incorrectly report max samples larger than 4
        this.maxSamples = Math.min(this.maxSamples, 4);
        // we handle anti-aliasing internally by allocating multi-sampled backbuffer
        this.samples = antialiasSupported && this.backBufferAntialias ? this.maxSamples : 1;
        // Don't allow area lights on old android devices, they often fail to compile the shader, run it incorrectly or are very slow.
        this.supportsAreaLights = !platform.android;
        // Also do not allow them when we only have small number of texture units
        if (this.maxTextures <= 8) {
            this.supportsAreaLights = false;
        }
        this.initCapsDefines();
    }
    /**
     * Set the initial render state on the WebGL context.
     *
     * @ignore
     */ initializeRenderState() {
        super.initializeRenderState();
        const gl = this.gl;
        // Initialize render state to a known start state
        // default blend state
        gl.disable(gl.BLEND);
        gl.blendFunc(gl.ONE, gl.ZERO);
        gl.blendEquation(gl.FUNC_ADD);
        gl.colorMask(true, true, true, true);
        gl.blendColor(0, 0, 0, 0);
        gl.enable(gl.CULL_FACE);
        this.cullFace = gl.BACK;
        gl.cullFace(gl.BACK);
        // default depth state
        gl.enable(gl.DEPTH_TEST);
        gl.depthFunc(gl.LEQUAL);
        gl.depthMask(true);
        this.stencil = false;
        gl.disable(gl.STENCIL_TEST);
        this.stencilFuncFront = this.stencilFuncBack = FUNC_ALWAYS;
        this.stencilRefFront = this.stencilRefBack = 0;
        this.stencilMaskFront = this.stencilMaskBack = 0xFF;
        gl.stencilFunc(gl.ALWAYS, 0, 0xFF);
        this.stencilFailFront = this.stencilFailBack = STENCILOP_KEEP;
        this.stencilZfailFront = this.stencilZfailBack = STENCILOP_KEEP;
        this.stencilZpassFront = this.stencilZpassBack = STENCILOP_KEEP;
        this.stencilWriteMaskFront = 0xFF;
        this.stencilWriteMaskBack = 0xFF;
        gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
        gl.stencilMask(0xFF);
        this.alphaToCoverage = false;
        this.raster = true;
        gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
        gl.disable(gl.RASTERIZER_DISCARD);
        this.depthBiasEnabled = false;
        gl.disable(gl.POLYGON_OFFSET_FILL);
        this.clearDepth = 1;
        gl.clearDepth(1);
        this.clearColor = new Color(0, 0, 0, 0);
        gl.clearColor(0, 0, 0, 0);
        this.clearStencil = 0;
        gl.clearStencil(0);
        gl.hint(gl.FRAGMENT_SHADER_DERIVATIVE_HINT, gl.NICEST);
        gl.enable(gl.SCISSOR_TEST);
        gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
        this.unpackFlipY = false;
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        this.unpackPremultiplyAlpha = false;
        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    }
    initTextureUnits(count = 16) {
        this.textureUnits = [];
        for(let i = 0; i < count; i++){
            this.textureUnits.push([
                null,
                null,
                null
            ]);
        }
    }
    initializeContextCaches() {
        super.initializeContextCaches();
        // cache of VAOs
        this._vaoMap = new Map();
        this.boundVao = null;
        this.activeFramebuffer = null;
        this.feedback = null;
        this.transformFeedbackBuffer = null;
        this.textureUnit = 0;
        this.initTextureUnits(this.maxCombinedTextures);
    }
    /**
     * Called when the WebGL context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        super.loseContext();
        // release shaders
        for (const shader of this.shaders){
            shader.loseContext();
        }
    }
    /**
     * Called when the WebGL context is restored. It reinitializes all context related resources.
     *
     * @ignore
     */ restoreContext() {
        this.initializeExtensions();
        this.initializeCapabilities();
        super.restoreContext();
        // Recompile all shaders
        for (const shader of this.shaders){
            shader.restoreContext();
        }
    }
    /**
     * Set the active rectangle for rendering on the specified device.
     *
     * @param {number} x - The pixel space x-coordinate of the bottom left corner of the viewport.
     * @param {number} y - The pixel space y-coordinate of the bottom left corner of the viewport.
     * @param {number} w - The width of the viewport in pixels.
     * @param {number} h - The height of the viewport in pixels.
     */ setViewport(x, y, w, h) {
        if (this.vx !== x || this.vy !== y || this.vw !== w || this.vh !== h) {
            this.gl.viewport(x, y, w, h);
            this.vx = x;
            this.vy = y;
            this.vw = w;
            this.vh = h;
        }
    }
    /**
     * Set the active scissor rectangle on the specified device.
     *
     * @param {number} x - The pixel space x-coordinate of the bottom left corner of the scissor rectangle.
     * @param {number} y - The pixel space y-coordinate of the bottom left corner of the scissor rectangle.
     * @param {number} w - The width of the scissor rectangle in pixels.
     * @param {number} h - The height of the scissor rectangle in pixels.
     */ setScissor(x, y, w, h) {
        if (this.sx !== x || this.sy !== y || this.sw !== w || this.sh !== h) {
            this.gl.scissor(x, y, w, h);
            this.sx = x;
            this.sy = y;
            this.sw = w;
            this.sh = h;
        }
    }
    /**
     * Binds the specified framebuffer object.
     *
     * @param {WebGLFramebuffer | null} fb - The framebuffer to bind.
     * @ignore
     */ setFramebuffer(fb) {
        if (this.activeFramebuffer !== fb) {
            const gl = this.gl;
            gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
            this.activeFramebuffer = fb;
        }
    }
    /**
     * Copies source render target into destination render target. Mostly used by post-effects.
     *
     * @param {RenderTarget} [source] - The source render target. Defaults to frame buffer.
     * @param {RenderTarget} [dest] - The destination render target. Defaults to frame buffer.
     * @param {boolean} [color] - If true, will copy the color buffer. Defaults to false.
     * @param {boolean} [depth] - If true, will copy the depth buffer. Defaults to false.
     * @returns {boolean} True if the copy was successful, false otherwise.
     */ copyRenderTarget(source, dest, color, depth) {
        const gl = this.gl;
        // if copying from the backbuffer
        if (source === this.backBuffer) {
            source = null;
        }
        if (color) {
            if (!dest) {
                // copying to backbuffer
                if (!source._colorBuffer) {
                    Debug.error('Can\'t copy empty color buffer to backbuffer');
                    return false;
                }
            } else if (source) {
                // copying to render target
                if (!source._colorBuffer || !dest._colorBuffer) {
                    Debug.error('Can\'t copy color buffer, because one of the render targets doesn\'t have it');
                    return false;
                }
                if (source._colorBuffer._format !== dest._colorBuffer._format) {
                    Debug.error('Can\'t copy render targets of different color formats');
                    return false;
                }
            }
        }
        if (depth && source) {
            if (!source._depth) {
                if (!source._depthBuffer || !dest._depthBuffer) {
                    Debug.error('Can\'t copy depth buffer, because one of the render targets doesn\'t have it');
                    return false;
                }
                if (source._depthBuffer._format !== dest._depthBuffer._format) {
                    Debug.error('Can\'t copy render targets of different depth formats');
                    return false;
                }
            }
        }
        DebugGraphics.pushGpuMarker(this, 'COPY-RT');
        const prevRt = this.renderTarget;
        this.renderTarget = dest;
        this.updateBegin();
        // copy from single sampled framebuffer
        const src = source ? source.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer;
        const dst = dest ? dest.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer;
        Debug.assert(src !== dst, 'Source and destination framebuffers must be different when blitting.');
        gl.bindFramebuffer(gl.READ_FRAMEBUFFER, src);
        gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst);
        const w = source ? source.width : dest ? dest.width : this.width;
        const h = source ? source.height : dest ? dest.height : this.height;
        gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, (color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0), gl.NEAREST);
        // TODO: not sure we need to restore the prev target, as this only should run in-between render passes
        this.renderTarget = prevRt;
        gl.bindFramebuffer(gl.FRAMEBUFFER, prevRt ? prevRt.impl._glFrameBuffer : null);
        DebugGraphics.popGpuMarker(this);
        return true;
    }
    frameStart() {
        super.frameStart();
        this.updateBackbuffer();
        this.gpuProfiler.frameStart();
    }
    frameEnd() {
        super.frameEnd();
        this.gpuProfiler.frameEnd();
        this.gpuProfiler.request();
    }
    /**
     * Start a render pass.
     *
     * @param {RenderPass} renderPass - The render pass to start.
     * @ignore
     */ startRenderPass(renderPass) {
        // set up render target
        const rt = renderPass.renderTarget ?? this.backBuffer;
        this.renderTarget = rt;
        Debug.assert(rt);
        DebugGraphics.pushGpuMarker(this, `Pass:${renderPass.name} RT:${rt.name}`);
        DebugGraphics.pushGpuMarker(this, 'START-PASS');
        this.updateBegin();
        // the pass always start using full size of the target
        const { width, height } = rt;
        this.setViewport(0, 0, width, height);
        this.setScissor(0, 0, width, height);
        // clear the render target
        const colorOps = renderPass.colorOps;
        const depthStencilOps = renderPass.depthStencilOps;
        if (colorOps?.clear || depthStencilOps.clearDepth || depthStencilOps.clearStencil) {
            let clearFlags = 0;
            const clearOptions = {};
            if (colorOps?.clear) {
                clearFlags |= CLEARFLAG_COLOR;
                clearOptions.color = [
                    colorOps.clearValue.r,
                    colorOps.clearValue.g,
                    colorOps.clearValue.b,
                    colorOps.clearValue.a
                ];
            }
            if (depthStencilOps.clearDepth) {
                clearFlags |= CLEARFLAG_DEPTH;
                clearOptions.depth = depthStencilOps.clearDepthValue;
            }
            if (depthStencilOps.clearStencil) {
                clearFlags |= CLEARFLAG_STENCIL;
                clearOptions.stencil = depthStencilOps.clearStencilValue;
            }
            // clear it
            clearOptions.flags = clearFlags;
            this.clear(clearOptions);
        }
        Debug.call(()=>{
            if (this.insideRenderPass) {
                Debug.errorOnce('RenderPass cannot be started while inside another render pass.');
            }
        });
        this.insideRenderPass = true;
        DebugGraphics.popGpuMarker(this);
    }
    /**
     * End a render pass.
     *
     * @param {RenderPass} renderPass - The render pass to end.
     * @ignore
     */ endRenderPass(renderPass) {
        DebugGraphics.pushGpuMarker(this, 'END-PASS');
        this.unbindVertexArray();
        const target = this.renderTarget;
        const colorBufferCount = renderPass.colorArrayOps.length;
        if (target) {
            // invalidate buffers to stop them being written to on tiled architectures
            invalidateAttachments.length = 0;
            const gl = this.gl;
            // color buffers
            for(let i = 0; i < colorBufferCount; i++){
                const colorOps = renderPass.colorArrayOps[i];
                // invalidate color only if we don't need to resolve it
                if (!(colorOps.store || colorOps.resolve)) {
                    invalidateAttachments.push(gl.COLOR_ATTACHMENT0 + i);
                }
            }
            // we cannot invalidate depth/stencil buffers of the backbuffer
            if (target !== this.backBuffer) {
                if (!renderPass.depthStencilOps.storeDepth) {
                    invalidateAttachments.push(gl.DEPTH_ATTACHMENT);
                }
                if (!renderPass.depthStencilOps.storeStencil) {
                    invalidateAttachments.push(gl.STENCIL_ATTACHMENT);
                }
            }
            if (invalidateAttachments.length > 0) {
                // invalidate the whole buffer
                // TODO: we could handle viewport invalidation as well
                if (renderPass.fullSizeClearRect) {
                    gl.invalidateFramebuffer(gl.DRAW_FRAMEBUFFER, invalidateAttachments);
                }
            }
            // resolve the color buffer (this resolves all MRT color buffers at once)
            if (colorBufferCount && renderPass.colorOps?.resolve) {
                if (renderPass.samples > 1 && target.autoResolve) {
                    target.resolve(true, false);
                }
            }
            // resolve depth/stencil buffer
            if (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) {
                if (renderPass.samples > 1 && target.autoResolve) {
                    target.resolve(false, true);
                }
            }
            // generate mipmaps
            for(let i = 0; i < colorBufferCount; i++){
                const colorOps = renderPass.colorArrayOps[i];
                if (colorOps.genMipmaps) {
                    const colorBuffer = target._colorBuffers[i];
                    if (colorBuffer && colorBuffer.impl._glTexture && colorBuffer.mipmaps) {
                        DebugGraphics.pushGpuMarker(this, `MIPS${i}`);
                        this.activeTexture(this.maxCombinedTextures - 1);
                        this.bindTexture(colorBuffer);
                        this.gl.generateMipmap(colorBuffer.impl._glTarget);
                        DebugGraphics.popGpuMarker(this);
                    }
                }
            }
        }
        this.insideRenderPass = false;
        DebugGraphics.popGpuMarker(this);
        DebugGraphics.popGpuMarker(this); // pop the pass-start marker
    }
    set defaultFramebuffer(value) {
        if (this._defaultFramebuffer !== value) {
            this._defaultFramebuffer = value;
            this._defaultFramebufferChanged = true;
        }
    }
    get defaultFramebuffer() {
        return this._defaultFramebuffer;
    }
    /**
     * Marks the beginning of a block of rendering. Internally, this function binds the render
     * target currently set on the device. This function should be matched with a call to
     * {@link GraphicsDevice#updateEnd}. Calls to {@link GraphicsDevice#updateBegin} and
     * {@link GraphicsDevice#updateEnd} must not be nested.
     *
     * @ignore
     */ updateBegin() {
        DebugGraphics.pushGpuMarker(this, 'UPDATE-BEGIN');
        this.boundVao = null;
        // clear texture units once a frame on desktop safari
        if (this._tempEnableSafariTextureUnitWorkaround) {
            for(let unit = 0; unit < this.textureUnits.length; ++unit){
                for(let slot = 0; slot < 3; ++slot){
                    this.textureUnits[unit][slot] = null;
                }
            }
        }
        // Set the render target
        const target = this.renderTarget ?? this.backBuffer;
        Debug.assert(target);
        // Initialize the framebuffer
        const targetImpl = target.impl;
        if (!targetImpl.initialized) {
            this.initRenderTarget(target);
        }
        // Bind the framebuffer
        this.setFramebuffer(targetImpl._glFrameBuffer);
        DebugGraphics.popGpuMarker(this);
    }
    /**
     * Marks the end of a block of rendering. This function should be called after a matching call
     * to {@link GraphicsDevice#updateBegin}. Calls to {@link GraphicsDevice#updateBegin} and
     * {@link GraphicsDevice#updateEnd} must not be nested.
     *
     * @ignore
     */ updateEnd() {
        DebugGraphics.pushGpuMarker(this, 'UPDATE-END');
        this.unbindVertexArray();
        // Unset the render target
        const target = this.renderTarget;
        if (target && target !== this.backBuffer) {
            // Resolve MSAA if needed
            if (target._samples > 1 && target.autoResolve) {
                target.resolve();
            }
            // If the active render target is auto-mipmapped, generate its mip chain
            const colorBuffer = target._colorBuffer;
            if (colorBuffer && colorBuffer.impl._glTexture && colorBuffer.mipmaps) {
                // FIXME: if colorBuffer is a cubemap currently we're re-generating mipmaps after
                // updating each face!
                this.activeTexture(this.maxCombinedTextures - 1);
                this.bindTexture(colorBuffer);
                this.gl.generateMipmap(colorBuffer.impl._glTarget);
            }
        }
        DebugGraphics.popGpuMarker(this);
    }
    /**
     * Updates a texture's vertical flip.
     *
     * @param {boolean} flipY - True to flip the texture vertically.
     * @ignore
     */ setUnpackFlipY(flipY) {
        if (this.unpackFlipY !== flipY) {
            this.unpackFlipY = flipY;
            // Note: the WebGL spec states that UNPACK_FLIP_Y_WEBGL only affects
            // texImage2D and texSubImage2D, not compressedTexImage2D
            const gl = this.gl;
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
        }
    }
    /**
     * Updates a texture to have its RGB channels premultiplied by its alpha channel or not.
     *
     * @param {boolean} premultiplyAlpha - True to premultiply the alpha channel against the RGB
     * channels.
     * @ignore
     */ setUnpackPremultiplyAlpha(premultiplyAlpha) {
        if (this.unpackPremultiplyAlpha !== premultiplyAlpha) {
            this.unpackPremultiplyAlpha = premultiplyAlpha;
            // Note: the WebGL spec states that UNPACK_PREMULTIPLY_ALPHA_WEBGL only affects
            // texImage2D and texSubImage2D, not compressedTexImage2D
            const gl = this.gl;
            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
        }
    }
    /**
     * Activate the specified texture unit.
     *
     * @param {number} textureUnit - The texture unit to activate.
     * @ignore
     */ activeTexture(textureUnit) {
        if (this.textureUnit !== textureUnit) {
            this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);
            this.textureUnit = textureUnit;
        }
    }
    /**
     * If the texture is not already bound on the currently active texture unit, bind it.
     *
     * @param {Texture} texture - The texture to bind.
     * @ignore
     */ bindTexture(texture) {
        const impl = texture.impl;
        const textureTarget = impl._glTarget;
        const textureObject = impl._glTexture;
        const textureUnit = this.textureUnit;
        const slot = this.targetToSlot[textureTarget];
        if (this.textureUnits[textureUnit][slot] !== textureObject) {
            this.gl.bindTexture(textureTarget, textureObject);
            this.textureUnits[textureUnit][slot] = textureObject;
        }
    }
    /**
     * If the texture is not bound on the specified texture unit, active the texture unit and bind
     * the texture to it.
     *
     * @param {Texture} texture - The texture to bind.
     * @param {number} textureUnit - The texture unit to activate and bind the texture to.
     * @ignore
     */ bindTextureOnUnit(texture, textureUnit) {
        const impl = texture.impl;
        const textureTarget = impl._glTarget;
        const textureObject = impl._glTexture;
        const slot = this.targetToSlot[textureTarget];
        if (this.textureUnits[textureUnit][slot] !== textureObject) {
            this.activeTexture(textureUnit);
            this.gl.bindTexture(textureTarget, textureObject);
            this.textureUnits[textureUnit][slot] = textureObject;
        }
    }
    /**
     * Update the texture parameters for a given texture if they have changed.
     *
     * @param {Texture} texture - The texture to update.
     * @ignore
     */ setTextureParameters(texture) {
        const gl = this.gl;
        const flags = texture.impl.dirtyParameterFlags;
        const target = texture.impl._glTarget;
        if (flags & TEXPROPERTY_MIN_FILTER) {
            let filter = texture._minFilter;
            if (!texture._mipmaps || texture._compressed && texture._levels.length === 1) {
                if (filter === FILTER_NEAREST_MIPMAP_NEAREST || filter === FILTER_NEAREST_MIPMAP_LINEAR) {
                    filter = FILTER_NEAREST;
                } else if (filter === FILTER_LINEAR_MIPMAP_NEAREST || filter === FILTER_LINEAR_MIPMAP_LINEAR) {
                    filter = FILTER_LINEAR;
                }
            }
            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, this.glFilter[filter]);
        }
        if (flags & TEXPROPERTY_MAG_FILTER) {
            gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, this.glFilter[texture._magFilter]);
        }
        if (flags & TEXPROPERTY_ADDRESS_U) {
            gl.texParameteri(target, gl.TEXTURE_WRAP_S, this.glAddress[texture._addressU]);
        }
        if (flags & TEXPROPERTY_ADDRESS_V) {
            gl.texParameteri(target, gl.TEXTURE_WRAP_T, this.glAddress[texture._addressV]);
        }
        if (flags & TEXPROPERTY_ADDRESS_W) {
            gl.texParameteri(target, gl.TEXTURE_WRAP_R, this.glAddress[texture._addressW]);
        }
        if (flags & TEXPROPERTY_COMPARE_ON_READ) {
            gl.texParameteri(target, gl.TEXTURE_COMPARE_MODE, texture._compareOnRead ? gl.COMPARE_REF_TO_TEXTURE : gl.NONE);
        }
        if (flags & TEXPROPERTY_COMPARE_FUNC) {
            gl.texParameteri(target, gl.TEXTURE_COMPARE_FUNC, this.glComparison[texture._compareFunc]);
        }
        if (flags & TEXPROPERTY_ANISOTROPY) {
            const ext = this.extTextureFilterAnisotropic;
            if (ext) {
                gl.texParameterf(target, ext.TEXTURE_MAX_ANISOTROPY_EXT, math.clamp(Math.round(texture._anisotropy), 1, this.maxAnisotropy));
            }
        }
    }
    /**
     * Sets the specified texture on the specified texture unit.
     *
     * @param {Texture} texture - The texture to set.
     * @param {number} textureUnit - The texture unit to set the texture on.
     * @ignore
     */ setTexture(texture, textureUnit) {
        const impl = texture.impl;
        if (!impl._glTexture) {
            impl.initialize(this, texture);
        }
        if (impl.dirtyParameterFlags > 0 || texture._needsUpload || texture._needsMipmapsUpload) {
            // Ensure the specified texture unit is active
            this.activeTexture(textureUnit);
            // Ensure the texture is bound on correct target of the specified texture unit
            this.bindTexture(texture);
            if (impl.dirtyParameterFlags) {
                this.setTextureParameters(texture);
                impl.dirtyParameterFlags = 0;
            }
            if (texture._needsUpload || texture._needsMipmapsUpload) {
                impl.upload(this, texture);
                texture._needsUpload = false;
                texture._needsMipmapsUpload = false;
            }
        } else {
            // Ensure the texture is currently bound to the correct target on the specified texture unit.
            // If the texture is already bound to the correct target on the specified unit, there's no need
            // to actually make the specified texture unit active because the texture itself does not need
            // to be updated.
            this.bindTextureOnUnit(texture, textureUnit);
        }
    }
    // function creates VertexArrayObject from list of vertex buffers
    createVertexArray(vertexBuffers) {
        let key, vao;
        // only use cache when more than 1 vertex buffer, otherwise it's unique
        const useCache = vertexBuffers.length > 1;
        if (useCache) {
            // generate unique key for the vertex buffers
            key = '';
            for(let i = 0; i < vertexBuffers.length; i++){
                const vertexBuffer = vertexBuffers[i];
                key += vertexBuffer.id + vertexBuffer.format.renderingHash;
            }
            // try to get VAO from cache
            vao = this._vaoMap.get(key);
        }
        // need to create new vao
        if (!vao) {
            // create VA object
            const gl = this.gl;
            vao = gl.createVertexArray();
            gl.bindVertexArray(vao);
            // don't capture index buffer in VAO
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
            let locZero = false;
            for(let i = 0; i < vertexBuffers.length; i++){
                // bind buffer
                const vertexBuffer = vertexBuffers[i];
                gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer.impl.bufferId);
                // for each attribute
                const elements = vertexBuffer.format.elements;
                for(let j = 0; j < elements.length; j++){
                    const e = elements[j];
                    const loc = semanticToLocation[e.name];
                    if (loc === 0) {
                        locZero = true;
                    }
                    if (e.asInt) {
                        gl.vertexAttribIPointer(loc, e.numComponents, this.glType[e.dataType], e.stride, e.offset);
                    } else {
                        gl.vertexAttribPointer(loc, e.numComponents, this.glType[e.dataType], e.normalize, e.stride, e.offset);
                    }
                    gl.enableVertexAttribArray(loc);
                    if (vertexBuffer.format.instancing) {
                        gl.vertexAttribDivisor(loc, 1);
                    }
                }
            }
            // end of VA object
            gl.bindVertexArray(null);
            // unbind any array buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            // add it to cache
            if (useCache) {
                this._vaoMap.set(key, vao);
            }
            if (!locZero) {
                Debug.warn('No vertex attribute is mapped to location 0, which might cause compatibility issues on Safari on MacOS - please use attribute SEMANTIC_POSITION or SEMANTIC_ATTR15');
            }
        }
        return vao;
    }
    unbindVertexArray() {
        // unbind VAO from device to protect it from being changed
        if (this.boundVao) {
            this.boundVao = null;
            this.gl.bindVertexArray(null);
        }
    }
    setBuffers(indexBuffer) {
        const gl = this.gl;
        let vao;
        // create VAO for specified vertex buffers
        if (this.vertexBuffers.length === 1) {
            // single VB keeps its VAO
            const vertexBuffer = this.vertexBuffers[0];
            Debug.assert(vertexBuffer.device === this, 'The VertexBuffer was not created using current GraphicsDevice');
            if (!vertexBuffer.impl.vao) {
                vertexBuffer.impl.vao = this.createVertexArray(this.vertexBuffers);
            }
            vao = vertexBuffer.impl.vao;
        } else {
            // obtain temporary VAO for multiple vertex buffers
            vao = this.createVertexArray(this.vertexBuffers);
        }
        // set active VAO
        if (this.boundVao !== vao) {
            this.boundVao = vao;
            gl.bindVertexArray(vao);
        }
        // Set the active index buffer object
        // Note: we don't cache this state and set it only when it changes, as VAO captures last bind buffer in it
        // and so we don't know what VAO sets it to.
        const bufferId = indexBuffer ? indexBuffer.impl.bufferId : null;
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferId);
    }
    draw(primitive, indexBuffer, numInstances, indirectSlot, first = true, last = true) {
        const shader = this.shader;
        if (shader) {
            this.activateShader();
            if (this.shaderValid) {
                const gl = this.gl;
                // vertex buffers
                if (first) {
                    Debug.call(()=>this.validateAttributes(this.shader, this.vertexBuffers[0]?.format, this.vertexBuffers[1]?.format));
                    this.setBuffers(indexBuffer);
                }
                // Commit the shader program variables
                let textureUnit = 0;
                const samplers = shader.impl.samplers;
                for(let i = 0, len = samplers.length; i < len; i++){
                    const sampler = samplers[i];
                    let samplerValue = sampler.scopeId.value;
                    if (!samplerValue) {
                        const samplerName = sampler.scopeId.name;
                        Debug.assert(samplerName !== 'texture_grabPass', 'Engine provided texture with sampler name \'texture_grabPass\' is not longer supported, use \'uSceneColorMap\' instead');
                        Debug.assert(samplerName !== 'uDepthMap', 'Engine provided texture with sampler name \'uDepthMap\' is not longer supported, use \'uSceneDepthMap\' instead');
                        if (samplerName === 'uSceneDepthMap') {
                            Debug.errorOnce(`A uSceneDepthMap texture is used by the shader but a scene depth texture is not available. Use CameraComponent.requestSceneDepthMap / enable Depth Grabpass on the Camera Component to enable it. Rendering [${DebugGraphics.toString()}]`);
                            samplerValue = getBuiltInTexture(this, 'white');
                        }
                        if (samplerName === 'uSceneColorMap') {
                            Debug.errorOnce(`A uSceneColorMap texture is used by the shader but a scene color texture is not available. Use CameraComponent.requestSceneColorMap / enable Color Grabpass on the Camera Component to enable it. Rendering [${DebugGraphics.toString()}]`);
                            samplerValue = getBuiltInTexture(this, 'pink');
                        }
                        // missing generic texture
                        if (!samplerValue) {
                            Debug.errorOnce(`Shader ${shader.name} requires ${samplerName} texture which was not set. Rendering [${DebugGraphics.toString()}]`, shader);
                            samplerValue = getBuiltInTexture(this, 'pink');
                        }
                    }
                    if (samplerValue instanceof Texture) {
                        const texture = samplerValue;
                        this.setTexture(texture, textureUnit);
                        if (this.renderTarget) {
                            // Set breakpoint here to debug "Source and destination textures of the draw are the same" errors
                            if (this.renderTarget._samples < 2) {
                                if (this.renderTarget.colorBuffer && this.renderTarget.colorBuffer === texture) {
                                    Debug.error('Trying to bind current color buffer as a texture', {
                                        renderTarget: this.renderTarget,
                                        texture
                                    });
                                } else if (this.renderTarget.depthBuffer && this.renderTarget.depthBuffer === texture) {
                                    Debug.error('Trying to bind current depth buffer as a texture', {
                                        texture
                                    });
                                }
                            }
                        }
                        if (sampler.slot !== textureUnit) {
                            gl.uniform1i(sampler.locationId, textureUnit);
                            sampler.slot = textureUnit;
                        }
                        textureUnit++;
                    } else {
                        sampler.array.length = 0;
                        const numTextures = samplerValue.length;
                        for(let j = 0; j < numTextures; j++){
                            const texture = samplerValue[j];
                            this.setTexture(texture, textureUnit);
                            sampler.array[j] = textureUnit;
                            textureUnit++;
                        }
                        gl.uniform1iv(sampler.locationId, sampler.array);
                    }
                }
                // Commit any updated uniforms
                const uniforms = shader.impl.uniforms;
                for(let i = 0, len = uniforms.length; i < len; i++){
                    const uniform = uniforms[i];
                    const scopeId = uniform.scopeId;
                    const uniformVersion = uniform.version;
                    const programVersion = scopeId.versionObject.version;
                    // Check the value is valid
                    if (uniformVersion.globalId !== programVersion.globalId || uniformVersion.revision !== programVersion.revision) {
                        uniformVersion.globalId = programVersion.globalId;
                        uniformVersion.revision = programVersion.revision;
                        // Call the function to commit the uniform value
                        const value = scopeId.value;
                        if (value !== null && value !== undefined) {
                            this.commitFunction[uniform.dataType](uniform, value);
                        } else {
                            Debug.warnOnce(`Shader [${shader.label}] requires uniform [${uniform.scopeId.name}] which has not been set, while rendering [${DebugGraphics.toString()}]`);
                        }
                    }
                }
                if (this.transformFeedbackBuffer) {
                    // Enable TF, start writing to out buffer
                    gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.transformFeedbackBuffer.impl.bufferId);
                    gl.beginTransformFeedback(gl.POINTS);
                }
                const mode = this.glPrimitive[primitive.type];
                const count = primitive.count;
                if (primitive.indexed) {
                    Debug.assert(indexBuffer.device === this, 'The IndexBuffer was not created using current GraphicsDevice');
                    const format = indexBuffer.impl.glFormat;
                    const offset = primitive.base * indexBuffer.bytesPerIndex;
                    if (numInstances > 0) {
                        gl.drawElementsInstanced(mode, count, format, offset, numInstances);
                    } else {
                        gl.drawElements(mode, count, format, offset);
                    }
                } else {
                    const first = primitive.base;
                    if (numInstances > 0) {
                        gl.drawArraysInstanced(mode, first, count, numInstances);
                    } else {
                        gl.drawArrays(mode, first, count);
                    }
                }
                if (this.transformFeedbackBuffer) {
                    // disable TF
                    gl.endTransformFeedback();
                    gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
                }
                this._drawCallsPerFrame++;
                this._primsPerFrame[primitive.type] += primitive.count * (numInstances > 1 ? numInstances : 1);
            }
        }
        if (last) {
            // empty array of vertex buffers
            this.clearVertexBuffer();
        }
    }
    /**
     * Clears the frame buffer of the currently set render target.
     *
     * @param {object} [options] - Optional options object that controls the behavior of the clear
     * operation defined as follows:
     * @param {number[]} [options.color] - The color to clear the color buffer to in the range 0 to
     * 1 for each component.
     * @param {number} [options.depth] - The depth value to clear the depth buffer to in the
     * range 0 to 1. Defaults to 1.
     * @param {number} [options.flags] - The buffers to clear (the types being color, depth and
     * stencil). Can be any bitwise combination of:
     *
     * - {@link CLEARFLAG_COLOR}
     * - {@link CLEARFLAG_DEPTH}
     * - {@link CLEARFLAG_STENCIL}
     *
     * @param {number} [options.stencil] - The stencil value to clear the stencil buffer to.
     * Defaults to 0.
     * @example
     * // Clear color buffer to black and depth buffer to 1
     * device.clear();
     *
     * // Clear just the color buffer to red
     * device.clear({
     *     color: [1, 0, 0, 1],
     *     flags: pc.CLEARFLAG_COLOR
     * });
     *
     * // Clear color buffer to yellow and depth to 1.0
     * device.clear({
     *     color: [1, 1, 0, 1],
     *     depth: 1,
     *     flags: pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH
     * });
     */ clear(options) {
        const defaultOptions = this.defaultClearOptions;
        options = options || defaultOptions;
        const flags = options.flags ?? defaultOptions.flags;
        if (flags !== 0) {
            const gl = this.gl;
            // Set the clear color
            if (flags & CLEARFLAG_COLOR) {
                const color = options.color ?? defaultOptions.color;
                const r = color[0];
                const g = color[1];
                const b = color[2];
                const a = color[3];
                const c = this.clearColor;
                if (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {
                    this.gl.clearColor(r, g, b, a);
                    this.clearColor.set(r, g, b, a);
                }
                this.setBlendState(BlendState.NOBLEND);
            }
            if (flags & CLEARFLAG_DEPTH) {
                // Set the clear depth
                const depth = options.depth ?? defaultOptions.depth;
                if (depth !== this.clearDepth) {
                    this.gl.clearDepth(depth);
                    this.clearDepth = depth;
                }
                this.setDepthState(DepthState.WRITEDEPTH);
            }
            if (flags & CLEARFLAG_STENCIL) {
                // Set the clear stencil
                const stencil = options.stencil ?? defaultOptions.stencil;
                if (stencil !== this.clearStencil) {
                    this.gl.clearStencil(stencil);
                    this.clearStencil = stencil;
                }
                gl.stencilMask(0xFF);
                this.stencilWriteMaskFront = 0xFF;
                this.stencilWriteMaskBack = 0xFF;
            }
            // Clear the frame buffer
            gl.clear(this.glClearFlag[flags]);
        }
    }
    submit() {
        this.gl.flush();
    }
    /**
     * Reads a block of pixels from a specified rectangle of the current color framebuffer into an
     * ArrayBufferView object.
     *
     * @param {number} x - The x-coordinate of the rectangle's lower-left corner.
     * @param {number} y - The y-coordinate of the rectangle's lower-left corner.
     * @param {number} w - The width of the rectangle, in pixels.
     * @param {number} h - The height of the rectangle, in pixels.
     * @param {ArrayBufferView} pixels - The ArrayBufferView object that holds the returned pixel
     * data.
     * @ignore
     */ readPixels(x, y, w, h, pixels) {
        const gl = this.gl;
        gl.readPixels(x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
    }
    clientWaitAsync(flags, interval_ms) {
        const gl = this.gl;
        const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
        this.submit();
        return new Promise((resolve, reject)=>{
            function test() {
                const res = gl.clientWaitSync(sync, flags, 0);
                if (res === gl.TIMEOUT_EXPIRED) {
                    // check again in a while
                    setTimeout(test, interval_ms);
                } else {
                    gl.deleteSync(sync);
                    if (res === gl.WAIT_FAILED) {
                        reject(new Error('webgl clientWaitSync sync failed'));
                    } else {
                        resolve();
                    }
                }
            }
            test();
        });
    }
    /**
     * Asynchronously reads a block of pixels from a specified rectangle of the current color framebuffer
     * into an ArrayBufferView object.
     *
     * @param {number} x - The x-coordinate of the rectangle's lower-left corner.
     * @param {number} y - The y-coordinate of the rectangle's lower-left corner.
     * @param {number} w - The width of the rectangle, in pixels.
     * @param {number} h - The height of the rectangle, in pixels.
     * @param {ArrayBufferView} pixels - The ArrayBufferView object that holds the returned pixel
     * data.
     * @ignore
     */ async readPixelsAsync(x, y, w, h, pixels) {
        const gl = this.gl;
        const impl = this.renderTarget.colorBuffer?.impl;
        const format = impl?._glFormat ?? gl.RGBA;
        const pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE;
        // create temporary (gpu-side) buffer and copy data into it
        const buf = gl.createBuffer();
        gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
        gl.bufferData(gl.PIXEL_PACK_BUFFER, pixels.byteLength, gl.STREAM_READ);
        gl.readPixels(x, y, w, h, format, pixelType, 0);
        gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
        // async wait for previous read to finish
        await this.clientWaitAsync(0, 16);
        // copy the resulting data once it's arrived
        gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
        gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, pixels);
        gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
        gl.deleteBuffer(buf);
        return pixels;
    }
    readTextureAsync(texture, x, y, width, height, options) {
        const face = options.face ?? 0;
        // create a temporary render target if needed
        const renderTarget = options.renderTarget ?? new RenderTarget({
            colorBuffer: texture,
            depth: false,
            face: face
        });
        Debug.assert(renderTarget.colorBuffer === texture);
        const buffer = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, 1, texture._format));
        const data = options.data ?? new (getPixelFormatArrayType(texture._format))(buffer);
        this.setRenderTarget(renderTarget);
        this.initRenderTarget(renderTarget);
        return new Promise((resolve, reject)=>{
            this.readPixelsAsync(x, y, width, height, data).then((data)=>{
                // destroy RT if we created it
                if (!options.renderTarget) {
                    renderTarget.destroy();
                }
                resolve(data);
            }).catch(reject);
        });
    }
    async writeTextureAsync(texture, x, y, width, height, data) {
        const gl = this.gl;
        const impl = texture.impl;
        const format = impl?._glFormat ?? gl.RGBA;
        const pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE;
        // create temporary (gpu-side) buffer and copy data into it
        const buf = gl.createBuffer();
        gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, buf);
        gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data, gl.STREAM_DRAW);
        gl.bindTexture(gl.TEXTURE_2D, impl._glTexture);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, width, height, format, pixelType, 0);
        gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
        texture._needsUpload = false;
        texture._mipmapsUploaded = false;
        // async wait for previous read to finish
        await this.clientWaitAsync(0, 16);
    }
    /**
     * Enables or disables alpha to coverage.
     *
     * @param {boolean} state - True to enable alpha to coverage and false to disable it.
     * @ignore
     */ setAlphaToCoverage(state) {
        if (this.alphaToCoverage !== state) {
            this.alphaToCoverage = state;
            if (state) {
                this.gl.enable(this.gl.SAMPLE_ALPHA_TO_COVERAGE);
            } else {
                this.gl.disable(this.gl.SAMPLE_ALPHA_TO_COVERAGE);
            }
        }
    }
    /**
     * Sets the output vertex buffer. It will be written to by a shader with transform feedback
     * varyings.
     *
     * @param {VertexBuffer} tf - The output vertex buffer.
     * @ignore
     */ setTransformFeedbackBuffer(tf) {
        if (this.transformFeedbackBuffer !== tf) {
            this.transformFeedbackBuffer = tf;
            const gl = this.gl;
            if (tf) {
                if (!this.feedback) {
                    this.feedback = gl.createTransformFeedback();
                }
                gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.feedback);
            } else {
                gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
            }
        }
    }
    /**
     * Toggles the rasterization render state. Useful with transform feedback, when you only need
     * to process the data without drawing.
     *
     * @param {boolean} on - True to enable rasterization and false to disable it.
     * @ignore
     */ setRaster(on) {
        if (this.raster !== on) {
            this.raster = on;
            if (on) {
                this.gl.disable(this.gl.RASTERIZER_DISCARD);
            } else {
                this.gl.enable(this.gl.RASTERIZER_DISCARD);
            }
        }
    }
    setStencilTest(enable) {
        if (this.stencil !== enable) {
            const gl = this.gl;
            if (enable) {
                gl.enable(gl.STENCIL_TEST);
            } else {
                gl.disable(gl.STENCIL_TEST);
            }
            this.stencil = enable;
        }
    }
    setStencilFunc(func, ref, mask) {
        if (this.stencilFuncFront !== func || this.stencilRefFront !== ref || this.stencilMaskFront !== mask || this.stencilFuncBack !== func || this.stencilRefBack !== ref || this.stencilMaskBack !== mask) {
            this.gl.stencilFunc(this.glComparison[func], ref, mask);
            this.stencilFuncFront = this.stencilFuncBack = func;
            this.stencilRefFront = this.stencilRefBack = ref;
            this.stencilMaskFront = this.stencilMaskBack = mask;
        }
    }
    setStencilFuncFront(func, ref, mask) {
        if (this.stencilFuncFront !== func || this.stencilRefFront !== ref || this.stencilMaskFront !== mask) {
            const gl = this.gl;
            gl.stencilFuncSeparate(gl.FRONT, this.glComparison[func], ref, mask);
            this.stencilFuncFront = func;
            this.stencilRefFront = ref;
            this.stencilMaskFront = mask;
        }
    }
    setStencilFuncBack(func, ref, mask) {
        if (this.stencilFuncBack !== func || this.stencilRefBack !== ref || this.stencilMaskBack !== mask) {
            const gl = this.gl;
            gl.stencilFuncSeparate(gl.BACK, this.glComparison[func], ref, mask);
            this.stencilFuncBack = func;
            this.stencilRefBack = ref;
            this.stencilMaskBack = mask;
        }
    }
    setStencilOperation(fail, zfail, zpass, writeMask) {
        if (this.stencilFailFront !== fail || this.stencilZfailFront !== zfail || this.stencilZpassFront !== zpass || this.stencilFailBack !== fail || this.stencilZfailBack !== zfail || this.stencilZpassBack !== zpass) {
            this.gl.stencilOp(this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);
            this.stencilFailFront = this.stencilFailBack = fail;
            this.stencilZfailFront = this.stencilZfailBack = zfail;
            this.stencilZpassFront = this.stencilZpassBack = zpass;
        }
        if (this.stencilWriteMaskFront !== writeMask || this.stencilWriteMaskBack !== writeMask) {
            this.gl.stencilMask(writeMask);
            this.stencilWriteMaskFront = writeMask;
            this.stencilWriteMaskBack = writeMask;
        }
    }
    setStencilOperationFront(fail, zfail, zpass, writeMask) {
        if (this.stencilFailFront !== fail || this.stencilZfailFront !== zfail || this.stencilZpassFront !== zpass) {
            this.gl.stencilOpSeparate(this.gl.FRONT, this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);
            this.stencilFailFront = fail;
            this.stencilZfailFront = zfail;
            this.stencilZpassFront = zpass;
        }
        if (this.stencilWriteMaskFront !== writeMask) {
            this.gl.stencilMaskSeparate(this.gl.FRONT, writeMask);
            this.stencilWriteMaskFront = writeMask;
        }
    }
    setStencilOperationBack(fail, zfail, zpass, writeMask) {
        if (this.stencilFailBack !== fail || this.stencilZfailBack !== zfail || this.stencilZpassBack !== zpass) {
            this.gl.stencilOpSeparate(this.gl.BACK, this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);
            this.stencilFailBack = fail;
            this.stencilZfailBack = zfail;
            this.stencilZpassBack = zpass;
        }
        if (this.stencilWriteMaskBack !== writeMask) {
            this.gl.stencilMaskSeparate(this.gl.BACK, writeMask);
            this.stencilWriteMaskBack = writeMask;
        }
    }
    setBlendState(blendState) {
        const currentBlendState = this.blendState;
        if (!currentBlendState.equals(blendState)) {
            const gl = this.gl;
            // state values to set
            const { blend, colorOp, alphaOp, colorSrcFactor, colorDstFactor, alphaSrcFactor, alphaDstFactor } = blendState;
            // enable blend
            if (currentBlendState.blend !== blend) {
                if (blend) {
                    gl.enable(gl.BLEND);
                } else {
                    gl.disable(gl.BLEND);
                }
            }
            // blend ops
            if (currentBlendState.colorOp !== colorOp || currentBlendState.alphaOp !== alphaOp) {
                const glBlendEquation = this.glBlendEquation;
                gl.blendEquationSeparate(glBlendEquation[colorOp], glBlendEquation[alphaOp]);
            }
            // blend factors
            if (currentBlendState.colorSrcFactor !== colorSrcFactor || currentBlendState.colorDstFactor !== colorDstFactor || currentBlendState.alphaSrcFactor !== alphaSrcFactor || currentBlendState.alphaDstFactor !== alphaDstFactor) {
                gl.blendFuncSeparate(this.glBlendFunctionColor[colorSrcFactor], this.glBlendFunctionColor[colorDstFactor], this.glBlendFunctionAlpha[alphaSrcFactor], this.glBlendFunctionAlpha[alphaDstFactor]);
            }
            // color write
            if (currentBlendState.allWrite !== blendState.allWrite) {
                this.gl.colorMask(blendState.redWrite, blendState.greenWrite, blendState.blueWrite, blendState.alphaWrite);
            }
            // update internal state
            currentBlendState.copy(blendState);
        }
    }
    /**
     * Set the source and destination blending factors.
     *
     * @param {number} r - The red component in the range of 0 to 1. Default value is 0.
     * @param {number} g - The green component in the range of 0 to 1. Default value is 0.
     * @param {number} b - The blue component in the range of 0 to 1. Default value is 0.
     * @param {number} a - The alpha component in the range of 0 to 1. Default value is 0.
     * @ignore
     */ setBlendColor(r, g, b, a) {
        const c = this.blendColor;
        if (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {
            this.gl.blendColor(r, g, b, a);
            c.set(r, g, b, a);
        }
    }
    setStencilState(stencilFront, stencilBack) {
        if (stencilFront || stencilBack) {
            this.setStencilTest(true);
            if (stencilFront === stencilBack) {
                // identical front/back stencil
                this.setStencilFunc(stencilFront.func, stencilFront.ref, stencilFront.readMask);
                this.setStencilOperation(stencilFront.fail, stencilFront.zfail, stencilFront.zpass, stencilFront.writeMask);
            } else {
                // front
                stencilFront ??= StencilParameters.DEFAULT;
                this.setStencilFuncFront(stencilFront.func, stencilFront.ref, stencilFront.readMask);
                this.setStencilOperationFront(stencilFront.fail, stencilFront.zfail, stencilFront.zpass, stencilFront.writeMask);
                // back
                stencilBack ??= StencilParameters.DEFAULT;
                this.setStencilFuncBack(stencilBack.func, stencilBack.ref, stencilBack.readMask);
                this.setStencilOperationBack(stencilBack.fail, stencilBack.zfail, stencilBack.zpass, stencilBack.writeMask);
            }
        } else {
            this.setStencilTest(false);
        }
    }
    setDepthState(depthState) {
        const currentDepthState = this.depthState;
        if (!currentDepthState.equals(depthState)) {
            const gl = this.gl;
            // write
            const write = depthState.write;
            if (currentDepthState.write !== write) {
                gl.depthMask(write);
            }
            // handle case where depth testing is off, but depth write is on => enable always test to depth write
            // Note on WebGL API behavior: When depth testing is disabled, writes to the depth buffer are also disabled.
            let { func, test } = depthState;
            if (!test && write) {
                test = true;
                func = FUNC_ALWAYS;
            }
            if (currentDepthState.func !== func) {
                gl.depthFunc(this.glComparison[func]);
            }
            if (currentDepthState.test !== test) {
                if (test) {
                    gl.enable(gl.DEPTH_TEST);
                } else {
                    gl.disable(gl.DEPTH_TEST);
                }
            }
            // depth bias
            const { depthBias, depthBiasSlope } = depthState;
            if (depthBias || depthBiasSlope) {
                // enable bias
                if (!this.depthBiasEnabled) {
                    this.depthBiasEnabled = true;
                    this.gl.enable(this.gl.POLYGON_OFFSET_FILL);
                }
                // values
                gl.polygonOffset(depthBiasSlope, depthBias);
            } else {
                // disable bias
                if (this.depthBiasEnabled) {
                    this.depthBiasEnabled = false;
                    this.gl.disable(this.gl.POLYGON_OFFSET_FILL);
                }
            }
            // update internal state
            currentDepthState.copy(depthState);
        }
    }
    setCullMode(cullMode) {
        if (this.cullMode !== cullMode) {
            if (cullMode === CULLFACE_NONE) {
                this.gl.disable(this.gl.CULL_FACE);
            } else {
                if (this.cullMode === CULLFACE_NONE) {
                    this.gl.enable(this.gl.CULL_FACE);
                }
                const mode = this.glCull[cullMode];
                if (this.cullFace !== mode) {
                    this.gl.cullFace(mode);
                    this.cullFace = mode;
                }
            }
            this.cullMode = cullMode;
        }
    }
    /**
     * Sets the active shader to be used during subsequent draw calls.
     *
     * @param {Shader} shader - The shader to assign to the device.
     * @param {boolean} [asyncCompile] - If true, rendering will be skipped until the shader is
     * compiled, otherwise the rendering will wait for the shader compilation to finish. Defaults
     * to false.
     */ setShader(shader, asyncCompile = false) {
        if (shader !== this.shader) {
            this.shader = shader;
            this.shaderAsyncCompile = asyncCompile;
            this.shaderValid = undefined; // need to run activation / validation
            this._shaderSwitchesPerFrame++;
        }
    }
    activateShader() {
        const { shader } = this;
        const { impl } = shader;
        if (this.shaderValid === undefined) {
            if (shader.failed) {
                this.shaderValid = false;
            } else if (!shader.ready) {
                // if the shader is async compiled and can be skipped if not ready
                if (this.shaderAsyncCompile) {
                    // if the shader is linked, finalize it
                    if (impl.isLinked(this)) {
                        if (!impl.finalize(this, shader)) {
                            shader.failed = true;
                            this.shaderValid = false;
                        }
                    } else {
                        // skip the async shader rendering
                        this.shaderValid = false;
                    }
                } else {
                    // this cannot be skipped, wait for the shader to be ready
                    if (!impl.finalize(this, shader)) {
                        shader.failed = true;
                        this.shaderValid = false;
                    }
                }
            }
        }
        if (this.shaderValid === undefined) {
            // Set the active shader
            this.gl.useProgram(impl.glProgram);
            this.shaderValid = true;
        }
    }
    /**
     * Frees memory from all vertex array objects ever allocated with this device.
     *
     * @ignore
     */ clearVertexArrayObjectCache() {
        const gl = this.gl;
        this._vaoMap.forEach((item, key, mapObj)=>{
            gl.deleteVertexArray(item);
        });
        this._vaoMap.clear();
    }
    /**
     * Sets whether the device is currently in fullscreen mode.
     *
     * @type {boolean}
     */ set fullscreen(fullscreen) {
        if (fullscreen) {
            const canvas = this.gl.canvas;
            canvas.requestFullscreen();
        } else {
            document.exitFullscreen();
        }
    }
    /**
     * Gets whether the device is currently in fullscreen mode.
     *
     * @type {boolean}
     */ get fullscreen() {
        return !!document.fullscreenElement;
    }
    // debug helper to force lost context
    debugLoseContext(sleep = 100) {
        const context = this.gl.getExtension('WEBGL_lose_context');
        context.loseContext();
        setTimeout(()=>context.restoreContext(), sleep);
    }
}

/**
 * A Null implementation of the IndexBuffer.
 *
 * @ignore
 */ class NullIndexBuffer {
    unlock(indexBuffer) {}
}

/**
 * A Null implementation of the RenderTarget.
 *
 * @ignore
 */ class NullRenderTarget {
    destroy(device) {}
    init(device, renderTarget) {}
    loseContext() {}
    resolve(device, target, color, depth) {}
}

/**
 * A Null implementation of the Shader.
 *
 * @ignore
 */ class NullShader {
    destroy(shader) {}
    loseContext() {}
    restoreContext(device, shader) {}
}

/**
 * A Null implementation of the Texture.
 *
 * @ignore
 */ class NullTexture {
    destroy(device) {}
    propertyChanged(flag) {}
    loseContext() {}
}

/**
 * A Null implementation of the VertexBuffer.
 *
 * @ignore
 */ class NullVertexBuffer {
    destroy(device) {}
    unlock(vertexBuffer) {}
}

class NullGraphicsDevice extends GraphicsDevice {
    constructor(canvas, options = {}){
        super(canvas, options);
        options = this.initOptions;
        this.isNull = true;
        this._deviceType = DEVICETYPE_NULL;
        this.samples = 1;
        this.backBuffer = new RenderTarget({
            name: 'Framebuffer',
            graphicsDevice: this,
            depth: this.initOptions.depth,
            stencil: this.supportsStencil,
            samples: this.samples
        });
        this.initDeviceCaps();
    }
    destroy() {
        super.destroy();
    }
    initDeviceCaps() {
        this.disableParticleSystem = true;
        this.precision = 'highp';
        this.maxPrecision = 'highp';
        this.maxSamples = 4;
        this.maxTextures = 16;
        this.maxTextureSize = 4096;
        this.maxCubeMapSize = 4096;
        this.maxVolumeSize = 4096;
        this.maxColorAttachments = 8;
        this.maxPixelRatio = 1;
        this.maxAnisotropy = 16;
        this.supportsUniformBuffers = false;
        this.supportsAreaLights = true;
        this.supportsGpuParticles = false;
        this.textureFloatRenderable = true;
        this.textureHalfFloatRenderable = true;
        this.supportsImageBitmap = false;
    }
    postInit() {
        super.postInit();
    }
    frameStart() {
        super.frameStart();
    }
    frameEnd() {
        super.frameEnd();
    }
    updateBegin() {}
    updateEnd() {}
    readPixels(x, y, w, h, pixels) {}
    createVertexBufferImpl(vertexBuffer, format) {
        return new NullVertexBuffer(vertexBuffer, format);
    }
    createIndexBufferImpl(indexBuffer) {
        return new NullIndexBuffer(indexBuffer);
    }
    createShaderImpl(shader) {
        return new NullShader(shader);
    }
    createTextureImpl(texture) {
        return new NullTexture(texture);
    }
    createRenderTargetImpl(renderTarget) {
        return new NullRenderTarget(renderTarget);
    }
    draw(primitive, indexBuffer, numInstances, indirectSlot, first = true, last = true) {}
    setShader(shader, asyncCompile = false) {}
    setBlendState(blendState) {}
    setDepthState(depthState) {}
    setStencilState(stencilFront, stencilBack) {}
    setBlendColor(r, g, b, a) {}
    setCullMode(cullMode) {}
    setAlphaToCoverage(state) {}
    initializeContextCaches() {
        super.initializeContextCaches();
    }
    clear(options) {}
    setViewport(x, y, w, h) {}
    setScissor(x, y, w, h) {}
    copyRenderTarget(source, dest, color, depth) {
        return true;
    }
    pushMarker(name) {}
    popMarker() {}
}

/**
 * Creates a graphics device.
 *
 * @param {HTMLCanvasElement} canvas - The canvas element.
 * @param {object} options - Graphics device options.
 * @param {string[]} [options.deviceTypes] - An array of DEVICETYPE_*** constants, defining the
 * order in which the devices are attempted to get created. Defaults to an empty array. If the
 * specified array does not contain {@link DEVICETYPE_WEBGL2}, it is internally added to its end.
 * Typically, you'd only specify {@link DEVICETYPE_WEBGPU}, or leave it empty.
 * @param {boolean} [options.antialias] - Boolean that indicates whether or not to perform
 * anti-aliasing if possible. Defaults to true.
 * @param {string} [options.displayFormat] - The display format of the canvas. Defaults to
 * {@link DISPLAYFORMAT_LDR}. Can be:
 *
 * - {@link DISPLAYFORMAT_LDR}
 * - {@link DISPLAYFORMAT_LDR_SRGB}
 * - {@link DISPLAYFORMAT_HDR}
 *
 * @param {boolean} [options.depth] - Boolean that indicates that the drawing buffer is
 * requested to have a depth buffer of at least 16 bits. Defaults to true.
 * @param {boolean} [options.stencil] - Boolean that indicates that the drawing buffer is
 * requested to have a stencil buffer of at least 8 bits. Defaults to true.
 * @param {string} [options.glslangUrl] - The URL to the glslang script. Required only if
 * user-defined shaders or shader chunk overrides are specified in GLSL and need to be transpiled to
 * WGSL for use with the {@link DEVICETYPE_WEBGPU} device type. This is not required if only the
 * engine's built-in shaders are used, as those are provided directly in WGSL. Not used for
 * {@link DEVICETYPE_WEBGL2} device type creation.
 * @param {string} [options.twgslUrl] - An url to twgsl script, required if glslangUrl was specified.
 * @param {boolean} [options.xrCompatible] - Boolean that hints to the user agent to use a
 * compatible graphics adapter for an immersive XR device.
 * @param {'default'|'high-performance'|'low-power'} [options.powerPreference] - A hint indicating
 * what configuration of GPU would be selected. Possible values are:
 *
 * - 'default': Let the user agent decide which GPU configuration is most suitable. This is the
 * default value.
 * - 'high-performance': Prioritizes rendering performance over power consumption.
 * - 'low-power': Prioritizes power saving over rendering performance.
 *
 * Defaults to 'default'.
 * @returns {Promise} - Promise object representing the created graphics device.
 * @category Graphics
 */ function createGraphicsDevice(canvas, options = {}) {
    const deviceTypes = options.deviceTypes ?? [];
    // automatically added fallbacks
    if (!deviceTypes.includes(DEVICETYPE_WEBGL2)) {
        deviceTypes.push(DEVICETYPE_WEBGL2);
    }
    if (!deviceTypes.includes(DEVICETYPE_NULL)) {
        deviceTypes.push(DEVICETYPE_NULL);
    }
    // XR compatibility if not specified
    if (platform.browser && !!navigator.xr) {
        options.xrCompatible ??= true;
    }
    // make a list of device creation functions in priority order
    const deviceCreateFuncs = [];
    for(let i = 0; i < deviceTypes.length; i++){
        const deviceType = deviceTypes[i];
        if (deviceType === DEVICETYPE_WEBGPU && window?.navigator?.gpu) {
            deviceCreateFuncs.push(()=>{
                const device = new WebgpuGraphicsDevice(canvas, options);
                return device.initWebGpu(options.glslangUrl, options.twgslUrl);
            });
        }
        if (deviceType === DEVICETYPE_WEBGL2) {
            deviceCreateFuncs.push(()=>{
                return new WebglGraphicsDevice(canvas, options);
            });
        }
        if (deviceType === DEVICETYPE_NULL) {
            deviceCreateFuncs.push(()=>{
                return new NullGraphicsDevice(canvas, options);
            });
        }
    }
    // execute each device creation function returning the first successful result
    return new Promise((resolve, reject)=>{
        let attempt = 0;
        const next = ()=>{
            if (attempt >= deviceCreateFuncs.length) {
                reject(new Error('Failed to create a graphics device'));
            } else {
                Promise.resolve(deviceCreateFuncs[attempt++]()).then((device)=>{
                    if (device) {
                        resolve(device);
                    } else {
                        next();
                    }
                }).catch((err)=>{
                    console.log(err);
                    next();
                });
            }
        };
        next();
    });
}

let id$6 = 0;
/**
 * An index buffer stores index values into a {@link VertexBuffer}. Indexed graphical primitives
 * can normally utilize less memory that unindexed primitives (if vertices are shared).
 *
 * Typically, index buffers are set on {@link Mesh} objects.
 *
 * @category Graphics
 */ class IndexBuffer {
    /**
     * Create a new IndexBuffer instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this index buffer.
     * @param {number} format - The type of each index to be stored in the index buffer. Can be:
     *
     * - {@link INDEXFORMAT_UINT8}
     * - {@link INDEXFORMAT_UINT16}
     * - {@link INDEXFORMAT_UINT32}
     * @param {number} numIndices - The number of indices to be stored in the index buffer.
     * @param {number} [usage] - The usage type of the vertex buffer. Can be:
     *
     * - {@link BUFFER_DYNAMIC}
     * - {@link BUFFER_STATIC}
     * - {@link BUFFER_STREAM}
     *
     * Defaults to {@link BUFFER_STATIC}.
     * @param {ArrayBuffer} [initialData] - Initial data. If left unspecified, the index buffer
     * will be initialized to zeros.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {boolean} [options.storage] - Defines if the index buffer can be used as a storage
     * buffer by a compute shader. Defaults to false. Only supported on WebGPU.
     * @example
     * // Create an index buffer holding 3 16-bit indices. The buffer is marked as
     * // static, hinting that the buffer will never be modified.
     * const indices = new UInt16Array([0, 1, 2]);
     * const indexBuffer = new pc.IndexBuffer(graphicsDevice,
     *                                        pc.INDEXFORMAT_UINT16,
     *                                        3,
     *                                        pc.BUFFER_STATIC,
     *                                        indices);
     */ constructor(graphicsDevice, format, numIndices, usage = BUFFER_STATIC, initialData, options){
        // By default, index buffers are static (better for performance since buffer data can be cached in VRAM)
        this.device = graphicsDevice;
        this.format = format;
        this.numIndices = numIndices;
        this.usage = usage;
        this.id = id$6++;
        this.impl = graphicsDevice.createIndexBufferImpl(this, options);
        // Allocate the storage
        const bytesPerIndex = typedArrayIndexFormatsByteSize[format];
        this.bytesPerIndex = bytesPerIndex;
        this.numBytes = this.numIndices * bytesPerIndex;
        if (initialData) {
            this.setData(initialData);
        } else {
            this.storage = new ArrayBuffer(this.numBytes);
        }
        this.adjustVramSizeTracking(graphicsDevice._vram, this.numBytes);
        this.device.buffers.push(this);
    }
    /**
     * Frees resources associated with this index buffer.
     */ destroy() {
        // stop tracking the index buffer
        const device = this.device;
        const idx = device.buffers.indexOf(this);
        if (idx !== -1) {
            device.buffers.splice(idx, 1);
        }
        if (this.device.indexBuffer === this) {
            this.device.indexBuffer = null;
        }
        if (this.impl.initialized) {
            this.impl.destroy(device);
            this.adjustVramSizeTracking(device._vram, -this.storage.byteLength);
        }
    }
    adjustVramSizeTracking(vram, size) {
        Debug.trace(TRACEID_VRAM_IB, `${this.id} size: ${size} vram.ib: ${vram.ib} => ${vram.ib + size}`);
        vram.ib += size;
    }
    /**
     * Called when the rendering context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        this.impl.loseContext();
    }
    /**
     * Returns the data format of the specified index buffer.
     *
     * @returns {number} The data format of the specified index buffer. Can be:
     *
     * - {@link INDEXFORMAT_UINT8}
     * - {@link INDEXFORMAT_UINT16}
     * - {@link INDEXFORMAT_UINT32}
     */ getFormat() {
        return this.format;
    }
    /**
     * Returns the number of indices stored in the specified index buffer.
     *
     * @returns {number} The number of indices stored in the specified index buffer.
     */ getNumIndices() {
        return this.numIndices;
    }
    /**
     * Gives access to the block of memory that stores the buffer's indices.
     *
     * @returns {ArrayBuffer} A contiguous block of memory where index data can be written to.
     */ lock() {
        return this.storage;
    }
    /**
     * Signals that the block of memory returned by a call to the lock function is ready to be
     * given to the graphics hardware. Only unlocked index buffers can be set on the currently
     * active device.
     */ unlock() {
        // Upload the new index data
        this.impl.unlock(this);
    }
    /**
     * Set preallocated data on the index buffer.
     *
     * @param {ArrayBuffer} data - The index data to set.
     * @returns {boolean} True if the data was set successfully, false otherwise.
     * @ignore
     */ setData(data) {
        if (data.byteLength !== this.numBytes) {
            Debug.error(`IndexBuffer: wrong initial data size: expected ${this.numBytes}, got ${data.byteLength}`);
            return false;
        }
        this.storage = data;
        this.unlock();
        return true;
    }
    /**
     * Get the appropriate typed array from an index buffer.
     *
     * @returns {Uint8Array|Uint16Array|Uint32Array} The typed array containing the index data.
     * @private
     */ _lockTypedArray() {
        const lock = this.lock();
        const indices = this.format === INDEXFORMAT_UINT32 ? new Uint32Array(lock) : this.format === INDEXFORMAT_UINT16 ? new Uint16Array(lock) : new Uint8Array(lock);
        return indices;
    }
    /**
     * Copies the specified number of elements from data into index buffer. Optimized for
     * performance from both typed array as well as array.
     *
     * @param {Uint8Array|Uint16Array|Uint32Array|number[]} data - The data to write.
     * @param {number} count - The number of indices to write.
     * @ignore
     */ writeData(data, count) {
        const indices = this._lockTypedArray();
        // if data contains more indices than needed, copy from its subarray
        if (data.length > count) {
            // if data is typed array
            if (ArrayBuffer.isView(data)) {
                data = data.subarray(0, count);
                indices.set(data);
            } else {
                // data is array, copy right amount manually
                for(let i = 0; i < count; i++){
                    indices[i] = data[i];
                }
            }
        } else {
            // copy whole data
            indices.set(data);
        }
        this.unlock();
    }
    /**
     * Copies index data from index buffer into provided data array.
     *
     * @param {Uint8Array|Uint16Array|Uint32Array|number[]} data - The data array to write to.
     * @returns {number} The number of indices read.
     * @ignore
     */ readData(data) {
        // note: there is no need to unlock this buffer, as we are only reading from it
        const indices = this._lockTypedArray();
        const count = this.numIndices;
        if (ArrayBuffer.isView(data)) {
            // destination data is typed array
            data.set(indices);
        } else {
            // data is array, copy right amount manually
            data.length = 0;
            for(let i = 0; i < count; i++){
                data[i] = indices[i];
            }
        }
        return count;
    }
}

/**
 * @import { GraphicsDevice } from '../graphics/graphics-device.js'
 * @import { RenderTarget } from '../graphics/render-target.js'
 * @import { Texture } from './texture.js'
 */ class ColorAttachmentOps {
    constructor(){
        /**
     * A color used to clear the color attachment when the clear is enabled, specified in sRGB space.
     */ this.clearValue = new Color(0, 0, 0, 1);
        /**
     * A color used to clear the color attachment when the clear is enabled, specified in linear
     * space.
     */ this.clearValueLinear = new Color(0, 0, 0, 1);
        /**
     * True if the attachment should be cleared before rendering, false to preserve
     * the existing content.
     */ this.clear = false;
        /**
     * True if the attachment needs to be stored after the render pass. False if it can be
     * discarded. Note: This relates to the surface that is getting rendered to, and can be either
     * single or multi-sampled. Further, if a multi-sampled surface is used, the resolve flag
     * further specifies if this gets resolved to a single-sampled surface. This behavior matches
     * the WebGPU specification.
     *
     * @type {boolean}
     */ this.store = false;
        /**
     * True if the attachment needs to be resolved.
     *
     * @type {boolean}
     */ this.resolve = true;
        /**
     * True if the attachment needs to have mipmaps generated.
     *
     * @type {boolean}
     */ this.genMipmaps = false;
    }
}
class DepthStencilAttachmentOps {
    constructor(){
        /**
     * A depth value used to clear the depth attachment when the clear is enabled.
     */ this.clearDepthValue = 1;
        /**
     * A stencil value used to clear the stencil attachment when the clear is enabled.
     */ this.clearStencilValue = 0;
        /**
     * True if the depth attachment should be cleared before rendering, false to preserve
     * the existing content.
     */ this.clearDepth = false;
        /**
     * True if the stencil attachment should be cleared before rendering, false to preserve
     * the existing content.
     */ this.clearStencil = false;
        /**
     * True if the depth attachment needs to be stored after the render pass. False
     * if it can be discarded.
     *
     * @type {boolean}
     */ this.storeDepth = false;
        /**
     * True if the depth attachment needs to be resolved.
     *
     * @type {boolean}
     */ this.resolveDepth = false;
        /**
     * True if the stencil attachment needs to be stored after the render pass. False
     * if it can be discarded.
     *
     * @type {boolean}
     */ this.storeStencil = false;
    }
}
/**
 * A render pass represents a node in the frame graph, and encapsulates a system which
 * renders to a render target using an execution callback.
 *
 * @ignore
 */ class RenderPass {
    /**
     * Color attachment operations for the first color attachment.
     *
     * @type {ColorAttachmentOps}
     */ get colorOps() {
        return this.colorArrayOps[0];
    }
    /**
     * Creates an instance of the RenderPass.
     *
     * @param {GraphicsDevice} graphicsDevice - The
     * graphics device.
     */ constructor(graphicsDevice){
        /**
     * True if the render pass is enabled.
     *
     * @type {boolean}
     * @private
     */ this._enabled = true;
        /**
     * True if the render pass start is skipped. This means the render pass is merged into the
     * previous one.
     *
     * @type {boolean}
     * @private
     */ this._skipStart = false;
        /**
     * True if the render pass end is skipped. This means the following render pass is merged into
     * this one.
     *
     * @type {boolean}
     * @private
     */ this._skipEnd = false;
        /**
     * True if the render pass is enabled and execute function will be called. Note that before and
     * after functions are called regardless of this flag.
     */ this.executeEnabled = true;
        /**
     * Number of samples. 0 if no render target, otherwise number of samples from the render target,
     * or the main framebuffer if render target is null.
     *
     * @type {number}
     */ this.samples = 0;
        /**
     * Array of color attachment operations. The first element corresponds to the color attachment
     * 0, and so on.
     *
     * @type {Array<ColorAttachmentOps>}
     */ this.colorArrayOps = [];
        /**
     * If true, this pass might use dynamically rendered cubemaps. Use for a case where rendering to cubemap
     * faces is interleaved with rendering to shadows, to avoid generating cubemap mipmaps. This will likely
     * be retired when render target dependency tracking gets implemented.
     *
     * @type {boolean}
     */ this.requiresCubemaps = true;
        /**
     * True if the render pass uses the full viewport / scissor for rendering into the render target.
     *
     * @type {boolean}
     */ this.fullSizeClearRect = true;
        /**
     * Render passes which need to be executed before this pass.
     *
     * @type {RenderPass[]}
     */ this.beforePasses = [];
        /**
     * Render passes which need to be executed after this pass.
     *
     * @type {RenderPass[]}
     */ this.afterPasses = [];
        Debug.assert(graphicsDevice);
        this.device = graphicsDevice;
    }
    set name(value) {
        this._name = value;
    }
    get name() {
        if (!this._name) {
            this._name = this.constructor.name;
        }
        return this._name;
    }
    set scaleX(value) {
        Debug.assert(this._options, 'The render pass needs to be initialized first.');
        this._options.scaleX = value;
    }
    get scaleX() {
        return this._options.scaleX;
    }
    set scaleY(value) {
        Debug.assert(this._options, 'The render pass needs to be initialized first.');
        this._options.scaleY = value;
    }
    get scaleY() {
        return this._options.scaleY;
    }
    set options(value) {
        this._options = value;
        // sanitize options
        if (value) {
            this.scaleX = this.scaleX ?? 1;
            this.scaleY = this.scaleY ?? 1;
        }
    }
    get options() {
        return this._options;
    }
    /**
     * @param {RenderTarget|null} [renderTarget] - The render target to render into (output). This
     * function should be called only for render passes which use render target, or passes which
     * render directly into the default framebuffer, in which case a null or undefined render
     * target is expected.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {Texture} [options.resizeSource] - A texture to use as a source for the automatic
     * render target resize operation. If not provided, no automatic resizing takes place.
     * @param {number} [options.scaleX] - The scale factor for the render target width. Defaults to 1.
     * @param {number} [options.scaleY] - The scale factor for the render target height. Defaults to 1.
     */ init(renderTarget = null, options) {
        this.options = options;
        // null represents the default framebuffer
        this.renderTarget = renderTarget;
        // defaults depend on multisampling
        this.samples = Math.max(this.renderTarget ? this.renderTarget.samples : this.device.samples, 1);
        // allocate ops only when render target is used (when this function was called)
        this.allocateAttachments();
        // allow for post-init setup
        this.postInit();
    }
    allocateAttachments() {
        const rt = this.renderTarget;
        // depth
        this.depthStencilOps = new DepthStencilAttachmentOps();
        // if a RT is used (so not a backbuffer) that was created with a user supplied depth buffer,
        // assume the user wants to use its content, and so store it by default
        if (rt?.depthBuffer) {
            this.depthStencilOps.storeDepth = true;
        }
        // color
        const numColorOps = rt ? rt._colorBuffers?.length ?? 0 : 1;
        this.colorArrayOps.length = 0;
        for(let i = 0; i < numColorOps; i++){
            const colorOps = new ColorAttachmentOps();
            this.colorArrayOps[i] = colorOps;
            // if rendering to single-sampled buffer, this buffer needs to be stored
            if (this.samples === 1) {
                colorOps.store = true;
                colorOps.resolve = false;
            }
            // if render target needs mipmaps
            const colorBuffer = this.renderTarget?._colorBuffers?.[i];
            if (this.renderTarget?.mipmaps && colorBuffer?.mipmaps) {
                const intFormat = isIntegerPixelFormat(colorBuffer._format);
                colorOps.genMipmaps = !intFormat; // no automatic mipmap generation for integer formats
            }
        }
    }
    destroy() {}
    postInit() {}
    frameUpdate() {
        // resize the render target if needed
        if (this._options && this.renderTarget) {
            const resizeSource = this._options.resizeSource ?? this.device.backBuffer;
            const width = Math.floor(resizeSource.width * this.scaleX);
            const height = Math.floor(resizeSource.height * this.scaleY);
            this.renderTarget.resize(width, height);
        }
    }
    before() {}
    execute() {}
    after() {}
    onEnable() {}
    onDisable() {}
    set enabled(value) {
        if (this._enabled !== value) {
            this._enabled = value;
            if (value) {
                this.onEnable();
            } else {
                this.onDisable();
            }
        }
    }
    get enabled() {
        return this._enabled;
    }
    /**
     * Mark render pass as clearing the full color buffer.
     *
     * @param {Color|undefined} color - The color to clear to, or undefined to preserve the existing
     * content.
     */ setClearColor(color) {
        // in case of MRT, we clear all color buffers.
        // TODO: expose per color buffer clear parameters on the camera, and copy them here.
        const count = this.colorArrayOps.length;
        for(let i = 0; i < count; i++){
            const colorOps = this.colorArrayOps[i];
            if (color) {
                colorOps.clearValue.copy(color);
                colorOps.clearValueLinear.linear(color);
            }
            colorOps.clear = !!color;
        }
    }
    /**
     * Mark render pass as clearing the full depth buffer.
     *
     * @param {number|undefined} depthValue - The depth value to clear to, or undefined to preserve
     * the existing content.
     */ setClearDepth(depthValue) {
        if (depthValue) {
            this.depthStencilOps.clearDepthValue = depthValue;
        }
        this.depthStencilOps.clearDepth = depthValue !== undefined;
    }
    /**
     * Mark render pass as clearing the full stencil buffer.
     *
     * @param {number|undefined} stencilValue - The stencil value to clear to, or undefined to
     * preserve the existing content.
     */ setClearStencil(stencilValue) {
        if (stencilValue) {
            this.depthStencilOps.clearStencilValue = stencilValue;
        }
        this.depthStencilOps.clearStencil = stencilValue !== undefined;
    }
    /**
     * Render the render pass
     */ render() {
        if (this.enabled) {
            const device = this.device;
            const realPass = this.renderTarget !== undefined;
            Debug.call(()=>{
                this.log(device, device.renderPassIndex);
            });
            this.before();
            if (this.executeEnabled) {
                if (realPass && !this._skipStart) {
                    device.startRenderPass(this);
                }
                this.execute();
                if (realPass && !this._skipEnd) {
                    device.endRenderPass(this);
                }
            }
            this.after();
            device.renderPassIndex++;
        }
    }
    log(device, index = 0) {
        if (Tracing.get(TRACEID_RENDER_PASS) || Tracing.get(TRACEID_RENDER_PASS_DETAIL)) {
            const rt = this.renderTarget ?? (this.renderTarget === null ? device.backBuffer : null);
            const isBackBuffer = !!rt?.impl.assignedColorTexture || rt?.impl.suppliedColorFramebuffer !== undefined;
            const numColor = rt?._colorBuffers?.length ?? (isBackBuffer ? 1 : 0);
            const hasDepth = rt?.depth;
            const hasStencil = rt?.stencil;
            const mipLevel = rt?.mipLevel;
            const rtInfo = !rt ? '' : ` RT: ${rt ? rt.name : 'NULL'} ` + `${numColor > 0 ? `[Color${numColor > 1 ? ` x ${numColor}` : ''}]` : ''}` + `${hasDepth ? '[Depth]' : ''}` + `${hasStencil ? '[Stencil]' : ''}` + ` ${rt.width} x ${rt.height}` + `${this.samples > 0 ? ` samples: ${this.samples}` : ''}` + `${mipLevel > 0 ? ` mipLevel: ${mipLevel}` : ''}`;
            const indexString = this._skipStart ? '++' : index.toString().padEnd(2, ' ');
            Debug.trace(TRACEID_RENDER_PASS, `${indexString}: ${this.name.padEnd(20, ' ')}` + `${this.executeEnabled ? '' : ' DISABLED '}${rtInfo.padEnd(30)}`);
            for(let i = 0; i < numColor; i++){
                const colorOps = this.colorArrayOps[i];
                const colorFormat = pixelFormatInfo.get(isBackBuffer ? device.backBufferFormat : rt.getColorBuffer(i).format)?.name;
                Debug.trace(TRACEID_RENDER_PASS_DETAIL, `    color[${i}]: ` + `${colorOps.clear ? 'clear' : 'load'}->` + `${colorOps.store ? 'store' : 'discard'} ` + `${colorOps.resolve ? 'resolve ' : ''}` + `${colorOps.genMipmaps ? 'mipmaps ' : ''}` + ` [format: ${colorFormat}]` + ` ${colorOps.clear ? `[clear: ${colorOps.clearValue.toString(true, true)}]` : ''}`);
            }
            if (this.depthStencilOps) {
                const depthFormat = `${rt.depthBuffer ? ` [format: ${pixelFormatInfo.get(rt.depthBuffer.format)?.name}]` : ''}`;
                if (hasDepth) {
                    Debug.trace(TRACEID_RENDER_PASS_DETAIL, '    depthOps: ' + `${this.depthStencilOps.clearDepth ? 'clear' : 'load'}->` + `${this.depthStencilOps.storeDepth ? 'store' : 'discard'}` + `${this.depthStencilOps.resolveDepth ? ' resolve' : ''}` + `${depthFormat}` + `${this.depthStencilOps.clearDepth ? ` [clear: ${this.depthStencilOps.clearDepthValue}]` : ''}`);
                }
                if (hasStencil) {
                    Debug.trace(TRACEID_RENDER_PASS_DETAIL, '    stencOps: ' + `${this.depthStencilOps.clearStencil ? 'clear' : 'load'}->` + `${this.depthStencilOps.storeStencil ? 'store' : 'discard'}` + `${depthFormat}` + `${this.depthStencilOps.clearStencil ? ` [clear: ${this.depthStencilOps.clearStencilValue}]` : ''}`);
                }
            }
        }
    }
}

/**
 * @import { ScopeId } from './scope-id.js'
 * @import { VertexBuffer } from './vertex-buffer.js'
 * @import { VertexFormat } from './vertex-format.js'
 */ function set1(a) {
    this.array[this.index] = a;
}
function set2(a, b) {
    this.array[this.index] = a;
    this.array[this.index + 1] = b;
}
function set3(a, b, c) {
    this.array[this.index] = a;
    this.array[this.index + 1] = b;
    this.array[this.index + 2] = c;
}
function set4(a, b, c, d) {
    this.array[this.index] = a;
    this.array[this.index + 1] = b;
    this.array[this.index + 2] = c;
    this.array[this.index + 3] = d;
}
function arraySet1(index, inputArray, inputIndex) {
    this.array[index] = inputArray[inputIndex];
}
function arraySet2(index, inputArray, inputIndex) {
    this.array[index] = inputArray[inputIndex];
    this.array[index + 1] = inputArray[inputIndex + 1];
}
function arraySet3(index, inputArray, inputIndex) {
    this.array[index] = inputArray[inputIndex];
    this.array[index + 1] = inputArray[inputIndex + 1];
    this.array[index + 2] = inputArray[inputIndex + 2];
}
function arraySet4(index, inputArray, inputIndex) {
    this.array[index] = inputArray[inputIndex];
    this.array[index + 1] = inputArray[inputIndex + 1];
    this.array[index + 2] = inputArray[inputIndex + 2];
    this.array[index + 3] = inputArray[inputIndex + 3];
}
function arrayGet1(offset, outputArray, outputIndex) {
    outputArray[outputIndex] = this.array[offset];
}
function arrayGet2(offset, outputArray, outputIndex) {
    outputArray[outputIndex] = this.array[offset];
    outputArray[outputIndex + 1] = this.array[offset + 1];
}
function arrayGet3(offset, outputArray, outputIndex) {
    outputArray[outputIndex] = this.array[offset];
    outputArray[outputIndex + 1] = this.array[offset + 1];
    outputArray[outputIndex + 2] = this.array[offset + 2];
}
function arrayGet4(offset, outputArray, outputIndex) {
    outputArray[outputIndex] = this.array[offset];
    outputArray[outputIndex + 1] = this.array[offset + 1];
    outputArray[outputIndex + 2] = this.array[offset + 2];
    outputArray[outputIndex + 3] = this.array[offset + 3];
}
/**
 * Helps with accessing a specific vertex attribute.
 *
 * @category Graphics
 * @ignore
 */ class VertexIteratorAccessor {
    /**
     * Create a new VertexIteratorAccessor instance.
     *
     * @param {ArrayBuffer} buffer - The vertex buffer containing the attribute to be accessed.
     * @param {object} vertexElement - The vertex attribute to be accessed.
     * @param {string} vertexElement.name - The meaning of the vertex element. This is used to link
     * the vertex data to a shader input. Can be:
     *
     * - {@link SEMANTIC_POSITION}
     * - {@link SEMANTIC_NORMAL}
     * - {@link SEMANTIC_TANGENT}
     * - {@link SEMANTIC_BLENDWEIGHT}
     * - {@link SEMANTIC_BLENDINDICES}
     * - {@link SEMANTIC_COLOR}
     * - {@link SEMANTIC_TEXCOORD0}
     * - {@link SEMANTIC_TEXCOORD1}
     * - {@link SEMANTIC_TEXCOORD2}
     * - {@link SEMANTIC_TEXCOORD3}
     * - {@link SEMANTIC_TEXCOORD4}
     * - {@link SEMANTIC_TEXCOORD5}
     * - {@link SEMANTIC_TEXCOORD6}
     * - {@link SEMANTIC_TEXCOORD7}
     *
     * If vertex data has a meaning other that one of those listed above, use the user-defined
     * semantics: {@link SEMANTIC_ATTR0} to {@link SEMANTIC_ATTR15}.
     * @param {number} vertexElement.numComponents - The number of components of the vertex
     * attribute. Can be 1, 2, 3 or 4.
     * @param {number} vertexElement.dataType - The data type of the attribute. Can be:
     *
     * - {@link TYPE_INT8}
     * - {@link TYPE_UINT8}
     * - {@link TYPE_INT16}
     * - {@link TYPE_UINT16}
     * - {@link TYPE_INT32}
     * - {@link TYPE_UINT32}
     * - {@link TYPE_FLOAT32}
     * @param {boolean} vertexElement.normalize - If true, vertex attribute data will be mapped
     * from a 0 to 255 range down to 0 to 1 when fed to a shader. If false, vertex attribute data
     * is left unchanged. If this property is unspecified, false is assumed.
     * @param {number} vertexElement.offset - The number of initial bytes at the start of a vertex
     * that are not relevant to this attribute.
     * @param {number} vertexElement.stride - The number of total bytes that are between the start
     * of one vertex, and the start of the next.
     * @param {ScopeId} vertexElement.scopeId - The shader input variable corresponding to the
     * attribute.
     * @param {number} vertexElement.size - The size of the attribute in bytes.
     * @param {VertexFormat} vertexFormat - A vertex format that defines the layout of vertex data
     * inside the buffer.
     */ constructor(buffer, vertexElement, vertexFormat){
        this.index = 0;
        this.numComponents = vertexElement.numComponents;
        // create the typed array based on the element data type
        if (vertexFormat.interleaved) {
            this.array = new typedArrayTypes[vertexElement.dataType](buffer, vertexElement.offset);
        } else {
            this.array = new typedArrayTypes[vertexElement.dataType](buffer, vertexElement.offset, vertexFormat.vertexCount * vertexElement.numComponents);
        }
        // BYTES_PER_ELEMENT is on the instance and constructor for Chrome, Safari and Firefox, but just the constructor for Opera
        this.stride = vertexElement.stride / this.array.constructor.BYTES_PER_ELEMENT;
        // Methods
        switch(vertexElement.numComponents){
            case 1:
                this.set = set1;
                this.getToArray = arrayGet1;
                this.setFromArray = arraySet1;
                break;
            case 2:
                this.set = set2;
                this.getToArray = arrayGet2;
                this.setFromArray = arraySet2;
                break;
            case 3:
                this.set = set3;
                this.getToArray = arrayGet3;
                this.setFromArray = arraySet3;
                break;
            case 4:
                this.set = set4;
                this.getToArray = arrayGet4;
                this.setFromArray = arraySet4;
                break;
        }
    }
    /**
     * Get a attribute component at the iterator's current index.
     *
     * @param {number} offset - The component offset. Should be either 0, 1, 2, or 3.
     * @returns {number} The value of a attribute component.
     */ get(offset) {
        return this.array[this.index + offset];
    }
    /**
     * Set all the attribute components at the iterator's current index.
     *
     * @param {number} a - The first component value.
     * @param {number} [b] - The second component value (if applicable).
     * @param {number} [c] - The third component value (if applicable).
     * @param {number} [d] - The fourth component value (if applicable).
     */ set(a, b, c, d) {
    // Will be replaced with specialized implementation based on number of components
    }
    /**
     * Read attribute components to an output array.
     *
     * @param {number} offset - The component offset at which to read data from the buffer. Will be
     * used instead of the iterator's current index.
     * @param {number[]|ArrayBufferView} outputArray - The output array to write data into.
     * @param {number} outputIndex - The output index at which to write into the output array.
     */ getToArray(offset, outputArray, outputIndex) {
    // Will be replaced with specialized implementation based on number of components
    }
    /**
     * Write attribute components from an input array.
     *
     * @param {number} index - The starting index at which to write data into the buffer. Will be
     * used instead of the iterator's current index.
     * @param {number[]|ArrayBufferView} inputArray - The input array to read data from.
     * @param {number} inputIndex - The input index at which to read from the input array.
     */ setFromArray(index, inputArray, inputIndex) {
    // Will be replaced with specialized implementation based on number of components
    }
}
/**
 * A vertex iterator simplifies the process of writing vertex data to a vertex buffer.
 *
 * @category Graphics
 */ class VertexIterator {
    /**
     * Create a new VertexIterator instance.
     *
     * @param {VertexBuffer} vertexBuffer - The vertex buffer to be iterated.
     */ constructor(vertexBuffer){
        // Store the vertex buffer
        this.vertexBuffer = vertexBuffer;
        this.vertexFormatSize = vertexBuffer.getFormat().size;
        // Lock the vertex buffer
        this.buffer = this.vertexBuffer.lock();
        // Create an empty list
        this.accessors = [];
        /**
         * The vertex buffer elements.
         *
         * @type {Object<string, VertexIteratorAccessor>}
         */ this.element = {};
        // Add a new 'setter' function for each element
        const vertexFormat = this.vertexBuffer.getFormat();
        for(let i = 0; i < vertexFormat.elements.length; i++){
            const vertexElement = vertexFormat.elements[i];
            this.accessors[i] = new VertexIteratorAccessor(this.buffer, vertexElement, vertexFormat);
            this.element[vertexElement.name] = this.accessors[i];
        }
    }
    /**
     * Moves the vertex iterator on to the next vertex.
     *
     * @param {number} [count] - Number of steps to move on when calling next. Defaults to 1.
     * @example
     * const iterator = new pc.VertexIterator(vertexBuffer);
     * iterator.element[pc.SEMANTIC_POSITION].set(-0.9, -0.9, 0.0);
     * iterator.element[pc.SEMANTIC_COLOR].set(255, 0, 0, 255);
     * iterator.next();
     * iterator.element[pc.SEMANTIC_POSITION].set(0.9, -0.9, 0.0);
     * iterator.element[pc.SEMANTIC_COLOR].set(0, 255, 0, 255);
     * iterator.next();
     * iterator.element[pc.SEMANTIC_POSITION].set(0.0, 0.9, 0.0);
     * iterator.element[pc.SEMANTIC_COLOR].set(0, 0, 255, 255);
     * iterator.end();
     */ next(count = 1) {
        let i = 0;
        const accessors = this.accessors;
        const numAccessors = this.accessors.length;
        while(i < numAccessors){
            const accessor = accessors[i++];
            accessor.index += count * accessor.stride;
        }
    }
    /**
     * Notifies the vertex buffer being iterated that writes are complete. Internally the vertex
     * buffer is unlocked and vertex data is uploaded to video memory.
     *
     * @example
     * const iterator = new pc.VertexIterator(vertexBuffer);
     * iterator.element[pc.SEMANTIC_POSITION].set(-0.9, -0.9, 0.0);
     * iterator.element[pc.SEMANTIC_COLOR].set(255, 0, 0, 255);
     * iterator.next();
     * iterator.element[pc.SEMANTIC_POSITION].set(0.9, -0.9, 0.0);
     * iterator.element[pc.SEMANTIC_COLOR].set(0, 255, 0, 255);
     * iterator.next();
     * iterator.element[pc.SEMANTIC_POSITION].set(0.0, 0.9, 0.0);
     * iterator.element[pc.SEMANTIC_COLOR].set(0, 0, 255, 255);
     * iterator.end();
     */ end() {
        // Unlock the vertex buffer
        this.vertexBuffer.unlock();
    }
    /**
     * Copies data for specified semantic into vertex buffer. Works with both interleaved (slower)
     * and non-interleaved (fast) vertex buffers.
     *
     * @param {string} semantic - The semantic of the vertex element to set.
     * @param {number[]|ArrayBufferView} data - The data to set.
     * @param {number} numVertices - The number of vertices to write.
     * @ignore
     */ writeData(semantic, data, numVertices) {
        const element = this.element[semantic];
        if (element) {
            if (numVertices > this.vertexBuffer.numVertices) {
                Debug.error(`NumVertices provided to setData: ${numVertices} is larger than space in VertexBuffer: ${this.vertexBuffer.numVertices}`);
                // avoid overwrite
                numVertices = this.vertexBuffer.numVertices;
            }
            const numComponents = element.numComponents;
            // copy data to interleaved buffer by looping over vertices and copying them manually
            if (this.vertexBuffer.getFormat().interleaved) {
                let index = 0;
                for(let i = 0; i < numVertices; i++){
                    element.setFromArray(index, data, i * numComponents);
                    index += element.stride;
                }
            } else {
                // if data contains more  data than needed, copy from its subarray
                if (data.length > numVertices * numComponents) {
                    const copyCount = numVertices * numComponents;
                    // if data is typed array
                    if (ArrayBuffer.isView(data)) {
                        data = data.subarray(0, copyCount);
                        element.array.set(data);
                    } else {
                        // data is array, copy right amount manually
                        for(let i = 0; i < copyCount; i++){
                            element.array[i] = data[i];
                        }
                    }
                } else {
                    // copy whole data
                    element.array.set(data);
                }
            }
        }
    }
    /**
     * Function to extract elements of a specified semantic from vertex buffer into flat array
     * (data). Works with both interleaved (slower) and non-interleaved (fast) vertex buffers.
     * Returns number of vertices. Note: when data is a typed array and is smaller than needed,
     * only part of the data gets copied out (typed arrays ignore read/write out of range).
     *
     * @param {string} semantic - The semantic of the vertex element to read.
     * @param {number[]|ArrayBufferView} data - The array to receive the data.
     * @returns {number} The number of vertices read.
     * @ignore
     */ readData(semantic, data) {
        const element = this.element[semantic];
        let count = 0;
        if (element) {
            count = this.vertexBuffer.numVertices;
            let i;
            const numComponents = element.numComponents;
            if (this.vertexBuffer.getFormat().interleaved) {
                // extract data from interleaved buffer by looping over vertices and copying them manually
                if (Array.isArray(data)) {
                    data.length = 0;
                }
                element.index = 0;
                let offset = 0;
                for(i = 0; i < count; i++){
                    element.getToArray(offset, data, i * numComponents);
                    offset += element.stride;
                }
            } else {
                if (ArrayBuffer.isView(data)) {
                    // destination data is typed array
                    data.set(element.array);
                } else {
                    // destination data is array
                    data.length = 0;
                    const copyCount = count * numComponents;
                    for(i = 0; i < copyCount; i++){
                        data[i] = element.array[i];
                    }
                }
            }
        }
        return count;
    }
}

/**
 * @import { EventHandler } from '../../core/event-handler.js';
 */ /**
 * @callback HttpResponseCallback
 * Callback used by {@link Http#get}, {@link Http#post}, {@link Http#put}, {@link Http#del}, and
 * {@link Http#request}.
 * @param {number|string|Error|null} err - The error code, message, or exception in the case where
 * the request fails.
 * @param {any} [response] - The response data if no errors were encountered. Format depends on
 * response type: text, Object, ArrayBuffer, XML.
 * @returns {void}
 */ /**
 * Used to send and receive HTTP requests.
 */ class Http {
    static{
        this.ContentType = {
            AAC: 'audio/aac',
            BASIS: 'image/basis',
            BIN: 'application/octet-stream',
            DDS: 'image/dds',
            FORM_URLENCODED: 'application/x-www-form-urlencoded',
            GIF: 'image/gif',
            GLB: 'model/gltf-binary',
            JPEG: 'image/jpeg',
            JSON: 'application/json',
            MP3: 'audio/mpeg',
            MP4: 'audio/mp4',
            OGG: 'audio/ogg',
            OPUS: 'audio/ogg; codecs="opus"',
            PNG: 'image/png',
            TEXT: 'text/plain',
            WAV: 'audio/x-wav',
            XML: 'application/xml'
        };
    }
    static{
        this.ResponseType = {
            TEXT: 'text',
            ARRAY_BUFFER: 'arraybuffer',
            BLOB: 'blob',
            DOCUMENT: 'document',
            JSON: 'json'
        };
    }
    static{
        this.binaryExtensions = [
            '.model',
            '.wav',
            '.ogg',
            '.mp3',
            '.mp4',
            '.m4a',
            '.aac',
            '.dds',
            '.basis',
            '.glb',
            '.opus'
        ];
    }
    static{
        this.retryDelay = 100;
    }
    /**
     * Perform an HTTP GET request to the given url with additional options such as headers,
     * retries, credentials, etc.
     *
     * @param {string} url - The URL to make the request to.
     * @param {object} options - Additional options.
     * @param {Object<string, string>} [options.headers] - HTTP headers to add to the request.
     * @param {boolean} [options.async] - Make the request asynchronously. Defaults to true.
     * @param {boolean} [options.cache] - If false, then add a timestamp to the request to prevent caching.
     * @param {boolean} [options.withCredentials] - Send cookies with this request. Defaults to false.
     * @param {string} [options.responseType] - Override the response type.
     * @param {Document|object} [options.postdata] - Data to send in the body of the request.
     * Some content types are handled automatically. If postdata is an XML Document, it is handled. If
     * the Content-Type header is set to 'application/json' then the postdata is JSON stringified.
     * Otherwise, by default, the data is sent as form-urlencoded.
     * @param {boolean} [options.retry] - If true then if the request fails it will be retried with an exponential backoff.
     * @param {number} [options.maxRetries] - If options.retry is true this specifies the maximum number of retries. Defaults to 5.
     * @param {number} [options.maxRetryDelay] - If options.retry is true this specifies the maximum amount of time to wait between retries in milliseconds. Defaults to 5000.
     * @param {EventHandler} [options.progress] - Object to use for firing progress events.
     * @param {HttpResponseCallback} callback - The callback used when the response has returned. Passed (err, data)
     * where data is the response (format depends on response type: text, Object, ArrayBuffer, XML) and
     * err is the error code.
     * @example
     * pc.http.get("http://example.com/", {
     *     "retry": true,
     *     "maxRetries": 5
     * }, (err, response) => {
     *     console.log(response);
     * });
     * @returns {XMLHttpRequest} The request object.
     */ get(url, options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        const result = this.request('GET', url, options, callback);
        const { progress } = options;
        if (progress) {
            const handler = (event)=>{
                if (event.lengthComputable) {
                    progress.fire('progress', event.loaded, event.total);
                }
            };
            const endHandler = (event)=>{
                handler(event);
                result.removeEventListener('loadstart', handler);
                result.removeEventListener('progress', handler);
                result.removeEventListener('loadend', endHandler);
            };
            result.addEventListener('loadstart', handler);
            result.addEventListener('progress', handler);
            result.addEventListener('loadend', endHandler);
        }
        return result;
    }
    /**
     * Perform an HTTP POST request to the given url with additional options such as headers,
     * retries, credentials, etc.
     *
     * @param {string} url - The URL to make the request to.
     * @param {object} data - Data to send in the body of the request.
     * Some content types are handled automatically. If postdata is an XML Document, it is handled.
     * If the Content-Type header is set to 'application/json' then the postdata is JSON
     * stringified. Otherwise, by default, the data is sent as form-urlencoded.
     * @param {object} options - Additional options.
     * @param {Object<string, string>} [options.headers] - HTTP headers to add to the request.
     * @param {boolean} [options.async] - Make the request asynchronously. Defaults to true.
     * @param {boolean} [options.cache] - If false, then add a timestamp to the request to prevent caching.
     * @param {boolean} [options.withCredentials] - Send cookies with this request. Defaults to false.
     * @param {string} [options.responseType] - Override the response type.
     * @param {boolean} [options.retry] - If true then if the request fails it will be retried with an exponential backoff.
     * @param {number} [options.maxRetries] - If options.retry is true this specifies the maximum
     * number of retries. Defaults to 5.
     * @param {number} [options.maxRetryDelay] - If options.retry is true this specifies the
     * maximum amount of time to wait between retries in milliseconds. Defaults to 5000.
     * @param {HttpResponseCallback} callback - The callback used when the response has returned.
     * Passed (err, data) where data is the response (format depends on response type: text,
     * Object, ArrayBuffer, XML) and err is the error code.
     * @example
     * pc.http.post("http://example.com/", {
     *     "name": "Alex"
     * }, {
     *     "retry": true,
     *     "maxRetries": 5
     * }, (err, response) => {
     *     console.log(response);
     * });
     * @returns {XMLHttpRequest} The request object.
     */ post(url, data, options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        options.postdata = data;
        return this.request('POST', url, options, callback);
    }
    /**
     * Perform an HTTP PUT request to the given url with additional options such as headers,
     * retries, credentials, etc.
     *
     * @param {string} url - The URL to make the request to.
     * @param {Document|object} data - Data to send in the body of the request. Some content types
     * are handled automatically. If postdata is an XML Document, it is handled. If the
     * Content-Type header is set to 'application/json' then the postdata is JSON stringified.
     * Otherwise, by default, the data is sent as form-urlencoded.
     * @param {object} options - Additional options.
     * @param {Object<string, string>} [options.headers] - HTTP headers to add to the request.
     * @param {boolean} [options.async] - Make the request asynchronously. Defaults to true.
     * @param {boolean} [options.cache] - If false, then add a timestamp to the request to prevent caching.
     * @param {boolean} [options.withCredentials] - Send cookies with this request. Defaults to false.
     * @param {string} [options.responseType] - Override the response type.
     * @param {boolean} [options.retry] - If true then if the request fails it will be retried with
     * an exponential backoff.
     * @param {number} [options.maxRetries] - If options.retry is true this specifies the maximum
     * number of retries. Defaults to 5.
     * @param {number} [options.maxRetryDelay] - If options.retry is true this specifies the
     * maximum amount of time to wait between retries in milliseconds. Defaults to 5000.
     * @param {HttpResponseCallback} callback - The callback used when the response has returned.
     * Passed (err, data) where data is the response (format depends on response type: text,
     * Object, ArrayBuffer, XML) and err is the error code.
     * @example
     * pc.http.put("http://example.com/", {
     *     "name": "Alex"
     * }, {
     *     "retry": true,
     *     "maxRetries": 5
     * }, (err, response) => {
     *     console.log(response);
     * });
     * @returns {XMLHttpRequest} The request object.
     */ put(url, data, options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        options.postdata = data;
        return this.request('PUT', url, options, callback);
    }
    /**
     * Perform an HTTP DELETE request to the given url with additional options such as headers,
     * retries, credentials, etc.
     *
     * @param {string} url - The URL to make the request to.
     * @param {object} options - Additional options.
     * @param {Object<string, string>} [options.headers] - HTTP headers to add to the request.
     * @param {boolean} [options.async] - Make the request asynchronously. Defaults to true.
     * @param {boolean} [options.cache] - If false, then add a timestamp to the request to prevent caching.
     * @param {boolean} [options.withCredentials] - Send cookies with this request. Defaults to false.
     * @param {string} [options.responseType] - Override the response type.
     * @param {Document|object} [options.postdata] - Data to send in the body of the request.
     * Some content types are handled automatically. If postdata is an XML Document, it is handled.
     * If the Content-Type header is set to 'application/json' then the postdata is JSON
     * stringified. Otherwise, by default, the data is sent as form-urlencoded.
     * @param {boolean} [options.retry] - If true then if the request fails it will be retried with
     * an exponential backoff.
     * @param {number} [options.maxRetries] - If options.retry is true this specifies the maximum
     * number of retries. Defaults to 5.
     * @param {number} [options.maxRetryDelay] - If options.retry is true this specifies the
     * maximum amount of time to wait between retries in milliseconds. Defaults to 5000.
     * @param {HttpResponseCallback} callback - The callback used when the response has returned.
     * Passed (err, data) where data is the response (format depends on response type: text,
     * Object, ArrayBuffer, XML) and err is the error code.
     * @example
     * pc.http.del("http://example.com/", {
     *     "retry": true,
     *     "maxRetries": 5
     * }, (err, response) => {
     *     console.log(response);
     * });
     * @returns {XMLHttpRequest} The request object.
     */ del(url, options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        return this.request('DELETE', url, options, callback);
    }
    /**
     * Make a general purpose HTTP request with additional options such as headers, retries,
     * credentials, etc.
     *
     * @param {string} method - The HTTP method "GET", "POST", "PUT", "DELETE".
     * @param {string} url - The url to make the request to.
     * @param {object} options - Additional options.
     * @param {Object<string, string>} [options.headers] - HTTP headers to add to the request.
     * @param {boolean} [options.async] - Make the request asynchronously. Defaults to true.
     * @param {boolean} [options.cache] - If false, then add a timestamp to the request to prevent caching.
     * @param {boolean} [options.withCredentials] - Send cookies with this request. Defaults to false.
     * @param {boolean} [options.retry] - If true then if the request fails it will be retried with
     * an exponential backoff.
     * @param {number} [options.maxRetries] - If options.retry is true this specifies the maximum
     * number of retries. Defaults to 5.
     * @param {number} [options.maxRetryDelay] - If options.retry is true this specifies the
     * maximum amount of time to wait between retries in milliseconds. Defaults to 5000.
     * @param {string} [options.responseType] - Override the response type.
     * @param {Document|object} [options.postdata] - Data to send in the body of the request.
     * Some content types are handled automatically. If postdata is an XML Document, it is handled.
     * If the Content-Type header is set to 'application/json' then the postdata is JSON
     * stringified. Otherwise, by default, the data is sent as form-urlencoded.
     * @param {HttpResponseCallback} callback - The callback used when the response has returned.
     * Passed (err, data) where data is the response (format depends on response type: text,
     * Object, ArrayBuffer, XML) and err is the error code.
     * @example
     * pc.http.request("get", "http://example.com/", {
     *     "retry": true,
     *     "maxRetries": 5
     * }, (err, response) => {
     *     console.log(response);
     * });
     * @returns {XMLHttpRequest} The request object.
     */ request(method, url, options, callback) {
        let uri, query, postdata;
        let errored = false;
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        // if retryable we are going to store new properties
        // in the options so create a new copy to not affect
        // the original
        if (options.retry) {
            options = Object.assign({
                retries: 0,
                maxRetries: 5
            }, options);
        }
        // store callback
        options.callback = callback;
        // setup defaults
        if (options.async == null) {
            options.async = true;
        }
        if (options.headers == null) {
            options.headers = {};
        }
        if (options.postdata != null) {
            if (options.postdata instanceof Document) {
                // It's an XML document, so we can send it directly.
                // XMLHttpRequest will set the content type correctly.
                postdata = options.postdata;
            } else if (options.postdata instanceof FormData) {
                postdata = options.postdata;
            } else if (options.postdata instanceof Object) {
                // Now to work out how to encode the post data based on the headers
                let contentType = options.headers['Content-Type'];
                // If there is no type then default to form-encoded
                if (contentType === undefined) {
                    options.headers['Content-Type'] = Http.ContentType.FORM_URLENCODED;
                    contentType = options.headers['Content-Type'];
                }
                switch(contentType){
                    case Http.ContentType.FORM_URLENCODED:
                        {
                            // Normal URL encoded form data
                            postdata = '';
                            let bFirstItem = true;
                            // Loop round each entry in the map and encode them into the post data
                            for(const key in options.postdata){
                                if (options.postdata.hasOwnProperty(key)) {
                                    if (bFirstItem) {
                                        bFirstItem = false;
                                    } else {
                                        postdata += '&';
                                    }
                                    const encodedKey = encodeURIComponent(key);
                                    const encodedValue = encodeURIComponent(options.postdata[key]);
                                    postdata += `${encodedKey}=${encodedValue}`;
                                }
                            }
                            break;
                        }
                    default:
                    case Http.ContentType.JSON:
                        if (contentType == null) {
                            options.headers['Content-Type'] = Http.ContentType.JSON;
                        }
                        postdata = JSON.stringify(options.postdata);
                        break;
                }
            } else {
                postdata = options.postdata;
            }
        }
        if (options.cache === false) {
            // Add timestamp to url to prevent browser caching file
            const timestamp = now();
            uri = new URI(url);
            if (!uri.query) {
                uri.query = `ts=${timestamp}`;
            } else {
                uri.query = `${uri.query}&ts=${timestamp}`;
            }
            url = uri.toString();
        }
        if (options.query) {
            uri = new URI(url);
            query = extend(uri.getQuery(), options.query);
            uri.setQuery(query);
            url = uri.toString();
        }
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, options.async);
        xhr.withCredentials = options.withCredentials !== undefined ? options.withCredentials : false;
        xhr.responseType = options.responseType || this._guessResponseType(url);
        // Set the http headers
        for(const header in options.headers){
            if (options.headers.hasOwnProperty(header)) {
                xhr.setRequestHeader(header, options.headers[header]);
            }
        }
        xhr.onreadystatechange = ()=>{
            this._onReadyStateChange(method, url, options, xhr);
        };
        xhr.onerror = ()=>{
            this._onError(method, url, options, xhr);
            errored = true;
        };
        try {
            xhr.send(postdata);
        } catch (e) {
            // DWE: Don't callback on exceptions as behavior is inconsistent, e.g. cross-domain request errors don't throw an exception.
            // Error callback should be called by xhr.onerror() callback instead.
            if (!errored) {
                options.error(xhr.status, xhr, e);
            }
        }
        // Return the request object as it can be handy for blocking calls
        return xhr;
    }
    _guessResponseType(url) {
        const uri = new URI(url);
        const ext = path$1.getExtension(uri.path).toLowerCase();
        if (Http.binaryExtensions.indexOf(ext) >= 0) {
            return Http.ResponseType.ARRAY_BUFFER;
        } else if (ext === '.json') {
            return Http.ResponseType.JSON;
        } else if (ext === '.xml') {
            return Http.ResponseType.DOCUMENT;
        }
        return Http.ResponseType.TEXT;
    }
    _isBinaryContentType(contentType) {
        const binTypes = [
            Http.ContentType.BASIS,
            Http.ContentType.BIN,
            Http.ContentType.DDS,
            Http.ContentType.GLB,
            Http.ContentType.MP3,
            Http.ContentType.MP4,
            Http.ContentType.OGG,
            Http.ContentType.OPUS,
            Http.ContentType.WAV
        ];
        if (binTypes.indexOf(contentType) >= 0) {
            return true;
        }
        return false;
    }
    _isBinaryResponseType(responseType) {
        return responseType === Http.ResponseType.ARRAY_BUFFER || responseType === Http.ResponseType.BLOB || responseType === Http.ResponseType.JSON;
    }
    _onReadyStateChange(method, url, options, xhr) {
        if (xhr.readyState === 4) {
            switch(xhr.status){
                case 0:
                    {
                        // If status code 0, it is assumed that the browser has cancelled the request
                        // Add support for running Chrome browsers in 'allow-file-access-from-file'
                        // This is to allow for specialized programs and libraries such as CefSharp
                        // which embed Chromium in the native app.
                        if (xhr.responseURL && xhr.responseURL.startsWith('file:///')) {
                            // Assume that any file loaded from disk is fine
                            this._onSuccess(method, url, options, xhr);
                        } else {
                            this._onError(method, url, options, xhr);
                        }
                        break;
                    }
                case 200:
                case 201:
                case 206:
                case 304:
                    {
                        this._onSuccess(method, url, options, xhr);
                        break;
                    }
                default:
                    {
                        this._onError(method, url, options, xhr);
                        break;
                    }
            }
        }
    }
    _onSuccess(method, url, options, xhr) {
        let response;
        let contentType;
        const header = xhr.getResponseHeader('Content-Type');
        if (header) {
            // Split up header into content type and parameter
            const parts = header.split(';');
            contentType = parts[0].trim();
        }
        try {
            // Check the content type to see if we want to parse it
            if (this._isBinaryContentType(contentType) || this._isBinaryResponseType(xhr.responseType)) {
                // It's a binary response
                response = xhr.response;
            } else if (contentType === Http.ContentType.JSON || url.split('?')[0].endsWith('.json')) {
                // It's a JSON response
                response = JSON.parse(xhr.responseText);
            } else if (xhr.responseType === Http.ResponseType.DOCUMENT || contentType === Http.ContentType.XML) {
                // It's an XML response
                response = xhr.responseXML;
            } else {
                // It's raw data
                response = xhr.responseText;
            }
            options.callback(null, response);
        } catch (err) {
            options.callback(err);
        }
    }
    _onError(method, url, options, xhr) {
        if (options.retrying) {
            return;
        }
        // retry if necessary
        if (options.retry && options.retries < options.maxRetries) {
            options.retries++;
            options.retrying = true; // used to stop retrying when both onError and xhr.onerror are called
            const retryDelay = math.clamp(Math.pow(2, options.retries) * Http.retryDelay, 0, options.maxRetryDelay || 5000);
            console.log(`${method}: ${url} - Error ${xhr.status}. Retrying in ${retryDelay} ms`);
            setTimeout(()=>{
                options.retrying = false;
                this.request(method, url, options, options.callback);
            }, retryDelay);
        } else {
            // no more retries or not retry so just fail
            options.callback(xhr.status === 0 ? 'Network error' : xhr.status, null);
        }
    }
}
const http = new Http();

/**
 * Subtract the color of the source fragment from the destination fragment and write the result to
 * the frame buffer.
 *
 * @category Graphics
 */ const BLEND_SUBTRACTIVE = 0;
/**
 * Add the color of the source fragment to the destination fragment and write the result to the
 * frame buffer.
 *
 * @category Graphics
 */ const BLEND_ADDITIVE = 1;
/**
 * Enable simple translucency for materials such as glass. This is equivalent to enabling a source
 * blend mode of {@link BLENDMODE_SRC_ALPHA} and a destination blend mode of
 * {@link BLENDMODE_ONE_MINUS_SRC_ALPHA}.
 *
 * @category Graphics
 */ const BLEND_NORMAL = 2;
/**
 * Disable blending.
 *
 * @category Graphics
 */ const BLEND_NONE = 3;
/**
 * Similar to {@link BLEND_NORMAL} expect the source fragment is assumed to have already been
 * multiplied by the source alpha value.
 *
 * @category Graphics
 */ const BLEND_PREMULTIPLIED = 4;
/**
 * Multiply the color of the source fragment by the color of the destination fragment and write the
 * result to the frame buffer.
 *
 * @category Graphics
 */ const BLEND_MULTIPLICATIVE = 5;
/**
 * Same as {@link BLEND_ADDITIVE} except the source RGB is multiplied by the source alpha.
 *
 * @category Graphics
 */ const BLEND_ADDITIVEALPHA = 6;
/**
 * Multiplies colors and doubles the result.
 *
 * @category Graphics
 */ const BLEND_MULTIPLICATIVE2X = 7;
/**
 * Softer version of additive.
 *
 * @category Graphics
 */ const BLEND_SCREEN = 8;
/**
 * Minimum color.
 *
 * @category Graphics
 */ const BLEND_MIN = 9;
/**
 * Maximum color.
 *
 * @category Graphics
 */ const BLEND_MAX = 10;
const blendNames = {
    [BLEND_SUBTRACTIVE]: 'SUBTRACTIVE',
    [BLEND_ADDITIVE]: 'ADDITIVE',
    [BLEND_NORMAL]: 'NORMAL',
    [BLEND_NONE]: 'NONE',
    [BLEND_PREMULTIPLIED]: 'PREMULTIPLIED',
    [BLEND_MULTIPLICATIVE]: 'MULTIPLICATIVE',
    [BLEND_ADDITIVEALPHA]: 'ADDITIVEALPHA',
    [BLEND_MULTIPLICATIVE2X]: 'MULTIPLICATIVE2X',
    [BLEND_SCREEN]: 'SCREEN',
    [BLEND_MIN]: 'MIN',
    [BLEND_MAX]: 'MAX'
};
/**
 * No fog is applied to the scene.
 *
 * @category Graphics
 */ const FOG_NONE = 'none';
/**
 * Fog rises linearly from zero to 1 between a start and end depth.
 *
 * @category Graphics
 */ const FOG_LINEAR = 'linear';
/**
 * No Fresnel.
 *
 * @category Graphics
 */ const FRESNEL_NONE = 0;
/**
 * Schlick's approximation of Fresnel.
 *
 * @category Graphics
 */ const FRESNEL_SCHLICK = 2;
const fresnelNames = {
    [FRESNEL_NONE]: 'NONE',
    [FRESNEL_SCHLICK]: 'SCHLICK'
};
// 3 - 14 are custom user layers
const LAYER_WORLD = 15;
// New layers
/**
 * The world layer.
 *
 * @category Graphics
 */ const LAYERID_WORLD = 0;
/**
 * The depth layer.
 *
 * @category Graphics
 */ const LAYERID_DEPTH = 1;
/**
 * The skybox layer.
 *
 * @category Graphics
 */ const LAYERID_SKYBOX = 2;
/**
 * The immediate layer.
 *
 * @category Graphics
 */ const LAYERID_IMMEDIATE = 3;
/**
 * The UI layer.
 *
 * @category Graphics
 */ const LAYERID_UI = 4;
/**
 * Directional (global) light source.
 *
 * @category Graphics
 */ const LIGHTTYPE_DIRECTIONAL = 0;
/**
 * Omni-directional (local) light source.
 *
 * @category Graphics
 */ const LIGHTTYPE_OMNI = 1;
/**
 * Spot (local) light source.
 *
 * @category Graphics
 */ const LIGHTTYPE_SPOT = 2;
const lightTypeNames = {
    [LIGHTTYPE_DIRECTIONAL]: 'DIRECTIONAL',
    [LIGHTTYPE_OMNI]: 'OMNI',
    [LIGHTTYPE_SPOT]: 'SPOT'
};
// a divider clustered lights use to bring physical light intensity to half-float range
const LIGHT_COLOR_DIVIDER = 100;
/**
 * Infinitesimally small point light source shape.
 *
 * @category Graphics
 */ const LIGHTSHAPE_PUNCTUAL = 0;
/**
 * Rectangle shape of light source.
 *
 * @category Graphics
 */ const LIGHTSHAPE_RECT = 1;
/**
 * Disk shape of light source.
 *
 * @category Graphics
 */ const LIGHTSHAPE_DISK = 2;
/**
 * Sphere shape of light source.
 *
 * @category Graphics
 */ const LIGHTSHAPE_SPHERE = 3;
const lightShapeNames = {
    [LIGHTSHAPE_PUNCTUAL]: 'PUNCTUAL',
    [LIGHTSHAPE_RECT]: 'RECT',
    [LIGHTSHAPE_DISK]: 'DISK',
    [LIGHTSHAPE_SPHERE]: 'SPHERE'
};
/**
 * Linear distance falloff model for light attenuation.
 *
 * @category Graphics
 */ const LIGHTFALLOFF_LINEAR = 0;
/**
 * Inverse squared distance falloff model for light attenuation.
 *
 * @category Graphics
 */ const LIGHTFALLOFF_INVERSESQUARED = 1;
const lightFalloffNames = {
    [LIGHTFALLOFF_LINEAR]: 'LINEAR',
    [LIGHTFALLOFF_INVERSESQUARED]: 'INVERSESQUARED'
};
/**
 * A shadow sampling technique using 32bit shadow map that averages depth comparisons from a 3x3
 * grid of texels for softened shadow edges.
 *
 * @category Graphics
 */ const SHADOW_PCF3_32F = 0;
/**
 * A shadow sampling technique using a 16-bit exponential variance shadow map that leverages
 * variance to approximate shadow boundaries, enabling soft shadows. Only supported when
 * {@link GraphicsDevice#textureHalfFloatRenderable} is true. Falls back to {@link SHADOW_PCF3_32F},
 * if not supported.
 *
 * @category Graphics
 */ const SHADOW_VSM_16F = 2;
/**
 * A shadow sampling technique using a 32-bit exponential variance shadow map that leverages
 * variance to approximate shadow boundaries, enabling soft shadows. Only supported when
 * {@link GraphicsDevice#textureFloatRenderable} is true. Falls back to {@link SHADOW_VSM_16F}, if
 * not supported.
 *
 * @category Graphics
 */ const SHADOW_VSM_32F = 3;
/**
 * A shadow sampling technique using 32bit shadow map that averages depth comparisons from a 5x5
 * grid of texels for softened shadow edges.
 *
 * @category Graphics
 */ const SHADOW_PCF5_32F = 4;
/**
 * A shadow sampling technique using a 32-bit shadow map that performs a single depth comparison for
 * sharp shadow edges.
 *
 * @category Graphics
 */ const SHADOW_PCF1_32F = 5;
/**
 * A shadow sampling technique using a 32-bit shadow map that adjusts filter size based on blocker
 * distance, producing realistic, soft shadow edges that vary with the light's occlusion. Note that
 * this technique requires either {@link GraphicsDevice#textureFloatRenderable} or
 * {@link GraphicsDevice#textureHalfFloatRenderable} to be true, and falls back to
 * {@link SHADOW_PCF3_32F} otherwise.
 *
 * @category Graphics
 */ const SHADOW_PCSS_32F = 6;
/**
 * A shadow sampling technique using a 16-bit shadow map that performs a single depth comparison for
 * sharp shadow edges.
 *
 * @category Graphics
 */ const SHADOW_PCF1_16F = 7;
/**
 * A shadow sampling technique using 16-bit shadow map that averages depth comparisons from a 3x3
 * grid of texels for softened shadow edges.
 *
 * @category Graphics
 */ const SHADOW_PCF3_16F = 8;
/**
 * A shadow sampling technique using 16-bit shadow map that averages depth comparisons from a 3x3
 * grid of texels for softened shadow edges.
 *
 * @category Graphics
 */ const SHADOW_PCF5_16F = 9;
/**
 * Information about shadow types.
 *
 * @type {Map<number, { name: string, format: number, pcf?: boolean, vsm?: boolean }>}
 * @ignore
 */ const shadowTypeInfo = new Map([
    [
        SHADOW_PCF1_32F,
        {
            name: 'PCF1_32F',
            kind: 'PCF1',
            format: PIXELFORMAT_DEPTH,
            pcf: true
        }
    ],
    [
        SHADOW_PCF3_32F,
        {
            name: 'PCF3_32F',
            kind: 'PCF3',
            format: PIXELFORMAT_DEPTH,
            pcf: true
        }
    ],
    [
        SHADOW_PCF5_32F,
        {
            name: 'PCF5_32F',
            kind: 'PCF5',
            format: PIXELFORMAT_DEPTH,
            pcf: true
        }
    ],
    [
        SHADOW_PCF1_16F,
        {
            name: 'PCF1_16F',
            kind: 'PCF1',
            format: PIXELFORMAT_DEPTH16,
            pcf: true
        }
    ],
    [
        SHADOW_PCF3_16F,
        {
            name: 'PCF3_16F',
            kind: 'PCF3',
            format: PIXELFORMAT_DEPTH16,
            pcf: true
        }
    ],
    [
        SHADOW_PCF5_16F,
        {
            name: 'PCF5_16F',
            kind: 'PCF5',
            format: PIXELFORMAT_DEPTH16,
            pcf: true
        }
    ],
    [
        SHADOW_VSM_16F,
        {
            name: 'VSM_16F',
            kind: 'VSM',
            format: PIXELFORMAT_RGBA16F,
            vsm: true
        }
    ],
    [
        SHADOW_VSM_32F,
        {
            name: 'VSM_32F',
            kind: 'VSM',
            format: PIXELFORMAT_RGBA32F,
            vsm: true
        }
    ],
    [
        SHADOW_PCSS_32F,
        {
            name: 'PCSS_32F',
            kind: 'PCSS',
            format: PIXELFORMAT_R32F,
            pcss: true
        }
    ]
]);
/**
 * The flag that controls shadow rendering for the all cascades
 *
 * @category Graphics
 */ const SHADOW_CASCADE_ALL = 255;
/**
 * Gaussian filter. May look smoother than box, but requires more samples.
 *
 * @category Graphics
 */ const BLUR_GAUSSIAN = 1;
/**
 * A perspective camera projection where the frustum shape is essentially pyramidal.
 *
 * @category Graphics
 */ const PROJECTION_PERSPECTIVE = 0;
/**
 * An orthographic camera projection where the frustum shape is essentially a cuboid.
 *
 * @category Graphics
 */ const PROJECTION_ORTHOGRAPHIC = 1;
/**
 * Render mesh instance as solid geometry.
 *
 * @category Graphics
 */ const RENDERSTYLE_SOLID = 0;
/**
 * Render mesh instance as wireframe.
 *
 * @category Graphics
 */ const RENDERSTYLE_WIREFRAME = 1;
/**
 * Render mesh instance as points.
 *
 * @category Graphics
 */ const RENDERSTYLE_POINTS = 2;
/**
 * The cube map is treated as if it is infinitely far away.
 *
 * @category Graphics
 */ const CUBEPROJ_NONE = 0;
/**
 * The cube map is box-projected based on a world space axis-aligned bounding box.
 *
 * @category Graphics
 */ const CUBEPROJ_BOX = 1;
// names of the cubemap projection
const cubemaProjectionNames = {
    [CUBEPROJ_NONE]: 'NONE',
    [CUBEPROJ_BOX]: 'BOX'
};
/**
 * Multiply together the primary and secondary colors.
 *
 * @category Graphics
 */ const DETAILMODE_MUL = 'mul';
/**
 * No gamma correction.
 *
 * @category Graphics
 */ const GAMMA_NONE = 0;
/**
 * Apply sRGB gamma correction.
 *
 * @category Graphics
 */ const GAMMA_SRGB = 1;
// names of the gamma correction modes
const gammaNames = {
    [GAMMA_NONE]: 'NONE',
    [GAMMA_SRGB]: 'SRGB'
};
/**
 * Linear tonemapping. The colors are preserved, but the exposure is applied.
 *
 * @category Graphics
 */ const TONEMAP_LINEAR = 0;
/**
 * Filmic tonemapping curve.
 *
 * @category Graphics
 */ const TONEMAP_FILMIC = 1;
/**
 * Hejl filmic tonemapping curve.
 *
 * @category Graphics
 */ const TONEMAP_HEJL = 2;
/**
 * ACES filmic tonemapping curve.
 *
 * @category Graphics
 */ const TONEMAP_ACES = 3;
/**
 * ACES v2 filmic tonemapping curve.
 *
 * @category Graphics
 */ const TONEMAP_ACES2 = 4;
/**
 * Khronos PBR Neutral tonemapping curve.
 *
 * @category Graphics
 */ const TONEMAP_NEUTRAL = 5;
/**
 * No tonemapping or exposure is applied. Used for HDR rendering.
 *
 * @category Graphics
 */ const TONEMAP_NONE = 6;
// names of the tonemaps
const tonemapNames = [
    'LINEAR',
    'FILMIC',
    'HEJL',
    'ACES',
    'ACES2',
    'NEUTRAL',
    'NONE'
];
/**
 * No specular occlusion.
 *
 * @category Graphics
 */ const SPECOCC_NONE = 0;
/**
 * Use AO directly to occlude specular.
 *
 * @category Graphics
 */ const SPECOCC_AO = 1;
/**
 * Modify AO based on material glossiness/view angle to occlude specular.
 *
 * @category Graphics
 */ const SPECOCC_GLOSSDEPENDENT = 2;
const specularOcclusionNames = {
    [SPECOCC_NONE]: 'NONE',
    [SPECOCC_AO]: 'AO',
    [SPECOCC_GLOSSDEPENDENT]: 'GLOSSDEPENDENT'
};
// reflection source used by the shader generation
const REFLECTIONSRC_NONE = 'none';
const REFLECTIONSRC_ENVATLAS = 'envAtlas';
const REFLECTIONSRC_ENVATLASHQ = 'envAtlasHQ';
const REFLECTIONSRC_CUBEMAP = 'cubeMap';
const REFLECTIONSRC_SPHEREMAP = 'sphereMap';
const reflectionSrcNames = {
    [REFLECTIONSRC_NONE]: 'NONE',
    [REFLECTIONSRC_ENVATLAS]: 'ENVATLAS',
    [REFLECTIONSRC_ENVATLASHQ]: 'ENVATLASHQ',
    [REFLECTIONSRC_CUBEMAP]: 'CUBEMAP',
    [REFLECTIONSRC_SPHEREMAP]: 'SPHEREMAP'
};
// ambient source used by the shader generation
const AMBIENTSRC_AMBIENTSH = 'ambientSH';
const AMBIENTSRC_ENVALATLAS = 'envAtlas';
const AMBIENTSRC_CONSTANT = 'constant';
const ambientSrcNames = {
    [AMBIENTSRC_AMBIENTSH]: 'AMBIENTSH',
    [AMBIENTSRC_ENVALATLAS]: 'ENVALATLAS',
    [AMBIENTSRC_CONSTANT]: 'CONSTANT'
};
// 16 bits for shader defs
const SHADERDEF_NOSHADOW = 1;
const SHADERDEF_SKIN = 2;
const SHADERDEF_UV0 = 4;
const SHADERDEF_UV1 = 8;
const SHADERDEF_VCOLOR = 16;
const SHADERDEF_INSTANCING = 32;
const SHADERDEF_LM = 64;
const SHADERDEF_DIRLM = 128;
const SHADERDEF_SCREENSPACE = 256;
const SHADERDEF_TANGENTS = 512;
const SHADERDEF_MORPH_POSITION = 1024;
const SHADERDEF_MORPH_NORMAL = 2048;
const SHADERDEF_LMAMBIENT = 4096; // lightmaps contain ambient
const SHADERDEF_MORPH_TEXTURE_BASED_INT = 8192;
const SHADERDEF_BATCH = 16384;
/**
 * The shadow map is not to be updated.
 *
 * @category Graphics
 */ const SHADOWUPDATE_NONE = 0;
/**
 * The shadow map is regenerated this frame and not on subsequent frames.
 *
 * @category Graphics
 */ const SHADOWUPDATE_THISFRAME = 1;
/**
 * The shadow map is regenerated every frame.
 *
 * @category Graphics
 */ const SHADOWUPDATE_REALTIME = 2;
// flags used on the mask property of the Light, and also on mask property of the MeshInstance
const MASK_AFFECT_DYNAMIC = 1;
const MASK_AFFECT_LIGHTMAPPED = 2;
const MASK_BAKE = 4;
/**
 * Render shaded materials using forward rendering.
 *
 * @category Graphics
 */ const SHADER_FORWARD = 0;
const SHADER_PREPASS = 1;
// shadow pass used by the shadow rendering code
const SHADER_SHADOW = 2;
// shader pass used by the Picker class to render mesh ID
const SHADER_PICK = 3;
/**
 * This mode renders a sprite as a simple quad.
 *
 * @category Graphics
 */ const SPRITE_RENDERMODE_SIMPLE = 0;
/**
 * This mode renders a sprite using 9-slicing in 'sliced' mode. Sliced mode stretches the top and
 * bottom regions of the sprite horizontally, the left and right regions vertically and the middle
 * region both horizontally and vertically.
 *
 * @category Graphics
 */ const SPRITE_RENDERMODE_SLICED = 1;
/**
 * This mode renders a sprite using 9-slicing in 'tiled' mode. Tiled mode tiles the top and bottom
 * regions of the sprite horizontally, the left and right regions vertically and the middle region
 * both horizontally and vertically.
 *
 * @category Graphics
 */ const SPRITE_RENDERMODE_TILED = 2;
const spriteRenderModeNames = {
    [SPRITE_RENDERMODE_SIMPLE]: 'SIMPLE',
    [SPRITE_RENDERMODE_SLICED]: 'SLICED',
    [SPRITE_RENDERMODE_TILED]: 'TILED'
};
/**
 * Single color lightmap + dominant light direction (used for bump/specular).
 *
 * @category Graphics
 */ const BAKE_COLORDIR = 1;
/**
 * Center of view.
 *
 * @category Graphics
 */ const VIEW_CENTER = 0;
/**
 * No sorting is applied. Mesh instances are rendered in the same order they were added to a layer.
 *
 * @category Graphics
 */ const SORTMODE_NONE = 0;
/**
 * Mesh instances are sorted based on {@link MeshInstance#drawOrder}.
 *
 * @category Graphics
 */ const SORTMODE_MANUAL = 1;
/**
 * Mesh instances are sorted to minimize switching between materials and meshes to improve
 * rendering performance.
 *
 * @category Graphics
 */ const SORTMODE_MATERIALMESH = 2;
/**
 * Mesh instances are sorted back to front. This is the way to properly render many
 * semi-transparent objects on different depth, one is blended on top of another.
 *
 * @category Graphics
 */ const SORTMODE_BACK2FRONT = 3;
/**
 * Mesh instances are sorted front to back. Depending on GPU and the scene, this option may give
 * better performance than {@link SORTMODE_MATERIALMESH} due to reduced overdraw.
 *
 * @category Graphics
 */ const SORTMODE_FRONT2BACK = 4;
/**
 * Provide custom functions for sorting drawcalls and calculating distance.
 *
 * @ignore
 * @category Graphics
 */ const SORTMODE_CUSTOM = 5;
/**
 * Automatically set aspect ratio to current render target's width divided by height.
 *
 * @category Graphics
 */ const ASPECT_AUTO = 0;
/**
 * Use the manual aspect ratio value.
 *
 * @category Graphics
 */ const ASPECT_MANUAL = 1;
/**
 * A sky texture is rendered using an infinite projection.
 *
 * @category Graphics
 */ const SKYTYPE_INFINITE = 'infinite';
/**
 * A sky texture is rendered using a box projection. This is generally suitable for interior
 * environments.
 *
 * @category Graphics
 */ const SKYTYPE_BOX = 'box';
/**
 *  A sky texture is rendered using a dome projection. This is generally suitable for exterior
 * environments.
 *
 * @category Graphics
 */ const SKYTYPE_DOME = 'dome';
/**
 * Opacity dithering is disabled.
 *
 * @category Graphics
 */ const DITHER_NONE = 'none';
/**
 * Opacity is dithered using a Bayer 8 matrix.
 *
 * @category Graphics
 */ const DITHER_BAYER8 = 'bayer8';
/**
 * Opacity is dithered using a blue noise.
 *
 * @category Graphics
 */ const DITHER_BLUENOISE = 'bluenoise';
/**
 * Opacity is dithered using an interleaved gradient noise.
 *
 * @category Graphics
 */ const DITHER_IGNNOISE = 'ignnoise';
const ditherNames = {
    [DITHER_NONE]: 'NONE',
    [DITHER_BAYER8]: 'BAYER8',
    [DITHER_BLUENOISE]: 'BLUENOISE',
    [DITHER_IGNNOISE]: 'IGNNOISE'
};
/**
 * Name of event fired before the camera renders the scene.
 *
 * @ignore
 */ const EVENT_PRERENDER = 'prerender';
/**
 * Name of event fired after the camera renders the scene.
 *
 * @ignore
 */ const EVENT_POSTRENDER = 'postrender';
/**
 * Name of event fired before a layer is rendered by a camera.
 *
 * @ignore
 */ const EVENT_PRERENDER_LAYER = 'prerender:layer';
/**
 * Name of event fired after a layer is rendered by a camera.
 *
 * @ignore
 */ const EVENT_POSTRENDER_LAYER = 'postrender:layer';
/**
 * Name of event fired before visibility culling is performed for the camera.
 *
 * @ignore
 */ const EVENT_PRECULL = 'precull';
/**
 * Name of event after visibility culling is performed for the camera.
 *
 * @ignore
 */ const EVENT_POSTCULL = 'postcull';
/**
 * Name of event after the engine has finished culling all cameras.
 *
 * @ignore
 */ const EVENT_CULL_END = 'cull:end';

/**
 * @import { BindGroupFormat } from './bind-group-format.js'
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { UniformBufferFormat } from './uniform-buffer-format.js'
 * @import { VertexFormat } from './vertex-format.js'
 */ /**
 * Options to drive shader processing to add support for bind groups and uniform buffers.
 *
 * @ignore
 */ class ShaderProcessorOptions {
    /**
     * Constructs shader processing options, used to process the shader for uniform buffer support.
     *
     * @param {UniformBufferFormat} [viewUniformFormat] - Format of the uniform buffer.
     * @param {BindGroupFormat} [viewBindGroupFormat] - Format of the bind group.
     * @param {VertexFormat} [vertexFormat] - Format of the vertex buffer.
     */ constructor(viewUniformFormat, viewBindGroupFormat, vertexFormat){
        /** @type {UniformBufferFormat[]} */ this.uniformFormats = [];
        /** @type {BindGroupFormat[]} */ this.bindGroupFormats = [];
        // construct a sparse array
        this.uniformFormats[BINDGROUP_VIEW] = viewUniformFormat;
        this.bindGroupFormats[BINDGROUP_VIEW] = viewBindGroupFormat;
        this.vertexFormat = vertexFormat;
    }
    /**
     * Get the bind group index for the uniform name.
     *
     * @param {string} name - The name of the uniform.
     * @returns {boolean} - Returns true if the uniform exists, false otherwise.
     */ hasUniform(name) {
        for(let i = 0; i < this.uniformFormats.length; i++){
            const uniformFormat = this.uniformFormats[i];
            if (uniformFormat?.get(name)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Get the bind group texture slot for the texture uniform name.
     *
     * @param {string} name - The name of the texture uniform.
     * @returns {boolean} - Returns true if the texture uniform exists, false otherwise.
     */ hasTexture(name) {
        for(let i = 0; i < this.bindGroupFormats.length; i++){
            const groupFormat = this.bindGroupFormats[i];
            if (groupFormat?.getTexture(name)) {
                return true;
            }
        }
        return false;
    }
    getVertexElement(semantic) {
        return this.vertexFormat?.elements.find((element)=>element.name === semantic);
    }
    /**
     * Generate unique key representing the processing options.
     *
     * @param {GraphicsDevice} device - The device.
     * @returns {string} - Returns the key.
     */ generateKey(device) {
        // TODO: Optimize. Uniform and BindGroup formats should have their keys evaluated in their
        // constructors, and here we should simply concatenate those.
        let key = JSON.stringify(this.uniformFormats) + JSON.stringify(this.bindGroupFormats);
        // WebGPU shaders are processed per vertex format
        if (device.isWebGPU) {
            key += this.vertexFormat?.shaderProcessingHashString;
        }
        return key;
    }
}

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { ProgramLibrary } from './program-library.js'
 */ // Device cache storing a program library
const programLibraryDeviceCache = new DeviceCache();
/**
 * Returns program library for a specified instance of a device.
 *
 * @param {GraphicsDevice} device - The graphics device used to own the program library.
 * @returns {ProgramLibrary} The instance of {@link ProgramLibrary}
 * @ignore
 */ function getProgramLibrary(device) {
    const library = programLibraryDeviceCache.get(device);
    Debug.assert(library);
    return library;
}
/**
 * Assigns the program library to device cache.
 *
 * @param {GraphicsDevice} device - The graphics device used to own the program library.
 * @param {ProgramLibrary} library - The instance of {@link ProgramLibrary}.
 * @ignore
 */ function setProgramLibrary(device, library) {
    Debug.assert(library);
    programLibraryDeviceCache.get(device, ()=>{
        return library;
    });
}

class ShaderGenerator {
    /**
     * @param {Map<string, string>} defines - the set of defines to be used in the shader.
     * @returns {number} the hash code of the defines.
     */ static definesHash(defines) {
        const sortedArray = Array.from(defines).sort((a, b)=>a[0] > b[0] ? 1 : -1);
        return hashCode(JSON.stringify(sortedArray));
    }
}

/**
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 */ // device cache storing shader pass data per device
const shaderPassDeviceCache = new DeviceCache();
/**
 * Info about a shader pass. Shader pass is represented by a unique index and a name, and the
 * index is used to access the shader required for the pass, from an array stored in the
 * material or mesh instance.
 *
 * @ignore
 */ class ShaderPassInfo {
    /**
     * @param {string} name - The name, for example 'depth'. Must contain only letters, numbers,
     * and underscores, and start with a letter.
     * @param {number} index - Index from ShaderPass#nextIndex.
     * @param {object} [options] - Options for additional configuration of the shader pass.
     * @param {boolean} [options.isForward] - Whether the pass is forward.
     * @param {boolean} [options.isShadow] - Whether the pass is shadow.
     * @param {boolean} [options.lightType] - Type of light, for example `pc.LIGHTTYPE_DIRECTIONAL`.
     * @param {boolean} [options.shadowType] - Type of shadow, for example `pc.SHADOW_PCF3_32F`.
     */ constructor(name, index, options = {}){
        /** @type {Map<string, string} */ this.defines = new Map();
        Debug.assert(/^[a-z]\w*$/i.test(name), `ShaderPass name can only contain letters, numbers and underscores and start with a letter: ${name}`);
        this.name = name;
        this.index = index;
        // assign options as properties to this object
        Object.assign(this, options);
        this.buildShaderDefines();
    }
    buildShaderDefines() {
        let keyword;
        if (this.isShadow) {
            keyword = 'SHADOW';
        } else if (this.isForward) {
            keyword = 'FORWARD';
        } else if (this.index === SHADER_PICK) {
            keyword = 'PICK';
        }
        this.defines.set(`${keyword}_PASS`, '');
        this.defines.set(`${this.name.toUpperCase()}_PASS`, '');
    }
}
/**
 * Class responsible for management of shader passes, associated with a device.
 *
 * @ignore
 */ class ShaderPass {
    constructor(){
        /**
     * Allocated shader passes, map of a shader pass name to info.
     *
     * @type {Map<string, ShaderPassInfo>}
     */ this.passesNamed = new Map();
        /**
     * Allocated shader passes, indexed by their index.
     *
     * @type {Array<ShaderPassInfo>}
     */ this.passesIndexed = [];
        /** Next available index */ this.nextIndex = 0;
        const add = (name, index, options)=>{
            const info = this.allocate(name, options);
            Debug.assert(info.index === index);
        };
        // add default passes in the required order, to match the constants
        add('forward', SHADER_FORWARD, {
            isForward: true
        });
        add('prepass', SHADER_PREPASS);
        add('shadow', SHADER_SHADOW);
        add('pick', SHADER_PICK);
    }
    /**
     * Get access to the shader pass instance for the specified device.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @returns { ShaderPass } The shader pass instance for the specified device.
     */ static get(device) {
        Debug.assert(device);
        return shaderPassDeviceCache.get(device, ()=>{
            return new ShaderPass();
        });
    }
    /**
     * Allocates a shader pass with the specified name and options.
     *
     * @param {string} name - A name of the shader pass.
     * @param {object} [options] - Options for the shader pass, which are added as properties to the
     * shader pass info.
     * @returns {ShaderPassInfo} The allocated shader pass info.
     */ allocate(name, options) {
        let info = this.passesNamed.get(name);
        if (info === undefined) {
            info = new ShaderPassInfo(name, this.nextIndex, options);
            this.passesNamed.set(info.name, info);
            this.passesIndexed[info.index] = info;
            this.nextIndex++;
        }
        return info;
    }
    /**
     * Return the shader pass info for the specified index.
     *
     * @param {number} index - The shader pass index.
     * @returns {ShaderPassInfo} - The shader pass info.
     */ getByIndex(index) {
        const info = this.passesIndexed[index];
        Debug.assert(info);
        return info;
    }
    getByName(name) {
        return this.passesNamed.get(name);
    }
}

/**
 * A collection of shader chunks, used by {@link ShaderChunks}. This is a map of shader chunk names
 * to their code.  As this class extends `Map`, it can be used as a `Map` as well in addition to
 * custom functionality it provides.
 *
 * @category Graphics
 */ class ShaderChunkMap extends Map {
    /**
     * Adds a new shader chunk with a specified name and shader source code to the Map. If an
     * element with the same name already exists, the element will be updated.
     *
     * @param {string} name - The name of the shader chunk.
     * @param {string} code - The shader source code.
     * @returns {this} The ShaderChunkMap instance.
     */ set(name, code) {
        if (!this.has(name) || this.get(name) !== code) {
            this.markDirty();
        }
        return super.set(name, code);
    }
    /**
     * Adds multiple shader chunks to the Map. This method accepts an object where the keys are the
     * names of the shader chunks and the values are the shader source code. If an element with the
     * same name already exists, the element will be updated.
     *
     * @param {Object} object - Object containing shader chunks.
     * @returns {this} The ShaderChunkMap instance.
     */ add(object) {
        for (const [key, value] of Object.entries(object)){
            this.set(key, value);
        }
        return this;
    }
    /**
     * Removes a shader chunk by name from the Map. If the element does not exist, no action is
     * taken.
     *
     * @param {string} name - The name of the shader chunk to remove.
     * @returns {boolean} True if an element in the Map existed and has been removed, or false if the
     * element does not exist.
     */ delete(name) {
        const existed = this.has(name);
        const result = super.delete(name);
        if (existed && result) {
            this.markDirty();
        }
        return result;
    }
    /**
     * Removes all shader chunks from the Map.
     */ clear() {
        if (this.size > 0) {
            this.markDirty();
        }
        super.clear();
    }
    markDirty() {
        this._dirty = true;
        this._keyDirty = true;
    }
    isDirty() {
        return this._dirty;
    }
    resetDirty() {
        this._dirty = false;
    }
    get key() {
        if (this._keyDirty) {
            this._keyDirty = false;
            // unique key for this chunk map
            this._key = Array.from(this.entries()).sort(([a], [b])=>a < b ? -1 : a > b ? 1 : 0).map(([k, v])=>`${k}=${hashCode(v)}`).join(',');
        }
        return this._key;
    }
    /**
     * Copy the shader chunk map.
     *
     * @param {ShaderChunkMap} source - The instance to copy.
     * @returns {this} The destination instance.
     * @ignore
     */ copy(source) {
        this.clear();
        for (const [key, value] of source){
            this.set(key, value);
        }
        return this;
    }
    constructor(...args){
        super(...args), this._keyDirty = false, this._key = '';
    }
}

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 */ const _chunksCache = new DeviceCache();
/**
 * A collection of GLSL and WGSL shader chunks, used to generate shaders.
 *
 * @category Graphics
 */ class ShaderChunks {
    /**
     * Returns a shader chunks map for the given device and shader language.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {string} shaderLanguage - The shader language to use (GLSL or WGSL).
     * @returns {ShaderChunkMap} The shader chunks for the specified language.
     */ static get(device, shaderLanguage = SHADERLANGUAGE_GLSL) {
        const cache = _chunksCache.get(device, ()=>{
            return new ShaderChunks();
        });
        return shaderLanguage === SHADERLANGUAGE_GLSL ? cache.glsl : cache.wgsl;
    }
    get useWGSL() {
        // if we have no glsl overrides, or have wgsl overrides, wgsl is used on WebGPU
        return this.glsl.size === 0 || this.wgsl.size > 0;
    }
    get key() {
        return `GLSL:${this.glsl.key}|WGSL:${this.wgsl.key}|API:${this.version}`;
    }
    isDirty() {
        return this.glsl.isDirty() || this.wgsl.isDirty();
    }
    resetDirty() {
        this.glsl.resetDirty();
        this.wgsl.resetDirty();
    }
    /**
     * Copy the shader chunks.
     *
     * @param {ShaderChunks} source - The instance to copy.
     * @returns {ShaderChunks} The destination instance.
     * @ignore
     */ copy(source) {
        this.version = source.version;
        this.glsl.copy(source.glsl);
        this.wgsl.copy(source.wgsl);
        return this;
    }
    constructor(){
        /**
     * A map of shader chunks for GLSL.
     *
     * @type {ShaderChunkMap}
     * @ignore
     */ this.glsl = new ShaderChunkMap();
        /**
     * A map of shader chunks for WGSL.
     *
     * @type {ShaderChunkMap}
     * @ignore
     */ this.wgsl = new ShaderChunkMap();
        /**
     * Specifies the API version of the shader chunks.
     *
     * This should be a string containing the current engine major and minor version (e.g., '2.8'
     * for engine v2.8.1) and ensures compatibility with the current engine version. When providing
     * custom shader chunks, set this to the latest supported version. If a future engine release no
     * longer supports the specified version, a warning will be issued. In that case, update your
     * shader chunks to match the new format and set this to the latest version accordingly.
     *
     * @type {string}
     */ this.version = '';
    }
}

/**
 * Map utility functions.
 *
 * @ignore
 */ class MapUtils {
    /**
     * Merges multiple maps into a new Map instance. If multiple maps contain the same key, the
     * value from the later map in the arguments list takes precedence.
     *
     * Null or undefined maps are safely handled and skipped during the merge process.
     *
     * @param {...(Map|null|undefined)} maps - Maps to merge.
     * @returns {Map} A new Map containing all entries from the input maps with conflicts resolved.
     * @example
     * // Create a merged map where entries from map2 override entries from map1
     * const mergedMap = MapUtils.merge(map1, map2);
     */ static merge(...maps) {
        // Start with a copy of the first map for better performance with large maps
        const result = new Map(maps[0] ?? []);
        // Add entries from remaining maps, overriding existing keys
        for(let i = 1; i < maps.length; i++){
            const map = maps[i];
            if (map) {
                for (const [key, value] of map){
                    result.set(key, value);
                }
            }
        }
        return result;
    }
}

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor-options.js'
 * @import { Material, ShaderVariantParams } from '../materials/material.js'
 * @import { CameraShaderParams } from '../camera-shader-params.js';
 */ class ShaderGeneratorPassThrough extends ShaderGenerator {
    constructor(key, shaderDefinition){
        super();
        this.key = key;
        this.shaderDefinition = shaderDefinition;
    }
    generateKey(options) {
        return this.key;
    }
    createShaderDefinition(device, options) {
        return this.shaderDefinition;
    }
}
class ShaderUtils {
    /**
     * Creates a shader. When the active graphics device is WebGL, the provided GLSL vertex and
     * fragment source code is used. For WebGPU, if WGSL vertex and fragment source code is
     * supplied, it is used directly; otherwise, the system automatically translates the provided
     * GLSL code into WGSL. In the case of GLSL shaders, additional blocks are appended to both the
     * vertex and fragment source code to support extended features and maintain compatibility.
     * These additions include the shader version declaration, precision qualifiers, and commonly
     * used extensions, and therefore should be excluded from the user-supplied GLSL source.
     * Note: The shader has access to all registered shader chunks via the `#include` directive.
     * Any provided includes will be applied as overrides on top of those.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {object} options - Object for passing optional arguments.
     * @param {string} options.uniqueName - Unique name for the shader. If a shader with this name
     * already exists, it will be returned instead of a new shader instance.
     * @param {Object<string, string>} options.attributes - Object detailing the mapping of vertex
     * shader attribute names to semantics SEMANTIC_*. This enables the engine to match vertex
     * buffer data to the shader attributes.
     * @param {boolean} [options.useTransformFeedback] - Whether to use transform feedback. Defaults
     * to false. Only supported by WebGL.
     * @param {string} [options.vertexChunk] - The name of the vertex shader chunk to use.
     * @param {string} [options.vertexGLSL] - The vertex shader code in GLSL. Ignored if vertexChunk
     * is provided.
     * @param {string} [options.vertexWGSL] - The vertex shader code in WGSL. Ignored if vertexChunk
     * is provided.
     * @param {string} [options.fragmentChunk] - The name of the fragment shader chunk to use.
     * @param {string} [options.fragmentGLSL] - The fragment shader code in GLSL. Ignored if
     * fragmentChunk is provided.
     * @param {string} [options.fragmentWGSL] - The fragment shader code in WGSL. Ignored if
     * fragmentChunk is provided.
     * @param {Map<string, string>} [options.vertexIncludes] - A map containing key-value pairs of
     * include names and their content. These are used for resolving #include directives in the
     * vertex shader source.
     * @param {Map<string, string>} [options.vertexDefines] - A map containing key-value pairs of
     * define names and their values. These are used for resolving #ifdef style of directives in the
     * vertex code.
     * @param {Map<string, string>} [options.fragmentIncludes] - A map containing key-value pairs
     * of include names and their content. These are used for resolving #include directives in the
     * fragment shader source.
     * @param {Map<string, string>} [options.fragmentDefines] - A map containing key-value pairs of
     * define names and their values. These are used for resolving #ifdef style of directives in the
     * fragment code.
     * @param {string | string[]} [options.fragmentOutputTypes] - Fragment shader output types,
     * which default to vec4. Passing a string will set the output type for all color attachments.
     * Passing an array will set the output type for each color attachment.
     * @returns {Shader} The newly created shader.
     */ static createShader(device, options) {
        const programLibrary = getProgramLibrary(device);
        let shader = programLibrary.getCachedShader(options.uniqueName);
        if (!shader) {
            // use WGSL language on WebGPU: if user provided WGSL code, or if named chunks are used
            const wgsl = device.isWebGPU && (!!options.vertexWGSL || !!options.vertexChunk) && (!!options.fragmentWGSL || !!options.fragmentChunk);
            // chunks map
            const chunksMap = ShaderChunks.get(device, wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL);
            // source code
            const vertexCode = options.vertexChunk ? chunksMap.get(options.vertexChunk) : wgsl ? options.vertexWGSL : options.vertexGLSL;
            const fragmentCode = options.fragmentChunk ? chunksMap.get(options.fragmentChunk) : wgsl ? options.fragmentWGSL : options.fragmentGLSL;
            Debug.assert(vertexCode, 'ShaderUtils.createShader: vertex shader code not provided', options);
            Debug.assert(fragmentCode, 'ShaderUtils.createShader: fragment shader code not provided', options);
            // add default shader chunks to includes
            const fragmentIncludes = MapUtils.merge(chunksMap, options.fragmentIncludes);
            const vertexIncludes = MapUtils.merge(chunksMap, options.vertexIncludes);
            shader = new Shader(device, ShaderDefinitionUtils.createDefinition(device, {
                name: options.uniqueName,
                shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL,
                attributes: options.attributes,
                vertexCode: vertexCode,
                fragmentCode: fragmentCode,
                useTransformFeedback: options.useTransformFeedback,
                vertexIncludes: vertexIncludes,
                vertexDefines: options.vertexDefines,
                fragmentIncludes: fragmentIncludes,
                fragmentDefines: options.fragmentDefines,
                fragmentOutputTypes: options.fragmentOutputTypes
            }));
            programLibrary.setCachedShader(options.uniqueName, shader);
        }
        return shader;
    }
    /**
     * Create a map of defines used for shader generation for a material.
     *
     * @param {Material} material - The material to create the shader defines for.
     * @param {ShaderVariantParams} params - The shader variant parameters.
     * @returns {Map<string, string>} The map of shader defines.
     * @ignore
     */ static getCoreDefines(material, params) {
        // merge both maps, with camera shader params taking precedence
        const defines = new Map(material.defines);
        params.cameraShaderParams.defines.forEach((value, key)=>defines.set(key, value));
        // add pass defines
        const shaderPassInfo = ShaderPass.get(params.device).getByIndex(params.pass);
        shaderPassInfo.defines.forEach((value, key)=>defines.set(key, value));
        return defines;
    }
    /**
     * Process shader using shader processing options, utilizing the cache of the ProgramLibrary.
     *
     * @param {Shader} shader - The shader to be processed.
     * @param {ShaderProcessorOptions} processingOptions - The shader processing options.
     * @returns {Shader} The processed shader.
     * @ignore
     */ static processShader(shader, processingOptions) {
        Debug.assert(shader);
        const shaderDefinition = shader.definition;
        // 'shader' generator for a material - simply return existing shader definition. Use generator and getProgram
        // to allow for shader processing to be cached
        const name = shaderDefinition.name ?? 'shader';
        // unique name based of the shader id
        const key = `${name}-id-${shader.id}`;
        const materialGenerator = new ShaderGeneratorPassThrough(key, shaderDefinition);
        // temporarily register the program generator
        const libraryModuleName = 'shader';
        const library = getProgramLibrary(shader.device);
        Debug.assert(!library.isRegistered(libraryModuleName));
        library.register(libraryModuleName, materialGenerator);
        // generate shader variant - its the same shader, but with different processing options
        const variant = library.getProgram(libraryModuleName, {}, processingOptions);
        // unregister it again
        library.unregister(libraryModuleName);
        return variant;
    }
    /**
     * Add defines required for correct screenDepthPS chunk functionality for the given camera
     * shader parameters.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {CameraShaderParams} cameraShaderParams - The camera shader parameters.
     * @ignore
     */ static addScreenDepthChunkDefines(device, cameraShaderParams, defines) {
        if (cameraShaderParams.sceneDepthMapLinear) {
            defines.set('SCENE_DEPTHMAP_LINEAR', '');
        }
        if (device.textureFloatRenderable) {
            defines.set('SCENE_DEPTHMAP_FLOAT', '');
        }
    }
}

/**
 * @import { Shader } from '../../platform/graphics/shader.js'
 */ const _quadPrimitive = {
    type: PRIMITIVE_TRISTRIP,
    base: 0,
    baseVertex: 0,
    count: 4,
    indexed: false
};
const _tempViewport = new Vec4();
const _tempScissor = new Vec4();
const _dynamicBindGroup$1 = new DynamicBindGroup();
/**
 * An object that renders a quad using a {@link Shader}.
 *
 * Example:
 *
 * ```javascript
 * const shader = pc.ShaderUtils.createShader(app.graphicsDevice, {
 *     uniqueName: 'MyShader',
 *     attributes: { aPosition: SEMANTIC_POSITION },
 *     vertexGLSL: '// vertex shader code',
 *     fragmentGLSL: '// fragment shader code'
 * });
 * const quad = new QuadRender(shader);
 * quad.render();
 * quad.destroy();
 * ```
 *
 * @category Graphics
 */ class QuadRender {
    /**
     * Create a new QuadRender instance.
     *
     * @param {Shader} shader - The shader to be used to render the quad.
     */ constructor(shader){
        const device = shader.device;
        this.shader = shader;
        Debug.assert(shader);
        if (device.supportsUniformBuffers) {
            // add uniform buffer support to shader
            const processingOptions = new ShaderProcessorOptions();
            this.shader = ShaderUtils.processShader(shader, processingOptions);
            // uniform buffer
            const ubFormat = this.shader.meshUniformBufferFormat;
            if (ubFormat) {
                this.uniformBuffer = new UniformBuffer(device, ubFormat, false);
            }
            // bind group
            const bindGroupFormat = this.shader.meshBindGroupFormat;
            Debug.assert(bindGroupFormat);
            this.bindGroup = new BindGroup(device, bindGroupFormat);
            DebugHelper.setName(this.bindGroup, `QuadRender-MeshBindGroup_${this.bindGroup.id}`);
        }
    }
    /**
     * Destroys the resources associated with this instance.
     */ destroy() {
        this.uniformBuffer?.destroy();
        this.uniformBuffer = null;
        this.bindGroup?.destroy();
        this.bindGroup = null;
    }
    /**
     * Renders the quad. If the viewport is provided, the original viewport and scissor is restored
     * after the rendering.
     *
     * @param {Vec4} [viewport] - The viewport rectangle of the quad, in pixels. The viewport is
     * not changed if not provided.
     * @param {Vec4} [scissor] - The scissor rectangle of the quad, in pixels. Used only if the
     * viewport is provided.
     */ render(viewport, scissor) {
        const device = this.shader.device;
        DebugGraphics.pushGpuMarker(device, 'QuadRender');
        // only modify viewport or scissor if viewport supplied
        if (viewport) {
            // backup current settings
            _tempViewport.set(device.vx, device.vy, device.vw, device.vh);
            _tempScissor.set(device.sx, device.sy, device.sw, device.sh);
            // set new values
            scissor = scissor ?? viewport;
            device.setViewport(viewport.x, viewport.y, viewport.z, viewport.w);
            device.setScissor(scissor.x, scissor.y, scissor.z, scissor.w);
        }
        device.setVertexBuffer(device.quadVertexBuffer);
        const shader = this.shader;
        device.setShader(shader);
        if (device.supportsUniformBuffers) {
            // not using view bind group
            device.setBindGroup(BINDGROUP_VIEW, device.emptyBindGroup);
            // mesh bind group
            const bindGroup = this.bindGroup;
            bindGroup.update();
            device.setBindGroup(BINDGROUP_MESH, bindGroup);
            // dynamic uniform buffer bind group
            const uniformBuffer = this.uniformBuffer;
            if (uniformBuffer) {
                uniformBuffer.update(_dynamicBindGroup$1);
                device.setBindGroup(BINDGROUP_MESH_UB, _dynamicBindGroup$1.bindGroup, _dynamicBindGroup$1.offsets);
            } else {
                device.setBindGroup(BINDGROUP_MESH_UB, device.emptyBindGroup);
            }
        }
        device.draw(_quadPrimitive);
        // restore if changed
        if (viewport) {
            device.setViewport(_tempViewport.x, _tempViewport.y, _tempViewport.z, _tempViewport.w);
            device.setScissor(_tempScissor.x, _tempScissor.y, _tempScissor.z, _tempScissor.w);
        }
        DebugGraphics.popGpuMarker(device);
    }
}

/**
 * A render pass implementing rendering of a QuadRender.
 */ class RenderPassQuad extends RenderPass {
    constructor(device, quad, rect, scissorRect){
        super(device);
        this.quad = quad;
        this.rect = rect;
        this.scissorRect = scissorRect;
    }
    execute() {
        const { device } = this;
        DebugGraphics.pushGpuMarker(device, `${this.name}:${this.quad.shader.name}`);
        device.setCullMode(CULLFACE_NONE);
        device.setDepthState(DepthState.NODEPTH);
        device.setStencilState(null, null);
        this.quad.render(this.rect, this.scissorRect);
        DebugGraphics.popGpuMarker(device);
    }
}

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { RenderTarget } from '../../platform/graphics/render-target.js'
 * @import { Shader } from '../../platform/graphics/shader.js'
 * @import { Texture } from '../../platform/graphics/texture.js'
 */ const _tempRect = new Vec4();
/**
 * Draws a screen-space quad using a specific shader.
 *
 * @param {GraphicsDevice} device - The graphics device used to draw the quad.
 * @param {RenderTarget|null} target - The destination render target. If undefined, target is the
 * frame buffer.
 * @param {Shader} shader - The shader used for rendering the quad. Vertex shader should contain
 * `attribute vec2 vertex_position`.
 * @param {Vec4} [rect] - The viewport rectangle of the quad, in pixels. Defaults to fullscreen:
 * `[0, 0, target.width, target.height]`.
 * @param {Vec4} [scissorRect] - The scissor rectangle of the quad, in pixels. Defaults to fullscreen:
 * `[0, 0, target.width, target.height]`.
 * @category Graphics
 */ function drawQuadWithShader(device, target, shader, rect, scissorRect) {
    // a valid target or a null target (framebuffer) are supported
    Debug.assert(target !== undefined);
    const useBlend = arguments[5];
    Debug.call(()=>{
        if (useBlend !== undefined) {
            Debug.warnOnce('pc.drawQuadWithShader no longer accepts useBlend parameter, and blending state needs to be set up using GraphicsDevice.setBlendState.');
        }
    });
    // prepare the quad for rendering with the shader
    const quad = new QuadRender(shader);
    // by default render to the whole render target
    if (!rect) {
        rect = _tempRect;
        rect.x = 0;
        rect.y = 0;
        rect.z = target ? target.width : device.width;
        rect.w = target ? target.height : device.height;
    }
    // prepare a render pass to render the quad to the render target
    const renderPass = new RenderPassQuad(device, quad, rect, scissorRect);
    renderPass.init(target);
    renderPass.colorOps.clear = false;
    renderPass.depthStencilOps.clearDepth = false;
    // TODO: This is a workaround for the case where post-effects are used together with multi-sampled framebuffer. Last post-effect
    // renders into multi-sampled framebuffer (render pass A), which is typically followed by further rendering to this framebuffer,
    // in a separate render pass B (e.g. rendering UI). Those two render passes need to be merged into one, as they both render into
    // the same framebuffer. The workaround here is to store multi-sampled color buffer, instead of only resolving it, which is wasted
    // memory bandwidth. Without this we end up with a black result (or just UI), as multi-sampled color buffer is never written to.
    if (device.isWebGPU && target === null && device.samples > 1) {
        renderPass.colorOps.store = true;
    }
    renderPass.render();
    quad.destroy();
}

/**
 * Holds mesh batching settings and a unique id. Created via {@link BatchManager#addGroup}.
 *
 * @category Graphics
 */ class BatchGroup {
    /**
     * Create a new BatchGroup instance.
     *
     * @param {number} id - Unique id. Can be assigned to model, render and element components.
     * @param {string} name - The name of the group.
     * @param {boolean} dynamic - Whether objects within this batch group should support
     * transforming at runtime.
     * @param {number} maxAabbSize - Maximum size of any dimension of a bounding box around batched
     * objects. {@link BatchManager#prepare} will split objects into local groups based on this
     * size.
     * @param {number[]} [layers] - Layer ID array. Default is [{@link LAYERID_WORLD}]. The whole
     * batch group will belong to these layers. Layers of source models will be ignored.
     */ constructor(id, name, dynamic, maxAabbSize, layers = [
        LAYERID_WORLD
    ]){
        /** @private */ this._ui = false;
        /** @private */ this._sprite = false;
        /** @private */ this._obj = {
            model: [],
            element: [],
            sprite: [],
            render: []
        };
        this.id = id;
        this.name = name;
        this.dynamic = dynamic;
        this.maxAabbSize = maxAabbSize;
        this.layers = layers;
    }
    static{
        this.MODEL = 'model';
    }
    static{
        this.ELEMENT = 'element';
    }
    static{
        this.SPRITE = 'sprite';
    }
    static{
        this.RENDER = 'render';
    }
}

/**
 * @import { Entity } from '../framework/entity.js'
 * @import { GraphNode } from './graph-node.js'
 * @import { Skin } from './skin.js'
 */ const _invMatrix = new Mat4();
/**
 * A skin instance is responsible for generating the matrix palette that is used to skin vertices
 * from object space to world space.
 *
 * @category Graphics
 */ class SkinInstance {
    /**
     * Create a new SkinInstance instance.
     *
     * @param {Skin} skin - The skin that will provide the inverse bind pose
     * matrices to generate the final matrix palette.
     */ constructor(skin){
        this._dirty = true;
        // optional root bone - used for cache lookup, not used for skinning
        this._rootBone = null;
        // sequential index of when the bone update was performed the last time
        this._skinUpdateIndex = -1;
        // true if bones need to be updated before the frustum culling (bones are needed to update bounds of the MeshInstance)
        this._updateBeforeCull = true;
        if (skin) {
            this.initSkin(skin);
        }
    }
    set rootBone(rootBone) {
        this._rootBone = rootBone;
    }
    get rootBone() {
        return this._rootBone;
    }
    init(device, numBones) {
        // texture size - roughly square that fits all bones, width is multiply of 3 to simplify shader math
        const numPixels = numBones * 3;
        let width = Math.ceil(Math.sqrt(numPixels));
        width = math.roundUp(width, 3);
        const height = Math.ceil(numPixels / width);
        this.boneTexture = new Texture(device, {
            width: width,
            height: height,
            format: PIXELFORMAT_RGBA32F,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            name: 'skin'
        });
        this.matrixPalette = this.boneTexture.lock({
            mode: TEXTURELOCK_READ
        });
        this.boneTexture.unlock();
    }
    destroy() {
        if (this.boneTexture) {
            this.boneTexture.destroy();
            this.boneTexture = null;
        }
    }
    /**
     * Resolves skin bones to a hierarchy with the rootBone at its root.
     *
     * @param {Entity} rootBone - A reference to the entity to be used as the root bone.
     * @param {Entity} entity - Specifies the entity used if the bone match is not found in the
     * hierarchy - usually the entity the render component is attached to.
     * @ignore
     */ resolve(rootBone, entity) {
        this.rootBone = rootBone;
        // Resolve bone IDs to actual graph nodes of the hierarchy
        const skin = this.skin;
        const bones = [];
        for(let j = 0; j < skin.boneNames.length; j++){
            const boneName = skin.boneNames[j];
            /** @type {Entity|GraphNode|null} */ let bone = rootBone.findByName(boneName);
            if (!bone) {
                Debug.error(`Failed to find bone [${boneName}] in the entity hierarchy, RenderComponent on ${entity.name}, rootBone: ${rootBone.name}`);
                bone = entity;
            }
            bones.push(bone);
        }
        this.bones = bones;
    }
    /**
     * @param {Skin} skin - The skin.
     */ initSkin(skin) {
        this.skin = skin;
        // Unique per clone
        this.bones = [];
        const numBones = skin.inverseBindPose.length;
        this.init(skin.device, numBones);
        this.matrices = [];
        for(let i = 0; i < numBones; i++){
            this.matrices[i] = new Mat4();
        }
    }
    uploadBones(device) {
        this.boneTexture.upload();
    }
    _updateMatrices(rootNode, skinUpdateIndex) {
        // if not already up to date
        if (this._skinUpdateIndex !== skinUpdateIndex) {
            this._skinUpdateIndex = skinUpdateIndex;
            _invMatrix.copy(rootNode.getWorldTransform()).invert();
            for(let i = this.bones.length - 1; i >= 0; i--){
                this.matrices[i].mulAffine2(_invMatrix, this.bones[i].getWorldTransform()); // world space -> rootNode space
                this.matrices[i].mulAffine2(this.matrices[i], this.skin.inverseBindPose[i]); // rootNode space -> bind space
            }
        }
    }
    updateMatrices(rootNode, skinUpdateIndex) {
        if (this._updateBeforeCull) {
            this._updateMatrices(rootNode, skinUpdateIndex);
        }
    }
    updateMatrixPalette(rootNode, skinUpdateIndex) {
        // make sure matrices are up to date
        this._updateMatrices(rootNode, skinUpdateIndex);
        // copy matrices to palette
        const mp = this.matrixPalette;
        const count = this.bones.length;
        for(let i = 0; i < count; i++){
            const pe = this.matrices[i].data;
            // Copy the matrix into the palette, ready to be sent to the vertex shader, transpose matrix from 4x4 to 4x3 format as well
            const base = i * 12;
            mp[base] = pe[0];
            mp[base + 1] = pe[4];
            mp[base + 2] = pe[8];
            mp[base + 3] = pe[12];
            mp[base + 4] = pe[1];
            mp[base + 5] = pe[5];
            mp[base + 6] = pe[9];
            mp[base + 7] = pe[13];
            mp[base + 8] = pe[2];
            mp[base + 9] = pe[6];
            mp[base + 10] = pe[10];
            mp[base + 11] = pe[14];
        }
        this.uploadBones(this.skin.device);
    }
}

/**
 * @import { Geometry } from './geometry/geometry.js'
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 * @import { Morph } from './morph.js'
 * @import { Skin } from './skin.js'
 */ let id$5 = 0;
// Helper class used to store vertex / index data streams and related properties, when mesh is programmatically modified
class GeometryData {
    constructor(){
        this.initDefaults();
    }
    initDefaults() {
        // by default, existing mesh is updated but not recreated, until .clear function is called
        this.recreate = false;
        // usage for buffers
        this.verticesUsage = BUFFER_STATIC;
        this.indicesUsage = BUFFER_STATIC;
        // vertex and index buffer allocated size (maximum number of vertices / indices that can be stored in those without the need to reallocate them)
        this.maxVertices = 0;
        this.maxIndices = 0;
        // current number of vertices and indices in use
        this.vertexCount = 0;
        this.indexCount = 0;
        // dirty flags representing what needs be updated
        this.vertexStreamsUpdated = false;
        this.indexStreamUpdated = false;
        // dictionary of vertex streams that need to be updated, looked up by semantic
        this.vertexStreamDictionary = {};
        // index stream data that needs to be updated
        this.indices = null;
    }
    // function called when vertex stream is requested to be updated, and validates / updates currently used vertex count
    _changeVertexCount(count, semantic) {
        // update vertex count and validate it with existing streams
        if (!this.vertexCount) {
            this.vertexCount = count;
        } else {
            Debug.assert(this.vertexCount === count, `Vertex stream ${semantic} has ${count} vertices, which does not match already set streams with ${this.vertexCount} vertices.`);
        }
    }
    static{
        // default counts for vertex components
        this.DEFAULT_COMPONENTS_POSITION = 3;
    }
    static{
        this.DEFAULT_COMPONENTS_NORMAL = 3;
    }
    static{
        this.DEFAULT_COMPONENTS_UV = 2;
    }
    static{
        this.DEFAULT_COMPONENTS_COLORS = 4;
    }
}
// class storing information about single vertex data stream
class GeometryVertexStream {
    constructor(data, componentCount, dataType, dataTypeNormalize, asInt){
        this.data = data; // array of data
        this.componentCount = componentCount; // number of components
        this.dataType = dataType; // format of elements (pc.TYPE_FLOAT32 ..)
        this.dataTypeNormalize = dataTypeNormalize; // normalize element (divide by 255)
        this.asInt = asInt; // treat data as integer (WebGL2 and WebGPU only)
    }
}
/**
 * A graphical primitive. The mesh is defined by a {@link VertexBuffer} and an optional
 * {@link IndexBuffer}. It also contains a primitive definition which controls the type of the
 * primitive and the portion of the vertex or index buffer to use.
 *
 * ## Mesh APIs
 * There are two ways a mesh can be generated or updated.
 *
 * ### Simple Mesh API
 * {@link Mesh} class provides interfaces such as {@link Mesh#setPositions} and {@link Mesh#setUvs}
 * that provide a simple way to provide vertex and index data for the Mesh, and hiding the
 * complexity of creating the {@link VertexFormat}. This is the recommended interface to use.
 *
 * A simple example which creates a Mesh with 3 vertices, containing position coordinates only, to
 * form a single triangle.
 *
 * ```javascript
 * const mesh = new pc.Mesh(device);
 * const positions = [
 *     0, 0, 0, // pos 0
 *     1, 0, 0, // pos 1
 *     1, 1, 0  // pos 2
 * ];
 * mesh.setPositions(positions);
 * mesh.update();
 * ```
 *
 * An example which creates a Mesh with 4 vertices, containing position and uv coordinates in
 * channel 0, and an index buffer to form two triangles. Float32Array is used for positions and uvs.
 *
 * ```javascript
 * const mesh = new pc.Mesh(device);
 * const positions = new Float32Array([
 *     0, 0, 0, // pos 0
 *     1, 0, 0, // pos 1
 *     1, 1, 0, // pos 2
 *     0, 1, 0  // pos 3
 * ]);
 * const uvs = new Float32Array([
 *     0, 1  // uv 3
 *     1, 1, // uv 2
 *     1, 0, // uv 1
 *     0, 0, // uv 0
 * ]);
 * const indices = [
 *     0, 1, 2, // triangle 0
 *     0, 2, 3  // triangle 1
 * ];
 * mesh.setPositions(positions);
 * mesh.setNormals(pc.calculateNormals(positions, indices));
 * mesh.setUvs(0, uvs);
 * mesh.setIndices(indices);
 * mesh.update();
 * ```
 *
 * This example demonstrates that vertex attributes such as position and normals, and also indices
 * can be provided using Arrays ([]) and also Typed Arrays (Float32Array and similar). Note that
 * typed arrays have higher performance, and are generally recommended for per-frame operations or
 * larger meshes, but their construction using new operator is costly operation. If you only need
 * to operate on a small number of vertices or indices, consider using Arrays to avoid the overhead
 * associated with allocating Typed Arrays.
 *
 * Follow these links for more complex examples showing the functionality.
 *
 * - {@link https://playcanvas.github.io/#graphics/mesh-decals}
 * - {@link https://playcanvas.github.io/#graphics/mesh-deformation}
 * - {@link https://playcanvas.github.io/#graphics/mesh-generation}
 * - {@link https://playcanvas.github.io/#graphics/point-cloud-simulation}
 *
 * ### Update Vertex and Index buffers
 * This allows greater flexibility, but is more complex to use. It allows more advanced setups, for
 * example sharing a Vertex or Index Buffer between multiple meshes. See {@link VertexBuffer},
 * {@link IndexBuffer} and {@link VertexFormat} for details.
 *
 * @category Graphics
 */ class Mesh extends RefCountedObject {
    /**
     * Create a new Mesh instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this mesh.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {boolean} [options.storageVertex] - Defines if the vertex buffer can be used as
     * a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
     * @param {boolean} [options.storageIndex] - Defines if the index buffer can be used as
     * a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
     */ constructor(graphicsDevice, options){
        super(), /**
     * An array of index buffers. For unindexed meshes, this array can be empty. The first index
     * buffer in the array is used by {@link MeshInstance}s with a `renderStyle` property set to
     * {@link RENDERSTYLE_SOLID}. The second index buffer in the array is used if `renderStyle` is
     * set to {@link RENDERSTYLE_WIREFRAME}.
     *
     * @type {IndexBuffer[]}
     */ this.indexBuffer = [
            null
        ], /**
     * The vertex buffer holding the vertex data of the mesh.
     *
     * @type {VertexBuffer}
     */ this.vertexBuffer = null, /**
     * Array of primitive objects defining how vertex (and index) data in the mesh should be
     * interpreted by the graphics device.
     *
     * - `type` is the type of primitive to render. Can be:
     *
     *   - {@link PRIMITIVE_POINTS}
     *   - {@link PRIMITIVE_LINES}
     *   - {@link PRIMITIVE_LINELOOP}
     *   - {@link PRIMITIVE_LINESTRIP}
     *   - {@link PRIMITIVE_TRIANGLES}
     *   - {@link PRIMITIVE_TRISTRIP}
     *   - {@link PRIMITIVE_TRIFAN}
     *
     * - `base` is the offset of the first index or vertex to dispatch in the draw call.
     * - `baseVertex` is the number added to each index value before indexing into the vertex buffers. (supported only in WebGPU, ignored in WebGL2)
     * - `count` is the number of indices or vertices to dispatch in the draw call.
     * - `indexed` specifies whether to interpret the primitive as indexed, thereby using the
     * currently set index buffer.
     *
     * @type {{type: number, base: number, baseVertex: number, count: number, indexed?: boolean}[]}
     */ this.primitive = [
            {
                type: 0,
                base: 0,
                baseVertex: 0,
                count: 0
            }
        ], /**
     * The skin data (if any) that drives skinned mesh animations for this mesh.
     *
     * @type {Skin|null}
     */ this.skin = null, /**
     * Array of object space AABBs of vertices affected by each bone.
     *
     * @type {BoundingBox[]|null}
     * @ignore
     */ this.boneAabb = null, /**
     * Internal version of AABB, incremented when local AABB changes.
     *
     * @ignore
     */ this._aabbVer = 0, /**
     * AABB representing object space bounds of the mesh.
     *
     * @type {BoundingBox}
     * @private
     */ this._aabb = new BoundingBox(), /**
     * @type {GeometryData|null}
     * @private
     */ this._geometryData = null, /**
     * @type {Morph|null}
     * @private
     */ this._morph = null, /**
     * True if the created index buffer should be accessible as a storage buffer in compute shader.
     *
     * @type {boolean}
     * @private
     */ this._storageIndex = false, /**
     * True if the created vertex buffer should be accessible as a storage buffer in compute shader.
     *
     * @type {boolean}
     * @private
     */ this._storageVertex = false;
        this.id = id$5++;
        Debug.assert(graphicsDevice, 'Mesh constructor takes a GraphicsDevice as a parameter, and it was not provided.');
        this.device = graphicsDevice;
        this._storageIndex = options?.storageIndex || false;
        this._storageVertex = options?.storageVertex || false;
    }
    /**
     * Create a new Mesh instance from {@link Geometry} object.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this mesh.
     * @param {Geometry} geometry - The geometry object to create the mesh from.
     * @param {object} [options] - An object that specifies optional inputs for the function as follows:
     * @param {boolean} [options.storageVertex] - Defines if the vertex buffer of the mesh can be used as
     * a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
     * @param {boolean} [options.storageIndex] - Defines if the index buffer of the mesh can be used as
     * a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
     * @returns {Mesh} A new mesh.
     */ static fromGeometry(graphicsDevice, geometry, options = {}) {
        const mesh = new Mesh(graphicsDevice, options);
        const { positions, normals, tangents, colors, uvs, uvs1, blendIndices, blendWeights, indices } = geometry;
        if (positions) {
            mesh.setPositions(positions);
        }
        if (normals) {
            mesh.setNormals(normals);
        }
        if (tangents) {
            mesh.setVertexStream(SEMANTIC_TANGENT, tangents, 4);
        }
        if (colors) {
            mesh.setColors32(colors);
        }
        if (uvs) {
            mesh.setUvs(0, uvs);
        }
        if (uvs1) {
            mesh.setUvs(1, uvs1);
        }
        if (blendIndices) {
            mesh.setVertexStream(SEMANTIC_BLENDINDICES, blendIndices, 4, blendIndices.length / 4, TYPE_UINT8);
        }
        if (blendWeights) {
            mesh.setVertexStream(SEMANTIC_BLENDWEIGHT, blendWeights, 4);
        }
        if (indices) {
            mesh.setIndices(indices);
        }
        mesh.update();
        return mesh;
    }
    /**
     * Sets the morph data that drives morph target animations for this mesh. Set to null if
     * morphing is not used.
     *
     * @type {Morph|null}
     */ set morph(morph) {
        if (morph !== this._morph) {
            if (this._morph) {
                this._morph.decRefCount();
            }
            this._morph = morph;
            if (morph) {
                morph.incRefCount();
            }
        }
    }
    /**
     * Gets the morph data that drives morph target animations for this mesh.
     *
     * @type {Morph|null}
     */ get morph() {
        return this._morph;
    }
    /**
     * Sets the axis-aligned bounding box for the object space vertices of this mesh.
     *
     * @type {BoundingBox}
     */ set aabb(aabb) {
        this._aabb = aabb;
        this._aabbVer++;
    }
    /**
     * Gets the axis-aligned bounding box for the object space vertices of this mesh.
     *
     * @type {BoundingBox}
     */ get aabb() {
        return this._aabb;
    }
    /**
     * Destroys the {@link VertexBuffer} and {@link IndexBuffer}s associated with the mesh. This is
     * normally called by {@link Model#destroy} and does not need to be called manually.
     */ destroy() {
        const morph = this.morph;
        if (morph) {
            // this decreases ref count on the morph
            this.morph = null;
            // destroy morph
            if (morph.refCount < 1) {
                morph.destroy();
            }
        }
        if (this.vertexBuffer) {
            this.vertexBuffer.destroy();
            this.vertexBuffer = null;
        }
        for(let j = 0; j < this.indexBuffer.length; j++){
            this._destroyIndexBuffer(j);
        }
        this.indexBuffer.length = 0;
        this._geometryData = null;
    }
    _destroyIndexBuffer(index) {
        if (this.indexBuffer[index]) {
            this.indexBuffer[index].destroy();
            this.indexBuffer[index] = null;
        }
    }
    // initializes local bounding boxes for each bone based on vertices affected by the bone
    // if morph targets are provided, it also adjusts local bone bounding boxes by maximum morph displacement
    _initBoneAabbs(morphTargets) {
        this.boneAabb = [];
        this.boneUsed = [];
        let x, y, z;
        let bMax, bMin;
        const boneMin = [];
        const boneMax = [];
        const boneUsed = this.boneUsed;
        const numBones = this.skin.boneNames.length;
        let maxMorphX, maxMorphY, maxMorphZ;
        // start with empty bone bounds
        for(let i = 0; i < numBones; i++){
            boneMin[i] = new Vec3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
            boneMax[i] = new Vec3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
        }
        // access to mesh from vertex buffer
        const iterator = new VertexIterator(this.vertexBuffer);
        const posElement = iterator.element[SEMANTIC_POSITION];
        const weightsElement = iterator.element[SEMANTIC_BLENDWEIGHT];
        const indicesElement = iterator.element[SEMANTIC_BLENDINDICES];
        // Find bone AABBs of attached vertices
        const numVerts = this.vertexBuffer.numVertices;
        for(let j = 0; j < numVerts; j++){
            for(let k = 0; k < 4; k++){
                const boneWeight = weightsElement.array[weightsElement.index + k];
                if (boneWeight > 0) {
                    const boneIndex = indicesElement.array[indicesElement.index + k];
                    boneUsed[boneIndex] = true;
                    x = posElement.array[posElement.index];
                    y = posElement.array[posElement.index + 1];
                    z = posElement.array[posElement.index + 2];
                    // adjust bounds of a bone by the vertex
                    bMax = boneMax[boneIndex];
                    bMin = boneMin[boneIndex];
                    if (bMin.x > x) bMin.x = x;
                    if (bMin.y > y) bMin.y = y;
                    if (bMin.z > z) bMin.z = z;
                    if (bMax.x < x) bMax.x = x;
                    if (bMax.y < y) bMax.y = y;
                    if (bMax.z < z) bMax.z = z;
                    if (morphTargets) {
                        // find maximum displacement of the vertex by all targets
                        let minMorphX = maxMorphX = x;
                        let minMorphY = maxMorphY = y;
                        let minMorphZ = maxMorphZ = z;
                        // morph this vertex by all morph targets
                        for(let l = 0; l < morphTargets.length; l++){
                            const target = morphTargets[l];
                            const dx = target.deltaPositions[j * 3];
                            const dy = target.deltaPositions[j * 3 + 1];
                            const dz = target.deltaPositions[j * 3 + 2];
                            if (dx < 0) {
                                minMorphX += dx;
                            } else {
                                maxMorphX += dx;
                            }
                            if (dy < 0) {
                                minMorphY += dy;
                            } else {
                                maxMorphY += dy;
                            }
                            if (dz < 0) {
                                minMorphZ += dz;
                            } else {
                                maxMorphZ += dz;
                            }
                        }
                        if (bMin.x > minMorphX) bMin.x = minMorphX;
                        if (bMin.y > minMorphY) bMin.y = minMorphY;
                        if (bMin.z > minMorphZ) bMin.z = minMorphZ;
                        if (bMax.x < maxMorphX) bMax.x = maxMorphX;
                        if (bMax.y < maxMorphY) bMax.y = maxMorphY;
                        if (bMax.z < maxMorphZ) bMax.z = maxMorphZ;
                    }
                }
            }
            iterator.next();
        }
        // account for normalized positional data
        const positionElement = this.vertexBuffer.getFormat().elements.find((e)=>e.name === SEMANTIC_POSITION);
        if (positionElement && positionElement.normalize) {
            const func = (()=>{
                switch(positionElement.dataType){
                    case TYPE_INT8:
                        return (x)=>Math.max(x / 127.0, -1);
                    case TYPE_UINT8:
                        return (x)=>x / 255.0;
                    case TYPE_INT16:
                        return (x)=>Math.max(x / 32767.0, -1);
                    case TYPE_UINT16:
                        return (x)=>x / 65535.0;
                    default:
                        return (x)=>x;
                }
            })();
            for(let i = 0; i < numBones; i++){
                if (boneUsed[i]) {
                    const min = boneMin[i];
                    const max = boneMax[i];
                    min.set(func(min.x), func(min.y), func(min.z));
                    max.set(func(max.x), func(max.y), func(max.z));
                }
            }
        }
        // store bone bounding boxes
        for(let i = 0; i < numBones; i++){
            const aabb = new BoundingBox();
            aabb.setMinMax(boneMin[i], boneMax[i]);
            this.boneAabb.push(aabb);
        }
    }
    // when mesh API to modify vertex / index data are used, this allocates structure to store the data
    _initGeometryData() {
        if (!this._geometryData) {
            this._geometryData = new GeometryData();
            // if vertex buffer exists already, store the sizes
            if (this.vertexBuffer) {
                this._geometryData.vertexCount = this.vertexBuffer.numVertices;
                this._geometryData.maxVertices = this.vertexBuffer.numVertices;
            }
            // if index buffer exists already, store the sizes
            if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
                this._geometryData.indexCount = this.indexBuffer[0].numIndices;
                this._geometryData.maxIndices = this.indexBuffer[0].numIndices;
            }
        }
    }
    /**
     * Clears the mesh of existing vertices and indices and resets the {@link VertexFormat}
     * associated with the mesh. This call is typically followed by calls to methods such as
     * {@link Mesh#setPositions}, {@link Mesh#setVertexStream} or {@link Mesh#setIndices} and
     * finally {@link Mesh#update} to rebuild the mesh, allowing different {@link VertexFormat}.
     *
     * @param {boolean} [verticesDynamic] - Indicates the {@link VertexBuffer} should be created
     * with {@link BUFFER_DYNAMIC} usage. If not specified, {@link BUFFER_STATIC} is used.
     * @param {boolean} [indicesDynamic] - Indicates the {@link IndexBuffer} should be created with
     * {@link BUFFER_DYNAMIC} usage. If not specified, {@link BUFFER_STATIC} is used.
     * @param {number} [maxVertices] - A {@link VertexBuffer} will be allocated with at least
     * maxVertices, allowing additional vertices to be added to it without the allocation. If no
     * value is provided, a size to fit the provided vertices will be allocated.
     * @param {number} [maxIndices] - An {@link IndexBuffer} will be allocated with at least
     * maxIndices, allowing additional indices to be added to it without the allocation. If no
     * value is provided, a size to fit the provided indices will be allocated.
     */ clear(verticesDynamic, indicesDynamic, maxVertices = 0, maxIndices = 0) {
        this._initGeometryData();
        this._geometryData.initDefaults();
        this._geometryData.recreate = true;
        this._geometryData.maxVertices = maxVertices;
        this._geometryData.maxIndices = maxIndices;
        this._geometryData.verticesUsage = verticesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC;
        this._geometryData.indicesUsage = indicesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC;
    }
    /**
     * Sets the vertex data for any supported semantic.
     *
     * @param {string} semantic - The meaning of the vertex element. For supported semantics, see
     * SEMANTIC_* in {@link VertexFormat}.
     * @param {number[]|ArrayBufferView} data - Vertex data for the specified semantic.
     * @param {number} componentCount - The number of values that form a single Vertex element. For
     * example when setting a 3D position represented by 3 numbers per vertex, number 3 should be
     * specified.
     * @param {number} [numVertices] - The number of vertices to be used from data array. If not
     * provided, the whole data array is used. This allows to use only part of the data array.
     * @param {number} [dataType] - The format of data when stored in the {@link VertexBuffer}, see
     * TYPE_* in {@link VertexFormat}. When not specified, {@link TYPE_FLOAT32} is used.
     * @param {boolean} [dataTypeNormalize] - If true, vertex attribute data will be mapped from a
     * 0 to 255 range down to 0 to 1 when fed to a shader. If false, vertex attribute data is left
     * unchanged. If this property is unspecified, false is assumed.
     * @param {boolean} [asInt] - If true, vertex attribute data will be accessible as integer
     * numbers in shader code. Defaults to false, which means that vertex attribute data will be
     * accessible as floating point numbers. Can be only used with INT and UINT data types.
     */ setVertexStream(semantic, data, componentCount, numVertices, dataType = TYPE_FLOAT32, dataTypeNormalize = false, asInt = false) {
        this._initGeometryData();
        const vertexCount = numVertices || data.length / componentCount;
        this._geometryData._changeVertexCount(vertexCount, semantic);
        this._geometryData.vertexStreamsUpdated = true;
        this._geometryData.vertexStreamDictionary[semantic] = new GeometryVertexStream(data, componentCount, dataType, dataTypeNormalize, asInt);
    }
    /**
     * Gets the vertex data corresponding to a semantic.
     *
     * @param {string} semantic - The semantic of the vertex element to get. For supported
     * semantics, see SEMANTIC_* in {@link VertexFormat}.
     * @param {number[]|ArrayBufferView} data - An array to populate with the vertex data. When
     * typed array is supplied, enough space needs to be reserved, otherwise only partial data is
     * copied.
     * @returns {number} Returns the number of vertices populated.
     */ getVertexStream(semantic, data) {
        let count = 0;
        let done = false;
        // see if we have un-applied stream
        if (this._geometryData) {
            const stream = this._geometryData.vertexStreamDictionary[semantic];
            if (stream) {
                done = true;
                count = this._geometryData.vertexCount;
                if (ArrayBuffer.isView(data)) {
                    // destination data is typed array
                    data.set(stream.data);
                } else {
                    // destination data is array
                    data.length = 0;
                    data.push(stream.data);
                }
            }
        }
        if (!done) {
            // get stream from VertexBuffer
            if (this.vertexBuffer) {
                // note: there is no need to .end the iterator, as we are only reading data from it
                const iterator = new VertexIterator(this.vertexBuffer);
                count = iterator.readData(semantic, data);
            }
        }
        return count;
    }
    /**
     * Sets the vertex positions array. Vertices are stored using {@link TYPE_FLOAT32} format.
     *
     * @param {number[]|ArrayBufferView} positions - Vertex data containing positions.
     * @param {number} [componentCount] - The number of values that form a single position element.
     * Defaults to 3 if not specified, corresponding to x, y and z coordinates.
     * @param {number} [numVertices] - The number of vertices to be used from data array. If not
     * provided, the whole data array is used. This allows to use only part of the data array.
     */ setPositions(positions, componentCount = GeometryData.DEFAULT_COMPONENTS_POSITION, numVertices) {
        this.setVertexStream(SEMANTIC_POSITION, positions, componentCount, numVertices, TYPE_FLOAT32, false);
    }
    /**
     * Sets the vertex normals array. Normals are stored using {@link TYPE_FLOAT32} format.
     *
     * @param {number[]|ArrayBufferView} normals - Vertex data containing normals.
     * @param {number} [componentCount] - The number of values that form a single normal element.
     * Defaults to 3 if not specified, corresponding to x, y and z direction.
     * @param {number} [numVertices] - The number of vertices to be used from data array. If not
     * provided, the whole data array is used. This allows to use only part of the data array.
     */ setNormals(normals, componentCount = GeometryData.DEFAULT_COMPONENTS_NORMAL, numVertices) {
        this.setVertexStream(SEMANTIC_NORMAL, normals, componentCount, numVertices, TYPE_FLOAT32, false);
    }
    /**
     * Sets the vertex uv array. Uvs are stored using {@link TYPE_FLOAT32} format.
     *
     * @param {number} channel - The uv channel in [0..7] range.
     * @param {number[]|ArrayBufferView} uvs - Vertex data containing uv-coordinates.
     * @param {number} [componentCount] - The number of values that form a single uv element.
     * Defaults to 2 if not specified, corresponding to u and v coordinates.
     * @param {number} [numVertices] - The number of vertices to be used from data array. If not
     * provided, the whole data array is used. This allows to use only part of the data array.
     */ setUvs(channel, uvs, componentCount = GeometryData.DEFAULT_COMPONENTS_UV, numVertices) {
        this.setVertexStream(SEMANTIC_TEXCOORD + channel, uvs, componentCount, numVertices, TYPE_FLOAT32, false);
    }
    /**
     * Sets the vertex color array. Colors are stored using {@link TYPE_FLOAT32} format, which is
     * useful for HDR colors.
     *
     * @param {number[]|ArrayBufferView} colors - Vertex data containing colors.
     * @param {number} [componentCount] - The number of values that form a single color element.
     * Defaults to 4 if not specified, corresponding to r, g, b and a.
     * @param {number} [numVertices] - The number of vertices to be used from data array. If not
     * provided, the whole data array is used. This allows to use only part of the data array.
     */ setColors(colors, componentCount = GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices) {
        this.setVertexStream(SEMANTIC_COLOR, colors, componentCount, numVertices, TYPE_FLOAT32, false);
    }
    /**
     * Sets the vertex color array. Colors are stored using {@link TYPE_UINT8} format, which is
     * useful for LDR colors. Values in the array are expected in [0..255] range, and are mapped to
     * [0..1] range in the shader.
     *
     * @param {number[]|ArrayBufferView} colors - Vertex data containing colors. The array is
     * expected to contain 4 components per vertex, corresponding to r, g, b and a.
     * @param {number} [numVertices] - The number of vertices to be used from data array. If not
     * provided, the whole data array is used. This allows to use only part of the data array.
     */ setColors32(colors, numVertices) {
        this.setVertexStream(SEMANTIC_COLOR, colors, GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices, TYPE_UINT8, true);
    }
    /**
     * Sets the index array. Indices are stored using 16-bit format by default, unless more than
     * 65535 vertices are specified, in which case 32-bit format is used.
     *
     * @param {number[]|Uint8Array|Uint16Array|Uint32Array} indices - The array of indices that
     * define primitives (lines, triangles, etc.).
     * @param {number} [numIndices] - The number of indices to be used from data array. If not
     * provided, the whole data array is used. This allows to use only part of the data array.
     */ setIndices(indices, numIndices) {
        this._initGeometryData();
        this._geometryData.indexStreamUpdated = true;
        this._geometryData.indices = indices;
        this._geometryData.indexCount = numIndices || indices.length;
    }
    /**
     * Gets the vertex positions data.
     *
     * @param {number[]|ArrayBufferView} positions - An array to populate with the vertex data.
     * When typed array is supplied, enough space needs to be reserved, otherwise only partial data
     * is copied.
     * @returns {number} Returns the number of vertices populated.
     */ getPositions(positions) {
        return this.getVertexStream(SEMANTIC_POSITION, positions);
    }
    /**
     * Gets the vertex normals data.
     *
     * @param {number[]|ArrayBufferView} normals - An array to populate with the vertex data. When
     * typed array is supplied, enough space needs to be reserved, otherwise only partial data is
     * copied.
     * @returns {number} Returns the number of vertices populated.
     */ getNormals(normals) {
        return this.getVertexStream(SEMANTIC_NORMAL, normals);
    }
    /**
     * Gets the vertex uv data.
     *
     * @param {number} channel - The uv channel in [0..7] range.
     * @param {number[]|ArrayBufferView} uvs - An array to populate with the vertex data. When
     * typed array is supplied, enough space needs to be reserved, otherwise only partial data is
     * copied.
     * @returns {number} Returns the number of vertices populated.
     */ getUvs(channel, uvs) {
        return this.getVertexStream(SEMANTIC_TEXCOORD + channel, uvs);
    }
    /**
     * Gets the vertex color data.
     *
     * @param {number[]|ArrayBufferView} colors - An array to populate with the vertex data. When
     * typed array is supplied, enough space needs to be reserved, otherwise only partial data is
     * copied.
     * @returns {number} Returns the number of vertices populated.
     */ getColors(colors) {
        return this.getVertexStream(SEMANTIC_COLOR, colors);
    }
    /**
     * Gets the index data.
     *
     * @param {number[]|Uint8Array|Uint16Array|Uint32Array} indices - An array to populate with the
     * index data. When a typed array is supplied, enough space needs to be reserved, otherwise
     * only partial data is copied.
     * @returns {number} Returns the number of indices populated.
     */ getIndices(indices) {
        let count = 0;
        // see if we have un-applied indices
        if (this._geometryData && this._geometryData.indices) {
            const streamIndices = this._geometryData.indices;
            count = this._geometryData.indexCount;
            if (ArrayBuffer.isView(indices)) {
                // destination data is typed array
                indices.set(streamIndices);
            } else {
                // destination data is array
                indices.length = 0;
                for(let i = 0, il = streamIndices.length; i < il; i++){
                    indices.push(streamIndices[i]);
                }
            }
        } else {
            // get data from IndexBuffer
            if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
                const indexBuffer = this.indexBuffer[0];
                count = indexBuffer.readData(indices);
            }
        }
        return count;
    }
    /**
     * Applies any changes to vertex stream and indices to mesh. This allocates or reallocates
     * {@link vertexBuffer} or {@link indexBuffer} to fit all provided vertices and indices, and
     * fills them with data.
     *
     * @param {number} [primitiveType] - The type of primitive to render.  Can be:
     *
     * - {@link PRIMITIVE_POINTS}
     * - {@link PRIMITIVE_LINES}
     * - {@link PRIMITIVE_LINELOOP}
     * - {@link PRIMITIVE_LINESTRIP}
     * - {@link PRIMITIVE_TRIANGLES}
     * - {@link PRIMITIVE_TRISTRIP}
     * - {@link PRIMITIVE_TRIFAN}
     *
     * Defaults to {@link PRIMITIVE_TRIANGLES} if not specified.
     * @param {boolean} [updateBoundingBox] - True to update bounding box. Bounding box is updated
     * only if positions were set since last time update was called, and `componentCount` for
     * position was 3, otherwise bounding box is not updated. See {@link Mesh#setPositions}.
     * Defaults to true if not specified. Set this to false to avoid update of the bounding box and
     * use aabb property to set it instead.
     */ update(primitiveType = PRIMITIVE_TRIANGLES, updateBoundingBox = true) {
        if (this._geometryData) {
            // update bounding box if needed
            if (updateBoundingBox) {
                // find vec3 position stream
                const stream = this._geometryData.vertexStreamDictionary[SEMANTIC_POSITION];
                if (stream) {
                    if (stream.componentCount === 3) {
                        this._aabb.compute(stream.data, this._geometryData.vertexCount);
                        this._aabbVer++;
                    }
                }
            }
            // destroy vertex buffer if recreate was requested or if vertices don't fit
            let destroyVB = this._geometryData.recreate;
            if (this._geometryData.vertexCount > this._geometryData.maxVertices) {
                destroyVB = true;
                this._geometryData.maxVertices = this._geometryData.vertexCount;
            }
            if (destroyVB) {
                if (this.vertexBuffer) {
                    this.vertexBuffer.destroy();
                    this.vertexBuffer = null;
                }
            }
            // destroy index buffer if recreate was requested or if indices don't fit
            let destroyIB = this._geometryData.recreate;
            if (this._geometryData.indexCount > this._geometryData.maxIndices) {
                destroyIB = true;
                this._geometryData.maxIndices = this._geometryData.indexCount;
            }
            if (destroyIB) {
                if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
                    this.indexBuffer[0].destroy();
                    this.indexBuffer[0] = null;
                }
            }
            // update vertices if needed
            if (this._geometryData.vertexStreamsUpdated) {
                this._updateVertexBuffer();
            }
            // update indices if needed
            if (this._geometryData.indexStreamUpdated) {
                this._updateIndexBuffer();
            }
            // set up primitive parameters
            this.primitive[0].type = primitiveType;
            if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
                if (this._geometryData.indexStreamUpdated) {
                    this.primitive[0].count = this._geometryData.indexCount;
                    this.primitive[0].indexed = true;
                }
            } else {
                if (this._geometryData.vertexStreamsUpdated) {
                    this.primitive[0].count = this._geometryData.vertexCount;
                    this.primitive[0].indexed = false;
                }
            }
            // counts can be changed on next frame, so set them to 0
            this._geometryData.vertexCount = 0;
            this._geometryData.indexCount = 0;
            this._geometryData.vertexStreamsUpdated = false;
            this._geometryData.indexStreamUpdated = false;
            this._geometryData.recreate = false;
            // update other render states
            this.updateRenderStates();
        }
    }
    // builds vertex format based on attached vertex streams
    _buildVertexFormat(vertexCount) {
        const vertexDesc = [];
        for(const semantic in this._geometryData.vertexStreamDictionary){
            const stream = this._geometryData.vertexStreamDictionary[semantic];
            vertexDesc.push({
                semantic: semantic,
                components: stream.componentCount,
                type: stream.dataType,
                normalize: stream.dataTypeNormalize,
                asInt: stream.asInt
            });
        }
        return new VertexFormat(this.device, vertexDesc, vertexCount);
    }
    // copy attached data into vertex buffer
    _updateVertexBuffer() {
        // if we don't have vertex buffer, create new one, otherwise update existing one
        if (!this.vertexBuffer) {
            const allocateVertexCount = this._geometryData.maxVertices;
            const format = this._buildVertexFormat(allocateVertexCount);
            this.vertexBuffer = new VertexBuffer(this.device, format, allocateVertexCount, {
                usage: this._geometryData.verticesUsage,
                storage: this._storageVertex
            });
        }
        // lock vertex buffer and create typed access arrays for individual elements
        const iterator = new VertexIterator(this.vertexBuffer);
        // copy all stream data into vertex buffer
        const numVertices = this._geometryData.vertexCount;
        for(const semantic in this._geometryData.vertexStreamDictionary){
            const stream = this._geometryData.vertexStreamDictionary[semantic];
            iterator.writeData(semantic, stream.data, numVertices);
            // remove stream
            delete this._geometryData.vertexStreamDictionary[semantic];
        }
        iterator.end();
    }
    // copy attached data into index buffer
    _updateIndexBuffer() {
        // if we don't have index buffer, create new one, otherwise update existing one
        if (this.indexBuffer.length <= 0 || !this.indexBuffer[0]) {
            const maxVertices = this._geometryData.maxVertices;
            const createFormat = maxVertices > 0xffff || maxVertices === 0 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16;
            const options = this._storageIndex ? {
                storage: true
            } : undefined;
            this.indexBuffer[0] = new IndexBuffer(this.device, createFormat, this._geometryData.maxIndices, this._geometryData.indicesUsage, undefined, options);
        }
        const srcIndices = this._geometryData.indices;
        if (srcIndices) {
            const indexBuffer = this.indexBuffer[0];
            indexBuffer.writeData(srcIndices, this._geometryData.indexCount);
            // remove data
            this._geometryData.indices = null;
        }
    }
    // prepares the mesh to be rendered with specific render style
    prepareRenderState(renderStyle) {
        if (renderStyle === RENDERSTYLE_WIREFRAME) {
            this.generateWireframe();
        } else if (renderStyle === RENDERSTYLE_POINTS) {
            this.primitive[RENDERSTYLE_POINTS] = {
                type: PRIMITIVE_POINTS,
                base: 0,
                baseVertex: 0,
                count: this.vertexBuffer ? this.vertexBuffer.numVertices : 0,
                indexed: false
            };
        }
    }
    // updates existing render states with changes to solid render state
    updateRenderStates() {
        if (this.primitive[RENDERSTYLE_POINTS]) {
            this.prepareRenderState(RENDERSTYLE_POINTS);
        }
        if (this.primitive[RENDERSTYLE_WIREFRAME]) {
            this.prepareRenderState(RENDERSTYLE_WIREFRAME);
        }
    }
    generateWireframe() {
        // release existing IB
        this._destroyIndexBuffer(RENDERSTYLE_WIREFRAME);
        const numVertices = this.vertexBuffer.numVertices;
        const lines = [];
        let format;
        if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
            const offsets = [
                [
                    0,
                    1
                ],
                [
                    1,
                    2
                ],
                [
                    2,
                    0
                ]
            ];
            const base = this.primitive[RENDERSTYLE_SOLID].base;
            const count = this.primitive[RENDERSTYLE_SOLID].count;
            const baseVertex = this.primitive[RENDERSTYLE_SOLID].baseVertex || 0;
            const indexBuffer = this.indexBuffer[RENDERSTYLE_SOLID];
            const srcIndices = new typedArrayIndexFormats[indexBuffer.format](indexBuffer.storage);
            const seen = new Set();
            for(let j = base; j < base + count; j += 3){
                for(let k = 0; k < 3; k++){
                    const i1 = srcIndices[j + offsets[k][0]] + baseVertex;
                    const i2 = srcIndices[j + offsets[k][1]] + baseVertex;
                    const hash = i1 > i2 ? i2 * numVertices + i1 : i1 * numVertices + i2;
                    if (!seen.has(hash)) {
                        seen.add(hash);
                        lines.push(i1, i2);
                    }
                }
            }
            format = indexBuffer.format;
        } else {
            for(let i = 0; i < numVertices; i += 3){
                lines.push(i, i + 1, i + 1, i + 2, i + 2, i);
            }
            format = lines.length > 65535 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16;
        }
        const wireBuffer = new IndexBuffer(this.vertexBuffer.device, format, lines.length);
        const dstIndices = new typedArrayIndexFormats[wireBuffer.format](wireBuffer.storage);
        dstIndices.set(lines);
        wireBuffer.unlock();
        this.primitive[RENDERSTYLE_WIREFRAME] = {
            type: PRIMITIVE_LINES,
            base: 0,
            baseVertex: 0,
            count: lines.length,
            indexed: true
        };
        this.indexBuffer[RENDERSTYLE_WIREFRAME] = wireBuffer;
    }
}

const scaleCompensatePosTransform = new Mat4();
const scaleCompensatePos = new Vec3();
const scaleCompensateRot = new Quat();
const scaleCompensateRot2 = new Quat();
const scaleCompensateScale = new Vec3();
const scaleCompensateScaleForParent = new Vec3();
const tmpMat4 = new Mat4();
const tmpQuat = new Quat();
const position$1 = new Vec3();
const invParentWtm = new Mat4();
const rotation = new Quat();
const invParentRot = new Quat();
const matrix = new Mat4();
const target = new Vec3();
const up = new Vec3();
/**
 * Helper function that handles signature overloading to receive a test function.
 *
 * @param {FindNodeCallback|string} attr - Attribute or lambda.
 * @param {*} [value] - Optional value in case of `attr` being a `string`
 * @returns {FindNodeCallback} Test function that receives a GraphNode and returns a boolean.
 */ function createTest(attr, value) {
    if (attr instanceof Function) {
        return attr;
    }
    return (node)=>{
        let x = node[attr];
        if (x instanceof Function) {
            x = x();
        }
        return x === value;
    };
}
/**
 * Helper function to recurse findOne without calling createTest constantly.
 *
 * @param {GraphNode} node - Current node.
 * @param {FindNodeCallback} test - Test function.
 * @returns {GraphNode|null} A graph node that matches the search criteria. Returns null if no
 * node is found.
 */ function findNode(node, test) {
    if (test(node)) {
        return node;
    }
    const children = node._children;
    const len = children.length;
    for(let i = 0; i < len; ++i){
        const result = findNode(children[i], test);
        if (result) {
            return result;
        }
    }
    return null;
}
/**
 * @callback FindNodeCallback
 * Callback used by {@link GraphNode#find} and {@link GraphNode#findOne} to search through a graph
 * node and all of its descendants.
 * @param {GraphNode} node - The current graph node.
 * @returns {boolean} Returning `true` will result in that node being returned from
 * {@link GraphNode#find} or {@link GraphNode#findOne}.
 */ /**
 * @callback ForEachNodeCallback
 * Callback used by {@link GraphNode#forEach} to iterate through a graph node and all of its
 * descendants.
 * @param {GraphNode} node - The current graph node.
 * @returns {void}
 */ /**
 * The GraphNode class represents a node within a hierarchical scene graph. Each GraphNode can
 * reference an array of {@link children}. This creates a tree-like structure that is fundamental
 * for organizing and managing the spatial relationships between objects in a 3D scene. This class
 * provides a comprehensive API for manipulating the position, rotation, and scale of nodes both
 * locally (relative to the {@link parent}) and in world space (relative to the {@link Scene}
 * origin).
 *
 * During the application's (see {@link AppBase}) main update loop, the engine automatically
 * synchronizes the entire GraphNode hierarchy each frame. This process ensures that the world
 * transformation matrices for all nodes are up-to-date. A node's world transformation matrix is
 * calculated by combining its local transformation matrix (derived from its local position,
 * rotation, and scale) with the world transformation matrix of its parent node. For the scene
 * graph's {@link root} node (which has no parent), its world matrix is simply its local matrix.
 * This hierarchical update mechanism ensures that changes made to a parent node's transform
 * correctly propagate down to all its children and descendants, accurately reflecting their final
 * position, orientation, and scale in the world. This synchronized world transform is essential
 * for systems like rendering and physics.
 *
 * GraphNode is the superclass of {@link Entity}, which is the primary class for creating objects
 * in a PlayCanvas application. For this reason, developers typically interact with the scene
 * hierarchy and transformations through the Entity interface rather than using GraphNode directly.
 * However, GraphNode provides the underlying powerful set of features for hierarchical
 * transformations that Entity leverages.
 */ class GraphNode extends EventHandler {
    /**
     * Create a new GraphNode instance.
     *
     * @param {string} [name] - The non-unique name of a graph node. Defaults to 'Untitled'.
     */ constructor(name = 'Untitled'){
        super(), /**
     * Interface for tagging graph nodes. Tag based searches can be performed using the
     * {@link findByTag} function.
     *
     * @type {Tags}
     */ this.tags = new Tags(this), // Local space properties of transform (only first 3 are settable by the user)
        /**
     * @type {Vec3}
     * @private
     */ this.localPosition = new Vec3(), /**
     * @type {Quat}
     * @private
     */ this.localRotation = new Quat(), /**
     * @type {Vec3}
     * @private
     */ this.localScale = new Vec3(1, 1, 1), /**
     * @type {Vec3}
     * @private
     */ this.localEulerAngles = new Vec3() // Only calculated on request
        , // World space properties of transform
        /**
     * @type {Vec3}
     * @private
     */ this.position = new Vec3(), /**
     * @type {Quat}
     * @private
     */ this.rotation = new Quat(), /**
     * @type {Vec3}
     * @private
     */ this.eulerAngles = new Vec3(), /**
     * @type {Vec3|null}
     * @private
     */ this._scale = null, /**
     * @type {Mat4}
     * @private
     */ this.localTransform = new Mat4(), /**
     * @type {boolean}
     * @private
     */ this._dirtyLocal = false, /**
     * @type {number}
     * @private
     */ this._aabbVer = 0, /**
     * Marks the node to ignore hierarchy sync entirely (including children nodes). The engine code
     * automatically freezes and unfreezes objects whenever required. Segregating dynamic and
     * stationary nodes into subhierarchies allows to reduce sync time significantly.
     *
     * @type {boolean}
     * @private
     */ this._frozen = false, /**
     * @type {Mat4}
     * @private
     */ this.worldTransform = new Mat4(), /**
     * @type {boolean}
     * @private
     */ this._dirtyWorld = false, /**
     * Cached value representing the negatively scaled world transform. If the value is 0, this
     * marks this value as dirty and it needs to be recalculated. If the value is 1, the world
     * transform is not negatively scaled. If the value is -1, the world transform is negatively
     * scaled.
     *
     * @type {number}
     * @private
     */ this._worldScaleSign = 0, /**
     * @type {Mat3}
     * @private
     */ this._normalMatrix = new Mat3(), /**
     * @type {boolean}
     * @private
     */ this._dirtyNormal = true, /**
     * @type {Vec3|null}
     * @private
     */ this._right = null, /**
     * @type {Vec3|null}
     * @private
     */ this._up = null, /**
     * @type {Vec3|null}
     * @private
     */ this._forward = null, /**
     * @type {GraphNode|null}
     * @private
     */ this._parent = null, /**
     * @type {GraphNode[]}
     * @protected
     */ this._children = [], /**
     * @type {number}
     * @private
     */ this._graphDepth = 0, /**
     * Represents enabled state of the entity. If the entity is disabled, the entity including all
     * children are excluded from updates.
     *
     * @type {boolean}
     * @private
     */ this._enabled = true, /**
     * Represents enabled state of the entity in the hierarchy. It's true only if this entity and
     * all parent entities all the way to the scene's root are enabled.
     *
     * @type {boolean}
     * @private
     */ this._enabledInHierarchy = false, /**
     * @type {boolean}
     * @ignore
     */ this.scaleCompensation = false;
        this.name = name;
    }
    /**
     * Gets the normalized local space X-axis vector of the graph node in world space.
     *
     * @type {Vec3}
     */ get right() {
        if (!this._right) {
            this._right = new Vec3();
        }
        return this.getWorldTransform().getX(this._right).normalize();
    }
    /**
     * Gets the normalized local space Y-axis vector of the graph node in world space.
     *
     * @type {Vec3}
     */ get up() {
        if (!this._up) {
            this._up = new Vec3();
        }
        return this.getWorldTransform().getY(this._up).normalize();
    }
    /**
     * Gets the normalized local space negative Z-axis vector of the graph node in world space.
     *
     * @type {Vec3}
     */ get forward() {
        if (!this._forward) {
            this._forward = new Vec3();
        }
        return this.getWorldTransform().getZ(this._forward).normalize().mulScalar(-1);
    }
    /**
     * Gets the 3x3 transformation matrix used to transform normals.
     *
     * @type {Mat3}
     * @ignore
     */ get normalMatrix() {
        const normalMat = this._normalMatrix;
        if (this._dirtyNormal) {
            normalMat.invertMat4(this.getWorldTransform()).transpose();
            this._dirtyNormal = false;
        }
        return normalMat;
    }
    /**
     * Sets the enabled state of the GraphNode. If one of the GraphNode's parents is disabled there
     * will be no other side effects. If all the parents are enabled then the new value will
     * activate or deactivate all the enabled children of the GraphNode.
     *
     * @type {boolean}
     */ set enabled(enabled) {
        if (this._enabled !== enabled) {
            this._enabled = enabled;
            // if enabling entity, make all children enabled in hierarchy only when the parent is as well
            // if disabling entity, make all children disabled in hierarchy in all cases
            if (enabled && this._parent?.enabled || !enabled) {
                this._notifyHierarchyStateChanged(this, enabled);
            }
        }
    }
    /**
     * Gets the enabled state of the GraphNode.
     *
     * @type {boolean}
     */ get enabled() {
        // make sure to check this._enabled too because if that
        // was false when a parent was updated the _enabledInHierarchy
        // flag may not have been updated for optimization purposes
        return this._enabled && this._enabledInHierarchy;
    }
    /**
     * Gets the parent of this graph node.
     *
     * @type {GraphNode|null}
     */ get parent() {
        return this._parent;
    }
    /**
     * Gets the path of this graph node relative to the root of the hierarchy.
     *
     * @type {string}
     */ get path() {
        let node = this._parent;
        if (!node) {
            return '';
        }
        let result = this.name;
        while(node && node._parent){
            result = `${node.name}/${result}`;
            node = node._parent;
        }
        return result;
    }
    /**
     * Gets the oldest ancestor graph node from this graph node.
     *
     * @type {GraphNode}
     */ get root() {
        let result = this;
        while(result._parent){
            result = result._parent;
        }
        return result;
    }
    /**
     * Gets the children of this graph node.
     *
     * @type {GraphNode[]}
     */ get children() {
        return this._children;
    }
    /**
     * Gets the depth of this child within the graph. Note that for performance reasons this is
     * only recalculated when a node is added to a new parent. In other words, it is not
     * recalculated when a node is simply removed from the graph.
     *
     * @type {number}
     */ get graphDepth() {
        return this._graphDepth;
    }
    /**
     * @param {GraphNode} node - Graph node to update.
     * @param {boolean} enabled - True if enabled in the hierarchy, false if disabled.
     * @protected
     */ _notifyHierarchyStateChanged(node, enabled) {
        node._onHierarchyStateChanged(enabled);
        const c = node._children;
        for(let i = 0, len = c.length; i < len; i++){
            if (c[i]._enabled) {
                this._notifyHierarchyStateChanged(c[i], enabled);
            }
        }
    }
    /**
     * Called when the enabled flag of the entity or one of its parents changes.
     *
     * @param {boolean} enabled - True if enabled in the hierarchy, false if disabled.
     * @protected
     */ _onHierarchyStateChanged(enabled) {
        // Override in derived classes
        this._enabledInHierarchy = enabled;
        if (enabled && !this._frozen) {
            this._unfreezeParentToRoot();
        }
    }
    /**
     * @param {this} clone - The cloned graph node to copy into.
     * @private
     */ _cloneInternal(clone) {
        clone.name = this.name;
        const tags = this.tags._list;
        clone.tags.clear();
        for(let i = 0; i < tags.length; i++){
            clone.tags.add(tags[i]);
        }
        clone.localPosition.copy(this.localPosition);
        clone.localRotation.copy(this.localRotation);
        clone.localScale.copy(this.localScale);
        clone.localEulerAngles.copy(this.localEulerAngles);
        clone.position.copy(this.position);
        clone.rotation.copy(this.rotation);
        clone.eulerAngles.copy(this.eulerAngles);
        clone.localTransform.copy(this.localTransform);
        clone._dirtyLocal = this._dirtyLocal;
        clone.worldTransform.copy(this.worldTransform);
        clone._dirtyWorld = this._dirtyWorld;
        clone._dirtyNormal = this._dirtyNormal;
        clone._aabbVer = this._aabbVer + 1;
        clone._enabled = this._enabled;
        clone.scaleCompensation = this.scaleCompensation;
        // false as this node is not in the hierarchy yet
        clone._enabledInHierarchy = false;
    }
    /**
     * Clone a graph node.
     *
     * @returns {this} A clone of the specified graph node.
     */ clone() {
        const clone = new this.constructor();
        this._cloneInternal(clone);
        return clone;
    }
    /**
     * Copy a graph node.
     *
     * @param {GraphNode} source - The graph node to copy.
     * @returns {GraphNode} The destination graph node.
     * @ignore
     */ copy(source) {
        source._cloneInternal(this);
        return this;
    }
    /**
     * Destroy the graph node and all of its descendants. First, the graph node is removed from the
     * hierarchy. This is then repeated recursively for all descendants of the graph node.
     *
     * The last thing the graph node does is fire the `destroy` event.
     *
     * @example
     * const firstChild = graphNode.children[0];
     * firstChild.destroy(); // destroy child and all of its descendants
     */ destroy() {
        // Detach from parent
        this.remove();
        // Recursively destroy all children
        const children = this._children;
        while(children.length){
            // Remove last child from the array
            const child = children.pop();
            // Disconnect it from the parent: this is only an optimization step, to prevent calling
            // GraphNode#removeChild which would try to refind it via this._children.indexOf (which
            // will fail, because we just removed it).
            child._parent = null;
            child.destroy();
        }
        // fire destroy event
        this.fire('destroy', this);
        // clear all events
        this.off();
    }
    /**
     * Search the graph node and all of its descendants for the nodes that satisfy some search
     * criteria.
     *
     * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a
     * function, it is executed for each descendant node to test if node satisfies the search
     * logic. Returning true from the function will include the node into the results. If it's a
     * string then it represents the name of a field or a method of the node. If this is the name
     * of a field then the value passed as the second argument will be checked for equality. If
     * this is the name of a function then the return value of the function will be checked for
     * equality against the valued passed as the second argument to this function.
     * @param {*} [value] - If the first argument (attr) is a property name then this value
     * will be checked against the value of the property.
     * @returns {GraphNode[]} The array of graph nodes that match the search criteria.
     * @example
     * // Finds all nodes that have a model component and have 'door' in their lower-cased name
     * const doors = house.find((node) => {
     *     return node.model && node.name.toLowerCase().indexOf('door') !== -1;
     * });
     * @example
     * // Finds all nodes that have the name property set to 'Test'
     * const entities = parent.find('name', 'Test');
     */ find(attr, value) {
        const results = [];
        const test = createTest(attr, value);
        this.forEach((node)=>{
            if (test(node)) {
                results.push(node);
            }
        });
        return results;
    }
    /**
     * Search the graph node and all of its descendants for the first node that satisfies some
     * search criteria.
     *
     * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a
     * function, it is executed for each descendant node to test if node satisfies the search
     * logic. Returning true from the function will result in that node being returned from
     * findOne. If it's a string then it represents the name of a field or a method of the node. If
     * this is the name of a field then the value passed as the second argument will be checked for
     * equality. If this is the name of a function then the return value of the function will be
     * checked for equality against the valued passed as the second argument to this function.
     * @param {*} [value] - If the first argument (attr) is a property name then this value
     * will be checked against the value of the property.
     * @returns {GraphNode|null} A graph node that match the search criteria. Returns null if no
     * node is found.
     * @example
     * // Find the first node that is called 'head' and has a model component
     * const head = player.findOne((node) => {
     *     return node.model && node.name === 'head';
     * });
     * @example
     * // Finds the first node that has the name property set to 'Test'
     * const node = parent.findOne('name', 'Test');
     */ findOne(attr, value) {
        const test = createTest(attr, value);
        return findNode(this, test);
    }
    /**
     * Return all graph nodes that satisfy the search query. Query can be simply a string, or comma
     * separated strings, to have inclusive results of assets that match at least one query. A
     * query that consists of an array of tags can be used to match graph nodes that have each tag
     * of array.
     *
     * @param {...*} query - Name of a tag or array of tags.
     * @returns {GraphNode[]} A list of all graph nodes that match the query.
     * @example
     * // Return all graph nodes that tagged by `animal`
     * const animals = node.findByTag("animal");
     * @example
     * // Return all graph nodes that tagged by `bird` OR `mammal`
     * const birdsAndMammals = node.findByTag("bird", "mammal");
     * @example
     * // Return all assets that tagged by `carnivore` AND `mammal`
     * const meatEatingMammals = node.findByTag(["carnivore", "mammal"]);
     * @example
     * // Return all assets that tagged by (`carnivore` AND `mammal`) OR (`carnivore` AND `reptile`)
     * const meatEatingMammalsAndReptiles = node.findByTag(["carnivore", "mammal"], ["carnivore", "reptile"]);
     */ findByTag(...query) {
        const results = [];
        const queryNode = (node, checkNode)=>{
            if (checkNode && node.tags.has(...query)) {
                results.push(node);
            }
            for(let i = 0; i < node._children.length; i++){
                queryNode(node._children[i], true);
            }
        };
        queryNode(this, false);
        return results;
    }
    /**
     * Get the first node found in the graph with the name. The search is depth first.
     *
     * @param {string} name - The name of the graph.
     * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns
     * null if no node is found.
     */ findByName(name) {
        return this.findOne('name', name);
    }
    /**
     * Get the first node found in the graph by its full path in the graph. The full path has this
     * form 'parent/child/sub-child'. The search is depth first.
     *
     * @param {string|string[]} path - The full path of the GraphNode as either a string or array
     * of GraphNode names.
     * @returns {GraphNode|null} The first node to be found matching the supplied path. Returns
     * null if no node is found.
     * @example
     * // String form
     * const grandchild = this.entity.findByPath('child/grandchild');
     * @example
     * // Array form
     * const grandchild = this.entity.findByPath(['child', 'grandchild']);
     */ findByPath(path) {
        // accept either string path with '/' separators or array of parts.
        const parts = Array.isArray(path) ? path : path.split('/');
        let result = this;
        for(let i = 0, imax = parts.length; i < imax; ++i){
            result = result.children.find((c)=>c.name === parts[i]);
            if (!result) {
                return null;
            }
        }
        return result;
    }
    /**
     * Executes a provided function once on this graph node and all of its descendants.
     *
     * @param {ForEachNodeCallback} callback - The function to execute on the graph node and each
     * descendant.
     * @param {object} [thisArg] - Optional value to use as this when executing callback function.
     * @example
     * // Log the path and name of each node in descendant tree starting with "parent"
     * parent.forEach((node) => {
     *     console.log(node.path + "/" + node.name);
     * });
     */ forEach(callback, thisArg) {
        callback.call(thisArg, this);
        const children = this._children;
        const len = children.length;
        for(let i = 0; i < len; ++i){
            children[i].forEach(callback, thisArg);
        }
    }
    /**
     * Check if node is descendant of another node.
     *
     * @param {GraphNode} node - Potential ancestor of node.
     * @returns {boolean} If node is descendant of another node.
     * @example
     * if (roof.isDescendantOf(house)) {
     *     // roof is descendant of house entity
     * }
     */ isDescendantOf(node) {
        let parent = this._parent;
        while(parent){
            if (parent === node) {
                return true;
            }
            parent = parent._parent;
        }
        return false;
    }
    /**
     * Check if node is ancestor for another node.
     *
     * @param {GraphNode} node - Potential descendant of node.
     * @returns {boolean} If node is ancestor for another node.
     * @example
     * if (body.isAncestorOf(foot)) {
     *     // foot is within body's hierarchy
     * }
     */ isAncestorOf(node) {
        return node.isDescendantOf(this);
    }
    /**
     * Get the world space rotation for the specified GraphNode in Euler angles. The angles are in
     * degrees and in XYZ order.
     *
     * Important: The value returned by this function should be considered read-only. In order to
     * set the world space rotation of the graph node, use {@link setEulerAngles}.
     *
     * @returns {Vec3} The world space rotation of the graph node in Euler angle form.
     * @example
     * const angles = this.entity.getEulerAngles();
     * angles.y = 180; // rotate the entity around Y by 180 degrees
     * this.entity.setEulerAngles(angles);
     */ getEulerAngles() {
        this.getWorldTransform().getEulerAngles(this.eulerAngles);
        return this.eulerAngles;
    }
    /**
     * Get the local space rotation for the specified GraphNode in Euler angles. The angles are in
     * degrees and in XYZ order.
     *
     * Important: The value returned by this function should be considered read-only. In order to
     * set the local space rotation of the graph node, use {@link setLocalEulerAngles}.
     *
     * @returns {Vec3} The local space rotation of the graph node as Euler angles in XYZ order.
     * @example
     * const angles = this.entity.getLocalEulerAngles();
     * angles.y = 180;
     * this.entity.setLocalEulerAngles(angles);
     */ getLocalEulerAngles() {
        this.localRotation.getEulerAngles(this.localEulerAngles);
        return this.localEulerAngles;
    }
    /**
     * Get the position in local space for the specified GraphNode. The position is returned as a
     * {@link Vec3}. The returned vector should be considered read-only. To update the local
     * position, use {@link setLocalPosition}.
     *
     * @returns {Vec3} The local space position of the graph node.
     * @example
     * const position = this.entity.getLocalPosition();
     * position.x += 1; // move the entity 1 unit along x.
     * this.entity.setLocalPosition(position);
     */ getLocalPosition() {
        return this.localPosition;
    }
    /**
     * Get the rotation in local space for the specified GraphNode. The rotation is returned as a
     * {@link Quat}. The returned quaternion should be considered read-only. To update the local
     * rotation, use {@link setLocalRotation}.
     *
     * @returns {Quat} The local space rotation of the graph node as a quaternion.
     * @example
     * const rotation = this.entity.getLocalRotation();
     */ getLocalRotation() {
        return this.localRotation;
    }
    /**
     * Get the scale in local space for the specified GraphNode. The scale is returned as a
     * {@link Vec3}. The returned vector should be considered read-only. To update the local scale,
     * use {@link setLocalScale}.
     *
     * @returns {Vec3} The local space scale of the graph node.
     * @example
     * const scale = this.entity.getLocalScale();
     * scale.x = 100;
     * this.entity.setLocalScale(scale);
     */ getLocalScale() {
        return this.localScale;
    }
    /**
     * Get the local transform matrix for this graph node. This matrix is the transform relative to
     * the node's parent's world transformation matrix.
     *
     * @returns {Mat4} The node's local transformation matrix.
     * @example
     * const transform = this.entity.getLocalTransform();
     */ getLocalTransform() {
        if (this._dirtyLocal) {
            this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);
            this._dirtyLocal = false;
        }
        return this.localTransform;
    }
    /**
     * Get the world space position for the specified GraphNode. The position is returned as a
     * {@link Vec3}. The value returned by this function should be considered read-only. In order
     * to set the world space position of the graph node, use {@link setPosition}.
     *
     * @returns {Vec3} The world space position of the graph node.
     * @example
     * const position = this.entity.getPosition();
     * position.x = 10;
     * this.entity.setPosition(position);
     */ getPosition() {
        this.getWorldTransform().getTranslation(this.position);
        return this.position;
    }
    /**
     * Get the world space rotation for the specified GraphNode. The rotation is returned as a
     * {@link Quat}. The value returned by this function should be considered read-only. In order
     * to set the world space rotation of the graph node, use {@link setRotation}.
     *
     * @returns {Quat} The world space rotation of the graph node as a quaternion.
     * @example
     * const rotation = this.entity.getRotation();
     */ getRotation() {
        this.rotation.setFromMat4(this.getWorldTransform());
        return this.rotation;
    }
    /**
     * Get the world space scale for the specified GraphNode. The returned value will only be
     * correct for graph nodes that have a non-skewed world transform (a skew can be introduced by
     * the compounding of rotations and scales higher in the graph node hierarchy). The scale is
     * returned as a {@link Vec3}. The value returned by this function should be considered
     * read-only. Note that it is not possible to set the world space scale of a graph node
     * directly.
     *
     * @returns {Vec3} The world space scale of the graph node.
     * @example
     * const scale = this.entity.getScale();
     * @ignore
     */ getScale() {
        if (!this._scale) {
            this._scale = new Vec3();
        }
        return this.getWorldTransform().getScale(this._scale);
    }
    /**
     * Get the world transformation matrix for this graph node.
     *
     * @returns {Mat4} The node's world transformation matrix.
     * @example
     * const transform = this.entity.getWorldTransform();
     */ getWorldTransform() {
        if (!this._dirtyLocal && !this._dirtyWorld) {
            return this.worldTransform;
        }
        if (this._parent) {
            this._parent.getWorldTransform();
        }
        this._sync();
        return this.worldTransform;
    }
    /**
     * Gets the cached value of negative scale sign of the world transform.
     *
     * @returns {number} -1 if world transform has negative scale, 1 otherwise.
     * @ignore
     */ get worldScaleSign() {
        if (this._worldScaleSign === 0) {
            this._worldScaleSign = this.getWorldTransform().scaleSign;
        }
        return this._worldScaleSign;
    }
    /**
     * Remove graph node from current parent.
     */ remove() {
        this._parent?.removeChild(this);
    }
    /**
     * Remove graph node from current parent and add as child to new parent.
     *
     * @param {GraphNode} parent - New parent to attach graph node to.
     * @param {number} [index] - The child index where the child node should be placed.
     */ reparent(parent, index) {
        this.remove();
        if (parent) {
            if (index >= 0) {
                parent.insertChild(this, index);
            } else {
                parent.addChild(this);
            }
        }
    }
    /**
     * Sets the local space rotation of the specified graph node using Euler angles. Eulers are
     * interpreted in XYZ order.
     *
     * @overload
     * @param {number} x - Rotation around local space x-axis in degrees.
     * @param {number} y - Rotation around local space y-axis in degrees.
     * @param {number} z - Rotation around local space z-axis in degrees.
     * @returns {void}
     * @example
     * // Set rotation of 90 degrees around y-axis via 3 numbers
     * this.entity.setLocalEulerAngles(0, 90, 0);
     */ /**
     * Sets the local space rotation of the specified graph node using Euler angles. Eulers are
     * interpreted in XYZ order.
     *
     * @overload
     * @param {Vec3} angles - Vector holding rotations around local space axes in degrees.
     * @returns {void}
     * @example
     * // Set rotation of 90 degrees around y-axis via a vector
     * const angles = new pc.Vec3(0, 90, 0);
     * this.entity.setLocalEulerAngles(angles);
     */ /**
     * @param {number|Vec3} x - Rotation around local space x-axis in degrees or vector holding rotations around local space axes in degrees.
     * @param {number} [y] - Rotation around local space y-axis in degrees.
     * @param {number} [z] - Rotation around local space z-axis in degrees.
     */ setLocalEulerAngles(x, y, z) {
        this.localRotation.setFromEulerAngles(x, y, z);
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Sets the local space position of the specified graph node.
     *
     * @overload
     * @param {number} x - X-coordinate of local space position.
     * @param {number} y - Y-coordinate of local space position.
     * @param {number} z - Z-coordinate of local space position.
     * @returns {void}
     * @example
     * this.entity.setLocalPosition(0, 10, 0);
     */ /**
     * Sets the local space position of the specified graph node.
     *
     * @overload
     * @param {Vec3} position - Vector holding local space position.
     * @returns {void}
     * @example
     * const pos = new pc.Vec3(0, 10, 0);
     * this.entity.setLocalPosition(pos);
     */ /**
     * @param {number|Vec3} x - X-coordinate of local space position or vector holding local space position.
     * @param {number} [y] - Y-coordinate of local space position.
     * @param {number} [z] - Z-coordinate of local space position.
     */ setLocalPosition(x, y, z) {
        if (x instanceof Vec3) {
            this.localPosition.copy(x);
        } else {
            this.localPosition.set(x, y, z);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Sets the local space rotation of the specified graph node.
     *
     * @overload
     * @param {number} x - X-component of local space quaternion rotation.
     * @param {number} y - Y-component of local space quaternion rotation.
     * @param {number} z - Z-component of local space quaternion rotation.
     * @param {number} w - W-component of local space quaternion rotation.
     * @returns {void}
     * @example
     * this.entity.setLocalRotation(0, 0, 0, 1);
     */ /**
     * Sets the local space rotation of the specified graph node.
     *
     * @overload
     * @param {Quat} rotation - Quaternion holding local space rotation.
     * @returns {void}
     * @example
     * const q = new pc.Quat();
     * this.entity.setLocalRotation(q);
     */ /**
     * @param {number|Quat} x - X-component of local space quaternion rotation or quaternion holding local space rotation.
     * @param {number} [y] - Y-component of local space quaternion rotation.
     * @param {number} [z] - Z-component of local space quaternion rotation.
     * @param {number} [w] - W-component of local space quaternion rotation.
     */ setLocalRotation(x, y, z, w) {
        if (x instanceof Quat) {
            this.localRotation.copy(x);
        } else {
            this.localRotation.set(x, y, z, w);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Sets the local space scale factor of the specified graph node.
     *
     * @overload
     * @param {number} x - X-coordinate of local space scale.
     * @param {number} y - Y-coordinate of local space scale.
     * @param {number} z - Z-coordinate of local space scale.
     * @returns {void}
     * @example
     * this.entity.setLocalScale(10, 10, 10);
     */ /**
     * Sets the local space scale factor of the specified graph node.
     *
     * @overload
     * @param {Vec3} scale - Vector holding local space scale.
     * @returns {void}
     * @example
     * const scale = new pc.Vec3(10, 10, 10);
     * this.entity.setLocalScale(scale);
     */ /**
     * @param {number|Vec3} x - X-coordinate of local space scale or vector holding local space scale.
     * @param {number} [y] - Y-coordinate of local space scale.
     * @param {number} [z] - Z-coordinate of local space scale.
     */ setLocalScale(x, y, z) {
        if (x instanceof Vec3) {
            this.localScale.copy(x);
        } else {
            this.localScale.set(x, y, z);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /** @private */ _dirtifyLocal() {
        if (!this._dirtyLocal) {
            this._dirtyLocal = true;
            if (!this._dirtyWorld) {
                this._dirtifyWorld();
            }
        }
    }
    /** @private */ _unfreezeParentToRoot() {
        let p = this._parent;
        while(p){
            p._frozen = false;
            p = p._parent;
        }
    }
    /** @private */ _dirtifyWorld() {
        if (!this._dirtyWorld) {
            this._unfreezeParentToRoot();
        }
        this._dirtifyWorldInternal();
    }
    /** @private */ _dirtifyWorldInternal() {
        if (!this._dirtyWorld) {
            this._frozen = false;
            this._dirtyWorld = true;
            for(let i = 0; i < this._children.length; i++){
                if (!this._children[i]._dirtyWorld) {
                    this._children[i]._dirtifyWorldInternal();
                }
            }
        }
        this._dirtyNormal = true;
        this._worldScaleSign = 0; // world matrix is dirty, mark this flag dirty too
        this._aabbVer++;
    }
    /**
     * Sets the world space position of the specified graph node.
     *
     * @overload
     * @param {number} x - X-coordinate of world space position.
     * @param {number} y - Y-coordinate of world space position.
     * @param {number} z - Z-coordinate of world space position.
     * @returns {void}
     * @example
     * this.entity.setPosition(0, 10, 0);
     */ /**
     * Sets the world space position of the specified graph node.
     *
     * @overload
     * @param {Vec3} position - Vector holding world space position.
     * @returns {void}
     * @example
     * const position = new pc.Vec3(0, 10, 0);
     * this.entity.setPosition(position);
     */ /**
     * @param {number|Vec3} x - X-coordinate of world space position or vector holding world space position.
     * @param {number} [y] - Y-coordinate of world space position.
     * @param {number} [z] - Z-coordinate of world space position.
     */ setPosition(x, y, z) {
        if (x instanceof Vec3) {
            position$1.copy(x);
        } else {
            position$1.set(x, y, z);
        }
        if (this._parent === null) {
            this.localPosition.copy(position$1);
        } else {
            invParentWtm.copy(this._parent.getWorldTransform()).invert();
            invParentWtm.transformPoint(position$1, this.localPosition);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Sets the world space rotation of the specified graph node.
     *
     * @overload
     * @param {number} x - X-component of world space quaternion rotation.
     * @param {number} y - Y-component of world space quaternion rotation.
     * @param {number} z - Z-component of world space quaternion rotation.
     * @param {number} w - W-component of world space quaternion rotation.
     * @returns {void}
     * @example
     * this.entity.setRotation(0, 0, 0, 1);
     */ /**
     * Sets the world space rotation of the specified graph node.
     *
     * @overload
     * @param {Quat} rotation - Quaternion holding world space rotation.
     * @returns {void}
     * @example
     * const rotation = new pc.Quat();
     * this.entity.setRotation(rotation);
     */ /**
     * @param {number|Quat} x - X-component of world space quaternion rotation or quaternion holding world space rotation.
     * @param {number} [y] - Y-component of world space quaternion rotation.
     * @param {number} [z] - Z-component of world space quaternion rotation.
     * @param {number} [w] - W-component of world space quaternion rotation.
     */ setRotation(x, y, z, w) {
        if (x instanceof Quat) {
            rotation.copy(x);
        } else {
            rotation.set(x, y, z, w);
        }
        if (this._parent === null) {
            this.localRotation.copy(rotation);
        } else {
            const parentRot = this._parent.getRotation();
            invParentRot.copy(parentRot).invert();
            this.localRotation.copy(invParentRot).mul(rotation);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Sets the world space position and rotation of the specified graph node. This is faster than
     * setting the position and rotation independently.
     *
     * @param {Vec3} position - The world space position to set.
     * @param {Quat} rotation - The world space rotation to set.
     * @example
     * const position = new pc.Vec3(0, 10, 0);
     * const rotation = new pc.Quat().setFromEulerAngles(0, 90, 0);
     * this.entity.setPositionAndRotation(position, rotation);
     */ setPositionAndRotation(position, rotation) {
        if (this._parent === null) {
            this.localPosition.copy(position);
            this.localRotation.copy(rotation);
        } else {
            const parentWtm = this._parent.getWorldTransform();
            invParentWtm.copy(parentWtm).invert();
            invParentWtm.transformPoint(position, this.localPosition);
            this.localRotation.setFromMat4(invParentWtm).mul(rotation);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Sets the world space rotation of the specified graph node using Euler angles. Eulers are
     * interpreted in XYZ order.
     *
     * @overload
     * @param {number} x - Rotation around world space x-axis in degrees.
     * @param {number} y - Rotation around world space y-axis in degrees.
     * @param {number} z - Rotation around world space z-axis in degrees.
     * @returns {void}
     * @example
     * this.entity.setEulerAngles(0, 90, 0);
     */ /**
     * Sets the world space rotation of the specified graph node using Euler angles. Eulers are
     * interpreted in XYZ order.
     *
     * @overload
     * @param {Vec3} angles - Vector holding rotations around world space axes in degrees.
     * @returns {void}
     * @example
     * const angles = new pc.Vec3(0, 90, 0);
     * this.entity.setEulerAngles(angles);
     */ /**
     * @param {number|Vec3} x - Rotation around world space x-axis in degrees or vector holding rotations around world space axes in degrees.
     * @param {number} [y] - Rotation around world space y-axis in degrees.
     * @param {number} [z] - Rotation around world space z-axis in degrees.
     */ setEulerAngles(x, y, z) {
        this.localRotation.setFromEulerAngles(x, y, z);
        if (this._parent !== null) {
            const parentRot = this._parent.getRotation();
            invParentRot.copy(parentRot).invert();
            this.localRotation.mul2(invParentRot, this.localRotation);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Add a new child to the child list and update the parent value of the child node.
     * If the node already had a parent, it is removed from its child list.
     *
     * @param {GraphNode} node - The new child to add.
     * @example
     * const e = new pc.Entity(app);
     * this.entity.addChild(e);
     */ addChild(node) {
        this._prepareInsertChild(node);
        this._children.push(node);
        this._onInsertChild(node);
    }
    /**
     * Add a child to this node, maintaining the child's transform in world space.
     * If the node already had a parent, it is removed from its child list.
     *
     * @param {GraphNode} node - The child to add.
     * @example
     * const e = new pc.Entity(app);
     * this.entity.addChildAndSaveTransform(e);
     * @ignore
     */ addChildAndSaveTransform(node) {
        const wPos = node.getPosition();
        const wRot = node.getRotation();
        this._prepareInsertChild(node);
        node.setPosition(tmpMat4.copy(this.worldTransform).invert().transformPoint(wPos));
        node.setRotation(tmpQuat.copy(this.getRotation()).invert().mul(wRot));
        this._children.push(node);
        this._onInsertChild(node);
    }
    /**
     * Insert a new child to the child list at the specified index and update the parent value of
     * the child node. If the node already had a parent, it is removed from its child list.
     *
     * @param {GraphNode} node - The new child to insert.
     * @param {number} index - The index in the child list of the parent where the new node will be
     * inserted.
     * @example
     * const e = new pc.Entity(app);
     * this.entity.insertChild(e, 1);
     */ insertChild(node, index) {
        this._prepareInsertChild(node);
        this._children.splice(index, 0, node);
        this._onInsertChild(node);
    }
    /**
     * Prepares node for being inserted to a parent node, and removes it from the previous parent.
     *
     * @param {GraphNode} node - The node being inserted.
     * @private
     */ _prepareInsertChild(node) {
        // remove it from the existing parent
        node.remove();
        Debug.assert(node !== this, `GraphNode ${node?.name} cannot be a child of itself`);
        Debug.assert(!this.isDescendantOf(node), `GraphNode ${node?.name} cannot add an ancestor as a child`);
    }
    /**
     * Fires an event on all children of the node. The event `name` is fired on the first (root)
     * node only. The event `nameHierarchy` is fired for all children.
     *
     * @param {string} name - The name of the event to fire on the root.
     * @param {string} nameHierarchy - The name of the event to fire for all descendants.
     * @param {GraphNode} parent - The parent of the node being added/removed from the hierarchy.
     * @private
     */ _fireOnHierarchy(name, nameHierarchy, parent) {
        this.fire(name, parent);
        for(let i = 0; i < this._children.length; i++){
            this._children[i]._fireOnHierarchy(nameHierarchy, nameHierarchy, parent);
        }
    }
    /**
     * Called when a node is inserted into a node's child list.
     *
     * @param {GraphNode} node - The node that was inserted.
     * @private
     */ _onInsertChild(node) {
        node._parent = this;
        // the child node should be enabled in the hierarchy only if itself is enabled and if
        // this parent is enabled
        const enabledInHierarchy = node._enabled && this.enabled;
        if (node._enabledInHierarchy !== enabledInHierarchy) {
            node._enabledInHierarchy = enabledInHierarchy;
            // propagate the change to the children - necessary if we reparent a node
            // under a parent with a different enabled state (if we reparent a node that is
            // not active in the hierarchy under a parent who is active in the hierarchy then
            // we want our node to be activated)
            node._notifyHierarchyStateChanged(node, enabledInHierarchy);
        }
        // The graph depth of the child and all of its descendants will now change
        node._updateGraphDepth();
        // The child (plus subhierarchy) will need world transforms to be recalculated
        node._dirtifyWorld();
        // node might be already marked as dirty, in that case the whole chain stays frozen, so let's enforce unfreeze
        if (this._frozen) {
            node._unfreezeParentToRoot();
        }
        // alert an entity hierarchy that it has been inserted
        node._fireOnHierarchy('insert', 'inserthierarchy', this);
        // alert the parent that it has had a child inserted
        if (this.fire) this.fire('childinsert', node);
    }
    /**
     * Recurse the hierarchy and update the graph depth at each node.
     *
     * @private
     */ _updateGraphDepth() {
        this._graphDepth = this._parent ? this._parent._graphDepth + 1 : 0;
        for(let i = 0, len = this._children.length; i < len; i++){
            this._children[i]._updateGraphDepth();
        }
    }
    /**
     * Remove the node from the child list and update the parent value of the child.
     *
     * @param {GraphNode} child - The node to remove.
     * @example
     * const child = this.entity.children[0];
     * this.entity.removeChild(child);
     */ removeChild(child) {
        const index = this._children.indexOf(child);
        if (index === -1) {
            return;
        }
        // Remove from child list
        this._children.splice(index, 1);
        // Clear parent
        child._parent = null;
        // NOTE: see PR #4047 - this fix is removed for now as it breaks other things
        // notify the child hierarchy it has been removed from the parent,
        // which marks them as not enabled in hierarchy
        // if (child._enabledInHierarchy) {
        //     child._notifyHierarchyStateChanged(child, false);
        // }
        // alert children that they has been removed
        child._fireOnHierarchy('remove', 'removehierarchy', this);
        // alert the parent that it has had a child removed
        this.fire('childremove', child);
    }
    _sync() {
        if (this._dirtyLocal) {
            this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);
            this._dirtyLocal = false;
        }
        if (this._dirtyWorld) {
            if (this._parent === null) {
                this.worldTransform.copy(this.localTransform);
            } else {
                if (this.scaleCompensation) {
                    let parentWorldScale;
                    const parent = this._parent;
                    // Find a parent of the first uncompensated node up in the hierarchy and use its scale * localScale
                    let scale = this.localScale;
                    let parentToUseScaleFrom = parent; // current parent
                    if (parentToUseScaleFrom) {
                        while(parentToUseScaleFrom && parentToUseScaleFrom.scaleCompensation){
                            parentToUseScaleFrom = parentToUseScaleFrom._parent;
                        }
                        // topmost node with scale compensation
                        if (parentToUseScaleFrom) {
                            parentToUseScaleFrom = parentToUseScaleFrom._parent; // node without scale compensation
                            if (parentToUseScaleFrom) {
                                parentWorldScale = parentToUseScaleFrom.worldTransform.getScale();
                                scaleCompensateScale.mul2(parentWorldScale, this.localScale);
                                scale = scaleCompensateScale;
                            }
                        }
                    }
                    // Rotation is as usual
                    scaleCompensateRot2.setFromMat4(parent.worldTransform);
                    scaleCompensateRot.mul2(scaleCompensateRot2, this.localRotation);
                    // Find matrix to transform position
                    let tmatrix = parent.worldTransform;
                    if (parent.scaleCompensation) {
                        scaleCompensateScaleForParent.mul2(parentWorldScale, parent.getLocalScale());
                        scaleCompensatePosTransform.setTRS(parent.worldTransform.getTranslation(scaleCompensatePos), scaleCompensateRot2, scaleCompensateScaleForParent);
                        tmatrix = scaleCompensatePosTransform;
                    }
                    tmatrix.transformPoint(this.localPosition, scaleCompensatePos);
                    this.worldTransform.setTRS(scaleCompensatePos, scaleCompensateRot, scale);
                } else {
                    this.worldTransform.mulAffine2(this._parent.worldTransform, this.localTransform);
                }
            }
            this._dirtyWorld = false;
        }
    }
    /**
     * Updates the world transformation matrices at this node and all of its descendants.
     *
     * @ignore
     */ syncHierarchy() {
        if (!this._enabled) {
            return;
        }
        if (this._frozen) {
            return;
        }
        this._frozen = true;
        if (this._dirtyLocal || this._dirtyWorld) {
            this._sync();
        }
        const children = this._children;
        for(let i = 0, len = children.length; i < len; i++){
            children[i].syncHierarchy();
        }
    }
    /**
     * Reorients the graph node so that the negative z-axis points towards the target.
     *
     * @overload
     * @param {number} x - X-component of the world space coordinate to look at.
     * @param {number} y - Y-component of the world space coordinate to look at.
     * @param {number} z - Z-component of the world space coordinate to look at.
     * @param {number} [ux] - X-component of the up vector for the look at transform. Defaults to 0.
     * @param {number} [uy] - Y-component of the up vector for the look at transform. Defaults to 1.
     * @param {number} [uz] - Z-component of the up vector for the look at transform. Defaults to 0.
     * @returns {void}
     * @example
     * // Look at the world space origin, using the (default) positive y-axis for up
     * this.entity.lookAt(0, 0, 0);
     * @example
     * // Look at world space coordinate [10, 10, 10], using the negative world y-axis for up
     * this.entity.lookAt(10, 10, 10, 0, -1, 0);
     */ /**
     * Reorients the graph node so that the negative z-axis points towards the target.
     *
     * @overload
     * @param {Vec3} target - The world space coordinate to look at.
     * @param {Vec3} [up] - The world space up vector for look at transform. Defaults to {@link Vec3.UP}.
     * @returns {void}
     * @example
     * // Look at another entity, using the (default) positive y-axis for up
     * const target = otherEntity.getPosition();
     * this.entity.lookAt(target);
     * @example
     * // Look at another entity, using the negative world y-axis for up
     * const target = otherEntity.getPosition();
     * this.entity.lookAt(target, pc.Vec3.DOWN);
     */ /**
     * @param {number|Vec3} x - If passing a 3D vector, this is the world space coordinate to look at.
     * Otherwise, it is the x-component of the world space coordinate to look at.
     * @param {number|Vec3} [y] - If passing a 3D vector, this is the world space up vector for look at
     * transform. Otherwise, it is the y-component of the world space coordinate to look at.
     * @param {number} [z] - Z-component of the world space coordinate to look at.
     * @param {number} [ux] - X-component of the up vector for the look at transform. Defaults to 0.
     * @param {number} [uy] - Y-component of the up vector for the look at transform. Defaults to 1.
     * @param {number} [uz] - Z-component of the up vector for the look at transform. Defaults to 0.
     */ lookAt(x, y, z, ux = 0, uy = 1, uz = 0) {
        if (x instanceof Vec3) {
            target.copy(x);
            if (y instanceof Vec3) {
                up.copy(y);
            } else {
                up.copy(Vec3.UP);
            }
        } else if (z === undefined) {
            return;
        } else {
            target.set(x, y, z);
            up.set(ux, uy, uz);
        }
        matrix.setLookAt(this.getPosition(), target, up);
        rotation.setFromMat4(matrix);
        this.setRotation(rotation);
    }
    /**
     * Translates the graph node in world space by the specified translation vector.
     *
     * @overload
     * @param {number} x - X-coordinate of world space translation.
     * @param {number} y - Y-coordinate of world space translation.
     * @param {number} z - Z-coordinate of world space translation.
     * @returns {void}
     * @example
     * this.entity.translate(10, 0, 0);
     */ /**
     * Translates the graph node in world space by the specified translation vector.
     *
     * @overload
     * @param {Vec3} translation - Vector holding world space translation.
     * @returns {void}
     * @example
     * const translation = new pc.Vec3(10, 0, 0);
     * this.entity.translate(translation);
     */ /**
     * @param {number|Vec3} x - X-coordinate of world space translation or vector holding world space translation.
     * @param {number} [y] - Y-coordinate of world space translation.
     * @param {number} [z] - Z-coordinate of world space translation.
     */ translate(x, y, z) {
        if (x instanceof Vec3) {
            position$1.copy(x);
        } else {
            position$1.set(x, y, z);
        }
        position$1.add(this.getPosition());
        this.setPosition(position$1);
    }
    /**
     * Translates the graph node in local space by the specified translation vector.
     *
     * @overload
     * @param {number} x - X-coordinate of local space translation.
     * @param {number} y - Y-coordinate of local space translation.
     * @param {number} z - Z-coordinate of local space translation.
     * @returns {void}
     * @example
     * this.entity.translateLocal(10, 0, 0);
     */ /**
     * Translates the graph node in local space by the specified translation vector.
     *
     * @overload
     * @param {Vec3} translation - Vector holding local space translation.
     * @returns {void}
     * @example
     * const t = new pc.Vec3(10, 0, 0);
     * this.entity.translateLocal(t);
     */ /**
     * @param {number|Vec3} x - X-coordinate of local space translation or vector holding local space translation.
     * @param {number} [y] - Y-coordinate of local space translation.
     * @param {number} [z] - Z-coordinate of local space translation.
     */ translateLocal(x, y, z) {
        if (x instanceof Vec3) {
            position$1.copy(x);
        } else {
            position$1.set(x, y, z);
        }
        this.localRotation.transformVector(position$1, position$1);
        this.localPosition.add(position$1);
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Rotates the graph node in world space by the specified Euler angles. Eulers are specified in
     * degrees in XYZ order.
     *
     * @overload
     * @param {number} x - Rotation around world space x-axis in degrees.
     * @param {number} y - Rotation around world space y-axis in degrees.
     * @param {number} z - Rotation around world space z-axis in degrees.
     * @returns {void}
     * @example
     * this.entity.rotate(0, 90, 0);
     */ /**
     * Rotates the graph node in world space by the specified Euler angles. Eulers are specified in
     * degrees in XYZ order.
     *
     * @overload
     * @param {Vec3} rotation - Vector holding world space rotation.
     * @returns {void}
     * @example
     * const rotation = new pc.Vec3(0, 90, 0);
     * this.entity.rotate(rotation);
     */ /**
     * @param {number|Vec3} x - Rotation around world space x-axis in degrees or vector holding world space rotation.
     * @param {number} [y] - Rotation around world space y-axis in degrees.
     * @param {number} [z] - Rotation around world space z-axis in degrees.
     */ rotate(x, y, z) {
        rotation.setFromEulerAngles(x, y, z);
        if (this._parent === null) {
            this.localRotation.mul2(rotation, this.localRotation);
        } else {
            const rot = this.getRotation();
            const parentRot = this._parent.getRotation();
            invParentRot.copy(parentRot).invert();
            rotation.mul2(invParentRot, rotation);
            this.localRotation.mul2(rotation, rot);
        }
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
    /**
     * Rotates the graph node in local space by the specified Euler angles. Eulers are specified in
     * degrees in XYZ order.
     *
     * @overload
     * @param {number} x - Rotation around local space x-axis in degrees.
     * @param {number} y - Rotation around local space y-axis in degrees.
     * @param {number} z - Rotation around local space z-axis in degrees.
     * @returns {void}
     * @example
     * this.entity.rotateLocal(0, 90, 0);
     */ /**
     * Rotates the graph node in local space by the specified Euler angles. Eulers are specified in
     * degrees in XYZ order.
     *
     * @overload
     * @param {Vec3} rotation - Vector holding local space rotation.
     * @returns {void}
     * @example
     * const rotation = new pc.Vec3(0, 90, 0);
     * this.entity.rotateLocal(rotation);
     */ /**
     * @param {number|Vec3} x - Rotation around local space x-axis in degrees or vector holding local space rotation.
     * @param {number} [y] - Rotation around local space y-axis in degrees.
     * @param {number} [z] - Rotation around local space z-axis in degrees.
     */ rotateLocal(x, y, z) {
        rotation.setFromEulerAngles(x, y, z);
        this.localRotation.mul(rotation);
        if (!this._dirtyLocal) {
            this._dirtifyLocal();
        }
    }
}

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { StandardMaterial } from './standard-material.js'
 */ // device cache storing default material
const defaultMaterialDeviceCache = new DeviceCache();
/**
 * Returns default material, which is a material used instead of null material.
 *
 * @param {GraphicsDevice} device - The graphics device used to own the material.
 * @returns {StandardMaterial} The default instance of {@link StandardMaterial}.
 */ function getDefaultMaterial(device) {
    const material = defaultMaterialDeviceCache.get(device);
    Debug.assert(material);
    return material;
}
/**
 * Assigns the default material to device cache
 *
 * @param {GraphicsDevice} device - The graphics device used to own the material.
 * @param {StandardMaterial} material - The instance of {@link StandardMaterial}.
 */ function setDefaultMaterial(device, material) {
    Debug.assert(material);
    defaultMaterialDeviceCache.get(device, ()=>{
        return material;
    });
}

/**
 * Class implementing reference counting cache for objects. The objects itself is used as the key.
 * If you need a reference counted cache for objects accessed by a key, use
 * {@link RefCountedKeyCache}.
 */ class RefCountedCache {
    /**
     * Destroy all stored objects.
     */ destroy() {
        this.cache.forEach((refCount, object)=>{
            object.destroy();
        });
        this.cache.clear();
    }
    /**
     * Add object reference to the cache.
     *
     * @param {object} object - The object to add.
     */ incRef(object) {
        const refCount = (this.cache.get(object) || 0) + 1;
        this.cache.set(object, refCount);
    }
    /**
     * Remove object reference from the cache.
     *
     * @param {object} object - The object to remove.
     */ decRef(object) {
        if (object) {
            let refCount = this.cache.get(object);
            if (refCount) {
                refCount--;
                if (refCount === 0) {
                    // destroy object and remove it from cache
                    this.cache.delete(object);
                    object.destroy();
                } else {
                    // update new ref count in the cache
                    this.cache.set(object, refCount);
                }
            }
        }
    }
    constructor(){
        /**
     * The cache. The key is the object being stored in the cache. The value is ref count of the
     * object. When that reaches zero, destroy function on the object gets called and object is
     * removed from the cache.
     *
     * @type {Map<object, number>}
     */ this.cache = new Map();
    }
}

// Pure static class, implementing the cache of lightmaps generated at runtime using Lightmapper
// this allows us to automatically release realtime baked lightmaps when mesh instances using them are destroyed
class LightmapCache {
    static{
        this.cache = new RefCountedCache();
    }
    // add texture reference to lightmap cache
    static incRef(texture) {
        this.cache.incRef(texture);
    }
    // remove texture reference from lightmap cache
    static decRef(texture) {
        this.cache.decRef(texture);
    }
    static destroy() {
        this.cache.destroy();
    }
}

/**
 * @import { BindGroupFormat } from '../platform/graphics/bind-group-format.js'
 * @import { Camera } from './camera.js'
 * @import { GSplatInstance } from './gsplat/gsplat-instance.js'
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 * @import { Material } from './materials/material.js'
 * @import { Mesh } from './mesh.js'
 * @import { MorphInstance } from './morph-instance.js'
 * @import { CameraShaderParams } from './camera-shader-params.js'
 * @import { Scene } from './scene.js'
 * @import { ScopeId } from '../platform/graphics/scope-id.js'
 * @import { Shader } from '../platform/graphics/shader.js'
 * @import { SkinInstance } from './skin-instance.js'
 * @import { StencilParameters } from '../platform/graphics/stencil-parameters.js'
 * @import { Texture } from '../platform/graphics/texture.js'
 * @import { UniformBufferFormat } from '../platform/graphics/uniform-buffer-format.js'
 * @import { Vec3 } from '../core/math/vec3.js'
 * @import { VertexBuffer } from '../platform/graphics/vertex-buffer.js'
 * @import { CameraComponent } from '../framework/components/camera/component.js';
 */ let id$4 = 0;
const _tmpAabb = new BoundingBox();
const _tempBoneAabb = new BoundingBox();
const _tempSphere = new BoundingSphere();
/** @type {Set<Mesh>} */ const _meshSet = new Set();
// internal array used to evaluate the hash for the shader instance
const lookupHashes = new Uint32Array(4);
/**
 * Internal data structure used to store data used by hardware instancing.
 *
 * @ignore
 */ class InstancingData {
    /**
     * @param {number} numObjects - The number of objects instanced.
     */ constructor(numObjects){
        /** @type {VertexBuffer|null} */ this.vertexBuffer = null;
        /**
     * True if the vertex buffer is destroyed when the mesh instance is destroyed.
     *
     * @type {boolean}
     */ this._destroyVertexBuffer = false;
        this.count = numObjects;
    }
    destroy() {
        if (this._destroyVertexBuffer) {
            this.vertexBuffer?.destroy();
        }
        this.vertexBuffer = null;
    }
}
/**
 * Internal data structure used to store data used by indirect rendering.
 *
 * @ignore
 */ class IndirectData {
    /**
     * Retrieves the slot in the indirect buffer for a specific camera.
     *
     * @param {CameraComponent|null} camera - The camera component to retrieve the slot for, or null
     * if the slot should be used for all cameras.
     * @returns {number|undefined} - The slot in the indirect buffer, or undefined.
     */ get(camera) {
        return this.map.get(camera) ?? this.map.get(null);
    }
    constructor(){
        /**
     * A map of camera components to their corresponding slot in the indirect buffer.
     *
     * @type {Map<CameraComponent, number>}
     */ this.map = new Map();
        /**
     * An array of 4 integers used to store mesh metadata needed for indirect rendering.
     *
     * @type {Int32Array}
     */ this.meshMetaData = new Int32Array(4);
    }
}
/**
 * Internal helper class for storing the shader and related mesh bind group in the shader cache.
 *
 * @ignore
 */ class ShaderInstance {
    /**
     * Returns the mesh bind group for the shader.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @returns {BindGroup} - The mesh bind group.
     */ getBindGroup(device) {
        // create bind group
        if (!this.bindGroup) {
            const shader = this.shader;
            Debug.assert(shader);
            const bindGroupFormat = shader.meshBindGroupFormat;
            Debug.assert(bindGroupFormat);
            this.bindGroup = new BindGroup(device, bindGroupFormat);
            DebugHelper.setName(this.bindGroup, `MeshBindGroup_${this.bindGroup.id}`);
        }
        return this.bindGroup;
    }
    /**
     * Returns the uniform buffer for the shader.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @returns {UniformBuffer} - The uniform buffer.
     */ getUniformBuffer(device) {
        // create uniform buffer
        if (!this.uniformBuffer) {
            const shader = this.shader;
            Debug.assert(shader);
            const ubFormat = shader.meshUniformBufferFormat;
            Debug.assert(ubFormat);
            this.uniformBuffer = new UniformBuffer(device, ubFormat, false);
        }
        return this.uniformBuffer;
    }
    destroy() {
        this.bindGroup?.destroy();
        this.bindGroup = null;
        this.uniformBuffer?.destroy();
        this.uniformBuffer = null;
    }
    constructor(){
        /**
     * A bind group storing mesh textures / samplers for the shader. but not the uniform buffer.
     *
     * @type {BindGroup|null}
     */ this.bindGroup = null;
        /**
     * A uniform buffer storing mesh uniforms for the shader.
     *
     * @type {UniformBuffer|null}
     */ this.uniformBuffer = null;
    }
}
/**
 * @callback CalculateSortDistanceCallback
 * Callback used by {@link Layer} to calculate the "sort distance" for a {@link MeshInstance},
 * which determines its place in the render order.
 * @param {MeshInstance} meshInstance - The mesh instance.
 * @param {Vec3} cameraPosition - The position of the camera.
 * @param {Vec3} cameraForward - The forward vector of the camera.
 * @returns {void}
 */ /**
 * An instance of a {@link Mesh}. A single mesh can be referenced by many mesh instances that can
 * have different transforms and materials.
 *
 * ### Instancing
 *
 * Hardware instancing lets the GPU draw many copies of the same geometry with a single draw call.
 * Use {@link setInstancing} to attach a vertex buffer that holds per-instance data
 * (for example a mat4 world-matrix for every instance). Set {@link instancingCount}
 * to control how many instances are rendered. Passing `null` to {@link setInstancing}
 * disables instancing once again.
 *
 * ```javascript
 * // vb is a vertex buffer with one 4×4 matrix per instance
 * meshInstance.setInstancing(vb);
 * meshInstance.instancingCount = numInstances;
 * ```
 *
 * **Examples**
 *
 * - {@link https://playcanvas.github.io/#graphics/instancing-basic graphics/instancing-basic}
 * - {@link https://playcanvas.github.io/#graphics/instancing-custom graphics/instancing-custom}
 *
 * ### GPU-Driven Indirect Rendering (WebGPU Only)
 *
 * Instead of issuing draw calls from the CPU, parameters are written into a GPU
 * storage buffer and executed via indirect draw commands. Allocate a slot with
 * `GraphicsDevice.getIndirectDrawSlot()`, then bind the mesh instance to that slot:
 *
 * ```javascript
 * const slot = app.graphicsDevice.getIndirectDrawSlot();
 * meshInstance.setIndirect(null, slot); // first arg can be a CameraComponent or null
 * ```
 *
 * **Example**
 *
 * - {@link https://playcanvas.github.io/#compute/indirect-draw compute/indirect-draw}
 *
 * @category Graphics
 */ class MeshInstance {
    /**
     * Create a new MeshInstance instance.
     *
     * @param {Mesh} mesh - The graphics mesh to instance.
     * @param {Material} material - The material to use for this mesh instance.
     * @param {GraphNode} [node] - The graph node defining the transform for this instance. This
     * parameter is optional when used with {@link RenderComponent} and will use the node the
     * component is attached to.
     * @example
     * // Create a mesh instance pointing to a 1x1x1 'cube' mesh
     * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, new pc.BoxGeometry());
     * const material = new pc.StandardMaterial();
     *
     * const meshInstance = new pc.MeshInstance(mesh, material);
     *
     * const entity = new pc.Entity();
     * entity.addComponent('render', {
     *     meshInstances: [meshInstance]
     * });
     *
     * // Add the entity to the scene hierarchy
     * this.app.scene.root.addChild(entity);
     */ constructor(mesh, material, node = null){
        /**
     * Enable shadow casting for this mesh instance. Use this property to enable/disable shadow
     * casting without overhead of removing from scene. Note that this property does not add the
     * mesh instance to appropriate list of shadow casters on a {@link Layer}, but allows mesh to
     * be skipped from shadow casting while it is in the list already. Defaults to false.
     *
     * @type {boolean}
     */ this.castShadow = false;
        /**
     * Specifies a bitmask that controls which shadow cascades a mesh instance contributes
     * to when rendered with a {@link LIGHTTYPE_DIRECTIONAL} light source.
     * This setting is only effective if the {@link castShadow} property is enabled.
     * Defaults to {@link SHADOW_CASCADE_ALL}, which means the mesh casts shadows into all available cascades.
     *
     * @type {number}
     */ this.shadowCascadeMask = SHADOW_CASCADE_ALL;
        /**
     * Controls whether the mesh instance can be culled by frustum culling (see
     * {@link CameraComponent#frustumCulling}). Defaults to true.
     *
     * @type {boolean}
     */ this.cull = true;
        /**
     * Determines the rendering order of mesh instances. Only used when mesh instances are added to
     * a {@link Layer} with {@link Layer#opaqueSortMode} or {@link Layer#transparentSortMode}
     * (depending on the material) set to {@link SORTMODE_MANUAL}.
     *
     * @type {number}
     */ this.drawOrder = 0;
        /**
     * @type {number}
     * @ignore
     */ this._drawBucket = 127;
        /**
     * Enable rendering for this mesh instance. Use visible property to enable/disable rendering
     * without overhead of removing from scene. But note that the mesh instance is still in the
     * hierarchy and still in the draw call list.
     *
     * @type {boolean}
     */ this.visible = true;
        /**
     * Read this value in {@link Scene.EVENT_POSTCULL} event to determine if the object is actually going
     * to be rendered.
     *
     * @type {boolean}
     */ this.visibleThisFrame = false;
        /**
     * Negative scale batching support.
     *
     * @type {number}
     * @ignore
     */ this.flipFacesFactor = 1;
        /**
     * @type {GSplatInstance|null}
     * @ignore
     */ this.gsplatInstance = null;
        /** @ignore */ this.id = id$4++;
        /**
     * Custom function used to customize culling (e.g. for 2D UI elements).
     *
     * @type {Function|null}
     * @ignore
     */ this.isVisibleFunc = null;
        /**
     * @type {InstancingData|null}
     * @ignore
     */ this.instancingData = null;
        /**
     * @type {IndirectData|null}
     * @ignore
     */ this.indirectData = null;
        /**
     * @type {Record<string, {scopeId: ScopeId|null, data: any, passFlags: number}>}
     * @ignore
     */ this.parameters = {};
        /**
     * True if the mesh instance is pickable by the {@link Picker}. Defaults to true.
     *
     * @type {boolean}
     * @ignore
     */ this.pick = true;
        /**
     * The stencil parameters for front faces or null if no stencil is enabled.
     *
     * @type {StencilParameters|null}
     * @ignore
     */ this.stencilFront = null;
        /**
     * The stencil parameters for back faces or null if no stencil is enabled.
     *
     * @type {StencilParameters|null}
     * @ignore
     */ this.stencilBack = null;
        /**
     * True if the material of the mesh instance is transparent. Optimization to avoid accessing
     * the material. Updated by the material instance itself.
     *
     * @ignore
     */ this.transparent = false;
        /** @private */ this._aabb = new BoundingBox();
        /** @private */ this._aabbVer = -1;
        /** @private */ this._aabbMeshVer = -1;
        /**
     * @type {BoundingBox|null}
     * @private
     */ this._customAabb = null;
        /** @private */ this._updateAabb = true;
        /** @private */ this._updateAabbFunc = null;
        /**
     * The internal sorting key used by the shadow renderer.
     *
     * @ignore
     */ this._sortKeyShadow = 0;
        /**
     * The internal sorting key used by the forward renderer, in case SORTMODE_MATERIALMESH sorting
     * is used.
     *
     * @private
     */ this._sortKeyForward = 0;
        /**
     * The internal sorting key used by the forward renderer, in case SORTMODE_BACK2FRONT or
     * SORTMODE_FRONT2BACK sorting is used.
     *
     * @ignore
     */ this._sortKeyDynamic = 0;
        /** @private */ this._layer = LAYER_WORLD;
        /**
     * @type {Material|null}
     * @private
     */ this._material = null;
        /**
     * @type {SkinInstance|null}
     * @private
     */ this._skinInstance = null;
        /**
     * @type {MorphInstance|null}
     * @private
     */ this._morphInstance = null;
        /** @private */ this._receiveShadow = true;
        /** @private */ this._renderStyle = RENDERSTYLE_SOLID;
        /** @private */ this._screenSpace = false;
        /**
     * The cache of shaders, indexed by a hash value.
     *
     * @type {Map<number, ShaderInstance>}
     * @private
     */ this._shaderCache = new Map();
        /**
     * 2 byte toggles, 2 bytes light mask; Default value is no toggles and mask = pc.MASK_AFFECT_DYNAMIC
     *
     * @private
     */ this._shaderDefs = MASK_AFFECT_DYNAMIC << 16;
        /**
     * @type {CalculateSortDistanceCallback|null}
     * @private
     */ this._calculateSortDistance = null;
        Debug.assert(!(mesh instanceof GraphNode), 'Incorrect parameters for MeshInstance\'s constructor. Use new MeshInstance(mesh, material, node)');
        this.node = node; // The node that defines the transform of the mesh instance
        this._mesh = mesh; // The mesh that this instance renders
        mesh.incRefCount();
        this.material = material; // The material with which to render this instance
        if (mesh.vertexBuffer) {
            const format = mesh.vertexBuffer.format;
            this._shaderDefs |= format.hasUv0 ? SHADERDEF_UV0 : 0;
            this._shaderDefs |= format.hasUv1 ? SHADERDEF_UV1 : 0;
            this._shaderDefs |= format.hasColor ? SHADERDEF_VCOLOR : 0;
            this._shaderDefs |= format.hasTangents ? SHADERDEF_TANGENTS : 0;
        }
        // 64-bit integer key that defines render order of this mesh instance
        this.updateKey();
    }
    /**
     * Sets the draw bucket for mesh instances. The draw bucket, an integer from 0 to 255 (default
     * 127), serves as the primary sort key for mesh rendering. Meshes are sorted by draw bucket,
     * then by sort mode. This setting is only effective when mesh instances are added to a
     * {@link Layer} with its {@link Layer#opaqueSortMode} or {@link Layer#transparentSortMode}
     * (depending on the material) set to {@link SORTMODE_BACK2FRONT}, {@link SORTMODE_FRONT2BACK},
     * or {@link SORTMODE_MATERIALMESH}.
     *
     * Note: When {@link SORTMODE_BACK2FRONT} is used, a descending sort order is used; otherwise,
     * an ascending sort order is used.
     *
     * @type {number}
     */ set drawBucket(bucket) {
        // 8bit integer
        this._drawBucket = Math.floor(bucket) & 0xff;
        this.updateKey();
    }
    /**
     * Gets the draw bucket for mesh instance.
     *
     * @type {number}
     */ get drawBucket() {
        return this._drawBucket;
    }
    /**
     * Sets the render style of the mesh instance. Can be:
     *
     * - {@link RENDERSTYLE_SOLID}
     * - {@link RENDERSTYLE_WIREFRAME}
     * - {@link RENDERSTYLE_POINTS}
     *
     * Defaults to {@link RENDERSTYLE_SOLID}.
     *
     * @type {number}
     */ set renderStyle(renderStyle) {
        this._renderStyle = renderStyle;
        this.mesh.prepareRenderState(renderStyle);
    }
    /**
     * Gets the render style of the mesh instance.
     *
     * @type {number}
     */ get renderStyle() {
        return this._renderStyle;
    }
    /**
     * Sets the graphics mesh being instanced.
     *
     * @type {Mesh}
     */ set mesh(mesh) {
        if (mesh === this._mesh) {
            return;
        }
        if (this._mesh) {
            this._mesh.decRefCount();
        }
        this._mesh = mesh;
        if (mesh) {
            mesh.incRefCount();
        }
    }
    /**
     * Gets the graphics mesh being instanced.
     *
     * @type {Mesh}
     */ get mesh() {
        return this._mesh;
    }
    /**
     * Sets the world space axis-aligned bounding box for this mesh instance.
     *
     * @type {BoundingBox}
     */ set aabb(aabb) {
        this._aabb = aabb;
    }
    /**
     * Gets the world space axis-aligned bounding box for this mesh instance.
     *
     * @type {BoundingBox}
     */ get aabb() {
        // use specified world space aabb
        if (!this._updateAabb) {
            return this._aabb;
        }
        // callback function returning world space aabb
        if (this._updateAabbFunc) {
            return this._updateAabbFunc(this._aabb);
        }
        // use local space override aabb if specified
        let localAabb = this._customAabb;
        let toWorldSpace = !!localAabb;
        // otherwise evaluate local aabb
        if (!localAabb) {
            localAabb = _tmpAabb;
            if (this.skinInstance) {
                // Initialize local bone AABBs if needed
                if (!this.mesh.boneAabb) {
                    const morphTargets = this._morphInstance ? this._morphInstance.morph._targets : null;
                    this.mesh._initBoneAabbs(morphTargets);
                }
                // evaluate local space bounds based on all active bones
                const boneUsed = this.mesh.boneUsed;
                let first = true;
                for(let i = 0; i < this.mesh.boneAabb.length; i++){
                    if (boneUsed[i]) {
                        // transform bone AABB by bone matrix
                        _tempBoneAabb.setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);
                        // add them up
                        if (first) {
                            first = false;
                            localAabb.center.copy(_tempBoneAabb.center);
                            localAabb.halfExtents.copy(_tempBoneAabb.halfExtents);
                        } else {
                            localAabb.add(_tempBoneAabb);
                        }
                    }
                }
                toWorldSpace = true;
            } else if (this.node._aabbVer !== this._aabbVer || this.mesh._aabbVer !== this._aabbMeshVer) {
                // local space bounding box - either from mesh or empty
                if (this.mesh) {
                    localAabb.center.copy(this.mesh.aabb.center);
                    localAabb.halfExtents.copy(this.mesh.aabb.halfExtents);
                } else {
                    localAabb.center.set(0, 0, 0);
                    localAabb.halfExtents.set(0, 0, 0);
                }
                // update local space bounding box by morph targets
                if (this.mesh && this.mesh.morph) {
                    const morphAabb = this.mesh.morph.aabb;
                    localAabb._expand(morphAabb.getMin(), morphAabb.getMax());
                }
                toWorldSpace = true;
                this._aabbVer = this.node._aabbVer;
                this._aabbMeshVer = this.mesh._aabbVer;
            }
        }
        // store world space bounding box
        if (toWorldSpace) {
            this._aabb.setFromTransformedAabb(localAabb, this.node.getWorldTransform());
        }
        return this._aabb;
    }
    /**
     * Clear the internal shader cache.
     *
     * @ignore
     */ clearShaders() {
        this._shaderCache.forEach((shaderInstance)=>{
            shaderInstance.destroy();
        });
        this._shaderCache.clear();
    }
    /**
     * Returns the shader instance for the specified shader pass and light hash that is compatible
     * with this mesh instance.
     *
     * @param {number} shaderPass - The shader pass index.
     * @param {number} lightHash - The hash value of the lights that are affecting this mesh instance.
     * @param {Scene} scene - The scene.
     * @param {CameraShaderParams} cameraShaderParams - The camera shader parameters.
     * @param {UniformBufferFormat} [viewUniformFormat] - The format of the view uniform buffer.
     * @param {BindGroupFormat} [viewBindGroupFormat] - The format of the view bind group.
     * @param {any} [sortedLights] - Array of arrays of lights.
     * @returns {ShaderInstance} - the shader instance.
     * @ignore
     */ getShaderInstance(shaderPass, lightHash, scene, cameraShaderParams, viewUniformFormat, viewBindGroupFormat, sortedLights) {
        const shaderDefs = this._shaderDefs;
        // unique hash for the required shader
        lookupHashes[0] = shaderPass;
        lookupHashes[1] = lightHash;
        lookupHashes[2] = shaderDefs;
        lookupHashes[3] = cameraShaderParams.hash;
        const hash = hash32Fnv1a(lookupHashes);
        // look up the cache
        let shaderInstance = this._shaderCache.get(hash);
        // cache miss in the shader cache of the mesh instance
        if (!shaderInstance) {
            const mat = this._material;
            // get the shader from the material
            shaderInstance = new ShaderInstance();
            shaderInstance.shader = mat.variants.get(hash);
            shaderInstance.hashes = new Uint32Array(lookupHashes);
            // cache miss in the material variants
            if (!shaderInstance.shader) {
                // marker to allow us to see the source node for shader alloc
                DebugGraphics.pushGpuMarker(this.mesh.device, `Node: ${this.node.name}`);
                const shader = mat.getShaderVariant({
                    device: this.mesh.device,
                    scene: scene,
                    objDefs: shaderDefs,
                    cameraShaderParams: cameraShaderParams,
                    pass: shaderPass,
                    sortedLights: sortedLights,
                    viewUniformFormat: viewUniformFormat,
                    viewBindGroupFormat: viewBindGroupFormat,
                    vertexFormat: this.mesh.vertexBuffer?.format
                });
                DebugGraphics.popGpuMarker(this.mesh.device);
                // add it to the material variants cache
                mat.variants.set(hash, shader);
                shaderInstance.shader = shader;
            }
            // add it to the mesh instance cache
            this._shaderCache.set(hash, shaderInstance);
        }
        Debug.call(()=>{
            // due to a small number of shaders in the cache, and to avoid performance hit, we're not
            // handling the hash collision. This is very unlikely but still possible. Check and report
            // if it happens in the debug mode, allowing us to fix the issue.
            if (!array.equals(shaderInstance.hashes, lookupHashes)) {
                Debug.errorOnce('Hash collision in the shader cache for mesh instance. This is very unlikely but still possible. Please report this issue.');
            }
        });
        return shaderInstance;
    }
    /**
     * Sets the material used by this mesh instance.
     *
     * @type {Material}
     */ set material(material) {
        this.clearShaders();
        const prevMat = this._material;
        // Remove the material's reference to this mesh instance
        if (prevMat) {
            prevMat.removeMeshInstanceRef(this);
        }
        this._material = material;
        if (material) {
            // Record that the material is referenced by this mesh instance
            material.addMeshInstanceRef(this);
            // update transparent flag based on material
            this.transparent = material.transparent;
            this.updateKey();
        }
    }
    /**
     * Gets the material used by this mesh instance.
     *
     * @type {Material}
     */ get material() {
        return this._material;
    }
    /**
     * @param {number} shaderDefs - The shader definitions to set.
     * @private
     */ _updateShaderDefs(shaderDefs) {
        if (shaderDefs !== this._shaderDefs) {
            this._shaderDefs = shaderDefs;
            this.clearShaders();
        }
    }
    /**
     * Sets the callback to calculate sort distance. In some circumstances mesh instances are
     * sorted by a distance calculation to determine their rendering order. Set this callback to
     * override the default distance calculation, which gives the dot product of the camera forward
     * vector and the vector between the camera position and the center of the mesh instance's
     * axis-aligned bounding box. This option can be particularly useful for rendering transparent
     * meshes in a better order than the default.
     *
     * @type {CalculateSortDistanceCallback|null}
     */ set calculateSortDistance(calculateSortDistance) {
        this._calculateSortDistance = calculateSortDistance;
    }
    /**
     * Gets the callback to calculate sort distance.
     *
     * @type {CalculateSortDistanceCallback|null}
     */ get calculateSortDistance() {
        return this._calculateSortDistance;
    }
    set receiveShadow(val) {
        if (this._receiveShadow !== val) {
            this._receiveShadow = val;
            this._updateShaderDefs(val ? this._shaderDefs & -2 : this._shaderDefs | SHADERDEF_NOSHADOW);
        }
    }
    get receiveShadow() {
        return this._receiveShadow;
    }
    set batching(val) {
        this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_BATCH : this._shaderDefs & -16385);
    }
    get batching() {
        return (this._shaderDefs & SHADERDEF_BATCH) !== 0;
    }
    /**
     * Sets the skin instance managing skinning of this mesh instance. Set to null if skinning is
     * not used.
     *
     * @type {SkinInstance|null}
     */ set skinInstance(val) {
        this._skinInstance = val;
        this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SKIN : this._shaderDefs & -3);
        this._setupSkinUpdate();
    }
    /**
     * Gets the skin instance managing skinning of this mesh instance.
     *
     * @type {SkinInstance|null}
     */ get skinInstance() {
        return this._skinInstance;
    }
    /**
     * Sets the morph instance managing morphing of this mesh instance. Set to null if morphing is
     * not used.
     *
     * @type {MorphInstance|null}
     */ set morphInstance(val) {
        // release existing
        this._morphInstance?.destroy();
        // assign new
        this._morphInstance = val;
        let shaderDefs = this._shaderDefs;
        shaderDefs = val && val.morph.morphPositions ? shaderDefs | SHADERDEF_MORPH_POSITION : shaderDefs & -1025;
        shaderDefs = val && val.morph.morphNormals ? shaderDefs | SHADERDEF_MORPH_NORMAL : shaderDefs & -2049;
        shaderDefs = val && val.morph.intRenderFormat ? shaderDefs | SHADERDEF_MORPH_TEXTURE_BASED_INT : shaderDefs & -8193;
        this._updateShaderDefs(shaderDefs);
    }
    /**
     * Gets the morph instance managing morphing of this mesh instance.
     *
     * @type {MorphInstance|null}
     */ get morphInstance() {
        return this._morphInstance;
    }
    set screenSpace(val) {
        if (this._screenSpace !== val) {
            this._screenSpace = val;
            this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SCREENSPACE : this._shaderDefs & -257);
        }
    }
    get screenSpace() {
        return this._screenSpace;
    }
    set key(val) {
        this._sortKeyForward = val;
    }
    get key() {
        return this._sortKeyForward;
    }
    /**
     * Sets the mask controlling which {@link LightComponent}s light this mesh instance, which
     * {@link CameraComponent} sees it and in which {@link Layer} it is rendered. Defaults to 1.
     *
     * @type {number}
     */ set mask(val) {
        const toggles = this._shaderDefs & 0x0000FFFF;
        this._updateShaderDefs(toggles | val << 16);
    }
    /**
     * Gets the mask controlling which {@link LightComponent}s light this mesh instance, which
     * {@link CameraComponent} sees it and in which {@link Layer} it is rendered.
     *
     * @type {number}
     */ get mask() {
        return this._shaderDefs >> 16;
    }
    /**
     * Sets the number of instances when using hardware instancing to render the mesh.
     *
     * @type {number}
     */ set instancingCount(value) {
        if (this.instancingData) {
            this.instancingData.count = value;
        }
    }
    /**
     * Gets the number of instances when using hardware instancing to render the mesh.
     *
     * @type {number}
     */ get instancingCount() {
        return this.instancingData ? this.instancingData.count : 0;
    }
    destroy() {
        const mesh = this.mesh;
        if (mesh) {
            // this decreases ref count on the mesh
            this.mesh = null;
            // destroy mesh
            if (mesh.refCount < 1) {
                mesh.destroy();
            }
        }
        // release ref counted lightmaps
        this.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);
        this.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);
        this._skinInstance?.destroy();
        this._skinInstance = null;
        this.morphInstance?.destroy();
        this.morphInstance = null;
        this.clearShaders();
        // make sure material clears references to this meshInstance
        this.material = null;
        this.instancingData?.destroy();
    }
    static{
        // shader uniform names for lightmaps
        this.lightmapParamNames = [
            'texture_lightMap',
            'texture_dirLightMap'
        ];
    }
    /**
     * Sets the render style for an array of mesh instances.
     *
     * @param {MeshInstance[]} meshInstances - The mesh instances to set the render style for.
     * @param {number} renderStyle - The render style to set.
     * @ignore
     */ static _prepareRenderStyleForArray(meshInstances, renderStyle) {
        if (meshInstances) {
            for(let i = 0; i < meshInstances.length; i++){
                // switch mesh instance to the requested style
                meshInstances[i]._renderStyle = renderStyle;
                // process all unique meshes
                const mesh = meshInstances[i].mesh;
                if (!_meshSet.has(mesh)) {
                    _meshSet.add(mesh);
                    mesh.prepareRenderState(renderStyle);
                }
            }
            _meshSet.clear();
        }
    }
    /**
     * Test if meshInstance is visible by camera. It requires the frustum of the camera to be up to
     * date, which forward-renderer takes care of. This function should not be called elsewhere.
     *
     * @param {Camera} camera - The camera to test visibility against.
     * @returns {boolean} - True if the mesh instance is visible by the camera, false otherwise.
     * @ignore
     */ _isVisible(camera) {
        if (this.visible) {
            // custom visibility method of MeshInstance
            if (this.isVisibleFunc) {
                return this.isVisibleFunc(camera);
            }
            _tempSphere.center = this.aabb.center; // this line evaluates aabb
            _tempSphere.radius = this._aabb.halfExtents.length();
            return camera.frustum.containsSphere(_tempSphere) > 0;
        }
        return false;
    }
    updateKey() {
        // 31      : sign bit (leave as 0)
        // 30 - 23 : 8 bits for draw bucket - highest priority for sorting
        // 22      : 1 bit for alpha test / coverage, to render them after opaque for GPU efficiency
        // 21 - 0  : 22 bits for material ID
        const { material } = this;
        this._sortKeyForward = this._drawBucket << 23 | (material.alphaToCoverage || material.alphaTest ? 0x400000 : 0) | material.id & 0x3fffff;
    }
    /**
     * Sets up {@link MeshInstance} to be rendered using Hardware Instancing.
     * Note that {@link instancingCount} is automatically set to the number of vertices of the
     * vertex buffer when it is provided.
     *
     * @param {VertexBuffer|null} vertexBuffer - Vertex buffer to hold per-instance vertex data
     * (usually world matrices). Pass null to turn off hardware instancing.
     * @param {boolean} cull - Whether to perform frustum culling on this instance. If true, the whole
     * instance will be culled by the  camera frustum. This often involves setting
     * {@link RenderComponent#customAabb} containing all instances. Defaults to false, which means
     * the whole instance is always rendered.
     */ setInstancing(vertexBuffer, cull = false) {
        if (vertexBuffer) {
            this.instancingData = new InstancingData(vertexBuffer.numVertices);
            this.instancingData.vertexBuffer = vertexBuffer;
            // mark vertex buffer as instancing data
            vertexBuffer.format.instancing = true;
            // set up culling
            this.cull = cull;
        } else {
            this.instancingData = null;
            this.cull = true;
        }
        this._updateShaderDefs(vertexBuffer ? this._shaderDefs | SHADERDEF_INSTANCING : this._shaderDefs & -33);
    }
    /**
     * Sets the {@link MeshInstance} to be rendered using indirect rendering, where the GPU,
     * typically using a Compute shader, stores draw call parameters in a buffer.
     * Note that this is only supported on WebGPU, and ignored on other platforms.
     *
     * @param {CameraComponent|null} camera - Camera component to set indirect data for, or
     * null if the indirect slot should be used for all cameras.
     * @param {number} slot - Slot in the buffer to set the draw call parameters. Allocate a slot
     * in the buffer by calling {@link GraphicsDevice#getIndirectDrawSlot}.
     */ setIndirect(camera, slot) {
        this._allocIndirectData();
        // store camera to slot mapping
        this.indirectData.map.set(camera?.camera ?? null, slot);
        // remove all data from this map at the end of the frame, slot needs to be assigned each frame
        const device = this.mesh.device;
        device.mapsToClear.add(this.indirectData.map);
    }
    /**
     * Retrieves the mesh metadata needed for indirect rendering.
     *
     * @returns {Int32Array} - A typed array with 4 elements representing the mesh metadata, which
     * is typically needed when generating indirect draw call parameters using Compute shader. These
     * can be provided to the Compute shader using vec4i uniform. The values are based on
     * {@link Mesh#primitive}, stored in this order: [count, base, baseVertex, 0]. The last value is
     * always zero and is reserved for future use.
     */ getIndirectMetaData() {
        this._allocIndirectData();
        const prim = this.mesh?.primitive[this.renderStyle];
        const data = this.indirectData.meshMetaData;
        data[0] = prim.count;
        data[1] = prim.base;
        data[2] = prim.baseVertex;
        // data[3] is padding, can be used for first instance in the future
        return data;
    }
    _allocIndirectData() {
        if (!this.indirectData) {
            this.indirectData = new IndirectData();
        }
    }
    ensureMaterial(device) {
        if (!this.material) {
            Debug.warn(`Mesh attached to entity '${this.node.name}' does not have a material, using a default one.`);
            this.material = getDefaultMaterial(device);
        }
    }
    // Parameter management
    clearParameters() {
        this.parameters = {};
    }
    getParameters() {
        return this.parameters;
    }
    /**
     * Retrieves the specified shader parameter from a mesh instance.
     *
     * @param {string} name - The name of the parameter to query.
     * @returns {object} The named parameter.
     */ getParameter(name) {
        return this.parameters[name];
    }
    /**
     * Sets a shader parameter on a mesh instance. Note that this parameter will take precedence
     * over parameter of the same name if set on Material this mesh instance uses for rendering.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {number|number[]|Texture|Float32Array} data - The value for the specified parameter.
     * @param {number} [passFlags] - Mask describing which passes the material should be included
     * in. Defaults to 0xFFFFFFFF (all passes).
     */ setParameter(name, data, passFlags = 0xFFFFFFFF) {
        const param = this.parameters[name];
        if (param) {
            param.data = data;
            param.passFlags = passFlags;
        } else {
            this.parameters[name] = {
                scopeId: null,
                data: data,
                passFlags: passFlags
            };
        }
    }
    /**
     * A wrapper over settings parameter specifically for realtime baked lightmaps. This handles
     * reference counting of lightmaps and releases them when no longer referenced.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {Texture|null} texture - The lightmap texture to set.
     * @ignore
     */ setRealtimeLightmap(name, texture) {
        // no change
        const old = this.getParameter(name);
        if (old === texture) {
            return;
        }
        // remove old
        if (old) {
            LightmapCache.decRef(old.data);
        }
        // assign new
        if (texture) {
            LightmapCache.incRef(texture);
            this.setParameter(name, texture);
        } else {
            this.deleteParameter(name);
        }
    }
    /**
     * Deletes a shader parameter on a mesh instance.
     *
     * @param {string} name - The name of the parameter to delete.
     */ deleteParameter(name) {
        if (this.parameters[name]) {
            delete this.parameters[name];
        }
    }
    /**
     * Used to apply parameters from this mesh instance into scope of uniforms, called internally
     * by forward-renderer.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {number} passFlag - The pass flag for the current render pass.
     * @ignore
     */ setParameters(device, passFlag) {
        const parameters = this.parameters;
        for(const paramName in parameters){
            const parameter = parameters[paramName];
            if (parameter.passFlags & passFlag) {
                if (!parameter.scopeId) {
                    parameter.scopeId = device.scope.resolve(paramName);
                }
                parameter.scopeId.setValue(parameter.data);
            }
        }
    }
    /**
     * @param {boolean} value - True to enable lightmapped rendering, false to disable.
     * @ignore
     */ setLightmapped(value) {
        if (value) {
            this.mask = (this.mask | MASK_AFFECT_LIGHTMAPPED) & -6;
        } else {
            this.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);
            this.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);
            this._shaderDefs &= -4289;
            this.mask = (this.mask | MASK_AFFECT_DYNAMIC) & -7;
        }
    }
    /**
     * @param {BoundingBox|null} aabb - The custom axis-aligned bounding box or null to reset to
     * the mesh's bounding box.
     * @ignore
     */ setCustomAabb(aabb) {
        if (aabb) {
            // store the override aabb
            if (this._customAabb) {
                this._customAabb.copy(aabb);
            } else {
                this._customAabb = aabb.clone();
            }
        } else {
            // no override, force refresh the actual one
            this._customAabb = null;
            this._aabbVer = -1;
        }
        this._setupSkinUpdate();
    }
    /** @private */ _setupSkinUpdate() {
        // set if bones need to be updated before culling
        if (this._skinInstance) {
            this._skinInstance._updateBeforeCull = !this._customAabb;
        }
    }
}

// uniform name
const _colorUniformName = 'uSceneColorMap';
/**
 * A render pass implementing grab of a color buffer.
 *
 * @ignore
 */ class RenderPassColorGrab extends RenderPass {
    destroy() {
        super.destroy();
        this.releaseRenderTarget(this.colorRenderTarget);
    }
    shouldReallocate(targetRT, sourceTexture, sourceFormat) {
        // need to reallocate if format does not match
        const targetFormat = targetRT?.colorBuffer.format;
        if (targetFormat !== sourceFormat) {
            return true;
        }
        // need to reallocate if dimensions don't match
        const width = sourceTexture?.width || this.device.width;
        const height = sourceTexture?.height || this.device.height;
        return !targetRT || width !== targetRT.width || height !== targetRT.height;
    }
    allocateRenderTarget(renderTarget, sourceRenderTarget, device, format) {
        // allocate texture buffer
        const texture = new Texture(device, {
            name: _colorUniformName,
            format,
            width: sourceRenderTarget ? sourceRenderTarget.colorBuffer.width : device.width,
            height: sourceRenderTarget ? sourceRenderTarget.colorBuffer.height : device.height,
            mipmaps: true,
            minFilter: FILTER_LINEAR_MIPMAP_LINEAR,
            magFilter: FILTER_LINEAR,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE
        });
        if (renderTarget) {
            // if reallocating RT size, release previous framebuffer
            renderTarget.destroyFrameBuffers();
            // assign new texture
            renderTarget._colorBuffer = texture;
            renderTarget._colorBuffers = [
                texture
            ];
        } else {
            // create new render target with the texture
            renderTarget = new RenderTarget({
                name: 'ColorGrabRT',
                colorBuffer: texture,
                depth: false,
                stencil: false,
                autoResolve: false
            });
        }
        return renderTarget;
    }
    releaseRenderTarget(rt) {
        if (rt) {
            rt.destroyTextureBuffers();
            rt.destroy();
        }
    }
    frameUpdate() {
        const device = this.device;
        // resize based on the source render target
        const sourceRt = this.source;
        const sourceFormat = sourceRt?.colorBuffer.format ?? this.device.backBufferFormat;
        // allocate / resize existing RT as needed
        if (this.shouldReallocate(this.colorRenderTarget, sourceRt?.colorBuffer, sourceFormat)) {
            this.releaseRenderTarget(this.colorRenderTarget);
            this.colorRenderTarget = this.allocateRenderTarget(this.colorRenderTarget, sourceRt, device, sourceFormat);
        }
        // assign uniform
        const colorBuffer = this.colorRenderTarget.colorBuffer;
        device.scope.resolve(_colorUniformName).setValue(colorBuffer);
    }
    execute() {
        // copy color from the current render target
        const device = this.device;
        DebugGraphics.pushGpuMarker(device, 'GRAB-COLOR');
        const sourceRt = this.source;
        const colorBuffer = this.colorRenderTarget.colorBuffer;
        if (device.isWebGPU) {
            device.copyRenderTarget(sourceRt, this.colorRenderTarget, true, false);
            // generate mipmaps
            device.mipmapRenderer.generate(this.colorRenderTarget.colorBuffer.impl);
        } else {
            device.copyRenderTarget(sourceRt, this.colorRenderTarget, true, false);
            // generate mipmaps
            device.activeTexture(device.maxCombinedTextures - 1);
            device.bindTexture(colorBuffer);
            device.gl.generateMipmap(colorBuffer.impl._glTarget);
        }
        DebugGraphics.popGpuMarker(device);
    }
    constructor(...args){
        super(...args), this.colorRenderTarget = null, /**
     * The source render target to grab the color from.
     *
     * @type {RenderTarget|null}
     */ this.source = null;
    }
}

// uniform name
const _depthUniformName = 'uSceneDepthMap';
/**
 * A render pass implementing grab of a depth buffer, used on WebGL 2 and WebGPU devices.
 *
 * @ignore
 */ class RenderPassDepthGrab extends RenderPass {
    constructor(device, camera){
        super(device), this.depthRenderTarget = null, this.camera = null;
        this.camera = camera;
    }
    destroy() {
        super.destroy();
        this.releaseRenderTarget(this.depthRenderTarget);
    }
    shouldReallocate(targetRT, sourceTexture) {
        // need to reallocate if dimensions don't match
        const width = sourceTexture?.width || this.device.width;
        const height = sourceTexture?.height || this.device.height;
        return !targetRT || width !== targetRT.width || height !== targetRT.height;
    }
    allocateRenderTarget(renderTarget, sourceRenderTarget, device, format, isDepth) {
        // allocate texture buffer
        const texture = new Texture(device, {
            name: _depthUniformName,
            format,
            width: sourceRenderTarget ? sourceRenderTarget.colorBuffer.width : device.width,
            height: sourceRenderTarget ? sourceRenderTarget.colorBuffer.height : device.height,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE
        });
        if (renderTarget) {
            // if reallocating RT size, release previous framebuffer
            renderTarget.destroyFrameBuffers();
            // assign new texture
            if (isDepth) {
                renderTarget._depthBuffer = texture;
            } else {
                renderTarget._colorBuffer = texture;
                renderTarget._colorBuffers = [
                    texture
                ];
            }
        } else {
            // create new render target with the texture
            renderTarget = new RenderTarget({
                name: 'DepthGrabRT',
                colorBuffer: isDepth ? null : texture,
                depthBuffer: isDepth ? texture : null,
                depth: !isDepth,
                stencil: device.supportsStencil,
                autoResolve: false
            });
        }
        return renderTarget;
    }
    releaseRenderTarget(rt) {
        if (rt) {
            rt.destroyTextureBuffers();
            rt.destroy();
        }
    }
    before() {
        const camera = this.camera;
        const device = this.device;
        const destinationRt = camera?.renderTarget ?? device.backBuffer;
        let useDepthBuffer = true;
        let format = destinationRt.stencil ? PIXELFORMAT_DEPTHSTENCIL : PIXELFORMAT_DEPTH;
        if (device.isWebGPU) {
            const numSamples = destinationRt.samples;
            // when depth buffer is multi-sampled, instead of copying it out, we use custom shader to resolve it
            // to a R32F texture, used as a color attachment of the render target
            if (numSamples > 1) {
                format = PIXELFORMAT_R32F;
                useDepthBuffer = false;
            }
        }
        const sourceTexture = camera.renderTarget?.depthBuffer ?? camera.renderTarget?.colorBuffer;
        // allocate / resize existing RT as needed
        if (this.shouldReallocate(this.depthRenderTarget, sourceTexture)) {
            this.releaseRenderTarget(this.depthRenderTarget);
            this.depthRenderTarget = this.allocateRenderTarget(this.depthRenderTarget, camera.renderTarget, device, format, useDepthBuffer);
        }
        // assign uniform
        const colorBuffer = useDepthBuffer ? this.depthRenderTarget.depthBuffer : this.depthRenderTarget.colorBuffer;
        device.scope.resolve(_depthUniformName).setValue(colorBuffer);
    }
    execute() {
        const device = this.device;
        DebugGraphics.pushGpuMarker(device, 'GRAB-DEPTH');
        // WebGL2 multisampling depth handling: we resolve multi-sampled depth buffer to a single-sampled destination buffer.
        // We could use existing API and resolve depth first and then blit it to destination, but this avoids the extra copy.
        if (device.isWebGL2 && device.renderTarget.samples > 1) {
            // multi-sampled buffer
            const src = device.renderTarget.impl._glFrameBuffer;
            // single sampled destination buffer
            const dest = this.depthRenderTarget;
            device.renderTarget = dest;
            device.updateBegin();
            this.depthRenderTarget.impl.internalResolve(device, src, dest.impl._glFrameBuffer, this.depthRenderTarget, device.gl.DEPTH_BUFFER_BIT);
        } else {
            // copy depth
            device.copyRenderTarget(device.renderTarget, this.depthRenderTarget, false, true);
        }
        DebugGraphics.popGpuMarker(device);
    }
}

/**
 * Internal camera shader parameters, used to generate and use matching shaders.
 *
 * @ignore
 */ class CameraShaderParams {
    /**
     * The hash of the rendering parameters.
     *
     * @type {number}
     * @ignore
     */ get hash() {
        if (this._hash === undefined) {
            const key = `${this.gammaCorrection}_${this.toneMapping}_${this.srgbRenderTarget}_${this.fog}_${this.ssaoEnabled}_${this.sceneDepthMapLinear}`;
            this._hash = hashCode(key);
        }
        return this._hash;
    }
    get defines() {
        const defines = this._defines;
        if (this._definesDirty) {
            this._definesDirty = false;
            defines.clear();
            if (this._sceneDepthMapLinear) defines.set('SCENE_DEPTHMAP_LINEAR', true);
            defines.set('FOG', this._fog.toUpperCase());
            defines.set('TONEMAP', tonemapNames[this._toneMapping]);
            defines.set('GAMMA', gammaNames[this.shaderOutputGamma]);
        }
        return defines;
    }
    markDirty() {
        this._hash = undefined;
        this._definesDirty = true;
    }
    set fog(type) {
        if (this._fog !== type) {
            this._fog = type;
            this.markDirty();
        }
    }
    get fog() {
        return this._fog;
    }
    set ssaoEnabled(value) {
        if (this._ssaoEnabled !== value) {
            this._ssaoEnabled = value;
            this.markDirty();
        }
    }
    get ssaoEnabled() {
        return this._ssaoEnabled;
    }
    set gammaCorrection(value) {
        this._gammaCorrectionAssigned = true;
        if (this._gammaCorrection !== value) {
            this._gammaCorrection = value;
            this.markDirty();
        }
    }
    get gammaCorrection() {
        return this._gammaCorrection;
    }
    set toneMapping(value) {
        if (this._toneMapping !== value) {
            this._toneMapping = value;
            this.markDirty();
        }
    }
    get toneMapping() {
        return this._toneMapping;
    }
    set srgbRenderTarget(value) {
        if (this._srgbRenderTarget !== value) {
            this._srgbRenderTarget = value;
            this.markDirty();
        }
    }
    get srgbRenderTarget() {
        return this._srgbRenderTarget;
    }
    set sceneDepthMapLinear(value) {
        if (this._sceneDepthMapLinear !== value) {
            this._sceneDepthMapLinear = value;
            this.markDirty();
        }
    }
    get sceneDepthMapLinear() {
        return this._sceneDepthMapLinear;
    }
    /**
     * Returns {@link GAMMA_SRGB} if the shader code needs to output gamma corrected color, otherwise
     * returns {@link GAMMA_NONE}.
     *
     * @type {number}
     * @ignore
     */ get shaderOutputGamma() {
        // if gamma rendering is enabled, but the render target does not have sRGB format,
        // the shader needs to do the linear -> gamma conversion
        const gammaOutput = this._gammaCorrection === GAMMA_SRGB && !this._srgbRenderTarget;
        return gammaOutput ? GAMMA_SRGB : GAMMA_NONE;
    }
    constructor(){
        /** @private */ this._gammaCorrection = GAMMA_SRGB;
        /** @private */ this._toneMapping = TONEMAP_LINEAR;
        /** @private */ this._srgbRenderTarget = false;
        /** @private */ this._ssaoEnabled = false;
        /** @private */ this._fog = FOG_NONE;
        /** @private */ this._sceneDepthMapLinear = false;
        /**
     * Content of this class relevant to shader generation, which is supplied as defines for the
     * shader.
     *
     * @type {Map<string, string>}
     * @private
     */ this._defines = new Map();
        this._definesDirty = true;
    }
}

/**
 * @import { RenderPass } from '../platform/graphics/render-pass.js'
 * @import { FogParams } from './fog-params.js'
 * @import { ShaderPassInfo } from './shader-pass.js'
 */ // pre-allocated temp variables
const _deviceCoord = new Vec3();
const _halfSize = new Vec3();
const _point = new Vec3();
const _invViewProjMat = new Mat4();
const _frustumPoints = [
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3()
];
/**
 * A camera.
 *
 * @ignore
 */ let Camera$1 = class Camera {
    constructor(){
        /**
     * @type {ShaderPassInfo|null}
     */ this.shaderPassInfo = null;
        /**
     * @type {RenderPassColorGrab|null}
     */ this.renderPassColorGrab = null;
        /**
     * @type {RenderPass|null}
     */ this.renderPassDepthGrab = null;
        /**
     * The fog parameters.
     *
     * @type {FogParams|null}
     */ this.fogParams = null;
        /**
     * Shader parameters used to generate and use matching shaders.
     *
     * @type {CameraShaderParams}
     */ this.shaderParams = new CameraShaderParams();
        /**
     * Render passes used to render this camera. If empty, the camera will render using the default
     * render passes.
     *
     * @type {RenderPass[]}
     */ this.renderPasses = [];
        /** @type {number} */ this.jitter = 0;
        this._aspectRatio = 16 / 9;
        this._aspectRatioMode = ASPECT_AUTO;
        this._calculateProjection = null;
        this._calculateTransform = null;
        this._clearColor = new Color(0.75, 0.75, 0.75, 1);
        this._clearColorBuffer = true;
        this._clearDepth = 1;
        this._clearDepthBuffer = true;
        this._clearStencil = 0;
        this._clearStencilBuffer = true;
        this._cullFaces = true;
        this._farClip = 1000;
        this._flipFaces = false;
        this._fov = 45;
        this._frustumCulling = true;
        this._horizontalFov = false;
        this._layers = [
            LAYERID_WORLD,
            LAYERID_DEPTH,
            LAYERID_SKYBOX,
            LAYERID_UI,
            LAYERID_IMMEDIATE
        ];
        this._layersSet = new Set(this._layers);
        this._nearClip = 0.1;
        this._node = null;
        this._orthoHeight = 10;
        this._projection = PROJECTION_PERSPECTIVE;
        this._rect = new Vec4(0, 0, 1, 1);
        this._renderTarget = null;
        this._scissorRect = new Vec4(0, 0, 1, 1);
        this._scissorRectClear = false; // by default rect is used when clearing. this allows scissorRect to be used when clearing.
        this._aperture = 16.0;
        this._shutter = 1.0 / 1000.0;
        this._sensitivity = 1000;
        this._projMat = new Mat4();
        this._projMatDirty = true;
        this._projMatSkybox = new Mat4(); // projection matrix used by skybox rendering shader is always perspective
        this._viewMat = new Mat4();
        this._viewMatDirty = true;
        this._viewProjMat = new Mat4();
        this._viewProjMatDirty = true;
        // storage of actual matrices used by the shaders, needed by TAA
        this._shaderMatricesVersion = 0;
        this._viewProjInverse = new Mat4(); // inverse view projection matrix from the current frame
        this._viewProjCurrent = null; // view projection matrix from the current frame
        this._viewProjPrevious = new Mat4(); // view projection matrix from the previous frame
        this._jitters = [
            0,
            0,
            0,
            0
        ]; // jitter values for TAA, 0-1 - current frame, 2-3 - previous frame
        this.frustum = new Frustum();
        // Set by XrManager
        this._xr = null;
        this._xrProperties = {
            horizontalFov: this._horizontalFov,
            fov: this._fov,
            aspectRatio: this._aspectRatio,
            farClip: this._farClip,
            nearClip: this._nearClip
        };
    }
    destroy() {
        this.renderPassColorGrab?.destroy();
        this.renderPassColorGrab = null;
        this.renderPassDepthGrab?.destroy();
        this.renderPassDepthGrab = null;
        this.renderPasses.length = 0;
    }
    /**
     * Store camera matrices required by TAA. Only update them once per frame.
     */ _storeShaderMatrices(viewProjMat, jitterX, jitterY, renderVersion) {
        if (this._shaderMatricesVersion !== renderVersion) {
            this._shaderMatricesVersion = renderVersion;
            this._viewProjPrevious.copy(this._viewProjCurrent ?? viewProjMat);
            this._viewProjCurrent ??= new Mat4();
            this._viewProjCurrent.copy(viewProjMat);
            this._viewProjInverse.invert(viewProjMat);
            this._jitters[2] = this._jitters[0];
            this._jitters[3] = this._jitters[1];
            this._jitters[0] = jitterX;
            this._jitters[1] = jitterY;
        }
    }
    /**
     * True if the camera clears the full render target. (viewport / scissor are full size)
     */ get fullSizeClearRect() {
        const rect = this._scissorRectClear ? this.scissorRect : this._rect;
        return rect.x === 0 && rect.y === 0 && rect.z === 1 && rect.w === 1;
    }
    set aspectRatio(newValue) {
        if (this._aspectRatio !== newValue) {
            this._aspectRatio = newValue;
            this._projMatDirty = true;
        }
    }
    get aspectRatio() {
        return this.xr?.active ? this._xrProperties.aspectRatio : this._aspectRatio;
    }
    set aspectRatioMode(newValue) {
        if (this._aspectRatioMode !== newValue) {
            this._aspectRatioMode = newValue;
            this._projMatDirty = true;
        }
    }
    get aspectRatioMode() {
        return this._aspectRatioMode;
    }
    set calculateProjection(newValue) {
        this._calculateProjection = newValue;
        this._projMatDirty = true;
    }
    get calculateProjection() {
        return this._calculateProjection;
    }
    set calculateTransform(newValue) {
        this._calculateTransform = newValue;
    }
    get calculateTransform() {
        return this._calculateTransform;
    }
    set clearColor(newValue) {
        this._clearColor.copy(newValue);
    }
    get clearColor() {
        return this._clearColor;
    }
    set clearColorBuffer(newValue) {
        this._clearColorBuffer = newValue;
    }
    get clearColorBuffer() {
        return this._clearColorBuffer;
    }
    set clearDepth(newValue) {
        this._clearDepth = newValue;
    }
    get clearDepth() {
        return this._clearDepth;
    }
    set clearDepthBuffer(newValue) {
        this._clearDepthBuffer = newValue;
    }
    get clearDepthBuffer() {
        return this._clearDepthBuffer;
    }
    set clearStencil(newValue) {
        this._clearStencil = newValue;
    }
    get clearStencil() {
        return this._clearStencil;
    }
    set clearStencilBuffer(newValue) {
        this._clearStencilBuffer = newValue;
    }
    get clearStencilBuffer() {
        return this._clearStencilBuffer;
    }
    set cullFaces(newValue) {
        this._cullFaces = newValue;
    }
    get cullFaces() {
        return this._cullFaces;
    }
    set farClip(newValue) {
        if (this._farClip !== newValue) {
            this._farClip = newValue;
            this._projMatDirty = true;
        }
    }
    get farClip() {
        return this.xr?.active ? this._xrProperties.farClip : this._farClip;
    }
    set flipFaces(newValue) {
        this._flipFaces = newValue;
    }
    get flipFaces() {
        return this._flipFaces;
    }
    set fov(newValue) {
        if (this._fov !== newValue) {
            this._fov = newValue;
            this._projMatDirty = true;
        }
    }
    get fov() {
        return this.xr?.active ? this._xrProperties.fov : this._fov;
    }
    set frustumCulling(newValue) {
        this._frustumCulling = newValue;
    }
    get frustumCulling() {
        return this._frustumCulling;
    }
    set horizontalFov(newValue) {
        if (this._horizontalFov !== newValue) {
            this._horizontalFov = newValue;
            this._projMatDirty = true;
        }
    }
    get horizontalFov() {
        return this.xr?.active ? this._xrProperties.horizontalFov : this._horizontalFov;
    }
    set layers(newValue) {
        this._layers = newValue.slice(0);
        this._layersSet = new Set(this._layers);
    }
    get layers() {
        return this._layers;
    }
    get layersSet() {
        return this._layersSet;
    }
    set nearClip(newValue) {
        if (this._nearClip !== newValue) {
            this._nearClip = newValue;
            this._projMatDirty = true;
        }
    }
    get nearClip() {
        return this.xr?.active ? this._xrProperties.nearClip : this._nearClip;
    }
    set node(newValue) {
        this._node = newValue;
    }
    get node() {
        return this._node;
    }
    set orthoHeight(newValue) {
        if (this._orthoHeight !== newValue) {
            this._orthoHeight = newValue;
            this._projMatDirty = true;
        }
    }
    get orthoHeight() {
        return this._orthoHeight;
    }
    set projection(newValue) {
        if (this._projection !== newValue) {
            this._projection = newValue;
            this._projMatDirty = true;
        }
    }
    get projection() {
        return this._projection;
    }
    get projectionMatrix() {
        this._evaluateProjectionMatrix();
        return this._projMat;
    }
    set rect(newValue) {
        this._rect.copy(newValue);
    }
    get rect() {
        return this._rect;
    }
    set renderTarget(newValue) {
        this._renderTarget = newValue;
    }
    get renderTarget() {
        return this._renderTarget;
    }
    set scissorRect(newValue) {
        this._scissorRect.copy(newValue);
    }
    get scissorRect() {
        return this._scissorRect;
    }
    get viewMatrix() {
        if (this._viewMatDirty) {
            const wtm = this._node.getWorldTransform();
            this._viewMat.copy(wtm).invert();
            this._viewMatDirty = false;
        }
        return this._viewMat;
    }
    set aperture(newValue) {
        this._aperture = newValue;
    }
    get aperture() {
        return this._aperture;
    }
    set sensitivity(newValue) {
        this._sensitivity = newValue;
    }
    get sensitivity() {
        return this._sensitivity;
    }
    set shutter(newValue) {
        this._shutter = newValue;
    }
    get shutter() {
        return this._shutter;
    }
    set xr(newValue) {
        if (this._xr !== newValue) {
            this._xr = newValue;
            this._projMatDirty = true;
        }
    }
    get xr() {
        return this._xr;
    }
    /**
     * Creates a duplicate of the camera.
     *
     * @returns {Camera} A cloned Camera.
     */ clone() {
        return new Camera().copy(this);
    }
    /**
     * Copies one camera to another.
     *
     * @param {Camera} other - Camera to copy.
     * @returns {Camera} Self for chaining.
     */ copy(other) {
        // We aren't using the getters and setters because there is additional logic
        // around using WebXR in the getters for these properties so that functions
        // like screenToWorld work correctly with other systems like the UI input
        // system
        this._aspectRatio = other._aspectRatio;
        this._farClip = other._farClip;
        this._fov = other._fov;
        this._horizontalFov = other._horizontalFov;
        this._nearClip = other._nearClip;
        this._xrProperties.aspectRatio = other._xrProperties.aspectRatio;
        this._xrProperties.farClip = other._xrProperties.farClip;
        this._xrProperties.fov = other._xrProperties.fov;
        this._xrProperties.horizontalFov = other._xrProperties.horizontalFov;
        this._xrProperties.nearClip = other._xrProperties.nearClip;
        this.aspectRatioMode = other.aspectRatioMode;
        this.calculateProjection = other.calculateProjection;
        this.calculateTransform = other.calculateTransform;
        this.clearColor = other.clearColor;
        this.clearColorBuffer = other.clearColorBuffer;
        this.clearDepth = other.clearDepth;
        this.clearDepthBuffer = other.clearDepthBuffer;
        this.clearStencil = other.clearStencil;
        this.clearStencilBuffer = other.clearStencilBuffer;
        this.cullFaces = other.cullFaces;
        this.flipFaces = other.flipFaces;
        this.frustumCulling = other.frustumCulling;
        this.layers = other.layers;
        this.orthoHeight = other.orthoHeight;
        this.projection = other.projection;
        this.rect = other.rect;
        this.renderTarget = other.renderTarget;
        this.scissorRect = other.scissorRect;
        this.aperture = other.aperture;
        this.shutter = other.shutter;
        this.sensitivity = other.sensitivity;
        this.shaderPassInfo = other.shaderPassInfo;
        this.jitter = other.jitter;
        this._projMatDirty = true;
        return this;
    }
    _enableRenderPassColorGrab(device, enable) {
        if (enable) {
            if (!this.renderPassColorGrab) {
                this.renderPassColorGrab = new RenderPassColorGrab(device);
            }
        } else {
            this.renderPassColorGrab?.destroy();
            this.renderPassColorGrab = null;
        }
    }
    _enableRenderPassDepthGrab(device, renderer, enable) {
        if (enable) {
            if (!this.renderPassDepthGrab) {
                this.renderPassDepthGrab = new RenderPassDepthGrab(device, this);
            }
        } else {
            this.renderPassDepthGrab?.destroy();
            this.renderPassDepthGrab = null;
        }
    }
    _updateViewProjMat() {
        if (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) {
            this._viewProjMat.mul2(this.projectionMatrix, this.viewMatrix);
            this._viewProjMatDirty = false;
        }
    }
    /**
     * Convert a point from 3D world space to 2D canvas pixel space based on the camera's rect.
     *
     * @param {Vec3} worldCoord - The world space coordinate to transform.
     * @param {number} cw - The width of PlayCanvas' canvas element.
     * @param {number} ch - The height of PlayCanvas' canvas element.
     * @param {Vec3} [screenCoord] - 3D vector to receive screen coordinate result.
     * @returns {Vec3} The screen space coordinate.
     */ worldToScreen(worldCoord, cw, ch, screenCoord = new Vec3()) {
        this._updateViewProjMat();
        this._viewProjMat.transformPoint(worldCoord, screenCoord);
        // calculate w co-coord
        const vpm = this._viewProjMat.data;
        const w = worldCoord.x * vpm[3] + worldCoord.y * vpm[7] + worldCoord.z * vpm[11] + 1 * vpm[15];
        // convert normalized clip space to screen space [0, 1]
        screenCoord.x = (screenCoord.x / w + 1) * 0.5;
        screenCoord.y = (1 - screenCoord.y / w) * 0.5;
        // convert screen space [0, 1] to pixel space based on camera rect
        const { x: rx, y: ry, z: rw, w: rh } = this._rect;
        screenCoord.x = screenCoord.x * rw * cw + rx * cw;
        screenCoord.y = screenCoord.y * rh * ch + (1 - ry - rh) * ch;
        return screenCoord;
    }
    /**
     * Convert a point from 2D canvas pixel space to 3D world space based on the camera's rect.
     *
     * @param {number} x - X coordinate on PlayCanvas' canvas element.
     * @param {number} y - Y coordinate on PlayCanvas' canvas element.
     * @param {number} z - The distance from the camera in world space to create the new point.
     * @param {number} cw - The width of PlayCanvas' canvas element.
     * @param {number} ch - The height of PlayCanvas' canvas element.
     * @param {Vec3} [worldCoord] - 3D vector to receive world coordinate result.
     * @returns {Vec3} The world space coordinate.
     */ screenToWorld(x, y, z, cw, ch, worldCoord = new Vec3()) {
        // Calculate the screen click as a point on the far plane of the normalized device coordinate 'box' (z=1)
        const { x: rx, y: ry, z: rw, w: rh } = this._rect;
        const range = this.farClip - this.nearClip;
        _deviceCoord.set((x - rx * cw) / (rw * cw), 1 - (y - (1 - ry - rh) * ch) / (rh * ch), z / range);
        _deviceCoord.mulScalar(2);
        _deviceCoord.sub(Vec3.ONE);
        if (this._projection === PROJECTION_PERSPECTIVE) {
            // calculate half width and height at the near clip plane
            Mat4._getPerspectiveHalfSize(_halfSize, this.fov, this.aspectRatio, this.nearClip, this.horizontalFov);
            // scale by normalized screen coordinates
            _halfSize.x *= _deviceCoord.x;
            _halfSize.y *= _deviceCoord.y;
            // transform to world space
            const invView = this._node.getWorldTransform();
            _halfSize.z = -this.nearClip;
            invView.transformPoint(_halfSize, _point);
            // point along camera->_point ray at distance z from the camera
            const cameraPos = this._node.getPosition();
            worldCoord.sub2(_point, cameraPos);
            worldCoord.normalize();
            worldCoord.mulScalar(z);
            worldCoord.add(cameraPos);
        } else {
            this._updateViewProjMat();
            _invViewProjMat.copy(this._viewProjMat).invert();
            // Transform to world space
            _invViewProjMat.transformPoint(_deviceCoord, worldCoord);
        }
        return worldCoord;
    }
    _evaluateProjectionMatrix() {
        if (this._projMatDirty) {
            if (this._projection === PROJECTION_PERSPECTIVE) {
                this._projMat.setPerspective(this.fov, this.aspectRatio, this.nearClip, this.farClip, this.horizontalFov);
                this._projMatSkybox.copy(this._projMat);
            } else {
                const y = this._orthoHeight;
                const x = y * this.aspectRatio;
                this._projMat.setOrtho(-x, x, -y, y, this.nearClip, this.farClip);
                this._projMatSkybox.setPerspective(this.fov, this.aspectRatio, this.nearClip, this.farClip);
            }
            this._projMatDirty = false;
        }
    }
    getProjectionMatrixSkybox() {
        this._evaluateProjectionMatrix();
        return this._projMatSkybox;
    }
    getExposure() {
        const ev100 = Math.log2(this._aperture * this._aperture / this._shutter * 100.0 / this._sensitivity);
        return 1.0 / (Math.pow(2.0, ev100) * 1.2);
    }
    // returns estimated size of the sphere on the screen in range of [0..1]
    // 0 - infinitely small, 1 - full screen or larger
    getScreenSize(sphere) {
        if (this._projection === PROJECTION_PERSPECTIVE) {
            // camera to sphere distance
            const distance = this._node.getPosition().distance(sphere.center);
            // if we're inside the sphere
            if (distance < sphere.radius) {
                return 1;
            }
            // The view-angle of the bounding sphere rendered on screen
            const viewAngle = Math.asin(sphere.radius / distance);
            // This assumes the near clipping plane is at a distance of 1
            const sphereViewHeight = Math.tan(viewAngle);
            // The size of (half) the screen if the near clipping plane is at a distance of 1
            const screenViewHeight = Math.tan(this.fov / 2 * math.DEG_TO_RAD);
            // The ratio of the geometry's screen size compared to the actual size of the screen
            return Math.min(sphereViewHeight / screenViewHeight, 1);
        }
        // ortho
        return math.clamp(sphere.radius / this._orthoHeight, 0, 1);
    }
    /**
     * Returns an array of corners of the frustum of the camera in the local coordinate system of the camera.
     *
     * @param {number} [near] - Near distance for the frustum points. Defaults to the near clip distance of the camera.
     * @param {number} [far] - Far distance for the frustum points. Defaults to the far clip distance of the camera.
     * @returns {Vec3[]} - An array of corners, using a global storage space.
     */ getFrustumCorners(near = this.nearClip, far = this.farClip) {
        const fov = this.fov * Math.PI / 180.0;
        let x, y;
        if (this.projection === PROJECTION_PERSPECTIVE) {
            if (this.horizontalFov) {
                x = near * Math.tan(fov / 2.0);
                y = x / this.aspectRatio;
            } else {
                y = near * Math.tan(fov / 2.0);
                x = y * this.aspectRatio;
            }
        } else {
            y = this._orthoHeight;
            x = y * this.aspectRatio;
        }
        const points = _frustumPoints;
        points[0].x = x;
        points[0].y = -y;
        points[0].z = -near;
        points[1].x = x;
        points[1].y = y;
        points[1].z = -near;
        points[2].x = -x;
        points[2].y = y;
        points[2].z = -near;
        points[3].x = -x;
        points[3].y = -y;
        points[3].z = -near;
        if (this._projection === PROJECTION_PERSPECTIVE) {
            if (this.horizontalFov) {
                x = far * Math.tan(fov / 2.0);
                y = x / this.aspectRatio;
            } else {
                y = far * Math.tan(fov / 2.0);
                x = y * this.aspectRatio;
            }
        }
        points[4].x = x;
        points[4].y = -y;
        points[4].z = -far;
        points[5].x = x;
        points[5].y = y;
        points[5].z = -far;
        points[6].x = -x;
        points[6].y = y;
        points[6].z = -far;
        points[7].x = -x;
        points[7].y = -y;
        points[7].z = -far;
        return points;
    }
    /**
     * Sets XR camera properties that should be derived physical camera in {@link XrManager}.
     *
     * @param {object} [properties] - Properties object.
     * @param {number} [properties.aspectRatio] - Aspect ratio.
     * @param {number} [properties.farClip] - Far clip.
     * @param {number} [properties.fov] - Field of view.
     * @param {boolean} [properties.horizontalFov] - Enable horizontal field of view.
     * @param {number} [properties.nearClip] - Near clip.
     */ setXrProperties(properties) {
        Object.assign(this._xrProperties, properties);
        this._projMatDirty = true;
    }
};

const _viewMat$1 = new Mat4();
const _viewProjMat = new Mat4();
const _viewportMatrix = new Mat4();
// helper static class for shared functionality for shadow and cookie cameras used by the lights
class LightCamera {
    static{
        // camera rotation angles used when rendering cubemap faces
        this.pointLightRotations = [
            new Quat().setFromEulerAngles(0, 90, 180),
            new Quat().setFromEulerAngles(0, -90, 180),
            new Quat().setFromEulerAngles(90, 0, 0),
            new Quat().setFromEulerAngles(-90, 0, 0),
            new Quat().setFromEulerAngles(0, 180, 180),
            new Quat().setFromEulerAngles(0, 0, 180)
        ];
    }
    static create(name, lightType, face) {
        const camera = new Camera$1();
        camera.node = new GraphNode(name);
        camera.aspectRatio = 1;
        camera.aspectRatioMode = ASPECT_MANUAL;
        camera._scissorRectClear = true;
        // set up constant settings based on light type
        switch(lightType){
            case LIGHTTYPE_OMNI:
                camera.node.setRotation(LightCamera.pointLightRotations[face]);
                camera.fov = 90;
                camera.projection = PROJECTION_PERSPECTIVE;
                break;
            case LIGHTTYPE_SPOT:
                camera.projection = PROJECTION_PERSPECTIVE;
                break;
            case LIGHTTYPE_DIRECTIONAL:
                camera.projection = PROJECTION_ORTHOGRAPHIC;
                break;
        }
        return camera;
    }
    static{
        this._spotCookieCamera = null;
    }
    // temporary camera to calculate spot light cookie view-projection matrix when shadow matrix is not available
    // todo - unify the code with the shadow spot camera
    static evalSpotCookieMatrix(light) {
        let cookieCamera = LightCamera._spotCookieCamera;
        if (!cookieCamera) {
            cookieCamera = LightCamera.create('SpotCookieCamera', LIGHTTYPE_SPOT);
            LightCamera._spotCookieCamera = cookieCamera;
        }
        cookieCamera.fov = light._outerConeAngle * 2;
        const cookieNode = cookieCamera._node;
        cookieNode.setPosition(light._node.getPosition());
        cookieNode.setRotation(light._node.getRotation());
        cookieNode.rotateLocal(-90, 0, 0);
        _viewMat$1.setTRS(cookieNode.getPosition(), cookieNode.getRotation(), Vec3.ONE).invert();
        _viewProjMat.mul2(cookieCamera.projectionMatrix, _viewMat$1);
        const cookieMatrix = light.cookieMatrix;
        const rectViewport = light.atlasViewport;
        _viewportMatrix.setViewport(rectViewport.x, rectViewport.y, rectViewport.z, rectViewport.w);
        cookieMatrix.mul2(_viewportMatrix, _viewProjMat);
        return cookieMatrix;
    }
}

const tempVec3$1 = new Vec3();
const tempAreaLightSizes = new Float32Array(6);
const areaHalfAxisWidth = new Vec3(-0.5, 0, 0);
const areaHalfAxisHeight = new Vec3(0, 0, 0.5);
// format of the float texture data
const TextureIndexFloat = {
    POSITION_RANGE: 0,
    DIRECTION_FLAGS: 1,
    COLOR_ANGLES_BIAS: 2,
    PROJ_MAT_0: 3,
    ATLAS_VIEWPORT: 3,
    PROJ_MAT_1: 4,
    PROJ_MAT_2: 5,
    PROJ_MAT_3: 6,
    AREA_DATA_WIDTH: 7,
    AREA_DATA_HEIGHT: 8,
    // leave last
    COUNT: 9
};
// enums supplied to the shader as inject-defines
const enums = {
    'LIGHTSHAPE_PUNCTUAL': `${LIGHTSHAPE_PUNCTUAL}u`,
    'LIGHTSHAPE_RECT': `${LIGHTSHAPE_RECT}u`,
    'LIGHTSHAPE_DISK': `${LIGHTSHAPE_DISK}u`,
    'LIGHTSHAPE_SPHERE': `${LIGHTSHAPE_SPHERE}u`,
    'LIGHT_COLOR_DIVIDER': `${LIGHT_COLOR_DIVIDER}.0`
};
// converts object with properties to a list of these as an example: "#define {CLUSTER_TEXTURE_8_BLAH} 1"
const buildShaderDefines = (object, prefix)=>{
    return Object.keys(object).map((key)=>`#define {${prefix}${key}} ${object[key]}`).join('\n');
};
// create a shader chunk with defines for the light buffer textures
const lightBufferDefines = `\n
    ${buildShaderDefines(TextureIndexFloat, 'CLUSTER_TEXTURE_')}
    ${buildShaderDefines(enums, '')}
`;
// A class used by clustered lighting, responsible for encoding light properties into textures for the use on the GPU
class LightsBuffer {
    constructor(device){
        this.areaLightsEnabled = false;
        this.device = device;
        // shader chunk with defines
        ShaderChunks.get(device, SHADERLANGUAGE_GLSL).set('lightBufferDefinesPS', lightBufferDefines);
        ShaderChunks.get(device, SHADERLANGUAGE_WGSL).set('lightBufferDefinesPS', lightBufferDefines);
        // features
        this.cookiesEnabled = false;
        this.shadowsEnabled = false;
        this.areaLightsEnabled = false;
        // using 8 bit index so this is maximum supported number of lights
        this.maxLights = 255;
        // float texture
        const pixelsPerLightFloat = TextureIndexFloat.COUNT;
        this.lightsFloat = new Float32Array(4 * pixelsPerLightFloat * this.maxLights);
        this.lightsUint = new Uint32Array(this.lightsFloat.buffer);
        this.lightsTexture = this.createTexture(this.device, pixelsPerLightFloat, this.maxLights, PIXELFORMAT_RGBA32F, 'LightsTexture');
        this._lightsTextureId = this.device.scope.resolve('lightsTexture');
        // compression ranges
        this.invMaxColorValue = 0;
        this.invMaxAttenuation = 0;
        this.boundsMin = new Vec3();
        this.boundsDelta = new Vec3();
    }
    destroy() {
        // release texture
        this.lightsTexture?.destroy();
        this.lightsTexture = null;
    }
    createTexture(device, width, height, format, name) {
        const tex = new Texture(device, {
            name: name,
            width: width,
            height: height,
            mipmaps: false,
            format: format,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            type: TEXTURETYPE_DEFAULT,
            magFilter: FILTER_NEAREST,
            minFilter: FILTER_NEAREST,
            anisotropy: 1
        });
        return tex;
    }
    setBounds(min, delta) {
        this.boundsMin.copy(min);
        this.boundsDelta.copy(delta);
    }
    uploadTextures() {
        this.lightsTexture.lock().set(this.lightsFloat);
        this.lightsTexture.unlock();
    }
    updateUniforms() {
        // texture
        this._lightsTextureId.setValue(this.lightsTexture);
    }
    getSpotDirection(direction, spot) {
        // Spots shine down the negative Y axis
        const mat = spot._node.getWorldTransform();
        mat.getY(direction).mulScalar(-1);
        direction.normalize();
    }
    // half sizes of area light in world space, returned as an array of 6 floats
    getLightAreaSizes(light) {
        const mat = light._node.getWorldTransform();
        mat.transformVector(areaHalfAxisWidth, tempVec3$1);
        tempAreaLightSizes[0] = tempVec3$1.x;
        tempAreaLightSizes[1] = tempVec3$1.y;
        tempAreaLightSizes[2] = tempVec3$1.z;
        mat.transformVector(areaHalfAxisHeight, tempVec3$1);
        tempAreaLightSizes[3] = tempVec3$1.x;
        tempAreaLightSizes[4] = tempVec3$1.y;
        tempAreaLightSizes[5] = tempVec3$1.z;
        return tempAreaLightSizes;
    }
    // fill up both float and 8bit texture data with light properties
    addLightData(light, lightIndex) {
        const isSpot = light._type === LIGHTTYPE_SPOT;
        const hasAtlasViewport = light.atlasViewportAllocated; // if the light does not have viewport, it does not fit to the atlas
        const isCookie = this.cookiesEnabled && !!light._cookie && hasAtlasViewport;
        const isArea = this.areaLightsEnabled && light.shape !== LIGHTSHAPE_PUNCTUAL;
        const castShadows = this.shadowsEnabled && light.castShadows && hasAtlasViewport;
        const pos = light._node.getPosition();
        let lightProjectionMatrix = null; // light projection matrix - used for shadow map and cookie of spot light
        let atlasViewport = null; // atlas viewport info - used for shadow map and cookie of omni light
        if (isSpot) {
            if (castShadows) {
                const lightRenderData = light.getRenderData(null, 0);
                lightProjectionMatrix = lightRenderData.shadowMatrix;
            } else if (isCookie) {
                lightProjectionMatrix = LightCamera.evalSpotCookieMatrix(light);
            }
        } else {
            if (castShadows || isCookie) {
                atlasViewport = light.atlasViewport;
            }
        }
        const dataFloat = this.lightsFloat;
        const dataUint = this.lightsUint;
        const dataFloatStart = lightIndex * this.lightsTexture.width * 4;
        // pos and range
        dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 0] = pos.x;
        dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 1] = pos.y;
        dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 2] = pos.z;
        dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 3] = light.attenuationEnd;
        // color, spot angles, biases
        const clusteredData = light.clusteredData;
        dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 0] = clusteredData[0];
        dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 1] = clusteredData[1];
        dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 2] = clusteredData[2];
        // biases (those are non-constant values, needs simplification)
        if (light.castShadows) {
            const lightRenderData = light.getRenderData(null, 0);
            const biases = light._getUniformBiasValues(lightRenderData);
            // store them in 32bits as half floats
            const biasHalf = FloatPacking.float2Half(biases.bias);
            const normalBiasHalf = FloatPacking.float2Half(biases.normalBias);
            dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 3] = biasHalf | normalBiasHalf << 16;
        }
        // spot direction
        if (isSpot) {
            this.getSpotDirection(tempVec3$1, light);
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 0] = tempVec3$1.x;
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 1] = tempVec3$1.y;
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 2] = tempVec3$1.z;
        // here we have unused float
        }
        // flags
        dataUint[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 3] = light.getClusteredFlags(castShadows, isCookie);
        // light projection matrix
        if (lightProjectionMatrix) {
            const matData = lightProjectionMatrix.data;
            for(let m = 0; m < 16; m++){
                dataFloat[dataFloatStart + 4 * TextureIndexFloat.PROJ_MAT_0 + m] = matData[m];
            }
        }
        if (atlasViewport) {
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 0] = atlasViewport.x;
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 1] = atlasViewport.y;
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 2] = atlasViewport.z / 3; // size of a face slot (3x3 grid)
        }
        // area light sizes
        if (isArea) {
            const areaSizes = this.getLightAreaSizes(light);
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 0] = areaSizes[0];
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 1] = areaSizes[1];
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 2] = areaSizes[2];
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 0] = areaSizes[3];
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 1] = areaSizes[4];
            dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 2] = areaSizes[5];
        }
    }
}

/**
 * @import { Texture } from '../../platform/graphics/texture.js'
 */ const tempVec3 = new Vec3();
const tempMin3 = new Vec3();
const tempMax3 = new Vec3();
const tempBox = new BoundingBox();
const maxTextureSize = 4096; // maximum texture size allowed to work on all devices
// helper class to store properties of a light used by clustering
class ClusterLight {
    constructor(){
        // the light itself
        this.light = null;
        // bounding box
        this.min = new Vec3();
        this.max = new Vec3();
    }
}
// Main class implementing clustered lighting. Internally it organizes the omni / spot lights placement in world space 3d cell structure,
// and also uses LightsBuffer class to store light properties in textures
class WorldClusters {
    constructor(device){
        this.device = device;
        this.name = 'Untitled';
        // number of times a warning was reported
        this.reportCount = 0;
        // bounds of all light volumes (volume covered by the clusters)
        this.boundsMin = new Vec3();
        this.boundsMax = new Vec3();
        this.boundsDelta = new Vec3();
        // number of cells along 3 axes
        this._cells = new Vec3(1, 1, 1); // number of cells
        this._cellsLimit = new Vec3(); // number of cells minus one
        this.cells = this._cells;
        // number of lights each cell can store
        this.maxCellLightCount = 4;
        // internal list of lights (of type ClusterLight)
        this._usedLights = [];
        // light 0 is always reserved for 'no light' index
        this._usedLights.push(new ClusterLight());
        // allocate textures to store lights
        this.lightsBuffer = new LightsBuffer(device);
        // register shader uniforms
        this.registerUniforms(device);
    }
    set maxCellLightCount(count) {
        if (count !== this._maxCellLightCount) {
            this._maxCellLightCount = count;
            this._cellsDirty = true;
        }
    }
    get maxCellLightCount() {
        return this._maxCellLightCount;
    }
    set cells(value) {
        // make sure we have whole numbers
        tempVec3.copy(value).floor();
        if (!this._cells.equals(tempVec3)) {
            this._cells.copy(tempVec3);
            this._cellsLimit.copy(tempVec3).sub(Vec3.ONE);
            this._cellsDirty = true;
        }
    }
    get cells() {
        return this._cells;
    }
    destroy() {
        this.lightsBuffer.destroy();
        this.releaseClusterTexture();
    }
    releaseClusterTexture() {
        if (this.clusterTexture) {
            this.clusterTexture.destroy();
            this.clusterTexture = null;
        }
    }
    registerUniforms(device) {
        this._clusterSkipId = device.scope.resolve('clusterSkip');
        this._clusterMaxCellsId = device.scope.resolve('clusterMaxCells');
        this._clusterWorldTextureId = device.scope.resolve('clusterWorldTexture');
        this._clusterTextureSizeId = device.scope.resolve('clusterTextureSize');
        this._clusterTextureSizeData = new Float32Array(3);
        this._clusterBoundsMinId = device.scope.resolve('clusterBoundsMin');
        this._clusterBoundsMinData = new Float32Array(3);
        this._clusterBoundsDeltaId = device.scope.resolve('clusterBoundsDelta');
        this._clusterBoundsDeltaData = new Float32Array(3);
        this._clusterCellsCountByBoundsSizeId = device.scope.resolve('clusterCellsCountByBoundsSize');
        this._clusterCellsCountByBoundsSizeData = new Float32Array(3);
        this._clusterCellsDotId = device.scope.resolve('clusterCellsDot');
        this._clusterCellsDotData = new Float32Array(3);
        // number of cells in each direction (vec3)
        this._clusterCellsMaxId = device.scope.resolve('clusterCellsMax');
        this._clusterCellsMaxData = new Float32Array(3);
    }
    // updates itself based on parameters stored in the scene
    updateParams(lightingParams) {
        if (lightingParams) {
            this.cells = lightingParams.cells;
            this.maxCellLightCount = lightingParams.maxLightsPerCell;
            this.lightsBuffer.cookiesEnabled = lightingParams.cookiesEnabled;
            this.lightsBuffer.shadowsEnabled = lightingParams.shadowsEnabled;
            this.lightsBuffer.areaLightsEnabled = lightingParams.areaLightsEnabled;
        }
    }
    updateCells() {
        if (this._cellsDirty) {
            this._cellsDirty = false;
            const cx = this._cells.x;
            const cy = this._cells.y;
            const cz = this._cells.z;
            // storing 1 light per pixel
            const numCells = cx * cy * cz;
            const totalPixels = this.maxCellLightCount * numCells;
            // cluster texture size - roughly square that fits all cells. The width is multiply of numPixels to simplify shader math
            let width = Math.ceil(Math.sqrt(totalPixels));
            width = math.roundUp(width, this.maxCellLightCount);
            const height = Math.ceil(totalPixels / width);
            // if the texture is allowed size
            Debug.assert(width <= maxTextureSize && height <= maxTextureSize, 'Clustered lights parameters cause the texture size to be over the limit, please adjust them.');
            // maximum range of cells
            this._clusterCellsMaxData[0] = cx;
            this._clusterCellsMaxData[1] = cy;
            this._clusterCellsMaxData[2] = cz;
            // vector to allow single dot product to convert from world coordinates to cluster index
            this._clusterCellsDotData[0] = this.maxCellLightCount;
            this._clusterCellsDotData[1] = cx * cz * this.maxCellLightCount;
            this._clusterCellsDotData[2] = cx * this.maxCellLightCount;
            // cluster data and number of lights per cell
            this.clusters = new Uint8ClampedArray(totalPixels);
            this.counts = new Int32Array(numCells);
            this._clusterTextureSizeData[0] = width;
            this._clusterTextureSizeData[1] = 1.0 / width;
            this._clusterTextureSizeData[2] = 1.0 / height;
            this.releaseClusterTexture();
            this.clusterTexture = this.lightsBuffer.createTexture(this.device, width, height, PIXELFORMAT_R8, 'ClusterTexture');
        }
    }
    uploadTextures() {
        this.clusterTexture.lock().set(this.clusters);
        this.clusterTexture.unlock();
        this.lightsBuffer.uploadTextures();
    }
    updateUniforms() {
        // skip clustered lights shader evaluation if only the dummy light exists
        this._clusterSkipId.setValue(this._usedLights.length > 1 ? 0 : 1);
        this.lightsBuffer.updateUniforms();
        // texture
        this._clusterWorldTextureId.setValue(this.clusterTexture);
        // uniform values
        this._clusterMaxCellsId.setValue(this.maxCellLightCount);
        const boundsDelta = this.boundsDelta;
        this._clusterCellsCountByBoundsSizeData[0] = this._cells.x / boundsDelta.x;
        this._clusterCellsCountByBoundsSizeData[1] = this._cells.y / boundsDelta.y;
        this._clusterCellsCountByBoundsSizeData[2] = this._cells.z / boundsDelta.z;
        this._clusterCellsCountByBoundsSizeId.setValue(this._clusterCellsCountByBoundsSizeData);
        this._clusterBoundsMinData[0] = this.boundsMin.x;
        this._clusterBoundsMinData[1] = this.boundsMin.y;
        this._clusterBoundsMinData[2] = this.boundsMin.z;
        this._clusterBoundsDeltaData[0] = boundsDelta.x;
        this._clusterBoundsDeltaData[1] = boundsDelta.y;
        this._clusterBoundsDeltaData[2] = boundsDelta.z;
        // assign values
        this._clusterTextureSizeId.setValue(this._clusterTextureSizeData);
        this._clusterBoundsMinId.setValue(this._clusterBoundsMinData);
        this._clusterBoundsDeltaId.setValue(this._clusterBoundsDeltaData);
        this._clusterCellsDotId.setValue(this._clusterCellsDotData);
        this._clusterCellsMaxId.setValue(this._clusterCellsMaxData);
    }
    // evaluates min and max coordinates of AABB of the light in the cell space
    evalLightCellMinMax(clusteredLight, min, max) {
        // min point of AABB in cell space
        min.copy(clusteredLight.min);
        min.sub(this.boundsMin);
        min.div(this.boundsDelta);
        min.mul2(min, this.cells);
        min.floor();
        // max point of AABB in cell space
        max.copy(clusteredLight.max);
        max.sub(this.boundsMin);
        max.div(this.boundsDelta);
        max.mul2(max, this.cells);
        max.ceil();
        // clamp to limits
        min.max(Vec3.ZERO);
        max.min(this._cellsLimit);
    }
    collectLights(lights) {
        const maxLights = this.lightsBuffer.maxLights;
        // skip index 0 as that is used for unused light
        const usedLights = this._usedLights;
        let lightIndex = 1;
        lights.forEach((light)=>{
            const runtimeLight = !!(light.mask & (MASK_AFFECT_DYNAMIC | MASK_AFFECT_LIGHTMAPPED));
            const zeroAngleSpotlight = light.type === LIGHTTYPE_SPOT && light._outerConeAngle === 0;
            if (light.enabled && light.type !== LIGHTTYPE_DIRECTIONAL && light.visibleThisFrame && light.intensity > 0 && runtimeLight && !zeroAngleSpotlight) {
                // within light limit
                if (lightIndex < maxLights) {
                    // reuse allocated spot
                    let clusteredLight;
                    if (lightIndex < usedLights.length) {
                        clusteredLight = usedLights[lightIndex];
                    } else {
                        // allocate new spot
                        clusteredLight = new ClusterLight();
                        usedLights.push(clusteredLight);
                    }
                    // store light properties
                    clusteredLight.light = light;
                    light.getBoundingBox(tempBox);
                    clusteredLight.min.copy(tempBox.getMin());
                    clusteredLight.max.copy(tempBox.getMax());
                    lightIndex++;
                } else {
                    Debug.warnOnce(`Clustered lighting: more than ${maxLights - 1} lights in the frame, ignoring some.`);
                }
            }
        });
        usedLights.length = lightIndex;
    }
    // evaluate the area all lights cover
    evaluateBounds() {
        const usedLights = this._usedLights;
        // bounds of the area the lights cover
        const min = this.boundsMin;
        const max = this.boundsMax;
        // if at least one light (index 0 is null, so ignore that one)
        if (usedLights.length > 1) {
            // AABB of the first light
            min.copy(usedLights[1].min);
            max.copy(usedLights[1].max);
            for(let i = 2; i < usedLights.length; i++){
                // expand by AABB of this light
                min.min(usedLights[i].min);
                max.max(usedLights[i].max);
            }
        } else {
            // any small volume if no lights
            min.set(0, 0, 0);
            max.set(1, 1, 1);
        }
        // bounds range
        this.boundsDelta.sub2(max, min);
        this.lightsBuffer.setBounds(min, this.boundsDelta);
    }
    updateClusters(lightingParams) {
        // clear clusters
        this.counts.fill(0);
        this.clusters.fill(0);
        this.lightsBuffer.areaLightsEnabled = lightingParams ? lightingParams.areaLightsEnabled : false;
        // local accessors
        const divX = this._cells.x;
        const divZ = this._cells.z;
        const counts = this.counts;
        const limit = this._maxCellLightCount;
        const clusters = this.clusters;
        const pixelsPerCellCount = this.maxCellLightCount;
        let tooManyLights = false;
        // started from index 1, zero is "no-light" index
        const usedLights = this._usedLights;
        for(let i = 1; i < usedLights.length; i++){
            const clusteredLight = usedLights[i];
            const light = clusteredLight.light;
            // add light data into textures
            this.lightsBuffer.addLightData(light, i);
            // light's bounds in cell space
            this.evalLightCellMinMax(clusteredLight, tempMin3, tempMax3);
            const xStart = tempMin3.x;
            const xEnd = tempMax3.x;
            const yStart = tempMin3.y;
            const yEnd = tempMax3.y;
            const zStart = tempMin3.z;
            const zEnd = tempMax3.z;
            // add the light to the cells
            for(let x = xStart; x <= xEnd; x++){
                for(let z = zStart; z <= zEnd; z++){
                    for(let y = yStart; y <= yEnd; y++){
                        const clusterIndex = x + divX * (z + y * divZ);
                        const count = counts[clusterIndex];
                        if (count < limit) {
                            clusters[pixelsPerCellCount * clusterIndex + count] = i;
                            counts[clusterIndex] = count + 1;
                        } else {
                            tooManyLights = true;
                        }
                    }
                }
            }
        }
        if (tooManyLights) {
            const reportLimit = 5;
            if (this.reportCount < reportLimit) {
                console.warn(`Too many lights in light cluster ${this.name}, please adjust parameters.${this.reportCount === reportLimit - 1 ? ' Giving up on reporting it.' : ''}`);
                this.reportCount++;
            }
        }
    }
    // internal update of the cluster data, executes once per frame
    update(lights, lightingParams = null) {
        this.updateParams(lightingParams);
        this.updateCells();
        this.collectLights(lights);
        this.evaluateBounds();
        this.updateClusters(lightingParams);
        this.uploadTextures();
    }
    // called on already updated clusters, activates for rendering by setting up uniforms / textures on the device
    activate() {
        this.updateUniforms();
    }
}

/**
 * @import { Vec2 } from './vec2.js'
 * @import { Vec3 } from './vec3.js'
 */ // golden angle in radians: PI * (3 - sqrt(5))
const _goldenAngle = 2.399963229728653;
/**
 * Random API.
 *
 * @namespace
 */ const random = {
    /**
     * Return a pseudo-random 2D point inside a unit circle with uniform distribution.
     *
     * @param {Vec2} point - The returned generated point.
     */ circlePoint (point) {
        const r = Math.sqrt(Math.random());
        const theta = Math.random() * 2 * Math.PI;
        point.x = r * Math.cos(theta);
        point.y = r * Math.sin(theta);
    },
    /**
     * Generates evenly distributed deterministic points inside a unit circle using Fermat's spiral
     * and Vogel's method.
     *
     * @param {Vec2} point - The returned generated point.
     * @param {number} index - Index of the point to generate, in the range from 0 to numPoints - 1.
     * @param {number} numPoints - The total number of points of the set.
     */ circlePointDeterministic (point, index, numPoints) {
        const theta = index * _goldenAngle;
        const r = Math.sqrt(index) / Math.sqrt(numPoints);
        point.x = r * Math.cos(theta);
        point.y = r * Math.sin(theta);
    },
    /**
     * Generates evenly distributed deterministic points on a unit sphere using Fibonacci sphere
     * algorithm. It also allows the points to cover only part of the sphere by specifying start
     * and end parameters, representing value from 0 (top of the sphere) and 1 (bottom of the
     * sphere). For example by specifying 0.4 and 0.6 and start and end, a band around the equator
     * would be generated.
     *
     * @param {Vec3} point - The returned generated point.
     * @param {number} index - Index of the point to generate, in the range from 0 to numPoints - 1.
     * @param {number} numPoints - The total number of points of the set.
     * @param {number} [start] - Part on the sphere along y axis to start the points, in the range
     * of 0 and 1. Defaults to 0.
     * @param {number} [end] - Part on the sphere along y axis to stop the points, in the range of
     * 0 and 1. Defaults to 1.
     */ spherePointDeterministic (point, index, numPoints, start = 0, end = 1) {
        // y coordinate needs to go from -1 (top) to 1 (bottom) for the full sphere
        // evaluate its value for this point and specified start and end
        start = 1 - 2 * start;
        end = 1 - 2 * end;
        const y = math.lerp(start, end, index / numPoints);
        // radius at y
        const radius = Math.sqrt(1 - y * y);
        // golden angle increment
        const theta = _goldenAngle * index;
        point.x = Math.cos(theta) * radius;
        point.y = y;
        point.z = Math.sin(theta) * radius;
    },
    /**
     * Generate a repeatable pseudo-random sequence using radical inverse. Based on
     * http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
     *
     * @param {number} i - The index in the sequence to return.
     * @returns {number} The pseudo-random value.
     */ radicalInverse (i) {
        let bits = (i << 16 | i >>> 16) >>> 0;
        bits = ((bits & 0x55555555) << 1 | (bits & 0xAAAAAAAA) >>> 1) >>> 0;
        bits = ((bits & 0x33333333) << 2 | (bits & 0xCCCCCCCC) >>> 2) >>> 0;
        bits = ((bits & 0x0F0F0F0F) << 4 | (bits & 0xF0F0F0F0) >>> 4) >>> 0;
        bits = ((bits & 0x00FF00FF) << 8 | (bits & 0xFF00FF00) >>> 8) >>> 0;
        return bits * 2.3283064365386963e-10;
    }
};

const decodeTable = {
    'linear': 'decodeLinear',
    'srgb': 'decodeGamma',
    'rgbm': 'decodeRGBM',
    'rgbe': 'decodeRGBE',
    'rgbp': 'decodeRGBP',
    'xy': 'unpackNormalXY',
    'xyz': 'unpackNormalXYZ'
};
const encodeTable = {
    'linear': 'encodeLinear',
    'srgb': 'encodeGamma',
    'rgbm': 'encodeRGBM',
    'rgbe': 'encodeRGBE',
    'rgbp': 'encodeRGBP'
};
class ChunkUtils {
    // returns the name of the decode function for the texture encoding
    static decodeFunc(encoding) {
        return decodeTable[encoding] ?? 'decodeGamma';
    }
    static encodeFunc(encoding) {
        return encodeTable[encoding] ?? 'encodeGamma';
    }
}

/**
 * @import { Vec4 } from '../../core/math/vec4.js'
 */ const getProjectionName = (projection)=>{
    switch(projection){
        case TEXTUREPROJECTION_CUBE:
            return 'Cubemap';
        case TEXTUREPROJECTION_OCTAHEDRAL:
            return 'Octahedral';
        default:
            return 'Equirect';
    }
};
// pack a 32bit floating point value into RGBA8
const packFloat32ToRGBA8 = (value, array, offset)=>{
    if (value <= 0) {
        array[offset + 0] = 0;
        array[offset + 1] = 0;
        array[offset + 2] = 0;
        array[offset + 3] = 0;
    } else if (value >= 1.0) {
        array[offset + 0] = 255;
        array[offset + 1] = 0;
        array[offset + 2] = 0;
        array[offset + 3] = 0;
    } else {
        let encX = 1 * value % 1;
        let encY = 255 * value % 1;
        let encZ = 65025 * value % 1;
        const encW = 16581375.0 * value % 1;
        encX -= encY / 255;
        encY -= encZ / 255;
        encZ -= encW / 255;
        array[offset + 0] = Math.min(255, Math.floor(encX * 256));
        array[offset + 1] = Math.min(255, Math.floor(encY * 256));
        array[offset + 2] = Math.min(255, Math.floor(encZ * 256));
        array[offset + 3] = Math.min(255, Math.floor(encW * 256));
    }
};
// pack samples into texture-ready format
const packSamples = (samples)=>{
    const numSamples = samples.length;
    const w = Math.min(numSamples, 512);
    const h = Math.ceil(numSamples / w);
    const data = new Uint8Array(w * h * 4);
    // normalize float data and pack into rgba8
    let off = 0;
    for(let i = 0; i < numSamples; i += 4){
        packFloat32ToRGBA8(samples[i + 0] * 0.5 + 0.5, data, off + 0);
        packFloat32ToRGBA8(samples[i + 1] * 0.5 + 0.5, data, off + 4);
        packFloat32ToRGBA8(samples[i + 2] * 0.5 + 0.5, data, off + 8);
        packFloat32ToRGBA8(samples[i + 3] / 8, data, off + 12);
        off += 16;
    }
    return {
        width: w,
        height: h,
        data: data
    };
};
// generate a vector on the hemisphere with constant distribution.
// function kept because it's useful for debugging
// vec3 hemisphereSampleUniform(vec2 uv) {
//     float phi = uv.y * 2.0 * PI;
//     float cosTheta = 1.0 - uv.x;
//     float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
//     return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
// }
// generate a vector on the hemisphere with phong reflection distribution
const hemisphereSamplePhong = (dstVec, x, y, specularPower)=>{
    const phi = y * 2 * Math.PI;
    const cosTheta = Math.pow(1 - x, 1 / (specularPower + 1));
    const sinTheta = Math.sqrt(1 - cosTheta * cosTheta);
    dstVec.set(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta).normalize();
};
// generate a vector on the hemisphere with lambert distribution
const hemisphereSampleLambert = (dstVec, x, y)=>{
    const phi = y * 2 * Math.PI;
    const cosTheta = Math.sqrt(1 - x);
    const sinTheta = Math.sqrt(x);
    dstVec.set(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta).normalize();
};
// generate a vector on the hemisphere with GGX distribution.
// a is linear roughness^2
const hemisphereSampleGGX = (dstVec, x, y, a)=>{
    const phi = y * 2 * Math.PI;
    const cosTheta = Math.sqrt((1 - x) / (1 + (a * a - 1) * x));
    const sinTheta = Math.sqrt(1 - cosTheta * cosTheta);
    dstVec.set(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta).normalize();
};
const D_GGX = (NoH, linearRoughness)=>{
    const a = NoH * linearRoughness;
    const k = linearRoughness / (1.0 - NoH * NoH + a * a);
    return k * k * (1 / Math.PI);
};
// generate precomputed samples for phong reflections of the given power
const generatePhongSamples = (numSamples, specularPower)=>{
    const H = new Vec3();
    const result = [];
    for(let i = 0; i < numSamples; ++i){
        hemisphereSamplePhong(H, i / numSamples, random.radicalInverse(i), specularPower);
        result.push(H.x, H.y, H.z, 0);
    }
    return result;
};
// generate precomputed samples for lambert convolution
const generateLambertSamples = (numSamples, sourceTotalPixels)=>{
    const pixelsPerSample = sourceTotalPixels / numSamples;
    const H = new Vec3();
    const result = [];
    for(let i = 0; i < numSamples; ++i){
        hemisphereSampleLambert(H, i / numSamples, random.radicalInverse(i));
        const pdf = H.z / Math.PI;
        const mipLevel = 0.5 * Math.log2(pixelsPerSample / pdf);
        result.push(H.x, H.y, H.z, mipLevel);
    }
    return result;
};
// print to the console the required samples table for GGX reflection convolution
// console.log(calculateRequiredSamplesGGX());
// this is a table with pre-calculated number of samples required for GGX.
// the table is generated by calculateRequiredSamplesGGX()
// the table is organized by [numSamples][specularPower]
//
// we use a repeatable pseudo-random sequence of numbers when generating samples
// for use in prefiltering GGX reflections. however not all the random samples
// will be valid. this is because some resulting reflection vectors will be below
// the hemisphere. this is especially apparent when calculating vectors for the
// higher roughnesses. (since vectors are more wild, more of them are invalid).
// for example, specularPower 2 results in half the generated vectors being
// invalid. (meaning the GPU would spend half the time on vectors that don't
// contribute to the final result).
//
// calculating how many samples are required to generate 'n' valid samples is a
// slow operation, so this table stores the pre-calculated numbers of samples
// required for the sets of (numSamples, specularPowers) pairs we expect to
// encounter at runtime.
const requiredSamplesGGX = {
    '16': {
        '2': 26,
        '8': 20,
        '32': 17,
        '128': 16,
        '512': 16
    },
    '32': {
        '2': 53,
        '8': 40,
        '32': 34,
        '128': 32,
        '512': 32
    },
    '128': {
        '2': 214,
        '8': 163,
        '32': 139,
        '128': 130,
        '512': 128
    },
    '1024': {
        '2': 1722,
        '8': 1310,
        '32': 1114,
        '128': 1041,
        '512': 1025
    }
};
// get the number of random samples required to generate numSamples valid samples.
const getRequiredSamplesGGX = (numSamples, specularPower)=>{
    const table = requiredSamplesGGX[numSamples];
    return table && table[specularPower] || numSamples;
};
// generate precomputed GGX samples
const generateGGXSamples = (numSamples, specularPower, sourceTotalPixels)=>{
    const pixelsPerSample = sourceTotalPixels / numSamples;
    const roughness = 1 - Math.log2(specularPower) / 11.0;
    const a = roughness * roughness;
    const H = new Vec3();
    const L = new Vec3();
    const N = new Vec3(0, 0, 1);
    const result = [];
    const requiredSamples = getRequiredSamplesGGX(numSamples, specularPower);
    for(let i = 0; i < requiredSamples; ++i){
        hemisphereSampleGGX(H, i / requiredSamples, random.radicalInverse(i), a);
        const NoH = H.z; // since N is (0, 0, 1)
        L.set(H.x, H.y, H.z).mulScalar(2 * NoH).sub(N);
        if (L.z > 0) {
            const pdf = D_GGX(Math.min(1, NoH), a) / 4 + 0.001;
            const mipLevel = 0.5 * Math.log2(pixelsPerSample / pdf);
            result.push(L.x, L.y, L.z, mipLevel);
        }
    }
    while(result.length < numSamples * 4){
        result.push(0, 0, 0, 0);
    }
    return result;
};
// pack float samples data into an rgba8 texture
const createSamplesTex = (device, name, samples)=>{
    const packedSamples = packSamples(samples);
    return new Texture(device, {
        name: name,
        width: packedSamples.width,
        height: packedSamples.height,
        mipmaps: false,
        minFilter: FILTER_NEAREST,
        magFilter: FILTER_NEAREST,
        levels: [
            packedSamples.data
        ]
    });
};
// simple cache storing key->value
// missFunc is called if the key is not present
class SimpleCache {
    constructor(destroyContent = true){
        this.map = new Map();
        this.destroyContent = destroyContent;
    }
    destroy() {
        if (this.destroyContent) {
            this.map.forEach((value, key)=>{
                value.destroy();
            });
        }
    }
    get(key, missFunc) {
        if (!this.map.has(key)) {
            const result = missFunc();
            this.map.set(key, result);
            return result;
        }
        return this.map.get(key);
    }
}
// cache, used to store samples. we store these separately from textures since multiple
// devices can use the same set of samples.
const samplesCache = new SimpleCache(false);
// cache, storing samples stored in textures, those are per device
const deviceCache$1 = new DeviceCache();
const getCachedTexture = (device, key, getSamplesFnc)=>{
    const cache = deviceCache$1.get(device, ()=>{
        return new SimpleCache();
    });
    return cache.get(key, ()=>{
        return createSamplesTex(device, key, samplesCache.get(key, getSamplesFnc));
    });
};
const generateLambertSamplesTex = (device, numSamples, sourceTotalPixels)=>{
    const key = `lambert-samples-${numSamples}-${sourceTotalPixels}`;
    return getCachedTexture(device, key, ()=>{
        return generateLambertSamples(numSamples, sourceTotalPixels);
    });
};
const generatePhongSamplesTex = (device, numSamples, specularPower)=>{
    const key = `phong-samples-${numSamples}-${specularPower}`;
    return getCachedTexture(device, key, ()=>{
        return generatePhongSamples(numSamples, specularPower);
    });
};
const generateGGXSamplesTex = (device, numSamples, specularPower, sourceTotalPixels)=>{
    const key = `ggx-samples-${numSamples}-${specularPower}-${sourceTotalPixels}`;
    return getCachedTexture(device, key, ()=>{
        return generateGGXSamples(numSamples, specularPower, sourceTotalPixels);
    });
};
/**
 * This function reprojects textures between cubemap, equirectangular and octahedral formats. The
 * function can read and write textures with pixel data in RGBE, RGBM, linear and sRGB formats.
 * When specularPower is specified it will perform a phong-weighted convolution of the source (for
 * generating a gloss maps).
 *
 * @param {Texture} source - The source texture.
 * @param {Texture} target - The target texture.
 * @param {object} [options] - The options object.
 * @param {number} [options.specularPower] - Optional specular power. When specular power is
 * specified, the source is convolved by a phong-weighted kernel raised to the specified power.
 * Otherwise the function performs a standard resample.
 * @param {number} [options.numSamples] - Optional number of samples (default is 1024).
 * @param {number} [options.face] - Optional cubemap face to update (default is update all faces).
 * @param {string} [options.distribution] - Specify convolution distribution - 'none', 'lambert',
 * 'phong', 'ggx'. Default depends on specularPower.
 * @param {Vec4} [options.rect] - Optional viewport rectangle.
 * @param {number} [options.seamPixels] - Optional number of seam pixels to render
 * @returns {boolean} True if the reprojection was applied and false otherwise (e.g. if rect is empty)
 * @category Graphics
 */ function reprojectTexture(source, target, options = {}) {
    Debug.assert(source instanceof Texture && target instanceof Texture, 'source and target must be textures');
    // calculate inner width and height
    const seamPixels = options.seamPixels ?? 0;
    const innerWidth = (options.rect?.z ?? target.width) - seamPixels * 2;
    const innerHeight = (options.rect?.w ?? target.height) - seamPixels * 2;
    if (innerWidth < 1 || innerHeight < 1) {
        // early out if inner space is empty
        return false;
    }
    // table of distribution -> function name
    const funcNames = {
        'none': 'reproject',
        'lambert': 'prefilterSamplesUnweighted',
        'phong': 'prefilterSamplesUnweighted',
        'ggx': 'prefilterSamples'
    };
    // extract options
    const specularPower = options.hasOwnProperty('specularPower') ? options.specularPower : 1;
    const face = options.hasOwnProperty('face') ? options.face : null;
    const distribution = options.hasOwnProperty('distribution') ? options.distribution : specularPower === 1 ? 'none' : 'phong';
    const processFunc = funcNames[distribution] || 'reproject';
    const prefilterSamples = processFunc.startsWith('prefilterSamples');
    const decodeFunc = ChunkUtils.decodeFunc(source.encoding);
    const encodeFunc = ChunkUtils.encodeFunc(target.encoding);
    const sourceFunc = `sample${getProjectionName(source.projection)}`;
    const targetFunc = `getDirection${getProjectionName(target.projection)}`;
    const numSamples = options.hasOwnProperty('numSamples') ? options.numSamples : 1024;
    // generate unique shader key
    const shaderKey = `ReprojectShader:${processFunc}_${decodeFunc}_${encodeFunc}_${sourceFunc}_${targetFunc}_${numSamples}`;
    const device = source.device;
    let shader = getProgramLibrary(device).getCachedShader(shaderKey);
    if (!shader) {
        const defines = new Map();
        if (prefilterSamples) defines.set('USE_SAMPLES_TEX', '');
        if (source.cubemap) defines.set('CUBEMAP_SOURCE', '');
        defines.set('{PROCESS_FUNC}', processFunc);
        defines.set('{DECODE_FUNC}', decodeFunc);
        defines.set('{ENCODE_FUNC}', encodeFunc);
        defines.set('{SOURCE_FUNC}', sourceFunc);
        defines.set('{TARGET_FUNC}', targetFunc);
        defines.set('{NUM_SAMPLES}', numSamples);
        defines.set('{NUM_SAMPLES_SQRT}', Math.round(Math.sqrt(numSamples)).toFixed(1));
        const wgsl = device.isWebGPU;
        const chunks = ShaderChunks.get(device, wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL);
        const includes = new Map();
        includes.set('decodePS', chunks.get('decodePS'));
        includes.set('encodePS', chunks.get('encodePS'));
        shader = ShaderUtils.createShader(device, {
            uniqueName: shaderKey,
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexChunk: 'reprojectVS',
            fragmentChunk: 'reprojectPS',
            fragmentIncludes: includes,
            fragmentDefines: defines
        });
    }
    DebugGraphics.pushGpuMarker(device, 'ReprojectTexture');
    // render state
    // TODO: set up other render state here to expected state
    device.setBlendState(BlendState.NOBLEND);
    const constantSource = device.scope.resolve(source.cubemap ? 'sourceCube' : 'sourceTex');
    Debug.assert(constantSource);
    constantSource.setValue(source);
    const constantParams = device.scope.resolve('params');
    const uvModParam = device.scope.resolve('uvMod');
    if (seamPixels > 0) {
        uvModParam.setValue([
            (innerWidth + seamPixels * 2) / innerWidth,
            (innerHeight + seamPixels * 2) / innerHeight,
            -seamPixels / innerWidth,
            -seamPixels / innerHeight
        ]);
    } else {
        uvModParam.setValue([
            1,
            1,
            0,
            0
        ]);
    }
    const params = [
        0,
        target.width * target.height * (target.cubemap ? 6 : 1),
        source.width * source.height * (source.cubemap ? 6 : 1)
    ];
    if (prefilterSamples) {
        // set or generate the pre-calculated samples data
        const sourceTotalPixels = source.width * source.height * (source.cubemap ? 6 : 1);
        const samplesTex = distribution === 'ggx' ? generateGGXSamplesTex(device, numSamples, specularPower, sourceTotalPixels) : distribution === 'lambert' ? generateLambertSamplesTex(device, numSamples, sourceTotalPixels) : generatePhongSamplesTex(device, numSamples, specularPower);
        device.scope.resolve('samplesTex').setValue(samplesTex);
        device.scope.resolve('samplesTexInverseSize').setValue([
            1.0 / samplesTex.width,
            1.0 / samplesTex.height
        ]);
    }
    for(let f = 0; f < (target.cubemap ? 6 : 1); f++){
        if (face === null || f === face) {
            const renderTarget = new RenderTarget({
                colorBuffer: target,
                face: f,
                depth: false,
                flipY: device.isWebGPU
            });
            params[0] = f;
            constantParams.setValue(params);
            drawQuadWithShader(device, renderTarget, shader, options?.rect);
            renderTarget.destroy();
        }
    }
    DebugGraphics.popGpuMarker(device);
    return true;
}

// calculate the number of mipmap levels given texture dimensions
const calcLevels = (width, height = 0)=>{
    return 1 + Math.floor(Math.log2(Math.max(width, height)));
};
const supportsFloat16 = (device)=>{
    return device.textureHalfFloatRenderable;
};
const supportsFloat32 = (device)=>{
    return device.textureFloatRenderable;
};
// lighting source should be stored HDR
const lightingSourcePixelFormat = (device)=>{
    return supportsFloat16(device) ? PIXELFORMAT_RGBA16F : supportsFloat32(device) ? PIXELFORMAT_RGBA32F : PIXELFORMAT_RGBA8;
};
// runtime lighting can be RGBM
const lightingPixelFormat = (device)=>{
    return PIXELFORMAT_RGBA8;
};
const createCubemap = (device, size, format, mipmaps)=>{
    return new Texture(device, {
        name: `lighting-${size}`,
        cubemap: true,
        width: size,
        height: size,
        format: format,
        type: TEXTURETYPE_RGBP ,
        addressU: ADDRESS_CLAMP_TO_EDGE,
        addressV: ADDRESS_CLAMP_TO_EDGE,
        mipmaps: false
    });
};
/**
 * Helper functions to support prefiltering lighting data.
 *
 * @ignore
 */ class EnvLighting {
    /**
     * Generate a skybox cubemap in the correct pixel format from the source texture.
     *
     * @param {Texture} source - The source texture. This is either a 2d texture in equirect format
     * or a cubemap.
     * @param {number} [size] - Size of the resulting texture. Otherwise use automatic sizing.
     * @returns {Texture} The resulting cubemap.
     */ static generateSkyboxCubemap(source, size) {
        const device = source.device;
        DebugGraphics.pushGpuMarker(device, 'genSkyboxCubemap');
        const result = createCubemap(device, size || (source.cubemap ? source.width : source.width / 4), PIXELFORMAT_RGBA8);
        reprojectTexture(source, result, {
            numSamples: 1024
        });
        DebugGraphics.popGpuMarker(device);
        return result;
    }
    /**
     * Create a texture in the format needed to precalculate lighting data.
     *
     * @param {Texture} source - The source texture. This is either a 2d texture in equirect format
     * or a cubemap.
     * @param {object} [options] - Specify generation options.
     * @param {Texture} [options.target] - The target texture. If one is not provided then a
     * new texture will be created and returned.
     * @param {number} [options.size] - Size of the lighting source cubemap texture. Only used
     * if target isn't specified. Defaults to 128.
     * @returns {Texture} The resulting cubemap.
     */ static generateLightingSource(source, options) {
        const device = source.device;
        DebugGraphics.pushGpuMarker(device, 'genLightingSource');
        const format = lightingSourcePixelFormat(device);
        const result = options?.target || new Texture(device, {
            name: 'lighting-source',
            cubemap: true,
            width: options?.size || 128,
            height: options?.size || 128,
            format: format,
            type: format === PIXELFORMAT_RGBA8 ? TEXTURETYPE_RGBP : TEXTURETYPE_DEFAULT,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            mipmaps: true
        });
        // copy into top level
        reprojectTexture(source, result, {
            numSamples: source.mipmaps ? 1 : 1024
        });
        DebugGraphics.popGpuMarker(device);
        // generate mipmaps
        return result;
    }
    /**
     * Generate the environment lighting atlas containing prefiltered reflections and ambient.
     *
     * @param {Texture} source - The source lighting texture, generated by generateLightingSource.
     * @param {object} [options] - Specify prefilter options.
     * @param {Texture} [options.target] - The target texture. If one is not provided then a
     * new texture will be created and returned.
     * @param {number} [options.size] - Size of the target texture to create. Only used if
     * target isn't specified. Defaults to 512.
     * @param {number} [options.numReflectionSamples] - Number of samples to use when generating
     * rough reflections. Defaults to 1024.
     * @param {number} [options.numAmbientSamples] - Number of samples to use when generating ambient
     * lighting. Defaults to 2048.
     * @returns {Texture} The resulting atlas
     */ static generateAtlas(source, options) {
        const device = source.device;
        const format = lightingPixelFormat();
        DebugGraphics.pushGpuMarker(device, 'genAtlas');
        const result = options?.target || new Texture(device, {
            name: 'envAtlas',
            width: options?.size || 512,
            height: options?.size || 512,
            format: format,
            type: TEXTURETYPE_RGBP ,
            projection: TEXTUREPROJECTION_EQUIRECT,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            mipmaps: false
        });
        DebugGraphics.pushGpuMarker(device, 'mipmaps');
        const s = result.width / 512;
        // generate mipmaps
        const rect = new Vec4(0, 0, 512 * s, 256 * s);
        const levels = calcLevels(256) - calcLevels(4);
        for(let i = 0; i < levels; ++i){
            reprojectTexture(source, result, {
                numSamples: 1,
                rect: rect,
                seamPixels: s
            });
            rect.x += rect.w;
            rect.y += rect.w;
            rect.z = Math.max(1, Math.floor(rect.z * 0.5));
            rect.w = Math.max(1, Math.floor(rect.w * 0.5));
        }
        DebugGraphics.popGpuMarker(device);
        DebugGraphics.pushGpuMarker(device, 'reflections');
        // generate blurry reflections
        rect.set(0, 256 * s, 256 * s, 128 * s);
        for(let i = 1; i < 7; ++i){
            reprojectTexture(source, result, {
                numSamples: options?.numReflectionSamples || 1024,
                distribution: options?.distribution || 'ggx',
                specularPower: Math.max(1, 2048 >> i * 2),
                rect: rect,
                seamPixels: s
            });
            rect.y += rect.w;
            rect.z = Math.max(1, Math.floor(rect.z * 0.5));
            rect.w = Math.max(1, Math.floor(rect.w * 0.5));
        }
        DebugGraphics.popGpuMarker(device);
        DebugGraphics.pushGpuMarker(device, 'ambient');
        // generate ambient
        rect.set(128 * s, (256 + 128) * s, 64 * s, 32 * s);
        reprojectTexture(source, result, {
            numSamples: options?.numAmbientSamples || 2048,
            distribution: 'lambert',
            rect: rect,
            seamPixels: s
        });
        DebugGraphics.popGpuMarker(device);
        DebugGraphics.popGpuMarker(device);
        return result;
    }
    /**
     * Generate the environment lighting atlas from prefiltered cubemap data.
     *
     * @param {Texture[]} sources - Array of 6 prefiltered textures.
     * @param {object} [options] - The options object
     * @param {Texture} [options.target] - The target texture. If one is not provided then a
     * new texture will be created and returned.
     * @param {number} [options.size] - Size of the target texture to create. Only used if
     * target isn't specified. Defaults to 512.
     * @param {boolean} [options.legacyAmbient] - Enable generating legacy ambient lighting.
     * Default is false.
     * @param {number} [options.numSamples] - Number of samples to use when generating ambient
     * lighting. Default is 2048.
     * @returns {Texture} The resulting atlas texture.
     */ static generatePrefilteredAtlas(sources, options) {
        const device = sources[0].device;
        const format = sources[0].format;
        const type = sources[0].type;
        DebugGraphics.pushGpuMarker(device, 'genPrefilteredAtlas');
        const result = options?.target || new Texture(device, {
            name: 'envPrefilteredAtlas',
            width: options?.size || 512,
            height: options?.size || 512,
            format: format,
            type: type,
            projection: TEXTUREPROJECTION_EQUIRECT,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            mipmaps: false
        });
        DebugGraphics.pushGpuMarker(device, 'mipmaps');
        const s = result.width / 512;
        // generate mipmaps
        const rect = new Vec4(0, 0, 512 * s, 256 * s);
        const levels = calcLevels(512);
        for(let i = 0; i < levels; ++i){
            reprojectTexture(sources[0], result, {
                numSamples: 1,
                rect: rect,
                seamPixels: s
            });
            rect.x += rect.w;
            rect.y += rect.w;
            rect.z = Math.max(1, Math.floor(rect.z * 0.5));
            rect.w = Math.max(1, Math.floor(rect.w * 0.5));
        }
        DebugGraphics.popGpuMarker(device);
        DebugGraphics.pushGpuMarker(device, 'reflections');
        // copy blurry reflections
        rect.set(0, 256 * s, 256 * s, 128 * s);
        for(let i = 1; i < sources.length; ++i){
            reprojectTexture(sources[i], result, {
                numSamples: 1,
                rect: rect,
                seamPixels: s
            });
            rect.y += rect.w;
            rect.z = Math.max(1, Math.floor(rect.z * 0.5));
            rect.w = Math.max(1, Math.floor(rect.w * 0.5));
        }
        DebugGraphics.popGpuMarker(device);
        DebugGraphics.pushGpuMarker(device, 'ambient');
        // generate ambient
        rect.set(128 * s, (256 + 128) * s, 64 * s, 32 * s);
        if (options?.legacyAmbient) {
            reprojectTexture(sources[5], result, {
                numSamples: 1,
                rect: rect,
                seamPixels: s
            });
        } else {
            reprojectTexture(sources[0], result, {
                numSamples: options?.numSamples || 2048,
                distribution: 'lambert',
                rect: rect,
                seamPixels: s
            });
        }
        DebugGraphics.popGpuMarker(device);
        DebugGraphics.popGpuMarker(device);
        return result;
    }
}

const chunkVersions = {
    // frontend
    aoPS: '1.57',
    clearCoatPS: '1.57',
    clearCoatGlossPS: '1.60',
    clearCoatNormalPS: '1.57',
    diffusePS: '1.57',
    emissivePS: '1.57',
    glossPS: '1.60',
    metalnessPS: '1.57',
    normalMapPS: '1.57',
    opacityPS: '1.57',
    parallaxPS: '1.57',
    sheenPS: '1.57',
    sheenGlossPS: '1.60',
    specularPS: '1.57',
    specularityFactorPS: '1.57',
    thicknessPS: '1.57',
    transmissionPS: '1.57',
    // backend
    normalVertexPS: '1.55',
    aoDiffuseOccPS: '1.62',
    aoSpecOccPS: '2.6',
    clusteredLightPS: '1.62',
    clusteredLightShadowPS: '1.62',
    combinePS: '1.62',
    falloffInvSquaredPS: '1.62',
    falloffLinearPS: '1.62',
    lightDiffuseLambertPS: '1.62',
    lightSheenPS: '1.62',
    lightSpecularAnisoGGXPS: '1.62',
    lightSpecularBlinnPS: '1.62',
    ltcPS: '1.62',
    reflDirPS: '1.62',
    reflDirAnisoPS: '1.62',
    reflectionCCPS: '1.62',
    reflectionCubePS: '2.6',
    reflectionEnvPS: '2.6',
    reflectionEnvHQPS: '2.6',
    reflectionSpherePS: '2.6',
    reflectionSheenPS: '1.62',
    shadowCommonPS: '1.62',
    shadowCoordPS: '1.62',
    shadowCoordPerspZBufferPS: '1.62',
    shadowEVSMPS: '1.62',
    spotPS: '1.62',
    TBNPS: '1.62',
    endPS: '1.65',
    metalnessModulatePS: '1.65',
    outputAlphaPS: '1.65',
    outputAlphaPremulPS: '1.65',
    fresnelSchlickPS: '1.65',
    iridescenceDiffractionPS: '1.65',
    lightmapAddPS: '1.65',
    refractionCubePS: '1.70',
    refractionDynamicPS: '1.70'
};
// removed
const removedChunks = {
    ambientPrefilteredCubePS: '1.51',
    ambientPrefilteredCubeLodPS: '1.51',
    dpAtlasQuadPS: '1.51',
    genParaboloidPS: '1.51',
    prefilterCubemapPS: '1.51',
    reflectionDpAtlasPS: '1.51',
    reflectionPrefilteredCubePS: '1.51',
    reflectionPrefilteredCubeLodPS: '1.51',
    refractionPS: '1.56',
    combineClearCoatPS: '1.56',
    combineDiffusePS: '1.56',
    combineDiffuseSpecularPS: '1.56',
    combineDiffuseSpecularNoReflPS: '1.56',
    combineDiffuseSpecularNoReflSeparateAmbientPS: '1.56',
    combineDiffuseSpecularOldPS: '1.56',
    lightmapSingleVertPS: '1.55',
    normalMapFastPS: '1.55',
    specularAaNonePS: '1.55',
    specularAaToksvigPS: '1.55',
    specularAaToksvigFastPS: '1.55',
    skyboxEnvPS: '2.1',
    skyboxHDRPS: '2.1',
    shadowVSM8PS: '2.3',
    fogExpPS: '2.5',
    fogExp2PS: '2.5',
    fogLinearPS: '2.5',
    fogNonePS: '2.5',
    gamma1_0PS: '2.5',
    gamma2_2PS: '2.5',
    storeEVSMPS: '2.6',
    shadowEVSMnPS: '2.6',
    shadowVSM_commonPS: '2.6',
    shadowStandardPS: '2.6',
    shadowStandardGL2PS: '2.6',
    startVS: '2.6',
    endVS: '2.6',
    baseVS: '2.6',
    baseNineSlicedVS: '2.6',
    viewNormalVS: '2.6',
    lightmapDirAddPS: '2.6',
    TBNObjectSpacePS: '2.6',
    TBNderivativePS: '2.6',
    startPS: '2.6',
    outputAlphaOpaque: '2.6',
    outputAlphaPremul: '2.6',
    cubeMapProjectBoxPS: '2.6',
    cubeMapProjectNonePS: '2.6',
    envMultiplyPS: '2.6',
    envConstPS: '2.6',
    aoSpecOccConstPS: '2.6',
    aoSpecOccConstSimplePS: '2.6',
    aoSpecOccSimplePS: '2.6',
    ambientConstantPS: '2.6',
    ambientEnvPS: '2.6',
    ambientSHPS: '2.6',
    shadowSampleCoordPS: '2.6',
    diffuseDetailMapPS: '2.7',
    normalDetailMapPS: '2.7',
    normalXYPS: '2.7',
    normalXYZPS: '2.7',
    aoDetailMapPS: '2.7',
    lightmapDirPS: '2.7',
    lightmapSinglePS: '2.7',
    tangentBinormalVS: '2.7'
};
// compare two "major.minor" semantic version strings and return true if a is a smaller version than b.
const semverLess = (a, b)=>{
    const aver = a.split('.').map((t)=>parseInt(t, 10));
    const bver = b.split('.').map((t)=>parseInt(t, 10));
    return aver[0] < bver[0] || aver[0] === bver[0] && aver[1] < bver[1];
};
/**
 * @param {Map<string, string>} userChunks - User-defined shader chunks, stored in a Map.
 * @param {string} userAPIVersion - The API version of the user-defined shader chunks.
 */ const validateUserChunks = (userChunks, userAPIVersion)=>{
    for (const chunkName of userChunks.keys()){
        if (removedChunks.hasOwnProperty(chunkName)) {
            const removedVersion = removedChunks[chunkName];
            Debug.warnOnce(`Shader chunk '${chunkName}' was removed in API ${removedVersion} and is no longer supported.`);
        } else {
            const engineAPIVersion = chunkVersions[chunkName];
            const chunkIsOutdated = engineAPIVersion && (!userAPIVersion || semverLess(userAPIVersion, engineAPIVersion));
            if (chunkIsOutdated) {
                Debug.warnOnce(`Shader chunk '${chunkName}' is API version ${engineAPIVersion}, but the supplied chunk is version ${userAPIVersion || 'undefined'}. Please update to the latest API: https://developer.playcanvas.com/user-manual/graphics/shaders/migrations/`);
            }
        }
    }
};

/**
 * @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js'
 * @import { LitShaderOptions } from './lit-shader-options.js'
 */ const builtinAttributes = {
    vertex_normal: SEMANTIC_NORMAL,
    vertex_tangent: SEMANTIC_TANGENT,
    vertex_texCoord0: SEMANTIC_TEXCOORD0,
    vertex_texCoord1: SEMANTIC_TEXCOORD1,
    vertex_color: SEMANTIC_COLOR,
    vertex_boneWeights: SEMANTIC_BLENDWEIGHT,
    vertex_boneIndices: SEMANTIC_BLENDINDICES
};
class LitShader {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {LitShaderOptions} options - The lit options.
     * @param {boolean} [allowWGSL] - Whether to allow WGSL shader language.
     */ constructor(device, options, allowWGSL = true){
        /**
     * Shader code representing varyings.
     *
     * @type {string}
     */ this.varyingsCode = '';
        /**
     * The vertex shader defines needed for the shader compilation.
     *
     * @type {Map<string, string>}
     */ this.vDefines = new Map();
        /**
     * The fragment shader defines needed for the shader compilation.
     *
     * @type {Map<string, string>}
     */ this.fDefines = new Map();
        /**
     * The vertex and fragment shader includes needed for the shader compilation.
     *
     * @type {Map<string, string>}
     */ this.includes = new Map();
        /**
     * The shader chunks to use for the shader generation.
     *
     * @type {Map<string, string>}
     */ this.chunks = null;
        this.device = device;
        this.options = options;
        // shader language
        const userChunks = options.shaderChunks;
        this.shaderLanguage = device.isWebGPU && allowWGSL && userChunks?.useWGSL ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL;
        // resolve custom chunk attributes
        this.attributes = {
            vertex_position: SEMANTIC_POSITION
        };
        if (options.userAttributes) {
            for (const [semantic, name] of Object.entries(options.userAttributes)){
                this.attributes[name] = semantic;
            }
        }
        // start with the default engine chunks
        const engineChunks = ShaderChunks.get(device, this.shaderLanguage);
        this.chunks = new Map(engineChunks);
        // optionally add user chunks
        if (userChunks) {
            const userChunkMap = this.shaderLanguage === SHADERLANGUAGE_GLSL ? userChunks.glsl : userChunks.wgsl;
            Debug.call(()=>{
                validateUserChunks(userChunkMap, userChunks.version);
            });
            userChunkMap.forEach((chunk, chunkName)=>{
                // extract attribute names from the used chunk
                for(const a in builtinAttributes){
                    if (builtinAttributes.hasOwnProperty(a) && chunk.indexOf(a) >= 0) {
                        this.attributes[a] = builtinAttributes[a];
                    }
                }
                // add user chunk
                this.chunks.set(chunkName, chunk);
            });
        }
        this.shaderPassInfo = ShaderPass.get(this.device).getByIndex(options.pass);
        this.shadowPass = this.shaderPassInfo.isShadow;
        this.lighting = options.lights.length > 0 || options.dirLightMapEnabled || options.clusteredLightingEnabled;
        this.reflections = options.reflectionSource !== REFLECTIONSRC_NONE;
        this.needsNormal = this.lighting || this.reflections || options.useSpecular || options.ambientSH || options.useHeights || options.enableGGXSpecular || options.clusteredLightingEnabled && !this.shadowPass || options.useClearCoatNormals;
        this.needsNormal = this.needsNormal && !this.shadowPass;
        this.needsSceneColor = options.useDynamicRefraction;
        this.needsScreenSize = options.useDynamicRefraction;
        this.needsTransforms = options.useDynamicRefraction;
        // generated by vshader
        this.vshader = null;
        // generated by fshader
        this.fshader = null;
    }
    /**
     * Helper function to define a value in the fragment shader.
     *
     * @param {boolean} condition - The define is added if the condition is true.
     * @param {string} name - The define name.
     * @param {string} [value] - The define value.
     */ fDefineSet(condition, name, value = '') {
        if (condition) {
            this.fDefines.set(name, value);
        }
    }
    /**
     * The function generates defines for the lit vertex shader, and handles required attributes and
     * varyings. The source code of the shader is supplied by litMainVS chunk. This vertex shader is
     * used for all render passes.
     *
     * @param {any} useUv - Info about used UVs.
     * @param {any} useUnmodifiedUv - Info about used unmodified UVs.
     * @param {any} mapTransforms - Info about used texture transforms.
     */ generateVertexShader(useUv, useUnmodifiedUv, mapTransforms) {
        const { options, vDefines, attributes } = this;
        // varyings
        const varyings = new Map();
        varyings.set('vPositionW', 'vec3');
        if (options.nineSlicedMode === SPRITE_RENDERMODE_SLICED || options.nineSlicedMode === SPRITE_RENDERMODE_TILED) {
            vDefines.set('NINESLICED', true);
        }
        if (this.options.linearDepth) {
            vDefines.set('LINEAR_DEPTH', true);
            varyings.set('vLinearDepth', 'float');
        }
        if (this.needsNormal) vDefines.set('NORMALS', true);
        if (this.options.useInstancing) {
            // only attach these if the default instancing chunk is used, otherwise it is expected
            // for the user to provide required attributes using material.setAttribute
            const languageChunks = ShaderChunks.get(this.device, this.shaderLanguage);
            if (this.chunks.get('transformInstancingVS') === languageChunks.get('transformInstancingVS')) {
                attributes.instance_line1 = SEMANTIC_ATTR11;
                attributes.instance_line2 = SEMANTIC_ATTR12;
                attributes.instance_line3 = SEMANTIC_ATTR14;
                attributes.instance_line4 = SEMANTIC_ATTR15;
            }
        }
        if (this.needsNormal) {
            attributes.vertex_normal = SEMANTIC_NORMAL;
            varyings.set('vNormalW', 'vec3');
            if (options.hasTangents && (options.useHeights || options.useNormals || options.useClearCoatNormals || options.enableGGXSpecular)) {
                vDefines.set('TANGENTS', true);
                attributes.vertex_tangent = SEMANTIC_TANGENT;
                varyings.set('vTangentW', 'vec3');
                varyings.set('vBinormalW', 'vec3');
            } else if (options.enableGGXSpecular) {
                vDefines.set('GGX_SPECULAR', true);
                varyings.set('vObjectSpaceUpW', 'vec3');
            }
        }
        const maxUvSets = 2;
        for(let i = 0; i < maxUvSets; i++){
            if (useUv[i]) {
                vDefines.set(`UV${i}`, true);
                attributes[`vertex_texCoord${i}`] = `TEXCOORD${i}`;
            }
            if (useUnmodifiedUv[i]) {
                vDefines.set(`UV${i}_UNMODIFIED`, true);
                varyings.set(`vUv${i}`, 'vec2');
            }
        }
        // prepare defines for texture transforms
        let numTransforms = 0;
        const transformDone = new Set();
        mapTransforms.forEach((mapTransform)=>{
            const { id, uv, name } = mapTransform;
            const checkId = id + uv * 100; // make sure each UV set is transformed by each unique transform only once
            if (!transformDone.has(checkId)) {
                transformDone.add(checkId);
                // register the varying
                varyings.set(`vUV${uv}_${id}`, 'vec2');
                // defines used by the included chunks
                const varName = `texture_${name}MapTransform`;
                vDefines.set(`{TRANSFORM_NAME_${numTransforms}}`, varName);
                vDefines.set(`{TRANSFORM_UV_${numTransforms}}`, uv);
                vDefines.set(`{TRANSFORM_ID_${numTransforms}}`, id);
                numTransforms++;
            }
        });
        // number of transforms, this drives the looped includes
        vDefines.set('UV_TRANSFORMS_COUNT', numTransforms);
        if (options.vertexColors) {
            attributes.vertex_color = SEMANTIC_COLOR;
            vDefines.set('VERTEX_COLOR', true);
            varyings.set('vVertexColor', 'vec4');
        }
        if (options.useMsdf && options.msdfTextAttribute) {
            attributes.vertex_outlineParameters = SEMANTIC_ATTR8;
            attributes.vertex_shadowParameters = SEMANTIC_ATTR9;
            vDefines.set('MSDF', true);
        }
        // morphing
        if (options.useMorphPosition || options.useMorphNormal) {
            vDefines.set('MORPHING', true);
            if (options.useMorphTextureBasedInt) vDefines.set('MORPHING_INT', true);
            if (options.useMorphPosition) vDefines.set('MORPHING_POSITION', true);
            if (options.useMorphNormal) vDefines.set('MORPHING_NORMAL', true);
            // vertex ids attributes
            attributes.morph_vertex_id = SEMANTIC_ATTR15;
        }
        if (options.skin) {
            attributes.vertex_boneIndices = SEMANTIC_BLENDINDICES;
            if (options.batch) {
                vDefines.set('BATCH', true);
            } else {
                attributes.vertex_boneWeights = SEMANTIC_BLENDWEIGHT;
                vDefines.set('SKIN', true);
            }
        }
        if (options.useInstancing) vDefines.set('INSTANCING', true);
        if (options.screenSpace) vDefines.set('SCREENSPACE', true);
        if (options.pixelSnap) vDefines.set('PIXELSNAP', true);
        // generate varyings code
        varyings.forEach((type, name)=>{
            this.varyingsCode += `#define VARYING_${name.toUpperCase()}\n`;
            this.varyingsCode += this.shaderLanguage === SHADERLANGUAGE_WGSL ? `varying ${name}: ${primitiveGlslToWgslTypeMap.get(type)};\n` : `varying ${type} ${name};\n`;
        });
        // varyings code exposed as an include
        this.includes.set('varyingsVS', this.varyingsCode);
        this.includes.set('varyingsPS', this.varyingsCode);
        this.vshader = `
            #include "litMainVS"
        `;
    }
    /**
     * Generate defines for lighting environment as well as individual lights.
     *
     * @param {boolean} hasAreaLights - Whether any of the lights are area lights.
     * @param {boolean} clusteredLightingEnabled - Whether clustered lighting is enabled.
     */ _setupLightingDefines(hasAreaLights, clusteredLightingEnabled) {
        const fDefines = this.fDefines;
        const options = this.options;
        this.fDefines.set('LIGHT_COUNT', options.lights.length);
        if (hasAreaLights) fDefines.set('AREA_LIGHTS', true);
        // clustered lights defines
        if (clusteredLightingEnabled && this.lighting) {
            fDefines.set('LIT_CLUSTERED_LIGHTS', true);
            if (options.clusteredLightingCookiesEnabled) fDefines.set('CLUSTER_COOKIES', true);
            if (options.clusteredLightingAreaLightsEnabled) fDefines.set('CLUSTER_AREALIGHTS', true);
            if (options.lightMaskDynamic) fDefines.set('CLUSTER_MESH_DYNAMIC_LIGHTS', true);
            // shadows
            if (options.clusteredLightingShadowsEnabled && !options.noShadow) {
                const clusteredShadowInfo = shadowTypeInfo.get(options.clusteredLightingShadowType);
                fDefines.set('CLUSTER_SHADOWS', true);
                fDefines.set(`SHADOW_KIND_${clusteredShadowInfo.kind}`, true);
                fDefines.set(`CLUSTER_SHADOW_TYPE_${clusteredShadowInfo.kind}`, true);
            }
        }
        // generate defines for all non-clustered lights
        for(let i = 0; i < options.lights.length; i++){
            const light = options.lights[i];
            const lightType = light._type;
            // when clustered lighting is enabled, skip non-directional lights
            if (clusteredLightingEnabled && lightType !== LIGHTTYPE_DIRECTIONAL) {
                continue;
            }
            const lightShape = hasAreaLights && light._shape ? light._shape : LIGHTSHAPE_PUNCTUAL;
            const shadowType = light._shadowType;
            const castShadow = light.castShadows && !options.noShadow;
            const shadowInfo = shadowTypeInfo.get(shadowType);
            Debug.assert(shadowInfo);
            // per light defines
            fDefines.set(`LIGHT${i}`, true);
            fDefines.set(`LIGHT${i}TYPE`, `${lightTypeNames[lightType]}`);
            fDefines.set(`LIGHT${i}SHADOWTYPE`, `${shadowInfo.name}`);
            fDefines.set(`LIGHT${i}SHAPE`, `${lightShapeNames[lightShape]}`);
            fDefines.set(`LIGHT${i}FALLOFF`, `${lightFalloffNames[light._falloffMode]}`);
            if (light.affectSpecularity) fDefines.set(`LIGHT${i}AFFECT_SPECULARITY`, true);
            if (light._cookie) {
                if (lightType === LIGHTTYPE_SPOT && !light._cookie._cubemap || lightType === LIGHTTYPE_OMNI && light._cookie._cubemap) {
                    fDefines.set(`LIGHT${i}COOKIE`, true);
                    fDefines.set(`{LIGHT${i}COOKIE_CHANNEL}`, light._cookieChannel);
                    if (lightType === LIGHTTYPE_SPOT) {
                        if (light._cookieTransform) fDefines.set(`LIGHT${i}COOKIE_TRANSFORM`, true);
                        if (light._cookieFalloff) fDefines.set(`LIGHT${i}COOKIE_FALLOFF`, true);
                    }
                }
            }
            if (castShadow) {
                fDefines.set(`LIGHT${i}CASTSHADOW`, true);
                if (shadowInfo.pcf) fDefines.set(`LIGHT${i}SHADOW_PCF`, true);
                // shadow addressing defines, used by lightFunctionShadowPS
                if (light._normalOffsetBias && !light._isVsm) fDefines.set(`LIGHT${i}_SHADOW_SAMPLE_NORMAL_OFFSET`, true);
                if (lightType === LIGHTTYPE_DIRECTIONAL) {
                    fDefines.set(`LIGHT${i}_SHADOW_SAMPLE_ORTHO`, true);
                    if (light.cascadeBlend > 0) fDefines.set(`LIGHT${i}_SHADOW_CASCADE_BLEND`, true);
                    if (light.numCascades > 1) fDefines.set(`LIGHT${i}_SHADOW_CASCADES`, true);
                }
                if (shadowInfo.pcf || shadowInfo.pcss || this.device.isWebGPU) fDefines.set(`LIGHT${i}_SHADOW_SAMPLE_SOURCE_ZBUFFER`, true);
                if (lightType === LIGHTTYPE_OMNI) fDefines.set(`LIGHT${i}_SHADOW_SAMPLE_POINT`, true);
            }
            // global lighting defines
            if (castShadow) {
                fDefines.set(`SHADOW_KIND_${shadowInfo.kind}`, true);
                if (lightType === LIGHTTYPE_DIRECTIONAL) fDefines.set('SHADOW_DIRECTIONAL', true);
            }
        }
    }
    prepareForwardPass(lightingUv) {
        const { options } = this;
        // area lights are used when clustered area lights are enabled or any lights have area shape
        const clusteredAreaLights = options.clusteredLightingEnabled && options.clusteredLightingAreaLightsEnabled;
        const hasAreaLights = clusteredAreaLights || options.lights.some((light)=>{
            return light._shape && light._shape !== LIGHTSHAPE_PUNCTUAL;
        });
        const addAmbient = !options.lightMapEnabled || options.lightMapWithoutAmbient;
        const hasTBN = this.needsNormal && (options.useNormals || options.useClearCoatNormals || options.enableGGXSpecular && !options.useHeights);
        if (options.useSpecular) {
            this.fDefineSet(true, 'LIT_SPECULAR');
            this.fDefineSet(this.reflections, 'LIT_REFLECTIONS');
            this.fDefineSet(options.useClearCoat, 'LIT_CLEARCOAT');
            this.fDefineSet(options.fresnelModel > 0, 'LIT_SPECULAR_FRESNEL');
            this.fDefineSet(options.useSheen, 'LIT_SHEEN');
            this.fDefineSet(options.useIridescence, 'LIT_IRIDESCENCE');
        }
        this.fDefineSet(this.lighting && options.useSpecular || this.reflections, 'LIT_SPECULAR_OR_REFLECTION');
        this.fDefineSet(this.needsSceneColor, 'LIT_SCENE_COLOR');
        this.fDefineSet(this.needsScreenSize, 'LIT_SCREEN_SIZE');
        this.fDefineSet(this.needsTransforms, 'LIT_TRANSFORMS');
        this.fDefineSet(this.needsNormal, 'LIT_NEEDS_NORMAL');
        this.fDefineSet(this.lighting, 'LIT_LIGHTING');
        this.fDefineSet(options.useMetalness, 'LIT_METALNESS');
        this.fDefineSet(options.enableGGXSpecular, 'LIT_GGX_SPECULAR');
        this.fDefineSet(options.useSpecularityFactor, 'LIT_SPECULARITY_FACTOR');
        this.fDefineSet(options.useCubeMapRotation, 'CUBEMAP_ROTATION');
        this.fDefineSet(options.occludeSpecularFloat, 'LIT_OCCLUDE_SPECULAR_FLOAT');
        this.fDefineSet(options.separateAmbient, 'LIT_SEPARATE_AMBIENT');
        this.fDefineSet(options.twoSidedLighting, 'LIT_TWO_SIDED_LIGHTING');
        this.fDefineSet(options.lightMapEnabled, 'LIT_LIGHTMAP');
        this.fDefineSet(options.dirLightMapEnabled, 'LIT_DIR_LIGHTMAP');
        this.fDefineSet(options.skyboxIntensity > 0, 'LIT_SKYBOX_INTENSITY');
        this.fDefineSet(options.clusteredLightingShadowsEnabled, 'LIT_CLUSTERED_SHADOWS');
        this.fDefineSet(options.clusteredLightingAreaLightsEnabled, 'LIT_CLUSTERED_AREA_LIGHTS');
        this.fDefineSet(hasTBN, 'LIT_TBN');
        this.fDefineSet(addAmbient, 'LIT_ADD_AMBIENT');
        this.fDefineSet(options.hasTangents, 'LIT_TANGENTS');
        this.fDefineSet(options.useNormals, 'LIT_USE_NORMALS');
        this.fDefineSet(options.useClearCoatNormals, 'LIT_USE_CLEARCOAT_NORMALS');
        this.fDefineSet(options.useRefraction, 'LIT_REFRACTION');
        this.fDefineSet(options.useDynamicRefraction, 'LIT_DYNAMIC_REFRACTION');
        this.fDefineSet(options.dispersion, 'LIT_DISPERSION');
        this.fDefineSet(options.useHeights, 'LIT_HEIGHTS');
        this.fDefineSet(options.opacityFadesSpecular, 'LIT_OPACITY_FADES_SPECULAR');
        this.fDefineSet(options.alphaToCoverage, 'LIT_ALPHA_TO_COVERAGE');
        this.fDefineSet(options.alphaTest, 'LIT_ALPHA_TEST');
        this.fDefineSet(options.useMsdf, 'LIT_MSDF');
        this.fDefineSet(options.ssao, 'LIT_SSAO');
        this.fDefineSet(options.useAo, 'LIT_AO');
        this.fDefineSet(options.occludeDirect, 'LIT_OCCLUDE_DIRECT');
        this.fDefineSet(options.msdfTextAttribute, 'LIT_MSDF_TEXT_ATTRIBUTE');
        this.fDefineSet(options.diffuseMapEnabled, 'LIT_DIFFUSE_MAP');
        this.fDefineSet(options.shadowCatcher, 'LIT_SHADOW_CATCHER');
        this.fDefineSet(true, 'LIT_FRESNEL_MODEL', fresnelNames[options.fresnelModel]);
        this.fDefineSet(true, 'LIT_NONE_SLICE_MODE', spriteRenderModeNames[options.nineSlicedMode]);
        this.fDefineSet(true, 'LIT_BLEND_TYPE', blendNames[options.blendType]);
        this.fDefineSet(true, 'LIT_CUBEMAP_PROJECTION', cubemaProjectionNames[options.cubeMapProjection]);
        this.fDefineSet(true, 'LIT_OCCLUDE_SPECULAR', specularOcclusionNames[options.occludeSpecular]);
        this.fDefineSet(true, 'LIT_REFLECTION_SOURCE', reflectionSrcNames[options.reflectionSource]);
        this.fDefineSet(true, 'LIT_AMBIENT_SOURCE', ambientSrcNames[options.ambientSource]);
        // injection defines
        this.fDefineSet(true, '{lightingUv}', lightingUv ?? ''); // example: vUV0_1
        this.fDefineSet(true, '{reflectionDecode}', ChunkUtils.decodeFunc(options.reflectionEncoding));
        this.fDefineSet(true, '{reflectionCubemapDecode}', ChunkUtils.decodeFunc(options.reflectionCubemapEncoding));
        this.fDefineSet(true, '{ambientDecode}', ChunkUtils.decodeFunc(options.ambientEncoding));
        // lighting defines
        this._setupLightingDefines(hasAreaLights, options.clusteredLightingEnabled);
    }
    prepareShadowPass() {
        const { options } = this;
        const lightType = this.shaderPassInfo.lightType;
        const shadowType = this.shaderPassInfo.shadowType;
        const shadowInfo = shadowTypeInfo.get(shadowType);
        Debug.assert(shadowInfo);
        // Use perspective depth for:
        // - Directional: Always since light has no position
        // - Spot: If not using VSM
        // - Point: Never
        const usePerspectiveDepth = lightType === LIGHTTYPE_DIRECTIONAL || !shadowInfo.vsm && lightType === LIGHTTYPE_SPOT;
        this.fDefineSet(usePerspectiveDepth, 'PERSPECTIVE_DEPTH');
        this.fDefineSet(true, 'LIGHT_TYPE', `${lightTypeNames[lightType]}`);
        this.fDefineSet(true, 'SHADOW_TYPE', `${shadowInfo.name}`);
        this.fDefineSet(options.alphaTest, 'LIT_ALPHA_TEST');
    }
    /**
     * Generates a fragment shader.
     *
     * @param {string} frontendDecl - Frontend declarations like `float dAlpha;`
     * @param {string} frontendCode - Frontend code containing `getOpacity()` etc.
     * @param {string} lightingUv - E.g. `vUv0`
     */ generateFragmentShader(frontendDecl, frontendCode, lightingUv) {
        const options = this.options;
        // generated code is exposed as an include
        this.includes.set('frontendDeclPS', frontendDecl ?? '');
        this.includes.set('frontendCodePS', frontendCode ?? '');
        if (options.pass === SHADER_PICK || options.pass === SHADER_PREPASS) ; else if (this.shadowPass) {
            this.prepareShadowPass();
        } else {
            this.prepareForwardPass(lightingUv);
        }
        this.fshader = `
            #include "litMainPS"
        `;
    }
}

/**
 * @import { ShaderChunks } from '../shader-chunks.js';
 */ /**
 * The lit shader options determines how the lit-shader gets generated. It specifies a set of
 * parameters which triggers different fragment and vertex shader generation in the backend.
 *
 * @category Graphics
 */ class LitShaderOptions {
    constructor(){
        this.hasTangents = false;
        /**
     * Custom shader chunks that will replace default ones.
     *
     * @type {ShaderChunks|null}
     */ this.shaderChunks = null;
        // one of the SHADER_ constants
        this.pass = 0;
        /**
     * Enable alpha testing. See {@link Material#alphaTest}.
     *
     * @type {boolean}
     */ this.alphaTest = false;
        /**
     * The value of {@link Material#blendType}.
     *
     * @type {number}
     */ this.blendType = BLEND_NONE;
        this.separateAmbient = false;
        this.screenSpace = false;
        this.skin = false;
        this.batch = false;
        /**
     * If hardware instancing compatible shader should be generated. Transform is read from
     * per-instance {@link VertexBuffer} instead of shader's uniforms.
     *
     * @type {boolean}
     */ this.useInstancing = false;
        /**
     * If morphing code should be generated to morph positions.
     *
     * @type {boolean}
     */ this.useMorphPosition = false;
        /**
     * If morphing code should be generated to morph normals.
     *
     * @type {boolean}
     */ this.useMorphNormal = false;
        this.useMorphTextureBasedInt = false;
        this.nineSlicedMode = 0;
        this.clusteredLightingEnabled = true;
        this.clusteredLightingCookiesEnabled = false;
        this.clusteredLightingShadowsEnabled = false;
        this.clusteredLightingShadowType = 0;
        this.clusteredLightingAreaLightsEnabled = false;
        this.vertexColors = false;
        this.lightMapEnabled = false;
        this.dirLightMapEnabled = false;
        this.useHeights = false;
        this.useNormals = false;
        this.useClearCoatNormals = false;
        this.useAo = false;
        this.diffuseMapEnabled = false;
        this.pixelSnap = false;
        /**
     * If ambient spherical harmonics are used. Ambient SH replace prefiltered cubemap ambient on
     * certain platforms (mostly Android) for performance reasons.
     *
     * @type {boolean}
     */ this.ambientSH = false;
        /**
     * Apply SSAO during the lighting.
     *
     * @type {boolean}
     */ this.ssao = false;
        /**
     * The value of {@link StandardMaterial#twoSidedLighting}.
     *
     * @type {boolean}
     */ this.twoSidedLighting = false;
        /**
     * The value of {@link StandardMaterial#occludeDirect}.
     *
     * @type {boolean}
     */ this.occludeDirect = false;
        /**
     * The value of {@link StandardMaterial#occludeSpecular}.
     *
     * @type {number}
     */ this.occludeSpecular = 0;
        /**
     * Defines if {@link StandardMaterial#occludeSpecularIntensity} constant should affect specular
     * occlusion.
     *
     * @type {boolean}
     */ this.occludeSpecularFloat = false;
        this.useMsdf = false;
        this.msdfTextAttribute = false;
        /**
     * Enable alpha to coverage. See {@link Material#alphaToCoverage}.
     *
     * @type {boolean}
     */ this.alphaToCoverage = false;
        /**
     * Enable specular fade. See {@link StandardMaterial#opacityFadesSpecular}.
     *
     * @type {boolean}
     */ this.opacityFadesSpecular = false;
        /**
     * Enable opacity dithering. See {@link StandardMaterial#opacityDither}.
     *
     * @type {string}
     */ this.opacityDither = DITHER_NONE;
        /**
     * Enable opacity shadow dithering. See {@link StandardMaterial#opacityShadowDither}.
     *
     * @type {string}
     */ this.opacityShadowDither = DITHER_NONE;
        /**
     * The value of {@link StandardMaterial#cubeMapProjection}.
     *
     * @type {number}
     */ this.cubeMapProjection = 0;
        /**
     * If any specular or reflections are needed at all.
     *
     * @type {boolean}
     */ this.useSpecular = false;
        this.useSpecularityFactor = false;
        this.enableGGXSpecular = false;
        /**
     * The value of {@link StandardMaterial#fresnelModel}.
     *
     * @type {number}
     */ this.fresnelModel = 0;
        /**
     * If refraction is used.
     *
     * @type {boolean}
     */ this.useRefraction = false;
        this.useClearCoat = false;
        this.useSheen = false;
        this.useIridescence = false;
        /**
     * The value of {@link StandardMaterial#useMetalness}.
     *
     * @type {boolean}
     */ this.useMetalness = false;
        this.useDynamicRefraction = false;
        this.dispersion = false;
        /**
     * The type of fog being applied in the shader. See {@link Scene#fog} for the list of possible
     * values.
     *
     * @type {string}
     */ this.fog = FOG_NONE;
        /**
     * The type of gamma correction being applied in the shader. See
     * {@link CameraComponent#gammaCorrection} for the list of possible values.
     *
     * @type {number}
     */ this.gamma = GAMMA_NONE;
        /**
     * The type of tone mapping being applied in the shader. See {@link CameraComponent#toneMapping}
     * for the list of possible values.
     *
     * @type {number}
     */ this.toneMap = -1;
        /**
     * One of REFLECTIONSRC_*** constants.
     *
     * @type {string}
     */ this.reflectionSource = REFLECTIONSRC_NONE;
        this.reflectionEncoding = null;
        this.reflectionCubemapEncoding = null;
        /**
     * One of "ambientSH", "envAtlas", "constant".
     *
     * @type {string}
     */ this.ambientSource = 'constant';
        this.ambientEncoding = null;
        // TODO: add a test for if non skybox cubemaps have rotation (when this is supported) - for now
        // assume no non-skybox cubemap rotation
        /**
     * Skybox intensity factor.
     *
     * @type {number}
     */ this.skyboxIntensity = 1.0;
        /**
     * If cube map rotation is enabled.
     *
     * @type {boolean}
     */ this.useCubeMapRotation = false;
        this.lightMapWithoutAmbient = false;
        this.lights = [];
        this.noShadow = false;
        this.lightMaskDynamic = 0x0;
        /**
     * Object containing a map of user defined vertex attributes to attached shader semantics.
     *
     * @type {Object<string, string>}
     */ this.userAttributes = {};
        /**
     * Make vLinearDepth available in the shader.
     *
     * @type {boolean}
     */ this.linearDepth = false;
        /**
     * Shader outputs the accumulated shadow value, used for shadow catcher materials.
     */ this.shadowCatcher = false;
    }
}

/**
 * The standard material options define a set of options used to control the shader frontend shader
 * generation, such as textures, tints and multipliers.
 *
 * @category Graphics
 */ class StandardMaterialOptions {
    // program-library assumes material options has a pass property
    get pass() {
        return this.litOptions.pass;
    }
    constructor(){
        /**
     * The set of defines used to generate the shader.
     *
     * @type {Map<string, string>}
     */ this.defines = new Map();
        /**
     * If UV1 (second set of texture coordinates) is required in the shader. Will be declared as
     * "vUv1" and passed to the fragment shader.
     *
     * @type {boolean}
     */ this.forceUv1 = false;
        /**
     * Defines if {@link StandardMaterial#specular} constant should affect specular color.
     *
     * @type {boolean}
     */ this.specularTint = false;
        /**
     * Defines if {@link StandardMaterial#metalness} constant should affect metalness value.
     *
     * @type {boolean}
     */ this.metalnessTint = false;
        /**
     * Defines if {@link StandardMaterial#gloss} constant should affect glossiness value.
     *
     * @type {boolean}
     */ this.glossTint = false;
        this.emissiveEncoding = 'linear';
        this.lightMapEncoding = 'linear';
        /**
     * If normal map contains X in RGB, Y in Alpha, and Z must be reconstructed.
     *
     * @type {boolean}
     */ this.packedNormal = false;
        /**
     * If normal detail map contains X in RGB, Y in Alpha, and Z must be reconstructed.
     *
     * @type {boolean}
     */ this.normalDetailPackedNormal = false;
        /**
     * If normal clear coat map contains X in RGB, Y in Alpha, and Z must be reconstructed.
     *
     * @type {boolean}
     */ this.clearCoatPackedNormal = false;
        /**
     * Invert the gloss channel.
     *
     * @type {boolean}
     */ this.glossInvert = false;
        /**
     * Invert the sheen gloss channel.
     *
     * @type {boolean}
     */ this.sheenGlossInvert = false;
        /**
     * Invert the clearcoat gloss channel.
     *
     * @type {boolean}
     */ this.clearCoatGlossInvert = false;
        /**
     * True to include AO variables even if AO is not used, which allows SSAO to be used in the lit shader.
     *
     * @type {boolean}
     */ this.useAO = false;
        /**
     * Storage for the options for lit the shader and material.
     *
     * @type {LitShaderOptions}
     */ this.litOptions = new LitShaderOptions();
    }
}

const LitOptionsUtils = {
    // generate a key for the lit options
    generateKey (options) {
        return `lit${Object.keys(options).sort().map((key)=>{
            if (key === 'shaderChunks') {
                return options.shaderChunks?.key ?? '';
            } else if (key === 'lights') {
                return LitOptionsUtils.generateLightsKey(options);
            }
            return key + options[key];
        }).join('\n')}`;
    },
    generateLightsKey (options) {
        return `lights:${options.lights.map((light)=>{
            return !options.clusteredLightingEnabled || light._type === LIGHTTYPE_DIRECTIONAL ? `${light.key},` : '';
        }).join('')}`;
    }
};

/**
 * @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js'
 */ const _matTex2D = [];
const buildPropertiesList = (options)=>{
    return Object.keys(options).filter((key)=>key !== 'litOptions').sort();
};
class ShaderGeneratorStandard extends ShaderGenerator {
    generateKey(options) {
        let props;
        if (options === this.optionsContextMin) {
            if (!this.propsMin) this.propsMin = buildPropertiesList(options);
            props = this.propsMin;
        } else if (options === this.optionsContext) {
            if (!this.props) this.props = buildPropertiesList(options);
            props = this.props;
        } else {
            props = buildPropertiesList(options);
        }
        const definesHash = ShaderGenerator.definesHash(options.defines);
        const key = `standard:\n${definesHash}\n${props.map((prop)=>prop + options[prop]).join('\n')}${LitOptionsUtils.generateKey(options.litOptions)}`;
        return key;
    }
    /**
     * Get the code with which to to replace '*_TEXTURE_UV' in the map shader functions.
     *
     * @param {string} transformPropName - Name of the transform id in the options block. Usually "basenameTransform".
     * @param {string} uVPropName - Name of the UV channel in the options block. Usually "basenameUv".
     * @param {object} options - The options passed into createShaderDefinition.
     * @returns {string} The code used to replace '*_TEXTURE_UV' in the shader code.
     * @private
     */ _getUvSourceExpression(transformPropName, uVPropName, options) {
        const transformId = options[transformPropName];
        const uvChannel = options[uVPropName];
        const isMainPass = options.litOptions.pass === SHADER_FORWARD;
        let expression;
        if (isMainPass && options.litOptions.nineSlicedMode === SPRITE_RENDERMODE_SLICED) {
            expression = 'nineSlicedUv';
        } else if (isMainPass && options.litOptions.nineSlicedMode === SPRITE_RENDERMODE_TILED) {
            expression = 'nineSlicedUv';
        } else {
            if (transformId === 0) {
                expression = `vUv${uvChannel}`;
            } else {
                // note: different capitalization!
                expression = `vUV${uvChannel}_${transformId}`;
            }
            // if heightmap is enabled all maps except the heightmap are offset
            if (options.heightMap && transformPropName !== 'heightMapTransform') {
                expression += ' + dUvOffset';
            }
        }
        return expression;
    }
    _validateMapChunk(propName, chunkName, chunks) {
        Debug.call(()=>{
            // strip comments from the chunk
            const code = chunks.get(chunkName).replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
            const requiredChangeStrings = [];
            // Helper function to add a formatted change string if the old syntax is found
            const trackChange = (oldSyntax, newSyntax)=>{
                if (code.includes(oldSyntax)) {
                    requiredChangeStrings.push(`  ${oldSyntax} -> ${newSyntax}`);
                }
            };
            // inject defines (with curly braces)
            [
                [
                    '$UV',
                    `{STD_${propName}_TEXTURE_UV}`
                ],
                [
                    '$CH',
                    `{STD_${propName}_TEXTURE_CHANNEL}`
                ],
                [
                    '$SAMPLER',
                    `{STD_${propName}_TEXTURE_NAME}`
                ],
                [
                    '$DECODE',
                    `{STD_${propName}_TEXTURE_DECODE}`
                ],
                [
                    '$VC',
                    `{STD_${propName}_VERTEX_CHANNEL}`
                ],
                [
                    '$DETAILMODE',
                    `{STD_${propName}_DETAILMODE}`
                ],
                [
                    'unpackNormal(',
                    `{STD_${propName}_TEXTURE_DECODE}(`
                ]
            ].forEach(([oldSyntax, newSyntax])=>trackChange(oldSyntax, newSyntax));
            // defines
            [
                [
                    'MAPFLOAT',
                    `STD_${propName}_CONSTANT`
                ],
                [
                    'MAPCOLOR',
                    `STD_${propName}_CONSTANT`
                ],
                [
                    'MAPVERTEX',
                    `STD_${propName}_VERTEX`
                ],
                [
                    'MAPTEXTURE',
                    `STD_${propName}_TEXTURE`
                ],
                [
                    'MAPINVERT',
                    `STD_${propName}_INVERT`
                ]
            ].forEach(([oldSyntax, newSyntax])=>trackChange(oldSyntax, newSyntax));
            // custom handling
            if (code.includes('$texture2DSAMPLE')) {
                trackChange('$texture2DSAMPLE', '(Macro no longer supported - remove/refactor)');
            }
            if (requiredChangeStrings.length > 0) {
                Debug.errorOnce(`Shader chunk ${chunkName} is in no longer compatible format. Please make these replacements to bring it to the current version:\n${requiredChangeStrings.join('\n')}`, {
                    code: code
                });
            }
        });
    }
    /**
     * Add shader defines for a texture map.
     *
     * @param {Map<string, string>} fDefines - The fragment defines.
     * @param {string} propName - The base name of the map: diffuse | emissive | opacity | light | height | metalness | specular | gloss | ao.
     * @param {string} chunkName - The name of the chunk to use. Usually "basenamePS".
     * @param {object} options - The options passed into to createShaderDefinition.
     * @param {Map<string, string>} chunks - The set of shader chunks to choose from.
     * @param {object} mapping - The mapping between chunk and sampler
     * @param {string|null} encoding - The texture's encoding
     * @private
     */ _addMapDefines(fDefines, propName, chunkName, options, chunks, mapping, encoding = null) {
        const mapPropName = `${propName}Map`;
        const propNameCaps = propName.toUpperCase();
        const uVPropName = `${mapPropName}Uv`;
        const identifierPropName = `${mapPropName}Identifier`;
        const transformPropName = `${mapPropName}Transform`;
        const channelPropName = `${mapPropName}Channel`;
        const vertexColorChannelPropName = `${propName}VertexColorChannel`;
        const tintPropName = `${propName}Tint`;
        const vertexColorPropName = `${propName}VertexColor`;
        const detailModePropName = `${propName}Mode`;
        const invertName = `${propName}Invert`;
        const tintOption = options[tintPropName];
        const vertexColorOption = options[vertexColorPropName];
        const textureOption = options[mapPropName];
        const textureIdentifier = options[identifierPropName];
        const detailModeOption = options[detailModePropName];
        const chunkCode = chunks.get(chunkName);
        Debug.assert(chunkCode, `Shader chunk ${chunkName} not found.`);
        // log errors if the chunk format is deprecated (format changed in engine 2.7)
        Debug.call(()=>{
            if (chunkCode) {
                this._validateMapChunk(propNameCaps, chunkName, chunks);
            }
        });
        if (textureOption) {
            fDefines.set(`STD_${propNameCaps}_TEXTURE`, '');
            const uv = this._getUvSourceExpression(transformPropName, uVPropName, options);
            // chunk injection defines
            fDefines.set(`{STD_${propNameCaps}_TEXTURE_UV}`, uv);
            fDefines.set(`{STD_${propNameCaps}_TEXTURE_CHANNEL}`, options[channelPropName]);
            // texture sampler define
            const textureId = `{STD_${propNameCaps}_TEXTURE_NAME}`;
            if (chunkCode.includes(textureId)) {
                let samplerName = `texture_${mapPropName}`;
                const alias = mapping[textureIdentifier];
                if (alias) {
                    samplerName = alias;
                } else {
                    mapping[textureIdentifier] = samplerName;
                    // texture is not aliased to existing texture, create a new one
                    fDefines.set(`STD_${propNameCaps}_TEXTURE_ALLOCATE`, '');
                }
                fDefines.set(textureId, samplerName);
            }
            if (encoding) {
                // decode function, ignored for alpha channel
                const textureDecode = options[channelPropName] === 'aaa' ? 'passThrough' : ChunkUtils.decodeFunc(encoding);
                fDefines.set(`{STD_${propNameCaps}_TEXTURE_DECODE}`, textureDecode);
            }
        }
        if (vertexColorOption) {
            fDefines.set(`STD_${propNameCaps}_VERTEX`, '');
            fDefines.set(`{STD_${propNameCaps}_VERTEX_CHANNEL}`, options[vertexColorChannelPropName]);
        }
        if (detailModeOption) {
            fDefines.set(`{STD_${propNameCaps}_DETAILMODE}`, detailModeOption);
        }
        if (tintOption) {
            fDefines.set(`STD_${propNameCaps}_CONSTANT`, '');
        }
        if (!!options[invertName]) {
            fDefines.set(`STD_${propNameCaps}_INVERT`, '');
        }
    }
    _correctChannel(p, chan, _matTex2D) {
        if (_matTex2D[p] > 0) {
            if (_matTex2D[p] < chan.length) {
                return chan.substring(0, _matTex2D[p]);
            } else if (_matTex2D[p] > chan.length) {
                let str = chan;
                const chr = str.charAt(str.length - 1);
                const addLen = _matTex2D[p] - str.length;
                for(let i = 0; i < addLen; i++)str += chr;
                return str;
            }
            return chan;
        }
    }
    createVertexShader(litShader, options) {
        const useUv = [];
        const useUnmodifiedUv = [];
        const mapTransforms = [];
        const maxUvSets = 2;
        for(const p in _matTex2D){
            const mapName = `${p}Map`;
            if (options[`${p}VertexColor`]) {
                const colorChannelName = `${p}VertexColorChannel`;
                options[colorChannelName] = this._correctChannel(p, options[colorChannelName], _matTex2D);
            }
            if (options[mapName]) {
                const channelName = `${mapName}Channel`;
                const transformName = `${mapName}Transform`;
                const uvName = `${mapName}Uv`;
                options[uvName] = Math.min(options[uvName], maxUvSets - 1);
                options[channelName] = this._correctChannel(p, options[channelName], _matTex2D);
                const uvSet = options[uvName];
                useUv[uvSet] = true;
                useUnmodifiedUv[uvSet] = useUnmodifiedUv[uvSet] || options[mapName] && !options[transformName];
                // create map transforms
                if (options[transformName]) {
                    mapTransforms.push({
                        name: p,
                        id: options[transformName],
                        uv: options[uvName]
                    });
                }
            }
        }
        if (options.forceUv1) {
            useUv[1] = true;
            useUnmodifiedUv[1] = useUnmodifiedUv[1] !== undefined ? useUnmodifiedUv[1] : true;
        }
        litShader.generateVertexShader(useUv, useUnmodifiedUv, mapTransforms);
    }
    /**
     * @param {StandardMaterialOptions} options - The create options.
     * @param {Map<string, string>} fDefines - The fragment defines.
     * @param {ShaderPass} shaderPassInfo - The shader pass info.
     */ prepareFragmentDefines(options, fDefines, shaderPassInfo) {
        const fDefineSet = (condition, name, value = '')=>{
            if (condition) {
                fDefines.set(name, value);
            }
        };
        fDefineSet(options.lightMap, 'STD_LIGHTMAP', '');
        fDefineSet(options.lightVertexColor, 'STD_LIGHT_VERTEX_COLOR', '');
        fDefineSet(options.dirLightMap && options.litOptions.useSpecular, 'STD_LIGHTMAP_DIR', '');
        fDefineSet(options.heightMap, 'STD_HEIGHT_MAP', '');
        fDefineSet(options.useSpecularColor, 'STD_SPECULAR_COLOR', '');
        fDefineSet(options.aoMap || options.aoVertexColor || options.useAO, 'STD_AO', '');
        fDefineSet(true, 'STD_OPACITY_DITHER', ditherNames[shaderPassInfo.isForward ? options.litOptions.opacityDither : options.litOptions.opacityShadowDither]);
    }
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {StandardMaterialOptions} options - The create options.
     * @returns {object} Returns the created shader definition.
     */ createShaderDefinition(device, options) {
        const shaderPassInfo = ShaderPass.get(device).getByIndex(options.litOptions.pass);
        const isForwardPass = shaderPassInfo.isForward;
        const litShader = new LitShader(device, options.litOptions);
        // generate vertex shader
        this.createVertexShader(litShader, options);
        // handle fragment shader
        const textureMapping = {};
        options.litOptions.fresnelModel = options.litOptions.fresnelModel === 0 ? FRESNEL_SCHLICK : options.litOptions.fresnelModel;
        // fragment defines
        const fDefines = litShader.fDefines;
        this.prepareFragmentDefines(options, fDefines, shaderPassInfo);
        let lightingUv = '';
        if (isForwardPass) {
            // parallax
            if (options.heightMap) {
                this._addMapDefines(fDefines, 'height', 'parallaxPS', options, litShader.chunks, textureMapping);
            }
            // opacity
            if (options.litOptions.blendType !== BLEND_NONE || options.litOptions.alphaTest || options.litOptions.alphaToCoverage || options.litOptions.opacityDither !== DITHER_NONE) {
                this._addMapDefines(fDefines, 'opacity', 'opacityPS', options, litShader.chunks, textureMapping);
            }
            // normal
            if (litShader.needsNormal) {
                if (options.normalMap || options.clearCoatNormalMap) {
                    if (!options.litOptions.hasTangents) {
                        // TODO: generalize to support each normalmap input (normalMap, normalDetailMap, clearCoatNormalMap) independently
                        const baseName = options.normalMap ? 'normalMap' : 'clearCoatNormalMap';
                        lightingUv = this._getUvSourceExpression(`${baseName}Transform`, `${baseName}Uv`, options);
                    }
                }
                this._addMapDefines(fDefines, 'normalDetail', 'normalMapPS', options, litShader.chunks, textureMapping, options.normalDetailPackedNormal ? 'xy' : 'xyz');
                this._addMapDefines(fDefines, 'normal', 'normalMapPS', options, litShader.chunks, textureMapping, options.packedNormal ? 'xy' : 'xyz');
            }
            // diffuse
            if (options.diffuseDetail) {
                this._addMapDefines(fDefines, 'diffuseDetail', 'diffusePS', options, litShader.chunks, textureMapping, options.diffuseDetailEncoding);
            }
            this._addMapDefines(fDefines, 'diffuse', 'diffusePS', options, litShader.chunks, textureMapping, options.diffuseEncoding);
            // refraction
            if (options.litOptions.useRefraction) {
                this._addMapDefines(fDefines, 'refraction', 'transmissionPS', options, litShader.chunks, textureMapping);
                this._addMapDefines(fDefines, 'thickness', 'thicknessPS', options, litShader.chunks, textureMapping);
            }
            // iridescence
            if (options.litOptions.useIridescence) {
                this._addMapDefines(fDefines, 'iridescence', 'iridescencePS', options, litShader.chunks, textureMapping);
                this._addMapDefines(fDefines, 'iridescenceThickness', 'iridescenceThicknessPS', options, litShader.chunks, textureMapping);
            }
            // specularity & glossiness
            if (litShader.lighting && options.litOptions.useSpecular || litShader.reflections) {
                if (options.litOptions.useSheen) {
                    this._addMapDefines(fDefines, 'sheen', 'sheenPS', options, litShader.chunks, textureMapping, options.sheenEncoding);
                    this._addMapDefines(fDefines, 'sheenGloss', 'sheenGlossPS', options, litShader.chunks, textureMapping);
                }
                if (options.litOptions.useMetalness) {
                    this._addMapDefines(fDefines, 'metalness', 'metalnessPS', options, litShader.chunks, textureMapping);
                    this._addMapDefines(fDefines, 'ior', 'iorPS', options, litShader.chunks, textureMapping);
                }
                if (options.litOptions.useSpecularityFactor) {
                    this._addMapDefines(fDefines, 'specularityFactor', 'specularityFactorPS', options, litShader.chunks, textureMapping);
                }
                if (options.useSpecularColor) {
                    this._addMapDefines(fDefines, 'specular', 'specularPS', options, litShader.chunks, textureMapping, options.specularEncoding);
                }
                this._addMapDefines(fDefines, 'gloss', 'glossPS', options, litShader.chunks, textureMapping);
            }
            // ao
            if (options.aoDetail) {
                this._addMapDefines(fDefines, 'aoDetail', 'aoPS', options, litShader.chunks, textureMapping);
            }
            if (options.aoMap || options.aoVertexColor || options.useAO) {
                this._addMapDefines(fDefines, 'ao', 'aoPS', options, litShader.chunks, textureMapping);
            }
            // emission
            this._addMapDefines(fDefines, 'emissive', 'emissivePS', options, litShader.chunks, textureMapping, options.emissiveEncoding);
            // clearcoat
            if (options.litOptions.useClearCoat) {
                this._addMapDefines(fDefines, 'clearCoat', 'clearCoatPS', options, litShader.chunks, textureMapping);
                this._addMapDefines(fDefines, 'clearCoatGloss', 'clearCoatGlossPS', options, litShader.chunks, textureMapping);
                this._addMapDefines(fDefines, 'clearCoatNormal', 'clearCoatNormalPS', options, litShader.chunks, textureMapping, options.clearCoatPackedNormal ? 'xy' : 'xyz');
            }
            // anisotropy
            if (options.litOptions.enableGGXSpecular) {
                this._addMapDefines(fDefines, 'anisotropy', 'anisotropyPS', options, litShader.chunks, textureMapping);
            }
            // lightmap
            if (options.lightMap || options.lightVertexColor) {
                this._addMapDefines(fDefines, 'light', 'lightmapPS', options, litShader.chunks, textureMapping, options.lightMapEncoding);
            }
        } else {
            // all other passes require only opacity
            const opacityShadowDither = options.litOptions.opacityShadowDither;
            if (options.litOptions.alphaTest || opacityShadowDither) {
                this._addMapDefines(fDefines, 'opacity', 'opacityPS', options, litShader.chunks, textureMapping);
            }
        }
        // generate fragment shader - supply the front end declaration and code to the lit shader, which will
        // use the outputs from this standard shader generator as an input to the lit shader generator
        litShader.generateFragmentShader(litShader.chunks.get('stdDeclarationPS'), litShader.chunks.get('stdFrontEndPS'), lightingUv);
        // chunks collected by the lit shader + includes generated by the lit shader
        const includes = MapUtils.merge(litShader.chunks, litShader.includes);
        const vDefines = litShader.vDefines;
        options.defines.forEach((value, key)=>vDefines.set(key, value));
        options.defines.forEach((value, key)=>fDefines.set(key, value));
        const definition = ShaderDefinitionUtils.createDefinition(device, {
            name: 'StandardShader',
            attributes: litShader.attributes,
            shaderLanguage: litShader.shaderLanguage,
            vertexCode: litShader.vshader,
            fragmentCode: litShader.fshader,
            vertexIncludes: includes,
            fragmentIncludes: includes,
            fragmentDefines: fDefines,
            vertexDefines: vDefines
        });
        if (litShader.shaderPassInfo.isForward) {
            definition.tag = SHADERTAG_MATERIAL;
        }
        return definition;
    }
    constructor(...args){
        super(...args), // Shared Standard Material option structures
        this.optionsContext = new StandardMaterialOptions(), this.optionsContextMin = new StandardMaterialOptions();
    }
}
const standard = new ShaderGeneratorStandard();

/**
 * @import { BindGroupFormat } from '../../platform/graphics/bind-group-format.js';
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { Light } from '../light.js';
 * @import { MeshInstance } from '../mesh-instance.js'
 * @import { CameraShaderParams } from '../camera-shader-params.js'
 * @import { Scene } from '../scene.js'
 * @import { Shader } from '../../platform/graphics/shader.js'
 * @import { StencilParameters } from '../../platform/graphics/stencil-parameters.js'
 * @import { Texture } from '../../platform/graphics/texture.js'
 * @import { UniformBufferFormat } from '../../platform/graphics/uniform-buffer-format.js';
 * @import { VertexFormat } from '../../platform/graphics/vertex-format.js';
 * @import { ShaderChunkMap } from '../shader-lib/shader-chunk-map.js';
 */ // blend mode mapping to op, srcBlend and dstBlend
const blendModes = [];
blendModes[BLEND_SUBTRACTIVE] = {
    src: BLENDMODE_ONE,
    dst: BLENDMODE_ONE,
    op: BLENDEQUATION_REVERSE_SUBTRACT
};
blendModes[BLEND_NONE] = {
    src: BLENDMODE_ONE,
    dst: BLENDMODE_ZERO,
    op: BLENDEQUATION_ADD
};
blendModes[BLEND_NORMAL] = {
    src: BLENDMODE_SRC_ALPHA,
    dst: BLENDMODE_ONE_MINUS_SRC_ALPHA,
    op: BLENDEQUATION_ADD,
    alphaSrc: BLENDMODE_ONE
};
blendModes[BLEND_PREMULTIPLIED] = {
    src: BLENDMODE_ONE,
    dst: BLENDMODE_ONE_MINUS_SRC_ALPHA,
    op: BLENDEQUATION_ADD
};
blendModes[BLEND_ADDITIVE] = {
    src: BLENDMODE_ONE,
    dst: BLENDMODE_ONE,
    op: BLENDEQUATION_ADD
};
blendModes[BLEND_ADDITIVEALPHA] = {
    src: BLENDMODE_SRC_ALPHA,
    dst: BLENDMODE_ONE,
    op: BLENDEQUATION_ADD
};
blendModes[BLEND_MULTIPLICATIVE2X] = {
    src: BLENDMODE_DST_COLOR,
    dst: BLENDMODE_SRC_COLOR,
    op: BLENDEQUATION_ADD
};
blendModes[BLEND_SCREEN] = {
    src: BLENDMODE_ONE_MINUS_DST_COLOR,
    dst: BLENDMODE_ONE,
    op: BLENDEQUATION_ADD
};
blendModes[BLEND_MULTIPLICATIVE] = {
    src: BLENDMODE_DST_COLOR,
    dst: BLENDMODE_ZERO,
    op: BLENDEQUATION_ADD
};
blendModes[BLEND_MIN] = {
    src: BLENDMODE_ONE,
    dst: BLENDMODE_ONE,
    op: BLENDEQUATION_MIN
};
blendModes[BLEND_MAX] = {
    src: BLENDMODE_ONE,
    dst: BLENDMODE_ONE,
    op: BLENDEQUATION_MAX
};
let id$3 = 0;
/**
 * @typedef {object} ShaderVariantParams - The description of the parameters used by the
 * Material#getShaderVariant function.
 * @property {GraphicsDevice} device - The graphics device.
 * @property {Scene} scene - The scene.
 * @property {number} objDefs - The object definitions.
 * @property {CameraShaderParams} cameraShaderParams - The camera shader parameters.
 * @property {number} pass - The shader pass.
 * @property {Light[][]} sortedLights - The sorted lights.
 * @property {UniformBufferFormat|undefined} viewUniformFormat - The view uniform format.
 * @property {BindGroupFormat|undefined} viewBindGroupFormat - The view bind group format.
 * @property {VertexFormat} vertexFormat - The vertex format.
 * @ignore
 */ /**
 * A material determines how a particular {@link MeshInstance} is rendered, and specifies
 * render state including uniforms, textures, defines, and other properties.
 *
 * This is a base class and cannot be instantiated and used directly. Only subclasses such
 * as {@link ShaderMaterial} and {@link StandardMaterial} can be used to define materials
 * for rendering.
 *
 * @category Graphics
 */ class Material {
    /** @protected */ constructor(){
        /**
     * The mesh instances referencing this material
     *
     * @type {MeshInstance[]}
     * @private
     */ this.meshInstances = [];
        /**
     * The name of the material.
     *
     * @type {string}
     */ this.name = 'Untitled';
        /**
     * A unique id the user can assign to the material. The engine internally does not use this for
     * anything, and the user can assign a value to this id for any purpose they like. Defaults to
     * an empty string.
     *
     * @type {string}
     */ this.userId = '';
        this.id = id$3++;
        /**
     * The cache of shader variants generated for this material. The key represents the unique
     * variant, the value is the shader.
     *
     * @type {Map<number, Shader>}
     * @ignore
     */ this.variants = new Map();
        /**
     * The set of defines used to generate the shader variants.
     *
     * @type {Map<string, string>}
     * @ignore
     */ this.defines = new Map();
        this._definesDirty = false;
        this.parameters = {};
        /**
     * The alpha test reference value to control which fragments are written to the currently
     * active render target based on alpha value. All fragments with an alpha value of less than
     * the alphaTest reference value will be discarded. alphaTest defaults to 0 (all fragments
     * pass).
     *
     * @type {number}
     */ this.alphaTest = 0;
        /**
     * Enables or disables alpha to coverage (WebGL2 only). When enabled, and if hardware
     * anti-aliasing is on, limited order-independent transparency can be achieved. Quality depends
     * on the number of MSAA samples of the current render target. It can nicely soften edges of
     * otherwise sharp alpha cutouts, but isn't recommended for large area semi-transparent
     * surfaces. Note, that you don't need to enable blending to make alpha to coverage work. It
     * will work without it, just like alphaTest.
     *
     * @type {boolean}
     */ this.alphaToCoverage = false;
        /** @ignore */ this._blendState = new BlendState();
        /** @ignore */ this._depthState = new DepthState();
        /**
     * Controls how triangles are culled based on their face direction with respect to the
     * viewpoint. Can be:
     *
     * - {@link CULLFACE_NONE}: Do not cull triangles based on face direction.
     * - {@link CULLFACE_BACK}: Cull the back faces of triangles (do not render triangles facing
     * away from the view point).
     * - {@link CULLFACE_FRONT}: Cull the front faces of triangles (do not render triangles facing
     * towards the view point).
     *
     * Defaults to {@link CULLFACE_BACK}.
     *
     * @type {number}
     */ this.cull = CULLFACE_BACK;
        /**
     * Stencil parameters for front faces (default is null).
     *
     * @type {StencilParameters|null}
     */ this.stencilFront = null;
        /**
     * Stencil parameters for back faces (default is null).
     *
     * @type {StencilParameters|null}
     */ this.stencilBack = null;
        /**
     * @type {ShaderChunks|null}
     * @private
     */ this._shaderChunks = null;
        // this is deprecated, keeping for backwards compatibility
        this._oldChunks = {};
        this._dirtyShader = true;
        this._shaderVersion = 0;
        this._scene = null;
        this.dirty = true;
        if (new.target === Material) {
            Debug.error('Material class cannot be instantiated, use ShaderMaterial instead');
        }
    }
    /**
     * Returns true if the material has custom shader chunks.
     *
     * @type {boolean}
     * @ignore
     */ get hasShaderChunks() {
        return this._shaderChunks != null;
    }
    /**
     * Returns the shader chunks for the material. Those get allocated if they are not already.
     *
     * @type {ShaderChunks}
     * @ignore
     */ get shaderChunks() {
        if (!this._shaderChunks) {
            this._shaderChunks = new ShaderChunks();
        }
        return this._shaderChunks;
    }
    /**
     * Returns an object containing shader chunks for a specific shader language for the material.
     * These chunks define custom GLSL or WGSL code used to construct the final shader for the
     * material. The chunks can be also be included in shaders using the `#include "ChunkName"`
     * directive.
     *
     * On the WebGL platform:
     *  - If GLSL chunks are provided, they are used directly.
     *
     * On the WebGPU platform:
     * - If WGSL chunks are provided, they are used directly.
     * - If only GLSL chunks are provided, a GLSL shader is generated and then transpiled to WGSL,
     * which is less efficient.
     *
     * To ensure faster shader compilation, it is recommended to provide shader chunks for all
     * supported platforms.
     *
     * A simple example on how to override a shader chunk providing emissive color for both GLSL and
     * WGSL to simply return a red color:
     *
     * ```javascript
     * material.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set('emissivePS', `
     *     void getEmission() {
     *         dEmission = vec3(1.0, 0.0, 1.0);
     *     }
     * `);
     *
     * material.getShaderChunks(pc.SHADERLANGUAGE_WGSL).set('emissivePS', `
     *     fn getEmission() {
     *         dEmission = vec3f(1.0, 0.0, 1.0);
     *     }
     * `);
     *
     * // call update to apply the changes
     * material.update();
     * ```
     *
     * @param {string} [shaderLanguage] - Specifies the shader language of shaders. Defaults to
     * {@link SHADERLANGUAGE_GLSL}.
     * @returns {ShaderChunkMap} - The shader chunks for the specified shader language.
     */ getShaderChunks(shaderLanguage = SHADERLANGUAGE_GLSL) {
        const chunks = this.shaderChunks;
        return shaderLanguage === SHADERLANGUAGE_GLSL ? chunks.glsl : chunks.wgsl;
    }
    /**
     * Sets the version of the shader chunks.
     *
     * This should be a string containing the current engine major and minor version (e.g., '2.8'
     * for engine v2.8.1) and ensures compatibility with the current engine version. When providing
     * custom shader chunks, set this to the latest supported version. If a future engine release no
     * longer supports the specified version, a warning will be issued. In that case, update your
     * shader chunks to match the new format and set this to the latest version accordingly.
     *
     * @type {string}
     */ set shaderChunksVersion(value) {
        this.shaderChunks.version = value;
    }
    /**
     * Returns the version of the shader chunks.
     *
     * @type {string}
     */ get shaderChunksVersion() {
        return this.shaderChunks.version;
    }
    set chunks(value) {
        Debug.deprecated('Material.chunks has been removed, please use Material.getShaderChunks instead. For example: material.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set("chunkName", "chunkCode")');
        this._oldChunks = value;
    }
    get chunks() {
        Debug.deprecated('Material.chunks has been removed, please use Material.getShaderChunks instead. For example: material.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set("chunkName", "chunkCode")');
        Object.assign(this._oldChunks, Object.fromEntries(this.shaderChunks.glsl));
        return this._oldChunks;
    }
    /**
     * Sets the offset for the output depth buffer value. Useful for decals to prevent z-fighting.
     * Typically a small negative value (-0.1) is used to render the mesh slightly closer to the
     * camera.
     *
     * @type {number}
     */ set depthBias(value) {
        this._depthState.depthBias = value;
    }
    /**
     * Gets the offset for the output depth buffer value.
     *
     * @type {number}
     */ get depthBias() {
        return this._depthState.depthBias;
    }
    /**
     * Sets the offset for the output depth buffer value based on the slope of the triangle
     * relative to the camera.
     *
     * @type {number}
     */ set slopeDepthBias(value) {
        this._depthState.depthBiasSlope = value;
    }
    /**
     * Gets the offset for the output depth buffer value based on the slope of the triangle
     * relative to the camera.
     *
     * @type {number}
     */ get slopeDepthBias() {
        return this._depthState.depthBiasSlope;
    }
    /**
     * Sets whether the red channel is written to the color buffer. If true, the red component of
     * fragments generated by the shader of this material is written to the color buffer of the
     * currently active render target. If false, the red component will not be written. Defaults to
     * true.
     *
     * @type {boolean}
     */ set redWrite(value) {
        this._blendState.redWrite = value;
    }
    /**
     * Gets whether the red channel is written to the color buffer.
     *
     * @type {boolean}
     */ get redWrite() {
        return this._blendState.redWrite;
    }
    /**
     * Sets whether the green channel is written to the color buffer. If true, the red component of
     * fragments generated by the shader of this material is written to the color buffer of the
     * currently active render target. If false, the green component will not be written. Defaults
     * to true.
     *
     * @type {boolean}
     */ set greenWrite(value) {
        this._blendState.greenWrite = value;
    }
    /**
     * Gets whether the green channel is written to the color buffer.
     *
     * @type {boolean}
     */ get greenWrite() {
        return this._blendState.greenWrite;
    }
    /**
     * Sets whether the blue channel is written to the color buffer. If true, the red component of
     * fragments generated by the shader of this material is written to the color buffer of the
     * currently active render target. If false, the blue component will not be written. Defaults
     * to true.
     *
     * @type {boolean}
     */ set blueWrite(value) {
        this._blendState.blueWrite = value;
    }
    /**
     * Gets whether the blue channel is written to the color buffer.
     *
     * @type {boolean}
     */ get blueWrite() {
        return this._blendState.blueWrite;
    }
    /**
     * Sets whether the alpha channel is written to the color buffer. If true, the red component of
     * fragments generated by the shader of this material is written to the color buffer of the
     * currently active render target. If false, the alpha component will not be written. Defaults
     * to true.
     *
     * @type {boolean}
     */ set alphaWrite(value) {
        this._blendState.alphaWrite = value;
    }
    /**
     * Gets whether the alpha channel is written to the color buffer.
     *
     * @type {boolean}
     */ get alphaWrite() {
        return this._blendState.alphaWrite;
    }
    // returns boolean depending on material being transparent
    get transparent() {
        return this._blendState.blend;
    }
    _updateTransparency() {
        const transparent = this.transparent;
        const meshInstances = this.meshInstances;
        for(let i = 0; i < meshInstances.length; i++){
            meshInstances[i].transparent = transparent;
        }
    }
    /**
     * Sets the blend state for this material. Controls how fragment shader outputs are blended
     * when being written to the currently active render target. This overwrites blending type set
     * using {@link Material#blendType}, and offers more control over blending.
     *
     * @type {BlendState}
     */ set blendState(value) {
        this._blendState.copy(value);
        this._updateTransparency();
    }
    /**
     * Gets the blend state for this material.
     *
     * @type {BlendState}
     */ get blendState() {
        return this._blendState;
    }
    /**
     * Sets the blend mode for this material. Controls how fragment shader outputs are blended when
     * being written to the currently active render target. Can be:
     *
     * - {@link BLEND_SUBTRACTIVE}: Subtract the color of the source fragment from the destination
     * fragment and write the result to the frame buffer.
     * - {@link BLEND_ADDITIVE}: Add the color of the source fragment to the destination fragment
     * and write the result to the frame buffer.
     * - {@link BLEND_NORMAL}: Enable simple translucency for materials such as glass. This is
     * equivalent to enabling a source blend mode of {@link BLENDMODE_SRC_ALPHA} and a destination
     * blend mode of {@link BLENDMODE_ONE_MINUS_SRC_ALPHA}.
     * - {@link BLEND_NONE}: Disable blending.
     * - {@link BLEND_PREMULTIPLIED}: Similar to {@link BLEND_NORMAL} expect the source fragment is
     * assumed to have already been multiplied by the source alpha value.
     * - {@link BLEND_MULTIPLICATIVE}: Multiply the color of the source fragment by the color of the
     * destination fragment and write the result to the frame buffer.
     * - {@link BLEND_ADDITIVEALPHA}: Same as {@link BLEND_ADDITIVE} except the source RGB is
     * multiplied by the source alpha.
     * - {@link BLEND_MULTIPLICATIVE2X}: Multiplies colors and doubles the result.
     * - {@link BLEND_SCREEN}: Softer version of additive.
     * - {@link BLEND_MIN}: Minimum color.
     * - {@link BLEND_MAX}: Maximum color.
     *
     * Defaults to {@link BLEND_NONE}.
     *
     * @type {number}
     */ set blendType(type) {
        const blendMode = blendModes[type];
        Debug.assert(blendMode, `Unknown blend mode ${type}`);
        this._blendState.setColorBlend(blendMode.op, blendMode.src, blendMode.dst);
        this._blendState.setAlphaBlend(blendMode.alphaOp ?? blendMode.op, blendMode.alphaSrc ?? blendMode.src, blendMode.alphaDst ?? blendMode.dst);
        const blend = type !== BLEND_NONE;
        if (this._blendState.blend !== blend) {
            this._blendState.blend = blend;
            this._updateTransparency();
        }
        this._updateMeshInstanceKeys();
    }
    /**
     * Gets the blend mode for this material.
     *
     * @type {number}
     */ get blendType() {
        if (!this.transparent) {
            return BLEND_NONE;
        }
        const { colorOp, colorSrcFactor, colorDstFactor, alphaOp, alphaSrcFactor, alphaDstFactor } = this._blendState;
        for(let i = 0; i < blendModes.length; i++){
            const blendMode = blendModes[i];
            if (blendMode.src === colorSrcFactor && blendMode.dst === colorDstFactor && blendMode.op === colorOp && blendMode.src === alphaSrcFactor && blendMode.dst === alphaDstFactor && blendMode.op === alphaOp) {
                return i;
            }
        }
        return BLEND_NORMAL;
    }
    /**
     * Sets the depth state. Note that this can also be done by using {@link Material#depthTest},
     * {@link Material#depthFunc} and {@link Material#depthWrite}.
     *
     * @type {DepthState}
     */ set depthState(value) {
        this._depthState.copy(value);
    }
    /**
     * Gets the depth state.
     *
     * @type {DepthState}
     */ get depthState() {
        return this._depthState;
    }
    /**
     * Sets whether depth testing is enabled. If true, fragments generated by the shader of this
     * material are only written to the current render target if they pass the depth test. If
     * false, fragments generated by the shader of this material are written to the current render
     * target regardless of what is in the depth buffer. Defaults to true.
     *
     * @type {boolean}
     */ set depthTest(value) {
        this._depthState.test = value;
    }
    /**
     * Gets whether depth testing is enabled.
     *
     * @type {boolean}
     */ get depthTest() {
        return this._depthState.test;
    }
    /**
     * Sets the depth test function. Controls how the depth of new fragments is compared against
     * the current depth contained in the depth buffer. Can be:
     *
     * - {@link FUNC_NEVER}: don't draw
     * - {@link FUNC_LESS}: draw if new depth < depth buffer
     * - {@link FUNC_EQUAL}: draw if new depth == depth buffer
     * - {@link FUNC_LESSEQUAL}: draw if new depth <= depth buffer
     * - {@link FUNC_GREATER}: draw if new depth > depth buffer
     * - {@link FUNC_NOTEQUAL}: draw if new depth != depth buffer
     * - {@link FUNC_GREATEREQUAL}: draw if new depth >= depth buffer
     * - {@link FUNC_ALWAYS}: always draw
     *
     * Defaults to {@link FUNC_LESSEQUAL}.
     *
     * @type {number}
     */ set depthFunc(value) {
        this._depthState.func = value;
    }
    /**
     * Gets the depth test function.
     *
     * @type {number}
     */ get depthFunc() {
        return this._depthState.func;
    }
    /**
     * Sets whether depth writing is enabled. If true, fragments generated by the shader of this
     * material write a depth value to the depth buffer of the currently active render target. If
     * false, no depth value is written. Defaults to true.
     *
     * @type {boolean}
     */ set depthWrite(value) {
        this._depthState.write = value;
    }
    /**
     * Gets whether depth writing is enabled.
     *
     * @type {boolean}
     */ get depthWrite() {
        return this._depthState.write;
    }
    /**
     * Copy a material.
     *
     * @param {Material} source - The material to copy.
     * @returns {Material} The destination material.
     */ copy(source) {
        this.name = source.name;
        // Render states
        this.alphaTest = source.alphaTest;
        this.alphaToCoverage = source.alphaToCoverage;
        this._blendState.copy(source._blendState);
        this._depthState.copy(source._depthState);
        this.cull = source.cull;
        this.stencilFront = source.stencilFront?.clone();
        if (source.stencilBack) {
            this.stencilBack = source.stencilFront === source.stencilBack ? this.stencilFront : source.stencilBack.clone();
        }
        // Shader parameters
        this.clearParameters();
        for(const name in source.parameters){
            if (source.parameters.hasOwnProperty(name)) {
                this._setParameterSimple(name, source.parameters[name].data);
            }
        }
        // defines
        this.defines.clear();
        source.defines.forEach((value, key)=>this.defines.set(key, value));
        // shader chunks
        this._shaderChunks = source.hasShaderChunks ? new ShaderChunks() : null;
        this._shaderChunks?.copy(source._shaderChunks);
        return this;
    }
    /**
     * Clone a material.
     *
     * @returns {this} A newly cloned material.
     */ clone() {
        const clone = new this.constructor();
        return clone.copy(this);
    }
    _updateMeshInstanceKeys() {
        const meshInstances = this.meshInstances;
        for(let i = 0; i < meshInstances.length; i++){
            meshInstances[i].updateKey();
        }
    }
    updateUniforms(device, scene) {
        if (this._dirtyShader) {
            this.clearVariants();
        }
    }
    /**
     * @param {ShaderVariantParams} params - The parameters used to generate the shader variant.
     * @ignore
     */ getShaderVariant(params) {
        Debug.assert(false, 'Not implemented');
    }
    /**
     * Applies any changes made to the material's properties.
     */ update() {
        // handle deprecated chunks for backwards compatibility
        if (Object.keys(this._oldChunks).length > 0) {
            for (const [key, value] of Object.entries(this._oldChunks)){
                this.shaderChunks.glsl.set(key, value);
                delete this._oldChunks[key];
            }
        }
        // if the defines or chunks were modified, we need to rebuild the shaders
        if (this._definesDirty || this._shaderChunks?.isDirty()) {
            this._definesDirty = false;
            this._shaderChunks?.resetDirty();
            this.clearVariants();
        }
        this.dirty = true;
    }
    // Parameter management
    clearParameters() {
        this.parameters = {};
    }
    getParameters() {
        return this.parameters;
    }
    clearVariants() {
        // clear variants on the material
        this.variants.clear();
        // but also clear them from all materials that reference them
        const meshInstances = this.meshInstances;
        const count = meshInstances.length;
        for(let i = 0; i < count; i++){
            meshInstances[i].clearShaders();
        }
    }
    /**
     * Retrieves the specified shader parameter from a material.
     *
     * @param {string} name - The name of the parameter to query.
     * @returns {object} The named parameter.
     */ getParameter(name) {
        return this.parameters[name];
    }
    _setParameterSimple(name, data) {
        Debug.call(()=>{
            if (data === undefined) {
                Debug.warnOnce(`Material#setParameter: Attempting to set undefined data for parameter "${name}", this is likely not expected.`, this);
            }
        });
        const param = this.parameters[name];
        if (param) {
            param.data = data;
        } else {
            this.parameters[name] = {
                scopeId: null,
                data: data
            };
        }
    }
    /**
     * Sets a shader parameter on a material.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {number|number[]|Float32Array|Texture} data - The value for the specified parameter.
     */ setParameter(name, data) {
        if (data === undefined && typeof name === 'object') {
            const uniformObject = name;
            if (uniformObject.length) {
                for(let i = 0; i < uniformObject.length; i++){
                    this.setParameter(uniformObject[i]);
                }
                return;
            }
            name = uniformObject.name;
            data = uniformObject.value;
        }
        this._setParameterSimple(name, data);
    }
    /**
     * Deletes a shader parameter on a material.
     *
     * @param {string} name - The name of the parameter to delete.
     */ deleteParameter(name) {
        if (this.parameters[name]) {
            delete this.parameters[name];
        }
    }
    // used to apply parameters from this material into scope of uniforms, called internally by forward-renderer
    // optional list of parameter names to be set can be specified, otherwise all parameters are set
    setParameters(device, names) {
        const parameters = this.parameters;
        if (names === undefined) names = parameters;
        for(const paramName in names){
            const parameter = parameters[paramName];
            if (parameter) {
                if (!parameter.scopeId) {
                    parameter.scopeId = device.scope.resolve(paramName);
                }
                parameter.scopeId.setValue(parameter.data);
            }
        }
    }
    /**
     * Adds or removes a define on the material. Defines can be used to enable or disable various
     * parts of the shader code.
     *
     * @param {string} name - The name of the define to set.
     * @param {string|undefined|boolean} value - The value of the define. If undefined or false, the
     * define is removed.
     *
     * A simple example on how to set a custom shader define value used by the shader processor.
     *
     * ```javascript
     * material.setDefine('MY_DEFINE', true);
     *
     * // call update to apply the changes, which will recompile the shader using the new define
     * material.update();
     * ```
     */ setDefine(name, value) {
        let modified = false;
        const { defines } = this;
        if (value !== undefined && value !== false) {
            modified = !defines.has(name) || defines.get(name) !== value;
            defines.set(name, value);
        } else {
            modified = defines.has(name);
            defines.delete(name);
        }
        this._definesDirty ||= modified;
    }
    /**
     * Returns true if a define is enabled on the material, otherwise false.
     *
     * @param {string} name - The name of the define to check.
     * @returns {boolean} The value of the define.
     */ getDefine(name) {
        return this.defines.has(name);
    }
    /**
     * Removes this material from the scene and possibly frees up memory from its shaders (if there
     * are no other materials using it).
     */ destroy() {
        this.variants.clear();
        for(let i = 0; i < this.meshInstances.length; i++){
            const meshInstance = this.meshInstances[i];
            meshInstance.clearShaders();
            meshInstance._material = null;
            if (meshInstance.mesh) {
                const defaultMaterial = getDefaultMaterial(meshInstance.mesh.device);
                if (this !== defaultMaterial) {
                    meshInstance.material = defaultMaterial;
                }
            } else {
                Debug.warn('pc.Material: MeshInstance.mesh is null, default material cannot be assigned to the MeshInstance');
            }
        }
        this.meshInstances.length = 0;
    }
    /**
     * Registers mesh instance as referencing the material.
     *
     * @param {MeshInstance} meshInstance - The mesh instance to register.
     * @ignore
     */ addMeshInstanceRef(meshInstance) {
        this.meshInstances.push(meshInstance);
    }
    /**
     * De-registers mesh instance as referencing the material.
     *
     * @param {MeshInstance} meshInstance - The mesh instance to de-register.
     * @ignore
     */ removeMeshInstanceRef(meshInstance) {
        const meshInstances = this.meshInstances;
        const i = meshInstances.indexOf(meshInstance);
        if (i !== -1) {
            meshInstances.splice(i, 1);
        }
    }
}

class LitMaterialOptionsBuilder {
    static update(litOptions, material, scene, renderParams, objDefs, pass, sortedLights) {
        LitMaterialOptionsBuilder.updateSharedOptions(litOptions, material, scene, objDefs, pass);
        LitMaterialOptionsBuilder.updateMaterialOptions(litOptions, material);
        LitMaterialOptionsBuilder.updateEnvOptions(litOptions, material, scene, renderParams);
        LitMaterialOptionsBuilder.updateLightingOptions(litOptions, material, scene, objDefs, sortedLights);
    }
    static updateSharedOptions(litOptions, material, scene, objDefs, pass) {
        litOptions.shaderChunks = material.shaderChunks;
        litOptions.pass = pass;
        litOptions.alphaTest = material.alphaTest > 0;
        litOptions.blendType = material.blendType;
        litOptions.screenSpace = objDefs && (objDefs & SHADERDEF_SCREENSPACE) !== 0;
        litOptions.skin = objDefs && (objDefs & SHADERDEF_SKIN) !== 0;
        litOptions.useInstancing = objDefs && (objDefs & SHADERDEF_INSTANCING) !== 0;
        litOptions.useMorphPosition = objDefs && (objDefs & SHADERDEF_MORPH_POSITION) !== 0;
        litOptions.useMorphNormal = objDefs && (objDefs & SHADERDEF_MORPH_NORMAL) !== 0;
        litOptions.useMorphTextureBasedInt = objDefs && (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0;
        litOptions.hasTangents = objDefs && (objDefs & SHADERDEF_TANGENTS) !== 0;
        litOptions.nineSlicedMode = material.nineSlicedMode || SPRITE_RENDERMODE_SIMPLE;
        // clustered lighting features (in shared options as shadow pass needs this too)
        if (material.useLighting && scene.clusteredLightingEnabled) {
            litOptions.clusteredLightingEnabled = true;
            litOptions.clusteredLightingCookiesEnabled = scene.lighting.cookiesEnabled;
            litOptions.clusteredLightingShadowsEnabled = scene.lighting.shadowsEnabled;
            litOptions.clusteredLightingShadowType = scene.lighting.shadowType;
            litOptions.clusteredLightingAreaLightsEnabled = scene.lighting.areaLightsEnabled;
        } else {
            litOptions.clusteredLightingEnabled = false;
            litOptions.clusteredLightingCookiesEnabled = false;
            litOptions.clusteredLightingShadowsEnabled = false;
            litOptions.clusteredLightingAreaLightsEnabled = false;
        }
    }
    static updateMaterialOptions(litOptions, material) {
        litOptions.separateAmbient = false; // store ambient light color in separate variable, instead of adding it to diffuse directly
        litOptions.pixelSnap = material.pixelSnap;
        litOptions.ambientSH = material.ambientSH;
        litOptions.twoSidedLighting = material.twoSidedLighting;
        litOptions.occludeDirect = material.occludeDirect;
        litOptions.occludeSpecular = material.occludeSpecular;
        litOptions.occludeSpecularFloat = material.occludeSpecularIntensity !== 1.0;
        litOptions.useMsdf = false;
        litOptions.msdfTextAttribute = false;
        litOptions.alphaToCoverage = material.alphaToCoverage;
        litOptions.opacityFadesSpecular = material.opacityFadesSpecular;
        litOptions.opacityDither = material.opacityDither;
        litOptions.cubeMapProjection = CUBEPROJ_NONE;
        litOptions.useSpecular = material.hasSpecular;
        litOptions.useSpecularityFactor = material.hasSpecularityFactor;
        litOptions.enableGGXSpecular = material.ggxSpecular;
        litOptions.fresnelModel = material.fresnelModel;
        litOptions.useRefraction = material.hasRefraction;
        litOptions.useClearCoat = material.hasClearCoat;
        litOptions.useSheen = material.hasSheen;
        litOptions.useIridescence = material.hasIrridescence;
        litOptions.useMetalness = material.hasMetalness;
        litOptions.useDynamicRefraction = material.dynamicRefraction;
        litOptions.dispersion = material.dispersion > 0;
        litOptions.vertexColors = false;
        litOptions.lightMapEnabled = material.hasLighting;
        litOptions.dirLightMapEnabled = material.dirLightMap;
        litOptions.useHeights = material.hasHeights;
        litOptions.useNormals = material.hasNormals;
        litOptions.useClearCoatNormals = material.hasClearCoatNormals;
        litOptions.useAo = material.hasAo;
        litOptions.diffuseMapEnabled = material.hasDiffuseMap;
    }
    static updateEnvOptions(litOptions, material, scene, renderParams) {
        litOptions.fog = material.useFog ? renderParams.fog : FOG_NONE;
        litOptions.gamma = renderParams.shaderOutputGamma;
        litOptions.toneMap = material.useTonemap ? renderParams.toneMapping : TONEMAP_NONE;
        // source of reflections
        if (material.useSkybox && scene.envAtlas && scene.skybox) {
            litOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;
            litOptions.reflectionEncoding = scene.envAtlas.encoding;
            litOptions.reflectionCubemapEncoding = scene.skybox.encoding;
        } else if (material.useSkybox && scene.envAtlas) {
            litOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;
            litOptions.reflectionEncoding = scene.envAtlas.encoding;
        } else if (material.useSkybox && scene.skybox) {
            litOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;
            litOptions.reflectionEncoding = scene.skybox.encoding;
        } else {
            litOptions.reflectionSource = REFLECTIONSRC_NONE;
            litOptions.reflectionEncoding = null;
        }
        // source of environment ambient is as follows:
        if (material.ambientSH) {
            litOptions.ambientSource = AMBIENTSRC_AMBIENTSH;
            litOptions.ambientEncoding = null;
        } else if (litOptions.reflectionSource !== REFLECTIONSRC_NONE && scene.envAtlas) {
            litOptions.ambientSource = AMBIENTSRC_ENVALATLAS;
            litOptions.ambientEncoding = scene.envAtlas.encoding;
        } else {
            litOptions.ambientSource = AMBIENTSRC_CONSTANT;
            litOptions.ambientEncoding = null;
        }
        const hasSkybox = litOptions.reflectionSource !== REFLECTIONSRC_NONE;
        litOptions.skyboxIntensity = hasSkybox;
        litOptions.useCubeMapRotation = hasSkybox && scene._skyboxRotationShaderInclude;
    }
    static updateLightingOptions(litOptions, material, scene, objDefs, sortedLights) {
        litOptions.lightMapWithoutAmbient = false;
        if (material.useLighting) {
            const lightsFiltered = [];
            const mask = objDefs ? objDefs >> 16 : MASK_AFFECT_DYNAMIC;
            // mask to select lights (dynamic vs lightmapped) when using clustered lighting
            litOptions.lightMaskDynamic = !!(mask & MASK_AFFECT_DYNAMIC);
            litOptions.lightMapWithoutAmbient = false;
            if (sortedLights) {
                LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_DIRECTIONAL, sortedLights[LIGHTTYPE_DIRECTIONAL], lightsFiltered, mask);
                if (!scene.clusteredLightingEnabled) {
                    LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_OMNI, sortedLights[LIGHTTYPE_OMNI], lightsFiltered, mask);
                    LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_SPOT, sortedLights[LIGHTTYPE_SPOT], lightsFiltered, mask);
                }
            }
            litOptions.lights = lightsFiltered;
        } else {
            litOptions.lights = [];
        }
        if (litOptions.lights.length === 0 && !scene.clusteredLightingEnabled || (objDefs & SHADERDEF_NOSHADOW) !== 0) {
            litOptions.noShadow = true;
        }
    }
    static collectLights(lType, lights, lightsFiltered, mask) {
        for(let i = 0; i < lights.length; i++){
            const light = lights[i];
            if (light.enabled) {
                if (light.mask & mask) {
                    lightsFiltered.push(light);
                }
            }
        }
    }
}

const arraysEqual = (a, b)=>{
    if (a.length !== b.length) {
        return false;
    }
    for(let i = 0; i < a.length; ++i){
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
};
const notWhite = (color)=>{
    return color.r !== 1 || color.g !== 1 || color.b !== 1;
};
const notBlack = (color)=>{
    return color.r !== 0 || color.g !== 0 || color.b !== 0;
};
class StandardMaterialOptionsBuilder {
    constructor(){
        this._mapXForms = null;
    }
    // Minimal options for Depth and Shadow passes
    updateMinRef(options, scene, stdMat, objDefs, pass, sortedLights) {
        this._updateSharedOptions(options, scene, stdMat, objDefs, pass);
        this._updateMinOptions(options, stdMat, pass);
        this._updateUVOptions(options, stdMat, objDefs, true);
    }
    updateRef(options, scene, cameraShaderParams, stdMat, objDefs, pass, sortedLights) {
        this._updateSharedOptions(options, scene, stdMat, objDefs, pass);
        this._updateEnvOptions(options, stdMat, scene, cameraShaderParams);
        this._updateMaterialOptions(options, stdMat, scene);
        options.litOptions.hasTangents = objDefs && (objDefs & SHADERDEF_TANGENTS) !== 0;
        this._updateLightOptions(options, scene, stdMat, objDefs, sortedLights);
        this._updateUVOptions(options, stdMat, objDefs, false, cameraShaderParams);
    }
    _updateSharedOptions(options, scene, stdMat, objDefs, pass) {
        options.forceUv1 = stdMat.forceUv1;
        // USER ATTRIBUTES
        if (stdMat.userAttributes) {
            options.litOptions.userAttributes = Object.fromEntries(stdMat.userAttributes.entries());
        }
        options.litOptions.shaderChunks = stdMat.shaderChunks;
        options.litOptions.pass = pass;
        options.litOptions.alphaTest = stdMat.alphaTest > 0;
        options.litOptions.blendType = stdMat.blendType;
        options.litOptions.screenSpace = objDefs && (objDefs & SHADERDEF_SCREENSPACE) !== 0;
        options.litOptions.skin = objDefs && (objDefs & SHADERDEF_SKIN) !== 0;
        options.litOptions.batch = objDefs && (objDefs & SHADERDEF_BATCH) !== 0;
        options.litOptions.useInstancing = objDefs && (objDefs & SHADERDEF_INSTANCING) !== 0;
        options.litOptions.useMorphPosition = objDefs && (objDefs & SHADERDEF_MORPH_POSITION) !== 0;
        options.litOptions.useMorphNormal = objDefs && (objDefs & SHADERDEF_MORPH_NORMAL) !== 0;
        options.litOptions.useMorphTextureBasedInt = objDefs && (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0;
        options.litOptions.nineSlicedMode = stdMat.nineSlicedMode || 0;
        // clustered lighting features (in shared options as shadow pass needs this too)
        if (scene.clusteredLightingEnabled && stdMat.useLighting) {
            options.litOptions.clusteredLightingEnabled = true;
            options.litOptions.clusteredLightingCookiesEnabled = scene.lighting.cookiesEnabled;
            options.litOptions.clusteredLightingShadowsEnabled = scene.lighting.shadowsEnabled;
            options.litOptions.clusteredLightingShadowType = scene.lighting.shadowType;
            options.litOptions.clusteredLightingAreaLightsEnabled = scene.lighting.areaLightsEnabled;
        } else {
            options.litOptions.clusteredLightingEnabled = false;
            options.litOptions.clusteredLightingCookiesEnabled = false;
            options.litOptions.clusteredLightingShadowsEnabled = false;
            options.litOptions.clusteredLightingAreaLightsEnabled = false;
        }
    }
    _updateUVOptions(options, stdMat, objDefs, minimalOptions, cameraShaderParams) {
        let hasUv0 = false;
        let hasUv1 = false;
        let hasVcolor = false;
        if (objDefs) {
            hasUv0 = (objDefs & SHADERDEF_UV0) !== 0;
            hasUv1 = (objDefs & SHADERDEF_UV1) !== 0;
            hasVcolor = (objDefs & SHADERDEF_VCOLOR) !== 0;
        }
        options.litOptions.vertexColors = false;
        this._mapXForms = [];
        const uniqueTextureMap = {};
        for(const p in _matTex2D){
            this._updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap);
        }
        this._mapXForms = null;
        // true if ssao is applied directly in the lit shaders. Also ensure the AO part is generated in the front end
        options.litOptions.ssao = cameraShaderParams?.ssaoEnabled;
        options.useAO = options.litOptions.ssao;
        // All texture related lit options
        options.litOptions.lightMapEnabled = options.lightMap;
        options.litOptions.dirLightMapEnabled = options.dirLightMap;
        options.litOptions.useHeights = options.heightMap;
        options.litOptions.useNormals = options.normalMap;
        options.litOptions.useClearCoatNormals = options.clearCoatNormalMap;
        options.litOptions.useAo = options.aoMap || options.aoVertexColor || options.litOptions.ssao;
        options.litOptions.diffuseMapEnabled = options.diffuseMap;
    }
    _updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap) {
        const isOpacity = p === 'opacity';
        if (!minimalOptions || isOpacity) {
            const mname = `${p}Map`;
            const vname = `${p}VertexColor`;
            const vcname = `${p}VertexColorChannel`;
            const cname = `${mname}Channel`;
            const tname = `${mname}Transform`;
            const uname = `${mname}Uv`;
            const iname = `${mname}Identifier`;
            // Avoid overriding previous lightMap properties
            if (p !== 'light') {
                options[mname] = false;
                options[iname] = undefined;
                options[cname] = '';
                options[tname] = 0;
                options[uname] = 0;
            }
            options[vname] = false;
            options[vcname] = '';
            if (isOpacity && stdMat.blendType === BLEND_NONE && stdMat.alphaTest === 0.0 && !stdMat.alphaToCoverage && stdMat.opacityDither === DITHER_NONE) {
                return;
            }
            if (p !== 'height' && stdMat[vname]) {
                if (hasVcolor) {
                    options[vname] = stdMat[vname];
                    options[vcname] = stdMat[vcname];
                    options.litOptions.vertexColors = true;
                }
            }
            if (stdMat[mname]) {
                let allow = true;
                if (stdMat[uname] === 0 && !hasUv0) allow = false;
                if (stdMat[uname] === 1 && !hasUv1) allow = false;
                if (allow) {
                    // create an intermediate map between the textures and their slots
                    // to ensure the unique texture mapping isn't dependent on the texture id
                    // as that will change when textures are changed, even if the sharing is the same
                    const mapId = stdMat[mname].id;
                    let identifier = uniqueTextureMap[mapId];
                    if (identifier === undefined) {
                        uniqueTextureMap[mapId] = p;
                        identifier = p;
                    }
                    options[mname] = !!stdMat[mname];
                    options[iname] = identifier;
                    options[tname] = this._getMapTransformID(stdMat.getUniform(tname), stdMat[uname]);
                    options[cname] = stdMat[cname];
                    options[uname] = stdMat[uname];
                }
            }
        }
    }
    _updateMinOptions(options, stdMat, pass) {
        // pre-pass uses the same dither setting as forward pass, otherwise shadow dither
        const isPrepass = pass === SHADER_PREPASS;
        options.litOptions.opacityShadowDither = isPrepass ? stdMat.opacityDither : stdMat.opacityShadowDither;
        options.litOptions.linearDepth = isPrepass;
        options.litOptions.lights = [];
    }
    _updateMaterialOptions(options, stdMat, scene) {
        const useSpecular = !!(stdMat.useMetalness || stdMat.specularMap || stdMat.sphereMap || stdMat.cubeMap || notBlack(stdMat.specular) || stdMat.specularityFactor > 0 && stdMat.useMetalness || stdMat.enableGGXSpecular || stdMat.clearCoat > 0);
        const useSpecularColor = !stdMat.useMetalness || stdMat.useMetalnessSpecularColor;
        const specularTint = useSpecular && (stdMat.specularTint || !stdMat.specularMap && !stdMat.specularVertexColor) && notWhite(stdMat.specular);
        const specularityFactorTint = useSpecular && stdMat.useMetalnessSpecularColor && (stdMat.specularityFactorTint || stdMat.specularityFactor < 1 && !stdMat.specularityFactorMap);
        const isPackedNormalMap = (texture)=>texture ? texture.format === PIXELFORMAT_DXT5 || texture.type === TEXTURETYPE_SWIZZLEGGGR : false;
        const equalish = (a, b)=>Math.abs(a - b) < 1e-4;
        options.specularTint = specularTint;
        options.specularityFactorTint = specularityFactorTint;
        options.metalnessTint = stdMat.useMetalness && stdMat.metalness < 1;
        options.glossTint = true;
        options.diffuseEncoding = stdMat.diffuseMap?.encoding;
        options.diffuseDetailEncoding = stdMat.diffuseDetailMap?.encoding;
        options.emissiveEncoding = stdMat.emissiveMap?.encoding;
        options.lightMapEncoding = stdMat.lightMap?.encoding;
        options.packedNormal = isPackedNormalMap(stdMat.normalMap);
        options.refractionTint = equalish(stdMat.refraction, 1.0);
        options.refractionIndexTint = equalish(stdMat.refractionIndex, 1.0 / 1.5);
        options.thicknessTint = stdMat.useDynamicRefraction && stdMat.thickness !== 1.0;
        options.specularEncoding = stdMat.specularMap?.encoding;
        options.sheenEncoding = stdMat.sheenMap?.encoding;
        options.aoMapUv = stdMat.aoUvSet; // backwards compatibility
        options.aoDetail = !!stdMat.aoDetailMap;
        options.diffuseDetail = !!stdMat.diffuseDetailMap;
        options.normalDetail = !!stdMat.normalMap;
        options.normalDetailPackedNormal = isPackedNormalMap(stdMat.normalDetailMap);
        options.diffuseDetailMode = stdMat.diffuseDetailMode;
        options.aoDetailMode = stdMat.aoDetailMode;
        options.clearCoatGloss = !!stdMat.clearCoatGloss;
        options.clearCoatPackedNormal = isPackedNormalMap(stdMat.clearCoatNormalMap);
        options.iorTint = equalish(stdMat.refractionIndex, 1.0 / 1.5);
        // hack, see Scene.forcePassThroughSpecular description
        if (scene.forcePassThroughSpecular) {
            options.specularEncoding = 'linear';
            options.sheenEncoding = 'linear';
        }
        options.iridescenceTint = stdMat.iridescence !== 1.0;
        options.glossInvert = stdMat.glossInvert;
        options.sheenGlossInvert = stdMat.sheenGlossInvert;
        options.clearCoatGlossInvert = stdMat.clearCoatGlossInvert;
        options.useSpecularColor = useSpecularColor;
        // LIT OPTIONS
        options.litOptions.separateAmbient = false; // store ambient light color in separate variable, instead of adding it to diffuse directly
        options.litOptions.pixelSnap = stdMat.pixelSnap;
        options.litOptions.ambientSH = !!stdMat.ambientSH;
        options.litOptions.twoSidedLighting = stdMat.twoSidedLighting;
        options.litOptions.occludeSpecular = stdMat.occludeSpecular;
        options.litOptions.occludeSpecularFloat = stdMat.occludeSpecularIntensity !== 1.0;
        options.litOptions.useMsdf = !!stdMat.msdfMap;
        options.litOptions.msdfTextAttribute = !!stdMat.msdfTextAttribute;
        options.litOptions.alphaToCoverage = stdMat.alphaToCoverage;
        options.litOptions.opacityFadesSpecular = stdMat.opacityFadesSpecular;
        options.litOptions.opacityDither = stdMat.opacityDither;
        options.litOptions.cubeMapProjection = stdMat.cubeMapProjection;
        options.litOptions.occludeDirect = stdMat.occludeDirect;
        options.litOptions.useSpecular = useSpecular;
        options.litOptions.useSpecularityFactor = (specularityFactorTint || !!stdMat.specularityFactorMap) && stdMat.useMetalnessSpecularColor;
        options.litOptions.enableGGXSpecular = stdMat.enableGGXSpecular;
        options.litOptions.fresnelModel = stdMat.fresnelModel;
        options.litOptions.useRefraction = (stdMat.refraction || !!stdMat.refractionMap) && (stdMat.useDynamicRefraction || options.litOptions.reflectionSource !== REFLECTIONSRC_NONE);
        options.litOptions.useClearCoat = !!stdMat.clearCoat;
        options.litOptions.useSheen = stdMat.useSheen;
        options.litOptions.useIridescence = stdMat.useIridescence && stdMat.iridescence !== 0.0;
        options.litOptions.useMetalness = stdMat.useMetalness;
        options.litOptions.useDynamicRefraction = stdMat.useDynamicRefraction;
        options.litOptions.dispersion = stdMat.dispersion > 0;
        options.litOptions.shadowCatcher = stdMat.shadowCatcher;
    }
    _updateEnvOptions(options, stdMat, scene, cameraShaderParams) {
        options.litOptions.fog = stdMat.useFog ? cameraShaderParams.fog : FOG_NONE;
        options.litOptions.gamma = cameraShaderParams.shaderOutputGamma;
        options.litOptions.toneMap = stdMat.useTonemap ? cameraShaderParams.toneMapping : TONEMAP_NONE;
        let usingSceneEnv = false;
        // source of environment reflections is as follows:
        if (stdMat.envAtlas && stdMat.cubeMap) {
            options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;
            options.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;
            options.litOptions.reflectionCubemapEncoding = stdMat.cubeMap.encoding;
        } else if (stdMat.envAtlas) {
            options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;
            options.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;
        } else if (stdMat.cubeMap) {
            options.litOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;
            options.litOptions.reflectionEncoding = stdMat.cubeMap.encoding;
        } else if (stdMat.sphereMap) {
            options.litOptions.reflectionSource = REFLECTIONSRC_SPHEREMAP;
            options.litOptions.reflectionEncoding = stdMat.sphereMap.encoding;
        } else if (stdMat.useSkybox && scene.envAtlas && scene.skybox) {
            options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;
            options.litOptions.reflectionEncoding = scene.envAtlas.encoding;
            options.litOptions.reflectionCubemapEncoding = scene.skybox.encoding;
            usingSceneEnv = true;
        } else if (stdMat.useSkybox && scene.envAtlas) {
            options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;
            options.litOptions.reflectionEncoding = scene.envAtlas.encoding;
            usingSceneEnv = true;
        } else if (stdMat.useSkybox && scene.skybox) {
            options.litOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;
            options.litOptions.reflectionEncoding = scene.skybox.encoding;
            usingSceneEnv = true;
        } else {
            options.litOptions.reflectionSource = REFLECTIONSRC_NONE;
            options.litOptions.reflectionEncoding = null;
        }
        // source of environment ambient is as follows:
        if (stdMat.ambientSH) {
            options.litOptions.ambientSource = AMBIENTSRC_AMBIENTSH;
            options.litOptions.ambientEncoding = null;
        } else {
            const envAtlas = stdMat.envAtlas || (stdMat.useSkybox && scene.envAtlas ? scene.envAtlas : null);
            if (envAtlas && !stdMat.sphereMap) {
                options.litOptions.ambientSource = AMBIENTSRC_ENVALATLAS;
                options.litOptions.ambientEncoding = envAtlas.encoding;
            } else {
                options.litOptions.ambientSource = AMBIENTSRC_CONSTANT;
                options.litOptions.ambientEncoding = null;
            }
        }
        // TODO: add a test for if non skybox cubemaps have rotation (when this is supported) - for now assume no non-skybox cubemap rotation
        options.litOptions.skyboxIntensity = usingSceneEnv;
        options.litOptions.useCubeMapRotation = usingSceneEnv && scene._skyboxRotationShaderInclude;
    }
    _updateLightOptions(options, scene, stdMat, objDefs, sortedLights) {
        options.lightMap = false;
        options.lightMapChannel = '';
        options.lightMapUv = 0;
        options.lightMapTransform = 0;
        options.litOptions.lightMapWithoutAmbient = false;
        options.dirLightMap = false;
        if (objDefs) {
            options.litOptions.noShadow = (objDefs & SHADERDEF_NOSHADOW) !== 0;
            if ((objDefs & SHADERDEF_LM) !== 0) {
                options.lightMapEncoding = scene.lightmapPixelFormat === PIXELFORMAT_RGBA8 ? 'rgbm' : 'linear';
                options.lightMap = true;
                options.lightMapChannel = 'rgb';
                options.lightMapUv = 1;
                options.lightMapTransform = 0;
                options.litOptions.lightMapWithoutAmbient = !stdMat.lightMap;
                if ((objDefs & SHADERDEF_DIRLM) !== 0) {
                    options.dirLightMap = true;
                }
                // if lightmaps contain baked ambient light, disable real-time ambient light
                if ((objDefs & SHADERDEF_LMAMBIENT) !== 0) {
                    options.litOptions.lightMapWithoutAmbient = false;
                }
            }
        }
        if (stdMat.useLighting) {
            const lightsFiltered = [];
            const mask = objDefs ? objDefs >> 16 : MASK_AFFECT_DYNAMIC;
            // mask to select lights (dynamic vs lightmapped) when using clustered lighting
            options.litOptions.lightMaskDynamic = !!(mask & MASK_AFFECT_DYNAMIC);
            if (sortedLights) {
                LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_DIRECTIONAL, sortedLights[LIGHTTYPE_DIRECTIONAL], lightsFiltered, mask);
                if (!scene.clusteredLightingEnabled) {
                    LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_OMNI, sortedLights[LIGHTTYPE_OMNI], lightsFiltered, mask);
                    LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_SPOT, sortedLights[LIGHTTYPE_SPOT], lightsFiltered, mask);
                }
            }
            options.litOptions.lights = lightsFiltered;
        } else {
            options.litOptions.lights = [];
        }
        if (options.litOptions.lights.length === 0 && !scene.clusteredLightingEnabled) {
            options.litOptions.noShadow = true;
        }
    }
    _getMapTransformID(xform, uv) {
        if (!xform) return 0;
        let xforms = this._mapXForms[uv];
        if (!xforms) {
            xforms = [];
            this._mapXForms[uv] = xforms;
        }
        for(let i = 0; i < xforms.length; i++){
            if (arraysEqual(xforms[i][0].value, xform[0].value) && arraysEqual(xforms[i][1].value, xform[1].value)) {
                return i + 1;
            }
        }
        return xforms.push(xform);
    }
}

function _textureParameter(name, channel = true, vertexColor = true) {
    const result = {};
    result[`${name}Map`] = 'texture';
    result[`${name}MapTiling`] = 'vec2';
    result[`${name}MapOffset`] = 'vec2';
    result[`${name}MapRotation`] = 'number';
    result[`${name}MapUv`] = 'number';
    if (channel) {
        result[`${name}MapChannel`] = 'string';
        if (vertexColor) {
            result[`${name}VertexColor`] = 'boolean';
            result[`${name}VertexColorChannel`] = 'string';
        }
    }
    return result;
}
const standardMaterialParameterTypes = {
    name: 'string',
    chunks: 'chunks',
    mappingFormat: 'string',
    _engine: 'boolean',
    ambient: 'rgb',
    ..._textureParameter('ao'),
    ..._textureParameter('aoDetail', true, false),
    aoDetailMode: 'string',
    aoIntensity: 'number',
    diffuse: 'rgb',
    ..._textureParameter('diffuse'),
    ..._textureParameter('diffuseDetail', true, false),
    diffuseDetailMode: 'string',
    specular: 'rgb',
    specularTint: 'boolean',
    ..._textureParameter('specular'),
    occludeSpecular: 'enum:occludeSpecular',
    specularityFactor: 'number',
    specularityFactorTint: 'boolean',
    ..._textureParameter('specularityFactor'),
    useMetalness: 'boolean',
    metalness: 'number',
    enableGGXSpecular: 'boolean',
    metalnessTint: 'boolean',
    ..._textureParameter('metalness'),
    useMetalnessSpecularColor: 'boolean',
    anisotropyIntensity: 'number',
    anisotropyRotation: 'number',
    ..._textureParameter('anisotropy'),
    shininess: 'number',
    gloss: 'number',
    glossInvert: 'boolean',
    ..._textureParameter('gloss'),
    clearCoat: 'number',
    ..._textureParameter('clearCoat'),
    clearCoatGloss: 'number',
    clearCoatGlossInvert: 'boolean',
    ..._textureParameter('clearCoatGloss'),
    clearCoatBumpiness: 'number',
    ..._textureParameter('clearCoatNormal', false),
    useSheen: 'boolean',
    sheen: 'rgb',
    ..._textureParameter('sheen'),
    sheenGloss: 'number',
    sheenGlossInvert: 'boolean',
    ..._textureParameter('sheenGloss'),
    fresnelModel: 'number',
    emissive: 'rgb',
    ..._textureParameter('emissive'),
    emissiveIntensity: 'number',
    ..._textureParameter('normal', false),
    bumpiness: 'number',
    // normalMapFactor: 'number', // TODO rename bumpiness to normalMapFactor
    ..._textureParameter('normalDetail', false),
    normalDetailMapBumpiness: 'number',
    ..._textureParameter('height', true, false),
    heightMapFactor: 'number',
    alphaToCoverage: 'boolean',
    alphaTest: 'number',
    alphaFade: 'number',
    opacity: 'number',
    ..._textureParameter('opacity'),
    opacityFadesSpecular: 'boolean',
    opacityDither: 'string',
    opacityShadowDither: 'string',
    reflectivity: 'number',
    refraction: 'number',
    refractionTint: 'boolean',
    ..._textureParameter('refraction'),
    refractionIndex: 'number',
    dispersion: 'number',
    thickness: 'number',
    thicknessTint: 'boolean',
    ..._textureParameter('thickness'),
    attenuation: 'rgb',
    attenuationDistance: 'number',
    useDynamicRefraction: 'boolean',
    sphereMap: 'texture',
    cubeMap: 'cubemap',
    cubeMapProjection: 'number',
    cubeMapProjectionBox: 'boundingbox',
    useIridescence: 'boolean',
    iridescence: 'number',
    iridescenceTint: 'boolean',
    ..._textureParameter('iridescence'),
    iridescenceThicknessTint: 'boolean',
    iridescenceThicknessMin: 'number',
    iridescenceThicknessMax: 'number',
    iridescenceRefractionIndex: 'number',
    ..._textureParameter('iridescenceThickness'),
    ..._textureParameter('light'),
    depthTest: 'boolean',
    depthFunc: 'enum:depthFunc',
    depthWrite: 'boolean',
    depthBias: 'number',
    slopeDepthBias: 'number',
    cull: 'enum:cull',
    blendType: 'enum:blendType',
    useFog: 'boolean',
    useLighting: 'boolean',
    useSkybox: 'boolean',
    useTonemap: 'boolean',
    envAtlas: 'texture',
    twoSidedLighting: 'boolean',
    shadowCatcher: 'boolean'
};
const standardMaterialTextureParameters = [];
for(const key in standardMaterialParameterTypes){
    const type = standardMaterialParameterTypes[key];
    if (type === 'texture') {
        standardMaterialTextureParameters.push(key);
    }
}
const standardMaterialCubemapParameters = [];
for(const key in standardMaterialParameterTypes){
    const type = standardMaterialParameterTypes[key];
    if (type === 'cubemap') {
        standardMaterialCubemapParameters.push(key);
    }
}

/**
 * @import { BoundingBox } from '../../core/shape/bounding-box.js'
 * @import { StandardMaterialOptions } from './standard-material-options.js'
 * @import { Texture } from '../../platform/graphics/texture.js'
 */ // properties that get created on a standard material
const _props = {};
// special uniform functions on a standard material
const _uniforms = {};
// temporary set of params
let _params = new Set();
const _tempColor = new Color();
/**
 * @callback UpdateShaderCallback
 * Callback used by {@link StandardMaterial#onUpdateShader}.
 * @param {StandardMaterialOptions} options - An object with shader generator settings (based on current
 * material and scene properties), that you can change and then return. Properties of the object passed
 * into this function are documented in {@link StandardMaterial}. Also contains a member named litOptions
 * which holds some of the options only used by the lit shader backend {@link LitShaderOptions}.
 * @returns {StandardMaterialOptions} Returned settings will be used by the shader.
 */ /**
 * A standard material is the main, general purpose material that is most often used for rendering.
 * It can approximate a wide variety of surface types and can simulate dynamic reflected light.
 * Most maps can use 3 types of input values in any combination: constant ({@link Color} or number),
 * mesh vertex colors and a {@link Texture}. All enabled inputs are multiplied together.
 *
 * @property {Color} ambient The ambient color of the material. This color value is 3-component
 * (RGB), where each component is between 0 and 1.
 * @property {Color} diffuse The diffuse color of the material. This color value is 3-component
 * (RGB), where each component is between 0 and 1. Defines basic surface color (aka albedo).
 * @property {Texture|null} diffuseMap The main (primary) diffuse map of the material (default is
 * null).
 * @property {number} diffuseMapUv Main (primary) diffuse map UV channel.
 * @property {Vec2} diffuseMapTiling Controls the 2D tiling of the main (primary) diffuse map.
 * @property {Vec2} diffuseMapOffset Controls the 2D offset of the main (primary) diffuse map. Each
 * component is between 0 and 1.
 * @property {number} diffuseMapRotation Controls the 2D rotation (in degrees) of the main
 * (primary) diffuse map.
 * @property {string} diffuseMapChannel Color channels of the main (primary) diffuse map to use.
 * Can be "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} diffuseVertexColor Multiply diffuse by the mesh vertex colors.
 * @property {string} diffuseVertexColorChannel Vertex color channels to use for diffuse. Can be
 * "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {Texture|null} diffuseDetailMap The detail (secondary) diffuse map of the material
 * (default is null). Will only be used if main (primary) diffuse map is non-null.
 * @property {number} diffuseDetailMapUv Detail (secondary) diffuse map UV channel.
 * @property {Vec2} diffuseDetailMapTiling Controls the 2D tiling of the detail (secondary) diffuse
 * map.
 * @property {Vec2} diffuseDetailMapOffset Controls the 2D offset of the detail (secondary) diffuse
 * map. Each component is between 0 and 1.
 * @property {number} diffuseDetailMapRotation Controls the 2D rotation (in degrees) of the main
 * (secondary) diffuse map.
 * @property {string} diffuseDetailMapChannel Color channels of the detail (secondary) diffuse map
 * to use. Can be "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {string} diffuseDetailMode Determines how the main (primary) and detail (secondary)
 * diffuse maps are blended together. Can be:
 *
 * - {@link DETAILMODE_MUL}: Multiply together the primary and secondary colors.
 * - {@link DETAILMODE_ADD}: Add together the primary and secondary colors.
 * - {@link DETAILMODE_SCREEN}: Softer version of {@link DETAILMODE_ADD}.
 * - {@link DETAILMODE_OVERLAY}: Multiplies or screens the colors, depending on the primary color.
 * - {@link DETAILMODE_MIN}: Select whichever of the primary and secondary colors is darker,
 * component-wise.
 * - {@link DETAILMODE_MAX}: Select whichever of the primary and secondary colors is lighter,
 * component-wise.
 *
 * Defaults to {@link DETAILMODE_MUL}.
 * @property {Color} specular The specular color of the material. This color value is 3-component
 * (RGB), where each component is between 0 and 1. Defines surface reflection/specular color.
 * Affects specular intensity and tint.
 * @property {boolean} specularTint Multiply specular map and/or specular vertex color by the
 * constant specular value.
 * @property {Texture|null} specularMap The specular map of the material (default is null).
 * @property {number} specularMapUv Specular map UV channel.
 * @property {Vec2} specularMapTiling Controls the 2D tiling of the specular map.
 * @property {Vec2} specularMapOffset Controls the 2D offset of the specular map. Each component is
 * between 0 and 1.
 * @property {number} specularMapRotation Controls the 2D rotation (in degrees) of the specular map.
 * @property {string} specularMapChannel Color channels of the specular map to use. Can be "r", "g",
 * "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} specularVertexColor Use mesh vertex colors for specular. If specularMap or
 * are specularTint are set, they'll be multiplied by vertex colors.
 * @property {string} specularVertexColorChannel Vertex color channels to use for specular. Can be
 * "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} specularityFactorTint Multiply specularity factor map and/or specular vertex color by the
 * constant specular value.
 * @property {number} specularityFactor The factor of specular intensity, used to weight the fresnel and specularity. Default is 1.0.
 * @property {Texture|null} specularityFactorMap The factor of specularity as a texture (default is
 * null).
 * @property {number} specularityFactorMapUv Specularity factor map UV channel.
 * @property {Vec2} specularityFactorMapTiling Controls the 2D tiling of the specularity factor map.
 * @property {Vec2} specularityFactorMapOffset Controls the 2D offset of the specularity factor map. Each component is
 * between 0 and 1.
 * @property {number} specularityFactorMapRotation Controls the 2D rotation (in degrees) of the specularity factor map.
 * @property {string} specularityFactorMapChannel The channel used by the specularity factor texture to sample from (default is 'a').
 * @property {boolean} specularityFactorVertexColor Use mesh vertex colors for specularity factor. If specularityFactorMap or
 * are specularityFactorTint are set, they'll be multiplied by vertex colors.
 * @property {string} specularityFactorVertexColorChannel Vertex color channels to use for specularity factor. Can be
 * "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} enableGGXSpecular Enables GGX specular. Also enables
 * {@link StandardMaterial#anisotropyIntensity} parameter to set material anisotropy.
 * @property {number} anisotropyIntensity Defines amount of anisotropy. Requires
 * {@link StandardMaterial#enableGGXSpecular} is set to true.
 * - When anisotropyIntensity == 0, specular is isotropic.
 * - Specular anisotropy increases as anisotropyIntensity value increases to maximum of 1.
 * @property {number} anisotropyRotation Defines the rotation (in degrees) of anisotropy.
 * @property {Texture|null} anisotropyMap The anisotropy map of the material (default is null).
 * @property {number} anisotropyMapUv Anisotropy map UV channel.
 * @property {Vec2} anisotropyMapTiling Controls the 2D tiling of the anisotropy map.
 * @property {Vec2} anisotropyMapOffset Controls the 2D offset of the anisotropy map. Each
 * component is between 0 and 1.
 * @property {number} anisotropyMapRotation Controls the 2D rotation (in degrees) of the anisotropy map.
 * @property {number} clearCoat Defines intensity of clearcoat layer from 0 to 1. Clearcoat layer
 * is disabled when clearCoat == 0. Default value is 0 (disabled).
 * @property {Texture|null} clearCoatMap Monochrome clearcoat intensity map (default is null). If
 * specified, will be multiplied by normalized 'clearCoat' value and/or vertex colors.
 * @property {number} clearCoatMapUv Clearcoat intensity map UV channel.
 * @property {Vec2} clearCoatMapTiling Controls the 2D tiling of the clearcoat intensity map.
 * @property {Vec2} clearCoatMapOffset Controls the 2D offset of the clearcoat intensity map. Each
 * component is between 0 and 1.
 * @property {number} clearCoatMapRotation Controls the 2D rotation (in degrees) of the clearcoat
 * intensity map.
 * @property {string} clearCoatMapChannel Color channel of the clearcoat intensity map to use. Can
 * be "r", "g", "b" or "a".
 * @property {boolean} clearCoatVertexColor Use mesh vertex colors for clearcoat intensity. If
 * clearCoatMap is set, it'll be multiplied by vertex colors.
 * @property {string} clearCoatVertexColorChannel Vertex color channel to use for clearcoat
 * intensity. Can be "r", "g", "b" or "a".
 * @property {number} clearCoatGloss Defines the clearcoat glossiness of the clearcoat layer
 * from 0 (rough) to 1 (mirror).
 * @property {boolean} clearCoatGlossInvert Invert the clearcoat gloss component (default is false).
 * Enabling this flag results in material treating the clear coat gloss members as roughness.
 * @property {Texture|null} clearCoatGlossMap Monochrome clearcoat glossiness map (default is
 * null). If specified, will be multiplied by normalized 'clearCoatGloss' value and/or vertex
 * colors.
 * @property {number} clearCoatGlossMapUv Clearcoat gloss map UV channel.
 * @property {Vec2} clearCoatGlossMapTiling Controls the 2D tiling of the clearcoat gloss map.
 * @property {Vec2} clearCoatGlossMapOffset Controls the 2D offset of the clearcoat gloss map.
 * Each component is between 0 and 1.
 * @property {number} clearCoatGlossMapRotation Controls the 2D rotation (in degrees) of the clear
 * coat gloss map.
 * @property {string} clearCoatGlossMapChannel Color channel of the clearcoat gloss map to use.
 * Can be "r", "g", "b" or "a".
 * @property {boolean} clearCoatGlossVertexColor Use mesh vertex colors for clearcoat glossiness.
 * If clearCoatGlossMap is set, it'll be multiplied by vertex colors.
 * @property {string} clearCoatGlossVertexColorChannel Vertex color channel to use for clearcoat
 * glossiness. Can be "r", "g", "b" or "a".
 * @property {Texture|null} clearCoatNormalMap The clearcoat normal map of the material (default is
 * null). The texture must contains normalized, tangent space normals.
 * @property {number} clearCoatNormalMapUv Clearcoat normal map UV channel.
 * @property {Vec2} clearCoatNormalMapTiling Controls the 2D tiling of the main clearcoat normal
 * map.
 * @property {Vec2} clearCoatNormalMapOffset Controls the 2D offset of the main clearcoat normal
 * map. Each component is between 0 and 1.
 * @property {number} clearCoatNormalMapRotation Controls the 2D rotation (in degrees) of the main
 * clearcoat map.
 * @property {number} clearCoatBumpiness The bumpiness of the clearcoat layer. This value scales
 * the assigned main clearcoat normal map. It should be normally between 0 (no bump mapping) and 1
 * (full bump mapping), but can be set to e.g. 2 to give even more pronounced bump effect.
 * @property {boolean} useIridescence Enable thin-film iridescence.
 * @property {Texture|null} iridescenceMap The per-pixel iridescence intensity. Only used when
 * useIridescence is enabled.
 * @property {number} iridescenceMapUv Iridescence map UV channel.
 * @property {Vec2} iridescenceMapTiling Controls the 2D tiling of the iridescence map.
 * @property {Vec2} iridescenceMapOffset Controls the 2D offset of the iridescence map. Each component is
 * between 0 and 1.
 * @property {number} iridescenceMapRotation Controls the 2D rotation (in degrees) of the iridescence
 * map.
 * @property {string} iridescenceMapChannel Color channels of the iridescence map to use. Can be "r",
 * "g", "b" or "a".
 * @property {Texture|null} iridescenceThicknessMap The per-pixel iridescence thickness. Defines a
 * gradient weight between iridescenceThicknessMin and iridescenceThicknessMax. Only used when
 * useIridescence is enabled.
 * @property {number} iridescenceThicknessMapUv Iridescence thickness map UV channel.
 * @property {Vec2} iridescenceThicknessMapTiling Controls the 2D tiling of the iridescence
 * thickness map.
 * @property {Vec2} iridescenceThicknessMapOffset Controls the 2D offset of the iridescence
 * thickness map. Each component is between 0 and 1.
 * @property {number} iridescenceThicknessMapRotation Controls the 2D rotation (in degrees)
 * of the iridescence map.
 * @property {string} iridescenceThicknessMapChannel Color channels of the iridescence thickness
 * map to use. Can be "r", "g", "b" or "a".
 * @property {number} iridescenceThicknessMin The minimum thickness for the iridescence layer.
 * Only used when an iridescence thickness map is used. The unit is in nm.
 * @property {number} iridescenceThicknessMax The maximum thickness for the iridescence layer.
 * Used as the 'base' thickness when no iridescence thickness map is defined. The unit is in nm.
 * @property {number} iridescenceRefractionIndex The index of refraction of the iridescent
 * thin-film. Affects the color phase shift as described here:
 * https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_iridescence
 * @property {boolean} useMetalness Use metalness properties instead of specular. When enabled,
 * diffuse colors also affect specular instead of the dedicated specular map. This can be used as
 * alternative to specular color to save space. With metalness == 0, the pixel is assumed to be
 * dielectric, and diffuse color is used as normal. With metalness == 1, the pixel is fully
 * metallic, and diffuse color is used as specular color instead.
 * @property {boolean} useMetalnessSpecularColor When metalness is enabled, use the
 * specular map to apply color tint to specular reflections.
 * at direct angles.
 * @property {number} metalness Defines how much the surface is metallic. From 0 (dielectric) to 1
 * (metal).
 * @property {Texture|null} metalnessMap Monochrome metalness map (default is null).
 * @property {number} metalnessMapUv Metalness map UV channel.
 * @property {Vec2} metalnessMapTiling Controls the 2D tiling of the metalness map.
 * @property {Vec2} metalnessMapOffset Controls the 2D offset of the metalness map. Each component
 * is between 0 and 1.
 * @property {number} metalnessMapRotation Controls the 2D rotation (in degrees) of the metalness
 * map.
 * @property {string} metalnessMapChannel Color channel of the metalness map to use. Can be "r",
 * "g", "b" or "a".
 * @property {boolean} metalnessVertexColor Use mesh vertex colors for metalness. If metalnessMap
 * is set, it'll be multiplied by vertex colors.
 * @property {string} metalnessVertexColorChannel Vertex color channel to use for metalness. Can be
 * "r", "g", "b" or "a".
 * @property {number} gloss Defines the glossiness of the material from 0 (rough) to 1 (shiny).
 * @property {Texture|null} glossMap Gloss map (default is null). If specified, will be multiplied
 * by normalized gloss value and/or vertex colors.
 * @property {boolean} glossInvert Invert the gloss component (default is false). Enabling this
 * flag results in material treating the gloss members as roughness.
 * @property {number} glossMapUv Gloss map UV channel.
 * @property {string} glossMapChannel Color channel of the gloss map to use. Can be "r", "g", "b"
 * or "a".
 * @property {Vec2} glossMapTiling Controls the 2D tiling of the gloss map.
 * @property {Vec2} glossMapOffset Controls the 2D offset of the gloss map. Each component is
 * between 0 and 1.
 * @property {number} glossMapRotation Controls the 2D rotation (in degrees) of the gloss map.
 * @property {boolean} glossVertexColor Use mesh vertex colors for glossiness. If glossMap is set,
 * it'll be multiplied by vertex colors.
 * @property {string} glossVertexColorChannel Vertex color channel to use for glossiness. Can be
 * "r", "g", "b" or "a".
 * @property {number} refraction Defines the visibility of refraction. Material can refract the
 * same cube map as used for reflections.
 * @property {Texture|null} refractionMap The map of the refraction visibility.
 * @property {number} refractionMapUv Refraction map UV channel.
 * @property {Vec2} refractionMapTiling Controls the 2D tiling of the refraction map.
 * @property {Vec2} refractionMapOffset Controls the 2D offset of the refraction map. Each component
 * is between 0 and 1.
 * @property {number} refractionMapRotation Controls the 2D rotation (in degrees) of the emissive
 * map.
 * @property {string} refractionMapChannel Color channels of the refraction map to use. Can be "r",
 * "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} refractionVertexColor Use mesh vertex colors for refraction. If
 * refraction map is set, it will be be multiplied by vertex colors.
 * @property {boolean} refractionVertexColorChannel Vertex color channel to use for refraction.
 * Can be "r", "g", "b" or "a".
 * @property {number} refractionIndex Defines the index of refraction, i.e. The amount of
 * distortion. The value is calculated as (outerIor / surfaceIor), where inputs are measured
 * indices of refraction, the one around the object and the one of its own surface. In most
 * situations outer medium is air, so outerIor will be approximately 1. Then you only need to do
 * (1.0 / surfaceIor).
 * @property {number} dispersion The strength of the angular separation of colors (chromatic
 * aberration) transmitting through a volume. Defaults to 0, which is equivalent to no dispersion.
 * @property {boolean} useDynamicRefraction Enables higher quality refractions using the grab pass
 * instead of pre-computed cube maps for refractions.
 * @property {number} thickness The thickness of the medium, only used when useDynamicRefraction
 * is enabled. The unit is in base units, and scales with the size of the object.
 * @property {Texture|null} thicknessMap The per-pixel thickness of the medium, only used when
 * useDynamicRefraction is enabled.
 * @property {number} thicknessMapUv Thickness map UV channel.
 * @property {Vec2} thicknessMapTiling Controls the 2D tiling of the thickness map.
 * @property {Vec2} thicknessMapOffset Controls the 2D offset of the thickness map. Each component is
 * between 0 and 1.
 * @property {number} thicknessMapRotation Controls the 2D rotation (in degrees) of the thickness
 * map.
 * @property {string} thicknessMapChannel Color channels of the thickness map to use. Can be "r",
 * "g", "b" or "a".
 * @property {boolean} thicknessVertexColor Use mesh vertex colors for thickness. If
 * thickness map is set, it will be be multiplied by vertex colors.
 * @property {Color} attenuation The attenuation color for refractive materials, only used when
 * useDynamicRefraction is enabled.
 * @property {number} attenuationDistance The distance defining the absorption rate of light
 * within the medium. Only used when useDynamicRefraction is enabled.
 * @property {Color} emissive The emissive color of the material. This color value is 3-component
 * (RGB), where each component is between 0 and 1.
 * @property {Texture|null} emissiveMap The emissive map of the material (default is null). Can be
 * HDR. When the emissive map is applied, the emissive color is multiplied by the texel color in the
 * map. Since the emissive color is black by default, the emissive map won't be visible unless the
 * emissive color is changed.
 * @property {number} emissiveIntensity Emissive color multiplier.
 * @property {number} emissiveMapUv Emissive map UV channel.
 * @property {Vec2} emissiveMapTiling Controls the 2D tiling of the emissive map.
 * @property {Vec2} emissiveMapOffset Controls the 2D offset of the emissive map. Each component is
 * between 0 and 1.
 * @property {number} emissiveMapRotation Controls the 2D rotation (in degrees) of the emissive
 * map.
 * @property {string} emissiveMapChannel Color channels of the emissive map to use. Can be "r",
 * "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} emissiveVertexColor Use mesh vertex colors for emission. If emissiveMap or
 * emissive are set, they'll be multiplied by vertex colors.
 * @property {string} emissiveVertexColorChannel Vertex color channels to use for emission. Can be
 * "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} useSheen Toggle sheen specular effect on/off.
 * @property {Color} sheen The specular color of the sheen (fabric) microfiber structure.
 * This color value is 3-component (RGB), where each component is between 0 and 1.
 * @property {Texture|null} sheenMap The sheen microstructure color map of the material (default is
 * null).
 * @property {number} sheenMapUv Sheen map UV channel.
 * @property {Vec2} sheenMapTiling Controls the 2D tiling of the sheen map.
 * @property {Vec2} sheenMapOffset Controls the 2D offset of the sheen map. Each component is
 * between 0 and 1.
 * @property {number} sheenMapRotation Controls the 2D rotation (in degrees) of the sheen
 * map.
 * @property {string} sheenMapChannel Color channels of the sheen map to use. Can be "r",
 * "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} sheenVertexColor Use mesh vertex colors for sheen. If sheen map or
 * sheen tint are set, they'll be multiplied by vertex colors.
 * @property {number} sheenGloss The glossiness of the sheen (fabric) microfiber structure.
 * This color value is a single value between 0 and 1.
 * @property {boolean} sheenGlossInvert Invert the sheen gloss component (default is false).
 * Enabling this flag results in material treating the sheen gloss members as roughness.
 * @property {Texture|null} sheenGlossMap The sheen glossiness microstructure color map of the
 * material (default is null).
 * @property {number} sheenGlossMapUv Sheen map UV channel.
 * @property {Vec2} sheenGlossMapTiling Controls the 2D tiling of the sheen glossiness map.
 * @property {Vec2} sheenGlossMapOffset Controls the 2D offset of the sheen glossiness map.
 * Each component is between 0 and 1.
 * @property {number} sheenGlossMapRotation Controls the 2D rotation (in degrees) of the sheen
 * glossiness map.
 * @property {string} sheenGlossMapChannel Color channels of the sheen glossiness map to use.
 * Can be "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {boolean} sheenGlossVertexColor Use mesh vertex colors for sheen glossiness.
 * If sheen glossiness map or sheen glossiness tint are set, they'll be multiplied by vertex colors.
 * @property {string} sheenGlossVertexColorChannel Vertex color channels to use for sheen glossiness.
 * Can be "r", "g", "b" or "a".
 * @property {number} opacity The opacity of the material. This value can be between 0 and 1, where
 * 0 is fully transparent and 1 is fully opaque. If you want the material to be semi-transparent
 * you also need to set the {@link Material#blendType} to {@link BLEND_NORMAL},
 * {@link BLEND_ADDITIVE} or any other mode. Also note that for most semi-transparent objects you
 * want {@link Material#depthWrite} to be false, otherwise they can fully occlude objects behind
 * them.
 * @property {Texture|null} opacityMap The opacity map of the material (default is null).
 * @property {number} opacityMapUv Opacity map UV channel.
 * @property {string} opacityMapChannel Color channel of the opacity map to use. Can be "r", "g",
 * "b" or "a".
 * @property {Vec2} opacityMapTiling Controls the 2D tiling of the opacity map.
 * @property {Vec2} opacityMapOffset Controls the 2D offset of the opacity map. Each component is
 * between 0 and 1.
 * @property {number} opacityMapRotation Controls the 2D rotation (in degrees) of the opacity map.
 * @property {boolean} opacityVertexColor Use mesh vertex colors for opacity. If opacityMap is set,
 * it'll be multiplied by vertex colors.
 * @property {string} opacityVertexColorChannel Vertex color channels to use for opacity. Can be
 * "r", "g", "b" or "a".
 * @property {boolean} opacityFadesSpecular Used to specify whether specular and reflections are
 * faded out using {@link StandardMaterial#opacity}. Default is true. When set to false use
 * {@link Material#alphaFade} to fade out materials.
 * @property {string} opacityDither Used to specify whether opacity is dithered, which allows
 * transparency without alpha blending. Can be:
 *
 * - {@link DITHER_NONE}: Opacity dithering is disabled.
 * - {@link DITHER_BAYER8}: Opacity is dithered using a Bayer 8 matrix.
 * - {@link DITHER_BLUENOISE}: Opacity is dithered using a blue noise.
 * - {@link DITHER_IGNNOISE}: Opacity is dithered using an interleaved gradient noise.
 *
 * Defaults to {@link DITHER_NONE}.
 * @property {boolean} opacityShadowDither Used to specify whether shadow opacity is dithered, which
 * allows shadow transparency without alpha blending.  Can be:
 *
 * - {@link DITHER_NONE}: Opacity dithering is disabled.
 * - {@link DITHER_BAYER8}: Opacity is dithered using a Bayer 8 matrix.
 * - {@link DITHER_BLUENOISE}: Opacity is dithered using a blue noise.
 * - {@link DITHER_IGNNOISE}: Opacity is dithered using an interleaved gradient noise.
 *
 * Defaults to {@link DITHER_NONE}.
 * @property {number} alphaFade Used to fade out materials when
 * {@link StandardMaterial#opacityFadesSpecular} is set to false.
 * @property {Texture|null} normalMap The main (primary) normal map of the material (default is
 * null). The texture must contains normalized, tangent space normals.
 * @property {number} normalMapUv Main (primary) normal map UV channel.
 * @property {Vec2} normalMapTiling Controls the 2D tiling of the main (primary) normal map.
 * @property {Vec2} normalMapOffset Controls the 2D offset of the main (primary) normal map. Each
 * component is between 0 and 1.
 * @property {number} normalMapRotation Controls the 2D rotation (in degrees) of the main (primary)
 * normal map.
 * @property {number} bumpiness The bumpiness of the material. This value scales the assigned main
 * (primary) normal map. It should be normally between 0 (no bump mapping) and 1 (full bump
 * mapping), but can be set to e.g. 2 to give even more pronounced bump effect.
 * @property {Texture|null} normalDetailMap The detail (secondary) normal map of the material
 * (default is null). Will only be used if main (primary) normal map is non-null.
 * @property {number} normalDetailMapUv Detail (secondary) normal map UV channel.
 * @property {Vec2} normalDetailMapTiling Controls the 2D tiling of the detail (secondary) normal
 * map.
 * @property {Vec2} normalDetailMapOffset Controls the 2D offset of the detail (secondary) normal
 * map. Each component is between 0 and 1.
 * @property {number} normalDetailMapRotation Controls the 2D rotation (in degrees) of the detail
 * (secondary) normal map.
 * @property {number} normalDetailMapBumpiness The bumpiness of the material. This value scales the
 * assigned detail (secondary) normal map. It should be normally between 0 (no bump mapping) and 1
 * (full bump mapping), but can be set to e.g. 2 to give even more pronounced bump effect.
 * @property {Texture|null} heightMap The height map of the material (default is null). Used for a
 * view-dependent parallax effect. The texture must represent the height of the surface where
 * darker pixels are lower and lighter pixels are higher. It is recommended to use it together with
 * a normal map.
 * @property {number} heightMapUv Height map UV channel.
 * @property {string} heightMapChannel Color channel of the height map to use. Can be "r", "g", "b"
 * or "a".
 * @property {Vec2} heightMapTiling Controls the 2D tiling of the height map.
 * @property {Vec2} heightMapOffset Controls the 2D offset of the height map. Each component is
 * between 0 and 1.
 * @property {number} heightMapRotation Controls the 2D rotation (in degrees) of the height map.
 * @property {number} heightMapFactor Height map multiplier. Affects the strength of the parallax
 * effect.
 * @property {Texture|null} envAtlas The prefiltered environment lighting atlas (default is null).
 * This setting overrides cubeMap and sphereMap and will replace the scene lighting environment.
 * @property {Texture|null} cubeMap The cubic environment map of the material (default is null).
 * This setting overrides sphereMap and will replace the scene lighting environment.
 * @property {Texture|null} sphereMap The spherical environment map of the material (default is
 * null). This will replace the scene lighting environment.
 * @property {number} cubeMapProjection The type of projection applied to the cubeMap property:
 * - {@link CUBEPROJ_NONE}: The cube map is treated as if it is infinitely far away.
 * - {@link CUBEPROJ_BOX}: Box-projection based on a world space axis-aligned bounding box.
 * Defaults to {@link CUBEPROJ_NONE}.
 * @property {BoundingBox} cubeMapProjectionBox The world space axis-aligned bounding box
 * defining the box-projection used for the cubeMap property. Only used when cubeMapProjection is
 * set to {@link CUBEPROJ_BOX}.
 * @property {number} reflectivity Environment map intensity.
 * @property {Texture|null} lightMap A custom lightmap of the material (default is null). Lightmaps
 * are textures that contain pre-rendered lighting. Can be HDR.
 * @property {number} lightMapUv Lightmap UV channel
 * @property {string} lightMapChannel Color channels of the lightmap to use. Can be "r", "g", "b",
 * "a", "rgb" or any swizzled combination.
 * @property {Vec2} lightMapTiling Controls the 2D tiling of the lightmap.
 * @property {Vec2} lightMapOffset Controls the 2D offset of the lightmap. Each component is
 * between 0 and 1.
 * @property {number} lightMapRotation Controls the 2D rotation (in degrees) of the lightmap.
 * @property {boolean} lightVertexColor Use baked vertex lighting. If lightMap is set, it'll be
 * multiplied by vertex colors.
 * @property {string} lightVertexColorChannel Vertex color channels to use for baked lighting. Can
 * be "r", "g", "b", "a", "rgb" or any swizzled combination.
 * @property {number} aoIntensity Ambient occlusion intensity. Defaults to 1.
 * @property {Texture|null} aoMap The main (primary) baked ambient occlusion (AO) map (default is
 * null). Modulates ambient color.
 * @property {number} aoMapUv Main (primary) AO map UV channel
 * @property {string} aoMapChannel Color channel of the main (primary) AO map to use. Can be "r", "g", "b" or "a".
 * @property {Vec2} aoMapTiling Controls the 2D tiling of the main (primary) AO map.
 * @property {Vec2} aoMapOffset Controls the 2D offset of the main (primary) AO map. Each component is between 0
 * and 1.
 * @property {number} aoMapRotation Controls the 2D rotation (in degrees) of the main (primary) AO map.
 * @property {boolean} aoVertexColor Use mesh vertex colors for AO. If aoMap is set, it'll be
 * multiplied by vertex colors.
 * @property {string} aoVertexColorChannel Vertex color channels to use for AO. Can be "r", "g",
 * "b" or "a".
 * @property {Texture|null} aoDetailMap The detail (secondary) baked ambient occlusion (AO) map of
 * the material (default is null). Will only be used if main (primary) ao map is non-null.
 * @property {number} aoDetailMapUv Detail (secondary) AO map UV channel.
 * @property {Vec2} aoDetailMapTiling Controls the 2D tiling of the detail (secondary) AO map.
 * @property {Vec2} aoDetailMapOffset Controls the 2D offset of the detail (secondary) AO map. Each
 * component is between 0 and 1.
 * @property {number} aoDetailMapRotation Controls the 2D rotation (in degrees) of the detail
 * (secondary) AO map.
 * @property {string} aoDetailMapChannel Color channels of the detail (secondary) AO map to use.
 * Can be "r", "g", "b" or "a" (default is "g").
 * @property {string} aoDetailMode Determines how the main (primary) and detail (secondary)
 * AO maps are blended together. Can be:
 *
 * - {@link DETAILMODE_MUL}: Multiply together the primary and secondary colors.
 * - {@link DETAILMODE_ADD}: Add together the primary and secondary colors.
 * - {@link DETAILMODE_SCREEN}: Softer version of {@link DETAILMODE_ADD}.
 * - {@link DETAILMODE_OVERLAY}: Multiplies or screens the colors, depending on the primary color.
 * - {@link DETAILMODE_MIN}: Select whichever of the primary and secondary colors is darker,
 * component-wise.
 * - {@link DETAILMODE_MAX}: Select whichever of the primary and secondary colors is lighter,
 * component-wise.
 *
 * Defaults to {@link DETAILMODE_MUL}.
 * @property {number} occludeSpecular Uses ambient occlusion to darken specular/reflection. It's a
 * hack, because real specular occlusion is view-dependent. However, it can be better than nothing.
 *
 * - {@link SPECOCC_NONE}: No specular occlusion
 * - {@link SPECOCC_AO}: Use AO directly to occlude specular.
 * - {@link SPECOCC_GLOSSDEPENDENT}: Modify AO based on material glossiness/view angle to occlude
 * specular.
 *
 * @property {number} occludeSpecularIntensity Controls visibility of specular occlusion.
 * @property {boolean} occludeDirect Tells if AO should darken directional lighting. Defaults to
 * false.
 * @property {number} fresnelModel Defines the formula used for Fresnel effect.
 * As a side-effect, enabling any Fresnel model changes the way diffuse and reflection components
 * are combined. When Fresnel is off, legacy non energy-conserving combining is used. When it is
 * on, combining behavior is energy-conserving.
 *
 * - {@link FRESNEL_NONE}: No Fresnel.
 * - {@link FRESNEL_SCHLICK}: Schlick's approximation of Fresnel (recommended). Parameterized by
 * specular color.
 *
 * @property {boolean} useFog Apply fogging (as configured in scene settings)
 * @property {boolean} useLighting Apply lighting
 * @property {boolean} useSkybox Apply scene skybox as prefiltered environment map
 * @property {boolean} useTonemap Apply tonemapping (as configured in {@link Scene#rendering} or
 * {@link CameraComponent.rendering}). Defaults to true.
 * @property {boolean} pixelSnap Align vertices to pixel coordinates when rendering. Useful for
 * pixel perfect 2D graphics.
 * @property {boolean} twoSidedLighting Calculate proper normals (and therefore lighting) on
 * backfaces.
 * @property {boolean} shadowCatcher When enabled, the material will output accumulated directional
 * shadow value in linear space as the color.
 *
 * @category Graphics
 */ class StandardMaterial extends Material {
    static{
        this.TEXTURE_PARAMETERS = standardMaterialTextureParameters;
    }
    static{
        this.CUBEMAP_PARAMETERS = standardMaterialCubemapParameters;
    }
    /**
     * Create a new StandardMaterial instance.
     *
     * @example
     * // Create a new Standard material
     * const material = new pc.StandardMaterial();
     *
     * // Update the material's diffuse and specular properties
     * material.diffuse.set(1, 0, 0);
     * material.specular.set(1, 1, 1);
     *
     * // Notify the material that it has been modified
     * material.update();
     * @example
     * // Create a new Standard material
     * const material = new pc.StandardMaterial();
     *
     * // Assign a texture to the diffuse slot
     * material.diffuseMap = texture;
     *
     * // Use the alpha channel of the texture for alpha testing with a reference value of 0.5
     * material.opacityMap = texture;
     * material.alphaTest = 0.5;
     *
     * // Notify the material that it has been modified
     * material.update();
     */ constructor(){
        super(), this.userAttributes = new Map();
        // storage for texture and cubemap asset references
        this._assetReferences = {};
        this._activeParams = new Set();
        this._activeLightingParams = new Set();
        this.shaderOptBuilder = new StandardMaterialOptionsBuilder();
        this.reset();
    }
    reset() {
        // set default values
        Object.keys(_props).forEach((name)=>{
            this[`_${name}`] = _props[name].value();
        });
        this._uniformCache = {};
    }
    /**
     * Copy a `StandardMaterial`.
     *
     * @param {StandardMaterial} source - The material to copy from.
     * @returns {StandardMaterial} The destination material.
     */ copy(source) {
        super.copy(source);
        // set properties
        Object.keys(_props).forEach((k)=>{
            this[k] = source[k];
        });
        // clone user attributes
        this.userAttributes = new Map(source.userAttributes);
        return this;
    }
    /**
     * Sets a vertex shader attribute on a material.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {string} semantic - Semantic to map the vertex data. Must match with the semantic set
     * on vertex stream of the mesh.
     * @example
     * mesh.setVertexStream(pc.SEMANTIC_ATTR15, offset, 3);
     * material.setAttribute('offset', pc.SEMANTIC_ATTR15);
     */ setAttribute(name, semantic) {
        this.userAttributes.set(semantic, name);
    }
    _setParameter(name, value) {
        _params.add(name);
        this.setParameter(name, value);
    }
    _setParameters(parameters) {
        parameters.forEach((v)=>{
            this._setParameter(v.name, v.value);
        });
    }
    _processParameters(paramsName) {
        const prevParams = this[paramsName];
        prevParams.forEach((param)=>{
            if (!_params.has(param)) {
                delete this.parameters[param];
            }
        });
        this[paramsName] = _params;
        _params = prevParams;
        _params.clear();
    }
    _updateMap(p) {
        const mname = `${p}Map`;
        const map = this[mname];
        if (map) {
            this._setParameter(`texture_${mname}`, map);
            const tname = `${mname}Transform`;
            const uniform = this.getUniform(tname);
            if (uniform) {
                this._setParameters(uniform);
            }
        }
    }
    // allocate a uniform if it doesn't already exist in the uniform cache
    _allocUniform(name, allocFunc) {
        let uniform = this._uniformCache[name];
        if (!uniform) {
            uniform = allocFunc();
            this._uniformCache[name] = uniform;
        }
        return uniform;
    }
    getUniform(name, device, scene) {
        return _uniforms[name](this, device, scene);
    }
    updateUniforms(device, scene) {
        const getUniform = (name)=>{
            return this.getUniform(name, device, scene);
        };
        this._setParameter('material_ambient', getUniform('ambient'));
        this._setParameter('material_diffuse', getUniform('diffuse'));
        this._setParameter('material_aoIntensity', this.aoIntensity);
        if (this.useMetalness) {
            if (!this.metalnessMap || this.metalness < 1) {
                this._setParameter('material_metalness', this.metalness);
            }
            if (!this.specularMap || this.specularTint) {
                this._setParameter('material_specular', getUniform('specular'));
            }
            if (!this.specularityFactorMap || this.specularityFactorTint) {
                this._setParameter('material_specularityFactor', this.specularityFactor);
            }
            this._setParameter('material_sheen', getUniform('sheen'));
            this._setParameter('material_sheenGloss', this.sheenGloss);
            this._setParameter('material_refractionIndex', this.refractionIndex);
        } else {
            if (!this.specularMap || this.specularTint) {
                this._setParameter('material_specular', getUniform('specular'));
            }
        }
        if (this.enableGGXSpecular) {
            this._setParameter('material_anisotropyIntensity', this.anisotropyIntensity);
            this._setParameter('material_anisotropyRotation', [
                Math.cos(this.anisotropyRotation * math.DEG_TO_RAD),
                Math.sin(this.anisotropyRotation * math.DEG_TO_RAD)
            ]);
        }
        if (this.clearCoat > 0) {
            this._setParameter('material_clearCoat', this.clearCoat);
            this._setParameter('material_clearCoatGloss', this.clearCoatGloss);
            this._setParameter('material_clearCoatBumpiness', this.clearCoatBumpiness);
        }
        this._setParameter('material_gloss', this.gloss);
        Debug.call(()=>{
            if (this.emissiveMap && this.emissive.r === 0 && this.emissive.g === 0 && this.emissive.b === 0) {
                Debug.warnOnce(`Emissive map is set but emissive color is black, making the map invisible. Set emissive color to white to make the map visible. Rendering [${DebugGraphics.toString()}]`, this);
            }
        });
        this._setParameter('material_emissive', getUniform('emissive'));
        this._setParameter('material_emissiveIntensity', this.emissiveIntensity);
        if (this.refraction > 0) {
            this._setParameter('material_refraction', this.refraction);
        }
        if (this.dispersion > 0) {
            this._setParameter('material_dispersion', this.dispersion);
        }
        if (this.useDynamicRefraction) {
            this._setParameter('material_thickness', this.thickness);
            this._setParameter('material_attenuation', getUniform('attenuation'));
            this._setParameter('material_invAttenuationDistance', this.attenuationDistance === 0 ? 0 : 1.0 / this.attenuationDistance);
        }
        if (this.useIridescence) {
            this._setParameter('material_iridescence', this.iridescence);
            this._setParameter('material_iridescenceRefractionIndex', this.iridescenceRefractionIndex);
            this._setParameter('material_iridescenceThicknessMin', this.iridescenceThicknessMin);
            this._setParameter('material_iridescenceThicknessMax', this.iridescenceThicknessMax);
        }
        this._setParameter('material_opacity', this.opacity);
        if (this.opacityFadesSpecular === false) {
            this._setParameter('material_alphaFade', this.alphaFade);
        }
        if (this.occludeSpecular) {
            this._setParameter('material_occludeSpecularIntensity', this.occludeSpecularIntensity);
        }
        if (this.cubeMapProjection === CUBEPROJ_BOX) {
            this._setParameter(getUniform('cubeMapProjectionBox'));
        }
        for(const p in _matTex2D){
            this._updateMap(p);
        }
        if (this.ambientSH) {
            this._setParameter('ambientSH[0]', this.ambientSH);
        }
        if (this.normalMap) {
            this._setParameter('material_bumpiness', this.bumpiness);
        }
        if (this.normalMap && this.normalDetailMap) {
            this._setParameter('material_normalDetailMapBumpiness', this.normalDetailMapBumpiness);
        }
        if (this.heightMap) {
            this._setParameter('material_heightMapFactor', getUniform('heightMapFactor'));
        }
        // set overridden environment textures
        if (this.envAtlas && this.cubeMap) {
            this._setParameter('texture_envAtlas', this.envAtlas);
            this._setParameter('texture_cubeMap', this.cubeMap);
        } else if (this.envAtlas) {
            this._setParameter('texture_envAtlas', this.envAtlas);
        } else if (this.cubeMap) {
            this._setParameter('texture_cubeMap', this.cubeMap);
        } else if (this.sphereMap) {
            this._setParameter('texture_sphereMap', this.sphereMap);
        }
        this._setParameter('material_reflectivity', this.reflectivity);
        // remove unused params
        this._processParameters('_activeParams');
        super.updateUniforms(device, scene);
    }
    updateEnvUniforms(device, scene) {
        const hasLocalEnvOverride = this.envAtlas || this.cubeMap || this.sphereMap;
        if (!hasLocalEnvOverride && this.useSkybox) {
            if (scene.envAtlas && scene.skybox) {
                this._setParameter('texture_envAtlas', scene.envAtlas);
                this._setParameter('texture_cubeMap', scene.skybox);
            } else if (scene.envAtlas) {
                this._setParameter('texture_envAtlas', scene.envAtlas);
            } else if (scene.skybox) {
                this._setParameter('texture_cubeMap', scene.skybox);
            }
        }
        this._processParameters('_activeLightingParams');
    }
    getShaderVariant(params) {
        const { device, scene, pass, objDefs, sortedLights, cameraShaderParams } = params;
        // update prefiltered lighting data
        this.updateEnvUniforms(device, scene);
        // Minimal options for Depth, Shadow and Prepass passes
        const shaderPassInfo = ShaderPass.get(device).getByIndex(pass);
        const minimalOptions = pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow;
        let options = minimalOptions ? standard.optionsContextMin : standard.optionsContext;
        options.defines = ShaderUtils.getCoreDefines(this, params);
        if (minimalOptions) {
            this.shaderOptBuilder.updateMinRef(options, scene, this, objDefs, pass, sortedLights);
        } else {
            this.shaderOptBuilder.updateRef(options, scene, cameraShaderParams, this, objDefs, pass, sortedLights);
        }
        // standard material can overwrite camera's fog setting
        if (!this.useFog) options.defines.set('FOG', 'NONE');
        // standard material can overwrite camera's tonemapping setting
        options.defines.set('TONEMAP', tonemapNames[options.litOptions.toneMap]);
        // execute user callback to modify the options
        if (this.onUpdateShader) {
            options = this.onUpdateShader(options);
        }
        const processingOptions = new ShaderProcessorOptions(params.viewUniformFormat, params.viewBindGroupFormat, params.vertexFormat);
        const library = getProgramLibrary(device);
        library.register('standard', standard);
        const shader = library.getProgram('standard', options, processingOptions, this.userId);
        this._dirtyShader = false;
        return shader;
    }
    /**
     * Removes this material from the scene and possibly frees up memory from its shaders (if there
     * are no other materials using it).
     */ destroy() {
        // unbind (texture) asset references
        for(const asset in this._assetReferences){
            this._assetReferences[asset]._unbind();
        }
        this._assetReferences = null;
        super.destroy();
    }
}
// define a uniform get function
const defineUniform = (name, getUniformFunc)=>{
    _uniforms[name] = getUniformFunc;
};
const definePropInternal = (name, constructorFunc, setterFunc, getterFunc)=>{
    Object.defineProperty(StandardMaterial.prototype, name, {
        get: getterFunc || function() {
            return this[`_${name}`];
        },
        set: setterFunc
    });
    _props[name] = {
        value: constructorFunc
    };
};
// define a simple value property (float, string etc)
const defineValueProp = (prop)=>{
    const internalName = `_${prop.name}`;
    const dirtyShaderFunc = prop.dirtyShaderFunc || (()=>true);
    const setterFunc = function(value) {
        const oldValue = this[internalName];
        if (oldValue !== value) {
            this._dirtyShader = this._dirtyShader || dirtyShaderFunc(oldValue, value);
            this[internalName] = value;
        }
    };
    definePropInternal(prop.name, ()=>prop.defaultValue, setterFunc, prop.getterFunc);
};
// define an aggregate property (color, vec3 etc)
const defineAggProp = (prop)=>{
    const internalName = `_${prop.name}`;
    const dirtyShaderFunc = prop.dirtyShaderFunc || (()=>true);
    const setterFunc = function(value) {
        const oldValue = this[internalName];
        if (!oldValue.equals(value)) {
            this._dirtyShader = this._dirtyShader || dirtyShaderFunc(oldValue, value);
            this[internalName] = oldValue.copy(value);
        }
    };
    definePropInternal(prop.name, ()=>prop.defaultValue.clone(), setterFunc, prop.getterFunc);
};
// define either a value or aggregate property
const defineProp = (prop)=>{
    return prop.defaultValue && prop.defaultValue.clone ? defineAggProp(prop) : defineValueProp(prop);
};
function _defineTex2D(name, channel = 'rgb', vertexColor = true, uv = 0) {
    // store texture name
    _matTex2D[name] = channel.length || -1;
    defineProp({
        name: `${name}Map`,
        defaultValue: null,
        dirtyShaderFunc: (oldValue, newValue)=>{
            return !!oldValue !== !!newValue || oldValue && (oldValue.type !== newValue.type || oldValue.format !== newValue.format);
        }
    });
    defineProp({
        name: `${name}MapTiling`,
        defaultValue: new Vec2(1, 1)
    });
    defineProp({
        name: `${name}MapOffset`,
        defaultValue: new Vec2(0, 0)
    });
    defineProp({
        name: `${name}MapRotation`,
        defaultValue: 0
    });
    defineProp({
        name: `${name}MapUv`,
        defaultValue: uv
    });
    if (channel) {
        defineProp({
            name: `${name}MapChannel`,
            defaultValue: channel
        });
        if (vertexColor) {
            defineProp({
                name: `${name}VertexColor`,
                defaultValue: false
            });
            defineProp({
                name: `${name}VertexColorChannel`,
                defaultValue: channel
            });
        }
    }
    // construct the transform uniform
    const mapTiling = `${name}MapTiling`;
    const mapOffset = `${name}MapOffset`;
    const mapRotation = `${name}MapRotation`;
    const mapTransform = `${name}MapTransform`;
    defineUniform(mapTransform, (material, device, scene)=>{
        const tiling = material[mapTiling];
        const offset = material[mapOffset];
        const rotation = material[mapRotation];
        if (tiling.x === 1 && tiling.y === 1 && offset.x === 0 && offset.y === 0 && rotation === 0) {
            return null;
        }
        const uniform = material._allocUniform(mapTransform, ()=>{
            return [
                {
                    name: `texture_${mapTransform}0`,
                    value: new Float32Array(3)
                },
                {
                    name: `texture_${mapTransform}1`,
                    value: new Float32Array(3)
                }
            ];
        });
        const cr = Math.cos(rotation * math.DEG_TO_RAD);
        const sr = Math.sin(rotation * math.DEG_TO_RAD);
        const uniform0 = uniform[0].value;
        uniform0[0] = cr * tiling.x;
        uniform0[1] = -sr * tiling.y;
        uniform0[2] = offset.x;
        const uniform1 = uniform[1].value;
        uniform1[0] = sr * tiling.x;
        uniform1[1] = cr * tiling.y;
        uniform1[2] = 1.0 - tiling.y - offset.y;
        return uniform;
    });
}
function _defineColor(name, defaultValue) {
    defineProp({
        name: name,
        defaultValue: defaultValue,
        getterFunc: function() {
            // HACK: since we can't detect whether a user is going to set a color property
            // after calling this getter (i.e doing material.ambient.r = 0.5) we must assume
            // the worst and flag the shader as dirty.
            // This means currently animating a material color is horribly slow.
            this._dirtyShader = true;
            return this[`_${name}`];
        }
    });
    defineUniform(name, (material, device, scene)=>{
        const uniform = material._allocUniform(name, ()=>new Float32Array(3));
        const color = material[name];
        // uniforms are always in linear space
        _tempColor.linear(color);
        uniform[0] = _tempColor.r;
        uniform[1] = _tempColor.g;
        uniform[2] = _tempColor.b;
        return uniform;
    });
}
function _defineFloat(name, defaultValue, getUniformFunc) {
    defineProp({
        name: name,
        defaultValue: defaultValue,
        dirtyShaderFunc: (oldValue, newValue)=>{
            // This is not always optimal and will sometimes trigger redundant shader
            // recompilation. However, no number property on a standard material
            // triggers a shader recompile if the previous and current values both
            // have a fractional part.
            return (oldValue === 0 || oldValue === 1) !== (newValue === 0 || newValue === 1);
        }
    });
    defineUniform(name, getUniformFunc);
}
function _defineObject(name, getUniformFunc) {
    defineProp({
        name: name,
        defaultValue: null,
        dirtyShaderFunc: (oldValue, newValue)=>{
            return !!oldValue === !!newValue;
        }
    });
    defineUniform(name, getUniformFunc);
}
function _defineFlag(name, defaultValue) {
    defineProp({
        name: name,
        defaultValue: defaultValue
    });
}
function _defineMaterialProps() {
    _defineColor('ambient', new Color(1, 1, 1));
    _defineColor('diffuse', new Color(1, 1, 1));
    _defineColor('specular', new Color(0, 0, 0));
    _defineColor('emissive', new Color(0, 0, 0));
    _defineColor('sheen', new Color(1, 1, 1));
    _defineColor('attenuation', new Color(1, 1, 1));
    _defineFloat('emissiveIntensity', 1);
    _defineFloat('specularityFactor', 1);
    _defineFloat('sheenGloss', 0.0);
    _defineFloat('gloss', 0.25);
    _defineFloat('aoIntensity', 1);
    _defineFloat('heightMapFactor', 1, (material, device, scene)=>{
        return material.heightMapFactor * 0.025;
    });
    _defineFloat('opacity', 1);
    _defineFloat('alphaFade', 1);
    _defineFloat('alphaTest', 0); // NOTE: overwrites Material.alphaTest
    _defineFloat('bumpiness', 1);
    _defineFloat('normalDetailMapBumpiness', 1);
    _defineFloat('reflectivity', 1);
    _defineFloat('occludeSpecularIntensity', 1);
    _defineFloat('refraction', 0);
    _defineFloat('refractionIndex', 1.0 / 1.5); // approx. (air ior / glass ior)
    _defineFloat('dispersion', 0);
    _defineFloat('thickness', 0);
    _defineFloat('attenuationDistance', 0);
    _defineFloat('metalness', 1);
    _defineFloat('anisotropyIntensity', 0);
    _defineFloat('anisotropyRotation', 0);
    _defineFloat('clearCoat', 0);
    _defineFloat('clearCoatGloss', 1);
    _defineFloat('clearCoatBumpiness', 1);
    _defineFloat('aoUvSet', 0, null); // legacy
    _defineFloat('iridescence', 0);
    _defineFloat('iridescenceRefractionIndex', 1.0 / 1.5);
    _defineFloat('iridescenceThicknessMin', 0);
    _defineFloat('iridescenceThicknessMax', 0);
    _defineObject('ambientSH');
    _defineObject('cubeMapProjectionBox', (material, device, scene)=>{
        const uniform = material._allocUniform('cubeMapProjectionBox', ()=>{
            return [
                {
                    name: 'envBoxMin',
                    value: new Float32Array(3)
                },
                {
                    name: 'envBoxMax',
                    value: new Float32Array(3)
                }
            ];
        });
        const bboxMin = material.cubeMapProjectionBox.getMin();
        const minUniform = uniform[0].value;
        minUniform[0] = bboxMin.x;
        minUniform[1] = bboxMin.y;
        minUniform[2] = bboxMin.z;
        const bboxMax = material.cubeMapProjectionBox.getMax();
        const maxUniform = uniform[1].value;
        maxUniform[0] = bboxMax.x;
        maxUniform[1] = bboxMax.y;
        maxUniform[2] = bboxMax.z;
        return uniform;
    });
    _defineFlag('specularTint', false);
    _defineFlag('specularityFactorTint', false);
    _defineFlag('useMetalness', false);
    _defineFlag('useMetalnessSpecularColor', false);
    _defineFlag('useSheen', false);
    _defineFlag('enableGGXSpecular', false);
    _defineFlag('occludeDirect', false);
    _defineFlag('opacityFadesSpecular', true);
    _defineFlag('occludeSpecular', SPECOCC_AO);
    _defineFlag('fresnelModel', FRESNEL_SCHLICK); // NOTE: this has been made to match the default shading model (to fix a bug)
    _defineFlag('useDynamicRefraction', false);
    _defineFlag('cubeMapProjection', CUBEPROJ_NONE);
    _defineFlag('useFog', true);
    _defineFlag('useLighting', true);
    _defineFlag('useTonemap', true);
    _defineFlag('useSkybox', true);
    _defineFlag('forceUv1', false);
    _defineFlag('pixelSnap', false);
    _defineFlag('twoSidedLighting', false);
    _defineFlag('nineSlicedMode', undefined); // NOTE: this used to be SPRITE_RENDERMODE_SLICED but was undefined pre-Rollup
    _defineFlag('msdfTextAttribute', false);
    _defineFlag('useIridescence', false);
    _defineFlag('glossInvert', false);
    _defineFlag('sheenGlossInvert', false);
    _defineFlag('clearCoatGlossInvert', false);
    _defineFlag('opacityDither', DITHER_NONE);
    _defineFlag('opacityShadowDither', DITHER_NONE);
    _defineFlag('shadowCatcher', false);
    _defineTex2D('diffuse');
    _defineTex2D('specular');
    _defineTex2D('emissive');
    _defineTex2D('thickness', 'g');
    _defineTex2D('specularityFactor', 'g');
    _defineTex2D('normal', '');
    _defineTex2D('metalness', 'g');
    _defineTex2D('gloss', 'g');
    _defineTex2D('opacity', 'a');
    _defineTex2D('refraction', 'g');
    _defineTex2D('height', 'g', false);
    _defineTex2D('ao', 'g');
    _defineTex2D('light', 'rgb', true, 1);
    _defineTex2D('msdf', '');
    _defineTex2D('diffuseDetail', 'rgb', false);
    _defineTex2D('normalDetail', '');
    _defineTex2D('aoDetail', 'g', false);
    _defineTex2D('clearCoat', 'g');
    _defineTex2D('clearCoatGloss', 'g');
    _defineTex2D('clearCoatNormal', '');
    _defineTex2D('sheen', 'rgb');
    _defineTex2D('sheenGloss', 'g');
    _defineTex2D('iridescence', 'g');
    _defineTex2D('iridescenceThickness', 'g');
    _defineTex2D('anisotropy', '');
    _defineFlag('diffuseDetailMode', DETAILMODE_MUL);
    _defineFlag('aoDetailMode', DETAILMODE_MUL);
    _defineObject('cubeMap');
    _defineObject('sphereMap');
    _defineObject('envAtlas');
    // prefiltered cubemap getter
    const getterFunc = function() {
        return this._prefilteredCubemaps;
    };
    // prefiltered cubemap setter
    const setterFunc = function(value) {
        const cubemaps = this._prefilteredCubemaps;
        value = value || [];
        let changed = false;
        let complete = true;
        for(let i = 0; i < 6; ++i){
            const v = value[i] || null;
            if (cubemaps[i] !== v) {
                cubemaps[i] = v;
                changed = true;
            }
            complete = complete && !!cubemaps[i];
        }
        if (changed) {
            if (complete) {
                this.envAtlas = EnvLighting.generatePrefilteredAtlas(cubemaps, {
                    target: this.envAtlas
                });
            } else {
                if (this.envAtlas) {
                    this.envAtlas.destroy();
                    this.envAtlas = null;
                }
            }
            this._dirtyShader = true;
        }
    };
    const empty = [
        null,
        null,
        null,
        null,
        null,
        null
    ];
    definePropInternal('prefilteredCubemaps', ()=>empty.slice(), setterFunc, getterFunc);
}
_defineMaterialProps();

class WorldClustersDebug {
    static{
        this.gridPositions = [];
    }
    static{
        this.gridColors = [];
    }
    static{
        this.mesh = null;
    }
    static{
        this.meshInstance = null;
    }
    static{
        this.colorLow = new Vec3(1, 1, 1);
    }
    static{
        this.colorHigh = new Vec3(40, 0, 0);
    }
    static render(worldClusters, scene) {
        const device = scene.device;
        const cells = worldClusters.cells;
        const lightsBuffer = worldClusters.lightsBuffer;
        const boundsMin = lightsBuffer.boundsMin;
        const boundsDelta = lightsBuffer.boundsDelta;
        const boundsMax = boundsMin.clone().add(boundsDelta);
        const cellDelta = lightsBuffer.boundsDelta.clone().div(cells);
        const gridPositions = WorldClustersDebug.gridPositions;
        const gridColors = WorldClustersDebug.gridColors;
        const c1 = new Color(0.3, 0.3, 0.3);
        const renderCellLines = (countA, countB, minA, deltaA, minB, deltaB, minC, maxC, order)=>{
            for(let a = 0; a <= countA; a++){
                for(let b = 0; b <= countB; b++){
                    const aa = minA + a * deltaA;
                    const bb = minB + b * deltaB;
                    if (order === 0) {
                        gridPositions.push(aa, minC, bb, aa, maxC, bb);
                    } else if (order === 1) {
                        gridPositions.push(aa, bb, minC, aa, bb, maxC);
                    } else if (order === 2) {
                        gridPositions.push(minC, aa, bb, maxC, aa, bb);
                    }
                }
            }
        };
        // generate grid lines
        renderCellLines(cells.x, cells.z, boundsMin.x, cellDelta.x, boundsMin.z, cellDelta.z, boundsMin.y, boundsMax.y, 0);
        renderCellLines(cells.x, cells.y, boundsMin.x, cellDelta.x, boundsMin.y, cellDelta.y, boundsMin.z, boundsMax.z, 1);
        renderCellLines(cells.y, cells.z, boundsMin.y, cellDelta.y, boundsMin.z, cellDelta.z, boundsMin.x, boundsMax.x, 2);
        // render grid lines
        if (gridPositions.length) {
            // update colors only when needed
            const numVerts = gridPositions.length / 3;
            if (numVerts !== gridColors.length / 4) {
                gridColors.length = 0;
                for(let i = 0; i < numVerts; i++){
                    gridColors.push(c1.r, c1.g, c1.b, c1.a);
                }
            }
            scene.drawLineArrays(gridPositions, gridColors);
            gridPositions.length = 0;
        }
        // render cell occupancy
        let mesh = WorldClustersDebug.mesh;
        if (!mesh) {
            mesh = new Mesh(device);
            mesh.clear(true, true);
            WorldClustersDebug.mesh = mesh;
        }
        const positions = [];
        const colors = [];
        const indices = [];
        const divX = worldClusters._cells.x;
        const divZ = worldClusters._cells.z;
        const counts = worldClusters.counts;
        const limit = worldClusters._maxCellLightCount;
        const min = new Vec3();
        const max = new Vec3();
        const col = new Vec3();
        const step = boundsDelta.clone().div(cells);
        // add cubes with a color representing cell occupancy to the dynamic mesh
        let cubes = 0;
        for(let x = 0; x < cells.x; x++){
            for(let z = 0; z < cells.z; z++){
                for(let y = 0; y < cells.y; y++){
                    const clusterIndex = x + divX * (z + y * divZ);
                    const count = counts[clusterIndex];
                    if (count > 0) {
                        // cube corners
                        min.x = boundsMin.x + step.x * x;
                        min.y = boundsMin.y + step.y * y;
                        min.z = boundsMin.z + step.z * z;
                        max.add2(min, step);
                        positions.push(min.x, min.y, max.z);
                        positions.push(max.x, min.y, max.z);
                        positions.push(max.x, max.y, max.z);
                        positions.push(min.x, max.y, max.z);
                        positions.push(max.x, min.y, min.z);
                        positions.push(min.x, min.y, min.z);
                        positions.push(min.x, max.y, min.z);
                        positions.push(max.x, max.y, min.z);
                        col.lerp(WorldClustersDebug.colorLow, WorldClustersDebug.colorHigh, count / limit).round();
                        for(let c = 0; c < 8; c++){
                            colors.push(col.x, col.y, col.z, 1);
                        }
                        // back
                        indices.push(cubes * 8 + 0, cubes * 8 + 1, cubes * 8 + 3);
                        indices.push(cubes * 8 + 3, cubes * 8 + 1, cubes * 8 + 2);
                        // front
                        indices.push(cubes * 8 + 4, cubes * 8 + 5, cubes * 8 + 7);
                        indices.push(cubes * 8 + 7, cubes * 8 + 5, cubes * 8 + 6);
                        // top
                        indices.push(cubes * 8 + 3, cubes * 8 + 2, cubes * 8 + 6);
                        indices.push(cubes * 8 + 2, cubes * 8 + 7, cubes * 8 + 6);
                        // bottom
                        indices.push(cubes * 8 + 1, cubes * 8 + 0, cubes * 8 + 4);
                        indices.push(cubes * 8 + 0, cubes * 8 + 5, cubes * 8 + 4);
                        // right
                        indices.push(cubes * 8 + 1, cubes * 8 + 4, cubes * 8 + 2);
                        indices.push(cubes * 8 + 4, cubes * 8 + 7, cubes * 8 + 2);
                        // left
                        indices.push(cubes * 8 + 5, cubes * 8 + 0, cubes * 8 + 6);
                        indices.push(cubes * 8 + 0, cubes * 8 + 3, cubes * 8 + 6);
                        cubes++;
                    }
                }
            }
        }
        if (cubes) {
            mesh.setPositions(positions);
            mesh.setNormals(new Float32Array(positions.length));
            mesh.setColors32(colors);
            mesh.setIndices(indices);
            mesh.update(PRIMITIVE_TRIANGLES, false);
            if (!WorldClustersDebug.meshInstance) {
                const material = new StandardMaterial();
                material.useLighting = false;
                material.emissive = new Color(1, 1, 1);
                material.emissiveVertexColor = true;
                material.blendType = BLEND_ADDITIVEALPHA;
                material.depthWrite = false;
                material.update();
                const node = new GraphNode('WorldClustersDebug');
                node.worldTransform = Mat4.IDENTITY;
                node._dirtyWorld = node._dirtyNormal = false;
                WorldClustersDebug.meshInstance = new MeshInstance(mesh, material, node);
                WorldClustersDebug.meshInstance.cull = false;
            }
            // render
            const meshInstance = WorldClustersDebug.meshInstance;
            scene.immediate.drawMesh(meshInstance.material, meshInstance.node.worldTransform, null, meshInstance, scene.defaultDrawLayer);
        }
    }
}

// this is a 32x32 blue noise texture LDR_RGBA_53.png from the database mentioned here: https://momentsingraphics.de/BlueNoise.html
const base64String = ' ';
let data = null;
const initData = ()=>{
    if (!data) {
        const binaryString = atob(base64String);
        // the data stores an array of RGBA 8-bit values, each of the color channels represents
        // a separate blue noise stream
        data = Uint8Array.from(binaryString, (char)=>char.charCodeAt(0));
    }
};
const blueNoiseData = ()=>{
    initData();
    return data;
};
/**
 * Blue noise based random numbers API.
 *
 * @ignore
 */ class BlueNoise {
    constructor(seed = 0){
        this.seed = 0;
        this.seed = seed * 4;
        initData();
    }
    _next() {
        this.seed = (this.seed + 4) % data.length;
    }
    value() {
        this._next();
        return data[this.seed] / 255;
    }
    vec4(dest = new Vec4()) {
        this._next();
        return dest.set(data[this.seed], data[this.seed + 1], data[this.seed + 2], data[this.seed + 3]).mulScalar(1 / 255);
    }
}

const lightCubeDir = [
    new Vec3(-1, 0, 0),
    new Vec3(1, 0, 0),
    new Vec3(0, -1, 0),
    new Vec3(0, 1, 0),
    new Vec3(0, 0, -1),
    new Vec3(0, 0, 1)
];
/**
 * A lighting cube represented by 6 colors, one per cube direction. Use for simple lighting on the
 * particle system.
 *
 * @ignore
 */ class LightCube {
    update(ambientLight, lights) {
        const colors = this.colors;
        // ambient contribution
        const { r, g, b } = ambientLight;
        for(let j = 0; j < 6; j++){
            colors[j * 3] = r;
            colors[j * 3 + 1] = g;
            colors[j * 3 + 2] = b;
        }
        // directional contribution
        for(let j = 0; j < lights.length; j++){
            const light = lights[j];
            if (light._type === LIGHTTYPE_DIRECTIONAL) {
                for(let c = 0; c < 6; c++){
                    const weight = Math.max(lightCubeDir[c].dot(light._direction), 0) * light._intensity;
                    const lightColor = light._color;
                    colors[c * 3] += lightColor.r * weight;
                    colors[c * 3 + 1] += lightColor.g * weight;
                    colors[c * 3 + 2] += lightColor.b * weight;
                }
            }
        }
    }
    constructor(){
        this.colors = new Float32Array(6 * 3);
    }
}

const createTexture = (device, namePrefix, size, data)=>{
    const texture = new Texture(device, {
        name: `${namePrefix}${size}`,
        width: size,
        height: size,
        format: PIXELFORMAT_RGBA8,
        addressU: ADDRESS_REPEAT,
        addressV: ADDRESS_REPEAT,
        type: TEXTURETYPE_DEFAULT,
        magFilter: FILTER_NEAREST,
        minFilter: FILTER_NEAREST,
        anisotropy: 1,
        mipmaps: false
    });
    texture.lock().set(data);
    texture.unlock();
    return texture;
};
// device cache storing the blue noise texture for the device
const deviceCacheBlueNoise = new DeviceCache();
const getBlueNoiseTexture = (device)=>{
    return deviceCacheBlueNoise.get(device, ()=>{
        const data = blueNoiseData();
        const size = Math.sqrt(data.length / 4);
        return createTexture(device, 'BlueNoise', size, data);
    });
};

class ShadowMap {
    constructor(texture, targets){
        // the actual texture buffer that is shared by shadow map render targets
        this.texture = texture;
        // set to true if the shadow map is owned by the shadow map cache
        this.cached = false;
        // an array of render targets:
        // 1 for directional and spot light
        // 6 for omni light
        this.renderTargets = targets;
    }
    destroy() {
        // single texture is shared by all render targets, destroy it once
        if (this.texture) {
            this.texture.destroy();
            this.texture = null;
        }
        const targets = this.renderTargets;
        for(let i = 0; i < targets.length; i++){
            targets[i].destroy();
        }
        this.renderTargets.length = 0;
    }
    static create(device, light) {
        let shadowMap = null;
        if (light._type === LIGHTTYPE_OMNI) {
            shadowMap = this.createCubemap(device, light._shadowResolution, light._shadowType);
        } else {
            shadowMap = this.create2dMap(device, light._shadowResolution, light._shadowType);
        }
        return shadowMap;
    }
    // creates a shadow map which is used by the light texture atlas for clustered lighting
    static createAtlas(device, resolution, shadowType) {
        const shadowMap = this.create2dMap(device, resolution, shadowType);
        // copy the target 5 more times to allow unified access for point light faces
        const targets = shadowMap.renderTargets;
        const rt = targets[0];
        for(let i = 0; i < 5; i++){
            targets.push(rt);
        }
        return shadowMap;
    }
    static create2dMap(device, size, shadowType) {
        const shadowInfo = shadowTypeInfo.get(shadowType);
        Debug.assert(shadowInfo);
        let format = shadowInfo.format;
        // when F32 is needed but not supported, fallback to F16 (PCSS)
        if (format === PIXELFORMAT_R32F && !device.textureFloatRenderable && device.textureHalfFloatRenderable) {
            format = PIXELFORMAT_R16F;
        }
        const formatName = pixelFormatInfo.get(format)?.name;
        let filter = FILTER_LINEAR;
        if (shadowType === SHADOW_VSM_32F) {
            filter = device.extTextureFloatLinear ? FILTER_LINEAR : FILTER_NEAREST;
        }
        if (shadowType === SHADOW_PCSS_32F) {
            // we're sampling and comparing depth, so need nearest filtering
            // also note that linear is failing on iOS devices
            filter = FILTER_NEAREST;
        }
        const texture = new Texture(device, {
            profilerHint: TEXHINT_SHADOWMAP,
            format: format,
            width: size,
            height: size,
            mipmaps: false,
            minFilter: filter,
            magFilter: filter,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            name: `ShadowMap2D_${formatName}`
        });
        let target = null;
        if (shadowInfo?.pcf) {
            // enable hardware PCF when sampling the depth texture
            texture.compareOnRead = true;
            texture.compareFunc = FUNC_LESS;
            // depthbuffer only
            target = new RenderTarget({
                depthBuffer: texture
            });
        } else {
            // encoded rgba depth
            target = new RenderTarget({
                colorBuffer: texture,
                depth: true
            });
        }
        // TODO: this is temporary, and will be handled on generic level for all render targets for WebGPU
        if (device.isWebGPU) {
            target.flipY = true;
        }
        return new ShadowMap(texture, [
            target
        ]);
    }
    static createCubemap(device, size, shadowType) {
        const shadowInfo = shadowTypeInfo.get(shadowType);
        Debug.assert(shadowInfo);
        const formatName = pixelFormatInfo.get(shadowInfo.format)?.name;
        const isPcss = shadowType === SHADOW_PCSS_32F;
        const filter = isPcss ? FILTER_NEAREST : FILTER_LINEAR;
        const cubemap = new Texture(device, {
            profilerHint: TEXHINT_SHADOWMAP,
            format: shadowInfo?.format,
            width: size,
            height: size,
            cubemap: true,
            mipmaps: false,
            minFilter: filter,
            magFilter: filter,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            name: `ShadowMapCube_${formatName}`
        });
        // enable hardware PCF when sampling the depth texture
        if (!isPcss) {
            cubemap.compareOnRead = true;
            cubemap.compareFunc = FUNC_LESS;
        }
        const targets = [];
        for(let i = 0; i < 6; i++){
            if (isPcss) {
                // color and depth buffer
                targets.push(new RenderTarget({
                    colorBuffer: cubemap,
                    face: i,
                    depth: true
                }));
            } else {
                // depth buffer only
                targets.push(new RenderTarget({
                    depthBuffer: cubemap,
                    face: i
                }));
            }
        }
        return new ShadowMap(cubemap, targets);
    }
}

const _tempArray = [];
const _tempArray2 = [];
const _viewport$1 = new Vec4();
const _scissor = new Vec4();
class Slot {
    constructor(rect){
        this.size = Math.floor(rect.w * 1024); // size normalized to 1024 atlas
        this.used = false;
        this.lightId = -1; // id of the light using the slot
        this.rect = rect;
    }
}
// A class handling runtime allocation of slots in a texture. It is used to allocate slots in the shadow and cookie atlas.
class LightTextureAtlas {
    constructor(device){
        this.device = device;
        this.version = 1; // incremented each time slot configuration changes
        this.shadowAtlasResolution = 2048;
        this.shadowAtlas = null;
        // number of additional pixels to render past the required shadow camera angle (90deg for omni, outer for spot) of the shadow camera for clustered lights.
        // This needs to be a pixel more than a shadow filter needs to access.
        this.shadowEdgePixels = 3;
        this.cookieAtlasResolution = 4;
        this.cookieAtlas = new Texture(this.device, {
            name: 'CookieAtlas',
            width: this.cookieAtlasResolution,
            height: this.cookieAtlasResolution,
            format: PIXELFORMAT_SRGBA8,
            cubemap: false,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE
        });
        this.cookieRenderTarget = new RenderTarget({
            colorBuffer: this.cookieAtlas,
            depth: false,
            flipY: true
        });
        // available slots (of type Slot)
        this.slots = [];
        // current subdivision strategy - matches format of LightingParams.atlasSplit
        this.atlasSplit = [];
        // offsets to individual faces of a cubemap inside 3x3 grid in an atlas slot
        this.cubeSlotsOffsets = [
            new Vec2(0, 0),
            new Vec2(0, 1),
            new Vec2(1, 0),
            new Vec2(1, 1),
            new Vec2(2, 0),
            new Vec2(2, 1)
        ];
        // handles gap between slots
        this.scissorVec = new Vec4();
        this.allocateShadowAtlas(1); // placeholder as shader requires it
        this.allocateCookieAtlas(1); // placeholder as shader requires it
        this.allocateUniforms();
    }
    destroy() {
        this.destroyShadowAtlas();
        this.destroyCookieAtlas();
    }
    destroyShadowAtlas() {
        this.shadowAtlas?.destroy();
        this.shadowAtlas = null;
    }
    destroyCookieAtlas() {
        this.cookieAtlas?.destroy();
        this.cookieAtlas = null;
        this.cookieRenderTarget?.destroy();
        this.cookieRenderTarget = null;
    }
    allocateShadowAtlas(resolution, shadowType = SHADOW_PCF3_32F) {
        const existingFormat = this.shadowAtlas?.texture.format;
        const requiredFormat = shadowTypeInfo.get(shadowType).format;
        if (!this.shadowAtlas || this.shadowAtlas.texture.width !== resolution || existingFormat !== requiredFormat) {
            // content of atlas is lost, force re-render of static shadows
            this.version++;
            this.destroyShadowAtlas();
            this.shadowAtlas = ShadowMap.createAtlas(this.device, resolution, shadowType);
            // avoid it being destroyed by lights
            this.shadowAtlas.cached = true;
            // leave gap between individual tiles to avoid shadow / cookie sampling other tiles (enough for PCF5)
            // note that this only fades / removes shadows on the edges, which is still not correct - a shader clipping is needed?
            const scissorOffset = 4 / this.shadowAtlasResolution;
            this.scissorVec.set(scissorOffset, scissorOffset, -2 * scissorOffset, -2 * scissorOffset);
        }
    }
    allocateCookieAtlas(resolution) {
        // resize atlas
        if (this.cookieAtlas.width !== resolution) {
            this.cookieRenderTarget.resize(resolution, resolution);
            // content of atlas is lost, force re-render of static cookies
            this.version++;
        }
    }
    allocateUniforms() {
        this._shadowAtlasTextureId = this.device.scope.resolve('shadowAtlasTexture');
        this._shadowAtlasParamsId = this.device.scope.resolve('shadowAtlasParams');
        this._shadowAtlasParams = new Float32Array(2);
        this._cookieAtlasTextureId = this.device.scope.resolve('cookieAtlasTexture');
    }
    updateUniforms() {
        // shadow atlas texture
        const rt = this.shadowAtlas.renderTargets[0];
        const shadowBuffer = rt.depthBuffer;
        this._shadowAtlasTextureId.setValue(shadowBuffer);
        // shadow atlas params
        this._shadowAtlasParams[0] = this.shadowAtlasResolution;
        this._shadowAtlasParams[1] = this.shadowEdgePixels;
        this._shadowAtlasParamsId.setValue(this._shadowAtlasParams);
        // cookie atlas textures
        this._cookieAtlasTextureId.setValue(this.cookieAtlas);
    }
    subdivide(numLights, lightingParams) {
        let atlasSplit = lightingParams.atlasSplit;
        // if no user specified subdivision
        if (!atlasSplit) {
            // split to equal number of squares
            const gridSize = Math.ceil(Math.sqrt(numLights));
            atlasSplit = _tempArray2;
            atlasSplit[0] = gridSize;
            atlasSplit.length = 1;
        }
        // compare two arrays
        const arraysEqual = (a, b)=>a.length === b.length && a.every((v, i)=>v === b[i]);
        // if the split has changed, regenerate slots
        if (!arraysEqual(atlasSplit, this.atlasSplit)) {
            this.version++;
            this.slots.length = 0;
            // store current settings
            this.atlasSplit.length = 0;
            this.atlasSplit.push(...atlasSplit);
            // generate top level split
            const splitCount = this.atlasSplit[0];
            if (splitCount > 1) {
                const invSize = 1 / splitCount;
                for(let i = 0; i < splitCount; i++){
                    for(let j = 0; j < splitCount; j++){
                        const rect = new Vec4(i * invSize, j * invSize, invSize, invSize);
                        const nextLevelSplit = this.atlasSplit[1 + i * splitCount + j];
                        // if need to split again
                        if (nextLevelSplit > 1) {
                            for(let x = 0; x < nextLevelSplit; x++){
                                for(let y = 0; y < nextLevelSplit; y++){
                                    const invSizeNext = invSize / nextLevelSplit;
                                    const rectNext = new Vec4(rect.x + x * invSizeNext, rect.y + y * invSizeNext, invSizeNext, invSizeNext);
                                    this.slots.push(new Slot(rectNext));
                                }
                            }
                        } else {
                            this.slots.push(new Slot(rect));
                        }
                    }
                }
            } else {
                // single slot
                this.slots.push(new Slot(new Vec4(0, 0, 1, 1)));
            }
            // sort slots descending
            this.slots.sort((a, b)=>{
                return b.size - a.size;
            });
        }
    }
    collectLights(localLights, lightingParams) {
        const cookiesEnabled = lightingParams.cookiesEnabled;
        const shadowsEnabled = lightingParams.shadowsEnabled;
        // get all lights that need shadows or cookies, if those are enabled
        let needsShadowAtlas = false;
        let needsCookieAtlas = false;
        const lights = _tempArray;
        lights.length = 0;
        const processLights = (list)=>{
            for(let i = 0; i < list.length; i++){
                const light = list[i];
                if (light.visibleThisFrame) {
                    const lightShadow = shadowsEnabled && light.castShadows;
                    const lightCookie = cookiesEnabled && !!light.cookie;
                    needsShadowAtlas ||= lightShadow;
                    needsCookieAtlas ||= lightCookie;
                    if (lightShadow || lightCookie) {
                        lights.push(light);
                    }
                }
            }
        };
        if (cookiesEnabled || shadowsEnabled) {
            processLights(localLights);
        }
        // sort lights by maxScreenSize - to have them ordered by atlas slot size
        lights.sort((a, b)=>{
            return b.maxScreenSize - a.maxScreenSize;
        });
        if (needsShadowAtlas) {
            this.allocateShadowAtlas(this.shadowAtlasResolution, lightingParams.shadowType);
        }
        if (needsCookieAtlas) {
            this.allocateCookieAtlas(this.cookieAtlasResolution);
        }
        if (needsShadowAtlas || needsCookieAtlas) {
            this.subdivide(lights.length, lightingParams);
        }
        return lights;
    }
    // configure light to use assigned slot
    setupSlot(light, rect) {
        light.atlasViewport.copy(rect);
        const faceCount = light.numShadowFaces;
        for(let face = 0; face < faceCount; face++){
            // setup slot for shadow and cookie
            if (light.castShadows || light._cookie) {
                _viewport$1.copy(rect);
                _scissor.copy(rect);
                // for spot lights in the atlas, make viewport slightly smaller to avoid sampling past the edges
                if (light._type === LIGHTTYPE_SPOT) {
                    _viewport$1.add(this.scissorVec);
                }
                // for cube map, allocate part of the slot
                if (light._type === LIGHTTYPE_OMNI) {
                    const smallSize = _viewport$1.z / 3;
                    const offset = this.cubeSlotsOffsets[face];
                    _viewport$1.x += smallSize * offset.x;
                    _viewport$1.y += smallSize * offset.y;
                    _viewport$1.z = smallSize;
                    _viewport$1.w = smallSize;
                    _scissor.copy(_viewport$1);
                }
                if (light.castShadows) {
                    const lightRenderData = light.getRenderData(null, face);
                    lightRenderData.shadowViewport.copy(_viewport$1);
                    lightRenderData.shadowScissor.copy(_scissor);
                }
            }
        }
    }
    // assign a slot to the light
    assignSlot(light, slotIndex, slotReassigned) {
        light.atlasViewportAllocated = true;
        const slot = this.slots[slotIndex];
        slot.lightId = light.id;
        slot.used = true;
        // slot is reassigned (content needs to be updated)
        if (slotReassigned) {
            light.atlasSlotUpdated = true;
            light.atlasVersion = this.version;
            light.atlasSlotIndex = slotIndex;
        }
    }
    // update texture atlas for a list of lights
    update(localLights, lightingParams) {
        // update texture resolutions
        this.shadowAtlasResolution = lightingParams.shadowAtlasResolution;
        this.cookieAtlasResolution = lightingParams.cookieAtlasResolution;
        // collect lights requiring atlas
        const lights = this.collectLights(localLights, lightingParams);
        if (lights.length > 0) {
            // mark all slots as unused
            const slots = this.slots;
            for(let i = 0; i < slots.length; i++){
                slots[i].used = false;
            }
            // assign slots to lights
            // The slot to light assignment logic:
            // - internally the atlas slots are sorted in the descending order (done when atlas split changes)
            // - every frame all visible lights are sorted by their screen space size (this handles all cameras where lights
            //   are visible using max value)
            // - all lights in this order get a slot size from the slot list in the same order. Care is taken to not reassign
            //   slot if the size of it is the same and only index changes - this is done using two pass assignment
            const assignCount = Math.min(lights.length, slots.length);
            // first pass - preserve allocated slots for lights requiring slot of the same size
            for(let i = 0; i < assignCount; i++){
                const light = lights[i];
                if (light.castShadows) {
                    light._shadowMap = this.shadowAtlas;
                }
                // if currently assigned slot is the same size as what is needed, and was last used by this light, reuse it
                const previousSlot = slots[light.atlasSlotIndex];
                if (light.atlasVersion === this.version && light.id === previousSlot?.lightId) {
                    const previousSlot = slots[light.atlasSlotIndex];
                    if (previousSlot.size === slots[i].size && !previousSlot.used) {
                        this.assignSlot(light, light.atlasSlotIndex, false);
                    }
                }
            }
            // second pass - assign slots to unhandled lights
            let usedCount = 0;
            for(let i = 0; i < assignCount; i++){
                // skip already used slots
                while(usedCount < slots.length && slots[usedCount].used){
                    usedCount++;
                }
                const light = lights[i];
                if (!light.atlasViewportAllocated) {
                    this.assignSlot(light, usedCount, true);
                }
                // set up all slots
                const slot = slots[light.atlasSlotIndex];
                this.setupSlot(light, slot.rect);
            }
        }
        this.updateUniforms();
    }
}

// In the normal case where the light renders a shadow, the light has a unique shadow map.
// ShadowMapCache is used in two cases:
// 1) by Lightmapper - when lights are baked to lightmaps one at a time, shadow maps are re-used
//    to limit allocations. Those are deleted when baking is done.
// 2) by ShadowRenderer - when VSM blur is done, a temporary buffer is grabbed from the cache
class ShadowMapCache {
    constructor(){
        // maps a shadow map key to an array of shadow maps in the cache
        this.cache = new Map();
    }
    destroy() {
        this.clear();
        this.cache = null;
    }
    // remove all shadowmaps from the cache
    clear() {
        this.cache.forEach((shadowMaps)=>{
            shadowMaps.forEach((shadowMap)=>{
                shadowMap.destroy();
            });
        });
        this.cache.clear();
    }
    // generates a string key for the shadow map required by the light
    getKey(light) {
        const isCubeMap = light._type === LIGHTTYPE_OMNI;
        const shadowType = light._shadowType;
        const resolution = light._shadowResolution;
        return `${isCubeMap}-${shadowType}-${resolution}`;
    }
    // returns shadow map from the cache, or creates a new one if none available
    get(device, light) {
        // get matching shadow buffer from the cache
        const key = this.getKey(light);
        const shadowMaps = this.cache.get(key);
        if (shadowMaps && shadowMaps.length) {
            return shadowMaps.pop();
        }
        // create new one if not in cache
        const shadowMap = ShadowMap.create(device, light);
        shadowMap.cached = true;
        return shadowMap;
    }
    // returns shadow map for the light back to the cache
    add(light, shadowMap) {
        const key = this.getKey(light);
        const shadowMaps = this.cache.get(key);
        if (shadowMaps) {
            shadowMaps.push(shadowMap);
        } else {
            this.cache.set(key, [
                shadowMap
            ]);
        }
    }
}

/**
 * A render pass used to render local non-clustered shadows. It represents rendering to a single
 * face of shadow map, as each face is a separate render target.
 */ class RenderPassShadowLocalNonClustered extends RenderPass {
    constructor(device, shadowRenderer, light, face, applyVsm){
        super(device);
        DebugHelper.setName(this, `${this.name}-${light._node.name}`);
        this.requiresCubemaps = false;
        this.shadowRenderer = shadowRenderer;
        this.light = light;
        this.face = face;
        this.applyVsm = applyVsm;
        this.shadowCamera = shadowRenderer.prepareFace(light, null, face);
        // clear the render target as well, as it contains a single shadow map
        shadowRenderer.setupRenderPass(this, this.shadowCamera, true);
    }
    execute() {
        this.shadowRenderer.renderFace(this.light, null, this.face, false);
    }
    after() {
        if (this.applyVsm) {
            this.shadowRenderer.renderVsm(this.light, this.shadowCamera);
        }
    }
}

/**
 * @import { FrameGraph } from '../../scene/frame-graph.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { Light } from '../../scene/light.js'
 * @import { Renderer } from './renderer.js'
 * @import { ShadowRenderer } from './shadow-renderer.js'
 */ class ShadowRendererLocal {
    constructor(renderer, shadowRenderer){
        // temporary list to collect lights to render shadows for
        this.shadowLights = [];
        this.renderer = renderer;
        this.shadowRenderer = shadowRenderer;
        this.device = renderer.device;
    }
    // cull local shadow map
    cull(light, comp, casters = null) {
        const isClustered = this.renderer.scene.clusteredLightingEnabled;
        // force light visibility if function was manually called
        light.visibleThisFrame = true;
        // allocate shadow map unless in clustered lighting mode
        if (!isClustered) {
            if (!light._shadowMap) {
                light._shadowMap = ShadowMap.create(this.device, light);
            }
        }
        const type = light._type;
        const faceCount = type === LIGHTTYPE_SPOT ? 1 : 6;
        for(let face = 0; face < faceCount; face++){
            // render data are shared between cameras for local lights, so pass null for camera
            const lightRenderData = light.getRenderData(null, face);
            const shadowCam = lightRenderData.shadowCamera;
            shadowCam.nearClip = light.attenuationEnd / 1000;
            shadowCam.farClip = light.attenuationEnd;
            const shadowCamNode = shadowCam._node;
            const lightNode = light._node;
            shadowCamNode.setPosition(lightNode.getPosition());
            if (type === LIGHTTYPE_SPOT) {
                shadowCam.fov = light._outerConeAngle * 2;
                // Camera looks down the negative Z, and spot light points down the negative Y
                shadowCamNode.setRotation(lightNode.getRotation());
                shadowCamNode.rotateLocal(-90, 0, 0);
            } else if (type === LIGHTTYPE_OMNI) {
                // when rendering omni shadows to an atlas, use larger fov by few pixels to allow shadow filtering to stay on a single face
                if (isClustered) {
                    const tileSize = this.shadowRenderer.lightTextureAtlas.shadowAtlasResolution * light.atlasViewport.z / 3; // using 3x3 for cubemap
                    const texelSize = 2 / tileSize;
                    const filterSize = texelSize * this.shadowRenderer.lightTextureAtlas.shadowEdgePixels;
                    shadowCam.fov = Math.atan(1 + filterSize) * math.RAD_TO_DEG * 2;
                } else {
                    shadowCam.fov = 90;
                }
            }
            // cull shadow casters
            this.renderer.updateCameraFrustum(shadowCam);
            this.shadowRenderer.cullShadowCasters(comp, light, lightRenderData.visibleCasters, shadowCam, casters);
        }
    }
    prepareLights(shadowLights, lights) {
        let shadowCamera;
        for(let i = 0; i < lights.length; i++){
            const light = lights[i];
            if (this.shadowRenderer.needsShadowRendering(light) && light.atlasViewportAllocated) {
                shadowLights.push(light);
                for(let face = 0; face < light.numShadowFaces; face++){
                    shadowCamera = this.shadowRenderer.prepareFace(light, null, face);
                }
            }
        }
        return shadowCamera;
    }
    /**
     * Prepare render passes for rendering of shadows for local non-clustered lights. Each shadow face
     * is a separate render pass as it renders to a separate render target.
     *
     * @param {FrameGraph} frameGraph - The frame graph.
     * @param {Light[]} localLights - The list of local lights.
     */ buildNonClusteredRenderPasses(frameGraph, localLights) {
        for(let i = 0; i < localLights.length; i++){
            const light = localLights[i];
            if (this.shadowRenderer.needsShadowRendering(light)) {
                // only spot lights support VSM
                const applyVsm = light._type === LIGHTTYPE_SPOT;
                // create render pass per face
                const faceCount = light.numShadowFaces;
                for(let face = 0; face < faceCount; face++){
                    const renderPass = new RenderPassShadowLocalNonClustered(this.device, this.shadowRenderer, light, face, applyVsm);
                    frameGraph.addRenderPass(renderPass);
                }
            }
        }
    }
}

/**
 * A render pass used to render directional shadows.
 *
 * @ignore
 */ class RenderPassShadowDirectional extends RenderPass {
    constructor(device, shadowRenderer, light, camera, allCascadesRendering){
        super(device);
        DebugHelper.setName(this, `${this.name}-${light._node.name}`);
        this.shadowRenderer = shadowRenderer;
        this.light = light;
        this.camera = camera;
        this.allCascadesRendering = allCascadesRendering;
    }
    execute() {
        const { light, camera, shadowRenderer, allCascadesRendering } = this;
        const faceCount = light.numShadowFaces;
        const shadowUpdateOverrides = light.shadowUpdateOverrides;
        // render all faces
        for(let face = 0; face < faceCount; face++){
            if (shadowUpdateOverrides?.[face] !== SHADOWUPDATE_NONE) {
                shadowRenderer.renderFace(light, camera, face, !allCascadesRendering);
            }
            if (shadowUpdateOverrides?.[face] === SHADOWUPDATE_THISFRAME) {
                shadowUpdateOverrides[face] = SHADOWUPDATE_NONE;
            }
        }
    }
    after() {
        // apply VSM blur if needed
        this.shadowRenderer.renderVsm(this.light, this.camera);
    }
}

/**
 * @import { Camera } from '../camera.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { Light } from '../light.js'
 * @import { Renderer } from './renderer.js'
 * @import { ShadowRenderer } from './shadow-renderer.js'
 * @import { MeshInstance } from '../mesh-instance.js';
 */ const visibleSceneAabb = new BoundingBox();
const center = new Vec3();
const shadowCamView$1 = new Mat4();
const aabbPoints = [
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3(),
    new Vec3()
];
// evaluate depth range the aabb takes in the space of the camera
const _depthRange = {
    min: 0,
    max: 0
};
function getDepthRange(cameraViewMatrix, aabbMin, aabbMax) {
    aabbPoints[0].x = aabbPoints[1].x = aabbPoints[2].x = aabbPoints[3].x = aabbMin.x;
    aabbPoints[1].y = aabbPoints[3].y = aabbPoints[7].y = aabbPoints[5].y = aabbMin.y;
    aabbPoints[2].z = aabbPoints[3].z = aabbPoints[6].z = aabbPoints[7].z = aabbMin.z;
    aabbPoints[4].x = aabbPoints[5].x = aabbPoints[6].x = aabbPoints[7].x = aabbMax.x;
    aabbPoints[0].y = aabbPoints[2].y = aabbPoints[4].y = aabbPoints[6].y = aabbMax.y;
    aabbPoints[0].z = aabbPoints[1].z = aabbPoints[4].z = aabbPoints[5].z = aabbMax.z;
    let minz = 9999999999;
    let maxz = -9999999999;
    for(let i = 0; i < 8; ++i){
        cameraViewMatrix.transformPoint(aabbPoints[i], aabbPoints[i]);
        const z = aabbPoints[i].z;
        if (z < minz) minz = z;
        if (z > maxz) maxz = z;
    }
    _depthRange.min = minz;
    _depthRange.max = maxz;
    return _depthRange;
}
class ShadowRendererDirectional {
    constructor(renderer, shadowRenderer){
        this.renderer = renderer;
        this.shadowRenderer = shadowRenderer;
        this.device = renderer.device;
    }
    // cull directional shadow map
    cull(light, comp, camera, casters = null) {
        // force light visibility if function was manually called
        light.visibleThisFrame = true;
        if (!light._shadowMap) {
            light._shadowMap = ShadowMap.create(this.device, light);
        }
        // generate splits for the cascades
        const nearDist = camera._nearClip;
        this.generateSplitDistances(light, nearDist, Math.min(camera._farClip, light.shadowDistance));
        const shadowUpdateOverrides = light.shadowUpdateOverrides;
        for(let cascade = 0; cascade < light.numCascades; cascade++){
            // if manually controlling cascade rendering and the cascade does not render this frame
            if (shadowUpdateOverrides?.[cascade] === SHADOWUPDATE_NONE) {
                break;
            }
            const lightRenderData = light.getRenderData(camera, cascade);
            const shadowCam = lightRenderData.shadowCamera;
            // assign render target
            // Note: this is done during rendering for all shadow maps, but do it here for the case shadow rendering for the directional light
            // is disabled - we need shadow map to be assigned for rendering to work even in this case. This needs further refactoring - as when
            // shadow rendering is set to SHADOWUPDATE_NONE, we should not even execute shadow map culling
            shadowCam.renderTarget = light._shadowMap.renderTargets[0];
            // viewport
            lightRenderData.shadowViewport.copy(light.cascades[cascade]);
            lightRenderData.shadowScissor.copy(light.cascades[cascade]);
            const shadowCamNode = shadowCam._node;
            const lightNode = light._node;
            shadowCamNode.setPosition(lightNode.getPosition());
            // Camera looks down the negative Z, and directional light points down the negative Y
            shadowCamNode.setRotation(lightNode.getRotation());
            shadowCamNode.rotateLocal(-90, 0, 0);
            // get camera's frustum corners for the cascade, convert them to world space and find their center
            const frustumNearDist = cascade === 0 ? nearDist : light._shadowCascadeDistances[cascade - 1];
            const frustumFarDist = light._shadowCascadeDistances[cascade];
            const frustumPoints = camera.getFrustumCorners(frustumNearDist, frustumFarDist);
            center.set(0, 0, 0);
            const cameraWorldMat = camera.node.getWorldTransform();
            for(let i = 0; i < 8; i++){
                cameraWorldMat.transformPoint(frustumPoints[i], frustumPoints[i]);
                center.add(frustumPoints[i]);
            }
            center.mulScalar(1 / 8);
            // radius of the world space bounding sphere for the frustum slice
            let radius = 0;
            for(let i = 0; i < 8; i++){
                const dist = frustumPoints[i].sub(center).length();
                if (dist > radius) {
                    radius = dist;
                }
            }
            // axis of light coordinate system
            const right = shadowCamNode.right;
            const up = shadowCamNode.up;
            const lightDir = shadowCamNode.forward;
            // transform the sphere's center into the center of the shadow map, pixel aligned.
            // this makes the shadow map stable and avoids shimmering on the edges when the camera moves
            const sizeRatio = 0.25 * light._shadowResolution / radius;
            const x = Math.ceil(center.dot(up) * sizeRatio) / sizeRatio;
            const y = Math.ceil(center.dot(right) * sizeRatio) / sizeRatio;
            const scaledUp = up.mulScalar(x);
            const scaledRight = right.mulScalar(y);
            const dot = center.dot(lightDir);
            const scaledDir = lightDir.mulScalar(dot);
            center.add2(scaledUp, scaledRight).add(scaledDir);
            // look at the center from far away to include all casters during culling
            shadowCamNode.setPosition(center);
            shadowCamNode.translateLocal(0, 0, 1000000);
            shadowCam.nearClip = 0.01;
            shadowCam.farClip = 2000000;
            shadowCam.orthoHeight = radius;
            // cull shadow casters
            this.renderer.updateCameraFrustum(shadowCam);
            this.shadowRenderer.cullShadowCasters(comp, light, lightRenderData.visibleCasters, shadowCam, casters);
            const cascadeFlag = 1 << cascade;
            const visibleCasters = lightRenderData.visibleCasters;
            const origNumVisibleCasters = visibleCasters.length;
            let numVisibleCasters = 0;
            // exclude all mesh instances that are hidden for this cascade.
            // find out AABB of visible shadow casters
            for(let i = 0; i < origNumVisibleCasters; i++){
                const meshInstance = visibleCasters[i];
                if (meshInstance.shadowCascadeMask & cascadeFlag) {
                    visibleCasters[numVisibleCasters++] = meshInstance;
                    if (numVisibleCasters === 1) {
                        visibleSceneAabb.copy(meshInstance.aabb);
                    } else {
                        visibleSceneAabb.add(meshInstance.aabb);
                    }
                }
            }
            // remove empty tail
            if (origNumVisibleCasters !== numVisibleCasters) {
                visibleCasters.length = numVisibleCasters;
            }
            // calculate depth range of the caster's AABB from the point of view of the shadow camera
            shadowCamView$1.copy(shadowCamNode.getWorldTransform()).invert();
            const depthRange = getDepthRange(shadowCamView$1, visibleSceneAabb.getMin(), visibleSceneAabb.getMax());
            // adjust shadow camera's near and far plane to the depth range of casters to maximize precision
            // of values stored in the shadow map. Make it slightly larger to avoid clipping on near / far plane.
            shadowCamNode.translateLocal(0, 0, depthRange.max + 0.1);
            shadowCam.farClip = depthRange.max - depthRange.min + 0.2;
            lightRenderData.projectionCompensation = radius;
        }
    }
    // function to generate frustum split distances
    generateSplitDistances(light, nearDist, farDist) {
        light._shadowCascadeDistances.fill(farDist);
        for(let i = 1; i < light.numCascades; i++){
            //  lerp between linear and logarithmic distance, called practical split distance
            const fraction = i / light.numCascades;
            const linearDist = nearDist + (farDist - nearDist) * fraction;
            const logDist = nearDist * (farDist / nearDist) ** fraction;
            const dist = math.lerp(linearDist, logDist, light.cascadeDistribution);
            light._shadowCascadeDistances[i - 1] = dist;
        }
    }
    /**
     * Create a render pass for directional light shadow rendering for a specified camera.
     *
     * @param {Light} light - The directional light.
     * @param {Camera} camera - The camera.
     * @returns {RenderPassShadowDirectional|null} - The render pass if the shadow rendering is
     * required, or null otherwise.
     */ getLightRenderPass(light, camera) {
        Debug.assert(light && light._type === LIGHTTYPE_DIRECTIONAL);
        let renderPass = null;
        if (this.shadowRenderer.needsShadowRendering(light)) {
            // shadow cascades have more faces rendered within a singe render pass
            const faceCount = light.numShadowFaces;
            const shadowUpdateOverrides = light.shadowUpdateOverrides;
            // prepare render targets / cameras for rendering
            let allCascadesRendering = true;
            let shadowCamera;
            for(let face = 0; face < faceCount; face++){
                if (shadowUpdateOverrides?.[face] === SHADOWUPDATE_NONE) {
                    allCascadesRendering = false;
                }
                shadowCamera = this.shadowRenderer.prepareFace(light, camera, face);
            }
            renderPass = new RenderPassShadowDirectional(this.device, this.shadowRenderer, light, camera, allCascadesRendering);
            // setup render pass using any of the cameras, they all have the same pass related properties
            this.shadowRenderer.setupRenderPass(renderPass, shadowCamera, allCascadesRendering);
        }
        return renderPass;
    }
}

/**
 * @import { Camera } from '../camera.js'
 * @import { LayerComposition } from '../composition/layer-composition.js'
 * @import { LightTextureAtlas } from '../lighting/light-texture-atlas.js'
 * @import { Light } from '../light.js'
 * @import { MeshInstance } from '../mesh-instance.js'
 * @import { Renderer } from './renderer.js'
 * @import { ShaderPassInfo } from '../shader-pass.js'
 */ const tempSet$1 = new Set();
const shadowCamView = new Mat4();
const shadowCamViewProj = new Mat4();
const pixelOffset = new Float32Array(2);
const blurScissorRect = new Vec4(1, 1, 0, 0);
const viewportMatrix = new Mat4();
function gauss(x, sigma) {
    return Math.exp(-(x * x) / (2.0 * sigma * sigma));
}
function gaussWeights(kernelSize) {
    const sigma = (kernelSize - 1) / (2 * 3);
    const halfWidth = (kernelSize - 1) * 0.5;
    const values = new Array(kernelSize);
    let sum = 0.0;
    for(let i = 0; i < kernelSize; ++i){
        values[i] = gauss(i - halfWidth, sigma);
        sum += values[i];
    }
    for(let i = 0; i < kernelSize; ++i){
        values[i] /= sum;
    }
    return values;
}
class ShadowRenderer {
    /**
     * @param {Renderer} renderer - The renderer.
     * @param {LightTextureAtlas} lightTextureAtlas - The shadow map atlas.
     */ constructor(renderer, lightTextureAtlas){
        /**
     * A cache of shadow passes. First index is looked up by light type, second by shadow type.
     *
     * @type {ShaderPassInfo[][]}
     * @private
     */ this.shadowPassCache = [];
        this.device = renderer.device;
        /** @type {Renderer} */ this.renderer = renderer;
        /** @type {LightTextureAtlas} */ this.lightTextureAtlas = lightTextureAtlas;
        const scope = this.device.scope;
        // VSM
        this.sourceId = scope.resolve('source');
        this.pixelOffsetId = scope.resolve('pixelOffset');
        this.weightId = scope.resolve('weight[0]');
        // cache for vsm blur shaders
        this.blurVsmShader = [
            {},
            {}
        ];
        this.blurVsmWeights = {};
        // uniforms
        this.shadowMapLightRadiusId = scope.resolve('light_radius');
        // view bind group format with its uniform buffer format
        this.viewUniformFormat = null;
        this.viewBindGroupFormat = null;
        // blend states
        this.blendStateWrite = new BlendState();
        this.blendStateNoWrite = new BlendState();
        this.blendStateNoWrite.setColorWrite(false, false, false, false);
    }
    // creates shadow camera for a light and sets up its constant properties
    static createShadowCamera(shadowType, type, face) {
        const shadowCam = LightCamera.create('ShadowCamera', type, face);
        const shadowInfo = shadowTypeInfo.get(shadowType);
        Debug.assert(shadowInfo);
        const isVsm = shadowInfo?.vsm ?? false;
        const isPcf = shadowInfo?.pcf ?? false;
        // don't clear the color buffer if rendering a depth map
        if (isVsm) {
            shadowCam.clearColor = new Color(0, 0, 0, 0);
        } else {
            shadowCam.clearColor = new Color(1, 1, 1, 1);
        }
        shadowCam.clearDepthBuffer = true;
        shadowCam.clearStencilBuffer = false;
        // clear color buffer only when using it
        shadowCam.clearColorBuffer = !isPcf;
        return shadowCam;
    }
    _cullShadowCastersInternal(meshInstances, visible, camera) {
        const numInstances = meshInstances.length;
        for(let i = 0; i < numInstances; i++){
            const meshInstance = meshInstances[i];
            if (meshInstance.castShadow) {
                if (!meshInstance.cull || meshInstance._isVisible(camera)) {
                    meshInstance.visibleThisFrame = true;
                    visible.push(meshInstance);
                }
            }
        }
    }
    /**
     * Culls the list of shadow casters used by the light by the camera, storing visible mesh
     * instances in the specified array.
     *
     * @param {LayerComposition} comp - The layer composition used as a source of shadow casters,
     * if those are not provided directly.
     * @param {Light} light - The light.
     * @param {MeshInstance[]} visible - The array to store visible mesh instances in.
     * @param {Camera} camera - The camera.
     * @param {MeshInstance[]} [casters] - Optional array of mesh instances to use as casters.
     */ cullShadowCasters(comp, light, visible, camera, casters) {
        // event before the camera is culling
        this.renderer.scene?.fire(EVENT_PRECULL, camera);
        visible.length = 0;
        // if the casters are supplied, use them
        if (casters) {
            this._cullShadowCastersInternal(casters, visible, camera);
        } else {
            // for each layer
            const layers = comp.layerList;
            const len = layers.length;
            for(let i = 0; i < len; i++){
                const layer = layers[i];
                if (layer._lightsSet.has(light)) {
                    // layer can be in the list two times (opaque, transp), add casters only one time
                    if (!tempSet$1.has(layer)) {
                        tempSet$1.add(layer);
                        this._cullShadowCastersInternal(layer.shadowCasters, visible, camera);
                    }
                }
            }
            tempSet$1.clear();
        }
        // this sorts the shadow casters by the shader id
        visible.sort(this.sortCompareShader);
        // event after the camera is done with culling
        this.renderer.scene?.fire(EVENT_POSTCULL, camera);
    }
    sortCompareShader(drawCallA, drawCallB) {
        const keyA = drawCallA._sortKeyShadow;
        const keyB = drawCallB._sortKeyShadow;
        if (keyA === keyB) {
            return drawCallB.mesh.id - drawCallA.mesh.id;
        }
        return keyB - keyA;
    }
    setupRenderState(device, light) {
        // Set standard shadowmap states
        const isClustered = this.renderer.scene.clusteredLightingEnabled;
        const useShadowSampler = isClustered ? light._isPcf : light._isPcf && light._type !== LIGHTTYPE_OMNI; // for non-clustered, point light is using depth encoded in color buffer (should change to shadow sampler)
        device.setBlendState(useShadowSampler ? this.blendStateNoWrite : this.blendStateWrite);
        device.setDepthState(light.shadowDepthState);
        device.setStencilState(null, null);
    }
    dispatchUniforms(light, shadowCam, lightRenderData, face) {
        const shadowCamNode = shadowCam._node;
        // position / range
        if (light._type !== LIGHTTYPE_DIRECTIONAL) {
            this.renderer.dispatchViewPos(shadowCamNode.getPosition());
            this.shadowMapLightRadiusId.setValue(light.attenuationEnd);
        }
        // view-projection shadow matrix
        shadowCamView.setTRS(shadowCamNode.getPosition(), shadowCamNode.getRotation(), Vec3.ONE).invert();
        shadowCamViewProj.mul2(shadowCam.projectionMatrix, shadowCamView);
        // viewport handling
        const rectViewport = lightRenderData.shadowViewport;
        shadowCam.rect = rectViewport;
        shadowCam.scissorRect = lightRenderData.shadowScissor;
        viewportMatrix.setViewport(rectViewport.x, rectViewport.y, rectViewport.z, rectViewport.w);
        lightRenderData.shadowMatrix.mul2(viewportMatrix, shadowCamViewProj);
        if (light._type === LIGHTTYPE_DIRECTIONAL) {
            // copy matrix to shadow cascade palette
            light._shadowMatrixPalette.set(lightRenderData.shadowMatrix.data, face * 16);
        }
    }
    /**
     * @param {Light} light - The light.
     * @returns {number} Index of shadow pass info.
     */ getShadowPass(light) {
        // get shader pass from cache for this light type and shadow type
        const lightType = light._type;
        const shadowType = light._shadowType;
        let shadowPassInfo = this.shadowPassCache[lightType]?.[shadowType];
        if (!shadowPassInfo) {
            // new shader pass if not in cache
            const shadowPassName = `ShadowPass_${lightType}_${shadowType}`;
            shadowPassInfo = ShaderPass.get(this.device).allocate(shadowPassName, {
                isShadow: true,
                lightType: lightType,
                shadowType: shadowType
            });
            // add it to the cache
            if (!this.shadowPassCache[lightType]) {
                this.shadowPassCache[lightType] = [];
            }
            this.shadowPassCache[lightType][shadowType] = shadowPassInfo;
        }
        return shadowPassInfo.index;
    }
    /**
     * @param {MeshInstance[]} visibleCasters - Visible mesh instances.
     * @param {Light} light - The light.
     * @param {Camera} camera - The camera.
     */ submitCasters(visibleCasters, light, camera) {
        const device = this.device;
        const renderer = this.renderer;
        const scene = renderer.scene;
        const passFlags = 1 << SHADER_SHADOW;
        const shadowPass = this.getShadowPass(light);
        const cameraShaderParams = camera.shaderParams;
        // reverse face culling when shadow map has flipY set to true which cases reversed winding order
        const flipFactor = camera.renderTarget.flipY ? -1 : 1;
        // Render
        const count = visibleCasters.length;
        for(let i = 0; i < count; i++){
            const meshInstance = visibleCasters[i];
            const mesh = meshInstance.mesh;
            // skip instanced rendering with 0 instances
            const instancingData = meshInstance.instancingData;
            if (instancingData && instancingData.count <= 0) {
                continue;
            }
            meshInstance.ensureMaterial(device);
            const material = meshInstance.material;
            DebugGraphics.pushGpuMarker(device, `Node: ${meshInstance.node.name}, Material: ${material.name}`);
            // set basic material states/parameters
            renderer.setBaseConstants(device, material);
            renderer.setSkinning(device, meshInstance);
            if (material.dirty) {
                material.updateUniforms(device, scene);
                material.dirty = false;
            }
            renderer.setupCullMode(true, flipFactor, meshInstance);
            // Uniforms I (shadow): material
            material.setParameters(device);
            // Uniforms II (shadow): meshInstance overrides
            meshInstance.setParameters(device, passFlags);
            const shaderInstance = meshInstance.getShaderInstance(shadowPass, 0, scene, cameraShaderParams, this.viewUniformFormat, this.viewBindGroupFormat);
            const shadowShader = shaderInstance.shader;
            Debug.assert(shadowShader, `no shader for pass ${shadowPass}`, material);
            if (shadowShader.failed) continue;
            // sort shadow casters by shader
            meshInstance._sortKeyShadow = shadowShader.id;
            device.setShader(shadowShader);
            // set buffers
            renderer.setVertexBuffers(device, mesh);
            renderer.setMorphing(device, meshInstance.morphInstance);
            if (instancingData) {
                device.setVertexBuffer(instancingData.vertexBuffer);
            }
            // mesh / mesh normal matrix
            renderer.setMeshInstanceMatrices(meshInstance);
            renderer.setupMeshUniformBuffers(shaderInstance);
            // draw
            const style = meshInstance.renderStyle;
            const indirectSlot = meshInstance.indirectData?.get(camera);
            device.draw(mesh.primitive[style], mesh.indexBuffer[style], instancingData?.count, indirectSlot);
            renderer._shadowDrawCalls++;
            if (instancingData) {
                renderer._instancedDrawCalls++;
            }
            DebugGraphics.popGpuMarker(device);
        }
    }
    needsShadowRendering(light) {
        const needs = light.enabled && light.castShadows && light.shadowUpdateMode !== SHADOWUPDATE_NONE && light.visibleThisFrame;
        if (light.shadowUpdateMode === SHADOWUPDATE_THISFRAME) {
            light.shadowUpdateMode = SHADOWUPDATE_NONE;
        }
        if (needs) {
            this.renderer._shadowMapUpdates += light.numShadowFaces;
        }
        return needs;
    }
    getLightRenderData(light, camera, face) {
        // directional shadows are per camera, so get appropriate render data
        return light.getRenderData(light._type === LIGHTTYPE_DIRECTIONAL ? camera : null, face);
    }
    setupRenderPass(renderPass, shadowCamera, clearRenderTarget) {
        const rt = shadowCamera.renderTarget;
        renderPass.init(rt);
        renderPass.depthStencilOps.clearDepthValue = 1;
        renderPass.depthStencilOps.clearDepth = clearRenderTarget;
        // if rendering to depth buffer
        if (rt.depthBuffer) {
            renderPass.depthStencilOps.storeDepth = true;
        } else {
            renderPass.colorOps.clearValue.copy(shadowCamera.clearColor);
            renderPass.colorOps.clear = clearRenderTarget;
            renderPass.depthStencilOps.storeDepth = false;
        }
        // not sampling dynamically generated cubemaps
        renderPass.requiresCubemaps = false;
    }
    // prepares render target / render target settings to allow render pass to be set up
    prepareFace(light, camera, face) {
        const type = light._type;
        const lightRenderData = this.getLightRenderData(light, camera, face);
        const shadowCam = lightRenderData.shadowCamera;
        // assign render target for the face
        const renderTargetIndex = type === LIGHTTYPE_DIRECTIONAL ? 0 : face;
        shadowCam.renderTarget = light._shadowMap.renderTargets[renderTargetIndex];
        return shadowCam;
    }
    renderFace(light, camera, face, clear, insideRenderPass = true) {
        const device = this.device;
        const shadowMapStartTime = now();
        DebugGraphics.pushGpuMarker(device, `SHADOW ${light._node.name} FACE ${face}`);
        const lightRenderData = this.getLightRenderData(light, camera, face);
        const shadowCam = lightRenderData.shadowCamera;
        this.dispatchUniforms(light, shadowCam, lightRenderData, face);
        const rt = shadowCam.renderTarget;
        const renderer = this.renderer;
        renderer.setCameraUniforms(shadowCam, rt);
        if (device.supportsUniformBuffers) {
            renderer.setupViewUniformBuffers(lightRenderData.viewBindGroups, this.viewUniformFormat, this.viewBindGroupFormat, null);
        }
        if (insideRenderPass) {
            renderer.setupViewport(shadowCam, rt);
            // clear here is used to clear a viewport inside render target.
            if (clear) {
                renderer.clear(shadowCam);
            }
        } else {
            // this is only used by lightmapper, till it's converted to render passes
            renderer.clearView(shadowCam, rt, true, false);
        }
        this.setupRenderState(device, light);
        // render mesh instances
        this.submitCasters(lightRenderData.visibleCasters, light, shadowCam);
        DebugGraphics.popGpuMarker(device);
        renderer._shadowMapTime += now() - shadowMapStartTime;
    }
    render(light, camera, insideRenderPass = true) {
        if (this.needsShadowRendering(light)) {
            const faceCount = light.numShadowFaces;
            // render faces
            for(let face = 0; face < faceCount; face++){
                this.prepareFace(light, camera, face);
                this.renderFace(light, camera, face, true, insideRenderPass);
            }
            // apply vsm
            this.renderVsm(light, camera);
        }
    }
    renderVsm(light, camera) {
        // VSM blur if light supports vsm (directional and spot in general)
        if (light._isVsm && light._vsmBlurSize > 1) {
            // in clustered mode, only directional light can be vms
            const isClustered = this.renderer.scene.clusteredLightingEnabled;
            if (!isClustered || light._type === LIGHTTYPE_DIRECTIONAL) {
                this.applyVsmBlur(light, camera);
            }
        }
    }
    getVsmBlurShader(blurMode, filterSize) {
        const cache = this.blurVsmShader;
        let blurShader = cache[blurMode][filterSize];
        if (!blurShader) {
            this.blurVsmWeights[filterSize] = gaussWeights(filterSize);
            const defines = new Map();
            defines.set('{SAMPLES}', filterSize);
            if (blurMode === 1) defines.set('GAUSS', '');
            blurShader = ShaderUtils.createShader(this.device, {
                uniqueName: `blurVsm${blurMode}${filterSize}`,
                attributes: {
                    vertex_position: SEMANTIC_POSITION
                },
                vertexChunk: 'fullscreenQuadVS',
                fragmentChunk: 'blurVSMPS',
                fragmentDefines: defines
            });
            cache[blurMode][filterSize] = blurShader;
        }
        return blurShader;
    }
    applyVsmBlur(light, camera) {
        const device = this.device;
        DebugGraphics.pushGpuMarker(device, `VSM ${light._node.name}`);
        // render state
        device.setBlendState(BlendState.NOBLEND);
        const lightRenderData = light.getRenderData(light._type === LIGHTTYPE_DIRECTIONAL ? camera : null, 0);
        const shadowCam = lightRenderData.shadowCamera;
        const origShadowMap = shadowCam.renderTarget;
        // temporary render target for blurring
        // TODO: this is probably not optimal and shadow map could have depth buffer on in addition to color buffer,
        // and for blurring only one buffer is needed.
        const tempShadowMap = this.renderer.shadowMapCache.get(device, light);
        const tempRt = tempShadowMap.renderTargets[0];
        const blurMode = light.vsmBlurMode;
        const filterSize = light._vsmBlurSize;
        const blurShader = this.getVsmBlurShader(blurMode, filterSize);
        blurScissorRect.z = light._shadowResolution - 2;
        blurScissorRect.w = blurScissorRect.z;
        // Blur horizontal
        this.sourceId.setValue(origShadowMap.colorBuffer);
        pixelOffset[0] = 1 / light._shadowResolution;
        pixelOffset[1] = 0;
        this.pixelOffsetId.setValue(pixelOffset);
        if (blurMode === BLUR_GAUSSIAN) this.weightId.setValue(this.blurVsmWeights[filterSize]);
        drawQuadWithShader(device, tempRt, blurShader, null, blurScissorRect);
        // Blur vertical
        this.sourceId.setValue(tempRt.colorBuffer);
        pixelOffset[1] = pixelOffset[0];
        pixelOffset[0] = 0;
        this.pixelOffsetId.setValue(pixelOffset);
        drawQuadWithShader(device, origShadowMap, blurShader, null, blurScissorRect);
        // return the temporary shadow map back to the cache
        this.renderer.shadowMapCache.add(light, tempShadowMap);
        DebugGraphics.popGpuMarker(device);
    }
    initViewBindGroupFormat() {
        if (this.device.supportsUniformBuffers && !this.viewUniformFormat) {
            // format of the view uniform buffer
            this.viewUniformFormat = new UniformBufferFormat(this.device, [
                new UniformFormat('matrix_viewProjection', UNIFORMTYPE_MAT4)
            ]);
            // format of the view bind group - contains single uniform buffer, and no textures
            this.viewBindGroupFormat = new BindGroupFormat(this.device, [
                new BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)
            ]);
        }
    }
    frameUpdate() {
        this.initViewBindGroupFormat();
    }
}

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { RenderAction } from '../composition/render-action.js'
 */ const tempClusterArray = [];
/**
 * A class managing instances of world clusters used by the renderer for layers with
 * unique sets of clustered lights.
 *
 * @ignore
 */ class WorldClustersAllocator {
    /**
     * Create a new instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device.
     */ constructor(graphicsDevice){
        /**
     * Empty cluster with no lights.
     *
     * @type {WorldClusters|null}
     */ this._empty = null;
        /**
     * All allocated clusters
     *
     * @type {WorldClusters[]}
     */ this._allocated = [];
        /**
     * Render actions with all unique light clusters. The key is the hash of lights on a layer, the
     * value is a render action with unique light clusters.
     *
     * @type {Map<number, RenderAction>}
     */ this._clusters = new Map();
        this.device = graphicsDevice;
    }
    destroy() {
        // empty light cluster
        if (this._empty) {
            this._empty.destroy();
            this._empty = null;
        }
        // all other clusters
        this._allocated.forEach((cluster)=>{
            cluster.destroy();
        });
        this._allocated.length = 0;
    }
    get count() {
        return this._allocated.length;
    }
    // returns an empty light cluster object to be used when no lights are used
    get empty() {
        if (!this._empty) {
            // create cluster structure with no lights
            const empty = new WorldClusters(this.device);
            empty.name = 'ClusterEmpty';
            // update it once to avoid doing it each frame
            empty.update([]);
            this._empty = empty;
        }
        return this._empty;
    }
    // assign light clusters to render actions that need it
    assign(renderPasses) {
        // reuse previously allocated clusters
        tempClusterArray.push(...this._allocated);
        this._allocated.length = 0;
        this._clusters.clear();
        // update render actions in passes that use them
        const passCount = renderPasses.length;
        for(let p = 0; p < passCount; p++){
            const renderPass = renderPasses[p];
            const renderActions = renderPass.renderActions;
            if (renderActions) {
                // process all render actions
                const count = renderActions.length;
                for(let i = 0; i < count; i++){
                    const ra = renderActions[i];
                    ra.lightClusters = null;
                    // if the layer has lights used by clusters, and meshes
                    const layer = ra.layer;
                    if (layer.hasClusteredLights && layer.meshInstances.length) {
                        // use existing clusters if the lights on the layer are the same
                        const hash = layer.getLightIdHash();
                        const existingRenderAction = this._clusters.get(hash);
                        let clusters = existingRenderAction?.lightClusters;
                        // no match, needs new clusters
                        if (!clusters) {
                            // use already allocated cluster from last frame, or create a new one
                            clusters = tempClusterArray.pop() ?? new WorldClusters(this.device);
                            DebugHelper.setName(clusters, `Cluster-${this._allocated.length}`);
                            this._allocated.push(clusters);
                            this._clusters.set(hash, ra);
                        }
                        ra.lightClusters = clusters;
                    }
                    // no clustered lights, use the cluster with no lights
                    if (!ra.lightClusters) {
                        ra.lightClusters = this.empty;
                    }
                }
            }
        }
        // delete leftovers
        tempClusterArray.forEach((item)=>item.destroy());
        tempClusterArray.length = 0;
    }
    update(renderPasses, lighting) {
        // assign clusters to render actions
        this.assign(renderPasses);
        // update all unique clusters
        this._clusters.forEach((renderAction)=>{
            const layer = renderAction.layer;
            const cluster = renderAction.lightClusters;
            cluster.update(layer.clusteredLightsSet, lighting);
        });
    }
}

/**
 * @import { EventHandle } from '../../core/event-handle.js';
 */ const _viewport = new Vec4();
// for rendering of cookies, store inverse view projection matrices for 6 faces, allowing cubemap faces to be copied into the atlas
const _invViewProjMatrices = [];
/**
 * A render pass used to render cookie textures (both 2D and Cubemap) into the texture atlas.
 *
 * @ignore
 */ class RenderPassCookieRenderer extends RenderPass {
    constructor(device, cubeSlotsOffsets){
        super(device), /** @type {QuadRender|null} */ this._quadRenderer2D = null, /** @type {QuadRender|null} */ this._quadRendererCube = null, this._filteredLights = [], this._forceCopy = false, /**
     * Event handle for device restored event.
     *
     * @type {EventHandle|null}
     * @private
     */ this._evtDeviceRestored = null;
        this._cubeSlotsOffsets = cubeSlotsOffsets;
        this.requiresCubemaps = false;
        this.blitTextureId = device.scope.resolve('blitTexture');
        this.invViewProjId = device.scope.resolve('invViewProj');
        this._evtDeviceRestored = device.on('devicerestored', this.onDeviceRestored, this);
    }
    destroy() {
        this._quadRenderer2D?.destroy();
        this._quadRenderer2D = null;
        this._quadRendererCube?.destroy();
        this._quadRendererCube = null;
        this._evtDeviceRestored?.off();
        this._evtDeviceRestored = null;
    }
    static create(renderTarget, cubeSlotsOffsets) {
        Debug.assert(renderTarget);
        // prepare a single render pass to render all quads to the render target
        const renderPass = new RenderPassCookieRenderer(renderTarget.device, cubeSlotsOffsets);
        renderPass.init(renderTarget);
        renderPass.colorOps.clear = false;
        renderPass.depthStencilOps.clearDepth = false;
        return renderPass;
    }
    onDeviceRestored() {
        this._forceCopy = true;
    }
    update(lights) {
        // pick lights we need to update the cookies for
        const filteredLights = this._filteredLights;
        this.filter(lights, filteredLights);
        // enabled / disable the pass
        this.executeEnabled = filteredLights.length > 0;
    }
    filter(lights, filteredLights) {
        for(let i = 0; i < lights.length; i++){
            const light = lights[i];
            // skip directional lights
            if (light._type === LIGHTTYPE_DIRECTIONAL) {
                continue;
            }
            // skip clustered cookies with no assigned atlas slot
            if (!light.atlasViewportAllocated) {
                continue;
            }
            // only render cookie when the slot is reassigned (assuming the cookie texture is static)
            if (!light.atlasSlotUpdated && !this._forceCopy) {
                continue;
            }
            if (light.enabled && light.cookie && light.visibleThisFrame) {
                filteredLights.push(light);
            }
        }
        this._forceCopy = false;
    }
    initInvViewProjMatrices() {
        if (!_invViewProjMatrices.length) {
            for(let face = 0; face < 6; face++){
                const camera = LightCamera.create(null, LIGHTTYPE_OMNI, face);
                const projMat = camera.projectionMatrix;
                const viewMat = camera.node.getLocalTransform().clone().invert();
                _invViewProjMatrices[face] = new Mat4().mul2(projMat, viewMat).invert();
            }
        }
    }
    get quadRenderer2D() {
        if (!this._quadRenderer2D) {
            const shader = ShaderUtils.createShader(this.device, {
                uniqueName: 'cookieRenderer2d',
                attributes: {
                    vertex_position: SEMANTIC_POSITION
                },
                vertexChunk: 'cookieBlitVS',
                fragmentChunk: 'cookieBlit2DPS'
            });
            this._quadRenderer2D = new QuadRender(shader);
        }
        return this._quadRenderer2D;
    }
    get quadRendererCube() {
        if (!this._quadRendererCube) {
            const shader = ShaderUtils.createShader(this.device, {
                uniqueName: 'cookieRendererCube',
                attributes: {
                    vertex_position: SEMANTIC_POSITION
                },
                vertexChunk: 'cookieBlitVS',
                fragmentChunk: 'cookieBlitCubePS'
            });
            this._quadRendererCube = new QuadRender(shader);
        }
        return this._quadRendererCube;
    }
    execute() {
        // render state
        const device = this.device;
        device.setBlendState(BlendState.NOBLEND);
        device.setCullMode(CULLFACE_NONE);
        device.setDepthState(DepthState.NODEPTH);
        device.setStencilState();
        const renderTargetWidth = this.renderTarget.colorBuffer.width;
        const cubeSlotsOffsets = this._cubeSlotsOffsets;
        const filteredLights = this._filteredLights;
        for(let i = 0; i < filteredLights.length; i++){
            const light = filteredLights[i];
            DebugGraphics.pushGpuMarker(this.device, `COOKIE ${light._node.name}`);
            const faceCount = light.numShadowFaces;
            const quad = faceCount > 1 ? this.quadRendererCube : this.quadRenderer2D;
            if (faceCount > 1) {
                this.initInvViewProjMatrices();
            }
            // source texture
            this.blitTextureId.setValue(light.cookie);
            // render it to a viewport of the target
            for(let face = 0; face < faceCount; face++){
                _viewport.copy(light.atlasViewport);
                if (faceCount > 1) {
                    // for cubemap, render to one of the 3x3 sub-areas
                    const smallSize = _viewport.z / 3;
                    const offset = cubeSlotsOffsets[face];
                    _viewport.x += smallSize * offset.x;
                    _viewport.y += smallSize * offset.y;
                    _viewport.z = smallSize;
                    _viewport.w = smallSize;
                    // cubemap face projection uniform
                    this.invViewProjId.setValue(_invViewProjMatrices[face].data);
                }
                _viewport.mulScalar(renderTargetWidth);
                quad.render(_viewport);
            }
            DebugGraphics.popGpuMarker(device);
        }
        filteredLights.length = 0;
    }
}

/**
 * A render pass used to render local clustered shadows. This is done inside a single render pass,
 * as all shadows are part of a single render target atlas.
 *
 * @ignore
 */ class RenderPassShadowLocalClustered extends RenderPass {
    constructor(device, shadowRenderer, shadowRendererLocal){
        super(device);
        this.requiresCubemaps = false;
        this.shadowRenderer = shadowRenderer;
        this.shadowRendererLocal = shadowRendererLocal;
    }
    update(localLights) {
        // prepare render targets / shadow cameras for rendering
        const shadowLights = this.shadowRendererLocal.shadowLights;
        const shadowCamera = this.shadowRendererLocal.prepareLights(shadowLights, localLights);
        // if any shadows need to be rendered
        const count = shadowLights.length;
        this.enabled = count > 0;
        if (count) {
            // setup render pass using any of the cameras, they all have the same pass related properties
            // Note that the render pass is set up to not clear the render target, as individual shadow maps clear it
            this.shadowRenderer.setupRenderPass(this, shadowCamera, false);
        }
    }
    execute() {
        const shadowLights = this.shadowRendererLocal.shadowLights;
        const count = shadowLights.length;
        for(let i = 0; i < count; i++){
            const light = shadowLights[i];
            for(let face = 0; face < light.numShadowFaces; face++){
                this.shadowRenderer.renderFace(light, null, face, true);
            }
        }
        shadowLights.length = 0;
    }
}

/**
 * A render pass used to update clustered lighting data - shadows, cookies, world clusters.
 *
 * @ignore
 */ class RenderPassUpdateClustered extends RenderPass {
    constructor(device, renderer, shadowRenderer, shadowRendererLocal, lightTextureAtlas){
        super(device);
        this.renderer = renderer;
        this.frameGraph = null;
        // render cookies for all local visible lights
        this.cookiesRenderPass = RenderPassCookieRenderer.create(lightTextureAtlas.cookieRenderTarget, lightTextureAtlas.cubeSlotsOffsets);
        this.beforePasses.push(this.cookiesRenderPass);
        // local shadows - these are shared by all cameras (not entirely correctly)
        this.shadowRenderPass = new RenderPassShadowLocalClustered(device, shadowRenderer, shadowRendererLocal);
        this.beforePasses.push(this.shadowRenderPass);
    }
    update(frameGraph, shadowsEnabled, cookiesEnabled, lights, localLights) {
        this.frameGraph = frameGraph;
        this.cookiesRenderPass.enabled = cookiesEnabled;
        if (cookiesEnabled) {
            this.cookiesRenderPass.update(lights);
        }
        this.shadowRenderPass.enabled = shadowsEnabled;
        if (shadowsEnabled) {
            this.shadowRenderPass.update(localLights);
        }
    }
    destroy() {
        this.cookiesRenderPass.destroy();
        this.cookiesRenderPass = null;
    }
    execute() {
        const startTime = now();
        const { renderer } = this;
        const { scene } = renderer;
        renderer.worldClustersAllocator.update(this.frameGraph.renderPasses, scene.lighting);
        renderer._lightClustersTime += now() - startTime;
        renderer._lightClusters = renderer.worldClustersAllocator.count;
    }
}

/**
 * @import { Camera } from '../camera.js'
 * @import { CulledInstances } from '../layer.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { LayerComposition } from '../composition/layer-composition.js'
 * @import { Light } from '../light.js'
 * @import { MeshInstance } from '../mesh-instance.js'
 * @import { RenderTarget } from '../../platform/graphics/render-target.js'
 * @import { Scene } from '../scene.js'
 * @import { GSplatDirector } from '../gsplat-unified/gsplat-director.js'
 */ let _skinUpdateIndex = 0;
const viewProjMat = new Mat4();
const viewInvMat = new Mat4();
const viewMat = new Mat4();
const viewMat3 = new Mat3();
const tempSphere = new BoundingSphere();
const _flipYMat = new Mat4().setScale(1, -1, 1);
const _tempLightSet = new Set();
const _tempLayerSet = new Set();
const _dynamicBindGroup = new DynamicBindGroup();
// Converts a projection matrix in OpenGL style (depth range of -1..1) to a DirectX style (depth range of 0..1).
const _fixProjRangeMat = new Mat4().set([
    1,
    0,
    0,
    0,
    0,
    1,
    0,
    0,
    0,
    0,
    0.5,
    0,
    0,
    0,
    0.5,
    1
]);
// helton sequence of 2d offsets for jittering
const _haltonSequence = [
    new Vec2(0.5, 0.333333),
    new Vec2(0.25, 0.666667),
    new Vec2(0.75, 0.111111),
    new Vec2(0.125, 0.444444),
    new Vec2(0.625, 0.777778),
    new Vec2(0.375, 0.222222),
    new Vec2(0.875, 0.555556),
    new Vec2(0.0625, 0.888889),
    new Vec2(0.5625, 0.037037),
    new Vec2(0.3125, 0.370370),
    new Vec2(0.8125, 0.703704),
    new Vec2(0.1875, 0.148148),
    new Vec2(0.6875, 0.481481),
    new Vec2(0.4375, 0.814815),
    new Vec2(0.9375, 0.259259),
    new Vec2(0.03125, 0.592593)
];
const _tempProjMat0 = new Mat4();
const _tempProjMat1 = new Mat4();
const _tempProjMat2 = new Mat4();
const _tempProjMat3 = new Mat4();
const _tempProjMat4 = new Mat4();
const _tempProjMat5 = new Mat4();
const _tempSet = new Set();
const _tempMeshInstances = [];
const _tempMeshInstancesSkinned = [];
/**
 * The base renderer functionality to allow implementation of specialized renderers.
 *
 * @ignore
 */ class Renderer {
    /**
     * Create a new instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used by the renderer.
     * @param {Scene} scene - The scene.
     */ constructor(graphicsDevice, scene){
        /** @type {boolean} */ this.clustersDebugRendered = false;
        /**
     * A set of visible mesh instances which need further processing before being rendered, e.g.
     * skinning or morphing. Extracted during culling.
     *
     * @type {Set<MeshInstance>}
     * @protected
     */ this.processingMeshInstances = new Set();
        /**
     * A list of all unique lights in the layer composition.
     *
     * @type {Light[]}
     */ this.lights = [];
        /**
     * A list of all unique local lights (spot & omni) in the layer composition.
     *
     * @type {Light[]}
     */ this.localLights = [];
        /**
     * A list of unique directional shadow casting lights for each enabled camera. This is generated
     * each frame during light culling.
     *
     * @type {Map<Camera, Array<Light>>}
     */ this.cameraDirShadowLights = new Map();
        /**
     * A mapping of a directional light to a camera, for which the shadow is currently valid. This
     * is cleared each frame, and updated each time a directional light shadow is rendered for a
     * camera, and allows us to manually schedule shadow passes when a new camera needs a shadow.
     *
     * @type {Map<Light, Camera>}
     */ this.dirLightShadows = new Map();
        this.blueNoise = new BlueNoise(123);
        /**
     * A gsplat director for unified splat rendering.
     *
     * @type {GSplatDirector|null}
     */ this.gsplatDirector = null;
        this.device = graphicsDevice;
        this.scene = scene;
        // TODO: allocate only when the scene has clustered lighting enabled
        this.worldClustersAllocator = new WorldClustersAllocator(graphicsDevice);
        // texture atlas managing shadow map / cookie texture atlassing for omni and spot lights
        this.lightTextureAtlas = new LightTextureAtlas(graphicsDevice);
        // shadows
        this.shadowMapCache = new ShadowMapCache();
        this.shadowRenderer = new ShadowRenderer(this, this.lightTextureAtlas);
        this._shadowRendererLocal = new ShadowRendererLocal(this, this.shadowRenderer);
        this._shadowRendererDirectional = new ShadowRendererDirectional(this, this.shadowRenderer);
        // clustered passes
        if (this.scene.clusteredLightingEnabled) {
            this._renderPassUpdateClustered = new RenderPassUpdateClustered(this.device, this, this.shadowRenderer, this._shadowRendererLocal, this.lightTextureAtlas);
        }
        // view bind group format with its uniform buffer format
        this.viewUniformFormat = null;
        this.viewBindGroupFormat = null;
        // timing
        this._skinTime = 0;
        this._morphTime = 0;
        this._cullTime = 0;
        this._shadowMapTime = 0;
        this._lightClustersTime = 0;
        this._layerCompositionUpdateTime = 0;
        // stats
        this._shadowDrawCalls = 0;
        this._skinDrawCalls = 0;
        this._instancedDrawCalls = 0;
        this._shadowMapUpdates = 0;
        this._numDrawCallsCulled = 0;
        this._camerasRendered = 0;
        this._lightClusters = 0;
        // Uniforms
        const scope = graphicsDevice.scope;
        this.boneTextureId = scope.resolve('texture_poseMap');
        this.modelMatrixId = scope.resolve('matrix_model');
        this.normalMatrixId = scope.resolve('matrix_normal');
        this.viewInvId = scope.resolve('matrix_viewInverse');
        this.viewPos = new Float32Array(3);
        this.viewPosId = scope.resolve('view_position');
        this.projId = scope.resolve('matrix_projection');
        this.projSkyboxId = scope.resolve('matrix_projectionSkybox');
        this.viewId = scope.resolve('matrix_view');
        this.viewId3 = scope.resolve('matrix_view3');
        this.viewProjId = scope.resolve('matrix_viewProjection');
        this.flipYId = scope.resolve('projectionFlipY');
        this.tbnBasis = scope.resolve('tbnBasis');
        this.nearClipId = scope.resolve('camera_near');
        this.farClipId = scope.resolve('camera_far');
        this.cameraParams = new Float32Array(4);
        this.cameraParamsId = scope.resolve('camera_params');
        this.viewIndexId = scope.resolve('view_index');
        this.viewIndexId.setValue(0);
        this.blueNoiseJitterVersion = 0;
        this.blueNoiseJitterVec = new Vec4();
        this.blueNoiseJitterData = new Float32Array(4);
        this.blueNoiseJitterId = scope.resolve('blueNoiseJitter');
        this.blueNoiseTextureId = scope.resolve('blueNoiseTex32');
        this.alphaTestId = scope.resolve('alpha_ref');
        this.opacityMapId = scope.resolve('texture_opacityMap');
        this.exposureId = scope.resolve('exposure');
        this.twoSidedLightingNegScaleFactorId = scope.resolve('twoSidedLightingNegScaleFactor');
        this.twoSidedLightingNegScaleFactorId.setValue(0);
        this.morphPositionTex = scope.resolve('morphPositionTex');
        this.morphNormalTex = scope.resolve('morphNormalTex');
        this.morphTexParams = scope.resolve('morph_tex_params');
        // a single instance of light cube
        this.lightCube = new LightCube();
        this.constantLightCube = scope.resolve('lightCube[0]');
    }
    destroy() {
        this.shadowRenderer = null;
        this._shadowRendererLocal = null;
        this._shadowRendererDirectional = null;
        this.shadowMapCache.destroy();
        this.shadowMapCache = null;
        this._renderPassUpdateClustered?.destroy();
        this._renderPassUpdateClustered = null;
        this.lightTextureAtlas.destroy();
        this.lightTextureAtlas = null;
    }
    /**
     * Set up the viewport and the scissor for camera rendering.
     *
     * @param {Camera} camera - The camera containing the viewport information.
     * @param {RenderTarget} [renderTarget] - The render target. NULL for the default one.
     */ setupViewport(camera, renderTarget) {
        const device = this.device;
        const pixelWidth = renderTarget ? renderTarget.width : device.width;
        const pixelHeight = renderTarget ? renderTarget.height : device.height;
        const rect = camera.rect;
        let x = Math.floor(rect.x * pixelWidth);
        let y = Math.floor(rect.y * pixelHeight);
        let w = Math.floor(rect.z * pixelWidth);
        let h = Math.floor(rect.w * pixelHeight);
        device.setViewport(x, y, w, h);
        // use viewport rectangle by default. Use scissor rectangle when required.
        if (camera._scissorRectClear) {
            const scissorRect = camera.scissorRect;
            x = Math.floor(scissorRect.x * pixelWidth);
            y = Math.floor(scissorRect.y * pixelHeight);
            w = Math.floor(scissorRect.z * pixelWidth);
            h = Math.floor(scissorRect.w * pixelHeight);
        }
        device.setScissor(x, y, w, h);
    }
    setCameraUniforms(camera, target) {
        // flipping proj matrix
        const flipY = target?.flipY;
        let viewList = null;
        if (camera.xr && camera.xr.session) {
            const transform = camera._node?.parent?.getWorldTransform() || null;
            const views = camera.xr.views;
            viewList = views.list;
            for(let v = 0; v < viewList.length; v++){
                const view = viewList[v];
                view.updateTransforms(transform);
                camera.frustum.setFromMat4(view.projViewOffMat);
            }
        } else {
            // Projection Matrix
            let projMat = camera.projectionMatrix;
            if (camera.calculateProjection) {
                camera.calculateProjection(projMat, VIEW_CENTER);
            }
            let projMatSkybox = camera.getProjectionMatrixSkybox();
            // flip projection matrices
            if (flipY) {
                projMat = _tempProjMat0.mul2(_flipYMat, projMat);
                projMatSkybox = _tempProjMat1.mul2(_flipYMat, projMatSkybox);
            }
            // update depth range of projection matrices (-1..1 to 0..1)
            if (this.device.isWebGPU) {
                projMat = _tempProjMat2.mul2(_fixProjRangeMat, projMat);
                projMatSkybox = _tempProjMat3.mul2(_fixProjRangeMat, projMatSkybox);
            }
            // camera jitter
            const { jitter } = camera;
            let jitterX = 0;
            let jitterY = 0;
            if (jitter > 0) {
                // render target size
                const targetWidth = target ? target.width : this.device.width;
                const targetHeight = target ? target.height : this.device.height;
                // offsets
                const offset = _haltonSequence[this.device.renderVersion % _haltonSequence.length];
                jitterX = jitter * (offset.x * 2 - 1) / targetWidth;
                jitterY = jitter * (offset.y * 2 - 1) / targetHeight;
                // apply offset to projection matrix
                projMat = _tempProjMat4.copy(projMat);
                projMat.data[8] = jitterX;
                projMat.data[9] = jitterY;
                // apply offset to skybox projection matrix
                projMatSkybox = _tempProjMat5.copy(projMatSkybox);
                projMatSkybox.data[8] = jitterX;
                projMatSkybox.data[9] = jitterY;
                // blue noise vec4 - only use when jitter is enabled
                if (this.blueNoiseJitterVersion !== this.device.renderVersion) {
                    this.blueNoiseJitterVersion = this.device.renderVersion;
                    this.blueNoise.vec4(this.blueNoiseJitterVec);
                }
            }
            const jitterVec = jitter > 0 ? this.blueNoiseJitterVec : Vec4.ZERO;
            this.blueNoiseJitterData[0] = jitterVec.x;
            this.blueNoiseJitterData[1] = jitterVec.y;
            this.blueNoiseJitterData[2] = jitterVec.z;
            this.blueNoiseJitterData[3] = jitterVec.w;
            this.blueNoiseJitterId.setValue(this.blueNoiseJitterData);
            this.projId.setValue(projMat.data);
            this.projSkyboxId.setValue(projMatSkybox.data);
            // ViewInverse Matrix
            if (camera.calculateTransform) {
                camera.calculateTransform(viewInvMat, VIEW_CENTER);
            } else {
                const pos = camera._node.getPosition();
                const rot = camera._node.getRotation();
                viewInvMat.setTRS(pos, rot, Vec3.ONE);
            }
            this.viewInvId.setValue(viewInvMat.data);
            // View Matrix
            viewMat.copy(viewInvMat).invert();
            this.viewId.setValue(viewMat.data);
            // View 3x3
            viewMat3.setFromMat4(viewMat);
            this.viewId3.setValue(viewMat3.data);
            // ViewProjection Matrix
            viewProjMat.mul2(projMat, viewMat);
            this.viewProjId.setValue(viewProjMat.data);
            // store matrices needed by TAA
            camera._storeShaderMatrices(viewProjMat, jitterX, jitterY, this.device.renderVersion);
            this.flipYId.setValue(flipY ? -1 : 1);
            // View Position (world space)
            this.dispatchViewPos(camera._node.getPosition());
            camera.frustum.setFromMat4(viewProjMat);
        }
        this.tbnBasis.setValue(flipY ? -1 : 1);
        // Near and far clip values
        const n = camera._nearClip;
        const f = camera._farClip;
        this.nearClipId.setValue(n);
        this.farClipId.setValue(f);
        // camera params
        this.cameraParams[0] = 1 / f;
        this.cameraParams[1] = f;
        this.cameraParams[2] = n;
        this.cameraParams[3] = camera.projection === PROJECTION_ORTHOGRAPHIC ? 1 : 0;
        this.cameraParamsId.setValue(this.cameraParams);
        // exposure
        this.exposureId.setValue(this.scene.physicalUnits ? camera.getExposure() : this.scene.exposure);
        return viewList;
    }
    /**
     * Clears the active render target. If the viewport is already set up, only its area is cleared.
     *
     * @param {Camera} camera - The camera supplying the value to clear to.
     * @param {boolean} [clearColor] - True if the color buffer should be cleared. Uses the value
     * from the camera if not supplied.
     * @param {boolean} [clearDepth] - True if the depth buffer should be cleared. Uses the value
     * from the camera if not supplied.
     * @param {boolean} [clearStencil] - True if the stencil buffer should be cleared. Uses the
     * value from the camera if not supplied.
     */ clear(camera, clearColor, clearDepth, clearStencil) {
        const flags = (clearColor ?? camera._clearColorBuffer ? CLEARFLAG_COLOR : 0) | (clearDepth ?? camera._clearDepthBuffer ? CLEARFLAG_DEPTH : 0) | (clearStencil ?? camera._clearStencilBuffer ? CLEARFLAG_STENCIL : 0);
        if (flags) {
            const device = this.device;
            DebugGraphics.pushGpuMarker(device, 'CLEAR');
            device.clear({
                color: [
                    camera._clearColor.r,
                    camera._clearColor.g,
                    camera._clearColor.b,
                    camera._clearColor.a
                ],
                depth: camera._clearDepth,
                stencil: camera._clearStencil,
                flags: flags
            });
            DebugGraphics.popGpuMarker(device);
        }
    }
    // make sure colorWrite is set to true to all channels, if you want to fully clear the target
    // TODO: this function is only used from outside of forward renderer, and should be deprecated
    // when the functionality moves to the render passes. Note that Editor uses it as well.
    setCamera(camera, target, clear, renderAction = null) {
        this.setCameraUniforms(camera, target);
        this.clearView(camera, target, clear, false);
    }
    // TODO: this is currently used by the lightmapper and the Editor,
    // and will be removed when those call are removed.
    clearView(camera, target, clear, forceWrite) {
        const device = this.device;
        DebugGraphics.pushGpuMarker(device, 'CLEAR-VIEW');
        device.setRenderTarget(target);
        device.updateBegin();
        if (forceWrite) {
            device.setColorWrite(true, true, true, true);
            device.setDepthWrite(true);
        }
        this.setupViewport(camera, target);
        if (clear) {
            // use camera clear options if any
            const options = camera._clearOptions;
            device.clear(options ? options : {
                color: [
                    camera._clearColor.r,
                    camera._clearColor.g,
                    camera._clearColor.b,
                    camera._clearColor.a
                ],
                depth: camera._clearDepth,
                flags: (camera._clearColorBuffer ? CLEARFLAG_COLOR : 0) | (camera._clearDepthBuffer ? CLEARFLAG_DEPTH : 0) | (camera._clearStencilBuffer ? CLEARFLAG_STENCIL : 0),
                stencil: camera._clearStencil
            });
        }
        DebugGraphics.popGpuMarker(device);
    }
    setupCullMode(cullFaces, flipFactor, drawCall) {
        const material = drawCall.material;
        let mode = CULLFACE_NONE;
        if (cullFaces) {
            let flipFaces = 1;
            if (material.cull === CULLFACE_FRONT || material.cull === CULLFACE_BACK) {
                flipFaces = flipFactor * drawCall.flipFacesFactor * drawCall.node.worldScaleSign;
            }
            if (flipFaces < 0) {
                mode = material.cull === CULLFACE_FRONT ? CULLFACE_BACK : CULLFACE_FRONT;
            } else {
                mode = material.cull;
            }
        }
        this.device.setCullMode(mode);
        if (mode === CULLFACE_NONE && material.cull === CULLFACE_NONE) {
            this.twoSidedLightingNegScaleFactorId.setValue(drawCall.node.worldScaleSign);
        }
    }
    updateCameraFrustum(camera) {
        if (camera.xr && camera.xr.views.list.length) {
            // calculate frustum based on XR view
            const view = camera.xr.views.list[0];
            viewProjMat.mul2(view.projMat, view.viewOffMat);
            camera.frustum.setFromMat4(viewProjMat);
            return;
        }
        const projMat = camera.projectionMatrix;
        if (camera.calculateProjection) {
            camera.calculateProjection(projMat, VIEW_CENTER);
        }
        if (camera.calculateTransform) {
            camera.calculateTransform(viewInvMat, VIEW_CENTER);
        } else {
            const pos = camera._node.getPosition();
            const rot = camera._node.getRotation();
            viewInvMat.setTRS(pos, rot, Vec3.ONE);
            this.viewInvId.setValue(viewInvMat.data);
        }
        viewMat.copy(viewInvMat).invert();
        viewProjMat.mul2(projMat, viewMat);
        camera.frustum.setFromMat4(viewProjMat);
    }
    setBaseConstants(device, material) {
        // Cull mode
        device.setCullMode(material.cull);
        // Alpha test
        if (material.opacityMap) {
            this.opacityMapId.setValue(material.opacityMap);
        }
        if (material.opacityMap || material.alphaTest > 0) {
            this.alphaTestId.setValue(material.alphaTest);
        }
    }
    updateCpuSkinMatrices(drawCalls) {
        _skinUpdateIndex++;
        const drawCallsCount = drawCalls.length;
        if (drawCallsCount === 0) return;
        const skinTime = now();
        for(let i = 0; i < drawCallsCount; i++){
            const si = drawCalls[i].skinInstance;
            if (si) {
                si.updateMatrices(drawCalls[i].node, _skinUpdateIndex);
                si._dirty = true;
            }
        }
        this._skinTime += now() - skinTime;
    }
    /**
     * Update skin matrices ahead of rendering.
     *
     * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances containing skinInstance.
     * @ignore
     */ updateGpuSkinMatrices(drawCalls) {
        const skinTime = now();
        for (const drawCall of drawCalls){
            const skin = drawCall.skinInstance;
            if (skin && skin._dirty) {
                skin.updateMatrixPalette(drawCall.node, _skinUpdateIndex);
                skin._dirty = false;
            }
        }
        this._skinTime += now() - skinTime;
    }
    /**
     * Update morphing ahead of rendering.
     *
     * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances containing morphInstance.
     * @ignore
     */ updateMorphing(drawCalls) {
        const morphTime = now();
        for (const drawCall of drawCalls){
            const morphInst = drawCall.morphInstance;
            if (morphInst && morphInst._dirty) {
                morphInst.update();
            }
        }
        this._morphTime += now() - morphTime;
    }
    /**
     * Update gsplats ahead of rendering.
     *
     * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances containing gsplatInstances.
     * @ignore
     */ updateGSplats(drawCalls) {
        for (const drawCall of drawCalls){
            drawCall.gsplatInstance?.update();
        }
    }
    /**
     * Update draw calls ahead of rendering.
     *
     * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances requiring updates.
     * @ignore
     */ gpuUpdate(drawCalls) {
        // Note that drawCalls can be either a Set or an Array and contains mesh instances
        // that are visible in this frame
        this.updateGpuSkinMatrices(drawCalls);
        this.updateMorphing(drawCalls);
        this.updateGSplats(drawCalls);
    }
    setVertexBuffers(device, mesh) {
        // main vertex buffer
        device.setVertexBuffer(mesh.vertexBuffer);
    }
    setMorphing(device, morphInstance) {
        if (morphInstance) {
            morphInstance.prepareRendering(device);
            // vertex buffer with vertex ids
            device.setVertexBuffer(morphInstance.morph.vertexBufferIds);
            // textures
            this.morphPositionTex.setValue(morphInstance.texturePositions);
            this.morphNormalTex.setValue(morphInstance.textureNormals);
            // texture params
            this.morphTexParams.setValue(morphInstance._textureParams);
        }
    }
    setSkinning(device, meshInstance) {
        const skinInstance = meshInstance.skinInstance;
        if (skinInstance) {
            this._skinDrawCalls++;
            const boneTexture = skinInstance.boneTexture;
            this.boneTextureId.setValue(boneTexture);
        }
    }
    // sets Vec3 camera position uniform
    dispatchViewPos(position) {
        const vp = this.viewPos; // note that this reuses an array
        vp[0] = position.x;
        vp[1] = position.y;
        vp[2] = position.z;
        this.viewPosId.setValue(vp);
    }
    initViewBindGroupFormat(isClustered) {
        if (this.device.supportsUniformBuffers && !this.viewUniformFormat) {
            // format of the view uniform buffer
            const uniforms = [
                new UniformFormat('matrix_view', UNIFORMTYPE_MAT4),
                new UniformFormat('matrix_viewInverse', UNIFORMTYPE_MAT4),
                new UniformFormat('matrix_projection', UNIFORMTYPE_MAT4),
                new UniformFormat('matrix_projectionSkybox', UNIFORMTYPE_MAT4),
                new UniformFormat('matrix_viewProjection', UNIFORMTYPE_MAT4),
                new UniformFormat('matrix_view3', UNIFORMTYPE_MAT3),
                new UniformFormat('cubeMapRotationMatrix', UNIFORMTYPE_MAT3),
                new UniformFormat('view_position', UNIFORMTYPE_VEC3),
                new UniformFormat('skyboxIntensity', UNIFORMTYPE_FLOAT),
                new UniformFormat('exposure', UNIFORMTYPE_FLOAT),
                new UniformFormat('textureBias', UNIFORMTYPE_FLOAT),
                new UniformFormat('view_index', UNIFORMTYPE_FLOAT)
            ];
            if (isClustered) {
                uniforms.push(...[
                    new UniformFormat('clusterCellsCountByBoundsSize', UNIFORMTYPE_VEC3),
                    new UniformFormat('clusterTextureSize', UNIFORMTYPE_VEC3),
                    new UniformFormat('clusterBoundsMin', UNIFORMTYPE_VEC3),
                    new UniformFormat('clusterBoundsDelta', UNIFORMTYPE_VEC3),
                    new UniformFormat('clusterCellsDot', UNIFORMTYPE_VEC3),
                    new UniformFormat('clusterCellsMax', UNIFORMTYPE_VEC3),
                    new UniformFormat('shadowAtlasParams', UNIFORMTYPE_VEC2),
                    new UniformFormat('clusterMaxCells', UNIFORMTYPE_INT),
                    new UniformFormat('clusterSkip', UNIFORMTYPE_FLOAT)
                ]);
            }
            this.viewUniformFormat = new UniformBufferFormat(this.device, uniforms);
            // format of the view bind group - contains single uniform buffer, and some textures
            const formats = [
                // uniform buffer needs to be first, as the shader processor assumes slot 0 for it
                new BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)
            ];
            // disable view level textures, as they consume texture slots. They get automatically added to mesh bind group
            // for the meshes that uses them
            // if (isClustered) {
            //     formats.push(...[
            //         new BindTextureFormat('clusterWorldTexture', SHADERSTAGE_FRAGMENT, TEXTUREDIMENSION_2D, SAMPLETYPE_UNFILTERABLE_FLOAT)
            //     ]);
            // }
            this.viewBindGroupFormat = new BindGroupFormat(this.device, formats);
        }
    }
    /**
     * Set up uniforms for an XR view.
     */ setupViewUniforms(view, index) {
        // any view uniforms need to be part of the view uniform buffer, see initViewBindGroupFormat
        this.projId.setValue(view.projMat.data);
        this.projSkyboxId.setValue(view.projMat.data);
        this.viewId.setValue(view.viewOffMat.data);
        this.viewInvId.setValue(view.viewInvOffMat.data);
        this.viewId3.setValue(view.viewMat3.data);
        this.viewProjId.setValue(view.projViewOffMat.data);
        this.viewPosId.setValue(view.positionData);
        this.viewIndexId.setValue(index);
    }
    setupViewUniformBuffers(viewBindGroups, viewUniformFormat, viewBindGroupFormat, viewList) {
        Debug.assert(Array.isArray(viewBindGroups), 'viewBindGroups must be an array');
        const { device } = this;
        // make sure we have bind group for each view
        const viewCount = viewList?.length ?? 1;
        while(viewBindGroups.length < viewCount){
            const ub = new UniformBuffer(device, viewUniformFormat, false);
            const bg = new BindGroup(device, viewBindGroupFormat, ub);
            DebugHelper.setName(bg, `ViewBindGroup_${bg.id}`);
            viewBindGroups.push(bg);
        }
        if (viewList) {
            for(let i = 0; i < viewCount; i++){
                // set up view uniforms
                const view = viewList[i];
                this.setupViewUniforms(view, i);
                // update view bind group / uniforms
                const viewBindGroup = viewBindGroups[i];
                viewBindGroup.defaultUniformBuffer.update();
                viewBindGroup.update();
            }
        } else {
            const viewBindGroup = viewBindGroups[0];
            viewBindGroup.defaultUniformBuffer.update();
            viewBindGroup.update();
        }
        // bind it when a single view is used, otherwise this is handled per view inside rendering loop
        if (!viewList) {
            device.setBindGroup(BINDGROUP_VIEW, viewBindGroups[0]);
        }
    }
    setupMeshUniformBuffers(shaderInstance) {
        const device = this.device;
        if (device.supportsUniformBuffers) {
            // update mesh bind group / uniform buffer
            const meshBindGroup = shaderInstance.getBindGroup(device);
            meshBindGroup.update();
            device.setBindGroup(BINDGROUP_MESH, meshBindGroup);
            const meshUniformBuffer = shaderInstance.getUniformBuffer(device);
            meshUniformBuffer.update(_dynamicBindGroup);
            device.setBindGroup(BINDGROUP_MESH_UB, _dynamicBindGroup.bindGroup, _dynamicBindGroup.offsets);
        }
    }
    setMeshInstanceMatrices(meshInstance, setNormalMatrix = false) {
        const modelMatrix = meshInstance.node.worldTransform;
        this.modelMatrixId.setValue(modelMatrix.data);
        if (setNormalMatrix) {
            this.normalMatrixId.setValue(meshInstance.node.normalMatrix.data);
        }
    }
    /**
     * @param {Camera} camera - The camera used for culling.
     * @param {MeshInstance[]} drawCalls - Draw calls to cull.
     * @param {CulledInstances} culledInstances - Stores culled instances.
     */ cull(camera, drawCalls, culledInstances) {
        const cullTime = now();
        const opaque = culledInstances.opaque;
        opaque.length = 0;
        const transparent = culledInstances.transparent;
        transparent.length = 0;
        const doCull = camera.frustumCulling;
        const count = drawCalls.length;
        for(let i = 0; i < count; i++){
            const drawCall = drawCalls[i];
            if (drawCall.visible) {
                const visible = !doCull || !drawCall.cull || drawCall._isVisible(camera);
                if (visible) {
                    drawCall.visibleThisFrame = true;
                    // sort mesh instance into the right bucket based on its transparency
                    const bucket = drawCall.transparent ? transparent : opaque;
                    bucket.push(drawCall);
                    if (drawCall.skinInstance || drawCall.morphInstance || drawCall.gsplatInstance) {
                        this.processingMeshInstances.add(drawCall);
                        // register visible cameras
                        if (drawCall.gsplatInstance) {
                            drawCall.gsplatInstance.cameras.push(camera);
                        }
                    }
                }
            }
        }
        this._cullTime += now() - cullTime;
        this._numDrawCallsCulled += doCull ? count : 0;
    }
    collectLights(comp) {
        // build a list and of all unique lights from all layers
        this.lights.length = 0;
        this.localLights.length = 0;
        // stats
        const stats = this.scene._stats;
        stats.dynamicLights = 0;
        stats.bakedLights = 0;
        const count = comp.layerList.length;
        for(let i = 0; i < count; i++){
            const layer = comp.layerList[i];
            // layer can be in the list two times (opaque, transp), process it only one time
            if (!_tempLayerSet.has(layer)) {
                _tempLayerSet.add(layer);
                const lights = layer._lights;
                for(let j = 0; j < lights.length; j++){
                    const light = lights[j];
                    // add new light
                    if (!_tempLightSet.has(light)) {
                        _tempLightSet.add(light);
                        this.lights.push(light);
                        if (light._type !== LIGHTTYPE_DIRECTIONAL) {
                            this.localLights.push(light);
                        }
                        // if affects dynamic or baked objects in real-time
                        if (light.mask & MASK_AFFECT_DYNAMIC || light.mask & MASK_AFFECT_LIGHTMAPPED) {
                            stats.dynamicLights++;
                        }
                        // bake lights
                        if (light.mask & MASK_BAKE) {
                            stats.bakedLights++;
                        }
                    }
                }
            }
        }
        stats.lights = this.lights.length;
        _tempLightSet.clear();
        _tempLayerSet.clear();
    }
    cullLights(camera, lights) {
        const clusteredLightingEnabled = this.scene.clusteredLightingEnabled;
        const physicalUnits = this.scene.physicalUnits;
        for(let i = 0; i < lights.length; i++){
            const light = lights[i];
            if (light.enabled) {
                // directional lights are marked visible at the start of the frame
                if (light._type !== LIGHTTYPE_DIRECTIONAL) {
                    light.getBoundingSphere(tempSphere);
                    if (camera.frustum.containsSphere(tempSphere)) {
                        light.visibleThisFrame = true;
                        light.usePhysicalUnits = physicalUnits;
                        // maximum screen area taken by the light
                        const screenSize = camera.getScreenSize(tempSphere);
                        light.maxScreenSize = Math.max(light.maxScreenSize, screenSize);
                    } else {
                        // if shadow casting light does not have shadow map allocated, mark it visible to allocate shadow map
                        // Note: This won't be needed when clustered shadows are used, but at the moment even culled out lights
                        // are used for rendering, and need shadow map to be allocated
                        // TODO: delete this code when clusteredLightingEnabled is being removed and is on by default.
                        if (!clusteredLightingEnabled) {
                            if (light.castShadows && !light.shadowMap) {
                                light.visibleThisFrame = true;
                            }
                        }
                    }
                } else {
                    light.usePhysicalUnits = this.scene.physicalUnits;
                }
            }
        }
    }
    /**
     * Shadow map culling for directional and visible local lights visible meshInstances are
     * collected into light._renderData, and are marked as visible for directional lights also
     * shadow camera matrix is set up.
     *
     * @param {LayerComposition} comp - The layer composition.
     */ cullShadowmaps(comp) {
        const isClustered = this.scene.clusteredLightingEnabled;
        // shadow casters culling for local (point and spot) lights
        for(let i = 0; i < this.localLights.length; i++){
            const light = this.localLights[i];
            if (light._type !== LIGHTTYPE_DIRECTIONAL) {
                if (isClustered) {
                    // if atlas slot is reassigned, make sure to update the shadow map, including the culling
                    if (light.atlasSlotUpdated && light.shadowUpdateMode === SHADOWUPDATE_NONE) {
                        light.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
                    }
                } else {
                    // force rendering shadow at least once to allocate the shadow map needed by the shaders
                    if (light.shadowUpdateMode === SHADOWUPDATE_NONE && light.castShadows) {
                        if (!light.getRenderData(null, 0).shadowCamera.renderTarget) {
                            light.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
                        }
                    }
                }
                if (light.visibleThisFrame && light.castShadows && light.shadowUpdateMode !== SHADOWUPDATE_NONE) {
                    this._shadowRendererLocal.cull(light, comp);
                }
            }
        }
        // shadow casters culling for directional lights - start with none and collect lights for cameras
        this.cameraDirShadowLights.clear();
        const cameras = comp.cameras;
        for(let i = 0; i < cameras.length; i++){
            const cameraComponent = cameras[i];
            if (cameraComponent.enabled) {
                const camera = cameraComponent.camera;
                // get directional lights from all layers of the camera
                let lightList;
                const cameraLayers = camera.layers;
                for(let l = 0; l < cameraLayers.length; l++){
                    const cameraLayer = comp.getLayerById(cameraLayers[l]);
                    if (cameraLayer) {
                        const layerDirLights = cameraLayer.splitLights[LIGHTTYPE_DIRECTIONAL];
                        for(let j = 0; j < layerDirLights.length; j++){
                            const light = layerDirLights[j];
                            // unique shadow casting lights
                            if (light.castShadows && !_tempSet.has(light)) {
                                _tempSet.add(light);
                                lightList = lightList ?? [];
                                lightList.push(light);
                                // frustum culling for the directional shadow when rendering the camera
                                this._shadowRendererDirectional.cull(light, comp, camera);
                            }
                        }
                    }
                }
                if (lightList) {
                    this.cameraDirShadowLights.set(camera, lightList);
                }
                _tempSet.clear();
            }
        }
    }
    /**
     * visibility culling of lights, meshInstances, shadows casters. Also applies
     * `meshInstance.visible`.
     *
     * @param {LayerComposition} comp - The layer composition.
     */ cullComposition(comp) {
        const cullTime = now();
        const { scene } = this;
        this.processingMeshInstances.clear();
        // for all cameras
        const numCameras = comp.cameras.length;
        this._camerasRendered += numCameras;
        for(let i = 0; i < numCameras; i++){
            const camera = comp.cameras[i];
            // event before the camera is culling
            scene?.fire(EVENT_PRECULL, camera);
            // update camera and frustum
            const renderTarget = camera.renderTarget;
            camera.frameUpdate(renderTarget);
            this.updateCameraFrustum(camera.camera);
            // for all of its enabled layers
            const layerIds = camera.layers;
            for(let j = 0; j < layerIds.length; j++){
                const layer = comp.getLayerById(layerIds[j]);
                if (layer && layer.enabled) {
                    // cull each layer's non-directional lights once with each camera
                    // lights aren't collected anywhere, but marked as visible
                    this.cullLights(camera.camera, layer._lights);
                    // cull mesh instances
                    const culledInstances = layer.getCulledInstances(camera.camera);
                    this.cull(camera.camera, layer.meshInstances, culledInstances);
                }
            }
            // event after the camera is done with culling
            scene?.fire(EVENT_POSTCULL, camera);
        }
        // update shadow / cookie atlas allocation for the visible lights. Update it after the ligthts were culled,
        // but before shadow maps were culling, as it might force some 'update once' shadows to cull.
        if (scene.clusteredLightingEnabled) {
            this.updateLightTextureAtlas();
        }
        // cull shadow casters for all lights
        this.cullShadowmaps(comp);
        // event after the engine has finished culling all cameras
        scene?.fire(EVENT_CULL_END);
        this._cullTime += now() - cullTime;
    }
    /**
     * @param {MeshInstance[]} drawCalls - Mesh instances.
     * @param {boolean} onlyLitShaders - Limits the update to shaders affected by lighting.
     */ updateShaders(drawCalls, onlyLitShaders) {
        const count = drawCalls.length;
        for(let i = 0; i < count; i++){
            const mat = drawCalls[i].material;
            if (mat) {
                // material not processed yet
                if (!_tempSet.has(mat)) {
                    _tempSet.add(mat);
                    // skip this for materials not using variants
                    if (mat.getShaderVariant !== Material.prototype.getShaderVariant) {
                        if (onlyLitShaders) {
                            // skip materials not using lighting
                            if (!mat.useLighting || mat.emitter && !mat.emitter.lighting) {
                                continue;
                            }
                        }
                        // clear shader variants on the material and also on mesh instances that use it
                        mat.clearVariants();
                    }
                }
            }
        }
        // keep temp set empty
        _tempSet.clear();
    }
    updateFrameUniforms() {
        // blue noise texture
        this.blueNoiseTextureId.setValue(getBlueNoiseTexture(this.device));
    }
    /**
     * @param {LayerComposition} comp - The layer composition to update.
     */ beginFrame(comp) {
        const scene = this.scene;
        const updateShaders = scene.updateShaders || this.device._shadersDirty;
        let totalMeshInstances = 0;
        const layers = comp.layerList;
        const layerCount = layers.length;
        for(let i = 0; i < layerCount; i++){
            const layer = layers[i];
            const meshInstances = layer.meshInstances;
            const count = meshInstances.length;
            totalMeshInstances += count;
            for(let j = 0; j < count; j++){
                const meshInst = meshInstances[j];
                // clear visibility
                meshInst.visibleThisFrame = false;
                // collect all mesh instances if we need to update their shaders. Note that there could
                // be duplicates, which is not a problem for the shader updates, so we do not filter them out.
                if (updateShaders) {
                    _tempMeshInstances.push(meshInst);
                }
                // collect skinned mesh instances
                if (meshInst.skinInstance) {
                    _tempMeshInstancesSkinned.push(meshInst);
                }
            }
        }
        scene._stats.meshInstances = totalMeshInstances;
        // update shaders if needed
        if (updateShaders) {
            const onlyLitShaders = !scene.updateShaders || !this.device._shadersDirty;
            this.updateShaders(_tempMeshInstances, onlyLitShaders);
            scene.updateShaders = false;
            this.device._shadersDirty = false;
            scene._shaderVersion++;
        }
        this.updateFrameUniforms();
        // Update all skin matrices to properly cull skinned objects (but don't update rendering data yet)
        this.updateCpuSkinMatrices(_tempMeshInstancesSkinned);
        // clear light arrays
        _tempMeshInstances.length = 0;
        _tempMeshInstancesSkinned.length = 0;
        // clear light visibility
        const lights = this.lights;
        const lightCount = lights.length;
        for(let i = 0; i < lightCount; i++){
            lights[i].beginFrame();
        }
    }
    updateLightTextureAtlas() {
        this.lightTextureAtlas.update(this.localLights, this.scene.lighting);
    }
    /**
     * Updates the layer composition for rendering.
     *
     * @param {LayerComposition} comp - The layer composition to update.
     */ updateLayerComposition(comp) {
        const layerCompositionUpdateTime = now();
        const len = comp.layerList.length;
        const scene = this.scene;
        const shaderVersion = scene._shaderVersion;
        for(let i = 0; i < len; i++){
            const layer = comp.layerList[i];
            layer._shaderVersion = shaderVersion;
            layer._skipRenderCounter = 0;
            layer._forwardDrawCalls = 0;
            layer._shadowDrawCalls = 0;
            layer._renderTime = 0;
        }
        // update composition
        comp._update();
        this._layerCompositionUpdateTime += now() - layerCompositionUpdateTime;
    }
    frameUpdate() {
        this.clustersDebugRendered = false;
        this.initViewBindGroupFormat(this.scene.clusteredLightingEnabled);
        // no valid shadows at the start of the frame
        this.dirLightShadows.clear();
    }
}

/**
 * @import { BindGroup } from '../../platform/graphics/bind-group.js'
 * @import { Layer } from '../layer.js'
 * @import { RenderTarget } from '../../platform/graphics/render-target.js'
 */ /**
 * Class representing an entry in the final order of rendering of cameras and layers in the engine
 * this is populated at runtime based on LayerComposition
 *
 * @ignore
 */ class RenderAction {
    constructor(){
        // {CameraComponent|null}
        this.camera = null;
        // the layer
        /** @type {Layer|null} */ this.layer = null;
        // true if this uses transparent sublayer, opaque otherwise
        this.transparent = false;
        /**
         * Render target this render action renders to.
         *
         * @type {RenderTarget|null}
         */ this.renderTarget = null;
        // light clusters (type WorldClusters)
        this.lightClusters = null;
        // clear flags
        this.clearColor = false;
        this.clearDepth = false;
        this.clearStencil = false;
        // true if this render action should trigger postprocessing callback for the camera
        this.triggerPostprocess = false;
        // true if this is first render action using this camera
        this.firstCameraUse = false;
        // true if this is the last render action using this camera
        this.lastCameraUse = false;
        // an array of view bind groups (the number of these corresponds to the number of views when XR is used)
        /** @type {BindGroup[]} */ this.viewBindGroups = [];
        // true if the camera should render using render passes it specifies
        this.useCameraPasses = false;
    }
    // releases GPU resources
    destroy() {
        this.viewBindGroups.forEach((bg)=>{
            bg.defaultUniformBuffer.destroy();
            bg.destroy();
        });
        this.viewBindGroups.length = 0;
    }
    setupClears(camera, layer) {
        this.clearColor = camera?.clearColorBuffer || layer.clearColorBuffer;
        this.clearDepth = camera?.clearDepthBuffer || layer.clearDepthBuffer;
        this.clearStencil = camera?.clearStencilBuffer || layer.clearStencilBuffer;
    }
}

/**
 * @import { CameraComponent } from '../../framework/components/camera/component.js'
 * @import { LayerComposition } from '../composition/layer-composition.js'
 * @import { Layer } from '../layer.js'
 * @import { Renderer } from './renderer.js'
 * @import { Scene } from '../scene.js'
 */ /**
 * A render pass used render a set of layers using a camera.
 *
 * @ignore
 */ class RenderPassForward extends RenderPass {
    constructor(device, layerComposition, scene, renderer){
        super(device), /**
     * @type {RenderAction[]}
     */ this.renderActions = [], /**
     * If true, do not clear the depth buffer before rendering, as it was already primed by a depth
     * pre-pass.
     *
     * @type {boolean}
     */ this.noDepthClear = false;
        this.layerComposition = layerComposition;
        this.scene = scene;
        this.renderer = renderer;
    }
    get rendersAnything() {
        return this.renderActions.length > 0;
    }
    addRenderAction(renderAction) {
        this.renderActions.push(renderAction);
    }
    /**
     * Adds a layer to be rendered by this render pass.
     *
     * @param {CameraComponent} cameraComponent - The camera component that is used to render the
     * layers.
     * @param {Layer} layer - The layer to be added.
     * @param {boolean} transparent - True if the layer is transparent.
     * @param {boolean} autoClears - True if the render target should be cleared based on the camera
     * and layer clear flags. Defaults to true.
     */ addLayer(cameraComponent, layer, transparent, autoClears = true) {
        Debug.assert(cameraComponent);
        Debug.assert(this.renderTarget !== undefined, 'Render pass needs to be initialized before adding layers');
        Debug.assert(cameraComponent.camera.layersSet.has(layer.id), `Camera ${cameraComponent.entity.name} does not render layer ${layer.name}.`);
        const ra = new RenderAction();
        ra.renderTarget = this.renderTarget;
        ra.camera = cameraComponent;
        ra.layer = layer;
        ra.transparent = transparent;
        // camera / layer clear flags
        if (autoClears) {
            const firstRa = this.renderActions.length === 0;
            ra.setupClears(firstRa ? cameraComponent : undefined, layer);
        }
        this.addRenderAction(ra);
    }
    /**
     * Adds layers to be rendered by this render pass, starting from the given index of the layer
     * in the layer composition, till the end of the layer list, or till the last layer with the
     * given id and transparency is reached (inclusive). Note that only layers that are rendered by
     * the specified camera are added.
     *
     * @param {LayerComposition} composition - The layer composition containing the layers to be
     * added, typically the scene layer composition.
     * @param {CameraComponent} cameraComponent - The camera component that is used to render the
     * layers.
     * @param {number} startIndex - The index of the first layer to be considered for adding.
     * @param {boolean} firstLayerClears - True if the first layer added should clear the render
     * target.
     * @param {number} [lastLayerId] - The id of the last layer to be added. If not specified, all
     * layers till the end of the layer list are added.
     * @param {boolean} [lastLayerIsTransparent] - True if the last layer to be added is transparent.
     * Defaults to true.
     * @returns {number} Returns the index of last layer added.
     */ addLayers(composition, cameraComponent, startIndex, firstLayerClears, lastLayerId, lastLayerIsTransparent = true) {
        const { layerList, subLayerList } = composition;
        let clearRenderTarget = firstLayerClears;
        let index = startIndex;
        while(index < layerList.length){
            const layer = layerList[index];
            const isTransparent = subLayerList[index];
            const renderedByCamera = cameraComponent.camera.layersSet.has(layer.id);
            // add it for rendering
            if (renderedByCamera) {
                this.addLayer(cameraComponent, layer, isTransparent, clearRenderTarget);
                clearRenderTarget = false;
            }
            index++;
            // stop at last requested layer
            if (layer.id === lastLayerId && isTransparent === lastLayerIsTransparent) {
                break;
            }
        }
        return index;
    }
    updateDirectionalShadows() {
        // add directional shadow passes if needed for the cameras used in this render pass
        const { renderer, renderActions } = this;
        for(let i = 0; i < renderActions.length; i++){
            const renderAction = renderActions[i];
            const cameraComp = renderAction.camera;
            const camera = cameraComp.camera;
            // if this camera uses directional shadow lights
            const shadowDirLights = this.renderer.cameraDirShadowLights.get(camera);
            if (shadowDirLights) {
                for(let l = 0; l < shadowDirLights.length; l++){
                    const light = shadowDirLights[l];
                    // the shadow map is not already rendered for this light
                    if (renderer.dirLightShadows.get(light) !== camera) {
                        renderer.dirLightShadows.set(light, camera);
                        // render the shadow before this render pass
                        const shadowPass = renderer._shadowRendererDirectional.getLightRenderPass(light, camera);
                        if (shadowPass) {
                            this.beforePasses.push(shadowPass);
                        }
                    }
                }
            }
        }
    }
    updateClears() {
        // based on the first render action
        const renderAction = this.renderActions[0];
        if (renderAction) {
            // set up clear params if the camera covers the full viewport
            const cameraComponent = renderAction.camera;
            const camera = cameraComponent.camera;
            const fullSizeClearRect = camera.fullSizeClearRect;
            this.setClearColor(fullSizeClearRect && renderAction.clearColor ? camera.clearColor : undefined);
            this.setClearDepth(fullSizeClearRect && renderAction.clearDepth && !this.noDepthClear ? camera.clearDepth : undefined);
            this.setClearStencil(fullSizeClearRect && renderAction.clearStencil ? camera.clearStencil : undefined);
        }
    }
    frameUpdate() {
        super.frameUpdate();
        this.updateDirectionalShadows();
        this.updateClears();
    }
    before() {
        const { renderActions } = this;
        // onPreRender events
        for(let i = 0; i < renderActions.length; i++){
            const ra = renderActions[i];
            if (ra.firstCameraUse) {
                this.scene.fire(EVENT_PRERENDER, ra.camera);
            }
        }
    }
    execute() {
        const { layerComposition, renderActions } = this;
        for(let i = 0; i < renderActions.length; i++){
            const ra = renderActions[i];
            const layer = ra.layer;
            Debug.call(()=>{
                const compLayer = layerComposition.getLayerByName(layer.name);
                if (!compLayer) {
                    Debug.warnOnce(`Layer ${layer.name} is not found in the scene and will not be rendered. Your render pass setup might need to be updated.`);
                }
            });
            if (layerComposition.isEnabled(layer, ra.transparent)) {
                this.renderRenderAction(ra, i === 0);
            }
        }
    }
    after() {
        // onPostRender events
        for(let i = 0; i < this.renderActions.length; i++){
            const ra = this.renderActions[i];
            if (ra.lastCameraUse) {
                this.scene.fire(EVENT_POSTRENDER, ra.camera);
            }
        }
        // remove shadow before-passes
        this.beforePasses.length = 0;
    }
    /**
     * @param {RenderAction} renderAction - The render action.
     * @param {boolean} firstRenderAction - True if this is the first render action in the render pass.
     */ renderRenderAction(renderAction, firstRenderAction) {
        const { renderer, scene } = this;
        const device = renderer.device;
        // layer
        const { layer, transparent, camera } = renderAction;
        DebugGraphics.pushGpuMarker(this.device, `Camera: ${camera ? camera.entity.name : 'Unnamed'}, Layer: ${layer.name}(${transparent ? 'TRANSP' : 'OPAQUE'})`);
        const drawTime = now();
        if (camera) {
            // override gamma correction and tone mapping settings
            const originalGammaCorrection = camera.gammaCorrection;
            const originalToneMapping = camera.toneMapping;
            if (this.gammaCorrection !== undefined) camera.gammaCorrection = this.gammaCorrection;
            if (this.toneMapping !== undefined) camera.toneMapping = this.toneMapping;
            // layer pre render event
            scene.fire(EVENT_PRERENDER_LAYER, camera, layer, transparent);
            const options = {
                lightClusters: renderAction.lightClusters
            };
            // shader pass - use setting from camera if available, otherwise forward
            const shaderPass = camera.camera.shaderPassInfo?.index ?? SHADER_FORWARD;
            // if this is not a first render action to the render target, or if the render target was not
            // fully cleared on pass start, we need to execute clears here
            if (!firstRenderAction || !camera.camera.fullSizeClearRect) {
                options.clearColor = renderAction.clearColor;
                options.clearDepth = renderAction.clearDepth;
                options.clearStencil = renderAction.clearStencil;
            }
            const renderTarget = renderAction.renderTarget ?? device.backBuffer;
            renderer.renderForwardLayer(camera.camera, renderTarget, layer, transparent, shaderPass, renderAction.viewBindGroups, options);
            // Revert temp frame stuff
            // TODO: this should not be here, as each rendering / clearing should explicitly set up what
            // it requires (the properties are part of render pipeline on WebGPU anyways)
            device.setBlendState(BlendState.NOBLEND);
            device.setStencilState(null, null);
            device.setAlphaToCoverage(false);
            // layer post render event
            scene.fire(EVENT_POSTRENDER_LAYER, camera, layer, transparent);
            // restore gamma correction and tone mapping settings
            if (this.gammaCorrection !== undefined) camera.gammaCorrection = originalGammaCorrection;
            if (this.toneMapping !== undefined) camera.toneMapping = originalToneMapping;
        }
        DebugGraphics.popGpuMarker(this.device);
        layer._renderTime += now() - drawTime;
    }
    log(device, index) {
        super.log(device, index);
        if (Tracing.get(TRACEID_RENDER_PASS_DETAIL)) {
            const { layerComposition } = this;
            this.renderActions.forEach((ra, index)=>{
                const layer = ra.layer;
                const enabled = layer.enabled && layerComposition.isEnabled(layer, ra.transparent);
                const camera = ra.camera;
                Debug.trace(TRACEID_RENDER_PASS_DETAIL, `    ${index}:${` Cam: ${camera ? camera.entity.name : '-'}`.padEnd(22, ' ')}${` Lay: ${layer.name}`.padEnd(22, ' ')}${ra.transparent ? ' TRANSP' : ' OPAQUE'}${enabled ? ' ENABLED' : ' DISABLED'}${` Meshes: ${layer.meshInstances.length}`.padEnd(5, ' ')}`);
            });
        }
    }
}

/**
 * A render pass used to render post-effects.
 */ class RenderPassPostprocessing extends RenderPass {
    constructor(device, renderer, renderAction){
        super(device);
        this.renderer = renderer;
        this.renderAction = renderAction;
        this.requiresCubemaps = false;
    }
    execute() {
        const renderAction = this.renderAction;
        const camera = renderAction.camera;
        Debug.assert(renderAction.triggerPostprocess && camera.onPostprocessing);
        // trigger postprocessing for camera
        camera.onPostprocessing();
    }
}

/**
 * @import { BindGroup } from '../../platform/graphics/bind-group.js'
 * @import { Camera } from '../camera.js'
 * @import { FrameGraph } from '../frame-graph.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { LayerComposition } from '../composition/layer-composition.js'
 * @import { Layer } from '../layer.js'
 * @import { MeshInstance } from '../mesh-instance.js'
 * @import { RenderTarget } from '../../platform/graphics/render-target.js'
 * @import { Scene } from '../scene.js'
 * @import { WorldClusters } from '../lighting/world-clusters.js'
 */ const _noLights = [
    [],
    [],
    []
];
const tmpColor$1 = new Color();
const _drawCallList = {
    drawCalls: [],
    shaderInstances: [],
    isNewMaterial: [],
    lightMaskChanged: [],
    clear: function() {
        this.drawCalls.length = 0;
        this.shaderInstances.length = 0;
        this.isNewMaterial.length = 0;
        this.lightMaskChanged.length = 0;
    }
};
function vogelDiskPrecalculationSamples(numSamples) {
    const samples = [];
    for(let i = 0; i < numSamples; ++i){
        const r = Math.sqrt(i + 0.5) / Math.sqrt(numSamples);
        samples.push(r);
    }
    return samples;
}
function vogelSpherePrecalculationSamples(numSamples) {
    const samples = [];
    for(let i = 0; i < numSamples; i++){
        const weight = i / numSamples;
        const radius = Math.sqrt(weight * weight);
        samples.push(radius);
    }
    return samples;
}
/**
 * The forward renderer renders {@link Scene}s.
 *
 * @ignore
 */ class ForwardRenderer extends Renderer {
    /**
     * Create a new ForwardRenderer instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used by the renderer.
     * @param {Scene} scene - The scene.
     */ constructor(graphicsDevice, scene){
        super(graphicsDevice, scene);
        const device = this.device;
        this._forwardDrawCalls = 0;
        this._materialSwitches = 0;
        this._depthMapTime = 0;
        this._forwardTime = 0;
        this._sortTime = 0;
        // Uniforms
        const scope = device.scope;
        this.fogColorId = scope.resolve('fog_color');
        this.fogStartId = scope.resolve('fog_start');
        this.fogEndId = scope.resolve('fog_end');
        this.fogDensityId = scope.resolve('fog_density');
        this.ambientId = scope.resolve('light_globalAmbient');
        this.skyboxIntensityId = scope.resolve('skyboxIntensity');
        this.cubeMapRotationMatrixId = scope.resolve('cubeMapRotationMatrix');
        this.pcssDiskSamplesId = scope.resolve('pcssDiskSamples[0]');
        this.pcssSphereSamplesId = scope.resolve('pcssSphereSamples[0]');
        this.lightColorId = [];
        this.lightDir = [];
        this.lightDirId = [];
        this.lightShadowMapId = [];
        this.lightShadowMatrixId = [];
        this.lightShadowParamsId = [];
        this.lightShadowIntensity = [];
        this.lightRadiusId = [];
        this.lightPos = [];
        this.lightPosId = [];
        this.lightWidth = [];
        this.lightWidthId = [];
        this.lightHeight = [];
        this.lightHeightId = [];
        this.lightInAngleId = [];
        this.lightOutAngleId = [];
        this.lightCookieId = [];
        this.lightCookieIntId = [];
        this.lightCookieMatrixId = [];
        this.lightCookieOffsetId = [];
        this.lightShadowSearchAreaId = [];
        this.lightCameraParamsId = [];
        this.lightSoftShadowParamsId = [];
        // shadow cascades
        this.shadowMatrixPaletteId = [];
        this.shadowCascadeDistancesId = [];
        this.shadowCascadeCountId = [];
        this.shadowCascadeBlendId = [];
        this.screenSizeId = scope.resolve('uScreenSize');
        this._screenSize = new Float32Array(4);
        this.fogColor = new Float32Array(3);
        this.ambientColor = new Float32Array(3);
        this.pcssDiskSamples = vogelDiskPrecalculationSamples(16);
        this.pcssSphereSamples = vogelSpherePrecalculationSamples(16);
    }
    destroy() {
        super.destroy();
    }
    static{
        // Static properties used by the Profiler in the Editor's Launch Page
        this.skipRenderCamera = null;
    }
    static{
        this._skipRenderCounter = 0;
    }
    static{
        this.skipRenderAfter = 0;
    }
    /**
     * @param {Scene} scene - The scene.
     */ dispatchGlobalLights(scene) {
        const ambientUniform = this.ambientColor;
        // color in linear space
        tmpColor$1.linear(scene.ambientLight);
        ambientUniform[0] = tmpColor$1.r;
        ambientUniform[1] = tmpColor$1.g;
        ambientUniform[2] = tmpColor$1.b;
        if (scene.physicalUnits) {
            for(let i = 0; i < 3; i++){
                ambientUniform[i] *= scene.ambientLuminance;
            }
        }
        this.ambientId.setValue(ambientUniform);
        this.skyboxIntensityId.setValue(scene.physicalUnits ? scene.skyboxLuminance : scene.skyboxIntensity);
        this.cubeMapRotationMatrixId.setValue(scene._skyboxRotationMat3.data);
    }
    _resolveLight(scope, i) {
        const light = `light${i}`;
        this.lightColorId[i] = scope.resolve(`${light}_color`);
        this.lightDir[i] = new Float32Array(3);
        this.lightDirId[i] = scope.resolve(`${light}_direction`);
        this.lightShadowMapId[i] = scope.resolve(`${light}_shadowMap`);
        this.lightShadowMatrixId[i] = scope.resolve(`${light}_shadowMatrix`);
        this.lightShadowParamsId[i] = scope.resolve(`${light}_shadowParams`);
        this.lightShadowIntensity[i] = scope.resolve(`${light}_shadowIntensity`);
        this.lightShadowSearchAreaId[i] = scope.resolve(`${light}_shadowSearchArea`);
        this.lightRadiusId[i] = scope.resolve(`${light}_radius`);
        this.lightPos[i] = new Float32Array(3);
        this.lightPosId[i] = scope.resolve(`${light}_position`);
        this.lightWidth[i] = new Float32Array(3);
        this.lightWidthId[i] = scope.resolve(`${light}_halfWidth`);
        this.lightHeight[i] = new Float32Array(3);
        this.lightHeightId[i] = scope.resolve(`${light}_halfHeight`);
        this.lightInAngleId[i] = scope.resolve(`${light}_innerConeAngle`);
        this.lightOutAngleId[i] = scope.resolve(`${light}_outerConeAngle`);
        this.lightCookieId[i] = scope.resolve(`${light}_cookie`);
        this.lightCookieIntId[i] = scope.resolve(`${light}_cookieIntensity`);
        this.lightCookieMatrixId[i] = scope.resolve(`${light}_cookieMatrix`);
        this.lightCookieOffsetId[i] = scope.resolve(`${light}_cookieOffset`);
        this.lightCameraParamsId[i] = scope.resolve(`${light}_cameraParams`);
        this.lightSoftShadowParamsId[i] = scope.resolve(`${light}_softShadowParams`);
        // shadow cascades
        this.shadowMatrixPaletteId[i] = scope.resolve(`${light}_shadowMatrixPalette[0]`);
        this.shadowCascadeDistancesId[i] = scope.resolve(`${light}_shadowCascadeDistances`);
        this.shadowCascadeCountId[i] = scope.resolve(`${light}_shadowCascadeCount`);
        this.shadowCascadeBlendId[i] = scope.resolve(`${light}_shadowCascadeBlend`);
    }
    setLTCDirectionalLight(wtm, cnt, dir, campos, far) {
        this.lightPos[cnt][0] = campos.x - dir.x * far;
        this.lightPos[cnt][1] = campos.y - dir.y * far;
        this.lightPos[cnt][2] = campos.z - dir.z * far;
        this.lightPosId[cnt].setValue(this.lightPos[cnt]);
        const hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));
        this.lightWidth[cnt][0] = hWidth.x * far;
        this.lightWidth[cnt][1] = hWidth.y * far;
        this.lightWidth[cnt][2] = hWidth.z * far;
        this.lightWidthId[cnt].setValue(this.lightWidth[cnt]);
        const hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));
        this.lightHeight[cnt][0] = hHeight.x * far;
        this.lightHeight[cnt][1] = hHeight.y * far;
        this.lightHeight[cnt][2] = hHeight.z * far;
        this.lightHeightId[cnt].setValue(this.lightHeight[cnt]);
    }
    dispatchDirectLights(dirs, mask, camera) {
        let cnt = 0;
        const scope = this.device.scope;
        for(let i = 0; i < dirs.length; i++){
            if (!(dirs[i].mask & mask)) continue;
            const directional = dirs[i];
            const wtm = directional._node.getWorldTransform();
            if (!this.lightColorId[cnt]) {
                this._resolveLight(scope, cnt);
            }
            this.lightColorId[cnt].setValue(directional._colorLinear);
            // Directional lights shine down the negative Y axis
            wtm.getY(directional._direction).mulScalar(-1);
            directional._direction.normalize();
            this.lightDir[cnt][0] = directional._direction.x;
            this.lightDir[cnt][1] = directional._direction.y;
            this.lightDir[cnt][2] = directional._direction.z;
            this.lightDirId[cnt].setValue(this.lightDir[cnt]);
            if (directional.shape !== LIGHTSHAPE_PUNCTUAL) {
                // non-punctual shape - NB directional area light specular is approximated by putting the area light at the far clip
                this.setLTCDirectionalLight(wtm, cnt, directional._direction, camera._node.getPosition(), camera.farClip);
            }
            if (directional.castShadows) {
                const lightRenderData = directional.getRenderData(camera, 0);
                const biases = directional._getUniformBiasValues(lightRenderData);
                this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
                this.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);
                this.shadowMatrixPaletteId[cnt].setValue(directional._shadowMatrixPalette);
                this.shadowCascadeDistancesId[cnt].setValue(directional._shadowCascadeDistances);
                this.shadowCascadeCountId[cnt].setValue(directional.numCascades);
                this.shadowCascadeBlendId[cnt].setValue(1 - directional.cascadeBlend);
                this.lightShadowIntensity[cnt].setValue(directional.shadowIntensity);
                this.lightSoftShadowParamsId[cnt].setValue(directional._softShadowParams);
                const shadowRT = lightRenderData.shadowCamera.renderTarget;
                if (shadowRT) {
                    this.lightShadowSearchAreaId[cnt].setValue(directional.penumbraSize / lightRenderData.shadowCamera.renderTarget.width * lightRenderData.projectionCompensation);
                }
                const cameraParams = directional._shadowCameraParams;
                cameraParams.length = 4;
                cameraParams[0] = 0; // unused
                cameraParams[1] = lightRenderData.shadowCamera._farClip;
                cameraParams[2] = lightRenderData.shadowCamera._nearClip;
                cameraParams[3] = 1;
                this.lightCameraParamsId[cnt].setValue(cameraParams);
                const params = directional._shadowRenderParams;
                params.length = 4;
                params[0] = directional._shadowResolution; // Note: this needs to change for non-square shadow maps (2 cascades). Currently square is used
                params[1] = biases.normalBias;
                params[2] = biases.bias;
                params[3] = 0;
                this.lightShadowParamsId[cnt].setValue(params);
            }
            cnt++;
        }
        return cnt;
    }
    setLTCPositionalLight(wtm, cnt) {
        const hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));
        this.lightWidth[cnt][0] = hWidth.x;
        this.lightWidth[cnt][1] = hWidth.y;
        this.lightWidth[cnt][2] = hWidth.z;
        this.lightWidthId[cnt].setValue(this.lightWidth[cnt]);
        const hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));
        this.lightHeight[cnt][0] = hHeight.x;
        this.lightHeight[cnt][1] = hHeight.y;
        this.lightHeight[cnt][2] = hHeight.z;
        this.lightHeightId[cnt].setValue(this.lightHeight[cnt]);
    }
    dispatchOmniLight(scope, omni, cnt) {
        const wtm = omni._node.getWorldTransform();
        if (!this.lightColorId[cnt]) {
            this._resolveLight(scope, cnt);
        }
        this.lightRadiusId[cnt].setValue(omni.attenuationEnd);
        this.lightColorId[cnt].setValue(omni._colorLinear);
        wtm.getTranslation(omni._position);
        this.lightPos[cnt][0] = omni._position.x;
        this.lightPos[cnt][1] = omni._position.y;
        this.lightPos[cnt][2] = omni._position.z;
        this.lightPosId[cnt].setValue(this.lightPos[cnt]);
        if (omni.shape !== LIGHTSHAPE_PUNCTUAL) {
            // non-punctual shape
            this.setLTCPositionalLight(wtm, cnt);
        }
        if (omni.castShadows) {
            // shadow map
            const lightRenderData = omni.getRenderData(null, 0);
            this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
            const biases = omni._getUniformBiasValues(lightRenderData);
            const params = omni._shadowRenderParams;
            params.length = 4;
            params[0] = omni._shadowResolution;
            params[1] = biases.normalBias;
            params[2] = biases.bias;
            params[3] = 1.0 / omni.attenuationEnd;
            this.lightShadowParamsId[cnt].setValue(params);
            this.lightShadowIntensity[cnt].setValue(omni.shadowIntensity);
            const pixelsPerMeter = omni.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;
            this.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter);
            const cameraParams = omni._shadowCameraParams;
            cameraParams.length = 4;
            cameraParams[0] = 0; // unused
            cameraParams[1] = lightRenderData.shadowCamera._farClip;
            cameraParams[2] = lightRenderData.shadowCamera._nearClip;
            cameraParams[3] = 0;
            this.lightCameraParamsId[cnt].setValue(cameraParams);
        }
        if (omni._cookie) {
            this.lightCookieId[cnt].setValue(omni._cookie);
            this.lightShadowMatrixId[cnt].setValue(wtm.data);
            this.lightCookieIntId[cnt].setValue(omni.cookieIntensity);
        }
    }
    dispatchSpotLight(scope, spot, cnt) {
        const wtm = spot._node.getWorldTransform();
        if (!this.lightColorId[cnt]) {
            this._resolveLight(scope, cnt);
        }
        this.lightInAngleId[cnt].setValue(spot._innerConeAngleCos);
        this.lightOutAngleId[cnt].setValue(spot._outerConeAngleCos);
        this.lightRadiusId[cnt].setValue(spot.attenuationEnd);
        this.lightColorId[cnt].setValue(spot._colorLinear);
        wtm.getTranslation(spot._position);
        this.lightPos[cnt][0] = spot._position.x;
        this.lightPos[cnt][1] = spot._position.y;
        this.lightPos[cnt][2] = spot._position.z;
        this.lightPosId[cnt].setValue(this.lightPos[cnt]);
        if (spot.shape !== LIGHTSHAPE_PUNCTUAL) {
            // non-punctual shape
            this.setLTCPositionalLight(wtm, cnt);
        }
        // Spots shine down the negative Y axis
        wtm.getY(spot._direction).mulScalar(-1);
        spot._direction.normalize();
        this.lightDir[cnt][0] = spot._direction.x;
        this.lightDir[cnt][1] = spot._direction.y;
        this.lightDir[cnt][2] = spot._direction.z;
        this.lightDirId[cnt].setValue(this.lightDir[cnt]);
        if (spot.castShadows) {
            // shadow map
            const lightRenderData = spot.getRenderData(null, 0);
            this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
            this.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);
            const biases = spot._getUniformBiasValues(lightRenderData);
            const params = spot._shadowRenderParams;
            params.length = 4;
            params[0] = spot._shadowResolution;
            params[1] = biases.normalBias;
            params[2] = biases.bias;
            params[3] = 1.0 / spot.attenuationEnd;
            this.lightShadowParamsId[cnt].setValue(params);
            this.lightShadowIntensity[cnt].setValue(spot.shadowIntensity);
            const pixelsPerMeter = spot.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;
            const fov = lightRenderData.shadowCamera._fov * Math.PI / 180.0;
            const fovRatio = 1.0 / Math.tan(fov / 2.0);
            this.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter * fovRatio);
            const cameraParams = spot._shadowCameraParams;
            cameraParams.length = 4;
            cameraParams[0] = 0; // unused
            cameraParams[1] = lightRenderData.shadowCamera._farClip;
            cameraParams[2] = lightRenderData.shadowCamera._nearClip;
            cameraParams[3] = 0;
            this.lightCameraParamsId[cnt].setValue(cameraParams);
        }
        if (spot._cookie) {
            // if shadow is not rendered, we need to evaluate light projection matrix
            if (!spot.castShadows) {
                const cookieMatrix = LightCamera.evalSpotCookieMatrix(spot);
                this.lightShadowMatrixId[cnt].setValue(cookieMatrix.data);
            }
            this.lightCookieId[cnt].setValue(spot._cookie);
            this.lightCookieIntId[cnt].setValue(spot.cookieIntensity);
            if (spot._cookieTransform) {
                spot._cookieTransformUniform[0] = spot._cookieTransform.x;
                spot._cookieTransformUniform[1] = spot._cookieTransform.y;
                spot._cookieTransformUniform[2] = spot._cookieTransform.z;
                spot._cookieTransformUniform[3] = spot._cookieTransform.w;
                this.lightCookieMatrixId[cnt].setValue(spot._cookieTransformUniform);
                spot._cookieOffsetUniform[0] = spot._cookieOffset.x;
                spot._cookieOffsetUniform[1] = spot._cookieOffset.y;
                this.lightCookieOffsetId[cnt].setValue(spot._cookieOffsetUniform);
            }
        }
    }
    dispatchLocalLights(sortedLights, mask, usedDirLights) {
        let cnt = usedDirLights;
        const scope = this.device.scope;
        const omnis = sortedLights[LIGHTTYPE_OMNI];
        const numOmnis = omnis.length;
        for(let i = 0; i < numOmnis; i++){
            const omni = omnis[i];
            if (!(omni.mask & mask)) continue;
            this.dispatchOmniLight(scope, omni, cnt);
            cnt++;
        }
        const spts = sortedLights[LIGHTTYPE_SPOT];
        const numSpts = spts.length;
        for(let i = 0; i < numSpts; i++){
            const spot = spts[i];
            if (!(spot.mask & mask)) continue;
            this.dispatchSpotLight(scope, spot, cnt);
            cnt++;
        }
    }
    // execute first pass over draw calls, in order to update materials / shaders
    renderForwardPrepareMaterials(camera, renderTarget, drawCalls, sortedLights, layer, pass) {
        // fog params from the scene, or overridden by the camera
        const fogParams = camera.fogParams ?? this.scene.fog;
        // camera shader params
        const shaderParams = camera.shaderParams;
        shaderParams.fog = fogParams.type;
        shaderParams.srgbRenderTarget = renderTarget?.isColorBufferSrgb(0) ?? false; // output gamma correction is determined by the render target
        const addCall = (drawCall, shaderInstance, isNewMaterial, lightMaskChanged)=>{
            _drawCallList.drawCalls.push(drawCall);
            _drawCallList.shaderInstances.push(shaderInstance);
            _drawCallList.isNewMaterial.push(isNewMaterial);
            _drawCallList.lightMaskChanged.push(lightMaskChanged);
        };
        // start with empty arrays
        _drawCallList.clear();
        const device = this.device;
        const scene = this.scene;
        const clusteredLightingEnabled = scene.clusteredLightingEnabled;
        const lightHash = layer?.getLightHash(clusteredLightingEnabled) ?? 0;
        let prevMaterial = null, prevObjDefs, prevLightMask;
        const drawCallsCount = drawCalls.length;
        for(let i = 0; i < drawCallsCount; i++){
            /** @type {MeshInstance} */ const drawCall = drawCalls[i];
            if (camera === ForwardRenderer.skipRenderCamera) {
                if (ForwardRenderer._skipRenderCounter >= ForwardRenderer.skipRenderAfter) {
                    continue;
                }
                ForwardRenderer._skipRenderCounter++;
            }
            if (layer) {
                if (layer._skipRenderCounter >= layer.skipRenderAfter) {
                    continue;
                }
                layer._skipRenderCounter++;
            }
            // skip instanced rendering with 0 instances
            const instancingData = drawCall.instancingData;
            if (instancingData && instancingData.count <= 0) {
                continue;
            }
            drawCall.ensureMaterial(device);
            const material = drawCall.material;
            const objDefs = drawCall._shaderDefs;
            const lightMask = drawCall.mask;
            if (material && material === prevMaterial && objDefs !== prevObjDefs) {
                prevMaterial = null; // force change shader if the object uses a different variant of the same material
            }
            if (material !== prevMaterial) {
                this._materialSwitches++;
                material._scene = scene;
                if (material.dirty) {
                    DebugGraphics.pushGpuMarker(device, `Node: ${drawCall.node.name}, Material: ${material.name}`);
                    material.updateUniforms(device, scene);
                    material.dirty = false;
                    DebugGraphics.popGpuMarker(device);
                }
            }
            const shaderInstance = drawCall.getShaderInstance(pass, lightHash, scene, shaderParams, this.viewUniformFormat, this.viewBindGroupFormat, sortedLights);
            addCall(drawCall, shaderInstance, material !== prevMaterial, !prevMaterial || lightMask !== prevLightMask);
            prevMaterial = material;
            prevObjDefs = objDefs;
            prevLightMask = lightMask;
        }
        return _drawCallList;
    }
    renderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups) {
        const device = this.device;
        const scene = this.scene;
        const passFlag = 1 << pass;
        const flipFactor = flipFaces ? -1 : 1;
        const clusteredLightingEnabled = scene.clusteredLightingEnabled;
        // multiview xr rendering
        const viewList = camera.xr?.session && camera.xr.views.list.length ? camera.xr.views.list : null;
        // Render the scene
        const preparedCallsCount = preparedCalls.drawCalls.length;
        for(let i = 0; i < preparedCallsCount; i++){
            const drawCall = preparedCalls.drawCalls[i];
            // We have a mesh instance
            const newMaterial = preparedCalls.isNewMaterial[i];
            const lightMaskChanged = preparedCalls.lightMaskChanged[i];
            const shaderInstance = preparedCalls.shaderInstances[i];
            const material = drawCall.material;
            const lightMask = drawCall.mask;
            if (shaderInstance.shader.failed) continue;
            if (newMaterial) {
                const asyncCompile = false;
                device.setShader(shaderInstance.shader, asyncCompile);
                // Uniforms I: material
                material.setParameters(device);
                if (lightMaskChanged) {
                    const usedDirLights = this.dispatchDirectLights(sortedLights[LIGHTTYPE_DIRECTIONAL], lightMask, camera);
                    if (!clusteredLightingEnabled) {
                        this.dispatchLocalLights(sortedLights, lightMask, usedDirLights);
                    }
                }
                this.alphaTestId.setValue(material.alphaTest);
                device.setBlendState(material.blendState);
                device.setDepthState(material.depthState);
                device.setAlphaToCoverage(material.alphaToCoverage);
            }
            DebugGraphics.pushGpuMarker(device, `Node: ${drawCall.node.name}, Material: ${material.name}`);
            this.setupCullMode(camera._cullFaces, flipFactor, drawCall);
            const stencilFront = drawCall.stencilFront ?? material.stencilFront;
            const stencilBack = drawCall.stencilBack ?? material.stencilBack;
            device.setStencilState(stencilFront, stencilBack);
            // Uniforms II: meshInstance overrides
            drawCall.setParameters(device, passFlag);
            // mesh ID - used by the picker
            device.scope.resolve('meshInstanceId').setValue(drawCall.id);
            const mesh = drawCall.mesh;
            this.setVertexBuffers(device, mesh);
            this.setMorphing(device, drawCall.morphInstance);
            this.setSkinning(device, drawCall);
            const instancingData = drawCall.instancingData;
            if (instancingData) {
                device.setVertexBuffer(instancingData.vertexBuffer);
            }
            // mesh / mesh normal matrix
            this.setMeshInstanceMatrices(drawCall, true);
            this.setupMeshUniformBuffers(shaderInstance);
            const style = drawCall.renderStyle;
            const indexBuffer = mesh.indexBuffer[style];
            drawCallback?.(drawCall, i);
            const indirectSlot = drawCall.indirectData?.get(camera);
            if (viewList) {
                for(let v = 0; v < viewList.length; v++){
                    const view = viewList[v];
                    device.setViewport(view.viewport.x, view.viewport.y, view.viewport.z, view.viewport.w);
                    if (device.supportsUniformBuffers) {
                        const viewBindGroup = viewBindGroups[v];
                        device.setBindGroup(BINDGROUP_VIEW, viewBindGroup);
                    } else {
                        this.setupViewUniforms(view, v);
                    }
                    const first = v === 0;
                    const last = v === viewList.length - 1;
                    device.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectSlot, first, last);
                    this._forwardDrawCalls++;
                    if (drawCall.instancingData) {
                        this._instancedDrawCalls++;
                    }
                }
            } else {
                device.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectSlot);
                this._forwardDrawCalls++;
                if (drawCall.instancingData) {
                    this._instancedDrawCalls++;
                }
            }
            // Unset meshInstance overrides back to material values if next draw call will use the same material
            if (i < preparedCallsCount - 1 && !preparedCalls.isNewMaterial[i + 1]) {
                material.setParameters(device, drawCall.parameters);
            }
            DebugGraphics.popGpuMarker(device);
        }
    }
    renderForward(camera, renderTarget, allDrawCalls, sortedLights, pass, drawCallback, layer, flipFaces, viewBindGroups) {
        const forwardStartTime = now();
        // run first pass over draw calls and handle material / shader updates
        const preparedCalls = this.renderForwardPrepareMaterials(camera, renderTarget, allDrawCalls, sortedLights, layer, pass);
        // render mesh instances
        this.renderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups);
        _drawCallList.clear();
        this._forwardTime += now() - forwardStartTime;
    }
    /**
     * Forward render mesh instances on a specified layer, using a camera and a render target.
     * Shaders used are based on the shaderPass provided, with optional clustered lighting support.
     *
     * @param {Camera} camera - The camera.
     * @param {RenderTarget|undefined} renderTarget - The render target.
     * @param {Layer} layer - The layer.
     * @param {boolean} transparent - True if transparent sublayer should be rendered, opaque
     * otherwise.
     * @param {number} shaderPass - A type of shader to use during rendering.
     * @param {BindGroup[]} viewBindGroups - An array storing the view level bing groups (can be
     * empty array, and this function populates if per view).
     * @param {object} [options] - Object for passing optional arguments.
     * @param {boolean} [options.clearColor] - True if the color buffer should be cleared.
     * @param {boolean} [options.clearDepth] - True if the depth buffer should be cleared.
     * @param {boolean} [options.clearStencil] - True if the stencil buffer should be cleared.
     * @param {WorldClusters} [options.lightClusters] - The world clusters object to be used for
     * clustered lighting.
     * @param {MeshInstance[]} [options.meshInstances] - The mesh instances to be rendered. Use
     * when layer is not provided.
     * @param {object} [options.splitLights] - The split lights to be used for clustered lighting.
     */ renderForwardLayer(camera, renderTarget, layer, transparent, shaderPass, viewBindGroups, options = {}) {
        const { scene, device } = this;
        const clusteredLightingEnabled = scene.clusteredLightingEnabled;
        this.setupViewport(camera, renderTarget);
        let visible, splitLights;
        if (layer) {
            const sortTime = now();
            layer.sortVisible(camera, transparent);
            this._sortTime += now() - sortTime;
            const culledInstances = layer.getCulledInstances(camera);
            visible = transparent ? culledInstances.transparent : culledInstances.opaque;
            // add debug mesh instances to visible list
            scene.immediate.onPreRenderLayer(layer, visible, transparent);
            // set up layer uniforms
            if (layer.requiresLightCube) {
                this.lightCube.update(scene.ambientLight, layer._lights);
                this.constantLightCube.setValue(this.lightCube.colors);
            }
            splitLights = layer.splitLights;
        } else {
            visible = options.meshInstances;
            splitLights = options.splitLights ?? _noLights;
        }
        Debug.assert(visible, 'Either layer or options.meshInstances must be provided');
        // upload clustered lights uniforms
        if (clusteredLightingEnabled) {
            const lightClusters = options.lightClusters ?? this.worldClustersAllocator.empty;
            lightClusters.activate();
            // debug rendering of clusters
            if (layer) {
                if (!this.clustersDebugRendered && scene.lighting.debugLayer === layer.id) {
                    this.clustersDebugRendered = true;
                    WorldClustersDebug.render(lightClusters, this.scene);
                }
            }
        }
        // Set the not very clever global variable which is only useful when there's just one camera
        scene._activeCamera = camera;
        const fogParams = camera.fogParams ?? this.scene.fog;
        this.setFogConstants(fogParams);
        const viewList = this.setCameraUniforms(camera, renderTarget);
        if (device.supportsUniformBuffers) {
            this.setupViewUniformBuffers(viewBindGroups, this.viewUniformFormat, this.viewBindGroupFormat, viewList);
        }
        // clearing - do it after the view bind groups are set up, to avoid overriding those
        const clearColor = options.clearColor ?? false;
        const clearDepth = options.clearDepth ?? false;
        const clearStencil = options.clearStencil ?? false;
        if (clearColor || clearDepth || clearStencil) {
            this.clear(camera, clearColor, clearDepth, clearStencil);
        }
        // enable flip faces if either the camera has _flipFaces enabled or the render target has flipY enabled
        const flipFaces = !!(camera._flipFaces ^ renderTarget?.flipY);
        const forwardDrawCalls = this._forwardDrawCalls;
        this.renderForward(camera, renderTarget, visible, splitLights, shaderPass, null, layer, flipFaces, viewBindGroups);
        if (layer) {
            layer._forwardDrawCalls += this._forwardDrawCalls - forwardDrawCalls;
        }
    }
    setFogConstants(fogParams) {
        if (fogParams.type !== FOG_NONE) {
            // color in linear space
            tmpColor$1.linear(fogParams.color);
            const fogUniform = this.fogColor;
            fogUniform[0] = tmpColor$1.r;
            fogUniform[1] = tmpColor$1.g;
            fogUniform[2] = tmpColor$1.b;
            this.fogColorId.setValue(fogUniform);
            if (fogParams.type === FOG_LINEAR) {
                this.fogStartId.setValue(fogParams.start);
                this.fogEndId.setValue(fogParams.end);
            } else {
                this.fogDensityId.setValue(fogParams.density);
            }
        }
    }
    setSceneConstants() {
        const scene = this.scene;
        // Set up ambient/exposure
        this.dispatchGlobalLights(scene);
        // Set up screen size // should be RT size?
        const device = this.device;
        this._screenSize[0] = device.width;
        this._screenSize[1] = device.height;
        this._screenSize[2] = 1 / device.width;
        this._screenSize[3] = 1 / device.height;
        this.screenSizeId.setValue(this._screenSize);
        this.pcssDiskSamplesId.setValue(this.pcssDiskSamples);
        this.pcssSphereSamplesId.setValue(this.pcssSphereSamples);
    }
    /**
     * Builds a frame graph for the rendering of the whole frame.
     *
     * @param {FrameGraph} frameGraph - The frame-graph that is built.
     * @param {LayerComposition} layerComposition - The layer composition used to build the frame
     * graph.
     * @ignore
     */ buildFrameGraph(frameGraph, layerComposition) {
        const scene = this.scene;
        frameGraph.reset();
        if (scene.clusteredLightingEnabled) {
            // clustered lighting passes
            const { shadowsEnabled, cookiesEnabled } = scene.lighting;
            this._renderPassUpdateClustered.update(frameGraph, shadowsEnabled, cookiesEnabled, this.lights, this.localLights);
            frameGraph.addRenderPass(this._renderPassUpdateClustered);
        } else {
            // non-clustered local shadows - these are shared by all cameras (not entirely correctly)
            this._shadowRendererLocal.buildNonClusteredRenderPasses(frameGraph, this.localLights);
        }
        // main passes
        let startIndex = 0;
        let newStart = true;
        let renderTarget = null;
        const renderActions = layerComposition._renderActions;
        for(let i = startIndex; i < renderActions.length; i++){
            const renderAction = renderActions[i];
            const { layer, camera } = renderAction;
            if (renderAction.useCameraPasses) {
                Debug.call(()=>{
                    if (camera.postEffects.effects.length > 0) {
                        Debug.warnOnce(`Camera '${camera.entity.name}' uses render passes, which are not compatible with post-effects scripts. Rendering of the post-effects is ignored, but they should not be attached to the camera.`);
                    }
                });
                // schedule render passes from the camera
                camera.camera.renderPasses.forEach((renderPass)=>{
                    frameGraph.addRenderPass(renderPass);
                });
            } else {
                const isDepthLayer = layer.id === LAYERID_DEPTH;
                const isGrabPass = isDepthLayer && (camera.renderSceneColorMap || camera.renderSceneDepthMap);
                // start of block of render actions rendering to the same render target
                if (newStart) {
                    newStart = false;
                    startIndex = i;
                    renderTarget = renderAction.renderTarget;
                }
                // info about the next render action
                const nextRenderAction = renderActions[i + 1];
                const isNextLayerDepth = nextRenderAction ? !nextRenderAction.useCameraPasses && nextRenderAction.layer.id === LAYERID_DEPTH : false;
                const isNextLayerGrabPass = isNextLayerDepth && (camera.renderSceneColorMap || camera.renderSceneDepthMap);
                const nextNeedDirShadows = nextRenderAction ? nextRenderAction.firstCameraUse && this.cameraDirShadowLights.has(nextRenderAction.camera.camera) : false;
                // end of the block using the same render target if the next render action uses a different render target, or needs directional shadows
                // rendered before it or similar or needs other pass before it.
                if (!nextRenderAction || nextRenderAction.renderTarget !== renderTarget || nextNeedDirShadows || isNextLayerGrabPass || isGrabPass) {
                    // render the render actions in the range
                    const isDepthOnly = isDepthLayer && startIndex === i;
                    if (!isDepthOnly) {
                        this.addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, i);
                    }
                    // depth layer triggers grab passes if enabled
                    if (isDepthLayer) {
                        if (camera.renderSceneColorMap) {
                            const colorGrabPass = camera.camera.renderPassColorGrab;
                            colorGrabPass.source = camera.renderTarget;
                            frameGraph.addRenderPass(colorGrabPass);
                        }
                        if (camera.renderSceneDepthMap) {
                            frameGraph.addRenderPass(camera.camera.renderPassDepthGrab);
                        }
                    }
                    // postprocessing
                    if (renderAction.triggerPostprocess && camera?.onPostprocessing) {
                        const renderPass = new RenderPassPostprocessing(this.device, this, renderAction);
                        frameGraph.addRenderPass(renderPass);
                    }
                    newStart = true;
                }
            }
        }
    }
    /**
     * @param {FrameGraph} frameGraph - The frame graph.
     * @param {LayerComposition} layerComposition - The layer composition.
     */ addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, endIndex) {
        const renderPass = new RenderPassForward(this.device, layerComposition, this.scene, this);
        renderPass.init(renderTarget);
        const renderActions = layerComposition._renderActions;
        for(let i = startIndex; i <= endIndex; i++){
            renderPass.addRenderAction(renderActions[i]);
        }
        frameGraph.addRenderPass(renderPass);
    }
    /**
     * @param {LayerComposition} comp - The layer composition.
     */ update(comp) {
        this.frameUpdate();
        this.shadowRenderer.frameUpdate();
        // update the skybox, since this might change _meshInstances
        this.scene._updateSkyMesh();
        // update layer composition
        this.updateLayerComposition(comp);
        this.collectLights(comp);
        // Single per-frame calculations
        this.beginFrame(comp);
        this.setSceneConstants();
        // update gsplat director
        this.gsplatDirector?.update(comp);
        // visibility culling of lights, meshInstances, shadows casters
        // after this the scene culling is done and script callbacks can be called to report which objects are visible
        this.cullComposition(comp);
        // GPU update for visible objects requiring one
        this.gpuUpdate(this.processingMeshInstances);
    }
}

/**
 * @import { Camera } from './camera.js'
 * @import { CameraComponent } from '../framework/components/camera/component.js'
 * @import { Light } from './light.js'
 * @import { LightComponent } from '../framework/components/light/component.js'
 * @import { MeshInstance } from './mesh-instance.js'
 * @import { Vec3 } from '../core/math/vec3.js'
 * @import { GSplatPlacement } from './gsplat-unified/gsplat-placement.js'
 */ // Layers
let layerCounter = 0;
const lightKeys = [];
const _tempMaterials = new Set();
function sortManual(drawCallA, drawCallB) {
    return drawCallA.drawOrder - drawCallB.drawOrder;
}
function sortMaterialMesh(drawCallA, drawCallB) {
    const keyA = drawCallA._sortKeyForward;
    const keyB = drawCallB._sortKeyForward;
    if (keyA === keyB) {
        return drawCallB.mesh.id - drawCallA.mesh.id;
    }
    return keyB - keyA;
}
function sortBackToFront(drawCallA, drawCallB) {
    return drawCallB._sortKeyDynamic - drawCallA._sortKeyDynamic;
}
function sortFrontToBack(drawCallA, drawCallB) {
    return drawCallA._sortKeyDynamic - drawCallB._sortKeyDynamic;
}
const sortCallbacks = [
    null,
    sortManual,
    sortMaterialMesh,
    sortBackToFront,
    sortFrontToBack
];
class CulledInstances {
    constructor(){
        /**
     * Visible opaque mesh instances.
     *
     * @type {MeshInstance[]}
     */ this.opaque = [];
        /**
     * Visible transparent mesh instances.
     *
     * @type {MeshInstance[]}
     */ this.transparent = [];
    }
}
/**
 * A Layer represents a renderable subset of the scene. It can contain a list of mesh instances,
 * lights and cameras, their render settings and also defines custom callbacks before, after or
 * during rendering. Layers are organized inside {@link LayerComposition} in a desired order.
 *
 * @category Graphics
 */ class Layer {
    /**
     * Create a new Layer instance.
     *
     * @param {object} options - Object for passing optional arguments. These arguments are the
     * same as properties of the Layer.
     */ constructor(options = {}){
        /**
     * Mesh instances assigned to this layer.
     *
     * @type {MeshInstance[]}
     * @ignore
     */ this.meshInstances = [];
        /**
     * Mesh instances assigned to this layer, stored in a set.
     *
     * @type {Set<MeshInstance>}
     * @ignore
     */ this.meshInstancesSet = new Set();
        /**
     * Shadow casting instances assigned to this layer.
     *
     * @type {MeshInstance[]}
     * @ignore
     */ this.shadowCasters = [];
        /**
     * Shadow casting instances assigned to this layer, stored in a set.
     *
     * @type {Set<MeshInstance>}
     * @ignore
     */ this.shadowCastersSet = new Set();
        /**
     * Visible (culled) mesh instances assigned to this layer. Looked up by the Camera.
     *
     * @type {WeakMap<Camera, CulledInstances>}
     * @private
     */ this._visibleInstances = new WeakMap();
        /**
     * All lights assigned to a layer.
     *
     * @type {Light[]}
     * @private
     */ this._lights = [];
        /**
     * All lights assigned to a layer stored in a set.
     *
     * @type {Set<Light>}
     * @private
     */ this._lightsSet = new Set();
        /**
     * Set of light used by clustered lighting (omni and spot, but no directional).
     *
     * @type {Set<Light>}
     * @private
     */ this._clusteredLightsSet = new Set();
        /**
     * Lights separated by light type. Lights in the individual arrays are sorted by the key,
     * to match their order in _lightIdHash, so that their order matches the order expected by the
     * generated shader code.
     *
     * @type {Light[][]}
     * @private
     */ this._splitLights = [
            [],
            [],
            []
        ];
        /**
     * True if _splitLights needs to be updated, which means if lights were added or removed from
     * the layer, or their key changed.
     *
     * @type {boolean}
     * @private
     */ this._splitLightsDirty = true;
        /**
     * True if the objects rendered on the layer require light cube (emitters with lighting do).
     *
     * @type {boolean}
     * @ignore
     */ this.requiresLightCube = false;
        /**
     * @type {CameraComponent[]}
     * @ignore
     */ this.cameras = [];
        /**
     * @type {Set<Camera>}
     * @ignore
     */ this.camerasSet = new Set();
        /**
     * @type {GSplatPlacement[]}
     * @ignore
     */ this.gsplatPlacements = [];
        /**
     * True if the gsplatPlacements array was modified.
     *
     * @type {boolean}
     * @ignore
     */ this.gsplatPlacementsDirty = true;
        /**
     * True if the composition is invalidated.
     *
     * @ignore
     */ this._dirtyComposition = false;
        if (options.id !== undefined) {
            /**
             * A unique ID of the layer. Layer IDs are stored inside {@link ModelComponent#layers},
             * {@link RenderComponent#layers}, {@link CameraComponent#layers},
             * {@link LightComponent#layers} and {@link ElementComponent#layers} instead of names.
             * Can be used in {@link LayerComposition#getLayerById}.
             *
             * @type {number}
             */ this.id = options.id;
            layerCounter = Math.max(this.id + 1, layerCounter);
        } else {
            this.id = layerCounter++;
        }
        /**
         * Name of the layer. Can be used in {@link LayerComposition#getLayerByName}.
         *
         * @type {string}
         */ this.name = options.name;
        /**
         * @type {boolean}
         * @private
         */ this._enabled = options.enabled ?? true;
        /**
         * @type {number}
         * @private
         */ this._refCounter = this._enabled ? 1 : 0;
        /**
         * Defines the method used for sorting opaque (that is, not semi-transparent) mesh
         * instances before rendering. Can be:
         *
         * - {@link SORTMODE_NONE}
         * - {@link SORTMODE_MANUAL}
         * - {@link SORTMODE_MATERIALMESH}
         * - {@link SORTMODE_BACK2FRONT}
         * - {@link SORTMODE_FRONT2BACK}
         *
         * Defaults to {@link SORTMODE_MATERIALMESH}.
         *
         * @type {number}
         */ this.opaqueSortMode = options.opaqueSortMode ?? SORTMODE_MATERIALMESH;
        /**
         * Defines the method used for sorting semi-transparent mesh instances before rendering. Can be:
         *
         * - {@link SORTMODE_NONE}
         * - {@link SORTMODE_MANUAL}
         * - {@link SORTMODE_MATERIALMESH}
         * - {@link SORTMODE_BACK2FRONT}
         * - {@link SORTMODE_FRONT2BACK}
         *
         * Defaults to {@link SORTMODE_BACK2FRONT}.
         *
         * @type {number}
         */ this.transparentSortMode = options.transparentSortMode ?? SORTMODE_BACK2FRONT;
        if (options.renderTarget) {
            this.renderTarget = options.renderTarget;
        }
        // clear flags
        /**
         * @type {boolean}
         * @private
         */ this._clearColorBuffer = !!options.clearColorBuffer;
        /**
         * @type {boolean}
         * @private
         */ this._clearDepthBuffer = !!options.clearDepthBuffer;
        /**
         * @type {boolean}
         * @private
         */ this._clearStencilBuffer = !!options.clearStencilBuffer;
        /**
         * Custom function that is called after the layer has been enabled. This happens when:
         *
         * - The layer is created with {@link Layer#enabled} set to true (which is the default value).
         * - {@link Layer#enabled} was changed from false to true
         *
         * @type {Function}
         */ this.onEnable = options.onEnable;
        /**
         * Custom function that is called after the layer has been disabled. This happens when:
         *
         * - {@link Layer#enabled} was changed from true to false
         * - {@link Layer#decrementCounter} was called and set the counter to zero.
         *
         * @type {Function}
         */ this.onDisable = options.onDisable;
        if (this._enabled && this.onEnable) {
            this.onEnable();
        }
        /**
         * @type {Function|null}
         * @ignore
         */ this.customSortCallback = null;
        /**
         * @type {Function|null}
         * @ignore
         */ this.customCalculateSortValues = null;
        // light hash based on the light keys
        this._lightHash = 0;
        this._lightHashDirty = false;
        // light hash based on light ids
        this._lightIdHash = 0;
        this._lightIdHashDirty = false;
        this.skipRenderAfter = Number.MAX_VALUE;
        this._skipRenderCounter = 0;
        this._renderTime = 0;
        this._forwardDrawCalls = 0;
        this._shadowDrawCalls = 0; // deprecated, not useful on a layer anymore, could be moved to camera
        this._shaderVersion = -1;
    }
    /**
     * Sets the enabled state of the layer. Disabled layers are skipped. Defaults to true.
     *
     * @type {boolean}
     */ set enabled(val) {
        if (val !== this._enabled) {
            this._dirtyComposition = true;
            this.gsplatPlacementsDirty = true;
            this._enabled = val;
            if (val) {
                this.incrementCounter();
                if (this.onEnable) this.onEnable();
            } else {
                this.decrementCounter();
                if (this.onDisable) this.onDisable();
            }
        }
    }
    /**
     * Gets the enabled state of the layer.
     *
     * @type {boolean}
     */ get enabled() {
        return this._enabled;
    }
    /**
     * Sets whether the camera will clear the color buffer when it renders this layer.
     *
     * @type {boolean}
     */ set clearColorBuffer(val) {
        this._clearColorBuffer = val;
        this._dirtyComposition = true;
    }
    /**
     * Gets whether the camera will clear the color buffer when it renders this layer.
     *
     * @type {boolean}
     */ get clearColorBuffer() {
        return this._clearColorBuffer;
    }
    /**
     * Sets whether the camera will clear the depth buffer when it renders this layer.
     *
     * @type {boolean}
     */ set clearDepthBuffer(val) {
        this._clearDepthBuffer = val;
        this._dirtyComposition = true;
    }
    /**
     * Gets whether the camera will clear the depth buffer when it renders this layer.
     *
     * @type {boolean}
     */ get clearDepthBuffer() {
        return this._clearDepthBuffer;
    }
    /**
     * Sets whether the camera will clear the stencil buffer when it renders this layer.
     *
     * @type {boolean}
     */ set clearStencilBuffer(val) {
        this._clearStencilBuffer = val;
        this._dirtyComposition = true;
    }
    /**
     * Gets whether the camera will clear the stencil buffer when it renders this layer.
     *
     * @type {boolean}
     */ get clearStencilBuffer() {
        return this._clearStencilBuffer;
    }
    /**
     * Gets whether the layer contains omni or spot lights.
     *
     * @type {boolean}
     * @ignore
     */ get hasClusteredLights() {
        return this._clusteredLightsSet.size > 0;
    }
    /**
     * Gets the lights used by clustered lighting in a set.
     *
     * @type {Set<Light>}
     * @ignore
     */ get clusteredLightsSet() {
        return this._clusteredLightsSet;
    }
    /**
     * Increments the usage counter of this layer. By default, layers are created with counter set
     * to 1 (if {@link Layer.enabled} is true) or 0 (if it was false). Incrementing the counter
     * from 0 to 1 will enable the layer and call {@link Layer.onEnable}. Use this function to
     * "subscribe" multiple effects to the same layer. For example, if the layer is used to render
     * a reflection texture which is used by 2 mirrors, then each mirror can call this function
     * when visible and {@link Layer.decrementCounter} if invisible. In such case the reflection
     * texture won't be updated, when there is nothing to use it, saving performance.
     *
     * @ignore
     */ incrementCounter() {
        if (this._refCounter === 0) {
            this._enabled = true;
            if (this.onEnable) this.onEnable();
        }
        this._refCounter++;
    }
    /**
     * Decrements the usage counter of this layer. Decrementing the counter from 1 to 0 will
     * disable the layer and call {@link Layer.onDisable}.
     *
     * @ignore
     */ decrementCounter() {
        if (this._refCounter === 1) {
            this._enabled = false;
            if (this.onDisable) this.onDisable();
        } else if (this._refCounter === 0) {
            Debug.warn('Trying to decrement layer counter below 0');
            return;
        }
        this._refCounter--;
    }
    /**
     * Adds a gsplat placement to this layer.
     *
     * @param {GSplatPlacement} placement - A placement of a gsplat.
     * @ignore
     */ addGSplatPlacement(placement) {
        if (!this.gsplatPlacements.includes(placement)) {
            this.gsplatPlacements.push(placement);
            this.gsplatPlacementsDirty = true;
        }
    }
    /**
     * Removes a gsplat placement from this layer.
     *
     * @param {GSplatPlacement} placement - A placement of a gsplat.
     * @ignore
     */ removeGSplatPlacement(placement) {
        const index = this.gsplatPlacements.indexOf(placement);
        if (index >= 0) {
            this.gsplatPlacements.splice(index, 1);
            this.gsplatPlacementsDirty = true;
        }
    }
    /**
     * Adds an array of mesh instances to this layer.
     *
     * @param {MeshInstance[]} meshInstances - Array of {@link MeshInstance}.
     * @param {boolean} [skipShadowCasters] - Set it to true if you don't want these mesh instances
     * to cast shadows in this layer. Defaults to false.
     */ addMeshInstances(meshInstances, skipShadowCasters) {
        const destMeshInstances = this.meshInstances;
        const destMeshInstancesSet = this.meshInstancesSet;
        // add mesh instances to the layer's array and the set
        for(let i = 0; i < meshInstances.length; i++){
            const mi = meshInstances[i];
            if (!destMeshInstancesSet.has(mi)) {
                destMeshInstances.push(mi);
                destMeshInstancesSet.add(mi);
                _tempMaterials.add(mi.material);
            }
        }
        // shadow casters
        if (!skipShadowCasters) {
            this.addShadowCasters(meshInstances);
        }
        // clear old shader variants if necessary
        if (_tempMaterials.size > 0) {
            const sceneShaderVer = this._shaderVersion;
            _tempMaterials.forEach((mat)=>{
                if (sceneShaderVer >= 0 && mat._shaderVersion !== sceneShaderVer) {
                    // skip this for materials not using variants
                    if (mat.getShaderVariant !== Material.prototype.getShaderVariant) {
                        // clear shader variants on the material and also on mesh instances that use it
                        mat.clearVariants();
                    }
                    mat._shaderVersion = sceneShaderVer;
                }
            });
            _tempMaterials.clear();
        }
    }
    /**
     * Removes multiple mesh instances from this layer.
     *
     * @param {MeshInstance[]} meshInstances - Array of {@link MeshInstance}. If they were added to
     * this layer, they will be removed.
     * @param {boolean} [skipShadowCasters] - Set it to true if you want to still cast shadows from
     * removed mesh instances or if they never did cast shadows before. Defaults to false.
     */ removeMeshInstances(meshInstances, skipShadowCasters) {
        const destMeshInstances = this.meshInstances;
        const destMeshInstancesSet = this.meshInstancesSet;
        // mesh instances
        for(let i = 0; i < meshInstances.length; i++){
            const mi = meshInstances[i];
            // remove from mesh instances list
            if (destMeshInstancesSet.has(mi)) {
                destMeshInstancesSet.delete(mi);
                const j = destMeshInstances.indexOf(mi);
                if (j >= 0) {
                    destMeshInstances.splice(j, 1);
                }
            }
        }
        // shadow casters
        if (!skipShadowCasters) {
            this.removeShadowCasters(meshInstances);
        }
    }
    /**
     * Adds an array of mesh instances to this layer, but only as shadow casters (they will not be
     * rendered anywhere, but only cast shadows on other objects).
     *
     * @param {MeshInstance[]} meshInstances - Array of {@link MeshInstance}.
     */ addShadowCasters(meshInstances) {
        const shadowCasters = this.shadowCasters;
        const shadowCastersSet = this.shadowCastersSet;
        for(let i = 0; i < meshInstances.length; i++){
            const mi = meshInstances[i];
            if (mi.castShadow && !shadowCastersSet.has(mi)) {
                shadowCastersSet.add(mi);
                shadowCasters.push(mi);
            }
        }
    }
    /**
     * Removes multiple mesh instances from the shadow casters list of this layer, meaning they
     * will stop casting shadows.
     *
     * @param {MeshInstance[]} meshInstances - Array of {@link MeshInstance}. If they were added to
     * this layer, they will be removed.
     */ removeShadowCasters(meshInstances) {
        const shadowCasters = this.shadowCasters;
        const shadowCastersSet = this.shadowCastersSet;
        for(let i = 0; i < meshInstances.length; i++){
            const mi = meshInstances[i];
            if (shadowCastersSet.has(mi)) {
                shadowCastersSet.delete(mi);
                const j = shadowCasters.indexOf(mi);
                if (j >= 0) {
                    shadowCasters.splice(j, 1);
                }
            }
        }
    }
    /**
     * Removes all mesh instances from this layer.
     *
     * @param {boolean} [skipShadowCasters] - Set it to true if you want to continue the existing mesh
     * instances to cast shadows. Defaults to false, which removes shadow casters as well.
     */ clearMeshInstances(skipShadowCasters = false) {
        this.meshInstances.length = 0;
        this.meshInstancesSet.clear();
        if (!skipShadowCasters) {
            this.shadowCasters.length = 0;
            this.shadowCastersSet.clear();
        }
    }
    markLightsDirty() {
        this._lightHashDirty = true;
        this._lightIdHashDirty = true;
        this._splitLightsDirty = true;
    }
    hasLight(light) {
        return this._lightsSet.has(light);
    }
    /**
     * Adds a light to this layer.
     *
     * @param {LightComponent} light - A {@link LightComponent}.
     */ addLight(light) {
        // if the light is not in the layer already
        const l = light.light;
        if (!this._lightsSet.has(l)) {
            this._lightsSet.add(l);
            this._lights.push(l);
            this.markLightsDirty();
        }
        if (l.type !== LIGHTTYPE_DIRECTIONAL) {
            this._clusteredLightsSet.add(l);
        }
    }
    /**
     * Removes a light from this layer.
     *
     * @param {LightComponent} light - A {@link LightComponent}.
     */ removeLight(light) {
        const l = light.light;
        if (this._lightsSet.has(l)) {
            this._lightsSet.delete(l);
            this._lights.splice(this._lights.indexOf(l), 1);
            this.markLightsDirty();
        }
        if (l.type !== LIGHTTYPE_DIRECTIONAL) {
            this._clusteredLightsSet.delete(l);
        }
    }
    /**
     * Removes all lights from this layer.
     */ clearLights() {
        // notify lights
        this._lightsSet.forEach((light)=>light.removeLayer(this));
        this._lightsSet.clear();
        this._clusteredLightsSet.clear();
        this._lights.length = 0;
        this.markLightsDirty();
    }
    get splitLights() {
        if (this._splitLightsDirty) {
            this._splitLightsDirty = false;
            const splitLights = this._splitLights;
            for(let i = 0; i < splitLights.length; i++){
                splitLights[i].length = 0;
            }
            const lights = this._lights;
            for(let i = 0; i < lights.length; i++){
                const light = lights[i];
                if (light.enabled) {
                    splitLights[light._type].push(light);
                }
            }
            // sort the lights by their key, as the order of lights is used to generate shader generation key,
            // and this avoids new shaders being generated when lights are reordered
            for(let i = 0; i < splitLights.length; i++){
                splitLights[i].sort((a, b)=>a.key - b.key);
            }
        }
        return this._splitLights;
    }
    evaluateLightHash(localLights, directionalLights, useIds) {
        let hash = 0;
        // select local/directional lights based on request
        const lights = this._lights;
        for(let i = 0; i < lights.length; i++){
            const isLocalLight = lights[i].type !== LIGHTTYPE_DIRECTIONAL;
            if (localLights && isLocalLight || directionalLights && !isLocalLight) {
                lightKeys.push(useIds ? lights[i].id : lights[i].key);
            }
        }
        if (lightKeys.length > 0) {
            // sort the keys to make sure the hash is the same for the same set of lights
            lightKeys.sort();
            hash = hash32Fnv1a(lightKeys);
            lightKeys.length = 0;
        }
        return hash;
    }
    getLightHash(isClustered) {
        if (this._lightHashDirty) {
            this._lightHashDirty = false;
            // Generate hash to check if layers have the same set of lights independent of their order.
            // Always use directional lights. Additionally use local lights if clustered lighting is disabled.
            // (only directional lights affect the shader generation for clustered lighting)
            this._lightHash = this.evaluateLightHash(!isClustered, true, false);
        }
        return this._lightHash;
    }
    // This is only used in clustered lighting mode
    getLightIdHash() {
        if (this._lightIdHashDirty) {
            this._lightIdHashDirty = false;
            // Generate hash based on Ids of lights sorted by ids, to check if the layers have the same set of lights
            // Only use local lights (directional lights are not used for clustered lighting)
            this._lightIdHash = this.evaluateLightHash(true, false, true);
        }
        return this._lightIdHash;
    }
    /**
     * Adds a camera to this layer.
     *
     * @param {CameraComponent} camera - A {@link CameraComponent}.
     */ addCamera(camera) {
        if (!this.camerasSet.has(camera.camera)) {
            this.camerasSet.add(camera.camera);
            this.cameras.push(camera);
            this._dirtyComposition = true;
        }
    }
    /**
     * Removes a camera from this layer.
     *
     * @param {CameraComponent} camera - A {@link CameraComponent}.
     */ removeCamera(camera) {
        if (this.camerasSet.has(camera.camera)) {
            this.camerasSet.delete(camera.camera);
            const index = this.cameras.indexOf(camera);
            this.cameras.splice(index, 1);
            this._dirtyComposition = true;
        }
    }
    /**
     * Removes all cameras from this layer.
     */ clearCameras() {
        this.cameras.length = 0;
        this.camerasSet.clear();
        this._dirtyComposition = true;
    }
    /**
     * @param {MeshInstance[]} drawCalls - Array of mesh instances.
     * @param {Vec3} camPos - Camera position.
     * @param {Vec3} camFwd - Camera forward vector.
     * @private
     */ _calculateSortDistances(drawCalls, camPos, camFwd) {
        const count = drawCalls.length;
        const { x: px, y: py, z: pz } = camPos;
        const { x: fx, y: fy, z: fz } = camFwd;
        for(let i = 0; i < count; i++){
            const drawCall = drawCalls[i];
            // compute distance from camera to mesh along the forward vector
            let zDist;
            if (drawCall.calculateSortDistance) {
                zDist = drawCall.calculateSortDistance(drawCall, camPos, camFwd);
            } else {
                const meshPos = drawCall.aabb.center;
                zDist = (meshPos.x - px) * fx + (meshPos.y - py) * fy + (meshPos.z - pz) * fz;
            }
            // scale the bucket to give it a significantly higher magnitude than distance (1 billion)
            const bucket = drawCall._drawBucket * 1e9;
            // create sorting key based on the drawBucket and distance
            drawCall._sortKeyDynamic = bucket + zDist;
        }
    }
    /**
     * Get access to culled mesh instances for the provided camera.
     *
     * @param {Camera} camera - The camera.
     * @returns {CulledInstances} The culled mesh instances.
     * @ignore
     */ getCulledInstances(camera) {
        let instances = this._visibleInstances.get(camera);
        if (!instances) {
            instances = new CulledInstances();
            this._visibleInstances.set(camera, instances);
        }
        return instances;
    }
    /**
     * @param {Camera} camera - The camera to sort the visible mesh instances for.
     * @param {boolean} transparent - True if transparent sorting should be used.
     * @ignore
     */ sortVisible(camera, transparent) {
        const sortMode = transparent ? this.transparentSortMode : this.opaqueSortMode;
        if (sortMode === SORTMODE_NONE) {
            return;
        }
        const culledInstances = this.getCulledInstances(camera);
        const instances = transparent ? culledInstances.transparent : culledInstances.opaque;
        const cameraNode = camera.node;
        if (sortMode === SORTMODE_CUSTOM) {
            const sortPos = cameraNode.getPosition();
            const sortDir = cameraNode.forward;
            if (this.customCalculateSortValues) {
                this.customCalculateSortValues(instances, instances.length, sortPos, sortDir);
            }
            if (this.customSortCallback) {
                instances.sort(this.customSortCallback);
            }
        } else {
            if (sortMode === SORTMODE_BACK2FRONT || sortMode === SORTMODE_FRONT2BACK) {
                const sortPos = cameraNode.getPosition();
                const sortDir = cameraNode.forward;
                this._calculateSortDistances(instances, sortPos, sortDir);
            }
            instances.sort(sortCallbacks[sortMode]);
        }
    }
}

/**
 * @param {{priority: number}} a - First object with priority property.
 * @param {{priority: number}} b - Second object with priority property.
 * @returns {number} A number indicating the relative position.
 */ const cmpPriority = (a, b)=>a.priority - b.priority;
/**
 * @param {Array<{priority: number}>} arr - Array to be sorted in place where each element contains
 * an object with at least a priority property.
 * @returns {Array<{priority: number}>} In place sorted array.
 */ const sortPriority = (arr)=>arr.sort(cmpPriority);

/**
 * @import { CameraComponent } from '../../framework/components/camera/component.js'
 * @import { Layer } from '../layer.js'
 * @import { Camera } from '../camera.js'
 */ /**
 * Layer Composition is a collection of {@link Layer} that is fed to {@link Scene#layers} to define
 * rendering order.
 *
 * @category Graphics
 */ class LayerComposition extends EventHandler {
    /**
     * Create a new layer composition.
     *
     * @param {string} [name] - Optional non-unique name of the layer composition. Defaults to
     * "Untitled" if not specified.
     */ constructor(name = 'Untitled'){
        super(), // Composition can hold only 2 sublayers of each layer
        /**
     * A read-only array of {@link Layer} sorted in the order they will be rendered.
     *
     * @type {Layer[]}
     */ this.layerList = [], /**
     * A mapping of {@link Layer#id} to {@link Layer}.
     *
     * @type {Map<number, Layer>}
     * @ignore
     */ this.layerIdMap = new Map(), /**
     * A mapping of {@link Layer#name} to {@link Layer}.
     *
     * @type {Map<string, Layer>}
     * @ignore
     */ this.layerNameMap = new Map(), /**
     * A mapping of {@link Layer} to its opaque index in {@link LayerComposition#layerList}.
     *
     * @type {Map<Layer, number>}
     * @ignore
     */ this.layerOpaqueIndexMap = new Map(), /**
     * A mapping of {@link Layer} to its transparent index in {@link LayerComposition#layerList}.
     *
     * @type {Map<Layer, number>}
     * @ignore
     */ this.layerTransparentIndexMap = new Map(), /**
     * A read-only array of boolean values, matching {@link LayerComposition#layerList}. True means only
     * semi-transparent objects are rendered, and false means opaque.
     *
     * @type {boolean[]}
     * @ignore
     */ this.subLayerList = [], /**
     * A read-only array of boolean values, matching {@link LayerComposition#layerList}. True means the
     * layer is rendered, false means it's skipped.
     *
     * @type {boolean[]}
     */ this.subLayerEnabled = [] // more granular control on top of layer.enabled (ANDed)
        , /**
     * An array of {@link CameraComponent}s.
     *
     * @type {CameraComponent[]}
     * @ignore
     */ this.cameras = [], /**
     * A set of {@link Camera}s.
     *
     * @type {Set<Camera>}
     * @ignore
     */ this.camerasSet = new Set(), /**
     * The actual rendering sequence, generated based on layers and cameras
     *
     * @type {RenderAction[]}
     * @ignore
     */ this._renderActions = [], /**
     * True if the composition needs to be updated before rendering.
     *
     * @ignore
     */ this._dirty = false;
        this.name = name;
        this._opaqueOrder = {};
        this._transparentOrder = {};
    }
    destroy() {
        this.destroyRenderActions();
    }
    destroyRenderActions() {
        this._renderActions.forEach((ra)=>ra.destroy());
        this._renderActions.length = 0;
    }
    markDirty() {
        this._dirty = true;
    }
    _update() {
        const len = this.layerList.length;
        // if composition dirty flag is not set, test if layers are marked dirty
        if (!this._dirty) {
            for(let i = 0; i < len; i++){
                if (this.layerList[i]._dirtyComposition) {
                    this._dirty = true;
                    break;
                }
            }
        }
        if (this._dirty) {
            this._dirty = false;
            // walk the layers and build an array of unique cameras from all layers
            this.cameras.length = 0;
            this.camerasSet.clear();
            for(let i = 0; i < len; i++){
                const layer = this.layerList[i];
                layer._dirtyComposition = false;
                // for all cameras in the layer
                for(let j = 0; j < layer.cameras.length; j++){
                    const cameraComponent = layer.cameras[j];
                    if (!this.camerasSet.has(cameraComponent.camera)) {
                        this.camerasSet.add(cameraComponent.camera);
                        this.cameras.push(cameraComponent);
                    }
                }
            }
            // sort cameras by priority
            if (this.cameras.length > 1) {
                sortPriority(this.cameras);
            }
            // render in order of cameras sorted by priority
            let renderActionCount = 0;
            this.destroyRenderActions();
            for(let i = 0; i < this.cameras.length; i++){
                const camera = this.cameras[i];
                // if the camera uses custom render passes, only add a dummy render action to mark
                // the place where to add them during building of the frame graph
                if (camera.camera.renderPasses.length > 0) {
                    this.addDummyRenderAction(renderActionCount, camera);
                    renderActionCount++;
                    continue;
                }
                // first render action for this camera
                let cameraFirstRenderAction = true;
                const cameraFirstRenderActionIndex = renderActionCount;
                // last render action for the camera
                let lastRenderAction = null;
                // true if post processing stop layer was found for the camera
                let postProcessMarked = false;
                // walk all global sorted list of layers (sublayers) to check if camera renders it
                // this adds both opaque and transparent sublayers if camera renders the layer
                for(let j = 0; j < len; j++){
                    const layer = this.layerList[j];
                    const isLayerEnabled = layer.enabled && this.subLayerEnabled[j];
                    if (isLayerEnabled) {
                        // if layer needs to be rendered
                        if (layer.cameras.length > 0) {
                            // if the camera renders this layer
                            if (camera.layers.indexOf(layer.id) >= 0) {
                                // if this layer is the stop layer for postprocessing
                                if (!postProcessMarked && layer.id === camera.disablePostEffectsLayer) {
                                    postProcessMarked = true;
                                    // the previously added render action is the last post-processed layer
                                    if (lastRenderAction) {
                                        // mark it to trigger postprocessing callback
                                        lastRenderAction.triggerPostprocess = true;
                                    }
                                }
                                // add render action to describe rendering step
                                const isTransparent = this.subLayerList[j];
                                lastRenderAction = this.addRenderAction(renderActionCount, layer, isTransparent, camera, cameraFirstRenderAction, postProcessMarked);
                                renderActionCount++;
                                cameraFirstRenderAction = false;
                            }
                        }
                    }
                }
                // if the camera renders any layers.
                if (cameraFirstRenderActionIndex < renderActionCount) {
                    // mark the last render action as last one using the camera
                    lastRenderAction.lastCameraUse = true;
                }
                // if no render action for this camera was marked for end of postprocessing, mark last one
                if (!postProcessMarked && lastRenderAction) {
                    lastRenderAction.triggerPostprocess = true;
                }
                // handle camera stacking if this render action has postprocessing enabled
                if (camera.renderTarget && camera.postEffectsEnabled) {
                    // process previous render actions starting with previous camera
                    this.propagateRenderTarget(cameraFirstRenderActionIndex - 1, camera);
                }
            }
            this._logRenderActions();
        }
    }
    getNextRenderAction(renderActionIndex) {
        Debug.assert(this._renderActions.length === renderActionIndex);
        const renderAction = new RenderAction();
        this._renderActions.push(renderAction);
        return renderAction;
    }
    addDummyRenderAction(renderActionIndex, camera) {
        const renderAction = this.getNextRenderAction(renderActionIndex);
        renderAction.camera = camera;
        renderAction.useCameraPasses = true;
    }
    // function adds new render action to a list, while trying to limit allocation and reuse already allocated objects
    addRenderAction(renderActionIndex, layer, isTransparent, camera, cameraFirstRenderAction, postProcessMarked) {
        // camera's render target, ignoring depth layer
        let rt = layer.id !== LAYERID_DEPTH ? camera.renderTarget : null;
        // was camera and render target combo used already
        let used = false;
        const renderActions = this._renderActions;
        for(let i = renderActionIndex - 1; i >= 0; i--){
            if (renderActions[i].camera === camera && renderActions[i].renderTarget === rt) {
                used = true;
                break;
            }
        }
        // for cameras with post processing enabled, on layers after post processing has been applied already (so UI and similar),
        // don't render them to render target anymore
        if (postProcessMarked && camera.postEffectsEnabled) {
            rt = null;
        }
        // store the properties
        const renderAction = this.getNextRenderAction(renderActionIndex);
        renderAction.triggerPostprocess = false;
        renderAction.layer = layer;
        renderAction.transparent = isTransparent;
        renderAction.camera = camera;
        renderAction.renderTarget = rt;
        renderAction.firstCameraUse = cameraFirstRenderAction;
        renderAction.lastCameraUse = false;
        // clear flags - use camera clear flags in the first render action for each camera,
        // or when render target (from layer) was not yet cleared by this camera
        const needsCameraClear = cameraFirstRenderAction || !used;
        const needsLayerClear = layer.clearColorBuffer || layer.clearDepthBuffer || layer.clearStencilBuffer;
        if (needsCameraClear || needsLayerClear) {
            renderAction.setupClears(needsCameraClear ? camera : undefined, layer);
        }
        return renderAction;
    }
    // executes when post-processing camera's render actions were created to propagate rendering to
    // render targets to previous camera as needed
    propagateRenderTarget(startIndex, fromCamera) {
        for(let a = startIndex; a >= 0; a--){
            const ra = this._renderActions[a];
            const layer = ra.layer;
            // if we hit render action with a render target (other than depth layer), that marks the end of camera stack
            // TODO: refactor this as part of depth layer refactoring
            if (ra.renderTarget && layer.id !== LAYERID_DEPTH) {
                break;
            }
            // skip over depth layer
            if (layer.id === LAYERID_DEPTH) {
                continue;
            }
            // end of stacking if camera with custom render passes
            if (ra.useCameraPasses) {
                break;
            }
            // camera stack ends when viewport or scissor of the camera changes
            const thisCamera = ra?.camera.camera;
            if (thisCamera) {
                if (!fromCamera.camera.rect.equals(thisCamera.rect) || !fromCamera.camera.scissorRect.equals(thisCamera.scissorRect)) {
                    break;
                }
            }
            // render it to render target
            ra.renderTarget = fromCamera.renderTarget;
        }
    }
    // logs render action and their properties
    _logRenderActions() {
        if (Tracing.get(TRACEID_RENDER_ACTION)) {
            Debug.trace(TRACEID_RENDER_ACTION, `Render Actions for composition: ${this.name}`);
            for(let i = 0; i < this._renderActions.length; i++){
                const ra = this._renderActions[i];
                const camera = ra.camera;
                if (ra.useCameraPasses) {
                    Debug.trace(TRACEID_RENDER_ACTION, `${i}CustomPasses Cam: ${camera ? camera.entity.name : '-'}`);
                } else {
                    const layer = ra.layer;
                    const enabled = layer.enabled && this.isEnabled(layer, ra.transparent);
                    const clear = (ra.clearColor ? 'Color ' : '..... ') + (ra.clearDepth ? 'Depth ' : '..... ') + (ra.clearStencil ? 'Stencil' : '.......');
                    Debug.trace(TRACEID_RENDER_ACTION, `${i + ` Cam: ${camera ? camera.entity.name : '-'}`.padEnd(22, ' ') + ` Lay: ${layer.name}`.padEnd(22, ' ') + (ra.transparent ? ' TRANSP' : ' OPAQUE') + (enabled ? ' ENABLED ' : ' DISABLED') + ` RT: ${ra.renderTarget ? ra.renderTarget.name : '-'}`.padEnd(30, ' ')} Clear: ${clear}${ra.firstCameraUse ? ' CAM-FIRST' : ''}${ra.lastCameraUse ? ' CAM-LAST' : ''}${ra.triggerPostprocess ? ' POSTPROCESS' : ''}`);
                }
            }
        }
    }
    _isLayerAdded(layer) {
        const found = this.layerIdMap.get(layer.id) === layer;
        Debug.assert(!found, `Layer is already added: ${layer.name}`);
        return found;
    }
    _isSublayerAdded(layer, transparent) {
        const map = transparent ? this.layerTransparentIndexMap : this.layerOpaqueIndexMap;
        if (map.get(layer) !== undefined) {
            Debug.error(`Sublayer ${layer.name}, transparent: ${transparent} is already added.`);
            return true;
        }
        return false;
    }
    // Whole layer API
    /**
     * Adds a layer (both opaque and semi-transparent parts) to the end of the {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to add.
     */ push(layer) {
        // add both opaque and transparent to the end of the array
        if (this._isLayerAdded(layer)) return;
        this.layerList.push(layer);
        this.layerList.push(layer);
        this._opaqueOrder[layer.id] = this.subLayerList.push(false) - 1;
        this._transparentOrder[layer.id] = this.subLayerList.push(true) - 1;
        this.subLayerEnabled.push(true);
        this.subLayerEnabled.push(true);
        this._updateLayerMaps();
        this._dirty = true;
        this.fire('add', layer);
    }
    /**
     * Inserts a layer (both opaque and semi-transparent parts) at the chosen index in the
     * {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to add.
     * @param {number} index - Insertion position.
     */ insert(layer, index) {
        // insert both opaque and transparent at the index
        if (this._isLayerAdded(layer)) return;
        this.layerList.splice(index, 0, layer, layer);
        this.subLayerList.splice(index, 0, false, true);
        const count = this.layerList.length;
        this._updateOpaqueOrder(index, count - 1);
        this._updateTransparentOrder(index, count - 1);
        this.subLayerEnabled.splice(index, 0, true, true);
        this._updateLayerMaps();
        this._dirty = true;
        this.fire('add', layer);
    }
    /**
     * Removes a layer (both opaque and semi-transparent parts) from {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to remove.
     */ remove(layer) {
        // remove all occurrences of a layer
        let id = this.layerList.indexOf(layer);
        delete this._opaqueOrder[id];
        delete this._transparentOrder[id];
        while(id >= 0){
            this.layerList.splice(id, 1);
            this.subLayerList.splice(id, 1);
            this.subLayerEnabled.splice(id, 1);
            id = this.layerList.indexOf(layer);
            this._dirty = true;
            this.fire('remove', layer);
        }
        // update both orders
        const count = this.layerList.length;
        this._updateOpaqueOrder(0, count - 1);
        this._updateTransparentOrder(0, count - 1);
        this._updateLayerMaps();
    }
    // Sublayer API
    /**
     * Adds part of the layer with opaque (non semi-transparent) objects to the end of the
     * {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to add.
     */ pushOpaque(layer) {
        // add opaque to the end of the array
        if (this._isSublayerAdded(layer, false)) return;
        this.layerList.push(layer);
        this._opaqueOrder[layer.id] = this.subLayerList.push(false) - 1;
        this.subLayerEnabled.push(true);
        this._updateLayerMaps();
        this._dirty = true;
        this.fire('add', layer);
    }
    /**
     * Inserts an opaque part of the layer (non semi-transparent mesh instances) at the chosen
     * index in the {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to add.
     * @param {number} index - Insertion position.
     */ insertOpaque(layer, index) {
        // insert opaque at index
        if (this._isSublayerAdded(layer, false)) return;
        this.layerList.splice(index, 0, layer);
        this.subLayerList.splice(index, 0, false);
        const count = this.subLayerList.length;
        this._updateOpaqueOrder(index, count - 1);
        this.subLayerEnabled.splice(index, 0, true);
        this._updateLayerMaps();
        this._dirty = true;
        this.fire('add', layer);
    }
    /**
     * Removes an opaque part of the layer (non semi-transparent mesh instances) from
     * {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to remove.
     */ removeOpaque(layer) {
        // remove opaque occurrences of a layer
        for(let i = 0, len = this.layerList.length; i < len; i++){
            if (this.layerList[i] === layer && !this.subLayerList[i]) {
                this.layerList.splice(i, 1);
                this.subLayerList.splice(i, 1);
                len--;
                this._updateOpaqueOrder(i, len - 1);
                this.subLayerEnabled.splice(i, 1);
                this._dirty = true;
                if (this.layerList.indexOf(layer) < 0) {
                    this.fire('remove', layer); // no sublayers left
                }
                break;
            }
        }
        this._updateLayerMaps();
    }
    /**
     * Adds part of the layer with semi-transparent objects to the end of the {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to add.
     */ pushTransparent(layer) {
        // add transparent to the end of the array
        if (this._isSublayerAdded(layer, true)) return;
        this.layerList.push(layer);
        this._transparentOrder[layer.id] = this.subLayerList.push(true) - 1;
        this.subLayerEnabled.push(true);
        this._updateLayerMaps();
        this._dirty = true;
        this.fire('add', layer);
    }
    /**
     * Inserts a semi-transparent part of the layer at the chosen index in the {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to add.
     * @param {number} index - Insertion position.
     */ insertTransparent(layer, index) {
        // insert transparent at index
        if (this._isSublayerAdded(layer, true)) return;
        this.layerList.splice(index, 0, layer);
        this.subLayerList.splice(index, 0, true);
        const count = this.subLayerList.length;
        this._updateTransparentOrder(index, count - 1);
        this.subLayerEnabled.splice(index, 0, true);
        this._updateLayerMaps();
        this._dirty = true;
        this.fire('add', layer);
    }
    /**
     * Removes a transparent part of the layer from {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to remove.
     */ removeTransparent(layer) {
        // remove transparent occurrences of a layer
        for(let i = 0, len = this.layerList.length; i < len; i++){
            if (this.layerList[i] === layer && this.subLayerList[i]) {
                this.layerList.splice(i, 1);
                this.subLayerList.splice(i, 1);
                len--;
                this._updateTransparentOrder(i, len - 1);
                this.subLayerEnabled.splice(i, 1);
                this._dirty = true;
                if (this.layerList.indexOf(layer) < 0) {
                    this.fire('remove', layer); // no sublayers left
                }
                break;
            }
        }
        this._updateLayerMaps();
    }
    /**
     * Gets index of the opaque part of the supplied layer in the {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to find index of.
     * @returns {number} The index of the opaque part of the specified layer, or -1 if it is not
     * part of the composition.
     */ getOpaqueIndex(layer) {
        return this.layerOpaqueIndexMap.get(layer) ?? -1;
    }
    /**
     * Gets index of the semi-transparent part of the supplied layer in the {@link LayerComposition#layerList}.
     *
     * @param {Layer} layer - A {@link Layer} to find index of.
     * @returns {number} The index of the semi-transparent part of the specified layer, or -1 if it
     * is not part of the composition.
     */ getTransparentIndex(layer) {
        return this.layerTransparentIndexMap.get(layer) ?? -1;
    }
    isEnabled(layer, transparent) {
        if (layer.enabled) {
            const index = transparent ? this.getTransparentIndex(layer) : this.getOpaqueIndex(layer);
            if (index >= 0) {
                return this.subLayerEnabled[index];
            }
        }
        return false;
    }
    /**
     * Update maps of layer IDs and names to match the layer list.
     *
     * @private
     */ _updateLayerMaps() {
        this.layerIdMap.clear();
        this.layerNameMap.clear();
        this.layerOpaqueIndexMap.clear();
        this.layerTransparentIndexMap.clear();
        for(let i = 0; i < this.layerList.length; i++){
            const layer = this.layerList[i];
            this.layerIdMap.set(layer.id, layer);
            this.layerNameMap.set(layer.name, layer);
            const subLayerIndexMap = this.subLayerList[i] ? this.layerTransparentIndexMap : this.layerOpaqueIndexMap;
            subLayerIndexMap.set(layer, i);
        }
    }
    /**
     * Finds a layer inside this composition by its ID. Null is returned, if nothing is found.
     *
     * @param {number} id - An ID of the layer to find.
     * @returns {Layer|null} The layer corresponding to the specified ID. Returns null if layer is
     * not found.
     */ getLayerById(id) {
        return this.layerIdMap.get(id) ?? null;
    }
    /**
     * Finds a layer inside this composition by its name. Null is returned, if nothing is found.
     *
     * @param {string} name - The name of the layer to find.
     * @returns {Layer|null} The layer corresponding to the specified name. Returns null if layer
     * is not found.
     */ getLayerByName(name) {
        return this.layerNameMap.get(name) ?? null;
    }
    _updateOpaqueOrder(startIndex, endIndex) {
        for(let i = startIndex; i <= endIndex; i++){
            if (this.subLayerList[i] === false) {
                this._opaqueOrder[this.layerList[i].id] = i;
            }
        }
    }
    _updateTransparentOrder(startIndex, endIndex) {
        for(let i = startIndex; i <= endIndex; i++){
            if (this.subLayerList[i] === true) {
                this._transparentOrder[this.layerList[i].id] = i;
            }
        }
    }
    // Used to determine which array of layers has any sublayer that is
    // on top of all the sublayers in the other array. The order is a dictionary
    // of <layerId, index>.
    _sortLayersDescending(layersA, layersB, order) {
        let topLayerA = -1;
        let topLayerB = -1;
        // search for which layer is on top in layersA
        for(let i = 0, len = layersA.length; i < len; i++){
            const id = layersA[i];
            if (order.hasOwnProperty(id)) {
                topLayerA = Math.max(topLayerA, order[id]);
            }
        }
        // search for which layer is on top in layersB
        for(let i = 0, len = layersB.length; i < len; i++){
            const id = layersB[i];
            if (order.hasOwnProperty(id)) {
                topLayerB = Math.max(topLayerB, order[id]);
            }
        }
        // if the layers of layersA or layersB do not exist at all
        // in the composition then return early with the other.
        if (topLayerA === -1 && topLayerB !== -1) {
            return 1;
        } else if (topLayerB === -1 && topLayerA !== -1) {
            return -1;
        }
        // sort in descending order since we want
        // the higher order to be first
        return topLayerB - topLayerA;
    }
    /**
     * Used to determine which array of layers has any transparent sublayer that is on top of all
     * the transparent sublayers in the other array.
     *
     * @param {number[]} layersA - IDs of layers.
     * @param {number[]} layersB - IDs of layers.
     * @returns {number} Returns a negative number if any of the transparent sublayers in layersA
     * is on top of all the transparent sublayers in layersB, or a positive number if any of the
     * transparent sublayers in layersB is on top of all the transparent sublayers in layersA, or 0
     * otherwise.
     * @private
     */ sortTransparentLayers(layersA, layersB) {
        return this._sortLayersDescending(layersA, layersB, this._transparentOrder);
    }
    /**
     * Used to determine which array of layers has any opaque sublayer that is on top of all the
     * opaque sublayers in the other array.
     *
     * @param {number[]} layersA - IDs of layers.
     * @param {number[]} layersB - IDs of layers.
     * @returns {number} Returns a negative number if any of the opaque sublayers in layersA is on
     * top of all the opaque sublayers in layersB, or a positive number if any of the opaque
     * sublayers in layersB is on top of all the opaque sublayers in layersA, or 0 otherwise.
     * @private
     */ sortOpaqueLayers(layersA, layersB) {
        return this._sortLayersDescending(layersA, layersB, this._opaqueOrder);
    }
}

/**
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 * @import { EventHandle } from '../core/event-handle.js';
 */ /**
 * @import { BindGroup } from '../platform/graphics/bind-group.js'
 * @import { Layer } from './layer.js'
 */ const tmpVec = new Vec3();
const tmpBiases = {
    bias: 0,
    normalBias: 0
};
const tmpColor = new Color();
const chanId = {
    r: 0,
    g: 1,
    b: 2,
    a: 3
};
const lightTypes = {
    'directional': LIGHTTYPE_DIRECTIONAL,
    'omni': LIGHTTYPE_OMNI,
    'point': LIGHTTYPE_OMNI,
    'spot': LIGHTTYPE_SPOT
};
// viewport in shadows map for cascades for directional light
const directionalCascades = [
    [
        new Vec4(0, 0, 1, 1)
    ],
    [
        new Vec4(0, 0, 0.5, 0.5),
        new Vec4(0, 0.5, 0.5, 0.5)
    ],
    [
        new Vec4(0, 0, 0.5, 0.5),
        new Vec4(0, 0.5, 0.5, 0.5),
        new Vec4(0.5, 0, 0.5, 0.5)
    ],
    [
        new Vec4(0, 0, 0.5, 0.5),
        new Vec4(0, 0.5, 0.5, 0.5),
        new Vec4(0.5, 0, 0.5, 0.5),
        new Vec4(0.5, 0.5, 0.5, 0.5)
    ]
];
const channelMap = {
    'rrr': 0b0001,
    'ggg': 0b0010,
    'bbb': 0b0100,
    'aaa': 0b1000,
    'rgb': 0b0111
};
let id$2 = 0;
/**
 * Class storing shadow rendering related private information
 */ class LightRenderData {
    constructor(camera, face, light){
        // light this data belongs to
        this.light = light;
        // camera this applies to. Only used by directional light, as directional shadow map
        // is culled and rendered for each camera. Local lights' shadow is culled and rendered one time
        // and shared between cameras (even though it's not strictly correct and we can get shadows
        // from a mesh that is not visible by the camera)
        this.camera = camera;
        // camera used to cull / render the shadow map
        this.shadowCamera = ShadowRenderer.createShadowCamera(light._shadowType, light._type, face);
        // shadow view-projection matrix
        this.shadowMatrix = new Mat4();
        // viewport for the shadow rendering to the texture (x, y, width, height)
        this.shadowViewport = new Vec4(0, 0, 1, 1);
        // scissor rectangle for the shadow rendering to the texture (x, y, width, height)
        this.shadowScissor = new Vec4(0, 0, 1, 1);
        // depth range compensation for PCSS with directional lights
        this.projectionCompensation = 0;
        // face index, value is based on light type:
        // - spot: always 0
        // - omni: cubemap face, 0..5
        // - directional: 0 for simple shadows, cascade index for cascaded shadow map
        this.face = face;
        // visible shadow casters
        this.visibleCasters = [];
        // an array of view bind groups, single entry is used for shadows
        /** @type {BindGroup[]} */ this.viewBindGroups = [];
    }
    // releases GPU resources
    destroy() {
        this.viewBindGroups.forEach((bg)=>{
            bg.defaultUniformBuffer.destroy();
            bg.destroy();
        });
        this.viewBindGroups.length = 0;
    }
    // returns shadow buffer currently attached to the shadow camera
    get shadowBuffer() {
        const rt = this.shadowCamera.renderTarget;
        if (rt) {
            return this.light._isPcf ? rt.depthBuffer : rt.colorBuffer;
        }
        return null;
    }
}
/**
 * A light.
 *
 * @ignore
 */ class Light {
    /**
     * @param {GraphicsDevice} graphicsDevice - The graphics device.
     * @param {boolean} clusteredLighting - True if the clustered lighting is enabled.
     */ constructor(graphicsDevice, clusteredLighting){
        /**
     * The Layers the light is on.
     *
     * @type {Set<Layer>}
     */ this.layers = new Set();
        /**
     * The depth state used when rendering the shadow map.
     *
     * @type {DepthState}
     */ this.shadowDepthState = DepthState.DEFAULT.clone();
        /**
     * The flags used for clustered lighting. Stored as a bitfield, updated as properties change to
     * avoid those being updated each frame.
     *
     * @type {number}
     * @ignore
     */ this.clusteredFlags = 0;
        /**
     * Storage data for light properties encoded as a Uint32Array.
     *
     * @type {Uint32Array}
     * @ignore
     */ this.clusteredData = new Uint32Array(3);
        /**
     * Alias for clusteredData using 16bit unsigned integers.
     *
     * @type {Uint16Array}
     * @ignore
     */ this.clusteredData16 = new Uint16Array(this.clusteredData.buffer);
        /**
     * Event handle for device restored event.
     *
     * @type {EventHandle|null}
     * @private
     */ this._evtDeviceRestored = null;
        this.device = graphicsDevice;
        this.clusteredLighting = clusteredLighting;
        this.id = id$2++;
        this._evtDeviceRestored = graphicsDevice.on('devicerestored', this.onDeviceRestored, this);
        // Light properties (defaults)
        this._type = LIGHTTYPE_DIRECTIONAL;
        this._color = new Color(0.8, 0.8, 0.8); // color in sRGB space
        this._intensity = 1;
        this._affectSpecularity = true;
        this._luminance = 0;
        this._castShadows = false;
        this._enabled = false;
        this._mask = MASK_AFFECT_DYNAMIC;
        this.isStatic = false;
        this.key = 0;
        this.bakeDir = true;
        this.bakeNumSamples = 1;
        this.bakeArea = 0;
        // Omni and spot properties
        this.attenuationStart = 10;
        this.attenuationEnd = 10;
        this._falloffMode = LIGHTFALLOFF_LINEAR;
        this._shadowType = SHADOW_PCF3_32F;
        this._vsmBlurSize = 11;
        this.vsmBlurMode = BLUR_GAUSSIAN;
        this.vsmBias = 0.01 * 0.25;
        this._cookie = null; // light cookie texture (2D for spot, cubemap for omni)
        this.cookieIntensity = 1;
        this._cookieFalloff = true;
        this._cookieChannel = 'rgb';
        this._cookieTransform = null; // 2d rotation/scale matrix (spot only)
        this._cookieTransformUniform = new Float32Array(4);
        this._cookieOffset = null; // 2d position offset (spot only)
        this._cookieOffsetUniform = new Float32Array(2);
        this._cookieTransformSet = false;
        this._cookieOffsetSet = false;
        // Spot properties
        this._innerConeAngle = 40;
        this._outerConeAngle = 45;
        // Directional properties
        this.cascades = null; // an array of Vec4 viewports per cascade
        this._shadowMatrixPalette = null; // a float array, 16 floats per cascade
        this._shadowCascadeDistances = null;
        this.numCascades = 1;
        this._cascadeBlend = 0;
        this.cascadeDistribution = 0.5;
        // Light source shape properties
        this._shape = LIGHTSHAPE_PUNCTUAL;
        // light color and intensity in the linear space
        this._colorLinear = new Float32Array(3);
        this._updateLinearColor();
        this._position = new Vec3(0, 0, 0);
        this._direction = new Vec3(0, 0, 0);
        this._innerConeAngleCos = Math.cos(this._innerConeAngle * Math.PI / 180);
        this._updateOuterAngle(this._outerConeAngle);
        this._usePhysicalUnits = undefined;
        // Shadow mapping resources
        this._shadowMap = null;
        this._shadowRenderParams = [];
        this._shadowCameraParams = [];
        // Shadow mapping properties
        this.shadowDistance = 40;
        this._shadowResolution = 1024;
        this._shadowBias = -5e-4;
        this._shadowIntensity = 1.0;
        this._normalOffsetBias = 0.0;
        this.shadowUpdateMode = SHADOWUPDATE_REALTIME;
        this.shadowUpdateOverrides = null;
        this._isVsm = false;
        this._isPcf = true;
        this._softShadowParams = new Float32Array(4);
        this.shadowSamples = 16;
        this.shadowBlockerSamples = 16;
        this.penumbraSize = 1.0;
        this.penumbraFalloff = 1.0;
        // cookie matrix (used in case the shadow mapping is disabled and so the shadow matrix cannot be used)
        this._cookieMatrix = null;
        // viewport of the cookie texture / shadow in the atlas
        this._atlasViewport = null;
        this.atlasViewportAllocated = false; // if true, atlas slot is allocated for the current frame
        this.atlasVersion = 0; // version of the atlas for the allocated slot, allows invalidation when atlas recreates slots
        this.atlasSlotIndex = 0; // allocated slot index, used for more persistent slot allocation
        this.atlasSlotUpdated = false; // true if the atlas slot was reassigned this frame (and content needs to be updated)
        this._node = null;
        // private rendering data
        this._renderData = [];
        // true if the light is visible by any camera within a frame
        this.visibleThisFrame = false;
        // maximum size of the light bounding sphere on the screen by any camera within a frame
        // (used to estimate shadow resolution), range [0..1]
        this.maxScreenSize = 0;
        this._updateShadowBias();
    }
    destroy() {
        this._evtDeviceRestored?.off();
        this._evtDeviceRestored = null;
        this._destroyShadowMap();
        this.releaseRenderData();
        this._renderData = null;
    }
    onDeviceRestored() {
        // when context is restored, re-render shadow map
        if (this.shadowUpdateMode === SHADOWUPDATE_NONE) {
            this.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
        }
    }
    releaseRenderData() {
        if (this._renderData) {
            for(let i = 0; i < this._renderData.length; i++){
                this._renderData[i].destroy();
            }
            this._renderData.length = 0;
        }
    }
    addLayer(layer) {
        this.layers.add(layer);
    }
    removeLayer(layer) {
        this.layers.delete(layer);
    }
    set shadowSamples(value) {
        this._softShadowParams[0] = value;
    }
    get shadowSamples() {
        return this._softShadowParams[0];
    }
    set shadowBlockerSamples(value) {
        this._softShadowParams[1] = value;
    }
    get shadowBlockerSamples() {
        return this._softShadowParams[1];
    }
    set shadowBias(value) {
        if (this._shadowBias !== value) {
            this._shadowBias = value;
            this._updateShadowBias();
        }
    }
    get shadowBias() {
        return this._shadowBias;
    }
    set numCascades(value) {
        if (!this.cascades || this.numCascades !== value) {
            this.cascades = directionalCascades[value - 1];
            this._shadowMatrixPalette = new Float32Array(4 * 16); // always 4
            this._shadowCascadeDistances = new Float32Array(4); // always 4
            this._destroyShadowMap();
            this.updateKey();
        }
    }
    get numCascades() {
        return this.cascades.length;
    }
    set cascadeBlend(value) {
        if (this._cascadeBlend !== value) {
            this._cascadeBlend = value;
            this.updateKey();
        }
    }
    get cascadeBlend() {
        return this._cascadeBlend;
    }
    set shadowMap(shadowMap) {
        if (this._shadowMap !== shadowMap) {
            this._destroyShadowMap();
            this._shadowMap = shadowMap;
        }
    }
    get shadowMap() {
        return this._shadowMap;
    }
    set mask(value) {
        if (this._mask !== value) {
            this._mask = value;
            this.updateKey();
            this.updateClusteredFlags();
        }
    }
    get mask() {
        return this._mask;
    }
    // returns number of render targets to render the shadow map
    get numShadowFaces() {
        const type = this._type;
        if (type === LIGHTTYPE_DIRECTIONAL) {
            return this.numCascades;
        } else if (type === LIGHTTYPE_OMNI) {
            return 6;
        }
        return 1;
    }
    set type(value) {
        if (this._type === value) {
            return;
        }
        this._type = value;
        this._destroyShadowMap();
        this._updateShadowBias();
        this.updateKey();
        this.updateClusteredFlags();
        const stype = this._shadowType;
        this._shadowType = null;
        this.shadowUpdateOverrides = null;
        this.shadowType = stype; // refresh shadow type; switching from direct/spot to omni and back may change it
    }
    get type() {
        return this._type;
    }
    set shape(value) {
        if (this._shape === value) {
            return;
        }
        this._shape = value;
        this._destroyShadowMap();
        this.updateKey();
        this.updateClusteredFlags();
        const stype = this._shadowType;
        this._shadowType = null;
        this.shadowType = stype; // refresh shadow type; switching shape and back may change it
    }
    get shape() {
        return this._shape;
    }
    set usePhysicalUnits(value) {
        if (this._usePhysicalUnits !== value) {
            this._usePhysicalUnits = value;
            this._updateLinearColor();
        }
    }
    get usePhysicalUnits() {
        return this._usePhysicalUnits;
    }
    set shadowType(value) {
        if (this._shadowType === value) {
            return;
        }
        // unsupported shadow type
        let shadowInfo = shadowTypeInfo.get(value);
        if (!shadowInfo) {
            value = SHADOW_PCF3_32F;
        }
        const device = this.device;
        // PCSS requires F16 or F32 render targets
        if (value === SHADOW_PCSS_32F && !device.textureFloatRenderable && !device.textureHalfFloatRenderable) {
            value = SHADOW_PCF3_32F;
        }
        // omni light supports PCF1, PCF3 and PCSS only
        if (this._type === LIGHTTYPE_OMNI && value !== SHADOW_PCF1_32F && value !== SHADOW_PCF3_32F && value !== SHADOW_PCF1_16F && value !== SHADOW_PCF3_16F && value !== SHADOW_PCSS_32F) {
            value = SHADOW_PCF3_32F;
        }
        // fallback from vsm32 to vsm16
        if (value === SHADOW_VSM_32F && (!device.textureFloatRenderable || !device.textureFloatFilterable)) {
            value = SHADOW_VSM_16F;
        }
        // fallback from vsm16 to pcf3
        if (value === SHADOW_VSM_16F && !device.textureHalfFloatRenderable) {
            value = SHADOW_PCF3_32F;
        }
        shadowInfo = shadowTypeInfo.get(value);
        this._isVsm = shadowInfo?.vsm ?? false;
        this._isPcf = shadowInfo?.pcf ?? false;
        this._shadowType = value;
        this._destroyShadowMap();
        this.updateKey();
    }
    get shadowType() {
        return this._shadowType;
    }
    set enabled(value) {
        if (this._enabled !== value) {
            this._enabled = value;
            this.layersDirty();
        }
    }
    get enabled() {
        return this._enabled;
    }
    set castShadows(value) {
        if (this._castShadows !== value) {
            this._castShadows = value;
            this._destroyShadowMap();
            this.layersDirty();
            this.updateKey();
        }
    }
    get castShadows() {
        return this._castShadows && this._mask !== MASK_BAKE && this._mask !== 0;
    }
    set shadowIntensity(value) {
        if (this._shadowIntensity !== value) {
            this._shadowIntensity = value;
            this.updateKey();
        }
    }
    get shadowIntensity() {
        return this._shadowIntensity;
    }
    get bakeShadows() {
        return this._castShadows && this._mask === MASK_BAKE;
    }
    set shadowResolution(value) {
        if (this._shadowResolution !== value) {
            if (this._type === LIGHTTYPE_OMNI) {
                value = Math.min(value, this.device.maxCubeMapSize);
            } else {
                value = Math.min(value, this.device.maxTextureSize);
            }
            this._shadowResolution = value;
            this._destroyShadowMap();
        }
    }
    get shadowResolution() {
        return this._shadowResolution;
    }
    set vsmBlurSize(value) {
        if (this._vsmBlurSize === value) {
            return;
        }
        if (value % 2 === 0) value++; // don't allow even size
        this._vsmBlurSize = value;
    }
    get vsmBlurSize() {
        return this._vsmBlurSize;
    }
    set normalOffsetBias(value) {
        if (this._normalOffsetBias !== value) {
            const dirty = !this._normalOffsetBias && value || this._normalOffsetBias && !value;
            this._normalOffsetBias = value;
            if (dirty) {
                this.updateKey();
            }
        }
    }
    get normalOffsetBias() {
        return this._normalOffsetBias;
    }
    set falloffMode(value) {
        if (this._falloffMode === value) {
            return;
        }
        this._falloffMode = value;
        this.updateKey();
        this.updateClusteredFlags();
    }
    get falloffMode() {
        return this._falloffMode;
    }
    set innerConeAngle(value) {
        if (this._innerConeAngle === value) {
            return;
        }
        this._innerConeAngle = value;
        this._innerConeAngleCos = Math.cos(value * Math.PI / 180);
        this.updateClusterData(false, true);
        if (this._usePhysicalUnits) {
            this._updateLinearColor();
        }
    }
    get innerConeAngle() {
        return this._innerConeAngle;
    }
    set outerConeAngle(value) {
        if (this._outerConeAngle === value) {
            return;
        }
        this._outerConeAngle = value;
        this._updateOuterAngle(value);
        if (this._usePhysicalUnits) {
            this._updateLinearColor();
        }
    }
    get outerConeAngle() {
        return this._outerConeAngle;
    }
    set penumbraSize(value) {
        this._penumbraSize = value;
        this._softShadowParams[2] = value;
    }
    get penumbraSize() {
        return this._penumbraSize;
    }
    set penumbraFalloff(value) {
        this._softShadowParams[3] = value;
    }
    get penumbraFalloff() {
        return this._softShadowParams[3];
    }
    _updateOuterAngle(angle) {
        const radAngle = angle * Math.PI / 180;
        this._outerConeAngleCos = Math.cos(radAngle);
        this._outerConeAngleSin = Math.sin(radAngle);
        this.updateClusterData(false, true);
    }
    set intensity(value) {
        if (this._intensity !== value) {
            this._intensity = value;
            this._updateLinearColor();
        }
    }
    get intensity() {
        return this._intensity;
    }
    set affectSpecularity(value) {
        if (this._type === LIGHTTYPE_DIRECTIONAL) {
            this._affectSpecularity = value;
            this.updateKey();
        }
    }
    get affectSpecularity() {
        return this._affectSpecularity;
    }
    set luminance(value) {
        if (this._luminance !== value) {
            this._luminance = value;
            this._updateLinearColor();
        }
    }
    get luminance() {
        return this._luminance;
    }
    get cookieMatrix() {
        if (!this._cookieMatrix) {
            this._cookieMatrix = new Mat4();
        }
        return this._cookieMatrix;
    }
    get atlasViewport() {
        if (!this._atlasViewport) {
            this._atlasViewport = new Vec4(0, 0, 1, 1);
        }
        return this._atlasViewport;
    }
    set cookie(value) {
        if (this._cookie === value) {
            return;
        }
        this._cookie = value;
        this.updateKey();
    }
    get cookie() {
        return this._cookie;
    }
    set cookieFalloff(value) {
        if (this._cookieFalloff === value) {
            return;
        }
        this._cookieFalloff = value;
        this.updateKey();
    }
    get cookieFalloff() {
        return this._cookieFalloff;
    }
    set cookieChannel(value) {
        if (this._cookieChannel === value) {
            return;
        }
        if (value.length < 3) {
            const chr = value.charAt(value.length - 1);
            const addLen = 3 - value.length;
            for(let i = 0; i < addLen; i++){
                value += chr;
            }
        }
        this._cookieChannel = value;
        this.updateKey();
        this.updateClusteredFlags();
    }
    get cookieChannel() {
        return this._cookieChannel;
    }
    set cookieTransform(value) {
        if (this._cookieTransform === value) {
            return;
        }
        this._cookieTransform = value;
        this._cookieTransformSet = !!value;
        if (value && !this._cookieOffset) {
            this.cookieOffset = new Vec2(); // using transform forces using offset code
            this._cookieOffsetSet = false;
        }
        this.updateKey();
    }
    get cookieTransform() {
        return this._cookieTransform;
    }
    set cookieOffset(value) {
        if (this._cookieOffset === value) {
            return;
        }
        const xformNew = !!(this._cookieTransformSet || value);
        if (xformNew && !value && this._cookieOffset) {
            this._cookieOffset.set(0, 0);
        } else {
            this._cookieOffset = value;
        }
        this._cookieOffsetSet = !!value;
        if (value && !this._cookieTransform) {
            this.cookieTransform = new Vec4(1, 1, 0, 0); // using offset forces using matrix code
            this._cookieTransformSet = false;
        }
        this.updateKey();
    }
    get cookieOffset() {
        return this._cookieOffset;
    }
    // prepares light for the frame rendering
    beginFrame() {
        this.visibleThisFrame = this._type === LIGHTTYPE_DIRECTIONAL && this._enabled;
        this.maxScreenSize = 0;
        this.atlasViewportAllocated = false;
        this.atlasSlotUpdated = false;
    }
    // destroys shadow map related resources, called when shadow properties change and resources
    // need to be recreated
    _destroyShadowMap() {
        this.releaseRenderData();
        if (this._shadowMap) {
            if (!this._shadowMap.cached) {
                this._shadowMap.destroy();
            }
            this._shadowMap = null;
        }
        if (this.shadowUpdateMode === SHADOWUPDATE_NONE) {
            this.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
        }
        if (this.shadowUpdateOverrides) {
            for(let i = 0; i < this.shadowUpdateOverrides.length; i++){
                if (this.shadowUpdateOverrides[i] === SHADOWUPDATE_NONE) {
                    this.shadowUpdateOverrides[i] = SHADOWUPDATE_THISFRAME;
                }
            }
        }
    }
    // returns LightRenderData with matching camera and face
    getRenderData(camera, face) {
        // returns existing
        for(let i = 0; i < this._renderData.length; i++){
            const current = this._renderData[i];
            if (current.camera === camera && current.face === face) {
                return current;
            }
        }
        // create new one
        const rd = new LightRenderData(camera, face, this);
        this._renderData.push(rd);
        return rd;
    }
    /**
     * Duplicates a light node but does not 'deep copy' the hierarchy.
     *
     * @returns {Light} A cloned Light.
     */ clone() {
        const clone = new Light(this.device, this.clusteredLighting);
        // Clone Light properties
        clone.type = this._type;
        clone.setColor(this._color);
        clone.intensity = this._intensity;
        clone.affectSpecularity = this._affectSpecularity;
        clone.luminance = this._luminance;
        clone.castShadows = this.castShadows;
        clone._enabled = this._enabled;
        // Omni and spot properties
        clone.attenuationStart = this.attenuationStart;
        clone.attenuationEnd = this.attenuationEnd;
        clone.falloffMode = this._falloffMode;
        clone.shadowType = this._shadowType;
        clone.vsmBlurSize = this._vsmBlurSize;
        clone.vsmBlurMode = this.vsmBlurMode;
        clone.vsmBias = this.vsmBias;
        clone.shadowUpdateMode = this.shadowUpdateMode;
        clone.mask = this.mask;
        if (this.shadowUpdateOverrides) {
            clone.shadowUpdateOverrides = this.shadowUpdateOverrides.slice();
        }
        // Spot properties
        clone.innerConeAngle = this._innerConeAngle;
        clone.outerConeAngle = this._outerConeAngle;
        // Directional properties
        clone.numCascades = this.numCascades;
        clone.cascadeDistribution = this.cascadeDistribution;
        clone.cascadeBlend = this._cascadeBlend;
        // shape properties
        clone.shape = this._shape;
        // Shadow properties
        clone.shadowDepthState.copy(this.shadowDepthState);
        clone.shadowBias = this.shadowBias;
        clone.normalOffsetBias = this._normalOffsetBias;
        clone.shadowResolution = this._shadowResolution;
        clone.shadowDistance = this.shadowDistance;
        clone.shadowIntensity = this.shadowIntensity;
        clone.shadowSamples = this.shadowSamples;
        clone.shadowBlockerSamples = this.shadowBlockerSamples;
        clone.penumbraSize = this.penumbraSize;
        clone.penumbraFalloff = this.penumbraFalloff;
        // Cookies properties
        // clone.cookie = this._cookie;
        // clone.cookieIntensity = this.cookieIntensity;
        // clone.cookieFalloff = this._cookieFalloff;
        // clone.cookieChannel = this._cookieChannel;
        // clone.cookieTransform = this._cookieTransform;
        // clone.cookieOffset = this._cookieOffset;
        return clone;
    }
    /**
     * Get conversion factor for luminance -> light specific light unit.
     *
     * @param {number} type - The type of light.
     * @param {number} [outerAngle] - The outer angle of a spot light.
     * @param {number} [innerAngle] - The inner angle of a spot light.
     * @returns {number} The scaling factor to multiply with the luminance value.
     */ static getLightUnitConversion(type, outerAngle = Math.PI / 4, innerAngle = 0) {
        switch(type){
            case LIGHTTYPE_SPOT:
                {
                    const falloffEnd = Math.cos(outerAngle);
                    const falloffStart = Math.cos(innerAngle);
                    // https://github.com/mmp/pbrt-v4/blob/faac34d1a0ebd24928828fe9fa65b65f7efc5937/src/pbrt/lights.cpp#L1463
                    return 2 * Math.PI * (1 - falloffStart + (falloffStart - falloffEnd) / 2.0);
                }
            case LIGHTTYPE_OMNI:
                // https://google.github.io/filament/Filament.md.html#lighting/directlighting/punctuallights/pointlights
                return 4 * Math.PI;
            case LIGHTTYPE_DIRECTIONAL:
                // https://google.github.io/filament/Filament.md.html#lighting/directlighting/directionallights
                return 1;
        }
    }
    // returns the bias (.x) and normalBias (.y) value for lights as passed to shaders by uniforms
    // Note: this needs to be revisited and simplified
    // Note: vsmBias is not used at all for omni light, even though it is editable in the Editor
    _getUniformBiasValues(lightRenderData) {
        const farClip = lightRenderData.shadowCamera._farClip;
        switch(this._type){
            case LIGHTTYPE_OMNI:
                tmpBiases.bias = this.shadowBias;
                tmpBiases.normalBias = this._normalOffsetBias;
                break;
            case LIGHTTYPE_SPOT:
                if (this._isVsm) {
                    tmpBiases.bias = -1e-5 * 20;
                } else {
                    tmpBiases.bias = this.shadowBias * 20; // approx remap from old bias values
                }
                tmpBiases.normalBias = this._isVsm ? this.vsmBias / (this.attenuationEnd / 7.0) : this._normalOffsetBias;
                break;
            case LIGHTTYPE_DIRECTIONAL:
                // make bias dependent on far plane because it's not constant for direct light
                // clip distance used is based on the nearest shadow cascade
                if (this._isVsm) {
                    tmpBiases.bias = -1e-5 * 20;
                } else {
                    tmpBiases.bias = this.shadowBias / farClip * 100;
                }
                tmpBiases.normalBias = this._isVsm ? this.vsmBias / (farClip / 7.0) : this._normalOffsetBias;
                break;
        }
        return tmpBiases;
    }
    getColor() {
        return this._color;
    }
    getBoundingSphere(sphere) {
        if (this._type === LIGHTTYPE_SPOT) {
            // based on https://bartwronski.com/2017/04/13/cull-that-cone/
            const size = this.attenuationEnd;
            const angle = this._outerConeAngle;
            const cosAngle = this._outerConeAngleCos;
            const node = this._node;
            tmpVec.copy(node.up);
            if (angle > 45) {
                sphere.radius = size * this._outerConeAngleSin;
                tmpVec.mulScalar(-size * cosAngle);
            } else {
                sphere.radius = size / (2 * cosAngle);
                tmpVec.mulScalar(-sphere.radius);
            }
            sphere.center.add2(node.getPosition(), tmpVec);
        } else if (this._type === LIGHTTYPE_OMNI) {
            sphere.center = this._node.getPosition();
            sphere.radius = this.attenuationEnd;
        }
    }
    getBoundingBox(box) {
        if (this._type === LIGHTTYPE_SPOT) {
            const range = this.attenuationEnd;
            const angle = this._outerConeAngle;
            const node = this._node;
            const scl = Math.abs(Math.sin(angle * math.DEG_TO_RAD) * range);
            box.center.set(0, -range * 0.5, 0);
            box.halfExtents.set(scl, range * 0.5, scl);
            box.setFromTransformedAabb(box, node.getWorldTransform(), true);
        } else if (this._type === LIGHTTYPE_OMNI) {
            box.center.copy(this._node.getPosition());
            box.halfExtents.set(this.attenuationEnd, this.attenuationEnd, this.attenuationEnd);
        }
    }
    _updateShadowBias() {
        if (this._type === LIGHTTYPE_OMNI && !this.clusteredLighting) {
            this.shadowDepthState.depthBias = 0;
            this.shadowDepthState.depthBiasSlope = 0;
        } else {
            const bias = this.shadowBias * -1e3;
            this.shadowDepthState.depthBias = bias;
            this.shadowDepthState.depthBiasSlope = bias;
        }
    }
    _updateLinearColor() {
        let intensity = this._intensity;
        // To calculate the lux, which is lm/m^2, we need to convert from luminous power
        if (this._usePhysicalUnits) {
            intensity = this._luminance / Light.getLightUnitConversion(this._type, this._outerConeAngle * math.DEG_TO_RAD, this._innerConeAngle * math.DEG_TO_RAD);
        }
        // Note: This is slightly unconventional, ideally we'd convert color to linear space and then
        // multiply by intensity, but keeping this for backwards compatibility
        const color = this._color;
        const colorLinear = this._colorLinear;
        if (intensity >= 1) {
            tmpColor.linear(color).mulScalar(intensity);
        } else {
            tmpColor.copy(color).mulScalar(intensity).linear();
        }
        colorLinear[0] = tmpColor.r;
        colorLinear[1] = tmpColor.g;
        colorLinear[2] = tmpColor.b;
        this.updateClusterData(true);
    }
    setColor() {
        if (arguments.length === 1) {
            this._color.set(arguments[0].r, arguments[0].g, arguments[0].b);
        } else if (arguments.length === 3) {
            this._color.set(arguments[0], arguments[1], arguments[2]);
        }
        this._updateLinearColor();
    }
    layersDirty() {
        this.layers.forEach((layer)=>{
            if (layer.hasLight(this)) {
                layer.markLightsDirty();
            }
        });
    }
    /**
     * Updates a integer key for the light. The key is used to identify all shader related features
     * of the light, and so needs to have all properties that modify the generated shader encoded.
     * Properties without an effect on the shader (color, shadow intensity) should not be encoded.
     */ updateKey() {
        // Key definition:
        // Bit
        // 31      : sign bit (leave)
        // 29 - 30 : type
        // 25 - 28 : shadow type
        // 23 - 24 : falloff mode
        // 22      : normal offset bias
        // 21      : cookie
        // 20      : cookie falloff
        // 18 - 19 : cookie channel R
        // 16 - 17 : cookie channel G
        // 14 - 15 : cookie channel B
        // 12      : cookie transform
        // 10 - 11 : light source shape
        //  9      : use cascades
        //  8      : cascade blend
        //  7      : disable specular
        //  6 -  4 : mask
        //  3      : cast shadows
        let key = this._type << 29 | this._shadowType << 25 | this._falloffMode << 23 | (this._normalOffsetBias !== 0.0 ? 1 : 0) << 22 | (this._cookie ? 1 : 0) << 21 | (this._cookieFalloff ? 1 : 0) << 20 | chanId[this._cookieChannel.charAt(0)] << 18 | (this._cookieTransform ? 1 : 0) << 12 | this._shape << 10 | (this.numCascades > 0 ? 1 : 0) << 9 | (this._cascadeBlend > 0 ? 1 : 0) << 8 | (this.affectSpecularity ? 1 : 0) << 7 | this.mask << 6 | (this._castShadows ? 1 : 0) << 3;
        if (this._cookieChannel.length === 3) {
            key |= chanId[this._cookieChannel.charAt(1)] << 16;
            key |= chanId[this._cookieChannel.charAt(2)] << 14;
        }
        if (key !== this.key) {
            // The layer maintains lights split and sorted by the key, notify it when the key changes
            this.layersDirty();
        }
        this.key = key;
    }
    /**
     * Updates 32bit flags used by the clustered lighting. This only stores constant data.
     * Note: this needs to match shader code in clusteredLight.js
     */ updateClusteredFlags() {
        const isDynamic = !!(this.mask & MASK_AFFECT_DYNAMIC);
        const isLightmapped = !!(this.mask & MASK_AFFECT_LIGHTMAPPED);
        this.clusteredFlags = (this.type === LIGHTTYPE_SPOT ? 1 : 0) << 30 | // bits 30
        (this._shape & 0x3) << 28 | // bits 29 - 28
        (this._falloffMode & 0x1) << 27 | // bits 27
        (channelMap[this._cookieChannel] ?? 0) << 23 | // bits 26 - 23
        (isDynamic ? 1 : 0) << 22 | // bits 22
        (isLightmapped ? 1 : 0) << 21; // bits 21
    }
    /**
     * Adds per-frame dynamic data to the 32bit flags used by the clustered lighting.
     */ getClusteredFlags(castShadows, useCookie) {
        return this.clusteredFlags | ((castShadows ? Math.floor(this.shadowIntensity * 255) : 0) & 0xFF) << 0 | // bits 7 - 0
        ((useCookie ? Math.floor(this.cookieIntensity * 255) : 0) & 0xFF) << 8; // bits 15 - 8
    }
    updateClusterData(updateColor, updateAngles) {
        const { clusteredData16 } = this;
        const float2Half = FloatPacking.float2Half;
        if (updateColor) {
            // bring HDR color to half-float range, as those values can be over 65K
            clusteredData16[0] = float2Half(math.clamp(this._colorLinear[0] / LIGHT_COLOR_DIVIDER, 0, 65504));
            clusteredData16[1] = float2Half(math.clamp(this._colorLinear[1] / LIGHT_COLOR_DIVIDER, 0, 65504));
            clusteredData16[2] = float2Half(math.clamp(this._colorLinear[2] / LIGHT_COLOR_DIVIDER, 0, 65504));
        // unused 16bits here
        }
        if (updateAngles) {
            clusteredData16[4] = float2Half(this._innerConeAngleCos);
            clusteredData16[5] = float2Half(this._outerConeAngleCos);
        }
    }
}

/**
 * Lighting parameters, allow configuration of the global lighting parameters. For details see
 * [Clustered Lighting](https://developer.playcanvas.com/user-manual/graphics/lighting/clustered-lighting/).
 *
 * @category Graphics
 */ class LightingParams {
    /**
     * Creates a new LightingParams object.
     *
     * @ignore
     */ constructor(supportsAreaLights, maxTextureSize, dirtyLightsFnc){
        /** @private */ this._areaLightsEnabled = false;
        /** @private */ this._cells = new Vec3(10, 3, 10);
        /** @private */ this._maxLightsPerCell = 255;
        /** @private */ this._shadowsEnabled = true;
        /** @private */ this._shadowType = SHADOW_PCF3_32F;
        /** @private */ this._shadowAtlasResolution = 2048;
        /** @private */ this._cookiesEnabled = false;
        /** @private */ this._cookieAtlasResolution = 2048;
        /**
     * Atlas textures split description, which applies to both the shadow and cookie texture atlas.
     * Defaults to null, which enables to automatic split mode. For details see [Configuring Atlas
     * Split](https://developer.playcanvas.com/user-manual/graphics/lighting/clustered-lighting/#configuring-atlas).
     *
     * @type {number[]|null}
     */ this.atlasSplit = null;
        this._supportsAreaLights = supportsAreaLights;
        this._maxTextureSize = maxTextureSize;
        this._dirtyLightsFnc = dirtyLightsFnc;
    }
    applySettings(render) {
        this.shadowsEnabled = render.lightingShadowsEnabled ?? this.shadowsEnabled;
        this.cookiesEnabled = render.lightingCookiesEnabled ?? this.cookiesEnabled;
        this.areaLightsEnabled = render.lightingAreaLightsEnabled ?? this.areaLightsEnabled;
        this.shadowAtlasResolution = render.lightingShadowAtlasResolution ?? this.shadowAtlasResolution;
        this.cookieAtlasResolution = render.lightingCookieAtlasResolution ?? this.cookieAtlasResolution;
        this.maxLightsPerCell = render.lightingMaxLightsPerCell ?? this.maxLightsPerCell;
        this.shadowType = render.lightingShadowType ?? this.shadowType;
        if (render.lightingCells) {
            this.cells = new Vec3(render.lightingCells);
        }
    }
    /**
     * Sets the number of cells along each world space axis the space containing lights is
     * subdivided into. Defaults to `[10, 3, 10]`.
     *
     * @type {Vec3}
     */ set cells(value) {
        this._cells.copy(value);
    }
    /**
     * Gets the number of cells along each world space axis the space containing lights is
     * subdivided into.
     *
     * @type {Vec3}
     */ get cells() {
        return this._cells;
    }
    /**
     * Sets the maximum number of lights a cell can store. Defaults to 255.
     *
     * @type {number}
     */ set maxLightsPerCell(value) {
        this._maxLightsPerCell = math.clamp(value, 1, 255);
    }
    /**
     * Gets the maximum number of lights a cell can store.
     *
     * @type {number}
     */ get maxLightsPerCell() {
        return this._maxLightsPerCell;
    }
    /**
     * Sets the resolution of the atlas texture storing all non-directional cookie textures.
     * Defaults to 2048.
     *
     * @type {number}
     */ set cookieAtlasResolution(value) {
        this._cookieAtlasResolution = math.clamp(value, 32, this._maxTextureSize);
    }
    /**
     * Gets the resolution of the atlas texture storing all non-directional cookie textures.
     *
     * @type {number}
     */ get cookieAtlasResolution() {
        return this._cookieAtlasResolution;
    }
    /**
     * Sets the resolution of the atlas texture storing all non-directional shadow textures.
     * Defaults to 2048.
     *
     * @type {number}
     */ set shadowAtlasResolution(value) {
        this._shadowAtlasResolution = math.clamp(value, 32, this._maxTextureSize);
    }
    /**
     * Gets the resolution of the atlas texture storing all non-directional shadow textures.
     *
     * @type {number}
     */ get shadowAtlasResolution() {
        return this._shadowAtlasResolution;
    }
    /**
     * Sets the type of shadow filtering used by all shadows. Can be:
     *
     * - {@link SHADOW_PCF1_32F}
     * - {@link SHADOW_PCF3_32F}
     * - {@link SHADOW_PCF5_32F}
     * - {@link SHADOW_PCF1_16F}
     * - {@link SHADOW_PCF3_16F}
     * - {@link SHADOW_PCF5_16F}
     *
     * Defaults to {@link SHADOW_PCF3_32F}
     *
     * @type {number}
     */ set shadowType(value) {
        if (this._shadowType !== value) {
            this._shadowType = value;
            // lit shaders need to be rebuilt
            this._dirtyLightsFnc();
        }
    }
    /**
     * Gets the type of shadow filtering used by all shadows.
     *
     * @type {number}
     */ get shadowType() {
        return this._shadowType;
    }
    /**
     * Sets whether clustered lighting supports cookie textures. Defaults to false.
     *
     * @type {boolean}
     */ set cookiesEnabled(value) {
        if (this._cookiesEnabled !== value) {
            this._cookiesEnabled = value;
            // lit shaders need to be rebuilt
            this._dirtyLightsFnc();
        }
    }
    /**
     * Gets whether clustered lighting supports cookie textures.
     *
     * @type {boolean}
     */ get cookiesEnabled() {
        return this._cookiesEnabled;
    }
    /**
     * Sets whether clustered lighting supports area lights. Defaults to false.
     *
     * @type {boolean}
     */ set areaLightsEnabled(value) {
        // ignore if not supported
        if (this._supportsAreaLights) {
            if (this._areaLightsEnabled !== value) {
                this._areaLightsEnabled = value;
                // lit shaders need to be rebuilt
                this._dirtyLightsFnc();
            }
        }
    }
    /**
     * Gets whether clustered lighting supports area lights.
     *
     * @type {boolean}
     */ get areaLightsEnabled() {
        return this._areaLightsEnabled;
    }
    /**
     * Sets whether clustered lighting supports shadow casting. Defaults to true.
     *
     * @type {boolean}
     */ set shadowsEnabled(value) {
        if (this._shadowsEnabled !== value) {
            this._shadowsEnabled = value;
            // lit shaders need to be rebuilt
            this._dirtyLightsFnc();
        }
    }
    /**
     * Gets whether clustered lighting supports shadow casting.
     *
     * @type {boolean}
     */ get shadowsEnabled() {
        return this._shadowsEnabled;
    }
}

/**
 * @import { Morph } from './morph.js'
 * @import { Shader } from '../platform/graphics/shader.js'
 */ /**
 * An instance of {@link Morph}. Contains weights to assign to every {@link MorphTarget}, manages
 * selection of active morph targets.
 *
 * @category Graphics
 */ class MorphInstance {
    /**
     * Create a new MorphInstance instance.
     *
     * @param {Morph} morph - The {@link Morph} to instance.
     */ constructor(morph){
        /**
         * The morph with its targets, which is being instanced.
         *
         * @type {Morph}
         */ this.morph = morph;
        morph.incRefCount();
        this.device = morph.device;
        // shader to blend a required number of morph targets
        const maxNumTargets = morph._targets.length;
        this.shader = this._createShader(maxNumTargets);
        // weights
        this._weights = [];
        this._weightMap = new Map();
        for(let v = 0; v < morph._targets.length; v++){
            const target = morph._targets[v];
            if (target.name) {
                this._weightMap.set(target.name, v);
            }
            this.setWeight(v, target.defaultWeight);
        }
        // array for max number of weights
        this._shaderMorphWeights = new Float32Array(maxNumTargets);
        // array for target indices
        this._shaderMorphIndex = new Uint32Array(maxNumTargets);
        // create render targets to morph targets into
        const createRT = (name, textureVar)=>{
            // render to appropriate, RGBA formats
            this[textureVar] = morph._createTexture(name, morph._renderTextureFormat);
            return new RenderTarget({
                colorBuffer: this[textureVar],
                depth: false
            });
        };
        if (morph.morphPositions) {
            this.rtPositions = createRT('MorphRTPos', 'texturePositions');
        }
        if (morph.morphNormals) {
            this.rtNormals = createRT('MorphRTNrm', 'textureNormals');
        }
        this._textureParams = new Float32Array([
            morph.morphTextureWidth,
            morph.morphTextureHeight
        ]);
        // position aabb data - expand it 2x on each side to handle the expected worse range. Note
        // that this is only needed for the fallback solution using integer textures to encode positions
        const halfSize = morph.aabb.halfExtents;
        this._aabbSize = new Float32Array([
            halfSize.x * 4,
            halfSize.y * 4,
            halfSize.z * 4
        ]);
        const min = morph.aabb.getMin();
        this._aabbMin = new Float32Array([
            min.x * 2,
            min.y * 2,
            min.z * 2
        ]);
        // aabb size and min factors for normal rendering, where the range is -1..1
        this._aabbNrmSize = new Float32Array([
            2,
            2,
            2
        ]);
        this._aabbNrmMin = new Float32Array([
            -1,
            -1,
            -1
        ]);
        this.aabbSizeId = this.device.scope.resolve('aabbSize');
        this.aabbMinId = this.device.scope.resolve('aabbMin');
        // resolve shader inputs
        this.morphTextureId = this.device.scope.resolve('morphTexture');
        this.morphFactor = this.device.scope.resolve('morphFactor[0]');
        this.morphIndex = this.device.scope.resolve('morphIndex[0]');
        this.countId = this.device.scope.resolve('count');
        // true indicates render target textures are full of zeros to avoid rendering to them when all weights are zero
        this.zeroTextures = false;
    }
    /**
     * Frees video memory allocated by this object.
     */ destroy() {
        // don't destroy shader as it's in the cache and can be used by other materials
        this.shader = null;
        const morph = this.morph;
        if (morph) {
            // decrease ref count
            this.morph = null;
            morph.decRefCount();
            // destroy morph
            if (morph.refCount < 1) {
                morph.destroy();
            }
        }
        this.rtPositions?.destroy();
        this.rtPositions = null;
        this.texturePositions?.destroy();
        this.texturePositions = null;
        this.rtNormals?.destroy();
        this.rtNormals = null;
        this.textureNormals?.destroy();
        this.textureNormals = null;
    }
    /**
     * Clones a MorphInstance. The returned clone uses the same {@link Morph} and weights are set
     * to defaults.
     *
     * @returns {MorphInstance} A clone of the specified MorphInstance.
     */ clone() {
        return new MorphInstance(this.morph);
    }
    _getWeightIndex(key) {
        if (typeof key === 'string') {
            const index = this._weightMap.get(key);
            if (index === undefined) {
                Debug.errorOnce(`Cannot find morph target with name: ${key}.`);
            }
            return index;
        }
        return key;
    }
    /**
     * Gets current weight of the specified morph target.
     *
     * @param {string|number} key - An identifier for the morph target. Either the weight index or
     * the weight name.
     * @returns {number} Weight.
     */ getWeight(key) {
        const index = this._getWeightIndex(key);
        return this._weights[index];
    }
    /**
     * Sets weight of the specified morph target.
     *
     * @param {string|number} key - An identifier for the morph target. Either the weight index or
     * the weight name.
     * @param {number} weight - Weight.
     */ setWeight(key, weight) {
        const index = this._getWeightIndex(key);
        Debug.assert(index >= 0 && index < this.morph._targets.length);
        this._weights[index] = weight;
        this._dirty = true;
    }
    /**
     * Create the shader for texture based morphing.
     *
     * @param {number} maxCount - Maximum bumber of textures to blend.
     * @returns {Shader} Shader.
     * @private
     */ _createShader(maxCount) {
        const defines = new Map();
        defines.set('{MORPH_TEXTURE_MAX_COUNT}', maxCount);
        if (this.morph.intRenderFormat) defines.set('MORPH_INT', '');
        const outputType = this.morph.intRenderFormat ? 'uvec4' : 'vec4';
        return ShaderUtils.createShader(this.device, {
            uniqueName: `TextureMorphShader_${maxCount}-${this.morph.intRenderFormat ? 'int' : 'float'}`,
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexChunk: 'morphVS',
            fragmentChunk: 'morphPS',
            fragmentDefines: defines,
            fragmentOutputTypes: [
                outputType
            ]
        });
    }
    _updateTextureRenderTarget(renderTarget, activeCount, isPos) {
        const { morph, device } = this;
        this.setAabbUniforms(isPos);
        this.morphTextureId.setValue(isPos ? morph.targetsTexturePositions : morph.targetsTextureNormals);
        device.setBlendState(BlendState.NOBLEND);
        // set up parameters for active blend targets
        this.countId.setValue(activeCount);
        this.morphFactor.setValue(this._shaderMorphWeights);
        this.morphIndex.setValue(this._shaderMorphIndex);
        // render quad with shader
        drawQuadWithShader(device, renderTarget, this.shader);
    }
    _updateTextureMorph(activeCount) {
        const device = this.device;
        DebugGraphics.pushGpuMarker(device, 'MorphUpdate');
        // update textures if active targets, or no active targets and textures need to be cleared
        if (activeCount > 0 || !this.zeroTextures) {
            // blend morph targets into render targets
            if (this.rtPositions) {
                this._updateTextureRenderTarget(this.rtPositions, activeCount, true);
            }
            if (this.rtNormals) {
                this._updateTextureRenderTarget(this.rtNormals, activeCount, false);
            }
            // textures were cleared if no active targets
            this.zeroTextures = activeCount === 0;
        }
        DebugGraphics.popGpuMarker(device);
    }
    setAabbUniforms(isPos = true) {
        this.aabbSizeId.setValue(isPos ? this._aabbSize : this._aabbNrmSize);
        this.aabbMinId.setValue(isPos ? this._aabbMin : this._aabbNrmMin);
    }
    prepareRendering(device) {
        this.setAabbUniforms();
    }
    /**
     * Selects active morph targets and prepares morph for rendering. Called automatically by
     * renderer.
     */ update() {
        this._dirty = false;
        const targets = this.morph._targets;
        // collect weights for active targets
        const epsilon = 0.00001;
        const weights = this._shaderMorphWeights;
        const indices = this._shaderMorphIndex;
        let activeCount = 0;
        for(let i = 0; i < targets.length; i++){
            if (Math.abs(this.getWeight(i)) > epsilon) {
                weights[activeCount] = this.getWeight(i);
                indices[activeCount] = i;
                activeCount++;
            }
        }
        // prepare for rendering
        this._updateTextureMorph(activeCount);
    }
}

/**
 * @import { GraphNode } from './graph-node.js'
 */ /**
 * A model is a graphical object that can be added to or removed from a scene. It contains a
 * hierarchy and any number of mesh instances.
 *
 * @category Graphics
 */ class Model {
    /**
     * Creates a new model.
     *
     * @example
     * // Create a new model
     * const model = new pc.Model();
     */ constructor(){
        /**
     * The root node of the model's graph node hierarchy.
     *
     * @type {GraphNode|null}
     */ this.graph = null;
        /**
     * An array of MeshInstances contained in this model.
     *
     * @type {MeshInstance[]}
     */ this.meshInstances = [];
        /**
     * An array of SkinInstances contained in this model.
     *
     * @type {SkinInstance[]}
     */ this.skinInstances = [];
        /**
     * An array of MorphInstances contained in this model.
     *
     * @type {MorphInstance[]}
     */ this.morphInstances = [];
        this.cameras = [];
        this.lights = [];
        this._shadersVersion = 0;
        // used by the model component to flag that this model has been assigned
        this._immutable = false;
    }
    getGraph() {
        return this.graph;
    }
    setGraph(graph) {
        this.graph = graph;
    }
    getCameras() {
        return this.cameras;
    }
    setCameras(cameras) {
        this.cameras = cameras;
    }
    getLights() {
        return this.lights;
    }
    setLights(lights) {
        this.lights = lights;
    }
    getMaterials() {
        const materials = [];
        for(let i = 0; i < this.meshInstances.length; i++){
            const meshInstance = this.meshInstances[i];
            if (materials.indexOf(meshInstance.material) === -1) {
                materials.push(meshInstance.material);
            }
        }
        return materials;
    }
    /**
     * Clones a model. The returned model has a newly created hierarchy and mesh instances, but
     * meshes are shared between the clone and the specified model.
     *
     * @returns {Model} A clone of the specified model.
     * @example
     * const clonedModel = model.clone();
     */ clone() {
        // Duplicate the node hierarchy
        const srcNodes = [];
        const cloneNodes = [];
        const _duplicate = function(node) {
            const newNode = node.clone();
            srcNodes.push(node);
            cloneNodes.push(newNode);
            for(let idx = 0; idx < node._children.length; idx++){
                newNode.addChild(_duplicate(node._children[idx]));
            }
            return newNode;
        };
        const cloneGraph = _duplicate(this.graph);
        const cloneMeshInstances = [];
        const cloneSkinInstances = [];
        const cloneMorphInstances = [];
        // Clone the skin instances
        for(let i = 0; i < this.skinInstances.length; i++){
            const skin = this.skinInstances[i].skin;
            const cloneSkinInstance = new SkinInstance(skin);
            // Resolve bone IDs to actual graph nodes
            const bones = [];
            for(let j = 0; j < skin.boneNames.length; j++){
                const boneName = skin.boneNames[j];
                const bone = cloneGraph.findByName(boneName);
                bones.push(bone);
            }
            cloneSkinInstance.bones = bones;
            cloneSkinInstances.push(cloneSkinInstance);
        }
        // Clone the morph instances
        for(let i = 0; i < this.morphInstances.length; i++){
            const morph = this.morphInstances[i].morph;
            const cloneMorphInstance = new MorphInstance(morph);
            cloneMorphInstances.push(cloneMorphInstance);
        }
        // Clone the mesh instances
        for(let i = 0; i < this.meshInstances.length; i++){
            const meshInstance = this.meshInstances[i];
            const nodeIndex = srcNodes.indexOf(meshInstance.node);
            const cloneMeshInstance = new MeshInstance(meshInstance.mesh, meshInstance.material, cloneNodes[nodeIndex]);
            if (meshInstance.skinInstance) {
                const skinInstanceIndex = this.skinInstances.indexOf(meshInstance.skinInstance);
                cloneMeshInstance.skinInstance = cloneSkinInstances[skinInstanceIndex];
            }
            if (meshInstance.morphInstance) {
                const morphInstanceIndex = this.morphInstances.indexOf(meshInstance.morphInstance);
                cloneMeshInstance.morphInstance = cloneMorphInstances[morphInstanceIndex];
            }
            cloneMeshInstances.push(cloneMeshInstance);
        }
        const clone = new Model();
        clone.graph = cloneGraph;
        clone.meshInstances = cloneMeshInstances;
        clone.skinInstances = cloneSkinInstances;
        clone.morphInstances = cloneMorphInstances;
        clone.getGraph().syncHierarchy();
        return clone;
    }
    /**
     * Destroys skinning texture and possibly deletes vertex/index buffers of a model. Mesh is
     * reference-counted, so buffers are only deleted if all models with referencing mesh instances
     * were deleted. That means all in-scene models + the "base" one (asset.resource) which is
     * created when the model is parsed. It is recommended to use asset.unload() instead, which
     * will also remove the model from the scene.
     */ destroy() {
        const meshInstances = this.meshInstances;
        for(let i = 0; i < meshInstances.length; i++){
            meshInstances[i].destroy();
        }
        this.meshInstances.length = 0;
    }
    /**
     * Generates the necessary internal data for a model to be renderable as wireframe. Once this
     * function has been called, any mesh instance in the model can have its renderStyle property
     * set to {@link RENDERSTYLE_WIREFRAME}.
     *
     * @example
     * model.generateWireframe();
     * for (let i = 0; i < model.meshInstances.length; i++) {
     *     model.meshInstances[i].renderStyle = pc.RENDERSTYLE_WIREFRAME;
     * }
     */ generateWireframe() {
        MeshInstance._prepareRenderStyleForArray(this.meshInstances, RENDERSTYLE_WIREFRAME);
    }
}

/**
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 * @import { MorphTarget } from './morph-target.js'
 */ /**
 * Contains a list of {@link MorphTarget}s, a combined delta AABB and some associated data.
 *
 * @category Graphics
 */ class Morph extends RefCountedObject {
    /**
     * Create a new Morph instance.
     *
     * @param {MorphTarget[]} targets - A list of morph targets.
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this morph target.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {boolean} [options.preferHighPrecision] - True if high precision storage should be
     * preferred. This is faster to create and allows higher precision, but takes more memory and
     * might be slower to render. Defaults to false.
     */ constructor(targets, graphicsDevice, { preferHighPrecision = false } = {}){
        super();
        Debug.assert(graphicsDevice, 'Morph constructor takes a GraphicsDevice as a parameter, and it was not provided.');
        this.device = graphicsDevice;
        const device = graphicsDevice;
        this.preferHighPrecision = preferHighPrecision;
        // validation
        Debug.assert(targets.every((target)=>!target.used), 'A specified target has already been used to create a Morph, use its clone instead.');
        this._targets = targets.slice();
        // renderable format
        const renderableHalf = device.textureHalfFloatRenderable ? PIXELFORMAT_RGBA16F : undefined;
        const renderableFloat = device.textureFloatRenderable ? PIXELFORMAT_RGBA32F : undefined;
        this._renderTextureFormat = this.preferHighPrecision ? renderableFloat ?? renderableHalf : renderableHalf ?? renderableFloat;
        // fallback to more limited int format
        this._renderTextureFormat = this._renderTextureFormat ?? PIXELFORMAT_RGBA16U;
        this.intRenderFormat = isIntegerPixelFormat(this._renderTextureFormat);
        // source texture format - both are always supported
        this._textureFormat = this.preferHighPrecision ? PIXELFORMAT_RGBA32F : PIXELFORMAT_RGBA16F;
        this._init();
        this._updateMorphFlags();
    }
    /**
     * Frees video memory allocated by this object.
     */ destroy() {
        this.vertexBufferIds?.destroy();
        this.vertexBufferIds = null;
        this.targetsTexturePositions?.destroy();
        this.targetsTexturePositions = null;
        this.targetsTextureNormals?.destroy();
        this.targetsTextureNormals = null;
    }
    get aabb() {
        // lazy evaluation, which allows us to skip this completely if customAABB is used
        if (!this._aabb) {
            // calculate min and max expansion size
            // Note: This represents average case, where most morph targets expand the mesh within the same area. It does not
            // represent the stacked worst case scenario where all morphs could be enabled at the same time, as this can result
            // in a very large aabb. In cases like this, the users should specify customAabb for Model/Render component.
            const min = new Vec3();
            const max = new Vec3();
            for(let i = 0; i < this._targets.length; i++){
                const targetAabb = this._targets[i].aabb;
                min.min(targetAabb.getMin());
                max.max(targetAabb.getMax());
            }
            this._aabb = new BoundingBox();
            this._aabb.setMinMax(min, max);
        }
        return this._aabb;
    }
    get morphPositions() {
        return this._morphPositions;
    }
    get morphNormals() {
        return this._morphNormals;
    }
    _init() {
        // texture based morphing
        this._initTextureBased();
        // finalize init
        for(let i = 0; i < this._targets.length; i++){
            this._targets[i]._postInit();
        }
    }
    _findSparseSet(deltaArrays, ids, usedDataIndices) {
        let freeIndex = 1; // reserve slot 0 for zero delta
        const dataCount = deltaArrays[0].length;
        for(let v = 0; v < dataCount; v += 3){
            // find if vertex is morphed by any target
            let vertexUsed = false;
            for(let i = 0; i < deltaArrays.length; i++){
                const data = deltaArrays[i];
                // if non-zero delta
                if (data[v] !== 0 || data[v + 1] !== 0 || data[v + 2] !== 0) {
                    vertexUsed = true;
                    break;
                }
            }
            if (vertexUsed) {
                ids.push(freeIndex);
                usedDataIndices.push(v / 3);
                freeIndex++;
            } else {
                // non morphed vertices would be all mapped to pixel 0 of texture
                ids.push(0);
            }
        }
        return freeIndex;
    }
    _initTextureBased() {
        // collect all source delta arrays to find sparse set of vertices
        const deltaArrays = [], deltaInfos = [];
        const targets = this._targets;
        for(let i = 0; i < targets.length; i++){
            const target = targets[i];
            if (target.options.deltaPositions) {
                deltaArrays.push(target.options.deltaPositions);
                deltaInfos.push(true); // position
            }
            if (target.options.deltaNormals) {
                deltaArrays.push(target.options.deltaNormals);
                deltaInfos.push(false); // normal
            }
        }
        // find sparse set for all target deltas into usedDataIndices and build vertex id buffer
        const ids = [], usedDataIndices = [];
        const freeIndex = this._findSparseSet(deltaArrays, ids, usedDataIndices);
        // texture size for freeIndex pixels - roughly square
        const maxTextureSize = this.device.maxTextureSize;
        let morphTextureWidth = Math.ceil(Math.sqrt(freeIndex));
        morphTextureWidth = Math.min(morphTextureWidth, maxTextureSize);
        const morphTextureHeight = Math.ceil(freeIndex / morphTextureWidth);
        // if data cannot fit into max size texture, fail this set up
        if (morphTextureHeight > maxTextureSize) {
            Debug.warnOnce(`Morph target data is too large to fit into a texture array. Required texture size: ${morphTextureWidth}x${morphTextureHeight}, max texture size: ${maxTextureSize}x${maxTextureSize}.`);
            return;
        }
        this.morphTextureWidth = morphTextureWidth;
        this.morphTextureHeight = morphTextureHeight;
        // texture format based vars
        let halfFloat = false;
        const float2Half = FloatPacking.float2Half;
        if (this._textureFormat === PIXELFORMAT_RGBA16F) {
            halfFloat = true;
        }
        // build texture data for each delta array, to be used as a texture array
        const texturesDataPositions = [];
        const texturesDataNormals = [];
        const textureDataSize = morphTextureWidth * morphTextureHeight * 4;
        for(let i = 0; i < deltaArrays.length; i++){
            const data = deltaArrays[i];
            const textureData = this._textureFormat === PIXELFORMAT_RGBA16F ? new Uint16Array(textureDataSize) : new Float32Array(textureDataSize);
            (deltaInfos[i] ? texturesDataPositions : texturesDataNormals).push(textureData);
            // copy full arrays into sparse arrays and convert format (skip 0th pixel - used by non-morphed vertices)
            if (halfFloat) {
                for(let v = 0; v < usedDataIndices.length; v++){
                    const index = usedDataIndices[v] * 3;
                    const dstIndex = v * 4 + 4;
                    textureData[dstIndex] = float2Half(data[index]);
                    textureData[dstIndex + 1] = float2Half(data[index + 1]);
                    textureData[dstIndex + 2] = float2Half(data[index + 2]);
                }
            } else {
                for(let v = 0; v < usedDataIndices.length; v++){
                    const index = usedDataIndices[v] * 3;
                    const dstIndex = v * 4 + 4;
                    textureData[dstIndex] = data[index];
                    textureData[dstIndex + 1] = data[index + 1];
                    textureData[dstIndex + 2] = data[index + 2];
                }
            }
        }
        // allocate texture arrays to store data from all morph targets
        if (texturesDataPositions.length > 0) {
            this.targetsTexturePositions = this._createTexture('MorphPositionsTexture', this._textureFormat, targets.length, [
                texturesDataPositions
            ]);
        }
        if (texturesDataNormals.length > 0) {
            this.targetsTextureNormals = this._createTexture('MorphNormalsTexture', this._textureFormat, targets.length, [
                texturesDataNormals
            ]);
        }
        // create vertex stream with vertex_id used to map vertex to texture
        const formatDesc = [
            {
                semantic: SEMANTIC_ATTR15,
                components: 1,
                type: TYPE_UINT32,
                asInt: true
            }
        ];
        this.vertexBufferIds = new VertexBuffer(this.device, new VertexFormat(this.device, formatDesc, ids.length), ids.length, {
            data: new Uint32Array(ids)
        });
        return true;
    }
    /**
     * Gets the array of morph targets.
     *
     * @type {MorphTarget[]}
     */ get targets() {
        return this._targets;
    }
    _updateMorphFlags() {
        // find out if this morph needs to morph positions and normals
        this._morphPositions = false;
        this._morphNormals = false;
        for(let i = 0; i < this._targets.length; i++){
            const target = this._targets[i];
            if (target.morphPositions) {
                this._morphPositions = true;
            }
            if (target.morphNormals) {
                this._morphNormals = true;
            }
        }
    }
    /**
     * Creates a texture / texture array. Used to create both source morph target data, as well as
     * render target used to morph these into, positions and normals.
     *
     * @param {string} name - The name of the texture.
     * @param {number} format - The format of the texture.
     * @param {Array} [levels] - The levels of the texture.
     * @param {number} [arrayLength] - The length of the texture array.
     * @returns {Texture} The created texture.
     * @private
     */ _createTexture(name, format, arrayLength, levels) {
        return new Texture(this.device, {
            levels: levels,
            arrayLength: arrayLength,
            width: this.morphTextureWidth,
            height: this.morphTextureHeight,
            format: format,
            cubemap: false,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            name: name
        });
    }
}

/**
 * A Morph Target (also known as Blend Shape) contains deformation data to apply to existing mesh.
 * Multiple morph targets can be blended together on a mesh. This is useful for effects that are
 * hard to achieve with conventional animation and skinning.
 *
 * @category Graphics
 */ class MorphTarget {
    /**
     * Create a new MorphTarget instance.
     *
     * @param {object} options - Object for passing optional arguments.
     * @param {ArrayBuffer} options.deltaPositions - An array of 3-dimensional vertex position
     * offsets.
     * @param {ArrayBuffer} [options.deltaNormals] - An array of 3-dimensional vertex normal
     * offsets.
     * @param {string} [options.name] - Name.
     * @param {BoundingBox} [options.aabb] - Bounding box. Will be automatically generated, if
     * undefined.
     * @param {number} [options.defaultWeight] - Default blend weight to use for this morph target.
     * @param {boolean} [options.preserveData] - When true, the morph target keeps its data passed using the options,
     * allowing the clone operation.
     */ constructor(options){
        /**
     * A used flag. A morph target can be used / owned by the Morph class only one time.
     *
     * @type {boolean}
     */ this.used = false;
        Debug.assert(arguments.length === 1);
        this.options = options;
        this._name = options.name;
        this._defaultWeight = options.defaultWeight || 0;
        // bounds
        this._aabb = options.aabb;
        // store delta positions, used by aabb evaluation
        this.deltaPositions = options.deltaPositions;
        // true if the streams are available
        this.morphPositions = !!options.deltaPositions;
        this.morphNormals = !!options.deltaNormals;
    }
    /**
     * Gets the name of the morph target.
     *
     * @type {string}
     */ get name() {
        return this._name;
    }
    /**
     * Gets the default weight of the morph target.
     *
     * @type {number}
     */ get defaultWeight() {
        return this._defaultWeight;
    }
    get aabb() {
        // lazy evaluation, which allows us to skip this completely if customAABB is used
        if (!this._aabb) {
            this._aabb = new BoundingBox();
            if (this.deltaPositions) {
                this._aabb.compute(this.deltaPositions);
            }
        }
        return this._aabb;
    }
    /**
     * Returns an identical copy of the specified morph target. This can only be used if the morph target
     * was created with options.preserveData set to true.
     *
     * @returns {MorphTarget} A morph target instance containing the result of the cloning.
     */ clone() {
        Debug.assert(this.options, 'MorphTarget cannot be cloned, was it created with a preserveData option?');
        return new MorphTarget(this.options);
    }
    _postInit() {
        // release original data
        if (!this.options.preserveData) {
            this.options = null;
        }
        // mark it as used
        this.used = true;
    }
}

class ShaderGeneratorShader extends ShaderGenerator {
    generateKey(options) {
        // Note: options.shaderChunks are not included in the key as currently shader variants are removed
        // from the material when its chunks are modified.
        const desc = options.shaderDesc;
        const vsHashGLSL = desc.vertexGLSL ? hashCode(desc.vertexGLSL) : 0;
        const fsHashGLSL = desc.fragmentGLSL ? hashCode(desc.fragmentGLSL) : 0;
        const vsHashWGSL = desc.vertexWGSL ? hashCode(desc.vertexWGSL) : 0;
        const fsHashWGSL = desc.fragmentWGSL ? hashCode(desc.fragmentWGSL) : 0;
        const definesHash = ShaderGenerator.definesHash(options.defines);
        const chunksKey = options.shaderChunks?.key ?? '';
        let key = `${desc.uniqueName}_${definesHash}_${vsHashGLSL}_${fsHashGLSL}_${vsHashWGSL}_${fsHashWGSL}_${chunksKey}`;
        if (options.skin) key += '_skin';
        if (options.useInstancing) key += '_inst';
        if (options.useMorphPosition) key += '_morphp';
        if (options.useMorphNormal) key += '_morphn';
        if (options.useMorphTextureBasedInt) key += '_morphi';
        return key;
    }
    createAttributesDefinition(definitionOptions, options) {
        // clone provided attributes if any
        const srcAttributes = options.shaderDesc.attributes;
        const attributes = srcAttributes ? {
            ...srcAttributes
        } : undefined;
        // add automatic attributes
        if (options.skin) {
            attributes.vertex_boneWeights = SEMANTIC_BLENDWEIGHT;
            attributes.vertex_boneIndices = SEMANTIC_BLENDINDICES;
        }
        if (options.useMorphPosition || options.useMorphNormal) {
            attributes.morph_vertex_id = SEMANTIC_ATTR15;
        }
        definitionOptions.attributes = attributes;
    }
    createVertexDefinition(definitionOptions, options, sharedIncludes, wgsl) {
        const desc = options.shaderDesc;
        const includes = new Map(sharedIncludes);
        includes.set('transformInstancingVS', ''); // no default instancing, needs to be implemented in the user shader
        const defines = new Map(options.defines);
        if (options.skin) defines.set('SKIN', true);
        if (options.useInstancing) defines.set('INSTANCING', true);
        if (options.useMorphPosition || options.useMorphNormal) {
            defines.set('MORPHING', true);
            if (options.useMorphTextureBasedInt) defines.set('MORPHING_INT', true);
            if (options.useMorphPosition) defines.set('MORPHING_POSITION', true);
            if (options.useMorphNormal) defines.set('MORPHING_NORMAL', true);
        }
        definitionOptions.vertexCode = wgsl ? desc.vertexWGSL : desc.vertexGLSL;
        definitionOptions.vertexIncludes = includes;
        definitionOptions.vertexDefines = defines;
    }
    createFragmentDefinition(definitionOptions, options, sharedIncludes, wgsl) {
        const desc = options.shaderDesc;
        const includes = new Map(sharedIncludes);
        const defines = new Map(options.defines);
        definitionOptions.fragmentCode = wgsl ? desc.fragmentWGSL : desc.fragmentGLSL;
        definitionOptions.fragmentIncludes = includes;
        definitionOptions.fragmentDefines = defines;
    }
    createShaderDefinition(device, options) {
        const desc = options.shaderDesc;
        const wgsl = device.isWebGPU && !!desc.vertexWGSL && !!desc.fragmentWGSL && (options.shaderChunks?.useWGSL ?? true);
        const definitionOptions = {
            name: `ShaderMaterial-${desc.uniqueName}`,
            shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL,
            fragmentOutputTypes: desc.fragmentOutputTypes,
            meshUniformBufferFormat: desc.meshUniformBufferFormat,
            meshBindGroupFormat: desc.meshBindGroupFormat
        };
        // includes - default chunks
        const shaderLanguage = wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL;
        const sharedIncludes = MapUtils.merge(ShaderChunks.get(device, shaderLanguage), options.shaderChunks[shaderLanguage]);
        this.createAttributesDefinition(definitionOptions, options);
        this.createVertexDefinition(definitionOptions, options, sharedIncludes, wgsl);
        this.createFragmentDefinition(definitionOptions, options, sharedIncludes, wgsl);
        return ShaderDefinitionUtils.createDefinition(device, definitionOptions);
    }
}
const shaderGeneratorShader = new ShaderGeneratorShader();

/**
 * @typedef {object} ShaderDesc - Defines the vertex and fragment shader source for
 * {@link ShaderMaterial}, supporting both GLSL and WGSL formats. WebGL always uses the GLSL code.
 * WebGPU prefers the WGSL code if available, otherwise it automatically transpiles the provided
 * GLSL code at runtime.
 * @property {string} uniqueName - Unique name for the shader. If a shader with this name already
 * exists, it will be returned instead of a new shader instance.
 * @property {string} [vertexGLSL] - The vertex shader code in GLSL.
 * @property {string} [fragmentGLSL] - The fragment shader code in GLSL.
 * @property {string} [vertexWGSL] - The vertex shader code in WGSL.
 * @property {string} [fragmentWGSL] - The fragment shader code in WGSL.
 * @property {Object<string, string>} [attributes] - Object detailing the mapping of vertex shader
 * attribute names to semantics SEMANTIC_*. This enables the engine to match vertex buffer data as
 * inputs to the shader. Defaults to undefined, which generates the default attributes.
 * @property {string | string[]} [fragmentOutputTypes] - Fragment shader output types, which default to
 * vec4. Passing a string will set the output type for all color attachments. Passing an array will
 * set the output type for each color attachment. @see ShaderDefinitionUtils.createDefinition
 */ /**
 * A ShaderMaterial is a type of material that utilizes a specified shader for rendering purposes.
 *
 * A simple example which creates a material with custom vertex and fragment shaders specified in
 * GLSL format:
 *
 * ```javascript
 * const material = new pc.ShaderMaterial({
 *     uniqueName: 'MyShader',
 *     attributes: { aPosition: pc.SEMANTIC_POSITION },
 *     vertexGLSL: `
 *         attribute vec3 aPosition;
 *         uniform mat4 matrix_viewProjection;
 *         void main(void)
 *         {
 *             gl_Position = matrix_viewProjection * pos;
 *         }`,
 *     fragmentGLSL: `
 *         void main(void) {
 *             gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
 *         }`
 * });
 * ```
 *
 * @category Graphics
 */ class ShaderMaterial extends Material {
    /**
     * Create a new ShaderMaterial instance.
     *
     * @param {ShaderDesc} [shaderDesc] - The description of the shader to be used by the material.
     */ constructor(shaderDesc){
        super();
        this.shaderDesc = shaderDesc;
    }
    /**
     * Sets the shader description.
     *
     * @type {ShaderDesc|undefined}
     */ set shaderDesc(value) {
        this._shaderDesc = undefined;
        if (value) {
            // clone the object - only supported properties
            this._shaderDesc = {
                uniqueName: value.uniqueName,
                attributes: value.attributes,
                fragmentOutputTypes: value.fragmentOutputTypes,
                vertexGLSL: value.vertexGLSL,
                fragmentGLSL: value.fragmentGLSL,
                vertexWGSL: value.vertexWGSL,
                fragmentWGSL: value.fragmentWGSL
            };
            // backward compatibility - convert old properties to new
            if (value.vertexCode || value.fragmentCode || value.shaderLanguage) {
                Debug.deprecated(`ShaderMaterial [${value.uniqueName}]: vertexCode, fragmentCode and shaderLanguage properties of ShaderDesc is deprecated. Use vertexGLSL, fragmentGLSL, vertexWGSL or fragmentWGSL instead.`);
                const language = value.shaderLanguage ?? SHADERLANGUAGE_GLSL;
                if (language === SHADERLANGUAGE_GLSL) {
                    this._shaderDesc.vertexGLSL = value.vertexCode;
                    this._shaderDesc.fragmentGLSL = value.fragmentCode;
                } else if (language === SHADERLANGUAGE_WGSL) {
                    this._shaderDesc.vertexWGSL = value.vertexCode;
                    this._shaderDesc.fragmentWGSL = value.fragmentCode;
                }
            }
        }
        this.clearVariants();
    }
    /**
     * Gets the shader description.
     *
     * @type {ShaderDesc|undefined}
     */ get shaderDesc() {
        return this._shaderDesc;
    }
    /**
     * Copy a `ShaderMaterial`.
     *
     * @param {ShaderMaterial} source - The material to copy from.
     * @returns {ShaderMaterial} The destination material.
     */ copy(source) {
        super.copy(source);
        this.shaderDesc = source.shaderDesc;
        return this;
    }
    getShaderVariant(params) {
        const { objDefs } = params;
        const options = {
            defines: ShaderUtils.getCoreDefines(this, params),
            skin: (objDefs & SHADERDEF_SKIN) !== 0,
            useInstancing: (objDefs & SHADERDEF_INSTANCING) !== 0,
            useMorphPosition: (objDefs & SHADERDEF_MORPH_POSITION) !== 0,
            useMorphNormal: (objDefs & SHADERDEF_MORPH_NORMAL) !== 0,
            useMorphTextureBasedInt: (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0,
            pass: params.pass,
            gamma: params.cameraShaderParams.shaderOutputGamma,
            toneMapping: params.cameraShaderParams.toneMapping,
            fog: params.cameraShaderParams.fog,
            shaderDesc: this.shaderDesc,
            shaderChunks: this.shaderChunks // override chunks from the material
        };
        const processingOptions = new ShaderProcessorOptions(params.viewUniformFormat, params.viewBindGroupFormat, params.vertexFormat);
        const library = getProgramLibrary(params.device);
        library.register('shader-material', shaderGeneratorShader);
        return library.getProgram('shader-material', options, processingOptions, this.userId);
    }
}

/**
 * Generates normal information from the specified positions and triangle indices.
 *
 * @param {number[]} positions - An array of 3-dimensional vertex positions.
 * @param {number[]} indices - An array of triangle indices.
 * @returns {number[]} An array of 3-dimensional vertex normals.
 * @example
 * const normals = pc.calculateNormals(positions, indices);
 * @category Graphics
 */ const calculateNormals = (positions, indices)=>{
    const triangleCount = indices.length / 3;
    const vertexCount = positions.length / 3;
    const p1 = new Vec3();
    const p2 = new Vec3();
    const p3 = new Vec3();
    const p1p2 = new Vec3();
    const p1p3 = new Vec3();
    const faceNormal = new Vec3();
    const normals = [];
    // Initialize the normal array to zero
    for(let i = 0; i < positions.length; i++){
        normals[i] = 0;
    }
    // Accumulate face normals for each vertex
    for(let i = 0; i < triangleCount; i++){
        const i1 = indices[i * 3];
        const i2 = indices[i * 3 + 1];
        const i3 = indices[i * 3 + 2];
        p1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
        p2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
        p3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);
        p1p2.sub2(p2, p1);
        p1p3.sub2(p3, p1);
        faceNormal.cross(p1p2, p1p3).normalize();
        normals[i1 * 3] += faceNormal.x;
        normals[i1 * 3 + 1] += faceNormal.y;
        normals[i1 * 3 + 2] += faceNormal.z;
        normals[i2 * 3] += faceNormal.x;
        normals[i2 * 3 + 1] += faceNormal.y;
        normals[i2 * 3 + 2] += faceNormal.z;
        normals[i3 * 3] += faceNormal.x;
        normals[i3 * 3 + 1] += faceNormal.y;
        normals[i3 * 3 + 2] += faceNormal.z;
    }
    // Normalize all normals
    for(let i = 0; i < vertexCount; i++){
        const nx = normals[i * 3];
        const ny = normals[i * 3 + 1];
        const nz = normals[i * 3 + 2];
        const invLen = 1 / Math.sqrt(nx * nx + ny * ny + nz * nz);
        normals[i * 3] *= invLen;
        normals[i * 3 + 1] *= invLen;
        normals[i * 3 + 2] *= invLen;
    }
    return normals;
};
/**
 * Generates tangent information from the specified positions, normals, texture coordinates and
 * triangle indices.
 *
 * @param {number[]} positions - An array of 3-dimensional vertex positions.
 * @param {number[]} normals - An array of 3-dimensional vertex normals.
 * @param {number[]} uvs - An array of 2-dimensional vertex texture coordinates.
 * @param {number[]} indices - An array of triangle indices.
 * @returns {number[]} An array of 3-dimensional vertex tangents.
 * @example
 * const tangents = pc.calculateTangents(positions, normals, uvs, indices);
 * @category Graphics
 */ const calculateTangents = (positions, normals, uvs, indices)=>{
    // Lengyel's Method
    // http://web.archive.org/web/20180620024439/http://www.terathon.com/code/tangent.html
    const triangleCount = indices.length / 3;
    const vertexCount = positions.length / 3;
    const v1 = new Vec3();
    const v2 = new Vec3();
    const v3 = new Vec3();
    const w1 = new Vec2();
    const w2 = new Vec2();
    const w3 = new Vec2();
    const sdir = new Vec3();
    const tdir = new Vec3();
    const tan1 = new Float32Array(vertexCount * 3);
    const tan2 = new Float32Array(vertexCount * 3);
    const tangents = [];
    for(let i = 0; i < triangleCount; i++){
        const i1 = indices[i * 3];
        const i2 = indices[i * 3 + 1];
        const i3 = indices[i * 3 + 2];
        v1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
        v2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
        v3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);
        w1.set(uvs[i1 * 2], uvs[i1 * 2 + 1]);
        w2.set(uvs[i2 * 2], uvs[i2 * 2 + 1]);
        w3.set(uvs[i3 * 2], uvs[i3 * 2 + 1]);
        const x1 = v2.x - v1.x;
        const x2 = v3.x - v1.x;
        const y1 = v2.y - v1.y;
        const y2 = v3.y - v1.y;
        const z1 = v2.z - v1.z;
        const z2 = v3.z - v1.z;
        const s1 = w2.x - w1.x;
        const s2 = w3.x - w1.x;
        const t1 = w2.y - w1.y;
        const t2 = w3.y - w1.y;
        const area = s1 * t2 - s2 * t1;
        // Area can be 0 for degenerate triangles or bad uv coordinates
        if (area === 0) {
            // Fallback to default values
            sdir.set(0, 1, 0);
            tdir.set(1, 0, 0);
        } else {
            const r = 1 / area;
            sdir.set((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
            tdir.set((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
        }
        tan1[i1 * 3 + 0] += sdir.x;
        tan1[i1 * 3 + 1] += sdir.y;
        tan1[i1 * 3 + 2] += sdir.z;
        tan1[i2 * 3 + 0] += sdir.x;
        tan1[i2 * 3 + 1] += sdir.y;
        tan1[i2 * 3 + 2] += sdir.z;
        tan1[i3 * 3 + 0] += sdir.x;
        tan1[i3 * 3 + 1] += sdir.y;
        tan1[i3 * 3 + 2] += sdir.z;
        tan2[i1 * 3 + 0] += tdir.x;
        tan2[i1 * 3 + 1] += tdir.y;
        tan2[i1 * 3 + 2] += tdir.z;
        tan2[i2 * 3 + 0] += tdir.x;
        tan2[i2 * 3 + 1] += tdir.y;
        tan2[i2 * 3 + 2] += tdir.z;
        tan2[i3 * 3 + 0] += tdir.x;
        tan2[i3 * 3 + 1] += tdir.y;
        tan2[i3 * 3 + 2] += tdir.z;
    }
    const t1 = new Vec3();
    const t2 = new Vec3();
    const n = new Vec3();
    const temp = new Vec3();
    for(let i = 0; i < vertexCount; i++){
        n.set(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
        t1.set(tan1[i * 3], tan1[i * 3 + 1], tan1[i * 3 + 2]);
        t2.set(tan2[i * 3], tan2[i * 3 + 1], tan2[i * 3 + 2]);
        // Gram-Schmidt orthogonalize
        const ndott = n.dot(t1);
        temp.copy(n).mulScalar(ndott);
        temp.sub2(t1, temp).normalize();
        tangents[i * 4] = temp.x;
        tangents[i * 4 + 1] = temp.y;
        tangents[i * 4 + 2] = temp.z;
        // Calculate handedness
        temp.cross(n, t1);
        tangents[i * 4 + 3] = temp.dot(t2) < 0.0 ? -1 : 1.0;
    }
    return tangents;
};

/**
 * The Geometry class serves as a container for storing geometric information. It encapsulates data
 * such as positions, normals, colors, and indices.
 *
 * @category Graphics
 */ class Geometry {
    /**
     * Generates normal information from the positions and triangle indices.
     */ calculateNormals() {
        Debug.assert(this.positions, 'Geometry must have positions set');
        Debug.assert(this.indices, 'Geometry must have indices set');
        this.normals = calculateNormals(this.positions, this.indices);
    }
    /**
     * Generates tangent information from the positions, normals, texture coordinates and triangle
     * indices.
     */ calculateTangents() {
        Debug.assert(this.positions, 'Geometry must have positions set');
        Debug.assert(this.normals, 'Geometry must have normals set');
        Debug.assert(this.uvs, 'Geometry must have uvs set');
        Debug.assert(this.indices, 'Geometry must have indices set');
        this.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);
    }
}

const primitiveUv1Padding$1 = 4.0 / 64;
const primitiveUv1PaddingScale$1 = 1.0 - primitiveUv1Padding$1 * 2;
/**
 * A procedural box-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a BoxGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.BoxGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class BoxGeometry extends Geometry {
    /**
     * Create a new BoxGeometry instance.
     *
     * By default, the constructor creates a box centered on the object space origin with a width,
     * length and height of 1 unit and 1 segment in either axis (2 triangles per face). The box is
     * created with UVs in the range of 0 to 1 on each face.
     *
     * @param {object} [opts] - Options object.
     * @param {Vec3} [opts.halfExtents] - The half dimensions of the box in each axis. Defaults to
     * [0.5, 0.5, 0.5].
     * @param {number} [opts.widthSegments] - The number of divisions along the X axis of the box.
     * Defaults to 1.
     * @param {number} [opts.lengthSegments] - The number of divisions along the Z axis of the box.
     * Defaults to 1.
     * @param {number} [opts.heightSegments] - The number of divisions along the Y axis of the box.
     * Defaults to 1.
     * @param {boolean} [opts.calculateTangents] - Generate tangent information. Defaults to false.
     * @param {number} [opts.yOffset] - Move the box vertically by given offset in local space. Pass
     * 0.5 to generate the box with pivot point at the bottom face. Defaults to 0.
     * @example
     * const geometry = new pc.BoxGeometry({
     *     halfExtents: new pc.Vec3(1, 1, 1),
     *     widthSegments: 2,
     *     lengthSegments: 2,
     *     heightSegments: 2
     * });
     */ constructor(opts = {}){
        super();
        // Check the supplied options and provide defaults for unspecified ones
        const he = opts.halfExtents ?? new Vec3(0.5, 0.5, 0.5);
        const ws = opts.widthSegments ?? 1;
        const ls = opts.lengthSegments ?? 1;
        const hs = opts.heightSegments ?? 1;
        const yOffset = opts.yOffset ?? 0;
        const minY = -he.y + yOffset;
        const maxY = he.y + yOffset;
        const corners = [
            new Vec3(-he.x, minY, he.z),
            new Vec3(he.x, minY, he.z),
            new Vec3(he.x, maxY, he.z),
            new Vec3(-he.x, maxY, he.z),
            new Vec3(he.x, minY, -he.z),
            new Vec3(-he.x, minY, -he.z),
            new Vec3(-he.x, maxY, -he.z),
            new Vec3(he.x, maxY, -he.z)
        ];
        const faceAxes = [
            [
                0,
                1,
                3
            ],
            [
                4,
                5,
                7
            ],
            [
                3,
                2,
                6
            ],
            [
                1,
                0,
                4
            ],
            [
                1,
                4,
                2
            ],
            [
                5,
                0,
                6
            ] // LEFT
        ];
        const faceNormals = [
            [
                0,
                0,
                1
            ],
            [
                0,
                0,
                -1
            ],
            [
                0,
                1,
                0
            ],
            [
                0,
                -1,
                0
            ],
            [
                1,
                0,
                0
            ],
            [
                -1,
                0,
                0
            ] // LEFT
        ];
        const sides = {
            FRONT: 0,
            BACK: 1,
            TOP: 2,
            BOTTOM: 3,
            RIGHT: 4,
            LEFT: 5
        };
        const positions = [];
        const normals = [];
        const uvs = [];
        const indices = [];
        let vcounter = 0;
        const generateFace = (side, uSegments, vSegments)=>{
            const temp1 = new Vec3();
            const temp2 = new Vec3();
            const temp3 = new Vec3();
            const r = new Vec3();
            for(let i = 0; i <= uSegments; i++){
                for(let j = 0; j <= vSegments; j++){
                    temp1.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][1]], i / uSegments);
                    temp2.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][2]], j / vSegments);
                    temp3.sub2(temp2, corners[faceAxes[side][0]]);
                    r.add2(temp1, temp3);
                    let u = i / uSegments;
                    let v = j / vSegments;
                    positions.push(r.x, r.y, r.z);
                    normals.push(faceNormals[side][0], faceNormals[side][1], faceNormals[side][2]);
                    uvs.push(u, 1 - v);
                    // pack as 3x2. 1/3 will be empty, but it's either that or stretched pixels
                    // TODO: generate non-rectangular lightMaps, so we could use space without stretching
                    u = u * primitiveUv1PaddingScale$1 + primitiveUv1Padding$1;
                    v = v * primitiveUv1PaddingScale$1 + primitiveUv1Padding$1;
                    u /= 3;
                    v /= 3;
                    u += side % 3 / 3;
                    v += Math.floor(side / 3) / 3;
                    if (i < uSegments && j < vSegments) {
                        indices.push(vcounter + vSegments + 1, vcounter + 1, vcounter);
                        indices.push(vcounter + vSegments + 1, vcounter + vSegments + 2, vcounter + 1);
                    }
                    vcounter++;
                }
            }
        };
        generateFace(sides.FRONT, ws, hs);
        generateFace(sides.BACK, ws, hs);
        generateFace(sides.TOP, ws, ls);
        generateFace(sides.BOTTOM, ws, ls);
        generateFace(sides.RIGHT, ls, hs);
        generateFace(sides.LEFT, ls, hs);
        this.positions = positions;
        this.normals = normals;
        this.uvs = uvs;
        this.uvs1 = uvs; // UV1 = UV0 for sphere
        this.indices = indices;
        if (opts.calculateTangents) {
            this.tangents = calculateTangents(positions, normals, uvs, indices);
        }
    }
}

/**
 * A procedural sphere-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a SphereGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.SphereGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class SphereGeometry extends Geometry {
    /**
     * Create a new SphereGeometry instance.
     *
     * By default, the constructor creates a sphere centered on the object space origin with a radius
     * of 0.5 and 16 segments in both longitude and latitude. The sphere is created with UVs in the
     * range of 0 to 1.
     *
     * @param {object} [opts] - Options object.
     * @param {number} [opts.radius] - The radius of the sphere. Defaults to 0.5.
     * @param {number} [opts.latitudeBands] - The number of divisions along the latitudinal axis of
     * the sphere. Defaults to 16.
     * @param {number} [opts.longitudeBands] - The number of divisions along the longitudinal axis of
     * the sphere. Defaults to 16.
     * @param {boolean} [opts.calculateTangents] - Generate tangent information. Defaults to false.
     * @example
     * const geometry = new pc.SphereGeometry({
     *     radius: 1,
     *     latitudeBands: 32,
     *     longitudeBands: 32
     * });
     */ constructor(opts = {}){
        super();
        // Check the supplied options and provide defaults for unspecified ones
        const radius = opts.radius ?? 0.5;
        const latitudeBands = opts.latitudeBands ?? 16;
        const longitudeBands = opts.longitudeBands ?? 16;
        // Variable declarations
        const positions = [];
        const normals = [];
        const uvs = [];
        const indices = [];
        for(let lat = 0; lat <= latitudeBands; lat++){
            const theta = lat * Math.PI / latitudeBands;
            const sinTheta = Math.sin(theta);
            const cosTheta = Math.cos(theta);
            for(let lon = 0; lon <= longitudeBands; lon++){
                // Sweep the sphere from the positive Z axis to match a 3DS Max sphere
                const phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;
                const sinPhi = Math.sin(phi);
                const cosPhi = Math.cos(phi);
                const x = cosPhi * sinTheta;
                const y = cosTheta;
                const z = sinPhi * sinTheta;
                const u = 1 - lon / longitudeBands;
                const v = 1 - lat / latitudeBands;
                positions.push(x * radius, y * radius, z * radius);
                normals.push(x, y, z);
                uvs.push(u, 1 - v);
            }
        }
        for(let lat = 0; lat < latitudeBands; ++lat){
            for(let lon = 0; lon < longitudeBands; ++lon){
                const first = lat * (longitudeBands + 1) + lon;
                const second = first + longitudeBands + 1;
                indices.push(first + 1, second, first);
                indices.push(first + 1, second + 1, second);
            }
        }
        this.positions = positions;
        this.normals = normals;
        this.uvs = uvs;
        this.uvs1 = uvs; // UV1 = UV0 for sphere
        this.indices = indices;
        if (opts.calculateTangents) {
            this.tangents = calculateTangents(positions, normals, uvs, indices);
        }
    }
}

/**
 * A procedural dome-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a DomeGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.DomeGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class DomeGeometry extends SphereGeometry {
    /**
     * Create a new DomeGeometry instance.
     *
     * By default, the constructor creates a dome with a radius of 0.5, 16 latitude bands and 16
     * longitude bands. The dome is created with UVs in the range of 0 to 1.
     *
     * @param {object} [opts] - Options object.
     * @param {number} [opts.latitudeBands] - The number of divisions along the latitudinal axis of
     * the sphere. Defaults to 16.
     * @param {number} [opts.longitudeBands] - The number of divisions along the longitudinal axis of
     * the sphere. Defaults to 16.
     * @example
     * const geometry = new pc.DomeGeometry({
     *     latitudeBands: 32,
     *     longitudeBands: 32
     * });
     */ constructor(opts = {}){
        // create a sphere geometry
        const radius = 0.5; // the math and constants are based on a unit sphere
        const latitudeBands = opts.latitudeBands ?? 16;
        const longitudeBands = opts.longitudeBands ?? 16;
        super({
            radius,
            latitudeBands,
            longitudeBands
        });
        // post-process the geometry to flatten the bottom hemisphere
        const bottomLimit = 0.1; // flatten bottom y-coordinate
        const curvatureRadius = 0.95; // normalized distance from the center that is completely flat
        const curvatureRadiusSq = curvatureRadius * curvatureRadius; // derived values
        const positions = this.positions;
        for(let i = 0; i < positions.length; i += 3){
            const x = positions[i] / radius;
            let y = positions[i + 1] / radius;
            const z = positions[i + 2] / radius;
            // flatten the lower hemisphere
            if (y < 0) {
                // scale vertices on the bottom
                y *= 0.3;
                // flatten the center
                if (x * x + z * z < curvatureRadiusSq) {
                    y = -0.1;
                }
            }
            // adjust y to have the center at the flat bottom
            y += bottomLimit;
            y *= radius;
            positions[i + 1] = y;
        }
    }
}

class SkyGeometry {
    static create(device, type) {
        switch(type){
            case SKYTYPE_BOX:
                return SkyGeometry.box(device);
            case SKYTYPE_DOME:
                return SkyGeometry.dome(device);
        }
        Debug.assert(type === SKYTYPE_INFINITE, `Unsupported sky geometry type ${type}`);
        return SkyGeometry.infinite(device);
    }
    static infinite(device) {
        return Mesh.fromGeometry(device, new BoxGeometry(device));
    }
    static box(device) {
        return Mesh.fromGeometry(device, new BoxGeometry({
            yOffset: 0.5
        }));
    }
    static dome(device) {
        const geom = new DomeGeometry({
            latitudeBands: 50,
            longitudeBands: 50
        });
        // remove unused normals and uvs
        geom.normals = undefined;
        geom.uvs = undefined;
        return Mesh.fromGeometry(device, geom);
    }
}

/**
 * @import { GraphNode } from '../graph-node.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { Scene } from '../scene.js'
 * @import { Texture } from '../../platform/graphics/texture.js'
 */ /**
 * A visual representation of the sky.
 *
 * @ignore
 */ class SkyMesh {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {Scene} scene - The scene owning the sky.
     * @param {GraphNode} node - The graph node of the sky mesh instance.
     * @param {Texture} texture - The texture of the sky.
     * @param {string} type - The type of the sky. One of the SKYMESH_* constants.
     */ constructor(device, scene, node, texture, type){
        /**
     * Mesh instance representing the visuals of the sky.
     *
     * @type {MeshInstance|null}
     */ this.meshInstance = null;
        /**
     * @type {boolean}
     */ this._depthWrite = false;
        const material = new ShaderMaterial({
            uniqueName: 'SkyMaterial',
            vertexGLSL: ShaderChunks.get(device, SHADERLANGUAGE_GLSL).get('skyboxVS'),
            fragmentGLSL: ShaderChunks.get(device, SHADERLANGUAGE_GLSL).get('skyboxPS'),
            vertexWGSL: ShaderChunks.get(device, SHADERLANGUAGE_WGSL).get('skyboxVS'),
            fragmentWGSL: ShaderChunks.get(device, SHADERLANGUAGE_WGSL).get('skyboxPS'),
            attributes: {
                aPosition: SEMANTIC_POSITION
            }
        });
        // defines
        material.setDefine('{SKYBOX_DECODE_FNC}', ChunkUtils.decodeFunc(texture.encoding));
        if (type !== SKYTYPE_INFINITE) material.setDefine('SKYMESH', '');
        if (texture.cubemap) material.setDefine('SKY_CUBEMAP', '');
        material.setParameter('skyboxHighlightMultiplier', scene.skyboxHighlightMultiplier);
        if (texture.cubemap) {
            material.setParameter('texture_cubeMap', texture);
        } else {
            material.setParameter('texture_envAtlas', texture);
            material.setParameter('mipLevel', scene.skyboxMip);
        }
        // render inside of the geometry
        material.cull = CULLFACE_FRONT;
        material.depthWrite = this._depthWrite;
        const skyLayer = scene.layers.getLayerById(LAYERID_SKYBOX);
        if (skyLayer) {
            const mesh = SkyGeometry.create(device, type);
            const meshInstance = new MeshInstance(mesh, material, node);
            this.meshInstance = meshInstance;
            meshInstance.cull = false;
            // disable picker, the material has custom update shader and does not handle picker variant
            meshInstance.pick = false;
            skyLayer.addMeshInstances([
                meshInstance
            ]);
            this.skyLayer = skyLayer;
        }
    }
    destroy() {
        if (this.meshInstance) {
            if (this.skyLayer) {
                this.skyLayer.removeMeshInstances([
                    this.meshInstance
                ]);
            }
            this.meshInstance.destroy();
            this.meshInstance = null;
        }
    }
    set depthWrite(value) {
        this._depthWrite = value;
        if (this.meshInstance) {
            this.meshInstance.material.depthWrite = value;
        }
    }
    get depthWrite() {
        return this._depthWrite;
    }
}

/**
 * @import { Scene } from '../scene.js'
 */ /**
 * Implementation of the sky.
 *
 * @category Graphics
 */ class Sky {
    /**
     * Constructs a new sky.
     *
     * @param {Scene} scene - The scene owning the sky.
     * @ignore
     */ constructor(scene){
        /**
     * The type of the sky. One of the SKYMESH_* constants.
     *
     * @type {string}
     * @private
     */ this._type = SKYTYPE_INFINITE;
        /**
     * The center of the sky.
     *
     * @type {Vec3}
     * @private
     */ this._center = new Vec3(0, 1, 0);
        /**
     * The sky mesh of the scene.
     *
     * @type {SkyMesh|null}
     * @ignore
     */ this.skyMesh = null;
        /**
     * @type {boolean}
     * @private
     */ this._depthWrite = false;
        /**
     * A graph node with a transform used to render the sky mesh. Adjust the position, rotation and
     * scale of this node to orient the sky mesh. Ignored for {@link SKYTYPE_INFINITE}.
     *
     * @type {GraphNode}
     * @readonly
     */ this.node = new GraphNode('SkyMeshNode');
        this.device = scene.device;
        this.scene = scene;
        // defaults
        this.center = new Vec3(0, 1, 0);
        this.centerArray = new Float32Array(3);
        this.projectedSkydomeCenterId = this.device.scope.resolve('projectedSkydomeCenter');
    }
    applySettings(render) {
        this.type = render.skyType ?? SKYTYPE_INFINITE;
        this.node.setLocalPosition(new Vec3(render.skyMeshPosition ?? [
            0,
            0,
            0
        ]));
        this.node.setLocalEulerAngles(new Vec3(render.skyMeshRotation ?? [
            0,
            0,
            0
        ]));
        this.node.setLocalScale(new Vec3(render.skyMeshScale ?? [
            1,
            1,
            1
        ]));
        if (render.skyCenter) {
            this._center = new Vec3(render.skyCenter);
        }
    }
    /**
     * The type of the sky. One of the SKYMESH_* constants. Defaults to {@link SKYTYPE_INFINITE}.
     * Can be:
     *
     * - {@link SKYTYPE_INFINITE}
     * - {@link SKYTYPE_BOX}
     * - {@link SKYTYPE_DOME}
     *
     * @type {string}
     */ set type(value) {
        if (this._type !== value) {
            this._type = value;
            this.scene.updateShaders = true;
            this.updateSkyMesh();
        }
    }
    get type() {
        return this._type;
    }
    /**
     * The center of the sky. Ignored for {@link SKYTYPE_INFINITE}. Typically only the y-coordinate
     * is used, representing the tripod height. Defaults to (0, 1, 0).
     *
     * @type {Vec3}
     */ set center(value) {
        this._center.copy(value);
    }
    get center() {
        return this._center;
    }
    /**
     * Whether depth writing is enabled for the sky. Defaults to false.
     *
     * Writing a depth value for the skydome is supported when its type is not
     * {@link SKYTYPE_INFINITE}. When enabled, the depth is written during a prepass render pass and
     * can be utilized by subsequent passes to apply depth-based effects, such as Depth of Field.
     *
     * Note: For the skydome to be rendered during the prepass, the Sky Layer must be ordered before
     * the Depth layer, which is the final layer used in the prepass.
     *
     * @type {boolean}
     */ set depthWrite(value) {
        if (this._depthWrite !== value) {
            this._depthWrite = value;
            if (this.skyMesh) {
                this.skyMesh.depthWrite = value;
            }
        }
    }
    /**
     * Returns whether depth writing is enabled for the sky.
     *
     * @type {boolean}
     */ get depthWrite() {
        return this._depthWrite;
    }
    updateSkyMesh() {
        const texture = this.scene._getSkyboxTex();
        if (texture) {
            this.resetSkyMesh();
            this.skyMesh = new SkyMesh(this.device, this.scene, this.node, texture, this.type);
            this.skyMesh.depthWrite = this._depthWrite;
            this.scene.fire('set:skybox', texture);
        }
    }
    resetSkyMesh() {
        this.skyMesh?.destroy();
        this.skyMesh = null;
    }
    update() {
        // uniforms
        if (this.type !== SKYTYPE_INFINITE) {
            const { center, centerArray } = this;
            // tripod position is relative to the node, transform it to the world space
            const temp = new Vec3();
            this.node.getWorldTransform().transformPoint(center, temp);
            centerArray[0] = temp.x;
            centerArray[1] = temp.y;
            centerArray[2] = temp.z;
            this.projectedSkydomeCenterId.setValue(centerArray);
        }
    }
}

const identityGraphNode = new GraphNode();
identityGraphNode.worldTransform = Mat4.IDENTITY;
identityGraphNode._dirtyWorld = identityGraphNode._dirtyNormal = false;
// helper class storing data for a single batch of line rendering using a single material
class ImmediateBatch {
    constructor(device, material, layer){
        this.material = material;
        this.layer = layer;
        // line data, arrays of numbers
        this.positions = [];
        this.colors = [];
        this.mesh = new Mesh(device);
        this.meshInstance = null;
    }
    // add line positions and colors to the batch
    // this function expects position in Vec3 and colors in Color format
    addLines(positions, color) {
        // positions
        const destPos = this.positions;
        const count = positions.length;
        for(let i = 0; i < count; i++){
            const pos = positions[i];
            destPos.push(pos.x, pos.y, pos.z);
        }
        // colors
        const destCol = this.colors;
        if (color.length) {
            // multi colored line
            for(let i = 0; i < count; i++){
                const col = color[i];
                destCol.push(col.r, col.g, col.b, col.a);
            }
        } else {
            // single colored line
            for(let i = 0; i < count; i++){
                destCol.push(color.r, color.g, color.b, color.a);
            }
        }
    }
    // add line positions and colors to the batch
    // this function expects positions as arrays of numbers
    // and color as instance of Color or array of number specifying the same number of vertices as positions
    addLinesArrays(positions, color) {
        // positions
        const destPos = this.positions;
        for(let i = 0; i < positions.length; i += 3){
            destPos.push(positions[i], positions[i + 1], positions[i + 2]);
        }
        // colors
        const destCol = this.colors;
        if (color.length) {
            for(let i = 0; i < color.length; i += 4){
                destCol.push(color[i], color[i + 1], color[i + 2], color[i + 3]);
            }
        } else {
            // single colored line
            const count = positions.length / 3;
            for(let i = 0; i < count; i++){
                destCol.push(color.r, color.g, color.b, color.a);
            }
        }
    }
    onPreRender(visibleList, transparent) {
        // prepare mesh if its transparency matches
        if (this.positions.length > 0 && this.material.transparent === transparent) {
            // update mesh vertices
            this.mesh.setPositions(this.positions);
            this.mesh.setColors(this.colors);
            this.mesh.update(PRIMITIVE_LINES, false);
            if (!this.meshInstance) {
                this.meshInstance = new MeshInstance(this.mesh, this.material, identityGraphNode);
            }
            // inject mesh instance into visible list to be rendered
            visibleList.push(this.meshInstance);
        }
    }
    clear() {
        // clear lines after they are rendered as their lifetime is one frame
        this.positions.length = 0;
        this.colors.length = 0;
    }
}

// helper class storing line batches for a single layer
class ImmediateBatches {
    constructor(device){
        this.device = device;
        // dictionary of Material to ImmediateBatch mapping
        this.map = new Map();
    }
    getBatch(material, layer) {
        let batch = this.map.get(material);
        if (!batch) {
            batch = new ImmediateBatch(this.device, material, layer);
            this.map.set(material, batch);
        }
        return batch;
    }
    onPreRender(visibleList, transparent) {
        this.map.forEach((batch)=>{
            batch.onPreRender(visibleList, transparent);
        });
    }
    clear() {
        this.map.forEach((batch)=>batch.clear());
    }
}

const tempPoints = [];
const vec$3 = new Vec3();
class Immediate {
    constructor(device){
        this.shaderDescs = new Map();
        this.device = device;
        this.quadMesh = null;
        this.textureShader = null;
        this.depthTextureShader = null;
        this.cubeLocalPos = null;
        this.cubeWorldPos = null;
        // map of Layer to ImmediateBatches, storing line batches for a layer
        this.batchesMap = new Map();
        // set of all batches that were used in the frame
        this.allBatches = new Set();
        // set of all layers updated during this frame
        this.updatedLayers = new Set();
        // line materials
        this._materialDepth = null;
        this._materialNoDepth = null;
        // map of meshes instances added to a layer. The key is layer, the value is an array of mesh instances
        this.layerMeshInstances = new Map();
    }
    // creates material for line rendering
    createMaterial(depthTest) {
        const material = new ShaderMaterial({
            uniqueName: 'ImmediateLine',
            vertexGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('immediateLineVS'),
            fragmentGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('immediateLinePS'),
            vertexWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('immediateLineVS'),
            fragmentWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('immediateLinePS'),
            attributes: {
                vertex_position: SEMANTIC_POSITION,
                vertex_color: SEMANTIC_COLOR
            }
        });
        material.blendType = BLEND_NORMAL;
        material.depthTest = depthTest;
        material.update();
        return material;
    }
    // material for line rendering with depth testing on
    get materialDepth() {
        if (!this._materialDepth) {
            this._materialDepth = this.createMaterial(true);
        }
        return this._materialDepth;
    }
    // material for line rendering with depth testing off
    get materialNoDepth() {
        if (!this._materialNoDepth) {
            this._materialNoDepth = this.createMaterial(false);
        }
        return this._materialNoDepth;
    }
    // returns a batch for rendering lines to a layer with required depth testing state
    getBatch(layer, depthTest) {
        // get batches for the layer
        let batches = this.batchesMap.get(layer);
        if (!batches) {
            batches = new ImmediateBatches(this.device);
            this.batchesMap.set(layer, batches);
        }
        // add it for rendering
        this.allBatches.add(batches);
        // get batch for the material
        const material = depthTest ? this.materialDepth : this.materialNoDepth;
        return batches.getBatch(material, layer);
    }
    getShaderDesc(id, fragmentGLSL, fragmentWGSL) {
        if (!this.shaderDescs.has(id)) {
            this.shaderDescs.set(id, {
                uniqueName: `DebugShader:${id}`,
                // shared vertex shader for textured quad rendering
                vertexGLSL: /* glsl */ `
                    attribute vec2 vertex_position;
                    uniform mat4 matrix_model;
                    varying vec2 uv0;
                    void main(void) {
                        gl_Position = matrix_model * vec4(vertex_position, 0, 1);
                        uv0 = vertex_position.xy + 0.5;
                    }
                `,
                vertexWGSL: /* wgsl */ `
                    attribute vertex_position: vec2f;
                    uniform matrix_model: mat4x4f;
                    varying uv0: vec2f;
                    @vertex fn vertexMain(input: VertexInput) -> VertexOutput {
                        var output: VertexOutput;
                        output.position = uniform.matrix_model * vec4f(input.vertex_position, 0.0, 1.0);
                        output.uv0 = input.vertex_position.xy + vec2f(0.5);
                        return output;
                    }
                `,
                fragmentGLSL: fragmentGLSL,
                fragmentWGSL: fragmentWGSL,
                attributes: {
                    vertex_position: SEMANTIC_POSITION
                }
            });
        }
        return this.shaderDescs.get(id);
    }
    // shader used to display texture
    getTextureShaderDesc(encoding) {
        const decodeFunc = ChunkUtils.decodeFunc(encoding);
        return this.getShaderDesc(`textureShader-${encoding}`, /* glsl */ `
            #include "gammaPS"
            varying vec2 uv0;
            uniform sampler2D colorMap;
            void main (void) {
                vec3 linearColor = ${decodeFunc}(texture2D(colorMap, uv0));
                gl_FragColor = vec4(gammaCorrectOutput(linearColor), 1);
            }
        `, /* wgsl */ `
            #include "gammaPS"
            varying uv0: vec2f;
            var colorMap: texture_2d<f32>;
            var colorMapSampler: sampler;
            @fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput {
                var output: FragmentOutput;
                let sampledTex = textureSample(colorMap, colorMapSampler, input.uv0);
                let linearColor: vec3f = ${decodeFunc}(sampledTex);
                output.color = vec4f(gammaCorrectOutput(linearColor), 1.0);
                return output;
            }
        `);
    }
    // shader used to display infilterable texture sampled using texelFetch
    getUnfilterableTextureShaderDesc() {
        return this.getShaderDesc('textureShaderUnfilterable', /* glsl */ `
            varying vec2 uv0;
            uniform highp sampler2D colorMap;
            void main (void) {
                ivec2 uv = ivec2(uv0 * textureSize(colorMap, 0));
                gl_FragColor = vec4(texelFetch(colorMap, uv, 0).xyz, 1);
            }
        `, /* wgsl */ `

            varying uv0: vec2f;
            var colorMap: texture_2d<uff>;
            @fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput {
                var output: FragmentOutput;
                let uv : vec2<i32> = vec2<i32>(input.uv0 * vec2f(textureDimensions(colorMap, 0)));
                let fetchedColor : vec4f = textureLoad(colorMap, uv, 0);
                output.color = vec4f(fetchedColor.xyz, 1.0);
                return output;
            }
        `);
    }
    // shader used to display depth texture
    getDepthTextureShaderDesc() {
        return this.getShaderDesc('depthTextureShader', /* glsl */ `
            #include "screenDepthPS"
            #include "gammaPS"
            varying vec2 uv0;
            void main() {
                float depth = getLinearScreenDepth(getImageEffectUV(uv0)) * camera_params.x;
                gl_FragColor = vec4(gammaCorrectOutput(vec3(depth)), 1.0);
            }
        `, /* wgsl */ `
            #include "screenDepthPS"
            #include "gammaPS"
            varying uv0: vec2f;
            @fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput {
                var output: FragmentOutput;
                let depth: f32 = getLinearScreenDepth(getImageEffectUV(input.uv0)) * uniform.camera_params.x;
                output.color = vec4f(gammaCorrectOutput(vec3f(depth)), 1.0);
                return output;
            }
        `);
    }
    // creates mesh used to render a quad
    getQuadMesh() {
        if (!this.quadMesh) {
            this.quadMesh = new Mesh(this.device);
            this.quadMesh.setPositions([
                -0.5,
                -0.5,
                0,
                0.5,
                -0.5,
                0,
                -0.5,
                0.5,
                0,
                0.5,
                0.5,
                0
            ]);
            this.quadMesh.update(PRIMITIVE_TRISTRIP);
        }
        return this.quadMesh;
    }
    // Draw mesh at this frame
    drawMesh(material, matrix, mesh, meshInstance, layer) {
        // create a mesh instance for the mesh if needed
        if (!meshInstance) {
            const graphNode = this.getGraphNode(matrix);
            meshInstance = new MeshInstance(mesh, material, graphNode);
        }
        // add the mesh instance to an array per layer, they get added to layers before rendering
        let layerMeshInstances = this.layerMeshInstances.get(layer);
        if (!layerMeshInstances) {
            layerMeshInstances = [];
            this.layerMeshInstances.set(layer, layerMeshInstances);
        }
        layerMeshInstances.push(meshInstance);
    }
    drawWireAlignedBox(min, max, color, depthTest, layer, mat) {
        if (mat) {
            const mulPoint = (x, y, z)=>{
                vec$3.set(x, y, z);
                mat.transformPoint(vec$3, vec$3);
                tempPoints.push(vec$3.x, vec$3.y, vec$3.z);
            };
            mulPoint(min.x, min.y, min.z);
            mulPoint(min.x, max.y, min.z);
            mulPoint(min.x, max.y, min.z);
            mulPoint(max.x, max.y, min.z);
            mulPoint(max.x, max.y, min.z);
            mulPoint(max.x, min.y, min.z);
            mulPoint(max.x, min.y, min.z);
            mulPoint(min.x, min.y, min.z);
            mulPoint(min.x, min.y, max.z);
            mulPoint(min.x, max.y, max.z);
            mulPoint(min.x, max.y, max.z);
            mulPoint(max.x, max.y, max.z);
            mulPoint(max.x, max.y, max.z);
            mulPoint(max.x, min.y, max.z);
            mulPoint(max.x, min.y, max.z);
            mulPoint(min.x, min.y, max.z);
            mulPoint(min.x, min.y, min.z);
            mulPoint(min.x, min.y, max.z);
            mulPoint(min.x, max.y, min.z);
            mulPoint(min.x, max.y, max.z);
            mulPoint(max.x, max.y, min.z);
            mulPoint(max.x, max.y, max.z);
            mulPoint(max.x, min.y, min.z);
            mulPoint(max.x, min.y, max.z);
        } else {
            tempPoints.push(min.x, min.y, min.z, min.x, max.y, min.z, min.x, max.y, min.z, max.x, max.y, min.z, max.x, max.y, min.z, max.x, min.y, min.z, max.x, min.y, min.z, min.x, min.y, min.z, min.x, min.y, max.z, min.x, max.y, max.z, min.x, max.y, max.z, max.x, max.y, max.z, max.x, max.y, max.z, max.x, min.y, max.z, max.x, min.y, max.z, min.x, min.y, max.z, min.x, min.y, min.z, min.x, min.y, max.z, min.x, max.y, min.z, min.x, max.y, max.z, max.x, max.y, min.z, max.x, max.y, max.z, max.x, min.y, min.z, max.x, min.y, max.z);
        }
        const batch = this.getBatch(layer, depthTest);
        batch.addLinesArrays(tempPoints, color);
        tempPoints.length = 0;
    }
    drawWireSphere(center, radius, color, numSegments, depthTest, layer) {
        const step = 2 * Math.PI / numSegments;
        let angle = 0;
        for(let i = 0; i < numSegments; i++){
            const sin0 = Math.sin(angle);
            const cos0 = Math.cos(angle);
            angle += step;
            const sin1 = Math.sin(angle);
            const cos1 = Math.cos(angle);
            tempPoints.push(center.x + radius * sin0, center.y, center.z + radius * cos0);
            tempPoints.push(center.x + radius * sin1, center.y, center.z + radius * cos1);
            tempPoints.push(center.x + radius * sin0, center.y + radius * cos0, center.z);
            tempPoints.push(center.x + radius * sin1, center.y + radius * cos1, center.z);
            tempPoints.push(center.x, center.y + radius * sin0, center.z + radius * cos0);
            tempPoints.push(center.x, center.y + radius * sin1, center.z + radius * cos1);
        }
        const batch = this.getBatch(layer, depthTest);
        batch.addLinesArrays(tempPoints, color);
        tempPoints.length = 0;
    }
    getGraphNode(matrix) {
        const graphNode = new GraphNode('ImmediateDebug');
        graphNode.worldTransform = matrix;
        graphNode._dirtyWorld = graphNode._dirtyNormal = false;
        return graphNode;
    }
    // This is called just before the layer is rendered to allow lines for the layer to be added from inside
    // the frame getting rendered
    onPreRenderLayer(layer, visibleList, transparent) {
        // update line batches for the specified sub-layer
        this.batchesMap.forEach((batches, batchLayer)=>{
            if (batchLayer === layer) {
                batches.onPreRender(visibleList, transparent);
            }
        });
        // only update meshes once for each layer (they're not per sub-layer at the moment)
        if (!this.updatedLayers.has(layer)) {
            this.updatedLayers.add(layer);
            // add mesh instances for specified layer to visible list
            const meshInstances = this.layerMeshInstances.get(layer);
            if (meshInstances) {
                for(let i = 0; i < meshInstances.length; i++){
                    visibleList.push(meshInstances[i]);
                }
                meshInstances.length = 0;
            }
        }
    }
    // called after the frame was rendered, clears data
    onPostRender() {
        // clean up line batches
        this.allBatches.forEach((batch)=>batch.clear());
        this.allBatches.clear();
        // all batches need updating next frame
        this.updatedLayers.clear();
    }
}

/**
 * Fog parameters.
 *
 * @category Graphics
 */ class FogParams {
    constructor(){
        /**
     * The type of fog used by the scene. Can be:
     *
     * - {@link FOG_NONE}
     * - {@link FOG_LINEAR}
     * - {@link FOG_EXP}
     * - {@link FOG_EXP2}
     *
     * Defaults to {@link FOG_NONE}.
     *
     * @type {string}
     */ this.type = FOG_NONE;
        /**
     * The color of the fog (if enabled), specified in sRGB color space. Defaults to black (0, 0, 0).
     *
     * @type {Color}
     */ this.color = new Color(0, 0, 0);
        /**
     * The density of the fog (if enabled). This property is only valid if the fog property is set
     * to {@link FOG_EXP} or {@link FOG_EXP2}. Defaults to 0.
     *
     * @type {number}
     */ this.density = 0;
        /**
     * The distance from the viewpoint where linear fog begins. This property is only valid if the
     * fog property is set to {@link FOG_LINEAR}. Defaults to 1.
     *
     * @type {number}
     */ this.start = 1;
        /**
     * The distance from the viewpoint where linear fog reaches its maximum. This property is only
     * valid if the fog property is set to {@link FOG_LINEAR}. Defaults to 1000.
     *
     * @type {number}
     */ this.end = 1000;
    }
}

/**
 * @import { Entity } from '../framework/entity.js'
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 * @import { LayerComposition } from './composition/layer-composition.js'
 * @import { Layer } from './layer.js'
 * @import { Texture } from '../platform/graphics/texture.js'
 */ /**
 * A scene is graphical representation of an environment. It manages the scene hierarchy, all
 * graphical objects, lights, and scene-wide properties.
 *
 * @category Graphics
 */ let Scene$1 = class Scene extends EventHandler {
    static{
        /**
     * Fired when the layer composition is set. Use this event to add callbacks or advanced
     * properties to your layers. The handler is passed the old and the new
     * {@link LayerComposition}.
     *
     * @event
     * @example
     * app.scene.on('set:layers', (oldComp, newComp) => {
     *     const list = newComp.layerList;
     *     for (let i = 0; i < list.length; i++) {
     *         const layer = list[i];
     *         switch (layer.name) {
     *             case 'MyLayer':
     *                 layer.onEnable = myOnEnableFunction;
     *                 layer.onDisable = myOnDisableFunction;
     *                 break;
     *             case 'MyOtherLayer':
     *                 layer.clearColorBuffer = true;
     *                 break;
     *         }
     *     }
     * });
     */ this.EVENT_SETLAYERS = 'set:layers';
    }
    static{
        /**
     * Fired when the skybox is set. The handler is passed the {@link Texture} that is the
     * previously used skybox cubemap texture. The new skybox cubemap texture is in the
     * {@link Scene#skybox} property.
     *
     * @event
     * @example
     * app.scene.on('set:skybox', (oldSkybox) => {
     *     console.log(`Skybox changed from ${oldSkybox.name} to ${app.scene.skybox.name}`);
     * });
     */ this.EVENT_SETSKYBOX = 'set:skybox';
    }
    static{
        /**
     * Fired before the camera renders the scene. The handler is passed the {@link CameraComponent}
     * that will render the scene.
     *
     * @event
     * @example
     * app.scene.on('prerender', (camera) => {
     *    console.log(`Camera ${camera.entity.name} will render the scene`);
     * });
     */ this.EVENT_PRERENDER = 'prerender';
    }
    static{
        /**
     * Fired when the camera renders the scene. The handler is passed the {@link CameraComponent}
     * that rendered the scene.
     *
     * @event
     * @example
     * app.scene.on('postrender', (camera) => {
     *    console.log(`Camera ${camera.entity.name} rendered the scene`);
     * });
     */ this.EVENT_POSTRENDER = 'postrender';
    }
    static{
        /**
     * Fired before the camera renders a layer. The handler is passed the {@link CameraComponent},
     * the {@link Layer} that will be rendered, and a boolean parameter set to true if the layer is
     * transparent. This is called during rendering to a render target or a default framebuffer, and
     * additional rendering can be performed here, for example using {@link QuadRender#render}.
     *
     * @event
     * @example
     * app.scene.on('prerender:layer', (camera, layer, transparent) => {
     *    console.log(`Camera ${camera.entity.name} will render the layer ${layer.name} (transparent: ${transparent})`);
     * });
     */ this.EVENT_PRERENDER_LAYER = 'prerender:layer';
    }
    static{
        /**
     * Fired when the camera renders a layer. The handler is passed the {@link CameraComponent},
     * the {@link Layer} that will be rendered, and a boolean parameter set to true if the layer is
     * transparent. This is called during rendering to a render target or a default framebuffer, and
     * additional rendering can be performed here, for example using {@link QuadRender#render}.
     *
     * @event
     * @example
     * app.scene.on('postrender:layer', (camera, layer, transparent) => {
     *    console.log(`Camera ${camera.entity.name} rendered the layer ${layer.name} (transparent: ${transparent})`);
     * });
     */ this.EVENT_POSTRENDER_LAYER = 'postrender:layer';
    }
    static{
        /**
     * Fired before visibility culling is performed for the camera.
     *
     * @event
     * @example
     * app.scene.on('precull', (camera) => {
     *    console.log(`Visibility culling will be performed for camera ${camera.entity.name}`);
     * });
     */ this.EVENT_PRECULL = 'precull';
    }
    static{
        /**
     * Fired after visibility culling is performed for the camera.
     *
     * @event
     * @example
     * app.scene.on('postcull', (camera) => {
     *    console.log(`Visibility culling was performed for camera ${camera.entity.name}`);
     * });
     */ this.EVENT_POSTCULL = 'postcull';
    }
    /**
     * Create a new Scene instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this scene.
     * @ignore
     */ constructor(graphicsDevice){
        super(), /**
     * If enabled, the ambient lighting will be baked into lightmaps. This will be either the
     * {@link Scene#skybox} if set up, otherwise {@link Scene#ambientLight}. Defaults to false.
     *
     * @type {boolean}
     */ this.ambientBake = false, /**
     * If {@link Scene#ambientBake} is true, this specifies the brightness of ambient occlusion.
     * Typical range is -1 to 1. Defaults to 0, representing no change to brightness.
     *
     * @type {number}
     */ this.ambientBakeOcclusionBrightness = 0, /**
     * If {@link Scene#ambientBake} is true, this specifies the contrast of ambient occlusion.
     * Typical range is -1 to 1. Defaults to 0, representing no change to contrast.
     *
     * @type {number}
     */ this.ambientBakeOcclusionContrast = 0, /**
     * The color of the scene's ambient light, specified in sRGB color space. Defaults to black
     * (0, 0, 0).
     *
     * @type {Color}
     */ this.ambientLight = new Color(0, 0, 0), /**
     * The luminosity of the scene's ambient light in lux (lm/m^2). Used if physicalUnits is true. Defaults to 0.
     *
     * @type {number}
     */ this.ambientLuminance = 0, /**
     * The exposure value tweaks the overall brightness of the scene. Ignored if physicalUnits is true. Defaults to 1.
     *
     * @type {number}
     */ this.exposure = 1, /**
     * The lightmap resolution multiplier. Defaults to 1.
     *
     * @type {number}
     */ this.lightmapSizeMultiplier = 1, /**
     * The maximum lightmap resolution. Defaults to 2048.
     *
     * @type {number}
     */ this.lightmapMaxResolution = 2048, /**
     * The lightmap baking mode. Can be:
     *
     * - {@link BAKE_COLOR}: single color lightmap
     * - {@link BAKE_COLORDIR}: single color lightmap + dominant light direction (used for bump or
     * specular). Only lights with bakeDir=true will be used for generating the dominant light
     * direction.
     *
     * Defaults to {@link BAKE_COLORDIR}.
     *
     * @type {number}
     */ this.lightmapMode = BAKE_COLORDIR, /**
     * Enables bilateral filter on runtime baked color lightmaps, which removes the noise and
     * banding while preserving the edges. Defaults to false. Note that the filtering takes place
     * in the image space of the lightmap, and it does not filter across lightmap UV space seams,
     * often making the seams more visible. It's important to balance the strength of the filter
     * with number of samples used for lightmap baking to limit the visible artifacts.
     *
     * @type {boolean}
     */ this.lightmapFilterEnabled = false, /**
     * Enables HDR lightmaps. This can result in smoother lightmaps especially when many samples
     * are used. Defaults to false.
     *
     * @type {boolean}
     */ this.lightmapHDR = false, /**
     * The root entity of the scene, which is usually the only child to the {@link Application}
     * root entity.
     *
     * @type {Entity}
     */ this.root = null, /**
     * Use physically based units for cameras and lights. When used, the exposure value is ignored.
     *
     * @type {boolean}
     */ this.physicalUnits = false, /**
     * Environment lighting atlas
     *
     * @type {Texture|null}
     * @private
     */ this._envAtlas = null, /**
     * The skybox cubemap as set by user (gets used when skyboxMip === 0)
     *
     * @type {Texture|null}
     * @private
     */ this._skyboxCubeMap = null, /**
     * The fog parameters.
     *
     * @private
     */ this._fogParams = new FogParams(), /**
     * Internal flag to indicate that the specular (and sheen) maps of standard materials should be
     * assumed to be in a linear space, instead of sRGB. This is used by the editor using engine v2
     * internally to render in a style of engine v1, where spec those textures were specified as
     * linear, while engine 2 assumes they are in sRGB space. This should be removed when the editor
     * no longer supports engine v1 projects.
     *
     * @ignore
     */ this.forcePassThroughSpecular = false;
        Debug.assert(graphicsDevice, 'Scene constructor takes a GraphicsDevice as a parameter, and it was not provided.');
        this.device = graphicsDevice;
        this._gravity = new Vec3(0, -9.8, 0);
        /**
         * @type {LayerComposition}
         * @private
         */ this._layers = null;
        /**
         * Array of 6 prefiltered lighting data cubemaps.
         *
         * @type {Texture[]}
         * @private
         */ this._prefilteredCubemaps = [];
        // internally generated envAtlas owned by the scene
        this._internalEnvAtlas = null;
        this._skyboxIntensity = 1;
        this._skyboxLuminance = 0;
        this._skyboxMip = 0;
        this._skyboxHighlightMultiplier = 1;
        this._skyboxRotationShaderInclude = false;
        this._skyboxRotation = new Quat();
        this._skyboxRotationMat3 = new Mat3();
        this._skyboxRotationMat4 = new Mat4();
        // ambient light lightmapping properties
        this._ambientBakeNumSamples = 1;
        this._ambientBakeSpherePart = 0.4;
        this._lightmapFilterRange = 10;
        this._lightmapFilterSmoothness = 0.2;
        // clustered lighting
        this._clusteredLightingEnabled = true;
        this._lightingParams = new LightingParams(this.device.supportsAreaLights, this.device.maxTextureSize, ()=>{
            this.updateShaders = true;
        });
        // skybox
        this._sky = new Sky(this);
        this._stats = {
            meshInstances: 0,
            lights: 0,
            dynamicLights: 0,
            bakedLights: 0,
            updateShadersTime: 0 // deprecated
        };
        /**
         * This flag indicates changes were made to the scene which may require recompilation of
         * shaders that reference global settings.
         *
         * @type {boolean}
         * @ignore
         */ this.updateShaders = true;
        this._shaderVersion = 0;
        // immediate rendering
        this.immediate = new Immediate(this.device);
    }
    /**
     * Gets the default layer used by the immediate drawing functions.
     *
     * @type {Layer}
     * @ignore
     */ get defaultDrawLayer() {
        return this.layers.getLayerById(LAYERID_IMMEDIATE);
    }
    /**
     * Sets the number of samples used to bake the ambient light into the lightmap. Note that
     * {@link Scene#ambientBake} must be true for this to have an effect. Defaults to 1. Maximum
     * value is 255.
     *
     * @type {number}
     */ set ambientBakeNumSamples(value) {
        this._ambientBakeNumSamples = math.clamp(Math.floor(value), 1, 255);
    }
    /**
     * Gets the number of samples used to bake the ambient light into the lightmap.
     *
     * @type {number}
     */ get ambientBakeNumSamples() {
        return this._ambientBakeNumSamples;
    }
    /**
     * Sets the part of the sphere which represents the source of ambient light. Note that
     * {@link Scene#ambientBake} must be true for this to have an effect. The valid range is 0..1,
     * representing a part of the sphere from top to the bottom. A value of 0.5 represents the
     * upper hemisphere. A value of 1 represents a full sphere. Defaults to 0.4, which is a smaller
     * upper hemisphere as this requires fewer samples to bake.
     *
     * @type {number}
     */ set ambientBakeSpherePart(value) {
        this._ambientBakeSpherePart = math.clamp(value, 0.001, 1);
    }
    /**
     * Gets the part of the sphere which represents the source of ambient light.
     *
     * @type {number}
     */ get ambientBakeSpherePart() {
        return this._ambientBakeSpherePart;
    }
    /**
     * Sets whether clustered lighting is enabled. Set to false before the first frame is rendered
     * to use non-clustered lighting. Defaults to true.
     *
     * @type {boolean}
     */ set clusteredLightingEnabled(value) {
        if (this.device.isWebGPU && !value) {
            Debug.warnOnce('WebGPU currently only supports clustered lighting, and this cannot be disabled.');
            return;
        }
        if (!this._clusteredLightingEnabled && value) {
            console.error('Turning on disabled clustered lighting is not currently supported');
            return;
        }
        this._clusteredLightingEnabled = value;
    }
    /**
     * Gets whether clustered lighting is enabled.
     *
     * @type {boolean}
     */ get clusteredLightingEnabled() {
        return this._clusteredLightingEnabled;
    }
    /**
     * Sets the environment lighting atlas.
     *
     * @type {Texture|null}
     */ set envAtlas(value) {
        if (value !== this._envAtlas) {
            this._envAtlas = value;
            // make sure required options are set up on the texture
            if (value) {
                value.addressU = ADDRESS_CLAMP_TO_EDGE;
                value.addressV = ADDRESS_CLAMP_TO_EDGE;
                value.minFilter = FILTER_LINEAR;
                value.magFilter = FILTER_LINEAR;
                value.mipmaps = false;
            }
            this._prefilteredCubemaps = [];
            if (this._internalEnvAtlas) {
                this._internalEnvAtlas.destroy();
                this._internalEnvAtlas = null;
            }
            this._resetSkyMesh();
        }
    }
    /**
     * Gets the environment lighting atlas.
     *
     * @type {Texture|null}
     */ get envAtlas() {
        return this._envAtlas;
    }
    /**
     * Sets the {@link LayerComposition} that defines rendering order of this scene.
     *
     * @type {LayerComposition}
     */ set layers(layers) {
        const prev = this._layers;
        this._layers = layers;
        this.fire('set:layers', prev, layers);
    }
    /**
     * Gets the {@link LayerComposition} that defines rendering order of this scene.
     *
     * @type {LayerComposition}
     */ get layers() {
        return this._layers;
    }
    /**
     * Gets the {@link Sky} that defines sky properties.
     *
     * @type {Sky}
     */ get sky() {
        return this._sky;
    }
    /**
     * Gets the {@link LightingParams} that define lighting parameters.
     *
     * @type {LightingParams}
     */ get lighting() {
        return this._lightingParams;
    }
    /**
     * Gets the {@link FogParams} that define fog parameters.
     *
     * @type {FogParams}
     */ get fog() {
        return this._fogParams;
    }
    /**
     * Sets the range parameter of the bilateral filter. It's used when {@link Scene#lightmapFilterEnabled}
     * is enabled. Larger value applies more widespread blur. This needs to be a positive non-zero
     * value. Defaults to 10.
     *
     * @type {number}
     */ set lightmapFilterRange(value) {
        this._lightmapFilterRange = Math.max(value, 0.001);
    }
    /**
     * Gets the range parameter of the bilateral filter.
     *
     * @type {number}
     */ get lightmapFilterRange() {
        return this._lightmapFilterRange;
    }
    /**
     * Sets the spatial parameter of the bilateral filter. It's used when {@link Scene#lightmapFilterEnabled}
     * is enabled. Larger value blurs less similar colors. This needs to be a positive non-zero
     * value. Defaults to 0.2.
     *
     * @type {number}
     */ set lightmapFilterSmoothness(value) {
        this._lightmapFilterSmoothness = Math.max(value, 0.001);
    }
    /**
     * Gets the spatial parameter of the bilateral filter.
     *
     * @type {number}
     */ get lightmapFilterSmoothness() {
        return this._lightmapFilterSmoothness;
    }
    /**
     * Sets the 6 prefiltered cubemaps acting as the source of image-based lighting.
     *
     * @type {Texture[]}
     */ set prefilteredCubemaps(value) {
        value = value || [];
        const cubemaps = this._prefilteredCubemaps;
        const changed = cubemaps.length !== value.length || cubemaps.some((c, i)=>c !== value[i]);
        if (changed) {
            const complete = value.length === 6 && value.every((c)=>!!c);
            if (complete) {
                // update env atlas
                this._internalEnvAtlas = EnvLighting.generatePrefilteredAtlas(value, {
                    target: this._internalEnvAtlas
                });
                this._envAtlas = this._internalEnvAtlas;
            } else {
                if (this._internalEnvAtlas) {
                    this._internalEnvAtlas.destroy();
                    this._internalEnvAtlas = null;
                }
                this._envAtlas = null;
            }
            this._prefilteredCubemaps = value.slice();
            this._resetSkyMesh();
        }
    }
    /**
     * Gets the 6 prefiltered cubemaps acting as the source of image-based lighting.
     *
     * @type {Texture[]}
     */ get prefilteredCubemaps() {
        return this._prefilteredCubemaps;
    }
    /**
     * Sets the base cubemap texture used as the scene's skybox when skyboxMip is 0. Defaults to null.
     *
     * @type {Texture|null}
     */ set skybox(value) {
        if (value !== this._skyboxCubeMap) {
            this._skyboxCubeMap = value;
            this._resetSkyMesh();
        }
    }
    /**
     * Gets the base cubemap texture used as the scene's skybox when skyboxMip is 0.
     *
     * @type {Texture|null}
     */ get skybox() {
        return this._skyboxCubeMap;
    }
    /**
     * Sets the multiplier for skybox intensity. Defaults to 1. Unused if physical units are used.
     *
     * @type {number}
     */ set skyboxIntensity(value) {
        if (value !== this._skyboxIntensity) {
            this._skyboxIntensity = value;
            this._resetSkyMesh();
        }
    }
    /**
     * Gets the multiplier for skybox intensity.
     *
     * @type {number}
     */ get skyboxIntensity() {
        return this._skyboxIntensity;
    }
    /**
     * Sets the luminance (in lm/m^2) of the skybox. Defaults to 0. Only used if physical units are used.
     *
     * @type {number}
     */ set skyboxLuminance(value) {
        if (value !== this._skyboxLuminance) {
            this._skyboxLuminance = value;
            this._resetSkyMesh();
        }
    }
    /**
     * Gets the luminance (in lm/m^2) of the skybox.
     *
     * @type {number}
     */ get skyboxLuminance() {
        return this._skyboxLuminance;
    }
    /**
     * Sets the mip level of the skybox to be displayed. Only valid for prefiltered cubemap skyboxes.
     * Defaults to 0 (base level).
     *
     * @type {number}
     */ set skyboxMip(value) {
        if (value !== this._skyboxMip) {
            this._skyboxMip = value;
            this._resetSkyMesh();
        }
    }
    /**
     * Gets the mip level of the skybox to be displayed.
     *
     * @type {number}
     */ get skyboxMip() {
        return this._skyboxMip;
    }
    /**
     * Sets the highlight multiplier for the skybox. The HDR skybox can represent brightness levels
     * up to a maximum of 64, with any values beyond this being clipped. This limitation prevents
     * the accurate representation of extremely bright sources, such as the Sun, which can affect
     * HDR bloom rendering by not producing enough bloom. The multiplier adjusts the brightness
     * after clipping, enhancing the bloom effect for bright sources. Defaults to 1.
     *
     * @type {number}
     */ set skyboxHighlightMultiplier(value) {
        if (value !== this._skyboxHighlightMultiplier) {
            this._skyboxHighlightMultiplier = value;
            this._resetSkyMesh();
        }
    }
    /**
     * Gets the highlight multiplied for the skybox.
     *
     * @type {number}
     */ get skyboxHighlightMultiplier() {
        return this._skyboxHighlightMultiplier;
    }
    /**
     * Sets the rotation of the skybox to be displayed. Defaults to {@link Quat.IDENTITY}.
     *
     * @type {Quat}
     */ set skyboxRotation(value) {
        if (!this._skyboxRotation.equals(value)) {
            const isIdentity = value.equals(Quat.IDENTITY);
            this._skyboxRotation.copy(value);
            if (isIdentity) {
                this._skyboxRotationMat3.setIdentity();
            } else {
                this._skyboxRotationMat4.setTRS(Vec3.ZERO, value, Vec3.ONE);
                this._skyboxRotationMat3.invertMat4(this._skyboxRotationMat4);
            }
            // only reset sky / rebuild scene shaders if rotation changed away from identity for the first time
            if (!this._skyboxRotationShaderInclude && !isIdentity) {
                this._skyboxRotationShaderInclude = true;
                this._resetSkyMesh();
            }
        }
    }
    /**
     * Gets the rotation of the skybox to be displayed.
     *
     * @type {Quat}
     */ get skyboxRotation() {
        return this._skyboxRotation;
    }
    destroy() {
        this._resetSkyMesh();
        this.root = null;
        this.off();
    }
    drawLine(start, end, color = Color.WHITE, depthTest = true, layer = this.defaultDrawLayer) {
        const batch = this.immediate.getBatch(layer, depthTest);
        batch.addLines([
            start,
            end
        ], [
            color,
            color
        ]);
    }
    drawLines(positions, colors, depthTest = true, layer = this.defaultDrawLayer) {
        const batch = this.immediate.getBatch(layer, depthTest);
        batch.addLines(positions, colors);
    }
    drawLineArrays(positions, colors, depthTest = true, layer = this.defaultDrawLayer) {
        const batch = this.immediate.getBatch(layer, depthTest);
        batch.addLinesArrays(positions, colors);
    }
    applySettings(settings) {
        const physics = settings.physics;
        const render = settings.render;
        // settings
        this._gravity.set(physics.gravity[0], physics.gravity[1], physics.gravity[2]);
        this.ambientLight.set(render.global_ambient[0], render.global_ambient[1], render.global_ambient[2]);
        this.ambientLuminance = render.ambientLuminance;
        this.fog.type = render.fog;
        this.fog.color.set(render.fog_color[0], render.fog_color[1], render.fog_color[2]);
        this.fog.start = render.fog_start;
        this.fog.end = render.fog_end;
        this.fog.density = render.fog_density;
        this.lightmapSizeMultiplier = render.lightmapSizeMultiplier;
        this.lightmapMaxResolution = render.lightmapMaxResolution;
        this.lightmapMode = render.lightmapMode;
        this.exposure = render.exposure;
        this._skyboxIntensity = render.skyboxIntensity ?? 1;
        this._skyboxLuminance = render.skyboxLuminance ?? 20000;
        this._skyboxMip = render.skyboxMip ?? 0;
        if (render.skyboxRotation) {
            this.skyboxRotation = new Quat().setFromEulerAngles(render.skyboxRotation[0], render.skyboxRotation[1], render.skyboxRotation[2]);
        }
        this.sky.applySettings(render);
        this.clusteredLightingEnabled = render.clusteredLightingEnabled ?? false;
        this.lighting.applySettings(render);
        // bake settings
        [
            'lightmapFilterEnabled',
            'lightmapFilterRange',
            'lightmapFilterSmoothness',
            'ambientBake',
            'ambientBakeNumSamples',
            'ambientBakeSpherePart',
            'ambientBakeOcclusionBrightness',
            'ambientBakeOcclusionContrast'
        ].forEach((setting)=>{
            if (render.hasOwnProperty(setting)) {
                this[setting] = render[setting];
            }
        });
        this._resetSkyMesh();
    }
    // get the actual texture to use for skybox rendering
    _getSkyboxTex() {
        const cubemaps = this._prefilteredCubemaps;
        if (this._skyboxMip) {
            // skybox selection for some reason has always skipped the 32x32 prefiltered mipmap, presumably a bug.
            // we can't simply fix this and map 3 to the correct level, since doing so has the potential
            // to change the look of existing scenes dramatically.
            // NOTE: the table skips the 32x32 mipmap
            const skyboxMapping = [
                0,
                1,
                /* 2 */ 3,
                4,
                5,
                6
            ];
            // select blurry texture for use on the skybox
            return cubemaps[skyboxMapping[this._skyboxMip]] || this._envAtlas || cubemaps[0] || this._skyboxCubeMap;
        }
        return this._skyboxCubeMap || cubemaps[0] || this._envAtlas;
    }
    _updateSkyMesh() {
        if (!this.sky.skyMesh) {
            this.sky.updateSkyMesh();
        }
        this.sky.update();
    }
    _resetSkyMesh() {
        this.sky.resetSkyMesh();
        this.updateShaders = true;
    }
    /**
     * Sets the cubemap for the scene skybox.
     *
     * @param {Texture[]} [cubemaps] - An array of cubemaps corresponding to the skybox at
     * different mip levels. If undefined, scene will remove skybox. Cubemap array should be of
     * size 7, with the first element (index 0) corresponding to the base cubemap (mip level 0)
     * with original resolution. Each remaining element (index 1-6) corresponds to a fixed
     * prefiltered resolution (128x128, 64x64, 32x32, 16x16, 8x8, 4x4).
     */ setSkybox(cubemaps) {
        if (!cubemaps) {
            this.skybox = null;
            this.envAtlas = null;
        } else {
            this.skybox = cubemaps[0] || null;
            if (cubemaps[1] && !cubemaps[1].cubemap) {
                // prefiltered data is an env atlas
                this.envAtlas = cubemaps[1];
            } else {
                // prefiltered data is a set of cubemaps
                this.prefilteredCubemaps = cubemaps.slice(1);
            }
        }
    }
    /**
     * Gets the lightmap pixel format.
     *
     * @type {number}
     */ get lightmapPixelFormat() {
        return this.lightmapHDR && this.device.getRenderableHdrFormat() || PIXELFORMAT_RGBA8;
    }
};

/**
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 * @import { Mat4 } from '../core/math/mat4.js'
 */ /**
 * A skin contains data about the bones in a hierarchy that drive a skinned mesh animation.
 * Specifically, the skin stores the bone name and inverse bind matrix and for each bone. Inverse
 * bind matrices are instrumental in the mathematics of vertex skinning.
 *
 * @category Graphics
 */ class Skin {
    /**
     * Create a new Skin instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this skin.
     * @param {Mat4[]} ibp - The array of inverse bind matrices.
     * @param {string[]} boneNames - The array of bone names for the bones referenced by this skin.
     */ constructor(graphicsDevice, ibp, boneNames){
        // Constant between clones
        this.device = graphicsDevice;
        this.inverseBindPose = ibp;
        this.boneNames = boneNames;
    }
}

const primitiveUv1Padding = 4.0 / 64;
const primitiveUv1PaddingScale = 1.0 - primitiveUv1Padding * 2;
/**
 * Shared superclass of {@link CapsuleGeometry}, {@link ConeGeometry} and {@link CylinderGeometry}.
 * Use those classes instead of this one.
 */ class ConeBaseGeometry extends Geometry {
    constructor(baseRadius, peakRadius, height, heightSegments, capSegments, roundedCaps){
        super();
        // Variable declarations
        const pos = new Vec3();
        const bottomToTop = new Vec3();
        const norm = new Vec3();
        const top = new Vec3();
        const bottom = new Vec3();
        const tangent = new Vec3();
        const positions = [];
        const normals = [];
        const uvs = [];
        const uvs1 = [];
        const indices = [];
        let offset;
        // Define the body of the cone/cylinder
        if (height > 0) {
            for(let i = 0; i <= heightSegments; i++){
                for(let j = 0; j <= capSegments; j++){
                    // Sweep the cone body from the positive Y axis to match a 3DS Max cone/cylinder
                    const theta = j / capSegments * 2 * Math.PI - Math.PI;
                    const sinTheta = Math.sin(theta);
                    const cosTheta = Math.cos(theta);
                    bottom.set(sinTheta * baseRadius, -height / 2, cosTheta * baseRadius);
                    top.set(sinTheta * peakRadius, height / 2, cosTheta * peakRadius);
                    pos.lerp(bottom, top, i / heightSegments);
                    bottomToTop.sub2(top, bottom).normalize();
                    tangent.set(cosTheta, 0, -sinTheta);
                    norm.cross(tangent, bottomToTop).normalize();
                    positions.push(pos.x, pos.y, pos.z);
                    normals.push(norm.x, norm.y, norm.z);
                    let u = j / capSegments;
                    let v = i / heightSegments;
                    uvs.push(u, 1 - v);
                    // Pack UV1 to 1st third
                    const _v = v;
                    v = u;
                    u = _v;
                    u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                    v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                    u /= 3;
                    uvs1.push(u, 1 - v);
                    if (i < heightSegments && j < capSegments) {
                        const first = i * (capSegments + 1) + j;
                        const second = i * (capSegments + 1) + (j + 1);
                        const third = (i + 1) * (capSegments + 1) + j;
                        const fourth = (i + 1) * (capSegments + 1) + (j + 1);
                        indices.push(first, second, third);
                        indices.push(second, fourth, third);
                    }
                }
            }
        }
        if (roundedCaps) {
            const latitudeBands = Math.floor(capSegments / 2);
            const longitudeBands = capSegments;
            const capOffset = height / 2;
            // Generate top cap
            for(let lat = 0; lat <= latitudeBands; lat++){
                const theta = lat * Math.PI * 0.5 / latitudeBands;
                const sinTheta = Math.sin(theta);
                const cosTheta = Math.cos(theta);
                for(let lon = 0; lon <= longitudeBands; lon++){
                    // Sweep the sphere from the positive Z axis to match a 3DS Max sphere
                    const phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;
                    const sinPhi = Math.sin(phi);
                    const cosPhi = Math.cos(phi);
                    const x = cosPhi * sinTheta;
                    const y = cosTheta;
                    const z = sinPhi * sinTheta;
                    let u = 1 - lon / longitudeBands;
                    let v = 1 - lat / latitudeBands;
                    positions.push(x * peakRadius, y * peakRadius + capOffset, z * peakRadius);
                    normals.push(x, y, z);
                    uvs.push(u, 1 - v);
                    // Pack UV1 to 2nd third
                    u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                    v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                    u /= 3;
                    v /= 3;
                    u += 1.0 / 3;
                    uvs1.push(u, 1 - v);
                }
            }
            offset = (heightSegments + 1) * (capSegments + 1);
            for(let lat = 0; lat < latitudeBands; ++lat){
                for(let lon = 0; lon < longitudeBands; ++lon){
                    const first = lat * (longitudeBands + 1) + lon;
                    const second = first + longitudeBands + 1;
                    indices.push(offset + first + 1, offset + second, offset + first);
                    indices.push(offset + first + 1, offset + second + 1, offset + second);
                }
            }
            // Generate bottom cap
            for(let lat = 0; lat <= latitudeBands; lat++){
                const theta = Math.PI * 0.5 + lat * Math.PI * 0.5 / latitudeBands;
                const sinTheta = Math.sin(theta);
                const cosTheta = Math.cos(theta);
                for(let lon = 0; lon <= longitudeBands; lon++){
                    // Sweep the sphere from the positive Z axis to match a 3DS Max sphere
                    const phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;
                    const sinPhi = Math.sin(phi);
                    const cosPhi = Math.cos(phi);
                    const x = cosPhi * sinTheta;
                    const y = cosTheta;
                    const z = sinPhi * sinTheta;
                    let u = 1 - lon / longitudeBands;
                    let v = 1 - lat / latitudeBands;
                    positions.push(x * peakRadius, y * peakRadius - capOffset, z * peakRadius);
                    normals.push(x, y, z);
                    uvs.push(u, 1 - v);
                    // Pack UV1 to 3rd third
                    u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                    v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                    u /= 3;
                    v /= 3;
                    u += 2.0 / 3;
                    uvs1.push(u, 1 - v);
                }
            }
            offset = (heightSegments + 1) * (capSegments + 1) + (longitudeBands + 1) * (latitudeBands + 1);
            for(let lat = 0; lat < latitudeBands; ++lat){
                for(let lon = 0; lon < longitudeBands; ++lon){
                    const first = lat * (longitudeBands + 1) + lon;
                    const second = first + longitudeBands + 1;
                    indices.push(offset + first + 1, offset + second, offset + first);
                    indices.push(offset + first + 1, offset + second + 1, offset + second);
                }
            }
        } else {
            // Generate bottom cap
            offset = (heightSegments + 1) * (capSegments + 1);
            if (baseRadius > 0) {
                for(let i = 0; i < capSegments; i++){
                    const theta = i / capSegments * 2 * Math.PI;
                    const x = Math.sin(theta);
                    const y = -height / 2;
                    const z = Math.cos(theta);
                    let u = 1 - (x + 1) / 2;
                    let v = (z + 1) / 2;
                    positions.push(x * baseRadius, y, z * baseRadius);
                    normals.push(0, -1, 0);
                    uvs.push(u, 1 - v);
                    // Pack UV1 to 2nd third
                    u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                    v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                    u /= 3;
                    v /= 3;
                    u += 1 / 3;
                    uvs1.push(u, 1 - v);
                    if (i > 1) {
                        indices.push(offset, offset + i, offset + i - 1);
                    }
                }
            }
            // Generate top cap
            offset += capSegments;
            if (peakRadius > 0) {
                for(let i = 0; i < capSegments; i++){
                    const theta = i / capSegments * 2 * Math.PI;
                    const x = Math.sin(theta);
                    const y = height / 2;
                    const z = Math.cos(theta);
                    let u = 1 - (x + 1) / 2;
                    let v = (z + 1) / 2;
                    positions.push(x * peakRadius, y, z * peakRadius);
                    normals.push(0, 1, 0);
                    uvs.push(u, 1 - v);
                    // Pack UV1 to 3rd third
                    u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                    v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                    u /= 3;
                    v /= 3;
                    u += 2 / 3;
                    uvs1.push(u, 1 - v);
                    if (i > 1) {
                        indices.push(offset, offset + i - 1, offset + i);
                    }
                }
            }
        }
        this.positions = positions;
        this.normals = normals;
        this.uvs = uvs;
        this.uvs1 = uvs1;
        this.indices = indices;
    }
}

/**
 * A procedural capsule-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a CapsuleGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.CapsuleGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class CapsuleGeometry extends ConeBaseGeometry {
    /**
     * Create a new CapsuleGeometry instance.
     *
     * By default, the constructor creates a capsule standing vertically centered on the XZ-plane
     * with a radius of 0.3, a height of 1.0, 1 height segment and 20 cap segments. The capsule is
     * created with UVs in the range of 0 to 1.
     *
     * @param {object} [opts] - Options object.
     * @param {number} [opts.radius] - The radius of the tube forming the body of the capsule.
     * Defaults to 0.3.
     * @param {number} [opts.height] - The length of the body of the capsule from tip to tip.
     * Defaults to 1.
     * @param {number} [opts.heightSegments] - The number of divisions along the tubular length of
     * the capsule. Defaults to 1.
     * @param {number} [opts.sides] - The number of divisions around the tubular body of the capsule.
     * Defaults to 20.
     * @param {boolean} [opts.calculateTangents] - Generate tangent information. Defaults to false.
     * @example
     * const geometry = new pc.CapsuleGeometry({
     *     radius: 1,
     *     height: 2,
     *     heightSegments: 2,
     *     sides: 20
     * });
     */ constructor(opts = {}){
        // Check the supplied options and provide defaults for unspecified ones
        const radius = opts.radius ?? 0.3;
        const height = opts.height ?? 1;
        const heightSegments = opts.heightSegments ?? 1;
        const sides = opts.sides ?? 20;
        // Create vertex data for a cone that has a base and peak radius that is the same (i.e. a cylinder)
        super(radius, radius, height - 2 * radius, heightSegments, sides, true);
        if (opts.calculateTangents) {
            this.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);
        }
    }
}

/**
 * A procedural cone-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a ConeGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.ConeGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class ConeGeometry extends ConeBaseGeometry {
    /**
     * Create a new ConeGeometry instance.
     *
     * By default, the constructor creates a cone standing vertically centered on the XZ-plane with
     * a base radius of 0.5, a height of 1.0, 5 height segments and 18 cap segments. The cone is
     * created with UVs in the range of 0 to 1.
     *
     * @param {object} [opts] - Options object.
     * @param {number} [opts.baseRadius] - The base radius of the cone. Defaults to 0.5.
     * @param {number} [opts.peakRadius] - The peak radius of the cone. Defaults to 0.
     * @param {number} [opts.height] - The length of the body of the cone. Defaults to 1.
     * @param {number} [opts.heightSegments] - The number of divisions along the length of the cone.
     * Defaults to 5.
     * @param {number} [opts.capSegments] - The number of divisions around the tubular body of the
     * cone. Defaults to 18.
     * @param {boolean} [opts.calculateTangents] - Generate tangent information. Defaults to false.
     * @example
     * const geometry = new pc.ConeGeometry({
     *     baseRadius: 1,
     *     height: 2,
     *     heightSegments: 2,
     *     capSegments: 20
     * });
     */ constructor(opts = {}){
        // Check the supplied options and provide defaults for unspecified ones
        const baseRadius = opts.baseRadius ?? 0.5;
        const peakRadius = opts.peakRadius ?? 0;
        const height = opts.height ?? 1;
        const heightSegments = opts.heightSegments ?? 5;
        const capSegments = opts.capSegments ?? 18;
        super(baseRadius, peakRadius, height, heightSegments, capSegments, false);
        if (opts.calculateTangents) {
            this.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);
        }
    }
}

/**
 * A procedural cylinder-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a CylinderGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.CylinderGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class CylinderGeometry extends ConeBaseGeometry {
    /**
     * Create a new CylinderGeometry instance.
     *
     * By default, the constructor creates a cylinder standing vertically centered on the XZ-plane
     * with a radius of 0.5, a height of 1.0, 1 height segment and 20 cap segments. The cylinder is
     * created with UVs in the range of 0 to 1.
     *
     * @param {object} [opts] - Options object.
     * @param {number} [opts.radius] - The radius of the tube forming the body of the cylinder.
     * Defaults to 0.5.
     * @param {number} [opts.height] - The length of the body of the cylinder. Defaults to 1.
     * @param {number} [opts.heightSegments] - The number of divisions along the length of the
     * cylinder. Defaults to 5.
     * @param {number} [opts.capSegments] - The number of divisions around the tubular body of the
     * cylinder. Defaults to 20.
     * @param {boolean} [opts.calculateTangents] - Generate tangent information. Defaults to false.
     * @example
     * const geometry = new pc.CylinderGeometry({
     *     radius: 1,
     *     height: 2,
     *     heightSegments: 2,
     *     capSegments: 10
     * });
     */ constructor(opts = {}){
        // Check the supplied options and provide defaults for unspecified ones
        const radius = opts.radius ?? 0.5;
        const height = opts.height ?? 1;
        const heightSegments = opts.heightSegments ?? 5;
        const capSegments = opts.capSegments ?? 20;
        // Create vertex data for a cone that has a base and peak radius that is the same (i.e. a cylinder)
        super(radius, radius, height, heightSegments, capSegments, false);
        if (opts.calculateTangents) {
            this.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);
        }
    }
}

/**
 * A procedural plane-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a PlaneGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.PlaneGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class PlaneGeometry extends Geometry {
    /**
     * Create a new PlaneGeometry instance.
     *
     * By default, the constructor creates a plane centered on the object space origin with a width
     * and length of 1 and 5 segments in either axis (50 triangles). The normal vector of the plane is
     * aligned along the positive Y axis. The plane is created with UVs in the range of 0 to 1.
     *
     * @param {object} [opts] - Options object.
     * @param {Vec2} [opts.halfExtents] - The half dimensions of the plane in the X and Z axes.
     * Defaults to [0.5, 0.5].
     * @param {number} [opts.widthSegments] - The number of divisions along the X axis of the plane.
     * Defaults to 5.
     * @param {number} [opts.lengthSegments] - The number of divisions along the Z axis of the plane.
     * Defaults to 5.
     * @param {boolean} [opts.calculateTangents] - Generate tangent information. Defaults to false.
     * @example
     * const geometry = new pc.PlaneGeometry({
     *     halfExtents: new pc.Vec2(1, 1),
     *     widthSegments: 10,
     *     lengthSegments: 10
     * });
     */ constructor(opts = {}){
        super();
        // Check the supplied options and provide defaults for unspecified ones
        const he = opts.halfExtents ?? new Vec2(0.5, 0.5);
        const ws = opts.widthSegments ?? 5;
        const ls = opts.lengthSegments ?? 5;
        // Variable declarations
        const positions = [];
        const normals = [];
        const uvs = [];
        const indices = [];
        // Generate plane as follows (assigned UVs denoted at corners):
        // (0,1)x---------x(1,1)
        //      |         |
        //      |         |
        //      |    O--X |length
        //      |    |    |
        //      |    Z    |
        // (0,0)x---------x(1,0)
        // width
        let vcounter = 0;
        for(let i = 0; i <= ws; i++){
            for(let j = 0; j <= ls; j++){
                const x = -he.x + 2 * he.x * i / ws;
                const y = 0.0;
                const z = -(-he.y + 2 * he.y * j / ls);
                const u = i / ws;
                const v = j / ls;
                positions.push(x, y, z);
                normals.push(0, 1, 0);
                uvs.push(u, 1 - v);
                if (i < ws && j < ls) {
                    indices.push(vcounter + ls + 1, vcounter + 1, vcounter);
                    indices.push(vcounter + ls + 1, vcounter + ls + 2, vcounter + 1);
                }
                vcounter++;
            }
        }
        this.positions = positions;
        this.normals = normals;
        this.uvs = uvs;
        this.uvs1 = uvs; // UV1 = UV0 for plane
        this.indices = indices;
        if (opts.calculateTangents) {
            this.tangents = calculateTangents(positions, normals, uvs, indices);
        }
    }
}

/**
 * A procedural torus-shaped geometry.
 *
 * Typically, you would:
 *
 * 1. Create a TorusGeometry instance.
 * 2. Generate a {@link Mesh} from the geometry.
 * 3. Create a {@link MeshInstance} referencing the mesh.
 * 4. Create an {@link Entity} with a {@link RenderComponent} and assign the {@link MeshInstance} to it.
 * 5. Add the entity to the {@link Scene}.
 *
 * ```javascript
 * // Create a mesh instance
 * const geometry = new pc.TorusGeometry();
 * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, geometry);
 * const material = new pc.StandardMaterial();
 * const meshInstance = new pc.MeshInstance(mesh, material);
 *
 * // Create an entity
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     meshInstances: [meshInstance]
 * });
 *
 * // Add the entity to the scene hierarchy
 * app.scene.root.addChild(entity);
 * ```
 *
 * @category Graphics
 */ class TorusGeometry extends Geometry {
    /**
     * Create a new TorusGeometry instance.
     *
     * By default, the constructor creates a torus in the XZ-plane with a tube radius of 0.2, a ring
     * radius of 0.3, 30 segments and 20 sides. The torus is created with UVs in the range of 0 to 1.
     *
     * @param {object} [opts] - Options object.
     * @param {number} [opts.tubeRadius] - The radius of the tube forming the body of the torus.
     * Defaults to 0.2.
     * @param {number} [opts.ringRadius] - The radius from the centre of the torus to the centre of the
     * tube. Defaults to 0.3.
     * @param {number} [opts.sectorAngle] - The sector angle in degrees of the ring of the torus.
     * Defaults to 2 * Math.PI.
     * @param {number} [opts.segments] - The number of radial divisions forming cross-sections of the
     * torus ring. Defaults to 20.
     * @param {number} [opts.sides] - The number of divisions around the tubular body of the torus ring.
     * Defaults to 30.
     * @param {boolean} [opts.calculateTangents] - Generate tangent information. Defaults to false.
     * @example
     * const geometry = new pc.TorusGeometry({
     *     tubeRadius: 1,
     *     ringRadius: 2,
     *     sectorAngle: 360,
     *     segments: 30,
     *     sides: 20
     * });
     */ constructor(opts = {}){
        super();
        // Check the supplied options and provide defaults for unspecified ones
        const rc = opts.tubeRadius ?? 0.2;
        const rt = opts.ringRadius ?? 0.3;
        const sectorAngle = (opts.sectorAngle ?? 360) * math.DEG_TO_RAD;
        const segments = opts.segments ?? 30;
        const sides = opts.sides ?? 20;
        // Variable declarations
        const positions = [];
        const normals = [];
        const uvs = [];
        const indices = [];
        for(let i = 0; i <= sides; i++){
            for(let j = 0; j <= segments; j++){
                const x = Math.cos(sectorAngle * j / segments) * (rt + rc * Math.cos(2 * Math.PI * i / sides));
                const y = Math.sin(2 * Math.PI * i / sides) * rc;
                const z = Math.sin(sectorAngle * j / segments) * (rt + rc * Math.cos(2 * Math.PI * i / sides));
                const nx = Math.cos(sectorAngle * j / segments) * Math.cos(2 * Math.PI * i / sides);
                const ny = Math.sin(2 * Math.PI * i / sides);
                const nz = Math.sin(sectorAngle * j / segments) * Math.cos(2 * Math.PI * i / sides);
                const u = i / sides;
                const v = 1 - j / segments;
                positions.push(x, y, z);
                normals.push(nx, ny, nz);
                uvs.push(u, 1.0 - v);
                if (i < sides && j < segments) {
                    const first = i * (segments + 1) + j;
                    const second = (i + 1) * (segments + 1) + j;
                    const third = i * (segments + 1) + (j + 1);
                    const fourth = (i + 1) * (segments + 1) + (j + 1);
                    indices.push(first, second, third);
                    indices.push(second, fourth, third);
                }
            }
        }
        this.positions = positions;
        this.normals = normals;
        this.uvs = uvs;
        this.uvs1 = uvs; // UV1 = UV0 for sphere
        this.indices = indices;
        if (opts.calculateTangents) {
            this.tangents = calculateTangents(positions, normals, uvs, indices);
        }
    }
}

/**
 * @import { ShaderGenerator } from './programs/shader-generator.js'
 */ /**
 * A class responsible for creation and caching of required shaders.
 * There is a two level cache. The first level generates the shader based on the provided options.
 * The second level processes this generated shader using processing options - in most cases
 * modifies it to support uniform buffers.
 *
 * @ignore
 */ class ProgramLibrary {
    constructor(device, standardMaterial){
        /**
     * A cache of shaders processed using processing options.
     *
     * @type {Map<string, Shader>}
     */ this.processedCache = new Map();
        /**
     * A cache of shader definitions before processing.
     *
     * @type {Map<number, object>}
     */ this.definitionsCache = new Map();
        /**
     * Named shader generators.
     *
     * @type {Map<string, ShaderGenerator>}
     */ this._generators = new Map();
        this._device = device;
        this._isClearingCache = false;
        this._precached = false;
        // Unique non-cached programs collection to dump and update game shaders cache
        this._programsCollection = [];
        this._defaultStdMatOption = new StandardMaterialOptions();
        this._defaultStdMatOptionMin = new StandardMaterialOptions();
        const defaultCameraShaderParams = new CameraShaderParams();
        standardMaterial.shaderOptBuilder.updateRef(this._defaultStdMatOption, {}, defaultCameraShaderParams, standardMaterial, null, [], SHADER_FORWARD, null);
        standardMaterial.shaderOptBuilder.updateMinRef(this._defaultStdMatOptionMin, {}, standardMaterial, null, SHADER_SHADOW, null);
        device.on('destroy:shader', (shader)=>{
            this.removeFromCache(shader);
        });
    }
    destroy() {
        this.clearCache();
    }
    register(name, generator) {
        if (!this._generators.has(name)) {
            this._generators.set(name, generator);
        }
    }
    unregister(name) {
        if (this._generators.has(name)) {
            this._generators.delete(name);
        }
    }
    isRegistered(name) {
        return this._generators.has(name);
    }
    /**
     * Returns a generated shader definition for the specified options. They key is used to cache the
     * shader definition.
     *
     * @param {ShaderGenerator} generator - The generator to use.
     * @param {string} name - The unique name of the shader generator.
     * @param {number} key - A unique key representing the shader options.
     * @param {object} options - The shader options.
     * @returns {object} - The shader definition.
     */ generateShaderDefinition(generator, name, key, options) {
        let def = this.definitionsCache.get(key);
        if (!def) {
            let lights;
            if (options.litOptions?.lights) {
                lights = options.litOptions.lights;
                options.litOptions.lights = lights.map((l)=>{
                    // TODO: refactor this to avoid creating a clone of the light.
                    const lcopy = l.clone ? l.clone() : l;
                    lcopy.key = l.key;
                    return lcopy;
                });
            }
            this.storeNewProgram(name, options);
            if (options.litOptions?.lights) {
                options.litOptions.lights = lights;
            }
            if (this._precached) {
                Debug.log(`ProgramLibrary#getProgram: Cache miss for shader ${name} key ${key} after shaders precaching`);
            }
            const device = this._device;
            def = generator.createShaderDefinition(device, options);
            def.name = def.name ?? (options.pass ? `${name}-pass:${options.pass}` : name);
            this.definitionsCache.set(key, def);
        }
        return def;
    }
    getCachedShader(key) {
        return this.processedCache.get(key);
    }
    setCachedShader(key, shader) {
        this.processedCache.set(key, shader);
    }
    getProgram(name, options, processingOptions, userMaterialId) {
        const generator = this._generators.get(name);
        if (!generator) {
            Debug.warn(`ProgramLibrary#getProgram: No program library functions registered for: ${name}`);
            return null;
        }
        // we have a key for shader source code generation, a key for its further processing to work with
        // uniform buffers, and a final key to get the processed shader from the cache
        const generationKeyString = generator.generateKey(options);
        const generationKey = hashCode(generationKeyString);
        const processingKeyString = processingOptions.generateKey(this._device);
        const processingKey = hashCode(processingKeyString);
        const totalKey = `${generationKey}#${processingKey}`;
        // do we have final processed shader
        let processedShader = this.getCachedShader(totalKey);
        if (!processedShader) {
            // get generated shader
            const generatedShaderDef = this.generateShaderDefinition(generator, name, generationKey, options);
            Debug.assert(generatedShaderDef);
            // use shader pass name if known
            let passName = '';
            let shaderPassInfo;
            if (options.pass !== undefined) {
                shaderPassInfo = ShaderPass.get(this._device).getByIndex(options.pass);
                passName = `-${shaderPassInfo.name}`;
            }
            // fire an event to allow the shader to be modified by the user. Note that any modifications are applied
            // to all materials using the same generated shader, as the cache key is not modified.
            this._device.fire('shader:generate', {
                userMaterialId,
                shaderPassInfo,
                definition: generatedShaderDef
            });
            // create a shader definition for the shader that will include the processingOptions
            const shaderDefinition = {
                name: `${generatedShaderDef.name}${passName}-proc`,
                attributes: generatedShaderDef.attributes,
                vshader: generatedShaderDef.vshader,
                vincludes: generatedShaderDef.vincludes,
                fincludes: generatedShaderDef.fincludes,
                fshader: generatedShaderDef.fshader,
                processingOptions: processingOptions,
                shaderLanguage: generatedShaderDef.shaderLanguage,
                meshUniformBufferFormat: generatedShaderDef.meshUniformBufferFormat,
                meshBindGroupFormat: generatedShaderDef.meshBindGroupFormat
            };
            // add new shader to the processed cache
            processedShader = new Shader(this._device, shaderDefinition);
            // keep the keys in the debug mode
            Debug.call(()=>{
                processedShader._generationKey = generationKeyString;
                processedShader._processingKey = processingKeyString;
            });
            this.setCachedShader(totalKey, processedShader);
        }
        return processedShader;
    }
    storeNewProgram(name, options) {
        let opt = {};
        if (name === 'standard') {
            // For standard material saving all default values is overkill, so we store only diff
            const defaultMat = this._getDefaultStdMatOptions(options.pass);
            for(const p in options){
                if (options.hasOwnProperty(p) && defaultMat[p] !== options[p] || p === 'pass') {
                    opt[p] = options[p];
                }
            }
            // Note: this was added in #4792 and it does not filter out the default values, like the loop above
            for(const p in options.litOptions){
                opt[p] = options.litOptions[p];
            }
        } else {
            // Other shaders have only dozen params
            opt = options;
        }
        this._programsCollection.push(JSON.stringify({
            name: name,
            options: opt
        }));
    }
    // run pc.getProgramLibrary(device).dumpPrograms(); from browser console to build shader options script
    dumpPrograms() {
        let text = 'let device = pc.app ? pc.app.graphicsDevice : pc.Application.getApplication().graphicsDevice;\n';
        text += 'let shaders = [';
        if (this._programsCollection[0]) {
            text += `\n\t${this._programsCollection[0]}`;
        }
        for(let i = 1; i < this._programsCollection.length; ++i){
            text += `,\n\t${this._programsCollection[i]}`;
        }
        text += '\n];\n';
        text += 'pc.getProgramLibrary(device).precompile(shaders);\n';
        text += `if (pc.version != \"${version$1}\" || pc.revision != \"${revision}\")\n`;
        text += '\tconsole.warn(\"precompile-shaders.js: engine version mismatch, rebuild shaders lib with current engine\");';
        const element = document.createElement('a');
        element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`);
        element.setAttribute('download', 'precompile-shaders.js');
        element.style.display = 'none';
        document.body.appendChild(element);
        element.click();
        document.body.removeChild(element);
    }
    clearCache() {
        this._isClearingCache = true;
        this.processedCache.forEach((shader)=>{
            shader.destroy();
        });
        this.processedCache.clear();
        this._isClearingCache = false;
    }
    /**
     * Remove shader from the cache. This function does not destroy it, that is the responsibility
     * of the caller.
     *
     * @param {Shader} shader - The shader to be removed.
     */ removeFromCache(shader) {
        // don't delete by one when clearing whole cache
        if (this._isClearingCache) {
            return;
        }
        this.processedCache.forEach((cachedShader, key)=>{
            if (shader === cachedShader) {
                this.processedCache.delete(key);
            }
        });
    }
    _getDefaultStdMatOptions(pass) {
        const shaderPassInfo = ShaderPass.get(this._device).getByIndex(pass);
        return pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow ? this._defaultStdMatOptionMin : this._defaultStdMatOption;
    }
    precompile(cache) {
        if (cache) {
            const shaders = new Array(cache.length);
            for(let i = 0; i < cache.length; i++){
                // default options for the standard materials are not stored, and so they are inserted
                // back into the loaded options
                if (cache[i].name === 'standard') {
                    const opt = cache[i].options;
                    const defaultMat = this._getDefaultStdMatOptions(opt.pass);
                    for(const p in defaultMat){
                        if (defaultMat.hasOwnProperty(p) && opt[p] === undefined) {
                            opt[p] = defaultMat[p];
                        }
                    }
                }
                shaders[i] = this.getProgram(cache[i].name, cache[i].options);
            }
        }
        this._precached = true;
    }
}

/**
 * @import { PlyElement } from '../../framework/parsers/ply.js'
 * @import { Scene } from '../scene.js'
 * @import { Vec4 } from '../../core/math/vec4.js'
 */ const mat4$1 = new Mat4();
const quat$1 = new Quat();
const aabb = new BoundingBox();
const aabb2 = new BoundingBox();
const debugColor = new Color(1, 1, 0, 0.4);
const SH_C0$3 = 0.28209479177387814;
// iterator for accessing uncompressed splat data
class SplatIterator {
    constructor(gsplatData, p, r, s, c){
        const x = gsplatData.getProp('x');
        const y = gsplatData.getProp('y');
        const z = gsplatData.getProp('z');
        const rx = gsplatData.getProp('rot_1');
        const ry = gsplatData.getProp('rot_2');
        const rz = gsplatData.getProp('rot_3');
        const rw = gsplatData.getProp('rot_0');
        const sx = gsplatData.getProp('scale_0');
        const sy = gsplatData.getProp('scale_1');
        const sz = gsplatData.getProp('scale_2');
        const cr = gsplatData.getProp('f_dc_0');
        const cg = gsplatData.getProp('f_dc_1');
        const cb = gsplatData.getProp('f_dc_2');
        const ca = gsplatData.getProp('opacity');
        /**
         * Calculates the sigmoid of a given value.
         *
         * @param {number} v - The value for which to compute the sigmoid function.
         * @returns {number} The result of the sigmoid function.
         */ const sigmoid = (v)=>{
            if (v > 0) {
                return 1 / (1 + Math.exp(-v));
            }
            const t = Math.exp(v);
            return t / (1 + t);
        };
        this.read = (i)=>{
            if (p) {
                p.x = x[i];
                p.y = y[i];
                p.z = z[i];
            }
            if (r) {
                r.set(rx[i], ry[i], rz[i], rw[i]);
            }
            if (s) {
                s.set(Math.exp(sx[i]), Math.exp(sy[i]), Math.exp(sz[i]));
            }
            if (c) {
                c.set(0.5 + cr[i] * SH_C0$3, 0.5 + cg[i] * SH_C0$3, 0.5 + cb[i] * SH_C0$3, sigmoid(ca[i]));
            }
        };
    }
}
/**
 * Calculate a splat orientation matrix from its position and rotation.
 * @param {Mat4} result - Mat4 instance holding calculated rotation matrix.
 * @param {Vec3} p - The splat position
 * @param {Quat} r - The splat rotation
 */ const calcSplatMat = (result, p, r)=>{
    quat$1.set(r.x, r.y, r.z, r.w).normalize();
    result.setTRS(p, quat$1, Vec3.ONE);
};
class GSplatData {
    /**
     * @param {PlyElement[]} elements - The elements.
     * @param {string[]} comments - File header comments.
     */ constructor(elements, comments = []){
        this.elements = elements;
        this.numSplats = this.getElement('vertex').count;
        this.comments = comments;
    }
    /**
     * @param {BoundingBox} result - Bounding box instance holding calculated result.
     * @param {Vec3} p - The splat position
     * @param {Quat} r - The splat rotation
     * @param {Vec3} s - The splat scale
     */ static calcSplatAabb(result, p, r, s) {
        calcSplatMat(mat4$1, p, r);
        aabb.center.set(0, 0, 0);
        aabb.halfExtents.set(s.x * 2, s.y * 2, s.z * 2);
        result.setFromTransformedAabb(aabb, mat4$1);
    }
    // access a named property
    getProp(name, elementName = 'vertex') {
        return this.getElement(elementName)?.properties.find((p)=>p.name === name)?.storage;
    }
    // access the named element
    getElement(name) {
        return this.elements.find((e)=>e.name === name);
    }
    // add a new property
    addProp(name, storage) {
        this.getElement('vertex').properties.push({
            type: 'float',
            name,
            storage,
            byteSize: 4
        });
    }
    /**
     * Create an iterator for accessing splat data
     *
     * @param {Vec3|null} [p] - the vector to receive splat position
     * @param {Quat|null} [r] - the quaternion to receive splat rotation
     * @param {Vec3|null} [s] - the vector to receive splat scale
     * @param {Vec4|null} [c] - the vector to receive splat color
     * @returns {SplatIterator} - The iterator
     */ createIter(p, r, s, c) {
        return new SplatIterator(this, p, r, s, c);
    }
    /**
     * Calculate pessimistic scene aabb taking into account splat size. This is faster than
     * calculating an exact aabb.
     *
     * @param {BoundingBox} result - Where to store the resulting bounding box.
     * @param {(i: number) => boolean} [pred] - Optional predicate function to filter splats.
     * @returns {boolean} - Whether the calculation was successful.
     */ calcAabb(result, pred) {
        let mx, my, mz, Mx, My, Mz;
        let first = true;
        const x = this.getProp('x');
        const y = this.getProp('y');
        const z = this.getProp('z');
        const sx = this.getProp('scale_0');
        const sy = this.getProp('scale_1');
        const sz = this.getProp('scale_2');
        for(let i = 0; i < this.numSplats; ++i){
            if (pred && !pred(i)) {
                continue;
            }
            const px = x[i];
            const py = y[i];
            const pz = z[i];
            const scale = Math.max(sx[i], sy[i], sz[i]);
            if (!isFinite(px) || !isFinite(py) || !isFinite(pz) || !isFinite(scale)) {
                continue;
            }
            const scaleVal = 2.0 * Math.exp(scale);
            if (first) {
                first = false;
                mx = px - scaleVal;
                my = py - scaleVal;
                mz = pz - scaleVal;
                Mx = px + scaleVal;
                My = py + scaleVal;
                Mz = pz + scaleVal;
            } else {
                mx = Math.min(mx, px - scaleVal);
                my = Math.min(my, py - scaleVal);
                mz = Math.min(mz, pz - scaleVal);
                Mx = Math.max(Mx, px + scaleVal);
                My = Math.max(My, py + scaleVal);
                Mz = Math.max(Mz, pz + scaleVal);
            }
        }
        if (!first) {
            result.center.set((mx + Mx) * 0.5, (my + My) * 0.5, (mz + Mz) * 0.5);
            result.halfExtents.set((Mx - mx) * 0.5, (My - my) * 0.5, (Mz - mz) * 0.5);
        }
        return !first;
    }
    /**
     * Calculate exact scene aabb taking into account splat size
     *
     * @param {BoundingBox} result - Where to store the resulting bounding box.
     * @param {(i: number) => boolean} [pred] - Optional predicate function to filter splats.
     * @returns {boolean} - Whether the calculation was successful.
     */ calcAabbExact(result, pred) {
        const p = new Vec3();
        const r = new Quat();
        const s = new Vec3();
        const iter = this.createIter(p, r, s);
        let first = true;
        for(let i = 0; i < this.numSplats; ++i){
            if (pred && !pred(i)) {
                continue;
            }
            iter.read(i);
            if (first) {
                first = false;
                GSplatData.calcSplatAabb(result, p, r, s);
            } else {
                GSplatData.calcSplatAabb(aabb2, p, r, s);
                result.add(aabb2);
            }
        }
        return !first;
    }
    /**
     * @param {Float32Array} result - Array containing the centers.
     */ getCenters(result) {
        const x = this.getProp('x');
        const y = this.getProp('y');
        const z = this.getProp('z');
        for(let i = 0; i < this.numSplats; ++i){
            result[i * 3 + 0] = x[i];
            result[i * 3 + 1] = y[i];
            result[i * 3 + 2] = z[i];
        }
    }
    /**
     * @param {Vec3} result - The result.
     * @param {Function} [pred] - Predicate given index for skipping.
     */ calcFocalPoint(result, pred) {
        const x = this.getProp('x');
        const y = this.getProp('y');
        const z = this.getProp('z');
        const sx = this.getProp('scale_0');
        const sy = this.getProp('scale_1');
        const sz = this.getProp('scale_2');
        result.x = 0;
        result.y = 0;
        result.z = 0;
        let sum = 0;
        for(let i = 0; i < this.numSplats; ++i){
            if (pred && !pred(i)) {
                continue;
            }
            const px = x[i];
            const py = y[i];
            const pz = z[i];
            if (!isFinite(px) || !isFinite(py) || !isFinite(pz)) {
                continue;
            }
            const weight = 1.0 / (1.0 + Math.exp(Math.max(sx[i], sy[i], sz[i])));
            result.x += px * weight;
            result.y += py * weight;
            result.z += pz * weight;
            sum += weight;
        }
        result.mulScalar(1 / sum);
    }
    /**
     * @param {Scene} scene - The application's scene.
     * @param {Mat4} worldMat - The world matrix.
     */ renderWireframeBounds(scene, worldMat) {
        const p = new Vec3();
        const r = new Quat();
        const s = new Vec3();
        const min = new Vec3();
        const max = new Vec3();
        const iter = this.createIter(p, r, s);
        for(let i = 0; i < this.numSplats; ++i){
            iter.read(i);
            calcSplatMat(mat4$1, p, r);
            mat4$1.mul2(worldMat, mat4$1);
            min.set(s.x * -2, s.y * -2, s.z * -2);
            max.set(s.x * 2.0, s.y * 2.0, s.z * 2.0);
            // @ts-ignore
            scene.immediate.drawWireAlignedBox(min, max, debugColor, true, scene.defaultDrawLayer, mat4$1);
        }
    }
    get isCompressed() {
        return false;
    }
    // return the number of spherical harmonic bands present. value will be between 0 and 3 inclusive.
    get shBands() {
        const numProps = ()=>{
            for(let i = 0; i < 45; ++i){
                if (!this.getProp(`f_rest_${i}`)) {
                    return i;
                }
            }
            return 45;
        };
        const sizes = {
            9: 1,
            24: 2,
            45: 3
        };
        return sizes[numProps()] ?? 0;
    }
    calcMortonOrder() {
        const calcMinMax = (arr)=>{
            let min = arr[0];
            let max = arr[0];
            for(let i = 1; i < arr.length; i++){
                if (arr[i] < min) min = arr[i];
                if (arr[i] > max) max = arr[i];
            }
            return {
                min,
                max
            };
        };
        // https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
        const encodeMorton3 = (x, y, z)=>{
            const Part1By2 = (x)=>{
                x &= 0x000003ff;
                x = (x ^ x << 16) & 0xff0000ff;
                x = (x ^ x << 8) & 0x0300f00f;
                x = (x ^ x << 4) & 0x030c30c3;
                x = (x ^ x << 2) & 0x09249249;
                return x;
            };
            return (Part1By2(z) << 2) + (Part1By2(y) << 1) + Part1By2(x);
        };
        const x = this.getProp('x');
        const y = this.getProp('y');
        const z = this.getProp('z');
        const { min: minX, max: maxX } = calcMinMax(x);
        const { min: minY, max: maxY } = calcMinMax(y);
        const { min: minZ, max: maxZ } = calcMinMax(z);
        const sizeX = minX === maxX ? 0 : 1024 / (maxX - minX);
        const sizeY = minY === maxY ? 0 : 1024 / (maxY - minY);
        const sizeZ = minZ === maxZ ? 0 : 1024 / (maxZ - minZ);
        const codes = new Map();
        for(let i = 0; i < this.numSplats; i++){
            const ix = Math.min(1023, Math.floor((x[i] - minX) * sizeX));
            const iy = Math.min(1023, Math.floor((y[i] - minY) * sizeY));
            const iz = Math.min(1023, Math.floor((z[i] - minZ) * sizeZ));
            const code = encodeMorton3(ix, iy, iz);
            const val = codes.get(code);
            if (val) {
                val.push(i);
            } else {
                codes.set(code, [
                    i
                ]);
            }
        }
        const keys = Array.from(codes.keys()).sort((a, b)=>a - b);
        const indices = new Uint32Array(this.numSplats);
        let idx = 0;
        for(let i = 0; i < keys.length; ++i){
            const val = codes.get(keys[i]);
            for(let j = 0; j < val.length; ++j){
                indices[idx++] = val[j];
            }
        }
        return indices;
    }
    // reorder the splat data to aid in better gpu memory access at render time
    reorder(order) {
        const cache = new Map();
        const getStorage = (size)=>{
            if (cache.has(size)) {
                const buffer = cache.get(size);
                cache.delete(size);
                return buffer;
            }
            return new ArrayBuffer(size);
        };
        const returnStorage = (buffer)=>{
            cache.set(buffer.byteLength, buffer);
        };
        const reorder = (data)=>{
            const result = new data.constructor(getStorage(data.byteLength));
            for(let i = 0; i < order.length; i++){
                result[i] = data[order[i]];
            }
            returnStorage(data.buffer);
            return result;
        };
        this.elements.forEach((element)=>{
            element.properties.forEach((property)=>{
                if (property.storage) {
                    property.storage = reorder(property.storage);
                }
            });
        });
    }
    // reorder the splat data to aid in better gpu memory access at render time
    reorderData() {
        this.reorder(this.calcMortonOrder());
    }
}

// fragment shader to copy splats in any supported format to MRT work-buffer
var glslGsplatCopyToWorkBufferPS = /* glsl */ `

#define GSPLAT_CENTER_NOPROJ

#include "gsplatStructsVS"
#include "gsplatCenterVS"
#include "gsplatEvalSHVS"
#include "gsplatQuatToMat3VS"
#include "gsplatSourceFormatVS"

uniform mat4 uTransform;

uniform int uStartLine;      // Start row in destination texture
uniform int uViewportWidth;  // Width of the destination viewport in pixels

#ifdef GSPLAT_LOD
    // LOD intervals texture
    uniform usampler2D uIntervalsTexture;
#endif

#ifdef GSPLAT_COLORIZE
    uniform vec3 uLodColor;
#endif

// number of splats
uniform int uActiveSplats;

void main(void) {
    // local fragment coordinates (within the viewport)
    ivec2 localFragCoords = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y) - uStartLine);

    // linear index of the splat
    int targetIndex = localFragCoords.y * uViewportWidth + localFragCoords.x;
    if (targetIndex >= uActiveSplats) {

        // Out of bounds: write zeros
        pcFragColor0 = vec4(0.0);
        pcFragColor1 = vec4(0.0);
        pcFragColor2 = vec4(0.0);
        pcFragColor3 = vec4(0.0);

    } else {

        #ifdef GSPLAT_LOD
            // Use intervals texture to remap target index to source index
            int intervalsSize = int(textureSize(uIntervalsTexture, 0).x);
            ivec2 intervalUV = ivec2(targetIndex % intervalsSize, targetIndex / intervalsSize);
            uint originalIndex = texelFetch(uIntervalsTexture, intervalUV, 0).r;
        #else
            uint originalIndex = uint(targetIndex);
        #endif
        
        // source texture size
        #if defined(GSPLAT_SOGS_DATA) || defined(GSPLAT_COMPRESSED_DATA)
            uint srcSize = uint(textureSize(packedTexture, 0).x);
        #else
            uint srcSize = uint(textureSize(splatColor, 0).x);
        #endif
        
        // Create SplatSource used to sample splat data textures
        SplatSource source;
        source.id = uint(originalIndex);
        source.uv = ivec2(source.id % srcSize, source.id / srcSize);

        // read and transform center
        vec3 modelCenter = readCenter(source);
        modelCenter = (uTransform * vec4(modelCenter, 1.0)).xyz;
        SplatCenter center;
        initCenter(modelCenter, center);

        // read and transform covariance
        vec3 covA, covB;
        readCovariance(source, covA, covB);

        mat3 C = mat3(
            covA.x, covA.y, covA.z,
            covA.y, covB.x, covB.y,
            covA.z, covB.y, covB.z
        );
        mat3 linear = mat3(uTransform);
        mat3 Ct = linear * C * transpose(linear);
        covA = Ct[0];
        covB = vec3(Ct[1][1], Ct[1][2], Ct[2][2]);

        // read color
        vec4 color = readColor(source);

        // evaluate spherical harmonics
        #if SH_BANDS > 0
            // calculate the model-space view direction
            vec3 dir = normalize(center.view * mat3(center.modelView));

            // read sh coefficients
            vec3 sh[SH_COEFFS];
            float scale;
            readSHData(source, sh, scale);

            // evaluate
            color.xyz += evalSH(sh, dir) * scale;
        #endif

        #ifdef GSPLAT_COLORIZE
            color.xyz *= uLodColor;
        #endif

        // write out results
        pcFragColor0 = color;
        pcFragColor1 = vec4(modelCenter, 1.0);
        pcFragColor2 = vec4(covA, 1.0);
        pcFragColor3 = vec4(covB, 1.0);
    }
}
`;

// fragment shader to copy splats in any supported format to MRT work-buffer
var wgslGsplatCopyToWorkBufferPS = /* wgsl */ `

#define GSPLAT_CENTER_NOPROJ

#include "gsplatStructsVS"
#include "gsplatCenterVS"
#include "gsplatEvalSHVS"
#include "gsplatQuatToMat3VS"
#include "gsplatSourceFormatVS"

uniform uTransform: mat4x4f;

uniform uStartLine: i32;      // Start row in destination texture
uniform uViewportWidth: i32;  // Width of the destination viewport in pixels

#ifdef GSPLAT_LOD
    // LOD intervals texture
    var uIntervalsTexture: texture_2d<u32>;
#endif

#ifdef GSPLAT_COLORIZE
    uniform uLodColor: vec3f;
#endif

// number of splats
uniform uActiveSplats: i32;

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;
    
    // local fragment coordinates (within the viewport)
    let localFragCoords = vec2i(i32(input.position.x), i32(input.position.y) - uniform.uStartLine);

    // linear index of the splat
    let targetIndex = localFragCoords.y * uniform.uViewportWidth + localFragCoords.x;
    
    if (targetIndex >= uniform.uActiveSplats) {

        // Out of bounds: write zeros
        output.color = vec4f(0.0);
        output.color1 = vec4f(0.0);
        output.color2 = vec4f(0.0);
        output.color3 = vec4f(0.0);

    } else {

        #ifdef GSPLAT_LOD
            // Use intervals texture to remap target index to source index
            let intervalsSize = i32(textureDimensions(uIntervalsTexture, 0).x);
            let intervalUV = vec2i(targetIndex % intervalsSize, targetIndex / intervalsSize);
            let originalIndex = textureLoad(uIntervalsTexture, intervalUV, 0).r;
        #else
            let originalIndex = targetIndex;
        #endif
        
        // source texture size
        var srcSize: u32;
        #if defined(GSPLAT_SOGS_DATA) || defined(GSPLAT_COMPRESSED_DATA)
            srcSize = u32(textureDimensions(packedTexture, 0).x);
        #else
            srcSize = u32(textureDimensions(splatColor, 0).x);
        #endif
        
        // Create SplatSource used to sample splat data textures
        var source: SplatSource;
        source.id = u32(originalIndex);
        source.uv = vec2i(i32(source.id % srcSize), i32(source.id / srcSize));

        // read and transform center
        var modelCenter = readCenter(&source);
        modelCenter = (uniform.uTransform * vec4f(modelCenter, 1.0)).xyz;
        var center: SplatCenter;
        initCenter(modelCenter, &center);

        // read and transform covariance
        var covA: vec3f;
        var covB: vec3f;
        readCovariance(&source, &covA, &covB);

        let C = mat3x3f(
            vec3f(covA.x, covA.y, covA.z),
            vec3f(covA.y, covB.x, covB.y),
            vec3f(covA.z, covB.y, covB.z)
        );
        let linear = mat3x3f(uniform.uTransform[0].xyz, uniform.uTransform[1].xyz, uniform.uTransform[2].xyz);
        let Ct = linear * C * transpose(linear);
        covA = Ct[0];
        covB = vec3f(Ct[1][1], Ct[1][2], Ct[2][2]);

        // read color
        var color = readColor(&source);

        // evaluate spherical harmonics
        #if SH_BANDS > 0
            // calculate the model-space view direction
            let dir = normalize(center.view * mat3x3f(center.modelView[0].xyz, center.modelView[1].xyz, center.modelView[2].xyz));

            // read sh coefficients
            var sh: array<vec3f, SH_COEFFS>;
            var scale: f32;
            readSHData(&source, &sh, &scale);

            // evaluate
            color = vec4f(color.xyz + evalSH(&sh, dir) * scale, color.w);
        #endif

        #ifdef GSPLAT_COLORIZE
            color = vec4f(color.xyz * uniform.uLodColor, color.w);
        #endif

        // write out results
        output.color = color;
        output.color1 = vec4f(modelCenter, 1.0);
        output.color2 = vec4f(covA, 1.0);
        output.color3 = vec4f(covB, 1.0);
    }
    
    return output;
}
`;

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { GSplatData } from './gsplat-data.js'
 * @import { GSplatCompressedData } from './gsplat-compressed-data.js'
 * @import { GSplatSogsData } from './gsplat-sogs-data.js'
 */ let id$1 = 0;
const tempMap = new Map();
/**
 * A helper class to cache quad renders for work buffer rendering.
 *
 * @ignore
 */ class WorkBufferRenderInfo {
    constructor(device, key, material){
        this.device = device;
        this.material = material;
        const clonedDefines = new Map(material.defines);
        const shader = ShaderUtils.createShader(this.device, {
            uniqueName: `SplatCopyToWorkBuffer:${key}`,
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexDefines: clonedDefines,
            fragmentDefines: clonedDefines,
            vertexChunk: 'fullscreenQuadVS',
            fragmentGLSL: glslGsplatCopyToWorkBufferPS,
            fragmentWGSL: wgslGsplatCopyToWorkBufferPS
        });
        this.quadRender = new QuadRender(shader);
    }
    destroy() {
        this.material?.destroy();
        this.quadRender?.destroy();
    }
}
/**
 * Base class for a GSplat resource and defines common properties.
 *
 *  @ignore
 */ class GSplatResourceBase {
    constructor(device, gsplatData){
        /** @type {number} */ this.id = id$1++;
        /** @type {Map<string, WorkBufferRenderInfo>} */ this.workBufferRenderInfos = new Map();
        this.device = device;
        this.gsplatData = gsplatData;
        this.centers = new Float32Array(gsplatData.numSplats * 3);
        gsplatData.getCenters(this.centers);
        this.aabb = new BoundingBox();
        gsplatData.calcAabb(this.aabb);
        // construct the mesh
        this.mesh = GSplatResourceBase.createMesh(device);
        this.instanceIndices = GSplatResourceBase.createInstanceIndices(device, gsplatData.numSplats);
        // keep extra reference since mesh is shared between instances
        this.mesh.incRefCount();
        this.mesh.aabb.copy(this.aabb);
    }
    destroy() {
        this.mesh?.destroy();
        this.instanceIndices?.destroy();
        this.workBufferRenderInfos.forEach((info)=>info.destroy());
        this.workBufferRenderInfos.clear();
    }
    /**
     * Get or create a QuadRender for rendering to work buffer.
     *
     * @param {boolean} useIntervals - Whether to use intervals.
     * @param {boolean} colorizeLod - Whether to colorize the LOD.
     * @returns {WorkBufferRenderInfo} The WorkBufferRenderInfo instance.
     */ getWorkBufferRenderInfo(useIntervals, colorizeLod) {
        // configure defines to fetch cached data
        this.configureMaterialDefines(tempMap);
        if (useIntervals) tempMap.set('GSPLAT_LOD', '');
        if (colorizeLod) tempMap.set('GSPLAT_COLORIZE', '');
        const key = Array.from(tempMap.entries()).map(([k, v])=>`${k}=${v}`).join(';');
        // get or create quad render
        let info = this.workBufferRenderInfos.get(key);
        if (!info) {
            const material = new ShaderMaterial();
            this.configureMaterial(material);
            // copy tempMap to material defines
            tempMap.forEach((v, k)=>material.setDefine(k, v));
            // create new cache entry
            info = new WorkBufferRenderInfo(this.device, key, material);
            this.workBufferRenderInfos.set(key, info);
        }
        tempMap.clear();
        return info;
    }
    static createMesh(device) {
        // number of quads to combine into a single instance. this is to increase occupancy
        // in the vertex shader.
        const splatInstanceSize = GSplatResourceBase.instanceSize;
        // build the instance mesh
        const meshPositions = new Float32Array(12 * splatInstanceSize);
        const meshIndices = new Uint32Array(6 * splatInstanceSize);
        for(let i = 0; i < splatInstanceSize; ++i){
            meshPositions.set([
                -1,
                -1,
                i,
                1,
                -1,
                i,
                1,
                1,
                i,
                -1,
                1,
                i
            ], i * 12);
            const b = i * 4;
            meshIndices.set([
                0 + b,
                1 + b,
                2 + b,
                0 + b,
                2 + b,
                3 + b
            ], i * 6);
        }
        const mesh = new Mesh(device);
        mesh.setPositions(meshPositions, 3);
        mesh.setIndices(meshIndices);
        mesh.update();
        return mesh;
    }
    static createInstanceIndices(device, splatCount) {
        const splatInstanceSize = GSplatResourceBase.instanceSize;
        const numSplats = Math.ceil(splatCount / splatInstanceSize) * splatInstanceSize;
        const numSplatInstances = numSplats / splatInstanceSize;
        const indexData = new Uint32Array(numSplatInstances);
        for(let i = 0; i < numSplatInstances; ++i){
            indexData[i] = i * splatInstanceSize;
        }
        const vertexFormat = new VertexFormat(device, [
            {
                semantic: SEMANTIC_ATTR13,
                components: 1,
                type: TYPE_UINT32,
                asInt: true
            }
        ]);
        const instanceIndices = new VertexBuffer(device, vertexFormat, numSplatInstances, {
            usage: BUFFER_STATIC,
            data: indexData.buffer
        });
        return instanceIndices;
    }
    static get instanceSize() {
        return 128; // number of splats per instance
    }
    get numSplats() {
        return this.gsplatData.numSplats;
    }
    configureMaterial(material) {}
    configureMaterialDefines(defines) {}
    /**
     * Evaluates the size of the texture based on the number of splats.
     *
     * @param {number} count - Number of gaussians.
     * @returns {Vec2} Returns a Vec2 object representing the size of the texture.
     */ evalTextureSize(count) {
        return Vec2.ZERO;
    }
    /**
     * Creates a new texture with the specified parameters.
     *
     * @param {string} name - The name of the texture to be created.
     * @param {number} format - The pixel format of the texture.
     * @param {Vec2} size - The size of the texture in a Vec2 object, containing width (x) and height (y).
     * @param {Uint8Array|Uint16Array|Uint32Array} [data] - The initial data to fill the texture with.
     * @returns {Texture} The created texture instance.
     */ createTexture(name, format, size, data) {
        return new Texture(this.device, {
            name: name,
            width: size.x,
            height: size.y,
            format: format,
            cubemap: false,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            ...data ? {
                levels: [
                    data
                ]
            } : {}
        });
    }
    instantiate() {
        Debug.removed('GSplatResource.instantiate is removed. Use gsplat component instead');
    }
}

/**
 * @import { GSplatData } from './gsplat-data.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { Texture } from '../../platform/graphics/texture.js';
 */ const getSHData = (gsplatData, numCoeffs)=>{
    const result = [];
    for(let i = 0; i < numCoeffs; ++i){
        result.push(gsplatData.getProp(`f_rest_${i}`));
    }
    return result;
};
/** @ignore */ class GSplatResource extends GSplatResourceBase {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {GSplatData} gsplatData - The splat data.
     */ constructor(device, gsplatData){
        super(device, gsplatData);
        const numSplats = gsplatData.numSplats;
        const size = this.evalTextureSize(numSplats);
        this.colorTexture = this.createTexture('splatColor', PIXELFORMAT_RGBA16F, size);
        this.transformATexture = this.createTexture('transformA', PIXELFORMAT_RGBA32U, size);
        this.transformBTexture = this.createTexture('transformB', PIXELFORMAT_RGBA16F, size);
        // write texture data
        this.updateColorData(gsplatData);
        this.updateTransformData(gsplatData);
        // initialize SH data
        this.shBands = gsplatData.shBands;
        if (this.shBands > 0) {
            this.sh1to3Texture = this.createTexture('splatSH_1to3', PIXELFORMAT_RGBA32U, size);
            if (this.shBands > 1) {
                this.sh4to7Texture = this.createTexture('splatSH_4to7', PIXELFORMAT_RGBA32U, size);
                if (this.shBands > 2) {
                    this.sh8to11Texture = this.createTexture('splatSH_8to11', PIXELFORMAT_RGBA32U, size);
                    this.sh12to15Texture = this.createTexture('splatSH_12to15', PIXELFORMAT_RGBA32U, size);
                } else {
                    this.sh8to11Texture = this.createTexture('splatSH_8to11', PIXELFORMAT_R32U, size);
                }
            }
            this.updateSHData(gsplatData);
        }
    }
    destroy() {
        this.colorTexture?.destroy();
        this.transformATexture?.destroy();
        this.transformBTexture?.destroy();
        this.sh1to3Texture?.destroy();
        this.sh4to7Texture?.destroy();
        this.sh8to11Texture?.destroy();
        this.sh12to15Texture?.destroy();
        super.destroy();
    }
    configureMaterialDefines(defines) {
        defines.set('SH_BANDS', this.shBands);
    }
    configureMaterial(material) {
        this.configureMaterialDefines(material.defines);
        material.setParameter('splatColor', this.colorTexture);
        material.setParameter('transformA', this.transformATexture);
        material.setParameter('transformB', this.transformBTexture);
        if (this.sh1to3Texture) material.setParameter('splatSH_1to3', this.sh1to3Texture);
        if (this.sh4to7Texture) material.setParameter('splatSH_4to7', this.sh4to7Texture);
        if (this.sh8to11Texture) material.setParameter('splatSH_8to11', this.sh8to11Texture);
        if (this.sh12to15Texture) material.setParameter('splatSH_12to15', this.sh12to15Texture);
    }
    /**
     * Evaluates the texture size needed to store a given number of elements.
     * The function calculates a width and height that is close to a square
     * that can contain 'count' elements.
     *
     * @param {number} count - The number of elements to store in the texture.
     * @returns {Vec2} An instance of Vec2 representing the width and height of the texture.
     */ evalTextureSize(count) {
        const width = Math.ceil(Math.sqrt(count));
        const height = Math.ceil(count / width);
        return new Vec2(width, height);
    }
    /**
     * Updates pixel data of this.colorTexture based on the supplied color components and opacity.
     * Assumes that the texture is using an RGBA format where RGB are color components influenced
     * by SH spherical harmonics and A is opacity after a sigmoid transformation.
     *
     * @param {GSplatData} gsplatData - The source data
     */ updateColorData(gsplatData) {
        const texture = this.colorTexture;
        if (!texture) {
            return;
        }
        const float2Half = FloatPacking.float2Half;
        const data = texture.lock();
        const cr = gsplatData.getProp('f_dc_0');
        const cg = gsplatData.getProp('f_dc_1');
        const cb = gsplatData.getProp('f_dc_2');
        const ca = gsplatData.getProp('opacity');
        const SH_C0 = 0.28209479177387814;
        for(let i = 0; i < this.numSplats; ++i){
            const r = cr[i] * SH_C0 + 0.5;
            const g = cg[i] * SH_C0 + 0.5;
            const b = cb[i] * SH_C0 + 0.5;
            const a = 1 / (1 + Math.exp(-ca[i]));
            data[i * 4 + 0] = float2Half(r);
            data[i * 4 + 1] = float2Half(g);
            data[i * 4 + 2] = float2Half(b);
            data[i * 4 + 3] = float2Half(a);
        }
        texture.unlock();
    }
    /**
     * @param {GSplatData} gsplatData - The source data
     */ updateTransformData(gsplatData) {
        const float2Half = FloatPacking.float2Half;
        if (!this.transformATexture) {
            return;
        }
        const dataA = this.transformATexture.lock();
        const dataAFloat32 = new Float32Array(dataA.buffer);
        const dataB = this.transformBTexture.lock();
        const p = new Vec3();
        const r = new Quat();
        const s = new Vec3();
        const iter = gsplatData.createIter(p, r, s);
        for(let i = 0; i < this.numSplats; i++){
            iter.read(i);
            r.normalize();
            if (r.w < 0) {
                r.mulScalar(-1);
            }
            dataAFloat32[i * 4 + 0] = p.x;
            dataAFloat32[i * 4 + 1] = p.y;
            dataAFloat32[i * 4 + 2] = p.z;
            dataA[i * 4 + 3] = float2Half(r.x) | float2Half(r.y) << 16;
            dataB[i * 4 + 0] = float2Half(s.x);
            dataB[i * 4 + 1] = float2Half(s.y);
            dataB[i * 4 + 2] = float2Half(s.z);
            dataB[i * 4 + 3] = float2Half(r.z);
        }
        this.transformATexture.unlock();
        this.transformBTexture.unlock();
    }
    /**
     * @param {GSplatData} gsplatData - The source data
     */ updateSHData(gsplatData) {
        const sh1to3Data = this.sh1to3Texture.lock();
        const sh4to7Data = this.sh4to7Texture?.lock();
        const sh8to11Data = this.sh8to11Texture?.lock();
        const sh12to15Data = this.sh12to15Texture?.lock();
        const numCoeffs = {
            1: 3,
            2: 8,
            3: 15
        }[this.shBands];
        const src = getSHData(gsplatData, numCoeffs * 3);
        const t11 = (1 << 11) - 1;
        const t10 = (1 << 10) - 1;
        const float32 = new Float32Array(1);
        const uint32 = new Uint32Array(float32.buffer);
        // coefficients
        const c = new Array(numCoeffs * 3).fill(0);
        for(let i = 0; i < gsplatData.numSplats; ++i){
            // extract coefficients
            for(let j = 0; j < numCoeffs; ++j){
                c[j * 3] = src[j][i];
                c[j * 3 + 1] = src[j + numCoeffs][i];
                c[j * 3 + 2] = src[j + numCoeffs * 2][i];
            }
            // calc maximum value
            let max = c[0];
            for(let j = 1; j < numCoeffs * 3; ++j){
                max = Math.max(max, Math.abs(c[j]));
            }
            if (max === 0) {
                continue;
            }
            // normalize
            for(let j = 0; j < numCoeffs; ++j){
                c[j * 3 + 0] = Math.max(0, Math.min(t11, Math.floor((c[j * 3 + 0] / max * 0.5 + 0.5) * t11 + 0.5)));
                c[j * 3 + 1] = Math.max(0, Math.min(t10, Math.floor((c[j * 3 + 1] / max * 0.5 + 0.5) * t10 + 0.5)));
                c[j * 3 + 2] = Math.max(0, Math.min(t11, Math.floor((c[j * 3 + 2] / max * 0.5 + 0.5) * t11 + 0.5)));
            }
            // pack
            float32[0] = max;
            sh1to3Data[i * 4 + 0] = uint32[0];
            sh1to3Data[i * 4 + 1] = c[0] << 21 | c[1] << 11 | c[2];
            sh1to3Data[i * 4 + 2] = c[3] << 21 | c[4] << 11 | c[5];
            sh1to3Data[i * 4 + 3] = c[6] << 21 | c[7] << 11 | c[8];
            if (this.shBands > 1) {
                sh4to7Data[i * 4 + 0] = c[9] << 21 | c[10] << 11 | c[11];
                sh4to7Data[i * 4 + 1] = c[12] << 21 | c[13] << 11 | c[14];
                sh4to7Data[i * 4 + 2] = c[15] << 21 | c[16] << 11 | c[17];
                sh4to7Data[i * 4 + 3] = c[18] << 21 | c[19] << 11 | c[20];
                if (this.shBands > 2) {
                    sh8to11Data[i * 4 + 0] = c[21] << 21 | c[22] << 11 | c[23];
                    sh8to11Data[i * 4 + 1] = c[24] << 21 | c[25] << 11 | c[26];
                    sh8to11Data[i * 4 + 2] = c[27] << 21 | c[28] << 11 | c[29];
                    sh8to11Data[i * 4 + 3] = c[30] << 21 | c[31] << 11 | c[32];
                    sh12to15Data[i * 4 + 0] = c[33] << 21 | c[34] << 11 | c[35];
                    sh12to15Data[i * 4 + 1] = c[36] << 21 | c[37] << 11 | c[38];
                    sh12to15Data[i * 4 + 2] = c[39] << 21 | c[40] << 11 | c[41];
                    sh12to15Data[i * 4 + 3] = c[42] << 21 | c[43] << 11 | c[44];
                } else {
                    sh8to11Data[i] = c[21] << 21 | c[22] << 11 | c[23];
                }
            }
        }
        this.sh1to3Texture.unlock();
        this.sh4to7Texture?.unlock();
        this.sh8to11Texture?.unlock();
        this.sh12to15Texture?.unlock();
    }
}

const vertexGLSL = /* glsl */ `
    attribute vec2 vertex_position;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.0, 1.0);
    }
`;
const fragmentGLSL = /* glsl */ `
    #include "gsplatEvalSHVS"

    // takes a normalized 3-component value, convert to (11, 11, 10) bit range and
    // then package into RGBA8
    vec4 packRgb(vec3 v) {
        uvec3 vb = uvec3(clamp(v, vec3(0.0), vec3(1.0)) * vec3(2047.0, 2047.0, 1023.0));
        uint bits = (vb.x << 21) | (vb.y << 10) | vb.z;
        return vec4((uvec4(bits) >> uvec4(24, 16, 8, 0)) & uvec4(0xff)) / vec4(255.0);
    }

    uniform mediump vec3 dir;
    uniform mediump sampler2D centroids;
    uniform mediump float shN_mins;
    uniform mediump float shN_maxs;

    void main(void) {
        ivec2 uv = ivec2(gl_FragCoord.xy) * ivec2(SH_COEFFS, 1);
        mediump vec3 coefficients[SH_COEFFS];

        // read coefficients
        for (int i = 0; i < SH_COEFFS; i++) {
            vec3 s = texelFetch(centroids, ivec2(uv.x + i, uv.y), 0).xyz;
            coefficients[i] = mix(vec3(shN_mins), vec3(shN_maxs), s);
        }

        // evaluate
        gl_FragColor = packRgb(evalSH(coefficients, dir) * 0.25 + 0.5);
    }
`;
const vertexWGSL = /* wgsl */ `
    attribute vertex_position: vec2f;
    @vertex
    fn vertexMain(input: VertexInput) -> VertexOutput {
        var output: VertexOutput;
        output.position = vec4f(vertex_position, 0.0, 1.0);
        return output;
    }
`;
const fragmentWGSL = /* wgsl */ `
    #include "gsplatEvalSHVS"

    // takes a normalized 3-component value, convert to (11, 11, 10) bit range and
    // then package into RGBA8
    fn packRgb(v: vec3f) -> vec4f {
        let vb = vec3u(clamp(v, vec3f(0.0), vec3f(1.0)) * vec3f(2047.0, 2047.0, 1023.0));
        let bits = dot(vb, vec3u(1 << 21, 1 << 10, 1));
        return vec4f((vec4u(bits) >> vec4u(24, 16, 8, 0)) & vec4u(0xff)) / vec4f(255.0);
    }

    uniform dir: vec3f;
    uniform shN_mins: f32;
    uniform shN_maxs: f32;

    var centroids: texture_2d<f32>;

    @fragment
    fn fragmentMain(input: FragmentInput) -> FragmentOutput {
        var output: FragmentOutput;

        var uv = vec2i(input.position.xy) * vec2i(SH_COEFFS, 1);
        var coefficients: array<vec3f, SH_COEFFS>;

        // read coefficients
        for (var i: i32 = 0; i < SH_COEFFS; i++) {
            let s: vec3f = textureLoad(centroids, vec2i(uv.x + i, uv.y), 0).xyz;
            coefficients[i] = mix(vec3f(uniform.shN_mins), vec3f(uniform.shN_maxs), s);
        }

        // evaluate
        output.color = packRgb(evalSH(&coefficients, uniform.dir) * 0.25 + 0.5);

        return output;
    }
`;
const gsplatSogsColorGLSL = /* glsl */ `
    uniform mediump sampler2D sh0;
    uniform highp sampler2D sh_labels;
    uniform mediump sampler2D sh_result;

    uniform vec4 sh0_mins;
    uniform vec4 sh0_maxs;

    float SH_C0 = 0.28209479177387814;

    // unpack 11, 11, 10 normalized value from rgba8 texture sample
    vec3 unpackRgb(vec4 v) {
        uvec4 uv = uvec4(v * 255.0);
        uint bits = (uv.x << 24) | (uv.y << 16) | (uv.z << 8) | uv.w;
        uvec3 vb = (uvec3(bits) >> uvec3(21, 10, 0)) & uvec3(0x7ffu, 0x7ffu, 0x3ffu);
        return vec3(vb) / vec3(2047.0, 2047.0, 1023.0);
    }

    vec4 readColor(in SplatSource source) {
        // sample base color
        vec4 baseSample = mix(sh0_mins, sh0_maxs, texelFetch(sh0, source.uv, 0));

        // resolve base color
        vec4 base = vec4(vec3(0.5) + baseSample.xyz * SH_C0, 1.0 / (1.0 + exp(-baseSample.w)));

        // extract spherical harmonics palette index
        ivec2 labelSample = ivec2(texelFetch(sh_labels, source.uv, 0).xy * 255.0);
        int n = labelSample.x + labelSample.y * 256;

        vec4 shSample = texelFetch(sh_result, ivec2(n % 64, n / 64), 0);
        vec3 sh = (unpackRgb(shSample) - vec3(0.5)) * 4.0;

        return vec4(base.xyz + sh, base.w);
    }
`;
const gsplatSogsColorWGSL = /* wgsl */ `
    var sh0: texture_2d<f32>;
    var sh_labels: texture_2d<f32>;
    var sh_result: texture_2d<f32>;

    uniform sh0_mins: vec4f;
    uniform sh0_maxs: vec4f;

    const SH_C0: f32 = 0.28209479177387814;

    // unpack 11, 11, 10 normalized value from rgba8 texture sample
    fn unpackRgb(v: vec4f) -> vec3f {
        let bits = dot(vec4u(v * 255.0), vec4u(1u << 24, 1u << 16, 1u << 8, 1u));
        let vb = (vec3u(bits) >> vec3u(21, 10, 0)) & vec3u(0x7ffu, 0x7ffu, 0x3ffu);
        return vec3f(vb) / vec3f(2047.0, 2047.0, 1023.0);
    }

    fn readColor(source: ptr<function, SplatSource>) -> vec4f {
        // sample base color
        let baseSample: vec4f = mix(uniform.sh0_mins, uniform.sh0_maxs, textureLoad(sh0, source.uv, 0));
        let base = vec4f(vec3f(0.5) + baseSample.xyz * SH_C0, 1.0 / (1.0 + exp(-baseSample.w)));

        // extract spherical harmonics palette index
        let labelSample: vec2i = vec2i(textureLoad(sh_labels, source.uv, 0).xy * 255.0);
        let n = labelSample.x + labelSample.y * 256;

        let shSample: vec4f = textureLoad(sh_result, vec2i(n % 64, n / 64), 0);
        let sh: vec3f = (unpackRgb(shSample) - vec3f(0.5)) * 4.0;

        return vec4f(base.xyz + sh, base.w);
    }
`;
const resolve$3 = (scope, values)=>{
    for(const key in values){
        scope.resolve(key).setValue(values[key]);
    }
};
class CustomRenderPass extends RenderPass {
    execute() {
        this.executeCallback?.();
    }
    constructor(...args){
        super(...args), /**
     * @type {() => void | null}
     */ this.executeCallback = null;
    }
}
const invModelMat$1 = new Mat4();
const dir$1 = new Vec3();
class GSplatResolveSH {
    constructor(device, gsplatInstance){
        this.prevDir = new Vec3();
        this.updateMode = 'enable' // 'enable', 'disable', 'always'
        ;
        this.device = device;
        this.gsplatInstance = gsplatInstance;
        const { resource } = gsplatInstance;
        const includes = new Map(ShaderChunks.get(device, device.isWebGPU ? 'wgsl' : 'glsl'));
        this.shader = ShaderUtils.createShader(device, {
            uniqueName: 'gsplatResolveSH',
            vertexGLSL,
            fragmentGLSL,
            vertexWGSL,
            fragmentWGSL,
            vertexIncludes: includes,
            fragmentIncludes: includes,
            fragmentDefines: new Map([
                [
                    'SH_BANDS',
                    resource.gsplatData.shBands.toString()
                ]
            ]),
            attributes: {
                vertex_position: SEMANTIC_POSITION
            }
        });
        this.texture = resource.createTexture('centroids', PIXELFORMAT_RGBA8, new Vec2(64, 1024));
        this.renderTarget = new RenderTarget({
            colorBuffer: this.texture,
            depth: false
        });
        this.renderPass = new CustomRenderPass(device);
        this.renderPass.init(this.renderTarget, {});
        this.renderPass.colorOps.clear = true;
        this.quadRender = new QuadRender(this.shader);
        const { material } = gsplatInstance;
        material.setDefine('SH_BANDS', '0');
        const { shaderChunks } = material;
        shaderChunks.glsl.set('gsplatSogsColorVS', gsplatSogsColorGLSL);
        shaderChunks.wgsl.set('gsplatSogsColorVS', gsplatSogsColorWGSL);
        material.update();
        device.scope.resolve('sh_result').setValue(this.texture);
    }
    destroy() {
        const { gsplatInstance } = this;
        const { material } = gsplatInstance;
        material.setDefine('SH_BANDS', gsplatInstance.resource.gsplatData.shBands.toString());
        const { shaderChunks } = material;
        shaderChunks.glsl.delete('gsplatSogsColorVS');
        shaderChunks.wgsl.delete('gsplatSogsColorVS');
        material.update();
        this.quadRender.destroy();
        this.renderPass.destroy();
        this.renderTarget.destroy();
        this.texture.destroy();
        this.shader.destroy();
    }
    render(camera, modelMat) {
        const { prevDir, updateMode } = this;
        // disabled
        if (updateMode === 'disable') {
            return;
        }
        // calculate camera Z in model space
        invModelMat$1.invert(modelMat);
        invModelMat$1.transformVector(camera.forward, dir$1);
        // if direction hasn't changed early out
        dir$1.normalize();
        if (updateMode === 'enable' && dir$1.equalsApprox(prevDir, 1e-3)) {
            return;
        }
        prevDir.copy(dir$1);
        const execute = ()=>{
            const { device } = this;
            const { sh_centroids, meta } = this.gsplatInstance.resource.gsplatData;
            resolve$3(device.scope, {
                dir: dir$1.toArray(),
                centroids: sh_centroids,
                shN_mins: meta.shN.mins,
                shN_maxs: meta.shN.maxs
            });
            device.setCullMode(CULLFACE_NONE);
            device.setDepthState(DepthState.NODEPTH);
            device.setStencilState(null, null);
            device.setBlendState(BlendState.NOBLEND);
            this.quadRender.render();
        };
        this.renderPass.executeCallback = execute;
        this.renderPass.render();
    }
}

// sort blind set of data
function SortWorker() {
    const myself = typeof self !== 'undefined' && self || require('node:worker_threads').parentPort;
    let order;
    let centers;
    let chunks;
    let mapping;
    let cameraPosition;
    let cameraDirection;
    let forceUpdate = false;
    const lastCameraPosition = {
        x: 0,
        y: 0,
        z: 0
    };
    const lastCameraDirection = {
        x: 0,
        y: 0,
        z: 0
    };
    const boundMin = {
        x: 0,
        y: 0,
        z: 0
    };
    const boundMax = {
        x: 0,
        y: 0,
        z: 0
    };
    let distances;
    let countBuffer;
    // could be increased, but this seems a good compromise between stability and performance
    const numBins = 32;
    const binCount = new Array(numBins).fill(0);
    const binBase = new Array(numBins).fill(0);
    const binDivider = new Array(numBins).fill(0);
    const binarySearch = (m, n, compare_fn)=>{
        while(m <= n){
            const k = n + m >> 1;
            const cmp = compare_fn(k);
            if (cmp > 0) {
                m = k + 1;
            } else if (cmp < 0) {
                n = k - 1;
            } else {
                return k;
            }
        }
        return ~m;
    };
    const update = ()=>{
        if (!order || !centers || centers.length === 0 || !cameraPosition || !cameraDirection) return;
        const px = cameraPosition.x;
        const py = cameraPosition.y;
        const pz = cameraPosition.z;
        const dx = cameraDirection.x;
        const dy = cameraDirection.y;
        const dz = cameraDirection.z;
        const epsilon = 0.001;
        if (!forceUpdate && Math.abs(px - lastCameraPosition.x) < epsilon && Math.abs(py - lastCameraPosition.y) < epsilon && Math.abs(pz - lastCameraPosition.z) < epsilon && Math.abs(dx - lastCameraDirection.x) < epsilon && Math.abs(dy - lastCameraDirection.y) < epsilon && Math.abs(dz - lastCameraDirection.z) < epsilon) {
            return;
        }
        forceUpdate = false;
        lastCameraPosition.x = px;
        lastCameraPosition.y = py;
        lastCameraPosition.z = pz;
        lastCameraDirection.x = dx;
        lastCameraDirection.y = dy;
        lastCameraDirection.z = dz;
        // calc min/max distance using bound
        let minDist;
        let maxDist;
        for(let i = 0; i < 8; ++i){
            const x = i & 1 ? boundMin.x : boundMax.x;
            const y = i & 2 ? boundMin.y : boundMax.y;
            const z = i & 4 ? boundMin.z : boundMax.z;
            const d = x * dx + y * dy + z * dz;
            if (i === 0) {
                minDist = maxDist = d;
            } else {
                minDist = Math.min(minDist, d);
                maxDist = Math.max(maxDist, d);
            }
        }
        const numVertices = centers.length / 3;
        // calculate number of bits needed to store sorting result
        const compareBits = Math.max(10, Math.min(20, Math.round(Math.log2(numVertices / 4))));
        const bucketCount = 2 ** compareBits + 1;
        // create distance buffer
        if (distances?.length !== numVertices) {
            distances = new Uint32Array(numVertices);
        }
        if (!countBuffer || countBuffer.length !== bucketCount) {
            countBuffer = new Uint32Array(bucketCount);
        } else {
            countBuffer.fill(0);
        }
        const range = maxDist - minDist;
        if (range < 1e-6) {
            // all points are at the same distance
            for(let i = 0; i < numVertices; ++i){
                distances[i] = 0;
                countBuffer[0]++;
            }
        } else {
            // use chunks to calculate rough histogram of splats per distance
            const numChunks = chunks.length / 4;
            binCount.fill(0);
            for(let i = 0; i < numChunks; ++i){
                const x = chunks[i * 4 + 0];
                const y = chunks[i * 4 + 1];
                const z = chunks[i * 4 + 2];
                const r = chunks[i * 4 + 3];
                const d = x * dx + y * dy + z * dz - minDist;
                const binMin = Math.max(0, Math.floor((d - r) * numBins / range));
                const binMax = Math.min(numBins, Math.ceil((d + r) * numBins / range));
                for(let j = binMin; j < binMax; ++j){
                    binCount[j]++;
                }
            }
            // count total number of histogram bin entries
            const binTotal = binCount.reduce((a, b)=>a + b, 0);
            // calculate per-bin base and divider
            for(let i = 0; i < numBins; ++i){
                binDivider[i] = binCount[i] / binTotal * bucketCount >>> 0;
            }
            for(let i = 0; i < numBins; ++i){
                binBase[i] = i === 0 ? 0 : binBase[i - 1] + binDivider[i - 1];
            }
            // generate per vertex distance key using histogram to distribute bits
            const binRange = range / numBins;
            let ii = 0;
            for(let i = 0; i < numVertices; ++i){
                const x = centers[ii++];
                const y = centers[ii++];
                const z = centers[ii++];
                const d = (x * dx + y * dy + z * dz - minDist) / binRange;
                const bin = d >>> 0;
                const sortKey = binBase[bin] + binDivider[bin] * (d - bin) >>> 0;
                distances[i] = sortKey;
                // count occurrences of each distance
                countBuffer[sortKey]++;
            }
        }
        // Change countBuffer[i] so that it contains actual position of this digit in outputArray
        for(let i = 1; i < bucketCount; i++){
            countBuffer[i] += countBuffer[i - 1];
        }
        // Build the output array
        for(let i = 0; i < numVertices; i++){
            const distance = distances[i];
            const destIndex = --countBuffer[distance];
            order[destIndex] = i;
        }
        // Find splat with distance 0 to limit rendering behind the camera
        const cameraDist = px * dx + py * dy + pz * dz;
        const dist = (i)=>{
            let o = order[i] * 3;
            return centers[o++] * dx + centers[o++] * dy + centers[o] * dz - cameraDist;
        };
        const findZero = ()=>{
            const result = binarySearch(0, numVertices - 1, (i)=>-dist(i));
            return Math.min(numVertices, Math.abs(result));
        };
        const count = dist(numVertices - 1) >= 0 ? findZero() : numVertices;
        // apply mapping
        if (mapping) {
            for(let i = 0; i < numVertices; ++i){
                order[i] = mapping[order[i]];
            }
        }
        // send results
        myself.postMessage({
            order: order.buffer,
            count
        }, [
            order.buffer
        ]);
        order = null;
    };
    myself.addEventListener('message', (message)=>{
        const msgData = message.data ?? message;
        if (msgData.order) {
            order = new Uint32Array(msgData.order);
        }
        if (msgData.centers) {
            centers = new Float32Array(msgData.centers);
            forceUpdate = true;
            if (msgData.chunks) {
                const chunksSrc = new Float32Array(msgData.chunks);
                // reuse chunks memory, but we only need 4 floats per chunk
                chunks = new Float32Array(msgData.chunks, 0, chunksSrc.length * 4 / 6);
                boundMin.x = chunksSrc[0];
                boundMin.y = chunksSrc[1];
                boundMin.z = chunksSrc[2];
                boundMax.x = chunksSrc[3];
                boundMax.y = chunksSrc[4];
                boundMax.z = chunksSrc[5];
                // convert chunk min/max to center/radius
                for(let i = 0; i < chunksSrc.length / 6; ++i){
                    const mx = chunksSrc[i * 6 + 0];
                    const my = chunksSrc[i * 6 + 1];
                    const mz = chunksSrc[i * 6 + 2];
                    const Mx = chunksSrc[i * 6 + 3];
                    const My = chunksSrc[i * 6 + 4];
                    const Mz = chunksSrc[i * 6 + 5];
                    chunks[i * 4 + 0] = (mx + Mx) * 0.5;
                    chunks[i * 4 + 1] = (my + My) * 0.5;
                    chunks[i * 4 + 2] = (mz + Mz) * 0.5;
                    chunks[i * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;
                    if (mx < boundMin.x) boundMin.x = mx;
                    if (my < boundMin.y) boundMin.y = my;
                    if (mz < boundMin.z) boundMin.z = mz;
                    if (Mx > boundMax.x) boundMax.x = Mx;
                    if (My > boundMax.y) boundMax.y = My;
                    if (Mz > boundMax.z) boundMax.z = Mz;
                }
            } else {
                // chunk bounds weren't provided, so calculate them from the centers
                const numVertices = centers.length / 3;
                const numChunks = Math.ceil(numVertices / 256);
                // allocate storage for one bounding sphere per 256-vertex chunk
                chunks = new Float32Array(numChunks * 4);
                boundMin.x = boundMin.y = boundMin.z = Infinity;
                boundMax.x = boundMax.y = boundMax.z = -Infinity;
                // calculate bounds
                let mx, my, mz, Mx, My, Mz;
                for(let c = 0; c < numChunks; ++c){
                    mx = my = mz = Infinity;
                    Mx = My = Mz = -Infinity;
                    const start = c * 256;
                    const end = Math.min(numVertices, (c + 1) * 256);
                    for(let i = start; i < end; ++i){
                        const x = centers[i * 3 + 0];
                        const y = centers[i * 3 + 1];
                        const z = centers[i * 3 + 2];
                        const validX = Number.isFinite(x);
                        const validY = Number.isFinite(y);
                        const validZ = Number.isFinite(z);
                        if (!validX) centers[i * 3 + 0] = 0;
                        if (!validY) centers[i * 3 + 1] = 0;
                        if (!validZ) centers[i * 3 + 2] = 0;
                        if (!validX || !validY || !validZ) {
                            continue;
                        }
                        if (x < mx) mx = x;
                        else if (x > Mx) Mx = x;
                        if (y < my) my = y;
                        else if (y > My) My = y;
                        if (z < mz) mz = z;
                        else if (z > Mz) Mz = z;
                        if (x < boundMin.x) boundMin.x = x;
                        else if (x > boundMax.x) boundMax.x = x;
                        if (y < boundMin.y) boundMin.y = y;
                        else if (y > boundMax.y) boundMax.y = y;
                        if (z < boundMin.z) boundMin.z = z;
                        else if (z > boundMax.z) boundMax.z = z;
                    }
                    // calculate chunk center and radius from bound min/max
                    chunks[c * 4 + 0] = (mx + Mx) * 0.5;
                    chunks[c * 4 + 1] = (my + My) * 0.5;
                    chunks[c * 4 + 2] = (mz + Mz) * 0.5;
                    chunks[c * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;
                }
            }
        }
        if (msgData.hasOwnProperty('mapping')) {
            mapping = msgData.mapping ? new Uint32Array(msgData.mapping) : null;
            forceUpdate = true;
        }
        if (msgData.cameraPosition) cameraPosition = msgData.cameraPosition;
        if (msgData.cameraDirection) cameraDirection = msgData.cameraDirection;
        update();
    });
}

class GSplatSorter extends EventHandler {
    constructor(){
        super();
        const messageHandler = (message)=>{
            const msgData = message.data ?? message;
            const newOrder = msgData.order;
            const oldOrder = this.orderTexture._levels[0].buffer;
            // send vertex storage to worker to start the next frame
            this.worker.postMessage({
                order: oldOrder
            }, [
                oldOrder
            ]);
            // write the new order data to gpu texture memory
            this.orderTexture._levels[0] = new Uint32Array(newOrder);
            this.orderTexture.upload();
            // set new data directly on texture
            this.fire('updated', msgData.count);
        };
        const workerSource = `(${SortWorker.toString()})()`;
        if (platform.environment === 'node') {
            this.worker = new Worker(workerSource, {
                eval: true
            });
            this.worker.on('message', messageHandler);
        } else {
            this.worker = new Worker(URL.createObjectURL(new Blob([
                workerSource
            ], {
                type: "application/javascript"
            })));
            this.worker.addEventListener('message', messageHandler);
        }
    }
    destroy() {
        this.worker.terminate();
        this.worker = null;
    }
    init(orderTexture, centers, chunks) {
        this.orderTexture = orderTexture;
        this.centers = centers.slice();
        // get the texture's storage buffer and make a copy
        const orderBuffer = this.orderTexture.lock({
            mode: TEXTURELOCK_READ
        }).slice();
        this.orderTexture.unlock();
        // initialize order data
        for(let i = 0; i < orderBuffer.length; ++i){
            orderBuffer[i] = i;
        }
        const obj = {
            order: orderBuffer.buffer,
            centers: centers.buffer,
            chunks: chunks?.buffer
        };
        const transfer = [
            orderBuffer.buffer,
            centers.buffer
        ].concat(chunks ? [
            chunks.buffer
        ] : []);
        // send the initial buffer to worker
        this.worker.postMessage(obj, transfer);
    }
    setMapping(mapping) {
        if (mapping) {
            // create new centers array
            const centers = new Float32Array(mapping.length * 3);
            for(let i = 0; i < mapping.length; ++i){
                const src = mapping[i] * 3;
                const dst = i * 3;
                centers[dst + 0] = this.centers[src + 0];
                centers[dst + 1] = this.centers[src + 1];
                centers[dst + 2] = this.centers[src + 2];
            }
            // update worker with new centers and mapping for the subset of splats
            this.worker.postMessage({
                centers: centers.buffer,
                mapping: mapping.buffer
            }, [
                centers.buffer,
                mapping.buffer
            ]);
        } else {
            // restore original centers
            const centers = this.centers.slice();
            this.worker.postMessage({
                centers: centers.buffer,
                mapping: null
            }, [
                centers.buffer
            ]);
        }
    }
    setCamera(pos, dir) {
        this.worker.postMessage({
            cameraPosition: {
                x: pos.x,
                y: pos.y,
                z: pos.z
            },
            cameraDirection: {
                x: dir.x,
                y: dir.y,
                z: dir.z
            }
        });
    }
}

var glslGsplatSogsReorderPS = /* glsl */ `
#include "gsplatPackingPS"

uniform highp sampler2D means_l;
uniform highp sampler2D means_u;
uniform highp sampler2D quats;
uniform highp sampler2D scales;
uniform highp sampler2D sh0;
uniform highp sampler2D sh_labels;

uniform highp uint numSplats;

#ifdef REORDER_V1
    float sigmoid(float x) { return 1.0 / (1.0 + exp(-x)); }
    vec3 vmin(vec3 v) { return vec3(min(min(v.x, v.y), v.z)); }
    vec3 vmax(vec3 v) { return vec3(max(max(v.x, v.y), v.z)); }
    vec3 resolve(vec3 m, vec3 M, vec3 v) { return (mix(m, M, v) - vmin(m)) / (vmax(M) - vmin(m)); }
    
    uniform vec3 scalesMins;
    uniform vec3 scalesMaxs;
    uniform vec4 sh0Mins;
    uniform vec4 sh0Maxs;
#else
    uniform vec4 scales_codebook[64];
    uniform vec4 sh0_codebook[64];
#endif

void main(void) {
    int w = int(textureSize(means_l, 0).x);
    ivec2 uv = ivec2(gl_FragCoord.xy);
    if (uint(uv.x + uv.y * w) >= numSplats) {
        discard;
    }

    vec3 meansLSample   = texelFetch(means_l, uv, 0).xyz;
    vec3 meansUSample   = texelFetch(means_u, uv, 0).xyz;
    vec4 quatsSample    = texelFetch(quats, uv, 0);
    vec3 scalesSample   = texelFetch(scales, uv, 0).xyz;
    vec4 sh0Sample      = texelFetch(sh0, uv, 0);
    vec2 shLabelsSample = texelFetch(sh_labels, uv, 0).xy;

    #ifdef REORDER_V1
        uint scale = pack101010(resolve(scalesMins, scalesMaxs, scalesSample));
        uint sh0 = pack111110(resolve(sh0Mins.xyz, sh0Maxs.xyz, sh0Sample.xyz));
        float alpha = sigmoid(mix(sh0Mins.w, sh0Maxs.w, sh0Sample.w));
    #else
        uint scale = pack101010(resolveCodebook(scalesSample, scales_codebook));    // resolve scale to 10,10,10 bits
        uint sh0 = pack111110(resolveCodebook(sh0Sample.xyz, sh0_codebook));        // resolve sh0 to 11,11,10 bits
        float alpha = sh0Sample.w;
    #endif

    uint qmode = uint(quatsSample.w * 255.0) - 252u;

    pcFragColor0 = uvec4(
        pack8888(vec4(meansLSample, shLabelsSample.x)),
        pack8888(vec4(meansUSample, shLabelsSample.y)),
        pack8888(vec4(quatsSample.xyz, alpha)),
        (scale << 2u) | qmode
    );
    pcFragColor1 = unpack8888(sh0);
}
`;

var wgslGsplatSogsReorderPS = /* wgsl */ `
#include "gsplatPackingPS"

var means_l: texture_2d<f32>;
var means_u: texture_2d<f32>;
var quats: texture_2d<f32>;
var scales: texture_2d<f32>;
var sh0: texture_2d<f32>;
var sh_labels: texture_2d<f32>;

uniform numSplats: u32;

#ifdef REORDER_V1
    fn sigmoid(x: f32) -> f32 { return 1.0 / (1.0 + exp(-x)); }
    fn vmin(v: vec3f) -> vec3f { return vec3f(min(min(v.x, v.y), v.z)); }
    fn vmax(v: vec3f) -> vec3f { return vec3f(max(max(v.x, v.y), v.z)); }
    fn resolve(m: vec3f, M: vec3f, v: vec3f) -> vec3f { return (mix(m, M, v) - vmin(m)) / (vmax(M) - vmin(m)); }

    uniform scalesMins: vec3f;
    uniform scalesMaxs: vec3f;
    uniform sh0Mins: vec4f;
    uniform sh0Maxs: vec4f;
#else
    uniform scales_codebook: array<vec4f, 64>;
    uniform sh0_codebook: array<vec4f, 64>;
#endif

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;

    let w: u32 = textureDimensions(means_l, 0).x;
    let uv: vec2<u32> = vec2<u32>(input.position.xy);
    if (uv.x + uv.y * w >= uniform.numSplats) {
        discard;
        return output;
    }

    // fetch the source index and calculate source uv
    let meansLSample: vec3<f32> = textureLoad(means_l, uv, 0).xyz;
    let meansUSample: vec3<f32> = textureLoad(means_u, uv, 0).xyz;
    let quatsSample: vec4<f32> = textureLoad(quats, uv, 0);
    let scalesSample: vec3<f32> = textureLoad(scales, uv, 0).xyz;
    let sh0Sample: vec4f = textureLoad(sh0, uv, 0);
    let shLabelsSample: vec2<f32> = textureLoad(sh_labels, uv, 0).xy;

    #ifdef REORDER_V1
        let scale = pack101010(resolve(uniform.scalesMins, uniform.scalesMaxs, scalesSample));
        let sh0 = pack111110(resolve(uniform.sh0Mins.xyz, uniform.sh0Maxs.xyz, sh0Sample.xyz));
        let alpha = sigmoid(mix(uniform.sh0Mins.w, uniform.sh0Maxs.w, sh0Sample.w));
    #else
        let scale = pack101010(resolveCodebook(scalesSample, &uniform.scales_codebook));    // resolve scale to 10,10,10 bits
        let sh0 = pack111110(resolveCodebook(sh0Sample.xyz, &uniform.sh0_codebook));        // resolve sh0 to 11,11,10 bits
        let alpha = sh0Sample.w;
    #endif

    let qmode = u32(quatsSample.w * 255.0) - 252u;

    output.color = vec4u(
        pack8888(vec4f(meansLSample, shLabelsSample.x)),
        pack8888(vec4f(meansUSample, shLabelsSample.y)),
        pack8888(vec4f(quatsSample.xyz, alpha)),
        (scale << 2u) | qmode
    );

    output.color1 = unpack8888(sh0);

    return output;
}
`;

var glslGsplatSogsReorderSh = /* glsl */ `
#include "gsplatPackingPS"

uniform highp sampler2D sh_centroids;

uniform vec4 shN_codebook[64];

void main(void) {
    ivec2 uv = ivec2(gl_FragCoord.xy);

    vec3 shNSample = texelFetch(sh_centroids, uv, 0).xyz;

#ifdef REORDER_V1
    pcFragColor0 = unpack8888(pack111110(shNSample));
#else
    pcFragColor0 = unpack8888(pack111110(resolveCodebook(shNSample, shN_codebook)));
#endif
}
`;

var gsplatPackingPS$1 = /* glsl */ `
uint pack8888(vec4 v) {
    uvec4 t = uvec4(v * 255.0) << uvec4(24u, 16u, 8u, 0u);
    return t.x | t.y | t.z | t.w;
}

uint pack101010(vec3 v) {
    uvec3 t = uvec3(v * 1023.0) << uvec3(20u, 10u, 0u);
    return t.x | t.y | t.z;
}

uint pack111110(vec3 v) {
    uvec3 t = uvec3(v * vec3(2047.0, 2047.0, 1023.0)) << uvec3(21u, 10u, 0u);
    return t.x | t.y | t.z;
}

vec4 unpack8888(uint v) {
    return vec4((uvec4(v) >> uvec4(24u, 16u, 8u, 0u)) & 0xffu) / 255.0;
}

vec3 unpack101010(uint v) {
    return vec3((uvec3(v) >> uvec3(20u, 10u, 0u)) & 0x3ffu) / 1023.0;
}

vec3 unpack111110(uint v) {
    return vec3((uvec3(v) >> uvec3(21u, 10u, 0u)) & uvec3(0x7ffu, 0x7ffu, 0x3ffu)) / vec3(2047.0, 2047.0, 1023.0);
}

// resolve the sample using the supplied codebook and return a normalized value relative to codebook min and max
vec3 resolveCodebook(vec3 s, vec4 codebook[64]) {
    uvec3 idx = uvec3(s * 255.0);
    vec3 v = vec3(
        codebook[idx.x >> 2u][idx.x & 3u],
        codebook[idx.y >> 2u][idx.y & 3u],
        codebook[idx.z >> 2u][idx.z & 3u]
    );
    return (v - codebook[0].x) / (codebook[63].w - codebook[0].x);
}
`;

var wgslGsplatSogsReorderSH = /* wgsl */ `
#include "gsplatPackingPS"

var sh_centroids: texture_2d<f32>;

uniform shN_codebook: array<vec4f, 64>;

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;

    var uv = vec2i(input.position.xy);

    var shNSample = textureLoad(sh_centroids, uv, 0).xyz;

#ifdef REORDER_V1
    output.color = unpack8888(pack111110(shNSample));
#else
    output.color = unpack8888(pack111110(resolveCodebook(shNSample, &uniform.shN_codebook)));
#endif

    return output;
}
`;

var gsplatPackingPS = /* wgsl */ `
fn pack8888(v: vec4f) -> u32 {
    let t = vec4u(v * 255.0) << vec4u(24u, 16u, 8u, 0u);
    return t.x | t.y | t.z | t.w;
}

fn pack101010(v: vec3f) -> u32 {
    let t = vec3u(v * vec3f(1023.0, 1023.0, 1023.0)) << vec3u(20u, 10u, 0u);
    return t.x | t.y | t.z;
}

fn pack111110(v: vec3f) -> u32 {
    let t = vec3u(v * vec3f(2047.0, 2047.0, 1023.0)) << vec3u(21u, 10u, 0u);
    return t.x | t.y | t.z;
}

fn unpack8888(v: u32) -> vec4f {
    return vec4f((vec4u(v) >> vec4u(24u, 16u, 8u, 0u)) & vec4u(0xffu)) / 255.0;
}

fn unpack101010(v: u32) -> vec3f {
    return vec3f((vec3u(v) >> vec3u(20u, 10u, 0u)) & vec3u(0x3ffu)) / 1023.0;
}

fn unpack111110(v: u32) -> vec3f {
    return vec3f((vec3u(v) >> vec3u(21u, 10u, 0u)) & vec3u(0x7ffu, 0x7ffu, 0x3ffu)) / vec3f(2047.0, 2047.0, 1023.0);
}

// resolve the sample using the supplied codebook and return a normalized value relative to codebook min and max
fn resolveCodebook(s: vec3f, codebook: ptr<uniform, array<vec4f, 64>>) -> vec3f {
    let idx = vec3u(s * 255.0);
    let v = vec3f(
        codebook[idx.x >> 2u][idx.x & 3u],
        codebook[idx.y >> 2u][idx.y & 3u],
        codebook[idx.z >> 2u][idx.z & 3u]
    );
    return (v - codebook[0].x) / (codebook[63].w - codebook[0].x);
}
`;

const SH_C0$2 = 0.28209479177387814;
const readImageDataAsync = (texture)=>{
    return texture.read(0, 0, texture.width, texture.height, {
        mipLevel: 0,
        face: 0,
        immediate: true
    });
};
const resolve$2 = (scope, values)=>{
    for(const key in values){
        scope.resolve(key).setValue(values[key]);
    }
};
class GSplatSogsIterator {
    constructor(data, p, r, s, c, sh){
        const lerp = (a, b, t)=>a * (1 - t) + b * t;
        // extract means for centers
        const { meta, shBands } = data;
        const { means, scales, sh0, shN } = meta;
        const means_l_data = p && data.means_l._levels[0];
        const means_u_data = p && data.means_u._levels[0];
        const quats_data = r && data.quats._levels[0];
        const scales_data = s && data.scales._levels[0];
        const sh0_data = c && data.sh0._levels[0];
        const sh_labels_data = sh && data.sh_labels._levels[0];
        const sh_centroids_data = sh && data.sh_centroids._levels[0];
        const norm = 2.0 / Math.sqrt(2.0);
        const coeffs = {
            1: 3,
            2: 8,
            3: 15
        }[shBands] ?? 0;
        this.read = (i)=>{
            if (p) {
                const nx = lerp(means.mins[0], means.maxs[0], ((means_u_data[i * 4 + 0] << 8) + means_l_data[i * 4 + 0]) / 65535);
                const ny = lerp(means.mins[1], means.maxs[1], ((means_u_data[i * 4 + 1] << 8) + means_l_data[i * 4 + 1]) / 65535);
                const nz = lerp(means.mins[2], means.maxs[2], ((means_u_data[i * 4 + 2] << 8) + means_l_data[i * 4 + 2]) / 65535);
                p.x = Math.sign(nx) * (Math.exp(Math.abs(nx)) - 1);
                p.y = Math.sign(ny) * (Math.exp(Math.abs(ny)) - 1);
                p.z = Math.sign(nz) * (Math.exp(Math.abs(nz)) - 1);
            }
            if (r) {
                const a = (quats_data[i * 4 + 0] / 255 - 0.5) * norm;
                const b = (quats_data[i * 4 + 1] / 255 - 0.5) * norm;
                const c = (quats_data[i * 4 + 2] / 255 - 0.5) * norm;
                const d = Math.sqrt(Math.max(0, 1 - (a * a + b * b + c * c)));
                const mode = quats_data[i * 4 + 3] - 252;
                switch(mode){
                    case 0:
                        r.set(a, b, c, d);
                        break;
                    case 1:
                        r.set(d, b, c, a);
                        break;
                    case 2:
                        r.set(b, d, c, a);
                        break;
                    case 3:
                        r.set(b, c, d, a);
                        break;
                }
            }
            if (s) {
                if (meta.version === 2) {
                    const sx = scales.codebook[scales_data[i * 4 + 0]];
                    const sy = scales.codebook[scales_data[i * 4 + 1]];
                    const sz = scales.codebook[scales_data[i * 4 + 2]];
                    s.set(sx, sy, sz);
                } else {
                    const sx = lerp(scales.mins[0], scales.maxs[0], scales_data[i * 4 + 0] / 255);
                    const sy = lerp(scales.mins[1], scales.maxs[1], scales_data[i * 4 + 1] / 255);
                    const sz = lerp(scales.mins[2], scales.maxs[2], scales_data[i * 4 + 2] / 255);
                    s.set(sx, sy, sz);
                }
            }
            if (c) {
                if (meta.version === 2) {
                    const r = sh0.codebook[sh0_data[i * 4 + 0]];
                    const g = sh0.codebook[sh0_data[i * 4 + 1]];
                    const b = sh0.codebook[sh0_data[i * 4 + 2]];
                    const a = sh0_data[i * 4 + 3] / 255;
                    c.set(0.5 + r * SH_C0$2, 0.5 + g * SH_C0$2, 0.5 + b * SH_C0$2, a);
                } else {
                    const r = lerp(sh0.mins[0], sh0.maxs[0], sh0_data[i * 4 + 0] / 255);
                    const g = lerp(sh0.mins[1], sh0.maxs[1], sh0_data[i * 4 + 1] / 255);
                    const b = lerp(sh0.mins[2], sh0.maxs[2], sh0_data[i * 4 + 2] / 255);
                    const a = lerp(sh0.mins[3], sh0.maxs[3], sh0_data[i * 4 + 3] / 255);
                    c.set(0.5 + r * SH_C0$2, 0.5 + g * SH_C0$2, 0.5 + b * SH_C0$2, 1.0 / (1.0 + Math.exp(-a)));
                }
            }
            if (sh) {
                const n = sh_labels_data[i * 4 + 0] + (sh_labels_data[i * 4 + 1] << 8);
                const u = n % 64 * coeffs;
                const v = Math.floor(n / 64);
                if (meta.version === 2) {
                    for(let j = 0; j < 3; ++j){
                        for(let k = 0; k < coeffs; ++k){
                            sh[j * 15 + k] = shN.codebook[sh_centroids_data[(u + k) * 4 + j + v * data.sh_centroids.width * 4]];
                        }
                    }
                } else {
                    for(let j = 0; j < 3; ++j){
                        for(let k = 0; k < coeffs; ++k){
                            sh[j * 15 + k] = lerp(shN.mins, shN.maxs, sh_centroids_data[(u + k) * 4 + j + v * data.sh_centroids.width * 4] / 255);
                        }
                    }
                }
            }
        };
    }
}
class GSplatSogsData {
    destroy() {
        this.means_l?.destroy();
        this.means_u?.destroy();
        this.quats?.destroy();
        this.scales?.destroy();
        this.sh0?.destroy();
        this.sh_centroids?.destroy();
        this.sh_labels?.destroy();
        this.packedTexture?.destroy();
        this.packedSh0?.destroy();
        this.packedShN?.destroy();
    }
    createIter(p, r, s, c, sh) {
        return new GSplatSogsIterator(this, p, r, s, c, sh);
    }
    calcAabb(result) {
        const { mins, maxs } = this.meta.means;
        const map = (v)=>Math.sign(v) * (Math.exp(Math.abs(v)) - 1);
        result.center.set((map(mins[0]) + map(maxs[0])) * 0.5, (map(mins[1]) + map(maxs[1])) * 0.5, (map(mins[2]) + map(maxs[2])) * 0.5);
        result.halfExtents.set((map(maxs[0]) - map(mins[0])) * 0.5, (map(maxs[1]) - map(mins[1])) * 0.5, (map(maxs[2]) - map(mins[2])) * 0.5);
    }
    getCenters(result) {
        const { meta, means_l, means_u, numSplats } = this;
        const { means } = meta;
        const means_u_data = new Uint32Array(means_u._levels[0].buffer);
        const means_l_data = new Uint32Array(means_l._levels[0].buffer);
        const mx = means.mins[0] / 65535;
        const my = means.mins[1] / 65535;
        const mz = means.mins[2] / 65535;
        const Mx = means.maxs[0] / 65535;
        const My = means.maxs[1] / 65535;
        const Mz = means.maxs[2] / 65535;
        for(let i = 0; i < numSplats; i++){
            const idx = i;
            const means_u = means_u_data[idx];
            const means_l = means_l_data[idx];
            const wx = means_u << 8 & 0xff00 | means_l & 0xff;
            const wy = means_u & 0xff00 | means_l >>> 8 & 0xff;
            const wz = means_u >>> 8 & 0xff00 | means_l >>> 16 & 0xff;
            const nx = mx * (65535 - wx) + Mx * wx;
            const ny = my * (65535 - wy) + My * wy;
            const nz = mz * (65535 - wz) + Mz * wz;
            const ax = nx < 0 ? -nx : nx;
            const ay = ny < 0 ? -ny : ny;
            const az = nz < 0 ? -nz : nz;
            result[i * 3] = (nx < 0 ? -1 : 1) * (Math.exp(ax) - 1);
            result[i * 3 + 1] = (ny < 0 ? -1 : 1) * (Math.exp(ay) - 1);
            result[i * 3 + 2] = (nz < 0 ? -1 : 1) * (Math.exp(az) - 1);
        }
    }
    // use bound center for focal point
    calcFocalPoint(result, pred) {
        const { mins, maxs } = this.meta.means;
        const map = (v)=>Math.sign(v) * (Math.exp(Math.abs(v)) - 1);
        result.set((map(mins[0]) + map(maxs[0])) * 0.5, (map(mins[1]) + map(maxs[1])) * 0.5, (map(mins[2]) + map(maxs[2])) * 0.5);
    }
    get isSogs() {
        return true;
    }
    get shBands() {
        // sh palette has 64 sh entries per row. use width to calculate number of bands
        const widths = {
            192: 1,
            512: 2,
            960: 3 // 64 * 15
        };
        return widths[this.sh_centroids?.width] ?? 0;
    }
    async decompress() {
        const members = [
            'x',
            'y',
            'z',
            'f_dc_0',
            'f_dc_1',
            'f_dc_2',
            'opacity',
            'scale_0',
            'scale_1',
            'scale_2',
            'rot_0',
            'rot_1',
            'rot_2',
            'rot_3'
        ];
        const { shBands } = this;
        // copy back gpu texture data so cpu iterator has access to it
        const { means_l, means_u, quats, scales, sh0, sh_labels, sh_centroids } = this;
        means_l._levels[0] = await readImageDataAsync(means_l);
        means_u._levels[0] = await readImageDataAsync(means_u);
        quats._levels[0] = await readImageDataAsync(quats);
        scales._levels[0] = await readImageDataAsync(scales);
        sh0._levels[0] = await readImageDataAsync(sh0);
        // allocate spherical harmonics data
        if (shBands > 0) {
            sh_labels._levels[0] = await readImageDataAsync(sh_labels);
            sh_centroids._levels[0] = await readImageDataAsync(sh_centroids);
            const shMembers = [];
            for(let i = 0; i < 45; ++i){
                shMembers.push(`f_rest_${i}`);
            }
            members.splice(members.indexOf('f_dc_0') + 1, 0, ...shMembers);
        }
        // allocate uncompressed data
        const data = {};
        members.forEach((name)=>{
            data[name] = new Float32Array(this.numSplats);
        });
        const p = new Vec3();
        const r = new Quat();
        const s = new Vec3();
        const c = new Vec4();
        const sh = shBands > 0 ? new Float32Array(45) : null;
        const iter = this.createIter(p, r, s, c, sh);
        for(let i = 0; i < this.numSplats; ++i){
            iter.read(i);
            data.x[i] = p.x;
            data.y[i] = p.y;
            data.z[i] = p.z;
            data.rot_1[i] = r.x;
            data.rot_2[i] = r.y;
            data.rot_3[i] = r.z;
            data.rot_0[i] = r.w;
            data.scale_0[i] = s.x;
            data.scale_1[i] = s.y;
            data.scale_2[i] = s.z;
            data.f_dc_0[i] = (c.x - 0.5) / SH_C0$2;
            data.f_dc_1[i] = (c.y - 0.5) / SH_C0$2;
            data.f_dc_2[i] = (c.z - 0.5) / SH_C0$2;
            // convert opacity to log sigmoid taking into account infinities at 0 and 1
            data.opacity[i] = c.w <= 0 ? -40 : c.w >= 1 ? 40 : -Math.log(1 / c.w - 1);
            if (sh) {
                for(let c = 0; c < 45; ++c){
                    data[`f_rest_${c}`][i] = sh[c];
                }
            }
        }
        return new GSplatData([
            {
                name: 'vertex',
                count: this.numSplats,
                properties: members.map((name)=>{
                    return {
                        name: name,
                        type: 'float',
                        byteSize: 4,
                        storage: data[name]
                    };
                })
            }
        ]);
    }
    // pack the means, quats, scales and sh_labels data into one RGBA32U texture
    packGpuMemory() {
        const { meta, means_l, means_u, quats, scales, sh0, sh_labels, numSplats } = this;
        const { device } = means_l;
        const { scope } = device;
        const shaderKey = meta.version === 2 ? 'v2' : 'v1';
        // Note: do not destroy it, keep it available for the lifetime of the app
        const shader = ShaderUtils.createShader(device, {
            uniqueName: `GsplatSogsReorderShader-${shaderKey}`,
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexChunk: 'fullscreenQuadVS',
            fragmentGLSL: glslGsplatSogsReorderPS,
            fragmentWGSL: wgslGsplatSogsReorderPS,
            fragmentOutputTypes: [
                'uvec4',
                'vec4'
            ],
            fragmentIncludes: new Map([
                [
                    'gsplatPackingPS',
                    device.isWebGPU ? gsplatPackingPS : gsplatPackingPS$1
                ]
            ]),
            fragmentDefines: meta.version === 2 ? undefined : new Map([
                [
                    'REORDER_V1',
                    '1'
                ]
            ])
        });
        const renderTarget = new RenderTarget({
            colorBuffers: [
                this.packedTexture,
                this.packedSh0
            ],
            depth: false,
            mipLevel: 0
        });
        device.setCullMode(CULLFACE_NONE);
        device.setBlendState(BlendState.NOBLEND);
        device.setDepthState(DepthState.NODEPTH);
        resolve$2(scope, {
            means_l,
            means_u,
            quats,
            scales,
            sh0,
            // use means_l as dummy texture for sh_labels if there is no spherical harmonics data
            sh_labels: sh_labels ?? means_l,
            numSplats,
            'scales_codebook[0]': this.meta.scales.codebook,
            'sh0_codebook[0]': this.meta.sh0.codebook,
            // V1
            scalesMins: meta.scales.mins,
            scalesMaxs: meta.scales.maxs,
            sh0Mins: meta.sh0.mins,
            sh0Maxs: meta.sh0.maxs
        });
        drawQuadWithShader(device, renderTarget, shader);
        renderTarget.destroy();
    }
    packShMemory() {
        const { meta, sh_centroids } = this;
        const { device } = sh_centroids;
        const { scope } = device;
        const shaderKey = meta.version === 2 ? 'v2' : 'v1';
        const shader = ShaderUtils.createShader(device, {
            uniqueName: `GsplatSogsReorderShShader-${shaderKey}`,
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexChunk: 'fullscreenQuadVS',
            fragmentGLSL: glslGsplatSogsReorderSh,
            fragmentWGSL: wgslGsplatSogsReorderSH,
            fragmentIncludes: new Map([
                [
                    'gsplatPackingPS',
                    device.isWebGPU ? gsplatPackingPS : gsplatPackingPS$1
                ]
            ]),
            fragmentDefines: meta.version === 2 ? undefined : new Map([
                [
                    'REORDER_V1',
                    '1'
                ]
            ])
        });
        const renderTarget = new RenderTarget({
            colorBuffer: this.packedShN,
            depth: false,
            mipLevel: 0
        });
        device.setCullMode(CULLFACE_NONE);
        device.setBlendState(BlendState.NOBLEND);
        device.setDepthState(DepthState.NODEPTH);
        resolve$2(scope, {
            sh_centroids,
            'shN_codebook[0]': this.meta.shN.codebook
        });
        drawQuadWithShader(device, renderTarget, shader);
        renderTarget.destroy();
    }
    async prepareGpuData() {
        const { device, height, width } = this.means_l;
        // copy back means_l and means_u data so cpu reorder has access to it
        this.means_l._levels[0] = await readImageDataAsync(this.means_l);
        this.means_u._levels[0] = await readImageDataAsync(this.means_u);
        this.packedTexture = new Texture(device, {
            name: 'sogsPackedTexture',
            width,
            height,
            format: PIXELFORMAT_RGBA32U,
            mipmaps: false
        });
        this.packedSh0 = new Texture(device, {
            name: 'sogsPackedSh0',
            width,
            height,
            format: PIXELFORMAT_RGBA8,
            mipmaps: false
        });
        this.packedShN = this.sh_centroids && new Texture(device, {
            name: 'sogsPackedShN',
            width: this.sh_centroids.width,
            height: this.sh_centroids.height,
            format: PIXELFORMAT_RGBA8,
            mipmaps: false
        });
        device.on('devicerestored', ()=>{
            this.packGpuMemory();
            if (this.packedShN) {
                this.packShMemory();
            }
        });
        this.packGpuMemory();
        if (this.packedShN) {
            this.packShMemory();
        }
    }
    // temporary, for backwards compatibility
    reorderData() {
        return this.prepareGpuData();
    }
}

/**
 * @import { Camera } from '../camera.js'
 * @import { GraphNode } from '../graph-node.js'
 * @import { Texture } from '../../platform/graphics/texture.js'
 */ const mat$3 = new Mat4();
const cameraPosition$2 = new Vec3();
const cameraDirection$1 = new Vec3();
const viewport = [
    0,
    0
];
/** @ignore */ class GSplatInstance {
    /**
     * @param {GSplatResourceBase} resource - The splat instance.
     * @param {object} [options] - Options for the instance.
     * @param {ShaderMaterial|null} [options.material] - The material instance.
     * @param {boolean} [options.highQualitySH] - Whether to use the high quality or the approximate spherical harmonic calculation. Only applies to SOGS data.
     */ constructor(resource, options = {}){
        this.options = {};
        /** @type {GSplatSorter|null} */ this.sorter = null;
        this.lastCameraPosition = new Vec3();
        this.lastCameraDirection = new Vec3();
        /** @type {GSplatResolveSH|null} */ this.resolveSH = null;
        /**
     * List of cameras this instance is visible for. Updated every frame by the renderer.
     *
     * @type {Camera[]}
     * @ignore
     */ this.cameras = [];
        this.resource = resource;
        // create the order texture
        this.orderTexture = resource.createTexture('splatOrder', PIXELFORMAT_R32U, resource.evalTextureSize(resource.numSplats));
        if (options.material) {
            // material is provided
            this._material = options.material;
            // patch splat order
            this._material.setParameter('splatOrder', this.orderTexture);
        } else {
            // construct the material
            this._material = new ShaderMaterial({
                uniqueName: 'SplatMaterial',
                vertexGLSL: '#include "gsplatVS"',
                fragmentGLSL: '#include "gsplatPS"',
                vertexWGSL: '#include "gsplatVS"',
                fragmentWGSL: '#include "gsplatPS"',
                attributes: {
                    vertex_position: SEMANTIC_POSITION,
                    vertex_id_attrib: SEMANTIC_ATTR13
                }
            });
            // default configure
            this.configureMaterial(this._material);
            // update
            this._material.update();
        }
        this.meshInstance = new MeshInstance(resource.mesh, this._material);
        this.meshInstance.setInstancing(resource.instanceIndices, true);
        this.meshInstance.gsplatInstance = this;
        // only start rendering the splat after we've received the splat order data
        this.meshInstance.instancingCount = 0;
        // clone centers to allow multiple instances of sorter
        const centers = resource.centers.slice();
        const chunks = resource.chunks?.slice();
        // create sorter
        this.sorter = new GSplatSorter();
        this.sorter.init(this.orderTexture, centers, chunks);
        this.sorter.on('updated', (count)=>{
            // limit splat render count to exclude those behind the camera
            this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize);
            // update splat count on the material
            this.material.setParameter('numSplats', count);
        });
        // configure sogs sh resolve
        this.setHighQualitySH(options.highQualitySH ?? false);
    }
    destroy() {
        this.resolveSH?.destroy();
        this.material?.destroy();
        this.meshInstance?.destroy();
        this.sorter?.destroy();
    }
    /**
     * @param {ShaderMaterial} value - The material instance.
     */ set material(value) {
        if (this._material !== value) {
            // set the new material
            this._material = value;
            // patch order texture
            this._material.setParameter('splatOrder', this.orderTexture);
            if (this.meshInstance) {
                this.meshInstance.material = value;
            }
        }
    }
    get material() {
        return this._material;
    }
    /**
     * Configure the material with gsplat instance and resource properties.
     *
     * @param {ShaderMaterial} material - The material to configure.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {boolean} [options.dither] - Specify true to configure the material for dithered rendering (stochastic alpha).
     */ configureMaterial(material, options = {}) {
        // allow resource to configure the material
        this.resource.configureMaterial(material);
        // set instance properties
        material.setParameter('numSplats', 0);
        material.setParameter('splatOrder', this.orderTexture);
        material.setParameter('alphaClip', 0.3);
        material.setDefine(`DITHER_${options.dither ? 'BLUENOISE' : 'NONE'}`, '');
        material.cull = CULLFACE_NONE;
        material.blendType = options.dither ? BLEND_NONE : BLEND_PREMULTIPLIED;
        material.depthWrite = !!options.dither;
    }
    updateViewport(cameraNode) {
        const camera = cameraNode?.camera;
        const renderTarget = camera?.renderTarget;
        const { width, height } = renderTarget ?? this.resource.device;
        viewport[0] = width;
        viewport[1] = height;
        // adjust viewport for stereoscopic VR sessions
        const xr = camera?.camera?.xr;
        if (xr?.active && xr.views.list.length === 2) {
            viewport[0] *= 0.5;
        }
        this.material.setParameter('viewport', viewport);
    }
    /**
     * Sorts the GS vertices based on the given camera.
     * @param {GraphNode} cameraNode - The camera node used for sorting.
     */ sort(cameraNode) {
        if (this.sorter) {
            const cameraMat = cameraNode.getWorldTransform();
            cameraMat.getTranslation(cameraPosition$2);
            cameraMat.getZ(cameraDirection$1);
            const modelMat = this.meshInstance.node.getWorldTransform();
            const invModelMat = mat$3.invert(modelMat);
            invModelMat.transformPoint(cameraPosition$2, cameraPosition$2);
            invModelMat.transformVector(cameraDirection$1, cameraDirection$1);
            // sort if the camera has changed
            if (!cameraPosition$2.equalsApprox(this.lastCameraPosition) || !cameraDirection$1.equalsApprox(this.lastCameraDirection)) {
                this.lastCameraPosition.copy(cameraPosition$2);
                this.lastCameraDirection.copy(cameraDirection$1);
                this.sorter.setCamera(cameraPosition$2, cameraDirection$1);
            }
        }
        this.updateViewport(cameraNode);
    }
    update() {
        if (this.cameras.length > 0) {
            // sort by the first camera it's visible for
            // TODO: extend to support multiple cameras
            const camera = this.cameras[0];
            this.sort(camera._node);
            // resolve spherical harmonics
            this.resolveSH?.render(camera._node, this.meshInstance.node.getWorldTransform());
            // we get new list of cameras each frame
            this.cameras.length = 0;
        }
    }
    setHighQualitySH(value) {
        const { resource } = this;
        const { gsplatData } = resource;
        if (gsplatData instanceof GSplatSogsData && gsplatData.shBands > 0 && value === !!this.resolveSH) {
            if (this.resolveSH) {
                this.resolveSH.destroy();
                this.resolveSH = null;
            } else {
                this.resolveSH = new GSplatResolveSH(resource.device, this);
            }
        }
    }
}

class GSplatSogsResource extends GSplatResourceBase {
    destroy() {
        this.gsplatData.destroy();
        super.destroy();
    }
    configureMaterialDefines(defines) {
        defines.set('GSPLAT_SOGS_DATA', true);
        defines.set('SH_BANDS', this.gsplatData.shBands);
    }
    configureMaterial(material) {
        const { gsplatData } = this;
        const { meta } = gsplatData;
        this.configureMaterialDefines(material.defines);
        [
            'packedTexture',
            'packedSh0',
            'packedShN'
        ].forEach((name)=>{
            if (gsplatData[name]) {
                material.setParameter(name, gsplatData[name]);
            }
        });
        [
            'means'
        ].forEach((name)=>{
            const v = meta[name];
            if (v) {
                material.setParameter(`${name}_mins`, v.mins);
                material.setParameter(`${name}_maxs`, v.maxs);
            }
        });
        if (meta.version === 2) {
            [
                'scales',
                'sh0',
                'shN'
            ].forEach((name)=>{
                const v = meta[name];
                if (v) {
                    material.setParameter(`${name}_mins`, v.codebook[0]);
                    material.setParameter(`${name}_maxs`, v.codebook[255]);
                }
            });
        } else {
            [
                'scales',
                'sh0'
            ].forEach((name)=>{
                const v = meta[name];
                if (v) {
                    material.setParameter(`${name}_mins`, Math.min(...v.mins.slice(0, 3)));
                    material.setParameter(`${name}_maxs`, Math.max(...v.maxs.slice(0, 3)));
                }
            });
            [
                'shN'
            ].forEach((name)=>{
                const v = meta[name];
                if (v) {
                    material.setParameter(`${name}_mins`, v.mins);
                    material.setParameter(`${name}_maxs`, v.maxs);
                }
            });
        }
    }
    evalTextureSize(count) {
        return new Vec2(this.gsplatData.means_l.width, this.gsplatData.means_l.height);
    }
}

/**
 * When resizing the window the size of the canvas will change to fill the window exactly.
 */ const FILLMODE_FILL_WINDOW = 'FILL_WINDOW';
/**
 * When resizing the window the size of the canvas will change to fill the window as best it can,
 * while maintaining the same aspect ratio.
 */ const FILLMODE_KEEP_ASPECT = 'KEEP_ASPECT';
/**
 * When the canvas is resized the resolution of the canvas will change to match the size of the
 * canvas.
 */ const RESOLUTION_AUTO = 'AUTO';
/**
 * When the canvas is resized the resolution of the canvas will remain at the same value and the
 * output will just be scaled to fit the canvas.
 */ const RESOLUTION_FIXED = 'FIXED';

let currentApplication;
function getApplication() {
    return currentApplication;
}
function setApplication(app) {
    currentApplication = app;
}

/**
 * @import { RenderPass } from '../platform/graphics/render-pass.js'
 * @import { RenderTarget } from '../platform/graphics/render-target.js'
 * @import { Texture } from '../platform/graphics/texture.js'
 */ /**
 * A frame graph represents a single rendering frame as a sequence of render passes.
 *
 * @ignore
 */ class FrameGraph {
    /**
     * Add a render pass to the frame.
     *
     * @param {RenderPass} renderPass - The render pass to add.
     */ addRenderPass(renderPass) {
        Debug.assert(renderPass);
        renderPass.frameUpdate();
        const beforePasses = renderPass.beforePasses;
        for(let i = 0; i < beforePasses.length; i++){
            const pass = beforePasses[i];
            if (pass.enabled) {
                this.addRenderPass(pass);
            }
        }
        if (renderPass.enabled) {
            this.renderPasses.push(renderPass);
        }
        const afterPasses = renderPass.afterPasses;
        for(let i = 0; i < afterPasses.length; i++){
            const pass = afterPasses[i];
            if (pass.enabled) {
                this.addRenderPass(pass);
            }
        }
    }
    reset() {
        this.renderPasses.length = 0;
    }
    compile() {
        const renderTargetMap = this.renderTargetMap;
        const renderPasses = this.renderPasses;
        for(let i = 0; i < renderPasses.length; i++){
            const renderPass = renderPasses[i];
            const renderTarget = renderPass.renderTarget;
            // if using a target, or null which represents the default back-buffer
            if (renderTarget !== undefined) {
                // previous pass using the same render target
                const prevPass = renderTargetMap.get(renderTarget);
                if (prevPass) {
                    // if we use the RT without clearing, make sure the previous pass stores data
                    const count = renderPass.colorArrayOps.length;
                    for(let j = 0; j < count; j++){
                        const colorOps = renderPass.colorArrayOps[j];
                        if (!colorOps.clear) {
                            prevPass.colorArrayOps[j].store = true;
                        }
                    }
                    if (!renderPass.depthStencilOps.clearDepth) {
                        prevPass.depthStencilOps.storeDepth = true;
                    }
                    if (!renderPass.depthStencilOps.clearStencil) {
                        prevPass.depthStencilOps.storeStencil = true;
                    }
                }
                // add the pass to the map
                renderTargetMap.set(renderTarget, renderPass);
            }
        }
        // merge passes if possible
        for(let i = 0; i < renderPasses.length - 1; i++){
            const firstPass = renderPasses[i];
            const firstRT = firstPass.renderTarget;
            const secondPass = renderPasses[i + 1];
            const secondRT = secondPass.renderTarget;
            // if the render targets are different, we can't merge the passes
            // also only merge passes that have a render target
            if (firstRT !== secondRT || firstRT === undefined) {
                continue;
            }
            // do not merge if the second pass clears any of the attachments
            if (secondPass.depthStencilOps.clearDepth || secondPass.depthStencilOps.clearStencil || secondPass.colorArrayOps.some((colorOps)=>colorOps.clear)) {
                continue;
            }
            // first pass cannot contain after passes
            if (firstPass.afterPasses.length > 0) {
                continue;
            }
            // second pass cannot contain before passes
            if (secondPass.beforePasses.length > 0) {
                continue;
            }
            // merge the passes
            firstPass._skipEnd = true;
            secondPass._skipStart = true;
        }
        // Walk over render passes to find passes rendering to the same cubemap texture.
        // If those passes are separated only by passes not requiring cubemap (shadows ..),
        // we skip the mipmap generation till the last rendering to the cubemap, to avoid
        // mipmaps being generated after each face.
        /** @type {Texture} */ let lastCubeTexture = null;
        /** @type {RenderPass} */ let lastCubeRenderPass = null;
        for(let i = 0; i < renderPasses.length; i++){
            const renderPass = renderPasses[i];
            const renderTarget = renderPass.renderTarget;
            const thisTexture = renderTarget?.colorBuffer;
            if (thisTexture?.cubemap) {
                // if previous pass used the same cubemap texture, it does not need mipmaps generated
                if (lastCubeTexture === thisTexture) {
                    const count = lastCubeRenderPass.colorArrayOps.length;
                    for(let j = 0; j < count; j++){
                        lastCubeRenderPass.colorArrayOps[j].mipmaps = false;
                    }
                }
                lastCubeTexture = renderTarget.colorBuffer;
                lastCubeRenderPass = renderPass;
            } else if (renderPass.requiresCubemaps) {
                // if the cubemap is required, break the cubemap rendering chain
                lastCubeTexture = null;
                lastCubeRenderPass = null;
            }
        }
        renderTargetMap.clear();
    }
    render(device) {
        this.compile();
        const renderPasses = this.renderPasses;
        for(let i = 0; i < renderPasses.length; i++){
            renderPasses[i].render();
        }
    }
    constructor(){
        /** @type {RenderPass[]} */ this.renderPasses = [];
        /**
     * Map used during frame graph compilation. It maps a render target to its previous occurrence.
     *
     *  @type {Map<RenderTarget, RenderPass>}
     */ this.renderTargetMap = new Map();
    }
}

// class used to hold LUT textures in the device cache
class AreaLightCacheEntry {
    constructor(texture0, texture1){
        this.texture0 = texture0;
        this.texture1 = texture1;
    }
    destroy() {
        this.texture0?.destroy();
        this.texture1?.destroy();
    }
}
// device cache storing LUT textures, taking care of their removal when the device is destroyed
const deviceCache = new DeviceCache();
// static class managing LUT tables for the area lights
class AreaLightLuts {
    static createTexture(device, format, size, postfix = '') {
        const tex = new Texture(device, {
            name: `AreaLightLUT${postfix}`,
            width: size,
            height: size,
            format: format,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            type: TEXTURETYPE_DEFAULT,
            magFilter: FILTER_LINEAR,
            minFilter: FILTER_NEAREST,
            anisotropy: 1,
            mipmaps: false
        });
        return tex;
    }
    static applyTextures(device, texture1, texture2) {
        // remove previous textures from cache
        deviceCache.remove(device);
        // add new textures to cache
        deviceCache.get(device, ()=>{
            return new AreaLightCacheEntry(texture1, texture1 === texture2 ? null : texture2);
        });
        // set them as uniforms
        device.scope.resolve('areaLightsLutTex1').setValue(texture1);
        device.scope.resolve('areaLightsLutTex2').setValue(texture2);
    }
    // placeholder LUT textures for area light
    static createPlaceholder(device) {
        const texture = AreaLightLuts.createTexture(device, PIXELFORMAT_RGBA16F, 2, 'placeholder');
        const pixels = texture.lock();
        pixels.fill(0);
        texture.unlock();
        AreaLightLuts.applyTextures(device, texture, texture);
    }
    // creates LUT texture used by area lights
    static set(device, ltcMat1, ltcMat2) {
        function buildTexture(device, data, format) {
            const texture = AreaLightLuts.createTexture(device, format, 64);
            texture.lock().set(data);
            texture.unlock();
            return texture;
        }
        function convertToHalfFloat(data) {
            const count = data.length;
            const ret = new Uint16Array(count);
            const float2Half = FloatPacking.float2Half;
            for(let i = 0; i < count; i++){
                ret[i] = float2Half(data[i]);
            }
            return ret;
        }
        const srcData1 = ltcMat1;
        const srcData2 = ltcMat2;
        // convert data to half format
        const data1 = convertToHalfFloat(srcData1);
        const data2 = convertToHalfFloat(srcData2);
        // create lut textures
        const tex1 = buildTexture(device, data1, PIXELFORMAT_RGBA16F);
        const tex2 = buildTexture(device, data2, PIXELFORMAT_RGBA16F);
        // assign to uniforms
        AreaLightLuts.applyTextures(device, tex1, tex2);
    }
}

const DEFAULT_LOCALE = 'en-US';
// default locale fallbacks if a specific locale
// was not found. E.g. if the desired locale is en-AS but we
// have en-US and en-GB then pick en-US. If a fallback does not exist either
// then pick the first that satisfies the language.
const DEFAULT_LOCALE_FALLBACKS = {
    'en': 'en-US',
    'es': 'en-ES',
    'zh': 'zh-CN',
    'zh-HK': 'zh-TW',
    'zh-TW': 'zh-HK',
    'zh-MO': 'zh-HK',
    'fr': 'fr-FR',
    'de': 'de-DE',
    'it': 'it-IT',
    'ru': 'ru-RU',
    'ja': 'ja-JP'
};

// Maps locale to function that returns the plural index
// based on the CLDR rules. See here for reference
// https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
// and http://unicode.org/reports/tr35/tr35-numbers.html#Operands .
// An initial set of locales is supported and we can keep adding more as we go.
const PLURALS = {};
// Helper function to define the plural function for an array of locales
function definePluralFn(locales, fn) {
    for(let i = 0, len = locales.length; i < len; i++){
        PLURALS[locales[i]] = fn;
    }
}
// Gets the language portion form a locale
function getLang(locale) {
    const idx = locale.indexOf('-');
    if (idx !== -1) {
        return locale.substring(0, idx);
    }
    return locale;
}
// Replaces the language in the specified locale and returns the result
function replaceLang(locale, desiredLang) {
    const idx = locale.indexOf('-');
    if (idx !== -1) {
        return desiredLang + locale.substring(idx);
    }
    return desiredLang;
}
function findAvailableLocale(desiredLocale, availableLocales) {
    if (availableLocales[desiredLocale]) {
        return desiredLocale;
    }
    let fallback = DEFAULT_LOCALE_FALLBACKS[desiredLocale];
    if (fallback && availableLocales[fallback]) {
        return fallback;
    }
    const lang = getLang(desiredLocale);
    fallback = DEFAULT_LOCALE_FALLBACKS[lang];
    if (availableLocales[fallback]) {
        return fallback;
    }
    if (availableLocales[lang]) {
        return lang;
    }
    return DEFAULT_LOCALE;
}
// Only OTHER
definePluralFn([
    'ja',
    'ko',
    'th',
    'vi',
    'zh',
    'id'
], (n)=>{
    return 0;
});
// ONE, OTHER
definePluralFn([
    'fa',
    'hi'
], (n)=>{
    if (n >= 0 && n <= 1) {
        return 0; // one
    }
    return 1; // other
});
// from Unicode rules: i = 0..1
definePluralFn([
    'fr',
    'pt'
], (n)=>{
    if (n >= 0 && n < 2) {
        return 0; // one
    }
    return 1; // other
});
// danish
definePluralFn([
    'da'
], (n)=>{
    if (n === 1 || !Number.isInteger(n) && n >= 0 && n <= 1) {
        return 0; // one
    }
    return 1; // other
});
definePluralFn([
    'de',
    'en',
    'it',
    'el',
    'es',
    'tr',
    'fi',
    'sv',
    'nb',
    'no',
    'ur'
], (n)=>{
    if (n === 1) {
        return 0; // one
    }
    return 1; // other
});
// ONE, FEW, MANY, OTHER
definePluralFn([
    'ru',
    'uk'
], (n)=>{
    if (Number.isInteger(n)) {
        const mod10 = n % 10;
        const mod100 = n % 100;
        if (mod10 === 1 && mod100 !== 11) {
            return 0; // one
        } else if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) {
            return 1; // few
        } else if (mod10 === 0 || mod10 >= 5 && mod10 <= 9 || mod100 >= 11 && mod100 <= 14) {
            return 2; // many
        }
    }
    return 3; // other
});
// polish
definePluralFn([
    'pl'
], (n)=>{
    if (Number.isInteger(n)) {
        if (n === 1) {
            return 0; // one
        }
        const mod10 = n % 10;
        const mod100 = n % 100;
        if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) {
            return 1; // few
        } else if (mod10 >= 0 && mod10 <= 1 || mod10 >= 5 && mod10 <= 9 || mod100 >= 12 && mod100 <= 14) {
            return 2; // many
        }
    }
    return 3; // other
});
// ZERO, ONE, TWO, FEW, MANY, OTHER
definePluralFn([
    'ar'
], (n)=>{
    if (n === 0) {
        return 0; // zero
    } else if (n === 1) {
        return 1; // one
    } else if (n === 2) {
        return 2; // two
    }
    if (Number.isInteger(n)) {
        const mod100 = n % 100;
        if (mod100 >= 3 && mod100 <= 10) {
            return 3; // few
        } else if (mod100 >= 11 && mod100 <= 99) {
            return 4; // many
        }
    }
    return 5; // other
});
const DEFAULT_PLURAL_FN = PLURALS[getLang(DEFAULT_LOCALE)];
// Gets the function that converts to plural for a language
function getPluralFn(lang) {
    return PLURALS[lang] || DEFAULT_PLURAL_FN;
}

const ABSOLUTE_URL = new RegExp('^' + // beginning of the url
'\\s*' + // ignore leading spaces (some browsers trim the url automatically, but we can't assume that)
'(?:' + // beginning of a non-captured regex group
// `{protocol}://`
'(?:' + // beginning of protocol scheme (non-captured regex group)
// eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/optimal-quantifier-concatenation
'[a-z]+[a-z0-9\\-+.]*' + // protocol scheme must (RFC 3986) consist of "a letter and followed by any combination of letters, digits, plus ("+"), period ("."), or hyphen ("-")."
':' + // protocol scheme must end with colon character
')?' + // end of optional scheme group, the group is optional since the string may be a protocol-relative absolute URL
'//' + // an absolute url must always begin with two forward slash characters (ignoring any leading spaces and protocol scheme)
'|' + // or another option(s):
// Data URL (RFC 2397), simplified
'data:' + // Blob data
'|blob:' + ')', 'i' // non case-sensitive flag
);

/**
 * Wraps a source of asset data.
 *
 * @ignore
 */ class AssetFile {
    constructor(url = '', filename = '', hash = null, size = null, opt = null, contents = null){
        this.url = url;
        this.filename = filename;
        this.hash = hash;
        this.size = size;
        this.opt = opt;
        this.contents = contents;
    }
    // Compare this AssetFile with another. Returns true if they have the same data
    // and false otherwise.
    equals(other) {
        return this.url === other.url && this.filename === other.filename && this.hash === other.hash && this.size === other.size && this.opt === other.opt && this.contents === other.contents;
    }
}

/**
 * @import { AssetRegistry } from './asset-registry.js'
 * @import { ResourceLoaderCallback } from '../handlers/loader.js'
 */ // auto incrementing number for asset ids
let assetIdCounter = -1;
const VARIANT_SUPPORT = {
    pvr: 'extCompressedTexturePVRTC',
    dxt: 'extCompressedTextureS3TC',
    etc2: 'extCompressedTextureETC',
    etc1: 'extCompressedTextureETC1',
    basis: 'canvas' // dummy, basis is always supported
};
const VARIANT_DEFAULT_PRIORITY = [
    'pvr',
    'dxt',
    'etc2',
    'etc1',
    'basis'
];
/**
 * @callback AssetReadyCallback
 * Callback used by {@link Asset#ready} and called when an asset is ready.
 * @param {Asset} asset - The ready asset.
 * @returns {void}
 */ /**
 * An asset record of a file or data resource that can be loaded by the engine. The asset contains
 * four important fields:
 *
 * - `file`: contains the details of a file (filename, url) which contains the resource data, e.g.
 * an image file for a texture asset.
 * - `data`: contains a JSON blob which contains either the resource data for the asset (e.g.
 * material data) or additional data for the file (e.g. material mappings for a model).
 * - `options`: contains a JSON blob with handler-specific load options.
 * - `resource`: contains the final resource when it is loaded. (e.g. a {@link StandardMaterial} or
 * a {@link Texture}).
 *
 * See the {@link AssetRegistry} for details on loading resources from assets.
 *
 * @category Asset
 */ class Asset extends EventHandler {
    static{
        /**
     * Fired when the asset has completed loading.
     *
     * @event
     * @example
     * asset.on('load', (asset) => {
     *     console.log(`Asset loaded: ${asset.name}`);
     * });
     */ this.EVENT_LOAD = 'load';
    }
    static{
        /**
     * Fired just before the asset unloads the resource. This allows for the opportunity to prepare
     * for an asset that will be unloaded. E.g. Changing the texture of a model to a default before
     * the one it was using is unloaded.
     *
     * @event
     * @example
     * asset.on('unload', (asset) => {
     *    console.log(`Asset about to unload: ${asset.name}`);
     * });
     */ this.EVENT_UNLOAD = 'unload';
    }
    static{
        /**
     * Fired when the asset is removed from the asset registry.
     *
     * @event
     * @example
     * asset.on('remove', (asset) => {
     *    console.log(`Asset removed: ${asset.name}`);
     * });
     */ this.EVENT_REMOVE = 'remove';
    }
    static{
        /**
     * Fired if the asset encounters an error while loading.
     *
     * @event
     * @example
     * asset.on('error', (err, asset) => {
     *    console.error(`Error loading asset ${asset.name}: ${err}`);
     * });
     */ this.EVENT_ERROR = 'error';
    }
    static{
        /**
     * Fired when one of the asset properties `file`, `data`, `resource` or `resources` is changed.
     *
     * @event
     * @example
     * asset.on('change', (asset, property, newValue, oldValue) => {
     *    console.log(`Asset ${asset.name} has property ${property} changed from ${oldValue} to ${newValue}`);
     * });
     */ this.EVENT_CHANGE = 'change';
    }
    static{
        /**
     * Fired when the asset's stream download progresses.
     *
     * Please note:
     * - only gsplat assets current emit this event
     * - totalBytes may not be reliable as it is based on the content-length header of the response
     *
     * @event
     * @example
     * asset.on('progress', (receivedBytes, totalBytes) => {
     *    console.log(`Asset ${asset.name} progress ${readBytes / totalBytes}`);
     * });
     */ this.EVENT_PROGRESS = 'progress';
    }
    static{
        /**
     * Fired when we add a new localized asset id to the asset.
     *
     * @event
     * @example
     * asset.on('add:localized', (locale, assetId) => {
     *    console.log(`Asset ${asset.name} has added localized asset ${assetId} for locale ${locale}`);
     * });
     */ this.EVENT_ADDLOCALIZED = 'add:localized';
    }
    static{
        /**
     * Fired when we remove a localized asset id from the asset.
     *
     * @event
     * @example
     * asset.on('remove:localized', (locale, assetId) => {
     *   console.log(`Asset ${asset.name} has removed localized asset ${assetId} for locale ${locale}`);
     * });
     */ this.EVENT_REMOVELOCALIZED = 'remove:localized';
    }
    /**
     * Create a new Asset record. Generally, Assets are created in the loading process and you
     * won't need to create them by hand.
     *
     * @param {string} name - A non-unique but human-readable name which can be later used to
     * retrieve the asset.
     * @param {"animation"|"audio"|"binary"|"container"|"cubemap"|"css"|"font"|"gsplat"|"json"|"html"|"material"|"model"|"render"|"script"|"shader"|"sprite"|"template"|"text"|"texture"|"textureatlas"} type - Type of asset.
     * @param {object} [file] - Details about the file the asset is made from. At the least must
     * contain the 'url' field. For assets that don't contain file data use null.
     * @param {string} [file.url] - The URL of the resource file that contains the asset data.
     * @param {string} [file.filename] - The filename of the resource file or null if no filename
     * was set (e.g from using {@link AssetRegistry#loadFromUrl}).
     * @param {number} [file.size] - The size of the resource file or null if no size was set
     * (e.g. from using {@link AssetRegistry#loadFromUrl}).
     * @param {string} [file.hash] - The MD5 hash of the resource file data and the Asset data
     * field or null if hash was set (e.g from using {@link AssetRegistry#loadFromUrl}).
     * @param {ArrayBuffer} [file.contents] - Optional file contents. This is faster than wrapping
     * the data in a (base64 encoded) blob. Currently only used by container assets.
     * @param {object|string} [data] - JSON object or string with additional data about the asset.
     * (e.g. for texture and model assets) or contains the asset data itself (e.g. in the case of
     * materials).
     * @param {object} [options] - The asset handler options. For container options see
     * {@link ContainerHandler}.
     * @param {'anonymous'|'use-credentials'|null} [options.crossOrigin] - For use with texture assets
     * that are loaded using the browser. This setting overrides the default crossOrigin specifier.
     * For more details on crossOrigin and its use, see
     * https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin.
     * @example
     * const asset = new pc.Asset("a texture", "texture", {
     *     url: "http://example.com/my/assets/here/texture.png"
     * });
     */ constructor(name, type, file, data = {}, options = {}){
        super(), /**
     * @type {AssetFile | null}
     * @private
     */ this._file = null, /**
     * A string-assetId dictionary that maps locale to asset id.
     *
     * @type {object}
     * @private
     */ this._i18n = {}, /**
     * Whether to preload the asset.
     *
     * @type {boolean}
     * @private
     */ this._preload = false, /**
     * This is where the loaded resource(s) are stored.
     *
     * @type {object[]}
     * @private
     */ this._resources = [], /**
     * The asset id.
     *
     * @type {number}
     */ this.id = assetIdCounter--, /**
     * True if the asset has finished attempting to load the resource. It is not guaranteed
     * that the resources are available as there could have been a network error.
     *
     * @type {boolean}
     */ this.loaded = false, /**
     * True if the resource is currently being loaded.
     *
     * @type {boolean}
     */ this.loading = false, /**
     * Optional JSON data that contains the asset handler options.
     *
     * @type {object}
     */ this.options = {}, /**
     * The asset registry that this Asset belongs to.
     *
     * @type {AssetRegistry|null}
     */ this.registry = null, /**
     * Asset tags. Enables finding of assets by tags using the {@link AssetRegistry#findByTag} method.
     *
     * @type {Tags}
     */ this.tags = new Tags(this), /**
     * The URL object.
     *
     * @type {string | null}
     * @ignore
     */ this.urlObject = null;
        this._name = name || '';
        this.type = type;
        this._data = data || {};
        this.options = options || {};
        if (file) this.file = file;
    }
    /**
     * Sets the asset name.
     *
     * @type {string}
     */ set name(value) {
        if (this._name === value) {
            return;
        }
        const old = this._name;
        this._name = value;
        this.fire('name', this, this._name, old);
    }
    /**
     * Gets the asset name.
     *
     * @type {string}
     */ get name() {
        return this._name;
    }
    /**
     * Sets the file details or null if no file.
     *
     * @type {object}
     */ set file(value) {
        // if value contains variants, choose the correct variant first
        if (value && value.variants && [
            'texture',
            'textureatlas',
            'bundle'
        ].indexOf(this.type) !== -1) {
            // search for active variant
            const app = this.registry?._loader?._app || getApplication();
            const device = app?.graphicsDevice;
            if (device) {
                for(let i = 0, len = VARIANT_DEFAULT_PRIORITY.length; i < len; i++){
                    const variant = VARIANT_DEFAULT_PRIORITY[i];
                    // if the device supports the variant
                    if (value.variants[variant] && device[VARIANT_SUPPORT[variant]]) {
                        value = value.variants[variant];
                        break;
                    }
                    // if the variant does not exist but the asset is in a bundle
                    // and the bundle contain assets with this variant then return the default
                    // file for the asset
                    if (app.enableBundles) {
                        const bundles = app.bundles.listBundlesForAsset(this);
                        if (bundles && bundles.find((b)=>{
                            return b?.file?.variants[variant];
                        })) {
                            break;
                        }
                    }
                }
            }
        }
        const oldFile = this._file;
        const newFile = value ? new AssetFile(value.url, value.filename, value.hash, value.size, value.opt, value.contents) : null;
        if (!!newFile !== !!oldFile || newFile && !newFile.equals(oldFile)) {
            this._file = newFile;
            this.fire('change', this, 'file', newFile, oldFile);
            this.reload();
        }
    }
    /**
     * Gets the file details or null if no file.
     *
     * @type {object}
     */ get file() {
        return this._file;
    }
    /**
     * Sets optional asset JSON data. This contains either the complete resource data (such as in
     * the case of a material) or additional data (such as in the case of a model which contains
     * mappings from mesh to material).
     *
     * @type {object}
     */ set data(value) {
        // fire change event when data changes
        // because the asset might need reloading if that happens
        const old = this._data;
        this._data = value;
        if (value !== old) {
            this.fire('change', this, 'data', value, old);
            if (this.loaded) {
                this.registry._loader.patch(this, this.registry);
            }
        }
    }
    /**
     * Gets optional asset JSON data.
     *
     * @type {object}
     */ get data() {
        return this._data;
    }
    /**
     * Sets the asset resource. For example, a {@link StandardMaterial} or a {@link Texture}.
     *
     * @type {object}
     */ set resource(value) {
        const _old = this._resources[0];
        this._resources[0] = value;
        this.fire('change', this, 'resource', value, _old);
    }
    /**
     * Gets the asset resource.
     *
     * @type {object}
     */ get resource() {
        return this._resources[0];
    }
    /**
     * Sets the asset resources. Some assets can hold more than one runtime resource (cube maps,
     * for example).
     *
     * @type {object[]}
     */ set resources(value) {
        const _old = this._resources;
        this._resources = value;
        this.fire('change', this, 'resources', value, _old);
    }
    /**
     * Gets the asset resources.
     *
     * @type {object[]}
     */ get resources() {
        return this._resources;
    }
    /**
     * Sets whether to preload an asset. If true, the asset will be loaded during the preload phase
     * of application set up.
     *
     * @type {boolean}
     */ set preload(value) {
        value = !!value;
        if (this._preload === value) {
            return;
        }
        this._preload = value;
        if (this._preload && !this.loaded && !this.loading && this.registry) {
            this.registry.load(this);
        }
    }
    /**
     * Gets whether to preload an asset.
     *
     * @type {boolean}
     */ get preload() {
        return this._preload;
    }
    set loadFaces(value) {
        value = !!value;
        if (!this.hasOwnProperty('_loadFaces') || value !== this._loadFaces) {
            this._loadFaces = value;
            // the loadFaces property should be part of the asset data block
            // because changing the flag should result in asset patch being invoked.
            // here we must invoke it manually instead.
            if (this.loaded) {
                this.registry._loader.patch(this, this.registry);
            }
        }
    }
    get loadFaces() {
        return this._loadFaces;
    }
    /**
     * Return the URL required to fetch the file for this asset.
     *
     * @returns {string|null} The URL. Returns null if the asset has no associated file.
     * @example
     * const assets = app.assets.find("My Image", "texture");
     * const img = "&lt;img src='" + assets[0].getFileUrl() + "'&gt;";
     */ getFileUrl() {
        const file = this.file;
        if (!file || !file.url) {
            return null;
        }
        let url = file.url;
        if (this.registry && this.registry.prefix && !ABSOLUTE_URL.test(url)) {
            url = this.registry.prefix + url;
        }
        // add file hash to avoid hard-caching problems
        if (this.type !== "script" && file.hash) {
            const separator = url.indexOf('?') !== -1 ? '&' : '?';
            url += `${separator}t=${file.hash}`;
        }
        return url;
    }
    /**
     * Construct an asset URL from this asset's location and a relative path. If the relativePath
     * is a blob or Base64 URI, then return that instead.
     *
     * @param {string} relativePath - The relative path to be concatenated to this asset's base url.
     * @returns {string} Resulting URL of the asset.
     * @ignore
     */ getAbsoluteUrl(relativePath) {
        if (relativePath.startsWith('blob:') || relativePath.startsWith('data:')) {
            return relativePath;
        }
        const base = path$1.getDirectory(this.file.url);
        return path$1.join(base, relativePath);
    }
    /**
     * Returns the asset id of the asset that corresponds to the specified locale.
     *
     * @param {string} locale - The desired locale e.g. Ar-AR.
     * @returns {number} An asset id or null if there is no asset specified for the desired locale.
     * @ignore
     */ getLocalizedAssetId(locale) {
        // tries to find either the desired locale or a fallback locale
        locale = findAvailableLocale(locale, this._i18n);
        return this._i18n[locale] || null;
    }
    /**
     * Adds a replacement asset id for the specified locale. When the locale in
     * {@link AppBase#i18n} changes then references to this asset will be replaced with the
     * specified asset id. (Currently only supported by the {@link ElementComponent}).
     *
     * @param {string} locale - The locale e.g. Ar-AR.
     * @param {number} assetId - The asset id.
     * @ignore
     */ addLocalizedAssetId(locale, assetId) {
        this._i18n[locale] = assetId;
        this.fire('add:localized', locale, assetId);
    }
    /**
     * Removes a localized asset.
     *
     * @param {string} locale - The locale e.g. Ar-AR.
     * @ignore
     */ removeLocalizedAssetId(locale) {
        const assetId = this._i18n[locale];
        if (assetId) {
            delete this._i18n[locale];
            this.fire('remove:localized', locale, assetId);
        }
    }
    /**
     * Take a callback which is called as soon as the asset is loaded. If the asset is already
     * loaded the callback is called straight away.
     *
     * @param {AssetReadyCallback} callback - The function called when the asset is ready. Passed
     * the (asset) arguments.
     * @param {object} [scope] - Scope object to use when calling the callback.
     * @example
     * const asset = app.assets.find("My Asset");
     * asset.ready((asset) => {
     *     // asset loaded
     * });
     * app.assets.load(asset);
     */ ready(callback, scope) {
        scope = scope || this;
        if (this.loaded) {
            callback.call(scope, this);
        } else {
            this.once('load', (asset)=>{
                callback.call(scope, asset);
            });
        }
    }
    reload() {
        // no need to be reloaded
        if (this.loaded) {
            this.loaded = false;
            this.registry.load(this);
        }
    }
    /**
     * Destroys the associated resource and marks asset as unloaded.
     *
     * @example
     * const asset = app.assets.find("My Asset");
     * asset.unload();
     * // asset.resource is null
     */ unload() {
        if (!this.loaded && this._resources.length === 0) {
            return;
        }
        this.fire('unload', this);
        this.registry.fire(`unload:${this.id}`, this);
        const old = this._resources;
        if (this.urlObject) {
            URL.revokeObjectURL(this.urlObject);
            this.urlObject = null;
        }
        // clear resources on the asset
        this.resources = [];
        this.loaded = false;
        // remove resource from loader cache
        if (this.file) {
            this.registry._loader.clearCache(this.getFileUrl(), this.type);
        }
        // destroy resources
        for(let i = 0; i < old.length; ++i){
            const resource = old[i];
            if (resource && resource.destroy) {
                resource.destroy();
            }
        }
    }
    /**
     * Helper function to resolve asset file data and return the contents as an ArrayBuffer. If the
     * asset file contents are present, that is returned. Otherwise the file data is be downloaded
     * via http.
     *
     * @param {string} loadUrl - The URL as passed into the handler
     * @param {ResourceLoaderCallback} callback - The callback function to receive results.
     * @param {Asset} [asset] - The asset
     * @param {number} maxRetries - Number of retries if http download is required
     * @ignore
     */ static fetchArrayBuffer(loadUrl, callback, asset, maxRetries = 0) {
        if (asset?.file?.contents) {
            // asset file contents were provided
            setTimeout(()=>{
                callback(null, asset.file.contents);
            });
        } else {
            // asset contents must be downloaded
            http.get(loadUrl, {
                cache: true,
                responseType: 'arraybuffer',
                retry: maxRetries > 0,
                maxRetries: maxRetries,
                progress: asset
            }, callback);
        }
    }
}

class TagsCache {
    constructor(key = null){
        this._index = {};
        this._key = key;
    }
    addItem(item) {
        const tags = item.tags._list;
        for (const tag of tags){
            this.add(tag, item);
        }
    }
    removeItem(item) {
        const tags = item.tags._list;
        for (const tag of tags){
            this.remove(tag, item);
        }
    }
    add(tag, item) {
        // already in cache
        if (this._index[tag] && this._index[tag].list.indexOf(item) !== -1) {
            return;
        }
        // create index for tag
        if (!this._index[tag]) {
            this._index[tag] = {
                list: []
            };
            // key indexing is available
            if (this._key) {
                this._index[tag].keys = {};
            }
        }
        // add to index list
        this._index[tag].list.push(item);
        // add to index keys
        if (this._key) {
            this._index[tag].keys[item[this._key]] = item;
        }
    }
    remove(tag, item) {
        // no index created for that tag
        if (!this._index[tag]) {
            return;
        }
        // check if item not in cache
        if (this._key) {
            // by key
            if (!this._index[tag].keys[item[this._key]]) {
                return;
            }
        }
        // by position in list
        const ind = this._index[tag].list.indexOf(item);
        if (ind === -1) {
            return;
        }
        // remove item from index list
        this._index[tag].list.splice(ind, 1);
        // remove item from index keys
        if (this._key) {
            delete this._index[tag].keys[item[this._key]];
        }
        // if index empty, remove it
        if (this._index[tag].list.length === 0) {
            delete this._index[tag];
        }
    }
    find(args) {
        const index = {};
        const items = [];
        let item, tag, tags, tagsRest, missingIndex;
        const sort = (a, b)=>{
            return this._index[a].list.length - this._index[b].list.length;
        };
        for(let i = 0; i < args.length; i++){
            tag = args[i];
            if (tag instanceof Array) {
                if (tag.length === 0) {
                    continue;
                }
                if (tag.length === 1) {
                    tag = tag[0];
                } else {
                    // check if all indexes are in present
                    missingIndex = false;
                    for(let t = 0; t < tag.length; t++){
                        if (!this._index[tag[t]]) {
                            missingIndex = true;
                            break;
                        }
                    }
                    if (missingIndex) {
                        continue;
                    }
                    // sort tags by least number of matches first
                    tags = tag.slice(0).sort(sort);
                    // remainder of tags for `has` checks
                    tagsRest = tags.slice(1);
                    if (tagsRest.length === 1) {
                        tagsRest = tagsRest[0];
                    }
                    for(let n = 0; n < this._index[tags[0]].list.length; n++){
                        item = this._index[tags[0]].list[n];
                        if ((this._key ? !index[item[this._key]] : items.indexOf(item) === -1) && item.tags.has(tagsRest)) {
                            if (this._key) {
                                index[item[this._key]] = true;
                            }
                            items.push(item);
                        }
                    }
                    continue;
                }
            }
            if (tag && typeof tag === 'string' && this._index[tag]) {
                for(let n = 0; n < this._index[tag].list.length; n++){
                    item = this._index[tag].list[n];
                    if (this._key) {
                        if (!index[item[this._key]]) {
                            index[item[this._key]] = true;
                            items.push(item);
                        }
                    } else if (items.indexOf(item) === -1) {
                        items.push(item);
                    }
                }
            }
        }
        return items;
    }
}

/**
 * @import { BundleRegistry } from '../bundle/bundle-registry.js'
 * @import { ResourceLoader } from '../handlers/loader.js'
 */ /**
 * @callback FilterAssetCallback
 * Callback used by {@link AssetRegistry#filter} to filter assets.
 * @param {Asset} asset - The current asset to filter.
 * @returns {boolean} Return `true` to include asset to result list.
 */ /**
 * @callback LoadAssetCallback
 * Callback used by {@link AssetRegistry#loadFromUrl} and called when an asset is loaded (or an
 * error occurs).
 * @param {string|null} err - The error message is null if no errors were encountered.
 * @param {Asset} [asset] - The loaded asset if no errors were encountered.
 * @returns {void}
 */ /**
 * @callback BundlesFilterCallback
 * Callback used by {@link ResourceLoader#load} and called when an asset is choosing a bundle
 * to load from. Return a single bundle to ensure asset is loaded from it.
 * @param {Asset[]} bundles - List of bundle assets which contain the asset.
 * @returns {Asset} Return a single bundle asset to ensure asset is loaded from it.
 */ /**
 * Container for all assets that are available to this application. Note that PlayCanvas scripts
 * are provided with an AssetRegistry instance as `app.assets`.
 *
 * @category Asset
 */ class AssetRegistry extends EventHandler {
    static{
        /**
     * Fired when an asset completes loading. This event is available in three forms. They are as
     * follows:
     *
     * 1. `load` - Fired when any asset finishes loading.
     * 2. `load:[id]` - Fired when a specific asset has finished loading, where `[id]` is the
     * unique id of the asset.
     * 3. `load:url:[url]` - Fired when an asset finishes loading whose URL matches `[url]`, where
     * `[url]` is the URL of the asset.
     *
     * @event
     * @example
     * app.assets.on('load', (asset) => {
     *     console.log(`Asset loaded: ${asset.name}`);
     * });
     * @example
     * const id = 123456;
     * const asset = app.assets.get(id);
     * app.assets.on('load:' + id, (asset) => {
     *     console.log(`Asset loaded: ${asset.name}`);
     * });
     * app.assets.load(asset);
     * @example
     * const id = 123456;
     * const asset = app.assets.get(id);
     * app.assets.on('load:url:' + asset.file.url, (asset) => {
     *     console.log(`Asset loaded: ${asset.name}`);
     * });
     * app.assets.load(asset);
     */ this.EVENT_LOAD = 'load';
    }
    static{
        /**
     * Fired when an asset is added to the registry. This event is available in three forms. They
     * are as follows:
     *
     * 1. `add` - Fired when any asset is added to the registry.
     * 2. `add:[id]` - Fired when an asset is added to the registry, where `[id]` is the unique id
     * of the asset.
     * 3. `add:url:[url]` - Fired when an asset is added to the registry and matches the URL
     * `[url]`, where `[url]` is the URL of the asset.
     *
     * @event
     * @example
     * app.assets.on('add', (asset) => {
     *    console.log(`Asset added: ${asset.name}`);
     * });
     * @example
     * const id = 123456;
     * app.assets.on('add:' + id, (asset) => {
     *    console.log(`Asset added: ${asset.name}`);
     * });
     * @example
     * const id = 123456;
     * const asset = app.assets.get(id);
     * app.assets.on('add:url:' + asset.file.url, (asset) => {
     *    console.log(`Asset added: ${asset.name}`);
     * });
     */ this.EVENT_ADD = 'add';
    }
    static{
        /**
     * Fired when an asset is removed from the registry. This event is available in three forms.
     * They are as follows:
     *
     * 1. `remove` - Fired when any asset is removed from the registry.
     * 2. `remove:[id]` - Fired when an asset is removed from the registry, where `[id]` is the
     * unique id of the asset.
     * 3. `remove:url:[url]` - Fired when an asset is removed from the registry and matches the
     * URL `[url]`, where `[url]` is the URL of the asset.
     *
     * @event
     * @param {Asset} asset - The asset that was removed.
     * @example
     * app.assets.on('remove', (asset) => {
     *    console.log(`Asset removed: ${asset.name}`);
     * });
     * @example
     * const id = 123456;
     * app.assets.on('remove:' + id, (asset) => {
     *    console.log(`Asset removed: ${asset.name}`);
     * });
     * @example
     * const id = 123456;
     * const asset = app.assets.get(id);
     * app.assets.on('remove:url:' + asset.file.url, (asset) => {
     *    console.log(`Asset removed: ${asset.name}`);
     * });
     */ this.EVENT_REMOVE = 'remove';
    }
    static{
        /**
     * Fired when an error occurs during asset loading. This event is available in two forms. They
     * are as follows:
     *
     * 1. `error` - Fired when any asset reports an error in loading.
     * 2. `error:[id]` - Fired when an asset reports an error in loading, where `[id]` is the
     * unique id of the asset.
     *
     * @event
     * @example
     * const id = 123456;
     * const asset = app.assets.get(id);
     * app.assets.on('error', (err, asset) => {
     *     console.error(err);
     * });
     * app.assets.load(asset);
     * @example
     * const id = 123456;
     * const asset = app.assets.get(id);
     * app.assets.on('error:' + id, (err, asset) => {
     *     console.error(err);
     * });
     * app.assets.load(asset);
     */ this.EVENT_ERROR = 'error';
    }
    /**
     * Create an instance of an AssetRegistry.
     *
     * @param {ResourceLoader} loader - The ResourceLoader used to load the asset files.
     */ constructor(loader){
        super(), /**
     * @type {Set<Asset>}
     * @private
     */ this._assets = new Set(), /**
     * @type {Map<number, Asset>}
     * @private
     */ this._idToAsset = new Map(), /**
     * @type {Map<string, Asset>}
     * @private
     */ this._urlToAsset = new Map(), /**
     * @type {Map<string, Set<Asset>>}
     * @private
     */ this._nameToAsset = new Map(), /**
     * Index for looking up by tags.
     *
     * @private
     */ this._tags = new TagsCache('id'), /**
     * A URL prefix that will be added to all asset loading requests.
     *
     * @type {string|null}
     */ this.prefix = null, /**
     * BundleRegistry
     *
     * @type {BundleRegistry|null}
     */ this.bundles = null;
        this._loader = loader;
    }
    /**
     * Create a filtered list of assets from the registry.
     *
     * @param {object} [filters] - Filter options.
     * @param {boolean} [filters.preload] - Filter by preload setting.
     * @returns {Asset[]} The filtered list of assets.
     */ list(filters = {}) {
        const assets = Array.from(this._assets);
        if (filters.preload !== undefined) {
            return assets.filter((asset)=>asset.preload === filters.preload);
        }
        return assets;
    }
    /**
     * Add an asset to the registry.
     *
     * @param {Asset} asset - The asset to add.
     * @example
     * const asset = new pc.Asset("My Asset", "texture", {
     *     url: "../path/to/image.jpg"
     * });
     * app.assets.add(asset);
     */ add(asset) {
        if (this._assets.has(asset)) return;
        this._assets.add(asset);
        this._idToAsset.set(asset.id, asset);
        if (asset.file?.url) {
            this._urlToAsset.set(asset.file.url, asset);
        }
        if (!this._nameToAsset.has(asset.name)) {
            this._nameToAsset.set(asset.name, new Set());
        }
        this._nameToAsset.get(asset.name).add(asset);
        asset.on('name', this._onNameChange, this);
        asset.registry = this;
        // tags cache
        this._tags.addItem(asset);
        asset.tags.on('add', this._onTagAdd, this);
        asset.tags.on('remove', this._onTagRemove, this);
        this.fire('add', asset);
        this.fire(`add:${asset.id}`, asset);
        if (asset.file?.url) {
            this.fire(`add:url:${asset.file.url}`, asset);
        }
        if (asset.preload) {
            this.load(asset);
        }
    }
    /**
     * Remove an asset from the registry.
     *
     * @param {Asset} asset - The asset to remove.
     * @returns {boolean} True if the asset was successfully removed and false otherwise.
     * @example
     * const asset = app.assets.get(100);
     * app.assets.remove(asset);
     */ remove(asset) {
        if (!this._assets.has(asset)) return false;
        this._assets.delete(asset);
        this._idToAsset.delete(asset.id);
        if (asset.file?.url) {
            this._urlToAsset.delete(asset.file.url);
        }
        asset.off('name', this._onNameChange, this);
        if (this._nameToAsset.has(asset.name)) {
            const items = this._nameToAsset.get(asset.name);
            items.delete(asset);
            if (items.size === 0) {
                this._nameToAsset.delete(asset.name);
            }
        }
        // tags cache
        this._tags.removeItem(asset);
        asset.tags.off('add', this._onTagAdd, this);
        asset.tags.off('remove', this._onTagRemove, this);
        asset.fire('remove', asset);
        this.fire('remove', asset);
        this.fire(`remove:${asset.id}`, asset);
        if (asset.file?.url) {
            this.fire(`remove:url:${asset.file.url}`, asset);
        }
        return true;
    }
    /**
     * Retrieve an asset from the registry by its id field.
     *
     * @param {number} id - The id of the asset to get.
     * @returns {Asset|undefined} The asset.
     * @example
     * const asset = app.assets.get(100);
     */ get(id) {
        // Since some apps incorrectly pass the id as a string, force a conversion to a number
        return this._idToAsset.get(Number(id));
    }
    /**
     * Retrieve an asset from the registry by its file's URL field.
     *
     * @param {string} url - The url of the asset to get.
     * @returns {Asset|undefined} The asset.
     * @example
     * const asset = app.assets.getByUrl("../path/to/image.jpg");
     */ getByUrl(url) {
        return this._urlToAsset.get(url);
    }
    /**
     * Load the asset's file from a remote source. Listen for `load` events on the asset to find
     * out when it is loaded.
     *
     * @param {Asset} asset - The asset to load.
     * @param {object} [options] - Options for asset loading.
     * @param {boolean} [options.bundlesIgnore] - If set to true, then asset will not try to load
     * from a bundle. Defaults to false.
     * @param {boolean} [options.force] - If set to true, then the check of asset being loaded or
     * is already loaded is bypassed, which forces loading of asset regardless.
     * @param {BundlesFilterCallback} [options.bundlesFilter] - A callback that will be called
     * when loading an asset that is contained in any of the bundles. It provides an array of
     * bundles and will ensure asset is loaded from bundle returned from a callback. By default,
     * the smallest filesize bundle is chosen.
     * @example
     * // load some assets
     * const assetsToLoad = [
     *     app.assets.find("My Asset"),
     *     app.assets.find("Another Asset")
     * ];
     * let count = 0;
     * assetsToLoad.forEach((assetToLoad) => {
     *     assetToLoad.ready((asset) => {
     *         count++;
     *         if (count === assetsToLoad.length) {
     *             // done
     *         }
     *     });
     *     app.assets.load(assetToLoad);
     * });
     */ load(asset, options) {
        // do nothing if asset is already loaded
        // note: lots of code calls assets.load() assuming this check is present
        // don't remove it without updating calls to assets.load() with checks for the asset.loaded state
        if ((asset.loading || asset.loaded) && !options?.force) {
            return;
        }
        const file = asset.file;
        const _fireLoad = ()=>{
            this.fire('load', asset);
            this.fire(`load:${asset.id}`, asset);
            if (file && file.url) {
                this.fire(`load:url:${file.url}`, asset);
            }
            asset.fire('load', asset);
        };
        // open has completed on the resource
        const _opened = (resource)=>{
            if (resource instanceof Array) {
                asset.resources = resource;
            } else {
                asset.resource = resource;
            }
            // let handler patch the resource
            this._loader.patch(asset, this);
            if (asset.type === 'bundle') {
                const assetIds = asset.data.assets;
                for(let i = 0; i < assetIds.length; i++){
                    const assetInBundle = this._idToAsset.get(assetIds[i]);
                    if (assetInBundle && !assetInBundle.loaded) {
                        this.load(assetInBundle, {
                            force: true
                        });
                    }
                }
                if (asset.resource.loaded) {
                    _fireLoad();
                } else {
                    this.fire('load:start', asset);
                    this.fire(`load:start:${asset.id}`, asset);
                    if (file && file.url) {
                        this.fire(`load:start:url:${file.url}`, asset);
                    }
                    asset.fire('load:start', asset);
                    asset.resource.on('load', _fireLoad);
                }
            } else {
                _fireLoad();
            }
        };
        // load has completed on the resource
        const _loaded = (err, resource, extra)=>{
            asset.loaded = true;
            asset.loading = false;
            if (err) {
                this.fire('error', err, asset);
                this.fire(`error:${asset.id}`, err, asset);
                asset.fire('error', err, asset);
            } else {
                if (asset.type === "script") {
                    const handler = this._loader.getHandler("script");
                    if (handler._cache[asset.id] && handler._cache[asset.id].parentNode === document.head) {
                        // remove old element
                        document.head.removeChild(handler._cache[asset.id]);
                    }
                    handler._cache[asset.id] = extra;
                }
                _opened(resource);
            }
        };
        if (file || asset.type === 'cubemap') {
            // start loading the resource
            this.fire('load:start', asset);
            this.fire(`load:${asset.id}:start`, asset);
            asset.loading = true;
            const fileUrl = asset.getFileUrl();
            // mark bundle assets as loading
            if (asset.type === 'bundle') {
                const assetIds = asset.data.assets;
                for(let i = 0; i < assetIds.length; i++){
                    const assetInBundle = this._idToAsset.get(assetIds[i]);
                    if (!assetInBundle) {
                        continue;
                    }
                    if (assetInBundle.loaded || assetInBundle.resource || assetInBundle.loading) {
                        continue;
                    }
                    assetInBundle.loading = true;
                }
            }
            this._loader.load(fileUrl, asset.type, _loaded, asset, options);
        } else {
            // asset has no file to load, open it directly
            const resource = this._loader.open(asset.type, asset.data);
            asset.loaded = true;
            _opened(resource);
        }
    }
    /**
     * Use this to load and create an asset if you don't have assets created. Usually you would
     * only use this if you are not integrated with the PlayCanvas Editor.
     *
     * @param {string} url - The url to load.
     * @param {string} type - The type of asset to load.
     * @param {LoadAssetCallback} callback - Function called when asset is loaded, passed (err,
     * asset), where err is null if no errors were encountered.
     * @example
     * app.assets.loadFromUrl("../path/to/texture.jpg", "texture", function (err, asset) {
     *     const texture = asset.resource;
     * });
     */ loadFromUrl(url, type, callback) {
        this.loadFromUrlAndFilename(url, null, type, callback);
    }
    /**
     * Use this to load and create an asset when both the URL and filename are required. For
     * example, use this function when loading BLOB assets, where the URL does not adequately
     * identify the file.
     *
     * @param {string} url - The url to load.
     * @param {string} filename - The filename of the asset to load.
     * @param {string} type - The type of asset to load.
     * @param {LoadAssetCallback} callback - Function called when asset is loaded, passed (err,
     * asset), where err is null if no errors were encountered.
     * @example
     * const file = magicallyObtainAFile();
     * app.assets.loadFromUrlAndFilename(URL.createObjectURL(file), "texture.png", "texture", function (err, asset) {
     *     const texture = asset.resource;
     * });
     */ loadFromUrlAndFilename(url, filename, type, callback) {
        const name = path$1.getBasename(filename || url);
        const file = {
            filename: filename || name,
            url: url
        };
        let asset = this.getByUrl(url);
        if (!asset) {
            asset = new Asset(name, type, file);
            this.add(asset);
        } else if (asset.loaded) {
            // asset is already loaded
            callback(asset.loadFromUrlError || null, asset);
            return;
        }
        const startLoad = (asset)=>{
            asset.once('load', (loadedAsset)=>{
                if (type === 'material') {
                    this._loadTextures(loadedAsset, (err, textures)=>{
                        callback(err, loadedAsset);
                    });
                } else {
                    callback(null, loadedAsset);
                }
            });
            asset.once('error', (err)=>{
                // store the error on the asset in case user requests this asset again
                if (err) {
                    this.loadFromUrlError = err;
                }
                callback(err, asset);
            });
            this.load(asset);
        };
        if (asset.resource) {
            callback(null, asset);
        } else if (type === 'model') {
            this._loadModel(asset, startLoad);
        } else {
            startLoad(asset);
        }
    }
    // private method used for engine-only loading of model data
    _loadModel(modelAsset, continuation) {
        const url = modelAsset.getFileUrl();
        const ext = path$1.getExtension(url);
        if (ext === '.json' || ext === '.glb') {
            const dir = path$1.getDirectory(url);
            const basename = path$1.getBasename(url);
            // PlayCanvas model format supports material mapping file
            const mappingUrl = path$1.join(dir, basename.replace(ext, '.mapping.json'));
            this._loader.load(mappingUrl, 'json', (err, data)=>{
                if (err) {
                    modelAsset.data = {
                        mapping: []
                    };
                    continuation(modelAsset);
                } else {
                    this._loadMaterials(modelAsset, data, (e, materials)=>{
                        modelAsset.data = data;
                        continuation(modelAsset);
                    });
                }
            });
        } else {
            // other model format (e.g. obj)
            continuation(modelAsset);
        }
    }
    // private method used for engine-only loading of model materials
    _loadMaterials(modelAsset, mapping, callback) {
        const materials = [];
        let count = 0;
        const onMaterialLoaded = (err, materialAsset)=>{
            // load dependent textures
            this._loadTextures(materialAsset, (err, textures)=>{
                materials.push(materialAsset);
                if (materials.length === count) {
                    callback(null, materials);
                }
            });
        };
        for(let i = 0; i < mapping.mapping.length; i++){
            const path = mapping.mapping[i].path;
            if (path) {
                count++;
                const url = modelAsset.getAbsoluteUrl(path);
                this.loadFromUrl(url, 'material', onMaterialLoaded);
            }
        }
        if (count === 0) {
            callback(null, materials);
        }
    }
    // private method used for engine-only loading of the textures referenced by
    // the material asset
    _loadTextures(materialAsset, callback) {
        const textures = [];
        let count = 0;
        const data = materialAsset.data;
        if (data.mappingFormat !== 'path') {
            Debug.warn(`Skipping: ${materialAsset.name}, material files must be mappingFormat: "path" to be loaded from URL`);
            callback(null, textures);
            return;
        }
        const onTextureLoaded = (err, texture)=>{
            if (err) console.error(err);
            textures.push(texture);
            if (textures.length === count) {
                callback(null, textures);
            }
        };
        const texParams = standardMaterialTextureParameters;
        for(let i = 0; i < texParams.length; i++){
            const path = data[texParams[i]];
            if (path && typeof path === 'string') {
                count++;
                const url = materialAsset.getAbsoluteUrl(path);
                this.loadFromUrl(url, 'texture', onTextureLoaded);
            }
        }
        if (count === 0) {
            callback(null, textures);
        }
    }
    _onTagAdd(tag, asset) {
        this._tags.add(tag, asset);
    }
    _onTagRemove(tag, asset) {
        this._tags.remove(tag, asset);
    }
    _onNameChange(asset, name, nameOld) {
        // remove
        if (this._nameToAsset.has(nameOld)) {
            const items = this._nameToAsset.get(nameOld);
            items.delete(asset);
            if (items.size === 0) {
                this._nameToAsset.delete(nameOld);
            }
        }
        // add
        if (!this._nameToAsset.has(asset.name)) {
            this._nameToAsset.set(asset.name, new Set());
        }
        this._nameToAsset.get(asset.name).add(asset);
    }
    /**
     * Return all Assets that satisfy the search query. Query can be simply a string, or comma
     * separated strings, to have inclusive results of assets that match at least one query. A
     * query that consists of an array of tags can be used to match assets that have each tag of
     * array.
     *
     * @param {...*} query - Name of a tag or array of tags.
     * @returns {Asset[]} A list of all Assets matched query.
     * @example
     * const assets = app.assets.findByTag("level-1");
     * // returns all assets that tagged by `level-1`
     * @example
     * const assets = app.assets.findByTag("level-1", "level-2");
     * // returns all assets that tagged by `level-1` OR `level-2`
     * @example
     * const assets = app.assets.findByTag(["level-1", "monster"]);
     * // returns all assets that tagged by `level-1` AND `monster`
     * @example
     * const assets = app.assets.findByTag(["level-1", "monster"], ["level-2", "monster"]);
     * // returns all assets that tagged by (`level-1` AND `monster`) OR (`level-2` AND `monster`)
     */ findByTag(...query) {
        return this._tags.find(query);
    }
    /**
     * Return all Assets that satisfy a filter callback.
     *
     * @param {FilterAssetCallback} callback - The callback function that is used to filter assets.
     * Return `true` to include an asset in the returned array.
     * @returns {Asset[]} A list of all Assets found.
     * @example
     * const assets = app.assets.filter(asset => asset.name.includes('monster'));
     * console.log(`Found ${assets.length} assets with a name containing 'monster'`);
     */ filter(callback) {
        return Array.from(this._assets).filter((asset)=>callback(asset));
    }
    /**
     * Return the first Asset with the specified name and type found in the registry.
     *
     * @param {string} name - The name of the Asset to find.
     * @param {string} [type] - The type of the Asset to find.
     * @returns {Asset|null} A single Asset or null if no Asset is found.
     * @example
     * const asset = app.assets.find("myTextureAsset", "texture");
     */ find(name, type) {
        const items = this._nameToAsset.get(name);
        if (!items) return null;
        for (const asset of items){
            if (!type || asset.type === type) {
                return asset;
            }
        }
        return null;
    }
    /**
     * Return all Assets with the specified name and type found in the registry.
     *
     * @param {string} name - The name of the Assets to find.
     * @param {string} [type] - The type of the Assets to find.
     * @returns {Asset[]} A list of all Assets found.
     * @example
     * const assets = app.assets.findAll('brick', 'texture');
     * console.log(`Found ${assets.length} texture assets named 'brick'`);
     */ findAll(name, type) {
        const items = this._nameToAsset.get(name);
        if (!items) return [];
        const results = Array.from(items);
        if (!type) return results;
        return results.filter((asset)=>asset.type === type);
    }
}

/**
 * @import { Asset } from '../asset/asset.js'
 * @import { AssetRegistry } from '../asset/asset-registry.js'
 */ /**
 * Keeps track of which assets are in bundles and loads files from bundles.
 *
 * @ignore
 */ class BundleRegistry {
    /**
     * Create a new BundleRegistry instance.
     *
     * @param {AssetRegistry} assets - The asset registry.
     */ constructor(assets){
        /**
     * Index of bundle assets.
     * @type {Map<number, Asset>}
     * @private
     */ this._idToBundle = new Map();
        /**
     * Index of asset id to set of bundle assets.
     * @type {Map<number, Set<Asset>>}
     * @private
     */ this._assetToBundles = new Map();
        /**
     * Index of file url to set of bundle assets.
     * @type {Map<string, Set<Asset>>}
     * @private
     */ this._urlsToBundles = new Map();
        /**
     * Index of file request to load callbacks.
     * @type {Map<string, function[]>}
     * @private
     */ this._fileRequests = new Map();
        this._assets = assets;
        this._assets.bundles = this;
        this._assets.on('add', this._onAssetAdd, this);
        this._assets.on('remove', this._onAssetRemove, this);
    }
    /**
     * Called when asset is added to AssetRegistry.
     *
     * @param {Asset} asset - The asset that has been added.
     * @private
     */ _onAssetAdd(asset) {
        // if this is a bundle asset then add it and
        // index its referenced assets
        if (asset.type === 'bundle') {
            this._idToBundle.set(asset.id, asset);
            this._assets.on(`load:start:${asset.id}`, this._onBundleLoadStart, this);
            this._assets.on(`load:${asset.id}`, this._onBundleLoad, this);
            this._assets.on(`error:${asset.id}`, this._onBundleError, this);
            const assetIds = asset.data.assets;
            for(let i = 0; i < assetIds.length; i++){
                this._indexAssetInBundle(assetIds[i], asset);
            }
        } else {
            // if this is not a bundle then index its URLs
            if (this._assetToBundles.has(asset.id)) {
                this._indexAssetFileUrls(asset);
            }
        }
    }
    _unbindAssetEvents(id) {
        this._assets.off(`load:start:${id}`, this._onBundleLoadStart, this);
        this._assets.off(`load:${id}`, this._onBundleLoad, this);
        this._assets.off(`error:${id}`, this._onBundleError, this);
    }
    // Index the specified asset id and its file URLs so that
    // the registry knows that the asset is in that bundle
    _indexAssetInBundle(id, bundle) {
        let bundles = this._assetToBundles.get(id);
        if (!bundles) {
            bundles = new Set();
            this._assetToBundles.set(id, bundles);
        }
        bundles.add(bundle);
        const asset = this._assets.get(id);
        if (asset) this._indexAssetFileUrls(asset);
    }
    // Index the file URLs of the specified asset
    _indexAssetFileUrls(asset) {
        const urls = this._getAssetFileUrls(asset);
        if (!urls) return;
        for(let i = 0; i < urls.length; i++){
            const bundles = this._assetToBundles.get(asset.id);
            if (!bundles) continue;
            this._urlsToBundles.set(urls[i], bundles);
        }
    }
    // Get all the possible URLs of an asset
    _getAssetFileUrls(asset) {
        let url = asset.getFileUrl();
        if (!url) return null;
        url = url.split('?')[0];
        const urls = [
            url
        ];
        // a font might have additional files
        // so add them in the list
        if (asset.type === 'font') {
            const numFiles = asset.data.info.maps.length;
            for(let i = 1; i < numFiles; i++){
                urls.push(url.replace('.png', `${i}.png`));
            }
        }
        return urls;
    }
    // Remove asset from internal indexes
    _onAssetRemove(asset) {
        if (asset.type === 'bundle') {
            // remove bundle from index
            this._idToBundle.delete(asset.id);
            // remove event listeners
            this._unbindAssetEvents(asset.id);
            // remove bundle from _assetToBundles and _urlInBundles indexes
            const assetIds = asset.data.assets;
            for(let i = 0; i < assetIds.length; i++){
                const bundles = this._assetToBundles.get(assetIds[i]);
                if (!bundles) continue;
                bundles.delete(asset);
                if (bundles.size === 0) {
                    this._assetToBundles.delete(assetIds[i]);
                    for (const [url, otherBundles] of this._urlsToBundles){
                        if (otherBundles !== bundles) {
                            continue;
                        }
                        this._urlsToBundles.delete(url);
                    }
                }
            }
            // fail any pending requests for this bundle
            this._onBundleError(`Bundle ${asset.id} was removed`);
        } else {
            const bundles = this._assetToBundles.get(asset.id);
            if (!bundles) return;
            this._assetToBundles.delete(asset.id);
            // remove asset urls from _urlsToBundles
            const urls = this._getAssetFileUrls(asset);
            if (!urls) return;
            for(let i = 0; i < urls.length; i++){
                this._urlsToBundles.delete(urls[i]);
            }
        }
    }
    _onBundleLoadStart(asset) {
        asset.resource.on('add', (url, data)=>{
            const callbacks = this._fileRequests.get(url);
            if (!callbacks) return;
            for(let i = 0; i < callbacks.length; i++){
                callbacks[i](null, data);
            }
            this._fileRequests.delete(url);
        });
    }
    // If we have any pending file requests
    // that can be satisfied by the specified bundle
    // then resolve them
    _onBundleLoad(asset) {
        // this can happen if the asset failed
        // to create its resource
        if (!asset.resource) {
            this._onBundleError(`Bundle ${asset.id} failed to load`);
            return;
        }
        // make sure the registry hasn't been destroyed already
        if (!this._fileRequests) {
            return;
        }
        for (const [url, requests] of this._fileRequests){
            const bundles = this._urlsToBundles.get(url);
            if (!bundles || !bundles.has(asset)) continue;
            const decodedUrl = decodeURIComponent(url);
            let err, data;
            if (asset.resource.has(decodedUrl)) {
                data = asset.resource.get(decodedUrl);
            } else if (asset.resource.loaded) {
                err = `Bundle ${asset.id} does not contain URL ${url}`;
            } else {
                continue;
            }
            for(let i = 0; i < requests.length; i++){
                requests[i](err, err || data);
            }
            this._fileRequests.delete(url);
        }
    }
    // If we have outstanding file requests for any
    // of the URLs in the specified bundle then search for
    // other bundles that can satisfy these requests.
    // If we do not find any other bundles then fail
    // those pending file requests with the specified error.
    _onBundleError(err) {
        for (const [url, requests] of this._fileRequests){
            const bundle = this._findLoadedOrLoadingBundleForUrl(url);
            if (!bundle) {
                for(let i = 0; i < requests.length; i++){
                    requests[i](err);
                }
                this._fileRequests.delete(url);
            }
        }
    }
    // Finds a bundle that contains the specified URL but
    // only returns the bundle if it's either loaded or being loaded
    _findLoadedOrLoadingBundleForUrl(url) {
        const bundles = this._urlsToBundles.get(url);
        if (!bundles) return null;
        let candidate = null;
        for (const bundle of bundles){
            if (bundle.loaded && bundle.resource) {
                return bundle;
            } else if (bundle.loading) {
                candidate = bundle;
            }
        }
        return candidate;
    }
    /**
     * Lists all of the available bundles that reference the specified asset.
     *
     * @param {Asset} asset - The asset to search by.
     * @returns {Asset[]|null} An array of bundle assets or null if the
     * asset is not in any bundle.
     */ listBundlesForAsset(asset) {
        const bundles = this._assetToBundles.get(asset.id);
        if (bundles) return Array.from(bundles);
        return null;
    }
    /**
     * Lists all bundle assets.
     *
     * @returns {Asset[]} An array of bundle assets.
     */ list() {
        return Array.from(this._idToBundle.values());
    }
    /**
     * Returns true if there is a bundle that contains the specified URL.
     *
     * @param {string} url - The url.
     * @returns {boolean} True or false.
     */ hasUrl(url) {
        return this._urlsToBundles.has(url);
    }
    /**
     * Returns true if there is a bundle that contains the specified URL and that bundle is either
     * loaded or currently being loaded.
     *
     * @param {string} url - The url.
     * @returns {boolean} True or false.
     */ urlIsLoadedOrLoading(url) {
        return !!this._findLoadedOrLoadingBundleForUrl(url);
    }
    /**
     * Loads the specified file URL from a bundle that is either loaded or currently being loaded.
     *
     * @param {string} url - The URL. Make sure you are using a relative URL that does not contain
     * any query parameters.
     * @param {Function} callback - The callback is called when the file has been loaded or if an
     * error occurs. The callback expects the first argument to be the error message (if any) and
     * the second argument is the file blob URL.
     * @example
     * const url = asset.getFileUrl().split('?')[0]; // get normalized asset URL
     * this.app.bundles.loadFile(url, function (err, data) {
     *     // do something with the data
     * });
     */ loadUrl(url, callback) {
        const bundle = this._findLoadedOrLoadingBundleForUrl(url);
        if (!bundle) {
            callback(`URL ${url} not found in any bundles`);
            return;
        }
        // Only load files from bundles that're explicitly requested to be loaded.
        if (bundle.loaded) {
            const decodedUrl = decodeURIComponent(url);
            if (bundle.resource.has(decodedUrl)) {
                callback(null, bundle.resource.get(decodedUrl));
                return;
            } else if (bundle.resource.loaded) {
                callback(`Bundle ${bundle.id} does not contain URL ${url}`);
                return;
            }
        }
        let callbacks = this._fileRequests.get(url);
        if (!callbacks) {
            callbacks = [];
            this._fileRequests.set(url, callbacks);
        }
        callbacks.push(callback);
    }
    /**
     * Destroys the registry, and releases its resources. Does not unload bundle assets as these
     * should be unloaded by the {@link AssetRegistry}.
     */ destroy() {
        this._assets.off('add', this._onAssetAdd, this);
        this._assets.off('remove', this._onAssetRemove, this);
        for (const id of this._idToBundle.keys()){
            this._unbindAssetEvents(id);
        }
        this._assets = null;
        this._idToBundle.clear();
        this._idToBundle = null;
        this._assetToBundles.clear();
        this._assetToBundles = null;
        this._urlsToBundles.clear();
        this._urlsToBundles = null;
        this._fileRequests.clear();
        this._fileRequests = null;
    }
}

/**
 * @import { AnimComponentSystem } from './anim/system.js'
 * @import { AnimationComponentSystem } from './animation/system.js'
 * @import { AudioListenerComponentSystem } from './audio-listener/system.js'
 * @import { ButtonComponentSystem } from './button/system.js'
 * @import { CameraComponentSystem } from './camera/system.js'
 * @import { CollisionComponentSystem } from './collision/system.js'
 * @import { ElementComponentSystem } from './element/system.js'
 * @import { JointComponentSystem } from './joint/system.js'
 * @import { LayoutChildComponentSystem } from './layout-child/system.js'
 * @import { LayoutGroupComponentSystem } from './layout-group/system.js'
 * @import { LightComponentSystem } from './light/system.js'
 * @import { ModelComponentSystem } from './model/system.js'
 * @import { ParticleSystemComponentSystem } from './particle-system/system.js'
 * @import { RenderComponentSystem } from './render/system.js'
 * @import { RigidBodyComponentSystem } from './rigid-body/system.js'
 * @import { ScreenComponentSystem } from './screen/system.js'
 * @import { ScriptComponentSystem } from './script/system.js'
 * @import { ScrollViewComponentSystem } from './scroll-view/system.js'
 * @import { ScrollbarComponentSystem } from './scrollbar/system.js'
 * @import { SoundComponentSystem } from './sound/system.js'
 * @import { SpriteComponentSystem } from './sprite/system.js'
 * @import { ZoneComponentSystem } from './zone/system.js'
 */ /**
 * The ComponentSystemRegistry manages the instances of an application's {@link ComponentSystem}s.
 * {@link AppBase} maintains a single instance of this class which can be accessed via
 * {@link AppBase#systems}.
 *
 * ```javascript
 * // Set the gravity to zero
 * app.systems.rigidbody.gravity = new pc.Vec3(0, 0, 0);
 *
 * // Set the volume to 50%
 * app.systems.sound.volume = 0.5;
 * ```
 */ class ComponentSystemRegistry extends EventHandler {
    /**
     * Create a new ComponentSystemRegistry instance.
     */ constructor(){
        super();
        // An array of pc.ComponentSystem objects
        this.list = [];
    }
    /**
     * Add a component system to the registry.
     *
     * @param {object} system - The {@link ComponentSystem} instance.
     * @ignore
     */ add(system) {
        const id = system.id;
        if (this[id]) {
            throw new Error(`ComponentSystem name '${id}' already registered or not allowed`);
        }
        this[id] = system;
        // Update the component system array
        this.list.push(system);
    }
    /**
     * Remove a component system from the registry.
     *
     * @param {object} system - The {@link ComponentSystem} instance.
     * @ignore
     */ remove(system) {
        const id = system.id;
        if (!this[id]) {
            throw new Error(`No ComponentSystem named '${id}' registered`);
        }
        delete this[id];
        // Update the component system array
        const index = this.list.indexOf(this[id]);
        if (index !== -1) {
            this.list.splice(index, 1);
        }
    }
    destroy() {
        this.off();
        for(let i = 0; i < this.list.length; i++){
            this.list[i].destroy();
        }
    }
}

/**
 * Represents the resource of a Bundle Asset, which contains an index that maps URLs to DataViews.
 *
 * @ignore
 */ class Bundle extends EventHandler {
    static{
        /**
     * Fired when a file has been added to a Bundle.
     *
     * @event
     * @example
     * bundle.on("add", (url, data) => {
     *     console.log("file added: " + url);
     * });
     */ this.EVENT_ADD = 'add';
    }
    static{
        /**
     * Fired when all files of a Bundle has been loaded.
     *
     * @event
     * @example
     * bundle.on("load", () => {
     *     console.log("All Bundle files has been loaded");
     * });
     */ this.EVENT_LOAD = 'load';
    }
    /**
     * Add file to a Bundle.
     *
     * @param {string} url - A url of a file.
     * @param {DataView} data - A DataView of a file.
     * @ignore
     */ addFile(url, data) {
        if (this._index.has(url)) {
            return;
        }
        this._index.set(url, data);
        this.fire('add', url, data);
    }
    /**
     * Returns true if the specified URL exists in the loaded bundle.
     *
     * @param {string} url - The original file URL. Make sure you have called decodeURIComponent on
     * the URL first.
     * @returns {boolean} True of false.
     */ has(url) {
        return this._index.has(url);
    }
    /**
     * Returns a DataView for the specified URL.
     *
     * @param {string} url - The original file URL. Make sure you have called decodeURIComponent on
     * the URL first.
     * @returns {DataView|null} A DataView.
     */ get(url) {
        return this._index.get(url) || null;
    }
    /**
     * Destroys the bundle.
     */ destroy() {
        this._index.clear();
    }
    /**
     * True if all files of a Bundle are loaded.
     * @type {boolean}
     */ set loaded(value) {
        if (!value || this._loaded) {
            return;
        }
        this._loaded = true;
        this.fire('load');
    }
    get loaded() {
        return this._loaded;
    }
    constructor(...args){
        super(...args), /**
     * Index of file url to to DataView.
     * @type {Map<string, DataView>}
     * @private
     */ this._index = new Map(), /**
     * If Bundle has all files loaded.
     * @type {boolean}
     * @private
     */ this._loaded = false;
    }
}

/**
 * A utility class for untaring archives from a fetch request. It processes files from a tar file
 * in a streamed manner, so asset parsing can happen in parallel instead of all at once at the end.
 */ class Untar extends EventHandler {
    /**
     * Create an instance of an Untar.
     *
     * @param {Promise} fetchPromise - A Promise object returned from a fetch request.
     * @param {string} assetsPrefix - Assets registry files prefix.
     */ constructor(fetchPromise, assetsPrefix = ''){
        super(), /**
     * @type {number}
     * @private
     */ this.headerSize = 512, /**
     * @type {number}
     * @private
     */ this.paddingSize = 512, /**
     * @type {number}
     * @private
     */ this.bytesRead = 0, /**
     * @type {number}
     * @private
     */ this.bytesReceived = 0, /**
     * @type {boolean}
     * @private
     */ this.headerRead = false, /**
     * @type {ReadableStream|null}
     * @private
     */ this.reader = null, /**
     * @type {Uint8Array}
     * @private
     */ this.data = new Uint8Array(0), /**
     * @type {TextDecoder|null}
     * @private
     */ this.decoder = null, /**
     * @type {string}
     * @private
     */ this.prefix = '', /**
     * @type {string}
     * @private
     */ this.fileName = '', /**
     * @type {number}
     * @private
     */ this.fileSize = 0, /**
     * @type {string}
     * @private
     */ this.fileType = '', /**
     * @type {string}
     * @private
     */ this.ustarFormat = '';
        this.prefix = assetsPrefix || '';
        this.reader = fetchPromise.body.getReader();
        this.reader.read().then((res)=>{
            this.pump(res.done, res.value);
        }).catch((err)=>{
            this.fire('error', err);
        });
    }
    /**
     * This method is called multiple times when the stream provides data.
     *
     * @param {boolean} done - True when reading data is complete.
     * @param {Uint8Array} value - Chunk of data read from a stream.
     * @returns {Promise|null} Return new pump Promise or null when no more data is available.
     */ pump(done, value) {
        if (done) {
            this.fire('done');
            return null;
        }
        this.bytesReceived += value.byteLength;
        const data = new Uint8Array(this.data.length + value.length);
        data.set(this.data);
        data.set(value, this.data.length);
        this.data = data;
        while(this.readFile());
        return this.reader.read().then((res)=>{
            this.pump(res.done, res.value);
        }).catch((err)=>{
            this.fire('error', err);
        });
    }
    /**
     * Attempt to read file from an available data buffer
     *
     * @returns {boolean} True if file was successfully read and more data is potentially available for
     * processing.
     */ readFile() {
        if (!this.headerRead && this.bytesReceived > this.bytesRead + this.headerSize) {
            this.headerRead = true;
            const view = new DataView(this.data.buffer, this.bytesRead, this.headerSize);
            this.decoder ??= new TextDecoder('windows-1252');
            const headers = this.decoder.decode(view);
            this.fileName = headers.substring(0, 100).replace(/\0/g, '');
            this.fileSize = parseInt(headers.substring(124, 136), 8);
            this.fileType = headers.substring(156, 157);
            this.ustarFormat = headers.substring(257, 263);
            if (this.ustarFormat.indexOf('ustar') !== -1) {
                const prefix = headers.substring(345, 500).replace(/\0/g, '');
                if (prefix.length > 0) {
                    this.fileName = prefix.trim() + this.fileName.trim();
                }
            }
            this.bytesRead += 512;
        }
        if (this.headerRead) {
            // buffer might be not long enough
            if (this.bytesReceived < this.bytesRead + this.fileSize) {
                return false;
            }
            // normal file
            if (this.fileType === '' || this.fileType === '0') {
                const dataView = new DataView(this.data.buffer, this.bytesRead, this.fileSize);
                const file = {
                    name: this.prefix + this.fileName,
                    size: this.fileSize,
                    data: dataView
                };
                this.fire('file', file);
            }
            this.bytesRead += this.fileSize;
            this.headerRead = false;
            // bytes padding
            const bytesRemained = this.bytesRead % this.paddingSize;
            if (bytesRemained !== 0) {
                this.bytesRead += this.paddingSize - bytesRemained;
            }
            return true;
        }
        return false;
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 * @import { AssetRegistry } from '../asset/asset-registry.js'
 * @import { Asset } from '../asset/asset.js'
 */ /**
 * @callback ResourceHandlerCallback
 * Callback used by {@link ResourceHandler#load} when a resource is loaded (or an error occurs).
 * @param {string|null} err - The error message in the case where the load fails.
 * @param {any} [response] - The raw data that has been successfully loaded.
 * @returns {void}
 */ /**
 * Base class for ResourceHandlers used by {@link ResourceLoader}.
 */ class ResourceHandler {
    /**
     * @param {AppBase} app - The running {@link AppBase}.
     * @param {string} handlerType - The type of the resource the handler handles.
     */ constructor(app, handlerType){
        /**
     * Type of the resource the handler handles.
     *
     * @type {string}
     */ this.handlerType = '';
        /** @private */ this._maxRetries = 0;
        this._app = app;
        this.handlerType = handlerType;
    }
    /**
     * Sets the number of times to retry a failed request for the resource.
     *
     * @type {number}
     */ set maxRetries(value) {
        this._maxRetries = value;
    }
    /**
     * Gets the number of times to retry a failed request for the resource.
     *
     * @type {number}
     */ get maxRetries() {
        return this._maxRetries;
    }
    /**
     * Load a resource from a remote URL. The base implementation does nothing.
     *
     * @param {string|object} url - Either the URL of the resource to load or a structure
     * containing the load and original URL.
     * @param {string} [url.load] - The URL to be used for loading the resource.
     * @param {string} [url.original] - The original URL to be used for identifying the resource
     * format. This is necessary when loading, for example from blob.
     * @param {ResourceHandlerCallback} callback - The callback used when the resource is loaded or
     * an error occurs.
     * @param {Asset} [asset] - Optional asset that is passed by ResourceLoader.
     */ load(url, callback, asset) {
    // do nothing
    }
    /**
     * The open function is passed the raw resource data. The handler can then process the data
     * into a format that can be used at runtime. The base implementation simply returns the data.
     *
     * @param {string} url - The URL of the resource to open.
     * @param {*} data - The raw resource data passed by callback from {@link ResourceHandler#load}.
     * @param {Asset} [asset] - Optional asset that is passed by ResourceLoader.
     * @returns {*} The parsed resource data.
     */ open(url, data, asset) {
        return data;
    }
    /**
     * The patch function performs any operations on a resource that requires a dependency on its
     * asset data or any other asset data. The base implementation does nothing.
     *
     * @param {Asset} asset - The asset to patch.
     * @param {AssetRegistry} assets - The asset registry.
     */ patch(asset, assets) {
    // do nothing
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 */ /**
 * Loads Bundle Assets.
 *
 * @ignore
 */ class BundleHandler extends ResourceHandler {
    /**
     * Create a new BundleHandler instance.
     *
     * @param {AppBase} app - The running {@link AppBase}.
     */ constructor(app){
        super(app, 'bundle');
        this._assets = app.assets;
    }
    _fetchRetries(url, options, retries = 0) {
        return new Promise((resolve, reject)=>{
            const tryFetch = ()=>{
                fetch(url, options).then(resolve).catch((err)=>{
                    retries++;
                    if (retries < this.maxRetries) {
                        Debug.error(`Bundle failed to load retrying (attempt ${retries}`);
                        tryFetch();
                    } else {
                        reject(err);
                    }
                });
            };
            tryFetch();
        });
    }
    load(url, callback) {
        if (typeof url === 'string') {
            url = {
                load: url,
                original: url
            };
        }
        this._fetchRetries(url.load, {
            mode: 'cors'
        }, this.maxRetries).then((res)=>{
            const bundle = new Bundle();
            callback(null, bundle);
            const untar = new Untar(res, this._assets.prefix);
            untar.on('file', (file)=>{
                bundle.addFile(file.name, file.data);
            });
            untar.on('done', ()=>{
                bundle.loaded = true;
            });
            untar.on('error', (err)=>{
                Debug.error(err);
                callback(err);
            });
        }).catch((err)=>{
            Debug.error(err);
            callback(err);
        });
    }
    /**
     * Open the bundle.
     *
     * @param {string} url - The URL of the resource to open.
     * @param {Bundle} bundle - Bundle to open.
     * @returns {Bundle} The bundle.
     */ open(url, bundle) {
        return bundle;
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 * @import { AssetRegistry } from '../asset/asset-registry.js'
 * @import { Asset } from '../asset/asset.js'
 * @import { BundlesFilterCallback } from '../asset/asset-registry.js'
 * @import { ResourceHandler } from './handler.js'
 */ /**
 * @callback ResourceLoaderCallback
 * Callback used by {@link ResourceLoader#load} when a resource is loaded (or an error occurs).
 * @param {string|null} err - The error message in the case where the load fails.
 * @param {any} [resource] - The resource that has been successfully loaded.
 * @returns {void}
 */ /**
 * Load resource data, potentially from remote sources. Caches resource on load to prevent multiple
 * requests. Add ResourceHandlers to handle different types of resources.
 */ class ResourceLoader {
    /**
     * Create a new ResourceLoader instance.
     *
     * @param {AppBase} app - The application.
     */ constructor(app){
        this._handlers = {};
        this._requests = {};
        this._cache = {};
        this._app = app;
    }
    /**
     * Add a {@link ResourceHandler} for a resource type. Handler should support at least `load()`
     * and `open()`. Handlers can optionally support patch(asset, assets) to handle dependencies on
     * other assets.
     *
     * @param {string} type - The name of the resource type that the handler will be registered
     * with. Can be:
     *
     * - {@link ASSET_ANIMATION}
     * - {@link ASSET_AUDIO}
     * - {@link ASSET_IMAGE}
     * - {@link ASSET_JSON}
     * - {@link ASSET_MODEL}
     * - {@link ASSET_MATERIAL}
     * - {@link ASSET_TEXT}
     * - {@link ASSET_TEXTURE}
     * - {@link ASSET_CUBEMAP}
     * - {@link ASSET_SHADER}
     * - {@link ASSET_CSS}
     * - {@link ASSET_HTML}
     * - {@link ASSET_SCRIPT}
     * - {@link ASSET_CONTAINER}
     *
     * @param {ResourceHandler} handler - An instance of a resource handler
     * supporting at least `load()` and `open()`.
     * @example
     * const loader = new ResourceLoader();
     * loader.addHandler("json", new pc.JsonHandler());
     */ addHandler(type, handler) {
        this._handlers[type] = handler;
        handler._loader = this;
    }
    /**
     * Remove a {@link ResourceHandler} for a resource type.
     *
     * @param {string} type - The name of the type that the handler will be removed.
     */ removeHandler(type) {
        delete this._handlers[type];
    }
    /**
     * Get a {@link ResourceHandler} for a resource type.
     *
     * @param {string} type - The name of the resource type that the handler is registered with.
     * @returns {ResourceHandler|undefined} The registered handler, or
     * undefined if the requested handler is not registered.
     */ getHandler(type) {
        return this._handlers[type];
    }
    static makeKey(url, type) {
        return `${url}-${type}`;
    }
    /**
     * Make a request for a resource from a remote URL. Parse the returned data using the handler
     * for the specified type. When loaded and parsed, use the callback to return an instance of
     * the resource.
     *
     * @param {string} url - The URL of the resource to load.
     * @param {string} type - The type of resource expected.
     * @param {ResourceLoaderCallback} callback - The callback used when the resource is loaded or
     * an error occurs. Passed (err, resource) where err is null if there are no errors.
     * @param {Asset} [asset] - Optional asset that is passed into
     * handler.
     * @param {object} [options] - Additional options for loading.
     * @param {boolean} [options.bundlesIgnore] - If set to true, then asset will not try to load
     * from a bundle. Defaults to false.
     * @param {BundlesFilterCallback} [options.bundlesFilter] - A callback that will be called
     * when loading an asset that is contained in any of the bundles. It provides an array of
     * bundles and will ensure asset is loaded from bundle returned from a callback. By default,
     * the smallest filesize bundle is chosen.
     * @example
     * app.loader.load("../path/to/texture.png", "texture", function (err, texture) {
     *     // use texture here
     * });
     */ load(url, type, callback, asset, options) {
        const handler = this._handlers[type];
        if (!handler) {
            const err = `No resource handler for asset type: '${type}' when loading [${url}]`;
            Debug.errorOnce(err);
            callback(err);
            return;
        }
        // handle requests with null file
        if (!url) {
            this._loadNull(handler, callback, asset);
            return;
        }
        const key = ResourceLoader.makeKey(url, type);
        if (this._cache[key] !== undefined) {
            // in cache
            callback(null, this._cache[key]);
        } else if (this._requests[key]) {
            // existing request
            this._requests[key].push(callback);
        } else {
            // new request
            this._requests[key] = [
                callback
            ];
            const self = this;
            const handleLoad = function(err, urlObj) {
                if (err) {
                    self._onFailure(key, err);
                    return;
                }
                if (urlObj.load instanceof DataView) {
                    if (handler.openBinary) {
                        if (!self._requests[key]) {
                            return;
                        }
                        try {
                            const data = handler.openBinary(urlObj.load);
                            self._onSuccess(key, data);
                        } catch (err) {
                            self._onFailure(key, err);
                        }
                        return;
                    }
                    urlObj.load = URL.createObjectURL(new Blob([
                        urlObj.load
                    ]));
                    if (asset) {
                        if (asset.urlObject) {
                            URL.revokeObjectURL(asset.urlObject);
                        }
                        asset.urlObject = urlObj.load;
                    }
                }
                handler.load(urlObj, (err, data, extra)=>{
                    // make sure key exists because loader
                    // might have been destroyed by now
                    if (!self._requests[key]) {
                        return;
                    }
                    if (err) {
                        self._onFailure(key, err);
                        return;
                    }
                    try {
                        self._onSuccess(key, handler.open(urlObj.original, data, asset), extra);
                    } catch (e) {
                        self._onFailure(key, e);
                    }
                }, asset);
            };
            const normalizedUrl = url.split('?')[0];
            if (this._app.enableBundles && this._app.bundles.hasUrl(normalizedUrl) && !(options && options.bundlesIgnore)) {
                // if there is no loaded bundle with asset, then start loading a bundle
                if (!this._app.bundles.urlIsLoadedOrLoading(normalizedUrl)) {
                    const bundles = this._app.bundles.listBundlesForAsset(asset);
                    let bundle;
                    if (options && options.bundlesFilter) {
                        bundle = options.bundlesFilter(bundles);
                    }
                    if (!bundle) {
                        // prioritize smallest bundle
                        bundles?.sort((a, b)=>{
                            return a.file.size - b.file.size;
                        });
                        bundle = bundles?.[0];
                    }
                    if (bundle) this._app.assets?.load(bundle);
                }
                this._app.bundles.loadUrl(normalizedUrl, (err, fileUrlFromBundle)=>{
                    handleLoad(err, {
                        load: fileUrlFromBundle,
                        original: normalizedUrl
                    });
                });
            } else {
                handleLoad(null, {
                    load: url,
                    original: asset && asset.file.filename || url
                });
            }
        }
    }
    // load an asset with no url, skipping bundles and caching
    _loadNull(handler, callback, asset) {
        const onLoad = function(err, data, extra) {
            if (err) {
                callback(err);
            } else {
                try {
                    callback(null, handler.open(null, data, asset), extra);
                } catch (e) {
                    callback(e);
                }
            }
        };
        handler.load(null, onLoad, asset);
    }
    _onSuccess(key, result, extra) {
        if (result !== null) {
            this._cache[key] = result;
        } else {
            delete this._cache[key];
        }
        for(let i = 0; i < this._requests[key].length; i++){
            this._requests[key][i](null, result, extra);
        }
        delete this._requests[key];
    }
    _onFailure(key, err) {
        console.error(err);
        if (this._requests[key]) {
            for(let i = 0; i < this._requests[key].length; i++){
                this._requests[key][i](err);
            }
            delete this._requests[key];
        }
    }
    /**
     * Convert raw resource data into a resource instance. E.g. Take 3D model format JSON and
     * return a {@link Model}.
     *
     * @param {string} type - The type of resource.
     * @param {*} data - The raw resource data.
     * @returns {*} The parsed resource data.
     */ open(type, data) {
        const handler = this._handlers[type];
        if (!handler) {
            console.warn(`No resource handler found for: ${type}`);
            return data;
        }
        return handler.open(null, data);
    }
    /**
     * Perform any operations on a resource, that requires a dependency on its asset data or any
     * other asset data.
     *
     * @param {Asset} asset - The asset to patch.
     * @param {AssetRegistry} assets - The asset registry.
     */ patch(asset, assets) {
        const handler = this._handlers[asset.type];
        if (!handler) {
            console.warn(`No resource handler found for: ${asset.type}`);
            return;
        }
        if (handler.patch) {
            handler.patch(asset, assets);
        }
    }
    /**
     * Remove resource from cache.
     *
     * @param {string} url - The URL of the resource.
     * @param {string} type - The type of resource.
     */ clearCache(url, type) {
        const key = ResourceLoader.makeKey(url, type);
        delete this._cache[key];
    }
    /**
     * Check cache for resource from a URL. If present, return the cached value.
     *
     * @param {string} url - The URL of the resource to get from the cache.
     * @param {string} type - The type of the resource.
     * @returns {*} The resource loaded from the cache.
     */ getFromCache(url, type) {
        const key = ResourceLoader.makeKey(url, type);
        if (this._cache[key]) {
            return this._cache[key];
        }
        return undefined;
    }
    /**
     * Enables retrying of failed requests when loading assets.
     *
     * @param {number} maxRetries - The maximum number of times to retry loading an asset. Defaults
     * to 5.
     * @ignore
     */ enableRetry(maxRetries = 5) {
        maxRetries = Math.max(0, maxRetries) || 0;
        for(const key in this._handlers){
            this._handlers[key].maxRetries = maxRetries;
        }
    }
    /**
     * Disables retrying of failed requests when loading assets.
     *
     * @ignore
     */ disableRetry() {
        for(const key in this._handlers){
            this._handlers[key].maxRetries = 0;
        }
    }
    /**
     * Destroys the resource loader.
     */ destroy() {
        this._handlers = {};
        this._requests = {};
        this._cache = {};
    }
}

class I18nParser {
    _validate(data) {
        if (!data.header) {
            throw new Error('pc.I18n#addData: Missing "header" field');
        }
        if (!data.header.version) {
            throw new Error('pc.I18n#addData: Missing "header.version" field');
        }
        if (data.header.version !== 1) {
            throw new Error('pc.I18n#addData: Invalid "header.version" field');
        }
        if (!data.data) {
            throw new Error('pc.I18n#addData: Missing "data" field');
        } else if (!Array.isArray(data.data)) {
            throw new Error('pc.I18n#addData: "data" field must be an array');
        }
        for(let i = 0, len = data.data.length; i < len; i++){
            const entry = data.data[i];
            if (!entry.info) {
                throw new Error(`pc.I18n#addData: missing "data[${i}].info" field`);
            }
            if (!entry.info.locale) {
                throw new Error(`pc.I18n#addData: missing "data[${i}].info.locale" field`);
            }
            if (typeof entry.info.locale !== 'string') {
                throw new Error(`pc.I18n#addData: "data[${i}].info.locale" must be a string`);
            }
            if (!entry.messages) {
                throw new Error(`pc.I18n#addData: missing "data[${i}].messages" field`);
            }
        }
    }
    parse(data) {
        this._validate(data);
        return data.data;
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 */ /**
 * Handles localization. Responsible for loading localization assets and returning translations for
 * a certain key. Can also handle plural forms. To override its default behavior define a different
 * implementation for {@link I18n#getText} and {@link I18n#getPluralText}.
 */ let I18n$1 = class I18n extends EventHandler {
    static{
        /**
     * Fired when when the locale is changed.
     *
     * @event
     * @example
     * app.i18n.on('change', (newLocale, oldLocale) => {
     *    console.log(`Locale changed from ${oldLocale} to ${newLocale}`);
     * });
     */ this.EVENT_CHANGE = 'change';
    }
    /**
     * Create a new I18n instance.
     *
     * @param {AppBase} app - The application.
     */ constructor(app){
        super();
        this.locale = DEFAULT_LOCALE;
        this._translations = {};
        this._availableLangs = {};
        this._app = app;
        this._assets = [];
        this._parser = new I18nParser();
    }
    /**
     * Sets the array of asset ids or assets that contain localization data in the expected format.
     * I18n will automatically load translations from these assets as the assets are loaded and it
     * will also automatically unload translations if the assets get removed or unloaded at runtime.
     *
     * @type {number[]|Asset[]}
     */ set assets(value) {
        const index = {};
        // convert array to dict
        for(let i = 0, len = value.length; i < len; i++){
            const id = value[i] instanceof Asset ? value[i].id : value[i];
            index[id] = true;
        }
        // remove assets not in value
        let i = this._assets.length;
        while(i--){
            const id = this._assets[i];
            if (!index[id]) {
                this._app.assets.off(`add:${id}`, this._onAssetAdd, this);
                const asset = this._app.assets.get(id);
                if (asset) {
                    this._onAssetRemove(asset);
                }
                this._assets.splice(i, 1);
            }
        }
        // add assets in value that do not already exist here
        for(const id in index){
            const idNum = parseInt(id, 10);
            if (this._assets.indexOf(idNum) !== -1) continue;
            this._assets.push(idNum);
            const asset = this._app.assets.get(idNum);
            if (!asset) {
                this._app.assets.once(`add:${idNum}`, this._onAssetAdd, this);
            } else {
                this._onAssetAdd(asset);
            }
        }
    }
    /**
     * Gets the array of asset ids that contain localization data in the expected format.
     *
     * @type {number[]|Asset[]}
     */ get assets() {
        return this._assets;
    }
    /**
     * Sets the current locale. For example, "en-US". Changing the locale will raise an event which
     * will cause localized Text Elements to change language to the new locale.
     *
     * @type {string}
     */ set locale(value) {
        if (this._locale === value) {
            return;
        }
        // replace 'in' language with 'id'
        // for Indonesian because both codes are valid
        // so that users only need to use the 'id' code
        let lang = getLang(value);
        if (lang === 'in') {
            lang = 'id';
            value = replaceLang(value, lang);
            if (this._locale === value) {
                return;
            }
        }
        const old = this._locale;
        // cache locale, lang and plural function
        this._locale = value;
        this._lang = lang;
        this._pluralFn = getPluralFn(this._lang);
        // raise event
        this.fire(I18n.EVENT_CHANGE, value, old);
    }
    /**
     * Gets the current locale.
     *
     * @type {string}
     */ get locale() {
        return this._locale;
    }
    /**
     * Returns the first available locale based on the desired locale specified. First tries to
     * find the desired locale and then tries to find an alternative locale based on the language.
     *
     * @param {string} desiredLocale - The desired locale e.g. en-US.
     * @param {object} availableLocales - A dictionary where each key is an available locale.
     * @returns {string} The locale found or if no locale is available returns the default en-US
     * locale.
     * @example
     * // With a defined dictionary of locales
     * const availableLocales = { en: 'en-US', fr: 'fr-FR' };
     * const locale = pc.I18n.getText('en-US', availableLocales);
     * // returns 'en'
     * @ignore
     */ static findAvailableLocale(desiredLocale, availableLocales) {
        return findAvailableLocale(desiredLocale, availableLocales);
    }
    /**
     * Returns the first available locale based on the desired locale specified. First tries to
     * find the desired locale in the loaded translations and then tries to find an alternative
     * locale based on the language.
     *
     * @param {string} desiredLocale - The desired locale e.g. en-US.
     * @returns {string} The locale found or if no locale is available returns the default en-US
     * locale.
     * @example
     * const locale = this.app.i18n.getText('en-US');
     */ findAvailableLocale(desiredLocale) {
        if (this._translations[desiredLocale]) {
            return desiredLocale;
        }
        const lang = getLang(desiredLocale);
        return this._findFallbackLocale(desiredLocale, lang);
    }
    /**
     * Returns the translation for the specified key and locale. If the locale is not specified it
     * will use the current locale.
     *
     * @param {string} key - The localization key.
     * @param {string} [locale] - The desired locale.
     * @returns {string} The translated text. If no translations are found at all for the locale
     * then it will return the en-US translation. If no translation exists for that key then it will
     * return the localization key.
     * @example
     * const localized = this.app.i18n.getText('localization-key');
     * const localizedFrench = this.app.i18n.getText('localization-key', 'fr-FR');
     */ getText(key, locale) {
        // default translation is the key
        let result = key;
        let lang;
        if (!locale) {
            locale = this._locale;
            lang = this._lang;
        }
        let translations = this._translations[locale];
        if (!translations) {
            if (!lang) {
                lang = getLang(locale);
            }
            locale = this._findFallbackLocale(locale, lang);
            translations = this._translations[locale];
        }
        if (translations && translations.hasOwnProperty(key)) {
            result = translations[key];
            // if this is a plural key then return the first entry in the array
            if (Array.isArray(result)) {
                result = result[0];
            }
            // if null or undefined switch back to the key (empty string is allowed)
            if (result === null || result === undefined) {
                result = key;
            }
        }
        return result;
    }
    /**
     * Returns the pluralized translation for the specified key, number n and locale. If the locale
     * is not specified it will use the current locale.
     *
     * @param {string} key - The localization key.
     * @param {number} n - The number used to determine which plural form to use. E.g. For the
     * phrase "5 Apples" n equals 5.
     * @param {string} [locale] - The desired locale.
     * @returns {string} The translated text. If no translations are found at all for the locale
     * then it will return the en-US translation. If no translation exists for that key then it
     * will return the localization key.
     * @example
     * // manually replace {number} in the resulting translation with our number
     * const localized = this.app.i18n.getPluralText('{number} apples', number).replace("{number}", number);
     */ getPluralText(key, n, locale) {
        // default translation is the key
        let result = key;
        let lang;
        let pluralFn;
        if (!locale) {
            locale = this._locale;
            lang = this._lang;
            pluralFn = this._pluralFn;
        } else {
            lang = getLang(locale);
            pluralFn = getPluralFn(lang);
        }
        let translations = this._translations[locale];
        if (!translations) {
            locale = this._findFallbackLocale(locale, lang);
            lang = getLang(locale);
            pluralFn = getPluralFn(lang);
            translations = this._translations[locale];
        }
        if (translations && translations[key] && pluralFn) {
            const index = pluralFn(n);
            result = translations[key][index];
            // if null or undefined switch back to the key (empty string is allowed)
            if (result === null || result === undefined) {
                result = key;
            }
        }
        return result;
    }
    /**
     * Adds localization data. If the locale and key for a translation already exists it will be
     * overwritten.
     *
     * @param {object} data - The localization data. See example for the expected format of the
     * data.
     * @example
     * this.app.i18n.addData({
     *     header: {
     *         version: 1
     *     },
     *     data: [{
     *         info: {
     *             locale: 'en-US'
     *         },
     *         messages: {
     *             "key": "translation",
     *             // The number of plural forms depends on the locale. See the manual for more information.
     *             "plural_key": ["one item", "more than one items"]
     *         }
     *     }, {
     *         info: {
     *             locale: 'fr-FR'
     *         },
     *         messages: {
     *             // ...
     *         }
     *     }]
     * });
     */ addData(data) {
        let parsed;
        try {
            parsed = this._parser.parse(data);
        } catch (err) {
            console.error(err);
            return;
        }
        for(let i = 0, len = parsed.length; i < len; i++){
            const entry = parsed[i];
            const locale = entry.info.locale;
            const messages = entry.messages;
            if (!this._translations[locale]) {
                this._translations[locale] = {};
                const lang = getLang(locale);
                // remember the first locale we've found for that language
                // in case we need to fall back to it
                if (!this._availableLangs[lang]) {
                    this._availableLangs[lang] = locale;
                }
            }
            Object.assign(this._translations[locale], messages);
            this.fire('data:add', locale, messages);
        }
    }
    /**
     * Removes localization data.
     *
     * @param {object} data - The localization data. The data is expected to be in the same format
     * as {@link I18n#addData}.
     */ removeData(data) {
        let parsed;
        try {
            parsed = this._parser.parse(data);
        } catch (err) {
            console.error(err);
            return;
        }
        for(let i = 0, len = parsed.length; i < len; i++){
            const entry = parsed[i];
            const locale = entry.info.locale;
            const translations = this._translations[locale];
            if (!translations) continue;
            const messages = entry.messages;
            for(const key in messages){
                delete translations[key];
            }
            // if no more entries for that locale then
            // delete the locale
            if (Object.keys(translations).length === 0) {
                delete this._translations[locale];
                delete this._availableLangs[getLang(locale)];
            }
            this.fire('data:remove', locale, messages);
        }
    }
    /**
     * Frees up memory.
     */ destroy() {
        this._translations = null;
        this._availableLangs = null;
        this._assets = null;
        this._parser = null;
        this.off();
    }
    // Finds a fallback locale for the specified locale and language.
    // 1) First tries DEFAULT_LOCALE_FALLBACKS
    // 2) If no translation exists for that locale return the first locale available for that language.
    // 3) If no translation exists for that either then return the DEFAULT_LOCALE
    _findFallbackLocale(locale, lang) {
        let result = DEFAULT_LOCALE_FALLBACKS[locale];
        if (result && this._translations[result]) {
            return result;
        }
        result = DEFAULT_LOCALE_FALLBACKS[lang];
        if (result && this._translations[result]) {
            return result;
        }
        result = this._availableLangs[lang];
        if (result && this._translations[result]) {
            return result;
        }
        return DEFAULT_LOCALE;
    }
    _onAssetAdd(asset) {
        asset.on('load', this._onAssetLoad, this);
        asset.on('change', this._onAssetChange, this);
        asset.on('remove', this._onAssetRemove, this);
        asset.on('unload', this._onAssetUnload, this);
        if (asset.resource) {
            this._onAssetLoad(asset);
        }
    }
    _onAssetLoad(asset) {
        this.addData(asset.resource);
    }
    _onAssetChange(asset) {
        if (asset.resource) {
            this.addData(asset.resource);
        }
    }
    _onAssetRemove(asset) {
        asset.off('load', this._onAssetLoad, this);
        asset.off('change', this._onAssetChange, this);
        asset.off('remove', this._onAssetRemove, this);
        asset.off('unload', this._onAssetUnload, this);
        if (asset.resource) {
            this.removeData(asset.resource);
        }
        this._app.assets.once(`add:${asset.id}`, this._onAssetAdd, this);
    }
    _onAssetUnload(asset) {
        if (asset.resource) {
            this.removeData(asset.resource);
        }
    }
};

/**
 * @import { AppBase } from '../app-base.js'
 * @import { AttributeSchema } from './script-attributes.js'
 * @import { ScriptType } from './script-type.js'
 */ /**
 * Container for all {@link ScriptType}s that are available to this application. Note that
 * PlayCanvas scripts can access the Script Registry from inside the application with
 * {@link AppBase#scripts}.
 *
 * @category Script
 */ class ScriptRegistry extends EventHandler {
    /**
     * Create a new ScriptRegistry instance.
     *
     * @param {AppBase} app - Application to attach registry to.
     */ constructor(app){
        super(), /**
     * @type {Object<string, typeof ScriptType>}
     * @private
     */ this._scripts = {}, /**
     * @type {typeof ScriptType[]}
     * @private
     */ this._list = [], /**
     * A Map of script names to attribute schemas.
     *
     * @type {Map<string, AttributeSchema>}
     * @private
     */ this._scriptSchemas = new Map();
        this.app = app;
    }
    destroy() {
        this.app = null;
        this.off();
    }
    /**
     * Registers a schema against a script instance.
     *
     * @param {string} id - The key to use to store the schema
     * @param {AttributeSchema} schema - An schema definition for the script
     */ addSchema(id, schema) {
        if (!schema) return;
        this._scriptSchemas.set(id, schema);
    }
    /**
     * Returns a schema for a given script name.
     *
     * @param {string} id - The key to store the schema under
     * @returns {AttributeSchema | undefined} - The schema stored under the key
     */ getSchema(id) {
        return this._scriptSchemas.get(id);
    }
    /**
     * Add {@link ScriptType} to registry. Note: when {@link createScript} is called, it will add
     * the {@link ScriptType} to the registry automatically. If a script already exists in
     * registry, and the new script has a `swap` method defined, it will perform code hot swapping
     * automatically in async manner.
     *
     * @param {typeof ScriptType} script - Script Type that is created
     * using {@link createScript}.
     * @returns {boolean} True if added for the first time or false if script already exists.
     * @example
     * var PlayerController = pc.createScript('playerController');
     * // playerController Script Type will be added to pc.ScriptRegistry automatically
     * console.log(app.scripts.has('playerController')); // outputs true
     */ add(script) {
        const scriptName = script.__name;
        if (this._scripts.hasOwnProperty(scriptName)) {
            setTimeout(()=>{
                if (script.prototype.swap) {
                    // swapping
                    const old = this._scripts[scriptName];
                    const ind = this._list.indexOf(old);
                    this._list[ind] = script;
                    this._scripts[scriptName] = script;
                    this.fire('swap', scriptName, script);
                    this.fire(`swap:${scriptName}`, script);
                } else {
                    console.warn(`script registry already has '${scriptName}' script, define 'swap' method for new script type to enable code hot swapping`);
                }
            });
            return false;
        }
        this._scripts[scriptName] = script;
        this._list.push(script);
        this.fire('add', scriptName, script);
        this.fire(`add:${scriptName}`, script);
        // for all components awaiting Script Type
        // create script instance
        setTimeout(()=>{
            if (!this._scripts.hasOwnProperty(scriptName)) {
                return;
            }
            // this is a check for a possible error
            // that might happen if the app has been destroyed before
            // setTimeout has finished
            if (!this.app || !this.app.systems || !this.app.systems.script) {
                return;
            }
            const components = this.app.systems.script._components;
            let attributes;
            const scriptInstances = [];
            const scriptInstancesInitialized = [];
            for(components.loopIndex = 0; components.loopIndex < components.length; components.loopIndex++){
                const component = components.items[components.loopIndex];
                // check if awaiting for script
                if (component._scriptsIndex[scriptName] && component._scriptsIndex[scriptName].awaiting) {
                    if (component._scriptsData && component._scriptsData[scriptName]) {
                        attributes = component._scriptsData[scriptName].attributes;
                    }
                    const scriptInstance = component.create(scriptName, {
                        preloading: true,
                        ind: component._scriptsIndex[scriptName].ind,
                        attributes: attributes
                    });
                    if (scriptInstance) {
                        scriptInstances.push(scriptInstance);
                    }
                    // initialize attributes
                    for (const script of component.scripts){
                        component.initializeAttributes(script);
                    }
                }
            }
            // call initialize()
            for(let i = 0; i < scriptInstances.length; i++){
                if (scriptInstances[i].enabled) {
                    scriptInstances[i]._initialized = true;
                    scriptInstancesInitialized.push(scriptInstances[i]);
                    if (scriptInstances[i].initialize) {
                        scriptInstances[i].initialize();
                    }
                }
            }
            // call postInitialize()
            for(let i = 0; i < scriptInstancesInitialized.length; i++){
                if (!scriptInstancesInitialized[i].enabled || scriptInstancesInitialized[i]._postInitialized) {
                    continue;
                }
                scriptInstancesInitialized[i]._postInitialized = true;
                if (scriptInstancesInitialized[i].postInitialize) {
                    scriptInstancesInitialized[i].postInitialize();
                }
            }
        });
        return true;
    }
    /**
     * Remove {@link ScriptType}.
     *
     * @param {string|typeof ScriptType} nameOrType - The name or type
     * of {@link ScriptType}.
     * @returns {boolean} True if removed or False if already not in registry.
     * @example
     * app.scripts.remove('playerController');
     */ remove(nameOrType) {
        let scriptType = nameOrType;
        let scriptName = nameOrType;
        if (typeof scriptName !== 'string') {
            scriptName = scriptType.__name;
        } else {
            scriptType = this.get(scriptName);
        }
        if (this.get(scriptName) !== scriptType) {
            return false;
        }
        delete this._scripts[scriptName];
        const ind = this._list.indexOf(scriptType);
        this._list.splice(ind, 1);
        this.fire('remove', scriptName, scriptType);
        this.fire(`remove:${scriptName}`, scriptType);
        return true;
    }
    /**
     * Get {@link ScriptType} by name.
     *
     * @param {string} name - Name of a {@link ScriptType}.
     * @returns {typeof ScriptType} The Script Type if it exists in the
     * registry or null otherwise.
     * @example
     * var PlayerController = app.scripts.get('playerController');
     */ get(name) {
        return this._scripts[name] || null;
    }
    /**
     * Check if a {@link ScriptType} with the specified name is in the registry.
     *
     * @param {string|typeof ScriptType} nameOrType - The name or type
     * of {@link ScriptType}.
     * @returns {boolean} True if {@link ScriptType} is in registry.
     * @example
     * if (app.scripts.has('playerController')) {
     *     // playerController is in pc.ScriptRegistry
     * }
     */ has(nameOrType) {
        if (typeof nameOrType === 'string') {
            return this._scripts.hasOwnProperty(nameOrType);
        }
        if (!nameOrType) return false;
        const scriptName = nameOrType.__name;
        return this._scripts[scriptName] === nameOrType;
    }
    /**
     * Get list of all {@link ScriptType}s from registry.
     *
     * @returns {Array<typeof ScriptType>} list of all {@link ScriptType}s
     * in registry.
     * @example
     * // logs array of all Script Type names available in registry
     * console.log(app.scripts.list().map(function (o) {
     *     return o.name;
     * }));
     */ list() {
        return this._list;
    }
}

/**
 * @import { AnimComponent } from './components/anim/component.js'
 * @import { AnimationComponent } from './components/animation/component.js'
 * @import { AppBase } from './app-base.js'
 * @import { AudioListenerComponent } from './components/audio-listener/component.js'
 * @import { ButtonComponent } from './components/button/component.js'
 * @import { CameraComponent } from './components/camera/component.js'
 * @import { CollisionComponent } from './components/collision/component.js'
 * @import { Component } from './components/component.js'
 * @import { ElementComponent } from './components/element/component.js'
 * @import { GSplatComponent } from './components/gsplat/component.js'
 * @import { LayoutChildComponent } from './components/layout-child/component.js'
 * @import { LayoutGroupComponent } from './components/layout-group/component.js'
 * @import { LightComponent } from './components/light/component.js'
 * @import { ModelComponent } from './components/model/component.js'
 * @import { ParticleSystemComponent } from './components/particle-system/component.js'
 * @import { RenderComponent } from './components/render/component.js'
 * @import { RigidBodyComponent } from './components/rigid-body/component.js'
 * @import { ScreenComponent } from './components/screen/component.js'
 * @import { ScriptComponent } from './components/script/component.js'
 * @import { ScriptType } from './script/script-type.js'
 * @import { ScrollViewComponent } from './components/scroll-view/component.js'
 * @import { ScrollbarComponent } from './components/scrollbar/component.js'
 * @import { SoundComponent } from './components/sound/component.js'
 * @import { SpriteComponent } from './components/sprite/component.js'
 */ /**
 * @param {Component} a - First object with `order` property.
 * @param {Component} b - Second object with `order` property.
 * @returns {number} A number indicating the relative position.
 */ const cmpStaticOrder = (a, b)=>a.constructor.order - b.constructor.order;
/**
 * @param {Array<Component>} arr - Array to be sorted in place where each element contains
 * an object with a static `order` property.
 * @returns {Array<Component>} In place sorted array.
 */ const sortStaticOrder = (arr)=>arr.sort(cmpStaticOrder);
/**
 * @type {GraphNode[]}
 */ const _enableList = [];
/**
 * @type {Array<Array<Component>>}
 */ const tmpPool = [];
const getTempArray = ()=>{
    return tmpPool.pop() ?? [];
};
/**
 * @param {Array<Component>} a - Array to return back to pool.
 */ const releaseTempArray = (a)=>{
    a.length = 0;
    tmpPool.push(a);
};
/**
 * The Entity is a core primitive of a PlayCanvas application. Generally speaking, any object in
 * your application will be represented by an Entity, along with a set of {@link Component}s. Each
 * component enables a particular capability. For example, the {@link RenderComponent} enables an
 * entity to render a 3D model, and the {@link ScriptComponent} enables an entity to run code that
 * implements custom behavior.
 *
 * Entity is a subclass of {@link GraphNode} which allows entities to form a tree-like hierarchy
 * (based on parent/child relationships). The root of the entity hierarchy can be queried with
 * {@link AppBase#root}. Entities inherit a 3D transform from {@link GraphNode} which allows them
 * to be positioned, rotated and scaled.
 */ class Entity extends GraphNode {
    static{
        /**
     * Fired after the entity is destroyed.
     *
     * @event
     * @example
     * entity.on('destroy', (e) => {
     *     console.log(`Entity ${e.name} has been destroyed`);
     * });
     */ this.EVENT_DESTROY = 'destroy';
    }
    /**
     * Create a new Entity.
     *
     * @param {string} [name] - The non-unique name of the entity, default is "Untitled".
     * @param {AppBase} [app] - The application the entity belongs to, default is the current
     * application.
     * @example
     * const entity = new pc.Entity();
     *
     * // Add a Component to the Entity
     * entity.addComponent('camera', {
     *     fov: 45,
     *     nearClip: 1,
     *     farClip: 10000
     * });
     *
     * // Add the Entity into the scene graph
     * app.root.addChild(entity);
     *
     * // Move the entity
     * entity.translate(10, 0, 0);
     *
     * // Or translate it by setting its position directly
     * const p = entity.getPosition();
     * entity.setPosition(p.x + 10, p.y, p.z);
     *
     * // Change the entity's rotation in local space
     * const e = entity.getLocalEulerAngles();
     * entity.setLocalEulerAngles(e.x, e.y + 90, e.z);
     *
     * // Or use rotateLocal
     * entity.rotateLocal(0, 90, 0);
     */ constructor(name, app = getApplication()){
        super(name), /**
     * Component storage.
     *
     * @type {Object<string, Component>}
     * @ignore
     */ this.c = {}, /**
     * Used by component systems to speed up destruction.
     *
     * @type {boolean}
     * @ignore
     */ this._destroying = false, /**
     * @type {string|null}
     * @private
     */ this._guid = null, /**
     * Used to differentiate between the entities of a template root instance, which have it set to
     * true, and the cloned instance entities (set to false).
     *
     * @type {boolean}
     * @ignore
     */ this._template = false;
        Debug.assert(app, 'Could not find current application');
        this._app = app;
    }
    /**
     * Create a new component and add it to the entity. Use this to add functionality to the entity
     * like rendering a model, playing sounds and so on.
     *
     * @param {string} type - The name of the component to add. Valid strings are:
     *
     * - "anim" - see {@link AnimComponent}
     * - "animation" - see {@link AnimationComponent}
     * - "audiolistener" - see {@link AudioListenerComponent}
     * - "button" - see {@link ButtonComponent}
     * - "camera" - see {@link CameraComponent}
     * - "collision" - see {@link CollisionComponent}
     * - "element" - see {@link ElementComponent}
     * - "gsplat" - see {@link GSplatComponent}
     * - "layoutchild" - see {@link LayoutChildComponent}
     * - "layoutgroup" - see {@link LayoutGroupComponent}
     * - "light" - see {@link LightComponent}
     * - "model" - see {@link ModelComponent}
     * - "particlesystem" - see {@link ParticleSystemComponent}
     * - "render" - see {@link RenderComponent}
     * - "rigidbody" - see {@link RigidBodyComponent}
     * - "screen" - see {@link ScreenComponent}
     * - "script" - see {@link ScriptComponent}
     * - "scrollbar" - see {@link ScrollbarComponent}
     * - "scrollview" - see {@link ScrollViewComponent}
     * - "sound" - see {@link SoundComponent}
     * - "sprite" - see {@link SpriteComponent}
     *
     * @param {object} [data] - The initialization data for the specific component type. Refer to
     * each specific component's API reference page for details on valid values for this parameter.
     * @returns {Component|null} The new Component that was attached to the entity or null if there
     * was an error.
     * @example
     * const entity = new pc.Entity();
     *
     * // Add a light component with default properties
     * entity.addComponent("light");
     *
     * // Add a camera component with some specified properties
     * entity.addComponent("camera", {
     *     fov: 45,
     *     clearColor: new pc.Color(1, 0, 0)
     * });
     */ addComponent(type, data) {
        const system = this._app.systems[type];
        if (!system) {
            Debug.error(`addComponent: System '${type}' doesn't exist`);
            return null;
        }
        if (this.c[type]) {
            Debug.warn(`addComponent: Entity already has '${type}' component`);
            return null;
        }
        return system.addComponent(this, data);
    }
    /**
     * Remove a component from the Entity.
     *
     * @param {string} type - The name of the Component type.
     * @example
     * const entity = new pc.Entity();
     * entity.addComponent("light"); // add new light component
     *
     * entity.removeComponent("light"); // remove light component
     */ removeComponent(type) {
        const system = this._app.systems[type];
        if (!system) {
            Debug.error(`removeComponent: System '${type}' doesn't exist`);
            return;
        }
        if (!this.c[type]) {
            Debug.warn(`removeComponent: Entity doesn't have '${type}' component`);
            return;
        }
        system.removeComponent(this);
    }
    /**
     * Search the entity and all of its descendants for the first component of specified type.
     *
     * @param {string} type - The name of the component type to retrieve.
     * @returns {Component} A component of specified type, if the entity or any of its descendants
     * has one. Returns undefined otherwise.
     * @example
     * // Get the first found light component in the hierarchy tree that starts with this entity
     * const light = entity.findComponent("light");
     */ findComponent(type) {
        const entity = this.findOne((entity)=>entity.c?.[type]);
        return entity && entity.c[type];
    }
    /**
     * Search the entity and all of its descendants for all components of specified type.
     *
     * @param {string} type - The name of the component type to retrieve.
     * @returns {Component[]} All components of specified type in the entity or any of its
     * descendants. Returns empty array if none found.
     * @example
     * // Get all light components in the hierarchy tree that starts with this entity
     * const lights = entity.findComponents("light");
     */ findComponents(type) {
        return this.find((entity)=>entity.c?.[type]).map((entity)=>entity.c[type]);
    }
    /**
     * Search the entity and all of its descendants for the first script instance of specified type.
     *
     * @param {string|typeof ScriptType} nameOrType - The name or type of {@link ScriptType}.
     * @returns {ScriptType|undefined} A script instance of specified type, if the entity or any of
     * its descendants has one. Returns undefined otherwise.
     * @example
     * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity
     * const controller = entity.findScript("playerController");
     */ findScript(nameOrType) {
        const entity = this.findOne((node)=>node.c?.script?.has(nameOrType));
        return entity?.c.script.get(nameOrType);
    }
    /**
     * Search the entity and all of its descendants for all script instances of specified type.
     *
     * @param {string|typeof ScriptType} nameOrType - The name or type of {@link ScriptType}.
     * @returns {ScriptType[]} All script instances of specified type in the entity or any of its
     * descendants. Returns empty array if none found.
     * @example
     * // Get all "playerController" instances in the hierarchy tree that starts with this entity
     * const controllers = entity.findScripts("playerController");
     */ findScripts(nameOrType) {
        const entities = this.find((node)=>node.c?.script?.has(nameOrType));
        return entities.map((entity)=>entity.c.script.get(nameOrType));
    }
    /**
     * Get the GUID value for this Entity.
     *
     * @returns {string} The GUID of the Entity.
     * @ignore
     */ getGuid() {
        // if the guid hasn't been set yet then set it now before returning it
        if (!this._guid) {
            this.setGuid(guid.create());
        }
        return this._guid;
    }
    /**
     * Set the GUID value for this Entity. Note that it is unlikely that you should need to change
     * the GUID value of an Entity at run-time. Doing so will corrupt the graph this Entity is in.
     *
     * @param {string} guid - The GUID to assign to the Entity.
     * @ignore
     */ setGuid(guid) {
        // remove current guid from entityIndex
        const index = this._app._entityIndex;
        if (this._guid) {
            delete index[this._guid];
        }
        // add new guid to entityIndex
        this._guid = guid;
        index[this._guid] = this;
    }
    /**
     * @param {GraphNode} node - The node to update.
     * @param {boolean} enabled - Enable or disable the node.
     * @protected
     */ _notifyHierarchyStateChanged(node, enabled) {
        let enableFirst = false;
        if (node === this && _enableList.length === 0) {
            enableFirst = true;
        }
        node._beingEnabled = true;
        node._onHierarchyStateChanged(enabled);
        if (node._onHierarchyStatePostChanged) {
            _enableList.push(node);
        }
        const c = node._children;
        for(let i = 0, len = c.length; i < len; i++){
            if (c[i]._enabled) {
                this._notifyHierarchyStateChanged(c[i], enabled);
            }
        }
        node._beingEnabled = false;
        if (enableFirst) {
            // do not cache the length here, as enableList may be added to during loop
            for(let i = 0; i < _enableList.length; i++){
                _enableList[i]._onHierarchyStatePostChanged();
            }
            _enableList.length = 0;
        }
    }
    /**
     * @param {boolean} enabled - Enable or disable the node.
     * @protected
     */ _onHierarchyStateChanged(enabled) {
        super._onHierarchyStateChanged(enabled);
        const components = this._getSortedComponents();
        for(let i = 0; i < components.length; i++){
            const component = components[i];
            if (component.enabled) {
                if (enabled) {
                    component.onEnable();
                } else {
                    component.onDisable();
                }
            }
        }
        releaseTempArray(components);
    }
    /** @private */ _onHierarchyStatePostChanged() {
        // post enable all the components
        const components = this._getSortedComponents();
        for(let i = 0; i < components.length; i++){
            components[i].onPostStateChange();
        }
        releaseTempArray(components);
    }
    /**
     * Find a descendant of this entity with the GUID.
     *
     * @param {string} guid - The GUID to search for.
     * @returns {Entity|null} The entity with the matching GUID or null if no entity is found.
     */ findByGuid(guid) {
        if (this._guid === guid) return this;
        const e = this._app._entityIndex[guid];
        if (e && (e === this || e.isDescendantOf(this))) {
            return e;
        }
        return null;
    }
    /**
     * Destroy the entity and all of its descendants. First, all of the entity's components are
     * disabled and then removed. Then, the entity is removed from the hierarchy. This is then
     * repeated recursively for all descendants of the entity.
     *
     * The last thing the entity does is fire the `destroy` event.
     *
     * @example
     * const firstChild = this.entity.children[0];
     * firstChild.destroy(); // destroy child and all of its descendants
     */ destroy() {
        this._destroying = true;
        // Disable all enabled components first
        for(const name in this.c){
            this.c[name].enabled = false;
        }
        // Remove all components
        for(const name in this.c){
            this.c[name].system.removeComponent(this);
        }
        super.destroy();
        // remove from entity index
        if (this._guid) {
            delete this._app._entityIndex[this._guid];
        }
        this._destroying = false;
    }
    /**
     * Create a deep copy of the Entity. Duplicate the full Entity hierarchy, with all Components
     * and all descendants. Note, this Entity is not in the hierarchy and must be added manually.
     *
     * @returns {this} A new Entity which is a deep copy of the original.
     * @example
     * const e = this.entity.clone();
     *
     * // Add clone as a sibling to the original
     * this.entity.parent.addChild(e);
     */ clone() {
        const duplicatedIdsMap = {};
        const clone = this._cloneRecursively(duplicatedIdsMap);
        duplicatedIdsMap[this.getGuid()] = clone;
        resolveDuplicatedEntityReferenceProperties(this, this, clone, duplicatedIdsMap);
        return clone;
    }
    _getSortedComponents() {
        const components = this.c;
        const sortedArray = getTempArray();
        let needSort = 0;
        for(const type in components){
            if (components.hasOwnProperty(type)) {
                const component = components[type];
                needSort |= component.constructor.order !== 0;
                sortedArray.push(component);
            }
        }
        if (needSort && sortedArray.length > 1) {
            sortStaticOrder(sortedArray);
        }
        return sortedArray;
    }
    /**
     * @param {Object<string, Entity>} duplicatedIdsMap - A map of original entity GUIDs to cloned
     * entities.
     * @returns {this} A new Entity which is a deep copy of the original.
     * @private
     */ _cloneRecursively(duplicatedIdsMap) {
        /** @type {this} */ const clone = new this.constructor(undefined, this._app);
        super._cloneInternal(clone);
        for(const type in this.c){
            const component = this.c[type];
            component.system.cloneComponent(this, clone);
        }
        for(let i = 0; i < this._children.length; i++){
            const oldChild = this._children[i];
            if (oldChild instanceof Entity) {
                const newChild = oldChild._cloneRecursively(duplicatedIdsMap);
                clone.addChild(newChild);
                duplicatedIdsMap[oldChild.getGuid()] = newChild;
            }
        }
        return clone;
    }
}
/**
 * When an entity that has properties that contain references to other entities within its subtree
 * is duplicated, the expectation of the user is likely that those properties will be updated to
 * point to the corresponding entities within the newly-created duplicate subtree.
 *
 * To handle this, we need to search for properties that refer to entities within the old
 * duplicated structure, find their newly-cloned partners within the new structure, and update the
 * references accordingly. This function implements that requirement.
 *
 * @param {Entity} oldSubtreeRoot - The root of the duplicated entity subtree that is being
 * resolved.
 * @param {Entity} oldEntity - The entity within the old duplicated subtree that is being resolved.
 * @param {Entity} newEntity - The entity within the new duplicated subtree that is being resolved.
 * @param {Object<string, Entity>} duplicatedIdsMap - A map of original entity GUIDs to cloned
 * entities.
 * @private
 */ function resolveDuplicatedEntityReferenceProperties(oldSubtreeRoot, oldEntity, newEntity, duplicatedIdsMap) {
    if (oldEntity instanceof Entity) {
        const components = oldEntity.c;
        // Handle component properties
        for(const componentName in components){
            const component = components[componentName];
            const entityProperties = component.system.getPropertiesOfType('entity');
            for(let i = 0, len = entityProperties.length; i < len; i++){
                const propertyDescriptor = entityProperties[i];
                const propertyName = propertyDescriptor.name;
                const oldEntityReferenceId = component[propertyName];
                const entityIsWithinOldSubtree = !!oldSubtreeRoot.findByGuid(oldEntityReferenceId);
                if (entityIsWithinOldSubtree) {
                    const newEntityReferenceId = duplicatedIdsMap[oldEntityReferenceId].getGuid();
                    if (newEntityReferenceId) {
                        newEntity.c[componentName][propertyName] = newEntityReferenceId;
                    } else {
                        Debug.warn('Could not find corresponding entity id when resolving duplicated entity references');
                    }
                }
            }
        }
        // Handle entity script attributes
        if (components.script) {
            newEntity.script.resolveDuplicatedEntityReferenceProperties(components.script, duplicatedIdsMap);
        }
        // Handle entity render attributes
        if (components.render) {
            newEntity.render.resolveDuplicatedEntityReferenceProperties(components.render, duplicatedIdsMap);
        }
        // Handle entity button attributes
        if (components.button) {
            newEntity.button.resolveDuplicatedEntityReferenceProperties(components.button, duplicatedIdsMap);
        }
        // Handle entity scrollview attributes
        if (components.scrollview) {
            newEntity.scrollview.resolveDuplicatedEntityReferenceProperties(components.scrollview, duplicatedIdsMap);
        }
        // Handle entity scrollbar attributes
        if (components.scrollbar) {
            newEntity.scrollbar.resolveDuplicatedEntityReferenceProperties(components.scrollbar, duplicatedIdsMap);
        }
        // Handle entity anim attributes
        if (components.anim) {
            newEntity.anim.resolveDuplicatedEntityReferenceProperties(components.anim, duplicatedIdsMap);
        }
        // Recurse into children. Note that we continue to pass in the same `oldSubtreeRoot`, in
        // order to correctly handle cases where a child has an entity reference field that points
        // to a parent or other ancestor that is still within the duplicated subtree.
        const _old = oldEntity.children.filter((e)=>e instanceof Entity);
        const _new = newEntity.children.filter((e)=>e instanceof Entity);
        for(let i = 0, len = _old.length; i < len; i++){
            resolveDuplicatedEntityReferenceProperties(oldSubtreeRoot, _old[i], _new[i], duplicatedIdsMap);
        }
    }
}

/**
 * Item to be stored in the {@link SceneRegistry}.
 *
 * @category Graphics
 */ class SceneRegistryItem {
    /**
     * Creates a new SceneRegistryItem instance.
     *
     * @param {string} name - The name of the scene.
     * @param {string} url - The url of the scene file.
     */ constructor(name, url){
        /** @ignore */ this.data = null;
        /** @private */ this._loading = false;
        /** @private */ this._onLoadedCallbacks = [];
        this.name = name;
        this.url = url;
    }
    /**
     * Returns true if the scene data has loaded.
     *
     * @type {boolean}
     */ get loaded() {
        return !!this.data;
    }
    /**
     * Returns true if the scene data is still being loaded.
     *
     * @type {boolean}
     */ get loading() {
        return this._loading;
    }
}

/**
 * @import { AppBase } from './app-base.js'
 * @import { Entity } from './entity.js'
 */ /**
 * @callback LoadHierarchyCallback
 * Callback used by {@link SceneRegistry#loadSceneHierarchy}.
 * @param {string|null} err - The error message in the case where the loading or parsing fails.
 * @param {Entity} [entity] - The loaded root entity if no errors were encountered.
 * @returns {void}
 */ /**
 * @callback LoadSettingsCallback
 * Callback used by {@link SceneRegistry#loadSceneSettings}.
 * @param {string|null} err - The error message in the case where the loading or parsing fails.
 * @returns {void}
 */ /**
 * @callback ChangeSceneCallback
 * Callback used by {@link SceneRegistry#changeScene}.
 * @param {string|null} err - The error message in the case where the loading or parsing fails.
 * @param {Entity} [entity] - The loaded root entity if no errors were encountered.
 * @returns {void}
 */ /**
 * @callback LoadSceneCallback
 * Callback used by {@link SceneRegistry#loadScene}.
 * @param {string|null} err - The error message in the case where the loading or parsing fails.
 * @param {Entity} [entity] - The loaded root entity if no errors were encountered.
 * @returns {void}
 */ /**
 * @callback LoadSceneDataCallback
 * Callback used by {@link SceneRegistry#loadSceneData}.
 * @param {string|null} err - The error message in the case where the loading or parsing fails.
 * @param {SceneRegistryItem} [sceneItem] - The scene registry item if no errors were encountered.
 * @returns {void}
 */ /**
 * Container for storing and loading of scenes. An instance of the registry is created on the
 * {@link AppBase} object as {@link AppBase#scenes}.
 *
 * @category Graphics
 */ class SceneRegistry {
    /**
     * Create a new SceneRegistry instance.
     *
     * @param {AppBase} app - The application.
     */ constructor(app){
        /**
     * @type {SceneRegistryItem[]}
     * @private
     */ this._list = [];
        /** @private */ this._index = {};
        /** @private */ this._urlIndex = {};
        this._app = app;
    }
    /** @ignore */ destroy() {
        this._app = null;
    }
    /**
     * Return the list of scene.
     *
     * @returns {SceneRegistryItem[]} All items in the registry.
     */ list() {
        return this._list;
    }
    /**
     * Add a new item to the scene registry.
     *
     * @param {string} name - The name of the scene.
     * @param {string} url - The url of the scene file.
     * @returns {boolean} Returns true if the scene was successfully added to the registry, false otherwise.
     */ add(name, url) {
        if (this._index.hasOwnProperty(name)) {
            Debug.warn(`pc.SceneRegistry: trying to add more than one scene called: ${name}`);
            return false;
        }
        const item = new SceneRegistryItem(name, url);
        const i = this._list.push(item);
        this._index[item.name] = i - 1;
        this._urlIndex[item.url] = i - 1;
        return true;
    }
    /**
     * Find a Scene by name and return the {@link SceneRegistryItem}.
     *
     * @param {string} name - The name of the scene.
     * @returns {SceneRegistryItem|null} The stored data about a scene or null if no scene with
     * that name exists.
     */ find(name) {
        if (this._index.hasOwnProperty(name)) {
            return this._list[this._index[name]];
        }
        return null;
    }
    /**
     * Find a scene by the URL and return the {@link SceneRegistryItem}.
     *
     * @param {string} url - The URL to search by.
     * @returns {SceneRegistryItem|null} The stored data about a scene or null if no scene with
     * that URL exists.
     */ findByUrl(url) {
        if (this._urlIndex.hasOwnProperty(url)) {
            return this._list[this._urlIndex[url]];
        }
        return null;
    }
    /**
     * Remove an item from the scene registry.
     *
     * @param {string} name - The name of the scene.
     */ remove(name) {
        if (this._index.hasOwnProperty(name)) {
            const idx = this._index[name];
            let item = this._list[idx];
            delete this._urlIndex[item.url];
            // remove from index
            delete this._index[name];
            // remove from list
            this._list.splice(idx, 1);
            // refresh index
            for(let i = 0; i < this._list.length; i++){
                item = this._list[i];
                this._index[item.name] = i;
                this._urlIndex[item.url] = i;
            }
        }
    }
    /**
     * Private function to load scene data with the option to cache. This allows us to retain
     * expected behavior of loadSceneSettings and loadSceneHierarchy where they don't store loaded
     * data which may be undesired behavior with projects that have many scenes.
     *
     * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with
     * {@link SceneRegistry#find}, URL of the scene file (e.g."scene_id.json") or name of the scene.
     * @param {boolean} storeInCache - Whether to store the loaded data in the scene item.
     * @param {LoadSceneDataCallback} callback - The function to call after loading,
     * passed (err, sceneItem) where err is null if no errors occurred.
     * @private
     */ _loadSceneData(sceneItem, storeInCache, callback) {
        const app = this._app;
        // If it's a sceneItem, we want to be able to cache the data that is loaded so we don't do
        // a subsequent http requests on the same scene later
        // If it's just a URL or scene name then attempt to find the scene item in the registry
        // else create a temp SceneRegistryItem to use for this function as the scene may not have
        // been added to the registry
        let url = sceneItem;
        if (typeof sceneItem === 'string') {
            sceneItem = this.findByUrl(url) || this.find(url) || new SceneRegistryItem('Untitled', url);
        }
        url = sceneItem.url;
        if (!url) {
            callback('Cannot find scene to load');
            return;
        }
        // If we have the data already loaded, no need to do another HTTP request
        if (sceneItem.loaded) {
            callback(null, sceneItem);
            return;
        }
        // include asset prefix if present
        if (app.assets && app.assets.prefix && !ABSOLUTE_URL.test(url)) {
            url = path$1.join(app.assets.prefix, url);
        }
        sceneItem._onLoadedCallbacks.push(callback);
        if (!sceneItem._loading) {
            // Because we need to load scripts before we instance the hierarchy (i.e. before we
            // create script components), split loading into load and open
            const handler = app.loader.getHandler('hierarchy');
            handler.load(url, (err, data)=>{
                sceneItem.data = data;
                sceneItem._loading = false;
                for(let i = 0; i < sceneItem._onLoadedCallbacks.length; i++){
                    sceneItem._onLoadedCallbacks[i](err, sceneItem);
                }
                // Remove the data if it's not been requested to store in cache
                if (!storeInCache) {
                    sceneItem.data = null;
                }
                sceneItem._onLoadedCallbacks.length = 0;
            });
        }
        sceneItem._loading = true;
    }
    /**
     * Loads and stores the scene data to reduce the number of the network requests when the same
     * scenes are loaded multiple times. Can also be used to load data before calling
     * {@link SceneRegistry#loadSceneHierarchy} and {@link SceneRegistry#loadSceneSettings} to make
     * scene loading quicker for the user.
     *
     * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with
     * {@link SceneRegistry#find}, URL of the scene file (e.g."scene_id.json") or name of the scene.
     * @param {LoadSceneDataCallback} callback - The function to call after loading,
     * passed (err, sceneItem) where err is null if no errors occurred.
     * @example
     * const sceneItem = app.scenes.find("Scene Name");
     * app.scenes.loadSceneData(sceneItem, (err, sceneItem) => {
     *     if (err) {
     *         // error
     *     }
     * });
     */ loadSceneData(sceneItem, callback) {
        this._loadSceneData(sceneItem, true, callback);
    }
    /**
     * Unloads scene data that has been loaded previously using {@link SceneRegistry#loadSceneData}.
     *
     * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with
     * {@link SceneRegistry#find} or URL of the scene file. Usually this will be "scene_id.json".
     * @example
     * const sceneItem = app.scenes.find("Scene Name");
     * app.scenes.unloadSceneData(sceneItem);
     */ unloadSceneData(sceneItem) {
        if (typeof sceneItem === 'string') {
            sceneItem = this.findByUrl(sceneItem);
        }
        if (sceneItem) {
            sceneItem.data = null;
        }
    }
    _loadSceneHierarchy(sceneItem, onBeforeAddHierarchy, callback) {
        this._loadSceneData(sceneItem, false, (err, sceneItem)=>{
            if (err) {
                if (callback) {
                    callback(err);
                }
                return;
            }
            if (onBeforeAddHierarchy) {
                onBeforeAddHierarchy(sceneItem);
            }
            const app = this._app;
            // called after scripts are preloaded
            const _loaded = ()=>{
                // Because we need to load scripts before we instance the hierarchy (i.e. before we create script components)
                // Split loading into load and open
                const handler = app.loader.getHandler('hierarchy');
                app.systems.script.preloading = true;
                const entity = handler.open(sceneItem.url, sceneItem.data);
                app.systems.script.preloading = false;
                // clear from cache because this data is modified by entity operations (e.g. destroy)
                app.loader.clearCache(sceneItem.url, 'hierarchy');
                // add to hierarchy
                app.root.addChild(entity);
                // initialize components
                app.systems.fire('initialize', entity);
                app.systems.fire('postInitialize', entity);
                app.systems.fire('postPostInitialize', entity);
                if (callback) callback(null, entity);
            };
            // load priority and referenced scripts before opening scene
            app._preloadScripts(sceneItem.data, _loaded);
        });
    }
    /**
     * Load a scene file, create and initialize the Entity hierarchy and add the hierarchy to the
     * application root Entity.
     *
     * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with
     * {@link SceneRegistry#find}, URL of the scene file (e.g."scene_id.json") or name of the scene.
     * @param {LoadHierarchyCallback} callback - The function to call after loading,
     * passed (err, entity) where err is null if no errors occurred.
     * @example
     * const sceneItem = app.scenes.find("Scene Name");
     * app.scenes.loadSceneHierarchy(sceneItem, (err, entity) => {
     *     if (!err) {
     *         const e = app.root.find("My New Entity");
     *     } else {
     *         // error
     *     }
     * });
     */ loadSceneHierarchy(sceneItem, callback) {
        this._loadSceneHierarchy(sceneItem, null, callback);
    }
    /**
     * Load a scene file and apply the scene settings to the current scene.
     *
     * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with
     * {@link SceneRegistry#find}, URL of the scene file (e.g."scene_id.json") or name of the scene.
     * @param {LoadSettingsCallback} callback - The function called after the settings
     * are applied. Passed (err) where err is null if no error occurred.
     * @example
     * const sceneItem = app.scenes.find("Scene Name");
     * app.scenes.loadSceneSettings(sceneItem, (err) => {
     *     if (!err) {
     *         // success
     *     } else {
     *         // error
     *     }
     * });
     */ loadSceneSettings(sceneItem, callback) {
        this._loadSceneData(sceneItem, false, (err, sceneItem)=>{
            if (!err) {
                this._app.applySceneSettings(sceneItem.data.settings);
                if (callback) {
                    callback(null);
                }
            } else {
                if (callback) {
                    callback(err);
                }
            }
        });
    }
    /**
     * Change to a new scene. Calling this function will load the scene data, delete all
     * entities and graph nodes under `app.root` and load the scene settings and hierarchy.
     *
     * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with
     * {@link SceneRegistry#find}, URL of the scene file (e.g."scene_id.json") or name of the scene.
     * @param {ChangeSceneCallback} [callback] - The function to call after loading,
     * passed (err, entity) where err is null if no errors occurred.
     * @example
     * app.scenes.changeScene("Scene Name", (err, entity) => {
     *     if (!err) {
     *         // success
     *     } else {
     *         // error
     *     }
     * });
     */ changeScene(sceneItem, callback) {
        const app = this._app;
        const onBeforeAddHierarchy = (sceneItem)=>{
            // Destroy all nodes on the app.root
            const { children } = app.root;
            while(children.length){
                children[0].destroy();
            }
            app.applySceneSettings(sceneItem.data.settings);
        };
        this._loadSceneHierarchy(sceneItem, onBeforeAddHierarchy, callback);
    }
    /**
     * Load the scene hierarchy and scene settings. This is an internal method used by the
     * {@link AppBase}.
     *
     * @param {string} url - The URL of the scene file.
     * @param {LoadSceneCallback} callback - The function called after the settings are
     * applied. Passed (err, scene) where err is null if no error occurred and scene is the
     * {@link Scene}.
     */ loadScene(url, callback) {
        const app = this._app;
        const handler = app.loader.getHandler('scene');
        // include asset prefix if present
        if (app.assets && app.assets.prefix && !ABSOLUTE_URL.test(url)) {
            url = path$1.join(app.assets.prefix, url);
        }
        handler.load(url, (err, data)=>{
            if (!err) {
                const _loaded = ()=>{
                    // parse and create scene
                    app.systems.script.preloading = true;
                    const scene = handler.open(url, data);
                    // Cache the data as we are loading via URL only
                    const sceneItem = this.findByUrl(url);
                    if (sceneItem && !sceneItem.loaded) {
                        sceneItem.data = data;
                    }
                    app.systems.script.preloading = false;
                    // clear scene from cache because we'll destroy it when we load another one
                    // so data will be invalid
                    app.loader.clearCache(url, 'scene');
                    app.loader.patch({
                        resource: scene,
                        type: 'scene'
                    }, app.assets);
                    app.root.addChild(scene.root);
                    // Initialize pack settings
                    if (app.systems.rigidbody && typeof Ammo !== 'undefined') {
                        app.systems.rigidbody.gravity.set(scene._gravity.x, scene._gravity.y, scene._gravity.z);
                    }
                    if (callback) {
                        callback(null, scene);
                    }
                };
                // preload scripts before opening scene
                app._preloadScripts(data, _loaded);
            } else {
                if (callback) {
                    callback(err);
                }
            }
        });
    }
}

/**
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 */ /**
 * Records performance-related statistics related to the application.
 */ class ApplicationStats {
    /**
     * Create a new ApplicationStats instance.
     *
     * @param {GraphicsDevice} device - The graphics device.
     */ constructor(device){
        this.frame = {
            fps: 0,
            ms: 0,
            dt: 0,
            updateStart: 0,
            updateTime: 0,
            renderStart: 0,
            renderTime: 0,
            physicsStart: 0,
            physicsTime: 0,
            cullTime: 0,
            sortTime: 0,
            skinTime: 0,
            morphTime: 0,
            instancingTime: 0,
            triangles: 0,
            otherPrimitives: 0,
            shaders: 0,
            materials: 0,
            cameras: 0,
            shadowMapUpdates: 0,
            shadowMapTime: 0,
            depthMapTime: 0,
            forwardTime: 0,
            lightClustersTime: 0,
            lightClusters: 0,
            _timeToCountFrames: 0,
            _fpsAccum: 0
        };
        this.drawCalls = {
            forward: 0,
            depth: 0,
            shadow: 0,
            immediate: 0,
            misc: 0,
            total: 0,
            // Some of forward/depth/shadow/misc draw calls:
            skinned: 0,
            instanced: 0,
            removedByInstancing: 0 // deprecated
        };
        this.misc = {
            renderTargetCreationTime: 0
        };
        this.particles = {
            updatesPerFrame: 0,
            _updatesPerFrame: 0,
            frameTime: 0,
            _frameTime: 0
        };
        this.shaders = device._shaderStats;
        this.vram = device._vram;
        Object.defineProperty(this.vram, 'totalUsed', {
            get: function() {
                return this.tex + this.vb + this.ib;
            }
        });
        Object.defineProperty(this.vram, 'geom', {
            get: function() {
                return this.vb + this.ib;
            }
        });
    }
    get scene() {
        return getApplication().scene._stats;
    }
    get lightmapper() {
        return getApplication().lightmapper?.stats;
    }
    get batcher() {
        const batcher = getApplication()._batcher;
        return batcher ? batcher._stats : null;
    }
}

var alphaTestPS$1 = /* glsl */ `
uniform float alpha_ref;

void alphaTest(float a) {
    if (a < alpha_ref) discard;
}
`;

var ambientPS$1 = /* glsl */ `

#ifdef LIT_AMBIENT_SOURCE == AMBIENTSH
    uniform vec3 ambientSH[9];
#endif

#if LIT_AMBIENT_SOURCE == ENVALATLAS
    #include "envAtlasPS"

    #ifndef ENV_ATLAS
    #define ENV_ATLAS
        uniform sampler2D texture_envAtlas;
    #endif
#endif

void addAmbient(vec3 worldNormal) {
    #ifdef LIT_AMBIENT_SOURCE == AMBIENTSH

        vec3 n = cubeMapRotate(worldNormal);
        vec3 color =
            ambientSH[0] +
            ambientSH[1] * n.x +
            ambientSH[2] * n.y +
            ambientSH[3] * n.z +
            ambientSH[4] * n.x * n.z +
            ambientSH[5] * n.z * n.y +
            ambientSH[6] * n.y * n.x +
            ambientSH[7] * (3.0 * n.z * n.z - 1.0) +
            ambientSH[8] * (n.x * n.x - n.y * n.y);

        dDiffuseLight += processEnvironment(max(color, vec3(0.0)));

    #endif

    #if LIT_AMBIENT_SOURCE == ENVALATLAS

        vec3 dir = normalize(cubeMapRotate(worldNormal) * vec3(-1.0, 1.0, 1.0));
        vec2 uv = mapUv(toSphericalUv(dir), vec4(128.0, 256.0 + 128.0, 64.0, 32.0) / atlasSize);

        vec4 raw = texture2D(texture_envAtlas, uv);
        vec3 linear = {ambientDecode}(raw);
        dDiffuseLight += processEnvironment(linear);

    #endif

    #if LIT_AMBIENT_SOURCE == CONSTANT

        dDiffuseLight += light_globalAmbient;

    #endif
}
`;

var anisotropyPS$1 = /* glsl */ `

#ifdef LIT_GGX_SPECULAR
    uniform float material_anisotropyIntensity;
    uniform vec2 material_anisotropyRotation;
#endif

void getAnisotropy() {
    dAnisotropy = 0.0;
    dAnisotropyRotation = vec2(1.0, 0.0);

#ifdef LIT_GGX_SPECULAR
    dAnisotropy = material_anisotropyIntensity;
    dAnisotropyRotation = material_anisotropyRotation;
#endif

    #ifdef STD_ANISOTROPY_TEXTURE
    vec3 anisotropyTex = texture2DBias({STD_ANISOTROPY_TEXTURE_NAME}, {STD_ANISOTROPY_TEXTURE_UV}, textureBias).rgb;
    dAnisotropy *= anisotropyTex.b;

    vec2 anisotropyRotationFromTex = anisotropyTex.rg * 2.0 - vec2(1.0);
    mat2 rotationMatrix = mat2(dAnisotropyRotation.x, dAnisotropyRotation.y, -dAnisotropyRotation.y, dAnisotropyRotation.x);
    dAnisotropyRotation = rotationMatrix * anisotropyRotationFromTex;
    #endif
    
    dAnisotropy = clamp(dAnisotropy, 0.0, 1.0);
}
`;

var aoPS$1 = /* glsl */ `

#if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)
    uniform float material_aoIntensity;
#endif

#ifdef STD_AODETAIL_TEXTURE
    #include "detailModesPS"
#endif

void getAO() {
    dAo = 1.0;

    #ifdef STD_AO_TEXTURE
        float aoBase = texture2DBias({STD_AO_TEXTURE_NAME}, {STD_AO_TEXTURE_UV}, textureBias).{STD_AO_TEXTURE_CHANNEL};

        #ifdef STD_AODETAIL_TEXTURE
            float aoDetail = texture2DBias({STD_AODETAIL_TEXTURE_NAME}, {STD_AODETAIL_TEXTURE_UV}, textureBias).{STD_AODETAIL_TEXTURE_CHANNEL};
            aoBase = detailMode_{STD_AODETAIL_DETAILMODE}(vec3(aoBase), vec3(aoDetail)).r;
        #endif

        dAo *= aoBase;
    #endif

    #ifdef STD_AO_VERTEX
        dAo *= saturate(vVertexColor.{STD_AO_VERTEX_CHANNEL});
    #endif

    #if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)
        dAo = mix(1.0, dAo, material_aoIntensity);
    #endif
}
`;

var aoDiffuseOccPS$1 = /* glsl */ `
void occludeDiffuse(float ao) {
    dDiffuseLight *= ao;
}
`;

var aoSpecOccPS$1 = /* glsl */ `

#if LIT_OCCLUDE_SPECULAR != NONE
    #ifdef LIT_OCCLUDE_SPECULAR_FLOAT
        uniform float material_occludeSpecularIntensity;
    #endif
#endif

void occludeSpecular(float gloss, float ao, vec3 worldNormal, vec3 viewDir) {

    #if LIT_OCCLUDE_SPECULAR == AO
        #ifdef LIT_OCCLUDE_SPECULAR_FLOAT
            float specOcc = mix(1.0, ao, material_occludeSpecularIntensity);
        #else
            float specOcc = ao;
        #endif
    #endif

    #if LIT_OCCLUDE_SPECULAR == GLOSSDEPENDENT

        // approximated specular occlusion from AO
        // http://research.tri-ace.com/Data/cedec2011_RealtimePBR_Implementation_e.pptx
        float specPow = exp2(gloss * 11.0);
        float specOcc = saturate(pow(dot(worldNormal, viewDir) + ao, 0.01 * specPow) - 1.0 + ao);

        #ifdef LIT_OCCLUDE_SPECULAR_FLOAT
            specOcc = mix(1.0, specOcc, material_occludeSpecularIntensity);
        #endif
    #endif

    #if LIT_OCCLUDE_SPECULAR != NONE
        dSpecularLight *= specOcc;
        dReflection *= specOcc;

        #ifdef LIT_SHEEN
            sSpecularLight *= specOcc;
            sReflection *= specOcc;
        #endif
    #endif
}
`;

var bakeDirLmEndPS$1 = /* glsl */ `
    vec4 dirLm = texture2D(texture_dirLightMap, vUv1);

    if (bakeDir > 0.5) {
        if (dAtten > 0.00001) {
            dirLm.xyz = dirLm.xyz * 2.0 - vec3(1.0);
            dAtten = saturate(dAtten);
            gl_FragColor.rgb = normalize(dLightDirNormW.xyz*dAtten + dirLm.xyz*dirLm.w) * 0.5 + vec3(0.5);
            gl_FragColor.a = dirLm.w + dAtten;
            gl_FragColor.a = max(gl_FragColor.a, 1.0 / 255.0);
        } else {
            gl_FragColor = dirLm;
        }
    } else {
        gl_FragColor.rgb = dirLm.xyz;
        gl_FragColor.a = max(dirLm.w, dAtten > 0.00001 ? (1.0/255.0) : 0.0);
    }
`;

var bakeLmEndPS$1 = /* glsl */ `

#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT
    // diffuse light stores accumulated AO, apply contrast and brightness to it
    // and multiply ambient light color by the AO
    dDiffuseLight = ((dDiffuseLight - 0.5) * max(ambientBakeOcclusionContrast + 1.0, 0.0)) + 0.5;
    dDiffuseLight += vec3(ambientBakeOcclusionBrightness);
    dDiffuseLight = saturate(dDiffuseLight);
    dDiffuseLight *= dAmbientLight;
#endif

#ifdef LIGHTMAP_RGBM
    // encode to RGBM
    gl_FragColor.rgb = dDiffuseLight;
    gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(0.5));
    gl_FragColor.rgb /= 8.0;
    gl_FragColor.a = clamp( max( max( gl_FragColor.r, gl_FragColor.g ), max( gl_FragColor.b, 1.0 / 255.0 ) ), 0.0,1.0 );
    gl_FragColor.a = ceil(gl_FragColor.a * 255.0) / 255.0;
    gl_FragColor.rgb /= gl_FragColor.a;
#else
    gl_FragColor = vec4(dDiffuseLight, 1.0);
#endif
`;

var basePS$1 = /* glsl */ `
uniform vec3 view_position;

uniform vec3 light_globalAmbient;

float square(float x) {
    return x*x;
}

float saturate(float x) {
    return clamp(x, 0.0, 1.0);
}

vec3 saturate(vec3 x) {
    return clamp(x, vec3(0.0), vec3(1.0));
}
`;

var baseNineSlicedPS$1 = /* glsl */ `
#define NINESLICED

varying vec2 vMask;
varying vec2 vTiledUv;

uniform mediump vec4 innerOffset;
uniform mediump vec2 outerScale;
uniform mediump vec4 atlasRect;

vec2 nineSlicedUv;
`;

var baseNineSlicedTiledPS$1 = /* glsl */ `
#define NINESLICED
#define NINESLICETILED

varying vec2 vMask;
varying vec2 vTiledUv;

uniform mediump vec4 innerOffset;
uniform mediump vec2 outerScale;
uniform mediump vec4 atlasRect;

vec2 nineSlicedUv;
`;

// procedural Bayer matrix, based on: https://www.shadertoy.com/view/Mlt3z8
var bayerPS$1 = /* glsl */ `
// 2x2 bayer matrix [1 2][3 0], p in [0,1]
float bayer2(vec2 p) {
    return mod(2.0 * p.y + p.x + 1.0, 4.0);
}

// 4x4 matrix, p - pixel coordinate
float bayer4(vec2 p) {
    vec2 p1 = mod(p, 2.0);
    vec2 p2 = floor(0.5 * mod(p, 4.0));
    return 4.0 * bayer2(p1) + bayer2(p2);
}

// 8x8 matrix, p - pixel coordinate
float bayer8(vec2 p) {
    vec2 p1 = mod(p, 2.0);
    vec2 p2 = floor(0.5 * mod(p, 4.0));
    vec2 p4 = floor(0.25 * mod(p, 8.0));
    return 4.0 * (4.0 * bayer2(p1) + bayer2(p2)) + bayer2(p4);
}
`;

var blurVSMPS$1 = /* glsl */ `
varying vec2 vUv0;

uniform sampler2D source;
uniform vec2 pixelOffset;

#ifdef GAUSS
    uniform float weight[{SAMPLES}];
#endif

void main(void) {
    vec3 moments = vec3(0.0);
    vec2 uv = vUv0 - pixelOffset * (float({SAMPLES}) * 0.5);
    for (int i = 0; i < {SAMPLES}; i++) {
        vec4 c = texture2D(source, uv + pixelOffset * float(i));

        #ifdef GAUSS
            moments += c.xyz * weight[i];
        #else
            moments += c.xyz;
        #endif
    }

    #ifndef GAUSS
        moments *= 1.0 / float({SAMPLES});
    #endif

    gl_FragColor = vec4(moments.x, moments.y, moments.z, 1.0);
}
`;

var clearCoatPS$1 = /* glsl */ `
uniform float material_clearCoat;

void getClearCoat() {
    ccSpecularity = material_clearCoat;

    #ifdef STD_CLEARCOAT_TEXTURE
    ccSpecularity *= texture2DBias({STD_CLEARCOAT_TEXTURE_NAME}, {STD_CLEARCOAT_TEXTURE_UV}, textureBias).{STD_CLEARCOAT_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_CLEARCOAT_VERTEX
    ccSpecularity *= saturate(vVertexColor.{STD_CLEARCOAT_VERTEX_CHANNEL});
    #endif
}
`;

var clearCoatGlossPS$1 = /* glsl */ `
uniform float material_clearCoatGloss;

void getClearCoatGlossiness() {
    ccGlossiness = material_clearCoatGloss;

    #ifdef STD_CLEARCOATGLOSS_TEXTURE
    ccGlossiness *= texture2DBias({STD_CLEARCOATGLOSS_TEXTURE_NAME}, {STD_CLEARCOATGLOSS_TEXTURE_UV}, textureBias).{STD_CLEARCOATGLOSS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_CLEARCOATGLOSS_VERTEX
    ccGlossiness *= saturate(vVertexColor.{STD_CLEARCOATGLOSS_VERTEX_CHANNEL});
    #endif

    #ifdef STD_CLEARCOATGLOSS_INVERT
    ccGlossiness = 1.0 - ccGlossiness;
    #endif

    ccGlossiness += 0.0000001;
}
`;

var clearCoatNormalPS$1 = /* glsl */ `
#ifdef STD_CLEARCOATNORMAL_TEXTURE
uniform float material_clearCoatBumpiness;
#endif

void getClearCoatNormal() {
#ifdef STD_CLEARCOATNORMAL_TEXTURE
    vec3 normalMap = {STD_CLEARCOATNORMAL_TEXTURE_DECODE}(texture2DBias({STD_CLEARCOATNORMAL_TEXTURE_NAME}, {STD_CLEARCOATNORMAL_TEXTURE_UV}, textureBias));
    normalMap = mix(vec3(0.0, 0.0, 1.0), normalMap, material_clearCoatBumpiness);
    ccNormalW = normalize(dTBN * normalMap);
#else
    ccNormalW = dVertexNormalW;
#endif
}
`;

var clusteredLightUtilsPS$1 = /* glsl */ `
// Converts unnormalized direction vector to a cubemap face index [0..5] and uv coordinates within the face in [0..1] range.
// Additionally offset to a tile in atlas within 3x3 subdivision is provided
vec2 getCubemapFaceCoordinates(const vec3 dir, out float faceIndex, out vec2 tileOffset)
{
    vec3 vAbs = abs(dir);
    float ma;
    vec2 uv;
    if (vAbs.z >= vAbs.x && vAbs.z >= vAbs.y) {   // front / back

        faceIndex = dir.z < 0.0 ? 5.0 : 4.0;
        ma = 0.5 / vAbs.z;
        uv = vec2(dir.z < 0.0 ? -dir.x : dir.x, -dir.y);
        tileOffset.x = 2.0;
        tileOffset.y = dir.z < 0.0 ? 1.0 : 0.0;

    } else if(vAbs.y >= vAbs.x) {  // top index 2, bottom index 3

        faceIndex = dir.y < 0.0 ? 3.0 : 2.0;
        ma = 0.5 / vAbs.y;
        uv = vec2(dir.x, dir.y < 0.0 ? -dir.z : dir.z);
        tileOffset.x = 1.0;
        tileOffset.y = dir.y < 0.0 ? 1.0 : 0.0;

    } else {    // left / right

        faceIndex = dir.x < 0.0 ? 1.0 : 0.0;
        ma = 0.5 / vAbs.x;
        uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y);
        tileOffset.x = 0.0;
        tileOffset.y = dir.x < 0.0 ? 1.0 : 0.0;

    }
    return uv * ma + 0.5;
}

// converts unnormalized direction vector to a texture coordinate for a cubemap face stored within texture atlas described by the viewport
vec2 getCubemapAtlasCoordinates(const vec3 omniAtlasViewport, float shadowEdgePixels, float shadowTextureResolution, const vec3 dir) {

    float faceIndex;
    vec2 tileOffset;
    vec2 uv = getCubemapFaceCoordinates(dir, faceIndex, tileOffset);

    // move uv coordinates inwards inside to compensate for larger fov when rendering shadow into atlas
    float atlasFaceSize = omniAtlasViewport.z;
    float tileSize = shadowTextureResolution * atlasFaceSize;
    float offset = shadowEdgePixels / tileSize;
    uv = uv * vec2(1.0 - offset * 2.0) + vec2(offset * 1.0);

    // scale uv coordinates to cube face area within the viewport
    uv *= atlasFaceSize;

    // offset into face of the atlas (3x3 grid)
    uv += tileOffset * atlasFaceSize;

    // offset into the atlas viewport
    uv += omniAtlasViewport.xy;

    return uv;
}
`;

var clusteredLightCookiesPS$1 = /* glsl */ `
vec3 _getCookieClustered(TEXTURE_ACCEPT(tex), vec2 uv, float intensity, vec4 cookieChannel) {
    vec4 pixel = mix(vec4(1.0), texture2DLod(tex, uv, 0.0), intensity);
    bool isRgb = dot(cookieChannel.rgb, vec3(1.0)) == 3.0;
    return isRgb ? pixel.rgb : vec3(dot(pixel, cookieChannel));
}

// getCookie2D for clustered lighting including channel selector
vec3 getCookie2DClustered(TEXTURE_ACCEPT(tex), mat4 transform, vec3 worldPosition, float intensity, vec4 cookieChannel) {
    vec4 projPos = transform * vec4(worldPosition, 1.0);
    return _getCookieClustered(TEXTURE_PASS(tex), projPos.xy / projPos.w, intensity, cookieChannel);
}

// getCookie for clustered omni light with the cookie texture being stored in the cookie atlas
vec3 getCookieCubeClustered(TEXTURE_ACCEPT(tex), vec3 dir, float intensity, vec4 cookieChannel, float shadowTextureResolution, float shadowEdgePixels, vec3 omniAtlasViewport) {
    vec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, dir);
    return _getCookieClustered(TEXTURE_PASS(tex), uv, intensity, cookieChannel);
}
`;

// Clustered Omni Sampling using atlas
var clusteredLightShadowsPS$1 = /* glsl */ `

vec3 _getShadowCoordPerspZbuffer(mat4 shadowMatrix, vec4 shadowParams, vec3 wPos) {
    vec4 projPos = shadowMatrix * vec4(wPos, 1.0);
    projPos.xyz /= projPos.w;
    return projPos.xyz;
    // depth bias is already applied on render
}

vec3 getShadowCoordPerspZbufferNormalOffset(mat4 shadowMatrix, vec4 shadowParams, vec3 normal) {
    vec3 wPos = vPositionW + normal * shadowParams.y;
    return _getShadowCoordPerspZbuffer(shadowMatrix, shadowParams, wPos);
}

vec3 normalOffsetPointShadow(vec4 shadowParams, vec3 lightPos, vec3 lightDir, vec3 lightDirNorm, vec3 normal) {
    float distScale = length(lightDir);
    vec3 wPos = vPositionW + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale; //0.02
    vec3 dir = wPos - lightPos;
    return dir;
}

#if defined(CLUSTER_SHADOW_TYPE_PCF1)

float getShadowOmniClusteredPCF1(SHADOWMAP_ACCEPT(shadowMap), vec4 shadowParams, vec3 omniAtlasViewport, float shadowEdgePixels, vec3 lightDir) {

    float shadowTextureResolution = shadowParams.x;
    vec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);

    float shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;
    return textureShadow(shadowMap, vec3(uv, shadowZ));
}

#endif

#if defined(CLUSTER_SHADOW_TYPE_PCF3)

float getShadowOmniClusteredPCF3(SHADOWMAP_ACCEPT(shadowMap), vec4 shadowParams, vec3 omniAtlasViewport, float shadowEdgePixels, vec3 lightDir) {

    float shadowTextureResolution = shadowParams.x;
    vec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);

    float shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;
    vec3 shadowCoord = vec3(uv, shadowZ);
    return getShadowPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);
}

#endif

#if defined(CLUSTER_SHADOW_TYPE_PCF5)

float getShadowOmniClusteredPCF5(SHADOWMAP_ACCEPT(shadowMap), vec4 shadowParams, vec3 omniAtlasViewport, float shadowEdgePixels, vec3 lightDir) {

    float shadowTextureResolution = shadowParams.x;
    vec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);

    float shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;
    vec3 shadowCoord = vec3(uv, shadowZ);
    return getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);
}

#endif

// Clustered Spot Sampling using atlas

#if defined(CLUSTER_SHADOW_TYPE_PCF1)

float getShadowSpotClusteredPCF1(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return textureShadow(shadowMap, shadowCoord);
}

#endif

#if defined(CLUSTER_SHADOW_TYPE_PCF3)

float getShadowSpotClusteredPCF3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return getShadowSpotPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);
}

#endif

#if defined(CLUSTER_SHADOW_TYPE_PCF5)

float getShadowSpotClusteredPCF5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);
}
#endif
`;

var clusteredLightPS$1 = /* glsl */ `

#include "lightBufferDefinesPS"

// include this before shadow / cookie code
#include "clusteredLightUtilsPS"

#ifdef CLUSTER_COOKIES
    #include "clusteredLightCookiesPS"
#endif

#ifdef CLUSTER_SHADOWS
    #include "clusteredLightShadowsPS"
#endif

uniform highp sampler2D clusterWorldTexture;
uniform highp sampler2D lightsTexture;

#ifdef CLUSTER_SHADOWS
    // TODO: when VSM shadow is supported, it needs to use sampler2D in webgl2
    uniform sampler2DShadow shadowAtlasTexture;
#endif

#ifdef CLUSTER_COOKIES
    uniform sampler2D cookieAtlasTexture;
#endif

uniform int clusterMaxCells;

// 1.0 if clustered lighting can be skipped (0 lights in the clusters)
uniform float clusterSkip;

uniform vec3 clusterCellsCountByBoundsSize;
uniform vec3 clusterTextureSize;
uniform vec3 clusterBoundsMin;
uniform vec3 clusterBoundsDelta;
uniform vec3 clusterCellsDot;
uniform vec3 clusterCellsMax;
uniform vec2 shadowAtlasParams;

// structure storing light properties of a clustered light
// it's sorted to have all vectors aligned to 4 floats to limit padding
struct ClusterLightData {

    // 32bit of flags
    uint flags;

    // area light sizes / orientation
    vec3 halfWidth;

    bool isSpot;

    // area light sizes / orientation
    vec3 halfHeight;

    // light index
    int lightIndex;

    // world space position
    vec3 position;

    // area light shape
    uint shape;

    // world space direction (spot light only)
    vec3 direction;

    // light follow mode
    bool falloffModeLinear;

    // color
    vec3 color;

    // 0.0 if the light doesn't cast shadows
    float shadowIntensity;

    // atlas viewport for omni light shadow and cookie (.xy is offset to the viewport slot, .z is size of the face in the atlas)
    vec3 omniAtlasViewport;

    // range of the light
    float range;

    // channel mask - one of the channels has 1, the others are 0
    vec4 cookieChannelMask;

    // compressed biases, two haf-floats stored in a float
    float biasesData;

    // shadow bias values
    float shadowBias;
    float shadowNormalBias;

    // compressed angles, two haf-floats stored in a float
    float anglesData;

    // spot light inner and outer angle cosine
    float innerConeAngleCos;
    float outerConeAngleCos;

    // intensity of the cookie
    float cookieIntensity;

    // light mask
    //float mask;
    bool isDynamic;
    bool isLightmapped;
};

// Note: on some devices (tested on Pixel 3A XL), this matrix when stored inside the light struct has lower precision compared to
// when stored outside, so we store it outside to avoid spot shadow flickering. This might need to be done to other / all members
// of the structure if further similar issues are observed.

// shadow (spot light only) / cookie projection matrix
mat4 lightProjectionMatrix;

vec4 sampleLightTextureF(const ClusterLightData clusterLightData, int index) {
    return texelFetch(lightsTexture, ivec2(index, clusterLightData.lightIndex), 0);
}

void decodeClusterLightCore(inout ClusterLightData clusterLightData, float lightIndex) {

    // light index
    clusterLightData.lightIndex = int(lightIndex);

    // sample data encoding half-float values into 32bit uints
    vec4 halfData = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS});

    // store floats we decode later as needed
    clusterLightData.anglesData = halfData.z;
    clusterLightData.biasesData = halfData.w;

    // decompress color half-floats
    vec2 colorRG = unpackHalf2x16(floatBitsToUint(halfData.x));
    vec2 colorB_ = unpackHalf2x16(floatBitsToUint(halfData.y));
    clusterLightData.color = vec3(colorRG, colorB_.x) * {LIGHT_COLOR_DIVIDER};

    // position and range, full floats
    vec4 lightPosRange = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_POSITION_RANGE});
    clusterLightData.position = lightPosRange.xyz;
    clusterLightData.range = lightPosRange.w;

    // spot direction & flags data
    vec4 lightDir_Flags = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_DIRECTION_FLAGS});

    // spot light direction
    clusterLightData.direction = lightDir_Flags.xyz;

    // 32bit flags
    clusterLightData.flags = floatBitsToUint(lightDir_Flags.w);
    clusterLightData.isSpot = (clusterLightData.flags & (1u << 30u)) != 0u;
    clusterLightData.shape = (clusterLightData.flags >> 28u) & 0x3u;
    clusterLightData.falloffModeLinear = (clusterLightData.flags & (1u << 27u)) == 0u;
    clusterLightData.shadowIntensity = float((clusterLightData.flags >> 0u) & 0xFFu) / 255.0;
    clusterLightData.cookieIntensity = float((clusterLightData.flags >> 8u) & 0xFFu) / 255.0;
    clusterLightData.isDynamic = (clusterLightData.flags & (1u << 22u)) != 0u;
    clusterLightData.isLightmapped = (clusterLightData.flags & (1u << 21u)) != 0u;
}

void decodeClusterLightSpot(inout ClusterLightData clusterLightData) {

    // spot light cos angles
    vec2 angles = unpackHalf2x16(floatBitsToUint(clusterLightData.anglesData));
    clusterLightData.innerConeAngleCos = angles.x;
    clusterLightData.outerConeAngleCos = angles.y;
}

void decodeClusterLightOmniAtlasViewport(inout ClusterLightData clusterLightData) {
    clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;
}

void decodeClusterLightAreaData(inout ClusterLightData clusterLightData) {
    clusterLightData.halfWidth = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz;
    clusterLightData.halfHeight = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz;
}

void decodeClusterLightProjectionMatrixData(inout ClusterLightData clusterLightData) {
    
    // shadow matrix
    vec4 m0 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_0});
    vec4 m1 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_1});
    vec4 m2 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_2});
    vec4 m3 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_3});
    lightProjectionMatrix = mat4(m0, m1, m2, m3);
}

void decodeClusterLightShadowData(inout ClusterLightData clusterLightData) {
    
    // shadow biases
    vec2 biases = unpackHalf2x16(floatBitsToUint(clusterLightData.biasesData));
    clusterLightData.shadowBias = biases.x;
    clusterLightData.shadowNormalBias = biases.y;
}

void decodeClusterLightCookieData(inout ClusterLightData clusterLightData) {

    // extract channel mask from flags
    uint cookieFlags = (clusterLightData.flags >> 23u) & 0x0Fu;  // 4bits, each bit enables a channel
    clusterLightData.cookieChannelMask = vec4(uvec4(cookieFlags) & uvec4(1u, 2u, 4u, 8u));
    clusterLightData.cookieChannelMask = step(1.0, clusterLightData.cookieChannelMask);  // Normalize to 0.0 or 1.0
}

void evaluateLight(
    ClusterLightData light, 
    vec3 worldNormal, 
    vec3 viewDir, 
    vec3 reflectionDir,
#if defined(LIT_CLEARCOAT)
    vec3 clearcoatReflectionDir,
#endif
    float gloss, 
    vec3 specularity, 
    vec3 geometricNormal, 
    mat3 tbn, 
#if defined(LIT_IRIDESCENCE)
    vec3 iridescenceFresnel,
#endif
    vec3 clearcoat_worldNormal,
    float clearcoat_gloss,
    float sheen_gloss,
    float iridescence_intensity
) {

    vec3 cookieAttenuation = vec3(1.0);
    float diffuseAttenuation = 1.0;
    float falloffAttenuation = 1.0;

    // evaluate omni part of the light
    vec3 lightDirW = evalOmniLight(light.position);
    vec3 lightDirNormW = normalize(lightDirW);

    #ifdef CLUSTER_AREALIGHTS

    // distance attenuation
    if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light

        // area lights
        decodeClusterLightAreaData(light);

        // handle light shape
        if (light.shape == {LIGHTSHAPE_RECT}) {
            calcRectLightValues(light.position, light.halfWidth, light.halfHeight);
        } else if (light.shape == {LIGHTSHAPE_DISK}) {
            calcDiskLightValues(light.position, light.halfWidth, light.halfHeight);
        } else { // sphere
            calcSphereLightValues(light.position, light.halfWidth, light.halfHeight);
        }

        falloffAttenuation = getFalloffWindow(light.range, lightDirW);

    } else

    #endif

    {   // punctual light

        if (light.falloffModeLinear)
            falloffAttenuation = getFalloffLinear(light.range, lightDirW);
        else
            falloffAttenuation = getFalloffInvSquared(light.range, lightDirW);
    }

    if (falloffAttenuation > 0.00001) {

        #ifdef CLUSTER_AREALIGHTS

        if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light

            // handle light shape
            if (light.shape == {LIGHTSHAPE_RECT}) {
                diffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;
            } else if (light.shape == {LIGHTSHAPE_DISK}) {
                diffuseAttenuation = getDiskLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;
            } else { // sphere
                diffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;
            }

        } else

        #endif

        {
            falloffAttenuation *= getLightDiffuse(worldNormal, viewDir, lightDirNormW); 
        }

        // spot light falloff
        if (light.isSpot) {
            decodeClusterLightSpot(light);
            falloffAttenuation *= getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);
        }

        #if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)

        if (falloffAttenuation > 0.00001) {

            // shadow / cookie
            if (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {

                // shared shadow / cookie data depends on light type
                if (light.isSpot) {
                    decodeClusterLightProjectionMatrixData(light);
                } else {
                    decodeClusterLightOmniAtlasViewport(light);
                }

                float shadowTextureResolution = shadowAtlasParams.x;
                float shadowEdgePixels = shadowAtlasParams.y;

                #ifdef CLUSTER_COOKIES

                // cookie
                if (light.cookieIntensity > 0.0) {
                    decodeClusterLightCookieData(light);

                    if (light.isSpot) {
                        cookieAttenuation = getCookie2DClustered(TEXTURE_PASS(cookieAtlasTexture), lightProjectionMatrix, vPositionW, light.cookieIntensity, light.cookieChannelMask);
                    } else {
                        cookieAttenuation = getCookieCubeClustered(TEXTURE_PASS(cookieAtlasTexture), lightDirW, light.cookieIntensity, light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);
                    }
                }

                #endif

                #ifdef CLUSTER_SHADOWS

                // shadow
                if (light.shadowIntensity > 0.0) {
                    decodeClusterLightShadowData(light);

                    vec4 shadowParams = vec4(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);

                    if (light.isSpot) {

                        // spot shadow
                        vec3 shadowCoord = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);
                        
                        #if defined(CLUSTER_SHADOW_TYPE_PCF1)
                            float shadow = getShadowSpotClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF3)
                            float shadow = getShadowSpotClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF5)
                            float shadow = getShadowSpotClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCSS)
                            float shadow = getShadowSpotClusteredPCSS(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
                        #endif
                        falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);

                    } else {

                        // omni shadow
                        vec3 dir = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, geometricNormal);  // normalBias adjusted for distance

                        #if defined(CLUSTER_SHADOW_TYPE_PCF1)
                            float shadow = getShadowOmniClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF3)
                            float shadow = getShadowOmniClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF5)
                            float shadow = getShadowOmniClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
                        #endif
                        falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);
                    }
                }

                #endif
            }
        }

        #endif

        // diffuse / specular / clearcoat
        #ifdef CLUSTER_AREALIGHTS

        if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light

            // area light diffuse
            {
                vec3 areaDiffuse = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;

                #if defined(LIT_SPECULAR)
                    areaDiffuse = mix(areaDiffuse, vec3(0), dLTCSpecFres);
                #endif

                // area light diffuse - it does not mix diffuse lighting into specular attenuation
                dDiffuseLight += areaDiffuse;
            }

            // specular and clear coat are material settings and get included by a define based on the material
            #ifdef LIT_SPECULAR

                // area light specular
                float areaLightSpecular;

                if (light.shape == {LIGHTSHAPE_RECT}) {
                    areaLightSpecular = getRectLightSpecular(worldNormal, viewDir);
                } else if (light.shape == {LIGHTSHAPE_DISK}) {
                    areaLightSpecular = getDiskLightSpecular(worldNormal, viewDir);
                } else { // sphere
                    areaLightSpecular = getSphereLightSpecular(worldNormal, viewDir);
                }

                dSpecularLight += dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;

                #ifdef LIT_CLEARCOAT

                    // area light specular clear coat
                    float areaLightSpecularCC;

                    if (light.shape == {LIGHTSHAPE_RECT}) {
                        areaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir);
                    } else if (light.shape == {LIGHTSHAPE_DISK}) {
                        areaLightSpecularCC = getDiskLightSpecular(clearcoat_worldNormal, viewDir);
                    } else { // sphere
                        areaLightSpecularCC = getSphereLightSpecular(clearcoat_worldNormal, viewDir);
                    }

                    ccSpecularLight += ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color  * cookieAttenuation;

                #endif

            #endif

        } else

        #endif

        {    // punctual light

            // punctual light diffuse
            {
                vec3 punctualDiffuse = falloffAttenuation * light.color * cookieAttenuation;

                #if defined(CLUSTER_AREALIGHTS)
                #if defined(LIT_SPECULAR)
                    punctualDiffuse = mix(punctualDiffuse, vec3(0), specularity);
                #endif
                #endif

                dDiffuseLight += punctualDiffuse;
            }
   
            // specular and clear coat are material settings and get included by a define based on the material
            #ifdef LIT_SPECULAR

                vec3 halfDir = normalize(-lightDirNormW + viewDir);
                
                // specular
                #ifdef LIT_SPECULAR_FRESNEL
                    dSpecularLight += 
                        getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * 
                        getFresnel(
                            dot(viewDir, halfDir), 
                            gloss, 
                            specularity
                        #if defined(LIT_IRIDESCENCE)
                            , iridescenceFresnel,
                            iridescence_intensity
                        #endif
                            );
                #else
                    dSpecularLight += getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity;
                #endif

                #ifdef LIT_CLEARCOAT
                    #ifdef LIT_SPECULAR_FRESNEL
                        ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));
                    #else
                        ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation; 
                    #endif
                #endif

                #ifdef LIT_SHEEN
                    sSpecularLight += getLightSpecularSheen(halfDir, worldNormal, viewDir, lightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation;
                #endif

            #endif
        }
    }

    // Write to global attenuation values (for lightmapper)
    dAtten = falloffAttenuation;
    dLightDirNormW = lightDirNormW;
}

void evaluateClusterLight(
    float lightIndex, 
    vec3 worldNormal, 
    vec3 viewDir, 
    vec3 reflectionDir, 
#if defined(LIT_CLEARCOAT)
    vec3 clearcoatReflectionDir,
#endif
    float gloss, 
    vec3 specularity, 
    vec3 geometricNormal, 
    mat3 tbn, 
#if defined(LIT_IRIDESCENCE)
    vec3 iridescenceFresnel,
#endif
    vec3 clearcoat_worldNormal,
    float clearcoat_gloss,
    float sheen_gloss,
    float iridescence_intensity
) {

    // decode core light data from textures
    ClusterLightData clusterLightData;
    decodeClusterLightCore(clusterLightData, lightIndex);

    // evaluate light if it uses accepted light mask
    #ifdef CLUSTER_MESH_DYNAMIC_LIGHTS
        bool acceptLightMask = clusterLightData.isDynamic;
    #else
        bool acceptLightMask = clusterLightData.isLightmapped;
    #endif

    if (acceptLightMask)
        evaluateLight(
            clusterLightData, 
            worldNormal, 
            viewDir, 
            reflectionDir, 
#if defined(LIT_CLEARCOAT)
            clearcoatReflectionDir, 
#endif
            gloss, 
            specularity, 
            geometricNormal, 
            tbn, 
#if defined(LIT_IRIDESCENCE)
            iridescenceFresnel,
#endif
            clearcoat_worldNormal,
            clearcoat_gloss,
            sheen_gloss,
            iridescence_intensity
        );
}

void addClusteredLights(
    vec3 worldNormal, 
    vec3 viewDir, 
    vec3 reflectionDir, 
#if defined(LIT_CLEARCOAT)
    vec3 clearcoatReflectionDir,
#endif
    float gloss, 
    vec3 specularity, 
    vec3 geometricNormal, 
    mat3 tbn, 
#if defined(LIT_IRIDESCENCE)
    vec3 iridescenceFresnel,
#endif
    vec3 clearcoat_worldNormal,
    float clearcoat_gloss,
    float sheen_gloss,
    float iridescence_intensity
) {

    // skip lights if no lights at all
    if (clusterSkip > 0.5)
        return;

    // world space position to 3d integer cell cordinates in the cluster structure
    vec3 cellCoords = floor((vPositionW - clusterBoundsMin) * clusterCellsCountByBoundsSize);

    // no lighting when cell coordinate is out of range
    if (!(any(lessThan(cellCoords, vec3(0.0))) || any(greaterThanEqual(cellCoords, clusterCellsMax)))) {

        // cell index (mapping from 3d cell coordinates to linear memory)
        float cellIndex = dot(clusterCellsDot, cellCoords);

        // convert cell index to uv coordinates
        float clusterV = floor(cellIndex * clusterTextureSize.y);
        float clusterU = cellIndex - (clusterV * clusterTextureSize.x);

        // loop over maximum number of light cells
        for (int lightCellIndex = 0; lightCellIndex < clusterMaxCells; lightCellIndex++) {

            // using a single channel texture with data in red channel
            float lightIndex = texelFetch(clusterWorldTexture, ivec2(int(clusterU) + lightCellIndex, clusterV), 0).x;

            if (lightIndex <= 0.0)
                break;

            evaluateClusterLight(
                lightIndex * 255.0, 
                worldNormal, 
                viewDir, 
                reflectionDir,
#if defined(LIT_CLEARCOAT)
                clearcoatReflectionDir,
#endif
                gloss, 
                specularity, 
                geometricNormal, 
                tbn, 
#if defined(LIT_IRIDESCENCE)
                iridescenceFresnel,
#endif
                clearcoat_worldNormal,
                clearcoat_gloss,
                sheen_gloss,
                iridescence_intensity
            ); 
        }
    }
}
`;

var combinePS$1 = /* glsl */ `
vec3 combineColor(vec3 albedo, vec3 sheenSpecularity, float clearcoatSpecularity) {
    vec3 ret = vec3(0);
#ifdef LIT_OLD_AMBIENT
    ret += (dDiffuseLight - light_globalAmbient) * albedo + material_ambient * light_globalAmbient;
#else
    ret += albedo * dDiffuseLight;
#endif
#ifdef LIT_SPECULAR
    ret += dSpecularLight;
#endif
#ifdef LIT_REFLECTIONS
    ret += dReflection.rgb * dReflection.a;
#endif

#ifdef LIT_SHEEN
    float sheenScaling = 1.0 - max(max(sheenSpecularity.r, sheenSpecularity.g), sheenSpecularity.b) * 0.157;
    ret = ret * sheenScaling + (sSpecularLight + sReflection.rgb) * sheenSpecularity;
#endif
#ifdef LIT_CLEARCOAT
    float clearCoatScaling = 1.0 - ccFresnel * clearcoatSpecularity;
    ret = ret * clearCoatScaling + (ccSpecularLight + ccReflection) * clearcoatSpecularity;
#endif

    return ret;
}
`;

var cookieBlit2DPS$1 = /* glsl */ `
    varying vec2 uv0;
    uniform sampler2D blitTexture;
    void main(void) {
        gl_FragColor = texture2D(blitTexture, uv0);
    }
`;

var cookieBlitCubePS$1 = /* glsl */ `
    varying vec2 uv0;
    uniform samplerCube blitTexture;
    uniform mat4 invViewProj;
    void main(void) {
        vec4 projPos = vec4(uv0 * 2.0 - 1.0, 0.5, 1.0);
        vec4 worldPos = invViewProj * projPos;
        gl_FragColor = textureCube(blitTexture, worldPos.xyz);
    }
`;

var cookieBlitVS$1 = /* glsl */ `
    attribute vec2 vertex_position;
    varying vec2 uv0;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.5, 1.0);
        uv0 = vertex_position.xy * 0.5 + 0.5;
        #ifndef WEBGPU
            uv0.y = 1.0 - uv0.y;
        #endif
    }
`;

var cookiePS = /* glsl */ `
// light cookie functionality for non-clustered lights
vec4 getCookie2D(sampler2D tex, mat4 transform, float intensity) {
    vec4 projPos = transform * vec4(vPositionW, 1.0);
    projPos.xy /= projPos.w;
    return mix(vec4(1.0), texture2D(tex, projPos.xy), intensity);
}

vec4 getCookie2DClip(sampler2D tex, mat4 transform, float intensity) {
    vec4 projPos = transform * vec4(vPositionW, 1.0);
    projPos.xy /= projPos.w;
    if (projPos.x < 0.0 || projPos.x > 1.0 || projPos.y < 0.0 || projPos.y > 1.0 || projPos.z < 0.0) return vec4(0.0);
    return mix(vec4(1.0), texture2D(tex, projPos.xy), intensity);
}

vec4 getCookie2DXform(sampler2D tex, mat4 transform, float intensity, vec4 cookieMatrix, vec2 cookieOffset) {
    vec4 projPos = transform * vec4(vPositionW, 1.0);
    projPos.xy /= projPos.w;
    projPos.xy += cookieOffset;
    vec2 uv = mat2(cookieMatrix) * (projPos.xy-vec2(0.5)) + vec2(0.5);
    return mix(vec4(1.0), texture2D(tex, uv), intensity);
}

vec4 getCookie2DClipXform(sampler2D tex, mat4 transform, float intensity, vec4 cookieMatrix, vec2 cookieOffset) {
    vec4 projPos = transform * vec4(vPositionW, 1.0);
    projPos.xy /= projPos.w;
    projPos.xy += cookieOffset;
    if (projPos.x < 0.0 || projPos.x > 1.0 || projPos.y < 0.0 || projPos.y > 1.0 || projPos.z < 0.0) return vec4(0.0);
    vec2 uv = mat2(cookieMatrix) * (projPos.xy-vec2(0.5)) + vec2(0.5);
    return mix(vec4(1.0), texture2D(tex, uv), intensity);
}

vec4 getCookieCube(samplerCube tex, mat4 transform, float intensity) {
    return mix(vec4(1.0), textureCube(tex, dLightDirNormW * mat3(transform)), intensity);
}
`;

var cubeMapProjectPS$1 = /* glsl */ `

#if LIT_CUBEMAP_PROJECTION == BOX
    uniform vec3 envBoxMin;
    uniform vec3 envBoxMax;
#endif

vec3 cubeMapProject(vec3 nrdir) {

    #if LIT_CUBEMAP_PROJECTION == NONE
        return cubeMapRotate(nrdir);
    #endif

    #if LIT_CUBEMAP_PROJECTION == BOX

        nrdir = cubeMapRotate(nrdir);

        vec3 rbmax = (envBoxMax - vPositionW) / nrdir;
        vec3 rbmin = (envBoxMin - vPositionW) / nrdir;

        vec3 rbminmax = mix(rbmin, rbmax, vec3(greaterThan(nrdir, vec3(0.0))));
        float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);

        vec3 posonbox = vPositionW + nrdir * fa;
        vec3 envBoxPos = (envBoxMin + envBoxMax) * 0.5;
        return normalize(posonbox - envBoxPos);

    #endif
}
`;

var cubeMapRotatePS$1 = /* glsl */ `
#ifdef CUBEMAP_ROTATION
uniform mat3 cubeMapRotationMatrix;
#endif

vec3 cubeMapRotate(vec3 refDir) {
#ifdef CUBEMAP_ROTATION
    return refDir * cubeMapRotationMatrix;
#else
    return refDir;
#endif
}
`;

var debugOutputPS$1 = /* glsl */ `
#ifdef DEBUG_ALBEDO_PASS
gl_FragColor = vec4(gammaCorrectOutput(dAlbedo), 1.0);
#endif

#ifdef DEBUG_UV0_PASS
gl_FragColor = vec4(litArgs_albedo , 1.0);
#endif

#ifdef DEBUG_WORLD_NORMAL_PASS
gl_FragColor = vec4(litArgs_worldNormal * 0.5 + 0.5, 1.0);
#endif

#ifdef DEBUG_OPACITY_PASS
gl_FragColor = vec4(vec3(litArgs_opacity) , 1.0);
#endif

#ifdef DEBUG_SPECULARITY_PASS
gl_FragColor = vec4(litArgs_specularity, 1.0);
#endif

#ifdef DEBUG_GLOSS_PASS
gl_FragColor = vec4(vec3(litArgs_gloss) , 1.0);
#endif

#ifdef DEBUG_METALNESS_PASS
gl_FragColor = vec4(vec3(litArgs_metalness) , 1.0);
#endif

#ifdef DEBUG_AO_PASS
gl_FragColor = vec4(vec3(litArgs_ao) , 1.0);
#endif

#ifdef DEBUG_EMISSION_PASS
gl_FragColor = vec4(gammaCorrectOutput(litArgs_emission), 1.0);
#endif
`;

var debugProcessFrontendPS$1 = /* glsl */ `
#ifdef DEBUG_LIGHTING_PASS
litArgs_albedo = vec3(0.5);
#endif

#ifdef DEBUG_UV0_PASS
#ifdef VARYING_VUV0
litArgs_albedo = vec3(vUv0, 0);
#else
litArgs_albedo = vec3(0);
#endif
#endif
`;

var decodePS$1 = /* glsl */ `

#ifndef _DECODE_INCLUDED_
#define _DECODE_INCLUDED_

vec3 decodeLinear(vec4 raw) {
    return raw.rgb;
}

float decodeGamma(float raw) {
    return pow(raw, 2.2);
}

vec3 decodeGamma(vec3 raw) {
    return pow(raw, vec3(2.2));
}

vec3 decodeGamma(vec4 raw) {
    return pow(raw.xyz, vec3(2.2));
}

vec3 decodeRGBM(vec4 raw) {
    vec3 color = (8.0 * raw.a) * raw.rgb;
    return color * color;
}

vec3 decodeRGBP(vec4 raw) {
    vec3 color = raw.rgb * (-raw.a * 7.0 + 8.0);
    return color * color;
}

vec3 decodeRGBE(vec4 raw) {
    if (raw.a == 0.0) {
        return vec3(0.0, 0.0, 0.0);
    } else {
        return raw.xyz * pow(2.0, raw.w * 255.0 - 128.0);
    }
}

vec4 passThrough(vec4 raw) {
    return raw;
}

vec3 unpackNormalXYZ(vec4 nmap) {
    return nmap.xyz * 2.0 - 1.0;
}

vec3 unpackNormalXY(vec4 nmap) {
    vec3 normal;
    normal.xy = nmap.wy * 2.0 - 1.0;
    normal.z = sqrt(1.0 - clamp(dot(normal.xy, normal.xy), 0.0, 1.0));
    return normal;
}

#endif
`;

var detailModesPS$1 = /* glsl */ `

#ifndef _DETAILMODES_INCLUDED_
#define _DETAILMODES_INCLUDED_

vec3 detailMode_mul(vec3 c1, vec3 c2) {
    return c1 * c2;
}

vec3 detailMode_add(vec3 c1, vec3 c2) {
    return c1 + c2;
}

// https://en.wikipedia.org/wiki/Blend_modes#Screen
vec3 detailMode_screen(vec3 c1, vec3 c2) {
    return 1.0 - (1.0 - c1)*(1.0 - c2);
}

// https://en.wikipedia.org/wiki/Blend_modes#Overlay
vec3 detailMode_overlay(vec3 c1, vec3 c2) {
    return mix(1.0 - 2.0 * (1.0 - c1)*(1.0 - c2), 2.0 * c1 * c2, step(c1, vec3(0.5)));
}

vec3 detailMode_min(vec3 c1, vec3 c2) {
    return min(c1, c2);
}

vec3 detailMode_max(vec3 c1, vec3 c2) {
    return max(c1, c2);
}

#endif
`;

var diffusePS$1 = /* glsl */ `
uniform vec3 material_diffuse;

#ifdef STD_DIFFUSEDETAIL_TEXTURE
    #include "detailModesPS"
#endif

void getAlbedo() {
    dAlbedo = material_diffuse.rgb;

    #ifdef STD_DIFFUSE_TEXTURE
        vec3 albedoTexture = {STD_DIFFUSE_TEXTURE_DECODE}(texture2DBias({STD_DIFFUSE_TEXTURE_NAME}, {STD_DIFFUSE_TEXTURE_UV}, textureBias)).{STD_DIFFUSE_TEXTURE_CHANNEL};

        #ifdef STD_DIFFUSEDETAIL_TEXTURE
            vec3 albedoDetail = {STD_DIFFUSEDETAIL_TEXTURE_DECODE}(texture2DBias({STD_DIFFUSEDETAIL_TEXTURE_NAME}, {STD_DIFFUSEDETAIL_TEXTURE_UV}, textureBias)).{STD_DIFFUSEDETAIL_TEXTURE_CHANNEL};
            albedoTexture = detailMode_{STD_DIFFUSEDETAIL_DETAILMODE}(albedoTexture, albedoDetail);
        #endif

        dAlbedo *= albedoTexture;
    #endif

    #ifdef STD_DIFFUSE_VERTEX
        dAlbedo *= gammaCorrectInput(saturate(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}));
    #endif
}
`;

var emissivePS$1 = /* glsl */ `
uniform vec3 material_emissive;
uniform float material_emissiveIntensity;

void getEmission() {
    dEmission = material_emissive * material_emissiveIntensity;

    #ifdef STD_EMISSIVE_TEXTURE
    dEmission *= {STD_EMISSIVE_TEXTURE_DECODE}(texture2DBias({STD_EMISSIVE_TEXTURE_NAME}, {STD_EMISSIVE_TEXTURE_UV}, textureBias)).{STD_EMISSIVE_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_EMISSIVE_VERTEX
    dEmission *= gammaCorrectInput(saturate(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}));
    #endif
}
`;

var encodePS$1 = /* glsl */ `
vec4 encodeLinear(vec3 source) {
    return vec4(source, 1.0);
}

vec4 encodeGamma(vec3 source) {
    return vec4(pow(source + 0.0000001, vec3(1.0 / 2.2)), 1.0);
}

vec4 encodeRGBM(vec3 source) { // modified RGBM
    vec4 result;
    result.rgb = pow(source.rgb, vec3(0.5));
    result.rgb *= 1.0 / 8.0;

    result.a = saturate( max( max( result.r, result.g ), max( result.b, 1.0 / 255.0 ) ) );
    result.a = ceil(result.a * 255.0) / 255.0;

    result.rgb /= result.a;
    return result;
}

vec4 encodeRGBP(vec3 source) {
    // convert incoming linear to gamma(ish)
    vec3 gamma = pow(source, vec3(0.5));

    // calculate the maximum component clamped to 1..8
    float maxVal = min(8.0, max(1.0, max(gamma.x, max(gamma.y, gamma.z))));

    // calculate storage factor
    float v = 1.0 - ((maxVal - 1.0) / 7.0);

    // round the value for storage in 8bit channel
    v = ceil(v * 255.0) / 255.0;

    return vec4(gamma / (-v * 7.0 + 8.0), v);    
}

vec4 encodeRGBE(vec3 source) {
    float maxVal = max(source.x, max(source.y, source.z));
    if (maxVal < 1e-32) {
        return vec4(0, 0, 0, 0);
    } else {
        float e = ceil(log2(maxVal));
        return vec4(source / pow(2.0, e), (e + 128.0) / 255.0);
    }
}
`;

var endPS$1 = /* glsl */ `
    gl_FragColor.rgb = combineColor(litArgs_albedo, litArgs_sheen_specularity, litArgs_clearcoat_specularity);

    gl_FragColor.rgb += litArgs_emission;
    gl_FragColor.rgb = addFog(gl_FragColor.rgb);
    gl_FragColor.rgb = toneMap(gl_FragColor.rgb);
    gl_FragColor.rgb = gammaCorrectOutput(gl_FragColor.rgb);
`;

var envAtlasPS$1 = /* glsl */ `

#ifndef _ENVATLAS_INCLUDED_
#define _ENVATLAS_INCLUDED_

// the envAtlas is fixed at 512 pixels. every equirect is generated with 1 pixel boundary.
const float atlasSize = 512.0;
const float seamSize = 1.0 / atlasSize;

// map a normalized equirect UV to the given rectangle (taking 1 pixel seam into account).
vec2 mapUv(vec2 uv, vec4 rect) {
    return vec2(mix(rect.x + seamSize, rect.x + rect.z - seamSize, uv.x),
                mix(rect.y + seamSize, rect.y + rect.w - seamSize, uv.y));
}

// map a normalized equirect UV and roughness level to the correct atlas rect.
vec2 mapRoughnessUv(vec2 uv, float level) {
    float t = 1.0 / exp2(level);
    return mapUv(uv, vec4(0, 1.0 - t, t, t * 0.5));
}

// map shiny level UV
vec2 mapShinyUv(vec2 uv, float level) {
    float t = 1.0 / exp2(level);
    return mapUv(uv, vec4(1.0 - t, 1.0 - t, t, t * 0.5));
}

#endif
`;

var envProcPS$1 = /* glsl */ `
#ifdef LIT_SKYBOX_INTENSITY
    uniform float skyboxIntensity;
#endif

vec3 processEnvironment(vec3 color) {
    #ifdef LIT_SKYBOX_INTENSITY
        return color * skyboxIntensity;
    #else
        return color;
    #endif
}
`;

var falloffInvSquaredPS$1 = /* glsl */ `
float getFalloffWindow(float lightRadius, vec3 lightDir) {
    float sqrDist = dot(lightDir, lightDir);
    float invRadius = 1.0 / lightRadius;
    return square(saturate(1.0 - square(sqrDist * square(invRadius))));
}

float getFalloffInvSquared(float lightRadius, vec3 lightDir) {
    float sqrDist = dot(lightDir, lightDir);
    float falloff = 1.0 / (sqrDist + 1.0);
    float invRadius = 1.0 / lightRadius;

    falloff *= 16.0;
    falloff *= square(saturate(1.0 - square(sqrDist * square(invRadius))));

    return falloff;
}
`;

var falloffLinearPS$1 = /* glsl */ `
float getFalloffLinear(float lightRadius, vec3 lightDir) {
    float d = length(lightDir);
    return max(((lightRadius - d) / lightRadius), 0.0);
}
`;

// Chunk that allows us to store all 32bits of float in a single RGBA8 texture without any loss of
// precision. The float value is encoded to RGBA8 and decoded back to float. Used as a fallback
// for platforms that do not support float textures but need to render to a float texture (without
// filtering)
var floatAsUintPS$1 = /* glsl */ `

#ifndef FLOAT_AS_UINT
#define FLOAT_AS_UINT

// encode float value to RGBA8
vec4 float2uint(float value) {
    uint intBits = floatBitsToUint(value);
    return vec4(
        float((intBits >> 24u) & 0xFFu) / 255.0,
        float((intBits >> 16u) & 0xFFu) / 255.0,
        float((intBits >> 8u) & 0xFFu) / 255.0,
        float(intBits & 0xFFu) / 255.0
    );
}

// decode RGBA8 value to float
float uint2float(vec4 value) {
    uint intBits = 
        (uint(value.r * 255.0) << 24u) |
        (uint(value.g * 255.0) << 16u) |
        (uint(value.b * 255.0) << 8u) |
        uint(value.a * 255.0);

    return uintBitsToFloat(intBits);
}

// store a single float value in vec4, assuming either RGBA8 or float renderable texture
vec4 float2vec4(float value) {
    #if defined(CAPS_TEXTURE_FLOAT_RENDERABLE)
        return vec4(value, 1.0, 1.0, 1.0);
    #else
        return float2uint(value);
    #endif
}

#endif // FLOAT_AS_UINT
`;

var fogPS$1 = /* glsl */ `

float dBlendModeFogFactor = 1.0;

#if (FOG != NONE)
    uniform vec3 fog_color;

    #if (FOG == LINEAR)
        uniform float fog_start;
        uniform float fog_end;
    #else
        uniform float fog_density;
    #endif
#endif

float getFogFactor() {

    float depth = gl_FragCoord.z / gl_FragCoord.w;
    float fogFactor = 0.0;

    #if (FOG == LINEAR)
        fogFactor = (fog_end - depth) / (fog_end - fog_start);
    #elif (FOG == EXP)
        fogFactor = exp(-depth * fog_density);
    #elif (FOG == EXP2)
        fogFactor = exp(-depth * depth * fog_density * fog_density);
    #endif

    return clamp(fogFactor, 0.0, 1.0);
}

vec3 addFog(vec3 color) {

    #if (FOG != NONE)
        return mix(fog_color * dBlendModeFogFactor, color, getFogFactor());
    #endif

    return color;
}
`;

var fresnelSchlickPS$1 = /* glsl */ `
// Schlick's approximation
vec3 getFresnel(
        float cosTheta, 
        float gloss, 
        vec3 specularity
#if defined(LIT_IRIDESCENCE)
        , vec3 iridescenceFresnel, 
        float iridescenceIntensity
#endif
    ) {
    float fresnel = pow(1.0 - saturate(cosTheta), 5.0);
    float glossSq = gloss * gloss;
    vec3 ret = specularity + (max(vec3(glossSq), specularity) - specularity) * fresnel;
#if defined(LIT_IRIDESCENCE)
    return mix(ret, iridescenceFresnel, iridescenceIntensity);
#else
    return ret;
#endif    
}

float getFresnelCC(float cosTheta) {
    float fresnel = pow(1.0 - saturate(cosTheta), 5.0);
    return 0.04 + (1.0 - 0.04) * fresnel;
}
`;

var fullscreenQuadVS$1 = /* glsl */ `
attribute vec2 vertex_position;

varying vec2 vUv0;

void main(void)
{
    gl_Position = vec4(vertex_position, 0.5, 1.0);
    vUv0 = vertex_position.xy * 0.5 + 0.5;
}
`;

var gammaPS$1 = /* glsl */ `

#include "decodePS"

#if (GAMMA == SRGB)

    float gammaCorrectInput(float color) {
        return decodeGamma(color);
    }

    vec3 gammaCorrectInput(vec3 color) {
        return decodeGamma(color);
    }

    vec4 gammaCorrectInput(vec4 color) {
        return vec4(decodeGamma(color.xyz), color.w);
    }

    vec3 gammaCorrectOutput(vec3 color) {
        return pow(color + 0.0000001, vec3(1.0 / 2.2));
    }

#else // NONE

    float gammaCorrectInput(float color) {
        return color;
    }

    vec3 gammaCorrectInput(vec3 color) {
        return color;
    }

    vec4 gammaCorrectInput(vec4 color) {
        return color;
    }

    vec3 gammaCorrectOutput(vec3 color) {
        return color;
    }

#endif
`;

var glossPS$1 = /* glsl */ `
#ifdef STD_GLOSS_CONSTANT
uniform float material_gloss;
#endif

void getGlossiness() {
    dGlossiness = 1.0;

    #ifdef STD_GLOSS_CONSTANT
    dGlossiness *= material_gloss;
    #endif

    #ifdef STD_GLOSS_TEXTURE
    dGlossiness *= texture2DBias({STD_GLOSS_TEXTURE_NAME}, {STD_GLOSS_TEXTURE_UV}, textureBias).{STD_GLOSS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_GLOSS_VERTEX
    dGlossiness *= saturate(vVertexColor.{STD_GLOSS_VERTEX_CHANNEL});
    #endif

    #ifdef STD_GLOSS_INVERT
    dGlossiness = 1.0 - dGlossiness;
    #endif

    dGlossiness += 0.0000001;
}
`;

var gsplatCenterVS$1 = /* glsl */ `
uniform mat4 matrix_model;
uniform mat4 matrix_view;
#ifndef GSPLAT_CENTER_NOPROJ
    uniform mat4 matrix_projection;
#endif

// project the model space gaussian center to view and clip space
bool initCenter(vec3 modelCenter, out SplatCenter center) {
    mat4 modelView = matrix_view * matrix_model;
    vec4 centerView = modelView * vec4(modelCenter, 1.0);

    #ifndef GSPLAT_CENTER_NOPROJ

        // early out if splat is behind the camera
        if (centerView.z > 0.0) {
            return false;
        }

        vec4 centerProj = matrix_projection * centerView;

        // ensure gaussians are not clipped by camera near and far
        #if WEBGPU
            centerProj.z = clamp(centerProj.z, 0, abs(centerProj.w));
        #else
            centerProj.z = clamp(centerProj.z, -abs(centerProj.w), abs(centerProj.w));
        #endif

        center.proj = centerProj;
        center.projMat00 = matrix_projection[0][0];

    #endif

    center.view = centerView.xyz / centerView.w;
    center.modelView = modelView;
    return true;
}
`;

var gsplatColorVS$1 = /* glsl */ `

uniform mediump sampler2D splatColor;

vec4 readColor(in SplatSource source) {
    return texelFetch(splatColor, source.uv, 0);
}

`;

var gsplatCommonVS$1 = /* glsl */ `

#include "gsplatStructsVS"
#include "gsplatEvalSHVS"
#include "gsplatQuatToMat3VS"
#include "gsplatSourceFormatVS"
#include "gsplatSourceVS"
#include "gsplatCenterVS"
#include "gsplatCornerVS"
#include "gsplatOutputVS"

// modify the gaussian corner so it excludes gaussian regions with alpha less than 1/255
void clipCorner(inout SplatCorner corner, float alpha) {
    float clip = min(1.0, sqrt(-log(1.0 / 255.0 / alpha)) / 2.0);
    corner.offset *= clip;
    corner.uv *= clip;
}
`;

var gsplatCompressedDataVS$1 = /* glsl */ `
uniform highp usampler2D packedTexture;
uniform highp sampler2D chunkTexture;

// work values
vec4 chunkDataA;    // x: min_x, y: min_y, z: min_z, w: max_x
vec4 chunkDataB;    // x: max_y, y: max_z, z: scale_min_x, w: scale_min_y
vec4 chunkDataC;    // x: scale_min_z, y: scale_max_x, z: scale_max_y, w: scale_max_z
vec4 chunkDataD;    // x: min_r, y: min_g, z: min_b, w: max_r
vec4 chunkDataE;    // x: max_g, y: max_b, z: unused, w: unused
uvec4 packedData;   // x: position bits, y: rotation bits, z: scale bits, w: color bits

vec3 unpack111011(uint bits) {
    return vec3(
        float(bits >> 21u) / 2047.0,
        float((bits >> 11u) & 0x3ffu) / 1023.0,
        float(bits & 0x7ffu) / 2047.0
    );
}

vec4 unpack8888(uint bits) {
    return vec4(
        float(bits >> 24u) / 255.0,
        float((bits >> 16u) & 0xffu) / 255.0,
        float((bits >> 8u) & 0xffu) / 255.0,
        float(bits & 0xffu) / 255.0
    );
}

const float norm = 1.0 / (sqrt(2.0) * 0.5);

vec4 unpackRotation(uint bits) {
    float a = (float((bits >> 20u) & 0x3ffu) / 1023.0 - 0.5) * norm;
    float b = (float((bits >> 10u) & 0x3ffu) / 1023.0 - 0.5) * norm;
    float c = (float(bits & 0x3ffu) / 1023.0 - 0.5) * norm;
    float m = sqrt(1.0 - (a * a + b * b + c * c));

    uint mode = bits >> 30u;
    if (mode == 0u) return vec4(m, a, b, c);
    if (mode == 1u) return vec4(a, m, b, c);
    if (mode == 2u) return vec4(a, b, m, c);
    return vec4(a, b, c, m);
}


// read center
vec3 readCenter(SplatSource source) {
    uint w = uint(textureSize(chunkTexture, 0).x) / 5u;
    uint chunkId = source.id / 256u;
    ivec2 chunkUV = ivec2((chunkId % w) * 5u, chunkId / w);

    // read chunk and packed compressed data
    chunkDataA = texelFetch(chunkTexture, chunkUV, 0);
    chunkDataB = texelFetch(chunkTexture, chunkUV + ivec2(1, 0), 0);
    chunkDataC = texelFetch(chunkTexture, chunkUV + ivec2(2, 0), 0);
    chunkDataD = texelFetch(chunkTexture, chunkUV + ivec2(3, 0), 0);
    chunkDataE = texelFetch(chunkTexture, chunkUV + ivec2(4, 0), 0);
    packedData = texelFetch(packedTexture, source.uv, 0);

    return mix(chunkDataA.xyz, vec3(chunkDataA.w, chunkDataB.xy), unpack111011(packedData.x));
}

vec4 readColor(in SplatSource source) {
    vec4 r = unpack8888(packedData.w);
    return vec4(mix(chunkDataD.xyz, vec3(chunkDataD.w, chunkDataE.xy), r.rgb), r.w);
}

vec4 getRotation() {
    return unpackRotation(packedData.y);
}

vec3 getScale() {
    return exp(mix(vec3(chunkDataB.zw, chunkDataC.x), chunkDataC.yzw, unpack111011(packedData.z)));
}

// given a rotation matrix and scale vector, compute 3d covariance A and B
void readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) {
    mat3 rot = quatToMat3(getRotation());
    vec3 scale = getScale();

    // M = S * R
    mat3 M = transpose(mat3(
        scale.x * rot[0],
        scale.y * rot[1],
        scale.z * rot[2]
    ));

    covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));
    covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));
}
`;

var gsplatCompressedSHVS$1 = /* glsl */ `
#if SH_BANDS > 0

uniform highp usampler2D shTexture0;
uniform highp usampler2D shTexture1;
uniform highp usampler2D shTexture2;

vec4 unpack8888s(in uint bits) {
    return vec4((uvec4(bits) >> uvec4(0u, 8u, 16u, 24u)) & 0xffu) * (8.0 / 255.0) - 4.0;
}

void readSHData(in SplatSource source, out vec3 sh[15], out float scale) {
    // read the sh coefficients
    uvec4 shData0 = texelFetch(shTexture0, source.uv, 0);
    uvec4 shData1 = texelFetch(shTexture1, source.uv, 0);
    uvec4 shData2 = texelFetch(shTexture2, source.uv, 0);

    vec4 r0 = unpack8888s(shData0.x);
    vec4 r1 = unpack8888s(shData0.y);
    vec4 r2 = unpack8888s(shData0.z);
    vec4 r3 = unpack8888s(shData0.w);

    vec4 g0 = unpack8888s(shData1.x);
    vec4 g1 = unpack8888s(shData1.y);
    vec4 g2 = unpack8888s(shData1.z);
    vec4 g3 = unpack8888s(shData1.w);

    vec4 b0 = unpack8888s(shData2.x);
    vec4 b1 = unpack8888s(shData2.y);
    vec4 b2 = unpack8888s(shData2.z);
    vec4 b3 = unpack8888s(shData2.w);

    sh[0] =  vec3(r0.x, g0.x, b0.x);
    sh[1] =  vec3(r0.y, g0.y, b0.y);
    sh[2] =  vec3(r0.z, g0.z, b0.z);
    sh[3] =  vec3(r0.w, g0.w, b0.w);
    sh[4] =  vec3(r1.x, g1.x, b1.x);
    sh[5] =  vec3(r1.y, g1.y, b1.y);
    sh[6] =  vec3(r1.z, g1.z, b1.z);
    sh[7] =  vec3(r1.w, g1.w, b1.w);
    sh[8] =  vec3(r2.x, g2.x, b2.x);
    sh[9] =  vec3(r2.y, g2.y, b2.y);
    sh[10] = vec3(r2.z, g2.z, b2.z);
    sh[11] = vec3(r2.w, g2.w, b2.w);
    sh[12] = vec3(r3.x, g3.x, b3.x);
    sh[13] = vec3(r3.y, g3.y, b3.y);
    sh[14] = vec3(r3.z, g3.z, b3.z);

    scale = 1.0;
}

#endif
`;

var gsplatEvalSHVS$1 = /* glsl */ `
    #if SH_BANDS == 1
        #define SH_COEFFS 3
    #elif SH_BANDS == 2
        #define SH_COEFFS 8
    #elif SH_BANDS == 3
        #define SH_COEFFS 15
    #else
        #define SH_COEFFS 0
    #endif

    #if SH_BANDS > 0

    const float SH_C1 = 0.4886025119029199f;

    #if SH_BANDS > 1
        const float SH_C2_0 = 1.0925484305920792f;
        const float SH_C2_1 = -1.0925484305920792f;
        const float SH_C2_2 = 0.31539156525252005f;
        const float SH_C2_3 = -1.0925484305920792f;
        const float SH_C2_4 = 0.5462742152960396f;
    #endif

    #if SH_BANDS > 2
        const float SH_C3_0 = -0.5900435899266435f;
        const float SH_C3_1 = 2.890611442640554f;
        const float SH_C3_2 = -0.4570457994644658f;
        const float SH_C3_3 = 0.3731763325901154f;
        const float SH_C3_4 = -0.4570457994644658f;
        const float SH_C3_5 = 1.445305721320277f;
        const float SH_C3_6 = -0.5900435899266435f;
    #endif

    // see https://github.com/graphdeco-inria/gaussian-splatting/blob/main/utils/sh_utils.py
    vec3 evalSH(in vec3 sh[SH_COEFFS], in vec3 dir) {
        float x = dir.x;
        float y = dir.y;
        float z = dir.z;

        // 1st degree
        vec3 result = SH_C1 * (-sh[0] * y + sh[1] * z - sh[2] * x);

        #if SH_BANDS > 1
            // 2nd degree
            float xx = x * x;
            float yy = y * y;
            float zz = z * z;
            float xy = x * y;
            float yz = y * z;
            float xz = x * z;

            result +=
                sh[3] * (SH_C2_0 * xy) +
                sh[4] * (SH_C2_1 * yz) +
                sh[5] * (SH_C2_2 * (2.0 * zz - xx - yy)) +
                sh[6] * (SH_C2_3 * xz) +
                sh[7] * (SH_C2_4 * (xx - yy));
        #endif

        #if SH_BANDS > 2
            // 3rd degree
            result +=
                sh[8]  * (SH_C3_0 * y * (3.0 * xx - yy)) +
                sh[9]  * (SH_C3_1 * xy * z) +
                sh[10] * (SH_C3_2 * y * (4.0 * zz - xx - yy)) +
                sh[11] * (SH_C3_3 * z * (2.0 * zz - 3.0 * xx - 3.0 * yy)) +
                sh[12] * (SH_C3_4 * x * (4.0 * zz - xx - yy)) +
                sh[13] * (SH_C3_5 * z * (xx - yy)) +
                sh[14] * (SH_C3_6 * x * (xx - 3.0 * yy));
        #endif

        return result;
    }
    #endif
`;

var gsplatQuatToMat3VS$1 = /* glsl */ `
mat3 quatToMat3(vec4 R) {
    vec4 R2 = R + R;
    float X = R2.x * R.w;
    vec4 Y  = R2.y * R;
    vec4 Z  = R2.z * R;
    float W = R2.w * R.w;

    return mat3(
        1.0 - Z.z - W,
              Y.z + X,
              Y.w - Z.x,
              Y.z - X,
        1.0 - Y.y - W,
              Z.w + Y.x,
              Y.w + Z.x,
              Z.w - Y.x,
        1.0 - Y.y - Z.z
    );
}
`;

var gsplatSogsColorVS$1 = /* glsl */ `
uniform highp sampler2D packedSh0;

uniform float sh0_mins;
uniform float sh0_maxs;

float SH_C0 = 0.28209479177387814;

vec4 readColor(in SplatSource source) {
    vec3 clr = mix(vec3(sh0_mins), vec3(sh0_maxs), unpack111110(pack8888(texelFetch(packedSh0, source.uv, 0))));
    float alpha = float(packedSample.z & 0xffu) / 255.0;
    return vec4(vec3(0.5) + clr.xyz * SH_C0, alpha);
}
`;

var gsplatSogsDataVS$1 = /* glsl */ `
#include "gsplatPackingPS"

uniform highp usampler2D packedTexture;

uniform vec3 means_mins;
uniform vec3 means_maxs;

uniform float scales_mins;
uniform float scales_maxs;

uvec4 packedSample;

// read the model-space center of the gaussian
vec3 readCenter(SplatSource source) {

    // read the packed texture sample
    packedSample = texelFetch(packedTexture, source.uv, 0);

    vec3 l = unpack8888(packedSample.x).xyz;
    vec3 u = unpack8888(packedSample.y).xyz;
    vec3 n = (l * 255.0 + u * 255.0 * 256.0) / 65535.0;
    vec3 v = mix(means_mins, means_maxs, n);

    return sign(v) * (exp(abs(v)) - 1.0);
}

const float norm = 2.0 / sqrt(2.0);

// sample covariance vectors
void readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) {
    // decode rotation quaternion
    vec3 qdata = unpack8888(packedSample.z).xyz;
    vec3 sdata = unpack101010(packedSample.w >> 2u);

    uint qmode = packedSample.w & 0x3u;
    vec3 abc = (qdata - 0.5) * norm;
    float d = sqrt(max(0.0, 1.0 - dot(abc, abc)));

    vec4 quat = (qmode == 0u) ? vec4(d, abc) :
                ((qmode == 1u) ? vec4(abc.x, d, abc.yz) :
                ((qmode == 2u) ? vec4(abc.xy, d, abc.z) : vec4(abc, d)));

    mat3 rot = quatToMat3(quat);
    vec3 scale = exp(mix(vec3(scales_mins), vec3(scales_maxs), sdata));

    // M = S * R
    mat3 M = transpose(mat3(
        scale.x * rot[0],
        scale.y * rot[1],
        scale.z * rot[2]
    ));

    covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));
    covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));
}
`;

var gsplatSogsSHVS$1 = /* glsl */ `
uniform highp sampler2D packedShN;

uniform float shN_mins;
uniform float shN_maxs;

void readSHData(in SplatSource source, out vec3 sh[SH_COEFFS], out float scale) {
    // extract spherical harmonics palette index
    ivec2 t = ivec2(packedSample.xy & 255u);
    int n = t.x + t.y * 256;
    int u = (n % 64) * SH_COEFFS;
    int v = n / 64;

    // calculate offset into the centroids texture and read consecutive texels
    for (int i = 0; i < SH_COEFFS; i++) {
        sh[i] = mix(vec3(shN_mins), vec3(shN_maxs), unpack111110(pack8888(texelFetch(packedShN, ivec2(u + i, v), 0))));
    }

    scale = 1.0;
}
`;

var gsplatSourceFormatVS$1 = /* glsl */ `
#if defined(GSPLAT_WORKBUFFER_DATA)
    // getting data from work buffer
    #include "gsplatWorkBufferVS"
#elif GSPLAT_COMPRESSED_DATA == true
    #include "gsplatCompressedDataVS"
    #if SH_COEFFS > 0
        #include "gsplatCompressedSHVS"
    #endif
#elif GSPLAT_SOGS_DATA == true
    #include "gsplatSogsDataVS"
    #include "gsplatSogsColorVS"
    #if SH_COEFFS > 0
        #include "gsplatSogsSHVS"
    #endif
#else
    #include "gsplatDataVS"
    #include "gsplatColorVS"
    #if SH_COEFFS > 0
        #include "gsplatSHVS"
    #endif
#endif
`;

var gsplatStructsVS$1 = /* glsl */ `

// stores the source UV and order of the splat
struct SplatSource {
    uint order;         // render order
    uint id;            // splat id
    ivec2 uv;           // splat uv
    vec2 cornerUV;      // corner coordinates for this vertex of the gaussian (-1, -1)..(1, 1)
};

// stores the camera and clip space position of the gaussian center
struct SplatCenter {
    vec3 view;          // center in view space
    vec4 proj;          // center in clip space
    mat4 modelView;     // model-view matrix
    float projMat00;    // element [0][0] of the projection matrix
};

// stores the offset from center for the current gaussian
struct SplatCorner {
    vec2 offset;        // corner offset from center in clip space
    vec2 uv;            // corner uv
    #if GSPLAT_AA
        float aaFactor; // for scenes generated with antialiasing
    #endif

    vec2 v;
    float dlen;
};

`;

var gsplatCornerVS$1 = /* glsl */ `
uniform vec2 viewport;                  // viewport dimensions
uniform vec4 camera_params;             // 1 / far, far, near, isOrtho

// calculate the clip-space offset from the center for this gaussian
bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corner, vec3 covA, vec3 covB) {

    mat3 Vrk = mat3(
        covA.x, covA.y, covA.z, 
        covA.y, covB.x, covB.y,
        covA.z, covB.y, covB.z
    );

    float focal = viewport.x * center.projMat00;

    vec3 v = camera_params.w == 1.0 ? vec3(0.0, 0.0, 1.0) : center.view.xyz;
    float J1 = focal / v.z;
    vec2 J2 = -J1 / v.z * v.xy;
    mat3 J = mat3(
        J1, 0.0, J2.x, 
        0.0, J1, J2.y, 
        0.0, 0.0, 0.0
    );

    mat3 W = transpose(mat3(center.modelView));
    mat3 T = W * J;
    mat3 cov = transpose(T) * Vrk * T;

    #if GSPLAT_AA
        // calculate AA factor
        float detOrig = cov[0][0] * cov[1][1] - cov[0][1] * cov[0][1];
        float detBlur = (cov[0][0] + 0.3) * (cov[1][1] + 0.3) - cov[0][1] * cov[0][1];
        corner.aaFactor = sqrt(max(detOrig / detBlur, 0.0));
    #endif

    float diagonal1 = cov[0][0] + 0.3;
    float offDiagonal = cov[0][1];
    float diagonal2 = cov[1][1] + 0.3;

    float mid = 0.5 * (diagonal1 + diagonal2);
    float radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal));
    float lambda1 = mid + radius;
    float lambda2 = max(mid - radius, 0.1);

    // Use the smaller viewport dimension to limit the kernel size relative to the screen resolution.
    float vmin = min(1024.0, min(viewport.x, viewport.y));

    float l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);
    float l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);

    // early-out gaussians smaller than 2 pixels
    if (l1 < 2.0 && l2 < 2.0) {
        return false;
    }

    vec2 c = center.proj.ww / viewport;

    // cull against frustum x/y axes
    if (any(greaterThan(abs(center.proj.xy) - vec2(max(l1, l2)) * c, center.proj.ww))) {
        return false;
    }

    vec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1));
    vec2 v1 = l1 * diagonalVector;
    vec2 v2 = l2 * vec2(diagonalVector.y, -diagonalVector.x);

    corner.offset = (source.cornerUV.x * v1 + source.cornerUV.y * v2) * c;
    corner.uv = source.cornerUV;

    return true;
}

// calculate the clip-space offset from the center for this gaussian
bool initCorner(SplatSource source, SplatCenter center, out SplatCorner corner) {
    vec3 covA, covB;
    readCovariance(source, covA, covB);
    return initCornerCov(source, center, corner, covA, covB);
}
`;

var gsplatDataVS$1 = /* glsl */ `
uniform highp usampler2D transformA;
uniform highp sampler2D transformB;

// work values
uint tAw;

// read the model-space center of the gaussian
vec3 readCenter(SplatSource source) {
    // read transform data
    uvec4 tA = texelFetch(transformA, source.uv, 0);
    tAw = tA.w;
    return uintBitsToFloat(tA.xyz);
}

vec4 unpackRotation(vec3 packed) {
    return vec4(packed.xyz, sqrt(max(0.0, 1.0 - dot(packed, packed))));
}

// sample covariance vectors
void readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) {
    vec4 tB = texelFetch(transformB, source.uv, 0);

    mat3 rot = quatToMat3(unpackRotation(vec3(unpackHalf2x16(tAw), tB.w)).wxyz);
    vec3 scale = tB.xyz;
    
    // M = S * R
    mat3 M = transpose(mat3(
        scale.x * rot[0],
        scale.y * rot[1],
        scale.z * rot[2]
    ));

    covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));
    covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));
}
`;

var gsplatOutputVS$1 = /* glsl */ `

#include "tonemappingPS"
#include "decodePS"
#include "gammaPS"

// prepare the output color for the given gamma-space color
vec3 prepareOutputFromGamma(vec3 gammaColor) {
    #if TONEMAP == NONE
        #if GAMMA == NONE
            // convert to linear space
            return decodeGamma(gammaColor);
        #else
            // output gamma space color directly
            return gammaColor;
        #endif
    #else
        // apply tonemapping in linear space and output to linear or
        // gamma (which is handled by gammaCorrectOutput)
        return gammaCorrectOutput(toneMap(decodeGamma(gammaColor)));
    #endif
}
`;

var gsplatPS$1 = /* glsl */ `

#ifndef DITHER_NONE
    #include "bayerPS"
    #include "opacityDitherPS"
    varying float id;
#endif

#ifdef PICK_PASS
    #include "pickPS"
#endif

#if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)
    uniform float alphaClip;
#endif

#ifdef PREPASS_PASS
    varying float vLinearDepth;
    #include "floatAsUintPS"
#endif

// Fast approximate e^x based on https://nic.schraudolph.org/pubs/Schraudolph99.pdf
const float  EXP_A      = 12102203.0;   // ≈ 2^23 / ln(2)
const int    EXP_BC_RMS = 1064866808;   // (127 << 23) - 60801 * 8
float fastExp(float x) {
    int i = int(EXP_A * x) + EXP_BC_RMS;
    return intBitsToFloat(i);
}

varying mediump vec2 gaussianUV;
varying mediump vec4 gaussianColor;

void main(void) {
    mediump float A = dot(gaussianUV, gaussianUV);
    if (A > 1.0) {
        discard;
    }

    // evaluate alpha
    mediump float alpha = fastExp(-A * 4.0) * gaussianColor.a;

    #if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)
        if (alpha < alphaClip) {
            discard;
        }
    #endif

    #ifdef PICK_PASS

        gl_FragColor = getPickOutput();

    #elif SHADOW_PASS

        gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);

    #elif PREPASS_PASS

        gl_FragColor = float2vec4(vLinearDepth);

    #else
        if (alpha < 1.0 / 255.0) {
            discard;
        }

        #ifndef DITHER_NONE
            opacityDither(alpha, id * 0.013);
        #endif

        gl_FragColor = vec4(gaussianColor.xyz * alpha, alpha);
    #endif
}
`;

var gsplatSHVS$1 = /* glsl */ `

#if SH_BANDS > 0

// unpack signed 11 10 11 bits
vec3 unpack111011s(uint bits) {
    return vec3((uvec3(bits) >> uvec3(21u, 11u, 0u)) & uvec3(0x7ffu, 0x3ffu, 0x7ffu)) / vec3(2047.0, 1023.0, 2047.0) * 2.0 - 1.0;
}

// fetch quantized spherical harmonic coefficients
void fetchScale(in uvec4 t, out float scale, out vec3 a, out vec3 b, out vec3 c) {
    scale = uintBitsToFloat(t.x);
    a = unpack111011s(t.y);
    b = unpack111011s(t.z);
    c = unpack111011s(t.w);
}

// fetch quantized spherical harmonic coefficients
void fetch(in uvec4 t, out vec3 a, out vec3 b, out vec3 c, out vec3 d) {
    a = unpack111011s(t.x);
    b = unpack111011s(t.y);
    c = unpack111011s(t.z);
    d = unpack111011s(t.w);
}

void fetch(in uint t, out vec3 a) {
    a = unpack111011s(t);
}

#if SH_BANDS == 1
    uniform highp usampler2D splatSH_1to3;
    void readSHData(in SplatSource source, out vec3 sh[3], out float scale) {
        fetchScale(texelFetch(splatSH_1to3, source.uv, 0), scale, sh[0], sh[1], sh[2]);
    }
#elif SH_BANDS == 2
    uniform highp usampler2D splatSH_1to3;
    uniform highp usampler2D splatSH_4to7;
    uniform highp usampler2D splatSH_8to11;
    void readSHData(in SplatSource source, out vec3 sh[8], out float scale) {
        fetchScale(texelFetch(splatSH_1to3, source.uv, 0), scale, sh[0], sh[1], sh[2]);
        fetch(texelFetch(splatSH_4to7, source.uv, 0), sh[3], sh[4], sh[5], sh[6]);
        fetch(texelFetch(splatSH_8to11, source.uv, 0).x, sh[7]);
    }
#else
    uniform highp usampler2D splatSH_1to3;
    uniform highp usampler2D splatSH_4to7;
    uniform highp usampler2D splatSH_8to11;
    uniform highp usampler2D splatSH_12to15;
    void readSHData(in SplatSource source, out vec3 sh[15], out float scale) {
        fetchScale(texelFetch(splatSH_1to3, source.uv, 0), scale, sh[0], sh[1], sh[2]);
        fetch(texelFetch(splatSH_4to7, source.uv, 0), sh[3], sh[4], sh[5], sh[6]);
        fetch(texelFetch(splatSH_8to11, source.uv, 0), sh[7], sh[8], sh[9], sh[10]);
        fetch(texelFetch(splatSH_12to15, source.uv, 0), sh[11], sh[12], sh[13], sh[14]);
    }
#endif

#endif
`;

var gsplatSourceVS$1 = /* glsl */ `
attribute vec3 vertex_position;         // xy: cornerUV, z: render order offset
attribute uint vertex_id_attrib;        // render order base

uniform uint numSplats;                 // total number of splats
uniform highp usampler2D splatOrder;    // per-splat index to source gaussian

// initialize the splat source structure
bool initSource(out SplatSource source) {
    uint w = uint(textureSize(splatOrder, 0).x);

    // calculate splat order
    source.order = vertex_id_attrib + uint(vertex_position.z);

    // return if out of range (since the last block of splats may be partially full)
    if (source.order >= numSplats) {
        return false;
    }

    ivec2 orderUV = ivec2(source.order % w, source.order / w);

    // read splat id
    source.id = texelFetch(splatOrder, orderUV, 0).r;

    // map id to uv
    source.uv = ivec2(source.id % w, source.id / w);

    // get the corner
    source.cornerUV = vertex_position.xy;

    return true;
}
`;

var gsplatVS$1 = /* glsl */ `
#include "gsplatCommonVS"

varying mediump vec2 gaussianUV;
varying mediump vec4 gaussianColor;

#ifndef DITHER_NONE
    varying float id;
#endif

mediump vec4 discardVec = vec4(0.0, 0.0, 2.0, 1.0);

#ifdef PREPASS_PASS
    varying float vLinearDepth;
#endif

void main(void) {
    // read gaussian details
    SplatSource source;
    if (!initSource(source)) {
        gl_Position = discardVec;
        return;
    }

    vec3 modelCenter = readCenter(source);

    SplatCenter center;
    if (!initCenter(modelCenter, center)) {
        gl_Position = discardVec;
        return;
    }

    // project center to screen space
    SplatCorner corner;
    if (!initCorner(source, center, corner)) {
        gl_Position = discardVec;
        return;
    }

    // read color
    vec4 clr = readColor(source);

    #if GSPLAT_AA
        // apply AA compensation
        clr.a *= corner.aaFactor;
    #endif

    // evaluate spherical harmonics
    #if SH_BANDS > 0
        // calculate the model-space view direction
        vec3 dir = normalize(center.view * mat3(center.modelView));

        // read sh coefficients
        vec3 sh[SH_COEFFS];
        float scale;
        readSHData(source, sh, scale);

        // evaluate
        clr.xyz += evalSH(sh, dir) * scale;
    #endif

    clipCorner(corner, clr.w);

    // write output
    gl_Position = center.proj + vec4(corner.offset, 0, 0);
    gaussianUV = corner.uv;
    gaussianColor = vec4(prepareOutputFromGamma(max(clr.xyz, 0.0)), clr.w);

    #ifndef DITHER_NONE
        id = float(source.id);
    #endif

    #ifdef PREPASS_PASS
        vLinearDepth = -center.view.z;
    #endif
}
`;

var gsplatWorkBufferVS$1 = /* glsl */ `
uniform highp sampler2D center;
uniform highp sampler2D covA;
uniform highp sampler2D covB;
uniform mediump sampler2D splatColor;

// read the model-space center of the gaussian
vec3 readCenter(SplatSource source) {
    return texelFetch(center, source.uv, 0).xyz;
}

// sample covariance vectors
void readCovariance(in SplatSource source, out vec3 cov_A, out vec3 cov_B) {
    cov_A = texelFetch(covA, source.uv, 0).xyz;
    cov_B = texelFetch(covB, source.uv, 0).xyz;
}

vec4 readColor(in SplatSource source) {
    return texelFetch(splatColor, source.uv, 0);
}
`;

// A simple vertex shader used to render a quad, which requires 'vec2 aPosition' in the vertex
// buffer, and generates uv coordinates uv0 for use in the fragment shader.
var quadVS$1 = /* glsl */ `
    attribute vec2 aPosition;
    varying vec2 uv0;
    void main(void)
    {
        gl_Position = vec4(aPosition, 0.0, 1.0);
        uv0 = getImageEffectUV((aPosition.xy + 1.0) * 0.5);
    }
`;

var immediateLinePS$1 = /* glsl */ `
        #include "gammaPS"
        varying vec4 color;
        void main(void) {
            gl_FragColor = vec4(gammaCorrectOutput(decodeGamma(color.rgb)), color.a);
        }
`;

var immediateLineVS$1 = /* glsl */ `
    attribute vec4 vertex_position;
    attribute vec4 vertex_color;
    uniform mat4 matrix_model;
    uniform mat4 matrix_viewProjection;
    varying vec4 color;
    void main(void) {
        color = vertex_color;
        gl_Position = matrix_viewProjection * matrix_model * vertex_position;
    }
`;

var iridescenceDiffractionPS$1 = /* glsl */ `
uniform float material_iridescenceRefractionIndex;

float iridescence_iorToFresnel(float transmittedIor, float incidentIor) {
    return pow((transmittedIor - incidentIor) / (transmittedIor + incidentIor), 2.0);
}

vec3 iridescence_iorToFresnel(vec3 transmittedIor, float incidentIor) {
    return pow((transmittedIor - vec3(incidentIor)) / (transmittedIor + vec3(incidentIor)), vec3(2.0));
}

vec3 iridescence_fresnelToIor(vec3 f0) {
    vec3 sqrtF0 = sqrt(f0);
    return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0);
}

vec3 iridescence_sensitivity(float opd, vec3 shift) {
    float PI = 3.141592653589793;
    float phase = 2.0 * PI * opd * 1.0e-9;
    const vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13);
    const vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06);
    const vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09);

    vec3 xyz = val * sqrt(2.0 * PI * var) * cos(pos * phase + shift) * exp(-pow(phase, 2.0) * var);
    xyz.x += 9.7470e-14 * sqrt(2.0 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(-4.5282e+09 * pow(phase, 2.0));
    xyz /= vec3(1.0685e-07);

    const mat3 XYZ_TO_REC709 = mat3(
        3.2404542, -0.9692660,  0.0556434,
       -1.5371385,  1.8760108, -0.2040259,
       -0.4985314,  0.0415560,  1.0572252
    );

    return XYZ_TO_REC709 * xyz;
}

float iridescence_fresnel(float cosTheta, float f0) {
    float x = clamp(1.0 - cosTheta, 0.0, 1.0);
    float x2 = x * x;
    float x5 = x * x2 * x2;
    return f0 + (1.0 - f0) * x5;
} 

vec3 iridescence_fresnel(float cosTheta, vec3 f0) {
    float x = clamp(1.0 - cosTheta, 0.0, 1.0);
    float x2 = x * x;
    float x5 = x * x2 * x2; 
    return f0 + (vec3(1.0) - f0) * x5;
}

vec3 calcIridescence(float outsideIor, float cosTheta, vec3 base_f0, float iridescenceThickness) {
    float PI = 3.141592653589793;

    float iridescenceIor = mix(outsideIor, material_iridescenceRefractionIndex, smoothstep(0.0, 0.03, iridescenceThickness));
    float sinTheta2Sq = pow(outsideIor / iridescenceIor, 2.0) * (1.0 - pow(cosTheta, 2.0));
    float cosTheta2Sq = 1.0 - sinTheta2Sq;

    if (cosTheta2Sq < 0.0) {
        return vec3(1.0);
    }

    float cosTheta2 = sqrt(cosTheta2Sq);

    float r0 = iridescence_iorToFresnel(iridescenceIor, outsideIor);
    float r12 = iridescence_fresnel(cosTheta, r0);
    float r21 = r12;
    float t121 = 1.0 - r12;

    float phi12 = iridescenceIor < outsideIor ? PI : 0.0;
    float phi21 = PI - phi12;

    vec3 baseIor = iridescence_fresnelToIor(base_f0 + vec3(0.0001));
    vec3 r1 = iridescence_iorToFresnel(baseIor, iridescenceIor);
    vec3 r23 = iridescence_fresnel(cosTheta2, r1);

    vec3 phi23 = vec3(0.0);
    if (baseIor[0] < iridescenceIor) phi23[0] = PI;
    if (baseIor[1] < iridescenceIor) phi23[1] = PI;
    if (baseIor[2] < iridescenceIor) phi23[2] = PI;
    float opd = 2.0 * iridescenceIor * iridescenceThickness * cosTheta2;
    vec3 phi = vec3(phi21) + phi23; 

    vec3 r123Sq = clamp(r12 * r23, 1e-5, 0.9999);
    vec3 r123 = sqrt(r123Sq);
    vec3 rs = pow(t121, 2.0) * r23 / (1.0 - r123Sq);

    vec3 c0 = r12 + rs;
    vec3 i = c0;

    vec3 cm = rs - t121;
    for (int m = 1; m <= 2; m++) {
        cm *= r123;
        vec3 sm = 2.0 * iridescence_sensitivity(float(m) * opd, float(m) * phi);
        i += cm * sm;
    }
    return max(i, vec3(0.0));
}

vec3 getIridescence(float cosTheta, vec3 specularity, float iridescenceThickness) {
    return calcIridescence(1.0, cosTheta, specularity, iridescenceThickness);
}
`;

var iridescencePS$1 = /* glsl */ `
#ifdef STD_IRIDESCENCE_CONSTANT
uniform float material_iridescence;
#endif

void getIridescence() {
    float iridescence = 1.0;

    #ifdef STD_IRIDESCENCE_CONSTANT
    iridescence *= material_iridescence;
    #endif

    #ifdef STD_IRIDESCENCE_TEXTURE
    iridescence *= texture2DBias({STD_IRIDESCENCE_TEXTURE_NAME}, {STD_IRIDESCENCE_TEXTURE_UV}, textureBias).{STD_IRIDESCENCE_TEXTURE_CHANNEL};
    #endif

    dIridescence = iridescence; 
}
`;

var iridescenceThicknessPS$1 = /* glsl */ `
uniform float material_iridescenceThicknessMax;

#ifdef STD_IRIDESCENCETHICKNESS_TEXTURE
uniform float material_iridescenceThicknessMin;
#endif

void getIridescenceThickness() {

    #ifdef STD_IRIDESCENCETHICKNESS_TEXTURE
        float blend = texture2DBias({STD_IRIDESCENCETHICKNESS_TEXTURE_NAME}, {STD_IRIDESCENCETHICKNESS_TEXTURE_UV}, textureBias).{STD_IRIDESCENCETHICKNESS_TEXTURE_CHANNEL};
        float iridescenceThickness = mix(material_iridescenceThicknessMin, material_iridescenceThicknessMax, blend);
    #else
        float iridescenceThickness = material_iridescenceThicknessMax;
    #endif

    dIridescenceThickness = iridescenceThickness; 
}
`;

var iorPS$1 = /* glsl */ `
#ifdef STD_IOR_CONSTANT
uniform float material_refractionIndex;
#endif

void getIor() {
#ifdef STD_IOR_CONSTANT
    dIor = material_refractionIndex;
#else
    dIor = 1.0 / 1.5;
#endif
}
`;

// uniforms for a light with index {i}, driven by defines
var lightDeclarationPS$1 = /* glsl */ `
#if defined(LIGHT{i})

    uniform vec3 light{i}_color;

    #if LIGHT{i}TYPE == DIRECTIONAL
        uniform vec3 light{i}_direction;
    #else

        #define LIT_CODE_LIGHTS_POINT // include functionality for point lights

        uniform vec3 light{i}_position;
        uniform float light{i}_radius;

        #if LIGHT{i}TYPE == SPOT

            #define LIT_CODE_LIGHTS_SPOT // include functionality for spot lights

            uniform vec3 light{i}_direction;
            uniform float light{i}_innerConeAngle;
            uniform float light{i}_outerConeAngle;
        #endif
    #endif

    // area lights
    #if LIGHT{i}SHAPE != PUNCTUAL

        #define LIT_CODE_FALLOFF_SQUARED // include functionality for inverse squared falloff

        #if LIGHT{i}TYPE == DIRECTIONAL
            uniform vec3 light{i}_position;
        #endif
        uniform vec3 light{i}_halfWidth;
        uniform vec3 light{i}_halfHeight;
    #else

        #if LIGHT{i}FALLOFF == LINEAR
            #define LIT_CODE_FALLOFF_LINEAR // include functionality for linear falloff
        #endif

        #if LIGHT{i}FALLOFF == INVERSESQUARED
            #define LIT_CODE_FALLOFF_SQUARED // include functionality for inverse squared falloff
        #endif

    #endif

    // shadow casting
    #if defined(LIGHT{i}CASTSHADOW)

        uniform mat4 light{i}_shadowMatrix;
        uniform float light{i}_shadowIntensity;
        uniform vec4 light{i}_shadowParams; // width, height, bias, radius

        #if LIGHT{i}SHADOWTYPE == PCSS_32F
            uniform float light{i}_shadowSearchArea;
            uniform vec4 light{i}_cameraParams;
            #if LIGHT{i}TYPE == DIRECTIONAL
                uniform vec4 light{i}_softShadowParams;
            #endif
        #endif

        // directional (cascaded) shadows
        #if LIGHT{i}TYPE == DIRECTIONAL
            uniform mat4 light{i}_shadowMatrixPalette[4];
            uniform vec4 light{i}_shadowCascadeDistances;
            uniform int light{i}_shadowCascadeCount;
            uniform float light{i}_shadowCascadeBlend;
        #endif

        #if LIGHT{i}TYPE == OMNI
            #if defined(LIGHT{i}SHADOW_PCF)
                uniform samplerCubeShadow light{i}_shadowMap;
            #else
                uniform samplerCube light{i}_shadowMap;
            #endif
        #else
            #if defined(LIGHT{i}SHADOW_PCF)
                uniform sampler2DShadow light{i}_shadowMap;
            #else
                uniform sampler2D light{i}_shadowMap;
            #endif
        #endif

    #endif

    // cookie
    #if defined(LIGHT{i}COOKIE)

        #define LIT_CODE_COOKIE // include functionality for cookies

        #if LIGHT{i}TYPE == OMNI
            uniform samplerCube light{i}_cookie;
            uniform float light{i}_cookieIntensity;
            #if !defined(LIGHT{i}CASTSHADOW)
                uniform mat4 light{i}_shadowMatrix;
            #endif
        #endif

        #if LIGHT{i}TYPE == SPOT
            uniform sampler2D light{i}_cookie;
            uniform float light{i}_cookieIntensity;
            #if !defined(LIGHT{i}CASTSHADOW)
                uniform mat4 light{i}_shadowMatrix;
            #endif
            #if defined(LIGHT{i}COOKIE_TRANSFORM)
                uniform vec4 light{i}_cookieMatrix;
                uniform vec2 light{i}_cookieOffset;
            #endif
        #endif
    #endif
#endif
`;

var lightDiffuseLambertPS$1 = /* glsl */ `
float getLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm) {
    return max(dot(worldNormal, -lightDirNorm), 0.0);
}
`;

var lightDirPointPS$1 = /* glsl */ `
vec3 evalOmniLight(vec3 lightPosW) {
    return vPositionW - lightPosW;
}
`;

// evaluation of a light with index {i}, driven by defines
var lightEvaluationPS$1 = /* glsl */ `
#if defined(LIGHT{i})
    evaluateLight{i}(
        #if defined(LIT_IRIDESCENCE)
            iridescenceFresnel
        #endif
    );
#endif
`;

// functions used to evaluate the light
var lightFunctionLightPS$1 = /* glsl */ `
#if defined(LIGHT{i})

void evaluateLight{i}(
    #if defined(LIT_IRIDESCENCE)
        vec3 iridescenceFresnel
    #endif
) {

    // light color
    vec3 lightColor = light{i}_color;

    #if LIGHT{i}TYPE == DIRECTIONAL && !defined(LIT_SHADOW_CATCHER)
        // early return if the light color is black (used by shadow catcher - this way this light is very cheap)
        if (all(equal(lightColor, vec3(0.0)))) {
            return;
        }
    #endif

    #if LIGHT{i}TYPE == DIRECTIONAL // directional light

        dLightDirNormW = light{i}_direction;
        dAtten = 1.0;

    #else // omni or spot light
        
        vec3 lightDirW = evalOmniLight(light{i}_position);
        dLightDirNormW = normalize(lightDirW);

        // cookie attenuation
        #if defined(LIGHT{i}COOKIE)

            #if LIGHT{i}TYPE == SPOT
                #ifdef LIGHT{i}COOKIE_FALLOFF
                    #ifdef LIGHT{i}COOKIE_TRANSFORM
                        vec3 cookieAttenuation = getCookie2DXform(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity, light{i}_cookieMatrix, light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};
                    #else
                        vec3 cookieAttenuation = getCookie2D(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};
                    #endif
                #else
                    #ifdef LIGHT{i}COOKIE_TRANSFORM
                        vec3 cookieAttenuation = getCookie2DClipXform(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity, light{i}_cookieMatrix, light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};
                    #else
                        vec3 cookieAttenuation = getCookie2DClip(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};
                    #endif
                #endif
            #endif

            #if LIGHT{i}TYPE == OMNI
                vec3 cookieAttenuation = getCookieCube(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};
            #endif

            // multiply light color by the cookie attenuation
            lightColor *= cookieAttenuation;

        #endif

        // distance falloff
        #if LIGHT{i}SHAPE == PUNCTUAL
            #if LIGHT{i}FALLOFF == LINEAR
                dAtten = getFalloffLinear(light{i}_radius, lightDirW);
            #else
                dAtten = getFalloffInvSquared(light{i}_radius, lightDirW);
            #endif
        #else
            // non punctual lights only gets the range window here
            dAtten = getFalloffWindow(light{i}_radius, lightDirW);
        #endif

        // spot light angle falloff
        #if LIGHT{i}TYPE == SPOT
            #if !defined(LIGHT{i}COOKIE) || defined(LIGHT{i}COOKIE_FALLOFF)
                dAtten *= getSpotEffect(light{i}_direction, light{i}_innerConeAngle, light{i}_outerConeAngle, dLightDirNormW);
            #endif
        #endif
    #endif

    if (dAtten < 0.00001) {
        return;
    }

    // evaluate area light values
    #if LIGHT{i}SHAPE != PUNCTUAL
        #if LIGHT{i}SHAPE == RECT
            calcRectLightValues(light{i}_position, light{i}_halfWidth, light{i}_halfHeight);
        #elif LIGHT{i}SHAPE == DISK
            calcDiskLightValues(light{i}_position, light{i}_halfWidth, light{i}_halfHeight);
        #elif LIGHT{i}SHAPE == SPHERE
            calcSphereLightValues(light{i}_position, light{i}_halfWidth, light{i}_halfHeight);
        #endif
    #endif

    // diffuse lighting - LTC lights do not mix diffuse lighting into attenuation that affects specular
    #if LIGHT{i}SHAPE != PUNCTUAL

        // attenDiffuse - separate diffuse attenuation for non-punctual light sources

        #if LIGHT{i}TYPE == DIRECTIONAL
            // NB: A better approximation perhaps using wrap lighting could be implemented here
            float attenDiffuse = getLightDiffuse(litArgs_worldNormal, dViewDirW, dLightDirNormW);
        #else
            // 16.0 is a constant that is in getFalloffInvSquared()
            #if LIGHT{i}SHAPE == RECT
                float attenDiffuse = getRectLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;
            #elif LIGHT{i}SHAPE == DISK
                float attenDiffuse = getDiskLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;
            #elif LIGHT{i}SHAPE == SPHERE
                float attenDiffuse = getSphereLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;
            #endif
        #endif
    #else
        // one parameter is unused for punctual lights
        dAtten *= getLightDiffuse(litArgs_worldNormal, vec3(0.0), dLightDirNormW);
    #endif

    // apply the shadow attenuation
    #ifdef LIGHT{i}CASTSHADOW

        #if LIGHT{i}TYPE == DIRECTIONAL
            float shadow = getShadow{i}(vec3(0.0));
        #else
            float shadow = getShadow{i}(lightDirW);
        #endif

        // Apply shadow intensity to the shadow value
        shadow = mix(1.0, shadow, light{i}_shadowIntensity);

        dAtten *= shadow;

        #if defined(LIT_SHADOW_CATCHER) && LIGHT{i}TYPE == DIRECTIONAL
            // accumulate shadows for directional lights
            dShadowCatcher *= shadow;
        #endif            

    #endif

    #if LIGHT{i}SHAPE != PUNCTUAL
        // area light - they do not mix diffuse lighting into specular attenuation
        #ifdef LIT_SPECULAR
            dDiffuseLight += ((attenDiffuse * dAtten) * lightColor) * (1.0 - dLTCSpecFres);
        #else
            dDiffuseLight += (attenDiffuse * dAtten) * lightColor;
        #endif                        
    #else
        // punctual light
        #if defined(AREA_LIGHTS) && defined(LIT_SPECULAR)
            dDiffuseLight += (dAtten * lightColor) * (1.0 - litArgs_specularity);
        #else
            dDiffuseLight += dAtten * lightColor;
        #endif
    #endif

    // specular lighting
    #ifdef LIGHT{i}AFFECT_SPECULARITY

        #if LIGHT{i}SHAPE != PUNCTUAL // area light

            #ifdef LIT_CLEARCOAT
                #if LIGHT{i}SHAPE == RECT
                    ccSpecularLight += ccLTCSpecFres * getRectLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor;
                #elif LIGHT{i}SHAPE == DISK
                    ccSpecularLight += ccLTCSpecFres * getDiskLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor;
                #elif LIGHT{i}SHAPE == SPHERE
                    ccSpecularLight += ccLTCSpecFres * getSphereLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor;
                #endif
            #endif

            #ifdef LIT_SPECULAR
                #if LIGHT{i}SHAPE == RECT
                    dSpecularLight += dLTCSpecFres * getRectLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor;
                #elif LIGHT{i}SHAPE == DISK
                    dSpecularLight += dLTCSpecFres * getDiskLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor;
                #elif LIGHT{i}SHAPE == SPHERE
                    dSpecularLight += dLTCSpecFres * getSphereLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor;
                #endif
            #endif

        #else // punctual light

            // is fresnel needed
            #if LIGHT{i}TYPE == DIRECTIONAL && LIT_FRESNEL_MODEL != NONE
                #define LIGHT{i}FRESNEL
            #endif

            #ifdef LIT_SPECULAR
                vec3 halfDirW = normalize(-dLightDirNormW + dViewDirW);
            #endif

            // if LTC lights are present, specular must be accumulated with specularity (specularity is pre multiplied by punctual light fresnel)
            #ifdef LIT_CLEARCOAT
                vec3 lightspecularCC = getLightSpecular(halfDirW, ccReflDirW, litArgs_clearcoat_worldNormal, dViewDirW, dLightDirNormW, litArgs_clearcoat_gloss, dTBN) * dAtten * lightColor;
                #ifdef LIGHT{i}FRESNEL
                    lightspecularCC *= getFresnelCC(dot(dViewDirW, halfDirW));
                #endif
                ccSpecularLight += lightspecularCC;
            #endif

            #ifdef LIT_SHEEN
                sSpecularLight += getLightSpecularSheen(halfDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_sheen_gloss) * dAtten * lightColor;
            #endif

            #ifdef LIT_SPECULAR

                vec3 lightSpecular = getLightSpecular(halfDirW, dReflDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_gloss, dTBN) * dAtten * lightColor;
                #ifdef LIGHT{i}FRESNEL

                    #if defined(LIT_IRIDESCENCE)
                        lightSpecular *= getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity, iridescenceFresnel, litArgs_iridescence_intensity);
                    #else
                        lightSpecular *= getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity);
                    #endif

                #else
                    lightSpecular *= litArgs_specularity;
                #endif
                
                dSpecularLight += lightSpecular;
            #endif
        #endif
    #endif
}
#endif
`;

// functions used to evaluate the light shadow
var lightFunctionShadowPS$1 = /* glsl */ `

// shadow casting functionality
#ifdef LIGHT{i}CASTSHADOW

    // generate shadow coordinates function, based on per light defines:
    // - _SHADOW_SAMPLE_NORMAL_OFFSET
    // - _SHADOW_SAMPLE_ORTHO
    // - _SHADOW_SAMPLE_POINT
    // - _SHADOW_SAMPLE_SOURCE_ZBUFFER
    vec3 getShadowSampleCoord{i}(mat4 shadowTransform, vec4 shadowParams, vec3 worldPosition, vec3 lightPos, inout vec3 lightDir, vec3 lightDirNorm, vec3 normal) {

        vec3 surfacePosition = worldPosition;

        #ifdef LIGHT{i}_SHADOW_SAMPLE_POINT
            #ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET
                float distScale = length(lightDir);
                surfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;
                lightDir = surfacePosition - lightPos;
                return lightDir;
            #endif
        #else
            #ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER
                #ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET
                    surfacePosition = surfacePosition + normal * shadowParams.y;
                #endif
            #else
                #ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET
                    #ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO
                        float distScale = 1.0;
                    #else
                        float distScale = abs(dot(vPositionW - lightPos, lightDirNorm));
                    #endif
                    surfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;
                #endif
            #endif

            vec4 positionInShadowSpace = shadowTransform * vec4(surfacePosition, 1.0);
            #ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO
                positionInShadowSpace.z = saturate(positionInShadowSpace.z) - 0.0001;
            #else
                #ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER
                    positionInShadowSpace.xyz /= positionInShadowSpace.w;
                #else
                    positionInShadowSpace.xy /= positionInShadowSpace.w;
                    positionInShadowSpace.z = length(lightDir) * shadowParams.w;
                #endif
            #endif

            // this is currently unused
            #ifdef SHADOW_SAMPLE_Z_BIAS
                // positionInShadowSpace.z += getShadowBias(shadowParams.x, shadowParams.z);
            #endif
            surfacePosition = positionInShadowSpace.xyz;
        #endif

        return surfacePosition;
    }

    // shadow evaluation function
    float getShadow{i}(vec3 lightDirW) {

        // directional shadow cascades
        #ifdef LIGHT{i}_SHADOW_CASCADES

            // select shadow cascade matrix
            int cascadeIndex = getShadowCascadeIndex(light{i}_shadowCascadeDistances, light{i}_shadowCascadeCount);

            #ifdef LIGHT{i}_SHADOW_CASCADE_BLEND
                cascadeIndex = ditherShadowCascadeIndex(cascadeIndex, light{i}_shadowCascadeDistances, light{i}_shadowCascadeCount, light{i}_shadowCascadeBlend);
            #endif

            mat4 shadowMatrix = light{i}_shadowMatrixPalette[cascadeIndex];

        #else

            mat4 shadowMatrix = light{i}_shadowMatrix;

        #endif

        #if LIGHT{i}TYPE == DIRECTIONAL
            // directional light does not have a position
            vec3 shadowCoord = getShadowSampleCoord{i}(shadowMatrix, light{i}_shadowParams, vPositionW, vec3(0.0), lightDirW, dLightDirNormW, dVertexNormalW);
        #else
            vec3 shadowCoord = getShadowSampleCoord{i}(shadowMatrix, light{i}_shadowParams, vPositionW, light{i}_position, lightDirW, dLightDirNormW, dVertexNormalW);
        #endif

        // Fade directional shadow at the far distance
        #if LIGHT{i}TYPE == DIRECTIONAL
            shadowCoord = fadeShadow(shadowCoord, light{i}_shadowCascadeDistances);
        #endif

        // ----- sample the shadow -----

        #if LIGHT{i}TYPE == DIRECTIONAL // ----- directional light -----

            #if LIGHT{i}SHADOWTYPE == VSM_16F
                return getShadowVSM16(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 5.54);
            #endif

            #if LIGHT{i}SHADOWTYPE == VSM_32F
                return getShadowVSM32(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 15.0);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCSS_32F

                #if LIGHT{i}SHAPE != PUNCTUAL
                    vec2 shadowSearchArea = vec2(length(light{i}_halfWidth), length(light{i}_halfHeight)) * light{i}_shadowSearchArea;
                    return getShadowPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, shadowSearchArea, lightDirW);
                #else
                    return getShadowPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, light{i}_softShadowParams, lightDirW);
                #endif

            #endif

            #if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F
                return getShadowPCF1x1(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F
                return getShadowPCF3x3(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F
                return getShadowPCF5x5(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);
            #endif

        #endif


        #if LIGHT{i}TYPE == SPOT // ----- spot light -----

            #if LIGHT{i}SHADOWTYPE == VSM_16F
                return getShadowSpotVSM16(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 5.54, lightDirW);
            #endif

            #if LIGHT{i}SHADOWTYPE == VSM_32F
                return getShadowSpotVSM32(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 15.0, lightDirW);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCSS_32F

                #if LIGHT{i}SHAPE != PUNCTUAL
                    vec2 shadowSearchArea = vec2(length(light{i}_halfWidth), length(light{i}_halfHeight)) * light{i}_shadowSearchArea;
                #else
                    vec2 shadowSearchArea = vec2(light{i}_shadowSearchArea);
                #endif

                return getShadowSpotPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, shadowSearchArea, lightDirW);

            #endif

            #if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F
                return getShadowSpotPCF1x1(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F
                return getShadowSpotPCF3x3(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F
                return getShadowSpotPCF5x5(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);
            #endif

        #endif


        #if LIGHT{i}TYPE == OMNI // ----- omni light -----

            #if LIGHT{i}SHADOWTYPE == PCSS_32F

                #if LIGHT{i}SHAPE != PUNCTUAL
                    vec2 shadowSearchArea = vec2(length(light{i}_halfWidth), length(light{i}_halfHeight)) * light{i}_shadowSearchArea;
                #else
                    vec2 shadowSearchArea = vec2(light{i}_shadowSearchArea);
                #endif

                return getShadowOmniPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, shadowSearchArea, lightDirW);

            #endif

            #if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F
                return getShadowOmniPCF1x1(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, lightDirW);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F
                return getShadowOmniPCF3x3(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, lightDirW);
            #endif

        #endif
    }
#endif
`;

// functionality includes for lighting / shadowing code.
var lightingPS$1 = /* glsl */ `

#ifdef LIT_CLUSTERED_LIGHTS
    // all this functionality that needs to be included for clustered lighting
    #define LIT_CODE_FALLOFF_LINEAR
    #define LIT_CODE_FALLOFF_SQUARED
    #define LIT_CODE_LIGHTS_POINT
    #define LIT_CODE_LIGHTS_SPOT
#endif

#ifdef AREA_LIGHTS
    uniform highp sampler2D areaLightsLutTex1;
    uniform highp sampler2D areaLightsLutTex2;
#endif

#ifdef LIT_LIGHTING
    #include "lightDiffuseLambertPS"

    // area lights
    #if defined(AREA_LIGHTS) || defined(LIT_CLUSTERED_AREA_LIGHTS)
        #include "ltcPS"
    #endif
#endif

#ifdef SHADOW_DIRECTIONAL
    #include "shadowCascadesPS"
#endif

#if defined(SHADOW_KIND_PCF1)
    #include "shadowPCF1PS"
#endif

#if defined(SHADOW_KIND_PCF3)
    #include "shadowPCF3PS"
#endif

#if defined(SHADOW_KIND_PCF5)
    #include "shadowPCF5PS"
#endif

#if defined(SHADOW_KIND_PCSS)
    #include "linearizeDepthPS"
    #include "shadowPCSSPS"
    #include "shadowSoftPS"
#endif

#if defined(SHADOW_KIND_VSM)
    #include "shadowEVSMPS"
#endif

#ifdef LIT_CODE_FALLOFF_LINEAR
    #include "falloffLinearPS"
#endif

#ifdef LIT_CODE_FALLOFF_SQUARED
    #include "falloffInvSquaredPS"
#endif

#ifdef LIT_CODE_LIGHTS_POINT
    #include "lightDirPointPS"
#endif

#ifdef LIT_CODE_LIGHTS_SPOT
    #include "spotPS"
#endif

#ifdef LIT_CODE_COOKIE
    #include "cookiePS"
#endif

// clustered lighting
#ifdef LIT_CLUSTERED_LIGHTS
    #include "clusteredLightPS"
#endif

#ifdef LIGHT_COUNT > 0
    // LOOP - generate shadow evaluation functions for all non-clustered lights
    #include "lightFunctionShadowPS, LIGHT_COUNT"

    // LOOP - generate light evaluation functions for all non-clustered lights
    #include "lightFunctionLightPS, LIGHT_COUNT"
#endif
`;

var lightmapAddPS$1 = /* glsl */ `
void addLightMap(
    vec3 lightmap, 
    vec3 dir, 
    vec3 worldNormal, 
    vec3 viewDir, 
    vec3 reflectionDir, 
    float gloss, 
    vec3 specularity, 
    vec3 vertexNormal, 
    mat3 tbn
#if defined(LIT_IRIDESCENCE)
    vec3 iridescenceFresnel, 
    float iridescenceIntensity
#endif
) {

    // directional lightmap
    #if defined(LIT_SPECULAR) && defined(LIT_DIR_LIGHTMAP)

        if (dot(dir, dir) < 0.0001) {
                dDiffuseLight += lightmap;
        } else {
            float vlight = saturate(dot(dir, -vertexNormal));
            float flight = saturate(dot(dir, -worldNormal));
            float nlight = (flight / max(vlight, 0.01)) * 0.5;

            dDiffuseLight += lightmap * nlight * 2.0;

            vec3 halfDir = normalize(-dir + viewDir);
            vec3 specularLight = lightmap * getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, dir, gloss, tbn);

            #ifdef LIT_SPECULAR_FRESNEL

                specularLight *= 
                    getFresnel(dot(viewDir, halfDir), 
                    gloss, 
                    specularity
                #if defined(LIT_IRIDESCENCE)
                    , iridescenceFresnel,
                    iridescenceIntensity
                #endif
                    );
            #endif

            dSpecularLight += specularLight;
        }

    #else // non-directional lightmap

        dDiffuseLight += lightmap;

    #endif
}
`;

var lightmapPS$1 = /* glsl */ `

#ifdef STD_LIGHTMAP_DIR
    vec3 dLightmapDir;
    uniform sampler2D texture_dirLightMap;
#endif

void getLightMap() {

    dLightmap = vec3(1.0);

    #ifdef STD_LIGHT_TEXTURE
        dLightmap *= {STD_LIGHT_TEXTURE_DECODE}(texture2DBias({STD_LIGHT_TEXTURE_NAME}, {STD_LIGHT_TEXTURE_UV}, textureBias)).{STD_LIGHT_TEXTURE_CHANNEL};

        #ifdef STD_LIGHTMAP_DIR
            vec3 dir = texture2DBias(texture_dirLightMap, {STD_LIGHT_TEXTURE_UV}, textureBias).xyz * 2.0 - 1.0;
            float dirDot = dot(dir, dir);
            dLightmapDir = (dirDot > 0.001) ? dir / sqrt(dirDot) : vec3(0.0);
        #endif
    #endif

    #ifdef STD_LIGHT_VERTEX
        dLightmap *= saturate(vVertexColor.{STD_LIGHT_VERTEX_CHANNEL});
    #endif
}
`;

var lightSpecularAnisoGGXPS$1 = /* glsl */ `
// Anisotropic GGX
float calcLightSpecular(float gloss, vec3 worldNormal, vec3 viewDir, vec3 h, vec3 lightDirNorm, mat3 tbn) {
    float PI = 3.141592653589793;
    float roughness = max((1.0 - gloss) * (1.0 - gloss), 0.001);
    float alphaRoughness = roughness * roughness;
    float anisotropy = dAnisotropy;
    vec2 direction = dAnisotropyRotation;

    float at = mix(alphaRoughness, 1.0, anisotropy * anisotropy);
    float ab = clamp(alphaRoughness, 0.001, 1.0);

    vec3 anisotropicT = normalize(tbn * vec3(direction, 0.0));
    vec3 anisotropicB = normalize(cross(tbn[2], anisotropicT));

    float NoH = dot(worldNormal, h);
    float ToH = dot(anisotropicT, h);
    float BoH = dot(anisotropicB, h);

    float a2 = at * ab;
    vec3 v = vec3(ab * ToH, at * BoH, a2 * NoH);
    float v2 = dot(v, v);
    float w2 = a2 / v2;
    float D = a2 * w2 * w2 * (1.0 / PI);

    float ToV = dot(anisotropicT, viewDir);
    float BoV = dot(anisotropicB, viewDir);
    float ToL = dot(anisotropicT, -lightDirNorm);
    float BoL = dot(anisotropicB, -lightDirNorm);
    float NoV = dot(worldNormal, viewDir);
    float NoL = dot(worldNormal, -lightDirNorm);

    float lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV));
    float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL));
    float G = 0.5 / (lambdaV + lambdaL);

    return D * G;
}

float getLightSpecular(vec3 h, vec3 reflDir, vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm, float gloss, mat3 tbn) {
    return calcLightSpecular(gloss, worldNormal, viewDir, h, lightDirNorm, tbn);
}
`;

var lightSpecularBlinnPS$1 = /* glsl */ `
// Energy-conserving (hopefully) Blinn-Phong
float calcLightSpecular(float gloss, vec3 worldNormal, vec3 h) {
    float nh = max( dot( h, worldNormal ), 0.0 );

    float specPow = exp2(gloss * 11.0); // glossiness is linear, power is not; 0 - 2048

    // Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little
    specPow = max(specPow, 0.0001);

    return pow(nh, specPow) * (specPow + 2.0) / 8.0;
}

float getLightSpecular(vec3 h, vec3 reflDir, vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm, float gloss, mat3 tbn) {
    return calcLightSpecular(gloss, worldNormal, h);
}
`;

var lightSheenPS$1 = /* glsl */ `

float sheenD(vec3 normal, vec3 h, float roughness) {
    const float PI = 3.141592653589793;
    float invR = 1.0 / (roughness * roughness);
    float cos2h = max(dot(normal, h), 0.0);
    cos2h *= cos2h;
    float sin2h = max(1.0 - cos2h, 0.0078125);
    return (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * PI);
}

float sheenV(vec3 normal, vec3 viewDir, vec3 light) {
    float NoV = max(dot(normal, viewDir), 0.000001);
    float NoL = max(dot(normal, light), 0.000001);
    return 1.0 / (4.0 * (NoL + NoV - NoL * NoV));
}

float getLightSpecularSheen(vec3 h, vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm, float sheenGloss) {
    float D = sheenD(worldNormal, h, sheenGloss);
    float V = sheenV(worldNormal, viewDir, -lightDirNorm);
    return D * V;
}
`;

var linearizeDepthPS$1 = /* glsl */ `

#ifndef LINEARIZE_DEPTH
#define LINEARIZE_DEPTH

float linearizeDepthWithParams(float z, vec4 cameraParams) {
    if (cameraParams.w == 0.0)
        return (cameraParams.z * cameraParams.y) / (cameraParams.y + z * (cameraParams.z - cameraParams.y));
    else
        return cameraParams.z + z * (cameraParams.y - cameraParams.z);
}

#ifndef CAMERAPLANES
    #define CAMERAPLANES
    uniform vec4 camera_params; // x: 1 / camera_far,      y: camera_far,     z: camera_near,        w: is_ortho
#endif

float linearizeDepth(float z) {
    return linearizeDepthWithParams(z, camera_params);
}
#endif
`;

// backend shader implementing material / lighting for the lit material for forward rendering
var litForwardBackendPS$1 = /* glsl */ `
void evaluateBackend() {

    // apply SSAO during lighting
    #ifdef LIT_SSAO
        litArgs_ao *= texture2DLod(ssaoTexture, gl_FragCoord.xy * ssaoTextureSizeInv, 0.0).r;
    #endif

    // transform tangent space normals to world space
    #ifdef LIT_NEEDS_NORMAL
        #ifdef LIT_SPECULAR
            getReflDir(litArgs_worldNormal, dViewDirW, litArgs_gloss, dTBN);
        #endif

        #ifdef LIT_CLEARCOAT
            ccReflDirW = normalize(-reflect(dViewDirW, litArgs_clearcoat_worldNormal));
        #endif
    #endif

    #ifdef LIT_SPECULAR_OR_REFLECTION
        #ifdef LIT_METALNESS
            float f0 = 1.0 / litArgs_ior;
            f0 = (f0 - 1.0) / (f0 + 1.0);
            f0 *= f0;
            litArgs_specularity = getSpecularModulate(litArgs_specularity, litArgs_albedo, litArgs_metalness, f0);
            litArgs_albedo = getAlbedoModulate(litArgs_albedo, litArgs_metalness);
        #endif

        #ifdef LIT_IRIDESCENCE
            vec3 iridescenceFresnel = getIridescence(saturate(dot(dViewDirW, litArgs_worldNormal)), litArgs_specularity, litArgs_iridescence_thickness);
        #endif
    #endif

    // ambient
    #ifdef LIT_ADD_AMBIENT
        addAmbient(litArgs_worldNormal);

        #ifdef LIT_SPECULAR
            dDiffuseLight = dDiffuseLight * (1.0 - litArgs_specularity);
        #endif

        // move ambient color out of diffuse (used by Lightmapper, to multiply ambient color by accumulated AO)
        #ifdef LIT_SEPARATE_AMBIENT
            vec3 dAmbientLight = dDiffuseLight;
            dDiffuseLight = vec3(0);
        #endif
    #endif

    #ifndef LIT_OLD_AMBIENT
        dDiffuseLight *= material_ambient;
    #endif

    #ifdef LIT_AO
        #ifndef LIT_OCCLUDE_DIRECT
            occludeDiffuse(litArgs_ao);
        #endif
    #endif

    #ifdef LIT_LIGHTMAP
        addLightMap(
            litArgs_lightmap, 
            litArgs_lightmapDir, 
            litArgs_worldNormal, 
            dViewDirW, 
            dReflDirW, 
            litArgs_gloss, 
            litArgs_specularity, 
            dVertexNormalW,
            dTBN
        #if defined(LIT_IRIDESCENCE)
            , iridescenceFresnel,
            litArgs_iridescence_intensity
        #endif
        );
    #endif

    #ifdef LIT_LIGHTING || LIT_REFLECTIONS

        #ifdef LIT_REFLECTIONS

            #ifdef LIT_CLEARCOAT
                addReflectionCC(ccReflDirW, litArgs_clearcoat_gloss);
            
                #ifdef LIT_SPECULAR_FRESNEL
                    ccFresnel = getFresnelCC(dot(dViewDirW, litArgs_clearcoat_worldNormal));
                    ccReflection *= ccFresnel;
                #else
                    ccFresnel = 0.0;
                #endif
            #endif

            #ifdef LIT_SPECULARITY_FACTOR
                ccReflection *= litArgs_specularityFactor;
            #endif

            #ifdef LIT_SHEEN
                addReflectionSheen(litArgs_worldNormal, dViewDirW, litArgs_sheen_gloss);
            #endif

            // Fresnel has to be applied to reflections
            addReflection(dReflDirW, litArgs_gloss);

            #ifdef LIT_FRESNEL_MODEL

                dReflection.rgb *= getFresnel(
                    dot(dViewDirW, litArgs_worldNormal), 
                    litArgs_gloss, 
                    litArgs_specularity
                #if defined(LIT_IRIDESCENCE)
                    , iridescenceFresnel,
                    litArgs_iridescence_intensity
                #endif
                    );

            #else

                dReflection.rgb *= litArgs_specularity;

            #endif

            #ifdef LIT_SPECULARITY_FACTOR
                dReflection.rgb *= litArgs_specularityFactor;
            #endif

        #endif

        #ifdef AREA_LIGHTS
            // specular has to be accumulated differently if we want area lights to look correct
            dSpecularLight *= litArgs_specularity;

            #ifdef LIT_SPECULAR
                // evaluate material based area lights data, shared by all area lights
                calcLTCLightValues(litArgs_gloss, litArgs_worldNormal, dViewDirW, litArgs_specularity, litArgs_clearcoat_gloss, litArgs_clearcoat_worldNormal, litArgs_clearcoat_specularity);
            #endif
        #endif
        
        // LOOP - evaluate all non-clustered lights
        #ifdef LIGHT_COUNT > 0
            #include "lightEvaluationPS, LIGHT_COUNT"
        #endif

        // clustered lighting
        #ifdef LIT_CLUSTERED_LIGHTS
            addClusteredLights(litArgs_worldNormal, dViewDirW, dReflDirW,
                #if defined(LIT_CLEARCOAT)
                        ccReflDirW,
                #endif
                        litArgs_gloss, litArgs_specularity, dVertexNormalW, dTBN, 
                #if defined(LIT_IRIDESCENCE)
                        iridescenceFresnel,
                #endif
                        litArgs_clearcoat_worldNormal, litArgs_clearcoat_gloss, litArgs_sheen_gloss, litArgs_iridescence_intensity
            );
        #endif

        #ifdef AREA_LIGHTS

            #ifdef LIT_CLEARCOAT
                // specular has to be accumulated differently if we want area lights to look correct
                litArgs_clearcoat_specularity = 1.0;
            #endif

            #ifdef LIT_SPECULAR
                litArgs_specularity = vec3(1);
            #endif

        #endif

        #ifdef LIT_REFRACTION
            addRefraction(
                litArgs_worldNormal, 
                dViewDirW, 
                litArgs_thickness, 
                litArgs_gloss, 
                litArgs_specularity, 
                litArgs_albedo, 
                litArgs_transmission,
                litArgs_ior,
                litArgs_dispersion
                #if defined(LIT_IRIDESCENCE)
                    , iridescenceFresnel, 
                    litArgs_iridescence_intensity
                #endif
            );
        #endif
    #endif

    // apply ambient occlusion
    #ifdef LIT_AO
        #ifdef LIT_OCCLUDE_DIRECT
            occludeDiffuse(litArgs_ao);
        #endif

        #if LIT_OCCLUDE_SPECULAR != NONE
            occludeSpecular(litArgs_gloss, litArgs_ao, litArgs_worldNormal, dViewDirW);
        #endif
    #endif

    #ifdef LIT_SPECULARITY_FACTOR
        dSpecularLight *= litArgs_specularityFactor;
    #endif

    #if !defined(LIT_OPACITY_FADES_SPECULAR)

        #if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == PREMULTIPLIED

            float specLum = dot((dSpecularLight + dReflection.rgb * dReflection.a), vec3( 0.2126, 0.7152, 0.0722 ));
            #ifdef LIT_CLEARCOAT
                specLum += dot(ccSpecularLight * litArgs_clearcoat_specularity + ccReflection * litArgs_clearcoat_specularity, vec3( 0.2126, 0.7152, 0.0722 ));
            #endif
            litArgs_opacity = clamp(litArgs_opacity + gammaCorrectInput(specLum), 0.0, 1.0);

        #endif

        litArgs_opacity *= material_alphaFade;

    #endif

    // end chunks - when baking lightmap
    #ifdef LIT_LIGHTMAP_BAKING
        #ifdef LIT_LIGHTMAP_BAKING_COLOR
            #include "bakeLmEndPS"
        #endif
        #ifdef LIT_LIGHTMAP_BAKING_DIR
            #include "bakeDirLmEndPS"
        #endif
    #else
        // end chunks - in all other cases
        #include "endPS"
        #include "outputAlphaPS"
    #endif

    #ifdef LIT_MSDF
        gl_FragColor = applyMsdf(gl_FragColor);
    #endif

    #include "outputPS"
    #include "debugOutputPS"

    #ifdef LIT_SHADOW_CATCHER
        // output when the shadow catcher is enabled - accumulated shadows
        gl_FragColor.rgb = vec3(dShadowCatcher);
    #endif
}
`;

// shader declarations for the lit material for forward rendering
var litForwardDeclarationPS$1 = /* glsl */ `

// globals
vec3 sReflection;
vec3 dVertexNormalW;
vec3 dTangentW;
vec3 dBinormalW;
vec3 dViewDirW;
vec3 dReflDirW;
vec3 ccReflDirW;

// Per-light temporaries
vec3 dLightDirNormW;
float dAtten;

// Outputs
mat3 dTBN;
vec4 dReflection;
vec3 dDiffuseLight;
vec3 dSpecularLight;
float ccFresnel;
vec3 ccReflection;
vec3 ccSpecularLight;
float ccSpecularityNoFres;
vec3 sSpecularLight;

// FRAGMENT SHADER INPUTS: UNIFORMS

#ifdef LIT_DISPERSION
    uniform float material_dispersion;
#endif

#ifndef LIT_OPACITY_FADES_SPECULAR
    uniform float material_alphaFade;
#endif

#ifdef LIT_SSAO
    uniform sampler2D ssaoTexture;
    uniform vec2 ssaoTextureSizeInv;
#endif

// lighting and shadowing declarations

#ifdef LIT_SHADOW_CATCHER
    // a variable to accumulate shadows for shadow catcher materials
    float dShadowCatcher = 1.0;
#endif

// LOOP - uniform declarations for all non-clustered lights
#if LIGHT_COUNT > 0
    #include "lightDeclarationPS, LIGHT_COUNT"
#endif

#ifdef LIT_SPECULAR
    #if LIT_FRESNEL_MODEL == NONE && !defined(LIT_REFLECTIONS) && !defined(LIT_DIFFUSE_MAP) 
        #define LIT_OLD_AMBIENT
    #endif
#endif

// lightmap baking
#ifdef STD_LIGHTMAP_DIR
    uniform float bakeDir;
#endif
#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT
    uniform float ambientBakeOcclusionContrast;
    uniform float ambientBakeOcclusionBrightness;
#endif
`;

// main shader entry point for the lit material for forward rendering
var litForwardMainPS$1 = /* glsl */ `

void main(void) {

    #include "litUserMainStartPS"

    dReflection = vec4(0);

    #ifdef LIT_CLEARCOAT
        ccSpecularLight = vec3(0);
        ccReflection = vec3(0);
    #endif

    #if LIT_NONE_SLICE_MODE == SLICED
        #include "startNineSlicedPS"
    #elif LIT_NONE_SLICE_MODE == TILED
        #include "startNineSlicedTiledPS"
    #endif

    #ifdef LIT_NEEDS_NORMAL
        dVertexNormalW = normalize(vNormalW);

        #ifdef LIT_TANGENTS
            #if defined(LIT_HEIGHTS) || defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS) || defined(LIT_GGX_SPECULAR)
                dTangentW = vTangentW;
                dBinormalW = vBinormalW;
            #endif
        #endif

        getViewDir();

        #ifdef LIT_TBN
            getTBN(dTangentW, dBinormalW, dVertexNormalW);

            #ifdef LIT_TWO_SIDED_LIGHTING
                handleTwoSidedLighting();
            #endif
        #endif
    #endif

    // invoke frontend functions
    evaluateFrontend();

    #include "debugProcessFrontendPS"

    evaluateBackend();

    #include "litUserMainEndPS"
}
`;

// backend shader implementation code which executes after the front end code.
var litForwardPostCodePS$1 = /* glsl */ `

#ifdef LIT_NEEDS_NORMAL
    #include "cubeMapRotatePS"
    #include "cubeMapProjectPS"
    #include "envProcPS"
#endif

// ----- specular or reflections -----
#ifdef LIT_SPECULAR_OR_REFLECTION
    #ifdef LIT_METALNESS
        #include "metalnessModulatePS"
    #endif

    #if LIT_FRESNEL_MODEL == SCHLICK
        #include "fresnelSchlickPS"
    #endif

    #ifdef LIT_IRIDESCENCE
        #include "iridescenceDiffractionPS"
    #endif
#endif

// ----- ambient occlusion -----
#ifdef LIT_AO
    #include "aoDiffuseOccPS"
    #include "aoSpecOccPS"
#endif

#if LIT_REFLECTION_SOURCE == ENVATLASHQ
    #include "envAtlasPS"
    #include "reflectionEnvHQPS"
#elif LIT_REFLECTION_SOURCE == ENVATLAS
    #include "envAtlasPS"
    #include "reflectionEnvPS"
#elif LIT_REFLECTION_SOURCE == CUBEMAP
    #include "reflectionCubePS"
#elif LIT_REFLECTION_SOURCE == SPHEREMAP
    #include "reflectionSpherePS"
#endif

#ifdef LIT_REFLECTIONS
    #ifdef LIT_CLEARCOAT
        #include "reflectionCCPS"
    #endif

    #ifdef LIT_SHEEN
        #include "reflectionSheenPS"
    #endif
#endif

#ifdef LIT_REFRACTION
    #if defined(LIT_DYNAMIC_REFRACTION)
        #include "refractionDynamicPS"
    #elif defined(LIT_REFLECTIONS)
        #include "refractionCubePS"
    #endif
#endif

#ifdef LIT_SHEEN
    #include "lightSheenPS"
#endif

uniform vec3 material_ambient;

#ifdef LIT_SPECULAR
    #ifdef LIT_LIGHTING
        #ifdef LIT_GGX_SPECULAR
            #include "lightSpecularAnisoGGXPS"
        #else
            #include "lightSpecularBlinnPS"
        #endif
    #endif
#endif

#include "combinePS"

#ifdef LIT_LIGHTMAP
    #include "lightmapAddPS"
#endif

#ifdef LIT_ADD_AMBIENT
    #include "ambientPS"
#endif

#ifdef LIT_MSDF
    #include "msdfPS"
#endif

#ifdef LIT_NEEDS_NORMAL
    #include "viewDirPS"
    #ifdef LIT_SPECULAR
        #ifdef LIT_GGX_SPECULAR
            #include "reflDirAnisoPS"
        #else
            #include "reflDirPS"
        #endif
    #endif
#endif

// lighting functionality
#include "lightingPS"

`;

// backend shader implementation code which executes prior to the front end code, and contains code
// which is required by the frontend code.
var litForwardPreCodePS$1 = /* glsl */ `

#include "basePS"
#include "sphericalPS"
#include "decodePS"
#include "gammaPS"
#include "tonemappingPS"
#include "fogPS"

// 9-slice support code
#if LIT_NONE_SLICE_MODE == SLICED
    #include "baseNineSlicedPS"
#elif LIT_NONE_SLICE_MODE == TILED
    #include "baseNineSlicedTiledPS"
#endif

// TBN
#ifdef LIT_TBN
    #include "TBNPS"

    #ifdef LIT_TWO_SIDED_LIGHTING
        #include "twoSidedLightingPS"
    #endif
#endif

`;

// main shader of the lit fragment shader
var litMainPS$1 = /* glsl */ `

#include "varyingsPS"
#include "litUserDeclarationPS"
#include "frontendDeclPS"

#if defined(PICK_PASS) || defined(PREPASS_PASS)

    #include "frontendCodePS"
    #include "litUserCodePS"
    #include "litOtherMainPS"

#elif defined(SHADOW_PASS)

    #include "frontendCodePS"
    #include "litUserCodePS"
    #include "litShadowMainPS"

#else // FORWARD_PASS

    #include "litForwardDeclarationPS"
    #include "litForwardPreCodePS"
    #include "frontendCodePS"
    #include "litForwardPostCodePS"
    #include "litForwardBackendPS"
    #include "litUserCodePS"
    #include "litForwardMainPS"

#endif

`;

// main shader of the lit vertex shader
var litMainVS$1 = /* glsl */ `

#include "varyingsVS"

#include  "litUserDeclarationVS"

#ifdef VERTEX_COLOR
    attribute vec4 vertex_color;
#endif

#ifdef NINESLICED

    varying vec2 vMask;
    varying vec2 vTiledUv;

    uniform mediump vec4 innerOffset;
    uniform mediump vec2 outerScale;
    uniform mediump vec4 atlasRect;

#endif

vec3 dPositionW;
mat4 dModelMatrix;

#include "transformCoreVS"

#ifdef UV0
    attribute vec2 vertex_texCoord0;
    #include "uv0VS"
#endif

#ifdef UV1
    attribute vec2 vertex_texCoord1;
    #include "uv1VS"
#endif


#ifdef LINEAR_DEPTH
    #ifndef VIEWMATRIX
    #define VIEWMATRIX
        uniform mat4 matrix_view;
    #endif
#endif

#include "transformVS"

#ifdef NORMALS
    #include "normalCoreVS"
    #include "normalVS"
#endif

#ifdef TANGENTS
    attribute vec4 vertex_tangent;
#endif

// expand uniforms for uv transforms
#include "uvTransformUniformsPS, UV_TRANSFORMS_COUNT"

#ifdef MSDF
    #include "msdfVS"
#endif

#include  "litUserCodeVS"

void main(void) {

    #include "litUserMainStartVS"

    // default point size to 1 in case the shader is used with points
    gl_PointSize = 1.0;

    gl_Position = getPosition();
    vPositionW = getWorldPosition();

    #ifdef NORMALS
        vNormalW = getNormal();
    #endif

    #ifdef TANGENTS
        vTangentW = normalize(dNormalMatrix * vertex_tangent.xyz);
        vBinormalW = cross(vNormalW, vTangentW) * vertex_tangent.w;
    #elif defined(GGX_SPECULAR)
        vObjectSpaceUpW = normalize(dNormalMatrix * vec3(0, 1, 0));
    #endif

    #ifdef UV0
        vec2 uv0 = getUv0();
        #ifdef UV0_UNMODIFIED
            vUv0 = uv0;
        #endif
    #endif

    #ifdef UV1
        vec2 uv1 = getUv1();
        #ifdef UV1_UNMODIFIED
            vUv1 = uv1;
        #endif
    #endif

    // expand code for uv transforms
    #include "uvTransformVS, UV_TRANSFORMS_COUNT"

    #ifdef VERTEX_COLOR
        vVertexColor = vertex_color;
    #endif

    #ifdef LINEAR_DEPTH
        // linear depth from the worldPosition, see getLinearDepth
        vLinearDepth = -(matrix_view * vec4(vPositionW, 1.0)).z;
    #endif

    #ifdef MSDF
        unpackMsdfParams();
    #endif

    #include "litUserMainEndVS"
}
`;

// main shader entry point for the lit material for other render passes
var litOtherMainPS$1 = /* glsl */ `

#ifdef PICK_PASS
    #include "pickPS"
#endif

#ifdef PREPASS_PASS
    #include "floatAsUintPS"
#endif

void main(void) {

    #include "litUserMainStartPS"

    evaluateFrontend();

    #ifdef PICK_PASS
        gl_FragColor = getPickOutput();
    #endif

    #ifdef PREPASS_PASS
        gl_FragColor = float2vec4(vLinearDepth);
    #endif

    #include "litUserMainEndPS"
}
`;

var litShaderArgsPS$1 = /* glsl */ `

// Surface albedo absorbance
vec3 litArgs_albedo;

// Transparency
float litArgs_opacity;

// Emission color
vec3 litArgs_emission;

// Normal direction in world space
vec3 litArgs_worldNormal;

// Ambient occlusion amount, range [0..1]
float litArgs_ao;

// Light map color
vec3 litArgs_lightmap;

// Light map direction
vec3 litArgs_lightmapDir;

// Surface metalness factor, range [0..1]
float litArgs_metalness;

// The f0 specularity factor
vec3 litArgs_specularity;

// Specularity intensity factor, range [0..1]
float litArgs_specularityFactor;

// The microfacet glossiness factor, range [0..1]
float litArgs_gloss;

// Glossiness of the sheen layer, range [0..1]
float litArgs_sheen_gloss;

// The color of the f0 specularity factor for the sheen layer
vec3 litArgs_sheen_specularity;

// Transmission factor (refraction), range [0..1]
float litArgs_transmission;

// Uniform thickness of medium, used by transmission, range [0..inf]
float litArgs_thickness;

// Index of refraction
float litArgs_ior;

// Dispersion, range [0..1] typically, but can be higher
float litArgs_dispersion;

// Iridescence effect intensity, range [0..1]
float litArgs_iridescence_intensity;

// Thickness of the iridescent microfilm layer, value is in nanometers, range [0..1000]
float litArgs_iridescence_thickness;

// The normal used for the clearcoat layer
vec3 litArgs_clearcoat_worldNormal;

// Intensity of the clearcoat layer, range [0..1]
float litArgs_clearcoat_specularity;

// Glossiness of clearcoat layer, range [0..1]
float litArgs_clearcoat_gloss;

`;

var litShaderCorePS$1 = /* glsl */ `

    // global texture bias for standard textures
    #if LIT_NONE_SLICE_MODE == TILED
        const float textureBias = -1000.0;
    #else
        uniform float textureBias;
    #endif

    #include "litShaderArgsPS"
`;

// main shader entry point for the lit material for shadow rendering
var litShadowMainPS$1 = /* glsl */ `

#if LIGHT_TYPE != DIRECTIONAL
    uniform vec3 view_position;
    uniform float light_radius;
#endif

#if SHADOW_TYPE == PCSS_32F
    #include "linearizeDepthPS"
#endif

void main(void) {

    #include "litUserMainStartPS"

    evaluateFrontend();

    // using non-standard depth, i.e gl_FragCoord.z
    #ifdef PERSPECTIVE_DEPTH
        float depth = gl_FragCoord.z;

        #if SHADOW_TYPE == PCSS_32F
            // spot/omni shadows currently use linear depth.
            // TODO: use perspective depth for spot/omni the same way as directional
            #if LIGHT_TYPE != DIRECTIONAL
                depth = linearizeDepthWithParams(depth, camera_params);
            #endif
        #endif

    #else
        float depth = min(distance(view_position, vPositionW) / light_radius, 0.99999);
        #define MODIFIED_DEPTH
    #endif

    #if SHADOW_TYPE == VSM_16F || SHADOW_TYPE == VSM_32F
        #if SHADOW_TYPE == VSM_32F
            float exponent = 15.0;
        #else
            float exponent = 5.54;
        #endif
        depth = 2.0 * depth - 1.0;
        depth =  exp(exponent * depth);
        gl_FragColor = vec4(depth, depth*depth, 1.0, 1.0);
    #else
        #if SHADOW_TYPE == PCSS_32F
            // store depth into R32
            gl_FragColor.r = depth;
        #else
            #ifdef MODIFIED_DEPTH
                // If we end up using modified depth, it needs to be explicitly written to gl_FragDepth
                gl_FragDepth = depth;
            #endif

            // just the simplest code, color is not written anyway
            gl_FragColor = vec4(1.0);
        #endif
    #endif

    #include "litUserMainEndPS"
}
`;

var ltcPS$1 = /* glsl */ `
// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
// by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
// code: https://github.com/selfshadow/ltc_code/

mat3 transposeMat3( const in mat3 m ) {
    mat3 tmp;
    tmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );
    tmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );
    tmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );
    return tmp;
}

vec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {
    const float LUT_SIZE = 64.0;
    const float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;
    const float LUT_BIAS = 0.5 / LUT_SIZE;
    float dotNV = saturate( dot( N, V ) );
    // texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) )
    vec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );
    uv = uv * LUT_SCALE + LUT_BIAS;
    return uv;
}

float LTC_ClippedSphereFormFactor( const in vec3 f ) {
    // Real-Time Area Lighting: a Journey from Research to Production (p.102)
    // An approximation of the form factor of a horizon-clipped rectangle.
    float l = length( f );
    return max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );
}

vec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {
    float x = dot( v1, v2 );
    float y = abs( x );
    // rational polynomial approximation to theta / sin( theta ) / 2PI
    float a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;
    float b = 3.4175940 + ( 4.1616724 + y ) * y;
    float v = a / b;
    float theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;
    return cross( v1, v2 ) * theta_sintheta;
}

struct Coords {
    vec3 coord0;
    vec3 coord1;
    vec3 coord2;
    vec3 coord3;
};

float LTC_EvaluateRect( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in Coords rectCoords) {
    // bail if point is on back side of plane of light
    // assumes ccw winding order of light vertices
    vec3 v1 = rectCoords.coord1 - rectCoords.coord0;
    vec3 v2 = rectCoords.coord3 - rectCoords.coord0;
    
    vec3 lightNormal = cross( v1, v2 );
    // if( dot( lightNormal, P - rectCoords.coord0 ) < 0.0 ) return 0.0;
    float factor = sign(-dot( lightNormal, P - rectCoords.coord0 ));

    // construct orthonormal basis around N
    vec3 T1, T2;
    T1 = normalize( V - N * dot( V, N ) );
    T2 =  factor * cross( N, T1 ); // negated from paper; possibly due to a different handedness of world coordinate system
    // compute transform
    mat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );
    // transform rect
    vec3 coords[ 4 ];
    coords[ 0 ] = mat * ( rectCoords.coord0 - P );
    coords[ 1 ] = mat * ( rectCoords.coord1 - P );
    coords[ 2 ] = mat * ( rectCoords.coord2 - P );
    coords[ 3 ] = mat * ( rectCoords.coord3 - P );
    // project rect onto sphere
    coords[ 0 ] = normalize( coords[ 0 ] );
    coords[ 1 ] = normalize( coords[ 1 ] );
    coords[ 2 ] = normalize( coords[ 2 ] );
    coords[ 3 ] = normalize( coords[ 3 ] );
    // calculate vector form factor
    vec3 vectorFormFactor = vec3( 0.0 );
    vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );
    vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );
    vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );
    vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );
    // adjust for horizon clipping
    float result = LTC_ClippedSphereFormFactor( vectorFormFactor );

    return result;
}

Coords dLTCCoords;
Coords getLTCLightCoords(vec3 lightPos, vec3 halfWidth, vec3 halfHeight){
    Coords coords;
    coords.coord0 = lightPos + halfWidth - halfHeight;
    coords.coord1 = lightPos - halfWidth - halfHeight;
    coords.coord2 = lightPos - halfWidth + halfHeight;
    coords.coord3 = lightPos + halfWidth + halfHeight;
    return coords;
}

float dSphereRadius;
Coords getSphereLightCoords(vec3 lightPos, vec3 halfWidth, vec3 halfHeight){
    // used for simple sphere light falloff
    // also, the code only handles a spherical light, it cannot be non-uniformly scaled in world space, and so we enforce it here
    dSphereRadius = max(length(halfWidth), length(halfHeight));

    // Billboard the 2d light quad to reflection vector, as it's used for specular. This allows us to use disk math for the sphere.
    vec3 f = reflect(normalize(lightPos - view_position), vNormalW);
    vec3 w = normalize(cross(f, halfHeight));
    vec3 h = normalize(cross(f, w));

    return getLTCLightCoords(lightPos, w * dSphereRadius, h * dSphereRadius);
}

// used for LTC LUT texture lookup
vec2 dLTCUV;
#ifdef LIT_CLEARCOAT
    vec2 ccLTCUV;
#endif

vec2 getLTCLightUV(float gloss, vec3 worldNormal, vec3 viewDir)
{
    float roughness = max((1.0 - gloss) * (1.0 - gloss), 0.001);
    return LTC_Uv( worldNormal, viewDir, roughness );
}

// used for energy conservation and to modulate specular
vec3 dLTCSpecFres;
#ifdef LIT_CLEARCOAT
    vec3 ccLTCSpecFres;
#endif

vec3 getLTCLightSpecFres(vec2 uv, vec3 specularity)
{
    vec4 t2 = texture2DLod(areaLightsLutTex2, uv, 0.0);
    return specularity * t2.x + ( vec3( 1.0 ) - specularity) * t2.y;
}

void calcLTCLightValues(float gloss, vec3 worldNormal, vec3 viewDir, vec3 specularity, float clearcoatGloss, vec3 clearcoatWorldNormal, float clearcoatSpecularity)
{
    dLTCUV = getLTCLightUV(gloss, worldNormal, viewDir);
    dLTCSpecFres = getLTCLightSpecFres(dLTCUV, specularity); 

#ifdef LIT_CLEARCOAT
    ccLTCUV = getLTCLightUV(clearcoatGloss, clearcoatWorldNormal, viewDir);
    ccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3(clearcoatSpecularity));
#endif
}

void calcRectLightValues(vec3 lightPos, vec3 halfWidth, vec3 halfHeight) {
    dLTCCoords = getLTCLightCoords(lightPos, halfWidth, halfHeight);
}
void calcDiskLightValues(vec3 lightPos, vec3 halfWidth, vec3 halfHeight) {
    calcRectLightValues(lightPos, halfWidth, halfHeight);
}
void calcSphereLightValues(vec3 lightPos, vec3 halfWidth, vec3 halfHeight) {
    dLTCCoords = getSphereLightCoords(lightPos, halfWidth, halfHeight);
}

// An extended version of the implementation from "How to solve a cubic equation, revisited"
// http://momentsingraphics.de/?p=105
vec3 SolveCubic(vec4 Coefficient)
{
    float pi = 3.14159;
    // Normalize the polynomial
    Coefficient.xyz /= Coefficient.w;
    // Divide middle coefficients by three
    Coefficient.yz /= 3.0;

    float A = Coefficient.w;
    float B = Coefficient.z;
    float C = Coefficient.y;
    float D = Coefficient.x;

    // Compute the Hessian and the discriminant
    vec3 Delta = vec3(
        -Coefficient.z * Coefficient.z + Coefficient.y,
        -Coefficient.y * Coefficient.z + Coefficient.x,
        dot(vec2(Coefficient.z, -Coefficient.y), Coefficient.xy)
    );

    float Discriminant = dot(vec2(4.0 * Delta.x, -Delta.y), Delta.zy);

    vec2 xlc, xsc;

    // Algorithm A
    {
        float A_a = 1.0;
        float C_a = Delta.x;
        float D_a = -2.0 * B * Delta.x + Delta.y;

        // Take the cubic root of a normalized complex number
        float Theta = atan(sqrt(Discriminant), -D_a) / 3.0;

        float x_1a = 2.0 * sqrt(-C_a) * cos(Theta);
        float x_3a = 2.0 * sqrt(-C_a) * cos(Theta + (2.0 / 3.0) * pi);

        float xl;
        if ((x_1a + x_3a) > 2.0 * B)
            xl = x_1a;
        else
            xl = x_3a;

        xlc = vec2(xl - B, A);
    }

    // Algorithm D
    {
        float A_d = D;
        float C_d = Delta.z;
        float D_d = -D * Delta.y + 2.0 * C * Delta.z;

        // Take the cubic root of a normalized complex number
        float Theta = atan(D * sqrt(Discriminant), -D_d) / 3.0;

        float x_1d = 2.0 * sqrt(-C_d) * cos(Theta);
        float x_3d = 2.0 * sqrt(-C_d) * cos(Theta + (2.0 / 3.0) * pi);

        float xs;
        if (x_1d + x_3d < 2.0 * C)
            xs = x_1d;
        else
            xs = x_3d;

        xsc = vec2(-D, xs + C);
    }

    float E =  xlc.y * xsc.y;
    float F = -xlc.x * xsc.y - xlc.y * xsc.x;
    float G =  xlc.x * xsc.x;

    vec2 xmc = vec2(C * F - B * G, -B * F + C * E);

    vec3 Root = vec3(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y);

    if (Root.x < Root.y && Root.x < Root.z)
        Root.xyz = Root.yxz;
    else if (Root.z < Root.x && Root.z < Root.y)
        Root.xyz = Root.xzy;

    return Root;
}

float LTC_EvaluateDisk(vec3 N, vec3 V, vec3 P, mat3 Minv, Coords points)
{
    // construct orthonormal basis around N
    vec3 T1 = normalize(V - N * dot(V, N));
    vec3 T2 = cross(N, T1);

    // rotate area light in (T1, T2, N) basis
    //mat3 R = transpose(mat3(T1, T2, N));
    mat3 R = transposeMat3( mat3( T1, T2, N ) );
    // polygon (allocate 5 vertices for clipping)
    vec3 L_[ 3 ];
    L_[ 0 ] = R * ( points.coord0 - P );
    L_[ 1 ] = R * ( points.coord1 - P );
    L_[ 2 ] = R * ( points.coord2 - P );

    // init ellipse
    vec3 C  = 0.5 * (L_[0] + L_[2]);
    vec3 V1 = 0.5 * (L_[1] - L_[2]);
    vec3 V2 = 0.5 * (L_[1] - L_[0]);

    C  = Minv * C;
    V1 = Minv * V1;
    V2 = Minv * V2;

    //if(dot(cross(V1, V2), C) > 0.0)
    //    return 0.0;

    // compute eigenvectors of ellipse
    float a, b;
    float d11 = dot(V1, V1);
    float d22 = dot(V2, V2);
    float d12 = dot(V1, V2);
    if (abs(d12) / sqrt(d11 * d22) > 0.0001)
    {
        float tr = d11 + d22;
        float det = -d12 * d12 + d11 * d22;

        // use sqrt matrix to solve for eigenvalues
        det = sqrt(det);
        float u = 0.5 * sqrt(tr - 2.0 * det);
        float v = 0.5 * sqrt(tr + 2.0 * det);
        float e_max = (u + v) * (u + v);
        float e_min = (u - v) * (u - v);

        vec3 V1_, V2_;

        if (d11 > d22)
        {
            V1_ = d12 * V1 + (e_max - d11) * V2;
            V2_ = d12 * V1 + (e_min - d11) * V2;
        }
        else
        {
            V1_ = d12*V2 + (e_max - d22)*V1;
            V2_ = d12*V2 + (e_min - d22)*V1;
        }

        a = 1.0 / e_max;
        b = 1.0 / e_min;
        V1 = normalize(V1_);
        V2 = normalize(V2_);
    }
    else
    {
        a = 1.0 / dot(V1, V1);
        b = 1.0 / dot(V2, V2);
        V1 *= sqrt(a);
        V2 *= sqrt(b);
    }

    vec3 V3 = normalize(cross(V1, V2));
    if (dot(C, V3) < 0.0)
        V3 *= -1.0;

    float L  = dot(V3, C);
    float x0 = dot(V1, C) / L;
    float y0 = dot(V2, C) / L;

    float E1 = inversesqrt(a);
    float E2 = inversesqrt(b);

    a *= L * L;
    b *= L * L;

    float c0 = a * b;
    float c1 = a * b * (1.0 + x0 * x0 + y0 * y0) - a - b;
    float c2 = 1.0 - a * (1.0 + x0 * x0) - b * (1.0 + y0 * y0);
    float c3 = 1.0;

    vec3 roots = SolveCubic(vec4(c0, c1, c2, c3));
    float e1 = roots.x;
    float e2 = roots.y;
    float e3 = roots.z;

    vec3 avgDir = vec3(a * x0 / (a - e2), b * y0 / (b - e2), 1.0);

    mat3 rotate = mat3(V1, V2, V3);

    avgDir = rotate * avgDir;
    avgDir = normalize(avgDir);

    float L1 = sqrt(-e2 / e3);
    float L2 = sqrt(-e2 / e1);

    float formFactor = max(0.0, L1 * L2 * inversesqrt((1.0 + L1 * L1) * (1.0 + L2 * L2)));
    
    const float LUT_SIZE = 64.0;
    const float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;
    const float LUT_BIAS = 0.5 / LUT_SIZE;

    // use tabulated horizon-clipped sphere
    vec2 uv = vec2(avgDir.z * 0.5 + 0.5, formFactor);
    uv = uv*LUT_SCALE + LUT_BIAS;

    float scale = texture2DLod(areaLightsLutTex2, uv, 0.0).w;

    return formFactor*scale;
}

// LTC_EvaluateDisk in some rare cases genereates NaN values in a or b, just before 'float c0 = a * b;'
// Get rid of those Nan values before they propagate further, as in case of bloom / DOF blurs they
// propagate to large areas. I didn't find the actual reason where those come from, so that is still TODO.
// Note that only disk/sphere lights are causing it, so only handle those.
float FixNan(float value) {
    #ifdef WEBGPU
        return value != value ? 0.0 : value;  // isnan does not transpile correctly, use a workaround
    #else
        return isnan(value) ? 0.0 : value;
    #endif
}

float getRectLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDir, vec3 lightDirNorm) {
    return LTC_EvaluateRect( worldNormal, viewDir, vPositionW, mat3( 1.0 ), dLTCCoords );
}

float getDiskLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDir, vec3 lightDirNorm) {
    return FixNan(LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, mat3( 1.0 ), dLTCCoords ));
}

float getSphereLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDir, vec3 lightDirNorm) {
    // NB: this could be improved further with distance based wrap lighting
    float falloff = dSphereRadius / (dot(lightDir, lightDir) + dSphereRadius);
    return FixNan(getLightDiffuse(worldNormal, viewDir, lightDirNorm) * falloff);
}

mat3 getLTCLightInvMat(vec2 uv)
{
    vec4 t1 = texture2DLod(areaLightsLutTex1, uv, 0.0);

    return mat3(
        vec3( t1.x, 0, t1.y ),
        vec3(    0, 1,    0 ),
        vec3( t1.z, 0, t1.w )
    );
}

float calcRectLightSpecular(vec3 worldNormal, vec3 viewDir, vec2 uv) {
    mat3 mInv = getLTCLightInvMat(uv);
    return LTC_EvaluateRect( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );
}

float getRectLightSpecular(vec3 worldNormal, vec3 viewDir) {
    return calcRectLightSpecular(worldNormal, viewDir, dLTCUV);
}

float calcDiskLightSpecular(vec3 worldNormal, vec3 viewDir, vec2 uv) {
    mat3 mInv = getLTCLightInvMat(uv);
    return LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );
}

float getDiskLightSpecular(vec3 worldNormal, vec3 viewDir) {
    return calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);
}

float getSphereLightSpecular(vec3 worldNormal, vec3 viewDir) {
    return calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);
}
`;

var metalnessPS$1 = /* glsl */ `
#ifdef STD_METALNESS_CONSTANT
uniform float material_metalness;
#endif

void getMetalness() {
    float metalness = 1.0;

    #ifdef STD_METALNESS_CONSTANT
    metalness *= material_metalness;
    #endif

    #ifdef STD_METALNESS_TEXTURE
    metalness *= texture2DBias({STD_METALNESS_TEXTURE_NAME}, {STD_METALNESS_TEXTURE_UV}, textureBias).{STD_METALNESS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_METALNESS_VERTEX
    metalness *= saturate(vVertexColor.{STD_METALNESS_VERTEX_CHANNEL});
    #endif

    dMetalness = metalness;
}
`;

var msdfPS$1 = /* glsl */ `
uniform sampler2D texture_msdfMap;

float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
}

float map (float min, float max, float v) {
    return (v - min) / (max - min);
}

uniform float font_sdfIntensity; // intensity is used to boost the value read from the SDF, 0 is no boost, 1.0 is max boost
uniform float font_pxrange;      // the number of pixels between inside and outside the font in SDF
uniform float font_textureWidth; // the width of the texture atlas

#ifndef LIT_MSDF_TEXT_ATTRIBUTE
    uniform vec4 outline_color;
    uniform float outline_thickness;
    uniform vec4 shadow_color;
    uniform vec2 shadow_offset;
#else
    varying vec4 outline_color;
    varying float outline_thickness;
    varying vec4 shadow_color;
    varying vec2 shadow_offset;
#endif

vec4 applyMsdf(vec4 color) {

    // Convert to linear space before processing
    // TODO: ideally this would receive the color in linear space, but that would require larger changes
    // on the engine side, with the way premultiplied alpha is handled as well.
    color.rgb = gammaCorrectInput(color.rgb);

    // sample the field
    vec3 tsample = texture2D(texture_msdfMap, vUv0).rgb;
    vec2 uvShdw = vUv0 - shadow_offset;
    vec3 ssample = texture2D(texture_msdfMap, uvShdw).rgb;

    // get the signed distance value
    float sigDist = median(tsample.r, tsample.g, tsample.b);
    float sigDistShdw = median(ssample.r, ssample.g, ssample.b);

    // smoothing limit - smaller value makes for sharper but more aliased text, especially on angles
    // too large value (0.5) creates a dark glow around the letters
    float smoothingMax = 0.2;

    // smoothing depends on size of texture on screen
    vec2 w = fwidth(vUv0);
    float smoothing = clamp(w.x * font_textureWidth / font_pxrange, 0.0, smoothingMax);

    float mapMin = 0.05;
    float mapMax = clamp(1.0 - font_sdfIntensity, mapMin, 1.0);

    // remap to a smaller range (used on smaller font sizes)
    float sigDistInner = map(mapMin, mapMax, sigDist);
    float sigDistOutline = map(mapMin, mapMax, sigDist + outline_thickness);
    sigDistShdw = map(mapMin, mapMax, sigDistShdw + outline_thickness);

    float center = 0.5;
    // calculate smoothing and use to generate opacity
    float inside = smoothstep(center-smoothing, center+smoothing, sigDistInner);
    float outline = smoothstep(center-smoothing, center+smoothing, sigDistOutline);
    float shadow = smoothstep(center-smoothing, center+smoothing, sigDistShdw);

    vec4 tcolor = (outline > inside) ? outline * vec4(outline_color.a * outline_color.rgb, outline_color.a) : vec4(0.0);
    tcolor = mix(tcolor, color, inside);

    vec4 scolor = (shadow > outline) ? shadow * vec4(shadow_color.a * shadow_color.rgb, shadow_color.a) : tcolor;
    tcolor = mix(scolor, tcolor, outline);

    // Convert back to gamma space before returning
    tcolor.rgb = gammaCorrectOutput(tcolor.rgb);
    
    return tcolor;
}
`;

var metalnessModulatePS$1 = /* glsl */ `

vec3 getSpecularModulate(in vec3 specularity, in vec3 albedo, in float metalness, in float f0) {
    vec3 dielectricF0 = f0 * specularity;
    return mix(dielectricF0, albedo, metalness);
}

vec3 getAlbedoModulate(in vec3 albedo, in float metalness) {
    return albedo * (1.0 - metalness);
}
`;

// fragment shader internally used to apply morph targets in textures into a final texture containing
// blended morph targets
var morphPS$1 = /* glsl */ `

    varying vec2 uv0;

    uniform sampler2DArray morphTexture;
    uniform highp float morphFactor[{MORPH_TEXTURE_MAX_COUNT}];
    uniform highp uint morphIndex[{MORPH_TEXTURE_MAX_COUNT}];
    uniform int count;

    #ifdef MORPH_INT
        uniform vec3 aabbSize;
        uniform vec3 aabbMin;
    #endif

    void main (void) {
        highp vec3 color = vec3(0, 0, 0);

        ivec2 pixelCoords = ivec2(uv0 * vec2(textureSize(morphTexture, 0).xy));
        
        for (int i = 0; i < count; i++) {
            uint textureIndex = morphIndex[i];
            vec3 delta = texelFetch(morphTexture, ivec3(pixelCoords, int(textureIndex)), 0).xyz;
            color += morphFactor[i] * delta;
        }

        #ifdef MORPH_INT
            color = (color - aabbMin) / aabbSize * 65535.0;
            gl_FragColor = uvec4(color, 1u);
        #else
            gl_FragColor = vec4(color, 1.0);
        #endif
    }
`;

// vertex shader internally used to apply morph targets in textures into a final texture containing
// blended morph targets
var morphVS$1 = /* glsl */ `
    attribute vec2 vertex_position;
    varying vec2 uv0;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.5, 1.0);
        uv0 = vertex_position.xy * 0.5 + 0.5;
    }
`;

var msdfVS$1 = /* glsl */ `
attribute vec3 vertex_outlineParameters;
attribute vec3 vertex_shadowParameters;

varying vec4 outline_color;
varying float outline_thickness;
varying vec4 shadow_color;
varying vec2 shadow_offset;

void unpackMsdfParams() {
    vec3 little = mod(vertex_outlineParameters, 256.);
    vec3 big = (vertex_outlineParameters - little) / 256.;

    outline_color.rb = little.xy / 255.;
    outline_color.ga = big.xy / 255.;

    // _outlineThicknessScale === 0.2
    outline_thickness = little.z / 255. * 0.2;

    little = mod(vertex_shadowParameters, 256.);
    big = (vertex_shadowParameters - little) / 256.;

    shadow_color.rb = little.xy / 255.;
    shadow_color.ga = big.xy / 255.;

    // vec2(little.z, big.z) / 127. - 1. remaps shadow offset from [0, 254] to [-1, 1]
    // _shadowOffsetScale === 0.005
    shadow_offset = (vec2(little.z, big.z) / 127. - 1.) * 0.005;
}
`;

var normalVS$1 = /* glsl */ `
mat3 dNormalMatrix;

vec3 getNormal() {
    dNormalMatrix = getNormalMatrix(dModelMatrix);
    vec3 localNormal = getLocalNormal(vertex_normal);
    return normalize(dNormalMatrix * localNormal);
}
`;

var normalCoreVS$1 = /* glsl */ `

attribute vec3 vertex_normal;

uniform mat3 matrix_normal;

#ifdef MORPHING_NORMAL
    #ifdef MORPHING_INT
        uniform highp usampler2D morphNormalTex;
    #else
        uniform highp sampler2D morphNormalTex;
    #endif
#endif

vec3 getLocalNormal(vec3 vertexNormal) {

    vec3 localNormal = vertex_normal;

    #ifdef MORPHING_NORMAL

        ivec2 morphUV = getTextureMorphCoords();

        #ifdef MORPHING_INT
            vec3 morphNormal = vec3(texelFetch(morphNormalTex, ivec2(morphUV), 0).xyz) / 65535.0 * 2.0 - 1.0;
        #else
            vec3 morphNormal = texelFetch(morphNormalTex, ivec2(morphUV), 0).xyz;
        #endif

        localNormal += morphNormal;

    #endif

    return localNormal;
}

#if defined(SKIN) || defined(BATCH)
    mat3 getNormalMatrix(mat4 modelMatrix) {
        return mat3(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);
    }
#elif defined(INSTANCING)
    mat3 getNormalMatrix(mat4 modelMatrix) {
        return mat3(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);
    }
#else
    mat3 getNormalMatrix(mat4 modelMatrix) {
        return matrix_normal;
    }
#endif
`;

var normalMapPS$1 = /* glsl */ `
#ifdef STD_NORMAL_TEXTURE
    uniform float material_bumpiness;
#endif

#ifdef STD_NORMALDETAIL_TEXTURE
    uniform float material_normalDetailMapBumpiness;

    vec3 blendNormals(vec3 n1, vec3 n2) {
        // https://blog.selfshadow.com/publications/blending-in-detail/#detail-oriented
        n1 += vec3(0, 0, 1);
        n2 *= vec3(-1, -1, 1);
        return n1 * dot(n1, n2) / n1.z - n2;
    }
#endif

void getNormal() {
#ifdef STD_NORMAL_TEXTURE
    vec3 normalMap = {STD_NORMAL_TEXTURE_DECODE}(texture2DBias({STD_NORMAL_TEXTURE_NAME}, {STD_NORMAL_TEXTURE_UV}, textureBias));
    normalMap = mix(vec3(0.0, 0.0, 1.0), normalMap, material_bumpiness);

    #ifdef STD_NORMALDETAIL_TEXTURE
        vec3 normalDetailMap = {STD_NORMALDETAIL_TEXTURE_DECODE}(texture2DBias({STD_NORMALDETAIL_TEXTURE_NAME}, {STD_NORMALDETAIL_TEXTURE_UV}, textureBias));
        normalDetailMap = mix(vec3(0.0, 0.0, 1.0), normalDetailMap, material_normalDetailMapBumpiness);
        normalMap = blendNormals(normalMap, normalDetailMap);
    #endif

    dNormalW = normalize(dTBN * normalMap);
#else
    dNormalW = dVertexNormalW;
#endif
}
`;

var opacityPS$1 = /* glsl */ `
uniform float material_opacity;

void getOpacity() {
    dAlpha = material_opacity;

    #ifdef STD_OPACITY_TEXTURE
    dAlpha *= texture2DBias({STD_OPACITY_TEXTURE_NAME}, {STD_OPACITY_TEXTURE_UV}, textureBias).{STD_OPACITY_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_OPACITY_VERTEX
    dAlpha *= clamp(vVertexColor.{STD_OPACITY_VERTEX_CHANNEL}, 0.0, 1.0);
    #endif
}
`;

var opacityDitherPS$1 = /* glsl */ `

#if STD_OPACITY_DITHER == BAYER8
    #include "bayerPS"
#endif

uniform vec4 blueNoiseJitter;

#if STD_OPACITY_DITHER == BLUENOISE
    uniform sampler2D blueNoiseTex32;
#endif

void opacityDither(float alpha, float id) {
    #if STD_OPACITY_DITHER == BAYER8

        float noise = bayer8(floor(mod(gl_FragCoord.xy + blueNoiseJitter.xy + id, 8.0))) / 64.0;

    #else

        #if STD_OPACITY_DITHER == BLUENOISE
            vec2 uv = fract(gl_FragCoord.xy / 32.0 + blueNoiseJitter.xy + id);
            float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
        #endif

        #if STD_OPACITY_DITHER == IGNNOISE
            // based on https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
            vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189);
            float noise = fract(magic.z * fract(dot(gl_FragCoord.xy + blueNoiseJitter.xy + id, magic.xy)));
        #endif

    #endif

    // convert the noise to linear space, as that is specified in sRGB space (stores perceptual values)
    noise = pow(noise, 2.2);

    if (alpha < noise)
        discard;
}
`;

var outputPS$1 = /* glsl */ `
`;

var outputAlphaPS$1 = /* glsl */ `

#if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == ADDITIVEALPHA || defined(LIT_ALPHA_TO_COVERAGE)

    gl_FragColor.a = litArgs_opacity;

#elif LIT_BLEND_TYPE == PREMULTIPLIED

    gl_FragColor.rgb *= litArgs_opacity;
    gl_FragColor.a = litArgs_opacity;

#else

    gl_FragColor.a = 1.0;

#endif
`;

var outputTex2DPS$1 = /* glsl */ `
varying vec2 vUv0;

uniform sampler2D source;

void main(void) {
    gl_FragColor = texture2D(source, vUv0);
}
`;

var sheenPS$1 = /* glsl */ `

uniform vec3 material_sheen;

void getSheen() {
    vec3 sheenColor = material_sheen;

    #ifdef STD_SHEEN_TEXTURE
    sheenColor *= {STD_SHEEN_TEXTURE_DECODE}(texture2DBias({STD_SHEEN_TEXTURE_NAME}, {STD_SHEEN_TEXTURE_UV}, textureBias)).{STD_SHEEN_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SHEEN_VERTEX
    sheenColor *= saturate(vVertexColor.{STD_SHEEN_VERTEX_CHANNEL});
    #endif

    sSpecularity = sheenColor;
}
`;

var sheenGlossPS$1 = /* glsl */ `
uniform float material_sheenGloss;

void getSheenGlossiness() {
    float sheenGlossiness = material_sheenGloss;

    #ifdef STD_SHEENGLOSS_TEXTURE
    sheenGlossiness *= texture2DBias({STD_SHEENGLOSS_TEXTURE_NAME}, {STD_SHEENGLOSS_TEXTURE_UV}, textureBias).{STD_SHEENGLOSS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SHEENGLOSS_VERTEX
    sheenGlossiness *= saturate(vVertexColor.{STD_SHEENGLOSS_VERTEX_CHANNEL});
    #endif

    #ifdef STD_SHEENGLOSS_INVERT
    sheenGlossiness = 1.0 - sheenGlossiness;
    #endif

    sGlossiness = sheenGlossiness + 0.0000001;
}
`;

var parallaxPS$1 = /* glsl */ `
uniform float material_heightMapFactor;

void getParallax() {
    float parallaxScale = material_heightMapFactor;

    float height = texture2DBias({STD_HEIGHT_TEXTURE_NAME}, {STD_HEIGHT_TEXTURE_UV}, textureBias).{STD_HEIGHT_TEXTURE_CHANNEL};
    height = height * parallaxScale - parallaxScale * 0.5;
    vec3 viewDirT = dViewDirW * dTBN;

    viewDirT.z += 0.42;
    dUvOffset = height * (viewDirT.xy / viewDirT.z);
}
`;

var pickPS$1 = /* glsl */ `
uniform uint meshInstanceId;

vec4 getPickOutput() {
    const vec4 inv = vec4(1.0 / 255.0);
    const uvec4 shifts = uvec4(16, 8, 0, 24);
    uvec4 col = (uvec4(meshInstanceId) >> shifts) & uvec4(0xff);
    return vec4(col) * inv;
}
`;

var reflDirPS$1 = /* glsl */ `
void getReflDir(vec3 worldNormal, vec3 viewDir, float gloss, mat3 tbn) {
    dReflDirW = normalize(-reflect(viewDir, worldNormal));
}
`;

var reflDirAnisoPS$1 = /* glsl */ `
void getReflDir(vec3 worldNormal, vec3 viewDir, float gloss, mat3 tbn) {
    float roughness = sqrt(1.0 - min(gloss, 1.0));

    vec2 direction = dAnisotropyRotation;
    vec3 anisotropicT = normalize(tbn * vec3(direction, 0.0));
    vec3 anisotropicB = normalize(cross(tbn[2], anisotropicT));

    float anisotropy = dAnisotropy;
    vec3 anisotropicDirection = anisotropicB;
    vec3 anisotropicTangent = cross(anisotropicDirection, viewDir);
    vec3 anisotropicNormal = cross(anisotropicTangent, anisotropicDirection);
    float bendFactor = 1.0 - anisotropy * (1.0 - roughness);
    float bendFactor4 = bendFactor * bendFactor * bendFactor * bendFactor;
    vec3 bentNormal = normalize(mix(normalize(anisotropicNormal), normalize(worldNormal), bendFactor4));
    dReflDirW = reflect(-viewDir, bentNormal);
}
`;

var reflectionCCPS$1 = /* glsl */ `
#ifdef LIT_CLEARCOAT
void addReflectionCC(vec3 reflDir, float gloss) {
    ccReflection += calcReflection(reflDir, gloss);
}
#endif
`;

var reflectionCubePS$1 = /* glsl */ `
uniform samplerCube texture_cubeMap;
uniform float material_reflectivity;

vec3 calcReflection(vec3 reflDir, float gloss) {
    vec3 lookupVec = cubeMapProject(reflDir);
    lookupVec.x *= -1.0;
    return {reflectionDecode}(textureCube(texture_cubeMap, lookupVec));
}

void addReflection(vec3 reflDir, float gloss) {   
    dReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);
}
`;

var reflectionEnvHQPS$1 = /* glsl */ `
#ifndef ENV_ATLAS
    #define ENV_ATLAS
    uniform sampler2D texture_envAtlas;
#endif
uniform samplerCube texture_cubeMap;
uniform float material_reflectivity;

vec3 calcReflection(vec3 reflDir, float gloss) {
    vec3 dir = cubeMapProject(reflDir) * vec3(-1.0, 1.0, 1.0);
    vec2 uv = toSphericalUv(dir);

    // calculate roughness level
    float level = saturate(1.0 - gloss) * 5.0;
    float ilevel = floor(level);
    float flevel = level - ilevel;

    vec3 sharp = {reflectionCubemapDecode}(textureCube(texture_cubeMap, dir));
    vec3 roughA = {reflectionDecode}(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel)));
    vec3 roughB = {reflectionDecode}(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel + 1.0)));

    return processEnvironment(mix(sharp, mix(roughA, roughB, flevel), min(level, 1.0)));
}

void addReflection(vec3 reflDir, float gloss) {   
    dReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);
}
`;

var reflectionEnvPS$1 = /* glsl */ `
#ifndef ENV_ATLAS
#define ENV_ATLAS
    uniform sampler2D texture_envAtlas;
#endif
uniform float material_reflectivity;

// calculate mip level for shiny reflection given equirect coords uv.
float shinyMipLevel(vec2 uv) {
    vec2 dx = dFdx(uv);
    vec2 dy = dFdy(uv);

    // calculate second dF at 180 degrees
    vec2 uv2 = vec2(fract(uv.x + 0.5), uv.y);
    vec2 dx2 = dFdx(uv2);
    vec2 dy2 = dFdy(uv2);

    // calculate min of both sets of dF to handle discontinuity at the azim edge
    float maxd = min(max(dot(dx, dx), dot(dy, dy)), max(dot(dx2, dx2), dot(dy2, dy2)));

    return clamp(0.5 * log2(maxd) - 1.0 + textureBias, 0.0, 5.0);
}

vec3 calcReflection(vec3 reflDir, float gloss) {
    vec3 dir = cubeMapProject(reflDir) * vec3(-1.0, 1.0, 1.0);
    vec2 uv = toSphericalUv(dir);

    // calculate roughness level
    float level = saturate(1.0 - gloss) * 5.0;
    float ilevel = floor(level);

    // accessing the shiny (top level) reflection - perform manual mipmap lookup
    float level2 = shinyMipLevel(uv * atlasSize);
    float ilevel2 = floor(level2);

    vec2 uv0, uv1;
    float weight;
    if (ilevel == 0.0) {
        uv0 = mapShinyUv(uv, ilevel2);
        uv1 = mapShinyUv(uv, ilevel2 + 1.0);
        weight = level2 - ilevel2;
    } else {
        // accessing rough reflection - just sample the same part twice
        uv0 = uv1 = mapRoughnessUv(uv, ilevel);
        weight = 0.0;
    }

    vec3 linearA = {reflectionDecode}(texture2D(texture_envAtlas, uv0));
    vec3 linearB = {reflectionDecode}(texture2D(texture_envAtlas, uv1));
    vec3 linear0 = mix(linearA, linearB, weight);
    vec3 linear1 = {reflectionDecode}(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel + 1.0)));

    return processEnvironment(mix(linear0, linear1, level - ilevel));
}

void addReflection(vec3 reflDir, float gloss) {   
    dReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);
}
`;

var reflectionSpherePS$1 = /* glsl */ `
#ifndef VIEWMATRIX
    #define VIEWMATRIX
    uniform mat4 matrix_view;
#endif
uniform sampler2D texture_sphereMap;
uniform float material_reflectivity;

vec3 calcReflection(vec3 reflDir, float gloss) {
    vec3 reflDirV = (mat3(matrix_view) * reflDir);

    float m = 2.0 * sqrt(dot(reflDirV.xy, reflDirV.xy) + (reflDirV.z + 1.0) * (reflDirV.z + 1.0));
    vec2 sphereMapUv = reflDirV.xy / m + 0.5;

    return {reflectionDecode}(texture2D(texture_sphereMap, sphereMapUv));
}

void addReflection(vec3 reflDir, float gloss) {   
    dReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);
}
`;

var reflectionSheenPS$1 = /* glsl */ `

void addReflectionSheen(vec3 worldNormal, vec3 viewDir, float gloss) {
    float NoV = dot(worldNormal, viewDir);
    float alphaG = gloss * gloss;

    // Avoid using a LUT and approximate the values analytically
    float a = gloss < 0.25 ? -339.2 * alphaG + 161.4 * gloss - 25.9 : -8.48 * alphaG + 14.3 * gloss - 9.95;
    float b = gloss < 0.25 ? 44.0 * alphaG - 23.7 * gloss + 3.26 : 1.97 * alphaG - 3.27 * gloss + 0.72;
    float DG = exp( a * NoV + b ) + ( gloss < 0.25 ? 0.0 : 0.1 * ( gloss - 0.25 ) );
    sReflection += calcReflection(worldNormal, 0.0) * saturate(DG);
}
`;

var refractionCubePS$1 = /* glsl */ `
vec3 refract2(vec3 viewVec, vec3 normal, float IOR) {
    float vn = dot(viewVec, normal);
    float k = 1.0 - IOR * IOR * (1.0 - vn * vn);
    vec3 refrVec = IOR * viewVec - (IOR * vn + sqrt(k)) * normal;
    return refrVec;
}

void addRefraction(
    vec3 worldNormal, 
    vec3 viewDir, 
    float thickness, 
    float gloss, 
    vec3 specularity, 
    vec3 albedo, 
    float transmission,
    float refractionIndex,
    float dispersion
#if defined(LIT_IRIDESCENCE)
    , vec3 iridescenceFresnel,
    float iridescenceIntensity
#endif 
) {
    // use same reflection code with refraction vector
    vec4 tmpRefl = dReflection;
    vec3 reflectionDir = refract2(-viewDir, worldNormal, refractionIndex);
    dReflection = vec4(0);
    addReflection(reflectionDir, gloss);
    dDiffuseLight = mix(dDiffuseLight, dReflection.rgb * albedo, transmission);
    dReflection = tmpRefl;
}
`;

var refractionDynamicPS$1 = /* glsl */ `
uniform float material_invAttenuationDistance;
uniform vec3 material_attenuation;

vec3 evalRefractionColor(vec3 refractionVector, float gloss, float refractionIndex) {

    // The refraction point is the entry point + vector to exit point
    vec4 pointOfRefraction = vec4(vPositionW + refractionVector, 1.0);

    // Project to texture space so we can sample it
    vec4 projectionPoint = matrix_viewProjection * pointOfRefraction;

    // use built-in getGrabScreenPos function to convert screen position to grab texture uv coords
    vec2 uv = getGrabScreenPos(projectionPoint);

    // Use IOR and roughness to select mip
    float iorToRoughness = (1.0 - gloss) * clamp((1.0 / refractionIndex) * 2.0 - 2.0, 0.0, 1.0);
    float refractionLod = log2(uScreenSize.x) * iorToRoughness;
    vec3 refraction = texture2DLod(uSceneColorMap, uv, refractionLod).rgb;

    return refraction;
}

void addRefraction(
    vec3 worldNormal, 
    vec3 viewDir, 
    float thickness, 
    float gloss, 
    vec3 specularity, 
    vec3 albedo, 
    float transmission,
    float refractionIndex,
    float dispersion
#if defined(LIT_IRIDESCENCE)
    , vec3 iridescenceFresnel,
    float iridescenceIntensity
#endif
) {

    // Extract scale from the model transform
    vec3 modelScale;
    modelScale.x = length(vec3(matrix_model[0].xyz));
    modelScale.y = length(vec3(matrix_model[1].xyz));
    modelScale.z = length(vec3(matrix_model[2].xyz));

    // Calculate the refraction vector, scaled by the thickness and scale of the object
    vec3 scale = thickness * modelScale;
    vec3 refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndex)) * scale;
    vec3 refraction = evalRefractionColor(refractionVector, gloss, refractionIndex);

    #ifdef LIT_DISPERSION
        // based on the dispersion material property, calculate modified refraction index values
        // for R and B channels and evaluate the refraction color for them.
        float halfSpread = (1.0 / refractionIndex - 1.0) * 0.025 * dispersion;

        float refractionIndexR = refractionIndex - halfSpread;
        refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexR)) * scale;
        refraction.r = evalRefractionColor(refractionVector, gloss, refractionIndexR).r;

        float refractionIndexB = refractionIndex + halfSpread;
        refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexB)) * scale;
        refraction.b = evalRefractionColor(refractionVector, gloss, refractionIndexB).b;
    #endif

    // Transmittance is our final refraction color
    vec3 transmittance;
    if (material_invAttenuationDistance != 0.0)
    {
        vec3 attenuation = -log(material_attenuation) * material_invAttenuationDistance;
        transmittance = exp(-attenuation * length(refractionVector));
    }
    else
    {
        transmittance = refraction;
    }

    // Apply fresnel effect on refraction
    vec3 fresnel = vec3(1.0) - 
        getFresnel(
            dot(viewDir, worldNormal), 
            gloss, 
            specularity
        #if defined(LIT_IRIDESCENCE)
            , iridescenceFresnel,
            iridescenceIntensity
        #endif
        );
    dDiffuseLight = mix(dDiffuseLight, refraction * transmittance * fresnel, transmission);
}
`;

// This shader requires the following #DEFINEs:
//
// PROCESS_FUNC - must be one of reproject, prefilter
// DECODE_FUNC - must be one of decodeRGBM, decodeRGBE, decodeGamma or decodeLinear
// ENCODE_FUNC - must be one of encodeRGBM, encodeRGBE, encideGamma or encodeLinear
// SOURCE_FUNC - must be one of sampleCubemap, sampleEquirect, sampleOctahedral
// TARGET_FUNC - must be one of getDirectionCubemap, getDirectionEquirect, getDirectionOctahedral
//
// When filtering:
// NUM_SAMPLES - number of samples
// NUM_SAMPLES_SQRT - sqrt of number of samples
var reprojectPS$1 = /* glsl */ `

varying vec2 vUv0;

// source
#ifdef CUBEMAP_SOURCE
    uniform samplerCube sourceCube;
#else
    uniform sampler2D sourceTex;
#endif

#ifdef USE_SAMPLES_TEX
    // samples
    uniform sampler2D samplesTex;
    uniform vec2 samplesTexInverseSize;
#endif

// params:
// x - target cubemap face 0..6
// y - target image total pixels
// z - source cubemap size
uniform vec3 params;

float targetFace() { return params.x; }
float targetTotalPixels() { return params.y; }
float sourceTotalPixels() { return params.z; }

float PI = 3.141592653589793;

float saturate(float x) {
    return clamp(x, 0.0, 1.0);
}

#include "decodePS"
#include "encodePS"

//-- supported projections

vec3 modifySeams(vec3 dir, float scale) {
    vec3 adir = abs(dir);
    float M = max(max(adir.x, adir.y), adir.z);
    return dir / M * vec3(
        adir.x == M ? 1.0 : scale,
        adir.y == M ? 1.0 : scale,
        adir.z == M ? 1.0 : scale
    );
}

vec2 toSpherical(vec3 dir) {
    return vec2(dir.xz == vec2(0.0) ? 0.0 : atan(dir.x, dir.z), asin(dir.y));
}

vec3 fromSpherical(vec2 uv) {
    return vec3(cos(uv.y) * sin(uv.x),
                sin(uv.y),
                cos(uv.y) * cos(uv.x));
}

vec3 getDirectionEquirect() {
    return fromSpherical((vec2(vUv0.x, 1.0 - vUv0.y) * 2.0 - 1.0) * vec2(PI, PI * 0.5));
}

// octahedral code, based on https://jcgt.org/published/0003/02/01/
// "Survey of Efficient Representations for Independent Unit Vectors" by Cigolle, Donow, Evangelakos, Mara, McGuire, Meyer

float signNotZero(float k){
    return(k >= 0.0) ? 1.0 : -1.0;
}

vec2 signNotZero(vec2 v) {
    return vec2(signNotZero(v.x), signNotZero(v.y));
}

// Returns a unit vector. Argument o is an octahedral vector packed via octEncode, on the [-1, +1] square
vec3 octDecode(vec2 o) {
    vec3 v = vec3(o.x, 1.0 - abs(o.x) - abs(o.y), o.y);
    if (v.y < 0.0) {
        v.xz = (1.0 - abs(v.zx)) * signNotZero(v.xz);
    }
    return normalize(v);
}

vec3 getDirectionOctahedral() {
    return octDecode(vec2(vUv0.x, 1.0 - vUv0.y) * 2.0 - 1.0);
}

// Assumes that v is a unit vector. The result is an octahedral vector on the [-1, +1] square
vec2 octEncode(in vec3 v) {
    float l1norm = abs(v.x) + abs(v.y) + abs(v.z);
    vec2 result = v.xz * (1.0 / l1norm);
    if (v.y < 0.0) {
        result = (1.0 - abs(result.yx)) * signNotZero(result.xy);
    }
    return result;
}

/////////////////////////////////////////////////////////////////////

#ifdef CUBEMAP_SOURCE
    vec4 sampleCubemap(vec3 dir) {
        return textureCube(sourceCube, modifySeams(dir, 1.0));
    }

    vec4 sampleCubemap(vec2 sph) {
        return sampleCubemap(fromSpherical(sph));
    }

    vec4 sampleCubemap(vec3 dir, float mipLevel) {
        return textureCubeLod(sourceCube, modifySeams(dir, 1.0), mipLevel);
    }

    vec4 sampleCubemap(vec2 sph, float mipLevel) {
        return sampleCubemap(fromSpherical(sph), mipLevel);
    }
#else

    vec4 sampleEquirect(vec2 sph) {
        vec2 uv = sph / vec2(PI * 2.0, PI) + 0.5;
        return texture2D(sourceTex, vec2(uv.x, 1.0 - uv.y));
    }

    vec4 sampleEquirect(vec3 dir) {
        return sampleEquirect(toSpherical(dir));
    }

    vec4 sampleEquirect(vec2 sph, float mipLevel) {
        vec2 uv = sph / vec2(PI * 2.0, PI) + 0.5;
        return texture2DLod(sourceTex, vec2(uv.x, 1.0 - uv.y), mipLevel);
    }

    vec4 sampleEquirect(vec3 dir, float mipLevel) {
        return sampleEquirect(toSpherical(dir), mipLevel);
    }

    vec4 sampleOctahedral(vec3 dir) {
        vec2 uv = octEncode(dir) * 0.5 + 0.5;
        return texture2D(sourceTex, vec2(uv.x, 1.0 - uv.y));
    }

    vec4 sampleOctahedral(vec2 sph) {
        return sampleOctahedral(fromSpherical(sph));
    }

    vec4 sampleOctahedral(vec3 dir, float mipLevel) {
        vec2 uv = octEncode(dir) * 0.5 + 0.5;
        return texture2DLod(sourceTex, vec2(uv.x, 1.0 - uv.y), mipLevel);
    }

    vec4 sampleOctahedral(vec2 sph, float mipLevel) {
        return sampleOctahedral(fromSpherical(sph), mipLevel);
    }

#endif

vec3 getDirectionCubemap() {
    vec2 st = vUv0 * 2.0 - 1.0;
    float face = targetFace();

    vec3 vec;
    if (face == 0.0) {
        vec = vec3(1, -st.y, -st.x);
    } else if (face == 1.0) {
        vec = vec3(-1, -st.y, st.x);
    } else if (face == 2.0) {
        vec = vec3(st.x, 1, st.y);
    } else if (face == 3.0) {
        vec = vec3(st.x, -1, -st.y);
    } else if (face == 4.0) {
        vec = vec3(st.x, -st.y, 1);
    } else {
        vec = vec3(-st.x, -st.y, -1);
    }

    return normalize(modifySeams(vec, 1.0));
}

mat3 matrixFromVector(vec3 n) { // frisvad
    float a = 1.0 / (1.0 + n.z);
    float b = -n.x * n.y * a;
    vec3 b1 = vec3(1.0 - n.x * n.x * a, b, -n.x);
    vec3 b2 = vec3(b, 1.0 - n.y * n.y * a, -n.y);
    return mat3(b1, b2, n);
}

mat3 matrixFromVectorSlow(vec3 n) {
    vec3 up = (1.0 - abs(n.y) <= 0.0000001) ? vec3(0.0, 0.0, n.y > 0.0 ? 1.0 : -1.0) : vec3(0.0, 1.0, 0.0);
    vec3 x = normalize(cross(up, n));
    vec3 y = cross(n, x);
    return mat3(x, y, n);
}

vec4 reproject() {
    if ({NUM_SAMPLES} <= 1) {
        // single sample
        return {ENCODE_FUNC}({DECODE_FUNC}({SOURCE_FUNC}({TARGET_FUNC}())));
    } else {
        // multi sample
        vec3 t = {TARGET_FUNC}();
        vec3 tu = dFdx(t);
        vec3 tv = dFdy(t);

        vec3 result = vec3(0.0);
        for (float u = 0.0; u < {NUM_SAMPLES_SQRT}; ++u) {
            for (float v = 0.0; v < {NUM_SAMPLES_SQRT}; ++v) {
                result += {DECODE_FUNC}({SOURCE_FUNC}(normalize(t +
                                                            tu * (u / {NUM_SAMPLES_SQRT} - 0.5) +
                                                            tv * (v / {NUM_SAMPLES_SQRT} - 0.5))));
            }
        }
        return {ENCODE_FUNC}(result / ({NUM_SAMPLES_SQRT} * {NUM_SAMPLES_SQRT}));
    }
}

vec4 unpackFloat = vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0);

#ifdef USE_SAMPLES_TEX
    void unpackSample(int i, out vec3 L, out float mipLevel) {
        float u = (float(i * 4) + 0.5) * samplesTexInverseSize.x;
        float v = (floor(u) + 0.5) * samplesTexInverseSize.y;

        vec4 raw;
        raw.x = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat); u += samplesTexInverseSize.x;
        raw.y = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat); u += samplesTexInverseSize.x;
        raw.z = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat); u += samplesTexInverseSize.x;
        raw.w = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat);

        L.xyz = raw.xyz * 2.0 - 1.0;
        mipLevel = raw.w * 8.0;
    }

    // convolve an environment given pre-generated samples
    vec4 prefilterSamples() {
        // construct vector space given target direction
        mat3 vecSpace = matrixFromVectorSlow({TARGET_FUNC}());

        vec3 L;
        float mipLevel;

        vec3 result = vec3(0.0);
        float totalWeight = 0.0;
        for (int i = 0; i < {NUM_SAMPLES}; ++i) {
            unpackSample(i, L, mipLevel);
            result += {DECODE_FUNC}({SOURCE_FUNC}(vecSpace * L, mipLevel)) * L.z;
            totalWeight += L.z;
        }

        return {ENCODE_FUNC}(result / totalWeight);
    }

    // unweighted version of prefilterSamples
    vec4 prefilterSamplesUnweighted() {
        // construct vector space given target direction
        mat3 vecSpace = matrixFromVectorSlow({TARGET_FUNC}());

        vec3 L;
        float mipLevel;

        vec3 result = vec3(0.0);
        float totalWeight = 0.0;
        for (int i = 0; i < {NUM_SAMPLES}; ++i) {
            unpackSample(i, L, mipLevel);
            result += {DECODE_FUNC}({SOURCE_FUNC}(vecSpace * L, mipLevel));
        }

        return {ENCODE_FUNC}(result / float({NUM_SAMPLES}));
    }
#endif

void main(void) {
    gl_FragColor = {PROCESS_FUNC}();
}
`;

var reprojectVS$1 = /* glsl */ `
attribute vec2 vertex_position;
uniform vec4 uvMod;
varying vec2 vUv0;

void main(void) {
    gl_Position = vec4(vertex_position, 0.5, 1.0);
    vUv0 = getImageEffectUV((vertex_position.xy * 0.5 + 0.5) * uvMod.xy + uvMod.zw);
}
`;

var screenDepthPS$1 = /* glsl */ `
uniform highp sampler2D uSceneDepthMap;

#ifndef SCREENSIZE
    #define SCREENSIZE
    uniform vec4 uScreenSize;
#endif

#ifndef VIEWMATRIX
    #define VIEWMATRIX
    uniform mat4 matrix_view;
#endif

#ifndef LINEARIZE_DEPTH
    #define LINEARIZE_DEPTH
    
    #ifndef CAMERAPLANES
        #define CAMERAPLANES
        uniform vec4 camera_params; // x: 1 / camera_far,      y: camera_far,     z: camera_near,        w: is_ortho
    #endif

    float linearizeDepth(float z) {
        if (camera_params.w == 0.0)
            return (camera_params.z * camera_params.y) / (camera_params.y + z * (camera_params.z - camera_params.y));
        else
            return camera_params.z + z * (camera_params.y - camera_params.z);
    }
#endif

float delinearizeDepth(float linearDepth) {
    if (camera_params.w == 0.0) {
        return (camera_params.y * (camera_params.z - linearDepth)) / (linearDepth * (camera_params.z - camera_params.y));
    } else {
        return (linearDepth - camera_params.z) / (camera_params.y - camera_params.z);
    }
}

// Retrieves rendered linear camera depth by UV
float getLinearScreenDepth(vec2 uv) {
    #ifdef SCENE_DEPTHMAP_LINEAR
        #ifdef SCENE_DEPTHMAP_FLOAT
            return texture2D(uSceneDepthMap, uv).r;
        #else

            ivec2 textureSize = textureSize(uSceneDepthMap, 0);
            ivec2 texel = ivec2(uv * vec2(textureSize));
            vec4 data = texelFetch(uSceneDepthMap, texel, 0);

            uint intBits = 
                (uint(data.r * 255.0) << 24u) |
                (uint(data.g * 255.0) << 16u) |
                (uint(data.b * 255.0) << 8u) |
                uint(data.a * 255.0);

            return uintBitsToFloat(intBits);
        #endif
    #else
        return linearizeDepth(texture2D(uSceneDepthMap, uv).r);
    #endif
}

#ifndef VERTEXSHADER
    // Retrieves rendered linear camera depth under the current pixel
    float getLinearScreenDepth() {
        vec2 uv = gl_FragCoord.xy * uScreenSize.zw;
        return getLinearScreenDepth(uv);
    }
#endif

// Generates linear camera depth for the given world position
float getLinearDepth(vec3 pos) {
    return -(matrix_view * vec4(pos, 1.0)).z;
}
`;

var shadowCascadesPS$1 = /* glsl */ `

// function which selects a shadow projection matrix index based on cascade distances 
int getShadowCascadeIndex(vec4 shadowCascadeDistances, int shadowCascadeCount) {

    // depth in 0 .. far plane range
    float depth = 1.0 / gl_FragCoord.w;

    // 1.0 if depth >= distance, 0.0 otherwise
    vec4 comparisons = step(shadowCascadeDistances, vec4(depth));

    // sum is the index
    int cascadeIndex = int(dot(comparisons, vec4(1.0)));

    // limit to actual number of used cascades
    return min(cascadeIndex, shadowCascadeCount - 1);
}

// function which modifies cascade index to dither between cascades
int ditherShadowCascadeIndex(int cascadeIndex, vec4 shadowCascadeDistances, int shadowCascadeCount, float blendFactor) {
 
    if (cascadeIndex < shadowCascadeCount - 1) {
        float currentRangeEnd = shadowCascadeDistances[cascadeIndex];
        float transitionStart = blendFactor * currentRangeEnd; // Start overlap factor away from the end distance
        float depth = 1.0 / gl_FragCoord.w;

        if (depth > transitionStart) {
            // Calculate a transition factor (0.0 to 1.0) within the overlap range
            float transitionFactor = smoothstep(transitionStart, currentRangeEnd, depth);

            // Add pseudo-random dithering
            // TODO: replace by user selectable dithering method
            float dither = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453);
            if (dither < transitionFactor) {
                cascadeIndex += 1;
            }
        }
    }

    return cascadeIndex;
}

vec3 fadeShadow(vec3 shadowCoord, vec4 shadowCascadeDistances) {                  
    // if the pixel is past the shadow distance, remove shadow
    // this enforces straight line instead of corner of shadow which moves when camera rotates  
    float depth = 1.0 / gl_FragCoord.w;
    if (depth > shadowCascadeDistances.w) {
        shadowCoord.z = -9999999.0;
    }

    return shadowCoord;
}
`;

var shadowEVSMPS$1 = /* glsl */ `

// ------ VSM Shared ------

float linstep(float a, float b, float v) {
    return saturate((v - a) / (b - a));
}

float reduceLightBleeding(float pMax, float amount) {
   // Remove the [0, amount] tail and linearly rescale (amount, 1].
   return linstep(amount, 1.0, pMax);
}

float chebyshevUpperBound(vec2 moments, float mean, float minVariance, float lightBleedingReduction) {
    // Compute variance
    float variance = moments.y - (moments.x * moments.x);
    variance = max(variance, minVariance);

    // Compute probabilistic upper bound
    float d = mean - moments.x;
    float pMax = variance / (variance + (d * d));

    pMax = reduceLightBleeding(pMax, lightBleedingReduction);

    // One-tailed Chebyshev
    return (mean <= moments.x ? 1.0 : pMax);
}

float calculateEVSM(vec3 moments, float Z, float vsmBias, float exponent) {
    Z = 2.0 * Z - 1.0;
    float warpedDepth = exp(exponent * Z);

    moments.xy += vec2(warpedDepth, warpedDepth*warpedDepth) * (1.0 - moments.z);

    float VSMBias = vsmBias;//0.01 * 0.25;
    float depthScale = VSMBias * exponent * warpedDepth;
    float minVariance1 = depthScale * depthScale;
    return chebyshevUpperBound(moments.xy, warpedDepth, minVariance1, 0.1);
}

// ------ VSM 16 ------

float VSM16(TEXTURE_ACCEPT(tex), vec2 texCoords, float resolution, float Z, float vsmBias, float exponent) {
    vec3 moments = texture2DLod(tex, texCoords, 0.0).xyz;
    return calculateEVSM(moments, Z, vsmBias, exponent);
}

float getShadowVSM16(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent) {
    return VSM16(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);
}

float getShadowSpotVSM16(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent, vec3 lightDir) {
    return VSM16(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, length(lightDir) * shadowParams.w + shadowParams.z, shadowParams.y, exponent);
}

// ------ VSM 32 ------

float VSM32(TEXTURE_ACCEPT(tex), vec2 texCoords, float resolution, float Z, float vsmBias, float exponent) {

    #ifdef CAPS_TEXTURE_FLOAT_FILTERABLE
        vec3 moments = texture2DLod(tex, texCoords, 0.0).xyz;
    #else
        // manual bilinear filtering
        float pixelSize = 1.0 / resolution;
        texCoords -= vec2(pixelSize);
        vec3 s00 = texture2DLod(tex, texCoords, 0.0).xyz;
        vec3 s10 = texture2DLod(tex, texCoords + vec2(pixelSize, 0), 0.0).xyz;
        vec3 s01 = texture2DLod(tex, texCoords + vec2(0, pixelSize), 0.0).xyz;
        vec3 s11 = texture2DLod(tex, texCoords + vec2(pixelSize), 0.0).xyz;
        vec2 fr = fract(texCoords * resolution);
        vec3 h0 = mix(s00, s10, fr.x);
        vec3 h1 = mix(s01, s11, fr.x);
        vec3 moments = mix(h0, h1, fr.y);
    #endif

    return calculateEVSM(moments, Z, vsmBias, exponent);
}

float getShadowVSM32(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent) {
    return VSM32(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);
}

float getShadowSpotVSM32(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent, vec3 lightDir) {
    float Z = length(lightDir) * shadowParams.w + shadowParams.z;
    return VSM32(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);
}
`;

var shadowPCF1PS$1 = /* glsl */ `
// ----- Directional/Spot Sampling -----

float getShadowPCF1x1(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return textureShadow(shadowMap, shadowCoord);
}

float getShadowSpotPCF1x1(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return textureShadow(shadowMap, shadowCoord);
}

// ----- Omni Sampling -----

#ifndef WEBGPU

float getShadowOmniPCF1x1(samplerCubeShadow shadowMap, vec3 shadowCoord, vec4 shadowParams, vec3 lightDir) {
    float shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;
    return texture(shadowMap, vec4(lightDir, shadowZ));
}

#endif
`;

var shadowPCF3PS$1 = /* glsl */ `
// ----- Directional/Spot Sampling -----

float _getShadowPCF3x3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec3 shadowParams) {
    float z = shadowCoord.z;
    vec2 uv = shadowCoord.xy * shadowParams.x; // 1 unit - 1 texel
    float shadowMapSizeInv = 1.0 / shadowParams.x;
    vec2 base_uv = floor(uv + 0.5);
    float s = (uv.x + 0.5 - base_uv.x);
    float t = (uv.y + 0.5 - base_uv.y); 
    base_uv -= vec2(0.5);
    base_uv *= shadowMapSizeInv;

    float sum = 0.0;

    float uw0 = (3.0 - 2.0 * s);
    float uw1 = (1.0 + 2.0 * s);

    float u0 = (2.0 - s) / uw0 - 1.0;
    float u1 = s / uw1 + 1.0;

    float vw0 = (3.0 - 2.0 * t);
    float vw1 = (1.0 + 2.0 * t);

    float v0 = (2.0 - t) / vw0 - 1.0;
    float v1 = t / vw1 + 1.0;

    u0 = u0 * shadowMapSizeInv + base_uv.x;
    v0 = v0 * shadowMapSizeInv + base_uv.y;

    u1 = u1 * shadowMapSizeInv + base_uv.x;
    v1 = v1 * shadowMapSizeInv + base_uv.y;

    sum += uw0 * vw0 * textureShadow(shadowMap, vec3(u0, v0, z));
    sum += uw1 * vw0 * textureShadow(shadowMap, vec3(u1, v0, z));
    sum += uw0 * vw1 * textureShadow(shadowMap, vec3(u0, v1, z));
    sum += uw1 * vw1 * textureShadow(shadowMap, vec3(u1, v1, z));

    sum *= 1.0f / 16.0;
    return sum;
}

float getShadowPCF3x3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return _getShadowPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);
}

float getShadowSpotPCF3x3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return _getShadowPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);
}

// ----- Omni Sampling -----

#ifndef WEBGPU

float getShadowOmniPCF3x3(samplerCubeShadow shadowMap, vec4 shadowParams, vec3 dir) {
    
    // Calculate shadow depth from the light direction
    float shadowZ = length(dir) * shadowParams.w + shadowParams.z;

    // offset
    float z = 1.0 / float(textureSize(shadowMap, 0));
    vec3 tc = normalize(dir);

    // average 4 samples - not a strict 3x3 PCF but that's tricky with cubemaps
    mediump vec4 shadows;
    shadows.x = texture(shadowMap, vec4(tc + vec3( z, z, z), shadowZ));
    shadows.y = texture(shadowMap, vec4(tc + vec3(-z,-z, z), shadowZ));
    shadows.z = texture(shadowMap, vec4(tc + vec3(-z, z,-z), shadowZ));
    shadows.w = texture(shadowMap, vec4(tc + vec3( z,-z,-z), shadowZ));

    return dot(shadows, vec4(0.25));
}

float getShadowOmniPCF3x3(samplerCubeShadow shadowMap, vec3 shadowCoord, vec4 shadowParams, vec3 lightDir) {
    return getShadowOmniPCF3x3(shadowMap, shadowParams, lightDir);
}

#endif
`;

var shadowPCF5PS$1 = /* glsl */ `
float _getShadowPCF5x5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec3 shadowParams) {
    // http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/

    float z = shadowCoord.z;
    vec2 uv = shadowCoord.xy * shadowParams.x; // 1 unit - 1 texel
    float shadowMapSizeInv = 1.0 / shadowParams.x;
    vec2 base_uv = floor(uv + 0.5);
    float s = (uv.x + 0.5 - base_uv.x);
    float t = (uv.y + 0.5 - base_uv.y);
    base_uv -= vec2(0.5);
    base_uv *= shadowMapSizeInv;


    float uw0 = (4.0 - 3.0 * s);
    float uw1 = 7.0;
    float uw2 = (1.0 + 3.0 * s);

    float u0 = (3.0 - 2.0 * s) / uw0 - 2.0;
    float u1 = (3.0 + s) / uw1;
    float u2 = s / uw2 + 2.0;

    float vw0 = (4.0 - 3.0 * t);
    float vw1 = 7.0;
    float vw2 = (1.0 + 3.0 * t);

    float v0 = (3.0 - 2.0 * t) / vw0 - 2.0;
    float v1 = (3.0 + t) / vw1;
    float v2 = t / vw2 + 2.0;

    float sum = 0.0;

    u0 = u0 * shadowMapSizeInv + base_uv.x;
    v0 = v0 * shadowMapSizeInv + base_uv.y;

    u1 = u1 * shadowMapSizeInv + base_uv.x;
    v1 = v1 * shadowMapSizeInv + base_uv.y;

    u2 = u2 * shadowMapSizeInv + base_uv.x;
    v2 = v2 * shadowMapSizeInv + base_uv.y;

    sum += uw0 * vw0 * textureShadow(shadowMap, vec3(u0, v0, z));
    sum += uw1 * vw0 * textureShadow(shadowMap, vec3(u1, v0, z));
    sum += uw2 * vw0 * textureShadow(shadowMap, vec3(u2, v0, z));

    sum += uw0 * vw1 * textureShadow(shadowMap, vec3(u0, v1, z));
    sum += uw1 * vw1 * textureShadow(shadowMap, vec3(u1, v1, z));
    sum += uw2 * vw1 * textureShadow(shadowMap, vec3(u2, v1, z));

    sum += uw0 * vw2 * textureShadow(shadowMap, vec3(u0, v2, z));
    sum += uw1 * vw2 * textureShadow(shadowMap, vec3(u1, v2, z));
    sum += uw2 * vw2 * textureShadow(shadowMap, vec3(u2, v2, z));

    sum *= 1.0f / 144.0;
    sum = saturate(sum);

    return sum;
}

float getShadowPCF5x5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return _getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);
}

float getShadowSpotPCF5x5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {
    return _getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);
}
`;

var shadowPCSSPS = /* glsl */ `

/**
 * PCSS is a shadow sampling method that provides contact hardening soft shadows, used for omni and spot lights.
 * Based on: 
 * - https://www.gamedev.net/tutorials/programming/graphics/effect-area-light-shadows-part-1-pcss-r4971/
 * - https://github.com/pboechat/PCSS 
 */

#define PCSS_SAMPLE_COUNT 16

uniform float pcssDiskSamples[PCSS_SAMPLE_COUNT];
uniform float pcssSphereSamples[PCSS_SAMPLE_COUNT];

vec2 vogelDisk(int sampleIndex, float count, float phi, float r) {
    const float GoldenAngle = 2.4;
    float theta = float(sampleIndex) * GoldenAngle + phi;

    float sine = sin(theta);
    float cosine = cos(theta);
    return vec2(r * cosine, r * sine);
}

vec3 vogelSphere(int sampleIndex, float count, float phi, float r) {
    const float GoldenAngle = 2.4;
    float theta = float(sampleIndex) * GoldenAngle + phi;

    float weight = float(sampleIndex) / count;
    return vec3(cos(theta) * r, weight, sin(theta) * r);
}

float noise(vec2 screenPos) {
    const float PHI = 1.61803398874989484820459;  // Φ = Golden Ratio   
    return fract(sin(dot(screenPos * PHI, screenPos)) * screenPos.x);
}

float viewSpaceDepth(float depth, mat4 invProjection) {
    float z = depth * 2.0 - 1.0;
    vec4 clipSpace = vec4(0.0, 0.0, z, 1.0);
    vec4 viewSpace = invProjection * clipSpace;
    return viewSpace.z;
}

float PCSSBlockerDistance(TEXTURE_ACCEPT(shadowMap), vec2 sampleCoords[PCSS_SAMPLE_COUNT], vec2 shadowCoords, vec2 searchSize, float z, vec4 cameraParams) {

    float blockers = 0.0;
    float averageBlocker = 0.0;
    for (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {
        vec2 offset = sampleCoords[i] * searchSize;
        vec2 sampleUV = shadowCoords + offset;

        float blocker = texture2DLod(shadowMap, sampleUV, 0.0).r;
        float isBlocking = step(blocker, z);
        blockers += isBlocking;
        averageBlocker += blocker * isBlocking;
    }

    if (blockers > 0.0)
        return averageBlocker / blockers;
    return -1.0;
}

float PCSS(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoords, vec4 cameraParams, vec2 shadowSearchArea) {
    float receiverDepth = linearizeDepthWithParams(shadowCoords.z, cameraParams);

    vec2 samplePoints[PCSS_SAMPLE_COUNT];
    const float PI = 3.141592653589793;
    float noise = noise( gl_FragCoord.xy ) * 2.0 * PI;
    for (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {
        float pcssPresample = pcssDiskSamples[i];
        samplePoints[i] = vogelDisk(i, float(PCSS_SAMPLE_COUNT), noise, pcssPresample);
    }

    float averageBlocker = PCSSBlockerDistance(TEXTURE_PASS(shadowMap), samplePoints, shadowCoords.xy, shadowSearchArea, receiverDepth, cameraParams);
    if (averageBlocker == -1.0) {
        return 1.0;
    } else {
        float depthDifference = (receiverDepth - averageBlocker) / 3.0;
        vec2 filterRadius = depthDifference * shadowSearchArea;

        float shadow = 0.0;

        for (int i = 0; i < PCSS_SAMPLE_COUNT; i ++)
        {
            vec2 sampleUV = samplePoints[i] * filterRadius;
            sampleUV = shadowCoords.xy + sampleUV;

            float depth = texture2DLod(shadowMap, sampleUV, 0.0).r;
            shadow += step(receiverDepth, depth);
        }
        return shadow / float(PCSS_SAMPLE_COUNT);
    } 
}

#ifndef WEBGPU

float PCSSCubeBlockerDistance(samplerCube shadowMap, vec3 lightDirNorm, vec3 samplePoints[PCSS_SAMPLE_COUNT], float z, float shadowSearchArea) {
    float blockers = 0.0;
    float averageBlocker = 0.0;
    for (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {
        vec3 sampleDir = lightDirNorm + samplePoints[i] * shadowSearchArea;
        sampleDir = normalize(sampleDir);

        float blocker = textureCubeLod(shadowMap, sampleDir, 0.0).r;
        float isBlocking = step(blocker, z);
        blockers += isBlocking;
        averageBlocker += blocker * isBlocking;
    }

    if (blockers > 0.0)
        return averageBlocker / blockers;
    return -1.0;
}

float PCSSCube(samplerCube shadowMap, vec4 shadowParams, vec3 shadowCoords, vec4 cameraParams, float shadowSearchArea, vec3 lightDir) {
    
    vec3 samplePoints[PCSS_SAMPLE_COUNT];
    const float PI = 3.141592653589793;
    float noise = noise( gl_FragCoord.xy ) * 2.0 * PI;
    for (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {
        float r = pcssSphereSamples[i];
        samplePoints[i] = vogelSphere(i, float(PCSS_SAMPLE_COUNT), noise, r);
    }

    float receiverDepth = length(lightDir) * shadowParams.w + shadowParams.z;
    vec3 lightDirNorm = normalize(lightDir);
    
    float averageBlocker = PCSSCubeBlockerDistance(shadowMap, lightDirNorm, samplePoints, receiverDepth, shadowSearchArea);
    if (averageBlocker == -1.0) {
        return 1.0;
    } else {

        float filterRadius = ((receiverDepth - averageBlocker) / averageBlocker) * shadowSearchArea;

        float shadow = 0.0;
        for (int i = 0; i < PCSS_SAMPLE_COUNT; i++)
        {
            vec3 offset = samplePoints[i] * filterRadius;
            vec3 sampleDir = lightDirNorm + offset;
            sampleDir = normalize(sampleDir);

            float depth = textureCubeLod(shadowMap, sampleDir, 0.0).r;
            shadow += step(receiverDepth, depth);
        }
        return shadow / float(PCSS_SAMPLE_COUNT);
    }
}

float getShadowOmniPCSS(samplerCube shadowMap, vec3 shadowCoord, vec4 shadowParams, vec4 cameraParams, vec2 shadowSearchArea, vec3 lightDir) {
    return PCSSCube(shadowMap, shadowParams, shadowCoord, cameraParams, shadowSearchArea.x, lightDir);
}

#endif

float getShadowSpotPCSS(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, vec4 cameraParams, vec2 shadowSearchArea, vec3 lightDir) {
    return PCSS(TEXTURE_PASS(shadowMap), shadowCoord, cameraParams, shadowSearchArea);
}

`;

// Soft directional shadows PCSS - with and without blocker search.
var shadowSoftPS$1 = /* glsl */ `

highp float fractSinRand(const in vec2 uv) {
    const float PI = 3.141592653589793;
    const highp float a = 12.9898, b = 78.233, c = 43758.5453;
    highp float dt = dot(uv.xy, vec2(a, b)), sn = mod(dt, PI);
    return fract(sin(sn) * c);
}

// struct to hold precomputed constants and current state
struct VogelDiskData {
    float invNumSamples;
    float initialAngle;
    float currentPointId;
};

// prepare the Vogel disk constants and initialize the current state in the struct
void prepareDiskConstants(out VogelDiskData data, int sampleCount, float randomSeed) {
    const float pi2 = 6.28318530718;
    data.invNumSamples = 1.0 / float(sampleCount);
    data.initialAngle = randomSeed * pi2;
    data.currentPointId = 0.0;
}


vec2 generateDiskSample(inout VogelDiskData data) {
    const float GOLDEN_ANGLE = 2.399963;
    float r = sqrt((data.currentPointId + 0.5) * data.invNumSamples);
    float theta = data.currentPointId * GOLDEN_ANGLE + data.initialAngle;

    vec2 offset = vec2(cos(theta), sin(theta)) * pow(r, 1.33);

    data.currentPointId += 1.0;
    return offset;
}

void PCSSFindBlocker(TEXTURE_ACCEPT(shadowMap), out float avgBlockerDepth, out int numBlockers,
    vec2 shadowCoords, float z, int shadowBlockerSamples, float penumbraSize, float invShadowMapSize, float randomSeed) {

    VogelDiskData diskData;
    prepareDiskConstants(diskData, shadowBlockerSamples, randomSeed);

    float searchWidth = penumbraSize * invShadowMapSize;
    float blockerSum = 0.0;
    numBlockers = 0;

    for( int i = 0; i < shadowBlockerSamples; ++i ) {
        vec2 diskUV = generateDiskSample(diskData);
        vec2 sampleUV = shadowCoords + diskUV * searchWidth;
        float shadowMapDepth = texture2DLod(shadowMap, sampleUV, 0.0).r;
        if ( shadowMapDepth < z ) {
            blockerSum += shadowMapDepth;
            numBlockers++;
        }
    }
    avgBlockerDepth = blockerSum / float(numBlockers);
}

float PCSSFilter(TEXTURE_ACCEPT(shadowMap), vec2 uv, float receiverDepth, int shadowSamples, float filterRadius, float randomSeed) {

    VogelDiskData diskData;
    prepareDiskConstants(diskData, shadowSamples, randomSeed);

    float sum = 0.0;
    for (int i = 0; i < shadowSamples; i++) {
        vec2 offsetUV = generateDiskSample(diskData) * filterRadius;
        float depth = texture2DLod(shadowMap, uv + offsetUV, 0.0).r;
        sum += step(receiverDepth, depth);
    }
    return sum / float(shadowSamples);
}

float getPenumbra(float dblocker, float dreceiver, float penumbraSize, float penumbraFalloff) {
    float dist = dreceiver - dblocker;
    float penumbra = 1.0 - pow(1.0 - dist, penumbraFalloff);
    return penumbra * penumbraSize;
}

float PCSSDirectional(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoords, vec4 cameraParams, vec4 softShadowParams) {

    float receiverDepth = shadowCoords.z;
    float randomSeed = fractSinRand(gl_FragCoord.xy);
    int shadowSamples = int(softShadowParams.x);
    int shadowBlockerSamples = int(softShadowParams.y);
    float penumbraSize = softShadowParams.z;
    float penumbraFalloff = softShadowParams.w;

    // normalized inverse shadow map size to preserve the shadow softness regardless of the shadow resolution
    int shadowMapSize = textureSize(shadowMap, 0).x;
    float invShadowMapSize = 1.0 / float(shadowMapSize);
    invShadowMapSize *= float(shadowMapSize) / 2048.0;

    float penumbra;

    // contact hardening path
    if (shadowBlockerSamples > 0) {

        // find average blocker depth
        float avgBlockerDepth = 0.0;
        int numBlockers = 0;
        PCSSFindBlocker(TEXTURE_PASS(shadowMap), avgBlockerDepth, numBlockers, shadowCoords.xy, receiverDepth, shadowBlockerSamples, penumbraSize, invShadowMapSize, randomSeed);

        // early out when no blockers are present
        if (numBlockers < 1)
            return 1.0f;

        // penumbra size is based on the blocker depth
        penumbra = getPenumbra(avgBlockerDepth, shadowCoords.z, penumbraSize, penumbraFalloff);

    } else {

        // constant filter size, no contact hardening
        penumbra = penumbraSize;
    }

    float filterRadius = penumbra * invShadowMapSize;

    // filtering
    return PCSSFilter(TEXTURE_PASS(shadowMap), shadowCoords.xy, receiverDepth, shadowSamples, filterRadius, randomSeed);
}

float getShadowPCSS(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, vec4 cameraParams, vec4 softShadowParams, vec3 lightDir) {
    return PCSSDirectional(TEXTURE_PASS(shadowMap), shadowCoord, cameraParams, softShadowParams);
}

`;

var skinBatchVS$1 = /* glsl */ `
attribute float vertex_boneIndices;

uniform highp sampler2D texture_poseMap;

mat4 getBoneMatrix(const in float indexFloat) {

    int width = textureSize(texture_poseMap, 0).x;
    int index = int(indexFloat + 0.5) * 3;
    int iy = index / width;
    int ix = index % width;

    // read elements of 4x3 matrix
    vec4 v1 = texelFetch(texture_poseMap, ivec2(ix + 0, iy), 0);
    vec4 v2 = texelFetch(texture_poseMap, ivec2(ix + 1, iy), 0);
    vec4 v3 = texelFetch(texture_poseMap, ivec2(ix + 2, iy), 0);

    // transpose to 4x4 matrix
    return mat4(
        v1.x, v2.x, v3.x, 0,
        v1.y, v2.y, v3.y, 0,
        v1.z, v2.z, v3.z, 0,
        v1.w, v2.w, v3.w, 1
    );
}
`;

var skinVS$1 = /* glsl */ `

attribute vec4 vertex_boneWeights;
attribute vec4 vertex_boneIndices;

uniform highp sampler2D texture_poseMap;

void getBoneMatrix(const in int width, const in int index, out vec4 v1, out vec4 v2, out vec4 v3) {

    int v = index / width;
    int u = index % width;

    v1 = texelFetch(texture_poseMap, ivec2(u + 0, v), 0);
    v2 = texelFetch(texture_poseMap, ivec2(u + 1, v), 0);
    v3 = texelFetch(texture_poseMap, ivec2(u + 2, v), 0);
}

mat4 getSkinMatrix(const in vec4 indicesFloat, const in vec4 weights) {

    int width = textureSize(texture_poseMap, 0).x;
    ivec4 indices = ivec4(indicesFloat + 0.5) * 3;

    // get 4 bone matrices
    vec4 a1, a2, a3;
    getBoneMatrix(width, indices.x, a1, a2, a3);

    vec4 b1, b2, b3;
    getBoneMatrix(width, indices.y, b1, b2, b3);

    vec4 c1, c2, c3;
    getBoneMatrix(width, indices.z, c1, c2, c3);

    vec4 d1, d2, d3;
    getBoneMatrix(width, indices.w, d1, d2, d3);

    // multiply them by weights and add up to get final 4x3 matrix
    vec4 v1 = a1 * weights.x + b1 * weights.y + c1 * weights.z + d1 * weights.w;
    vec4 v2 = a2 * weights.x + b2 * weights.y + c2 * weights.z + d2 * weights.w;
    vec4 v3 = a3 * weights.x + b3 * weights.y + c3 * weights.z + d3 * weights.w;

    // add up weights
    float one = dot(weights, vec4(1.0));

    // transpose to 4x4 matrix
    return mat4(
        v1.x, v2.x, v3.x, 0,
        v1.y, v2.y, v3.y, 0,
        v1.z, v2.z, v3.z, 0,
        v1.w, v2.w, v3.w, one
    );
}
`;

var skyboxPS$1 = /* glsl */ `
    #define LIT_SKYBOX_INTENSITY

    #include "envProcPS"
    #include "gammaPS"
    #include "tonemappingPS"

    #ifdef PREPASS_PASS
        varying float vLinearDepth;
        #include "floatAsUintPS"
    #endif

    varying vec3 vViewDir;
    uniform float skyboxHighlightMultiplier;

    #ifdef SKY_CUBEMAP

        uniform samplerCube texture_cubeMap;

        #ifdef SKYMESH
            varying vec3 vWorldPos;
            uniform mat3 cubeMapRotationMatrix;
            uniform vec3 projectedSkydomeCenter;
        #endif

    #else // env-atlas

        #include "sphericalPS"
        #include "envAtlasPS"

        uniform sampler2D texture_envAtlas;
        uniform float mipLevel;

    #endif

    void main(void) {

        #ifdef PREPASS_PASS

            // output linear depth during prepass
            gl_FragColor = float2vec4(vLinearDepth);

        #else

            #ifdef SKY_CUBEMAP

                #ifdef SKYMESH

                    // get vector from world space pos to tripod origin
                    vec3 envDir = normalize(vWorldPos - projectedSkydomeCenter);
                    vec3 dir = envDir * cubeMapRotationMatrix;

                #else

                    vec3 dir = vViewDir;

                #endif

                dir.x *= -1.0;
                vec3 linear = {SKYBOX_DECODE_FNC}(textureCube(texture_cubeMap, dir));

            #else // env-atlas

                vec3 dir = vViewDir * vec3(-1.0, 1.0, 1.0);
                vec2 uv = toSphericalUv(normalize(dir));

                vec3 linear = {SKYBOX_DECODE_FNC}(texture2D(texture_envAtlas, mapRoughnessUv(uv, mipLevel)));

            #endif

            // our HDR encodes values up to 64, so allow extra brightness for the clipped values
            if (any(greaterThanEqual(linear, vec3(64.0)))) {
                linear *= skyboxHighlightMultiplier;
            }

            gl_FragColor = vec4(gammaCorrectOutput(toneMap(processEnvironment(linear))), 1.0);

        #endif
    }
`;

var skyboxVS$1 = /* glsl */ `
attribute vec4 aPosition;

uniform mat4 matrix_view;
uniform mat4 matrix_projectionSkybox;
uniform mat3 cubeMapRotationMatrix;

varying vec3 vViewDir;

#ifdef PREPASS_PASS
    // when skydome renders depth during prepass, generate linear depth
    varying float vLinearDepth;
#endif

#ifdef SKYMESH
    uniform mat4 matrix_model;
    varying vec3 vWorldPos;
#endif

void main(void) {

    mat4 view = matrix_view;

    #ifdef SKYMESH

        vec4 worldPos = matrix_model * aPosition;
        vWorldPos = worldPos.xyz;
        gl_Position = matrix_projectionSkybox * (view * worldPos);

        #ifdef PREPASS_PASS
            // linear depth from the worldPosition, see getLinearDepth
            vLinearDepth = -(matrix_view * vec4(vWorldPos, 1.0)).z;
        #endif

    #else

        view[3][0] = view[3][1] = view[3][2] = 0.0;
        gl_Position = matrix_projectionSkybox * (view * aPosition);
        vViewDir = aPosition.xyz * cubeMapRotationMatrix;

        #ifdef PREPASS_PASS
            // for infinite skybox, use negative gl_Position.w to get positive linear depth
            vLinearDepth = -gl_Position.w;
        #endif
    #endif

    // Force skybox to far Z, regardless of the clip planes on the camera
    // Subtract a tiny fudge factor to ensure floating point errors don't
    // still push pixels beyond far Z. See:
    // https://community.khronos.org/t/skybox-problem/61857

    gl_Position.z = gl_Position.w - 1.0e-7;
}
`;

var specularPS$1 = /* glsl */ `

#ifdef STD_SPECULAR_CONSTANT
uniform vec3 material_specular;
#endif

void getSpecularity() {
    vec3 specularColor = vec3(1,1,1);

    #ifdef STD_SPECULAR_CONSTANT
    specularColor *= material_specular;
    #endif

    #ifdef STD_SPECULAR_TEXTURE
    specularColor *= {STD_SPECULAR_TEXTURE_DECODE}(texture2DBias({STD_SPECULAR_TEXTURE_NAME}, {STD_SPECULAR_TEXTURE_UV}, textureBias)).{STD_SPECULAR_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SPECULAR_VERTEX
    specularColor *= saturate(vVertexColor.{STD_SPECULAR_VERTEX_CHANNEL});
    #endif

    dSpecularity = specularColor;
}
`;

var sphericalPS$1 = /* glsl */ `
// equirectangular helper functions
vec2 toSpherical(vec3 dir) {
    return vec2(dir.xz == vec2(0.0) ? 0.0 : atan(dir.x, dir.z), asin(dir.y));
}

vec2 toSphericalUv(vec3 dir) {
    const float PI = 3.141592653589793;
    vec2 uv = toSpherical(dir) / vec2(PI * 2.0, PI) + 0.5;
    return vec2(uv.x, 1.0 - uv.y);
}
`;

var specularityFactorPS$1 = /* glsl */ `

#ifdef STD_SPECULARITYFACTOR_CONSTANT
uniform float material_specularityFactor;
#endif

void getSpecularityFactor() {
    float specularityFactor = 1.0;

    #ifdef STD_SPECULARITYFACTOR_CONSTANT
    specularityFactor *= material_specularityFactor;
    #endif

    #ifdef STD_SPECULARITYFACTOR_TEXTURE
    specularityFactor *= texture2DBias({STD_SPECULARITYFACTOR_TEXTURE_NAME}, {STD_SPECULARITYFACTOR_TEXTURE_UV}, textureBias).{STD_SPECULARITYFACTOR_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SPECULARITYFACTOR_VERTEX
    specularityFactor *= saturate(vVertexColor.{STD_SPECULARITYFACTOR_VERTEX_CHANNEL});
    #endif

    dSpecularityFactor = specularityFactor;
}
`;

var spotPS$1 = /* glsl */ `
float getSpotEffect(vec3 lightSpotDir, float lightInnerConeAngle, float lightOuterConeAngle, vec3 lightDirNorm) {
    float cosAngle = dot(lightDirNorm, lightSpotDir);
    return smoothstep(lightOuterConeAngle, lightInnerConeAngle, cosAngle);
}
`;

var startNineSlicedPS$1 = /* glsl */ `
    nineSlicedUv = vec2(vUv0.x, 1.0 - vUv0.y);
`;

var startNineSlicedTiledPS$1 = /* glsl */ `
    vec2 tileMask = step(vMask, vec2(0.99999));
    vec2 tileSize = 0.5 * (innerOffset.xy + innerOffset.zw);
    vec2 tileScale = vec2(1.0) / (vec2(1.0) - tileSize);
    vec2 clampedUv = mix(innerOffset.xy * 0.5, vec2(1.0) - innerOffset.zw * 0.5, fract((vTiledUv - tileSize) * tileScale));
    clampedUv = clampedUv * atlasRect.zw + atlasRect.xy;
    nineSlicedUv = vUv0 * tileMask + clampedUv * (vec2(1.0) - tileMask);
    nineSlicedUv.y = 1.0 - nineSlicedUv.y;
    
`;

// Declaration part of the standard shader. Declares the uniforms, textures and global variables used
// by the fragment shader of the standard shader.
var stdDeclarationPS$1 = /* glsl */ `

    // globals
    float dAlpha = 1.0;

    // all passes handle opacity
    #if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE
        #ifdef STD_OPACITY_TEXTURE_ALLOCATE
            uniform sampler2D texture_opacityMap;
        #endif
    #endif

    #ifdef FORWARD_PASS // ----------------

        // globals
        vec3 dAlbedo;
        vec3 dNormalW;
        vec3 dSpecularity = vec3(0.0);
        float dGlossiness = 0.0;

        #ifdef LIT_REFRACTION
            float dTransmission;
            float dThickness;
        #endif

        #ifdef LIT_SCENE_COLOR
            uniform sampler2D uSceneColorMap;
        #endif

        #ifdef LIT_SCREEN_SIZE
            uniform vec4 uScreenSize;
        #endif

        #ifdef LIT_TRANSFORMS
            uniform mat4 matrix_viewProjection;
            uniform mat4 matrix_model;
        #endif

        // parallax
        #ifdef STD_HEIGHT_MAP
            vec2 dUvOffset;
            #ifdef STD_HEIGHT_TEXTURE_ALLOCATE
                uniform sampler2D texture_heightMap;
            #endif
        #endif

        // diffuse
        #ifdef STD_DIFFUSE_TEXTURE_ALLOCATE
            uniform sampler2D texture_diffuseMap;
        #endif

        #ifdef STD_DIFFUSEDETAIL_TEXTURE_ALLOCATE
            uniform sampler2D texture_diffuseDetailMap;
        #endif

        // normal
        #ifdef STD_NORMAL_TEXTURE_ALLOCATE
            uniform sampler2D texture_normalMap;
        #endif

        #ifdef STD_NORMALDETAIL_TEXTURE_ALLOCATE
            uniform sampler2D texture_normalDetailMap;
        #endif

        // refraction
        #ifdef STD_THICKNESS_TEXTURE_ALLOCATE
            uniform sampler2D texture_thicknessMap;
        #endif
        #ifdef STD_REFRACTION_TEXTURE_ALLOCATE
            uniform sampler2D texture_refractionMap;
        #endif

        // iridescence
        #ifdef LIT_IRIDESCENCE
            float dIridescence;
            float dIridescenceThickness;

            #ifdef STD_IRIDESCENCE_THICKNESS_TEXTURE_ALLOCATE
                uniform sampler2D texture_iridescenceThicknessMap;
            #endif
            #ifdef STD_IRIDESCENCE_TEXTURE_ALLOCATE
                uniform sampler2D texture_iridescenceMap;
            #endif
        #endif

        #ifdef LIT_CLEARCOAT
            float ccSpecularity;
            float ccGlossiness;
            vec3 ccNormalW;
        #endif

        #ifdef LIT_GGX_SPECULAR
            float dAnisotropy;
            vec2 dAnisotropyRotation;
        #endif

        // specularity & glossiness
        #ifdef LIT_SPECULAR_OR_REFLECTION

            // sheen
            #ifdef LIT_SHEEN
                vec3 sSpecularity;
                float sGlossiness;

                #ifdef STD_SHEEN_TEXTURE_ALLOCATE
                    uniform sampler2D texture_sheenMap;
                #endif
                #ifdef STD_SHEENGLOSS_TEXTURE_ALLOCATE
                    uniform sampler2D texture_sheenGlossMap;
                #endif
            #endif

            // metalness
            #ifdef LIT_METALNESS
                float dMetalness;
                float dIor;

                #ifdef STD_METALNESS_TEXTURE_ALLOCATE
                    uniform sampler2D texture_metalnessMap;
                #endif
            #endif

            // specularity factor
            #ifdef LIT_SPECULARITY_FACTOR
                float dSpecularityFactor;

                #ifdef STD_SPECULARITYFACTOR_TEXTURE_ALLOCATE
                    uniform sampler2D texture_specularityFactorMap;
                #endif
            #endif

            // specular color
            #ifdef STD_SPECULAR_COLOR
                #ifdef STD_SPECULAR_TEXTURE_ALLOCATE
                    uniform sampler2D texture_specularMap;
                #endif
            #endif

            // gloss
            #ifdef STD_GLOSS_TEXTURE_ALLOCATE
                uniform sampler2D texture_glossMap;
            #endif
        #endif

        // ao
        #ifdef STD_AO
            float dAo;
            #ifdef STD_AO_TEXTURE_ALLOCATE
                uniform sampler2D texture_aoMap;
            #endif
            #ifdef STD_AODETAIL_TEXTURE_ALLOCATE
                uniform sampler2D texture_aoDetailMap;
            #endif
        #endif

        // emission
        vec3 dEmission;
        #ifdef STD_EMISSIVE_TEXTURE_ALLOCATE
            uniform sampler2D texture_emissiveMap;
        #endif

        // clearcoat
        #ifdef LIT_CLEARCOAT
            #ifdef STD_CLEARCOAT_TEXTURE_ALLOCATE
                uniform sampler2D texture_clearCoatMap;
            #endif
            #ifdef STD_CLEARCOATGLOSS_TEXTURE_ALLOCATE
                uniform sampler2D texture_clearCoatGlossMap;
            #endif
            #ifdef STD_CLEARCOATNORMAL_TEXTURE_ALLOCATE
                uniform sampler2D texture_clearCoatNormalMap;
            #endif
        #endif
        
        // anisotropy
        #ifdef LIT_GGX_SPECULAR
            #ifdef STD_ANISOTROPY_TEXTURE_ALLOCATE
                uniform sampler2D texture_anisotropyMap;
            #endif
        #endif

        // lightmap
        #if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)
            vec3 dLightmap;
            #ifdef STD_LIGHT_TEXTURE_ALLOCATE
                uniform sampler2D texture_lightMap;
            #endif
        #endif
    #endif

    // front end outputs to lit shader
    #include "litShaderCorePS"
`;

// includes and functionality of the front end shader, generates the input to the lit shader.
var stdFrontEndPS$1 = /* glsl */ `

    // all passes handle opacity
    #if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE
        #include "opacityPS"

        #if defined(LIT_ALPHA_TEST)
            #include "alphaTestPS"
        #endif

        // dithering
        #if STD_OPACITY_DITHER != NONE
            #include "opacityDitherPS"
        #endif
    #endif

    #ifdef FORWARD_PASS // ----------------

        // parallax
        #ifdef STD_HEIGHT_MAP
            #include "parallaxPS"
        #endif

        // diffuse
        #include  "diffusePS"

        // normal
        #ifdef LIT_NEEDS_NORMAL
            #include "normalMapPS"
        #endif

        // refraction
        #ifdef LIT_REFRACTION
            #include "transmissionPS"
            #include "thicknessPS"
        #endif

        // iridescence
        #ifdef LIT_IRIDESCENCE
            #include "iridescencePS"
            #include "iridescenceThicknessPS"
        #endif

        // specularity & glossiness
        #ifdef LIT_SPECULAR_OR_REFLECTION

            // sheen
            #ifdef LIT_SHEEN
                #include "sheenPS"
                #include "sheenGlossPS"
            #endif

            // metalness
            #ifdef LIT_METALNESS
                #include "metalnessPS"
                #include "iorPS"
            #endif

            // specularity factor
            #ifdef LIT_SPECULARITY_FACTOR
                #include "specularityFactorPS"
            #endif

            // specular color
            #ifdef STD_SPECULAR_COLOR
                #include "specularPS"
            #else
                void getSpecularity() { 
                    dSpecularity = vec3(1);
                }
            #endif

            // gloss
            #include "glossPS"
        #endif

        // ao
        #ifdef STD_AO
            #include "aoPS"
        #endif

        // emission
        #include "emissivePS"

        // clearcoat
        #ifdef LIT_CLEARCOAT
            #include "clearCoatPS"
            #include "clearCoatGlossPS"
            #include "clearCoatNormalPS"
        #endif

        // anisotropy
        #if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)
            #include "anisotropyPS"
        #endif

        // lightmap
        #if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)
            #include "lightmapPS"
        #endif
    #endif

    void evaluateFrontend() {

        // all passes handle opacity
        #if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE
            getOpacity();

            #if defined(LIT_ALPHA_TEST)
                alphaTest(dAlpha);
            #endif

            #if STD_OPACITY_DITHER != NONE
                opacityDither(dAlpha, 0.0);
            #endif

            litArgs_opacity = dAlpha;
        #endif

        #ifdef FORWARD_PASS // ----------------

            // parallax
            #ifdef STD_HEIGHT_MAP
                getParallax();
            #endif

            // diffuse
            getAlbedo();
            litArgs_albedo = dAlbedo;

            // normal
            #ifdef LIT_NEEDS_NORMAL
                getNormal();
                litArgs_worldNormal = dNormalW;
            #endif

            // refraction
            #ifdef LIT_REFRACTION
                getRefraction();
                litArgs_transmission = dTransmission;

                getThickness();
                litArgs_thickness = dThickness;

                #ifdef LIT_DISPERSION
                    litArgs_dispersion = material_dispersion;
                #endif
            #endif

            // iridescence
            #ifdef LIT_IRIDESCENCE
                getIridescence();
                getIridescenceThickness();
                litArgs_iridescence_intensity = dIridescence;
                litArgs_iridescence_thickness = dIridescenceThickness;
            #endif

            // specularity & glossiness
            #ifdef LIT_SPECULAR_OR_REFLECTION

                // sheen
                #ifdef LIT_SHEEN
                    getSheen();
                    litArgs_sheen_specularity = sSpecularity;
                    getSheenGlossiness();
                    litArgs_sheen_gloss = sGlossiness;
                #endif

                // metalness
                #ifdef LIT_METALNESS
                    getMetalness();
                    litArgs_metalness = dMetalness;
                    getIor();
                    litArgs_ior = dIor;
                #endif

                // specularity factor
                #ifdef LIT_SPECULARITY_FACTOR
                    getSpecularityFactor();
                    litArgs_specularityFactor = dSpecularityFactor;
                #endif

                // gloss
                getGlossiness();
                getSpecularity();
                litArgs_specularity = dSpecularity;
                litArgs_gloss = dGlossiness;
            #endif

            // ao
            #ifdef STD_AO
                getAO();
                litArgs_ao = dAo;
            #endif

            // emission
            getEmission();
            litArgs_emission = dEmission;

            // clearcoat
            #ifdef LIT_CLEARCOAT
                getClearCoat();
                getClearCoatGlossiness();
                getClearCoatNormal();
                litArgs_clearcoat_specularity = ccSpecularity;
                litArgs_clearcoat_gloss = ccGlossiness;
                litArgs_clearcoat_worldNormal = ccNormalW;
            #endif

            // anisotropy
            #if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)
                getAnisotropy();
            #endif

            // lightmap
            #if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)
                getLightMap();
                litArgs_lightmap = dLightmap;

                #ifdef STD_LIGHTMAP_DIR
                    litArgs_lightmapDir = dLightmapDir;
                #endif
            #endif
        #endif
    }
`;

var TBNPS$1 = /* glsl */ `

#ifdef LIT_TANGENTS
    #define TBN_TANGENTS
#else
    #if defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS)
        #define TBN_DERIVATIVES
    #endif
#endif

#if defined(TBN_DERIVATIVES)
    uniform float tbnBasis;
#endif

void getTBN(vec3 tangent, vec3 binormal, vec3 normal) {

    #ifdef TBN_TANGENTS // tangents / binormals based TBN

        dTBN = mat3(normalize(tangent), normalize(binormal), normalize(normal));

    #elif defined(TBN_DERIVATIVES) // derivatives based TBN

        vec2 uv = {lightingUv};

        // get edge vectors of the pixel triangle
        vec3 dp1 = dFdx( vPositionW );
        vec3 dp2 = dFdy( vPositionW );
        vec2 duv1 = dFdx( uv );
        vec2 duv2 = dFdy( uv );

        // solve the linear system
        vec3 dp2perp = cross( dp2, normal );
        vec3 dp1perp = cross( normal, dp1 );
        vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
        vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;

        // construct a scale-invariant frame
        float denom = max( dot(T,T), dot(B,B) );
        float invmax = (denom == 0.0) ? 0.0 : tbnBasis / sqrt( denom );
        dTBN = mat3(T * invmax, -B * invmax, normal );

    #else // object space TBN

        vec3 B = cross(normal, vObjectSpaceUpW);
        vec3 T = cross(normal, B);

        if (dot(B,B)==0.0) // deal with case when vObjectSpaceUpW normal are parallel
        {
            float major=max(max(normal.x, normal.y), normal.z);

            if (normal.x == major)
            {
                B = cross(normal, vec3(0,1,0));
                T = cross(normal, B);
            }
            else if (normal.y == major)
            {
                B = cross(normal, vec3(0,0,1));
                T = cross(normal, B);
            }
            else if (normal.z == major)
            {
                B = cross(normal, vec3(1,0,0));
                T = cross(normal, B);
            }
        }

        dTBN = mat3(normalize(T), normalize(B), normalize(normal));

    #endif
}
`;

var thicknessPS$1 = /* glsl */ `
#ifdef STD_THICKNESS_CONSTANT
uniform float material_thickness;
#endif

void getThickness() {
    dThickness = 1.0;

    #ifdef STD_THICKNESS_CONSTANT
    dThickness *= material_thickness;
    #endif

    #ifdef STD_THICKNESS_TEXTURE
    dThickness *= texture2DBias({STD_THICKNESS_TEXTURE_NAME}, {STD_THICKNESS_TEXTURE_UV}, textureBias).{STD_THICKNESS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_THICKNESS_VERTEX
    dThickness *= saturate(vVertexColor.{STD_THICKNESS_VERTEX_CHANNEL});
    #endif
}
`;

var tonemappingPS$1 = /* glsl */ `

#if (TONEMAP == NONE)
    #include "tonemappingNonePS"
#elif TONEMAP == FILMIC
    #include "tonemappingFilmicPS"
#elif TONEMAP == LINEAR
    #include "tonemappingLinearPS"
#elif TONEMAP == HEJL
    #include "tonemappingHejlPS"
#elif TONEMAP == ACES
    #include "tonemappingAcesPS"
#elif TONEMAP == ACES2
    #include "tonemappingAces2PS"
#elif TONEMAP == NEUTRAL
    #include "tonemappingNeutralPS"
#endif

`;

var tonemappingAcesPS$1 = /* glsl */ `
uniform float exposure;

vec3 toneMap(vec3 color) {
    float tA = 2.51;
    float tB = 0.03;
    float tC = 2.43;
    float tD = 0.59;
    float tE = 0.14;
    vec3 x = color * exposure;
    return (x*(tA*x+tB))/(x*(tC*x+tD)+tE);
}
`;

var tonemappingAces2PS$1 = /* glsl */ `
uniform float exposure;

// ACES approximation by Stephen Hill

// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
const mat3 ACESInputMat = mat3(
    0.59719, 0.35458, 0.04823,
    0.07600, 0.90834, 0.01566,
    0.02840, 0.13383, 0.83777
);

// ODT_SAT => XYZ => D60_2_D65 => sRGB
const mat3 ACESOutputMat = mat3(
     1.60475, -0.53108, -0.07367,
    -0.10208,  1.10813, -0.00605,
    -0.00327, -0.07276,  1.07602
);

vec3 RRTAndODTFit(vec3 v) {
    vec3 a = v * (v + 0.0245786) - 0.000090537;
    vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;
    return a / b;
}

vec3 toneMap(vec3 color) {
    color *= exposure / 0.6;
    color = color * ACESInputMat;

    // Apply RRT and ODT
    color = RRTAndODTFit(color);
    color = color * ACESOutputMat;

    // Clamp to [0, 1]
    color = clamp(color, 0.0, 1.0);

    return color;
}
`;

var tonemappingFilmicPS$1 = /* glsl */ `
const float A =  0.15;
const float B =  0.50;
const float C =  0.10;
const float D =  0.20;
const float E =  0.02;
const float F =  0.30;
const float W =  11.2;

uniform float exposure;

vec3 uncharted2Tonemap(vec3 x) {
   return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
}

vec3 toneMap(vec3 color) {
    color = uncharted2Tonemap(color * exposure);
    vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W,W,W));
    color = color * whiteScale;

    return color;
}
`;

var tonemappingHejlPS$1 = /* glsl */ `
uniform float exposure;

vec3 toneMap(vec3 color) {
    color *= exposure;
    const float  A = 0.22, B = 0.3, C = .1, D = 0.2, E = .01, F = 0.3;
    const float Scl = 1.25;

    vec3 h = max( vec3(0.0), color - vec3(0.004) );
    return (h*((Scl*A)*h+Scl*vec3(C*B,C*B,C*B))+Scl*vec3(D*E,D*E,D*E)) / (h*(A*h+vec3(B,B,B))+vec3(D*F,D*F,D*F)) - Scl*vec3(E/F,E/F,E/F);
}
`;

var tonemappingLinearPS$1 = /* glsl */ `
uniform float exposure;

vec3 toneMap(vec3 color) {
    return color * exposure;
}
`;

// https://modelviewer.dev/examples/tone-mapping
var tonemappingNeutralPS$1 = /* glsl */ `
uniform float exposure;

vec3 toneMap(vec3 color) {

    color *= exposure;

    float startCompression = 0.8 - 0.04;
    float desaturation = 0.15;

    float x = min(color.r, min(color.g, color.b));
    float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
    color -= offset;

    float peak = max(color.r, max(color.g, color.b));
    if (peak < startCompression) return color;

    float d = 1. - startCompression;
    float newPeak = 1. - d * d / (peak + d - startCompression);
    color *= newPeak / peak;

    float g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);
    return mix(color, newPeak * vec3(1, 1, 1), g);
}
`;

var tonemappingNonePS$1 = /* glsl */ `
vec3 toneMap(vec3 color) {
    return color;
}
`;

var transformVS$1 = /* glsl */ `
#ifdef PIXELSNAP
uniform vec4 uScreenSize;
#endif

#ifdef SCREENSPACE
uniform float projectionFlipY;
#endif

vec4 evalWorldPosition(vec3 vertexPosition, mat4 modelMatrix) {

    vec3 localPos = getLocalPosition(vertexPosition);

    #ifdef NINESLICED
        // outer and inner vertices are at the same position, scale both
        localPos.xz *= outerScale;

        // offset inner vertices inside
        // (original vertices must be in [-1;1] range)
        vec2 positiveUnitOffset = clamp(vertexPosition.xz, vec2(0.0), vec2(1.0));
        vec2 negativeUnitOffset = clamp(-vertexPosition.xz, vec2(0.0), vec2(1.0));
        localPos.xz += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;

        vTiledUv = (localPos.xz - outerScale + innerOffset.xy) * -0.5 + 1.0; // uv = local pos - inner corner

        localPos.xz *= -0.5; // move from -1;1 to -0.5;0.5
        localPos = localPos.xzy;
    #endif

    vec4 posW = modelMatrix * vec4(localPos, 1.0);

    #ifdef SCREENSPACE
        posW.zw = vec2(0.0, 1.0);
    #endif

    return posW;
}

vec4 getPosition() {

    dModelMatrix = getModelMatrix();

    vec4 posW = evalWorldPosition(vertex_position.xyz, dModelMatrix);
    dPositionW = posW.xyz;

    vec4 screenPos;
    #ifdef UV1LAYOUT
        screenPos = vec4(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1);
        #ifdef WEBGPU
            screenPos.y *= -1.0;
        #endif
    #else
        #ifdef SCREENSPACE
            screenPos = posW;
            screenPos.y *= projectionFlipY;
        #else
            screenPos = matrix_viewProjection * posW;
        #endif

        #ifdef PIXELSNAP
            // snap vertex to a pixel boundary
            screenPos.xy = (screenPos.xy * 0.5) + 0.5;
            screenPos.xy *= uScreenSize.xy;
            screenPos.xy = floor(screenPos.xy);
            screenPos.xy *= uScreenSize.zw;
            screenPos.xy = (screenPos.xy * 2.0) - 1.0;
        #endif
    #endif

    return screenPos;
}

vec3 getWorldPosition() {
    return dPositionW;
}
`;

var transformCoreVS$1 = /* glsl */ `

attribute vec4 vertex_position;

uniform mat4 matrix_viewProjection;
uniform mat4 matrix_model;

#ifdef MORPHING
    uniform vec2 morph_tex_params;
    attribute uint morph_vertex_id;

    ivec2 getTextureMorphCoords() {

        // turn morph_vertex_id into int grid coordinates
        ivec2 textureSize = ivec2(morph_tex_params);
        int morphGridV = int(morph_vertex_id) / textureSize.x;
        int morphGridU = int(morph_vertex_id) - (morphGridV * textureSize.x);
        #ifdef WEBGPU
            // flipY
            morphGridV = textureSize.y - morphGridV - 1;
        #endif
        return ivec2(morphGridU, morphGridV);
    }

    #ifdef MORPHING_POSITION
        #ifdef MORPHING_INT
            uniform vec3 aabbSize;
            uniform vec3 aabbMin;
            uniform usampler2D morphPositionTex;
        #else
            uniform highp sampler2D morphPositionTex;
        #endif
    #endif
#endif

#ifdef defined(BATCH)
    #include "skinBatchVS"

    mat4 getModelMatrix() {
        return getBoneMatrix(vertex_boneIndices);
    }

#elif defined(SKIN)
    #include "skinVS"

    mat4 getModelMatrix() {
        return matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);
    }

#elif defined(INSTANCING)

    #include "transformInstancingVS"

#else

    mat4 getModelMatrix() {
        return matrix_model;
    }

#endif

vec3 getLocalPosition(vec3 vertexPosition) {

    vec3 localPos = vertexPosition;

    #ifdef MORPHING_POSITION

        ivec2 morphUV = getTextureMorphCoords();

        #ifdef MORPHING_INT
            vec3 morphPos = vec3(texelFetch(morphPositionTex, ivec2(morphUV), 0).xyz) / 65535.0 * aabbSize + aabbMin;
        #else
            vec3 morphPos = texelFetch(morphPositionTex, ivec2(morphUV), 0).xyz;
        #endif

        localPos += morphPos;

    #endif

    return localPos;
}

`;

var transformInstancingVS$1 = /* glsl */ `

attribute vec4 instance_line1;
attribute vec4 instance_line2;
attribute vec4 instance_line3;
attribute vec4 instance_line4;

mat4 getModelMatrix() {
    return matrix_model * mat4(instance_line1, instance_line2, instance_line3, instance_line4);
}
`;

var transmissionPS$1 = /* glsl */ `

#ifdef STD_REFRACTION_CONSTANT
uniform float material_refraction;
#endif

void getRefraction() {
    float refraction = 1.0;

    #ifdef STD_REFRACTION_CONSTANT
    refraction = material_refraction;
    #endif

    #ifdef STD_REFRACTION_TEXTURE
    refraction *= texture2DBias({STD_REFRACTION_TEXTURE_NAME}, {STD_REFRACTION_TEXTURE_UV}, textureBias).{STD_REFRACTION_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_REFRACTION_VERTEX
    refraction *= saturate(vVertexColor.{STD_REFRACTION_VERTEX_CHANNEL});
    #endif

    dTransmission = refraction;
}
`;

var twoSidedLightingPS$1 = /* glsl */ `
uniform float twoSidedLightingNegScaleFactor;
void handleTwoSidedLighting() {
    dTBN[2] *= gl_FrontFacing ? twoSidedLightingNegScaleFactor : -twoSidedLightingNegScaleFactor;
}
`;

var uv0VS$1 = /* glsl */ `
#ifdef NINESLICED
    vec2 getUv0() {
        vec2 uv = vertex_position.xz;

        // offset inner vertices inside
        // (original vertices must be in [-1;1] range)
        vec2 positiveUnitOffset = clamp(vertex_position.xz, vec2(0.0), vec2(1.0));
        vec2 negativeUnitOffset = clamp(-vertex_position.xz, vec2(0.0), vec2(1.0));
        uv += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;

        uv = uv * -0.5 + 0.5;
        uv = uv * atlasRect.zw + atlasRect.xy;

        vMask = vertex_texCoord0.xy;

        return uv;
    }
#else
    vec2 getUv0() {
        return vertex_texCoord0;
    }
#endif
`;

var uv1VS$1 = /* glsl */ `
vec2 getUv1() {
    return vertex_texCoord1;
}
`;

// chunk that generates uv coordinate transformed by uv transform matrix
var uvTransformVS$1 = /* glsl */ `
vUV{TRANSFORM_UV_{i}}_{TRANSFORM_ID_{i}} = vec2(
    dot(vec3(uv{TRANSFORM_UV_{i}}, 1), {TRANSFORM_NAME_{i}}0),
    dot(vec3(uv{TRANSFORM_UV_{i}}, 1), {TRANSFORM_NAME_{i}}1)
);
`;

// uniform declaration for uv transform matrix, 3x2 matrix
var uvTransformUniformsPS$1 = /* glsl */ `
    uniform vec3 {TRANSFORM_NAME_{i}}0;
    uniform vec3 {TRANSFORM_NAME_{i}}1;
`;

var viewDirPS$1 = /* glsl */ `
void getViewDir() {
    dViewDirW = normalize(view_position - vPositionW);
}
`;

/**
 * Object containing all default shader chunks used by shader generators.
 *
 * @type {Record<string, string>}
 * @category Graphics
 * @ignore
 */ const shaderChunksGLSL = {
    alphaTestPS: alphaTestPS$1,
    ambientPS: ambientPS$1,
    anisotropyPS: anisotropyPS$1,
    aoPS: aoPS$1,
    aoDiffuseOccPS: aoDiffuseOccPS$1,
    aoSpecOccPS: aoSpecOccPS$1,
    bakeDirLmEndPS: bakeDirLmEndPS$1,
    bakeLmEndPS: bakeLmEndPS$1,
    basePS: basePS$1,
    baseNineSlicedPS: baseNineSlicedPS$1,
    baseNineSlicedTiledPS: baseNineSlicedTiledPS$1,
    bayerPS: bayerPS$1,
    blurVSMPS: blurVSMPS$1,
    clearCoatPS: clearCoatPS$1,
    clearCoatGlossPS: clearCoatGlossPS$1,
    clearCoatNormalPS: clearCoatNormalPS$1,
    clusteredLightCookiesPS: clusteredLightCookiesPS$1,
    clusteredLightShadowsPS: clusteredLightShadowsPS$1,
    clusteredLightUtilsPS: clusteredLightUtilsPS$1,
    clusteredLightPS: clusteredLightPS$1,
    combinePS: combinePS$1,
    cookieBlit2DPS: cookieBlit2DPS$1,
    cookieBlitCubePS: cookieBlitCubePS$1,
    cookieBlitVS: cookieBlitVS$1,
    cookiePS,
    cubeMapProjectPS: cubeMapProjectPS$1,
    cubeMapRotatePS: cubeMapRotatePS$1,
    debugOutputPS: debugOutputPS$1,
    debugProcessFrontendPS: debugProcessFrontendPS$1,
    detailModesPS: detailModesPS$1,
    diffusePS: diffusePS$1,
    decodePS: decodePS$1,
    emissivePS: emissivePS$1,
    encodePS: encodePS$1,
    endPS: endPS$1,
    envAtlasPS: envAtlasPS$1,
    envProcPS: envProcPS$1,
    falloffInvSquaredPS: falloffInvSquaredPS$1,
    falloffLinearPS: falloffLinearPS$1,
    floatAsUintPS: floatAsUintPS$1,
    fogPS: fogPS$1,
    fresnelSchlickPS: fresnelSchlickPS$1,
    frontendCodePS: '',
    frontendDeclPS: '',
    fullscreenQuadVS: fullscreenQuadVS$1,
    gammaPS: gammaPS$1,
    gles3PS,
    gles3VS,
    glossPS: glossPS$1,
    gsplatCenterVS: gsplatCenterVS$1,
    gsplatCornerVS: gsplatCornerVS$1,
    gsplatColorVS: gsplatColorVS$1,
    gsplatCommonVS: gsplatCommonVS$1,
    gsplatCompressedDataVS: gsplatCompressedDataVS$1,
    gsplatCompressedSHVS: gsplatCompressedSHVS$1,
    gsplatEvalSHVS: gsplatEvalSHVS$1,
    gsplatQuatToMat3VS: gsplatQuatToMat3VS$1,
    gsplatSogsColorVS: gsplatSogsColorVS$1,
    gsplatSogsDataVS: gsplatSogsDataVS$1,
    gsplatSogsSHVS: gsplatSogsSHVS$1,
    gsplatSourceFormatVS: gsplatSourceFormatVS$1,
    gsplatStructsVS: gsplatStructsVS$1,
    gsplatDataVS: gsplatDataVS$1,
    gsplatOutputVS: gsplatOutputVS$1,
    gsplatPS: gsplatPS$1,
    gsplatSHVS: gsplatSHVS$1,
    gsplatSourceVS: gsplatSourceVS$1,
    gsplatVS: gsplatVS$1,
    gsplatWorkBufferVS: gsplatWorkBufferVS$1,
    gsplatPackingPS: gsplatPackingPS$1,
    quadVS: quadVS$1,
    immediateLinePS: immediateLinePS$1,
    immediateLineVS: immediateLineVS$1,
    iridescenceDiffractionPS: iridescenceDiffractionPS$1,
    iridescencePS: iridescencePS$1,
    iridescenceThicknessPS: iridescenceThicknessPS$1,
    iorPS: iorPS$1,
    lightDeclarationPS: lightDeclarationPS$1,
    lightDiffuseLambertPS: lightDiffuseLambertPS$1,
    lightDirPointPS: lightDirPointPS$1,
    lightEvaluationPS: lightEvaluationPS$1,
    lightFunctionLightPS: lightFunctionLightPS$1,
    lightFunctionShadowPS: lightFunctionShadowPS$1,
    lightingPS: lightingPS$1,
    lightmapAddPS: lightmapAddPS$1,
    lightmapPS: lightmapPS$1,
    lightSpecularAnisoGGXPS: lightSpecularAnisoGGXPS$1,
    lightSpecularBlinnPS: lightSpecularBlinnPS$1,
    lightSheenPS: lightSheenPS$1,
    linearizeDepthPS: linearizeDepthPS$1,
    litForwardBackendPS: litForwardBackendPS$1,
    litForwardDeclarationPS: litForwardDeclarationPS$1,
    litForwardMainPS: litForwardMainPS$1,
    litForwardPostCodePS: litForwardPostCodePS$1,
    litForwardPreCodePS: litForwardPreCodePS$1,
    litMainPS: litMainPS$1,
    litMainVS: litMainVS$1,
    litOtherMainPS: litOtherMainPS$1,
    litShaderArgsPS: litShaderArgsPS$1,
    litShaderCorePS: litShaderCorePS$1,
    litShadowMainPS: litShadowMainPS$1,
    litUserDeclarationPS: '',
    litUserDeclarationVS: '',
    litUserCodePS: '',
    litUserCodeVS: '',
    litUserMainStartPS: '',
    litUserMainStartVS: '',
    litUserMainEndPS: '',
    litUserMainEndVS: '',
    ltcPS: ltcPS$1,
    metalnessPS: metalnessPS$1,
    metalnessModulatePS: metalnessModulatePS$1,
    morphPS: morphPS$1,
    morphVS: morphVS$1,
    msdfPS: msdfPS$1,
    msdfVS: msdfVS$1,
    normalVS: normalVS$1,
    normalCoreVS: normalCoreVS$1,
    normalMapPS: normalMapPS$1,
    opacityPS: opacityPS$1,
    opacityDitherPS: opacityDitherPS$1,
    outputPS: outputPS$1,
    outputAlphaPS: outputAlphaPS$1,
    outputTex2DPS: outputTex2DPS$1,
    sheenPS: sheenPS$1,
    sheenGlossPS: sheenGlossPS$1,
    parallaxPS: parallaxPS$1,
    pickPS: pickPS$1,
    reflDirPS: reflDirPS$1,
    reflDirAnisoPS: reflDirAnisoPS$1,
    reflectionCCPS: reflectionCCPS$1,
    reflectionCubePS: reflectionCubePS$1,
    reflectionEnvHQPS: reflectionEnvHQPS$1,
    reflectionEnvPS: reflectionEnvPS$1,
    reflectionSpherePS: reflectionSpherePS$1,
    reflectionSheenPS: reflectionSheenPS$1,
    refractionCubePS: refractionCubePS$1,
    refractionDynamicPS: refractionDynamicPS$1,
    reprojectPS: reprojectPS$1,
    reprojectVS: reprojectVS$1,
    screenDepthPS: screenDepthPS$1,
    shadowCascadesPS: shadowCascadesPS$1,
    shadowEVSMPS: shadowEVSMPS$1,
    shadowPCF1PS: shadowPCF1PS$1,
    shadowPCF3PS: shadowPCF3PS$1,
    shadowPCF5PS: shadowPCF5PS$1,
    shadowPCSSPS,
    shadowSoftPS: shadowSoftPS$1,
    skinBatchVS: skinBatchVS$1,
    skinVS: skinVS$1,
    skyboxPS: skyboxPS$1,
    skyboxVS: skyboxVS$1,
    specularPS: specularPS$1,
    sphericalPS: sphericalPS$1,
    specularityFactorPS: specularityFactorPS$1,
    spotPS: spotPS$1,
    startNineSlicedPS: startNineSlicedPS$1,
    startNineSlicedTiledPS: startNineSlicedTiledPS$1,
    stdDeclarationPS: stdDeclarationPS$1,
    stdFrontEndPS: stdFrontEndPS$1,
    TBNPS: TBNPS$1,
    thicknessPS: thicknessPS$1,
    tonemappingPS: tonemappingPS$1,
    tonemappingAcesPS: tonemappingAcesPS$1,
    tonemappingAces2PS: tonemappingAces2PS$1,
    tonemappingFilmicPS: tonemappingFilmicPS$1,
    tonemappingHejlPS: tonemappingHejlPS$1,
    tonemappingLinearPS: tonemappingLinearPS$1,
    tonemappingNeutralPS: tonemappingNeutralPS$1,
    tonemappingNonePS: tonemappingNonePS$1,
    transformVS: transformVS$1,
    transformCoreVS: transformCoreVS$1,
    transformInstancingVS: transformInstancingVS$1,
    transmissionPS: transmissionPS$1,
    twoSidedLightingPS: twoSidedLightingPS$1,
    uv0VS: uv0VS$1,
    uv1VS: uv1VS$1,
    uvTransformVS: uvTransformVS$1,
    uvTransformUniformsPS: uvTransformUniformsPS$1,
    viewDirPS: viewDirPS$1,
    webgpuPS: webgpuPS$1,
    webgpuVS: webgpuVS$1
};

var alphaTestPS = /* wgsl */ `
uniform alpha_ref: f32;

fn alphaTest(a: f32) {
    if (a < uniform.alpha_ref) {
        discard;
    }
}
`;

var ambientPS = /* wgsl */ `

#if LIT_AMBIENT_SOURCE == AMBIENTSH
    uniform ambientSH: array<vec3f, 9>;
#endif

#if LIT_AMBIENT_SOURCE == ENVALATLAS
    #include "envAtlasPS"

    #ifndef ENV_ATLAS
        #define ENV_ATLAS
        var texture_envAtlas: texture_2d<f32>;
        var texture_envAtlasSampler: sampler;
    #endif
#endif

fn addAmbient(worldNormal: vec3f) {
    #ifdef LIT_AMBIENT_SOURCE == AMBIENTSH

        let n: vec3f = cubeMapRotate(worldNormal);
        let color: vec3f =
            uniform.ambientSH[0] +
            uniform.ambientSH[1] * n.x +
            uniform.ambientSH[2] * n.y +
            uniform.ambientSH[3] * n.z +
            uniform.ambientSH[4] * n.x * n.z +
            uniform.ambientSH[5] * n.z * n.y +
            uniform.ambientSH[6] * n.y * n.x +
            uniform.ambientSH[7] * (3.0 * n.z * n.z - 1.0) +
            uniform.ambientSH[8] * (n.x * n.x - n.y * n.y);

        dDiffuseLight += processEnvironment(max(color, vec3f(0.0)));

    #endif

    #if LIT_AMBIENT_SOURCE == ENVALATLAS

        let dir: vec3f = normalize(cubeMapRotate(worldNormal) * vec3f(-1.0, 1.0, 1.0));
        let uv: vec2f = mapUv(toSphericalUv(dir), vec4f(128.0, 256.0 + 128.0, 64.0, 32.0) / atlasSize);

        let raw: vec4f = textureSample(texture_envAtlas, texture_envAtlasSampler, uv);
        let linear: vec3f = {ambientDecode}(raw);
        dDiffuseLight += processEnvironment(linear);

    #endif

    #if LIT_AMBIENT_SOURCE == CONSTANT

        dDiffuseLight += uniform.light_globalAmbient;

    #endif
}
`;

var anisotropyPS = /* wgsl */ `
#ifdef LIT_GGX_SPECULAR
    uniform material_anisotropyIntensity: f32;
    uniform material_anisotropyRotation: vec2f;
#endif

fn getAnisotropy() {
    dAnisotropy = 0.0;
    dAnisotropyRotation = vec2f(1.0, 0.0);

#ifdef LIT_GGX_SPECULAR
    dAnisotropy = uniform.material_anisotropyIntensity;
    dAnisotropyRotation = uniform.material_anisotropyRotation;
#endif

#ifdef STD_ANISOTROPY_TEXTURE
    let anisotropyTex: vec3f = textureSampleBias({STD_ANISOTROPY_TEXTURE_NAME}, {STD_ANISOTROPY_TEXTURE_NAME}Sampler, {STD_ANISOTROPY_TEXTURE_UV}, uniform.textureBias).rgb;
    dAnisotropy *= anisotropyTex.b;

    let anisotropyRotationFromTex: vec2f = anisotropyTex.rg * 2.0 - vec2f(1.0);
    let rotationMatrix: mat2x2f = mat2x2f(dAnisotropyRotation.x, dAnisotropyRotation.y, -dAnisotropyRotation.y, dAnisotropyRotation.x);
    dAnisotropyRotation = rotationMatrix * anisotropyRotationFromTex;
#endif

    dAnisotropy = clamp(dAnisotropy, 0.0, 1.0);
}
`;

var aoPS = /* wgsl */ `

#if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)
    uniform material_aoIntensity: f32;
#endif

#ifdef STD_AODETAIL_TEXTURE
    #include "detailModesPS"
#endif

fn getAO() {
    dAo = 1.0;

    #ifdef STD_AO_TEXTURE
        var aoBase: f32 = textureSampleBias({STD_AO_TEXTURE_NAME}, {STD_AO_TEXTURE_NAME}Sampler, {STD_AO_TEXTURE_UV}, uniform.textureBias).{STD_AO_TEXTURE_CHANNEL};

        #ifdef STD_AODETAIL_TEXTURE
            var aoDetail: f32 = textureSampleBias({STD_AODETAIL_TEXTURE_NAME}, {STD_AODETAIL_TEXTURE_NAME}Sampler, {STD_AODETAIL_TEXTURE_UV}, uniform.textureBias).{STD_AODETAIL_TEXTURE_CHANNEL};
            aoBase = detailMode_{STD_AODETAIL_DETAILMODE}(vec3f(aoBase), vec3f(aoDetail)).r;
        #endif

        dAo = dAo * aoBase;
    #endif

    #ifdef STD_AO_VERTEX
        dAo = dAo * saturate(vVertexColor.{STD_AO_VERTEX_CHANNEL});
    #endif

    #if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)
        dAo = mix(1.0, dAo, uniform.material_aoIntensity);
    #endif
}
`;

var aoDiffuseOccPS = /* wgsl */ `
fn occludeDiffuse(ao: f32) {
    dDiffuseLight = dDiffuseLight * ao;
}
`;

var aoSpecOccPS = /* wgsl */ `

#if LIT_OCCLUDE_SPECULAR != NONE
    #ifdef LIT_OCCLUDE_SPECULAR_FLOAT
        uniform material_occludeSpecularIntensity: f32;
    #endif
#endif

fn occludeSpecular(gloss: f32, ao: f32, worldNormal: vec3f, viewDir: vec3f) {

    #if LIT_OCCLUDE_SPECULAR == AO
        #ifdef LIT_OCCLUDE_SPECULAR_FLOAT
            var specOcc: f32 = mix(1.0, ao, uniform.material_occludeSpecularIntensity);
        #else
            var specOcc: f32 = ao;
        #endif
    #endif

    #if LIT_OCCLUDE_SPECULAR == GLOSSDEPENDENT

        // approximated specular occlusion from AO
        // http://research.tri-ace.com/Data/cedec2011_RealtimePBR_Implementation_e.pptx
        var specPow: f32 = exp2(gloss * 11.0);
        var specOcc: f32 = saturate(pow(dot(worldNormal, viewDir) + ao, 0.01 * specPow) - 1.0 + ao);

        #ifdef LIT_OCCLUDE_SPECULAR_FLOAT
            specOcc = mix(1.0, specOcc, uniform.material_occludeSpecularIntensity);
        #endif
    #endif

    #if LIT_OCCLUDE_SPECULAR != NONE
        dSpecularLight = dSpecularLight * specOcc;
        dReflection = dReflection * specOcc;

        #ifdef LIT_SHEEN
            sSpecularLight = sSpecularLight * specOcc;
            sReflection = sReflection * specOcc;
        #endif
    #endif
}
`;

var bakeDirLmEndPS = /* wgsl */ `
    let dirLm = textureSample(texture_dirLightMap, texture_dirLightMapSampler, vUv1);

    if (uniform.bakeDir > 0.5) {
        if (dAtten > 0.00001) {
            let unpacked_dir = dirLm.xyz * 2.0 - vec3f(1.0);
            dAtten = clamp(dAtten, 0.0, 1.0);
            let combined_dir = dLightDirNormW.xyz * dAtten + unpacked_dir * dirLm.w;
            let finalRgb = normalize(combined_dir) * 0.5 + vec3f(0.5);
            let finalA = max(dirLm.w + dAtten, 1.0 / 255.0);
            output.color = vec4f(finalRgb, finalA);
        } else {
            output.color = dirLm;
        }
    } else {
        let alpha_min = select(0.0, 1.0 / 255.0, dAtten > 0.00001);
        let finalA = max(dirLm.w, alpha_min);
        output.color = vec4f(dirLm.rgb, finalA);
    }
`;

var bakeLmEndPS = /* wgsl */ `

#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT
    // diffuse light stores accumulated AO, apply contrast and brightness to it
    // and multiply ambient light color by the AO
    dDiffuseLight = ((dDiffuseLight - 0.5) * max(uniform.ambientBakeOcclusionContrast + 1.0, 0.0)) + 0.5;
    dDiffuseLight = dDiffuseLight + vec3f(uniform.ambientBakeOcclusionBrightness);
    dDiffuseLight = saturate3(dDiffuseLight);
    dDiffuseLight = dDiffuseLight * dAmbientLight;
#endif

#ifdef LIGHTMAP_RGBM
    // encode to RGBM
    var temp_color_rgbm = vec4f(dDiffuseLight, 1.0);
    temp_color_rgbm = vec4f(pow(temp_color_rgbm.rgb, vec3f(0.5)), temp_color_rgbm.a);
    temp_color_rgbm = vec4f(temp_color_rgbm.rgb / 8.0, temp_color_rgbm.a);
    let max_g_b = max(temp_color_rgbm.g, max(temp_color_rgbm.b, 1.0 / 255.0));
    let max_rgb = max(temp_color_rgbm.r, max_g_b);
    temp_color_rgbm.a = clamp(max_rgb, 0.0, 1.0);
    temp_color_rgbm.a = ceil(temp_color_rgbm.a * 255.0) / 255.0;
    temp_color_rgbm = vec4f(temp_color_rgbm.rgb / temp_color_rgbm.a, temp_color_rgbm.a);
    output.color = temp_color_rgbm;
#else
    output.color = vec4f(dDiffuseLight, 1.0);
#endif
`;

var basePS = /* wgsl */ `
uniform view_position: vec3f;

uniform light_globalAmbient: vec3f;

fn square(x: f32) -> f32 {
    return x*x;
}

fn saturate(x: f32) -> f32 {
    return clamp(x, 0.0, 1.0);
}

fn saturate3(x: vec3f) -> vec3f {
    return clamp(x, vec3f(0.0), vec3f(1.0));
}
`;

var baseNineSlicedPS = /* wgsl */ `
#define NINESLICED

varying vMask: vec2f;
varying vTiledUv: vec2f;

uniform innerOffset: vec4f;
uniform outerScale: vec2f;
uniform atlasRect: vec4f;

var<private> nineSlicedUv: vec2f;
`;

var baseNineSlicedTiledPS = /* wgsl */ `
#define NINESLICED
#define NINESLICETILED

varying vMask: vec2f;
varying vTiledUv: vec2f;

uniform innerOffset: vec4f;
uniform outerScale: vec2f;
uniform atlasRect: vec4f;

var<private> nineSlicedUv: vec2f;
`;

// procedural Bayer matrix, based on: https://www.shadertoy.com/view/Mlt3z8
var bayerPS = /* wgsl */ `
// 2x2 bayer matrix [1 2][3 0], p in [0,1]
fn bayer2(p: vec2f) -> f32 {
    return (2.0 * p.y + p.x + 1.0) % 4.0;
}

// 4x4 matrix, p - pixel coordinate
fn bayer4(p: vec2f) -> f32 {
    let p1: vec2f = p % vec2f(2.0);
    let p2: vec2f = floor(0.5 * (p % vec2f(4.0)));
    return 4.0 * bayer2(p1) + bayer2(p2);
}

// 8x8 matrix, p - pixel coordinate
fn bayer8(p: vec2f) -> f32 {
    let p1: vec2f = p % vec2f(2.0);
    let p2: vec2f = floor(0.5 * (p % vec2f(4.0)));
    let p4: vec2f = floor(0.25 * (p % vec2f(8.0)));
    return 4.0 * (4.0 * bayer2(p1) + bayer2(p2)) + bayer2(p4);
}
`;

var blurVSMPS = /* wgsl */ `
varying vUv0: vec2f;

var source: texture_2d<f32>;
var sourceSampler: sampler;

#ifdef GAUSS
    uniform weight: array<f32, {SAMPLES}>;
#endif
uniform pixelOffset: vec2f;

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;
    var moments: vec3f = vec3f(0.0);
    let uv: vec2f = input.vUv0 - uniform.pixelOffset * (f32({SAMPLES}) * 0.5);

    for (var i: i32 = 0; i < {SAMPLES}; i = i + 1) {
        let c: vec4f = textureSample(source, sourceSampler, uv + uniform.pixelOffset * f32(i));

        #ifdef GAUSS
            moments = moments + c.xyz * uniform.weight[i].element;
        #else
            moments = moments + c.xyz;
        #endif
    }

    #ifndef GAUSS
        moments = moments * (1.0 / f32({SAMPLES}));
    #endif

    output.color = vec4f(moments, 1.0);
    return output;
}
`;

var clearCoatPS = /* wgsl */ `
uniform material_clearCoat: f32;

fn getClearCoat() {
    ccSpecularity = uniform.material_clearCoat;

    #ifdef STD_CLEARCOAT_TEXTURE
    ccSpecularity = ccSpecularity * textureSampleBias({STD_CLEARCOAT_TEXTURE_NAME}, {STD_CLEARCOAT_TEXTURE_NAME}Sampler, {STD_CLEARCOAT_TEXTURE_UV}, uniform.textureBias).{STD_CLEARCOAT_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_CLEARCOAT_VERTEX
    ccSpecularity = ccSpecularity * saturate(vVertexColor.{STD_CLEARCOAT_VERTEX_CHANNEL});
    #endif
}
`;

var clearCoatGlossPS = /* wgsl */ `
    uniform material_clearCoatGloss: f32;

fn getClearCoatGlossiness() {
    ccGlossiness = uniform.material_clearCoatGloss;

    #ifdef STD_CLEARCOATGLOSS_TEXTURE
    ccGlossiness = ccGlossiness * textureSampleBias({STD_CLEARCOATGLOSS_TEXTURE_NAME}, {STD_CLEARCOATGLOSS_TEXTURE_NAME}Sampler, {STD_CLEARCOATGLOSS_TEXTURE_UV}, uniform.textureBias).{STD_CLEARCOATGLOSS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_CLEARCOATGLOSS_VERTEX
    ccGlossiness = ccGlossiness * saturate(vVertexColor.{STD_CLEARCOATGLOSS_VERTEX_CHANNEL});
    #endif

    #ifdef STD_CLEARCOATGLOSS_INVERT
    ccGlossiness = 1.0 - ccGlossiness;
    #endif

    ccGlossiness += 0.0000001;
}
`;

var clearCoatNormalPS = /* wgsl */ `
#ifdef STD_CLEARCOATNORMAL_TEXTURE
    uniform material_clearCoatBumpiness: f32;
#endif

fn getClearCoatNormal() {
#ifdef STD_CLEARCOATNORMAL_TEXTURE
    var normalMap: vec3f = {STD_CLEARCOATNORMAL_TEXTURE_DECODE}(textureSampleBias({STD_CLEARCOATNORMAL_TEXTURE_NAME}, {STD_CLEARCOATNORMAL_TEXTURE_NAME}Sampler, {STD_CLEARCOATNORMAL_TEXTURE_UV}, uniform.textureBias));
    normalMap = mix(vec3f(0.0, 0.0, 1.0), normalMap, uniform.material_clearCoatBumpiness);
    ccNormalW = normalize(dTBN * normalMap);
#else
    ccNormalW = dVertexNormalW;
#endif
}
`;

var clusteredLightUtilsPS = /* wgsl */ `
struct FaceCoords {
    uv: vec2f,          // Original return value
    faceIndex: f32,    // Was out parameter
    tileOffset: vec2f, // Was out parameter
}

// Converts unnormalized direction vector to a cubemap face index [0..5] and uv coordinates within the face in [0..1] range.
// Additionally offset to a tile in atlas within 3x3 subdivision is provided
fn getCubemapFaceCoordinates(dir: vec3f) -> FaceCoords {
    var faceIndex: f32;
    var tileOffset: vec2f;
    var uv: vec2f; // This is the face UV [0..1]
    let vAbs: vec3f = abs(dir);
    var ma: f32;

    if (vAbs.z >= vAbs.x && vAbs.z >= vAbs.y) {   // front / back

        let is_neg_z = dir.z < 0.0;
        faceIndex = select(4.0, 5.0, is_neg_z);
        ma = 0.5 / vAbs.z;
        uv = vec2f(select(dir.x, -dir.x, is_neg_z), -dir.y);
        tileOffset = vec2f(2.0, select(0.0, 1.0, is_neg_z));

    } else if (vAbs.y >= vAbs.x) {  // top index 2, bottom index 3

        let is_neg_y = dir.y < 0.0;
        faceIndex = select(2.0, 3.0, is_neg_y);
        ma = 0.5 / vAbs.y;
        uv = vec2f(dir.x, select(dir.z, -dir.z, is_neg_y));
        tileOffset = vec2f(1.0, select(0.0, 1.0, is_neg_y));

    } else {    // left / right

        let is_neg_x = dir.x < 0.0;
        faceIndex = select(0.0, 1.0, is_neg_x);
        ma = 0.5 / vAbs.x;
        uv = vec2f(select(-dir.z, dir.z, is_neg_x), -dir.y);
        tileOffset = vec2f(0.0, select(0.0, 1.0, is_neg_x));
    }

    uv = uv * ma + 0.5;
    return FaceCoords(uv, faceIndex, tileOffset);
}

// converts unnormalized direction vector to a texture coordinate for a cubemap face stored within texture atlas described by the viewport
fn getCubemapAtlasCoordinates(omniAtlasViewport: vec3f, shadowEdgePixels: f32, shadowTextureResolution: f32, dir: vec3f) -> vec2f {

    let faceData: FaceCoords = getCubemapFaceCoordinates(dir);
    var uv: vec2f = faceData.uv;
    let tileOffset: vec2f = faceData.tileOffset;

    // move uv coordinates inwards inside to compensate for larger fov when rendering shadow into atlas
    let atlasFaceSize: f32 = omniAtlasViewport.z;
    let tileSize: f32 = shadowTextureResolution * atlasFaceSize;
    var offset: f32 = shadowEdgePixels / tileSize;
    uv = uv * (1.0 - offset * 2.0) + offset;

    // scale uv coordinates to cube face area within the viewport
    uv = uv * atlasFaceSize;

    // offset into face of the atlas (3x3 grid)
    uv = uv + tileOffset * atlasFaceSize;

    // offset into the atlas viewport
    uv = uv + omniAtlasViewport.xy;

    return uv;
}
`;

var clusteredLightCookiesPS = /* wgsl */ `
fn _getCookieClustered(tex: texture_2d<f32>, texSampler: sampler, uv: vec2f, intensity: f32, cookieChannel: vec4f) -> vec3f {
    let pixel: vec4f = mix(vec4f(1.0), textureSampleLevel(tex, texSampler, uv, 0.0), intensity);
    let isRgb: bool = dot(cookieChannel.rgb, vec3f(1.0)) == 3.0;
    return select(vec3f(dot(pixel, cookieChannel)), pixel.rgb, isRgb);
}

// getCookie2D for clustered lighting including channel selector
fn getCookie2DClustered(tex: texture_2d<f32>, texSampler: sampler, transform: mat4x4f, worldPosition: vec3f, intensity: f32, cookieChannel: vec4f) -> vec3f {
    let projPos: vec4f = transform * vec4f(worldPosition, 1.0);
    return _getCookieClustered(tex, texSampler, projPos.xy / projPos.w, intensity, cookieChannel);
}

// getCookie for clustered omni light with the cookie texture being stored in the cookie atlas
fn getCookieCubeClustered(tex: texture_2d<f32>, texSampler: sampler, dir: vec3f, intensity: f32, cookieChannel: vec4f, shadowTextureResolution: f32, shadowEdgePixels: f32, omniAtlasViewport: vec3f) -> vec3f {
    let uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, dir);
    return _getCookieClustered(tex, texSampler, uv, intensity, cookieChannel);
}
`;

// Clustered Omni Sampling using atlas
var clusteredLightShadowsPS = /* wgsl */ `

fn _getShadowCoordPerspZbuffer(shadowMatrix: mat4x4f, shadowParams: vec4f, wPos: vec3f) -> vec3f {
    var projPos = shadowMatrix * vec4f(wPos, 1.0);
    return projPos.xyz / projPos.w;
    // depth bias is already applied on render
}

fn getShadowCoordPerspZbufferNormalOffset(shadowMatrix: mat4x4f, shadowParams: vec4f, normal: vec3f) -> vec3f {
    let wPos: vec3f = vPositionW + normal * shadowParams.y;
    return _getShadowCoordPerspZbuffer(shadowMatrix, shadowParams, wPos);
}

fn normalOffsetPointShadow(shadowParams: vec4f, lightPos: vec3f, lightDir: vec3f, lightDirNorm: vec3f, normal: vec3f) -> vec3f {
    let distScale: f32 = length(lightDir);
    let wPos: vec3f = vPositionW + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale; //0.02
    let dir: vec3f = wPos - lightPos;
    return dir;
}

#if defined(CLUSTER_SHADOW_TYPE_PCF1)

    fn getShadowOmniClusteredPCF1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowParams: vec4f, omniAtlasViewport: vec3f, shadowEdgePixels: f32, lightDir: vec3f) -> f32 {

        let shadowTextureResolution: f32 = shadowParams.x;
        let uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);

        let shadowZ: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
        return textureSampleCompareLevel(shadowMap, shadowMapSampler, uv, shadowZ);
    }

#endif

#if defined(CLUSTER_SHADOW_TYPE_PCF3)

    fn getShadowOmniClusteredPCF3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowParams: vec4f, omniAtlasViewport: vec3f, shadowEdgePixels: f32, lightDir: vec3f) -> f32 {

        let shadowTextureResolution: f32 = shadowParams.x;
        let uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);

        let shadowZ: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
        let shadowCoord: vec3f = vec3f(uv, shadowZ);
        return getShadowPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams);
    }

#endif

#if defined(CLUSTER_SHADOW_TYPE_PCF5)

    fn getShadowOmniClusteredPCF5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowParams: vec4f, omniAtlasViewport: vec3f, shadowEdgePixels: f32, lightDir: vec3f) -> f32 {

        let shadowTextureResolution: f32 = shadowParams.x;
        let uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);

        let shadowZ: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
        let shadowCoord: vec3f = vec3f(uv, shadowZ);
        return getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams);
    }

#endif

// Clustered Spot Sampling using atlas

#if defined(CLUSTER_SHADOW_TYPE_PCF1)

    fn getShadowSpotClusteredPCF1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
        return textureSampleCompareLevel(shadowMap, shadowMapSampler, shadowCoord.xy, shadowCoord.z);
    }

#endif

    #if defined(CLUSTER_SHADOW_TYPE_PCF3)

    fn getShadowSpotClusteredPCF3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
        return getShadowSpotPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams);
    }

#endif

    #if defined(CLUSTER_SHADOW_TYPE_PCF5)

    fn getShadowSpotClusteredPCF5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
        return getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams);
    }

#endif
`;

var clusteredLightPS = /* wgsl */ `

#include "lightBufferDefinesPS"

// include this before shadow / cookie code
#include "clusteredLightUtilsPS"

#ifdef CLUSTER_COOKIES
    #include "clusteredLightCookiesPS"
#endif

#ifdef CLUSTER_SHADOWS
    #include "clusteredLightShadowsPS"
#endif

var clusterWorldTexture: texture_2d<f32>;
var lightsTexture: texture_2d<f32>;

#ifdef CLUSTER_SHADOWS
    // TODO: when VSM shadow is supported, it needs to use sampler2D in webgl2
    var shadowAtlasTexture: texture_depth_2d;
    var shadowAtlasTextureSampler: sampler_comparison;
#endif

#ifdef CLUSTER_COOKIES
    var cookieAtlasTexture: texture_2d<f32>;
    var cookieAtlasTextureSampler: sampler;
#endif

uniform clusterMaxCells: i32;

// 1.0 if clustered lighting can be skipped (0 lights in the clusters)
uniform clusterSkip: f32;

uniform clusterCellsCountByBoundsSize: vec3f;
uniform clusterTextureSize: vec3f;
uniform clusterBoundsMin: vec3f;
uniform clusterBoundsDelta: vec3f;
uniform clusterCellsDot: vec3f;
uniform clusterCellsMax: vec3f;
uniform shadowAtlasParams: vec2f;

// structure storing light properties of a clustered light
// it's sorted to have all vectors aligned to 4 floats to limit padding
struct ClusterLightData {

    // 32bit of flags
    flags: u32,

    // area light sizes / orientation
    halfWidth: vec3f,

    isSpot: bool,

    // area light sizes / orientation
    halfHeight: vec3f,

    // light index
    lightIndex: i32,

    // world space position
    position: vec3f,

    // area light shape
    shape: u32,

    // world space direction (spot light only)
    direction: vec3f,

    // light follow mode
    falloffModeLinear: bool,

    // color
    color: vec3f,

    // 0.0 if the light doesn't cast shadows
    shadowIntensity: f32,

    // atlas viewport for omni light shadow and cookie (.xy is offset to the viewport slot, .z is size of the face in the atlas)
    omniAtlasViewport: vec3f,

    // range of the light
    range: f32,

    // channel mask - one of the channels has 1, the others are 0
    cookieChannelMask: vec4f,

    // compressed biases, two haf-floats stored in a float
    biasesData: f32,

    // shadow bias values
    shadowBias: f32,
    shadowNormalBias: f32,

    // compressed angles, two haf-floats stored in a float
    anglesData: f32,

    // spot light inner and outer angle cosine
    innerConeAngleCos: f32,
    outerConeAngleCos: f32,

    // intensity of the cookie
    cookieIntensity: f32,

    // light mask
    //float mask;
    isDynamic: bool,
    isLightmapped: bool
}

// Note: on some devices (tested on Pixel 3A XL), this matrix when stored inside the light struct has lower precision compared to
// when stored outside, so we store it outside to avoid spot shadow flickering. This might need to be done to other / all members
// of the structure if further similar issues are observed.

// shadow (spot light only) / cookie projection matrix
var<private> lightProjectionMatrix: mat4x4f;

fn sampleLightTextureF(lightIndex: i32, index: i32) -> vec4f {
    return textureLoad(lightsTexture, vec2<i32>(index, lightIndex), 0);
}

fn decodeClusterLightCore(clusterLightData: ptr<function, ClusterLightData>, lightIndex: f32) {

    // light index
    clusterLightData.lightIndex = i32(lightIndex);

    // sample data encoding half-float values into 32bit uints
    let halfData: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS});

    // store floats we decode later as needed
    clusterLightData.anglesData = halfData.z;
    clusterLightData.biasesData = halfData.w;

    // decompress color half-floats
    let colorRG: vec2f = unpack2x16float(bitcast<u32>(halfData.x));
    let colorB_: vec2f = unpack2x16float(bitcast<u32>(halfData.y));
    clusterLightData.color = vec3f(colorRG, colorB_.x) * {LIGHT_COLOR_DIVIDER};

    // position and range, full floats
    let lightPosRange: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_POSITION_RANGE});
    clusterLightData.position = lightPosRange.xyz;
    clusterLightData.range = lightPosRange.w;

    // spot direction & flags data
    let lightDir_Flags: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_DIRECTION_FLAGS});

    // spot light direction
    clusterLightData.direction = lightDir_Flags.xyz;

    // 32bit flags
    let flags_uint: u32 = bitcast<u32>(lightDir_Flags.w);
    clusterLightData.flags = flags_uint;
    clusterLightData.isSpot = (flags_uint & (1u << 30u)) != 0u;
    clusterLightData.shape = (flags_uint >> 28u) & 0x3u;
    clusterLightData.falloffModeLinear = (flags_uint & (1u << 27u)) == 0u;
    clusterLightData.shadowIntensity = f32((flags_uint >> 0u) & 0xFFu) / 255.0;
    clusterLightData.cookieIntensity = f32((flags_uint >> 8u) & 0xFFu) / 255.0;
    clusterLightData.isDynamic = (flags_uint & (1u << 22u)) != 0u;
    clusterLightData.isLightmapped = (flags_uint & (1u << 21u)) != 0u;
}

fn decodeClusterLightSpot(clusterLightData: ptr<function, ClusterLightData>) {
    // spot light cos angles
    let angles: vec2f = unpack2x16float(bitcast<u32>(clusterLightData.anglesData));
    clusterLightData.innerConeAngleCos = angles.x;
    clusterLightData.outerConeAngleCos = angles.y;
}

fn decodeClusterLightOmniAtlasViewport(clusterLightData: ptr<function, ClusterLightData>) {
    clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;
}

fn decodeClusterLightAreaData(clusterLightData: ptr<function, ClusterLightData>) {
    clusterLightData.halfWidth = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz;
    clusterLightData.halfHeight = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz;
}

fn decodeClusterLightProjectionMatrixData(clusterLightData: ptr<function, ClusterLightData>) {
    // shadow matrix
    let m0: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0});
    let m1: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_1});
    let m2: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_2});
    let m3: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_3});
    lightProjectionMatrix = mat4x4f(m0, m1, m2, m3);
}

fn decodeClusterLightShadowData(clusterLightData: ptr<function, ClusterLightData>) {
    // shadow biases
    let biases: vec2f = unpack2x16float(bitcast<u32>(clusterLightData.biasesData));
    clusterLightData.shadowBias = biases.x;
    clusterLightData.shadowNormalBias = biases.y;
}

fn decodeClusterLightCookieData(clusterLightData: ptr<function, ClusterLightData>) {

    // extract channel mask from flags
    let cookieFlags: u32 = (clusterLightData.flags >> 23u) & 0x0Fu;  // 4bits, each bit enables a channel
    let mask_uvec: vec4<u32> = vec4<u32>(cookieFlags) & vec4<u32>(1u, 2u, 4u, 8u);
    clusterLightData.cookieChannelMask = step(vec4f(1.0), vec4f(mask_uvec)); // Normalize to 0.0 or 1.0
}

fn evaluateLight(
    light: ptr<function, ClusterLightData>,
    worldNormal: vec3f,
    viewDir: vec3f,
    reflectionDir: vec3f,
#if defined(LIT_CLEARCOAT)
    clearcoatReflectionDir: vec3f,
#endif
    gloss: f32,
    specularity: vec3f,
    geometricNormal: vec3f,
    tbn: mat3x3f,
#if defined(LIT_IRIDESCENCE)
    iridescenceFresnel: vec3f,
#endif
    clearcoat_worldNormal: vec3f,
    clearcoat_gloss: f32,
    sheen_gloss: f32,
    iridescence_intensity: f32
) {

    var cookieAttenuation: vec3f = vec3f(1.0);
    var diffuseAttenuation: f32 = 1.0;
    var falloffAttenuation: f32 = 1.0;

    // evaluate omni part of the light
    let lightDirW: vec3f = evalOmniLight(light.position);
    let lightDirNormW: vec3f = normalize(lightDirW);

    #ifdef CLUSTER_AREALIGHTS

    // distance attenuation
    if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light

        // area lights
        decodeClusterLightAreaData(light);

        // handle light shape
        if (light.shape == {LIGHTSHAPE_RECT}) {
            calcRectLightValues(light.position, light.halfWidth, light.halfHeight);
        } else if (light.shape == {LIGHTSHAPE_DISK}) {
            calcDiskLightValues(light.position, light.halfWidth, light.halfHeight);
        } else { // sphere
            calcSphereLightValues(light.position, light.halfWidth, light.halfHeight);
        }

        falloffAttenuation = getFalloffWindow(light.range, lightDirW);

    } else

    #endif

    {   // punctual light

        if (light.falloffModeLinear) {
            falloffAttenuation = getFalloffLinear(light.range, lightDirW);
        } else {
            falloffAttenuation = getFalloffInvSquared(light.range, lightDirW);
        }
    }

    if (falloffAttenuation > 0.00001) {

        #ifdef CLUSTER_AREALIGHTS

        if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light

            // handle light shape
            if (light.shape == {LIGHTSHAPE_RECT}) {
                diffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;
            } else if (light.shape == {LIGHTSHAPE_DISK}) {
                diffuseAttenuation = getDiskLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;
            } else { // sphere
                diffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;
            }

        } else

        #endif

        {
            falloffAttenuation = falloffAttenuation * getLightDiffuse(worldNormal, viewDir, lightDirNormW);
        }

        // spot light falloff
        if (light.isSpot) {
            decodeClusterLightSpot(light);
            falloffAttenuation = falloffAttenuation * getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);
        }

        #if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)

        if (falloffAttenuation > 0.00001) {

            // shadow / cookie
            if (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {

                // shared shadow / cookie data depends on light type
                if (light.isSpot) {
                    decodeClusterLightProjectionMatrixData(light);
                } else {
                    decodeClusterLightOmniAtlasViewport(light);
                }

                let shadowTextureResolution: f32 = uniform.shadowAtlasParams.x;
                let shadowEdgePixels: f32 = uniform.shadowAtlasParams.y;

                #ifdef CLUSTER_COOKIES

                // cookie
                if (light.cookieIntensity > 0.0) {
                    decodeClusterLightCookieData(light);

                    if (light.isSpot) {
                        // !!!!!!!!!!! TEXTURE_PASS likely needs sampler. Assuming cookieAtlasTextureSampler exists.
                        cookieAttenuation = getCookie2DClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightProjectionMatrix, vPositionW, light.cookieIntensity, light.cookieChannelMask);
                    } else {
                        // !!!!!!!!!!! TEXTURE_PASS likely needs sampler. Assuming cookieAtlasTextureSampler exists.
                        cookieAttenuation = getCookieCubeClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightDirW, light.cookieIntensity, light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);
                    }
                }

                #endif

                #ifdef CLUSTER_SHADOWS

                // shadow
                if (light.shadowIntensity > 0.0) {
                    decodeClusterLightShadowData(light);

                    let shadowParams: vec4f = vec4f(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);

                    if (light.isSpot) {

                        // spot shadow
                        let shadowCoord: vec3f = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);

                        // !!!!!!!!!!! SHADOWMAP_PASS needs texture and sampler_comparison.
                        // !!!!!!!!!!! Shadow functions need update for WGSL textureSampleCompare etc. Assuming these are handled in includes.
                        #if defined(CLUSTER_SHADOW_TYPE_PCF1)
                            let shadow: f32 = getShadowSpotClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF3)
                            let shadow: f32 = getShadowSpotClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF5)
                            let shadow: f32 = getShadowSpotClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCSS)
                            let shadow: f32 = getShadowSpotClusteredPCSS(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);
                        #endif
                        falloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity);

                    } else {

                        // omni shadow
                        let dir: vec3f = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, geometricNormal);  // normalBias adjusted for distance

                        // !!!!!!!!!!! SHADOWMAP_PASS needs texture and sampler_comparison.
                        // !!!!!!!!!!! Shadow functions need update for WGSL textureSampleCompare etc. Assuming these are handled in includes.
                        #if defined(CLUSTER_SHADOW_TYPE_PCF1)
                            let shadow: f32 = getShadowOmniClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF3)
                            let shadow: f32 = getShadowOmniClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
                        #elif defined(CLUSTER_SHADOW_TYPE_PCF5)
                            let shadow: f32 = getShadowOmniClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
                        #endif
                        falloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity);
                    }
                }

                #endif
            }
        }

        #endif

        // diffuse / specular / clearcoat
        #ifdef CLUSTER_AREALIGHTS

        if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light

            // area light diffuse
            {
                var areaDiffuse: vec3f = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;

                #if defined(LIT_SPECULAR)
                    areaDiffuse = mix(areaDiffuse, vec3f(0.0), dLTCSpecFres);
                #endif

                // area light diffuse - it does not mix diffuse lighting into specular attenuation
                dDiffuseLight = dDiffuseLight + areaDiffuse;
            }

            // specular and clear coat are material settings and get included by a define based on the material
            #ifdef LIT_SPECULAR

                // area light specular
                var areaLightSpecular: f32; // Use var because assigned in if/else

                if (light.shape == {LIGHTSHAPE_RECT}) {
                    areaLightSpecular = getRectLightSpecular(worldNormal, viewDir);
                } else if (light.shape == {LIGHTSHAPE_DISK}) {
                    areaLightSpecular = getDiskLightSpecular(worldNormal, viewDir);
                } else { // sphere
                    areaLightSpecular = getSphereLightSpecular(worldNormal, viewDir);
                }

                dSpecularLight = dSpecularLight + dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;

                #ifdef LIT_CLEARCOAT

                    // area light specular clear coat
                    var areaLightSpecularCC: f32; // Use var because assigned in if/else

                    if (light.shape == {LIGHTSHAPE_RECT}) {
                        areaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir);
                    } else if (light.shape == {LIGHTSHAPE_DISK}) {
                        areaLightSpecularCC = getDiskLightSpecular(clearcoat_worldNormal, viewDir);
                    } else { // sphere
                        areaLightSpecularCC = getSphereLightSpecular(clearcoat_worldNormal, viewDir);
                    }

                    ccSpecularLight = ccSpecularLight + ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color  * cookieAttenuation;

                #endif

            #endif

        } else

        #endif

        {    // punctual light

            // punctual light diffuse
            {
                var punctualDiffuse: vec3f = falloffAttenuation * light.color * cookieAttenuation;

                #if defined(CLUSTER_AREALIGHTS)
                #if defined(LIT_SPECULAR)
                    punctualDiffuse = mix(punctualDiffuse, vec3f(0.0), specularity);
                #endif
                #endif

                dDiffuseLight = dDiffuseLight + punctualDiffuse;
            }

            // specular and clear coat are material settings and get included by a define based on the material
            #ifdef LIT_SPECULAR

                let halfDir: vec3f = normalize(-lightDirNormW + viewDir);

                // specular
                #ifdef LIT_SPECULAR_FRESNEL
                    dSpecularLight = dSpecularLight +
                        getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation *
                        getFresnel(
                            dot(viewDir, halfDir),
                            gloss,
                            specularity
                        #if defined(LIT_IRIDESCENCE)
                            , iridescenceFresnel,
                            iridescence_intensity
                        #endif
                            );
                #else
                    dSpecularLight = dSpecularLight + getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity;
                #endif

                #ifdef LIT_CLEARCOAT
                    #ifdef LIT_SPECULAR_FRESNEL
                        ccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));
                    #else
                        ccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation;
                    #endif
                #endif

                #ifdef LIT_SHEEN
                    sSpecularLight = sSpecularLight + getLightSpecularSheen(halfDir, worldNormal, viewDir, lightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation;
                #endif

            #endif
        }
    }

    // Write to global attenuation values (for lightmapper)
    dAtten = falloffAttenuation;
    dLightDirNormW = lightDirNormW;
}


fn evaluateClusterLight(
    lightIndex: f32,
    worldNormal: vec3f,
    viewDir: vec3f,
    reflectionDir: vec3f,
#if defined(LIT_CLEARCOAT)
    clearcoatReflectionDir: vec3f,
#endif
    gloss: f32,
    specularity: vec3f,
    geometricNormal: vec3f,
    tbn: mat3x3f,
#if defined(LIT_IRIDESCENCE)
    iridescenceFresnel: vec3f,
#endif
    clearcoat_worldNormal: vec3f,
    clearcoat_gloss: f32,
    sheen_gloss: f32,
    iridescence_intensity: f32
) {

    // decode core light data from textures
    var clusterLightData: ClusterLightData;
    decodeClusterLightCore(&clusterLightData, lightIndex);

    // evaluate light if it uses accepted light mask
    #ifdef CLUSTER_MESH_DYNAMIC_LIGHTS
        let acceptLightMask: bool = clusterLightData.isDynamic;
    #else
        let acceptLightMask: bool = clusterLightData.isLightmapped;
    #endif

    if (acceptLightMask) {
        evaluateLight(
            &clusterLightData,
            worldNormal,
            viewDir,
            reflectionDir,
#if defined(LIT_CLEARCOAT)
            clearcoatReflectionDir,
#endif
            gloss,
            specularity,
            geometricNormal,
            tbn,
#if defined(LIT_IRIDESCENCE)
            iridescenceFresnel,
#endif
            clearcoat_worldNormal,
            clearcoat_gloss,
            sheen_gloss,
            iridescence_intensity
        );
    }
}


fn addClusteredLights(
    worldNormal: vec3f,
    viewDir: vec3f,
    reflectionDir: vec3f,
#if defined(LIT_CLEARCOAT)
    clearcoatReflectionDir: vec3f,
#endif
    gloss: f32,
    specularity: vec3f,
    geometricNormal: vec3f,
    tbn: mat3x3f,
#if defined(LIT_IRIDESCENCE)
    iridescenceFresnel: vec3f,
#endif
    clearcoat_worldNormal: vec3f,
    clearcoat_gloss: f32,
    sheen_gloss: f32,
    iridescence_intensity: f32
) {

    // skip lights if no lights at all
    if (uniform.clusterSkip > 0.5) {
        return;
    }

    // world space position to 3d integer cell cordinates in the cluster structure
    let cellCoords: vec3f = floor((vPositionW - uniform.clusterBoundsMin) * uniform.clusterCellsCountByBoundsSize);

    // no lighting when cell coordinate is out of range
    if (!(any(cellCoords < vec3f(0.0)) || any(cellCoords >= uniform.clusterCellsMax))) {

        // cell index (mapping from 3d cell coordinates to linear memory)
        let cellIndex: f32 = dot(uniform.clusterCellsDot, cellCoords);

        // convert cell index to uv coordinates
        let clusterV: f32 = floor(cellIndex * uniform.clusterTextureSize.y);
        let clusterU: f32 = cellIndex - (clusterV * uniform.clusterTextureSize.x);

        // loop over maximum number of light cells
        for (var lightCellIndex: i32 = 0; lightCellIndex < uniform.clusterMaxCells; lightCellIndex = lightCellIndex + 1) {

            // using a single channel texture with data in red channel
            let lightIndexPacked: f32 = textureLoad(clusterWorldTexture, vec2<i32>(i32(clusterU) + lightCellIndex, i32(clusterV)), 0).r;

            if (lightIndexPacked <= 0.0) {
                break;
            }

            evaluateClusterLight(
                lightIndexPacked * 255.0,
                worldNormal,
                viewDir,
                reflectionDir,
#if defined(LIT_CLEARCOAT)
                clearcoatReflectionDir,
#endif
                gloss,
                specularity,
                geometricNormal,
                tbn,
#if defined(LIT_IRIDESCENCE)
                iridescenceFresnel,
#endif
                clearcoat_worldNormal,
                clearcoat_gloss,
                sheen_gloss,
                iridescence_intensity
            );
        }
    }
}`;

var combinePS = /* wgsl */ `
fn combineColor(albedo: vec3f, sheenSpecularity: vec3f, clearcoatSpecularity: f32) -> vec3f {
    var ret: vec3f = vec3f(0.0);

    #ifdef LIT_OLD_AMBIENT
        ret = ret + ((dDiffuseLight - uniform.light_globalAmbient) * albedo + uniform.material_ambient * uniform.light_globalAmbient);
    #else
        ret = ret + (albedo * dDiffuseLight);
    #endif // LIT_OLD_AMBIENT
    #ifdef LIT_SPECULAR
        ret = ret + dSpecularLight;
    #endif // LIT_SPECULAR
    #ifdef LIT_REFLECTIONS
        ret = ret + (dReflection.rgb * dReflection.a);
    #endif // LIT_REFLECTIONS

    #ifdef LIT_SHEEN
        let sheenScaling: f32 = 1.0 - max(max(sheenSpecularity.r, sheenSpecularity.g), sheenSpecularity.b) * 0.157;
        ret = ret * sheenScaling + (sSpecularLight + sReflection.rgb) * sheenSpecularity;
    #endif // LIT_SHEEN
    #ifdef LIT_CLEARCOAT
        let clearCoatScaling: f32 = 1.0 - ccFresnel * clearcoatSpecularity;
        ret = ret * clearCoatScaling + (ccSpecularLight + ccReflection) * clearcoatSpecularity;
    #endif // LIT_CLEARCOAT

    return ret;
}
`;

var cookieBlit2DPS = /* wgsl */ `
    varying uv0: vec2f;

    var blitTexture: texture_2d<f32>;
    var blitTextureSampler : sampler;

    @fragment
    fn fragmentMain(input : FragmentInput) -> FragmentOutput {
        var output: FragmentOutput;
        output.color = textureSample(blitTexture, blitTextureSampler, input.uv0);
        return output;
    }
`;

var cookieBlitCubePS = /* wgsl */ `
    varying uv0: vec2f;
    uniform invViewProj: mat4x4<f32>;
    var blitTexture: texture_cube<f32>;
    var blitTextureSampler : sampler;

    @fragment
    fn fragmentMain(input : FragmentInput) -> FragmentOutput {
        var output: FragmentOutput;
        var projPos = vec4f(input.uv0 * 2.0 - 1.0, 0.5, 1.0);
        var worldPos = uniform.invViewProj * projPos;
        output.color = textureSample(blitTexture, blitTextureSampler, worldPos.xyz);
        return output;
    }
`;

var cookieBlitVS = /* wgsl */ `
    attribute vertex_position: vec2f;
    varying uv0: vec2f;

    @vertex
    fn vertexMain(input: VertexInput) -> VertexOutput {
        var output: VertexOutput;
        output.position = vec4f(input.vertex_position, 0.5, 1.0);
        output.uv0 = input.vertex_position * 0.5 + vec2f(0.5, 0.5);
        output.uv0.y = 1.0 - output.uv0.y;
        return output;
    }
`;

var cubeMapProjectPS = /* wgsl */ `

#if LIT_CUBEMAP_PROJECTION == BOX
    uniform envBoxMin: vec3f;
    uniform envBoxMax: vec3f;
#endif

fn cubeMapProject(nrdir: vec3f) -> vec3f {

    #if LIT_CUBEMAP_PROJECTION == NONE
        return cubeMapRotate(nrdir);
    #endif

    #if LIT_CUBEMAP_PROJECTION == BOX

        let nrdir_rotated: vec3f = cubeMapRotate(nrdir);

        let rbmax: vec3f = (uniform.envBoxMax - vPositionW) / nrdir_rotated;
        let rbmin: vec3f = (uniform.envBoxMin - vPositionW) / nrdir_rotated;

        let rbminmax: vec3f = select(rbmin, rbmax, nrdir_rotated > vec3f(0.0));
        let fa: f32 = min(min(rbminmax.x, rbminmax.y), rbminmax.z);

        let posonbox: vec3f = vPositionW + nrdir_rotated * fa;
        let envBoxPos: vec3f = (uniform.envBoxMin + uniform.envBoxMax) * 0.5;
        return normalize(posonbox - envBoxPos);

    #endif
}
`;

var cubeMapRotatePS = /* wgsl */ `
#ifdef CUBEMAP_ROTATION
uniform cubeMapRotationMatrix: mat3x3f;
#endif

fn cubeMapRotate(refDir: vec3f) -> vec3f {
#ifdef CUBEMAP_ROTATION
    return refDir * uniform.cubeMapRotationMatrix;
#else
    return refDir;
#endif
}
`;

var debugOutputPS = /* wgsl */ `
#ifdef DEBUG_ALBEDO_PASS
output.color = vec4(gammaCorrectOutput(dAlbedo), 1.0);
#endif

#ifdef DEBUG_UV0_PASS
output.color = vec4f(litArgs_albedo , 1.0);
#endif

#ifdef DEBUG_WORLD_NORMAL_PASS
output.color = vec4f(litArgs_worldNormal * 0.5 + 0.5, 1.0);
#endif

#ifdef DEBUG_OPACITY_PASS
output.color = vec4f(vec3f(litArgs_opacity) , 1.0);
#endif

#ifdef DEBUG_SPECULARITY_PASS
output.color = vec4f(litArgs_specularity, 1.0);
#endif

#ifdef DEBUG_GLOSS_PASS
output.color = vec4f(vec3f(litArgs_gloss) , 1.0);
#endif

#ifdef DEBUG_METALNESS_PASS
output.color = vec4f(vec3f(litArgs_metalness) , 1.0);
#endif

#ifdef DEBUG_AO_PASS
output.color = vec4f(vec3f(litArgs_ao) , 1.0);
#endif

#ifdef DEBUG_EMISSION_PASS
output.color = vec4f(gammaCorrectOutput(litArgs_emission), 1.0);
#endif
`;

var debugProcessFrontendPS = /* wgsl */ `
#ifdef DEBUG_LIGHTING_PASS
    litArgs_albedo = vec3f(0.5);
#endif

#ifdef DEBUG_UV0_PASS
#ifdef VARYING_VUV0
    litArgs_albedo = vec3f(vUv0, 0.0);
#else
    litArgs_albedo = vec3f(0.0);
#endif
#endif
`;

var decodePS = /* wgsl */ `

#ifndef _DECODE_INCLUDED_
#define _DECODE_INCLUDED_

fn decodeLinear(raw: vec4f) -> vec3f {
    return raw.rgb;
}

fn decodeGammaFloat(raw: f32) -> f32 {
    return pow(raw, 2.2);
}

fn decodeGamma3(raw: vec3f) -> vec3f {
    return pow(raw, vec3f(2.2));
}

fn decodeGamma(raw: vec4f) -> vec3f {
    return pow(raw.xyz, vec3f(2.2));
}

fn decodeRGBM(raw: vec4f) -> vec3f {
    let color = (8.0 * raw.a) * raw.rgb;
    return color * color;
}

fn decodeRGBP(raw: vec4f) -> vec3f {
    let color = raw.rgb * (-raw.a * 7.0 + 8.0);
    return color * color;
}

fn decodeRGBE(raw: vec4f) -> vec3f {
    return select(vec3f(0.0), raw.xyz * pow(2.0, raw.w * 255.0 - 128.0), raw.a != 0.0);
}

fn passThrough(raw: vec4f) -> vec4f {
    return raw;
}

fn unpackNormalXYZ(nmap: vec4f) -> vec3f {
    return nmap.xyz * 2.0 - 1.0;
}

fn unpackNormalXY(nmap: vec4f) -> vec3f {
    var xy = nmap.wy * 2.0 - 1.0;
    return vec3f(xy, sqrt(1.0 - clamp(dot(xy, xy), 0.0, 1.0)));
}

#endif
`;

var detailModesPS = /* wgsl */ `

#ifndef _DETAILMODES_INCLUDED_
#define _DETAILMODES_INCLUDED_

fn detailMode_mul(c1: vec3f, c2: vec3f) -> vec3f {
    return c1 * c2;
}

fn detailMode_add(c1: vec3f, c2: vec3f) -> vec3f {
    return c1 + c2;
}

// https://en.wikipedia.org/wiki/Blend_modes#Screen
fn detailMode_screen(c1: vec3f, c2: vec3f) -> vec3f {
    return 1.0 - (1.0 - c1)*(1.0 - c2);
}

// https://en.wikipedia.org/wiki/Blend_modes#Overlay
fn detailMode_overlay(c1: vec3f, c2: vec3f) -> vec3f {
    return mix(1.0 - 2.0 * (1.0 - c1)*(1.0 - c2), 2.0 * c1 * c2, step(c1, vec3f(0.5)));
}

fn detailMode_min(c1: vec3f, c2: vec3f) -> vec3f {
    return min(c1, c2);
}

fn detailMode_max(c1: vec3f, c2: vec3f) -> vec3f {
    return max(c1, c2);
}

#endif
`;

var diffusePS = /* wgsl */ `
uniform material_diffuse: vec3f;

#ifdef STD_DIFFUSEDETAIL_TEXTURE
    #include "detailModesPS"
#endif

fn getAlbedo() {
    dAlbedo = uniform.material_diffuse.rgb;

    #ifdef STD_DIFFUSE_TEXTURE
        var albedoTexture: vec3f = {STD_DIFFUSE_TEXTURE_DECODE}(textureSampleBias({STD_DIFFUSE_TEXTURE_NAME}, {STD_DIFFUSE_TEXTURE_NAME}Sampler, {STD_DIFFUSE_TEXTURE_UV}, uniform.textureBias)).{STD_DIFFUSE_TEXTURE_CHANNEL};

        #ifdef STD_DIFFUSEDETAIL_TEXTURE
            var albedoDetail: vec3f = {STD_DIFFUSEDETAIL_TEXTURE_DECODE}(textureSampleBias({STD_DIFFUSEDETAIL_TEXTURE_NAME}, {STD_DIFFUSEDETAIL_TEXTURE_NAME}Sampler, {STD_DIFFUSEDETAIL_TEXTURE_UV}, uniform.textureBias)).{STD_DIFFUSEDETAIL_TEXTURE_CHANNEL};
            albedoTexture = detailMode_{STD_DIFFUSEDETAIL_DETAILMODE}(albedoTexture, albedoDetail);
        #endif

        dAlbedo = dAlbedo * albedoTexture;
    #endif

    #ifdef STD_DIFFUSE_VERTEX
        dAlbedo = dAlbedo * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}));
    #endif
}
`;

var emissivePS = /* wgsl */ `
uniform material_emissive: vec3f;
uniform material_emissiveIntensity: f32;

fn getEmission() {
    dEmission = uniform.material_emissive * uniform.material_emissiveIntensity;

    #ifdef STD_EMISSIVE_TEXTURE
    dEmission *= {STD_EMISSIVE_TEXTURE_DECODE}(textureSampleBias({STD_EMISSIVE_TEXTURE_NAME}, {STD_EMISSIVE_TEXTURE_NAME}Sampler, {STD_EMISSIVE_TEXTURE_UV}, uniform.textureBias)).{STD_EMISSIVE_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_EMISSIVE_VERTEX
    dEmission = dEmission * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}));
    #endif
}
`;

var encodePS = /* wgsl */ `
fn encodeLinear(source: vec3f) -> vec4f {
    return vec4f(source, 1.0);
}

fn encodeGamma(source: vec3f) -> vec4f {
    return vec4f(pow(source + vec3f(0.0000001), vec3f(1.0 / 2.2)), 1.0);
}

fn encodeRGBM(source: vec3f) -> vec4f {
    var color: vec3f = pow(source, vec3f(0.5));
    color *= 1.0 / 8.0;

    var a: f32 = saturate(max(max(color.r, color.g), max(color.b, 1.0 / 255.0)));
    a = ceil(a * 255.0) / 255.0;

    color /= a;
    return vec4f(color, a);
}

fn encodeRGBP(source: vec3f) -> vec4f {
    // convert incoming linear to gamma(ish)
    var gamma: vec3f = pow(source, vec3f(0.5));

    // calculate the maximum component clamped to 1..8
    var maxVal: f32 = min(8.0, max(1.0, max(gamma.x, max(gamma.y, gamma.z))));

    // calculate storage factor
    var v: f32 = 1.0 - ((maxVal - 1.0) / 7.0);

    // round the value for storage in 8bit channel
    v = ceil(v * 255.0) / 255.0;

    return vec4f(gamma / (-v * 7.0 + 8.0), v);
}

fn encodeRGBE(source: vec3f) -> vec4f {
    var maxVal: f32 = max(source.x, max(source.y, source.z));
    if (maxVal < 1e-32) {
        return vec4f(0.0, 0.0, 0.0, 0.0);
    } else {
        var e: f32 = ceil(log2(maxVal));
        return vec4f(source / pow(2.0, e), (e + 128.0) / 255.0);
    }
}
`;

var endPS = /* wgsl */ `
    var finalRgb: vec3f = combineColor(litArgs_albedo, litArgs_sheen_specularity, litArgs_clearcoat_specularity);

    finalRgb = finalRgb + litArgs_emission;
    finalRgb = addFog(finalRgb);
    finalRgb = toneMap(finalRgb);
    finalRgb = gammaCorrectOutput(finalRgb);
    output.color = vec4f(finalRgb, output.color.a);
`;

var envAtlasPS = /* wgsl */ `

#ifndef _ENVATLAS_INCLUDED_
#define _ENVATLAS_INCLUDED_

// the envAtlas is fixed at 512 pixels. every equirect is generated with 1 pixel boundary.
const atlasSize : f32 = 512.0;
const seamSize : f32 = 1.0 / atlasSize;

// map a normalized equirect UV to the given rectangle (taking 1 pixel seam into account).
fn mapUv(uv : vec2f, rect : vec4f) -> vec2f {
    return vec2f(mix(rect.x + seamSize, rect.x + rect.z - seamSize, uv.x),
                 mix(rect.y + seamSize, rect.y + rect.w - seamSize, uv.y));
}

// map a normalized equirect UV and roughness level to the correct atlas rect.
fn mapRoughnessUv(uv : vec2f, level : f32) -> vec2f {
    let t : f32 = 1.0 / exp2(level);
    return mapUv(uv, vec4f(0.0, 1.0 - t, t, t * 0.5));
}

// map shiny level UV
fn mapShinyUv(uv : vec2f, level : f32) -> vec2f {
    let t : f32 = 1.0 / exp2(level);
    return mapUv(uv, vec4f(1.0 - t, 1.0 - t, t, t * 0.5));
}

#endif
`;

var envProcPS = /* wgsl */ `
#ifdef LIT_SKYBOX_INTENSITY
    uniform skyboxIntensity : f32;
#endif

fn processEnvironment(color : vec3f) -> vec3f {
    #ifdef LIT_SKYBOX_INTENSITY
        return color * uniform.skyboxIntensity;
    #else
        return color;
    #endif
}
`;

var falloffInvSquaredPS = /* wgsl */ `
fn getFalloffWindow(lightRadius: f32, lightDir: vec3f) -> f32 {
    let sqrDist: f32 = dot(lightDir, lightDir);
    let invRadius: f32 = 1.0 / lightRadius;
    return square(saturate(1.0 - square(sqrDist * square(invRadius))));
}

fn getFalloffInvSquared(lightRadius: f32, lightDir: vec3f) -> f32 {
    let sqrDist: f32 = dot(lightDir, lightDir);
    var falloff: f32 = 1.0 / (sqrDist + 1.0);
    let invRadius: f32 = 1.0 / lightRadius;

    falloff = falloff * 16.0;
    falloff = falloff * square(saturate(1.0 - square(sqrDist * square(invRadius))));

    return falloff;
}
`;

var falloffLinearPS = /* wgsl */ `
fn getFalloffLinear(lightRadius: f32, lightDir: vec3f) -> f32 {
    let d: f32 = length(lightDir);
    return max(((lightRadius - d) / lightRadius), 0.0);
}
`;

// Chunk that allows us to store all 32bits of float in a single RGBA8 texture without any loss of
// precision. The float value is encoded to RGBA8 and decoded back to float. Used as a fallback
// for platforms that do not support float textures but need to render to a float texture (without
// filtering)
var floatAsUintPS = /* wgsl */ `

#ifndef FLOAT_AS_UINT
#define FLOAT_AS_UINT

// encode float value to RGBA8 representation (0.0-1.0 range)
fn float2uint(value: f32) -> vec4f {
    let intBits = bitcast<u32>(value);
    return vec4f(
        f32((intBits >> 24u) & 0xffu),
        f32((intBits >> 16u) & 0xffu),
        f32((intBits >> 8u) & 0xffu),
        f32(intBits & 0xffu)
    ) / 255.0;
}

// decode RGBA8 value to float
fn uint2float(value: vec4f) -> f32 {
    let rgba_u32 = vec4<u32>(value * 255.0);
    let intBits: u32 =
        (rgba_u32.r << 24u) |
        (rgba_u32.g << 16u) |
        (rgba_u32.b << 8u)  |
         rgba_u32.a;
    return bitcast<f32>(intBits);
}

// store a single float value in vec4, assuming either RGBA8 or float renderable texture
fn float2vec4(value: f32) -> vec4f {
    #if defined(CAPS_TEXTURE_FLOAT_RENDERABLE)
        return vec4f(value, 1.0, 1.0, 1.0);
    #else
        return float2uint(value);
    #endif
}

#endif // FLOAT_AS_UINT
`;

var fogPS = /* wgsl */ `

var<private> dBlendModeFogFactor : f32 = 1.0;

#if (FOG != NONE)
    uniform fog_color : vec3f;
    
    #if (FOG == LINEAR)
        uniform fog_start : f32;
        uniform fog_end : f32;
    #else
        uniform fog_density : f32;
    #endif
#endif

fn getFogFactor() -> f32 {

    let depth = pcPosition.z / pcPosition.w;

    var fogFactor : f32 = 0.0;

    #if (FOG == LINEAR)
        fogFactor = (uniform.fog_end - depth) / (uniform.fog_end - uniform.fog_start);
    #elif (FOG == EXP)
        fogFactor = exp(-depth * uniform.fog_density);
    #elif (FOG == EXP2)
        fogFactor = exp(-depth * depth * uniform.fog_density * uniform.fog_density);
    #endif

    return clamp(fogFactor, 0.0, 1.0);
}

fn addFog(color : vec3f) -> vec3f {
    #if (FOG != NONE)
        return mix(uniform.fog_color * dBlendModeFogFactor, color, getFogFactor());
    #else
        return color;
    #endif
}
`;

var fresnelSchlickPS = /* wgsl */ `
// Schlick's approximation
fn getFresnel(
        cosTheta: f32,
        gloss: f32,
        specularity: vec3f
    #if defined(LIT_IRIDESCENCE)
        , iridescenceFresnel: vec3f,
        iridescenceIntensity: f32
    #endif
) -> vec3f {
    let fresnel: f32 = pow(1.0 - saturate(cosTheta), 5.0);
    let glossSq: f32 = gloss * gloss;
    let ret: vec3f = specularity + (max(vec3f(glossSq), specularity) - specularity) * fresnel;

    #if defined(LIT_IRIDESCENCE)
        return mix(ret, iridescenceFresnel, iridescenceIntensity);
    #else
        return ret;
    #endif
}

fn getFresnelCC(cosTheta: f32) -> f32 {
    let fresnel: f32 = pow(1.0 - saturate(cosTheta), 5.0);
    return 0.04 + (1.0 - 0.04) * fresnel;
}`;

var fullscreenQuadVS = /* wgsl */ `
attribute vertex_position: vec2f;

varying vUv0: vec2f;

@vertex
fn vertexMain(input: VertexInput) -> VertexOutput {
    var output: VertexOutput;
    output.position = vec4f(input.vertex_position, 0.5, 1.0);
    output.vUv0 = input.vertex_position.xy * 0.5 + vec2f(0.5);
    return output;
}
`;

var gammaPS = /* wgsl */ `

#include "decodePS"

#if (GAMMA == SRGB)

    fn gammaCorrectInput(color: f32) -> f32 {
        return decodeGammaFloat(color);
    }

    fn gammaCorrectInputVec3(color: vec3f) -> vec3f {
        return decodeGamma3(color);
    }

    fn gammaCorrectInputVec4(color: vec4f) -> vec4f {
        return vec4f(decodeGamma3(color.xyz), color.w);
    }

    fn gammaCorrectOutput(color: vec3f) -> vec3f {
        return pow(color + 0.0000001, vec3f(1.0 / 2.2));
    }

#else // NONE

    fn gammaCorrectInput(color: f32) -> f32 {
        return color;
    }

    fn gammaCorrectInputVec3(color: vec3f) -> vec3f {
        return color;
    }

    fn gammaCorrectInputVec4(color: vec4f) -> vec4f {
        return color;
    }

    fn gammaCorrectOutput(color: vec3f) -> vec3f {
        return color;
    }

#endif
`;

var glossPS = /* wgsl */ `
#ifdef STD_GLOSS_CONSTANT
    uniform material_gloss: f32;
#endif

fn getGlossiness() {
    dGlossiness = 1.0;

    #ifdef STD_GLOSS_CONSTANT
    dGlossiness = dGlossiness * uniform.material_gloss;
    #endif

    #ifdef STD_GLOSS_TEXTURE
    dGlossiness = dGlossiness * textureSampleBias({STD_GLOSS_TEXTURE_NAME}, {STD_GLOSS_TEXTURE_NAME}Sampler, {STD_GLOSS_TEXTURE_UV}, uniform.textureBias).{STD_GLOSS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_GLOSS_VERTEX
    dGlossiness = dGlossiness * saturate(vVertexColor.{STD_GLOSS_VERTEX_CHANNEL});
    #endif

    #ifdef STD_GLOSS_INVERT
    dGlossiness = 1.0 - dGlossiness;
    #endif

    dGlossiness = dGlossiness + 0.0000001;
}
`;

var gsplatCenterVS = /* wgsl */ `
uniform matrix_model: mat4x4f;
uniform matrix_view: mat4x4f;
#ifndef GSPLAT_CENTER_NOPROJ
    uniform matrix_projection: mat4x4f;
#endif

// project the model space gaussian center to view and clip space
fn initCenter(modelCenter: vec3f, center: ptr<function, SplatCenter>) -> bool {
    let modelView: mat4x4f = uniform.matrix_view * uniform.matrix_model;
    let centerView: vec4f = modelView * vec4f(modelCenter, 1.0);

    #ifndef GSPLAT_CENTER_NOPROJ

        // early out if splat is behind the camera
        if (centerView.z > 0.0) {
            return false;
        }

        var centerProj: vec4f = uniform.matrix_projection * centerView;

        // ensure gaussians are not clipped by camera near and far
        centerProj.z = clamp(centerProj.z, 0.0, abs(centerProj.w));

        center.proj = centerProj;
        center.projMat00 = uniform.matrix_projection[0][0];

    #endif

    center.view = centerView.xyz / centerView.w;
    center.modelView = modelView;
    return true;
}
`;

var gsplatColorVS = /* wgsl */ `

var splatColor: texture_2d<f32>;

fn readColor(source: ptr<function, SplatSource>) -> vec4f {
    return textureLoad(splatColor, source.uv, 0);
}
`;

var gsplatCommonVS = /* wgsl */ `

#include "gsplatStructsVS"
#include "gsplatEvalSHVS"
#include "gsplatQuatToMat3VS"
#include "gsplatSourceFormatVS"
#include "gsplatSourceVS"
#include "gsplatCenterVS"
#include "gsplatCornerVS"
#include "gsplatOutputVS"

// modify the gaussian corner so it excludes gaussian regions with alpha less than 1/255
fn clipCorner(corner: ptr<function, SplatCorner>, alpha: f32) {
    let clip: f32 = min(1.0, sqrt(-log(1.0 / (255.0 * alpha))) / 2.0);
    corner.offset = corner.offset * clip;
    corner.uv = corner.uv * clip;
}

`;

var gsplatCompressedDataVS = /* wgsl */ `
var packedTexture: texture_2d<u32>;
var chunkTexture: texture_2d<f32>;

// work values
var<private> chunkDataA: vec4f;    // x: min_x, y: min_y, z: min_z, w: max_x
var<private> chunkDataB: vec4f;    // x: max_y, y: max_z, z: scale_min_x, w: scale_min_y
var<private> chunkDataC: vec4f;    // x: scale_min_z, y: scale_max_x, z: scale_max_y, w: scale_max_z
var<private> chunkDataD: vec4f;    // x: min_r, y: min_g, z: min_b, w: max_r
var<private> chunkDataE: vec4f;    // x: max_g, y: max_b, z: unused, w: unused
var<private> packedData: vec4u;    // x: position bits, y: rotation bits, z: scale bits, w: color bits

fn unpack111011(bits: u32) -> vec3f {
    return (vec3f((vec3<u32>(bits) >> vec3<u32>(21u, 11u, 0u)) & vec3<u32>(0x7ffu, 0x3ffu, 0x7ffu))) / vec3f(2047.0, 1023.0, 2047.0);
}

fn unpack8888(bits: u32) -> vec4f {
    return vec4f(
        f32((bits >> 24u) & 0xffu),
        f32((bits >> 16u) & 0xffu),
        f32((bits >> 8u)  & 0xffu),
        f32(bits         & 0xffu)
    ) / 255.0;
}

const norm_const: f32 = 1.0 / (sqrt(2.0) * 0.5);

fn unpackRotation(bits: u32) -> vec4f {
    let a = (f32((bits >> 20u) & 0x3ffu) / 1023.0 - 0.5) * norm_const;
    let b = (f32((bits >> 10u) & 0x3ffu) / 1023.0 - 0.5) * norm_const;
    let c = (f32(bits & 0x3ffu) / 1023.0 - 0.5) * norm_const;
    let m = sqrt(1.0 - (a * a + b * b + c * c));

    let mode = bits >> 30u;
    if (mode == 0u) { return vec4f(m, a, b, c); }
    if (mode == 1u) { return vec4f(a, m, b, c); }
    if (mode == 2u) { return vec4f(a, b, m, c); }
    return vec4f(a, b, c, m);
}

// read center
fn readCenter(source: ptr<function, SplatSource>) -> vec3f {
    let tex_size_u = textureDimensions(chunkTexture, 0);
    let w: u32 = tex_size_u.x / 5u;
    let chunkId: u32 = source.id / 256u;
    let chunkUV: vec2<i32> = vec2<i32>(i32((chunkId % w) * 5u), i32(chunkId / w));

    // read chunk and packed compressed data
    chunkDataA = textureLoad(chunkTexture, chunkUV + vec2<i32>(0, 0), 0);
    chunkDataB = textureLoad(chunkTexture, chunkUV + vec2<i32>(1, 0), 0);
    chunkDataC = textureLoad(chunkTexture, chunkUV + vec2<i32>(2, 0), 0);
    chunkDataD = textureLoad(chunkTexture, chunkUV + vec2<i32>(3, 0), 0);
    chunkDataE = textureLoad(chunkTexture, chunkUV + vec2<i32>(4, 0), 0);
    packedData = textureLoad(packedTexture, source.uv, 0);

    return mix(chunkDataA.xyz, vec3f(chunkDataA.w, chunkDataB.xy), unpack111011(packedData.x));
}

fn readColor(source: ptr<function, SplatSource>) -> vec4f {
    let r = unpack8888(packedData.w);
    return vec4f(mix(chunkDataD.xyz, vec3f(chunkDataD.w, chunkDataE.xy), r.rgb), r.w);
}

fn getRotation() -> vec4f {
    return unpackRotation(packedData.y);
}

fn getScale() -> vec3f {
    return exp(mix(vec3f(chunkDataB.zw, chunkDataC.x), chunkDataC.yzw, unpack111011(packedData.z)));
}

// given a rotation matrix and scale vector, compute 3d covariance A and B
fn readCovariance(source: ptr<function, SplatSource>, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {
    let rot = quatToMat3(getRotation());
    let scale = getScale();

    // M = S * R
    let M = transpose(mat3x3f(
        scale.x * rot[0],
        scale.y * rot[1],
        scale.z * rot[2]
    ));

    *covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));
    *covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));
}
`;

var gsplatCompressedSHVS = /* wgsl */ `
#if SH_BANDS > 0

var shTexture0: texture_2d<u32>;
var shTexture1: texture_2d<u32>;
var shTexture2: texture_2d<u32>;

fn unpack8888s(bits: u32) -> vec4f {
    let unpacked_u = (vec4<u32>(bits) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(0xffu);
    return vec4f(unpacked_u) * (8.0 / 255.0) - 4.0;
}

fn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 15>>, scale: ptr<function, f32>) {
    // read the sh coefficients
    let shData0: vec4<u32> = textureLoad(shTexture0, source.uv, 0);
    let shData1: vec4<u32> = textureLoad(shTexture1, source.uv, 0);
    let shData2: vec4<u32> = textureLoad(shTexture2, source.uv, 0);

    let r0 = unpack8888s(shData0.x);
    let r1 = unpack8888s(shData0.y);
    let r2 = unpack8888s(shData0.z);
    let r3 = unpack8888s(shData0.w);

    let g0 = unpack8888s(shData1.x);
    let g1 = unpack8888s(shData1.y);
    let g2 = unpack8888s(shData1.z);
    let g3 = unpack8888s(shData1.w);

    let b0 = unpack8888s(shData2.x);
    let b1 = unpack8888s(shData2.y);
    let b2 = unpack8888s(shData2.z);
    let b3 = unpack8888s(shData2.w);

    sh[0] =  vec3f(r0.x, g0.x, b0.x);
    sh[1] =  vec3f(r0.y, g0.y, b0.y);
    sh[2] =  vec3f(r0.z, g0.z, b0.z);
    sh[3] =  vec3f(r0.w, g0.w, b0.w);
    sh[4] =  vec3f(r1.x, g1.x, b1.x);
    sh[5] =  vec3f(r1.y, g1.y, b1.y);
    sh[6] =  vec3f(r1.z, g1.z, b1.z);
    sh[7] =  vec3f(r1.w, g1.w, b1.w);
    sh[8] =  vec3f(r2.x, g2.x, b2.x);
    sh[9] =  vec3f(r2.y, g2.y, b2.y);
    sh[10] = vec3f(r2.z, g2.z, b2.z);
    sh[11] = vec3f(r2.w, g2.w, b2.w);
    sh[12] = vec3f(r3.x, g3.x, b3.x);
    sh[13] = vec3f(r3.y, g3.y, b3.y);
    sh[14] = vec3f(r3.z, g3.z, b3.z);

    *scale = 1.0;
}

#endif
`;

var gsplatEvalSHVS = /* wgsl */ `
    #if SH_BANDS == 1
        const SH_COEFFS: i32 = 3;
    #elif SH_BANDS == 2
        const SH_COEFFS: i32 = 8;
    #elif SH_BANDS == 3
        const SH_COEFFS: i32 = 15;
    #else
        const SH_COEFFS: i32 = 0;
    #endif

    #if SH_BANDS > 0

    const SH_C1: f32 = 0.4886025119029199;

    #if SH_BANDS > 1
        const SH_C2_0: f32 = 1.0925484305920792;
        const SH_C2_1: f32 = -1.0925484305920792;
        const SH_C2_2: f32 = 0.31539156525252005;
        const SH_C2_3: f32 = -1.0925484305920792;
        const SH_C2_4: f32 = 0.5462742152960396;
    #endif

    #if SH_BANDS > 2
        const SH_C3_0: f32 = -0.5900435899266435;
        const SH_C3_1: f32 = 2.890611442640554;
        const SH_C3_2: f32 = -0.4570457994644658;
        const SH_C3_3: f32 = 0.3731763325901154;
        const SH_C3_4: f32 = -0.4570457994644658;
        const SH_C3_5: f32 = 1.445305721320277;
        const SH_C3_6: f32 = -0.5900435899266435;
    #endif

    // see https://github.com/graphdeco-inria/gaussian-splatting/blob/main/utils/sh_utils.py
    fn evalSH(sh: ptr<function, array<vec3f, SH_COEFFS>>, dir: vec3f) -> vec3f {
        let x = dir.x;
        let y = dir.y;
        let z = dir.z;

        // 1st degree
        var result = SH_C1 * (-sh[0] * y + sh[1] * z - sh[2] * x);

        #if SH_BANDS > 1
            // 2nd degree
            let xx = x * x;
            let yy = y * y;
            let zz = z * z;
            let xy = x * y;
            let yz = y * z;
            let xz = x * z;

            result = result + (
                sh[3] * (SH_C2_0 * xy) +
                sh[4] * (SH_C2_1 * yz) +
                sh[5] * (SH_C2_2 * (2.0 * zz - xx - yy)) +
                sh[6] * (SH_C2_3 * xz) +
                sh[7] * (SH_C2_4 * (xx - yy))
            );
        #endif

        #if SH_BANDS > 2
            // 3rd degree
            result = result + (
                sh[8]  * (SH_C3_0 * y * (3.0 * xx - yy)) +
                sh[9]  * (SH_C3_1 * xy * z) +
                sh[10] * (SH_C3_2 * y * (4.0 * zz - xx - yy)) +
                sh[11] * (SH_C3_3 * z * (2.0 * zz - 3.0 * xx - 3.0 * yy)) +
                sh[12] * (SH_C3_4 * x * (4.0 * zz - xx - yy)) +
                sh[13] * (SH_C3_5 * z * (xx - yy)) +
                sh[14] * (SH_C3_6 * x * (xx - 3.0 * yy))
            );
        #endif

        return result;
    }
    #endif
`;

var gsplatQuatToMat3VS = /* wgsl */ `
fn quatToMat3(R: vec4<f32>) -> mat3x3<f32> {
    let R2: vec4<f32> = R + R;
    let X: f32       = R2.x * R.w;
    let Y: vec4<f32> = R2.y * R;
    let Z: vec4<f32> = R2.z * R;
    let W: f32       = R2.w * R.w;

    return mat3x3<f32>(
        1.0 - Z.z - W,  Y.z + X,      Y.w - Z.x,
        Y.z - X,        1.0 - Y.y - W, Z.w + Y.x,
        Y.w + Z.x,      Z.w - Y.x,     1.0 - Y.y - Z.z
    );
}
`;

var gsplatSogsColorVS = /* wgsl */ `
var packedSh0: texture_2d<f32>;

uniform sh0_mins: f32;
uniform sh0_maxs: f32;

const SH_C0: f32 = 0.28209479177387814;

fn readColor(source: ptr<function, SplatSource>) -> vec4f {
    let clr = mix(vec3f(uniform.sh0_mins), vec3f(uniform.sh0_maxs), unpack111110(pack8888(textureLoad(packedSh0, source.uv, 0))));
    let alpha = f32(packedSample.z & 0xffu) / 255.0;
    return vec4f(vec3f(0.5) + clr.xyz * SH_C0, alpha);
}
`;

var gsplatSogsDataVS = /* wgsl */ `
#include "gsplatPackingPS"

var packedTexture: texture_2d<u32>;

uniform means_mins: vec3f;
uniform means_maxs: vec3f;

uniform scales_mins: f32;
uniform scales_maxs: f32;

var<private> packedSample: vec4<u32>;

// read the model-space center of the gaussian
fn readCenter(source: ptr<function, SplatSource>) -> vec3f {

    packedSample = textureLoad(packedTexture, source.uv, 0);

    let l = unpack8888(packedSample.x).xyz;
    let u = unpack8888(packedSample.y).xyz;
    let n = (l * 255.0 + u * 255.0 * 256.0) / 65535.0;
    let v = mix(uniform.means_mins, uniform.means_maxs, n);

    return sign(v) * (exp(abs(v)) - 1.0);
}

const norm: f32 = 2.0 / sqrt(2.0);

// sample covariance vectors
fn readCovariance(source: ptr<function, SplatSource>, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {
    let qdata = unpack8888(packedSample.z).xyz;
    let sdata = unpack101010(packedSample.w >> 2u);

    let qmode = packedSample.w & 0x3u;
    let abc = (qdata - 0.5) * norm;
    let d = sqrt(max(0.0, 1.0 - dot(abc, abc)));

    var quat: vec4f;
    if (qmode == 0u) {
        quat = vec4f(d, abc);
    } else if (qmode == 1u) {
        quat = vec4f(abc.x, d, abc.y, abc.z);
    } else if (qmode == 2u) {
        quat = vec4f(abc.x, abc.y, d, abc.z);
    } else {
        quat = vec4f(abc.x, abc.y, abc.z, d);
    }

    let rot = quatToMat3(quat);
    let scale = exp(mix(vec3f(uniform.scales_mins), vec3f(uniform.scales_maxs), sdata));

    // M = S * R
    let M = transpose(mat3x3f(
        scale.x * rot[0],
        scale.y * rot[1],
        scale.z * rot[2]
    ));

    *covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));
    *covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));
}
`;

var gsplatSogsSHVS = /* wgsl */ `
var packedShN: texture_2d<f32>;

uniform shN_mins: f32;
uniform shN_maxs: f32;

fn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, SH_COEFFS>>, scale: ptr<function, f32>) {
    // extract spherical harmonics palette index
    let t = vec2i(packedSample.xy & vec2u(255u));
    let n = t.x + t.y * 256;
    let u = (n % 64) * SH_COEFFS;
    let v = n / 64;

    // calculate offset into the centroids texture and read consecutive texels
    for (var i: i32 = 0; i < SH_COEFFS; i = i + 1) {
        sh[i] = mix(vec3f(uniform.shN_mins), vec3f(uniform.shN_maxs), unpack111110(pack8888(textureLoad(packedShN, vec2i(u + i, v), 0))));
    }

    *scale = 1.0;
}
`;

var gsplatSourceFormatVS = /* wgsl */ `
#if defined(GSPLAT_WORKBUFFER_DATA)
    // getting data from work buffer
    #include "gsplatWorkBufferVS"
#elif GSPLAT_COMPRESSED_DATA == true
    #include "gsplatCompressedDataVS"
    #if SH_BANDS > 0
        #include "gsplatCompressedSHVS"
    #endif
#elif GSPLAT_SOGS_DATA
    #include "gsplatSogsDataVS"
    #include "gsplatSogsColorVS"
    #if SH_BANDS > 0
        #include "gsplatSogsSHVS"
    #endif
#else
    #include "gsplatDataVS"
    #include "gsplatColorVS"
    #if SH_BANDS > 0
        #include "gsplatSHVS"
    #endif
#endif
`;

var gsplatStructsVS = /* wgsl */ `

// stores the source UV and order of the splat
struct SplatSource {
    order: u32,         // render order
    id: u32,            // splat id
    uv: vec2<i32>,      // splat uv
    cornerUV: vec2f,    // corner coordinates for this vertex of the gaussian (-1, -1)..(1, 1)
}

// stores the camera and clip space position of the gaussian center
struct SplatCenter {
    view: vec3f,          // center in view space
    proj: vec4f,          // center in clip space
    modelView: mat4x4f,   // model-view matrix
    projMat00: f32,       // elememt [0][0] of the projection matrix
}

// stores the offset from center for the current gaussian
struct SplatCorner {
    offset: vec2f,        // corner offset from center in clip space
    uv: vec2f,            // corner uv
    #if GSPLAT_AA
        aaFactor: f32, // for scenes generated with antialiasing
    #endif
}

`;

var gsplatCornerVS = /* wgsl */ `
uniform viewport: vec2f;                  // viewport dimensions
uniform camera_params: vec4f;             // 1 / far, far, near, isOrtho

// calculate the clip-space offset from the center for this gaussian
fn initCornerCov(source: ptr<function, SplatSource>, center: ptr<function, SplatCenter>, corner: ptr<function, SplatCorner>, covA: vec3f, covB: vec3f) -> bool {

    let Vrk = mat3x3f(
        vec3f(covA.x, covA.y, covA.z),
        vec3f(covA.y, covB.x, covB.y),
        vec3f(covA.z, covB.y, covB.z)
    );

    let focal = uniform.viewport.x * center.projMat00;

    let v = select(center.view.xyz, vec3f(0.0, 0.0, 1.0), uniform.camera_params.w == 1.0);
    let J1 = focal / v.z;
    let J2 = -J1 / v.z * v.xy;
    let J = mat3x3f(
        vec3f(J1, 0.0, J2.x),
        vec3f(0.0, J1, J2.y),
        vec3f(0.0, 0.0, 0.0)
    );

    let W = transpose(mat3x3f(center.modelView[0].xyz, center.modelView[1].xyz, center.modelView[2].xyz));
    let T = W * J;
    let cov = transpose(T) * Vrk * T;

    #if GSPLAT_AA
        // calculate AA factor
        let detOrig = cov[0][0] * cov[1][1] - cov[0][1] * cov[1][0]; // Using [0][1] * [1][0] as matrix might not be perfectly symmetric numerically
        let detBlur = (cov[0][0] + 0.3) * (cov[1][1] + 0.3) - cov[0][1] * cov[1][0];
        corner.aaFactor = sqrt(detOrig / detBlur);
    #endif

    let diagonal1 = cov[0][0] + 0.3;
    let offDiagonal = cov[0][1];
    let diagonal2 = cov[1][1] + 0.3;

    let mid = 0.5 * (diagonal1 + diagonal2);
    let radius = length(vec2f((diagonal1 - diagonal2) / 2.0, offDiagonal));
    let lambda1 = mid + radius;
    let lambda2 = max(mid - radius, 0.1);

    // Use the smaller viewport dimension to limit the kernel size relative to the screen resolution.
    let vmin = min(1024.0, min(uniform.viewport.x, uniform.viewport.y));

    let l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);
    let l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);

    // early-out gaussians smaller than 2 pixels
    if (l1 < 2.0 && l2 < 2.0) {
        return false;
    }

    let c = center.proj.ww / uniform.viewport;

    // cull against frustum x/y axes
    if (any((abs(center.proj.xy) - vec2f(max(l1, l2)) * c) > center.proj.ww)) {
        return false;
    }

    let diagonalVector = normalize(vec2f(offDiagonal, lambda1 - diagonal1));
    let v1 = l1 * diagonalVector;
    let v2 = l2 * vec2f(diagonalVector.y, -diagonalVector.x); // Swizzle

    corner.offset = (source.cornerUV.x * v1 + source.cornerUV.y * v2) * c;
    corner.uv = source.cornerUV;

    return true;
}

// calculate the clip-space offset from the center for this gaussian
fn initCorner(source: ptr<function, SplatSource>, center: ptr<function, SplatCenter>, corner: ptr<function, SplatCorner>) -> bool {
    var covA: vec3f;
    var covB: vec3f;
    readCovariance(source, &covA, &covB);
    return initCornerCov(source, center, corner, covA, covB);
}
`;

var gsplatDataVS = /* wgsl */ `
var transformA: texture_2d<u32>;
var transformB: texture_2d<f32>;

// work values
var<private> tAw: u32;

// read the model-space center of the gaussian
fn readCenter(source: ptr<function, SplatSource>) -> vec3f {
    // read transform data
    let tA: vec4<u32> = textureLoad(transformA, source.uv, 0);
    tAw = tA.w;
    return bitcast<vec3f>(tA.xyz);
}

fn unpackRotation(packed: vec3f) -> vec4f {
    return vec4f(packed.xyz, sqrt(max(0.0, 1.0 - dot(packed, packed))));
}

// sample covariance vectors
fn readCovariance(source: ptr<function, SplatSource>, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {
    let tB: vec4f = textureLoad(transformB, source.uv, 0);

    let rot: mat3x3f = quatToMat3(unpackRotation(vec3f(unpack2x16float(bitcast<u32>(tAw)), tB.w)).wxyz);
    let scale: vec3f = tB.xyz;

    // M = S * R
    let M = transpose(mat3x3f(
        scale.x * rot[0],
        scale.y * rot[1],
        scale.z * rot[2]
    ));

    *covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));
    *covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));
}
`;

var gsplatOutputVS = /* wgsl */ `

#include "tonemappingPS"
#include "decodePS"
#include "gammaPS"

// prepare the output color for the given gamma-space color
fn prepareOutputFromGamma(gammaColor: vec3f) -> vec3f {
    #if TONEMAP == NONE
        #if GAMMA == NONE
            // convert to linear space
            return decodeGamma3(gammaColor);
        #else 
            // output gamma space color directly
            return gammaColor;
        #endif
    #else
        // apply tonemapping in linear space and output to linear or
        // gamma (which is handled by gammaCorrectOutput)
        return gammaCorrectOutput(toneMap(decodeGamma3(gammaColor)));
    #endif
}
`;

var gsplatPS = /* wgsl */ `

#ifndef DITHER_NONE
    #include "bayerPS"
    #include "opacityDitherPS"
    varying id: f32;
#endif

#ifdef PICK_PASS
    #include "pickPS"
#endif

#if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)
    uniform alphaClip: f32;
#endif

#ifdef PREPASS_PASS
    varying vLinearDepth: f32;
    #include "floatAsUintPS"
#endif

// Fast approximate e^x based on https://nic.schraudolph.org/pubs/Schraudolph99.pdf
const EXP_A: f32      = 12102203.0;   // ≈ 2^23 / ln(2)
const EXP_BC_RMS: i32 = 1064866808;   // (127 << 23) - 60801 * 8
fn fastExp(x: f32) -> f32 {
    var i: i32 = i32(EXP_A * x) + EXP_BC_RMS;
    return bitcast<f32>(i);
}

varying gaussianUV: vec2f;
varying gaussianColor: vec4f;

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;

    let A: f32 = dot(gaussianUV, gaussianUV);
    if (A > 1.0) {
        discard;
        return output;
    }

    // evaluate alpha
    var alpha: f32 = fastExp(-A * 4.0) * gaussianColor.a;

    #if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)
        if (alpha < uniform.alphaClip) {
            discard;
            return output;
        }
    #endif

    #ifdef PICK_PASS

        output.color = getPickOutput();

    #elif SHADOW_PASS

        output.color = vec4f(0.0, 0.0, 0.0, 1.0);

    #elif PREPASS_PASS

        output.color = float2vec4(vLinearDepth);

    #else

        if (alpha < (1.0 / 255.0)) {
            discard;
            return output;
        }

        #ifndef DITHER_NONE
            opacityDither(&alpha, id * 0.013);
        #endif

        output.color = vec4f(input.gaussianColor.xyz * alpha, alpha);
    #endif

    return output;
}`;

var gsplatSHVS = /* wgsl */ `

#if SH_BANDS > 0

// unpack signed 11 10 11 bits
fn unpack111011s(bits: u32) -> vec3f {
    return (vec3f((vec3<u32>(bits) >> vec3<u32>(21u, 11u, 0u)) & vec3<u32>(0x7ffu, 0x3ffu, 0x7ffu)) / vec3f(2047.0, 1023.0, 2047.0)) * 2.0 - 1.0;
}

struct ScaleAndSH {
    scale: f32,
    a: vec3f,
    b: vec3f,
    c: vec3f
};

// fetch quantized spherical harmonic coefficients
fn fetchScale(t_in: vec4<u32>) -> ScaleAndSH {
    var result: ScaleAndSH;
    result.scale = bitcast<f32>(t_in.x);
    result.a = unpack111011s(t_in.y);
    result.b = unpack111011s(t_in.z);
    result.c = unpack111011s(t_in.w);
    return result;
}

struct SH {
    a: vec3f,
    b: vec3f,
    c: vec3f,
    d: vec3f
};

// fetch quantized spherical harmonic coefficients
fn fetch4(t_in: vec4<u32>) -> SH {
    var result: SH;
    result.a = unpack111011s(t_in.x);
    result.b = unpack111011s(t_in.y);
    result.c = unpack111011s(t_in.z);
    result.d = unpack111011s(t_in.w);
    return result;
}

fn fetch1(t_in: u32) -> vec3f {
    return unpack111011s(t_in);
}

#if SH_BANDS == 1
    var splatSH_1to3: texture_2d<u32>;

    fn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 3>>, scale: ptr<function, f32>) {
        let result = fetchScale(textureLoad(splatSH_1to3, source.uv, 0));
        *scale = result.scale;
        sh[0] = result.a;
        sh[1] = result.b;
        sh[2] = result.c;
    }
#elif SH_BANDS == 2
    var splatSH_1to3: texture_2d<u32>;
    var splatSH_4to7: texture_2d<u32>;
    var splatSH_8to11: texture_2d<u32>;

    fn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 8>>, scale: ptr<function, f32>) {
        let first: ScaleAndSH = fetchScale(textureLoad(splatSH_1to3, source.uv, 0));
        *scale = first.scale;
        sh[0] = first.a;
        sh[1] = first.b;
        sh[2] = first.c;

        let second: SH = fetch4(textureLoad(splatSH_4to7, source.uv, 0));
        sh[3] = second.a;
        sh[4] = second.b;
        sh[5] = second.c;
        sh[6] = second.d;

        sh[7] = fetch1(textureLoad(splatSH_8to11, source.uv, 0).x);
    }
#else
    var splatSH_1to3: texture_2d<u32>;
    var splatSH_4to7: texture_2d<u32>;
    var splatSH_8to11: texture_2d<u32>;
    var splatSH_12to15: texture_2d<u32>;

    fn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 15>>, scale: ptr<function, f32>) {
        let first: ScaleAndSH = fetchScale(textureLoad(splatSH_1to3, source.uv, 0));
        *scale = first.scale;
        sh[0] = first.a;
        sh[1] = first.b;
        sh[2] = first.c;

        let second: SH = fetch4(textureLoad(splatSH_4to7, source.uv, 0));
        sh[3] = second.a;
        sh[4] = second.b;
        sh[5] = second.c;
        sh[6] = second.d;

        let third: SH = fetch4(textureLoad(splatSH_8to11, source.uv, 0));
        sh[7] = third.a;
        sh[8] = third.b;
        sh[9] = third.c;
        sh[10] = third.d;

        let fourth: SH = fetch4(textureLoad(splatSH_12to15, source.uv, 0));
        sh[11] = fourth.a;
        sh[12] = fourth.b;
        sh[13] = fourth.c;
        sh[14] = fourth.d;
    }
#endif

#endif
`;

var gsplatSourceVS = /* wgsl */ `
attribute vertex_position: vec3f;         // xy: cornerUV, z: render order offset
attribute vertex_id_attrib: u32;          // render order base

uniform numSplats: u32;                   // total number of splats
var splatOrder: texture_2d<u32>;          // per-splat index to source gaussian

// initialize the splat source structure
fn initSource(source: ptr<function, SplatSource>) -> bool {
    let w: u32 = textureDimensions(splatOrder, 0).x;

    // calculate splat order
    source.order = vertex_id_attrib + u32(vertex_position.z);

    // return if out of range (since the last block of splats may be partially full)
    if (source.order >= uniform.numSplats) {
        return false;
    }

    let orderUV = vec2i(vec2u(source.order % w, source.order / w));

    // read splat id
    source.id = textureLoad(splatOrder, orderUV, 0).r;

    // map id to uv
    source.uv = vec2i(vec2u(source.id % w, source.id / w));

    // get the corner
    source.cornerUV = vertex_position.xy;

    return true;
}
`;

var gsplatVS = /* wgsl */ `
#include "gsplatCommonVS"

varying gaussianUV: vec2f;
varying gaussianColor: vec4f;

#ifndef DITHER_NONE
    varying id: f32;
#endif

const discardVec: vec4f = vec4f(0.0, 0.0, 2.0, 1.0);

#ifdef PREPASS_PASS
    varying vLinearDepth: f32;
#endif

@vertex
fn vertexMain(input: VertexInput) -> VertexOutput {
    var output: VertexOutput;

    // read gaussian details
    var source: SplatSource;
    if (!initSource(&source)) {
        output.position = discardVec;
        return output;
    }

    let modelCenter: vec3f = readCenter(&source);

    var center: SplatCenter;
    if (!initCenter(modelCenter, &center)) {
        output.position = discardVec;
        return output;
    }

    // project center to screen space
    var corner: SplatCorner;
    if (!initCorner(&source, &center, &corner)) {
        output.position = discardVec;
        return output;
    }

    // read color
    var clr: vec4f = readColor(&source);

    #if GSPLAT_AA
        // apply AA compensation
        clr.a = clr.a * corner.aaFactor;
    #endif

    // evaluate spherical harmonics
    #if SH_BANDS > 0
        // calculate the model-space view direction
        let modelView3x3 = mat3x3f(center.modelView[0].xyz, center.modelView[1].xyz, center.modelView[2].xyz);
        let dir = normalize(center.view * modelView3x3);

        // read sh coefficients
        var sh: array<vec3f, SH_COEFFS>;
        var scale: f32;
        readSHData(&source, &sh, &scale);

        // evaluate
        clr = vec4f(clr.xyz + evalSH(&sh, dir) * scale, clr.a);
    #endif

    clipCorner(&corner, clr.w);

    // write output
    output.position = center.proj + vec4f(corner.offset, 0.0, 0.0);
    output.gaussianUV = corner.uv;
    output.gaussianColor = vec4f(prepareOutputFromGamma(max(clr.xyz, vec3f(0.0))), clr.w);

    #ifndef DITHER_NONE
        output.id = f32(source.id);
    #endif

    #ifdef PREPASS_PASS
        output.vLinearDepth = -center.view.z;
    #endif

    return output;
}
`;

var gsplatWorkBufferVS = /* wgsl */ `
var center: texture_2d<f32>;
var covA: texture_2d<f32>;
var covB: texture_2d<f32>;
var splatColor: texture_2d<f32>;

// read the model-space center of the gaussian
fn readCenter(source: ptr<function, SplatSource>) -> vec3f {
    return textureLoad(center, source.uv, 0).xyz;
}

// sample covariance vectors
fn readCovariance(source: ptr<function, SplatSource>, cov_A: ptr<function, vec3f>, cov_B: ptr<function, vec3f>) {
    *cov_A = textureLoad(covA, source.uv, 0).xyz;
    *cov_B = textureLoad(covB, source.uv, 0).xyz;
}

fn readColor(source: ptr<function, SplatSource>) -> vec4f {
    return textureLoad(splatColor, source.uv, 0);
}   
`;

// A simple vertex shader used to render a quad, which requires 'vec2 aPosition' in the vertex
// buffer, and generates uv coordinates uv0 for use in the fragment shader.
var quadVS = /* wgsl */ `
    attribute aPosition: vec2f;
    varying uv0: vec2f;
    @vertex fn vertexMain(input: VertexInput) -> VertexOutput {
        var output: VertexOutput;
        output.position = vec4f(input.aPosition, 0.0, 1.0);
        output.uv0 = getImageEffectUV((input.aPosition + 1.0) * 0.5);
        return output;
    }
`;

var indirectCoreCS = /* wgsl */ `
// Indirect indexed draw call parameters
struct DrawIndexedIndirectArgs {
    indexCount: u32,        // Number of indices to draw
    instanceCount: u32,     // Number of instances to draw
    firstIndex: u32,        // Index of the first index in the index buffer
    baseVertex: i32,        // Offset added to each index before fetching vertex
    firstInstance: u32      // First instance ID
};

// Indirect non-indexed draw call parameters
struct DrawIndirectArgs {
    vertexCount: u32,       // Number of vertices to draw
    instanceCount: u32,     // Number of instances to draw
    firstVertex: u32,       // Index of the first vertex
    firstInstance: u32,     // First instance ID
    _pad: u32               // Padding to match indexed size (unused)
};
`;

var immediateLinePS = /* wgsl */ `
    #include "gammaPS"
    varying color: vec4f;
    @fragment
    fn fragmentMain(input : FragmentInput) -> FragmentOutput {
        var output: FragmentOutput;
        output.color = vec4f(gammaCorrectOutput(decodeGamma3(input.color.rgb)), input.color.a);
        return output;
    }
`;

var immediateLineVS = /* wgsl */ `
    attribute vertex_position: vec4f;
    attribute vertex_color: vec4f;
    uniform matrix_model: mat4x4f;
    uniform matrix_viewProjection: mat4x4f;
    varying color: vec4f;
    @vertex
    fn vertexMain(input : VertexInput) -> VertexOutput {
        var output : VertexOutput;
        output.color = input.vertex_color;
        output.position = uniform.matrix_viewProjection * uniform.matrix_model * input.vertex_position;
        return output;
    }
`;

var iridescenceDiffractionPS = /* wgsl */ `
uniform material_iridescenceRefractionIndex: f32;

fn iridescence_iorToFresnelScalar(transmittedIor: f32, incidentIor: f32) -> f32 {
    return pow((transmittedIor - incidentIor) / (transmittedIor + incidentIor), 2.0);
}

fn iridescence_iorToFresnelVec3(transmittedIor: vec3f, incidentIor: f32) -> vec3f {
    return pow((transmittedIor - vec3f(incidentIor)) / (transmittedIor + vec3f(incidentIor)), vec3f(2.0));
}

fn iridescence_fresnelToIor(f0: vec3f) -> vec3f {
    let sqrtF0: vec3f = sqrt(f0);
    return (vec3f(1.0) + sqrtF0) / (vec3f(1.0) - sqrtF0);
}

const XYZ_TO_REC709: mat3x3f = mat3x3f(
    vec3f(3.2404542, -1.5371385, -0.4985314),
    vec3f(-0.9692660,  1.8760108,  0.0415560),
    vec3f(0.0556434, -0.2040259,  1.0572252)
);

fn iridescence_sensitivity(opd: f32, shift: vec3f) -> vec3f {
    let PI: f32 = 3.141592653589793;
    let phase: f32 = 2.0 * PI * opd * 1.0e-9;
    const val: vec3f = vec3f(5.4856e-13, 4.4201e-13, 5.2481e-13);
    const pos: vec3f = vec3f(1.6810e+06, 1.7953e+06, 2.2084e+06);
    const var_: vec3f = vec3f(4.3278e+09, 9.3046e+09, 6.6121e+09);

    var xyz: vec3f = val * sqrt(2.0 * PI * var_) * cos(pos * phase + shift) * exp(-pow(phase, 2.0) * var_);
    xyz.x = xyz.x + 9.7470e-14 * sqrt(2.0 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(-4.5282e+09 * pow(phase, 2.0));
    xyz = xyz / vec3f(1.0685e-07);

    return XYZ_TO_REC709 * xyz;
}

fn iridescence_fresnelScalar(cosTheta: f32, f0: f32) -> f32 {
    let x: f32 = clamp(1.0 - cosTheta, 0.0, 1.0);
    let x2: f32 = x * x;
    let x5: f32 = x * x2 * x2;
    return f0 + (1.0 - f0) * x5;
}

fn iridescence_fresnelVec3(cosTheta: f32, f0: vec3f) -> vec3f {
    let x: f32 = clamp(1.0 - cosTheta, 0.0, 1.0);
    let x2: f32 = x * x;
    let x5: f32 = x * x2 * x2;
    return f0 + (vec3f(1.0) - f0) * x5;
}

fn calcIridescence(outsideIor: f32, cosTheta: f32, base_f0: vec3f, iridescenceThickness: f32) -> vec3f {
    let PI: f32 = 3.141592653589793;

    let iridescenceIor: f32 = mix(outsideIor, uniform.material_iridescenceRefractionIndex, smoothstep(0.0, 0.03, iridescenceThickness));
    let sinTheta2Sq: f32 = pow(outsideIor / iridescenceIor, 2.0) * (1.0 - pow(cosTheta, 2.0));
    let cosTheta2Sq: f32 = 1.0 - sinTheta2Sq;

    if (cosTheta2Sq < 0.0) {
        return vec3f(1.0);
    }

    let cosTheta2: f32 = sqrt(cosTheta2Sq);

    let r0: f32 = iridescence_iorToFresnelScalar(iridescenceIor, outsideIor);
    let r12: f32 = iridescence_fresnelScalar(cosTheta, r0);
    let r21: f32 = r12;
    let t121: f32 = 1.0 - r12;

    let phi12: f32 = select(0.0, PI, iridescenceIor < outsideIor);
    let phi21: f32 = PI - phi12;

    let baseIor: vec3f = iridescence_fresnelToIor(base_f0 + vec3f(0.0001));
    let r1: vec3f = iridescence_iorToFresnelVec3(baseIor, iridescenceIor);
    let r23: vec3f = iridescence_fresnelVec3(cosTheta2, r1);

    let phi23: vec3f = select(vec3f(0.0), vec3f(PI), baseIor < vec3f(iridescenceIor));
    let opd: f32 = 2.0 * iridescenceIor * iridescenceThickness * cosTheta2;
    let phi: vec3f = vec3f(phi21) + phi23; // Promote scalar phi21 to vec3f

    let r123Sq: vec3f = clamp(vec3f(r12) * r23, vec3f(1e-5), vec3f(0.9999));
    let r123: vec3f = sqrt(r123Sq);
    let rs: vec3f = pow(vec3f(t121), vec3f(2.0)) * r23 / (vec3f(1.0) - r123Sq);

    let c0: vec3f = vec3f(r12) + rs;
    var i_irid: vec3f = c0;

    var cm: vec3f = rs - vec3f(t121);

    cm = cm * r123;
    let sm1: vec3f = 2.0 * iridescence_sensitivity(1.0 * opd, 1.0 * phi);
    i_irid = i_irid + cm * sm1;

    cm = cm * r123;
    let sm2: vec3f = 2.0 * iridescence_sensitivity(2.0 * opd, 2.0 * phi);
    i_irid = i_irid + cm * sm2;

    return max(i_irid, vec3f(0.0));
}

fn getIridescenceDiffraction(cosTheta: f32, specularity: vec3f, iridescenceThickness: f32) -> vec3f {
    return calcIridescence(1.0, cosTheta, specularity, iridescenceThickness);
}
`;

var iridescencePS = /* wgsl */ `
#ifdef STD_IRIDESCENCE_CONSTANT
    uniform material_iridescence: f32;
#endif

fn getIridescence() {
    var iridescence = 1.0;

    #ifdef STD_IRIDESCENCE_CONSTANT
    iridescence = iridescence * uniform.material_iridescence;
    #endif

    #ifdef STD_IRIDESCENCE_TEXTURE
    iridescence = iridescence * textureSampleBias({STD_IRIDESCENCE_TEXTURE_NAME}, {STD_IRIDESCENCE_TEXTURE_NAME}Sampler, {STD_IRIDESCENCE_TEXTURE_UV}, uniform.textureBias).{STD_IRIDESCENCE_TEXTURE_CHANNEL};
    #endif

    dIridescence = iridescence; 
}
`;

var iridescenceThicknessPS = /* wgsl */ `
uniform material_iridescenceThicknessMax: f32;

#ifdef STD_IRIDESCENCETHICKNESS_TEXTURE
    uniform material_iridescenceThicknessMin: f32;
#endif

fn getIridescenceThickness() {

    #ifdef STD_IRIDESCENCETHICKNESS_TEXTURE
        var blend: f32 = textureSampleBias({STD_IRIDESCENCETHICKNESS_TEXTURE_NAME}, {STD_IRIDESCENCETHICKNESS_TEXTURE_NAME}Sampler, {STD_IRIDESCENCETHICKNESS_TEXTURE_UV}, uniform.textureBias).{STD_IRIDESCENCETHICKNESS_TEXTURE_CHANNEL};
        var iridescenceThickness: f32 = mix(uniform.material_iridescenceThicknessMin, uniform.material_iridescenceThicknessMax, blend);
    #else
        var iridescenceThickness: f32 = uniform.material_iridescenceThicknessMax;
    #endif

    dIridescenceThickness = iridescenceThickness; 
}
`;

var iorPS = /* wgsl */ `
#ifdef STD_IOR_CONSTANT
    uniform material_refractionIndex: f32;
#endif

fn getIor() {
#ifdef STD_IOR_CONSTANT
    dIor = uniform.material_refractionIndex;
#else
    dIor = 1.0 / 1.5;
#endif
}
`;

// uniforms for a light with index {i}, driven by defines
var lightDeclarationPS = /* wgsl */ `
#if defined(LIGHT{i})

    uniform light{i}_color: vec3f;

    #if LIGHT{i}TYPE == DIRECTIONAL
        uniform light{i}_direction: vec3f;
    #else

        #define LIT_CODE_LIGHTS_POINT // include functionality for point lights

        uniform light{i}_position: vec3f;
        uniform light{i}_radius: f32;

        #if LIGHT{i}TYPE == SPOT

            #define LIT_CODE_LIGHTS_SPOT // include functionality for spot lights

            uniform light{i}_direction: vec3f;
            uniform light{i}_innerConeAngle: f32;
            uniform light{i}_outerConeAngle: f32;
        #endif
    #endif

    // area lights
    #if LIGHT{i}SHAPE != PUNCTUAL

        #define LIT_CODE_FALLOFF_SQUARED // include functionality for inverse squared falloff

        #if LIGHT{i}TYPE == DIRECTIONAL
            uniform light{i}_position: vec3f;
        #endif
        uniform light{i}_halfWidth: vec3f;
        uniform light{i}_halfHeight: vec3f;
    #else

        #if LIGHT{i}FALLOFF == LINEAR
            #define LIT_CODE_FALLOFF_LINEAR // include functionality for linear falloff
        #endif

        #if LIGHT{i}FALLOFF == INVERSESQUARED
            #define LIT_CODE_FALLOFF_SQUARED // include functionality for inverse squared falloff
        #endif

    #endif

    // shadow casting
    #if defined(LIGHT{i}CASTSHADOW)

        uniform light{i}_shadowMatrix: mat4x4f;
        uniform light{i}_shadowIntensity: f32;
        uniform light{i}_shadowParams: vec4f; // width, height, bias, radius

        #if LIGHT{i}SHADOWTYPE == PCSS_32F
            uniform light{i}_shadowSearchArea: f32;
            uniform light{i}_cameraParams: vec4f;
            #if LIGHT{i}TYPE == DIRECTIONAL
                uniform light{i}_softShadowParams: vec4f;
            #endif
        #endif

        // directional (cascaded) shadows
        #if LIGHT{i}TYPE == DIRECTIONAL
            uniform light{i}_shadowMatrixPalette: array<mat4x4f, 4>;
            uniform light{i}_shadowCascadeDistances: vec4f;
            uniform light{i}_shadowCascadeCount: i32;
            uniform light{i}_shadowCascadeBlend: f32;
        #endif

        #if LIGHT{i}TYPE == OMNI
            NOT SUPPORTED
/*            #if defined(LIGHT{i}SHADOW_PCF)
                uniform samplerCubeShadow light{i}_shadowMap;
            #else
                uniform samplerCube light{i}_shadowMap;
            #endif
*/            
        #else
            #if defined(LIGHT{i}SHADOW_PCF)
                var light{i}_shadowMap: texture_depth_2d;
                var light{i}_shadowMapSampler: sampler_comparison;
            #else
                var light{i}_shadowMap: texture_2d<f32>;
                var light{i}_shadowMapSampler: sampler;
            #endif
        #endif

    #endif

    // cookie
    #if defined(LIGHT{i}COOKIE)

        #define LIT_CODE_COOKIE // include functionality for cookies

        #if LIGHT{i}TYPE == OMNI
            NOT SUPPORTED
/*        
            uniform samplerCube light{i}_cookie;
            uniform float light{i}_cookieIntensity;
            #if !defined(LIGHT{i}CASTSHADOW)
                uniform mat4 light{i}_shadowMatrix;
            #endif
*/
        #endif

        #if LIGHT{i}TYPE == SPOT
            NOT SUPPORTED
/*            uniform sampler2D light{i}_cookie;
            uniform float light{i}_cookieIntensity;
            #if !defined(LIGHT{i}CASTSHADOW)
                uniform mat4 light{i}_shadowMatrix;
            #endif
            #if defined(LIGHT{i}COOKIE_TRANSFORM)
                uniform vec4 light{i}_cookieMatrix;
                uniform vec2 light{i}_cookieOffset;
            #endif
*/
        #endif
    #endif
#endif
`;

var lightDiffuseLambertPS = /* wgsl */ `
fn getLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f) -> f32 {
    return max(dot(worldNormal, -lightDirNorm), 0.0);
}
`;

var lightDirPointPS = /* wgsl */ `
fn evalOmniLight(lightPosW: vec3f) -> vec3f {
    return vPositionW - lightPosW;
}
`;

// evaluation of a light with index {i}, driven by defines
var lightEvaluationPS = /* glsl */ `
#if defined(LIGHT{i})
    evaluateLight{i}(
        #if defined(LIT_IRIDESCENCE)
            iridescenceFresnel
        #endif
    );
#endif
`;

// functions used to evaluate the light
var lightFunctionLightPS = /* wgsl */ `
#if defined(LIGHT{i})

fn evaluateLight{i}(
    #if defined(LIT_IRIDESCENCE)
        iridescenceFresnel: vec3f
    #endif
) {
    // light color
    var lightColor: vec3f = uniform.light{i}_color;

    #if LIGHT{i}TYPE == DIRECTIONAL && !defined(LIT_SHADOW_CATCHER)
        // early return if the light color is black (used by shadow catcher - this way this light is very cheap)
        if (all(lightColor == vec3f(0.0, 0.0, 0.0))) {
            return;
        }
    #endif

    #if LIGHT{i}TYPE == DIRECTIONAL // directional light

        dLightDirNormW = uniform.light{i}_direction;
        dAtten = 1.0;

    #else // omni or spot light

        var lightDirW: vec3f = evalOmniLight(uniform.light{i}_position);
        dLightDirNormW = normalize(lightDirW);

        // cookie attenuation
        #if defined(LIGHT{i}COOKIE)

            #if LIGHT{i}TYPE == SPOT
                #ifdef LIGHT{i}COOKIE_FALLOFF
                    #ifdef LIGHT{i}COOKIE_TRANSFORM
                        var cookieAttenuation: vec3f = getCookie2DXform(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity, uniform.light{i}_cookieMatrix, uniform.light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};
                    #else
                        var cookieAttenuation: vec3f = getCookie2D(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};
                    #endif
                #else
                    #ifdef LIGHT{i}COOKIE_TRANSFORM
                        var cookieAttenuation: vec3f = getCookie2DClipXform(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity, uniform.light{i}_cookieMatrix, uniform.light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};
                    #else
                        var cookieAttenuation: vec3f = getCookie2DClip(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};
                    #endif
                #endif
            #endif

            #if LIGHT{i}TYPE == OMNI
                var cookieAttenuation: vec3f = getCookieCube(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};
            #endif

            // multiply light color by the cookie attenuation
            lightColor = lightColor * cookieAttenuation;

        #endif

        // distance falloff
        #if LIGHT{i}SHAPE == PUNCTUAL
            #if LIGHT{i}FALLOFF == LINEAR
                dAtten = getFalloffLinear(uniform.light{i}_radius, lightDirW);
            #else
                dAtten = getFalloffInvSquared(uniform.light{i}_radius, lightDirW);
            #endif
        #else
            // non punctual lights only gets the range window here
            dAtten = getFalloffWindow(uniform.light{i}_radius, lightDirW);
        #endif

        // spot light angle falloff
        #if LIGHT{i}TYPE == SPOT
            #if !defined(LIGHT{i}COOKIE) || defined(LIGHT{i}COOKIE_FALLOFF)
                dAtten = dAtten * getSpotEffect(uniform.light{i}_direction, uniform.light{i}_innerConeAngle, uniform.light{i}_outerConeAngle, dLightDirNormW);
            #endif
        #endif
    #endif

    if (dAtten < 0.00001) {
        return;
    }

    // evaluate area light values
    #if LIGHT{i}SHAPE != PUNCTUAL
        #if LIGHT{i}SHAPE == RECT
            calcRectLightValues(uniform.light{i}_position, uniform.light{i}_halfWidth, uniform.light{i}_halfHeight);
        #elif LIGHT{i}SHAPE == DISK
            calcDiskLightValues(uniform.light{i}_position, uniform.light{i}_halfWidth, uniform.light{i}_halfHeight);
        #elif LIGHT{i}SHAPE == SPHERE
            calcSphereLightValues(uniform.light{i}_position, uniform.light{i}_halfWidth, uniform.light{i}_halfHeight);
        #endif
    #endif

    // diffuse lighting - LTC lights do not mix diffuse lighting into attenuation that affects specular
    #if LIGHT{i}SHAPE != PUNCTUAL

        // attenDiffuse - separate diffuse attenuation for non-punctual light sources

        #if LIGHT{i}TYPE == DIRECTIONAL
            // NB: A better approximation perhaps using wrap lighting could be implemented here
            var attenDiffuse: f32 = getLightDiffuse(litArgs_worldNormal, dViewDirW, dLightDirNormW);
        #else
            // 16.0 is a constant that is in getFalloffInvSquared()
            #if LIGHT{i}SHAPE == RECT
                var attenDiffuse: f32 = getRectLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;
            #elif LIGHT{i}SHAPE == DISK
                var attenDiffuse: f32 = getDiskLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;
            #elif LIGHT{i}SHAPE == SPHERE
                var attenDiffuse: f32 = getSphereLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;
            #endif
        #endif
    #else
        // one parameter is unused for punctual lights
        dAtten = dAtten * getLightDiffuse(litArgs_worldNormal, vec3(0.0), dLightDirNormW);
    #endif

    // apply the shadow attenuation
    #ifdef LIGHT{i}CASTSHADOW

        #if LIGHT{i}TYPE == DIRECTIONAL
            var shadow: f32 = getShadow{i}(vec3(0.0));
        #else
            var shadow: f32 = getShadow{i}(lightDirW);
        #endif

        // Apply shadow intensity to the shadow value
        shadow = mix(1.0, shadow, uniform.light{i}_shadowIntensity);

        dAtten = dAtten * shadow;

        #if defined(LIT_SHADOW_CATCHER) && LIGHT{i}TYPE == DIRECTIONAL
            // accumulate shadows for directional lights
            dShadowCatcher = dShadowCatcher * shadow;
        #endif            

    #endif

    #if LIGHT{i}SHAPE != PUNCTUAL
        // area light - they do not mix diffuse lighting into specular attenuation
        #ifdef LIT_SPECULAR
            dDiffuseLight = dDiffuseLight + (((attenDiffuse * dAtten) * lightColor) * (1.0 - dLTCSpecFres));
        #else
            dDiffuseLight = dDiffuseLight + ((attenDiffuse * dAtten) * lightColor);
        #endif                        
    #else
        // punctual light
        #if defined(AREA_LIGHTS) && defined(LIT_SPECULAR)
            dDiffuseLight = dDiffuseLight + ((dAtten * lightColor) * (1.0 - litArgs_specularity));
        #else
            dDiffuseLight = dDiffuseLight + (dAtten * lightColor);
        #endif
    #endif

    // specular lighting
    #ifdef LIGHT{i}AFFECT_SPECULARITY

        #if LIGHT{i}SHAPE != PUNCTUAL // area light

            #ifdef LIT_CLEARCOAT
                #if LIGHT{i}SHAPE == RECT
                    ccSpecularLight = ccSpecularLight + (ccLTCSpecFres * getRectLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor);
                #elif LIGHT{i}SHAPE == DISK
                    ccSpecularLight = ccSpecularLight + (ccLTCSpecFres * getDiskLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor);
                #elif LIGHT{i}SHAPE == SPHERE
                    ccSpecularLight = ccSpecularLight + (ccLTCSpecFres * getSphereLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor);
                #endif
            #endif

            #ifdef LIT_SPECULAR
                #if LIGHT{i}SHAPE == RECT
                    dSpecularLight = dSpecularLight + (dLTCSpecFres * getRectLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor);
                #elif LIGHT{i}SHAPE == DISK
                    dSpecularLight = dSpecularLight + (dLTCSpecFres * getDiskLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor);
                #elif LIGHT{i}SHAPE == SPHERE
                    dSpecularLight = dSpecularLight + (dLTCSpecFres * getSphereLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor);
                #endif
            #endif

        #else // punctual light

            // is fresnel needed
            #if LIGHT{i}TYPE == DIRECTIONAL && LIT_FRESNEL_MODEL != NONE
                #define LIGHT{i}FRESNEL
            #endif

            #ifdef LIT_SPECULAR
                var halfDirW: vec3f = normalize(-dLightDirNormW + dViewDirW);
            #endif

            // if LTC lights are present, specular must be accumulated with specularity (specularity is pre multiplied by punctual light fresnel)
            #ifdef LIT_CLEARCOAT
                var lightspecularCC: vec3f = getLightSpecular(halfDirW, ccReflDirW, litArgs_clearcoat_worldNormal, dViewDirW, dLightDirNormW, litArgs_clearcoat_gloss, dTBN) * dAtten * lightColor;
                #ifdef LIGHT{i}FRESNEL
                    lightspecularCC = lightspecularCC * getFresnelCC(dot(dViewDirW, halfDirW));
                #endif
                ccSpecularLight = ccSpecularLight + lightspecularCC;
            #endif

            #ifdef LIT_SHEEN
                sSpecularLight = sSpecularLight + (getLightSpecularSheen(halfDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_sheen_gloss) * dAtten * lightColor);
            #endif

            #ifdef LIT_SPECULAR

                var lightSpecular: vec3f = getLightSpecular(halfDirW, dReflDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_gloss, dTBN) * dAtten * lightColor;
                #ifdef LIGHT{i}FRESNEL

                    #if defined(LIT_IRIDESCENCE)
                        lightSpecular = lightSpecular * getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity, iridescenceFresnel, litArgs_iridescence_intensity);
                    #else
                        lightSpecular = lightSpecular * getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity);
                    #endif

                #else
                    lightSpecular = lightSpecular * litArgs_specularity;
                #endif
                
                dSpecularLight = dSpecularLight + lightSpecular;
            #endif
        #endif
    #endif
}
#endif
`;

// functions used to evaluate the light shadow
var lightFunctionShadowPS = /* wgsl */ `

// shadow casting functionality
#ifdef LIGHT{i}CASTSHADOW

    // generate shadow coordinates function, based on per light defines:
    // - _SHADOW_SAMPLE_NORMAL_OFFSET
    // - _SHADOW_SAMPLE_ORTHO
    // - _SHADOW_SAMPLE_POINT
    // - _SHADOW_SAMPLE_SOURCE_ZBUFFER
    fn getShadowSampleCoord{i}(shadowTransform: mat4x4f, shadowParams: vec4f, worldPosition: vec3f, lightPos: vec3f, lightDir: ptr<function, vec3f>, lightDirNorm: vec3f, normal: vec3f) -> vec3f {

        var surfacePosition = worldPosition;

        #ifdef LIGHT{i}_SHADOW_SAMPLE_POINT
            #ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET
                let distScale: f32 = length(*lightDir);
                surfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;
                *lightDir = surfacePosition - lightPos;
                return *lightDir;
            #endif
        #else
            #ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER
                #ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET
                    surfacePosition = surfacePosition + normal * shadowParams.y;
                #endif
            #else
                #ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET
                    #ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO
                        var distScale: f32 = 1.0;
                    #else
                        var distScale: f32 = abs(dot(vPositionW - lightPos, lightDirNorm));
                    #endif
                    surfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;
                #endif
            #endif

            // Use var for modification
            var positionInShadowSpace: vec4f = shadowTransform * vec4f(surfacePosition, 1.0);
            #ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO
                positionInShadowSpace.z = saturate(positionInShadowSpace.z) - 0.0001;
            #else
                #ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER
                    positionInShadowSpace.xyz = positionInShadowSpace.xyz / positionInShadowSpace.w;
                #else
                    positionInShadowSpace.xy = positionInShadowSpace.xy / positionInShadowSpace.w;
                    positionInShadowSpace.z = length(*lightDir) * shadowParams.w;
                #endif
            #endif

            // this is currently unused
            #ifdef SHADOW_SAMPLE_Z_BIAS
                // positionInShadowSpace.z += getShadowBias(shadowParams.x, shadowParams.z);
            #endif

            surfacePosition = positionInShadowSpace.xyz;
        #endif

        return surfacePosition;
    }

    // shadow evaluation function
    fn getShadow{i}(lightDirW_in: vec3f) -> f32 {

        // directional shadow cascades
        #ifdef LIGHT{i}_SHADOW_CASCADES

            // select shadow cascade matrix
            var cascadeIndex: i32 = getShadowCascadeIndex(uniform.light{i}_shadowCascadeDistances, uniform.light{i}_shadowCascadeCount);

            #ifdef LIGHT{i}_SHADOW_CASCADE_BLEND
                cascadeIndex = ditherShadowCascadeIndex(cascadeIndex, uniform.light{i}_shadowCascadeDistances, uniform.light{i}_shadowCascadeCount, uniform.light{i}_shadowCascadeBlend);
            #endif

            var shadowMatrix: mat4x4f = uniform.light{i}_shadowMatrixPalette[cascadeIndex];

        #else

            var shadowMatrix: mat4x4f = uniform.light{i}_shadowMatrix;

        #endif

        var lightDirArg = lightDirW_in;

        #if LIGHT{i}TYPE == DIRECTIONAL
            // directional light does not have a position
            var shadowCoord: vec3f = getShadowSampleCoord{i}(shadowMatrix, uniform.light{i}_shadowParams, vPositionW, vec3f(0.0), &lightDirArg, dLightDirNormW, dVertexNormalW);
        #else
             var shadowCoord: vec3f = getShadowSampleCoord{i}(shadowMatrix, uniform.light{i}_shadowParams, vPositionW, uniform.light{i}_position, &lightDirArg, dLightDirNormW, dVertexNormalW);
        #endif


        // Fade directional shadow at the far distance
        #if LIGHT{i}TYPE == DIRECTIONAL
            shadowCoord = fadeShadow(shadowCoord, uniform.light{i}_shadowCascadeDistances);
        #endif

        // ----- sample the shadow -----

        #if LIGHT{i}TYPE == DIRECTIONAL // ----- directional light -----

            #if LIGHT{i}SHADOWTYPE == VSM_16F
                return getShadowVSM16(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 5.54);
            #endif

            #if LIGHT{i}SHADOWTYPE == VSM_32F
                return getShadowVSM32(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 15.0);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCSS_32F

                #if LIGHT{i}SHAPE != PUNCTUAL
                    let shadowSearchArea = vec2f(length(uniform.light{i}_halfWidth), length(uniform.light{i}_halfHeight)) * uniform.light{i}_shadowSearchArea;
                    return getShadowPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, shadowSearchArea, lightDirW_in);
                #else
                    return getShadowPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, uniform.light{i}_softShadowParams, lightDirW_in);
                #endif

            #endif

            #if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F
                return getShadowPCF1x1(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F
                return getShadowPCF3x3(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F
                return getShadowPCF5x5(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);
            #endif

        #endif


        #if LIGHT{i}TYPE == SPOT // ----- spot light -----

            #if LIGHT{i}SHADOWTYPE == VSM_16F
                return getShadowSpotVSM16(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 5.54, lightDirW_in);
            #endif

            #if LIGHT{i}SHADOWTYPE == VSM_32F
                return getShadowSpotVSM32(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 15.0, lightDirW_in);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCSS_32F

                #if LIGHT{i}SHAPE != PUNCTUAL
                    var shadowSearchArea: vec2f = vec2f(length(uniform.light{i}_halfWidth), length(uniform.light{i}_halfHeight)) * uniform.light{i}_shadowSearchArea;
                #else
                    var shadowSearchArea: vec2f = vec2f(uniform.light{i}_shadowSearchArea);
                #endif
                return getShadowSpotPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, shadowSearchArea, lightDirW_in);

            #endif

            #if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F
                return getShadowSpotPCF1x1(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F
                return getShadowSpotPCF3x3(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F
                return getShadowSpotPCF5x5(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);
            #endif

        #endif


        #if LIGHT{i}TYPE == OMNI // ----- omni light -----

            #if LIGHT{i}SHADOWTYPE == PCSS_32F

                 var shadowSearchArea: vec2f; // Use var because assigned in if/else
                 #if LIGHT{i}SHAPE != PUNCTUAL
                    var shadowSearchArea: vec2f = vec2f(length(uniform.light{i}_halfWidth), length(uniform.light{i}_halfHeight)) * uniform.light{i}_shadowSearchArea;
                #else
                    var shadowSearchArea: vec2f = vec2f(uniform.light{i}_shadowSearchArea);
                #endif

                return getShadowOmniPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, shadowSearchArea, lightDirW_in);

            #endif

            #if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F
                return getShadowOmniPCF1x1(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, lightDirW_in);
            #endif

            #if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F
                return getShadowOmniPCF3x3(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, lightDirW_in);
            #endif

        #endif
    }

#endif
`;

// functionality includes for lighting / shadowing code.
var lightingPS = /* wgsl */ `

#ifdef LIT_CLUSTERED_LIGHTS
    // all this functionality that needs to be included for clustered lighting
    #define LIT_CODE_FALLOFF_LINEAR
    #define LIT_CODE_FALLOFF_SQUARED
    #define LIT_CODE_LIGHTS_POINT
    #define LIT_CODE_LIGHTS_SPOT
#endif

#ifdef AREA_LIGHTS
    var areaLightsLutTex1: texture_2d<f32>;
    var areaLightsLutTex1Sampler: sampler;
    var areaLightsLutTex2: texture_2d<f32>;
    var areaLightsLutTex2Sampler: sampler;
#endif

#ifdef LIT_LIGHTING
    #include "lightDiffuseLambertPS"

    // area lights
    #if defined(AREA_LIGHTS) || defined(LIT_CLUSTERED_AREA_LIGHTS)
        #include "ltcPS"
    #endif
#endif

#ifdef SHADOW_DIRECTIONAL
    #include "shadowCascadesPS"
#endif

#if defined(SHADOW_KIND_PCF1)
    #include "shadowPCF1PS"
#endif

#if defined(SHADOW_KIND_PCF3)
    #include "shadowPCF3PS"
#endif

#if defined(SHADOW_KIND_PCF5)
    #include "shadowPCF5PS"
#endif

#if defined(SHADOW_KIND_PCSS)
    #include "linearizeDepthPS"

    // omni / spot PCSS is not supported on WebGPU currently, as this is only for non-clustered lights
    // #include "shadowPCSSPS"
    #include "shadowSoftPS"
#endif

#if defined(SHADOW_KIND_VSM)
    #include "shadowEVSMPS"
#endif

#ifdef LIT_CODE_FALLOFF_LINEAR
    #include "falloffLinearPS"
#endif

#ifdef LIT_CODE_FALLOFF_SQUARED
    #include "falloffInvSquaredPS"
#endif

#ifdef LIT_CODE_LIGHTS_POINT
    #include "lightDirPointPS"
#endif

#ifdef LIT_CODE_LIGHTS_SPOT
    #include "spotPS"
#endif

#ifdef LIT_CODE_COOKIE
    #include "cookiePS"
#endif

// clustered lighting
#ifdef LIT_CLUSTERED_LIGHTS
    #include "clusteredLightPS"
#endif

#ifdef LIGHT_COUNT > 0
    // LOOP - generate shadow evaluation functions for all non-clustered lights
    #include "lightFunctionShadowPS, LIGHT_COUNT"

    // LOOP - generate light evaluation functions for all non-clustered lights
    #include "lightFunctionLightPS, LIGHT_COUNT"
#endif
`;

var lightmapAddPS = /* wgsl */ `
fn addLightMap(
    lightmap: vec3f,
    dir: vec3f,
    worldNormal: vec3f,
    viewDir: vec3f,
    reflectionDir: vec3f,
    gloss: f32,
    specularity: vec3f,
    vertexNormal: vec3f,
    tbn: mat3x3f
#if defined(LIT_IRIDESCENCE)
    , iridescenceFresnel: vec3f,
    iridescenceIntensity: f32
#endif
) {

    // directional lightmap
    #if defined(LIT_SPECULAR) && defined(LIT_DIR_LIGHTMAP)

        if (dot(dir, dir) < 0.0001) {
                dDiffuseLight = dDiffuseLight + lightmap;
        } else {
            let vlight: f32 = saturate(dot(dir, -vertexNormal));
            let flight: f32 = saturate(dot(dir, -worldNormal));
            let nlight: f32 = (flight / max(vlight, 0.01)) * 0.5;

            dDiffuseLight = dDiffuseLight + lightmap * nlight * 2.0;

            let halfDir: vec3f = normalize(-dir + viewDir);
            var specularLight: vec3f = lightmap * getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, dir, gloss, tbn);

            #ifdef LIT_SPECULAR_FRESNEL

                specularLight = specularLight *
                    getFresnel(dot(viewDir, halfDir),
                    gloss,
                    specularity
                #if defined(LIT_IRIDESCENCE)
                    , iridescenceFresnel,
                    iridescenceIntensity
                #endif
                    );
            #endif

            dSpecularLight = dSpecularLight + specularLight;
        }

    #else // non-directional lightmap

        dDiffuseLight = dDiffuseLight + lightmap;

    #endif
}
`;

var lightmapPS = /* wgsl */ `

#ifdef STD_LIGHTMAP_DIR
    var<private> dLightmapDir: vec3f;
    var texture_dirLightMap: texture_2d<f32>;
    var texture_dirLightMapSampler: sampler;
#endif

fn getLightMap() {

    dLightmap = vec3f(1.0);

    #ifdef STD_LIGHT_TEXTURE
        dLightmap = dLightmap * {STD_LIGHT_TEXTURE_DECODE}(textureSampleBias({STD_LIGHT_TEXTURE_NAME}, {STD_LIGHT_TEXTURE_NAME}Sampler, {STD_LIGHT_TEXTURE_UV}, uniform.textureBias)).{STD_LIGHT_TEXTURE_CHANNEL};

        #ifdef STD_LIGHTMAP_DIR
            var dir: vec3f = textureSampleBias(texture_dirLightMap, texture_dirLightMapSampler, {STD_LIGHT_TEXTURE_UV}, uniform.textureBias).xyz * 2.0 - 1.0;
            var dirDot = dot(dir, dir);
            dLightmapDir = select(vec3(0.0), dir / sqrt(dirDot), dirDot > 0.001);
        #endif
    #endif

    #ifdef STD_LIGHT_VERTEX
        dLightmap = dLightmap * saturate(vVertexColor.{STD_LIGHT_VERTEX_CHANNEL});
    #endif
}
`;

var lightSpecularAnisoGGXPS = /* wgsl */ `
// Anisotropic GGX
fn calcLightSpecular(gloss: f32, worldNormal: vec3f, viewDir: vec3f, h: vec3f, lightDirNorm: vec3f, tbn: mat3x3f) -> f32 {
    let PI: f32 = 3.141592653589793;
    let roughness: f32 = max((1.0 - gloss) * (1.0 - gloss), 0.001);
    let alphaRoughness: f32 = roughness * roughness;
    let anisotropy: f32 = dAnisotropy;
    let direction: vec2f = dAnisotropyRotation;

    let at: f32 = mix(alphaRoughness, 1.0, anisotropy * anisotropy);
    let ab: f32 = clamp(alphaRoughness, 0.001, 1.0);

    let anisotropicT: vec3f = normalize(tbn * vec3f(direction, 0.0));
    let anisotropicB: vec3f = normalize(cross(tbn[2], anisotropicT));

    let NoH: f32 = dot(worldNormal, h);
    let ToH: f32 = dot(anisotropicT, h);
    let BoH: f32 = dot(anisotropicB, h);

    let a2: f32 = at * ab;
    let v: vec3f = vec3f(ab * ToH, at * BoH, a2 * NoH);
    let v2: f32 = dot(v, v);
    let w2: f32 = a2 / v2;
    let D: f32 = a2 * w2 * w2 * (1.0 / PI);

    let ToV: f32 = dot(anisotropicT, viewDir);
    let BoV: f32 = dot(anisotropicB, viewDir);
    let ToL: f32 = dot(anisotropicT, -lightDirNorm);
    let BoL: f32 = dot(anisotropicB, -lightDirNorm);
    let NoV: f32 = dot(worldNormal, viewDir);
    let NoL: f32 = dot(worldNormal, -lightDirNorm);

    let lambdaV: f32 = NoL * length(vec3f(at * ToV, ab * BoV, NoV));
    let lambdaL: f32 = NoV * length(vec3f(at * ToL, ab * BoL, NoL));
    let G: f32 = 0.5 / (lambdaV + lambdaL);

    return D * G;
}

fn getLightSpecular(h: vec3f, reflDir: vec3f, worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f, gloss: f32, tbn: mat3x3f) -> f32 {
    return calcLightSpecular(gloss, worldNormal, viewDir, h, lightDirNorm, tbn);
}
`;

var lightSpecularBlinnPS = /* wgsl */ `
// Energy-conserving (hopefully) Blinn-Phong
fn calcLightSpecular(gloss: f32, worldNormal: vec3f, h: vec3f) -> f32 {
    let nh: f32 = max( dot( h, worldNormal ), 0.0 );

    var specPow: f32 = exp2(gloss * 11.0); // glossiness is linear, power is not; 0 - 2048

    // Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little
    specPow = max(specPow, 0.0001);

    return pow(nh, specPow) * (specPow + 2.0) / 8.0;
}

fn getLightSpecular(h: vec3f, reflDir: vec3f, worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f, gloss: f32, tbn: mat3x3f) -> f32 {
    return calcLightSpecular(gloss, worldNormal, h);
}
`;

var lightSheenPS = /* wgsl */ `

fn sheenD(normal: vec3f, h: vec3f, roughness: f32) -> f32 {
    let PI: f32 = 3.141592653589793;
    let invR: f32 = 1.0 / (roughness * roughness);
    var cos2h: f32 = max(dot(normal, h), 0.0);
    cos2h = cos2h * cos2h;
    let sin2h: f32 = max(1.0 - cos2h, 0.0078125);
    return (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * PI);
}

fn sheenV(normal: vec3f, viewDir: vec3f, light: vec3f) -> f32 {
    let NoV: f32 = max(dot(normal, viewDir), 0.000001);
    let NoL: f32 = max(dot(normal, light), 0.000001);
    return 1.0 / (4.0 * (NoL + NoV - NoL * NoV));
}

fn getLightSpecularSheen(h: vec3f, worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f, sheenGloss: f32) -> f32 {
    let D: f32 = sheenD(worldNormal, h, sheenGloss);
    let V: f32 = sheenV(worldNormal, viewDir, -lightDirNorm);
    return D * V;
}`;

var linearizeDepthPS = /* wgsl */ `

#ifndef LINEARIZE_DEPTH
#define LINEARIZE_DEPTH

fn linearizeDepthWithParams(z: f32, cameraParams: vec4f) -> f32 {
    if (cameraParams.w == 0.0) {
        return (cameraParams.z * cameraParams.y) / (cameraParams.y + z * (cameraParams.z - cameraParams.y));
    } else {
        return cameraParams.z + z * (cameraParams.y - cameraParams.z);
    }
}

#ifndef CAMERAPLANES
    #define CAMERAPLANES
    uniform camera_params: vec4f;   // x: 1 / camera_far,      y: camera_far,     z: camera_near,        w: is_ortho
#endif

fn linearizeDepth(z: f32) -> f32 {
    return linearizeDepthWithParams(z, uniform.camera_params);
}
#endif // LINEARIZE_DEPTH
`;

// backend shader implementing material / lighting for the lit material for forward rendering
var litForwardBackendPS = /* wgsl */ `
fn evaluateBackend() -> FragmentOutput {

    var output: FragmentOutput;

    // apply SSAO during lighting
    #ifdef LIT_SSAO
        litArgs_ao = litArgs_ao * textureSampleLevel(ssaoTexture, ssaoTextureSampler, pcPosition.xy * uniform.ssaoTextureSizeInv, 0.0).r;
    #endif

    // transform tangent space normals to world space
    #ifdef LIT_NEEDS_NORMAL
        #ifdef LIT_SPECULAR
            getReflDir(litArgs_worldNormal, dViewDirW, litArgs_gloss, dTBN);
        #endif

        #ifdef LIT_CLEARCOAT
            ccReflDirW = normalize(-reflect(dViewDirW, litArgs_clearcoat_worldNormal));
        #endif
    #endif

    #ifdef LIT_SPECULAR_OR_REFLECTION
        #ifdef LIT_METALNESS
            var f0: f32 = 1.0 / litArgs_ior;
            f0 = (f0 - 1.0) / (f0 + 1.0);
            f0 = f0 * f0;
            litArgs_specularity = getSpecularModulate(litArgs_specularity, litArgs_albedo, litArgs_metalness, f0);
            litArgs_albedo = getAlbedoModulate(litArgs_albedo, litArgs_metalness);
        #endif

        #ifdef LIT_IRIDESCENCE
            var iridescenceFresnel: vec3f = getIridescenceDiffraction(saturate(dot(dViewDirW, litArgs_worldNormal)), litArgs_specularity, litArgs_iridescence_thickness);
        #endif
    #endif

    // ambient
    #ifdef LIT_ADD_AMBIENT
        addAmbient(litArgs_worldNormal);

        #ifdef LIT_SPECULAR
            dDiffuseLight = dDiffuseLight * (1.0 - litArgs_specularity);
        #endif

        // move ambient color out of diffuse (used by Lightmapper, to multiply ambient color by accumulated AO)
        #ifdef LIT_SEPARATE_AMBIENT
            var dAmbientLight: vec3f = dDiffuseLight;
            dDiffuseLight = vec3(0.0);
        #endif
    #endif

    #ifndef LIT_OLD_AMBIENT
        dDiffuseLight = dDiffuseLight * uniform.material_ambient;
    #endif

    #ifdef LIT_AO
        #ifndef LIT_OCCLUDE_DIRECT
            occludeDiffuse(litArgs_ao);
        #endif
    #endif

    #ifdef LIT_LIGHTMAP
        addLightMap(
            litArgs_lightmap, 
            litArgs_lightmapDir, 
            litArgs_worldNormal, 
            dViewDirW, 
            dReflDirW, 
            litArgs_gloss, 
            litArgs_specularity, 
            dVertexNormalW,
            dTBN
        #if defined(LIT_IRIDESCENCE)
            , iridescenceFresnel,
            litArgs_iridescence_intensity
        #endif
        );
    #endif

    #ifdef LIT_LIGHTING || LIT_REFLECTIONS

        #ifdef LIT_REFLECTIONS

            #ifdef LIT_CLEARCOAT
                addReflectionCC(ccReflDirW, litArgs_clearcoat_gloss);
            
                #ifdef LIT_SPECULAR_FRESNEL
                    ccFresnel = getFresnelCC(dot(dViewDirW, litArgs_clearcoat_worldNormal));
                    ccReflection = ccReflection * ccFresnel;
                #else
                    ccFresnel = 0.0;
                #endif
            #endif

            #ifdef LIT_SPECULARITY_FACTOR
                ccReflection = ccReflection * litArgs_specularityFactor;
            #endif

            #ifdef LIT_SHEEN
                addReflectionSheen(litArgs_worldNormal, dViewDirW, litArgs_sheen_gloss);
            #endif

            // Fresnel has to be applied to reflections
            addReflection(dReflDirW, litArgs_gloss);

            #ifdef LIT_FRESNEL_MODEL

                dReflection = vec4f(
                    dReflection.rgb * getFresnel(
                        dot(dViewDirW, litArgs_worldNormal),
                        litArgs_gloss,
                        litArgs_specularity
                    #if defined(LIT_IRIDESCENCE)
                        , iridescenceFresnel,
                        litArgs_iridescence_intensity
                    #endif
                        ),
                    dReflection.a
                );

            #else

                dReflection = vec4f(dReflection.rgb * litArgs_specularity, dReflection.a);

            #endif

            #ifdef LIT_SPECULARITY_FACTOR
                dReflection = vec4f(dReflection.rgb * litArgs_specularityFactor, dReflection.a);
            #endif

        #endif

        #ifdef AREA_LIGHTS
            // specular has to be accumulated differently if we want area lights to look correct
            dSpecularLight = dSpecularLight * litArgs_specularity;

            #ifdef LIT_SPECULAR
                // evaluate material based area lights data, shared by all area lights
                calcLTCLightValues(litArgs_gloss, litArgs_worldNormal, dViewDirW, litArgs_specularity, litArgs_clearcoat_gloss, litArgs_clearcoat_worldNormal, litArgs_clearcoat_specularity);
            #endif
        #endif
        
        // LOOP - evaluate all non-clustered lights
        #ifdef LIGHT_COUNT > 0
            #include "lightEvaluationPS, LIGHT_COUNT"
        #endif

        // clustered lighting
        #ifdef LIT_CLUSTERED_LIGHTS
            addClusteredLights(litArgs_worldNormal, dViewDirW, dReflDirW,
                #if defined(LIT_CLEARCOAT)
                        ccReflDirW,
                #endif
                        litArgs_gloss, litArgs_specularity, dVertexNormalW, dTBN, 
                #if defined(LIT_IRIDESCENCE)
                        iridescenceFresnel,
                #endif
                        litArgs_clearcoat_worldNormal, litArgs_clearcoat_gloss, litArgs_sheen_gloss, litArgs_iridescence_intensity
            );
        #endif

        #ifdef AREA_LIGHTS

            #ifdef LIT_CLEARCOAT
                // specular has to be accumulated differently if we want area lights to look correct
                litArgs_clearcoat_specularity = 1.0;
            #endif

            #ifdef LIT_SPECULAR
                litArgs_specularity = vec3(1.0);
            #endif

        #endif

        #ifdef LIT_REFRACTION
            addRefraction(
                litArgs_worldNormal, 
                dViewDirW, 
                litArgs_thickness, 
                litArgs_gloss, 
                litArgs_specularity, 
                litArgs_albedo, 
                litArgs_transmission,
                litArgs_ior,
                litArgs_dispersion
                #if defined(LIT_IRIDESCENCE)
                    , iridescenceFresnel, 
                    litArgs_iridescence_intensity
                #endif
            );
        #endif
    #endif

    // apply ambient occlusion
    #ifdef LIT_AO
        #ifdef LIT_OCCLUDE_DIRECT
            occludeDiffuse(litArgs_ao);
        #endif

        #if LIT_OCCLUDE_SPECULAR != NONE
            occludeSpecular(litArgs_gloss, litArgs_ao, litArgs_worldNormal, dViewDirW);
        #endif
    #endif

    #ifdef LIT_SPECULARITY_FACTOR
        dSpecularLight = dSpecularLight * litArgs_specularityFactor;
    #endif

    #if !defined(LIT_OPACITY_FADES_SPECULAR)

        #if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == PREMULTIPLIED

            var specLum: f32 = dot((dSpecularLight + dReflection.rgb * dReflection.a), vec3f( 0.2126, 0.7152, 0.0722 ));
            #ifdef LIT_CLEARCOAT
                specLum = specLum + dot(ccSpecularLight * litArgs_clearcoat_specularity + ccReflection * litArgs_clearcoat_specularity, vec3f( 0.2126, 0.7152, 0.0722 ));
            #endif
            litArgs_opacity = clamp(litArgs_opacity + gammaCorrectInput(specLum), 0.0, 1.0);

        #endif

        litArgs_opacity = litArgs_opacity * uniform.material_alphaFade;

    #endif

    // end chunks - when baking lightmap
    #ifdef LIT_LIGHTMAP_BAKING
        #ifdef LIT_LIGHTMAP_BAKING_COLOR
            #include "bakeLmEndPS"
        #endif
        #ifdef LIT_LIGHTMAP_BAKING_DIR
            #include "bakeDirLmEndPS"
        #endif
    #else
        // end chunks - in all other cases
        #include "endPS"
        #include "outputAlphaPS"
    #endif

    #ifdef LIT_MSDF
        output.color = applyMsdf(output.color);
    #endif

    #include "outputPS"
    #include "debugOutputPS"

    #ifdef LIT_SHADOW_CATCHER
        // output when the shadow catcher is enabled - accumulated shadows
        output.color = vec4f(vec3f(dShadowCatcher), output.color.a);
    #endif

    return output;
}
`;

// shader declarations for the lit material for forward rendering
var litForwardDeclarationPS = /* wgsl */ `

// globals
var<private> sReflection: vec3f;
var<private> dVertexNormalW: vec3f;
var<private> dTangentW: vec3f;
var<private> dBinormalW: vec3f;
var<private> dViewDirW: vec3f;
var<private> dReflDirW: vec3f;
var<private> ccReflDirW: vec3f;

// Per-light temporaries
var<private> dLightDirNormW: vec3f;
var<private> dAtten: f32;

// Outputs
var<private> dTBN: mat3x3f;
var<private> dReflection: vec4f;
var<private> dDiffuseLight: vec3f;
var<private> dSpecularLight: vec3f;
var<private> ccFresnel: f32;
var<private> ccReflection: vec3f;
var<private> ccSpecularLight: vec3f;
var<private> ccSpecularityNoFres: f32;
var<private> sSpecularLight: vec3f;

// FRAGMENT SHADER INPUTS: UNIFORMS

#ifdef LIT_DISPERSION
    uniform material_dispersion: f32;
#endif

#ifndef LIT_OPACITY_FADES_SPECULAR
    uniform material_alphaFade: f32;
#endif

#ifdef LIT_SSAO
    var ssaoTexture : texture_2d<f32>;
    var ssaoTextureSampler : sampler;
    uniform ssaoTextureSizeInv: vec2f;
#endif

// lighting and shadowing declarations

#ifdef LIT_SHADOW_CATCHER
    // a variable to accumulate shadows for shadow catcher materials
    var<private> dShadowCatcher: f32 = 1.0;
#endif

// LOOP - uniform declarations for all non-clustered lights
#if LIGHT_COUNT > 0
    #include "lightDeclarationPS, LIGHT_COUNT"
#endif

#ifdef LIT_SPECULAR
    #if LIT_FRESNEL_MODEL == NONE && !defined(LIT_REFLECTIONS) && !defined(LIT_DIFFUSE_MAP) 
        #define LIT_OLD_AMBIENT
    #endif
#endif

// lightmap baking
#ifdef STD_LIGHTMAP_DIR
    uniform bakeDir: f32;
#endif
#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT
    uniform ambientBakeOcclusionContrast: f32;
    uniform ambientBakeOcclusionBrightness: f32;
#endif
`;

// main shader entry point for the lit material for forward rendering
var litForwardMainPS = /* wgsl */ `

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {

    #include "litUserMainStartPS"

    dReflection = vec4f(0.0);

    #ifdef LIT_CLEARCOAT
        ccSpecularLight = vec3f(0.0);
        ccReflection = vec3f(0.0);
    #endif

    #if LIT_NONE_SLICE_MODE == SLICED
        #include "startNineSlicedPS"
    #elif LIT_NONE_SLICE_MODE == TILED
        #include "startNineSlicedTiledPS"
    #endif

    #ifdef LIT_NEEDS_NORMAL
        dVertexNormalW = normalize(vNormalW);

        #ifdef LIT_TANGENTS
            #if defined(LIT_HEIGHTS) || defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS) || defined(LIT_GGX_SPECULAR)
                dTangentW = vTangentW;
                dBinormalW = vBinormalW;
            #endif
        #endif

        getViewDir();

        #ifdef LIT_TBN
            getTBN(dTangentW, dBinormalW, dVertexNormalW);

            #ifdef LIT_TWO_SIDED_LIGHTING
                handleTwoSidedLighting();
            #endif
        #endif
    #endif

    // invoke frontend functions
    evaluateFrontend();

    #include "debugProcessFrontendPS"

    var output: FragmentOutput = evaluateBackend();

    #include "litUserMainEndPS"

    return output;
}
`;

// backend shader implementation code which executes after the front end code.
var litForwardPostCodePS = /* wgsl */ `

#ifdef LIT_NEEDS_NORMAL
    #include "cubeMapRotatePS"
    #include "cubeMapProjectPS"
    #include "envProcPS"
#endif

// ----- specular or reflections -----
#ifdef LIT_SPECULAR_OR_REFLECTION
    #ifdef LIT_METALNESS
        #include "metalnessModulatePS"
    #endif

    #if LIT_FRESNEL_MODEL == SCHLICK
        #include "fresnelSchlickPS"
    #endif

    #ifdef LIT_IRIDESCENCE
        #include "iridescenceDiffractionPS"
    #endif
#endif

// ----- ambient occlusion -----
#ifdef LIT_AO
    #include "aoDiffuseOccPS"
    #include "aoSpecOccPS"
#endif

#if LIT_REFLECTION_SOURCE == ENVATLASHQ
    #include "envAtlasPS"
    #include "reflectionEnvHQPS"
#elif LIT_REFLECTION_SOURCE == ENVATLAS
    #include "envAtlasPS"
    #include "reflectionEnvPS"
#elif LIT_REFLECTION_SOURCE == CUBEMAP
    #include "reflectionCubePS"
#elif LIT_REFLECTION_SOURCE == SPHEREMAP
    #include "reflectionSpherePS"
#endif

#ifdef LIT_REFLECTIONS
    #ifdef LIT_CLEARCOAT
        #include "reflectionCCPS"
    #endif

    #ifdef LIT_SHEEN
        #include "reflectionSheenPS"
    #endif
#endif

#ifdef LIT_REFRACTION
    #if defined(LIT_DYNAMIC_REFRACTION)
        #include "refractionDynamicPS"
    #elif defined(LIT_REFLECTIONS)
        #include "refractionCubePS"
    #endif
#endif

#ifdef LIT_SHEEN
    #include "lightSheenPS"
#endif

uniform material_ambient: vec3f;

#ifdef LIT_SPECULAR
    #ifdef LIT_LIGHTING
        #ifdef LIT_GGX_SPECULAR
            #include "lightSpecularAnisoGGXPS"
        #else
            #include "lightSpecularBlinnPS"
        #endif
    #endif
#endif

#include "combinePS"

#ifdef LIT_LIGHTMAP
    #include "lightmapAddPS"
#endif

#ifdef LIT_ADD_AMBIENT
    #include "ambientPS"
#endif

#ifdef LIT_MSDF
    #include "msdfPS"
#endif

#ifdef LIT_NEEDS_NORMAL
    #include "viewDirPS"
    #ifdef LIT_SPECULAR
        #ifdef LIT_GGX_SPECULAR
            #include "reflDirAnisoPS"
        #else
            #include "reflDirPS"
        #endif
    #endif
#endif

// lighting functionality
#include "lightingPS"

`;

// backend shader implementation code which executes prior to the front end code, and contains code
// which is required by the frontend code.
var litForwardPreCodePS = /* wgsl */ `

#include "basePS"
#include "sphericalPS"
#include "decodePS"
#include "gammaPS"
#include "tonemappingPS"
#include "fogPS"

// 9-slice support code
#if LIT_NONE_SLICE_MODE == SLICED
    #include "baseNineSlicedPS"
#elif LIT_NONE_SLICE_MODE == TILED
    #include "baseNineSlicedTiledPS"
#endif

// TBN
#ifdef LIT_TBN
    #include "TBNPS"

    #ifdef LIT_TWO_SIDED_LIGHTING
        #include "twoSidedLightingPS"
    #endif
#endif

`;

// main shader of the lit fragment shader
var litMainPS = /* wgsl */ `

#include "varyingsPS"
#include "litUserDeclarationPS"
#include "frontendDeclPS"

#if defined(PICK_PASS) || defined(PREPASS_PASS)

    #include "frontendCodePS"
    #include "litUserCodePS"
    #include "litOtherMainPS"

#elif defined(SHADOW_PASS)

    #include "frontendCodePS"
    #include "litUserCodePS"
    #include "litShadowMainPS"

#else // FORWARD_PASS

    #include "litForwardDeclarationPS"
    #include "litForwardPreCodePS"
    #include "frontendCodePS"
    #include "litForwardPostCodePS"
    #include "litForwardBackendPS"
    #include "litUserCodePS"
    #include "litForwardMainPS"

#endif

`;

// main shader of the lit vertex shader
var litMainVS = /* wgsl */ `

#include "varyingsVS"

#include  "litUserDeclarationVS"

#ifdef VERTEX_COLOR
    attribute vertex_color: vec4f;
#endif

#ifdef NINESLICED

    varying vMask: vec2f;
    varying vTiledUv: vec2f;

    var<private> dMaskGlobal: vec2f;
    var<private> dTiledUvGlobal: vec2f;

    uniform innerOffset: vec4f;
    uniform outerScale: vec2f;
    uniform atlasRect: vec4f;

#endif

var<private> dPositionW: vec3f;
var<private> dModelMatrix: mat4x4f;

#include "transformCoreVS"

#ifdef UV0
    attribute vertex_texCoord0: vec2f;
    #include "uv0VS"
#endif

#ifdef UV1
    attribute vertex_texCoord1: vec2f;
    #include "uv1VS"
#endif


#ifdef LINEAR_DEPTH
    #ifndef VIEWMATRIX
    #define VIEWMATRIX
        uniform matrix_view: mat4x4f;
    #endif
#endif

#include "transformVS"

#ifdef NORMALS
    #include "normalCoreVS"
    #include "normalVS"
#endif

#ifdef TANGENTS
    attribute vertex_tangent: vec4f;
#endif

// expand uniforms for uv transforms
#include "uvTransformUniformsPS, UV_TRANSFORMS_COUNT"

#ifdef MSDF
    #include "msdfVS"
#endif

#include  "litUserCodeVS"

@vertex
fn vertexMain(input : VertexInput) -> VertexOutput {

    #include "litUserMainStartVS"

    var output : VertexOutput;
    output.position = getPosition();
    output.vPositionW = getWorldPosition();

    #ifdef NORMALS
        output.vNormalW = getNormal();
    #endif

    #ifdef TANGENTS
        output.vTangentW = normalize(dNormalMatrix * vertex_tangent.xyz);
        output.vBinormalW = cross(output.vNormalW, output.vTangentW) * vertex_tangent.w;
    #elif defined(GGX_SPECULAR)
        output.vObjectSpaceUpW = normalize(dNormalMatrix * vec3f(0.0, 1.0, 0.0));
    #endif

    #ifdef UV0
        var uv0: vec2f = getUv0();
        #ifdef UV0_UNMODIFIED
            output.vUv0 = uv0;
        #endif
    #endif

    #ifdef UV1
        var uv1: vec2f = getUv1();
        #ifdef UV1_UNMODIFIED
            output.vUv1 = uv1;
        #endif
    #endif

    // expand code for uv transforms
    #include "uvTransformVS, UV_TRANSFORMS_COUNT"

    #ifdef VERTEX_COLOR
        output.vVertexColor = vertex_color;
    #endif

    #ifdef LINEAR_DEPTH
        // linear depth from the worldPosition, see getLinearDepth
        output.vLinearDepth = -(uniform.matrix_view * vec4f(output.vPositionW, 1.0)).z;
    #endif

    #ifdef MSDF
        unpackMsdfParams();

        output.outline_color = dOutlineColor;
        output.outline_thickness = dOutlineThickness;
        output.shadow_color = dShadowColor;
        output.shadow_offset = dShadowOffset;
    #endif

    #ifdef NINESLICED
        output.vMask = dMaskGlobal;
        output.vTiledUv = dTiledUvGlobal;
    #endif

    #include "litUserMainEndVS"

    return output;
}
`;

// main shader entry point for the lit material for other render passes
var litOtherMainPS = /* wgsl */ `

#ifdef PICK_PASS
    #include "pickPS"
#endif

#ifdef PREPASS_PASS
    #include "floatAsUintPS"
#endif

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {

    #include "litUserMainStartPS"

    var output: FragmentOutput;
    
    evaluateFrontend();

    #ifdef PICK_PASS
        output.color = getPickOutput();
    #endif

    #ifdef PREPASS_PASS
        output.color = float2vec4(vLinearDepth);
    #endif

    #include "litUserMainEndPS"

    return output;
}
`;

var litShaderArgsPS = /* wgsl */ `

// Surface albedo absorbance
var<private> litArgs_albedo: vec3f;

// Transparency
var<private> litArgs_opacity: f32;

// Emission color
var<private> litArgs_emission: vec3f;

// Normal direction in world space
var<private> litArgs_worldNormal: vec3f;

// Ambient occlusion amount, range [0..1]
var<private> litArgs_ao: f32;

// Light map color
var<private> litArgs_lightmap: vec3f;

// Light map direction
var<private> litArgs_lightmapDir: vec3f;

// Surface metalness factor, range [0..1]
var<private> litArgs_metalness: f32;

// The f0 specularity factor
var<private> litArgs_specularity: vec3f;

// Specularity intensity factor, range [0..1]
var<private> litArgs_specularityFactor: f32;

// The microfacet glossiness factor, range [0..1]
var<private> litArgs_gloss: f32;

// Glossiness of the sheen layer, range [0..1]
var<private> litArgs_sheen_gloss: f32;

// The color of the f0 specularity factor for the sheen layer
var<private> litArgs_sheen_specularity: vec3f;

// Transmission factor (refraction), range [0..1]
var<private> litArgs_transmission: f32;

// Uniform thickness of medium, used by transmission, range [0..inf]
var<private> litArgs_thickness: f32;

// Index of refraction
var<private> litArgs_ior: f32;

// Dispersion, range [0..1] typically, but can be higher
var<private> litArgs_dispersion: f32;

// Iridescence effect intensity, range [0..1]
var<private> litArgs_iridescence_intensity: f32;

// Thickness of the iridescent microfilm layer, value is in nanometers, range [0..1000]
var<private> litArgs_iridescence_thickness: f32;

// The normal used for the clearcoat layer
var<private> litArgs_clearcoat_worldNormal: vec3f;

// Intensity of the clearcoat layer, range [0..1]
var<private> litArgs_clearcoat_specularity: f32;

// Glossiness of clearcoat layer, range [0..1]
var<private> litArgs_clearcoat_gloss: f32;

`;

var litShaderCorePS = /* wgsl */ `

    // global texture bias for standard textures
    #if LIT_NONE_SLICE_MODE == TILED
        var<private> textureBias: f32 = -1000.0;
    #else
        uniform textureBias: f32;
    #endif

    #include "litShaderArgsPS"
`;

// main shader entry point for the lit material for shadow rendering
var litShadowMainPS = /* wgsl */ `

#if LIGHT_TYPE != DIRECTIONAL
    uniform view_position: vec3f;
    uniform light_radius: f32;
#endif

#if SHADOW_TYPE == PCSS_32F
    #include "linearizeDepthPS"
#endif

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {

    #include "litUserMainStartPS"

    var output: FragmentOutput;

    evaluateFrontend();

    // using non-standard depth, i.e gl_FragCoord.z
    #ifdef PERSPECTIVE_DEPTH
        var depth: f32 = input.position.z;

        #if SHADOW_TYPE == PCSS_32F
            // spot/omni shadows currently use linear depth.
            // TODO: use perspective depth for spot/omni the same way as directional
            #if LIGHT_TYPE != DIRECTIONAL
                depth = linearizeDepthWithParams(depth, camera_params);
            #endif
        #endif

    #else
        var depth: f32 = min(distance(uniform.view_position, input.vPositionW) / uniform.light_radius, 0.99999);
        #define MODIFIED_DEPTH
    #endif

    #if SHADOW_TYPE == VSM_16F || SHADOW_TYPE == VSM_32F
        #if SHADOW_TYPE == VSM_32F
            var exponent: f32 = 15.0;
        #else
            var exponent: f32 = 5.54;
        #endif

        var depth_vsm = 2.0 * depth - 1.0;
        depth_vsm = exp(exponent * depth_vsm);
        output.color = vec4f(depth_vsm, depth_vsm * depth_vsm, 1.0, 1.0);
    #else
        #if SHADOW_TYPE == PCSS_32F
            output.color = vec4f(depth, 0.0, 0.0, 1.0);
        #else
            #ifdef MODIFIED_DEPTH
                // If we end up using modified depth, it needs to be explicitly written to gl_FragDepth
                output.fragDepth = depth;
            #endif

            // just the simplest code, color is not written anyway
            output.color = vec4f(1.0);
        #endif
    #endif

    #include "litUserMainEndPS"
    
    return output;
}
`;

var ltcPS = /* wgsl */ `
// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
// by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
// code: https://github.com/selfshadow/ltc_code/

fn LTC_Uv(N: vec3f, V: vec3f, roughness: f32) -> vec2f {
    const LUT_SIZE: f32 = 64.0;
    const LUT_SCALE: f32 = (LUT_SIZE - 1.0) / LUT_SIZE;
    const LUT_BIAS: f32 = 0.5 / LUT_SIZE;
    let dotNV: f32 = saturate(dot( N, V ));
    // texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) )
    let uv: vec2f = vec2f( roughness, sqrt( 1.0 - dotNV ) );
    return uv * LUT_SCALE + LUT_BIAS;
}

fn LTC_ClippedSphereFormFactor( f: vec3f ) -> f32 {
    // Real-Time Area Lighting: a Journey from Research to Production (p.102)
    // An approximation of the form factor of a horizon-clipped rectangle.
    let l: f32 = length( f );
    return max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );
}

fn LTC_EdgeVectorFormFactor( v1: vec3f, v2: vec3f ) -> vec3f {
    let x: f32 = dot( v1, v2 );
    let y: f32 = abs( x );
    // rational polynomial approximation to theta / sin( theta ) / 2PI
    let a: f32 = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;
    let b: f32 = 3.4175940 + ( 4.1616724 + y ) * y;
    let v: f32 = a / b;
    let inv_sqrt_term = inverseSqrt( max( 1.0 - x * x, 1e-7f ) );
    let theta_sintheta: f32 = select( (0.5 * inv_sqrt_term - v), v, x > 0.0 );
    return cross( v1, v2 ) * theta_sintheta;
}

struct Coords {
    coord0: vec3f,
    coord1: vec3f,
    coord2: vec3f,
    coord3: vec3f,
}

fn LTC_EvaluateRect( N: vec3f, V: vec3f, P: vec3f, mInv: mat3x3f, rectCoords: Coords) -> f32 {
    // bail if point is on back side of plane of light
    // assumes ccw winding order of light vertices
    let v1: vec3f = rectCoords.coord1 - rectCoords.coord0;
    let v2: vec3f = rectCoords.coord3 - rectCoords.coord0;

    let lightNormal: vec3f = cross( v1, v2 );
    let factor: f32 = sign(-dot( lightNormal, P - rectCoords.coord0 ));

    // construct orthonormal basis around N
    let T1: vec3f = normalize( V - N * dot( V, N ) );
    let T2: vec3f = factor * cross( N, T1 ); // negated from paper; possibly due to a different handedness of world coordinate system
    // compute transform
    let mat: mat3x3f = mInv * transpose( mat3x3f( T1, T2, N ) );
    // transform rect
    var coords: array<vec3f, 4>;
    coords[0] = mat * ( rectCoords.coord0 - P );
    coords[1] = mat * ( rectCoords.coord1 - P );
    coords[2] = mat * ( rectCoords.coord2 - P );
    coords[3] = mat * ( rectCoords.coord3 - P );
    // project rect onto sphere
    coords[0] = normalize( coords[0] );
    coords[1] = normalize( coords[1] );
    coords[2] = normalize( coords[2] );
    coords[3] = normalize( coords[3] );
    // calculate vector form factor
    var vectorFormFactor: vec3f = vec3f( 0.0 );
    vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[0], coords[1] );
    vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[1], coords[2] );
    vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[2], coords[3] );
    vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[3], coords[0] );
    // adjust for horizon clipping
    let result: f32 = LTC_ClippedSphereFormFactor( vectorFormFactor );

    return result;
}

var<private> dLTCCoords: Coords;
fn getLTCLightCoords(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) -> Coords {
    var coords: Coords;
    coords.coord0 = lightPos + halfWidth - halfHeight;
    coords.coord1 = lightPos - halfWidth - halfHeight;
    coords.coord2 = lightPos - halfWidth + halfHeight;
    coords.coord3 = lightPos + halfWidth + halfHeight;
    return coords;
}

var<private> dSphereRadius: f32;
fn getSphereLightCoords(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) -> Coords {
    // used for simple sphere light falloff
    // also, the code only handles a spherical light, it cannot be non-uniformly scaled in world space, and so we enforce it here
    dSphereRadius = max(length(halfWidth), length(halfHeight));

    // Billboard the 2d light quad to reflection vector, as it's used for specular. This allows us to use disk math for the sphere.
    let f: vec3f = reflect(normalize(lightPos - uniform.view_position), vNormalW);
    let w: vec3f = normalize(cross(f, halfHeight));
    let h: vec3f = normalize(cross(f, w));

    return getLTCLightCoords(lightPos, w * dSphereRadius, h * dSphereRadius);
}

// used for LTC LUT texture lookup
var<private> dLTCUV: vec2f;
#ifdef LIT_CLEARCOAT
    var<private> ccLTCUV: vec2f;
#endif

fn getLTCLightUV(gloss: f32, worldNormal: vec3f, viewDir: vec3f) -> vec2f {
    let roughness: f32 = max((1.0 - gloss) * (1.0 - gloss), 0.001);
    return LTC_Uv( worldNormal, viewDir, roughness );
}

// used for energy conservation and to modulate specular
var<private> dLTCSpecFres: vec3f;
#ifdef LIT_CLEARCOAT
    var<private> ccLTCSpecFres: vec3f;
#endif

fn getLTCLightSpecFres(uv: vec2f, specularity: vec3f) -> vec3f {
    let t2: vec4f = textureSampleLevel(areaLightsLutTex2, areaLightsLutTex2Sampler, uv, 0.0);
    return specularity * t2.x + ( vec3f( 1.0 ) - specularity) * t2.y;
}

fn calcLTCLightValues(gloss: f32, worldNormal: vec3f, viewDir: vec3f, specularity: vec3f, clearcoatGloss: f32, clearcoatWorldNormal: vec3f, clearcoatSpecularity: f32) {
    dLTCUV = getLTCLightUV(gloss, worldNormal, viewDir);
    dLTCSpecFres = getLTCLightSpecFres(dLTCUV, specularity);

    #ifdef LIT_CLEARCOAT
        ccLTCUV = getLTCLightUV(clearcoatGloss, clearcoatWorldNormal, viewDir);
        ccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3f(clearcoatSpecularity));
    #endif
}

fn calcRectLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) {
    dLTCCoords = getLTCLightCoords(lightPos, halfWidth, halfHeight);
}
fn calcDiskLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) {
    calcRectLightValues(lightPos, halfWidth, halfHeight);
}
fn calcSphereLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) {
    dLTCCoords = getSphereLightCoords(lightPos, halfWidth, halfHeight);
}

// An extended version of the implementation from "How to solve a cubic equation, revisited"
// http://momentsingraphics.de/?p=105
fn SolveCubic(Coefficient_in: vec4f) -> vec3f {
    let pi: f32 = 3.14159;
    var Coefficient = Coefficient_in;

    // Normalize the polynomial
    Coefficient = vec4f(Coefficient.xyz / Coefficient.w, Coefficient.w);

    // Divide middle coefficients by three
    let new_yz: vec2f = Coefficient.yz / 3.0;
    Coefficient = vec4f(Coefficient.x, new_yz.x, new_yz.y, Coefficient.w);
    
    let A: f32 = Coefficient.w;
    let B: f32 = Coefficient.z;
    let C: f32 = Coefficient.y;
    let D: f32 = Coefficient.x;

    // Compute the Hessian and the discriminant
    let Delta: vec3f = vec3f(
        -Coefficient.z * Coefficient.z + Coefficient.y,
        -Coefficient.y * Coefficient.z + Coefficient.x,
        dot(vec2f(Coefficient.z, -Coefficient.y), Coefficient.xy)
    );

    let Discriminant: f32 = dot(vec2f(4.0 * Delta.x, -Delta.y), Delta.zy);

    var xlc: vec2f;
    var xsc: vec2f;

    // Algorithm A
    {
        let A_a: f32 = 1.0;
        let C_a: f32 = Delta.x;
        let D_a: f32 = -2.0 * B * Delta.x + Delta.y;

        // Take the cubic root of a normalized complex number
        let Theta: f32 = atan2(sqrt(Discriminant), -D_a) / 3.0;

        let sqrt_neg_Ca = sqrt(-C_a);
        let x_1a: f32 = 2.0 * sqrt_neg_Ca * cos(Theta);
        let x_3a: f32 = 2.0 * sqrt_neg_Ca * cos(Theta + (2.0 / 3.0) * pi);

        let xl: f32 = select(x_3a, x_1a, (x_1a + x_3a) > 2.0 * B);
        xlc = vec2f(xl - B, A);
    }

    // Algorithm D
    {
        let A_d: f32 = D;
        let C_d: f32 = Delta.z;
        let D_d: f32 = -D * Delta.y + 2.0 * C * Delta.z;

        // Take the cubic root of a normalized complex number
        let Theta: f32 = atan2(D * sqrt(Discriminant), -D_d) / 3.0;

        let sqrt_neg_Cd = sqrt(-C_d);
        let x_1d: f32 = 2.0 * sqrt_neg_Cd * cos(Theta);
        let x_3d: f32 = 2.0 * sqrt_neg_Cd * cos(Theta + (2.0 / 3.0) * pi);

        let xs: f32 = select(x_3d, x_1d, x_1d + x_3d < 2.0 * C);
        xsc = vec2f(-D, xs + C);
    }

    let E: f32 =  xlc.y * xsc.y;
    let F: f32 = -xlc.x * xsc.y - xlc.y * xsc.x;
    let G: f32 =  xlc.x * xsc.x;

    let xmc: vec2f = vec2f(C * F - B * G, -B * F + C * E);

    var Root: vec3f = vec3f(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y);

    if (Root.x < Root.y && Root.x < Root.z) {
        Root = Root.yxz;
    } else if (Root.z < Root.x && Root.z < Root.y) {
        Root = Root.xzy;
    }
    return Root;
}

fn LTC_EvaluateDisk(N: vec3f, V: vec3f, P: vec3f, Minv: mat3x3f, points: Coords) -> f32 {
    // construct orthonormal basis around N
    let T1: vec3f = normalize(V - N * dot(V, N));
    let T2: vec3f = cross(N, T1);

    // rotate area light in (T1, T2, N) basis
    let R: mat3x3f = transpose( mat3x3f( T1, T2, N ) );
    // polygon (allocate 5 vertices for clipping
    var L_: array<vec3f, 3>;
    L_[0] = R * ( points.coord0 - P );
    L_[1] = R * ( points.coord1 - P );
    L_[2] = R * ( points.coord2 - P );

    // init ellipse
    let C: vec3f  = 0.5 * (L_[0] + L_[2]);
    var V1: vec3f = 0.5 * (L_[1] - L_[2]);
    var V2: vec3f = 0.5 * (L_[1] - L_[0]);

    let C_Minv: vec3f  = Minv * C;
    let V1_Minv: vec3f = Minv * V1;
    let V2_Minv: vec3f = Minv * V2;

    // compute eigenvectors of ellipse
    var a: f32;
    var b: f32;
    let d11: f32 = dot(V1_Minv, V1_Minv);
    let d22: f32 = dot(V2_Minv, V2_Minv);
    let d12: f32 = dot(V1_Minv, V2_Minv);
    if (abs(d12) / sqrt(d11 * d22) > 0.0001) {
        let tr: f32 = d11 + d22;
        let det_inner: f32 = -d12 * d12 + d11 * d22;
        let det: f32 = sqrt(det_inner);
        let u: f32 = 0.5 * sqrt(tr - 2.0 * det);
        let v: f32 = 0.5 * sqrt(tr + 2.0 * det);
        let e_max: f32 = (u + v) * (u + v);
        let e_min: f32 = (u - v) * (u - v);

        var V1_: vec3f;
        var V2_: vec3f;

        if (d11 > d22) {
            V1_ = d12 * V1_Minv + (e_max - d11) * V2_Minv;
            V2_ = d12 * V1_Minv + (e_min - d11) * V2_Minv;
        } else {
            V1_ = d12*V2_Minv + (e_max - d22)*V1_Minv;
            V2_ = d12*V2_Minv + (e_min - d22)*V1_Minv;
        }

        a = 1.0 / e_max;
        b = 1.0 / e_min;
        V1 = normalize(V1_);
        V2 = normalize(V2_);
    } else {
        a = 1.0 / dot(V1_Minv, V1_Minv);
        b = 1.0 / dot(V2_Minv, V2_Minv);
        V1 = V1_Minv * sqrt(a);
        V2 = V2_Minv * sqrt(b);
    }

    var V3: vec3f = normalize(cross(V1, V2));
    if (dot(C_Minv, V3) < 0.0) {
        V3 = V3 * -1.0;
    }

    let L: f32  = dot(V3, C_Minv);
    let x0: f32 = dot(V1, C_Minv) / L;
    let y0: f32 = dot(V2, C_Minv) / L;

    let E1: f32 = inverseSqrt(a);
    let E2: f32 = inverseSqrt(b);

    let a_scaled = a * L * L;
    let b_scaled = b * L * L;

    let c0: f32 = a_scaled * b_scaled;
    let c1: f32 = a_scaled * b_scaled * (1.0 + x0 * x0 + y0 * y0) - a_scaled - b_scaled;
    let c2: f32 = 1.0 - a_scaled * (1.0 + x0 * x0) - b_scaled * (1.0 + y0 * y0);
    let c3: f32 = 1.0;

    let roots: vec3f = SolveCubic(vec4f(c0, c1, c2, c3));
    let e1: f32 = roots.x;
    let e2: f32 = roots.y;
    let e3: f32 = roots.z;

    var avgDir: vec3f = vec3f(a_scaled * x0 / (a_scaled - e2), b_scaled * y0 / (b_scaled - e2), 1.0);

    let rotate: mat3x3f = mat3x3f(V1, V2, V3);

    avgDir = rotate * avgDir;
    avgDir = normalize(avgDir);

    let L1: f32 = sqrt(-e2 / e3);
    let L2: f32 = sqrt(-e2 / e1);

    let formFactor: f32 = max(0.0, L1 * L2 * inverseSqrt((1.0 + L1 * L1) * (1.0 + L2 * L2)));

    const LUT_SIZE_disk: f32 = 64.0;
    const LUT_SCALE_disk: f32 = ( LUT_SIZE_disk - 1.0 ) / LUT_SIZE_disk;
    const LUT_BIAS_disk: f32 = 0.5 / LUT_SIZE_disk;

    // use tabulated horizon-clipped sphere
    var uv: vec2f = vec2f(avgDir.z * 0.5 + 0.5, formFactor);
    uv = uv * LUT_SCALE_disk + LUT_BIAS_disk;

    let scale: f32 = textureSampleLevel(areaLightsLutTex2, areaLightsLutTex2Sampler, uv, 0.0).w;

    return formFactor * scale;
}

// LTC_EvaluateDisk in some rare cases genereates NaN values in a or b, just before 'float c0 = a * b;'
// Get rid of those Nan values before they propagate further, as in case of bloom / DOF blurs they
// propagate to large areas. I didn't find the actual reason where those come from, so that is still TODO.
// Note that only disk/sphere lights are causing it, so only handle those.
fn FixNan(value: f32) -> f32 {
    // use value != value check for NaN as isnan() is not available in WGSL
    return select(value, 0.0, value != value);
}

fn getRectLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 {
    let identityMat = mat3x3f(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0));
    return LTC_EvaluateRect( worldNormal, viewDir, vPositionW, identityMat, dLTCCoords );
}

fn getDiskLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 {
    let identityMat = mat3x3f(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0));
    return FixNan(LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, identityMat, dLTCCoords ));
}

fn getSphereLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 {
    // NB: this could be improved further with distance based wrap lighting
    let falloff: f32 = dSphereRadius / (dot(lightDir, lightDir) + dSphereRadius);
    return FixNan(getLightDiffuse(worldNormal, viewDir, lightDirNorm) * falloff);
}

fn getLTCLightInvMat(uv: vec2f) -> mat3x3f {
    let t1: vec4f = textureSampleLevel(areaLightsLutTex1, areaLightsLutTex1Sampler, uv, 0.0);

    return mat3x3f(
        vec3f( t1.x, 0.0, t1.y ),
        vec3f( 0.0, 1.0, 0.0 ),
        vec3f( t1.z, 0.0, t1.w )
    );
}

fn calcRectLightSpecular(worldNormal: vec3f, viewDir: vec3f, uv: vec2f) -> f32 {
    let mInv: mat3x3f = getLTCLightInvMat(uv);
    return LTC_EvaluateRect( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );
}

fn getRectLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 {
    return calcRectLightSpecular(worldNormal, viewDir, dLTCUV);
}

fn calcDiskLightSpecular(worldNormal: vec3f, viewDir: vec3f, uv: vec2f) -> f32 {
    let mInv: mat3x3f = getLTCLightInvMat(uv);
    return LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );
}

fn getDiskLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 {
    return calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);
}

fn getSphereLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 {
    return calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);
}
`;

var metalnessPS = /* wgsl */ `
#ifdef STD_METALNESS_CONSTANT
uniform material_metalness: f32;
#endif

fn getMetalness() {
    var metalness: f32 = 1.0;

    #ifdef STD_METALNESS_CONSTANT
        metalness = metalness * uniform.material_metalness;
    #endif

    #ifdef STD_METALNESS_TEXTURE
        metalness = metalness * textureSampleBias({STD_METALNESS_TEXTURE_NAME}, {STD_METALNESS_TEXTURE_NAME}Sampler, {STD_METALNESS_TEXTURE_UV}, uniform.textureBias).{STD_METALNESS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_METALNESS_VERTEX
    metalness = metalness * saturate(vVertexColor.{STD_METALNESS_VERTEX_CHANNEL});
    #endif

    dMetalness = metalness;
}
`;

var msdfPS = /* wgsl */ `
var texture_msdfMap: texture_2d<f32>;
var texture_msdfMapSampler: sampler;

fn median(r: f32, g: f32, b: f32) -> f32 {
    return max(min(r, g), min(max(r, g), b));
}

fn map(min: f32, max: f32, v: f32) -> f32 {
    return (v - min) / (max - min);
}

uniform font_sdfIntensity: f32; // intensity is used to boost the value read from the SDF, 0 is no boost, 1.0 is max boost
uniform font_pxrange: f32;      // the number of pixels between inside and outside the font in SDF
uniform font_textureWidth: f32; // the width of the texture atlas

#ifndef LIT_MSDF_TEXT_ATTRIBUTE
    uniform outline_color: vec4f;
    uniform outline_thickness: f32;
    uniform shadow_color: vec4f;
    uniform shadow_offset: vec2f;
#else
    varying outline_color: vec4f;
    varying outline_thickness: f32;
    varying shadow_color: vec4f;
    varying shadow_offset: vec2f;
#endif

fn applyMsdf(color_in: vec4f) -> vec4f {

    #ifndef LIT_MSDF_TEXT_ATTRIBUTE
        var outline_colorValue = uniform.outline_color;
        var outline_thicknessValue = uniform.outline_thickness;
        var shadow_colorValue = uniform.shadow_color;
        var shadow_offsetValue = uniform.shadow_offset;
    #else
        var outline_colorValue = outline_color;
        var outline_thicknessValue = outline_thickness;
        var shadow_colorValue = shadow_color;
        var shadow_offsetValue = shadow_offset;
    #endif

    // Convert to linear space before processing
    // TODO: ideally this would receive the color in linear space, but that would require larger changes
    // on the engine side, with the way premultiplied alpha is handled as well.
    var color = vec4f(gammaCorrectInputVec3(color_in.rgb), color_in.a);

    // sample the field
    let tsample: vec3f = textureSample(texture_msdfMap, texture_msdfMapSampler, vUv0).rgb;
    let uvShdw: vec2f = vUv0 - shadow_offsetValue;
    let ssample: vec3f = textureSample(texture_msdfMap, texture_msdfMapSampler, uvShdw).rgb;

    // get the signed distance value
    let sigDist: f32 = median(tsample.r, tsample.g, tsample.b);
    var sigDistShdw: f32 = median(ssample.r, ssample.g, ssample.b);

    // smoothing limit - smaller value makes for sharper but more aliased text, especially on angles
    // too large value (0.5) creates a dark glow around the letters
    let smoothingMax: f32 = 0.2;

    // smoothing depends on size of texture on screen
    let w: vec2f = abs(dpdx(vUv0)) + abs(dpdy(vUv0));
    let smoothing: f32 = clamp(w.x * uniform.font_textureWidth / uniform.font_pxrange, 0.0, smoothingMax);

    let mapMin: f32 = 0.05;
    let mapMax: f32 = clamp(1.0 - uniform.font_sdfIntensity, mapMin, 1.0);

    // remap to a smaller range (used on smaller font sizes)
    let sigDistInner: f32 = map(mapMin, mapMax, sigDist);
    let sigDistOutline: f32 = map(mapMin, mapMax, sigDist + outline_thicknessValue);
    sigDistShdw = map(mapMin, mapMax, sigDistShdw + outline_thicknessValue);

    let center: f32 = 0.5;
    // calculate smoothing and use to generate opacity
    let inside: f32 = smoothstep(center - smoothing, center + smoothing, sigDistInner);
    let outline: f32 = smoothstep(center - smoothing, center + smoothing, sigDistOutline);
    let shadow: f32 = smoothstep(center - smoothing, center + smoothing, sigDistShdw);

    let tcolor_outline: vec4f = outline * vec4f(outline_colorValue.a * outline_colorValue.rgb, outline_colorValue.a);
    var tcolor: vec4f = select(vec4f(0.0), tcolor_outline, outline > inside);
    tcolor = mix(tcolor, color, inside);

    let scolor_shadow: vec4f = shadow * vec4f(shadow_colorValue.a * shadow_colorValue.rgb, shadow_colorValue.a);
    let scolor: vec4f = select(tcolor, scolor_shadow, shadow > outline);
    tcolor = mix(scolor, tcolor, outline);

    // Convert back to gamma space before returning
    tcolor = vec4f(gammaCorrectOutput(tcolor.rgb), tcolor.a);

    return tcolor;
}
`;

var metalnessModulatePS = /* glsl */ `

fn getSpecularModulate(specularity: vec3f, albedo: vec3f, metalness: f32, f0: f32) -> vec3f {
    let dielectricF0: vec3f = f0 * specularity;
    return mix(dielectricF0, albedo, metalness);
}

fn getAlbedoModulate(albedo: vec3f, metalness: f32) -> vec3f {
    return albedo * (1.0 - metalness);
}
`;

// fragment shader internally used to apply morph targets in textures into a final texture containing
// blended morph targets
var morphPS = /* wgsl */ `

    varying uv0: vec2f;

    var morphTexture: texture_2d_array<f32>;
    uniform morphFactor: array<f32, {MORPH_TEXTURE_MAX_COUNT}>;
    uniform morphIndex: array<u32, {MORPH_TEXTURE_MAX_COUNT}>;
    uniform count: u32;

    @fragment
    fn fragmentMain(input : FragmentInput) -> FragmentOutput {
        var color = vec3f(0, 0, 0);
        let textureDims = textureDimensions(morphTexture);
        let pixelCoords = vec2i(input.uv0 * vec2f(textureDims));
        
        for (var i: u32 = 0; i < uniform.count; i = i + 1) {
            var textureIndex: u32 = uniform.morphIndex[i].element;
            var delta = textureLoad(morphTexture, pixelCoords, textureIndex, 0).xyz;
            color += uniform.morphFactor[i].element * delta;
        }

        var output: FragmentOutput;
        output.color = vec4f(color, 1.0);
        return output;
    }
`;

// vertex shader internally used to apply morph targets in textures into a final texture containing
// blended morph targets
var morphVS = /* wgsl */ `
    attribute vertex_position: vec2f;
    varying uv0: vec2f;

    @vertex
    fn vertexMain(input: VertexInput) -> VertexOutput {
        var output: VertexOutput;
        output.position = vec4f(input.vertex_position, 0.5, 1.0);
        output.uv0 = input.vertex_position * 0.5 + vec2f(0.5, 0.5);
        return output;
    }
`;

var msdfVS = /* wgsl */ `
attribute vertex_outlineParameters: vec3f;
attribute vertex_shadowParameters: vec3f;

varying outline_color: vec4f;
varying outline_thickness: f32;
varying shadow_color: vec4f;
varying shadow_offset: vec2f;

var<private> dOutlineColor: vec4f;
var<private> dOutlineThickness: f32;
var<private> dShadowColor: vec4f;
var<private> dShadowOffset: vec2f;

fn unpackMsdfParams() {
    let little: vec3f = vertex_outlineParameters % vec3f(256.0);
    let big: vec3f = (vertex_outlineParameters - little) / 256.0;

    dOutlineColor = vec4f(little.x, big.x, little.y, big.y) / 255.0;

    // _outlineThicknessScale === 0.2
    dOutlineThickness = little.z / 255.0 * 0.2;

    let little_shadow = vertex_shadowParameters % vec3f(256.0);
    let big_shadow = (vertex_shadowParameters - little_shadow) / 256.0;

    dShadowColor = vec4f(little_shadow.x, big_shadow.x, little_shadow.y, big_shadow.y) / 255.0;

    // vec2(little.z, big.z) / 127. - 1. remaps shadow offset from [0, 254] to [-1, 1]
    // _shadowOffsetScale === 0.005
    dShadowOffset = (vec2f(little_shadow.z, big_shadow.z) / 127.0 - 1.0) * 0.005;
}
`;

var normalVS = /* wgsl */ `
var<private> dNormalMatrix: mat3x3f;

fn getNormal() -> vec3f {
    dNormalMatrix = getNormalMatrix(dModelMatrix);
    let localNormal: vec3f = getLocalNormal(vertex_normal);
    return normalize(dNormalMatrix * localNormal);
}`;

var normalCoreVS = /* wgsl */ `

attribute vertex_normal: vec3f;

uniform matrix_normal: mat3x3f;

#ifdef MORPHING_NORMAL
    #ifdef MORPHING_INT
        var morphNormalTex: texture_2d<u32>;
        var morphNormalTexSampler: sampler;
    #else
        var morphNormalTex: texture_2d<f32>;
        var morphNormalTexSampler: sampler;
    #endif
#endif

fn getLocalNormal(vertexNormal: vec3f) -> vec3f {

    var localNormal: vec3f = vertexNormal;

    #ifdef MORPHING_NORMAL

        let morphUV: vec2i = getTextureMorphCoords();

        #ifdef MORPHING_INT
            let morphNormalInt: vec4u = textureLoad(morphNormalTex, morphUV, 0);
            let morphNormalF: vec3f = vec3f(morphNormalInt.xyz) / 65535.0 * 2.0 - 1.0;
            localNormal = localNormal + morphNormalF;
        #else
            let morphNormal: vec3f = textureLoad(morphNormalTex, morphUV, 0).xyz;
            localNormal = localNormal + morphNormal;
        #endif

    #endif

    return localNormal;
}

#if defined(SKIN) || defined(BATCH)
    fn getNormalMatrix(modelMatrix: mat4x4f) -> mat3x3f {
        return mat3x3f(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);
    }
#elif defined(INSTANCING)
    fn getNormalMatrix(modelMatrix: mat4x4f) -> mat3x3f {
        return mat3x3f(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);
    }
#else
    fn getNormalMatrix(modelMatrix: mat4x4f) -> mat3x3f {
        return uniform.matrix_normal;
    }
#endif
`;

var normalMapPS = /* wgsl */ `
#ifdef STD_NORMAL_TEXTURE
    uniform material_bumpiness: f32;
#endif

#ifdef STD_NORMALDETAIL_TEXTURE
    uniform material_normalDetailMapBumpiness: f32;

    // https://blog.selfshadow.com/publications/blending-in-detail/#detail-oriented
    fn blendNormals(inN1: vec3f, inN2: vec3f) -> vec3f {
        let n1: vec3f = inN1 + vec3f(0.0, 0.0, 1.0);
        let n2: vec3f = inN2 * vec3f(-1.0, -1.0, 1.0);
        return n1 * dot(n1, n2) / n1.z - n2;
    }
#endif

fn getNormal() {
#ifdef STD_NORMAL_TEXTURE
    var normalMap: vec3f = {STD_NORMAL_TEXTURE_DECODE}(textureSampleBias({STD_NORMAL_TEXTURE_NAME}, {STD_NORMAL_TEXTURE_NAME}Sampler, {STD_NORMAL_TEXTURE_UV}, uniform.textureBias));
    normalMap = mix(vec3f(0.0, 0.0, 1.0), normalMap, uniform.material_bumpiness);

    #ifdef STD_NORMALDETAIL_TEXTURE
        var normalDetailMap: vec3f = {STD_NORMALDETAIL_TEXTURE_DECODE}(textureSampleBias({STD_NORMALDETAIL_TEXTURE_NAME}, {STD_NORMALDETAIL_TEXTURE_NAME}Sampler, {STD_NORMALDETAIL_TEXTURE_UV}, uniform.textureBias));
        normalDetailMap = mix(vec3f(0.0, 0.0, 1.0), normalDetailMap, uniform.material_normalDetailMapBumpiness);
        normalMap = blendNormals(normalMap, normalDetailMap);
    #endif

    dNormalW = normalize(dTBN * normalMap);
#else
    dNormalW = dVertexNormalW;
#endif
}
`;

var opacityPS = /* wgsl */ `
uniform material_opacity: f32;

fn getOpacity() {
    dAlpha = uniform.material_opacity;

    #ifdef STD_OPACITY_TEXTURE
    dAlpha = dAlpha * textureSampleBias({STD_OPACITY_TEXTURE_NAME}, {STD_OPACITY_TEXTURE_NAME}Sampler, {STD_OPACITY_TEXTURE_UV}, uniform.textureBias).{STD_OPACITY_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_OPACITY_VERTEX
    dAlpha = dAlpha * clamp(vVertexColor.{STD_OPACITY_VERTEX_CHANNEL}, 0.0, 1.0);
    #endif
}
`;

var opacityDitherPS = /* wgsl */ `

#if STD_OPACITY_DITHER == BAYER8
    #include "bayerPS"
#endif

uniform blueNoiseJitter: vec4f;

#if STD_OPACITY_DITHER == BLUENOISE
    var blueNoiseTex32 : texture_2d<f32>;
    var blueNoiseTex32Sampler : sampler;
#endif

fn opacityDither(alpha: f32, id: f32) {
    #if STD_OPACITY_DITHER == BAYER8

        var noise: f32 = bayer8(floor((pcPosition.xy + uniform.blueNoiseJitter.xy + id) % vec2f(8.0))) / 64.0;

    #else

        #if STD_OPACITY_DITHER == BLUENOISE
            var uv = fract(pcPosition.xy / 32.0 + uniform.blueNoiseJitter.xy + id);
            var noise: f32 = textureSampleLevel(blueNoiseTex32, blueNoiseTex32Sampler, uv, 0.0).y;
        #endif

        #if STD_OPACITY_DITHER == IGNNOISE
            // based on https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
            var magic = vec3f(0.06711056, 0.00583715, 52.9829189);
            var noise: f32 = fract(magic.z * fract(dot(pcPosition.xy + uniform.blueNoiseJitter.xy + id, magic.xy)));
        #endif

    #endif

    // convert the noise to linear space, as that is specified in sRGB space (stores perceptual values)
    noise = pow(noise, 2.2);

    if (alpha < noise) {
        discard;
    }
}
`;

var outputPS = /* wgsl */ `
`;

var outputAlphaPS = /* wgsl */ `

#if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == ADDITIVEALPHA || defined(LIT_ALPHA_TO_COVERAGE)

    output.color = vec4f(output.color.rgb, litArgs_opacity);

#elif LIT_BLEND_TYPE == PREMULTIPLIED

    output.color = vec4f(output.color.rgb * litArgs_opacity, litArgs_opacity);

#else

    output.color = vec4f(output.color.rgb, 1.0);

#endif
`;

var outputTex2DPS = /* wgsl */ `
varying vUv0: vec2f;
var source: texture_2d<f32>;
var sourceSampler: sampler;

@fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;
    output.color = textureSample(source, sourceSampler, input.vUv0);
    return output;
}
`;

var sheenPS = /* wgsl */ `

uniform material_sheen: vec3f;

fn getSheen() {
    var sheenColor = uniform.material_sheen;

    #ifdef STD_SHEEN_TEXTURE
    sheenColor = sheenColor * {STD_SHEEN_TEXTURE_DECODE}(textureSampleBias({STD_SHEEN_TEXTURE_NAME}, {STD_SHEEN_TEXTURE_NAME}Sampler, {STD_SHEEN_TEXTURE_UV}, uniform.textureBias)).{STD_SHEEN_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SHEEN_VERTEX
    sheenColor = sheenColor * saturate3(vVertexColor.{STD_SHEEN_VERTEX_CHANNEL});
    #endif

    sSpecularity = sheenColor;
}
`;

var sheenGlossPS = /* wgsl */ `
uniform material_sheenGloss: f32;

fn getSheenGlossiness() {
    var sheenGlossiness = uniform.material_sheenGloss;

    #ifdef STD_SHEENGLOSS_TEXTURE
    sheenGlossiness = sheenGlossiness * textureSampleBias({STD_SHEENGLOSS_TEXTURE_NAME}, {STD_SHEENGLOSS_TEXTURE_NAME}Sampler, {STD_SHEENGLOSS_TEXTURE_UV}, uniform.textureBias).{STD_SHEENGLOSS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SHEENGLOSS_VERTEX
    sheenGlossiness = sheenGlossiness * saturate(vVertexColor.{STD_SHEENGLOSS_VERTEX_CHANNEL});
    #endif

    #ifdef STD_SHEENGLOSS_INVERT
    sheenGlossiness = 1.0 - sheenGlossiness;
    #endif

    sGlossiness = sheenGlossiness + 0.0000001;
}
`;

var parallaxPS = /* wgsl */ `
uniform material_heightMapFactor: f32;

fn getParallax() {
    var parallaxScale = uniform.material_heightMapFactor;

    var height: f32 = textureSampleBias({STD_HEIGHT_TEXTURE_NAME}, {STD_HEIGHT_TEXTURE_NAME}Sampler, {STD_HEIGHT_TEXTURE_UV}, uniform.textureBias).{STD_HEIGHT_TEXTURE_CHANNEL};
    height = height * parallaxScale - parallaxScale * 0.5;
    var viewDirT: vec3f = dViewDirW * dTBN;

    viewDirT.z = viewDirT.z + 0.42;
    dUvOffset = height * (viewDirT.xy / viewDirT.z);
}
`;

var pickPS = /* wgsl */ `
uniform meshInstanceId: u32;

fn getPickOutput() -> vec4f {
    let inv: vec4f = vec4f(1.0 / 255.0);
    let shifts: vec4u = vec4u(16u, 8u, 0u, 24u);
    let col: vec4u = (vec4u(uniform.meshInstanceId) >> shifts) & vec4u(0xffu);
    return vec4f(col) * inv;
}`;

var reflDirPS = /* wgsl */ `
fn getReflDir(worldNormal: vec3f, viewDir: vec3f, gloss: f32, tbn: mat3x3f) {
    dReflDirW = normalize(-reflect(viewDir, worldNormal));
}
`;

var reflDirAnisoPS = /* wgsl */ `
fn getReflDir(worldNormal: vec3f, viewDir: vec3f, gloss: f32, tbn: mat3x3f) {
    let roughness: f32 = sqrt(1.0 - min(gloss, 1.0));

    let direction: vec2f = dAnisotropyRotation;
    let anisotropicT: vec3f = normalize(tbn * vec3f(direction, 0.0));
    let anisotropicB: vec3f = normalize(cross(tbn[2], anisotropicT));

    let anisotropy: f32 = dAnisotropy;
    let anisotropicDirection: vec3f = anisotropicB;
    let anisotropicTangent: vec3f = cross(anisotropicDirection, viewDir);
    let anisotropicNormal: vec3f = cross(anisotropicTangent, anisotropicDirection);
    let bendFactor: f32 = 1.0 - anisotropy * (1.0 - roughness);
    let bendFactor4: f32 = bendFactor * bendFactor * bendFactor * bendFactor;
    let bentNormal: vec3f = normalize(mix(normalize(anisotropicNormal), normalize(worldNormal), bendFactor4));
    dReflDirW = reflect(-viewDir, bentNormal);
}`;

var reflectionCCPS = /* wgsl */ `
#ifdef LIT_CLEARCOAT
fn addReflectionCC(reflDir: vec3f, gloss: f32) {
    ccReflection = ccReflection + calcReflection(reflDir, gloss);
}
#endif
`;

var reflectionCubePS = /* wgsl */ `
var texture_cubeMap: texture_cube<f32>;
var texture_cubeMapSampler: sampler;
uniform material_reflectivity: f32;

fn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {
    var lookupVec: vec3f = cubeMapProject(reflDir);
    lookupVec.x = lookupVec.x * -1.0;
    return {reflectionDecode}(textureSample(texture_cubeMap, texture_cubeMapSampler, lookupVec));
}

fn addReflection(reflDir: vec3f, gloss: f32) {
    dReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);
}
`;

var reflectionEnvHQPS = /* wgsl */ `
#ifndef ENV_ATLAS
    #define ENV_ATLAS
    var texture_envAtlas: texture_2d<f32>;
    var texture_envAtlasSampler: sampler;
#endif

var texture_cubeMap: texture_cube<f32>;
var texture_cubeMapSampler: sampler;
uniform material_reflectivity: f32;

fn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {
    let dir: vec3f = cubeMapProject(reflDir) * vec3f(-1.0, 1.0, 1.0);
    let uv: vec2f = toSphericalUv(dir);

    // calculate roughness level
    let level: f32 = saturate(1.0 - gloss) * 5.0;
    let ilevel: f32 = floor(level);
    let flevel: f32 = level - ilevel;

    let sharp: vec3f = {reflectionCubemapDecode}(textureSample(texture_cubeMap, texture_cubeMapSampler, dir));
    let roughA: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, mapRoughnessUv(uv, ilevel)));
    let roughB: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, mapRoughnessUv(uv, ilevel + 1.0)));

    return processEnvironment(mix(sharp, mix(roughA, roughB, flevel), min(level, 1.0)));
}

fn addReflection(reflDir: vec3f, gloss: f32) {
    dReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);
}
`;

var reflectionEnvPS = /* wgsl */ `
#ifndef ENV_ATLAS
#define ENV_ATLAS
    var texture_envAtlas: texture_2d<f32>;
    var texture_envAtlasSampler: sampler;
#endif
uniform material_reflectivity: f32;

// calculate mip level for shiny reflection given equirect coords uv.
fn shinyMipLevel(uv: vec2f) -> f32 {
    let dx: vec2f = dpdx(uv);
    let dy: vec2f = dpdy(uv);

    // calculate second dF at 180 degrees
    let uv2: vec2f = vec2f(fract(uv.x + 0.5), uv.y);
    let dx2: vec2f = dpdx(uv2);
    let dy2: vec2f = dpdy(uv2);

    // calculate min of both sets of dF to handle discontinuity at the azim edge
    let maxd: f32 = min(max(dot(dx, dx), dot(dy, dy)), max(dot(dx2, dx2), dot(dy2, dy2)));

    return clamp(0.5 * log2(maxd) - 1.0 + uniform.textureBias, 0.0, 5.0);
}

fn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {
    let dir: vec3f = cubeMapProject(reflDir) * vec3f(-1.0, 1.0, 1.0);
    let uv: vec2f = toSphericalUv(dir);

    // calculate roughness level
    let level: f32 = saturate(1.0 - gloss) * 5.0;
    let ilevel: f32 = floor(level);

    // accessing the shiny (top level) reflection - perform manual mipmap lookup
    let level2: f32 = shinyMipLevel(uv * atlasSize);
    let ilevel2: f32 = floor(level2);

    var uv0: vec2f;
    var uv1: vec2f;
    var weight: f32;
    if (ilevel == 0.0) {
        uv0 = mapShinyUv(uv, ilevel2);
        uv1 = mapShinyUv(uv, ilevel2 + 1.0);
        weight = level2 - ilevel2;
    } else {
        // accessing rough reflection - just sample the same part twice
        uv0 = mapRoughnessUv(uv, ilevel);
        uv1 = uv0;
        weight = 0.0;
    }

    let linearA: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, uv0));
    let linearB: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, uv1));
    let linear0: vec3f = mix(linearA, linearB, weight);
    let linear1: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, mapRoughnessUv(uv, ilevel + 1.0)));

    return processEnvironment(mix(linear0, linear1, level - ilevel));
}

fn addReflection(reflDir: vec3f, gloss: f32) {
    dReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);
}
`;

var reflectionSpherePS = /* wgsl */ `
#ifndef VIEWMATRIX
    #define VIEWMATRIX
    uniform matrix_view: mat4x4f;
#endif

var texture_sphereMap: texture_2d<f32>;
var texture_sphereMapSampler: sampler;
uniform material_reflectivity: f32;

fn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {
    let viewRotationMatrix = mat3x3f(uniform.matrix_view[0].xyz, uniform.matrix_view[1].xyz, uniform.matrix_view[2].xyz);
    let reflDirV: vec3f = viewRotationMatrix * reflDir;

    let m: f32 = 2.0 * sqrt(dot(reflDirV.xy, reflDirV.xy) + (reflDirV.z + 1.0) * (reflDirV.z + 1.0));
    let sphereMapUv: vec2f = reflDirV.xy / m + 0.5;

    return {reflectionDecode}(textureSample(texture_sphereMap, texture_sphereMapSampler, sphereMapUv));
}

fn addReflection(reflDir: vec3f, gloss: f32) {
    dReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);
}
`;

var reflectionSheenPS = /* glsl */ `

fn addReflectionSheen(worldNormal: vec3f, viewDir: vec3f, gloss: f32) {
    let NoV: f32 = dot(worldNormal, viewDir);
    let alphaG: f32 = gloss * gloss;

    // Avoid using a LUT and approximate the values analytically
    let a: f32 = select(
        -8.48 * alphaG + 14.3 * gloss - 9.95,
        -339.2 * alphaG + 161.4 * gloss - 25.9,
        gloss < 0.25
    );
    let b: f32 = select(
        1.97 * alphaG - 3.27 * gloss + 0.72,
        44.0 * alphaG - 23.7 * gloss + 3.26,
        gloss < 0.25
    );
    let dg_add: f32 = select(
        0.1 * ( gloss - 0.25 ),
        0.0,
        gloss < 0.25
    );
    let dg: f32 = exp( a * NoV + b ) + dg_add;
    sReflection = sReflection + (calcReflection(worldNormal, 0.0) * saturate(dg));
}`;

var refractionCubePS = /* wgsl */ `
fn refract2(viewVec: vec3f, normal: vec3f, IOR: f32) -> vec3f {
    let vn: f32 = dot(viewVec, normal);
    let k: f32 = 1.0 - IOR * IOR * (1.0 - vn * vn);
    let refrVec: vec3f = IOR * viewVec - (IOR * vn + sqrt(k)) * normal;
    return refrVec;
}

fn addRefraction(
    worldNormal: vec3f,
    viewDir: vec3f,
    thickness: f32,
    gloss: f32,
    specularity: vec3f,
    albedo: vec3f,
    transmission: f32,
    refractionIndex: f32,
    dispersion: f32
#if defined(LIT_IRIDESCENCE)
    , iridescenceFresnel: vec3f,
    iridescenceIntensity: f32
#endif
) {
    // use same reflection code with refraction vector
    let tmpRefl: vec4f = dReflection;
    let reflectionDir: vec3f = refract2(-viewDir, worldNormal, refractionIndex);
    dReflection = vec4f(0.0);
    addReflection(reflectionDir, gloss);
    dDiffuseLight = mix(dDiffuseLight, dReflection.rgb * albedo, transmission);
    dReflection = tmpRefl;
}
`;

var refractionDynamicPS = /* wgsl */ `
uniform material_invAttenuationDistance: f32;
uniform material_attenuation: vec3f;

fn evalRefractionColor(refractionVector: vec3f, gloss: f32, refractionIndex: f32) -> vec3f {

    // The refraction point is the entry point + vector to exit point
    let pointOfRefraction: vec4f = vec4f(vPositionW + refractionVector, 1.0);

    // Project to texture space so we can sample it
    let projectionPoint: vec4f = uniform.matrix_viewProjection * pointOfRefraction;

    // use built-in getGrabScreenPos function to convert screen position to grab texture uv coords
    let uv: vec2f = getGrabScreenPos(projectionPoint);

    // Use IOR and roughness to select mip
    let iorToRoughness: f32 = (1.0 - gloss) * clamp((1.0 / refractionIndex) * 2.0 - 2.0, 0.0, 1.0);
    let refractionLod: f32 = log2(uniform.uScreenSize.x) * iorToRoughness;
    let refraction: vec3f = textureSampleLevel(uSceneColorMap, uSceneColorMapSampler, uv, refractionLod).rgb;

    return refraction;
}

fn addRefraction(
    worldNormal: vec3f,
    viewDir: vec3f,
    thickness: f32,
    gloss: f32,
    specularity: vec3f,
    albedo: vec3f,
    transmission: f32,
    refractionIndex: f32,
    dispersion: f32,
#if defined(LIT_IRIDESCENCE)
    iridescenceFresnel: vec3f,
    iridescenceIntensity: f32
#endif
) {

    // Extract scale from the model transform
    var modelScale: vec3f;
    modelScale.x = length(uniform.matrix_model[0].xyz);
    modelScale.y = length(uniform.matrix_model[1].xyz);
    modelScale.z = length(uniform.matrix_model[2].xyz);

    // Calculate the refraction vector, scaled by the thickness and scale of the object
    let scale: vec3f = thickness * modelScale;
    var refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndex)) * scale;
    var refraction = evalRefractionColor(refractionVector, gloss, refractionIndex);

    #ifdef LIT_DISPERSION
        // based on the dispersion material property, calculate modified refraction index values
        // for R and B channels and evaluate the refraction color for them.
        let halfSpread: f32 = (1.0 / refractionIndex - 1.0) * 0.025 * dispersion;

        let refractionIndexR: f32 = refractionIndex - halfSpread;
        refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexR)) * scale;
        refraction.r = evalRefractionColor(refractionVector, gloss, refractionIndexR).r;

        let refractionIndexB: f32 = refractionIndex + halfSpread;
        refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexB)) * scale;
        refraction.b = evalRefractionColor(refractionVector, gloss, refractionIndexB).b;
    #endif

    // Transmittance is our final refraction color
    var transmittance: vec3f;
    if (uniform.material_invAttenuationDistance != 0.0)
    {
        let attenuation: vec3f = -log(uniform.material_attenuation) * uniform.material_invAttenuationDistance;
        transmittance = exp(-attenuation * length(refractionVector));
    }
    else
    {
        transmittance = refraction;
    }

    // Apply fresnel effect on refraction
    let fresnel: vec3f = vec3f(1.0) -
        getFresnel(
            dot(viewDir, worldNormal),
            gloss,
            specularity
        #if defined(LIT_IRIDESCENCE)
            , iridescenceFresnel,
            iridescenceIntensity
        #endif
        );
    dDiffuseLight = mix(dDiffuseLight, refraction * transmittance * fresnel, transmission);
}
`;

// This shader requires the following #DEFINEs:
//
// PROCESS_FUNC - must be one of reproject, prefilter
// DECODE_FUNC - must be one of decodeRGBM, decodeRGBE, decodeGamma or decodeLinear
// ENCODE_FUNC - must be one of encodeRGBM, encodeRGBE, encideGamma or encodeLinear
// SOURCE_FUNC - must be one of sampleCubemap, sampleEquirect, sampleOctahedral
// TARGET_FUNC - must be one of getDirectionCubemap, getDirectionEquirect, getDirectionOctahedral
//
// When filtering:
// NUM_SAMPLES - number of samples
// NUM_SAMPLES_SQRT - sqrt of number of samples
var reprojectPS = /* wgsl */ `

varying vUv0: vec2f;

#ifdef CUBEMAP_SOURCE
    var sourceCube: texture_cube<f32>;
    var sourceCubeSampler : sampler;
#else
    var sourceTex: texture_2d<f32>;
    var sourceTexSampler : sampler;
#endif

#ifdef USE_SAMPLES_TEX
    // samples
    var samplesTex: texture_2d<f32>;
    var samplesTexSampler : sampler;
    uniform samplesTexInverseSize: vec2f;
#endif

// params:
// x - target cubemap face 0..6
// y - target image total pixels
// z - source cubemap size
uniform params: vec3f;

fn targetFace() -> f32 { return uniform.params.x; }
fn targetTotalPixels() -> f32 { return uniform.params.y; }
fn sourceTotalPixels() -> f32 { return uniform.params.z; }

const PI: f32 = 3.141592653589793;

fn saturate(x: f32) -> f32 {
    return clamp(x, 0.0, 1.0);
}

#include "decodePS"
#include "encodePS"

//-- supported projections

fn modifySeams(dir: vec3f, scale: f32) -> vec3f {
    let adir = abs(dir);
    let M = max(max(adir.x, adir.y), adir.z);
    return dir / M * vec3f(
        select(scale, 1.0, adir.x == M),
        select(scale, 1.0, adir.y == M),
        select(scale, 1.0, adir.z == M)
    );
}

fn toSpherical(dir: vec3f) -> vec2f {
    let nonZeroXZ = any(dir.xz != vec2f(0.0, 0.0));
    return vec2f(select(0.0, atan2(dir.x, dir.z), nonZeroXZ), asin(dir.y));
}

fn fromSpherical(uv: vec2f) -> vec3f {
    return vec3f(cos(uv.y) * sin(uv.x),
                sin(uv.y),
                cos(uv.y) * cos(uv.x));
}

fn getDirectionEquirect(uv: vec2f) -> vec3f {
    return fromSpherical((vec2f(uv.x, 1.0 - uv.y) * 2.0 - 1.0) * vec2f(PI, PI * 0.5));
}

// octahedral code, based on https://jcgt.org/published/0003/02/01/
// "Survey of Efficient Representations for Independent Unit Vectors" by Cigolle, Donow, Evangelakos, Mara, McGuire, Meyer

fn signNotZero(k: f32) -> f32 {
    return select(-1.0, 1.0, k >= 0.0);
}

fn signNotZeroVec2(v: vec2f) -> vec2f {
    return vec2f(signNotZero(v.x), signNotZero(v.y));
}

// Returns a unit vector. Argument o is an octahedral vector packed via octEncode, on the [-1, +1] square
fn octDecode(o: vec2f) -> vec3f {
    var v = vec3f(o.x, 1.0 - abs(o.x) - abs(o.y), o.y);
    if (v.y < 0.0) {
        var temp: vec2f = (1.0 - abs(v.zx)) * signNotZeroVec2(v.xz);
        v = vec3f(temp.x, v.y, temp.y);
    }
    return normalize(v);
}

fn getDirectionOctahedral(uv: vec2f) -> vec3f {
    return octDecode(vec2f(uv.x, 1.0 - uv.y) * 2.0 - 1.0);
}

// Assumes that v is a unit vector. The result is an octahedral vector on the [-1, +1] square
fn octEncode(v: vec3f) -> vec2f {
    let l1norm = abs(v.x) + abs(v.y) + abs(v.z);
    var result = v.xz * (1.0 / l1norm);
    if (v.y < 0.0) {
        result = (1.0 - abs(result.yx)) * signNotZeroVec2(result.xy);
    }
    return result;
}

/////////////////////////////////////////////////////////////////////

#ifdef CUBEMAP_SOURCE
    fn sampleCubemapDir(dir: vec3f) -> vec4f {
        return textureSample(sourceCube, sourceCubeSampler, modifySeams(dir, 1.0));
    }

    fn sampleCubemapSph(sph: vec2f) -> vec4f {
        return sampleCubemapDir(fromSpherical(sph));
    }

    fn sampleCubemapDirLod(dir: vec3f, mipLevel: f32) -> vec4f {
        return textureSampleLevel(sourceCube, sourceCubeSampler, modifySeams(dir, 1.0), mipLevel);
    }

    fn sampleCubemapSphLod(sph: vec2f, mipLevel: f32) -> vec4f {
        return sampleCubemapDirLod(fromSpherical(sph), mipLevel);
    }
#else

    fn sampleEquirectSph(sph: vec2f) -> vec4f {
        let uv = sph / vec2f(PI * 2.0, PI) + 0.5;
        return textureSample(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y));
    }

    fn sampleEquirectDir(dir: vec3f) -> vec4f {
        return sampleEquirectSph(toSpherical(dir));
    }

    fn sampleEquirectSphLod(sph: vec2f, mipLevel: f32) -> vec4f {
        let uv = sph / vec2f(PI * 2.0, PI) + 0.5;
        return textureSampleLevel(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y), mipLevel);
    }

    fn sampleEquirectDirLod(dir: vec3f, mipLevel: f32) -> vec4f {
        return sampleEquirectSphLod(toSpherical(dir), mipLevel);
    }

    fn sampleOctahedralDir(dir: vec3f) -> vec4f {
        let uv = octEncode(dir) * 0.5 + 0.5;
        return textureSample(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y));
    }

    fn sampleOctahedralSph(sph: vec2f) -> vec4f {
        return sampleOctahedralDir(fromSpherical(sph));
    }

    fn sampleOctahedralDirLod(dir: vec3f, mipLevel: f32) -> vec4f {
        let uv = octEncode(dir) * 0.5 + 0.5;
        return textureSampleLevel(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y), mipLevel);
    }

    fn sampleOctahedralSphLod(sph: vec2f, mipLevel: f32) -> vec4f {
        return sampleOctahedralDirLod(fromSpherical(sph), mipLevel);
    }

#endif

fn getDirectionCubemap(uv: vec2f) -> vec3f {
    let st = uv * 2.0 - 1.0;
    let face = targetFace();

    var vec: vec3f;
    if (face == 0.0) {
        vec = vec3f(1, -st.y, -st.x);
    } else if (face == 1.0) {
        vec = vec3f(-1, -st.y, st.x);
    } else if (face == 2.0) {
        vec = vec3f(st.x, 1, st.y);
    } else if (face == 3.0) {
        vec = vec3f(st.x, -1, -st.y);
    } else if (face == 4.0) {
        vec = vec3f(st.x, -st.y, 1);
    } else {
        vec = vec3f(-st.x, -st.y, -1);
    }

    return normalize(modifySeams(vec, 1.0));
}

fn matrixFromVector(n: vec3f) -> mat3x3f {
    let a = 1.0 / (1.0 + n.z);
    let b = -n.x * n.y * a;
    let b1 = vec3f(1.0 - n.x * n.x * a, b, -n.x);
    let b2 = vec3f(b, 1.0 - n.y * n.y * a, -n.y);
    return mat3x3f(b1, b2, n);
}

fn matrixFromVectorSlow(n: vec3f) -> mat3x3f {
    let up = select(vec3f(0.0, 0.0, select(-1.0, 1.0, n.y > 0.0)), vec3f(0.0, 1.0, 0.0), abs(n.y) > 0.0000001);
    let x = normalize(cross(up, n));
    let y = cross(n, x);
    return mat3x3f(x, y, n);
}

fn reproject(uv: vec2f) -> vec4f {
    if ({NUM_SAMPLES} <= 1) {
        // single sample
        return {ENCODE_FUNC}({DECODE_FUNC}({SOURCE_FUNC}Dir({TARGET_FUNC}(uv))));
    } else {
        // multi sample
        let t = {TARGET_FUNC}(uv);
        let tu = dpdx(t);
        let tv = dpdy(t);

        var result = vec3f(0.0);
        for (var u = 0.0; u < {NUM_SAMPLES_SQRT}; u += 1.0) {
            for (var v = 0.0; v < {NUM_SAMPLES_SQRT}; v += 1.0) {
                result += {DECODE_FUNC}({SOURCE_FUNC}Dir(normalize(t +
                                                            tu * (u / {NUM_SAMPLES_SQRT} - 0.5) +
                                                            tv * (v / {NUM_SAMPLES_SQRT} - 0.5))));
            }
        }
        return {ENCODE_FUNC}(result / ({NUM_SAMPLES_SQRT} * {NUM_SAMPLES_SQRT}));
    }
}

const unpackFloat: vec4f = vec4f(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0);

#ifdef USE_SAMPLES_TEX
    fn unpackSample(i: i32, L: ptr<function, vec3f>, mipLevel: ptr<function, f32>) {
        var u = (f32(i * 4) + 0.5) * uniform.samplesTexInverseSize.x;
        var v = (floor(u) + 0.5) * uniform.samplesTexInverseSize.y;

        var raw: vec4f;
        raw.x = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat); u += uniform.samplesTexInverseSize.x;
        raw.y = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat); u += uniform.samplesTexInverseSize.x;
        raw.z = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat); u += uniform.samplesTexInverseSize.x;
        raw.w = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat);

        *L = raw.xyz * 2.0 - 1.0;
        *mipLevel = raw.w * 8.0;
    }

    // convolve an environment given pre-generated samples
    fn prefilterSamples(uv: vec2f) -> vec4f {
        // construct vector space given target direction
        let vecSpace = matrixFromVectorSlow({TARGET_FUNC}(uv));

        var L: vec3f;
        var mipLevel: f32;

        var result = vec3f(0.0);
        var totalWeight = 0.0;
        for (var i = 0; i < {NUM_SAMPLES}; i += 1) {
            unpackSample(i, &L, &mipLevel);
            result += {DECODE_FUNC}({SOURCE_FUNC}DirLod(vecSpace * L, mipLevel)) * L.z;
            totalWeight += L.z;
        }

        return {ENCODE_FUNC}(result / totalWeight);
    }

    // unweighted version of prefilterSamples
    fn prefilterSamplesUnweighted(uv: vec2f) -> vec4f {
        // construct vector space given target direction
        let vecSpace = matrixFromVectorSlow({TARGET_FUNC}(uv));

        var L: vec3f;
        var mipLevel: f32;

        var result = vec3f(0.0);
        for (var i = 0; i < {NUM_SAMPLES}; i += 1) {
            unpackSample(i, &L, &mipLevel);
            result += {DECODE_FUNC}({SOURCE_FUNC}DirLod(vecSpace * L, mipLevel));
        }

        return {ENCODE_FUNC}(result / f32({NUM_SAMPLES}));
    }
#endif

@fragment
fn fragmentMain(input : FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;
    output.color = {PROCESS_FUNC}(input.vUv0);
    return output;
}
`;

var reprojectVS = /* wgsl */ `
attribute vertex_position: vec2f;
uniform uvMod: vec4f;
varying vUv0: vec2f;

@vertex
fn vertexMain(input: VertexInput) -> VertexOutput {
  var output: VertexOutput;
  output.position = vec4f(input.vertex_position, 0.5, 1.0);
  output.vUv0 = getImageEffectUV((input.vertex_position * 0.5 + vec2f(0.5, 0.5)) * uniform.uvMod.xy + uniform.uvMod.zw);
  return output;
}
`;

var screenDepthPS = /* wgsl */ `

var uSceneDepthMap: texture_2d<f32>;
var uSceneDepthMapSampler: sampler;

#ifndef SCREENSIZE
    #define SCREENSIZE
    uniform uScreenSize: vec4f;
#endif

#ifndef VIEWMATRIX
    #define VIEWMATRIX
    uniform matrix_view: mat4x4f;
#endif

#ifndef LINEARIZE_DEPTH
    #define LINEARIZE_DEPTH

    #ifndef CAMERAPLANES
        #define CAMERAPLANES
        uniform camera_params: vec4f; // x: 1 / camera_far,      y: camera_far,     z: camera_near,        w: is_ortho
    #endif

    fn linearizeDepth(z: f32) -> f32 {
        if (uniform.camera_params.w == 0.0) { // Perspective
            return (uniform.camera_params.z * uniform.camera_params.y) / (uniform.camera_params.y + z * (uniform.camera_params.z - uniform.camera_params.y));
        } else {
            return uniform.camera_params.z + z * (uniform.camera_params.y - uniform.camera_params.z);
        }
    }
#endif

fn delinearizeDepth(linearDepth: f32) -> f32 {
    if (uniform.camera_params.w == 0.0) {
        return (uniform.camera_params.y * (uniform.camera_params.z - linearDepth)) / (linearDepth * (uniform.camera_params.z - uniform.camera_params.y));
    } else {
        return (linearDepth - uniform.camera_params.z) / (uniform.camera_params.y - uniform.camera_params.z);
    }
}

// Retrieves rendered linear camera depth by UV
fn getLinearScreenDepth(uv: vec2f) -> f32 {
    #ifdef SCENE_DEPTHMAP_LINEAR
        #ifdef SCENE_DEPTHMAP_FLOAT
            return textureSample(uSceneDepthMap, uSceneDepthMapSampler, uv).r;
        #else

            let textureSize = textureDimensions(uSceneDepthMap, 0);
            let texel: vec2i = vec2i(uv * vec2f(textureSize));
            let data: vec4f = textureLoad(uSceneDepthMap, texel, 0);

            let data_u32: vec4u = vec4u(data * 255.0);
            let intBits: u32 = (data_u32.r << 24u) | (data_u32.g << 16u) | (data_u32.b << 8u) | data_u32.a;

            return bitcast<f32>(intBits);

        #endif
    #else
        return linearizeDepth(textureSample(uSceneDepthMap, uSceneDepthMapSampler, uv).r);
    #endif
}

#ifndef VERTEXSHADER
    // Retrieves rendered linear camera depth under the current pixel
    fn getLinearScreenDepthFrag() -> f32 {
        let uv: vec2f = pcPosition.xy * uniform.uScreenSize.zw;
        return getLinearScreenDepth(uv);
    }
#endif

// Generates linear camera depth for the given world position
fn getLinearDepth(pos: vec3f) -> f32 {
    return -(uniform.matrix_view * vec4f(pos, 1.0)).z;
}
`;

var shadowCascadesPS = /* wgsl */ `

// function which selects a shadow projection matrix index based on cascade distances
fn getShadowCascadeIndex(shadowCascadeDistances: vec4f, shadowCascadeCount: i32) -> i32 {

    // depth in 0 .. far plane range
    let depth: f32 = 1.0 / pcPosition.w;

    // 1.0 if depth >= distance, 0.0 otherwise
    let comparisons: vec4f = step(shadowCascadeDistances, vec4f(depth));

    // sum is the index
    let cascadeIndex: i32 = i32(dot(comparisons, vec4f(1.0)));

    // limit to actual number of used cascades
    return min(cascadeIndex, shadowCascadeCount - 1);
}

// function which modifies cascade index to dither between cascades
fn ditherShadowCascadeIndex(cascadeIndex_in: i32, shadowCascadeDistances: vec4f, shadowCascadeCount: i32, blendFactor: f32) -> i32 {

    // Use var as cascadeIndex might be modified
    var cascadeIndex: i32 = cascadeIndex_in;
    if (cascadeIndex < shadowCascadeCount - 1) {
        let currentRangeEnd: f32 = shadowCascadeDistances[cascadeIndex];
        let transitionStart: f32 = blendFactor * currentRangeEnd; // Start overlap factor away from the end distance
        let depth: f32 = 1.0 / pcPosition.w;

        if (depth > transitionStart) {
            // Calculate a transition factor (0.0 to 1.0) within the overlap range
            let transitionFactor: f32 = smoothstep(transitionStart, currentRangeEnd, depth);

            // Add pseudo-random dithering
            // TODO: replace by user selectable dithering method
            let dither: f32 = fract(sin(dot(pcPosition.xy, vec2f(12.9898, 78.233))) * 43758.5453);
            if (dither < transitionFactor) {
                cascadeIndex = cascadeIndex + 1;
            }
        }
    }

    return cascadeIndex;
}

fn fadeShadow(shadowCoord_in: vec3f, shadowCascadeDistances: vec4f) -> vec3f {
    // if the pixel is past the shadow distance, remove shadow
    // this enforces straight line instead of corner of shadow which moves when camera rotates
    var shadowCoord: vec3f = shadowCoord_in;
    let depth: f32 = 1.0 / pcPosition.w;
    if (depth > shadowCascadeDistances.w) {
        shadowCoord.z = -9999999.0;
    }

    return shadowCoord;
}
`;

var shadowEVSMPS = /* wgsl */ `

// ------ VSM Shared ------

fn linstep(a: f32, b: f32, v: f32) -> f32 {
    // WGSL saturate -> clamp
    return clamp((v - a) / (b - a), 0.0, 1.0);
}

fn reduceLightBleeding(pMax: f32, amount: f32) -> f32 {
   // Remove the [0, amount] tail and linearly rescale (amount, 1].
   return linstep(amount, 1.0, pMax);
}

fn chebyshevUpperBound(moments: vec2f, mean: f32, minVariance: f32, lightBleedingReduction: f32) -> f32 {
    // Compute variance
    var variance: f32 = moments.y - (moments.x * moments.x);
    variance = max(variance, minVariance);

    // Compute probabilistic upper bound
    let d: f32 = mean - moments.x;
    var pMax: f32 = variance / (variance + (d * d));

    pMax = reduceLightBleeding(pMax, lightBleedingReduction);

    // One-tailed Chebyshev
    return select(pMax, 1.0, mean <= moments.x);
}

fn calculateEVSM(moments_in: vec3f, Z_in: f32, vsmBias: f32, exponent: f32) -> f32 {
    let Z: f32 = 2.0 * Z_in - 1.0;
    let warpedDepth: f32 = exp(exponent * Z);

    let moments: vec2f = moments_in.xy + vec2f(warpedDepth, warpedDepth*warpedDepth) * (1.0 - moments_in.z);

    let VSMBias: f32 = vsmBias;//0.01 * 0.25;
    let depthScale: f32 = VSMBias * exponent * warpedDepth;
    let minVariance1: f32 = depthScale * depthScale;
    return chebyshevUpperBound(moments, warpedDepth, minVariance1, 0.1);
}
// ------ VSM 16 ------

fn VSM16(tex: texture_2d<f32>, texSampler: sampler, texCoords: vec2f, resolution: f32, Z: f32, vsmBias: f32, exponent: f32) -> f32 {
    let moments: vec3f = textureSampleLevel(tex, texSampler, texCoords, 0.0).xyz;
    return calculateEVSM(moments, Z, vsmBias, exponent);
}

fn getShadowVSM16(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32) -> f32 {
    return VSM16(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);
}

fn getShadowSpotVSM16(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32, lightDir: vec3f) -> f32 {
    let Z: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
    return VSM16(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);
}

// ------ VSM 32 ------

fn VSM32(tex: texture_2d<f32>, texSampler: sampler, texCoords_in: vec2f, resolution: f32, Z: f32, vsmBias: f32, exponent: f32) -> f32 {

    #ifdef CAPS_TEXTURE_FLOAT_FILTERABLE
        var moments: vec3f = textureSampleLevel(tex, texSampler, texCoords_in, 0.0).xyz;
    #else
        // manual bilinear filtering
        var pixelSize : f32 = 1.0 / resolution;
        let texCoords: vec2f = texCoords_in - vec2f(pixelSize);
        let s00: vec3f = textureSampleLevel(tex, texSampler, texCoords, 0.0).xyz;
        let s10: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(pixelSize, 0.0), 0.0).xyz;
        let s01: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(0.0, pixelSize), 0.0).xyz;
        let s11: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(pixelSize), 0.0).xyz;
        let fr: vec2f = fract(texCoords * resolution);
        let h0: vec3f = mix(s00, s10, fr.x);
        let h1: vec3f = mix(s01, s11, fr.x);
        var moments: vec3f = mix(h0, h1, fr.y);
    #endif

    return calculateEVSM(moments, Z, vsmBias, exponent);
}

fn getShadowVSM32(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32) -> f32 {
    return VSM32(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);
}

fn getShadowSpotVSM32(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32, lightDir: vec3f) -> f32 {
    let Z: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
    return VSM32(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);
}
`;

var shadowPCF1PS = /* wgsl */ `
// ----- Directional/Spot Sampling -----

fn getShadowPCF1x1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
    return textureSampleCompareLevel(shadowMap, shadowMapSampler, shadowCoord.xy, shadowCoord.z);
}

fn getShadowSpotPCF1x1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
    return textureSampleCompareLevel(shadowMap, shadowMapSampler, shadowCoord.xy, shadowCoord.z);
}

// ----- Omni Sampling -----

// Not supported on WebGPU device
// fn getShadowOmniPCF1x1(shadowMap: texture_depth_cube, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f, lightDir: vec3f) -> f32 {
//     let shadowZ: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
//     return textureSampleCompareLevel(shadowMap, shadowMapSampler, lightDir, shadowZ);
// }
`;

var shadowPCF3PS = /* glsl */ `
// ----- Directional/Spot Sampling -----
fn _getShadowPCF3x3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec3f) -> f32 {
    let z: f32 = shadowCoord.z;
    let uv: vec2f = shadowCoord.xy * shadowParams.x; // 1 unit - 1 texel
    let shadowMapSizeInv: f32 = 1.0 / shadowParams.x;
    let base_uv_temp: vec2f = floor(uv + 0.5);
    let s: f32 = (uv.x + 0.5 - base_uv_temp.x);
    let t: f32 = (uv.y + 0.5 - base_uv_temp.y);
    let base_uv: vec2f = (base_uv_temp - vec2f(0.5)) * shadowMapSizeInv;

    var sum: f32 = 0.0;

    let uw0: f32 = (3.0 - 2.0 * s);
    let uw1: f32 = (1.0 + 2.0 * s);

    let u0_offset: f32 = (2.0 - s) / uw0 - 1.0;
    let u1_offset: f32 = s / uw1 + 1.0;

    let vw0: f32 = (3.0 - 2.0 * t);
    let vw1: f32 = (1.0 + 2.0 * t);

    let v0_offset: f32 = (2.0 - t) / vw0 - 1.0;
    let v1_offset: f32 = t / vw1 + 1.0;

    let u0: f32 = u0_offset * shadowMapSizeInv + base_uv.x;
    let v0: f32 = v0_offset * shadowMapSizeInv + base_uv.y;

    let u1: f32 = u1_offset * shadowMapSizeInv + base_uv.x;
    let v1: f32 = v1_offset * shadowMapSizeInv + base_uv.y;

    sum = sum + uw0 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v0), z);
    sum = sum + uw1 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v0), z);
    sum = sum + uw0 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v1), z);
    sum = sum + uw1 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v1), z);

    sum = sum * (1.0 / 16.0);
    return sum;
}

fn getShadowPCF3x3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
    return _getShadowPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);
}

fn getShadowSpotPCF3x3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
    return _getShadowPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);
}

// ----- Omni Sampling -----

// Not currently supported on WebGPU device
// fn getShadowOmniPCF3x3(shadowMap: texture_depth_cube, shadowMapSampler: sampler_comparison, shadowParams: vec4f, dir: vec3f) -> f32 {

//     // Calculate shadow depth from the light direction
//     let shadowZ: f32 = length(dir) * shadowParams.w + shadowParams.z;

//     // offset
//     let texSize_i = textureDimensions(shadowMap, 0);
//     let z: f32 = 1.0 / f32(texSize_i.x);
//     let tc: vec3f = normalize(dir);

//     // average 4 samples - not a strict 3x3 PCF but that's tricky with cubemaps
//     var shadows: vec4f;
//     shadows.x = textureSampleCompareLevel(shadowMap, shadowMapSampler, tc + vec3f( z,  z,  z), shadowZ);
//     shadows.y = textureSampleCompareLevel(shadowMap, shadowMapSampler, tc + vec3f(-z, -z,  z), shadowZ);
//     shadows.z = textureSampleCompareLevel(shadowMap, shadowMapSampler, tc + vec3f(-z,  z, -z), shadowZ);
//     shadows.w = textureSampleCompareLevel(shadowMap, shadowMapSampler, tc + vec3f( z, -z, -z), shadowZ);

//     return dot(shadows, vec4f(0.25));
// }

// fn getShadowOmniPCF3x3_Wrapper(shadowMap: texture_depth_cube, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f, lightDir: vec3f) -> f32 {
//     return getShadowOmniPCF3x3(shadowMap, shadowMapSampler, shadowParams, lightDir);
// }
`;

var shadowPCF5PS = /* wgsl */ `
// http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/
fn _getShadowPCF5x5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec3f) -> f32 {

    let z: f32 = shadowCoord.z;
    let uv: vec2f = shadowCoord.xy * shadowParams.x; // 1 unit - 1 texel
    let shadowMapSizeInv: f32 = 1.0 / shadowParams.x;
    let base_uv_temp: vec2f = floor(uv + 0.5);
    let s: f32 = (uv.x + 0.5 - base_uv_temp.x);
    let t: f32 = (uv.y + 0.5 - base_uv_temp.y);
    let base_uv: vec2f = (base_uv_temp - vec2f(0.5)) * shadowMapSizeInv;

    let uw0: f32 = (4.0 - 3.0 * s);
    let uw1: f32 = 7.0;
    let uw2: f32 = (1.0 + 3.0 * s);

    let u0_offset: f32 = (3.0 - 2.0 * s) / uw0 - 2.0;
    let u1_offset: f32 = (3.0 + s) / uw1;
    let u2_offset: f32 = s / uw2 + 2.0;

    let vw0: f32 = (4.0 - 3.0 * t);
    let vw1: f32 = 7.0;
    let vw2: f32 = (1.0 + 3.0 * t);

    let v0_offset: f32 = (3.0 - 2.0 * t) / vw0 - 2.0;
    let v1_offset: f32 = (3.0 + t) / vw1;
    let v2_offset: f32 = t / vw2 + 2.0;

    var sum: f32 = 0.0;

    let u0: f32 = u0_offset * shadowMapSizeInv + base_uv.x;
    let v0: f32 = v0_offset * shadowMapSizeInv + base_uv.y;

    let u1: f32 = u1_offset * shadowMapSizeInv + base_uv.x;
    let v1: f32 = v1_offset * shadowMapSizeInv + base_uv.y;

    let u2: f32 = u2_offset * shadowMapSizeInv + base_uv.x;
    let v2: f32 = v2_offset * shadowMapSizeInv + base_uv.y;

    sum = sum + uw0 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v0), z);
    sum = sum + uw1 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v0), z);
    sum = sum + uw2 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u2, v0), z);

    sum = sum + uw0 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v1), z);
    sum = sum + uw1 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v1), z);
    sum = sum + uw2 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u2, v1), z);

    sum = sum + uw0 * vw2 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v2), z);
    sum = sum + uw1 * vw2 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v2), z);
    sum = sum + uw2 * vw2 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u2, v2), z);

    sum = sum * (1.0 / 144.0);
    sum = saturate(sum);

    return sum;
}

fn getShadowPCF5x5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
    return _getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);
}

fn getShadowSpotPCF5x5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {
    return _getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);
}
`;

// Soft directional shadows PCSS - with and without blocker search.
var shadowSoftPS = /* wgsl */ `

fn fractSinRand(uv: vec2f) -> f32 {
    let PI: f32 = 3.141592653589793;
    let a: f32 = 12.9898; let b: f32 = 78.233; let c: f32 = 43758.5453;
    let dt: f32 = dot(uv.xy, vec2f(a, b));
    let sn: f32 = dt % PI;
    return fract(sin(sn) * c);
}

// struct to hold precomputed constants and current state
struct VogelDiskData {
    invNumSamples: f32,
    initialAngle: f32,
    currentPointId: f32,
}

// prepare the Vogel disk constants and initialize the current state in the struct
fn prepareDiskConstants(data: ptr<function, VogelDiskData>, sampleCount: i32, randomSeed: f32) {
    let pi2: f32 = 6.28318530718;
    data.invNumSamples = 1.0 / f32(sampleCount);
    data.initialAngle = randomSeed * pi2;
    data.currentPointId = 0.0;
}


fn generateDiskSample(data: ptr<function, VogelDiskData>) -> vec2f {
    let GOLDEN_ANGLE: f32 = 2.399963;
    let r: f32 = sqrt((data.currentPointId + 0.5) * data.invNumSamples);
    let theta: f32 = data.currentPointId * GOLDEN_ANGLE + data.initialAngle;

    let offset: vec2f = vec2f(cos(theta), sin(theta)) * pow(r, 1.33);

    data.currentPointId = data.currentPointId + 1.0;
    return offset;
}

fn PCSSFindBlocker(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, avgBlockerDepth: ptr<function, f32>, numBlockers: ptr<function, i32>,
    shadowCoords: vec2f, z: f32, shadowBlockerSamples: i32, penumbraSize: f32, invShadowMapSize: f32, randomSeed: f32) {

    var diskData: VogelDiskData;
    prepareDiskConstants(&diskData, shadowBlockerSamples, randomSeed);

    let searchWidth: f32 = penumbraSize * invShadowMapSize;
    var blockerSum: f32 = 0.0;
    var numBlockers_local: i32 = 0;

    for( var i: i32 = 0; i < shadowBlockerSamples; i = i + 1 ) {
        let diskUV: vec2f = generateDiskSample(&diskData);
        let sampleUV: vec2f = shadowCoords + diskUV * searchWidth;
        let shadowMapDepth: f32 = textureSampleLevel(shadowMap, shadowMapSampler, sampleUV, 0.0).r;
        if ( shadowMapDepth < z ) {
            blockerSum = blockerSum + shadowMapDepth;
            numBlockers_local = numBlockers_local + 1;
        }
    }
    *avgBlockerDepth = blockerSum / f32(numBlockers_local);
    *numBlockers = numBlockers_local;
}

fn PCSSFilter(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, uv: vec2f, receiverDepth: f32, shadowSamples: i32, filterRadius: f32, randomSeed: f32) -> f32 {

    var diskData: VogelDiskData;
    prepareDiskConstants(&diskData, shadowSamples, randomSeed);

    var sum: f32 = 0.0;
    for (var i: i32 = 0; i < shadowSamples; i = i + 1) {
        let offsetUV: vec2f = generateDiskSample(&diskData) * filterRadius;
        let depth: f32 = textureSampleLevel(shadowMap, shadowMapSampler, uv + offsetUV, 0.0).r;
        sum = sum + step(receiverDepth, depth);
    }
    return sum / f32(shadowSamples);
}

fn getPenumbra(dblocker: f32, dreceiver: f32, penumbraSize: f32, penumbraFalloff: f32) -> f32 {
    let dist: f32 = dreceiver - dblocker;
    let penumbra: f32 = 1.0 - pow(1.0 - dist, penumbraFalloff);
    return penumbra * penumbraSize;
}

fn PCSSDirectional(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoords: vec3f, cameraParams: vec4f, softShadowParams: vec4f) -> f32 {

    let receiverDepth: f32 = shadowCoords.z;
    let randomSeed: f32 = fractSinRand(pcPosition.xy);
    let shadowSamples: i32 = i32(softShadowParams.x);
    let shadowBlockerSamples: i32 = i32(softShadowParams.y);
    let penumbraSize: f32 = softShadowParams.z;
    let penumbraFalloff: f32 = softShadowParams.w;

    let shadowMapSize: i32 = i32(textureDimensions(shadowMap, 0).x);
    var invShadowMapSize: f32 = 1.0 / f32(shadowMapSize);
    invShadowMapSize = invShadowMapSize * (f32(shadowMapSize) / 2048.0);

    var penumbra: f32;

    // contact hardening path
    if (shadowBlockerSamples > 0) {

        // find average blocker depth
        var avgBlockerDepth: f32 = 0.0;
        var numBlockers: i32 = 0;
        PCSSFindBlocker(shadowMap, shadowMapSampler, &avgBlockerDepth, &numBlockers, shadowCoords.xy, receiverDepth, shadowBlockerSamples, penumbraSize, invShadowMapSize, randomSeed);

        // early out when no blockers are present
        if (numBlockers < 1) {
            return 1.0;
        }

        // penumbra size is based on the blocker depth
        penumbra = getPenumbra(avgBlockerDepth, shadowCoords.z, penumbraSize, penumbraFalloff);

    } else {

        // constant filter size, no contact hardening
        penumbra = penumbraSize;
    }

    let filterRadius: f32 = penumbra * invShadowMapSize;

    // filtering
    return PCSSFilter(shadowMap, shadowMapSampler, shadowCoords.xy, receiverDepth, shadowSamples, filterRadius, randomSeed);
}

fn getShadowPCSS(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, cameraParams: vec4f, softShadowParams: vec4f, lightDir: vec3f) -> f32 { // lightDir unused? Kept param.
    return PCSSDirectional(shadowMap, shadowMapSampler, shadowCoord, cameraParams, softShadowParams);
}
`;

var skinBatchVS = /* wgsl */ `
attribute vertex_boneIndices: f32;

var texture_poseMap: texture_2d<f32>;

fn getBoneMatrix(indexFloat: f32) -> mat4x4f {

    let width = i32(textureDimensions(texture_poseMap).x);
    let index: i32 = i32(indexFloat + 0.5) * 3;
    let iy: i32 = index / width;
    let ix: i32 = index % width;

    // read elements of 4x3 matrix
    let v1: vec4f = textureLoad(texture_poseMap, vec2i(ix + 0, iy), 0);
    let v2: vec4f = textureLoad(texture_poseMap, vec2i(ix + 1, iy), 0);
    let v3: vec4f = textureLoad(texture_poseMap, vec2i(ix + 2, iy), 0);

    // transpose to 4x4 matrix
    return mat4x4f(
        v1.x, v2.x, v3.x, 0,
        v1.y, v2.y, v3.y, 0,
        v1.z, v2.z, v3.z, 0,
        v1.w, v2.w, v3.w, 1.0
    );
}
`;

var skinVS = /* wgsl */ `

attribute vertex_boneWeights: vec4f;
attribute vertex_boneIndices: vec4f;

var texture_poseMap: texture_2d<f32>;

struct BoneMatrix {
    v1: vec4f,
    v2: vec4f,
    v3: vec4f,
}

fn getBoneMatrix(width: i32, index: i32) -> BoneMatrix {

    let v = index / width;
    let u = index % width;

    var result: BoneMatrix;
    result.v1 = textureLoad(texture_poseMap, vec2i(u + 0, v), 0);
    result.v2 = textureLoad(texture_poseMap, vec2i(u + 1, v), 0);
    result.v3 = textureLoad(texture_poseMap, vec2i(u + 2, v), 0);
    return result;
}

fn getSkinMatrix(indicesFloat: vec4f, weights: vec4f) -> mat4x4f {

    let width = i32(textureDimensions(texture_poseMap).x);
    var indices = vec4i(indicesFloat + 0.5) * 3;

    let boneA = getBoneMatrix(width, indices.x);
    let boneB = getBoneMatrix(width, indices.y);
    let boneC = getBoneMatrix(width, indices.z);
    let boneD = getBoneMatrix(width, indices.w);

    // ... rest of getSkinMatrix remains the same ...
    let v1 = boneA.v1 * weights.x + boneB.v1 * weights.y + boneC.v1 * weights.z + boneD.v1 * weights.w;
    let v2 = boneA.v2 * weights.x + boneB.v2 * weights.y + boneC.v2 * weights.z + boneD.v2 * weights.w;
    let v3 = boneA.v3 * weights.x + boneB.v3 * weights.y + boneC.v3 * weights.z + boneD.v3 * weights.w;

    let one = dot(weights, vec4f(1.0, 1.0, 1.0, 1.0));

    // transpose to 4x4 matrix
    return mat4x4f(
        v1.x, v2.x, v3.x, 0,
        v1.y, v2.y, v3.y, 0,
        v1.z, v2.z, v3.z, 0,
        v1.w, v2.w, v3.w, one
    );
}
`;

var skyboxPS = /* wgsl */ `
    #define LIT_SKYBOX_INTENSITY

    #include "envProcPS"
    #include "gammaPS"
    #include "tonemappingPS"

    #ifdef PREPASS_PASS
        varying vLinearDepth: f32;
        #include "floatAsUintPS"
    #endif

    // Varying and uniform declarations
    varying vViewDir : vec3f;
    uniform skyboxHighlightMultiplier : f32;

    #ifdef SKY_CUBEMAP

        var texture_cubeMap : texture_cube<f32>;
        var texture_cubeMap_sampler : sampler;

        #ifdef SKYMESH
            varying vWorldPos : vec3f;
            uniform cubeMapRotationMatrix : mat3x3f;
            uniform projectedSkydomeCenter : vec3f;
        #endif

    #else // env-atlas

        #include "sphericalPS"
        #include "envAtlasPS"

        var texture_envAtlas : texture_2d<f32>;
        var texture_envAtlas_sampler : sampler;

        uniform mipLevel : f32;

    #endif

    @fragment
    fn fragmentMain(input : FragmentInput) -> FragmentOutput {

        var output: FragmentOutput;

        #ifdef PREPASS_PASS

            // output linear depth during prepass
            output.color = float2vec4(vLinearDepth);

        #else

            var linear : vec3f;
            var dir : vec3f;

            #ifdef SKY_CUBEMAP

                #ifdef SKYMESH
                    // get vector from world space pos to tripod origin
                    var envDir : vec3f = normalize(input.vWorldPos - uniform.projectedSkydomeCenter);
                    dir = envDir * uniform.cubeMapRotationMatrix;
                #else
                    dir = input.vViewDir;
                #endif

                dir.x *= -1.0;
                linear = {SKYBOX_DECODE_FNC}(textureSample(texture_cubeMap, texture_cubeMap_sampler, dir));

            #else // env-atlas

                dir = input.vViewDir * vec3f(-1.0, 1.0, 1.0);
                let uv : vec2f = toSphericalUv(normalize(dir));
                linear = {SKYBOX_DECODE_FNC}(textureSample(texture_envAtlas, texture_envAtlas_sampler, mapRoughnessUv(uv, uniform.mipLevel)));

            #endif

            // our HDR encodes values up to 64, so allow extra brightness for the clipped values
            if (any(linear >= vec3f(64.0))) {
                linear *= uniform.skyboxHighlightMultiplier;
            }
            
            output.color = vec4f(gammaCorrectOutput(toneMap(processEnvironment(linear))), 1.0);

        #endif

        return output;
    }
`;

var skyboxVS = /* wgsl */ `
    // Attribute
    attribute aPosition : vec4f;

    uniform matrix_view : mat4x4f;
    uniform matrix_projectionSkybox : mat4x4f;
    uniform cubeMapRotationMatrix : mat3x3f;

    varying vViewDir : vec3f;

    #ifdef PREPASS_PASS
        // when skydome renders depth during prepass, generate linear depth
        varying vLinearDepth: f32;
    #endif

    #ifdef SKYMESH
        uniform matrix_model : mat4x4f;
        varying vWorldPos : vec3f;
    #endif

    @vertex
    fn vertexMain(input : VertexInput) -> VertexOutput {

        var output : VertexOutput;
        var view : mat4x4f = uniform.matrix_view;

        #ifdef SKYMESH

            var worldPos : vec4f = uniform.matrix_model * input.aPosition;
            output.vWorldPos = worldPos.xyz;
            output.position = uniform.matrix_projectionSkybox * (view * worldPos);

            #ifdef PREPASS_PASS
                // linear depth from the worldPosition, see getLinearDepth
                output.vLinearDepth = -(uniform.matrix_view * vec4f(worldPos.xyz, 1.0)).z;
            #endif

        #else

            view[3][0] = 0.0;
            view[3][1] = 0.0;
            view[3][2] = 0.0;
            output.position = uniform.matrix_projectionSkybox * (view * input.aPosition);
            output.vViewDir = input.aPosition.xyz * uniform.cubeMapRotationMatrix;

            #ifdef PREPASS_PASS
                // for infinite skybox, use negative gl_Position.w to get positive linear depth
                output.vLinearDepth = -pcPosition.w;
            #endif
        #endif

        // Force skybox to far Z, regardless of the clip planes on the camera
        // Subtract a tiny fudge factor to ensure floating point errors don't
        // still push pixels beyond far Z. See:
        // https://community.khronos.org/t/skybox-problem/61857

        output.position.z = output.position.w - 1.0e-7;

        return output;
    }
`;

var specularPS = /* wgsl */ `

#ifdef STD_SPECULAR_CONSTANT
    uniform material_specular: vec3f;
#endif

fn getSpecularity() {
    var specularColor = vec3f(1.0, 1.0, 1.0);

    #ifdef STD_SPECULAR_CONSTANT
    specularColor = specularColor * uniform.material_specular;
    #endif

    #ifdef STD_SPECULAR_TEXTURE
    specularColor = specularColor * {STD_SPECULAR_TEXTURE_DECODE}(textureSampleBias({STD_SPECULAR_TEXTURE_NAME}, {STD_SPECULAR_TEXTURE_NAME}Sampler, {STD_SPECULAR_TEXTURE_UV}, uniform.textureBias)).{STD_SPECULAR_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SPECULAR_VERTEX
    specularColor = specularColor * saturate3(vVertexColor.{STD_SPECULAR_VERTEX_CHANNEL});
    #endif

    dSpecularity = specularColor;
}
`;

var sphericalPS = /* wgsl */ `

fn toSpherical(dir: vec3f) -> vec2f {
    let angle_xz = select(0.0, atan2(dir.x, dir.z), any(dir.xz != vec2f(0.0)));
    return vec2f(angle_xz, asin(dir.y));
}

fn toSphericalUv(dir : vec3f) -> vec2f {
    const PI : f32 = 3.141592653589793;
    let uv : vec2f = toSpherical(dir) / vec2f(PI * 2.0, PI) + vec2f(0.5, 0.5);
    return vec2f(uv.x, 1.0 - uv.y);
}
`;

var specularityFactorPS = /* wgsl */ `

#ifdef STD_SPECULARITYFACTOR_CONSTANT
    uniform material_specularityFactor: f32;
#endif

fn getSpecularityFactor() {
    var specularityFactor = 1.0;

    #ifdef STD_SPECULARITYFACTOR_CONSTANT
    specularityFactor = specularityFactor * uniform.material_specularityFactor;
    #endif

    #ifdef STD_SPECULARITYFACTOR_TEXTURE
    specularityFactor = specularityFactor * textureSampleBias({STD_SPECULARITYFACTOR_TEXTURE_NAME}, {STD_SPECULARITYFACTOR_TEXTURE_NAME}Sampler, {STD_SPECULARITYFACTOR_TEXTURE_UV}, uniform.textureBias).{STD_SPECULARITYFACTOR_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_SPECULARITYFACTOR_VERTEX
    specularityFactor = specularityFactor * saturate(vVertexColor.{STD_SPECULARITYFACTOR_VERTEX_CHANNEL});
    #endif

    dSpecularityFactor = specularityFactor;
}
`;

var spotPS = /* glsl */ `
fn getSpotEffect(lightSpotDir: vec3f, lightInnerConeAngle: f32, lightOuterConeAngle: f32, lightDirNorm: vec3f) -> f32 {
    let cosAngle: f32 = dot(lightDirNorm, lightSpotDir);
    return smoothstep(lightOuterConeAngle, lightInnerConeAngle, cosAngle);
}`;

var startNineSlicedPS = /* wgsl */ `
    nineSlicedUv = vec2f(vUv0.x, 1.0 - vUv0.y);
`;

var startNineSlicedTiledPS = /* wgsl */ `
    let tileMask: vec2f = step(vMask, vec2f(0.99999));
    let tileSize: vec2f = 0.5 * (innerOffset.xy + innerOffset.zw);
    let tileScale: vec2f = vec2f(1.0) / (vec2f(1.0) - tileSize);
    var clampedUv: vec2f = mix(innerOffset.xy * 0.5, vec2f(1.0) - innerOffset.zw * 0.5, fract((vTiledUv - tileSize) * tileScale));
    clampedUv = clampedUv * atlasRect.zw + atlasRect.xy;
    var nineSlicedUv: vec2f = vUv0 * tileMask + clampedUv * (vec2f(1.0) - tileMask);
    nineSlicedUv.y = 1.0 - nineSlicedUv.y;

`;

// Declaration part of the standard shader. Declares the uniforms, textures and global variables used
// by the fragment shader of the standard shader.
var stdDeclarationPS = /* wgsl */ `

    // globals
    var<private> dAlpha: f32 = 1.0;

    // all passes handle opacity
    #if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE
        #ifdef STD_OPACITY_TEXTURE_ALLOCATE
            var texture_opacityMap : texture_2d<f32>;
            var texture_opacityMapSampler : sampler;
        #endif
    #endif

    #ifdef FORWARD_PASS // ----------------

        // globals
        var<private> dAlbedo: vec3f;
        var<private> dNormalW: vec3f;
        var<private> dSpecularity: vec3f = vec3f(0.0, 0.0, 0.0);
        var<private> dGlossiness: f32 = 0.0;

        #ifdef LIT_REFRACTION
            var<private> dTransmission: f32;
            var<private> dThickness: f32;
        #endif

        #ifdef LIT_SCENE_COLOR
            var uSceneColorMap : texture_2d<f32>;
            var uSceneColorMapSampler : sampler;
        #endif

        #ifdef LIT_SCREEN_SIZE
            uniform uScreenSize: vec4f;
        #endif

        #ifdef LIT_TRANSFORMS
            var<private> matrix_viewProjection: mat4x4f;
            var<private> matrix_model: mat4x4f;
        #endif

        // parallax
        #ifdef STD_HEIGHT_MAP
            var<private> dUvOffset: vec2f;
            #ifdef STD_HEIGHT_TEXTURE_ALLOCATE
                var texture_heightMap : texture_2d<f32>;
                var texture_heightMapSampler : sampler;
            #endif
        #endif

        // diffuse
        #ifdef STD_DIFFUSE_TEXTURE_ALLOCATE
            var texture_diffuseMap : texture_2d<f32>;
            var texture_diffuseMapSampler : sampler;
        #endif

        #ifdef STD_DIFFUSEDETAIL_TEXTURE_ALLOCATE
            var texture_diffuseDetailMap : texture_2d<f32>;
            var texture_diffuseDetailMapSampler : sampler;
        #endif

        // normal
        #ifdef STD_NORMAL_TEXTURE_ALLOCATE
            var texture_normalMap : texture_2d<f32>;
            var texture_normalMapSampler : sampler;
        #endif

        #ifdef STD_NORMALDETAIL_TEXTURE_ALLOCATE
            var texture_normalDetailMap : texture_2d<f32>;
            var texture_normalDetailMapSampler : sampler;
        #endif

        // refraction
        #ifdef STD_THICKNESS_TEXTURE_ALLOCATE
            var texture_thicknessMap : texture_2d<f32>;
            var texture_thicknessMapSampler : sampler;
        #endif
        #ifdef STD_REFRACTION_TEXTURE_ALLOCATE
            var texture_refractionMap : texture_2d<f32>;
            var texture_refractionMapSampler : sampler;
        #endif

        // iridescence
        #ifdef LIT_IRIDESCENCE
            var<private> dIridescence: f32;
            var<private> dIridescenceThickness: f32;

            #ifdef STD_IRIDESCENCE_THICKNESS_TEXTURE_ALLOCATE
                var texture_iridescenceThicknessMap : texture_2d<f32>;
                var texture_iridescenceThicknessMapSampler : sampler;
            #endif
            #ifdef STD_IRIDESCENCE_TEXTURE_ALLOCATE
                var texture_iridescenceMap : texture_2d<f32>;
                var texture_iridescenceMapSampler : sampler;
            #endif
        #endif

        #ifdef LIT_CLEARCOAT
            var<private> ccSpecularity: f32;
            var<private> ccGlossiness: f32;
            var<private> ccNormalW: vec3f;
        #endif

        #ifdef LIT_GGX_SPECULAR
            var<private> dAnisotropy: f32;
            var<private> dAnisotropyRotation: vec2f;
        #endif

        // specularity & glossiness
        #ifdef LIT_SPECULAR_OR_REFLECTION

            // sheen
            #ifdef LIT_SHEEN
                var<private> sSpecularity: vec3f;
                var<private> sGlossiness: f32;

                #ifdef STD_SHEEN_TEXTURE_ALLOCATE
                    var texture_sheenMap : texture_2d<f32>;
                    var texture_sheenMapSampler : sampler;
                #endif
                #ifdef STD_SHEENGLOSS_TEXTURE_ALLOCATE
                    var texture_sheenGlossMap : texture_2d<f32>;
                    var texture_sheenGlossMapSampler : sampler;
                #endif
            #endif

            // metalness
            #ifdef LIT_METALNESS
                var<private> dMetalness: f32;
                var<private> dIor: f32;

                #ifdef STD_METALNESS_TEXTURE_ALLOCATE
                    var texture_metalnessMap : texture_2d<f32>;
                    var texture_metalnessMapSampler : sampler;
                #endif
            #endif

            // specularity factor
            #ifdef LIT_SPECULARITY_FACTOR
                var<private> dSpecularityFactor: f32;

                #ifdef STD_SPECULARITYFACTOR_TEXTURE_ALLOCATE
                    var texture_specularityFactorMap : texture_2d<f32>;
                    var texture_specularityFactorMapSampler : sampler;
                #endif
            #endif

            // specular color
            #ifdef STD_SPECULAR_COLOR
                #ifdef STD_SPECULAR_TEXTURE_ALLOCATE
                    var texture_specularMap : texture_2d<f32>;
                    var texture_specularMapSampler : sampler;
                #endif
            #endif

            // gloss
            #ifdef STD_GLOSS_TEXTURE_ALLOCATE
                var texture_glossMap : texture_2d<f32>;
                var texture_glossMapSampler : sampler;
            #endif
        #endif

        // ao
        #ifdef STD_AO
            var <private> dAo: f32;
            #ifdef STD_AO_TEXTURE_ALLOCATE
                var texture_aoMap : texture_2d<f32>;
                var texture_aoMapSampler : sampler;
            #endif
            #ifdef STD_AODETAIL_TEXTURE_ALLOCATE
                var texture_aoDetailMap : texture_2d<f32>;
                var texture_aoDetailMapSampler : sampler;
            #endif
        #endif

        // emission
        var <private> dEmission: vec3f;
        #ifdef STD_EMISSIVE_TEXTURE_ALLOCATE
            var texture_emissiveMap : texture_2d<f32>;
            var texture_emissiveMapSampler : sampler;
        #endif

        // clearcoat
        #ifdef LIT_CLEARCOAT
            #ifdef STD_CLEARCOAT_TEXTURE_ALLOCATE
                var texture_clearCoatMap : texture_2d<f32>;
                var texture_clearCoatMapSampler : sampler;
            #endif
            #ifdef STD_CLEARCOATGLOSS_TEXTURE_ALLOCATE
                var texture_clearCoatGlossMap : texture_2d<f32>;
                var texture_clearCoatGlossMapSampler : sampler;
            #endif
            #ifdef STD_CLEARCOATNORMAL_TEXTURE_ALLOCATE
                var texture_clearCoatNormalMap : texture_2d<f32>;
                var texture_clearCoatNormalMapSampler : sampler;
            #endif
        #endif

        // anisotropy
        #ifdef LIT_GGX_SPECULAR
            #ifdef STD_ANISOTROPY_TEXTURE_ALLOCATE
                var texture_anisotropyMap : texture_2d<f32>;
                var texture_anisotropyMapSampler : sampler;
            #endif
        #endif

        // lightmap
        #if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)
            var<private> dLightmap: vec3f;
            #ifdef STD_LIGHT_TEXTURE_ALLOCATE
                var texture_lightMap : texture_2d<f32>;
                var texture_lightMapSampler : sampler;
            #endif
        #endif
    #endif

    // front end outputs to lit shader
    #include "litShaderCorePS"
`;

// includes and functionality of the front end shader, generates the input to the lit shader.
var stdFrontEndPS = /* wgsl */ `

    // all passes handle opacity
    #if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE
        #include "opacityPS"

        #if defined(LIT_ALPHA_TEST)
            #include "alphaTestPS"
        #endif

        // dithering
        #if STD_OPACITY_DITHER != NONE
            #include "opacityDitherPS"
        #endif
    #endif

    #ifdef FORWARD_PASS // ----------------

        // parallax
        #ifdef STD_HEIGHT_MAP
            #include "parallaxPS"
        #endif

        // diffuse
        #include  "diffusePS"

        // normal
        #ifdef LIT_NEEDS_NORMAL
            #include "normalMapPS"
        #endif

        // refraction
        #ifdef LIT_REFRACTION
            #include "transmissionPS"
            #include "thicknessPS"
        #endif

        // iridescence
        #ifdef LIT_IRIDESCENCE
            #include "iridescencePS"
            #include "iridescenceThicknessPS"
        #endif

        // specularity & glossiness
        #ifdef LIT_SPECULAR_OR_REFLECTION

            // sheen
            #ifdef LIT_SHEEN
                #include "sheenPS"
                #include "sheenGlossPS"
            #endif

            // metalness
            #ifdef LIT_METALNESS
                #include "metalnessPS"
                #include "iorPS"
            #endif

            // specularity factor
            #ifdef LIT_SPECULARITY_FACTOR
                #include "specularityFactorPS"
            #endif

            // specular color
            #ifdef STD_SPECULAR_COLOR
                #include "specularPS"
            #else
                fn getSpecularity() { 
                    dSpecularity = vec3f(1.0, 1.0, 1.0);
                }
            #endif

            // gloss
            #include "glossPS"
        #endif

        // ao
        #ifdef STD_AO
            #include "aoPS"
        #endif

        // emission
        #include "emissivePS"

        // clearcoat
        #ifdef LIT_CLEARCOAT
            #include "clearCoatPS"
            #include "clearCoatGlossPS"
            #include "clearCoatNormalPS"
        #endif

        // anisotropy
        #if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)
            #include "anisotropyPS"
        #endif

        // lightmap
        #if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)
            #include "lightmapPS"
        #endif
    #endif

    fn evaluateFrontend() {

        // all passes handle opacity
        #if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE
            getOpacity();

            #if defined(LIT_ALPHA_TEST)
                alphaTest(dAlpha);
            #endif

            #if STD_OPACITY_DITHER != NONE
                opacityDither(dAlpha, 0.0);
            #endif

            litArgs_opacity = dAlpha;
        #endif

        #ifdef FORWARD_PASS // ----------------

            // parallax
            #ifdef STD_HEIGHT_MAP
                getParallax();
            #endif

            // diffuse
            getAlbedo();
            litArgs_albedo = dAlbedo;

            // normal
            #ifdef LIT_NEEDS_NORMAL
                getNormal();
                litArgs_worldNormal = dNormalW;
            #endif

            // refraction
            #ifdef LIT_REFRACTION
                getRefraction();
                litArgs_transmission = dTransmission;

                getThickness();
                litArgs_thickness = dThickness;

                #ifdef LIT_DISPERSION
                    litArgs_dispersion = uniform.material_dispersion;
                #endif
            #endif

            // iridescence
            #ifdef LIT_IRIDESCENCE
                getIridescence();
                getIridescenceThickness();
                litArgs_iridescence_intensity = dIridescence;
                litArgs_iridescence_thickness = dIridescenceThickness;
            #endif

            // specularity & glossiness
            #ifdef LIT_SPECULAR_OR_REFLECTION

                // sheen
                #ifdef LIT_SHEEN
                    getSheen();
                    litArgs_sheen_specularity = sSpecularity;
                    getSheenGlossiness();
                    litArgs_sheen_gloss = sGlossiness;
                #endif

                // metalness
                #ifdef LIT_METALNESS
                    getMetalness();
                    litArgs_metalness = dMetalness;
                    getIor();
                    litArgs_ior = dIor;
                #endif

                // specularity factor
                #ifdef LIT_SPECULARITY_FACTOR
                    getSpecularityFactor();
                    litArgs_specularityFactor = dSpecularityFactor;
                #endif

                // gloss
                getGlossiness();
                getSpecularity();
                litArgs_specularity = dSpecularity;
                litArgs_gloss = dGlossiness;
            #endif

            // ao
            #ifdef STD_AO
                getAO();
                litArgs_ao = dAo;
            #endif

            // emission
            getEmission();
            litArgs_emission = dEmission;

            // clearcoat
            #ifdef LIT_CLEARCOAT
                getClearCoat();
                getClearCoatGlossiness();
                getClearCoatNormal();
                litArgs_clearcoat_specularity = ccSpecularity;
                litArgs_clearcoat_gloss = ccGlossiness;
                litArgs_clearcoat_worldNormal = ccNormalW;
            #endif

            // anisotropy
            #if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)
                getAnisotropy();
            #endif

            // lightmap
            #if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)
                getLightMap();
                litArgs_lightmap = dLightmap;

                #ifdef STD_LIGHTMAP_DIR
                    litArgs_lightmapDir = dLightmapDir;
                #endif
            #endif
        #endif
    }
`;

var TBNPS = /* wgsl */ `

#ifdef LIT_TANGENTS
    #define TBN_TANGENTS
#else
    #if defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS)
        #define TBN_DERIVATIVES
    #endif
#endif

#if defined(TBN_DERIVATIVES)
    uniform tbnBasis: f32;
#endif

fn getTBN(tangent: vec3f, binormal: vec3f, normal: vec3f) {

    #ifdef TBN_TANGENTS // tangents / binormals based TBN

        dTBN = mat3x3f(normalize(tangent), normalize(binormal), normalize(normal));

    #elif defined(TBN_DERIVATIVES) // derivatives based TBN

        let uv: vec2f = {lightingUv};

        // get edge vectors of the pixel triangle
        let dp1: vec3f = dpdx( vPositionW );
        let dp2: vec3f = dpdy( vPositionW );
        let duv1: vec2f = dpdx( uv );
        let duv2: vec2f = dpdy( uv );

        // solve the linear system
        let dp2perp: vec3f = cross( dp2, normal );
        let dp1perp: vec3f = cross( normal, dp1 );
        let T: vec3f = dp2perp * duv1.x + dp1perp * duv2.x;
        let B: vec3f = dp2perp * duv1.y + dp1perp * duv2.y;

        // construct a scale-invariant frame
        let denom: f32 = max( dot(T, T), dot(B, B) );
        let invmax: f32 = select(uniform.tbnBasis / sqrt( denom ), 0.0, denom == 0.0);
        dTBN = mat3x3f(T * invmax, -B * invmax, normal );

    #else // object space TBN

        var B: vec3f = cross(normal, vObjectSpaceUpW);
        var T: vec3f = cross(normal, B);

        if (dot(B,B) == 0.0) // deal with case when vObjectSpaceUpW normal are parallel
        {
            let major: f32 = max(max(normal.x, normal.y), normal.z);

            if (normal.x == major)
            {
                B = cross(normal, vec3f(0.0, 1.0, 0.0));
                T = cross(normal, B);
            }
            else if (normal.y == major)
            {
                B = cross(normal, vec3f(0.0, 0.0, 1.0));
                T = cross(normal, B);
            }
            else // removed "if (normal.z == major)" assuming it's the only remaining case
            {
                B = cross(normal, vec3f(1.0, 0.0, 0.0));
                T = cross(normal, B);
            }
        }

        dTBN = mat3x3f(normalize(T), normalize(B), normalize(normal));

    #endif
}`;

var thicknessPS = /* wgsl */ `
#ifdef STD_THICKNESS_CONSTANT
uniform material_thickness: f32;
#endif

fn getThickness() {
    dThickness = 1.0;

    #ifdef STD_THICKNESS_CONSTANT
    dThickness = dThickness * uniform.material_thickness;
    #endif

    #ifdef STD_THICKNESS_TEXTURE
    dThickness = dThickness * textureSampleBias({STD_THICKNESS_TEXTURE_NAME}, {STD_THICKNESS_TEXTURE_NAME}Sampler, {STD_THICKNESS_TEXTURE_UV}, uniform.textureBias).{STD_THICKNESS_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_THICKNESS_VERTEX
    dThickness = dThickness * saturate(vVertexColor.{STD_THICKNESS_VERTEX_CHANNEL});
    #endif
}
`;

var tonemappingPS = /* wgsl */ `
#if (TONEMAP == NONE)
    #include "tonemappingNonePS"
#elif TONEMAP == FILMIC
    #include "tonemappingFilmicPS"
#elif TONEMAP == LINEAR
    #include "tonemappingLinearPS"
#elif TONEMAP == HEJL
    #include "tonemappingHejlPS"
#elif TONEMAP == ACES
    #include "tonemappingAcesPS"
#elif TONEMAP == ACES2
    #include "tonemappingAces2PS"
#elif TONEMAP == NEUTRAL
    #include "tonemappingNeutralPS"
#endif
`;

var tonemappingAcesPS = /* wgsl */ `
uniform exposure: f32;

fn toneMap(color: vec3f) -> vec3f {
    let tA: f32 = 2.51;
    let tB: f32 = 0.03;
    let tC: f32 = 2.43;
    let tD: f32 = 0.59;
    let tE: f32 = 0.14;
    let x: vec3f = color * uniform.exposure;
    return (x * (tA * x + tB)) / (x * (tC * x + tD) + tE);
}
`;

var tonemappingAces2PS = /* wgsl */ `
uniform exposure: f32;

// ACES approximation by Stephen Hill

// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
const ACESInputMat: mat3x3f = mat3x3f(
    vec3f(0.59719, 0.35458, 0.04823),
    vec3f(0.07600, 0.90834, 0.01566),
    vec3f(0.02840, 0.13383, 0.83777)
);

// ODT_SAT => XYZ => D60_2_D65 => sRGB
const ACESOutputMat: mat3x3f = mat3x3f(
    vec3f( 1.60475, -0.53108, -0.07367),
    vec3f(-0.10208,  1.10813, -0.00605),
    vec3f(-0.00327, -0.07276,  1.07602)
);

fn RRTAndODTFit(v: vec3f) -> vec3f {
    let a: vec3f = v * (v + vec3f(0.0245786)) - vec3f(0.000090537);
    let b: vec3f = v * (vec3f(0.983729) * v + vec3f(0.4329510)) + vec3f(0.238081);
    return a / b;
}

fn toneMap(color: vec3f) -> vec3f {
    var c: vec3f = color * (uniform.exposure / 0.6);
    c = c * ACESInputMat;

    // Apply RRT and ODT
    c = RRTAndODTFit(c);
    c = c * ACESOutputMat;

    // Clamp to [0, 1]
    return clamp(c, vec3f(0.0), vec3f(1.0));
}
`;

var tonemappingFilmicPS = /* wgsl */ `
const A: f32 = 0.15;
const B: f32 = 0.50;
const C: f32 = 0.10;
const D: f32 = 0.20;
const E: f32 = 0.02;
const F: f32 = 0.30;
const W: f32 = 11.2;

uniform exposure: f32;

fn uncharted2Tonemap(x: vec3f) -> vec3f {
    return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - vec3f(E / F);
}

fn toneMap(color: vec3f) -> vec3f {
    var c: vec3f = uncharted2Tonemap(color * uniform.exposure);
    let whiteScale: vec3f = vec3f(1.0) / uncharted2Tonemap(vec3f(W, W, W));
    c *= whiteScale;
    return c;
}
`;

var tonemappingHejlPS = /* wgsl */ `
uniform exposure: f32;

fn toneMap(color: vec3f) -> vec3f {
    let A: f32 = 0.22;
    let B: f32 = 0.3;
    let C: f32 = 0.1;
    let D: f32 = 0.2;
    let E: f32 = 0.01;
    let F: f32 = 0.3;
    let Scl: f32 = 1.25;

    let adjusted_color = color * uniform.exposure;
    let h = max(vec3f(0.0), adjusted_color - vec3f(0.004));

    return (h * ((Scl * A) * h + Scl * vec3f(C * B)) + Scl * vec3f(D * E)) /
           (h * (A * h + vec3f(B)) + vec3f(D * F)) -
           Scl * vec3f(E / F);
}
`;

var tonemappingLinearPS = /* wgsl */ `
uniform exposure: f32;

fn toneMap(color: vec3f) -> vec3f {
    return color * uniform.exposure;
}
`;

// https://modelviewer.dev/examples/tone-mapping
var tonemappingNeutralPS = /* wgsl */ `
uniform exposure: f32;

fn toneMap(col: vec3f) -> vec3f {
    var color = col * uniform.exposure;

    let startCompression = 0.8 - 0.04;
    let desaturation = 0.15;

    let x = min(color.r, min(color.g, color.b));
    let offset = select(0.04, x - 6.25 * x * x, x < 0.08);
    color -= vec3f(offset);

    let peak = max(color.r, max(color.g, color.b));
    if (peak < startCompression) {
        return color;
    }

    let d = 1.0 - startCompression;
    let newPeak = 1.0 - d * d / (peak + d - startCompression);
    color *= newPeak / peak;

    let g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
    return mix(color, vec3f(newPeak), vec3f(g));
}
`;

var tonemappingNonePS = /* wgsl */ `
fn toneMap(color: vec3f) -> vec3f {
    return color;
}
`;

var transformVS = /* wgsl */ `
#ifdef PIXELSNAP
    uniform uScreenSize: vec4f;
#endif

#ifdef SCREENSPACE
    uniform projectionFlipY: f32;
#endif

fn evalWorldPosition(vertexPosition: vec3f, modelMatrix: mat4x4f) -> vec4f {

    var localPos: vec3f = getLocalPosition(vertexPosition);

    #ifdef NINESLICED
        // outer and inner vertices are at the same position, scale both
        var localPosXZ: vec2f = localPos.xz;
        localPosXZ = localPosXZ * uniform.outerScale;

        // offset inner vertices inside
        // (original vertices must be in [-1;1] range)
        let positiveUnitOffset: vec2f = clamp(vertexPosition.xz, vec2f(0.0), vec2f(1.0));
        let negativeUnitOffset: vec2f = clamp(-vertexPosition.xz, vec2f(0.0), vec2f(1.0));
        localPosXZ = localPosXZ + (-positiveUnitOffset * uniform.innerOffset.xy + negativeUnitOffset * uniform.innerOffset.zw) * vertex_texCoord0.xy;

        dTiledUvGlobal = (localPosXZ - uniform.outerScale + uniform.innerOffset.xy) * -0.5 + 1.0; // uv = local pos - inner corner

        localPosXZ = localPosXZ * -0.5;
        localPos = vec3f(localPosXZ.x, localPosXZ.y, localPos.y);
    #endif

    var posW: vec4f = modelMatrix * vec4f(localPos, 1.0);

    #ifdef SCREENSPACE
        posW = vec4f(posW.xy, 0.0, 1.0);
    #endif

    return posW;
}

fn getPosition() -> vec4f {

    dModelMatrix = getModelMatrix();

    let posW: vec4f = evalWorldPosition(vertex_position.xyz, dModelMatrix);
    dPositionW = posW.xyz;

    var screenPos: vec4f;
    #ifdef UV1LAYOUT
        screenPos = vec4f(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1.0);
        screenPos.y *= -1.0;
    #else
        #ifdef SCREENSPACE
            screenPos = posW;
            screenPos.y *= uniform.projectionFlipY;
        #else
            screenPos = uniform.matrix_viewProjection * posW;
        #endif

        #ifdef PIXELSNAP
            // snap vertex to a pixel boundary
            screenPos.xy = (screenPos.xy * 0.5) + 0.5;
            screenPos.xy *= uniforms.uScreenSize.xy;
            screenPos.xy = floor(screenPos.xy);
            screenPos.xy *= uniforms.uScreenSize.zw;
            screenPos.xy = (screenPos.xy * 2.0) - 1.0;
        #endif
    #endif

    return screenPos;
}

fn getWorldPosition() -> vec3f {
    return dPositionW;
}
`;

var transformCoreVS = /* wgsl */ `

    attribute vertex_position: vec4f;

    uniform matrix_viewProjection: mat4x4f;
    uniform matrix_model: mat4x4f;
    
    #ifdef MORPHING

        uniform morph_tex_params: vec2f;
        attribute morph_vertex_id: u32;

        fn getTextureMorphCoords() -> vec2i {

            // turn morph_vertex_id into int grid coordinates
            var textureSize: vec2i = vec2i(uniform.morph_tex_params);
            var morphGridV: i32 = i32(morph_vertex_id) / textureSize.x;
            var morphGridU: i32 = i32(morph_vertex_id) - (morphGridV * textureSize.x);
            morphGridV = textureSize.y - morphGridV - 1;
            return vec2i(morphGridU, morphGridV);
        }

        #ifdef MORPHING_POSITION
            #ifdef MORPHING_INT
                uniform aabbSize: vec3f;
                uniform aabbMin: vec3f;
                var morphPositionTex: texture_2d<u32>;
            #else
                var morphPositionTex: texture_2d<f32>;
            #endif
        #endif
    #endif

    #ifdef defined(BATCH)
        #include "skinBatchVS"

        fn getModelMatrix() -> mat4x4f {
            return getBoneMatrix(vertex_boneIndices);
        }

    #elif defined(SKIN)
        #include "skinVS"
        fn getModelMatrix() -> mat4x4f {
            return uniform.matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);
        }

    #elif defined(INSTANCING)

        #include "transformInstancingVS"

    #else

        fn getModelMatrix() -> mat4x4f {
            return uniform.matrix_model;
        }

    #endif

    fn getLocalPosition(vertexPosition: vec3f) -> vec3f {

        var localPos: vec3f = vertexPosition;

        #ifdef MORPHING_POSITION

            var morphUV: vec2i = getTextureMorphCoords();

            #ifdef MORPHING_INT
                // Use textureLoad instead of texelFetch. Coordinates must be integer type (vec2i).
                // WGSL requires explicit type conversion for vectors.
                // Division by float literal ensures floating point division.
                var morphPos: vec3f = vec3f(textureLoad(morphPositionTex, morphUV, 0).xyz) / 65535.0 * uniform.aabbSize + uniform.aabbMin;
            #else
                // Use textureLoad instead of texelFetch. Coordinates must be integer type (vec2i).
                var morphPos: vec3f = textureLoad(morphPositionTex, morphUV, 0).xyz;
            #endif

            localPos += morphPos;

        #endif

        return localPos;
    }
`;

var transformInstancingVS = /* wgsl */ `

attribute instance_line1: vec4f;
attribute instance_line2: vec4f;
attribute instance_line3: vec4f;
attribute instance_line4: vec4f;

fn getModelMatrix() -> mat4x4f {
    return uniform.matrix_model * mat4x4f(instance_line1, instance_line2, instance_line3, instance_line4);
}
`;

var transmissionPS = /* wgsl */ `

#ifdef STD_REFRACTION_CONSTANT
    uniform material_refraction: f32;
#endif

fn getRefraction() {
    var refraction: f32 = 1.0;

    #ifdef STD_REFRACTION_CONSTANT
    refraction = uniform.material_refraction;
    #endif

    #ifdef STD_REFRACTION_TEXTURE
    refraction = refraction * textureSampleBias({STD_REFRACTION_TEXTURE_NAME}, {STD_REFRACTION_TEXTURE_NAME}Sampler, {STD_REFRACTION_TEXTURE_UV}, uniform.textureBias).{STD_REFRACTION_TEXTURE_CHANNEL};
    #endif

    #ifdef STD_REFRACTION_VERTEX
    refraction = refraction * saturate(vVertexColor.{STD_REFRACTION_VERTEX_CHANNEL});
    #endif

    dTransmission = refraction;
}
`;

var twoSidedLightingPS = /* wgsl */ `
uniform twoSidedLightingNegScaleFactor: f32;

fn handleTwoSidedLighting() {
    dTBN[2] = dTBN[2] * select(-uniform.twoSidedLightingNegScaleFactor, uniform.twoSidedLightingNegScaleFactor, pcFrontFacing);
}
`;

var uv0VS = /* wgsl */ `
#ifdef NINESLICED
    fn getUv0() -> vec2f {
        var uv = vertex_position.xz;

        // offset inner vertices inside
        let positiveUnitOffset = clamp(vertex_position.xz, vec2f(0.0, 0.0), vec2f(1.0, 1.0));
        let negativeUnitOffset = clamp(-vertex_position.xz, vec2f(0.0, 0.0), vec2f(1.0, 1.0));

        uv = uv + ((-positiveUnitOffset * uniform.innerOffset.xy) + (negativeUnitOffset * uniform.innerOffset.zw)) * vertex_texCoord0.xy;

        uv = uv * -0.5 + vec2f(0.5, 0.5);
        uv = uv * uniform.atlasRect.zw + uniform.atlasRect.xy;

        dMaskGlobal = vertex_texCoord0.xy;

        return uv;
    }
#else
    fn getUv0() -> vec2f {
        return vertex_texCoord0;
    }
#endif
`;

var uv1VS = /* wgsl */ `
fn getUv1() -> vec2f {
    return vertex_texCoord1;
}
`;

// chunk that generates uv coordinate transformed by uv transform matrix
var uvTransformVS = /* wgsl */ `
output.vUV{TRANSFORM_UV_{i}}_{TRANSFORM_ID_{i}} = vec2f(
    dot(vec3f(uv{TRANSFORM_UV_{i}}, 1), uniform.{TRANSFORM_NAME_{i}}0),
    dot(vec3f(uv{TRANSFORM_UV_{i}}, 1), uniform.{TRANSFORM_NAME_{i}}1)
);
`;

// uniform declaration for uv transform matrix, 3x2 matrix
var uvTransformUniformsPS = /* wgsl */ `
    uniform {TRANSFORM_NAME_{i}}0: vec3f;
    uniform {TRANSFORM_NAME_{i}}1: vec3f;
`;

var viewDirPS = /* wgsl */ `
fn getViewDir() {
    dViewDirW = normalize(uniform.view_position - vPositionW);
}
`;

/**
 * Object containing all default WGSL shader chunks used by shader generators.
 *
 * @type {Record<string, string>}
 * @category Graphics
 * @ignore
 */ const shaderChunksWGSL = {
    alphaTestPS,
    ambientPS,
    anisotropyPS,
    aoPS,
    aoDiffuseOccPS,
    aoSpecOccPS,
    bakeDirLmEndPS,
    bakeLmEndPS,
    basePS,
    baseNineSlicedPS,
    baseNineSlicedTiledPS,
    bayerPS,
    blurVSMPS,
    clearCoatPS,
    clearCoatGlossPS,
    clearCoatNormalPS,
    clusteredLightCookiesPS,
    clusteredLightShadowsPS,
    clusteredLightUtilsPS,
    clusteredLightPS,
    combinePS,
    cookieBlit2DPS,
    cookieBlitCubePS,
    cookieBlitVS,
    // cookiePS,
    cubeMapProjectPS,
    cubeMapRotatePS,
    debugOutputPS,
    debugProcessFrontendPS,
    detailModesPS,
    diffusePS,
    decodePS,
    emissivePS,
    encodePS,
    endPS,
    envAtlasPS,
    envProcPS,
    falloffInvSquaredPS,
    falloffLinearPS,
    floatAsUintPS,
    fogPS,
    fresnelSchlickPS,
    frontendCodePS: '',
    frontendDeclPS: '',
    fullscreenQuadVS,
    gammaPS,
    glossPS,
    gsplatCenterVS,
    gsplatCornerVS,
    gsplatColorVS,
    gsplatCommonVS,
    gsplatCompressedDataVS,
    gsplatCompressedSHVS,
    gsplatEvalSHVS,
    gsplatSourceFormatVS,
    gsplatStructsVS,
    gsplatQuatToMat3VS,
    gsplatSogsColorVS,
    gsplatSogsDataVS,
    gsplatSogsSHVS,
    gsplatDataVS,
    gsplatOutputVS,
    gsplatPS,
    gsplatSHVS,
    gsplatSourceVS,
    gsplatVS,
    gsplatWorkBufferVS,
    gsplatPackingPS,
    quadVS,
    indirectCoreCS,
    immediateLinePS,
    immediateLineVS,
    iridescenceDiffractionPS,
    iridescencePS,
    iridescenceThicknessPS,
    iorPS,
    lightDeclarationPS,
    lightDiffuseLambertPS,
    lightDirPointPS,
    lightEvaluationPS,
    lightFunctionLightPS,
    lightFunctionShadowPS,
    lightingPS,
    lightmapAddPS,
    lightmapPS,
    lightSpecularAnisoGGXPS,
    lightSpecularBlinnPS,
    lightSheenPS,
    linearizeDepthPS,
    litForwardBackendPS,
    litForwardDeclarationPS,
    litForwardMainPS,
    litForwardPostCodePS,
    litForwardPreCodePS,
    litMainPS,
    litMainVS,
    litOtherMainPS,
    litShaderArgsPS,
    litShaderCorePS,
    litShadowMainPS,
    litUserDeclarationPS: '',
    litUserDeclarationVS: '',
    litUserCodePS: '',
    litUserCodeVS: '',
    litUserMainStartPS: '',
    litUserMainStartVS: '',
    litUserMainEndPS: '',
    litUserMainEndVS: '',
    ltcPS,
    metalnessPS,
    metalnessModulatePS,
    morphPS,
    morphVS,
    msdfPS,
    msdfVS,
    normalVS,
    normalCoreVS,
    normalMapPS,
    opacityPS,
    opacityDitherPS,
    outputPS,
    outputAlphaPS,
    outputTex2DPS,
    sheenPS,
    sheenGlossPS,
    parallaxPS,
    pickPS,
    reflDirPS,
    reflDirAnisoPS,
    reflectionCCPS,
    reflectionCubePS,
    reflectionEnvHQPS,
    reflectionEnvPS,
    reflectionSpherePS,
    reflectionSheenPS,
    refractionCubePS,
    refractionDynamicPS,
    reprojectPS,
    reprojectVS,
    screenDepthPS,
    shadowCascadesPS,
    shadowEVSMPS,
    shadowPCF1PS,
    shadowPCF3PS,
    shadowPCF5PS,
    // shadowPCSSPS,
    shadowSoftPS,
    skinBatchVS,
    skinVS,
    skyboxPS,
    skyboxVS,
    specularPS,
    sphericalPS,
    specularityFactorPS,
    spotPS,
    startNineSlicedPS,
    startNineSlicedTiledPS,
    stdDeclarationPS,
    stdFrontEndPS,
    TBNPS,
    thicknessPS,
    tonemappingPS,
    tonemappingAcesPS,
    tonemappingAces2PS,
    tonemappingFilmicPS,
    tonemappingHejlPS,
    tonemappingLinearPS,
    tonemappingNeutralPS,
    tonemappingNonePS,
    transformVS,
    transformCoreVS,
    transformInstancingVS,
    transmissionPS,
    twoSidedLightingPS,
    uv0VS,
    uv1VS,
    uvTransformVS,
    uvTransformUniformsPS,
    viewDirPS,
    webgpuPS,
    webgpuVS
};

/**
 * AppBase represents the base functionality for all PlayCanvas applications. It is responsible for
 * initializing and managing the application lifecycle. It coordinates core engine systems such
 * as:
 *
 * - The graphics device - see {@link GraphicsDevice}.
 * - The asset registry - see {@link AssetRegistry}.
 * - The component system registry - see {@link ComponentSystemRegistry}.
 * - The scene - see {@link Scene}.
 * - Input devices - see {@link Keyboard}, {@link Mouse}, {@link TouchDevice}, and {@link GamePads}.
 * - The main update/render loop.
 *
 * Using AppBase directly requires you to register {@link ComponentSystem}s and
 * {@link ResourceHandler}s yourself. This facilitates
 * [tree-shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) when bundling
 * your application.
 */ class AppBase extends EventHandler {
    /**
     * Create a new AppBase instance.
     *
     * @param {HTMLCanvasElement | OffscreenCanvas} canvas - The canvas element.
     * @example
     * const app = new pc.AppBase(canvas);
     *
     * const options = new AppOptions();
     * app.init(options);
     *
     * // Start the application's main loop
     * app.start();
     */ constructor(canvas){
        super(), /**
     * The application's batch manager.
     *
     * @type {BatchManager|null}
     * @private
     */ this._batcher = null, /** @private */ this._destroyRequested = false, /** @private */ this._inFrameUpdate = false, /** @private */ this._librariesLoaded = false, /** @private */ this._fillMode = FILLMODE_KEEP_ASPECT, /** @private */ this._resolutionMode = RESOLUTION_FIXED, /** @private */ this._allowResize = true, /**
     * @type {Asset|null}
     * @private
     */ this._skyboxAsset = null, /**
     * Stores all entities that have been created for this app by guid.
     *
     * @type {Object<string, Entity>}
     * @ignore
     */ this._entityIndex = {}, /**
     * @type {boolean}
     * @ignore
     */ this._inTools = false, /**
     * @type {string}
     * @ignore
     */ this._scriptPrefix = '', /** @ignore */ this._time = 0, /**
     * Set this to false if you want to run without using bundles. We set it to true only if
     * TextDecoder is available because we currently rely on it for untarring.
     *
     * @type {boolean}
     * @ignore
     */ this.enableBundles = typeof TextDecoder !== 'undefined', /**
     * Scales the global time delta. Defaults to 1.
     *
     * @type {number}
     * @example
     * // Set the app to run at half speed
     * this.app.timeScale = 0.5;
     */ this.timeScale = 1, /**
     * Clamps per-frame delta time to an upper bound. Useful since returning from a tab
     * deactivation can generate huge values for dt, which can adversely affect game state.
     * Defaults to 0.1 (seconds).
     *
     * @type {number}
     * @example
     * // Don't clamp inter-frame times of 200ms or less
     * this.app.maxDeltaTime = 0.2;
     */ this.maxDeltaTime = 0.1 // Maximum delta is 0.1s or 10 fps.
        , /**
     * The total number of frames the application has updated since start() was called.
     *
     * @type {number}
     * @ignore
     */ this.frame = 0, /**
     * The frame graph.
     *
     * @type {FrameGraph}
     * @ignore
     */ this.frameGraph = new FrameGraph(), /**
     * Scripts in order of loading first.
     *
     * @type {string[]}
     */ this.scriptsOrder = [], /**
     * When true, the application's render function is called every frame. Setting autoRender to
     * false is useful to applications where the rendered image may often be unchanged over time.
     * This can heavily reduce the application's load on the CPU and GPU. Defaults to true.
     *
     * @type {boolean}
     * @example
     * // Disable rendering every frame and only render on a keydown event
     * this.app.autoRender = false;
     * this.app.keyboard.on('keydown', (event) => {
     *     this.app.renderNextFrame = true;
     * });
     */ this.autoRender = true, /**
     * Set to true to render the scene on the next iteration of the main loop. This only has an
     * effect if {@link autoRender} is set to false. The value of renderNextFrame is set back to
     * false again as soon as the scene has been rendered.
     *
     * @type {boolean}
     * @example
     * // Render the scene only while space key is pressed
     * if (this.app.keyboard.isPressed(pc.KEY_SPACE)) {
     *     this.app.renderNextFrame = true;
     * }
     */ this.renderNextFrame = false, /**
     * The run-time lightmapper.
     *
     * @type {Lightmapper|null}
     */ this.lightmapper = null, /**
     * The resource loader.
     *
     * @type {ResourceLoader}
     */ this.loader = new ResourceLoader(this), /**
     * The scene registry managed by the application.
     *
     * @type {SceneRegistry}
     * @example
     * // Search the scene registry for a item with the name 'racetrack1'
     * const sceneItem = this.app.scenes.find('racetrack1');
     *
     * // Load the scene using the item's url
     * this.app.scenes.loadScene(sceneItem.url);
     */ this.scenes = new SceneRegistry(this), /**
     * The application's script registry.
     *
     * @type {ScriptRegistry}
     */ this.scripts = new ScriptRegistry(this), /**
     * The application's component system registry.
     *
     * @type {ComponentSystemRegistry}
     * @example
     * // Set global gravity to zero
     * this.app.systems.rigidbody.gravity.set(0, 0, 0);
     * @example
     * // Set the global sound volume to 50%
     * this.app.systems.sound.volume = 0.5;
     */ this.systems = new ComponentSystemRegistry(), /**
     * Handles localization.
     *
     * @type {I18n}
     */ this.i18n = new I18n$1(this), /**
     * The keyboard device.
     *
     * @type {Keyboard|null}
     */ this.keyboard = null, /**
     * The mouse device.
     *
     * @type {Mouse|null}
     */ this.mouse = null, /**
     * Used to get touch events input.
     *
     * @type {TouchDevice|null}
     */ this.touch = null, /**
     * Used to access GamePad input.
     *
     * @type {GamePads|null}
     */ this.gamepads = null, /**
     * Used to handle input for {@link ElementComponent}s.
     *
     * @type {ElementInput|null}
     */ this.elementInput = null, /**
     * The XR Manager that provides ability to start VR/AR sessions.
     *
     * @type {XrManager|null}
     * @example
     * // check if VR is available
     * if (app.xr.isAvailable(pc.XRTYPE_VR)) {
     *     // VR is available
     * }
     */ this.xr = null;
        if (version$1?.indexOf('$') < 0) {
            Debug.log(`Powered by PlayCanvas ${version$1} ${revision}`);
        }
        // Store application instance
        AppBase._applications[canvas.id] = this;
        setApplication(this);
        this.root = new Entity();
        this.root._enabledInHierarchy = true;
    }
    /**
     * Initialize the app.
     *
     * @param {AppOptions} appOptions - Options specifying the init parameters for the app.
     */ init(appOptions) {
        const { assetPrefix, batchManager, componentSystems, elementInput, gamepads, graphicsDevice, keyboard, lightmapper, mouse, resourceHandlers, scriptsOrder, scriptPrefix, soundManager, touch, xr } = appOptions;
        Debug.assert(graphicsDevice, 'The application cannot be created without a valid GraphicsDevice');
        this.graphicsDevice = graphicsDevice;
        // register shader chunks
        ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_GLSL).add(shaderChunksGLSL);
        ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_WGSL).add(shaderChunksWGSL);
        this._initDefaultMaterial();
        this._initProgramLibrary();
        this.stats = new ApplicationStats(graphicsDevice);
        this._soundManager = soundManager;
        this.scene = new Scene$1(graphicsDevice);
        this._registerSceneImmediate(this.scene);
        this.assets = new AssetRegistry(this.loader);
        if (assetPrefix) this.assets.prefix = assetPrefix;
        this.bundles = new BundleRegistry(this.assets);
        this.scriptsOrder = scriptsOrder || [];
        this.defaultLayerWorld = new Layer({
            name: 'World',
            id: LAYERID_WORLD
        });
        this.defaultLayerDepth = new Layer({
            name: 'Depth',
            id: LAYERID_DEPTH,
            enabled: false,
            opaqueSortMode: SORTMODE_NONE
        });
        this.defaultLayerSkybox = new Layer({
            name: 'Skybox',
            id: LAYERID_SKYBOX,
            opaqueSortMode: SORTMODE_NONE
        });
        this.defaultLayerUi = new Layer({
            name: 'UI',
            id: LAYERID_UI,
            transparentSortMode: SORTMODE_MANUAL
        });
        this.defaultLayerImmediate = new Layer({
            name: 'Immediate',
            id: LAYERID_IMMEDIATE,
            opaqueSortMode: SORTMODE_NONE
        });
        const defaultLayerComposition = new LayerComposition('default');
        defaultLayerComposition.pushOpaque(this.defaultLayerWorld);
        defaultLayerComposition.pushOpaque(this.defaultLayerDepth);
        defaultLayerComposition.pushOpaque(this.defaultLayerSkybox);
        defaultLayerComposition.pushTransparent(this.defaultLayerWorld);
        defaultLayerComposition.pushOpaque(this.defaultLayerImmediate);
        defaultLayerComposition.pushTransparent(this.defaultLayerImmediate);
        defaultLayerComposition.pushTransparent(this.defaultLayerUi);
        this.scene.layers = defaultLayerComposition;
        // Placeholder texture for area light LUTs
        AreaLightLuts.createPlaceholder(graphicsDevice);
        this.renderer = new ForwardRenderer(graphicsDevice, this.scene);
        if (lightmapper) {
            this.lightmapper = new lightmapper(graphicsDevice, this.root, this.scene, this.renderer, this.assets);
            this.once('prerender', this._firstBake, this);
        }
        if (batchManager) {
            this._batcher = new batchManager(graphicsDevice, this.root, this.scene);
            this.once('prerender', this._firstBatch, this);
        }
        this.keyboard = keyboard || null;
        this.mouse = mouse || null;
        this.touch = touch || null;
        this.gamepads = gamepads || null;
        if (elementInput) {
            this.elementInput = elementInput;
            this.elementInput.app = this;
        }
        this.xr = xr ? new xr(this) : null;
        if (this.elementInput) this.elementInput.attachSelectEvents();
        this._scriptPrefix = scriptPrefix || '';
        if (this.enableBundles) {
            this.loader.addHandler('bundle', new BundleHandler(this));
        }
        // Create and register all required resource handlers
        resourceHandlers.forEach((resourceHandler)=>{
            const handler = new resourceHandler(this);
            this.loader.addHandler(handler.handlerType, handler);
        });
        // Create and register all required component systems
        componentSystems.forEach((componentSystem)=>{
            this.systems.add(new componentSystem(this));
        });
        this._visibilityChangeHandler = this.onVisibilityChange.bind(this);
        // Depending on browser add the correct visibilitychange event and store the name of the
        // hidden attribute in this._hiddenAttr.
        if (typeof document !== 'undefined') {
            if (document.hidden !== undefined) {
                this._hiddenAttr = 'hidden';
                document.addEventListener('visibilitychange', this._visibilityChangeHandler, false);
            } else if (document.mozHidden !== undefined) {
                this._hiddenAttr = 'mozHidden';
                document.addEventListener('mozvisibilitychange', this._visibilityChangeHandler, false);
            } else if (document.msHidden !== undefined) {
                this._hiddenAttr = 'msHidden';
                document.addEventListener('msvisibilitychange', this._visibilityChangeHandler, false);
            } else if (document.webkitHidden !== undefined) {
                this._hiddenAttr = 'webkitHidden';
                document.addEventListener('webkitvisibilitychange', this._visibilityChangeHandler, false);
            }
        }
        // bind tick function to current scope
        /* eslint-disable-next-line no-use-before-define */ this.tick = makeTick(this); // Circular linting issue as makeTick and Application reference each other
    }
    static{
        this._applications = {};
    }
    /**
     * Get the current application. In the case where there are multiple running applications, the
     * function can get an application based on a supplied canvas id. This function is particularly
     * useful when the current Application is not readily available. For example, in the JavaScript
     * console of the browser's developer tools.
     *
     * @param {string} [id] - If defined, the returned application should use the canvas which has
     * this id. Otherwise current application will be returned.
     * @returns {AppBase|undefined} The running application, if any.
     * @example
     * const app = pc.AppBase.getApplication();
     */ static getApplication(id) {
        return id ? AppBase._applications[id] : getApplication();
    }
    /** @private */ _initDefaultMaterial() {
        const material = new StandardMaterial();
        material.name = 'Default Material';
        setDefaultMaterial(this.graphicsDevice, material);
    }
    /** @private */ _initProgramLibrary() {
        const library = new ProgramLibrary(this.graphicsDevice, new StandardMaterial());
        setProgramLibrary(this.graphicsDevice, library);
    }
    /**
     * @type {SoundManager}
     * @ignore
     */ get soundManager() {
        return this._soundManager;
    }
    /**
     * The application's batch manager. The batch manager is used to merge mesh instances in
     * the scene, which reduces the overall number of draw calls, thereby boosting performance.
     *
     * @type {BatchManager}
     */ get batcher() {
        Debug.assert(this._batcher, 'BatchManager has not been created and is required for correct functionality.');
        return this._batcher;
    }
    /**
     * The current fill mode of the canvas. Can be:
     *
     * - {@link FILLMODE_NONE}: the canvas will always match the size provided.
     * - {@link FILLMODE_FILL_WINDOW}: the canvas will simply fill the window, changing aspect ratio.
     * - {@link FILLMODE_KEEP_ASPECT}: the canvas will grow to fill the window as best it can while
     * maintaining the aspect ratio.
     *
     * @type {string}
     */ get fillMode() {
        return this._fillMode;
    }
    /**
     * The current resolution mode of the canvas, Can be:
     *
     * - {@link RESOLUTION_AUTO}: if width and height are not provided, canvas will be resized to
     * match canvas client size.
     * - {@link RESOLUTION_FIXED}: resolution of canvas will be fixed.
     *
     * @type {string}
     */ get resolutionMode() {
        return this._resolutionMode;
    }
    /**
     * Load the application configuration file and apply application properties and fill the asset
     * registry.
     *
     * @param {string} url - The URL of the configuration file to load.
     * @param {ConfigureAppCallback} callback - The Function called when the configuration file is
     * loaded and parsed (or an error occurs).
     */ configure(url, callback) {
        http.get(url, (err, response)=>{
            if (err) {
                callback(err);
                return;
            }
            const props = response.application_properties;
            const scenes = response.scenes;
            const assets = response.assets;
            this._parseApplicationProperties(props, (err)=>{
                this._parseScenes(scenes);
                this._parseAssets(assets);
                if (!err) {
                    callback(null);
                } else {
                    callback(err);
                }
            });
        });
    }
    /**
     * Load all assets in the asset registry that are marked as 'preload'.
     *
     * @param {PreloadAppCallback} callback - Function called when all assets are loaded.
     */ preload(callback) {
        this.fire('preload:start');
        // get list of assets to preload
        const assets = this.assets.list({
            preload: true
        });
        if (assets.length === 0) {
            this.fire('preload:end');
            callback();
            return;
        }
        let loadedCount = 0;
        const onAssetLoadOrError = ()=>{
            loadedCount++;
            this.fire('preload:progress', loadedCount / assets.length);
            if (loadedCount === assets.length) {
                this.fire('preload:end');
                callback();
            }
        };
        // for each asset
        assets.forEach((asset)=>{
            if (!asset.loaded) {
                asset.once('load', onAssetLoadOrError);
                asset.once('error', onAssetLoadOrError);
                this.assets.load(asset);
            } else {
                onAssetLoadOrError();
            }
        });
    }
    _preloadScripts(sceneData, callback) {
        callback();
    }
    // set application properties from data file
    _parseApplicationProperties(props, callback) {
        // configure retrying assets
        if (typeof props.maxAssetRetries === 'number' && props.maxAssetRetries > 0) {
            this.loader.enableRetry(props.maxAssetRetries);
        }
        // TODO: remove this temporary block after migrating properties
        if (!props.useDevicePixelRatio) {
            props.useDevicePixelRatio = props.use_device_pixel_ratio;
        }
        if (!props.resolutionMode) {
            props.resolutionMode = props.resolution_mode;
        }
        if (!props.fillMode) {
            props.fillMode = props.fill_mode;
        }
        this._width = props.width;
        this._height = props.height;
        if (props.useDevicePixelRatio) {
            this.graphicsDevice.maxPixelRatio = window.devicePixelRatio;
        }
        this.setCanvasResolution(props.resolutionMode, this._width, this._height);
        this.setCanvasFillMode(props.fillMode, this._width, this._height);
        // set up layers
        if (props.layers && props.layerOrder) {
            const composition = new LayerComposition('application');
            const layers = {};
            for(const key in props.layers){
                const data = props.layers[key];
                data.id = parseInt(key, 10);
                // depth layer should only be enabled when needed
                // by incrementing its ref counter
                data.enabled = data.id !== LAYERID_DEPTH;
                layers[key] = new Layer(data);
            }
            for(let i = 0, len = props.layerOrder.length; i < len; i++){
                const sublayer = props.layerOrder[i];
                const layer = layers[sublayer.layer];
                if (!layer) continue;
                if (sublayer.transparent) {
                    composition.pushTransparent(layer);
                } else {
                    composition.pushOpaque(layer);
                }
                composition.subLayerEnabled[i] = sublayer.enabled;
            }
            this.scene.layers = composition;
        }
        // add batch groups
        if (props.batchGroups) {
            const batcher = this.batcher;
            if (batcher) {
                for(let i = 0, len = props.batchGroups.length; i < len; i++){
                    const grp = props.batchGroups[i];
                    batcher.addGroup(grp.name, grp.dynamic, grp.maxAabbSize, grp.id, grp.layers);
                }
            }
        }
        // set localization assets
        if (props.i18nAssets) {
            this.i18n.assets = props.i18nAssets;
        }
        this._loadLibraries(props.libraries, callback);
    }
    /**
     * @param {string[]} urls - List of URLs to load.
     * @param {Function} callback - Callback function.
     * @private
     */ _loadLibraries(urls, callback) {
        const len = urls.length;
        let count = len;
        const regex = /^https?:\/\//;
        if (len) {
            const onLoad = (err, script)=>{
                count--;
                if (err) {
                    callback(err);
                } else if (count === 0) {
                    this.onLibrariesLoaded();
                    callback(null);
                }
            };
            for(let i = 0; i < len; ++i){
                let url = urls[i];
                if (!regex.test(url.toLowerCase()) && this._scriptPrefix) {
                    url = path$1.join(this._scriptPrefix, url);
                }
                this.loader.load(url, "script", onLoad);
            }
        } else {
            this.onLibrariesLoaded();
            callback(null);
        }
    }
    /**
     * Insert scene name/urls into the registry.
     *
     * @param {*} scenes - Scenes to add to the scene registry.
     * @private
     */ _parseScenes(scenes) {
        if (!scenes) return;
        for(let i = 0; i < scenes.length; i++){
            this.scenes.add(scenes[i].name, scenes[i].url);
        }
    }
    /**
     * Insert assets into registry.
     *
     * @param {*} assets - Assets to insert.
     * @private
     */ _parseAssets(assets) {
        const list = [];
        const scriptsIndex = {};
        const bundlesIndex = {};
        // add scripts in order of loading first
        for(let i = 0; i < this.scriptsOrder.length; i++){
            const id = this.scriptsOrder[i];
            if (!assets[id]) {
                continue;
            }
            scriptsIndex[id] = true;
            list.push(assets[id]);
        }
        // then add bundles
        if (this.enableBundles) {
            for(const id in assets){
                if (assets[id].type === 'bundle') {
                    bundlesIndex[id] = true;
                    list.push(assets[id]);
                }
            }
        }
        // then add rest of assets
        for(const id in assets){
            if (scriptsIndex[id] || bundlesIndex[id]) {
                continue;
            }
            list.push(assets[id]);
        }
        for(let i = 0; i < list.length; i++){
            const data = list[i];
            const asset = new Asset(data.name, data.type, data.file, data.data);
            asset.id = parseInt(data.id, 10);
            asset.preload = data.preload ? data.preload : false;
            // if this is a script asset and has already been embedded in the page then
            // mark it as loaded
            asset.loaded = data.type === "script" && data.data && data.data.loadingType > 0;
            // tags
            asset.tags.add(data.tags);
            // i18n
            if (data.i18n) {
                for(const locale in data.i18n){
                    asset.addLocalizedAssetId(locale, data.i18n[locale]);
                }
            }
            // registry
            this.assets.add(asset);
        }
    }
    /**
     * Start the application. This function does the following:
     *
     * 1. Fires an event on the application named 'start'
     * 2. Calls initialize for all components on entities in the hierarchy
     * 3. Fires an event on the application named 'initialize'
     * 4. Calls postInitialize for all components on entities in the hierarchy
     * 5. Fires an event on the application named 'postinitialize'
     * 6. Starts executing the main loop of the application
     *
     * This function is called internally by PlayCanvas applications made in the Editor but you
     * will need to call start yourself if you are using the engine stand-alone.
     *
     * @example
     * app.start();
     */ start() {
        Debug.call(()=>{
            Debug.assert(!this._alreadyStarted, 'The application can be started only one time.');
            this._alreadyStarted = true;
        });
        this.frame = 0;
        this.fire('start', {
            timestamp: now(),
            target: this
        });
        if (!this._librariesLoaded) {
            this.onLibrariesLoaded();
        }
        this.systems.fire('initialize', this.root);
        this.fire('initialize');
        this.systems.fire('postInitialize', this.root);
        this.systems.fire('postPostInitialize', this.root);
        this.fire('postinitialize');
        this.tick();
    }
    /**
     * Update all input devices managed by the application.
     *
     * @param {number} dt - The time in seconds since the last update.
     * @private
     */ inputUpdate(dt) {
        if (this.controller) {
            this.controller.update(dt);
        }
        if (this.mouse) {
            this.mouse.update();
        }
        if (this.keyboard) {
            this.keyboard.update();
        }
        if (this.gamepads) {
            this.gamepads.update();
        }
    }
    /**
     * Update the application. This function will call the update functions and then the postUpdate
     * functions of all enabled components. It will then update the current state of all connected
     * input devices. This function is called internally in the application's main loop and does
     * not need to be called explicitly.
     *
     * @param {number} dt - The time delta in seconds since the last frame.
     */ update(dt) {
        this.frame++;
        this.graphicsDevice.update();
        this.stats.frame.updateStart = now();
        this.systems.fire(this._inTools ? 'toolsUpdate' : 'update', dt);
        this.systems.fire('animationUpdate', dt);
        this.systems.fire('postUpdate', dt);
        // fire update event
        this.fire('update', dt);
        // update input devices
        this.inputUpdate(dt);
        this.stats.frame.updateTime = now() - this.stats.frame.updateStart;
    }
    /**
     * Render the application's scene. More specifically, the scene's {@link LayerComposition} is
     * rendered. This function is called internally in the application's main loop and does not
     * need to be called explicitly.
     *
     * @ignore
     */ render() {
        this.updateCanvasSize();
        this.graphicsDevice.frameStart();
        this.stats.frame.renderStart = now();
        this.fire('prerender');
        this.root.syncHierarchy();
        if (this._batcher) {
            this._batcher.updateAll();
        }
        ForwardRenderer._skipRenderCounter = 0;
        // render the scene composition
        this.renderComposition(this.scene.layers);
        this.fire('postrender');
        this.stats.frame.renderTime = now() - this.stats.frame.renderStart;
        this.graphicsDevice.frameEnd();
    }
    // render a layer composition
    renderComposition(layerComposition) {
        DebugGraphics.clearGpuMarkers();
        // update composition, cull everything, assign atlas slots for clustered lighting
        this.renderer.update(layerComposition);
        this.renderer.buildFrameGraph(this.frameGraph, layerComposition);
        this.frameGraph.render(this.graphicsDevice);
    }
    /**
     * @param {number} now - The timestamp passed to the requestAnimationFrame callback.
     * @param {number} dt - The time delta in seconds since the last frame. This is subject to the
     * application's time scale and max delta values.
     * @param {number} ms - The time in milliseconds since the last frame.
     * @private
     */ _fillFrameStatsBasic(now, dt, ms) {
        // Timing stats
        const stats = this.stats.frame;
        stats.dt = dt;
        stats.ms = ms;
        if (now > stats._timeToCountFrames) {
            stats.fps = stats._fpsAccum;
            stats._fpsAccum = 0;
            stats._timeToCountFrames = now + 1000;
        } else {
            stats._fpsAccum++;
        }
        // total draw call
        this.stats.drawCalls.total = this.graphicsDevice._drawCallsPerFrame;
        this.graphicsDevice._drawCallsPerFrame = 0;
    }
    /** @private */ _fillFrameStats() {
        let stats = this.stats.frame;
        // Render stats
        stats.cameras = this.renderer._camerasRendered;
        stats.materials = this.renderer._materialSwitches;
        stats.shaders = this.graphicsDevice._shaderSwitchesPerFrame;
        stats.shadowMapUpdates = this.renderer._shadowMapUpdates;
        stats.shadowMapTime = this.renderer._shadowMapTime;
        stats.depthMapTime = this.renderer._depthMapTime;
        stats.forwardTime = this.renderer._forwardTime;
        const prims = this.graphicsDevice._primsPerFrame;
        stats.triangles = prims[PRIMITIVE_TRIANGLES] / 3 + Math.max(prims[PRIMITIVE_TRISTRIP] - 2, 0) + Math.max(prims[PRIMITIVE_TRIFAN] - 2, 0);
        stats.cullTime = this.renderer._cullTime;
        stats.sortTime = this.renderer._sortTime;
        stats.skinTime = this.renderer._skinTime;
        stats.morphTime = this.renderer._morphTime;
        stats.lightClusters = this.renderer._lightClusters;
        stats.lightClustersTime = this.renderer._lightClustersTime;
        stats.otherPrimitives = 0;
        for(let i = 0; i < prims.length; i++){
            if (i < PRIMITIVE_TRIANGLES) {
                stats.otherPrimitives += prims[i];
            }
            prims[i] = 0;
        }
        this.renderer._camerasRendered = 0;
        this.renderer._materialSwitches = 0;
        this.renderer._shadowMapUpdates = 0;
        this.graphicsDevice._shaderSwitchesPerFrame = 0;
        this.renderer._cullTime = 0;
        this.renderer._layerCompositionUpdateTime = 0;
        this.renderer._lightClustersTime = 0;
        this.renderer._sortTime = 0;
        this.renderer._skinTime = 0;
        this.renderer._morphTime = 0;
        this.renderer._shadowMapTime = 0;
        this.renderer._depthMapTime = 0;
        this.renderer._forwardTime = 0;
        // Draw call stats
        stats = this.stats.drawCalls;
        stats.forward = this.renderer._forwardDrawCalls;
        stats.culled = this.renderer._numDrawCallsCulled;
        stats.depth = 0;
        stats.shadow = this.renderer._shadowDrawCalls;
        stats.skinned = this.renderer._skinDrawCalls;
        stats.immediate = 0;
        stats.instanced = 0;
        stats.removedByInstancing = 0;
        stats.misc = stats.total - (stats.forward + stats.shadow);
        this.renderer._depthDrawCalls = 0;
        this.renderer._shadowDrawCalls = 0;
        this.renderer._forwardDrawCalls = 0;
        this.renderer._numDrawCallsCulled = 0;
        this.renderer._skinDrawCalls = 0;
        this.renderer._immediateRendered = 0;
        this.renderer._instancedDrawCalls = 0;
        this.stats.misc.renderTargetCreationTime = this.graphicsDevice.renderTargetCreationTime;
        stats = this.stats.particles;
        stats.updatesPerFrame = stats._updatesPerFrame;
        stats.frameTime = stats._frameTime;
        stats._updatesPerFrame = 0;
        stats._frameTime = 0;
    }
    /**
     * Controls how the canvas fills the window and resizes when the window changes.
     *
     * @param {string} mode - The mode to use when setting the size of the canvas. Can be:
     *
     * - {@link FILLMODE_NONE}: the canvas will always match the size provided.
     * - {@link FILLMODE_FILL_WINDOW}: the canvas will simply fill the window, changing aspect ratio.
     * - {@link FILLMODE_KEEP_ASPECT}: the canvas will grow to fill the window as best it can while
     * maintaining the aspect ratio.
     *
     * @param {number} [width] - The width of the canvas (only used when mode is {@link FILLMODE_NONE}).
     * @param {number} [height] - The height of the canvas (only used when mode is {@link FILLMODE_NONE}).
     */ setCanvasFillMode(mode, width, height) {
        this._fillMode = mode;
        this.resizeCanvas(width, height);
    }
    /**
     * Change the resolution of the canvas, and set the way it behaves when the window is resized.
     *
     * @param {string} mode - The mode to use when setting the resolution. Can be:
     *
     * - {@link RESOLUTION_AUTO}: if width and height are not provided, canvas will be resized to
     * match canvas client size.
     * - {@link RESOLUTION_FIXED}: resolution of canvas will be fixed.
     *
     * @param {number} [width] - The horizontal resolution, optional in AUTO mode, if not provided
     * canvas clientWidth is used.
     * @param {number} [height] - The vertical resolution, optional in AUTO mode, if not provided
     * canvas clientHeight is used.
     */ setCanvasResolution(mode, width, height) {
        this._resolutionMode = mode;
        // In AUTO mode the resolution is the same as the canvas size, unless specified
        if (mode === RESOLUTION_AUTO && width === undefined) {
            width = this.graphicsDevice.canvas.clientWidth;
            height = this.graphicsDevice.canvas.clientHeight;
        }
        this.graphicsDevice.resizeCanvas(width, height);
    }
    /**
     * Queries the visibility of the window or tab in which the application is running.
     *
     * @returns {boolean} True if the application is not visible and false otherwise.
     */ isHidden() {
        return document[this._hiddenAttr];
    }
    /**
     * Called when the visibility state of the current tab/window changes.
     *
     * @private
     */ onVisibilityChange() {
        if (this.isHidden()) {
            if (this._soundManager) {
                this._soundManager.suspend();
            }
        } else {
            if (this._soundManager) {
                this._soundManager.resume();
            }
        }
    }
    /**
     * Resize the application's canvas element in line with the current fill mode.
     *
     * - In {@link FILLMODE_KEEP_ASPECT} mode, the canvas will grow to fill the window as best it
     * can while maintaining the aspect ratio.
     * - In {@link FILLMODE_FILL_WINDOW} mode, the canvas will simply fill the window, changing
     * aspect ratio.
     * - In {@link FILLMODE_NONE} mode, the canvas will always match the size provided.
     *
     * @param {number} [width] - The width of the canvas. Only used if current fill mode is {@link FILLMODE_NONE}.
     * @param {number} [height] - The height of the canvas. Only used if current fill mode is {@link FILLMODE_NONE}.
     * @returns {object} A object containing the values calculated to use as width and height.
     */ resizeCanvas(width, height) {
        if (!this._allowResize) return undefined; // prevent resizing (e.g. if presenting in VR HMD)
        // prevent resizing when in XR session
        if (this.xr && this.xr.session) {
            return undefined;
        }
        const windowWidth = window.innerWidth;
        const windowHeight = window.innerHeight;
        if (this._fillMode === FILLMODE_KEEP_ASPECT) {
            const r = this.graphicsDevice.canvas.width / this.graphicsDevice.canvas.height;
            const winR = windowWidth / windowHeight;
            if (r > winR) {
                width = windowWidth;
                height = width / r;
            } else {
                height = windowHeight;
                width = height * r;
            }
        } else if (this._fillMode === FILLMODE_FILL_WINDOW) {
            width = windowWidth;
            height = windowHeight;
        }
        // OTHERWISE: FILLMODE_NONE use width and height that are provided
        this.graphicsDevice.canvas.style.width = `${width}px`;
        this.graphicsDevice.canvas.style.height = `${height}px`;
        this.updateCanvasSize();
        // return the final values calculated for width and height
        return {
            width: width,
            height: height
        };
    }
    /**
     * Updates the {@link GraphicsDevice} canvas size to match the canvas size on the document
     * page. It is recommended to call this function when the canvas size changes (e.g on window
     * resize and orientation change events) so that the canvas resolution is immediately updated.
     */ updateCanvasSize() {
        // Don't update if we are in VR or XR
        if (!this._allowResize || this.xr?.active) {
            return;
        }
        // In AUTO mode the resolution is changed to match the canvas size
        if (this._resolutionMode === RESOLUTION_AUTO) {
            // Check if the canvas DOM has changed size
            const canvas = this.graphicsDevice.canvas;
            this.graphicsDevice.resizeCanvas(canvas.clientWidth, canvas.clientHeight);
        }
    }
    /**
     * Event handler called when all code libraries have been loaded. Code libraries are passed
     * into the constructor of the Application and the application won't start running or load
     * packs until all libraries have been loaded.
     *
     * @private
     */ onLibrariesLoaded() {
        this._librariesLoaded = true;
        if (this.systems.rigidbody) {
            this.systems.rigidbody.onLibraryLoaded();
        }
    }
    /**
     * Apply scene settings to the current scene. Useful when your scene settings are parsed or
     * generated from a non-URL source.
     *
     * @param {object} settings - The scene settings to be applied.
     * @param {object} settings.physics - The physics settings to be applied.
     * @param {number[]} settings.physics.gravity - The world space vector representing global
     * gravity in the physics simulation. Must be a fixed size array with three number elements,
     * corresponding to each axis [ X, Y, Z ].
     * @param {object} settings.render - The rendering settings to be applied.
     * @param {number[]} settings.render.global_ambient - The color of the scene's ambient light.
     * Must be a fixed size array with three number elements, corresponding to each color channel
     * [ R, G, B ].
     * @param {string} settings.render.fog - The type of fog used by the scene. Can be:
     *
     * - {@link FOG_NONE}
     * - {@link FOG_LINEAR}
     * - {@link FOG_EXP}
     * - {@link FOG_EXP2}
     *
     * @param {number[]} settings.render.fog_color - The color of the fog (if enabled). Must be a
     * fixed size array with three number elements, corresponding to each color channel [ R, G, B ].
     * @param {number} settings.render.fog_density - The density of the fog (if enabled). This
     * property is only valid if the fog property is set to {@link FOG_EXP} or {@link FOG_EXP2}.
     * @param {number} settings.render.fog_start - The distance from the viewpoint where linear fog
     * begins. This property is only valid if the fog property is set to {@link FOG_LINEAR}.
     * @param {number} settings.render.fog_end - The distance from the viewpoint where linear fog
     * reaches its maximum. This property is only valid if the fog property is set to {@link FOG_LINEAR}.
     * @param {number} settings.render.gamma_correction - The gamma correction to apply when
     * rendering the scene. Can be:
     *
     * - {@link GAMMA_NONE}
     * - {@link GAMMA_SRGB}
     *
     * @param {number} settings.render.tonemapping - The tonemapping transform to apply when
     * writing fragments to the frame buffer. Can be:
     *
     * - {@link TONEMAP_LINEAR}
     * - {@link TONEMAP_FILMIC}
     * - {@link TONEMAP_HEJL}
     * - {@link TONEMAP_ACES}
     * - {@link TONEMAP_ACES2}
     * - {@link TONEMAP_NEUTRAL}
     *
     * @param {number} settings.render.exposure - The exposure value tweaks the overall brightness
     * of the scene.
     * @param {number|null} [settings.render.skybox] - The asset ID of the cube map texture to be
     * used as the scene's skybox. Defaults to null.
     * @param {number} settings.render.skyboxIntensity - Multiplier for skybox intensity.
     * @param {number} settings.render.skyboxLuminance - Lux (lm/m^2) value for skybox intensity when physical light units are enabled.
     * @param {number} settings.render.skyboxMip - The mip level of the skybox to be displayed.
     * Only valid for prefiltered cubemap skyboxes.
     * @param {number[]} settings.render.skyboxRotation - Rotation of skybox.
     * @param {number} settings.render.lightmapSizeMultiplier - The lightmap resolution multiplier.
     * @param {number} settings.render.lightmapMaxResolution - The maximum lightmap resolution.
     * @param {number} settings.render.lightmapMode - The lightmap baking mode. Can be:
     *
     * - {@link BAKE_COLOR}: single color lightmap
     * - {@link BAKE_COLORDIR}: single color lightmap + dominant light direction (used for bump/specular)
     *
     * @param {boolean} settings.render.ambientBake - Enable baking ambient light into lightmaps.
     * @param {number} settings.render.ambientBakeNumSamples - Number of samples to use when baking ambient light.
     * @param {number} settings.render.ambientBakeSpherePart - How much of the sphere to include when baking ambient light.
     * @param {number} settings.render.ambientBakeOcclusionBrightness - Brightness of the baked ambient occlusion.
     * @param {number} settings.render.ambientBakeOcclusionContrast - Contrast of the baked ambient occlusion.
     * @param {number} settings.render.ambientLuminance - Lux (lm/m^2) value for ambient light intensity.
     *
     * @param {boolean} settings.render.clusteredLightingEnabled - Enable clustered lighting.
     * @param {boolean} settings.render.lightingShadowsEnabled - If set to true, the clustered lighting will support shadows.
     * @param {boolean} settings.render.lightingCookiesEnabled - If set to true, the clustered lighting will support cookie textures.
     * @param {boolean} settings.render.lightingAreaLightsEnabled - If set to true, the clustered lighting will support area lights.
     * @param {number} settings.render.lightingShadowAtlasResolution - Resolution of the atlas texture storing all non-directional shadow textures.
     * @param {number} settings.render.lightingCookieAtlasResolution - Resolution of the atlas texture storing all non-directional cookie textures.
     * @param {number} settings.render.lightingMaxLightsPerCell - Maximum number of lights a cell can store.
     * @param {number} settings.render.lightingShadowType - The type of shadow filtering used by all shadows. Can be:
     *
     * - {@link SHADOW_PCF1_32F}
     * - {@link SHADOW_PCF3_32F}
     * - {@link SHADOW_PCF5_32F}
     * - {@link SHADOW_PCF1_16F}
     * - {@link SHADOW_PCF3_16F}
     * - {@link SHADOW_PCF5_16F}
     *
     * @param {Vec3} settings.render.lightingCells - Number of cells along each world space axis the space containing lights
     * is subdivided into.
     *
     * Only lights with bakeDir=true will be used for generating the dominant light direction.
     * @example
     *
     * const settings = {
     *     physics: {
     *         gravity: [0, -9.8, 0]
     *     },
     *     render: {
     *         fog_end: 1000,
     *         tonemapping: 0,
     *         skybox: null,
     *         fog_density: 0.01,
     *         gamma_correction: 1,
     *         exposure: 1,
     *         fog_start: 1,
     *         global_ambient: [0, 0, 0],
     *         skyboxIntensity: 1,
     *         skyboxRotation: [0, 0, 0],
     *         fog_color: [0, 0, 0],
     *         lightmapMode: 1,
     *         fog: 'none',
     *         lightmapMaxResolution: 2048,
     *         skyboxMip: 2,
     *         lightmapSizeMultiplier: 16
     *     }
     * };
     * app.applySceneSettings(settings);
     */ applySceneSettings(settings) {
        let asset;
        if (this.systems.rigidbody && typeof Ammo !== 'undefined') {
            const [x, y, z] = settings.physics.gravity;
            this.systems.rigidbody.gravity.set(x, y, z);
        }
        this.scene.applySettings(settings);
        if (settings.render.hasOwnProperty('skybox')) {
            if (settings.render.skybox) {
                asset = this.assets.get(settings.render.skybox);
                if (asset) {
                    this.setSkybox(asset);
                } else {
                    this.assets.once(`add:${settings.render.skybox}`, this.setSkybox, this);
                }
            } else {
                this.setSkybox(null);
            }
        }
    }
    /**
     * Sets the area light LUT tables for this app.
     *
     * @param {number[]} ltcMat1 - LUT table of type `array` to be set.
     * @param {number[]} ltcMat2 - LUT table of type `array` to be set.
     */ setAreaLightLuts(ltcMat1, ltcMat2) {
        if (ltcMat1 && ltcMat2) {
            AreaLightLuts.set(this.graphicsDevice, ltcMat1, ltcMat2);
        } else {
            Debug.warn('setAreaLightLuts: LUTs for area light are not valid');
        }
    }
    /**
     * Sets the skybox asset to current scene, and subscribes to asset load/change events.
     *
     * @param {Asset} asset - Asset of type `skybox` to be set to, or null to remove skybox.
     */ setSkybox(asset) {
        if (asset !== this._skyboxAsset) {
            const onSkyboxRemoved = ()=>{
                this.setSkybox(null);
            };
            const onSkyboxChanged = ()=>{
                this.scene.setSkybox(this._skyboxAsset ? this._skyboxAsset.resources : null);
            };
            // cleanup previous asset
            if (this._skyboxAsset) {
                this.assets.off(`load:${this._skyboxAsset.id}`, onSkyboxChanged, this);
                this.assets.off(`remove:${this._skyboxAsset.id}`, onSkyboxRemoved, this);
                this._skyboxAsset.off('change', onSkyboxChanged, this);
            }
            // set new asset
            this._skyboxAsset = asset;
            if (this._skyboxAsset) {
                this.assets.on(`load:${this._skyboxAsset.id}`, onSkyboxChanged, this);
                this.assets.once(`remove:${this._skyboxAsset.id}`, onSkyboxRemoved, this);
                this._skyboxAsset.on('change', onSkyboxChanged, this);
                if (this.scene.skyboxMip === 0 && !this._skyboxAsset.loadFaces) {
                    this._skyboxAsset.loadFaces = true;
                }
                this.assets.load(this._skyboxAsset);
            }
            onSkyboxChanged();
        }
    }
    /** @private */ _firstBake() {
        this.lightmapper?.bake(null, this.scene.lightmapMode);
    }
    /** @private */ _firstBatch() {
        this.batcher?.generate();
    }
    /**
     * Provide an opportunity to modify the timestamp supplied by requestAnimationFrame.
     *
     * @param {number} [timestamp] - The timestamp supplied by requestAnimationFrame.
     * @returns {number|undefined} The modified timestamp.
     * @ignore
     */ _processTimestamp(timestamp) {
        return timestamp;
    }
    /**
     * Draws a single line. Line start and end coordinates are specified in world space. The line
     * will be flat-shaded with the specified color.
     *
     * @param {Vec3} start - The start world space coordinate of the line.
     * @param {Vec3} end - The end world space coordinate of the line.
     * @param {Color} [color] - The color of the line. It defaults to white if not specified.
     * @param {boolean} [depthTest] - Specifies if the line is depth tested against the depth
     * buffer. Defaults to true.
     * @param {Layer} [layer] - The layer to render the line into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @example
     * // Render a 1-unit long white line
     * const start = new pc.Vec3(0, 0, 0);
     * const end = new pc.Vec3(1, 0, 0);
     * app.drawLine(start, end);
     * @example
     * // Render a 1-unit long red line which is not depth tested and renders on top of other geometry
     * const start = new pc.Vec3(0, 0, 0);
     * const end = new pc.Vec3(1, 0, 0);
     * app.drawLine(start, end, pc.Color.RED, false);
     * @example
     * // Render a 1-unit long white line into the world layer
     * const start = new pc.Vec3(0, 0, 0);
     * const end = new pc.Vec3(1, 0, 0);
     * const worldLayer = app.scene.layers.getLayerById(pc.LAYERID_WORLD);
     * app.drawLine(start, end, pc.Color.WHITE, true, worldLayer);
     */ drawLine(start, end, color, depthTest, layer) {
        this.scene.drawLine(start, end, color, depthTest, layer);
    }
    /**
     * Renders an arbitrary number of discrete line segments. The lines are not connected by each
     * subsequent point in the array. Instead, they are individual segments specified by two
     * points. Therefore, the lengths of the supplied position and color arrays must be the same
     * and also must be a multiple of 2. The colors of the ends of each line segment will be
     * interpolated along the length of each line.
     *
     * @param {Vec3[]} positions - An array of points to draw lines between. The length of the
     * array must be a multiple of 2.
     * @param {Color[] | Color} colors - An array of colors or a single color. If an array is
     * specified, this must be the same length as the position array. The length of the array
     * must also be a multiple of 2.
     * @param {boolean} [depthTest] - Specifies if the lines are depth tested against the depth
     * buffer. Defaults to true.
     * @param {Layer} [layer] - The layer to render the lines into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @example
     * // Render a single line, with unique colors for each point
     * const start = new pc.Vec3(0, 0, 0);
     * const end = new pc.Vec3(1, 0, 0);
     * app.drawLines([start, end], [pc.Color.RED, pc.Color.WHITE]);
     * @example
     * // Render 2 discrete line segments
     * const points = [
     *     // Line 1
     *     new pc.Vec3(0, 0, 0),
     *     new pc.Vec3(1, 0, 0),
     *     // Line 2
     *     new pc.Vec3(1, 1, 0),
     *     new pc.Vec3(1, 1, 1)
     * ];
     * const colors = [
     *     // Line 1
     *     pc.Color.RED,
     *     pc.Color.YELLOW,
     *     // Line 2
     *     pc.Color.CYAN,
     *     pc.Color.BLUE
     * ];
     * app.drawLines(points, colors);
     */ drawLines(positions, colors, depthTest = true, layer = this.scene.defaultDrawLayer) {
        this.scene.drawLines(positions, colors, depthTest, layer);
    }
    /**
     * Renders an arbitrary number of discrete line segments. The lines are not connected by each
     * subsequent point in the array. Instead, they are individual segments specified by two
     * points.
     *
     * @param {number[]} positions - An array of points to draw lines between. Each point is
     * represented by 3 numbers - x, y and z coordinate.
     * @param {number[]|Color} colors - A single color for all lines, or an array of colors to color
     * the lines. If an array is specified, number of colors it stores must match the number of
     * positions provided.
     * @param {boolean} [depthTest] - Specifies if the lines are depth tested against the depth
     * buffer. Defaults to true.
     * @param {Layer} [layer] - The layer to render the lines into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @example
     * // Render 2 discrete line segments
     * const points = [
     *     // Line 1
     *     0, 0, 0,
     *     1, 0, 0,
     *     // Line 2
     *     1, 1, 0,
     *     1, 1, 1
     * ];
     * const colors = [
     *     // Line 1
     *     1, 0, 0, 1,  // red
     *     0, 1, 0, 1,  // green
     *     // Line 2
     *     0, 0, 1, 1,  // blue
     *     1, 1, 1, 1   // white
     * ];
     * app.drawLineArrays(points, colors);
     */ drawLineArrays(positions, colors, depthTest = true, layer = this.scene.defaultDrawLayer) {
        this.scene.drawLineArrays(positions, colors, depthTest, layer);
    }
    /**
     * Draws a wireframe sphere with center, radius and color.
     *
     * @param {Vec3} center - The center of the sphere.
     * @param {number} radius - The radius of the sphere.
     * @param {Color} [color] - The color of the sphere. It defaults to white if not specified.
     * @param {number} [segments] - Number of line segments used to render the circles forming the
     * sphere. Defaults to 20.
     * @param {boolean} [depthTest] - Specifies if the sphere lines are depth tested against the
     * depth buffer. Defaults to true.
     * @param {Layer} [layer] - The layer to render the sphere into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @example
     * // Render a red wire sphere with radius of 1
     * const center = new pc.Vec3(0, 0, 0);
     * app.drawWireSphere(center, 1.0, pc.Color.RED);
     * @ignore
     */ drawWireSphere(center, radius, color = Color.WHITE, segments = 20, depthTest = true, layer = this.scene.defaultDrawLayer) {
        this.scene.immediate.drawWireSphere(center, radius, color, segments, depthTest, layer);
    }
    /**
     * Draws a wireframe axis aligned box specified by min and max points and color.
     *
     * @param {Vec3} minPoint - The min corner point of the box.
     * @param {Vec3} maxPoint - The max corner point of the box.
     * @param {Color} [color] - The color of the sphere. It defaults to white if not specified.
     * @param {boolean} [depthTest] - Specifies if the sphere lines are depth tested against the
     * depth buffer. Defaults to true.
     * @param {Layer} [layer] - The layer to render the sphere into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @param {Mat4} [mat] - Matrix to transform the box before rendering.
     * @example
     * // Render a red wire aligned box
     * const min = new pc.Vec3(-1, -1, -1);
     * const max = new pc.Vec3(1, 1, 1);
     * app.drawWireAlignedBox(min, max, pc.Color.RED);
     * @ignore
     */ drawWireAlignedBox(minPoint, maxPoint, color = Color.WHITE, depthTest = true, layer = this.scene.defaultDrawLayer, mat) {
        this.scene.immediate.drawWireAlignedBox(minPoint, maxPoint, color, depthTest, layer, mat);
    }
    /**
     * Draw meshInstance at this frame
     *
     * @param {MeshInstance} meshInstance - The mesh instance
     * to draw.
     * @param {Layer} [layer] - The layer to render the mesh instance into. Defaults to
     * {@link LAYERID_IMMEDIATE}.
     * @ignore
     */ drawMeshInstance(meshInstance, layer = this.scene.defaultDrawLayer) {
        this.scene.immediate.drawMesh(null, null, null, meshInstance, layer);
    }
    /**
     * Draw mesh at this frame.
     *
     * @param {Mesh} mesh - The mesh to draw.
     * @param {Material} material - The material to use to render the mesh.
     * @param {Mat4} matrix - The matrix to use to render the mesh.
     * @param {Layer} [layer] - The layer to render the mesh into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @ignore
     */ drawMesh(mesh, material, matrix, layer = this.scene.defaultDrawLayer) {
        this.scene.immediate.drawMesh(material, matrix, mesh, null, layer);
    }
    /**
     * Draw quad of size [-0.5, 0.5] at this frame.
     *
     * @param {Mat4} matrix - The matrix to use to render the quad.
     * @param {Material} material - The material to use to render the quad.
     * @param {Layer} [layer] - The layer to render the quad into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @ignore
     */ drawQuad(matrix, material, layer = this.scene.defaultDrawLayer) {
        this.scene.immediate.drawMesh(material, matrix, this.scene.immediate.getQuadMesh(), null, layer);
    }
    /**
     * Draws a texture at [x, y] position on screen, with size [width, height]. The origin of the
     * screen is top-left [0, 0]. Coordinates and sizes are in projected space (-1 .. 1).
     *
     * @param {number} x - The x coordinate on the screen of the top left corner of the texture.
     * Should be in the range [-1, 1].
     * @param {number} y - The y coordinate on the screen of the top left corner of the texture.
     * Should be in the range [-1, 1].
     * @param {number} width - The width of the rectangle of the rendered texture. Should be in the
     * range [0, 2].
     * @param {number} height - The height of the rectangle of the rendered texture. Should be in
     * the range [0, 2].
     * @param {Texture} texture - The texture to render.
     * @param {Material} material - The material used when rendering the texture.
     * @param {Layer} [layer] - The layer to render the texture into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @param {boolean} [filterable] - Indicate if the texture can be sampled using filtering.
     * Passing false uses unfiltered sampling, allowing a depth texture to be sampled on WebGPU.
     * Defaults to true.
     * @ignore
     */ drawTexture(x, y, width, height, texture, material, layer = this.scene.defaultDrawLayer, filterable = true) {
        // only WebGPU supports filterable parameter to be false, allowing a depth texture / shadow
        // map to be fetched (without filtering) and rendered
        if (filterable === false && !this.graphicsDevice.isWebGPU) {
            return;
        }
        // TODO: if this is used for anything other than debug texture display, we should optimize this to avoid allocations
        const matrix = new Mat4();
        matrix.setTRS(new Vec3(x, y, 0.0), Quat.IDENTITY, new Vec3(width, -height, 0.0));
        if (!material) {
            material = new ShaderMaterial();
            material.cull = CULLFACE_NONE;
            material.setParameter('colorMap', texture);
            material.shaderDesc = filterable ? this.scene.immediate.getTextureShaderDesc(texture.encoding) : this.scene.immediate.getUnfilterableTextureShaderDesc();
            material.update();
        }
        this.drawQuad(matrix, material, layer);
    }
    /**
     * Draws a depth texture at [x, y] position on screen, with size [width, height]. The origin of
     * the screen is top-left [0, 0]. Coordinates and sizes are in projected space (-1 .. 1).
     *
     * @param {number} x - The x coordinate on the screen of the top left corner of the texture.
     * Should be in the range [-1, 1].
     * @param {number} y - The y coordinate on the screen of the top left corner of the texture.
     * Should be in the range [-1, 1].
     * @param {number} width - The width of the rectangle of the rendered texture. Should be in the
     * range [0, 2].
     * @param {number} height - The height of the rectangle of the rendered texture. Should be in
     * the range [0, 2].
     * @param {Layer} [layer] - The layer to render the texture into. Defaults to {@link LAYERID_IMMEDIATE}.
     * @ignore
     */ drawDepthTexture(x, y, width, height, layer = this.scene.defaultDrawLayer) {
        const material = new ShaderMaterial();
        material.cull = CULLFACE_NONE;
        material.shaderDesc = this.scene.immediate.getDepthTextureShaderDesc();
        material.update();
        this.drawTexture(x, y, width, height, null, material, layer);
    }
    /**
     * Destroys application and removes all event listeners at the end of the current engine frame
     * update. However, if called outside of the engine frame update, calling destroy() will
     * destroy the application immediately.
     *
     * @example
     * app.destroy();
     */ destroy() {
        if (this._inFrameUpdate) {
            this._destroyRequested = true;
            return;
        }
        const canvasId = this.graphicsDevice.canvas.id;
        this.fire('destroy', this); // fire destroy event
        this.off('librariesloaded');
        if (typeof document !== 'undefined') {
            document.removeEventListener('visibilitychange', this._visibilityChangeHandler, false);
            document.removeEventListener('mozvisibilitychange', this._visibilityChangeHandler, false);
            document.removeEventListener('msvisibilitychange', this._visibilityChangeHandler, false);
            document.removeEventListener('webkitvisibilitychange', this._visibilityChangeHandler, false);
        }
        this._visibilityChangeHandler = null;
        this.root.destroy();
        this.root = null;
        if (this.mouse) {
            this.mouse.off();
            this.mouse.detach();
            this.mouse = null;
        }
        if (this.keyboard) {
            this.keyboard.off();
            this.keyboard.detach();
            this.keyboard = null;
        }
        if (this.touch) {
            this.touch.off();
            this.touch.detach();
            this.touch = null;
        }
        if (this.elementInput) {
            this.elementInput.detach();
            this.elementInput = null;
        }
        if (this.gamepads) {
            this.gamepads.destroy();
            this.gamepads = null;
        }
        if (this.controller) {
            this.controller = null;
        }
        this.systems.destroy();
        // layer composition
        if (this.scene.layers) {
            this.scene.layers.destroy();
        }
        // destroy all texture resources
        const assets = this.assets.list();
        for(let i = 0; i < assets.length; i++){
            assets[i].unload();
            assets[i].off();
        }
        this.assets.off();
        // destroy bundle registry
        this.bundles.destroy();
        this.bundles = null;
        this.i18n.destroy();
        this.i18n = null;
        const scriptHandler = this.loader.getHandler("script");
        scriptHandler?.clearCache();
        this.loader.destroy();
        this.loader = null;
        this.scene.destroy();
        this.scene = null;
        this.systems = null;
        this.context = null;
        // script registry
        this.scripts.destroy();
        this.scripts = null;
        this.scenes.destroy();
        this.scenes = null;
        this.lightmapper?.destroy();
        this.lightmapper = null;
        if (this._batcher) {
            this._batcher.destroy();
            this._batcher = null;
        }
        this._entityIndex = {};
        this.defaultLayerDepth.onDisable = null;
        this.defaultLayerDepth.onEnable = null;
        this.defaultLayerDepth = null;
        this.defaultLayerWorld = null;
        this.xr?.end();
        this.xr?.destroy();
        this.renderer.destroy();
        this.renderer = null;
        this.graphicsDevice.destroy();
        this.graphicsDevice = null;
        this.tick = null;
        this.off(); // remove all events
        this._soundManager?.destroy();
        this._soundManager = null;
        AppBase._applications[canvasId] = null;
        if (getApplication() === this) {
            setApplication(null);
        }
        AppBase.cancelTick(this);
    }
    static cancelTick(app) {
        if (app.frameRequestId) {
            window.cancelAnimationFrame(app.frameRequestId);
            app.frameRequestId = undefined;
        }
    }
    /**
     * Get entity from the index by guid.
     *
     * @param {string} guid - The GUID to search for.
     * @returns {Entity} The Entity with the GUID or null.
     * @ignore
     */ getEntityFromIndex(guid) {
        return this._entityIndex[guid];
    }
    /**
     * @param {Scene} scene - The scene.
     * @private
     */ _registerSceneImmediate(scene) {
        this.on('postrender', scene.immediate.onPostRender, scene.immediate);
    }
}
/**
 * Create tick function to be wrapped in closure.
 *
 * @param {AppBase} _app - The application.
 * @returns {MakeTickCallback} The tick function.
 * @private
 */ const makeTick = function(_app) {
    const application = _app;
    /**
     * @param {number} [timestamp] - The timestamp supplied by requestAnimationFrame.
     * @param {XRFrame} [xrFrame] - XRFrame from requestAnimationFrame callback.
     */ return function(timestamp, xrFrame) {
        if (!application.graphicsDevice) {
            return;
        }
        // cancel any hanging rAF to avoid multiple rAF callbacks per frame
        if (application.frameRequestId) {
            application.xr?.session?.cancelAnimationFrame(application.frameRequestId);
            cancelAnimationFrame(application.frameRequestId);
            application.frameRequestId = null;
        }
        application._inFrameUpdate = true;
        setApplication(application);
        const currentTime = application._processTimestamp(timestamp) || now();
        const ms = currentTime - (application._time || currentTime);
        let dt = ms / 1000.0;
        dt = math.clamp(dt, 0, application.maxDeltaTime);
        dt *= application.timeScale;
        application._time = currentTime;
        // Submit a request to queue up a new animation frame immediately
        if (application.xr?.session) {
            application.frameRequestId = application.xr.session.requestAnimationFrame(application.tick);
        } else {
            application.frameRequestId = platform.browser || platform.worker ? requestAnimationFrame(application.tick) : null;
        }
        if (application.graphicsDevice.contextLost) {
            return;
        }
        application._fillFrameStatsBasic(currentTime, dt, ms);
        application._fillFrameStats();
        application.fire('frameupdate', ms);
        let skipUpdate = false;
        if (xrFrame) {
            skipUpdate = !application.xr?.update(xrFrame);
            application.graphicsDevice.defaultFramebuffer = xrFrame.session.renderState.baseLayer.framebuffer;
        } else {
            application.graphicsDevice.defaultFramebuffer = null;
        }
        if (!skipUpdate) {
            Debug.trace(TRACEID_RENDER_FRAME, `---- Frame ${application.frame}`);
            Debug.trace(TRACEID_RENDER_FRAME_TIME, `-- UpdateStart ${now().toFixed(2)}ms`);
            application.update(dt);
            application.fire('framerender');
            if (application.autoRender || application.renderNextFrame) {
                Debug.trace(TRACEID_RENDER_FRAME_TIME, `-- RenderStart ${now().toFixed(2)}ms`);
                application.render();
                application.renderNextFrame = false;
                Debug.trace(TRACEID_RENDER_FRAME_TIME, `-- RenderEnd ${now().toFixed(2)}ms`);
            }
            application.fire('frameend');
        }
        application._inFrameUpdate = false;
        if (application._destroyRequested) {
            application.destroy();
        }
    };
};

/**
 * @import { BatchManager } from '../scene/batching/batch-manager.js'
 * @import { ComponentSystem } from './components/system.js'
 * @import { ElementInput } from './input/element-input.js'
 * @import { GamePads } from '../platform/input/game-pads.js'
 * @import { GraphicsDevice } from '../platform/graphics/graphics-device.js'
 * @import { Keyboard } from '../platform/input/keyboard.js'
 * @import { Lightmapper } from './lightmapper/lightmapper.js'
 * @import { Mouse } from '../platform/input/mouse.js'
 * @import { ResourceHandler } from './handlers/handler.js'
 * @import { SoundManager } from '../platform/sound/manager.js'
 * @import { TouchDevice } from '../platform/input/touch-device.js'
 * @import { XrManager } from './xr/xr-manager.js'
 */ /**
 * AppOptions holds configuration settings utilized in the creation of an {@link AppBase} instance.
 * It allows functionality to be included or excluded from the AppBase instance.
 */ class AppOptions {
    constructor(){
        /**
     * The component systems the app requires.
     *
     * @type {typeof ComponentSystem[]}
     */ this.componentSystems = [];
        /**
     * The resource handlers the app requires.
     *
     * @type {typeof ResourceHandler[]}
     */ this.resourceHandlers = [];
    }
}

/**
 * @import { ComponentSystem } from './system.js'
 * @import { Entity } from '../entity.js'
 */ /**
 * Components are used to attach functionality on a {@link Entity}. Components can receive update
 * events each frame, and expose properties to the PlayCanvas Editor.
 *
 * @hideconstructor
 */ class Component extends EventHandler {
    static{
        /**
     * Component order. When an entity with multiple components gets enabled, this order specifies
     * in which order the components get enabled. The lowest number gets enabled first.
     *
     * @type {number} - Component order number.
     * @private
     */ this.order = 0;
    }
    /**
     * Base constructor for a Component.
     *
     * @param {ComponentSystem} system - The ComponentSystem used to create this component.
     * @param {Entity} entity - The Entity that this Component is attached to.
     */ constructor(system, entity){
        super();
        this.system = system;
        this.entity = entity;
        if (this.system.schema && !this._accessorsBuilt) {
            this.buildAccessors(this.system.schema);
        }
        this.on('set', function(name, oldValue, newValue) {
            this.fire(`set_${name}`, name, oldValue, newValue);
        });
        this.on('set_enabled', this.onSetEnabled, this);
    }
    /** @ignore */ static _buildAccessors(obj, schema) {
        // Create getter/setter pairs for each property defined in the schema
        schema.forEach((descriptor)=>{
            // If the property descriptor is an object, it should have a `name`
            // member. If not, it should just be the plain property name.
            const name = typeof descriptor === 'object' ? descriptor.name : descriptor;
            Object.defineProperty(obj, name, {
                get: function() {
                    return this.data[name];
                },
                set: function(value) {
                    const data = this.data;
                    const oldValue = data[name];
                    data[name] = value;
                    this.fire('set', name, oldValue, value);
                },
                configurable: true
            });
        });
        obj._accessorsBuilt = true;
    }
    /** @ignore */ buildAccessors(schema) {
        Component._buildAccessors(this, schema);
    }
    /** @ignore */ onSetEnabled(name, oldValue, newValue) {
        if (oldValue !== newValue) {
            if (this.entity.enabled) {
                if (newValue) {
                    this.onEnable();
                } else {
                    this.onDisable();
                }
            }
        }
    }
    /** @ignore */ onEnable() {}
    /** @ignore */ onDisable() {}
    /** @ignore */ onPostStateChange() {}
    /**
     * Access the component data directly. Usually you should access the data properties via the
     * individual properties as modifying this data directly will not fire 'set' events.
     *
     * @type {*}
     * @ignore
     */ get data() {
        const record = this.system.store[this.entity.getGuid()];
        return record ? record.data : null;
    }
    /**
     * Sets the enabled state of the component.
     *
     * @type {boolean}
     */ set enabled(arg) {}
    /**
     * Gets the enabled state of the component.
     *
     * @type {boolean}
     */ get enabled() {
        return true;
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 * @import { Component } from './component.js'
 * @import { Entity } from '../entity.js'
 */ /**
 * Component Systems contain the logic and functionality to update all Components of a particular
 * type.
 */ class ComponentSystem extends EventHandler {
    /**
     * Create a new ComponentSystem instance.
     *
     * @param {AppBase} app - The application managing this system.
     */ constructor(app){
        super();
        this.app = app;
        // The store where all ComponentData objects are kept
        this.store = {};
        this.schema = [];
    }
    /**
     * Create new {@link Component} and component data instances and attach them to the entity.
     *
     * @param {Entity} entity - The Entity to attach this component to.
     * @param {object} [data] - The source data with which to create the component.
     * @returns {Component} Returns a Component of type defined by the component system.
     * @example
     * const entity = new pc.Entity(app);
     * app.systems.model.addComponent(entity, { type: 'box' });
     * // entity.model is now set to a pc.ModelComponent
     * @ignore
     */ addComponent(entity, data = {}) {
        const component = new this.ComponentType(this, entity);
        const componentData = new this.DataType();
        this.store[entity.getGuid()] = {
            entity: entity,
            data: componentData
        };
        entity[this.id] = component;
        entity.c[this.id] = component;
        this.initializeComponentData(component, data, []);
        this.fire('add', entity, component);
        return component;
    }
    /**
     * Remove the {@link Component} from the entity and delete the associated component data.
     *
     * @param {Entity} entity - The entity to remove the component from.
     * @example
     * app.systems.model.removeComponent(entity);
     * // entity.model === undefined
     * @ignore
     */ removeComponent(entity) {
        const id = this.id;
        const record = this.store[entity.getGuid()];
        const component = entity.c[id];
        component.fire('beforeremove');
        this.fire('beforeremove', entity, component);
        delete this.store[entity.getGuid()];
        entity[id] = undefined;
        delete entity.c[id];
        this.fire('remove', entity, record.data);
    }
    /**
     * Create a clone of component. This creates a copy of all component data variables.
     *
     * @param {Entity} entity - The entity to clone the component from.
     * @param {Entity} clone - The entity to clone the component into.
     * @returns {Component} The newly cloned component.
     * @ignore
     */ cloneComponent(entity, clone) {
        // default clone is just to add a new component with existing data
        const src = this.store[entity.getGuid()];
        return this.addComponent(clone, src.data);
    }
    /**
     * Called during {@link ComponentSystem#addComponent} to initialize the component data in the
     * store. This can be overridden by derived Component Systems and either called by the derived
     * System or replaced entirely.
     *
     * @param {Component} component - The component being initialized.
     * @param {object} data - The data block used to initialize the component.
     * @param {Array<string | {name: string, type: string}>} properties - The array of property
     * descriptors for the component. A descriptor can be either a plain property name, or an
     * object specifying the name and type.
     * @ignore
     */ initializeComponentData(component, data = {}, properties) {
        // initialize
        for(let i = 0, len = properties.length; i < len; i++){
            const descriptor = properties[i];
            let name, type;
            // If the descriptor is an object, it will have `name` and `type` members
            if (typeof descriptor === 'object') {
                name = descriptor.name;
                type = descriptor.type;
            } else {
                // Otherwise, the descriptor is just the property name
                name = descriptor;
                type = undefined;
            }
            let value = data[name];
            if (value !== undefined) {
                // If we know the intended type of the value, convert the raw data
                // into an instance of the specified type.
                if (type !== undefined) {
                    value = convertValue(value, type);
                }
                component[name] = value;
            } else {
                component[name] = component.data[name];
            }
        }
        // after component is initialized call onEnable
        if (component.enabled && component.entity.enabled) {
            component.onEnable();
        }
    }
    /**
     * Searches the component schema for properties that match the specified type.
     *
     * @param {string} type - The type to search for.
     * @returns {string[]|object[]} An array of property descriptors matching the specified type.
     * @ignore
     */ getPropertiesOfType(type) {
        const matchingProperties = [];
        const schema = this.schema || [];
        schema.forEach((descriptor)=>{
            if (descriptor && typeof descriptor === 'object' && descriptor.type === type) {
                matchingProperties.push(descriptor);
            }
        });
        return matchingProperties;
    }
    destroy() {
        this.off();
    }
}
function convertValue(value, type) {
    if (!value) {
        return value;
    }
    switch(type){
        case 'rgb':
            if (value instanceof Color) {
                return value.clone();
            }
            return new Color(value[0], value[1], value[2]);
        case 'rgba':
            if (value instanceof Color) {
                return value.clone();
            }
            return new Color(value[0], value[1], value[2], value[3]);
        case 'vec2':
            if (value instanceof Vec2) {
                return value.clone();
            }
            return new Vec2(value[0], value[1]);
        case 'vec3':
            if (value instanceof Vec3) {
                return value.clone();
            }
            return new Vec3(value[0], value[1], value[2]);
        case 'vec4':
            if (value instanceof Vec4) {
                return value.clone();
            }
            return new Vec4(value[0], value[1], value[2], value[3]);
        case 'boolean':
        case 'number':
        case 'string':
            return value;
        case 'entity':
            return value; // Entity fields should just be a string guid
        default:
            throw new Error(`Could not convert unhandled type: ${type}`);
    }
}

/**
 * A stepped interpolation scheme.
 *
 * @category Animation
 */ const INTERPOLATION_STEP = 0;
/**
 * A linear interpolation scheme.
 *
 * @category Animation
 */ const INTERPOLATION_LINEAR = 1;
/**
 * A cubic spline interpolation scheme.
 *
 * @category Animation
 */ const INTERPOLATION_CUBIC = 2;

/**
 * Internal cache data for the evaluation of a single curve timeline.
 *
 * @ignore
 */ class AnimCache {
    /**
     * Create a new AnimCache instance.
     */ constructor(){
        // these members are calculated per-segment
        this._left = Infinity; // time of left knot
        this._right = -Infinity; // time of right knot
        this._len = 0; // distance between current knots
        this._recip = 0; // reciprocal len
        this._p0 = 0; // index of the left knot
        this._p1 = 0; // index of the right knot
        // these members are calculated per-time evaluation
        this._t = 0; // normalized time
        this._hermite = {
            valid: false,
            p0: 0,
            m0: 0,
            p1: 0,
            m1: 0
        };
    }
    update(time, input) {
        if (time < this._left || time >= this._right) {
            // recalculate knots
            const len = input.length;
            if (!len) {
                // curve is empty
                this._left = -Infinity;
                this._right = Infinity;
                this._len = 0;
                this._recip = 0;
                this._p0 = this._p1 = 0;
            } else {
                if (time < input[0]) {
                    // time falls before the first key
                    this._left = -Infinity;
                    this._right = input[0];
                    this._len = 0;
                    this._recip = 0;
                    this._p0 = this._p1 = 0;
                } else if (time >= input[len - 1]) {
                    // time falls after the last key
                    this._left = input[len - 1];
                    this._right = Infinity;
                    this._len = 0;
                    this._recip = 0;
                    this._p0 = this._p1 = len - 1;
                } else {
                    // time falls within the bounds of the curve
                    const index = this._findKey(time, input);
                    this._left = input[index];
                    this._right = input[index + 1];
                    this._len = this._right - this._left;
                    const diff = 1.0 / this._len;
                    this._recip = isFinite(diff) ? diff : 0;
                    this._p0 = index;
                    this._p1 = index + 1;
                }
            }
        }
        // calculate normalized time
        this._t = this._recip === 0 ? 0 : (time - this._left) * this._recip;
        this._hermite.valid = false;
    }
    _findKey(time, input) {
        // TODO: start the search around the currently selected knots
        let index = 0;
        while(time >= input[index + 1]){
            index++;
        }
        return index;
    }
    // evaluate the output anim data at the current time
    eval(result, interpolation, output) {
        const data = output._data;
        const comp = output._components;
        const idx0 = this._p0 * comp;
        if (interpolation === INTERPOLATION_STEP) {
            for(let i = 0; i < comp; ++i){
                result[i] = data[idx0 + i];
            }
        } else {
            const t = this._t;
            const idx1 = this._p1 * comp;
            switch(interpolation){
                case INTERPOLATION_LINEAR:
                    for(let i = 0; i < comp; ++i){
                        result[i] = math.lerp(data[idx0 + i], data[idx1 + i], t);
                    }
                    break;
                case INTERPOLATION_CUBIC:
                    {
                        const hermite = this._hermite;
                        if (!hermite.valid) {
                            // cache hermite weights
                            const t2 = t * t;
                            const twot = t + t;
                            const omt = 1 - t;
                            const omt2 = omt * omt;
                            hermite.valid = true;
                            hermite.p0 = (1 + twot) * omt2;
                            hermite.m0 = t * omt2;
                            hermite.p1 = t2 * (3 - twot);
                            hermite.m1 = t2 * (t - 1);
                        }
                        const p0 = (this._p0 * 3 + 1) * comp; // point at k
                        const m0 = (this._p0 * 3 + 2) * comp; // out-tangent at k
                        const p1 = (this._p1 * 3 + 1) * comp; // point at k + 1
                        const m1 = (this._p1 * 3 + 0) * comp; // in-tangent at k + 1
                        for(let i = 0; i < comp; ++i){
                            result[i] = hermite.p0 * data[p0 + i] + hermite.m0 * data[m0 + i] * this._len + hermite.p1 * data[p1 + i] + hermite.m1 * data[m1 + i] * this._len;
                        }
                        break;
                    }
            }
        }
    }
}

/**
 * @import { AnimTrack } from './anim-track.js'
 */ /**
 * AnimSnapshot stores the state of an animation track at a particular time.
 *
 * @ignore
 */ class AnimSnapshot {
    /**
     * Create a new animation snapshot.
     *
     * @param {AnimTrack} animTrack - The source track.
     */ constructor(animTrack){
        this._name = `${animTrack.name}Snapshot`;
        this._time = -1;
        // per-curve input cache
        this._cache = [];
        // per-curve evaluation results
        this._results = [];
        // pre-allocate input caches
        for(let i = 0; i < animTrack._inputs.length; ++i){
            this._cache[i] = new AnimCache();
        }
        // pre-allocate storage for evaluation results
        const curves = animTrack._curves;
        const outputs = animTrack._outputs;
        for(let i = 0; i < curves.length; ++i){
            const curve = curves[i];
            const output = outputs[curve._output];
            const storage = [];
            for(let j = 0; j < output._components; ++j){
                storage[j] = 0;
            }
            this._results[i] = storage;
        }
    }
}

/**
 * @import { AnimTrack } from './anim-track.js'
 * @import { EventHandler } from '../../../core/event-handler.js'
 */ // TODO: add configurable looping start/end times?
/**
 * AnimClip wraps the running state of an animation track. It contains and update the animation
 * 'cursor' and performs looping logic.
 *
 * @ignore
 */ class AnimClip {
    static{
        this.eventFrame = {
            start: 0,
            end: 0,
            residual: 0
        };
    }
    /**
     * Create a new animation clip.
     *
     * @param {AnimTrack} track - The animation data.
     * @param {number} time - The initial time of the clip.
     * @param {number} speed - Speed of the animation playback.
     * @param {boolean} playing - true if the clip is playing and false otherwise.
     * @param {boolean} loop - Whether the clip should loop.
     * @param {EventHandler} [eventHandler] - The handler to call when an event is fired by the clip.
     */ constructor(track, time, speed, playing, loop, eventHandler){
        this._name = track.name; // default to track name
        this._track = track;
        this._snapshot = new AnimSnapshot(track);
        this._playing = playing;
        this._time = time; // play cursor
        this._speed = speed; // playback speed, may be negative
        this._loop = loop; // whether to loop
        this._blendWeight = 1.0; // blend weight 0..1
        this._blendOrder = 0.0; // blend order relative to other clips
        this._eventHandler = eventHandler;
        this.alignCursorToCurrentTime();
    }
    set name(name) {
        this._name = name;
    }
    get name() {
        return this._name;
    }
    set track(track) {
        this._track = track;
        this._snapshot = new AnimSnapshot(track);
    }
    get track() {
        return this._track;
    }
    get snapshot() {
        return this._snapshot;
    }
    set time(time) {
        this._time = time;
        this.alignCursorToCurrentTime();
    }
    get time() {
        return this._time;
    }
    set speed(speed) {
        const signChanged = Math.sign(speed) !== Math.sign(this._speed);
        this._speed = speed;
        if (signChanged) {
            this.alignCursorToCurrentTime();
        }
    }
    get speed() {
        return this._speed;
    }
    set loop(loop) {
        this._loop = loop;
    }
    get loop() {
        return this._loop;
    }
    set blendWeight(blendWeight) {
        this._blendWeight = blendWeight;
    }
    get blendWeight() {
        return this._blendWeight;
    }
    set blendOrder(blendOrder) {
        this._blendOrder = blendOrder;
    }
    get blendOrder() {
        return this._blendOrder;
    }
    set eventCursor(value) {
        this._eventCursor = value;
    }
    get eventCursor() {
        return this._eventCursor;
    }
    get eventCursorEnd() {
        return this.isReverse ? 0 : this._track.events.length - 1;
    }
    get nextEvent() {
        return this._track.events[this._eventCursor];
    }
    get isReverse() {
        return this._speed < 0;
    }
    nextEventAheadOfTime(time) {
        if (!this.nextEvent) return false;
        return this.isReverse ? this.nextEvent.time <= time : this.nextEvent.time >= time;
    }
    nextEventBehindTime(time) {
        if (!this.nextEvent) return false;
        if (time === this.track.duration) {
            return this.isReverse ? this.nextEvent.time >= time : this.nextEvent.time <= time;
        }
        return this.isReverse ? this.nextEvent.time > time : this.nextEvent.time < time;
    }
    resetEventCursor() {
        this._eventCursor = this.isReverse ? this._track.events.length - 1 : 0;
    }
    moveEventCursor() {
        this._eventCursor += this.isReverse ? -1 : 1;
        if (this._eventCursor >= this.track.events.length) {
            this._eventCursor = 0;
        } else if (this._eventCursor < 0) {
            this._eventCursor = this.track.events.length - 1;
        }
    }
    clipFrameTime(frameEndTime) {
        const eventFrame = AnimClip.eventFrame;
        eventFrame.start = 0;
        eventFrame.end = frameEndTime;
        eventFrame.residual = 0;
        // if this frame overlaps with the end of the track, we should clip off the end of the frame time then check that clipped time later
        if (this.isReverse) {
            if (frameEndTime < 0) {
                eventFrame.start = this.track.duration;
                eventFrame.end = 0;
                eventFrame.residual = frameEndTime + this.track.duration;
            }
        } else {
            if (frameEndTime > this.track.duration) {
                eventFrame.start = 0;
                eventFrame.end = this.track.duration;
                eventFrame.residual = frameEndTime - this.track.duration;
            }
        }
    }
    alignCursorToCurrentTime() {
        this.resetEventCursor();
        while(this.nextEventBehindTime(this._time) && this._eventCursor !== this.eventCursorEnd){
            this.moveEventCursor();
        }
    }
    fireNextEvent() {
        this._eventHandler.fire(this.nextEvent.name, {
            track: this.track,
            ...this.nextEvent
        });
        this.moveEventCursor();
    }
    fireNextEventInFrame(frameStartTime, frameEndTime) {
        if (this.nextEventAheadOfTime(frameStartTime) && this.nextEventBehindTime(frameEndTime)) {
            this.fireNextEvent();
            return true;
        }
        return false;
    }
    activeEventsForFrame(frameStartTime, frameEndTime) {
        // get frame start and end times clipped to the track duration with the residual duration stored
        const eventFrame = AnimClip.eventFrame;
        this.clipFrameTime(frameEndTime);
        // fire all events that should fire during this clipped frame
        const initialCursor = this.eventCursor;
        while(this.fireNextEventInFrame(frameStartTime, eventFrame.end)){
            if (initialCursor === this.eventCursor) {
                break;
            }
        }
        // recurse the process until the residual duration is 0
        if (this.loop && Math.abs(eventFrame.residual) > 0) {
            this.activeEventsForFrame(eventFrame.start, eventFrame.residual);
        }
    }
    progressForTime(time) {
        return time * this._speed / this._track.duration;
    }
    _update(deltaTime) {
        if (this._playing) {
            let time = this._time;
            const duration = this._track.duration;
            const speed = this._speed;
            const loop = this._loop;
            // check for events that should fire during this frame
            if (this._track.events.length > 0 && duration > 0) {
                this.activeEventsForFrame(time, time + speed * deltaTime);
            }
            // update time
            time += speed * deltaTime;
            // perform looping
            if (speed >= 0) {
                // playing forwards
                if (time > duration) {
                    if (loop) {
                        time = time % duration || 0; // if duration is 0, % is NaN
                    } else {
                        time = this._track.duration;
                        this.pause();
                    }
                }
            } else {
                // playing backwards
                if (time < 0) {
                    if (loop) {
                        time = duration + (time % duration || 0);
                    } else {
                        time = 0;
                        this.pause();
                    }
                }
            }
            this._time = time;
        }
        // update snapshot if time has changed
        if (this._time !== this._snapshot._time) {
            this._track.eval(this._time, this._snapshot);
        }
    }
    play() {
        this._playing = true;
        this._time = 0;
    }
    stop() {
        this._playing = false;
        this._time = 0;
    }
    pause() {
        this._playing = false;
    }
    resume() {
        this._playing = true;
    }
    reset() {
        this._time = 0;
    }
}

/**
 * Used to set the anim state graph transition interruption source to no state.
 *
 * @category Animation
 */ const ANIM_INTERRUPTION_NONE = 'NONE';
/**
 * Used to set the anim state graph transition interruption source as the previous state only.
 *
 * @category Animation
 */ const ANIM_INTERRUPTION_PREV = 'PREV_STATE';
/**
 * Used to set the anim state graph transition interruption source as the next state only.
 *
 * @category Animation
 */ const ANIM_INTERRUPTION_NEXT = 'NEXT_STATE';
/**
 * Used to set the anim state graph transition interruption sources as the previous state followed
 * by the next state.
 *
 * @category Animation
 */ const ANIM_INTERRUPTION_PREV_NEXT = 'PREV_STATE_NEXT_STATE';
/**
 * Used to set the anim state graph transition interruption sources as the next state followed by
 * the previous state.
 *
 * @category Animation
 */ const ANIM_INTERRUPTION_NEXT_PREV = 'NEXT_STATE_PREV_STATE';
/**
 * Used to set an anim state graph transition condition predicate as '>'.
 *
 * @category Animation
 */ const ANIM_GREATER_THAN = 'GREATER_THAN';
/**
 * Used to set an anim state graph transition condition predicate as '<'.
 *
 * @category Animation
 */ const ANIM_LESS_THAN = 'LESS_THAN';
/**
 * Used to set an anim state graph transition condition predicate as '>='.
 *
 * @category Animation
 */ const ANIM_GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO';
/**
 * Used to set an anim state graph transition condition predicate as '<='.
 *
 * @category Animation
 */ const ANIM_LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO';
/**
 * Used to set an anim state graph transition condition predicate as '==='.
 *
 * @category Animation
 */ const ANIM_EQUAL_TO = 'EQUAL_TO';
/**
 * Used to set an anim state graph transition condition predicate as '!=='.
 *
 * @category Animation
 */ const ANIM_NOT_EQUAL_TO = 'NOT_EQUAL_TO';
/**
 * Used to set an anim state graph parameter as type integer.
 *
 * @category Animation
 */ const ANIM_PARAMETER_INTEGER = 'INTEGER';
/**
 * Used to set an anim state graph parameter as type float.
 *
 * @category Animation
 */ const ANIM_PARAMETER_FLOAT = 'FLOAT';
/**
 * Used to set an anim state graph parameter as type boolean.
 *
 * @category Animation
 */ const ANIM_PARAMETER_BOOLEAN = 'BOOLEAN';
/**
 * Used to set an anim state graph parameter as type trigger.
 *
 * @category Animation
 */ const ANIM_PARAMETER_TRIGGER = 'TRIGGER';
/**
 * @type {string}
 * @category Animation
 */ const ANIM_BLEND_1D = '1D';
/**
 * @type {string}
 * @category Animation
 */ const ANIM_BLEND_2D_DIRECTIONAL = '2D_DIRECTIONAL';
/**
 * @type {string}
 * @category Animation
 */ const ANIM_BLEND_2D_CARTESIAN = '2D_CARTESIAN';
/**
 * @type {string}
 * @category Animation
 */ const ANIM_BLEND_DIRECT = 'DIRECT';
/**
 * The starting state in an anim state graph layer.
 *
 * @category Animation
 */ const ANIM_STATE_START = 'START';
/**
 * The ending state in an anim state graph layer.
 *
 * @category Animation
 */ const ANIM_STATE_END = 'END';
/**
 * Used to indicate any state in an anim state graph layer.
 *
 * @category Animation
 */ const ANIM_STATE_ANY = 'ANY';
const ANIM_CONTROL_STATES = [
    ANIM_STATE_START,
    ANIM_STATE_END,
    ANIM_STATE_ANY
];
/**
 * Used to indicate that a layers animations should overwrite all previous layers.
 *
 * @category Animation
 */ const ANIM_LAYER_OVERWRITE = 'OVERWRITE';
/**
 * Used to indicate that a layers animations should blend additively with previous layers.
 *
 * @category Animation
 */ const ANIM_LAYER_ADDITIVE = 'ADDITIVE';

class AnimBlend {
    static dot(a, b) {
        const len = a.length;
        let result = 0;
        for(let i = 0; i < len; ++i){
            result += a[i] * b[i];
        }
        return result;
    }
    static normalize(a) {
        let l = AnimBlend.dot(a, a);
        if (l > 0) {
            l = 1.0 / Math.sqrt(l);
            const len = a.length;
            for(let i = 0; i < len; ++i){
                a[i] *= l;
            }
        }
    }
    static set(a, b, type) {
        const len = a.length;
        if (type === 'quaternion') {
            let l = AnimBlend.dot(b, b);
            if (l > 0) {
                l = 1.0 / Math.sqrt(l);
            }
            for(let i = 0; i < len; ++i){
                a[i] = b[i] * l;
            }
        } else {
            for(let i = 0; i < len; ++i){
                a[i] = b[i];
            }
        }
    }
    static blendVec(a, b, t, additive) {
        const it = additive ? 1.0 : 1.0 - t;
        const len = a.length;
        for(let i = 0; i < len; ++i){
            a[i] = a[i] * it + b[i] * t;
        }
    }
    static blendQuat(a, b, t, additive) {
        const len = a.length;
        const it = additive ? 1.0 : 1.0 - t;
        // negate b if a and b don't lie in the same winding (due to
        // double cover). if we don't do this then often rotations from
        // one orientation to another go the long way around.
        if (AnimBlend.dot(a, b) < 0) {
            t = -t;
        }
        for(let i = 0; i < len; ++i){
            a[i] = a[i] * it + b[i] * t;
        }
        if (!additive) {
            AnimBlend.normalize(a);
        }
    }
    static blend(a, b, t, type, additive) {
        if (type === 'quaternion') {
            AnimBlend.blendQuat(a, b, t, additive);
        } else {
            AnimBlend.blendVec(a, b, t, additive);
        }
    }
    static stableSort(a, lessFunc) {
        const len = a.length;
        for(let i = 0; i < len - 1; ++i){
            for(let j = i + 1; j < len; ++j){
                if (lessFunc(a[j], a[i])) {
                    const tmp = a[i];
                    a[i] = a[j];
                    a[j] = tmp;
                }
            }
        }
    }
}

/**
 * @import { AnimComponent } from '../../components/anim/component.js'
 */ /**
 * Used to store and update the value of an animation target. This combines the values of multiple
 * layer targets into a single value.
 */ class AnimTargetValue {
    static{
        this.TYPE_QUAT = 'quaternion';
    }
    static{
        this.TYPE_VEC3 = 'vector3';
    }
    static{
        this.q1 = new Quat();
    }
    static{
        this.q2 = new Quat();
    }
    static{
        this.q3 = new Quat();
    }
    static{
        this.quatArr = [
            0,
            0,
            0,
            1
        ];
    }
    static{
        this.vecArr = [
            0,
            0,
            0
        ];
    }
    static{
        this.IDENTITY_QUAT_ARR = [
            0,
            0,
            0,
            1
        ];
    }
    /**
     * Create a new AnimTargetValue instance.
     *
     * @param {AnimComponent} component - The anim component this target value is associated with.
     * @param {string} type - The type of value stored, either quat or vec3.
     */ constructor(component, type){
        this._component = component;
        this.mask = new Int8Array(component.layers.length);
        this.weights = new Float32Array(component.layers.length);
        this.totalWeight = 0;
        this.counter = 0;
        this.layerCounter = 0;
        this.valueType = type;
        this.dirty = true;
        this.value = type === AnimTargetValue.TYPE_QUAT ? [
            0,
            0,
            0,
            1
        ] : [
            0,
            0,
            0
        ];
        this.baseValue = null;
        this.setter = null;
    }
    get _normalizeWeights() {
        return this._component.normalizeWeights;
    }
    getWeight(index) {
        if (this.dirty) this.updateWeights();
        if (this._normalizeWeights && this.totalWeight === 0 || !this.mask[index]) {
            return 0;
        } else if (this._normalizeWeights) {
            return this.weights[index] / this.totalWeight;
        }
        return math.clamp(this.weights[index], 0, 1);
    }
    _layerBlendType(index) {
        return this._component.layers[index].blendType;
    }
    setMask(index, value) {
        this.mask[index] = value;
        if (this._normalizeWeights) {
            if (this._component.layers[index].blendType === ANIM_LAYER_OVERWRITE) {
                this.mask = this.mask.fill(0, 0, index);
            }
            this.dirty = true;
        }
    }
    updateWeights() {
        this.totalWeight = 0;
        for(let i = 0; i < this.weights.length; i++){
            this.weights[i] = this._component.layers[i].weight;
            this.totalWeight += this.mask[i] * this.weights[i];
        }
        this.dirty = false;
    }
    updateValue(index, value) {
        // always reset the value of the target when the counter is 0
        if (this.counter === 0) {
            AnimBlend.set(this.value, AnimTargetValue.IDENTITY_QUAT_ARR, this.valueType);
            if (!this._normalizeWeights) {
                AnimBlend.blend(this.value, this.baseValue, 1, this.valueType);
            }
        }
        if (!this.mask[index] || this.getWeight(index) === 0) return;
        if (this._layerBlendType(index) === ANIM_LAYER_ADDITIVE && !this._normalizeWeights) {
            if (this.valueType === AnimTargetValue.TYPE_QUAT) {
                // current value
                const v = AnimTargetValue.q1.set(this.value[0], this.value[1], this.value[2], this.value[3]);
                // additive value
                const aV1 = AnimTargetValue.q2.set(this.baseValue[0], this.baseValue[1], this.baseValue[2], this.baseValue[3]);
                const aV2 = AnimTargetValue.q3.set(value[0], value[1], value[2], value[3]);
                const aV = aV1.invert().mul(aV2);
                // scale additive value by it's weight
                aV.slerp(Quat.IDENTITY, aV, this.getWeight(index));
                // add the additive value onto the current value then set it to the targets value
                v.mul(aV);
                AnimTargetValue.quatArr[0] = v.x;
                AnimTargetValue.quatArr[1] = v.y;
                AnimTargetValue.quatArr[2] = v.z;
                AnimTargetValue.quatArr[3] = v.w;
                AnimBlend.set(this.value, AnimTargetValue.quatArr, this.valueType);
            } else {
                AnimTargetValue.vecArr[0] = value[0] - this.baseValue[0];
                AnimTargetValue.vecArr[1] = value[1] - this.baseValue[1];
                AnimTargetValue.vecArr[2] = value[2] - this.baseValue[2];
                AnimBlend.blend(this.value, AnimTargetValue.vecArr, this.getWeight(index), this.valueType, true);
            }
        } else {
            AnimBlend.blend(this.value, value, this.getWeight(index), this.valueType);
        }
        if (this.setter) this.setter(this.value);
    }
    unbind() {
        if (this.setter) {
            this.setter(this.baseValue);
        }
    }
}

/**
 * @import { AnimBinder } from '../binder/anim-binder.js'
 * @import { AnimClip } from './anim-clip.js'
 */ /**
 * AnimEvaluator blends multiple sets of animation clips together.
 *
 * @ignore
 */ class AnimEvaluator {
    /**
     * Create a new animation evaluator.
     *
     * @param {AnimBinder} binder - Interface that resolves curve paths to instances of
     * {@link AnimTarget}.
     */ constructor(binder){
        this._binder = binder;
        this._clips = [];
        this._inputs = [];
        this._outputs = [];
        this._targets = {};
    }
    /**
     * The list of animation clips.
     *
     * @type {AnimClip[]}
     */ get clips() {
        return this._clips;
    }
    /**
     * Add a clip to the evaluator.
     *
     * @param {AnimClip} clip - The clip to add to the evaluator.
     */ addClip(clip) {
        const targets = this._targets;
        const binder = this._binder;
        // store list of input/output arrays
        const curves = clip.track.curves;
        const snapshot = clip.snapshot;
        const inputs = [];
        const outputs = [];
        for(let i = 0; i < curves.length; ++i){
            const curve = curves[i];
            const paths = curve.paths;
            for(let j = 0; j < paths.length; ++j){
                const path = paths[j];
                const resolved = binder.resolve(path);
                let target = targets[resolved && resolved.targetPath || null];
                // create new target if it doesn't exist yet
                if (!target && resolved) {
                    target = {
                        target: resolved,
                        value: [],
                        curves: 0,
                        blendCounter: 0 // per-frame number of blends (used to identify first blend)
                    };
                    for(let k = 0; k < target.target.components; ++k){
                        target.value.push(0);
                    }
                    targets[resolved.targetPath] = target;
                    if (binder.animComponent) {
                        if (!binder.animComponent.targets[resolved.targetPath]) {
                            let type;
                            if (resolved.targetPath.substring(resolved.targetPath.length - 13) === 'localRotation') {
                                type = AnimTargetValue.TYPE_QUAT;
                            } else {
                                type = AnimTargetValue.TYPE_VEC3;
                            }
                            binder.animComponent.targets[resolved.targetPath] = new AnimTargetValue(binder.animComponent, type);
                        }
                        binder.animComponent.targets[resolved.targetPath].layerCounter++;
                        binder.animComponent.targets[resolved.targetPath].setMask(binder.layerIndex, 1);
                    }
                }
                // binding may have failed
                // TODO: it may be worth storing quaternions and vector targets in separate
                // lists. this way the update code won't be forced to check target type before
                // setting/blending each target.
                if (target) {
                    target.curves++;
                    inputs.push(snapshot._results[i]);
                    outputs.push(target);
                }
            }
        }
        this._clips.push(clip);
        this._inputs.push(inputs);
        this._outputs.push(outputs);
    }
    /**
     * Remove a clip from the evaluator.
     *
     * @param {number} index - Index of the clip to remove.
     */ removeClip(index) {
        const targets = this._targets;
        const binder = this._binder;
        const clips = this._clips;
        const clip = clips[index];
        const curves = clip.track.curves;
        for(let i = 0; i < curves.length; ++i){
            const curve = curves[i];
            const paths = curve.paths;
            for(let j = 0; j < paths.length; ++j){
                const path = paths[j];
                const target = this._binder.resolve(path);
                if (target) {
                    target.curves--;
                    if (target.curves === 0) {
                        binder.unresolve(path);
                        delete targets[target.targetPath];
                        if (binder.animComponent) {
                            binder.animComponent.targets[target.targetPath].layerCounter--;
                        }
                    }
                }
            }
        }
        clips.splice(index, 1);
        this._inputs.splice(index, 1);
        this._outputs.splice(index, 1);
    }
    /**
     * Remove all clips from the evaluator.
     */ removeClips() {
        while(this._clips.length > 0){
            this.removeClip(0);
        }
    }
    updateClipTrack(name, animTrack) {
        this._clips.forEach((clip)=>{
            if (clip.name.includes(name)) {
                clip.track = animTrack;
            }
        });
        this.rebind();
    }
    /**
     * Returns the first clip which matches the given name, or null if no such clip was found.
     *
     * @param {string} name - Name of the clip to find.
     * @returns {AnimClip|null} - The clip with the given name or null if no such clip was found.
     */ findClip(name) {
        const clips = this._clips;
        for(let i = 0; i < clips.length; ++i){
            const clip = clips[i];
            if (clip.name === name) {
                return clip;
            }
        }
        return null;
    }
    rebind() {
        this._binder.rebind();
        this._targets = {};
        const clips = [
            ...this.clips
        ];
        this.removeClips();
        clips.forEach((clip)=>{
            this.addClip(clip);
        });
    }
    assignMask(mask) {
        return this._binder.assignMask(mask);
    }
    /**
     * Evaluator frame update function. All the attached {@link AnimClip}s are evaluated, blended
     * and the results set on the {@link AnimTarget}.
     *
     * @param {number} deltaTime - The amount of time that has passed since the last update, in
     * seconds.
     * @param {boolean} [outputAnimation] - Whether the evaluator should output the results of the
     * update to the bound animation targets.
     */ update(deltaTime, outputAnimation = true) {
        // copy clips
        const clips = this._clips;
        // stable sort order
        const order = clips.map((c, i)=>{
            return i;
        });
        AnimBlend.stableSort(order, (a, b)=>{
            return clips[a].blendOrder < clips[b].blendOrder;
        });
        for(let i = 0; i < order.length; ++i){
            const index = order[i];
            const clip = clips[index];
            const inputs = this._inputs[index];
            const outputs = this._outputs[index];
            const blendWeight = clip.blendWeight;
            // update clip
            if (blendWeight > 0.0) {
                clip._update(deltaTime);
            }
            if (!outputAnimation) break;
            let input;
            let output;
            let value;
            if (blendWeight >= 1.0) {
                for(let j = 0; j < inputs.length; ++j){
                    input = inputs[j];
                    output = outputs[j];
                    value = output.value;
                    AnimBlend.set(value, input, output.target.type);
                    output.blendCounter++;
                }
            } else if (blendWeight > 0.0) {
                for(let j = 0; j < inputs.length; ++j){
                    input = inputs[j];
                    output = outputs[j];
                    value = output.value;
                    if (output.blendCounter === 0) {
                        AnimBlend.set(value, input, output.target.type);
                    } else {
                        AnimBlend.blend(value, input, blendWeight, output.target.type);
                    }
                    output.blendCounter++;
                }
            }
        }
        // apply result to anim targets
        const targets = this._targets;
        const binder = this._binder;
        for(const path in targets){
            if (targets.hasOwnProperty(path)) {
                const target = targets[path];
                // if this evaluator is associated with an anim component then we should blend the result of this evaluator with all other anim layer's evaluators
                if (binder.animComponent && target.target.isTransform) {
                    const animTarget = binder.animComponent.targets[path];
                    if (animTarget.counter === animTarget.layerCounter) {
                        animTarget.counter = 0;
                    }
                    if (!animTarget.path) {
                        animTarget.path = path;
                        animTarget.baseValue = target.target.get();
                        animTarget.setter = target.target.set;
                    }
                    // Add this layer's value onto the target value
                    animTarget.updateValue(binder.layerIndex, target.value);
                    animTarget.counter++;
                } else {
                    target.target.set(target.value);
                }
                target.blendCounter = 0;
            }
        }
        // give the binder an opportunity to update itself
        // TODO: is this even necessary? binder could know when to update
        // itself without our help.
        this._binder.update(deltaTime);
    }
}

/**
 * AnimEvents stores a sorted array of animation events which should fire sequentially during the
 * playback of an {@link AnimTrack}.
 *
 * @category Animation
 */ class AnimEvents {
    /**
     * Create a new AnimEvents instance.
     *
     * @param {object[]} events - An array of animation events.
     * @example
     * const events = new pc.AnimEvents([
     *     {
     *         name: 'my_event',
     *         time: 1.3, // given in seconds
     *         // any additional properties added are optional and will be available in the EventHandler callback's event object
     *         myProperty: 'test',
     *         myOtherProperty: true
     *     }
     * ]);
     * animTrack.events = events;
     */ constructor(events){
        this._events = [
            ...events
        ];
        this._events.sort((a, b)=>a.time - b.time);
    }
    get events() {
        return this._events;
    }
}

/**
 * @import { AnimCurve } from './anim-curve.js'
 * @import { AnimData } from './anim-data.js'
 */ /**
 * An AnimTrack stores the curve data necessary to animate a set of target nodes. It can be linked
 * to the nodes it should animate using the {@link AnimComponent#assignAnimation} method.
 *
 * @category Animation
 */ class AnimTrack {
    static{
        /**
     * This AnimTrack can be used as a placeholder track when creating a state graph before having all associated animation data available.
     *
     * @type {AnimTrack}
     */ this.EMPTY = Object.freeze(new AnimTrack('empty', Number.MAX_VALUE, [], [], []));
    }
    /**
     * Create a new AnimTrack instance.
     *
     * @param {string} name - The track name.
     * @param {number} duration - The duration of the track in seconds.
     * @param {AnimData[]} inputs - List of curve key data.
     * @param {AnimData[]} outputs - List of curve value data.
     * @param {AnimCurve[]} curves - The list of curves.
     * @param {AnimEvents} animEvents - A sequence of animation events.
     * @ignore
     */ constructor(name, duration, inputs, outputs, curves, animEvents = new AnimEvents([])){
        this._name = name;
        this._duration = duration;
        this._inputs = inputs;
        this._outputs = outputs;
        this._curves = curves;
        this._animEvents = animEvents;
    }
    /**
     * Gets the name of the AnimTrack.
     *
     * @type {string}
     */ get name() {
        return this._name;
    }
    /**
     * Gets the duration of the AnimTrack.
     *
     * @type {number}
     */ get duration() {
        return this._duration;
    }
    /**
     * Gets the list of curve key data contained in the AnimTrack.
     *
     * @type {AnimData[]}
     */ get inputs() {
        return this._inputs;
    }
    /**
     * Gets the list of curve values contained in the AnimTrack.
     *
     * @type {AnimData[]}
     */ get outputs() {
        return this._outputs;
    }
    /**
     * Gets the list of curves contained in the AnimTrack.
     *
     * @type {AnimCurve[]}
     */ get curves() {
        return this._curves;
    }
    /**
     * Sets the animation events that will fire during the playback of this anim track.
     *
     * @type {AnimEvents}
     */ set events(animEvents) {
        this._animEvents = animEvents;
    }
    /**
     * Gets the animation events that will fire during the playback of this anim track.
     *
     * @type {AnimEvents}
     */ get events() {
        return this._animEvents.events;
    }
    // evaluate all track curves at the specified time and store results
    // in the provided snapshot.
    eval(time, snapshot) {
        snapshot._time = time;
        const inputs = this._inputs;
        const outputs = this._outputs;
        const curves = this._curves;
        const cache = snapshot._cache;
        const results = snapshot._results;
        // evaluate inputs
        for(let i = 0; i < inputs.length; ++i){
            cache[i].update(time, inputs[i]._data);
        }
        // evaluate outputs
        for(let i = 0; i < curves.length; ++i){
            const curve = curves[i];
            const output = outputs[curve._output];
            const result = results[i];
            cache[curve._input].eval(result, curve._interpolation, output);
        }
    }
}

/**
 * @import { AnimTarget } from '../evaluator/anim-target.js'
 */ /**
 * This interface is used by {@link AnimEvaluator} to resolve unique animation target path strings
 * into instances of {@link AnimTarget}.
 *
 * @ignore
 */ class AnimBinder {
    // join a list of path segments into a path string using the full stop character. If another character is supplied,
    // it will join using that character instead
    static joinPath(pathSegments, character) {
        character = character || '.';
        const escape = function(string) {
            return string.replace(/\\/g, '\\\\').replace(new RegExp(`\\${character}`, 'g'), `\\${character}`);
        };
        return pathSegments.map(escape).join(character);
    }
    // split a path string into its segments and resolve character escaping
    static splitPath(path, character) {
        character = character || '.';
        const result = [];
        let curr = '';
        let i = 0;
        while(i < path.length){
            let c = path[i++];
            if (c === '\\' && i < path.length) {
                c = path[i++];
                if (c === '\\' || c === character) {
                    curr += c;
                } else {
                    curr += `\\${c}`;
                }
            } else if (c === character) {
                result.push(curr);
                curr = '';
            } else {
                curr += c;
            }
        }
        if (curr.length > 0) {
            result.push(curr);
        }
        return result;
    }
    /**
     * Converts a locator array into its string version.
     *
     * @param {string|string[]} entityPath - The entity location in the scene defined as an array or
     * string path.
     * @param {string} component - The component of the entity the property is located under.
     * @param {string|string[]} propertyPath - The property location in the entity defined as an array
     * or string path.
     * @returns {string} The locator encoded as a string.
     * @example
     * // returns 'spotLight/light/color.r'
     * encode(['spotLight'], 'light', ['color', 'r']);
     */ static encode(entityPath, component, propertyPath) {
        return `${Array.isArray(entityPath) ? entityPath.join('/') : entityPath}/${component}/${Array.isArray(propertyPath) ? propertyPath.join('/') : propertyPath}`;
    }
    /**
     * Resolve the provided target path and return an instance of {@link AnimTarget} which will
     * handle setting the value, or return null if no such target exists.
     *
     * @param {string} path - The animation curve path to resolve.
     * @returns {AnimTarget|null} - Returns the target
     * instance on success and null otherwise.
     */ resolve(path) {
        return null;
    }
    /**
     * Called when the {@link AnimEvaluator} no longer has a curve driving the given key.
     *
     * @param {string} path - The animation curve path which is no longer driven.
     */ unresolve(path) {}
    /**
     * Called by {@link AnimEvaluator} once a frame after animation updates are done.
     *
     * @param {number} deltaTime - Amount of time that passed in the current update.
     */ update(deltaTime) {}
}

/**
 * Stores the information required by {@link AnimEvaluator} for updating a target value.
 *
 * @ignore
 */ class AnimTarget {
    /**
     * Create a new AnimTarget instance.
     *
     * @param {(value: number[]) => void} func - This function will be called when a new animation value is output
     * by the {@link AnimEvaluator}.
     * @param {'vector'|'quaternion'} type - The type of animation data this target expects.
     * @param {number} components - The number of components on this target (this should ideally
     * match the number of components found on all attached animation curves).
     * @param {string} targetPath - The path to the target value.
     */ constructor(func, type, components, targetPath){
        if (func.set) {
            this._set = func.set;
            this._get = func.get;
        } else {
            this._set = func;
        }
        this._type = type;
        this._components = components;
        this._targetPath = targetPath;
        this._isTransform = this._targetPath.substring(this._targetPath.length - 13) === 'localRotation' || this._targetPath.substring(this._targetPath.length - 13) === 'localPosition' || this._targetPath.substring(this._targetPath.length - 10) === 'localScale';
    }
    get set() {
        return this._set;
    }
    get get() {
        return this._get;
    }
    get type() {
        return this._type;
    }
    get components() {
        return this._components;
    }
    get targetPath() {
        return this._targetPath;
    }
    get isTransform() {
        return this._isTransform;
    }
}

/**
 * Implementation of {@link AnimBinder} for animating a skeleton in the graph-node hierarchy.
 *
 * @ignore
 */ class DefaultAnimBinder {
    constructor(graph){
        this._isPathInMask = (path, checkMaskValue)=>{
            const maskItem = this._mask[path];
            if (!maskItem) return false;
            else if (maskItem.children || checkMaskValue && maskItem.value !== false) return true;
            return false;
        };
        this.graph = graph;
        if (!graph) return;
        this._mask = null;
        const nodes = {};
        // cache node names so we can quickly resolve animation paths
        const flatten = function(node) {
            nodes[node.name] = node;
            for(let i = 0; i < node.children.length; ++i){
                flatten(node.children[i]);
            }
        };
        flatten(graph);
        this.nodes = nodes;
        this.targetCache = {};
        this.visitedFallbackGraphPaths = {};
        const findMeshInstances = function(node) {
            // walk up to the first parent node of entity type (skips internal nodes of Model)
            let object = node;
            while(object && !(object instanceof Entity)){
                object = object.parent;
            }
            // get meshInstances from either model or render component
            let meshInstances;
            if (object) {
                if (object.render) {
                    meshInstances = object.render.meshInstances;
                } else if (object.model) {
                    meshInstances = object.model.meshInstances;
                }
            }
            return meshInstances;
        };
        this.nodeCounts = {}; // map of node path -> count
        this.activeNodes = []; // list of active nodes
        this.handlers = {
            'localPosition': function(node) {
                const object = node.localPosition;
                const func = function(value) {
                    object.set(...value);
                };
                return DefaultAnimBinder.createAnimTarget(func, 'vector', 3, node, 'localPosition');
            },
            'localRotation': function(node) {
                const object = node.localRotation;
                const func = function(value) {
                    object.set(...value);
                };
                return DefaultAnimBinder.createAnimTarget(func, 'quaternion', 4, node, 'localRotation');
            },
            'localScale': function(node) {
                const object = node.localScale;
                const func = function(value) {
                    object.set(...value);
                };
                return DefaultAnimBinder.createAnimTarget(func, 'vector', 3, node, 'localScale');
            },
            'weight': function(node, weightName) {
                if (weightName.indexOf('name.') === 0) {
                    weightName = weightName.replace('name.', '');
                } else {
                    weightName = Number(weightName);
                }
                const meshInstances = findMeshInstances(node);
                let setters;
                if (meshInstances) {
                    for(let i = 0; i < meshInstances.length; ++i){
                        if (meshInstances[i].node.name === node.name && meshInstances[i].morphInstance) {
                            const morphInstance = meshInstances[i].morphInstance;
                            const func = (value)=>{
                                morphInstance.setWeight(weightName, value[0]);
                            };
                            if (!setters) setters = [];
                            setters.push(func);
                        }
                    }
                }
                if (setters) {
                    const callSetters = (value)=>{
                        for(let i = 0; i < setters.length; ++i){
                            setters[i](value);
                        }
                    };
                    return DefaultAnimBinder.createAnimTarget(callSetters, 'number', 1, node, `weight.${weightName}`);
                }
                return null;
            },
            'materialTexture': (node, textureName)=>{
                const meshInstances = findMeshInstances(node);
                if (meshInstances) {
                    let meshInstance;
                    for(let i = 0; i < meshInstances.length; ++i){
                        if (meshInstances[i].node.name === node.name) {
                            meshInstance = meshInstances[i];
                            break;
                        }
                    }
                    if (meshInstance) {
                        const func = (value)=>{
                            const textureAsset = this.animComponent.system.app.assets.get(value[0]);
                            if (textureAsset && textureAsset.resource && textureAsset.type === 'texture') {
                                meshInstance.material[textureName] = textureAsset.resource;
                                meshInstance.material.update();
                            }
                        };
                        return DefaultAnimBinder.createAnimTarget(func, 'vector', 1, node, 'materialTexture', 'material');
                    }
                }
                return null;
            }
        };
    }
    _isPathActive(path) {
        if (!this._mask) return true;
        const rootNodeNames = [
            path.entityPath[0],
            this.graph.name
        ];
        for(let j = 0; j < rootNodeNames.length; ++j){
            let currEntityPath = rootNodeNames[j];
            if (this._isPathInMask(currEntityPath, path.entityPath.length === 1)) return true;
            for(let i = 1; i < path.entityPath.length; i++){
                currEntityPath += `/${path.entityPath[i]}`;
                if (this._isPathInMask(currEntityPath, i === path.entityPath.length - 1)) return true;
            }
        }
        return false;
    }
    findNode(path) {
        if (!this._isPathActive(path)) {
            return null;
        }
        let node;
        if (this.graph) {
            node = this.graph.findByPath(path.entityPath);
            // if the path is not found under the given root node, try to find the path using the root node as the base of the path instead
            if (!node) {
                node = this.graph.findByPath(path.entityPath.slice(1));
            }
        }
        if (!node) {
            node = this.nodes[path.entityPath[path.entityPath.length - 1] || ''];
            const fallbackGraphPath = AnimBinder.encode(path.entityPath[path.entityPath.length - 1] || '', 'graph', path.propertyPath);
            if (this.visitedFallbackGraphPaths[fallbackGraphPath] === 1) {
                Debug.warnOnce(`Anim Binder: Multiple animation curves with the path ${fallbackGraphPath} are present in the ${this.graph.path} graph which may result in the incorrect binding of animations`);
            }
            if (!Number.isFinite(this.visitedFallbackGraphPaths[fallbackGraphPath])) {
                this.visitedFallbackGraphPaths[fallbackGraphPath] = 0;
            } else {
                this.visitedFallbackGraphPaths[fallbackGraphPath]++;
            }
        }
        return node;
    }
    static createAnimTarget(func, type, valueCount, node, propertyPath, componentType) {
        const targetPath = AnimBinder.encode(node.path, componentType ? componentType : 'entity', propertyPath);
        return new AnimTarget(func, type, valueCount, targetPath);
    }
    resolve(path) {
        const encodedPath = AnimBinder.encode(path.entityPath, path.component, path.propertyPath);
        let target = this.targetCache[encodedPath];
        if (target) return target;
        const node = this.findNode(path);
        if (!node) {
            return null;
        }
        const handler = this.handlers[path.propertyPath];
        if (!handler) {
            return null;
        }
        target = handler(node);
        if (!target) {
            return null;
        }
        this.targetCache[encodedPath] = target;
        if (!this.nodeCounts[node.path]) {
            this.activeNodes.push(node);
            this.nodeCounts[node.path] = 1;
        } else {
            this.nodeCounts[node.path]++;
        }
        return target;
    }
    unresolve(path) {
        if (path.component !== 'graph') {
            return;
        }
        const node = this.nodes[path.entityPath[path.entityPath.length - 1] || ''];
        this.nodeCounts[node.path]--;
        if (this.nodeCounts[node.path] === 0) {
            const activeNodes = this.activeNodes;
            const i = activeNodes.indexOf(node.node); // :(
            const len = activeNodes.length;
            if (i < len - 1) {
                activeNodes[i] = activeNodes[len - 1];
            }
            activeNodes.pop();
        }
    }
    // flag animating nodes as dirty
    update(deltaTime) {
        const activeNodes = this.activeNodes;
        for(let i = 0; i < activeNodes.length; ++i){
            activeNodes[i]._dirtifyLocal();
        }
    }
    assignMask(mask) {
        if (mask !== this._mask) {
            this._mask = mask;
            return true;
        }
        return false;
    }
}

/**
 * @import { AnimBlendTree } from './anim-blend-tree.js'
 * @import { AnimState } from './anim-state.js'
 */ /**
 * AnimNodes are used to represent a single animation track in the current state. Each state can
 * contain multiple AnimNodes, in which case they are stored in a BlendTree hierarchy, which will
 * control the weight (contribution to the states final animation) of its child AnimNodes.
 *
 * @category Animation
 */ class AnimNode {
    /**
     * Create a new AnimNode instance.
     *
     * @param {AnimState} state - The AnimState that this BlendTree belongs to.
     * @param {AnimBlendTree|null} parent - The parent of the AnimNode. If not null, the AnimNode
     * is stored as part of an {@link AnimBlendTree} hierarchy.
     * @param {string} name - The name of the AnimNode. Used when assigning an {@link AnimTrack} to
     * it.
     * @param {number[]|number} point - The coordinate/vector thats used to determine the weight of
     * this node when it's part of an {@link AnimBlendTree}.
     * @param {number} [speed] - The speed that its {@link AnimTrack} should play at. Defaults to 1.
     */ constructor(state, parent, name, point, speed = 1){
        this._state = state;
        this._parent = parent;
        this._name = name;
        if (Array.isArray(point)) {
            this._point = new Vec2(point[0], point[1]);
            this._pointLength = this._point.length();
        } else {
            this._point = point;
            this._pointLength = point;
        }
        this._speed = speed;
        this._weightedSpeed = 1.0;
        this._weight = 1.0;
        this._animTrack = null;
    }
    get parent() {
        return this._parent;
    }
    get name() {
        return this._name;
    }
    get path() {
        return this._parent ? `${this._parent.path}.${this._name}` : this._name;
    }
    get point() {
        return this._point;
    }
    get pointLength() {
        return this._pointLength;
    }
    set weight(value) {
        this._weight = value;
    }
    get weight() {
        return this._parent ? this._parent.weight * this._weight : this._weight;
    }
    get normalizedWeight() {
        const totalWeight = this._state.totalWeight;
        if (totalWeight === 0.0) return 0.0;
        return this.weight / totalWeight;
    }
    get speed() {
        return this._weightedSpeed * this._speed;
    }
    get absoluteSpeed() {
        return Math.abs(this._speed);
    }
    set weightedSpeed(weightedSpeed) {
        this._weightedSpeed = weightedSpeed;
    }
    get weightedSpeed() {
        return this._weightedSpeed;
    }
    set animTrack(value) {
        this._animTrack = value;
    }
    get animTrack() {
        return this._animTrack;
    }
}

/**
 * @import { AnimState } from './anim-state.js'
 * @import { Vec2 } from '../../../core/math/vec2.js'
 */ /**
 * AnimBlendTrees are used to store and blend multiple {@link AnimNode}s together. BlendTrees can
 * be the child of other AnimBlendTrees, in order to create a hierarchy of AnimNodes. It takes a
 * blend type as an argument which defines which function should be used to determine the weights
 * of each of its children, based on the current parameter value.
 *
 * @category Animation
 */ class AnimBlendTree extends AnimNode {
    /**
     * Create a new AnimBlendTree instance.
     *
     * @param {AnimState} state - The AnimState that this AnimBlendTree belongs to.
     * @param {AnimBlendTree|null} parent - The parent of the AnimBlendTree. If not null, the
     * AnimNode is stored as part of a {@link AnimBlendTree} hierarchy.
     * @param {string} name - The name of the BlendTree. Used when assigning an {@link AnimTrack}
     * to its children.
     * @param {number|Vec2} point - The coordinate/vector that's used to determine the weight of
     * this node when it's part of an {@link AnimBlendTree}.
     * @param {string[]} parameters - The anim component parameters which are used to calculate the
     * current weights of the blend trees children.
     * @param {object[]} children - The child nodes that this blend tree should create. Can either
     * be of type {@link AnimNode} or {@link AnimBlendTree}.
     * @param {boolean} syncAnimations - If true, the speed of each blended animation will be
     * synchronized.
     * @param {Function} createTree - Used to create child blend trees of varying types.
     * @param {Function} findParameter - Used at runtime to get the current parameter values.
     */ constructor(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter){
        super(state, parent, name, point);
        this._parameters = parameters;
        this._parameterValues = new Array(parameters.length);
        this._children = [];
        this._findParameter = findParameter;
        this._syncAnimations = syncAnimations !== false;
        this._pointCache = {};
        for(let i = 0; i < children.length; i++){
            const child = children[i];
            if (child.children) {
                this._children.push(createTree(child.type, state, this, child.name, 1.0, child.parameter ? [
                    child.parameter
                ] : child.parameters, child.children, child.syncAnimations, createTree, findParameter));
            } else {
                this._children.push(new AnimNode(state, this, child.name, child.point, child.speed));
            }
        }
    }
    get weight() {
        this.calculateWeights();
        return this._parent ? this._parent.weight * this._weight : this._weight;
    }
    get syncAnimations() {
        return this._syncAnimations;
    }
    getChild(name) {
        for(let i = 0; i < this._children.length; i++){
            if (this._children[i].name === name) return this._children[i];
        }
        return null;
    }
    updateParameterValues() {
        let paramsEqual = true;
        for(let i = 0; i < this._parameterValues.length; i++){
            const updatedParameter = this._findParameter(this._parameters[i]).value;
            if (this._parameterValues[i] !== updatedParameter) {
                this._parameterValues[i] = updatedParameter;
                paramsEqual = false;
            }
        }
        return paramsEqual;
    }
    getNodeWeightedDuration(i) {
        return this._children[i].animTrack.duration / this._children[i].speedMultiplier * this._children[i].weight;
    }
    getNodeCount() {
        let count = 0;
        for(let i = 0; i < this._children.length; i++){
            const child = this._children[i];
            if (child.constructor === AnimBlendTree) {
                count += this._children[i].getNodeCount();
            } else {
                count++;
            }
        }
        return count;
    }
}

/**
 * @import { AnimState } from './anim-state.js'
 * @import { Vec2 } from '../../../core/math/vec2.js'
 */ /**
 * An AnimBlendTree that calculates its weights using a 1D algorithm based on the thesis
 * http://runevision.com/thesis/rune_skovbo_johansen_thesis.pdf Chapter 6.
 *
 * @category Animation
 */ class AnimBlendTree1D extends AnimBlendTree {
    /**
     * Create a new BlendTree1D instance.
     *
     * @param {AnimState} state - The AnimState that this AnimBlendTree belongs to.
     * @param {AnimBlendTree|null} parent - The parent of the AnimBlendTree. If not null, the
     * AnimNode is stored as part of a {@link AnimBlendTree} hierarchy.
     * @param {string} name - The name of the BlendTree. Used when assigning an {@link AnimTrack}
     * to its children.
     * @param {number|Vec2} point - The coordinate/vector that's used to determine the weight of
     * this node when it's part of an {@link AnimBlendTree}.
     * @param {string[]} parameters - The anim component parameters which are used to calculate the
     * current weights of the blend trees children.
     * @param {object[]} children - The child nodes that this blend tree should create. Can either
     * be of type {@link AnimNode} or {@link AnimBlendTree}.
     * @param {boolean} syncAnimations - If true, the speed of each blended animation will be
     * synchronized.
     * @param {Function} createTree - Used to create child blend trees of varying types.
     * @param {Function} findParameter - Used at runtime to get the current parameter values.
     */ constructor(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter){
        children.sort((a, b)=>a.point - b.point);
        super(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);
    }
    calculateWeights() {
        if (this.updateParameterValues()) return;
        let weightedDurationSum = 0.0;
        this._children[0].weight = 0.0;
        for(let i = 0; i < this._children.length; i++){
            const c1 = this._children[i];
            if (i !== this._children.length - 1) {
                const c2 = this._children[i + 1];
                if (c1.point === c2.point) {
                    c1.weight = 0.5;
                    c2.weight = 0.5;
                } else if (math.between(this._parameterValues[0], c1.point, c2.point, true)) {
                    const child2Distance = Math.abs(c1.point - c2.point);
                    const parameterDistance = Math.abs(c1.point - this._parameterValues[0]);
                    const weight = (child2Distance - parameterDistance) / child2Distance;
                    c1.weight = weight;
                    c2.weight = 1.0 - weight;
                } else {
                    c2.weight = 0.0;
                }
            }
            if (this._syncAnimations) {
                weightedDurationSum += c1.animTrack.duration / c1.absoluteSpeed * c1.weight;
            }
        }
        if (this._syncAnimations) {
            for(let i = 0; i < this._children.length; i++){
                const child = this._children[i];
                child.weightedSpeed = child.animTrack.duration / child.absoluteSpeed / weightedDurationSum;
            }
        }
    }
}

/**
 * An AnimBlendTree that calculates its weights using a 2D Cartesian algorithm based on the thesis
 * http://runevision.com/thesis/rune_skovbo_johansen_thesis.pdf Chapter 6 Section 3.
 *
 * @category Animation
 */ class AnimBlendTreeCartesian2D extends AnimBlendTree {
    static{
        this._p = new Vec2();
    }
    static{
        this._pip = new Vec2();
    }
    pointDistanceCache(i, j) {
        const pointKey = `${i}${j}`;
        if (!this._pointCache[pointKey]) {
            this._pointCache[pointKey] = this._children[j].point.clone().sub(this._children[i].point);
        }
        return this._pointCache[pointKey];
    }
    calculateWeights() {
        if (this.updateParameterValues()) return;
        let weightSum, weightedDurationSum;
        AnimBlendTreeCartesian2D._p.set(...this._parameterValues);
        weightSum = 0.0;
        weightedDurationSum = 0.0;
        for(let i = 0; i < this._children.length; i++){
            const child = this._children[i];
            const pi = child.point;
            AnimBlendTreeCartesian2D._pip.set(AnimBlendTreeCartesian2D._p.x, AnimBlendTreeCartesian2D._p.y).sub(pi);
            let minj = Number.MAX_VALUE;
            for(let j = 0; j < this._children.length; j++){
                if (i === j) continue;
                const pipj = this.pointDistanceCache(i, j);
                const result = math.clamp(1.0 - AnimBlendTreeCartesian2D._pip.dot(pipj) / pipj.lengthSq(), 0.0, 1.0);
                if (result < minj) minj = result;
            }
            child.weight = minj;
            weightSum += minj;
            if (this._syncAnimations) {
                weightedDurationSum += child.animTrack.duration / child.absoluteSpeed * child.weight;
            }
        }
        for(let i = 0; i < this._children.length; i++){
            const child = this._children[i];
            child.weight = child._weight / weightSum;
            if (this._syncAnimations) {
                child.weightedSpeed = child.animTrack.duration / child.absoluteSpeed / weightedDurationSum;
            }
        }
    }
}

/**
 * An AnimBlendTree that calculates its weights using a 2D directional algorithm based on the thesis
 * http://runevision.com/thesis/rune_skovbo_johansen_thesis.pdf Chapter 6.
 *
 * @category Animation
 */ class AnimBlendTreeDirectional2D extends AnimBlendTree {
    static{
        this._p = new Vec2();
    }
    static{
        this._pip = new Vec2();
    }
    pointCache(i, j) {
        const pointKey = `${i}${j}`;
        if (!this._pointCache[pointKey]) {
            this._pointCache[pointKey] = new Vec2((this._children[j].pointLength - this._children[i].pointLength) / ((this._children[j].pointLength + this._children[i].pointLength) / 2), Vec2.angleRad(this._children[i].point, this._children[j].point) * 2.0);
        }
        return this._pointCache[pointKey];
    }
    calculateWeights() {
        if (this.updateParameterValues()) return;
        let weightSum, weightedDurationSum;
        AnimBlendTreeDirectional2D._p.set(...this._parameterValues);
        const pLength = AnimBlendTreeDirectional2D._p.length();
        weightSum = 0.0;
        weightedDurationSum = 0.0;
        for(let i = 0; i < this._children.length; i++){
            const child = this._children[i];
            const pi = child.point;
            const piLength = child.pointLength;
            let minj = Number.MAX_VALUE;
            for(let j = 0; j < this._children.length; j++){
                if (i === j) continue;
                const pipj = this.pointCache(i, j);
                const pjLength = this._children[j].pointLength;
                AnimBlendTreeDirectional2D._pip.set((pLength - piLength) / ((pjLength + piLength) / 2), Vec2.angleRad(pi, AnimBlendTreeDirectional2D._p) * 2.0);
                const result = math.clamp(1.0 - Math.abs(AnimBlendTreeDirectional2D._pip.dot(pipj) / pipj.lengthSq()), 0.0, 1.0);
                if (result < minj) minj = result;
            }
            child.weight = minj;
            weightSum += minj;
            if (this._syncAnimations) {
                weightedDurationSum += child.animTrack.duration / child.absoluteSpeed * child.weight;
            }
        }
        for(let i = 0; i < this._children.length; i++){
            const child = this._children[i];
            child.weight = child._weight / weightSum;
            if (this._syncAnimations) {
                const weightedChildDuration = child.animTrack.duration / weightedDurationSum * weightSum;
                child.weightedSpeed = child.absoluteSpeed * weightedChildDuration;
            }
        }
    }
}

/**
 * An AnimBlendTree that calculates normalized weight values based on the total weight.
 *
 * @category Animation
 */ class AnimBlendTreeDirect extends AnimBlendTree {
    calculateWeights() {
        if (this.updateParameterValues()) return;
        let weightSum = 0.0;
        let weightedDurationSum = 0.0;
        for(let i = 0; i < this._children.length; i++){
            weightSum += Math.max(this._parameterValues[i], 0.0);
            if (this._syncAnimations) {
                const child = this._children[i];
                weightedDurationSum += child.animTrack.duration / child.absoluteSpeed * child.weight;
            }
        }
        for(let i = 0; i < this._children.length; i++){
            const child = this._children[i];
            const weight = Math.max(this._parameterValues[i], 0.0);
            if (weightSum) {
                child.weight = weight / weightSum;
                if (this._syncAnimations) {
                    child.weightedSpeed = child.animTrack.duration / child.absoluteSpeed / weightedDurationSum;
                }
            } else {
                child.weight = 0.0;
                if (this._syncAnimations) {
                    child.weightedSpeed = 0;
                }
            }
        }
    }
}

/**
 * @import { AnimController } from './anim-controller.js'
 */ /**
 * Defines a single state that the controller can be in. Each state contains either a single
 * {@link AnimNode} or an {@link AnimBlendTree} of multiple {@link AnimNode}s, which will be used
 * to animate the {@link Entity} while the state is active. An AnimState will stay active and play
 * as long as there is no {@link AnimTransition} with its conditions met that has that AnimState
 * as its source state.
 *
 * @category Animation
 */ class AnimState {
    /**
     * Create a new AnimState instance.
     *
     * @param {AnimController} controller - The controller this AnimState is associated with.
     * @param {string} name - The name of the state. Used to find this state when the controller
     * transitions between states and links animations.
     * @param {number} [speed] - The speed animations in the state should play at. Individual
     * {@link AnimNode}s can override this value.
     * @param {boolean} [loop] - Determines whether animations in this state should loop.
     * @param {object|null} [blendTree] - If supplied, the AnimState will recursively build a
     * {@link AnimBlendTree} hierarchy, used to store, blend and play multiple animations.
     */ constructor(controller, name, speed = 1, loop = true, blendTree){
        /** @private */ this._animations = {};
        /** @private */ this._animationList = [];
        this._controller = controller;
        this._name = name;
        this._speed = speed;
        this._loop = loop;
        this._hasAnimations = false;
        if (blendTree) {
            this._blendTree = this._createTree(blendTree.type, this, null, name, 1.0, blendTree.parameter ? [
                blendTree.parameter
            ] : blendTree.parameters, blendTree.children, blendTree.syncAnimations, this._createTree, this._controller.findParameter);
        } else {
            this._blendTree = new AnimNode(this, null, name, 1.0, speed);
        }
    }
    _createTree(type, state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter) {
        switch(type){
            case ANIM_BLEND_1D:
                return new AnimBlendTree1D(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);
            case ANIM_BLEND_2D_CARTESIAN:
                return new AnimBlendTreeCartesian2D(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);
            case ANIM_BLEND_2D_DIRECTIONAL:
                return new AnimBlendTreeDirectional2D(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);
            case ANIM_BLEND_DIRECT:
                return new AnimBlendTreeDirect(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);
        }
        Debug.error(`Invalid anim blend type: ${type}`);
        return undefined;
    }
    _getNodeFromPath(path) {
        let currNode = this._blendTree;
        for(let i = 1; i < path.length; i++){
            currNode = currNode.getChild(path[i]);
        }
        return currNode;
    }
    addAnimation(path, animTrack) {
        const pathString = path.join('.');
        const indexOfAnimation = this._animationList.findIndex((animation)=>{
            return animation.path === pathString;
        });
        if (indexOfAnimation >= 0) {
            this._animationList[indexOfAnimation].animTrack = animTrack;
        } else {
            const node = this._getNodeFromPath(path);
            node.animTrack = animTrack;
            this._animationList.push(node);
        }
        this._updateHasAnimations();
    }
    _updateHasAnimations() {
        this._hasAnimations = this._animationList.length > 0 && this._animationList.every((animation)=>animation.animTrack && animation.animTrack !== AnimTrack.EMPTY);
    }
    get name() {
        return this._name;
    }
    set animations(value) {
        this._animationList = value;
        this._updateHasAnimations();
    }
    get animations() {
        return this._animationList;
    }
    get hasAnimations() {
        return this._hasAnimations;
    }
    set speed(value) {
        this._speed = value;
    }
    get speed() {
        return this._speed;
    }
    set loop(value) {
        this._loop = value;
    }
    get loop() {
        return this._loop;
    }
    get nodeCount() {
        if (!this._blendTree || this._blendTree.constructor === AnimNode) return 1;
        return this._blendTree.getNodeCount();
    }
    get playable() {
        return ANIM_CONTROL_STATES.indexOf(this.name) !== -1 || this.animations.length === this.nodeCount;
    }
    get looping() {
        if (this.animations.length > 0) {
            const trackClipName = `${this.name}.${this.animations[0].animTrack.name}`;
            const trackClip = this._controller.animEvaluator.findClip(trackClipName);
            if (trackClip) {
                return trackClip.loop;
            }
        }
        return false;
    }
    get totalWeight() {
        let sum = 0;
        for(let i = 0; i < this.animations.length; i++){
            sum += this.animations[i].weight;
        }
        return sum;
    }
    get timelineDuration() {
        let duration = 0;
        for(let i = 0; i < this.animations.length; i++){
            const animation = this.animations[i];
            if (animation.animTrack.duration > duration) {
                duration = animation.animTrack.duration;
            }
        }
        return duration;
    }
}

/**
 * AnimTransitions represent connections in the controllers state graph between AnimStates. During
 * each frame, the controller tests to see if any of the AnimTransitions have the current AnimState
 * as their source (from) state. If so and the AnimTransitions parameter based conditions are met,
 * the controller will transition to the destination state.
 *
 * @category Animation
 */ class AnimTransition {
    /**
     * Create a new AnimTransition.
     *
     * @param {object} options - Options.
     * @param {string} options.from - The state that this transition will exit from.
     * @param {string} options.to - The state that this transition will transition to.
     * @param {number} [options.time] - The duration of the transition in seconds. Defaults to 0.
     * @param {number} [options.priority] - Used to sort all matching transitions in ascending
     * order. The first transition in the list will be selected. Defaults to 0.
     * @param {object[]} [options.conditions] - A list of conditions which must pass for this
     * transition to be used. Defaults to [].
     * @param {number} [options.exitTime] - If provided, this transition will only be active for
     * the exact frame during which the source states progress passes the time specified. Given as
     * a normalized value of the source states duration. Values less than 1 will be checked every
     * animation loop. Defaults to null.
     * @param {number} [options.transitionOffset] - If provided, the destination state will begin
     * playing its animation at this time. Given in normalized time, based on the state's duration
     * and must be between 0 and 1. Defaults to null.
     * @param {string} [options.interruptionSource] - Defines whether another transition can
     * interrupt this one and which of the current or previous states transitions can do so. One of
     * pc.ANIM_INTERRUPTION_*. Defaults to pc.ANIM_INTERRUPTION_NONE.
     */ constructor({ from, to, time = 0, priority = 0, conditions = [], exitTime = null, transitionOffset = null, interruptionSource = ANIM_INTERRUPTION_NONE }){
        this._from = from;
        this._to = to;
        this._time = time;
        this._priority = priority;
        this._conditions = conditions;
        this._exitTime = exitTime;
        this._transitionOffset = transitionOffset;
        this._interruptionSource = interruptionSource;
    }
    get from() {
        return this._from;
    }
    set to(value) {
        this._to = value;
    }
    get to() {
        return this._to;
    }
    get time() {
        return this._time;
    }
    get priority() {
        return this._priority;
    }
    get conditions() {
        return this._conditions;
    }
    get exitTime() {
        return this._exitTime;
    }
    get transitionOffset() {
        return this._transitionOffset;
    }
    get interruptionSource() {
        return this._interruptionSource;
    }
    get hasExitTime() {
        return !!this.exitTime;
    }
}

/**
 * @import { AnimEvaluator } from '../evaluator/anim-evaluator.js'
 * @import { EventHandler } from '../../../core/event-handler.js'
 */ /**
 * The AnimController manages the animations for its entity, based on the provided state graph and
 * parameters. Its update method determines which state the controller should be in based on the
 * current time, parameters and available states / transitions. It also ensures the AnimEvaluator
 * is supplied with the correct animations, based on the currently active state.
 *
 * @ignore
 */ class AnimController {
    /**
     * Create a new AnimController.
     *
     * @param {AnimEvaluator} animEvaluator - The animation evaluator used to blend all current
     * playing animation keyframes and update the entities properties based on the current
     * animation values.
     * @param {object[]} states - The list of states used to form the controller state graph.
     * @param {object[]} transitions - The list of transitions used to form the controller state
     * graph.
     * @param {boolean} activate - Determines whether the anim controller should automatically play
     * once all {@link AnimNodes} are assigned animations.
     * @param {EventHandler} eventHandler - The event handler which should be notified with anim
     * events.
     * @param {Function} findParameter - Retrieves a parameter which is used to control the
     * transition between states.
     * @param {Function} consumeTrigger - Used to set triggers back to their default state after
     * they have been consumed by a transition.
     */ constructor(animEvaluator, states, transitions, activate, eventHandler, findParameter, consumeTrigger){
        /**
     * @type {Object<string, AnimState>}
     * @private
     */ this._states = {};
        /**
     * @type {string[]}
     * @private
     */ this._stateNames = [];
        /**
     * @type {Object<string, AnimTransition[]>}
     * @private
     */ this._findTransitionsFromStateCache = {};
        /**
     * @type {Object<string, AnimTransition[]>}
     * @private
     */ this._findTransitionsBetweenStatesCache = {};
        /**
     * @type {string|null}
     * @private
     */ this._previousStateName = null;
        /** @private */ this._activeStateName = ANIM_STATE_START;
        /** @private */ this._activeStateDuration = 0;
        /** @private */ this._activeStateDurationDirty = true;
        /** @private */ this._playing = false;
        /** @private */ this._currTransitionTime = 1;
        /** @private */ this._totalTransitionTime = 1;
        /** @private */ this._isTransitioning = false;
        /** @private */ this._transitionInterruptionSource = ANIM_INTERRUPTION_NONE;
        /** @private */ this._transitionPreviousStates = [];
        /** @private */ this._timeInState = 0;
        /** @private */ this._timeInStateBefore = 0;
        this.findParameter = (name)=>{
            return this._findParameter(name);
        };
        this._animEvaluator = animEvaluator;
        this._eventHandler = eventHandler;
        this._findParameter = findParameter;
        this._consumeTrigger = consumeTrigger;
        for(let i = 0; i < states.length; i++){
            this._states[states[i].name] = new AnimState(this, states[i].name, states[i].speed, states[i].loop, states[i].blendTree);
            this._stateNames.push(states[i].name);
        }
        this._transitions = transitions.map((transition)=>{
            return new AnimTransition({
                ...transition
            });
        });
        this._activate = activate;
    }
    get animEvaluator() {
        return this._animEvaluator;
    }
    set activeState(stateName) {
        this._activeStateName = stateName;
    }
    get activeState() {
        return this._findState(this._activeStateName);
    }
    get activeStateName() {
        return this._activeStateName;
    }
    get activeStateAnimations() {
        return this.activeState.animations;
    }
    set previousState(stateName) {
        this._previousStateName = stateName;
    }
    get previousState() {
        return this._findState(this._previousStateName);
    }
    get previousStateName() {
        return this._previousStateName;
    }
    get playable() {
        let playable = true;
        for(let i = 0; i < this._stateNames.length; i++){
            if (!this._states[this._stateNames[i]].playable) {
                playable = false;
            }
        }
        return playable;
    }
    set playing(value) {
        this._playing = value;
    }
    get playing() {
        return this._playing;
    }
    get activeStateProgress() {
        return this._getActiveStateProgressForTime(this._timeInState);
    }
    get activeStateDuration() {
        if (this._activeStateDurationDirty) {
            let maxDuration = 0.0;
            for(let i = 0; i < this.activeStateAnimations.length; i++){
                const activeClip = this._animEvaluator.findClip(this.activeStateAnimations[i].name);
                if (activeClip) {
                    maxDuration = Math.max(maxDuration, activeClip.track.duration);
                }
            }
            this._activeStateDuration = maxDuration;
            this._activeStateDurationDirty = false;
        }
        return this._activeStateDuration;
    }
    set activeStateCurrentTime(time) {
        this._timeInStateBefore = time;
        this._timeInState = time;
        for(let i = 0; i < this.activeStateAnimations.length; i++){
            const clip = this.animEvaluator.findClip(this.activeStateAnimations[i].name);
            if (clip) {
                clip.time = time;
            }
        }
    }
    get activeStateCurrentTime() {
        return this._timeInState;
    }
    get transitioning() {
        return this._isTransitioning;
    }
    get transitionProgress() {
        return this._currTransitionTime / this._totalTransitionTime;
    }
    get states() {
        return this._stateNames;
    }
    assignMask(mask) {
        return this._animEvaluator.assignMask(mask);
    }
    /**
     * @param {string} stateName - The name of the state to find.
     * @returns {AnimState} The state with the given name.
     * @private
     */ _findState(stateName) {
        return this._states[stateName];
    }
    _getActiveStateProgressForTime(time) {
        if (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END || this.activeStateName === ANIM_STATE_ANY) {
            return 1.0;
        }
        const activeClip = this._animEvaluator.findClip(this.activeStateAnimations[0].name);
        if (activeClip) {
            return activeClip.progressForTime(time);
        }
        return null;
    }
    /**
     * Return all the transitions that have the given stateName as their source state.
     *
     * @param {string} stateName - The name of the state to find transitions from.
     * @returns {AnimTransition[]} The transitions that have the given stateName as their source
     * state.
     * @private
     */ _findTransitionsFromState(stateName) {
        let transitions = this._findTransitionsFromStateCache[stateName];
        if (!transitions) {
            transitions = this._transitions.filter((transition)=>{
                return transition.from === stateName;
            });
            // sort transitions in priority order
            sortPriority(transitions);
            this._findTransitionsFromStateCache[stateName] = transitions;
        }
        return transitions;
    }
    /**
     * Return all the transitions that contain the given source and destination states.
     *
     * @param {string} sourceStateName - The name of the source state to find transitions from.
     * @param {string} destinationStateName - The name of the destination state to find transitions
     * to.
     * @returns {AnimTransition[]} The transitions that have the given source and destination states.
     * @private
     */ _findTransitionsBetweenStates(sourceStateName, destinationStateName) {
        let transitions = this._findTransitionsBetweenStatesCache[`${sourceStateName}->${destinationStateName}`];
        if (!transitions) {
            transitions = this._transitions.filter((transition)=>{
                return transition.from === sourceStateName && transition.to === destinationStateName;
            });
            // sort transitions in priority order
            sortPriority(transitions);
            this._findTransitionsBetweenStatesCache[`${sourceStateName}->${destinationStateName}`] = transitions;
        }
        return transitions;
    }
    _transitionHasConditionsMet(transition) {
        const conditions = transition.conditions;
        for(let i = 0; i < conditions.length; i++){
            const condition = conditions[i];
            const parameter = this._findParameter(condition.parameterName);
            switch(condition.predicate){
                case ANIM_GREATER_THAN:
                    if (!(parameter.value > condition.value)) return false;
                    break;
                case ANIM_LESS_THAN:
                    if (!(parameter.value < condition.value)) return false;
                    break;
                case ANIM_GREATER_THAN_EQUAL_TO:
                    if (!(parameter.value >= condition.value)) return false;
                    break;
                case ANIM_LESS_THAN_EQUAL_TO:
                    if (!(parameter.value <= condition.value)) return false;
                    break;
                case ANIM_EQUAL_TO:
                    if (!(parameter.value === condition.value)) return false;
                    break;
                case ANIM_NOT_EQUAL_TO:
                    if (!(parameter.value !== condition.value)) return false;
                    break;
            }
        }
        return true;
    }
    _findTransition(from, to) {
        let transitions = [];
        // If from and to are supplied, find transitions that include the required source and destination states
        if (from && to) {
            transitions = transitions.concat(this._findTransitionsBetweenStates(from, to));
        } else {
            // If no transition is active, look for transitions from the active & any states.
            if (!this._isTransitioning) {
                transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));
                transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));
            } else {
                // Otherwise look for transitions from the previous and active states based on the current interruption source.
                // Accept transitions from the any state unless the interruption source is set to none
                switch(this._transitionInterruptionSource){
                    case ANIM_INTERRUPTION_PREV:
                        transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName));
                        transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));
                        break;
                    case ANIM_INTERRUPTION_NEXT:
                        transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));
                        transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));
                        break;
                    case ANIM_INTERRUPTION_PREV_NEXT:
                        transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName));
                        transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));
                        transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));
                        break;
                    case ANIM_INTERRUPTION_NEXT_PREV:
                        transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));
                        transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName));
                        transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));
                        break;
                }
            }
        }
        // filter out transitions that don't have their conditions met
        transitions = transitions.filter((transition)=>{
            // if the transition is moving to the already active state, ignore it
            if (transition.to === this.activeStateName) {
                return false;
            }
            // when an exit time is present, we should only exit if it falls within the current frame delta time
            if (transition.hasExitTime) {
                let progressBefore = this._getActiveStateProgressForTime(this._timeInStateBefore);
                let progress = this._getActiveStateProgressForTime(this._timeInState);
                // when the exit time is smaller than 1 and the state is looping, we should check for an exit each loop
                if (transition.exitTime < 1.0 && this.activeState.loop) {
                    progressBefore -= Math.floor(progressBefore);
                    progress -= Math.floor(progress);
                }
                // if the delta time is 0 and the progress matches the exit time, the exitTime condition has been met
                if (progress === progressBefore) {
                    if (progress !== transition.exitTime) {
                        return null;
                    }
                // otherwise if the delta time is greater than 0, return false if exit time isn't within the frames delta time
                } else if (!(transition.exitTime > progressBefore && transition.exitTime <= progress)) {
                    return null;
                }
            }
            // if the exitTime condition has been met or is not present, check condition parameters
            return this._transitionHasConditionsMet(transition);
        });
        // return the highest priority transition to use
        if (transitions.length > 0) {
            const transition = transitions[0];
            if (transition.to === ANIM_STATE_END) {
                const startTransition = this._findTransitionsFromState(ANIM_STATE_START)[0];
                transition.to = startTransition.to;
            }
            return transition;
        }
        return null;
    }
    updateStateFromTransition(transition) {
        let state;
        let animation;
        let clip;
        // If transition.from is set, transition from the active state irregardless of the transition.from value (this could be the previous, active or ANY states).
        // Otherwise the previousState is cleared.
        this.previousState = transition.from ? this.activeStateName : null;
        this.activeState = transition.to;
        // when transitioning to a new state, we need to recalculate the duration of the active state based on its animations
        this._activeStateDurationDirty = true;
        // turn off any triggers which were required to activate this transition
        for(let i = 0; i < transition.conditions.length; i++){
            const condition = transition.conditions[i];
            const parameter = this._findParameter(condition.parameterName);
            if (parameter.type === ANIM_PARAMETER_TRIGGER) {
                this._consumeTrigger(condition.parameterName);
            }
        }
        if (this.previousState) {
            if (!this._isTransitioning) {
                this._transitionPreviousStates = [];
            }
            // record the transition source state in the previous states array
            this._transitionPreviousStates.push({
                name: this._previousStateName,
                weight: 1
            });
            // if this new transition was activated during another transition, update the previous transition state weights based
            // on the progress through the previous transition.
            const interpolatedTime = Math.min(this._totalTransitionTime !== 0 ? this._currTransitionTime / this._totalTransitionTime : 1, 1.0);
            for(let i = 0; i < this._transitionPreviousStates.length; i++){
                // interpolate the weights of the most recent previous state and all other previous states based on the progress through the previous transition
                if (!this._isTransitioning) {
                    this._transitionPreviousStates[i].weight = 1.0;
                } else if (i !== this._transitionPreviousStates.length - 1) {
                    this._transitionPreviousStates[i].weight *= 1.0 - interpolatedTime;
                } else {
                    this._transitionPreviousStates[i].weight = interpolatedTime;
                }
                state = this._findState(this._transitionPreviousStates[i].name);
                // update the animations of previous states, set their name to include their position in the previous state array
                // to uniquely identify animations from the same state that were added during different transitions
                for(let j = 0; j < state.animations.length; j++){
                    animation = state.animations[j];
                    clip = this._animEvaluator.findClip(`${animation.name}.previous.${i}`);
                    if (!clip) {
                        clip = this._animEvaluator.findClip(animation.name);
                        clip.name = `${animation.name}.previous.${i}`;
                    }
                    // // pause previous animation clips to reduce their impact on performance
                    if (i !== this._transitionPreviousStates.length - 1) {
                        clip.pause();
                    }
                }
            }
        }
        this._isTransitioning = true;
        this._totalTransitionTime = transition.time;
        this._currTransitionTime = 0;
        this._transitionInterruptionSource = transition.interruptionSource;
        const activeState = this.activeState;
        const hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0;
        // set the time in the new state to 0 or to a value based on transitionOffset if one was given
        let timeInState = 0;
        let timeInStateBefore = 0;
        if (hasTransitionOffset) {
            const offsetTime = activeState.timelineDuration * transition.transitionOffset;
            timeInState = offsetTime;
            timeInStateBefore = offsetTime;
        }
        this._timeInState = timeInState;
        this._timeInStateBefore = timeInStateBefore;
        // Add clips to the evaluator for each animation in the new state.
        for(let i = 0; i < activeState.animations.length; i++){
            clip = this._animEvaluator.findClip(activeState.animations[i].name);
            if (!clip) {
                const speed = Number.isFinite(activeState.animations[i].speed) ? activeState.animations[i].speed : activeState.speed;
                clip = new AnimClip(activeState.animations[i].animTrack, this._timeInState, speed, true, activeState.loop, this._eventHandler);
                clip.name = activeState.animations[i].name;
                this._animEvaluator.addClip(clip);
            } else {
                clip.reset();
            }
            if (transition.time > 0) {
                clip.blendWeight = 0.0;
            } else {
                clip.blendWeight = activeState.animations[i].normalizedWeight;
            }
            clip.play();
            if (hasTransitionOffset) {
                clip.time = activeState.timelineDuration * transition.transitionOffset;
            } else {
                const startTime = activeState.speed >= 0 ? 0 : this.activeStateDuration;
                clip.time = startTime;
            }
        }
    }
    _transitionToState(newStateName) {
        if (!this._findState(newStateName)) {
            return;
        }
        // move to the given state, if a transition is present in the state graph use it. Otherwise move instantly to it.
        let transition = this._findTransition(this._activeStateName, newStateName);
        if (!transition) {
            this._animEvaluator.removeClips();
            transition = new AnimTransition({
                from: null,
                to: newStateName
            });
        }
        this.updateStateFromTransition(transition);
    }
    assignAnimation(pathString, animTrack, speed, loop) {
        const path = pathString.split('.');
        let state = this._findState(path[0]);
        if (!state) {
            state = new AnimState(this, path[0], speed);
            this._states[path[0]] = state;
            this._stateNames.push(path[0]);
        }
        state.addAnimation(path, animTrack);
        this._animEvaluator.updateClipTrack(state.name, animTrack);
        if (speed !== undefined) {
            state.speed = speed;
        }
        if (loop !== undefined) {
            state.loop = loop;
        }
        if (!this._playing && this._activate && this.playable) {
            this.play();
        }
        // when a new animation is added, the active state duration needs to be recalculated
        this._activeStateDurationDirty = true;
    }
    removeNodeAnimations(nodeName) {
        if (ANIM_CONTROL_STATES.indexOf(nodeName) !== -1) {
            return false;
        }
        const state = this._findState(nodeName);
        if (!state) {
            Debug.error('Attempting to unassign animation tracks from a state that does not exist.', nodeName);
            return false;
        }
        state.animations = [];
        return true;
    }
    play(stateName) {
        if (stateName) {
            this._transitionToState(stateName);
        }
        this._playing = true;
    }
    pause() {
        this._playing = false;
    }
    reset() {
        this._previousStateName = null;
        this._activeStateName = ANIM_STATE_START;
        this._playing = false;
        this._currTransitionTime = 1.0;
        this._totalTransitionTime = 1.0;
        this._isTransitioning = false;
        this._timeInState = 0;
        this._timeInStateBefore = 0;
        this._animEvaluator.removeClips();
    }
    rebind() {
        this._animEvaluator.rebind();
    }
    update(dt) {
        if (!this._playing) {
            return;
        }
        let state;
        let animation;
        let clip;
        // update time when looping or when the active state is not at the end of its duration
        if (this.activeState.loop || this._timeInState < this.activeStateDuration) {
            this._timeInStateBefore = this._timeInState;
            this._timeInState += dt * this.activeState.speed;
            // if the active state is not looping and the time in state is greater than the duration, set the time in state to the state duration
            // and update the delta time accordingly
            if (!this.activeState.loop && this._timeInState > this.activeStateDuration) {
                this._timeInState = this.activeStateDuration;
                dt = this.activeStateDuration - this._timeInStateBefore;
            }
        }
        // transition between states if a transition is available from the active state
        const transition = this._findTransition(this._activeStateName);
        if (transition) {
            this.updateStateFromTransition(transition);
        }
        if (this._isTransitioning) {
            this._currTransitionTime += dt;
            if (this._currTransitionTime <= this._totalTransitionTime) {
                const interpolatedTime = this._totalTransitionTime !== 0 ? this._currTransitionTime / this._totalTransitionTime : 1;
                // while transitioning, set all previous state animations to be weighted by (1.0 - interpolationTime).
                for(let i = 0; i < this._transitionPreviousStates.length; i++){
                    state = this._findState(this._transitionPreviousStates[i].name);
                    const stateWeight = this._transitionPreviousStates[i].weight;
                    for(let j = 0; j < state.animations.length; j++){
                        animation = state.animations[j];
                        clip = this._animEvaluator.findClip(`${animation.name}.previous.${i}`);
                        if (clip) {
                            clip.blendWeight = (1.0 - interpolatedTime) * animation.normalizedWeight * stateWeight;
                        }
                    }
                }
                // while transitioning, set active state animations to be weighted by (interpolationTime).
                state = this.activeState;
                for(let i = 0; i < state.animations.length; i++){
                    animation = state.animations[i];
                    this._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.normalizedWeight;
                }
            } else {
                this._isTransitioning = false;
                // when a transition ends, remove all previous state clips from the evaluator
                const activeClips = this.activeStateAnimations.length;
                const totalClips = this._animEvaluator.clips.length;
                for(let i = 0; i < totalClips - activeClips; i++){
                    this._animEvaluator.removeClip(0);
                }
                this._transitionPreviousStates = [];
                // when a transition ends, set the active state clip weights so they sum to 1
                state = this.activeState;
                for(let i = 0; i < state.animations.length; i++){
                    animation = state.animations[i];
                    clip = this._animEvaluator.findClip(animation.name);
                    if (clip) {
                        clip.blendWeight = animation.normalizedWeight;
                    }
                }
            }
        } else {
            if (this.activeState._blendTree.constructor !== AnimNode) {
                state = this.activeState;
                for(let i = 0; i < state.animations.length; i++){
                    animation = state.animations[i];
                    clip = this._animEvaluator.findClip(animation.name);
                    if (clip) {
                        clip.blendWeight = animation.normalizedWeight;
                        if (animation.parent.syncAnimations) {
                            clip.speed = animation.speed;
                        }
                    }
                }
            }
        }
        this._animEvaluator.update(dt, this.activeState.hasAnimations);
    }
}

const v2$5 = new Vec2();
const v3$1 = new Vec3();
const v4$1 = new Vec4();
const c = new Color();
const q$3 = new Quat();
class AnimComponentBinder extends DefaultAnimBinder {
    constructor(animComponent, graph, layerName, mask, layerIndex){
        super(graph);
        this.animComponent = animComponent;
        this._mask = mask;
        this.layerName = layerName;
        this.layerIndex = layerIndex;
    }
    static _packFloat(values) {
        return values[0];
    }
    static _packBoolean(values) {
        return !!values[0];
    }
    static _packVec2(values) {
        v2$5.x = values[0];
        v2$5.y = values[1];
        return v2$5;
    }
    static _packVec3(values) {
        v3$1.x = values[0];
        v3$1.y = values[1];
        v3$1.z = values[2];
        return v3$1;
    }
    static _packVec4(values) {
        v4$1.x = values[0];
        v4$1.y = values[1];
        v4$1.z = values[2];
        v4$1.w = values[3];
        return v4$1;
    }
    static _packColor(values) {
        c.r = values[0];
        c.g = values[1];
        c.b = values[2];
        c.a = values[3];
        return c;
    }
    static _packQuat(values) {
        q$3.x = values[0];
        q$3.y = values[1];
        q$3.z = values[2];
        q$3.w = values[3];
        return q$3;
    }
    resolve(path) {
        const encodedPath = AnimBinder.encode(path.entityPath, path.component, path.propertyPath);
        let target = this.targetCache[encodedPath];
        if (target) return target;
        let entity;
        let propertyComponent;
        let targetPath;
        switch(path.component){
            case 'entity':
                entity = this._getEntityFromHierarchy(path.entityPath);
                targetPath = AnimBinder.encode(entity.path, 'entity', path.propertyPath);
                propertyComponent = entity;
                break;
            case 'graph':
                propertyComponent = this.findNode(path);
                if (!propertyComponent) return null;
                targetPath = AnimBinder.encode(propertyComponent.path, 'graph', path.propertyPath);
                break;
            default:
                entity = this._getEntityFromHierarchy(path.entityPath);
                propertyComponent = entity.findComponent(path.component);
                if (!propertyComponent) {
                    return null;
                }
                targetPath = AnimBinder.encode(entity.path, path.component, path.propertyPath);
                break;
        }
        target = this._createAnimTargetForProperty(propertyComponent, path.propertyPath, targetPath);
        this.targetCache[encodedPath] = target;
        return target;
    }
    update(deltaTime) {
        // flag active nodes as dirty
        const activeNodes = this.activeNodes;
        if (activeNodes) {
            for(let i = 0; i < activeNodes.length; i++){
                activeNodes[i]._dirtifyLocal();
            }
        }
    }
    _getEntityFromHierarchy(entityHierarchy) {
        if (!this.animComponent.entity.name === entityHierarchy[0]) {
            return null;
        }
        const currEntity = this.animComponent.entity;
        if (entityHierarchy.length === 1) {
            return currEntity;
        }
        return currEntity._parent.findByPath(entityHierarchy);
    }
    // resolve an object path
    _resolvePath(object, path, resolveLeaf) {
        const steps = path.length - (resolveLeaf ? 0 : 1);
        for(let i = 0; i < steps; i++){
            object = object[path[i]];
        }
        return object;
    }
    // construct a setter function for the property located at 'path' from the base object. packFunc
    // is a function which takes the animation values array and packages them for the target property
    // in the correct format (i.e. vec2, quat, color etc).
    _setter(object, path, packFunc) {
        const obj = this._resolvePath(object, path);
        const key = path[path.length - 1];
        // if the object has a setter function, use it
        const setterFuncName = `set${key.substring(0, 1).toUpperCase()}${key.substring(1)}`;
        if (obj[setterFuncName]) {
            // if the object has a setter function, use it
            const getterFunc = obj[`get${key.substring(0, 1).toUpperCase()}${key.substring(1)}`].bind(obj);
            let baseValues = getterFunc();
            baseValues = [
                baseValues.x,
                baseValues.y,
                baseValues.z,
                baseValues.w
            ];
            const setterFunc = obj[setterFuncName].bind(obj);
            return {
                set: (values)=>{
                    setterFunc(packFunc(values));
                },
                get: ()=>baseValues
            };
        }
        const prop = obj[key];
        // if the target property has a copy function, use it (vec3, color, quat)
        if (typeof prop === 'object' && prop.hasOwnProperty('copy')) {
            return function(values) {
                prop.copy(packFunc(values));
            };
        }
        // when animating individual members of vec/color/quaternion, we must also invoke the
        // object's setter. this is required by some component properties which have custom
        // handlers which propagate the changes correctly.
        if ([
            Vec2,
            Vec3,
            Vec4,
            Color,
            Quat
        ].indexOf(obj.constructor) !== -1 && path.length > 1) {
            const parent = path.length > 2 ? this._resolvePath(object, path.slice(0, -1)) : object;
            const objKey = path[path.length - 2];
            return function(values) {
                obj[key] = packFunc(values);
                parent[objKey] = obj;
            };
        }
        // otherwise set the property directly (float, boolean)
        return function(values) {
            obj[key] = packFunc(values);
        };
    }
    _createAnimTargetForProperty(propertyComponent, propertyHierarchy, targetPath) {
        if (this.handlers && propertyHierarchy[0].startsWith('weight.')) {
            return this.handlers.weight(propertyComponent, propertyHierarchy[0].replace('weight.', ''));
        } else if (this.handlers && propertyHierarchy[0] === 'material' && propertyHierarchy.length === 2) {
            const materialPropertyName = propertyHierarchy[1];
            // if the property name ends in Map then we're binding a material texture
            if (materialPropertyName.endsWith('Map')) {
                return this.handlers.materialTexture(propertyComponent, materialPropertyName);
            }
        }
        const property = this._resolvePath(propertyComponent, propertyHierarchy, true);
        if (typeof property === 'undefined') {
            return null;
        }
        let setter;
        let animDataType;
        let animDataComponents;
        if (typeof property === 'number') {
            setter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packFloat);
            animDataType = 'vector';
            animDataComponents = 1;
        } else if (typeof property === 'boolean') {
            setter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packBoolean);
            animDataType = 'vector';
            animDataComponents = 1;
        } else if (typeof property === 'object') {
            switch(property.constructor){
                case Vec2:
                    setter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packVec2);
                    animDataType = 'vector';
                    animDataComponents = 2;
                    break;
                case Vec3:
                    setter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packVec3);
                    animDataType = 'vector';
                    animDataComponents = 3;
                    break;
                case Vec4:
                    setter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packVec4);
                    animDataType = 'vector';
                    animDataComponents = 4;
                    break;
                case Color:
                    setter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packColor);
                    animDataType = 'vector';
                    animDataComponents = 4;
                    break;
                case Quat:
                    setter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packQuat);
                    animDataType = 'quaternion';
                    animDataComponents = 4;
                    break;
                default:
                    return null;
            }
        }
        // materials must have update called after changing settings
        if (propertyHierarchy.indexOf('material') !== -1) {
            return new AnimTarget((values)=>{
                setter(values);
                propertyComponent.material.update();
            }, animDataType, animDataComponents, targetPath);
        }
        return new AnimTarget(setter, animDataType, animDataComponents, targetPath);
    }
    rebind() {
        this.targetCache = {};
        this.visitedFallbackGraphPaths = {};
        if (this.animComponent.rootBone) {
            this.graph = this.animComponent.rootBone;
        } else {
            this.graph = this.animComponent.entity;
        }
        const nodes = {};
        // cache node names so we can quickly resolve animation paths
        const flatten = function(node) {
            nodes[node.name] = node;
            for(let i = 0; i < node.children.length; ++i){
                flatten(node.children[i]);
            }
        };
        flatten(this.graph);
        this.nodes = nodes;
    }
}

/**
 * @import { AnimComponent } from './component.js'
 * @import { AnimController } from '../../anim/controller/anim-controller.js'
 */ /**
 * The Anim Component Layer allows managers a single layer of the animation state graph.
 *
 * @category Animation
 */ class AnimComponentLayer {
    /**
     * Create a new AnimComponentLayer instance.
     *
     * @param {string} name - The name of the layer.
     * @param {AnimController} controller - The controller to manage this layers animations.
     * @param {AnimComponent} component - The component that this layer is a member of.
     * @param {number} [weight] - The weight of this layer. Defaults to 1.
     * @param {string} [blendType] - The blend type of this layer. Defaults to {@link ANIM_LAYER_OVERWRITE}.
     * @ignore
     */ constructor(name, controller, component, weight = 1, blendType = ANIM_LAYER_OVERWRITE){
        /** @private */ this._mask = null;
        /** @private */ this._blendTime = 0;
        /** @private */ this._blendTimeElapsed = 0;
        /** @private */ this._startingWeight = 0;
        /** @private */ this._targetWeight = 0;
        this._name = name;
        this._controller = controller;
        this._component = component;
        this._weight = weight;
        this._blendType = blendType;
    }
    /**
     * Returns the name of the layer.
     *
     * @type {string}
     */ get name() {
        return this._name;
    }
    /**
     * Sets whether this layer is currently playing.
     *
     * @type {boolean}
     */ set playing(value) {
        this._controller.playing = value;
    }
    /**
     * Gets whether this layer is currently playing.
     *
     * @type {boolean}
     */ get playing() {
        return this._controller.playing;
    }
    /**
     * Returns true if a state graph has been loaded and all states in the graph have been assigned
     * animation tracks.
     *
     * @type {boolean}
     */ get playable() {
        return this._controller.playable;
    }
    /**
     * Gets the currently active state name.
     *
     * @type {string}
     */ get activeState() {
        return this._controller.activeStateName;
    }
    /**
     * Gets the previously active state name.
     *
     * @type {string|null}
     */ get previousState() {
        return this._controller.previousStateName;
    }
    /**
     * Gets the currently active state's progress as a value normalized by the state's animation
     * duration. Looped animations will return values greater than 1.
     *
     * @type {number}
     */ get activeStateProgress() {
        return this._controller.activeStateProgress;
    }
    /**
     * Gets the currently active states duration.
     *
     * @type {number}
     */ get activeStateDuration() {
        return this._controller.activeStateDuration;
    }
    /**
     * Sets the active state's time in seconds.
     *
     * @type {number}
     */ set activeStateCurrentTime(time) {
        const controller = this._controller;
        const layerPlaying = controller.playing;
        controller.playing = true;
        controller.activeStateCurrentTime = time;
        if (!layerPlaying) {
            controller.update(0);
        }
        controller.playing = layerPlaying;
    }
    /**
     * Gets the active state's time in seconds.
     *
     * @type {number}
     */ get activeStateCurrentTime() {
        return this._controller.activeStateCurrentTime;
    }
    /**
     * Gets whether the anim component layer is currently transitioning between states.
     *
     * @type {boolean}
     */ get transitioning() {
        return this._controller.transitioning;
    }
    /**
     * Gets the progress, if the anim component layer is currently transitioning between states.
     * Otherwise returns null.
     *
     * @type {number|null}
     */ get transitionProgress() {
        if (this.transitioning) {
            return this._controller.transitionProgress;
        }
        return null;
    }
    /**
     * Gets all available states in this layers state graph.
     *
     * @type {string[]}
     */ get states() {
        return this._controller.states;
    }
    /**
     * Sets the blending weight of this layer. Used when calculating the value of properties that
     * are animated by more than one layer.
     *
     * @type {number}
     */ set weight(value) {
        this._weight = value;
        this._component.dirtifyTargets();
    }
    /**
     * Sets the blending weight of this layer.
     *
     * @type {number}
     */ get weight() {
        return this._weight;
    }
    set blendType(value) {
        if (value !== this._blendType) {
            this._blendType = value;
            if (this._controller.normalizeWeights) {
                this._component.rebind();
            }
        }
    }
    get blendType() {
        return this._blendType;
    }
    /**
     * Sets the mask of bones which should be animated or ignored by this layer.
     *
     * @type {object}
     * @example
     * entity.anim.baseLayer.mask = {
     *     // include the spine of the current model and all of its children
     *     "path/to/spine": {
     *         children: true
     *     },
     *     // include the hip of the current model but not all of its children
     *     "path/to/hip": true
     * };
     */ set mask(value) {
        if (this._controller.assignMask(value)) {
            this._component.rebind();
        }
        this._mask = value;
    }
    /**
     * Gets the mask of bones which should be animated or ignored by this layer.
     *
     * @type {object}
     */ get mask() {
        return this._mask;
    }
    /**
     * Start playing the animation in the current state.
     *
     * @param {string} [name] - If provided, will begin playing from the start of the state with
     * this name.
     */ play(name) {
        this._controller.play(name);
    }
    /**
     * Pause the animation in the current state.
     */ pause() {
        this._controller.pause();
    }
    /**
     * Reset the animation component to its initial state, including all parameters. The system
     * will be paused.
     */ reset() {
        this._controller.reset();
    }
    /**
     * Rebind any animations in the layer to the currently present components and model of the anim
     * components entity.
     */ rebind() {
        this._controller.rebind();
    }
    update(dt) {
        if (this._blendTime) {
            if (this._blendTimeElapsed < this._blendTime) {
                this.weight = math.lerp(this._startingWeight, this._targetWeight, this._blendTimeElapsed / this._blendTime);
                this._blendTimeElapsed += dt;
            } else {
                this.weight = this._targetWeight;
                this._blendTime = 0;
                this._blendTimeElapsed = 0;
                this._startingWeight = 0;
                this._targetWeight = 0;
            }
        }
        this._controller.update(dt);
    }
    /**
     * Blend from the current weight value to the provided weight value over a given amount of time.
     *
     * @param {number} weight - The new weight value to blend to.
     * @param {number} time - The duration of the blend in seconds.
     */ blendToWeight(weight, time) {
        this._startingWeight = this.weight;
        this._targetWeight = weight;
        this._blendTime = Math.max(0, time);
        this._blendTimeElapsed = 0;
    }
    /**
     * Assigns an animation track to a state or blend tree node in the current graph. If a state
     * for the given nodePath doesn't exist, it will be created. If all states nodes are linked and
     * the {@link AnimComponent#activate} value was set to true then the component will begin
     * playing.
     *
     * @param {string} nodePath - Either the state name or the path to a blend tree node that this
     * animation should be associated with. Each section of a blend tree path is split using a
     * period (`.`) therefore state names should not include this character (e.g "MyStateName" or
     * "MyStateName.BlendTreeNode").
     * @param {AnimTrack} animTrack - The animation track that will be assigned to this state and
     * played whenever this state is active.
     * @param {number} [speed] - Update the speed of the state you are assigning an animation to.
     * Defaults to 1.
     * @param {boolean} [loop] - Update the loop property of the state you are assigning an
     * animation to. Defaults to true.
     */ assignAnimation(nodePath, animTrack, speed, loop) {
        if (!(animTrack instanceof AnimTrack)) {
            Debug.error('assignAnimation: animTrack supplied to function was not of type AnimTrack');
            return;
        }
        this._controller.assignAnimation(nodePath, animTrack, speed, loop);
        if (this._controller._transitions.length === 0) {
            this._controller._transitions.push(new AnimTransition({
                from: 'START',
                to: nodePath
            }));
        }
        if (this._component.activate && this._component.playable) {
            this._component.playing = true;
        }
    }
    /**
     * Removes animations from a node in the loaded state graph.
     *
     * @param {string} nodeName - The name of the node that should have its animation tracks removed.
     */ removeNodeAnimations(nodeName) {
        if (this._controller.removeNodeAnimations(nodeName)) {
            this._component.playing = false;
        }
    }
    /**
     * Returns an object holding the animation asset id that is associated with the given state.
     *
     * @param {string} stateName - The name of the state to get the asset for.
     * @returns {{ asset: number }} An object containing the animation asset id associated with the given state.
     */ getAnimationAsset(stateName) {
        return this._component.animationAssets[`${this.name}:${stateName}`];
    }
    /**
     * Transition to any state in the current layers graph. Transitions can be instant or take an
     * optional blend time.
     *
     * @param {string} to - The state that this transition will transition to.
     * @param {number} [time] - The duration of the transition in seconds. Defaults to 0.
     * @param {number} [transitionOffset] - If provided, the destination state will begin playing
     * its animation at this time. Given in normalized time, based on the states duration & must be
     * between 0 and 1. Defaults to null.
     */ transition(to, time = 0, transitionOffset = null) {
        this._controller.updateStateFromTransition(new AnimTransition({
            from: this._controller.activeStateName,
            to,
            time,
            transitionOffset
        }));
    }
}

/**
 * An asset resource which represents an anim state graph. It can be loaded into an anim component using the {@link AnimComponent#loadStateGraph} method.
 *
 * ## Usage
 * Scripts can retrieve an AnimStateGraph instance from assets of type 'animstategraph'. An AnimStateGraph can then be loaded into an anim component as follows:
 * ```javascript
 * const animStateGraph = app.assets.get(ASSET_ID).resource;
 * const entity = new pc.Entity();
 * entity.addComponent('anim');
 * entity.anim.loadStateGraph(animStateGraph);
 * ```
 *
 * @category Animation
 */ class AnimStateGraph {
    /**
     * Create an AnimStateGraph instance from JSON data.
     *
     * @param {object} data - The JSON data to create the AnimStateGraph from.
     * @ignore
     */ constructor(data){
        this._layers = [];
        this._parameters = {};
        if (!Array.isArray(data.layers)) {
            // Layers as an object
            // const data = {
            //     "layers": {
            //         "0": {
            //             "name": "Base",
            //             "states": [0, 1],
            //             "transitions": [0]
            //         }
            //     },
            //     "states": {
            //         "0": {
            //             "name": "START",
            //             "speed": 1
            //         },
            //         "1": {
            //             "name": "New State",
            //             "speed": 1,
            //             "loop": true,
            //             "defaultState": true
            //         }
            //     },
            //     "transitions": {
            //         "0": {
            //             "from": 0,
            //             "to": 1,
            //             "conditions": {}
            //         }
            //     },
            //     "parameters": {}
            // };
            for(const layerId in data.layers){
                const dataLayer = data.layers[layerId];
                const layer = {
                    name: dataLayer.name,
                    blendType: dataLayer.blendType,
                    weight: dataLayer.weight,
                    states: [],
                    transitions: []
                };
                for(let i = 0; i < dataLayer.states.length; i++){
                    layer.states.push(data.states[dataLayer.states[i]]);
                }
                for(let i = 0; i < dataLayer.transitions.length; i++){
                    const dataLayerTransition = data.transitions[dataLayer.transitions[i]];
                    if (dataLayerTransition.conditions && !Array.isArray(dataLayerTransition.conditions)) {
                        const conditionKeys = Object.keys(dataLayerTransition.conditions);
                        const conditions = [];
                        for(let j = 0; j < conditionKeys.length; j++){
                            const condition = dataLayerTransition.conditions[conditionKeys[j]];
                            if (condition.parameterName) {
                                conditions.push(condition);
                            }
                        }
                        dataLayerTransition.conditions = conditions;
                    }
                    if (Number.isInteger(dataLayerTransition.from)) {
                        dataLayerTransition.from = data.states[dataLayerTransition.from].name;
                    }
                    if (Number.isInteger(dataLayerTransition.to)) {
                        dataLayerTransition.to = data.states[dataLayerTransition.to].name;
                    }
                    layer.transitions.push(dataLayerTransition);
                }
                this._layers.push(layer);
            }
        } else {
            // Layers as an array:
            // const data = {
            //     "layers": [
            //         {
            //             "name": "Base",
            //             "states": [
            //                 {
            //                     "name": "START",
            //                     "speed": 1
            //                 },
            //                 {
            //                     "name": "New State",
            //                     "speed": 1,
            //                     "loop": true,
            //                     "defaultState": true,
            //                 }
            //             ],
            //             "transitions": [
            //                 {
            //                     "from": 0,
            //                     "to": 1,
            //                     "conditions": {}
            //                 }
            //             ]
            //         }
            //     ],
            //     "parameters": {}
            // };
            this._layers = data.layers;
        }
        for(const paramId in data.parameters){
            const param = data.parameters[paramId];
            this._parameters[param.name] = {
                type: param.type,
                value: param.value
            };
        }
    }
    get parameters() {
        return Object.assign({}, this._parameters);
    }
    get layers() {
        return this._layers;
    }
}

/**
 * The AnimComponent allows an {@link Entity} to playback animations on models and entity
 * properties.
 *
 * @hideconstructor
 * @category Animation
 */ class AnimComponent extends Component {
    set stateGraphAsset(value) {
        if (value === null) {
            this.removeStateGraph();
            return;
        }
        // remove event from previous asset
        if (this._stateGraphAsset) {
            const stateGraphAsset = this.system.app.assets.get(this._stateGraphAsset);
            stateGraphAsset.off('change', this._onStateGraphAssetChangeEvent, this);
        }
        let _id;
        let _asset;
        if (value instanceof Asset) {
            _id = value.id;
            _asset = this.system.app.assets.get(_id);
            if (!_asset) {
                this.system.app.assets.add(value);
                _asset = this.system.app.assets.get(_id);
            }
        } else {
            _id = value;
            _asset = this.system.app.assets.get(_id);
        }
        if (!_asset || this._stateGraphAsset === _id) {
            return;
        }
        if (_asset.resource) {
            this._stateGraph = _asset.resource;
            this.loadStateGraph(this._stateGraph);
            _asset.on('change', this._onStateGraphAssetChangeEvent, this);
        } else {
            _asset.once('load', (asset)=>{
                this._stateGraph = asset.resource;
                this.loadStateGraph(this._stateGraph);
            });
            _asset.on('change', this._onStateGraphAssetChangeEvent, this);
            this.system.app.assets.load(_asset);
        }
        this._stateGraphAsset = _id;
    }
    get stateGraphAsset() {
        return this._stateGraphAsset;
    }
    /**
     * Sets whether the animation component will normalize the weights of its layers by their sum total.
     *
     * @type {boolean}
     */ set normalizeWeights(value) {
        this._normalizeWeights = value;
        this.unbind();
    }
    /**
     * Gets whether the animation component will normalize the weights of its layers by their sum total.
     *
     * @type {boolean}
     */ get normalizeWeights() {
        return this._normalizeWeights;
    }
    set animationAssets(value) {
        this._animationAssets = value;
        this.loadAnimationAssets();
    }
    get animationAssets() {
        return this._animationAssets;
    }
    /**
     * Sets the speed multiplier for animation play back speed. 1.0 is playback at normal speed, 0.0 pauses
     * the animation.
     *
     * @type {number}
     */ set speed(value) {
        this._speed = value;
    }
    /**
     * Gets the speed multiplier for animation play back speed.
     *
     * @type {number}
     */ get speed() {
        return this._speed;
    }
    /**
     * Sets whether the first animation will begin playing when the scene is loaded.
     *
     * @type {boolean}
     */ set activate(value) {
        this._activate = value;
    }
    /**
     * Gets whether the first animation will begin playing when the scene is loaded.
     *
     * @type {boolean}
     */ get activate() {
        return this._activate;
    }
    /**
     * Sets whether to play or pause all animations in the component.
     *
     * @type {boolean}
     */ set playing(value) {
        this._playing = value;
    }
    /**
     * Gets whether to play or pause all animations in the component.
     *
     * @type {boolean}
     */ get playing() {
        return this._playing;
    }
    /**
     * Sets the entity that this anim component should use as the root of the animation hierarchy.
     *
     * @type {Entity}
     */ set rootBone(value) {
        if (typeof value === 'string') {
            const entity = this.entity.root.findByGuid(value);
            Debug.assert(entity, `rootBone entity for supplied guid:${value} cannot be found in the scene`);
            this._rootBone = entity;
        } else if (value instanceof Entity) {
            this._rootBone = value;
        } else {
            this._rootBone = null;
        }
        this.rebind();
    }
    /**
     * Gets the entity that this anim component should use as the root of the animation hierarchy.
     *
     * @type {Entity}
     */ get rootBone() {
        return this._rootBone;
    }
    set stateGraph(value) {
        this._stateGraph = value;
    }
    get stateGraph() {
        return this._stateGraph;
    }
    /**
     * Returns the animation layers available in this anim component.
     *
     * @type {AnimComponentLayer[]}
     */ get layers() {
        return this._layers;
    }
    set layerIndices(value) {
        this._layerIndices = value;
    }
    get layerIndices() {
        return this._layerIndices;
    }
    set parameters(value) {
        this._parameters = value;
    }
    get parameters() {
        return this._parameters;
    }
    set targets(value) {
        this._targets = value;
    }
    get targets() {
        return this._targets;
    }
    /**
     * Returns whether all component layers are currently playable.
     *
     * @type {boolean}
     */ get playable() {
        for(let i = 0; i < this._layers.length; i++){
            if (!this._layers[i].playable) {
                return false;
            }
        }
        return true;
    }
    /**
     * Returns the base layer of the state graph.
     *
     * @type {AnimComponentLayer|null}
     */ get baseLayer() {
        if (this._layers.length > 0) {
            return this._layers[0];
        }
        return null;
    }
    _onStateGraphAssetChangeEvent(asset) {
        // both animationAssets and layer masks should be maintained when switching AnimStateGraph assets
        const prevAnimationAssets = this.animationAssets;
        const prevMasks = this.layers.map((layer)=>layer.mask);
        // clear the previous state graph
        this.removeStateGraph();
        // load the new state graph
        this._stateGraph = new AnimStateGraph(asset._data);
        this.loadStateGraph(this._stateGraph);
        // assign the previous animation assets
        this.animationAssets = prevAnimationAssets;
        this.loadAnimationAssets();
        // assign the previous layer masks then rebind all anim targets
        this.layers.forEach((layer, i)=>{
            layer.mask = prevMasks[i];
        });
        this.rebind();
    }
    dirtifyTargets() {
        const targets = Object.values(this._targets);
        for(let i = 0; i < targets.length; i++){
            targets[i].dirty = true;
        }
    }
    _addLayer({ name, states, transitions, weight, mask, blendType }) {
        let graph;
        if (this.rootBone) {
            graph = this.rootBone;
        } else {
            graph = this.entity;
        }
        const layerIndex = this._layers.length;
        const animBinder = new AnimComponentBinder(this, graph, name, mask, layerIndex);
        const animEvaluator = new AnimEvaluator(animBinder);
        const controller = new AnimController(animEvaluator, states, transitions, this._activate, this, this.findParameter, this.consumeTrigger);
        this._layers.push(new AnimComponentLayer(name, controller, this, weight, blendType));
        this._layerIndices[name] = layerIndex;
        return this._layers[layerIndex];
    }
    /**
     * Adds a new anim component layer to the anim component.
     *
     * @param {string} name - The name of the layer to create.
     * @param {number} [weight] - The blending weight of the layer. Defaults to 1.
     * @param {object[]} [mask] - A list of paths to bones in the model which should be animated in
     * this layer. If omitted the full model is used. Defaults to null.
     * @param {string} [blendType] - Defines how properties animated by this layer blend with
     * animations of those properties in previous layers. Defaults to pc.ANIM_LAYER_OVERWRITE.
     * @returns {AnimComponentLayer} The created anim component layer.
     */ addLayer(name, weight, mask, blendType) {
        const layer = this.findAnimationLayer(name);
        if (layer) return layer;
        const states = [
            {
                'name': 'START',
                'speed': 1
            }
        ];
        const transitions = [];
        return this._addLayer({
            name,
            states,
            transitions,
            weight,
            mask,
            blendType
        });
    }
    _assignParameters(stateGraph) {
        this._parameters = {};
        const paramKeys = Object.keys(stateGraph.parameters);
        for(let i = 0; i < paramKeys.length; i++){
            const paramKey = paramKeys[i];
            this._parameters[paramKey] = {
                type: stateGraph.parameters[paramKey].type,
                value: stateGraph.parameters[paramKey].value
            };
        }
    }
    /**
     * Initializes component animation controllers using the provided state graph.
     *
     * @param {object} stateGraph - The state graph asset to load into the component. Contains the
     * states, transitions and parameters used to define a complete animation controller.
     * @example
     * entity.anim.loadStateGraph({
     *     "layers": [
     *         {
     *             "name": layerName,
     *             "states": [
     *                 {
     *                     "name": "START",
     *                     "speed": 1
     *                 },
     *                 {
     *                     "name": "Initial State",
     *                     "speed": speed,
     *                     "loop": loop,
     *                     "defaultState": true
     *                 }
     *             ],
     *             "transitions": [
     *                 {
     *                     "from": "START",
     *                     "to": "Initial State"
     *                 }
     *             ]
     *         }
     *     ],
     *     "parameters": {}
     * });
     */ loadStateGraph(stateGraph) {
        this._stateGraph = stateGraph;
        this._assignParameters(stateGraph);
        this._layers = [];
        let containsBlendTree = false;
        for(let i = 0; i < stateGraph.layers.length; i++){
            const layer = stateGraph.layers[i];
            this._addLayer({
                ...layer
            });
            if (layer.states.some((state)=>state.blendTree)) {
                containsBlendTree = true;
            }
        }
        // blend trees do not support the automatic assignment of animation assets
        if (!containsBlendTree) {
            this.setupAnimationAssets();
        }
    }
    setupAnimationAssets() {
        for(let i = 0; i < this._layers.length; i++){
            const layer = this._layers[i];
            const layerName = layer.name;
            for(let j = 0; j < layer.states.length; j++){
                const stateName = layer.states[j];
                if (ANIM_CONTROL_STATES.indexOf(stateName) === -1) {
                    const stateKey = `${layerName}:${stateName}`;
                    if (!this._animationAssets[stateKey]) {
                        this._animationAssets[stateKey] = {
                            asset: null
                        };
                    }
                }
            }
        }
        this.loadAnimationAssets();
    }
    loadAnimationAssets() {
        for(let i = 0; i < this._layers.length; i++){
            const layer = this._layers[i];
            for(let j = 0; j < layer.states.length; j++){
                const stateName = layer.states[j];
                if (ANIM_CONTROL_STATES.indexOf(stateName) !== -1) continue;
                const animationAsset = this._animationAssets[`${layer.name}:${stateName}`];
                if (!animationAsset || !animationAsset.asset) {
                    this.findAnimationLayer(layer.name).assignAnimation(stateName, AnimTrack.EMPTY);
                    continue;
                }
                const assetId = animationAsset.asset;
                const asset = this.system.app.assets.get(assetId);
                // check whether assigned animation asset still exists
                if (asset) {
                    if (asset.resource) {
                        this.onAnimationAssetLoaded(layer.name, stateName, asset);
                    } else {
                        asset.once('load', (function(layerName, stateName) {
                            return (function(asset) {
                                this.onAnimationAssetLoaded(layerName, stateName, asset);
                            }).bind(this);
                        }).bind(this)(layer.name, stateName));
                        this.system.app.assets.load(asset);
                    }
                }
            }
        }
    }
    onAnimationAssetLoaded(layerName, stateName, asset) {
        this.findAnimationLayer(layerName).assignAnimation(stateName, asset.resource);
    }
    /**
     * Removes all layers from the anim component.
     */ removeStateGraph() {
        this._stateGraph = null;
        this._stateGraphAsset = null;
        this._animationAssets = {};
        this._layers = [];
        this._layerIndices = {};
        this._parameters = {};
        this._playing = false;
        this.unbind();
        // clear all targets from previous binding
        this._targets = {};
    }
    /**
     * Reset all of the components layers and parameters to their initial states. If a layer was
     * playing before it will continue playing.
     */ reset() {
        this._assignParameters(this._stateGraph);
        for(let i = 0; i < this._layers.length; i++){
            const layerPlaying = this._layers[i].playing;
            this._layers[i].reset();
            this._layers[i].playing = layerPlaying;
        }
    }
    unbind() {
        if (!this._normalizeWeights) {
            Object.keys(this._targets).forEach((targetKey)=>{
                this._targets[targetKey].unbind();
            });
        }
    }
    /**
     * Rebind all of the components layers.
     */ rebind() {
        // clear all targets from previous binding
        this._targets = {};
        // rebind all layers
        for(let i = 0; i < this._layers.length; i++){
            this._layers[i].rebind();
        }
    }
    /**
     * Finds an {@link AnimComponentLayer} in this component.
     *
     * @param {string} name - The name of the anim component layer to find.
     * @returns {AnimComponentLayer} Layer.
     */ findAnimationLayer(name) {
        const layerIndex = this._layerIndices[name];
        return this._layers[layerIndex] || null;
    }
    addAnimationState(nodeName, animTrack, speed = 1, loop = true, layerName = 'Base') {
        if (!this._stateGraph) {
            this.loadStateGraph(new AnimStateGraph({
                'layers': [
                    {
                        'name': layerName,
                        'states': [
                            {
                                'name': 'START',
                                'speed': 1
                            },
                            {
                                'name': nodeName,
                                'speed': speed,
                                'loop': loop,
                                'defaultState': true
                            }
                        ],
                        'transitions': [
                            {
                                'from': 'START',
                                'to': nodeName
                            }
                        ]
                    }
                ],
                'parameters': {}
            }));
        }
        const layer = this.findAnimationLayer(layerName);
        if (layer) {
            layer.assignAnimation(nodeName, animTrack, speed, loop);
        } else {
            this.addLayer(layerName)?.assignAnimation(nodeName, animTrack, speed, loop);
        }
    }
    /**
     * Associates an animation with a state or blend tree node in the loaded state graph. If all
     * states are linked and the {@link activate} value was set to true then the component will
     * begin playing. If no state graph is loaded, a default state graph will be created with a
     * single state based on the provided nodePath parameter.
     *
     * @param {string} nodePath - Either the state name or the path to a blend tree node that this
     * animation should be associated with. Each section of a blend tree path is split using a
     * period (`.`) therefore state names should not include this character (e.g "MyStateName" or
     * "MyStateName.BlendTreeNode").
     * @param {AnimTrack} animTrack - The animation track that will be assigned to this state and
     * played whenever this state is active.
     * @param {string} [layerName] - The name of the anim component layer to update. If omitted the
     * default layer is used. If no state graph has been previously loaded this parameter is
     * ignored.
     * @param {number} [speed] - Update the speed of the state you are assigning an animation to.
     * Defaults to 1.
     * @param {boolean} [loop] - Update the loop property of the state you are assigning an
     * animation to. Defaults to true.
     */ assignAnimation(nodePath, animTrack, layerName, speed = 1, loop = true) {
        if (!this._stateGraph && nodePath.indexOf('.') === -1) {
            this.loadStateGraph(new AnimStateGraph({
                'layers': [
                    {
                        'name': 'Base',
                        'states': [
                            {
                                'name': 'START',
                                'speed': 1
                            },
                            {
                                'name': nodePath,
                                'speed': speed,
                                'loop': loop,
                                'defaultState': true
                            }
                        ],
                        'transitions': [
                            {
                                'from': 'START',
                                'to': nodePath
                            }
                        ]
                    }
                ],
                'parameters': {}
            }));
            this.baseLayer.assignAnimation(nodePath, animTrack);
            return;
        }
        const layer = layerName ? this.findAnimationLayer(layerName) : this.baseLayer;
        if (!layer) {
            Debug.error('assignAnimation: Trying to assign an anim track to a layer that doesn\'t exist');
            return;
        }
        layer.assignAnimation(nodePath, animTrack, speed, loop);
    }
    /**
     * Removes animations from a node in the loaded state graph.
     *
     * @param {string} nodeName - The name of the node that should have its animation tracks removed.
     * @param {string} [layerName] - The name of the anim component layer to update. If omitted the
     * default layer is used.
     */ removeNodeAnimations(nodeName, layerName) {
        const layer = layerName ? this.findAnimationLayer(layerName) : this.baseLayer;
        if (!layer) {
            Debug.error('removeStateAnimations: Trying to remove animation tracks from a state before the state graph has been loaded. Have you called loadStateGraph?');
            return;
        }
        layer.removeNodeAnimations(nodeName);
    }
    getParameterValue(name, type) {
        const param = this._parameters[name];
        if (param && param.type === type) {
            return param.value;
        }
        Debug.log(`Cannot get parameter value. No parameter found in anim controller named "${name}" of type "${type}"`);
        return undefined;
    }
    setParameterValue(name, type, value) {
        const param = this._parameters[name];
        if (param && param.type === type) {
            param.value = value;
            return;
        }
        Debug.log(`Cannot set parameter value. No parameter found in anim controller named "${name}" of type "${type}"`);
    }
    /**
     * Returns a float parameter value by name.
     *
     * @param {string} name - The name of the float to return the value of.
     * @returns {number} A float.
     */ getFloat(name) {
        return this.getParameterValue(name, ANIM_PARAMETER_FLOAT);
    }
    /**
     * Sets the value of a float parameter that was defined in the animation components state graph.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {number} value - The new float value to set this parameter to.
     */ setFloat(name, value) {
        this.setParameterValue(name, ANIM_PARAMETER_FLOAT, value);
    }
    /**
     * Returns an integer parameter value by name.
     *
     * @param {string} name - The name of the integer to return the value of.
     * @returns {number} An integer.
     */ getInteger(name) {
        return this.getParameterValue(name, ANIM_PARAMETER_INTEGER);
    }
    /**
     * Sets the value of an integer parameter that was defined in the animation components state
     * graph.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {number} value - The new integer value to set this parameter to.
     */ setInteger(name, value) {
        if (typeof value === 'number' && value % 1 === 0) {
            this.setParameterValue(name, ANIM_PARAMETER_INTEGER, value);
        } else {
            Debug.error('Attempting to assign non integer value to integer parameter', name, value);
        }
    }
    /**
     * Returns a boolean parameter value by name.
     *
     * @param {string} name - The name of the boolean to return the value of.
     * @returns {boolean} A boolean.
     */ getBoolean(name) {
        return this.getParameterValue(name, ANIM_PARAMETER_BOOLEAN);
    }
    /**
     * Sets the value of a boolean parameter that was defined in the animation components state
     * graph.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {boolean} value - The new boolean value to set this parameter to.
     */ setBoolean(name, value) {
        this.setParameterValue(name, ANIM_PARAMETER_BOOLEAN, !!value);
    }
    /**
     * Returns a trigger parameter value by name.
     *
     * @param {string} name - The name of the trigger to return the value of.
     * @returns {boolean} A boolean.
     */ getTrigger(name) {
        return this.getParameterValue(name, ANIM_PARAMETER_TRIGGER);
    }
    /**
     * Sets the value of a trigger parameter that was defined in the animation components state
     * graph to true.
     *
     * @param {string} name - The name of the parameter to set.
     * @param {boolean} [singleFrame] - If true, this trigger will be set back to false at the end
     * of the animation update. Defaults to false.
     */ setTrigger(name, singleFrame = false) {
        this.setParameterValue(name, ANIM_PARAMETER_TRIGGER, true);
        if (singleFrame) {
            this._consumedTriggers.add(name);
        }
    }
    /**
     * Resets the value of a trigger parameter that was defined in the animation components state
     * graph to false.
     *
     * @param {string} name - The name of the parameter to set.
     */ resetTrigger(name) {
        this.setParameterValue(name, ANIM_PARAMETER_TRIGGER, false);
    }
    onBeforeRemove() {
        if (Number.isFinite(this._stateGraphAsset)) {
            const stateGraphAsset = this.system.app.assets.get(this._stateGraphAsset);
            stateGraphAsset.off('change', this._onStateGraphAssetChangeEvent, this);
        }
    }
    update(dt) {
        for(let i = 0; i < this.layers.length; i++){
            this.layers[i].update(dt * this.speed);
        }
        this._consumedTriggers.forEach((trigger)=>{
            this.parameters[trigger].value = false;
        });
        this._consumedTriggers.clear();
    }
    resolveDuplicatedEntityReferenceProperties(oldAnim, duplicatedIdsMap) {
        if (oldAnim.rootBone && duplicatedIdsMap[oldAnim.rootBone.getGuid()]) {
            this.rootBone = duplicatedIdsMap[oldAnim.rootBone.getGuid()];
        } else {
            this.rebind();
        }
    }
    constructor(...args){
        super(...args), /** @private */ this._stateGraphAsset = null, /** @private */ this._animationAssets = {}, /** @private */ this._speed = 1, /** @private */ this._activate = true, /** @private */ this._playing = false, /** @private */ this._rootBone = null, /** @private */ this._stateGraph = null, /** @private */ this._layers = [], /** @private */ this._layerIndices = {}, /** @private */ this._parameters = {}, /** @private */ this._targets = {}, /** @private */ this._consumedTriggers = new Set(), /** @private */ this._normalizeWeights = false, /**
     * Returns the parameter object for the specified parameter name. This function is anonymous so that it can be passed to the AnimController
     * while still being called in the scope of the AnimComponent.
     *
     * @param {string} name - The name of the parameter to return the value of.
     * @returns {object} The parameter object.
     * @private
     */ this.findParameter = (name)=>{
            return this._parameters[name];
        }, /**
     * Sets a trigger parameter as having been used by a transition. This function is anonymous so that it can be passed to the AnimController
     * while still being called in the scope of the AnimComponent.
     *
     * @param {string} name - The name of the trigger to set as consumed.
     * @private
     */ this.consumeTrigger = (name)=>{
            this._consumedTriggers.add(name);
        };
    }
}

class AnimComponentData {
    constructor(){
        this.enabled = true;
    }
}

/**
 * @import { AppBase } from '../../app-base.js'
 */ const _schema$3 = [
    'enabled'
];
/**
 * The AnimComponentSystem manages creating and deleting AnimComponents.
 *
 * @category Animation
 */ class AnimComponentSystem extends ComponentSystem {
    /**
     * Create an AnimComponentSystem instance.
     *
     * @param {AppBase} app - The application managing this system.
     * @ignore
     */ constructor(app){
        super(app);
        this.id = 'anim';
        this.ComponentType = AnimComponent;
        this.DataType = AnimComponentData;
        this.schema = _schema$3;
        this.on('beforeremove', this.onBeforeRemove, this);
        this.app.systems.on('animationUpdate', this.onAnimationUpdate, this);
    }
    initializeComponentData(component, data, properties) {
        super.initializeComponentData(component, data, _schema$3);
        const complexProperties = [
            'animationAssets',
            'stateGraph',
            'layers',
            'masks'
        ];
        Object.keys(data).forEach((key)=>{
            // these properties will be initialized manually below
            if (complexProperties.includes(key)) return;
            component[key] = data[key];
        });
        if (data.stateGraph) {
            component.stateGraph = data.stateGraph;
            component.loadStateGraph(component.stateGraph);
        }
        if (data.layers) {
            data.layers.forEach((layer, i)=>{
                layer._controller.states.forEach((stateKey)=>{
                    layer._controller._states[stateKey]._animationList.forEach((node)=>{
                        if (!node.animTrack || node.animTrack === AnimTrack.EMPTY) {
                            const animationAsset = this.app.assets.get(layer._component._animationAssets[`${layer.name}:${node.name}`].asset);
                            // If there is an animation asset that hasn't been loaded, assign it once it has loaded. If it is already loaded it will be assigned already.
                            if (animationAsset && !animationAsset.loaded) {
                                animationAsset.once('load', ()=>{
                                    component.layers[i].assignAnimation(node.name, animationAsset.resource);
                                });
                            }
                        } else {
                            component.layers[i].assignAnimation(node.name, node.animTrack);
                        }
                    });
                });
            });
        }
        if (data.animationAssets) {
            component.animationAssets = Object.assign(component.animationAssets, data.animationAssets);
        }
        if (data.masks) {
            Object.keys(data.masks).forEach((key)=>{
                if (component.layers[key]) {
                    const maskData = data.masks[key].mask;
                    const mask = {};
                    Object.keys(maskData).forEach((maskKey)=>{
                        mask[decodeURI(maskKey)] = maskData[maskKey];
                    });
                    component.layers[key].mask = mask;
                }
            });
        }
    }
    onAnimationUpdate(dt) {
        const components = this.store;
        for(const id in components){
            if (components.hasOwnProperty(id)) {
                const component = components[id].entity.anim;
                const componentData = component.data;
                if (componentData.enabled && component.entity.enabled && component.playing) {
                    component.update(dt);
                }
            }
        }
    }
    cloneComponent(entity, clone) {
        let masks;
        // If the component animates from the components entity, any layer mask hierarchy should be
        // updated from the old entity to the cloned entity.
        if (!entity.anim.rootBone || entity.anim.rootBone === entity) {
            masks = {};
            entity.anim.layers.forEach((layer, i)=>{
                if (layer.mask) {
                    const mask = {};
                    Object.keys(layer.mask).forEach((path)=>{
                        // The base of all mask paths should be mapped from the previous entity to the cloned entity
                        const pathArr = path.split('/');
                        pathArr.shift();
                        const clonePath = [
                            clone.name,
                            ...pathArr
                        ].join('/');
                        mask[clonePath] = layer.mask[path];
                    });
                    masks[i] = {
                        mask
                    };
                }
            });
        }
        const data = {
            enabled: entity.anim.enabled,
            stateGraphAsset: entity.anim.stateGraphAsset,
            animationAssets: entity.anim.animationAssets,
            speed: entity.anim.speed,
            activate: entity.anim.activate,
            playing: entity.anim.playing,
            rootBone: entity.anim.rootBone,
            stateGraph: entity.anim.stateGraph,
            layers: entity.anim.layers,
            layerIndices: entity.anim.layerIndices,
            parameters: entity.anim.parameters,
            normalizeWeights: entity.anim.normalizeWeights,
            masks
        };
        return this.addComponent(clone, data);
    }
    onBeforeRemove(entity, component) {
        component.onBeforeRemove();
    }
    destroy() {
        super.destroy();
        this.app.systems.off('animationUpdate', this.onAnimationUpdate, this);
    }
}
Component._buildAccessors(AnimComponent.prototype, _schema$3);

// class used to hold primitives in the device cache
class PrimitivesCache {
    // destroy all created primitives when the device is destroyed
    destroy(device) {
        this.map.forEach((primData)=>primData.mesh.destroy());
    }
    constructor(){
        this.map = new Map();
    }
}
const _primitivesCache = new DeviceCache();
// returns Primitive data, used by ModelComponent and RenderComponent
const getShapePrimitive = (device, type)=>{
    // cache for the device
    const cache = _primitivesCache.get(device, ()=>{
        return new PrimitivesCache();
    });
    let primData = cache.map.get(type);
    // not in cache, create new
    if (!primData) {
        let mesh, area;
        switch(type){
            case 'box':
                mesh = Mesh.fromGeometry(device, new BoxGeometry());
                area = {
                    x: 2,
                    y: 2,
                    z: 2,
                    uv: 2.0 / 3
                };
                break;
            case 'capsule':
                mesh = Mesh.fromGeometry(device, new CapsuleGeometry({
                    radius: 0.5,
                    height: 2
                }));
                area = {
                    x: Math.PI * 2,
                    y: Math.PI,
                    z: Math.PI * 2,
                    uv: 1.0 / 3 + 1.0 / 3 / 3 * 2
                };
                break;
            case 'cone':
                mesh = Mesh.fromGeometry(device, new ConeGeometry({
                    baseRadius: 0.5,
                    peakRadius: 0,
                    height: 1
                }));
                area = {
                    x: 2.54,
                    y: 2.54,
                    z: 2.54,
                    uv: 1.0 / 3 + 1.0 / 3 / 3
                };
                break;
            case 'cylinder':
                mesh = Mesh.fromGeometry(device, new CylinderGeometry({
                    radius: 0.5,
                    height: 1
                }));
                area = {
                    x: Math.PI,
                    y: 0.79 * 2,
                    z: Math.PI,
                    uv: 1.0 / 3 + 1.0 / 3 / 3 * 2
                };
                break;
            case 'plane':
                mesh = Mesh.fromGeometry(device, new PlaneGeometry({
                    halfExtents: new Vec2(0.5, 0.5),
                    widthSegments: 1,
                    lengthSegments: 1
                }));
                area = {
                    x: 0,
                    y: 1,
                    z: 0,
                    uv: 1
                };
                break;
            case 'sphere':
                mesh = Mesh.fromGeometry(device, new SphereGeometry({
                    radius: 0.5
                }));
                area = {
                    x: Math.PI,
                    y: Math.PI,
                    z: Math.PI,
                    uv: 1
                };
                break;
            case 'torus':
                mesh = Mesh.fromGeometry(device, new TorusGeometry({
                    tubeRadius: 0.2,
                    ringRadius: 0.3
                }));
                area = {
                    x: Math.PI * 0.5 * 0.5 - Math.PI * 0.1 * 0.1,
                    y: 0.4,
                    z: 0.4,
                    uv: 1
                };
                break;
            default:
                throw new Error(`Invalid primitive type: ${type}`);
        }
        // inc reference to keep primitive alive
        mesh.incRefCount();
        primData = {
            mesh: mesh,
            area: area
        };
        cache.map.set(type, primData);
    }
    return primData;
};

// Class used as an entry in the ref-counted skin instance cache
class SkinInstanceCachedObject extends RefCountedObject {
    constructor(skin, skinInstance){
        super();
        this.skin = skin;
        this.skinInstance = skinInstance;
    }
}
// Pure static class, implementing the cache of skin instances used by render component.
class SkinInstanceCache {
    static{
        // map of SkinInstances allowing those to be shared between
        // (specifically a single glb with multiple render components)
        // It maps a rootBone to an array of SkinInstanceCachedObject
        // this allows us to find if a skin instance already exists for a rootbone, and a specific skin
        this._skinInstanceCache = new Map();
    }
    // function that logs out the state of the skin instances cache
    static logCachedSkinInstances() {
        console.log('CachedSkinInstances');
        SkinInstanceCache._skinInstanceCache.forEach((array, rootBone)=>{
            console.log(`${rootBone.name}: Array(${array.length})`);
            for(let i = 0; i < array.length; i++){
                console.log(`  ${i}: RefCount ${array[i].refCount}`);
            }
        });
    }
    // returns cached or creates a skin instance for the skin and a rootBone, to be used by render component
    // on the specified entity
    static createCachedSkinInstance(skin, rootBone, entity) {
        // try and get skin instance from the cache
        let skinInst = SkinInstanceCache.getCachedSkinInstance(skin, rootBone);
        // don't have skin instance for this skin
        if (!skinInst) {
            skinInst = new SkinInstance(skin);
            skinInst.resolve(rootBone, entity);
            // add it to the cache
            SkinInstanceCache.addCachedSkinInstance(skin, rootBone, skinInst);
        }
        return skinInst;
    }
    // returns already created skin instance from skin, for use on the rootBone
    // ref count of existing skinInstance is increased
    static getCachedSkinInstance(skin, rootBone) {
        let skinInstance = null;
        // get an array of cached object for the rootBone
        const cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);
        if (cachedObjArray) {
            // find matching skin
            const cachedObj = cachedObjArray.find((element)=>element.skin === skin);
            if (cachedObj) {
                cachedObj.incRefCount();
                skinInstance = cachedObj.skinInstance;
            }
        }
        return skinInstance;
    }
    // adds skin instance to the cache, and increases ref count on it
    static addCachedSkinInstance(skin, rootBone, skinInstance) {
        // get an array for the rootBone
        let cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);
        if (!cachedObjArray) {
            cachedObjArray = [];
            SkinInstanceCache._skinInstanceCache.set(rootBone, cachedObjArray);
        }
        // find entry for the skin
        let cachedObj = cachedObjArray.find((element)=>element.skin === skin);
        if (!cachedObj) {
            cachedObj = new SkinInstanceCachedObject(skin, skinInstance);
            cachedObjArray.push(cachedObj);
        }
        cachedObj.incRefCount();
    }
    // removes skin instance from the cache. This decreases ref count, and when that reaches 0 it gets destroyed
    static removeCachedSkinInstance(skinInstance) {
        if (skinInstance) {
            const rootBone = skinInstance.rootBone;
            if (rootBone) {
                // an array for boot bone
                const cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);
                if (cachedObjArray) {
                    // actual skin instance
                    const cachedObjIndex = cachedObjArray.findIndex((element)=>element.skinInstance === skinInstance);
                    if (cachedObjIndex >= 0) {
                        // dec ref on the object
                        const cachedObj = cachedObjArray[cachedObjIndex];
                        cachedObj.decRefCount();
                        // last reference, needs to be destroyed
                        if (cachedObj.refCount === 0) {
                            cachedObjArray.splice(cachedObjIndex, 1);
                            // if the array is empty
                            if (!cachedObjArray.length) {
                                SkinInstanceCache._skinInstanceCache.delete(rootBone);
                            }
                            // destroy the skin instance
                            if (skinInstance) {
                                skinInstance.destroy();
                                cachedObj.skinInstance = null;
                            }
                        }
                    }
                }
            }
        }
    }
}

/**
 * @import { Asset } from './asset.js'
 * @import { AssetRegistry } from './asset-registry.js'
 * @import { EventHandle } from '../../core/event-handle.js'
 */ /**
 * An object that manages the case where an object holds a reference to an asset and needs to be
 * notified when changes occur in the asset. e.g. notifications include load, add and remove
 * events.
 *
 * @category Asset
 */ class AssetReference {
    /**
     * Create a new AssetReference instance.
     *
     * @param {string} propertyName - The name of the property that the asset is stored under,
     * passed into callbacks to enable updating.
     * @param {Asset|object} parent - The parent object that contains the asset reference, passed
     * into callbacks to enable updating. Currently an asset, but could be component or other.
     * @param {AssetRegistry} registry - The asset registry that stores all assets.
     * @param {object} callbacks - A set of functions called when the asset state changes: load,
     * add, remove.
     * @param {object} [callbacks.load] - The function called when the asset loads
     * load(propertyName, parent, asset).
     * @param {object} [callbacks.add] - The function called when the asset is added to the
     * registry add(propertyName, parent, asset).
     * @param {object} [callbacks.remove] - The function called when the asset is remove from the
     * registry remove(propertyName, parent, asset).
     * @param {object} [callbacks.unload] - The function called when the asset is unloaded
     * unload(propertyName, parent, asset).
     * @param {object} [scope] - The scope to call the callbacks in.
     * @example
     * const reference = new pc.AssetReference('textureAsset', this, this.app.assets, {
     *     load: this.onTextureAssetLoad,
     *     add: this.onTextureAssetAdd,
     *     remove: this.onTextureAssetRemove
     * }, this);
     * reference.id = this.textureAsset.id;
     */ constructor(propertyName, parent, registry, callbacks, scope){
        /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLoadById = null;
        /**
     * @type {EventHandle|null}
     * @private
     */ this._evtUnloadById = null;
        /**
     * @type {EventHandle|null}
     * @private
     */ this._evtAddById = null;
        /**
     * @type {EventHandle|null}
     * @private
     */ this._evtRemoveById = null;
        /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLoadByUrl = null;
        /**
     * @type {EventHandle|null}
     * @private
     */ this._evtAddByUrl = null;
        /**
     * @type {EventHandle|null}
     * @private
     */ this._evtRemoveByUrl = null;
        this.propertyName = propertyName;
        this.parent = parent;
        this._scope = scope;
        this._registry = registry;
        this.id = null;
        this.url = null;
        this.asset = null;
        this._onAssetLoad = callbacks.load;
        this._onAssetAdd = callbacks.add;
        this._onAssetRemove = callbacks.remove;
        this._onAssetUnload = callbacks.unload;
    }
    /**
     * Sets the asset id which this references. One of either id or url must be set to
     * initialize an asset reference.
     *
     * @type {number}
     */ set id(value) {
        if (this.url) throw Error('Can\'t set id and url');
        this._unbind();
        this._id = value;
        this.asset = this._registry.get(this._id);
        this._bind();
    }
    /**
     * Gets the asset id which this references.
     *
     * @type {number}
     */ get id() {
        return this._id;
    }
    /**
     * Sets the asset url which this references. One of either id or url must be called to
     * initialize an asset reference.
     *
     * @type {string|null}
     */ set url(value) {
        if (this.id) throw Error('Can\'t set id and url');
        this._unbind();
        this._url = value;
        this.asset = this._registry.getByUrl(this._url);
        this._bind();
    }
    /**
     * Gets the asset url which this references.
     *
     * @type {string|null}
     */ get url() {
        return this._url;
    }
    _bind() {
        if (this.id) {
            if (this._onAssetLoad) this._evtLoadById = this._registry.on(`load:${this.id}`, this._onLoad, this);
            if (this._onAssetAdd) this._evtAddById = this._registry.once(`add:${this.id}`, this._onAdd, this);
            if (this._onAssetRemove) this._evtRemoveById = this._registry.on(`remove:${this.id}`, this._onRemove, this);
            if (this._onAssetUnload) this._evtUnloadById = this._registry.on(`unload:${this.id}`, this._onUnload, this);
        }
        if (this.url) {
            if (this._onAssetLoad) this._evtLoadByUrl = this._registry.on(`load:url:${this.url}`, this._onLoad, this);
            if (this._onAssetAdd) this._evtAddByUrl = this._registry.once(`add:url:${this.url}`, this._onAdd, this);
            if (this._onAssetRemove) this._evtRemoveByUrl = this._registry.on(`remove:url:${this.url}`, this._onRemove, this);
        }
    }
    _unbind() {
        if (this.id) {
            this._evtLoadById?.off();
            this._evtLoadById = null;
            this._evtAddById?.off();
            this._evtAddById = null;
            this._evtRemoveById?.off();
            this._evtRemoveById = null;
            this._evtUnloadById?.off();
            this._evtUnloadById = null;
        }
        if (this.url) {
            this._evtLoadByUrl?.off();
            this._evtLoadByUrl = null;
            this._evtAddByUrl?.off();
            this._evtAddByUrl = null;
            this._evtRemoveByUrl?.off();
            this._evtRemoveByUrl = null;
        }
    }
    _onLoad(asset) {
        this._onAssetLoad.call(this._scope, this.propertyName, this.parent, asset);
    }
    _onAdd(asset) {
        this.asset = asset;
        this._onAssetAdd.call(this._scope, this.propertyName, this.parent, asset);
    }
    _onRemove(asset) {
        this._onAssetRemove.call(this._scope, this.propertyName, this.parent, asset);
        this.asset = null;
    }
    _onUnload(asset) {
        this._onAssetUnload.call(this._scope, this.propertyName, this.parent, asset);
    }
}

/**
 * @import { BoundingBox } from '../../../core/shape/bounding-box.js'
 * @import { Entity } from '../../entity.js'
 * @import { EventHandle } from '../../../core/event-handle.js'
 * @import { Material } from '../../../scene/materials/material.js'
 * @import { RenderComponentSystem } from './system.js'
 */ /**
 * The RenderComponent enables an {@link Entity} to render 3D meshes. The {@link type} property can
 * be set to one of several predefined shapes (such as `box`, `sphere`, `cone` and so on).
 * Alternatively, the component can be configured to manage an arbitrary array of
 * {@link MeshInstance}s. These can either be created programmatically or loaded from an
 * {@link Asset}.
 *
 * The {@link MeshInstance}s managed by this component are positioned, rotated, and scaled in world
 * space by the world transformation matrix of the owner {@link Entity}. This world matrix is
 * derived by combining the entity's local transformation (position, rotation, and scale) with the
 * world transformation matrix of its parent entity in the scene hierarchy.
 *
 * You should never need to use the RenderComponent constructor directly. To add a RenderComponent
 * to an Entity, use {@link Entity#addComponent}:
 *
 * ```javascript
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     type: 'box'
 * });
 * ```
 *
 * Once the RenderComponent is added to the entity, you can access it via the {@link Entity#render}
 * property:
 *
 * ```javascript
 * entity.render.type = 'capsule';  // Set the render component's type
 *
 * console.log(entity.render.type); // Get the render component's type and print it
 * ```
 *
 * Relevant Engine API examples:
 *
 * - [Loading Render Assets](https://playcanvas.github.io/#/graphics/render-asset)
 * - [Primitive Shapes](https://playcanvas.github.io/#/graphics/shapes)
 * - [Spinning Cube](https://playcanvas.github.io/#/misc/hello-world)
 *
 * @category Graphics
 */ class RenderComponent extends Component {
    /**
     * Create a new RenderComponent.
     *
     * @param {RenderComponentSystem} system - The ComponentSystem that created this Component.
     * @param {Entity} entity - The Entity that this Component is attached to.
     */ constructor(system, entity){
        super(system, entity), /**
     * @type {'asset'|'box'|'capsule'|'cone'|'cylinder'|'plane'|'sphere'|'torus'}
     * @private
     */ this._type = 'asset', /** @private */ this._castShadows = true, /** @private */ this._receiveShadows = true, /** @private */ this._castShadowsLightmap = true, /** @private */ this._lightmapped = false, /** @private */ this._lightmapSizeMultiplier = 1, /**
     * Mark meshes as non-movable (optimization).
     *
     * @type {boolean}
     */ this.isStatic = false, /** @private */ this._batchGroupId = -1, /** @private */ this._layers = [
            LAYERID_WORLD
        ] // assign to the default world layer
        , /** @private */ this._renderStyle = RENDERSTYLE_SOLID, /**
     * @type {MeshInstance[]}
     * @private
     */ this._meshInstances = [], /**
     * @type {BoundingBox|null}
     * @private
     */ this._customAabb = null, /**
     * Used by lightmapper.
     *
     * @type {{x: number, y: number, z: number, uv: number}|null}
     * @ignore
     */ this._area = null, /**
     * @type {AssetReference[]}
     * @private
     */ this._materialReferences = [], /**
     * A reference to the entity to be used as the root bone for any skinned meshes that
     * are rendered by this component.
     *
     * @type {Entity|null}
     * @private
     */ this._rootBone = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayersChanged = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerAdded = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerRemoved = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtSetMeshes = null;
        // the entity that represents the root bone if this render component has skinned meshes
        // render asset reference
        this._assetReference = new AssetReference('asset', this, system.app.assets, {
            add: this._onRenderAssetAdded,
            load: this._onRenderAssetLoad,
            remove: this._onRenderAssetRemove,
            unload: this._onRenderAssetUnload
        }, this);
        this._material = system.defaultMaterial;
        // handle events when the entity is directly (or indirectly as a child of sub-hierarchy)
        // added or removed from the parent
        entity.on('remove', this.onRemoveChild, this);
        entity.on('removehierarchy', this.onRemoveChild, this);
        entity.on('insert', this.onInsertChild, this);
        entity.on('inserthierarchy', this.onInsertChild, this);
    }
    /**
     * Sets the render style of this component's {@link MeshInstance}s. Can be:
     *
     * - {@link RENDERSTYLE_SOLID}
     * - {@link RENDERSTYLE_WIREFRAME}
     * - {@link RENDERSTYLE_POINTS}
     *
     * Defaults to {@link RENDERSTYLE_SOLID}.
     *
     * @type {number}
     */ set renderStyle(renderStyle) {
        if (this._renderStyle !== renderStyle) {
            this._renderStyle = renderStyle;
            MeshInstance._prepareRenderStyleForArray(this._meshInstances, renderStyle);
        }
    }
    /**
     * Gets the render style of this component's {@link MeshInstance}s.
     *
     * @type {number}
     */ get renderStyle() {
        return this._renderStyle;
    }
    /**
     * Sets the custom object space bounding box that is used for visibility culling of attached
     * mesh instances. This is an optimization, allowing an oversized bounding box to be specified
     * for skinned characters in order to avoid per frame bounding box computations based on bone
     * positions.
     *
     * @type {BoundingBox|null}
     */ set customAabb(value) {
        this._customAabb = value;
        // set it on meshInstances
        const mi = this._meshInstances;
        if (mi) {
            for(let i = 0; i < mi.length; i++){
                mi[i].setCustomAabb(this._customAabb);
            }
        }
    }
    /**
     * Gets the custom object space bounding box that is used for visibility culling of attached
     * mesh instances.
     *
     * @type {BoundingBox|null}
     */ get customAabb() {
        return this._customAabb;
    }
    /**
     * Sets the type of the component, determining the source of the geometry to be rendered.
     * The geometry, whether it's a primitive shape or originates from an asset, is rendered
     * using the owning entity's final world transform. This world transform is calculated by
     * concatenating (multiplying) the local transforms (position, rotation, scale) of the
     * entity and all its ancestors in the scene hierarchy. This process positions, orientates,
     * and scales the geometry in world space.
     *
     * Can be one of the following values:
     *
     * - **"asset"**: Renders geometry defined in an {@link Asset} of type `render`. This asset,
     *   assigned to the {@link asset} property, contains one or more {@link MeshInstance}s.
     *   Alternatively, {@link meshInstances} can be set programmatically.
     * - **"box"**: A unit cube (sides of length 1) centered at the local space origin.
     * - **"capsule"**: A shape composed of a cylinder and two hemispherical caps that is aligned
     *   with the local Y-axis. It is centered at the local space origin and has an unscaled height
     *   of 2 and a radius of 0.5.
     * - **"cone"**: A cone aligned with the local Y-axis. It is centered at the local space
     *   origin, with its base in the local XZ plane at Y = -0.5 and its tip at Y = +0.5. It has
     *   an unscaled height of 1 and a base radius of 0.5.
     * - **"cylinder"**: A cylinder aligned with the local Y-axis. It is centered at the local
     *   space origin with an unscaled height of 1 and a radius of 0.5.
     * - **"plane"**: A flat plane in the local XZ plane at Y = 0 (normal along +Y). It is
     *   centered at the local space origin with unscaled dimensions of 1x1 units along local X and
     *   Z axes.
     * - **"sphere"**: A sphere with a radius of 0.5. It is centered at the local space origin and
     *   has poles at Y = -0.5 and Y = +0.5.
     * - **"torus"**: A doughnut shape lying in the local XZ plane at Y = 0. It is centered at
     *   the local space origin with a tube radius of 0.2 and a ring radius of 0.3.
     *
     * @type {'asset'|'box'|'capsule'|'cone'|'cylinder'|'plane'|'sphere'|'torus'}
     */ set type(value) {
        if (this._type !== value) {
            this._area = null;
            this._type = value;
            this.destroyMeshInstances();
            if (value !== 'asset') {
                let material = this._material;
                if (!material || material === this.system.defaultMaterial) {
                    material = this._materialReferences[0] && this._materialReferences[0].asset && this._materialReferences[0].asset.resource;
                }
                const primData = getShapePrimitive(this.system.app.graphicsDevice, value);
                this._area = primData.area;
                this.meshInstances = [
                    new MeshInstance(primData.mesh, material || this.system.defaultMaterial, this.entity)
                ];
            }
        }
    }
    /**
     * Gets the type of the component.
     *
     * @type {'asset'|'box'|'capsule'|'cone'|'cylinder'|'plane'|'sphere'|'torus'}
     */ get type() {
        return this._type;
    }
    /**
     * Sets the array of meshInstances contained in the component.
     *
     * @type {MeshInstance[]}
     */ set meshInstances(value) {
        Debug.assert(Array.isArray(value), 'MeshInstances set to a Render component must be an array.');
        this.destroyMeshInstances();
        this._meshInstances = value;
        if (this._meshInstances) {
            const mi = this._meshInstances;
            for(let i = 0; i < mi.length; i++){
                // if mesh instance was created without a node, assign it here
                if (!mi[i].node) {
                    mi[i].node = this.entity;
                }
                mi[i].castShadow = this._castShadows;
                mi[i].receiveShadow = this._receiveShadows;
                mi[i].renderStyle = this._renderStyle;
                mi[i].setLightmapped(this._lightmapped);
                mi[i].setCustomAabb(this._customAabb);
            }
            if (this.enabled && this.entity.enabled) {
                this.addToLayers();
            }
        }
    }
    /**
     * Gets the array of meshInstances contained in the component.
     *
     * @type {MeshInstance[]}
     */ get meshInstances() {
        return this._meshInstances;
    }
    /**
     * Sets whether the component is affected by the runtime lightmapper. If true, the meshes will
     * be lightmapped after using lightmapper.bake().
     *
     * @type {boolean}
     */ set lightmapped(value) {
        if (value !== this._lightmapped) {
            this._lightmapped = value;
            const mi = this._meshInstances;
            if (mi) {
                for(let i = 0; i < mi.length; i++){
                    mi[i].setLightmapped(value);
                }
            }
        }
    }
    /**
     * Gets whether the component is affected by the runtime lightmapper.
     *
     * @type {boolean}
     */ get lightmapped() {
        return this._lightmapped;
    }
    /**
     * Sets whether attached meshes will cast shadows for lights that have shadow casting enabled.
     *
     * @type {boolean}
     */ set castShadows(value) {
        if (this._castShadows !== value) {
            const mi = this._meshInstances;
            if (mi) {
                const layers = this.layers;
                const scene = this.system.app.scene;
                if (this._castShadows && !value) {
                    for(let i = 0; i < layers.length; i++){
                        const layer = scene.layers.getLayerById(this.layers[i]);
                        if (layer) {
                            layer.removeShadowCasters(mi);
                        }
                    }
                }
                for(let i = 0; i < mi.length; i++){
                    mi[i].castShadow = value;
                }
                if (!this._castShadows && value) {
                    for(let i = 0; i < layers.length; i++){
                        const layer = scene.layers.getLayerById(layers[i]);
                        if (layer) {
                            layer.addShadowCasters(mi);
                        }
                    }
                }
            }
            this._castShadows = value;
        }
    }
    /**
     * Gets whether attached meshes will cast shadows for lights that have shadow casting enabled.
     *
     * @type {boolean}
     */ get castShadows() {
        return this._castShadows;
    }
    /**
     * Sets whether shadows will be cast on attached meshes.
     *
     * @type {boolean}
     */ set receiveShadows(value) {
        if (this._receiveShadows !== value) {
            this._receiveShadows = value;
            const mi = this._meshInstances;
            if (mi) {
                for(let i = 0; i < mi.length; i++){
                    mi[i].receiveShadow = value;
                }
            }
        }
    }
    /**
     * Gets whether shadows will be cast on attached meshes.
     *
     * @type {boolean}
     */ get receiveShadows() {
        return this._receiveShadows;
    }
    /**
     * Sets whether meshes instances will cast shadows when rendering lightmaps.
     *
     * @type {boolean}
     */ set castShadowsLightmap(value) {
        this._castShadowsLightmap = value;
    }
    /**
     * Gets whether meshes instances will cast shadows when rendering lightmaps.
     *
     * @type {boolean}
     */ get castShadowsLightmap() {
        return this._castShadowsLightmap;
    }
    /**
     * Sets the lightmap resolution multiplier.
     *
     * @type {number}
     */ set lightmapSizeMultiplier(value) {
        this._lightmapSizeMultiplier = value;
    }
    /**
     * Gets the lightmap resolution multiplier.
     *
     * @type {number}
     */ get lightmapSizeMultiplier() {
        return this._lightmapSizeMultiplier;
    }
    /**
     * Sets the array of layer IDs ({@link Layer#id}) to which the mesh instances belong. Don't
     * push, pop, splice or modify this array. If you want to change it, set a new one instead.
     *
     * @type {number[]}
     */ set layers(value) {
        const layers = this.system.app.scene.layers;
        let layer;
        if (this._meshInstances) {
            // remove all mesh instances from old layers
            for(let i = 0; i < this._layers.length; i++){
                layer = layers.getLayerById(this._layers[i]);
                if (layer) {
                    layer.removeMeshInstances(this._meshInstances);
                }
            }
        }
        // set the layer list
        this._layers.length = 0;
        for(let i = 0; i < value.length; i++){
            this._layers[i] = value[i];
        }
        // don't add into layers until we're enabled
        if (!this.enabled || !this.entity.enabled || !this._meshInstances) return;
        // add all mesh instances to new layers
        for(let i = 0; i < this._layers.length; i++){
            layer = layers.getLayerById(this._layers[i]);
            if (layer) {
                layer.addMeshInstances(this._meshInstances);
            }
        }
    }
    /**
     * Gets the array of layer IDs ({@link Layer#id}) to which the mesh instances belong.
     *
     * @type {number[]}
     */ get layers() {
        return this._layers;
    }
    /**
     * Sets the batch group for the mesh instances in this component (see {@link BatchGroup}).
     * Default is -1 (no group).
     *
     * @type {number}
     */ set batchGroupId(value) {
        if (this._batchGroupId !== value) {
            if (this.entity.enabled && this._batchGroupId >= 0) {
                this.system.app.batcher?.remove(BatchGroup.RENDER, this.batchGroupId, this.entity);
            }
            if (this.entity.enabled && value >= 0) {
                this.system.app.batcher?.insert(BatchGroup.RENDER, value, this.entity);
            }
            if (value < 0 && this._batchGroupId >= 0 && this.enabled && this.entity.enabled) {
                // re-add render to scene, in case it was removed by batching
                this.addToLayers();
            }
            this._batchGroupId = value;
        }
    }
    /**
     * Gets the batch group for the mesh instances in this component (see {@link BatchGroup}).
     *
     * @type {number}
     */ get batchGroupId() {
        return this._batchGroupId;
    }
    /**
     * Sets the material {@link Material} that will be used to render the component. The material
     * is ignored for renders of type 'asset'.
     *
     * @type {Material}
     */ set material(value) {
        if (this._material !== value) {
            this._material = value;
            if (this._meshInstances && this._type !== 'asset') {
                for(let i = 0; i < this._meshInstances.length; i++){
                    this._meshInstances[i].material = value;
                }
            }
        }
    }
    /**
     * Gets the material {@link Material} that will be used to render the component.
     *
     * @type {Material}
     */ get material() {
        return this._material;
    }
    /**
     * Sets the material assets that will be used to render the component. Each material
     * corresponds to the respective mesh instance.
     *
     * @type {Asset[]|number[]}
     */ set materialAssets(value = []) {
        if (this._materialReferences.length > value.length) {
            for(let i = value.length; i < this._materialReferences.length; i++){
                this._materialReferences[i].id = null;
            }
            this._materialReferences.length = value.length;
        }
        for(let i = 0; i < value.length; i++){
            if (!this._materialReferences[i]) {
                this._materialReferences.push(new AssetReference(i, this, this.system.app.assets, {
                    add: this._onMaterialAdded,
                    load: this._onMaterialLoad,
                    remove: this._onMaterialRemove,
                    unload: this._onMaterialUnload
                }, this));
            }
            if (value[i]) {
                const id = value[i] instanceof Asset ? value[i].id : value[i];
                if (this._materialReferences[i].id !== id) {
                    this._materialReferences[i].id = id;
                }
                if (this._materialReferences[i].asset) {
                    this._onMaterialAdded(i, this, this._materialReferences[i].asset);
                }
            } else {
                this._materialReferences[i].id = null;
                if (this._meshInstances[i]) {
                    this._meshInstances[i].material = this.system.defaultMaterial;
                }
            }
        }
    }
    /**
     * Gets the material assets that will be used to render the component.
     *
     * @type {Asset[]|number[]}
     */ get materialAssets() {
        return this._materialReferences.map((ref)=>{
            return ref.id;
        });
    }
    /**
     * Sets the render asset (or asset id) for the render component. This only applies to render components with
     * type 'asset'.
     *
     * @type {Asset|number}
     */ set asset(value) {
        const id = value instanceof Asset ? value.id : value;
        if (this._assetReference.id === id) return;
        if (this._assetReference.asset && this._assetReference.asset.resource) {
            this._onRenderAssetRemove();
        }
        this._assetReference.id = id;
        if (this._assetReference.asset) {
            this._onRenderAssetAdded();
        }
    }
    /**
     * Gets the render asset id for the render component.
     *
     * @type {number}
     */ get asset() {
        return this._assetReference.id;
    }
    /**
     * Assign asset id to the component, without updating the component with the new asset.
     * This can be used to assign the asset id to already fully created component.
     *
     * @param {Asset|number} asset - The render asset or asset id to assign.
     * @ignore
     */ assignAsset(asset) {
        const id = asset instanceof Asset ? asset.id : asset;
        this._assetReference.id = id;
    }
    /**
     * Sets the root bone entity (or entity guid) for the render component.
     *
     * @type {Entity|string|null}
     */ set rootBone(value) {
        if (this._rootBone !== value) {
            const isString = typeof value === 'string';
            if (this._rootBone && isString && this._rootBone.getGuid() === value) {
                return;
            }
            if (this._rootBone) {
                this._clearSkinInstances();
            }
            if (value instanceof GraphNode) {
                this._rootBone = value;
            } else if (isString) {
                this._rootBone = this.system.app.getEntityFromIndex(value) || null;
                if (!this._rootBone) {
                    Debug.warn('Failed to find rootBone Entity by GUID');
                }
            } else {
                this._rootBone = null;
            }
            if (this._rootBone) {
                this._cloneSkinInstances();
            }
        }
    }
    /**
     * Gets the root bone entity for the render component.
     *
     * @type {Entity|null}
     */ get rootBone() {
        return this._rootBone;
    }
    /** @private */ destroyMeshInstances() {
        const meshInstances = this._meshInstances;
        if (meshInstances) {
            this.removeFromLayers();
            // destroy mesh instances separately to allow them to be removed from the cache
            this._clearSkinInstances();
            for(let i = 0; i < meshInstances.length; i++){
                meshInstances[i].destroy();
            }
            this._meshInstances.length = 0;
        }
    }
    /** @private */ addToLayers() {
        const layers = this.system.app.scene.layers;
        for(let i = 0; i < this._layers.length; i++){
            const layer = layers.getLayerById(this._layers[i]);
            if (layer) {
                layer.addMeshInstances(this._meshInstances);
            }
        }
    }
    removeFromLayers() {
        if (this._meshInstances && this._meshInstances.length) {
            const layers = this.system.app.scene.layers;
            for(let i = 0; i < this._layers.length; i++){
                const layer = layers.getLayerById(this._layers[i]);
                if (layer) {
                    layer.removeMeshInstances(this._meshInstances);
                }
            }
        }
    }
    /** @private */ onRemoveChild() {
        this.removeFromLayers();
    }
    /** @private */ onInsertChild() {
        if (this._meshInstances && this.enabled && this.entity.enabled) {
            this.addToLayers();
        }
    }
    onRemove() {
        this.destroyMeshInstances();
        this.asset = null;
        this.materialAsset = null;
        this._assetReference.id = null;
        for(let i = 0; i < this._materialReferences.length; i++){
            this._materialReferences[i].id = null;
        }
        this.entity.off('remove', this.onRemoveChild, this);
        this.entity.off('insert', this.onInsertChild, this);
    }
    onLayersChanged(oldComp, newComp) {
        this.addToLayers();
        oldComp.off('add', this.onLayerAdded, this);
        oldComp.off('remove', this.onLayerRemoved, this);
        newComp.on('add', this.onLayerAdded, this);
        newComp.on('remove', this.onLayerRemoved, this);
    }
    onLayerAdded(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index < 0) return;
        layer.addMeshInstances(this._meshInstances);
    }
    onLayerRemoved(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index < 0) return;
        layer.removeMeshInstances(this._meshInstances);
    }
    onEnable() {
        const app = this.system.app;
        const scene = app.scene;
        const layers = scene.layers;
        if (this._rootBone) {
            this._cloneSkinInstances();
        }
        this._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);
        if (layers) {
            this._evtLayerAdded = layers.on('add', this.onLayerAdded, this);
            this._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);
        }
        const isAsset = this._type === 'asset';
        if (this._meshInstances && this._meshInstances.length) {
            this.addToLayers();
        } else if (isAsset && this.asset) {
            this._onRenderAssetAdded();
        }
        // load materials
        for(let i = 0; i < this._materialReferences.length; i++){
            if (this._materialReferences[i].asset) {
                this.system.app.assets.load(this._materialReferences[i].asset);
            }
        }
        if (this._batchGroupId >= 0) {
            app.batcher?.insert(BatchGroup.RENDER, this.batchGroupId, this.entity);
        }
    }
    onDisable() {
        const app = this.system.app;
        const scene = app.scene;
        const layers = scene.layers;
        this._evtLayersChanged?.off();
        this._evtLayersChanged = null;
        if (this._rootBone) {
            this._clearSkinInstances();
        }
        if (layers) {
            this._evtLayerAdded?.off();
            this._evtLayerAdded = null;
            this._evtLayerRemoved?.off();
            this._evtLayerRemoved = null;
        }
        if (this._batchGroupId >= 0) {
            app.batcher?.remove(BatchGroup.RENDER, this.batchGroupId, this.entity);
        }
        this.removeFromLayers();
    }
    /**
     * Stop rendering {@link MeshInstance}s without removing them from the scene hierarchy. This
     * method sets the {@link MeshInstance#visible} property of every MeshInstance to false. Note,
     * this does not remove the mesh instances from the scene hierarchy or draw call list. So the
     * render component still incurs some CPU overhead.
     */ hide() {
        if (this._meshInstances) {
            for(let i = 0; i < this._meshInstances.length; i++){
                this._meshInstances[i].visible = false;
            }
        }
    }
    /**
     * Enable rendering of the component's {@link MeshInstance}s if hidden using
     * {@link RenderComponent#hide}. This method sets the {@link MeshInstance#visible} property on
     * all mesh instances to true.
     */ show() {
        if (this._meshInstances) {
            for(let i = 0; i < this._meshInstances.length; i++){
                this._meshInstances[i].visible = true;
            }
        }
    }
    _onRenderAssetAdded() {
        if (!this._assetReference.asset) return;
        if (this._assetReference.asset.resource) {
            this._onRenderAssetLoad();
        } else if (this.enabled && this.entity.enabled) {
            this.system.app.assets.load(this._assetReference.asset);
        }
    }
    _onRenderAssetLoad() {
        // remove existing instances
        this.destroyMeshInstances();
        if (this._assetReference.asset) {
            const render = this._assetReference.asset.resource;
            this._evtSetMeshes?.off();
            this._evtSetMeshes = render.on('set:meshes', this._onSetMeshes, this);
            if (render.meshes) {
                this._onSetMeshes(render.meshes);
            }
        }
    }
    _onSetMeshes(meshes) {
        this._cloneMeshes(meshes);
    }
    _clearSkinInstances() {
        for(let i = 0; i < this._meshInstances.length; i++){
            const meshInstance = this._meshInstances[i];
            // remove it from the cache
            SkinInstanceCache.removeCachedSkinInstance(meshInstance.skinInstance);
            meshInstance.skinInstance = null;
        }
    }
    _cloneSkinInstances() {
        if (this._meshInstances.length && this._rootBone instanceof GraphNode) {
            for(let i = 0; i < this._meshInstances.length; i++){
                const meshInstance = this._meshInstances[i];
                const mesh = meshInstance.mesh;
                // if skinned but does not have instance created yet
                if (mesh.skin && !meshInstance.skinInstance) {
                    meshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(mesh.skin, this._rootBone, this.entity);
                }
            }
        }
    }
    _cloneMeshes(meshes) {
        if (meshes && meshes.length) {
            // cloned mesh instances
            const meshInstances = [];
            for(let i = 0; i < meshes.length; i++){
                // mesh instance
                const mesh = meshes[i];
                const material = this._materialReferences[i] && this._materialReferences[i].asset && this._materialReferences[i].asset.resource;
                const meshInst = new MeshInstance(mesh, material || this.system.defaultMaterial, this.entity);
                meshInstances.push(meshInst);
                // morph instance
                if (mesh.morph) {
                    meshInst.morphInstance = new MorphInstance(mesh.morph);
                }
            }
            this.meshInstances = meshInstances;
            // try to create skin instances if rootBone has been set, otherwise this executes when rootBone is set later
            this._cloneSkinInstances();
        }
    }
    _onRenderAssetUnload() {
        // when unloading asset, only remove asset mesh instances (type could have been already changed to 'box' or similar)
        if (this._type === 'asset') {
            this.destroyMeshInstances();
        }
    }
    _onRenderAssetRemove() {
        this._evtSetMeshes?.off();
        this._evtSetMeshes = null;
        this._onRenderAssetUnload();
    }
    _onMaterialAdded(index, component, asset) {
        if (asset.resource) {
            this._onMaterialLoad(index, component, asset);
        } else {
            if (this.enabled && this.entity.enabled) {
                this.system.app.assets.load(asset);
            }
        }
    }
    _updateMainMaterial(index, material) {
        // first material for primitives can be accessed using material property, so set it up
        if (index === 0) {
            this.material = material;
        }
    }
    _onMaterialLoad(index, component, asset) {
        if (this._meshInstances[index]) {
            this._meshInstances[index].material = asset.resource;
        }
        this._updateMainMaterial(index, asset.resource);
    }
    _onMaterialRemove(index, component, asset) {
        if (this._meshInstances[index]) {
            this._meshInstances[index].material = this.system.defaultMaterial;
        }
        this._updateMainMaterial(index, this.system.defaultMaterial);
    }
    _onMaterialUnload(index, component, asset) {
        if (this._meshInstances[index]) {
            this._meshInstances[index].material = this.system.defaultMaterial;
        }
        this._updateMainMaterial(index, this.system.defaultMaterial);
    }
    resolveDuplicatedEntityReferenceProperties(oldRender, duplicatedIdsMap) {
        if (oldRender.rootBone) {
            this.rootBone = duplicatedIdsMap[oldRender.rootBone.getGuid()];
        }
    }
}

class RenderComponentData {
    constructor(){
        this.enabled = true;
    }
}

/**
 * @import { AppBase } from '../../app-base.js'
 */ const _schema$2 = [
    'enabled'
];
// order matters here
const _properties$1 = [
    'material',
    'meshInstances',
    'asset',
    'materialAssets',
    'castShadows',
    'receiveShadows',
    'castShadowsLightmap',
    'lightmapped',
    'lightmapSizeMultiplier',
    'renderStyle',
    'type',
    'layers',
    'isStatic',
    'batchGroupId',
    'rootBone'
];
/**
 * Allows an Entity to render a mesh or a primitive shape like a box, capsule, sphere, cylinder,
 * cone etc.
 *
 * @category Graphics
 */ class RenderComponentSystem extends ComponentSystem {
    /**
     * Create a new RenderComponentSystem.
     *
     * @param {AppBase} app - The Application.
     * @ignore
     */ constructor(app){
        super(app);
        this.id = 'render';
        this.ComponentType = RenderComponent;
        this.DataType = RenderComponentData;
        this.schema = _schema$2;
        this.defaultMaterial = getDefaultMaterial(app.graphicsDevice);
        this.on('beforeremove', this.onRemove, this);
    }
    initializeComponentData(component, _data, properties) {
        if (_data.batchGroupId === null || _data.batchGroupId === undefined) {
            _data.batchGroupId = -1;
        }
        // duplicate layer list
        if (_data.layers && _data.layers.length) {
            _data.layers = _data.layers.slice(0);
        }
        for(let i = 0; i < _properties$1.length; i++){
            if (_data.hasOwnProperty(_properties$1[i])) {
                component[_properties$1[i]] = _data[_properties$1[i]];
            }
        }
        if (_data.aabbCenter && _data.aabbHalfExtents) {
            component.customAabb = new BoundingBox(new Vec3(_data.aabbCenter), new Vec3(_data.aabbHalfExtents));
        }
        super.initializeComponentData(component, _data, _schema$2);
    }
    cloneComponent(entity, clone) {
        // copy properties
        const data = {};
        for(let i = 0; i < _properties$1.length; i++){
            data[_properties$1[i]] = entity.render[_properties$1[i]];
        }
        data.enabled = entity.render.enabled;
        // mesh instances cannot be used this way, remove them and manually clone them later
        delete data.meshInstances;
        // clone component
        const component = this.addComponent(clone, data);
        // clone mesh instances
        const srcMeshInstances = entity.render.meshInstances;
        const meshes = srcMeshInstances.map((mi)=>mi.mesh);
        component._onSetMeshes(meshes);
        // assign materials
        for(let m = 0; m < srcMeshInstances.length; m++){
            component.meshInstances[m].material = srcMeshInstances[m].material;
        }
        if (entity.render.customAabb) {
            component.customAabb = entity.render.customAabb.clone();
        }
        return component;
    }
    onRemove(entity, component) {
        component.onRemove();
    }
}
Component._buildAccessors(RenderComponent.prototype, _schema$2);

/**
 * @import { AppBase } from '../../app-base.js'
 * @import { CameraComponent } from './component.js'
 * @import { PostEffect } from '../../../scene/graphics/post-effect.js'
 */ class PostEffectEntry {
    constructor(effect, inputTarget){
        this.effect = effect;
        this.inputTarget = inputTarget;
        this.outputTarget = null;
        this.name = effect.constructor.name;
    }
}
/**
 * Used to manage multiple post effects for a camera.
 *
 * @category Graphics
 */ class PostEffectQueue {
    /**
     * Create a new PostEffectQueue instance.
     *
     * @param {AppBase} app - The application.
     * @param {CameraComponent} camera - The camera component.
     */ constructor(app, camera){
        this.app = app;
        this.camera = camera;
        /**
         * Render target where the postprocessed image needs to be rendered to. Defaults to null
         * which is main framebuffer.
         *
         * @type {RenderTarget}
         * @ignore
         */ this.destinationRenderTarget = null;
        /**
         * All of the post effects in the queue.
         *
         * @type {PostEffectEntry[]}
         * @ignore
         */ this.effects = [];
        /**
         * If the queue is enabled it will render all of its effects, otherwise it will not render
         * anything.
         *
         * @type {boolean}
         * @ignore
         */ this.enabled = false;
        // legacy
        this.depthTarget = null;
        camera.on('set:rect', this.onCameraRectChanged, this);
    }
    /**
     * Allocate a color buffer texture.
     *
     * @param {number} format - The format of the color buffer.
     * @param {string} name - The name of the color buffer.
     * @returns {Texture} The color buffer texture.
     * @private
     */ _allocateColorBuffer(format, name) {
        const rect = this.camera.rect;
        const renderTarget = this.destinationRenderTarget;
        const device = this.app.graphicsDevice;
        const width = Math.floor(rect.z * (renderTarget?.width ?? device.width));
        const height = Math.floor(rect.w * (renderTarget?.height ?? device.height));
        const colorBuffer = new Texture(device, {
            name: name,
            format: format,
            width: width,
            height: height,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE
        });
        return colorBuffer;
    }
    /**
     * Creates a render target with the dimensions of the canvas, with an optional depth buffer.
     *
     * @param {boolean} useDepth - Set to true to create a render target with a depth buffer.
     * @param {boolean} hdr - Use HDR render target format.
     * @returns {RenderTarget} The render target.
     * @private
     */ _createOffscreenTarget(useDepth, hdr) {
        const device = this.app.graphicsDevice;
        // use srgb LDR format if backbuffer is srgb
        const outputRt = this.destinationRenderTarget ?? device.backBuffer;
        const srgb = outputRt.isColorBufferSrgb(0);
        const format = (hdr && device.getRenderableHdrFormat([
            PIXELFORMAT_RGBA16F,
            PIXELFORMAT_RGBA32F
        ], true)) ?? (srgb ? PIXELFORMAT_SRGBA8 : PIXELFORMAT_RGBA8);
        const name = `${this.camera.entity.name}-posteffect-${this.effects.length}`;
        const colorBuffer = this._allocateColorBuffer(format, name);
        return new RenderTarget({
            colorBuffer: colorBuffer,
            depth: useDepth,
            stencil: useDepth && this.app.graphicsDevice.supportsStencil,
            samples: useDepth ? device.samples : 1
        });
    }
    _resizeOffscreenTarget(rt) {
        const format = rt.colorBuffer.format;
        const name = rt.colorBuffer.name;
        rt.destroyFrameBuffers();
        rt.destroyTextureBuffers();
        rt._colorBuffer = this._allocateColorBuffer(format, name);
        rt._colorBuffers = [
            rt._colorBuffer
        ];
    }
    _destroyOffscreenTarget(rt) {
        rt.destroyTextureBuffers();
        rt.destroy();
    }
    /**
     * Adds a post effect to the queue. If the queue is disabled adding a post effect will
     * automatically enable the queue.
     *
     * @param {PostEffect} effect - The post effect to add to the queue.
     */ addEffect(effect) {
        // first rendering of the scene requires depth buffer
        const effects = this.effects;
        const isFirstEffect = effects.length === 0;
        const inputTarget = this._createOffscreenTarget(isFirstEffect, effect.hdr);
        const newEntry = new PostEffectEntry(effect, inputTarget);
        effects.push(newEntry);
        this._sourceTarget = newEntry.inputTarget;
        // connect the effect with the previous effect if one exists
        if (effects.length > 1) {
            effects[effects.length - 2].outputTarget = newEntry.inputTarget;
        }
        // Request depthmap if needed
        this._newPostEffect = effect;
        if (effect.needsDepthBuffer) {
            this._requestDepthMap();
        }
        this.enable();
        this._newPostEffect = undefined;
    }
    /**
     * Removes a post effect from the queue. If the queue becomes empty it will be disabled
     * automatically.
     *
     * @param {PostEffect} effect - The post effect to remove.
     */ removeEffect(effect) {
        // find index of effect
        let index = -1;
        for(let i = 0, len = this.effects.length; i < len; i++){
            if (this.effects[i].effect === effect) {
                index = i;
                break;
            }
        }
        if (index >= 0) {
            if (index > 0) {
                // connect the previous effect with the effect after the one we're about to remove
                this.effects[index - 1].outputTarget = index + 1 < this.effects.length ? this.effects[index + 1].inputTarget : null;
            } else {
                if (this.effects.length > 1) {
                    // if we removed the first effect then make sure that
                    // the input render target of the effect that will now become the first one
                    // has a depth buffer
                    if (!this.effects[1].inputTarget._depth) {
                        this._destroyOffscreenTarget(this.effects[1].inputTarget);
                        this.effects[1].inputTarget = this._createOffscreenTarget(true, this.effects[1].hdr);
                        this._sourceTarget = this.effects[1].inputTarget;
                    }
                    this.camera.renderTarget = this.effects[1].inputTarget;
                }
            }
            // release memory for removed effect
            this._destroyOffscreenTarget(this.effects[index].inputTarget);
            this.effects.splice(index, 1);
        }
        if (this.enabled) {
            if (effect.needsDepthBuffer) {
                this._releaseDepthMap();
            }
        }
        if (this.effects.length === 0) {
            this.disable();
        }
    }
    _requestDepthMaps() {
        for(let i = 0, len = this.effects.length; i < len; i++){
            const effect = this.effects[i].effect;
            if (this._newPostEffect === effect) {
                continue;
            }
            if (effect.needsDepthBuffer) {
                this._requestDepthMap();
            }
        }
    }
    _releaseDepthMaps() {
        for(let i = 0, len = this.effects.length; i < len; i++){
            const effect = this.effects[i].effect;
            if (effect.needsDepthBuffer) {
                this._releaseDepthMap();
            }
        }
    }
    _requestDepthMap() {
        const depthLayer = this.app.scene.layers.getLayerById(LAYERID_DEPTH);
        if (depthLayer) {
            depthLayer.incrementCounter();
            this.camera.requestSceneDepthMap(true);
        }
    }
    _releaseDepthMap() {
        const depthLayer = this.app.scene.layers.getLayerById(LAYERID_DEPTH);
        if (depthLayer) {
            depthLayer.decrementCounter();
            this.camera.requestSceneDepthMap(false);
        }
    }
    /**
     * Removes all the effects from the queue and disables it.
     */ destroy() {
        // release memory for all effects
        for(let i = 0, len = this.effects.length; i < len; i++){
            this.effects[i].inputTarget.destroy();
        }
        this.effects.length = 0;
        this.disable();
    }
    /**
     * Enables the queue and all of its effects. If there are no effects then the queue will not be
     * enabled.
     */ enable() {
        if (!this.enabled && this.effects.length) {
            this.enabled = true;
            this._requestDepthMaps();
            this.app.graphicsDevice.on('resizecanvas', this._onCanvasResized, this);
            // original camera's render target is where the final output needs to go
            this.destinationRenderTarget = this.camera.renderTarget;
            // camera renders to the first effect's render target
            this.camera.renderTarget = this.effects[0].inputTarget;
            // callback when postprocessing takes place
            this.camera.onPostprocessing = ()=>{
                if (this.enabled) {
                    let rect = null;
                    const len = this.effects.length;
                    if (len) {
                        for(let i = 0; i < len; i++){
                            const fx = this.effects[i];
                            let destTarget = fx.outputTarget;
                            // last effect
                            if (i === len - 1) {
                                rect = this.camera.rect;
                                // if camera originally rendered to a render target, render last effect to it
                                if (this.destinationRenderTarget) {
                                    destTarget = this.destinationRenderTarget;
                                }
                            }
                            DebugGraphics.pushGpuMarker(this.app.graphicsDevice, fx.name);
                            fx.effect.render(fx.inputTarget, destTarget, rect);
                            DebugGraphics.popGpuMarker(this.app.graphicsDevice);
                        }
                    }
                }
            };
        }
    }
    /**
     * Disables the queue and all of its effects.
     */ disable() {
        if (this.enabled) {
            this.enabled = false;
            this.app.graphicsDevice.off('resizecanvas', this._onCanvasResized, this);
            this._releaseDepthMaps();
            this._destroyOffscreenTarget(this._sourceTarget);
            this.camera.renderTarget = this.destinationRenderTarget;
            this.camera.onPostprocessing = null;
        }
    }
    /**
     * Handler called when the application's canvas element is resized.
     *
     * @param {number} width - The new width of the canvas.
     * @param {number} height - The new height of the canvas.
     * @private
     */ _onCanvasResized(width, height) {
        const rect = this.camera.rect;
        const renderTarget = this.destinationRenderTarget;
        width = renderTarget?.width ?? width;
        height = renderTarget?.height ?? height;
        this.camera.camera.aspectRatio = width * rect.z / (height * rect.w);
        this.resizeRenderTargets();
    }
    resizeRenderTargets() {
        const device = this.app.graphicsDevice;
        const renderTarget = this.destinationRenderTarget;
        const width = renderTarget?.width ?? device.width;
        const height = renderTarget?.height ?? device.height;
        const rect = this.camera.rect;
        const desiredWidth = Math.floor(rect.z * width);
        const desiredHeight = Math.floor(rect.w * height);
        const effects = this.effects;
        for(let i = 0, len = effects.length; i < len; i++){
            const fx = effects[i];
            if (fx.inputTarget.width !== desiredWidth || fx.inputTarget.height !== desiredHeight) {
                this._resizeOffscreenTarget(fx.inputTarget);
            }
        }
    }
    onCameraRectChanged(name, oldValue, newValue) {
        if (this.enabled) {
            this.resizeRenderTargets();
        }
    }
}

/**
 * @import { CameraComponentSystem } from './system.js'
 * @import { Color } from '../../../core/math/color.js'
 * @import { Entity } from '../../entity.js'
 * @import { EventHandle } from '../../../core/event-handle.js'
 * @import { Frustum } from '../../../core/shape/frustum.js'
 * @import { LayerComposition } from '../../../scene/composition/layer-composition.js'
 * @import { Layer } from '../../../scene/layer.js'
 * @import { Mat4 } from '../../../core/math/mat4.js'
 * @import { RenderPass } from '../../../platform/graphics/render-pass.js'
 * @import { RenderTarget } from '../../../platform/graphics/render-target.js'
 * @import { FogParams } from '../../../scene/fog-params.js'
 * @import { Vec3 } from '../../../core/math/vec3.js'
 * @import { Vec4 } from '../../../core/math/vec4.js'
 * @import { XrErrorCallback } from '../../xr/xr-manager.js'
 */ /**
 * @callback CalculateMatrixCallback
 * Callback used by {@link CameraComponent#calculateTransform} and {@link CameraComponent#calculateProjection}.
 * @param {Mat4} transformMatrix - Output of the function.
 * @param {number} view - Type of view. Can be {@link VIEW_CENTER}, {@link VIEW_LEFT} or
 * {@link VIEW_RIGHT}. Left and right are only used in stereo rendering.
 * @returns {void}
 */ /**
 * The CameraComponent enables an {@link Entity} to render the scene. A scene requires at least
 * one enabled camera component to be rendered. The camera's view direction is along the negative
 * z-axis of the owner entity.
 *
 * Note that multiple camera components can be enabled simultaneously (for split-screen or
 * offscreen rendering, for example).
 *
 * You should never need to use the CameraComponent constructor directly. To add a CameraComponent
 * to an {@link Entity}, use {@link Entity#addComponent}:
 *
 * ```javascript
 * const entity = new pc.Entity();
 * entity.addComponent('camera', {
 *     nearClip: 1,
 *     farClip: 100,
 *     fov: 55
 * });
 * ```
 *
 * Once the CameraComponent is added to the entity, you can access it via the {@link Entity#camera}
 * property:
 *
 * ```javascript
 * entity.camera.nearClip = 2; // Set the near clip of the camera
 *
 * console.log(entity.camera.nearClip); // Get the near clip of the camera
 * ```
 *
 * @hideconstructor
 * @category Graphics
 */ class CameraComponent extends Component {
    /**
     * Create a new CameraComponent instance.
     *
     * @param {CameraComponentSystem} system - The ComponentSystem that created this Component.
     * @param {Entity} entity - The Entity that this Component is attached to.
     */ constructor(system, entity){
        super(system, entity), /**
     * Custom function that is called when postprocessing should execute.
     *
     * @type {Function|null}
     * @ignore
     */ this.onPostprocessing = null, /**
     * A counter of requests of depth map rendering.
     *
     * @type {number}
     * @private
     */ this._renderSceneDepthMap = 0, /**
     * A counter of requests of color map rendering.
     *
     * @type {number}
     * @private
     */ this._renderSceneColorMap = 0, /** @private */ this._sceneDepthMapRequested = false, /** @private */ this._sceneColorMapRequested = false, /** @private */ this._priority = 0, /**
     * Layer id at which the postprocessing stops for the camera.
     *
     * @type {number}
     * @private
     */ this._disablePostEffectsLayer = LAYERID_UI, /** @private */ this._camera = new Camera$1(), /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayersChanged = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerAdded = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerRemoved = null;
        this._camera.node = entity;
        // postprocessing management
        this._postEffects = new PostEffectQueue(system.app, this);
    }
    /**
     * Sets the name of the shader pass the camera will use when rendering.
     *
     * In addition to existing names (see the parameter description), a new name can be specified,
     * which creates a new shader pass with the given name. The name provided can only use
     * alphanumeric characters and underscores. When a shader is compiled for the new pass, a define
     * is added to the shader. For example, if the name is 'custom_rendering', the define
     * 'CUSTOM_RENDERING_PASS' is added to the shader, allowing the shader code to conditionally
     * execute code only when that shader pass is active.
     *
     * Another instance where this approach may prove useful is when a camera needs to render a more
     * cost-effective version of shaders, such as when creating a reflection texture. To accomplish
     * this, a callback on the material that triggers during shader compilation can be used. This
     * callback can modify the shader generation options specifically for this shader pass.
     *
     * ```javascript
     * const shaderPassId = camera.setShaderPass('custom_rendering');
     *
     * material.onUpdateShader = function (options) {
     *     if (options.pass === shaderPassId) {
     *         options.litOptions.normalMapEnabled = false;
     *         options.litOptions.useSpecular = false;
     *     }
     *     return options;
     * };
     * ```
     *
     * @param {string} name - The name of the shader pass. Defaults to undefined, which is
     * equivalent to {@link SHADERPASS_FORWARD}. Can be:
     *
     * - {@link SHADERPASS_FORWARD}
     * - {@link SHADERPASS_ALBEDO}
     * - {@link SHADERPASS_OPACITY}
     * - {@link SHADERPASS_WORLDNORMAL}
     * - {@link SHADERPASS_SPECULARITY}
     * - {@link SHADERPASS_GLOSS}
     * - {@link SHADERPASS_METALNESS}
     * - {@link SHADERPASS_AO}
     * - {@link SHADERPASS_EMISSION}
     * - {@link SHADERPASS_LIGHTING}
     * - {@link SHADERPASS_UV0}
     *
     * @returns {number} The id of the shader pass.
     */ setShaderPass(name) {
        const shaderPass = ShaderPass.get(this.system.app.graphicsDevice);
        const shaderPassInfo = name ? shaderPass.allocate(name, {
            isForward: true
        }) : null;
        this._camera.shaderPassInfo = shaderPassInfo;
        return shaderPassInfo.index;
    }
    /**
     * Shader pass name.
     *
     * @returns {string|undefined} The name of the shader pass, or undefined if no shader pass is set.
     */ getShaderPass() {
        return this._camera.shaderPassInfo?.name;
    }
    /**
     * Sets the render passes the camera uses for rendering, instead of its default rendering.
     * Set this to null to return to the default behavior.
     *
     * @type {RenderPass[]|null}
     * @ignore
     */ set renderPasses(passes) {
        this._camera.renderPasses = passes || [];
        this.dirtyLayerCompositionCameras();
        this.system.app.scene.updateShaders = true;
    }
    /**
     * Gets the render passes the camera uses for rendering, instead of its default rendering.
     *
     * @type {RenderPass[]}
     * @ignore
     */ get renderPasses() {
        return this._camera.renderPasses;
    }
    get shaderParams() {
        return this._camera.shaderParams;
    }
    /**
     * Sets the gamma correction to apply when rendering the scene. Can be:
     *
     * - {@link GAMMA_NONE}
     * - {@link GAMMA_SRGB}
     *
     * Defaults to {@link GAMMA_SRGB}.
     *
     * @type {number}
     */ set gammaCorrection(value) {
        this.camera.shaderParams.gammaCorrection = value;
    }
    /**
     * Gets the gamma correction used when rendering the scene.
     *
     * @type {number}
     */ get gammaCorrection() {
        return this.camera.shaderParams.gammaCorrection;
    }
    /**
     * Sets the tonemapping transform to apply to the rendered color buffer. Can be:
     *
     * - {@link TONEMAP_LINEAR}
     * - {@link TONEMAP_FILMIC}
     * - {@link TONEMAP_HEJL}
     * - {@link TONEMAP_ACES}
     * - {@link TONEMAP_ACES2}
     * - {@link TONEMAP_NEUTRAL}
     *
     * Defaults to {@link TONEMAP_LINEAR}.
     *
     * @type {number}
     */ set toneMapping(value) {
        this.camera.shaderParams.toneMapping = value;
    }
    /**
     * Gets the tonemapping transform applied to the rendered color buffer.
     *
     * @type {number}
     */ get toneMapping() {
        return this.camera.shaderParams.toneMapping;
    }
    /**
     * Sets the fog parameters. If this is not null, the camera will use these fog parameters
     * instead of those specified on the {@link Scene#fog}.
     *
     * @type {FogParams|null}
     */ set fog(value) {
        this._camera.fogParams = value;
    }
    /**
     * Gets a {@link FogParams} that defines fog parameters, or null if those are not set.
     *
     * @type {FogParams|null}
     */ get fog() {
        return this._camera.fogParams;
    }
    /**
     * Sets the camera aperture in f-stops. Default is 16. Higher value means less exposure. Used
     * if {@link Scene#physicalUnits} is true.
     *
     * @type {number}
     */ set aperture(value) {
        this._camera.aperture = value;
    }
    /**
     * Gets the camera aperture in f-stops.
     *
     * @type {number}
     */ get aperture() {
        return this._camera.aperture;
    }
    /**
     * Sets the aspect ratio (width divided by height) of the camera. If {@link aspectRatioMode} is
     * {@link ASPECT_AUTO}, then this value will be automatically calculated every frame, and you
     * can only read it. If it's {@link ASPECT_MANUAL}, you can set the value.
     *
     * @type {number}
     */ set aspectRatio(value) {
        this._camera.aspectRatio = value;
    }
    /**
     * Gets the aspect ratio (width divided by height) of the camera.
     *
     * @type {number}
     */ get aspectRatio() {
        return this._camera.aspectRatio;
    }
    /**
     * Sets the aspect ratio mode of the camera. Can be:
     *
     * - {@link ASPECT_AUTO}: aspect ratio will be calculated from the current render
     * target's width divided by height.
     * - {@link ASPECT_MANUAL}: use the aspectRatio value.
     *
     * Defaults to {@link ASPECT_AUTO}.
     *
     * @type {number}
     */ set aspectRatioMode(value) {
        this._camera.aspectRatioMode = value;
    }
    /**
     * Gets the aspect ratio mode of the camera.
     *
     * @type {number}
     */ get aspectRatioMode() {
        return this._camera.aspectRatioMode;
    }
    /**
     * Sets the custom function to calculate the camera projection matrix manually. Can be used for
     * complex effects like doing oblique projection. Function is called using component's scope.
     *
     * Arguments:
     *
     * - {@link Mat4} transformMatrix: output of the function
     * - view: Type of view. Can be {@link VIEW_CENTER}, {@link VIEW_LEFT} or {@link VIEW_RIGHT}.
     *
     * Left and right are only used in stereo rendering.
     *
     * @type {CalculateMatrixCallback}
     */ set calculateProjection(value) {
        this._camera.calculateProjection = value;
    }
    /**
     * Gets the custom function to calculate the camera projection matrix manually.
     *
     * @type {CalculateMatrixCallback}
     */ get calculateProjection() {
        return this._camera.calculateProjection;
    }
    /**
     * Sets the custom function to calculate the camera transformation matrix manually. Can be used
     * for complex effects like reflections. Function is called using component's scope. Arguments:
     *
     * - {@link Mat4} transformMatrix: output of the function.
     * - view: Type of view. Can be {@link VIEW_CENTER}, {@link VIEW_LEFT} or {@link VIEW_RIGHT}.
     *
     * Left and right are only used in stereo rendering.
     *
     * @type {CalculateMatrixCallback}
     */ set calculateTransform(value) {
        this._camera.calculateTransform = value;
    }
    /**
     * Gets the custom function to calculate the camera transformation matrix manually.
     *
     * @type {CalculateMatrixCallback}
     */ get calculateTransform() {
        return this._camera.calculateTransform;
    }
    /**
     * Gets the camera component's underlying Camera instance.
     *
     * @type {Camera}
     * @ignore
     */ get camera() {
        return this._camera;
    }
    /**
     * Sets the camera component's clear color. Defaults to `[0.75, 0.75, 0.75, 1]`.
     *
     * @type {Color}
     */ set clearColor(value) {
        this._camera.clearColor = value;
    }
    /**
     * Gets the camera component's clear color.
     *
     * @type {Color}
     */ get clearColor() {
        return this._camera.clearColor;
    }
    /**
     * Sets whether the camera will automatically clear the color buffer before rendering. Defaults to true.
     *
     * @type {boolean}
     */ set clearColorBuffer(value) {
        this._camera.clearColorBuffer = value;
        this.dirtyLayerCompositionCameras();
    }
    /**
     * Gets whether the camera will automatically clear the color buffer before rendering.
     *
     * @type {boolean}
     */ get clearColorBuffer() {
        return this._camera.clearColorBuffer;
    }
    /**
     * Sets whether the camera will automatically clear the depth buffer before rendering. Defaults to true.
     *
     * @type {boolean}
     */ set clearDepthBuffer(value) {
        this._camera.clearDepthBuffer = value;
        this.dirtyLayerCompositionCameras();
    }
    /**
     * Gets whether the camera will automatically clear the depth buffer before rendering.
     *
     * @type {boolean}
     */ get clearDepthBuffer() {
        return this._camera.clearDepthBuffer;
    }
    /**
     * Sets whether the camera will automatically clear the stencil buffer before rendering. Defaults to true.
     *
     * @type {boolean}
     */ set clearStencilBuffer(value) {
        this._camera.clearStencilBuffer = value;
        this.dirtyLayerCompositionCameras();
    }
    /**
     * Gets whether the camera will automatically clear the stencil buffer before rendering.
     *
     * @type {boolean}
     */ get clearStencilBuffer() {
        return this._camera.clearStencilBuffer;
    }
    /**
     * Sets whether the camera will cull triangle faces. If true, the camera will take
     * {@link Material#cull} into account. Otherwise both front and back faces will be rendered.
     * Defaults to true.
     *
     * @type {boolean}
     */ set cullFaces(value) {
        this._camera.cullFaces = value;
    }
    /**
     * Gets whether the camera will cull triangle faces.
     *
     * @type {boolean}
     */ get cullFaces() {
        return this._camera.cullFaces;
    }
    /**
     * Sets the layer id of the layer on which the post-processing of the camera stops being applied
     * to. Defaults to {@link LAYERID_UI}, which causes post-processing to not be applied to UI
     * layer and any following layers for the camera. Set to `undefined` for post-processing to be
     * applied to all layers of the camera.
     *
     * @type {number}
     */ set disablePostEffectsLayer(layer) {
        this._disablePostEffectsLayer = layer;
        this.dirtyLayerCompositionCameras();
    }
    /**
     * Gets the layer id of the layer on which the post-processing of the camera stops being applied
     * to.
     *
     * @type {number}
     */ get disablePostEffectsLayer() {
        return this._disablePostEffectsLayer;
    }
    /**
     * Sets the distance from the camera after which no rendering will take place. Defaults to 1000.
     *
     * @type {number}
     */ set farClip(value) {
        this._camera.farClip = value;
    }
    /**
     * Gets the distance from the camera after which no rendering will take place.
     *
     * @type {number}
     */ get farClip() {
        return this._camera.farClip;
    }
    /**
     * Sets whether the camera will flip the face direction of triangles. If set to true, the
     * camera will invert front and back faces. Can be useful for reflection rendering. Defaults to
     * false.
     *
     * @type {boolean}
     */ set flipFaces(value) {
        this._camera.flipFaces = value;
    }
    /**
     * Gets whether the camera will flip the face direction of triangles.
     *
     * @type {boolean}
     */ get flipFaces() {
        return this._camera.flipFaces;
    }
    /**
     * Sets the field of view of the camera in degrees. Usually this is the Y-axis field of view
     * (see {@link horizontalFov}). Used for {@link PROJECTION_PERSPECTIVE} cameras only. Defaults to
     * 45.
     *
     * @type {number}
     */ set fov(value) {
        this._camera.fov = value;
    }
    /**
     * Gets the field of view of the camera in degrees.
     *
     * @type {number}
     */ get fov() {
        return this._camera.fov;
    }
    /**
     * Gets the camera's frustum shape.
     *
     * @type {Frustum}
     */ get frustum() {
        return this._camera.frustum;
    }
    /**
     * Sets whether frustum culling is enabled. This controls the culling of {@link MeshInstance}s
     * against the camera frustum, i.e. if objects outside of the camera's frustum should be
     * omitted from rendering. If false, all mesh instances in the scene are rendered by the
     * camera, regardless of visibility. Defaults to false.
     *
     * @type {boolean}
     */ set frustumCulling(value) {
        this._camera.frustumCulling = value;
    }
    /**
     * Gets whether frustum culling is enabled.
     *
     * @type {boolean}
     */ get frustumCulling() {
        return this._camera.frustumCulling;
    }
    /**
     * Sets whether the camera's field of view ({@link fov}) is horizontal or vertical. Defaults to
     * false (meaning it is vertical by default).
     *
     * @type {boolean}
     */ set horizontalFov(value) {
        this._camera.horizontalFov = value;
    }
    /**
     * Gets whether the camera's field of view ({@link fov}) is horizontal or vertical.
     *
     * @type {boolean}
     */ get horizontalFov() {
        return this._camera.horizontalFov;
    }
    /**
     * Sets the array of layer IDs ({@link Layer#id}) to which this camera should belong. Don't
     * push, pop, splice or modify this array. If you want to change it, set a new one instead.
     * Defaults to [{@link LAYERID_WORLD}, {@link LAYERID_DEPTH}, {@link LAYERID_SKYBOX},
     * {@link LAYERID_UI}, {@link LAYERID_IMMEDIATE}].
     *
     * @type {number[]}
     */ set layers(newValue) {
        const oldLayers = this._camera.layers;
        const scene = this.system.app.scene;
        // Remove from old layers
        oldLayers.forEach((layerId)=>{
            const layer = scene.layers.getLayerById(layerId);
            layer?.removeCamera(this);
        });
        this._camera.layers = newValue;
        // Only add to new layers if enabled
        if (this.enabled && this.entity.enabled) {
            newValue.forEach((layerId)=>{
                const layer = scene.layers.getLayerById(layerId);
                layer?.addCamera(this);
            });
        }
        this.fire('set:layers');
    }
    /**
     * Gets the array of layer IDs ({@link Layer#id}) to which this camera belongs.
     *
     * @type {number[]}
     */ get layers() {
        return this._camera.layers;
    }
    get layersSet() {
        return this._camera.layersSet;
    }
    /**
     * Sets the jitter intensity applied in the projection matrix. Used for jittered sampling by TAA.
     * A value of 1 represents a jitter in the range of `[-1, 1]` of a pixel. Smaller values result
     * in a crisper yet more aliased outcome, whereas increased values produce a smoother but blurred
     * result. Defaults to 0, representing no jitter.
     *
     * @type {number}
     */ set jitter(value) {
        this._camera.jitter = value;
    }
    /**
     * Gets the jitter intensity applied in the projection matrix.
     *
     * @type {number}
     */ get jitter() {
        return this._camera.jitter;
    }
    /**
     * Sets the distance from the camera before which no rendering will take place. Defaults to 0.1.
     *
     * @type {number}
     */ set nearClip(value) {
        this._camera.nearClip = value;
    }
    /**
     * Gets the distance from the camera before which no rendering will take place.
     *
     * @type {number}
     */ get nearClip() {
        return this._camera.nearClip;
    }
    /**
     * Sets the half-height of the orthographic view window (in the Y-axis). Used for
     * {@link PROJECTION_ORTHOGRAPHIC} cameras only. Defaults to 10.
     *
     * @type {number}
     */ set orthoHeight(value) {
        this._camera.orthoHeight = value;
    }
    /**
     * Gets the half-height of the orthographic view window (in the Y-axis).
     *
     * @type {number}
     */ get orthoHeight() {
        return this._camera.orthoHeight;
    }
    /**
     * Gets the post effects queue for this camera. Use this to add or remove post effects from the
     * camera.
     *
     * @type {PostEffectQueue}
     */ get postEffects() {
        return this._postEffects;
    }
    get postEffectsEnabled() {
        return this._postEffects.enabled;
    }
    /**
     * Sets the priority to control the render order of this camera. Cameras with a smaller
     * priority value are rendered first. Defaults to 0.
     *
     * @type {number}
     */ set priority(newValue) {
        this._priority = newValue;
        this.dirtyLayerCompositionCameras();
    }
    /**
     * Gets the priority to control the render order of this camera.
     *
     * @type {number}
     */ get priority() {
        return this._priority;
    }
    /**
     * Sets the type of projection used to render the camera. Can be:
     *
     * - {@link PROJECTION_PERSPECTIVE}: A perspective projection. The camera frustum
     * resembles a truncated pyramid.
     * - {@link PROJECTION_ORTHOGRAPHIC}: An orthographic projection. The camera
     * frustum is a cuboid.
     *
     * Defaults to {@link PROJECTION_PERSPECTIVE}.
     *
     * @type {number}
     */ set projection(value) {
        this._camera.projection = value;
    }
    /**
     * Gets the type of projection used to render the camera.
     *
     * @type {number}
     */ get projection() {
        return this._camera.projection;
    }
    /**
     * Gets the camera's projection matrix.
     *
     * @type {Mat4}
     */ get projectionMatrix() {
        return this._camera.projectionMatrix;
    }
    /**
     * Sets the rendering rectangle for the camera. This controls where on the screen the camera
     * will render in normalized screen coordinates. Defaults to `[0, 0, 1, 1]`.
     *
     * @type {Vec4}
     */ set rect(value) {
        this._camera.rect = value;
        this.fire('set:rect', this._camera.rect);
    }
    /**
     * Gets the rendering rectangle for the camera.
     *
     * @type {Vec4}
     */ get rect() {
        return this._camera.rect;
    }
    set renderSceneColorMap(value) {
        if (value && !this._sceneColorMapRequested) {
            this.requestSceneColorMap(true);
            this._sceneColorMapRequested = true;
        } else if (this._sceneColorMapRequested) {
            this.requestSceneColorMap(false);
            this._sceneColorMapRequested = false;
        }
    }
    get renderSceneColorMap() {
        return this._renderSceneColorMap > 0;
    }
    set renderSceneDepthMap(value) {
        if (value && !this._sceneDepthMapRequested) {
            this.requestSceneDepthMap(true);
            this._sceneDepthMapRequested = true;
        } else if (this._sceneDepthMapRequested) {
            this.requestSceneDepthMap(false);
            this._sceneDepthMapRequested = false;
        }
    }
    get renderSceneDepthMap() {
        return this._renderSceneDepthMap > 0;
    }
    /**
     * Sets the render target to which rendering of the camera is performed. If not set, it will
     * render simply to the screen.
     *
     * @type {RenderTarget}
     */ set renderTarget(value) {
        Debug.call(()=>{
            if (this._camera.renderPasses.length > 0) {
                Debug.warn(`Setting a render target on the camera ${this.entity.name} after the render passes is not supported, set it up first.`);
            }
        });
        this._camera.renderTarget = value;
        this.dirtyLayerCompositionCameras();
    }
    /**
     * Gets the render target to which rendering of the camera is performed.
     *
     * @type {RenderTarget}
     */ get renderTarget() {
        return this._camera.renderTarget;
    }
    /**
     * Sets the scissor rectangle for the camera. This clips all pixels which are not in the
     * rectangle. The order of the values is `[x, y, width, height]`. Defaults to `[0, 0, 1, 1]`.
     *
     * @type {Vec4}
     */ set scissorRect(value) {
        this._camera.scissorRect = value;
    }
    /**
     * Gets the scissor rectangle for the camera.
     *
     * @type {Vec4}
     */ get scissorRect() {
        return this._camera.scissorRect;
    }
    /**
     * Sets the camera sensitivity in ISO. Defaults to 1000. Higher value means more exposure. Used
     * if {@link Scene#physicalUnits} is true.
     *
     * @type {number}
     */ set sensitivity(value) {
        this._camera.sensitivity = value;
    }
    /**
     * Gets the camera sensitivity in ISO.
     *
     * @type {number}
     */ get sensitivity() {
        return this._camera.sensitivity;
    }
    /**
     * Sets the camera shutter speed in seconds. Defaults to 1/1000s. Longer shutter means more
     * exposure. Used if {@link Scene#physicalUnits} is true.
     *
     * @type {number}
     */ set shutter(value) {
        this._camera.shutter = value;
    }
    /**
     * Gets the camera shutter speed in seconds.
     *
     * @type {number}
     */ get shutter() {
        return this._camera.shutter;
    }
    /**
     * Gets the camera's view matrix.
     *
     * @type {Mat4}
     */ get viewMatrix() {
        return this._camera.viewMatrix;
    }
    /**
     * Based on the value, the depth layer's enable counter is incremented or decremented.
     *
     * @param {boolean} value - True to increment the counter, false to decrement it.
     * @returns {boolean} True if the counter was incremented or decremented, false if the depth
     * layer is not present.
     * @private
     */ _enableDepthLayer(value) {
        const hasDepthLayer = this.layers.find((layerId)=>layerId === LAYERID_DEPTH);
        if (hasDepthLayer) {
            /** @type {Layer} */ const depthLayer = this.system.app.scene.layers.getLayerById(LAYERID_DEPTH);
            if (value) {
                depthLayer?.incrementCounter();
            } else {
                depthLayer?.decrementCounter();
            }
        } else if (value) {
            return false;
        }
        return true;
    }
    /**
     * Request the scene to generate a texture containing the scene color map. Note that this call
     * is accumulative, and for each enable request, a disable request need to be called. Note that
     * this setting is ignored when the {@link CameraComponent#renderPasses} is used.
     *
     * @param {boolean} enabled - True to request the generation, false to disable it.
     */ requestSceneColorMap(enabled) {
        this._renderSceneColorMap += enabled ? 1 : -1;
        Debug.assert(this._renderSceneColorMap >= 0);
        const ok = this._enableDepthLayer(enabled);
        if (!ok) {
            Debug.warnOnce('CameraComponent.requestSceneColorMap was called, but the camera does not have a Depth layer, ignoring.');
        }
        this.camera._enableRenderPassColorGrab(this.system.app.graphicsDevice, this.renderSceneColorMap);
        this.system.app.scene.layers.markDirty();
    }
    /**
     * Request the scene to generate a texture containing the scene depth map. Note that this call
     * is accumulative, and for each enable request, a disable request need to be called. Note that
     * this setting is ignored when the {@link CameraComponent#renderPasses} is used.
     *
     * @param {boolean} enabled - True to request the generation, false to disable it.
     */ requestSceneDepthMap(enabled) {
        this._renderSceneDepthMap += enabled ? 1 : -1;
        Debug.assert(this._renderSceneDepthMap >= 0);
        const ok = this._enableDepthLayer(enabled);
        if (!ok) {
            Debug.warnOnce('CameraComponent.requestSceneDepthMap was called, but the camera does not have a Depth layer, ignoring.');
        }
        this.camera._enableRenderPassDepthGrab(this.system.app.graphicsDevice, this.system.app.renderer, this.renderSceneDepthMap);
        this.system.app.scene.layers.markDirty();
    }
    dirtyLayerCompositionCameras() {
        // layer composition needs to update order
        const layerComp = this.system.app.scene.layers;
        layerComp._dirty = true;
    }
    /**
     * Convert a point from 2D screen space to 3D world space.
     *
     * @param {number} screenx - X coordinate on PlayCanvas' canvas element. Should be in the range
     * 0 to `canvas.offsetWidth` of the application's canvas element.
     * @param {number} screeny - Y coordinate on PlayCanvas' canvas element. Should be in the range
     * 0 to `canvas.offsetHeight` of the application's canvas element.
     * @param {number} cameraz - The distance from the camera in world space to create the new
     * point.
     * @param {Vec3} [worldCoord] - 3D vector to receive world coordinate result.
     * @example
     * // Get the start and end points of a 3D ray fired from a screen click position
     * const start = entity.camera.screenToWorld(clickX, clickY, entity.camera.nearClip);
     * const end = entity.camera.screenToWorld(clickX, clickY, entity.camera.farClip);
     *
     * // Use the ray coordinates to perform a raycast
     * app.systems.rigidbody.raycastFirst(start, end, function (result) {
     *     console.log("Entity " + result.entity.name + " was selected");
     * });
     * @returns {Vec3} The world space coordinate.
     */ screenToWorld(screenx, screeny, cameraz, worldCoord) {
        const device = this.system.app.graphicsDevice;
        const { width, height } = device.clientRect;
        return this._camera.screenToWorld(screenx, screeny, cameraz, width, height, worldCoord);
    }
    /**
     * Convert a point from 3D world space to 2D screen space.
     *
     * @param {Vec3} worldCoord - The world space coordinate.
     * @param {Vec3} [screenCoord] - 3D vector to receive screen coordinate result.
     * @returns {Vec3} The screen space coordinate.
     */ worldToScreen(worldCoord, screenCoord) {
        const device = this.system.app.graphicsDevice;
        const { width, height } = device.clientRect;
        return this._camera.worldToScreen(worldCoord, width, height, screenCoord);
    }
    /**
     * Called before application renders the scene.
     *
     * @ignore
     */ onAppPrerender() {
        this._camera._viewMatDirty = true;
        this._camera._viewProjMatDirty = true;
    }
    /** @private */ addCameraToLayers() {
        const layers = this.layers;
        for(let i = 0; i < layers.length; i++){
            const layer = this.system.app.scene.layers.getLayerById(layers[i]);
            if (layer) {
                layer.addCamera(this);
            }
        }
    }
    /** @private */ removeCameraFromLayers() {
        const layers = this.layers;
        for(let i = 0; i < layers.length; i++){
            const layer = this.system.app.scene.layers.getLayerById(layers[i]);
            if (layer) {
                layer.removeCamera(this);
            }
        }
    }
    /**
     * @param {LayerComposition} oldComp - Old layer composition.
     * @param {LayerComposition} newComp - New layer composition.
     * @private
     */ onLayersChanged(oldComp, newComp) {
        this.addCameraToLayers();
        oldComp.off('add', this.onLayerAdded, this);
        oldComp.off('remove', this.onLayerRemoved, this);
        newComp.on('add', this.onLayerAdded, this);
        newComp.on('remove', this.onLayerRemoved, this);
    }
    /**
     * @param {Layer} layer - The layer to add the camera to.
     * @private
     */ onLayerAdded(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index < 0) return;
        layer.addCamera(this);
    }
    /**
     * @param {Layer} layer - The layer to remove the camera from.
     * @private
     */ onLayerRemoved(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index < 0) return;
        layer.removeCamera(this);
    }
    onEnable() {
        const scene = this.system.app.scene;
        const layers = scene.layers;
        this.system.addCamera(this);
        this._evtLayersChanged?.off();
        this._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);
        if (layers) {
            this._evtLayerAdded?.off();
            this._evtLayerAdded = layers.on('add', this.onLayerAdded, this);
            this._evtLayerRemoved?.off();
            this._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);
        }
        if (this.enabled && this.entity.enabled) {
            this.addCameraToLayers();
        }
        this.postEffects.enable();
    }
    onDisable() {
        const scene = this.system.app.scene;
        const layers = scene.layers;
        this.postEffects.disable();
        this.removeCameraFromLayers();
        this._evtLayersChanged?.off();
        this._evtLayersChanged = null;
        if (layers) {
            this._evtLayerAdded?.off();
            this._evtLayerAdded = null;
            this._evtLayerRemoved?.off();
            this._evtLayerRemoved = null;
        }
        this.system.removeCamera(this);
    }
    onRemove() {
        this.onDisable();
        this.off();
        this.camera.destroy();
    }
    /**
     * Calculates aspect ratio value for a given render target.
     *
     * @param {RenderTarget|null} [rt] - Optional
     * render target. If unspecified, the backbuffer is used.
     * @returns {number} The aspect ratio of the render target (or backbuffer).
     */ calculateAspectRatio(rt) {
        const device = this.system.app.graphicsDevice;
        const width = rt ? rt.width : device.width;
        const height = rt ? rt.height : device.height;
        return width * this.rect.z / (height * this.rect.w);
    }
    /**
     * Prepare the camera for frame rendering.
     *
     * @param {RenderTarget|null} [rt] - Render
     * target to which rendering will be performed. Will affect camera's aspect ratio, if
     * aspectRatioMode is {@link ASPECT_AUTO}.
     * @ignore
     */ frameUpdate(rt) {
        if (this.aspectRatioMode === ASPECT_AUTO) {
            this.aspectRatio = this.calculateAspectRatio(rt);
        }
    }
    /**
     * Attempt to start XR session with this camera.
     *
     * @param {string} type - The type of session. Can be one of the following:
     *
     * - {@link XRTYPE_INLINE}: Inline - always available type of session. It has limited feature
     * availability and is rendered into HTML element.
     * - {@link XRTYPE_VR}: Immersive VR - session that provides exclusive access to the VR device
     * with the best available tracking features.
     * - {@link XRTYPE_AR}: Immersive AR - session that provides exclusive access to the VR/AR
     * device that is intended to be blended with the real-world environment.
     *
     * @param {string} spaceType - Reference space type. Can be one of the following:
     *
     * - {@link XRSPACE_VIEWER}: Viewer - always supported space with some basic tracking
     * capabilities.
     * - {@link XRSPACE_LOCAL}: Local - represents a tracking space with a native origin near the
     * viewer at the time of creation. It is meant for seated or basic local XR sessions.
     * - {@link XRSPACE_LOCALFLOOR}: Local Floor - represents a tracking space with a native origin
     * at the floor in a safe position for the user to stand. The y-axis equals 0 at floor level.
     * Floor level value might be estimated by the underlying platform. It is meant for seated or
     * basic local XR sessions.
     * - {@link XRSPACE_BOUNDEDFLOOR}: Bounded Floor - represents a tracking space with its native
     * origin at the floor, where the user is expected to move within a pre-established boundary.
     * - {@link XRSPACE_UNBOUNDED}: Unbounded - represents a tracking space where the user is
     * expected to move freely around their environment, potentially long distances from their
     * starting point.
     *
     * @param {object} [options] - Object with options for XR session initialization.
     * @param {string[]} [options.optionalFeatures] - Optional features for XRSession start. It is
     * used for getting access to additional WebXR spec extensions.
     * @param {boolean} [options.imageTracking] - Set to true to attempt to enable {@link XrImageTracking}.
     * @param {boolean} [options.planeDetection] - Set to true to attempt to enable {@link XrPlaneDetection}.
     * @param {XrErrorCallback} [options.callback] - Optional callback function called once the
     * session is started. The callback has one argument Error - it is null if the XR session
     * started successfully.
     * @param {boolean} [options.anchors] - Optional boolean to attempt to enable {@link XrAnchors}.
     * @param {object} [options.depthSensing] - Optional object with parameters to attempt to enable
     * depth sensing.
     * @param {string} [options.depthSensing.usagePreference] - Optional usage preference for depth
     * sensing, can be 'cpu-optimized' or 'gpu-optimized' (XRDEPTHSENSINGUSAGE_*), defaults to
     * 'cpu-optimized'. Most preferred and supported will be chosen by the underlying depth sensing
     * system.
     * @param {string} [options.depthSensing.dataFormatPreference] - Optional data format
     * preference for depth sensing. Can be 'luminance-alpha' or 'float32' (XRDEPTHSENSINGFORMAT_*),
     * defaults to 'luminance-alpha'. Most preferred and supported will be chosen by the underlying
     * depth sensing system.
     * @example
     * // On an entity with a camera component
     * this.entity.camera.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCAL, {
     *     callback: (err) => {
     *         if (err) {
     *             // failed to start XR session
     *         } else {
     *             // in XR
     *         }
     *     }
     * });
     */ startXr(type, spaceType, options) {
        this.system.app.xr.start(this, type, spaceType, options);
    }
    /**
     * Attempt to end XR session of this camera.
     *
     * @param {XrErrorCallback} [callback] - Optional callback function called once session is
     * ended. The callback has one argument Error - it is null if successfully ended XR session.
     * @example
     * // On an entity with a camera component
     * this.entity.camera.endXr((err) => {
     *     // not anymore in XR
     * });
     */ endXr(callback) {
        if (!this._camera.xr) {
            if (callback) callback(new Error('Camera is not in XR'));
            return;
        }
        this._camera.xr.end(callback);
    }
    /**
     * Function to copy properties from the source CameraComponent. Properties not copied:
     * postEffects. Inherited properties not copied (all): system, entity, enabled.
     *
     * @param {CameraComponent} source - The source component.
     * @ignore
     */ copy(source) {
        this.aperture = source.aperture;
        this.aspectRatio = source.aspectRatio;
        this.aspectRatioMode = source.aspectRatioMode;
        this.calculateProjection = source.calculateProjection;
        this.calculateTransform = source.calculateTransform;
        this.clearColor = source.clearColor;
        this.clearColorBuffer = source.clearColorBuffer;
        this.clearDepthBuffer = source.clearDepthBuffer;
        this.clearStencilBuffer = source.clearStencilBuffer;
        this.cullFaces = source.cullFaces;
        this.disablePostEffectsLayer = source.disablePostEffectsLayer;
        this.farClip = source.farClip;
        this.flipFaces = source.flipFaces;
        this.fov = source.fov;
        this.frustumCulling = source.frustumCulling;
        this.horizontalFov = source.horizontalFov;
        this.layers = source.layers;
        this.nearClip = source.nearClip;
        this.orthoHeight = source.orthoHeight;
        this.priority = source.priority;
        this.projection = source.projection;
        this.rect = source.rect;
        this.renderTarget = source.renderTarget;
        this.scissorRect = source.scissorRect;
        this.sensitivity = source.sensitivity;
        this.shutter = source.shutter;
    }
}

class CameraComponentData {
    constructor(){
        this.enabled = true;
    }
}

/**
 * @import { AppBase } from '../../app-base.js'
 */ const _schema$1 = [
    'enabled'
];
/**
 * Used to add and remove {@link CameraComponent}s from Entities. It also holds an array of all
 * active cameras.
 *
 * @category Graphics
 */ class CameraComponentSystem extends ComponentSystem {
    /**
     * Create a new CameraComponentSystem instance.
     *
     * @param {AppBase} app - The Application.
     * @ignore
     */ constructor(app){
        super(app), /**
     * Holds all the active camera components.
     *
     * @type {CameraComponent[]}
     */ this.cameras = [];
        this.id = 'camera';
        this.ComponentType = CameraComponent;
        this.DataType = CameraComponentData;
        this.schema = _schema$1;
        this.on('beforeremove', this.onBeforeRemove, this);
        this.app.on('prerender', this.onAppPrerender, this);
    }
    initializeComponentData(component, data, properties) {
        properties = [
            'aspectRatio',
            'aspectRatioMode',
            'calculateProjection',
            'calculateTransform',
            'clearColor',
            'clearColorBuffer',
            'clearDepthBuffer',
            'clearStencilBuffer',
            'renderSceneColorMap',
            'renderSceneDepthMap',
            'cullFaces',
            'farClip',
            'flipFaces',
            'fog',
            'fov',
            'frustumCulling',
            'horizontalFov',
            'layers',
            'renderTarget',
            'nearClip',
            'orthoHeight',
            'projection',
            'priority',
            'rect',
            'scissorRect',
            'aperture',
            'shutter',
            'sensitivity',
            'gammaCorrection',
            'toneMapping'
        ];
        for(let i = 0; i < properties.length; i++){
            const property = properties[i];
            if (data.hasOwnProperty(property)) {
                const value = data[property];
                switch(property){
                    case 'rect':
                    case 'scissorRect':
                        if (Array.isArray(value)) {
                            component[property] = new Vec4(value[0], value[1], value[2], value[3]);
                        } else {
                            component[property] = value;
                        }
                        break;
                    case 'clearColor':
                        if (Array.isArray(value)) {
                            component[property] = new Color(value[0], value[1], value[2], value[3]);
                        } else {
                            component[property] = value;
                        }
                        break;
                    default:
                        component[property] = value;
                        break;
                }
            }
        }
        super.initializeComponentData(component, data, [
            'enabled'
        ]);
    }
    cloneComponent(entity, clone) {
        const c = entity.camera;
        return this.addComponent(clone, {
            aspectRatio: c.aspectRatio,
            aspectRatioMode: c.aspectRatioMode,
            calculateProjection: c.calculateProjection,
            calculateTransform: c.calculateTransform,
            clearColor: c.clearColor,
            clearColorBuffer: c.clearColorBuffer,
            clearDepthBuffer: c.clearDepthBuffer,
            clearStencilBuffer: c.clearStencilBuffer,
            renderSceneDepthMap: c.renderSceneDepthMap,
            renderSceneColorMap: c.renderSceneColorMap,
            cullFaces: c.cullFaces,
            enabled: c.enabled,
            farClip: c.farClip,
            flipFaces: c.flipFaces,
            fov: c.fov,
            frustumCulling: c.frustumCulling,
            horizontalFov: c.horizontalFov,
            layers: c.layers,
            renderTarget: c.renderTarget,
            nearClip: c.nearClip,
            orthoHeight: c.orthoHeight,
            projection: c.projection,
            priority: c.priority,
            rect: c.rect,
            scissorRect: c.scissorRect,
            aperture: c.aperture,
            sensitivity: c.sensitivity,
            shutter: c.shutter,
            gammaCorrection: c.gammaCorrection,
            toneMapping: c.toneMapping
        });
    }
    onBeforeRemove(entity, component) {
        this.removeCamera(component);
        component.onRemove();
    }
    onAppPrerender() {
        for(let i = 0, len = this.cameras.length; i < len; i++){
            this.cameras[i].onAppPrerender();
        }
    }
    addCamera(camera) {
        this.cameras.push(camera);
        sortPriority(this.cameras);
    }
    removeCamera(camera) {
        const index = this.cameras.indexOf(camera);
        if (index >= 0) {
            this.cameras.splice(index, 1);
            sortPriority(this.cameras);
        }
    }
    destroy() {
        this.app.off('prerender', this.onAppPrerender, this);
        super.destroy();
    }
}
Component._buildAccessors(CameraComponent.prototype, _schema$1);

/**
 * @import { Light } from '../../../scene/light.js'
 */ class LightComponentData {
    constructor(){
        this.enabled = true;
        this.type = 'directional';
        this.color = new Color(1, 1, 1);
        this.intensity = 1;
        this.luminance = 0;
        this.shape = LIGHTSHAPE_PUNCTUAL;
        this.affectSpecularity = true;
        this.castShadows = false;
        this.shadowDistance = 40;
        this.shadowIntensity = 1;
        this.shadowResolution = 1024;
        this.shadowBias = 0.05;
        this.numCascades = 1;
        this.cascadeBlend = 0;
        this.bakeNumSamples = 1;
        this.bakeArea = 0;
        this.cascadeDistribution = 0.5;
        this.normalOffsetBias = 0;
        this.range = 10;
        this.innerConeAngle = 40;
        this.outerConeAngle = 45;
        this.falloffMode = LIGHTFALLOFF_LINEAR;
        this.shadowType = SHADOW_PCF3_32F;
        this.vsmBlurSize = 11;
        this.vsmBlurMode = BLUR_GAUSSIAN;
        this.vsmBias = 0.01 * 0.25;
        this.cookieAsset = null;
        this.cookie = null;
        this.cookieIntensity = 1;
        this.cookieFalloff = true;
        this.cookieChannel = 'rgb';
        this.cookieAngle = 0;
        this.cookieScale = null;
        this.cookieOffset = null;
        this.shadowUpdateMode = SHADOWUPDATE_REALTIME;
        this.mask = 1;
        this.affectDynamic = true;
        this.affectLightmapped = false;
        this.bake = false;
        this.bakeDir = true;
        this.isStatic = false;
        this.layers = [
            LAYERID_WORLD
        ];
        this.penumbraSize = 1;
        this.penumbraFalloff = 1;
        this.shadowSamples = 16;
        this.shadowBlockerSamples = 16;
    }
}
const properties = Object.keys(new LightComponentData());

/**
 * @import { Color } from '../../../core/math/color.js'
 * @import { EventHandle } from '../../../core/event-handle.js'
 * @import { LightComponentData } from './data.js'
 * @import { Light } from '../../../scene/light.js'
 * @import { Texture } from '../../../platform/graphics/texture.js'
 * @import { Vec2 } from '../../../core/math/vec2.js'
 */ /**
 * The LightComponent enables an {@link Entity} to light the scene. There are three types of light:
 *
 * - `directional`: A global light that emits light in the direction of the negative y-axis of the
 * owner entity. Emulates light sources that appear to be infinitely far away such as the sun. The
 * owner entity's position is effectively ignored.
 * - `omni`: A local light that emits light in all directions from the owner entity's position.
 * Emulates candles, lamps, bulbs, etc.
 * - `spot`: A local light that emits light similarly to an omni light but is bounded by a cone
 * centered on the owner entity's negative y-axis. Emulates flashlights, spotlights, etc.
 *
 * You should never need to use the LightComponent constructor directly. To add an LightComponent
 * to an {@link Entity}, use {@link Entity#addComponent}:
 *
 * ```javascript
 * const entity = new pc.Entity();
 * entity.addComponent('light', {
 *     type: 'omni',
 *     color: new pc.Color(1, 0, 0),
 *     intensity: 2
 * });
 * ```
 *
 * Once the LightComponent is added to the entity, you can access it via the {@link Entity#light}
 * property:
 *
 * ```javascript
 * entity.light.intensity = 3; // Set the intensity of the light
 *
 * console.log(entity.light.intensity); // Get the intensity of the light
 * ```
 *
 * Relevant Engine API examples:
 *
 * - [Area Lights](https://playcanvas.github.io/#/graphics/area-lights)
 * - [Clustered Area Lights](https://playcanvas.github.io/#/graphics/clustered-area-lights)
 * - [Clustered Lighting](https://playcanvas.github.io/#/graphics/clustered-lighting)
 * - [Clustered Onmi Shadows](https://playcanvas.github.io/#/graphics/clustered-omni-shadows)
 * - [Clustered Spot Shadows](https://playcanvas.github.io/#/graphics/clustered-spot-shadows)
 * - [Lights](https://playcanvas.github.io/#/graphics/lights)
 *
 * @hideconstructor
 * @category Graphics
 */ class LightComponent extends Component {
    // TODO: Remove this override in upgrading component
    /**
     * @type {LightComponentData}
     * @ignore
     */ get data() {
        const record = this.system.store[this.entity.getGuid()];
        return record ? record.data : null;
    }
    /**
     * Sets the enabled state of the component.
     *
     * @type {boolean}
     */ set enabled(arg) {
        this._setValue('enabled', arg, function(newValue, oldValue) {
            this.onSetEnabled(null, oldValue, newValue);
        });
    }
    /**
     * Gets the enabled state of the component.
     *
     * @type {boolean}
     */ get enabled() {
        return this.data.enabled;
    }
    /**
     * @type {Light}
     * @ignore
     */ set light(arg) {
        this._setValue('light', arg);
    }
    /**
     * @type {Light}
     * @ignore
     */ get light() {
        return this.data.light;
    }
    /**
     * Sets the type of the light. Can be:
     *
     * - "directional": A light that is infinitely far away and lights the entire scene from one
     * direction.
     * - "omni": An omni-directional light that illuminates in all directions from the light source.
     * - "spot": An omni-directional light but is bounded by a cone.
     *
     * Defaults to "directional".
     *
     * @type {string}
     */ set type(arg) {
        this._setValue('type', arg, function(newValue, oldValue) {
            this.system.changeType(this, oldValue, newValue);
            this.refreshProperties();
        });
    }
    /**
     * Gets the type of the light.
     *
     * @type {string}
     */ get type() {
        return this.data.type;
    }
    /**
     * Sets the color of the light. The alpha component of the color is ignored. Defaults to white
     * (`[1, 1, 1]`).
     *
     * @type {Color};
     */ set color(arg) {
        this._setValue('color', arg, function(newValue, oldValue) {
            this.light.setColor(newValue);
        }, true);
    }
    /**
     * Gets the color of the light.
     *
     * @type {Color};
     */ get color() {
        return this.data.color;
    }
    /**
     * Sets the brightness of the light. Defaults to 1.
     *
     * @type {number}
     */ set intensity(arg) {
        this._setValue('intensity', arg, function(newValue, oldValue) {
            this.light.intensity = newValue;
        });
    }
    /**
     * Gets the brightness of the light.
     *
     * @type {number}
     */ get intensity() {
        return this.data.intensity;
    }
    /**
     * Sets the physically-based luminance. Only used if `scene.physicalUnits` is true. Defaults to 0.
     *
     * @type {number}
     */ set luminance(arg) {
        this._setValue('luminance', arg, function(newValue, oldValue) {
            this.light.luminance = newValue;
        });
    }
    /**
     * Gets the physically-based luminance.
     *
     * @type {number}
     */ get luminance() {
        return this.data.luminance;
    }
    /**
     * Sets the light source shape. Can be:
     *
     * - {@link LIGHTSHAPE_PUNCTUAL}: Infinitesimally small point.
     * - {@link LIGHTSHAPE_RECT}: Rectangle shape.
     * - {@link LIGHTSHAPE_DISK}: Disk shape.
     * - {@link LIGHTSHAPE_SPHERE}: Sphere shape.
     *
     * Defaults to pc.LIGHTSHAPE_PUNCTUAL.
     *
     * @type {number}
     */ set shape(arg) {
        this._setValue('shape', arg, function(newValue, oldValue) {
            this.light.shape = newValue;
        });
    }
    /**
     * Gets the light source shape.
     *
     * @type {number}
     */ get shape() {
        return this.data.shape;
    }
    /**
     * Sets whether material specularity will be affected by this light. Ignored for lights other
     * than {@link LIGHTTYPE_DIRECTIONAL}. Defaults to true.
     *
     * @type {boolean}
     */ set affectSpecularity(arg) {
        this._setValue('affectSpecularity', arg, function(newValue, oldValue) {
            this.light.affectSpecularity = newValue;
        });
    }
    /**
     * Gets whether material specularity will be affected by this light.
     *
     * @type {boolean}
     */ get affectSpecularity() {
        return this.data.affectSpecularity;
    }
    /**
     * Sets whether the light will cast shadows. Defaults to false.
     *
     * @type {boolean}
     */ set castShadows(arg) {
        this._setValue('castShadows', arg, function(newValue, oldValue) {
            this.light.castShadows = newValue;
        });
    }
    /**
     * Gets whether the light will cast shadows.
     *
     * @type {boolean}
     */ get castShadows() {
        return this.data.castShadows;
    }
    /**
     * Sets the distance from the viewpoint beyond which shadows are no longer rendered. Affects
     * directional lights only. Defaults to 40.
     *
     * @type {number}
     */ set shadowDistance(arg) {
        this._setValue('shadowDistance', arg, function(newValue, oldValue) {
            this.light.shadowDistance = newValue;
        });
    }
    /**
     * Gets the distance from the viewpoint beyond which shadows are no longer rendered.
     *
     * @type {number}
     */ get shadowDistance() {
        return this.data.shadowDistance;
    }
    /**
     * Sets the intensity of the shadow darkening. 0 having no effect and 1 meaning shadows are
     * entirely black. Defaults to 1.
     *
     * @type {number}
     */ set shadowIntensity(arg) {
        this._setValue('shadowIntensity', arg, function(newValue, oldValue) {
            this.light.shadowIntensity = newValue;
        });
    }
    /**
     * Gets the intensity of the shadow darkening.
     *
     * @type {number}
     */ get shadowIntensity() {
        return this.data.shadowIntensity;
    }
    /**
     * Sets the size of the texture used for the shadow map. Valid sizes are 64, 128, 256, 512,
     * 1024, 2048. Defaults to 1024.
     *
     * @type {number}
     */ set shadowResolution(arg) {
        this._setValue('shadowResolution', arg, function(newValue, oldValue) {
            this.light.shadowResolution = newValue;
        });
    }
    /**
     * Gets the size of the texture used for the shadow map.
     *
     * @type {number}
     */ get shadowResolution() {
        return this.data.shadowResolution;
    }
    /**
     * Set the depth bias for tuning the appearance of the shadow mapping generated by this light. Valid
     * range is 0 to 1. Defaults to 0.05.
     *
     * @type {number}
     */ set shadowBias(arg) {
        this._setValue('shadowBias', arg, function(newValue, oldValue) {
            this.light.shadowBias = -0.01 * math.clamp(newValue, 0, 1);
        });
    }
    /**
     * Get the depth bias for tuning the appearance of the shadow mapping generated by this light.
     *
     * @type {number}
     */ get shadowBias() {
        return this.data.shadowBias;
    }
    /**
     * Sets the number of shadow cascades. Can be 1, 2, 3 or 4. Defaults to 1, representing no
     * cascades.
     *
     * @type {number}
     */ set numCascades(arg) {
        this._setValue('numCascades', arg, function(newValue, oldValue) {
            this.light.numCascades = math.clamp(Math.floor(newValue), 1, 4);
        });
    }
    /**
     * Gets the number of shadow cascades.
     *
     * @type {number}
     */ get numCascades() {
        return this.data.numCascades;
    }
    /**
     * Sets the blend factor for cascaded shadow maps, defining the fraction of each cascade level
     * used for blending between adjacent cascades. The value should be between 0 and 1, with
     * a default of 0, which disables blending between cascades.
     *
     * @type {number}
     */ set cascadeBlend(value) {
        this._setValue('cascadeBlend', value, function(newValue, oldValue) {
            this.light.cascadeBlend = math.clamp(newValue, 0, 1);
        });
    }
    /**
     * Gets the blend factor for cascaded shadow maps.
     *
     * @type {number}
     */ get cascadeBlend() {
        return this.data.cascadeBlend;
    }
    /**
     * Sets the number of samples used to bake this light into the lightmap. Defaults to 1. Maximum
     * value is 255.
     *
     * @type {number}
     */ set bakeNumSamples(arg) {
        this._setValue('bakeNumSamples', arg, function(newValue, oldValue) {
            this.light.bakeNumSamples = math.clamp(Math.floor(newValue), 1, 255);
        });
    }
    /**
     * Gets the number of samples used to bake this light into the lightmap.
     *
     * @type {number}
     */ get bakeNumSamples() {
        return this.data.bakeNumSamples;
    }
    /**
     * Sets the penumbra angle in degrees, allowing for a soft shadow boundary. Defaults to 0.
     * Requires `bake` to be set to true and the light type is {@link LIGHTTYPE_DIRECTIONAL}.
     *
     * @type {number}
     */ set bakeArea(arg) {
        this._setValue('bakeArea', arg, function(newValue, oldValue) {
            this.light.bakeArea = math.clamp(newValue, 0, 180);
        });
    }
    /**
     * Gets the penumbra angle in degrees.
     *
     * @type {number}
     */ get bakeArea() {
        return this.data.bakeArea;
    }
    /**
     * Sets the distribution of subdivision of the camera frustum for individual shadow cascades.
     * Only used if {@link LightComponent#numCascades} is larger than 1. Can be a value in range of
     * 0 and 1. Value of 0 represents a linear distribution, value of 1 represents a logarithmic
     * distribution. Defaults to 0.5. Larger value increases the resolution of the shadows in the
     * near distance.
     *
     * @type {number}
     */ set cascadeDistribution(arg) {
        this._setValue('cascadeDistribution', arg, function(newValue, oldValue) {
            this.light.cascadeDistribution = math.clamp(newValue, 0, 1);
        });
    }
    /**
     * Gets the distribution of subdivision of the camera frustum for individual shadow cascades.
     *
     * @type {number}
     */ get cascadeDistribution() {
        return this.data.cascadeDistribution;
    }
    /**
     * Sets the normal offset depth bias. Valid range is 0 to 1. Defaults to 0.
     *
     * @type {number}
     */ set normalOffsetBias(arg) {
        this._setValue('normalOffsetBias', arg, function(newValue, oldValue) {
            this.light.normalOffsetBias = math.clamp(newValue, 0, 1);
        });
    }
    /**
     * Gets the normal offset depth bias.
     *
     * @type {number}
     */ get normalOffsetBias() {
        return this.data.normalOffsetBias;
    }
    /**
     * Sets the range of the light. Affects omni and spot lights only. Defaults to 10.
     *
     * @type {number}
     */ set range(arg) {
        this._setValue('range', arg, function(newValue, oldValue) {
            this.light.attenuationEnd = newValue;
        });
    }
    /**
     * Gets the range of the light.
     *
     * @type {number}
     */ get range() {
        return this.data.range;
    }
    /**
     * Sets the angle at which the spotlight cone starts to fade off. The angle is specified in
     * degrees. Affects spot lights only. Defaults to 40.
     *
     * @type {number}
     */ set innerConeAngle(arg) {
        this._setValue('innerConeAngle', arg, function(newValue, oldValue) {
            this.light.innerConeAngle = newValue;
        });
    }
    /**
     * Gets the angle at which the spotlight cone starts to fade off.
     *
     * @type {number}
     */ get innerConeAngle() {
        return this.data.innerConeAngle;
    }
    /**
     * Sets the angle at which the spotlight cone has faded to nothing. The angle is specified in
     * degrees. Affects spot lights only. Defaults to 45.
     *
     * @type {number}
     */ set outerConeAngle(arg) {
        this._setValue('outerConeAngle', arg, function(newValue, oldValue) {
            this.light.outerConeAngle = newValue;
        });
    }
    /**
     * Gets the angle at which the spotlight cone has faded to nothing.
     *
     * @type {number}
     */ get outerConeAngle() {
        return this.data.outerConeAngle;
    }
    /**
     * Sets the fall off mode for the light. This controls the rate at which a light attenuates
     * from its position. Can be:
     *
     * - {@link LIGHTFALLOFF_LINEAR}: Linear.
     * - {@link LIGHTFALLOFF_INVERSESQUARED}: Inverse squared.
     *
     * Affects omni and spot lights only. Defaults to {@link LIGHTFALLOFF_LINEAR}.
     *
     * @type {number}
     */ set falloffMode(arg) {
        this._setValue('falloffMode', arg, function(newValue, oldValue) {
            this.light.falloffMode = newValue;
        });
    }
    /**
     * Gets the fall off mode for the light.
     *
     * @type {number}
     */ get falloffMode() {
        return this.data.falloffMode;
    }
    /**
     * Sets the type of shadows being rendered by this light. Can be:
     *
     * - {@link SHADOW_PCF1_32F}
     * - {@link SHADOW_PCF3_32F}
     * - {@link SHADOW_PCF5_32F}
     * - {@link SHADOW_PCF1_16F}
     * - {@link SHADOW_PCF3_16F}
     * - {@link SHADOW_PCF5_16F}
     * - {@link SHADOW_VSM_16F}
     * - {@link SHADOW_VSM_32F}
     * - {@link SHADOW_PCSS_32F}
     *
     * @type {number}
     */ set shadowType(arg) {
        this._setValue('shadowType', arg, function(newValue, oldValue) {
            this.light.shadowType = newValue;
        });
    }
    /**
     * Gets the type of shadows being rendered by this light.
     *
     * @type {number}
     */ get shadowType() {
        return this.data.shadowType;
    }
    /**
     * Sets the number of samples used for blurring a variance shadow map. Only uneven numbers
     * work, even are incremented. Minimum value is 1, maximum is 25. Defaults to 11.
     *
     * @type {number}
     */ set vsmBlurSize(arg) {
        this._setValue('vsmBlurSize', arg, function(newValue, oldValue) {
            this.light.vsmBlurSize = newValue;
        });
    }
    /**
     * Gets the number of samples used for blurring a variance shadow map.
     *
     * @type {number}
     */ get vsmBlurSize() {
        return this.data.vsmBlurSize;
    }
    /**
     * Sets the blurring mode for variance shadow maps. Can be:
     *
     * - {@link BLUR_BOX}: Box filter.
     * - {@link BLUR_GAUSSIAN}: Gaussian filter. May look smoother than box, but requires more samples.
     *
     * @type {number}
     */ set vsmBlurMode(arg) {
        this._setValue('vsmBlurMode', arg, function(newValue, oldValue) {
            this.light.vsmBlurMode = newValue;
        });
    }
    /**
     * Gets the blurring mode for variance shadow maps.
     *
     * @type {number}
     */ get vsmBlurMode() {
        return this.data.vsmBlurMode;
    }
    /**
     * Sets the VSM bias value.
     *
     * @type {number}
     */ set vsmBias(arg) {
        this._setValue('vsmBias', arg, function(newValue, oldValue) {
            this.light.vsmBias = math.clamp(newValue, 0, 1);
        });
    }
    /**
     * Gets the VSM bias value.
     *
     * @type {number}
     */ get vsmBias() {
        return this.data.vsmBias;
    }
    /**
     * Sets the texture asset to be used as the cookie for this light. Only spot and omni lights can
     * have cookies. Defaults to null.
     *
     * @type {number|null}
     */ set cookieAsset(arg) {
        this._setValue('cookieAsset', arg, function(newValue, oldValue) {
            if (this._cookieAssetId && (newValue instanceof Asset && newValue.id === this._cookieAssetId || newValue === this._cookieAssetId)) {
                return;
            }
            this.onCookieAssetRemove();
            this._cookieAssetId = null;
            if (newValue instanceof Asset) {
                this.data.cookieAsset = newValue.id;
                this._cookieAssetId = newValue.id;
                this.onCookieAssetAdd(newValue);
            } else if (typeof newValue === 'number') {
                this._cookieAssetId = newValue;
                const asset = this.system.app.assets.get(newValue);
                if (asset) {
                    this.onCookieAssetAdd(asset);
                } else {
                    this._cookieAssetAdd = true;
                    this.system.app.assets.on(`add:${this._cookieAssetId}`, this.onCookieAssetAdd, this);
                }
            }
        });
    }
    /**
     * Gets the texture asset to be used as the cookie for this light.
     *
     * @type {number|null}
     */ get cookieAsset() {
        return this.data.cookieAsset;
    }
    /**
     * Sets the texture to be used as the cookie for this light. Only spot and omni lights can have
     * cookies. Defaults to null.
     *
     * @type {Texture|null}
     */ set cookie(arg) {
        this._setValue('cookie', arg, function(newValue, oldValue) {
            this.light.cookie = newValue;
        });
    }
    /**
     * Gets the texture to be used as the cookie for this light.
     *
     * @type {Texture|null}
     */ get cookie() {
        return this.data.cookie;
    }
    /**
     * Sets the cookie texture intensity. Defaults to 1.
     *
     * @type {number}
     */ set cookieIntensity(arg) {
        this._setValue('cookieIntensity', arg, function(newValue, oldValue) {
            this.light.cookieIntensity = math.clamp(newValue, 0, 1);
        });
    }
    /**
     * Gets the cookie texture intensity.
     *
     * @type {number}
     */ get cookieIntensity() {
        return this.data.cookieIntensity;
    }
    /**
     * Sets whether normal spotlight falloff is active when a cookie texture is set. When set to
     * false, a spotlight will work like a pure texture projector (only fading with distance).
     * Default is false.
     *
     * @type {boolean}
     */ set cookieFalloff(arg) {
        this._setValue('cookieFalloff', arg, function(newValue, oldValue) {
            this.light.cookieFalloff = newValue;
        });
    }
    /**
     * Gets whether normal spotlight falloff is active when a cookie texture is set.
     *
     * @type {boolean}
     */ get cookieFalloff() {
        return this.data.cookieFalloff;
    }
    /**
     * Sets the color channels of the cookie texture to use. Can be "r", "g", "b", "a", "rgb".
     *
     * @type {string}
     */ set cookieChannel(arg) {
        this._setValue('cookieChannel', arg, function(newValue, oldValue) {
            this.light.cookieChannel = newValue;
        });
    }
    /**
     * Gets the color channels of the cookie texture to use.
     *
     * @type {string}
     */ get cookieChannel() {
        return this.data.cookieChannel;
    }
    /**
     * Sets the angle for spotlight cookie rotation (in degrees).
     *
     * @type {number}
     */ set cookieAngle(arg) {
        this._setValue('cookieAngle', arg, function(newValue, oldValue) {
            if (newValue !== 0 || this.cookieScale !== null) {
                if (!this._cookieMatrix) this._cookieMatrix = new Vec4();
                let scx = 1;
                let scy = 1;
                if (this.cookieScale) {
                    scx = this.cookieScale.x;
                    scy = this.cookieScale.y;
                }
                const c = Math.cos(newValue * math.DEG_TO_RAD);
                const s = Math.sin(newValue * math.DEG_TO_RAD);
                this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);
                this.light.cookieTransform = this._cookieMatrix;
            } else {
                this.light.cookieTransform = null;
            }
        });
    }
    /**
     * Gets the angle for spotlight cookie rotation (in degrees).
     *
     * @type {number}
     */ get cookieAngle() {
        return this.data.cookieAngle;
    }
    /**
     * Sets the spotlight cookie scale.
     *
     * @type {Vec2|null}
     */ set cookieScale(arg) {
        this._setValue('cookieScale', arg, function(newValue, oldValue) {
            if (newValue !== null || this.cookieAngle !== 0) {
                if (!this._cookieMatrix) this._cookieMatrix = new Vec4();
                const scx = newValue.x;
                const scy = newValue.y;
                const c = Math.cos(this.cookieAngle * math.DEG_TO_RAD);
                const s = Math.sin(this.cookieAngle * math.DEG_TO_RAD);
                this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);
                this.light.cookieTransform = this._cookieMatrix;
            } else {
                this.light.cookieTransform = null;
            }
        }, true);
    }
    /**
     * Gets the spotlight cookie scale.
     *
     * @type {Vec2|null}
     */ get cookieScale() {
        return this.data.cookieScale;
    }
    /**
     * Sets the spotlight cookie position offset.
     *
     * @type {Vec2|null}
     */ set cookieOffset(arg) {
        this._setValue('cookieOffset', arg, function(newValue, oldValue) {
            this.light.cookieOffset = newValue;
        }, true);
    }
    /**
     * Gets the spotlight cookie position offset.
     *
     * @type {Vec2|null}
     */ get cookieOffset() {
        return this.data.cookieOffset;
    }
    /**
     * Sets the shadow update model. This tells the renderer how often shadows must be updated for
     * this light. Can be:
     *
     * - {@link SHADOWUPDATE_NONE}: Don't render shadows.
     * - {@link SHADOWUPDATE_THISFRAME}: Render shadows only once (then automatically switches
     * to {@link SHADOWUPDATE_NONE}.
     * - {@link SHADOWUPDATE_REALTIME}: Render shadows every frame (default).
     *
     * @type {number}
     */ set shadowUpdateMode(arg) {
        this._setValue('shadowUpdateMode', arg, function(newValue, oldValue) {
            this.light.shadowUpdateMode = newValue;
        }, true);
    }
    /**
     * Gets the shadow update model.
     *
     * @type {number}
     */ get shadowUpdateMode() {
        return this.data.shadowUpdateMode;
    }
    /**
     * Sets the mask to determine which {@link MeshInstance}s are lit by this light. Defaults to 1.
     *
     * @type {number}
     */ set mask(arg) {
        this._setValue('mask', arg, function(newValue, oldValue) {
            this.light.mask = newValue;
        });
    }
    /**
     * Gets the mask to determine which {@link MeshInstance}s are lit by this light.
     *
     * @type {number}
     */ get mask() {
        return this.data.mask;
    }
    /**
     * Sets whether the light will affect non-lightmapped objects.
     *
     * @type {boolean}
     */ set affectDynamic(arg) {
        this._setValue('affectDynamic', arg, function(newValue, oldValue) {
            if (newValue) {
                this.light.mask |= MASK_AFFECT_DYNAMIC;
            } else {
                this.light.mask &= -2;
            }
            this.light.layersDirty();
        });
    }
    /**
     * Gets whether the light will affect non-lightmapped objects.
     *
     * @type {boolean}
     */ get affectDynamic() {
        return this.data.affectDynamic;
    }
    /**
     * Sets whether the light will affect lightmapped objects.
     *
     * @type {boolean}
     */ set affectLightmapped(arg) {
        this._setValue('affectLightmapped', arg, function(newValue, oldValue) {
            if (newValue) {
                this.light.mask |= MASK_AFFECT_LIGHTMAPPED;
                if (this.bake) this.light.mask &= -5;
            } else {
                this.light.mask &= -3;
                if (this.bake) this.light.mask |= MASK_BAKE;
            }
        });
    }
    /**
     * Gets whether the light will affect lightmapped objects.
     *
     * @type {boolean}
     */ get affectLightmapped() {
        return this.data.affectLightmapped;
    }
    /**
     * Sets whether the light will be rendered into lightmaps.
     *
     * @type {boolean}
     */ set bake(arg) {
        this._setValue('bake', arg, function(newValue, oldValue) {
            if (newValue) {
                this.light.mask |= MASK_BAKE;
                if (this.affectLightmapped) this.light.mask &= -3;
            } else {
                this.light.mask &= -5;
                if (this.affectLightmapped) this.light.mask |= MASK_AFFECT_LIGHTMAPPED;
            }
            this.light.layersDirty();
        });
    }
    /**
     * Gets whether the light will be rendered into lightmaps.
     *
     * @type {boolean}
     */ get bake() {
        return this.data.bake;
    }
    /**
     * Sets whether the light's direction will contribute to directional lightmaps. The light must
     * be enabled and `bake` set to true. Be aware, that directional lightmap is an approximation
     * and can only hold single direction per pixel. Intersecting multiple lights with bakeDir=true
     * may lead to incorrect look of specular/bump-mapping in the area of intersection. The error
     * is not always visible though, and highly scene-dependent.
     *
     * @type {boolean}
     */ set bakeDir(arg) {
        this._setValue('bakeDir', arg, function(newValue, oldValue) {
            this.light.bakeDir = newValue;
        });
    }
    /**
     * Gets whether the light's direction will contribute to directional lightmaps.
     *
     * @type {boolean}
     */ get bakeDir() {
        return this.data.bakeDir;
    }
    /**
     * Sets whether the light ever moves. This is an optimization hint.
     *
     * @type {boolean}
     */ set isStatic(arg) {
        this._setValue('isStatic', arg, function(newValue, oldValue) {
            this.light.isStatic = newValue;
        });
    }
    /**
     * Gets whether the light ever moves.
     *
     * @type {boolean}
     */ get isStatic() {
        return this.data.isStatic;
    }
    /**
     * Sets the array of layer IDs ({@link Layer#id}) to which this light should belong. Don't
     * push/pop/splice or modify this array. If you want to change it, set a new one instead.
     *
     * @type {number[]}
     */ set layers(arg) {
        this._setValue('layers', arg, function(newValue, oldValue) {
            for(let i = 0; i < oldValue.length; i++){
                const layer = this.system.app.scene.layers.getLayerById(oldValue[i]);
                if (!layer) continue;
                layer.removeLight(this);
                this.light.removeLayer(layer);
            }
            for(let i = 0; i < newValue.length; i++){
                const layer = this.system.app.scene.layers.getLayerById(newValue[i]);
                if (!layer) continue;
                if (this.enabled && this.entity.enabled) {
                    layer.addLight(this);
                    this.light.addLayer(layer);
                }
            }
        });
    }
    /**
     * Gets the array of layer IDs ({@link Layer#id}) to which this light should belong.
     *
     * @type {number[]}
     */ get layers() {
        return this.data.layers;
    }
    /**
     * Sets an array of SHADOWUPDATE_ settings per shadow cascade. Set to undefined if not used.
     *
     * @type {number[] | null}
     */ set shadowUpdateOverrides(values) {
        this.light.shadowUpdateOverrides = values;
    }
    /**
     * Gets an array of SHADOWUPDATE_ settings per shadow cascade.
     *
     * @type {number[] | null}
     */ get shadowUpdateOverrides() {
        return this.light.shadowUpdateOverrides;
    }
    /**
     * Sets the number of shadow samples used for soft shadows when the shadow type is
     * {@link SHADOW_PCSS_32F}. This value must be a positive whole number starting at 1. Higher
     * values result in smoother shadows but can significantly decrease performance. Defaults to 16.
     *
     * @type {number}
     */ set shadowSamples(value) {
        this.light.shadowSamples = value;
    }
    /**
     * Gets the number of shadow samples used for soft shadows.
     *
     * @type {number}
     */ get shadowSamples() {
        return this.light.shadowSamples;
    }
    /**
     * Sets the number of blocker samples used for soft shadows when the shadow type is
     * {@link SHADOW_PCSS_32F}. These samples are used to estimate the distance between the shadow
     * caster and the shadow receiver, which is then used for the estimation of contact hardening in
     * the shadow. This value must be a positive whole number starting at 0. Higher values improve
     * shadow quality by considering more occlusion points, but can decrease performance. When set
     * to 0, contact hardening is disabled and the shadow has constant softness. Defaults to 16. Note
     * that this values can be lower than shadowSamples to optimize performance, often without large
     * impact on quality.
     *
     * @type {number}
     */ set shadowBlockerSamples(value) {
        this.light.shadowBlockerSamples = value;
    }
    /**
     * Gets the number of blocker samples used for contact hardening shadows.
     *
     * @type {number}
     */ get shadowBlockerSamples() {
        return this.light.shadowBlockerSamples;
    }
    /**
     * Sets the size of penumbra for contact hardening shadows. For area lights, acts as a
     * multiplier with the dimensions of the area light. For punctual and directional lights it's
     * the area size of the light. Defaults to 1.
     *
     * @type {number}
     */ set penumbraSize(value) {
        this.light.penumbraSize = value;
    }
    /**
     * Gets the size of penumbra for contact hardening shadows.
     *
     * @type {number}
     */ get penumbraSize() {
        return this.light.penumbraSize;
    }
    /**
     * Sets the falloff rate for shadow penumbra for contact hardening shadows. This is a value larger
     * than or equal to 1. This parameter determines how quickly the shadow softens with distance.
     * Higher values result in a faster softening of the shadow, while lower values produce a more
     * gradual transition. Defaults to 1.
     *
     * @type {number}
     */ set penumbraFalloff(value) {
        this.light.penumbraFalloff = value;
    }
    /**
     * Gets the falloff rate for shadow penumbra for contact hardening shadows.
     *
     * @type {number}
     */ get penumbraFalloff() {
        return this.light.penumbraFalloff;
    }
    /** @ignore */ _setValue(name, value, setFunc, skipEqualsCheck) {
        const data = this.data;
        const oldValue = data[name];
        if (!skipEqualsCheck && oldValue === value) return;
        data[name] = value;
        if (setFunc) setFunc.call(this, value, oldValue);
    }
    addLightToLayers() {
        for(let i = 0; i < this.layers.length; i++){
            const layer = this.system.app.scene.layers.getLayerById(this.layers[i]);
            if (layer) {
                layer.addLight(this);
                this.light.addLayer(layer);
            }
        }
    }
    removeLightFromLayers() {
        for(let i = 0; i < this.layers.length; i++){
            const layer = this.system.app.scene.layers.getLayerById(this.layers[i]);
            if (layer) {
                layer.removeLight(this);
                this.light.removeLayer(layer);
            }
        }
    }
    onLayersChanged(oldComp, newComp) {
        if (this.enabled && this.entity.enabled) {
            this.addLightToLayers();
        }
        oldComp.off('add', this.onLayerAdded, this);
        oldComp.off('remove', this.onLayerRemoved, this);
        newComp.on('add', this.onLayerAdded, this);
        newComp.on('remove', this.onLayerRemoved, this);
    }
    onLayerAdded(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index >= 0 && this.enabled && this.entity.enabled) {
            layer.addLight(this);
            this.light.addLayer(layer);
        }
    }
    onLayerRemoved(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index >= 0) {
            layer.removeLight(this);
            this.light.removeLayer(layer);
        }
    }
    refreshProperties() {
        for(let i = 0; i < properties.length; i++){
            const name = properties[i];
            /* eslint-disable no-self-assign */ this[name] = this[name];
        /* eslint-enable no-self-assign */ }
        if (this.enabled && this.entity.enabled) {
            this.onEnable();
        }
    }
    onCookieAssetSet() {
        let forceLoad = false;
        if (this._cookieAsset.type === 'cubemap' && !this._cookieAsset.loadFaces) {
            this._cookieAsset.loadFaces = true;
            forceLoad = true;
        }
        if (!this._cookieAsset.resource || forceLoad) this.system.app.assets.load(this._cookieAsset);
        if (this._cookieAsset.resource) {
            this.onCookieAssetLoad();
        }
    }
    onCookieAssetAdd(asset) {
        if (this._cookieAssetId !== asset.id) return;
        this._cookieAsset = asset;
        if (this.light.enabled) {
            this.onCookieAssetSet();
        }
        this._cookieAsset.on('load', this.onCookieAssetLoad, this);
        this._cookieAsset.on('remove', this.onCookieAssetRemove, this);
    }
    onCookieAssetLoad() {
        if (!this._cookieAsset || !this._cookieAsset.resource) {
            return;
        }
        this.cookie = this._cookieAsset.resource;
    }
    onCookieAssetRemove() {
        if (!this._cookieAssetId) {
            return;
        }
        if (this._cookieAssetAdd) {
            this.system.app.assets.off(`add:${this._cookieAssetId}`, this.onCookieAssetAdd, this);
            this._cookieAssetAdd = false;
        }
        if (this._cookieAsset) {
            this._cookieAsset.off('load', this.onCookieAssetLoad, this);
            this._cookieAsset.off('remove', this.onCookieAssetRemove, this);
            this._cookieAsset = null;
        }
        this.cookie = null;
    }
    onEnable() {
        const scene = this.system.app.scene;
        const layers = scene.layers;
        this.light.enabled = true;
        this._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);
        if (layers) {
            this._evtLayerAdded = layers.on('add', this.onLayerAdded, this);
            this._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);
        }
        if (this.enabled && this.entity.enabled) {
            this.addLightToLayers();
        }
        if (this._cookieAsset && !this.cookie) {
            this.onCookieAssetSet();
        }
    }
    onDisable() {
        const scene = this.system.app.scene;
        const layers = scene.layers;
        this.light.enabled = false;
        this._evtLayersChanged?.off();
        this._evtLayersChanged = null;
        if (layers) {
            this._evtLayerAdded?.off();
            this._evtLayerAdded = null;
            this._evtLayerRemoved?.off();
            this._evtLayerRemoved = null;
        }
        this.removeLightFromLayers();
    }
    onRemove() {
        // remove from layers
        this.onDisable();
        // destroy light node
        this.light.destroy();
        // remove cookie asset events
        this.cookieAsset = null;
    }
    constructor(...args){
        super(...args), /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayersChanged = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerAdded = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerRemoved = null, /** @private */ this._cookieAsset = null, /** @private */ this._cookieAssetId = null, /** @private */ this._cookieAssetAdd = false, /** @private */ this._cookieMatrix = null;
    }
}

/**
 * @import { AppBase } from '../../app-base.js'
 */ /**
 * A Light Component is used to dynamically light the scene.
 *
 * @category Graphics
 */ class LightComponentSystem extends ComponentSystem {
    /**
     * Create a new LightComponentSystem instance.
     *
     * @param {AppBase} app - The application.
     * @ignore
     */ constructor(app){
        super(app);
        this.id = 'light';
        this.ComponentType = LightComponent;
        this.DataType = LightComponentData;
        this.on('beforeremove', this._onRemoveComponent, this);
    }
    initializeComponentData(component, _data) {
        // duplicate because we're modifying the data
        const data = {
            ..._data
        };
        if (!data.type) {
            data.type = component.data.type;
        }
        component.data.type = data.type;
        if (data.layers && Array.isArray(data.layers)) {
            data.layers = data.layers.slice(0);
        }
        if (data.color && Array.isArray(data.color)) {
            data.color = new Color(data.color[0], data.color[1], data.color[2]);
        }
        if (data.cookieOffset && data.cookieOffset instanceof Array) {
            data.cookieOffset = new Vec2(data.cookieOffset[0], data.cookieOffset[1]);
        }
        if (data.cookieScale && data.cookieScale instanceof Array) {
            data.cookieScale = new Vec2(data.cookieScale[0], data.cookieScale[1]);
        }
        if (data.enable) {
            console.warn('WARNING: enable: Property is deprecated. Set enabled property instead.');
            data.enabled = data.enable;
        }
        if (!data.shape) {
            data.shape = LIGHTSHAPE_PUNCTUAL;
        }
        const light = new Light(this.app.graphicsDevice, this.app.scene.clusteredLightingEnabled);
        light.type = lightTypes[data.type];
        light._node = component.entity;
        component.data.light = light;
        super.initializeComponentData(component, data, properties);
    }
    _onRemoveComponent(entity, component) {
        component.onRemove();
    }
    cloneComponent(entity, clone) {
        const light = entity.light;
        const data = [];
        let name;
        for(let i = 0; i < properties.length; i++){
            name = properties[i];
            if (name === 'light') {
                continue;
            }
            if (light[name] && light[name].clone) {
                data[name] = light[name].clone();
            } else {
                data[name] = light[name];
            }
        }
        return this.addComponent(clone, data);
    }
    changeType(component, oldValue, newValue) {
        if (oldValue !== newValue) {
            component.light.type = lightTypes[newValue];
        }
    }
}

// fragment shader to generate intervals texture for GSplat LOD system
var gsplatIntervalTextureGLSL = /* glsl */ `

precision highp usampler2D;

// RG32U: (start, accumulatedSum)
uniform usampler2D uIntervalsTexture;
uniform int uNumIntervals;
uniform int uTextureWidth;
uniform int uActiveSplats;

ivec2 getCoordFromIndex(int index, int textureWidth) {
    return ivec2(index % textureWidth, index / textureWidth);
}

void main() {
    ivec2 coord = ivec2(gl_FragCoord.xy);
    int targetIndex = coord.y * uTextureWidth + coord.x;
    
    if (targetIndex >= uActiveSplats) {
        gl_FragColor = 0u;
        return;
    }
    
    // Binary search through accumulated sums (G channel)
    int left = 0;
    int right = uNumIntervals - 1;
    int intervalIndex = 0;
    
    while (left <= right) {
        int mid = (left + right) / 2;
        
        int textureWidth = textureSize(uIntervalsTexture, 0).x;
        ivec2 intervalCoord = getCoordFromIndex(mid, textureWidth);
        uvec2 intervalData = texelFetch(uIntervalsTexture, intervalCoord, 0).rg;
        
        uint accumulatedSum = intervalData.g;  // G channel
        
        if (uint(targetIndex) < accumulatedSum) {
            intervalIndex = mid;
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    
    // Get interval data (both start and accumulated sum in one fetch)
    int textureWidth = textureSize(uIntervalsTexture, 0).x;
    ivec2 intervalCoord = getCoordFromIndex(intervalIndex, textureWidth);
    uvec2 intervalData = texelFetch(uIntervalsTexture, intervalCoord, 0).rg;
    
    uint intervalStart = intervalData.r;      // R channel
    uint currentAccSum = intervalData.g;      // G channel
    
    // Get previous accumulated sum
    uint prevAccSum = 0u;
    if (intervalIndex > 0) {
        ivec2 prevCoord = getCoordFromIndex(intervalIndex - 1, textureWidth);
        prevAccSum = texelFetch(uIntervalsTexture, prevCoord, 0).g;
    }
    
    // Calculate original splat index
    uint offsetInInterval = uint(targetIndex) - prevAccSum;
    uint originalIndex = intervalStart + offsetInInterval;
    
    gl_FragColor = originalIndex;
}
`;

// fragment shader to generate intervals texture for GSplat LOD system
var gsplatIntervalTextureWGSL = /* wgsl */ `

// RG32U: (start, accumulatedSum)
var uIntervalsTexture: texture_2d<u32>;
uniform uNumIntervals: i32;
uniform uTextureWidth: i32;
uniform uActiveSplats: i32;

fn getCoordFromIndex(index: i32, textureWidth: i32) -> vec2i {
    return vec2i(index % textureWidth, index / textureWidth);
}

@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
    var output: FragmentOutput;
    
    let coord = vec2i(i32(input.position.x), i32(input.position.y));
    let targetIndex = coord.y * uniform.uTextureWidth + coord.x;
    
    if (targetIndex >= uniform.uActiveSplats) {
        output.color = 0u;
        return output;
    }
    
    // Binary search through accumulated sums (G channel)
    var left = 0i;
    var right = uniform.uNumIntervals - 1;
    var intervalIndex = 0i;
    
    while (left <= right) {
        let mid = (left + right) / 2;
        
        let textureWidth = i32(textureDimensions(uIntervalsTexture, 0).x);
        let intervalCoord = getCoordFromIndex(mid, textureWidth);
        let intervalData = textureLoad(uIntervalsTexture, intervalCoord, 0).rg;
        
        let accumulatedSum = intervalData.g;  // G channel
        
        if (u32(targetIndex) < accumulatedSum) {
            intervalIndex = mid;
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    
    // Get interval data (both start and accumulated sum in one fetch)
    let textureWidth = i32(textureDimensions(uIntervalsTexture, 0).x);
    let intervalCoord = getCoordFromIndex(intervalIndex, textureWidth);
    let intervalData = textureLoad(uIntervalsTexture, intervalCoord, 0).rg;
    
    let intervalStart = intervalData.r;      // R channel
    let currentAccSum = intervalData.g;      // G channel
    
    // Get previous accumulated sum
    var prevAccSum = 0u;
    if (intervalIndex > 0) {
        let prevCoord = getCoordFromIndex(intervalIndex - 1, textureWidth);
        prevAccSum = textureLoad(uIntervalsTexture, prevCoord, 0).g;
    }
    
    // Calculate original splat index
    let offsetInInterval = u32(targetIndex) - prevAccSum;
    let originalIndex = intervalStart + offsetInInterval;
    
    output.color = originalIndex;
    return output;
}
`;

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { Shader } from '../../platform/graphics/shader.js'
 */ /**
 * Manages the intervals texture generation for GSplat LOD system using GPU acceleration. A list of
 * intervals is provided to the update method, and the texture is generated on the GPU. The texture
 * is then used to map target indices to source splat indices.
 *
 * @ignore
 */ class GSplatIntervalTexture {
    /**
     * @param {GraphicsDevice} device - The graphics device
     */ constructor(device){
        /**
     * Texture that maps target indices to source splat indices based on intervals
     *
     * @type {Texture|null}
     */ this.texture = null;
        /**
     * Render target for the intervals texture
     *
     * @type {RenderTarget|null}
     */ this.rt = null;
        /**
     * Texture that stores interval data (start + accumulated sum pairs) for GPU processing
     *
     * @type {Texture|null}
     */ this.intervalsDataTexture = null;
        /**
     * Shader for generating intervals texture on GPU
     *
     * @type {Shader|null}
     */ this.shader = null;
        this.device = device;
    }
    destroy() {
        this.texture?.destroy();
        this.texture = null;
        this.rt?.destroy();
        this.rt = null;
        this.intervalsDataTexture?.destroy();
        this.intervalsDataTexture = null;
        this.shader = null;
    }
    /**
     * Creates shader for GPU-based intervals texture generation
     */ getShader() {
        if (!this.shader) {
            this.shader = ShaderUtils.createShader(this.device, {
                uniqueName: 'GSplatIntervalsShader',
                attributes: {
                    aPosition: SEMANTIC_POSITION
                },
                vertexChunk: 'quadVS',
                fragmentGLSL: gsplatIntervalTextureGLSL,
                fragmentWGSL: gsplatIntervalTextureWGSL,
                fragmentOutputTypes: [
                    'uint'
                ]
            });
        }
        return this.shader;
    }
    /**
     * Creates a texture with specified parameters
     */ createTexture(name, format, width, height) {
        return new Texture(this.device, {
            name: name,
            width: width,
            height: height,
            format: format,
            cubemap: false,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE
        });
    }
    /**
     * Updates the intervals texture based on provided intervals array
     *
     * @param {number[]} intervals - Array of intervals (start, end pairs)
     * @returns {number} The number of active splats
     */ update(intervals) {
        if (!intervals || intervals.length === 0) {
            return 0;
        }
        // Count total number of splats referenced by intervals
        let totalSplats = 0;
        for(let i = 0; i < intervals.length; i += 2){
            const start = intervals[i];
            const end = intervals[i + 1];
            totalSplats += end - start;
        }
        // Calculate texture dimensions for output intervals texture
        const maxTextureSize = this.device.maxTextureSize;
        let textureWidth = Math.ceil(Math.sqrt(totalSplats));
        textureWidth = Math.min(textureWidth, maxTextureSize);
        const textureHeight = Math.ceil(totalSplats / textureWidth);
        // Create main intervals texture
        if (!this.texture) {
            this.texture = this.createTexture('intervalsTexture', PIXELFORMAT_R32U, textureWidth, textureHeight);
        }
        if (!this.rt) {
            this.rt = new RenderTarget({
                colorBuffer: this.texture,
                depth: false
            });
        }
        // resize texture / rt
        if (this.rt.width !== textureWidth || this.rt.height !== textureHeight) {
            this.rt.resize(textureWidth, textureHeight);
        }
        // Prepare intervals data with CPU prefix sum
        const numIntervals = intervals.length / 2;
        const dataTextureSize = Math.ceil(Math.sqrt(numIntervals));
        // Create/resize intervals data texture
        if (!this.intervalsDataTexture) {
            this.intervalsDataTexture = this.createTexture('intervalsData', PIXELFORMAT_RG32U, dataTextureSize, dataTextureSize);
        }
        if (this.intervalsDataTexture.width !== dataTextureSize) {
            this.intervalsDataTexture.resize(dataTextureSize, dataTextureSize);
        }
        // Compute intervals data with accumulated sums on CPU
        // TODO: consider doing this using compute shader on WebGPU
        const intervalsData = this.intervalsDataTexture.lock();
        let runningSum = 0;
        for(let i = 0; i < numIntervals; i++){
            const start = intervals[i * 2];
            const end = intervals[i * 2 + 1];
            const intervalSize = end - start;
            runningSum += intervalSize;
            intervalsData[i * 2] = start; // R: interval start
            intervalsData[i * 2 + 1] = runningSum; // G: accumulated sum
        }
        this.intervalsDataTexture.unlock();
        // Generate intervals texture on GPU
        const scope = this.device.scope;
        scope.resolve('uIntervalsTexture').setValue(this.intervalsDataTexture);
        scope.resolve('uNumIntervals').setValue(numIntervals);
        scope.resolve('uTextureWidth').setValue(textureWidth);
        scope.resolve('uActiveSplats').setValue(totalSplats);
        this.device.setCullMode(CULLFACE_NONE);
        this.device.setBlendState(BlendState.NOBLEND);
        this.device.setDepthState(DepthState.NODEPTH);
        drawQuadWithShader(this.device, this.rt, this.getShader());
        return totalSplats;
    }
}

/**
 * @import { GraphicsDevice } from "../../platform/graphics/graphics-device.js";
 * @import { GSplatResourceBase } from "../gsplat/gsplat-resource-base.js"
 * @import { GSplatPlacement } from "./gsplat-placement.js"
 * @import { GraphNode } from '../graph-node.js';
 */ /**
 * @ignore
 */ class GSplatInfo {
    /**
     * Create a new GSplatInfo.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {GSplatResourceBase} resource - The splat resource.
     * @param {GSplatPlacement} placement - The placement of the splat.
     */ constructor(device, resource, placement){
        /** @type {number} */ this.activeSplats = 0;
        /**
     * Array of intervals for remapping of indices, each two consecutive numbers represent
     * start and end of a range of splats.
     *
     * @type {number[]}
     */ this.intervals = [];
        /** @type {number} */ this.lineStart = 0;
        /** @type {number} */ this.lineCount = 0;
        /** @type {number} */ this.padding = 0;
        /** @type {Vec4} */ this.viewport = new Vec4();
        /** @type {Mat4} */ this.previousWorldTransform = new Mat4();
        /** @type {BoundingBox} */ this.aabb = new BoundingBox();
        Debug.assert(resource);
        Debug.assert(placement);
        this.device = device;
        this.resource = resource;
        this.node = placement.node;
        this.lodIndex = placement.lodIndex;
        this.numSplats = resource.centers.length / 3;
        this.aabb.copy(placement.aabb);
        this.intervalTexture = new GSplatIntervalTexture(device);
        this.updateIntervals(placement.intervals);
    }
    destroy() {
        this.intervals.length = 0;
        this.intervalTexture.destroy();
    }
    setLines(start, count, textureSize, activeSplats) {
        this.lineStart = start;
        this.lineCount = count;
        this.padding = textureSize * count - activeSplats;
        Debug.assert(this.padding >= 0);
        this.viewport.set(0, start, textureSize, count);
    }
    updateIntervals(intervals) {
        const resource = this.resource;
        this.intervals.length = 0;
        this.activeSplats = resource.numSplats;
        // If placement has intervals defined
        if (intervals.size > 0) {
            // copy the intervals to the state
            for (const interval of intervals.values()){
                this.intervals.push(interval.x, interval.y + 1);
            }
            this.activeSplats = this.intervalTexture.update(this.intervals);
        }
    }
    update() {
        // if the object's matrix has changed, store the update version to know when it happened
        const worldMatrix = this.node.getWorldTransform();
        const worldMatrixChanged = !this.previousWorldTransform.equals(worldMatrix);
        if (worldMatrixChanged) {
            this.previousWorldTransform.copy(worldMatrix);
        }
        return worldMatrixChanged;
    }
}

function UnifiedSortWorker() {
    const myself = typeof self !== 'undefined' && self || require('node:worker_threads').parentPort;
    // let chunks;
    // let sortSplatCount;
    // cache of centers for each splat id
    const centersMap = new Map();
    let centersData;
    let distances;
    let countBuffer;
    // could be increased, but this seems a good compromise between stability and performance
    // const numBins = 32;
    // const binCount = new Array(numBins).fill(0);
    // const binBase = new Array(numBins).fill(0);
    // const binDivider = new Array(numBins).fill(0);
    // const binarySearch = (m, n, compare_fn) => {
    //     while (m <= n) {
    //         const k = (n + m) >> 1;
    //         const cmp = compare_fn(k);
    //         if (cmp > 0) {
    //             m = k + 1;
    //         } else if (cmp < 0) {
    //             n = k - 1;
    //         } else {
    //             return k;
    //         }
    //     }
    //     return ~m;
    // };
    const evaluateSortKeys = (sortParams, minDist, divider, distances, countBuffer, centersData, bucketCount)=>{
        const { ids, lineStarts, padding, intervals, textureSize } = centersData;
        // loop over all the splat placements
        for(let paramIdx = 0; paramIdx < sortParams.length; paramIdx++){
            // camera related params
            const params = sortParams[paramIdx];
            const { transformedDirection, offset, scale } = params;
            const dx = transformedDirection.x;
            const dy = transformedDirection.y;
            const dz = transformedDirection.z;
            // pre-calculate camera related constants
            const sdx = dx * scale * divider;
            const sdy = dy * scale * divider;
            const sdz = dz * scale * divider;
            const add = (offset - minDist) * divider;
            // source centers
            const id = ids[paramIdx];
            const centers = centersMap.get(id);
            if (!centers) {
                console.error('UnifiedSortWorker: No centers found for id', id);
            }
            // start index in unified buffer
            let targetIndex = lineStarts[paramIdx] * textureSize;
            // Use provided intervals or process all centers
            const intervalsArray = intervals[paramIdx].length > 0 ? intervals[paramIdx] : [
                0,
                centers.length / 3
            ];
            // loop over all intervals of centers
            for(let i = 0; i < intervalsArray.length; i += 2){
                const intervalStart = intervalsArray[i] * 3;
                const intervalEnd = intervalsArray[i + 1] * 3;
                // Process each center in this interval
                for(let srcIndex = intervalStart; srcIndex < intervalEnd; srcIndex += 3){
                    const x = centers[srcIndex];
                    const y = centers[srcIndex + 1];
                    const z = centers[srcIndex + 2];
                    const sortKey = Math.floor(x * sdx + y * sdy + z * sdz + add);
                    distances[targetIndex++] = sortKey;
                    countBuffer[sortKey]++;
                }
            }
            // add padding, to make sure the whole buffer (including padding) is sorted
            const pad = padding[paramIdx];
            countBuffer[0] += pad;
            // set distance values for padding positions to prevent garbage data
            distances.fill(0, targetIndex, targetIndex + pad);
            targetIndex += pad;
        }
    };
    const countingSort = (bucketCount, countBuffer, numVertices, distances, order)=>{
        // accumulate counts
        for(let i = 1; i < bucketCount; i++){
            countBuffer[i] += countBuffer[i - 1];
        }
        // build output array
        for(let i = 0; i < numVertices; i++){
            const distance = distances[i];
            const destIndex = --countBuffer[distance];
            order[destIndex] = i;
        }
    };
    // compute min/max effective distance using 8-corner local AABB projection per splat
    const computeEffectiveDistanceRange = (sortParams)=>{
        let minDist = Infinity;
        let maxDist = -Infinity;
        for(let paramIdx = 0; paramIdx < sortParams.length; paramIdx++){
            const params = sortParams[paramIdx];
            const { transformedDirection, offset, scale, aabbMin, aabbMax } = params;
            const dx = transformedDirection.x;
            const dy = transformedDirection.y;
            const dz = transformedDirection.z;
            // For a direction d and AABB [min,max], the min/max of dot(d, p) over the box
            // is obtained by picking min/max per component based on the sign of d
            const pxMin = dx >= 0 ? aabbMin[0] : aabbMax[0];
            const pyMin = dy >= 0 ? aabbMin[1] : aabbMax[1];
            const pzMin = dz >= 0 ? aabbMin[2] : aabbMax[2];
            const pxMax = dx >= 0 ? aabbMax[0] : aabbMin[0];
            const pyMax = dy >= 0 ? aabbMax[1] : aabbMin[1];
            const pzMax = dz >= 0 ? aabbMax[2] : aabbMin[2];
            const dMin = pxMin * dx + pyMin * dy + pzMin * dz;
            const dMax = pxMax * dx + pyMax * dy + pzMax * dz;
            const eMin = dMin * scale + offset;
            const eMax = dMax * scale + offset;
            // handle negative scale by swapping
            const localMin = Math.min(eMin, eMax);
            const localMax = Math.max(eMin, eMax);
            if (localMin < minDist) minDist = localMin;
            if (localMax > maxDist) maxDist = localMax;
        }
        if (minDist === Infinity) {
            minDist = 0;
            maxDist = 0;
        }
        return {
            minDist,
            maxDist
        };
    };
    const sort = (sortParams, order, centersData)=>{
        // distance bounds from AABB projections per splat
        const { minDist, maxDist } = computeEffectiveDistanceRange(sortParams);
        const numVertices = centersData.totalUsedPixels;
        // calculate number of bits needed to store sorting result
        const compareBits = Math.max(10, Math.min(20, Math.round(Math.log2(numVertices / 4))));
        const bucketCount = 2 ** compareBits + 1;
        // create distance buffer
        if (distances?.length !== numVertices) {
            distances = new Uint32Array(numVertices);
        }
        if (!countBuffer || countBuffer.length !== bucketCount) {
            countBuffer = new Uint32Array(bucketCount);
        } else {
            countBuffer.fill(0);
        }
        const range = maxDist - minDist;
        // // use chunks to calculate rough histogram of splats per distance
        // const numChunks = chunks.length / 4;
        // binCount.fill(0);
        // for (let i = 0; i < numChunks; ++i) {
        //     const x = chunks[i * 4 + 0];
        //     const y = chunks[i * 4 + 1];
        //     const z = chunks[i * 4 + 2];
        //     const r = chunks[i * 4 + 3];
        //     const d = x * dx + y * dy + z * dz - minDist;
        //     const binMin = Math.max(0, Math.floor((d - r) * numBins / range));
        //     const binMax = Math.min(numBins, Math.ceil((d + r) * numBins / range));
        //     for (let j = binMin; j < binMax; ++j) {
        //         binCount[j]++;
        //     }
        // }
        // // count total number of histogram bin entries
        // const binTotal = binCount.reduce((a, b) => a + b, 0);
        // // calculate per-bin base and divider
        // for (let i = 0; i < numBins; ++i) {
        //     binDivider[i] = (binCount[i] / binTotal * bucketCount) >>> 0;
        // }
        // for (let i = 0; i < numBins; ++i) {
        //     binBase[i] = i === 0 ? 0 : binBase[i - 1] + binDivider[i - 1];
        // }
        // // generate per vertex distance key using histogram to distribute bits
        // const binRange = range / numBins;
        // let ii = 0;
        // for (let i = 0; i < numVertices; ++i) {
        //     const x = centers[ii++];
        //     const y = centers[ii++];
        //     const z = centers[ii++];
        //     const d = (x * dx + y * dy + z * dz - minDist) / binRange;
        //     const bin = d >>> 0;
        //     const sortKey = (binBase[bin] + binDivider[bin] * (d - bin)) >>> 0;
        //     distances[i] = sortKey;
        //     // count occurrences of each distance
        //     countBuffer[sortKey]++;
        // }
        const divider = range < 1e-6 ? 0 : 1 / range * 2 ** compareBits;
        // for (let i = 0; i < numVertices; ++i) {
        //     const istride = i * 3;
        //     const x = centers[istride + 0] - px;
        //     const y = centers[istride + 1] - py;
        //     const z = centers[istride + 2] - pz;
        //     const d = x * dx + y * dy + z * dz;
        //     const sortKey = Math.floor((d - minDist) * divider);
        //     distances[i] = sortKey;
        //     // count occurrences of each distance
        //     countBuffer[sortKey]++;
        // }
        evaluateSortKeys(sortParams, minDist, divider, distances, countBuffer, centersData);
        countingSort(bucketCount, countBuffer, numVertices, distances, order);
        // // Find splat with distance 0 to limit rendering behind the camera
        // const cameraDist = px * dx + py * dy + pz * dz;
        // const dist = (i) => {
        //     let o = order[i] * 3;
        //     return centers[o++] * dx + centers[o++] * dy + centers[o] * dz - cameraDist;
        // };
        // const findZero = () => {
        //     const result = binarySearch(0, numVertices - 1, i => -dist(i));
        //     return Math.min(numVertices, Math.abs(result));
        // };
        // const count = dist(numVertices - 1) >= 0 ? findZero() : numVertices;
        const count = numVertices;
        // send results
        const transferList = [
            order.buffer
        ];
        const response = {
            order: order.buffer,
            count,
            version: centersData.version
        };
        myself.postMessage(response, transferList);
    };
    myself.addEventListener('message', (message)=>{
        const msgData = message.data ?? message;
        switch(msgData.command){
            // add centers to map
            case 'addCenters':
                {
                    centersMap.set(msgData.id, new Float32Array(msgData.centers));
                    break;
                }
            // remove centers from map
            case 'removeCenters':
                {
                    centersMap.delete(msgData.id);
                    break;
                }
            // sort
            case 'sort':
                {
                    const order = new Uint32Array(msgData.order);
                    sort(msgData.sortParams, order, centersData);
                    break;
                }
            // intervals
            case 'intervals':
                {
                    centersData = msgData;
                    break;
                }
        }
    });
}

class GSplatUnifiedSorter extends EventHandler {
    constructor(){
        super(), this.bufferLength = 0, this.availableOrderData = [], // track how many jobs are in flight
        this.jobsInFlight = 0, // true if we have new version to process
        this.hasNewVersion = false, /** @type {Set<number>} */ this.centersSet = new Set();
        const workerSource = `(${UnifiedSortWorker.toString()})()`;
        if (platform.environment === 'node') {
            this.worker = new Worker(workerSource, {
                eval: true
            });
            this.worker.on('message', this.onSorted.bind(this));
        } else {
            this.worker = new Worker(URL.createObjectURL(new Blob([
                workerSource
            ], {
                type: "application/javascript"
            })));
            this.worker.addEventListener('message', this.onSorted.bind(this));
        }
    }
    onSorted(message) {
        const msgData = message.data ?? message;
        const orderData = new Uint32Array(msgData.order);
        // decrement jobs in flight counter
        this.jobsInFlight--;
        this.fire('sorted', msgData.count, msgData.version, orderData);
        // reuse order data
        if (orderData.length === this.bufferLength) {
            this.availableOrderData.push(orderData);
        }
    }
    destroy() {
        this.worker.terminate();
        this.worker = null;
    }
    /**
     * Adds or removes centers from the sorter.
     *
     * @param {number} id - The id of the centers.
     * @param {Float32Array|null} centers - The centers buffer.
     */ setCenters(id, centers) {
        if (centers) {
            if (!this.centersSet.has(id)) {
                this.centersSet.add(id);
                // clone centers buffer
                const centersBuffer = centers.buffer.slice();
                // post centers to worker
                this.worker.postMessage({
                    command: 'addCenters',
                    id: id,
                    centers: centersBuffer
                }, [
                    centersBuffer
                ]);
            }
        } else {
            if (this.centersSet.has(id)) {
                this.centersSet.delete(id);
                // post centers to worker
                this.worker.postMessage({
                    command: 'removeCenters',
                    id: id
                });
            }
        }
    }
    /**
     * Sets sort parameters data for sorting of splats.
     *
     * @param {object} payload - The sort parameters payload to send.
     */ setSortParameters(payload) {
        // we have a new version to process
        this.hasNewVersion = true;
        // output size matches input size, clear available data when it changes
        const { textureSize } = payload;
        const newLength = textureSize * textureSize;
        if (newLength !== this.bufferLength) {
            this.bufferLength = newLength;
            this.availableOrderData.length = 0;
        }
        this.worker.postMessage(payload);
    }
    /**
     * Sends sorting parameters to the sorter. Called every frame sorting is needed.
     *
     * @param {object} params - The sorting parameters - per-splat directions, offsets, scales, AABBs.
     */ setSortParams(params) {
        // only process job requests if we have a new version or no jobs are in flight
        if (this.hasNewVersion || this.jobsInFlight === 0) {
            // reuse or allocate new order data
            let orderData = this.availableOrderData.pop();
            if (!orderData) {
                orderData = new Uint32Array(this.bufferLength);
            }
            // worker management
            this.jobsInFlight++;
            this.hasNewVersion = false;
            // send job to worker
            this.worker.postMessage({
                command: 'sort',
                sortParams: params,
                order: orderData.buffer
            }, [
                orderData.buffer
            ]);
        }
    }
}

/**
 * @import { GSplatInfo } from './gsplat-info.js'
 * @import { GraphNode } from '../graph-node.js'
 * @import { RenderTarget } from '../../platform/graphics/render-target.js'
 */ const _viewMat = new Mat4();
// enable to colorize LODs
const colorizeLod = false;
/**
 * A render pass used to render multiple gsplats to a work buffer render target.
 *
 * @ignore
 */ class GSplatWorkBufferRenderPass extends RenderPass {
    /**
     * Initialize the render pass with the specified render target.
     *
     * @param {RenderTarget} renderTarget - The target to render to.
     */ init(renderTarget) {
        super.init(renderTarget);
        this.colorOps.clear = false;
        this.depthStencilOps.clearDepth = false;
    }
    /**
     * Update the render pass with splats to render and camera.
     *
     * @param {GSplatInfo[]} splats - Array of GSplatInfo objects to render.
     * @param {GraphNode} cameraNode - The camera node for rendering.
     * @returns {boolean} True if there are splats to render, false otherwise.
     */ update(splats, cameraNode) {
        this.splats.length = 0;
        // Filter active splats that need rendering
        for(let i = 0; i < splats.length; i++){
            const splatInfo = splats[i];
            if (splatInfo.activeSplats > 0) {
                this.splats.push(splatInfo);
            }
        }
        this.cameraNode = cameraNode;
        return this.splats.length > 0;
    }
    execute() {
        const { device, splats, cameraNode } = this;
        DebugGraphics.pushGpuMarker(device, 'GSplatWorkBuffer');
        // Set up render state
        device.setBlendState(BlendState.NOBLEND);
        device.setCullMode(CULLFACE_NONE);
        device.setDepthState(DepthState.NODEPTH);
        device.setStencilState();
        // view matrix
        const viewInvMat = cameraNode.getWorldTransform();
        const viewMat = _viewMat.copy(viewInvMat).invert();
        device.scope.resolve('matrix_view').setValue(viewMat.data);
        // render each splat info
        for(let i = 0; i < splats.length; i++){
            this.renderSplat(splats[i]);
        }
        DebugGraphics.popGpuMarker(device);
    }
    /**
     * Render a single splat info object.
     *
     * @param {GSplatInfo} splatInfo - The splat info to render.
     */ renderSplat(splatInfo) {
        const { device, resource } = splatInfo;
        const scope = device.scope;
        Debug.assert(resource);
        const { intervals, activeSplats, lineStart, viewport, intervalTexture } = splatInfo;
        // quad renderer and material are cached in the resource
        const workBufferRenderInfo = resource.getWorkBufferRenderInfo(intervals.length > 0, colorizeLod);
        // Assign material properties to scope
        workBufferRenderInfo.material.setParameters(device);
        // Matrix to transform splats to the world space
        scope.resolve('uTransform').setValue(splatInfo.node.getWorldTransform().data);
        if (intervalTexture.texture) {
            // Set LOD intervals texture for remapping of indices
            scope.resolve('uIntervalsTexture').setValue(intervalTexture.texture);
        }
        scope.resolve('uActiveSplats').setValue(activeSplats);
        scope.resolve('uStartLine').setValue(lineStart);
        scope.resolve('uViewportWidth').setValue(viewport.z);
        // SH related
        scope.resolve('matrix_model').setValue(splatInfo.node.getWorldTransform().data);
        // Render the quad - QuadRender handles all the complex setup internally
        workBufferRenderInfo.quadRender.render(viewport);
    }
    destroy() {
        this.splats.length = 0;
        super.destroy();
    }
    constructor(...args){
        super(...args), /**
     * Array of GSplatInfo objects to render in this pass.
     *
     * @type {GSplatInfo[]}
     */ this.splats = [];
    }
}

let id = 0;
/**
 * @import { GSplatInfo } from "./gsplat-info.js"
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { GraphNode } from '../graph-node.js';
 */ /**
 * @ignore
 */ class GSplatWorkBuffer {
    constructor(device){
        /** @type {number} */ this.id = id++;
        this.device = device;
        this.colorTexture = this.createTexture('splatColor', PIXELFORMAT_RGBA16F, 1, 1);
        this.covATexture = this.createTexture('covA', PIXELFORMAT_RGBA16F, 1, 1);
        this.covBTexture = this.createTexture('covB', PIXELFORMAT_RGBA16F, 1, 1);
        this.centerTexture = this.createTexture('center', PIXELFORMAT_RGBA16F, 1, 1);
        this.renderTarget = new RenderTarget({
            name: `GsplatWorkBuffer-MRT-${this.id}`,
            colorBuffers: [
                this.colorTexture,
                this.centerTexture,
                this.covATexture,
                this.covBTexture
            ],
            depth: false,
            flipY: true
        });
        this.orderTexture = this.createTexture('SplatGlobalOrder', PIXELFORMAT_R32U, 1, 1);
        // Create the optimized render pass for batched splat rendering
        this.renderPass = new GSplatWorkBufferRenderPass(device);
        this.renderPass.init(this.renderTarget);
    }
    destroy() {
        this.renderPass?.destroy();
        this.colorTexture?.destroy();
        this.covATexture?.destroy();
        this.covBTexture?.destroy();
        this.centerTexture?.destroy();
        this.orderTexture?.destroy();
        this.renderTarget?.destroy();
    }
    get textureSize() {
        return this.orderTexture.width;
    }
    setOrderData(data) {
        const len = this.orderTexture.width * this.orderTexture.height;
        if (len !== data.length) {
            Debug.error('setOrderData: data length mismatch, got:', data.length, 'expected:', len, `(${this.orderTexture.width}x${this.orderTexture.height})`);
        }
        // upload data to texture
        this.orderTexture._levels[0] = data;
        this.orderTexture.upload();
    }
    createTexture(name, format, w, h) {
        return new Texture(this.device, {
            name: name,
            width: w,
            height: h,
            format: format,
            cubemap: false,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE
        });
    }
    /**
     * @param {number} textureSize - The texture size to resize to.
     */ resize(textureSize) {
        Debug.assert(textureSize);
        this.renderTarget.resize(textureSize, textureSize);
        this.orderTexture.resize(textureSize, textureSize);
    }
    /**
     * Render given splats to the work buffer.
     *
     * @param {GSplatInfo[]} splats - The splats to render.
     * @param {GraphNode} cameraNode - The camera node.
     */ render(splats, cameraNode) {
        // render splats using render pass
        if (this.renderPass.update(splats, cameraNode)) {
            this.renderPass.render();
        }
    }
}

/**
 * @import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js'
 * @import { Layer } from '../layer.js'
 * @import { GraphNode } from '../graph-node.js'
 */ /**
 * Class that renders the splats from the work buffer.
 *
 * @ignore
 */ class GSplatRenderer {
    constructor(device, node, cameraNode, layer, workBuffer){
        /** @type {number} */ this.maxNumSplats = 0;
        /** @type {VertexBuffer|null} */ this.instanceIndices = null;
        this.viewportParams = [
            0,
            0
        ];
        this.device = device;
        this.node = node;
        this.cameraNode = cameraNode;
        this.layer = layer;
        this.workBuffer = workBuffer;
        // construct the material which renders the splats from the work buffer
        this._material = new ShaderMaterial({
            uniqueName: 'UnifiedSplatMaterial',
            vertexGLSL: '#include "gsplatVS"',
            fragmentGLSL: '#include "gsplatPS"',
            vertexWGSL: '#include "gsplatVS"',
            fragmentWGSL: '#include "gsplatPS"',
            attributes: {
                vertex_position: SEMANTIC_POSITION,
                vertex_id_attrib: SEMANTIC_ATTR13
            }
        });
        // input format
        this._material.setDefine('GSPLAT_WORKBUFFER_DATA', true);
        // input textures (work buffer textures)
        this._material.setParameter('splatColor', workBuffer.colorTexture);
        this._material.setParameter('covA', workBuffer.covATexture);
        this._material.setParameter('covB', workBuffer.covBTexture);
        this._material.setParameter('center', workBuffer.centerTexture);
        this._material.setDefine('SH_BANDS', '0');
        this._material.setParameter('numSplats', 0);
        this._material.setParameter('splatOrder', workBuffer.orderTexture);
        this._material.setParameter('alphaClip', 0.3);
        this._material.setDefine(`DITHER_${'NONE'}`, '');
        this._material.cull = CULLFACE_NONE;
        this._material.blendType = BLEND_PREMULTIPLIED;
        this._material.depthWrite = false;
        this._material.update();
        this.meshInstance = this.createMeshInstance();
        layer.addMeshInstances([
            this.meshInstance
        ]);
    }
    destroy() {
        this.layer.removeMeshInstances([
            this.meshInstance
        ]);
        this._material.destroy();
        this.meshInstance.destroy();
    }
    setNumSplats(count) {
        // limit splat render count to exclude those behind the camera
        this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize);
        // update splat count on the material
        this._material.setParameter('numSplats', count);
        // disable rendering if no splats to render
        this.meshInstance.visible = count > 0;
    }
    setMaxNumSplats(numSplats) {
        if (this.maxNumSplats !== numSplats) {
            this.maxNumSplats = numSplats;
            // destroy old instance indices
            this.instanceIndices?.destroy();
            // create new instance indices
            this.instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, numSplats);
            this.meshInstance.setInstancing(this.instanceIndices, true);
        }
    }
    createMeshInstance() {
        const mesh = GSplatResourceBase.createMesh(this.device);
        const textureSize = this.workBuffer.textureSize;
        const instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, textureSize * textureSize);
        const meshInstance = new MeshInstance(mesh, this._material);
        meshInstance.node = this.node;
        meshInstance.setInstancing(instanceIndices, true);
        // only start rendering the splat after we've received the splat order data
        meshInstance.instancingCount = 0;
        // custom culling to only disable rendering for matching camera
        // TODO: consider using aabb as well to avoid rendering off-screen splats
        const thisCamera = this.cameraNode.camera;
        meshInstance.isVisibleFunc = (camera)=>{
            const vis = thisCamera.camera === camera;
            return vis;
        };
        return meshInstance;
    }
    updateViewport(cameraNode) {
        const camera = cameraNode.camera;
        const cameraRect = camera.rect;
        const renderTarget = camera?.renderTarget;
        const { width, height } = renderTarget ?? this.device;
        const viewport = this.viewportParams;
        viewport[0] = width * cameraRect.z;
        viewport[1] = height * cameraRect.w;
        // adjust viewport for stereoscopic VR sessions
        const xr = camera?.camera?.xr;
        if (xr?.active && xr.views.list.length === 2) {
            viewport[0] *= 0.5;
        }
        this._material.setParameter('viewport', viewport);
    }
}

/**
 * @import { GraphNode } from '../graph-node.js'
 * @import { GSplatResource } from '../gsplat/gsplat-resource.js'
 * @import { GSplatOctreeResource } from './gsplat-octree.resource.js'
 * @import { Vec2 } from '../../core/math/vec2.js'
 */ /**
 * Class representing a placement of a gsplat resource.
 *
 * @ignore
 */ class GSplatPlacement {
    /**
     * Create a new GSplatPlacement.
     *
     * @param {GSplatResource|null} resource - The resource of the splat.
     * @param {GraphNode} node - The node that the gsplat is linked to.
     * @param {number} lodIndex - The LOD index for this placement.
     */ constructor(resource, node, lodIndex = 0){
        /**
     * Map of intervals for octree nodes using this placement.
     * Key is octree node index, value is Vec2 representing start and end index (inclusive).
     *
     * @type {Map<number, Vec2>}
     */ this.intervals = new Map();
        /**
     * The LOD index for this placement.
     *
     * @type {number}
     */ this.lodIndex = 0;
        /**
     * The axis-aligned bounding box for this placement, in local space.
     *
     * @type {BoundingBox}
     */ this._aabb = new BoundingBox();
        this.resource = resource;
        this.node = node;
        this.lodIndex = lodIndex;
    }
    set aabb(aabb) {
        this._aabb.copy(aabb);
    }
    get aabb() {
        return this._aabb;
    }
}

/**
 * @import { GraphNode } from '../graph-node.js'
 * @import { GSplatOctree } from './gsplat-octree.js'
 * @import { GSplatAssetLoaderBase } from './gsplat-asset-loader-base.js'
 */ const _invWorldMat = new Mat4();
const _localCameraPos = new Vec3();
const _tempCompletedUrls = [];
class GSplatOctreeInstance {
    /**
     * @param {GSplatOctree} octree - The octree.
     * @param {GSplatPlacement} placement - The placement.
     * @param {GSplatAssetLoaderBase} assetLoader - The asset loader.
     */ constructor(octree, placement, assetLoader){
        /** @type {Set<GSplatPlacement>} */ this.activePlacements = new Set();
        /** @type {boolean} */ this.dirtyModifiedPlacements = false;
        /**
     * Set of pending file loads (file indices).
     * @type {Set<number>}
     */ this.pending = new Set();
        /**
     * Map of nodeIndex -> oldFileIndex that needs to be decremented when current LOD loads.
     * Used to track delayed cleanup from LOD gap prevention.
     *
     * @type {Map<number, number>}
     */ this.pendingDecrements = new Map();
        /**
     * Previous node position at which LOD was last updated. This is used to determine if LOD needs
     * to be updated as the octree splat moves.
     *
     * @type {Vec3}
     */ this.previousPosition = new Vec3();
        this.octree = octree;
        this.placement = placement;
        this.assetLoader = assetLoader;
        // Initialize nodeLods array with all nodes set to -1 (not visible)
        this.nodeLods = new Int32Array(octree.nodes.length);
        this.nodeLods.fill(-1);
        // Initialize file placements array
        const numFiles = octree.files.length;
        this.filePlacements = new Array(numFiles).fill(null);
    }
    /**
     * Destroys this octree instance and clears internal references.
     */ destroy() {
        this.pending.clear();
        this.pendingDecrements.clear();
        this.filePlacements.length = 0;
    }
    /**
     * Calculate LOD index for a specific node using pre-calculated local camera position.
     * @param {Vec3} localCameraPosition - The camera position in local space.
     * @param {number} nodeIndex - The node index.
     * @param {number} maxLod - The maximum LOD index (lodLevels - 1).
     * @returns {number} The LOD index for this node, or -1 if node should not be rendered.
     */ calculateNodeLod(localCameraPosition, nodeIndex, maxLod) {
        const node = this.octree.nodes[nodeIndex];
        // Calculate distance in local space (no transforms needed)
        const distance = localCameraPosition.distance(node.bounds.center);
        // Distance thresholds for LOD selection
        const lodDistances = [
            2,
            4,
            6,
            8,
            10
        ];
        // Find appropriate LOD based on distance and available LOD levels
        for(let lod = 0; lod < maxLod; lod++){
            if (distance < lodDistances[lod]) {
                return lod;
            }
        }
        // If distance is greater than all thresholds, use the highest available LOD
        return maxLod;
    // return -1 for past far plane
    }
    /**
     * Updates the octree instance when LOD needs to be updated.
     *
     * @param {GraphNode} cameraNode - The camera node.
     */ updateLod(cameraNode) {
        // transform camera position to octree local space
        const worldCameraPosition = cameraNode.getPosition();
        const octreeWorldTransform = this.placement.node.getWorldTransform();
        _invWorldMat.copy(octreeWorldTransform).invert();
        const localCameraPosition = _invWorldMat.transformPoint(worldCameraPosition, _localCameraPos);
        // calculate max LOD once for all nodes
        const maxLod = this.octree.lodLevels - 1;
        // process all nodes
        const nodes = this.octree.nodes;
        for(let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++){
            const node = nodes[nodeIndex];
            // LOD for the node
            const newLodIndex = this.calculateNodeLod(localCameraPosition, nodeIndex, maxLod);
            const currentLodIndex = this.nodeLods[nodeIndex];
            // if LOD changed
            if (newLodIndex !== currentLodIndex) {
                // FIRST: Execute any existing pending decrement for this node
                const pendingOldFileIndex = this.pendingDecrements.get(nodeIndex);
                if (pendingOldFileIndex !== undefined) {
                    this.decrementFileRef(pendingOldFileIndex, nodeIndex);
                    this.pendingDecrements.delete(nodeIndex);
                }
                // update the stored LOD index
                this.nodeLods[nodeIndex] = newLodIndex;
                const wasVisible = currentLodIndex >= 0;
                const willBeVisible = newLodIndex >= 0;
                if (!wasVisible && willBeVisible) {
                    // becoming visible (invisible -> visible)
                    const newFileIndex = node.lods[newLodIndex].fileIndex;
                    this.incrementFileRef(newFileIndex, nodeIndex, newLodIndex);
                } else if (wasVisible && !willBeVisible) {
                    // becoming invisible (visible -> invisible)
                    const oldFileIndex = node.lods[currentLodIndex].fileIndex;
                    this.decrementFileRef(oldFileIndex, nodeIndex);
                } else if (wasVisible && willBeVisible) {
                    // switching between visible LODs (visible -> visible)
                    const newFileIndex = node.lods[newLodIndex].fileIndex;
                    const oldFileIndex = node.lods[currentLodIndex].fileIndex;
                    this.incrementFileRef(newFileIndex, nodeIndex, newLodIndex);
                    const newPlacement = this.filePlacements[newFileIndex];
                    if (newPlacement?.resource) {
                        // New LOD ready - remove old LOD immediately
                        this.decrementFileRef(oldFileIndex, nodeIndex);
                    } else {
                        // New LOD not ready - track pending decrement for when it loads
                        this.pendingDecrements.set(nodeIndex, oldFileIndex);
                    }
                }
            }
        }
    }
    /**
     * Increments reference count for a file and creates placement immediately.
     *
     * @param {number} fileIndex - The file index.
     * @param {number} nodeIndex - The octree node index.
     * @param {number} lodIndex - The LOD index for this node.
     */ incrementFileRef(fileIndex, nodeIndex, lodIndex) {
        if (fileIndex === -1) return;
        // check if this is the first reference
        let placement = this.filePlacements[fileIndex];
        if (!placement) {
            // create placement (with null resource initially)
            placement = new GSplatPlacement(null, this.placement.node, lodIndex);
            this.filePlacements[fileIndex] = placement;
            // if resource is already loaded, allow it to be used
            if (!this.addFilePlacement(fileIndex)) {
                // resource not loaded yet, kick off load and add to pending
                const fileUrl = this.octree.files[fileIndex];
                this.octree.ensureFileResource(fileUrl, this.assetLoader);
                this.pending.add(fileIndex);
            }
        }
        // Add interval for this node to the placement
        const nodes = this.octree.nodes;
        const node = nodes[nodeIndex];
        const lod = node.lods[lodIndex];
        // Create interval as Vec2(start, end)
        const interval = new Vec2(lod.offset, lod.offset + lod.count - 1);
        placement.intervals.set(nodeIndex, interval);
        this.dirtyModifiedPlacements = true;
    }
    /**
     * Decrements reference count for a file and removes placement if needed.
     *
     * @param {number} fileIndex - The file index.
     * @param {number} nodeIndex - The octree node index.
     */ decrementFileRef(fileIndex, nodeIndex) {
        if (fileIndex === -1) return;
        const placement = this.filePlacements[fileIndex];
        Debug.assert(placement);
        if (placement) {
            // remove interval for this node from the placement
            placement.intervals.delete(nodeIndex);
            this.dirtyModifiedPlacements = true;
            // if this was the last reference, remove placement
            if (placement.intervals.size === 0) {
                // Only remove if it was added (has resource)
                if (placement.resource) {
                    this.activePlacements.delete(placement);
                }
                this.filePlacements[fileIndex] = null;
                this.pending.delete(fileIndex);
            }
        }
    }
    /**
     * Updates existing placement with loaded resource and adds to manager.
     *
     * @param {number} fileIndex - The file index.
     * @returns {boolean} True if placement was updated and added to manager, false otherwise.
     */ addFilePlacement(fileIndex) {
        const fileUrl = this.octree.files[fileIndex];
        const res = this.octree.getFileResource(fileUrl);
        if (res) {
            // get the existing placement and update its resource
            const placement = this.filePlacements[fileIndex];
            if (placement) {
                placement.resource = res;
                placement.aabb.copy(res.aabb);
                this.activePlacements.add(placement);
                this.dirtyModifiedPlacements = true;
                return true;
            }
        }
        return false;
    }
    /**
     * Tests if the octree instance has moved by more than 1 unit.
     *
     * @returns {boolean} True if the octree instance has moved by more than 1 unit, false otherwise.
     */ testMoved() {
        const position = this.placement.node.getPosition();
        const length = position.distance(this.previousPosition);
        if (length > 1) {
            this.previousPosition.copy(position);
            return true;
        }
        return false;
    }
    /**
     * Updates the octree instance each frame.
     *
     * @returns {boolean} True if octree instance is dirty, false otherwise.
     */ update() {
        // handle pending loads
        if (this.pending.size) {
            for (const fileIndex of this.pending){
                // check if the asset has finished loading and store it if so
                const fileUrl = this.octree.files[fileIndex];
                this.octree.ensureFileResource(fileUrl, this.assetLoader);
                // add placement if resource is now available and still needed
                const placement = this.filePlacements[fileIndex];
                if (placement && placement.intervals.size > 0 && this.addFilePlacement(fileIndex)) {
                    _tempCompletedUrls.push(fileIndex);
                    // execute pending decrements for nodes using this file
                    for (const [nodeIndex] of placement.intervals){
                        // Execute any pending decrements for this node now that resource is loaded
                        const oldFileIndex = this.pendingDecrements.get(nodeIndex);
                        if (oldFileIndex !== undefined) {
                            this.decrementFileRef(oldFileIndex, nodeIndex);
                            this.pendingDecrements.delete(nodeIndex);
                        }
                    }
                }
            }
            // remove completed items from pending
            for (const fileIndex of _tempCompletedUrls){
                this.pending.delete(fileIndex);
            }
            // clear temp array
            _tempCompletedUrls.length = 0;
        }
        // check if any placements need LOD update
        const dirty = this.dirtyModifiedPlacements;
        this.dirtyModifiedPlacements = false;
        return dirty;
    }
}

/**
 * @typedef {Object} GSplatOctreeNodeLod
 * @property {string} file - The file path
 * @property {number} fileIndex - The file index in the octree files array
 * @property {number} offset - The offset in the file
 * @property {number} count - The count of items
 */ const tmpMin = new Vec3();
const tmpMax = new Vec3();
class GSplatOctreeNode {
    /**
     * @param {GSplatOctreeNodeLod[]} lods - The LOD data for this node
     * @param {Object} [boundData] - The bounding box data with min and max arrays
     */ constructor(lods, boundData){
        /**
     * @type {BoundingBox}
     */ this.bounds = new BoundingBox();
        this.lods = lods;
        // bounds
        tmpMin.set(boundData.min[0], boundData.min[1], boundData.min[2]);
        tmpMax.set(boundData.max[0], boundData.max[1], boundData.max[2]);
        this.bounds.setMinMax(tmpMin, tmpMax);
    }
}

/**
 * @import { GSplatResource } from '../gsplat/gsplat-resource.js'
 * @import { GSplatOctreeNodeLod } from './gsplat-octree-node.js'
 * @import { GSplatAssetLoaderBase } from './gsplat-asset-loader-base.js'
 */ class GSplatOctree {
    /**
     * @param {string} assetFileUrl - The file URL of the container asset.
     * @param {Object} data - The parsed JSON data containing info, filenames and tree.
     */ constructor(assetFileUrl, data){
        /**
     * Resources of individual files, identified by their filename.
     *
     * @type {Map<string, GSplatResource>}
     */ this.fileResources = new Map();
        this.files = data.filenames;
        this.lodLevels = data.lodLevels;
        this.assetFileUrl = assetFileUrl;
        this.baseDir = path$1.getDirectory(assetFileUrl);
        // Extract leaf nodes from hierarchical tree structure
        const leafNodes = [];
        this._extractLeafNodes(data.tree, leafNodes);
        // Create nodes from the extracted leaf nodes
        this.nodes = leafNodes.map((nodeData)=>{
            /** @type {GSplatOctreeNodeLod[]} */ const lods = [];
            // Ensure we have exactly lodLevels entries
            for(let i = 0; i < this.lodLevels; i++){
                const lodData = nodeData.lods[i.toString()];
                if (lodData) {
                    lods.push({
                        file: this.files[lodData.file] || '',
                        fileIndex: lodData.file,
                        offset: lodData.offset || 0,
                        count: lodData.count || 0
                    });
                } else {
                    // Missing LOD entry - fill with defaults
                    lods.push({
                        file: '',
                        fileIndex: -1,
                        offset: 0,
                        count: 0
                    });
                }
            }
            return new GSplatOctreeNode(lods, nodeData.bound);
        });
    }
    /**
     * Recursively extracts leaf nodes (nodes with 'lods' property) from the hierarchical tree.
     *
     * @param {Object} node - The current tree node to process.
     * @param {Array} leafNodes - Array to collect leaf nodes.
     * @private
     */ _extractLeafNodes(node, leafNodes) {
        if (node.lods) {
            // This is a leaf node with LOD data
            leafNodes.push({
                lods: node.lods,
                bound: node.bound
            });
        } else if (node.children) {
            // This is a branch node, recurse into children
            for (const child of node.children){
                this._extractLeafNodes(child, leafNodes);
            }
        }
    }
    getFullUrl(url) {
        // Use pre-computed base directory for fast path joining
        return path$1.join(this.baseDir, url);
    }
    getFileResource(url) {
        return this.fileResources.get(url);
    }
    /**
     * Ensures a file resource is loaded and available. This function:
     * - Starts loading if not already started
     * - Checks if loading completed and stores the resource if available
     *
     * @param {string} url - The url of the file.
     * @param {GSplatAssetLoaderBase} assetLoader - The asset loader.
     */ ensureFileResource(url, assetLoader) {
        // Check if we already have the resource
        if (this.fileResources.has(url)) {
            return;
        }
        // Check if the resource is now available from the asset loader
        const fullUrl = this.getFullUrl(url);
        const res = assetLoader.getResource(fullUrl);
        if (res) {
            this.fileResources.set(url, res);
            return;
        }
        // Start/continue loading (asset loader handles duplicates internally)
        assetLoader.load(fullUrl);
    }
}

class GSplatOctreeResource {
    /**
     * @param {string} assetFileUrl - The file URL of the container asset.
     * @param {object} data - Parsed JSON data.
     */ constructor(assetFileUrl, data){
        /** @type {BoundingBox} */ this.aabb = new BoundingBox();
        this.octree = new GSplatOctree(assetFileUrl, data);
        this.aabb.setMinMax(new Vec3(data.tree.bound.min), new Vec3(data.tree.bound.max));
    }
}

/**
 * @import { GSplatInfo } from './gsplat-info.js'
 */ class GSplatWorldState {
    constructor(device, version, splats){
        /**
     * The version of the world state.
     *
     * @type {number}
     */ this.version = 0;
        /**
     * Whether the sort parameters have been set on the sorter.
     *
     * @type {boolean}
     */ this.sortParametersSet = false;
        /**
     * Whether the world state has been sorted before.
     *
     * @type {boolean}
     */ this.sortedBefore = false;
        /**
     * An array of all splats managed by this world state.
     *
     * @type {GSplatInfo[]}
     */ this.splats = [];
        /**
     * The texture size of work buffer.
     *
     * @type {number}
     */ this.textureSize = 0;
        /**
     * Total number of pixels actually used in the texture (excluding unused regions).
     * This is the count that should be sent to the sort worker and renderer.
     *
     * @type {number}
     */ this.totalUsedPixels = 0;
        this.version = version;
        this.splats = splats;
        this.estimateTextureSize(this.splats, device.maxTextureSize);
        this.assignLines(this.splats, this.textureSize);
    }
    /**
     * Estimates the square texture size that can store all splats, using binary search to find the
     * smallest size that fits.
     *
     * @param {GSplatInfo[]} splats - The splats to allocate space for.
     * @param {number} maxSize - Max texture size (width and height).
     * @returns {boolean} - True if the texture size was found.
     */ estimateTextureSize(splats, maxSize) {
        const fits = (size)=>{
            let rows = 0;
            for (const splat of splats){
                rows += Math.ceil(splat.activeSplats / size);
                if (rows > size) return false;
            }
            return true;
        };
        let low = 1;
        let high = maxSize;
        let bestSize = null;
        while(low <= high){
            const mid = Math.floor((low + high) / 2);
            if (fits(mid)) {
                bestSize = mid;
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        if (bestSize === null) {
            this.textureSize = 0;
            Debug.error('estimateTextureSize: failed to find a valid texture size');
            return false;
        }
        this.textureSize = bestSize;
        return true;
    }
    destroy() {
        this.splats.forEach((splat)=>splat.destroy());
        this.splats.length = 0;
    }
    /**
     * Assigns lines to each splat based on the texture size.
     *
     * @param {GSplatInfo[]} splats - The splats to assign lines to.
     * @param {number} size - The texture size.
     */ assignLines(splats, size) {
        if (splats.length === 0) {
            this.totalUsedPixels = 0;
            return;
        }
        let start = 0;
        for (const splat of splats){
            const activeSplats = splat.activeSplats;
            const numLines = Math.ceil(activeSplats / size);
            splat.setLines(start, numLines, size, activeSplats);
            start += numLines;
        }
        // Calculate total used pixels (lines used × texture width)
        this.totalUsedPixels = start * size;
    }
}

const cameraPosition$1 = new Vec3();
const cameraDirection = new Vec3();
const translation = new Vec3();
const invModelMat = new Mat4();
const tempNonOctreePlacements = new Set();
const tempOctreePlacements = new Set();
const _updatedSplats = [];
/**
 * GSplatManager manages the rendering of splats using a work buffer, where all active splats are
 * stored and rendered from.
 *
 * @ignore
 */ class GSplatManager {
    constructor(device, director, layer, cameraNode){
        /** @type {GraphNode} */ this.node = new GraphNode('GSplatManager');
        /**
     * A map of versioned world states, keyed by version.
     *
     * @type {Map<number, GSplatWorldState>}
     */ this.worldStates = new Map();
        /**
     * The version of the last world state.
     *
     * @type {number}
     */ this.lastWorldStateVersion = 0;
        /** @type {number} */ this.sortedVersion = 0;
        /** @type {Vec3} */ this.lastCameraPos = new Vec3(Infinity, Infinity, Infinity);
        /**
     * Layer placements, only non-octree placements are included.
     *
     * @type {GSplatPlacement[]}
     */ this.layerPlacements = [];
        /** @type {boolean} */ this.layerPlacementsDirty = false;
        /** @type {Map<GSplatPlacement, GSplatOctreeInstance>} */ this.octreeInstances = new Map();
        this.device = device;
        this.director = director;
        this.cameraNode = cameraNode;
        this.workBuffer = new GSplatWorkBuffer(device);
        this.renderer = new GSplatRenderer(device, this.node, this.cameraNode, layer, this.workBuffer);
        this.sorter = this.createSorter();
    }
    destroy() {
        this.workBuffer.destroy();
        this.renderer.destroy();
        this.sorter.destroy();
    }
    createSorter() {
        // create sorter
        const sorter = new GSplatUnifiedSorter();
        sorter.on('sorted', (count, version, orderData)=>{
            this.onSorted(count, version, orderData);
        });
        return sorter;
    }
    /**
     * Supply the manager with the placements to use. This is used to update the manager when the
     * layer's placements have changed, called infrequently.
     *
     * @param {GSplatPlacement[]} placements - The placements to reconcile with.
     */ reconcile(placements) {
        tempNonOctreePlacements.clear();
        for (const p of placements){
            if (p.resource instanceof GSplatOctreeResource) {
                // make sure octree instance exists for placement
                if (!this.octreeInstances.has(p)) {
                    this.octreeInstances.set(p, new GSplatOctreeInstance(p.resource.octree, p, this.director.assetLoader));
                }
                tempOctreePlacements.add(p);
            } else {
                // collect non-octree placement
                tempNonOctreePlacements.add(p);
            }
        }
        // destroy and remove octree instances that are no longer present
        for (const [placement, inst] of this.octreeInstances){
            if (!tempOctreePlacements.has(placement)) {
                this.octreeInstances.delete(placement);
                inst.destroy();
            }
        }
        // compute dirtiness of non-octree placements compared to existing layerPlacements
        this.layerPlacementsDirty = this.layerPlacements.length !== tempNonOctreePlacements.size;
        if (!this.layerPlacementsDirty) {
            for(let i = 0; i < this.layerPlacements.length; i++){
                const existing = this.layerPlacements[i];
                if (!tempNonOctreePlacements.has(existing)) {
                    this.layerPlacementsDirty = true;
                    break;
                }
            }
        }
        // update layerPlacements to new non-octree list
        this.layerPlacements.length = 0;
        for (const p of tempNonOctreePlacements){
            this.layerPlacements.push(p);
        }
        // clear temporaries
        tempNonOctreePlacements.clear();
        tempOctreePlacements.clear();
    }
    updateWorldState() {
        // update octree instances - this handles loading of any pending resources
        for (const [, inst] of this.octreeInstances){
            this.layerPlacementsDirty ||= inst.update();
        }
        // Recreate world state if there are changes
        const worldChanged = this.layerPlacementsDirty || this.worldStates.size === 0;
        if (worldChanged) {
            this.lastWorldStateVersion++;
            const splats = [];
            // add standalone splats
            for (const p of this.layerPlacements){
                const splatInfo = new GSplatInfo(this.device, p.resource, p);
                splats.push(splatInfo);
            }
            // add octree splats
            for (const [, inst] of this.octreeInstances){
                inst.activePlacements.forEach((p)=>{
                    splats.push(new GSplatInfo(this.device, p.resource, p));
                });
            }
            // add resource centers to sorter
            splats.forEach((splat)=>{
                this.sorter.setCenters(splat.resource.id, splat.resource.centers);
            });
            const newState = new GSplatWorldState(this.device, this.lastWorldStateVersion, splats);
            this.worldStates.set(this.lastWorldStateVersion, newState);
            this.layerPlacementsDirty = false;
        }
    }
    onSorted(count, version, orderData) {
        this.sortedVersion = version;
        // remove old state
        const oldState = this.worldStates.get(version - 1);
        if (oldState) {
            this.worldStates.delete(version - 1);
            oldState.destroy();
        }
        // find the world state that has been sorted
        const worldState = this.worldStates.get(version);
        Debug.assert(worldState, `World state with version ${version} not found`);
        if (worldState) {
            // when a new version was sorted for the first time, we need to fully update work buffer
            // to match centers buffer / sorted data
            if (!worldState.sortedBefore) {
                worldState.sortedBefore = true;
                // resize work buffer if needed
                const textureSize = worldState.textureSize;
                if (textureSize !== this.workBuffer.textureSize) {
                    this.workBuffer.resize(textureSize);
                    this.renderer.setMaxNumSplats(textureSize * textureSize);
                }
                // render all splats to work buffer
                this.workBuffer.render(worldState.splats, this.cameraNode);
            }
            // update order texture
            this.workBuffer.setOrderData(orderData);
            // number of splats to render
            this.renderer.setNumSplats(count);
        }
    }
    update(scene) {
        // check if any octree instances have moved enough to require LOD update
        let anyOctreeMoved = false;
        for (const [, inst] of this.octreeInstances){
            anyOctreeMoved ||= inst.testMoved();
        }
        // check if camera has moved enough to require LOD update
        const currentCameraPos = this.cameraNode.getPosition();
        const distance = this.lastCameraPos.distance(currentCameraPos);
        const cameraMoved = distance > 1.0;
        // when camera of octree need LOD evaluated
        if (cameraMoved || anyOctreeMoved) {
            this.lastCameraPos.copy(currentCameraPos);
            // update LOD for all octree instances
            for (const [, inst] of this.octreeInstances){
                inst.updateLod(this.cameraNode);
            }
        }
        // create new world state if needed
        this.updateWorldState();
        // update sorter with new world state
        const lastState = this.worldStates.get(this.lastWorldStateVersion);
        if (lastState) {
            if (!lastState.sortParametersSet) {
                lastState.sortParametersSet = true;
                const payload = this.prepareSortParameters(lastState);
                this.sorter.setSortParameters(payload);
            }
            // debug render world space bounds for all splats
            Debug.call(()=>{
            });
            // kick off sorting
            this.sort(lastState);
        }
        // re-render splats that have changed their transform this frame, using last sorted state
        const sortedState = this.worldStates.get(this.sortedVersion);
        if (sortedState) {
            // Collect splats that have been updated
            sortedState.splats.forEach((splat)=>{
                if (splat.update()) {
                    _updatedSplats.push(splat);
                }
            });
            // Batch render all updated splats in a single render pass
            if (_updatedSplats.length > 0) {
                this.workBuffer.render(_updatedSplats, this.cameraNode);
                _updatedSplats.length = 0;
            }
        }
    }
    /**
     * Sorts the splats of the given world state.
     *
     * @param {GSplatWorldState} lastState - The last world state.
     */ sort(lastState) {
        // Get camera's world-space properties
        const cameraNode = this.cameraNode;
        const cameraMat = cameraNode.getWorldTransform();
        cameraMat.getTranslation(cameraPosition$1);
        cameraMat.getZ(cameraDirection).normalize();
        const sorterRequest = [];
        lastState.splats.forEach((splat)=>{
            const modelMat = splat.node.getWorldTransform();
            invModelMat.copy(modelMat).invert();
            // uniform scale
            const uniformScale = modelMat.getScale().x;
            // camera direction in splat's rotated space
            // transform by the full inverse matrix and then normalize, which cancels the (1/s) scaling factor
            const transformedDirection = invModelMat.transformVector(cameraDirection).normalize();
            // world-space offset
            modelMat.getTranslation(translation);
            const offset = translation.sub(cameraPosition$1).dot(cameraDirection);
            // sorter parameters
            const aabbMin = splat.aabb.getMin();
            const aabbMax = splat.aabb.getMax();
            sorterRequest.push({
                transformedDirection,
                offset,
                scale: uniformScale,
                modelMat: modelMat.data.slice(),
                aabbMin: [
                    aabbMin.x,
                    aabbMin.y,
                    aabbMin.z
                ],
                aabbMax: [
                    aabbMax.x,
                    aabbMax.y,
                    aabbMax.z
                ]
            });
        });
        this.sorter.setSortParams(sorterRequest);
        this.renderer.updateViewport(cameraNode);
    }
    /**
     * Prepares sort parameters data for the sorter worker.
     *
     * @param {GSplatWorldState} worldState - The world state containing all needed data.
     * @returns {object} - Data for sorter worker.
     */ prepareSortParameters(worldState) {
        return {
            command: 'intervals',
            textureSize: worldState.textureSize,
            totalUsedPixels: worldState.totalUsedPixels,
            version: worldState.version,
            ids: worldState.splats.map((splat)=>splat.resource.id),
            lineStarts: worldState.splats.map((splat)=>splat.lineStart),
            padding: worldState.splats.map((splat)=>splat.padding),
            // TODO: consider storing this in typed array and transfer it to sorter worker
            intervals: worldState.splats.map((splat)=>splat.intervals)
        };
    }
}

/**
 * @import { GSplatOctree } from './gsplat-octree.js';
 * @import { LayerComposition } from '../composition/layer-composition.js'
 * @import { Camera } from '../camera.js'
 * @import { Layer } from '../layer.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { GraphNode } from '../graph-node.js'
 * @import { GSplatOctreeResource } from './gsplat-octree.resource.js'
 * @import { GSplatAssetLoaderBase } from './gsplat-asset-loader-base.js'
 * @import { Scene } from '../scene.js'
 */ /**
 * Per layer data the director keeps track of.
 *
 * @ignore
 */ class GSplatLayerData {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {GSplatDirector} director - The director.
     * @param {Layer} layer - The layer.
     * @param {GraphNode} cameraNode - The camera node.
     */ constructor(device, director, layer, cameraNode){
        this.gsplatManager = new GSplatManager(device, director, layer, cameraNode);
    }
    destroy() {
        this.gsplatManager.destroy();
    }
}
/**
 * Per camera data the director keeps track of.
 *
 * @ignore
 */ class GSplatCameraData {
    destroy() {
        this.layersMap.forEach((layerData)=>layerData.destroy());
        this.layersMap.clear();
    }
    removeLayerData(layer) {
        const layerData = this.layersMap.get(layer);
        if (layerData) {
            layerData.destroy();
            this.layersMap.delete(layer);
        }
    }
    getLayerData(device, director, layer, cameraNode) {
        let layerData = this.layersMap.get(layer);
        if (!layerData) {
            layerData = new GSplatLayerData(device, director, layer, cameraNode);
            this.layersMap.set(layer, layerData);
        }
        return layerData;
    }
    constructor(){
        /**
     * @type {Map<Layer, GSplatLayerData>}
     */ this.layersMap = new Map();
    }
}
/**
 * Class responsible for managing {@link GSplatManager} instances for Cameras and their Layers.
 *
 * @ignore
 */ class GSplatDirector {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {Scene} scene - The scene.
     * @param {GSplatAssetLoaderBase} assetLoader - The asset loader.
     */ constructor(device, scene, assetLoader){
        /**
     * Per camera data.
     *
     * @type {Map<Camera, GSplatCameraData>}
     */ this.camerasMap = new Map();
        /**
     * Global octrees, allowing sharing between its placements.
     *
     * @type {Map<GSplatOctreeResource, GSplatOctree>}
     */ this.octrees = new Map();
        this.device = device;
        this.assetLoader = assetLoader;
        this.scene = scene;
    }
    getCameraData(camera) {
        let cameraData = this.camerasMap.get(camera);
        if (!cameraData) {
            cameraData = new GSplatCameraData();
            this.camerasMap.set(camera, cameraData);
        }
        return cameraData;
    }
    /**
     * Updates the director for the given layer composition cameras and layers.
     *
     * @param {LayerComposition} comp - The layer composition.
     */ update(comp) {
        // remove camera / layer entires for cameras / layers no longer in the composition
        this.camerasMap.forEach((cameraData, camera)=>{
            // camera is no longer in the composition
            if (!comp.camerasSet.has(camera)) {
                cameraData.destroy();
                this.camerasMap.delete(camera);
            } else {
                // remove all layerdata for removed / disabled layers of this camera
                cameraData.layersMap.forEach((layerData, layer)=>{
                    if (!camera.layersSet.has(layer.id) || !layer.enabled) {
                        layerData.destroy();
                        cameraData.layersMap.delete(layer);
                    }
                });
            }
        });
        // for all cameras in the composition
        const camerasComponents = comp.cameras;
        for(let i = 0; i < camerasComponents.length; i++){
            const camera = camerasComponents[i].camera;
            let cameraData = this.camerasMap.get(camera);
            // for all of its layers
            const layerIds = camera.layers;
            for(let j = 0; j < layerIds.length; j++){
                const layer = comp.getLayerById(layerIds[j]);
                if (layer?.enabled) {
                    // if layer's splat placements were modified, or new camera
                    if (layer.gsplatPlacementsDirty || !cameraData) {
                        // no splats on layer
                        const placements = layer.gsplatPlacements;
                        if (placements.length === 0) {
                            // remove gsplat manager if it exists
                            if (cameraData) {
                                cameraData.removeLayerData(layer);
                            }
                        } else {
                            // update gsplat manager with modified placements
                            cameraData ??= this.getCameraData(camera);
                            const layerData = cameraData.getLayerData(this.device, this, layer, camera.node);
                            layerData.gsplatManager.reconcile(placements);
                        }
                    }
                }
            }
            // update gsplat managers
            // const cameraData = this.camerasMap.get(camera);
            cameraData?.layersMap.forEach((layerData)=>{
                layerData.gsplatManager.update(this.scene);
            });
        }
        // clear dirty flags on all layers of the composition
        for(let i = 0; i < comp.layerList.length; i++){
            comp.layerList[i].gsplatPlacementsDirty = false;
        }
    }
}

/**
 * @import { BoundingBox } from '../../../core/shape/bounding-box.js'
 * @import { Entity } from '../../entity.js'
 * @import { EventHandle } from '../../../core/event-handle.js'
 * @import { GSplatComponentSystem } from './system.js'
 * @import { ShaderMaterial } from '../../../scene/materials/shader-material.js'
 */ /**
 * The GSplatComponent enables an {@link Entity} to render 3D Gaussian Splats. Splats are always
 * loaded from {@link Asset}s rather than being created programmatically. The asset type is
 * `gsplat` which are in the `.ply` file format.
 *
 * You should never need to use the GSplatComponent constructor directly. To add an
 * GSplatComponent to an {@link Entity}, use {@link Entity#addComponent}:
 *
 * ```javascript
 * const entity = pc.Entity();
 * entity.addComponent('gsplat', {
 *     asset: asset
 * });
 * ```
 *
 * Once the GSplatComponent is added to the entity, you can access it via the {@link Entity#gsplat}
 * property:
 *
 * ```javascript
 * entity.gsplat.customAabb = new pc.BoundingBox(new pc.Vec3(), new pc.Vec3(10, 10, 10));
 *
 * console.log(entity.gsplat.customAabb);
 * ```
 *
 * Relevant Engine API examples:
 *
 * - [Loading a Splat](https://playcanvas.github.io/#/gaussian-splatting/simple)
 * - [Custom Splat Shaders](https://playcanvas.github.io/#/gaussian-splatting/multi-splat)
 * - [Splat picking](https://playcanvas.github.io/#/gaussian-splatting/picking)
 *
 * @hideconstructor
 * @category Graphics
 */ class GSplatComponent extends Component {
    /**
     * Create a new GSplatComponent.
     *
     * @param {GSplatComponentSystem} system - The ComponentSystem that created this Component.
     * @param {Entity} entity - The Entity that this Component is attached to.
     */ constructor(system, entity){
        super(system, entity), /** @private */ this._layers = [
            LAYERID_WORLD
        ] // assign to the default world layer
        , /**
     * @type {GSplatInstance|null}
     * @private
     */ this._instance = null, /**
     * @type {GSplatPlacement|null}
     * @private
     */ this._placement = null, /**
     * @type {ShaderMaterial|null}
     * @private
     */ this._materialTmp = null, /** @private */ this._highQualitySH = true, /**
     * @type {BoundingBox|null}
     * @private
     */ this._customAabb = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayersChanged = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerAdded = null, /**
     * @type {EventHandle|null}
     * @private
     */ this._evtLayerRemoved = null, /** @private */ this._castShadows = false, /**
     * Whether to use the unified gsplat rendering.
     *
     * @type {boolean}
     * @private
     */ this._unified = false;
        // gsplat asset reference
        this._assetReference = new AssetReference('asset', this, system.app.assets, {
            add: this._onGSplatAssetAdded,
            load: this._onGSplatAssetLoad,
            remove: this._onGSplatAssetRemove,
            unload: this._onGSplatAssetUnload
        }, this);
        // handle events when the entity is directly (or indirectly as a child of sub-hierarchy)
        // added or removed from the parent
        entity.on('remove', this.onRemoveChild, this);
        entity.on('removehierarchy', this.onRemoveChild, this);
        entity.on('insert', this.onInsertChild, this);
        entity.on('inserthierarchy', this.onInsertChild, this);
    }
    /**
     * Sets a custom object space bounding box for visibility culling of the attached gsplat.
     *
     * @type {BoundingBox|null}
     */ set customAabb(value) {
        this._customAabb = value;
        // set it on meshInstance
        this._instance?.meshInstance?.setCustomAabb(this._customAabb);
        // set it on placement
        if (this._placement && this._customAabb) {
            this._placement.aabb = this._customAabb;
        }
    }
    /**
     * Gets the custom object space bounding box for visibility culling of the attached gsplat.
     *
     * @type {BoundingBox|null}
     */ get customAabb() {
        return this._customAabb;
    }
    /**
     * Sets a {@link GSplatInstance} on the component. If not set or loaded, it returns null.
     *
     * @type {GSplatInstance|null}
     * @ignore
     */ set instance(value) {
        Debug.assert(!this.unified);
        // destroy existing instance
        this.destroyInstance();
        this._instance = value;
        if (this._instance) {
            // if mesh instance was created without a node, assign it here
            const mi = this._instance.meshInstance;
            if (!mi.node) {
                mi.node = this.entity;
            }
            mi.castShadow = this._castShadows;
            mi.setCustomAabb(this._customAabb);
            if (this.enabled && this.entity.enabled) {
                this.addToLayers();
            }
        }
    }
    /**
     * Gets the {@link GSplatInstance} on the component.
     *
     * @type {GSplatInstance|null}
     * @ignore
     */ get instance() {
        return this._instance;
    }
    /**
     * Sets the material used to render the gsplat.
     *
     * @param {ShaderMaterial} value - The material instance.
     */ set material(value) {
        Debug.assert(!this.unified);
        if (this._instance) {
            this._instance.material = value;
        } else {
            this._materialTmp = value;
        }
    }
    /**
     * Gets the material used to render the gsplat.
     *
     * @type {ShaderMaterial|null}
     */ get material() {
        return this._instance?.material ?? this._materialTmp ?? null;
    }
    /**
     * Sets whether to use the high quality or the approximate (but fast) spherical-harmonic calculation when rendering SOGS data.
     *
     * The low quality approximation evaluates the scene's spherical harmonic contributions
     * along the camera's Z-axis instead of using each gaussian's view vector. This results
     * in gaussians being accurate at the center of the screen and becoming less accurate
     * as they appear further from the center. This is a good trade-off for performance
     * when rendering large SOGS datasets, especially on mobile devices.
     *
     * Defaults to false.
     *
     * @type {boolean}
     */ set highQualitySH(value) {
        if (value !== this._highQualitySH) {
            this._highQualitySH = value;
            this._instance?.setHighQualitySH(value);
        }
    }
    /**
     * Gets whether the high quality (true) or the fast approximate (false) spherical-harmonic calculation is used when rendering SOGS data.
     *
     * @type {boolean}
     */ get highQualitySH() {
        return this._highQualitySH;
    }
    /**
     * Sets whether gsplat will cast shadows for lights that have shadow casting enabled. Defaults
     * to false.
     *
     * @type {boolean}
     */ set castShadows(value) {
        if (this._castShadows !== value) {
            const mi = this.instance?.meshInstance;
            if (mi) {
                const layers = this.layers;
                const scene = this.system.app.scene;
                if (this._castShadows && !value) {
                    for(let i = 0; i < layers.length; i++){
                        const layer = scene.layers.getLayerById(this.layers[i]);
                        if (layer) {
                            layer.removeShadowCasters([
                                mi
                            ]);
                        }
                    }
                }
                mi.castShadow = value;
                if (!this._castShadows && value) {
                    for(let i = 0; i < layers.length; i++){
                        const layer = scene.layers.getLayerById(layers[i]);
                        if (layer) {
                            layer.addShadowCasters([
                                mi
                            ]);
                        }
                    }
                }
            }
            this._castShadows = value;
        }
    }
    /**
     * Gets whether gsplat will cast shadows for lights that have shadow casting enabled.
     *
     * @type {boolean}
     */ get castShadows() {
        return this._castShadows;
    }
    /**
     * Sets whether to use the unified gsplat rendering. Can be changed only when the component is
     * not enabled. Default is false.
     *
     * @type {boolean}
     * @alpha
     */ set unified(value) {
        if (this.enabled && this.entity.enabled) {
            Debug.warn('GSplatComponent#unified can be changed only when the component is not enabled. Ignoring change.');
            return;
        }
        this._unified = value;
        this._onGSplatAssetAdded();
    }
    /**
     * Gets whether to use the unified gsplat rendering.
     *
     * @type {boolean}
     * @alpha
     */ get unified() {
        return this._unified;
    }
    /**
     * Sets an array of layer IDs ({@link Layer#id}) to which this gsplat should belong. Don't
     * push, pop, splice or modify this array. If you want to change it, set a new one instead.
     *
     * @type {number[]}
     */ set layers(value) {
        // remove the mesh instances from old layers
        this.removeFromLayers();
        // set the layer list
        this._layers.length = 0;
        for(let i = 0; i < value.length; i++){
            this._layers[i] = value[i];
        }
        // don't add into layers until we're enabled
        if (!this.enabled || !this.entity.enabled) {
            return;
        }
        // add the mesh instance to new layers
        this.addToLayers();
    }
    /**
     * Gets the array of layer IDs ({@link Layer#id}) to which this gsplat belongs.
     *
     * @type {number[]}
     */ get layers() {
        return this._layers;
    }
    /**
     * Sets the gsplat asset for this gsplat component. Can also be an asset id.
     *
     * @type {Asset|number}
     */ set asset(value) {
        const id = value instanceof Asset ? value.id : value;
        if (this._assetReference.id === id) return;
        if (this._assetReference.asset && this._assetReference.asset.resource) {
            this._onGSplatAssetRemove();
        }
        this._assetReference.id = id;
        if (this._assetReference.asset) {
            this._onGSplatAssetAdded();
        }
    }
    /**
     * Gets the gsplat asset id for this gsplat component.
     *
     * @type {Asset|number}
     */ get asset() {
        return this._assetReference.id;
    }
    /** @private */ destroyInstance() {
        if (this._placement) {
            this.removeFromLayers();
            this._placement = null;
        }
        if (this._instance) {
            this.removeFromLayers();
            this._instance?.destroy();
            this._instance = null;
        }
    }
    /** @private */ addToLayers() {
        if (this._placement) {
            const layers = this.system.app.scene.layers;
            for(let i = 0; i < this._layers.length; i++){
                layers.getLayerById(this._layers[i])?.addGSplatPlacement(this._placement);
            }
            return;
        }
        const meshInstance = this.instance?.meshInstance;
        if (meshInstance) {
            const layers = this.system.app.scene.layers;
            for(let i = 0; i < this._layers.length; i++){
                layers.getLayerById(this._layers[i])?.addMeshInstances([
                    meshInstance
                ]);
            }
        }
    }
    removeFromLayers() {
        if (this._placement) {
            const layers = this.system.app.scene.layers;
            for(let i = 0; i < this._layers.length; i++){
                layers.getLayerById(this._layers[i])?.removeGSplatPlacement(this._placement);
            }
            return;
        }
        const meshInstance = this.instance?.meshInstance;
        if (meshInstance) {
            const layers = this.system.app.scene.layers;
            for(let i = 0; i < this._layers.length; i++){
                layers.getLayerById(this._layers[i])?.removeMeshInstances([
                    meshInstance
                ]);
            }
        }
    }
    /** @private */ onRemoveChild() {
        this.removeFromLayers();
    }
    /** @private */ onInsertChild() {
        if (this.enabled && this.entity.enabled) {
            if (this._instance || this._placement) {
                this.addToLayers();
            }
        }
    }
    onRemove() {
        this.destroyInstance();
        this.asset = null;
        this._assetReference.id = null;
        this.entity.off('remove', this.onRemoveChild, this);
        this.entity.off('insert', this.onInsertChild, this);
    }
    onLayersChanged(oldComp, newComp) {
        this.addToLayers();
        oldComp.off('add', this.onLayerAdded, this);
        oldComp.off('remove', this.onLayerRemoved, this);
        newComp.on('add', this.onLayerAdded, this);
        newComp.on('remove', this.onLayerRemoved, this);
    }
    onLayerAdded(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index < 0) return;
        if (this._instance) {
            layer.addMeshInstances(this._instance.meshInstance);
        }
        Debug.assert(!this.unified);
    }
    onLayerRemoved(layer) {
        const index = this.layers.indexOf(layer.id);
        if (index < 0) return;
        if (this._instance) {
            layer.removeMeshInstances(this._instance.meshInstance);
        }
        Debug.assert(!this.unified);
    }
    onEnable() {
        const scene = this.system.app.scene;
        const layers = scene.layers;
        this._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);
        if (layers) {
            this._evtLayerAdded = layers.on('add', this.onLayerAdded, this);
            this._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);
        }
        if (this._instance || this._placement) {
            this.addToLayers();
        } else if (this.asset) {
            this._onGSplatAssetAdded();
        }
    }
    onDisable() {
        const scene = this.system.app.scene;
        const layers = scene.layers;
        this._evtLayersChanged?.off();
        this._evtLayersChanged = null;
        if (layers) {
            this._evtLayerAdded?.off();
            this._evtLayerAdded = null;
            this._evtLayerRemoved?.off();
            this._evtLayerRemoved = null;
        }
        this.removeFromLayers();
    }
    /**
     * Stop rendering this component without removing its mesh instance from the scene hierarchy.
     */ hide() {
        if (this._instance) {
            this._instance.meshInstance.visible = false;
        }
    }
    /**
     * Enable rendering of the component if hidden using {@link GSplatComponent#hide}.
     */ show() {
        if (this._instance) {
            this._instance.meshInstance.visible = true;
        }
    }
    _onGSplatAssetAdded() {
        if (!this._assetReference.asset) {
            return;
        }
        if (this._assetReference.asset.resource) {
            this._onGSplatAssetLoad();
        } else if (this.enabled && this.entity.enabled) {
            this.system.app.assets.load(this._assetReference.asset);
        }
    }
    _onGSplatAssetLoad() {
        // remove existing instance
        this.destroyInstance();
        const asset = this._assetReference.asset;
        if (this.unified) {
            this._placement = null;
            if (asset) {
                this._placement = new GSplatPlacement(asset.resource, this.entity);
            }
        } else {
            // create new instance
            if (asset) {
                this.instance = new GSplatInstance(asset.resource, {
                    material: this._materialTmp,
                    highQualitySH: this._highQualitySH
                });
                this._materialTmp = null;
            }
        }
        if (asset) {
            this.customAabb = asset.resource.aabb.clone();
        }
    }
    _onGSplatAssetUnload() {
        // when unloading asset, only remove the instance
        this.destroyInstance();
    }
    _onGSplatAssetRemove() {
        this._onGSplatAssetUnload();
    }
}

class GSplatComponentData {
    constructor(){
        this.enabled = true;
    }
}

/**
 * Base class for GSplat asset loaders. This provides the interface that all
 * GSplat asset loaders must implement.
 *
 * @category Asset
 */ class GSplatAssetLoaderBase {
    /**
     * Initiates loading of a gsplat asset. This is a fire-and-forget operation that starts
     * the loading process.
     *
     * @param {string} url - The URL of the gsplat file to load.
     * @abstract
     */ load(url) {
        Debug.error('GSplatAssetLoaderBase#load: Not implemented');
    }
    /**
     * Unloads an asset that was previously loaded by this loader.
     *
     * @param {string} url - The URL of the asset to unload.
     * @abstract
     */ unload(url) {
        Debug.error('GSplatAssetLoaderBase#unload: Not implemented');
    }
    /**
     * Gets the resource for a given URL if it has been loaded by this loader.
     *
     * @param {string} url - The URL of the asset to retrieve the resource from.
     * @returns {object|undefined} The loaded resource if found and loaded, undefined otherwise.
     * @abstract
     */ getResource(url) {
        Debug.error('GSplatAssetLoaderBase#getResource: Not implemented');
    }
}

/**
 * @import { AssetRegistry } from '../../asset/asset-registry.js'
 */ /**
 * A utility class for programmatically loading and unloading gsplat resources. This class provides
 * a simple interface for loading gsplat assets dynamically and manages their lifecycle, including
 * keeping track of loaded assets for efficient unloading.
 *
 * @category Asset
 * @ignore
 */ class GSplatAssetLoader extends GSplatAssetLoaderBase {
    /**
     * Create a new GSplatAssetLoader.
     *
     * @param {AssetRegistry} registry - The asset registry to use for loading assets.
     */ constructor(registry){
        super(), /**
     * Map of URL to Asset instances that this loader has created.
     *
     * @type {Map<string, Asset>}
     * @private
     */ this._urlToAsset = new Map();
        this._registry = registry;
    }
    /**
     * Initiates loading of a gsplat asset. This is a fire-and-forget operation that starts
     * the loading process. Use getResource() later to check if the asset has finished loading.
     *
     * @param {string} url - The URL of the gsplat file to load.
     */ load(url) {
        Debug.assert(url);
        // Check if we already have this asset tracked
        let asset = this._urlToAsset.get(url);
        if (!asset) {
            // Check if the asset registry already has this asset
            asset = this._registry.getByUrl(url);
            if (!asset) {
                // Create a new gsplat asset
                asset = new Asset(url, 'gsplat', {
                    url
                });
                this._registry.add(asset);
            }
            // Track this asset in our map
            this._urlToAsset.set(url, asset);
        }
        // Start loading the asset if it's not already loaded or loading
        if (!asset.loaded && !asset.loading) {
            this._registry.load(asset);
        }
    }
    /**
     * Unloads an asset that was previously loaded by this loader. The asset resource will be
     * destroyed and freed from memory.
     *
     * @param {string} url - The URL of the asset to unload.
     */ unload(url) {
        const asset = this._urlToAsset.get(url);
        if (asset) {
            asset.unload();
            this._urlToAsset.delete(url);
        }
    }
    /**
     * Gets the resource for a given URL if it has been loaded by this loader.
     * Use this when you just need the loaded resource data.
     *
     * @param {string} url - The URL of the asset to retrieve the resource from.
     * @returns {object|undefined} The loaded resource if found and loaded, undefined otherwise.
     */ getResource(url) {
        const asset = this._urlToAsset.get(url);
        return asset?.resource;
    }
}

/**
 * @import { AppBase } from '../../app-base.js'
 */ const _schema = [
    'enabled'
];
// order matters here
const _properties = [
    'unified',
    'castShadows',
    'material',
    'highQualitySH',
    'asset',
    'layers'
];
/**
 * Allows an Entity to render a gsplat.
 *
 * @category Graphics
 */ class GSplatComponentSystem extends ComponentSystem {
    /**
     * Create a new GSplatComponentSystem.
     *
     * @param {AppBase} app - The Application.
     * @ignore
     */ constructor(app){
        super(app);
        this.id = 'gsplat';
        this.ComponentType = GSplatComponent;
        this.DataType = GSplatComponentData;
        this.schema = _schema;
        // loader for splat LOD assets, as asset system is not available on the scene level
        const gsplatAssetLoader = new GSplatAssetLoader(app.assets);
        app.renderer.gsplatDirector = new GSplatDirector(app.graphicsDevice, app.scene, gsplatAssetLoader);
        this.on('beforeremove', this.onRemove, this);
    }
    initializeComponentData(component, _data, properties) {
        // duplicate layer list
        if (_data.layers && _data.layers.length) {
            _data.layers = _data.layers.slice(0);
        }
        for(let i = 0; i < _properties.length; i++){
            if (_data.hasOwnProperty(_properties[i])) {
                component[_properties[i]] = _data[_properties[i]];
            }
        }
        if (_data.aabbCenter && _data.aabbHalfExtents) {
            component.customAabb = new BoundingBox(new Vec3(_data.aabbCenter), new Vec3(_data.aabbHalfExtents));
        }
        super.initializeComponentData(component, _data, _schema);
    }
    cloneComponent(entity, clone) {
        const gSplatComponent = entity.gsplat;
        // copy properties
        const data = {};
        _properties.forEach((prop)=>{
            if (prop === 'material') {
                const srcMaterial = gSplatComponent[prop];
                if (srcMaterial) {
                    data[prop] = srcMaterial.clone();
                }
            } else {
                data[prop] = gSplatComponent[prop];
            }
        });
        data.enabled = gSplatComponent.enabled;
        // clone component
        const component = this.addComponent(clone, data);
        if (gSplatComponent.customAabb) {
            component.customAabb = gSplatComponent.customAabb.clone();
        }
        return component;
    }
    onRemove(entity, component) {
        component.onRemove();
    }
}
Component._buildAccessors(GSplatComponent.prototype, _schema);

/**
 * @import { Mesh } from './mesh.js'
 */ /**
 * A `Render` contains an array of meshes that are referenced by a single hierarchy node in a GLB
 * scene, and are accessible using the {@link ContainerResource#renders} property. A `Render` is
 * the resource of a Render Asset. They are usually created by the GLB loader and not created by
 * hand.
 *
 * @ignore
 */ class Render extends EventHandler {
    static{
        /**
     * Fired when the meshes are set on the render. The handler is passed the an array of
     * {@link Mesh} objects.
     *
     * @event
     * @example
     * render.on('set:meshes', (meshes) => {
     *     console.log(`Render has ${meshes.length} meshes`);
     * });
     */ this.EVENT_SETMESHES = 'set:meshes';
    }
    /**
     * Sets the meshes that the render contains.
     *
     * @type {Array<Mesh|null>|null}
     */ set meshes(value) {
        // decrement references on the existing meshes
        this.decRefMeshes();
        // assign new meshes
        this._meshes = value;
        this.incRefMeshes();
        this.fire('set:meshes', value);
    }
    /**
     * Gets the meshes that the render contains.
     *
     * @type {Array<Mesh|null>|null}
     */ get meshes() {
        return this._meshes;
    }
    destroy() {
        this.meshes = null;
    }
    /**
     * Decrement references to meshes. Destroy the ones with zero references.
     */ decRefMeshes() {
        this._meshes?.forEach((mesh, index)=>{
            if (mesh) {
                mesh.decRefCount();
                if (mesh.refCount < 1) {
                    mesh.destroy();
                    this._meshes[index] = null;
                }
            }
        });
    }
    /**
     * Increments ref count on all meshes.
     */ incRefMeshes() {
        this._meshes?.forEach((mesh)=>{
            mesh?.incRefCount();
        });
    }
    constructor(...args){
        super(...args), /**
     * Meshes are reference counted, and this class owns the references and is responsible for
     * releasing the meshes when they are no longer referenced.
     *
     * @type {Array<Mesh|null>|null}
     * @private
     */ this._meshes = null;
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 */ // The scope of this function is the render asset
function onContainerAssetLoaded(containerAsset) {
    const renderAsset = this;
    if (!renderAsset.resource) return;
    const containerResource = containerAsset.resource;
    const render = containerResource.renders && containerResource.renders[renderAsset.data.renderIndex];
    if (render) {
        renderAsset.resource.meshes = render.resource.meshes;
    }
}
// The scope of this function is the render asset
function onContainerAssetAdded(containerAsset) {
    const renderAsset = this;
    renderAsset.registry.off(`load:${containerAsset.id}`, onContainerAssetLoaded, renderAsset);
    renderAsset.registry.on(`load:${containerAsset.id}`, onContainerAssetLoaded, renderAsset);
    renderAsset.registry.off(`remove:${containerAsset.id}`, onContainerAssetRemoved, renderAsset);
    renderAsset.registry.once(`remove:${containerAsset.id}`, onContainerAssetRemoved, renderAsset);
    if (!containerAsset.resource) {
        renderAsset.registry.load(containerAsset);
    } else {
        onContainerAssetLoaded.call(renderAsset, containerAsset);
    }
}
function onContainerAssetRemoved(containerAsset) {
    const renderAsset = this;
    renderAsset.registry.off(`load:${containerAsset.id}`, onContainerAssetLoaded, renderAsset);
    if (renderAsset.resource) {
        renderAsset.resource.destroy();
    }
}
/**
 * Resource handler used for loading {@link Render} resources.
 *
 * @category Graphics
 */ class RenderHandler extends ResourceHandler {
    /**
     * Create a new RenderHandler instance.
     *
     * @param {AppBase} app - The running {@link AppBase}.
     * @ignore
     */ constructor(app){
        super(app, 'render');
        this._registry = app.assets;
    }
    open(url, data) {
        return new Render();
    }
    patch(asset, registry) {
        if (!asset.data.containerAsset) {
            return;
        }
        const containerAsset = registry.get(asset.data.containerAsset);
        if (!containerAsset) {
            registry.once(`add:${asset.data.containerAsset}`, onContainerAssetAdded, asset);
            return;
        }
        onContainerAssetAdded.call(asset, containerAsset);
    }
}

/**
 * Animation curve links an input data set to an output data set and defines the interpolation
 * method to use.
 *
 * @category Animation
 */ class AnimCurve {
    /**
     * Create a new animation curve.
     *
     * @param {string[]} paths - Array of path strings identifying the targets of this curve, for
     * example "rootNode.translation".
     * @param {number} input - Index of the curve which specifies the key data.
     * @param {number} output - Index of the curve which specifies the value data.
     * @param {number} interpolation - The interpolation method to use. One of the following:
     *
     * - {@link INTERPOLATION_STEP}
     * - {@link INTERPOLATION_LINEAR}
     * - {@link INTERPOLATION_CUBIC}
     */ constructor(paths, input, output, interpolation){
        this._paths = paths;
        this._input = input;
        this._output = output;
        this._interpolation = interpolation;
    }
    /**
     * The list of paths which identify targets of this curve.
     *
     * @type {string[]}
     */ get paths() {
        return this._paths;
    }
    /**
     * The index of the AnimTrack input which contains the key data for this curve.
     *
     * @type {number}
     */ get input() {
        return this._input;
    }
    /**
     * The index of the AnimTrack input which contains the key data for this curve.
     *
     * @type {number}
     */ get output() {
        return this._output;
    }
    /**
     * The interpolation method used by this curve.
     *
     * @type {number}
     */ get interpolation() {
        return this._interpolation;
    }
}

/**
 * Wraps a set of data used in animation.
 *
 * @category Animation
 */ class AnimData {
    /**
     * Create a new animation AnimData instance.
     *
     * @param {number} components - Specifies how many components make up an element of data. For
     * example, specify 3 for a set of 3-dimensional vectors. The number of elements in data array
     * must be a multiple of components.
     * @param {Float32Array|number[]} data - The set of data.
     */ constructor(components, data){
        this._components = components;
        this._data = data;
    }
    /**
     * Gets the number of components that make up an element.
     *
     * @type {number}
     */ get components() {
        return this._components;
    }
    /**
     * Gets the data.
     *
     * @type {Float32Array|number[]}
     */ get data() {
        return this._data;
    }
}

function DracoWorker(jsUrl, wasmUrl) {
    let draco;
    // https://github.com/google/draco/blob/master/src/draco/attributes/geometry_attribute.h#L43
    const POSITION_ATTRIBUTE = 0;
    const NORMAL_ATTRIBUTE = 1;
    const wrap = (typedArray, dataType)=>{
        switch(dataType){
            case draco.DT_INT8:
                return new Int8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
            case draco.DT_INT16:
                return new Int16Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 2);
            case draco.DT_INT32:
                return new Int32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);
            case draco.DT_UINT8:
                return new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
            case draco.DT_UINT16:
                return new Uint16Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 2);
            case draco.DT_UINT32:
                return new Uint32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);
            case draco.DT_FLOAT32:
                return new Float32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);
        }
        return null;
    };
    const componentSizeInBytes = (dataType)=>{
        switch(dataType){
            case draco.DT_INT8:
                return 1;
            case draco.DT_INT16:
                return 2;
            case draco.DT_INT32:
                return 4;
            case draco.DT_UINT8:
                return 1;
            case draco.DT_UINT16:
                return 2;
            case draco.DT_UINT32:
                return 4;
            case draco.DT_FLOAT32:
                return 4;
        }
        return 1;
    };
    const attributeSizeInBytes = (attribute)=>{
        return attribute.num_components() * componentSizeInBytes(attribute.data_type());
    };
    const attributeOrder = {
        0: 0,
        1: 1,
        5: 2,
        2: 3,
        7: 4,
        8: 5,
        4: 6,
        3: 7 // texcoord
    };
    const generateNormals = (vertices, indices)=>{
        const subtract = (dst, a, b)=>{
            dst[0] = a[0] - b[0];
            dst[1] = a[1] - b[1];
            dst[2] = a[2] - b[2];
        };
        const cross = (dst, a, b)=>{
            dst[0] = a[1] * b[2] - b[1] * a[2];
            dst[1] = a[2] * b[0] - b[2] * a[0];
            dst[2] = a[0] * b[1] - b[0] * a[1];
        };
        const normalize = (dst, offset)=>{
            const a = dst[offset + 0];
            const b = dst[offset + 1];
            const c = dst[offset + 2];
            const l = 1.0 / Math.sqrt(a * a + b * b + c * c);
            dst[offset + 0] *= l;
            dst[offset + 1] *= l;
            dst[offset + 2] *= l;
        };
        const copy = (dst, src, srcOffset)=>{
            for(let i = 0; i < 3; ++i){
                dst[i] = src[srcOffset + i];
            }
        };
        const numTriangles = indices.length / 3;
        const numVertices = vertices.length / 3;
        const result = new Float32Array(vertices.length);
        const a = [
            0,
            0,
            0
        ], b = [
            0,
            0,
            0
        ], c = [
            0,
            0,
            0
        ], t1 = [
            0,
            0,
            0
        ], t2 = [
            0,
            0,
            0
        ], n = [
            0,
            0,
            0
        ];
        for(let i = 0; i < numTriangles; ++i){
            const v0 = indices[i * 3 + 0] * 3;
            const v1 = indices[i * 3 + 1] * 3;
            const v2 = indices[i * 3 + 2] * 3;
            copy(a, vertices, v0);
            copy(b, vertices, v1);
            copy(c, vertices, v2);
            subtract(t1, b, a);
            subtract(t2, c, a);
            cross(n, t1, t2);
            normalize(n, 0);
            for(let j = 0; j < 3; ++j){
                result[v0 + j] += n[j];
                result[v1 + j] += n[j];
                result[v2 + j] += n[j];
            }
        }
        for(let i = 0; i < numVertices; ++i){
            normalize(result, i * 3);
        }
        return new Uint8Array(result.buffer);
    };
    const decodeMesh = (inputBuffer)=>{
        const result = {};
        const buffer = new draco.DecoderBuffer();
        buffer.Init(inputBuffer, inputBuffer.length);
        const decoder = new draco.Decoder();
        if (decoder.GetEncodedGeometryType(buffer) !== draco.TRIANGULAR_MESH) {
            result.error = 'Failed to decode draco mesh: not a mesh';
            return result;
        }
        const mesh = new draco.Mesh();
        const status = decoder.DecodeBufferToMesh(buffer, mesh);
        if (!status || !status.ok() || draco.getPointer(mesh) === 0) {
            result.error = 'Failed to decode draco asset';
            return result;
        }
        // indices
        const numIndices = mesh.num_faces() * 3;
        const shortIndices = mesh.num_points() <= 65535;
        const indicesSize = numIndices * (shortIndices ? 2 : 4);
        const indicesPtr = draco._malloc(indicesSize);
        if (shortIndices) {
            decoder.GetTrianglesUInt16Array(mesh, indicesSize, indicesPtr);
            result.indices = new Uint16Array(draco.HEAPU16.buffer, indicesPtr, numIndices).slice().buffer;
        } else {
            decoder.GetTrianglesUInt32Array(mesh, indicesSize, indicesPtr);
            result.indices = new Uint32Array(draco.HEAPU32.buffer, indicesPtr, numIndices).slice().buffer;
        }
        draco._free(indicesPtr);
        // vertices
        const attributes = [];
        for(let i = 0; i < mesh.num_attributes(); ++i){
            attributes.push(decoder.GetAttribute(mesh, i));
        }
        // order attributes
        attributes.sort((a, b)=>{
            return (attributeOrder[a.attribute_type()] ?? attributeOrder.length) - (attributeOrder[b.attribute_type()] ?? attributeOrder.length);
        });
        // store attribute order by unique_id
        result.attributes = attributes.map((a)=>a.unique_id());
        // calculate total vertex size and attribute offsets
        let totalVertexSize = 0;
        const offsets = attributes.map((a)=>{
            const offset = totalVertexSize;
            totalVertexSize += Math.ceil(attributeSizeInBytes(a) / 4) * 4;
            return offset;
        });
        // we will generate normals if they're missing
        const hasNormals = attributes.some((a)=>a.attribute_type() === NORMAL_ATTRIBUTE);
        const normalOffset = offsets[1];
        if (!hasNormals) {
            for(let i = 1; i < offsets.length; ++i){
                offsets[i] += 12;
            }
            totalVertexSize += 12;
        }
        // create vertex buffer
        result.vertices = new ArrayBuffer(mesh.num_points() * totalVertexSize);
        // decode and interleave the vertex data
        const dst = new Uint8Array(result.vertices);
        for(let i = 0; i < mesh.num_attributes(); ++i){
            const attribute = attributes[i];
            const sizeInBytes = attributeSizeInBytes(attribute);
            const ptrSize = mesh.num_points() * sizeInBytes;
            const ptr = draco._malloc(ptrSize);
            decoder.GetAttributeDataArrayForAllPoints(mesh, attribute, attribute.data_type(), ptrSize, ptr);
            const src = new Uint8Array(draco.HEAPU8.buffer, ptr, ptrSize);
            // pack
            for(let j = 0; j < mesh.num_points(); ++j){
                for(let c = 0; c < sizeInBytes; ++c){
                    dst[j * totalVertexSize + offsets[i] + c] = src[j * sizeInBytes + c];
                }
            }
            if (!hasNormals && attribute.attribute_type() === POSITION_ATTRIBUTE) {
                // generate normals just after position
                const normals = generateNormals(wrap(src, attribute.data_type()), shortIndices ? new Uint16Array(result.indices) : new Uint32Array(result.indices));
                // pack normals
                for(let j = 0; j < mesh.num_points(); ++j){
                    for(let c = 0; c < 12; ++c){
                        dst[j * totalVertexSize + normalOffset + c] = normals[j * 12 + c];
                    }
                }
            }
            draco._free(ptr);
        }
        // cleanup
        draco.destroy(mesh);
        draco.destroy(decoder);
        draco.destroy(buffer);
        return result;
    };
    const decode = (data)=>{
        const result = decodeMesh(new Uint8Array(data.buffer));
        self.postMessage({
            jobId: data.jobId,
            error: result.error,
            indices: result.indices,
            vertices: result.vertices,
            attributes: result.attributes
        }, [
            result.indices,
            result.vertices
        ].filter((t)=>t != null));
    };
    const workQueue = [];
    // handle incoming message
    self.onmessage = (message)=>{
        const data = message.data;
        switch(data.type){
            case 'init':
                // initialize draco module
                self.DracoDecoderModule({
                    instantiateWasm: (imports, successCallback)=>{
                        WebAssembly.instantiate(data.module, imports).then((result)=>successCallback(result)).catch((reason)=>console.error(`instantiate failed + ${reason}`));
                        return {};
                    }
                }).then((instance)=>{
                    draco = instance;
                    workQueue.forEach((data)=>decode(data));
                });
                break;
            case 'decodeMesh':
                if (draco) {
                    decode(data);
                } else {
                    workQueue.push(data);
                }
                break;
        }
    };
}

const downloadMaxRetries = 3;
// JobQueue keeps track of a set of web workers and enqueues jobs
// on them. To keep workload as balanced as possible (but also keep
// workers busy) workers have a maximum of 2 jobs assigned at any
// one time.
class JobQueue {
    constructor(){
        // list of workers with: [[0 jobs], [1 job], [2 jobs]]
        this.workers = [
            [],
            [],
            []
        ];
        this.jobId = 0;
        this.jobQueue = [];
        this.jobCallbacks = new Map();
        this.run = (worker, job)=>{
            worker.postMessage({
                type: 'decodeMesh',
                jobId: job.jobId,
                buffer: job.buffer
            }, [
                job.buffer
            ]);
        };
    }
    // initialize the queue with worker instances
    init(workers) {
        workers.forEach((worker)=>{
            worker.addEventListener('message', (message)=>{
                const data = message.data;
                const callback = this.jobCallbacks.get(data.jobId);
                if (callback) {
                    callback(data.error, {
                        indices: data.indices,
                        vertices: data.vertices,
                        attributes: data.attributes
                    });
                }
                this.jobCallbacks.delete(data.jobId);
                if (this.jobQueue.length > 0) {
                    const job = this.jobQueue.shift();
                    this.run(worker, job);
                } else {
                    const index2 = this.workers[2].indexOf(worker);
                    if (index2 !== -1) {
                        this.workers[2].splice(index2, 1);
                        this.workers[1].push(worker);
                    } else {
                        const index1 = this.workers[1].indexOf(worker);
                        if (index1 !== -1) {
                            this.workers[1].splice(index1, 1);
                            this.workers[0].push(worker);
                        } else {
                            // logical error
                            Debug.error('logical error');
                        }
                    }
                }
            });
        });
        // assign workers to the first queue
        this.workers[0] = workers;
        // run queued up jobs
        while(this.jobQueue.length && (this.workers[0].length || this.workers[1].length)){
            const job = this.jobQueue.shift();
            if (this.workers[0].length > 0) {
                const worker = this.workers[0].shift();
                this.workers[1].push(worker);
                this.run(worker, job);
            } else {
                const worker = this.workers[1].shift();
                this.workers[2].push(worker);
                this.run(worker, job);
            }
        }
    }
    enqueueJob(buffer, callback) {
        const job = {
            jobId: this.jobId++,
            buffer: buffer
        };
        this.jobCallbacks.set(job.jobId, callback);
        if (this.workers[0].length > 0) {
            const worker = this.workers[0].shift();
            this.workers[1].push(worker);
            this.run(worker, job);
        } else if (this.workers[1].length > 0) {
            const worker = this.workers[1].shift();
            this.workers[2].push(worker);
            this.run(worker, job);
        } else {
            this.jobQueue.push(job);
        }
    }
}
const downloadScript = (url)=>{
    return new Promise((resolve, reject)=>{
        const options = {
            cache: true,
            responseType: 'text',
            retry: downloadMaxRetries > 0,
            maxRetries: downloadMaxRetries
        };
        http.get(url, options, (err, response)=>{
            if (err) {
                reject(err);
            } else {
                resolve(response);
            }
        });
    });
};
const compileModule = (url)=>{
    const compileManual = ()=>{
        return fetch(url).then((result)=>result.arrayBuffer()).then((buffer)=>WebAssembly.compile(buffer));
    };
    const compileStreaming = ()=>{
        return WebAssembly.compileStreaming(fetch(url)).catch((err)=>{
            Debug.warn(`compileStreaming() failed for ${url} (${err}), falling back to arraybuffer download.`);
            return compileManual();
        });
    };
    // download and compile wasm module
    return WebAssembly.compileStreaming ? compileStreaming() : compileManual();
};
const defaultNumWorkers$1 = 1;
let jobQueue;
const initializeWorkers = (config)=>{
    if (jobQueue) {
        return true;
    }
    if (!config) {
        {
            const moduleConfig = WasmModule.getConfig('DracoDecoderModule');
            if (moduleConfig) {
                config = {
                    jsUrl: moduleConfig.glueUrl,
                    wasmUrl: moduleConfig.wasmUrl,
                    numWorkers: moduleConfig.numWorkers
                };
            } else {
                config = {
                    jsUrl: 'draco.wasm.js',
                    wasmUrl: 'draco.wasm.wasm',
                    numWorkers: defaultNumWorkers$1
                };
            }
        }
    }
    if (!config.jsUrl || !config.wasmUrl) {
        return false;
    }
    // create the job queue
    jobQueue = new JobQueue();
    // worker urls must be absolute
    Promise.all([
        downloadScript(config.jsUrl),
        compileModule(config.wasmUrl)
    ]).then(([dracoSource, dracoModule])=>{
        // build worker source
        const code = [
            '/* draco */',
            dracoSource,
            '/* worker */',
            `(\n${DracoWorker.toString()}\n)()\n\n`
        ].join('\n');
        const blob = new Blob([
            code
        ], {
            type: "application/javascript"
        });
        const workerUrl = URL.createObjectURL(blob);
        const numWorkers = Math.max(1, Math.min(16, config.numWorkers || defaultNumWorkers$1));
        // create worker instances
        const workers = [];
        for(let i = 0; i < numWorkers; ++i){
            const worker = new Worker(workerUrl);
            worker.postMessage({
                type: 'init',
                module: dracoModule
            });
            workers.push(worker);
        }
        jobQueue.init(workers);
    });
    return true;
};
/**
 * Enqueue a buffer for decoding.
 *
 * @param {ArrayBuffer} buffer - The draco data to decode.
 * @param {Function} callback - Callback function to receive decoded result.
 * @returns {boolean} True if the draco worker was initialized and false otherwise.
 */ const dracoDecode = (buffer, callback)=>{
    if (!initializeWorkers()) {
        return false;
    }
    jobQueue.enqueueJob(buffer, callback);
    return true;
};

// resources loaded from GLB file that the parser returns
class GlbResources {
    destroy() {
        // render needs to dec ref meshes
        if (this.renders) {
            this.renders.forEach((render)=>{
                render.meshes = null;
            });
        }
    }
}
const isDataURI = (uri)=>{
    return /^data:[^\n\r,\u2028\u2029]*,.*$/i.test(uri);
};
const getDataURIMimeType = (uri)=>{
    return uri.substring(uri.indexOf(':') + 1, uri.indexOf(';'));
};
const getNumComponents = (accessorType)=>{
    switch(accessorType){
        case 'SCALAR':
            return 1;
        case 'VEC2':
            return 2;
        case 'VEC3':
            return 3;
        case 'VEC4':
            return 4;
        case 'MAT2':
            return 4;
        case 'MAT3':
            return 9;
        case 'MAT4':
            return 16;
        default:
            return 3;
    }
};
const getComponentType = (componentType)=>{
    switch(componentType){
        case 5120:
            return TYPE_INT8;
        case 5121:
            return TYPE_UINT8;
        case 5122:
            return TYPE_INT16;
        case 5123:
            return TYPE_UINT16;
        case 5124:
            return TYPE_INT32;
        case 5125:
            return TYPE_UINT32;
        case 5126:
            return TYPE_FLOAT32;
        default:
            return 0;
    }
};
const getComponentSizeInBytes = (componentType)=>{
    switch(componentType){
        case 5120:
            return 1; // int8
        case 5121:
            return 1; // uint8
        case 5122:
            return 2; // int16
        case 5123:
            return 2; // uint16
        case 5124:
            return 4; // int32
        case 5125:
            return 4; // uint32
        case 5126:
            return 4; // float32
        default:
            return 0;
    }
};
const getComponentDataType = (componentType)=>{
    switch(componentType){
        case 5120:
            return Int8Array;
        case 5121:
            return Uint8Array;
        case 5122:
            return Int16Array;
        case 5123:
            return Uint16Array;
        case 5124:
            return Int32Array;
        case 5125:
            return Uint32Array;
        case 5126:
            return Float32Array;
        default:
            return null;
    }
};
const gltfToEngineSemanticMap = {
    'POSITION': SEMANTIC_POSITION,
    'NORMAL': SEMANTIC_NORMAL,
    'TANGENT': SEMANTIC_TANGENT,
    'COLOR_0': SEMANTIC_COLOR,
    'JOINTS_0': SEMANTIC_BLENDINDICES,
    'WEIGHTS_0': SEMANTIC_BLENDWEIGHT,
    'TEXCOORD_0': SEMANTIC_TEXCOORD0,
    'TEXCOORD_1': SEMANTIC_TEXCOORD1,
    'TEXCOORD_2': SEMANTIC_TEXCOORD2,
    'TEXCOORD_3': SEMANTIC_TEXCOORD3,
    'TEXCOORD_4': SEMANTIC_TEXCOORD4,
    'TEXCOORD_5': SEMANTIC_TEXCOORD5,
    'TEXCOORD_6': SEMANTIC_TEXCOORD6,
    'TEXCOORD_7': SEMANTIC_TEXCOORD7
};
// order vertexDesc to match the rest of the engine
const attributeOrder = {
    [SEMANTIC_POSITION]: 0,
    [SEMANTIC_NORMAL]: 1,
    [SEMANTIC_TANGENT]: 2,
    [SEMANTIC_COLOR]: 3,
    [SEMANTIC_BLENDINDICES]: 4,
    [SEMANTIC_BLENDWEIGHT]: 5,
    [SEMANTIC_TEXCOORD0]: 6,
    [SEMANTIC_TEXCOORD1]: 7,
    [SEMANTIC_TEXCOORD2]: 8,
    [SEMANTIC_TEXCOORD3]: 9,
    [SEMANTIC_TEXCOORD4]: 10,
    [SEMANTIC_TEXCOORD5]: 11,
    [SEMANTIC_TEXCOORD6]: 12,
    [SEMANTIC_TEXCOORD7]: 13
};
// returns a function for dequantizing the data type
const getDequantizeFunc = (srcType)=>{
    // see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data
    switch(srcType){
        case TYPE_INT8:
            return (x)=>Math.max(x / 127.0, -1);
        case TYPE_UINT8:
            return (x)=>x / 255.0;
        case TYPE_INT16:
            return (x)=>Math.max(x / 32767.0, -1);
        case TYPE_UINT16:
            return (x)=>x / 65535.0;
        default:
            return (x)=>x;
    }
};
// dequantize an array of data
const dequantizeArray = (dstArray, srcArray, srcType)=>{
    const convFunc = getDequantizeFunc(srcType);
    const len = srcArray.length;
    for(let i = 0; i < len; ++i){
        dstArray[i] = convFunc(srcArray[i]);
    }
    return dstArray;
};
// get accessor data, making a copy and patching in the case of a sparse accessor
const getAccessorData = (gltfAccessor, bufferViews, flatten = false)=>{
    const numComponents = getNumComponents(gltfAccessor.type);
    const dataType = getComponentDataType(gltfAccessor.componentType);
    if (!dataType) {
        return null;
    }
    let result;
    if (gltfAccessor.sparse) {
        // handle sparse data
        const sparse = gltfAccessor.sparse;
        // get indices data
        const indicesAccessor = {
            count: sparse.count,
            type: 'SCALAR'
        };
        const indices = getAccessorData(Object.assign(indicesAccessor, sparse.indices), bufferViews, true);
        // data values data
        const valuesAccessor = {
            count: sparse.count,
            type: gltfAccessor.type,
            componentType: gltfAccessor.componentType
        };
        const values = getAccessorData(Object.assign(valuesAccessor, sparse.values), bufferViews, true);
        // get base data
        if (gltfAccessor.hasOwnProperty('bufferView')) {
            const baseAccessor = {
                bufferView: gltfAccessor.bufferView,
                byteOffset: gltfAccessor.byteOffset,
                componentType: gltfAccessor.componentType,
                count: gltfAccessor.count,
                type: gltfAccessor.type
            };
            // make a copy of the base data since we'll patch the values
            result = getAccessorData(baseAccessor, bufferViews, true).slice();
        } else {
            // there is no base data, create empty 0'd out data
            result = new dataType(gltfAccessor.count * numComponents);
        }
        for(let i = 0; i < sparse.count; ++i){
            const targetIndex = indices[i];
            for(let j = 0; j < numComponents; ++j){
                result[targetIndex * numComponents + j] = values[i * numComponents + j];
            }
        }
    } else {
        if (gltfAccessor.hasOwnProperty('bufferView')) {
            const bufferView = bufferViews[gltfAccessor.bufferView];
            if (flatten && bufferView.hasOwnProperty('byteStride')) {
                // flatten stridden data
                const bytesPerElement = numComponents * dataType.BYTES_PER_ELEMENT;
                const storage = new ArrayBuffer(gltfAccessor.count * bytesPerElement);
                const tmpArray = new Uint8Array(storage);
                let dstOffset = 0;
                for(let i = 0; i < gltfAccessor.count; ++i){
                    // no need to add bufferView.byteOffset because accessor takes this into account
                    let srcOffset = (gltfAccessor.byteOffset || 0) + i * bufferView.byteStride;
                    for(let b = 0; b < bytesPerElement; ++b){
                        tmpArray[dstOffset++] = bufferView[srcOffset++];
                    }
                }
                result = new dataType(storage);
            } else {
                result = new dataType(bufferView.buffer, bufferView.byteOffset + (gltfAccessor.byteOffset || 0), gltfAccessor.count * numComponents);
            }
        } else {
            result = new dataType(gltfAccessor.count * numComponents);
        }
    }
    return result;
};
// get accessor data as (unnormalized, unquantized) Float32 data
const getAccessorDataFloat32 = (gltfAccessor, bufferViews)=>{
    const data = getAccessorData(gltfAccessor, bufferViews, true);
    if (data instanceof Float32Array || !gltfAccessor.normalized) {
        // if the source data is quantized (say to int16), but not normalized
        // then reading the values of the array is the same whether the values
        // are stored as float32 or int16. so probably no need to convert to
        // float32.
        return data;
    }
    const float32Data = new Float32Array(data.length);
    dequantizeArray(float32Data, data, getComponentType(gltfAccessor.componentType));
    return float32Data;
};
// returns a dequantized bounding box for the accessor
const getAccessorBoundingBox = (gltfAccessor)=>{
    let min = gltfAccessor.min;
    let max = gltfAccessor.max;
    if (!min || !max) {
        return null;
    }
    if (gltfAccessor.normalized) {
        const ctype = getComponentType(gltfAccessor.componentType);
        min = dequantizeArray([], min, ctype);
        max = dequantizeArray([], max, ctype);
    }
    return new BoundingBox(new Vec3((max[0] + min[0]) * 0.5, (max[1] + min[1]) * 0.5, (max[2] + min[2]) * 0.5), new Vec3((max[0] - min[0]) * 0.5, (max[1] - min[1]) * 0.5, (max[2] - min[2]) * 0.5));
};
const getPrimitiveType = (primitive)=>{
    if (!primitive.hasOwnProperty('mode')) {
        return PRIMITIVE_TRIANGLES;
    }
    switch(primitive.mode){
        case 0:
            return PRIMITIVE_POINTS;
        case 1:
            return PRIMITIVE_LINES;
        case 2:
            return PRIMITIVE_LINELOOP;
        case 3:
            return PRIMITIVE_LINESTRIP;
        case 4:
            return PRIMITIVE_TRIANGLES;
        case 5:
            return PRIMITIVE_TRISTRIP;
        case 6:
            return PRIMITIVE_TRIFAN;
        default:
            return PRIMITIVE_TRIANGLES;
    }
};
const generateIndices = (numVertices)=>{
    const dummyIndices = new Uint16Array(numVertices);
    for(let i = 0; i < numVertices; i++){
        dummyIndices[i] = i;
    }
    return dummyIndices;
};
const generateNormals = (sourceDesc, indices)=>{
    // get positions
    const p = sourceDesc[SEMANTIC_POSITION];
    if (!p || p.components !== 3) {
        return;
    }
    let positions;
    if (p.size !== p.stride) {
        // extract positions which aren't tightly packed
        const srcStride = p.stride / typedArrayTypesByteSize[p.type];
        const src = new typedArrayTypes[p.type](p.buffer, p.offset, p.count * srcStride);
        positions = new typedArrayTypes[p.type](p.count * 3);
        for(let i = 0; i < p.count; ++i){
            positions[i * 3 + 0] = src[i * srcStride + 0];
            positions[i * 3 + 1] = src[i * srcStride + 1];
            positions[i * 3 + 2] = src[i * srcStride + 2];
        }
    } else {
        // position data is tightly packed so we can use it directly
        positions = new typedArrayTypes[p.type](p.buffer, p.offset, p.count * 3);
    }
    const numVertices = p.count;
    // generate indices if necessary
    if (!indices) {
        indices = generateIndices(numVertices);
    }
    // generate normals
    const normalsTemp = calculateNormals(positions, indices);
    const normals = new Float32Array(normalsTemp.length);
    normals.set(normalsTemp);
    sourceDesc[SEMANTIC_NORMAL] = {
        buffer: normals.buffer,
        size: 12,
        offset: 0,
        stride: 12,
        count: numVertices,
        components: 3,
        type: TYPE_FLOAT32
    };
};
// given a texture, clone it
// NOTE: CPU-side texture data will be shared but GPU memory will be duplicated
const cloneTexture = (texture)=>{
    const shallowCopyLevels = (texture)=>{
        const result = [];
        for(let mip = 0; mip < texture._levels.length; ++mip){
            let level = [];
            if (texture.cubemap) {
                for(let face = 0; face < 6; ++face){
                    level.push(texture._levels[mip][face]);
                }
            } else {
                level = texture._levels[mip];
            }
            result.push(level);
        }
        return result;
    };
    const result = new Texture(texture.device, texture); // duplicate texture
    result._levels = shallowCopyLevels(texture); // shallow copy the levels structure
    return result;
};
// given a texture asset, clone it
const cloneTextureAsset = (src)=>{
    const result = new Asset(`${src.name}_clone`, src.type, src.file, src.data, src.options);
    result.loaded = true;
    result.resource = cloneTexture(src.resource);
    src.registry.add(result);
    return result;
};
const createVertexBufferInternal = (device, sourceDesc)=>{
    const positionDesc = sourceDesc[SEMANTIC_POSITION];
    if (!positionDesc) {
        // ignore meshes without positions
        return null;
    }
    const numVertices = positionDesc.count;
    // generate vertexDesc elements
    const vertexDesc = [];
    for(const semantic in sourceDesc){
        if (sourceDesc.hasOwnProperty(semantic)) {
            const element = {
                semantic: semantic,
                components: sourceDesc[semantic].components,
                type: sourceDesc[semantic].type,
                normalize: !!sourceDesc[semantic].normalize
            };
            if (!VertexFormat.isElementValid(device, element)) {
                // WebGP does not support some formats and we need to remap it to one larger, for example int16x3 -> int16x4
                // TODO: this might need the actual data changes if this element is the last one in the vertex, as it might
                // try to read outside of the vertex buffer.
                element.components++;
            }
            vertexDesc.push(element);
        }
    }
    // sort vertex elements by engine-ideal order
    vertexDesc.sort((lhs, rhs)=>{
        return attributeOrder[lhs.semantic] - attributeOrder[rhs.semantic];
    });
    let i, j, k;
    let source, target, sourceOffset;
    const vertexFormat = new VertexFormat(device, vertexDesc);
    // check whether source data is correctly interleaved
    let isCorrectlyInterleaved = true;
    for(i = 0; i < vertexFormat.elements.length; ++i){
        target = vertexFormat.elements[i];
        source = sourceDesc[target.name];
        sourceOffset = source.offset - positionDesc.offset;
        if (source.buffer !== positionDesc.buffer || source.stride !== target.stride || source.size !== target.size || sourceOffset !== target.offset) {
            isCorrectlyInterleaved = false;
            break;
        }
    }
    // create vertex buffer
    const vertexBuffer = new VertexBuffer(device, vertexFormat, numVertices);
    const vertexData = vertexBuffer.lock();
    const targetArray = new Uint32Array(vertexData);
    let sourceArray;
    if (isCorrectlyInterleaved) {
        // copy data
        sourceArray = new Uint32Array(positionDesc.buffer, positionDesc.offset, numVertices * vertexBuffer.format.size / 4);
        targetArray.set(sourceArray);
    } else {
        let targetStride, sourceStride;
        // copy data and interleave
        for(i = 0; i < vertexBuffer.format.elements.length; ++i){
            target = vertexBuffer.format.elements[i];
            targetStride = target.stride / 4;
            source = sourceDesc[target.name];
            sourceStride = source.stride / 4;
            // ensure we don't go beyond the end of the arraybuffer when dealing with
            // interlaced vertex formats
            sourceArray = new Uint32Array(source.buffer, source.offset, (source.count - 1) * sourceStride + (source.size + 3) / 4);
            let src = 0;
            let dst = target.offset / 4;
            const kend = Math.floor((source.size + 3) / 4);
            for(j = 0; j < numVertices; ++j){
                for(k = 0; k < kend; ++k){
                    targetArray[dst + k] = sourceArray[src + k];
                }
                src += sourceStride;
                dst += targetStride;
            }
        }
    }
    vertexBuffer.unlock();
    return vertexBuffer;
};
const createVertexBuffer = (device, attributes, indices, accessors, bufferViews, vertexBufferDict)=>{
    // extract list of attributes to use
    const useAttributes = {};
    const attribIds = [];
    for(const attrib in attributes){
        if (attributes.hasOwnProperty(attrib) && gltfToEngineSemanticMap.hasOwnProperty(attrib)) {
            useAttributes[attrib] = attributes[attrib];
            // build unique id for each attribute in format: Semantic:accessorIndex
            attribIds.push(`${attrib}:${attributes[attrib]}`);
        }
    }
    // sort unique ids and create unique vertex buffer ID
    attribIds.sort();
    const vbKey = attribIds.join();
    // return already created vertex buffer if identical
    let vb = vertexBufferDict[vbKey];
    if (!vb) {
        // build vertex buffer format desc and source
        const sourceDesc = {};
        for(const attrib in useAttributes){
            const accessor = accessors[attributes[attrib]];
            const accessorData = getAccessorData(accessor, bufferViews);
            const bufferView = bufferViews[accessor.bufferView];
            const semantic = gltfToEngineSemanticMap[attrib];
            const size = getNumComponents(accessor.type) * getComponentSizeInBytes(accessor.componentType);
            const stride = bufferView && bufferView.hasOwnProperty('byteStride') ? bufferView.byteStride : size;
            sourceDesc[semantic] = {
                buffer: accessorData.buffer,
                size: size,
                offset: accessorData.byteOffset,
                stride: stride,
                count: accessor.count,
                components: getNumComponents(accessor.type),
                type: getComponentType(accessor.componentType),
                normalize: accessor.normalized
            };
        }
        // generate normals if they're missing (this should probably be a user option)
        if (!sourceDesc.hasOwnProperty(SEMANTIC_NORMAL)) {
            generateNormals(sourceDesc, indices);
        }
        // create and store it in the dictionary
        vb = createVertexBufferInternal(device, sourceDesc);
        vertexBufferDict[vbKey] = vb;
    }
    return vb;
};
const createSkin = (device, gltfSkin, accessors, bufferViews, nodes, glbSkins)=>{
    let i, j, bindMatrix;
    const joints = gltfSkin.joints;
    const numJoints = joints.length;
    const ibp = [];
    if (gltfSkin.hasOwnProperty('inverseBindMatrices')) {
        const inverseBindMatrices = gltfSkin.inverseBindMatrices;
        const ibmData = getAccessorData(accessors[inverseBindMatrices], bufferViews, true);
        const ibmValues = [];
        for(i = 0; i < numJoints; i++){
            for(j = 0; j < 16; j++){
                ibmValues[j] = ibmData[i * 16 + j];
            }
            bindMatrix = new Mat4();
            bindMatrix.set(ibmValues);
            ibp.push(bindMatrix);
        }
    } else {
        for(i = 0; i < numJoints; i++){
            bindMatrix = new Mat4();
            ibp.push(bindMatrix);
        }
    }
    const boneNames = [];
    for(i = 0; i < numJoints; i++){
        boneNames[i] = nodes[joints[i]].name;
    }
    // create a cache key from bone names and see if we have matching skin
    const key = boneNames.join('#');
    let skin = glbSkins.get(key);
    if (!skin) {
        // create the skin and add it to the cache
        skin = new Skin(device, ibp, boneNames);
        glbSkins.set(key, skin);
    }
    return skin;
};
const createDracoMesh = (device, primitive, accessors, bufferViews, meshVariants, meshDefaultMaterials, promises)=>{
    // create the mesh
    const result = new Mesh(device);
    result.aabb = getAccessorBoundingBox(accessors[primitive.attributes.POSITION]);
    // create vertex description
    const vertexDesc = [];
    for (const [name, index] of Object.entries(primitive.attributes)){
        const accessor = accessors[index];
        const semantic = gltfToEngineSemanticMap[name];
        const componentType = getComponentType(accessor.componentType);
        vertexDesc.push({
            semantic: semantic,
            components: getNumComponents(accessor.type),
            type: componentType,
            normalize: accessor.normalized ?? (semantic === SEMANTIC_COLOR && (componentType === TYPE_UINT8 || componentType === TYPE_UINT16))
        });
    }
    promises.push(new Promise((resolve, reject)=>{
        // decode draco data
        const dracoExt = primitive.extensions.KHR_draco_mesh_compression;
        dracoDecode(bufferViews[dracoExt.bufferView].slice().buffer, (err, decompressedData)=>{
            if (err) {
                console.log(err);
                reject(err);
            } else {
                // worker reports order of attributes as array of attribute unique_id
                const order = {};
                for (const [name, index] of Object.entries(dracoExt.attributes)){
                    order[gltfToEngineSemanticMap[name]] = decompressedData.attributes.indexOf(index);
                }
                // order vertexDesc
                vertexDesc.sort((a, b)=>{
                    return order[a.semantic] - order[b.semantic];
                });
                // draco decompressor will generate normals if they are missing
                if (!primitive.attributes?.NORMAL) {
                    vertexDesc.splice(1, 0, {
                        semantic: 'NORMAL',
                        components: 3,
                        type: TYPE_FLOAT32
                    });
                }
                const vertexFormat = new VertexFormat(device, vertexDesc);
                // create vertex buffer
                const numVertices = decompressedData.vertices.byteLength / vertexFormat.size;
                const indexFormat = numVertices <= 65535 ? INDEXFORMAT_UINT16 : INDEXFORMAT_UINT32;
                const numIndices = decompressedData.indices.byteLength / (numVertices <= 65535 ? 2 : 4);
                Debug.call(()=>{
                    if (numVertices !== accessors[primitive.attributes.POSITION].count) {
                        Debug.warn('mesh has invalid vertex count');
                    }
                    if (numIndices !== accessors[primitive.indices].count) {
                        Debug.warn('mesh has invalid index count');
                    }
                });
                const vertexBuffer = new VertexBuffer(device, vertexFormat, numVertices, {
                    data: decompressedData.vertices
                });
                const indexBuffer = new IndexBuffer(device, indexFormat, numIndices, BUFFER_STATIC, decompressedData.indices);
                result.vertexBuffer = vertexBuffer;
                result.indexBuffer[0] = indexBuffer;
                result.primitive[0].type = getPrimitiveType(primitive);
                result.primitive[0].base = 0;
                result.primitive[0].count = indexBuffer ? numIndices : numVertices;
                result.primitive[0].indexed = !!indexBuffer;
                resolve();
            }
        });
    }));
    // handle material variants
    if (primitive?.extensions?.KHR_materials_variants) {
        const variants = primitive.extensions.KHR_materials_variants;
        const tempMapping = {};
        variants.mappings.forEach((mapping)=>{
            mapping.variants.forEach((variant)=>{
                tempMapping[variant] = mapping.material;
            });
        });
        meshVariants[result.id] = tempMapping;
    }
    meshDefaultMaterials[result.id] = primitive.material;
    return result;
};
const createMesh = (device, gltfMesh, accessors, bufferViews, vertexBufferDict, meshVariants, meshDefaultMaterials, assetOptions, promises)=>{
    const meshes = [];
    gltfMesh.primitives.forEach((primitive)=>{
        if (primitive.extensions?.KHR_draco_mesh_compression) {
            // handle draco compressed mesh
            meshes.push(createDracoMesh(device, primitive, accessors, bufferViews, meshVariants, meshDefaultMaterials, promises));
        } else {
            // handle uncompressed mesh
            let indices = primitive.hasOwnProperty('indices') ? getAccessorData(accessors[primitive.indices], bufferViews, true) : null;
            const vertexBuffer = createVertexBuffer(device, primitive.attributes, indices, accessors, bufferViews, vertexBufferDict);
            const primitiveType = getPrimitiveType(primitive);
            // build the mesh
            const mesh = new Mesh(device);
            mesh.vertexBuffer = vertexBuffer;
            mesh.primitive[0].type = primitiveType;
            mesh.primitive[0].base = 0;
            mesh.primitive[0].indexed = indices !== null;
            // index buffer
            if (indices !== null) {
                let indexFormat;
                if (indices instanceof Uint8Array) {
                    indexFormat = INDEXFORMAT_UINT8;
                } else if (indices instanceof Uint16Array) {
                    indexFormat = INDEXFORMAT_UINT16;
                } else {
                    indexFormat = INDEXFORMAT_UINT32;
                }
                if (indexFormat === INDEXFORMAT_UINT8 && device.isWebGPU) {
                    // silently convert to 16bit
                    indexFormat = INDEXFORMAT_UINT16;
                    indices = new Uint16Array(indices);
                }
                const indexBuffer = new IndexBuffer(device, indexFormat, indices.length, BUFFER_STATIC, indices);
                mesh.indexBuffer[0] = indexBuffer;
                mesh.primitive[0].count = indices.length;
            } else {
                mesh.primitive[0].count = vertexBuffer.numVertices;
            }
            if (primitive.hasOwnProperty('extensions') && primitive.extensions.hasOwnProperty('KHR_materials_variants')) {
                const variants = primitive.extensions.KHR_materials_variants;
                const tempMapping = {};
                variants.mappings.forEach((mapping)=>{
                    mapping.variants.forEach((variant)=>{
                        tempMapping[variant] = mapping.material;
                    });
                });
                meshVariants[mesh.id] = tempMapping;
            }
            meshDefaultMaterials[mesh.id] = primitive.material;
            let accessor = accessors[primitive.attributes.POSITION];
            mesh.aabb = getAccessorBoundingBox(accessor);
            // morph targets
            if (primitive.hasOwnProperty('targets')) {
                const targets = [];
                primitive.targets.forEach((target, index)=>{
                    const options = {};
                    if (target.hasOwnProperty('POSITION')) {
                        accessor = accessors[target.POSITION];
                        options.deltaPositions = getAccessorDataFloat32(accessor, bufferViews);
                        options.aabb = getAccessorBoundingBox(accessor);
                    }
                    if (target.hasOwnProperty('NORMAL')) {
                        accessor = accessors[target.NORMAL];
                        // NOTE: the morph targets can't currently accept quantized normals
                        options.deltaNormals = getAccessorDataFloat32(accessor, bufferViews);
                    }
                    // name if specified
                    if (gltfMesh.hasOwnProperty('extras') && gltfMesh.extras.hasOwnProperty('targetNames')) {
                        options.name = gltfMesh.extras.targetNames[index];
                    } else {
                        options.name = index.toString(10);
                    }
                    // default weight if specified
                    if (gltfMesh.hasOwnProperty('weights')) {
                        options.defaultWeight = gltfMesh.weights[index];
                    }
                    options.preserveData = assetOptions.morphPreserveData;
                    targets.push(new MorphTarget(options));
                });
                mesh.morph = new Morph(targets, device, {
                    preferHighPrecision: assetOptions.morphPreferHighPrecision
                });
            }
            meshes.push(mesh);
        }
    });
    return meshes;
};
const extractTextureTransform = (source, material, maps)=>{
    let map;
    const texCoord = source.texCoord;
    if (texCoord) {
        for(map = 0; map < maps.length; ++map){
            material[`${maps[map]}MapUv`] = texCoord;
        }
    }
    const zeros = [
        0,
        0
    ];
    const ones = [
        1,
        1
    ];
    const textureTransform = source.extensions?.KHR_texture_transform;
    if (textureTransform) {
        const offset = textureTransform.offset || zeros;
        const scale = textureTransform.scale || ones;
        const rotation = textureTransform.rotation ? -textureTransform.rotation * math.RAD_TO_DEG : 0;
        const tilingVec = new Vec2(scale[0], scale[1]);
        const offsetVec = new Vec2(offset[0], 1.0 - scale[1] - offset[1]);
        for(map = 0; map < maps.length; ++map){
            material[`${maps[map]}MapTiling`] = tilingVec;
            material[`${maps[map]}MapOffset`] = offsetVec;
            material[`${maps[map]}MapRotation`] = rotation;
        }
    }
};
const extensionPbrSpecGlossiness = (data, material, textures)=>{
    let color, texture;
    if (data.hasOwnProperty('diffuseFactor')) {
        color = data.diffuseFactor;
        // Convert from linear space to sRGB space
        material.diffuse.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));
        material.opacity = color[3];
    } else {
        material.diffuse.set(1, 1, 1);
        material.opacity = 1;
    }
    if (data.hasOwnProperty('diffuseTexture')) {
        const diffuseTexture = data.diffuseTexture;
        texture = textures[diffuseTexture.index];
        material.diffuseMap = texture;
        material.diffuseMapChannel = 'rgb';
        material.opacityMap = texture;
        material.opacityMapChannel = 'a';
        extractTextureTransform(diffuseTexture, material, [
            'diffuse',
            'opacity'
        ]);
    }
    material.useMetalness = false;
    if (data.hasOwnProperty('specularFactor')) {
        color = data.specularFactor;
        // Convert from linear space to sRGB space
        material.specular.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));
    } else {
        material.specular.set(1, 1, 1);
    }
    if (data.hasOwnProperty('glossinessFactor')) {
        material.gloss = data.glossinessFactor;
    } else {
        material.gloss = 1.0;
    }
    if (data.hasOwnProperty('specularGlossinessTexture')) {
        const specularGlossinessTexture = data.specularGlossinessTexture;
        material.specularMap = material.glossMap = textures[specularGlossinessTexture.index];
        material.specularMapChannel = 'rgb';
        material.glossMapChannel = 'a';
        extractTextureTransform(specularGlossinessTexture, material, [
            'gloss',
            'metalness'
        ]);
    }
};
const extensionClearCoat = (data, material, textures)=>{
    if (data.hasOwnProperty('clearcoatFactor')) {
        material.clearCoat = data.clearcoatFactor * 0.25; // TODO: remove temporary workaround for replicating glTF clear-coat visuals
    } else {
        material.clearCoat = 0;
    }
    if (data.hasOwnProperty('clearcoatTexture')) {
        const clearcoatTexture = data.clearcoatTexture;
        material.clearCoatMap = textures[clearcoatTexture.index];
        material.clearCoatMapChannel = 'r';
        extractTextureTransform(clearcoatTexture, material, [
            'clearCoat'
        ]);
    }
    if (data.hasOwnProperty('clearcoatRoughnessFactor')) {
        material.clearCoatGloss = data.clearcoatRoughnessFactor;
    } else {
        material.clearCoatGloss = 0;
    }
    if (data.hasOwnProperty('clearcoatRoughnessTexture')) {
        const clearcoatRoughnessTexture = data.clearcoatRoughnessTexture;
        material.clearCoatGlossMap = textures[clearcoatRoughnessTexture.index];
        material.clearCoatGlossMapChannel = 'g';
        extractTextureTransform(clearcoatRoughnessTexture, material, [
            'clearCoatGloss'
        ]);
    }
    if (data.hasOwnProperty('clearcoatNormalTexture')) {
        const clearcoatNormalTexture = data.clearcoatNormalTexture;
        material.clearCoatNormalMap = textures[clearcoatNormalTexture.index];
        extractTextureTransform(clearcoatNormalTexture, material, [
            'clearCoatNormal'
        ]);
        if (clearcoatNormalTexture.hasOwnProperty('scale')) {
            material.clearCoatBumpiness = clearcoatNormalTexture.scale;
        } else {
            material.clearCoatBumpiness = 1;
        }
    }
    material.clearCoatGlossInvert = true;
};
const extensionUnlit = (data, material, textures)=>{
    material.useLighting = false;
    // copy diffuse into emissive
    material.emissive.copy(material.diffuse);
    material.emissiveMap = material.diffuseMap;
    material.emissiveMapUv = material.diffuseMapUv;
    material.emissiveMapTiling.copy(material.diffuseMapTiling);
    material.emissiveMapOffset.copy(material.diffuseMapOffset);
    material.emissiveMapRotation = material.diffuseMapRotation;
    material.emissiveMapChannel = material.diffuseMapChannel;
    material.emissiveVertexColor = material.diffuseVertexColor;
    material.emissiveVertexColorChannel = material.diffuseVertexColorChannel;
    // disable lighting and skybox
    material.useLighting = false;
    material.useSkybox = false;
    // blank diffuse
    material.diffuse.set(1, 1, 1);
    material.diffuseMap = null;
    material.diffuseVertexColor = false;
};
const extensionSpecular = (data, material, textures)=>{
    material.useMetalnessSpecularColor = true;
    if (data.hasOwnProperty('specularColorTexture')) {
        material.specularMap = textures[data.specularColorTexture.index];
        material.specularMapChannel = 'rgb';
        extractTextureTransform(data.specularColorTexture, material, [
            'specular'
        ]);
    }
    if (data.hasOwnProperty('specularColorFactor')) {
        const color = data.specularColorFactor;
        material.specular.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));
    } else {
        material.specular.set(1, 1, 1);
    }
    if (data.hasOwnProperty('specularFactor')) {
        material.specularityFactor = data.specularFactor;
    } else {
        material.specularityFactor = 1;
    }
    if (data.hasOwnProperty('specularTexture')) {
        material.specularityFactorMapChannel = 'a';
        material.specularityFactorMap = textures[data.specularTexture.index];
        extractTextureTransform(data.specularTexture, material, [
            'specularityFactor'
        ]);
    }
};
const extensionIor = (data, material, textures)=>{
    if (data.hasOwnProperty('ior')) {
        material.refractionIndex = 1.0 / data.ior;
    }
};
const extensionDispersion = (data, material, textures)=>{
    if (data.hasOwnProperty('dispersion')) {
        material.dispersion = data.dispersion;
    }
};
const extensionTransmission = (data, material, textures)=>{
    material.blendType = BLEND_NORMAL;
    material.useDynamicRefraction = true;
    if (data.hasOwnProperty('transmissionFactor')) {
        material.refraction = data.transmissionFactor;
    }
    if (data.hasOwnProperty('transmissionTexture')) {
        material.refractionMapChannel = 'r';
        material.refractionMap = textures[data.transmissionTexture.index];
        extractTextureTransform(data.transmissionTexture, material, [
            'refraction'
        ]);
    }
};
const extensionSheen = (data, material, textures)=>{
    material.useSheen = true;
    if (data.hasOwnProperty('sheenColorFactor')) {
        const color = data.sheenColorFactor;
        material.sheen.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));
    } else {
        material.sheen.set(1, 1, 1);
    }
    if (data.hasOwnProperty('sheenColorTexture')) {
        material.sheenMap = textures[data.sheenColorTexture.index];
        extractTextureTransform(data.sheenColorTexture, material, [
            'sheen'
        ]);
    }
    material.sheenGloss = data.hasOwnProperty('sheenRoughnessFactor') ? data.sheenRoughnessFactor : 0.0;
    if (data.hasOwnProperty('sheenRoughnessTexture')) {
        material.sheenGlossMap = textures[data.sheenRoughnessTexture.index];
        material.sheenGlossMapChannel = 'a';
        extractTextureTransform(data.sheenRoughnessTexture, material, [
            'sheenGloss'
        ]);
    }
    material.sheenGlossInvert = true;
};
const extensionVolume = (data, material, textures)=>{
    material.blendType = BLEND_NORMAL;
    material.useDynamicRefraction = true;
    if (data.hasOwnProperty('thicknessFactor')) {
        material.thickness = data.thicknessFactor;
    }
    if (data.hasOwnProperty('thicknessTexture')) {
        material.thicknessMap = textures[data.thicknessTexture.index];
        material.thicknessMapChannel = 'g';
        extractTextureTransform(data.thicknessTexture, material, [
            'thickness'
        ]);
    }
    if (data.hasOwnProperty('attenuationDistance')) {
        material.attenuationDistance = data.attenuationDistance;
    }
    if (data.hasOwnProperty('attenuationColor')) {
        const color = data.attenuationColor;
        material.attenuation.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));
    }
};
const extensionEmissiveStrength = (data, material, textures)=>{
    if (data.hasOwnProperty('emissiveStrength')) {
        material.emissiveIntensity = data.emissiveStrength;
    }
};
const extensionIridescence = (data, material, textures)=>{
    material.useIridescence = true;
    if (data.hasOwnProperty('iridescenceFactor')) {
        material.iridescence = data.iridescenceFactor;
    }
    if (data.hasOwnProperty('iridescenceTexture')) {
        material.iridescenceMapChannel = 'r';
        material.iridescenceMap = textures[data.iridescenceTexture.index];
        extractTextureTransform(data.iridescenceTexture, material, [
            'iridescence'
        ]);
    }
    if (data.hasOwnProperty('iridescenceIor')) {
        material.iridescenceRefractionIndex = data.iridescenceIor;
    }
    if (data.hasOwnProperty('iridescenceThicknessMinimum')) {
        material.iridescenceThicknessMin = data.iridescenceThicknessMinimum;
    }
    if (data.hasOwnProperty('iridescenceThicknessMaximum')) {
        material.iridescenceThicknessMax = data.iridescenceThicknessMaximum;
    }
    if (data.hasOwnProperty('iridescenceThicknessTexture')) {
        material.iridescenceThicknessMapChannel = 'g';
        material.iridescenceThicknessMap = textures[data.iridescenceThicknessTexture.index];
        extractTextureTransform(data.iridescenceThicknessTexture, material, [
            'iridescenceThickness'
        ]);
    }
};
const extensionAnisotropy = (data, material, textures)=>{
    material.enableGGXSpecular = true;
    if (data.hasOwnProperty('anisotropyStrength')) {
        material.anisotropyIntensity = data.anisotropyStrength;
    } else {
        material.anisotropyIntensity = 0;
    }
    if (data.hasOwnProperty('anisotropyTexture')) {
        const anisotropyTexture = data.anisotropyTexture;
        material.anisotropyMap = textures[anisotropyTexture.index];
        extractTextureTransform(anisotropyTexture, material, [
            'anisotropy'
        ]);
    }
    if (data.hasOwnProperty('anisotropyRotation')) {
        material.anisotropyRotation = data.anisotropyRotation * math.RAD_TO_DEG;
    } else {
        material.anisotropyRotation = 0;
    }
};
const createMaterial = (gltfMaterial, textures)=>{
    const material = new StandardMaterial();
    if (gltfMaterial.hasOwnProperty('name')) {
        material.name = gltfMaterial.name;
    }
    // glTF doesn't define how to occlude specular
    material.occludeSpecular = SPECOCC_AO;
    material.diffuseVertexColor = true;
    material.specularTint = true;
    material.specularVertexColor = true;
    // Set glTF spec defaults
    material.specular.set(1, 1, 1);
    material.gloss = 1;
    material.glossInvert = true;
    material.useMetalness = true;
    let color, texture;
    if (gltfMaterial.hasOwnProperty('pbrMetallicRoughness')) {
        const pbrData = gltfMaterial.pbrMetallicRoughness;
        if (pbrData.hasOwnProperty('baseColorFactor')) {
            color = pbrData.baseColorFactor;
            // Convert from linear space to sRGB space
            material.diffuse.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));
            material.opacity = color[3];
        }
        if (pbrData.hasOwnProperty('baseColorTexture')) {
            const baseColorTexture = pbrData.baseColorTexture;
            texture = textures[baseColorTexture.index];
            material.diffuseMap = texture;
            material.diffuseMapChannel = 'rgb';
            material.opacityMap = texture;
            material.opacityMapChannel = 'a';
            extractTextureTransform(baseColorTexture, material, [
                'diffuse',
                'opacity'
            ]);
        }
        if (pbrData.hasOwnProperty('metallicFactor')) {
            material.metalness = pbrData.metallicFactor;
        }
        if (pbrData.hasOwnProperty('roughnessFactor')) {
            material.gloss = pbrData.roughnessFactor;
        }
        if (pbrData.hasOwnProperty('metallicRoughnessTexture')) {
            const metallicRoughnessTexture = pbrData.metallicRoughnessTexture;
            material.metalnessMap = material.glossMap = textures[metallicRoughnessTexture.index];
            material.metalnessMapChannel = 'b';
            material.glossMapChannel = 'g';
            extractTextureTransform(metallicRoughnessTexture, material, [
                'gloss',
                'metalness'
            ]);
        }
    }
    if (gltfMaterial.hasOwnProperty('normalTexture')) {
        const normalTexture = gltfMaterial.normalTexture;
        material.normalMap = textures[normalTexture.index];
        extractTextureTransform(normalTexture, material, [
            'normal'
        ]);
        if (normalTexture.hasOwnProperty('scale')) {
            material.bumpiness = normalTexture.scale;
        }
    }
    if (gltfMaterial.hasOwnProperty('occlusionTexture')) {
        const occlusionTexture = gltfMaterial.occlusionTexture;
        material.aoMap = textures[occlusionTexture.index];
        material.aoMapChannel = 'r';
        extractTextureTransform(occlusionTexture, material, [
            'ao'
        ]);
    // TODO: support 'strength'
    }
    if (gltfMaterial.hasOwnProperty('emissiveFactor')) {
        color = gltfMaterial.emissiveFactor;
        // Convert from linear space to sRGB space
        material.emissive.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));
    }
    if (gltfMaterial.hasOwnProperty('emissiveTexture')) {
        const emissiveTexture = gltfMaterial.emissiveTexture;
        material.emissiveMap = textures[emissiveTexture.index];
        extractTextureTransform(emissiveTexture, material, [
            'emissive'
        ]);
    }
    if (gltfMaterial.hasOwnProperty('alphaMode')) {
        switch(gltfMaterial.alphaMode){
            case 'MASK':
                material.blendType = BLEND_NONE;
                if (gltfMaterial.hasOwnProperty('alphaCutoff')) {
                    material.alphaTest = gltfMaterial.alphaCutoff;
                } else {
                    material.alphaTest = 0.5;
                }
                break;
            case 'BLEND':
                material.blendType = BLEND_NORMAL;
                // note: by default don't write depth on semitransparent materials
                material.depthWrite = false;
                break;
            default:
            case 'OPAQUE':
                material.blendType = BLEND_NONE;
                break;
        }
    } else {
        material.blendType = BLEND_NONE;
    }
    if (gltfMaterial.hasOwnProperty('doubleSided')) {
        material.twoSidedLighting = gltfMaterial.doubleSided;
        material.cull = gltfMaterial.doubleSided ? CULLFACE_NONE : CULLFACE_BACK;
    } else {
        material.twoSidedLighting = false;
        material.cull = CULLFACE_BACK;
    }
    // Provide list of supported extensions and their functions
    const extensions = {
        'KHR_materials_clearcoat': extensionClearCoat,
        'KHR_materials_emissive_strength': extensionEmissiveStrength,
        'KHR_materials_ior': extensionIor,
        'KHR_materials_dispersion': extensionDispersion,
        'KHR_materials_iridescence': extensionIridescence,
        'KHR_materials_pbrSpecularGlossiness': extensionPbrSpecGlossiness,
        'KHR_materials_sheen': extensionSheen,
        'KHR_materials_specular': extensionSpecular,
        'KHR_materials_transmission': extensionTransmission,
        'KHR_materials_unlit': extensionUnlit,
        'KHR_materials_volume': extensionVolume,
        'KHR_materials_anisotropy': extensionAnisotropy
    };
    // Handle extensions
    if (gltfMaterial.hasOwnProperty('extensions')) {
        for(const key in gltfMaterial.extensions){
            const extensionFunc = extensions[key];
            if (extensionFunc !== undefined) {
                extensionFunc(gltfMaterial.extensions[key], material, textures);
            }
        }
    }
    material.update();
    return material;
};
// create the anim structure
const createAnimation = (gltfAnimation, animationIndex, gltfAccessors, bufferViews, nodes, meshes, gltfNodes)=>{
    // create animation data block for the accessor
    const createAnimData = (gltfAccessor)=>{
        return new AnimData(getNumComponents(gltfAccessor.type), getAccessorDataFloat32(gltfAccessor, bufferViews));
    };
    const interpMap = {
        'STEP': INTERPOLATION_STEP,
        'LINEAR': INTERPOLATION_LINEAR,
        'CUBICSPLINE': INTERPOLATION_CUBIC
    };
    // Input and output maps reference data by sampler input/output key.
    const inputMap = {};
    const outputMap = {};
    // The curve map stores temporary curve data by sampler index. Each curves input/output value will be resolved to an inputs/outputs array index after all samplers have been processed.
    // Curves and outputs that are deleted from their maps will not be included in the final AnimTrack
    const curveMap = {};
    let outputCounter = 1;
    let i;
    // convert samplers
    for(i = 0; i < gltfAnimation.samplers.length; ++i){
        const sampler = gltfAnimation.samplers[i];
        // get input data
        if (!inputMap.hasOwnProperty(sampler.input)) {
            inputMap[sampler.input] = createAnimData(gltfAccessors[sampler.input]);
        }
        // get output data
        if (!outputMap.hasOwnProperty(sampler.output)) {
            outputMap[sampler.output] = createAnimData(gltfAccessors[sampler.output]);
        }
        const interpolation = sampler.hasOwnProperty('interpolation') && interpMap.hasOwnProperty(sampler.interpolation) ? interpMap[sampler.interpolation] : INTERPOLATION_LINEAR;
        // create curve
        const curve = {
            paths: [],
            input: sampler.input,
            output: sampler.output,
            interpolation: interpolation
        };
        curveMap[i] = curve;
    }
    const quatArrays = [];
    const transformSchema = {
        'translation': 'localPosition',
        'rotation': 'localRotation',
        'scale': 'localScale'
    };
    const constructNodePath = (node)=>{
        const path = [];
        while(node){
            path.unshift(node.name);
            node = node.parent;
        }
        return path;
    };
    // All morph targets are included in a single channel of the animation, with all targets output data interleaved with each other.
    // This function splits each morph target out into it a curve with its own output data, allowing us to animate each morph target independently by name.
    const createMorphTargetCurves = (curve, gltfNode, entityPath)=>{
        const out = outputMap[curve.output];
        if (!out) {
            Debug.warn(`glb-parser: No output data is available for the morph target curve (${entityPath}/graph/weights). Skipping.`);
            return;
        }
        // names of morph targets
        let targetNames;
        if (meshes && meshes[gltfNode.mesh]) {
            const mesh = meshes[gltfNode.mesh];
            if (mesh.hasOwnProperty('extras') && mesh.extras.hasOwnProperty('targetNames')) {
                targetNames = mesh.extras.targetNames;
            }
        }
        const outData = out.data;
        const morphTargetCount = outData.length / inputMap[curve.input].data.length;
        const keyframeCount = outData.length / morphTargetCount;
        // single array buffer for all keys, 4 bytes per entry
        const singleBufferSize = keyframeCount * 4;
        const buffer = new ArrayBuffer(singleBufferSize * morphTargetCount);
        for(let j = 0; j < morphTargetCount; j++){
            const morphTargetOutput = new Float32Array(buffer, singleBufferSize * j, keyframeCount);
            // the output data for all morph targets in a single curve is interleaved. We need to retrieve the keyframe output data for a single morph target
            for(let k = 0; k < keyframeCount; k++){
                morphTargetOutput[k] = outData[k * morphTargetCount + j];
            }
            const output = new AnimData(1, morphTargetOutput);
            const weightName = targetNames?.[j] ? `name.${targetNames[j]}` : j;
            // add the individual morph target output data to the outputMap using a negative value key (so as not to clash with sampler.output values)
            outputMap[-outputCounter] = output;
            const morphCurve = {
                paths: [
                    {
                        entityPath: entityPath,
                        component: 'graph',
                        propertyPath: [
                            `weight.${weightName}`
                        ]
                    }
                ],
                // each morph target curve input can use the same sampler.input from the channel they were all in
                input: curve.input,
                // but each morph target curve should reference its individual output that was just created
                output: -outputCounter,
                interpolation: curve.interpolation
            };
            outputCounter++;
            // add the morph target curve to the curveMap
            curveMap[`morphCurve-${i}-${j}`] = morphCurve;
        }
    };
    // convert anim channels
    for(i = 0; i < gltfAnimation.channels.length; ++i){
        const channel = gltfAnimation.channels[i];
        const target = channel.target;
        const curve = curveMap[channel.sampler];
        const node = nodes[target.node];
        const gltfNode = gltfNodes[target.node];
        const entityPath = constructNodePath(node);
        if (target.path.startsWith('weights')) {
            createMorphTargetCurves(curve, gltfNode, entityPath);
            // as all individual morph targets in this morph curve have their own curve now, this morph curve should be flagged
            // so it's not included in the final output
            curveMap[channel.sampler].morphCurve = true;
        } else {
            curve.paths.push({
                entityPath: entityPath,
                component: 'graph',
                propertyPath: [
                    transformSchema[target.path]
                ]
            });
        }
    }
    const inputs = [];
    const outputs = [];
    const curves = [];
    // Add each input in the map to the final inputs array. The inputMap should now reference the index of input in the inputs array instead of the input itself.
    for(const inputKey in inputMap){
        inputs.push(inputMap[inputKey]);
        inputMap[inputKey] = inputs.length - 1;
    }
    // Add each output in the map to the final outputs array. The outputMap should now reference the index of output in the outputs array instead of the output itself.
    for(const outputKey in outputMap){
        outputs.push(outputMap[outputKey]);
        outputMap[outputKey] = outputs.length - 1;
    }
    // Create an AnimCurve for each curve object in the curveMap. Each curve object's input value should be resolved to the index of the input in the
    // inputs arrays using the inputMap. Likewise for output values.
    for(const curveKey in curveMap){
        const curveData = curveMap[curveKey];
        // if the curveData contains a morph curve then do not add it to the final curve list as the individual morph target curves are included instead
        if (curveData.morphCurve) {
            continue;
        }
        curves.push(new AnimCurve(curveData.paths, inputMap[curveData.input], outputMap[curveData.output], curveData.interpolation));
        // if this target is a set of quaternion keys, make note of its index so we can perform
        // quaternion-specific processing on it.
        if (curveData.paths.length > 0 && curveData.paths[0].propertyPath[0] === 'localRotation' && curveData.interpolation !== INTERPOLATION_CUBIC) {
            quatArrays.push(curves[curves.length - 1].output);
        }
    }
    // sort the list of array indexes so we can skip dups
    quatArrays.sort();
    // run through the quaternion data arrays flipping quaternion keys
    // that don't fall in the same winding order.
    let prevIndex = null;
    let data;
    for(i = 0; i < quatArrays.length; ++i){
        const index = quatArrays[i];
        // skip over duplicate array indices
        if (i === 0 || index !== prevIndex) {
            data = outputs[index];
            if (data.components === 4) {
                const d = data.data;
                const len = d.length - 4;
                for(let j = 0; j < len; j += 4){
                    const dp = d[j + 0] * d[j + 4] + d[j + 1] * d[j + 5] + d[j + 2] * d[j + 6] + d[j + 3] * d[j + 7];
                    if (dp < 0) {
                        d[j + 4] *= -1;
                        d[j + 5] *= -1;
                        d[j + 6] *= -1;
                        d[j + 7] *= -1;
                    }
                }
            }
            prevIndex = index;
        }
    }
    // calculate duration of the animation as maximum time value
    let duration = 0;
    for(i = 0; i < inputs.length; i++){
        data = inputs[i]._data;
        duration = Math.max(duration, data.length === 0 ? 0 : data[data.length - 1]);
    }
    return new AnimTrack(gltfAnimation.hasOwnProperty('name') ? gltfAnimation.name : `animation_${animationIndex}`, duration, inputs, outputs, curves);
};
const tempMat = new Mat4();
const tempVec = new Vec3();
const createNode = (gltfNode, nodeIndex, nodeInstancingMap)=>{
    const entity = new GraphNode();
    if (gltfNode.hasOwnProperty('name') && gltfNode.name.length > 0) {
        entity.name = gltfNode.name;
    } else {
        entity.name = `node_${nodeIndex}`;
    }
    // Parse transformation properties
    if (gltfNode.hasOwnProperty('matrix')) {
        tempMat.data.set(gltfNode.matrix);
        tempMat.getTranslation(tempVec);
        entity.setLocalPosition(tempVec);
        tempMat.getEulerAngles(tempVec);
        entity.setLocalEulerAngles(tempVec);
        tempMat.getScale(tempVec);
        entity.setLocalScale(tempVec);
    }
    if (gltfNode.hasOwnProperty('rotation')) {
        const r = gltfNode.rotation;
        entity.setLocalRotation(r[0], r[1], r[2], r[3]);
    }
    if (gltfNode.hasOwnProperty('translation')) {
        const t = gltfNode.translation;
        entity.setLocalPosition(t[0], t[1], t[2]);
    }
    if (gltfNode.hasOwnProperty('scale')) {
        const s = gltfNode.scale;
        entity.setLocalScale(s[0], s[1], s[2]);
    }
    if (gltfNode.hasOwnProperty('extensions') && gltfNode.extensions.EXT_mesh_gpu_instancing) {
        nodeInstancingMap.set(gltfNode, {
            ext: gltfNode.extensions.EXT_mesh_gpu_instancing
        });
    }
    return entity;
};
// creates a camera component on the supplied node, and returns it
const createCamera = (gltfCamera, node)=>{
    const projection = gltfCamera.type === 'orthographic' ? PROJECTION_ORTHOGRAPHIC : PROJECTION_PERSPECTIVE;
    const gltfProperties = projection === PROJECTION_ORTHOGRAPHIC ? gltfCamera.orthographic : gltfCamera.perspective;
    const componentData = {
        enabled: false,
        projection: projection,
        nearClip: gltfProperties.znear,
        aspectRatioMode: ASPECT_AUTO
    };
    if (gltfProperties.zfar) {
        componentData.farClip = gltfProperties.zfar;
    }
    if (projection === PROJECTION_ORTHOGRAPHIC) {
        componentData.orthoHeight = 0.5 * gltfProperties.ymag;
        if (gltfProperties.ymag) {
            componentData.aspectRatioMode = ASPECT_MANUAL;
            componentData.aspectRatio = gltfProperties.xmag / gltfProperties.ymag;
        }
    } else {
        componentData.fov = gltfProperties.yfov * math.RAD_TO_DEG;
        if (gltfProperties.aspectRatio) {
            componentData.aspectRatioMode = ASPECT_MANUAL;
            componentData.aspectRatio = gltfProperties.aspectRatio;
        }
    }
    const cameraEntity = new Entity(gltfCamera.name);
    cameraEntity.addComponent('camera', componentData);
    return cameraEntity;
};
// creates light component, adds it to the node and returns the created light component
const createLight = (gltfLight, node)=>{
    const lightProps = {
        enabled: false,
        type: gltfLight.type === 'point' ? 'omni' : gltfLight.type,
        color: gltfLight.hasOwnProperty('color') ? new Color(gltfLight.color) : Color.WHITE,
        // when range is not defined, infinity should be used - but that is causing infinity in bounds calculations
        range: gltfLight.hasOwnProperty('range') ? gltfLight.range : 9999,
        falloffMode: LIGHTFALLOFF_INVERSESQUARED,
        // TODO: (engine issue #3252) Set intensity to match glTF specification, which uses physically based values:
        // - Omni and spot lights use luminous intensity in candela (lm/sr)
        // - Directional lights use illuminance in lux (lm/m2).
        // Current implementation: clapms specified intensity to 0..2 range
        intensity: gltfLight.hasOwnProperty('intensity') ? math.clamp(gltfLight.intensity, 0, 2) : 1
    };
    if (gltfLight.hasOwnProperty('spot')) {
        lightProps.innerConeAngle = gltfLight.spot.hasOwnProperty('innerConeAngle') ? gltfLight.spot.innerConeAngle * math.RAD_TO_DEG : 0;
        lightProps.outerConeAngle = gltfLight.spot.hasOwnProperty('outerConeAngle') ? gltfLight.spot.outerConeAngle * math.RAD_TO_DEG : Math.PI / 4;
    }
    // glTF stores light already in energy/area, but we need to provide the light with only the energy parameter,
    // so we need the intensities in candela back to lumen
    if (gltfLight.hasOwnProperty('intensity')) {
        lightProps.luminance = gltfLight.intensity * Light.getLightUnitConversion(lightTypes[lightProps.type], lightProps.outerConeAngle, lightProps.innerConeAngle);
    }
    // Rotate to match light orientation in glTF specification
    // Note that this adds a new entity node into the hierarchy that does not exist in the gltf hierarchy
    const lightEntity = new Entity(node.name);
    lightEntity.rotateLocal(90, 0, 0);
    // add component
    lightEntity.addComponent('light', lightProps);
    return lightEntity;
};
const createSkins = (device, gltf, nodes, bufferViews)=>{
    if (!gltf.hasOwnProperty('skins') || gltf.skins.length === 0) {
        return [];
    }
    // cache for skins to filter out duplicates
    const glbSkins = new Map();
    return gltf.skins.map((gltfSkin)=>{
        return createSkin(device, gltfSkin, gltf.accessors, bufferViews, nodes, glbSkins);
    });
};
const createMeshes = (device, gltf, bufferViews, options)=>{
    // dictionary of vertex buffers to avoid duplicates
    const vertexBufferDict = {};
    const meshVariants = {};
    const meshDefaultMaterials = {};
    const promises = [];
    const valid = !options.skipMeshes && gltf?.meshes?.length && gltf?.accessors?.length && gltf?.bufferViews?.length;
    const meshes = valid ? gltf.meshes.map((gltfMesh)=>{
        return createMesh(device, gltfMesh, gltf.accessors, bufferViews, vertexBufferDict, meshVariants, meshDefaultMaterials, options, promises);
    }) : [];
    return {
        meshes,
        meshVariants,
        meshDefaultMaterials,
        promises
    };
};
const createMaterials = (gltf, textures, options)=>{
    if (!gltf.hasOwnProperty('materials') || gltf.materials.length === 0) {
        return [];
    }
    const preprocess = options?.material?.preprocess;
    const process = options?.material?.process ?? createMaterial;
    const postprocess = options?.material?.postprocess;
    return gltf.materials.map((gltfMaterial)=>{
        if (preprocess) {
            preprocess(gltfMaterial);
        }
        const material = process(gltfMaterial, textures);
        if (postprocess) {
            postprocess(gltfMaterial, material);
        }
        return material;
    });
};
const createVariants = (gltf)=>{
    if (!gltf.hasOwnProperty('extensions') || !gltf.extensions.hasOwnProperty('KHR_materials_variants')) {
        return null;
    }
    const data = gltf.extensions.KHR_materials_variants.variants;
    const variants = {};
    for(let i = 0; i < data.length; i++){
        variants[data[i].name] = i;
    }
    return variants;
};
const createAnimations = (gltf, nodes, bufferViews, options)=>{
    if (!gltf.hasOwnProperty('animations') || gltf.animations.length === 0) {
        return [];
    }
    const preprocess = options?.animation?.preprocess;
    const postprocess = options?.animation?.postprocess;
    return gltf.animations.map((gltfAnimation, index)=>{
        if (preprocess) {
            preprocess(gltfAnimation);
        }
        const animation = createAnimation(gltfAnimation, index, gltf.accessors, bufferViews, nodes, gltf.meshes, gltf.nodes);
        if (postprocess) {
            postprocess(gltfAnimation, animation);
        }
        return animation;
    });
};
const createInstancing = (device, gltf, nodeInstancingMap, bufferViews)=>{
    const accessors = gltf.accessors;
    nodeInstancingMap.forEach((data, entity)=>{
        const attributes = data.ext.attributes;
        let translations;
        if (attributes.hasOwnProperty('TRANSLATION')) {
            const accessor = accessors[attributes.TRANSLATION];
            translations = getAccessorDataFloat32(accessor, bufferViews);
        }
        let rotations;
        if (attributes.hasOwnProperty('ROTATION')) {
            const accessor = accessors[attributes.ROTATION];
            rotations = getAccessorDataFloat32(accessor, bufferViews);
        }
        let scales;
        if (attributes.hasOwnProperty('SCALE')) {
            const accessor = accessors[attributes.SCALE];
            scales = getAccessorDataFloat32(accessor, bufferViews);
        }
        const instanceCount = (translations ? translations.length / 3 : 0) || (rotations ? rotations.length / 4 : 0) || (scales ? scales.length / 3 : 0);
        if (instanceCount) {
            const matrices = new Float32Array(instanceCount * 16);
            const pos = new Vec3();
            const rot = new Quat();
            const scl = new Vec3(1, 1, 1);
            const matrix = new Mat4();
            let matrixIndex = 0;
            for(let i = 0; i < instanceCount; i++){
                const i3 = i * 3;
                if (translations) {
                    pos.set(translations[i3], translations[i3 + 1], translations[i3 + 2]);
                }
                if (rotations) {
                    const i4 = i * 4;
                    rot.set(rotations[i4], rotations[i4 + 1], rotations[i4 + 2], rotations[i4 + 3]);
                }
                if (scales) {
                    scl.set(scales[i3], scales[i3 + 1], scales[i3 + 2]);
                }
                matrix.setTRS(pos, rot, scl);
                // copy matrix elements into array of floats
                for(let m = 0; m < 16; m++){
                    matrices[matrixIndex++] = matrix.data[m];
                }
            }
            data.matrices = matrices;
        }
    });
};
const createNodes = (gltf, options, nodeInstancingMap)=>{
    if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) {
        return [];
    }
    const preprocess = options?.node?.preprocess;
    const process = options?.node?.process ?? createNode;
    const postprocess = options?.node?.postprocess;
    const nodes = gltf.nodes.map((gltfNode, index)=>{
        if (preprocess) {
            preprocess(gltfNode);
        }
        const node = process(gltfNode, index, nodeInstancingMap);
        if (postprocess) {
            postprocess(gltfNode, node);
        }
        return node;
    });
    // build node hierarchy
    for(let i = 0; i < gltf.nodes.length; ++i){
        const gltfNode = gltf.nodes[i];
        if (gltfNode.hasOwnProperty('children')) {
            const parent = nodes[i];
            const uniqueNames = {};
            for(let j = 0; j < gltfNode.children.length; ++j){
                const child = nodes[gltfNode.children[j]];
                if (!child.parent) {
                    if (uniqueNames.hasOwnProperty(child.name)) {
                        child.name += uniqueNames[child.name]++;
                    } else {
                        uniqueNames[child.name] = 1;
                    }
                    parent.addChild(child);
                }
            }
        }
    }
    return nodes;
};
const createScenes = (gltf, nodes)=>{
    const scenes = [];
    const count = gltf.scenes.length;
    // if there's a single scene with a single node in it, don't create wrapper nodes
    if (count === 1 && gltf.scenes[0].nodes?.length === 1) {
        const nodeIndex = gltf.scenes[0].nodes[0];
        scenes.push(nodes[nodeIndex]);
    } else {
        // create root node per scene
        for(let i = 0; i < count; i++){
            const scene = gltf.scenes[i];
            if (scene.nodes) {
                const sceneRoot = new GraphNode(scene.name);
                for(let n = 0; n < scene.nodes.length; n++){
                    const childNode = nodes[scene.nodes[n]];
                    sceneRoot.addChild(childNode);
                }
                scenes.push(sceneRoot);
            }
        }
    }
    return scenes;
};
const createCameras = (gltf, nodes, options)=>{
    let cameras = null;
    if (gltf.hasOwnProperty('nodes') && gltf.hasOwnProperty('cameras') && gltf.cameras.length > 0) {
        const preprocess = options?.camera?.preprocess;
        const process = options?.camera?.process ?? createCamera;
        const postprocess = options?.camera?.postprocess;
        gltf.nodes.forEach((gltfNode, nodeIndex)=>{
            if (gltfNode.hasOwnProperty('camera')) {
                const gltfCamera = gltf.cameras[gltfNode.camera];
                if (gltfCamera) {
                    if (preprocess) {
                        preprocess(gltfCamera);
                    }
                    const camera = process(gltfCamera, nodes[nodeIndex]);
                    if (postprocess) {
                        postprocess(gltfCamera, camera);
                    }
                    // add the camera to node->camera map
                    if (camera) {
                        if (!cameras) cameras = new Map();
                        cameras.set(gltfNode, camera);
                    }
                }
            }
        });
    }
    return cameras;
};
const createLights = (gltf, nodes, options)=>{
    let lights = null;
    if (gltf.hasOwnProperty('nodes') && gltf.hasOwnProperty('extensions') && gltf.extensions.hasOwnProperty('KHR_lights_punctual') && gltf.extensions.KHR_lights_punctual.hasOwnProperty('lights')) {
        const gltfLights = gltf.extensions.KHR_lights_punctual.lights;
        if (gltfLights.length) {
            const preprocess = options?.light?.preprocess;
            const process = options?.light?.process ?? createLight;
            const postprocess = options?.light?.postprocess;
            // handle nodes with lights
            gltf.nodes.forEach((gltfNode, nodeIndex)=>{
                if (gltfNode.hasOwnProperty('extensions') && gltfNode.extensions.hasOwnProperty('KHR_lights_punctual') && gltfNode.extensions.KHR_lights_punctual.hasOwnProperty('light')) {
                    const lightIndex = gltfNode.extensions.KHR_lights_punctual.light;
                    const gltfLight = gltfLights[lightIndex];
                    if (gltfLight) {
                        if (preprocess) {
                            preprocess(gltfLight);
                        }
                        const light = process(gltfLight, nodes[nodeIndex]);
                        if (postprocess) {
                            postprocess(gltfLight, light);
                        }
                        // add the light to node->light map
                        if (light) {
                            if (!lights) lights = new Map();
                            lights.set(gltfNode, light);
                        }
                    }
                }
            });
        }
    }
    return lights;
};
// link skins to the meshes
const linkSkins = (gltf, renders, skins)=>{
    gltf.nodes.forEach((gltfNode)=>{
        if (gltfNode.hasOwnProperty('mesh') && gltfNode.hasOwnProperty('skin')) {
            const meshGroup = renders[gltfNode.mesh].meshes;
            meshGroup.forEach((mesh)=>{
                mesh.skin = skins[gltfNode.skin];
            });
        }
    });
};
// create engine resources from the downloaded GLB data
const createResources = async (device, gltf, bufferViews, textures, options)=>{
    const preprocess = options?.global?.preprocess;
    const postprocess = options?.global?.postprocess;
    if (preprocess) {
        preprocess(gltf);
    }
    // The very first version of FACT generated incorrectly flipped V texture
    // coordinates. Since this first version was only ever available behind an
    // editor flag there should be very few such GLB models in the wild.
    // Instead of bloating the engine forevermore with code to handle this case,
    // we now issue a warning and prompt user to reconvert their FBX.
    if (gltf.asset && gltf.asset.generator === 'PlayCanvas') {
        Debug.warn('glTF model may have been generated with flipped UVs. Please reconvert.');
    }
    const nodeInstancingMap = new Map();
    const nodes = createNodes(gltf, options, nodeInstancingMap);
    const scenes = createScenes(gltf, nodes);
    const lights = createLights(gltf, nodes, options);
    const cameras = createCameras(gltf, nodes, options);
    const variants = createVariants(gltf);
    // buffer data must have finished loading in order to create meshes and animations
    const bufferViewData = await Promise.all(bufferViews);
    const { meshes, meshVariants, meshDefaultMaterials, promises } = createMeshes(device, gltf, bufferViewData, options);
    const animations = createAnimations(gltf, nodes, bufferViewData, options);
    createInstancing(device, gltf, nodeInstancingMap, bufferViewData);
    // textures must have finished loading in order to create materials
    const textureAssets = await Promise.all(textures);
    const textureInstances = textureAssets.map((t)=>t.resource);
    const materials = createMaterials(gltf, textureInstances, options);
    const skins = createSkins(device, gltf, nodes, bufferViewData);
    // create renders to wrap meshes
    const renders = [];
    for(let i = 0; i < meshes.length; i++){
        renders[i] = new Render();
        renders[i].meshes = meshes[i];
    }
    // link skins to meshes
    linkSkins(gltf, renders, skins);
    const result = new GlbResources();
    result.gltf = gltf;
    result.nodes = nodes;
    result.scenes = scenes;
    result.animations = animations;
    result.textures = textureAssets;
    result.materials = materials;
    result.variants = variants;
    result.meshVariants = meshVariants;
    result.meshDefaultMaterials = meshDefaultMaterials;
    result.renders = renders;
    result.skins = skins;
    result.lights = lights;
    result.cameras = cameras;
    result.nodeInstancingMap = nodeInstancingMap;
    if (postprocess) {
        postprocess(gltf, result);
    }
    // wait for draco meshes to complete decoding
    await Promise.all(promises);
    return result;
};
const applySampler = (texture, gltfSampler)=>{
    const getFilter = (filter, defaultValue)=>{
        switch(filter){
            case 9728:
                return FILTER_NEAREST;
            case 9729:
                return FILTER_LINEAR;
            case 9984:
                return FILTER_NEAREST_MIPMAP_NEAREST;
            case 9985:
                return FILTER_LINEAR_MIPMAP_NEAREST;
            case 9986:
                return FILTER_NEAREST_MIPMAP_LINEAR;
            case 9987:
                return FILTER_LINEAR_MIPMAP_LINEAR;
            default:
                return defaultValue;
        }
    };
    const getWrap = (wrap, defaultValue)=>{
        switch(wrap){
            case 33071:
                return ADDRESS_CLAMP_TO_EDGE;
            case 33648:
                return ADDRESS_MIRRORED_REPEAT;
            case 10497:
                return ADDRESS_REPEAT;
            default:
                return defaultValue;
        }
    };
    if (texture) {
        gltfSampler = gltfSampler ?? {};
        texture.minFilter = getFilter(gltfSampler.minFilter, FILTER_LINEAR_MIPMAP_LINEAR);
        texture.magFilter = getFilter(gltfSampler.magFilter, FILTER_LINEAR);
        texture.addressU = getWrap(gltfSampler.wrapS, ADDRESS_REPEAT);
        texture.addressV = getWrap(gltfSampler.wrapT, ADDRESS_REPEAT);
    }
};
let gltfTextureUniqueId = 0;
const getTextureSource = (gltfTexture)=>gltfTexture.extensions?.KHR_texture_basisu?.source ?? gltfTexture.extensions?.EXT_texture_webp?.source ?? gltfTexture.source;
// create gltf images. returns an array of promises that resolve to texture assets.
const createImages = (gltf, bufferViews, urlBase, registry, options)=>{
    if (!gltf.images || gltf.images.length === 0) {
        return [];
    }
    const preprocess = options?.image?.preprocess;
    const processAsync = options?.image?.processAsync;
    const postprocess = options?.image?.postprocess;
    const mimeTypeFileExtensions = {
        'image/png': 'png',
        'image/jpeg': 'jpg',
        'image/basis': 'basis',
        'image/ktx': 'ktx',
        'image/ktx2': 'ktx2',
        'image/vnd-ms.dds': 'dds'
    };
    // a Set of image indices that use sRGB textures (base and emissive)
    const getGammaTextures = (gltf)=>{
        const set = new Set();
        if (gltf.hasOwnProperty('materials')) {
            gltf.materials.forEach((gltfMaterial)=>{
                // base texture
                if (gltfMaterial.hasOwnProperty('pbrMetallicRoughness')) {
                    const pbrData = gltfMaterial.pbrMetallicRoughness;
                    if (pbrData.hasOwnProperty('baseColorTexture')) {
                        const gltfTexture = gltf.textures[pbrData.baseColorTexture.index];
                        set.add(getTextureSource(gltfTexture));
                    }
                }
                // emissive
                if (gltfMaterial.hasOwnProperty('emissiveTexture')) {
                    const gltfTexture = gltf.textures[gltfMaterial.emissiveTexture.index];
                    set.add(getTextureSource(gltfTexture));
                }
                if (gltfMaterial.hasOwnProperty('extensions')) {
                    // sheen
                    const sheen = gltfMaterial.extensions.KHR_materials_sheen;
                    if (sheen) {
                        if (sheen.hasOwnProperty('sheenColorTexture')) {
                            const gltfTexture = gltf.textures[sheen.sheenColorTexture.index];
                            set.add(getTextureSource(gltfTexture));
                        }
                    }
                    // specular glossiness
                    const specularGlossiness = gltfMaterial.extensions.KHR_materials_pbrSpecularGlossiness;
                    if (specularGlossiness) {
                        if (specularGlossiness.hasOwnProperty('specularGlossinessTexture')) {
                            const gltfTexture = gltf.textures[specularGlossiness.specularGlossinessTexture.index];
                            set.add(getTextureSource(gltfTexture));
                        }
                    }
                    // specular
                    const specular = gltfMaterial.extensions.KHR_materials_specular;
                    if (specular) {
                        if (specular.hasOwnProperty('specularColorTexture')) {
                            const gltfTexture = gltf.textures[specular.specularColorTexture.index];
                            set.add(getTextureSource(gltfTexture));
                        }
                    }
                }
            });
        }
        return set;
    };
    const loadTexture = (gltfImage, url, bufferView, mimeType, options, srgb)=>{
        return new Promise((resolve, reject)=>{
            const continuation = (bufferViewData)=>{
                const name = `${gltfImage.name || 'gltf-texture'}-${gltfTextureUniqueId++}`;
                // construct the asset file
                const file = {
                    url: url || name
                };
                if (bufferViewData) {
                    file.contents = bufferViewData.slice(0).buffer;
                }
                if (mimeType) {
                    const extension = mimeTypeFileExtensions[mimeType];
                    if (extension) {
                        file.filename = `${file.url}.${extension}`;
                    }
                }
                // create and load the asset
                const data = {
                    srgb
                };
                const asset = new Asset(name, 'texture', file, data, options);
                asset.on('load', (asset)=>resolve(asset));
                asset.on('error', (err)=>reject(err));
                registry.add(asset);
                registry.load(asset);
            };
            if (bufferView) {
                bufferView.then((bufferViewData)=>continuation(bufferViewData));
            } else {
                continuation(null);
            }
        });
    };
    const gammaTextures = getGammaTextures(gltf);
    return gltf.images.map((gltfImage, i)=>{
        if (preprocess) {
            preprocess(gltfImage);
        }
        let promise;
        if (processAsync) {
            promise = new Promise((resolve, reject)=>{
                processAsync(gltfImage, (err, textureAsset)=>{
                    if (err) {
                        reject(err);
                    } else {
                        resolve(textureAsset);
                    }
                });
            });
        } else {
            promise = new Promise((resolve)=>{
                resolve(null);
            });
        }
        promise = promise.then((textureAsset)=>{
            // if the image uses sRGB, pass it as an option to the texture creation
            const srgb = gammaTextures.has(i);
            if (textureAsset) {
                return textureAsset;
            } else if (gltfImage.hasOwnProperty('uri')) {
                // uri specified
                if (isDataURI(gltfImage.uri)) {
                    return loadTexture(gltfImage, gltfImage.uri, null, getDataURIMimeType(gltfImage.uri), null, srgb);
                }
                return loadTexture(gltfImage, ABSOLUTE_URL.test(gltfImage.uri) ? gltfImage.uri : path$1.join(urlBase, gltfImage.uri), null, null, {
                    crossOrigin: 'anonymous'
                }, srgb);
            } else if (gltfImage.hasOwnProperty('bufferView') && gltfImage.hasOwnProperty('mimeType')) {
                // bufferview
                return loadTexture(gltfImage, null, bufferViews[gltfImage.bufferView], gltfImage.mimeType, null, srgb);
            }
            // fail
            return Promise.reject(new Error(`Invalid image found in gltf (neither uri or bufferView found). index=${i}`));
        });
        if (postprocess) {
            promise = promise.then((textureAsset)=>{
                postprocess(gltfImage, textureAsset);
                return textureAsset;
            });
        }
        return promise;
    });
};
// create gltf textures. returns an array of promises that resolve to texture assets.
const createTextures = (gltf, images, options)=>{
    if (!gltf?.images?.length || !gltf?.textures?.length) {
        return [];
    }
    const preprocess = options?.texture?.preprocess;
    const processAsync = options?.texture?.processAsync;
    const postprocess = options?.texture?.postprocess;
    const seenImages = new Set();
    return gltf.textures.map((gltfTexture)=>{
        if (preprocess) {
            preprocess(gltfTexture);
        }
        let promise;
        if (processAsync) {
            promise = new Promise((resolve, reject)=>{
                processAsync(gltfTexture, gltf.images, (err, gltfImageIndex)=>{
                    if (err) {
                        reject(err);
                    } else {
                        resolve(gltfImageIndex);
                    }
                });
            });
        } else {
            promise = new Promise((resolve)=>{
                resolve(null);
            });
        }
        promise = promise.then((gltfImageIndex)=>{
            // resolve image index
            gltfImageIndex = gltfImageIndex ?? getTextureSource(gltfTexture);
            const cloneAsset = seenImages.has(gltfImageIndex);
            seenImages.add(gltfImageIndex);
            return images[gltfImageIndex].then((imageAsset)=>{
                const asset = cloneAsset ? cloneTextureAsset(imageAsset) : imageAsset;
                applySampler(asset.resource, (gltf.samplers ?? [])[gltfTexture.sampler]);
                return asset;
            });
        });
        if (postprocess) {
            promise = promise.then((textureAsset)=>{
                postprocess(gltfTexture, textureAsset);
                return textureAsset;
            });
        }
        return promise;
    });
};
// load gltf buffers. returns an array of promises that resolve to typed arrays.
const loadBuffers = (gltf, binaryChunk, urlBase, options)=>{
    if (!gltf.buffers || gltf.buffers.length === 0) {
        return [];
    }
    const preprocess = options?.buffer?.preprocess;
    const processAsync = options?.buffer?.processAsync;
    const postprocess = options?.buffer?.postprocess;
    return gltf.buffers.map((gltfBuffer, i)=>{
        if (preprocess) {
            preprocess(gltfBuffer);
        }
        let promise;
        if (processAsync) {
            promise = new Promise((resolve, reject)=>{
                processAsync(gltfBuffer, (err, arrayBuffer)=>{
                    if (err) {
                        reject(err);
                    } else {
                        resolve(arrayBuffer);
                    }
                });
            });
        } else {
            promise = new Promise((resolve)=>{
                resolve(null);
            });
        }
        promise = promise.then((arrayBuffer)=>{
            if (arrayBuffer) {
                return arrayBuffer;
            } else if (gltfBuffer.hasOwnProperty('uri')) {
                if (isDataURI(gltfBuffer.uri)) {
                    // convert base64 to raw binary data held in a string
                    // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
                    const byteString = atob(gltfBuffer.uri.split(',')[1]);
                    // create a view into the buffer
                    const binaryArray = new Uint8Array(byteString.length);
                    // set the bytes of the buffer to the correct values
                    for(let j = 0; j < byteString.length; j++){
                        binaryArray[j] = byteString.charCodeAt(j);
                    }
                    return binaryArray;
                }
                return new Promise((resolve, reject)=>{
                    http.get(ABSOLUTE_URL.test(gltfBuffer.uri) ? gltfBuffer.uri : path$1.join(urlBase, gltfBuffer.uri), {
                        cache: true,
                        responseType: 'arraybuffer',
                        retry: false
                    }, (err, result)=>{
                        if (err) {
                            reject(err);
                        } else {
                            resolve(new Uint8Array(result));
                        }
                    });
                });
            }
            // glb buffer reference
            return binaryChunk;
        });
        if (postprocess) {
            promise = promise.then((buffer)=>{
                postprocess(gltf.buffers[i], buffer);
                return buffer;
            });
        }
        return promise;
    });
};
// parse the gltf chunk, returns the gltf json
const parseGltf = (gltfChunk, callback)=>{
    const decodeBinaryUtf8 = (array)=>{
        if (typeof TextDecoder !== 'undefined') {
            return new TextDecoder().decode(array);
        }
        let str = '';
        for(let i = 0; i < array.length; i++){
            str += String.fromCharCode(array[i]);
        }
        return decodeURIComponent(escape(str));
    };
    const gltf = JSON.parse(decodeBinaryUtf8(gltfChunk));
    // check gltf version
    if (gltf.asset && gltf.asset.version && parseFloat(gltf.asset.version) < 2) {
        callback(`Invalid gltf version. Expected version 2.0 or above but found version '${gltf.asset.version}'.`);
        return;
    }
    // check required extensions
    callback(null, gltf);
};
// parse glb data, returns the gltf and binary chunk
const parseGlb = (glbData, callback)=>{
    const data = glbData instanceof ArrayBuffer ? new DataView(glbData) : new DataView(glbData.buffer, glbData.byteOffset, glbData.byteLength);
    // read header
    const magic = data.getUint32(0, true);
    const version = data.getUint32(4, true);
    const length = data.getUint32(8, true);
    if (magic !== 0x46546C67) {
        callback(`Invalid magic number found in glb header. Expected 0x46546C67, found 0x${magic.toString(16)}`);
        return;
    }
    if (version !== 2) {
        callback(`Invalid version number found in glb header. Expected 2, found ${version}`);
        return;
    }
    if (length <= 0 || length > data.byteLength) {
        callback(`Invalid length found in glb header. Found ${length}`);
        return;
    }
    // read chunks
    const chunks = [];
    let offset = 12;
    while(offset < length){
        const chunkLength = data.getUint32(offset, true);
        if (offset + chunkLength + 8 > data.byteLength) {
            callback(`Invalid chunk length found in glb. Found ${chunkLength}`);
        }
        const chunkType = data.getUint32(offset + 4, true);
        const chunkData = new Uint8Array(data.buffer, data.byteOffset + offset + 8, chunkLength);
        chunks.push({
            length: chunkLength,
            type: chunkType,
            data: chunkData
        });
        offset += chunkLength + 8;
    }
    if (chunks.length !== 1 && chunks.length !== 2) {
        callback('Invalid number of chunks found in glb file.');
        return;
    }
    if (chunks[0].type !== 0x4E4F534A) {
        callback(`Invalid chunk type found in glb file. Expected 0x4E4F534A, found 0x${chunks[0].type.toString(16)}`);
        return;
    }
    if (chunks.length > 1 && chunks[1].type !== 0x004E4942) {
        callback(`Invalid chunk type found in glb file. Expected 0x004E4942, found 0x${chunks[1].type.toString(16)}`);
        return;
    }
    callback(null, {
        gltfChunk: chunks[0].data,
        binaryChunk: chunks.length === 2 ? chunks[1].data : null
    });
};
// parse the chunk of data, which can be glb or gltf
const parseChunk = (filename, data, callback)=>{
    const hasGlbHeader = ()=>{
        // glb format starts with 'glTF'
        const u8 = new Uint8Array(data);
        return u8[0] === 103 && u8[1] === 108 && u8[2] === 84 && u8[3] === 70;
    };
    if (filename && filename.toLowerCase().endsWith('.glb') || hasGlbHeader()) {
        parseGlb(data, callback);
    } else {
        callback(null, {
            gltfChunk: data,
            binaryChunk: null
        });
    }
};
// create buffer views
const createBufferViews = (gltf, buffers, options)=>{
    const result = [];
    const preprocess = options?.bufferView?.preprocess;
    const processAsync = options?.bufferView?.processAsync;
    const postprocess = options?.bufferView?.postprocess;
    // handle case of no buffers
    if (!gltf.bufferViews?.length) {
        return result;
    }
    for(let i = 0; i < gltf.bufferViews.length; ++i){
        const gltfBufferView = gltf.bufferViews[i];
        if (preprocess) {
            preprocess(gltfBufferView);
        }
        let promise;
        if (processAsync) {
            promise = new Promise((resolve, reject)=>{
                processAsync(gltfBufferView, buffers, (err, result)=>{
                    if (err) {
                        reject(err);
                    } else {
                        resolve(result);
                    }
                });
            });
        } else {
            promise = new Promise((resolve)=>{
                resolve(null);
            });
        }
        promise = promise.then((buffer)=>{
            if (buffer) {
                return buffer;
            }
            // convert buffer to typed array
            return buffers[gltfBufferView.buffer].then((buffer)=>{
                return new Uint8Array(buffer.buffer, buffer.byteOffset + (gltfBufferView.byteOffset || 0), gltfBufferView.byteLength);
            });
        });
        // add a 'byteStride' member to the typed array so we have easy access to it later
        if (gltfBufferView.hasOwnProperty('byteStride')) {
            promise = promise.then((typedArray)=>{
                typedArray.byteStride = gltfBufferView.byteStride;
                return typedArray;
            });
        }
        if (postprocess) {
            promise = promise.then((typedArray)=>{
                postprocess(gltfBufferView, typedArray);
                return typedArray;
            });
        }
        result.push(promise);
    }
    return result;
};
class GlbParser {
    // parse the gltf or glb data asynchronously, loading external resources
    static parse(filename, urlBase, data, device, registry, options, callback) {
        // parse the data
        parseChunk(filename, data, (err, chunks)=>{
            if (err) {
                callback(err);
                return;
            }
            // parse gltf
            parseGltf(chunks.gltfChunk, (err, gltf)=>{
                if (err) {
                    callback(err);
                    return;
                }
                const buffers = loadBuffers(gltf, chunks.binaryChunk, urlBase, options);
                const bufferViews = createBufferViews(gltf, buffers, options);
                const images = createImages(gltf, bufferViews, urlBase, registry, options);
                const textures = createTextures(gltf, images, options);
                createResources(device, gltf, bufferViews, textures, options).then((result)=>callback(null, result)).catch((err)=>callback(err));
            });
        });
    }
    static createDefaultMaterial() {
        return createMaterial({
            name: 'defaultGlbMaterial'
        }, []);
    }
}

/**
 * Resource handler used for loading {@link AnimClip} resources.
 *
 * @ignore
 */ class AnimClipHandler extends ResourceHandler {
    constructor(app){
        super(app, 'animclip');
    }
    load(url, callback) {
        if (typeof url === 'string') {
            url = {
                load: url,
                original: url
            };
        }
        // we need to specify JSON for blob URLs
        const options = {
            retry: this.maxRetries > 0,
            maxRetries: this.maxRetries
        };
        if (url.load.startsWith('blob:')) {
            options.responseType = Http.ResponseType.JSON;
        }
        http.get(url.load, options, (err, response)=>{
            if (err) {
                callback(`Error loading animation clip resource: ${url.original} [${err}]`);
            } else {
                callback(null, response);
            }
        });
    }
    open(url, data) {
        const name = data.name;
        const duration = data.duration;
        const inputs = data.inputs.map((input)=>{
            return new AnimData(1, input);
        });
        const outputs = data.outputs.map((output)=>{
            return new AnimData(output.components, output.data);
        });
        const curves = data.curves.map((curve)=>{
            return new AnimCurve([
                curve.path
            ], curve.inputIndex, curve.outputIndex, curve.interpolation);
        });
        return new AnimTrack(name, duration, inputs, outputs, curves);
    }
}

/**
 * Resource handler used for loading {@link AnimStateGraph} resources.
 *
 * @ignore
 */ class AnimStateGraphHandler extends ResourceHandler {
    constructor(app){
        super(app, 'animstategraph');
    }
    load(url, callback) {
        if (typeof url === 'string') {
            url = {
                load: url,
                original: url
            };
        }
        // we need to specify JSON for blob URLs
        const options = {
            retry: this.maxRetries > 0,
            maxRetries: this.maxRetries
        };
        if (url.load.startsWith('blob:')) {
            options.responseType = Http.ResponseType.JSON;
        }
        http.get(url.load, options, (err, response)=>{
            if (err) {
                callback(`Error loading animation state graph resource: ${url.original} [${err}]`);
            } else {
                callback(null, response);
            }
        });
    }
    open(url, data) {
        return new AnimStateGraph(data);
    }
}

// Container resource returned by the GlbParser. Implements the ContainerResource interface.
class GlbContainerResource {
    constructor(data, asset, assets, defaultMaterial){
        const createAsset = function(type, resource, index) {
            const subAsset = GlbContainerResource.createAsset(asset.name, type, resource, index);
            assets.add(subAsset);
            return subAsset;
        };
        // render assets
        const renders = [];
        for(let i = 0; i < data.renders.length; ++i){
            renders.push(createAsset('render', data.renders[i], i));
        }
        // create material assets
        const materials = [];
        for(let i = 0; i < data.materials.length; ++i){
            materials.push(createAsset('material', data.materials[i], i));
        }
        // create animation assets
        const animations = [];
        for(let i = 0; i < data.animations.length; ++i){
            animations.push(createAsset('animation', data.animations[i], i));
        }
        this.data = data;
        this._model = null;
        this._assetName = asset.name;
        this._assets = assets;
        this._defaultMaterial = defaultMaterial;
        this.renders = renders;
        this.materials = materials;
        this.textures = data.textures; // texture assets are created directly
        this.animations = animations;
    }
    get model() {
        if (!this._model) {
            // create model only when needed
            const model = GlbContainerResource.createModel(this.data, this._defaultMaterial);
            const modelAsset = GlbContainerResource.createAsset(this._assetName, 'model', model, 0);
            this._assets.add(modelAsset);
            this._model = modelAsset;
        }
        return this._model;
    }
    static createAsset(assetName, type, resource, index) {
        const subAsset = new Asset(`${assetName}/${type}/${index}`, type, {
            url: ''
        });
        subAsset.resource = resource;
        subAsset.loaded = true;
        return subAsset;
    }
    instantiateModelEntity(options) {
        const entity = new Entity(undefined, this._assets._loader._app);
        entity.addComponent('model', Object.assign({
            type: 'asset',
            asset: this.model
        }, options));
        return entity;
    }
    instantiateRenderEntity(options) {
        const defaultMaterial = this._defaultMaterial;
        const skinnedMeshInstances = [];
        const createMeshInstance = function(root, entity, mesh, materials, meshDefaultMaterials, skins, gltfNode, nodeInstancingMap) {
            // clone mesh instance
            const materialIndex = meshDefaultMaterials[mesh.id];
            const material = materialIndex === undefined ? defaultMaterial : materials[materialIndex];
            const meshInstance = new MeshInstance(mesh, material);
            // create morph instance
            if (mesh.morph) {
                meshInstance.morphInstance = new MorphInstance(mesh.morph);
            }
            // store data to create skin instance after the hierarchy is created
            if (gltfNode.hasOwnProperty('skin')) {
                skinnedMeshInstances.push({
                    meshInstance: meshInstance,
                    rootBone: root,
                    entity: entity
                });
            }
            // if the node is instanced, hook up instancing
            const instData = nodeInstancingMap.get(gltfNode);
            if (instData) {
                const matrices = instData.matrices;
                const vbFormat = VertexFormat.getDefaultInstancingFormat(mesh.device);
                const vb = new VertexBuffer(mesh.device, vbFormat, matrices.length / 16, {
                    data: matrices
                });
                meshInstance.setInstancing(vb);
                // mark the vertex buffer for destruction when the mesh instance is destroyed
                meshInstance.instancingData._destroyVertexBuffer = true;
            }
            return meshInstance;
        };
        // helper function to recursively clone a hierarchy of GraphNodes to Entities
        const cloneHierarchy = (root, node, glb)=>{
            const entity = new Entity(undefined, this._assets._loader._app);
            node._cloneInternal(entity);
            // first entity becomes the root
            if (!root) root = entity;
            // find all components needed for this node
            let attachedMi = null;
            let renderAsset = null;
            for(let i = 0; i < glb.nodes.length; i++){
                const glbNode = glb.nodes[i];
                if (glbNode === node) {
                    const gltfNode = glb.gltf.nodes[i];
                    // mesh
                    if (gltfNode.hasOwnProperty('mesh')) {
                        const meshGroup = glb.renders[gltfNode.mesh].meshes;
                        renderAsset = this.renders[gltfNode.mesh];
                        for(let mi = 0; mi < meshGroup.length; mi++){
                            const mesh = meshGroup[mi];
                            if (mesh) {
                                const cloneMi = createMeshInstance(root, entity, mesh, glb.materials, glb.meshDefaultMaterials, glb.skins, gltfNode, glb.nodeInstancingMap);
                                // add it to list
                                if (!attachedMi) {
                                    attachedMi = [];
                                }
                                attachedMi.push(cloneMi);
                            }
                        }
                    }
                    // light - clone (additional child) entity with the light component
                    // cannot clone the component as additional entity has a rotation to handle different light direction
                    if (glb.lights) {
                        const lightEntity = glb.lights.get(gltfNode);
                        if (lightEntity) {
                            entity.addChild(lightEntity.clone());
                        }
                    }
                    // camera
                    if (glb.cameras) {
                        const cameraEntity = glb.cameras.get(gltfNode);
                        if (cameraEntity) {
                            // clone camera component into the entity
                            cameraEntity.camera.system.cloneComponent(cameraEntity, entity);
                        }
                    }
                }
            }
            // create render components for mesh instances
            if (attachedMi) {
                entity.addComponent('render', Object.assign({
                    type: 'asset',
                    meshInstances: attachedMi
                }, options));
                // assign asset id without recreating mesh instances which are already set up with materials
                entity.render.assignAsset(renderAsset);
            }
            // recursively clone children
            const children = node.children;
            for(let i = 0; i < children.length; i++){
                const childClone = cloneHierarchy(root, children[i], glb);
                entity.addChild(childClone);
            }
            return entity;
        };
        // clone scenes hierarchies
        const sceneClones = [];
        for (const scene of this.data.scenes){
            sceneClones.push(cloneHierarchy(null, scene, this.data));
        }
        // now that the hierarchy is created, create skin instances and resolve bones using the hierarchy
        skinnedMeshInstances.forEach((data)=>{
            data.meshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(data.meshInstance.mesh.skin, data.rootBone, data.entity);
            data.meshInstance.node.render.rootBone = data.rootBone;
        });
        // return the scene hierarchy created from scene clones
        return GlbContainerResource.createSceneHierarchy(sceneClones, Entity);
    }
    // get material variants
    getMaterialVariants() {
        return this.data.variants ? Object.keys(this.data.variants) : [];
    }
    // apply material variant to entity
    applyMaterialVariant(entity, name) {
        const variant = name ? this.data.variants[name] : null;
        if (variant === undefined) {
            Debug.warn(`No variant named ${name} exists in resource`);
            return;
        }
        const renders = entity.findComponents('render');
        for(let i = 0; i < renders.length; i++){
            const renderComponent = renders[i];
            this._applyMaterialVariant(variant, renderComponent.meshInstances);
        }
    }
    // apply material variant to mesh instances
    applyMaterialVariantInstances(instances, name) {
        const variant = name ? this.data.variants[name] : null;
        if (variant === undefined) {
            Debug.warn(`No variant named ${name} exists in resource`);
            return;
        }
        this._applyMaterialVariant(variant, instances);
    }
    // internally apply variant to instances
    _applyMaterialVariant(variant, instances) {
        instances.forEach((instance)=>{
            if (variant === null) {
                instance.material = this._defaultMaterial;
            } else {
                const meshVariants = this.data.meshVariants[instance.mesh.id];
                if (meshVariants) {
                    instance.material = this.data.materials[meshVariants[variant]];
                }
            }
            Debug.assert(instance.material);
        });
    }
    // helper function to create a single hierarchy from an array of nodes
    static createSceneHierarchy(sceneNodes, nodeType) {
        // create a single root of the hierarchy - either the single scene, or a new Entity parent if multiple scenes
        let root = null;
        if (sceneNodes.length === 1) {
            // use scene if only one
            root = sceneNodes[0];
        } else {
            // create group node for all scenes
            root = new nodeType('SceneGroup');
            for (const scene of sceneNodes){
                root.addChild(scene);
            }
        }
        return root;
    }
    // create a pc.Model from the parsed GLB data structures
    static createModel(glb, defaultMaterial) {
        const createMeshInstance = function(model, mesh, skins, skinInstances, materials, node, gltfNode) {
            const materialIndex = glb.meshDefaultMaterials[mesh.id];
            const material = materialIndex === undefined ? defaultMaterial : materials[materialIndex];
            const meshInstance = new MeshInstance(mesh, material, node);
            if (mesh.morph) {
                const morphInstance = new MorphInstance(mesh.morph);
                meshInstance.morphInstance = morphInstance;
                model.morphInstances.push(morphInstance);
            }
            if (gltfNode.hasOwnProperty('skin')) {
                const skinIndex = gltfNode.skin;
                const skin = skins[skinIndex];
                mesh.skin = skin;
                const skinInstance = skinInstances[skinIndex];
                meshInstance.skinInstance = skinInstance;
                model.skinInstances.push(skinInstance);
            }
            model.meshInstances.push(meshInstance);
        };
        const model = new Model();
        // create skinInstance for each skin
        const skinInstances = [];
        for (const skin of glb.skins){
            const skinInstance = new SkinInstance(skin);
            skinInstance.bones = skin.bones;
            skinInstances.push(skinInstance);
        }
        // node hierarchy for the model
        model.graph = GlbContainerResource.createSceneHierarchy(glb.scenes, GraphNode);
        // create mesh instance for meshes on nodes that are part of hierarchy
        for(let i = 0; i < glb.nodes.length; i++){
            const node = glb.nodes[i];
            if (node.root === model.graph) {
                const gltfNode = glb.gltf.nodes[i];
                if (gltfNode.hasOwnProperty('mesh')) {
                    const meshGroup = glb.renders[gltfNode.mesh].meshes;
                    for(let mi = 0; mi < meshGroup.length; mi++){
                        const mesh = meshGroup[mi];
                        if (mesh) {
                            createMeshInstance(model, mesh, glb.skins, skinInstances, glb.materials, node, gltfNode);
                        }
                    }
                }
            }
        }
        return model;
    }
    destroy() {
        const registry = this._assets;
        const destroyAsset = function(asset) {
            registry.remove(asset);
            asset.unload();
        };
        const destroyAssets = function(assets) {
            assets.forEach((asset)=>{
                destroyAsset(asset);
            });
        };
        // unload and destroy assets
        if (this.animations) {
            destroyAssets(this.animations);
            this.animations = null;
        }
        if (this.textures) {
            destroyAssets(this.textures);
            this.textures = null;
        }
        if (this.materials) {
            destroyAssets(this.materials);
            this.materials = null;
        }
        if (this.renders) {
            destroyAssets(this.renders);
            this.renders = null;
        }
        if (this._model) {
            destroyAsset(this._model);
            this._model = null;
        }
        this.data = null;
        this.assets = null;
    }
}

class GlbContainerParser {
    constructor(device, assets, maxRetries){
        this._device = device;
        this._assets = assets;
        this._defaultMaterial = GlbParser.createDefaultMaterial();
        this.maxRetries = maxRetries;
    }
    _getUrlWithoutParams(url) {
        return url.indexOf('?') >= 0 ? url.split('?')[0] : url;
    }
    load(url, callback, asset) {
        Asset.fetchArrayBuffer(url.load, (err, result)=>{
            if (err) {
                callback(err);
            } else {
                GlbParser.parse(this._getUrlWithoutParams(url.original), path$1.extractPath(url.load), result, this._device, asset.registry, asset.options, (err, result)=>{
                    if (err) {
                        callback(err);
                    } else {
                        // return everything
                        callback(null, new GlbContainerResource(result, asset, this._assets, this._defaultMaterial));
                    }
                });
            }
        }, asset, this.maxRetries);
    }
    open(url, data, asset) {
        return data;
    }
    patch(asset, assets) {}
}

/**
 * Loads files that contain multiple resources. For example glTF files can contain textures, models
 * and animations.
 *
 * For glTF files, the asset options object can be used to pass load time callbacks for handling
 * the various resources at different stages of loading. The table below lists the resource types
 * and the corresponding supported process functions.
 *
 * | resource   | preprocess | process | processAsync | postprocess |
 * | ---------- | :--------: | :-----: | :----------: | :---------: |
 * | global     |      √     |         |              |      √      |
 * | node       |      √     |    √    |              |      √      |
 * | light      |      √     |    √    |              |      √      |
 * | camera     |      √     |    √    |              |      √      |
 * | animation  |      √     |         |              |      √      |
 * | material   |      √     |    √    |              |      √      |
 * | image      |      √     |         |      √       |      √      |
 * | texture    |      √     |         |      √       |      √      |
 * | buffer     |      √     |         |      √       |      √      |
 * | bufferView |      √     |         |      √       |      √      |
 *
 * Additional options that can be passed for glTF files:
 * [options.morphPreserveData] - When true, the morph target keeps its data passed using the options,
 * allowing the clone operation.
 * [options.morphPreferHighPrecision] - When true, high precision storage for morph targets should
 * be preferred. This is faster to create and allows higher precision, but takes more memory and
 * might be slower to render. Defaults to false.
 * [options.skipMeshes] - When true, the meshes from the container are not created. This can be
 * useful if you only need access to textures or animations and similar.
 *
 * For example, to receive a texture preprocess callback:
 *
 * ```javascript
 * const containerAsset = new pc.Asset(filename, 'container', { url: url, filename: filename }, null, {
 *     texture: {
 *         preprocess: (gltfTexture) => {
 *             console.log("texture preprocess");
 *         }
 *     }
 * });
 * ```
 *
 * @category Graphics
 */ class ContainerHandler extends ResourceHandler {
    /**
     * Create a new ContainerResource instance.
     *
     * @param {AppBase} app - The running {@link AppBase}.
     * @ignore
     */ constructor(app){
        super(app, 'container');
        this.glbContainerParser = new GlbContainerParser(app.graphicsDevice, app.assets, 0);
        this.parsers = {};
    }
    set maxRetries(value) {
        this.glbContainerParser.maxRetries = value;
        for(const parser in this.parsers){
            if (this.parsers.hasOwnProperty(parser)) {
                this.parsers[parser].maxRetries = value;
            }
        }
    }
    get maxRetries() {
        return this.glbContainerParser.maxRetries;
    }
    /**
     * @param {string} url - The resource URL.
     * @returns {string} The URL with query parameters removed.
     * @private
     */ _getUrlWithoutParams(url) {
        return url.indexOf('?') >= 0 ? url.split('?')[0] : url;
    }
    /**
     * @param {string} url - The resource URL.
     * @returns {*} A suitable parser to parse the resource.
     * @private
     */ _getParser(url) {
        const ext = url ? path$1.getExtension(this._getUrlWithoutParams(url)).toLowerCase().replace('.', '') : null;
        return this.parsers[ext] || this.glbContainerParser;
    }
    /**
     * @param {string|object} url - Either the URL of the resource to load or a structure
     * containing the load and original URL.
     * @param {string} [url.load] - The URL to be used for loading the resource.
     * @param {string} [url.original] - The original URL to be used for identifying the resource
     * format. This is necessary when loading, for example from blob.
     * @param {ResourceHandlerCallback} callback - The callback used when the resource is loaded or
     * an error occurs.
     * @param {Asset} [asset] - Optional asset that is passed by ResourceLoader.
     */ load(url, callback, asset) {
        if (typeof url === 'string') {
            url = {
                load: url,
                original: url
            };
        }
        this._getParser(url.original).load(url, callback, asset);
    }
    /**
     * @param {string} url - The URL of the resource to open.
     * @param {*} data - The raw resource data passed by callback from {@link ResourceHandler#load}.
     * @param {Asset} [asset] - Optional asset that is passed by ResourceLoader.
     * @returns {*} The parsed resource data.
     */ open(url, data, asset) {
        return this._getParser(url).open(url, data, asset);
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 */ /**
 * Resource handler used for loading cubemap {@link Texture} resources.
 *
 * @category Graphics
 */ class CubemapHandler extends ResourceHandler {
    /**
     * Create a new CubemapHandler instance.
     *
     * @param {AppBase} app - The running {@link AppBase}.
     * @ignore
     */ constructor(app){
        super(app, 'cubemap');
        this._device = app.graphicsDevice;
        this._registry = app.assets;
        this._loader = app.loader;
    }
    load(url, callback, asset) {
        this.loadAssets(asset, callback);
    }
    open(url, data, asset) {
        // caller will set our return value to asset.resources[0]. We've already set resources[0],
        // but we must return it again here so it doesn't get overwritten.
        return asset ? asset.resource : null;
    }
    patch(asset, registry) {
        this.loadAssets(asset, (err, result)=>{
            if (err) {
                // fire error event if patch failed
                registry.fire('error', asset);
                registry.fire(`error:${asset.id}`, err, asset);
                asset.fire('error', asset);
            }
        // nothing to do since asset:change would have been raised if
        // resources were changed.
        });
    }
    // get the list of dependent asset ids for the cubemap
    getAssetIds(cubemapAsset) {
        const result = [];
        // prefiltered cubemap is stored at index 0
        result[0] = cubemapAsset.file;
        // faces are stored at index 1..6
        if ((cubemapAsset.loadFaces || !cubemapAsset.file) && cubemapAsset.data && cubemapAsset.data.textures) {
            for(let i = 0; i < 6; ++i){
                result[i + 1] = cubemapAsset.data.textures[i];
            }
        } else {
            result[1] = result[2] = result[3] = result[4] = result[5] = result[6] = null;
        }
        return result;
    }
    // test whether two assets ids are the same
    compareAssetIds(assetIdA, assetIdB) {
        if (assetIdA && assetIdB) {
            if (parseInt(assetIdA, 10) === assetIdA || typeof assetIdA === 'string') {
                return assetIdA === assetIdB; // id or url
            }
            // else {
            return assetIdA.url === assetIdB.url; // file/url structure with url and filename
        }
        // else {
        return assetIdA !== null === (assetIdB !== null);
    }
    // update the cubemap resources given a newly loaded set of assets with their corresponding ids
    update(cubemapAsset, assetIds, assets) {
        const assetData = cubemapAsset.data || {};
        const oldAssets = cubemapAsset._handlerState.assets;
        const oldResources = cubemapAsset._resources;
        let tex, mip, i;
        // faces, prelit cubemap 128, 64, 32, 16, 8, 4
        const resources = [
            null,
            null,
            null,
            null,
            null,
            null,
            null
        ];
        // texture type used for faces and prelit cubemaps are both taken from
        // cubemap.data.rgbm
        const getType = function() {
            if (assetData.hasOwnProperty('type')) {
                return assetData.type;
            }
            if (assetData.hasOwnProperty('rgbm')) {
                return assetData.rgbm ? TEXTURETYPE_RGBM : TEXTURETYPE_DEFAULT;
            }
            return null;
        };
        // handle the prelit data
        if (!cubemapAsset.loaded || assets[0] !== oldAssets[0]) {
            // prelit asset changed
            if (assets[0]) {
                tex = assets[0].resource;
                if (tex.cubemap) {
                    for(i = 0; i < 6; ++i){
                        resources[i + 1] = new Texture(this._device, {
                            name: `${cubemapAsset.name}_prelitCubemap${tex.width >> i}`,
                            cubemap: true,
                            // assume prefiltered data has same encoding as the faces asset
                            type: getType() || tex.type,
                            width: tex.width >> i,
                            height: tex.height >> i,
                            format: tex.format,
                            levels: [
                                tex._levels[i]
                            ],
                            addressU: ADDRESS_CLAMP_TO_EDGE,
                            addressV: ADDRESS_CLAMP_TO_EDGE,
                            // generate cubemaps on the top level only
                            mipmaps: i === 0
                        });
                    }
                } else {
                    // prefiltered data is an env atlas
                    resources[1] = tex;
                }
            }
        } else {
            // prelit asset didn't change so keep the existing cubemap resources
            resources[1] = oldResources[1] || null;
            resources[2] = oldResources[2] || null;
            resources[3] = oldResources[3] || null;
            resources[4] = oldResources[4] || null;
            resources[5] = oldResources[5] || null;
            resources[6] = oldResources[6] || null;
        }
        const faceAssets = assets.slice(1);
        if (!cubemapAsset.loaded || !this.cmpArrays(faceAssets, oldAssets.slice(1))) {
            // face assets have changed
            if (faceAssets.indexOf(null) === -1) {
                // extract cubemap level data from face textures
                const faceTextures = faceAssets.map((asset)=>{
                    return asset.resource;
                });
                const faceLevels = [];
                for(mip = 0; mip < faceTextures[0]._levels.length; ++mip){
                    faceLevels.push(faceTextures.map((faceTexture)=>{
                        return faceTexture._levels[mip];
                    }));
                }
                // Force RGBA8 if we are loading a RGB8 texture due to a bug on M1 Macs Monterey and Chrome not
                // rendering the face on right of the cubemap (`faceAssets[0]` and `resources[1]`).
                // Using a RGBA8 texture works around the issue https://github.com/playcanvas/engine/issues/4091
                const format = faceTextures[0].format;
                const faces = new Texture(this._device, {
                    name: `${cubemapAsset.name}_faces`,
                    cubemap: true,
                    type: getType() || faceTextures[0].type,
                    width: faceTextures[0].width,
                    height: faceTextures[0].height,
                    format: format === PIXELFORMAT_RGB8 ? PIXELFORMAT_RGBA8 : format,
                    mipmaps: assetData.mipmaps ?? true,
                    levels: faceLevels,
                    minFilter: assetData.hasOwnProperty('minFilter') ? assetData.minFilter : faceTextures[0].minFilter,
                    magFilter: assetData.hasOwnProperty('magFilter') ? assetData.magFilter : faceTextures[0].magFilter,
                    anisotropy: assetData.hasOwnProperty('anisotropy') ? assetData.anisotropy : 1,
                    addressU: ADDRESS_CLAMP_TO_EDGE,
                    addressV: ADDRESS_CLAMP_TO_EDGE
                });
                resources[0] = faces;
            }
        } else {
            // no faces changed so keep existing faces cubemap
            resources[0] = oldResources[0] || null;
        }
        // check if any resource changed
        if (!this.cmpArrays(resources, oldResources)) {
            // set the new resources, change events will fire
            cubemapAsset.resources = resources;
            cubemapAsset._handlerState.assetIds = assetIds;
            cubemapAsset._handlerState.assets = assets;
            // destroy the old cubemap resources that are not longer needed
            for(i = 0; i < oldResources.length; ++i){
                if (oldResources[i] !== null && resources.indexOf(oldResources[i]) === -1) {
                    oldResources[i].destroy();
                }
            }
        }
        // destroy old assets which have been replaced
        for(i = 0; i < oldAssets.length; ++i){
            if (oldAssets[i] !== null && assets.indexOf(oldAssets[i]) === -1) {
                oldAssets[i].unload();
            }
        }
    }
    cmpArrays(arr1, arr2) {
        if (arr1.length !== arr2.length) {
            return false;
        }
        for(let i = 0; i < arr1.length; ++i){
            if (arr1[i] !== arr2[i]) {
                return false;
            }
        }
        return true;
    }
    // convert string id to int
    resolveId(value) {
        const valueInt = parseInt(value, 10);
        return valueInt === value || valueInt.toString() === value ? valueInt : value;
    }
    loadAssets(cubemapAsset, callback) {
        // initialize asset structures for tracking load requests
        if (!cubemapAsset.hasOwnProperty('_handlerState')) {
            cubemapAsset._handlerState = {
                // the list of requested asset ids in order of [prelit cubemap, 6 faces]
                assetIds: [
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
                ],
                // the dependent (loaded, active) texture assets
                assets: [
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
                ]
            };
        }
        const self = this;
        const assetIds = self.getAssetIds(cubemapAsset);
        const assets = [
            null,
            null,
            null,
            null,
            null,
            null,
            null
        ];
        const loadedAssetIds = cubemapAsset._handlerState.assetIds;
        const loadedAssets = cubemapAsset._handlerState.assets;
        const registry = self._registry;
        // one of the dependent assets has finished loading
        let awaiting = 7;
        const onLoad = function(index, asset) {
            assets[index] = asset;
            awaiting--;
            if (awaiting === 0) {
                // all dependent assets are finished loading, set them as the active resources
                self.update(cubemapAsset, assetIds, assets);
                callback(null, cubemapAsset.resources);
            }
        };
        // handle an asset load failure
        const onError = function(index, err, asset) {
            callback(err);
        };
        // process the texture asset
        const processTexAsset = function(index, texAsset) {
            if (texAsset.loaded) {
                // asset already exists
                onLoad(index, texAsset);
            } else {
                // asset is not loaded, register for load and error events
                registry.once(`load:${texAsset.id}`, onLoad.bind(self, index));
                registry.once(`error:${texAsset.id}`, onError.bind(self, index));
                if (!texAsset.loading) {
                    // kick off load if it's not already
                    registry.load(texAsset);
                }
            }
        };
        let texAsset;
        for(let i = 0; i < 7; ++i){
            const assetId = this.resolveId(assetIds[i]);
            if (!assetId) {
                // no asset
                onLoad(i, null);
            } else if (self.compareAssetIds(assetId, loadedAssetIds[i])) {
                // asset id hasn't changed from what is currently set
                processTexAsset(i, loadedAssets[i]);
            } else if (parseInt(assetId, 10) === assetId) {
                // assetId is an asset id
                texAsset = registry.get(assetId);
                if (texAsset) {
                    processTexAsset(i, texAsset);
                } else {
                    // if we are unable to find the dependent asset, then we introduce here an
                    // asynchronous step. this gives the caller (for example the scene loader)
                    // a chance to add the dependent scene texture to registry before we attempt
                    // to get the asset again.
                    setTimeout(((index, assetId_)=>{
                        const texAsset = registry.get(assetId_);
                        if (texAsset) {
                            processTexAsset(index, texAsset);
                        } else {
                            onError(index, `failed to find dependent cubemap asset=${assetId_}`);
                        }
                    }).bind(null, i, assetId));
                }
            } else {
                // assetId is a url or file object and we're responsible for creating it
                const file = typeof assetId === 'string' ? {
                    url: assetId,
                    filename: assetId
                } : assetId;
                // if the referenced prefiltered texture is not a dds file, then we're loading an
                // envAtlas. In this case we must specify the correct texture state.
                const data = file.url.search('.dds') === -1 ? {
                    type: 'rgbp',
                    addressu: 'clamp',
                    addressv: 'clamp',
                    mipmaps: false
                } : null;
                texAsset = new Asset(`${cubemapAsset.name}_part_${i}`, 'texture', file, data);
                registry.add(texAsset);
                processTexAsset(i, texAsset);
            }
        }
    }
}

/**
 * @import { BoundingBox } from '../../core/shape/bounding-box.js'
 */ const SH_C0$1 = 0.28209479177387814;
// iterator for accessing compressed splat data
class SplatCompressedIterator {
    constructor(gsplatData, p, r, s, c, sh){
        const unpackUnorm = (value, bits)=>{
            const t = (1 << bits) - 1;
            return (value & t) / t;
        };
        const unpack111011 = (result, value)=>{
            result.x = unpackUnorm(value >>> 21, 11);
            result.y = unpackUnorm(value >>> 11, 10);
            result.z = unpackUnorm(value, 11);
        };
        const unpack8888 = (result, value)=>{
            result.x = unpackUnorm(value >>> 24, 8);
            result.y = unpackUnorm(value >>> 16, 8);
            result.z = unpackUnorm(value >>> 8, 8);
            result.w = unpackUnorm(value, 8);
        };
        // unpack quaternion with 2,10,10,10 format (largest element, 3x10bit element)
        const unpackRot = (result, value)=>{
            const norm = 1.0 / (Math.sqrt(2) * 0.5);
            const a = (unpackUnorm(value >>> 20, 10) - 0.5) * norm;
            const b = (unpackUnorm(value >>> 10, 10) - 0.5) * norm;
            const c = (unpackUnorm(value, 10) - 0.5) * norm;
            const m = Math.sqrt(1.0 - (a * a + b * b + c * c));
            switch(value >>> 30){
                case 0:
                    result.set(a, b, c, m);
                    break;
                case 1:
                    result.set(m, b, c, a);
                    break;
                case 2:
                    result.set(b, m, c, a);
                    break;
                case 3:
                    result.set(b, c, m, a);
                    break;
            }
        };
        const lerp = (a, b, t)=>a * (1 - t) + b * t;
        const { chunkData, chunkSize, vertexData, shData0, shData1, shData2, shBands } = gsplatData;
        const shCoeffs = [
            3,
            8,
            15
        ][shBands - 1];
        this.read = (i)=>{
            const ci = Math.floor(i / 256) * chunkSize;
            if (p) {
                unpack111011(p, vertexData[i * 4 + 0]);
                p.x = lerp(chunkData[ci + 0], chunkData[ci + 3], p.x);
                p.y = lerp(chunkData[ci + 1], chunkData[ci + 4], p.y);
                p.z = lerp(chunkData[ci + 2], chunkData[ci + 5], p.z);
            }
            if (r) {
                unpackRot(r, vertexData[i * 4 + 1]);
            }
            if (s) {
                unpack111011(s, vertexData[i * 4 + 2]);
                s.x = lerp(chunkData[ci + 6], chunkData[ci + 9], s.x);
                s.y = lerp(chunkData[ci + 7], chunkData[ci + 10], s.y);
                s.z = lerp(chunkData[ci + 8], chunkData[ci + 11], s.z);
            }
            if (c) {
                unpack8888(c, vertexData[i * 4 + 3]);
                if (chunkSize > 12) {
                    c.x = lerp(chunkData[ci + 12], chunkData[ci + 15], c.x);
                    c.y = lerp(chunkData[ci + 13], chunkData[ci + 16], c.y);
                    c.z = lerp(chunkData[ci + 14], chunkData[ci + 17], c.z);
                }
            }
            if (sh && shBands > 0) {
                const shData = [
                    shData0,
                    shData1,
                    shData2
                ];
                for(let j = 0; j < 3; ++j){
                    for(let k = 0; k < 15; ++k){
                        sh[j * 15 + k] = k < shCoeffs ? shData[j][i * 16 + k] * (8 / 255) - 4 : 0;
                    }
                }
            }
        };
    }
}
class GSplatCompressedData {
    /**
     * Create an iterator for accessing splat data
     *
     * @param {Vec3|null} [p] - the vector to receive splat position
     * @param {Quat|null} [r] - the quaternion to receive splat rotation
     * @param {Vec3|null} [s] - the vector to receive splat scale
     * @param {Vec4|null} [c] - the vector to receive splat color
     * @param {Float32Array|null} [sh] - the array to receive spherical harmonics data
     * @returns {SplatCompressedIterator} - The iterator
     */ createIter(p, r, s, c, sh) {
        return new SplatCompressedIterator(this, p, r, s, c, sh);
    }
    /**
     * Calculate pessimistic scene aabb taking into account splat size. This is faster than
     * calculating an exact aabb.
     *
     * @param {BoundingBox} result - Where to store the resulting bounding box.
     * @returns {boolean} - Whether the calculation was successful.
     */ calcAabb(result) {
        const { chunkData, numChunks, chunkSize } = this;
        let s = Math.exp(Math.max(chunkData[9], chunkData[10], chunkData[11]));
        let mx = chunkData[0] - s;
        let my = chunkData[1] - s;
        let mz = chunkData[2] - s;
        let Mx = chunkData[3] + s;
        let My = chunkData[4] + s;
        let Mz = chunkData[5] + s;
        for(let i = 1; i < numChunks; ++i){
            const off = i * chunkSize;
            s = Math.exp(Math.max(chunkData[off + 9], chunkData[off + 10], chunkData[off + 11]));
            mx = Math.min(mx, chunkData[off + 0] - s);
            my = Math.min(my, chunkData[off + 1] - s);
            mz = Math.min(mz, chunkData[off + 2] - s);
            Mx = Math.max(Mx, chunkData[off + 3] + s);
            My = Math.max(My, chunkData[off + 4] + s);
            Mz = Math.max(Mz, chunkData[off + 5] + s);
        }
        result.center.set((mx + Mx) * 0.5, (my + My) * 0.5, (mz + Mz) * 0.5);
        result.halfExtents.set((Mx - mx) * 0.5, (My - my) * 0.5, (Mz - mz) * 0.5);
        return true;
    }
    /**
     * @param {Float32Array} result - Array containing the centers.
     */ getCenters(result) {
        const { vertexData, chunkData, numChunks, chunkSize } = this;
        let mx, my, mz, Mx, My, Mz;
        for(let c = 0; c < numChunks; ++c){
            const off = c * chunkSize;
            mx = chunkData[off + 0];
            my = chunkData[off + 1];
            mz = chunkData[off + 2];
            Mx = chunkData[off + 3];
            My = chunkData[off + 4];
            Mz = chunkData[off + 5];
            const end = Math.min(this.numSplats, (c + 1) * 256);
            for(let i = c * 256; i < end; ++i){
                const p = vertexData[i * 4];
                const px = (p >>> 21) / 2047;
                const py = (p >>> 11 & 0x3ff) / 1023;
                const pz = (p & 0x7ff) / 2047;
                result[i * 3 + 0] = (1 - px) * mx + px * Mx;
                result[i * 3 + 1] = (1 - py) * my + py * My;
                result[i * 3 + 2] = (1 - pz) * mz + pz * Mz;
            }
        }
    }
    getChunks(result) {
        const { chunkData, numChunks, chunkSize } = this;
        let mx, my, mz, Mx, My, Mz;
        for(let c = 0; c < numChunks; ++c){
            const off = c * chunkSize;
            mx = chunkData[off + 0];
            my = chunkData[off + 1];
            mz = chunkData[off + 2];
            Mx = chunkData[off + 3];
            My = chunkData[off + 4];
            Mz = chunkData[off + 5];
            result[c * 6 + 0] = mx;
            result[c * 6 + 1] = my;
            result[c * 6 + 2] = mz;
            result[c * 6 + 3] = Mx;
            result[c * 6 + 4] = My;
            result[c * 6 + 5] = Mz;
        }
    }
    /**
     * @param {Vec3} result - The result.
     */ calcFocalPoint(result) {
        const { chunkData, numChunks, chunkSize } = this;
        result.x = 0;
        result.y = 0;
        result.z = 0;
        for(let i = 0; i < numChunks; ++i){
            const off = i * chunkSize;
            result.x += chunkData[off + 0] + chunkData[off + 3];
            result.y += chunkData[off + 1] + chunkData[off + 4];
            result.z += chunkData[off + 2] + chunkData[off + 5];
        }
        result.mulScalar(0.5 / numChunks);
    }
    get isCompressed() {
        return true;
    }
    get numChunks() {
        return Math.ceil(this.numSplats / 256);
    }
    get chunkSize() {
        return this.chunkData.length / this.numChunks;
    }
    // decompress into GSplatData
    decompress() {
        const members = [
            'x',
            'y',
            'z',
            'f_dc_0',
            'f_dc_1',
            'f_dc_2',
            'opacity',
            'scale_0',
            'scale_1',
            'scale_2',
            'rot_0',
            'rot_1',
            'rot_2',
            'rot_3'
        ];
        const { shBands } = this;
        // allocate spherical harmonics data
        if (shBands > 0) {
            const shMembers = [];
            for(let i = 0; i < 45; ++i){
                shMembers.push(`f_rest_${i}`);
            }
            const location = Math.max(...[
                'f_dc_0',
                'f_dc_1',
                'f_dc_2'
            ].map((name)=>members.indexOf(name)));
            members.splice(location + 1, 0, ...shMembers);
        }
        // allocate uncompressed data
        const data = {};
        members.forEach((name)=>{
            data[name] = new Float32Array(this.numSplats);
        });
        const p = new Vec3();
        const r = new Quat();
        const s = new Vec3();
        const c = new Vec4();
        const sh = shBands > 0 ? new Float32Array(45) : null;
        const iter = this.createIter(p, r, s, c, sh);
        for(let i = 0; i < this.numSplats; ++i){
            iter.read(i);
            data.x[i] = p.x;
            data.y[i] = p.y;
            data.z[i] = p.z;
            data.rot_1[i] = r.x;
            data.rot_2[i] = r.y;
            data.rot_3[i] = r.z;
            data.rot_0[i] = r.w;
            data.scale_0[i] = s.x;
            data.scale_1[i] = s.y;
            data.scale_2[i] = s.z;
            data.f_dc_0[i] = (c.x - 0.5) / SH_C0$1;
            data.f_dc_1[i] = (c.y - 0.5) / SH_C0$1;
            data.f_dc_2[i] = (c.z - 0.5) / SH_C0$1;
            // convert opacity to log sigmoid taking into account infinities at 0 and 1
            data.opacity[i] = c.w <= 0 ? -40 : c.w >= 1 ? 40 : -Math.log(1 / c.w - 1);
            if (sh) {
                for(let c = 0; c < 45; ++c){
                    data[`f_rest_${c}`][i] = sh[c];
                }
            }
        }
        return new GSplatData([
            {
                name: 'vertex',
                count: this.numSplats,
                properties: members.map((name)=>{
                    return {
                        name: name,
                        type: 'float',
                        byteSize: 4,
                        storage: data[name]
                    };
                })
            }
        ], this.comments);
    }
}

/**
 * @import { GSplatCompressedData } from './gsplat-compressed-data.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { Texture } from '../../platform/graphics/texture.js';
 */ // copy data with padding
const strideCopy = (target, targetStride, src, srcStride, numEntries)=>{
    for(let i = 0; i < numEntries; ++i){
        for(let j = 0; j < srcStride; ++j){
            target[i * targetStride + j] = src[i * srcStride + j];
        }
    }
};
class GSplatCompressedResource extends GSplatResourceBase {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {GSplatCompressedData} gsplatData - The splat data.
     */ constructor(device, gsplatData){
        super(device, gsplatData);
        const { chunkData, chunkSize, numChunks, numSplats, vertexData, shBands } = gsplatData;
        this.chunks = new Float32Array(numChunks * 6);
        gsplatData.getChunks(this.chunks);
        // initialize packed data
        this.packedTexture = this.createTexture('packedData', PIXELFORMAT_RGBA32U, this.evalTextureSize(numSplats), vertexData);
        // initialize chunk data
        const chunkTextureSize = this.evalTextureSize(numChunks);
        chunkTextureSize.x *= 5;
        this.chunkTexture = this.createTexture('chunkData', PIXELFORMAT_RGBA32F, chunkTextureSize);
        const chunkTextureData = this.chunkTexture.lock();
        strideCopy(chunkTextureData, 20, chunkData, chunkSize, numChunks);
        if (chunkSize === 12) {
            // if the chunks don't contain color min/max values we must update max to 1 (min is filled with 0's)
            for(let i = 0; i < numChunks; ++i){
                chunkTextureData[i * 20 + 15] = 1;
                chunkTextureData[i * 20 + 16] = 1;
                chunkTextureData[i * 20 + 17] = 1;
            }
        }
        this.chunkTexture.unlock();
        // load optional spherical harmonics data
        if (shBands > 0) {
            const size = this.evalTextureSize(numSplats);
            this.shTexture0 = this.createTexture('shTexture0', PIXELFORMAT_RGBA32U, size, new Uint32Array(gsplatData.shData0.buffer));
            this.shTexture1 = this.createTexture('shTexture1', PIXELFORMAT_RGBA32U, size, new Uint32Array(gsplatData.shData1.buffer));
            this.shTexture2 = this.createTexture('shTexture2', PIXELFORMAT_RGBA32U, size, new Uint32Array(gsplatData.shData2.buffer));
        } else {
            this.shTexture0 = null;
            this.shTexture1 = null;
            this.shTexture2 = null;
        }
    }
    destroy() {
        this.packedTexture?.destroy();
        this.chunkTexture?.destroy();
        this.shTexture0?.destroy();
        this.shTexture1?.destroy();
        this.shTexture2?.destroy();
        super.destroy();
    }
    configureMaterialDefines(defines) {
        defines.set('GSPLAT_COMPRESSED_DATA', true);
        defines.set('SH_BANDS', this.shTexture0 ? 3 : 0);
    }
    configureMaterial(material) {
        this.configureMaterialDefines(material.defines);
        material.setParameter('packedTexture', this.packedTexture);
        material.setParameter('chunkTexture', this.chunkTexture);
        if (this.shTexture0) {
            material.setParameter('shTexture0', this.shTexture0);
            material.setParameter('shTexture1', this.shTexture1);
            material.setParameter('shTexture2', this.shTexture2);
        }
    }
    /**
     * Evaluates the texture size needed to store a given number of elements.
     * The function calculates a width and height that is close to a square
     * that can contain 'count' elements.
     *
     * @param {number} count - The number of elements to store in the texture.
     * @returns {Vec2} The width and height of the texture.
     */ evalTextureSize(count) {
        const width = Math.ceil(Math.sqrt(count));
        const height = Math.ceil(count / width);
        return new Vec2(width, height);
    }
}

/**
 * @import { Asset } from '../asset/asset.js'
 * @import { AppBase } from '../app-base.js'
 * @import { ResourceHandlerCallback } from '../handlers/handler.js'
 */ /**
 * @typedef {Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} DataType
 */ /**
 * @typedef {object} PlyProperty
 * @property {string} type - E.g. 'float'.
 * @property {string} name - E.g. 'x', 'y', 'z', 'f_dc_0' etc.
 * @property {DataType} storage - Data type, e.g. instance of Float32Array.
 * @property {number} byteSize - BYTES_PER_ELEMENT of given data type.
 */ /**
 * @typedef {object} PlyElement
 * @property {string} name - E.g. 'vertex'.
 * @property {number} count - Given count.
 * @property {PlyProperty[]} properties - The properties.
 */ const magicBytes = new Uint8Array([
    112,
    108,
    121,
    10
]); // ply\n
const endHeaderBytes = new Uint8Array([
    10,
    101,
    110,
    100,
    95,
    104,
    101,
    97,
    100,
    101,
    114,
    10
]); // \nend_header\n
const dataTypeMap = new Map([
    [
        'char',
        Int8Array
    ],
    [
        'uchar',
        Uint8Array
    ],
    [
        'short',
        Int16Array
    ],
    [
        'ushort',
        Uint16Array
    ],
    [
        'int',
        Int32Array
    ],
    [
        'uint',
        Uint32Array
    ],
    [
        'float',
        Float32Array
    ],
    [
        'double',
        Float64Array
    ]
]);
// helper for streaming in chunks of data in a memory efficient way
class StreamBuf {
    constructor(reader, progressFunc){
        this.head = 0;
        this.tail = 0;
        this.reader = reader;
        this.progressFunc = progressFunc;
    }
    // read the next chunk of data
    async read() {
        const { value, done } = await this.reader.read();
        if (done) {
            throw new Error('Stream finished before end of header');
        }
        this.push(value);
        this.progressFunc?.(value.byteLength);
    }
    // append data to the buffer
    push(data) {
        if (!this.data) {
            // first buffer
            this.data = data;
            this.view = new DataView(this.data.buffer);
            this.tail = data.length;
        } else {
            const remaining = this.tail - this.head;
            const newSize = remaining + data.length;
            if (this.data.length >= newSize) {
                // buffer is large enough to contain combined data
                if (this.head > 0) {
                    // shuffle existing data to index 0 and append the new data
                    this.data.copyWithin(0, this.head, this.tail);
                    this.data.set(data, remaining);
                    this.head = 0;
                    this.tail = newSize;
                } else {
                    // no shuffle needed, just append new data
                    this.data.set(data, this.tail);
                    this.tail += data.length;
                }
            } else {
                // buffer is too small and must grow
                const tmp = new Uint8Array(newSize);
                if (this.head > 0 || this.tail < this.data.length) {
                    // shuffle existing data to index 0 and append the new data
                    tmp.set(this.data.subarray(this.head, this.tail), 0);
                } else {
                    tmp.set(this.data, 0);
                }
                tmp.set(data, remaining);
                this.data = tmp;
                this.view = new DataView(this.data.buffer);
                this.head = 0;
                this.tail = newSize;
            }
        }
    }
    // remove the read data from the head of the buffer
    compact() {
        if (this.head > 0) {
            this.data.copyWithin(0, this.head, this.tail);
            this.tail -= this.head;
            this.head = 0;
        }
    }
    get remaining() {
        return this.tail - this.head;
    }
    // helpers for extracting data from head
    getInt8() {
        const result = this.view.getInt8(this.head);
        this.head++;
        return result;
    }
    getUint8() {
        const result = this.view.getUint8(this.head);
        this.head++;
        return result;
    }
    getInt16() {
        const result = this.view.getInt16(this.head, true);
        this.head += 2;
        return result;
    }
    getUint16() {
        const result = this.view.getUint16(this.head, true);
        this.head += 2;
        return result;
    }
    getInt32() {
        const result = this.view.getInt32(this.head, true);
        this.head += 4;
        return result;
    }
    getUint32() {
        const result = this.view.getUint32(this.head, true);
        this.head += 4;
        return result;
    }
    getFloat32() {
        const result = this.view.getFloat32(this.head, true);
        this.head += 4;
        return result;
    }
    getFloat64() {
        const result = this.view.getFloat64(this.head, true);
        this.head += 8;
        return result;
    }
}
// parse the ply header text and return an array of Element structures and a
// string containing the ply format
const parseHeader = (lines)=>{
    const elements = [];
    const comments = [];
    let format;
    for(let i = 1; i < lines.length; ++i){
        const words = lines[i].split(' ');
        switch(words[0]){
            case 'comment':
                comments.push(words.slice(1).join(' '));
                break;
            case 'format':
                format = words[1];
                break;
            case 'element':
                elements.push({
                    name: words[1],
                    count: parseInt(words[2], 10),
                    properties: []
                });
                break;
            case 'property':
                {
                    if (!dataTypeMap.has(words[1])) {
                        throw new Error(`Unrecognized property data type '${words[1]}' in ply header`);
                    }
                    const element = elements[elements.length - 1];
                    element.properties.push({
                        type: words[1],
                        name: words[2],
                        storage: null,
                        byteSize: dataTypeMap.get(words[1]).BYTES_PER_ELEMENT
                    });
                    break;
                }
            default:
                throw new Error(`Unrecognized header value '${words[0]}' in ply header`);
        }
    }
    return {
        elements,
        format,
        comments
    };
};
// return true if the array of elements references a compressed ply file
const isCompressedPly = (elements)=>{
    const chunkProperties = [
        'min_x',
        'min_y',
        'min_z',
        'max_x',
        'max_y',
        'max_z',
        'min_scale_x',
        'min_scale_y',
        'min_scale_z',
        'max_scale_x',
        'max_scale_y',
        'max_scale_z',
        'min_r',
        'min_g',
        'min_b',
        'max_r',
        'max_g',
        'max_b'
    ];
    const vertexProperties = [
        'packed_position',
        'packed_rotation',
        'packed_scale',
        'packed_color'
    ];
    const shProperties = new Array(45).fill('').map((_, i)=>`f_rest_${i}`);
    const hasBaseElements = ()=>{
        return elements[0].name === 'chunk' && elements[0].properties.every((p, i)=>p.name === chunkProperties[i] && p.type === 'float') && elements[1].name === 'vertex' && elements[1].properties.every((p, i)=>p.name === vertexProperties[i] && p.type === 'uint');
    };
    const hasSHElements = ()=>{
        return elements[2].name === 'sh' && [
            9,
            24,
            45
        ].indexOf(elements[2].properties.length) !== -1 && elements[2].properties.every((p, i)=>p.name === shProperties[i] && p.type === 'uchar');
    };
    return elements.length === 2 && hasBaseElements() || elements.length === 3 && hasBaseElements() && hasSHElements();
};
const isFloatPly = (elements)=>{
    return elements.length === 1 && elements[0].name === 'vertex' && elements[0].properties.every((p)=>p.type === 'float');
};
// read the data of a compressed ply file
const readCompressedPly = async (streamBuf, elements, comments)=>{
    const result = new GSplatCompressedData();
    result.comments = comments;
    const numChunks = elements[0].count;
    const numChunkProperties = elements[0].properties.length;
    const numVertices = elements[1].count;
    // evaluate the storage size for the given count (this must match the
    // texture size calculation in GSplatCompressed).
    const evalStorageSize = (count)=>{
        const width = Math.ceil(Math.sqrt(count));
        const height = Math.ceil(count / width);
        return width * height;
    };
    const storageSize = evalStorageSize(numVertices);
    // allocate result
    result.numSplats = numVertices;
    result.chunkData = new Float32Array(numChunks * numChunkProperties);
    result.vertexData = new Uint32Array(storageSize * 4);
    // read length bytes of data into buffer
    const read = async (buffer, length)=>{
        const target = new Uint8Array(buffer);
        let cursor = 0;
        while(cursor < length){
            while(streamBuf.remaining === 0){
                /* eslint-disable no-await-in-loop */ await streamBuf.read();
            }
            const toCopy = Math.min(length - cursor, streamBuf.remaining);
            const src = streamBuf.data;
            for(let i = 0; i < toCopy; ++i){
                target[cursor++] = src[streamBuf.head++];
            }
        }
    };
    // read chunk data
    await read(result.chunkData.buffer, numChunks * numChunkProperties * 4);
    // read packed vertices
    await read(result.vertexData.buffer, numVertices * 4 * 4);
    // read sh data
    if (elements.length === 3) {
        // allocate memory for 48 coefficients per gaussian
        const texStorageSize = storageSize * 16; // RGBA32U per texel
        const shData0 = new Uint8Array(texStorageSize);
        const shData1 = new Uint8Array(texStorageSize);
        const shData2 = new Uint8Array(texStorageSize);
        // the file contains 1, 2 or 3 bands of SH data (with 9, 24 or 45 coefficients respectively)
        // we must load the data and pad it for GPU
        const chunkSize = 1024;
        const srcCoeffs = elements[2].properties.length / 3;
        const tmpBuf = new Uint8Array(chunkSize * srcCoeffs * 3);
        // read in chunks of 1k gaussians and write to the padded texture data
        for(let i = 0; i < result.numSplats; i += chunkSize){
            const toRead = Math.min(chunkSize, result.numSplats - i);
            // read the next chunk of data
            await read(tmpBuf.buffer, toRead * srcCoeffs * 3);
            // pad the data
            for(let j = 0; j < toRead; ++j){
                for(let k = 0; k < 15; ++k){
                    const tidx = (i + j) * 16 + k;
                    if (k < srcCoeffs) {
                        shData0[tidx] = tmpBuf[(j * 3 + 0) * srcCoeffs + k];
                        shData1[tidx] = tmpBuf[(j * 3 + 1) * srcCoeffs + k];
                        shData2[tidx] = tmpBuf[(j * 3 + 2) * srcCoeffs + k];
                    } else {
                        shData0[tidx] = 127;
                        shData1[tidx] = 127;
                        shData2[tidx] = 127;
                    }
                }
            }
        }
        result.shData0 = shData0;
        result.shData1 = shData1;
        result.shData2 = shData2;
        result.shBands = ({
            3: 1,
            8: 2,
            15: 3
        })[srcCoeffs];
    } else {
        result.shBands = 0;
    }
    return result;
};
// read the data of a floating point ply file
const readFloatPly = async (streamBuf, elements, comments)=>{
    // calculate the size of an input element record
    const element = elements[0];
    const properties = element.properties;
    const numProperties = properties.length;
    const storage = properties.map((p)=>p.storage);
    const inputSize = properties.reduce((a, p)=>a + p.byteSize, 0);
    let vertexIdx = 0;
    let floatData;
    const checkFloatData = ()=>{
        const buffer = streamBuf.data.buffer;
        if (floatData?.buffer !== buffer) {
            floatData = new Float32Array(buffer, 0, buffer.byteLength / 4);
        }
    };
    checkFloatData();
    while(vertexIdx < element.count){
        while(streamBuf.remaining < inputSize){
            /* eslint-disable no-await-in-loop */ await streamBuf.read();
            checkFloatData();
        }
        const toRead = Math.min(element.count - vertexIdx, Math.floor(streamBuf.remaining / inputSize));
        for(let j = 0; j < numProperties; ++j){
            const s = storage[j];
            for(let n = 0; n < toRead; ++n){
                s[n + vertexIdx] = floatData[n * numProperties + j];
            }
        }
        vertexIdx += toRead;
        streamBuf.head += toRead * inputSize;
    }
    return new GSplatData(elements, comments);
};
const readGeneralPly = async (streamBuf, elements, comments)=>{
    // read and deinterleave the data
    for(let i = 0; i < elements.length; ++i){
        const element = elements[i];
        // calculate the size of an input element record
        const inputSize = element.properties.reduce((a, p)=>a + p.byteSize, 0);
        const propertyParsingFunctions = element.properties.map((p)=>{
            /* eslint-disable brace-style */ if (p.storage) {
                switch(p.type){
                    case 'char':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getInt8();
                        };
                    case 'uchar':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getUint8();
                        };
                    case 'short':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getInt16();
                        };
                    case 'ushort':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getUint16();
                        };
                    case 'int':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getInt32();
                        };
                    case 'uint':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getUint32();
                        };
                    case 'float':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getFloat32();
                        };
                    case 'double':
                        return (streamBuf, c)=>{
                            p.storage[c] = streamBuf.getFloat64();
                        };
                    default:
                        throw new Error(`Unsupported property data type '${p.type}' in ply header`);
                }
            } else {
                return (streamBuf)=>{
                    streamBuf.head += p.byteSize;
                };
            }
        /* eslint-enable brace-style */ });
        let c = 0;
        while(c < element.count){
            while(streamBuf.remaining < inputSize){
                /* eslint-disable no-await-in-loop */ await streamBuf.read();
            }
            const toRead = Math.min(element.count - c, Math.floor(streamBuf.remaining / inputSize));
            for(let n = 0; n < toRead; ++n){
                for(let j = 0; j < element.properties.length; ++j){
                    propertyParsingFunctions[j](streamBuf, c);
                }
                c++;
            }
        }
    }
    // console.log(elements);
    return new GSplatData(elements, comments);
};
/**
 * asynchronously read a ply file data
 *
 * @param {ReadableStreamDefaultReader<Uint8Array>} reader - The reader.
 * @param {Function|null} propertyFilter - Function to filter properties with.
 * @param {Function|null} progressFunc - Function to call with progress updates.
 * @returns {Promise<GSplatData | GSplatCompressedData>} The ply file data.
 */ const readPly = async (reader, propertyFilter = null, progressFunc = null)=>{
    /**
     * Searches for the first occurrence of a sequence within a buffer.
     * @example
     * find(new Uint8Array([1, 2, 3, 4]), new Uint8Array([3, 4])); // 2
     * @param {Uint8Array} buf - The buffer in which to search.
     * @param {Uint8Array} search - The sequence to search for.
     * @returns {number} The index of the first occurrence of the search sequence in the buffer, or -1 if not found.
     */ const find = (buf, search)=>{
        const endIndex = buf.length - search.length;
        let i, j;
        for(i = 0; i <= endIndex; ++i){
            for(j = 0; j < search.length; ++j){
                if (buf[i + j] !== search[j]) {
                    break;
                }
            }
            if (j === search.length) {
                return i;
            }
        }
        return -1;
    };
    /**
     * Checks if array 'a' starts with the same elements as array 'b'.
     * @example
     * startsWith(new Uint8Array([1, 2, 3, 4]), new Uint8Array([1, 2])); // true
     * @param {Uint8Array} a - The array to check against.
     * @param {Uint8Array} b - The array of elements to look for at the start of 'a'.
     * @returns {boolean} - True if 'a' starts with all elements of 'b', otherwise false.
     */ const startsWith = (a, b)=>{
        if (a.length < b.length) {
            return false;
        }
        for(let i = 0; i < b.length; ++i){
            if (a[i] !== b[i]) {
                return false;
            }
        }
        return true;
    };
    const streamBuf = new StreamBuf(reader, progressFunc);
    let headerLength;
    while(true){
        // get the next chunk of data
        /* eslint-disable no-await-in-loop */ await streamBuf.read();
        // check magic bytes
        if (streamBuf.tail >= magicBytes.length && !startsWith(streamBuf.data, magicBytes)) {
            throw new Error('Invalid ply header');
        }
        // search for end-of-header marker
        headerLength = find(streamBuf.data, endHeaderBytes);
        if (headerLength !== -1) {
            break;
        }
    }
    // decode buffer header text and split into lines and remove comments
    const lines = new TextDecoder('ascii').decode(streamBuf.data.subarray(0, headerLength)).split('\n');
    // decode header and build element and property list
    const { elements, format, comments } = parseHeader(lines);
    // check format is supported
    if (format !== 'binary_little_endian') {
        throw new Error('Unsupported ply format');
    }
    // skip past header and compact the chunk data so the read operations
    // fall nicely on aligned data boundaries
    streamBuf.head = headerLength + endHeaderBytes.length;
    streamBuf.compact();
    const readData = async ()=>{
        // load compressed PLY with fast path
        if (isCompressedPly(elements)) {
            return await readCompressedPly(streamBuf, elements, comments);
        }
        // allocate element storage
        elements.forEach((e)=>{
            e.properties.forEach((p)=>{
                const storageType = dataTypeMap.get(p.type);
                if (storageType) {
                    const storage = !propertyFilter || propertyFilter(p.name) ? new storageType(e.count) : null;
                    p.storage = storage;
                }
            });
        });
        // load float32 PLY with fast path
        if (isFloatPly(elements)) {
            return await readFloatPly(streamBuf, elements, comments);
        }
        // fallback, general case
        return await readGeneralPly(streamBuf, elements, comments);
    };
    return await readData();
};
// by default load everything
const defaultElementFilter = (val)=>true;
class PlyParser {
    /**
     * @param {AppBase} app - The app instance.
     * @param {number} maxRetries - Maximum amount of retries.
     */ constructor(app, maxRetries){
        this.app = app;
        this.maxRetries = maxRetries;
    }
    /**
     * @param {object} url - The URL of the resource to load.
     * @param {string} url.load - The URL to use for loading the resource.
     * @param {string} url.original - The original URL useful for identifying the resource type.
     * @param {ResourceHandlerCallback} callback - The callback used when
     * the resource is loaded or an error occurs.
     * @param {Asset} asset - Container asset.
     */ async load(url, callback, asset) {
        try {
            // either use the fetch request passed in by the application or initiate it ourselves
            const response = await (asset.file?.contents ?? fetch(url.load));
            if (!response || !response.body) {
                callback('Error loading resource', null);
            } else {
                const totalLength = parseInt(response.headers.get('content-length') ?? '0', 10);
                let totalReceived = 0;
                const data = await readPly(response.body.getReader(), asset.data.elementFilter ?? defaultElementFilter, (bytes)=>{
                    totalReceived += bytes;
                    if (asset) {
                        asset.fire('progress', totalReceived, totalLength);
                    }
                });
                // allow application to process the data
                asset.fire('load:data', data);
                // reorder data
                if (!data.isCompressed) {
                    if (asset.data.reorder ?? true) {
                        data.reorderData();
                    }
                }
                // construct the resource
                const resource = data.isCompressed && !asset.data.decompress ? new GSplatCompressedResource(this.app.graphicsDevice, data) : new GSplatResource(this.app.graphicsDevice, data.isCompressed ? data.decompress() : data);
                callback(null, resource);
            }
        } catch (err) {
            callback(err, null);
        }
    }
    /**
     * @param {string} url - The URL.
     * @param {GSplatResource} data - The data.
     * @returns {GSplatResource} Return the data.
     */ open(url, data) {
        return data;
    }
}

// combine the progress updates from multiple assets
// and fire progress events on the target
const combineProgress = (target, assets)=>{
    const map = new Map();
    const fire = ()=>{
        let loaded = 0;
        let total = 0;
        map.forEach((value)=>{
            loaded += value.loaded;
            total += value.total;
        });
        target.fire('progress', loaded, total);
    };
    assets.forEach((asset)=>{
        const progress = (loaded, total)=>{
            map.set(asset, {
                loaded,
                total
            });
            fire();
        };
        const done = ()=>{
            asset.off('progress', progress);
            asset.off('load', done);
            asset.off('error', done);
        };
        asset.on('progress', progress);
        asset.on('load', done);
        asset.on('error', done);
    });
};
// given a v1 meta.json, upgrade it to the v2 shape
const upgradeMeta = (meta)=>{
    const result = {
        version: 1,
        count: meta.means.shape[0],
        means: {
            mins: meta.means.mins,
            maxs: meta.means.maxs,
            files: meta.means.files
        },
        scales: {
            mins: meta.scales.mins,
            maxs: meta.scales.maxs,
            files: meta.scales.files
        },
        quats: {
            files: meta.quats.files
        },
        sh0: {
            mins: meta.sh0.mins,
            maxs: meta.sh0.maxs,
            files: meta.sh0.files
        }
    };
    if (meta.shN) {
        result.shN = {
            mins: meta.shN.mins,
            maxs: meta.shN.maxs,
            files: meta.shN.files
        };
    }
    return result;
};
/**
 * @import { AppBase } from '../app-base.js'
 * @import { ResourceHandlerCallback } from '../handlers/handler.js'
 */ class SogsParser {
    /**
     * @param {AppBase} app - The app instance.
     * @param {number} maxRetries - Maximum amount of retries.
     */ constructor(app, maxRetries){
        this.app = app;
        this.maxRetries = maxRetries;
    }
    async loadTextures(url, callback, asset, meta) {
        // transform meta to latest shape
        if (meta.version !== 2) {
            Debug.deprecated('Loading SOG v1 data which is deprecated. Please recompress your scene with latest tools.');
            meta = upgradeMeta(meta);
        }
        const { assets } = this.app;
        const subs = [
            'means',
            'quats',
            'scales',
            'sh0',
            'shN'
        ];
        const textures = {};
        const promises = [];
        subs.forEach((sub)=>{
            const files = meta[sub]?.files ?? [];
            textures[sub] = files.map((filename)=>{
                const texture = new Asset(filename, 'texture', {
                    url: asset.options?.mapUrl?.(filename) ?? new URL(filename, new URL(url.load, window.location.href).toString()).toString(),
                    filename
                }, {
                    mipmaps: false
                });
                const promise = new Promise((resolve, reject)=>{
                    texture.on('load', ()=>resolve(null));
                    texture.on('error', (err)=>reject(err));
                });
                assets.add(texture);
                promises.push(promise);
                return texture;
            });
        });
        const textureAssets = subs.map((sub)=>textures[sub]).flat();
        combineProgress(asset, textureAssets);
        textureAssets.forEach((t)=>assets.load(t));
        // wait for all textures to complete loading
        await Promise.allSettled(promises);
        // construct the gsplat resource
        const data = new GSplatSogsData();
        data.meta = meta;
        data.numSplats = meta.count;
        data.means_l = textures.means[0].resource;
        data.means_u = textures.means[1].resource;
        data.quats = textures.quats[0].resource;
        data.scales = textures.scales[0].resource;
        data.sh0 = textures.sh0[0].resource;
        data.sh_centroids = textures.shN?.[0]?.resource;
        data.sh_labels = textures.shN?.[1]?.resource;
        const decompress = asset.data?.decompress;
        if (!decompress) {
            // no need to prepare gpu data if decompressing
            await data.prepareGpuData();
        }
        const resource = decompress ? new GSplatResource(this.app.graphicsDevice, await data.decompress()) : new GSplatSogsResource(this.app.graphicsDevice, data);
        callback(null, resource);
    }
    /**
     * @param {object} url - The URL of the resource to load.
     * @param {string} url.load - The URL to use for loading the resource.
     * @param {string} url.original - The original URL useful for identifying the resource type.
     * @param {ResourceHandlerCallback} callback - The callback used when
     * the resource is loaded or an error occurs.
     * @param {Asset} asset - Container asset.
     */ load(url, callback, asset) {
        if (asset.data?.means) {
            // user can specify meta.json in asset data
            this.loadTextures(url, callback, asset, asset.data);
        } else {
            // otherwise download meta.json using asset url
            if (typeof url === 'string') {
                url = {
                    load: url,
                    original: url
                };
            }
            // we need to specify JSON for blob URLs
            const options = {
                retry: this.maxRetries > 0,
                maxRetries: this.maxRetries,
                responseType: Http.ResponseType.JSON
            };
            http.get(url.load, options, (err, meta)=>{
                if (!err) {
                    this.loadTextures(url, callback, asset, meta);
                } else {
                    callback(`Error loading gsplat meta: ${url.original} [${err}]`);
                }
            });
        }
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 * @import { ResourceHandlerCallback } from '../handlers/handler.js'
 */ /**
 * Parse an ArrayBuffer containing a zip archive.
 *
 * @param {ArrayBuffer} data - the file data
 * @returns {Array<{filename: string, compression: 'none' | 'deflate' | 'unknown', data: Uint8Array}>} the extracted files
 */ const parseZipArchive = (data)=>{
    const dataView = new DataView(data);
    const u16 = (offset)=>dataView.getUint16(offset, true);
    const u32 = (offset)=>dataView.getUint32(offset, true);
    // read the end of central directory record
    const extractEocd = (offset)=>{
        return {
            magic: u32(offset),
            numFiles: u16(offset + 8),
            cdSizeBytes: u32(offset + 12),
            cdOffsetBytes: u32(offset + 16)
        };
    };
    const extractCdr = (offset)=>{
        const filenameLength = u16(offset + 28);
        const extraFieldLength = u16(offset + 30);
        const fileCommentLength = u16(offset + 32);
        return {
            magic: u32(offset),
            compressionMethod: u16(offset + 10),
            compressedSizeBytes: u32(offset + 20),
            uncompressedSizeBytes: u32(offset + 24),
            lfhOffsetBytes: u32(offset + 42),
            filename: new TextDecoder().decode(new Uint8Array(data, offset + 46, filenameLength)),
            recordSizeBytes: 46 + filenameLength + extraFieldLength + fileCommentLength
        };
    };
    // read a local file header
    const extractLfh = (offset)=>{
        const filenameLength = u16(offset + 26);
        const extraLength = u16(offset + 28);
        return {
            magic: u32(offset),
            offsetBytes: offset + 30 + filenameLength + extraLength
        };
    };
    // read the end of central directory record
    const eocd = extractEocd(dataView.byteLength - 22);
    if (eocd.magic !== 0x06054b50) {
        throw new Error('Invalid zip file: EOCDR not found');
    }
    if (eocd.cdOffsetBytes === 0xffffffff || eocd.cdSizeBytes === 0xffffffff) {
        throw new Error('Invalid zip file: Zip64 not supported');
    }
    // step over central directory records
    const result = [];
    let offset = eocd.cdOffsetBytes;
    for(let i = 0; i < eocd.numFiles; i++){
        const cdr = extractCdr(offset);
        if (cdr.magic !== 0x02014b50) {
            throw new Error('Invalid zip file: CDR not found');
        }
        const lfh = extractLfh(cdr.lfhOffsetBytes);
        if (lfh.magic !== 0x04034b50) {
            throw new Error('Invalid zip file: LFH not found');
        }
        result.push({
            filename: cdr.filename,
            compression: {
                0: 'none',
                8: 'deflate'
            }[cdr.compressionMethod] ?? 'unknown',
            data: new Uint8Array(data, lfh.offsetBytes, cdr.compressedSizeBytes)
        });
        offset += cdr.recordSizeBytes;
    }
    return result;
};
const inflate = async (compressed)=>{
    const ds = new DecompressionStream('deflate-raw');
    const out = new Blob([
        compressed
    ]).stream().pipeThrough(ds);
    const ab = await new Response(out).arrayBuffer();
    return new Uint8Array(ab); // uncompressed file bytes
};
class SogBundleParser {
    constructor(app, maxRetries = 3){
        this.app = app;
        this.maxRetries = maxRetries;
    }
    /**
     * @param {object} url - The URL of the resource to load.
     * @param {string} url.load - The URL to use for loading the resource.
     * @param {string} url.original - The original URL useful for identifying the resource type.
     * @param {ResourceHandlerCallback} callback - The callback used when
     * the resource is loaded or an error occurs.
     * @param {Asset} asset - Container asset.
     */ load(url, callback, asset) {
        const handleArrayBuffer = async (arrayBuffer)=>{
            const files = parseZipArchive(arrayBuffer);
            // deflate
            for (const file of files){
                if (file.compression === 'deflate') {
                    file.data = await inflate(file.data); // eslint-disable-line no-await-in-loop
                }
            }
            // access bundled meta.json
            const metaFile = files.find((f)=>f.filename === 'meta.json');
            if (!metaFile) {
                callback('Error: meta.json not found');
                return;
            }
            // parse json
            let meta;
            try {
                meta = JSON.parse(new TextDecoder().decode(metaFile.data));
            } catch (err) {
                callback(`Error parsing meta.json: ${err}`);
                return;
            }
            // extract filenames from meta.json
            const filenames = [
                'means',
                'scales',
                'quats',
                'sh0',
                'shN'
            ].map((key)=>meta[key]?.files ?? []).flat();
            // load referenced textures
            const textures = {};
            const promises = [];
            for (const filename of filenames){
                const file = files.find((f)=>f.filename === filename);
                let texture;
                if (file) {
                    // file is embedded
                    texture = new Asset(filename, 'texture', {
                        url: `${url.load}/${filename}`,
                        filename,
                        contents: file.data
                    }, {
                        mipmaps: false
                    });
                } else {
                    // file doesn't exist in bundle, treat it as a url
                    const url = new URL(filename, new URL(filename, window.location.href).toString()).toString();
                    texture = new Asset(filename, 'texture', {
                        url,
                        filename
                    }, {
                        mipmaps: false
                    });
                }
                const promise = new Promise((resolve, reject)=>{
                    texture.on('load', ()=>resolve(null));
                    texture.on('error', (err)=>reject(err));
                });
                this.app.assets.add(texture);
                textures[filename] = texture;
                promises.push(promise);
            }
            Object.values(textures).forEach((t)=>this.app.assets.load(t));
            await Promise.allSettled(promises);
            // construct the gsplat resource
            const data = new GSplatSogsData();
            data.meta = meta;
            data.numSplats = meta.count;
            data.means_l = textures[meta.means.files[0]].resource;
            data.means_u = textures[meta.means.files[1]].resource;
            data.quats = textures[meta.quats.files[0]].resource;
            data.scales = textures[meta.scales.files[0]].resource;
            data.sh0 = textures[meta.sh0.files[0]].resource;
            data.sh_centroids = textures[meta.shN?.files[0]]?.resource;
            data.sh_labels = textures[meta.shN?.files[1]]?.resource;
            const decompress = asset.data?.decompress;
            if (!decompress) {
                // no need to prepare gpu data if decompressing
                await data.prepareGpuData();
            }
            const resource = decompress ? new GSplatResource(this.app.graphicsDevice, await data.decompress()) : new GSplatSogsResource(this.app.graphicsDevice, data);
            callback(null, resource);
        };
        Asset.fetchArrayBuffer(url.load, (error, arrayBuffer)=>{
            if (error) {
                callback(error);
            } else {
                handleArrayBuffer(arrayBuffer);
            }
        }, asset, this.maxRetries);
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 * @import { ResourceHandlerCallback } from '../handlers/handler.js'
 */ class GSplatOctreeParser {
    /**
     * @param {AppBase} app - The app instance.
     * @param {number} maxRetries - Maximum amount of retries.
     */ constructor(app, maxRetries){
        this.app = app;
        this.maxRetries = maxRetries;
    }
    /**
     * @param {object} url - The URL of the resource to load.
     * @param {string} url.load - The URL to use for loading the resource.
     * @param {string} url.original - The original URL useful for identifying the resource type.
     * @param {ResourceHandlerCallback} callback - The callback used when
     * the resource is loaded or an error occurs.
     * @param {object} asset - Container asset.
     */ load(url, callback, asset) {
        // download .json using asset url
        if (typeof url === 'string') {
            url = {
                load: url,
                original: url
            };
        }
        // we need to specify JSON for blob URLs
        const options = {
            retry: this.maxRetries > 0,
            maxRetries: this.maxRetries,
            responseType: Http.ResponseType.JSON
        };
        http.get(url.load, options, (err, data)=>{
            if (!err) {
                // create a resource with the parsed data, passing the asset's file URL
                const resource = new GSplatOctreeResource(asset.file.url, data);
                callback(null, resource);
            } else {
                callback(`Error loading gsplat octree: ${url.original} [${err}]`);
            }
        });
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 */ class GSplatHandler extends ResourceHandler {
    /**
     * Create a new GSplatHandler instance.
     *
     * @param {AppBase} app - The running {@link AppBase}.
     * @ignore
     */ constructor(app){
        super(app, 'gsplat');
        this.parsers = {
            ply: new PlyParser(app, 3),
            sog: new SogBundleParser(app),
            json: new SogsParser(app, 3),
            octree: new GSplatOctreeParser(app, 3)
        };
    }
    _getUrlWithoutParams(url) {
        return url.indexOf('?') >= 0 ? url.split('?')[0] : url;
    }
    _getParser(url) {
        const basename = path$1.getBasename(this._getUrlWithoutParams(url)).toLowerCase();
        if (basename === 'lod-meta.json') {
            return this.parsers.octree;
        }
        const ext = path$1.getExtension(basename).replace('.', '');
        return this.parsers[ext] || this.parsers.ply;
    }
    load(url, callback, asset) {
        if (typeof url === 'string') {
            url = {
                load: url,
                original: url
            };
        }
        this._getParser(url.original).load(url, callback, asset);
    }
    open(url, data, asset) {
        return data;
    }
}

// Basis worker
function BasisWorker() {
    // basis compression format enums, reproduced here
    const BASIS_FORMAT = {
        cTFETC1: 0,
        cTFETC2: 1,
        cTFBC1: 2,
        cTFBC3: 3,
        cTFPVRTC1_4_RGB: 8,
        cTFPVRTC1_4_RGBA: 9,
        cTFASTC_4x4: 10,
        cTFATC_RGB: 11,
        cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
        // uncompressed (fallback) formats
        cTFRGBA32: 13,
        cTFRGB565: 14,
        cTFRGBA4444: 16 // rgba 4444
    };
    // map of GPU to basis format for textures without alpha
    const opaqueMapping = {
        astc: BASIS_FORMAT.cTFASTC_4x4,
        dxt: BASIS_FORMAT.cTFBC1,
        etc1: BASIS_FORMAT.cTFETC1,
        etc2: BASIS_FORMAT.cTFETC1,
        pvr: BASIS_FORMAT.cTFPVRTC1_4_RGB,
        atc: BASIS_FORMAT.cTFATC_RGB,
        none: BASIS_FORMAT.cTFRGB565
    };
    // map of GPU to basis format for textures with alpha
    const alphaMapping = {
        astc: BASIS_FORMAT.cTFASTC_4x4,
        dxt: BASIS_FORMAT.cTFBC3,
        etc1: BASIS_FORMAT.cTFRGBA4444,
        etc2: BASIS_FORMAT.cTFETC2,
        pvr: BASIS_FORMAT.cTFPVRTC1_4_RGBA,
        atc: BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA,
        none: BASIS_FORMAT.cTFRGBA4444
    };
    // engine pixel format constants, reproduced here
    const PIXEL_FORMAT = {
        ETC1: 21,
        ETC2_RGB: 22,
        ETC2_RGBA: 23,
        DXT1: 8,
        DXT5: 10,
        PVRTC_4BPP_RGB_1: 26,
        PVRTC_4BPP_RGBA_1: 27,
        ASTC_4x4: 28,
        ATC_RGB: 29,
        ATC_RGBA: 30,
        R8_G8_B8_A8: 7,
        R5_G6_B5: 3,
        R4_G4_B4_A4: 5
    };
    // map of basis format to engine pixel format
    const basisToEngineMapping = (basisFormat, deviceDetails)=>{
        switch(basisFormat){
            case BASIS_FORMAT.cTFETC1:
                return deviceDetails.formats.etc2 ? PIXEL_FORMAT.ETC2_RGB : PIXEL_FORMAT.ETC1;
            case BASIS_FORMAT.cTFETC2:
                return PIXEL_FORMAT.ETC2_RGBA;
            case BASIS_FORMAT.cTFBC1:
                return PIXEL_FORMAT.DXT1;
            case BASIS_FORMAT.cTFBC3:
                return PIXEL_FORMAT.DXT5;
            case BASIS_FORMAT.cTFPVRTC1_4_RGB:
                return PIXEL_FORMAT.PVRTC_4BPP_RGB_1;
            case BASIS_FORMAT.cTFPVRTC1_4_RGBA:
                return PIXEL_FORMAT.PVRTC_4BPP_RGBA_1;
            case BASIS_FORMAT.cTFASTC_4x4:
                return PIXEL_FORMAT.ASTC_4x4;
            case BASIS_FORMAT.cTFATC_RGB:
                return PIXEL_FORMAT.ATC_RGB;
            case BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA:
                return PIXEL_FORMAT.ATC_RGBA;
            case BASIS_FORMAT.cTFRGBA32:
                return PIXEL_FORMAT.R8_G8_B8_A8;
            case BASIS_FORMAT.cTFRGB565:
                return PIXEL_FORMAT.R5_G6_B5;
            case BASIS_FORMAT.cTFRGBA4444:
                return PIXEL_FORMAT.R4_G4_B4_A4;
        }
    };
    // unswizzle two-component gggr8888 normal data into rgba8888
    const unswizzleGGGR = (data)=>{
        // given R and G generate B
        const genB = function(R, G) {
            const r = R * (2.0 / 255.0) - 1.0;
            const g = G * (2.0 / 255.0) - 1.0;
            const b = Math.sqrt(1.0 - Math.min(1.0, r * r + g * g));
            return Math.max(0, Math.min(255, Math.floor((b + 1.0) * 0.5 * 255.0)));
        };
        for(let offset = 0; offset < data.length; offset += 4){
            const R = data[offset + 3];
            const G = data[offset + 1];
            data[offset + 0] = R;
            data[offset + 2] = genB(R, G);
            data[offset + 3] = 255;
        }
        return data;
    };
    // pack rgba8888 data into rgb565
    const pack565 = (data)=>{
        const result = new Uint16Array(data.length / 4);
        for(let offset = 0; offset < data.length; offset += 4){
            const R = data[offset + 0];
            const G = data[offset + 1];
            const B = data[offset + 2];
            result[offset / 4] = (R & 0xf8) << 8 | // 5
            (G & 0xfc) << 3 | B >> 3; // 5
        }
        return result;
    };
    const isPOT = (width, height)=>{
        return (width & width - 1) === 0 && (height & height - 1) === 0;
    };
    const performanceNow = ()=>{
        return typeof performance !== 'undefined' ? performance.now() : 0;
    };
    // globals, set on worker init
    let basis;
    let rgbPriority;
    let rgbaPriority;
    const chooseTargetFormat = (deviceDetails, hasAlpha, isUASTC)=>{
        // attempt to match file compression scheme with runtime compression
        if (isUASTC) {
            if (deviceDetails.formats.astc) {
                return 'astc';
            }
        } else {
            if (hasAlpha) {
                if (deviceDetails.formats.etc2) {
                    return 'etc2';
                }
            } else {
                if (deviceDetails.formats.etc2) {
                    return 'etc2';
                }
                if (deviceDetails.formats.etc1) {
                    return 'etc1';
                }
            }
        }
        const testInOrder = (priority)=>{
            for(let i = 0; i < priority.length; ++i){
                const format = priority[i];
                if (deviceDetails.formats[format]) {
                    return format;
                }
            }
            return 'none';
        };
        return testInOrder(hasAlpha ? rgbaPriority : rgbPriority);
    };
    // return true if the texture dimensions are valid for the target format
    const dimensionsValid = (width, height, format)=>{
        switch(format){
            // etc1, 2
            case BASIS_FORMAT.cTFETC1:
            case BASIS_FORMAT.cTFETC2:
                // no size restrictions
                return true;
            // dxt1, 5
            case BASIS_FORMAT.cTFBC1:
            case BASIS_FORMAT.cTFBC3:
                // width and height must be multiple of 4
                return (width & 0x3) === 0 && (height & 0x3) === 0;
            // pvrtc
            case BASIS_FORMAT.cTFPVRTC1_4_RGB:
            case BASIS_FORMAT.cTFPVRTC1_4_RGBA:
                return isPOT(width, height);
            // astc
            case BASIS_FORMAT.cTFASTC_4x4:
                return true;
            // atc
            case BASIS_FORMAT.cTFATC_RGB:
            case BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA:
                // TODO: remove atc support? looks like it's been removed from the webgl spec, see
                // https://www.khronos.org/registry/webgl/extensions/rejected/WEBGL_compressed_texture_atc/
                return true;
        }
        return false;
    };
    const transcodeKTX2 = (url, data, options)=>{
        if (!basis.KTX2File) {
            throw new Error('Basis transcoder module does not include support for KTX2.');
        }
        const funcStart = performanceNow();
        const basisFile = new basis.KTX2File(new Uint8Array(data));
        const width = basisFile.getWidth();
        const height = basisFile.getHeight();
        const levels = basisFile.getLevels();
        const hasAlpha = !!basisFile.getHasAlpha();
        const isUASTC = basisFile.isUASTC && basisFile.isUASTC();
        if (!width || !height || !levels) {
            basisFile.close();
            basisFile.delete();
            throw new Error(`Invalid image dimensions url=${url} width=${width} height=${height} levels=${levels}`);
        }
        // choose the target format
        const format = chooseTargetFormat(options.deviceDetails, hasAlpha, isUASTC);
        // unswizzle gggr textures under pvr compression
        const unswizzle = !!options.isGGGR && format === 'pvr';
        // convert to basis format taking into consideration platform restrictions
        let basisFormat;
        if (unswizzle) {
            // in order to unswizzle we need gggr8888
            basisFormat = BASIS_FORMAT.cTFRGBA32;
        } else {
            // select output format based on supported formats
            basisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format];
            // if image dimensions don't work on target, fall back to uncompressed
            if (!dimensionsValid(width, height, basisFormat)) {
                basisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565;
            }
        }
        if (!basisFile.startTranscoding()) {
            basisFile.close();
            basisFile.delete();
            throw new Error(`Failed to start transcoding url=${url}`);
        }
        let i;
        const levelData = [];
        for(let mip = 0; mip < levels; ++mip){
            const dstSize = basisFile.getImageTranscodedSizeInBytes(mip, 0, 0, basisFormat);
            const dst = new Uint8Array(dstSize);
            if (!basisFile.transcodeImage(dst, mip, 0, 0, basisFormat, 0, -1, -1)) {
                basisFile.close();
                basisFile.delete();
                throw new Error(`Failed to transcode image url=${url}`);
            }
            const is16BitFormat = basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444;
            levelData.push(is16BitFormat ? new Uint16Array(dst.buffer) : dst);
        }
        basisFile.close();
        basisFile.delete();
        // handle unswizzle option
        if (unswizzle) {
            basisFormat = BASIS_FORMAT.cTFRGB565;
            for(i = 0; i < levelData.length; ++i){
                levelData[i] = pack565(unswizzleGGGR(levelData[i]));
            }
        }
        return {
            format: basisToEngineMapping(basisFormat, options.deviceDetails),
            width: width,
            height: height,
            levels: levelData,
            cubemap: false,
            transcodeTime: performanceNow() - funcStart,
            url: url,
            unswizzledGGGR: unswizzle
        };
    };
    // transcode the basis super-compressed data into one of the runtime gpu native formats
    const transcodeBasis = (url, data, options)=>{
        const funcStart = performanceNow();
        const basisFile = new basis.BasisFile(new Uint8Array(data));
        const width = basisFile.getImageWidth(0, 0);
        const height = basisFile.getImageHeight(0, 0);
        const images = basisFile.getNumImages();
        const levels = basisFile.getNumLevels(0);
        const hasAlpha = !!basisFile.getHasAlpha();
        const isUASTC = basisFile.isUASTC && basisFile.isUASTC();
        if (!width || !height || !images || !levels) {
            basisFile.close();
            basisFile.delete();
            throw new Error(`Invalid image dimensions url=${url} width=${width} height=${height} images=${images} levels=${levels}`);
        }
        // choose the target format
        const format = chooseTargetFormat(options.deviceDetails, hasAlpha, isUASTC);
        // unswizzle gggr textures under pvr compression
        const unswizzle = !!options.isGGGR && format === 'pvr';
        // convert to basis format taking into consideration platform restrictions
        let basisFormat;
        if (unswizzle) {
            // in order to unswizzle we need gggr8888
            basisFormat = BASIS_FORMAT.cTFRGBA32;
        } else {
            // select output format based on supported formats
            basisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format];
            // if image dimensions don't work on target, fall back to uncompressed
            if (!dimensionsValid(width, height, basisFormat)) {
                basisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565;
            }
        }
        if (!basisFile.startTranscoding()) {
            basisFile.close();
            basisFile.delete();
            throw new Error(`Failed to start transcoding url=${url}`);
        }
        let i;
        const levelData = [];
        for(let mip = 0; mip < levels; ++mip){
            const dstSize = basisFile.getImageTranscodedSizeInBytes(0, mip, basisFormat);
            const dst = new Uint8Array(dstSize);
            if (!basisFile.transcodeImage(dst, 0, mip, basisFormat, 0, 0)) {
                if (mip === levels - 1 && dstSize === levelData[mip - 1].buffer.byteLength) {
                    // https://github.com/BinomialLLC/basis_universal/issues/358
                    // there is a regression on iOS/safari 17 where the last mipmap level
                    // fails to transcode. this is a workaround which copies the previous mip
                    // level data instead of failing.
                    dst.set(new Uint8Array(levelData[mip - 1].buffer));
                    console.warn(`Failed to transcode last mipmap level, using previous level instead url=${url}`);
                } else {
                    basisFile.close();
                    basisFile.delete();
                    throw new Error(`Failed to transcode image url=${url}`);
                }
            }
            const is16BitFormat = basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444;
            levelData.push(is16BitFormat ? new Uint16Array(dst.buffer) : dst);
        }
        basisFile.close();
        basisFile.delete();
        // handle unswizzle option
        if (unswizzle) {
            basisFormat = BASIS_FORMAT.cTFRGB565;
            for(i = 0; i < levelData.length; ++i){
                levelData[i] = pack565(unswizzleGGGR(levelData[i]));
            }
        }
        return {
            format: basisToEngineMapping(basisFormat, options.deviceDetails),
            width: width,
            height: height,
            levels: levelData,
            cubemap: false,
            transcodeTime: performanceNow() - funcStart,
            url: url,
            unswizzledGGGR: unswizzle
        };
    };
    const transcode = (url, data, options)=>{
        return options.isKTX2 ? transcodeKTX2(url, data, options) : transcodeBasis(url, data, options);
    };
    // download and transcode the file given the basis module and
    // file url
    const workerTranscode = (url, data, options)=>{
        try {
            const result = transcode(url, data, options);
            result.levels = result.levels.map((v)=>v.buffer);
            self.postMessage({
                url: url,
                data: result
            }, result.levels);
        } catch (err) {
            self.postMessage({
                url: url,
                err: err
            }, null);
        }
    };
    const workerInit = (config, callback)=>{
        // initialize the wasm module
        const instantiateWasmFunc = (imports, successCallback)=>{
            WebAssembly.instantiate(config.module, imports).then((result)=>{
                successCallback(result);
            }).catch((reason)=>{
                console.error(`instantiate failed + ${reason}`);
            });
            return {};
        };
        self.BASIS(config.module ? {
            instantiateWasm: instantiateWasmFunc
        } : null).then((instance)=>{
            instance.initializeBasis();
            // set globals
            basis = instance;
            rgbPriority = config.rgbPriority;
            rgbaPriority = config.rgbaPriority;
            callback(null);
        });
    };
    // handle incoming worker requests
    const queue = [];
    self.onmessage = (message)=>{
        const data = message.data;
        switch(data.type){
            case 'init':
                workerInit(data.config, ()=>{
                    for(let i = 0; i < queue.length; ++i){
                        workerTranscode(queue[i].url, queue[i].data, queue[i].options);
                    }
                    queue.length = 0;
                });
                break;
            case 'transcode':
                if (basis) {
                    workerTranscode(data.url, data.data, data.options);
                } else {
                    queue.push(data);
                }
                break;
        }
    };
}

/**
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 */ // get the list of the device's supported compression formats
const getCompressionFormats = (device)=>{
    return {
        astc: !!device.extCompressedTextureASTC,
        atc: !!device.extCompressedTextureATC,
        dxt: !!device.extCompressedTextureS3TC,
        etc1: !!device.extCompressedTextureETC1,
        etc2: !!device.extCompressedTextureETC,
        pvr: !!device.extCompressedTexturePVRTC
    };
};
// download basis code and compile the wasm module for use in workers
const prepareWorkerModules = (config, callback)=>{
    const getWorkerBlob = (basisCode)=>{
        const code = [
            '/* basis */',
            basisCode,
            '',
            `(${BasisWorker.toString()})()\n\n`
        ].join('\n');
        return new Blob([
            code
        ], {
            type: "application/javascript"
        });
    };
    const wasmSupported = ()=>{
        try {
            if (typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function') {
                const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
                if (module instanceof WebAssembly.Module) {
                    return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
                }
            }
        } catch (e) {}
        return false;
    };
    const sendResponse = (basisCode, module)=>{
        callback(null, {
            workerUrl: URL.createObjectURL(getWorkerBlob(basisCode)),
            module: module,
            rgbPriority: config.rgbPriority,
            rgbaPriority: config.rgbaPriority
        });
    };
    const options = {
        cache: true,
        responseType: 'text',
        retry: config.maxRetries > 0,
        maxRetries: config.maxRetries
    };
    if (config.glueUrl && config.wasmUrl && wasmSupported()) {
        let basisCode = null;
        let module = null;
        // download glue script
        http.get(config.glueUrl, options, (err, response)=>{
            if (err) {
                callback(err);
            } else {
                if (module) {
                    sendResponse(response, module);
                } else {
                    basisCode = response;
                }
            }
        });
        const fetchPromise = fetch(config.wasmUrl);
        const compileManual = ()=>{
            fetchPromise.then((result)=>result.arrayBuffer()).then((buffer)=>WebAssembly.compile(buffer)).then((module_)=>{
                if (basisCode) {
                    sendResponse(basisCode, module_);
                } else {
                    module = module_;
                }
            }).catch((err)=>{
                callback(err, null);
            });
        };
        // download and compile wasm module
        if (WebAssembly.compileStreaming) {
            WebAssembly.compileStreaming(fetchPromise).then((module_)=>{
                if (basisCode) {
                    sendResponse(basisCode, module_);
                } else {
                    module = module_;
                }
            }).catch((err)=>{
                Debug.warn(`compileStreaming() failed for ${config.wasmUrl} (${err}), falling back to arraybuffer download.`);
                compileManual();
            });
        } else {
            compileManual();
        }
    } else {
        http.get(config.fallbackUrl, options, (err, response)=>{
            if (err) {
                callback(err, null);
            } else {
                sendResponse(response, null);
            }
        });
    }
};
// queue of transcode jobs and clients ready to run them
class BasisQueue {
    constructor(){
        this.callbacks = {};
        this.queue = [];
        this.clients = [];
    }
    enqueueJob(url, data, callback, options) {
        if (this.callbacks.hasOwnProperty(url)) {
            // duplicate URL request
            this.callbacks[url].push(callback);
        } else {
            // new URL request
            this.callbacks[url] = [
                callback
            ];
            const job = {
                url: url,
                data: data,
                options: options
            };
            if (this.clients.length > 0) {
                this.clients.shift().run(job);
            } else {
                this.queue.push(job);
            }
        }
    }
    enqueueClient(client) {
        if (this.queue.length > 0) {
            client.run(this.queue.shift());
        } else {
            this.clients.push(client);
        }
    }
    handleResponse(url, err, data) {
        const callback = this.callbacks[url];
        if (err) {
            for(let i = 0; i < callback.length; ++i){
                callback[i](err);
            }
        } else {
            // (re)create typed array from the returned array buffers
            if (data.format === PIXELFORMAT_RGB565 || data.format === PIXELFORMAT_RGBA4) {
                // handle 16 bit formats
                data.levels = data.levels.map((v)=>{
                    return new Uint16Array(v);
                });
            } else {
                // all other
                data.levels = data.levels.map((v)=>{
                    return new Uint8Array(v);
                });
            }
            for(let i = 0; i < callback.length; ++i){
                callback[i](null, data);
            }
        }
        delete this.callbacks[url];
    }
}
// client interface to a basis transcoder instance running on a web worker
class BasisClient {
    constructor(queue, config, eager){
        this.queue = queue;
        this.worker = new Worker(config.workerUrl);
        this.worker.addEventListener('message', (message)=>{
            const data = message.data;
            this.queue.handleResponse(data.url, data.err, data.data);
            if (!this.eager) {
                this.queue.enqueueClient(this);
            }
        });
        this.worker.postMessage({
            type: 'init',
            config: config
        });
        // an eager client will enqueue itself while a job is running. a
        // non-eager client will only enqueue itself once the current job
        // has finished running.
        this.eager = eager;
    }
    run(job) {
        const transfer = [];
        if (job.data instanceof ArrayBuffer) {
            transfer.push(job.data);
        }
        this.worker.postMessage({
            type: 'transcode',
            url: job.url,
            format: job.format,
            data: job.data,
            options: job.options
        }, transfer);
        if (this.eager) {
            this.queue.enqueueClient(this);
        }
    }
}
// defaults
const defaultNumWorkers = 1;
const defaultRgbPriority = [
    'etc2',
    'etc1',
    'astc',
    'dxt',
    'pvr',
    'atc'
];
const defaultRgbaPriority = [
    'astc',
    'dxt',
    'etc2',
    'pvr',
    'atc'
];
const defaultMaxRetries = 5;
// global state
const queue = new BasisQueue();
let lazyConfig = null;
let initializing = false;
/**
 * Initialize the Basis transcode worker.
 *
 * @param {object} [config] - The Basis configuration.
 * @param {string} [config.glueUrl] - URL of glue script.
 * @param {string} [config.wasmUrl] - URL of the wasm module.
 * @param {string} [config.fallbackUrl] - URL of the fallback script to use when wasm modules
 * aren't supported.
 * @param {boolean} [config.lazyInit] - Wait for first transcode request before initializing Basis
 * (default is false). Otherwise initialize Basis immediately.
 * @param {number} [config.numWorkers] - Number of workers to use for transcoding (default is 1).
 * While it is possible to improve transcode performance using multiple workers, this will likely
 * depend on the runtime platform. For example, desktop will likely benefit from more workers
 * compared to mobile. Also keep in mind that it takes time to initialize workers and increasing
 * this value could impact application startup time. Make sure to test your application performance
 * on all target platforms when changing this parameter.
 * @param {boolean} [config.eagerWorkers] - Use eager workers (default is true). When enabled, jobs
 * are assigned to workers immediately, independent of their work load. This can result in
 * unbalanced workloads, however there is no delay between jobs. If disabled, new jobs are assigned
 * to workers only when their previous job has completed. This will result in balanced workloads
 * across workers, however workers can be idle for a short time between jobs.
 * @param {string[]} [config.rgbPriority] - Array of texture compression formats in priority order
 * for textures without alpha. The supported compressed formats are: 'astc', 'atc', 'dxt', 'etc1',
 * 'etc2', 'pvr'.
 * @param {string[]} [config.rgbaPriority] - Array of texture compression formats in priority order
 * for textures with alpha. The supported compressed formats are: 'astc', 'atc', 'dxt', 'etc1',
 * 'etc2', 'pvr'.
 * @param {number} [config.maxRetries] - Number of http load retry attempts. Defaults to 5.
 */ function basisInitialize(config) {
    if (initializing) {
        // already initializing
        return;
    }
    if (!config) {
        config = lazyConfig || {};
    } else if (config.lazyInit) {
        lazyConfig = config;
        return;
    }
    // if any URLs are not specified in the config, take them from WasmModule config
    if (!config.glueUrl || !config.wasmUrl || !config.fallbackUrl) {
        const moduleConfig = WasmModule.getConfig('BASIS');
        if (moduleConfig) {
            config = {
                glueUrl: moduleConfig.glueUrl,
                wasmUrl: moduleConfig.wasmUrl,
                fallbackUrl: moduleConfig.fallbackUrl,
                numWorkers: moduleConfig.numWorkers
            };
        }
    }
    if (config.glueUrl || config.wasmUrl || config.fallbackUrl) {
        initializing = true;
        const numWorkers = Math.max(1, Math.min(16, config.numWorkers || defaultNumWorkers));
        const eagerWorkers = config.numWorkers === 1 || (config.hasOwnProperty('eagerWorkers') ? config.eagerWorkers : true);
        config.rgbPriority = config.rgbPriority || defaultRgbPriority;
        config.rgbaPriority = config.rgbaPriority || defaultRgbaPriority;
        config.maxRetries = config.hasOwnProperty('maxRetries') ? config.maxRetries : defaultMaxRetries;
        prepareWorkerModules(config, (err, clientConfig)=>{
            if (err) {
                console.error(`failed to initialize basis worker: ${err}`);
            } else {
                for(let i = 0; i < numWorkers; ++i){
                    queue.enqueueClient(new BasisClient(queue, clientConfig, eagerWorkers));
                }
            }
        });
    }
}
let deviceDetails = null;
/**
 * Enqueue a blob of basis data for transcoding.
 *
 * @param {GraphicsDevice} device - The graphics device.
 * @param {string} url - URL of the basis file.
 * @param {object} data - The file data to transcode.
 * @param {Function} callback - Callback function to receive transcode result.
 * @param {object} [options] - Options structure
 * @param {boolean} [options.isGGGR] - Indicates this is a GGGR swizzled texture. Under some
 * circumstances the texture will be unswizzled during transcoding.
 * @param {boolean} [options.isKTX2] - Indicates the image is KTX2 format. Otherwise
 * basis format is assumed.
 * @returns {boolean} True if the basis worker was initialized and false otherwise.
 * @ignore
 */ function basisTranscode(device, url, data, callback, options) {
    basisInitialize();
    if (!deviceDetails) {
        deviceDetails = {
            formats: getCompressionFormats(device)
        };
    }
    queue.enqueueJob(url, data, callback, {
        deviceDetails: deviceDetails,
        isGGGR: !!options?.isGGGR,
        isKTX2: !!options?.isKTX2
    });
    return initializing;
}

/**
 * @import { Asset } from '../../asset/asset.js'
 * @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js'
 * @import { ResourceHandlerCallback } from '../../../framework/handlers/handler.js'
 * @import { Texture } from '../../../platform/graphics/texture.js'
 */ /**
 * Interface to a texture parser. Implementations of this interface handle the loading and opening
 * of texture assets.
 */ class TextureParser {
    /**
     * Load the texture from the remote URL. When loaded (or failed), use the callback to return an
     * the raw resource data (or error).
     *
     * @param {object} url - The URL of the resource to load.
     * @param {string} url.load - The URL to use for loading the resource.
     * @param {string} url.original - The original URL useful for identifying the resource type.
     * @param {ResourceHandlerCallback} callback - The callback used when the resource is loaded or
     * an error occurs.
     * @param {Asset} [asset] - Optional asset that is passed by ResourceLoader.
     */ load(url, callback, asset) {
        throw new Error('not implemented');
    }
    /* eslint-disable jsdoc/require-returns-check */ /**
     * Convert raw resource data into a {@link Texture}.
     *
     * @param {string} url - The URL of the resource to open.
     * @param {*} data - The raw resource data passed by callback from {@link ResourceHandler#load}.
     * @param {GraphicsDevice} device - The graphics device.
     * @returns {Texture} The parsed resource data.
     */ open(url, data, device) {
        throw new Error('not implemented');
    }
}

/**
 * Parser for basis files.
 */ class BasisParser extends TextureParser {
    constructor(registry, device){
        super();
        this.device = device;
        this.maxRetries = 0;
    }
    load(url, callback, asset) {
        const device = this.device;
        const transcode = (data)=>{
            const basisModuleFound = basisTranscode(device, url.load, data, callback, {
                isGGGR: (asset?.file?.variants?.basis?.opt & 8) !== 0
            });
            if (!basisModuleFound) {
                callback(`Basis module not found. Asset [${asset.name}](${asset.getFileUrl()}) basis texture variant will not be loaded.`);
            }
        };
        Asset.fetchArrayBuffer(url.load, (err, result)=>{
            if (err) {
                callback(err);
            } else {
                transcode(result);
            }
        }, asset, this.maxRetries);
    }
    // our async transcode call provides the neat structure we need to create the texture instance
    open(url, data, device, textureOptions = {}) {
        const format = textureOptions.srgb ? pixelFormatLinearToGamma(data.format) : data.format;
        const texture = new Texture(device, {
            name: url,
            profilerHint: TEXHINT_ASSET,
            addressU: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            addressV: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            width: data.width,
            height: data.height,
            format: format,
            cubemap: data.cubemap,
            levels: data.levels,
            ...textureOptions
        });
        texture.upload();
        return texture;
    }
}

//
// Current state of ImageBitmap (April 2023):
//
// Chrome MacOS and Android (Pixel 3a and Pixel 7 Pro):
//   Correctly loads PNG alpha and runs faster (significantly so on mobile) than HTMLImageElement.
//
// Firefox MacOS:
//   Correctly loads PNG alpha, but runs significantly slower than HTMLImageElement.
//
// Safari MacOS and iOS (iPhone 8 and iPhone 13 Pro Max):
//   Incorrectly loads PNG alpha and runs significantly slower than HTMLImageElement.
//
// 1x1 png image containing rgba(1, 2, 3, 63)
const pngBytes = new Uint8Array([
    137,
    80,
    78,
    71,
    13,
    10,
    26,
    10,
    0,
    0,
    0,
    13,
    73,
    72,
    68,
    82,
    0,
    0,
    0,
    1,
    0,
    0,
    0,
    1,
    8,
    6,
    0,
    0,
    0,
    31,
    21,
    196,
    137,
    0,
    0,
    0,
    13,
    73,
    68,
    65,
    84,
    120,
    218,
    99,
    100,
    100,
    98,
    182,
    7,
    0,
    0,
    89,
    0,
    71,
    67,
    133,
    148,
    237,
    0,
    0,
    0,
    0,
    73,
    69,
    78,
    68,
    174,
    66,
    96,
    130
]);
const testAlpha = (device, image)=>{
    // create the texture
    const texture = new Texture(device, {
        width: 1,
        height: 1,
        format: PIXELFORMAT_RGBA8,
        mipmaps: false,
        levels: [
            image
        ]
    });
    // read pixels
    const rt = new RenderTarget({
        colorBuffer: texture,
        depth: false
    });
    device.setFramebuffer(rt.impl._glFrameBuffer);
    device.initRenderTarget(rt);
    const data = new Uint8ClampedArray(4);
    device.gl.readPixels(0, 0, 1, 1, device.gl.RGBA, device.gl.UNSIGNED_BYTE, data);
    rt.destroy();
    texture.destroy();
    return data[0] === 1 && data[1] === 2 && data[2] === 3 && data[3] === 63;
};
// Test whether ImageBitmap correctly loads PNG alpha data.
const testImageBitmapAlpha = (device)=>{
    return createImageBitmap(new Blob([
        pngBytes
    ], {
        type: 'image/png'
    }), {
        premultiplyAlpha: 'none',
        colorSpaceConversion: 'none'
    }).then((image)=>{
        return testAlpha(device, image);
    }).catch((e)=>false);
};
// Test whether image element correctly loads PNG alpha data.
const testImgElementAlpha = (device)=>{
    return new Promise((resolve, reject)=>{
        const image = new Image();
        image.src = URL.createObjectURL(new Blob([
            pngBytes
        ]));
        image.onload = ()=>{
            resolve(testAlpha(device, image));
        };
    });
};
class ImgAlphaTest {
    static run(device) {
        testImageBitmapAlpha(device).then((result)=>{
            console.log(`imageBitmapIsCorrect=${result}`);
        });
        testImgElementAlpha(device).then((result)=>{
            console.log(`imgElementIsCorrect=${result}`);
        });
    }
}

/**
 * Parser for browser-supported image formats.
 */ class ImgParser extends TextureParser {
    constructor(registry, device){
        super();
        // by default don't try cross-origin, because some browsers send different cookies (e.g. safari) if this is set.
        this.crossOrigin = registry.prefix ? 'anonymous' : null;
        this.maxRetries = 0;
        this.device = device;
        // run image alpha test
        if (Tracing.get('IMG_ALPHA_TEST')) {
            ImgAlphaTest.run(this.device);
        }
    }
    load(url, callback, asset) {
        const hasContents = !!asset?.file?.contents;
        if (hasContents) {
            // ImageBitmap interface can load image
            if (this.device.supportsImageBitmap) {
                this._loadImageBitmapFromBlob(new Blob([
                    asset.file.contents
                ]), callback);
                return;
            }
            url = {
                load: URL.createObjectURL(new Blob([
                    asset.file.contents
                ])),
                original: url.original
            };
        }
        const handler = (err, result)=>{
            if (hasContents) {
                URL.revokeObjectURL(url.load);
            }
            callback(err, result);
        };
        let crossOrigin;
        if (asset && asset.options && asset.options.hasOwnProperty('crossOrigin')) {
            crossOrigin = asset.options.crossOrigin;
        } else if (ABSOLUTE_URL.test(url.load)) {
            crossOrigin = this.crossOrigin;
        }
        if (this.device.supportsImageBitmap) {
            this._loadImageBitmap(url.load, url.original, crossOrigin, handler, asset);
        } else {
            this._loadImage(url.load, url.original, crossOrigin, handler, asset);
        }
    }
    open(url, data, device, textureOptions = {}) {
        const texture = new Texture(device, {
            name: url,
            profilerHint: TEXHINT_ASSET,
            width: data.width,
            height: data.height,
            format: textureOptions.srgb ? PIXELFORMAT_SRGBA8 : PIXELFORMAT_RGBA8,
            ...textureOptions
        });
        texture.setSource(data);
        return texture;
    }
    _loadImage(url, originalUrl, crossOrigin, callback, asset) {
        const image = new Image();
        if (crossOrigin) {
            image.crossOrigin = crossOrigin;
        }
        let retries = 0;
        const maxRetries = this.maxRetries;
        let retryTimeout;
        const dummySize = 1024 * 1024;
        // HTMLImageElement doesn't support progress events, so we emulate it instead
        asset?.fire('progress', 0, dummySize);
        // Call success callback after opening Texture
        image.onload = function() {
            asset?.fire('progress', dummySize, dummySize);
            callback(null, image);
        };
        image.onerror = function() {
            // Retry a few times before failing
            if (retryTimeout) return;
            if (maxRetries > 0 && ++retries <= maxRetries) {
                const retryDelay = Math.pow(2, retries) * 100;
                console.log(`Error loading Texture from: '${originalUrl}' - Retrying in ${retryDelay}ms...`);
                const idx = url.indexOf('?');
                const separator = idx >= 0 ? '&' : '?';
                retryTimeout = setTimeout(()=>{
                    // we need to add a cache busting argument if we are trying to re-load an image element
                    // with the same URL
                    image.src = `${url + separator}retry=${Date.now()}`;
                    retryTimeout = null;
                }, retryDelay);
            } else {
                // Call error callback with details.
                callback(`Error loading Texture from: '${originalUrl}'`);
            }
        };
        image.src = url;
    }
    _loadImageBitmap(url, originalUrl, crossOrigin, callback, asset) {
        const options = {
            cache: true,
            responseType: 'blob',
            retry: this.maxRetries > 0,
            maxRetries: this.maxRetries,
            progress: asset
        };
        http.get(url, options, (err, blob)=>{
            if (err) {
                callback(err);
            } else {
                this._loadImageBitmapFromBlob(blob, callback);
            }
        });
    }
    _loadImageBitmapFromBlob(blob, callback) {
        createImageBitmap(blob, {
            premultiplyAlpha: 'none',
            colorSpaceConversion: 'none'
        }).then((imageBitmap)=>callback(null, imageBitmap)).catch((e)=>callback(e));
    }
}

// Defined here: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
const IDENTIFIER = [
    0x58544BAB,
    0xBB313120,
    0x0A1A0A0D
]; // «KTX 11»\r\n\x1A\n
const KNOWN_FORMATS = {
    // compressed formats
    0x83F0: PIXELFORMAT_DXT1,
    0x83F2: PIXELFORMAT_DXT3,
    0x83F3: PIXELFORMAT_DXT5,
    0x8D64: PIXELFORMAT_ETC1,
    0x9274: PIXELFORMAT_ETC2_RGB,
    0x9278: PIXELFORMAT_ETC2_RGBA,
    0x8C00: PIXELFORMAT_PVRTC_4BPP_RGB_1,
    0x8C01: PIXELFORMAT_PVRTC_2BPP_RGB_1,
    0x8C02: PIXELFORMAT_PVRTC_4BPP_RGBA_1,
    0x8C03: PIXELFORMAT_PVRTC_2BPP_RGBA_1,
    // uncompressed formats
    0x8051: PIXELFORMAT_RGB8,
    0x8058: PIXELFORMAT_RGBA8,
    0x8C41: PIXELFORMAT_SRGB8,
    0x8C43: PIXELFORMAT_SRGBA8,
    0x8C3A: PIXELFORMAT_111110F,
    0x881B: PIXELFORMAT_RGB16F,
    0x881A: PIXELFORMAT_RGBA16F // GL_RGBA16F
};
function createContainer(pixelFormat, buffer, byteOffset, byteSize) {
    return pixelFormat === PIXELFORMAT_111110F ? new Uint32Array(buffer, byteOffset, byteSize / 4) : new Uint8Array(buffer, byteOffset, byteSize);
}
/**
 * Texture parser for ktx files.
 */ class KtxParser extends TextureParser {
    constructor(registry){
        super();
        this.maxRetries = 0;
    }
    load(url, callback, asset) {
        Asset.fetchArrayBuffer(url.load, callback, asset, this.maxRetries);
    }
    open(url, data, device, textureOptions = {}) {
        const textureData = this.parse(data);
        if (!textureData) {
            return null;
        }
        const format = textureOptions.srgb ? pixelFormatLinearToGamma(textureData.format) : textureData.format;
        const texture = new Texture(device, {
            name: url,
            profilerHint: TEXHINT_ASSET,
            addressU: textureData.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            addressV: textureData.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            width: textureData.width,
            height: textureData.height,
            format: format,
            cubemap: textureData.cubemap,
            levels: textureData.levels,
            ...textureOptions
        });
        texture.upload();
        return texture;
    }
    parse(data) {
        const dataU32 = new Uint32Array(data);
        // check magic bits
        if (IDENTIFIER[0] !== dataU32[0] || IDENTIFIER[1] !== dataU32[1] || IDENTIFIER[2] !== dataU32[2]) {
            Debug.warn('Invalid definition header found in KTX file. Expected 0xAB4B5458, 0x203131BB, 0x0D0A1A0A');
            return null;
        }
        // unpack header info
        const header = {
            glInternalFormat: dataU32[7],
            pixelWidth: dataU32[9],
            pixelHeight: dataU32[10],
            pixelDepth: dataU32[11],
            numberOfArrayElements: dataU32[12],
            numberOfFaces: dataU32[13],
            numberOfMipmapLevels: dataU32[14],
            bytesOfKeyValueData: dataU32[15]
        };
        // don't support volume textures
        if (header.pixelDepth > 1) {
            Debug.warn('More than 1 pixel depth not supported!');
            return null;
        }
        // don't support texture arrays
        if (header.numberOfArrayElements !== 0) {
            Debug.warn('Array texture not supported!');
            return null;
        }
        const format = KNOWN_FORMATS[header.glInternalFormat];
        // only support subset of pixel formats
        if (format === undefined) {
            Debug.warn(`Unknown glInternalFormat: ${header.glInternalFormat}`);
            return null;
        }
        // offset locating the first byte of texture level data
        let offset = 16 + header.bytesOfKeyValueData / 4;
        const isCubemap = header.numberOfFaces > 1;
        const levels = [];
        for(let mipmapLevel = 0; mipmapLevel < (header.numberOfMipmapLevels || 1); mipmapLevel++){
            const imageSizeInBytes = dataU32[offset++];
            if (isCubemap) {
                levels.push([]);
            }
            const target = isCubemap ? levels[mipmapLevel] : levels;
            for(let face = 0; face < (isCubemap ? 6 : 1); ++face){
                target.push(createContainer(format, data, offset * 4, imageSizeInBytes));
                offset += imageSizeInBytes + 3 >> 2;
            }
        }
        return {
            format: format,
            width: header.pixelWidth,
            height: header.pixelHeight,
            levels: levels,
            cubemap: isCubemap
        };
    }
}

const KHRConstants = {
    KHR_DF_MODEL_UASTC: 166
};
/**
 * Texture parser for ktx2 files.
 */ class Ktx2Parser extends TextureParser {
    constructor(registry, device){
        super();
        this.maxRetries = 0;
        this.device = device;
    }
    load(url, callback, asset) {
        Asset.fetchArrayBuffer(url.load, (err, result)=>{
            if (err) {
                callback(err, result);
            } else {
                this.parse(result, url, callback, asset);
            }
        }, asset, this.maxRetries);
    }
    open(url, data, device, textureOptions = {}) {
        const format = textureOptions.srgb ? pixelFormatLinearToGamma(data.format) : data.format;
        const texture = new Texture(device, {
            name: url,
            profilerHint: TEXHINT_ASSET,
            addressU: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            addressV: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            width: data.width,
            height: data.height,
            format: format,
            cubemap: data.cubemap,
            levels: data.levels,
            ...textureOptions
        });
        texture.upload();
        return texture;
    }
    parse(arraybuffer, url, callback, asset) {
        const rs = new ReadStream(arraybuffer);
        // check magic header bits:  '«', 'K', 'T', 'X', ' ', '2', '0', '»', '\r', '\n', '\x1A', '\n'\
        const magic = [
            rs.readU32be(),
            rs.readU32be(),
            rs.readU32be()
        ];
        if (magic[0] !== 0xAB4B5458 || magic[1] !== 0x203230BB || magic[2] !== 0x0D0A1A0A) {
            Debug.warn('Invalid definition header found in KTX2 file. Expected 0xAB4B5458, 0x203131BB, 0x0D0A1A0A');
            return null;
        }
        // unpack header
        const header = {
            vkFormat: rs.readU32(),
            typeSize: rs.readU32(),
            pixelWidth: rs.readU32(),
            pixelHeight: rs.readU32(),
            pixelDepth: rs.readU32(),
            layerCount: rs.readU32(),
            faceCount: rs.readU32(),
            levelCount: rs.readU32(),
            supercompressionScheme: rs.readU32()
        };
        // unpack index
        const index = {
            dfdByteOffset: rs.readU32(),
            dfdByteLength: rs.readU32(),
            kvdByteOffset: rs.readU32(),
            kvdByteLength: rs.readU32(),
            sgdByteOffset: rs.readU64(),
            sgdByteLength: rs.readU64()
        };
        // unpack levels
        const levels = [];
        for(let i = 0; i < Math.max(1, header.levelCount); ++i){
            levels.push({
                byteOffset: rs.readU64(),
                byteLength: rs.readU64(),
                uncompressedByteLength: rs.readU64()
            });
        }
        // unpack data format descriptor
        const dfdTotalSize = rs.readU32();
        if (dfdTotalSize !== index.kvdByteOffset - index.dfdByteOffset) {
            Debug.warn('Invalid file data encountered.');
            return null;
        }
        rs.skip(8);
        const colorModel = rs.readU8();
        rs.skip(index.dfdByteLength - 9);
        // skip key/value pairs
        rs.skip(index.kvdByteLength);
        if (header.supercompressionScheme === 1 || colorModel === KHRConstants.KHR_DF_MODEL_UASTC) {
            // assume for now all super compressed images are basis
            const basisModuleFound = basisTranscode(this.device, url.load, arraybuffer, callback, {
                isGGGR: (asset?.file?.variants?.basis?.opt & 8) !== 0,
                isKTX2: true
            });
            if (!basisModuleFound) {
                callback(`Basis module not found. Asset [${asset.name}](${asset.getFileUrl()}) basis texture variant will not be loaded.`);
            }
        } else {
            // TODO: load non-supercompressed formats
            callback('unsupported KTX2 pixel format');
        }
    }
}

/**
 * Legacy texture parser for dds files.
 */ class DdsParser extends TextureParser {
    constructor(registry){
        super();
        this.maxRetries = 0;
    }
    load(url, callback, asset) {
        Asset.fetchArrayBuffer(url.load, callback, asset, this.maxRetries);
    }
    open(url, data, device, textureOptions = {}) {
        const header = new Uint32Array(data, 0, 128 / 4);
        const width = header[4];
        const height = header[3];
        const mips = Math.max(header[7], 1);
        const isFourCc = header[20] === 4;
        const fcc = header[21];
        const bpp = header[22];
        const isCubemap = header[28] === 65024; // TODO: check by bitflag
        const FCC_DXT1 = 827611204; // DXT1
        const FCC_DXT5 = 894720068; // DXT5
        const FCC_FP16 = 113; // RGBA16f
        const FCC_FP32 = 116; // RGBA32f
        // non standard
        const FCC_ETC1 = 826496069;
        const FCC_PVRTC_2BPP_RGB_1 = 825438800;
        const FCC_PVRTC_2BPP_RGBA_1 = 825504336;
        const FCC_PVRTC_4BPP_RGB_1 = 825439312;
        const FCC_PVRTC_4BPP_RGBA_1 = 825504848;
        let compressed = false;
        let etc1 = false;
        let pvrtc2 = false;
        let pvrtc4 = false;
        let format = null;
        let componentSize = 1;
        let texture;
        if (isFourCc) {
            if (fcc === FCC_DXT1) {
                format = PIXELFORMAT_DXT1;
                compressed = true;
            } else if (fcc === FCC_DXT5) {
                format = PIXELFORMAT_DXT5;
                compressed = true;
            } else if (fcc === FCC_FP16) {
                format = PIXELFORMAT_RGBA16F;
                componentSize = 2;
            } else if (fcc === FCC_FP32) {
                format = PIXELFORMAT_RGBA32F;
                componentSize = 4;
            } else if (fcc === FCC_ETC1) {
                format = PIXELFORMAT_ETC1;
                compressed = true;
                etc1 = true;
            } else if (fcc === FCC_PVRTC_2BPP_RGB_1 || fcc === FCC_PVRTC_2BPP_RGBA_1) {
                format = fcc === FCC_PVRTC_2BPP_RGB_1 ? PIXELFORMAT_PVRTC_2BPP_RGB_1 : PIXELFORMAT_PVRTC_2BPP_RGBA_1;
                compressed = true;
                pvrtc2 = true;
            } else if (fcc === FCC_PVRTC_4BPP_RGB_1 || fcc === FCC_PVRTC_4BPP_RGBA_1) {
                format = fcc === FCC_PVRTC_4BPP_RGB_1 ? PIXELFORMAT_PVRTC_4BPP_RGB_1 : PIXELFORMAT_PVRTC_4BPP_RGBA_1;
                compressed = true;
                pvrtc4 = true;
            }
        } else {
            if (bpp === 32) {
                format = PIXELFORMAT_RGBA8;
            }
        }
        if (!format) {
            Debug.error(`This DDS pixel format is currently unsupported. Empty texture will be created instead of ${url}.`);
            texture = new Texture(device, {
                width: 4,
                height: 4,
                format: PIXELFORMAT_RGB8,
                name: 'dds-legacy-empty'
            });
            return texture;
        }
        texture = new Texture(device, {
            name: url,
            profilerHint: TEXHINT_ASSET,
            addressU: isCubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            addressV: isCubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
            width: width,
            height: height,
            format: format,
            cubemap: isCubemap,
            mipmaps: mips > 1,
            ...textureOptions
        });
        let offset = 128;
        const faces = isCubemap ? 6 : 1;
        let mipSize;
        const DXT_BLOCK_WIDTH = 4;
        const DXT_BLOCK_HEIGHT = 4;
        const blockSize = fcc === FCC_DXT1 ? 8 : 16;
        let numBlocksAcross, numBlocksDown, numBlocks;
        for(let face = 0; face < faces; face++){
            let mipWidth = width;
            let mipHeight = height;
            for(let i = 0; i < mips; i++){
                if (compressed) {
                    if (etc1) {
                        mipSize = Math.floor((mipWidth + 3) / 4) * Math.floor((mipHeight + 3) / 4) * 8;
                    } else if (pvrtc2) {
                        mipSize = Math.max(mipWidth, 16) * Math.max(mipHeight, 8) / 4;
                    } else if (pvrtc4) {
                        mipSize = Math.max(mipWidth, 8) * Math.max(mipHeight, 8) / 2;
                    } else {
                        numBlocksAcross = Math.floor((mipWidth + DXT_BLOCK_WIDTH - 1) / DXT_BLOCK_WIDTH);
                        numBlocksDown = Math.floor((mipHeight + DXT_BLOCK_HEIGHT - 1) / DXT_BLOCK_HEIGHT);
                        numBlocks = numBlocksAcross * numBlocksDown;
                        mipSize = numBlocks * blockSize;
                    }
                } else {
                    mipSize = mipWidth * mipHeight * 4;
                }
                const mipBuff = format === PIXELFORMAT_RGBA32F ? new Float32Array(data, offset, mipSize) : format === PIXELFORMAT_RGBA16F ? new Uint16Array(data, offset, mipSize) : new Uint8Array(data, offset, mipSize);
                if (!isCubemap) {
                    texture._levels[i] = mipBuff;
                } else {
                    if (!texture._levels[i]) texture._levels[i] = [];
                    texture._levels[i][face] = mipBuff;
                }
                offset += mipSize * componentSize;
                mipWidth = Math.max(mipWidth * 0.5, 1);
                mipHeight = Math.max(mipHeight * 0.5, 1);
            }
        }
        texture.upload();
        return texture;
    }
}

/**
 * Texture parser for hdr files.
 */ class HdrParser extends TextureParser {
    constructor(registry){
        super();
        this.maxRetries = 0;
    }
    load(url, callback, asset) {
        Asset.fetchArrayBuffer(url.load, callback, asset, this.maxRetries);
        // .hdr assets should be loaded with 'rgbe' type, but historically they were not, so set a default type here
        if (asset.data && !asset.data.type) {
            asset.data.type = TEXTURETYPE_RGBE;
        }
    }
    open(url, data, device, textureOptions = {}) {
        const textureData = this.parse(data);
        if (!textureData) {
            return null;
        }
        const texture = new Texture(device, {
            name: url,
            profilerHint: TEXHINT_ASSET,
            addressU: ADDRESS_REPEAT,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            width: textureData.width,
            height: textureData.height,
            levels: textureData.levels,
            format: PIXELFORMAT_RGBA8,
            type: TEXTURETYPE_RGBE,
            // RGBE can't be filtered, so mipmaps are out of the question! (unless we generated them ourselves)
            mipmaps: false,
            ...textureOptions
        });
        texture.upload();
        return texture;
    }
    // https://floyd.lbl.gov/radiance/refer/filefmts.pdf with help from http://www.graphics.cornell.edu/~bjw/rgbe/rgbe.c
    parse(data) {
        const readStream = new ReadStream(data);
        // require magic
        const magic = readStream.readLine();
        if (!magic.startsWith('#?RADIANCE')) {
            Debug.error('radiance header has invalid magic');
            return null;
        }
        // read header variables
        const variables = {};
        while(true){
            const line = readStream.readLine();
            if (line.length === 0) {
                break;
            } else {
                const parts = line.split('=');
                if (parts.length === 2) {
                    variables[parts[0]] = parts[1];
                }
            }
        }
        // we require FORMAT variable
        if (!variables.hasOwnProperty('FORMAT')) {
            Debug.error('radiance header missing FORMAT variable');
            return null;
        }
        // read the resolution specifier
        const resolution = readStream.readLine().split(' ');
        if (resolution.length !== 4) {
            Debug.error('radiance header has invalid resolution');
            return null;
        }
        const height = parseInt(resolution[1], 10);
        const width = parseInt(resolution[3], 10);
        const pixels = this._readPixels(readStream, width, height, resolution[0] === '-Y');
        if (!pixels) {
            return null;
        }
        // create texture
        return {
            width: width,
            height: height,
            levels: [
                pixels
            ]
        };
    }
    _readPixels(readStream, width, height, flipY) {
        // out of bounds
        if (width < 8 || width > 0x7fff) {
            return this._readPixelsFlat(readStream, width, height);
        }
        const rgbe = [
            0,
            0,
            0,
            0
        ];
        // check first scanline width to determine whether the file is RLE
        readStream.readArray(rgbe);
        if (rgbe[0] !== 2 || rgbe[1] !== 2 || (rgbe[2] & 0x80) !== 0) {
            // not RLE
            readStream.skip(-4);
            return this._readPixelsFlat(readStream, width, height);
        }
        // allocate texture buffer
        const buffer = new ArrayBuffer(width * height * 4);
        const view = new Uint8Array(buffer);
        let scanstart = flipY ? 0 : width * 4 * (height - 1);
        let x, y, i, channel, count, value;
        for(y = 0; y < height; ++y){
            // read scanline width specifier
            if (y) {
                readStream.readArray(rgbe);
            }
            // sanity check it
            if ((rgbe[2] << 8) + rgbe[3] !== width) {
                Debug.error('radiance has invalid scanline width');
                return null;
            }
            // each scanline is stored by channel
            for(channel = 0; channel < 4; ++channel){
                x = 0;
                while(x < width){
                    count = readStream.readU8();
                    if (count > 128) {
                        // run of the same value
                        count -= 128;
                        if (x + count > width) {
                            Debug.error('radiance has invalid scanline data');
                            return null;
                        }
                        value = readStream.readU8();
                        for(i = 0; i < count; ++i){
                            view[scanstart + channel + 4 * x++] = value;
                        }
                    } else {
                        // non-run
                        if (count === 0 || x + count > width) {
                            Debug.error('radiance has invalid scanline data');
                            return null;
                        }
                        for(i = 0; i < count; ++i){
                            view[scanstart + channel + 4 * x++] = readStream.readU8();
                        }
                    }
                }
            }
            scanstart += width * 4 * (flipY ? 1 : -1);
        }
        return view;
    }
    _readPixelsFlat(readStream, width, height) {
        return readStream.remainingBytes === width * height * 4 ? new Uint8Array(readStream.arraybuffer, readStream.offset) : null;
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 */ const JSON_ADDRESS_MODE = {
    'repeat': ADDRESS_REPEAT,
    'clamp': ADDRESS_CLAMP_TO_EDGE,
    'mirror': ADDRESS_MIRRORED_REPEAT
};
const JSON_FILTER_MODE = {
    'nearest': FILTER_NEAREST,
    'linear': FILTER_LINEAR,
    'nearest_mip_nearest': FILTER_NEAREST_MIPMAP_NEAREST,
    'linear_mip_nearest': FILTER_LINEAR_MIPMAP_NEAREST,
    'nearest_mip_linear': FILTER_NEAREST_MIPMAP_LINEAR,
    'linear_mip_linear': FILTER_LINEAR_MIPMAP_LINEAR
};
const JSON_TEXTURE_TYPE = {
    'default': TEXTURETYPE_DEFAULT,
    'rgbm': TEXTURETYPE_RGBM,
    'rgbe': TEXTURETYPE_RGBE,
    'rgbp': TEXTURETYPE_RGBP,
    'swizzleGGGR': TEXTURETYPE_SWIZZLEGGGR
};
// In the case where a texture has more than 1 level of mip data specified, but not the full
// mip chain, we generate the missing levels here.
// This is to overcome an issue where iphone xr and xs ignores further updates to the mip data
// after invoking gl.generateMipmap on the texture (which was the previous method of ensuring
// the texture's full mip chain was complete).
// NOTE: this function only resamples RGBA8 and RGBAFloat32 data.
const _completePartialMipmapChain = function(texture) {
    const requiredMipLevels = TextureUtils.calcMipLevelsCount(texture._width, texture._height);
    const isHtmlElement = function(object) {
        return object instanceof HTMLCanvasElement || object instanceof HTMLImageElement || object instanceof HTMLVideoElement;
    };
    if (!(texture._format === PIXELFORMAT_RGBA8 || texture._format === PIXELFORMAT_RGBA32F) || texture._volume || texture._compressed || texture._levels.length === 1 || texture._levels.length === requiredMipLevels || isHtmlElement(texture._cubemap ? texture._levels[0][0] : texture._levels[0])) {
        return;
    }
    const downsample = function(width, height, data) {
        const sampledWidth = Math.max(1, width >> 1);
        const sampledHeight = Math.max(1, height >> 1);
        const sampledData = new data.constructor(sampledWidth * sampledHeight * 4);
        const xs = Math.floor(width / sampledWidth);
        const ys = Math.floor(height / sampledHeight);
        const xsys = xs * ys;
        for(let y = 0; y < sampledHeight; ++y){
            for(let x = 0; x < sampledWidth; ++x){
                for(let e = 0; e < 4; ++e){
                    let sum = 0;
                    for(let sy = 0; sy < ys; ++sy){
                        for(let sx = 0; sx < xs; ++sx){
                            sum += data[(x * xs + sx + (y * ys + sy) * width) * 4 + e];
                        }
                    }
                    sampledData[(x + y * sampledWidth) * 4 + e] = sum / xsys;
                }
            }
        }
        return sampledData;
    };
    // step through levels
    for(let level = texture._levels.length; level < requiredMipLevels; ++level){
        const width = Math.max(1, texture._width >> level - 1);
        const height = Math.max(1, texture._height >> level - 1);
        if (texture._cubemap) {
            const mips = [];
            for(let face = 0; face < 6; ++face){
                mips.push(downsample(width, height, texture._levels[level - 1][face]));
            }
            texture._levels.push(mips);
        } else {
            texture._levels.push(downsample(width, height, texture._levels[level - 1]));
        }
    }
    texture._levelsUpdated = texture._cubemap ? [
        [
            true,
            true,
            true,
            true,
            true,
            true
        ]
    ] : [
        true
    ];
};
/**
 * Resource handler used for loading 2D and 3D {@link Texture} resources.
 *
 * @category Graphics
 */ class TextureHandler extends ResourceHandler {
    /**
     * Create a new TextureHandler instance.
     *
     * @param {AppBase} app - The running {@link AppBase}.
     * @ignore
     */ constructor(app){
        super(app, 'texture');
        const assets = app.assets;
        const device = app.graphicsDevice;
        this._device = device;
        this._assets = assets;
        // img parser handles all browser-supported image formats, this
        // parser will be used when other more specific parsers are not found.
        this.imgParser = new ImgParser(assets, device);
        this.parsers = {
            dds: new DdsParser(assets),
            ktx: new KtxParser(assets),
            ktx2: new Ktx2Parser(assets, device),
            basis: new BasisParser(assets, device),
            hdr: new HdrParser(assets)
        };
    }
    set crossOrigin(value) {
        this.imgParser.crossOrigin = value;
    }
    get crossOrigin() {
        return this.imgParser.crossOrigin;
    }
    set maxRetries(value) {
        this.imgParser.maxRetries = value;
        for(const parser in this.parsers){
            if (this.parsers.hasOwnProperty(parser)) {
                this.parsers[parser].maxRetries = value;
            }
        }
    }
    get maxRetries() {
        return this.imgParser.maxRetries;
    }
    _getUrlWithoutParams(url) {
        return url.indexOf('?') >= 0 ? url.split('?')[0] : url;
    }
    _getParser(url) {
        const ext = path$1.getExtension(this._getUrlWithoutParams(url)).toLowerCase().replace('.', '');
        return this.parsers[ext] || this.imgParser;
    }
    _getTextureOptions(asset) {
        const options = {
            profilerHint: TEXHINT_ASSET
        };
        if (asset) {
            if (asset.name?.length > 0) {
                options.name = asset.name;
            }
            const assetData = asset.data;
            if (assetData.hasOwnProperty('minfilter')) {
                options.minFilter = JSON_FILTER_MODE[assetData.minfilter];
            }
            if (assetData.hasOwnProperty('magfilter')) {
                options.magFilter = JSON_FILTER_MODE[assetData.magfilter];
            }
            if (assetData.hasOwnProperty('addressu')) {
                options.addressU = JSON_ADDRESS_MODE[assetData.addressu];
            }
            if (assetData.hasOwnProperty('addressv')) {
                options.addressV = JSON_ADDRESS_MODE[assetData.addressv];
            }
            if (assetData.hasOwnProperty('mipmaps')) {
                options.mipmaps = assetData.mipmaps;
            }
            if (assetData.hasOwnProperty('anisotropy')) {
                options.anisotropy = assetData.anisotropy;
            }
            if (assetData.hasOwnProperty('flipY')) {
                options.flipY = !!assetData.flipY;
            }
            if (assetData.hasOwnProperty('srgb')) {
                options.srgb = !!assetData.srgb;
            }
            // extract asset type (this is bit of a mess)
            options.type = TEXTURETYPE_DEFAULT;
            if (assetData.hasOwnProperty('type')) {
                options.type = JSON_TEXTURE_TYPE[assetData.type];
            } else if (assetData.hasOwnProperty('rgbm') && assetData.rgbm) {
                options.type = TEXTURETYPE_RGBM;
            } else if (asset.file && (asset.file.opt & 8) !== 0) {
                // basis normalmaps flag the variant as swizzled
                options.type = TEXTURETYPE_SWIZZLEGGGR;
            }
        }
        return options;
    }
    load(url, callback, asset) {
        if (typeof url === 'string') {
            url = {
                load: url,
                original: url
            };
        }
        this._getParser(url.original).load(url, callback, asset);
    }
    open(url, data, asset) {
        if (!url) {
            return undefined;
        }
        const textureOptions = this._getTextureOptions(asset);
        let texture = this._getParser(url).open(url, data, this._device, textureOptions);
        if (texture === null) {
            texture = new Texture(this._device, {
                width: 4,
                height: 4,
                format: PIXELFORMAT_RGB8
            });
        } else {
            // check if the texture has only a partial mipmap chain specified and generate the
            // missing levels if possible.
            _completePartialMipmapChain(texture);
            // if the basis transcoder unswizzled a GGGR texture, remove the flag from the asset
            if (data.unswizzledGGGR) {
                asset.file.variants.basis.opt &= -9;
            }
        }
        return texture;
    }
    patch(asset, assets) {
        const texture = asset.resource;
        if (!texture) {
            return;
        }
        // apply asset options, based on asset.data
        const options = this._getTextureOptions(asset);
        for (const key of Object.keys(options)){
            texture[key] = options[key];
        }
    }
}

/**
 * @import { BindGroup } from '../../platform/graphics/bind-group.js'
 */ const tempMeshInstances = [];
const lights = [
    [],
    [],
    []
];
/**
 * A render pass implementing rendering of mesh instances into a pick buffer.
 *
 * @ignore
 */ class RenderPassPicker extends RenderPass {
    constructor(device, renderer){
        super(device), /** @type {BindGroup[]} */ this.viewBindGroups = [];
        this.renderer = renderer;
    }
    destroy() {
        this.viewBindGroups.forEach((bg)=>{
            bg.defaultUniformBuffer.destroy();
            bg.destroy();
        });
        this.viewBindGroups.length = 0;
    }
    update(camera, scene, layers, mapping) {
        this.camera = camera;
        this.scene = scene;
        this.layers = layers;
        this.mapping = mapping;
        if (scene.clusteredLightingEnabled) {
            this.emptyWorldClusters = this.renderer.worldClustersAllocator.empty;
        }
    }
    execute() {
        const device = this.device;
        const { renderer, camera, scene, layers, mapping, renderTarget } = this;
        const srcLayers = scene.layers.layerList;
        const subLayerEnabled = scene.layers.subLayerEnabled;
        const isTransparent = scene.layers.subLayerList;
        for(let i = 0; i < srcLayers.length; i++){
            const srcLayer = srcLayers[i];
            // skip the layer if it does not match the provided ones
            if (layers && layers.indexOf(srcLayer) < 0) {
                continue;
            }
            if (srcLayer.enabled && subLayerEnabled[i]) {
                // if the layer is rendered by the camera
                if (srcLayer.camerasSet.has(camera.camera)) {
                    const transparent = isTransparent[i];
                    DebugGraphics.pushGpuMarker(device, `${srcLayer.name}(${transparent ? 'TRANSP' : 'OPAQUE'})`);
                    // if the layer clears the depth
                    if (srcLayer._clearDepthBuffer) {
                        renderer.clear(camera.camera, false, true, false);
                    }
                    // Use mesh instances from the layer. Ideally we'd just pick culled instances for the camera,
                    // but we have no way of knowing if culling has been performed since changes to the layer.
                    // Disadvantage here is that we render all mesh instances, even those not visible by the camera.
                    const meshInstances = srcLayer.meshInstances;
                    // only need mesh instances with a pick flag
                    for(let j = 0; j < meshInstances.length; j++){
                        const meshInstance = meshInstances[j];
                        if (meshInstance.pick && meshInstance.transparent === transparent) {
                            tempMeshInstances.push(meshInstance);
                            // keep the index -> meshInstance index mapping
                            mapping.set(meshInstance.id, meshInstance);
                        }
                    }
                    if (tempMeshInstances.length > 0) {
                        // upload clustered lights uniforms
                        const clusteredLightingEnabled = scene.clusteredLightingEnabled;
                        if (clusteredLightingEnabled) {
                            const lightClusters = this.emptyWorldClusters;
                            lightClusters.activate();
                        }
                        renderer.setCameraUniforms(camera.camera, renderTarget);
                        if (device.supportsUniformBuffers) {
                            renderer.setupViewUniformBuffers(this.viewBindGroups, renderer.viewUniformFormat, renderer.viewBindGroupFormat, null);
                        }
                        renderer.renderForward(camera.camera, renderTarget, tempMeshInstances, lights, SHADER_PICK, (meshInstance)=>{
                            device.setBlendState(BlendState.NOBLEND);
                        });
                        tempMeshInstances.length = 0;
                    }
                    DebugGraphics.popGpuMarker(device);
                }
            }
        }
    }
}

/**
 * @import { AppBase } from '../app-base.js'
 * @import { CameraComponent } from '../components/camera/component.js'
 * @import { MeshInstance } from '../../scene/mesh-instance.js'
 * @import { Scene } from '../../scene/scene.js'
 */ const tempSet = new Set();
const _rect = new Vec4();
/**
 * Picker object used to select mesh instances from screen coordinates.
 *
 * @property {number} width Width of the pick buffer in pixels (read-only).
 * @property {number} height Height of the pick buffer in pixels (read-only).
 * @property {RenderTarget} renderTarget The render target used by the picker internally
 * (read-only).
 *
 * @category Graphics
 */ class Picker {
    /**
     * Create a new Picker instance.
     *
     * @param {AppBase} app - The application managing this picker instance.
     * @param {number} width - The width of the pick buffer in pixels.
     * @param {number} height - The height of the pick buffer in pixels.
     */ constructor(app, width, height){
        // internal render target
        this.renderTarget = null;
        // mapping table from ids to meshInstances
        this.mapping = new Map();
        // when the device is destroyed, this allows us to ignore async results
        this.deviceValid = true;
        Debug.assert(app);
        // Note: The only reason this class needs the app is to access the renderer. Ideally we remove this dependency and move
        // the Picker from framework to the scene level, or even the extras.
        this.renderer = app.renderer;
        this.device = app.graphicsDevice;
        this.renderPass = new RenderPassPicker(this.device, app.renderer);
        this.width = 0;
        this.height = 0;
        this.resize(width, height);
        // handle the device getting destroyed
        this.device.on('destroy', ()=>{
            this.deviceValid = false;
        });
    }
    /**
     * Return the list of mesh instances selected by the specified rectangle in the previously
     * prepared pick buffer. The rectangle using top-left coordinate system.
     *
     * Note: This function is not supported on WebGPU. Use {@link Picker#getSelectionAsync} instead.
     * Note: This function is blocks the main thread while reading pixels from GPU memory. It's
     * recommended to use {@link Picker#getSelectionAsync} instead.
     *
     * @param {number} x - The left edge of the rectangle.
     * @param {number} y - The top edge of the rectangle.
     * @param {number} [width] - The width of the rectangle. Defaults to 1.
     * @param {number} [height] - The height of the rectangle. Defaults to 1.
     * @returns {MeshInstance[]} An array of mesh instances that are in the selection.
     * @example
     * // Get the selection at the point (10,20)
     * const selection = picker.getSelection(10, 20);
     * @example
     * // Get all models in rectangle with corners at (10,20) and (20,40)
     * const selection = picker.getSelection(10, 20, 10, 20);
     */ getSelection(x, y, width = 1, height = 1) {
        const device = this.device;
        if (device.isWebGPU) {
            Debug.errorOnce('pc.Picker#getSelection is not supported on WebGPU, use pc.Picker#getSelectionAsync instead.');
            return [];
        }
        Debug.assert(typeof x !== 'object', 'Picker.getSelection:param \'rect\' is deprecated, use \'x, y, width, height\' instead.');
        y = this.renderTarget.height - (y + height);
        const rect = this.sanitizeRect(x, y, width, height);
        // read pixels from the render target
        device.setRenderTarget(this.renderTarget);
        device.updateBegin();
        const pixels = new Uint8Array(4 * rect.z * rect.w);
        device.readPixels(rect.x, rect.y, rect.z, rect.w, pixels);
        device.updateEnd();
        return this.decodePixels(pixels, this.mapping);
    }
    /**
     * Return the list of mesh instances selected by the specified rectangle in the previously
     * prepared pick buffer. The rectangle uses top-left coordinate system.
     *
     * This method is asynchronous and does not block the execution.
     *
     * @param {number} x - The left edge of the rectangle.
     * @param {number} y - The top edge of the rectangle.
     * @param {number} [width] - The width of the rectangle. Defaults to 1.
     * @param {number} [height] - The height of the rectangle. Defaults to 1.
     * @returns {Promise<MeshInstance[]>} - Promise that resolves with an array of mesh instances
     * that are in the selection.
     * @example
     * // Get the mesh instances at the rectangle with start at (10,20) and size of (5,5)
     * picker.getSelectionAsync(10, 20, 5, 5).then((meshInstances) => {
     *    console.log(meshInstances);
     * });
     */ getSelectionAsync(x, y, width = 1, height = 1) {
        if (this.device?.isWebGL2) {
            y = this.renderTarget.height - (y + height);
        }
        const rect = this.sanitizeRect(x, y, width, height);
        return this.renderTarget.colorBuffer.read(rect.x, rect.y, rect.z, rect.w, {
            renderTarget: this.renderTarget,
            immediate: true
        }).then((pixels)=>{
            return this.decodePixels(pixels, this.mapping);
        });
    }
    // sanitize the rectangle to make sure it;s inside the texture and does not use fractions
    sanitizeRect(x, y, width, height) {
        const maxWidth = this.renderTarget.width;
        const maxHeight = this.renderTarget.height;
        x = math.clamp(Math.floor(x), 0, maxWidth - 1);
        y = math.clamp(Math.floor(y), 0, maxHeight - 1);
        width = Math.floor(Math.max(width, 1));
        width = Math.min(width, maxWidth - x);
        height = Math.floor(Math.max(height, 1));
        height = Math.min(height, maxHeight - y);
        return _rect.set(x, y, width, height);
    }
    decodePixels(pixels, mapping) {
        const selection = [];
        // when we decode results from async calls, ignore them if the device is no longer valid
        if (this.deviceValid) {
            const count = pixels.length;
            for(let i = 0; i < count; i += 4){
                const r = pixels[i + 0];
                const g = pixels[i + 1];
                const b = pixels[i + 2];
                const a = pixels[i + 3];
                const index = (a << 24 | r << 16 | g << 8 | b) >>> 0;
                // White is 'no selection
                if (index !== 0xFFFFFFFF) {
                    tempSet.add(mapping.get(index));
                }
            }
            // return the content of the set as an array
            tempSet.forEach((meshInstance)=>{
                if (meshInstance) {
                    selection.push(meshInstance);
                }
            });
            tempSet.clear();
        }
        return selection;
    }
    allocateRenderTarget() {
        // TODO: Ideally we'd use a UINT32 texture format and avoid RGBA8 conversion, but WebGL2 does not
        // support clearing render targets of this format, so we'd need a quad based clear solution.
        const colorBuffer = new Texture(this.device, {
            format: PIXELFORMAT_RGBA8,
            width: this.width,
            height: this.height,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE,
            name: 'pick'
        });
        this.renderTarget = new RenderTarget({
            colorBuffer: colorBuffer,
            depth: true
        });
    }
    releaseRenderTarget() {
        if (this.renderTarget) {
            this.renderTarget.destroyTextureBuffers();
            this.renderTarget.destroy();
            this.renderTarget = null;
        }
    }
    /**
     * Primes the pick buffer with a rendering of the specified models from the point of view of
     * the supplied camera. Once the pick buffer has been prepared, {@link Picker#getSelection} can
     * be called multiple times on the same picker object. Therefore, if the models or camera do
     * not change in any way, {@link Picker#prepare} does not need to be called again.
     *
     * @param {CameraComponent} camera - The camera component used to render the scene.
     * @param {Scene} scene - The scene containing the pickable mesh instances.
     * @param {Layer[]} [layers] - Layers from which objects will be picked. If not supplied, all
     * layers of the specified camera will be used.
     */ prepare(camera, scene, layers) {
        if (layers instanceof Layer) {
            layers = [
                layers
            ];
        }
        // make the render target the right size
        if (!this.renderTarget || this.width !== this.renderTarget.width || this.height !== this.renderTarget.height) {
            this.releaseRenderTarget();
            this.allocateRenderTarget();
        }
        // clear registered meshes mapping
        this.mapping.clear();
        const renderPass = this.renderPass;
        renderPass.init(this.renderTarget);
        // set up clears
        renderPass.colorOps.clearValue = Color.WHITE;
        renderPass.colorOps.clear = true;
        renderPass.depthStencilOps.clearDepth = true;
        // render the pass to update the render target
        renderPass.update(camera, scene, layers, this.mapping);
        renderPass.render();
    }
    /**
     * Sets the resolution of the pick buffer. The pick buffer resolution does not need to match
     * the resolution of the corresponding frame buffer use for general rendering of the 3D scene.
     * However, the lower the resolution of the pick buffer, the less accurate the selection
     * results returned by {@link Picker#getSelection}. On the other hand, smaller pick buffers
     * will yield greater performance, so there is a trade off.
     *
     * @param {number} width - The width of the pick buffer in pixels.
     * @param {number} height - The height of the pick buffer in pixels.
     */ resize(width, height) {
        this.width = Math.floor(width);
        this.height = Math.floor(height);
    }
}

/**
 * @import { AppBase } from '../../framework/app-base.js'
 * @import { CameraComponent } from '../../framework/components/camera/component.js';
 * @import { GraphNode } from '../../scene/graph-node.js'
 * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
 * @import { MeshInstance } from '../../scene/mesh-instance.js'
 * @import { EventHandle } from '../../core/event-handle.js'
 * @import { Shape } from './shape/shape.js'
 * @import { GizmoSpace } from './constants.js'
 */ // temporary variables
const v$4 = new Vec3();
const position = new Vec3();
const angles = new Vec3();
const dir = new Vec3();
const m1 = new Mat4();
const m2 = new Mat4();
const ray$2 = new Ray();
// constants
const MIN_SCALE = 1e-4;
const PERS_SCALE_RATIO = 0.3;
const ORTHO_SCALE_RATIO = 0.32;
const UPDATE_EPSILON$2 = 1e-6;
const DIST_EPSILON = 1e-4;
/**
 * The base class for all gizmos.
 *
 * @category Gizmo
 */ class Gizmo extends EventHandler {
    static{
        /**
     * Fired when the pointer is down on the gizmo.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('pointer:down', (x, y, meshInstance) => {
     *     console.log(`Pointer was down on ${meshInstance.node.name} at ${x}, ${y}`);
     * });
     */ this.EVENT_POINTERDOWN = 'pointer:down';
    }
    static{
        /**
     * Fired when the pointer is moving over the gizmo.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('pointer:move', (x, y, meshInstance) => {
     *     console.log(`Pointer was moving on ${meshInstance.node.name} at ${x}, ${y}`);
     * });
     */ this.EVENT_POINTERMOVE = 'pointer:move';
    }
    static{
        /**
     * Fired when the pointer is up off the gizmo.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('pointer:up', (x, y, meshInstance) => {
     *     console.log(`Pointer was up on ${meshInstance.node.name} at ${x}, ${y}`);
     * })
     */ this.EVENT_POINTERUP = 'pointer:up';
    }
    static{
        /**
     * Fired when the gizmo's position is updated.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('position:update', (position) => {
     *     console.log(`The gizmo's position was updated to ${position}`);
     * })
     */ this.EVENT_POSITIONUPDATE = 'position:update';
    }
    static{
        /**
     * Fired when the gizmo's rotation is updated.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('rotation:update', (rotation) => {
     *     console.log(`The gizmo's rotation was updated to ${rotation}`);
     * });
     */ this.EVENT_ROTATIONUPDATE = 'rotation:update';
    }
    static{
        /**
     * Fired when the gizmo's scale is updated.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('scale:update', (scale) => {
     *     console.log(`The gizmo's scale was updated to ${scale}`);
     * });
     */ this.EVENT_SCALEUPDATE = 'scale:update';
    }
    static{
        /**
     * Fired when graph nodes are attached.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('nodes:attach', () => {
     *     console.log('Graph nodes attached');
     * });
     */ this.EVENT_NODESATTACH = 'nodes:attach';
    }
    static{
        /**
     * Fired when graph nodes are detached.
     *
     * @event
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.on('nodes:detach', () => {
     *     console.log('Graph nodes detached');
     * });
     */ this.EVENT_NODESDETACH = 'nodes:detach';
    }
    static{
        /**
     * Fired when when the gizmo render has updated.
     *
     * @event
     * @example
     * const gizmo = new pc.TransformGizmo(camera, layer);
     * gizmo.on('render:update', () => {
     *     console.log('Gizmo render has been updated');
     * });
     */ this.EVENT_RENDERUPDATE = 'render:update';
    }
    /**
     * Creates a new gizmo layer and adds it to the scene.
     *
     * @param {AppBase} app - The app.
     * @param {string} [layerName] - The layer name. Defaults to 'Gizmo'.
     * @param {number} [layerIndex] - The layer index. Defaults to the end of the layer list.
     * @returns {Layer} The new layer.
     */ static createLayer(app, layerName = 'Gizmo', layerIndex = app.scene.layers.layerList.length) {
        const layer = new Layer({
            name: layerName,
            clearDepthBuffer: true,
            opaqueSortMode: SORTMODE_NONE,
            transparentSortMode: SORTMODE_NONE
        });
        app.scene.layers.insert(layer, layerIndex);
        return layer;
    }
    /**
     * Creates a new Gizmo object.
     *
     * @param {CameraComponent} camera - The camera component.
     * @param {Layer} layer - The render layer. This can be provided by the user or will be created
     * and added to the scene and camera if not provided. Successive gizmos will share the same layer
     * and will be removed from the camera and scene when the last gizmo is destroyed.
     * @param {string} [name] - The name of the gizmo. Defaults to 'gizmo'.
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     */ constructor(camera, layer, name = 'gizmo'){
        super(), /**
     * Internal version of the gizmo size. Defaults to 1.
     *
     * @type {number}
     * @private
     */ this._size = 1, /**
     * Internal version of the gizmo scale. Defaults to 1.
     *
     * @type {number}
     * @protected
     */ this._scale = 1, /**
     * Internal version of coordinate space. Defaults to 'world'.
     *
     * @type {GizmoSpace}
     * @protected
     */ this._coordSpace = 'world', /**
     * Internal list of app event handles for the gizmo.
     *
     * @type {EventHandle[]}
     * @protected
     */ this._handles = [], /**
     * Internal flag to track if a render update is required.
     *
     * @type {boolean}
     * @protected
     */ this._renderUpdate = false, /**
     * The graph nodes attached to the gizmo.
     *
     * @type {GraphNode[]}
     */ this.nodes = [], /**
     * The intersection shapes for the gizmo.
     *
     * @type {Shape[]}
     */ this.intersectShapes = [];
        this._layer = layer;
        this._camera = camera;
        this._camera.layers = this._camera.layers.concat(this._layer.id);
        this._app = this._camera.system.app;
        this._device = this._app.graphicsDevice;
        this.root = new Entity(name);
        this._app.root.addChild(this.root);
        this.root.enabled = false;
        this._updateScale();
        this._onPointerDown = this._onPointerDown.bind(this);
        this._onPointerMove = this._onPointerMove.bind(this);
        this._onPointerUp = this._onPointerUp.bind(this);
        this._device.canvas.addEventListener('pointerdown', this._onPointerDown);
        this._device.canvas.addEventListener('pointermove', this._onPointerMove);
        this._device.canvas.addEventListener('pointerup', this._onPointerUp);
        this._handles.push(this._app.on('prerender', ()=>this.prerender()));
        this._handles.push(this._app.on('update', ()=>this.update()));
        this._handles.push(this._app.on('destroy', ()=>this.destroy()));
    }
    /**
     * Sets the gizmo enabled state.
     *
     * @type {boolean}
     */ set enabled(state) {
        const cameraDist = this.root.getLocalPosition().distance(this.camera.entity.getPosition());
        const enabled = state ? this.nodes.length > 0 && cameraDist > DIST_EPSILON : false;
        if (enabled !== this.root.enabled) {
            this.root.enabled = enabled;
            this._renderUpdate = true;
        }
    }
    /**
     * Gets the gizmo enabled state.
     *
     * @type {boolean}
     */ get enabled() {
        return this.root.enabled;
    }
    /**
     * Sets the gizmo render layer.
     *
     * @param {Layer} layer - The layer to render the gizmo.
     */ set layer(layer) {
        if (this._layer === layer) {
            return;
        }
        this._camera.layers = this._camera.layers.filter((id)=>id !== this._layer.id);
        this._layer = layer;
        this._camera.layers = this._camera.layers.concat(this._layer.id);
        this.enabled = true;
    }
    /**
     * Gets the gizmo render layer.
     *
     * @type {Layer}
     */ get layer() {
        return this._layer;
    }
    /**
     * Sets the camera component to view the gizmo.
     *
     * @type {CameraComponent} camera - The camera component.
     */ set camera(camera) {
        if (this._camera === camera) {
            return;
        }
        this._camera.layers = this._camera.layers.filter((id)=>id !== this._layer.id);
        this._camera = camera;
        this._camera.layers = this._camera.layers.concat(this._layer.id);
        this.enabled = true;
    }
    /**
     * Gets the camera component to view the gizmo.
     *
     * @type {CameraComponent} The camera component.
     */ get camera() {
        return this._camera;
    }
    /**
     * Sets the gizmo coordinate space. Defaults to 'world'
     *
     * @type {GizmoSpace}
     */ set coordSpace(value) {
        this._coordSpace = value ?? this._coordSpace;
        this._updateRotation();
    }
    /**
     * Gets the gizmo coordinate space.
     *
     * @type {GizmoSpace}
     */ get coordSpace() {
        return this._coordSpace;
    }
    /**
     * Sets the gizmo size. Defaults to 1.
     *
     * @type {number}
     */ set size(value) {
        this._size = value;
        this._updateScale();
    }
    /**
     * Gets the gizmo size.
     *
     * @type {number}
     */ get size() {
        return this._size;
    }
    /**
     * @type {Vec3}
     * @protected
     */ get facingDir() {
        if (this._camera.projection === PROJECTION_PERSPECTIVE) {
            const gizmoPos = this.root.getLocalPosition();
            const cameraPos = this._camera.entity.getPosition();
            return dir.sub2(cameraPos, gizmoPos).normalize();
        }
        return dir.copy(this._camera.entity.forward).mulScalar(-1);
    }
    /**
     * @type {Vec3}
     * @protected
     */ get cameraDir() {
        const cameraPos = this._camera.entity.getPosition();
        const gizmoPos = this.root.getLocalPosition();
        return dir.sub2(cameraPos, gizmoPos).normalize();
    }
    /**
     * @param {PointerEvent} e - The pointer event.
     * @private
     */ _onPointerDown(e) {
        if (!this.enabled || document.pointerLockElement) {
            return;
        }
        const selection = this._getSelection(e.offsetX, e.offsetY);
        if (selection[0]) {
            e.preventDefault();
            e.stopPropagation();
        }
        // capture the pointer during drag
        const { canvas } = this._device;
        canvas.setPointerCapture(e.pointerId);
        this.fire(Gizmo.EVENT_POINTERDOWN, e.offsetX, e.offsetY, selection[0]);
    }
    /**
     * @param {PointerEvent} e - The pointer event.
     * @private
     */ _onPointerMove(e) {
        if (!this.enabled || document.pointerLockElement) {
            return;
        }
        const selection = this._getSelection(e.offsetX, e.offsetY);
        if (selection[0]) {
            e.preventDefault();
            e.stopPropagation();
        }
        this.fire(Gizmo.EVENT_POINTERMOVE, e.offsetX, e.offsetY, selection[0]);
    }
    /**
     * @param {PointerEvent} e - The pointer event.
     * @private
     */ _onPointerUp(e) {
        if (!this.enabled || document.pointerLockElement) {
            return;
        }
        const selection = this._getSelection(e.offsetX, e.offsetY);
        if (selection[0]) {
            e.preventDefault();
            e.stopPropagation();
        }
        const { canvas } = this._device;
        canvas.releasePointerCapture(e.pointerId);
        this.fire(Gizmo.EVENT_POINTERUP, e.offsetX, e.offsetY, selection[0]);
    }
    /**
     * @protected
     */ _updatePosition() {
        position.set(0, 0, 0);
        for(let i = 0; i < this.nodes.length; i++){
            const node = this.nodes[i];
            position.add(node.getPosition());
        }
        position.mulScalar(1.0 / (this.nodes.length || 1));
        if (position.distance(this.root.getLocalPosition()) < UPDATE_EPSILON$2) {
            return;
        }
        this.root.setLocalPosition(position);
        this.fire(Gizmo.EVENT_POSITIONUPDATE, position);
        this._renderUpdate = true;
    }
    /**
     * @protected
     */ _updateRotation() {
        angles.set(0, 0, 0);
        if (this._coordSpace === 'local' && this.nodes.length !== 0) {
            angles.copy(this.nodes[this.nodes.length - 1].getEulerAngles());
        }
        if (angles.distance(this.root.getLocalEulerAngles()) < UPDATE_EPSILON$2) {
            return;
        }
        this.root.setLocalEulerAngles(angles);
        this.fire(Gizmo.EVENT_ROTATIONUPDATE, angles);
        this._renderUpdate = true;
    }
    /**
     * @protected
     */ _updateScale() {
        if (this._camera.projection === PROJECTION_PERSPECTIVE) {
            const gizmoPos = this.root.getLocalPosition();
            const cameraPos = this._camera.entity.getPosition();
            const dist = gizmoPos.distance(cameraPos);
            this._scale = Math.tan(0.5 * this._camera.fov * math.DEG_TO_RAD) * dist * PERS_SCALE_RATIO;
        } else {
            this._scale = this._camera.orthoHeight * ORTHO_SCALE_RATIO;
        }
        this._scale = Math.max(this._scale * this._size, MIN_SCALE);
        if (Math.abs(this._scale - this.root.getLocalScale().x) < UPDATE_EPSILON$2) {
            return;
        }
        this.root.setLocalScale(this._scale, this._scale, this._scale);
        this.fire(Gizmo.EVENT_SCALEUPDATE, this._scale);
        this._renderUpdate = true;
    }
    /**
     * @param {number} x - The x coordinate.
     * @param {number} y - The y coordinate.
     * @returns {MeshInstance[]} - The mesh instances.
     * @private
     */ _getSelection(x, y) {
        const start = this._camera.screenToWorld(x, y, 0);
        const end = this._camera.screenToWorld(x, y, this._camera.farClip - this._camera.nearClip);
        const dir = v$4.copy(end).sub(start).normalize();
        const selection = [];
        for(let i = 0; i < this.intersectShapes.length; i++){
            const shape = this.intersectShapes[i];
            if (shape.disabled || !shape.entity.enabled) {
                continue;
            }
            const parentTM = shape.entity.getWorldTransform();
            for(let j = 0; j < shape.triData.length; j++){
                const { tris, transform, priority } = shape.triData[j];
                // combine node world transform with transform of tri relative to parent
                const triWTM = m1.copy(parentTM).mul(transform);
                const invTriWTM = m2.copy(triWTM).invert();
                invTriWTM.transformPoint(start, ray$2.origin);
                invTriWTM.transformVector(dir, ray$2.direction);
                ray$2.direction.normalize();
                for(let k = 0; k < tris.length; k++){
                    if (tris[k].intersectsRay(ray$2, v$4)) {
                        selection.push({
                            dist: triWTM.transformPoint(v$4).sub(start).length(),
                            meshInstances: shape.meshInstances,
                            priority: priority
                        });
                    }
                }
            }
        }
        if (selection.length) {
            selection.sort((s0, s1)=>{
                if (s0.priority !== 0 && s1.priority !== 0) {
                    return s1.priority - s0.priority;
                }
                return s0.dist - s1.dist;
            });
            return selection[0].meshInstances;
        }
        return [];
    }
    /**
     * Attach an array of graph nodes to the gizmo.
     *
     * @param {GraphNode[] | GraphNode} [nodes] - The graph nodes. Defaults to [].
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.attach([boxA, boxB]);
     */ attach(nodes = []) {
        if (Array.isArray(nodes)) {
            if (nodes.length === 0) {
                return;
            }
            this.nodes = nodes;
        } else {
            this.nodes = [
                nodes
            ];
        }
        this._updatePosition();
        this._updateRotation();
        this._updateScale();
        this.fire(Gizmo.EVENT_NODESATTACH);
        this.enabled = true;
    }
    /**
     * Detaches all graph nodes from the gizmo.
     *
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.attach([boxA, boxB]);
     * gizmo.detach();
     */ detach() {
        this.enabled = false;
        this.fire(Gizmo.EVENT_NODESDETACH);
        this.nodes = [];
    }
    /**
     * Pre-render method. This is called before the gizmo is rendered.
     *
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.attach([boxA, boxB]);
     * gizmo.prerender();
     */ prerender() {}
    /**
     * Updates the gizmo position, rotation, and scale.
     *
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.attach([boxA, boxB]);
     * gizmo.update();
     */ update() {
        if (this._renderUpdate) {
            this._renderUpdate = false;
            this.fire(Gizmo.EVENT_RENDERUPDATE);
        }
        if (!this.enabled) {
            return;
        }
        this._updatePosition();
        this._updateRotation();
        this._updateScale();
    }
    /**
     * Detaches all graph nodes and destroys the gizmo instance.
     *
     * @example
     * const gizmo = new pc.Gizmo(camera, layer);
     * gizmo.attach([boxA, boxB]);
     * gizmo.destroy();
     */ destroy() {
        this.detach();
        this._device.canvas.removeEventListener('pointerdown', this._onPointerDown);
        this._device.canvas.removeEventListener('pointermove', this._onPointerMove);
        this._device.canvas.removeEventListener('pointerup', this._onPointerUp);
        this._handles.forEach((handle)=>handle.off());
        this.root.destroy();
    }
}

const COLOR_RED = Object.freeze(new Color(1, 0.3, 0.3));
const COLOR_GREEN = Object.freeze(new Color(0.3, 1, 0.3));
const COLOR_BLUE = Object.freeze(new Color(0.3, 0.3, 1));
const COLOR_YELLOW = Object.freeze(new Color(1, 1, 0.5));
const COLOR_GRAY = Object.freeze(new Color(0.5, 0.5, 0.5, 0.5));
const color4from3 = (color, a)=>{
    return new Color(color.r, color.g, color.b, a);
};

/**
 * @import { Shape } from './shape/shape.js'
 * @import { CameraComponent } from '../../framework/components/camera/component.js'
 * @import { Layer } from '../../scene/layer.js'
 * @import { MeshInstance } from '../../scene/mesh-instance.js'
 * @import { GizmoAxis, GizmoDragMode } from './constants.js'
 */ /**
 * @typedef {object} GizmoTheme
 * @property {{ [K in 'x' | 'y' | 'z' | 'f' | 'xyz']: Color }} shapeBase - The axis colors.
 * @property {{ [K in 'x' | 'y' | 'z' | 'f' | 'xyz']: Color }} shapeHover - The hover colors.
 * @property {{ [K in 'x' | 'y' | 'z' | 'f']: Color }} guideBase - The guide line colors.
 * @property {number} guideOcclusion - The guide occlusion value. Defaults to 0.8.
 * @property {Color} disabled - The disabled color.
 */ // temporary variables
const v1$4 = new Vec3();
const v2$4 = new Vec3();
const point$3 = new Vec3();
const ray$1 = new Ray();
const plane$1 = new Plane();
const color$1 = new Color();
// constants
const AXES$1 = /** @type {('x' | 'y' | 'z')[]} */ [
    'x',
    'y',
    'z'
];
/**
 * The base class for all transform gizmos.
 *
 * @category Gizmo
 */ class TransformGizmo extends Gizmo {
    static{
        /**
     * Fired when when the transformation has started.
     *
     * @event
     * @example
     * const gizmo = new pc.TransformGizmo(camera, layer);
     * gizmo.on('transform:start', () => {
     *     console.log('Transformation started');
     * });
     */ this.EVENT_TRANSFORMSTART = 'transform:start';
    }
    static{
        /**
     * Fired during the transformation.
     *
     * @event
     * @example
     * const gizmo = new pc.TransformGizmo(camera, layer);
     * gizmo.on('transform:move', (pointDelta, angleDelta) => {
     *     console.log('Transformation moved by ${pointDelta} (angle: ${angleDelta})');
     * });
     */ this.EVENT_TRANSFORMMOVE = 'transform:move';
    }
    static{
        /**
     * Fired when when the transformation has ended.
     *
     * @event
     * @example
     * const gizmo = new pc.TransformGizmo(camera, layer);
     * gizmo.on('transform:end', () => {
     *     console.log('Transformation ended');
     * });
     */ this.EVENT_TRANSFORMEND = 'transform:end';
    }
    /**
     * Creates a new TransformGizmo object.
     *
     * @param {CameraComponent} camera - The camera component.
     * @param {Layer} layer - The render layer.
     * @param {string} [name] - The name of the gizmo.
     * @example
     * const gizmo = new pc.TransformGizmo(camera, layer);
     */ constructor(camera, layer, name = 'gizmo:transform'){
        super(camera, layer, name), /**
     * Internal theme.
     *
     * @type {GizmoTheme}
     * @protected
     */ this._theme = {
            shapeBase: {
                x: color4from3(COLOR_RED, 0.6),
                y: color4from3(COLOR_GREEN, 0.6),
                z: color4from3(COLOR_BLUE, 0.6),
                xyz: color4from3(Color.WHITE, 0.6),
                f: color4from3(Color.WHITE, 0.6)
            },
            shapeHover: {
                x: COLOR_RED.clone(),
                y: COLOR_GREEN.clone(),
                z: COLOR_BLUE.clone(),
                xyz: Color.WHITE.clone(),
                f: COLOR_YELLOW.clone()
            },
            guideBase: {
                x: COLOR_RED.clone(),
                y: COLOR_GREEN.clone(),
                z: COLOR_BLUE.clone(),
                f: COLOR_YELLOW.clone()
            },
            guideOcclusion: 0.8,
            disabled: COLOR_GRAY.clone()
        }, /**
     * Internal gizmo starting rotation in world space.
     *
     * @type {Vec3}
     * @protected
     */ this._rootStartPos = new Vec3(), /**
     * Internal gizmo starting rotation in world space.
     *
     * @type {Quat}
     * @protected
     */ this._rootStartRot = new Quat(), /**
     * Internal object containing the gizmo shapes to render.
     *
     * @type {{ [key in GizmoAxis]?: Shape }}
     * @protected
     */ this._shapes = {}, /**
     * Internal mapping of mesh instances to gizmo shapes.
     *
     * @type {Map<MeshInstance, Shape>}
     * @private
     */ this._shapeMap = new Map(), /**
     * Internal currently hovered axes
     *
     * @type {Set<GizmoAxis>}
     * @private
     */ this._hovering = new Set(), /**
     * Internal currently hovered axis.
     *
     * @type {GizmoAxis | ''}
     * @private
     */ this._hoverAxis = '', /**
     * Internal state of if currently hovered shape is a plane.
     *
     * @type {boolean}
     * @private
     */ this._hoverIsPlane = false, /**
     * Internal currently selected axis.
     *
     * @type {GizmoAxis | ''}
     * @protected
     */ this._selectedAxis = '', /**
     * Internal state of if currently selected shape is a plane.
     *
     * @type {boolean}
     * @protected
     */ this._selectedIsPlane = false, /**
     * Internal selection starting coordinates in world space.
     *
     * @type {Vec3}
     * @protected
     */ this._selectionStartPoint = new Vec3(), /**
     * Internal state for if snapping is enabled. Defaults to false.
     *
     * @type {boolean}
     * @private
     */ this._snap = false, /**
     * Snapping increment. Defaults to 1.
     *
     * @type {number}
     */ this.snapIncrement = 1, /**
     * Whether to hide the shapes when dragging. Defaults to 'selected'.
     *
     * @type {GizmoDragMode}
     */ this.dragMode = 'selected';
        this.on(Gizmo.EVENT_POINTERDOWN, (x, y, meshInstance)=>{
            const shape = this._shapeMap.get(meshInstance);
            if (shape?.disabled) {
                return;
            }
            if (this._dragging) {
                return;
            }
            if (!meshInstance) {
                return;
            }
            this._hoverAxis = '';
            this._hoverIsPlane = false;
            this._selectedAxis = this._getAxis(meshInstance);
            this._selectedIsPlane = this._getIsPlane(meshInstance);
            this._rootStartPos.copy(this.root.getLocalPosition());
            this._rootStartRot.copy(this.root.getRotation());
            const point = this._screenToPoint(x, y);
            this._selectionStartPoint.copy(point);
            this.fire(TransformGizmo.EVENT_TRANSFORMSTART, point, x, y);
        });
        this.on(Gizmo.EVENT_POINTERMOVE, (x, y, meshInstance)=>{
            const shape = this._shapeMap.get(meshInstance);
            if (shape?.disabled) {
                return;
            }
            this._hover(meshInstance);
            if (!this._dragging) {
                return;
            }
            const point = this._screenToPoint(x, y);
            this.fire(TransformGizmo.EVENT_TRANSFORMMOVE, point, x, y);
        });
        this.on(Gizmo.EVENT_POINTERUP, (_x, _y, meshInstance)=>{
            this._hover(meshInstance);
            if (!this._dragging) {
                return;
            }
            if (meshInstance) {
                this._hoverAxis = this._selectedAxis;
                this._hoverIsPlane = this._selectedIsPlane;
            }
            this._selectedAxis = '';
            this._selectedIsPlane = false;
            this.fire(TransformGizmo.EVENT_TRANSFORMEND);
        });
        this.on(Gizmo.EVENT_NODESDETACH, ()=>{
            this.snap = false;
            this._hoverAxis = '';
            this._hoverIsPlane = false;
            this._hover();
            this.fire(Gizmo.EVENT_POINTERUP);
        });
    }
    /**
     * Sets whether snapping is enabled. Defaults to false.
     *
     * @type {boolean}
     */ set snap(value) {
        this._snap = this.enabled && value;
    }
    /**
     * Gets whether snapping is enabled. Defaults to false.
     *
     * @type {boolean}
     */ get snap() {
        return this._snap;
    }
    /**
     * Gets the current theme for the gizmo.
     *
     * @type {GizmoTheme}
     */ get theme() {
        return this._theme;
    }
    /**
     * @type {Color}
     * @deprecated Use {@link TransformGizmo#setTheme} instead.
     * @ignore
     */ set xAxisColor(value) {
        this.setTheme({
            shapeBase: {
                x: value
            },
            shapeHover: {
                x: color4from3(value, this.colorAlpha)
            },
            guideBase: {
                x: value
            },
            guideOcclusion: 1
        });
    }
    /**
     * @type {Color}
     * @deprecated Use {@link TransformGizmo#theme} instead.
     * @ignore
     */ get xAxisColor() {
        return this._theme.shapeBase.x;
    }
    /**
     * @type {Color}
     * @deprecated Use {@link TransformGizmo#setTheme} instead.
     * @ignore
     */ set yAxisColor(value) {
        this.setTheme({
            shapeBase: {
                y: value
            },
            shapeHover: {
                y: color4from3(value, this.colorAlpha)
            },
            guideBase: {
                y: value
            },
            guideOcclusion: 1
        });
    }
    /**
     * @type {Color}
     * @deprecated Use {@link TransformGizmo#theme} instead.
     * @ignore
     */ get yAxisColor() {
        return this._theme.shapeBase.y;
    }
    /**
     * @type {Color}
     * @deprecated Use {@link TransformGizmo#setTheme} instead.
     * @ignore
     */ set zAxisColor(value) {
        this.setTheme({
            shapeBase: {
                z: value
            },
            shapeHover: {
                z: color4from3(value, this.colorAlpha)
            },
            guideBase: {
                z: value
            },
            guideOcclusion: 1
        });
    }
    /**
     * @type {Color}
     * @deprecated Use {@link TransformGizmo#theme} instead.
     * @ignore
     */ get zAxisColor() {
        return this._theme.shapeBase.z;
    }
    /**
     * @type {number}
     * @deprecated Use {@link TransformGizmo#setTheme} instead.
     * @ignore
     */ set colorAlpha(value) {
        this.setTheme({
            shapeHover: {
                x: color4from3(this._theme.shapeHover.x, value),
                y: color4from3(this._theme.shapeHover.y, value),
                z: color4from3(this._theme.shapeHover.z, value),
                xyz: color4from3(this._theme.shapeHover.xyz, value),
                f: color4from3(this._theme.shapeHover.f, value)
            }
        });
    }
    /**
     * @type {number}
     * @deprecated Use {@link TransformGizmo#theme} instead.
     * @ignore
     */ get colorAlpha() {
        return (this._theme.shapeHover.x.a + this._theme.shapeHover.y.a + this._theme.shapeHover.z.a + this._theme.shapeHover.xyz.a + this._theme.shapeHover.f.a) / 5;
    }
    /**
     * @type {boolean}
     * @protected
     */ get _dragging() {
        return !this._hoverAxis && !!this._selectedAxis;
    }
    /**
     * @param {MeshInstance} [meshInstance] - The mesh instance.
     * @returns {GizmoAxis | ''} - The axis.
     * @private
     */ _getAxis(meshInstance) {
        if (!meshInstance) {
            return '';
        }
        return /** @type {GizmoAxis | ''} */ meshInstance.node.name.split(':')[1];
    }
    /**
     * @param {MeshInstance} [meshInstance] - The mesh instance.
     * @returns {boolean} - Whether the mesh instance is a plane.
     * @private
     */ _getIsPlane(meshInstance) {
        if (!meshInstance) {
            return false;
        }
        return meshInstance.node.name.indexOf('plane') !== -1;
    }
    /**
     * @param {MeshInstance} [meshInstance] - The mesh instance.
     * @private
     */ _hover(meshInstance) {
        if (this._dragging) {
            return;
        }
        // track changes
        const remove = new Set(this._hovering);
        let changed = false;
        const add = (/** @type {GizmoAxis} */ axis)=>{
            if (remove.has(axis)) {
                remove.delete(axis);
            } else {
                this._hovering.add(axis);
                this._shapes[axis]?.hover(true);
                changed = true;
            }
        };
        // determine which axis is hovered
        this._hoverAxis = this._getAxis(meshInstance);
        this._hoverIsPlane = this._getIsPlane(meshInstance);
        // add shapes that are hovered
        if (this._hoverAxis) {
            if (this._hoverAxis === 'xyz') {
                add('x');
                add('y');
                add('z');
                add('xyz');
            } else if (this._hoverIsPlane) {
                switch(this._hoverAxis){
                    case 'x':
                        add('y');
                        add('z');
                        add('yz');
                        break;
                    case 'y':
                        add('x');
                        add('z');
                        add('xz');
                        break;
                    case 'z':
                        add('x');
                        add('y');
                        add('xy');
                        break;
                }
            } else {
                add(this._hoverAxis);
            }
        }
        // unhover removed shapes
        for (const axis of remove){
            this._hovering.delete(axis);
            this._shapes[axis]?.hover(false);
            changed = true;
        }
        if (changed) {
            this._renderUpdate = true;
        }
    }
    /**
     * @param {Vec3} mouseWPos - The mouse world position.
     * @returns {Ray} - The ray.
     * @protected
     */ _createRay(mouseWPos) {
        if (this._camera.projection === PROJECTION_PERSPECTIVE) {
            ray$1.origin.copy(this._camera.entity.getPosition());
            ray$1.direction.sub2(mouseWPos, ray$1.origin).normalize();
            return ray$1;
        }
        const orthoDepth = this._camera.farClip - this._camera.nearClip;
        ray$1.origin.sub2(mouseWPos, v1$4.copy(this._camera.entity.forward).mulScalar(orthoDepth));
        ray$1.direction.copy(this._camera.entity.forward);
        return ray$1;
    }
    /**
     * @param {string} axis - The axis to create the plane for.
     * @param {boolean} isFacing - Whether the axis is facing the camera.
     * @param {boolean} isLine - Whether the axis is a line.
     * @returns {Plane} - The plane.
     * @protected
     */ _createPlane(axis, isFacing, isLine) {
        const facingDir = v1$4.copy(this.facingDir);
        const normal = plane$1.normal.set(0, 0, 0);
        if (isFacing) {
            // set plane normal to face camera
            normal.copy(this._camera.entity.forward).mulScalar(-1);
        } else {
            // set plane normal based on axis
            normal[axis] = 1;
            this._rootStartRot.transformVector(normal, normal);
            if (isLine) {
                // set plane normal to face camera but keep normal perpendicular to axis
                v2$4.cross(normal, facingDir).normalize();
                normal.cross(v2$4, normal).normalize();
            }
        }
        return plane$1.setFromPointNormal(this._rootStartPos, normal);
    }
    /**
     * @param {string} axis - The axis
     * @param {Vec3} dir - The direction
     * @returns {Vec3} - The direction
     * @protected
     */ _dirFromAxis(axis, dir) {
        if (axis === 'f') {
            dir.copy(this._camera.entity.forward).mulScalar(-1);
        } else {
            dir.set(0, 0, 0);
            dir[axis] = 1;
        }
        return dir;
    }
    /**
     * @param {Vec3} point - The point to project.
     * @param {string} axis - The axis to project to.
     * @protected
     */ _projectToAxis(point, axis) {
        // set normal to axis and project position from plane onto normal
        v1$4.set(0, 0, 0);
        v1$4[axis] = 1;
        point.copy(v1$4.mulScalar(v1$4.dot(point)));
        // set other axes to zero (floating point fix)
        const v = point[axis];
        point.set(0, 0, 0);
        point[axis] = v;
    }
    /**
     * @param {number} x - The x coordinate.
     * @param {number} y - The y coordinate.
     * @param {boolean} isFacing - Whether the axis is facing the camera.
     * @param {boolean} isLine - Whether the axis is a line.
     * @returns {Vec3} The point (space is {@link TransformGizmo#coordSpace}).
     * @protected
     */ _screenToPoint(x, y, isFacing = false, isLine = false) {
        const mouseWPos = this._camera.screenToWorld(x, y, 1);
        const axis = this._selectedAxis;
        const ray = this._createRay(mouseWPos);
        const plane = this._createPlane(axis, isFacing, isLine);
        if (!plane.intersectsRay(ray, point$3)) {
            return point$3;
        }
        return point$3;
    }
    /**
     * @param {Vec3} pos - The position.
     * @param {Quat} rot - The rotation.
     * @param {GizmoAxis | ''} activeAxis - The active axis.
     * @param {boolean} activeIsPlane - Whether the active axis is a plane.
     * @protected
     */ _drawGuideLines(pos, rot, activeAxis, activeIsPlane) {
        for (const axis of AXES$1){
            if (activeAxis === 'xyz') {
                this._drawSpanLine(pos, rot, axis);
                continue;
            }
            if (activeIsPlane) {
                if (axis !== activeAxis) {
                    this._drawSpanLine(pos, rot, axis);
                }
            } else {
                if (axis === activeAxis) {
                    this._drawSpanLine(pos, rot, axis);
                }
            }
        }
    }
    /**
     * @param {Vec3} pos - The position.
     * @param {Quat} rot - The rotation.
     * @param {'x' | 'y' | 'z'} axis - The axis.
     * @protected
     */ _drawSpanLine(pos, rot, axis) {
        const dir = this._dirFromAxis(axis, v1$4);
        const base = this._theme.guideBase[axis];
        const from = v1$4.copy(dir).mulScalar(this._camera.farClip - this._camera.nearClip);
        const to = v2$4.copy(from).mulScalar(-1);
        rot.transformVector(from, from).add(pos);
        rot.transformVector(to, to).add(pos);
        if (this._theme.guideOcclusion < 1) {
            const occluded = color$1.copy(base);
            occluded.a *= 1 - this._theme.guideOcclusion;
            this._app.drawLine(from, to, occluded, false, this._layer);
        }
        if (base.a !== 0) {
            this._app.drawLine(from, to, base, true);
        }
    }
    /**
     * @protected
     */ _createTransform() {
        // shapes
        for(const key in this._shapes){
            const shape = this._shapes[key];
            this.root.addChild(shape.entity);
            this.intersectShapes.push(shape);
            for(let i = 0; i < shape.meshInstances.length; i++){
                this._shapeMap.set(shape.meshInstances[i], shape);
            }
        }
    }
    /**
     * Set the shape to be enabled or disabled.
     *
     * @param {GizmoAxis | 'face'} shapeAxis - The shape axis.
     * @param {boolean} enabled - The enabled state of shape.
     */ enableShape(shapeAxis, enabled) {
        if (shapeAxis === 'face') {
            Debug.deprecated('"face" literal is deprecated use "f" literal instead');
            shapeAxis = 'f';
        }
        const shape = this._shapes[shapeAxis];
        if (!shape) {
            return;
        }
        shape.disabled = !enabled;
    }
    /**
     * Get the enabled state of the shape.
     *
     * @param {GizmoAxis | 'face'} shapeAxis - The shape axis. Can be:
     * @returns {boolean} - Then enabled state of the shape
     */ isShapeEnabled(shapeAxis) {
        if (shapeAxis === 'face') {
            Debug.deprecated('"face" literal is deprecated use "f" literal instead');
            shapeAxis = 'f';
        }
        const shape = this._shapes[shapeAxis];
        if (!shape) {
            return false;
        }
        return !shape.disabled;
    }
    /**
     * Sets the theme or partial theme for the gizmo.
     *
     * @param {{ [K in keyof GizmoTheme]?: Partial<GizmoTheme[K]> }} partial - The partial theme to set.
     */ setTheme(partial) {
        const theme = {
            ...this._theme,
            ...partial
        };
        if (typeof theme !== 'object' || typeof theme.shapeBase !== 'object' || typeof theme.shapeHover !== 'object' || typeof theme.guideBase !== 'object') {
            return;
        }
        // shape
        for(const axis in theme.shapeBase){
            if (theme.shapeBase[axis] instanceof Color) {
                this._theme.shapeBase[axis].copy(theme.shapeBase[axis]);
            }
        }
        for(const axis in theme.shapeHover){
            if (theme.shapeHover[axis] instanceof Color) {
                this._theme.shapeHover[axis].copy(theme.shapeHover[axis]);
            }
        }
        // guide
        for(const axis in theme.guideBase){
            if (theme.guideBase[axis] instanceof Color) {
                this._theme.guideBase[axis].copy(theme.guideBase[axis]);
            }
        }
        if (typeof theme.guideOcclusion === 'number') {
            this._theme.guideOcclusion = math.clamp(theme.guideOcclusion, 0, 1);
        }
        // disabled
        if (theme.disabled instanceof Color) {
            this._theme.disabled.copy(theme.disabled);
        }
        // update shapes
        for(const name in this._shapes){
            this._shapes[name].hover(!!this._hoverAxis);
        }
    }
    /**
     * @override
     */ prerender() {
        super.prerender();
        if (!this.enabled) {
            return;
        }
        const gizmoPos = this.root.getLocalPosition();
        const gizmoRot = this.root.getRotation();
        const activeAxis = this._hoverAxis || this._selectedAxis;
        const activeIsPlane = this._hoverIsPlane || this._selectedIsPlane;
        this._drawGuideLines(gizmoPos, gizmoRot, activeAxis, activeIsPlane);
    }
    /**
     * @override
     */ destroy() {
        super.destroy();
        for(const key in this._shapes){
            this._shapes[key].destroy();
        }
    }
}

const tmpV1$3 = new Vec3();
const tmpV2$2 = new Vec3();
const tmpV3 = new Vec3();
/**
 * The class for holding triangle data.
 *
 * @ignore
 */ class TriData {
    /**
     * @param {Geometry} geometry - The geometry to create the triangle data from.
     * @param {number} [priority] - The priority of the triangle data.
     */ constructor(geometry, priority = 0){
        /**
     * The priority of the triangle data (Used for intersection ordering):
     *   - priority = 0 - no priority
     *   - priority > 0 - higher value represents a higher priority
     * defaults to 0.
     *
     * @type {number}
     */ this._priority = 0;
        /**
     * The transform of the triangles.
     *
     * @type {Mat4}
     */ this._transform = new Mat4();
        /**
     * The array of triangles for the geometry.
     *
     * @type {Tri[]}
     */ this.tris = [];
        this.fromGeometry(geometry);
        this._priority = priority;
    }
    get transform() {
        return this._transform;
    }
    get priority() {
        return this._priority;
    }
    /**
     * Sets the transform of the triangle data.
     *
     * @param {Vec3} [pos] - The position of the transform.
     * @param {Quat} [rot] - The rotation of the transform.
     * @param {Vec3} [scale] - The scale of the transform.
     */ setTransform(pos = new Vec3(), rot = new Quat(), scale = new Vec3()) {
        this.transform.setTRS(pos, rot, scale);
    }
    /**
     * @param {Geometry} geometry - The geometry to create the triangle data from.
     */ fromGeometry(geometry) {
        if (!geometry || !(geometry instanceof Geometry)) {
            throw new Error('No geometry provided.');
        }
        const positions = geometry.positions ?? [];
        const indices = geometry.indices ?? [];
        this.tris = [];
        for(let k = 0; k < indices.length; k += 3){
            const i1 = indices[k];
            const i2 = indices[k + 1];
            const i3 = indices[k + 2];
            tmpV1$3.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
            tmpV2$2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
            tmpV3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);
            const tri = new Tri(tmpV1$3, tmpV2$2, tmpV3);
            this.tris.push(tri);
        }
    }
}

/** @import { ShaderDesc } from '../../scene/materials/shader-material.js'; */ /**
 * @type {ShaderDesc}
 */ const unlitShader = {
    uniqueName: 'gizmo-unlit',
    attributes: {
        vertex_position: SEMANTIC_POSITION
    },
    vertexGLSL: /* glsl */ `
        attribute vec3 vertex_position;
    
        uniform mat4 matrix_model;
        uniform mat4 matrix_viewProjection;
    
        void main(void) {
            gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
            gl_Position.z = clamp(gl_Position.z, -abs(gl_Position.w), abs(gl_Position.w));
        }
    `,
    fragmentGLSL: /* glsl */ `
        #include "gammaPS"
    
        precision highp float;
    
        uniform vec4 uColor;

        void main(void) {
            if (uColor.a < 1.0 / 255.0) {
                discard;
            }
            gl_FragColor = vec4(gammaCorrectOutput(decodeGamma(uColor)), uColor.a);
            #if DEPTH_WRITE == 0
                gl_FragDepth = 0.0;
            #endif
        }
    `,
    vertexWGSL: /* wgsl */ `
        attribute vertex_position: vec3f;

        uniform matrix_model: mat4x4f;
        uniform matrix_viewProjection: mat4x4f;

        @vertex
        fn vertexMain(input: VertexInput) -> VertexOutput {
            var output: VertexOutput;
            let pos = vec4f(input.vertex_position, 1.0);
            output.position = uniform.matrix_viewProjection * uniform.matrix_model * pos;
            output.position.z = clamp(output.position.z, -abs(output.position.w), abs(output.position.w));
            return output;
        }
    `,
    fragmentWGSL: /* wgsl */ `
        #include "gammaPS"

        uniform uColor: vec4f;

        @fragment
        fn fragmentMain(input: FragmentInput) -> FragmentOutput {
            var output: FragmentOutput;
            if (uniform.uColor.a < 1.0 / 255.0) {
                discard;
            }
            output.color = vec4f(gammaCorrectOutput(decodeGamma(uniform.uColor)), uniform.uColor.a);
            #if DEPTH_WRITE == 0
                output.fragDepth = 0.0;
            #endif
            return output;
        }
    `
};

/**
 * @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js';
 * @import { Mesh } from '../../../scene/mesh.js';
 * @import { TriData } from '../tri-data.js';
 */ const tmpG = new Geometry();
tmpG.positions = [];
tmpG.normals = [];
/**
 * @typedef {object} ShapeArgs
 * @property {string} [axis] - The axis of the shape (e.g., 'x', 'y', 'z').
 * @property {Vec3} [position] - The position of the shape.
 * @property {Vec3} [rotation] - The rotation of the shape.
 * @property {Vec3} [scale] - The scale of the shape.
 * @property {boolean} [disabled] - Whether the shape is disabled.
 * @property {boolean} [hidden] - Whether the shape is hidden.
 * @property {number[]} [layers] - The layers the shape belongs to.
 * @property {Color} [defaultColor] - The default color of the shape.
 * @property {Color} [hoverColor] - The hover color of the shape.
 * @property {Color} [disabledColor] - The disabled color of the shape.
 */ /**
 * @ignore
 */ class Shape {
    /**
     * Create a shape.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {string} name - The name of the shape.
     * @param {ShapeArgs} args - The options for the shape.
     */ constructor(device, name, args){
        /**
     * The internal position of the shape.
     *
     * @type {Vec3}
     * @protected
     */ this._position = new Vec3();
        /**
     * The internal rotation of the shape.
     *
     * @type {Vec3}
     * @protected
     */ this._rotation = new Vec3();
        /**
     * The internal scale of the shape.
     *
     * @type {Vec3}
     * @protected
     */ this._scale = new Vec3(1, 1, 1);
        /**
     * The internal render component layers of the shape.
     *
     * @type {number[]}
     * @protected
     */ this._layers = [];
        /**
     * The internal material state of the shape.
     *
     * @type {ShaderMaterial}
     * @protected
     */ this._material = new ShaderMaterial(unlitShader);
        /**
     * The internal disabled state of the shape.
     *
     * @protected
     * @type {boolean}
     */ this._disabled = false;
        /**
     * The internal visibility state of the shape.
     *
     * @type {boolean}
     * @protected
     */ this._visible = true;
        /**
     * The internal default color of the shape.
     *
     * @type {Color}
     * @protected
     */ this._defaultColor = Color.WHITE;
        /**
     * The internal hover color of the shape.
     *
     * @type {Color}
     * @protected
     */ this._hoverColor = Color.BLACK;
        /**
     * The internal disabled color of the shape.
     *
     * @type {Color}
     * @protected
     */ this._disabledColor = COLOR_GRAY;
        /**
     * The internal culling state of the shape.
     *
     * @type {number}
     * @protected
     */ this._cull = CULLFACE_BACK;
        /**
     * The triangle data of the shape.
     *
     * @type {TriData[]}
     */ this.triData = [];
        /**
     * The mesh instances of the shape.
     *
     * @type {MeshInstance[]}
     */ this.meshInstances = [];
        this.device = device;
        this.axis = args.axis ?? 'x';
        if (args.position instanceof Vec3) {
            this._position.copy(args.position);
        }
        if (args.rotation instanceof Vec3) {
            this._rotation.copy(args.rotation);
        }
        if (args.scale instanceof Vec3) {
            this._scale.copy(args.scale);
        }
        this._disabled = args.disabled ?? false;
        this._visible = args.hidden ?? false;
        this._layers = args.layers ?? this._layers;
        if (args.defaultColor instanceof Color) {
            this._defaultColor = args.defaultColor;
        }
        if (args.hoverColor instanceof Color) {
            this._hoverColor = args.hoverColor;
        }
        if (args.disabledColor instanceof Color) {
            this._disabledColor = args.disabledColor;
        }
        // entity
        this.entity = new Entity(`${name}:${this.axis}`);
        this.entity.setLocalPosition(this._position);
        this.entity.setLocalEulerAngles(this._rotation);
        this.entity.setLocalScale(this._scale);
    }
    /**
     * Set the disabled state of the shape.
     *
     * @type {boolean}
     */ set disabled(value) {
        this._disabled = value ?? false;
        this.hover(false);
    }
    /**
     * Get the disabled state of the shape.
     *
     * @type {boolean}
     */ get disabled() {
        return this._disabled;
    }
    /**
     * Set the visibility state of the shape.
     *
     * @type {boolean}
     */ set visible(value) {
        if (value === this._visible) {
            return;
        }
        for(let i = 0; i < this.meshInstances.length; i++){
            this.meshInstances[i].visible = value;
        }
        this._visible = value;
    }
    /**
     * Get the visibility state of the shape.
     *
     * @type {boolean}
     */ get visible() {
        return this._visible;
    }
    /**
     * Create a render component for an entity.
     *
     * @param {Entity} entity - The entity to create the render component for.
     * @param {Mesh[]} meshes - The meshes to create the render component with.
     * @protected
     */ _createRenderComponent(entity, meshes) {
        const color = this._disabled ? this._disabledColor : this._defaultColor;
        this._material.setParameter('uColor', color.toArray());
        this._material.cull = this._cull;
        this._material.blendType = BLEND_NORMAL;
        this._material.update();
        const meshInstances = [];
        for(let i = 0; i < meshes.length; i++){
            const mi = new MeshInstance(meshes[i], this._material);
            mi.cull = false;
            meshInstances.push(mi);
            this.meshInstances.push(mi);
        }
        entity.addComponent('render', {
            meshInstances: meshInstances,
            layers: this._layers,
            castShadows: false
        });
    }
    /**
     * Update the shape's transform.
     *
     * @protected
     */ _update() {
        this.entity.setLocalPosition(this._position);
        this.entity.setLocalEulerAngles(this._rotation);
        this.entity.setLocalScale(this._scale);
    }
    /**
     * Sets the hover state of the shape.
     *
     * @param {boolean} state - Whether the shape is hovered.
     * @returns {void}
     */ hover(state) {
        const color = this._disabled ? this._disabledColor : state ? this._hoverColor : this._defaultColor;
        this._material.setParameter('uColor', color.toArray());
    }
    /**
     * Destroys the shape and its entity.
     *
     * @returns {void}
     */ destroy() {
        this.entity.destroy();
    }
}

/** @import { ShapeArgs } from './shape.js' */ /** @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js' */ const UPDATE_EPSILON$1 = 1e-6;
/**
 * @typedef {object} PlaneShapeArgs
 * @property {number} [size] - The size of the plane
 * @property {number} [gap] - The gap between the plane and the center
 */ /**
 * @ignore
 */ class PlaneShape extends Shape {
    /**
     * Create a new PlaneShape.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {ShapeArgs & PlaneShapeArgs} args - The shape options.
     */ constructor(device, args = {}){
        super(device, 'plane', args), /**
     * The culling mode for the plane.
     *
     * @type {number}
     * @protected
     */ this._cull = CULLFACE_NONE, /**
     * The size of the plane.
     *
     * @type {number}
     * @private
     */ this._size = 0.2, /**
     * The gap between the plane and the center.
     *
     * @type {number}
     * @private
     */ this._gap = 0.1, /**
     * The internal flipped state of the plane.
     *
     * @type {Vec3}
     * @private
     */ this._flipped = new Vec3();
        this._size = args.size ?? this._size;
        this._gap = args.gap ?? this._gap;
        // intersect
        this.triData = [
            new TriData(new PlaneGeometry())
        ];
        // render
        this._createRenderComponent(this.entity, [
            Mesh.fromGeometry(this.device, new PlaneGeometry())
        ]);
        // update transform
        this._update();
    }
    /**
     * Set the size of the plane.
     *
     * @type {number}
     */ set size(value) {
        this._size = value ?? this._size;
        this._update();
    }
    /**
     * Get the size of the plane.
     *
     * @type {number}
     */ get size() {
        return this._size;
    }
    /**
     * Set the gap between the plane and the center.
     *
     * @type {number}
     */ set gap(value) {
        this._gap = value ?? this._gap;
        this._update();
    }
    /**
     * Get the gap between the plane and the center.
     *
     * @type {number}
     */ get gap() {
        return this._gap;
    }
    /**
     * Set the flipped state of the plane.
     *
     * @type {Vec3}
     */ set flipped(value) {
        if (this._flipped.distance(value) < UPDATE_EPSILON$1) {
            return;
        }
        this._flipped.copy(value);
        this._update();
    }
    /**
     * Get the flipped state of the plane.
     *
     * @type {Vec3}
     */ get flipped() {
        return this._flipped;
    }
    /**
     * Update the shape's transform.
     *
     * @protected
     * @override
     */ _update() {
        // intersect/render
        const offset = this._size / 2 + this._gap;
        this._position.set(this._flipped.x ? -offset : offset, this._flipped.y ? -offset : offset, this._flipped.z ? -offset : offset);
        this._position[this.axis] = 0;
        this.entity.setLocalPosition(this._position);
        this.entity.setLocalEulerAngles(this._rotation);
        this.entity.setLocalScale(this._size, this._size, this._size);
    }
}

/** @import { ShapeArgs } from './shape.js' */ /** @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js' */ const tmpV1$2 = new Vec3();
const tmpV2$1 = new Vec3();
const tmpQ1$1 = new Quat();
/**
 * @typedef {object} ArrowShapeArgs
 * @property {number} [gap] - The gap between the arrow base and the center
 * @property {number} [lineThickness] - The thickness of the line
 * @property {number} [lineLength] - The length of the line
 * @property {number} [arrowThickness] - The thickness of the arrow head
 * @property {number} [arrowLength] - The length of the arrow head
 * @property {number} [tolerance] - The tolerance for intersection tests
 */ /**
 * @ignore
 */ class ArrowShape extends Shape {
    /**
     * Create a new ArrowShape.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {ShapeArgs & ArrowShapeArgs} args - The shape options.
     */ constructor(device, args = {}){
        super(device, 'arrow', args), /**
     * The internal gap between the arrow base and the center.
     *
     * @type {number}
     * @private
     */ this._gap = 0, /**
     * The internal line thickness of the arrow.
     *
     * @type {number}
     * @private
     */ this._lineThickness = 0.02, /**
     * The internal line length of the arrow.
     *
     * @type {number}
     * @private
     */ this._lineLength = 0.5, /**
     * The internal arrow thickness of the arrow.
     *
     * @type {number}
     * @private
     */ this._arrowThickness = 0.12, /**
     * The internal arrow length of the arrow.
     *
     * @type {number}
     * @private
     */ this._arrowLength = 0.18, /**
     * The internal tolerance of the arrow.
     *
     * @type {number}
     * @private
     */ this._tolerance = 0.1, /**
     * The internal flipped state of the arrow.
     *
     * @type {boolean}
     * @private
     */ this._flipped = false;
        this._gap = args.gap ?? this._gap;
        this._lineThickness = args.lineThickness ?? this._lineThickness;
        this._lineLength = args.lineLength ?? this._lineLength;
        this._arrowThickness = args.arrowThickness ?? this._arrowThickness;
        this._arrowLength = args.arrowLength ?? this._arrowLength;
        this._tolerance = args.tolerance ?? this._tolerance;
        // intersect
        this.triData = [
            new TriData(new ConeGeometry()),
            new TriData(new CylinderGeometry(), 1)
        ];
        // render
        this._head = new Entity(`head:${this.axis}`);
        this.entity.addChild(this._head);
        this._createRenderComponent(this._head, [
            Mesh.fromGeometry(this.device, new ConeGeometry())
        ]);
        this._line = new Entity(`line:${this.axis}`);
        this.entity.addChild(this._line);
        this._createRenderComponent(this._line, [
            Mesh.fromGeometry(this.device, new CylinderGeometry())
        ]);
        // update
        this._update();
    }
    /**
     * Set the gap between the arrow base and the center.
     *
     * @type {number}
     */ set gap(value) {
        this._gap = value ?? this._gap;
        this._update();
    }
    /**
     * Get the gap between the arrow base and the center.
     *
     * @type {number}
     */ get gap() {
        return this._gap;
    }
    /**
     * Set the line thickness of the arrow.
     *
     * @type {number}
     */ set lineThickness(value) {
        this._lineThickness = value ?? this._lineThickness;
        this._update();
    }
    /**
     * Get the line thickness of the arrow.
     *
     * @type {number}
     */ get lineThickness() {
        return this._lineThickness;
    }
    /**
     * Set the line length of the arrow.
     *
     * @type {number}
     */ set lineLength(value) {
        this._lineLength = value ?? this._lineLength;
        this._update();
    }
    /**
     * Get the line length of the arrow.
     *
     * @type {number}
     */ get lineLength() {
        return this._lineLength;
    }
    /**
     * Set the arrow thickness of the arrow.
     *
     * @type {number}
     */ set arrowThickness(value) {
        this._arrowThickness = value ?? this._arrowThickness;
        this._update();
    }
    /**
     * Get the arrow thickness of the arrow.
     *
     * @type {number}
     */ get arrowThickness() {
        return this._arrowThickness;
    }
    /**
     * Set the arrow length of the arrow.
     *
     * @type {number}
     */ set arrowLength(value) {
        this._arrowLength = value ?? this._arrowLength;
        this._update();
    }
    /**
     * Get the arrow length of the arrow.
     *
     * @type {number}
     */ get arrowLength() {
        return this._arrowLength;
    }
    /**
     * Set the tolerance of the arrow.
     *
     * @type {number}
     */ set tolerance(value) {
        this._tolerance = value;
        this._update();
    }
    /**
     * Get the tolerance of the arrow.
     *
     * @type {number}
     */ get tolerance() {
        return this._tolerance;
    }
    /**
     * Set the flipped state of the arrow.
     *
     * @type {boolean}
     */ set flipped(value) {
        if (this._flipped === value) {
            return;
        }
        this._flipped = value;
        if (this._rotation.equals(Vec3.ZERO)) {
            tmpV1$2.set(0, 0, this._flipped ? 180 : 0);
        } else {
            tmpV1$2.copy(this._rotation).mulScalar(this._flipped ? -1 : 1);
        }
        this._line.enabled = !this._flipped;
        this.entity.setLocalEulerAngles(tmpV1$2);
    }
    /**
     * Get the flipped state of the arrow.
     *
     * @type {boolean}
     */ get flipped() {
        return this._flipped;
    }
    /**
     * Update the shape's transform.
     *
     * @protected
     * @override
     */ _update() {
        // intersect
        tmpV1$2.set(0, this._gap + this._arrowLength * 0.5 + this._lineLength, 0);
        tmpQ1$1.set(0, 0, 0, 1);
        tmpV2$1.set(this._arrowThickness, this._arrowLength, this._arrowThickness);
        this.triData[0].setTransform(tmpV1$2, tmpQ1$1, tmpV2$1);
        tmpV1$2.set(0, this._gap + this._lineLength * 0.5, 0);
        tmpQ1$1.set(0, 0, 0, 1);
        tmpV2$1.set(this._lineThickness + this._tolerance, this._lineLength, this._lineThickness + this._tolerance);
        this.triData[1].setTransform(tmpV1$2, tmpQ1$1, tmpV2$1);
        // render
        this._head.setLocalPosition(0, this._gap + this._arrowLength * 0.5 + this._lineLength, 0);
        this._head.setLocalScale(this._arrowThickness, this._arrowLength, this._arrowThickness);
        this._line.setLocalPosition(0, this._gap + this._lineLength * 0.5, 0);
        this._line.setLocalScale(this._lineThickness, this._lineLength, this._lineThickness);
    }
}

/** @import { ShapeArgs } from './shape.js' */ /** @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js' */ /**
 * @typedef {object} SphereShapeArgs
 * @property {number} [radius] - The radius of the sphere.
 * @property {number} [tolerance] - The intersection tolerance of the sphere.
 */ /**
 * @ignore
 */ let SphereShape$1 = class SphereShape extends Shape {
    /**
     * Create a new SphereShape.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {ShapeArgs & SphereShapeArgs} args - The shape options.
     */ constructor(device, args = {}){
        super(device, 'sphereCenter', args), /**
     * The internal size of the sphere.
     *
     * @type {number}
     * @private
     */ this._radius = 0.06, /**
     * The internal intersection tolerance of the sphere.
     *
     * @type {number}
     * @private
     */ this._tolerance = 0.05;
        this._radius = args.radius ?? this._radius;
        this._tolerance = args.tolerance ?? this._tolerance;
        // intersect
        this.triData = [
            new TriData(new SphereGeometry(), 2)
        ];
        // render
        this._createRenderComponent(this.entity, [
            Mesh.fromGeometry(this.device, new SphereGeometry({
                latitudeBands: 32,
                longitudeBands: 32
            }))
        ]);
        // update transform
        this._update();
    }
    /**
     * Set the rendered radius of the sphere.
     *
     * @param {number} value - The new radius of the sphere.
     */ set radius(value) {
        this._radius = value ?? this._radius;
        this._update();
    }
    /**
     * Get the rendered radius of the sphere.
     *
     * @returns {number} The radius of the sphere.
     */ get radius() {
        return this._radius;
    }
    /**
     * Set the intersection tolerance of the sphere.
     *
     * @param {number} value - The new tolerance of the sphere.
     */ set tolerance(value) {
        this._tolerance = value ?? this._tolerance;
        this._update();
    }
    /**
     * Get the intersection tolerance of the sphere.
     *
     * @returns {number} The tolerance of the sphere.
     */ get tolerance() {
        return this._tolerance;
    }
    /**
     * Update the shape's transform.
     *
     * @protected
     * @override
     */ _update() {
        // intersect/render
        const scale = this._radius * 2;
        this.entity.setLocalScale(scale, scale, scale);
    }
};

/**
 * @import { CameraComponent } from '../../framework/components/camera/component.js'
 * @import { GraphNode } from '../../scene/graph-node.js'
 * @import { Layer } from '../../scene/layer.js'
 * @import { GizmoAxis } from './constants.js'
 */ // temporary variables
const v1$3 = new Vec3();
const v2$3 = new Vec3();
const point$2 = new Vec3();
const delta$1 = new Vec3();
const q$2 = new Quat();
// constants
const GLANCE_EPSILON$1 = 0.01;
const AXES = /** @type {('x' | 'y' | 'z')[]} */ [
    'x',
    'y',
    'z'
];
/**
 * The TranslateGizmo provides interactive 3D manipulation handles for translating/moving
 * {@link Entity}s in a {@link Scene}. It creates a visual widget with arrows along the X, Y
 * and Z axes, planes at their intersections, and a center sphere, allowing precise control over
 * object positioning through direct manipulation. The gizmo's visual appearance can be customized
 * away from the defaults as required.
 *
 * Note that the gizmo can be driven by both mouse+keyboard and touch input.
 *
 * ```javascript
 * // Create a layer for rendering all gizmos
 * const gizmoLayer = pc.Gizmo.createLayer(app);
 *
 * // Create a translate gizmo
 * const gizmo = new pc.TranslateGizmo(cameraComponent, gizmoLayer);
 *
 * // Create an entity to attach the gizmo to
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     type: 'box'
 * });
 * app.root.addChild(entity);
 *
 * // Attach the gizmo to the entity
 * gizmo.attach([entity]);
 * ```
 *
 * Relevant Engine API examples:
 *
 * - [Translate Gizmo](https://playcanvas.github.io/#/gizmos/transform-translate)
 * - [Editor](https://playcanvas.github.io/#/misc/editor)
 *
 * @category Gizmo
 */ class TranslateGizmo extends TransformGizmo {
    /**
     * Creates a new TranslateGizmo object. Use {@link Gizmo.createLayer} to create the layer
     * required to display the gizmo.
     *
     * @param {CameraComponent} camera - The camera component.
     * @param {Layer} layer - The layer responsible for rendering the gizmo.
     * @example
     * const gizmo = new pc.TranslateGizmo(camera, layer);
     */ constructor(camera, layer){
        super(camera, layer, 'gizmo:translate'), this._shapes = {
            xyz: new SphereShape$1(this._device, {
                axis: 'xyz',
                layers: [
                    this._layer.id
                ],
                defaultColor: this._theme.shapeBase.xyz,
                hoverColor: this._theme.shapeHover.xyz,
                disabledColor: this._theme.disabled
            }),
            yz: new PlaneShape(this._device, {
                axis: 'x',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, -90),
                defaultColor: this._theme.shapeBase.x,
                hoverColor: this._theme.shapeHover.x,
                disabledColor: this._theme.disabled
            }),
            xz: new PlaneShape(this._device, {
                axis: 'y',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, 0),
                defaultColor: this._theme.shapeBase.y,
                hoverColor: this._theme.shapeHover.y,
                disabledColor: this._theme.disabled
            }),
            xy: new PlaneShape(this._device, {
                axis: 'z',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(90, 0, 0),
                defaultColor: this._theme.shapeBase.z,
                hoverColor: this._theme.shapeHover.z,
                disabledColor: this._theme.disabled
            }),
            x: new ArrowShape(this._device, {
                axis: 'x',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, -90),
                defaultColor: this._theme.shapeBase.x,
                hoverColor: this._theme.shapeHover.x,
                disabledColor: this._theme.disabled
            }),
            y: new ArrowShape(this._device, {
                axis: 'y',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, 0),
                defaultColor: this._theme.shapeBase.y,
                hoverColor: this._theme.shapeHover.y,
                disabledColor: this._theme.disabled
            }),
            z: new ArrowShape(this._device, {
                axis: 'z',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(90, 0, 0),
                defaultColor: this._theme.shapeBase.z,
                hoverColor: this._theme.shapeHover.z,
                disabledColor: this._theme.disabled
            })
        }, /**
     * Internal mapping from each attached node to their starting position in local space.
     *
     * @type {Map<GraphNode, Vec3>}
     * @private
     */ this._nodeLocalPositions = new Map(), /**
     * Internal mapping from each attached node to their starting position in world space.
     *
     * @type {Map<GraphNode, Vec3>}
     * @private
     */ this._nodePositions = new Map(), /**
     * @override
     */ this.snapIncrement = 1, /**
     * Whether to flip the axes to face the camera.
     *
     * @type {boolean}
     */ this.flipAxes = true, /**
     * Flips the planes to face the camera.
     *
     * @type {boolean}
     */ this.flipPlanes = true;
        this._createTransform();
        this.on(TransformGizmo.EVENT_TRANSFORMSTART, ()=>{
            // store the initial positions of the nodes
            this._storeNodePositions();
            // hide shapes that are not selected
            this._drag(true);
        });
        this.on(TransformGizmo.EVENT_TRANSFORMMOVE, (point)=>{
            // calculate translate delta and update node positions
            const translateDelta = delta$1.copy(point).sub(this._selectionStartPoint);
            if (this.snap) {
                translateDelta.mulScalar(1 / this.snapIncrement);
                translateDelta.round();
                translateDelta.mulScalar(this.snapIncrement);
            }
            this._setNodePositions(translateDelta);
        });
        this.on(TransformGizmo.EVENT_TRANSFORMEND, ()=>{
            // show all shapes
            this._drag(false);
        });
        this.on(TransformGizmo.EVENT_NODESDETACH, ()=>{
            // reset stored positions
            this._nodeLocalPositions.clear();
            this._nodePositions.clear();
        });
    }
    /**
     * Sets the axis gap.
     *
     * @type {number}
     */ set axisGap(value) {
        this._setArrowProp('gap', value);
    }
    /**
     * Gets the axis gap.
     *
     * @type {number}
     */ get axisGap() {
        return this._shapes.x.gap;
    }
    /**
     * Sets the axis line thickness.
     *
     * @type {number}
     */ set axisLineThickness(value) {
        this._setArrowProp('lineThickness', value);
    }
    /**
     * Gets the axis line thickness.
     *
     * @type {number}
     */ get axisLineThickness() {
        return this._shapes.x.lineThickness;
    }
    /**
     * Sets the axis line length.
     *
     * @type {number}
     */ set axisLineLength(value) {
        this._setArrowProp('lineLength', value);
    }
    /**
     * Gets the axis line length.
     *
     * @type {number}
     */ get axisLineLength() {
        return this._shapes.x.lineLength;
    }
    /**
     * Sets the axis line tolerance.
     *
     * @type {number}
     */ set axisLineTolerance(value) {
        this._setArrowProp('tolerance', value);
    }
    /**
     * Gets the axis line tolerance.
     *
     * @type {number}
     */ get axisLineTolerance() {
        return this._shapes.x.tolerance;
    }
    /**
     * Sets the arrow thickness.
     *
     * @type {number}
     */ set axisArrowThickness(value) {
        this._setArrowProp('arrowThickness', value);
    }
    /**
     * Gets the arrow thickness.
     *
     * @type {number}
     */ get axisArrowThickness() {
        return this._shapes.x.arrowThickness;
    }
    /**
     * Sets the arrow length.
     *
     * @type {number}
     */ set axisArrowLength(value) {
        this._setArrowProp('arrowLength', value);
    }
    /**
     * Gets the arrow length.
     *
     * @type {number}
     */ get axisArrowLength() {
        return this._shapes.x.arrowLength;
    }
    /**
     * Sets the plane size.
     *
     * @type {number}
     */ set axisPlaneSize(value) {
        this._setPlaneProp('size', value);
    }
    /**
     * Gets the plane size.
     *
     * @type {number}
     */ get axisPlaneSize() {
        return this._shapes.yz.size;
    }
    /**
     * Sets the plane gap.
     *
     * @type {number}
     */ set axisPlaneGap(value) {
        this._setPlaneProp('gap', value);
    }
    /**
     * Gets the plane gap.
     *
     * @type {number}
     */ get axisPlaneGap() {
        return this._shapes.yz.gap;
    }
    /**
     * Sets the axis center size.
     *
     * @type {number}
     */ set axisCenterSize(value) {
        this._shapes.xyz.radius = value;
    }
    /**
     * Gets the axis center size.
     *
     * @type {number}
     */ get axisCenterSize() {
        return this._shapes.xyz.radius;
    }
    /**
     * Sets the axis center tolerance.
     *
     * @type {number}
     */ set axisCenterTolerance(value) {
        this._shapes.xyz.tolerance = value;
    }
    /**
     * Gets the axis center tolerance.
     *
     * @type {number}
     */ get axisCenterTolerance() {
        return this._shapes.xyz.tolerance;
    }
    /**
     * @type {boolean}
     * @deprecated Use {@link TranslateGizmo#flipAxes} or {@link TranslateGizmo#flipPlanes} instead.
     * @ignore
     */ set flipShapes(value) {
        this.flipAxes = value;
        this.flipPlanes = value;
    }
    /**
     * @type {boolean}
     * @deprecated Use {@link TranslateGizmo#flipAxes} or {@link TranslateGizmo#flipPlanes} instead.
     * @ignore
     */ get flipShapes() {
        return this.flipAxes && this.flipPlanes;
    }
    /**
     * @param {string} prop - The property to set.
     * @param {any} value - The value to set.
     * @private
     */ _setArrowProp(prop, value) {
        this._shapes.x[prop] = value;
        this._shapes.y[prop] = value;
        this._shapes.z[prop] = value;
    }
    /**
     * @param {string} prop - The property to set.
     * @param {any} value - The value to set.
     * @private
     */ _setPlaneProp(prop, value) {
        this._shapes.yz[prop] = value;
        this._shapes.xz[prop] = value;
        this._shapes.xy[prop] = value;
    }
    /**
     * @private
     */ _shapesLookAtCamera() {
        const cameraDir = this.cameraDir;
        // axes
        let changed = false;
        let dot, enabled, flipAxis;
        dot = cameraDir.dot(this.root.right);
        enabled = 1 - Math.abs(dot) > GLANCE_EPSILON$1;
        if (this._shapes.x.entity.enabled !== enabled) {
            this._shapes.x.entity.enabled = enabled;
            changed = true;
        }
        if (this.flipAxes) {
            flipAxis = dot < 0;
            if (this._shapes.x.flipped !== flipAxis) {
                this._shapes.x.flipped = flipAxis;
                changed = true;
            }
        }
        dot = cameraDir.dot(this.root.up);
        enabled = 1 - Math.abs(dot) > GLANCE_EPSILON$1;
        if (this._shapes.y.entity.enabled !== enabled) {
            this._shapes.y.entity.enabled = enabled;
            changed = true;
        }
        if (this.flipAxes) {
            flipAxis = dot < 0;
            if (this._shapes.y.flipped !== flipAxis) {
                this._shapes.y.flipped = flipAxis;
                changed = true;
            }
        }
        dot = cameraDir.dot(this.root.forward);
        enabled = 1 - Math.abs(dot) > GLANCE_EPSILON$1;
        if (this._shapes.z.entity.enabled !== enabled) {
            this._shapes.z.entity.enabled = enabled;
            changed = true;
        }
        if (this.flipAxes) {
            flipAxis = dot > 0;
            if (this._shapes.z.flipped !== flipAxis) {
                this._shapes.z.flipped = flipAxis;
                changed = true;
            }
        }
        // planes
        let flipped;
        v1$3.cross(cameraDir, this.root.right);
        if (this.flipPlanes) {
            enabled = 1 - v1$3.length() > GLANCE_EPSILON$1;
            if (this._shapes.yz.entity.enabled !== enabled) {
                this._shapes.yz.entity.enabled = enabled;
                changed = true;
            }
        }
        flipped = v2$3.set(0, +(v1$3.dot(this.root.forward) < 0), +(v1$3.dot(this.root.up) < 0));
        if (!this._shapes.yz.flipped.equals(flipped)) {
            this._shapes.yz.flipped = flipped;
            changed = true;
        }
        v1$3.cross(cameraDir, this.root.forward);
        if (this.flipPlanes) {
            enabled = 1 - v1$3.length() > GLANCE_EPSILON$1;
            if (this._shapes.xy.entity.enabled !== enabled) {
                this._shapes.xy.entity.enabled = enabled;
                changed = true;
            }
        }
        flipped = v2$3.set(+(v1$3.dot(this.root.up) < 0), +(v1$3.dot(this.root.right) > 0), 0);
        if (!this._shapes.xy.flipped.equals(flipped)) {
            this._shapes.xy.flipped = flipped;
            changed = true;
        }
        v1$3.cross(cameraDir, this.root.up);
        if (this.flipPlanes) {
            enabled = 1 - v1$3.length() > GLANCE_EPSILON$1;
            if (this._shapes.xz.entity.enabled !== enabled) {
                this._shapes.xz.entity.enabled = enabled;
                changed = true;
            }
        }
        flipped = v2$3.set(+(v1$3.dot(this.root.forward) > 0), 0, +(v1$3.dot(this.root.right) > 0));
        if (!this._shapes.xz.flipped.equals(flipped)) {
            this._shapes.xz.flipped = flipped;
            changed = true;
        }
        if (changed) {
            this._renderUpdate = true;
        }
    }
    /**
     * @param {boolean} state - The state.
     * @private
     */ _drag(state) {
        for(const axis in this._shapes){
            const shape = this._shapes[axis];
            switch(this.dragMode){
                case 'show':
                    {
                        continue;
                    }
                case 'hide':
                    {
                        shape.visible = !state;
                        continue;
                    }
                case 'selected':
                    {
                        if (this._selectedAxis === 'xyz') {
                            shape.visible = state ? axis.length === 1 : true;
                            continue;
                        }
                        if (this._selectedIsPlane) {
                            shape.visible = state ? axis.length === 1 && !axis.includes(this._selectedAxis) : true;
                            continue;
                        }
                        shape.visible = state ? axis === this._selectedAxis : true;
                    }
            }
        }
        this._renderUpdate = true;
    }
    /**
     * @private
     */ _storeNodePositions() {
        for(let i = 0; i < this.nodes.length; i++){
            const node = this.nodes[i];
            this._nodeLocalPositions.set(node, node.getLocalPosition().clone());
            this._nodePositions.set(node, node.getPosition().clone());
        }
    }
    /**
     * @param {Vec3} translateDelta - The delta to apply to the node positions.
     * @private
     */ _setNodePositions(translateDelta) {
        for(let i = 0; i < this.nodes.length; i++){
            const node = this.nodes[i];
            if (this._coordSpace === 'local') {
                const pos = this._nodeLocalPositions.get(node);
                if (!pos) {
                    continue;
                }
                v1$3.copy(translateDelta);
                node.parent?.getWorldTransform().getScale(v2$3);
                v2$3.x = 1 / v2$3.x;
                v2$3.y = 1 / v2$3.y;
                v2$3.z = 1 / v2$3.z;
                q$2.copy(node.getLocalRotation()).transformVector(v1$3, v1$3);
                v1$3.mul(v2$3);
                node.setLocalPosition(v1$3.add(pos));
            } else {
                const pos = this._nodePositions.get(node);
                if (!pos) {
                    continue;
                }
                node.setPosition(v1$3.copy(translateDelta).add(pos));
            }
        }
        this._updatePosition();
    }
    /**
     * @param {number} x - The x coordinate.
     * @param {number} y - The y coordinate.
     * @returns {Vec3} The point (space is {@link TransformGizmo#coordSpace}).
     * @protected
     */ _screenToPoint(x, y) {
        const mouseWPos = this._camera.screenToWorld(x, y, 1);
        const axis = this._selectedAxis;
        const isPlane = this._selectedIsPlane;
        const ray = this._createRay(mouseWPos);
        const plane = this._createPlane(axis, axis === 'xyz', !isPlane);
        if (!plane.intersectsRay(ray, point$2)) {
            return point$2;
        }
        // rotate point back to world coords
        q$2.copy(this._rootStartRot).invert().transformVector(point$2, point$2);
        // project point onto axis
        if (!isPlane && axis !== 'xyz') {
            this._projectToAxis(point$2, axis);
        }
        return point$2;
    }
    /**
     * @param {Vec3} pos - The position.
     * @param {Quat} rot - The rotation.
     * @param {GizmoAxis} activeAxis - The active axis.
     * @param {boolean} activeIsPlane - Whether the active axis is a plane.
     * @override
     */ _drawGuideLines(pos, rot, activeAxis, activeIsPlane) {
        for (const axis of AXES){
            if (this._dragging || activeAxis === 'xyz') {
                this._drawSpanLine(pos, rot, axis);
                continue;
            }
            if (activeIsPlane) {
                if (axis !== activeAxis) {
                    this._drawSpanLine(pos, rot, axis);
                }
            } else {
                if (axis === activeAxis) {
                    this._drawSpanLine(pos, rot, axis);
                }
            }
        }
    }
    /**
     * @override
     */ prerender() {
        super.prerender();
        if (!this.enabled) {
            return;
        }
        this._shapesLookAtCamera();
    }
}

/** @import { ShapeArgs } from './shape.js' */ /** @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js' */ const TORUS_RENDER_SEGMENTS = 80;
const TORUS_INTERSECT_SEGMENTS = 20;
/**
 * @typedef {object} ArcShapeArgs
 * @property {number} [tubeRadius] - The tube radius.
 * @property {number} [ringRadius] - The ring radius.
 * @property {number} [sectorAngle] - The sector angle.
 */ /**
 * @ignore
 */ class ArcShape extends Shape {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {ShapeArgs & ArcShapeArgs} args - The shape options.
     */ constructor(device, args = {}){
        super(device, 'disk', args), /**
     * The internal tube radius of the arc.
     *
     * @type {number}
     * @private
     */ this._tubeRadius = 0.01, /**
     * The internal ring radius of the arc.
     *
     * @type {number}
     * @private
     */ this._ringRadius = 0.5, /**
     * The internal sector angle of the arc.
     *
     * @type {number}
     * @private
     */ this._sectorAngle = 360, /**
     * The internal intersection tolerance of the arc.
     *
     * @type {number}
     * @private
     */ this._tolerance = 0.05;
        this._tubeRadius = args.tubeRadius ?? this._tubeRadius;
        this._ringRadius = args.ringRadius ?? this._ringRadius;
        this._sectorAngle = args.sectorAngle ?? this._sectorAngle;
        // intersect
        this._triDataCache = [
            new TriData(this._createTorusGeometry(this._sectorAngle)),
            new TriData(this._createTorusGeometry(360))
        ];
        this.triData = [
            this._triDataCache[0]
        ];
        // render
        this._createRenderComponent(this.entity, [
            this._createTorusMesh(this._sectorAngle),
            this._createTorusMesh(360)
        ]);
        this.show('sector');
        // update transform
        this._update();
    }
    /**
     * Create the torus geometry.
     *
     * @param {number} sectorAngle - The sector angle.
     * @returns {TorusGeometry} The torus geometry.
     * @private
     */ _createTorusGeometry(sectorAngle) {
        return new TorusGeometry({
            tubeRadius: this._tubeRadius + this._tolerance,
            ringRadius: this._ringRadius,
            sectorAngle: sectorAngle,
            segments: TORUS_INTERSECT_SEGMENTS
        });
    }
    /**
     * Create the torus mesh.
     *
     * @param {number} sectorAngle - The sector angle.
     * @returns {Mesh} The torus mesh.
     * @private
     */ _createTorusMesh(sectorAngle) {
        const geom = new TorusGeometry({
            tubeRadius: this._tubeRadius,
            ringRadius: this._ringRadius,
            sectorAngle: sectorAngle,
            segments: TORUS_RENDER_SEGMENTS
        });
        return Mesh.fromGeometry(this.device, geom);
    }
    /**
     * Set the tube radius.
     *
     * @type {number}
     */ set tubeRadius(value) {
        this._tubeRadius = value ?? this._tubeRadius;
        this._update();
    }
    /**
     * Get the tube radius.
     *
     * @type {number}
     */ get tubeRadius() {
        return this._tubeRadius;
    }
    /**
     * Set the ring radius.
     *
     * @type {number}
     */ set ringRadius(value) {
        this._ringRadius = value ?? this._ringRadius;
        this._update();
    }
    /**
     * Get the ring radius.
     *
     * @type {number}
     */ get ringRadius() {
        return this._ringRadius;
    }
    /**
     * Set the intersection tolerance.
     *
     * @type {number}
     */ set tolerance(value) {
        this._tolerance = value ?? this._tolerance;
        this._update();
    }
    /**
     * Get the intersection tolerance.
     *
     * @type {number}
     */ get tolerance() {
        return this._tolerance;
    }
    /**
     * Update the shape's transform.
     *
     * @protected
     * @override
     */ _update() {
        // intersect
        this._triDataCache[0].fromGeometry(this._createTorusGeometry(this._sectorAngle));
        this._triDataCache[1].fromGeometry(this._createTorusGeometry(360));
        // render
        this.meshInstances[0].mesh = this._createTorusMesh(this._sectorAngle);
        this.meshInstances[1].mesh = this._createTorusMesh(360);
    }
    /**
     * @param {'sector' | 'ring' | 'none'} state - The visibility state.
     */ show(state) {
        switch(state){
            case 'sector':
                {
                    this.triData[0] = this._triDataCache[0];
                    this.meshInstances[0].visible = true;
                    this.meshInstances[1].visible = false;
                    break;
                }
            case 'ring':
                {
                    this.triData[0] = this._triDataCache[1];
                    this.meshInstances[0].visible = false;
                    this.meshInstances[1].visible = true;
                    break;
                }
            case 'none':
                {
                    this.meshInstances[0].visible = false;
                    this.meshInstances[1].visible = false;
                    break;
                }
        }
    }
}

/**
 * @import { Color } from '../../core/math/color.js'
 * @import { AppBase } from '../../framework/app-base.js'
 * @import { Layer } from '../../scene/layer.js'
 */ const tmpV1$1 = new Vec3();
/**
 * @ignore
 */ class MeshLine {
    /**
     * @param {AppBase} app - The application instance
     * @param {Layer} layer - The layer to draw the guideline on
     * @param {object} [args] - The arguments object
     * @param {number} [args.thickness] - The thickness of the line
     */ constructor(app, layer, args = {}){
        /**
     * @type {number}
     * @private
     */ this._thickness = 0.02;
        /**
     * @type {ShaderMaterial}
     * @private
     */ this._material = new ShaderMaterial(unlitShader);
        /**
     * @type {Entity}
     */ this.entity = new Entity('mesh-line');
        this._thickness = args.thickness ?? this._thickness;
        this._material.blendState = BlendState.ALPHABLEND;
        this._material.setDefine('DEPTH_WRITE', '0');
        this._material.update();
        const mesh = Mesh.fromGeometry(app.graphicsDevice, new CylinderGeometry());
        const meshInstance = new MeshInstance(mesh, this._material);
        this.entity.addComponent('render', {
            meshInstances: [
                meshInstance
            ],
            layers: [
                layer.id
            ]
        });
    }
    /**
     * @type {number}
     */ set thickness(value) {
        this._thickness = value ?? this._thickness;
    }
    /**
     * @type {number}
     */ get thickness() {
        return this._thickness;
    }
    /**
     * Draw a line from one point to another with a specific color.
     *
     * @param {Vec3} from - The starting point of the line.
     * @param {Vec3} to - The ending point of the line.
     * @param {number} scale - The scale of the line.
     * @param {Color} color - The color of the line.
     */ draw(from, to, scale, color) {
        this._material.setParameter('uColor', color.toArray());
        const dir = tmpV1$1.sub2(to, from).normalize();
        const elev = Math.atan2(-dir.y, Math.sqrt(dir.x * dir.x + dir.z * dir.z)) * math.RAD_TO_DEG;
        const azim = Math.atan2(-dir.x, -dir.z) * math.RAD_TO_DEG;
        this.entity.setLocalEulerAngles(-elev + 90, azim, 0);
        const length = from.distance(to) * scale;
        this.entity.setLocalPosition(dir.mulScalar(0.5 * length).add(from));
        this.entity.setLocalScale(this._thickness * scale, length, this._thickness * scale);
    }
}

/**
 * @import { CameraComponent } from '../../framework/components/camera/component.js'
 * @import { GraphNode } from '../../scene/graph-node.js'
 * @import { Layer } from '../../scene/layer.js'
 * @import { GizmoAxis } from './constants.js'
 */ // temporary variables
const point$1 = new Vec3();
const v1$2 = new Vec3();
const v2$2 = new Vec3();
const v3 = new Vec3();
const v4 = new Vec3();
const q1 = new Quat();
const q2 = new Quat();
const color = new Color();
// constants
const ROTATE_FACING_EPSILON = 0.1;
const RING_FACING_EPSILON = 1e-4;
const UPDATE_EPSILON = 1e-6;
/**
 * The RotateGizmo provides interactive 3D manipulation handles for rotating/reorienting
 * {@link Entity}s in a {@link Scene}. It creates a visual widget with a draggable ring for each
 * axis of rotation, plus a fourth ring for rotation in the camera's view plane, allowing precise
 * control over object orientation through direct manipulation. The gizmo's visual appearance can
 * be customized away from the defaults as required.
 *
 * Note that the gizmo can be driven by both mouse+keyboard and touch input.
 *
 * ```javascript
 * // Create a layer for rendering all gizmos
 * const gizmoLayer = pc.Gizmo.createLayer(app);
 *
 * // Create a rotate gizmo
 * const gizmo = new pc.RotateGizmo(cameraComponent, gizmoLayer);
 *
 * // Create an entity to attach the gizmo to
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     type: 'box'
 * });
 * app.root.addChild(entity);
 *
 * // Attach the gizmo to the entity
 * gizmo.attach([entity]);
 * ```
 *
 * Relevant Engine API examples:
 *
 * - [Rotate Gizmo](https://playcanvas.github.io/#/gizmos/transform-rotate)
 * - [Editor](https://playcanvas.github.io/#/misc/editor)
 *
 * @category Gizmo
 */ class RotateGizmo extends TransformGizmo {
    /**
     * Creates a new RotateGizmo object. Use {@link Gizmo.createLayer} to create the layer
     * required to display the gizmo.
     *
     * @param {CameraComponent} camera - The camera component.
     * @param {Layer} layer - The layer responsible for rendering the gizmo.
     * @example
     * const gizmo = new pc.RotateGizmo(camera, layer);
     */ constructor(camera, layer){
        super(camera, layer, 'gizmo:rotate'), this._shapes = {
            z: new ArcShape(this._device, {
                axis: 'z',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(90, 0, 90),
                defaultColor: this._theme.shapeBase.z,
                hoverColor: this._theme.shapeHover.z,
                disabledColor: this._theme.disabled,
                sectorAngle: 180
            }),
            x: new ArcShape(this._device, {
                axis: 'x',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, -90),
                defaultColor: this._theme.shapeBase.x,
                hoverColor: this._theme.shapeHover.x,
                disabledColor: this._theme.disabled,
                sectorAngle: 180
            }),
            y: new ArcShape(this._device, {
                axis: 'y',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, 0),
                defaultColor: this._theme.shapeBase.y,
                hoverColor: this._theme.shapeHover.y,
                disabledColor: this._theme.disabled,
                sectorAngle: 180
            }),
            f: new ArcShape(this._device, {
                axis: 'f',
                layers: [
                    this._layer.id
                ],
                defaultColor: this._theme.shapeBase.f,
                hoverColor: this._theme.shapeHover.f,
                disabledColor: this._theme.disabled,
                ringRadius: 0.55
            })
        }, /**
     * Internal selection starting angle in world space.
     *
     * @type {number}
     * @private
     */ this._selectionStartAngle = 0, /**
     * Internal mapping from each attached node to their starting rotation in local space.
     *
     * @type {Map<GraphNode, Quat>}
     * @private
     */ this._nodeLocalRotations = new Map(), /**
     * Internal mapping from each attached node to their starting rotation in world space.
     *
     * @type {Map<GraphNode, Quat>}
     * @private
     */ this._nodeRotations = new Map(), /**
     * Internal mapping from each attached node to their offset position from the gizmo.
     *
     * @type {Map<GraphNode, Vec3>}
     * @private
     */ this._nodeOffsets = new Map(), /**
     * Internal vector for the start point of the guide line angle.
     *
     * @type {Vec3}
     * @private
     */ this._guideAngleStart = new Vec3(), /**
     * Internal vector for the end point of the guide line angle.
     *
     * @type {Vec3}
     * @private
     */ this._guideAngleEnd = new Vec3(), /**
     * Internal copy of facing direction to avoid unnecessary updates.
     *
     * @type {Vec3}
     * @private
     */ this._facingDir = new Vec3(), /**
     * @override
     */ this.snapIncrement = 5, /**
     * This forces the rotation to always be calculated based on the mouse position around the gizmo.
     *
     * @type {boolean}
     */ this.orbitRotation = false;
        this._createTransform();
        this._guideAngleLines = [
            new MeshLine(this._app, this._layer),
            new MeshLine(this._app, this._layer)
        ];
        this._guideAngleLines.forEach((line)=>{
            this._app.root.addChild(line.entity);
            line.entity.enabled = false;
        });
        this.on(TransformGizmo.EVENT_TRANSFORMSTART, (point, x, y)=>{
            // store start angle
            this._selectionStartAngle = this._calculateArcAngle(point, x, y);
            // store initial node rotations
            this._storeNodeRotations();
            // store guide points
            this._storeGuidePoints();
            // drag handle for disk (arc <-> circle)
            this._drag(true);
            // angle guide lines
            this._angleGuide(true);
        });
        this.on(TransformGizmo.EVENT_TRANSFORMMOVE, (point, x, y)=>{
            const axis = this._selectedAxis;
            if (!axis) {
                return;
            }
            // calculate angle axis and delta and update node rotations
            let angleDelta = this._calculateArcAngle(point, x, y) - this._selectionStartAngle;
            if (this.snap) {
                angleDelta = Math.round(angleDelta / this.snapIncrement) * this.snapIncrement;
            }
            const angleAxis = this._dirFromAxis(axis, v1$2);
            this._setNodeRotations(axis, angleAxis, angleDelta);
            // update guide points and show angle guide
            this._updateGuidePoints(angleDelta);
            this._angleGuide(true);
        });
        this.on(TransformGizmo.EVENT_TRANSFORMEND, ()=>{
            // show all shapes
            this._drag(false);
            // hide angle guide
            this._angleGuide(false);
        });
        this.on(TransformGizmo.EVENT_NODESDETACH, ()=>{
            // reset stored rotations and offsets
            this._nodeLocalRotations.clear();
            this._nodeRotations.clear();
            this._nodeOffsets.clear();
        });
    }
    /**
     * Sets the XYZ tube radius.
     *
     * @type {number}
     */ set xyzTubeRadius(value) {
        this._setDiskProp('tubeRadius', value);
    }
    /**
     * Gets the XYZ tube radius.
     *
     * @type {number}
     */ get xyzTubeRadius() {
        return this._shapes.x.tubeRadius;
    }
    /**
     * Sets the XYZ ring radius.
     *
     * @type {number}
     */ set xyzRingRadius(value) {
        this._setDiskProp('ringRadius', value);
    }
    /**
     * Gets the XYZ ring radius.
     *
     * @type {number}
     */ get xyzRingRadius() {
        return this._shapes.x.ringRadius;
    }
    /**
     * Sets the face tube radius.
     *
     * @type {number}
     */ set faceTubeRadius(value) {
        this._shapes.f.tubeRadius = value;
    }
    /**
     * Gets the face tube radius.
     *
     * @type {number}
     */ get faceTubeRadius() {
        return this._shapes.f.tubeRadius;
    }
    /**
     * Sets the face ring radius.
     *
     * @type {number}
     */ set faceRingRadius(value) {
        this._shapes.f.ringRadius = value;
    }
    /**
     * Gets the face ring radius.
     *
     * @type {number}
     */ get faceRingRadius() {
        return this._shapes.f.ringRadius;
    }
    /**
     * Sets the ring tolerance.
     *
     * @type {number}
     */ set ringTolerance(value) {
        this._setDiskProp('tolerance', value);
        this._shapes.f.tolerance = value;
    }
    /**
     * Gets the ring tolerance.
     *
     * @type {number}
     */ get ringTolerance() {
        return this._shapes.x.tolerance;
    }
    /**
     * Sets the angle guide line thickness.
     *
     * @type {number}
     */ set angleGuideThickness(value) {
        this._guideAngleLines[0].thickness = value;
        this._guideAngleLines[1].thickness = value;
    }
    /**
     * Gets the angle guide line thickness.
     *
     * @type {number}
     */ get angleGuideThickness() {
        return this._guideAngleLines[0].thickness;
    }
    /**
     * @param {string} prop - The property.
     * @param {any} value - The value.
     * @private
     */ _setDiskProp(prop, value) {
        this._shapes.x[prop] = value;
        this._shapes.y[prop] = value;
        this._shapes.z[prop] = value;
    }
    /**
     * @private
     */ _storeGuidePoints() {
        const gizmoPos = this.root.getLocalPosition();
        const axis = this._selectedAxis;
        const isFacing = axis === 'f';
        const scale = isFacing ? this.faceRingRadius : this.xyzRingRadius;
        this._guideAngleStart.copy(this._selectionStartPoint).sub(gizmoPos).normalize();
        this._guideAngleStart.mulScalar(scale);
        this._guideAngleEnd.copy(this._guideAngleStart);
    }
    /**
     * @param {number} angleDelta - The angle delta.
     * @private
     */ _updateGuidePoints(angleDelta) {
        const axis = this._selectedAxis;
        const isFacing = axis === 'f';
        if (isFacing) {
            v1$2.copy(this.facingDir);
        } else {
            v1$2.set(0, 0, 0);
            v1$2[axis] = 1;
            this._rootStartRot.transformVector(v1$2, v1$2);
        }
        q1.setFromAxisAngle(v1$2, angleDelta);
        q1.transformVector(this._guideAngleStart, this._guideAngleEnd);
        this._renderUpdate = true;
    }
    /**
     * @param {boolean} state - The state.
     * @private
     */ _angleGuide(state) {
        const axis = this._selectedAxis;
        if (state && this.dragMode !== 'show' && axis !== 'xyz') {
            const gizmoPos = this.root.getLocalPosition();
            const baseColor = this._theme.guideBase[axis];
            const startColor = color.copy(baseColor);
            startColor.a *= 0.3;
            this._guideAngleLines[0].draw(gizmoPos, v1$2.copy(this._guideAngleStart).add(gizmoPos), this._scale, startColor);
            this._guideAngleLines[1].draw(gizmoPos, v1$2.copy(this._guideAngleEnd).add(gizmoPos), this._scale, baseColor);
            this._guideAngleLines[0].entity.enabled = true;
            this._guideAngleLines[1].entity.enabled = true;
        } else {
            this._guideAngleLines[0].entity.enabled = false;
            this._guideAngleLines[1].entity.enabled = false;
        }
    }
    /**
     * @private
     */ _shapesLookAtCamera() {
        // face shape
        if (this._camera.projection === PROJECTION_PERSPECTIVE) {
            const dir = this._camera.entity.getPosition().sub(this.root.getPosition()).normalize();
            const elev = Math.atan2(-dir.y, Math.sqrt(dir.x * dir.x + dir.z * dir.z)) * math.RAD_TO_DEG;
            const azim = Math.atan2(-dir.x, -dir.z) * math.RAD_TO_DEG;
            this._shapes.f.entity.setEulerAngles(-elev + 90, azim, 0);
        } else {
            q1.copy(this._camera.entity.getRotation()).getEulerAngles(v1$2);
            this._shapes.f.entity.setEulerAngles(v1$2);
            this._shapes.f.entity.rotateLocal(-90, 0, 0);
        }
        // axes shapes
        let angle, dot, sector;
        const facingDir = v1$2.copy(this.facingDir);
        q1.copy(this.root.getRotation()).invert().transformVector(facingDir, facingDir);
        angle = Math.atan2(facingDir.z, facingDir.y) * math.RAD_TO_DEG;
        this._shapes.x.entity.setLocalEulerAngles(0, angle - 90, -90);
        angle = Math.atan2(facingDir.x, facingDir.z) * math.RAD_TO_DEG;
        this._shapes.y.entity.setLocalEulerAngles(0, angle, 0);
        angle = Math.atan2(facingDir.y, facingDir.x) * math.RAD_TO_DEG;
        this._shapes.z.entity.setLocalEulerAngles(90, 0, angle + 90);
        if (!this._dragging) {
            dot = facingDir.dot(this.root.right);
            sector = 1 - Math.abs(dot) > RING_FACING_EPSILON;
            this._shapes.x.show(sector ? 'sector' : 'ring');
            dot = facingDir.dot(this.root.up);
            sector = 1 - Math.abs(dot) > RING_FACING_EPSILON;
            this._shapes.y.show(sector ? 'sector' : 'ring');
            dot = facingDir.dot(this.root.forward);
            sector = 1 - Math.abs(dot) > RING_FACING_EPSILON;
            this._shapes.z.show(sector ? 'sector' : 'ring');
        }
        if (!facingDir.equalsApprox(this._facingDir, UPDATE_EPSILON)) {
            this._facingDir.copy(facingDir);
            this._renderUpdate = true;
        }
    }
    /**
     * @param {boolean} state - The state.
     * @private
     */ _drag(state) {
        for(const axis in this._shapes){
            const shape = this._shapes[axis];
            if (!(shape instanceof ArcShape)) {
                continue;
            }
            switch(this.dragMode){
                case 'show':
                    {
                        break;
                    }
                case 'hide':
                    {
                        shape.show(state ? axis === this._selectedAxis ? 'ring' : 'none' : 'sector');
                        continue;
                    }
                case 'selected':
                    {
                        shape.show(state ? axis === this._selectedAxis ? 'ring' : 'sector' : 'sector');
                        break;
                    }
            }
        }
        this._renderUpdate = true;
    }
    /**
     * @private
     */ _storeNodeRotations() {
        const gizmoPos = this.root.getLocalPosition();
        for(let i = 0; i < this.nodes.length; i++){
            const node = this.nodes[i];
            this._nodeLocalRotations.set(node, node.getLocalRotation().clone());
            this._nodeRotations.set(node, node.getRotation().clone());
            this._nodeOffsets.set(node, node.getPosition().clone().sub(gizmoPos));
        }
    }
    /**
     * @param {GizmoAxis} axis - The axis.
     * @param {Vec3} angleAxis - The angle axis.
     * @param {number} angleDelta - The angle delta.
     * @private
     */ _setNodeRotations(axis, angleAxis, angleDelta) {
        const gizmoPos = this.root.getLocalPosition();
        // calculate rotation from axis and angle
        q1.setFromAxisAngle(angleAxis, angleDelta);
        for(let i = 0; i < this.nodes.length; i++){
            const node = this.nodes[i];
            if ((axis === 'x' || axis === 'y' || axis === 'z') && this._coordSpace === 'local') {
                const rot = this._nodeLocalRotations.get(node);
                if (!rot) {
                    continue;
                }
                q2.copy(rot).mul(q1);
                node.setLocalRotation(q2);
            } else {
                const rot = this._nodeRotations.get(node);
                if (!rot) {
                    continue;
                }
                const offset = this._nodeOffsets.get(node);
                if (!offset) {
                    continue;
                }
                v1$2.copy(offset);
                q1.transformVector(v1$2, v1$2);
                q2.copy(q1).mul(rot);
                // FIXME: Rotation via quaternion when scale inverted causes scale warping?
                node.setEulerAngles(q2.getEulerAngles());
                node.setPosition(v1$2.add(gizmoPos));
            }
        }
        if (this._coordSpace === 'local') {
            this._updateRotation();
        }
    }
    /**
     * @param {number} x - The x coordinate.
     * @param {number} y - The y coordinate.
     * @returns {Vec3} The point (space is {@link TransformGizmo#coordSpace}).
     * @protected
     */ _screenToPoint(x, y) {
        const mouseWPos = this._camera.screenToWorld(x, y, 1);
        const axis = this._selectedAxis;
        const ray = this._createRay(mouseWPos);
        const plane = this._createPlane(axis, axis === 'f', false);
        if (!plane.intersectsRay(ray, point$1)) {
            // use gizmo position if ray does not intersect to position angle guide correctly
            return point$1.copy(this.root.getLocalPosition());
        }
        return point$1;
    }
    /**
     * @param {Vec3} point - The point.
     * @param {number} x - The x coordinate.
     * @param {number} y - The y coordinate.
     * @returns {number} The angle.
     * @protected
     */ _calculateArcAngle(point, x, y) {
        const gizmoPos = this.root.getLocalPosition();
        const axis = this._selectedAxis;
        const plane = this._createPlane(axis, axis === 'f', false);
        let angle = 0;
        // arc angle
        const facingDir = v2$2.copy(this.facingDir);
        const facingDot = plane.normal.dot(facingDir);
        if (this.orbitRotation || 1 - Math.abs(facingDot) < ROTATE_FACING_EPSILON) {
            // plane facing camera so based on mouse position around gizmo
            v1$2.sub2(point, gizmoPos);
            switch(axis){
                case 'x':
                    {
                        // convert to local space
                        q1.copy(this._rootStartRot).invert().transformVector(v1$2, v1$2);
                        angle = Math.atan2(v1$2.z, v1$2.y) * math.RAD_TO_DEG;
                        break;
                    }
                case 'y':
                    {
                        // convert to local space
                        q1.copy(this._rootStartRot).invert().transformVector(v1$2, v1$2);
                        angle = Math.atan2(v1$2.x, v1$2.z) * math.RAD_TO_DEG;
                        break;
                    }
                case 'z':
                    {
                        // convert to local space
                        q1.copy(this._rootStartRot).invert().transformVector(v1$2, v1$2);
                        angle = Math.atan2(v1$2.y, v1$2.x) * math.RAD_TO_DEG;
                        break;
                    }
                case 'f':
                    {
                        // convert to camera space
                        q1.copy(this._camera.entity.getRotation()).invert().transformVector(v1$2, v1$2);
                        angle = Math.sign(facingDot) * Math.atan2(v1$2.y, v1$2.x) * math.RAD_TO_DEG;
                        break;
                    }
            }
        } else {
            // convert rotation axis to screen space
            v1$2.copy(gizmoPos);
            v2$2.cross(plane.normal, facingDir).normalize().add(gizmoPos);
            // convert world space vectors to screen space
            this._camera.worldToScreen(v1$2, v3);
            this._camera.worldToScreen(v2$2, v4);
            // angle is dot product with mouse position
            v1$2.sub2(v4, v3).normalize();
            v2$2.set(x, y, 0);
            angle = v1$2.dot(v2$2);
        }
        return angle;
    }
    /**
     * @override
     */ prerender() {
        super.prerender();
        if (!this.enabled) {
            return;
        }
        this._shapesLookAtCamera();
    }
}

/** @import { ShapeArgs } from './shape.js' */ /** @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js' */ /**
 * @typedef {object} BoxShapeArgs
 * @property {number} [size] - The size of the box.
 * @property {number} [tolerance] - The intersection tolerance of the box.
 */ /**
 * @ignore
 */ let BoxShape$1 = class BoxShape extends Shape {
    /**
     * Create a new BoxShape.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {ShapeArgs & BoxShapeArgs} args - The shape options.
     */ constructor(device, args = {}){
        super(device, 'boxCenter', args), /**
     * The internal size of the box.
     *
     * @type {number}
     * @private
     */ this._size = 0.12, /**
     * The internal intersection tolerance of the box.
     *
     * @type {number}
     * @private
     */ this._tolerance = 0.05;
        this._size = args.size ?? this._size;
        this._tolerance = args.tolerance ?? this._tolerance;
        // intersect
        this.triData = [
            new TriData(new BoxGeometry(), 2)
        ];
        // render
        this._createRenderComponent(this.entity, [
            Mesh.fromGeometry(this.device, new BoxGeometry())
        ]);
        // update transform
        this._update();
    }
    /**
     * Set the rendered size of the box.
     *
     * @param {number} value - The new size of the box.
     */ set size(value) {
        this._size = value ?? this._size;
        this._update();
    }
    /**
     * Get the rendered size of the box.
     *
     * @returns {number} The size of the box.
     */ get size() {
        return this._size;
    }
    /**
     * Set the intersection tolerance of the box.
     *
     * @param {number} value - The new tolerance of the box.
     */ set tolerance(value) {
        this._tolerance = value ?? this._tolerance;
        this._update();
    }
    /**
     * Get the intersection tolerance of the box.
     *
     * @returns {number} The tolerance of the box.
     */ get tolerance() {
        return this._tolerance;
    }
    /**
     * Update the shapes's transform.
     *
     * @protected
     * @override
     */ _update() {
        // intersect/render
        this.entity.setLocalScale(this._size, this._size, this._size);
    }
};

/** @import { ShapeArgs } from './shape.js' */ /** @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js' */ const tmpV1 = new Vec3();
const tmpV2 = new Vec3();
const tmpQ1 = new Quat();
/**
 * @typedef {object} BoxLineShapeArgs
 * @property {number} [gap] - The gap between the box and the line
 * @property {number} [lineThickness] - The thickness of the line
 * @property {number} [lineLength] - The length of the line
 * @property {number} [boxSize] - The size of the box
 * @property {number} [tolerance] - The tolerance for intersection tests
 */ /**
 * @ignore
 */ class BoxLineShape extends Shape {
    /**
     * Create a new BoxLineShape.
     *
     * @param {GraphicsDevice} device - The graphics device.
     * @param {ShapeArgs & BoxLineShapeArgs} args - The shape options.
     */ constructor(device, args = {}){
        super(device, 'boxLine', args), /**
     * The internal gap between the box and the line.
     *
     * @type {number}
     * @private
     */ this._gap = 0, /**
     * The internal line thickness of the box line.
     *
     * @type {number}
     * @private
     */ this._lineThickness = 0.02, /**
     * The internal line length of the box line.
     *
     * @type {number}
     * @private
     */ this._lineLength = 0.5, /**
     * The internal box size of the box line.
     *
     * @type {number}
     * @private
     */ this._boxSize = 0.12, /**
     * The internal tolerance of the box line.
     *
     * @type {number}
     * @private
     */ this._tolerance = 0.1, /**
     * The internal flipped state of the box line.
     *
     * @type {boolean}
     * @private
     */ this._flipped = false;
        this._gap = args.gap ?? this._gap;
        this._lineThickness = args.lineThickness ?? this._lineThickness;
        this._lineLength = args.lineLength ?? this._lineLength;
        this._boxSize = args.boxSize ?? this._boxSize;
        this._tolerance = args.tolerance ?? this._tolerance;
        // intersect
        this.triData = [
            new TriData(new BoxGeometry()),
            new TriData(new CylinderGeometry(), 1)
        ];
        // render
        this._box = new Entity(`box:${this.axis}`);
        this.entity.addChild(this._box);
        this._createRenderComponent(this._box, [
            Mesh.fromGeometry(this.device, new BoxGeometry())
        ]);
        this._line = new Entity(`line:${this.axis}`);
        this.entity.addChild(this._line);
        this._createRenderComponent(this._line, [
            Mesh.fromGeometry(this.device, new CylinderGeometry())
        ]);
        // update transform
        this._update();
    }
    /**
     * Set the gap between the box and the line.
     *
     * @type {number}
     */ set gap(value) {
        this._gap = value ?? this._gap;
        this._update();
    }
    /**
     * Get the gap between the box and the line.
     *
     * @type {number}
     */ get gap() {
        return this._gap;
    }
    /**
     * Set the line thickness of the box line.
     *
     * @type {number}
     */ set lineThickness(value) {
        this._lineThickness = value ?? this._lineThickness;
        this._update();
    }
    /**
     * Get the line thickness of the box line.
     *
     * @type {number}
     */ get lineThickness() {
        return this._lineThickness;
    }
    /**
     * Set the line length of the box line.
     *
     * @type {number}
     */ set lineLength(value) {
        this._lineLength = value ?? this._lineLength;
        this._update();
    }
    /**
     * Get the line length of the box line.
     *
     * @type {number}
     */ get lineLength() {
        return this._lineLength;
    }
    /**
     * Set the box size of the box line.
     *
     * @type {number}
     */ set boxSize(value) {
        this._boxSize = value ?? this._boxSize;
        this._update();
    }
    /**
     * Get the box size of the box line.
     *
     * @type {number}
     */ get boxSize() {
        return this._boxSize;
    }
    /**
     * Set the tolerance of the box line.
     *
     * @type {number}
     */ set tolerance(value) {
        this._tolerance = value;
        this._update();
    }
    /**
     * Get the tolerance of the box line.
     *
     * @type {number}
     */ get tolerance() {
        return this._tolerance;
    }
    /**
     * Set the flipped state of the box line.
     *
     * @type {boolean}
     */ set flipped(value) {
        if (this._flipped === value) {
            return;
        }
        this._flipped = value;
        if (this._rotation.equals(Vec3.ZERO)) {
            tmpV1.set(0, 0, this._flipped ? 180 : 0);
        } else {
            tmpV1.copy(this._rotation).mulScalar(this._flipped ? -1 : 1);
        }
        this._line.enabled = !this._flipped;
        this.entity.setLocalEulerAngles(tmpV1);
    }
    /**
     * Get the flipped state of the box line.
     *
     * @type {boolean}
     */ get flipped() {
        return this._flipped;
    }
    /**
     * Update the shape's transform.
     *
     * @protected
     * @override
     */ _update() {
        // intersect
        tmpV1.set(0, this._gap + this._boxSize * 0.5 + this._lineLength, 0);
        tmpQ1.set(0, 0, 0, 1);
        tmpV2.set(this._boxSize, this._boxSize, this._boxSize);
        this.triData[0].setTransform(tmpV1, tmpQ1, tmpV2);
        tmpV1.set(0, this._gap + this._lineLength * 0.5, 0);
        tmpQ1.set(0, 0, 0, 1);
        tmpV2.set(this._lineThickness + this._tolerance, this._lineLength, this._lineThickness + this._tolerance);
        this.triData[1].setTransform(tmpV1, tmpQ1, tmpV2);
        // render
        this._box.setLocalPosition(0, this._gap + this._boxSize * 0.5 + this._lineLength, 0);
        this._box.setLocalScale(this._boxSize, this._boxSize, this._boxSize);
        this._line.setLocalPosition(0, this._gap + this._lineLength * 0.5, 0);
        this._line.setLocalScale(this._lineThickness, this._lineLength, this._lineThickness);
    }
}

/**
 * @import { CameraComponent } from '../../framework/components/camera/component.js'
 * @import { GraphNode } from '../../scene/graph-node.js'
 * @import { Layer } from '../../scene/layer.js'
 * @import { GizmoSpace } from './constants.js'
 */ // temporary variables
const v1$1 = new Vec3();
const v2$1 = new Vec3();
const point = new Vec3();
const delta = new Vec3();
const q$1 = new Quat();
// constants
const GLANCE_EPSILON = 0.01;
/**
 * The ScaleGizmo provides interactive 3D manipulation handles for scaling/resizing
 * {@link Entity}s in a {@link Scene}. It creates a visual widget with box-tipped lines along the
 * X, Y and Z axes, planes at their intersections, and a center box, allowing precise control over
 * object scaling through direct manipulation. The gizmo's visual appearance can be customized
 * away from the defaults as required.
 *
 * Note that the gizmo can be driven by both mouse+keyboard and touch input.
 *
 * ```javascript
 * // Create a layer for rendering all gizmos
 * const gizmoLayer = pc.Gizmo.createLayer(app);
 *
 * // Create a scale gizmo
 * const gizmo = new pc.ScaleGizmo(cameraComponent, gizmoLayer);
 *
 * // Create an entity to attach the gizmo to
 * const entity = new pc.Entity();
 * entity.addComponent('render', {
 *     type: 'box'
 * });
 * app.root.addChild(entity);
 *
 * // Attach the gizmo to the entity
 * gizmo.attach([entity]);
 * ```
 *
 * Relevant Engine API examples:
 *
 * - [Scale Gizmo](https://playcanvas.github.io/#/gizmos/transform-scale)
 * - [Editor](https://playcanvas.github.io/#/misc/editor)
 *
 * @category Gizmo
 */ class ScaleGizmo extends TransformGizmo {
    /**
     * Creates a new ScaleGizmo object. Use {@link Gizmo.createLayer} to create the layer
     * required to display the gizmo.
     *
     * @param {CameraComponent} camera - The camera component.
     * @param {Layer} layer - The layer responsible for rendering the gizmo.
     * @example
     * const gizmo = new pc.ScaleGizmo(camera, layer);
     */ constructor(camera, layer){
        super(camera, layer, 'gizmo:scale'), this._shapes = {
            xyz: new BoxShape$1(this._device, {
                axis: 'xyz',
                layers: [
                    this._layer.id
                ],
                defaultColor: this._theme.shapeBase.xyz,
                hoverColor: this._theme.shapeHover.xyz,
                disabledColor: this._theme.disabled
            }),
            yz: new PlaneShape(this._device, {
                axis: 'x',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, -90),
                defaultColor: this._theme.shapeBase.x,
                hoverColor: this._theme.shapeHover.x,
                disabledColor: this._theme.disabled
            }),
            xz: new PlaneShape(this._device, {
                axis: 'y',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, 0),
                defaultColor: this._theme.shapeBase.y,
                hoverColor: this._theme.shapeHover.y,
                disabledColor: this._theme.disabled
            }),
            xy: new PlaneShape(this._device, {
                axis: 'z',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(90, 0, 0),
                defaultColor: this._theme.shapeBase.z,
                hoverColor: this._theme.shapeHover.z,
                disabledColor: this._theme.disabled
            }),
            x: new BoxLineShape(this._device, {
                axis: 'x',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, -90),
                defaultColor: this._theme.shapeBase.x,
                hoverColor: this._theme.shapeHover.x,
                disabledColor: this._theme.disabled
            }),
            y: new BoxLineShape(this._device, {
                axis: 'y',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(0, 0, 0),
                defaultColor: this._theme.shapeBase.y,
                hoverColor: this._theme.shapeHover.y,
                disabledColor: this._theme.disabled
            }),
            z: new BoxLineShape(this._device, {
                axis: 'z',
                layers: [
                    this._layer.id
                ],
                rotation: new Vec3(90, 0, 0),
                defaultColor: this._theme.shapeBase.z,
                hoverColor: this._theme.shapeHover.z,
                disabledColor: this._theme.disabled
            })
        }, /**
     * @type {GizmoSpace}
     * @protected
     */ this._coordSpace = 'local', /**
     * Internal mapping from each attached node to their starting scale.
     *
     * @type {Map<GraphNode, Vec3>}
     * @private
     */ this._nodeScales = new Map(), /**
     * Internal state if transform should use uniform scaling.
     *
     * @type {boolean}
     * @protected
     */ this._uniform = false, /**
     * @override
     */ this.snapIncrement = 1, /**
     * Whether to flip the axes to face the camera.
     *
     * @type {boolean}
     */ this.flipAxes = true, /**
     * Flips the planes to face the camera.
     *
     * @type {boolean}
     */ this.flipPlanes = true, /**
     * The lower bound for scaling.
     *
     * @type {Vec3}
     */ this.lowerBoundScale = new Vec3(-Infinity, -Infinity, -Infinity);
        this._createTransform();
        this.on(TransformGizmo.EVENT_TRANSFORMSTART, ()=>{
            // store initial scales of nodes
            this._storeNodeScales();
            // hide shapes that are not selected
            this._drag(true);
        });
        this.on(TransformGizmo.EVENT_TRANSFORMMOVE, (point)=>{
            // calculate scale delta and update node scales
            const scaleDelta = delta.copy(point).sub(this._selectionStartPoint);
            if (this.snap) {
                scaleDelta.mulScalar(1 / this.snapIncrement);
                scaleDelta.round();
                scaleDelta.mulScalar(this.snapIncrement);
            }
            scaleDelta.mulScalar(1 / this._scale);
            this._setNodeScales(scaleDelta.add(Vec3.ONE));
        });
        this.on(TransformGizmo.EVENT_TRANSFORMEND, ()=>{
            // show all shapes
            this._drag(false);
        });
        this.on(TransformGizmo.EVENT_NODESDETACH, ()=>{
            // reset stored scales
            this._nodeScales.clear();
        });
    }
    set coordSpace(value) {
    // disallow changing coordSpace for scale
    }
    get coordSpace() {
        return this._coordSpace;
    }
    /**
     * Sets the uniform scaling state for planes.
     *
     * @type {boolean}
     */ set uniform(value) {
        this._uniform = value ?? this._uniform;
    }
    /**
     * Gets the uniform scaling state for planes.
     *
     * @type {boolean}
     */ get uniform() {
        return this._uniform;
    }
    /**
     * Sets the axis gap.
     *
     * @type {number}
     */ set axisGap(value) {
        this._setArrowProp('gap', value);
    }
    /**
     * Gets the axis gap.
     *
     * @type {number}
     */ get axisGap() {
        return this._shapes.x.gap;
    }
    /**
     * Sets the axis line thickness.
     *
     * @type {number}
     */ set axisLineThickness(value) {
        this._setArrowProp('lineThickness', value);
    }
    /**
     * Gets the axis line thickness.
     *
     * @type {number}
     */ get axisLineThickness() {
        return this._shapes.x.lineThickness;
    }
    /**
     * Sets the axis line length.
     *
     * @type {number}
     */ set axisLineLength(value) {
        this._setArrowProp('lineLength', value);
    }
    /**
     * Gets the axis line length.
     *
     * @type {number}
     */ get axisLineLength() {
        return this._shapes.x.lineLength;
    }
    /**
     * Sets the axis line tolerance.
     *
     * @type {number}
     */ set axisLineTolerance(value) {
        this._setArrowProp('tolerance', value);
    }
    /**
     * Gets the axis line tolerance.
     *
     * @type {number}
     */ get axisLineTolerance() {
        return this._shapes.x.tolerance;
    }
    /**
     * Sets the axis box size.
     *
     * @type {number}
     */ set axisBoxSize(value) {
        this._setArrowProp('boxSize', value);
    }
    /**
     * Gets the axis box size.
     *
     * @type {number}
     */ get axisBoxSize() {
        return this._shapes.x.boxSize;
    }
    /**
     * Sets the plane size.
     *
     * @type {number}
     */ set axisPlaneSize(value) {
        this._setPlaneProp('size', value);
    }
    /**
     * Gets the plane size.
     *
     * @type {number}
     */ get axisPlaneSize() {
        return this._shapes.yz.size;
    }
    /**
     * Sets the plane gap.
     *
     * @type {number}
     */ set axisPlaneGap(value) {
        this._setPlaneProp('gap', value);
    }
    /**
     * Gets the plane gap.
     *
     * @type {number}
     */ get axisPlaneGap() {
        return this._shapes.yz.gap;
    }
    /**
     * Sets the axis center size.
     *
     * @type {number}
     */ set axisCenterSize(value) {
        this._shapes.xyz.size = value;
    }
    /**
     * Gets the axis center size.
     *
     * @type {number}
     */ get axisCenterSize() {
        return this._shapes.xyz.size;
    }
    /**
     * Sets the axis center tolerance.
     *
     * @type {number}
     */ set axisCenterTolerance(value) {
        this._shapes.xyz.tolerance = value;
    }
    /**
     * Gets the axis center tolerance.
     *
     * @type {number}
     */ get axisCenterTolerance() {
        return this._shapes.xyz.tolerance;
    }
    /**
     * @type {boolean}
     * @deprecated Use {@link ScaleGizmo#flipAxes} or {@link ScaleGizmo#flipPlanes} instead.
     * @ignore
     */ set flipShapes(value) {
        this.flipAxes = value;
        this.flipPlanes = value;
    }
    /**
     * @type {boolean}
     * @deprecated Use {@link ScaleGizmo#flipAxes} or {@link ScaleGizmo#flipPlanes} instead.
     * @ignore
     */ get flipShapes() {
        return this.flipAxes && this.flipPlanes;
    }
    /**
     * @param {string} prop - The property name.
     * @param {any} value - The property value.
     * @private
     */ _setArrowProp(prop, value) {
        this._shapes.x[prop] = value;
        this._shapes.y[prop] = value;
        this._shapes.z[prop] = value;
    }
    /**
     * @param {string} prop - The property name.
     * @param {any} value - The property value.
     * @private
     */ _setPlaneProp(prop, value) {
        this._shapes.yz[prop] = value;
        this._shapes.xz[prop] = value;
        this._shapes.xy[prop] = value;
    }
    /**
     * @private
     */ _shapesLookAtCamera() {
        const cameraDir = this.cameraDir;
        // axes
        let changed = false;
        let dot, enabled, flipAxis;
        dot = cameraDir.dot(this.root.right);
        enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
        if (this._shapes.x.entity.enabled !== enabled) {
            this._shapes.x.entity.enabled = enabled;
            changed = true;
        }
        if (this.flipAxes) {
            flipAxis = dot < 0;
            if (this._shapes.x.flipped !== flipAxis) {
                this._shapes.x.flipped = flipAxis;
                changed = true;
            }
        }
        dot = cameraDir.dot(this.root.up);
        enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
        if (this._shapes.y.entity.enabled !== enabled) {
            this._shapes.y.entity.enabled = enabled;
            changed = true;
        }
        if (this.flipAxes) {
            flipAxis = dot < 0;
            if (this._shapes.y.flipped !== flipAxis) {
                this._shapes.y.flipped = flipAxis;
                changed = true;
            }
        }
        dot = cameraDir.dot(this.root.forward);
        enabled = 1 - Math.abs(dot) > GLANCE_EPSILON;
        if (this._shapes.z.entity.enabled !== enabled) {
            this._shapes.z.entity.enabled = enabled;
            changed = true;
        }
        if (this.flipAxes) {
            flipAxis = dot > 0;
            if (this._shapes.z.flipped !== flipAxis) {
                this._shapes.z.flipped = flipAxis;
                changed = true;
            }
        }
        // planes
        let flipped;
        v1$1.cross(cameraDir, this.root.right);
        if (this.flipPlanes) {
            enabled = 1 - v1$1.length() > GLANCE_EPSILON;
            if (this._shapes.yz.entity.enabled !== enabled) {
                this._shapes.yz.entity.enabled = enabled;
                changed = true;
            }
        }
        flipped = v2$1.set(0, +(v1$1.dot(this.root.forward) < 0), +(v1$1.dot(this.root.up) < 0));
        if (!this._shapes.yz.flipped.equals(flipped)) {
            this._shapes.yz.flipped = flipped;
            changed = true;
        }
        v1$1.cross(cameraDir, this.root.forward);
        if (this.flipPlanes) {
            enabled = 1 - v1$1.length() > GLANCE_EPSILON;
            if (this._shapes.xy.entity.enabled !== enabled) {
                this._shapes.xy.entity.enabled = enabled;
                changed = true;
            }
        }
        flipped = v2$1.set(+(v1$1.dot(this.root.up) < 0), +(v1$1.dot(this.root.right) > 0), 0);
        if (!this._shapes.xy.flipped.equals(flipped)) {
            this._shapes.xy.flipped = flipped;
            changed = true;
        }
        v1$1.cross(cameraDir, this.root.up);
        if (this.flipPlanes) {
            enabled = 1 - v1$1.length() > GLANCE_EPSILON;
            if (this._shapes.xz.entity.enabled !== enabled) {
                this._shapes.xz.entity.enabled = enabled;
                changed = true;
            }
        }
        flipped = v2$1.set(+(v1$1.dot(this.root.forward) > 0), 0, +(v1$1.dot(this.root.right) > 0));
        if (!this._shapes.xz.flipped.equals(flipped)) {
            this._shapes.xz.flipped = flipped;
            changed = true;
        }
        if (changed) {
            this._renderUpdate = true;
        }
    }
    /**
     * @param {boolean} state - The state.
     * @private
     */ _drag(state) {
        for(const axis in this._shapes){
            const shape = this._shapes[axis];
            switch(this.dragMode){
                case 'show':
                    {
                        continue;
                    }
                case 'hide':
                    {
                        shape.visible = !state;
                        continue;
                    }
                case 'selected':
                    {
                        if (this._selectedAxis === 'xyz') {
                            shape.visible = state ? axis.length === 1 : true;
                            continue;
                        }
                        if (this._selectedIsPlane) {
                            shape.visible = state ? axis.length === 1 && !axis.includes(this._selectedAxis) : true;
                            continue;
                        }
                        shape.visible = state ? axis === this._selectedAxis : true;
                        continue;
                    }
            }
        }
        this._renderUpdate = true;
    }
    /**
     * @private
     */ _storeNodeScales() {
        for(let i = 0; i < this.nodes.length; i++){
            const node = this.nodes[i];
            this._nodeScales.set(node, node.getLocalScale().clone());
        }
    }
    /**
     * @param {Vec3} scaleDelta - The point delta.
     * @private
     */ _setNodeScales(scaleDelta) {
        for(let i = 0; i < this.nodes.length; i++){
            const node = this.nodes[i];
            const scale = this._nodeScales.get(node);
            if (!scale) {
                continue;
            }
            node.setLocalScale(v1$1.copy(scale).mul(scaleDelta).max(this.lowerBoundScale));
        }
    }
    /**
     * @param {number} x - The x coordinate.
     * @param {number} y - The y coordinate.
     * @returns {Vec3} The point (space is {@link TransformGizmo#coordSpace}).
     * @protected
     */ _screenToPoint(x, y) {
        const gizmoPos = this.root.getLocalPosition();
        const mouseWPos = this._camera.screenToWorld(x, y, 1);
        const axis = this._selectedAxis;
        const isPlane = this._selectedIsPlane;
        const ray = this._createRay(mouseWPos);
        const plane = this._createPlane(axis, axis === 'xyz', !isPlane);
        if (!plane.intersectsRay(ray, point)) {
            return point;
        }
        // uniform scaling for XYZ axis
        if (axis === 'xyz') {
            // calculate projection vector for scale direction
            const projDir = v2$1.add2(this._camera.entity.up, this._camera.entity.right).normalize();
            // calculate direction vector for scaling
            const dir = v1$1.sub2(point, gizmoPos);
            // normalize vector and project it to scale direction
            const v = dir.length() * dir.normalize().dot(projDir);
            point.set(v, v, v);
            return point;
        }
        // rotate point back to world coords
        q$1.copy(this._rootStartRot).invert().transformVector(point, point);
        // project point onto axis
        if (!isPlane) {
            this._projectToAxis(point, axis);
        }
        // mirror axes
        if (this.flipAxes) {
            const cameraDir = this.cameraDir;
            const rot = q$1.copy(this._rootStartRot);
            let dot = cameraDir.dot(rot.transformVector(Vec3.RIGHT, v1$1));
            point.x *= dot < 0 ? -1 : 1;
            dot = cameraDir.dot(rot.transformVector(Vec3.UP, v1$1));
            point.y *= dot < 0 ? -1 : 1;
            dot = cameraDir.dot(rot.transformVector(Vec3.FORWARD, v1$1));
            point.z *= dot > 0 ? -1 : 1;
        }
        // uniform scaling for planes
        if (this._uniform && isPlane) {
            // project to diagonal line
            v1$1.set(1, 1, 1);
            v1$1[axis] = 0;
            point.copy(v1$1.mulScalar(v1$1.dot(point)));
            point[axis] = 0;
        }
        return point;
    }
    /**
     * @override
     */ prerender() {
        super.prerender();
        if (!this.enabled) {
            return;
        }
        this._shapesLookAtCamera();
    }
}

class CubicSpline {
    // control times
    times;
    // control data: in-tangent, point, out-tangent
    knots;
    // dimension of the knot points
    dim;
    constructor(times, knots) {
        this.times = times;
        this.knots = knots;
        this.dim = knots.length / times.length / 3;
    }
    evaluate(time, result) {
        const { times } = this;
        const last = times.length - 1;
        if (time <= times[0]) {
            this.getKnot(0, result);
        }
        else if (time >= times[last]) {
            this.getKnot(last, result);
        }
        else {
            let seg = 0;
            while (time >= times[seg + 1]) {
                seg++;
            }
            this.evaluateSegment(seg, (time - times[seg]) / (times[seg + 1] - times[seg]), result);
        }
    }
    getKnot(index, result) {
        const { knots, dim } = this;
        const idx = index * 3 * dim;
        for (let i = 0; i < dim; ++i) {
            result[i] = knots[idx + i * 3 + 1];
        }
    }
    // evaluate the spline segment at the given normalized time t
    evaluateSegment(segment, t, result) {
        const { knots, dim } = this;
        const t2 = t * t;
        const twot = t + t;
        const omt = 1 - t;
        const omt2 = omt * omt;
        let idx = segment * dim * 3; // each knot has 3 values: tangent in, value, tangent out
        for (let i = 0; i < dim; ++i) {
            const p0 = knots[idx + 1]; // p0
            const m0 = knots[idx + 2]; // outgoing tangent
            const m1 = knots[idx + dim * 3]; // incoming tangent
            const p1 = knots[idx + dim * 3 + 1]; // p1
            idx += 3;
            result[i] =
                p0 * ((1 + twot) * omt2) +
                    m0 * (t * omt2) +
                    p1 * (t2 * (3 - twot)) +
                    m1 * (t2 * (t - 1));
        }
    }
    // calculate cubic spline knots from points
    // times: time values for each control point
    // points: control point values to be interpolated (n dimensional)
    // smoothness: 0 = linear, 1 = smooth
    static calcKnots(times, points, smoothness) {
        const n = times.length;
        const dim = points.length / n;
        const knots = new Array(n * dim * 3);
        for (let i = 0; i < n; i++) {
            const t = times[i];
            for (let j = 0; j < dim; j++) {
                const idx = i * dim + j;
                const p = points[idx];
                let tangent;
                if (i === 0) {
                    tangent = (points[idx + dim] - p) / (times[i + 1] - t);
                }
                else if (i === n - 1) {
                    tangent = (p - points[idx - dim]) / (t - times[i - 1]);
                }
                else {
                    tangent = (points[idx + dim] - points[idx - dim]) / (times[i + 1] - times[i - 1]);
                }
                // convert to derivatives w.r.t normalized segment parameter
                const inScale = i > 0 ? (times[i] - times[i - 1]) : (times[1] - times[0]);
                const outScale = i < n - 1 ? (times[i + 1] - times[i]) : (times[i] - times[i - 1]);
                knots[idx * 3] = tangent * inScale * smoothness;
                knots[idx * 3 + 1] = p;
                knots[idx * 3 + 2] = tangent * outScale * smoothness;
            }
        }
        return knots;
    }
    static fromPoints(times, points, smoothness = 1) {
        return new CubicSpline(times, CubicSpline.calcKnots(times, points, smoothness));
    }
    // create a looping spline by duplicating animation points at the end and beginning
    static fromPointsLooping(length, times, points, smoothness = 1) {
        if (times.length < 2) {
            return CubicSpline.fromPoints(times, points);
        }
        const dim = points.length / times.length;
        const newTimes = times.slice();
        const newPoints = points.slice();
        // append first two points
        newTimes.push(length + times[0], length + times[1]);
        newPoints.push(...points.slice(0, dim * 2));
        // prepend last two points
        newTimes.splice(0, 0, times[times.length - 2] - length, times[times.length - 1] - length);
        newPoints.splice(0, 0, ...points.slice(points.length - dim * 2));
        return CubicSpline.fromPoints(newTimes, newPoints, smoothness);
    }
}

const registerCameraPosesEvents = (events) => {
    const poses = [];
    let onTimelineChange;
    const rebuildSpline = () => {
        const duration = events.invoke('timeline.frames');
        const orderedPoses = poses.slice()
            // filter out keys beyond the end of the timeline
            .filter(a => a.frame < duration)
            // order keys by time for spline
            .sort((a, b) => a.frame - b.frame);
        // construct the spline points to be interpolated
        const times = orderedPoses.map(p => p.frame);
        const points = [];
        for (let i = 0; i < orderedPoses.length; ++i) {
            const p = orderedPoses[i];
            points.push(p.position.x, p.position.y, p.position.z);
            points.push(p.target.x, p.target.y, p.target.z);
        }
        if (orderedPoses.length > 1) {
            // interpolate camera positions and camera target positions
            const spline = CubicSpline.fromPointsLooping(duration, times, points, events.invoke('timeline.smoothness'));
            const result = [];
            const pose = { position: new Vec3(), target: new Vec3() };
            // handle application update tick
            onTimelineChange = (frame) => {
                const time = frame;
                // evaluate the spline at current time
                spline.evaluate(time, result);
                // set camera pose
                pose.position.set(result[0], result[1], result[2]);
                pose.target.set(result[3], result[4], result[5]);
                events.fire('camera.setPose', pose, 0);
            };
        }
        else {
            onTimelineChange = null;
        }
    };
    events.on('timeline.time', (time) => {
        onTimelineChange?.(time);
    });
    events.on('timeline.frame', (frame) => {
        onTimelineChange?.(frame);
    });
    events.on('timeline.frames', () => {
        rebuildSpline();
        events.fire('timeline.time', events.invoke('timeline.frame'));
    });
    events.on('timeline.smoothness', () => {
        rebuildSpline();
        events.fire('timeline.time', events.invoke('timeline.frame'));
    });
    // poses
    const addPose = (pose) => {
        if (pose.frame === undefined) {
            return false;
        }
        // if a pose already exists at this time, update it
        const idx = poses.findIndex(p => p.frame === pose.frame);
        if (idx !== -1) {
            poses[idx] = pose;
        }
        else {
            poses.push(pose);
            events.fire('timeline.addKey', pose.frame);
        }
        rebuildSpline();
    };
    const removePose = (index) => {
        poses.splice(index, 1);
        // remove the timeline key
        rebuildSpline();
        events.fire('timeline.removeKey', index);
    };
    const movePose = (index, frame) => {
        // remove target frame pose
        const toIndex = poses.findIndex(p => p.frame === frame);
        // move pose
        poses[index].frame = frame;
        if (toIndex !== -1) {
            removePose(toIndex);
        }
        rebuildSpline();
        events.fire('timeline.setKey', index, frame);
    };
    events.function('camera.poses', () => {
        return poses;
    });
    events.on('camera.addPose', (pose) => {
        addPose(pose);
    });
    events.on('timeline.add', (frame) => {
        // get the current camera pose
        const pose = events.invoke('camera.getPose');
        addPose({
            name: `camera_${poses.length}`,
            frame,
            position: pose.position,
            target: pose.target
        });
    });
    events.on('timeline.move', (frameFrom, frameTo) => {
        if (frameFrom === frameTo)
            return;
        const index = poses.findIndex(p => p.frame === frameFrom);
        if (index !== -1) {
            movePose(index, frameTo);
        }
    });
    events.on('timeline.remove', (index) => {
        removePose(index);
    });
    events.on('timeline.frames', () => {
        rebuildSpline();
    });
    // doc
    events.function('docSerialize.poseSets', () => {
        const pack3 = (v) => [v.x, v.y, v.z];
        if (poses.length === 0) {
            return [];
        }
        return [{
                name: 'set0',
                poses: poses.map((pose) => {
                    return {
                        name: pose.name,
                        frame: pose.frame,
                        position: pack3(pose.position),
                        target: pack3(pose.target)
                    };
                })
            }];
    });
    events.function('docDeserialize.poseSets', (poseSets) => {
        if (poseSets.length === 0) {
            return;
        }
        const fps = events.invoke('timeline.frameRate');
        // for now, load the first poseSet
        poseSets[0].poses.forEach((docPose, index) => {
            addPose({
                name: docPose.name,
                frame: docPose.frame ?? (index * fps),
                position: new Vec3(docPose.position),
                target: new Vec3(docPose.target)
            });
        });
    });
};

// write data to a file stream
class FileStreamWriter {
    write;
    close;
    constructor(stream) {
        let cursor = 0;
        stream.seek(0);
        this.write = async (data) => {
            cursor += data.byteLength;
            await stream.write(data);
        };
        this.close = async () => {
            await stream.truncate(cursor);
            await stream.close();
            return true;
        };
    }
}
// write data to a memory buffer
class BufferWriter {
    write;
    close;
    constructor() {
        const buffers = [];
        let buffer;
        let cursor = 0;
        this.write = (data) => {
            let readcursor = 0;
            while (readcursor < data.byteLength) {
                const readSize = data.byteLength - readcursor;
                // allocate buffer
                if (!buffer) {
                    buffer = new Uint8Array(Math.max(5 * 1024 * 1024, readSize));
                }
                const writeSize = buffer.byteLength - cursor;
                const copySize = Math.min(readSize, writeSize);
                buffer.set(data.subarray(readcursor, readcursor + copySize), cursor);
                readcursor += copySize;
                cursor += copySize;
                if (cursor === buffer.byteLength) {
                    buffers.push(buffer);
                    buffer = null;
                    cursor = 0;
                }
            }
        };
        this.close = () => {
            if (buffer) {
                buffers.push(new Uint8Array(buffer.buffer, 0, cursor));
                buffer = null;
                cursor = 0;
            }
            return buffers;
        };
    }
}
// write to a memory download buffer and trigger a browser download when closed
class DownloadWriter {
    write;
    close;
    constructor(filename) {
        const bufferWriter = new BufferWriter();
        this.write = (data) => {
            bufferWriter.write(data);
        };
        this.close = () => {
            const buffers = bufferWriter.close();
            // download file to client
            const blob = new Blob(buffers, { type: 'application/octet-stream' });
            const url = window.URL.createObjectURL(blob);
            const lnk = document.createElement('a');
            lnk.download = filename;
            lnk.href = url;
            // create a "fake" click-event to trigger the download
            if (document.createEvent) {
                const e = document.createEvent('MouseEvents');
                e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
                lnk.dispatchEvent(e);
            }
            else {
                // @ts-ignore
                lnk.fireEvent?.('onclick');
            }
            window.URL.revokeObjectURL(url);
            return true;
        };
    }
}
// compress the incoming stream with gzip
class GZipWriter {
    write;
    close;
    constructor(writer) {
        const stream = new CompressionStream('gzip');
        const streamWriter = stream.writable.getWriter();
        const streamReader = stream.readable.getReader();
        // hook up the reader side of the compressed stream
        const reader = (async () => {
            while (true) {
                const { done, value } = await streamReader.read();
                if (done)
                    break;
                await writer.write(value);
            }
        })();
        this.write = async (data) => {
            await streamWriter.ready;
            await streamWriter.write(data);
        };
        this.close = async () => {
            // close the writer, we're done
            await streamWriter.close();
            // wait for the reader to finish sending data
            await reader;
        };
    }
}
class ProgressWriter {
    write;
    close;
    constructor(writer, totalBytes, progress) {
        let cursor = 0;
        this.write = async (data) => {
            cursor += data.byteLength;
            await writer.write(data);
            progress?.(cursor, totalBytes);
        };
        this.close = () => {
            if (cursor !== totalBytes) {
                throw new Error(`ProgressWriter: expected ${totalBytes} bytes, but wrote ${cursor} bytes`);
            }
            progress?.(cursor, totalBytes);
            return totalBytes;
        };
    }
}

const crc32_table = (() => {
    const tbl = [];
    let c;
    for (let n = 0; n < 256; n++) {
        c = n;
        for (let k = 0; k < 8; k++) {
            c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
        }
        tbl[n] = c;
    }
    return tbl;
})();
class Crc {
    reset;
    update;
    value;
    constructor() {
        let bits = -1;
        this.update = (data) => {
            for (let i = 0; i < data.length; i++) {
                bits = (bits >>> 8) ^ crc32_table[(bits ^ data[i]) & 0xFF];
            }
        };
        this.value = () => (bits ^ (-1)) >>> 0;
    }
}

// https://gist.github.com/rvaiya/4a2192df729056880a027789ae3cd4b7
class ZipWriter {
    // start a new file
    start;
    // write func
    write;
    // finish the archive by writing the footer
    close;
    // helper function to start and write file contents
    async file(filename, content) {
        // start a new file
        await this.start(filename);
        // write file content
        if (typeof content === 'string') {
            await this.write(new TextEncoder().encode(content));
        }
        else if (content instanceof Uint8Array) {
            await this.write(content);
        }
        else {
            for (let i = 0; i < content.length; i++) {
                await this.write(content[i]);
            }
        }
    }
    // write uncompressed data to a zip file using the passed-in writer
    constructor(writer) {
        const textEncoder = new TextEncoder();
        const files = [];
        const date = new Date();
        const dosTime = (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2);
        const dosDate = ((date.getFullYear() - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
        const writeHeader = async (filename) => {
            const filenameBuf = textEncoder.encode(filename);
            const nameLen = filenameBuf.length;
            const header = new Uint8Array(30 + nameLen);
            const view = new DataView(header.buffer);
            view.setUint32(0, 0x04034b50, true);
            view.setUint16(4, 20, true); // version needed to extract = 2.0
            view.setUint16(6, 0x8 | 0x800, true); // indicate crc and size comes after, utf-8 encoding
            view.setUint16(8, 0, true); // method = 0 (store)
            view.setUint16(10, dosTime, true);
            view.setUint16(12, dosDate, true);
            view.setUint16(26, nameLen, true);
            header.set(filenameBuf, 30);
            await writer.write(header);
            files.push({ filename: filenameBuf, crc: new Crc(), sizeBytes: 0 });
        };
        const writeFooter = async () => {
            const file = files[files.length - 1];
            const { crc, sizeBytes } = file;
            const data = new Uint8Array(16);
            const view = new DataView(data.buffer);
            view.setUint32(0, 0x08074b50, true);
            view.setUint32(4, crc.value(), true);
            view.setUint32(8, sizeBytes, true);
            view.setUint32(12, sizeBytes, true);
            await writer.write(data);
        };
        this.start = async (filename) => {
            // write previous file footer
            if (files.length > 0) {
                await writeFooter();
            }
            await writeHeader(filename);
        };
        this.write = async (data) => {
            const file = files[files.length - 1];
            file.sizeBytes += data.length;
            file.crc.update(data);
            await writer.write(data);
        };
        this.close = async () => {
            // write last file's footer
            await writeFooter();
            // write cd records
            let offset = 0;
            for (const file of files) {
                const { filename, crc, sizeBytes } = file;
                const nameLen = filename.length;
                const cdr = new Uint8Array(46 + nameLen);
                const view = new DataView(cdr.buffer);
                view.setUint32(0, 0x02014b50, true);
                view.setUint16(4, 20, true);
                view.setUint16(6, 20, true);
                view.setUint16(8, 0x8 | 0x800, true);
                view.setUint16(10, 0, true);
                view.setUint16(12, dosTime, true);
                view.setUint16(14, dosDate, true);
                view.setUint32(16, crc.value(), true);
                view.setUint32(20, sizeBytes, true);
                view.setUint32(24, sizeBytes, true);
                view.setUint16(28, nameLen, true);
                view.setUint32(42, offset, true);
                cdr.set(filename, 46);
                await writer.write(cdr);
                offset += 30 + nameLen + sizeBytes + 16; // 30 local header + name + data + 16 descriptor
            }
            const filenameLength = files.reduce((tot, file) => tot + file.filename.length, 0);
            const dataLength = files.reduce((tot, file) => tot + file.sizeBytes, 0);
            // write eocd record
            const eocd = new Uint8Array(22);
            const eocdView = new DataView(eocd.buffer);
            eocdView.setUint32(0, 0x06054b50, true);
            eocdView.setUint16(8, files.length, true);
            eocdView.setUint16(10, files.length, true);
            eocdView.setUint32(12, filenameLength + files.length * 46, true);
            eocdView.setUint32(16, filenameLength + files.length * (30 + 16) + dataLength, true);
            await writer.write(eocd);
        };
    }
}

var index$2 = "* {\n    margin: 0;\n    padding: 0;\n    touch-action: none;\n}\nbody {\n    overflow: hidden;\n    font-family: 'Arial', sans-serif;\n    user-select: none;\n    -webkit-user-select: none;\n    -webkit-touch-callout: none;\n}\n.hidden {\n    display: none !important;\n}\n#ui {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    pointer-events: none;\n}\n#ui * {\n    pointer-events: auto;\n}\n#infoPanel {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.3);\n}\n#infoPanel > #infoPanelContent {\n    position: absolute;\n    top: 40px;\n    left: 50%;\n    transform: translate(-50%, 0);\n    min-height: 280px;\n    min-width: 320px;\n    padding: 8px;\n    border-radius: 24px;\n    color: black;\n    background-color: #eeed;\n    display: flex;\n    flex-direction: column;\n}\n#infoPanel > #infoPanelContent > #header {\n    font-size: 24px;\n    font-weight: bold;\n    padding-bottom: 16px;\n    color: #444;\n    display: none;\n}\n#infoPanel > #infoPanelContent > #tabs {\n    display: flex;\n    gap: 16px;\n    background-color: #666;\n    padding: 8px;\n    border-radius: 22px;\n}\n#infoPanel > #infoPanelContent > #tabs > .tab {\n    padding: 8px;\n    border-radius: 16px;\n    cursor: pointer;\n    flex-grow: 1;\n    text-align: center;\n    font-weight: bold;\n    font-size: 14px;\n    color: #222;\n    transition: background-color 250ms ease;\n}\n#infoPanel > #infoPanelContent > #tabs > .tab:hover {\n    background-color: #eee;\n}\n#infoPanel > #infoPanelContent > #tabs > .tab.active {\n    background-color: #eee;\n}\n#infoPanel > #infoPanelContent > #infoPanels {\n    padding: 16px;\n}\n#infoPanel > #infoPanelContent .control-item {\n    display: flex;\n    justify-content: space-between;\n    gap: 32px;\n    line-height: 1.5;\n}\n#infoPanel > #infoPanelContent > #infoPanels h1 {\n    font-size: 14px;\n    font-weight: bold;\n    padding: 0 0 6px 0;\n}\n#infoPanel > #infoPanelContent .control-item > .control-action {\n    text-align: left;\n    color: #333;\n}\n#infoPanel > #infoPanelContent .control-item > .control-key {\n    text-align: right;\n    color: #666;\n}\n#infoPanel > #infoPanelContent .control-spacer {\n    border-bottom: 1px dashed #666;\n    margin: 10px 0;\n}\n\n/* loadingWrap */\n\n#loadingWrap {\n    position: fixed;\n    bottom: 120px;\n    left: 50%;\n    transform: translate(-50%, 0);\n    width: 380px;\n\n    display: flex;\n    flex-direction: column;\n\n    padding: 16px;\n}\n#loadingWrap > #loadingText {\n    font-size: 18px;\n    color: white;\n    text-align: center;\n    text-shadow: 0 0 4px rgba(0, 0, 0, 0.5);\n}\n#loadingWrap > #loadingBar {\n    width: 100%;\n    height: 10px;\n    margin-top: 8px;\n    border-radius: 4px;\n    overflow: hidden;\n}\n\n/* controlsWrap */\n\n#controlsWrap {\n    position: absolute;\n    left: max(16px, env(safe-area-inset-left));\n    right: max(16px, env(safe-area-inset-right));\n    bottom: max(16px, env(safe-area-inset-bottom));\n\n    display: flex;\n    flex-direction: column;\n}\n\n#controlsWrap.faded-in {\n    visibility: visible;\n    opacity: 1;\n    transition: opacity 0.5s ease-out;\n}\n#controlsWrap.faded-out {\n    visibility: hidden;\n    opacity: 0;\n    transition: visibility 0s 0.5s, opacity 0.5s ease-out;\n}\n\n#controlsWrap > #timelineContainer {\n    height: 30px;\n    cursor: pointer;\n}\n#controlsWrap > #timelineContainer > #line {\n    width: 100%;\n    height: 50%;\n    border-bottom: 1px solid #8888;\n}\n#controlsWrap > #timelineContainer > #handle {\n    position: absolute;\n    top: 15.5px;\n    width: 10px;\n    height: 10px;\n    transform: translate(-50%, -50%);\n    border: 1px solid black;\n    border-radius: 50%;\n    background-color: #fff8;\n}\n#controlsWrap > #timelineContainer > #time {\n    position: absolute;\n    top: 0;\n    padding: 2px 4px;\n    transform: translate(-50%, -100%);\n    font-size: 12px;\n    color: white;\n    background-color: rgba(0, 0, 0, 0.5);\n    border-radius: 4px;\n}\n#controlsWrap > #buttonContainer {\n    display: flex;\n    gap: 8px;\n}\n\n/* spacer */\n\n.spacer {\n    flex-grow: 1;\n}\n\n/* settingsPanel */\n\n#settingsPanel {\n    position: fixed;\n    right: max(16px, env(safe-area-inset-right));\n    bottom: calc(max(16px, env(safe-area-inset-bottom)) + 70px);\n    padding: 10px;\n    color: #eee;\n    background-color: rgba(0, 0, 0, 0.3);\n    border-radius: 8px;\n\n    display: flex;\n    flex-direction: column;\n    align-items: flex-end;\n    gap: 4px;\n\n    font-size: 14px;\n}\n#settingsPanel > .settingsRow {\n    display: flex;\n    gap: 4px;\n    width: 100%;\n    \n}\n#settingsPanel > .settingsRow > .button {\n    padding: 10px 0px;\n    flex-grow: 1;\n}\n\n/* toggleWrap */\n\n.toggleWrap {\n    position: relative;\n    background-color: #dddd;\n    border: 1px solid #888;\n    border-radius: 8px;\n    height: 40px;\n    width: 120px;\n}\n.toggleWrap > .toggleHighlight {\n    position: absolute;\n    left: 0;\n    width: 50%;\n    height: 100%;\n    background-color: #eee;\n    border-radius: 8px;\n    transition: left 0.1s ease-out;\n}\n.toggleWrap > .toggleHighlight.right {\n    left: 60px;\n}\n.toggleWrap > button {\n    position: absolute;\n    width: 60px;\n    height: 100%;\n    border: 0;\n    border-radius: 8px;\n\n    cursor: pointer;\n    font-size: 14px;\n    letter-spacing: -0.25px;\n\n    color: #222;\n    background-color: transparent;\n\n    appearance: none;\n}\n.toggleWrap > button:hover {\n    color: black;\n}\n.toggleWrap > button.left {\n    left: 0;\n}\n.toggleWrap > button.right {\n    right: 0;\n}\n\n/* button */\n.button {\n    width: 40px;\n    height: 40px;\n    padding: 0;\n    margin: 0;\n    color: #222;\n    background-color: #dddd;\n    border: 1px solid #888;\n    border-radius: 8px;\n    cursor: pointer;\n\n    /* font-weight: bold; */\n    font-size: 14px;\n    letter-spacing: -0.25px;\n}\n.button:hover {\n    background-color: #eee;\n}\n.button > svg {\n    display: block;\n    margin: auto;\n}\n#poster {\n    display: none;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-size: cover;\n    background-position: center;\n    background-repeat: no-repeat;\n}\nbutton {\n    color: #222;\n}\nbutton:active {\n    outline: none;\n}\n#joystickBase {\n    position: absolute;\n    width: 96px;\n    height: 96px;\n    transform: translate(-50%, -50%);\n    border-radius: 50%;\n    touch-action: none;\n\n    background: radial-gradient(circle at center, #0000 50%, #000f 100%);\n    background-color: #0002;\n}\n\n#joystickBase > #joystick {\n    position: absolute;\n    width: 48px;\n    height: 48px;\n    transform: translate(-50%, -50%);\n    border-radius: 50%;\n    touch-action: none;\n\n    background-color: #fff8;\n}";

var index$1 = "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>SuperSplat Viewer</title>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n        <base href=\"\">\n        <link rel=\"stylesheet\" href=\"./index.css\">\n        <script type=\"module\">\n            const url = new URL(location.href);\n            const settingsUrl = url.searchParams.has('settings') ? url.searchParams.get('settings') : './settings.json';\n            const contentUrl = url.searchParams.has('content') ? url.searchParams.get('content') : './scene.compressed.ply';\n            const params = {};\n\n            // apply url parameter overrides\n            if (url.searchParams.has('noui')) params.noui = true;\n            if (url.searchParams.has('noanim')) params.noanim = true;\n            if (url.searchParams.has('poster')) params.posterUrl = url.searchParams.get('poster');\n            if (url.searchParams.has('skybox')) params.skyboxUrl = url.searchParams.get('skybox');\n            if (url.searchParams.has('ministats')) params.ministats = true;\n\n            const createImage = (url) => {\n                const img = new Image();\n                img.src = url;\n                return img;\n            };\n\n            window.sse = {\n                poster: params.posterUrl && createImage(params.posterUrl),\n                settings: fetch(settingsUrl).then(response => response.json()),\n                contentUrl,\n                contents: fetch(contentUrl),\n                params\n            };\n        </script>\n        <script type=\"module\" src=\"./index.js\"></script>\n    </head>\n    <body>\n        <pc-app antialias=\"false\" depth=\"false\" high-resolution=\"true\" stencil=\"false\">\n            <pc-scene>\n                <!-- Camera (with XR support) -->\n                <pc-entity name=\"camera root\">\n                    <pc-entity name=\"camera\">\n                        <pc-camera near-clip=\"0.001\" far-clip=\"5000\" horizontal-fov=\"true\" tonemap=\"none\"></pc-camera>\n                    </pc-entity>\n                    <!-- XR controllers will be added here -->\n                    <pc-scripts>\n                    </pc-scripts>\n                </pc-entity>\n                <!-- Light (for XR controllers) -->\n                <pc-entity name=\"light\" rotation=\"35 45 0\">\n                    <pc-light color=\"white\" intensity=\"1.5\"></pc-light>\n                </pc-entity>\n                <!-- Splat -->\n                <pc-entity name=\"splat\" rotation=\"0 0 180\">\n                </pc-entity>\n            </pc-scene>\n        </pc-app>\n\n        <div id=\"ui\">\n            <div id=\"poster\"></div>\n\n            <!-- Loading Indicator -->\n            <div id=\"loadingWrap\">\n                <div id=\"loadingText\"></div>\n                <div id=\"loadingBar\"></div>\n            </div>\n\n            <div id=\"controlsWrap\" class=\"faded-in\">\n\n                <!-- Timeline Panel -->\n                <div id=\"timelineContainer\" class=\"hidden\">\n                    <div id=\"line\"></div>\n                    <div id=\"handle\"></div>\n                    <div id=\"time\" class=\"hidden\">0:00</div>\n                </div>\n\n                <!-- Buttons Panel -->\n                <div id=\"buttonContainer\">\n                    <button id=\"play\" class=\"button hidden\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\" viewBox=\"4 4 16 16\" fill=\"currentColor\"><path d=\"M15 12.3301L9 16.6603L9 8L15 12.3301Z\"/></svg>\n                    </button>\n                    <button id=\"pause\" class=\"button hidden\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M5 16V4h3v12H5zm7-12h3v12h-3V4z\"/></svg>\n                    </button>\n                    <div class=\"spacer\"></div>\n                    <button id=\"arMode\" class=\"button hidden\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\"><path d=\"M440-181 240-296q-19-11-29.5-29T200-365v-230q0-22 10.5-40t29.5-29l200-115q19-11 40-11t40 11l200 115q19 11 29.5 29t10.5 40v230q0 22-10.5 40T720-296L520-181q-19 11-40 11t-40-11Zm0-92v-184l-160-93v185l160 92Zm80 0 160-92v-185l-160 93v184ZM80-680v-120q0-33 23.5-56.5T160-880h120v80H160v120H80ZM280-80H160q-33 0-56.5-23.5T80-160v-120h80v120h120v80Zm400 0v-80h120v-120h80v120q0 33-23.5 56.5T800-80H680Zm120-600v-120H680v-80h120q33 0 56.5 23.5T880-800v120h-80ZM480-526l158-93-158-91-158 91 158 93Zm0 45Zm0-45Zm40 69Zm-80 0Z\"/></svg>\n                    </button>\n                    <button id=\"vrMode\" class=\"button hidden\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\"><path d=\"M300-240q-66 0-113-47t-47-113v-163q0-51 32-89.5t82-47.5q57-11 113-15.5t113-4.5q57 0 113.5 4.5T706-700q50 10 82 48t32 89v163q0 66-47 113t-113 47h-40q-13 0-26-1.5t-25-6.5l-64-22q-12-5-25-5t-25 5l-64 22q-12 5-25 6.5t-26 1.5h-40Zm0-80h40q7 0 13.5-1t12.5-3q29-9 56.5-19t57.5-10q30 0 58 9.5t56 19.5q6 2 12.5 3t13.5 1h40q33 0 56.5-23.5T740-400v-163q0-22-14-38t-35-21q-52-11-104.5-14.5T480-640q-54 0-106 4t-105 14q-21 4-35 20.5T220-563v163q0 33 23.5 56.5T300-320ZM40-400v-160h60v160H40Zm820 0v-160h60v160h-60Zm-380-80Z\"/></svg>\n                    </button>\n                    <button id=\"info\" class=\"button\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\"><path d=\"M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n                    </button>\n                    <button id=\"orbitSettings\" class=\"button\">\n                        <!-- <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\"><path d=\"M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h480q33 0 56.5 23.5T720-720v180l160-160v440L720-420v180q0 33-23.5 56.5T640-160H160Zm0-80h480v-480H160v480Zm0 0v-480 480Z\"/></svg> -->\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"2 2 20 20\" width=\"24\" fill=\"currentColor\">\n                        <path d=\"m 7.1442743,11.663766 q -0.9582938,0 -1.8114091,-0.368125 Q 4.47975,10.927516 3.8428352,10.290601 3.2059204,9.6536865 2.8377954,8.8005713 2.4696703,7.947456 2.4696703,6.9891622 q 0,-0.9699803 0.3681251,-1.8172523 0.368125,-0.847272 1.0050398,-1.4841868 0.6369148,-0.6369148 1.49003,-1.0050398 0.8531153,-0.3681251 1.8114091,-0.3681251 0.9699803,0 1.8172523,0.3681251 0.847272,0.368125 1.4841864,1.0050398 0.636915,0.6369148 1.00504,1.4841868 0.368125,0.847272 0.368125,1.8172523 0,0.9582938 -0.368125,1.8114091 -0.368125,0.8531152 -1.00504,1.4900297 -0.6369144,0.636915 -1.4841864,1.00504 -0.847272,0.368125 -1.8172523,0.368125 z m 0,-0.958294 Q 7.4481236,10.284758 7.6701673,9.8289841 7.8922109,9.3732102 8.0324491,8.8590038 H 6.2560995 Q 6.3963377,9.3732102 6.6183814,9.8289841 6.840425,10.284758 7.1442743,10.705472 Z M 5.9288773,10.518488 Q 5.7185201,10.132833 5.5607522,9.7179623 5.4029843,9.3030912 5.2978057,8.8590038 H 3.9187975 q 0.3389088,0.5843255 0.847272,1.0167264 0.5083632,0.4324008 1.1628078,0.6427578 z m 2.430794,0 Q 9.0141159,10.308131 9.5224791,9.8757302 10.030842,9.4433293 10.369751,8.8590038 H 8.9907429 Q 8.8855643,9.3030912 8.7277964,9.7179623 8.5700285,10.132833 8.3596713,10.518488 Z M 3.5214562,7.924083 h 1.5893654 q -0.03506,-0.2337302 -0.052589,-0.4616171 -0.01753,-0.227887 -0.01753,-0.4733037 0,-0.2454167 0.01753,-0.4733037 0.01753,-0.2278869 0.052589,-0.4616171 H 3.5214562 q -0.058432,0.2337302 -0.087649,0.4616171 -0.029216,0.227887 -0.029216,0.4733037 0,0.2454167 0.029216,0.4733037 0.029216,0.2278869 0.087649,0.4616171 z m 2.5242862,0 h 2.1970638 q 0.03506,-0.2337302 0.052589,-0.4616171 0.01753,-0.227887 0.01753,-0.4733037 0,-0.2454167 -0.01753,-0.4733037 -0.01753,-0.2278869 -0.052589,-0.4616171 H 6.0457424 q -0.03506,0.2337302 -0.052589,0.4616171 -0.01753,0.227887 -0.01753,0.4733037 0,0.2454167 0.01753,0.4733037 0.01753,0.2278869 0.052589,0.4616171 z m 3.1319846,0 h 1.589365 q 0.05843,-0.2337302 0.08765,-0.4616171 0.02922,-0.227887 0.02922,-0.4733037 0,-0.2454167 -0.02922,-0.4733037 -0.02922,-0.2278869 -0.08765,-0.4616171 H 9.177727 q 0.03506,0.2337302 0.052589,0.4616171 0.01753,0.227887 0.01753,0.4733037 0,0.2454167 -0.01753,0.4733037 Q 9.212786,7.6903528 9.177727,7.924083 Z M 8.9907429,5.1193206 H 10.369751 Q 10.030842,4.5349951 9.5224791,4.1025942 9.0141159,3.6701934 8.3596713,3.4598362 8.5700285,3.845491 8.7277964,4.2603621 8.8855643,4.6752332 8.9907429,5.1193206 Z m -2.7346434,0 H 8.0324491 Q 7.8922109,4.6051142 7.6701673,4.1493403 7.4481236,3.6935664 7.1442743,3.272852 6.840425,3.6935664 6.6183814,4.1493403 6.3963377,4.6051142 6.2560995,5.1193206 Z m -2.337302,0 H 5.2978057 Q 5.4029843,4.6752332 5.5607522,4.2603621 5.7185201,3.845491 5.9288773,3.4598362 5.2744327,3.6701934 4.7660695,4.1025942 4.2577063,4.5349951 3.9187975,5.1193206 Z\"/>\n                        <path d=\"m 15.71996,21.990604 -4.663443,-4.853407 a 1.3461538,1.5192307 46.143522 0 1 0.162796,-2.023287 l 1.095483,-1.052606 -4.0563428,0.163849 6.5728988,-6.3156334 -0.32559,4.0465734 1.095484,-1.052605 a 1.3461538,1.5192307 46.143522 0 1 2.028171,-0.08193 l 4.663442,4.85341 a 1.3461538,1.5192307 46.143522 0 1 -0.162794,2.023289 L 17.748132,21.90868 A 1.3461538,1.5192307 46.143522 0 1 15.71996,21.9906 Z m 1.095484,-1.052606 4.381933,-4.210422 -4.663442,-4.853407 -4.381934,4.210422 z\"/>\n                        </svg>\n                    </button>\n                    <button id=\"flySettings\" class=\"button hidden\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"2 2 20 20\" width=\"24\" fill=\"currentColor\">\n                        <path d=\"M 9.8906998,16.233103 5.1313273,11.47373 a 1.3461538,1.5192307 45 0 1 0.122383,-2.0261336 l 1.0742595,-1.074259 -4.0522666,0.244769 6.4455506,-6.445549 -0.244768,4.052264 1.074259,-1.074259 a 1.3461538,1.5192307 45 0 1 2.0261302,-0.122383 l 4.759374,4.7593716 a 1.3461538,1.5192307 45 0 1 -0.122384,2.026135 l -4.297034,4.297033 A 1.3461538,1.5192307 45 0 1 9.8906998,16.233103 Z M 10.964957,15.158844 15.261991,10.861811 10.502618,6.1024374 6.2055848,10.399472 Z\"/>\n                        <path d=\"m 14.224293,15.714388 c 3.299831,3.005202 5.165738,4.544311 5.165738,4.544311 l -3.240906,1.355289 -3.633673,-5.8996 z\"/>\n                        <path d=\"m 15.993848,12.784194 2.558715,1.345379 1.867746,2.763273 2.180246,-2.121321 -4.83894,-2.694438 z\"/>\n                        <path d=\"m 14.340359,14.30196 3.720169,3.629993 1.885618,-0.942809 -4.014797,-2.687184 z\"/>\n                        </svg>\n                    </button>\n                    <button id=\"enterFullscreen\" class=\"button\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\"><path d=\"M120-120v-200h80v120h120v80H120Zm520 0v-80h120v-120h80v200H640ZM120-640v-200h200v80H200v120h-80Zm640 0v-120H640v-80h200v200h-80Z\"/></svg>\n                    </button>\n                    <button id=\"exitFullscreen\" class=\"button hidden\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\"><path d=\"M240-120v-120H120v-80h200v200h-80Zm400 0v-200h200v80H720v120h-80ZM120-640v-80h120v-120h80v200H120Zm520 0v-200h80v120h120v80H640Z\"/></svg>\n                    </button>\n                </div>\n            </div>\n\n            <!-- Settings Panel -->\n            <div id=\"settingsPanel\" class=\"hidden\">\n                <div>Camera Mode</div>\n                <div id=\"cameraToggle\" class=\"toggleWrap\">\n                    <div id=\"cameraToggleHighlight\" class=\"toggleHighlight\"></div>\n                    <button id=\"orbit\" class=\"left\">Orbit</button>\n                    <button id=\"fly\" class=\"right\">Fly</button>\n                </div>\n                <div>Render Quality</div>\n                <div id=\"qualityToggle\" class=\"toggleWrap\">\n                    <div id=\"qualityToggleHighlight\" class=\"toggleHighlight\"></div>\n                    <button id=\"low\" class=\"left\">Low</button>\n                    <button id=\"high\" class=\"right\">High</button>\n                </div>\n                <div>View</div>\n                <div class=\"settingsRow\">\n                    <button id=\"frame\" class=\"button\">Frame</button>\n                    <button id=\"reset\" class=\"button\">Reset</button>\n                </div>\n            </div>\n\n            <!-- Info Panel -->\n            <div id=\"infoPanel\" class=\"hidden\">\n                <div id=\"infoPanelContent\" onpointerdown=\"event.stopPropagation()\">\n                    <div id=\"header\">Controls</div>\n                    <div id=\"tabs\">\n                        <div id=\"desktopTab\" class=\"tab active\">Desktop</div>\n                        <div id=\"touchTab\" class=\"tab\">Touch</div>\n                    </div>\n                    <div id=\"infoPanels\">\n                        <div id=\"desktopInfoPanel\">\n                            <div class=\"control-spacer\"></div>\n                            <h1>Orbit Mode</h1>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Orbit</span>\n                                <span class=\"control-key\">Left Mouse</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Pan</span>\n                                <span class=\"control-key\">Right Mouse</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Zoom</span>\n                                <span class=\"control-key\">Mouse Wheel</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Set Focus</span>\n                                <span class=\"control-key\">Double Click</span>\n                            </div>\n                            <div class=\"control-spacer\"></div>\n                            <h1>Fly Mode</h1>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Look Around</span>\n                                <span class=\"control-key\">Left Mouse</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Fly</span>\n                                <span class=\"control-key\">W,S,A,D</span>\n                            </div>\n                            <div class=\"control-spacer\"></div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Frame Scene</span>\n                                <span class=\"control-key\">F</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Reset Camera</span>\n                                <span class=\"control-key\">R</span>\n                            </div>\n                        </div>\n                        <div id=\"touchInfoPanel\" class=\"hidden\">\n                            <div class=\"control-spacer\"></div>\n                            <h1>Orbit Mode</h1>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Orbit</span>\n                                <span class=\"control-key\">One Finger Drag</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Pan</span>\n                                <span class=\"control-key\">Two Finger Drag</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Zoom</span>\n                                <span class=\"control-key\">Pinch</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Set Focus</span>\n                                <span class=\"control-key\">Double Tap</span>\n                            </div>\n                            <div class=\"control-spacer\"></div>\n                            <h1>Fly Mode</h1>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Look Around</span>\n                                <span class=\"control-key\">Touch on Right</span>\n                            </div>\n                            <div class=\"control-item\">\n                                <span class=\"control-action\">Fly</span>\n                                <span class=\"control-key\">Touch on Left</span>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n\n            <!-- Touch Joystick -->\n            <div id=\"joystickBase\" class=\"hidden\">\n                <div id=\"joystick\"></div>\n            </div>\n        </div>\n        <script type=\"module\">\n            // Load the poster image if available\n            const poster = window.sse?.poster;\n            if (poster) {\n                const element = document.getElementById('poster');\n                element.style.backgroundImage = `url(${poster.src})`;\n                element.style.display = 'block';\n                element.style.filter = 'blur(40px)';\n            }\n        </script>\n    </body>\n</html>\n";

var index = "const TRACEID_GPU_TIMINGS = 'GpuTimings';\n\nconst version = '2.11.8';\nconst revision = 'd712e1a';\nfunction extend(target, ex) {\n\t\tfor(const prop in ex){\n\t\t\t\tconst copy = ex[prop];\n\t\t\t\tif (Array.isArray(copy)) {\n\t\t\t\t\t\ttarget[prop] = extend([], copy);\n\t\t\t\t} else if (copy && typeof copy === 'object') {\n\t\t\t\t\t\ttarget[prop] = extend({}, copy);\n\t\t\t\t} else {\n\t\t\t\t\t\ttarget[prop] = copy;\n\t\t\t\t}\n\t\t}\n\t\treturn target;\n}\n\nconst guid = {\n\t\tcreate () {\n\t\t\t\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c)=>{\n\t\t\t\t\t\tconst r = Math.random() * 16 | 0;\n\t\t\t\t\t\tconst v = c === 'x' ? r : r & 0x3 | 0x8;\n\t\t\t\t\t\treturn v.toString(16);\n\t\t\t\t});\n\t\t}\n};\n\nconst path = {\n\t\tdelimiter: '/',\n\t\tjoin (...sections) {\n\t\t\t\tlet result = sections[0];\n\t\t\t\tfor(let i = 0; i < sections.length - 1; i++){\n\t\t\t\t\t\tconst one = sections[i];\n\t\t\t\t\t\tconst two = sections[i + 1];\n\t\t\t\t\t\tif (two[0] === path.delimiter) {\n\t\t\t\t\t\t\t\tresult = two;\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (one && two && one[one.length - 1] !== path.delimiter && two[0] !== path.delimiter) {\n\t\t\t\t\t\t\t\tresult += path.delimiter + two;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresult += two;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t},\n\t\tnormalize (pathname) {\n\t\t\t\tconst lead = pathname.startsWith(path.delimiter);\n\t\t\t\tconst trail = pathname.endsWith(path.delimiter);\n\t\t\t\tconst parts = pathname.split('/');\n\t\t\t\tlet result = '';\n\t\t\t\tlet cleaned = [];\n\t\t\t\tfor(let i = 0; i < parts.length; i++){\n\t\t\t\t\t\tif (parts[i] === '') continue;\n\t\t\t\t\t\tif (parts[i] === '.') continue;\n\t\t\t\t\t\tif (parts[i] === '..' && cleaned.length > 0) {\n\t\t\t\t\t\t\t\tcleaned = cleaned.slice(0, cleaned.length - 2);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (i > 0) cleaned.push(path.delimiter);\n\t\t\t\t\t\tcleaned.push(parts[i]);\n\t\t\t\t}\n\t\t\t\tresult = cleaned.join('');\n\t\t\t\tif (!lead && result[0] === path.delimiter) {\n\t\t\t\t\t\tresult = result.slice(1);\n\t\t\t\t}\n\t\t\t\tif (trail && result[result.length - 1] !== path.delimiter) {\n\t\t\t\t\t\tresult += path.delimiter;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t},\n\t\tsplit (pathname) {\n\t\t\t\tconst lastDelimiterIndex = pathname.lastIndexOf(path.delimiter);\n\t\t\t\tif (lastDelimiterIndex !== -1) {\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\tpathname.substring(0, lastDelimiterIndex),\n\t\t\t\t\t\t\t\tpathname.substring(lastDelimiterIndex + 1)\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\treturn [\n\t\t\t\t\t\t'',\n\t\t\t\t\t\tpathname\n\t\t\t\t];\n\t\t},\n\t\tgetBasename (pathname) {\n\t\t\t\treturn path.split(pathname)[1];\n\t\t},\n\t\tgetDirectory (pathname) {\n\t\t\t\treturn path.split(pathname)[0];\n\t\t},\n\t\tgetExtension (pathname) {\n\t\t\t\tconst ext = pathname.split('?')[0].split('.').pop();\n\t\t\t\tif (ext !== pathname) {\n\t\t\t\t\t\treturn `.${ext}`;\n\t\t\t\t}\n\t\t\t\treturn '';\n\t\t},\n\t\tisRelativePath (pathname) {\n\t\t\t\treturn pathname.charAt(0) !== '/' && pathname.match(/:\\/\\//) === null;\n\t\t},\n\t\textractPath (pathname) {\n\t\t\t\tlet result = '';\n\t\t\t\tconst parts = pathname.split('/');\n\t\t\t\tlet i = 0;\n\t\t\t\tif (parts.length > 1) {\n\t\t\t\t\t\tif (path.isRelativePath(pathname)) {\n\t\t\t\t\t\t\t\tif (parts[0] === '.') {\n\t\t\t\t\t\t\t\t\t\tfor(i = 0; i < parts.length - 1; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tresult += i === 0 ? parts[i] : `/${parts[i]}`;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (parts[0] === '..') {\n\t\t\t\t\t\t\t\t\t\tfor(i = 0; i < parts.length - 1; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tresult += i === 0 ? parts[i] : `/${parts[i]}`;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tresult = '.';\n\t\t\t\t\t\t\t\t\t\tfor(i = 0; i < parts.length - 1; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tresult += `/${parts[i]}`;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(i = 0; i < parts.length - 1; ++i){\n\t\t\t\t\t\t\t\t\t\tresult += i === 0 ? parts[i] : `/${parts[i]}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n};\n\nconst detectPassiveEvents = ()=>{\n\t\tlet result = false;\n\t\ttry {\n\t\t\t\tconst opts = Object.defineProperty({}, 'passive', {\n\t\t\t\t\t\tget: function() {\n\t\t\t\t\t\t\t\tresult = true;\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\twindow.addEventListener('testpassive', null, opts);\n\t\t\t\twindow.removeEventListener('testpassive', null, opts);\n\t\t} catch (e) {}\n\t\treturn result;\n};\nconst ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';\nconst environment = typeof window !== 'undefined' ? 'browser' : typeof global !== 'undefined' ? 'node' : 'worker';\nconst platformName = /android/i.test(ua) ? 'android' : /ip(?:[ao]d|hone)/i.test(ua) ? 'ios' : /windows/i.test(ua) ? 'windows' : /mac os/i.test(ua) ? 'osx' : /linux/i.test(ua) ? 'linux' : /cros/i.test(ua) ? 'cros' : null;\nconst browserName = environment !== 'browser' ? null : /Chrome\\/|Chromium\\/|Edg.*\\//.test(ua) ? 'chrome' : /Safari\\//.test(ua) ? 'safari' : /Firefox\\//.test(ua) ? 'firefox' : 'other';\nconst touch = environment === 'browser' && ('ontouchstart' in window || 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0);\nconst passiveEvents = detectPassiveEvents();\nconst platform = {\n\t\tname: platformName,\n\t\tenvironment: environment,\n\t\tbrowser: environment === 'browser',\n\t\tworker: environment === 'worker',\n\t\tandroid: platformName === 'android',\n\t\ttouch: touch,\n\t\tpassiveEvents: passiveEvents,\n\t\tbrowserName: browserName\n};\n\nconst ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';\nconst ASCII_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\nconst ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE;\nconst HIGH_SURROGATE_BEGIN = 0xD800;\nconst HIGH_SURROGATE_END = 0xDBFF;\nconst LOW_SURROGATE_BEGIN = 0xDC00;\nconst LOW_SURROGATE_END = 0xDFFF;\nconst ZERO_WIDTH_JOINER = 0x200D;\nconst REGIONAL_INDICATOR_BEGIN = 0x1F1E6;\nconst REGIONAL_INDICATOR_END = 0x1F1FF;\nconst FITZPATRICK_MODIFIER_BEGIN = 0x1F3FB;\nconst FITZPATRICK_MODIFIER_END = 0x1F3FF;\nconst DIACRITICAL_MARKS_BEGIN = 0x20D0;\nconst DIACRITICAL_MARKS_END = 0x20FF;\nconst VARIATION_MODIFIER_BEGIN = 0xFE00;\nconst VARIATION_MODIFIER_END = 0xFE0F;\nfunction getCodePointData(string, i = 0) {\n\t\tconst size = string.length;\n\t\tif (i < 0 || i >= size) {\n\t\t\t\treturn null;\n\t\t}\n\t\tconst first = string.charCodeAt(i);\n\t\tif (size > 1 && first >= HIGH_SURROGATE_BEGIN && first <= HIGH_SURROGATE_END) {\n\t\t\t\tconst second = string.charCodeAt(i + 1);\n\t\t\t\tif (second >= LOW_SURROGATE_BEGIN && second <= LOW_SURROGATE_END) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcode: (first - HIGH_SURROGATE_BEGIN) * 0x400 + second - LOW_SURROGATE_BEGIN + 0x10000,\n\t\t\t\t\t\t\t\tlong: true\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\t\tcode: first,\n\t\t\t\tlong: false\n\t\t};\n}\nfunction isCodeBetween(string, begin, end) {\n\t\tif (!string) {\n\t\t\t\treturn false;\n\t\t}\n\t\tconst codeData = getCodePointData(string);\n\t\tif (codeData) {\n\t\t\t\tconst code = codeData.code;\n\t\t\t\treturn code >= begin && code <= end;\n\t\t}\n\t\treturn false;\n}\nfunction numCharsToTakeForNextSymbol(string, index) {\n\t\tif (index === string.length - 1) {\n\t\t\t\treturn 1;\n\t\t}\n\t\tif (isCodeBetween(string[index], HIGH_SURROGATE_BEGIN, HIGH_SURROGATE_END)) {\n\t\t\t\tconst first = string.substring(index, index + 2);\n\t\t\t\tconst second = string.substring(index + 2, index + 4);\n\t\t\t\tif (isCodeBetween(second, FITZPATRICK_MODIFIER_BEGIN, FITZPATRICK_MODIFIER_END) || isCodeBetween(first, REGIONAL_INDICATOR_BEGIN, REGIONAL_INDICATOR_END) && isCodeBetween(second, REGIONAL_INDICATOR_BEGIN, REGIONAL_INDICATOR_END)) {\n\t\t\t\t\t\treturn 4;\n\t\t\t\t}\n\t\t\t\tif (isCodeBetween(second, VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) {\n\t\t\t\t\t\treturn 3;\n\t\t\t\t}\n\t\t\t\treturn 2;\n\t\t}\n\t\tif (isCodeBetween(string[index + 1], VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) {\n\t\t\t\treturn 2;\n\t\t}\n\t\treturn 1;\n}\nconst string = {\n\t\tASCII_LOWERCASE: ASCII_LOWERCASE,\n\t\tASCII_UPPERCASE: ASCII_UPPERCASE,\n\t\tASCII_LETTERS: ASCII_LETTERS,\n\t\tformat (s, ...args) {\n\t\t\t\tfor(let i = 0; i < args.length; i++){\n\t\t\t\t\t\ts = s.replace(`{${i}}`, args[i]);\n\t\t\t\t}\n\t\t\t\treturn s;\n\t\t},\n\t\tgetCodePoint (string, i) {\n\t\t\t\tconst codePointData = getCodePointData(string, i);\n\t\t\t\treturn codePointData && codePointData.code;\n\t\t},\n\t\tgetCodePoints (string) {\n\t\t\t\tif (typeof string !== 'string') {\n\t\t\t\t\t\tthrow new TypeError('Not a string');\n\t\t\t\t}\n\t\t\t\tlet i = 0;\n\t\t\t\tconst arr = [];\n\t\t\t\tlet codePoint;\n\t\t\t\twhile(!!(codePoint = getCodePointData(string, i))){\n\t\t\t\t\t\tarr.push(codePoint.code);\n\t\t\t\t\t\ti += codePoint.long ? 2 : 1;\n\t\t\t\t}\n\t\t\t\treturn arr;\n\t\t},\n\t\tgetSymbols (string) {\n\t\t\t\tif (typeof string !== 'string') {\n\t\t\t\t\t\tthrow new TypeError('Not a string');\n\t\t\t\t}\n\t\t\t\tlet index = 0;\n\t\t\t\tconst length = string.length;\n\t\t\t\tconst output = [];\n\t\t\t\tlet take = 0;\n\t\t\t\tlet ch;\n\t\t\t\twhile(index < length){\n\t\t\t\t\t\ttake += numCharsToTakeForNextSymbol(string, index + take);\n\t\t\t\t\t\tch = string[index + take];\n\t\t\t\t\t\tif (isCodeBetween(ch, DIACRITICAL_MARKS_BEGIN, DIACRITICAL_MARKS_END)) {\n\t\t\t\t\t\t\t\tch = string[index + take++];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (isCodeBetween(ch, VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) {\n\t\t\t\t\t\t\t\tch = string[index + take++];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ch && ch.charCodeAt(0) === ZERO_WIDTH_JOINER) {\n\t\t\t\t\t\t\t\tch = string[index + take++];\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst char = string.substring(index, index + take);\n\t\t\t\t\t\toutput.push(char);\n\t\t\t\t\t\tindex += take;\n\t\t\t\t\t\ttake = 0;\n\t\t\t\t}\n\t\t\t\treturn output;\n\t\t},\n\t\tfromCodePoint (...args) {\n\t\t\t\treturn args.map((codePoint)=>{\n\t\t\t\t\t\tif (codePoint > 0xFFFF) {\n\t\t\t\t\t\t\t\tcodePoint -= 0x10000;\n\t\t\t\t\t\t\t\treturn String.fromCharCode((codePoint >> 10) + 0xD800, codePoint % 0x400 + 0xDC00);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn String.fromCharCode(codePoint);\n\t\t\t\t}).join('');\n\t\t}\n};\n\nclass EventHandle {\n\t\tconstructor(handler, name, callback, scope, once = false){\n\t\t\t\tthis._removed = false;\n\t\t\t\tthis.handler = handler;\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.callback = callback;\n\t\t\t\tthis.scope = scope;\n\t\t\t\tthis._once = once;\n\t\t}\n\t\toff() {\n\t\t\t\tif (this._removed) return;\n\t\t\t\tthis.handler.offByHandle(this);\n\t\t}\n\t\ton(name, callback, scope = this) {\n\t\t\t\treturn this.handler._addCallback(name, callback, scope, false);\n\t\t}\n\t\tonce(name, callback, scope = this) {\n\t\t\t\treturn this.handler._addCallback(name, callback, scope, true);\n\t\t}\n\t\tset removed(value) {\n\t\t\t\tif (!value) return;\n\t\t\t\tthis._removed = true;\n\t\t}\n\t\tget removed() {\n\t\t\t\treturn this._removed;\n\t\t}\n\t\ttoJSON(key) {\n\t\t\t\treturn undefined;\n\t\t}\n}\n\nclass EventHandler {\n\t\tinitEventHandler() {\n\t\t\t\tthis._callbacks = new Map();\n\t\t\t\tthis._callbackActive = new Map();\n\t\t}\n\t\t_addCallback(name, callback, scope, once) {\n\t\t\t\tif (!this._callbacks.has(name)) {\n\t\t\t\t\t\tthis._callbacks.set(name, []);\n\t\t\t\t}\n\t\t\t\tif (this._callbackActive.has(name)) {\n\t\t\t\t\t\tconst callbackActive = this._callbackActive.get(name);\n\t\t\t\t\t\tif (callbackActive && callbackActive === this._callbacks.get(name)) {\n\t\t\t\t\t\t\t\tthis._callbackActive.set(name, callbackActive.slice());\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst evt = new EventHandle(this, name, callback, scope, once);\n\t\t\t\tthis._callbacks.get(name).push(evt);\n\t\t\t\treturn evt;\n\t\t}\n\t\ton(name, callback, scope = this) {\n\t\t\t\treturn this._addCallback(name, callback, scope, false);\n\t\t}\n\t\tonce(name, callback, scope = this) {\n\t\t\t\treturn this._addCallback(name, callback, scope, true);\n\t\t}\n\t\toff(name, callback, scope) {\n\t\t\t\tif (name) {\n\t\t\t\t\t\tif (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) {\n\t\t\t\t\t\t\t\tthis._callbackActive.set(name, this._callbackActive.get(name).slice());\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tfor (const [key, callbacks] of this._callbackActive){\n\t\t\t\t\t\t\t\tif (!this._callbacks.has(key)) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this._callbacks.get(key) !== callbacks) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._callbackActive.set(key, callbacks.slice());\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!name) {\n\t\t\t\t\t\tfor (const callbacks of this._callbacks.values()){\n\t\t\t\t\t\t\t\tfor(let i = 0; i < callbacks.length; i++){\n\t\t\t\t\t\t\t\t\t\tcallbacks[i].removed = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._callbacks.clear();\n\t\t\t\t} else if (!callback) {\n\t\t\t\t\t\tconst callbacks = this._callbacks.get(name);\n\t\t\t\t\t\tif (callbacks) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < callbacks.length; i++){\n\t\t\t\t\t\t\t\t\t\tcallbacks[i].removed = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._callbacks.delete(name);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst callbacks = this._callbacks.get(name);\n\t\t\t\t\t\tif (!callbacks) {\n\t\t\t\t\t\t\t\treturn this;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < callbacks.length; i++){\n\t\t\t\t\t\t\t\tif (callbacks[i].callback !== callback) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (scope && callbacks[i].scope !== scope) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcallbacks[i].removed = true;\n\t\t\t\t\t\t\t\tcallbacks.splice(i, 1);\n\t\t\t\t\t\t\t\ti--;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (callbacks.length === 0) {\n\t\t\t\t\t\t\t\tthis._callbacks.delete(name);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\toffByHandle(handle) {\n\t\t\t\tconst name = handle.name;\n\t\t\t\thandle.removed = true;\n\t\t\t\tif (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) {\n\t\t\t\t\t\tthis._callbackActive.set(name, this._callbackActive.get(name).slice());\n\t\t\t\t}\n\t\t\t\tconst callbacks = this._callbacks.get(name);\n\t\t\t\tif (!callbacks) {\n\t\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tconst ind = callbacks.indexOf(handle);\n\t\t\t\tif (ind !== -1) {\n\t\t\t\t\t\tcallbacks.splice(ind, 1);\n\t\t\t\t\t\tif (callbacks.length === 0) {\n\t\t\t\t\t\t\t\tthis._callbacks.delete(name);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tfire(name, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) {\n\t\t\t\tif (!name) {\n\t\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tconst callbacksInitial = this._callbacks.get(name);\n\t\t\t\tif (!callbacksInitial) {\n\t\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tlet callbacks;\n\t\t\t\tif (!this._callbackActive.has(name)) {\n\t\t\t\t\t\tthis._callbackActive.set(name, callbacksInitial);\n\t\t\t\t} else if (this._callbackActive.get(name) !== callbacksInitial) {\n\t\t\t\t\t\tcallbacks = callbacksInitial.slice();\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; (callbacks || this._callbackActive.get(name)) && i < (callbacks || this._callbackActive.get(name)).length; i++){\n\t\t\t\t\t\tconst evt = (callbacks || this._callbackActive.get(name))[i];\n\t\t\t\t\t\tif (!evt.callback) continue;\n\t\t\t\t\t\tevt.callback.call(evt.scope, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);\n\t\t\t\t\t\tif (evt._once) {\n\t\t\t\t\t\t\t\tconst existingCallback = this._callbacks.get(name);\n\t\t\t\t\t\t\t\tconst ind = existingCallback ? existingCallback.indexOf(evt) : -1;\n\t\t\t\t\t\t\t\tif (ind !== -1) {\n\t\t\t\t\t\t\t\t\t\tif (this._callbackActive.get(name) === existingCallback) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._callbackActive.set(name, this._callbackActive.get(name).slice());\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst callbacks = this._callbacks.get(name);\n\t\t\t\t\t\t\t\t\t\tif (!callbacks) continue;\n\t\t\t\t\t\t\t\t\t\tcallbacks[ind].removed = true;\n\t\t\t\t\t\t\t\t\t\tcallbacks.splice(ind, 1);\n\t\t\t\t\t\t\t\t\t\tif (callbacks.length === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._callbacks.delete(name);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!callbacks) {\n\t\t\t\t\t\tthis._callbackActive.delete(name);\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\thasEvent(name) {\n\t\t\t\treturn !!this._callbacks.get(name)?.length;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis._callbacks = new Map();\n\t\t\t\tthis._callbackActive = new Map();\n\t\t}\n}\n\nclass IndexedList {\n\t\tpush(key, item) {\n\t\t\t\tif (this._index[key]) {\n\t\t\t\t\t\tthrow Error(`Key already in index ${key}`);\n\t\t\t\t}\n\t\t\t\tconst location = this._list.push(item) - 1;\n\t\t\t\tthis._index[key] = location;\n\t\t}\n\t\thas(key) {\n\t\t\t\treturn this._index[key] !== undefined;\n\t\t}\n\t\tget(key) {\n\t\t\t\tconst location = this._index[key];\n\t\t\t\tif (location !== undefined) {\n\t\t\t\t\t\treturn this._list[location];\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tremove(key) {\n\t\t\t\tconst location = this._index[key];\n\t\t\t\tif (location !== undefined) {\n\t\t\t\t\t\tthis._list.splice(location, 1);\n\t\t\t\t\t\tdelete this._index[key];\n\t\t\t\t\t\tfor(key in this._index){\n\t\t\t\t\t\t\t\tconst idx = this._index[key];\n\t\t\t\t\t\t\t\tif (idx > location) {\n\t\t\t\t\t\t\t\t\t\tthis._index[key] = idx - 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tlist() {\n\t\t\t\treturn this._list;\n\t\t}\n\t\tclear() {\n\t\t\t\tthis._list.length = 0;\n\t\t\t\tfor(const prop in this._index){\n\t\t\t\t\t\tdelete this._index[prop];\n\t\t\t\t}\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis._list = [];\n\t\t\t\tthis._index = {};\n\t\t}\n}\n\nconst cachedResult = (func)=>{\n\t\tconst uninitToken = {};\n\t\tlet result = uninitToken;\n\t\treturn ()=>{\n\t\t\t\tif (result === uninitToken) {\n\t\t\t\t\t\tresult = func();\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t};\n};\nclass Impl {\n\t\tstatic{\n\t\t\t\tthis.modules = {};\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.wasmSupported = cachedResult(()=>{\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tif (typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function') {\n\t\t\t\t\t\t\t\t\t\tconst module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));\n\t\t\t\t\t\t\t\t\t\tif (module instanceof WebAssembly.Module) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn new WebAssembly.Instance(module) instanceof WebAssembly.Instance;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {}\n\t\t\t\t\t\treturn false;\n\t\t\t\t});\n\t\t}\n\t\tstatic loadScript(url, callback) {\n\t\t\t\tconst s = document.createElement(\"script\");\n\t\t\t\ts.setAttribute('src', url);\n\t\t\t\ts.onload = ()=>{\n\t\t\t\t\t\tcallback(null);\n\t\t\t\t};\n\t\t\t\ts.onerror = ()=>{\n\t\t\t\t\t\tcallback(`Failed to load script='${url}'`);\n\t\t\t\t};\n\t\t\t\tdocument.body.appendChild(s);\n\t\t}\n\t\tstatic loadWasm(moduleName, config, callback) {\n\t\t\t\tconst loadUrl = Impl.wasmSupported() && config.glueUrl && config.wasmUrl ? config.glueUrl : config.fallbackUrl;\n\t\t\t\tif (loadUrl) {\n\t\t\t\t\t\tImpl.loadScript(loadUrl, (err)=>{\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tcallback(err, null);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst module = window[moduleName];\n\t\t\t\t\t\t\t\t\t\twindow[moduleName] = undefined;\n\t\t\t\t\t\t\t\t\t\tmodule({\n\t\t\t\t\t\t\t\t\t\t\t\tlocateFile: ()=>config.wasmUrl,\n\t\t\t\t\t\t\t\t\t\t\t\tonAbort: ()=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback('wasm module aborted.');\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}).then((instance)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tcallback(null, instance);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tcallback('No supported wasm modules found.', null);\n\t\t\t\t}\n\t\t}\n\t\tstatic getModule(name) {\n\t\t\t\tif (!Impl.modules.hasOwnProperty(name)) {\n\t\t\t\t\t\tImpl.modules[name] = {\n\t\t\t\t\t\t\t\tconfig: null,\n\t\t\t\t\t\t\t\tinitializing: false,\n\t\t\t\t\t\t\t\tinstance: null,\n\t\t\t\t\t\t\t\tcallbacks: []\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn Impl.modules[name];\n\t\t}\n\t\tstatic initialize(moduleName, module) {\n\t\t\t\tif (module.initializing) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst config = module.config;\n\t\t\t\tif (config.glueUrl || config.wasmUrl || config.fallbackUrl) {\n\t\t\t\t\t\tmodule.initializing = true;\n\t\t\t\t\t\tImpl.loadWasm(moduleName, config, (err, instance)=>{\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tif (config.errorHandler) {\n\t\t\t\t\t\t\t\t\t\t\t\tconfig.errorHandler(err);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tconsole.error(`failed to initialize module=${moduleName} error=${err}`);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmodule.instance = instance;\n\t\t\t\t\t\t\t\t\t\tmodule.callbacks.forEach((callback)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tcallback(instance);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n}\nclass WasmModule {\n\t\tstatic setConfig(moduleName, config) {\n\t\t\t\tconst module = Impl.getModule(moduleName);\n\t\t\t\tmodule.config = config;\n\t\t\t\tif (module.callbacks.length > 0) {\n\t\t\t\t\t\tImpl.initialize(moduleName, module);\n\t\t\t\t}\n\t\t}\n\t\tstatic getConfig(moduleName) {\n\t\t\t\treturn Impl.modules?.[moduleName]?.config;\n\t\t}\n\t\tstatic getInstance(moduleName, callback) {\n\t\t\t\tconst module = Impl.getModule(moduleName);\n\t\t\t\tif (module.instance) {\n\t\t\t\t\t\tcallback(module.instance);\n\t\t\t\t} else {\n\t\t\t\t\t\tmodule.callbacks.push(callback);\n\t\t\t\t\t\tif (module.config) {\n\t\t\t\t\t\t\t\tImpl.initialize(moduleName, module);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass ReadStream {\n\t\tconstructor(arraybuffer){\n\t\t\t\tthis.offset = 0;\n\t\t\t\tthis.arraybuffer = arraybuffer;\n\t\t\t\tthis.dataView = new DataView(arraybuffer);\n\t\t}\n\t\tget remainingBytes() {\n\t\t\t\treturn this.dataView.byteLength - this.offset;\n\t\t}\n\t\treset(offset = 0) {\n\t\t\t\tthis.offset = offset;\n\t\t}\n\t\tskip(bytes) {\n\t\t\t\tthis.offset += bytes;\n\t\t}\n\t\talign(bytes) {\n\t\t\t\tthis.offset = this.offset + bytes - 1 & ~(bytes - 1);\n\t\t}\n\t\t_inc(amount) {\n\t\t\t\tthis.offset += amount;\n\t\t\t\treturn this.offset - amount;\n\t\t}\n\t\treadChar() {\n\t\t\t\treturn String.fromCharCode(this.dataView.getUint8(this.offset++));\n\t\t}\n\t\treadChars(numChars) {\n\t\t\t\tlet result = '';\n\t\t\t\tfor(let i = 0; i < numChars; ++i){\n\t\t\t\t\t\tresult += this.readChar();\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\treadU8() {\n\t\t\t\treturn this.dataView.getUint8(this.offset++);\n\t\t}\n\t\treadU16() {\n\t\t\t\treturn this.dataView.getUint16(this._inc(2), true);\n\t\t}\n\t\treadU32() {\n\t\t\t\treturn this.dataView.getUint32(this._inc(4), true);\n\t\t}\n\t\treadU64() {\n\t\t\t\treturn this.readU32() + 2 ** 32 * this.readU32();\n\t\t}\n\t\treadU32be() {\n\t\t\t\treturn this.dataView.getUint32(this._inc(4), false);\n\t\t}\n\t\treadArray(result) {\n\t\t\t\tfor(let i = 0; i < result.length; ++i){\n\t\t\t\t\t\tresult[i] = this.readU8();\n\t\t\t\t}\n\t\t}\n\t\treadLine() {\n\t\t\t\tconst view = this.dataView;\n\t\t\t\tlet result = '';\n\t\t\t\twhile(true){\n\t\t\t\t\t\tif (this.offset >= view.byteLength) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst c = String.fromCharCode(this.readU8());\n\t\t\t\t\t\tif (c === '\\n') {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresult += c;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n}\n\nclass SortedLoopArray {\n\t\tconstructor(args){\n\t\t\t\tthis.items = [];\n\t\t\t\tthis.length = 0;\n\t\t\t\tthis.loopIndex = -1;\n\t\t\t\tthis._sortBy = args.sortBy;\n\t\t\t\tthis._sortHandler = this._doSort.bind(this);\n\t\t}\n\t\t_binarySearch(item) {\n\t\t\t\tlet left = 0;\n\t\t\t\tlet right = this.items.length - 1;\n\t\t\t\tconst search = item[this._sortBy];\n\t\t\t\tlet middle;\n\t\t\t\tlet current;\n\t\t\t\twhile(left <= right){\n\t\t\t\t\t\tmiddle = Math.floor((left + right) / 2);\n\t\t\t\t\t\tcurrent = this.items[middle][this._sortBy];\n\t\t\t\t\t\tif (current <= search) {\n\t\t\t\t\t\t\t\tleft = middle + 1;\n\t\t\t\t\t\t} else if (current > search) {\n\t\t\t\t\t\t\t\tright = middle - 1;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn left;\n\t\t}\n\t\t_doSort(a, b) {\n\t\t\t\tconst sortBy = this._sortBy;\n\t\t\t\treturn a[sortBy] - b[sortBy];\n\t\t}\n\t\tinsert(item) {\n\t\t\t\tconst index = this._binarySearch(item);\n\t\t\t\tthis.items.splice(index, 0, item);\n\t\t\t\tthis.length++;\n\t\t\t\tif (this.loopIndex >= index) {\n\t\t\t\t\t\tthis.loopIndex++;\n\t\t\t\t}\n\t\t}\n\t\tappend(item) {\n\t\t\t\tthis.items.push(item);\n\t\t\t\tthis.length++;\n\t\t}\n\t\tremove(item) {\n\t\t\t\tconst idx = this.items.indexOf(item);\n\t\t\t\tif (idx < 0) return;\n\t\t\t\tthis.items.splice(idx, 1);\n\t\t\t\tthis.length--;\n\t\t\t\tif (this.loopIndex >= idx) {\n\t\t\t\t\t\tthis.loopIndex--;\n\t\t\t\t}\n\t\t}\n\t\tsort() {\n\t\t\t\tconst current = this.loopIndex >= 0 ? this.items[this.loopIndex] : null;\n\t\t\t\tthis.items.sort(this._sortHandler);\n\t\t\t\tif (current !== null) {\n\t\t\t\t\t\tthis.loopIndex = this.items.indexOf(current);\n\t\t\t\t}\n\t\t}\n}\n\nclass Tags extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_CHANGE = 'change';\n\t\t}\n\t\tconstructor(parent){\n\t\t\t\tsuper(), this._index = {}, this._list = [];\n\t\t\t\tthis._parent = parent;\n\t\t}\n\t\tadd(...args) {\n\t\t\t\tlet changed = false;\n\t\t\t\tconst tags = this._processArguments(args, true);\n\t\t\t\tif (!tags.length) {\n\t\t\t\t\t\treturn changed;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < tags.length; i++){\n\t\t\t\t\t\tif (this._index[tags[i]]) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\tthis._index[tags[i]] = true;\n\t\t\t\t\t\tthis._list.push(tags[i]);\n\t\t\t\t\t\tthis.fire('add', tags[i], this._parent);\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\t\tthis.fire('change', this._parent);\n\t\t\t\t}\n\t\t\t\treturn changed;\n\t\t}\n\t\tremove(...args) {\n\t\t\t\tlet changed = false;\n\t\t\t\tif (!this._list.length) {\n\t\t\t\t\t\treturn changed;\n\t\t\t\t}\n\t\t\t\tconst tags = this._processArguments(args, true);\n\t\t\t\tif (!tags.length) {\n\t\t\t\t\t\treturn changed;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < tags.length; i++){\n\t\t\t\t\t\tif (!this._index[tags[i]]) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\tdelete this._index[tags[i]];\n\t\t\t\t\t\tthis._list.splice(this._list.indexOf(tags[i]), 1);\n\t\t\t\t\t\tthis.fire('remove', tags[i], this._parent);\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\t\tthis.fire('change', this._parent);\n\t\t\t\t}\n\t\t\t\treturn changed;\n\t\t}\n\t\tclear() {\n\t\t\t\tif (!this._list.length) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst tags = this._list.slice(0);\n\t\t\t\tthis._list = [];\n\t\t\t\tthis._index = {};\n\t\t\t\tfor(let i = 0; i < tags.length; i++){\n\t\t\t\t\t\tthis.fire('remove', tags[i], this._parent);\n\t\t\t\t}\n\t\t\t\tthis.fire('change', this._parent);\n\t\t}\n\t\thas(...query) {\n\t\t\t\tif (!this._list.length) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn this._has(this._processArguments(query));\n\t\t}\n\t\t_has(tags) {\n\t\t\t\tif (!this._list.length || !tags.length) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < tags.length; i++){\n\t\t\t\t\t\tif (tags[i].length === 1) {\n\t\t\t\t\t\t\t\tif (this._index[tags[i][0]]) {\n\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlet multiple = true;\n\t\t\t\t\t\t\t\tfor(let t = 0; t < tags[i].length; t++){\n\t\t\t\t\t\t\t\t\t\tif (this._index[tags[i][t]]) {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tmultiple = false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (multiple) {\n\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tlist() {\n\t\t\t\treturn this._list.slice(0);\n\t\t}\n\t\t_processArguments(args, flat) {\n\t\t\t\tconst tags = [];\n\t\t\t\tlet tmp = [];\n\t\t\t\tif (!args || !args.length) {\n\t\t\t\t\t\treturn tags;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < args.length; i++){\n\t\t\t\t\t\tif (args[i] instanceof Array) {\n\t\t\t\t\t\t\t\tif (!flat) {\n\t\t\t\t\t\t\t\t\t\ttmp = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor(let t = 0; t < args[i].length; t++){\n\t\t\t\t\t\t\t\t\t\tif (typeof args[i][t] !== 'string') {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (flat) {\n\t\t\t\t\t\t\t\t\t\t\t\ttags.push(args[i][t]);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\ttmp.push(args[i][t]);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!flat && tmp.length) {\n\t\t\t\t\t\t\t\t\t\ttags.push(tmp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (typeof args[i] === 'string') {\n\t\t\t\t\t\t\t\tif (flat) {\n\t\t\t\t\t\t\t\t\t\ttags.push(args[i]);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\ttags.push([\n\t\t\t\t\t\t\t\t\t\t\t\targs[i]\n\t\t\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn tags;\n\t\t}\n\t\tget size() {\n\t\t\t\treturn this._list.length;\n\t\t}\n}\n\nconst now = typeof window !== 'undefined' && window.performance && window.performance.now ? performance.now.bind(performance) : Date.now;\n\nconst re = /^(([^:/?#]+):)?(\\/\\/([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?/;\nclass URI {\n\t\tconstructor(uri){\n\t\t\t\tconst result = uri.match(re);\n\t\t\t\tthis.scheme = result[2];\n\t\t\t\tthis.authority = result[4];\n\t\t\t\tthis.path = result[5];\n\t\t\t\tthis.query = result[7];\n\t\t\t\tthis.fragment = result[9];\n\t\t}\n\t\ttoString() {\n\t\t\t\tlet s = '';\n\t\t\t\tif (this.scheme) {\n\t\t\t\t\t\ts += `${this.scheme}:`;\n\t\t\t\t}\n\t\t\t\tif (this.authority) {\n\t\t\t\t\t\ts += `//${this.authority}`;\n\t\t\t\t}\n\t\t\t\ts += this.path;\n\t\t\t\tif (this.query) {\n\t\t\t\t\t\ts += `?${this.query}`;\n\t\t\t\t}\n\t\t\t\tif (this.fragment) {\n\t\t\t\t\t\ts += `#${this.fragment}`;\n\t\t\t\t}\n\t\t\t\treturn s;\n\t\t}\n\t\tgetQuery() {\n\t\t\t\tconst result = {};\n\t\t\t\tif (this.query) {\n\t\t\t\t\t\tconst queryParams = decodeURIComponent(this.query).split('&');\n\t\t\t\t\t\tfor (const queryParam of queryParams){\n\t\t\t\t\t\t\t\tconst pair = queryParam.split('=');\n\t\t\t\t\t\t\t\tresult[pair[0]] = pair[1];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tsetQuery(params) {\n\t\t\t\tlet q = '';\n\t\t\t\tfor(const key in params){\n\t\t\t\t\t\tif (params.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tif (q !== '') {\n\t\t\t\t\t\t\t\t\t\tq += '&';\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tq += `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.query = q;\n\t\t}\n}\n\nclass Tracing {\n\t\tstatic{\n\t\t\t\tthis._traceChannels = new Set();\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.stack = false;\n\t\t}\n\t\tstatic set(channel, enabled = true) {}\n\t\tstatic get(channel) {\n\t\t\t\treturn Tracing._traceChannels.has(channel);\n\t\t}\n}\n\nconst CURVE_LINEAR = 0;\nconst CURVE_SMOOTHSTEP = 1;\nconst CURVE_SPLINE = 4;\nconst CURVE_STEP = 5;\n\nconst math = {\n\t\tDEG_TO_RAD: Math.PI / 180,\n\t\tRAD_TO_DEG: 180 / Math.PI,\n\t\tclamp (value, min, max) {\n\t\t\t\tif (value >= max) return max;\n\t\t\t\tif (value <= min) return min;\n\t\t\t\treturn value;\n\t\t},\n\t\tintToBytes24 (i) {\n\t\t\t\tconst r = i >> 16 & 0xff;\n\t\t\t\tconst g = i >> 8 & 0xff;\n\t\t\t\tconst b = i & 0xff;\n\t\t\t\treturn [\n\t\t\t\t\t\tr,\n\t\t\t\t\t\tg,\n\t\t\t\t\t\tb\n\t\t\t\t];\n\t\t},\n\t\tintToBytes32 (i) {\n\t\t\t\tconst r = i >> 24 & 0xff;\n\t\t\t\tconst g = i >> 16 & 0xff;\n\t\t\t\tconst b = i >> 8 & 0xff;\n\t\t\t\tconst a = i & 0xff;\n\t\t\t\treturn [\n\t\t\t\t\t\tr,\n\t\t\t\t\t\tg,\n\t\t\t\t\t\tb,\n\t\t\t\t\t\ta\n\t\t\t\t];\n\t\t},\n\t\tbytesToInt24 (r, g, b) {\n\t\t\t\tif (r.length) {\n\t\t\t\t\t\tb = r[2];\n\t\t\t\t\t\tg = r[1];\n\t\t\t\t\t\tr = r[0];\n\t\t\t\t}\n\t\t\t\treturn r << 16 | g << 8 | b;\n\t\t},\n\t\tbytesToInt32 (r, g, b, a) {\n\t\t\t\tif (r.length) {\n\t\t\t\t\t\ta = r[3];\n\t\t\t\t\t\tb = r[2];\n\t\t\t\t\t\tg = r[1];\n\t\t\t\t\t\tr = r[0];\n\t\t\t\t}\n\t\t\t\treturn (r << 24 | g << 16 | b << 8 | a) >>> 0;\n\t\t},\n\t\tlerp (a, b, alpha) {\n\t\t\t\treturn a + (b - a) * math.clamp(alpha, 0, 1);\n\t\t},\n\t\tlerpAngle (a, b, alpha) {\n\t\t\t\tif (b - a > 180) {\n\t\t\t\t\t\tb -= 360;\n\t\t\t\t}\n\t\t\t\tif (b - a < -180) {\n\t\t\t\t\t\tb += 360;\n\t\t\t\t}\n\t\t\t\treturn math.lerp(a, b, math.clamp(alpha, 0, 1));\n\t\t},\n\t\tpowerOfTwo (x) {\n\t\t\t\treturn x !== 0 && !(x & x - 1);\n\t\t},\n\t\tnextPowerOfTwo (val) {\n\t\t\t\tval--;\n\t\t\t\tval |= val >> 1;\n\t\t\t\tval |= val >> 2;\n\t\t\t\tval |= val >> 4;\n\t\t\t\tval |= val >> 8;\n\t\t\t\tval |= val >> 16;\n\t\t\t\tval++;\n\t\t\t\treturn val;\n\t\t},\n\t\tnearestPowerOfTwo (val) {\n\t\t\t\treturn Math.pow(2, Math.round(Math.log2(val)));\n\t\t},\n\t\trandom (min, max) {\n\t\t\t\tconst diff = max - min;\n\t\t\t\treturn Math.random() * diff + min;\n\t\t},\n\t\tsmoothstep (min, max, x) {\n\t\t\t\tif (x <= min) return 0;\n\t\t\t\tif (x >= max) return 1;\n\t\t\t\tx = (x - min) / (max - min);\n\t\t\t\treturn x * x * (3 - 2 * x);\n\t\t},\n\t\tsmootherstep (min, max, x) {\n\t\t\t\tif (x <= min) return 0;\n\t\t\t\tif (x >= max) return 1;\n\t\t\t\tx = (x - min) / (max - min);\n\t\t\t\treturn x * x * x * (x * (x * 6 - 15) + 10);\n\t\t},\n\t\troundUp (numToRound, multiple) {\n\t\t\t\tif (multiple === 0) {\n\t\t\t\t\t\treturn numToRound;\n\t\t\t\t}\n\t\t\t\treturn Math.ceil(numToRound / multiple) * multiple;\n\t\t},\n\t\tbetween (num, a, b, inclusive) {\n\t\t\t\tconst min = Math.min(a, b);\n\t\t\t\tconst max = Math.max(a, b);\n\t\t\t\treturn inclusive ? num >= min && num <= max : num > min && num < max;\n\t\t}\n};\n\nclass Color {\n\t\tconstructor(r = 0, g = 0, b = 0, a = 1){\n\t\t\t\tconst length = r.length;\n\t\t\t\tif (length === 3 || length === 4) {\n\t\t\t\t\t\tthis.r = r[0];\n\t\t\t\t\t\tthis.g = r[1];\n\t\t\t\t\t\tthis.b = r[2];\n\t\t\t\t\t\tthis.a = r[3] ?? 1;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.r = r;\n\t\t\t\t\t\tthis.g = g;\n\t\t\t\t\t\tthis.b = b;\n\t\t\t\t\t\tthis.a = a;\n\t\t\t\t}\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr(this.r, this.g, this.b, this.a);\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis.r = rhs.r;\n\t\t\t\tthis.g = rhs.g;\n\t\t\t\tthis.b = rhs.b;\n\t\t\t\tthis.a = rhs.a;\n\t\t\t\treturn this;\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\treturn this.r === rhs.r && this.g === rhs.g && this.b === rhs.b && this.a === rhs.a;\n\t\t}\n\t\tset(r, g, b, a = 1) {\n\t\t\t\tthis.r = r;\n\t\t\t\tthis.g = g;\n\t\t\t\tthis.b = b;\n\t\t\t\tthis.a = a;\n\t\t\t\treturn this;\n\t\t}\n\t\tlerp(lhs, rhs, alpha) {\n\t\t\t\tthis.r = lhs.r + alpha * (rhs.r - lhs.r);\n\t\t\t\tthis.g = lhs.g + alpha * (rhs.g - lhs.g);\n\t\t\t\tthis.b = lhs.b + alpha * (rhs.b - lhs.b);\n\t\t\t\tthis.a = lhs.a + alpha * (rhs.a - lhs.a);\n\t\t\t\treturn this;\n\t\t}\n\t\tlinear(src = this) {\n\t\t\t\tthis.r = Math.pow(src.r, 2.2);\n\t\t\t\tthis.g = Math.pow(src.g, 2.2);\n\t\t\t\tthis.b = Math.pow(src.b, 2.2);\n\t\t\t\tthis.a = src.a;\n\t\t\t\treturn this;\n\t\t}\n\t\tgamma(src = this) {\n\t\t\t\tthis.r = Math.pow(src.r, 1 / 2.2);\n\t\t\t\tthis.g = Math.pow(src.g, 1 / 2.2);\n\t\t\t\tthis.b = Math.pow(src.b, 1 / 2.2);\n\t\t\t\tthis.a = src.a;\n\t\t\t\treturn this;\n\t\t}\n\t\tmulScalar(scalar) {\n\t\t\t\tthis.r *= scalar;\n\t\t\t\tthis.g *= scalar;\n\t\t\t\tthis.b *= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tfromString(hex) {\n\t\t\t\tconst i = parseInt(hex.replace('#', '0x'), 16);\n\t\t\t\tlet bytes;\n\t\t\t\tif (hex.length > 7) {\n\t\t\t\t\t\tbytes = math.intToBytes32(i);\n\t\t\t\t} else {\n\t\t\t\t\t\tbytes = math.intToBytes24(i);\n\t\t\t\t\t\tbytes[3] = 255;\n\t\t\t\t}\n\t\t\t\tthis.set(bytes[0] / 255, bytes[1] / 255, bytes[2] / 255, bytes[3] / 255);\n\t\t\t\treturn this;\n\t\t}\n\t\tfromArray(arr, offset = 0) {\n\t\t\t\tthis.r = arr[offset] ?? this.r;\n\t\t\t\tthis.g = arr[offset + 1] ?? this.g;\n\t\t\t\tthis.b = arr[offset + 2] ?? this.b;\n\t\t\t\tthis.a = arr[offset + 3] ?? this.a;\n\t\t\t\treturn this;\n\t\t}\n\t\ttoString(alpha, asArray) {\n\t\t\t\tconst { r, g, b, a } = this;\n\t\t\t\tif (asArray || r > 1 || g > 1 || b > 1) {\n\t\t\t\t\t\treturn `${r.toFixed(3)}, ${g.toFixed(3)}, ${b.toFixed(3)}, ${a.toFixed(3)}`;\n\t\t\t\t}\n\t\t\t\tlet s = `#${((1 << 24) + (Math.round(r * 255) << 16) + (Math.round(g * 255) << 8) + Math.round(b * 255)).toString(16).slice(1)}`;\n\t\t\t\tif (alpha === true) {\n\t\t\t\t\t\tconst aa = Math.round(a * 255).toString(16);\n\t\t\t\t\t\tif (this.a < 16 / 255) {\n\t\t\t\t\t\t\t\ts += `0${aa}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ts += aa;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn s;\n\t\t}\n\t\ttoArray(arr = [], offset = 0, alpha = true) {\n\t\t\t\tarr[offset] = this.r;\n\t\t\t\tarr[offset + 1] = this.g;\n\t\t\t\tarr[offset + 2] = this.b;\n\t\t\t\tif (alpha) {\n\t\t\t\t\t\tarr[offset + 3] = this.a;\n\t\t\t\t}\n\t\t\t\treturn arr;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.BLACK = Object.freeze(new Color(0, 0, 0, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.BLUE = Object.freeze(new Color(0, 0, 1, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.CYAN = Object.freeze(new Color(0, 1, 1, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.GRAY = Object.freeze(new Color(0.5, 0.5, 0.5, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.GREEN = Object.freeze(new Color(0, 1, 0, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.MAGENTA = Object.freeze(new Color(1, 0, 1, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.RED = Object.freeze(new Color(1, 0, 0, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.WHITE = Object.freeze(new Color(1, 1, 1, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.YELLOW = Object.freeze(new Color(1, 1, 0, 1));\n\t\t}\n}\n\nclass CurveEvaluator {\n\t\tconstructor(curve, time = 0){\n\t\t\t\tthis._left = -Infinity;\n\t\t\t\tthis._right = Infinity;\n\t\t\t\tthis._recip = 0;\n\t\t\t\tthis._p0 = 0;\n\t\t\t\tthis._p1 = 0;\n\t\t\t\tthis._m0 = 0;\n\t\t\t\tthis._m1 = 0;\n\t\t\t\tthis._curve = curve;\n\t\t\t\tthis._reset(time);\n\t\t}\n\t\tevaluate(time, forceReset = false) {\n\t\t\t\tif (forceReset || time < this._left || time >= this._right) {\n\t\t\t\t\t\tthis._reset(time);\n\t\t\t\t}\n\t\t\t\tlet result;\n\t\t\t\tconst type = this._curve.type;\n\t\t\t\tif (type === CURVE_STEP) {\n\t\t\t\t\t\tresult = this._p0;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst t = this._recip === 0 ? 0 : (time - this._left) * this._recip;\n\t\t\t\t\t\tif (type === CURVE_LINEAR) {\n\t\t\t\t\t\t\t\tresult = math.lerp(this._p0, this._p1, t);\n\t\t\t\t\t\t} else if (type === CURVE_SMOOTHSTEP) {\n\t\t\t\t\t\t\t\tresult = math.lerp(this._p0, this._p1, t * t * (3 - 2 * t));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresult = this._evaluateHermite(this._p0, this._p1, this._m0, this._m1, t);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\t_reset(time) {\n\t\t\t\tconst keys = this._curve.keys;\n\t\t\t\tconst len = keys.length;\n\t\t\t\tif (!len) {\n\t\t\t\t\t\tthis._left = -Infinity;\n\t\t\t\t\t\tthis._right = Infinity;\n\t\t\t\t\t\tthis._recip = 0;\n\t\t\t\t\t\tthis._p0 = this._p1 = this._m0 = this._m1 = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\tif (time < keys[0][0]) {\n\t\t\t\t\t\t\t\tthis._left = -Infinity;\n\t\t\t\t\t\t\t\tthis._right = keys[0][0];\n\t\t\t\t\t\t\t\tthis._recip = 0;\n\t\t\t\t\t\t\t\tthis._p0 = this._p1 = keys[0][1];\n\t\t\t\t\t\t\t\tthis._m0 = this._m1 = 0;\n\t\t\t\t\t\t} else if (time >= keys[len - 1][0]) {\n\t\t\t\t\t\t\t\tthis._left = keys[len - 1][0];\n\t\t\t\t\t\t\t\tthis._right = Infinity;\n\t\t\t\t\t\t\t\tthis._recip = 0;\n\t\t\t\t\t\t\t\tthis._p0 = this._p1 = keys[len - 1][1];\n\t\t\t\t\t\t\t\tthis._m0 = this._m1 = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlet index = 0;\n\t\t\t\t\t\t\t\twhile(time >= keys[index + 1][0]){\n\t\t\t\t\t\t\t\t\t\tindex++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._left = keys[index][0];\n\t\t\t\t\t\t\t\tthis._right = keys[index + 1][0];\n\t\t\t\t\t\t\t\tconst diff = 1.0 / (this._right - this._left);\n\t\t\t\t\t\t\t\tthis._recip = isFinite(diff) ? diff : 0;\n\t\t\t\t\t\t\t\tthis._p0 = keys[index][1];\n\t\t\t\t\t\t\t\tthis._p1 = keys[index + 1][1];\n\t\t\t\t\t\t\t\tif (this._curve.type === CURVE_SPLINE) {\n\t\t\t\t\t\t\t\t\t\tthis._calcTangents(keys, index);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_calcTangents(keys, index) {\n\t\t\t\tlet a;\n\t\t\t\tconst b = keys[index];\n\t\t\t\tconst c = keys[index + 1];\n\t\t\t\tlet d;\n\t\t\t\tif (index === 0) {\n\t\t\t\t\t\ta = [\n\t\t\t\t\t\t\t\tkeys[0][0] + (keys[0][0] - keys[1][0]),\n\t\t\t\t\t\t\t\tkeys[0][1] + (keys[0][1] - keys[1][1])\n\t\t\t\t\t\t];\n\t\t\t\t} else {\n\t\t\t\t\t\ta = keys[index - 1];\n\t\t\t\t}\n\t\t\t\tif (index === keys.length - 2) {\n\t\t\t\t\t\td = [\n\t\t\t\t\t\t\t\tkeys[index + 1][0] + (keys[index + 1][0] - keys[index][0]),\n\t\t\t\t\t\t\t\tkeys[index + 1][1] + (keys[index + 1][1] - keys[index][1])\n\t\t\t\t\t\t];\n\t\t\t\t} else {\n\t\t\t\t\t\td = keys[index + 2];\n\t\t\t\t}\n\t\t\t\tif (this._curve.type === CURVE_SPLINE) {\n\t\t\t\t\t\tconst s1_ = 2 * (c[0] - b[0]) / (c[0] - a[0]);\n\t\t\t\t\t\tconst s2_ = 2 * (c[0] - b[0]) / (d[0] - b[0]);\n\t\t\t\t\t\tthis._m0 = this._curve.tension * (isFinite(s1_) ? s1_ : 0) * (c[1] - a[1]);\n\t\t\t\t\t\tthis._m1 = this._curve.tension * (isFinite(s2_) ? s2_ : 0) * (d[1] - b[1]);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst s1 = (c[0] - b[0]) / (b[0] - a[0]);\n\t\t\t\t\t\tconst s2 = (c[0] - b[0]) / (d[0] - c[0]);\n\t\t\t\t\t\tconst a_ = b[1] + (a[1] - b[1]) * (isFinite(s1) ? s1 : 0);\n\t\t\t\t\t\tconst d_ = c[1] + (d[1] - c[1]) * (isFinite(s2) ? s2 : 0);\n\t\t\t\t\t\tconst tension = this._curve.tension;\n\t\t\t\t\t\tthis._m0 = tension * (c[1] - a_);\n\t\t\t\t\t\tthis._m1 = tension * (d_ - b[1]);\n\t\t\t\t}\n\t\t}\n\t\t_evaluateHermite(p0, p1, m0, m1, t) {\n\t\t\t\tconst t2 = t * t;\n\t\t\t\tconst twot = t + t;\n\t\t\t\tconst omt = 1 - t;\n\t\t\t\tconst omt2 = omt * omt;\n\t\t\t\treturn p0 * ((1 + twot) * omt2) + m0 * (t * omt2) + p1 * (t2 * (3 - twot)) + m1 * (t2 * (t - 1));\n\t\t}\n}\n\nclass Curve {\n\t\tconstructor(data){\n\t\t\t\tthis.keys = [];\n\t\t\t\tthis.type = CURVE_SMOOTHSTEP;\n\t\t\t\tthis.tension = 0.5;\n\t\t\t\tthis._eval = new CurveEvaluator(this);\n\t\t\t\tif (data) {\n\t\t\t\t\t\tfor(let i = 0; i < data.length - 1; i += 2){\n\t\t\t\t\t\t\t\tthis.keys.push([\n\t\t\t\t\t\t\t\t\t\tdata[i],\n\t\t\t\t\t\t\t\t\t\tdata[i + 1]\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.sort();\n\t\t}\n\t\tget length() {\n\t\t\t\treturn this.keys.length;\n\t\t}\n\t\tadd(time, value) {\n\t\t\t\tconst keys = this.keys;\n\t\t\t\tconst len = keys.length;\n\t\t\t\tlet i = 0;\n\t\t\t\tfor(; i < len; i++){\n\t\t\t\t\t\tif (keys[i][0] > time) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst key = [\n\t\t\t\t\t\ttime,\n\t\t\t\t\t\tvalue\n\t\t\t\t];\n\t\t\t\tthis.keys.splice(i, 0, key);\n\t\t\t\treturn key;\n\t\t}\n\t\tget(index) {\n\t\t\t\treturn this.keys[index];\n\t\t}\n\t\tsort() {\n\t\t\t\tthis.keys.sort((a, b)=>a[0] - b[0]);\n\t\t}\n\t\tvalue(time) {\n\t\t\t\treturn this._eval.evaluate(time, true);\n\t\t}\n\t\tclosest(time) {\n\t\t\t\tconst keys = this.keys;\n\t\t\t\tconst length = keys.length;\n\t\t\t\tlet min = 2;\n\t\t\t\tlet result = null;\n\t\t\t\tfor(let i = 0; i < length; i++){\n\t\t\t\t\t\tconst diff = Math.abs(time - keys[i][0]);\n\t\t\t\t\t\tif (min >= diff) {\n\t\t\t\t\t\t\t\tmin = diff;\n\t\t\t\t\t\t\t\tresult = keys[i];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst result = new this.constructor();\n\t\t\t\tresult.keys = this.keys.map((key)=>[\n\t\t\t\t\t\t\t\t...key\n\t\t\t\t\t\t]);\n\t\t\t\tresult.type = this.type;\n\t\t\t\tresult.tension = this.tension;\n\t\t\t\treturn result;\n\t\t}\n\t\tquantize(precision) {\n\t\t\t\tprecision = Math.max(precision, 2);\n\t\t\t\tconst values = new Float32Array(precision);\n\t\t\t\tconst step = 1.0 / (precision - 1);\n\t\t\t\tvalues[0] = this._eval.evaluate(0, true);\n\t\t\t\tfor(let i = 1; i < precision; i++){\n\t\t\t\t\t\tvalues[i] = this._eval.evaluate(step * i);\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t}\n\t\tquantizeClamped(precision, min, max) {\n\t\t\t\tconst result = this.quantize(precision);\n\t\t\t\tfor(let i = 0; i < result.length; ++i){\n\t\t\t\t\t\tresult[i] = Math.min(max, Math.max(min, result[i]));\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n}\n\nclass CurveSet {\n\t\tconstructor(...args){\n\t\t\t\tthis.curves = [];\n\t\t\t\tthis._type = CURVE_SMOOTHSTEP;\n\t\t\t\tif (args.length > 1) {\n\t\t\t\t\t\tfor(let i = 0; i < args.length; i++){\n\t\t\t\t\t\t\t\tthis.curves.push(new Curve(args[i]));\n\t\t\t\t\t\t}\n\t\t\t\t} else if (args.length === 0) {\n\t\t\t\t\t\tthis.curves.push(new Curve());\n\t\t\t\t} else {\n\t\t\t\t\t\tconst arg = args[0];\n\t\t\t\t\t\tif (typeof arg === 'number') {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < arg; i++){\n\t\t\t\t\t\t\t\t\t\tthis.curves.push(new Curve());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < arg.length; i++){\n\t\t\t\t\t\t\t\t\t\tthis.curves.push(new Curve(arg[i]));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget length() {\n\t\t\t\treturn this.curves.length;\n\t\t}\n\t\tset type(value) {\n\t\t\t\tthis._type = value;\n\t\t\t\tfor(let i = 0; i < this.curves.length; i++){\n\t\t\t\t\t\tthis.curves[i].type = value;\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tget(index) {\n\t\t\t\treturn this.curves[index];\n\t\t}\n\t\tvalue(time, result = []) {\n\t\t\t\tconst length = this.curves.length;\n\t\t\t\tresult.length = length;\n\t\t\t\tfor(let i = 0; i < length; i++){\n\t\t\t\t\t\tresult[i] = this.curves[i].value(time);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst result = new this.constructor();\n\t\t\t\tresult.curves = [];\n\t\t\t\tfor(let i = 0; i < this.curves.length; i++){\n\t\t\t\t\t\tresult.curves.push(this.curves[i].clone());\n\t\t\t\t}\n\t\t\t\tresult._type = this._type;\n\t\t\t\treturn result;\n\t\t}\n\t\tquantize(precision) {\n\t\t\t\tprecision = Math.max(precision, 2);\n\t\t\t\tconst numCurves = this.curves.length;\n\t\t\t\tconst values = new Float32Array(precision * numCurves);\n\t\t\t\tconst step = 1.0 / (precision - 1);\n\t\t\t\tfor(let c = 0; c < numCurves; c++){\n\t\t\t\t\t\tconst ev = new CurveEvaluator(this.curves[c]);\n\t\t\t\t\t\tfor(let i = 0; i < precision; i++){\n\t\t\t\t\t\t\t\tvalues[i * numCurves + c] = ev.evaluate(step * i);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t}\n\t\tquantizeClamped(precision, min, max) {\n\t\t\t\tconst result = this.quantize(precision);\n\t\t\t\tfor(let i = 0; i < result.length; ++i){\n\t\t\t\t\t\tresult[i] = Math.min(max, Math.max(min, result[i]));\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n}\n\nconst floatView = new Float32Array(1);\nconst int32View = new Int32Array(floatView.buffer);\nclass FloatPacking {\n\t\tstatic float2Half(value) {\n\t\t\t\tfloatView[0] = value;\n\t\t\t\tconst x = int32View[0];\n\t\t\t\tlet bits = x >> 16 & 0x8000;\n\t\t\t\tlet m = x >> 12 & 0x07ff;\n\t\t\t\tconst e = x >> 23 & 0xff;\n\t\t\t\tif (e < 103) {\n\t\t\t\t\t\treturn bits;\n\t\t\t\t}\n\t\t\t\tif (e > 142) {\n\t\t\t\t\t\tbits |= 0x7c00;\n\t\t\t\t\t\tbits |= (e === 255 ? 0 : 1) && x & 0x007fffff;\n\t\t\t\t\t\treturn bits;\n\t\t\t\t}\n\t\t\t\tif (e < 113) {\n\t\t\t\t\t\tm |= 0x0800;\n\t\t\t\t\t\tbits |= (m >> 114 - e) + (m >> 113 - e & 1);\n\t\t\t\t\t\treturn bits;\n\t\t\t\t}\n\t\t\t\tbits |= e - 112 << 10 | m >> 1;\n\t\t\t\tbits += m & 1;\n\t\t\t\treturn bits;\n\t\t}\n\t\tstatic float2RGBA8(value, data) {\n\t\t\t\tfloatView[0] = value;\n\t\t\t\tconst intBits = int32View[0];\n\t\t\t\tdata.r = (intBits >> 24 & 0xFF) / 255.0;\n\t\t\t\tdata.g = (intBits >> 16 & 0xFF) / 255.0;\n\t\t\t\tdata.b = (intBits >> 8 & 0xFF) / 255.0;\n\t\t\t\tdata.a = (intBits & 0xFF) / 255.0;\n\t\t}\n}\n\nclass Vec3 {\n\t\tconstructor(x = 0, y = 0, z = 0){\n\t\t\t\tif (x.length === 3) {\n\t\t\t\t\t\tthis.x = x[0];\n\t\t\t\t\t\tthis.y = x[1];\n\t\t\t\t\t\tthis.z = x[2];\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.x = x;\n\t\t\t\t\t\tthis.y = y;\n\t\t\t\t\t\tthis.z = z;\n\t\t\t\t}\n\t\t}\n\t\tadd(rhs) {\n\t\t\t\tthis.x += rhs.x;\n\t\t\t\tthis.y += rhs.y;\n\t\t\t\tthis.z += rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tadd2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x + rhs.x;\n\t\t\t\tthis.y = lhs.y + rhs.y;\n\t\t\t\tthis.z = lhs.z + rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\taddScalar(scalar) {\n\t\t\t\tthis.x += scalar;\n\t\t\t\tthis.y += scalar;\n\t\t\t\tthis.z += scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\taddScaled(rhs, scalar) {\n\t\t\t\tthis.x += rhs.x * scalar;\n\t\t\t\tthis.y += rhs.y * scalar;\n\t\t\t\tthis.z += rhs.z * scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr(this.x, this.y, this.z);\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis.x = rhs.x;\n\t\t\t\tthis.y = rhs.y;\n\t\t\t\tthis.z = rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tcross(lhs, rhs) {\n\t\t\t\tconst lx = lhs.x;\n\t\t\t\tconst ly = lhs.y;\n\t\t\t\tconst lz = lhs.z;\n\t\t\t\tconst rx = rhs.x;\n\t\t\t\tconst ry = rhs.y;\n\t\t\t\tconst rz = rhs.z;\n\t\t\t\tthis.x = ly * rz - ry * lz;\n\t\t\t\tthis.y = lz * rx - rz * lx;\n\t\t\t\tthis.z = lx * ry - rx * ly;\n\t\t\t\treturn this;\n\t\t}\n\t\tdistance(rhs) {\n\t\t\t\tconst x = this.x - rhs.x;\n\t\t\t\tconst y = this.y - rhs.y;\n\t\t\t\tconst z = this.z - rhs.z;\n\t\t\t\treturn Math.sqrt(x * x + y * y + z * z);\n\t\t}\n\t\tdiv(rhs) {\n\t\t\t\tthis.x /= rhs.x;\n\t\t\t\tthis.y /= rhs.y;\n\t\t\t\tthis.z /= rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tdiv2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x / rhs.x;\n\t\t\t\tthis.y = lhs.y / rhs.y;\n\t\t\t\tthis.z = lhs.z / rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tdivScalar(scalar) {\n\t\t\t\tthis.x /= scalar;\n\t\t\t\tthis.y /= scalar;\n\t\t\t\tthis.z /= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tdot(rhs) {\n\t\t\t\treturn this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\treturn this.x === rhs.x && this.y === rhs.y && this.z === rhs.z;\n\t\t}\n\t\tequalsApprox(rhs, epsilon = 1e-6) {\n\t\t\t\treturn Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon;\n\t\t}\n\t\tlength() {\n\t\t\t\treturn Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);\n\t\t}\n\t\tlengthSq() {\n\t\t\t\treturn this.x * this.x + this.y * this.y + this.z * this.z;\n\t\t}\n\t\tlerp(lhs, rhs, alpha) {\n\t\t\t\tthis.x = lhs.x + alpha * (rhs.x - lhs.x);\n\t\t\t\tthis.y = lhs.y + alpha * (rhs.y - lhs.y);\n\t\t\t\tthis.z = lhs.z + alpha * (rhs.z - lhs.z);\n\t\t\t\treturn this;\n\t\t}\n\t\tmul(rhs) {\n\t\t\t\tthis.x *= rhs.x;\n\t\t\t\tthis.y *= rhs.y;\n\t\t\t\tthis.z *= rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tmul2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x * rhs.x;\n\t\t\t\tthis.y = lhs.y * rhs.y;\n\t\t\t\tthis.z = lhs.z * rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tmulScalar(scalar) {\n\t\t\t\tthis.x *= scalar;\n\t\t\t\tthis.y *= scalar;\n\t\t\t\tthis.z *= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tnormalize(src = this) {\n\t\t\t\tconst lengthSq = src.x * src.x + src.y * src.y + src.z * src.z;\n\t\t\t\tif (lengthSq > 0) {\n\t\t\t\t\t\tconst invLength = 1 / Math.sqrt(lengthSq);\n\t\t\t\t\t\tthis.x = src.x * invLength;\n\t\t\t\t\t\tthis.y = src.y * invLength;\n\t\t\t\t\t\tthis.z = src.z * invLength;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tfloor(src = this) {\n\t\t\t\tthis.x = Math.floor(src.x);\n\t\t\t\tthis.y = Math.floor(src.y);\n\t\t\t\tthis.z = Math.floor(src.z);\n\t\t\t\treturn this;\n\t\t}\n\t\tceil(src = this) {\n\t\t\t\tthis.x = Math.ceil(src.x);\n\t\t\t\tthis.y = Math.ceil(src.y);\n\t\t\t\tthis.z = Math.ceil(src.z);\n\t\t\t\treturn this;\n\t\t}\n\t\tround(src = this) {\n\t\t\t\tthis.x = Math.round(src.x);\n\t\t\t\tthis.y = Math.round(src.y);\n\t\t\t\tthis.z = Math.round(src.z);\n\t\t\t\treturn this;\n\t\t}\n\t\tmin(rhs) {\n\t\t\t\tif (rhs.x < this.x) this.x = rhs.x;\n\t\t\t\tif (rhs.y < this.y) this.y = rhs.y;\n\t\t\t\tif (rhs.z < this.z) this.z = rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tmax(rhs) {\n\t\t\t\tif (rhs.x > this.x) this.x = rhs.x;\n\t\t\t\tif (rhs.y > this.y) this.y = rhs.y;\n\t\t\t\tif (rhs.z > this.z) this.z = rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tproject(rhs) {\n\t\t\t\tconst a_dot_b = this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;\n\t\t\t\tconst b_dot_b = rhs.x * rhs.x + rhs.y * rhs.y + rhs.z * rhs.z;\n\t\t\t\tconst s = a_dot_b / b_dot_b;\n\t\t\t\tthis.x = rhs.x * s;\n\t\t\t\tthis.y = rhs.y * s;\n\t\t\t\tthis.z = rhs.z * s;\n\t\t\t\treturn this;\n\t\t}\n\t\tset(x, y, z) {\n\t\t\t\tthis.x = x;\n\t\t\t\tthis.y = y;\n\t\t\t\tthis.z = z;\n\t\t\t\treturn this;\n\t\t}\n\t\tsub(rhs) {\n\t\t\t\tthis.x -= rhs.x;\n\t\t\t\tthis.y -= rhs.y;\n\t\t\t\tthis.z -= rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tsub2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x - rhs.x;\n\t\t\t\tthis.y = lhs.y - rhs.y;\n\t\t\t\tthis.z = lhs.z - rhs.z;\n\t\t\t\treturn this;\n\t\t}\n\t\tsubScalar(scalar) {\n\t\t\t\tthis.x -= scalar;\n\t\t\t\tthis.y -= scalar;\n\t\t\t\tthis.z -= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tfromArray(arr, offset = 0) {\n\t\t\t\tthis.x = arr[offset] ?? this.x;\n\t\t\t\tthis.y = arr[offset + 1] ?? this.y;\n\t\t\t\tthis.z = arr[offset + 2] ?? this.z;\n\t\t\t\treturn this;\n\t\t}\n\t\ttoString() {\n\t\t\t\treturn `[${this.x}, ${this.y}, ${this.z}]`;\n\t\t}\n\t\ttoArray(arr = [], offset = 0) {\n\t\t\t\tarr[offset] = this.x;\n\t\t\t\tarr[offset + 1] = this.y;\n\t\t\t\tarr[offset + 2] = this.z;\n\t\t\t\treturn arr;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ZERO = Object.freeze(new Vec3(0, 0, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.HALF = Object.freeze(new Vec3(0.5, 0.5, 0.5));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ONE = Object.freeze(new Vec3(1, 1, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.UP = Object.freeze(new Vec3(0, 1, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DOWN = Object.freeze(new Vec3(0, -1, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.RIGHT = Object.freeze(new Vec3(1, 0, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.LEFT = Object.freeze(new Vec3(-1, 0, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.FORWARD = Object.freeze(new Vec3(0, 0, -1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.BACK = Object.freeze(new Vec3(0, 0, 1));\n\t\t}\n}\n\nclass Mat3 {\n\t\tconstructor(){\n\t\t\t\tthis.data = new Float32Array(9);\n\t\t\t\tthis.data[0] = this.data[4] = this.data[8] = 1;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr().copy(this);\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tconst src = rhs.data;\n\t\t\t\tconst dst = this.data;\n\t\t\t\tdst[0] = src[0];\n\t\t\t\tdst[1] = src[1];\n\t\t\t\tdst[2] = src[2];\n\t\t\t\tdst[3] = src[3];\n\t\t\t\tdst[4] = src[4];\n\t\t\t\tdst[5] = src[5];\n\t\t\t\tdst[6] = src[6];\n\t\t\t\tdst[7] = src[7];\n\t\t\t\tdst[8] = src[8];\n\t\t\t\treturn this;\n\t\t}\n\t\tset(src) {\n\t\t\t\tconst dst = this.data;\n\t\t\t\tdst[0] = src[0];\n\t\t\t\tdst[1] = src[1];\n\t\t\t\tdst[2] = src[2];\n\t\t\t\tdst[3] = src[3];\n\t\t\t\tdst[4] = src[4];\n\t\t\t\tdst[5] = src[5];\n\t\t\t\tdst[6] = src[6];\n\t\t\t\tdst[7] = src[7];\n\t\t\t\tdst[8] = src[8];\n\t\t\t\treturn this;\n\t\t}\n\t\tgetX(x = new Vec3()) {\n\t\t\t\treturn x.set(this.data[0], this.data[1], this.data[2]);\n\t\t}\n\t\tgetY(y = new Vec3()) {\n\t\t\t\treturn y.set(this.data[3], this.data[4], this.data[5]);\n\t\t}\n\t\tgetZ(z = new Vec3()) {\n\t\t\t\treturn z.set(this.data[6], this.data[7], this.data[8]);\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\tconst l = this.data;\n\t\t\t\tconst r = rhs.data;\n\t\t\t\treturn l[0] === r[0] && l[1] === r[1] && l[2] === r[2] && l[3] === r[3] && l[4] === r[4] && l[5] === r[5] && l[6] === r[6] && l[7] === r[7] && l[8] === r[8];\n\t\t}\n\t\tisIdentity() {\n\t\t\t\tconst m = this.data;\n\t\t\t\treturn m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 0 && m[4] === 1 && m[5] === 0 && m[6] === 0 && m[7] === 0 && m[8] === 1;\n\t\t}\n\t\tsetIdentity() {\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = 1;\n\t\t\t\tm[1] = 0;\n\t\t\t\tm[2] = 0;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = 1;\n\t\t\t\tm[5] = 0;\n\t\t\t\tm[6] = 0;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\ttoString() {\n\t\t\t\treturn `[${this.data.join(', ')}]`;\n\t\t}\n\t\ttranspose(src = this) {\n\t\t\t\tconst s = src.data;\n\t\t\t\tconst t = this.data;\n\t\t\t\tif (s === t) {\n\t\t\t\t\t\tlet tmp;\n\t\t\t\t\t\ttmp = s[1];\n\t\t\t\t\t\tt[1] = s[3];\n\t\t\t\t\t\tt[3] = tmp;\n\t\t\t\t\t\ttmp = s[2];\n\t\t\t\t\t\tt[2] = s[6];\n\t\t\t\t\t\tt[6] = tmp;\n\t\t\t\t\t\ttmp = s[5];\n\t\t\t\t\t\tt[5] = s[7];\n\t\t\t\t\t\tt[7] = tmp;\n\t\t\t\t} else {\n\t\t\t\t\t\tt[0] = s[0];\n\t\t\t\t\t\tt[1] = s[3];\n\t\t\t\t\t\tt[2] = s[6];\n\t\t\t\t\t\tt[3] = s[1];\n\t\t\t\t\t\tt[4] = s[4];\n\t\t\t\t\t\tt[5] = s[7];\n\t\t\t\t\t\tt[6] = s[2];\n\t\t\t\t\t\tt[7] = s[5];\n\t\t\t\t\t\tt[8] = s[8];\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromMat4(m) {\n\t\t\t\tconst src = m.data;\n\t\t\t\tconst dst = this.data;\n\t\t\t\tdst[0] = src[0];\n\t\t\t\tdst[1] = src[1];\n\t\t\t\tdst[2] = src[2];\n\t\t\t\tdst[3] = src[4];\n\t\t\t\tdst[4] = src[5];\n\t\t\t\tdst[5] = src[6];\n\t\t\t\tdst[6] = src[8];\n\t\t\t\tdst[7] = src[9];\n\t\t\t\tdst[8] = src[10];\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromQuat(r) {\n\t\t\t\tconst qx = r.x;\n\t\t\t\tconst qy = r.y;\n\t\t\t\tconst qz = r.z;\n\t\t\t\tconst qw = r.w;\n\t\t\t\tconst x2 = qx + qx;\n\t\t\t\tconst y2 = qy + qy;\n\t\t\t\tconst z2 = qz + qz;\n\t\t\t\tconst xx = qx * x2;\n\t\t\t\tconst xy = qx * y2;\n\t\t\t\tconst xz = qx * z2;\n\t\t\t\tconst yy = qy * y2;\n\t\t\t\tconst yz = qy * z2;\n\t\t\t\tconst zz = qz * z2;\n\t\t\t\tconst wx = qw * x2;\n\t\t\t\tconst wy = qw * y2;\n\t\t\t\tconst wz = qw * z2;\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = 1 - (yy + zz);\n\t\t\t\tm[1] = xy + wz;\n\t\t\t\tm[2] = xz - wy;\n\t\t\t\tm[3] = xy - wz;\n\t\t\t\tm[4] = 1 - (xx + zz);\n\t\t\t\tm[5] = yz + wx;\n\t\t\t\tm[6] = xz + wy;\n\t\t\t\tm[7] = yz - wx;\n\t\t\t\tm[8] = 1 - (xx + yy);\n\t\t\t\treturn this;\n\t\t}\n\t\tinvertMat4(src) {\n\t\t\t\tconst s = src.data;\n\t\t\t\tconst a0 = s[0];\n\t\t\t\tconst a1 = s[1];\n\t\t\t\tconst a2 = s[2];\n\t\t\t\tconst a4 = s[4];\n\t\t\t\tconst a5 = s[5];\n\t\t\t\tconst a6 = s[6];\n\t\t\t\tconst a8 = s[8];\n\t\t\t\tconst a9 = s[9];\n\t\t\t\tconst a10 = s[10];\n\t\t\t\tconst b11 = a10 * a5 - a6 * a9;\n\t\t\t\tconst b21 = -a10 * a1 + a2 * a9;\n\t\t\t\tconst b31 = a6 * a1 - a2 * a5;\n\t\t\t\tconst b12 = -a10 * a4 + a6 * a8;\n\t\t\t\tconst b22 = a10 * a0 - a2 * a8;\n\t\t\t\tconst b32 = -a6 * a0 + a2 * a4;\n\t\t\t\tconst b13 = a9 * a4 - a5 * a8;\n\t\t\t\tconst b23 = -a9 * a0 + a1 * a8;\n\t\t\t\tconst b33 = a5 * a0 - a1 * a4;\n\t\t\t\tconst det = a0 * b11 + a1 * b12 + a2 * b13;\n\t\t\t\tif (det === 0) {\n\t\t\t\t\t\tthis.setIdentity();\n\t\t\t\t} else {\n\t\t\t\t\t\tconst invDet = 1 / det;\n\t\t\t\t\t\tconst t = this.data;\n\t\t\t\t\t\tt[0] = b11 * invDet;\n\t\t\t\t\t\tt[1] = b21 * invDet;\n\t\t\t\t\t\tt[2] = b31 * invDet;\n\t\t\t\t\t\tt[3] = b12 * invDet;\n\t\t\t\t\t\tt[4] = b22 * invDet;\n\t\t\t\t\t\tt[5] = b32 * invDet;\n\t\t\t\t\t\tt[6] = b13 * invDet;\n\t\t\t\t\t\tt[7] = b23 * invDet;\n\t\t\t\t\t\tt[8] = b33 * invDet;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\ttransformVector(vec, res = new Vec3()) {\n\t\t\t\tconst m = this.data;\n\t\t\t\tconst { x, y, z } = vec;\n\t\t\t\tres.x = x * m[0] + y * m[3] + z * m[6];\n\t\t\t\tres.y = x * m[1] + y * m[4] + z * m[7];\n\t\t\t\tres.z = x * m[2] + y * m[5] + z * m[8];\n\t\t\t\treturn res;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.IDENTITY = Object.freeze(new Mat3());\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ZERO = Object.freeze(new Mat3().set([\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t]));\n\t\t}\n}\n\nclass Vec2 {\n\t\tconstructor(x = 0, y = 0){\n\t\t\t\tif (x.length === 2) {\n\t\t\t\t\t\tthis.x = x[0];\n\t\t\t\t\t\tthis.y = x[1];\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.x = x;\n\t\t\t\t\t\tthis.y = y;\n\t\t\t\t}\n\t\t}\n\t\tadd(rhs) {\n\t\t\t\tthis.x += rhs.x;\n\t\t\t\tthis.y += rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tadd2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x + rhs.x;\n\t\t\t\tthis.y = lhs.y + rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\taddScalar(scalar) {\n\t\t\t\tthis.x += scalar;\n\t\t\t\tthis.y += scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\taddScaled(rhs, scalar) {\n\t\t\t\tthis.x += rhs.x * scalar;\n\t\t\t\tthis.y += rhs.y * scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr(this.x, this.y);\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis.x = rhs.x;\n\t\t\t\tthis.y = rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tcross(rhs) {\n\t\t\t\treturn this.x * rhs.y - this.y * rhs.x;\n\t\t}\n\t\tdistance(rhs) {\n\t\t\t\tconst x = this.x - rhs.x;\n\t\t\t\tconst y = this.y - rhs.y;\n\t\t\t\treturn Math.sqrt(x * x + y * y);\n\t\t}\n\t\tdiv(rhs) {\n\t\t\t\tthis.x /= rhs.x;\n\t\t\t\tthis.y /= rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tdiv2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x / rhs.x;\n\t\t\t\tthis.y = lhs.y / rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tdivScalar(scalar) {\n\t\t\t\tthis.x /= scalar;\n\t\t\t\tthis.y /= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tdot(rhs) {\n\t\t\t\treturn this.x * rhs.x + this.y * rhs.y;\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\treturn this.x === rhs.x && this.y === rhs.y;\n\t\t}\n\t\tequalsApprox(rhs, epsilon = 1e-6) {\n\t\t\t\treturn Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon;\n\t\t}\n\t\tlength() {\n\t\t\t\treturn Math.sqrt(this.x * this.x + this.y * this.y);\n\t\t}\n\t\tlengthSq() {\n\t\t\t\treturn this.x * this.x + this.y * this.y;\n\t\t}\n\t\tlerp(lhs, rhs, alpha) {\n\t\t\t\tthis.x = lhs.x + alpha * (rhs.x - lhs.x);\n\t\t\t\tthis.y = lhs.y + alpha * (rhs.y - lhs.y);\n\t\t\t\treturn this;\n\t\t}\n\t\tmul(rhs) {\n\t\t\t\tthis.x *= rhs.x;\n\t\t\t\tthis.y *= rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tmul2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x * rhs.x;\n\t\t\t\tthis.y = lhs.y * rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tmulScalar(scalar) {\n\t\t\t\tthis.x *= scalar;\n\t\t\t\tthis.y *= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tnormalize(src = this) {\n\t\t\t\tconst lengthSq = src.x * src.x + src.y * src.y;\n\t\t\t\tif (lengthSq > 0) {\n\t\t\t\t\t\tconst invLength = 1 / Math.sqrt(lengthSq);\n\t\t\t\t\t\tthis.x = src.x * invLength;\n\t\t\t\t\t\tthis.y = src.y * invLength;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\trotate(degrees) {\n\t\t\t\tconst angle = Math.atan2(this.x, this.y) + degrees * math.DEG_TO_RAD;\n\t\t\t\tconst len = Math.sqrt(this.x * this.x + this.y * this.y);\n\t\t\t\tthis.x = Math.sin(angle) * len;\n\t\t\t\tthis.y = Math.cos(angle) * len;\n\t\t\t\treturn this;\n\t\t}\n\t\tangle() {\n\t\t\t\treturn Math.atan2(this.x, this.y) * math.RAD_TO_DEG;\n\t\t}\n\t\tangleTo(rhs) {\n\t\t\t\treturn Math.atan2(this.x * rhs.y + this.y * rhs.x, this.x * rhs.x + this.y * rhs.y) * math.RAD_TO_DEG;\n\t\t}\n\t\tfloor(src = this) {\n\t\t\t\tthis.x = Math.floor(src.x);\n\t\t\t\tthis.y = Math.floor(src.y);\n\t\t\t\treturn this;\n\t\t}\n\t\tceil(src = this) {\n\t\t\t\tthis.x = Math.ceil(src.x);\n\t\t\t\tthis.y = Math.ceil(src.y);\n\t\t\t\treturn this;\n\t\t}\n\t\tround(src = this) {\n\t\t\t\tthis.x = Math.round(src.x);\n\t\t\t\tthis.y = Math.round(src.y);\n\t\t\t\treturn this;\n\t\t}\n\t\tmin(rhs) {\n\t\t\t\tif (rhs.x < this.x) this.x = rhs.x;\n\t\t\t\tif (rhs.y < this.y) this.y = rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tmax(rhs) {\n\t\t\t\tif (rhs.x > this.x) this.x = rhs.x;\n\t\t\t\tif (rhs.y > this.y) this.y = rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tset(x, y) {\n\t\t\t\tthis.x = x;\n\t\t\t\tthis.y = y;\n\t\t\t\treturn this;\n\t\t}\n\t\tsub(rhs) {\n\t\t\t\tthis.x -= rhs.x;\n\t\t\t\tthis.y -= rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tsub2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x - rhs.x;\n\t\t\t\tthis.y = lhs.y - rhs.y;\n\t\t\t\treturn this;\n\t\t}\n\t\tsubScalar(scalar) {\n\t\t\t\tthis.x -= scalar;\n\t\t\t\tthis.y -= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tfromArray(arr, offset = 0) {\n\t\t\t\tthis.x = arr[offset] ?? this.x;\n\t\t\t\tthis.y = arr[offset + 1] ?? this.y;\n\t\t\t\treturn this;\n\t\t}\n\t\ttoString() {\n\t\t\t\treturn `[${this.x}, ${this.y}]`;\n\t\t}\n\t\ttoArray(arr = [], offset = 0) {\n\t\t\t\tarr[offset] = this.x;\n\t\t\t\tarr[offset + 1] = this.y;\n\t\t\t\treturn arr;\n\t\t}\n\t\tstatic angleRad(lhs, rhs) {\n\t\t\t\treturn Math.atan2(lhs.x * rhs.y - lhs.y * rhs.x, lhs.x * rhs.x + lhs.y * rhs.y);\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ZERO = Object.freeze(new Vec2(0, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.HALF = Object.freeze(new Vec2(0.5, 0.5));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ONE = Object.freeze(new Vec2(1, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.UP = Object.freeze(new Vec2(0, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DOWN = Object.freeze(new Vec2(0, -1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.RIGHT = Object.freeze(new Vec2(1, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.LEFT = Object.freeze(new Vec2(-1, 0));\n\t\t}\n}\n\nclass Vec4 {\n\t\tconstructor(x = 0, y = 0, z = 0, w = 0){\n\t\t\t\tif (x.length === 4) {\n\t\t\t\t\t\tthis.x = x[0];\n\t\t\t\t\t\tthis.y = x[1];\n\t\t\t\t\t\tthis.z = x[2];\n\t\t\t\t\t\tthis.w = x[3];\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.x = x;\n\t\t\t\t\t\tthis.y = y;\n\t\t\t\t\t\tthis.z = z;\n\t\t\t\t\t\tthis.w = w;\n\t\t\t\t}\n\t\t}\n\t\tadd(rhs) {\n\t\t\t\tthis.x += rhs.x;\n\t\t\t\tthis.y += rhs.y;\n\t\t\t\tthis.z += rhs.z;\n\t\t\t\tthis.w += rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tadd2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x + rhs.x;\n\t\t\t\tthis.y = lhs.y + rhs.y;\n\t\t\t\tthis.z = lhs.z + rhs.z;\n\t\t\t\tthis.w = lhs.w + rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\taddScalar(scalar) {\n\t\t\t\tthis.x += scalar;\n\t\t\t\tthis.y += scalar;\n\t\t\t\tthis.z += scalar;\n\t\t\t\tthis.w += scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\taddScaled(rhs, scalar) {\n\t\t\t\tthis.x += rhs.x * scalar;\n\t\t\t\tthis.y += rhs.y * scalar;\n\t\t\t\tthis.z += rhs.z * scalar;\n\t\t\t\tthis.w += rhs.w * scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr(this.x, this.y, this.z, this.w);\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis.x = rhs.x;\n\t\t\t\tthis.y = rhs.y;\n\t\t\t\tthis.z = rhs.z;\n\t\t\t\tthis.w = rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tdiv(rhs) {\n\t\t\t\tthis.x /= rhs.x;\n\t\t\t\tthis.y /= rhs.y;\n\t\t\t\tthis.z /= rhs.z;\n\t\t\t\tthis.w /= rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tdiv2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x / rhs.x;\n\t\t\t\tthis.y = lhs.y / rhs.y;\n\t\t\t\tthis.z = lhs.z / rhs.z;\n\t\t\t\tthis.w = lhs.w / rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tdivScalar(scalar) {\n\t\t\t\tthis.x /= scalar;\n\t\t\t\tthis.y /= scalar;\n\t\t\t\tthis.z /= scalar;\n\t\t\t\tthis.w /= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tdot(rhs) {\n\t\t\t\treturn this.x * rhs.x + this.y * rhs.y + this.z * rhs.z + this.w * rhs.w;\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\treturn this.x === rhs.x && this.y === rhs.y && this.z === rhs.z && this.w === rhs.w;\n\t\t}\n\t\tequalsApprox(rhs, epsilon = 1e-6) {\n\t\t\t\treturn Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon && Math.abs(this.w - rhs.w) < epsilon;\n\t\t}\n\t\tlength() {\n\t\t\t\treturn Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);\n\t\t}\n\t\tlengthSq() {\n\t\t\t\treturn this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;\n\t\t}\n\t\tlerp(lhs, rhs, alpha) {\n\t\t\t\tthis.x = lhs.x + alpha * (rhs.x - lhs.x);\n\t\t\t\tthis.y = lhs.y + alpha * (rhs.y - lhs.y);\n\t\t\t\tthis.z = lhs.z + alpha * (rhs.z - lhs.z);\n\t\t\t\tthis.w = lhs.w + alpha * (rhs.w - lhs.w);\n\t\t\t\treturn this;\n\t\t}\n\t\tmul(rhs) {\n\t\t\t\tthis.x *= rhs.x;\n\t\t\t\tthis.y *= rhs.y;\n\t\t\t\tthis.z *= rhs.z;\n\t\t\t\tthis.w *= rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tmul2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x * rhs.x;\n\t\t\t\tthis.y = lhs.y * rhs.y;\n\t\t\t\tthis.z = lhs.z * rhs.z;\n\t\t\t\tthis.w = lhs.w * rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tmulScalar(scalar) {\n\t\t\t\tthis.x *= scalar;\n\t\t\t\tthis.y *= scalar;\n\t\t\t\tthis.z *= scalar;\n\t\t\t\tthis.w *= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tnormalize(src = this) {\n\t\t\t\tconst lengthSq = src.x * src.x + src.y * src.y + src.z * src.z + src.w * src.w;\n\t\t\t\tif (lengthSq > 0) {\n\t\t\t\t\t\tconst invLength = 1 / Math.sqrt(lengthSq);\n\t\t\t\t\t\tthis.x = src.x * invLength;\n\t\t\t\t\t\tthis.y = src.y * invLength;\n\t\t\t\t\t\tthis.z = src.z * invLength;\n\t\t\t\t\t\tthis.w = src.w * invLength;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tfloor(src = this) {\n\t\t\t\tthis.x = Math.floor(src.x);\n\t\t\t\tthis.y = Math.floor(src.y);\n\t\t\t\tthis.z = Math.floor(src.z);\n\t\t\t\tthis.w = Math.floor(src.w);\n\t\t\t\treturn this;\n\t\t}\n\t\tceil(src = this) {\n\t\t\t\tthis.x = Math.ceil(src.x);\n\t\t\t\tthis.y = Math.ceil(src.y);\n\t\t\t\tthis.z = Math.ceil(src.z);\n\t\t\t\tthis.w = Math.ceil(src.w);\n\t\t\t\treturn this;\n\t\t}\n\t\tround(src = this) {\n\t\t\t\tthis.x = Math.round(src.x);\n\t\t\t\tthis.y = Math.round(src.y);\n\t\t\t\tthis.z = Math.round(src.z);\n\t\t\t\tthis.w = Math.round(src.w);\n\t\t\t\treturn this;\n\t\t}\n\t\tmin(rhs) {\n\t\t\t\tif (rhs.x < this.x) this.x = rhs.x;\n\t\t\t\tif (rhs.y < this.y) this.y = rhs.y;\n\t\t\t\tif (rhs.z < this.z) this.z = rhs.z;\n\t\t\t\tif (rhs.w < this.w) this.w = rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tmax(rhs) {\n\t\t\t\tif (rhs.x > this.x) this.x = rhs.x;\n\t\t\t\tif (rhs.y > this.y) this.y = rhs.y;\n\t\t\t\tif (rhs.z > this.z) this.z = rhs.z;\n\t\t\t\tif (rhs.w > this.w) this.w = rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tset(x, y, z, w) {\n\t\t\t\tthis.x = x;\n\t\t\t\tthis.y = y;\n\t\t\t\tthis.z = z;\n\t\t\t\tthis.w = w;\n\t\t\t\treturn this;\n\t\t}\n\t\tsub(rhs) {\n\t\t\t\tthis.x -= rhs.x;\n\t\t\t\tthis.y -= rhs.y;\n\t\t\t\tthis.z -= rhs.z;\n\t\t\t\tthis.w -= rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tsub2(lhs, rhs) {\n\t\t\t\tthis.x = lhs.x - rhs.x;\n\t\t\t\tthis.y = lhs.y - rhs.y;\n\t\t\t\tthis.z = lhs.z - rhs.z;\n\t\t\t\tthis.w = lhs.w - rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tsubScalar(scalar) {\n\t\t\t\tthis.x -= scalar;\n\t\t\t\tthis.y -= scalar;\n\t\t\t\tthis.z -= scalar;\n\t\t\t\tthis.w -= scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tfromArray(arr, offset = 0) {\n\t\t\t\tthis.x = arr[offset] ?? this.x;\n\t\t\t\tthis.y = arr[offset + 1] ?? this.y;\n\t\t\t\tthis.z = arr[offset + 2] ?? this.z;\n\t\t\t\tthis.w = arr[offset + 3] ?? this.w;\n\t\t\t\treturn this;\n\t\t}\n\t\ttoString() {\n\t\t\t\treturn `[${this.x}, ${this.y}, ${this.z}, ${this.w}]`;\n\t\t}\n\t\ttoArray(arr = [], offset = 0) {\n\t\t\t\tarr[offset] = this.x;\n\t\t\t\tarr[offset + 1] = this.y;\n\t\t\t\tarr[offset + 2] = this.z;\n\t\t\t\tarr[offset + 3] = this.w;\n\t\t\t\treturn arr;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ZERO = Object.freeze(new Vec4(0, 0, 0, 0));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.HALF = Object.freeze(new Vec4(0.5, 0.5, 0.5, 0.5));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ONE = Object.freeze(new Vec4(1, 1, 1, 1));\n\t\t}\n}\n\nconst _halfSize$1 = new Vec2();\nconst x = new Vec3();\nconst y = new Vec3();\nconst z = new Vec3();\nconst scale = new Vec3();\nclass Mat4 {\n\t\tconstructor(){\n\t\t\t\tthis.data = new Float32Array(16);\n\t\t\t\tthis.data[0] = this.data[5] = this.data[10] = this.data[15] = 1;\n\t\t}\n\t\tstatic _getPerspectiveHalfSize(halfSize, fov, aspect, znear, fovIsHorizontal) {\n\t\t\t\tif (fovIsHorizontal) {\n\t\t\t\t\t\thalfSize.x = znear * Math.tan(fov * Math.PI / 360);\n\t\t\t\t\t\thalfSize.y = halfSize.x / aspect;\n\t\t\t\t} else {\n\t\t\t\t\t\thalfSize.y = znear * Math.tan(fov * Math.PI / 360);\n\t\t\t\t\t\thalfSize.x = halfSize.y * aspect;\n\t\t\t\t}\n\t\t}\n\t\tadd2(lhs, rhs) {\n\t\t\t\tconst a = lhs.data, b = rhs.data, r = this.data;\n\t\t\t\tr[0] = a[0] + b[0];\n\t\t\t\tr[1] = a[1] + b[1];\n\t\t\t\tr[2] = a[2] + b[2];\n\t\t\t\tr[3] = a[3] + b[3];\n\t\t\t\tr[4] = a[4] + b[4];\n\t\t\t\tr[5] = a[5] + b[5];\n\t\t\t\tr[6] = a[6] + b[6];\n\t\t\t\tr[7] = a[7] + b[7];\n\t\t\t\tr[8] = a[8] + b[8];\n\t\t\t\tr[9] = a[9] + b[9];\n\t\t\t\tr[10] = a[10] + b[10];\n\t\t\t\tr[11] = a[11] + b[11];\n\t\t\t\tr[12] = a[12] + b[12];\n\t\t\t\tr[13] = a[13] + b[13];\n\t\t\t\tr[14] = a[14] + b[14];\n\t\t\t\tr[15] = a[15] + b[15];\n\t\t\t\treturn this;\n\t\t}\n\t\tadd(rhs) {\n\t\t\t\treturn this.add2(this, rhs);\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr().copy(this);\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tconst src = rhs.data, dst = this.data;\n\t\t\t\tdst[0] = src[0];\n\t\t\t\tdst[1] = src[1];\n\t\t\t\tdst[2] = src[2];\n\t\t\t\tdst[3] = src[3];\n\t\t\t\tdst[4] = src[4];\n\t\t\t\tdst[5] = src[5];\n\t\t\t\tdst[6] = src[6];\n\t\t\t\tdst[7] = src[7];\n\t\t\t\tdst[8] = src[8];\n\t\t\t\tdst[9] = src[9];\n\t\t\t\tdst[10] = src[10];\n\t\t\t\tdst[11] = src[11];\n\t\t\t\tdst[12] = src[12];\n\t\t\t\tdst[13] = src[13];\n\t\t\t\tdst[14] = src[14];\n\t\t\t\tdst[15] = src[15];\n\t\t\t\treturn this;\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\tconst l = this.data, r = rhs.data;\n\t\t\t\treturn l[0] === r[0] && l[1] === r[1] && l[2] === r[2] && l[3] === r[3] && l[4] === r[4] && l[5] === r[5] && l[6] === r[6] && l[7] === r[7] && l[8] === r[8] && l[9] === r[9] && l[10] === r[10] && l[11] === r[11] && l[12] === r[12] && l[13] === r[13] && l[14] === r[14] && l[15] === r[15];\n\t\t}\n\t\tisIdentity() {\n\t\t\t\tconst m = this.data;\n\t\t\t\treturn m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 0 && m[4] === 0 && m[5] === 1 && m[6] === 0 && m[7] === 0 && m[8] === 0 && m[9] === 0 && m[10] === 1 && m[11] === 0 && m[12] === 0 && m[13] === 0 && m[14] === 0 && m[15] === 1;\n\t\t}\n\t\tmul2(lhs, rhs) {\n\t\t\t\tconst a = lhs.data;\n\t\t\t\tconst b = rhs.data;\n\t\t\t\tconst r = this.data;\n\t\t\t\tconst a00 = a[0];\n\t\t\t\tconst a01 = a[1];\n\t\t\t\tconst a02 = a[2];\n\t\t\t\tconst a03 = a[3];\n\t\t\t\tconst a10 = a[4];\n\t\t\t\tconst a11 = a[5];\n\t\t\t\tconst a12 = a[6];\n\t\t\t\tconst a13 = a[7];\n\t\t\t\tconst a20 = a[8];\n\t\t\t\tconst a21 = a[9];\n\t\t\t\tconst a22 = a[10];\n\t\t\t\tconst a23 = a[11];\n\t\t\t\tconst a30 = a[12];\n\t\t\t\tconst a31 = a[13];\n\t\t\t\tconst a32 = a[14];\n\t\t\t\tconst a33 = a[15];\n\t\t\t\tlet b0, b1, b2, b3;\n\t\t\t\tb0 = b[0];\n\t\t\t\tb1 = b[1];\n\t\t\t\tb2 = b[2];\n\t\t\t\tb3 = b[3];\n\t\t\t\tr[0] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;\n\t\t\t\tr[1] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;\n\t\t\t\tr[2] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;\n\t\t\t\tr[3] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;\n\t\t\t\tb0 = b[4];\n\t\t\t\tb1 = b[5];\n\t\t\t\tb2 = b[6];\n\t\t\t\tb3 = b[7];\n\t\t\t\tr[4] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;\n\t\t\t\tr[5] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;\n\t\t\t\tr[6] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;\n\t\t\t\tr[7] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;\n\t\t\t\tb0 = b[8];\n\t\t\t\tb1 = b[9];\n\t\t\t\tb2 = b[10];\n\t\t\t\tb3 = b[11];\n\t\t\t\tr[8] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;\n\t\t\t\tr[9] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;\n\t\t\t\tr[10] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;\n\t\t\t\tr[11] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;\n\t\t\t\tb0 = b[12];\n\t\t\t\tb1 = b[13];\n\t\t\t\tb2 = b[14];\n\t\t\t\tb3 = b[15];\n\t\t\t\tr[12] = a00 * b0 + a10 * b1 + a20 * b2 + a30 * b3;\n\t\t\t\tr[13] = a01 * b0 + a11 * b1 + a21 * b2 + a31 * b3;\n\t\t\t\tr[14] = a02 * b0 + a12 * b1 + a22 * b2 + a32 * b3;\n\t\t\t\tr[15] = a03 * b0 + a13 * b1 + a23 * b2 + a33 * b3;\n\t\t\t\treturn this;\n\t\t}\n\t\tmulAffine2(lhs, rhs) {\n\t\t\t\tconst a = lhs.data;\n\t\t\t\tconst b = rhs.data;\n\t\t\t\tconst r = this.data;\n\t\t\t\tconst a00 = a[0];\n\t\t\t\tconst a01 = a[1];\n\t\t\t\tconst a02 = a[2];\n\t\t\t\tconst a10 = a[4];\n\t\t\t\tconst a11 = a[5];\n\t\t\t\tconst a12 = a[6];\n\t\t\t\tconst a20 = a[8];\n\t\t\t\tconst a21 = a[9];\n\t\t\t\tconst a22 = a[10];\n\t\t\t\tconst a30 = a[12];\n\t\t\t\tconst a31 = a[13];\n\t\t\t\tconst a32 = a[14];\n\t\t\t\tlet b0, b1, b2;\n\t\t\t\tb0 = b[0];\n\t\t\t\tb1 = b[1];\n\t\t\t\tb2 = b[2];\n\t\t\t\tr[0] = a00 * b0 + a10 * b1 + a20 * b2;\n\t\t\t\tr[1] = a01 * b0 + a11 * b1 + a21 * b2;\n\t\t\t\tr[2] = a02 * b0 + a12 * b1 + a22 * b2;\n\t\t\t\tr[3] = 0;\n\t\t\t\tb0 = b[4];\n\t\t\t\tb1 = b[5];\n\t\t\t\tb2 = b[6];\n\t\t\t\tr[4] = a00 * b0 + a10 * b1 + a20 * b2;\n\t\t\t\tr[5] = a01 * b0 + a11 * b1 + a21 * b2;\n\t\t\t\tr[6] = a02 * b0 + a12 * b1 + a22 * b2;\n\t\t\t\tr[7] = 0;\n\t\t\t\tb0 = b[8];\n\t\t\t\tb1 = b[9];\n\t\t\t\tb2 = b[10];\n\t\t\t\tr[8] = a00 * b0 + a10 * b1 + a20 * b2;\n\t\t\t\tr[9] = a01 * b0 + a11 * b1 + a21 * b2;\n\t\t\t\tr[10] = a02 * b0 + a12 * b1 + a22 * b2;\n\t\t\t\tr[11] = 0;\n\t\t\t\tb0 = b[12];\n\t\t\t\tb1 = b[13];\n\t\t\t\tb2 = b[14];\n\t\t\t\tr[12] = a00 * b0 + a10 * b1 + a20 * b2 + a30;\n\t\t\t\tr[13] = a01 * b0 + a11 * b1 + a21 * b2 + a31;\n\t\t\t\tr[14] = a02 * b0 + a12 * b1 + a22 * b2 + a32;\n\t\t\t\tr[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tmul(rhs) {\n\t\t\t\treturn this.mul2(this, rhs);\n\t\t}\n\t\ttransformPoint(vec, res = new Vec3()) {\n\t\t\t\tconst m = this.data;\n\t\t\t\tconst { x, y, z } = vec;\n\t\t\t\tres.x = x * m[0] + y * m[4] + z * m[8] + m[12];\n\t\t\t\tres.y = x * m[1] + y * m[5] + z * m[9] + m[13];\n\t\t\t\tres.z = x * m[2] + y * m[6] + z * m[10] + m[14];\n\t\t\t\treturn res;\n\t\t}\n\t\ttransformVector(vec, res = new Vec3()) {\n\t\t\t\tconst m = this.data;\n\t\t\t\tconst { x, y, z } = vec;\n\t\t\t\tres.x = x * m[0] + y * m[4] + z * m[8];\n\t\t\t\tres.y = x * m[1] + y * m[5] + z * m[9];\n\t\t\t\tres.z = x * m[2] + y * m[6] + z * m[10];\n\t\t\t\treturn res;\n\t\t}\n\t\ttransformVec4(vec, res = new Vec4()) {\n\t\t\t\tconst m = this.data;\n\t\t\t\tconst { x, y, z, w } = vec;\n\t\t\t\tres.x = x * m[0] + y * m[4] + z * m[8] + w * m[12];\n\t\t\t\tres.y = x * m[1] + y * m[5] + z * m[9] + w * m[13];\n\t\t\t\tres.z = x * m[2] + y * m[6] + z * m[10] + w * m[14];\n\t\t\t\tres.w = x * m[3] + y * m[7] + z * m[11] + w * m[15];\n\t\t\t\treturn res;\n\t\t}\n\t\tsetLookAt(position, target, up) {\n\t\t\t\tz.sub2(position, target).normalize();\n\t\t\t\ty.copy(up).normalize();\n\t\t\t\tx.cross(y, z).normalize();\n\t\t\t\ty.cross(z, x);\n\t\t\t\tconst r = this.data;\n\t\t\t\tr[0] = x.x;\n\t\t\t\tr[1] = x.y;\n\t\t\t\tr[2] = x.z;\n\t\t\t\tr[3] = 0;\n\t\t\t\tr[4] = y.x;\n\t\t\t\tr[5] = y.y;\n\t\t\t\tr[6] = y.z;\n\t\t\t\tr[7] = 0;\n\t\t\t\tr[8] = z.x;\n\t\t\t\tr[9] = z.y;\n\t\t\t\tr[10] = z.z;\n\t\t\t\tr[11] = 0;\n\t\t\t\tr[12] = position.x;\n\t\t\t\tr[13] = position.y;\n\t\t\t\tr[14] = position.z;\n\t\t\t\tr[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFrustum(left, right, bottom, top, znear, zfar) {\n\t\t\t\tconst temp1 = 2 * znear;\n\t\t\t\tconst temp2 = right - left;\n\t\t\t\tconst temp3 = top - bottom;\n\t\t\t\tconst temp4 = zfar - znear;\n\t\t\t\tconst r = this.data;\n\t\t\t\tr[0] = temp1 / temp2;\n\t\t\t\tr[1] = 0;\n\t\t\t\tr[2] = 0;\n\t\t\t\tr[3] = 0;\n\t\t\t\tr[4] = 0;\n\t\t\t\tr[5] = temp1 / temp3;\n\t\t\t\tr[6] = 0;\n\t\t\t\tr[7] = 0;\n\t\t\t\tr[8] = (right + left) / temp2;\n\t\t\t\tr[9] = (top + bottom) / temp3;\n\t\t\t\tr[10] = (-zfar - znear) / temp4;\n\t\t\t\tr[11] = -1;\n\t\t\t\tr[12] = 0;\n\t\t\t\tr[13] = 0;\n\t\t\t\tr[14] = -temp1 * zfar / temp4;\n\t\t\t\tr[15] = 0;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetPerspective(fov, aspect, znear, zfar, fovIsHorizontal) {\n\t\t\t\tMat4._getPerspectiveHalfSize(_halfSize$1, fov, aspect, znear, fovIsHorizontal);\n\t\t\t\treturn this.setFrustum(-_halfSize$1.x, _halfSize$1.x, -_halfSize$1.y, _halfSize$1.y, znear, zfar);\n\t\t}\n\t\tsetOrtho(left, right, bottom, top, near, far) {\n\t\t\t\tconst r = this.data;\n\t\t\t\tr[0] = 2 / (right - left);\n\t\t\t\tr[1] = 0;\n\t\t\t\tr[2] = 0;\n\t\t\t\tr[3] = 0;\n\t\t\t\tr[4] = 0;\n\t\t\t\tr[5] = 2 / (top - bottom);\n\t\t\t\tr[6] = 0;\n\t\t\t\tr[7] = 0;\n\t\t\t\tr[8] = 0;\n\t\t\t\tr[9] = 0;\n\t\t\t\tr[10] = -2 / (far - near);\n\t\t\t\tr[11] = 0;\n\t\t\t\tr[12] = -(right + left) / (right - left);\n\t\t\t\tr[13] = -(top + bottom) / (top - bottom);\n\t\t\t\tr[14] = -(far + near) / (far - near);\n\t\t\t\tr[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromAxisAngle(axis, angle) {\n\t\t\t\tangle *= math.DEG_TO_RAD;\n\t\t\t\tconst { x, y, z } = axis;\n\t\t\t\tconst c = Math.cos(angle);\n\t\t\t\tconst s = Math.sin(angle);\n\t\t\t\tconst t = 1 - c;\n\t\t\t\tconst tx = t * x;\n\t\t\t\tconst ty = t * y;\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = tx * x + c;\n\t\t\t\tm[1] = tx * y + s * z;\n\t\t\t\tm[2] = tx * z - s * y;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = tx * y - s * z;\n\t\t\t\tm[5] = ty * y + c;\n\t\t\t\tm[6] = ty * z + s * x;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = tx * z + s * y;\n\t\t\t\tm[9] = ty * z - x * s;\n\t\t\t\tm[10] = t * z * z + c;\n\t\t\t\tm[11] = 0;\n\t\t\t\tm[12] = 0;\n\t\t\t\tm[13] = 0;\n\t\t\t\tm[14] = 0;\n\t\t\t\tm[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetTranslate(x, y, z) {\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = 1;\n\t\t\t\tm[1] = 0;\n\t\t\t\tm[2] = 0;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = 0;\n\t\t\t\tm[5] = 1;\n\t\t\t\tm[6] = 0;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = 0;\n\t\t\t\tm[9] = 0;\n\t\t\t\tm[10] = 1;\n\t\t\t\tm[11] = 0;\n\t\t\t\tm[12] = x;\n\t\t\t\tm[13] = y;\n\t\t\t\tm[14] = z;\n\t\t\t\tm[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetScale(x, y, z) {\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = x;\n\t\t\t\tm[1] = 0;\n\t\t\t\tm[2] = 0;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = 0;\n\t\t\t\tm[5] = y;\n\t\t\t\tm[6] = 0;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = 0;\n\t\t\t\tm[9] = 0;\n\t\t\t\tm[10] = z;\n\t\t\t\tm[11] = 0;\n\t\t\t\tm[12] = 0;\n\t\t\t\tm[13] = 0;\n\t\t\t\tm[14] = 0;\n\t\t\t\tm[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetViewport(x, y, width, height) {\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = width * 0.5;\n\t\t\t\tm[1] = 0;\n\t\t\t\tm[2] = 0;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = 0;\n\t\t\t\tm[5] = height * 0.5;\n\t\t\t\tm[6] = 0;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = 0;\n\t\t\t\tm[9] = 0;\n\t\t\t\tm[10] = 0.5;\n\t\t\t\tm[11] = 0;\n\t\t\t\tm[12] = x + width * 0.5;\n\t\t\t\tm[13] = y + height * 0.5;\n\t\t\t\tm[14] = 0.5;\n\t\t\t\tm[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetReflection(normal, distance) {\n\t\t\t\tconst a = normal.x;\n\t\t\t\tconst b = normal.y;\n\t\t\t\tconst c = normal.z;\n\t\t\t\tconst data = this.data;\n\t\t\t\tdata[0] = 1.0 - 2 * a * a;\n\t\t\t\tdata[1] = -2 * a * b;\n\t\t\t\tdata[2] = -2 * a * c;\n\t\t\t\tdata[3] = 0;\n\t\t\t\tdata[4] = -2 * a * b;\n\t\t\t\tdata[5] = 1.0 - 2 * b * b;\n\t\t\t\tdata[6] = -2 * b * c;\n\t\t\t\tdata[7] = 0;\n\t\t\t\tdata[8] = -2 * a * c;\n\t\t\t\tdata[9] = -2 * b * c;\n\t\t\t\tdata[10] = 1.0 - 2 * c * c;\n\t\t\t\tdata[11] = 0;\n\t\t\t\tdata[12] = -2 * a * distance;\n\t\t\t\tdata[13] = -2 * b * distance;\n\t\t\t\tdata[14] = -2 * c * distance;\n\t\t\t\tdata[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tinvert(src = this) {\n\t\t\t\tconst s = src.data;\n\t\t\t\tconst a00 = s[0];\n\t\t\t\tconst a01 = s[1];\n\t\t\t\tconst a02 = s[2];\n\t\t\t\tconst a03 = s[3];\n\t\t\t\tconst a10 = s[4];\n\t\t\t\tconst a11 = s[5];\n\t\t\t\tconst a12 = s[6];\n\t\t\t\tconst a13 = s[7];\n\t\t\t\tconst a20 = s[8];\n\t\t\t\tconst a21 = s[9];\n\t\t\t\tconst a22 = s[10];\n\t\t\t\tconst a23 = s[11];\n\t\t\t\tconst a30 = s[12];\n\t\t\t\tconst a31 = s[13];\n\t\t\t\tconst a32 = s[14];\n\t\t\t\tconst a33 = s[15];\n\t\t\t\tconst b00 = a00 * a11 - a01 * a10;\n\t\t\t\tconst b01 = a00 * a12 - a02 * a10;\n\t\t\t\tconst b02 = a00 * a13 - a03 * a10;\n\t\t\t\tconst b03 = a01 * a12 - a02 * a11;\n\t\t\t\tconst b04 = a01 * a13 - a03 * a11;\n\t\t\t\tconst b05 = a02 * a13 - a03 * a12;\n\t\t\t\tconst b06 = a20 * a31 - a21 * a30;\n\t\t\t\tconst b07 = a20 * a32 - a22 * a30;\n\t\t\t\tconst b08 = a20 * a33 - a23 * a30;\n\t\t\t\tconst b09 = a21 * a32 - a22 * a31;\n\t\t\t\tconst b10 = a21 * a33 - a23 * a31;\n\t\t\t\tconst b11 = a22 * a33 - a23 * a32;\n\t\t\t\tconst det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;\n\t\t\t\tif (det === 0) {\n\t\t\t\t\t\tthis.setIdentity();\n\t\t\t\t} else {\n\t\t\t\t\t\tconst invDet = 1 / det;\n\t\t\t\t\t\tconst t = this.data;\n\t\t\t\t\t\tt[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;\n\t\t\t\t\t\tt[1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet;\n\t\t\t\t\t\tt[2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet;\n\t\t\t\t\t\tt[3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet;\n\t\t\t\t\t\tt[4] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet;\n\t\t\t\t\t\tt[5] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet;\n\t\t\t\t\t\tt[6] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet;\n\t\t\t\t\t\tt[7] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet;\n\t\t\t\t\t\tt[8] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet;\n\t\t\t\t\t\tt[9] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet;\n\t\t\t\t\t\tt[10] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet;\n\t\t\t\t\t\tt[11] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet;\n\t\t\t\t\t\tt[12] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet;\n\t\t\t\t\t\tt[13] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet;\n\t\t\t\t\t\tt[14] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet;\n\t\t\t\t\t\tt[15] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tset(src) {\n\t\t\t\tconst dst = this.data;\n\t\t\t\tdst[0] = src[0];\n\t\t\t\tdst[1] = src[1];\n\t\t\t\tdst[2] = src[2];\n\t\t\t\tdst[3] = src[3];\n\t\t\t\tdst[4] = src[4];\n\t\t\t\tdst[5] = src[5];\n\t\t\t\tdst[6] = src[6];\n\t\t\t\tdst[7] = src[7];\n\t\t\t\tdst[8] = src[8];\n\t\t\t\tdst[9] = src[9];\n\t\t\t\tdst[10] = src[10];\n\t\t\t\tdst[11] = src[11];\n\t\t\t\tdst[12] = src[12];\n\t\t\t\tdst[13] = src[13];\n\t\t\t\tdst[14] = src[14];\n\t\t\t\tdst[15] = src[15];\n\t\t\t\treturn this;\n\t\t}\n\t\tsetIdentity() {\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = 1;\n\t\t\t\tm[1] = 0;\n\t\t\t\tm[2] = 0;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = 0;\n\t\t\t\tm[5] = 1;\n\t\t\t\tm[6] = 0;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = 0;\n\t\t\t\tm[9] = 0;\n\t\t\t\tm[10] = 1;\n\t\t\t\tm[11] = 0;\n\t\t\t\tm[12] = 0;\n\t\t\t\tm[13] = 0;\n\t\t\t\tm[14] = 0;\n\t\t\t\tm[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetTRS(t, r, s) {\n\t\t\t\tconst qx = r.x;\n\t\t\t\tconst qy = r.y;\n\t\t\t\tconst qz = r.z;\n\t\t\t\tconst qw = r.w;\n\t\t\t\tconst sx = s.x;\n\t\t\t\tconst sy = s.y;\n\t\t\t\tconst sz = s.z;\n\t\t\t\tconst x2 = qx + qx;\n\t\t\t\tconst y2 = qy + qy;\n\t\t\t\tconst z2 = qz + qz;\n\t\t\t\tconst xx = qx * x2;\n\t\t\t\tconst xy = qx * y2;\n\t\t\t\tconst xz = qx * z2;\n\t\t\t\tconst yy = qy * y2;\n\t\t\t\tconst yz = qy * z2;\n\t\t\t\tconst zz = qz * z2;\n\t\t\t\tconst wx = qw * x2;\n\t\t\t\tconst wy = qw * y2;\n\t\t\t\tconst wz = qw * z2;\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = (1 - (yy + zz)) * sx;\n\t\t\t\tm[1] = (xy + wz) * sx;\n\t\t\t\tm[2] = (xz - wy) * sx;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = (xy - wz) * sy;\n\t\t\t\tm[5] = (1 - (xx + zz)) * sy;\n\t\t\t\tm[6] = (yz + wx) * sy;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = (xz + wy) * sz;\n\t\t\t\tm[9] = (yz - wx) * sz;\n\t\t\t\tm[10] = (1 - (xx + yy)) * sz;\n\t\t\t\tm[11] = 0;\n\t\t\t\tm[12] = t.x;\n\t\t\t\tm[13] = t.y;\n\t\t\t\tm[14] = t.z;\n\t\t\t\tm[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\ttranspose(src = this) {\n\t\t\t\tconst s = src.data;\n\t\t\t\tconst t = this.data;\n\t\t\t\tif (s === t) {\n\t\t\t\t\t\tlet tmp;\n\t\t\t\t\t\ttmp = s[1];\n\t\t\t\t\t\tt[1] = s[4];\n\t\t\t\t\t\tt[4] = tmp;\n\t\t\t\t\t\ttmp = s[2];\n\t\t\t\t\t\tt[2] = s[8];\n\t\t\t\t\t\tt[8] = tmp;\n\t\t\t\t\t\ttmp = s[3];\n\t\t\t\t\t\tt[3] = s[12];\n\t\t\t\t\t\tt[12] = tmp;\n\t\t\t\t\t\ttmp = s[6];\n\t\t\t\t\t\tt[6] = s[9];\n\t\t\t\t\t\tt[9] = tmp;\n\t\t\t\t\t\ttmp = s[7];\n\t\t\t\t\t\tt[7] = s[13];\n\t\t\t\t\t\tt[13] = tmp;\n\t\t\t\t\t\ttmp = s[11];\n\t\t\t\t\t\tt[11] = s[14];\n\t\t\t\t\t\tt[14] = tmp;\n\t\t\t\t} else {\n\t\t\t\t\t\tt[0] = s[0];\n\t\t\t\t\t\tt[1] = s[4];\n\t\t\t\t\t\tt[2] = s[8];\n\t\t\t\t\t\tt[3] = s[12];\n\t\t\t\t\t\tt[4] = s[1];\n\t\t\t\t\t\tt[5] = s[5];\n\t\t\t\t\t\tt[6] = s[9];\n\t\t\t\t\t\tt[7] = s[13];\n\t\t\t\t\t\tt[8] = s[2];\n\t\t\t\t\t\tt[9] = s[6];\n\t\t\t\t\t\tt[10] = s[10];\n\t\t\t\t\t\tt[11] = s[14];\n\t\t\t\t\t\tt[12] = s[3];\n\t\t\t\t\t\tt[13] = s[7];\n\t\t\t\t\t\tt[14] = s[11];\n\t\t\t\t\t\tt[15] = s[15];\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tgetTranslation(t = new Vec3()) {\n\t\t\t\treturn t.set(this.data[12], this.data[13], this.data[14]);\n\t\t}\n\t\tgetX(x = new Vec3()) {\n\t\t\t\treturn x.set(this.data[0], this.data[1], this.data[2]);\n\t\t}\n\t\tgetY(y = new Vec3()) {\n\t\t\t\treturn y.set(this.data[4], this.data[5], this.data[6]);\n\t\t}\n\t\tgetZ(z = new Vec3()) {\n\t\t\t\treturn z.set(this.data[8], this.data[9], this.data[10]);\n\t\t}\n\t\tgetScale(scale = new Vec3()) {\n\t\t\t\tthis.getX(x);\n\t\t\t\tthis.getY(y);\n\t\t\t\tthis.getZ(z);\n\t\t\t\tscale.set(x.length(), y.length(), z.length());\n\t\t\t\treturn scale;\n\t\t}\n\t\tget scaleSign() {\n\t\t\t\tthis.getX(x);\n\t\t\t\tthis.getY(y);\n\t\t\t\tthis.getZ(z);\n\t\t\t\tx.cross(x, y);\n\t\t\t\treturn x.dot(z) < 0 ? -1 : 1;\n\t\t}\n\t\tsetFromEulerAngles(ex, ey, ez) {\n\t\t\t\tex *= math.DEG_TO_RAD;\n\t\t\t\tey *= math.DEG_TO_RAD;\n\t\t\t\tez *= math.DEG_TO_RAD;\n\t\t\t\tconst s1 = Math.sin(-ex);\n\t\t\t\tconst c1 = Math.cos(-ex);\n\t\t\t\tconst s2 = Math.sin(-ey);\n\t\t\t\tconst c2 = Math.cos(-ey);\n\t\t\t\tconst s3 = Math.sin(-ez);\n\t\t\t\tconst c3 = Math.cos(-ez);\n\t\t\t\tconst m = this.data;\n\t\t\t\tm[0] = c2 * c3;\n\t\t\t\tm[1] = -c2 * s3;\n\t\t\t\tm[2] = s2;\n\t\t\t\tm[3] = 0;\n\t\t\t\tm[4] = c1 * s3 + c3 * s1 * s2;\n\t\t\t\tm[5] = c1 * c3 - s1 * s2 * s3;\n\t\t\t\tm[6] = -c2 * s1;\n\t\t\t\tm[7] = 0;\n\t\t\t\tm[8] = s1 * s3 - c1 * c3 * s2;\n\t\t\t\tm[9] = c3 * s1 + c1 * s2 * s3;\n\t\t\t\tm[10] = c1 * c2;\n\t\t\t\tm[11] = 0;\n\t\t\t\tm[12] = 0;\n\t\t\t\tm[13] = 0;\n\t\t\t\tm[14] = 0;\n\t\t\t\tm[15] = 1;\n\t\t\t\treturn this;\n\t\t}\n\t\tgetEulerAngles(eulers = new Vec3()) {\n\t\t\t\tthis.getScale(scale);\n\t\t\t\tconst sx = scale.x;\n\t\t\t\tconst sy = scale.y;\n\t\t\t\tconst sz = scale.z;\n\t\t\t\tif (sx === 0 || sy === 0 || sz === 0) {\n\t\t\t\t\t\treturn eulers.set(0, 0, 0);\n\t\t\t\t}\n\t\t\t\tconst m = this.data;\n\t\t\t\tconst y = Math.asin(-m[2] / sx);\n\t\t\t\tconst halfPi = Math.PI * 0.5;\n\t\t\t\tlet x, z;\n\t\t\t\tif (y < halfPi) {\n\t\t\t\t\t\tif (y > -halfPi) {\n\t\t\t\t\t\t\t\tx = Math.atan2(m[6] / sy, m[10] / sz);\n\t\t\t\t\t\t\t\tz = Math.atan2(m[1] / sx, m[0] / sx);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tz = 0;\n\t\t\t\t\t\t\t\tx = -Math.atan2(m[4] / sy, m[5] / sy);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tz = 0;\n\t\t\t\t\t\tx = Math.atan2(m[4] / sy, m[5] / sy);\n\t\t\t\t}\n\t\t\t\treturn eulers.set(x, y, z).mulScalar(math.RAD_TO_DEG);\n\t\t}\n\t\ttoString() {\n\t\t\t\treturn `[${this.data.join(', ')}]`;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.IDENTITY = Object.freeze(new Mat4());\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ZERO = Object.freeze(new Mat4().set([\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t]));\n\t\t}\n}\n\nclass Quat {\n\t\tconstructor(x = 0, y = 0, z = 0, w = 1){\n\t\t\t\tif (x.length === 4) {\n\t\t\t\t\t\tthis.x = x[0];\n\t\t\t\t\t\tthis.y = x[1];\n\t\t\t\t\t\tthis.z = x[2];\n\t\t\t\t\t\tthis.w = x[3];\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.x = x;\n\t\t\t\t\t\tthis.y = y;\n\t\t\t\t\t\tthis.z = z;\n\t\t\t\t\t\tthis.w = w;\n\t\t\t\t}\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr(this.x, this.y, this.z, this.w);\n\t\t}\n\t\tconjugate(src = this) {\n\t\t\t\tthis.x = src.x * -1;\n\t\t\t\tthis.y = src.y * -1;\n\t\t\t\tthis.z = src.z * -1;\n\t\t\t\tthis.w = src.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis.x = rhs.x;\n\t\t\t\tthis.y = rhs.y;\n\t\t\t\tthis.z = rhs.z;\n\t\t\t\tthis.w = rhs.w;\n\t\t\t\treturn this;\n\t\t}\n\t\tdot(other) {\n\t\t\t\treturn this.x * other.x + this.y * other.y + this.z * other.z + this.w * other.w;\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\treturn this.x === rhs.x && this.y === rhs.y && this.z === rhs.z && this.w === rhs.w;\n\t\t}\n\t\tequalsApprox(rhs, epsilon = 1e-6) {\n\t\t\t\treturn Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon && Math.abs(this.w - rhs.w) < epsilon;\n\t\t}\n\t\tgetAxisAngle(axis) {\n\t\t\t\tlet rad = Math.acos(this.w) * 2;\n\t\t\t\tconst s = Math.sin(rad / 2);\n\t\t\t\tif (s !== 0) {\n\t\t\t\t\t\taxis.x = this.x / s;\n\t\t\t\t\t\taxis.y = this.y / s;\n\t\t\t\t\t\taxis.z = this.z / s;\n\t\t\t\t\t\tif (axis.x < 0 || axis.y < 0 || axis.z < 0) {\n\t\t\t\t\t\t\t\taxis.x *= -1;\n\t\t\t\t\t\t\t\taxis.y *= -1;\n\t\t\t\t\t\t\t\taxis.z *= -1;\n\t\t\t\t\t\t\t\trad *= -1;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\taxis.x = 1;\n\t\t\t\t\t\taxis.y = 0;\n\t\t\t\t\t\taxis.z = 0;\n\t\t\t\t}\n\t\t\t\treturn rad * math.RAD_TO_DEG;\n\t\t}\n\t\tgetEulerAngles(eulers = new Vec3()) {\n\t\t\t\tlet x, y, z;\n\t\t\t\tconst qx = this.x;\n\t\t\t\tconst qy = this.y;\n\t\t\t\tconst qz = this.z;\n\t\t\t\tconst qw = this.w;\n\t\t\t\tconst a2 = 2 * (qw * qy - qx * qz);\n\t\t\t\tif (a2 <= -0.99999) {\n\t\t\t\t\t\tx = 2 * Math.atan2(qx, qw);\n\t\t\t\t\t\ty = -Math.PI / 2;\n\t\t\t\t\t\tz = 0;\n\t\t\t\t} else if (a2 >= 0.99999) {\n\t\t\t\t\t\tx = 2 * Math.atan2(qx, qw);\n\t\t\t\t\t\ty = Math.PI / 2;\n\t\t\t\t\t\tz = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\tx = Math.atan2(2 * (qw * qx + qy * qz), 1 - 2 * (qx * qx + qy * qy));\n\t\t\t\t\t\ty = Math.asin(a2);\n\t\t\t\t\t\tz = Math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qy * qy + qz * qz));\n\t\t\t\t}\n\t\t\t\treturn eulers.set(x, y, z).mulScalar(math.RAD_TO_DEG);\n\t\t}\n\t\tinvert(src = this) {\n\t\t\t\treturn this.conjugate(src).normalize();\n\t\t}\n\t\tlength() {\n\t\t\t\treturn Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);\n\t\t}\n\t\tlengthSq() {\n\t\t\t\treturn this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;\n\t\t}\n\t\tlerp(lhs, rhs, alpha) {\n\t\t\t\tconst omt = (1 - alpha) * (lhs.dot(rhs) < 0 ? -1 : 1);\n\t\t\t\tthis.x = lhs.x * omt + rhs.x * alpha;\n\t\t\t\tthis.y = lhs.y * omt + rhs.y * alpha;\n\t\t\t\tthis.z = lhs.z * omt + rhs.z * alpha;\n\t\t\t\tthis.w = lhs.w * omt + rhs.w * alpha;\n\t\t\t\treturn this.normalize();\n\t\t}\n\t\tmul(rhs) {\n\t\t\t\tconst q1x = this.x;\n\t\t\t\tconst q1y = this.y;\n\t\t\t\tconst q1z = this.z;\n\t\t\t\tconst q1w = this.w;\n\t\t\t\tconst q2x = rhs.x;\n\t\t\t\tconst q2y = rhs.y;\n\t\t\t\tconst q2z = rhs.z;\n\t\t\t\tconst q2w = rhs.w;\n\t\t\t\tthis.x = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y;\n\t\t\t\tthis.y = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z;\n\t\t\t\tthis.z = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x;\n\t\t\t\tthis.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;\n\t\t\t\treturn this;\n\t\t}\n\t\tmulScalar(scalar, src = this) {\n\t\t\t\tthis.x = src.x * scalar;\n\t\t\t\tthis.y = src.y * scalar;\n\t\t\t\tthis.z = src.z * scalar;\n\t\t\t\tthis.w = src.w * scalar;\n\t\t\t\treturn this;\n\t\t}\n\t\tmul2(lhs, rhs) {\n\t\t\t\tconst q1x = lhs.x;\n\t\t\t\tconst q1y = lhs.y;\n\t\t\t\tconst q1z = lhs.z;\n\t\t\t\tconst q1w = lhs.w;\n\t\t\t\tconst q2x = rhs.x;\n\t\t\t\tconst q2y = rhs.y;\n\t\t\t\tconst q2z = rhs.z;\n\t\t\t\tconst q2w = rhs.w;\n\t\t\t\tthis.x = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y;\n\t\t\t\tthis.y = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z;\n\t\t\t\tthis.z = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x;\n\t\t\t\tthis.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;\n\t\t\t\treturn this;\n\t\t}\n\t\tnormalize(src = this) {\n\t\t\t\tlet len = src.length();\n\t\t\t\tif (len === 0) {\n\t\t\t\t\t\tthis.x = this.y = this.z = 0;\n\t\t\t\t\t\tthis.w = 1;\n\t\t\t\t} else {\n\t\t\t\t\t\tlen = 1 / len;\n\t\t\t\t\t\tthis.x = src.x * len;\n\t\t\t\t\t\tthis.y = src.y * len;\n\t\t\t\t\t\tthis.z = src.z * len;\n\t\t\t\t\t\tthis.w = src.w * len;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tset(x, y, z, w) {\n\t\t\t\tthis.x = x;\n\t\t\t\tthis.y = y;\n\t\t\t\tthis.z = z;\n\t\t\t\tthis.w = w;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromAxisAngle(axis, angle) {\n\t\t\t\tangle *= 0.5 * math.DEG_TO_RAD;\n\t\t\t\tconst sa = Math.sin(angle);\n\t\t\t\tconst ca = Math.cos(angle);\n\t\t\t\tthis.x = sa * axis.x;\n\t\t\t\tthis.y = sa * axis.y;\n\t\t\t\tthis.z = sa * axis.z;\n\t\t\t\tthis.w = ca;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromEulerAngles(ex, ey, ez) {\n\t\t\t\tif (ex instanceof Vec3) {\n\t\t\t\t\t\tconst vec = ex;\n\t\t\t\t\t\tex = vec.x;\n\t\t\t\t\t\tey = vec.y;\n\t\t\t\t\t\tez = vec.z;\n\t\t\t\t}\n\t\t\t\tconst halfToRad = 0.5 * math.DEG_TO_RAD;\n\t\t\t\tex *= halfToRad;\n\t\t\t\tey *= halfToRad;\n\t\t\t\tez *= halfToRad;\n\t\t\t\tconst sx = Math.sin(ex);\n\t\t\t\tconst cx = Math.cos(ex);\n\t\t\t\tconst sy = Math.sin(ey);\n\t\t\t\tconst cy = Math.cos(ey);\n\t\t\t\tconst sz = Math.sin(ez);\n\t\t\t\tconst cz = Math.cos(ez);\n\t\t\t\tthis.x = sx * cy * cz - cx * sy * sz;\n\t\t\t\tthis.y = cx * sy * cz + sx * cy * sz;\n\t\t\t\tthis.z = cx * cy * sz - sx * sy * cz;\n\t\t\t\tthis.w = cx * cy * cz + sx * sy * sz;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromMat4(m) {\n\t\t\t\tconst d = m.data;\n\t\t\t\tlet m00 = d[0];\n\t\t\t\tlet m01 = d[1];\n\t\t\t\tlet m02 = d[2];\n\t\t\t\tlet m10 = d[4];\n\t\t\t\tlet m11 = d[5];\n\t\t\t\tlet m12 = d[6];\n\t\t\t\tlet m20 = d[8];\n\t\t\t\tlet m21 = d[9];\n\t\t\t\tlet m22 = d[10];\n\t\t\t\tconst det = m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20);\n\t\t\t\tif (det < 0) {\n\t\t\t\t\t\tm00 = -m00;\n\t\t\t\t\t\tm01 = -m01;\n\t\t\t\t\t\tm02 = -m02;\n\t\t\t\t}\n\t\t\t\tlet l;\n\t\t\t\tl = m00 * m00 + m01 * m01 + m02 * m02;\n\t\t\t\tif (l === 0) return this.set(0, 0, 0, 1);\n\t\t\t\tl = 1 / Math.sqrt(l);\n\t\t\t\tm00 *= l;\n\t\t\t\tm01 *= l;\n\t\t\t\tm02 *= l;\n\t\t\t\tl = m10 * m10 + m11 * m11 + m12 * m12;\n\t\t\t\tif (l === 0) return this.set(0, 0, 0, 1);\n\t\t\t\tl = 1 / Math.sqrt(l);\n\t\t\t\tm10 *= l;\n\t\t\t\tm11 *= l;\n\t\t\t\tm12 *= l;\n\t\t\t\tl = m20 * m20 + m21 * m21 + m22 * m22;\n\t\t\t\tif (l === 0) return this.set(0, 0, 0, 1);\n\t\t\t\tl = 1 / Math.sqrt(l);\n\t\t\t\tm20 *= l;\n\t\t\t\tm21 *= l;\n\t\t\t\tm22 *= l;\n\t\t\t\tif (m22 < 0) {\n\t\t\t\t\t\tif (m00 > m11) {\n\t\t\t\t\t\t\t\tthis.set(1 + m00 - m11 - m22, m01 + m10, m20 + m02, m12 - m21);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.set(m01 + m10, 1 - m00 + m11 - m22, m12 + m21, m20 - m02);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (m00 < -m11) {\n\t\t\t\t\t\t\t\tthis.set(m20 + m02, m12 + m21, 1 - m00 - m11 + m22, m01 - m10);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.set(m12 - m21, m20 - m02, m01 - m10, 1 + m00 + m11 + m22);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this.mulScalar(1.0 / this.length());\n\t\t}\n\t\tsetFromDirections(from, to) {\n\t\t\t\tconst dotProduct = 1 + from.dot(to);\n\t\t\t\tif (dotProduct < Number.EPSILON) {\n\t\t\t\t\t\tif (Math.abs(from.x) > Math.abs(from.y)) {\n\t\t\t\t\t\t\t\tthis.x = -from.z;\n\t\t\t\t\t\t\t\tthis.y = 0;\n\t\t\t\t\t\t\t\tthis.z = from.x;\n\t\t\t\t\t\t\t\tthis.w = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.x = 0;\n\t\t\t\t\t\t\t\tthis.y = -from.z;\n\t\t\t\t\t\t\t\tthis.z = from.y;\n\t\t\t\t\t\t\t\tthis.w = 0;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.x = from.y * to.z - from.z * to.y;\n\t\t\t\t\t\tthis.y = from.z * to.x - from.x * to.z;\n\t\t\t\t\t\tthis.z = from.x * to.y - from.y * to.x;\n\t\t\t\t\t\tthis.w = dotProduct;\n\t\t\t\t}\n\t\t\t\treturn this.normalize();\n\t\t}\n\t\tslerp(lhs, rhs, alpha) {\n\t\t\t\tconst lx = lhs.x;\n\t\t\t\tconst ly = lhs.y;\n\t\t\t\tconst lz = lhs.z;\n\t\t\t\tconst lw = lhs.w;\n\t\t\t\tlet rx = rhs.x;\n\t\t\t\tlet ry = rhs.y;\n\t\t\t\tlet rz = rhs.z;\n\t\t\t\tlet rw = rhs.w;\n\t\t\t\tlet cosHalfTheta = lw * rw + lx * rx + ly * ry + lz * rz;\n\t\t\t\tif (cosHalfTheta < 0) {\n\t\t\t\t\t\trw = -rw;\n\t\t\t\t\t\trx = -rx;\n\t\t\t\t\t\try = -ry;\n\t\t\t\t\t\trz = -rz;\n\t\t\t\t\t\tcosHalfTheta = -cosHalfTheta;\n\t\t\t\t}\n\t\t\t\tif (Math.abs(cosHalfTheta) >= 1) {\n\t\t\t\t\t\tthis.w = lw;\n\t\t\t\t\t\tthis.x = lx;\n\t\t\t\t\t\tthis.y = ly;\n\t\t\t\t\t\tthis.z = lz;\n\t\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tconst halfTheta = Math.acos(cosHalfTheta);\n\t\t\t\tconst sinHalfTheta = Math.sqrt(1 - cosHalfTheta * cosHalfTheta);\n\t\t\t\tif (Math.abs(sinHalfTheta) < 0.001) {\n\t\t\t\t\t\tthis.w = lw * 0.5 + rw * 0.5;\n\t\t\t\t\t\tthis.x = lx * 0.5 + rx * 0.5;\n\t\t\t\t\t\tthis.y = ly * 0.5 + ry * 0.5;\n\t\t\t\t\t\tthis.z = lz * 0.5 + rz * 0.5;\n\t\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tconst ratioA = Math.sin((1 - alpha) * halfTheta) / sinHalfTheta;\n\t\t\t\tconst ratioB = Math.sin(alpha * halfTheta) / sinHalfTheta;\n\t\t\t\tthis.w = lw * ratioA + rw * ratioB;\n\t\t\t\tthis.x = lx * ratioA + rx * ratioB;\n\t\t\t\tthis.y = ly * ratioA + ry * ratioB;\n\t\t\t\tthis.z = lz * ratioA + rz * ratioB;\n\t\t\t\treturn this;\n\t\t}\n\t\ttransformVector(vec, res = new Vec3()) {\n\t\t\t\tconst x = vec.x, y = vec.y, z = vec.z;\n\t\t\t\tconst qx = this.x, qy = this.y, qz = this.z, qw = this.w;\n\t\t\t\tconst ix = qw * x + qy * z - qz * y;\n\t\t\t\tconst iy = qw * y + qz * x - qx * z;\n\t\t\t\tconst iz = qw * z + qx * y - qy * x;\n\t\t\t\tconst iw = -qx * x - qy * y - qz * z;\n\t\t\t\tres.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;\n\t\t\t\tres.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;\n\t\t\t\tres.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;\n\t\t\t\treturn res;\n\t\t}\n\t\tfromArray(arr, offset = 0) {\n\t\t\t\tthis.x = arr[offset] ?? this.x;\n\t\t\t\tthis.y = arr[offset + 1] ?? this.y;\n\t\t\t\tthis.z = arr[offset + 2] ?? this.z;\n\t\t\t\tthis.w = arr[offset + 3] ?? this.w;\n\t\t\t\treturn this;\n\t\t}\n\t\ttoString() {\n\t\t\t\treturn `[${this.x}, ${this.y}, ${this.z}, ${this.w}]`;\n\t\t}\n\t\ttoArray(arr = [], offset = 0) {\n\t\t\t\tarr[offset] = this.x;\n\t\t\t\tarr[offset + 1] = this.y;\n\t\t\t\tarr[offset + 2] = this.z;\n\t\t\t\tarr[offset + 3] = this.w;\n\t\t\t\treturn arr;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.IDENTITY = Object.freeze(new Quat(0, 0, 0, 1));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ZERO = Object.freeze(new Quat(0, 0, 0, 0));\n\t\t}\n}\n\nconst tmpVecA$1 = new Vec3();\nconst tmpVecB$1 = new Vec3();\nconst tmpVecC = new Vec3();\nconst tmpVecD = new Vec3();\nconst tmpVecE = new Vec3();\nclass BoundingBox {\n\t\tconstructor(center, halfExtents){\n\t\t\t\tthis.center = new Vec3();\n\t\t\t\tthis.halfExtents = new Vec3(0.5, 0.5, 0.5);\n\t\t\t\tthis._min = new Vec3();\n\t\t\t\tthis._max = new Vec3();\n\t\t\t\tif (center) {\n\t\t\t\t\t\tthis.center.copy(center);\n\t\t\t\t}\n\t\t\t\tif (halfExtents) {\n\t\t\t\t\t\tthis.halfExtents.copy(halfExtents);\n\t\t\t\t}\n\t\t}\n\t\tadd(other) {\n\t\t\t\tconst tc = this.center;\n\t\t\t\tconst tcx = tc.x;\n\t\t\t\tconst tcy = tc.y;\n\t\t\t\tconst tcz = tc.z;\n\t\t\t\tconst th = this.halfExtents;\n\t\t\t\tconst thx = th.x;\n\t\t\t\tconst thy = th.y;\n\t\t\t\tconst thz = th.z;\n\t\t\t\tlet tminx = tcx - thx;\n\t\t\t\tlet tmaxx = tcx + thx;\n\t\t\t\tlet tminy = tcy - thy;\n\t\t\t\tlet tmaxy = tcy + thy;\n\t\t\t\tlet tminz = tcz - thz;\n\t\t\t\tlet tmaxz = tcz + thz;\n\t\t\t\tconst oc = other.center;\n\t\t\t\tconst ocx = oc.x;\n\t\t\t\tconst ocy = oc.y;\n\t\t\t\tconst ocz = oc.z;\n\t\t\t\tconst oh = other.halfExtents;\n\t\t\t\tconst ohx = oh.x;\n\t\t\t\tconst ohy = oh.y;\n\t\t\t\tconst ohz = oh.z;\n\t\t\t\tconst ominx = ocx - ohx;\n\t\t\t\tconst omaxx = ocx + ohx;\n\t\t\t\tconst ominy = ocy - ohy;\n\t\t\t\tconst omaxy = ocy + ohy;\n\t\t\t\tconst ominz = ocz - ohz;\n\t\t\t\tconst omaxz = ocz + ohz;\n\t\t\t\tif (ominx < tminx) tminx = ominx;\n\t\t\t\tif (omaxx > tmaxx) tmaxx = omaxx;\n\t\t\t\tif (ominy < tminy) tminy = ominy;\n\t\t\t\tif (omaxy > tmaxy) tmaxy = omaxy;\n\t\t\t\tif (ominz < tminz) tminz = ominz;\n\t\t\t\tif (omaxz > tmaxz) tmaxz = omaxz;\n\t\t\t\ttc.x = (tminx + tmaxx) * 0.5;\n\t\t\t\ttc.y = (tminy + tmaxy) * 0.5;\n\t\t\t\ttc.z = (tminz + tmaxz) * 0.5;\n\t\t\t\tth.x = (tmaxx - tminx) * 0.5;\n\t\t\t\tth.y = (tmaxy - tminy) * 0.5;\n\t\t\t\tth.z = (tmaxz - tminz) * 0.5;\n\t\t}\n\t\tcopy(src) {\n\t\t\t\tthis.center.copy(src.center);\n\t\t\t\tthis.halfExtents.copy(src.halfExtents);\n\t\t}\n\t\tclone() {\n\t\t\t\treturn new BoundingBox(this.center, this.halfExtents);\n\t\t}\n\t\tintersects(other) {\n\t\t\t\tconst aMax = this.getMax();\n\t\t\t\tconst aMin = this.getMin();\n\t\t\t\tconst bMax = other.getMax();\n\t\t\t\tconst bMin = other.getMin();\n\t\t\t\treturn aMin.x <= bMax.x && aMax.x >= bMin.x && aMin.y <= bMax.y && aMax.y >= bMin.y && aMin.z <= bMax.z && aMax.z >= bMin.z;\n\t\t}\n\t\t_intersectsRay(ray, point) {\n\t\t\t\tconst tMin = tmpVecA$1.copy(this.getMin()).sub(ray.origin);\n\t\t\t\tconst tMax = tmpVecB$1.copy(this.getMax()).sub(ray.origin);\n\t\t\t\tconst dir = ray.direction;\n\t\t\t\tif (dir.x === 0) {\n\t\t\t\t\t\ttMin.x = tMin.x < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;\n\t\t\t\t\t\ttMax.x = tMax.x < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;\n\t\t\t\t} else {\n\t\t\t\t\t\ttMin.x /= dir.x;\n\t\t\t\t\t\ttMax.x /= dir.x;\n\t\t\t\t}\n\t\t\t\tif (dir.y === 0) {\n\t\t\t\t\t\ttMin.y = tMin.y < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;\n\t\t\t\t\t\ttMax.y = tMax.y < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;\n\t\t\t\t} else {\n\t\t\t\t\t\ttMin.y /= dir.y;\n\t\t\t\t\t\ttMax.y /= dir.y;\n\t\t\t\t}\n\t\t\t\tif (dir.z === 0) {\n\t\t\t\t\t\ttMin.z = tMin.z < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;\n\t\t\t\t\t\ttMax.z = tMax.z < 0 ? -Number.MAX_VALUE : Number.MAX_VALUE;\n\t\t\t\t} else {\n\t\t\t\t\t\ttMin.z /= dir.z;\n\t\t\t\t\t\ttMax.z /= dir.z;\n\t\t\t\t}\n\t\t\t\tconst realMin = tmpVecC.set(Math.min(tMin.x, tMax.x), Math.min(tMin.y, tMax.y), Math.min(tMin.z, tMax.z));\n\t\t\t\tconst realMax = tmpVecD.set(Math.max(tMin.x, tMax.x), Math.max(tMin.y, tMax.y), Math.max(tMin.z, tMax.z));\n\t\t\t\tconst minMax = Math.min(Math.min(realMax.x, realMax.y), realMax.z);\n\t\t\t\tconst maxMin = Math.max(Math.max(realMin.x, realMin.y), realMin.z);\n\t\t\t\tconst intersects = minMax >= maxMin && maxMin >= 0;\n\t\t\t\tif (intersects) {\n\t\t\t\t\t\tpoint.copy(ray.direction).mulScalar(maxMin).add(ray.origin);\n\t\t\t\t}\n\t\t\t\treturn intersects;\n\t\t}\n\t\t_fastIntersectsRay(ray) {\n\t\t\t\tconst diff = tmpVecA$1;\n\t\t\t\tconst cross = tmpVecB$1;\n\t\t\t\tconst prod = tmpVecC;\n\t\t\t\tconst absDiff = tmpVecD;\n\t\t\t\tconst absDir = tmpVecE;\n\t\t\t\tconst rayDir = ray.direction;\n\t\t\t\tdiff.sub2(ray.origin, this.center);\n\t\t\t\tabsDiff.set(Math.abs(diff.x), Math.abs(diff.y), Math.abs(diff.z));\n\t\t\t\tprod.mul2(diff, rayDir);\n\t\t\t\tif (absDiff.x > this.halfExtents.x && prod.x >= 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (absDiff.y > this.halfExtents.y && prod.y >= 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (absDiff.z > this.halfExtents.z && prod.z >= 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tabsDir.set(Math.abs(rayDir.x), Math.abs(rayDir.y), Math.abs(rayDir.z));\n\t\t\t\tcross.cross(rayDir, diff);\n\t\t\t\tcross.set(Math.abs(cross.x), Math.abs(cross.y), Math.abs(cross.z));\n\t\t\t\tif (cross.x > this.halfExtents.y * absDir.z + this.halfExtents.z * absDir.y) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (cross.y > this.halfExtents.x * absDir.z + this.halfExtents.z * absDir.x) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (cross.z > this.halfExtents.x * absDir.y + this.halfExtents.y * absDir.x) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tintersectsRay(ray, point) {\n\t\t\t\tif (point) {\n\t\t\t\t\t\treturn this._intersectsRay(ray, point);\n\t\t\t\t}\n\t\t\t\treturn this._fastIntersectsRay(ray);\n\t\t}\n\t\tsetMinMax(min, max) {\n\t\t\t\tthis.center.add2(max, min).mulScalar(0.5);\n\t\t\t\tthis.halfExtents.sub2(max, min).mulScalar(0.5);\n\t\t}\n\t\tgetMin() {\n\t\t\t\treturn this._min.copy(this.center).sub(this.halfExtents);\n\t\t}\n\t\tgetMax() {\n\t\t\t\treturn this._max.copy(this.center).add(this.halfExtents);\n\t\t}\n\t\tcontainsPoint(point) {\n\t\t\t\tconst min = this.getMin();\n\t\t\t\tconst max = this.getMax();\n\t\t\t\tif (point.x < min.x || point.x > max.x || point.y < min.y || point.y > max.y || point.z < min.z || point.z > max.z) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tsetFromTransformedAabb(aabb, m, ignoreScale = false) {\n\t\t\t\tconst ac = aabb.center;\n\t\t\t\tconst ar = aabb.halfExtents;\n\t\t\t\tconst d = m.data;\n\t\t\t\tlet mx0 = d[0];\n\t\t\t\tlet mx1 = d[4];\n\t\t\t\tlet mx2 = d[8];\n\t\t\t\tlet my0 = d[1];\n\t\t\t\tlet my1 = d[5];\n\t\t\t\tlet my2 = d[9];\n\t\t\t\tlet mz0 = d[2];\n\t\t\t\tlet mz1 = d[6];\n\t\t\t\tlet mz2 = d[10];\n\t\t\t\tif (ignoreScale) {\n\t\t\t\t\t\tlet lengthSq = mx0 * mx0 + mx1 * mx1 + mx2 * mx2;\n\t\t\t\t\t\tif (lengthSq > 0) {\n\t\t\t\t\t\t\t\tconst invLength = 1 / Math.sqrt(lengthSq);\n\t\t\t\t\t\t\t\tmx0 *= invLength;\n\t\t\t\t\t\t\t\tmx1 *= invLength;\n\t\t\t\t\t\t\t\tmx2 *= invLength;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlengthSq = my0 * my0 + my1 * my1 + my2 * my2;\n\t\t\t\t\t\tif (lengthSq > 0) {\n\t\t\t\t\t\t\t\tconst invLength = 1 / Math.sqrt(lengthSq);\n\t\t\t\t\t\t\t\tmy0 *= invLength;\n\t\t\t\t\t\t\t\tmy1 *= invLength;\n\t\t\t\t\t\t\t\tmy2 *= invLength;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlengthSq = mz0 * mz0 + mz1 * mz1 + mz2 * mz2;\n\t\t\t\t\t\tif (lengthSq > 0) {\n\t\t\t\t\t\t\t\tconst invLength = 1 / Math.sqrt(lengthSq);\n\t\t\t\t\t\t\t\tmz0 *= invLength;\n\t\t\t\t\t\t\t\tmz1 *= invLength;\n\t\t\t\t\t\t\t\tmz2 *= invLength;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.center.set(d[12] + mx0 * ac.x + mx1 * ac.y + mx2 * ac.z, d[13] + my0 * ac.x + my1 * ac.y + my2 * ac.z, d[14] + mz0 * ac.x + mz1 * ac.y + mz2 * ac.z);\n\t\t\t\tthis.halfExtents.set(Math.abs(mx0) * ar.x + Math.abs(mx1) * ar.y + Math.abs(mx2) * ar.z, Math.abs(my0) * ar.x + Math.abs(my1) * ar.y + Math.abs(my2) * ar.z, Math.abs(mz0) * ar.x + Math.abs(mz1) * ar.y + Math.abs(mz2) * ar.z);\n\t\t}\n\t\tstatic computeMinMax(vertices, min, max, numVerts = vertices.length / 3) {\n\t\t\t\tif (numVerts > 0) {\n\t\t\t\t\t\tlet minx = vertices[0];\n\t\t\t\t\t\tlet miny = vertices[1];\n\t\t\t\t\t\tlet minz = vertices[2];\n\t\t\t\t\t\tlet maxx = minx;\n\t\t\t\t\t\tlet maxy = miny;\n\t\t\t\t\t\tlet maxz = minz;\n\t\t\t\t\t\tconst n = numVerts * 3;\n\t\t\t\t\t\tfor(let i = 3; i < n; i += 3){\n\t\t\t\t\t\t\t\tconst x = vertices[i];\n\t\t\t\t\t\t\t\tconst y = vertices[i + 1];\n\t\t\t\t\t\t\t\tconst z = vertices[i + 2];\n\t\t\t\t\t\t\t\tif (x < minx) minx = x;\n\t\t\t\t\t\t\t\tif (y < miny) miny = y;\n\t\t\t\t\t\t\t\tif (z < minz) minz = z;\n\t\t\t\t\t\t\t\tif (x > maxx) maxx = x;\n\t\t\t\t\t\t\t\tif (y > maxy) maxy = y;\n\t\t\t\t\t\t\t\tif (z > maxz) maxz = z;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmin.set(minx, miny, minz);\n\t\t\t\t\t\tmax.set(maxx, maxy, maxz);\n\t\t\t\t}\n\t\t}\n\t\tcompute(vertices, numVerts) {\n\t\t\t\tBoundingBox.computeMinMax(vertices, tmpVecA$1, tmpVecB$1, numVerts);\n\t\t\t\tthis.setMinMax(tmpVecA$1, tmpVecB$1);\n\t\t}\n\t\tintersectsBoundingSphere(sphere) {\n\t\t\t\tconst sq = this._distanceToBoundingSphereSq(sphere);\n\t\t\t\tif (sq <= sphere.radius * sphere.radius) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\t_distanceToBoundingSphereSq(sphere) {\n\t\t\t\tconst boxMin = this.getMin();\n\t\t\t\tconst boxMax = this.getMax();\n\t\t\t\tlet sq = 0;\n\t\t\t\tconst axis = [\n\t\t\t\t\t\t'x',\n\t\t\t\t\t\t'y',\n\t\t\t\t\t\t'z'\n\t\t\t\t];\n\t\t\t\tfor(let i = 0; i < 3; ++i){\n\t\t\t\t\t\tlet out = 0;\n\t\t\t\t\t\tconst pn = sphere.center[axis[i]];\n\t\t\t\t\t\tconst bMin = boxMin[axis[i]];\n\t\t\t\t\t\tconst bMax = boxMax[axis[i]];\n\t\t\t\t\t\tlet val = 0;\n\t\t\t\t\t\tif (pn < bMin) {\n\t\t\t\t\t\t\t\tval = bMin - pn;\n\t\t\t\t\t\t\t\tout += val * val;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (pn > bMax) {\n\t\t\t\t\t\t\t\tval = pn - bMax;\n\t\t\t\t\t\t\t\tout += val * val;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsq += out;\n\t\t\t\t}\n\t\t\t\treturn sq;\n\t\t}\n\t\t_expand(expandMin, expandMax) {\n\t\t\t\ttmpVecA$1.add2(this.getMin(), expandMin);\n\t\t\t\ttmpVecB$1.add2(this.getMax(), expandMax);\n\t\t\t\tthis.setMinMax(tmpVecA$1, tmpVecB$1);\n\t\t}\n}\n\nconst tmpVecA = new Vec3();\nconst tmpVecB = new Vec3();\nclass BoundingSphere {\n\t\tconstructor(center = new Vec3(), radius = 0.5){\n\t\t\t\tthis.center = center;\n\t\t\t\tthis.radius = radius;\n\t\t}\n\t\tcontainsPoint(point) {\n\t\t\t\tconst lenSq = tmpVecA.sub2(point, this.center).lengthSq();\n\t\t\t\tconst r = this.radius;\n\t\t\t\treturn lenSq < r * r;\n\t\t}\n\t\tintersectsRay(ray, point) {\n\t\t\t\tconst m = tmpVecA.copy(ray.origin).sub(this.center);\n\t\t\t\tconst b = m.dot(tmpVecB.copy(ray.direction).normalize());\n\t\t\t\tconst c = m.dot(m) - this.radius * this.radius;\n\t\t\t\tif (c > 0 && b > 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst discr = b * b - c;\n\t\t\t\tif (discr < 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst t = Math.abs(-b - Math.sqrt(discr));\n\t\t\t\tif (point) {\n\t\t\t\t\t\tpoint.copy(ray.direction).mulScalar(t).add(ray.origin);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tintersectsBoundingSphere(sphere) {\n\t\t\t\ttmpVecA.sub2(sphere.center, this.center);\n\t\t\t\tconst totalRadius = sphere.radius + this.radius;\n\t\t\t\tif (tmpVecA.lengthSq() <= totalRadius * totalRadius) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n}\n\nclass Plane {\n\t\tconstructor(normal = Vec3.UP, distance = 0){\n\t\t\t\tthis.normal = new Vec3();\n\t\t\t\tthis.normal.copy(normal);\n\t\t\t\tthis.distance = distance;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr().copy(this);\n\t\t}\n\t\tcopy(src) {\n\t\t\t\tthis.normal.copy(src.normal);\n\t\t\t\tthis.distance = src.distance;\n\t\t\t\treturn this;\n\t\t}\n\t\tintersectsLine(start, end, point) {\n\t\t\t\tconst d = this.distance;\n\t\t\t\tconst d0 = this.normal.dot(start) + d;\n\t\t\t\tconst d1 = this.normal.dot(end) + d;\n\t\t\t\tconst t = d0 / (d0 - d1);\n\t\t\t\tconst intersects = t >= 0 && t <= 1;\n\t\t\t\tif (intersects && point) {\n\t\t\t\t\t\tpoint.lerp(start, end, t);\n\t\t\t\t}\n\t\t\t\treturn intersects;\n\t\t}\n\t\tintersectsRay(ray, point) {\n\t\t\t\tconst denominator = this.normal.dot(ray.direction);\n\t\t\t\tif (denominator === 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst t = -(this.normal.dot(ray.origin) + this.distance) / denominator;\n\t\t\t\tif (t >= 0 && point) {\n\t\t\t\t\t\tpoint.copy(ray.direction).mulScalar(t).add(ray.origin);\n\t\t\t\t}\n\t\t\t\treturn t >= 0;\n\t\t}\n\t\tnormalize() {\n\t\t\t\tconst invLength = 1 / this.normal.length();\n\t\t\t\tthis.normal.mulScalar(invLength);\n\t\t\t\tthis.distance *= invLength;\n\t\t\t\treturn this;\n\t\t}\n\t\tset(nx, ny, nz, d) {\n\t\t\t\tthis.normal.set(nx, ny, nz);\n\t\t\t\tthis.distance = d;\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromPointNormal(point, normal) {\n\t\t\t\tthis.normal.copy(normal);\n\t\t\t\tthis.distance = -this.normal.dot(point);\n\t\t\t\treturn this;\n\t\t}\n}\n\nclass Frustum {\n\t\tconstructor(){\n\t\t\t\tthis.planes = [];\n\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\tthis.planes[i] = new Plane();\n\t\t\t\t}\n\t\t}\n\t\tclone() {\n\t\t\t\tconst cstr = this.constructor;\n\t\t\t\treturn new cstr().copy(this);\n\t\t}\n\t\tcopy(src) {\n\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\tthis.planes[i].copy(src.planes[i]);\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tsetFromMat4(matrix) {\n\t\t\t\tconst [m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33] = matrix.data;\n\t\t\t\tconst planes = this.planes;\n\t\t\t\tplanes[0].set(m03 - m00, m13 - m10, m23 - m20, m33 - m30).normalize();\n\t\t\t\tplanes[1].set(m03 + m00, m13 + m10, m23 + m20, m33 + m30).normalize();\n\t\t\t\tplanes[2].set(m03 + m01, m13 + m11, m23 + m21, m33 + m31).normalize();\n\t\t\t\tplanes[3].set(m03 - m01, m13 - m11, m23 - m21, m33 - m31).normalize();\n\t\t\t\tplanes[4].set(m03 - m02, m13 - m12, m23 - m22, m33 - m32).normalize();\n\t\t\t\tplanes[5].set(m03 + m02, m13 + m12, m23 + m22, m33 + m32).normalize();\n\t\t}\n\t\tcontainsPoint(point) {\n\t\t\t\tfor(let p = 0; p < 6; p++){\n\t\t\t\t\t\tconst { normal, distance } = this.planes[p];\n\t\t\t\t\t\tif (normal.dot(point) + distance <= 0) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tcontainsSphere(sphere) {\n\t\t\t\tconst { center, radius } = sphere;\n\t\t\t\tlet c = 0;\n\t\t\t\tfor(let p = 0; p < 6; p++){\n\t\t\t\t\t\tconst { normal, distance } = this.planes[p];\n\t\t\t\t\t\tconst d = normal.dot(center) + distance;\n\t\t\t\t\t\tif (d <= -radius) {\n\t\t\t\t\t\t\t\treturn 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (d > radius) {\n\t\t\t\t\t\t\t\tc++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn c === 6 ? 2 : 1;\n\t\t}\n}\n\nclass Ray {\n\t\tconstructor(origin, direction){\n\t\t\t\tthis.origin = new Vec3();\n\t\t\t\tthis.direction = Vec3.FORWARD.clone();\n\t\t\t\tif (origin) {\n\t\t\t\t\t\tthis.origin.copy(origin);\n\t\t\t\t}\n\t\t\t\tif (direction) {\n\t\t\t\t\t\tthis.direction.copy(direction);\n\t\t\t\t}\n\t\t}\n\t\tset(origin, direction) {\n\t\t\t\tthis.origin.copy(origin);\n\t\t\t\tthis.direction.copy(direction);\n\t\t\t\treturn this;\n\t\t}\n\t\tcopy(src) {\n\t\t\t\treturn this.set(src.origin, src.direction);\n\t\t}\n\t\tclone() {\n\t\t\t\treturn new this.constructor(this.origin, this.direction);\n\t\t}\n}\n\nconst ADDRESS_REPEAT = 0;\nconst ADDRESS_CLAMP_TO_EDGE = 1;\nconst ADDRESS_MIRRORED_REPEAT = 2;\nconst BLENDMODE_ZERO = 0;\nconst BLENDMODE_ONE = 1;\nconst BLENDMODE_SRC_COLOR = 2;\nconst BLENDMODE_DST_COLOR = 4;\nconst BLENDMODE_ONE_MINUS_DST_COLOR = 5;\nconst BLENDMODE_SRC_ALPHA = 6;\nconst BLENDMODE_ONE_MINUS_SRC_ALPHA = 8;\nconst BLENDEQUATION_ADD = 0;\nconst BLENDEQUATION_REVERSE_SUBTRACT = 2;\nconst BLENDEQUATION_MIN = 3;\nconst BLENDEQUATION_MAX = 4;\nconst BUFFERUSAGE_READ = 0x0001;\nconst BUFFERUSAGE_COPY_DST = 0x0008;\nconst BUFFERUSAGE_INDEX = 0x0010;\nconst BUFFERUSAGE_VERTEX = 0x0020;\nconst BUFFERUSAGE_UNIFORM = 0x0040;\nconst BUFFERUSAGE_STORAGE = 0x0080;\nconst BUFFERUSAGE_INDIRECT = 0x0100;\nconst BUFFER_STATIC = 0;\nconst BUFFER_DYNAMIC = 1;\nconst BUFFER_STREAM = 2;\nconst BUFFER_GPUDYNAMIC = 3;\nconst CLEARFLAG_COLOR = 1;\nconst CLEARFLAG_DEPTH = 2;\nconst CLEARFLAG_STENCIL = 4;\nconst CULLFACE_NONE = 0;\nconst CULLFACE_BACK = 1;\nconst CULLFACE_FRONT = 2;\nconst CULLFACE_FRONTANDBACK = 3;\nconst FILTER_NEAREST = 0;\nconst FILTER_LINEAR = 1;\nconst FILTER_NEAREST_MIPMAP_NEAREST = 2;\nconst FILTER_NEAREST_MIPMAP_LINEAR = 3;\nconst FILTER_LINEAR_MIPMAP_NEAREST = 4;\nconst FILTER_LINEAR_MIPMAP_LINEAR = 5;\nconst FUNC_NEVER = 0;\nconst FUNC_LESS = 1;\nconst FUNC_EQUAL = 2;\nconst FUNC_LESSEQUAL = 3;\nconst FUNC_GREATER = 4;\nconst FUNC_NOTEQUAL = 5;\nconst FUNC_GREATEREQUAL = 6;\nconst FUNC_ALWAYS = 7;\nconst INDEXFORMAT_UINT8 = 0;\nconst INDEXFORMAT_UINT16 = 1;\nconst INDEXFORMAT_UINT32 = 2;\nconst PIXELFORMAT_A8 = 0;\nconst PIXELFORMAT_L8 = 1;\nconst PIXELFORMAT_LA8 = 2;\nconst PIXELFORMAT_RGB565 = 3;\nconst PIXELFORMAT_RGBA5551 = 4;\nconst PIXELFORMAT_RGBA4 = 5;\nconst PIXELFORMAT_RGB8 = 6;\nconst PIXELFORMAT_RGBA8 = 7;\nconst PIXELFORMAT_DXT1 = 8;\nconst PIXELFORMAT_DXT3 = 9;\nconst PIXELFORMAT_DXT5 = 10;\nconst PIXELFORMAT_RGB16F = 11;\nconst PIXELFORMAT_RGBA16F = 12;\nconst PIXELFORMAT_RGB32F = 13;\nconst PIXELFORMAT_RGBA32F = 14;\nconst PIXELFORMAT_R32F = 15;\nconst PIXELFORMAT_DEPTH = 16;\nconst PIXELFORMAT_DEPTHSTENCIL = 17;\nconst PIXELFORMAT_111110F = 18;\nconst PIXELFORMAT_SRGB8 = 19;\nconst PIXELFORMAT_SRGBA8 = 20;\nconst PIXELFORMAT_ETC1 = 21;\nconst PIXELFORMAT_ETC2_RGB = 22;\nconst PIXELFORMAT_ETC2_RGBA = 23;\nconst PIXELFORMAT_PVRTC_2BPP_RGB_1 = 24;\nconst PIXELFORMAT_PVRTC_2BPP_RGBA_1 = 25;\nconst PIXELFORMAT_PVRTC_4BPP_RGB_1 = 26;\nconst PIXELFORMAT_PVRTC_4BPP_RGBA_1 = 27;\nconst PIXELFORMAT_ASTC_4x4 = 28;\nconst PIXELFORMAT_ATC_RGB = 29;\nconst PIXELFORMAT_ATC_RGBA = 30;\nconst PIXELFORMAT_BGRA8 = 31;\nconst PIXELFORMAT_R8I = 32;\nconst PIXELFORMAT_R8U = 33;\nconst PIXELFORMAT_R16I = 34;\nconst PIXELFORMAT_R16U = 35;\nconst PIXELFORMAT_R32I = 36;\nconst PIXELFORMAT_R32U = 37;\nconst PIXELFORMAT_RG8I = 38;\nconst PIXELFORMAT_RG8U = 39;\nconst PIXELFORMAT_RG16I = 40;\nconst PIXELFORMAT_RG16U = 41;\nconst PIXELFORMAT_RG32I = 42;\nconst PIXELFORMAT_RG32U = 43;\nconst PIXELFORMAT_RGBA8I = 44;\nconst PIXELFORMAT_RGBA8U = 45;\nconst PIXELFORMAT_RGBA16I = 46;\nconst PIXELFORMAT_RGBA16U = 47;\nconst PIXELFORMAT_RGBA32I = 48;\nconst PIXELFORMAT_RGBA32U = 49;\nconst PIXELFORMAT_R16F = 50;\nconst PIXELFORMAT_RG16F = 51;\nconst PIXELFORMAT_R8 = 52;\nconst PIXELFORMAT_RG8 = 53;\nconst PIXELFORMAT_DXT1_SRGB = 54;\nconst PIXELFORMAT_DXT3_SRGBA = 55;\nconst PIXELFORMAT_DXT5_SRGBA = 56;\nconst PIXELFORMAT_ETC2_SRGB = 61;\nconst PIXELFORMAT_ETC2_SRGBA = 62;\nconst PIXELFORMAT_ASTC_4x4_SRGB = 63;\nconst PIXELFORMAT_SBGRA8 = 64;\nconst PIXELFORMAT_BC6F = 65;\nconst PIXELFORMAT_BC6UF = 66;\nconst PIXELFORMAT_BC7 = 67;\nconst PIXELFORMAT_BC7_SRGBA = 68;\nconst PIXELFORMAT_DEPTH16 = 69;\nconst pixelFormatInfo = new Map([\n\t\t[\n\t\t\t\tPIXELFORMAT_A8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'A8',\n\t\t\t\t\t\tsize: 1,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R8',\n\t\t\t\t\t\tsize: 1,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_L8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'L8',\n\t\t\t\t\t\tsize: 1,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_LA8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'LA8',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG8',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGB565,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGB565',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA5551,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA5551',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA4,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA4',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGB8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGB8',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA8',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_SRGBA8\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R16F',\n\t\t\t\t\t\tsize: 2\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG16F',\n\t\t\t\t\t\tsize: 4\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGB16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGB16F',\n\t\t\t\t\t\tsize: 8\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA16F',\n\t\t\t\t\t\tsize: 8\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGB32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGB32F',\n\t\t\t\t\t\tsize: 16\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA32F',\n\t\t\t\t\t\tsize: 16\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R32F',\n\t\t\t\t\t\tsize: 4\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DEPTH,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DEPTH',\n\t\t\t\t\t\tsize: 4\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DEPTH16,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DEPTH16',\n\t\t\t\t\t\tsize: 2\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DEPTHSTENCIL,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DEPTHSTENCIL',\n\t\t\t\t\t\tsize: 4\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_111110F,\n\t\t\t\t{\n\t\t\t\t\t\tname: '111110F',\n\t\t\t\t\t\tsize: 4\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_SRGB8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'SRGB8',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_SRGBA8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'SRGBA8',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_BGRA8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'BGRA8',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_SBGRA8,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'SBGRA8',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DXT1,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DXT1',\n\t\t\t\t\t\tblockSize: 8,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_DXT1_SRGB\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DXT3,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DXT3',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_DXT3_SRGBA\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DXT5,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DXT5',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_DXT5_SRGBA\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ETC1,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ETC1',\n\t\t\t\t\t\tblockSize: 8,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ETC2_RGB,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ETC2_RGB',\n\t\t\t\t\t\tblockSize: 8,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_ETC2_SRGB\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ETC2_RGBA,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ETC2_RGBA',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_ETC2_SRGBA\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_PVRTC_2BPP_RGB_1,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PVRTC_2BPP_RGB_1',\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tblockSize: 8\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_PVRTC_2BPP_RGBA_1,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PVRTC_2BPP_RGBA_1',\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tblockSize: 8\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_PVRTC_4BPP_RGB_1,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PVRTC_4BPP_RGB_1',\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tblockSize: 8\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_PVRTC_4BPP_RGBA_1,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PVRTC_4BPP_RGBA_1',\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tblockSize: 8\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ASTC_4x4,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ASTC_4x4',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_ASTC_4x4_SRGB\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ATC_RGB,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ATC_RGB',\n\t\t\t\t\t\tblockSize: 8,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ATC_RGBA,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ATC_RGBA',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_BC6F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'BC6H_RGBF',\n\t\t\t\t\t\tblockSize: 16\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_BC6UF,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'BC6H_RGBUF',\n\t\t\t\t\t\tblockSize: 16\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_BC7,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'BC7_RGBA',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgbFormat: PIXELFORMAT_BC7_SRGBA\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DXT1_SRGB,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DXT1_SRGB',\n\t\t\t\t\t\tblockSize: 8,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DXT3_SRGBA,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DXT3_SRGBA',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_DXT5_SRGBA,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'DXT5_SRGBA',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ETC2_SRGB,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ETC2_SRGB',\n\t\t\t\t\t\tblockSize: 8,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ETC2_SRGBA,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ETC2_SRGBA',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_ASTC_4x4_SRGB,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'ASTC_4x4_SRGB',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_BC7_SRGBA,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'BC7_SRGBA',\n\t\t\t\t\t\tblockSize: 16,\n\t\t\t\t\t\tldr: true,\n\t\t\t\t\t\tsrgb: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R8I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R8I',\n\t\t\t\t\t\tsize: 1,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R8U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R8U',\n\t\t\t\t\t\tsize: 1,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R16I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R16I',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R16U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R16U',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R32I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R32I',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_R32U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'R32U',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG8I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG8I',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG8U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG8U',\n\t\t\t\t\t\tsize: 2,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG16I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG16I',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG16U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG16U',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG32I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG32I',\n\t\t\t\t\t\tsize: 8,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RG32U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RG32U',\n\t\t\t\t\t\tsize: 8,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA8I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA8I',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA8U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA8U',\n\t\t\t\t\t\tsize: 4,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA16I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA16I',\n\t\t\t\t\t\tsize: 8,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA16U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA16U',\n\t\t\t\t\t\tsize: 8,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA32I,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA32I',\n\t\t\t\t\t\tsize: 16,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tPIXELFORMAT_RGBA32U,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'RGBA32U',\n\t\t\t\t\t\tsize: 16,\n\t\t\t\t\t\tisInt: true\n\t\t\t\t}\n\t\t]\n]);\nconst isCompressedPixelFormat = (format)=>{\n\t\treturn pixelFormatInfo.get(format)?.blockSize !== undefined;\n};\nconst isSrgbPixelFormat = (format)=>{\n\t\treturn pixelFormatInfo.get(format)?.srgb === true;\n};\nconst isIntegerPixelFormat = (format)=>{\n\t\treturn pixelFormatInfo.get(format)?.isInt === true;\n};\nconst pixelFormatLinearToGamma = (format)=>{\n\t\treturn pixelFormatInfo.get(format)?.srgbFormat || format;\n};\nconst pixelFormatGammaToLinear = (format)=>{\n\t\tfor (const [key, value] of pixelFormatInfo){\n\t\t\t\tif (value.srgbFormat === format) {\n\t\t\t\t\t\treturn key;\n\t\t\t\t}\n\t\t}\n\t\treturn format;\n};\nconst requiresManualGamma = (format)=>{\n\t\tconst info = pixelFormatInfo.get(format);\n\t\treturn !!(info?.ldr && !info?.srgb);\n};\nconst getPixelFormatArrayType = (format)=>{\n\t\tswitch(format){\n\t\t\t\tcase PIXELFORMAT_R32F:\n\t\t\t\tcase PIXELFORMAT_RGB32F:\n\t\t\t\tcase PIXELFORMAT_RGBA32F:\n\t\t\t\t\t\treturn Float32Array;\n\t\t\t\tcase PIXELFORMAT_R32I:\n\t\t\t\tcase PIXELFORMAT_RG32I:\n\t\t\t\tcase PIXELFORMAT_RGBA32I:\n\t\t\t\t\t\treturn Int32Array;\n\t\t\t\tcase PIXELFORMAT_R32U:\n\t\t\t\tcase PIXELFORMAT_RG32U:\n\t\t\t\tcase PIXELFORMAT_RGBA32U:\n\t\t\t\t\t\treturn Uint32Array;\n\t\t\t\tcase PIXELFORMAT_R16I:\n\t\t\t\tcase PIXELFORMAT_RG16I:\n\t\t\t\tcase PIXELFORMAT_RGBA16I:\n\t\t\t\t\t\treturn Int16Array;\n\t\t\t\tcase PIXELFORMAT_RG8:\n\t\t\t\tcase PIXELFORMAT_R16U:\n\t\t\t\tcase PIXELFORMAT_RG16U:\n\t\t\t\tcase PIXELFORMAT_RGBA16U:\n\t\t\t\tcase PIXELFORMAT_RGB565:\n\t\t\t\tcase PIXELFORMAT_RGBA5551:\n\t\t\t\tcase PIXELFORMAT_RGBA4:\n\t\t\t\tcase PIXELFORMAT_R16F:\n\t\t\t\tcase PIXELFORMAT_RG16F:\n\t\t\t\tcase PIXELFORMAT_RGB16F:\n\t\t\t\tcase PIXELFORMAT_RGBA16F:\n\t\t\t\t\t\treturn Uint16Array;\n\t\t\t\tcase PIXELFORMAT_R8I:\n\t\t\t\tcase PIXELFORMAT_RG8I:\n\t\t\t\tcase PIXELFORMAT_RGBA8I:\n\t\t\t\t\t\treturn Int8Array;\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn Uint8Array;\n\t\t}\n};\nconst PRIMITIVE_POINTS = 0;\nconst PRIMITIVE_LINES = 1;\nconst PRIMITIVE_LINELOOP = 2;\nconst PRIMITIVE_LINESTRIP = 3;\nconst PRIMITIVE_TRIANGLES = 4;\nconst PRIMITIVE_TRISTRIP = 5;\nconst PRIMITIVE_TRIFAN = 6;\nconst SEMANTIC_POSITION = 'POSITION';\nconst SEMANTIC_NORMAL = 'NORMAL';\nconst SEMANTIC_TANGENT = 'TANGENT';\nconst SEMANTIC_BLENDWEIGHT = 'BLENDWEIGHT';\nconst SEMANTIC_BLENDINDICES = 'BLENDINDICES';\nconst SEMANTIC_COLOR = 'COLOR';\nconst SEMANTIC_TEXCOORD = 'TEXCOORD';\nconst SEMANTIC_TEXCOORD0 = 'TEXCOORD0';\nconst SEMANTIC_TEXCOORD1 = 'TEXCOORD1';\nconst SEMANTIC_TEXCOORD2 = 'TEXCOORD2';\nconst SEMANTIC_TEXCOORD3 = 'TEXCOORD3';\nconst SEMANTIC_TEXCOORD4 = 'TEXCOORD4';\nconst SEMANTIC_TEXCOORD5 = 'TEXCOORD5';\nconst SEMANTIC_TEXCOORD6 = 'TEXCOORD6';\nconst SEMANTIC_TEXCOORD7 = 'TEXCOORD7';\nconst SEMANTIC_ATTR0 = 'ATTR0';\nconst SEMANTIC_ATTR1 = 'ATTR1';\nconst SEMANTIC_ATTR2 = 'ATTR2';\nconst SEMANTIC_ATTR3 = 'ATTR3';\nconst SEMANTIC_ATTR4 = 'ATTR4';\nconst SEMANTIC_ATTR5 = 'ATTR5';\nconst SEMANTIC_ATTR6 = 'ATTR6';\nconst SEMANTIC_ATTR7 = 'ATTR7';\nconst SEMANTIC_ATTR8 = 'ATTR8';\nconst SEMANTIC_ATTR9 = 'ATTR9';\nconst SEMANTIC_ATTR10 = 'ATTR10';\nconst SEMANTIC_ATTR11 = 'ATTR11';\nconst SEMANTIC_ATTR12 = 'ATTR12';\nconst SEMANTIC_ATTR13 = 'ATTR13';\nconst SEMANTIC_ATTR14 = 'ATTR14';\nconst SEMANTIC_ATTR15 = 'ATTR15';\nconst SHADERTAG_MATERIAL = 1;\nconst STENCILOP_KEEP = 0;\nconst STENCILOP_REPLACE = 2;\nconst STENCILOP_INCREMENT = 3;\nconst STENCILOP_DECREMENT = 5;\nconst TEXTURELOCK_NONE = 0;\nconst TEXTURELOCK_READ = 1;\nconst TEXTURELOCK_WRITE = 2;\nconst TEXTURETYPE_DEFAULT = 'default';\nconst TEXTURETYPE_RGBM = 'rgbm';\nconst TEXTURETYPE_RGBE = 'rgbe';\nconst TEXTURETYPE_RGBP = 'rgbp';\nconst TEXTURETYPE_SWIZZLEGGGR = 'swizzleGGGR';\nconst TEXTUREDIMENSION_1D = '1d';\nconst TEXTUREDIMENSION_2D = '2d';\nconst TEXTUREDIMENSION_2D_ARRAY = '2d-array';\nconst TEXTUREDIMENSION_CUBE = 'cube';\nconst TEXTUREDIMENSION_CUBE_ARRAY = 'cube-array';\nconst TEXTUREDIMENSION_3D = '3d';\nconst SAMPLETYPE_FLOAT = 0;\nconst SAMPLETYPE_UNFILTERABLE_FLOAT = 1;\nconst SAMPLETYPE_DEPTH = 2;\nconst SAMPLETYPE_INT = 3;\nconst SAMPLETYPE_UINT = 4;\nconst TEXTUREPROJECTION_NONE = 'none';\nconst TEXTUREPROJECTION_CUBE = 'cube';\nconst TEXTUREPROJECTION_EQUIRECT = 'equirect';\nconst TEXTUREPROJECTION_OCTAHEDRAL = 'octahedral';\nconst SHADERLANGUAGE_GLSL = 'glsl';\nconst SHADERLANGUAGE_WGSL = 'wgsl';\nconst TYPE_INT8 = 0;\nconst TYPE_UINT8 = 1;\nconst TYPE_INT16 = 2;\nconst TYPE_UINT16 = 3;\nconst TYPE_INT32 = 4;\nconst TYPE_UINT32 = 5;\nconst TYPE_FLOAT32 = 6;\nconst TYPE_FLOAT16 = 7;\nconst UNIFORMTYPE_BOOL = 0;\nconst UNIFORMTYPE_INT = 1;\nconst UNIFORMTYPE_FLOAT = 2;\nconst UNIFORMTYPE_VEC2 = 3;\nconst UNIFORMTYPE_VEC3 = 4;\nconst UNIFORMTYPE_VEC4 = 5;\nconst UNIFORMTYPE_IVEC2 = 6;\nconst UNIFORMTYPE_IVEC3 = 7;\nconst UNIFORMTYPE_IVEC4 = 8;\nconst UNIFORMTYPE_BVEC2 = 9;\nconst UNIFORMTYPE_BVEC3 = 10;\nconst UNIFORMTYPE_BVEC4 = 11;\nconst UNIFORMTYPE_MAT2 = 12;\nconst UNIFORMTYPE_MAT3 = 13;\nconst UNIFORMTYPE_MAT4 = 14;\nconst UNIFORMTYPE_TEXTURE2D = 15;\nconst UNIFORMTYPE_TEXTURECUBE = 16;\nconst UNIFORMTYPE_FLOATARRAY = 17;\nconst UNIFORMTYPE_TEXTURE2D_SHADOW = 18;\nconst UNIFORMTYPE_TEXTURECUBE_SHADOW = 19;\nconst UNIFORMTYPE_TEXTURE3D = 20;\nconst UNIFORMTYPE_VEC2ARRAY = 21;\nconst UNIFORMTYPE_VEC3ARRAY = 22;\nconst UNIFORMTYPE_VEC4ARRAY = 23;\nconst UNIFORMTYPE_MAT4ARRAY = 24;\nconst UNIFORMTYPE_TEXTURE2D_ARRAY = 25;\nconst UNIFORMTYPE_UINT = 26;\nconst UNIFORMTYPE_UVEC2 = 27;\nconst UNIFORMTYPE_UVEC3 = 28;\nconst UNIFORMTYPE_UVEC4 = 29;\nconst UNIFORMTYPE_INTARRAY = 30;\nconst UNIFORMTYPE_UINTARRAY = 31;\nconst UNIFORMTYPE_BOOLARRAY = 32;\nconst UNIFORMTYPE_IVEC2ARRAY = 33;\nconst UNIFORMTYPE_UVEC2ARRAY = 34;\nconst UNIFORMTYPE_BVEC2ARRAY = 35;\nconst UNIFORMTYPE_IVEC3ARRAY = 36;\nconst UNIFORMTYPE_UVEC3ARRAY = 37;\nconst UNIFORMTYPE_BVEC3ARRAY = 38;\nconst UNIFORMTYPE_IVEC4ARRAY = 39;\nconst UNIFORMTYPE_UVEC4ARRAY = 40;\nconst UNIFORMTYPE_BVEC4ARRAY = 41;\nconst UNIFORMTYPE_ITEXTURE2D = 42;\nconst UNIFORMTYPE_UTEXTURE2D = 43;\nconst UNIFORMTYPE_ITEXTURECUBE = 44;\nconst UNIFORMTYPE_UTEXTURECUBE = 45;\nconst UNIFORMTYPE_ITEXTURE3D = 46;\nconst UNIFORMTYPE_UTEXTURE3D = 47;\nconst UNIFORMTYPE_ITEXTURE2D_ARRAY = 48;\nconst UNIFORMTYPE_UTEXTURE2D_ARRAY = 49;\nconst uniformTypeToName = [\n\t\t'bool',\n\t\t'int',\n\t\t'float',\n\t\t'vec2',\n\t\t'vec3',\n\t\t'vec4',\n\t\t'ivec2',\n\t\t'ivec3',\n\t\t'ivec4',\n\t\t'bvec2',\n\t\t'bvec3',\n\t\t'bvec4',\n\t\t'mat2',\n\t\t'mat3',\n\t\t'mat4',\n\t\t'sampler2D',\n\t\t'samplerCube',\n\t\t'',\n\t\t'sampler2DShadow',\n\t\t'samplerCubeShadow',\n\t\t'sampler3D',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'sampler2DArray',\n\t\t'uint',\n\t\t'uvec2',\n\t\t'uvec3',\n\t\t'uvec4',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'',\n\t\t'isampler2D',\n\t\t'usampler2D',\n\t\t'isamplerCube',\n\t\t'usamplerCube',\n\t\t'isampler3D',\n\t\t'usampler3D',\n\t\t'isampler2DArray',\n\t\t'usampler2DArray'\n];\nconst uniformTypeToNameWGSL = [\n\t\t[\n\t\t\t\t'bool'\n\t\t],\n\t\t[\n\t\t\t\t'i32'\n\t\t],\n\t\t[\n\t\t\t\t'f32'\n\t\t],\n\t\t[\n\t\t\t\t'vec2f',\n\t\t\t\t'vec2<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec3f',\n\t\t\t\t'vec3<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec4f',\n\t\t\t\t'vec4<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec2i',\n\t\t\t\t'vec2<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec3i',\n\t\t\t\t'vec3<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec4i',\n\t\t\t\t'vec4<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec2<bool>'\n\t\t],\n\t\t[\n\t\t\t\t'vec3<bool>'\n\t\t],\n\t\t[\n\t\t\t\t'vec4<bool>'\n\t\t],\n\t\t[\n\t\t\t\t'mat2x2f',\n\t\t\t\t'mat2x2<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'mat3x3f',\n\t\t\t\t'mat3x3<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'mat4x4f',\n\t\t\t\t'mat4x4<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_2d<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_cube<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'array<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_depth_2d'\n\t\t],\n\t\t[\n\t\t\t\t'texture_depth_cube'\n\t\t],\n\t\t[\n\t\t\t\t'texture_3d<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec2<f32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec3<f32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec4<f32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<mat4x4<f32>>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_2d_array<f32>'\n\t\t],\n\t\t[\n\t\t\t\t'u32'\n\t\t],\n\t\t[\n\t\t\t\t'vec2u',\n\t\t\t\t'vec2<u32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec3u',\n\t\t\t\t'vec3<u32>'\n\t\t],\n\t\t[\n\t\t\t\t'vec4u',\n\t\t\t\t'vec4<u32>'\n\t\t],\n\t\t[\n\t\t\t\t'array<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'array<u32>'\n\t\t],\n\t\t[\n\t\t\t\t'array<bool>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec2i>',\n\t\t\t\t'array<vec2<i32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec2u>',\n\t\t\t\t'array<vec2<u32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec2b>',\n\t\t\t\t'array<vec2<bool>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec3i>',\n\t\t\t\t'array<vec3<i32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec3u>',\n\t\t\t\t'array<vec3<u32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec3b>',\n\t\t\t\t'array<vec3<bool>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec4i>',\n\t\t\t\t'array<vec4<i32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec4u>',\n\t\t\t\t'array<vec4<u32>>'\n\t\t],\n\t\t[\n\t\t\t\t'array<vec4b>',\n\t\t\t\t'array<vec4<bool>>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_2d<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_2d<u32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_cube<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_cube<u32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_3d<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_3d<u32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_2d_array<i32>'\n\t\t],\n\t\t[\n\t\t\t\t'texture_2d_array<u32>'\n\t\t]\n];\nconst uniformTypeToNameMapWGSL = new Map();\nuniformTypeToNameWGSL.forEach((names, index)=>{\n\t\tnames.forEach((name)=>uniformTypeToNameMapWGSL.set(name, index));\n});\nconst DEVICETYPE_WEBGL2 = 'webgl2';\nconst DEVICETYPE_WEBGPU = 'webgpu';\nconst DEVICETYPE_NULL = 'null';\nconst SHADERSTAGE_VERTEX = 1;\nconst SHADERSTAGE_FRAGMENT = 2;\nconst SHADERSTAGE_COMPUTE = 4;\nconst DISPLAYFORMAT_LDR = 'ldr';\nconst DISPLAYFORMAT_LDR_SRGB = 'ldr_srgb';\nconst DISPLAYFORMAT_HDR = 'hdr';\nconst TEXPROPERTY_MIN_FILTER = 1;\nconst TEXPROPERTY_MAG_FILTER = 2;\nconst TEXPROPERTY_ADDRESS_U = 4;\nconst TEXPROPERTY_ADDRESS_V = 8;\nconst TEXPROPERTY_ADDRESS_W = 16;\nconst TEXPROPERTY_COMPARE_ON_READ = 32;\nconst TEXPROPERTY_COMPARE_FUNC = 64;\nconst TEXPROPERTY_ANISOTROPY = 128;\nconst TEXPROPERTY_ALL = 255;\nconst BINDGROUP_VIEW = 0;\nconst BINDGROUP_MESH = 1;\nconst BINDGROUP_MESH_UB = 2;\nconst bindGroupNames = [\n\t\t'view',\n\t\t'mesh',\n\t\t'mesh_ub'\n];\nconst UNIFORM_BUFFER_DEFAULT_SLOT_NAME = 'default';\nconst UNUSED_UNIFORM_NAME = '_unused_float_uniform';\nconst typedArrayTypes = [\n\t\tInt8Array,\n\t\tUint8Array,\n\t\tInt16Array,\n\t\tUint16Array,\n\t\tInt32Array,\n\t\tUint32Array,\n\t\tFloat32Array,\n\t\tUint16Array\n];\nconst typedArrayTypesByteSize = [\n\t\t1,\n\t\t1,\n\t\t2,\n\t\t2,\n\t\t4,\n\t\t4,\n\t\t4,\n\t\t2\n];\nconst typedArrayIndexFormats = [\n\t\tUint8Array,\n\t\tUint16Array,\n\t\tUint32Array\n];\nconst typedArrayIndexFormatsByteSize = [\n\t\t1,\n\t\t2,\n\t\t4\n];\nconst primitiveGlslToWgslTypeMap = new Map([\n\t\t[\n\t\t\t\t'float',\n\t\t\t\t'f32'\n\t\t],\n\t\t[\n\t\t\t\t'vec2',\n\t\t\t\t'vec2f'\n\t\t],\n\t\t[\n\t\t\t\t'vec3',\n\t\t\t\t'vec3f'\n\t\t],\n\t\t[\n\t\t\t\t'vec4',\n\t\t\t\t'vec4f'\n\t\t],\n\t\t[\n\t\t\t\t'int',\n\t\t\t\t'i32'\n\t\t],\n\t\t[\n\t\t\t\t'ivec2',\n\t\t\t\t'vec2i'\n\t\t],\n\t\t[\n\t\t\t\t'ivec3',\n\t\t\t\t'vec3i'\n\t\t],\n\t\t[\n\t\t\t\t'ivec4',\n\t\t\t\t'vec4i'\n\t\t],\n\t\t[\n\t\t\t\t'uint',\n\t\t\t\t'u32'\n\t\t],\n\t\t[\n\t\t\t\t'uvec2',\n\t\t\t\t'vec2u'\n\t\t],\n\t\t[\n\t\t\t\t'uvec3',\n\t\t\t\t'vec3u'\n\t\t],\n\t\t[\n\t\t\t\t'uvec4',\n\t\t\t\t'vec4u'\n\t\t]\n]);\nconst semanticToLocation = {};\nsemanticToLocation[SEMANTIC_POSITION] = 0;\nsemanticToLocation[SEMANTIC_NORMAL] = 1;\nsemanticToLocation[SEMANTIC_BLENDWEIGHT] = 2;\nsemanticToLocation[SEMANTIC_BLENDINDICES] = 3;\nsemanticToLocation[SEMANTIC_COLOR] = 4;\nsemanticToLocation[SEMANTIC_TEXCOORD0] = 5;\nsemanticToLocation[SEMANTIC_TEXCOORD1] = 6;\nsemanticToLocation[SEMANTIC_TEXCOORD2] = 7;\nsemanticToLocation[SEMANTIC_TEXCOORD3] = 8;\nsemanticToLocation[SEMANTIC_TEXCOORD4] = 9;\nsemanticToLocation[SEMANTIC_TEXCOORD5] = 10;\nsemanticToLocation[SEMANTIC_TEXCOORD6] = 11;\nsemanticToLocation[SEMANTIC_TEXCOORD7] = 12;\nsemanticToLocation[SEMANTIC_TANGENT] = 13;\nsemanticToLocation[SEMANTIC_ATTR0] = 0;\nsemanticToLocation[SEMANTIC_ATTR1] = 1;\nsemanticToLocation[SEMANTIC_ATTR2] = 2;\nsemanticToLocation[SEMANTIC_ATTR3] = 3;\nsemanticToLocation[SEMANTIC_ATTR4] = 4;\nsemanticToLocation[SEMANTIC_ATTR5] = 5;\nsemanticToLocation[SEMANTIC_ATTR6] = 6;\nsemanticToLocation[SEMANTIC_ATTR7] = 7;\nsemanticToLocation[SEMANTIC_ATTR8] = 8;\nsemanticToLocation[SEMANTIC_ATTR9] = 9;\nsemanticToLocation[SEMANTIC_ATTR10] = 10;\nsemanticToLocation[SEMANTIC_ATTR11] = 11;\nsemanticToLocation[SEMANTIC_ATTR12] = 12;\nsemanticToLocation[SEMANTIC_ATTR13] = 13;\nsemanticToLocation[SEMANTIC_ATTR14] = 14;\nsemanticToLocation[SEMANTIC_ATTR15] = 15;\n\nlet id$d = 0;\nclass BindBaseFormat {\n\t\tconstructor(name, visibility){\n\t\t\t\tthis.slot = -1;\n\t\t\t\tthis.scopeId = null;\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.visibility = visibility;\n\t\t}\n}\nclass BindUniformBufferFormat extends BindBaseFormat {\n}\nclass BindStorageBufferFormat extends BindBaseFormat {\n\t\tconstructor(name, visibility, readOnly = false){\n\t\t\t\tsuper(name, visibility), this.format = '';\n\t\t\t\tthis.readOnly = readOnly;\n\t\t}\n}\nclass BindTextureFormat extends BindBaseFormat {\n\t\tconstructor(name, visibility, textureDimension = TEXTUREDIMENSION_2D, sampleType = SAMPLETYPE_FLOAT, hasSampler = true, samplerName = null){\n\t\t\t\tsuper(name, visibility);\n\t\t\t\tthis.textureDimension = textureDimension;\n\t\t\t\tthis.sampleType = sampleType;\n\t\t\t\tthis.hasSampler = hasSampler;\n\t\t\t\tthis.samplerName = samplerName ?? `${name}_sampler`;\n\t\t}\n}\nclass BindStorageTextureFormat extends BindBaseFormat {\n\t\tconstructor(name, format = PIXELFORMAT_RGBA8, textureDimension = TEXTUREDIMENSION_2D, write = true, read = false){\n\t\t\t\tsuper(name, SHADERSTAGE_COMPUTE);\n\t\t\t\tthis.format = format;\n\t\t\t\tthis.textureDimension = textureDimension;\n\t\t\t\tthis.write = write;\n\t\t\t\tthis.read = read;\n\t\t}\n}\nclass BindGroupFormat {\n\t\tconstructor(graphicsDevice, formats){\n\t\t\t\tthis.uniformBufferFormats = [];\n\t\t\t\tthis.textureFormats = [];\n\t\t\t\tthis.storageTextureFormats = [];\n\t\t\t\tthis.storageBufferFormats = [];\n\t\t\t\tthis.id = id$d++;\n\t\t\t\tlet slot = 0;\n\t\t\t\tformats.forEach((format)=>{\n\t\t\t\t\t\tformat.slot = slot++;\n\t\t\t\t\t\tif (format instanceof BindTextureFormat && format.hasSampler) {\n\t\t\t\t\t\t\t\tslot++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (format instanceof BindUniformBufferFormat) {\n\t\t\t\t\t\t\t\tthis.uniformBufferFormats.push(format);\n\t\t\t\t\t\t} else if (format instanceof BindTextureFormat) {\n\t\t\t\t\t\t\t\tthis.textureFormats.push(format);\n\t\t\t\t\t\t} else if (format instanceof BindStorageTextureFormat) {\n\t\t\t\t\t\t\t\tthis.storageTextureFormats.push(format);\n\t\t\t\t\t\t} else if (format instanceof BindStorageBufferFormat) {\n\t\t\t\t\t\t\t\tthis.storageBufferFormats.push(format);\n\t\t\t\t\t\t} else ;\n\t\t\t\t});\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tconst scope = graphicsDevice.scope;\n\t\t\t\tthis.bufferFormatsMap = new Map();\n\t\t\t\tthis.uniformBufferFormats.forEach((bf, i)=>this.bufferFormatsMap.set(bf.name, i));\n\t\t\t\tthis.textureFormatsMap = new Map();\n\t\t\t\tthis.textureFormats.forEach((tf, i)=>{\n\t\t\t\t\t\tthis.textureFormatsMap.set(tf.name, i);\n\t\t\t\t\t\ttf.scopeId = scope.resolve(tf.name);\n\t\t\t\t});\n\t\t\t\tthis.storageTextureFormatsMap = new Map();\n\t\t\t\tthis.storageTextureFormats.forEach((tf, i)=>{\n\t\t\t\t\t\tthis.storageTextureFormatsMap.set(tf.name, i);\n\t\t\t\t\t\ttf.scopeId = scope.resolve(tf.name);\n\t\t\t\t});\n\t\t\t\tthis.storageBufferFormatsMap = new Map();\n\t\t\t\tthis.storageBufferFormats.forEach((bf, i)=>{\n\t\t\t\t\t\tthis.storageBufferFormatsMap.set(bf.name, i);\n\t\t\t\t\t\tbf.scopeId = scope.resolve(bf.name);\n\t\t\t\t});\n\t\t\t\tthis.impl = graphicsDevice.createBindGroupFormatImpl(this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.impl.destroy();\n\t\t}\n\t\tgetTexture(name) {\n\t\t\t\tconst index = this.textureFormatsMap.get(name);\n\t\t\t\tif (index !== undefined) {\n\t\t\t\t\t\treturn this.textureFormats[index];\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tgetStorageTexture(name) {\n\t\t\t\tconst index = this.storageTextureFormatsMap.get(name);\n\t\t\t\tif (index !== undefined) {\n\t\t\t\t\t\treturn this.storageTextureFormats[index];\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tloseContext() {}\n}\n\nclass DeviceCache {\n\t\tget(device, onCreate) {\n\t\t\t\tif (!this._cache.has(device)) {\n\t\t\t\t\t\tthis._cache.set(device, onCreate());\n\t\t\t\t\t\tdevice.on('destroy', ()=>{\n\t\t\t\t\t\t\t\tthis.remove(device);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tdevice.on('devicelost', ()=>{\n\t\t\t\t\t\t\t\tthis._cache.get(device)?.loseContext?.(device);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this._cache.get(device);\n\t\t}\n\t\tremove(device) {\n\t\t\t\tthis._cache.get(device)?.destroy?.(device);\n\t\t\t\tthis._cache.delete(device);\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis._cache = new Map();\n\t\t}\n}\n\nclass TextureUtils {\n\t\tstatic calcLevelDimension(dimension, mipLevel) {\n\t\t\t\treturn Math.max(dimension >> mipLevel, 1);\n\t\t}\n\t\tstatic calcMipLevelsCount(width, height, depth = 1) {\n\t\t\t\treturn 1 + Math.floor(Math.log2(Math.max(width, height, depth)));\n\t\t}\n\t\tstatic calcLevelGpuSize(width, height, depth, format) {\n\t\t\t\tconst formatInfo = pixelFormatInfo.get(format);\n\t\t\t\tconst pixelSize = pixelFormatInfo.get(format)?.size ?? 0;\n\t\t\t\tif (pixelSize > 0) {\n\t\t\t\t\t\treturn width * height * depth * pixelSize;\n\t\t\t\t}\n\t\t\t\tconst blockSize = formatInfo.blockSize ?? 0;\n\t\t\t\tlet blockWidth = Math.floor((width + 3) / 4);\n\t\t\t\tconst blockHeight = Math.floor((height + 3) / 4);\n\t\t\t\tconst blockDepth = Math.floor((depth + 3) / 4);\n\t\t\t\tif (format === PIXELFORMAT_PVRTC_2BPP_RGB_1 || format === PIXELFORMAT_PVRTC_2BPP_RGBA_1) {\n\t\t\t\t\t\tblockWidth = Math.max(Math.floor(blockWidth / 2), 1);\n\t\t\t\t}\n\t\t\t\treturn blockWidth * blockHeight * blockDepth * blockSize;\n\t\t}\n\t\tstatic calcGpuSize(width, height, depth, format, mipmaps, cubemap) {\n\t\t\t\tlet result = 0;\n\t\t\t\twhile(1){\n\t\t\t\t\t\tresult += TextureUtils.calcLevelGpuSize(width, height, depth, format);\n\t\t\t\t\t\tif (!mipmaps || width === 1 && height === 1 && depth === 1) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\twidth = Math.max(width >> 1, 1);\n\t\t\t\t\t\theight = Math.max(height >> 1, 1);\n\t\t\t\t\t\tdepth = Math.max(depth >> 1, 1);\n\t\t\t\t}\n\t\t\t\treturn result * (cubemap ? 6 : 1);\n\t\t}\n}\n\nlet id$c = 0;\nclass Texture {\n\t\tconstructor(graphicsDevice, options = {}){\n\t\t\t\tthis._gpuSize = 0;\n\t\t\t\tthis.id = id$c++;\n\t\t\t\tthis._invalid = false;\n\t\t\t\tthis._lockedLevel = -1;\n\t\t\t\tthis._lockedMode = TEXTURELOCK_NONE;\n\t\t\t\tthis.renderVersionDirty = 0;\n\t\t\t\tthis._storage = false;\n\t\t\t\tthis._numLevels = 0;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.name = options.name ?? '';\n\t\t\t\tthis._width = Math.floor(options.width ?? 4);\n\t\t\t\tthis._height = Math.floor(options.height ?? 4);\n\t\t\t\tthis._format = options.format ?? PIXELFORMAT_RGBA8;\n\t\t\t\tthis._compressed = isCompressedPixelFormat(this._format);\n\t\t\t\tthis._integerFormat = isIntegerPixelFormat(this._format);\n\t\t\t\tif (this._integerFormat) {\n\t\t\t\t\t\toptions.minFilter = FILTER_NEAREST;\n\t\t\t\t\t\toptions.magFilter = FILTER_NEAREST;\n\t\t\t\t}\n\t\t\t\tthis._volume = options.volume ?? false;\n\t\t\t\tthis._depth = Math.floor(options.depth ?? 1);\n\t\t\t\tthis._arrayLength = Math.floor(options.arrayLength ?? 0);\n\t\t\t\tthis._storage = options.storage ?? false;\n\t\t\t\tthis._cubemap = options.cubemap ?? false;\n\t\t\t\tthis._flipY = options.flipY ?? false;\n\t\t\t\tthis._premultiplyAlpha = options.premultiplyAlpha ?? false;\n\t\t\t\tthis._mipmaps = options.mipmaps ?? true;\n\t\t\t\tthis._numLevelsRequested = options.numLevels;\n\t\t\t\tif (options.numLevels !== undefined) {\n\t\t\t\t\t\tthis._numLevels = options.numLevels;\n\t\t\t\t}\n\t\t\t\tthis._updateNumLevel();\n\t\t\t\tthis._minFilter = options.minFilter ?? FILTER_LINEAR_MIPMAP_LINEAR;\n\t\t\t\tthis._magFilter = options.magFilter ?? FILTER_LINEAR;\n\t\t\t\tthis._anisotropy = options.anisotropy ?? 1;\n\t\t\t\tthis._addressU = options.addressU ?? ADDRESS_REPEAT;\n\t\t\t\tthis._addressV = options.addressV ?? ADDRESS_REPEAT;\n\t\t\t\tthis._addressW = options.addressW ?? ADDRESS_REPEAT;\n\t\t\t\tthis._compareOnRead = options.compareOnRead ?? false;\n\t\t\t\tthis._compareFunc = options.compareFunc ?? FUNC_LESS;\n\t\t\t\tthis._type = options.type ?? TEXTURETYPE_DEFAULT;\n\t\t\t\tthis.projection = TEXTUREPROJECTION_NONE;\n\t\t\t\tif (this._cubemap) {\n\t\t\t\t\t\tthis.projection = TEXTUREPROJECTION_CUBE;\n\t\t\t\t} else if (options.projection && options.projection !== TEXTUREPROJECTION_CUBE) {\n\t\t\t\t\t\tthis.projection = options.projection;\n\t\t\t\t}\n\t\t\t\tthis._levels = options.levels;\n\t\t\t\tconst upload = !!options.levels;\n\t\t\t\tif (!this._levels) {\n\t\t\t\t\t\tthis._clearLevels();\n\t\t\t\t}\n\t\t\t\tthis.recreateImpl(upload);\n\t\t\t\tgraphicsDevice.textures.push(this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tif (device) {\n\t\t\t\t\t\tconst idx = device.textures.indexOf(this);\n\t\t\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\t\t\tdevice.textures.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdevice.scope.removeValue(this);\n\t\t\t\t\t\tthis.impl.destroy(device);\n\t\t\t\t\t\tthis.adjustVramSizeTracking(device._vram, -this._gpuSize);\n\t\t\t\t\t\tthis._levels = null;\n\t\t\t\t\t\tthis.device = null;\n\t\t\t\t}\n\t\t}\n\t\trecreateImpl(upload = true) {\n\t\t\t\tconst { device } = this;\n\t\t\t\tthis.impl?.destroy(device);\n\t\t\t\tthis.impl = null;\n\t\t\t\tthis.impl = device.createTextureImpl(this);\n\t\t\t\tthis.dirtyAll();\n\t\t\t\tif (upload) {\n\t\t\t\t\t\tthis.upload();\n\t\t\t\t}\n\t\t}\n\t\t_clearLevels() {\n\t\t\t\tthis._levels = this._cubemap ? [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t]\n\t\t\t\t] : [\n\t\t\t\t\t\tnull\n\t\t\t\t];\n\t\t}\n\t\tresize(width, height, depth = 1) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tthis.adjustVramSizeTracking(device._vram, -this._gpuSize);\n\t\t\t\tthis.impl.destroy(device);\n\t\t\t\tthis._clearLevels();\n\t\t\t\tthis._width = Math.floor(width);\n\t\t\t\tthis._height = Math.floor(height);\n\t\t\t\tthis._depth = Math.floor(depth);\n\t\t\t\tthis._updateNumLevel();\n\t\t\t\tthis.impl = device.createTextureImpl(this);\n\t\t\t\tthis.dirtyAll();\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.impl.loseContext();\n\t\t\t\tthis.dirtyAll();\n\t\t}\n\t\tadjustVramSizeTracking(vram, size) {\n\t\t\t\tvram.tex += size;\n\t\t}\n\t\tpropertyChanged(flag) {\n\t\t\t\tthis.impl.propertyChanged(flag);\n\t\t\t\tthis.renderVersionDirty = this.device.renderVersion;\n\t\t}\n\t\t_updateNumLevel() {\n\t\t\t\tconst maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1;\n\t\t\t\tconst requestedLevels = this._numLevelsRequested;\n\t\t\t\tthis._numLevels = Math.min(requestedLevels ?? maxLevels, maxLevels);\n\t\t\t\tthis._mipmaps = this._numLevels > 1;\n\t\t}\n\t\tget lockedMode() {\n\t\t\t\treturn this._lockedMode;\n\t\t}\n\t\tset minFilter(v) {\n\t\t\t\tif (this._minFilter !== v) {\n\t\t\t\t\t\tif (isIntegerPixelFormat(this._format)) ; else {\n\t\t\t\t\t\t\t\tthis._minFilter = v;\n\t\t\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_MIN_FILTER);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget minFilter() {\n\t\t\t\treturn this._minFilter;\n\t\t}\n\t\tset magFilter(v) {\n\t\t\t\tif (this._magFilter !== v) {\n\t\t\t\t\t\tif (isIntegerPixelFormat(this._format)) ; else {\n\t\t\t\t\t\t\t\tthis._magFilter = v;\n\t\t\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_MAG_FILTER);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget magFilter() {\n\t\t\t\treturn this._magFilter;\n\t\t}\n\t\tset addressU(v) {\n\t\t\t\tif (this._addressU !== v) {\n\t\t\t\t\t\tthis._addressU = v;\n\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_ADDRESS_U);\n\t\t\t\t}\n\t\t}\n\t\tget addressU() {\n\t\t\t\treturn this._addressU;\n\t\t}\n\t\tset addressV(v) {\n\t\t\t\tif (this._addressV !== v) {\n\t\t\t\t\t\tthis._addressV = v;\n\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_ADDRESS_V);\n\t\t\t\t}\n\t\t}\n\t\tget addressV() {\n\t\t\t\treturn this._addressV;\n\t\t}\n\t\tset addressW(addressW) {\n\t\t\t\tif (!this._volume) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (addressW !== this._addressW) {\n\t\t\t\t\t\tthis._addressW = addressW;\n\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_ADDRESS_W);\n\t\t\t\t}\n\t\t}\n\t\tget addressW() {\n\t\t\t\treturn this._addressW;\n\t\t}\n\t\tset compareOnRead(v) {\n\t\t\t\tif (this._compareOnRead !== v) {\n\t\t\t\t\t\tthis._compareOnRead = v;\n\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_COMPARE_ON_READ);\n\t\t\t\t}\n\t\t}\n\t\tget compareOnRead() {\n\t\t\t\treturn this._compareOnRead;\n\t\t}\n\t\tset compareFunc(v) {\n\t\t\t\tif (this._compareFunc !== v) {\n\t\t\t\t\t\tthis._compareFunc = v;\n\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_COMPARE_FUNC);\n\t\t\t\t}\n\t\t}\n\t\tget compareFunc() {\n\t\t\t\treturn this._compareFunc;\n\t\t}\n\t\tset anisotropy(v) {\n\t\t\t\tif (this._anisotropy !== v) {\n\t\t\t\t\t\tthis._anisotropy = v;\n\t\t\t\t\t\tthis.propertyChanged(TEXPROPERTY_ANISOTROPY);\n\t\t\t\t}\n\t\t}\n\t\tget anisotropy() {\n\t\t\t\treturn this._anisotropy;\n\t\t}\n\t\tset mipmaps(v) {\n\t\t\t\tif (this._mipmaps !== v) {\n\t\t\t\t\t\tif (this.device.isWebGPU) ; else if (isIntegerPixelFormat(this._format)) ; else {\n\t\t\t\t\t\t\t\tthis._mipmaps = v;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (v) this._needsMipmapsUpload = true;\n\t\t\t\t}\n\t\t}\n\t\tget mipmaps() {\n\t\t\t\treturn this._mipmaps;\n\t\t}\n\t\tget numLevels() {\n\t\t\t\treturn this._numLevels;\n\t\t}\n\t\tget storage() {\n\t\t\t\treturn this._storage;\n\t\t}\n\t\tget width() {\n\t\t\t\treturn this._width;\n\t\t}\n\t\tget height() {\n\t\t\t\treturn this._height;\n\t\t}\n\t\tget depth() {\n\t\t\t\treturn this._depth;\n\t\t}\n\t\tget format() {\n\t\t\t\treturn this._format;\n\t\t}\n\t\tget cubemap() {\n\t\t\t\treturn this._cubemap;\n\t\t}\n\t\tget gpuSize() {\n\t\t\t\tconst mips = this.pot && this._mipmaps && !(this._compressed && this._levels.length === 1);\n\t\t\t\treturn TextureUtils.calcGpuSize(this._width, this._height, this._depth, this._format, mips, this._cubemap);\n\t\t}\n\t\tget array() {\n\t\t\t\treturn this._arrayLength > 0;\n\t\t}\n\t\tget arrayLength() {\n\t\t\t\treturn this._arrayLength;\n\t\t}\n\t\tget volume() {\n\t\t\t\treturn this._volume;\n\t\t}\n\t\tset type(value) {\n\t\t\t\tif (this._type !== value) {\n\t\t\t\t\t\tthis._type = value;\n\t\t\t\t\t\tthis.device._shadersDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tset srgb(value) {\n\t\t\t\tconst currentSrgb = isSrgbPixelFormat(this.format);\n\t\t\t\tif (value !== currentSrgb) {\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\tconst srgbFormat = pixelFormatLinearToGamma(this.format);\n\t\t\t\t\t\t\t\tif (this._format !== srgbFormat) {\n\t\t\t\t\t\t\t\t\t\tthis._format = srgbFormat;\n\t\t\t\t\t\t\t\t\t\tthis.recreateImpl();\n\t\t\t\t\t\t\t\t\t\tthis.device._shadersDirty = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst linearFormat = pixelFormatGammaToLinear(this.format);\n\t\t\t\t\t\t\t\tif (this._format !== linearFormat) {\n\t\t\t\t\t\t\t\t\t\tthis._format = linearFormat;\n\t\t\t\t\t\t\t\t\t\tthis.recreateImpl();\n\t\t\t\t\t\t\t\t\t\tthis.device._shadersDirty = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget srgb() {\n\t\t\t\treturn isSrgbPixelFormat(this.format);\n\t\t}\n\t\tset flipY(flipY) {\n\t\t\t\tif (this._flipY !== flipY) {\n\t\t\t\t\t\tthis._flipY = flipY;\n\t\t\t\t\t\tthis._needsUpload = true;\n\t\t\t\t}\n\t\t}\n\t\tget flipY() {\n\t\t\t\treturn this._flipY;\n\t\t}\n\t\tset premultiplyAlpha(premultiplyAlpha) {\n\t\t\t\tif (this._premultiplyAlpha !== premultiplyAlpha) {\n\t\t\t\t\t\tthis._premultiplyAlpha = premultiplyAlpha;\n\t\t\t\t\t\tthis._needsUpload = true;\n\t\t\t\t}\n\t\t}\n\t\tget premultiplyAlpha() {\n\t\t\t\treturn this._premultiplyAlpha;\n\t\t}\n\t\tget pot() {\n\t\t\t\treturn math.powerOfTwo(this._width) && math.powerOfTwo(this._height);\n\t\t}\n\t\tget encoding() {\n\t\t\t\tswitch(this.type){\n\t\t\t\t\t\tcase TEXTURETYPE_RGBM:\n\t\t\t\t\t\t\t\treturn 'rgbm';\n\t\t\t\t\t\tcase TEXTURETYPE_RGBE:\n\t\t\t\t\t\t\t\treturn 'rgbe';\n\t\t\t\t\t\tcase TEXTURETYPE_RGBP:\n\t\t\t\t\t\t\t\treturn 'rgbp';\n\t\t\t\t}\n\t\t\t\treturn requiresManualGamma(this.format) ? 'srgb' : 'linear';\n\t\t}\n\t\tdirtyAll() {\n\t\t\t\tthis._levelsUpdated = this._cubemap ? [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\ttrue\n\t\t\t\t\t\t]\n\t\t\t\t] : [\n\t\t\t\t\t\ttrue\n\t\t\t\t];\n\t\t\t\tthis._needsUpload = true;\n\t\t\t\tthis._needsMipmapsUpload = this._mipmaps;\n\t\t\t\tthis._mipmapsUploaded = false;\n\t\t\t\tthis.propertyChanged(TEXPROPERTY_ALL);\n\t\t}\n\t\tlock(options = {}) {\n\t\t\t\toptions.level ??= 0;\n\t\t\t\toptions.face ??= 0;\n\t\t\t\toptions.mode ??= TEXTURELOCK_WRITE;\n\t\t\t\tthis._lockedMode = options.mode;\n\t\t\t\tthis._lockedLevel = options.level;\n\t\t\t\tconst levels = this.cubemap ? this._levels[options.face] : this._levels;\n\t\t\t\tif (levels[options.level] === null) {\n\t\t\t\t\t\tconst width = Math.max(1, this._width >> options.level);\n\t\t\t\t\t\tconst height = Math.max(1, this._height >> options.level);\n\t\t\t\t\t\tconst depth = Math.max(1, this._depth >> options.level);\n\t\t\t\t\t\tconst data = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, depth, this._format));\n\t\t\t\t\t\tlevels[options.level] = new (getPixelFormatArrayType(this._format))(data);\n\t\t\t\t}\n\t\t\t\treturn levels[options.level];\n\t\t}\n\t\tsetSource(source, mipLevel = 0) {\n\t\t\t\tlet invalid = false;\n\t\t\t\tlet width, height;\n\t\t\t\tif (this._cubemap) {\n\t\t\t\t\t\tif (source[0]) {\n\t\t\t\t\t\t\t\twidth = source[0].width || 0;\n\t\t\t\t\t\t\t\theight = source[0].height || 0;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\t\t\t\t\tconst face = source[i];\n\t\t\t\t\t\t\t\t\t\tif (!face || face.width !== width || face.height !== height || !this.device._isBrowserInterface(face)) {\n\t\t\t\t\t\t\t\t\t\t\t\tinvalid = true;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tinvalid = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!invalid) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\t\t\t\t\tif (this._levels[mipLevel][i] !== source[i]) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._levelsUpdated[mipLevel][i] = true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.device._isBrowserInterface(source)) {\n\t\t\t\t\t\t\t\tinvalid = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!invalid) {\n\t\t\t\t\t\t\t\tif (source !== this._levels[mipLevel]) {\n\t\t\t\t\t\t\t\t\t\tthis._levelsUpdated[mipLevel] = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\t\t\t\t\t\twidth = source.videoWidth;\n\t\t\t\t\t\t\t\t\t\theight = source.videoHeight;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\twidth = source.width;\n\t\t\t\t\t\t\t\t\t\theight = source.height;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (invalid) {\n\t\t\t\t\t\tthis._width = 4;\n\t\t\t\t\t\tthis._height = 4;\n\t\t\t\t\t\tif (this._cubemap) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\t\t\t\t\tthis._levels[mipLevel][i] = null;\n\t\t\t\t\t\t\t\t\t\tthis._levelsUpdated[mipLevel][i] = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._levels[mipLevel] = null;\n\t\t\t\t\t\t\t\tthis._levelsUpdated[mipLevel] = true;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (mipLevel === 0) {\n\t\t\t\t\t\t\t\tthis._width = width;\n\t\t\t\t\t\t\t\tthis._height = height;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._levels[mipLevel] = source;\n\t\t\t\t}\n\t\t\t\tif (this._invalid !== invalid || !invalid) {\n\t\t\t\t\t\tthis._invalid = invalid;\n\t\t\t\t\t\tthis.upload();\n\t\t\t\t}\n\t\t}\n\t\tgetSource(mipLevel = 0) {\n\t\t\t\treturn this._levels[mipLevel];\n\t\t}\n\t\tunlock() {\n\t\t\t\tif (this._lockedMode === TEXTURELOCK_NONE) ;\n\t\t\t\tif (this._lockedMode === TEXTURELOCK_WRITE) {\n\t\t\t\t\t\tthis.upload();\n\t\t\t\t}\n\t\t\t\tthis._lockedLevel = -1;\n\t\t\t\tthis._lockedMode = TEXTURELOCK_NONE;\n\t\t}\n\t\tupload() {\n\t\t\t\tthis._needsUpload = true;\n\t\t\t\tthis._needsMipmapsUpload = this._mipmaps;\n\t\t\t\tthis.impl.uploadImmediate?.(this.device, this);\n\t\t}\n\t\tread(x, y, width, height, options = {}) {\n\t\t\t\treturn this.impl.read?.(x, y, width, height, options);\n\t\t}\n\t\twrite(x, y, width, height, data) {\n\t\t\t\treturn this.impl.write?.(x, y, width, height, data);\n\t\t}\n}\n\nconst textureData = {\n\t\twhite: [\n\t\t\t\t255,\n\t\t\t\t255,\n\t\t\t\t255,\n\t\t\t\t255\n\t\t],\n\t\tgray: [\n\t\t\t\t128,\n\t\t\t\t128,\n\t\t\t\t128,\n\t\t\t\t255\n\t\t],\n\t\tblack: [\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t255\n\t\t],\n\t\tnormal: [\n\t\t\t\t128,\n\t\t\t\t128,\n\t\t\t\t255,\n\t\t\t\t255\n\t\t],\n\t\tpink: [\n\t\t\t\t255,\n\t\t\t\t128,\n\t\t\t\t255,\n\t\t\t\t255\n\t\t]\n};\nclass BuiltInTextures {\n\t\tdestroy() {\n\t\t\t\tthis.map.forEach((texture)=>{\n\t\t\t\t\t\ttexture.destroy();\n\t\t\t\t});\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.map = new Map();\n\t\t}\n}\nconst deviceCache$3 = new DeviceCache();\nconst getBuiltInTexture = (device, name)=>{\n\t\tconst cache = deviceCache$3.get(device, ()=>{\n\t\t\t\treturn new BuiltInTextures();\n\t\t});\n\t\tif (!cache.map.has(name)) {\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: `built-in-texture-${name}`,\n\t\t\t\t\t\twidth: 1,\n\t\t\t\t\t\theight: 1,\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA8\n\t\t\t\t});\n\t\t\t\tconst pixels = texture.lock();\n\t\t\t\tconst data = textureData[name];\n\t\t\t\tpixels.set(data);\n\t\t\t\ttexture.unlock();\n\t\t\t\tcache.map.set(name, texture);\n\t\t}\n\t\treturn cache.map.get(name);\n};\n\nlet id$b = 0;\nclass DynamicBindGroup {\n\t\tconstructor(){\n\t\t\t\tthis.offsets = [];\n\t\t}\n}\nclass BindGroup {\n\t\tconstructor(graphicsDevice, format, defaultUniformBuffer){\n\t\t\t\tthis.renderVersionUpdated = -1;\n\t\t\t\tthis.uniformBufferOffsets = [];\n\t\t\t\tthis.id = id$b++;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.format = format;\n\t\t\t\tthis.dirty = true;\n\t\t\t\tthis.impl = graphicsDevice.createBindGroupImpl(this);\n\t\t\t\tthis.textures = [];\n\t\t\t\tthis.storageTextures = [];\n\t\t\t\tthis.storageBuffers = [];\n\t\t\t\tthis.uniformBuffers = [];\n\t\t\t\tthis.defaultUniformBuffer = defaultUniformBuffer;\n\t\t\t\tif (defaultUniformBuffer) {\n\t\t\t\t\t\tthis.setUniformBuffer(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, defaultUniformBuffer);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.impl.destroy();\n\t\t\t\tthis.impl = null;\n\t\t\t\tthis.format = null;\n\t\t\t\tthis.defaultUniformBuffer = null;\n\t\t}\n\t\tsetUniformBuffer(name, uniformBuffer) {\n\t\t\t\tconst index = this.format.bufferFormatsMap.get(name);\n\t\t\t\tif (this.uniformBuffers[index] !== uniformBuffer) {\n\t\t\t\t\t\tthis.uniformBuffers[index] = uniformBuffer;\n\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t}\n\t\t}\n\t\tsetStorageBuffer(name, storageBuffer) {\n\t\t\t\tconst index = this.format.storageBufferFormatsMap.get(name);\n\t\t\t\tif (this.storageBuffers[index] !== storageBuffer) {\n\t\t\t\t\t\tthis.storageBuffers[index] = storageBuffer;\n\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t}\n\t\t}\n\t\tsetTexture(name, texture) {\n\t\t\t\tconst index = this.format.textureFormatsMap.get(name);\n\t\t\t\tif (this.textures[index] !== texture) {\n\t\t\t\t\t\tthis.textures[index] = texture;\n\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t} else if (this.renderVersionUpdated < texture.renderVersionDirty) {\n\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t}\n\t\t}\n\t\tsetStorageTexture(name, texture) {\n\t\t\t\tconst index = this.format.storageTextureFormatsMap.get(name);\n\t\t\t\tif (this.storageTextures[index] !== texture) {\n\t\t\t\t\t\tthis.storageTextures[index] = texture;\n\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t} else if (this.renderVersionUpdated < texture.renderVersionDirty) {\n\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t}\n\t\t}\n\t\tupdateUniformBuffers() {\n\t\t\t\tfor(let i = 0; i < this.uniformBuffers.length; i++){\n\t\t\t\t\t\tthis.uniformBuffers[i].update();\n\t\t\t\t}\n\t\t}\n\t\tupdate() {\n\t\t\t\tconst { textureFormats, storageTextureFormats, storageBufferFormats } = this.format;\n\t\t\t\tfor(let i = 0; i < textureFormats.length; i++){\n\t\t\t\t\t\tconst textureFormat = textureFormats[i];\n\t\t\t\t\t\tlet value = textureFormat.scopeId.value;\n\t\t\t\t\t\tif (!value) {\n\t\t\t\t\t\t\t\tif (textureFormat.name === 'uSceneDepthMap') {\n\t\t\t\t\t\t\t\t\t\tvalue = getBuiltInTexture(this.device, 'white');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (textureFormat.name === 'uSceneColorMap') {\n\t\t\t\t\t\t\t\t\t\tvalue = getBuiltInTexture(this.device, 'pink');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!value) {\n\t\t\t\t\t\t\t\t\t\tvalue = getBuiltInTexture(this.device, 'pink');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.setTexture(textureFormat.name, value);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < storageTextureFormats.length; i++){\n\t\t\t\t\t\tconst storageTextureFormat = storageTextureFormats[i];\n\t\t\t\t\t\tconst value = storageTextureFormat.scopeId.value;\n\t\t\t\t\t\tthis.setStorageTexture(storageTextureFormat.name, value);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < storageBufferFormats.length; i++){\n\t\t\t\t\t\tconst storageBufferFormat = storageBufferFormats[i];\n\t\t\t\t\t\tconst value = storageBufferFormat.scopeId.value;\n\t\t\t\t\t\tthis.setStorageBuffer(storageBufferFormat.name, value);\n\t\t\t\t}\n\t\t\t\tthis.uniformBufferOffsets.length = this.uniformBuffers.length;\n\t\t\t\tfor(let i = 0; i < this.uniformBuffers.length; i++){\n\t\t\t\t\t\tconst uniformBuffer = this.uniformBuffers[i];\n\t\t\t\t\t\tthis.uniformBufferOffsets[i] = uniformBuffer.offset;\n\t\t\t\t\t\tif (this.renderVersionUpdated < uniformBuffer.renderVersionDirty) {\n\t\t\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.dirty) {\n\t\t\t\t\t\tthis.dirty = false;\n\t\t\t\t\t\tthis.renderVersionUpdated = this.device.renderVersion;\n\t\t\t\t\t\tthis.impl.update(this);\n\t\t\t\t}\n\t\t}\n}\n\nconst BitPacking = {\n\t\tset (storage, value, shift, mask = 1) {\n\t\t\t\tconst data = storage & ~(mask << shift);\n\t\t\t\treturn data | value << shift;\n\t\t},\n\t\tget (storage, shift, mask = 1) {\n\t\t\t\treturn storage >> shift & mask;\n\t\t},\n\t\tall (storage, shift, mask = 1) {\n\t\t\t\tconst shifted = mask << shift;\n\t\t\t\treturn (storage & shifted) === shifted;\n\t\t},\n\t\tany (storage, shift, mask = 1) {\n\t\t\t\treturn (storage & mask << shift) !== 0;\n\t\t}\n};\n\nconst opMask = 0b111;\nconst factorMask = 0b1111;\nconst colorOpShift = 0;\nconst colorSrcFactorShift = 3;\nconst colorDstFactorShift = 7;\nconst alphaOpShift = 11;\nconst alphaSrcFactorShift = 14;\nconst alphaDstFactorShift = 18;\nconst redWriteShift = 22;\nconst greenWriteShift = 23;\nconst blueWriteShift = 24;\nconst alphaWriteShift = 25;\nconst blendShift = 26;\nconst allWriteMasks = 0b1111;\nconst allWriteShift = redWriteShift;\nclass BlendState {\n\t\tconstructor(blend = false, colorOp = BLENDEQUATION_ADD, colorSrcFactor = BLENDMODE_ONE, colorDstFactor = BLENDMODE_ZERO, alphaOp, alphaSrcFactor, alphaDstFactor, redWrite = true, greenWrite = true, blueWrite = true, alphaWrite = true){\n\t\t\t\tthis.target0 = 0;\n\t\t\t\tthis.setColorBlend(colorOp, colorSrcFactor, colorDstFactor);\n\t\t\t\tthis.setAlphaBlend(alphaOp ?? colorOp, alphaSrcFactor ?? colorSrcFactor, alphaDstFactor ?? colorDstFactor);\n\t\t\t\tthis.setColorWrite(redWrite, greenWrite, blueWrite, alphaWrite);\n\t\t\t\tthis.blend = blend;\n\t\t}\n\t\tset blend(value) {\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, value ? 1 : 0, blendShift);\n\t\t}\n\t\tget blend() {\n\t\t\t\treturn BitPacking.all(this.target0, blendShift);\n\t\t}\n\t\tsetColorBlend(op, srcFactor, dstFactor) {\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, op, colorOpShift, opMask);\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, srcFactor, colorSrcFactorShift, factorMask);\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, dstFactor, colorDstFactorShift, factorMask);\n\t\t}\n\t\tsetAlphaBlend(op, srcFactor, dstFactor) {\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, op, alphaOpShift, opMask);\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, srcFactor, alphaSrcFactorShift, factorMask);\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, dstFactor, alphaDstFactorShift, factorMask);\n\t\t}\n\t\tsetColorWrite(redWrite, greenWrite, blueWrite, alphaWrite) {\n\t\t\t\tthis.redWrite = redWrite;\n\t\t\t\tthis.greenWrite = greenWrite;\n\t\t\t\tthis.blueWrite = blueWrite;\n\t\t\t\tthis.alphaWrite = alphaWrite;\n\t\t}\n\t\tget colorOp() {\n\t\t\t\treturn BitPacking.get(this.target0, colorOpShift, opMask);\n\t\t}\n\t\tget colorSrcFactor() {\n\t\t\t\treturn BitPacking.get(this.target0, colorSrcFactorShift, factorMask);\n\t\t}\n\t\tget colorDstFactor() {\n\t\t\t\treturn BitPacking.get(this.target0, colorDstFactorShift, factorMask);\n\t\t}\n\t\tget alphaOp() {\n\t\t\t\treturn BitPacking.get(this.target0, alphaOpShift, opMask);\n\t\t}\n\t\tget alphaSrcFactor() {\n\t\t\t\treturn BitPacking.get(this.target0, alphaSrcFactorShift, factorMask);\n\t\t}\n\t\tget alphaDstFactor() {\n\t\t\t\treturn BitPacking.get(this.target0, alphaDstFactorShift, factorMask);\n\t\t}\n\t\tset redWrite(value) {\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, value ? 1 : 0, redWriteShift);\n\t\t}\n\t\tget redWrite() {\n\t\t\t\treturn BitPacking.all(this.target0, redWriteShift);\n\t\t}\n\t\tset greenWrite(value) {\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, value ? 1 : 0, greenWriteShift);\n\t\t}\n\t\tget greenWrite() {\n\t\t\t\treturn BitPacking.all(this.target0, greenWriteShift);\n\t\t}\n\t\tset blueWrite(value) {\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, value ? 1 : 0, blueWriteShift);\n\t\t}\n\t\tget blueWrite() {\n\t\t\t\treturn BitPacking.all(this.target0, blueWriteShift);\n\t\t}\n\t\tset alphaWrite(value) {\n\t\t\t\tthis.target0 = BitPacking.set(this.target0, value ? 1 : 0, alphaWriteShift);\n\t\t}\n\t\tget alphaWrite() {\n\t\t\t\treturn BitPacking.all(this.target0, alphaWriteShift);\n\t\t}\n\t\tget allWrite() {\n\t\t\t\treturn BitPacking.get(this.target0, allWriteShift, allWriteMasks);\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis.target0 = rhs.target0;\n\t\t\t\treturn this;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst clone = new this.constructor();\n\t\t\t\treturn clone.copy(this);\n\t\t}\n\t\tget key() {\n\t\t\t\treturn this.target0;\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\treturn this.target0 === rhs.target0;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.NOBLEND = Object.freeze(new BlendState());\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.NOWRITE = Object.freeze(new BlendState(undefined, undefined, undefined, undefined, undefined, undefined, undefined, false, false, false, false));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ALPHABLEND = Object.freeze(new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ADDBLEND = Object.freeze(new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE));\n\t\t}\n}\n\nclass StringIds {\n\t\tget(name) {\n\t\t\t\tlet value = this.map.get(name);\n\t\t\t\tif (value === undefined) {\n\t\t\t\t\t\tvalue = this.id++;\n\t\t\t\t\t\tthis.map.set(name, value);\n\t\t\t\t}\n\t\t\t\treturn value;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.map = new Map();\n\t\t\t\tthis.id = 0;\n\t\t}\n}\n\nconst stringIds$4 = new StringIds();\nconst funcMask = 0b111;\nconst funcShift = 0;\nconst writeShift = 3;\nclass DepthState {\n\t\tconstructor(func = FUNC_LESSEQUAL, write = true){\n\t\t\t\tthis.data = 0;\n\t\t\t\tthis._depthBias = 0;\n\t\t\t\tthis._depthBiasSlope = 0;\n\t\t\t\tthis.key = 0;\n\t\t\t\tthis.func = func;\n\t\t\t\tthis.write = write;\n\t\t}\n\t\tset test(value) {\n\t\t\t\tthis.func = value ? FUNC_LESSEQUAL : FUNC_ALWAYS;\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget test() {\n\t\t\t\treturn this.func !== FUNC_ALWAYS;\n\t\t}\n\t\tset write(value) {\n\t\t\t\tthis.data = BitPacking.set(this.data, value ? 1 : 0, writeShift);\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget write() {\n\t\t\t\treturn BitPacking.all(this.data, writeShift);\n\t\t}\n\t\tset func(value) {\n\t\t\t\tthis.data = BitPacking.set(this.data, value, funcShift, funcMask);\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget func() {\n\t\t\t\treturn BitPacking.get(this.data, funcShift, funcMask);\n\t\t}\n\t\tset depthBias(value) {\n\t\t\t\tthis._depthBias = value;\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget depthBias() {\n\t\t\t\treturn this._depthBias;\n\t\t}\n\t\tset depthBiasSlope(value) {\n\t\t\t\tthis._depthBiasSlope = value;\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget depthBiasSlope() {\n\t\t\t\treturn this._depthBiasSlope;\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis.data = rhs.data;\n\t\t\t\tthis._depthBias = rhs._depthBias;\n\t\t\t\tthis._depthBiasSlope = rhs._depthBiasSlope;\n\t\t\t\tthis.key = rhs.key;\n\t\t\t\treturn this;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst clone = new this.constructor();\n\t\t\t\treturn clone.copy(this);\n\t\t}\n\t\tupdateKey() {\n\t\t\t\tconst { data, _depthBias, _depthBiasSlope } = this;\n\t\t\t\tconst key = `${data}-${_depthBias}-${_depthBiasSlope}`;\n\t\t\t\tthis.key = stringIds$4.get(key);\n\t\t}\n\t\tequals(rhs) {\n\t\t\t\treturn this.key === rhs.key;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DEFAULT = Object.freeze(new DepthState());\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.NODEPTH = Object.freeze(new DepthState(FUNC_ALWAYS, false));\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.WRITEDEPTH = Object.freeze(new DepthState(FUNC_ALWAYS, true));\n\t\t}\n}\n\nclass Version {\n\t\tequals(other) {\n\t\t\t\treturn this.globalId === other.globalId && this.revision === other.revision;\n\t\t}\n\t\tcopy(other) {\n\t\t\t\tthis.globalId = other.globalId;\n\t\t\t\tthis.revision = other.revision;\n\t\t}\n\t\treset() {\n\t\t\t\tthis.globalId = 0;\n\t\t\t\tthis.revision = 0;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.globalId = 0;\n\t\t\t\tthis.revision = 0;\n\t\t}\n}\n\nlet idCounter = 0;\nclass VersionedObject {\n\t\tconstructor(){\n\t\t\t\tidCounter++;\n\t\t\t\tthis.version = new Version();\n\t\t\t\tthis.version.globalId = idCounter;\n\t\t}\n\t\tincrement() {\n\t\t\t\tthis.version.revision++;\n\t\t}\n}\n\nclass ScopeId {\n\t\tconstructor(name){\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.value = null;\n\t\t\t\tthis.versionObject = new VersionedObject();\n\t\t}\n\t\ttoJSON(key) {\n\t\t\t\treturn undefined;\n\t\t}\n\t\tsetValue(value) {\n\t\t\t\tthis.value = value;\n\t\t\t\tthis.versionObject.increment();\n\t\t}\n\t\tgetValue() {\n\t\t\t\treturn this.value;\n\t\t}\n}\n\nclass ScopeSpace {\n\t\tconstructor(name){\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.variables = new Map();\n\t\t}\n\t\tresolve(name) {\n\t\t\t\tif (!this.variables.has(name)) {\n\t\t\t\t\t\tthis.variables.set(name, new ScopeId(name));\n\t\t\t\t}\n\t\t\t\treturn this.variables.get(name);\n\t\t}\n\t\tremoveValue(value) {\n\t\t\t\tfor(const uniformName in this.variables){\n\t\t\t\t\t\tconst uniform = this.variables[uniformName];\n\t\t\t\t\t\tif (uniform.value === value) {\n\t\t\t\t\t\t\t\tuniform.value = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nlet id$a = 0;\nclass VertexBuffer {\n\t\tconstructor(graphicsDevice, format, numVertices, options){\n\t\t\t\tthis.usage = BUFFER_STATIC;\n\t\t\t\tthis.usage = options?.usage ?? BUFFER_STATIC;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.format = format;\n\t\t\t\tthis.numVertices = numVertices;\n\t\t\t\tthis.id = id$a++;\n\t\t\t\tthis.impl = graphicsDevice.createVertexBufferImpl(this, format, options);\n\t\t\t\tthis.numBytes = format.verticesByteSize ? format.verticesByteSize : format.size * numVertices;\n\t\t\t\tthis.adjustVramSizeTracking(graphicsDevice._vram, this.numBytes);\n\t\t\t\tconst initialData = options?.data;\n\t\t\t\tif (initialData) {\n\t\t\t\t\t\tthis.setData(initialData);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.storage = new ArrayBuffer(this.numBytes);\n\t\t\t\t}\n\t\t\t\tthis.device.buffers.push(this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst idx = device.buffers.indexOf(this);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tdevice.buffers.splice(idx, 1);\n\t\t\t\t}\n\t\t\t\tif (this.impl.initialized) {\n\t\t\t\t\t\tthis.impl.destroy(device);\n\t\t\t\t\t\tthis.adjustVramSizeTracking(device._vram, -this.storage.byteLength);\n\t\t\t\t}\n\t\t}\n\t\tadjustVramSizeTracking(vram, size) {\n\t\t\t\tvram.vb += size;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.impl.loseContext();\n\t\t}\n\t\tgetFormat() {\n\t\t\t\treturn this.format;\n\t\t}\n\t\tgetUsage() {\n\t\t\t\treturn this.usage;\n\t\t}\n\t\tgetNumVertices() {\n\t\t\t\treturn this.numVertices;\n\t\t}\n\t\tlock() {\n\t\t\t\treturn this.storage;\n\t\t}\n\t\tunlock() {\n\t\t\t\tthis.impl.unlock(this);\n\t\t}\n\t\tsetData(data) {\n\t\t\t\tif (data.byteLength !== this.numBytes) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis.storage = data;\n\t\t\t\tthis.unlock();\n\t\t\t\treturn true;\n\t\t}\n}\n\nfunction hashCode(str) {\n\t\tif (str === null || str === undefined) {\n\t\t\t\treturn 0;\n\t\t}\n\t\tlet hash = 0;\n\t\tfor(let i = 0, len = str.length; i < len; i++){\n\t\t\t\thash = (hash << 5) - hash + str.charCodeAt(i);\n\t\t\t\thash |= 0;\n\t\t}\n\t\treturn hash;\n}\nfunction hash32Fnv1a(array) {\n\t\tconst prime = 16777619;\n\t\tlet hash = 2166136261;\n\t\tfor(let i = 0; i < array.length; i++){\n\t\t\t\thash ^= array[i];\n\t\t\t\thash *= prime;\n\t\t}\n\t\treturn hash >>> 0;\n}\n\nconst stringIds$3 = new StringIds();\nconst webgpuValidElementSizes = [\n\t\t2,\n\t\t4,\n\t\t8,\n\t\t12,\n\t\t16\n];\nconst deviceCache$2 = new DeviceCache();\nclass VertexFormat {\n\t\tconstructor(graphicsDevice, description, vertexCount){\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis._elements = [];\n\t\t\t\tthis.hasUv0 = false;\n\t\t\t\tthis.hasUv1 = false;\n\t\t\t\tthis.hasColor = false;\n\t\t\t\tthis.hasTangents = false;\n\t\t\t\tthis.verticesByteSize = 0;\n\t\t\t\tthis.vertexCount = vertexCount;\n\t\t\t\tthis.interleaved = vertexCount === undefined;\n\t\t\t\tthis.instancing = false;\n\t\t\t\tthis.size = description.reduce((total, desc)=>{\n\t\t\t\t\t\treturn total + Math.ceil(desc.components * typedArrayTypesByteSize[desc.type] / 4) * 4;\n\t\t\t\t}, 0);\n\t\t\t\tlet offset = 0, elementSize;\n\t\t\t\tfor(let i = 0, len = description.length; i < len; i++){\n\t\t\t\t\t\tconst elementDesc = description[i];\n\t\t\t\t\t\telementSize = elementDesc.components * typedArrayTypesByteSize[elementDesc.type];\n\t\t\t\t\t\tif (vertexCount) {\n\t\t\t\t\t\t\t\toffset = math.roundUp(offset, elementSize);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst asInt = elementDesc.asInt ?? false;\n\t\t\t\t\t\tconst normalize = asInt ? false : elementDesc.normalize ?? false;\n\t\t\t\t\t\tconst element = {\n\t\t\t\t\t\t\t\tname: elementDesc.semantic,\n\t\t\t\t\t\t\t\toffset: vertexCount ? offset : elementDesc.hasOwnProperty('offset') ? elementDesc.offset : offset,\n\t\t\t\t\t\t\t\tstride: vertexCount ? elementSize : elementDesc.hasOwnProperty('stride') ? elementDesc.stride : this.size,\n\t\t\t\t\t\t\t\tdataType: elementDesc.type,\n\t\t\t\t\t\t\t\tnumComponents: elementDesc.components,\n\t\t\t\t\t\t\t\tnormalize: normalize,\n\t\t\t\t\t\t\t\tsize: elementSize,\n\t\t\t\t\t\t\t\tasInt: asInt\n\t\t\t\t\t\t};\n\t\t\t\t\t\tthis._elements.push(element);\n\t\t\t\t\t\tif (vertexCount) {\n\t\t\t\t\t\t\t\toffset += elementSize * vertexCount;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toffset += Math.ceil(elementSize / 4) * 4;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (elementDesc.semantic === SEMANTIC_TEXCOORD0) {\n\t\t\t\t\t\t\t\tthis.hasUv0 = true;\n\t\t\t\t\t\t} else if (elementDesc.semantic === SEMANTIC_TEXCOORD1) {\n\t\t\t\t\t\t\t\tthis.hasUv1 = true;\n\t\t\t\t\t\t} else if (elementDesc.semantic === SEMANTIC_COLOR) {\n\t\t\t\t\t\t\t\tthis.hasColor = true;\n\t\t\t\t\t\t} else if (elementDesc.semantic === SEMANTIC_TANGENT) {\n\t\t\t\t\t\t\t\tthis.hasTangents = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (vertexCount) {\n\t\t\t\t\t\tthis.verticesByteSize = offset;\n\t\t\t\t}\n\t\t\t\tthis._evaluateHash();\n\t\t}\n\t\tget elements() {\n\t\t\t\treturn this._elements;\n\t\t}\n\t\tstatic getDefaultInstancingFormat(graphicsDevice) {\n\t\t\t\treturn deviceCache$2.get(graphicsDevice, ()=>{\n\t\t\t\t\t\treturn new VertexFormat(graphicsDevice, [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR11,\n\t\t\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR12,\n\t\t\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR14,\n\t\t\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR15,\n\t\t\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]);\n\t\t\t\t});\n\t\t}\n\t\tstatic isElementValid(graphicsDevice, elementDesc) {\n\t\t\t\tconst elementSize = elementDesc.components * typedArrayTypesByteSize[elementDesc.type];\n\t\t\t\tif (graphicsDevice.isWebGPU && !webgpuValidElementSizes.includes(elementSize)) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tupdate() {\n\t\t\t\tthis._evaluateHash();\n\t\t}\n\t\t_evaluateHash() {\n\t\t\t\tconst stringElementsBatch = [];\n\t\t\t\tconst stringElementsRender = [];\n\t\t\t\tconst len = this._elements.length;\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tconst { name, dataType, numComponents, normalize, offset, stride, size, asInt } = this._elements[i];\n\t\t\t\t\t\tconst stringElementBatch = name + dataType + numComponents + normalize + asInt;\n\t\t\t\t\t\tstringElementsBatch.push(stringElementBatch);\n\t\t\t\t\t\tconst stringElementRender = stringElementBatch + offset + stride + size;\n\t\t\t\t\t\tstringElementsRender.push(stringElementRender);\n\t\t\t\t}\n\t\t\t\tstringElementsBatch.sort();\n\t\t\t\tconst batchingString = stringElementsBatch.join();\n\t\t\t\tthis.batchingHash = hashCode(batchingString);\n\t\t\t\tthis.shaderProcessingHashString = batchingString;\n\t\t\t\tthis.renderingHashString = stringElementsRender.join('_');\n\t\t\t\tthis.renderingHash = stringIds$3.get(this.renderingHashString);\n\t\t}\n}\n\nconst stringIds$2 = new StringIds();\nclass StencilParameters {\n\t\tset func(value) {\n\t\t\t\tthis._func = value;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\tget func() {\n\t\t\t\treturn this._func;\n\t\t}\n\t\tset ref(value) {\n\t\t\t\tthis._ref = value;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\tget ref() {\n\t\t\t\treturn this._ref;\n\t\t}\n\t\tset fail(value) {\n\t\t\t\tthis._fail = value;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\tget fail() {\n\t\t\t\treturn this._fail;\n\t\t}\n\t\tset zfail(value) {\n\t\t\t\tthis._zfail = value;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\tget zfail() {\n\t\t\t\treturn this._zfail;\n\t\t}\n\t\tset zpass(value) {\n\t\t\t\tthis._zpass = value;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\tget zpass() {\n\t\t\t\treturn this._zpass;\n\t\t}\n\t\tset readMask(value) {\n\t\t\t\tthis._readMask = value;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\tget readMask() {\n\t\t\t\treturn this._readMask;\n\t\t}\n\t\tset writeMask(value) {\n\t\t\t\tthis._writeMask = value;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\tget writeMask() {\n\t\t\t\treturn this._writeMask;\n\t\t}\n\t\tconstructor(options = {}){\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis._func = options.func ?? FUNC_ALWAYS;\n\t\t\t\tthis._ref = options.ref ?? 0;\n\t\t\t\tthis._readMask = options.readMask ?? 0xFF;\n\t\t\t\tthis._writeMask = options.writeMask ?? 0xFF;\n\t\t\t\tthis._fail = options.fail ?? STENCILOP_KEEP;\n\t\t\t\tthis._zfail = options.zfail ?? STENCILOP_KEEP;\n\t\t\t\tthis._zpass = options.zpass ?? STENCILOP_KEEP;\n\t\t\t\tthis._evalKey();\n\t\t}\n\t\t_evalKey() {\n\t\t\t\tconst { _func, _ref, _fail, _zfail, _zpass, _readMask, _writeMask } = this;\n\t\t\t\tconst key = `${_func},${_ref},${_fail},${_zfail},${_zpass},${_readMask},${_writeMask}`;\n\t\t\t\tthis._key = stringIds$2.get(key);\n\t\t\t\tthis._dirty = false;\n\t\t}\n\t\tget key() {\n\t\t\t\tif (this._dirty) {\n\t\t\t\t\t\tthis._evalKey();\n\t\t\t\t}\n\t\t\t\treturn this._key;\n\t\t}\n\t\tcopy(rhs) {\n\t\t\t\tthis._func = rhs._func;\n\t\t\t\tthis._ref = rhs._ref;\n\t\t\t\tthis._readMask = rhs._readMask;\n\t\t\t\tthis._writeMask = rhs._writeMask;\n\t\t\t\tthis._fail = rhs._fail;\n\t\t\t\tthis._zfail = rhs._zfail;\n\t\t\t\tthis._zpass = rhs._zpass;\n\t\t\t\tthis._dirty = rhs._dirty;\n\t\t\t\tthis._key = rhs._key;\n\t\t\t\treturn this;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst clone = new this.constructor();\n\t\t\t\treturn clone.copy(this);\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DEFAULT = Object.freeze(new StencilParameters());\n\t\t}\n}\n\nclass GraphicsDevice extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESIZE = 'resizecanvas';\n\t\t}\n\t\tconstructor(canvas, options){\n\t\t\t\tsuper(), this.backBuffer = null, this.backBufferSize = new Vec2(), this.backBufferAntialias = false, this.isWebGPU = false, this.isWebGL2 = false, this.isHdr = false, this.maxIndirectDrawCount = 1024, this.maxColorAttachments = 1, this.maxSamples = 1, this.supportsCompute = false, this.supportsStorageTextureRead = false, this.renderTarget = null, this.shaders = [], this.textures = [], this.targets = new Set(), this.renderVersion = 0, this.insideRenderPass = false, this.supportsUniformBuffers = false, this.supportsClipDistances = false, this.textureRG11B10Renderable = false, this.textureFloatFilterable = false, this.blendState = new BlendState(), this.depthState = new DepthState(), this.stencilEnabled = false, this.stencilFront = new StencilParameters(), this.stencilBack = new StencilParameters(), this.defaultClearOptions = {\n\t\t\t\t\t\tcolor: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdepth: 1,\n\t\t\t\t\t\tstencil: 0,\n\t\t\t\t\t\tflags: CLEARFLAG_COLOR | CLEARFLAG_DEPTH\n\t\t\t\t}, this.clientRect = {\n\t\t\t\t\t\twidth: 0,\n\t\t\t\t\t\theight: 0\n\t\t\t\t}, this._shadersDirty = false, this.capsDefines = new Map(), this.mapsToClear = new Set();\n\t\t\t\tthis.canvas = canvas;\n\t\t\t\tif ('setAttribute' in canvas) {\n\t\t\t\t\t\tcanvas.setAttribute('data-engine', `PlayCanvas ${version}`);\n\t\t\t\t}\n\t\t\t\tthis.initOptions = {\n\t\t\t\t\t\t...options\n\t\t\t\t};\n\t\t\t\tthis.initOptions.alpha ??= true;\n\t\t\t\tthis.initOptions.depth ??= true;\n\t\t\t\tthis.initOptions.stencil ??= true;\n\t\t\t\tthis.initOptions.antialias ??= true;\n\t\t\t\tthis.initOptions.powerPreference ??= 'high-performance';\n\t\t\t\tthis.initOptions.displayFormat ??= DISPLAYFORMAT_LDR;\n\t\t\t\tthis._maxPixelRatio = platform.browser ? Math.min(1, window.devicePixelRatio) : 1;\n\t\t\t\tthis.buffers = [];\n\t\t\t\tthis._vram = {\n\t\t\t\t\t\ttex: 0,\n\t\t\t\t\t\tvb: 0,\n\t\t\t\t\t\tib: 0,\n\t\t\t\t\t\tub: 0,\n\t\t\t\t\t\tsb: 0\n\t\t\t\t};\n\t\t\t\tthis._shaderStats = {\n\t\t\t\t\t\tvsCompiled: 0,\n\t\t\t\t\t\tfsCompiled: 0,\n\t\t\t\t\t\tlinked: 0,\n\t\t\t\t\t\tmaterialShaders: 0,\n\t\t\t\t\t\tcompileTime: 0\n\t\t\t\t};\n\t\t\t\tthis.initializeContextCaches();\n\t\t\t\tthis._drawCallsPerFrame = 0;\n\t\t\t\tthis._shaderSwitchesPerFrame = 0;\n\t\t\t\tthis._primsPerFrame = [];\n\t\t\t\tfor(let i = PRIMITIVE_POINTS; i <= PRIMITIVE_TRIFAN; i++){\n\t\t\t\t\t\tthis._primsPerFrame[i] = 0;\n\t\t\t\t}\n\t\t\t\tthis._renderTargetCreationTime = 0;\n\t\t\t\tthis.scope = new ScopeSpace('Device');\n\t\t\t\tthis.textureBias = this.scope.resolve('textureBias');\n\t\t\t\tthis.textureBias.setValue(0.0);\n\t\t}\n\t\tpostInit() {\n\t\t\t\tconst vertexFormat = new VertexFormat(this, [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_POSITION,\n\t\t\t\t\t\t\t\tcomponents: 2,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t}\n\t\t\t\t]);\n\t\t\t\tconst positions = new Float32Array([\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1\n\t\t\t\t]);\n\t\t\t\tthis.quadVertexBuffer = new VertexBuffer(this, vertexFormat, 4, {\n\t\t\t\t\t\tdata: positions\n\t\t\t\t});\n\t\t}\n\t\tinitCapsDefines() {\n\t\t\t\tconst { capsDefines } = this;\n\t\t\t\tcapsDefines.clear();\n\t\t\t\tif (this.textureFloatFilterable) capsDefines.set('CAPS_TEXTURE_FLOAT_FILTERABLE', '');\n\t\t\t\tif (this.textureFloatRenderable) capsDefines.set('CAPS_TEXTURE_FLOAT_RENDERABLE', '');\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.fire('destroy');\n\t\t\t\tthis.quadVertexBuffer?.destroy();\n\t\t\t\tthis.quadVertexBuffer = null;\n\t\t\t\tthis.dynamicBuffers?.destroy();\n\t\t\t\tthis.dynamicBuffers = null;\n\t\t\t\tthis.gpuProfiler?.destroy();\n\t\t\t\tthis.gpuProfiler = null;\n\t\t}\n\t\tonDestroyShader(shader) {\n\t\t\t\tthis.fire('destroy:shader', shader);\n\t\t\t\tconst idx = this.shaders.indexOf(shader);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tthis.shaders.splice(idx, 1);\n\t\t\t\t}\n\t\t}\n\t\tpostDestroy() {\n\t\t\t\tthis.scope = null;\n\t\t\t\tthis.canvas = null;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.contextLost = true;\n\t\t\t\tthis.backBufferSize.set(-1, -1);\n\t\t\t\tfor (const texture of this.textures){\n\t\t\t\t\t\ttexture.loseContext();\n\t\t\t\t}\n\t\t\t\tfor (const buffer of this.buffers){\n\t\t\t\t\t\tbuffer.loseContext();\n\t\t\t\t}\n\t\t\t\tfor (const target of this.targets){\n\t\t\t\t\t\ttarget.loseContext();\n\t\t\t\t}\n\t\t\t\tthis.gpuProfiler?.loseContext();\n\t\t}\n\t\trestoreContext() {\n\t\t\t\tthis.contextLost = false;\n\t\t\t\tthis.initializeRenderState();\n\t\t\t\tthis.initializeContextCaches();\n\t\t\t\tfor (const buffer of this.buffers){\n\t\t\t\t\t\tbuffer.unlock();\n\t\t\t\t}\n\t\t\t\tthis.gpuProfiler?.restoreContext?.();\n\t\t}\n\t\ttoJSON(key) {\n\t\t\t\treturn undefined;\n\t\t}\n\t\tinitializeContextCaches() {\n\t\t\t\tthis.vertexBuffers = [];\n\t\t\t\tthis.shader = null;\n\t\t\t\tthis.shaderValid = undefined;\n\t\t\t\tthis.shaderAsyncCompile = false;\n\t\t\t\tthis.renderTarget = null;\n\t\t}\n\t\tinitializeRenderState() {\n\t\t\t\tthis.blendState = new BlendState();\n\t\t\t\tthis.depthState = new DepthState();\n\t\t\t\tthis.cullMode = CULLFACE_BACK;\n\t\t\t\tthis.vx = this.vy = this.vw = this.vh = 0;\n\t\t\t\tthis.sx = this.sy = this.sw = this.sh = 0;\n\t\t\t\tthis.blendColor = new Color(0, 0, 0, 0);\n\t\t}\n\t\tsetStencilState(stencilFront, stencilBack) {}\n\t\tsetBlendState(blendState) {}\n\t\tsetBlendColor(r, g, b, a) {}\n\t\tsetDepthState(depthState) {}\n\t\tsetCullMode(cullMode) {}\n\t\tsetRenderTarget(renderTarget) {\n\t\t\t\tthis.renderTarget = renderTarget;\n\t\t}\n\t\tsetVertexBuffer(vertexBuffer) {\n\t\t\t\tif (vertexBuffer) {\n\t\t\t\t\t\tthis.vertexBuffers.push(vertexBuffer);\n\t\t\t\t}\n\t\t}\n\t\tclearVertexBuffer() {\n\t\t\t\tthis.vertexBuffers.length = 0;\n\t\t}\n\t\tgetIndirectDrawSlot() {\n\t\t\t\treturn 0;\n\t\t}\n\t\tget indirectDrawBuffer() {\n\t\t\t\treturn null;\n\t\t}\n\t\tgetRenderTarget() {\n\t\t\t\treturn this.renderTarget;\n\t\t}\n\t\tinitRenderTarget(target) {\n\t\t\t\tif (target.initialized) return;\n\t\t\t\ttarget.init();\n\t\t\t\tthis.targets.add(target);\n\t\t}\n\t\tdraw(primitive, indexBuffer, numInstances, indirectSlot, first = true, last = true) {}\n\t\t_isBrowserInterface(texture) {\n\t\t\t\treturn this._isImageBrowserInterface(texture) || this._isImageCanvasInterface(texture) || this._isImageVideoInterface(texture);\n\t\t}\n\t\t_isImageBrowserInterface(texture) {\n\t\t\t\treturn typeof ImageBitmap !== 'undefined' && texture instanceof ImageBitmap || typeof HTMLImageElement !== 'undefined' && texture instanceof HTMLImageElement;\n\t\t}\n\t\t_isImageCanvasInterface(texture) {\n\t\t\t\treturn typeof HTMLCanvasElement !== 'undefined' && texture instanceof HTMLCanvasElement;\n\t\t}\n\t\t_isImageVideoInterface(texture) {\n\t\t\t\treturn typeof HTMLVideoElement !== 'undefined' && texture instanceof HTMLVideoElement;\n\t\t}\n\t\tresizeCanvas(width, height) {\n\t\t\t\tconst pixelRatio = Math.min(this._maxPixelRatio, platform.browser ? window.devicePixelRatio : 1);\n\t\t\t\tconst w = Math.floor(width * pixelRatio);\n\t\t\t\tconst h = Math.floor(height * pixelRatio);\n\t\t\t\tif (w !== this.canvas.width || h !== this.canvas.height) {\n\t\t\t\t\t\tthis.setResolution(w, h);\n\t\t\t\t}\n\t\t}\n\t\tsetResolution(width, height) {\n\t\t\t\tthis.canvas.width = width;\n\t\t\t\tthis.canvas.height = height;\n\t\t\t\tthis.fire(GraphicsDevice.EVENT_RESIZE, width, height);\n\t\t}\n\t\tupdate() {\n\t\t\t\tthis.updateClientRect();\n\t\t}\n\t\tupdateClientRect() {\n\t\t\t\tif (platform.worker) {\n\t\t\t\t\t\tthis.clientRect.width = this.canvas.width;\n\t\t\t\t\t\tthis.clientRect.height = this.canvas.height;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst rect = this.canvas.getBoundingClientRect();\n\t\t\t\t\t\tthis.clientRect.width = rect.width;\n\t\t\t\t\t\tthis.clientRect.height = rect.height;\n\t\t\t\t}\n\t\t}\n\t\tget width() {\n\t\t\t\treturn this.canvas.width;\n\t\t}\n\t\tget height() {\n\t\t\t\treturn this.canvas.height;\n\t\t}\n\t\tset fullscreen(fullscreen) {}\n\t\tget fullscreen() {\n\t\t\t\treturn false;\n\t\t}\n\t\tset maxPixelRatio(ratio) {\n\t\t\t\tthis._maxPixelRatio = ratio;\n\t\t}\n\t\tget maxPixelRatio() {\n\t\t\t\treturn this._maxPixelRatio;\n\t\t}\n\t\tget deviceType() {\n\t\t\t\treturn this._deviceType;\n\t\t}\n\t\tstartRenderPass(renderPass) {}\n\t\tendRenderPass(renderPass) {}\n\t\tstartComputePass(name) {}\n\t\tendComputePass() {}\n\t\tframeStart() {\n\t\t\t\tthis.renderPassIndex = 0;\n\t\t\t\tthis.renderVersion++;\n\t\t}\n\t\tframeEnd() {\n\t\t\t\tthis.mapsToClear.forEach((map)=>map.clear());\n\t\t\t\tthis.mapsToClear.clear();\n\t\t}\n\t\tcomputeDispatch(computes, name = 'Unnamed') {}\n\t\tgetRenderableHdrFormat(formats = [\n\t\t\t\tPIXELFORMAT_111110F,\n\t\t\t\tPIXELFORMAT_RGBA16F,\n\t\t\t\tPIXELFORMAT_RGBA32F\n\t\t], filterable = true, samples = 1) {\n\t\t\t\tfor(let i = 0; i < formats.length; i++){\n\t\t\t\t\t\tconst format = formats[i];\n\t\t\t\t\t\tswitch(format){\n\t\t\t\t\t\t\t\tcase PIXELFORMAT_111110F:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tif (this.textureRG11B10Renderable) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn format;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase PIXELFORMAT_RGBA16F:\n\t\t\t\t\t\t\t\t\t\tif (this.textureHalfFloatRenderable) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn format;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase PIXELFORMAT_RGBA32F:\n\t\t\t\t\t\t\t\t\t\tif (this.isWebGPU && samples > 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (this.textureFloatRenderable && (!filterable || this.textureFloatFilterable)) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn format;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\tvalidateAttributes(shader, vb0Format, vb1Format) {}\n}\n\nlet id$9 = 0;\nclass RenderTarget {\n\t\tconstructor(options = {}){\n\t\t\t\tthis.id = id$9++;\n\t\t\t\tconst device = options.colorBuffer?.device ?? options.colorBuffers?.[0].device ?? options.depthBuffer?.device ?? options.graphicsDevice;\n\t\t\t\tthis._device = device;\n\t\t\t\tconst { maxSamples } = this._device;\n\t\t\t\tthis._samples = Math.min(options.samples ?? 1, maxSamples);\n\t\t\t\tif (device.isWebGPU) {\n\t\t\t\t\t\tthis._samples = this._samples > 1 ? maxSamples : 1;\n\t\t\t\t}\n\t\t\t\tthis._colorBuffer = options.colorBuffer;\n\t\t\t\tif (options.colorBuffer) {\n\t\t\t\t\t\tthis._colorBuffers = [\n\t\t\t\t\t\t\t\toptions.colorBuffer\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tthis._depthBuffer = options.depthBuffer;\n\t\t\t\tthis._face = options.face ?? 0;\n\t\t\t\tif (this._depthBuffer) {\n\t\t\t\t\t\tconst format = this._depthBuffer._format;\n\t\t\t\t\t\tif (format === PIXELFORMAT_DEPTH || format === PIXELFORMAT_DEPTH16) {\n\t\t\t\t\t\t\t\tthis._depth = true;\n\t\t\t\t\t\t\t\tthis._stencil = false;\n\t\t\t\t\t\t} else if (format === PIXELFORMAT_DEPTHSTENCIL) {\n\t\t\t\t\t\t\t\tthis._depth = true;\n\t\t\t\t\t\t\t\tthis._stencil = true;\n\t\t\t\t\t\t} else if (format === PIXELFORMAT_R32F && this._depthBuffer.device.isWebGPU && this._samples > 1) {\n\t\t\t\t\t\t\t\tthis._depth = true;\n\t\t\t\t\t\t\t\tthis._stencil = false;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._depth = false;\n\t\t\t\t\t\t\t\tthis._stencil = false;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._depth = options.depth ?? true;\n\t\t\t\t\t\tthis._stencil = options.stencil ?? false;\n\t\t\t\t}\n\t\t\t\tif (options.colorBuffers) {\n\t\t\t\t\t\tif (!this._colorBuffers) {\n\t\t\t\t\t\t\t\tthis._colorBuffers = [\n\t\t\t\t\t\t\t\t\t\t...options.colorBuffers\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\tthis._colorBuffer = options.colorBuffers[0];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.autoResolve = options.autoResolve ?? true;\n\t\t\t\tthis.name = options.name;\n\t\t\t\tif (!this.name) {\n\t\t\t\t\t\tthis.name = this._colorBuffer?.name;\n\t\t\t\t}\n\t\t\t\tif (!this.name) {\n\t\t\t\t\t\tthis.name = this._depthBuffer?.name;\n\t\t\t\t}\n\t\t\t\tif (!this.name) {\n\t\t\t\t\t\tthis.name = 'Untitled';\n\t\t\t\t}\n\t\t\t\tthis.flipY = options.flipY ?? false;\n\t\t\t\tthis._mipLevel = options.mipLevel ?? 0;\n\t\t\t\tif (this._mipLevel > 0 && this._depth) {\n\t\t\t\t\t\tthis._mipLevel = 0;\n\t\t\t\t}\n\t\t\t\tthis._mipmaps = options.mipLevel === undefined;\n\t\t\t\tthis.validateMrt();\n\t\t\t\tthis.impl = device.createRenderTargetImpl(this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst device = this._device;\n\t\t\t\tif (device) {\n\t\t\t\t\t\tdevice.targets.delete(this);\n\t\t\t\t\t\tif (device.renderTarget === this) {\n\t\t\t\t\t\t\t\tdevice.setRenderTarget(null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.destroyFrameBuffers();\n\t\t\t\t}\n\t\t}\n\t\tdestroyFrameBuffers() {\n\t\t\t\tconst device = this._device;\n\t\t\t\tif (device) {\n\t\t\t\t\t\tthis.impl.destroy(device);\n\t\t\t\t}\n\t\t}\n\t\tdestroyTextureBuffers() {\n\t\t\t\tthis._depthBuffer?.destroy();\n\t\t\t\tthis._depthBuffer = null;\n\t\t\t\tthis._colorBuffers?.forEach((colorBuffer)=>{\n\t\t\t\t\t\tcolorBuffer.destroy();\n\t\t\t\t});\n\t\t\t\tthis._colorBuffers = null;\n\t\t\t\tthis._colorBuffer = null;\n\t\t}\n\t\tresize(width, height) {\n\t\t\t\tif (this.width !== width || this.height !== height) {\n\t\t\t\t\t\tif (this.mipLevel > 0) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst device = this._device;\n\t\t\t\t\t\tthis.destroyFrameBuffers();\n\t\t\t\t\t\tif (device.renderTarget === this) {\n\t\t\t\t\t\t\t\tdevice.setRenderTarget(null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._depthBuffer?.resize(width, height);\n\t\t\t\t\t\tthis._colorBuffers?.forEach((colorBuffer)=>{\n\t\t\t\t\t\t\t\tcolorBuffer.resize(width, height);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.validateMrt();\n\t\t\t\t\t\tthis.impl = device.createRenderTargetImpl(this);\n\t\t\t\t}\n\t\t}\n\t\tvalidateMrt() {}\n\t\tinit() {\n\t\t\t\tthis.impl.init(this._device, this);\n\t\t}\n\t\tget initialized() {\n\t\t\t\treturn this.impl.initialized;\n\t\t}\n\t\tget device() {\n\t\t\t\treturn this._device;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.impl.loseContext();\n\t\t}\n\t\tresolve(color = true, depth = !!this._depthBuffer) {\n\t\t\t\tif (this._device && this._samples > 1) {\n\t\t\t\t\t\tthis.impl.resolve(this._device, this, color, depth);\n\t\t\t\t}\n\t\t}\n\t\tcopy(source, color, depth) {\n\t\t\t\tif (!this._device) {\n\t\t\t\t\t\tif (source._device) {\n\t\t\t\t\t\t\t\tthis._device = source._device;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst success = this._device.copyRenderTarget(source, this, color, depth);\n\t\t\t\treturn success;\n\t\t}\n\t\tget samples() {\n\t\t\t\treturn this._samples;\n\t\t}\n\t\tget depth() {\n\t\t\t\treturn this._depth;\n\t\t}\n\t\tget stencil() {\n\t\t\t\treturn this._stencil;\n\t\t}\n\t\tget colorBuffer() {\n\t\t\t\treturn this._colorBuffer;\n\t\t}\n\t\tgetColorBuffer(index) {\n\t\t\t\treturn this._colorBuffers?.[index];\n\t\t}\n\t\tget depthBuffer() {\n\t\t\t\treturn this._depthBuffer;\n\t\t}\n\t\tget face() {\n\t\t\t\treturn this._face;\n\t\t}\n\t\tget mipLevel() {\n\t\t\t\treturn this._mipLevel;\n\t\t}\n\t\tget mipmaps() {\n\t\t\t\treturn this._mipmaps;\n\t\t}\n\t\tget width() {\n\t\t\t\tlet width = this._colorBuffer?.width || this._depthBuffer?.width || this._device.width;\n\t\t\t\tif (this._mipLevel > 0) {\n\t\t\t\t\t\twidth = TextureUtils.calcLevelDimension(width, this._mipLevel);\n\t\t\t\t}\n\t\t\t\treturn width;\n\t\t}\n\t\tget height() {\n\t\t\t\tlet height = this._colorBuffer?.height || this._depthBuffer?.height || this._device.height;\n\t\t\t\tif (this._mipLevel > 0) {\n\t\t\t\t\t\theight = TextureUtils.calcLevelDimension(height, this._mipLevel);\n\t\t\t\t}\n\t\t\t\treturn height;\n\t\t}\n\t\tisColorBufferSrgb(index = 0) {\n\t\t\t\tif (this.device.backBuffer === this) {\n\t\t\t\t\t\treturn isSrgbPixelFormat(this.device.backBufferFormat);\n\t\t\t\t}\n\t\t\t\tconst colorBuffer = this.getColorBuffer(index);\n\t\t\t\treturn colorBuffer ? isSrgbPixelFormat(colorBuffer.format) : false;\n\t\t}\n}\n\nclass WebgpuBindGroup {\n\t\tupdate(bindGroup) {\n\t\t\t\tthis.destroy();\n\t\t\t\tconst device = bindGroup.device;\n\t\t\t\tconst desc = this.createDescriptor(device, bindGroup);\n\t\t\t\tthis.bindGroup = device.wgpu.createBindGroup(desc);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.bindGroup = null;\n\t\t}\n\t\tcreateDescriptor(device, bindGroup) {\n\t\t\t\tconst entries = [];\n\t\t\t\tconst format = bindGroup.format;\n\t\t\t\tconst uniformBufferFormats = bindGroup.format.uniformBufferFormats;\n\t\t\t\tbindGroup.uniformBuffers.forEach((ub, i)=>{\n\t\t\t\t\t\tconst slot = uniformBufferFormats[i].slot;\n\t\t\t\t\t\tconst buffer = ub.persistent ? ub.impl.buffer : ub.allocation.gpuBuffer.buffer;\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: slot,\n\t\t\t\t\t\t\t\tresource: {\n\t\t\t\t\t\t\t\t\t\tbuffer: buffer,\n\t\t\t\t\t\t\t\t\t\toffset: 0,\n\t\t\t\t\t\t\t\t\t\tsize: ub.format.byteSize\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tconst textureFormats = bindGroup.format.textureFormats;\n\t\t\t\tbindGroup.textures.forEach((tex, textureIndex)=>{\n\t\t\t\t\t\tconst wgpuTexture = tex.impl;\n\t\t\t\t\t\tconst textureFormat = format.textureFormats[textureIndex];\n\t\t\t\t\t\tconst slot = textureFormats[textureIndex].slot;\n\t\t\t\t\t\tconst view = wgpuTexture.getView(device);\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: slot,\n\t\t\t\t\t\t\t\tresource: view\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (textureFormat.hasSampler) {\n\t\t\t\t\t\t\t\tconst sampler = wgpuTexture.getSampler(device, textureFormat.sampleType);\n\t\t\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\t\t\tbinding: slot + 1,\n\t\t\t\t\t\t\t\t\t\tresource: sampler\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst storageTextureFormats = bindGroup.format.storageTextureFormats;\n\t\t\t\tbindGroup.storageTextures.forEach((tex, textureIndex)=>{\n\t\t\t\t\t\tconst wgpuTexture = tex.impl;\n\t\t\t\t\t\tconst slot = storageTextureFormats[textureIndex].slot;\n\t\t\t\t\t\tconst view = wgpuTexture.getView(device);\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: slot,\n\t\t\t\t\t\t\t\tresource: view\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tconst storageBufferFormats = bindGroup.format.storageBufferFormats;\n\t\t\t\tbindGroup.storageBuffers.forEach((buffer, bufferIndex)=>{\n\t\t\t\t\t\tconst wgpuBuffer = buffer.impl.buffer;\n\t\t\t\t\t\tconst slot = storageBufferFormats[bufferIndex].slot;\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: slot,\n\t\t\t\t\t\t\t\tresource: {\n\t\t\t\t\t\t\t\t\t\tbuffer: wgpuBuffer\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tconst desc = {\n\t\t\t\t\t\tlayout: bindGroup.format.impl.bindGroupLayout,\n\t\t\t\t\t\tentries: entries\n\t\t\t\t};\n\t\t\t\treturn desc;\n\t\t}\n}\n\nclass WebgpuUtils {\n\t\tstatic shaderStage(stage) {\n\t\t\t\tlet ret = 0;\n\t\t\t\tif (stage & SHADERSTAGE_VERTEX) ret |= GPUShaderStage.VERTEX;\n\t\t\t\tif (stage & SHADERSTAGE_FRAGMENT) ret |= GPUShaderStage.FRAGMENT;\n\t\t\t\tif (stage & SHADERSTAGE_COMPUTE) ret |= GPUShaderStage.COMPUTE;\n\t\t\t\treturn ret;\n\t\t}\n}\n\nconst gpuTextureFormats = [];\ngpuTextureFormats[PIXELFORMAT_A8] = '';\ngpuTextureFormats[PIXELFORMAT_L8] = '';\ngpuTextureFormats[PIXELFORMAT_LA8] = '';\ngpuTextureFormats[PIXELFORMAT_R8] = 'r8unorm';\ngpuTextureFormats[PIXELFORMAT_RG8] = 'rg8unorm';\ngpuTextureFormats[PIXELFORMAT_RGB565] = '';\ngpuTextureFormats[PIXELFORMAT_RGBA5551] = '';\ngpuTextureFormats[PIXELFORMAT_RGBA4] = '';\ngpuTextureFormats[PIXELFORMAT_RGB8] = 'rgba8unorm';\ngpuTextureFormats[PIXELFORMAT_RGBA8] = 'rgba8unorm';\ngpuTextureFormats[PIXELFORMAT_DXT1] = 'bc1-rgba-unorm';\ngpuTextureFormats[PIXELFORMAT_DXT3] = 'bc2-rgba-unorm';\ngpuTextureFormats[PIXELFORMAT_DXT5] = 'bc3-rgba-unorm';\ngpuTextureFormats[PIXELFORMAT_RGB16F] = '';\ngpuTextureFormats[PIXELFORMAT_RGBA16F] = 'rgba16float';\ngpuTextureFormats[PIXELFORMAT_R16F] = 'r16float';\ngpuTextureFormats[PIXELFORMAT_RG16F] = 'rg16float';\ngpuTextureFormats[PIXELFORMAT_RGB32F] = '';\ngpuTextureFormats[PIXELFORMAT_RGBA32F] = 'rgba32float';\ngpuTextureFormats[PIXELFORMAT_R32F] = 'r32float';\ngpuTextureFormats[PIXELFORMAT_DEPTH] = 'depth32float';\ngpuTextureFormats[PIXELFORMAT_DEPTH16] = 'depth16unorm';\ngpuTextureFormats[PIXELFORMAT_DEPTHSTENCIL] = 'depth24plus-stencil8';\ngpuTextureFormats[PIXELFORMAT_111110F] = 'rg11b10ufloat';\ngpuTextureFormats[PIXELFORMAT_SRGB8] = '';\ngpuTextureFormats[PIXELFORMAT_SRGBA8] = 'rgba8unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_ETC1] = '';\ngpuTextureFormats[PIXELFORMAT_ETC2_RGB] = 'etc2-rgb8unorm';\ngpuTextureFormats[PIXELFORMAT_ETC2_RGBA] = 'etc2-rgba8unorm';\ngpuTextureFormats[PIXELFORMAT_PVRTC_2BPP_RGB_1] = '';\ngpuTextureFormats[PIXELFORMAT_PVRTC_2BPP_RGBA_1] = '';\ngpuTextureFormats[PIXELFORMAT_PVRTC_4BPP_RGB_1] = '';\ngpuTextureFormats[PIXELFORMAT_PVRTC_4BPP_RGBA_1] = '';\ngpuTextureFormats[PIXELFORMAT_ASTC_4x4] = 'astc-4x4-unorm';\ngpuTextureFormats[PIXELFORMAT_ATC_RGB] = '';\ngpuTextureFormats[PIXELFORMAT_ATC_RGBA] = '';\ngpuTextureFormats[PIXELFORMAT_BGRA8] = 'bgra8unorm';\ngpuTextureFormats[PIXELFORMAT_SBGRA8] = 'bgra8unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_R8I] = 'r8sint';\ngpuTextureFormats[PIXELFORMAT_R8U] = 'r8uint';\ngpuTextureFormats[PIXELFORMAT_R16I] = 'r16sint';\ngpuTextureFormats[PIXELFORMAT_R16U] = 'r16uint';\ngpuTextureFormats[PIXELFORMAT_R32I] = 'r32sint';\ngpuTextureFormats[PIXELFORMAT_R32U] = 'r32uint';\ngpuTextureFormats[PIXELFORMAT_RG8I] = 'rg8sint';\ngpuTextureFormats[PIXELFORMAT_RG8U] = 'rg8uint';\ngpuTextureFormats[PIXELFORMAT_RG16I] = 'rg16sint';\ngpuTextureFormats[PIXELFORMAT_RG16U] = 'rg16uint';\ngpuTextureFormats[PIXELFORMAT_RG32I] = 'rg32sint';\ngpuTextureFormats[PIXELFORMAT_RG32U] = 'rg32uint';\ngpuTextureFormats[PIXELFORMAT_RGBA8I] = 'rgba8sint';\ngpuTextureFormats[PIXELFORMAT_RGBA8U] = 'rgba8uint';\ngpuTextureFormats[PIXELFORMAT_RGBA16I] = 'rgba16sint';\ngpuTextureFormats[PIXELFORMAT_RGBA16U] = 'rgba16uint';\ngpuTextureFormats[PIXELFORMAT_RGBA32I] = 'rgba32sint';\ngpuTextureFormats[PIXELFORMAT_RGBA32U] = 'rgba32uint';\ngpuTextureFormats[PIXELFORMAT_BC6F] = 'bc6h-rgb-float';\ngpuTextureFormats[PIXELFORMAT_BC6UF] = 'bc6h-rgb-ufloat';\ngpuTextureFormats[PIXELFORMAT_BC7] = 'bc7-rgba-unorm';\ngpuTextureFormats[PIXELFORMAT_DXT1_SRGB] = 'bc1-rgba-unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_DXT3_SRGBA] = 'bc2-rgba-unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_DXT5_SRGBA] = 'bc3-rgba-unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_ETC2_SRGB] = 'etc2-rgb8unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_ETC2_SRGBA] = 'etc2-rgba8unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_BC7_SRGBA] = 'bc7-rgba-unorm-srgb';\ngpuTextureFormats[PIXELFORMAT_ASTC_4x4_SRGB] = 'astc-4x4-unorm-srgb';\n\nconst samplerTypes = [];\nsamplerTypes[SAMPLETYPE_FLOAT] = 'filtering';\nsamplerTypes[SAMPLETYPE_UNFILTERABLE_FLOAT] = 'non-filtering';\nsamplerTypes[SAMPLETYPE_DEPTH] = 'comparison';\nsamplerTypes[SAMPLETYPE_INT] = 'comparison';\nsamplerTypes[SAMPLETYPE_UINT] = 'comparison';\nconst sampleTypes = [];\nsampleTypes[SAMPLETYPE_FLOAT] = 'float';\nsampleTypes[SAMPLETYPE_UNFILTERABLE_FLOAT] = 'unfilterable-float';\nsampleTypes[SAMPLETYPE_DEPTH] = 'depth';\nsampleTypes[SAMPLETYPE_INT] = 'sint';\nsampleTypes[SAMPLETYPE_UINT] = 'uint';\nconst stringIds$1 = new StringIds();\nclass WebgpuBindGroupFormat {\n\t\tconstructor(bindGroupFormat){\n\t\t\t\tconst device = bindGroupFormat.device;\n\t\t\t\tconst { key, desc } = this.createDescriptor(bindGroupFormat);\n\t\t\t\tthis.key = stringIds$1.get(key);\n\t\t\t\tthis.bindGroupLayout = device.wgpu.createBindGroupLayout(desc);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.bindGroupLayout = null;\n\t\t}\n\t\tloseContext() {}\n\t\tcreateDescriptor(bindGroupFormat) {\n\t\t\t\tconst entries = [];\n\t\t\t\tlet key = '';\n\t\t\t\tbindGroupFormat.uniformBufferFormats.forEach((bufferFormat)=>{\n\t\t\t\t\t\tconst visibility = WebgpuUtils.shaderStage(bufferFormat.visibility);\n\t\t\t\t\t\tkey += `#${bufferFormat.slot}U:${visibility}`;\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: bufferFormat.slot,\n\t\t\t\t\t\t\t\tvisibility: visibility,\n\t\t\t\t\t\t\t\tbuffer: {\n\t\t\t\t\t\t\t\t\t\ttype: 'uniform',\n\t\t\t\t\t\t\t\t\t\thasDynamicOffset: true\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbindGroupFormat.textureFormats.forEach((textureFormat)=>{\n\t\t\t\t\t\tconst visibility = WebgpuUtils.shaderStage(textureFormat.visibility);\n\t\t\t\t\t\tconst sampleType = textureFormat.sampleType;\n\t\t\t\t\t\tconst viewDimension = textureFormat.textureDimension;\n\t\t\t\t\t\tconst multisampled = false;\n\t\t\t\t\t\tconst gpuSampleType = sampleTypes[sampleType];\n\t\t\t\t\t\tkey += `#${textureFormat.slot}T:${visibility}-${gpuSampleType}-${viewDimension}-${multisampled}`;\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: textureFormat.slot,\n\t\t\t\t\t\t\t\tvisibility: visibility,\n\t\t\t\t\t\t\t\ttexture: {\n\t\t\t\t\t\t\t\t\t\tsampleType: gpuSampleType,\n\t\t\t\t\t\t\t\t\t\tviewDimension: viewDimension,\n\t\t\t\t\t\t\t\t\t\tmultisampled: multisampled\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (textureFormat.hasSampler) {\n\t\t\t\t\t\t\t\tconst gpuSamplerType = samplerTypes[sampleType];\n\t\t\t\t\t\t\t\tkey += `#${textureFormat.slot + 1}S:${visibility}-${gpuSamplerType}`;\n\t\t\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\t\t\tbinding: textureFormat.slot + 1,\n\t\t\t\t\t\t\t\t\t\tvisibility: visibility,\n\t\t\t\t\t\t\t\t\t\tsampler: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: gpuSamplerType\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tbindGroupFormat.storageTextureFormats.forEach((textureFormat)=>{\n\t\t\t\t\t\tconst { format, textureDimension } = textureFormat;\n\t\t\t\t\t\tconst { read, write } = textureFormat;\n\t\t\t\t\t\tkey += `#${textureFormat.slot}ST:${format}-${textureDimension}-${read ? 'r1' : 'r0'}-${write ? 'w1' : 'w0'}`;\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: textureFormat.slot,\n\t\t\t\t\t\t\t\tvisibility: GPUShaderStage.COMPUTE,\n\t\t\t\t\t\t\t\tstorageTexture: {\n\t\t\t\t\t\t\t\t\t\taccess: read ? write ? 'read-write' : 'read-only' : 'write-only',\n\t\t\t\t\t\t\t\t\t\tformat: gpuTextureFormats[format],\n\t\t\t\t\t\t\t\t\t\tviewDimension: textureDimension\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbindGroupFormat.storageBufferFormats.forEach((bufferFormat)=>{\n\t\t\t\t\t\tconst readOnly = bufferFormat.readOnly;\n\t\t\t\t\t\tconst visibility = WebgpuUtils.shaderStage(bufferFormat.visibility);\n\t\t\t\t\t\tkey += `#${bufferFormat.slot}SB:${visibility}-${readOnly ? 'ro' : 'rw'}`;\n\t\t\t\t\t\tentries.push({\n\t\t\t\t\t\t\t\tbinding: bufferFormat.slot,\n\t\t\t\t\t\t\t\tvisibility: visibility,\n\t\t\t\t\t\t\t\tbuffer: {\n\t\t\t\t\t\t\t\t\t\ttype: readOnly ? 'read-only-storage' : 'storage'\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tconst desc = {\n\t\t\t\t\t\tentries: entries\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tdesc\n\t\t\t\t};\n\t\t}\n}\n\nclass WebgpuBuffer {\n\t\tconstructor(usageFlags = 0){\n\t\t\t\tthis.buffer = null;\n\t\t\t\tthis.usageFlags = 0;\n\t\t\t\tthis.usageFlags = usageFlags;\n\t\t}\n\t\tdestroy(device) {\n\t\t\t\tif (this.buffer) {\n\t\t\t\t\t\tthis.buffer.destroy();\n\t\t\t\t\t\tthis.buffer = null;\n\t\t\t\t}\n\t\t}\n\t\tget initialized() {\n\t\t\t\treturn !!this.buffer;\n\t\t}\n\t\tloseContext() {}\n\t\tallocate(device, size) {\n\t\t\t\tthis.buffer = device.wgpu.createBuffer({\n\t\t\t\t\t\tsize,\n\t\t\t\t\t\tusage: this.usageFlags\n\t\t\t\t});\n\t\t}\n\t\tunlock(device, storage) {\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tif (!this.buffer) {\n\t\t\t\t\t\tconst size = storage.byteLength + 3 & -4;\n\t\t\t\t\t\tthis.usageFlags |= GPUBufferUsage.COPY_DST;\n\t\t\t\t\t\tthis.allocate(device, size);\n\t\t\t\t}\n\t\t\t\tconst srcOffset = storage.byteOffset ?? 0;\n\t\t\t\tconst srcData = new Uint8Array(storage.buffer ?? storage, srcOffset, storage.byteLength);\n\t\t\t\tconst data = new Uint8Array(this.buffer.size);\n\t\t\t\tdata.set(srcData);\n\t\t\t\twgpu.queue.writeBuffer(this.buffer, 0, data, 0, data.length);\n\t\t}\n\t\tread(device, offset, size, data, immediate) {\n\t\t\t\treturn device.readStorageBuffer(this, offset, size, data, immediate);\n\t\t}\n\t\twrite(device, bufferOffset, data, dataOffset, size) {\n\t\t\t\tdevice.writeStorageBuffer(this, bufferOffset, data, dataOffset, size);\n\t\t}\n\t\tclear(device, offset, size) {\n\t\t\t\tdevice.clearStorageBuffer(this, offset, size);\n\t\t}\n}\n\nclass WebgpuIndexBuffer extends WebgpuBuffer {\n\t\tconstructor(indexBuffer, options){\n\t\t\t\tsuper(BUFFERUSAGE_INDEX | (options?.storage ? BUFFERUSAGE_STORAGE : 0)), this.format = null;\n\t\t\t\tthis.format = indexBuffer.format === INDEXFORMAT_UINT16 ? 'uint16' : 'uint32';\n\t\t}\n\t\tunlock(indexBuffer) {\n\t\t\t\tconst device = indexBuffer.device;\n\t\t\t\tsuper.unlock(device, indexBuffer.storage);\n\t\t}\n}\n\nconst array$1 = {\n\t\tequals (arr1, arr2) {\n\t\t\t\tif (arr1.length !== arr2.length) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < arr1.length; i++){\n\t\t\t\t\t\tif (arr1[i] !== arr2[i]) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n};\n\nconst gpuVertexFormats = [];\ngpuVertexFormats[TYPE_INT8] = 'sint8';\ngpuVertexFormats[TYPE_UINT8] = 'uint8';\ngpuVertexFormats[TYPE_INT16] = 'sint16';\ngpuVertexFormats[TYPE_UINT16] = 'uint16';\ngpuVertexFormats[TYPE_INT32] = 'sint32';\ngpuVertexFormats[TYPE_UINT32] = 'uint32';\ngpuVertexFormats[TYPE_FLOAT32] = 'float32';\ngpuVertexFormats[TYPE_FLOAT16] = 'float16';\nconst gpuVertexFormatsNormalized = [];\ngpuVertexFormatsNormalized[TYPE_INT8] = 'snorm8';\ngpuVertexFormatsNormalized[TYPE_UINT8] = 'unorm8';\ngpuVertexFormatsNormalized[TYPE_INT16] = 'snorm16';\ngpuVertexFormatsNormalized[TYPE_UINT16] = 'unorm16';\ngpuVertexFormatsNormalized[TYPE_INT32] = 'sint32';\ngpuVertexFormatsNormalized[TYPE_UINT32] = 'uint32';\ngpuVertexFormatsNormalized[TYPE_FLOAT32] = 'float32';\ngpuVertexFormatsNormalized[TYPE_FLOAT16] = 'float16';\nclass WebgpuVertexBufferLayout {\n\t\tget(vertexFormat0, vertexFormat1 = null) {\n\t\t\t\tconst key = this.getKey(vertexFormat0, vertexFormat1);\n\t\t\t\tlet layout = this.cache.get(key);\n\t\t\t\tif (!layout) {\n\t\t\t\t\t\tlayout = this.create(vertexFormat0, vertexFormat1);\n\t\t\t\t\t\tthis.cache.set(key, layout);\n\t\t\t\t}\n\t\t\t\treturn layout;\n\t\t}\n\t\tgetKey(vertexFormat0, vertexFormat1 = null) {\n\t\t\t\treturn `${vertexFormat0?.renderingHashString}-${vertexFormat1?.renderingHashString}`;\n\t\t}\n\t\tcreate(vertexFormat0, vertexFormat1) {\n\t\t\t\tconst layout = [];\n\t\t\t\tconst addFormat = (format)=>{\n\t\t\t\t\t\tconst interleaved = format.interleaved;\n\t\t\t\t\t\tconst stepMode = format.instancing ? 'instance' : 'vertex';\n\t\t\t\t\t\tlet attributes = [];\n\t\t\t\t\t\tconst elementCount = format.elements.length;\n\t\t\t\t\t\tfor(let i = 0; i < elementCount; i++){\n\t\t\t\t\t\t\t\tconst element = format.elements[i];\n\t\t\t\t\t\t\t\tconst location = semanticToLocation[element.name];\n\t\t\t\t\t\t\t\tconst formatTable = element.normalize ? gpuVertexFormatsNormalized : gpuVertexFormats;\n\t\t\t\t\t\t\t\tattributes.push({\n\t\t\t\t\t\t\t\t\t\tshaderLocation: location,\n\t\t\t\t\t\t\t\t\t\toffset: interleaved ? element.offset : 0,\n\t\t\t\t\t\t\t\t\t\tformat: `${formatTable[element.dataType]}${element.numComponents > 1 ? `x${element.numComponents}` : ''}`\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (!interleaved || i === elementCount - 1) {\n\t\t\t\t\t\t\t\t\t\tlayout.push({\n\t\t\t\t\t\t\t\t\t\t\t\tattributes: attributes,\n\t\t\t\t\t\t\t\t\t\t\t\tarrayStride: element.stride,\n\t\t\t\t\t\t\t\t\t\t\t\tstepMode: stepMode\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tattributes = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tif (vertexFormat0) {\n\t\t\t\t\t\taddFormat(vertexFormat0);\n\t\t\t\t}\n\t\t\t\tif (vertexFormat1) {\n\t\t\t\t\t\taddFormat(vertexFormat1);\n\t\t\t\t}\n\t\t\t\treturn layout;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.cache = new Map();\n\t\t}\n}\n\nclass WebgpuPipeline {\n\t\tconstructor(device){\n\t\t\t\tthis.device = device;\n\t\t}\n\t\tgetPipelineLayout(bindGroupFormats) {\n\t\t\t\tconst bindGroupLayouts = [];\n\t\t\t\tbindGroupFormats.forEach((format)=>{\n\t\t\t\t\t\tbindGroupLayouts.push(format.bindGroupLayout);\n\t\t\t\t});\n\t\t\t\tconst desc = {\n\t\t\t\t\t\tbindGroupLayouts: bindGroupLayouts\n\t\t\t\t};\n\t\t\t\tconst pipelineLayout = this.device.wgpu.createPipelineLayout(desc);\n\t\t\t\treturn pipelineLayout;\n\t\t}\n}\n\nconst _primitiveTopology = [\n\t\t'point-list',\n\t\t'line-list',\n\t\tundefined,\n\t\t'line-strip',\n\t\t'triangle-list',\n\t\t'triangle-strip',\n\t\tundefined\n];\nconst _blendOperation = [\n\t\t'add',\n\t\t'subtract',\n\t\t'reverse-subtract',\n\t\t'min',\n\t\t'max'\n];\nconst _blendFactor = [\n\t\t'zero',\n\t\t'one',\n\t\t'src',\n\t\t'one-minus-src',\n\t\t'dst',\n\t\t'one-minus-dst',\n\t\t'src-alpha',\n\t\t'src-alpha-saturated',\n\t\t'one-minus-src-alpha',\n\t\t'dst-alpha',\n\t\t'one-minus-dst-alpha',\n\t\t'constant',\n\t\t'one-minus-constant'\n];\nconst _compareFunction = [\n\t\t'never',\n\t\t'less',\n\t\t'equal',\n\t\t'less-equal',\n\t\t'greater',\n\t\t'not-equal',\n\t\t'greater-equal',\n\t\t'always'\n];\nconst _cullModes = [\n\t\t'none',\n\t\t'back',\n\t\t'front'\n];\nconst _stencilOps = [\n\t\t'keep',\n\t\t'zero',\n\t\t'replace',\n\t\t'increment-clamp',\n\t\t'increment-wrap',\n\t\t'decrement-clamp',\n\t\t'decrement-wrap',\n\t\t'invert'\n];\nconst _indexFormat = [\n\t\t'',\n\t\t'uint16',\n\t\t'uint32'\n];\nclass CacheEntry {\n}\nclass WebgpuRenderPipeline extends WebgpuPipeline {\n\t\tconstructor(device){\n\t\t\t\tsuper(device), this.lookupHashes = new Uint32Array(14);\n\t\t\t\tthis.vertexBufferLayout = new WebgpuVertexBufferLayout();\n\t\t\t\tthis.cache = new Map();\n\t\t}\n\t\tget(primitive, vertexFormat0, vertexFormat1, ibFormat, shader, renderTarget, bindGroupFormats, blendState, depthState, cullMode, stencilEnabled, stencilFront, stencilBack) {\n\t\t\t\tconst primitiveType = primitive.type;\n\t\t\t\tif (ibFormat && primitiveType !== PRIMITIVE_LINESTRIP && primitiveType !== PRIMITIVE_TRISTRIP) {\n\t\t\t\t\t\tibFormat = undefined;\n\t\t\t\t}\n\t\t\t\tconst lookupHashes = this.lookupHashes;\n\t\t\t\tlookupHashes[0] = primitiveType;\n\t\t\t\tlookupHashes[1] = shader.id;\n\t\t\t\tlookupHashes[2] = cullMode;\n\t\t\t\tlookupHashes[3] = depthState.key;\n\t\t\t\tlookupHashes[4] = blendState.key;\n\t\t\t\tlookupHashes[5] = vertexFormat0?.renderingHash ?? 0;\n\t\t\t\tlookupHashes[6] = vertexFormat1?.renderingHash ?? 0;\n\t\t\t\tlookupHashes[7] = renderTarget.impl.key;\n\t\t\t\tlookupHashes[8] = bindGroupFormats[0]?.key ?? 0;\n\t\t\t\tlookupHashes[9] = bindGroupFormats[1]?.key ?? 0;\n\t\t\t\tlookupHashes[10] = bindGroupFormats[2]?.key ?? 0;\n\t\t\t\tlookupHashes[11] = stencilEnabled ? stencilFront.key : 0;\n\t\t\t\tlookupHashes[12] = stencilEnabled ? stencilBack.key : 0;\n\t\t\t\tlookupHashes[13] = ibFormat ?? 0;\n\t\t\t\tconst hash = hash32Fnv1a(lookupHashes);\n\t\t\t\tlet cacheEntries = this.cache.get(hash);\n\t\t\t\tif (cacheEntries) {\n\t\t\t\t\t\tfor(let i = 0; i < cacheEntries.length; i++){\n\t\t\t\t\t\t\t\tconst entry = cacheEntries[i];\n\t\t\t\t\t\t\t\tif (array$1.equals(entry.hashes, lookupHashes)) {\n\t\t\t\t\t\t\t\t\t\treturn entry.pipeline;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst primitiveTopology = _primitiveTopology[primitiveType];\n\t\t\t\tconst pipelineLayout = this.getPipelineLayout(bindGroupFormats);\n\t\t\t\tconst vertexBufferLayout = this.vertexBufferLayout.get(vertexFormat0, vertexFormat1);\n\t\t\t\tconst cacheEntry = new CacheEntry();\n\t\t\t\tcacheEntry.hashes = new Uint32Array(lookupHashes);\n\t\t\t\tcacheEntry.pipeline = this.create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack);\n\t\t\t\tif (cacheEntries) {\n\t\t\t\t\t\tcacheEntries.push(cacheEntry);\n\t\t\t\t} else {\n\t\t\t\t\t\tcacheEntries = [\n\t\t\t\t\t\t\t\tcacheEntry\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tthis.cache.set(hash, cacheEntries);\n\t\t\t\treturn cacheEntry.pipeline;\n\t\t}\n\t\tgetBlend(blendState) {\n\t\t\t\tlet blend;\n\t\t\t\tif (blendState.blend) {\n\t\t\t\t\t\tblend = {\n\t\t\t\t\t\t\t\tcolor: {\n\t\t\t\t\t\t\t\t\t\toperation: _blendOperation[blendState.colorOp],\n\t\t\t\t\t\t\t\t\t\tsrcFactor: _blendFactor[blendState.colorSrcFactor],\n\t\t\t\t\t\t\t\t\t\tdstFactor: _blendFactor[blendState.colorDstFactor]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\talpha: {\n\t\t\t\t\t\t\t\t\t\toperation: _blendOperation[blendState.alphaOp],\n\t\t\t\t\t\t\t\t\t\tsrcFactor: _blendFactor[blendState.alphaSrcFactor],\n\t\t\t\t\t\t\t\t\t\tdstFactor: _blendFactor[blendState.alphaDstFactor]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn blend;\n\t\t}\n\t\tgetDepthStencil(depthState, renderTarget, stencilEnabled, stencilFront, stencilBack, primitiveTopology) {\n\t\t\t\tlet depthStencil;\n\t\t\t\tconst { depth, stencil } = renderTarget;\n\t\t\t\tif (depth || stencil) {\n\t\t\t\t\t\tdepthStencil = {\n\t\t\t\t\t\t\t\tformat: renderTarget.impl.depthAttachment.format\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (depth) {\n\t\t\t\t\t\t\t\tdepthStencil.depthWriteEnabled = depthState.write;\n\t\t\t\t\t\t\t\tdepthStencil.depthCompare = _compareFunction[depthState.func];\n\t\t\t\t\t\t\t\tconst biasAllowed = primitiveTopology === 'triangle-list' || primitiveTopology === 'triangle-strip';\n\t\t\t\t\t\t\t\tdepthStencil.depthBias = biasAllowed ? depthState.depthBias : 0;\n\t\t\t\t\t\t\t\tdepthStencil.depthBiasSlopeScale = biasAllowed ? depthState.depthBiasSlope : 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdepthStencil.depthWriteEnabled = false;\n\t\t\t\t\t\t\t\tdepthStencil.depthCompare = 'always';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (stencil && stencilEnabled) {\n\t\t\t\t\t\t\t\tdepthStencil.stencilReadMas = stencilFront.readMask;\n\t\t\t\t\t\t\t\tdepthStencil.stencilWriteMask = stencilFront.writeMask;\n\t\t\t\t\t\t\t\tdepthStencil.stencilFront = {\n\t\t\t\t\t\t\t\t\t\tcompare: _compareFunction[stencilFront.func],\n\t\t\t\t\t\t\t\t\t\tfailOp: _stencilOps[stencilFront.fail],\n\t\t\t\t\t\t\t\t\t\tpassOp: _stencilOps[stencilFront.zpass],\n\t\t\t\t\t\t\t\t\t\tdepthFailOp: _stencilOps[stencilFront.zfail]\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tdepthStencil.stencilBack = {\n\t\t\t\t\t\t\t\t\t\tcompare: _compareFunction[stencilBack.func],\n\t\t\t\t\t\t\t\t\t\tfailOp: _stencilOps[stencilBack.fail],\n\t\t\t\t\t\t\t\t\t\tpassOp: _stencilOps[stencilBack.zpass],\n\t\t\t\t\t\t\t\t\t\tdepthFailOp: _stencilOps[stencilBack.zfail]\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn depthStencil;\n\t\t}\n\t\tcreate(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack) {\n\t\t\t\tconst wgpu = this.device.wgpu;\n\t\t\t\tconst webgpuShader = shader.impl;\n\t\t\t\tconst desc = {\n\t\t\t\t\t\tvertex: {\n\t\t\t\t\t\t\t\tmodule: webgpuShader.getVertexShaderModule(),\n\t\t\t\t\t\t\t\tentryPoint: webgpuShader.vertexEntryPoint,\n\t\t\t\t\t\t\t\tbuffers: vertexBufferLayout\n\t\t\t\t\t\t},\n\t\t\t\t\t\tprimitive: {\n\t\t\t\t\t\t\t\ttopology: primitiveTopology,\n\t\t\t\t\t\t\t\tfrontFace: 'ccw',\n\t\t\t\t\t\t\t\tcullMode: _cullModes[cullMode]\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdepthStencil: this.getDepthStencil(depthState, renderTarget, stencilEnabled, stencilFront, stencilBack, primitiveTopology),\n\t\t\t\t\t\tmultisample: {\n\t\t\t\t\t\t\t\tcount: renderTarget.samples\n\t\t\t\t\t\t},\n\t\t\t\t\t\tlayout: pipelineLayout\n\t\t\t\t};\n\t\t\t\tif (ibFormat) {\n\t\t\t\t\t\tdesc.primitive.stripIndexFormat = _indexFormat[ibFormat];\n\t\t\t\t}\n\t\t\t\tdesc.fragment = {\n\t\t\t\t\t\tmodule: webgpuShader.getFragmentShaderModule(),\n\t\t\t\t\t\tentryPoint: webgpuShader.fragmentEntryPoint,\n\t\t\t\t\t\ttargets: []\n\t\t\t\t};\n\t\t\t\tconst colorAttachments = renderTarget.impl.colorAttachments;\n\t\t\t\tif (colorAttachments.length > 0) {\n\t\t\t\t\t\tlet writeMask = 0;\n\t\t\t\t\t\tif (blendState.redWrite) writeMask |= GPUColorWrite.RED;\n\t\t\t\t\t\tif (blendState.greenWrite) writeMask |= GPUColorWrite.GREEN;\n\t\t\t\t\t\tif (blendState.blueWrite) writeMask |= GPUColorWrite.BLUE;\n\t\t\t\t\t\tif (blendState.alphaWrite) writeMask |= GPUColorWrite.ALPHA;\n\t\t\t\t\t\tconst blend = this.getBlend(blendState);\n\t\t\t\t\t\tcolorAttachments.forEach((attachment)=>{\n\t\t\t\t\t\t\t\tdesc.fragment.targets.push({\n\t\t\t\t\t\t\t\t\t\tformat: attachment.format,\n\t\t\t\t\t\t\t\t\t\twriteMask: writeMask,\n\t\t\t\t\t\t\t\t\t\tblend: blend\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst pipeline = wgpu.createRenderPipeline(desc);\n\t\t\t\treturn pipeline;\n\t\t}\n}\n\nclass WebgpuComputePipeline extends WebgpuPipeline {\n\t\tget(shader, bindGroupFormat) {\n\t\t\t\tconst pipelineLayout = this.getPipelineLayout([\n\t\t\t\t\t\tbindGroupFormat.impl\n\t\t\t\t]);\n\t\t\t\tconst pipeline = this.create(shader, pipelineLayout);\n\t\t\t\treturn pipeline;\n\t\t}\n\t\tcreate(shader, pipelineLayout) {\n\t\t\t\tconst wgpu = this.device.wgpu;\n\t\t\t\tconst webgpuShader = shader.impl;\n\t\t\t\tconst desc = {\n\t\t\t\t\t\tcompute: {\n\t\t\t\t\t\t\t\tmodule: webgpuShader.getComputeShaderModule(),\n\t\t\t\t\t\t\t\tentryPoint: webgpuShader.computeEntryPoint\n\t\t\t\t\t\t},\n\t\t\t\t\t\tlayout: pipelineLayout\n\t\t\t\t};\n\t\t\t\tconst pipeline = wgpu.createComputePipeline(desc);\n\t\t\t\treturn pipeline;\n\t\t}\n}\n\nclass RefCountedObject {\n\t\tincRefCount() {\n\t\t\t\tthis._refCount++;\n\t\t}\n\t\tdecRefCount() {\n\t\t\t\tthis._refCount--;\n\t\t}\n\t\tget refCount() {\n\t\t\t\treturn this._refCount;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis._refCount = 0;\n\t\t}\n}\n\nclass Entry extends RefCountedObject {\n\t\tconstructor(obj){\n\t\t\t\tsuper();\n\t\t\t\tthis.object = obj;\n\t\t\t\tthis.incRefCount();\n\t\t}\n}\nclass RefCountedKeyCache {\n\t\tdestroy() {\n\t\t\t\tthis.cache.forEach((entry)=>{\n\t\t\t\t\t\tentry.object?.destroy();\n\t\t\t\t});\n\t\t\t\tthis.cache.clear();\n\t\t}\n\t\tclear() {\n\t\t\t\tthis.cache.clear();\n\t\t}\n\t\tget(key) {\n\t\t\t\tconst entry = this.cache.get(key);\n\t\t\t\tif (entry) {\n\t\t\t\t\t\tentry.incRefCount();\n\t\t\t\t\t\treturn entry.object;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset(key, object) {\n\t\t\t\tthis.cache.set(key, new Entry(object));\n\t\t}\n\t\trelease(key) {\n\t\t\t\tconst entry = this.cache.get(key);\n\t\t\t\tif (entry) {\n\t\t\t\t\t\tentry.decRefCount();\n\t\t\t\t\t\tif (entry.refCount === 0) {\n\t\t\t\t\t\t\t\tthis.cache.delete(key);\n\t\t\t\t\t\t\t\tentry.object?.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.cache = new Map();\n\t\t}\n}\n\nclass MultisampledTextureCache extends RefCountedKeyCache {\n\t\tloseContext(device) {\n\t\t\t\tthis.clear();\n\t\t}\n}\nconst multisampledTextureCache = new DeviceCache();\nconst getMultisampledTextureCache = (device)=>{\n\t\treturn multisampledTextureCache.get(device, ()=>{\n\t\t\t\treturn new MultisampledTextureCache();\n\t\t});\n};\n\nconst stringIds = new StringIds();\nclass ColorAttachment {\n\t\tdestroy() {\n\t\t\t\tthis.multisampledBuffer?.destroy();\n\t\t\t\tthis.multisampledBuffer = null;\n\t\t}\n}\nclass DepthAttachment {\n\t\tconstructor(gpuFormat){\n\t\t\t\tthis.depthTexture = null;\n\t\t\t\tthis.depthTextureInternal = false;\n\t\t\t\tthis.multisampledDepthBuffer = null;\n\t\t\t\tthis.format = gpuFormat;\n\t\t\t\tthis.hasStencil = gpuFormat === 'depth24plus-stencil8';\n\t\t}\n\t\tdestroy(device) {\n\t\t\t\tif (this.depthTextureInternal) {\n\t\t\t\t\t\tthis.depthTexture?.destroy();\n\t\t\t\t\t\tthis.depthTexture = null;\n\t\t\t\t}\n\t\t\t\tif (this.multisampledDepthBuffer) {\n\t\t\t\t\t\tthis.multisampledDepthBuffer = null;\n\t\t\t\t\t\tgetMultisampledTextureCache(device).release(this.multisampledDepthBufferKey);\n\t\t\t\t}\n\t\t}\n}\nclass WebgpuRenderTarget {\n\t\tconstructor(renderTarget){\n\t\t\t\tthis.initialized = false;\n\t\t\t\tthis.colorAttachments = [];\n\t\t\t\tthis.depthAttachment = null;\n\t\t\t\tthis.assignedColorTexture = null;\n\t\t\t\tthis.renderPassDescriptor = {};\n\t\t\t\tthis.isBackbuffer = false;\n\t\t\t\tthis.renderTarget = renderTarget;\n\t\t}\n\t\tdestroy(device) {\n\t\t\t\tthis.initialized = false;\n\t\t\t\tthis.assignedColorTexture = null;\n\t\t\t\tthis.colorAttachments.forEach((colorAttachment)=>{\n\t\t\t\t\t\tcolorAttachment.destroy();\n\t\t\t\t});\n\t\t\t\tthis.colorAttachments.length = 0;\n\t\t\t\tthis.depthAttachment?.destroy(device);\n\t\t\t\tthis.depthAttachment = null;\n\t\t}\n\t\tupdateKey() {\n\t\t\t\tconst rt = this.renderTarget;\n\t\t\t\tlet key = `${rt.samples}:${this.depthAttachment ? this.depthAttachment.format : 'nodepth'}`;\n\t\t\t\tthis.colorAttachments.forEach((colorAttachment)=>{\n\t\t\t\t\t\tkey += `:${colorAttachment.format}`;\n\t\t\t\t});\n\t\t\t\tthis.key = stringIds.get(key);\n\t\t}\n\t\tassignColorTexture(device, gpuTexture) {\n\t\t\t\tthis.assignedColorTexture = gpuTexture;\n\t\t\t\tconst view = gpuTexture.createView({\n\t\t\t\t\t\tformat: device.backBufferViewFormat\n\t\t\t\t});\n\t\t\t\tconst colorAttachment = this.renderPassDescriptor.colorAttachments[0];\n\t\t\t\tconst samples = this.renderTarget.samples;\n\t\t\t\tif (samples > 1) {\n\t\t\t\t\t\tcolorAttachment.resolveTarget = view;\n\t\t\t\t} else {\n\t\t\t\t\t\tcolorAttachment.view = view;\n\t\t\t\t}\n\t\t\t\tthis.setColorAttachment(0, undefined, device.backBufferViewFormat);\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tsetColorAttachment(index, multisampledBuffer, format) {\n\t\t\t\tif (!this.colorAttachments[index]) {\n\t\t\t\t\t\tthis.colorAttachments[index] = new ColorAttachment();\n\t\t\t\t}\n\t\t\t\tif (multisampledBuffer) {\n\t\t\t\t\t\tthis.colorAttachments[index].multisampledBuffer = multisampledBuffer;\n\t\t\t\t}\n\t\t\t\tif (format) {\n\t\t\t\t\t\tthis.colorAttachments[index].format = format;\n\t\t\t\t}\n\t\t}\n\t\tinit(device, renderTarget) {\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tthis.initDepthStencil(device, wgpu, renderTarget);\n\t\t\t\tif (renderTarget._colorBuffers) {\n\t\t\t\t\t\trenderTarget._colorBuffers.forEach((colorBuffer, index)=>{\n\t\t\t\t\t\t\t\tthis.setColorAttachment(index, undefined, colorBuffer.impl.format);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthis.renderPassDescriptor.colorAttachments = [];\n\t\t\t\tconst count = this.isBackbuffer ? 1 : renderTarget._colorBuffers?.length ?? 0;\n\t\t\t\tfor(let i = 0; i < count; ++i){\n\t\t\t\t\t\tconst colorAttachment = this.initColor(device, wgpu, renderTarget, i);\n\t\t\t\t\t\tconst isDefaultFramebuffer = i === 0 && this.colorAttachments[0]?.format;\n\t\t\t\t\t\tif (colorAttachment.view || isDefaultFramebuffer) {\n\t\t\t\t\t\t\t\tthis.renderPassDescriptor.colorAttachments.push(colorAttachment);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.updateKey();\n\t\t\t\tthis.initialized = true;\n\t\t}\n\t\tinitDepthStencil(device, wgpu, renderTarget) {\n\t\t\t\tconst { samples, width, height, depth, depthBuffer } = renderTarget;\n\t\t\t\tif (depth || depthBuffer) {\n\t\t\t\t\t\tlet renderingView;\n\t\t\t\t\t\tif (!depthBuffer) {\n\t\t\t\t\t\t\t\tthis.depthAttachment = new DepthAttachment('depth24plus-stencil8');\n\t\t\t\t\t\t\t\tconst depthTextureDesc = {\n\t\t\t\t\t\t\t\t\t\tsize: [\n\t\t\t\t\t\t\t\t\t\t\t\twidth,\n\t\t\t\t\t\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\t\t\tsampleCount: samples,\n\t\t\t\t\t\t\t\t\t\tformat: this.depthAttachment.format,\n\t\t\t\t\t\t\t\t\t\tusage: GPUTextureUsage.RENDER_ATTACHMENT\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif (samples > 1) {\n\t\t\t\t\t\t\t\t\t\tdepthTextureDesc.usage |= GPUTextureUsage.TEXTURE_BINDING;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdepthTextureDesc.usage |= GPUTextureUsage.COPY_SRC;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst depthTexture = wgpu.createTexture(depthTextureDesc);\n\t\t\t\t\t\t\t\tthis.depthAttachment.depthTexture = depthTexture;\n\t\t\t\t\t\t\t\tthis.depthAttachment.depthTextureInternal = true;\n\t\t\t\t\t\t\t\trenderingView = depthTexture.createView();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.depthAttachment = new DepthAttachment(depthBuffer.impl.format);\n\t\t\t\t\t\t\t\tif (samples > 1) {\n\t\t\t\t\t\t\t\t\t\tconst depthFormat = 'depth24plus-stencil8';\n\t\t\t\t\t\t\t\t\t\tthis.depthAttachment.format = depthFormat;\n\t\t\t\t\t\t\t\t\t\tthis.depthAttachment.hasStencil = depthFormat === 'depth24plus-stencil8';\n\t\t\t\t\t\t\t\t\t\tconst key = `${depthBuffer.id}:${width}:${height}:${samples}:${depthFormat}`;\n\t\t\t\t\t\t\t\t\t\tconst msTextures = getMultisampledTextureCache(device);\n\t\t\t\t\t\t\t\t\t\tlet msDepthTexture = msTextures.get(key);\n\t\t\t\t\t\t\t\t\t\tif (!msDepthTexture) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst multisampledDepthDesc = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize: [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twidth,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsampleCount: samples,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: depthFormat,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tusage: GPUTextureUsage.RENDER_ATTACHMENT | (depthFormat !== depthBuffer.impl.format ? GPUTextureUsage.TEXTURE_BINDING : 0)\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\tmsDepthTexture = wgpu.createTexture(multisampledDepthDesc);\n\t\t\t\t\t\t\t\t\t\t\t\tmsTextures.set(key, msDepthTexture);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.depthAttachment.multisampledDepthBuffer = msDepthTexture;\n\t\t\t\t\t\t\t\t\t\tthis.depthAttachment.multisampledDepthBufferKey = key;\n\t\t\t\t\t\t\t\t\t\trenderingView = msDepthTexture.createView();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst depthTexture = depthBuffer.impl.gpuTexture;\n\t\t\t\t\t\t\t\t\t\tthis.depthAttachment.depthTexture = depthTexture;\n\t\t\t\t\t\t\t\t\t\trenderingView = depthTexture.createView();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.renderPassDescriptor.depthStencilAttachment = {\n\t\t\t\t\t\t\t\tview: renderingView\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tinitColor(device, wgpu, renderTarget, index) {\n\t\t\t\tconst colorAttachment = {};\n\t\t\t\tconst { samples, width, height, mipLevel } = renderTarget;\n\t\t\t\tconst colorBuffer = renderTarget.getColorBuffer(index);\n\t\t\t\tlet colorView = null;\n\t\t\t\tif (colorBuffer) {\n\t\t\t\t\t\tconst mipLevelCount = 1;\n\t\t\t\t\t\tif (colorBuffer.cubemap) {\n\t\t\t\t\t\t\t\tcolorView = colorBuffer.impl.createView({\n\t\t\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\t\t\tbaseArrayLayer: renderTarget.face,\n\t\t\t\t\t\t\t\t\t\tarrayLayerCount: 1,\n\t\t\t\t\t\t\t\t\t\tmipLevelCount,\n\t\t\t\t\t\t\t\t\t\tbaseMipLevel: mipLevel\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcolorView = colorBuffer.impl.createView({\n\t\t\t\t\t\t\t\t\t\tmipLevelCount,\n\t\t\t\t\t\t\t\t\t\tbaseMipLevel: mipLevel\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (samples > 1) {\n\t\t\t\t\t\tconst format = this.isBackbuffer ? device.backBufferViewFormat : colorBuffer.impl.format;\n\t\t\t\t\t\tconst multisampledTextureDesc = {\n\t\t\t\t\t\t\t\tsize: [\n\t\t\t\t\t\t\t\t\t\twidth,\n\t\t\t\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\tsampleCount: samples,\n\t\t\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\t\t\tusage: GPUTextureUsage.RENDER_ATTACHMENT\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst multisampledColorBuffer = wgpu.createTexture(multisampledTextureDesc);\n\t\t\t\t\t\tthis.setColorAttachment(index, multisampledColorBuffer, multisampledTextureDesc.format);\n\t\t\t\t\t\tcolorAttachment.view = multisampledColorBuffer.createView();\n\t\t\t\t\t\tcolorAttachment.resolveTarget = colorView;\n\t\t\t\t} else {\n\t\t\t\t\t\tcolorAttachment.view = colorView;\n\t\t\t\t}\n\t\t\t\treturn colorAttachment;\n\t\t}\n\t\tsetupForRenderPass(renderPass, renderTarget) {\n\t\t\t\tconst count = this.renderPassDescriptor.colorAttachments?.length ?? 0;\n\t\t\t\tfor(let i = 0; i < count; ++i){\n\t\t\t\t\t\tconst colorAttachment = this.renderPassDescriptor.colorAttachments[i];\n\t\t\t\t\t\tconst colorOps = renderPass.colorArrayOps[i];\n\t\t\t\t\t\tconst srgb = renderTarget.isColorBufferSrgb(i);\n\t\t\t\t\t\tcolorAttachment.clearValue = srgb ? colorOps.clearValueLinear : colorOps.clearValue;\n\t\t\t\t\t\tcolorAttachment.loadOp = colorOps.clear ? 'clear' : 'load';\n\t\t\t\t\t\tcolorAttachment.storeOp = colorOps.store ? 'store' : 'discard';\n\t\t\t\t}\n\t\t\t\tconst depthAttachment = this.renderPassDescriptor.depthStencilAttachment;\n\t\t\t\tif (depthAttachment) {\n\t\t\t\t\t\tdepthAttachment.depthClearValue = renderPass.depthStencilOps.clearDepthValue;\n\t\t\t\t\t\tdepthAttachment.depthLoadOp = renderPass.depthStencilOps.clearDepth ? 'clear' : 'load';\n\t\t\t\t\t\tdepthAttachment.depthStoreOp = renderPass.depthStencilOps.storeDepth ? 'store' : 'discard';\n\t\t\t\t\t\tdepthAttachment.depthReadOnly = false;\n\t\t\t\t\t\tif (this.depthAttachment.hasStencil) {\n\t\t\t\t\t\t\t\tdepthAttachment.stencilClearValue = renderPass.depthStencilOps.clearStencilValue;\n\t\t\t\t\t\t\t\tdepthAttachment.stencilLoadOp = renderPass.depthStencilOps.clearStencil ? 'clear' : 'load';\n\t\t\t\t\t\t\t\tdepthAttachment.stencilStoreOp = renderPass.depthStencilOps.storeStencil ? 'store' : 'discard';\n\t\t\t\t\t\t\t\tdepthAttachment.stencilReadOnly = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.initialized = false;\n\t\t}\n\t\tresolve(device, target, color, depth) {}\n}\n\nconst uniformTypeToNumComponents = [];\nuniformTypeToNumComponents[UNIFORMTYPE_FLOAT] = 1;\nuniformTypeToNumComponents[UNIFORMTYPE_VEC2] = 2;\nuniformTypeToNumComponents[UNIFORMTYPE_VEC3] = 3;\nuniformTypeToNumComponents[UNIFORMTYPE_VEC4] = 4;\nuniformTypeToNumComponents[UNIFORMTYPE_INT] = 1;\nuniformTypeToNumComponents[UNIFORMTYPE_IVEC2] = 2;\nuniformTypeToNumComponents[UNIFORMTYPE_IVEC3] = 3;\nuniformTypeToNumComponents[UNIFORMTYPE_IVEC4] = 4;\nuniformTypeToNumComponents[UNIFORMTYPE_BOOL] = 1;\nuniformTypeToNumComponents[UNIFORMTYPE_BVEC2] = 2;\nuniformTypeToNumComponents[UNIFORMTYPE_BVEC3] = 3;\nuniformTypeToNumComponents[UNIFORMTYPE_BVEC4] = 4;\nuniformTypeToNumComponents[UNIFORMTYPE_MAT2] = 8;\nuniformTypeToNumComponents[UNIFORMTYPE_MAT3] = 12;\nuniformTypeToNumComponents[UNIFORMTYPE_MAT4] = 16;\nuniformTypeToNumComponents[UNIFORMTYPE_UINT] = 1;\nuniformTypeToNumComponents[UNIFORMTYPE_UVEC2] = 2;\nuniformTypeToNumComponents[UNIFORMTYPE_UVEC3] = 3;\nuniformTypeToNumComponents[UNIFORMTYPE_UVEC4] = 4;\nclass UniformFormat {\n\t\tget isArrayType() {\n\t\t\t\treturn this.count > 0;\n\t\t}\n\t\tconstructor(name, type, count = 0){\n\t\t\t\tthis.shortName = name;\n\t\t\t\tthis.name = count ? `${name}[0]` : name;\n\t\t\t\tthis.type = type;\n\t\t\t\tthis.numComponents = uniformTypeToNumComponents[type];\n\t\t\t\tthis.updateType = type;\n\t\t\t\tif (count > 0) {\n\t\t\t\t\t\tswitch(type){\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_FLOAT:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_FLOATARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_INT:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_INTARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UINT:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_UINTARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BOOL:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_BOOLARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_VEC2:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_VEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_IVEC2:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_IVEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UVEC2:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_UVEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BVEC2:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_BVEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_VEC3:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_VEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_IVEC3:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_IVEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UVEC3:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_UVEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BVEC3:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_BVEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_VEC4:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_VEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_IVEC4:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_IVEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UVEC4:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_UVEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BVEC4:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_BVEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_MAT4:\n\t\t\t\t\t\t\t\t\t\tthis.updateType = UNIFORMTYPE_MAT4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.count = count;\n\t\t\t\tlet componentSize = this.numComponents;\n\t\t\t\tif (count) {\n\t\t\t\t\t\tcomponentSize = math.roundUp(componentSize, 4);\n\t\t\t\t}\n\t\t\t\tthis.byteSize = componentSize * 4;\n\t\t\t\tif (count) {\n\t\t\t\t\t\tthis.byteSize *= count;\n\t\t\t\t}\n\t\t}\n\t\tcalculateOffset(offset) {\n\t\t\t\tlet alignment = this.byteSize <= 8 ? this.byteSize : 16;\n\t\t\t\tif (this.count) {\n\t\t\t\t\t\talignment = 16;\n\t\t\t\t}\n\t\t\t\toffset = math.roundUp(offset, alignment);\n\t\t\t\tthis.offset = offset / 4;\n\t\t}\n}\nclass UniformBufferFormat {\n\t\tconstructor(graphicsDevice, uniforms){\n\t\t\t\tthis.byteSize = 0;\n\t\t\t\tthis.map = new Map();\n\t\t\t\tthis.scope = graphicsDevice.scope;\n\t\t\t\tthis.uniforms = uniforms;\n\t\t\t\tlet offset = 0;\n\t\t\t\tfor(let i = 0; i < uniforms.length; i++){\n\t\t\t\t\t\tconst uniform = uniforms[i];\n\t\t\t\t\t\tuniform.calculateOffset(offset);\n\t\t\t\t\t\toffset = uniform.offset * 4 + uniform.byteSize;\n\t\t\t\t\t\tuniform.scopeId = this.scope.resolve(uniform.name);\n\t\t\t\t\t\tthis.map.set(uniform.name, uniform);\n\t\t\t\t}\n\t\t\t\tthis.byteSize = math.roundUp(offset, 16);\n\t\t}\n\t\tget(name) {\n\t\t\t\treturn this.map.get(name);\n\t\t}\n}\n\nconst KEYWORD$2 = /[ \\t]*(\\battribute\\b|\\bvarying\\b|\\buniform\\b)/g;\nconst KEYWORD_LINE$1 = /(\\battribute\\b|\\bvarying\\b|\\bout\\b|\\buniform\\b)[ \\t]*([^;]+)(;+)/g;\nconst MARKER$1 = '@@@';\nconst ARRAY_IDENTIFIER = /([\\w-]+)\\[(.*?)\\]/;\nconst precisionQualifiers = new Set([\n\t\t'highp',\n\t\t'mediump',\n\t\t'lowp'\n]);\nconst shadowSamplers = new Set([\n\t\t'sampler2DShadow',\n\t\t'samplerCubeShadow',\n\t\t'sampler2DArrayShadow'\n]);\nconst textureDimensions = {\n\t\tsampler2D: TEXTUREDIMENSION_2D,\n\t\tsampler3D: TEXTUREDIMENSION_3D,\n\t\tsamplerCube: TEXTUREDIMENSION_CUBE,\n\t\tsamplerCubeShadow: TEXTUREDIMENSION_CUBE,\n\t\tsampler2DShadow: TEXTUREDIMENSION_2D,\n\t\tsampler2DArray: TEXTUREDIMENSION_2D_ARRAY,\n\t\tsampler2DArrayShadow: TEXTUREDIMENSION_2D_ARRAY,\n\t\tisampler2D: TEXTUREDIMENSION_2D,\n\t\tusampler2D: TEXTUREDIMENSION_2D,\n\t\tisampler3D: TEXTUREDIMENSION_3D,\n\t\tusampler3D: TEXTUREDIMENSION_3D,\n\t\tisamplerCube: TEXTUREDIMENSION_CUBE,\n\t\tusamplerCube: TEXTUREDIMENSION_CUBE,\n\t\tisampler2DArray: TEXTUREDIMENSION_2D_ARRAY,\n\t\tusampler2DArray: TEXTUREDIMENSION_2D_ARRAY\n};\nconst textureDimensionInfo = {\n\t\t[TEXTUREDIMENSION_2D]: 'texture2D',\n\t\t[TEXTUREDIMENSION_CUBE]: 'textureCube',\n\t\t[TEXTUREDIMENSION_3D]: 'texture3D',\n\t\t[TEXTUREDIMENSION_2D_ARRAY]: 'texture2DArray'\n};\nlet UniformLine$1 = class UniformLine {\n\t\tconstructor(line, shader){\n\t\t\t\tthis.line = line;\n\t\t\t\tconst words = line.trim().split(/\\s+/);\n\t\t\t\tif (precisionQualifiers.has(words[0])) {\n\t\t\t\t\t\tthis.precision = words.shift();\n\t\t\t\t}\n\t\t\t\tthis.type = words.shift();\n\t\t\t\tif (line.includes(',')) ;\n\t\t\t\tif (line.includes('[')) {\n\t\t\t\t\t\tconst rest = words.join(' ');\n\t\t\t\t\t\tconst match = ARRAY_IDENTIFIER.exec(rest);\n\t\t\t\t\t\tthis.name = match[1];\n\t\t\t\t\t\tthis.arraySize = Number(match[2]);\n\t\t\t\t\t\tif (isNaN(this.arraySize)) {\n\t\t\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.name = words.shift();\n\t\t\t\t\t\tthis.arraySize = 0;\n\t\t\t\t}\n\t\t\t\tthis.isSampler = this.type.indexOf('sampler') !== -1;\n\t\t\t\tthis.isSignedInt = this.type.indexOf('isampler') !== -1;\n\t\t\t\tthis.isUnsignedInt = this.type.indexOf('usampler') !== -1;\n\t\t}\n};\nclass ShaderProcessorGLSL {\n\t\tstatic run(device, shaderDefinition, shader) {\n\t\t\t\tconst varyingMap = new Map();\n\t\t\t\tconst vertexExtracted = ShaderProcessorGLSL.extract(shaderDefinition.vshader);\n\t\t\t\tconst fragmentExtracted = ShaderProcessorGLSL.extract(shaderDefinition.fshader);\n\t\t\t\tconst attributesMap = new Map();\n\t\t\t\tconst attributesBlock = ShaderProcessorGLSL.processAttributes(vertexExtracted.attributes, shaderDefinition.attributes, attributesMap, shaderDefinition.processingOptions);\n\t\t\t\tconst vertexVaryingsBlock = ShaderProcessorGLSL.processVaryings(vertexExtracted.varyings, varyingMap, true);\n\t\t\t\tconst fragmentVaryingsBlock = ShaderProcessorGLSL.processVaryings(fragmentExtracted.varyings, varyingMap, false);\n\t\t\t\tconst outBlock = ShaderProcessorGLSL.processOuts(fragmentExtracted.outs);\n\t\t\t\tconst concatUniforms = vertexExtracted.uniforms.concat(fragmentExtracted.uniforms);\n\t\t\t\tconst uniforms = Array.from(new Set(concatUniforms));\n\t\t\t\tconst parsedUniforms = uniforms.map((line)=>new UniformLine$1(line, shader));\n\t\t\t\tconst uniformsData = ShaderProcessorGLSL.processUniforms(device, parsedUniforms, shaderDefinition.processingOptions, shader);\n\t\t\t\tconst vBlock = `${attributesBlock}\\n${vertexVaryingsBlock}\\n${uniformsData.code}`;\n\t\t\t\tconst vshader = vertexExtracted.src.replace(MARKER$1, vBlock);\n\t\t\t\tconst fBlock = `${fragmentVaryingsBlock}\\n${outBlock}\\n${uniformsData.code}`;\n\t\t\t\tconst fshader = fragmentExtracted.src.replace(MARKER$1, fBlock);\n\t\t\t\treturn {\n\t\t\t\t\t\tvshader: vshader,\n\t\t\t\t\t\tfshader: fshader,\n\t\t\t\t\t\tattributes: attributesMap,\n\t\t\t\t\t\tmeshUniformBufferFormat: uniformsData.meshUniformBufferFormat,\n\t\t\t\t\t\tmeshBindGroupFormat: uniformsData.meshBindGroupFormat\n\t\t\t\t};\n\t\t}\n\t\tstatic extract(src) {\n\t\t\t\tconst attributes = [];\n\t\t\t\tconst varyings = [];\n\t\t\t\tconst outs = [];\n\t\t\t\tconst uniforms = [];\n\t\t\t\tlet replacement = `${MARKER$1}\\n`;\n\t\t\t\tlet match;\n\t\t\t\twhile((match = KEYWORD$2.exec(src)) !== null){\n\t\t\t\t\t\tconst keyword = match[1];\n\t\t\t\t\t\tswitch(keyword){\n\t\t\t\t\t\t\t\tcase 'attribute':\n\t\t\t\t\t\t\t\tcase 'varying':\n\t\t\t\t\t\t\t\tcase 'uniform':\n\t\t\t\t\t\t\t\tcase 'out':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD_LINE$1.lastIndex = match.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst lineMatch = KEYWORD_LINE$1.exec(src);\n\t\t\t\t\t\t\t\t\t\t\t\tif (keyword === 'attribute') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tattributes.push(lineMatch[2]);\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (keyword === 'varying') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvaryings.push(lineMatch[2]);\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (keyword === 'out') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\touts.push(lineMatch[2]);\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (keyword === 'uniform') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tuniforms.push(lineMatch[2]);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tsrc = ShaderProcessorGLSL.cutOut(src, match.index, KEYWORD_LINE$1.lastIndex, replacement);\n\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD$2.lastIndex = match.index + replacement.length;\n\t\t\t\t\t\t\t\t\t\t\t\treplacement = '';\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tsrc,\n\t\t\t\t\t\tattributes,\n\t\t\t\t\t\tvaryings,\n\t\t\t\t\t\touts,\n\t\t\t\t\t\tuniforms\n\t\t\t\t};\n\t\t}\n\t\tstatic processUniforms(device, uniforms, processingOptions, shader) {\n\t\t\t\tconst uniformLinesSamplers = [];\n\t\t\t\tconst uniformLinesNonSamplers = [];\n\t\t\t\tuniforms.forEach((uniform)=>{\n\t\t\t\t\t\tif (uniform.isSampler) {\n\t\t\t\t\t\t\t\tuniformLinesSamplers.push(uniform);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tuniformLinesNonSamplers.push(uniform);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst meshUniforms = [];\n\t\t\t\tuniformLinesNonSamplers.forEach((uniform)=>{\n\t\t\t\t\t\tif (!processingOptions.hasUniform(uniform.name)) {\n\t\t\t\t\t\t\t\tconst uniformType = uniformTypeToName.indexOf(uniform.type);\n\t\t\t\t\t\t\t\tconst uniformFormat = new UniformFormat(uniform.name, uniformType, uniform.arraySize);\n\t\t\t\t\t\t\t\tmeshUniforms.push(uniformFormat);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (meshUniforms.length === 0) {\n\t\t\t\t\t\tmeshUniforms.push(new UniformFormat(UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT));\n\t\t\t\t}\n\t\t\t\tconst meshUniformBufferFormat = meshUniforms.length ? new UniformBufferFormat(device, meshUniforms) : null;\n\t\t\t\tconst textureFormats = [];\n\t\t\t\tuniformLinesSamplers.forEach((uniform)=>{\n\t\t\t\t\t\tif (!processingOptions.hasTexture(uniform.name)) {\n\t\t\t\t\t\t\t\tlet sampleType = SAMPLETYPE_FLOAT;\n\t\t\t\t\t\t\t\tif (uniform.isSignedInt) {\n\t\t\t\t\t\t\t\t\t\tsampleType = SAMPLETYPE_INT;\n\t\t\t\t\t\t\t\t} else if (uniform.isUnsignedInt) {\n\t\t\t\t\t\t\t\t\t\tsampleType = SAMPLETYPE_UINT;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (uniform.precision === 'highp') {\n\t\t\t\t\t\t\t\t\t\t\t\tsampleType = SAMPLETYPE_UNFILTERABLE_FLOAT;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (shadowSamplers.has(uniform.type)) {\n\t\t\t\t\t\t\t\t\t\t\t\tsampleType = SAMPLETYPE_DEPTH;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst dimension = textureDimensions[uniform.type];\n\t\t\t\t\t\t\t\ttextureFormats.push(new BindTextureFormat(uniform.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, dimension, sampleType));\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst meshBindGroupFormat = new BindGroupFormat(device, textureFormats);\n\t\t\t\tlet code = '';\n\t\t\t\tprocessingOptions.uniformFormats.forEach((format, bindGroupIndex)=>{\n\t\t\t\t\t\tif (format) {\n\t\t\t\t\t\t\t\tcode += ShaderProcessorGLSL.getUniformShaderDeclaration(format, bindGroupIndex, 0);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (meshUniformBufferFormat) {\n\t\t\t\t\t\tcode += ShaderProcessorGLSL.getUniformShaderDeclaration(meshUniformBufferFormat, BINDGROUP_MESH_UB, 0);\n\t\t\t\t}\n\t\t\t\tprocessingOptions.bindGroupFormats.forEach((format, bindGroupIndex)=>{\n\t\t\t\t\t\tif (format) {\n\t\t\t\t\t\t\t\tcode += ShaderProcessorGLSL.getTexturesShaderDeclaration(format, bindGroupIndex);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcode += ShaderProcessorGLSL.getTexturesShaderDeclaration(meshBindGroupFormat, BINDGROUP_MESH);\n\t\t\t\treturn {\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\tmeshUniformBufferFormat,\n\t\t\t\t\t\tmeshBindGroupFormat\n\t\t\t\t};\n\t\t}\n\t\tstatic processVaryings(varyingLines, varyingMap, isVertex) {\n\t\t\t\tlet block = '';\n\t\t\t\tconst op = isVertex ? 'out' : 'in';\n\t\t\t\tvaryingLines.forEach((line, index)=>{\n\t\t\t\t\t\tconst words = ShaderProcessorGLSL.splitToWords(line);\n\t\t\t\t\t\tconst type = words.slice(0, -1).join(' ');\n\t\t\t\t\t\tconst name = words[words.length - 1];\n\t\t\t\t\t\tif (isVertex) {\n\t\t\t\t\t\t\t\tvaryingMap.set(name, index);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tindex = varyingMap.get(name);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tblock += `layout(location = ${index}) ${op} ${type} ${name};\\n`;\n\t\t\t\t});\n\t\t\t\treturn block;\n\t\t}\n\t\tstatic processOuts(outsLines) {\n\t\t\t\tlet block = '';\n\t\t\t\toutsLines.forEach((line, index)=>{\n\t\t\t\t\t\tblock += `layout(location = ${index}) out ${line};\\n`;\n\t\t\t\t});\n\t\t\t\treturn block;\n\t\t}\n\t\tstatic getTypeCount(type) {\n\t\t\t\tconst lastChar = type.substring(type.length - 1);\n\t\t\t\tconst num = parseInt(lastChar, 10);\n\t\t\t\treturn isNaN(num) ? 1 : num;\n\t\t}\n\t\tstatic processAttributes(attributeLines, shaderDefinitionAttributes, attributesMap, processingOptions) {\n\t\t\t\tlet block = '';\n\t\t\t\tattributeLines.forEach((line)=>{\n\t\t\t\t\t\tconst words = ShaderProcessorGLSL.splitToWords(line);\n\t\t\t\t\t\tlet type = words[0];\n\t\t\t\t\t\tlet name = words[1];\n\t\t\t\t\t\tif (shaderDefinitionAttributes.hasOwnProperty(name)) {\n\t\t\t\t\t\t\t\tconst semantic = shaderDefinitionAttributes[name];\n\t\t\t\t\t\t\t\tconst location = semanticToLocation[semantic];\n\t\t\t\t\t\t\t\tattributesMap.set(location, name);\n\t\t\t\t\t\t\t\tlet copyCode;\n\t\t\t\t\t\t\t\tconst element = processingOptions.getVertexElement(semantic);\n\t\t\t\t\t\t\t\tif (element) {\n\t\t\t\t\t\t\t\t\t\tconst dataType = element.dataType;\n\t\t\t\t\t\t\t\t\t\tif (dataType !== TYPE_FLOAT32 && dataType !== TYPE_FLOAT16 && !element.normalize && !element.asInt) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst attribNumElements = ShaderProcessorGLSL.getTypeCount(type);\n\t\t\t\t\t\t\t\t\t\t\t\tconst newName = `_private_${name}`;\n\t\t\t\t\t\t\t\t\t\t\t\tcopyCode = `vec${attribNumElements} ${name} = vec${attribNumElements}(${newName});\\n`;\n\t\t\t\t\t\t\t\t\t\t\t\tname = newName;\n\t\t\t\t\t\t\t\t\t\t\t\tconst isSignedType = dataType === TYPE_INT8 || dataType === TYPE_INT16 || dataType === TYPE_INT32;\n\t\t\t\t\t\t\t\t\t\t\t\tif (attribNumElements === 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype = isSignedType ? 'int' : 'uint';\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype = isSignedType ? `ivec${attribNumElements}` : `uvec${attribNumElements}`;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tblock += `layout(location = ${location}) in ${type} ${name};\\n`;\n\t\t\t\t\t\t\t\tif (copyCode) {\n\t\t\t\t\t\t\t\t\t\tblock += copyCode;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn block;\n\t\t}\n\t\tstatic splitToWords(line) {\n\t\t\t\tline = line.replace(/\\s+/g, ' ').trim();\n\t\t\t\treturn line.split(' ');\n\t\t}\n\t\tstatic cutOut(src, start, end, replacement) {\n\t\t\t\treturn src.substring(0, start) + replacement + src.substring(end);\n\t\t}\n\t\tstatic getUniformShaderDeclaration(format, bindGroup, bindIndex) {\n\t\t\t\tconst name = bindGroupNames[bindGroup];\n\t\t\t\tlet code = `layout(set = ${bindGroup}, binding = ${bindIndex}, std140) uniform ub_${name} {\\n`;\n\t\t\t\tformat.uniforms.forEach((uniform)=>{\n\t\t\t\t\t\tconst typeString = uniformTypeToName[uniform.type];\n\t\t\t\t\t\tcode += `    ${typeString} ${uniform.shortName}${uniform.count ? `[${uniform.count}]` : ''};\\n`;\n\t\t\t\t});\n\t\t\t\treturn `${code}};\\n`;\n\t\t}\n\t\tstatic getTexturesShaderDeclaration(bindGroupFormat, bindGroup) {\n\t\t\t\tlet code = '';\n\t\t\t\tbindGroupFormat.textureFormats.forEach((format)=>{\n\t\t\t\t\t\tlet textureType = textureDimensionInfo[format.textureDimension];\n\t\t\t\t\t\tconst isArray = textureType === 'texture2DArray';\n\t\t\t\t\t\tconst sampleTypePrefix = format.sampleType === SAMPLETYPE_UINT ? 'u' : format.sampleType === SAMPLETYPE_INT ? 'i' : '';\n\t\t\t\t\t\ttextureType = `${sampleTypePrefix}${textureType}`;\n\t\t\t\t\t\tlet namePostfix = '';\n\t\t\t\t\t\tlet extraCode = '';\n\t\t\t\t\t\tif (isArray) {\n\t\t\t\t\t\t\t\tnamePostfix = '_texture';\n\t\t\t\t\t\t\t\textraCode = `#define ${format.name} ${sampleTypePrefix}sampler2DArray(${format.name}${namePostfix}, ${format.name}_sampler)\\n`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcode += `layout(set = ${bindGroup}, binding = ${format.slot}) uniform ${textureType} ${format.name}${namePostfix};\\n`;\n\t\t\t\t\t\tif (format.hasSampler) {\n\t\t\t\t\t\t\t\tcode += `layout(set = ${bindGroup}, binding = ${format.slot + 1}) uniform sampler ${format.name}_sampler;\\n`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcode += extraCode;\n\t\t\t\t});\n\t\t\t\treturn code;\n\t\t}\n}\n\nconst KEYWORD$1 = /^[ \\t]*(attribute|varying|uniform)[\\t ]+/gm;\nconst KEYWORD_LINE = /^[ \\t]*(attribute|varying|uniform)[ \\t]*([^;]+)(;+)/gm;\nconst KEYWORD_RESOURCE = /^[ \\t]*var\\s*(?:(<storage,[^>]*>)\\s*([\\w\\d_]+)\\s*:\\s*(.*?)\\s*;|(<(?!storage,)[^>]*>)?\\s*([\\w\\d_]+)\\s*:\\s*(texture_.*|storage_texture_.*|storage\\w.*|external_texture|sampler(?:_comparison)?)\\s*;)\\s*$/gm;\nconst VARYING = /(?:@interpolate\\([^)]*\\)\\s*)?([\\w]+)\\s*:/;\nconst MARKER = '@@@';\nconst ENTRY_FUNCTION = /(@vertex|@fragment)\\s*fn\\s+\\w+\\s*\\(\\s*(\\w+)\\s*:[\\s\\S]*?\\{/;\nconst textureBaseInfo = {\n\t\t'texture_1d': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_1D,\n\t\t\t\tbaseSampleType: SAMPLETYPE_FLOAT\n\t\t},\n\t\t'texture_2d': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_2D,\n\t\t\t\tbaseSampleType: SAMPLETYPE_FLOAT\n\t\t},\n\t\t'texture_2d_array': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_2D_ARRAY,\n\t\t\t\tbaseSampleType: SAMPLETYPE_FLOAT\n\t\t},\n\t\t'texture_3d': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_3D,\n\t\t\t\tbaseSampleType: SAMPLETYPE_FLOAT\n\t\t},\n\t\t'texture_cube': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_CUBE,\n\t\t\t\tbaseSampleType: SAMPLETYPE_FLOAT\n\t\t},\n\t\t'texture_cube_array': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_CUBE_ARRAY,\n\t\t\t\tbaseSampleType: SAMPLETYPE_FLOAT\n\t\t},\n\t\t'texture_multisampled_2d': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_2D,\n\t\t\t\tbaseSampleType: SAMPLETYPE_FLOAT\n\t\t},\n\t\t'texture_depth_2d': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_2D,\n\t\t\t\tbaseSampleType: SAMPLETYPE_DEPTH\n\t\t},\n\t\t'texture_depth_2d_array': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_2D_ARRAY,\n\t\t\t\tbaseSampleType: SAMPLETYPE_DEPTH\n\t\t},\n\t\t'texture_depth_cube': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_CUBE,\n\t\t\t\tbaseSampleType: SAMPLETYPE_DEPTH\n\t\t},\n\t\t'texture_depth_cube_array': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_CUBE_ARRAY,\n\t\t\t\tbaseSampleType: SAMPLETYPE_DEPTH\n\t\t},\n\t\t'texture_external': {\n\t\t\t\tviewDimension: TEXTUREDIMENSION_2D,\n\t\t\t\tbaseSampleType: SAMPLETYPE_UNFILTERABLE_FLOAT\n\t\t}\n};\nconst getTextureInfo = (baseType, componentType)=>{\n\t\tconst baseInfo = textureBaseInfo[baseType];\n\t\tlet finalSampleType = baseInfo.baseSampleType;\n\t\tif (baseInfo.baseSampleType === SAMPLETYPE_FLOAT && baseType !== 'texture_multisampled_2d') {\n\t\t\t\tswitch(componentType){\n\t\t\t\t\t\tcase 'u32':\n\t\t\t\t\t\t\t\tfinalSampleType = SAMPLETYPE_UINT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'i32':\n\t\t\t\t\t\t\t\tfinalSampleType = SAMPLETYPE_INT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'f32':\n\t\t\t\t\t\t\t\tfinalSampleType = SAMPLETYPE_FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'uff':\n\t\t\t\t\t\t\t\tfinalSampleType = SAMPLETYPE_UNFILTERABLE_FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\t\tviewDimension: baseInfo.viewDimension,\n\t\t\t\tsampleType: finalSampleType\n\t\t};\n};\nconst getTextureDeclarationType = (viewDimension, sampleType)=>{\n\t\tif (sampleType === SAMPLETYPE_DEPTH) {\n\t\t\t\tswitch(viewDimension){\n\t\t\t\t\t\tcase TEXTUREDIMENSION_2D:\n\t\t\t\t\t\t\t\treturn 'texture_depth_2d';\n\t\t\t\t\t\tcase TEXTUREDIMENSION_2D_ARRAY:\n\t\t\t\t\t\t\t\treturn 'texture_depth_2d_array';\n\t\t\t\t\t\tcase TEXTUREDIMENSION_CUBE:\n\t\t\t\t\t\t\t\treturn 'texture_depth_cube';\n\t\t\t\t\t\tcase TEXTUREDIMENSION_CUBE_ARRAY:\n\t\t\t\t\t\t\t\treturn 'texture_depth_cube_array';\n\t\t\t\t}\n\t\t}\n\t\tlet baseTypeString;\n\t\tswitch(viewDimension){\n\t\t\t\tcase TEXTUREDIMENSION_1D:\n\t\t\t\t\t\tbaseTypeString = 'texture_1d';\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase TEXTUREDIMENSION_2D:\n\t\t\t\t\t\tbaseTypeString = 'texture_2d';\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase TEXTUREDIMENSION_2D_ARRAY:\n\t\t\t\t\t\tbaseTypeString = 'texture_2d_array';\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase TEXTUREDIMENSION_3D:\n\t\t\t\t\t\tbaseTypeString = 'texture_3d';\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase TEXTUREDIMENSION_CUBE:\n\t\t\t\t\t\tbaseTypeString = 'texture_cube';\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase TEXTUREDIMENSION_CUBE_ARRAY:\n\t\t\t\t\t\tbaseTypeString = 'texture_cube_array';\n\t\t\t\t\t\tbreak;\n\t\t}\n\t\tlet coreFormatString;\n\t\tswitch(sampleType){\n\t\t\t\tcase SAMPLETYPE_FLOAT:\n\t\t\t\tcase SAMPLETYPE_UNFILTERABLE_FLOAT:\n\t\t\t\t\t\tcoreFormatString = 'f32';\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase SAMPLETYPE_UINT:\n\t\t\t\t\t\tcoreFormatString = 'u32';\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase SAMPLETYPE_INT:\n\t\t\t\t\t\tcoreFormatString = 'i32';\n\t\t\t\t\t\tbreak;\n\t\t}\n\t\treturn `${baseTypeString}<${coreFormatString}>`;\n};\nconst wrappedArrayTypes = {\n\t\t'f32': 'WrappedF32',\n\t\t'i32': 'WrappedI32',\n\t\t'u32': 'WrappedU32',\n\t\t'vec2f': 'WrappedVec2F',\n\t\t'vec2i': 'WrappedVec2I',\n\t\t'vec2u': 'WrappedVec2U'\n};\nconst splitToWords = (line)=>{\n\t\tline = line.replace(/\\s+/g, ' ').trim();\n\t\treturn line.split(/[\\s:]+/);\n};\nconst UNIFORM_ARRAY_REGEX = /array<([^,]+),\\s*([^>]+)>/;\nclass UniformLine {\n\t\tconstructor(line, shader){\n\t\t\t\tthis.ubName = null;\n\t\t\t\tthis.arraySize = 0;\n\t\t\t\tthis.line = line;\n\t\t\t\tconst parts = splitToWords(line);\n\t\t\t\tif (parts.length < 2) {\n\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.name = parts[0];\n\t\t\t\tthis.type = parts.slice(1).join(' ');\n\t\t\t\tif (this.type.includes('array<')) {\n\t\t\t\t\t\tconst match = UNIFORM_ARRAY_REGEX.exec(this.type);\n\t\t\t\t\t\tthis.type = match[1].trim();\n\t\t\t\t\t\tthis.arraySize = Number(match[2]);\n\t\t\t\t\t\tif (isNaN(this.arraySize)) {\n\t\t\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\nconst TEXTURE_REGEX = /^\\s*var\\s+(\\w+)\\s*:\\s*(texture_\\w+)(?:<(\\w+)>)?;\\s*$/;\nconst STORAGE_TEXTURE_REGEX = /^\\s*var\\s+([\\w\\d_]+)\\s*:\\s*(texture_storage_2d|texture_storage_2d_array)<([\\w\\d_]+),\\s*(\\w+)>\\s*;\\s*$/;\nconst STORAGE_BUFFER_REGEX = /^\\s*var\\s*<storage,\\s*(read|write)?>\\s*([\\w\\d_]+)\\s*:\\s*(.*)\\s*;\\s*$/;\nconst EXTERNAL_TEXTURE_REGEX = /^\\s*var\\s+([\\w\\d_]+)\\s*:\\s*texture_external;\\s*$/;\nconst SAMPLER_REGEX = /^\\s*var\\s+([\\w\\d_]+)\\s*:\\s*(sampler|sampler_comparison)\\s*;\\s*$/;\nclass ResourceLine {\n\t\tconstructor(line, shader){\n\t\t\t\tthis.originalLine = line;\n\t\t\t\tthis.line = line;\n\t\t\t\tthis.isTexture = false;\n\t\t\t\tthis.isSampler = false;\n\t\t\t\tthis.isStorageTexture = false;\n\t\t\t\tthis.isStorageBuffer = false;\n\t\t\t\tthis.isExternalTexture = false;\n\t\t\t\tthis.type = '';\n\t\t\t\tthis.matchedElements = [];\n\t\t\t\tconst textureMatch = this.line.match(TEXTURE_REGEX);\n\t\t\t\tif (textureMatch) {\n\t\t\t\t\t\tthis.name = textureMatch[1];\n\t\t\t\t\t\tthis.type = textureMatch[2];\n\t\t\t\t\t\tthis.textureFormat = textureMatch[3];\n\t\t\t\t\t\tthis.isTexture = true;\n\t\t\t\t\t\tthis.matchedElements.push(...textureMatch);\n\t\t\t\t\t\tconst info = getTextureInfo(this.type, this.textureFormat);\n\t\t\t\t\t\tthis.textureDimension = info.viewDimension;\n\t\t\t\t\t\tthis.sampleType = info.sampleType;\n\t\t\t\t}\n\t\t\t\tconst storageTextureMatch = this.line.match(STORAGE_TEXTURE_REGEX);\n\t\t\t\tif (storageTextureMatch) {\n\t\t\t\t\t\tthis.isStorageTexture = true;\n\t\t\t\t\t\tthis.name = storageTextureMatch[1];\n\t\t\t\t\t\tthis.textureType = storageTextureMatch[2];\n\t\t\t\t\t\tthis.format = storageTextureMatch[3];\n\t\t\t\t\t\tthis.access = storageTextureMatch[4];\n\t\t\t\t\t\tthis.matchedElements.push(...storageTextureMatch);\n\t\t\t\t}\n\t\t\t\tconst storageBufferMatch = this.line.match(STORAGE_BUFFER_REGEX);\n\t\t\t\tif (storageBufferMatch) {\n\t\t\t\t\t\tthis.isStorageBuffer = true;\n\t\t\t\t\t\tthis.accessMode = storageBufferMatch[1] || 'none';\n\t\t\t\t\t\tthis.name = storageBufferMatch[2];\n\t\t\t\t\t\tthis.type = storageBufferMatch[3];\n\t\t\t\t\t\tthis.matchedElements.push(...storageBufferMatch);\n\t\t\t\t}\n\t\t\t\tconst externalTextureMatch = this.line.match(EXTERNAL_TEXTURE_REGEX);\n\t\t\t\tif (externalTextureMatch) {\n\t\t\t\t\t\tthis.name = externalTextureMatch[1];\n\t\t\t\t\t\tthis.isExternalTexture = true;\n\t\t\t\t\t\tthis.matchedElements.push(...storageBufferMatch);\n\t\t\t\t}\n\t\t\t\tconst samplerMatch = this.line.match(SAMPLER_REGEX);\n\t\t\t\tif (samplerMatch) {\n\t\t\t\t\t\tthis.name = samplerMatch[1];\n\t\t\t\t\t\tthis.samplerType = samplerMatch[2];\n\t\t\t\t\t\tthis.isSampler = true;\n\t\t\t\t\t\tthis.matchedElements.push(...samplerMatch);\n\t\t\t\t}\n\t\t\t\tif (this.matchedElements.length === 0) {\n\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t}\n\t\t}\n\t\tequals(other) {\n\t\t\t\tif (this.name !== other.name) return false;\n\t\t\t\tif (this.type !== other.type) return false;\n\t\t\t\tif (this.isTexture !== other.isTexture) return false;\n\t\t\t\tif (this.isSampler !== other.isSampler) return false;\n\t\t\t\tif (this.isStorageTexture !== other.isStorageTexture) return false;\n\t\t\t\tif (this.isStorageBuffer !== other.isStorageBuffer) return false;\n\t\t\t\tif (this.isExternalTexture !== other.isExternalTexture) return false;\n\t\t\t\tif (this.textureFormat !== other.textureFormat) return false;\n\t\t\t\tif (this.textureDimension !== other.textureDimension) return false;\n\t\t\t\tif (this.sampleType !== other.sampleType) return false;\n\t\t\t\tif (this.textureType !== other.textureType) return false;\n\t\t\t\tif (this.format !== other.format) return false;\n\t\t\t\tif (this.access !== other.access) return false;\n\t\t\t\tif (this.accessMode !== other.accessMode) return false;\n\t\t\t\tif (this.samplerType !== other.samplerType) return false;\n\t\t\t\treturn true;\n\t\t}\n}\nclass WebgpuShaderProcessorWGSL {\n\t\tstatic run(device, shaderDefinition, shader) {\n\t\t\t\tconst varyingMap = new Map();\n\t\t\t\tconst vertexExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.vshader);\n\t\t\t\tconst fragmentExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.fshader);\n\t\t\t\tconst attributesMap = new Map();\n\t\t\t\tconst attributesBlock = WebgpuShaderProcessorWGSL.processAttributes(vertexExtracted.attributes, shaderDefinition.attributes, attributesMap, shaderDefinition.processingOptions, shader);\n\t\t\t\tconst vertexVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(vertexExtracted.varyings, varyingMap, true);\n\t\t\t\tconst fragmentVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(fragmentExtracted.varyings, varyingMap, false);\n\t\t\t\tconst concatUniforms = vertexExtracted.uniforms.concat(fragmentExtracted.uniforms);\n\t\t\t\tconst uniforms = Array.from(new Set(concatUniforms));\n\t\t\t\tconst parsedUniforms = uniforms.map((line)=>new UniformLine(line, shader));\n\t\t\t\tconst uniformsData = WebgpuShaderProcessorWGSL.processUniforms(device, parsedUniforms, shaderDefinition.processingOptions, shader);\n\t\t\t\tvertexExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(vertexExtracted.src, parsedUniforms);\n\t\t\t\tfragmentExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(fragmentExtracted.src, parsedUniforms);\n\t\t\t\tconst parsedResources = WebgpuShaderProcessorWGSL.mergeResources(vertexExtracted.resources, fragmentExtracted.resources, shader);\n\t\t\t\tconst resourcesData = WebgpuShaderProcessorWGSL.processResources(device, parsedResources, shaderDefinition.processingOptions, shader);\n\t\t\t\tconst fOutput = WebgpuShaderProcessorWGSL.generateFragmentOutputStruct(fragmentExtracted.src, device.maxColorAttachments);\n\t\t\t\tvertexExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(vertexExtracted.src, shader);\n\t\t\t\tfragmentExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(fragmentExtracted.src, shader);\n\t\t\t\tconst vBlock = `${attributesBlock}\\n${vertexVaryingsBlock}\\n${uniformsData.code}\\n${resourcesData.code}\\n`;\n\t\t\t\tconst vshader = vertexExtracted.src.replace(MARKER, vBlock);\n\t\t\t\tconst fBlock = `${fragmentVaryingsBlock}\\n${fOutput}\\n${uniformsData.code}\\n${resourcesData.code}\\n`;\n\t\t\t\tconst fshader = fragmentExtracted.src.replace(MARKER, fBlock);\n\t\t\t\treturn {\n\t\t\t\t\t\tvshader: vshader,\n\t\t\t\t\t\tfshader: fshader,\n\t\t\t\t\t\tattributes: attributesMap,\n\t\t\t\t\t\tmeshUniformBufferFormat: uniformsData.meshUniformBufferFormat,\n\t\t\t\t\t\tmeshBindGroupFormat: resourcesData.meshBindGroupFormat\n\t\t\t\t};\n\t\t}\n\t\tstatic extract(src) {\n\t\t\t\tconst attributes = [];\n\t\t\t\tconst varyings = [];\n\t\t\t\tconst uniforms = [];\n\t\t\t\tconst resources = [];\n\t\t\t\tlet replacement = `${MARKER}\\n`;\n\t\t\t\tlet match;\n\t\t\t\twhile((match = KEYWORD$1.exec(src)) !== null){\n\t\t\t\t\t\tconst keyword = match[1];\n\t\t\t\t\t\tKEYWORD_LINE.lastIndex = match.index;\n\t\t\t\t\t\tconst lineMatch = KEYWORD_LINE.exec(src);\n\t\t\t\t\t\tif (keyword === 'attribute') {\n\t\t\t\t\t\t\t\tattributes.push(lineMatch[2]);\n\t\t\t\t\t\t} else if (keyword === 'varying') {\n\t\t\t\t\t\t\t\tvaryings.push(lineMatch[2]);\n\t\t\t\t\t\t} else if (keyword === 'uniform') {\n\t\t\t\t\t\t\t\tuniforms.push(lineMatch[2]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsrc = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_LINE.lastIndex, replacement);\n\t\t\t\t\t\tKEYWORD$1.lastIndex = match.index + replacement.length;\n\t\t\t\t\t\treplacement = '';\n\t\t\t\t}\n\t\t\t\twhile((match = KEYWORD_RESOURCE.exec(src)) !== null){\n\t\t\t\t\t\tresources.push(match[0]);\n\t\t\t\t\t\tsrc = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_RESOURCE.lastIndex, replacement);\n\t\t\t\t\t\tKEYWORD_RESOURCE.lastIndex = match.index + replacement.length;\n\t\t\t\t\t\treplacement = '';\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tsrc,\n\t\t\t\t\t\tattributes,\n\t\t\t\t\t\tvaryings,\n\t\t\t\t\t\tuniforms,\n\t\t\t\t\t\tresources\n\t\t\t\t};\n\t\t}\n\t\tstatic processUniforms(device, uniforms, processingOptions, shader) {\n\t\t\t\tconst meshUniforms = [];\n\t\t\t\tuniforms.forEach((uniform)=>{\n\t\t\t\t\t\tif (!processingOptions.hasUniform(uniform.name)) {\n\t\t\t\t\t\t\t\tuniform.ubName = 'ub_mesh_ub';\n\t\t\t\t\t\t\t\tconst uniformType = uniformTypeToNameMapWGSL.get(uniform.type);\n\t\t\t\t\t\t\t\tconst uniformFormat = new UniformFormat(uniform.name, uniformType, uniform.arraySize);\n\t\t\t\t\t\t\t\tmeshUniforms.push(uniformFormat);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tuniform.ubName = 'ub_view';\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (meshUniforms.length === 0) {\n\t\t\t\t\t\tmeshUniforms.push(new UniformFormat(UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT));\n\t\t\t\t}\n\t\t\t\tconst meshUniformBufferFormat = new UniformBufferFormat(device, meshUniforms);\n\t\t\t\tlet code = '';\n\t\t\t\tprocessingOptions.uniformFormats.forEach((format, bindGroupIndex)=>{\n\t\t\t\t\t\tif (format) {\n\t\t\t\t\t\t\t\tcode += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(format, bindGroupIndex, 0);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (meshUniformBufferFormat) {\n\t\t\t\t\t\tcode += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(meshUniformBufferFormat, BINDGROUP_MESH_UB, 0);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\tmeshUniformBufferFormat\n\t\t\t\t};\n\t\t}\n\t\tstatic renameUniformAccess(source, uniforms) {\n\t\t\t\tuniforms.forEach((uniform)=>{\n\t\t\t\t\t\tconst srcName = `uniform.${uniform.name}`;\n\t\t\t\t\t\tconst dstName = `${uniform.ubName}.${uniform.name}`;\n\t\t\t\t\t\tconst regex = new RegExp(`\\\\b${srcName}\\\\b`, 'g');\n\t\t\t\t\t\tsource = source.replace(regex, dstName);\n\t\t\t\t});\n\t\t\t\treturn source;\n\t\t}\n\t\tstatic mergeResources(vertex, fragment, shader) {\n\t\t\t\tconst resources = vertex.map((line)=>new ResourceLine(line, shader));\n\t\t\t\tconst fragmentResources = fragment.map((line)=>new ResourceLine(line, shader));\n\t\t\t\tfragmentResources.forEach((fragmentResource)=>{\n\t\t\t\t\t\tconst existing = resources.find((resource)=>resource.name === fragmentResource.name);\n\t\t\t\t\t\tif (existing) {\n\t\t\t\t\t\t\t\tif (!existing.equals(fragmentResource)) {\n\t\t\t\t\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresources.push(fragmentResource);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn resources;\n\t\t}\n\t\tstatic processResources(device, resources, processingOptions, shader) {\n\t\t\t\tconst textureFormats = [];\n\t\t\t\tfor(let i = 0; i < resources.length; i++){\n\t\t\t\t\t\tconst resource = resources[i];\n\t\t\t\t\t\tif (resource.isTexture) {\n\t\t\t\t\t\t\t\tconst sampler = resources[i + 1];\n\t\t\t\t\t\t\t\tconst hasSampler = sampler?.isSampler;\n\t\t\t\t\t\t\t\tconst sampleType = resource.sampleType;\n\t\t\t\t\t\t\t\tconst dimension = resource.textureDimension;\n\t\t\t\t\t\t\t\ttextureFormats.push(new BindTextureFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, dimension, sampleType, hasSampler, hasSampler ? sampler.name : null));\n\t\t\t\t\t\t\t\tif (hasSampler) i++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (resource.isStorageBuffer) {\n\t\t\t\t\t\t\t\tconst readOnly = resource.accessMode !== 'read_write';\n\t\t\t\t\t\t\t\tconst bufferFormat = new BindStorageBufferFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, readOnly);\n\t\t\t\t\t\t\t\tbufferFormat.format = resource.type;\n\t\t\t\t\t\t\t\ttextureFormats.push(bufferFormat);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst meshBindGroupFormat = new BindGroupFormat(device, textureFormats);\n\t\t\t\tlet code = '';\n\t\t\t\tprocessingOptions.bindGroupFormats.forEach((format, bindGroupIndex)=>{\n\t\t\t\t\t\tif (format) {\n\t\t\t\t\t\t\t\tcode += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(format, bindGroupIndex, 1);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcode += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(meshBindGroupFormat, BINDGROUP_MESH, 0);\n\t\t\t\treturn {\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\tmeshBindGroupFormat\n\t\t\t\t};\n\t\t}\n\t\tstatic getUniformShaderDeclaration(ubFormat, bindGroup, bindIndex) {\n\t\t\t\tconst name = bindGroupNames[bindGroup];\n\t\t\t\tconst structName = `struct_ub_${name}`;\n\t\t\t\tlet code = `struct ${structName} {\\n`;\n\t\t\t\tubFormat.uniforms.forEach((uniform)=>{\n\t\t\t\t\t\tlet typeString = uniformTypeToNameWGSL[uniform.type][0];\n\t\t\t\t\t\tif (uniform.count > 0) {\n\t\t\t\t\t\t\t\tif (wrappedArrayTypes.hasOwnProperty(typeString)) {\n\t\t\t\t\t\t\t\t\t\ttypeString = wrappedArrayTypes[typeString];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcode += `    ${uniform.shortName}: array<${typeString}, ${uniform.count}>,\\n`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcode += `    ${uniform.shortName}: ${typeString},\\n`;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcode += '};\\n';\n\t\t\t\tcode += `@group(${bindGroup}) @binding(${bindIndex}) var<uniform> ub_${name} : ${structName};\\n\\n`;\n\t\t\t\treturn code;\n\t\t}\n\t\tstatic getTextureShaderDeclaration(format, bindGroup, startBindIndex) {\n\t\t\t\tlet code = '';\n\t\t\t\tlet bindIndex = startBindIndex;\n\t\t\t\tformat.textureFormats.forEach((format)=>{\n\t\t\t\t\t\tconst textureTypeName = getTextureDeclarationType(format.textureDimension, format.sampleType);\n\t\t\t\t\t\tcode += `@group(${bindGroup}) @binding(${bindIndex}) var ${format.name}: ${textureTypeName};\\n`;\n\t\t\t\t\t\tbindIndex++;\n\t\t\t\t\t\tif (format.hasSampler) {\n\t\t\t\t\t\t\t\tconst samplerName = format.sampleType === SAMPLETYPE_DEPTH ? 'sampler_comparison' : 'sampler';\n\t\t\t\t\t\t\t\tcode += `@group(${bindGroup}) @binding(${bindIndex}) var ${format.samplerName}: ${samplerName};\\n`;\n\t\t\t\t\t\t\t\tbindIndex++;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tformat.storageBufferFormats.forEach((format)=>{\n\t\t\t\t\t\tconst access = format.readOnly ? 'read' : 'read_write';\n\t\t\t\t\t\tcode += `@group(${bindGroup}) @binding(${bindIndex}) var<storage, ${access}> ${format.name} : ${format.format};\\n`;\n\t\t\t\t\t\tbindIndex++;\n\t\t\t\t});\n\t\t\t\treturn code;\n\t\t}\n\t\tstatic processVaryings(varyingLines, varyingMap, isVertex) {\n\t\t\t\tlet block = '';\n\t\t\t\tlet blockPrivates = '';\n\t\t\t\tlet blockCopy = '';\n\t\t\t\tvaryingLines.forEach((line, index)=>{\n\t\t\t\t\t\tconst match = line.match(VARYING);\n\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tconst name = match[1];\n\t\t\t\t\t\t\t\tif (isVertex) {\n\t\t\t\t\t\t\t\t\t\tvaryingMap.set(name, index);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tindex = varyingMap.get(name);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tblock += `    @location(${index}) ${line},\\n`;\n\t\t\t\t\t\t\t\tif (!isVertex) {\n\t\t\t\t\t\t\t\t\t\tblockPrivates += `    var<private> ${line};\\n`;\n\t\t\t\t\t\t\t\t\t\tblockCopy += `    ${name} = input.${name};\\n`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (isVertex) {\n\t\t\t\t\t\tblock += '    @builtin(position) position : vec4f,\\n';\n\t\t\t\t} else {\n\t\t\t\t\t\tblock += '    @builtin(position) position : vec4f,\\n';\n\t\t\t\t\t\tblock += '    @builtin(front_facing) frontFacing : bool,\\n';\n\t\t\t\t\t\tblock += '    @builtin(sample_index) sampleIndex : u32\\n';\n\t\t\t\t}\n\t\t\t\tconst fragmentGlobals = isVertex ? '' : `\n\t\t\t\t\t\tvar<private> pcPosition: vec4f;\n\t\t\t\t\t\tvar<private> pcFrontFacing: bool;\n\t\t\t\t\t\tvar<private> pcSampleIndex: u32;\n\t\t\t\t\t\t${blockPrivates}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// function to copy inputs (varyings) to private global variables\n\t\t\t\t\t\tfn _pcCopyInputs(input: FragmentInput) {\n\t\t\t\t\t\t\t\t${blockCopy}\n\t\t\t\t\t\t\t\tpcPosition = input.position;\n\t\t\t\t\t\t\t\tpcFrontFacing = input.frontFacing;\n\t\t\t\t\t\t\t\tpcSampleIndex = input.sampleIndex;\n\t\t\t\t\t\t}\n\t\t\t\t`;\n\t\t\t\tconst structName = isVertex ? 'VertexOutput' : 'FragmentInput';\n\t\t\t\treturn `\n\t\t\t\t\t\tstruct ${structName} {\n\t\t\t\t\t\t\t\t${block}\n\t\t\t\t\t\t};\n\t\t\t\t\t\t${fragmentGlobals}\n\t\t\t\t`;\n\t\t}\n\t\tstatic generateFragmentOutputStruct(src, numRenderTargets) {\n\t\t\t\tlet structCode = 'struct FragmentOutput {\\n';\n\t\t\t\tfor(let i = 0; i < numRenderTargets; i++){\n\t\t\t\t\t\tstructCode += `    @location(${i}) color${i > 0 ? i : ''} : pcOutType${i},\\n`;\n\t\t\t\t}\n\t\t\t\tconst needsFragDepth = src.search(/\\.fragDepth\\s*=/) !== -1;\n\t\t\t\tif (needsFragDepth) {\n\t\t\t\t\t\tstructCode += '    @builtin(frag_depth) fragDepth : f32\\n';\n\t\t\t\t}\n\t\t\t\treturn `${structCode}};\\n`;\n\t\t}\n\t\tstatic floatAttributeToInt(type, signed) {\n\t\t\t\tconst longToShortMap = {\n\t\t\t\t\t\t'f32': 'f32',\n\t\t\t\t\t\t'vec2<f32>': 'vec2f',\n\t\t\t\t\t\t'vec3<f32>': 'vec3f',\n\t\t\t\t\t\t'vec4<f32>': 'vec4f'\n\t\t\t\t};\n\t\t\t\tconst shortType = longToShortMap[type] || type;\n\t\t\t\tconst floatToIntShort = {\n\t\t\t\t\t\t'f32': signed ? 'i32' : 'u32',\n\t\t\t\t\t\t'vec2f': signed ? 'vec2i' : 'vec2u',\n\t\t\t\t\t\t'vec3f': signed ? 'vec3i' : 'vec3u',\n\t\t\t\t\t\t'vec4f': signed ? 'vec4i' : 'vec4u'\n\t\t\t\t};\n\t\t\t\treturn floatToIntShort[shortType] || null;\n\t\t}\n\t\tstatic processAttributes(attributeLines, shaderDefinitionAttributes = {}, attributesMap, processingOptions, shader) {\n\t\t\t\tlet blockAttributes = '';\n\t\t\t\tlet blockPrivates = '';\n\t\t\t\tlet blockCopy = '';\n\t\t\t\tattributeLines.forEach((line)=>{\n\t\t\t\t\t\tconst words = splitToWords(line);\n\t\t\t\t\t\tconst name = words[0];\n\t\t\t\t\t\tlet type = words[1];\n\t\t\t\t\t\tconst originalType = type;\n\t\t\t\t\t\tif (shaderDefinitionAttributes.hasOwnProperty(name)) {\n\t\t\t\t\t\t\t\tconst semantic = shaderDefinitionAttributes[name];\n\t\t\t\t\t\t\t\tconst location = semanticToLocation[semantic];\n\t\t\t\t\t\t\t\tattributesMap.set(location, name);\n\t\t\t\t\t\t\t\tconst element = processingOptions.getVertexElement(semantic);\n\t\t\t\t\t\t\t\tif (element) {\n\t\t\t\t\t\t\t\t\t\tconst dataType = element.dataType;\n\t\t\t\t\t\t\t\t\t\tif (dataType !== TYPE_FLOAT32 && dataType !== TYPE_FLOAT16 && !element.normalize && !element.asInt) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst isSignedType = dataType === TYPE_INT8 || dataType === TYPE_INT16 || dataType === TYPE_INT32;\n\t\t\t\t\t\t\t\t\t\t\t\ttype = WebgpuShaderProcessorWGSL.floatAttributeToInt(type, isSignedType);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tblockAttributes += `    @location(${location}) ${name}: ${type},\\n`;\n\t\t\t\t\t\t\t\tblockPrivates += `    var<private> ${line};\\n`;\n\t\t\t\t\t\t\t\tblockCopy += `    ${name} = ${originalType}(input.${name});\\n`;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn `\n\t\t\t\t\t\tstruct VertexInput {\n\t\t\t\t\t\t\t\t${blockAttributes}\n\t\t\t\t\t\t\t\t@builtin(vertex_index) vertexIndex : u32,       // built-in vertex index\n\t\t\t\t\t\t\t\t@builtin(instance_index) instanceIndex : u32    // built-in instance index\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t${blockPrivates}\n\t\t\t\t\t\tvar<private> pcVertexIndex: u32;\n\t\t\t\t\t\tvar<private> pcInstanceIndex: u32;\n\n\t\t\t\t\t\tfn _pcCopyInputs(input: VertexInput) {\n\t\t\t\t\t\t\t\t${blockCopy}\n\t\t\t\t\t\t\t\tpcVertexIndex = input.vertexIndex;\n\t\t\t\t\t\t\t\tpcInstanceIndex = input.instanceIndex;\n\t\t\t\t\t\t}\n\t\t\t\t`;\n\t\t}\n\t\tstatic copyInputs(src, shader) {\n\t\t\t\tconst match = src.match(ENTRY_FUNCTION);\n\t\t\t\tif (!match || !match[2]) {\n\t\t\t\t\t\treturn src;\n\t\t\t\t}\n\t\t\t\tconst inputName = match[2];\n\t\t\t\tconst braceIndex = match.index + match[0].length - 1;\n\t\t\t\tconst beginning = src.slice(0, braceIndex + 1);\n\t\t\t\tconst end = src.slice(braceIndex + 1);\n\t\t\t\tconst lineToInject = `\\n    _pcCopyInputs(${inputName});`;\n\t\t\t\treturn beginning + lineToInject + end;\n\t\t}\n\t\tstatic cutOut(src, start, end, replacement) {\n\t\t\t\treturn src.substring(0, start) + replacement + src.substring(end);\n\t\t}\n}\n\nclass WebgpuShader {\n\t\tconstructor(shader){\n\t\t\t\tthis._vertexCode = null;\n\t\t\t\tthis._fragmentCode = null;\n\t\t\t\tthis._computeCode = null;\n\t\t\t\tthis.vertexEntryPoint = 'main';\n\t\t\t\tthis.fragmentEntryPoint = 'main';\n\t\t\t\tthis.computeEntryPoint = 'main';\n\t\t\t\tthis.shader = shader;\n\t\t\t\tconst definition = shader.definition;\n\t\t\t\tif (definition.shaderLanguage === SHADERLANGUAGE_WGSL) {\n\t\t\t\t\t\tif (definition.cshader) {\n\t\t\t\t\t\t\t\tthis._computeCode = definition.cshader ?? null;\n\t\t\t\t\t\t\t\tthis.computeUniformBufferFormats = definition.computeUniformBufferFormats;\n\t\t\t\t\t\t\t\tthis.computeBindGroupFormat = definition.computeBindGroupFormat;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.vertexEntryPoint = 'vertexMain';\n\t\t\t\t\t\t\t\tthis.fragmentEntryPoint = 'fragmentMain';\n\t\t\t\t\t\t\t\tif (definition.processingOptions) {\n\t\t\t\t\t\t\t\t\t\tthis.processWGSL();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._vertexCode = definition.vshader ?? null;\n\t\t\t\t\t\t\t\t\t\tthis._fragmentCode = definition.fshader ?? null;\n\t\t\t\t\t\t\t\t\t\tshader.meshUniformBufferFormat = definition.meshUniformBufferFormat;\n\t\t\t\t\t\t\t\t\t\tshader.meshBindGroupFormat = definition.meshBindGroupFormat;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tshader.ready = true;\n\t\t\t\t} else {\n\t\t\t\t\t\tif (definition.processingOptions) {\n\t\t\t\t\t\t\t\tthis.processGLSL();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdestroy(shader) {\n\t\t\t\tthis._vertexCode = null;\n\t\t\t\tthis._fragmentCode = null;\n\t\t}\n\t\tcreateShaderModule(code, shaderType) {\n\t\t\t\tconst device = this.shader.device;\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tconst shaderModule = wgpu.createShaderModule({\n\t\t\t\t\t\tcode: code\n\t\t\t\t});\n\t\t\t\treturn shaderModule;\n\t\t}\n\t\tgetVertexShaderModule() {\n\t\t\t\treturn this.createShaderModule(this._vertexCode, 'Vertex');\n\t\t}\n\t\tgetFragmentShaderModule() {\n\t\t\t\treturn this.createShaderModule(this._fragmentCode, 'Fragment');\n\t\t}\n\t\tgetComputeShaderModule() {\n\t\t\t\treturn this.createShaderModule(this._computeCode, 'Compute');\n\t\t}\n\t\tprocessGLSL() {\n\t\t\t\tconst shader = this.shader;\n\t\t\t\tconst processed = ShaderProcessorGLSL.run(shader.device, shader.definition, shader);\n\t\t\t\tthis._vertexCode = this.transpile(processed.vshader, 'vertex', shader.definition.vshader);\n\t\t\t\tthis._fragmentCode = this.transpile(processed.fshader, 'fragment', shader.definition.fshader);\n\t\t\t\tif (!(this._vertexCode && this._fragmentCode)) {\n\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t} else {\n\t\t\t\t\t\tshader.ready = true;\n\t\t\t\t}\n\t\t\t\tshader.meshUniformBufferFormat = processed.meshUniformBufferFormat;\n\t\t\t\tshader.meshBindGroupFormat = processed.meshBindGroupFormat;\n\t\t\t\tshader.attributes = processed.attributes;\n\t\t}\n\t\tprocessWGSL() {\n\t\t\t\tconst shader = this.shader;\n\t\t\t\tconst processed = WebgpuShaderProcessorWGSL.run(shader.device, shader.definition, shader);\n\t\t\t\tthis._vertexCode = processed.vshader;\n\t\t\t\tthis._fragmentCode = processed.fshader;\n\t\t\t\tshader.meshUniformBufferFormat = processed.meshUniformBufferFormat;\n\t\t\t\tshader.meshBindGroupFormat = processed.meshBindGroupFormat;\n\t\t\t\tshader.attributes = processed.attributes;\n\t\t}\n\t\ttranspile(src, shaderType, originalSrc) {\n\t\t\t\tconst device = this.shader.device;\n\t\t\t\tif (!device.glslang || !device.twgsl) {\n\t\t\t\t\t\tconsole.error(`Cannot transpile shader [${this.shader.label}] - shader transpilers (glslang/twgsl) are not available. Make sure to provide glslangUrl and twgslUrl when creating the device.`, {\n\t\t\t\t\t\t\t\tshader: this.shader\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\t\tconst spirv = device.glslang.compileGLSL(src, shaderType);\n\t\t\t\t\t\tconst wgsl = device.twgsl.convertSpirV2WGSL(spirv);\n\t\t\t\t\t\treturn wgsl;\n\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error(`Failed to transpile webgl ${shaderType} shader [${this.shader.label}] to WebGPU while rendering ${ void 0}, error:\\n [${err.stack}]`, {\n\t\t\t\t\t\t\t\tprocessed: src,\n\t\t\t\t\t\t\t\toriginal: originalSrc,\n\t\t\t\t\t\t\t\tshader: this.shader,\n\t\t\t\t\t\t\t\terror: err,\n\t\t\t\t\t\t\t\tstack: err.stack\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tget vertexCode() {\n\t\t\t\treturn this._vertexCode;\n\t\t}\n\t\tget fragmentCode() {\n\t\t\t\treturn this._fragmentCode;\n\t\t}\n\t\tloseContext() {}\n\t\trestoreContext(device, shader) {}\n}\n\nconst gpuAddressModes = [];\ngpuAddressModes[ADDRESS_REPEAT] = 'repeat';\ngpuAddressModes[ADDRESS_CLAMP_TO_EDGE] = 'clamp-to-edge';\ngpuAddressModes[ADDRESS_MIRRORED_REPEAT] = 'mirror-repeat';\nconst gpuFilterModes = [];\ngpuFilterModes[FILTER_NEAREST] = {\n\t\tlevel: 'nearest',\n\t\tmip: 'nearest'\n};\ngpuFilterModes[FILTER_LINEAR] = {\n\t\tlevel: 'linear',\n\t\tmip: 'nearest'\n};\ngpuFilterModes[FILTER_NEAREST_MIPMAP_NEAREST] = {\n\t\tlevel: 'nearest',\n\t\tmip: 'nearest'\n};\ngpuFilterModes[FILTER_NEAREST_MIPMAP_LINEAR] = {\n\t\tlevel: 'nearest',\n\t\tmip: 'linear'\n};\ngpuFilterModes[FILTER_LINEAR_MIPMAP_NEAREST] = {\n\t\tlevel: 'linear',\n\t\tmip: 'nearest'\n};\ngpuFilterModes[FILTER_LINEAR_MIPMAP_LINEAR] = {\n\t\tlevel: 'linear',\n\t\tmip: 'linear'\n};\nconst dummyUse = (thingOne)=>{};\nclass WebgpuTexture {\n\t\tconstructor(texture){\n\t\t\t\tthis.samplers = [];\n\t\t\t\tthis.texture = texture;\n\t\t\t\tthis.format = gpuTextureFormats[texture.format];\n\t\t\t\tthis.create(texture.device);\n\t\t}\n\t\tcreate(device) {\n\t\t\t\tconst texture = this.texture;\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tconst numLevels = texture.numLevels;\n\t\t\t\tthis.desc = {\n\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\twidth: texture.width,\n\t\t\t\t\t\t\t\theight: texture.height,\n\t\t\t\t\t\t\t\tdepthOrArrayLayers: texture.cubemap ? 6 : texture.array ? texture.arrayLength : 1\n\t\t\t\t\t\t},\n\t\t\t\t\t\tformat: this.format,\n\t\t\t\t\t\tmipLevelCount: numLevels,\n\t\t\t\t\t\tsampleCount: 1,\n\t\t\t\t\t\tdimension: texture.volume ? '3d' : '2d',\n\t\t\t\t\t\tusage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | (isCompressedPixelFormat(texture.format) ? 0 : GPUTextureUsage.RENDER_ATTACHMENT) | (texture.storage ? GPUTextureUsage.STORAGE_BINDING : 0)\n\t\t\t\t};\n\t\t\t\tthis.gpuTexture = wgpu.createTexture(this.desc);\n\t\t\t\tlet viewDescr;\n\t\t\t\tif (this.texture.format === PIXELFORMAT_DEPTHSTENCIL) {\n\t\t\t\t\t\tviewDescr = {\n\t\t\t\t\t\t\t\tformat: 'depth24plus',\n\t\t\t\t\t\t\t\taspect: 'depth-only'\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tthis.view = this.createView(viewDescr);\n\t\t}\n\t\tdestroy(device) {}\n\t\tpropertyChanged(flag) {\n\t\t\t\tthis.samplers.length = 0;\n\t\t}\n\t\tgetView(device) {\n\t\t\t\tthis.uploadImmediate(device, this.texture);\n\t\t\t\treturn this.view;\n\t\t}\n\t\tcreateView(viewDescr) {\n\t\t\t\tconst options = viewDescr ?? {};\n\t\t\t\tconst textureDescr = this.desc;\n\t\t\t\tconst texture = this.texture;\n\t\t\t\tconst defaultViewDimension = ()=>{\n\t\t\t\t\t\tif (texture.cubemap) return 'cube';\n\t\t\t\t\t\tif (texture.volume) return '3d';\n\t\t\t\t\t\tif (texture.array) return '2d-array';\n\t\t\t\t\t\treturn '2d';\n\t\t\t\t};\n\t\t\t\tconst desc = {\n\t\t\t\t\t\tformat: options.format ?? textureDescr.format,\n\t\t\t\t\t\tdimension: options.dimension ?? defaultViewDimension(),\n\t\t\t\t\t\taspect: options.aspect ?? 'all',\n\t\t\t\t\t\tbaseMipLevel: options.baseMipLevel ?? 0,\n\t\t\t\t\t\tmipLevelCount: options.mipLevelCount ?? textureDescr.mipLevelCount,\n\t\t\t\t\t\tbaseArrayLayer: options.baseArrayLayer ?? 0,\n\t\t\t\t\t\tarrayLayerCount: options.arrayLayerCount ?? textureDescr.depthOrArrayLayers\n\t\t\t\t};\n\t\t\t\tconst view = this.gpuTexture.createView(desc);\n\t\t\t\treturn view;\n\t\t}\n\t\tgetSampler(device, sampleType) {\n\t\t\t\tlet sampler = this.samplers[sampleType];\n\t\t\t\tif (!sampler) {\n\t\t\t\t\t\tconst texture = this.texture;\n\t\t\t\t\t\tconst desc = {\n\t\t\t\t\t\t\t\taddressModeU: gpuAddressModes[texture.addressU],\n\t\t\t\t\t\t\t\taddressModeV: gpuAddressModes[texture.addressV],\n\t\t\t\t\t\t\t\taddressModeW: gpuAddressModes[texture.addressW]\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (!sampleType && texture.compareOnRead) {\n\t\t\t\t\t\t\t\tsampleType = SAMPLETYPE_DEPTH;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sampleType === SAMPLETYPE_DEPTH || sampleType === SAMPLETYPE_INT || sampleType === SAMPLETYPE_UINT) {\n\t\t\t\t\t\t\t\tdesc.compare = 'less';\n\t\t\t\t\t\t\t\tdesc.magFilter = 'linear';\n\t\t\t\t\t\t\t\tdesc.minFilter = 'linear';\n\t\t\t\t\t\t} else if (sampleType === SAMPLETYPE_UNFILTERABLE_FLOAT) {\n\t\t\t\t\t\t\t\tdesc.magFilter = 'nearest';\n\t\t\t\t\t\t\t\tdesc.minFilter = 'nearest';\n\t\t\t\t\t\t\t\tdesc.mipmapFilter = 'nearest';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst forceNearest = !device.textureFloatFilterable && (texture.format === PIXELFORMAT_RGBA32F || texture.format === PIXELFORMAT_RGBA16F);\n\t\t\t\t\t\t\t\tif (forceNearest || this.texture.format === PIXELFORMAT_DEPTHSTENCIL || isIntegerPixelFormat(this.texture.format)) {\n\t\t\t\t\t\t\t\t\t\tdesc.magFilter = 'nearest';\n\t\t\t\t\t\t\t\t\t\tdesc.minFilter = 'nearest';\n\t\t\t\t\t\t\t\t\t\tdesc.mipmapFilter = 'nearest';\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdesc.magFilter = gpuFilterModes[texture.magFilter].level;\n\t\t\t\t\t\t\t\t\t\tdesc.minFilter = gpuFilterModes[texture.minFilter].level;\n\t\t\t\t\t\t\t\t\t\tdesc.mipmapFilter = gpuFilterModes[texture.minFilter].mip;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst allLinear = desc.minFilter === 'linear' && desc.magFilter === 'linear' && desc.mipmapFilter === 'linear';\n\t\t\t\t\t\tdesc.maxAnisotropy = allLinear ? math.clamp(Math.round(texture._anisotropy), 1, device.maxTextureAnisotropy) : 1;\n\t\t\t\t\t\tsampler = device.wgpu.createSampler(desc);\n\t\t\t\t\t\tthis.samplers[sampleType] = sampler;\n\t\t\t\t}\n\t\t\t\treturn sampler;\n\t\t}\n\t\tloseContext() {}\n\t\tuploadImmediate(device, texture) {\n\t\t\t\tif (texture._needsUpload || texture._needsMipmapsUpload) {\n\t\t\t\t\t\tthis.uploadData(device);\n\t\t\t\t\t\ttexture._needsUpload = false;\n\t\t\t\t\t\ttexture._needsMipmapsUpload = false;\n\t\t\t\t}\n\t\t}\n\t\tuploadData(device) {\n\t\t\t\tconst texture = this.texture;\n\t\t\t\tif (texture._levels) {\n\t\t\t\t\t\tlet anyUploads = false;\n\t\t\t\t\t\tlet anyLevelMissing = false;\n\t\t\t\t\t\tconst requiredMipLevels = texture.numLevels;\n\t\t\t\t\t\tfor(let mipLevel = 0; mipLevel < requiredMipLevels; mipLevel++){\n\t\t\t\t\t\t\t\tconst mipObject = texture._levels[mipLevel];\n\t\t\t\t\t\t\t\tif (mipObject) {\n\t\t\t\t\t\t\t\t\t\tif (texture.cubemap) {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let face = 0; face < 6; face++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst faceSource = mipObject[face];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (faceSource) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this.isExternalImage(faceSource)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.uploadExternalImage(device, faceSource, mipLevel, face);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyUploads = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else if (ArrayBuffer.isView(faceSource)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.uploadTypedArrayData(device, faceSource, mipLevel, face);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyUploads = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else ;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyLevelMissing = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else if (texture._volume) ; else if (texture.array) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (texture.arrayLength === mipObject.length) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let index = 0; index < texture._arrayLength; index++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst arraySource = mipObject[index];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this.isExternalImage(arraySource)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.uploadExternalImage(device, arraySource, mipLevel, index);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyUploads = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else if (ArrayBuffer.isView(arraySource)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.uploadTypedArrayData(device, arraySource, mipLevel, index);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyUploads = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else ;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyLevelMissing = true;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tif (this.isExternalImage(mipObject)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.uploadExternalImage(device, mipObject, mipLevel, 0);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyUploads = true;\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (ArrayBuffer.isView(mipObject)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.uploadTypedArrayData(device, mipObject, mipLevel, 0);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyUploads = true;\n\t\t\t\t\t\t\t\t\t\t\t\t} else ;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tanyLevelMissing = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (anyUploads && anyLevelMissing && texture.mipmaps && !isCompressedPixelFormat(texture.format) && !isIntegerPixelFormat(texture.format)) {\n\t\t\t\t\t\t\t\tdevice.mipmapRenderer.generate(this);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (texture._gpuSize) {\n\t\t\t\t\t\t\t\ttexture.adjustVramSizeTracking(device._vram, -texture._gpuSize);\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttexture._gpuSize = texture.gpuSize;\n\t\t\t\t\t\ttexture.adjustVramSizeTracking(device._vram, texture._gpuSize);\n\t\t\t\t}\n\t\t}\n\t\tisExternalImage(image) {\n\t\t\t\treturn typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap || typeof HTMLVideoElement !== 'undefined' && image instanceof HTMLVideoElement || typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas;\n\t\t}\n\t\tuploadExternalImage(device, image, mipLevel, index) {\n\t\t\t\tconst src = {\n\t\t\t\t\t\tsource: image,\n\t\t\t\t\t\torigin: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\tflipY: false\n\t\t\t\t};\n\t\t\t\tconst dst = {\n\t\t\t\t\t\ttexture: this.gpuTexture,\n\t\t\t\t\t\tmipLevel: mipLevel,\n\t\t\t\t\t\torigin: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tindex\n\t\t\t\t\t\t],\n\t\t\t\t\t\taspect: 'all'\n\t\t\t\t};\n\t\t\t\tconst copySize = {\n\t\t\t\t\t\twidth: this.desc.size.width,\n\t\t\t\t\t\theight: this.desc.size.height,\n\t\t\t\t\t\tdepthOrArrayLayers: 1\n\t\t\t\t};\n\t\t\t\tdevice.submit();\n\t\t\t\tdummyUse(image instanceof HTMLCanvasElement && image.getContext('2d'));\n\t\t\t\tdevice.wgpu.queue.copyExternalImageToTexture(src, dst, copySize);\n\t\t}\n\t\tuploadTypedArrayData(device, data, mipLevel, index) {\n\t\t\t\tconst texture = this.texture;\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tconst dest = {\n\t\t\t\t\t\ttexture: this.gpuTexture,\n\t\t\t\t\t\torigin: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tindex\n\t\t\t\t\t\t],\n\t\t\t\t\t\tmipLevel: mipLevel\n\t\t\t\t};\n\t\t\t\tconst width = TextureUtils.calcLevelDimension(texture.width, mipLevel);\n\t\t\t\tconst height = TextureUtils.calcLevelDimension(texture.height, mipLevel);\n\t\t\t\tTextureUtils.calcLevelGpuSize(width, height, 1, texture.format);\n\t\t\t\tconst formatInfo = pixelFormatInfo.get(texture.format);\n\t\t\t\tlet dataLayout;\n\t\t\t\tlet size;\n\t\t\t\tif (formatInfo.size) {\n\t\t\t\t\t\tdataLayout = {\n\t\t\t\t\t\t\t\toffset: 0,\n\t\t\t\t\t\t\t\tbytesPerRow: formatInfo.size * width,\n\t\t\t\t\t\t\t\trowsPerImage: height\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsize = {\n\t\t\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\t\t\theight: height\n\t\t\t\t\t\t};\n\t\t\t\t} else if (formatInfo.blockSize) {\n\t\t\t\t\t\tconst blockDim = (size)=>{\n\t\t\t\t\t\t\t\treturn Math.floor((size + 3) / 4);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tdataLayout = {\n\t\t\t\t\t\t\t\toffset: 0,\n\t\t\t\t\t\t\t\tbytesPerRow: formatInfo.blockSize * blockDim(width),\n\t\t\t\t\t\t\t\trowsPerImage: blockDim(height)\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsize = {\n\t\t\t\t\t\t\t\twidth: Math.max(4, width),\n\t\t\t\t\t\t\t\theight: Math.max(4, height)\n\t\t\t\t\t\t};\n\t\t\t\t} else ;\n\t\t\t\tdevice.submit();\n\t\t\t\twgpu.queue.writeTexture(dest, data, dataLayout, size);\n\t\t}\n\t\tread(x, y, width, height, options) {\n\t\t\t\tconst mipLevel = options.mipLevel ?? 0;\n\t\t\t\tconst face = options.face ?? 0;\n\t\t\t\tconst data = options.data ?? null;\n\t\t\t\tconst immediate = options.immediate ?? false;\n\t\t\t\tconst texture = this.texture;\n\t\t\t\tconst formatInfo = pixelFormatInfo.get(texture.format);\n\t\t\t\tconst bytesPerRow = width * formatInfo.size;\n\t\t\t\tconst paddedBytesPerRow = math.roundUp(bytesPerRow, 256);\n\t\t\t\tconst size = paddedBytesPerRow * height;\n\t\t\t\tconst device = texture.device;\n\t\t\t\tconst stagingBuffer = device.createBufferImpl(BUFFERUSAGE_READ | BUFFERUSAGE_COPY_DST);\n\t\t\t\tstagingBuffer.allocate(device, size);\n\t\t\t\tconst src = {\n\t\t\t\t\t\ttexture: this.gpuTexture,\n\t\t\t\t\t\tmipLevel: mipLevel,\n\t\t\t\t\t\torigin: [\n\t\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\t\ty,\n\t\t\t\t\t\t\t\tface\n\t\t\t\t\t\t]\n\t\t\t\t};\n\t\t\t\tconst dst = {\n\t\t\t\t\t\tbuffer: stagingBuffer.buffer,\n\t\t\t\t\t\toffset: 0,\n\t\t\t\t\t\tbytesPerRow: paddedBytesPerRow\n\t\t\t\t};\n\t\t\t\tconst copySize = {\n\t\t\t\t\t\twidth,\n\t\t\t\t\t\theight,\n\t\t\t\t\t\tdepthOrArrayLayers: 1\n\t\t\t\t};\n\t\t\t\tconst commandEncoder = device.getCommandEncoder();\n\t\t\t\tcommandEncoder.copyTextureToBuffer(src, dst, copySize);\n\t\t\t\treturn device.readBuffer(stagingBuffer, size, null, immediate).then((temp)=>{\n\t\t\t\t\t\tconst target = data?.constructor === Uint8Array ? data : new Uint8Array(data?.buffer ?? height * bytesPerRow);\n\t\t\t\t\t\tfor(let i = 0; i < height; i++){\n\t\t\t\t\t\t\t\tconst srcOffset = i * paddedBytesPerRow;\n\t\t\t\t\t\t\t\tconst dstOffset = i * bytesPerRow;\n\t\t\t\t\t\t\t\tconst sub = temp.subarray(srcOffset, srcOffset + bytesPerRow);\n\t\t\t\t\t\t\t\ttarget.set(sub, dstOffset);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn data ?? target;\n\t\t\t\t});\n\t\t}\n}\n\nclass WebgpuUniformBuffer extends WebgpuBuffer {\n\t\tconstructor(uniformBuffer){\n\t\t\t\tsuper(BUFFERUSAGE_UNIFORM);\n\t\t}\n\t\tunlock(uniformBuffer) {\n\t\t\t\tconst device = uniformBuffer.device;\n\t\t\t\tsuper.unlock(device, uniformBuffer.storageInt32.buffer);\n\t\t}\n}\n\nclass WebgpuVertexBuffer extends WebgpuBuffer {\n\t\tconstructor(vertexBuffer, format, options){\n\t\t\t\tsuper(BUFFERUSAGE_VERTEX | (options?.storage ? BUFFERUSAGE_STORAGE : 0));\n\t\t}\n\t\tunlock(vertexBuffer) {\n\t\t\t\tconst device = vertexBuffer.device;\n\t\t\t\tsuper.unlock(device, vertexBuffer.storage);\n\t\t}\n}\n\nconst KEYWORD = /[ \\t]*#(ifn?def|if|endif|else|elif|define|undef|extension|include)/g;\nconst DEFINE = /define[ \\t]+([^\\n]+)\\r?(?:\\n|$)/g;\nconst EXTENSION = /extension[ \\t]+([\\w-]+)[ \\t]*:[ \\t]*(enable|require)/g;\nconst UNDEF = /undef[ \\t]+([^\\n]+)\\r?(?:\\n|$)/g;\nconst IF = /(ifdef|ifndef|if)[ \\t]*([^\\r\\n]+)\\r?\\n/g;\nconst ENDIF = /(endif|else|elif)(?:[ \\t]+([^\\r\\n]*))?\\r?\\n?/g;\nconst IDENTIFIER$1 = /\\{?[\\w-]+\\}?/;\nconst DEFINED = /(!|\\s)?defined\\(([\\w-]+)\\)/;\nconst DEFINED_PARENS = /!?defined\\s*\\([^)]*\\)/g;\nconst DEFINED_BEFORE_PAREN = /!?defined\\s*$/;\nconst COMPARISON = /([a-z_]\\w*)\\s*(==|!=|<|<=|>|>=)\\s*([\\w\"']+)/i;\nconst INVALID = /[+\\-]/g;\nconst INCLUDE = /include[ \\t]+\"([\\w-]+)(?:\\s*,\\s*([\\w-]+))?\"/g;\nconst LOOP_INDEX = /\\{i\\}/g;\nconst FRAGCOLOR = /(pcFragColor[1-8])\\b/g;\nclass Preprocessor {\n\t\tstatic run(source, includes = new Map(), options = {}) {\n\t\t\t\tPreprocessor.sourceName = options.sourceName;\n\t\t\t\tsource = this.stripComments(source);\n\t\t\t\tsource = source.split(/\\r?\\n/).map((line)=>line.trimEnd()).join('\\n');\n\t\t\t\tconst defines = new Map();\n\t\t\t\tconst injectDefines = new Map();\n\t\t\t\tsource = this._preprocess(source, defines, injectDefines, includes, options.stripDefines);\n\t\t\t\tif (source === null) return null;\n\t\t\t\tconst intDefines = new Map();\n\t\t\t\tdefines.forEach((value, key)=>{\n\t\t\t\t\t\tif (Number.isInteger(parseFloat(value)) && !value.includes('.')) {\n\t\t\t\t\t\t\t\tintDefines.set(key, value);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tsource = this.stripComments(source);\n\t\t\t\tsource = this.stripUnusedColorAttachments(source, options);\n\t\t\t\tsource = this.RemoveEmptyLines(source);\n\t\t\t\tsource = this.processArraySize(source, intDefines);\n\t\t\t\tsource = this.injectDefines(source, injectDefines);\n\t\t\t\treturn source;\n\t\t}\n\t\tstatic stripUnusedColorAttachments(source, options) {\n\t\t\t\tif (options.stripUnusedColorAttachments) {\n\t\t\t\t\t\tconst counts = new Map();\n\t\t\t\t\t\tconst matches = source.match(FRAGCOLOR);\n\t\t\t\t\t\tmatches?.forEach((match)=>{\n\t\t\t\t\t\t\t\tconst index = parseInt(match.charAt(match.length - 1), 10);\n\t\t\t\t\t\t\t\tcounts.set(index, (counts.get(index) ?? 0) + 1);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst anySingleUse = Array.from(counts.values()).some((count)=>count === 1);\n\t\t\t\t\t\tif (anySingleUse) {\n\t\t\t\t\t\t\t\tconst lines = source.split('\\n');\n\t\t\t\t\t\t\t\tconst keepLines = [];\n\t\t\t\t\t\t\t\tfor(let i = 0; i < lines.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst match = lines[i].match(FRAGCOLOR);\n\t\t\t\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst index = parseInt(match[0].charAt(match[0].length - 1), 10);\n\t\t\t\t\t\t\t\t\t\t\t\tif (index > 0 && counts.get(index) === 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tkeepLines.push(lines[i]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsource = keepLines.join('\\n');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn source;\n\t\t}\n\t\tstatic stripComments(source) {\n\t\t\t\treturn source.replace(/\\/\\*[\\s\\S]*?\\*\\/|([^\\\\:]|^)\\/\\/.*$/gm, '$1');\n\t\t}\n\t\tstatic processArraySize(source, intDefines) {\n\t\t\t\tif (source !== null) {\n\t\t\t\t\t\tintDefines.forEach((value, key)=>{\n\t\t\t\t\t\t\t\tsource = source.replace(new RegExp(`\\\\[${key}\\\\]`, 'g'), `[${value}]`);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn source;\n\t\t}\n\t\tstatic injectDefines(source, injectDefines) {\n\t\t\t\tif (source !== null && injectDefines.size > 0) {\n\t\t\t\t\t\tconst lines = source.split('\\n');\n\t\t\t\t\t\tinjectDefines.forEach((value, key)=>{\n\t\t\t\t\t\t\t\tconst regex = new RegExp(key, 'g');\n\t\t\t\t\t\t\t\tfor(let i = 0; i < lines.length; i++){\n\t\t\t\t\t\t\t\t\t\tif (!lines[i].includes('#')) {\n\t\t\t\t\t\t\t\t\t\t\t\tlines[i] = lines[i].replace(regex, value);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsource = lines.join('\\n');\n\t\t\t\t}\n\t\t\t\treturn source;\n\t\t}\n\t\tstatic RemoveEmptyLines(source) {\n\t\t\t\tif (source !== null) {\n\t\t\t\t\t\tsource = source.split(/\\r?\\n/).map((line)=>line.trim() === '' ? '' : line).join('\\n');\n\t\t\t\t\t\tsource = source.replace(/(\\n\\n){3,}/g, '\\n\\n');\n\t\t\t\t}\n\t\t\t\treturn source;\n\t\t}\n\t\tstatic _preprocess(source, defines = new Map(), injectDefines, includes, stripDefines) {\n\t\t\t\tconst originalSource = source;\n\t\t\t\tconst stack = [];\n\t\t\t\tlet error = false;\n\t\t\t\tlet match;\n\t\t\t\twhile((match = KEYWORD.exec(source)) !== null && !error){\n\t\t\t\t\t\tconst keyword = match[1];\n\t\t\t\t\t\tswitch(keyword){\n\t\t\t\t\t\t\t\tcase 'define':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tDEFINE.lastIndex = match.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst define = DEFINE.exec(source);\n\t\t\t\t\t\t\t\t\t\t\t\terror ||= define === null;\n\t\t\t\t\t\t\t\t\t\t\t\tconst expression = define[1];\n\t\t\t\t\t\t\t\t\t\t\t\tIDENTIFIER$1.lastIndex = define.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst identifierValue = IDENTIFIER$1.exec(expression);\n\t\t\t\t\t\t\t\t\t\t\t\tconst identifier = identifierValue[0];\n\t\t\t\t\t\t\t\t\t\t\t\tlet value = expression.substring(identifier.length).trim();\n\t\t\t\t\t\t\t\t\t\t\t\tif (value === '') value = 'true';\n\t\t\t\t\t\t\t\t\t\t\t\tconst keep = Preprocessor._keep(stack);\n\t\t\t\t\t\t\t\t\t\t\t\tlet stripThisDefine = stripDefines;\n\t\t\t\t\t\t\t\t\t\t\t\tif (keep) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst replacementDefine = identifier.startsWith('{') && identifier.endsWith('}');\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (replacementDefine) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstripThisDefine = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (replacementDefine) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tinjectDefines.set(identifier, value);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefines.set(identifier, value);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (stripThisDefine) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsource = source.substring(0, define.index - 1) + source.substring(DEFINE.lastIndex);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = define.index - 1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (!stripThisDefine) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = define.index + define[0].length;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase 'undef':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tUNDEF.lastIndex = match.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst undef = UNDEF.exec(source);\n\t\t\t\t\t\t\t\t\t\t\t\tconst identifier = undef[1].trim();\n\t\t\t\t\t\t\t\t\t\t\t\tconst keep = Preprocessor._keep(stack);\n\t\t\t\t\t\t\t\t\t\t\t\tif (keep) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefines.delete(identifier);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (stripDefines) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsource = source.substring(0, undef.index - 1) + source.substring(UNDEF.lastIndex);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = undef.index - 1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (!stripDefines) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = undef.index + undef[0].length;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase 'extension':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tEXTENSION.lastIndex = match.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst extension = EXTENSION.exec(source);\n\t\t\t\t\t\t\t\t\t\t\t\terror ||= extension === null;\n\t\t\t\t\t\t\t\t\t\t\t\tif (extension) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst identifier = extension[1];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst keep = Preprocessor._keep(stack);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (keep) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefines.set(identifier, 'true');\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = extension.index + extension[0].length;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase 'ifdef':\n\t\t\t\t\t\t\t\tcase 'ifndef':\n\t\t\t\t\t\t\t\tcase 'if':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tIF.lastIndex = match.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst iff = IF.exec(source);\n\t\t\t\t\t\t\t\t\t\t\t\tconst expression = iff[2];\n\t\t\t\t\t\t\t\t\t\t\t\tconst evaluated = Preprocessor.evaluate(expression, defines);\n\t\t\t\t\t\t\t\t\t\t\t\terror ||= evaluated.error;\n\t\t\t\t\t\t\t\t\t\t\t\tlet result = evaluated.result;\n\t\t\t\t\t\t\t\t\t\t\t\tif (keyword === 'ifndef') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult = !result;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tstack.push({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyKeep: result,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tkeep: result,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstart: match.index,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tend: IF.lastIndex\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = iff.index + iff[0].length;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase 'endif':\n\t\t\t\t\t\t\t\tcase 'else':\n\t\t\t\t\t\t\t\tcase 'elif':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tENDIF.lastIndex = match.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst endif = ENDIF.exec(source);\n\t\t\t\t\t\t\t\t\t\t\t\tconst blockInfo = stack.pop();\n\t\t\t\t\t\t\t\t\t\t\t\tif (!blockInfo) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconsole.error(`Shader preprocessing encountered \"#${endif[1]}\" without a preceding #if #ifdef #ifndef while preprocessing ${Preprocessor.sourceName} on line:\\n ${source.substring(match.index, match.index + 100)}...`, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsource: originalSource\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\terror = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst blockCode = blockInfo.keep ? source.substring(blockInfo.end, match.index) : '';\n\t\t\t\t\t\t\t\t\t\t\t\tsource = source.substring(0, blockInfo.start) + blockCode + source.substring(ENDIF.lastIndex);\n\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = blockInfo.start + blockCode.length;\n\t\t\t\t\t\t\t\t\t\t\t\tconst endifCommand = endif[1];\n\t\t\t\t\t\t\t\t\t\t\t\tif (endifCommand === 'else' || endifCommand === 'elif') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet result = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (!blockInfo.anyKeep) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (endifCommand === 'else') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult = !blockInfo.keep;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst evaluated = Preprocessor.evaluate(endif[2], defines);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult = evaluated.result;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\terror ||= evaluated.error;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstack.push({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanyKeep: blockInfo.anyKeep || result,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tkeep: result,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstart: KEYWORD.lastIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tend: KEYWORD.lastIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase 'include':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tINCLUDE.lastIndex = match.index;\n\t\t\t\t\t\t\t\t\t\t\t\tconst include = INCLUDE.exec(source);\n\t\t\t\t\t\t\t\t\t\t\t\terror ||= include === null;\n\t\t\t\t\t\t\t\t\t\t\t\tif (!include) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\terror = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst identifier = include[1].trim();\n\t\t\t\t\t\t\t\t\t\t\t\tconst countIdentifier = include[2]?.trim();\n\t\t\t\t\t\t\t\t\t\t\t\tconst keep = Preprocessor._keep(stack);\n\t\t\t\t\t\t\t\t\t\t\t\tif (keep) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet includeSource = includes?.get(identifier);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (includeSource !== undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tincludeSource = this.stripComments(includeSource);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (countIdentifier) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst countString = defines.get(countIdentifier);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst count = parseFloat(countString);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (Number.isInteger(count)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet result = '';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult += includeSource.replace(LOOP_INDEX, String(i));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tincludeSource = result;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconsole.error(`Include Count identifier \"${countIdentifier}\" not resolved while preprocessing ${Preprocessor.sourceName} on line:\\n ${source.substring(match.index, match.index + 100)}...`, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\toriginalSource: originalSource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsource: source\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\terror = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsource = source.substring(0, include.index - 1) + includeSource + source.substring(INCLUDE.lastIndex);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tKEYWORD.lastIndex = include.index - 1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconsole.error(`Include \"${identifier}\" not resolved while preprocessing ${Preprocessor.sourceName}`, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\toriginalSource: originalSource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsource: source\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\terror = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (stack.length > 0) {\n\t\t\t\t\t\tconsole.error(`Shader preprocessing reached the end of the file without encountering the necessary #endif to close a preceding #if, #ifdef, or #ifndef block. ${Preprocessor.sourceName}`);\n\t\t\t\t\t\terror = true;\n\t\t\t\t}\n\t\t\t\tif (error) {\n\t\t\t\t\t\tconsole.error('Failed to preprocess shader: ', {\n\t\t\t\t\t\t\t\tsource: originalSource\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn source;\n\t\t}\n\t\tstatic _keep(stack) {\n\t\t\t\tfor(let i = 0; i < stack.length; i++){\n\t\t\t\t\t\tif (!stack[i].keep) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tstatic evaluateAtomicExpression(expr, defines) {\n\t\t\t\tlet error = false;\n\t\t\t\texpr = expr.trim();\n\t\t\t\tlet invert = false;\n\t\t\t\tif (expr === 'true') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tresult: true,\n\t\t\t\t\t\t\t\terror\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (expr === 'false') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tresult: false,\n\t\t\t\t\t\t\t\terror\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst definedMatch = DEFINED.exec(expr);\n\t\t\t\tif (definedMatch) {\n\t\t\t\t\t\tinvert = definedMatch[1] === '!';\n\t\t\t\t\t\texpr = definedMatch[2].trim();\n\t\t\t\t\t\tconst exists = defines.has(expr);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tresult: invert ? !exists : exists,\n\t\t\t\t\t\t\t\terror\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst comparisonMatch = COMPARISON.exec(expr);\n\t\t\t\tif (comparisonMatch) {\n\t\t\t\t\t\tconst left = defines.get(comparisonMatch[1].trim()) ?? comparisonMatch[1].trim();\n\t\t\t\t\t\tconst right = defines.get(comparisonMatch[3].trim()) ?? comparisonMatch[3].trim();\n\t\t\t\t\t\tconst operator = comparisonMatch[2].trim();\n\t\t\t\t\t\tlet result = false;\n\t\t\t\t\t\tswitch(operator){\n\t\t\t\t\t\t\t\tcase '==':\n\t\t\t\t\t\t\t\t\t\tresult = left === right;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase '!=':\n\t\t\t\t\t\t\t\t\t\tresult = left !== right;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase '<':\n\t\t\t\t\t\t\t\t\t\tresult = left < right;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase '<=':\n\t\t\t\t\t\t\t\t\t\tresult = left <= right;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase '>':\n\t\t\t\t\t\t\t\t\t\tresult = left > right;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase '>=':\n\t\t\t\t\t\t\t\t\t\tresult = left >= right;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\terror = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t\t\terror\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst result = defines.has(expr);\n\t\t\t\treturn {\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\terror\n\t\t\t\t};\n\t\t}\n\t\tstatic processParentheses(expression, defines) {\n\t\t\t\tlet error = false;\n\t\t\t\tlet processed = expression.trim();\n\t\t\t\twhile(processed.startsWith('(') && processed.endsWith(')')){\n\t\t\t\t\t\tlet depth = 0;\n\t\t\t\t\t\tlet wrapsEntire = true;\n\t\t\t\t\t\tfor(let i = 0; i < processed.length - 1; i++){\n\t\t\t\t\t\t\t\tif (processed[i] === '(') depth++;\n\t\t\t\t\t\t\t\telse if (processed[i] === ')') {\n\t\t\t\t\t\t\t\t\t\tdepth--;\n\t\t\t\t\t\t\t\t\t\tif (depth === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\twrapsEntire = false;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (wrapsEntire) {\n\t\t\t\t\t\t\t\tprocessed = processed.slice(1, -1).trim();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twhile(true){\n\t\t\t\t\t\tlet foundParen = false;\n\t\t\t\t\t\tlet depth = 0;\n\t\t\t\t\t\tlet maxDepth = 0;\n\t\t\t\t\t\tlet deepestStart = -1;\n\t\t\t\t\t\tlet deepestEnd = -1;\n\t\t\t\t\t\tlet inDefinedParen = 0;\n\t\t\t\t\t\tfor(let i = 0; i < processed.length; i++){\n\t\t\t\t\t\t\t\tif (processed[i] === '(') {\n\t\t\t\t\t\t\t\t\t\tconst beforeParen = processed.substring(0, i);\n\t\t\t\t\t\t\t\t\t\tif (DEFINED_BEFORE_PAREN.test(beforeParen)) {\n\t\t\t\t\t\t\t\t\t\t\t\tinDefinedParen++;\n\t\t\t\t\t\t\t\t\t\t} else if (inDefinedParen === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tdepth++;\n\t\t\t\t\t\t\t\t\t\t\t\tif (depth > maxDepth) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmaxDepth = depth;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeepestStart = i;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tfoundParen = true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (processed[i] === ')') {\n\t\t\t\t\t\t\t\t\t\tif (inDefinedParen > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tinDefinedParen--;\n\t\t\t\t\t\t\t\t\t\t} else if (depth > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (depth === maxDepth && deepestStart !== -1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeepestEnd = i;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tdepth--;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!foundParen || deepestStart === -1 || deepestEnd === -1) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst subExpr = processed.substring(deepestStart + 1, deepestEnd);\n\t\t\t\t\t\tconst { result, error: subError } = Preprocessor.evaluate(subExpr, defines);\n\t\t\t\t\t\terror = error || subError;\n\t\t\t\t\t\tprocessed = processed.substring(0, deepestStart) + (result ? 'true' : 'false') + processed.substring(deepestEnd + 1);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\texpression: processed,\n\t\t\t\t\t\terror\n\t\t\t\t};\n\t\t}\n\t\tstatic evaluate(expression, defines) {\n\t\t\t\tconst correct = INVALID.exec(expression) === null;\n\t\t\t\tlet processedExpr = expression;\n\t\t\t\tlet parenError = false;\n\t\t\t\tconst withoutDefined = expression.replace(DEFINED_PARENS, '');\n\t\t\t\tif (withoutDefined.indexOf('(') !== -1) {\n\t\t\t\t\t\tconst processed = Preprocessor.processParentheses(expression, defines);\n\t\t\t\t\t\tprocessedExpr = processed.expression;\n\t\t\t\t\t\tparenError = processed.error;\n\t\t\t\t}\n\t\t\t\tif (parenError) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tresult: false,\n\t\t\t\t\t\t\t\terror: true\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst orSegments = processedExpr.split('||');\n\t\t\t\tfor (const orSegment of orSegments){\n\t\t\t\t\t\tconst andSegments = orSegment.split('&&');\n\t\t\t\t\t\tlet andResult = true;\n\t\t\t\t\t\tfor (const andSegment of andSegments){\n\t\t\t\t\t\t\t\tconst { result, error } = Preprocessor.evaluateAtomicExpression(andSegment.trim(), defines);\n\t\t\t\t\t\t\t\tif (!result || error) {\n\t\t\t\t\t\t\t\t\t\tandResult = false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (andResult) {\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\tresult: true,\n\t\t\t\t\t\t\t\t\t\terror: !correct\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tresult: false,\n\t\t\t\t\t\terror: !correct\n\t\t\t\t};\n\t\t}\n}\n\nvar gles3PS = `\n#ifndef outType_0\n#define outType_0 vec4\n#endif\nlayout(location = 0) out highp outType_0 pcFragColor0;\n#if COLOR_ATTACHMENT_1\nlayout(location = 1) out highp outType_1 pcFragColor1;\n#endif\n#if COLOR_ATTACHMENT_2\nlayout(location = 2) out highp outType_2 pcFragColor2;\n#endif\n#if COLOR_ATTACHMENT_3\nlayout(location = 3) out highp outType_3 pcFragColor3;\n#endif\n#if COLOR_ATTACHMENT_4\nlayout(location = 4) out highp outType_4 pcFragColor4;\n#endif\n#if COLOR_ATTACHMENT_5\nlayout(location = 5) out highp outType_5 pcFragColor5;\n#endif\n#if COLOR_ATTACHMENT_6\nlayout(location = 6) out highp outType_6 pcFragColor6;\n#endif\n#if COLOR_ATTACHMENT_7\nlayout(location = 7) out highp outType_7 pcFragColor7;\n#endif\n#define gl_FragColor pcFragColor0\n#define varying in\n#define texture2D texture\n#define texture2DBias texture\n#define textureCube texture\n#define texture2DProj textureProj\n#define texture2DLod textureLod\n#define texture2DProjLod textureProjLod\n#define textureCubeLod textureLod\n#define texture2DGrad textureGrad\n#define texture2DProjGrad textureProjGrad\n#define textureCubeGrad textureGrad\n#define utexture2D texture\n#define itexture2D texture\n#define texture2DLodEXT texture2DLodEXT_is_no_longer_supported_use_texture2DLod_instead\n#define texture2DProjLodEXT texture2DProjLodEXT_is_no_longer_supported_use_texture2DProjLod\n#define textureCubeLodEXT textureCubeLodEXT_is_no_longer_supported_use_textureCubeLod_instead\n#define texture2DGradEXT texture2DGradEXT_is_no_longer_supported_use_texture2DGrad_instead\n#define texture2DProjGradEXT texture2DProjGradEXT_is_no_longer_supported_use_texture2DProjGrad_instead\n#define textureCubeGradEXT textureCubeGradEXT_is_no_longer_supported_use_textureCubeGrad_instead\n#define textureShadow(res, uv) textureGrad(res, uv, vec2(1, 1), vec2(1, 1))\n#define SHADOWMAP_PASS(name) name\n#define SHADOWMAP_ACCEPT(name) sampler2DShadow name\n#define TEXTURE_PASS(name) name\n#define TEXTURE_ACCEPT(name) sampler2D name\n#define TEXTURE_ACCEPT_HIGHP(name) highp sampler2D name\n#define GL2\n`;\n\nvar gles3VS = `\n#define attribute in\n#define varying out\n#define texture2D texture\n#define utexture2D texture\n#define itexture2D texture\n#define GL2\n#define VERTEXSHADER\n#define TEXTURE_PASS(name) name\n#define TEXTURE_ACCEPT(name) sampler2D name\n#define TEXTURE_ACCEPT_HIGHP(name) highp sampler2D name\n`;\n\nvar webgpuPS$1 = `\n#extension GL_EXT_samplerless_texture_functions : require\n#ifndef outType_0\n#define outType_0 vec4\n#endif\n#ifndef outType_1\n#define outType_1 vec4\n#endif\n#ifndef outType_2\n#define outType_2 vec4\n#endif\n#ifndef outType_3\n#define outType_3 vec4\n#endif\n#ifndef outType_4\n#define outType_4 vec4\n#endif\n#ifndef outType_5\n#define outType_5 vec4\n#endif\n#ifndef outType_6\n#define outType_6 vec4\n#endif\n#ifndef outType_7\n#define outType_7 vec4\n#endif\nlayout(location = 0) out highp outType_0 pcFragColor0;\nlayout(location = 1) out highp outType_1 pcFragColor1;\nlayout(location = 2) out highp outType_2 pcFragColor2;\nlayout(location = 3) out highp outType_3 pcFragColor3;\nlayout(location = 4) out highp outType_4 pcFragColor4;\nlayout(location = 5) out highp outType_5 pcFragColor5;\nlayout(location = 6) out highp outType_6 pcFragColor6;\nlayout(location = 7) out highp outType_7 pcFragColor7;\n#define gl_FragColor pcFragColor0\n#define texture2D(res, uv) texture(sampler2D(res, res ## _sampler), uv)\n#define texture2DBias(res, uv, bias) texture(sampler2D(res, res ## _sampler), uv, bias)\n#define texture2DLod(res, uv, lod) textureLod(sampler2D(res, res ## _sampler), uv, lod)\n#define textureCube(res, uv) texture(samplerCube(res, res ## _sampler), uv)\n#define textureCubeLod(res, uv, lod) textureLod(samplerCube(res, res ## _sampler), uv, lod)\n#define textureShadow(res, uv) textureLod(sampler2DShadow(res, res ## _sampler), uv, 0.0)\n#define itexture2D(res, uv) texture(isampler2D(res, res ## _sampler), uv)\n#define utexture2D(res, uv) texture(usampler2D(res, res ## _sampler), uv)\n#define texture2DLodEXT texture2DLodEXT_is_no_longer_supported_use_texture2DLod_instead\n#define texture2DProjLodEXT texture2DProjLodEXT_is_no_longer_supported_use_texture2DProjLod\n#define textureCubeLodEXT textureCubeLodEXT_is_no_longer_supported_use_textureCubeLod_instead\n#define texture2DGradEXT texture2DGradEXT_is_no_longer_supported_use_texture2DGrad_instead\n#define texture2DProjGradEXT texture2DProjGradEXT_is_no_longer_supported_use_texture2DProjGrad_instead\n#define textureCubeGradEXT textureCubeGradEXT_is_no_longer_supported_use_textureCubeGrad_instead\n#define SHADOWMAP_PASS(name) name, name ## _sampler\n#define SHADOWMAP_ACCEPT(name) texture2D name, sampler name ## _sampler\n#define TEXTURE_PASS(name) name, name ## _sampler\n#define TEXTURE_ACCEPT(name) texture2D name, sampler name ## _sampler\n#define TEXTURE_ACCEPT_HIGHP TEXTURE_ACCEPT\n#define GL2\n#define WEBGPU\n`;\n\nvar webgpuVS$1 = `\n#extension GL_EXT_samplerless_texture_functions : require\n#define texture2D(res, uv) texture(sampler2D(res, res ## _sampler), uv)\n#define itexture2D(res, uv) texture(isampler2D(res, res ## _sampler), uv)\n#define utexture2D(res, uv) texture(usampler2D(res, res ## _sampler), uv)\n#define TEXTURE_PASS(name) name, name ## _sampler\n#define TEXTURE_ACCEPT(name) texture2D name, sampler name ## _sampler\n#define TEXTURE_ACCEPT_HIGHP TEXTURE_ACCEPT\n#define GL2\n#define WEBGPU\n#define VERTEXSHADER\n#define gl_VertexID gl_VertexIndex\n#define gl_InstanceID gl_InstanceIndex\n`;\n\nvar webgpuPS = `\n`;\n\nvar webgpuVS = `\n#define VERTEXSHADER\n`;\n\nvar sharedGLSL = `\nvec2 getGrabScreenPos(vec4 clipPos) {\n\tvec2 uv = (clipPos.xy / clipPos.w) * 0.5 + 0.5;\n\t#ifdef WEBGPU\n\t\tuv.y = 1.0 - uv.y;\n\t#endif\n\treturn uv;\n}\nvec2 getImageEffectUV(vec2 uv) {\n\t#ifdef WEBGPU\n\t\tuv.y = 1.0 - uv.y;\n\t#endif\n\treturn uv;\n}\n`;\n\nvar sharedWGSL = `\nfn getGrabScreenPos(clipPos: vec4<f32>) -> vec2<f32> {\n\tvar uv: vec2<f32> = (clipPos.xy / clipPos.w) * 0.5 + vec2<f32>(0.5);\n\tuv.y = 1.0 - uv.y;\n\treturn uv;\n}\nfn getImageEffectUV(uv: vec2<f32>) -> vec2<f32> {\n\tvar modifiedUV: vec2<f32> = uv;\n\tmodifiedUV.y = 1.0 - modifiedUV.y;\n\treturn modifiedUV;\n}\nstruct WrappedF32 { @size(16) element: f32 }\nstruct WrappedI32 { @size(16) element: i32 }\nstruct WrappedU32 { @size(16) element: u32 }\nstruct WrappedVec2F { @size(16) element: vec2f }\nstruct WrappedVec2I { @size(16) element: vec2i }\nstruct WrappedVec2U { @size(16) element: vec2u }\n`;\n\nconst _attrib2Semantic = {\n\t\tvertex_position: SEMANTIC_POSITION,\n\t\tvertex_normal: SEMANTIC_NORMAL,\n\t\tvertex_tangent: SEMANTIC_TANGENT,\n\t\tvertex_texCoord0: SEMANTIC_TEXCOORD0,\n\t\tvertex_texCoord1: SEMANTIC_TEXCOORD1,\n\t\tvertex_texCoord2: SEMANTIC_TEXCOORD2,\n\t\tvertex_texCoord3: SEMANTIC_TEXCOORD3,\n\t\tvertex_texCoord4: SEMANTIC_TEXCOORD4,\n\t\tvertex_texCoord5: SEMANTIC_TEXCOORD5,\n\t\tvertex_texCoord6: SEMANTIC_TEXCOORD6,\n\t\tvertex_texCoord7: SEMANTIC_TEXCOORD7,\n\t\tvertex_color: SEMANTIC_COLOR,\n\t\tvertex_boneIndices: SEMANTIC_BLENDINDICES,\n\t\tvertex_boneWeights: SEMANTIC_BLENDWEIGHT\n};\nclass ShaderDefinitionUtils {\n\t\tstatic createDefinition(device, options) {\n\t\t\t\tconst normalizedOutputTypes = (options)=>{\n\t\t\t\t\t\tlet fragmentOutputTypes = options.fragmentOutputTypes ?? 'vec4';\n\t\t\t\t\t\tif (!Array.isArray(fragmentOutputTypes)) {\n\t\t\t\t\t\t\t\tfragmentOutputTypes = [\n\t\t\t\t\t\t\t\t\t\tfragmentOutputTypes\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn fragmentOutputTypes;\n\t\t\t\t};\n\t\t\t\tconst getDefines = (gpu, gl2, isVertex, options)=>{\n\t\t\t\t\t\tconst deviceIntro = device.isWebGPU ? gpu : gl2;\n\t\t\t\t\t\tlet attachmentsDefine = '';\n\t\t\t\t\t\tif (!isVertex) {\n\t\t\t\t\t\t\t\tconst fragmentOutputTypes = normalizedOutputTypes(options);\n\t\t\t\t\t\t\t\tfor(let i = 0; i < device.maxColorAttachments; i++){\n\t\t\t\t\t\t\t\t\t\tattachmentsDefine += `#define COLOR_ATTACHMENT_${i}\\n`;\n\t\t\t\t\t\t\t\t\t\tconst outType = fragmentOutputTypes[i] ?? 'vec4';\n\t\t\t\t\t\t\t\t\t\tattachmentsDefine += `#define outType_${i} ${outType}\\n`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn attachmentsDefine + deviceIntro;\n\t\t\t\t};\n\t\t\t\tconst getDefinesWgsl = (isVertex, options)=>{\n\t\t\t\t\t\tlet attachmentsDefine = '';\n\t\t\t\t\t\tif (!isVertex) {\n\t\t\t\t\t\t\t\tconst fragmentOutputTypes = normalizedOutputTypes(options);\n\t\t\t\t\t\t\t\tfor(let i = 0; i < device.maxColorAttachments; i++){\n\t\t\t\t\t\t\t\t\t\tconst glslOutType = fragmentOutputTypes[i] ?? 'vec4';\n\t\t\t\t\t\t\t\t\t\tconst wgslOutType = primitiveGlslToWgslTypeMap.get(glslOutType);\n\t\t\t\t\t\t\t\t\t\tattachmentsDefine += `alias pcOutType${i} = ${wgslOutType};\\n`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn attachmentsDefine;\n\t\t\t\t};\n\t\t\t\tconst name = options.name ?? 'Untitled';\n\t\t\t\tlet vertCode;\n\t\t\t\tlet fragCode;\n\t\t\t\tconst vertexDefinesCode = ShaderDefinitionUtils.getDefinesCode(device, options.vertexDefines);\n\t\t\t\tconst fragmentDefinesCode = ShaderDefinitionUtils.getDefinesCode(device, options.fragmentDefines);\n\t\t\t\tconst wgsl = options.shaderLanguage === SHADERLANGUAGE_WGSL;\n\t\t\t\tif (wgsl) {\n\t\t\t\t\t\tvertCode = `\n\t\t\t\t\t\t\t\t${getDefinesWgsl(true, options)}\n\t\t\t\t\t\t\t\t${webgpuVS}\n\t\t\t\t\t\t\t\t${sharedWGSL}\n\t\t\t\t\t\t\t\t${vertexDefinesCode}\n\t\t\t\t\t\t\t\t${options.vertexCode}\n\t\t\t\t\t\t`;\n\t\t\t\t\t\tfragCode = `\n\t\t\t\t\t\t\t\t${getDefinesWgsl(false, options)}\n\t\t\t\t\t\t\t\t${webgpuPS}\n\t\t\t\t\t\t\t\t${sharedWGSL}\n\t\t\t\t\t\t\t\t${fragmentDefinesCode}\n\t\t\t\t\t\t\t\t${options.fragmentCode}\n\t\t\t\t\t\t`;\n\t\t\t\t} else {\n\t\t\t\t\t\tvertCode = `${ShaderDefinitionUtils.versionCode(device) + getDefines(webgpuVS$1, gles3VS, true, options) + vertexDefinesCode + ShaderDefinitionUtils.precisionCode(device)}\n\t\t\t\t\t\t\t\t${sharedGLSL}\n\t\t\t\t\t\t\t\t${ShaderDefinitionUtils.getShaderNameCode(name)}\n\t\t\t\t\t\t\t\t${options.vertexCode}`;\n\t\t\t\t\t\tfragCode = `${(options.fragmentPreamble || '') + ShaderDefinitionUtils.versionCode(device) + getDefines(webgpuPS$1, gles3PS, false, options) + fragmentDefinesCode + ShaderDefinitionUtils.precisionCode(device)}\n\t\t\t\t\t\t\t\t${sharedGLSL}\n\t\t\t\t\t\t\t\t${ShaderDefinitionUtils.getShaderNameCode(name)}\n\t\t\t\t\t\t\t\t${options.fragmentCode}`;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\tshaderLanguage: options.shaderLanguage ?? SHADERLANGUAGE_GLSL,\n\t\t\t\t\t\tattributes: options.attributes,\n\t\t\t\t\t\tvshader: vertCode,\n\t\t\t\t\t\tvincludes: options.vertexIncludes,\n\t\t\t\t\t\tfincludes: options.fragmentIncludes,\n\t\t\t\t\t\tfshader: fragCode,\n\t\t\t\t\t\tuseTransformFeedback: options.useTransformFeedback,\n\t\t\t\t\t\tmeshUniformBufferFormat: options.meshUniformBufferFormat,\n\t\t\t\t\t\tmeshBindGroupFormat: options.meshBindGroupFormat\n\t\t\t\t};\n\t\t}\n\t\tstatic getDefinesCode(device, defines) {\n\t\t\t\tlet code = '';\n\t\t\t\tdevice.capsDefines.forEach((value, key)=>{\n\t\t\t\t\t\tcode += `#define ${key} ${value}\\n`;\n\t\t\t\t});\n\t\t\t\tcode += '\\n';\n\t\t\t\tdefines?.forEach((value, key)=>{\n\t\t\t\t\t\tcode += `#define ${key} ${value}\\n`;\n\t\t\t\t});\n\t\t\t\tcode += '\\n';\n\t\t\t\treturn code;\n\t\t}\n\t\tstatic getShaderNameCode(name) {\n\t\t\t\treturn `#define SHADER_NAME ${name}\\n`;\n\t\t}\n\t\tstatic versionCode(device) {\n\t\t\t\treturn device.isWebGPU ? '#version 450\\n' : '#version 300 es\\n';\n\t\t}\n\t\tstatic precisionCode(device, forcePrecision) {\n\t\t\t\tif (forcePrecision && forcePrecision !== 'highp' && forcePrecision !== 'mediump' && forcePrecision !== 'lowp') {\n\t\t\t\t\t\tforcePrecision = null;\n\t\t\t\t}\n\t\t\t\tif (forcePrecision) {\n\t\t\t\t\t\tif (forcePrecision === 'highp' && device.maxPrecision !== 'highp') {\n\t\t\t\t\t\t\t\tforcePrecision = 'mediump';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (forcePrecision === 'mediump' && device.maxPrecision === 'lowp') {\n\t\t\t\t\t\t\t\tforcePrecision = 'lowp';\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst precision = forcePrecision ? forcePrecision : device.precision;\n\t\t\t\tconst code = `\n\t\t\t\t\t\tprecision ${precision} float;\n\t\t\t\t\t\tprecision ${precision} int;\n\t\t\t\t\t\tprecision ${precision} usampler2D;\n\t\t\t\t\t\tprecision ${precision} isampler2D;\n\t\t\t\t\t\tprecision ${precision} sampler2DShadow;\n\t\t\t\t\t\tprecision ${precision} samplerCubeShadow;\n\t\t\t\t\t\tprecision ${precision} sampler2DArray;\n\t\t\t\t`;\n\t\t\t\treturn code;\n\t\t}\n\t\tstatic collectAttributes(vsCode) {\n\t\t\t\tconst attribs = {};\n\t\t\t\tlet attrs = 0;\n\t\t\t\tlet found = vsCode.indexOf('attribute');\n\t\t\t\twhile(found >= 0){\n\t\t\t\t\t\tif (found > 0 && vsCode[found - 1] === '/') break;\n\t\t\t\t\t\tlet ignore = false;\n\t\t\t\t\t\tif (found > 0) {\n\t\t\t\t\t\t\t\tlet startOfLine = vsCode.lastIndexOf('\\n', found);\n\t\t\t\t\t\t\t\tstartOfLine = startOfLine !== -1 ? startOfLine + 1 : 0;\n\t\t\t\t\t\t\t\tconst lineStartString = vsCode.substring(startOfLine, found);\n\t\t\t\t\t\t\t\tif (lineStartString.includes('#')) {\n\t\t\t\t\t\t\t\t\t\tignore = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!ignore) {\n\t\t\t\t\t\t\t\tconst endOfLine = vsCode.indexOf(';', found);\n\t\t\t\t\t\t\t\tconst startOfAttribName = vsCode.lastIndexOf(' ', endOfLine);\n\t\t\t\t\t\t\t\tconst attribName = vsCode.substring(startOfAttribName + 1, endOfLine);\n\t\t\t\t\t\t\t\tif (attribs[attribName]) ; else {\n\t\t\t\t\t\t\t\t\t\tconst semantic = _attrib2Semantic[attribName];\n\t\t\t\t\t\t\t\t\t\tif (semantic !== undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\tattribs[attribName] = semantic;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tattribs[attribName] = `ATTR${attrs}`;\n\t\t\t\t\t\t\t\t\t\t\t\tattrs++;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfound = vsCode.indexOf('attribute', found + 1);\n\t\t\t\t}\n\t\t\t\treturn attribs;\n\t\t}\n}\n\nlet id$8 = 0;\nclass Shader {\n\t\tconstructor(graphicsDevice, definition){\n\t\t\t\tthis.attributes = new Map();\n\t\t\t\tthis.id = id$8++;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.definition = definition;\n\t\t\t\tthis.name = definition.name || 'Untitled';\n\t\t\t\tthis.init();\n\t\t\t\tif (definition.cshader) {\n\t\t\t\t\t\tdefinition.cshader = Preprocessor.run(definition.cshader, definition.cincludes, {\n\t\t\t\t\t\t\t\tsourceName: `compute shader for ${this.label}`,\n\t\t\t\t\t\t\t\tstripDefines: true\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tconst wgsl = definition.shaderLanguage === SHADERLANGUAGE_WGSL;\n\t\t\t\t\t\tdefinition.vshader = Preprocessor.run(definition.vshader, definition.vincludes, {\n\t\t\t\t\t\t\t\tsourceName: `vertex shader for ${this.label}`,\n\t\t\t\t\t\t\t\tstripDefines: wgsl\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (definition.shaderLanguage === SHADERLANGUAGE_GLSL) {\n\t\t\t\t\t\t\t\tdefinition.attributes ??= ShaderDefinitionUtils.collectAttributes(definition.vshader);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst stripUnusedColorAttachments = graphicsDevice.isWebGL2 && (platform.name === 'osx' || platform.name === 'ios');\n\t\t\t\t\t\tdefinition.fshader = Preprocessor.run(definition.fshader, definition.fincludes, {\n\t\t\t\t\t\t\t\tstripUnusedColorAttachments,\n\t\t\t\t\t\t\t\tstripDefines: wgsl,\n\t\t\t\t\t\t\t\tsourceName: `fragment shader for ${this.label}`\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!definition.vshader || !definition.fshader) {\n\t\t\t\t\t\t\t\tthis.failed = true;\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.impl = graphicsDevice.createShaderImpl(this);\n\t\t}\n\t\tinit() {\n\t\t\t\tthis.ready = false;\n\t\t\t\tthis.failed = false;\n\t\t}\n\t\tget label() {\n\t\t\t\treturn `Shader Id ${this.id} (${this.definition.shaderLanguage === SHADERLANGUAGE_WGSL ? 'WGSL' : 'GLSL'}) ${this.name}`;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.device.onDestroyShader(this);\n\t\t\t\tthis.impl.destroy(this);\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.init();\n\t\t\t\tthis.impl.loseContext();\n\t\t}\n\t\trestoreContext() {\n\t\t\t\tthis.impl.restoreContext(this.device, this);\n\t\t}\n}\n\nclass UsedBuffer {\n}\nclass DynamicBufferAllocation {\n}\nclass DynamicBuffers {\n\t\tconstructor(device, bufferSize, bufferAlignment){\n\t\t\t\tthis.gpuBuffers = [];\n\t\t\t\tthis.stagingBuffers = [];\n\t\t\t\tthis.usedBuffers = [];\n\t\t\t\tthis.activeBuffer = null;\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.bufferSize = bufferSize;\n\t\t\t\tthis.bufferAlignment = bufferAlignment;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.gpuBuffers.forEach((gpuBuffer)=>{\n\t\t\t\t\t\tgpuBuffer.destroy(this.device);\n\t\t\t\t});\n\t\t\t\tthis.gpuBuffers = null;\n\t\t\t\tthis.stagingBuffers.forEach((stagingBuffer)=>{\n\t\t\t\t\t\tstagingBuffer.destroy(this.device);\n\t\t\t\t});\n\t\t\t\tthis.stagingBuffers = null;\n\t\t\t\tthis.usedBuffers = null;\n\t\t\t\tthis.activeBuffer = null;\n\t\t}\n\t\talloc(allocation, size) {\n\t\t\t\tif (this.activeBuffer) {\n\t\t\t\t\t\tconst alignedStart = math.roundUp(this.activeBuffer.size, this.bufferAlignment);\n\t\t\t\t\t\tconst space = this.bufferSize - alignedStart;\n\t\t\t\t\t\tif (space < size) {\n\t\t\t\t\t\t\t\tthis.scheduleSubmit();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!this.activeBuffer) {\n\t\t\t\t\t\tlet gpuBuffer = this.gpuBuffers.pop();\n\t\t\t\t\t\tif (!gpuBuffer) {\n\t\t\t\t\t\t\t\tgpuBuffer = this.createBuffer(this.device, this.bufferSize, false);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet stagingBuffer = this.stagingBuffers.pop();\n\t\t\t\t\t\tif (!stagingBuffer) {\n\t\t\t\t\t\t\t\tstagingBuffer = this.createBuffer(this.device, this.bufferSize, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.activeBuffer = new UsedBuffer();\n\t\t\t\t\t\tthis.activeBuffer.stagingBuffer = stagingBuffer;\n\t\t\t\t\t\tthis.activeBuffer.gpuBuffer = gpuBuffer;\n\t\t\t\t\t\tthis.activeBuffer.offset = 0;\n\t\t\t\t\t\tthis.activeBuffer.size = 0;\n\t\t\t\t}\n\t\t\t\tconst activeBuffer = this.activeBuffer;\n\t\t\t\tconst alignedStart = math.roundUp(activeBuffer.size, this.bufferAlignment);\n\t\t\t\tallocation.gpuBuffer = activeBuffer.gpuBuffer;\n\t\t\t\tallocation.offset = alignedStart;\n\t\t\t\tallocation.storage = activeBuffer.stagingBuffer.alloc(alignedStart, size);\n\t\t\t\tactiveBuffer.size = alignedStart + size;\n\t\t}\n\t\tscheduleSubmit() {\n\t\t\t\tif (this.activeBuffer) {\n\t\t\t\t\t\tthis.usedBuffers.push(this.activeBuffer);\n\t\t\t\t\t\tthis.activeBuffer = null;\n\t\t\t\t}\n\t\t}\n\t\tsubmit() {\n\t\t\t\tthis.scheduleSubmit();\n\t\t}\n}\n\nconst _updateFunctions = [];\n_updateFunctions[UNIFORMTYPE_FLOAT] = function(uniformBuffer, value, offset) {\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tdst[offset] = value;\n};\n_updateFunctions[UNIFORMTYPE_VEC2] = (uniformBuffer, value, offset)=>{\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n};\n_updateFunctions[UNIFORMTYPE_VEC3] = (uniformBuffer, value, offset)=>{\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 2] = value[2];\n};\n_updateFunctions[UNIFORMTYPE_VEC4] = (uniformBuffer, value, offset)=>{\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 2] = value[2];\n\t\tdst[offset + 3] = value[3];\n};\n_updateFunctions[UNIFORMTYPE_INT] = function(uniformBuffer, value, offset) {\n\t\tconst dst = uniformBuffer.storageInt32;\n\t\tdst[offset] = value;\n};\n_updateFunctions[UNIFORMTYPE_IVEC2] = function(uniformBuffer, value, offset) {\n\t\tconst dst = uniformBuffer.storageInt32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n};\n_updateFunctions[UNIFORMTYPE_IVEC3] = function(uniformBuffer, value, offset) {\n\t\tconst dst = uniformBuffer.storageInt32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 2] = value[2];\n};\n_updateFunctions[UNIFORMTYPE_IVEC4] = function(uniformBuffer, value, offset) {\n\t\tconst dst = uniformBuffer.storageInt32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 2] = value[2];\n\t\tdst[offset + 3] = value[3];\n};\n_updateFunctions[UNIFORMTYPE_MAT2] = (uniformBuffer, value, offset)=>{\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 4] = value[2];\n\t\tdst[offset + 5] = value[3];\n\t\tdst[offset + 8] = value[4];\n\t\tdst[offset + 9] = value[5];\n};\n_updateFunctions[UNIFORMTYPE_MAT3] = (uniformBuffer, value, offset)=>{\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 2] = value[2];\n\t\tdst[offset + 4] = value[3];\n\t\tdst[offset + 5] = value[4];\n\t\tdst[offset + 6] = value[5];\n\t\tdst[offset + 8] = value[6];\n\t\tdst[offset + 9] = value[7];\n\t\tdst[offset + 10] = value[8];\n};\n_updateFunctions[UNIFORMTYPE_FLOATARRAY] = function(uniformBuffer, value, offset, count) {\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_VEC2ARRAY] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i * 2];\n\t\t\t\tdst[offset + i * 4 + 1] = value[i * 2 + 1];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_VEC3ARRAY] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageFloat32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i * 3];\n\t\t\t\tdst[offset + i * 4 + 1] = value[i * 3 + 1];\n\t\t\t\tdst[offset + i * 4 + 2] = value[i * 3 + 2];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_UINT] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageUint32;\n\t\tdst[offset] = value;\n};\n_updateFunctions[UNIFORMTYPE_UVEC2] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageUint32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n};\n_updateFunctions[UNIFORMTYPE_UVEC3] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageUint32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 2] = value[2];\n};\n_updateFunctions[UNIFORMTYPE_UVEC4] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageUint32;\n\t\tdst[offset] = value[0];\n\t\tdst[offset + 1] = value[1];\n\t\tdst[offset + 2] = value[2];\n\t\tdst[offset + 3] = value[3];\n};\n_updateFunctions[UNIFORMTYPE_INTARRAY] = function(uniformBuffer, value, offset, count) {\n\t\tconst dst = uniformBuffer.storageInt32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_BOOLARRAY] = _updateFunctions[UNIFORMTYPE_INTARRAY];\n_updateFunctions[UNIFORMTYPE_UINTARRAY] = function(uniformBuffer, value, offset, count) {\n\t\tconst dst = uniformBuffer.storageUint32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_IVEC2ARRAY] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageInt32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i * 2];\n\t\t\t\tdst[offset + i * 4 + 1] = value[i * 2 + 1];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_BVEC2ARRAY] = _updateFunctions[UNIFORMTYPE_IVEC2ARRAY];\n_updateFunctions[UNIFORMTYPE_UVEC2ARRAY] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageUint32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i * 2];\n\t\t\t\tdst[offset + i * 4 + 1] = value[i * 2 + 1];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_IVEC3ARRAY] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageInt32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i * 3];\n\t\t\t\tdst[offset + i * 4 + 1] = value[i * 3 + 1];\n\t\t\t\tdst[offset + i * 4 + 2] = value[i * 3 + 2];\n\t\t}\n};\n_updateFunctions[UNIFORMTYPE_BVEC3ARRAY] = _updateFunctions[UNIFORMTYPE_IVEC3ARRAY];\n_updateFunctions[UNIFORMTYPE_UVEC3ARRAY] = (uniformBuffer, value, offset, count)=>{\n\t\tconst dst = uniformBuffer.storageUint32;\n\t\tfor(let i = 0; i < count; i++){\n\t\t\t\tdst[offset + i * 4] = value[i * 3];\n\t\t\t\tdst[offset + i * 4 + 1] = value[i * 3 + 1];\n\t\t\t\tdst[offset + i * 4 + 2] = value[i * 3 + 2];\n\t\t}\n};\nclass UniformBuffer {\n\t\tconstructor(graphicsDevice, format, persistent = true){\n\t\t\t\tthis.renderVersionDirty = 0;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.format = format;\n\t\t\t\tthis.persistent = persistent;\n\t\t\t\tif (persistent) {\n\t\t\t\t\t\tthis.impl = graphicsDevice.createUniformBufferImpl(this);\n\t\t\t\t\t\tconst storage = new ArrayBuffer(format.byteSize);\n\t\t\t\t\t\tthis.assignStorage(new Int32Array(storage));\n\t\t\t\t\t\tgraphicsDevice._vram.ub += this.format.byteSize;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.allocation = new DynamicBufferAllocation();\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this.persistent) {\n\t\t\t\t\t\tconst device = this.device;\n\t\t\t\t\t\tthis.impl.destroy(device);\n\t\t\t\t\t\tdevice._vram.ub -= this.format.byteSize;\n\t\t\t\t}\n\t\t}\n\t\tget offset() {\n\t\t\t\treturn this.persistent ? 0 : this.allocation.offset;\n\t\t}\n\t\tassignStorage(storage) {\n\t\t\t\tthis.storageInt32 = storage;\n\t\t\t\tthis.storageUint32 = new Uint32Array(storage.buffer, storage.byteOffset, storage.byteLength / 4);\n\t\t\t\tthis.storageFloat32 = new Float32Array(storage.buffer, storage.byteOffset, storage.byteLength / 4);\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.impl?.loseContext();\n\t\t}\n\t\tsetUniform(uniformFormat, value) {\n\t\t\t\tconst offset = uniformFormat.offset;\n\t\t\t\tif (value !== null && value !== undefined) {\n\t\t\t\t\t\tconst updateFunction = _updateFunctions[uniformFormat.updateType];\n\t\t\t\t\t\tif (updateFunction) {\n\t\t\t\t\t\t\t\tupdateFunction(this, value, offset, uniformFormat.count);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.storageFloat32.set(value, offset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tset(name, value) {\n\t\t\t\tconst uniformFormat = this.format.map.get(name);\n\t\t\t\tif (uniformFormat) {\n\t\t\t\t\t\tthis.setUniform(uniformFormat, value);\n\t\t\t\t}\n\t\t}\n\t\tstartUpdate(dynamicBindGroup) {\n\t\t\t\tif (!this.persistent) {\n\t\t\t\t\t\tconst allocation = this.allocation;\n\t\t\t\t\t\tconst oldGpuBuffer = allocation.gpuBuffer;\n\t\t\t\t\t\tthis.device.dynamicBuffers.alloc(allocation, this.format.byteSize);\n\t\t\t\t\t\tthis.assignStorage(allocation.storage);\n\t\t\t\t\t\tif (dynamicBindGroup) {\n\t\t\t\t\t\t\t\tdynamicBindGroup.bindGroup = allocation.gpuBuffer.getBindGroup(this);\n\t\t\t\t\t\t\t\tdynamicBindGroup.offsets[0] = allocation.offset;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (oldGpuBuffer !== allocation.gpuBuffer) {\n\t\t\t\t\t\t\t\tthis.renderVersionDirty = this.device.renderVersion;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tendUpdate() {\n\t\t\t\tif (this.persistent) {\n\t\t\t\t\t\tthis.impl.unlock(this);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.storageFloat32 = null;\n\t\t\t\t\t\tthis.storageInt32 = null;\n\t\t\t\t}\n\t\t}\n\t\tupdate(dynamicBindGroup) {\n\t\t\t\tthis.startUpdate(dynamicBindGroup);\n\t\t\t\tconst uniforms = this.format.uniforms;\n\t\t\t\tfor(let i = 0; i < uniforms.length; i++){\n\t\t\t\t\t\tconst value = uniforms[i].scopeId.value;\n\t\t\t\t\t\tthis.setUniform(uniforms[i], value);\n\t\t\t\t}\n\t\t\t\tthis.endUpdate();\n\t\t}\n}\n\nconst primitive = {\n\t\ttype: PRIMITIVE_TRISTRIP,\n\t\tbase: 0,\n\t\tbaseVertex: 0,\n\t\tcount: 4,\n\t\tindexed: false\n};\nclass WebgpuClearRenderer {\n\t\tconstructor(device){\n\t\t\t\tconst code = `\n\n\t\t\t\t\t\tstruct ub_mesh {\n\t\t\t\t\t\t\t\tcolor : vec4f,\n\t\t\t\t\t\t\t\tdepth: f32\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@group(2) @binding(0) var<uniform> ubMesh : ub_mesh;\n\n\t\t\t\t\t\tvar<private> pos : array<vec2f, 4> = array<vec2f, 4>(\n\t\t\t\t\t\t\t\tvec2(-1.0, 1.0), vec2(1.0, 1.0),\n\t\t\t\t\t\t\t\tvec2(-1.0, -1.0), vec2(1.0, -1.0)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tstruct VertexOutput {\n\t\t\t\t\t\t\t\t@builtin(position) position : vec4f\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@vertex\n\t\t\t\t\t\tfn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {\n\t\t\t\t\t\t\t\tvar output : VertexOutput;\n\t\t\t\t\t\t\t\toutput.position = vec4(pos[vertexIndex], ubMesh.depth, 1.0);\n\t\t\t\t\t\t\t\treturn output;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@fragment\n\t\t\t\t\t\tfn fragmentMain() -> @location(0) vec4f {\n\t\t\t\t\t\t\t\treturn ubMesh.color;\n\t\t\t\t\t\t}\n\t\t\t\t`;\n\t\t\t\tthis.shader = new Shader(device, {\n\t\t\t\t\t\tname: 'WebGPUClearRendererShader',\n\t\t\t\t\t\tshaderLanguage: SHADERLANGUAGE_WGSL,\n\t\t\t\t\t\tvshader: code,\n\t\t\t\t\t\tfshader: code\n\t\t\t\t});\n\t\t\t\tthis.uniformBuffer = new UniformBuffer(device, new UniformBufferFormat(device, [\n\t\t\t\t\t\tnew UniformFormat('color', UNIFORMTYPE_VEC4),\n\t\t\t\t\t\tnew UniformFormat('depth', UNIFORMTYPE_FLOAT)\n\t\t\t\t]), false);\n\t\t\t\tthis.dynamicBindGroup = new DynamicBindGroup();\n\t\t\t\tthis.colorData = new Float32Array(4);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.shader.destroy();\n\t\t\t\tthis.shader = null;\n\t\t\t\tthis.uniformBuffer.destroy();\n\t\t\t\tthis.uniformBuffer = null;\n\t\t}\n\t\tclear(device, renderTarget, options, defaultOptions) {\n\t\t\t\toptions = options || defaultOptions;\n\t\t\t\tconst flags = options.flags ?? defaultOptions.flags;\n\t\t\t\tif (flags !== 0) {\n\t\t\t\t\t\tconst { uniformBuffer, dynamicBindGroup } = this;\n\t\t\t\t\t\tuniformBuffer.startUpdate(dynamicBindGroup);\n\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_MESH_UB, dynamicBindGroup.bindGroup, dynamicBindGroup.offsets);\n\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_MESH, device.emptyBindGroup);\n\t\t\t\t\t\tif (flags & CLEARFLAG_COLOR && (renderTarget.colorBuffer || renderTarget.impl.assignedColorTexture)) {\n\t\t\t\t\t\t\t\tconst color = options.color ?? defaultOptions.color;\n\t\t\t\t\t\t\t\tthis.colorData.set(color);\n\t\t\t\t\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdevice.setBlendState(BlendState.NOWRITE);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tuniformBuffer.set('color', this.colorData);\n\t\t\t\t\t\tif (flags & CLEARFLAG_DEPTH && renderTarget.depth) {\n\t\t\t\t\t\t\t\tconst depth = options.depth ?? defaultOptions.depth;\n\t\t\t\t\t\t\t\tuniformBuffer.set('depth', depth);\n\t\t\t\t\t\t\t\tdevice.setDepthState(DepthState.WRITEDEPTH);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tuniformBuffer.set('depth', 1);\n\t\t\t\t\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (flags & CLEARFLAG_STENCIL && renderTarget.stencil) ;\n\t\t\t\t\t\tuniformBuffer.endUpdate();\n\t\t\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\t\t\tdevice.setShader(this.shader);\n\t\t\t\t\t\tdevice.draw(primitive);\n\t\t\t\t}\n\t\t}\n}\n\nclass WebgpuMipmapRenderer {\n\t\tconstructor(device){\n\t\t\t\tthis.device = device;\n\t\t\t\tconst code = `\n \n\t\t\t\t\t\tvar<private> pos : array<vec2f, 4> = array<vec2f, 4>(\n\t\t\t\t\t\t\t\tvec2(-1.0, 1.0), vec2(1.0, 1.0),\n\t\t\t\t\t\t\t\tvec2(-1.0, -1.0), vec2(1.0, -1.0)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tstruct VertexOutput {\n\t\t\t\t\t\t\t\t@builtin(position) position : vec4f,\n\t\t\t\t\t\t\t\t@location(0) texCoord : vec2f\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t@vertex\n\t\t\t\t\t\tfn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {\n\t\t\t\t\t\t\tvar output : VertexOutput;\n\t\t\t\t\t\t\toutput.texCoord = pos[vertexIndex] * vec2f(0.5, -0.5) + vec2f(0.5);\n\t\t\t\t\t\t\toutput.position = vec4f(pos[vertexIndex], 0, 1);\n\t\t\t\t\t\t\treturn output;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@group(0) @binding(0) var imgSampler : sampler;\n\t\t\t\t\t\t@group(0) @binding(1) var img : texture_2d<f32>;\n\n\t\t\t\t\t\t@fragment\n\t\t\t\t\t\tfn fragmentMain(@location(0) texCoord : vec2f) -> @location(0) vec4f {\n\t\t\t\t\t\t\treturn textureSample(img, imgSampler, texCoord);\n\t\t\t\t\t\t}\n\t\t\t\t`;\n\t\t\t\tthis.shader = new Shader(device, {\n\t\t\t\t\t\tname: 'WebGPUMipmapRendererShader',\n\t\t\t\t\t\tshaderLanguage: SHADERLANGUAGE_WGSL,\n\t\t\t\t\t\tvshader: code,\n\t\t\t\t\t\tfshader: code\n\t\t\t\t});\n\t\t\t\tthis.minSampler = device.wgpu.createSampler({\n\t\t\t\t\t\tminFilter: 'linear'\n\t\t\t\t});\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.shader.destroy();\n\t\t\t\tthis.shader = null;\n\t\t}\n\t\tgenerate(webgpuTexture) {\n\t\t\t\tconst textureDescr = webgpuTexture.desc;\n\t\t\t\tif (textureDescr.mipLevelCount <= 1) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (webgpuTexture.texture.volume) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tconst webgpuShader = this.shader.impl;\n\t\t\t\tconst pipeline = wgpu.createRenderPipeline({\n\t\t\t\t\t\tlayout: 'auto',\n\t\t\t\t\t\tvertex: {\n\t\t\t\t\t\t\t\tmodule: webgpuShader.getVertexShaderModule(),\n\t\t\t\t\t\t\t\tentryPoint: webgpuShader.vertexEntryPoint\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfragment: {\n\t\t\t\t\t\t\t\tmodule: webgpuShader.getFragmentShaderModule(),\n\t\t\t\t\t\t\t\tentryPoint: webgpuShader.fragmentEntryPoint,\n\t\t\t\t\t\t\t\ttargets: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tformat: textureDescr.format\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\tprimitive: {\n\t\t\t\t\t\t\t\ttopology: 'triangle-strip'\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst texture = webgpuTexture.texture;\n\t\t\t\tconst numFaces = texture.cubemap ? 6 : texture.array ? texture.arrayLength : 1;\n\t\t\t\tconst srcViews = [];\n\t\t\t\tfor(let face = 0; face < numFaces; face++){\n\t\t\t\t\t\tsrcViews.push(webgpuTexture.createView({\n\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\tbaseMipLevel: 0,\n\t\t\t\t\t\t\t\tmipLevelCount: 1,\n\t\t\t\t\t\t\t\tbaseArrayLayer: face\n\t\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t\tconst commandEncoder = device.getCommandEncoder();\n\t\t\t\tfor(let i = 1; i < textureDescr.mipLevelCount; i++){\n\t\t\t\t\t\tfor(let face = 0; face < numFaces; face++){\n\t\t\t\t\t\t\t\tconst dstView = webgpuTexture.createView({\n\t\t\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\t\t\tbaseMipLevel: i,\n\t\t\t\t\t\t\t\t\t\tmipLevelCount: 1,\n\t\t\t\t\t\t\t\t\t\tbaseArrayLayer: face\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tconst passEncoder = commandEncoder.beginRenderPass({\n\t\t\t\t\t\t\t\t\t\tcolorAttachments: [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tview: dstView,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tloadOp: 'clear',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstoreOp: 'store'\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tconst bindGroup = wgpu.createBindGroup({\n\t\t\t\t\t\t\t\t\t\tlayout: pipeline.getBindGroupLayout(0),\n\t\t\t\t\t\t\t\t\t\tentries: [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbinding: 0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresource: this.minSampler\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbinding: 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresource: srcViews[face]\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tpassEncoder.setPipeline(pipeline);\n\t\t\t\t\t\t\t\tpassEncoder.setBindGroup(0, bindGroup);\n\t\t\t\t\t\t\t\tpassEncoder.draw(4);\n\t\t\t\t\t\t\t\tpassEncoder.end();\n\t\t\t\t\t\t\t\tsrcViews[face] = dstView;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdevice.pipeline = null;\n\t\t}\n}\n\nclass DynamicBuffer {\n\t\tconstructor(device){\n\t\t\t\tthis.bindGroupCache = new Map();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.bindGroupFormat = new BindGroupFormat(this.device, [\n\t\t\t\t\t\tnew BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)\n\t\t\t\t]);\n\t\t}\n\t\tgetBindGroup(ub) {\n\t\t\t\tconst ubSize = ub.format.byteSize;\n\t\t\t\tlet bindGroup = this.bindGroupCache.get(ubSize);\n\t\t\t\tif (!bindGroup) {\n\t\t\t\t\t\tbindGroup = new BindGroup(this.device, this.bindGroupFormat, ub);\n\t\t\t\t\t\tbindGroup.update();\n\t\t\t\t\t\tthis.bindGroupCache.set(ubSize, bindGroup);\n\t\t\t\t}\n\t\t\t\treturn bindGroup;\n\t\t}\n}\n\nclass WebgpuDynamicBuffer extends DynamicBuffer {\n\t\tconstructor(device, size, isStaging){\n\t\t\t\tsuper(device), this.buffer = null, this.mappedRange = null;\n\t\t\t\tthis.buffer = device.wgpu.createBuffer({\n\t\t\t\t\t\tsize: size,\n\t\t\t\t\t\tusage: isStaging ? GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC : GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n\t\t\t\t\t\tmappedAtCreation: isStaging\n\t\t\t\t});\n\t\t\t\tif (isStaging) {\n\t\t\t\t\t\tthis.onAvailable();\n\t\t\t\t}\n\t\t\t\tdevice._vram.ub += size;\n\t\t}\n\t\tdestroy(device) {\n\t\t\t\tdevice._vram.ub -= this.buffer.size;\n\t\t\t\tthis.buffer.destroy();\n\t\t\t\tthis.buffer = null;\n\t\t}\n\t\tonAvailable() {\n\t\t\t\tthis.mappedRange = this.buffer.getMappedRange();\n\t\t}\n\t\talloc(offset, size) {\n\t\t\t\treturn new Int32Array(this.mappedRange, offset, size / 4);\n\t\t}\n}\n\nclass WebgpuDynamicBuffers extends DynamicBuffers {\n\t\tcreateBuffer(device, size, isStaging) {\n\t\t\t\treturn new WebgpuDynamicBuffer(device, size, isStaging);\n\t\t}\n\t\tsubmit() {\n\t\t\t\tsuper.submit();\n\t\t\t\tconst count = this.usedBuffers.length;\n\t\t\t\tif (count) {\n\t\t\t\t\t\tconst device = this.device;\n\t\t\t\t\t\tconst gpuBuffers = this.gpuBuffers;\n\t\t\t\t\t\tconst commandEncoder = device.wgpu.createCommandEncoder();\n\t\t\t\t\t\tfor(let i = count - 1; i >= 0; i--){\n\t\t\t\t\t\t\t\tconst usedBuffer = this.usedBuffers[i];\n\t\t\t\t\t\t\t\tconst { stagingBuffer, gpuBuffer, offset, size } = usedBuffer;\n\t\t\t\t\t\t\t\tconst src = stagingBuffer.buffer;\n\t\t\t\t\t\t\t\tsrc.unmap();\n\t\t\t\t\t\t\t\tcommandEncoder.copyBufferToBuffer(src, offset, gpuBuffer.buffer, offset, size);\n\t\t\t\t\t\t\t\tgpuBuffers.push(gpuBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst cb = commandEncoder.finish();\n\t\t\t\t\t\tdevice.addCommandBuffer(cb, true);\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\tconst stagingBuffer = this.usedBuffers[i].stagingBuffer;\n\t\t\t\t\t\t\t\tthis.pendingStagingBuffers.push(stagingBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.usedBuffers.length = 0;\n\t\t\t\t}\n\t\t}\n\t\tonCommandBuffersSubmitted() {\n\t\t\t\tconst count = this.pendingStagingBuffers.length;\n\t\t\t\tif (count) {\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\tconst stagingBuffer = this.pendingStagingBuffers[i];\n\t\t\t\t\t\t\t\tstagingBuffer.buffer.mapAsync(GPUMapMode.WRITE).then(()=>{\n\t\t\t\t\t\t\t\t\t\tif (this.stagingBuffers) {\n\t\t\t\t\t\t\t\t\t\t\t\tstagingBuffer.onAvailable();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.stagingBuffers.push(stagingBuffer);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.pendingStagingBuffers.length = 0;\n\t\t\t\t}\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this.pendingStagingBuffers = [];\n\t\t}\n}\n\nclass GpuProfiler {\n\t\tloseContext() {\n\t\t\t\tthis.pastFrameAllocations.clear();\n\t\t}\n\t\tset enabled(value) {\n\t\t\t\tthis._enableRequest = value;\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enableRequest;\n\t\t}\n\t\tprocessEnableRequest() {\n\t\t\t\tif (this._enableRequest !== this._enabled) {\n\t\t\t\t\t\tthis._enabled = this._enableRequest;\n\t\t\t\t\t\tif (!this._enabled) {\n\t\t\t\t\t\t\t\tthis._frameTime = 0;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\trequest(renderVersion) {\n\t\t\t\tthis.pastFrameAllocations.set(renderVersion, this.frameAllocations);\n\t\t\t\tthis.frameAllocations = [];\n\t\t}\n\t\treport(renderVersion, timings) {\n\t\t\t\tif (timings) {\n\t\t\t\t\t\tconst allocations = this.pastFrameAllocations.get(renderVersion);\n\t\t\t\t\t\tif (timings.length > 0) {\n\t\t\t\t\t\t\t\tthis._frameTime = timings.reduce((sum, t)=>sum + t, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (Tracing.get(TRACEID_GPU_TIMINGS)) {\n\t\t\t\t\t\t\t\tlet total = 0;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < allocations.length; ++i){\n\t\t\t\t\t\t\t\t\t\tallocations[i];\n\t\t\t\t\t\t\t\t\t\ttotal += timings[i];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.pastFrameAllocations.delete(renderVersion);\n\t\t}\n\t\tgetSlot(name) {\n\t\t\t\tconst slot = this.frameAllocations.length;\n\t\t\t\tthis.frameAllocations.push(name);\n\t\t\t\treturn slot;\n\t\t}\n\t\tget slotCount() {\n\t\t\t\treturn this.frameAllocations.length;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.frameAllocations = [];\n\t\t\t\tthis.pastFrameAllocations = new Map();\n\t\t\t\tthis._enabled = false;\n\t\t\t\tthis._enableRequest = false;\n\t\t\t\tthis._frameTime = 0;\n\t\t}\n}\n\nclass WebgpuQuerySet {\n\t\tconstructor(device, isTimestamp, capacity){\n\t\t\t\tthis.stagingBuffers = [];\n\t\t\t\tthis.activeStagingBuffer = null;\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.capacity = capacity;\n\t\t\t\tthis.bytesPerSlot = isTimestamp ? 8 : 4;\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tthis.querySet = wgpu.createQuerySet({\n\t\t\t\t\t\ttype: isTimestamp ? 'timestamp' : 'occlusion',\n\t\t\t\t\t\tcount: capacity\n\t\t\t\t});\n\t\t\t\tthis.queryBuffer = wgpu.createBuffer({\n\t\t\t\t\t\tsize: this.bytesPerSlot * capacity,\n\t\t\t\t\t\tusage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST\n\t\t\t\t});\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.querySet?.destroy();\n\t\t\t\tthis.querySet = null;\n\t\t\t\tthis.queryBuffer?.destroy();\n\t\t\t\tthis.queryBuffer = null;\n\t\t\t\tthis.activeStagingBuffer = null;\n\t\t\t\tthis.stagingBuffers.forEach((stagingBuffer)=>{\n\t\t\t\t\t\tstagingBuffer.destroy();\n\t\t\t\t});\n\t\t\t\tthis.stagingBuffers = null;\n\t\t}\n\t\tgetStagingBuffer() {\n\t\t\t\tlet stagingBuffer = this.stagingBuffers.pop();\n\t\t\t\tif (!stagingBuffer) {\n\t\t\t\t\t\tstagingBuffer = this.device.wgpu.createBuffer({\n\t\t\t\t\t\t\t\tsize: this.queryBuffer.size,\n\t\t\t\t\t\t\t\tusage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn stagingBuffer;\n\t\t}\n\t\tresolve(count) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst commandEncoder = device.getCommandEncoder();\n\t\t\t\tcommandEncoder.resolveQuerySet(this.querySet, 0, count, this.queryBuffer, 0);\n\t\t\t\tconst activeStagingBuffer = this.getStagingBuffer();\n\t\t\t\tthis.activeStagingBuffer = activeStagingBuffer;\n\t\t\t\tcommandEncoder.copyBufferToBuffer(this.queryBuffer, 0, activeStagingBuffer, 0, this.bytesPerSlot * count);\n\t\t}\n\t\trequest(count, renderVersion) {\n\t\t\t\tconst stagingBuffer = this.activeStagingBuffer;\n\t\t\t\tthis.activeStagingBuffer = null;\n\t\t\t\treturn stagingBuffer.mapAsync(GPUMapMode.READ).then(()=>{\n\t\t\t\t\t\tconst srcTimings = new BigInt64Array(stagingBuffer.getMappedRange());\n\t\t\t\t\t\tconst timings = [];\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\ttimings.push(Number(srcTimings[i * 2 + 1] - srcTimings[i * 2]) * 0.000001);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstagingBuffer.unmap();\n\t\t\t\t\t\tthis.stagingBuffers?.push(stagingBuffer);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\trenderVersion,\n\t\t\t\t\t\t\t\ttimings\n\t\t\t\t\t\t};\n\t\t\t\t});\n\t\t}\n}\n\nclass WebgpuGpuProfiler extends GpuProfiler {\n\t\tconstructor(device){\n\t\t\t\tsuper();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.timestampQueriesSet = device.supportsTimestampQuery ? new WebgpuQuerySet(device, true, 512) : null;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.timestampQueriesSet?.destroy();\n\t\t\t\tthis.timestampQueriesSet = null;\n\t\t}\n\t\tframeStart() {\n\t\t\t\tthis.processEnableRequest();\n\t\t}\n\t\tframeEnd() {\n\t\t\t\tif (this._enabled) {\n\t\t\t\t\t\tthis.timestampQueriesSet?.resolve(this.slotCount * 2);\n\t\t\t\t}\n\t\t}\n\t\trequest() {\n\t\t\t\tif (this._enabled) {\n\t\t\t\t\t\tconst renderVersion = this.device.renderVersion;\n\t\t\t\t\t\tthis.timestampQueriesSet?.request(this.slotCount, renderVersion).then((results)=>{\n\t\t\t\t\t\t\t\tthis.report(results.renderVersion, results.timings);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsuper.request(renderVersion);\n\t\t\t\t}\n\t\t}\n}\n\nclass WebgpuResolver {\n\t\tconstructor(device){\n\t\t\t\tthis.pipelineCache = new Map();\n\t\t\t\tthis.device = device;\n\t\t\t\tconst code = `\n \n\t\t\t\t\t\tvar<private> pos : array<vec2f, 4> = array<vec2f, 4>(\n\t\t\t\t\t\t\t\tvec2(-1.0, 1.0), vec2(1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, -1.0)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tstruct VertexOutput {\n\t\t\t\t\t\t\t\t@builtin(position) position : vec4f,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t@vertex\n\t\t\t\t\t\tfn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {\n\t\t\t\t\t\t\tvar output : VertexOutput;\n\t\t\t\t\t\t\toutput.position = vec4f(pos[vertexIndex], 0, 1);\n\t\t\t\t\t\t\treturn output;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@group(0) @binding(0) var img : texture_depth_multisampled_2d;\n\n\t\t\t\t\t\t@fragment\n\t\t\t\t\t\tfn fragmentMain(@builtin(position) fragColor: vec4f) -> @location(0) vec4f {\n\t\t\t\t\t\t\t\t// load th depth value from sample index 0\n\t\t\t\t\t\t\t\tvar depth = textureLoad(img, vec2i(fragColor.xy), 0u);\n\t\t\t\t\t\t\t\treturn vec4f(depth, 0.0, 0.0, 0.0);\n\t\t\t\t\t\t}\n\t\t\t\t`;\n\t\t\t\tthis.shader = new Shader(device, {\n\t\t\t\t\t\tname: 'WebGPUResolverDepthShader',\n\t\t\t\t\t\tshaderLanguage: SHADERLANGUAGE_WGSL,\n\t\t\t\t\t\tvshader: code,\n\t\t\t\t\t\tfshader: code\n\t\t\t\t});\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.shader.destroy();\n\t\t\t\tthis.shader = null;\n\t\t\t\tthis.pipelineCache = null;\n\t\t}\n\t\tgetPipeline(format) {\n\t\t\t\tlet pipeline = this.pipelineCache.get(format);\n\t\t\t\tif (!pipeline) {\n\t\t\t\t\t\tpipeline = this.createPipeline(format);\n\t\t\t\t\t\tthis.pipelineCache.set(format, pipeline);\n\t\t\t\t}\n\t\t\t\treturn pipeline;\n\t\t}\n\t\tcreatePipeline(format) {\n\t\t\t\tconst webgpuShader = this.shader.impl;\n\t\t\t\tconst pipeline = this.device.wgpu.createRenderPipeline({\n\t\t\t\t\t\tlayout: 'auto',\n\t\t\t\t\t\tvertex: {\n\t\t\t\t\t\t\t\tmodule: webgpuShader.getVertexShaderModule(),\n\t\t\t\t\t\t\t\tentryPoint: webgpuShader.vertexEntryPoint\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfragment: {\n\t\t\t\t\t\t\t\tmodule: webgpuShader.getFragmentShaderModule(),\n\t\t\t\t\t\t\t\tentryPoint: webgpuShader.fragmentEntryPoint,\n\t\t\t\t\t\t\t\ttargets: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tformat: format\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\tprimitive: {\n\t\t\t\t\t\t\t\ttopology: 'triangle-strip'\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn pipeline;\n\t\t}\n\t\tresolveDepth(commandEncoder, sourceTexture, destinationTexture) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst wgpu = device.wgpu;\n\t\t\t\tconst pipeline = this.getPipeline(destinationTexture.format);\n\t\t\t\tconst numFaces = sourceTexture.depthOrArrayLayers;\n\t\t\t\tfor(let face = 0; face < numFaces; face++){\n\t\t\t\t\t\tconst srcView = sourceTexture.createView({\n\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\taspect: 'depth-only',\n\t\t\t\t\t\t\t\tbaseMipLevel: 0,\n\t\t\t\t\t\t\t\tmipLevelCount: 1,\n\t\t\t\t\t\t\t\tbaseArrayLayer: face\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst dstView = destinationTexture.createView({\n\t\t\t\t\t\t\t\tdimension: '2d',\n\t\t\t\t\t\t\t\tbaseMipLevel: 0,\n\t\t\t\t\t\t\t\tmipLevelCount: 1,\n\t\t\t\t\t\t\t\tbaseArrayLayer: face\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst passEncoder = commandEncoder.beginRenderPass({\n\t\t\t\t\t\t\t\tcolorAttachments: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tview: dstView,\n\t\t\t\t\t\t\t\t\t\t\t\tloadOp: 'clear',\n\t\t\t\t\t\t\t\t\t\t\t\tstoreOp: 'store'\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst bindGroup = wgpu.createBindGroup({\n\t\t\t\t\t\t\t\tlayout: pipeline.getBindGroupLayout(0),\n\t\t\t\t\t\t\t\tentries: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tbinding: 0,\n\t\t\t\t\t\t\t\t\t\t\t\tresource: srcView\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tpassEncoder.setPipeline(pipeline);\n\t\t\t\t\t\tpassEncoder.setBindGroup(0, bindGroup);\n\t\t\t\t\t\tpassEncoder.draw(4);\n\t\t\t\t\t\tpassEncoder.end();\n\t\t\t\t}\n\t\t\t\tdevice.pipeline = null;\n\t\t}\n}\n\nclass WebgpuCompute {\n\t\tconstructor(compute){\n\t\t\t\tthis.uniformBuffers = [];\n\t\t\t\tthis.bindGroup = null;\n\t\t\t\tthis.compute = compute;\n\t\t\t\tconst { device, shader } = compute;\n\t\t\t\tconst { computeBindGroupFormat, computeUniformBufferFormats } = shader.impl;\n\t\t\t\tthis.bindGroup = new BindGroup(device, computeBindGroupFormat);\n\t\t\t\tif (computeUniformBufferFormats) {\n\t\t\t\t\t\tfor(const name in computeUniformBufferFormats){\n\t\t\t\t\t\t\t\tif (computeUniformBufferFormats.hasOwnProperty(name)) {\n\t\t\t\t\t\t\t\t\t\tconst ub = new UniformBuffer(device, computeUniformBufferFormats[name], true);\n\t\t\t\t\t\t\t\t\t\tthis.uniformBuffers.push(ub);\n\t\t\t\t\t\t\t\t\t\tthis.bindGroup.setUniformBuffer(name, ub);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.pipeline = device.computePipeline.get(shader, computeBindGroupFormat);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.uniformBuffers.forEach((ub)=>ub.destroy());\n\t\t\t\tthis.uniformBuffers.length = 0;\n\t\t\t\tthis.bindGroup.destroy();\n\t\t\t\tthis.bindGroup = null;\n\t\t}\n\t\tupdateBindGroup() {\n\t\t\t\tconst { bindGroup } = this;\n\t\t\t\tbindGroup.updateUniformBuffers();\n\t\t\t\tbindGroup.update();\n\t\t}\n\t\tdispatch(x, y, z) {\n\t\t\t\tconst device = this.compute.device;\n\t\t\t\tdevice.setBindGroup(0, this.bindGroup);\n\t\t\t\tconst passEncoder = device.passEncoder;\n\t\t\t\tpassEncoder.setPipeline(this.pipeline);\n\t\t\t\tpassEncoder.dispatchWorkgroups(x, y, z);\n\t\t}\n}\n\nlet id$7 = 0;\nclass StorageBuffer {\n\t\tconstructor(graphicsDevice, byteSize, bufferUsage = 0){\n\t\t\t\tthis.id = id$7++;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.byteSize = byteSize;\n\t\t\t\tthis.bufferUsage = bufferUsage;\n\t\t\t\tthis.impl = graphicsDevice.createBufferImpl(BUFFERUSAGE_STORAGE | bufferUsage);\n\t\t\t\tthis.impl.allocate(graphicsDevice, byteSize);\n\t\t\t\tthis.device.buffers.push(this);\n\t\t\t\tthis.adjustVramSizeTracking(graphicsDevice._vram, this.byteSize);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst idx = device.buffers.indexOf(this);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tdevice.buffers.splice(idx, 1);\n\t\t\t\t}\n\t\t\t\tthis.adjustVramSizeTracking(device._vram, -this.byteSize);\n\t\t\t\tthis.impl.destroy(device);\n\t\t}\n\t\tadjustVramSizeTracking(vram, size) {\n\t\t\t\tvram.sb += size;\n\t\t}\n\t\tread(offset = 0, size = this.byteSize, data = null, immediate = false) {\n\t\t\t\treturn this.impl.read(this.device, offset, size, data, immediate);\n\t\t}\n\t\twrite(bufferOffset = 0, data, dataOffset = 0, size) {\n\t\t\t\tthis.impl.write(this.device, bufferOffset, data, dataOffset, size);\n\t\t}\n\t\tclear(offset = 0, size = this.byteSize) {\n\t\t\t\tthis.impl.clear(this.device, offset, size);\n\t\t}\n}\n\nconst _uniqueLocations = new Map();\nconst _indirectEntryByteSize = 5 * 4;\nclass WebgpuGraphicsDevice extends GraphicsDevice {\n\t\tconstructor(canvas, options = {}){\n\t\t\t\tsuper(canvas, options), this.renderPipeline = new WebgpuRenderPipeline(this), this.computePipeline = new WebgpuComputePipeline(this), this._indirectDrawBuffer = null, this._indirectDrawBufferCount = 0, this._indirectDrawNextIndex = 0, this.pipeline = null, this.bindGroupFormats = [], this.commandEncoder = null, this.commandBuffers = [], this.glslang = null, this.twgsl = null;\n\t\t\t\toptions = this.initOptions;\n\t\t\t\toptions.alpha = options.alpha ?? true;\n\t\t\t\tthis.backBufferAntialias = options.antialias ?? false;\n\t\t\t\tthis.isWebGPU = true;\n\t\t\t\tthis._deviceType = DEVICETYPE_WEBGPU;\n\t\t\t\tthis.scope.resolve(UNUSED_UNIFORM_NAME).setValue(0);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.clearRenderer.destroy();\n\t\t\t\tthis.clearRenderer = null;\n\t\t\t\tthis.mipmapRenderer.destroy();\n\t\t\t\tthis.mipmapRenderer = null;\n\t\t\t\tthis.resolver.destroy();\n\t\t\t\tthis.resolver = null;\n\t\t\t\tsuper.destroy();\n\t\t}\n\t\tinitDeviceCaps() {\n\t\t\t\tconst limits = this.wgpu?.limits;\n\t\t\t\tthis.limits = limits;\n\t\t\t\tthis.precision = 'highp';\n\t\t\t\tthis.maxPrecision = 'highp';\n\t\t\t\tthis.maxSamples = 4;\n\t\t\t\tthis.maxTextures = 16;\n\t\t\t\tthis.maxTextureSize = limits.maxTextureDimension2D;\n\t\t\t\tthis.maxCubeMapSize = limits.maxTextureDimension2D;\n\t\t\t\tthis.maxVolumeSize = limits.maxTextureDimension3D;\n\t\t\t\tthis.maxColorAttachments = limits.maxColorAttachments;\n\t\t\t\tthis.maxPixelRatio = 1;\n\t\t\t\tthis.maxAnisotropy = 16;\n\t\t\t\tthis.fragmentUniformsCount = limits.maxUniformBufferBindingSize / 16;\n\t\t\t\tthis.vertexUniformsCount = limits.maxUniformBufferBindingSize / 16;\n\t\t\t\tthis.supportsUniformBuffers = true;\n\t\t\t\tthis.supportsAreaLights = true;\n\t\t\t\tthis.supportsGpuParticles = true;\n\t\t\t\tthis.supportsCompute = true;\n\t\t\t\tthis.textureFloatRenderable = true;\n\t\t\t\tthis.textureHalfFloatRenderable = true;\n\t\t\t\tthis.supportsImageBitmap = true;\n\t\t\t\tthis.samples = this.backBufferAntialias ? 4 : 1;\n\t\t\t\tconst wgslFeatures = window.navigator.gpu.wgslLanguageFeatures;\n\t\t\t\tthis.supportsStorageTextureRead = wgslFeatures?.has('readonly_and_readwrite_storage_textures');\n\t\t\t\tthis.initCapsDefines();\n\t\t}\n\t\tasync initWebGpu(glslangUrl, twgslUrl) {\n\t\t\t\tif (!window.navigator.gpu) {\n\t\t\t\t\t\tthrow new Error('Unable to retrieve GPU. Ensure you are using a browser that supports WebGPU rendering.');\n\t\t\t\t}\n\t\t\t\tif (glslangUrl && twgslUrl) {\n\t\t\t\t\t\tconst buildUrl = (srcPath)=>{\n\t\t\t\t\t\t\t\treturn new URL(srcPath, window.location.href).toString();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst results = await Promise.all([\n\t\t\t\t\t\t\t\timport(/* @vite-ignore */ /* webpackIgnore: true */ `${buildUrl(twgslUrl)}`).then((module)=>twgsl(twgslUrl.replace('.js', '.wasm'))),\n\t\t\t\t\t\t\t\timport(/* @vite-ignore */ /* webpackIgnore: true */ `${buildUrl(glslangUrl)}`).then((module)=>module.default())\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis.twgsl = results[0];\n\t\t\t\t\t\tthis.glslang = results[1];\n\t\t\t\t}\n\t\t\t\treturn this.createDevice();\n\t\t}\n\t\tasync createDevice() {\n\t\t\t\tconst adapterOptions = {\n\t\t\t\t\t\tpowerPreference: this.initOptions.powerPreference !== 'default' ? this.initOptions.powerPreference : undefined,\n\t\t\t\t\t\txrCompatible: this.initOptions.xrCompatible\n\t\t\t\t};\n\t\t\t\tthis.gpuAdapter = await window.navigator.gpu.requestAdapter(adapterOptions);\n\t\t\t\tconst requiredFeatures = [];\n\t\t\t\tconst requireFeature = (feature)=>{\n\t\t\t\t\t\tconst supported = this.gpuAdapter.features.has(feature);\n\t\t\t\t\t\tif (supported) {\n\t\t\t\t\t\t\t\trequiredFeatures.push(feature);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn supported;\n\t\t\t\t};\n\t\t\t\tthis.textureFloatFilterable = requireFeature('float32-filterable');\n\t\t\t\tthis.textureFloatBlendable = requireFeature('float32-blendable');\n\t\t\t\tthis.extCompressedTextureS3TC = requireFeature('texture-compression-bc');\n\t\t\t\tthis.extCompressedTextureS3TCSliced3D = requireFeature('texture-compression-bc-sliced-3d');\n\t\t\t\tthis.extCompressedTextureETC = requireFeature('texture-compression-etc2');\n\t\t\t\tthis.extCompressedTextureASTC = requireFeature('texture-compression-astc');\n\t\t\t\tthis.extCompressedTextureASTCSliced3D = requireFeature('texture-compression-astc-sliced-3d');\n\t\t\t\tthis.supportsTimestampQuery = requireFeature('timestamp-query');\n\t\t\t\tthis.supportsDepthClip = requireFeature('depth-clip-control');\n\t\t\t\tthis.supportsDepth32Stencil = requireFeature('depth32float-stencil8');\n\t\t\t\tthis.supportsIndirectFirstInstance = requireFeature('indirect-first-instance');\n\t\t\t\tthis.supportsShaderF16 = requireFeature('shader-f16');\n\t\t\t\tthis.supportsStorageRGBA8 = requireFeature('bgra8unorm-storage');\n\t\t\t\tthis.textureRG11B10Renderable = requireFeature('rg11b10ufloat-renderable');\n\t\t\t\tthis.supportsClipDistances = requireFeature('clip-distances');\n\t\t\t\tconst adapterLimits = this.gpuAdapter?.limits;\n\t\t\t\tconst requiredLimits = {};\n\t\t\t\tif (adapterLimits) {\n\t\t\t\t\t\tfor(const limitName in adapterLimits){\n\t\t\t\t\t\t\t\tif (limitName === 'minSubgroupSize' || limitName === 'maxSubgroupSize') {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\trequiredLimits[limitName] = adapterLimits[limitName];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst deviceDescr = {\n\t\t\t\t\t\trequiredFeatures,\n\t\t\t\t\t\trequiredLimits,\n\t\t\t\t\t\tdefaultQueue: {\n\t\t\t\t\t\t\t\tlabel: 'Default Queue'\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.wgpu = await this.gpuAdapter.requestDevice(deviceDescr);\n\t\t\t\tthis.wgpu.lost?.then(this.handleDeviceLost.bind(this));\n\t\t\t\tthis.initDeviceCaps();\n\t\t\t\tthis.gpuContext = this.canvas.getContext('webgpu');\n\t\t\t\tlet canvasToneMapping = 'standard';\n\t\t\t\tlet preferredCanvasFormat = window.navigator.gpu.getPreferredCanvasFormat();\n\t\t\t\tconst displayFormat = this.initOptions.displayFormat;\n\t\t\t\tthis.backBufferFormat = preferredCanvasFormat === 'rgba8unorm' ? displayFormat === DISPLAYFORMAT_LDR_SRGB ? PIXELFORMAT_SRGBA8 : PIXELFORMAT_RGBA8 : displayFormat === DISPLAYFORMAT_LDR_SRGB ? PIXELFORMAT_SBGRA8 : PIXELFORMAT_BGRA8;\n\t\t\t\tthis.backBufferViewFormat = displayFormat === DISPLAYFORMAT_LDR_SRGB ? `${preferredCanvasFormat}-srgb` : preferredCanvasFormat;\n\t\t\t\tif (displayFormat === DISPLAYFORMAT_HDR && this.textureFloatFilterable) {\n\t\t\t\t\t\tconst hdrMediaQuery = window.matchMedia('(dynamic-range: high)');\n\t\t\t\t\t\tif (hdrMediaQuery?.matches) {\n\t\t\t\t\t\t\t\tthis.backBufferFormat = PIXELFORMAT_RGBA16F;\n\t\t\t\t\t\t\t\tthis.backBufferViewFormat = 'rgba16float';\n\t\t\t\t\t\t\t\tpreferredCanvasFormat = 'rgba16float';\n\t\t\t\t\t\t\t\tthis.isHdr = true;\n\t\t\t\t\t\t\t\tcanvasToneMapping = 'extended';\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.canvasConfig = {\n\t\t\t\t\t\tdevice: this.wgpu,\n\t\t\t\t\t\tcolorSpace: 'srgb',\n\t\t\t\t\t\talphaMode: this.initOptions.alpha ? 'premultiplied' : 'opaque',\n\t\t\t\t\t\tformat: preferredCanvasFormat,\n\t\t\t\t\t\ttoneMapping: {\n\t\t\t\t\t\t\t\tmode: canvasToneMapping\n\t\t\t\t\t\t},\n\t\t\t\t\t\tusage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,\n\t\t\t\t\t\tviewFormats: displayFormat === DISPLAYFORMAT_LDR_SRGB ? [\n\t\t\t\t\t\t\t\tthis.backBufferViewFormat\n\t\t\t\t\t\t] : []\n\t\t\t\t};\n\t\t\t\tthis.gpuContext?.configure(this.canvasConfig);\n\t\t\t\tthis.createBackbuffer();\n\t\t\t\tthis.clearRenderer = new WebgpuClearRenderer(this);\n\t\t\t\tthis.mipmapRenderer = new WebgpuMipmapRenderer(this);\n\t\t\t\tthis.resolver = new WebgpuResolver(this);\n\t\t\t\tthis.postInit();\n\t\t\t\treturn this;\n\t\t}\n\t\tasync handleDeviceLost(info) {\n\t\t\t\tif (info.reason !== 'destroyed') {\n\t\t\t\t\t\tsuper.loseContext();\n\t\t\t\t\t\tawait this.createDevice();\n\t\t\t\t\t\tsuper.restoreContext();\n\t\t\t\t}\n\t\t}\n\t\tpostInit() {\n\t\t\t\tsuper.postInit();\n\t\t\t\tthis.initializeRenderState();\n\t\t\t\tthis.setupPassEncoderDefaults();\n\t\t\t\tthis.gpuProfiler = new WebgpuGpuProfiler(this);\n\t\t\t\tthis.dynamicBuffers = new WebgpuDynamicBuffers(this, 100 * 1024, this.limits.minUniformBufferOffsetAlignment);\n\t\t\t\tthis.emptyBindGroup = new BindGroup(this, new BindGroupFormat(this, []));\n\t\t\t\tthis.emptyBindGroup.update();\n\t\t}\n\t\tcreateBackbuffer() {\n\t\t\t\tthis.supportsStencil = this.initOptions.stencil;\n\t\t\t\tthis.backBuffer = new RenderTarget({\n\t\t\t\t\t\tname: 'WebgpuFramebuffer',\n\t\t\t\t\t\tgraphicsDevice: this,\n\t\t\t\t\t\tdepth: this.initOptions.depth,\n\t\t\t\t\t\tstencil: this.supportsStencil,\n\t\t\t\t\t\tsamples: this.samples\n\t\t\t\t});\n\t\t\t\tthis.backBuffer.impl.isBackbuffer = true;\n\t\t}\n\t\tframeStart() {\n\t\t\t\tsuper.frameStart();\n\t\t\t\tthis.gpuProfiler.frameStart();\n\t\t\t\tthis.submit();\n\t\t\t\tconst outColorBuffer = this.gpuContext?.getCurrentTexture?.() ?? this.externalBackbuffer?.impl.gpuTexture;\n\t\t\t\tif (this.backBufferSize.x !== outColorBuffer.width || this.backBufferSize.y !== outColorBuffer.height) {\n\t\t\t\t\t\tthis.backBufferSize.set(outColorBuffer.width, outColorBuffer.height);\n\t\t\t\t\t\tthis.backBuffer.destroy();\n\t\t\t\t\t\tthis.backBuffer = null;\n\t\t\t\t\t\tthis.createBackbuffer();\n\t\t\t\t}\n\t\t\t\tconst rt = this.backBuffer;\n\t\t\t\tconst wrt = rt.impl;\n\t\t\t\twrt.setColorAttachment(0, undefined, this.backBufferViewFormat);\n\t\t\t\tthis.initRenderTarget(rt);\n\t\t\t\twrt.assignColorTexture(this, outColorBuffer);\n\t\t}\n\t\tframeEnd() {\n\t\t\t\tsuper.frameEnd();\n\t\t\t\tthis.gpuProfiler.frameEnd();\n\t\t\t\tthis.submit();\n\t\t\t\tif (!this.contextLost) {\n\t\t\t\t\t\tthis.gpuProfiler.request();\n\t\t\t\t}\n\t\t\t\tthis._indirectDrawNextIndex = 0;\n\t\t}\n\t\tcreateBufferImpl(usageFlags) {\n\t\t\t\treturn new WebgpuBuffer(usageFlags);\n\t\t}\n\t\tcreateUniformBufferImpl(uniformBuffer) {\n\t\t\t\treturn new WebgpuUniformBuffer(uniformBuffer);\n\t\t}\n\t\tcreateVertexBufferImpl(vertexBuffer, format, options) {\n\t\t\t\treturn new WebgpuVertexBuffer(vertexBuffer, format, options);\n\t\t}\n\t\tcreateIndexBufferImpl(indexBuffer, options) {\n\t\t\t\treturn new WebgpuIndexBuffer(indexBuffer, options);\n\t\t}\n\t\tcreateShaderImpl(shader) {\n\t\t\t\treturn new WebgpuShader(shader);\n\t\t}\n\t\tcreateTextureImpl(texture) {\n\t\t\t\treturn new WebgpuTexture(texture);\n\t\t}\n\t\tcreateRenderTargetImpl(renderTarget) {\n\t\t\t\treturn new WebgpuRenderTarget(renderTarget);\n\t\t}\n\t\tcreateBindGroupFormatImpl(bindGroupFormat) {\n\t\t\t\treturn new WebgpuBindGroupFormat(bindGroupFormat);\n\t\t}\n\t\tcreateBindGroupImpl(bindGroup) {\n\t\t\t\treturn new WebgpuBindGroup();\n\t\t}\n\t\tcreateComputeImpl(compute) {\n\t\t\t\treturn new WebgpuCompute(compute);\n\t\t}\n\t\tget indirectDrawBuffer() {\n\t\t\t\tthis.allocateIndirectDrawBuffer();\n\t\t\t\treturn this._indirectDrawBuffer;\n\t\t}\n\t\tallocateIndirectDrawBuffer() {\n\t\t\t\tif (this._indirectDrawNextIndex === 0 && this._indirectDrawBufferCount < this.maxIndirectDrawCount) {\n\t\t\t\t\t\tthis._indirectDrawBuffer?.destroy();\n\t\t\t\t\t\tthis._indirectDrawBuffer = null;\n\t\t\t\t}\n\t\t\t\tif (this._indirectDrawBuffer === null) {\n\t\t\t\t\t\tthis._indirectDrawBuffer = new StorageBuffer(this, this.maxIndirectDrawCount * 4, BUFFERUSAGE_INDIRECT | BUFFERUSAGE_COPY_DST);\n\t\t\t\t\t\tthis._indirectDrawBufferCount = this.maxIndirectDrawCount;\n\t\t\t\t}\n\t\t}\n\t\tgetIndirectDrawSlot() {\n\t\t\t\tthis.allocateIndirectDrawBuffer();\n\t\t\t\tconst slot = this._indirectDrawNextIndex;\n\t\t\t\tthis._indirectDrawNextIndex++;\n\t\t\t\treturn slot;\n\t\t}\n\t\tsetBindGroup(index, bindGroup, offsets) {\n\t\t\t\tif (this.passEncoder) {\n\t\t\t\t\t\tthis.passEncoder.setBindGroup(index, bindGroup.impl.bindGroup, offsets ?? bindGroup.uniformBufferOffsets);\n\t\t\t\t\t\tthis.bindGroupFormats[index] = bindGroup.format.impl;\n\t\t\t\t}\n\t\t}\n\t\tsubmitVertexBuffer(vertexBuffer, slot) {\n\t\t\t\tconst format = vertexBuffer.format;\n\t\t\t\tconst { interleaved, elements } = format;\n\t\t\t\tconst elementCount = elements.length;\n\t\t\t\tconst vbBuffer = vertexBuffer.impl.buffer;\n\t\t\t\tif (interleaved) {\n\t\t\t\t\t\tthis.passEncoder.setVertexBuffer(slot, vbBuffer);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < elementCount; i++){\n\t\t\t\t\t\tthis.passEncoder.setVertexBuffer(slot + i, vbBuffer, elements[i].offset);\n\t\t\t\t}\n\t\t\t\treturn elementCount;\n\t\t}\n\t\tvalidateVBLocations(vb0, vb1) {\n\t\t\t\tconst validateVB = (vb)=>{\n\t\t\t\t\t\tconst { elements } = vb.format;\n\t\t\t\t\t\tfor(let i = 0; i < elements.length; i++){\n\t\t\t\t\t\t\t\tconst name = elements[i].name;\n\t\t\t\t\t\t\t\tconst location = semanticToLocation[name];\n\t\t\t\t\t\t\t\tif (_uniqueLocations.has(location)) ;\n\t\t\t\t\t\t\t\t_uniqueLocations.set(location, name);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tvalidateVB(vb0);\n\t\t\t\tvalidateVB(vb1);\n\t\t\t\t_uniqueLocations.clear();\n\t\t}\n\t\tdraw(primitive, indexBuffer, numInstances = 1, indirectSlot, first = true, last = true) {\n\t\t\t\tif (this.shader.ready && !this.shader.failed) {\n\t\t\t\t\t\tconst passEncoder = this.passEncoder;\n\t\t\t\t\t\tlet pipeline = this.pipeline;\n\t\t\t\t\t\tconst vb0 = this.vertexBuffers[0];\n\t\t\t\t\t\tconst vb1 = this.vertexBuffers[1];\n\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\t\tif (vb0) {\n\t\t\t\t\t\t\t\t\t\tconst vbSlot = this.submitVertexBuffer(vb0, 0);\n\t\t\t\t\t\t\t\t\t\tif (vb1) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.submitVertexBuffer(vb1, vbSlot);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpipeline = this.renderPipeline.get(primitive, vb0?.format, vb1?.format, indexBuffer?.format, this.shader, this.renderTarget, this.bindGroupFormats, this.blendState, this.depthState, this.cullMode, this.stencilEnabled, this.stencilFront, this.stencilBack);\n\t\t\t\t\t\t\t\tif (this.pipeline !== pipeline) {\n\t\t\t\t\t\t\t\t\t\tthis.pipeline = pipeline;\n\t\t\t\t\t\t\t\t\t\tpassEncoder.setPipeline(pipeline);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (indexBuffer) {\n\t\t\t\t\t\t\t\tpassEncoder.setIndexBuffer(indexBuffer.impl.buffer, indexBuffer.impl.format);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (indirectSlot !== undefined) {\n\t\t\t\t\t\t\t\tconst indirectOffset = indirectSlot * _indirectEntryByteSize;\n\t\t\t\t\t\t\t\tconst indirectBuffer = this.indirectDrawBuffer.impl.buffer;\n\t\t\t\t\t\t\t\tif (indexBuffer) {\n\t\t\t\t\t\t\t\t\t\tpassEncoder.drawIndexedIndirect(indirectBuffer, indirectOffset);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tpassEncoder.drawIndirect(indirectBuffer, indirectOffset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (indexBuffer) {\n\t\t\t\t\t\t\t\t\t\tpassEncoder.drawIndexed(primitive.count, numInstances, primitive.base, primitive.baseVertex ?? 0, 0);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tpassEncoder.draw(primitive.count, numInstances, primitive.base, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (last) {\n\t\t\t\t\t\tthis.clearVertexBuffer();\n\t\t\t\t\t\tthis.pipeline = null;\n\t\t\t\t}\n\t\t}\n\t\tsetShader(shader, asyncCompile = false) {\n\t\t\t\tif (shader !== this.shader) {\n\t\t\t\t\t\tthis.shader = shader;\n\t\t\t\t}\n\t\t}\n\t\tsetBlendState(blendState) {\n\t\t\t\tthis.blendState.copy(blendState);\n\t\t}\n\t\tsetDepthState(depthState) {\n\t\t\t\tthis.depthState.copy(depthState);\n\t\t}\n\t\tsetStencilState(stencilFront, stencilBack) {\n\t\t\t\tif (stencilFront || stencilBack) {\n\t\t\t\t\t\tthis.stencilEnabled = true;\n\t\t\t\t\t\tthis.stencilFront.copy(stencilFront ?? StencilParameters.DEFAULT);\n\t\t\t\t\t\tthis.stencilBack.copy(stencilBack ?? StencilParameters.DEFAULT);\n\t\t\t\t\t\tconst ref = this.stencilFront.ref;\n\t\t\t\t\t\tif (this.stencilRef !== ref) {\n\t\t\t\t\t\t\t\tthis.stencilRef = ref;\n\t\t\t\t\t\t\t\tthis.passEncoder.setStencilReference(ref);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.stencilEnabled = false;\n\t\t\t\t}\n\t\t}\n\t\tsetBlendColor(r, g, b, a) {\n\t\t\t\tconst c = this.blendColor;\n\t\t\t\tif (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {\n\t\t\t\t\t\tc.set(r, g, b, a);\n\t\t\t\t\t\tthis.passEncoder.setBlendConstant(c);\n\t\t\t\t}\n\t\t}\n\t\tsetCullMode(cullMode) {\n\t\t\t\tthis.cullMode = cullMode;\n\t\t}\n\t\tsetAlphaToCoverage(state) {}\n\t\tinitializeContextCaches() {\n\t\t\t\tsuper.initializeContextCaches();\n\t\t}\n\t\tsetupPassEncoderDefaults() {\n\t\t\t\tthis.pipeline = null;\n\t\t\t\tthis.stencilRef = 0;\n\t\t\t\tthis.blendColor.set(0, 0, 0, 0);\n\t\t}\n\t\t_uploadDirtyTextures() {\n\t\t\t\tthis.textures.forEach((texture)=>{\n\t\t\t\t\t\tif (texture._needsUpload || texture._needsMipmaps) {\n\t\t\t\t\t\t\t\ttexture.upload();\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tsetupTimeStampWrites(passDesc, name) {\n\t\t\t\tif (this.gpuProfiler._enabled) {\n\t\t\t\t\t\tif (this.gpuProfiler.timestampQueriesSet) {\n\t\t\t\t\t\t\t\tconst slot = this.gpuProfiler.getSlot(name);\n\t\t\t\t\t\t\t\tpassDesc = passDesc ?? {};\n\t\t\t\t\t\t\t\tpassDesc.timestampWrites = {\n\t\t\t\t\t\t\t\t\t\tquerySet: this.gpuProfiler.timestampQueriesSet.querySet,\n\t\t\t\t\t\t\t\t\t\tbeginningOfPassWriteIndex: slot * 2,\n\t\t\t\t\t\t\t\t\t\tendOfPassWriteIndex: slot * 2 + 1\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn passDesc;\n\t\t}\n\t\tstartRenderPass(renderPass) {\n\t\t\t\tthis._uploadDirtyTextures();\n\t\t\t\tconst rt = renderPass.renderTarget || this.backBuffer;\n\t\t\t\tthis.renderTarget = rt;\n\t\t\t\tconst wrt = rt.impl;\n\t\t\t\tif (rt !== this.backBuffer) {\n\t\t\t\t\t\tthis.initRenderTarget(rt);\n\t\t\t\t}\n\t\t\t\twrt.setupForRenderPass(renderPass, rt);\n\t\t\t\tconst renderPassDesc = wrt.renderPassDescriptor;\n\t\t\t\tthis.setupTimeStampWrites(renderPassDesc, renderPass.name);\n\t\t\t\tconst commandEncoder = this.getCommandEncoder();\n\t\t\t\tthis.passEncoder = commandEncoder.beginRenderPass(renderPassDesc);\n\t\t\t\tthis.passEncoder.label = `${renderPass.name}-PassEncoder RT:${rt.name}`;\n\t\t\t\tthis.setupPassEncoderDefaults();\n\t\t\t\tconst { width, height } = rt;\n\t\t\t\tthis.setViewport(0, 0, width, height);\n\t\t\t\tthis.setScissor(0, 0, width, height);\n\t\t\t\tthis.insideRenderPass = true;\n\t\t}\n\t\tendRenderPass(renderPass) {\n\t\t\t\tthis.passEncoder.end();\n\t\t\t\tthis.passEncoder = null;\n\t\t\t\tthis.insideRenderPass = false;\n\t\t\t\tthis.bindGroupFormats.length = 0;\n\t\t\t\tconst target = this.renderTarget;\n\t\t\t\tif (target) {\n\t\t\t\t\t\tif (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) {\n\t\t\t\t\t\t\t\tif (renderPass.samples > 1 && target.autoResolve) {\n\t\t\t\t\t\t\t\t\t\tconst depthAttachment = target.impl.depthAttachment;\n\t\t\t\t\t\t\t\t\t\tconst destTexture = target.depthBuffer.impl.gpuTexture;\n\t\t\t\t\t\t\t\t\t\tif (depthAttachment && destTexture) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.resolver.resolveDepth(this.commandEncoder, depthAttachment.multisampledDepthBuffer, destTexture);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < renderPass.colorArrayOps.length; i++){\n\t\t\t\t\t\tconst colorOps = renderPass.colorArrayOps[i];\n\t\t\t\t\t\tif (colorOps.genMipmaps) {\n\t\t\t\t\t\t\t\tthis.mipmapRenderer.generate(renderPass.renderTarget._colorBuffers[i].impl);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tstartComputePass(name) {\n\t\t\t\tthis.pipeline = null;\n\t\t\t\tconst computePassDesc = this.setupTimeStampWrites(undefined, name);\n\t\t\t\tconst commandEncoder = this.getCommandEncoder();\n\t\t\t\tthis.passEncoder = commandEncoder.beginComputePass(computePassDesc);\n\t\t\t\tthis.insideRenderPass = true;\n\t\t}\n\t\tendComputePass() {\n\t\t\t\tthis.passEncoder.end();\n\t\t\t\tthis.passEncoder = null;\n\t\t\t\tthis.insideRenderPass = false;\n\t\t\t\tthis.bindGroupFormats.length = 0;\n\t\t}\n\t\tcomputeDispatch(computes, name = 'Unnamed') {\n\t\t\t\tthis.startComputePass(name);\n\t\t\t\tfor(let i = 0; i < computes.length; i++){\n\t\t\t\t\t\tconst compute = computes[i];\n\t\t\t\t\t\tcompute.applyParameters();\n\t\t\t\t\t\tcompute.impl.updateBindGroup();\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < computes.length; i++){\n\t\t\t\t\t\tconst compute = computes[i];\n\t\t\t\t\t\tcompute.impl.dispatch(compute.countX, compute.countY, compute.countZ);\n\t\t\t\t}\n\t\t\t\tthis.endComputePass();\n\t\t}\n\t\tgetCommandEncoder() {\n\t\t\t\tlet commandEncoder = this.commandEncoder;\n\t\t\t\tif (!commandEncoder) {\n\t\t\t\t\t\tcommandEncoder = this.wgpu.createCommandEncoder();\n\t\t\t\t\t\tthis.commandEncoder = commandEncoder;\n\t\t\t\t}\n\t\t\t\treturn commandEncoder;\n\t\t}\n\t\tendCommandEncoder() {\n\t\t\t\tconst { commandEncoder } = this;\n\t\t\t\tif (commandEncoder) {\n\t\t\t\t\t\tconst cb = commandEncoder.finish();\n\t\t\t\t\t\tthis.addCommandBuffer(cb);\n\t\t\t\t\t\tthis.commandEncoder = null;\n\t\t\t\t}\n\t\t}\n\t\taddCommandBuffer(commandBuffer, front = false) {\n\t\t\t\tif (front) {\n\t\t\t\t\t\tthis.commandBuffers.unshift(commandBuffer);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.commandBuffers.push(commandBuffer);\n\t\t\t\t}\n\t\t}\n\t\tsubmit() {\n\t\t\t\tthis.endCommandEncoder();\n\t\t\t\tif (this.commandBuffers.length > 0) {\n\t\t\t\t\t\tthis.dynamicBuffers.submit();\n\t\t\t\t\t\tthis.wgpu.queue.submit(this.commandBuffers);\n\t\t\t\t\t\tthis.commandBuffers.length = 0;\n\t\t\t\t\t\tthis.dynamicBuffers.onCommandBuffersSubmitted();\n\t\t\t\t}\n\t\t}\n\t\tclear(options) {\n\t\t\t\tif (options.flags) {\n\t\t\t\t\t\tthis.clearRenderer.clear(this, this.renderTarget, options, this.defaultClearOptions);\n\t\t\t\t}\n\t\t}\n\t\tsetViewport(x, y, w, h) {\n\t\t\t\tif (this.passEncoder) {\n\t\t\t\t\t\tif (!this.renderTarget.flipY) {\n\t\t\t\t\t\t\t\ty = this.renderTarget.height - y - h;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.vx = x;\n\t\t\t\t\t\tthis.vy = y;\n\t\t\t\t\t\tthis.vw = w;\n\t\t\t\t\t\tthis.vh = h;\n\t\t\t\t\t\tthis.passEncoder.setViewport(x, y, w, h, 0, 1);\n\t\t\t\t}\n\t\t}\n\t\tsetScissor(x, y, w, h) {\n\t\t\t\tif (this.passEncoder) {\n\t\t\t\t\t\tif (!this.renderTarget.flipY) {\n\t\t\t\t\t\t\t\ty = this.renderTarget.height - y - h;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.sx = x;\n\t\t\t\t\t\tthis.sy = y;\n\t\t\t\t\t\tthis.sw = w;\n\t\t\t\t\t\tthis.sh = h;\n\t\t\t\t\t\tthis.passEncoder.setScissorRect(x, y, w, h);\n\t\t\t\t}\n\t\t}\n\t\tclearStorageBuffer(storageBuffer, offset = 0, size = storageBuffer.byteSize) {\n\t\t\t\tconst commandEncoder = this.getCommandEncoder();\n\t\t\t\tcommandEncoder.clearBuffer(storageBuffer.buffer, offset, size);\n\t\t}\n\t\treadStorageBuffer(storageBuffer, offset = 0, size = storageBuffer.byteSize - offset, data = null, immediate = false) {\n\t\t\t\tconst stagingBuffer = this.createBufferImpl(BUFFERUSAGE_READ | BUFFERUSAGE_COPY_DST);\n\t\t\t\tstagingBuffer.allocate(this, size);\n\t\t\t\tconst destBuffer = stagingBuffer.buffer;\n\t\t\t\tconst commandEncoder = this.getCommandEncoder();\n\t\t\t\tcommandEncoder.copyBufferToBuffer(storageBuffer.buffer, offset, destBuffer, 0, size);\n\t\t\t\treturn this.readBuffer(stagingBuffer, size, data, immediate);\n\t\t}\n\t\treadBuffer(stagingBuffer, size, data = null, immediate = false) {\n\t\t\t\tconst destBuffer = stagingBuffer.buffer;\n\t\t\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\t\t\tconst read = ()=>{\n\t\t\t\t\t\t\t\tdestBuffer?.mapAsync(GPUMapMode.READ).then(()=>{\n\t\t\t\t\t\t\t\t\t\tdata ??= new Uint8Array(size);\n\t\t\t\t\t\t\t\t\t\tconst copySrc = destBuffer.getMappedRange(0, size);\n\t\t\t\t\t\t\t\t\t\tconst srcType = data.constructor;\n\t\t\t\t\t\t\t\t\t\tdata.set(new srcType(copySrc));\n\t\t\t\t\t\t\t\t\t\tdestBuffer.unmap();\n\t\t\t\t\t\t\t\t\t\tstagingBuffer.destroy(this);\n\t\t\t\t\t\t\t\t\t\tresolve(data);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (immediate) {\n\t\t\t\t\t\t\t\tthis.submit();\n\t\t\t\t\t\t\t\tread();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetTimeout(()=>{\n\t\t\t\t\t\t\t\t\t\tread();\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\twriteStorageBuffer(storageBuffer, bufferOffset = 0, data, dataOffset = 0, size) {\n\t\t\t\tthis.wgpu.queue.writeBuffer(storageBuffer.buffer, bufferOffset, data, dataOffset, size);\n\t\t}\n\t\tcopyRenderTarget(source, dest, color, depth) {\n\t\t\t\tconst copySize = {\n\t\t\t\t\t\twidth: source ? source.width : dest.width,\n\t\t\t\t\t\theight: source ? source.height : dest.height,\n\t\t\t\t\t\tdepthOrArrayLayers: 1\n\t\t\t\t};\n\t\t\t\tconst commandEncoder = this.getCommandEncoder();\n\t\t\t\tif (color) {\n\t\t\t\t\t\tconst copySrc = {\n\t\t\t\t\t\t\t\ttexture: source ? source.colorBuffer.impl.gpuTexture : this.backBuffer.impl.assignedColorTexture,\n\t\t\t\t\t\t\t\tmipLevel: 0\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst copyDst = {\n\t\t\t\t\t\t\t\ttexture: dest ? dest.colorBuffer.impl.gpuTexture : this.backBuffer.impl.assignedColorTexture,\n\t\t\t\t\t\t\t\tmipLevel: 0\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcommandEncoder.copyTextureToTexture(copySrc, copyDst, copySize);\n\t\t\t\t}\n\t\t\t\tif (depth) {\n\t\t\t\t\t\tconst sourceRT = source ? source : this.renderTarget;\n\t\t\t\t\t\tconst sourceTexture = sourceRT.impl.depthAttachment.depthTexture;\n\t\t\t\t\t\tif (source.samples > 1) {\n\t\t\t\t\t\t\t\tconst destTexture = dest.colorBuffer.impl.gpuTexture;\n\t\t\t\t\t\t\t\tthis.resolver.resolveDepth(commandEncoder, sourceTexture, destTexture);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst destTexture = dest ? dest.depthBuffer.impl.gpuTexture : this.renderTarget.impl.depthAttachment.depthTexture;\n\t\t\t\t\t\t\t\tconst copySrc = {\n\t\t\t\t\t\t\t\t\t\ttexture: sourceTexture,\n\t\t\t\t\t\t\t\t\t\tmipLevel: 0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tconst copyDst = {\n\t\t\t\t\t\t\t\t\t\ttexture: destTexture,\n\t\t\t\t\t\t\t\t\t\tmipLevel: 0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tcommandEncoder.copyTextureToTexture(copySrc, copyDst, copySize);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n}\n\nclass WebglBuffer {\n\t\tdestroy(device) {\n\t\t\t\tif (this.bufferId) {\n\t\t\t\t\t\tdevice.gl.deleteBuffer(this.bufferId);\n\t\t\t\t\t\tthis.bufferId = null;\n\t\t\t\t}\n\t\t}\n\t\tget initialized() {\n\t\t\t\treturn !!this.bufferId;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.bufferId = null;\n\t\t}\n\t\tunlock(device, usage, target, storage) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (!this.bufferId) {\n\t\t\t\t\t\tlet glUsage;\n\t\t\t\t\t\tswitch(usage){\n\t\t\t\t\t\t\t\tcase BUFFER_STATIC:\n\t\t\t\t\t\t\t\t\t\tglUsage = gl.STATIC_DRAW;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase BUFFER_DYNAMIC:\n\t\t\t\t\t\t\t\t\t\tglUsage = gl.DYNAMIC_DRAW;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase BUFFER_STREAM:\n\t\t\t\t\t\t\t\t\t\tglUsage = gl.STREAM_DRAW;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase BUFFER_GPUDYNAMIC:\n\t\t\t\t\t\t\t\t\t\tglUsage = gl.DYNAMIC_COPY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.bufferId = gl.createBuffer();\n\t\t\t\t\t\tgl.bindBuffer(target, this.bufferId);\n\t\t\t\t\t\tgl.bufferData(target, storage, glUsage);\n\t\t\t\t} else {\n\t\t\t\t\t\tgl.bindBuffer(target, this.bufferId);\n\t\t\t\t\t\tgl.bufferSubData(target, 0, storage);\n\t\t\t\t}\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.bufferId = null;\n\t\t}\n}\n\nclass WebglVertexBuffer extends WebglBuffer {\n\t\tdestroy(device) {\n\t\t\t\tsuper.destroy(device);\n\t\t\t\tdevice.unbindVertexArray();\n\t\t}\n\t\tloseContext() {\n\t\t\t\tsuper.loseContext();\n\t\t\t\tthis.vao = null;\n\t\t}\n\t\tunlock(vertexBuffer) {\n\t\t\t\tconst device = vertexBuffer.device;\n\t\t\t\tsuper.unlock(device, vertexBuffer.usage, device.gl.ARRAY_BUFFER, vertexBuffer.storage);\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this.vao = null;\n\t\t}\n}\n\nclass WebglIndexBuffer extends WebglBuffer {\n\t\tconstructor(indexBuffer){\n\t\t\t\tsuper();\n\t\t\t\tconst gl = indexBuffer.device.gl;\n\t\t\t\tconst format = indexBuffer.format;\n\t\t\t\tif (format === INDEXFORMAT_UINT8) {\n\t\t\t\t\t\tthis.glFormat = gl.UNSIGNED_BYTE;\n\t\t\t\t} else if (format === INDEXFORMAT_UINT16) {\n\t\t\t\t\t\tthis.glFormat = gl.UNSIGNED_SHORT;\n\t\t\t\t} else if (format === INDEXFORMAT_UINT32) {\n\t\t\t\t\t\tthis.glFormat = gl.UNSIGNED_INT;\n\t\t\t\t}\n\t\t}\n\t\tunlock(indexBuffer) {\n\t\t\t\tconst device = indexBuffer.device;\n\t\t\t\tsuper.unlock(device, indexBuffer.usage, device.gl.ELEMENT_ARRAY_BUFFER, indexBuffer.storage);\n\t\t}\n}\n\nclass WebglShaderInput {\n\t\tconstructor(graphicsDevice, name, type, locationId){\n\t\t\t\tthis.locationId = locationId;\n\t\t\t\tthis.scopeId = graphicsDevice.scope.resolve(name);\n\t\t\t\tthis.version = new Version();\n\t\t\t\tif (name.substring(name.length - 3) === '[0]') {\n\t\t\t\t\t\tswitch(type){\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_FLOAT:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_FLOATARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_INT:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_INTARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UINT:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_UINTARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BOOL:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_BOOLARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_VEC2:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_VEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_IVEC2:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_IVEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UVEC2:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_UVEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BVEC2:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_BVEC2ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_VEC3:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_VEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_IVEC3:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_IVEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UVEC3:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_UVEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BVEC3:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_BVEC3ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_VEC4:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_VEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_IVEC4:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_IVEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_UVEC4:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_UVEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase UNIFORMTYPE_BVEC4:\n\t\t\t\t\t\t\t\t\t\ttype = UNIFORMTYPE_BVEC4ARRAY;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.dataType = type;\n\t\t\t\tthis.value = [\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull\n\t\t\t\t];\n\t\t\t\tthis.array = [];\n\t\t}\n}\n\nconst _vertexShaderBuiltins = new Set([\n\t\t'gl_VertexID',\n\t\t'gl_InstanceID',\n\t\t'gl_DrawID',\n\t\t'gl_BaseVertex',\n\t\t'gl_BaseInstance'\n]);\nclass CompiledShaderCache {\n\t\tdestroy(device) {\n\t\t\t\tthis.map.forEach((shader)=>{\n\t\t\t\t\t\tdevice.gl.deleteShader(shader);\n\t\t\t\t});\n\t\t}\n\t\tloseContext(device) {\n\t\t\t\tthis.map.clear();\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.map = new Map();\n\t\t}\n}\nconst _vertexShaderCache = new DeviceCache();\nconst _fragmentShaderCache = new DeviceCache();\nclass WebglShader {\n\t\tconstructor(shader){\n\t\t\t\tthis.compileDuration = 0;\n\t\t\t\tthis.init();\n\t\t\t\tthis.compile(shader.device, shader);\n\t\t\t\tthis.link(shader.device, shader);\n\t\t\t\tshader.device.shaders.push(shader);\n\t\t}\n\t\tdestroy(shader) {\n\t\t\t\tif (this.glProgram) {\n\t\t\t\t\t\tshader.device.gl.deleteProgram(this.glProgram);\n\t\t\t\t\t\tthis.glProgram = null;\n\t\t\t\t}\n\t\t}\n\t\tinit() {\n\t\t\t\tthis.uniforms = [];\n\t\t\t\tthis.samplers = [];\n\t\t\t\tthis.attributes = [];\n\t\t\t\tthis.glProgram = null;\n\t\t\t\tthis.glVertexShader = null;\n\t\t\t\tthis.glFragmentShader = null;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.init();\n\t\t}\n\t\trestoreContext(device, shader) {\n\t\t\t\tthis.compile(device, shader);\n\t\t\t\tthis.link(device, shader);\n\t\t}\n\t\tcompile(device, shader) {\n\t\t\t\tconst definition = shader.definition;\n\t\t\t\tthis.glVertexShader = this._compileShaderSource(device, definition.vshader, true);\n\t\t\t\tthis.glFragmentShader = this._compileShaderSource(device, definition.fshader, false);\n\t\t}\n\t\tlink(device, shader) {\n\t\t\t\tif (this.glProgram) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (gl.isContextLost()) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst glProgram = gl.createProgram();\n\t\t\t\tthis.glProgram = glProgram;\n\t\t\t\tgl.attachShader(glProgram, this.glVertexShader);\n\t\t\t\tgl.attachShader(glProgram, this.glFragmentShader);\n\t\t\t\tconst definition = shader.definition;\n\t\t\t\tconst attrs = definition.attributes;\n\t\t\t\tif (definition.useTransformFeedback) {\n\t\t\t\t\t\tconst outNames = [];\n\t\t\t\t\t\tfor(const attr in attrs){\n\t\t\t\t\t\t\t\tif (attrs.hasOwnProperty(attr)) {\n\t\t\t\t\t\t\t\t\t\toutNames.push(`out_${attr}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgl.transformFeedbackVaryings(glProgram, outNames, gl.INTERLEAVED_ATTRIBS);\n\t\t\t\t}\n\t\t\t\tfor(const attr in attrs){\n\t\t\t\t\t\tif (attrs.hasOwnProperty(attr)) {\n\t\t\t\t\t\t\t\tconst semantic = attrs[attr];\n\t\t\t\t\t\t\t\tconst loc = semanticToLocation[semantic];\n\t\t\t\t\t\t\t\tgl.bindAttribLocation(glProgram, loc, attr);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tgl.linkProgram(glProgram);\n\t\t}\n\t\t_compileShaderSource(device, src, isVertexShader) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (gl.isContextLost()) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst shaderDeviceCache = isVertexShader ? _vertexShaderCache : _fragmentShaderCache;\n\t\t\t\tconst shaderCache = shaderDeviceCache.get(device, ()=>{\n\t\t\t\t\t\treturn new CompiledShaderCache();\n\t\t\t\t});\n\t\t\t\tlet glShader = shaderCache.map.get(src);\n\t\t\t\tif (!glShader) {\n\t\t\t\t\t\tglShader = gl.createShader(isVertexShader ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);\n\t\t\t\t\t\tgl.shaderSource(glShader, src);\n\t\t\t\t\t\tgl.compileShader(glShader);\n\t\t\t\t\t\tshaderCache.map.set(src, glShader);\n\t\t\t\t}\n\t\t\t\treturn glShader;\n\t\t}\n\t\tfinalize(device, shader) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (gl.isContextLost()) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconst glProgram = this.glProgram;\n\t\t\t\tconst definition = shader.definition;\n\t\t\t\tconst linkStatus = gl.getProgramParameter(glProgram, gl.LINK_STATUS);\n\t\t\t\tif (!linkStatus) {\n\t\t\t\t\t\tif (!this._isCompiled(device, shader, this.glVertexShader, definition.vshader, 'vertex')) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this._isCompiled(device, shader, this.glFragmentShader, definition.fshader, 'fragment')) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst message = `Failed to link shader program. Error: ${gl.getProgramInfoLog(glProgram)}`;\n\t\t\t\t\t\tconsole.error(message);\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst numAttributes = gl.getProgramParameter(glProgram, gl.ACTIVE_ATTRIBUTES);\n\t\t\t\tshader.attributes.clear();\n\t\t\t\tfor(let i = 0; i < numAttributes; i++){\n\t\t\t\t\t\tconst info = gl.getActiveAttrib(glProgram, i);\n\t\t\t\t\t\tconst location = gl.getAttribLocation(glProgram, info.name);\n\t\t\t\t\t\tif (_vertexShaderBuiltins.has(info.name)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (definition.attributes[info.name] === undefined) {\n\t\t\t\t\t\t\t\tconsole.error(`Vertex shader attribute \"${info.name}\" is not mapped to a semantic in shader definition, shader [${shader.label}]`, shader);\n\t\t\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tshader.attributes.set(location, info.name);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst samplerTypes = device._samplerTypes;\n\t\t\t\tconst numUniforms = gl.getProgramParameter(glProgram, gl.ACTIVE_UNIFORMS);\n\t\t\t\tfor(let i = 0; i < numUniforms; i++){\n\t\t\t\t\t\tconst info = gl.getActiveUniform(glProgram, i);\n\t\t\t\t\t\tconst location = gl.getUniformLocation(glProgram, info.name);\n\t\t\t\t\t\tconst shaderInput = new WebglShaderInput(device, info.name, device.pcUniformType[info.type], location);\n\t\t\t\t\t\tif (samplerTypes.has(info.type)) {\n\t\t\t\t\t\t\t\tthis.samplers.push(shaderInput);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.uniforms.push(shaderInput);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tshader.ready = true;\n\t\t\t\treturn true;\n\t\t}\n\t\t_isCompiled(device, shader, glShader, source, shaderType) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (!gl.getShaderParameter(glShader, gl.COMPILE_STATUS)) {\n\t\t\t\t\t\tconst infoLog = gl.getShaderInfoLog(glShader);\n\t\t\t\t\t\tconst [code, error] = this._processError(source, infoLog);\n\t\t\t\t\t\tconst message = `Failed to compile ${shaderType} shader:\\n\\n${infoLog}\\n${code} while rendering ${ void 0}`;\n\t\t\t\t\t\tconsole.error(message);\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tisLinked(device) {\n\t\t\t\tconst { extParallelShaderCompile } = device;\n\t\t\t\tif (extParallelShaderCompile) {\n\t\t\t\t\t\treturn device.gl.getProgramParameter(this.glProgram, extParallelShaderCompile.COMPLETION_STATUS_KHR);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\t_processError(src, infoLog) {\n\t\t\t\tconst error = {};\n\t\t\t\tlet code = '';\n\t\t\t\tif (src) {\n\t\t\t\t\t\tconst lines = src.split('\\n');\n\t\t\t\t\t\tlet from = 0;\n\t\t\t\t\t\tlet to = lines.length;\n\t\t\t\t\t\tif (infoLog && infoLog.startsWith('ERROR:')) {\n\t\t\t\t\t\t\t\tconst match = infoLog.match(/^ERROR:\\s(\\d+):(\\d+):\\s*(.+)/);\n\t\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\t\t\terror.message = match[3];\n\t\t\t\t\t\t\t\t\t\terror.line = parseInt(match[2], 10);\n\t\t\t\t\t\t\t\t\t\tfrom = Math.max(0, error.line - 6);\n\t\t\t\t\t\t\t\t\t\tto = Math.min(lines.length, error.line + 5);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = from; i < to; i++){\n\t\t\t\t\t\t\t\tconst linePrefix = i + 1 === error.line ? '> ' : '  ';\n\t\t\t\t\t\t\t\tcode += `${linePrefix}${i + 1}:\\t${lines[i]}\\n`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\terror.source = src;\n\t\t\t\t}\n\t\t\t\treturn [\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\terror\n\t\t\t\t];\n\t\t}\n}\n\nfunction downsampleImage(image, size) {\n\t\tconst srcW = image.width;\n\t\tconst srcH = image.height;\n\t\tif (srcW > size || srcH > size) {\n\t\t\t\tconst scale = size / Math.max(srcW, srcH);\n\t\t\t\tconst dstW = Math.floor(srcW * scale);\n\t\t\t\tconst dstH = Math.floor(srcH * scale);\n\t\t\t\tconst canvas = document.createElement('canvas');\n\t\t\t\tcanvas.width = dstW;\n\t\t\t\tcanvas.height = dstH;\n\t\t\t\tconst context = canvas.getContext('2d');\n\t\t\t\tcontext.drawImage(image, 0, 0, srcW, srcH, 0, 0, dstW, dstH);\n\t\t\t\treturn canvas;\n\t\t}\n\t\treturn image;\n}\nclass WebglTexture {\n\t\tconstructor(texture){\n\t\t\t\tthis._glTexture = null;\n\t\t\t\tthis.dirtyParameterFlags = 0;\n\t\t\t\tthis.texture = texture;\n\t\t}\n\t\tdestroy(device) {\n\t\t\t\tif (this._glTexture) {\n\t\t\t\t\t\tfor(let i = 0; i < device.textureUnits.length; i++){\n\t\t\t\t\t\t\t\tconst textureUnit = device.textureUnits[i];\n\t\t\t\t\t\t\t\tfor(let j = 0; j < textureUnit.length; j++){\n\t\t\t\t\t\t\t\t\t\tif (textureUnit[j] === this._glTexture) {\n\t\t\t\t\t\t\t\t\t\t\t\ttextureUnit[j] = null;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdevice.gl.deleteTexture(this._glTexture);\n\t\t\t\t\t\tthis._glTexture = null;\n\t\t\t\t}\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis._glTexture = null;\n\t\t}\n\t\tpropertyChanged(flag) {\n\t\t\t\tthis.dirtyParameterFlags |= flag;\n\t\t}\n\t\tinitialize(device, texture) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tthis._glTexture = gl.createTexture();\n\t\t\t\tthis._glTarget = texture._cubemap ? gl.TEXTURE_CUBE_MAP : texture._volume ? gl.TEXTURE_3D : texture.array ? gl.TEXTURE_2D_ARRAY : gl.TEXTURE_2D;\n\t\t\t\tswitch(texture._format){\n\t\t\t\t\t\tcase PIXELFORMAT_A8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.ALPHA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.ALPHA;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_L8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.LUMINANCE;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.LUMINANCE;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_LA8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.LUMINANCE_ALPHA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.LUMINANCE_ALPHA;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R8;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG8;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGB565:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGB565;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_SHORT_5_6_5;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA5551:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGB5_A1;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_SHORT_5_5_5_1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA4:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA4;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_SHORT_4_4_4_4;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGB8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGB8;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA8;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_BGRA8:\n\t\t\t\t\t\tcase PIXELFORMAT_SBGRA8:\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DXT1:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DXT3:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DXT5:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ETC1:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureETC1.COMPRESSED_RGB_ETC1_WEBGL;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_PVRTC_2BPP_RGB_1:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_PVRTC_2BPP_RGBA_1:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_PVRTC_4BPP_RGB_1:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_PVRTC_4BPP_RGBA_1:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTexturePVRTC.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ETC2_RGB:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_RGB8_ETC2;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ETC2_RGBA:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_RGBA8_ETC2_EAC;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ASTC_4x4:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureASTC.COMPRESSED_RGBA_ASTC_4x4_KHR;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ATC_RGB:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureATC.COMPRESSED_RGB_ATC_WEBGL;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ATC_RGBA:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureATC.COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_BC6F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_BC6UF:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_BC7:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_RGBA_BPTC_UNORM_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DXT1_SRGB:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.SRGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureS3TC_SRGB.COMPRESSED_SRGB_S3TC_DXT1_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DXT3_SRGBA:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.SRGB_ALPHA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureS3TC_SRGB.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DXT5_SRGBA:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.SRGB_ALPHA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureS3TC_SRGB.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ETC2_SRGB:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.SRGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_SRGB8_ETC2;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ETC2_SRGBA:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.SRGB_ALPHA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureETC.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_ASTC_4x4_SRGB:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.SRGB_ALPHA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extCompressedTextureASTC.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_BC7_SRGBA:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = device.extTextureCompressionBPTC.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R16F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R16F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.HALF_FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG16F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG16F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.HALF_FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGB16F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGB16F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.HALF_FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA16F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA16F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.HALF_FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGB32F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGB32F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA32F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA32F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R32F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R32F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DEPTH:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.DEPTH_COMPONENT;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.DEPTH_COMPONENT32F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.FLOAT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DEPTH16:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.DEPTH_COMPONENT;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.DEPTH_COMPONENT16;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_SHORT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_DEPTHSTENCIL:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.DEPTH_STENCIL;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.DEPTH24_STENCIL8;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_INT_24_8;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_111110F:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R11F_G11F_B10F;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_INT_10F_11F_11F_REV;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_SRGB8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGB;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.SRGB8;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_SRGBA8:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.SRGB8_ALPHA8;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R8I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R8I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R8U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R8UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R16I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R16I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.SHORT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R16U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R16UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_SHORT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R32I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R32I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.INT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_R32U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RED_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.R32UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_INT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG8I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG8I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG8U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG8UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG16I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG16I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.SHORT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG16U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG16UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_SHORT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG32I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG32I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.INT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RG32U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RG_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RG32UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_INT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA8I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA8I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA8U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA8UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_BYTE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA16I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA16I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.SHORT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA16U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA16UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_SHORT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA32I:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA32I;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.INT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PIXELFORMAT_RGBA32U:\n\t\t\t\t\t\t\t\tthis._glFormat = gl.RGBA_INTEGER;\n\t\t\t\t\t\t\t\tthis._glInternalFormat = gl.RGBA32UI;\n\t\t\t\t\t\t\t\tthis._glPixelType = gl.UNSIGNED_INT;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tthis._glCreated = false;\n\t\t}\n\t\tupload(device, texture) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (!texture._needsUpload && (texture._needsMipmapsUpload && texture._mipmapsUploaded || !texture.pot)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet mipLevel = 0;\n\t\t\t\tlet mipObject;\n\t\t\t\tlet resMult;\n\t\t\t\tconst requiredMipLevels = texture.numLevels;\n\t\t\t\tif (texture.array && !this._glCreated) {\n\t\t\t\t\t\tgl.texStorage3D(gl.TEXTURE_2D_ARRAY, requiredMipLevels, this._glInternalFormat, texture._width, texture._height, texture._arrayLength);\n\t\t\t\t}\n\t\t\t\twhile(texture._levels[mipLevel] || mipLevel === 0){\n\t\t\t\t\t\tif (!texture._needsUpload && mipLevel === 0) {\n\t\t\t\t\t\t\t\tmipLevel++;\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t} else if (mipLevel && (!texture._needsMipmapsUpload || !texture._mipmaps)) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmipObject = texture._levels[mipLevel];\n\t\t\t\t\t\tresMult = 1 / Math.pow(2, mipLevel);\n\t\t\t\t\t\tif (mipLevel === 1 && !texture._compressed && !texture._integerFormat && texture._levels.length < requiredMipLevels) {\n\t\t\t\t\t\t\t\tgl.generateMipmap(this._glTarget);\n\t\t\t\t\t\t\t\ttexture._mipmapsUploaded = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (texture._cubemap) {\n\t\t\t\t\t\t\t\tlet face;\n\t\t\t\t\t\t\t\tif (device._isBrowserInterface(mipObject[0])) {\n\t\t\t\t\t\t\t\t\t\tfor(face = 0; face < 6; face++){\n\t\t\t\t\t\t\t\t\t\t\t\tif (!texture._levelsUpdated[0][face]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tlet src = mipObject[face];\n\t\t\t\t\t\t\t\t\t\t\t\tif (device._isImageBrowserInterface(src)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (src.width > device.maxCubeMapSize || src.height > device.maxCubeMapSize) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsrc = downsampleImage(src, device.maxCubeMapSize);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (mipLevel === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttexture._width = src.width;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttexture._height = src.height;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tdevice.setUnpackFlipY(false);\n\t\t\t\t\t\t\t\t\t\t\t\tdevice.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);\n\t\t\t\t\t\t\t\t\t\t\t\tif (this._glCreated) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, 0, 0, this._glFormat, this._glPixelType, src);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, this._glInternalFormat, this._glFormat, this._glPixelType, src);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tresMult = 1 / Math.pow(2, mipLevel);\n\t\t\t\t\t\t\t\t\t\tfor(face = 0; face < 6; face++){\n\t\t\t\t\t\t\t\t\t\t\t\tif (!texture._levelsUpdated[0][face]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst texData = mipObject[face];\n\t\t\t\t\t\t\t\t\t\t\t\tif (texture._compressed) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this._glCreated && texData) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.compressedTexSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, 0, 0, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), this._glInternalFormat, texData);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.compressedTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, texData);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdevice.setUnpackFlipY(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdevice.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this._glCreated && texData) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, 0, 0, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), this._glFormat, this._glPixelType, texData);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, this._glFormat, this._glPixelType, texData);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (texture._volume) {\n\t\t\t\t\t\t\t\tif (texture._compressed) {\n\t\t\t\t\t\t\t\t\t\tgl.compressedTexImage3D(gl.TEXTURE_3D, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), Math.max(texture._depth * resMult, 1), 0, mipObject);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdevice.setUnpackFlipY(false);\n\t\t\t\t\t\t\t\t\t\tdevice.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);\n\t\t\t\t\t\t\t\t\t\tgl.texImage3D(gl.TEXTURE_3D, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), Math.max(texture._depth * resMult, 1), 0, this._glFormat, this._glPixelType, mipObject);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (texture.array && typeof mipObject === 'object') {\n\t\t\t\t\t\t\t\tif (texture._arrayLength === mipObject.length) {\n\t\t\t\t\t\t\t\t\t\tif (texture._compressed) {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let index = 0; index < texture._arrayLength; index++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.compressedTexSubImage3D(gl.TEXTURE_2D_ARRAY, mipLevel, 0, 0, index, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), 1, this._glInternalFormat, mipObject[index]);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let index = 0; index < texture._arrayLength; index++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.texSubImage3D(gl.TEXTURE_2D_ARRAY, mipLevel, 0, 0, index, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), 1, this._glFormat, this._glPixelType, mipObject[index]);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (device._isBrowserInterface(mipObject)) {\n\t\t\t\t\t\t\t\t\t\tif (device._isImageBrowserInterface(mipObject)) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (mipObject.width > device.maxTextureSize || mipObject.height > device.maxTextureSize) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmipObject = downsampleImage(mipObject, device.maxTextureSize);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (mipLevel === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttexture._width = mipObject.width;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttexture._height = mipObject.height;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst w = mipObject.width || mipObject.videoWidth;\n\t\t\t\t\t\t\t\t\t\tconst h = mipObject.height || mipObject.videoHeight;\n\t\t\t\t\t\t\t\t\t\tdevice.setUnpackFlipY(texture._flipY);\n\t\t\t\t\t\t\t\t\t\tdevice.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);\n\t\t\t\t\t\t\t\t\t\tif (this._glCreated && texture._width === w && texture._height === h && !device._isImageVideoInterface(mipObject)) {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.texSubImage2D(gl.TEXTURE_2D, mipLevel, 0, 0, this._glFormat, this._glPixelType, mipObject);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.texImage2D(gl.TEXTURE_2D, mipLevel, this._glInternalFormat, this._glFormat, this._glPixelType, mipObject);\n\t\t\t\t\t\t\t\t\t\t\t\tif (mipLevel === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttexture._width = w;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttexture._height = h;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tresMult = 1 / Math.pow(2, mipLevel);\n\t\t\t\t\t\t\t\t\t\tif (texture._compressed) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (this._glCreated && mipObject) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.compressedTexSubImage2D(gl.TEXTURE_2D, mipLevel, 0, 0, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), this._glInternalFormat, mipObject);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.compressedTexImage2D(gl.TEXTURE_2D, mipLevel, this._glInternalFormat, Math.max(Math.floor(texture._width * resMult), 1), Math.max(Math.floor(texture._height * resMult), 1), 0, mipObject);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tdevice.setUnpackFlipY(false);\n\t\t\t\t\t\t\t\t\t\t\t\tdevice.setUnpackPremultiplyAlpha(texture._premultiplyAlpha);\n\t\t\t\t\t\t\t\t\t\t\t\tif (this._glCreated && mipObject) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.texSubImage2D(gl.TEXTURE_2D, mipLevel, 0, 0, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), this._glFormat, this._glPixelType, mipObject);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.texImage2D(gl.TEXTURE_2D, mipLevel, this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), 0, this._glFormat, this._glPixelType, mipObject);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (mipLevel === 0) {\n\t\t\t\t\t\t\t\t\t\ttexture._mipmapsUploaded = false;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\ttexture._mipmapsUploaded = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmipLevel++;\n\t\t\t\t}\n\t\t\t\tif (texture._needsUpload) {\n\t\t\t\t\t\tif (texture._cubemap) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\t\t\t\t\ttexture._levelsUpdated[0][i] = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttexture._levelsUpdated[0] = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!texture._compressed && !texture._integerFormat && texture._mipmaps && texture._needsMipmapsUpload && texture._levels.length === 1) {\n\t\t\t\t\t\tgl.generateMipmap(this._glTarget);\n\t\t\t\t\t\ttexture._mipmapsUploaded = true;\n\t\t\t\t}\n\t\t\t\tif (texture._gpuSize) {\n\t\t\t\t\t\ttexture.adjustVramSizeTracking(device._vram, -texture._gpuSize);\n\t\t\t\t}\n\t\t\t\ttexture._gpuSize = texture.gpuSize;\n\t\t\t\ttexture.adjustVramSizeTracking(device._vram, texture._gpuSize);\n\t\t\t\tthis._glCreated = true;\n\t\t}\n\t\tuploadImmediate(device, texture) {\n\t\t\t\tif (texture._needsUpload || texture._needsMipmapsUpload) {\n\t\t\t\t\t\tdevice.setTexture(texture, 0);\n\t\t\t\t\t\ttexture._needsUpload = false;\n\t\t\t\t\t\ttexture._needsMipmapsUpload = false;\n\t\t\t\t}\n\t\t}\n\t\tread(x, y, width, height, options) {\n\t\t\t\tconst texture = this.texture;\n\t\t\t\tconst device = texture.device;\n\t\t\t\treturn device.readTextureAsync(texture, x, y, width, height, options);\n\t\t}\n\t\twrite(x, y, width, height, data) {\n\t\t\t\tconst { texture } = this;\n\t\t\t\tconst { device } = texture;\n\t\t\t\tdevice.setTexture(texture, 0);\n\t\t\t\treturn device.writeTextureAsync(texture, x, y, width, height, data);\n\t\t}\n}\n\nclass FramebufferPair {\n\t\tconstructor(msaaFB, resolveFB){\n\t\t\t\tthis.msaaFB = msaaFB;\n\t\t\t\tthis.resolveFB = resolveFB;\n\t\t}\n\t\tdestroy(gl) {\n\t\t\t\tif (this.msaaFB) {\n\t\t\t\t\t\tgl.deleteRenderbuffer(this.msaaFB);\n\t\t\t\t\t\tthis.msaaFB = null;\n\t\t\t\t}\n\t\t\t\tif (this.resolveFB) {\n\t\t\t\t\t\tgl.deleteRenderbuffer(this.resolveFB);\n\t\t\t\t\t\tthis.resolveFB = null;\n\t\t\t\t}\n\t\t}\n}\nclass WebglRenderTarget {\n\t\tdestroy(device) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tthis._isInitialized = false;\n\t\t\t\tif (this._glFrameBuffer) {\n\t\t\t\t\t\tif (this._glFrameBuffer !== this.suppliedColorFramebuffer) {\n\t\t\t\t\t\t\t\tgl.deleteFramebuffer(this._glFrameBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._glFrameBuffer = null;\n\t\t\t\t}\n\t\t\t\tif (this._glDepthBuffer) {\n\t\t\t\t\t\tgl.deleteRenderbuffer(this._glDepthBuffer);\n\t\t\t\t\t\tthis._glDepthBuffer = null;\n\t\t\t\t}\n\t\t\t\tif (this._glResolveFrameBuffer) {\n\t\t\t\t\t\tif (this._glResolveFrameBuffer !== this.suppliedColorFramebuffer) {\n\t\t\t\t\t\t\t\tgl.deleteFramebuffer(this._glResolveFrameBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._glResolveFrameBuffer = null;\n\t\t\t\t}\n\t\t\t\tthis._glMsaaColorBuffers.forEach((buffer)=>{\n\t\t\t\t\t\tgl.deleteRenderbuffer(buffer);\n\t\t\t\t});\n\t\t\t\tthis._glMsaaColorBuffers.length = 0;\n\t\t\t\tthis.colorMrtFramebuffers?.forEach((framebuffer)=>{\n\t\t\t\t\t\tframebuffer.destroy(gl);\n\t\t\t\t});\n\t\t\t\tthis.colorMrtFramebuffers = null;\n\t\t\t\tif (this._glMsaaDepthBuffer) {\n\t\t\t\t\t\tthis._glMsaaDepthBuffer = null;\n\t\t\t\t\t\tif (this.msaaDepthBufferKey) {\n\t\t\t\t\t\t\t\tgetMultisampledTextureCache(device).release(this.msaaDepthBufferKey);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.suppliedColorFramebuffer = undefined;\n\t\t}\n\t\tget initialized() {\n\t\t\t\treturn this._isInitialized;\n\t\t}\n\t\tinit(device, target) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tthis._isInitialized = true;\n\t\t\t\tconst buffers = [];\n\t\t\t\tif (this.suppliedColorFramebuffer !== undefined) {\n\t\t\t\t\t\tthis._glFrameBuffer = this.suppliedColorFramebuffer;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._glFrameBuffer = gl.createFramebuffer();\n\t\t\t\t\t\tdevice.setFramebuffer(this._glFrameBuffer);\n\t\t\t\t\t\tconst colorBufferCount = target._colorBuffers?.length ?? 0;\n\t\t\t\t\t\tconst attachmentBaseConstant = gl.COLOR_ATTACHMENT0;\n\t\t\t\t\t\tfor(let i = 0; i < colorBufferCount; ++i){\n\t\t\t\t\t\t\t\tconst colorBuffer = target.getColorBuffer(i);\n\t\t\t\t\t\t\t\tif (colorBuffer) {\n\t\t\t\t\t\t\t\t\t\tif (!colorBuffer.impl._glTexture) {\n\t\t\t\t\t\t\t\t\t\t\t\tcolorBuffer._width = Math.min(colorBuffer.width, device.maxRenderBufferSize);\n\t\t\t\t\t\t\t\t\t\t\t\tcolorBuffer._height = Math.min(colorBuffer.height, device.maxRenderBufferSize);\n\t\t\t\t\t\t\t\t\t\t\t\tdevice.setTexture(colorBuffer, 0);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentBaseConstant + i, colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, target.mipLevel);\n\t\t\t\t\t\t\t\t\t\tbuffers.push(attachmentBaseConstant + i);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgl.drawBuffers(buffers);\n\t\t\t\t\t\tconst depthBuffer = target._depthBuffer;\n\t\t\t\t\t\tif (depthBuffer || target._depth) {\n\t\t\t\t\t\t\t\tconst attachmentPoint = target._stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;\n\t\t\t\t\t\t\t\tif (depthBuffer) {\n\t\t\t\t\t\t\t\t\t\tif (!depthBuffer.impl._glTexture) {\n\t\t\t\t\t\t\t\t\t\t\t\tdepthBuffer._width = Math.min(depthBuffer.width, device.maxRenderBufferSize);\n\t\t\t\t\t\t\t\t\t\t\t\tdepthBuffer._height = Math.min(depthBuffer.height, device.maxRenderBufferSize);\n\t\t\t\t\t\t\t\t\t\t\t\tdevice.setTexture(depthBuffer, 0);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, depthBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, target._depthBuffer.impl._glTexture, target.mipLevel);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst willRenderMsaa = target._samples > 1;\n\t\t\t\t\t\t\t\t\t\tif (!willRenderMsaa) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!this._glDepthBuffer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._glDepthBuffer = gl.createRenderbuffer();\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst internalFormat = target._stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT32F;\n\t\t\t\t\t\t\t\t\t\t\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, this._glDepthBuffer);\n\t\t\t\t\t\t\t\t\t\t\t\tgl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, target.width, target.height);\n\t\t\t\t\t\t\t\t\t\t\t\tgl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentPoint, gl.RENDERBUFFER, this._glDepthBuffer);\n\t\t\t\t\t\t\t\t\t\t\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, null);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (target._samples > 1) {\n\t\t\t\t\t\tthis._glResolveFrameBuffer = this._glFrameBuffer;\n\t\t\t\t\t\tthis._glFrameBuffer = gl.createFramebuffer();\n\t\t\t\t\t\tdevice.setFramebuffer(this._glFrameBuffer);\n\t\t\t\t\t\tconst colorBufferCount = target._colorBuffers?.length ?? 0;\n\t\t\t\t\t\tif (this.suppliedColorFramebuffer !== undefined) {\n\t\t\t\t\t\t\t\tconst buffer = gl.createRenderbuffer();\n\t\t\t\t\t\t\t\tthis._glMsaaColorBuffers.push(buffer);\n\t\t\t\t\t\t\t\tconst internalFormat = device.backBufferFormat === PIXELFORMAT_RGBA8 ? gl.RGBA8 : gl.RGB8;\n\t\t\t\t\t\t\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, buffer);\n\t\t\t\t\t\t\t\tgl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, internalFormat, target.width, target.height);\n\t\t\t\t\t\t\t\tgl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, buffer);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < colorBufferCount; ++i){\n\t\t\t\t\t\t\t\t\t\tconst colorBuffer = target.getColorBuffer(i);\n\t\t\t\t\t\t\t\t\t\tif (colorBuffer) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst buffer = gl.createRenderbuffer();\n\t\t\t\t\t\t\t\t\t\t\t\tthis._glMsaaColorBuffers.push(buffer);\n\t\t\t\t\t\t\t\t\t\t\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, buffer);\n\t\t\t\t\t\t\t\t\t\t\t\tgl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, colorBuffer.impl._glInternalFormat, target.width, target.height);\n\t\t\t\t\t\t\t\t\t\t\t\tgl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, buffer);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (target._depth) {\n\t\t\t\t\t\t\t\tconst internalFormat = target._stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT32F;\n\t\t\t\t\t\t\t\tconst attachmentPoint = target._stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;\n\t\t\t\t\t\t\t\tlet key;\n\t\t\t\t\t\t\t\tconst depthBuffer = target._depthBuffer;\n\t\t\t\t\t\t\t\tif (depthBuffer) {\n\t\t\t\t\t\t\t\t\t\tkey = `${depthBuffer.id}:${target.width}:${target.height}:${target._samples}:${internalFormat}:${attachmentPoint}`;\n\t\t\t\t\t\t\t\t\t\tthis._glMsaaDepthBuffer = getMultisampledTextureCache(device).get(key);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!this._glMsaaDepthBuffer) {\n\t\t\t\t\t\t\t\t\t\tthis._glMsaaDepthBuffer = gl.createRenderbuffer();\n\t\t\t\t\t\t\t\t\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, this._glMsaaDepthBuffer);\n\t\t\t\t\t\t\t\t\t\tgl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, internalFormat, target.width, target.height);\n\t\t\t\t\t\t\t\t\t\tthis._glMsaaDepthBuffer.destroy = function() {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.deleteRenderbuffer(this);\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tif (depthBuffer) {\n\t\t\t\t\t\t\t\t\t\t\t\tgetMultisampledTextureCache(device).set(key, this._glMsaaDepthBuffer);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.msaaDepthBufferKey = key;\n\t\t\t\t\t\t\t\tgl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentPoint, gl.RENDERBUFFER, this._glMsaaDepthBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (colorBufferCount > 1) {\n\t\t\t\t\t\t\t\tthis._createMsaaMrtFramebuffers(device, target, colorBufferCount);\n\t\t\t\t\t\t\t\tdevice.setFramebuffer(this._glFrameBuffer);\n\t\t\t\t\t\t\t\tgl.drawBuffers(buffers);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_createMsaaMrtFramebuffers(device, target, colorBufferCount) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tthis.colorMrtFramebuffers = [];\n\t\t\t\tfor(let i = 0; i < colorBufferCount; ++i){\n\t\t\t\t\t\tconst colorBuffer = target.getColorBuffer(i);\n\t\t\t\t\t\tconst srcFramebuffer = gl.createFramebuffer();\n\t\t\t\t\t\tdevice.setFramebuffer(srcFramebuffer);\n\t\t\t\t\t\tconst buffer = this._glMsaaColorBuffers[i];\n\t\t\t\t\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, buffer);\n\t\t\t\t\t\tgl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, colorBuffer.impl._glInternalFormat, target.width, target.height);\n\t\t\t\t\t\tgl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, buffer);\n\t\t\t\t\t\tgl.drawBuffers([\n\t\t\t\t\t\t\t\tgl.COLOR_ATTACHMENT0\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tconst dstFramebuffer = gl.createFramebuffer();\n\t\t\t\t\t\tdevice.setFramebuffer(dstFramebuffer);\n\t\t\t\t\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, 0);\n\t\t\t\t\t\tthis.colorMrtFramebuffers[i] = new FramebufferPair(srcFramebuffer, dstFramebuffer);\n\t\t\t\t}\n\t\t}\n\t\t_checkFbo(device, target, type = '') {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tconst status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n\t\t\t\tswitch(status){\n\t\t\t\t\t\tcase gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase gl.FRAMEBUFFER_UNSUPPORTED:\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis._glFrameBuffer = null;\n\t\t\t\tthis._glDepthBuffer = null;\n\t\t\t\tthis._glResolveFrameBuffer = null;\n\t\t\t\tthis._glMsaaColorBuffers.length = 0;\n\t\t\t\tthis._glMsaaDepthBuffer = null;\n\t\t\t\tthis.msaaDepthBufferKey = undefined;\n\t\t\t\tthis.colorMrtFramebuffers = null;\n\t\t\t\tthis.suppliedColorFramebuffer = undefined;\n\t\t\t\tthis._isInitialized = false;\n\t\t}\n\t\tinternalResolve(device, src, dst, target, mask) {\n\t\t\t\tdevice.setScissor(0, 0, target.width, target.height);\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tgl.bindFramebuffer(gl.READ_FRAMEBUFFER, src);\n\t\t\t\tgl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst);\n\t\t\t\tgl.blitFramebuffer(0, 0, target.width, target.height, 0, 0, target.width, target.height, mask, gl.NEAREST);\n\t\t}\n\t\tresolve(device, target, color, depth) {\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (this.colorMrtFramebuffers) {\n\t\t\t\t\t\tif (color) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < this.colorMrtFramebuffers.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst fbPair = this.colorMrtFramebuffers[i];\n\t\t\t\t\t\t\t\t\t\tthis.internalResolve(device, fbPair.msaaFB, fbPair.resolveFB, target, gl.COLOR_BUFFER_BIT);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (depth) {\n\t\t\t\t\t\t\t\tthis.internalResolve(device, this._glFrameBuffer, this._glResolveFrameBuffer, target, gl.DEPTH_BUFFER_BIT);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.internalResolve(device, this._glFrameBuffer, this._glResolveFrameBuffer, target, (color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0));\n\t\t\t\t}\n\t\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis._glFrameBuffer = null;\n\t\t\t\tthis._glDepthBuffer = null;\n\t\t\t\tthis._glResolveFrameBuffer = null;\n\t\t\t\tthis.colorMrtFramebuffers = null;\n\t\t\t\tthis._glMsaaColorBuffers = [];\n\t\t\t\tthis._glMsaaDepthBuffer = null;\n\t\t\t\tthis._isInitialized = false;\n\t\t}\n}\n\nclass FrameQueriesInfo {\n\t\tdestroy(gl) {\n\t\t\t\tthis.queries.forEach((query)=>gl.deleteQuery(query));\n\t\t\t\tthis.queries = null;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.queries = [];\n\t\t}\n}\nclass WebglGpuProfiler extends GpuProfiler {\n\t\tconstructor(device){\n\t\t\t\tsuper(), this.freeQueries = [], this.frameQueries = [], this.previousFrameQueries = [], this.timings = [];\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.ext = device.extDisjointTimerQuery;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.freeQueries.forEach((query)=>this.device.gl.deleteQuery(query));\n\t\t\t\tthis.frameQueries.forEach((query)=>this.device.gl.deleteQuery(query));\n\t\t\t\tthis.previousFrameQueries.forEach((frameQueriesInfo)=>frameQueriesInfo.destroy(this.device.gl));\n\t\t\t\tthis.freeQueries = null;\n\t\t\t\tthis.frameQueries = null;\n\t\t\t\tthis.previousFrameQueries = null;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tsuper.loseContext();\n\t\t\t\tthis.freeQueries = [];\n\t\t\t\tthis.frameQueries = [];\n\t\t\t\tthis.previousFrameQueries = [];\n\t\t}\n\t\trestoreContext() {\n\t\t\t\tthis.ext = this.device.extDisjointTimerQuery;\n\t\t}\n\t\tgetQuery() {\n\t\t\t\treturn this.freeQueries.pop() ?? this.device.gl.createQuery();\n\t\t}\n\t\tstart(name) {\n\t\t\t\tif (this.ext) {\n\t\t\t\t\t\tconst slot = this.getSlot(name);\n\t\t\t\t\t\tconst query = this.getQuery();\n\t\t\t\t\t\tthis.frameQueries[slot] = query;\n\t\t\t\t\t\tthis.device.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, query);\n\t\t\t\t\t\treturn slot;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\tend(slot) {\n\t\t\t\tif (slot !== undefined) {\n\t\t\t\t\t\tthis.device.gl.endQuery(this.ext.TIME_ELAPSED_EXT);\n\t\t\t\t}\n\t\t}\n\t\tframeStart() {\n\t\t\t\tthis.processEnableRequest();\n\t\t\t\tif (this._enabled) {\n\t\t\t\t\t\tthis.frameGPUMarkerSlot = this.start('GpuFrame');\n\t\t\t\t}\n\t\t}\n\t\tframeEnd() {\n\t\t\t\tif (this._enabled) {\n\t\t\t\t\t\tthis.end(this.frameGPUMarkerSlot);\n\t\t\t\t}\n\t\t}\n\t\trequest() {\n\t\t\t\tif (this._enabled) {\n\t\t\t\t\t\tconst ext = this.ext;\n\t\t\t\t\t\tconst gl = this.device.gl;\n\t\t\t\t\t\tconst renderVersion = this.device.renderVersion;\n\t\t\t\t\t\tconst frameQueries = this.frameQueries;\n\t\t\t\t\t\tif (frameQueries.length > 0) {\n\t\t\t\t\t\t\t\tthis.frameQueries = [];\n\t\t\t\t\t\t\t\tconst frameQueriesInfo = new FrameQueriesInfo();\n\t\t\t\t\t\t\t\tframeQueriesInfo.queries = frameQueries;\n\t\t\t\t\t\t\t\tframeQueriesInfo.renderVersion = renderVersion;\n\t\t\t\t\t\t\t\tthis.previousFrameQueries.push(frameQueriesInfo);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.previousFrameQueries.length > 0) {\n\t\t\t\t\t\t\t\tconst previousQueriesInfo = this.previousFrameQueries[0];\n\t\t\t\t\t\t\t\tconst previousQueries = previousQueriesInfo.queries;\n\t\t\t\t\t\t\t\tconst lastQuery = previousQueries[previousQueries.length - 1];\n\t\t\t\t\t\t\t\tconst available = gl.getQueryParameter(lastQuery, gl.QUERY_RESULT_AVAILABLE);\n\t\t\t\t\t\t\t\tconst disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT);\n\t\t\t\t\t\t\t\tif (available && !disjoint) {\n\t\t\t\t\t\t\t\t\t\tthis.previousFrameQueries.shift();\n\t\t\t\t\t\t\t\t\t\tconst timings = this.timings;\n\t\t\t\t\t\t\t\t\t\ttimings.length = 0;\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < previousQueries.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst query = previousQueries[i];\n\t\t\t\t\t\t\t\t\t\t\t\tconst duration = gl.getQueryParameter(query, gl.QUERY_RESULT);\n\t\t\t\t\t\t\t\t\t\t\t\ttimings[i] = duration * 0.000001;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.freeQueries.push(query);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.report(previousQueriesInfo.renderVersion, timings);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (disjoint) {\n\t\t\t\t\t\t\t\t\t\tthis.previousFrameQueries.forEach((frameQueriesInfo)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tthis.report(frameQueriesInfo.renderVersion, null);\n\t\t\t\t\t\t\t\t\t\t\t\tframeQueriesInfo.destroy(gl);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tthis.previousFrameQueries.length = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsuper.request(renderVersion);\n\t\t\t\t}\n\t\t}\n}\n\nconst invalidateAttachments = [];\nclass WebglGraphicsDevice extends GraphicsDevice {\n\t\tconstructor(canvas, options = {}){\n\t\t\t\tsuper(canvas, options), this._defaultFramebuffer = null, this._defaultFramebufferChanged = false;\n\t\t\t\toptions = this.initOptions;\n\t\t\t\tthis.updateClientRect();\n\t\t\t\tthis.initTextureUnits();\n\t\t\t\tthis.contextLost = false;\n\t\t\t\tthis._contextLostHandler = (event)=>{\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tthis.loseContext();\n\t\t\t\t\t\tthis.fire('devicelost');\n\t\t\t\t};\n\t\t\t\tthis._contextRestoredHandler = ()=>{\n\t\t\t\t\t\tthis.restoreContext();\n\t\t\t\t\t\tthis.fire('devicerestored');\n\t\t\t\t};\n\t\t\t\tconst ua = typeof navigator !== 'undefined' && navigator.userAgent;\n\t\t\t\tthis.forceDisableMultisampling = ua && ua.includes('AppleWebKit') && (ua.includes('15.4') || ua.includes('15_4'));\n\t\t\t\tif (this.forceDisableMultisampling) {\n\t\t\t\t\t\toptions.antialias = false;\n\t\t\t\t}\n\t\t\t\tif (platform.browserName === 'firefox') {\n\t\t\t\t\t\tconst ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n\t\t\t\t\t\tconst match = ua.match(/Firefox\\/(\\d+(\\.\\d+)*)/);\n\t\t\t\t\t\tconst firefoxVersion = match ? match[1] : null;\n\t\t\t\t\t\tif (firefoxVersion) {\n\t\t\t\t\t\t\t\tconst version = parseFloat(firefoxVersion);\n\t\t\t\t\t\t\t\tconst disableAntialias = platform.name === 'windows' && (version >= 120 || version === 115) || platform.name === 'android' && version >= 132;\n\t\t\t\t\t\t\t\tif (disableAntialias) {\n\t\t\t\t\t\t\t\t\t\toptions.antialias = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.backBufferAntialias = options.antialias ?? false;\n\t\t\t\toptions.antialias = false;\n\t\t\t\tconst gl = options.gl ?? canvas.getContext('webgl2', options);\n\t\t\t\tif (!gl) {\n\t\t\t\t\t\tthrow new Error('WebGL not supported');\n\t\t\t\t}\n\t\t\t\tthis.gl = gl;\n\t\t\t\tthis.isWebGL2 = true;\n\t\t\t\tthis._deviceType = DEVICETYPE_WEBGL2;\n\t\t\t\tthis.updateBackbufferFormat(null);\n\t\t\t\tconst isChrome = platform.browserName === 'chrome';\n\t\t\t\tconst isSafari = platform.browserName === 'safari';\n\t\t\t\tconst isMac = platform.browser && navigator.appVersion.indexOf('Mac') !== -1;\n\t\t\t\tthis._tempEnableSafariTextureUnitWorkaround = isSafari;\n\t\t\t\tthis._tempMacChromeBlitFramebufferWorkaround = isMac && isChrome && !options.alpha;\n\t\t\t\tcanvas.addEventListener('webglcontextlost', this._contextLostHandler, false);\n\t\t\t\tcanvas.addEventListener('webglcontextrestored', this._contextRestoredHandler, false);\n\t\t\t\tthis.initializeExtensions();\n\t\t\t\tthis.initializeCapabilities();\n\t\t\t\tthis.initializeRenderState();\n\t\t\t\tthis.initializeContextCaches();\n\t\t\t\tthis.createBackbuffer(null);\n\t\t\t\tthis.supportsImageBitmap = !isSafari && typeof ImageBitmap !== 'undefined';\n\t\t\t\tthis._samplerTypes = new Set([\n\t\t\t\t\t\tgl.SAMPLER_2D,\n\t\t\t\t\t\tgl.SAMPLER_CUBE,\n\t\t\t\t\t\tgl.UNSIGNED_INT_SAMPLER_2D,\n\t\t\t\t\t\tgl.INT_SAMPLER_2D,\n\t\t\t\t\t\tgl.SAMPLER_2D_SHADOW,\n\t\t\t\t\t\tgl.SAMPLER_CUBE_SHADOW,\n\t\t\t\t\t\tgl.SAMPLER_3D,\n\t\t\t\t\t\tgl.INT_SAMPLER_3D,\n\t\t\t\t\t\tgl.UNSIGNED_INT_SAMPLER_3D,\n\t\t\t\t\t\tgl.SAMPLER_2D_ARRAY,\n\t\t\t\t\t\tgl.INT_SAMPLER_2D_ARRAY,\n\t\t\t\t\t\tgl.UNSIGNED_INT_SAMPLER_2D_ARRAY\n\t\t\t\t]);\n\t\t\t\tthis.glAddress = [\n\t\t\t\t\t\tgl.REPEAT,\n\t\t\t\t\t\tgl.CLAMP_TO_EDGE,\n\t\t\t\t\t\tgl.MIRRORED_REPEAT\n\t\t\t\t];\n\t\t\t\tthis.glBlendEquation = [\n\t\t\t\t\t\tgl.FUNC_ADD,\n\t\t\t\t\t\tgl.FUNC_SUBTRACT,\n\t\t\t\t\t\tgl.FUNC_REVERSE_SUBTRACT,\n\t\t\t\t\t\tgl.MIN,\n\t\t\t\t\t\tgl.MAX\n\t\t\t\t];\n\t\t\t\tthis.glBlendFunctionColor = [\n\t\t\t\t\t\tgl.ZERO,\n\t\t\t\t\t\tgl.ONE,\n\t\t\t\t\t\tgl.SRC_COLOR,\n\t\t\t\t\t\tgl.ONE_MINUS_SRC_COLOR,\n\t\t\t\t\t\tgl.DST_COLOR,\n\t\t\t\t\t\tgl.ONE_MINUS_DST_COLOR,\n\t\t\t\t\t\tgl.SRC_ALPHA,\n\t\t\t\t\t\tgl.SRC_ALPHA_SATURATE,\n\t\t\t\t\t\tgl.ONE_MINUS_SRC_ALPHA,\n\t\t\t\t\t\tgl.DST_ALPHA,\n\t\t\t\t\t\tgl.ONE_MINUS_DST_ALPHA,\n\t\t\t\t\t\tgl.CONSTANT_COLOR,\n\t\t\t\t\t\tgl.ONE_MINUS_CONSTANT_COLOR\n\t\t\t\t];\n\t\t\t\tthis.glBlendFunctionAlpha = [\n\t\t\t\t\t\tgl.ZERO,\n\t\t\t\t\t\tgl.ONE,\n\t\t\t\t\t\tgl.SRC_COLOR,\n\t\t\t\t\t\tgl.ONE_MINUS_SRC_COLOR,\n\t\t\t\t\t\tgl.DST_COLOR,\n\t\t\t\t\t\tgl.ONE_MINUS_DST_COLOR,\n\t\t\t\t\t\tgl.SRC_ALPHA,\n\t\t\t\t\t\tgl.SRC_ALPHA_SATURATE,\n\t\t\t\t\t\tgl.ONE_MINUS_SRC_ALPHA,\n\t\t\t\t\t\tgl.DST_ALPHA,\n\t\t\t\t\t\tgl.ONE_MINUS_DST_ALPHA,\n\t\t\t\t\t\tgl.CONSTANT_ALPHA,\n\t\t\t\t\t\tgl.ONE_MINUS_CONSTANT_ALPHA\n\t\t\t\t];\n\t\t\t\tthis.glComparison = [\n\t\t\t\t\t\tgl.NEVER,\n\t\t\t\t\t\tgl.LESS,\n\t\t\t\t\t\tgl.EQUAL,\n\t\t\t\t\t\tgl.LEQUAL,\n\t\t\t\t\t\tgl.GREATER,\n\t\t\t\t\t\tgl.NOTEQUAL,\n\t\t\t\t\t\tgl.GEQUAL,\n\t\t\t\t\t\tgl.ALWAYS\n\t\t\t\t];\n\t\t\t\tthis.glStencilOp = [\n\t\t\t\t\t\tgl.KEEP,\n\t\t\t\t\t\tgl.ZERO,\n\t\t\t\t\t\tgl.REPLACE,\n\t\t\t\t\t\tgl.INCR,\n\t\t\t\t\t\tgl.INCR_WRAP,\n\t\t\t\t\t\tgl.DECR,\n\t\t\t\t\t\tgl.DECR_WRAP,\n\t\t\t\t\t\tgl.INVERT\n\t\t\t\t];\n\t\t\t\tthis.glClearFlag = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tgl.COLOR_BUFFER_BIT,\n\t\t\t\t\t\tgl.DEPTH_BUFFER_BIT,\n\t\t\t\t\t\tgl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT,\n\t\t\t\t\t\tgl.STENCIL_BUFFER_BIT,\n\t\t\t\t\t\tgl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT,\n\t\t\t\t\t\tgl.STENCIL_BUFFER_BIT | gl.DEPTH_BUFFER_BIT,\n\t\t\t\t\t\tgl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT\n\t\t\t\t];\n\t\t\t\tthis.glCull = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tgl.BACK,\n\t\t\t\t\t\tgl.FRONT,\n\t\t\t\t\t\tgl.FRONT_AND_BACK\n\t\t\t\t];\n\t\t\t\tthis.glFilter = [\n\t\t\t\t\t\tgl.NEAREST,\n\t\t\t\t\t\tgl.LINEAR,\n\t\t\t\t\t\tgl.NEAREST_MIPMAP_NEAREST,\n\t\t\t\t\t\tgl.NEAREST_MIPMAP_LINEAR,\n\t\t\t\t\t\tgl.LINEAR_MIPMAP_NEAREST,\n\t\t\t\t\t\tgl.LINEAR_MIPMAP_LINEAR\n\t\t\t\t];\n\t\t\t\tthis.glPrimitive = [\n\t\t\t\t\t\tgl.POINTS,\n\t\t\t\t\t\tgl.LINES,\n\t\t\t\t\t\tgl.LINE_LOOP,\n\t\t\t\t\t\tgl.LINE_STRIP,\n\t\t\t\t\t\tgl.TRIANGLES,\n\t\t\t\t\t\tgl.TRIANGLE_STRIP,\n\t\t\t\t\t\tgl.TRIANGLE_FAN\n\t\t\t\t];\n\t\t\t\tthis.glType = [\n\t\t\t\t\t\tgl.BYTE,\n\t\t\t\t\t\tgl.UNSIGNED_BYTE,\n\t\t\t\t\t\tgl.SHORT,\n\t\t\t\t\t\tgl.UNSIGNED_SHORT,\n\t\t\t\t\t\tgl.INT,\n\t\t\t\t\t\tgl.UNSIGNED_INT,\n\t\t\t\t\t\tgl.FLOAT,\n\t\t\t\t\t\tgl.HALF_FLOAT\n\t\t\t\t];\n\t\t\t\tthis.pcUniformType = {};\n\t\t\t\tthis.pcUniformType[gl.BOOL] = UNIFORMTYPE_BOOL;\n\t\t\t\tthis.pcUniformType[gl.INT] = UNIFORMTYPE_INT;\n\t\t\t\tthis.pcUniformType[gl.FLOAT] = UNIFORMTYPE_FLOAT;\n\t\t\t\tthis.pcUniformType[gl.FLOAT_VEC2] = UNIFORMTYPE_VEC2;\n\t\t\t\tthis.pcUniformType[gl.FLOAT_VEC3] = UNIFORMTYPE_VEC3;\n\t\t\t\tthis.pcUniformType[gl.FLOAT_VEC4] = UNIFORMTYPE_VEC4;\n\t\t\t\tthis.pcUniformType[gl.INT_VEC2] = UNIFORMTYPE_IVEC2;\n\t\t\t\tthis.pcUniformType[gl.INT_VEC3] = UNIFORMTYPE_IVEC3;\n\t\t\t\tthis.pcUniformType[gl.INT_VEC4] = UNIFORMTYPE_IVEC4;\n\t\t\t\tthis.pcUniformType[gl.BOOL_VEC2] = UNIFORMTYPE_BVEC2;\n\t\t\t\tthis.pcUniformType[gl.BOOL_VEC3] = UNIFORMTYPE_BVEC3;\n\t\t\t\tthis.pcUniformType[gl.BOOL_VEC4] = UNIFORMTYPE_BVEC4;\n\t\t\t\tthis.pcUniformType[gl.FLOAT_MAT2] = UNIFORMTYPE_MAT2;\n\t\t\t\tthis.pcUniformType[gl.FLOAT_MAT3] = UNIFORMTYPE_MAT3;\n\t\t\t\tthis.pcUniformType[gl.FLOAT_MAT4] = UNIFORMTYPE_MAT4;\n\t\t\t\tthis.pcUniformType[gl.SAMPLER_2D] = UNIFORMTYPE_TEXTURE2D;\n\t\t\t\tthis.pcUniformType[gl.SAMPLER_CUBE] = UNIFORMTYPE_TEXTURECUBE;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT] = UNIFORMTYPE_UINT;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT_VEC2] = UNIFORMTYPE_UVEC2;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT_VEC3] = UNIFORMTYPE_UVEC3;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT_VEC4] = UNIFORMTYPE_UVEC4;\n\t\t\t\tthis.pcUniformType[gl.SAMPLER_2D_SHADOW] = UNIFORMTYPE_TEXTURE2D_SHADOW;\n\t\t\t\tthis.pcUniformType[gl.SAMPLER_CUBE_SHADOW] = UNIFORMTYPE_TEXTURECUBE_SHADOW;\n\t\t\t\tthis.pcUniformType[gl.SAMPLER_2D_ARRAY] = UNIFORMTYPE_TEXTURE2D_ARRAY;\n\t\t\t\tthis.pcUniformType[gl.SAMPLER_3D] = UNIFORMTYPE_TEXTURE3D;\n\t\t\t\tthis.pcUniformType[gl.INT_SAMPLER_2D] = UNIFORMTYPE_ITEXTURE2D;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURE2D;\n\t\t\t\tthis.pcUniformType[gl.INT_SAMPLER_CUBE] = UNIFORMTYPE_ITEXTURECUBE;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURECUBE;\n\t\t\t\tthis.pcUniformType[gl.INT_SAMPLER_3D] = UNIFORMTYPE_ITEXTURE3D;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT_SAMPLER_3D] = UNIFORMTYPE_UTEXTURE3D;\n\t\t\t\tthis.pcUniformType[gl.INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_ITEXTURE2D_ARRAY;\n\t\t\t\tthis.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_UTEXTURE2D_ARRAY;\n\t\t\t\tthis.targetToSlot = {};\n\t\t\t\tthis.targetToSlot[gl.TEXTURE_2D] = 0;\n\t\t\t\tthis.targetToSlot[gl.TEXTURE_CUBE_MAP] = 1;\n\t\t\t\tthis.targetToSlot[gl.TEXTURE_3D] = 2;\n\t\t\t\tlet scopeX, scopeY, scopeZ, scopeW;\n\t\t\t\tlet uniformValue;\n\t\t\t\tthis.commitFunction = [];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BOOL] = function(uniform, value) {\n\t\t\t\t\t\tif (uniform.value !== value) {\n\t\t\t\t\t\t\t\tgl.uniform1i(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniform.value = value;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_INT] = this.commitFunction[UNIFORMTYPE_BOOL];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_FLOAT] = function(uniform, value) {\n\t\t\t\t\t\tif (uniform.value !== value) {\n\t\t\t\t\t\t\t\tgl.uniform1f(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniform.value = value;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_VEC2] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {\n\t\t\t\t\t\t\t\tgl.uniform2fv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_VEC3] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tscopeZ = value[2];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {\n\t\t\t\t\t\t\t\tgl.uniform3fv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t\t\tuniformValue[2] = scopeZ;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_VEC4] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tscopeZ = value[2];\n\t\t\t\t\t\tscopeW = value[3];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {\n\t\t\t\t\t\t\t\tgl.uniform4fv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t\t\tuniformValue[2] = scopeZ;\n\t\t\t\t\t\t\t\tuniformValue[3] = scopeW;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_IVEC2] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {\n\t\t\t\t\t\t\t\tgl.uniform2iv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BVEC2] = this.commitFunction[UNIFORMTYPE_IVEC2];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_IVEC3] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tscopeZ = value[2];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {\n\t\t\t\t\t\t\t\tgl.uniform3iv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t\t\tuniformValue[2] = scopeZ;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BVEC3] = this.commitFunction[UNIFORMTYPE_IVEC3];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_IVEC4] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tscopeZ = value[2];\n\t\t\t\t\t\tscopeW = value[3];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {\n\t\t\t\t\t\t\t\tgl.uniform4iv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t\t\tuniformValue[2] = scopeZ;\n\t\t\t\t\t\t\t\tuniformValue[3] = scopeW;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BVEC4] = this.commitFunction[UNIFORMTYPE_IVEC4];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_MAT2] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniformMatrix2fv(uniform.locationId, false, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_MAT3] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniformMatrix3fv(uniform.locationId, false, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_MAT4] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniformMatrix4fv(uniform.locationId, false, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_FLOATARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform1fv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_VEC2ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform2fv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_VEC3ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform3fv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_VEC4ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform4fv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UINT] = function(uniform, value) {\n\t\t\t\t\t\tif (uniform.value !== value) {\n\t\t\t\t\t\t\t\tgl.uniform1ui(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniform.value = value;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UVEC2] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {\n\t\t\t\t\t\t\t\tgl.uniform2uiv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UVEC3] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tscopeZ = value[2];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {\n\t\t\t\t\t\t\t\tgl.uniform3uiv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t\t\tuniformValue[2] = scopeZ;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UVEC4] = function(uniform, value) {\n\t\t\t\t\t\tuniformValue = uniform.value;\n\t\t\t\t\t\tscopeX = value[0];\n\t\t\t\t\t\tscopeY = value[1];\n\t\t\t\t\t\tscopeZ = value[2];\n\t\t\t\t\t\tscopeW = value[3];\n\t\t\t\t\t\tif (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {\n\t\t\t\t\t\t\t\tgl.uniform4uiv(uniform.locationId, value);\n\t\t\t\t\t\t\t\tuniformValue[0] = scopeX;\n\t\t\t\t\t\t\t\tuniformValue[1] = scopeY;\n\t\t\t\t\t\t\t\tuniformValue[2] = scopeZ;\n\t\t\t\t\t\t\t\tuniformValue[3] = scopeW;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_INTARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform1iv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UINTARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform1uiv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BOOLARRAY] = this.commitFunction[UNIFORMTYPE_INTARRAY];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_IVEC2ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform2iv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UVEC2ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform2uiv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BVEC2ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC2ARRAY];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_IVEC3ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform3iv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UVEC3ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform3uiv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BVEC3ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC3ARRAY];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_IVEC4ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform4iv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_UVEC4ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniform4uiv(uniform.locationId, value);\n\t\t\t\t};\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_BVEC4ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC4ARRAY];\n\t\t\t\tthis.commitFunction[UNIFORMTYPE_MAT4ARRAY] = function(uniform, value) {\n\t\t\t\t\t\tgl.uniformMatrix4fv(uniform.locationId, false, value);\n\t\t\t\t};\n\t\t\t\tthis.constantTexSource = this.scope.resolve('source');\n\t\t\t\tthis.postInit();\n\t\t}\n\t\tpostInit() {\n\t\t\t\tsuper.postInit();\n\t\t\t\tthis.gpuProfiler = new WebglGpuProfiler(this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tif (this.feedback) {\n\t\t\t\t\t\tgl.deleteTransformFeedback(this.feedback);\n\t\t\t\t}\n\t\t\t\tthis.clearVertexArrayObjectCache();\n\t\t\t\tthis.canvas.removeEventListener('webglcontextlost', this._contextLostHandler, false);\n\t\t\t\tthis.canvas.removeEventListener('webglcontextrestored', this._contextRestoredHandler, false);\n\t\t\t\tthis._contextLostHandler = null;\n\t\t\t\tthis._contextRestoredHandler = null;\n\t\t\t\tthis.gl = null;\n\t\t\t\tsuper.postDestroy();\n\t\t}\n\t\tcreateBackbuffer(frameBuffer) {\n\t\t\t\tthis.supportsStencil = this.initOptions.stencil;\n\t\t\t\tthis.backBuffer = new RenderTarget({\n\t\t\t\t\t\tname: 'WebglFramebuffer',\n\t\t\t\t\t\tgraphicsDevice: this,\n\t\t\t\t\t\tdepth: this.initOptions.depth,\n\t\t\t\t\t\tstencil: this.supportsStencil,\n\t\t\t\t\t\tsamples: this.samples\n\t\t\t\t});\n\t\t\t\tthis.backBuffer.impl.suppliedColorFramebuffer = frameBuffer;\n\t\t}\n\t\tupdateBackbufferFormat(framebuffer) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n\t\t\t\tconst alphaBits = this.gl.getParameter(this.gl.ALPHA_BITS);\n\t\t\t\tthis.backBufferFormat = alphaBits ? PIXELFORMAT_RGBA8 : PIXELFORMAT_RGB8;\n\t\t}\n\t\tupdateBackbuffer() {\n\t\t\t\tconst resolutionChanged = this.canvas.width !== this.backBufferSize.x || this.canvas.height !== this.backBufferSize.y;\n\t\t\t\tif (this._defaultFramebufferChanged || resolutionChanged) {\n\t\t\t\t\t\tif (this._defaultFramebufferChanged) {\n\t\t\t\t\t\t\t\tthis.updateBackbufferFormat(this._defaultFramebuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._defaultFramebufferChanged = false;\n\t\t\t\t\t\tthis.backBufferSize.set(this.canvas.width, this.canvas.height);\n\t\t\t\t\t\tthis.backBuffer.destroy();\n\t\t\t\t\t\tthis.createBackbuffer(this._defaultFramebuffer);\n\t\t\t\t}\n\t\t}\n\t\tcreateVertexBufferImpl(vertexBuffer, format) {\n\t\t\t\treturn new WebglVertexBuffer();\n\t\t}\n\t\tcreateIndexBufferImpl(indexBuffer) {\n\t\t\t\treturn new WebglIndexBuffer(indexBuffer);\n\t\t}\n\t\tcreateShaderImpl(shader) {\n\t\t\t\treturn new WebglShader(shader);\n\t\t}\n\t\tcreateTextureImpl(texture) {\n\t\t\t\treturn new WebglTexture(texture);\n\t\t}\n\t\tcreateRenderTargetImpl(renderTarget) {\n\t\t\t\treturn new WebglRenderTarget();\n\t\t}\n\t\tgetPrecision() {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tlet precision = 'highp';\n\t\t\t\tif (gl.getShaderPrecisionFormat) {\n\t\t\t\t\t\tconst vertexShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT);\n\t\t\t\t\t\tconst vertexShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT);\n\t\t\t\t\t\tconst fragmentShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);\n\t\t\t\t\t\tconst fragmentShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);\n\t\t\t\t\t\tif (vertexShaderPrecisionHighpFloat && vertexShaderPrecisionMediumpFloat && fragmentShaderPrecisionHighpFloat && fragmentShaderPrecisionMediumpFloat) {\n\t\t\t\t\t\t\t\tconst highpAvailable = vertexShaderPrecisionHighpFloat.precision > 0 && fragmentShaderPrecisionHighpFloat.precision > 0;\n\t\t\t\t\t\t\t\tconst mediumpAvailable = vertexShaderPrecisionMediumpFloat.precision > 0 && fragmentShaderPrecisionMediumpFloat.precision > 0;\n\t\t\t\t\t\t\t\tif (!highpAvailable) {\n\t\t\t\t\t\t\t\t\t\tif (mediumpAvailable) {\n\t\t\t\t\t\t\t\t\t\t\t\tprecision = 'mediump';\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tprecision = 'lowp';\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn precision;\n\t\t}\n\t\tgetExtension() {\n\t\t\t\tfor(let i = 0; i < arguments.length; i++){\n\t\t\t\t\t\tif (this.supportedExtensions.indexOf(arguments[i]) !== -1) {\n\t\t\t\t\t\t\t\treturn this.gl.getExtension(arguments[i]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tget extDisjointTimerQuery() {\n\t\t\t\tif (!this._extDisjointTimerQuery) {\n\t\t\t\t\t\tthis._extDisjointTimerQuery = this.getExtension('EXT_disjoint_timer_query_webgl2', 'EXT_disjoint_timer_query');\n\t\t\t\t}\n\t\t\t\treturn this._extDisjointTimerQuery;\n\t\t}\n\t\tinitializeExtensions() {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tthis.supportedExtensions = gl.getSupportedExtensions() ?? [];\n\t\t\t\tthis._extDisjointTimerQuery = null;\n\t\t\t\tthis.textureRG11B10Renderable = true;\n\t\t\t\tthis.extColorBufferFloat = this.getExtension('EXT_color_buffer_float');\n\t\t\t\tthis.textureFloatRenderable = !!this.extColorBufferFloat;\n\t\t\t\tthis.extColorBufferHalfFloat = this.getExtension('EXT_color_buffer_half_float');\n\t\t\t\tthis.textureHalfFloatRenderable = !!this.extColorBufferHalfFloat || !!this.extColorBufferFloat;\n\t\t\t\tthis.extDebugRendererInfo = this.getExtension('WEBGL_debug_renderer_info');\n\t\t\t\tthis.extTextureFloatLinear = this.getExtension('OES_texture_float_linear');\n\t\t\t\tthis.textureFloatFilterable = !!this.extTextureFloatLinear;\n\t\t\t\tthis.extFloatBlend = this.getExtension('EXT_float_blend');\n\t\t\t\tthis.extTextureFilterAnisotropic = this.getExtension('EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic');\n\t\t\t\tthis.extParallelShaderCompile = this.getExtension('KHR_parallel_shader_compile');\n\t\t\t\tthis.extCompressedTextureETC1 = this.getExtension('WEBGL_compressed_texture_etc1');\n\t\t\t\tthis.extCompressedTextureETC = this.getExtension('WEBGL_compressed_texture_etc');\n\t\t\t\tthis.extCompressedTexturePVRTC = this.getExtension('WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc');\n\t\t\t\tthis.extCompressedTextureS3TC = this.getExtension('WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc');\n\t\t\t\tthis.extCompressedTextureS3TC_SRGB = this.getExtension('WEBGL_compressed_texture_s3tc_srgb');\n\t\t\t\tthis.extCompressedTextureATC = this.getExtension('WEBGL_compressed_texture_atc');\n\t\t\t\tthis.extCompressedTextureASTC = this.getExtension('WEBGL_compressed_texture_astc');\n\t\t\t\tthis.extTextureCompressionBPTC = this.getExtension('EXT_texture_compression_bptc');\n\t\t}\n\t\tinitializeCapabilities() {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tlet ext;\n\t\t\t\tconst userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n\t\t\t\tthis.maxPrecision = this.precision = this.getPrecision();\n\t\t\t\tconst contextAttribs = gl.getContextAttributes();\n\t\t\t\tthis.supportsMsaa = contextAttribs?.antialias ?? false;\n\t\t\t\tthis.supportsStencil = contextAttribs?.stencil ?? false;\n\t\t\t\tthis.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);\n\t\t\t\tthis.maxCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);\n\t\t\t\tthis.maxRenderBufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);\n\t\t\t\tthis.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);\n\t\t\t\tthis.maxCombinedTextures = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);\n\t\t\t\tthis.maxVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);\n\t\t\t\tthis.vertexUniformsCount = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);\n\t\t\t\tthis.fragmentUniformsCount = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);\n\t\t\t\tthis.maxColorAttachments = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);\n\t\t\t\tthis.maxVolumeSize = gl.getParameter(gl.MAX_3D_TEXTURE_SIZE);\n\t\t\t\text = this.extDebugRendererInfo;\n\t\t\t\tthis.unmaskedRenderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : '';\n\t\t\t\tthis.unmaskedVendor = ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : '';\n\t\t\t\tconst maliRendererRegex = /\\bMali-G52+/;\n\t\t\t\tconst samsungModelRegex = /SM-[a-zA-Z0-9]+/;\n\t\t\t\tthis.supportsGpuParticles = !(this.unmaskedVendor === 'ARM' && userAgent.match(samsungModelRegex)) && !this.unmaskedRenderer.match(maliRendererRegex);\n\t\t\t\text = this.extTextureFilterAnisotropic;\n\t\t\t\tthis.maxAnisotropy = ext ? gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1;\n\t\t\t\tconst antialiasSupported = !this.forceDisableMultisampling;\n\t\t\t\tthis.maxSamples = antialiasSupported ? gl.getParameter(gl.MAX_SAMPLES) : 1;\n\t\t\t\tthis.maxSamples = Math.min(this.maxSamples, 4);\n\t\t\t\tthis.samples = antialiasSupported && this.backBufferAntialias ? this.maxSamples : 1;\n\t\t\t\tthis.supportsAreaLights = !platform.android;\n\t\t\t\tif (this.maxTextures <= 8) {\n\t\t\t\t\t\tthis.supportsAreaLights = false;\n\t\t\t\t}\n\t\t\t\tthis.initCapsDefines();\n\t\t}\n\t\tinitializeRenderState() {\n\t\t\t\tsuper.initializeRenderState();\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tgl.disable(gl.BLEND);\n\t\t\t\tgl.blendFunc(gl.ONE, gl.ZERO);\n\t\t\t\tgl.blendEquation(gl.FUNC_ADD);\n\t\t\t\tgl.colorMask(true, true, true, true);\n\t\t\t\tgl.blendColor(0, 0, 0, 0);\n\t\t\t\tgl.enable(gl.CULL_FACE);\n\t\t\t\tthis.cullFace = gl.BACK;\n\t\t\t\tgl.cullFace(gl.BACK);\n\t\t\t\tgl.enable(gl.DEPTH_TEST);\n\t\t\t\tgl.depthFunc(gl.LEQUAL);\n\t\t\t\tgl.depthMask(true);\n\t\t\t\tthis.stencil = false;\n\t\t\t\tgl.disable(gl.STENCIL_TEST);\n\t\t\t\tthis.stencilFuncFront = this.stencilFuncBack = FUNC_ALWAYS;\n\t\t\t\tthis.stencilRefFront = this.stencilRefBack = 0;\n\t\t\t\tthis.stencilMaskFront = this.stencilMaskBack = 0xFF;\n\t\t\t\tgl.stencilFunc(gl.ALWAYS, 0, 0xFF);\n\t\t\t\tthis.stencilFailFront = this.stencilFailBack = STENCILOP_KEEP;\n\t\t\t\tthis.stencilZfailFront = this.stencilZfailBack = STENCILOP_KEEP;\n\t\t\t\tthis.stencilZpassFront = this.stencilZpassBack = STENCILOP_KEEP;\n\t\t\t\tthis.stencilWriteMaskFront = 0xFF;\n\t\t\t\tthis.stencilWriteMaskBack = 0xFF;\n\t\t\t\tgl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);\n\t\t\t\tgl.stencilMask(0xFF);\n\t\t\t\tthis.alphaToCoverage = false;\n\t\t\t\tthis.raster = true;\n\t\t\t\tgl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);\n\t\t\t\tgl.disable(gl.RASTERIZER_DISCARD);\n\t\t\t\tthis.depthBiasEnabled = false;\n\t\t\t\tgl.disable(gl.POLYGON_OFFSET_FILL);\n\t\t\t\tthis.clearDepth = 1;\n\t\t\t\tgl.clearDepth(1);\n\t\t\t\tthis.clearColor = new Color(0, 0, 0, 0);\n\t\t\t\tgl.clearColor(0, 0, 0, 0);\n\t\t\t\tthis.clearStencil = 0;\n\t\t\t\tgl.clearStencil(0);\n\t\t\t\tgl.hint(gl.FRAGMENT_SHADER_DERIVATIVE_HINT, gl.NICEST);\n\t\t\t\tgl.enable(gl.SCISSOR_TEST);\n\t\t\t\tgl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);\n\t\t\t\tthis.unpackFlipY = false;\n\t\t\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n\t\t\t\tthis.unpackPremultiplyAlpha = false;\n\t\t\t\tgl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n\t\t\t\tgl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);\n\t\t}\n\t\tinitTextureUnits(count = 16) {\n\t\t\t\tthis.textureUnits = [];\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tthis.textureUnits.push([\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\tinitializeContextCaches() {\n\t\t\t\tsuper.initializeContextCaches();\n\t\t\t\tthis._vaoMap = new Map();\n\t\t\t\tthis.boundVao = null;\n\t\t\t\tthis.activeFramebuffer = null;\n\t\t\t\tthis.feedback = null;\n\t\t\t\tthis.transformFeedbackBuffer = null;\n\t\t\t\tthis.textureUnit = 0;\n\t\t\t\tthis.initTextureUnits(this.maxCombinedTextures);\n\t\t}\n\t\tloseContext() {\n\t\t\t\tsuper.loseContext();\n\t\t\t\tfor (const shader of this.shaders){\n\t\t\t\t\t\tshader.loseContext();\n\t\t\t\t}\n\t\t}\n\t\trestoreContext() {\n\t\t\t\tthis.initializeExtensions();\n\t\t\t\tthis.initializeCapabilities();\n\t\t\t\tsuper.restoreContext();\n\t\t\t\tfor (const shader of this.shaders){\n\t\t\t\t\t\tshader.restoreContext();\n\t\t\t\t}\n\t\t}\n\t\tsetViewport(x, y, w, h) {\n\t\t\t\tif (this.vx !== x || this.vy !== y || this.vw !== w || this.vh !== h) {\n\t\t\t\t\t\tthis.gl.viewport(x, y, w, h);\n\t\t\t\t\t\tthis.vx = x;\n\t\t\t\t\t\tthis.vy = y;\n\t\t\t\t\t\tthis.vw = w;\n\t\t\t\t\t\tthis.vh = h;\n\t\t\t\t}\n\t\t}\n\t\tsetScissor(x, y, w, h) {\n\t\t\t\tif (this.sx !== x || this.sy !== y || this.sw !== w || this.sh !== h) {\n\t\t\t\t\t\tthis.gl.scissor(x, y, w, h);\n\t\t\t\t\t\tthis.sx = x;\n\t\t\t\t\t\tthis.sy = y;\n\t\t\t\t\t\tthis.sw = w;\n\t\t\t\t\t\tthis.sh = h;\n\t\t\t\t}\n\t\t}\n\t\tsetFramebuffer(fb) {\n\t\t\t\tif (this.activeFramebuffer !== fb) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, fb);\n\t\t\t\t\t\tthis.activeFramebuffer = fb;\n\t\t\t\t}\n\t\t}\n\t\tcopyRenderTarget(source, dest, color, depth) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tif (source === this.backBuffer) {\n\t\t\t\t\t\tsource = null;\n\t\t\t\t}\n\t\t\t\tif (color) {\n\t\t\t\t\t\tif (!dest) {\n\t\t\t\t\t\t\t\tif (!source._colorBuffer) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (source) {\n\t\t\t\t\t\t\t\tif (!source._colorBuffer || !dest._colorBuffer) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (source._colorBuffer._format !== dest._colorBuffer._format) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (depth && source) {\n\t\t\t\t\t\tif (!source._depth) {\n\t\t\t\t\t\t\t\tif (!source._depthBuffer || !dest._depthBuffer) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (source._depthBuffer._format !== dest._depthBuffer._format) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst prevRt = this.renderTarget;\n\t\t\t\tthis.renderTarget = dest;\n\t\t\t\tthis.updateBegin();\n\t\t\t\tconst src = source ? source.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer;\n\t\t\t\tconst dst = dest ? dest.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer;\n\t\t\t\tgl.bindFramebuffer(gl.READ_FRAMEBUFFER, src);\n\t\t\t\tgl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst);\n\t\t\t\tconst w = source ? source.width : dest ? dest.width : this.width;\n\t\t\t\tconst h = source ? source.height : dest ? dest.height : this.height;\n\t\t\t\tgl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, (color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0), gl.NEAREST);\n\t\t\t\tthis.renderTarget = prevRt;\n\t\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, prevRt ? prevRt.impl._glFrameBuffer : null);\n\t\t\t\treturn true;\n\t\t}\n\t\tframeStart() {\n\t\t\t\tsuper.frameStart();\n\t\t\t\tthis.updateBackbuffer();\n\t\t\t\tthis.gpuProfiler.frameStart();\n\t\t}\n\t\tframeEnd() {\n\t\t\t\tsuper.frameEnd();\n\t\t\t\tthis.gpuProfiler.frameEnd();\n\t\t\t\tthis.gpuProfiler.request();\n\t\t}\n\t\tstartRenderPass(renderPass) {\n\t\t\t\tconst rt = renderPass.renderTarget ?? this.backBuffer;\n\t\t\t\tthis.renderTarget = rt;\n\t\t\t\tthis.updateBegin();\n\t\t\t\tconst { width, height } = rt;\n\t\t\t\tthis.setViewport(0, 0, width, height);\n\t\t\t\tthis.setScissor(0, 0, width, height);\n\t\t\t\tconst colorOps = renderPass.colorOps;\n\t\t\t\tconst depthStencilOps = renderPass.depthStencilOps;\n\t\t\t\tif (colorOps?.clear || depthStencilOps.clearDepth || depthStencilOps.clearStencil) {\n\t\t\t\t\t\tlet clearFlags = 0;\n\t\t\t\t\t\tconst clearOptions = {};\n\t\t\t\t\t\tif (colorOps?.clear) {\n\t\t\t\t\t\t\t\tclearFlags |= CLEARFLAG_COLOR;\n\t\t\t\t\t\t\t\tclearOptions.color = [\n\t\t\t\t\t\t\t\t\t\tcolorOps.clearValue.r,\n\t\t\t\t\t\t\t\t\t\tcolorOps.clearValue.g,\n\t\t\t\t\t\t\t\t\t\tcolorOps.clearValue.b,\n\t\t\t\t\t\t\t\t\t\tcolorOps.clearValue.a\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (depthStencilOps.clearDepth) {\n\t\t\t\t\t\t\t\tclearFlags |= CLEARFLAG_DEPTH;\n\t\t\t\t\t\t\t\tclearOptions.depth = depthStencilOps.clearDepthValue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (depthStencilOps.clearStencil) {\n\t\t\t\t\t\t\t\tclearFlags |= CLEARFLAG_STENCIL;\n\t\t\t\t\t\t\t\tclearOptions.stencil = depthStencilOps.clearStencilValue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tclearOptions.flags = clearFlags;\n\t\t\t\t\t\tthis.clear(clearOptions);\n\t\t\t\t}\n\t\t\t\tthis.insideRenderPass = true;\n\t\t}\n\t\tendRenderPass(renderPass) {\n\t\t\t\tthis.unbindVertexArray();\n\t\t\t\tconst target = this.renderTarget;\n\t\t\t\tconst colorBufferCount = renderPass.colorArrayOps.length;\n\t\t\t\tif (target) {\n\t\t\t\t\t\tinvalidateAttachments.length = 0;\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tfor(let i = 0; i < colorBufferCount; i++){\n\t\t\t\t\t\t\t\tconst colorOps = renderPass.colorArrayOps[i];\n\t\t\t\t\t\t\t\tif (!(colorOps.store || colorOps.resolve)) {\n\t\t\t\t\t\t\t\t\t\tinvalidateAttachments.push(gl.COLOR_ATTACHMENT0 + i);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (target !== this.backBuffer) {\n\t\t\t\t\t\t\t\tif (!renderPass.depthStencilOps.storeDepth) {\n\t\t\t\t\t\t\t\t\t\tinvalidateAttachments.push(gl.DEPTH_ATTACHMENT);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!renderPass.depthStencilOps.storeStencil) {\n\t\t\t\t\t\t\t\t\t\tinvalidateAttachments.push(gl.STENCIL_ATTACHMENT);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (invalidateAttachments.length > 0) {\n\t\t\t\t\t\t\t\tif (renderPass.fullSizeClearRect) {\n\t\t\t\t\t\t\t\t\t\tgl.invalidateFramebuffer(gl.DRAW_FRAMEBUFFER, invalidateAttachments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (colorBufferCount && renderPass.colorOps?.resolve) {\n\t\t\t\t\t\t\t\tif (renderPass.samples > 1 && target.autoResolve) {\n\t\t\t\t\t\t\t\t\t\ttarget.resolve(true, false);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) {\n\t\t\t\t\t\t\t\tif (renderPass.samples > 1 && target.autoResolve) {\n\t\t\t\t\t\t\t\t\t\ttarget.resolve(false, true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < colorBufferCount; i++){\n\t\t\t\t\t\t\t\tconst colorOps = renderPass.colorArrayOps[i];\n\t\t\t\t\t\t\t\tif (colorOps.genMipmaps) {\n\t\t\t\t\t\t\t\t\t\tconst colorBuffer = target._colorBuffers[i];\n\t\t\t\t\t\t\t\t\t\tif (colorBuffer && colorBuffer.impl._glTexture && colorBuffer.mipmaps) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.activeTexture(this.maxCombinedTextures - 1);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.bindTexture(colorBuffer);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.gl.generateMipmap(colorBuffer.impl._glTarget);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.insideRenderPass = false;\n\t\t}\n\t\tset defaultFramebuffer(value) {\n\t\t\t\tif (this._defaultFramebuffer !== value) {\n\t\t\t\t\t\tthis._defaultFramebuffer = value;\n\t\t\t\t\t\tthis._defaultFramebufferChanged = true;\n\t\t\t\t}\n\t\t}\n\t\tget defaultFramebuffer() {\n\t\t\t\treturn this._defaultFramebuffer;\n\t\t}\n\t\tupdateBegin() {\n\t\t\t\tthis.boundVao = null;\n\t\t\t\tif (this._tempEnableSafariTextureUnitWorkaround) {\n\t\t\t\t\t\tfor(let unit = 0; unit < this.textureUnits.length; ++unit){\n\t\t\t\t\t\t\t\tfor(let slot = 0; slot < 3; ++slot){\n\t\t\t\t\t\t\t\t\t\tthis.textureUnits[unit][slot] = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst target = this.renderTarget ?? this.backBuffer;\n\t\t\t\tconst targetImpl = target.impl;\n\t\t\t\tif (!targetImpl.initialized) {\n\t\t\t\t\t\tthis.initRenderTarget(target);\n\t\t\t\t}\n\t\t\t\tthis.setFramebuffer(targetImpl._glFrameBuffer);\n\t\t}\n\t\tupdateEnd() {\n\t\t\t\tthis.unbindVertexArray();\n\t\t\t\tconst target = this.renderTarget;\n\t\t\t\tif (target && target !== this.backBuffer) {\n\t\t\t\t\t\tif (target._samples > 1 && target.autoResolve) {\n\t\t\t\t\t\t\t\ttarget.resolve();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst colorBuffer = target._colorBuffer;\n\t\t\t\t\t\tif (colorBuffer && colorBuffer.impl._glTexture && colorBuffer.mipmaps) {\n\t\t\t\t\t\t\t\tthis.activeTexture(this.maxCombinedTextures - 1);\n\t\t\t\t\t\t\t\tthis.bindTexture(colorBuffer);\n\t\t\t\t\t\t\t\tthis.gl.generateMipmap(colorBuffer.impl._glTarget);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetUnpackFlipY(flipY) {\n\t\t\t\tif (this.unpackFlipY !== flipY) {\n\t\t\t\t\t\tthis.unpackFlipY = flipY;\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);\n\t\t\t\t}\n\t\t}\n\t\tsetUnpackPremultiplyAlpha(premultiplyAlpha) {\n\t\t\t\tif (this.unpackPremultiplyAlpha !== premultiplyAlpha) {\n\t\t\t\t\t\tthis.unpackPremultiplyAlpha = premultiplyAlpha;\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tgl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);\n\t\t\t\t}\n\t\t}\n\t\tactiveTexture(textureUnit) {\n\t\t\t\tif (this.textureUnit !== textureUnit) {\n\t\t\t\t\t\tthis.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);\n\t\t\t\t\t\tthis.textureUnit = textureUnit;\n\t\t\t\t}\n\t\t}\n\t\tbindTexture(texture) {\n\t\t\t\tconst impl = texture.impl;\n\t\t\t\tconst textureTarget = impl._glTarget;\n\t\t\t\tconst textureObject = impl._glTexture;\n\t\t\t\tconst textureUnit = this.textureUnit;\n\t\t\t\tconst slot = this.targetToSlot[textureTarget];\n\t\t\t\tif (this.textureUnits[textureUnit][slot] !== textureObject) {\n\t\t\t\t\t\tthis.gl.bindTexture(textureTarget, textureObject);\n\t\t\t\t\t\tthis.textureUnits[textureUnit][slot] = textureObject;\n\t\t\t\t}\n\t\t}\n\t\tbindTextureOnUnit(texture, textureUnit) {\n\t\t\t\tconst impl = texture.impl;\n\t\t\t\tconst textureTarget = impl._glTarget;\n\t\t\t\tconst textureObject = impl._glTexture;\n\t\t\t\tconst slot = this.targetToSlot[textureTarget];\n\t\t\t\tif (this.textureUnits[textureUnit][slot] !== textureObject) {\n\t\t\t\t\t\tthis.activeTexture(textureUnit);\n\t\t\t\t\t\tthis.gl.bindTexture(textureTarget, textureObject);\n\t\t\t\t\t\tthis.textureUnits[textureUnit][slot] = textureObject;\n\t\t\t\t}\n\t\t}\n\t\tsetTextureParameters(texture) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tconst flags = texture.impl.dirtyParameterFlags;\n\t\t\t\tconst target = texture.impl._glTarget;\n\t\t\t\tif (flags & TEXPROPERTY_MIN_FILTER) {\n\t\t\t\t\t\tlet filter = texture._minFilter;\n\t\t\t\t\t\tif (!texture._mipmaps || texture._compressed && texture._levels.length === 1) {\n\t\t\t\t\t\t\t\tif (filter === FILTER_NEAREST_MIPMAP_NEAREST || filter === FILTER_NEAREST_MIPMAP_LINEAR) {\n\t\t\t\t\t\t\t\t\t\tfilter = FILTER_NEAREST;\n\t\t\t\t\t\t\t\t} else if (filter === FILTER_LINEAR_MIPMAP_NEAREST || filter === FILTER_LINEAR_MIPMAP_LINEAR) {\n\t\t\t\t\t\t\t\t\t\tfilter = FILTER_LINEAR;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgl.texParameteri(target, gl.TEXTURE_MIN_FILTER, this.glFilter[filter]);\n\t\t\t\t}\n\t\t\t\tif (flags & TEXPROPERTY_MAG_FILTER) {\n\t\t\t\t\t\tgl.texParameteri(target, gl.TEXTURE_MAG_FILTER, this.glFilter[texture._magFilter]);\n\t\t\t\t}\n\t\t\t\tif (flags & TEXPROPERTY_ADDRESS_U) {\n\t\t\t\t\t\tgl.texParameteri(target, gl.TEXTURE_WRAP_S, this.glAddress[texture._addressU]);\n\t\t\t\t}\n\t\t\t\tif (flags & TEXPROPERTY_ADDRESS_V) {\n\t\t\t\t\t\tgl.texParameteri(target, gl.TEXTURE_WRAP_T, this.glAddress[texture._addressV]);\n\t\t\t\t}\n\t\t\t\tif (flags & TEXPROPERTY_ADDRESS_W) {\n\t\t\t\t\t\tgl.texParameteri(target, gl.TEXTURE_WRAP_R, this.glAddress[texture._addressW]);\n\t\t\t\t}\n\t\t\t\tif (flags & TEXPROPERTY_COMPARE_ON_READ) {\n\t\t\t\t\t\tgl.texParameteri(target, gl.TEXTURE_COMPARE_MODE, texture._compareOnRead ? gl.COMPARE_REF_TO_TEXTURE : gl.NONE);\n\t\t\t\t}\n\t\t\t\tif (flags & TEXPROPERTY_COMPARE_FUNC) {\n\t\t\t\t\t\tgl.texParameteri(target, gl.TEXTURE_COMPARE_FUNC, this.glComparison[texture._compareFunc]);\n\t\t\t\t}\n\t\t\t\tif (flags & TEXPROPERTY_ANISOTROPY) {\n\t\t\t\t\t\tconst ext = this.extTextureFilterAnisotropic;\n\t\t\t\t\t\tif (ext) {\n\t\t\t\t\t\t\t\tgl.texParameterf(target, ext.TEXTURE_MAX_ANISOTROPY_EXT, math.clamp(Math.round(texture._anisotropy), 1, this.maxAnisotropy));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetTexture(texture, textureUnit) {\n\t\t\t\tconst impl = texture.impl;\n\t\t\t\tif (!impl._glTexture) {\n\t\t\t\t\t\timpl.initialize(this, texture);\n\t\t\t\t}\n\t\t\t\tif (impl.dirtyParameterFlags > 0 || texture._needsUpload || texture._needsMipmapsUpload) {\n\t\t\t\t\t\tthis.activeTexture(textureUnit);\n\t\t\t\t\t\tthis.bindTexture(texture);\n\t\t\t\t\t\tif (impl.dirtyParameterFlags) {\n\t\t\t\t\t\t\t\tthis.setTextureParameters(texture);\n\t\t\t\t\t\t\t\timpl.dirtyParameterFlags = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (texture._needsUpload || texture._needsMipmapsUpload) {\n\t\t\t\t\t\t\t\timpl.upload(this, texture);\n\t\t\t\t\t\t\t\ttexture._needsUpload = false;\n\t\t\t\t\t\t\t\ttexture._needsMipmapsUpload = false;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.bindTextureOnUnit(texture, textureUnit);\n\t\t\t\t}\n\t\t}\n\t\tcreateVertexArray(vertexBuffers) {\n\t\t\t\tlet key, vao;\n\t\t\t\tconst useCache = vertexBuffers.length > 1;\n\t\t\t\tif (useCache) {\n\t\t\t\t\t\tkey = '';\n\t\t\t\t\t\tfor(let i = 0; i < vertexBuffers.length; i++){\n\t\t\t\t\t\t\t\tconst vertexBuffer = vertexBuffers[i];\n\t\t\t\t\t\t\t\tkey += vertexBuffer.id + vertexBuffer.format.renderingHash;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvao = this._vaoMap.get(key);\n\t\t\t\t}\n\t\t\t\tif (!vao) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tvao = gl.createVertexArray();\n\t\t\t\t\t\tgl.bindVertexArray(vao);\n\t\t\t\t\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);\n\t\t\t\t\t\tfor(let i = 0; i < vertexBuffers.length; i++){\n\t\t\t\t\t\t\t\tconst vertexBuffer = vertexBuffers[i];\n\t\t\t\t\t\t\t\tgl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer.impl.bufferId);\n\t\t\t\t\t\t\t\tconst elements = vertexBuffer.format.elements;\n\t\t\t\t\t\t\t\tfor(let j = 0; j < elements.length; j++){\n\t\t\t\t\t\t\t\t\t\tconst e = elements[j];\n\t\t\t\t\t\t\t\t\t\tconst loc = semanticToLocation[e.name];\n\t\t\t\t\t\t\t\t\t\tif (e.asInt) {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.vertexAttribIPointer(loc, e.numComponents, this.glType[e.dataType], e.stride, e.offset);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.vertexAttribPointer(loc, e.numComponents, this.glType[e.dataType], e.normalize, e.stride, e.offset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tgl.enableVertexAttribArray(loc);\n\t\t\t\t\t\t\t\t\t\tif (vertexBuffer.format.instancing) {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.vertexAttribDivisor(loc, 1);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgl.bindVertexArray(null);\n\t\t\t\t\t\tgl.bindBuffer(gl.ARRAY_BUFFER, null);\n\t\t\t\t\t\tif (useCache) {\n\t\t\t\t\t\t\t\tthis._vaoMap.set(key, vao);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn vao;\n\t\t}\n\t\tunbindVertexArray() {\n\t\t\t\tif (this.boundVao) {\n\t\t\t\t\t\tthis.boundVao = null;\n\t\t\t\t\t\tthis.gl.bindVertexArray(null);\n\t\t\t\t}\n\t\t}\n\t\tsetBuffers(indexBuffer) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tlet vao;\n\t\t\t\tif (this.vertexBuffers.length === 1) {\n\t\t\t\t\t\tconst vertexBuffer = this.vertexBuffers[0];\n\t\t\t\t\t\tif (!vertexBuffer.impl.vao) {\n\t\t\t\t\t\t\t\tvertexBuffer.impl.vao = this.createVertexArray(this.vertexBuffers);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvao = vertexBuffer.impl.vao;\n\t\t\t\t} else {\n\t\t\t\t\t\tvao = this.createVertexArray(this.vertexBuffers);\n\t\t\t\t}\n\t\t\t\tif (this.boundVao !== vao) {\n\t\t\t\t\t\tthis.boundVao = vao;\n\t\t\t\t\t\tgl.bindVertexArray(vao);\n\t\t\t\t}\n\t\t\t\tconst bufferId = indexBuffer ? indexBuffer.impl.bufferId : null;\n\t\t\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferId);\n\t\t}\n\t\tdraw(primitive, indexBuffer, numInstances, indirectSlot, first = true, last = true) {\n\t\t\t\tconst shader = this.shader;\n\t\t\t\tif (shader) {\n\t\t\t\t\t\tthis.activateShader();\n\t\t\t\t\t\tif (this.shaderValid) {\n\t\t\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\t\t\t\tthis.setBuffers(indexBuffer);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet textureUnit = 0;\n\t\t\t\t\t\t\t\tconst samplers = shader.impl.samplers;\n\t\t\t\t\t\t\t\tfor(let i = 0, len = samplers.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tconst sampler = samplers[i];\n\t\t\t\t\t\t\t\t\t\tlet samplerValue = sampler.scopeId.value;\n\t\t\t\t\t\t\t\t\t\tif (!samplerValue) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst samplerName = sampler.scopeId.name;\n\t\t\t\t\t\t\t\t\t\t\t\tif (samplerName === 'uSceneDepthMap') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsamplerValue = getBuiltInTexture(this, 'white');\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (samplerName === 'uSceneColorMap') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsamplerValue = getBuiltInTexture(this, 'pink');\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (!samplerValue) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsamplerValue = getBuiltInTexture(this, 'pink');\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (samplerValue instanceof Texture) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst texture = samplerValue;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.setTexture(texture, textureUnit);\n\t\t\t\t\t\t\t\t\t\t\t\tif (sampler.slot !== textureUnit) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgl.uniform1i(sampler.locationId, textureUnit);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsampler.slot = textureUnit;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\ttextureUnit++;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tsampler.array.length = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tconst numTextures = samplerValue.length;\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < numTextures; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst texture = samplerValue[j];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.setTexture(texture, textureUnit);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsampler.array[j] = textureUnit;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttextureUnit++;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tgl.uniform1iv(sampler.locationId, sampler.array);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst uniforms = shader.impl.uniforms;\n\t\t\t\t\t\t\t\tfor(let i = 0, len = uniforms.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tconst uniform = uniforms[i];\n\t\t\t\t\t\t\t\t\t\tconst scopeId = uniform.scopeId;\n\t\t\t\t\t\t\t\t\t\tconst uniformVersion = uniform.version;\n\t\t\t\t\t\t\t\t\t\tconst programVersion = scopeId.versionObject.version;\n\t\t\t\t\t\t\t\t\t\tif (uniformVersion.globalId !== programVersion.globalId || uniformVersion.revision !== programVersion.revision) {\n\t\t\t\t\t\t\t\t\t\t\t\tuniformVersion.globalId = programVersion.globalId;\n\t\t\t\t\t\t\t\t\t\t\t\tuniformVersion.revision = programVersion.revision;\n\t\t\t\t\t\t\t\t\t\t\t\tconst value = scopeId.value;\n\t\t\t\t\t\t\t\t\t\t\t\tif (value !== null && value !== undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.commitFunction[uniform.dataType](uniform, value);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this.transformFeedbackBuffer) {\n\t\t\t\t\t\t\t\t\t\tgl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.transformFeedbackBuffer.impl.bufferId);\n\t\t\t\t\t\t\t\t\t\tgl.beginTransformFeedback(gl.POINTS);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst mode = this.glPrimitive[primitive.type];\n\t\t\t\t\t\t\t\tconst count = primitive.count;\n\t\t\t\t\t\t\t\tif (primitive.indexed) {\n\t\t\t\t\t\t\t\t\t\tconst format = indexBuffer.impl.glFormat;\n\t\t\t\t\t\t\t\t\t\tconst offset = primitive.base * indexBuffer.bytesPerIndex;\n\t\t\t\t\t\t\t\t\t\tif (numInstances > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.drawElementsInstanced(mode, count, format, offset, numInstances);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.drawElements(mode, count, format, offset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst first = primitive.base;\n\t\t\t\t\t\t\t\t\t\tif (numInstances > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.drawArraysInstanced(mode, first, count, numInstances);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tgl.drawArrays(mode, first, count);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this.transformFeedbackBuffer) {\n\t\t\t\t\t\t\t\t\t\tgl.endTransformFeedback();\n\t\t\t\t\t\t\t\t\t\tgl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._drawCallsPerFrame++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (last) {\n\t\t\t\t\t\tthis.clearVertexBuffer();\n\t\t\t\t}\n\t\t}\n\t\tclear(options) {\n\t\t\t\tconst defaultOptions = this.defaultClearOptions;\n\t\t\t\toptions = options || defaultOptions;\n\t\t\t\tconst flags = options.flags ?? defaultOptions.flags;\n\t\t\t\tif (flags !== 0) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tif (flags & CLEARFLAG_COLOR) {\n\t\t\t\t\t\t\t\tconst color = options.color ?? defaultOptions.color;\n\t\t\t\t\t\t\t\tconst r = color[0];\n\t\t\t\t\t\t\t\tconst g = color[1];\n\t\t\t\t\t\t\t\tconst b = color[2];\n\t\t\t\t\t\t\t\tconst a = color[3];\n\t\t\t\t\t\t\t\tconst c = this.clearColor;\n\t\t\t\t\t\t\t\tif (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {\n\t\t\t\t\t\t\t\t\t\tthis.gl.clearColor(r, g, b, a);\n\t\t\t\t\t\t\t\t\t\tthis.clearColor.set(r, g, b, a);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.setBlendState(BlendState.NOBLEND);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (flags & CLEARFLAG_DEPTH) {\n\t\t\t\t\t\t\t\tconst depth = options.depth ?? defaultOptions.depth;\n\t\t\t\t\t\t\t\tif (depth !== this.clearDepth) {\n\t\t\t\t\t\t\t\t\t\tthis.gl.clearDepth(depth);\n\t\t\t\t\t\t\t\t\t\tthis.clearDepth = depth;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.setDepthState(DepthState.WRITEDEPTH);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (flags & CLEARFLAG_STENCIL) {\n\t\t\t\t\t\t\t\tconst stencil = options.stencil ?? defaultOptions.stencil;\n\t\t\t\t\t\t\t\tif (stencil !== this.clearStencil) {\n\t\t\t\t\t\t\t\t\t\tthis.gl.clearStencil(stencil);\n\t\t\t\t\t\t\t\t\t\tthis.clearStencil = stencil;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgl.stencilMask(0xFF);\n\t\t\t\t\t\t\t\tthis.stencilWriteMaskFront = 0xFF;\n\t\t\t\t\t\t\t\tthis.stencilWriteMaskBack = 0xFF;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgl.clear(this.glClearFlag[flags]);\n\t\t\t\t}\n\t\t}\n\t\tsubmit() {\n\t\t\t\tthis.gl.flush();\n\t\t}\n\t\treadPixels(x, y, w, h, pixels) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tgl.readPixels(x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);\n\t\t}\n\t\tclientWaitAsync(flags, interval_ms) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tconst sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);\n\t\t\t\tthis.submit();\n\t\t\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\t\t\tfunction test() {\n\t\t\t\t\t\t\t\tconst res = gl.clientWaitSync(sync, flags, 0);\n\t\t\t\t\t\t\t\tif (res === gl.TIMEOUT_EXPIRED) {\n\t\t\t\t\t\t\t\t\t\tsetTimeout(test, interval_ms);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tgl.deleteSync(sync);\n\t\t\t\t\t\t\t\t\t\tif (res === gl.WAIT_FAILED) {\n\t\t\t\t\t\t\t\t\t\t\t\treject(new Error('webgl clientWaitSync sync failed'));\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttest();\n\t\t\t\t});\n\t\t}\n\t\tasync readPixelsAsync(x, y, w, h, pixels) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tconst impl = this.renderTarget.colorBuffer?.impl;\n\t\t\t\tconst format = impl?._glFormat ?? gl.RGBA;\n\t\t\t\tconst pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE;\n\t\t\t\tconst buf = gl.createBuffer();\n\t\t\t\tgl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);\n\t\t\t\tgl.bufferData(gl.PIXEL_PACK_BUFFER, pixels.byteLength, gl.STREAM_READ);\n\t\t\t\tgl.readPixels(x, y, w, h, format, pixelType, 0);\n\t\t\t\tgl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n\t\t\t\tawait this.clientWaitAsync(0, 16);\n\t\t\t\tgl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);\n\t\t\t\tgl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, pixels);\n\t\t\t\tgl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n\t\t\t\tgl.deleteBuffer(buf);\n\t\t\t\treturn pixels;\n\t\t}\n\t\treadTextureAsync(texture, x, y, width, height, options) {\n\t\t\t\tconst face = options.face ?? 0;\n\t\t\t\tconst renderTarget = options.renderTarget ?? new RenderTarget({\n\t\t\t\t\t\tcolorBuffer: texture,\n\t\t\t\t\t\tdepth: false,\n\t\t\t\t\t\tface: face\n\t\t\t\t});\n\t\t\t\tconst buffer = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, 1, texture._format));\n\t\t\t\tconst data = options.data ?? new (getPixelFormatArrayType(texture._format))(buffer);\n\t\t\t\tthis.setRenderTarget(renderTarget);\n\t\t\t\tthis.initRenderTarget(renderTarget);\n\t\t\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\t\t\tthis.readPixelsAsync(x, y, width, height, data).then((data)=>{\n\t\t\t\t\t\t\t\tif (!options.renderTarget) {\n\t\t\t\t\t\t\t\t\t\trenderTarget.destroy();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tresolve(data);\n\t\t\t\t\t\t}).catch(reject);\n\t\t\t\t});\n\t\t}\n\t\tasync writeTextureAsync(texture, x, y, width, height, data) {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tconst impl = texture.impl;\n\t\t\t\tconst format = impl?._glFormat ?? gl.RGBA;\n\t\t\t\tconst pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE;\n\t\t\t\tconst buf = gl.createBuffer();\n\t\t\t\tgl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, buf);\n\t\t\t\tgl.bufferData(gl.PIXEL_UNPACK_BUFFER, data, gl.STREAM_DRAW);\n\t\t\t\tgl.bindTexture(gl.TEXTURE_2D, impl._glTexture);\n\t\t\t\tgl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, width, height, format, pixelType, 0);\n\t\t\t\tgl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);\n\t\t\t\ttexture._needsUpload = false;\n\t\t\t\ttexture._mipmapsUploaded = false;\n\t\t\t\tawait this.clientWaitAsync(0, 16);\n\t\t}\n\t\tsetAlphaToCoverage(state) {\n\t\t\t\tif (this.alphaToCoverage !== state) {\n\t\t\t\t\t\tthis.alphaToCoverage = state;\n\t\t\t\t\t\tif (state) {\n\t\t\t\t\t\t\t\tthis.gl.enable(this.gl.SAMPLE_ALPHA_TO_COVERAGE);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.gl.disable(this.gl.SAMPLE_ALPHA_TO_COVERAGE);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetTransformFeedbackBuffer(tf) {\n\t\t\t\tif (this.transformFeedbackBuffer !== tf) {\n\t\t\t\t\t\tthis.transformFeedbackBuffer = tf;\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tif (tf) {\n\t\t\t\t\t\t\t\tif (!this.feedback) {\n\t\t\t\t\t\t\t\t\t\tthis.feedback = gl.createTransformFeedback();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.feedback);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tgl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetRaster(on) {\n\t\t\t\tif (this.raster !== on) {\n\t\t\t\t\t\tthis.raster = on;\n\t\t\t\t\t\tif (on) {\n\t\t\t\t\t\t\t\tthis.gl.disable(this.gl.RASTERIZER_DISCARD);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.gl.enable(this.gl.RASTERIZER_DISCARD);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetStencilTest(enable) {\n\t\t\t\tif (this.stencil !== enable) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tif (enable) {\n\t\t\t\t\t\t\t\tgl.enable(gl.STENCIL_TEST);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tgl.disable(gl.STENCIL_TEST);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.stencil = enable;\n\t\t\t\t}\n\t\t}\n\t\tsetStencilFunc(func, ref, mask) {\n\t\t\t\tif (this.stencilFuncFront !== func || this.stencilRefFront !== ref || this.stencilMaskFront !== mask || this.stencilFuncBack !== func || this.stencilRefBack !== ref || this.stencilMaskBack !== mask) {\n\t\t\t\t\t\tthis.gl.stencilFunc(this.glComparison[func], ref, mask);\n\t\t\t\t\t\tthis.stencilFuncFront = this.stencilFuncBack = func;\n\t\t\t\t\t\tthis.stencilRefFront = this.stencilRefBack = ref;\n\t\t\t\t\t\tthis.stencilMaskFront = this.stencilMaskBack = mask;\n\t\t\t\t}\n\t\t}\n\t\tsetStencilFuncFront(func, ref, mask) {\n\t\t\t\tif (this.stencilFuncFront !== func || this.stencilRefFront !== ref || this.stencilMaskFront !== mask) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tgl.stencilFuncSeparate(gl.FRONT, this.glComparison[func], ref, mask);\n\t\t\t\t\t\tthis.stencilFuncFront = func;\n\t\t\t\t\t\tthis.stencilRefFront = ref;\n\t\t\t\t\t\tthis.stencilMaskFront = mask;\n\t\t\t\t}\n\t\t}\n\t\tsetStencilFuncBack(func, ref, mask) {\n\t\t\t\tif (this.stencilFuncBack !== func || this.stencilRefBack !== ref || this.stencilMaskBack !== mask) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tgl.stencilFuncSeparate(gl.BACK, this.glComparison[func], ref, mask);\n\t\t\t\t\t\tthis.stencilFuncBack = func;\n\t\t\t\t\t\tthis.stencilRefBack = ref;\n\t\t\t\t\t\tthis.stencilMaskBack = mask;\n\t\t\t\t}\n\t\t}\n\t\tsetStencilOperation(fail, zfail, zpass, writeMask) {\n\t\t\t\tif (this.stencilFailFront !== fail || this.stencilZfailFront !== zfail || this.stencilZpassFront !== zpass || this.stencilFailBack !== fail || this.stencilZfailBack !== zfail || this.stencilZpassBack !== zpass) {\n\t\t\t\t\t\tthis.gl.stencilOp(this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);\n\t\t\t\t\t\tthis.stencilFailFront = this.stencilFailBack = fail;\n\t\t\t\t\t\tthis.stencilZfailFront = this.stencilZfailBack = zfail;\n\t\t\t\t\t\tthis.stencilZpassFront = this.stencilZpassBack = zpass;\n\t\t\t\t}\n\t\t\t\tif (this.stencilWriteMaskFront !== writeMask || this.stencilWriteMaskBack !== writeMask) {\n\t\t\t\t\t\tthis.gl.stencilMask(writeMask);\n\t\t\t\t\t\tthis.stencilWriteMaskFront = writeMask;\n\t\t\t\t\t\tthis.stencilWriteMaskBack = writeMask;\n\t\t\t\t}\n\t\t}\n\t\tsetStencilOperationFront(fail, zfail, zpass, writeMask) {\n\t\t\t\tif (this.stencilFailFront !== fail || this.stencilZfailFront !== zfail || this.stencilZpassFront !== zpass) {\n\t\t\t\t\t\tthis.gl.stencilOpSeparate(this.gl.FRONT, this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);\n\t\t\t\t\t\tthis.stencilFailFront = fail;\n\t\t\t\t\t\tthis.stencilZfailFront = zfail;\n\t\t\t\t\t\tthis.stencilZpassFront = zpass;\n\t\t\t\t}\n\t\t\t\tif (this.stencilWriteMaskFront !== writeMask) {\n\t\t\t\t\t\tthis.gl.stencilMaskSeparate(this.gl.FRONT, writeMask);\n\t\t\t\t\t\tthis.stencilWriteMaskFront = writeMask;\n\t\t\t\t}\n\t\t}\n\t\tsetStencilOperationBack(fail, zfail, zpass, writeMask) {\n\t\t\t\tif (this.stencilFailBack !== fail || this.stencilZfailBack !== zfail || this.stencilZpassBack !== zpass) {\n\t\t\t\t\t\tthis.gl.stencilOpSeparate(this.gl.BACK, this.glStencilOp[fail], this.glStencilOp[zfail], this.glStencilOp[zpass]);\n\t\t\t\t\t\tthis.stencilFailBack = fail;\n\t\t\t\t\t\tthis.stencilZfailBack = zfail;\n\t\t\t\t\t\tthis.stencilZpassBack = zpass;\n\t\t\t\t}\n\t\t\t\tif (this.stencilWriteMaskBack !== writeMask) {\n\t\t\t\t\t\tthis.gl.stencilMaskSeparate(this.gl.BACK, writeMask);\n\t\t\t\t\t\tthis.stencilWriteMaskBack = writeMask;\n\t\t\t\t}\n\t\t}\n\t\tsetBlendState(blendState) {\n\t\t\t\tconst currentBlendState = this.blendState;\n\t\t\t\tif (!currentBlendState.equals(blendState)) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tconst { blend, colorOp, alphaOp, colorSrcFactor, colorDstFactor, alphaSrcFactor, alphaDstFactor } = blendState;\n\t\t\t\t\t\tif (currentBlendState.blend !== blend) {\n\t\t\t\t\t\t\t\tif (blend) {\n\t\t\t\t\t\t\t\t\t\tgl.enable(gl.BLEND);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tgl.disable(gl.BLEND);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (currentBlendState.colorOp !== colorOp || currentBlendState.alphaOp !== alphaOp) {\n\t\t\t\t\t\t\t\tconst glBlendEquation = this.glBlendEquation;\n\t\t\t\t\t\t\t\tgl.blendEquationSeparate(glBlendEquation[colorOp], glBlendEquation[alphaOp]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (currentBlendState.colorSrcFactor !== colorSrcFactor || currentBlendState.colorDstFactor !== colorDstFactor || currentBlendState.alphaSrcFactor !== alphaSrcFactor || currentBlendState.alphaDstFactor !== alphaDstFactor) {\n\t\t\t\t\t\t\t\tgl.blendFuncSeparate(this.glBlendFunctionColor[colorSrcFactor], this.glBlendFunctionColor[colorDstFactor], this.glBlendFunctionAlpha[alphaSrcFactor], this.glBlendFunctionAlpha[alphaDstFactor]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (currentBlendState.allWrite !== blendState.allWrite) {\n\t\t\t\t\t\t\t\tthis.gl.colorMask(blendState.redWrite, blendState.greenWrite, blendState.blueWrite, blendState.alphaWrite);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentBlendState.copy(blendState);\n\t\t\t\t}\n\t\t}\n\t\tsetBlendColor(r, g, b, a) {\n\t\t\t\tconst c = this.blendColor;\n\t\t\t\tif (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {\n\t\t\t\t\t\tthis.gl.blendColor(r, g, b, a);\n\t\t\t\t\t\tc.set(r, g, b, a);\n\t\t\t\t}\n\t\t}\n\t\tsetStencilState(stencilFront, stencilBack) {\n\t\t\t\tif (stencilFront || stencilBack) {\n\t\t\t\t\t\tthis.setStencilTest(true);\n\t\t\t\t\t\tif (stencilFront === stencilBack) {\n\t\t\t\t\t\t\t\tthis.setStencilFunc(stencilFront.func, stencilFront.ref, stencilFront.readMask);\n\t\t\t\t\t\t\t\tthis.setStencilOperation(stencilFront.fail, stencilFront.zfail, stencilFront.zpass, stencilFront.writeMask);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tstencilFront ??= StencilParameters.DEFAULT;\n\t\t\t\t\t\t\t\tthis.setStencilFuncFront(stencilFront.func, stencilFront.ref, stencilFront.readMask);\n\t\t\t\t\t\t\t\tthis.setStencilOperationFront(stencilFront.fail, stencilFront.zfail, stencilFront.zpass, stencilFront.writeMask);\n\t\t\t\t\t\t\t\tstencilBack ??= StencilParameters.DEFAULT;\n\t\t\t\t\t\t\t\tthis.setStencilFuncBack(stencilBack.func, stencilBack.ref, stencilBack.readMask);\n\t\t\t\t\t\t\t\tthis.setStencilOperationBack(stencilBack.fail, stencilBack.zfail, stencilBack.zpass, stencilBack.writeMask);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.setStencilTest(false);\n\t\t\t\t}\n\t\t}\n\t\tsetDepthState(depthState) {\n\t\t\t\tconst currentDepthState = this.depthState;\n\t\t\t\tif (!currentDepthState.equals(depthState)) {\n\t\t\t\t\t\tconst gl = this.gl;\n\t\t\t\t\t\tconst write = depthState.write;\n\t\t\t\t\t\tif (currentDepthState.write !== write) {\n\t\t\t\t\t\t\t\tgl.depthMask(write);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet { func, test } = depthState;\n\t\t\t\t\t\tif (!test && write) {\n\t\t\t\t\t\t\t\ttest = true;\n\t\t\t\t\t\t\t\tfunc = FUNC_ALWAYS;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (currentDepthState.func !== func) {\n\t\t\t\t\t\t\t\tgl.depthFunc(this.glComparison[func]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (currentDepthState.test !== test) {\n\t\t\t\t\t\t\t\tif (test) {\n\t\t\t\t\t\t\t\t\t\tgl.enable(gl.DEPTH_TEST);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst { depthBias, depthBiasSlope } = depthState;\n\t\t\t\t\t\tif (depthBias || depthBiasSlope) {\n\t\t\t\t\t\t\t\tif (!this.depthBiasEnabled) {\n\t\t\t\t\t\t\t\t\t\tthis.depthBiasEnabled = true;\n\t\t\t\t\t\t\t\t\t\tthis.gl.enable(this.gl.POLYGON_OFFSET_FILL);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgl.polygonOffset(depthBiasSlope, depthBias);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.depthBiasEnabled) {\n\t\t\t\t\t\t\t\t\t\tthis.depthBiasEnabled = false;\n\t\t\t\t\t\t\t\t\t\tthis.gl.disable(this.gl.POLYGON_OFFSET_FILL);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentDepthState.copy(depthState);\n\t\t\t\t}\n\t\t}\n\t\tsetCullMode(cullMode) {\n\t\t\t\tif (this.cullMode !== cullMode) {\n\t\t\t\t\t\tif (cullMode === CULLFACE_NONE) {\n\t\t\t\t\t\t\t\tthis.gl.disable(this.gl.CULL_FACE);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.cullMode === CULLFACE_NONE) {\n\t\t\t\t\t\t\t\t\t\tthis.gl.enable(this.gl.CULL_FACE);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst mode = this.glCull[cullMode];\n\t\t\t\t\t\t\t\tif (this.cullFace !== mode) {\n\t\t\t\t\t\t\t\t\t\tthis.gl.cullFace(mode);\n\t\t\t\t\t\t\t\t\t\tthis.cullFace = mode;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.cullMode = cullMode;\n\t\t\t\t}\n\t\t}\n\t\tsetShader(shader, asyncCompile = false) {\n\t\t\t\tif (shader !== this.shader) {\n\t\t\t\t\t\tthis.shader = shader;\n\t\t\t\t\t\tthis.shaderAsyncCompile = asyncCompile;\n\t\t\t\t\t\tthis.shaderValid = undefined;\n\t\t\t\t}\n\t\t}\n\t\tactivateShader() {\n\t\t\t\tconst { shader } = this;\n\t\t\t\tconst { impl } = shader;\n\t\t\t\tif (this.shaderValid === undefined) {\n\t\t\t\t\t\tif (shader.failed) {\n\t\t\t\t\t\t\t\tthis.shaderValid = false;\n\t\t\t\t\t\t} else if (!shader.ready) {\n\t\t\t\t\t\t\t\tif (this.shaderAsyncCompile) {\n\t\t\t\t\t\t\t\t\t\tif (impl.isLinked(this)) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!impl.finalize(this, shader)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.shaderValid = false;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.shaderValid = false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!impl.finalize(this, shader)) {\n\t\t\t\t\t\t\t\t\t\t\t\tshader.failed = true;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.shaderValid = false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.shaderValid === undefined) {\n\t\t\t\t\t\tthis.gl.useProgram(impl.glProgram);\n\t\t\t\t\t\tthis.shaderValid = true;\n\t\t\t\t}\n\t\t}\n\t\tclearVertexArrayObjectCache() {\n\t\t\t\tconst gl = this.gl;\n\t\t\t\tthis._vaoMap.forEach((item, key, mapObj)=>{\n\t\t\t\t\t\tgl.deleteVertexArray(item);\n\t\t\t\t});\n\t\t\t\tthis._vaoMap.clear();\n\t\t}\n\t\tset fullscreen(fullscreen) {\n\t\t\t\tif (fullscreen) {\n\t\t\t\t\t\tconst canvas = this.gl.canvas;\n\t\t\t\t\t\tcanvas.requestFullscreen();\n\t\t\t\t} else {\n\t\t\t\t\t\tdocument.exitFullscreen();\n\t\t\t\t}\n\t\t}\n\t\tget fullscreen() {\n\t\t\t\treturn !!document.fullscreenElement;\n\t\t}\n}\n\nclass NullIndexBuffer {\n\t\tunlock(indexBuffer) {}\n}\n\nclass NullRenderTarget {\n\t\tdestroy(device) {}\n\t\tinit(device, renderTarget) {}\n\t\tloseContext() {}\n\t\tresolve(device, target, color, depth) {}\n}\n\nclass NullShader {\n\t\tdestroy(shader) {}\n\t\tloseContext() {}\n\t\trestoreContext(device, shader) {}\n}\n\nclass NullTexture {\n\t\tdestroy(device) {}\n\t\tpropertyChanged(flag) {}\n\t\tloseContext() {}\n}\n\nclass NullVertexBuffer {\n\t\tdestroy(device) {}\n\t\tunlock(vertexBuffer) {}\n}\n\nclass NullGraphicsDevice extends GraphicsDevice {\n\t\tconstructor(canvas, options = {}){\n\t\t\t\tsuper(canvas, options);\n\t\t\t\toptions = this.initOptions;\n\t\t\t\tthis.isNull = true;\n\t\t\t\tthis._deviceType = DEVICETYPE_NULL;\n\t\t\t\tthis.samples = 1;\n\t\t\t\tthis.backBuffer = new RenderTarget({\n\t\t\t\t\t\tname: 'Framebuffer',\n\t\t\t\t\t\tgraphicsDevice: this,\n\t\t\t\t\t\tdepth: this.initOptions.depth,\n\t\t\t\t\t\tstencil: this.supportsStencil,\n\t\t\t\t\t\tsamples: this.samples\n\t\t\t\t});\n\t\t\t\tthis.initDeviceCaps();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t}\n\t\tinitDeviceCaps() {\n\t\t\t\tthis.disableParticleSystem = true;\n\t\t\t\tthis.precision = 'highp';\n\t\t\t\tthis.maxPrecision = 'highp';\n\t\t\t\tthis.maxSamples = 4;\n\t\t\t\tthis.maxTextures = 16;\n\t\t\t\tthis.maxTextureSize = 4096;\n\t\t\t\tthis.maxCubeMapSize = 4096;\n\t\t\t\tthis.maxVolumeSize = 4096;\n\t\t\t\tthis.maxColorAttachments = 8;\n\t\t\t\tthis.maxPixelRatio = 1;\n\t\t\t\tthis.maxAnisotropy = 16;\n\t\t\t\tthis.supportsUniformBuffers = false;\n\t\t\t\tthis.supportsAreaLights = true;\n\t\t\t\tthis.supportsGpuParticles = false;\n\t\t\t\tthis.textureFloatRenderable = true;\n\t\t\t\tthis.textureHalfFloatRenderable = true;\n\t\t\t\tthis.supportsImageBitmap = false;\n\t\t}\n\t\tpostInit() {\n\t\t\t\tsuper.postInit();\n\t\t}\n\t\tframeStart() {\n\t\t\t\tsuper.frameStart();\n\t\t}\n\t\tframeEnd() {\n\t\t\t\tsuper.frameEnd();\n\t\t}\n\t\tupdateBegin() {}\n\t\tupdateEnd() {}\n\t\treadPixels(x, y, w, h, pixels) {}\n\t\tcreateVertexBufferImpl(vertexBuffer, format) {\n\t\t\t\treturn new NullVertexBuffer(vertexBuffer, format);\n\t\t}\n\t\tcreateIndexBufferImpl(indexBuffer) {\n\t\t\t\treturn new NullIndexBuffer(indexBuffer);\n\t\t}\n\t\tcreateShaderImpl(shader) {\n\t\t\t\treturn new NullShader(shader);\n\t\t}\n\t\tcreateTextureImpl(texture) {\n\t\t\t\treturn new NullTexture(texture);\n\t\t}\n\t\tcreateRenderTargetImpl(renderTarget) {\n\t\t\t\treturn new NullRenderTarget(renderTarget);\n\t\t}\n\t\tdraw(primitive, indexBuffer, numInstances, indirectSlot, first = true, last = true) {}\n\t\tsetShader(shader, asyncCompile = false) {}\n\t\tsetBlendState(blendState) {}\n\t\tsetDepthState(depthState) {}\n\t\tsetStencilState(stencilFront, stencilBack) {}\n\t\tsetBlendColor(r, g, b, a) {}\n\t\tsetCullMode(cullMode) {}\n\t\tsetAlphaToCoverage(state) {}\n\t\tinitializeContextCaches() {\n\t\t\t\tsuper.initializeContextCaches();\n\t\t}\n\t\tclear(options) {}\n\t\tsetViewport(x, y, w, h) {}\n\t\tsetScissor(x, y, w, h) {}\n\t\tcopyRenderTarget(source, dest, color, depth) {\n\t\t\t\treturn true;\n\t\t}\n}\n\nfunction createGraphicsDevice(canvas, options = {}) {\n\t\tconst deviceTypes = options.deviceTypes ?? [];\n\t\tif (!deviceTypes.includes(DEVICETYPE_WEBGL2)) {\n\t\t\t\tdeviceTypes.push(DEVICETYPE_WEBGL2);\n\t\t}\n\t\tif (!deviceTypes.includes(DEVICETYPE_NULL)) {\n\t\t\t\tdeviceTypes.push(DEVICETYPE_NULL);\n\t\t}\n\t\tif (platform.browser && !!navigator.xr) {\n\t\t\t\toptions.xrCompatible ??= true;\n\t\t}\n\t\tconst deviceCreateFuncs = [];\n\t\tfor(let i = 0; i < deviceTypes.length; i++){\n\t\t\t\tconst deviceType = deviceTypes[i];\n\t\t\t\tif (deviceType === DEVICETYPE_WEBGPU && window?.navigator?.gpu) {\n\t\t\t\t\t\tdeviceCreateFuncs.push(()=>{\n\t\t\t\t\t\t\t\tconst device = new WebgpuGraphicsDevice(canvas, options);\n\t\t\t\t\t\t\t\treturn device.initWebGpu(options.glslangUrl, options.twgslUrl);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (deviceType === DEVICETYPE_WEBGL2) {\n\t\t\t\t\t\tdeviceCreateFuncs.push(()=>{\n\t\t\t\t\t\t\t\treturn new WebglGraphicsDevice(canvas, options);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (deviceType === DEVICETYPE_NULL) {\n\t\t\t\t\t\tdeviceCreateFuncs.push(()=>{\n\t\t\t\t\t\t\t\treturn new NullGraphicsDevice(canvas, options);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\tlet attempt = 0;\n\t\t\t\tconst next = ()=>{\n\t\t\t\t\t\tif (attempt >= deviceCreateFuncs.length) {\n\t\t\t\t\t\t\t\treject(new Error('Failed to create a graphics device'));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tPromise.resolve(deviceCreateFuncs[attempt++]()).then((device)=>{\n\t\t\t\t\t\t\t\t\t\tif (device) {\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(device);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tnext();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\t\t\t\t\tconsole.log(err);\n\t\t\t\t\t\t\t\t\t\tnext();\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tnext();\n\t\t});\n}\n\nlet id$6 = 0;\nclass IndexBuffer {\n\t\tconstructor(graphicsDevice, format, numIndices, usage = BUFFER_STATIC, initialData, options){\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.format = format;\n\t\t\t\tthis.numIndices = numIndices;\n\t\t\t\tthis.usage = usage;\n\t\t\t\tthis.id = id$6++;\n\t\t\t\tthis.impl = graphicsDevice.createIndexBufferImpl(this, options);\n\t\t\t\tconst bytesPerIndex = typedArrayIndexFormatsByteSize[format];\n\t\t\t\tthis.bytesPerIndex = bytesPerIndex;\n\t\t\t\tthis.numBytes = this.numIndices * bytesPerIndex;\n\t\t\t\tif (initialData) {\n\t\t\t\t\t\tthis.setData(initialData);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.storage = new ArrayBuffer(this.numBytes);\n\t\t\t\t}\n\t\t\t\tthis.adjustVramSizeTracking(graphicsDevice._vram, this.numBytes);\n\t\t\t\tthis.device.buffers.push(this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst idx = device.buffers.indexOf(this);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tdevice.buffers.splice(idx, 1);\n\t\t\t\t}\n\t\t\t\tif (this.device.indexBuffer === this) {\n\t\t\t\t\t\tthis.device.indexBuffer = null;\n\t\t\t\t}\n\t\t\t\tif (this.impl.initialized) {\n\t\t\t\t\t\tthis.impl.destroy(device);\n\t\t\t\t\t\tthis.adjustVramSizeTracking(device._vram, -this.storage.byteLength);\n\t\t\t\t}\n\t\t}\n\t\tadjustVramSizeTracking(vram, size) {\n\t\t\t\tvram.ib += size;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.impl.loseContext();\n\t\t}\n\t\tgetFormat() {\n\t\t\t\treturn this.format;\n\t\t}\n\t\tgetNumIndices() {\n\t\t\t\treturn this.numIndices;\n\t\t}\n\t\tlock() {\n\t\t\t\treturn this.storage;\n\t\t}\n\t\tunlock() {\n\t\t\t\tthis.impl.unlock(this);\n\t\t}\n\t\tsetData(data) {\n\t\t\t\tif (data.byteLength !== this.numBytes) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis.storage = data;\n\t\t\t\tthis.unlock();\n\t\t\t\treturn true;\n\t\t}\n\t\t_lockTypedArray() {\n\t\t\t\tconst lock = this.lock();\n\t\t\t\tconst indices = this.format === INDEXFORMAT_UINT32 ? new Uint32Array(lock) : this.format === INDEXFORMAT_UINT16 ? new Uint16Array(lock) : new Uint8Array(lock);\n\t\t\t\treturn indices;\n\t\t}\n\t\twriteData(data, count) {\n\t\t\t\tconst indices = this._lockTypedArray();\n\t\t\t\tif (data.length > count) {\n\t\t\t\t\t\tif (ArrayBuffer.isView(data)) {\n\t\t\t\t\t\t\t\tdata = data.subarray(0, count);\n\t\t\t\t\t\t\t\tindices.set(data);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\t\t\tindices[i] = data[i];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tindices.set(data);\n\t\t\t\t}\n\t\t\t\tthis.unlock();\n\t\t}\n\t\treadData(data) {\n\t\t\t\tconst indices = this._lockTypedArray();\n\t\t\t\tconst count = this.numIndices;\n\t\t\t\tif (ArrayBuffer.isView(data)) {\n\t\t\t\t\t\tdata.set(indices);\n\t\t\t\t} else {\n\t\t\t\t\t\tdata.length = 0;\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\tdata[i] = indices[i];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn count;\n\t\t}\n}\n\nclass ColorAttachmentOps {\n\t\tconstructor(){\n\t\t\t\tthis.clearValue = new Color(0, 0, 0, 1);\n\t\t\t\tthis.clearValueLinear = new Color(0, 0, 0, 1);\n\t\t\t\tthis.clear = false;\n\t\t\t\tthis.store = false;\n\t\t\t\tthis.resolve = true;\n\t\t\t\tthis.genMipmaps = false;\n\t\t}\n}\nclass DepthStencilAttachmentOps {\n\t\tconstructor(){\n\t\t\t\tthis.clearDepthValue = 1;\n\t\t\t\tthis.clearStencilValue = 0;\n\t\t\t\tthis.clearDepth = false;\n\t\t\t\tthis.clearStencil = false;\n\t\t\t\tthis.storeDepth = false;\n\t\t\t\tthis.resolveDepth = false;\n\t\t\t\tthis.storeStencil = false;\n\t\t}\n}\nclass RenderPass {\n\t\tget colorOps() {\n\t\t\t\treturn this.colorArrayOps[0];\n\t\t}\n\t\tconstructor(graphicsDevice){\n\t\t\t\tthis._enabled = true;\n\t\t\t\tthis._skipStart = false;\n\t\t\t\tthis._skipEnd = false;\n\t\t\t\tthis.executeEnabled = true;\n\t\t\t\tthis.samples = 0;\n\t\t\t\tthis.colorArrayOps = [];\n\t\t\t\tthis.requiresCubemaps = true;\n\t\t\t\tthis.fullSizeClearRect = true;\n\t\t\t\tthis.beforePasses = [];\n\t\t\t\tthis.afterPasses = [];\n\t\t\t\tthis.device = graphicsDevice;\n\t\t}\n\t\tset name(value) {\n\t\t\t\tthis._name = value;\n\t\t}\n\t\tget name() {\n\t\t\t\tif (!this._name) {\n\t\t\t\t\t\tthis._name = this.constructor.name;\n\t\t\t\t}\n\t\t\t\treturn this._name;\n\t\t}\n\t\tset scaleX(value) {\n\t\t\t\tthis._options.scaleX = value;\n\t\t}\n\t\tget scaleX() {\n\t\t\t\treturn this._options.scaleX;\n\t\t}\n\t\tset scaleY(value) {\n\t\t\t\tthis._options.scaleY = value;\n\t\t}\n\t\tget scaleY() {\n\t\t\t\treturn this._options.scaleY;\n\t\t}\n\t\tset options(value) {\n\t\t\t\tthis._options = value;\n\t\t\t\tif (value) {\n\t\t\t\t\t\tthis.scaleX = this.scaleX ?? 1;\n\t\t\t\t\t\tthis.scaleY = this.scaleY ?? 1;\n\t\t\t\t}\n\t\t}\n\t\tget options() {\n\t\t\t\treturn this._options;\n\t\t}\n\t\tinit(renderTarget = null, options) {\n\t\t\t\tthis.options = options;\n\t\t\t\tthis.renderTarget = renderTarget;\n\t\t\t\tthis.samples = Math.max(this.renderTarget ? this.renderTarget.samples : this.device.samples, 1);\n\t\t\t\tthis.allocateAttachments();\n\t\t\t\tthis.postInit();\n\t\t}\n\t\tallocateAttachments() {\n\t\t\t\tconst rt = this.renderTarget;\n\t\t\t\tthis.depthStencilOps = new DepthStencilAttachmentOps();\n\t\t\t\tif (rt?.depthBuffer) {\n\t\t\t\t\t\tthis.depthStencilOps.storeDepth = true;\n\t\t\t\t}\n\t\t\t\tconst numColorOps = rt ? rt._colorBuffers?.length ?? 0 : 1;\n\t\t\t\tthis.colorArrayOps.length = 0;\n\t\t\t\tfor(let i = 0; i < numColorOps; i++){\n\t\t\t\t\t\tconst colorOps = new ColorAttachmentOps();\n\t\t\t\t\t\tthis.colorArrayOps[i] = colorOps;\n\t\t\t\t\t\tif (this.samples === 1) {\n\t\t\t\t\t\t\t\tcolorOps.store = true;\n\t\t\t\t\t\t\t\tcolorOps.resolve = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst colorBuffer = this.renderTarget?._colorBuffers?.[i];\n\t\t\t\t\t\tif (this.renderTarget?.mipmaps && colorBuffer?.mipmaps) {\n\t\t\t\t\t\t\t\tconst intFormat = isIntegerPixelFormat(colorBuffer._format);\n\t\t\t\t\t\t\t\tcolorOps.genMipmaps = !intFormat;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {}\n\t\tpostInit() {}\n\t\tframeUpdate() {\n\t\t\t\tif (this._options && this.renderTarget) {\n\t\t\t\t\t\tconst resizeSource = this._options.resizeSource ?? this.device.backBuffer;\n\t\t\t\t\t\tconst width = Math.floor(resizeSource.width * this.scaleX);\n\t\t\t\t\t\tconst height = Math.floor(resizeSource.height * this.scaleY);\n\t\t\t\t\t\tthis.renderTarget.resize(width, height);\n\t\t\t\t}\n\t\t}\n\t\tbefore() {}\n\t\texecute() {}\n\t\tafter() {}\n\t\tonEnable() {}\n\t\tonDisable() {}\n\t\tset enabled(value) {\n\t\t\t\tif (this._enabled !== value) {\n\t\t\t\t\t\tthis._enabled = value;\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\tthis.onEnable();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.onDisable();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled;\n\t\t}\n\t\tsetClearColor(color) {\n\t\t\t\tconst count = this.colorArrayOps.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst colorOps = this.colorArrayOps[i];\n\t\t\t\t\t\tif (color) {\n\t\t\t\t\t\t\t\tcolorOps.clearValue.copy(color);\n\t\t\t\t\t\t\t\tcolorOps.clearValueLinear.linear(color);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcolorOps.clear = !!color;\n\t\t\t\t}\n\t\t}\n\t\tsetClearDepth(depthValue) {\n\t\t\t\tif (depthValue) {\n\t\t\t\t\t\tthis.depthStencilOps.clearDepthValue = depthValue;\n\t\t\t\t}\n\t\t\t\tthis.depthStencilOps.clearDepth = depthValue !== undefined;\n\t\t}\n\t\tsetClearStencil(stencilValue) {\n\t\t\t\tif (stencilValue) {\n\t\t\t\t\t\tthis.depthStencilOps.clearStencilValue = stencilValue;\n\t\t\t\t}\n\t\t\t\tthis.depthStencilOps.clearStencil = stencilValue !== undefined;\n\t\t}\n\t\trender() {\n\t\t\t\tif (this.enabled) {\n\t\t\t\t\t\tconst device = this.device;\n\t\t\t\t\t\tconst realPass = this.renderTarget !== undefined;\n\t\t\t\t\t\tthis.before();\n\t\t\t\t\t\tif (this.executeEnabled) {\n\t\t\t\t\t\t\t\tif (realPass && !this._skipStart) {\n\t\t\t\t\t\t\t\t\t\tdevice.startRenderPass(this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.execute();\n\t\t\t\t\t\t\t\tif (realPass && !this._skipEnd) {\n\t\t\t\t\t\t\t\t\t\tdevice.endRenderPass(this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.after();\n\t\t\t\t\t\tdevice.renderPassIndex++;\n\t\t\t\t}\n\t\t}\n}\n\nfunction set1(a) {\n\t\tthis.array[this.index] = a;\n}\nfunction set2(a, b) {\n\t\tthis.array[this.index] = a;\n\t\tthis.array[this.index + 1] = b;\n}\nfunction set3(a, b, c) {\n\t\tthis.array[this.index] = a;\n\t\tthis.array[this.index + 1] = b;\n\t\tthis.array[this.index + 2] = c;\n}\nfunction set4(a, b, c, d) {\n\t\tthis.array[this.index] = a;\n\t\tthis.array[this.index + 1] = b;\n\t\tthis.array[this.index + 2] = c;\n\t\tthis.array[this.index + 3] = d;\n}\nfunction arraySet1(index, inputArray, inputIndex) {\n\t\tthis.array[index] = inputArray[inputIndex];\n}\nfunction arraySet2(index, inputArray, inputIndex) {\n\t\tthis.array[index] = inputArray[inputIndex];\n\t\tthis.array[index + 1] = inputArray[inputIndex + 1];\n}\nfunction arraySet3(index, inputArray, inputIndex) {\n\t\tthis.array[index] = inputArray[inputIndex];\n\t\tthis.array[index + 1] = inputArray[inputIndex + 1];\n\t\tthis.array[index + 2] = inputArray[inputIndex + 2];\n}\nfunction arraySet4(index, inputArray, inputIndex) {\n\t\tthis.array[index] = inputArray[inputIndex];\n\t\tthis.array[index + 1] = inputArray[inputIndex + 1];\n\t\tthis.array[index + 2] = inputArray[inputIndex + 2];\n\t\tthis.array[index + 3] = inputArray[inputIndex + 3];\n}\nfunction arrayGet1(offset, outputArray, outputIndex) {\n\t\toutputArray[outputIndex] = this.array[offset];\n}\nfunction arrayGet2(offset, outputArray, outputIndex) {\n\t\toutputArray[outputIndex] = this.array[offset];\n\t\toutputArray[outputIndex + 1] = this.array[offset + 1];\n}\nfunction arrayGet3(offset, outputArray, outputIndex) {\n\t\toutputArray[outputIndex] = this.array[offset];\n\t\toutputArray[outputIndex + 1] = this.array[offset + 1];\n\t\toutputArray[outputIndex + 2] = this.array[offset + 2];\n}\nfunction arrayGet4(offset, outputArray, outputIndex) {\n\t\toutputArray[outputIndex] = this.array[offset];\n\t\toutputArray[outputIndex + 1] = this.array[offset + 1];\n\t\toutputArray[outputIndex + 2] = this.array[offset + 2];\n\t\toutputArray[outputIndex + 3] = this.array[offset + 3];\n}\nclass VertexIteratorAccessor {\n\t\tconstructor(buffer, vertexElement, vertexFormat){\n\t\t\t\tthis.index = 0;\n\t\t\t\tthis.numComponents = vertexElement.numComponents;\n\t\t\t\tif (vertexFormat.interleaved) {\n\t\t\t\t\t\tthis.array = new typedArrayTypes[vertexElement.dataType](buffer, vertexElement.offset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.array = new typedArrayTypes[vertexElement.dataType](buffer, vertexElement.offset, vertexFormat.vertexCount * vertexElement.numComponents);\n\t\t\t\t}\n\t\t\t\tthis.stride = vertexElement.stride / this.array.constructor.BYTES_PER_ELEMENT;\n\t\t\t\tswitch(vertexElement.numComponents){\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\tthis.set = set1;\n\t\t\t\t\t\t\t\tthis.getToArray = arrayGet1;\n\t\t\t\t\t\t\t\tthis.setFromArray = arraySet1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\tthis.set = set2;\n\t\t\t\t\t\t\t\tthis.getToArray = arrayGet2;\n\t\t\t\t\t\t\t\tthis.setFromArray = arraySet2;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 3:\n\t\t\t\t\t\t\t\tthis.set = set3;\n\t\t\t\t\t\t\t\tthis.getToArray = arrayGet3;\n\t\t\t\t\t\t\t\tthis.setFromArray = arraySet3;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 4:\n\t\t\t\t\t\t\t\tthis.set = set4;\n\t\t\t\t\t\t\t\tthis.getToArray = arrayGet4;\n\t\t\t\t\t\t\t\tthis.setFromArray = arraySet4;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\tget(offset) {\n\t\t\t\treturn this.array[this.index + offset];\n\t\t}\n\t\tset(a, b, c, d) {}\n\t\tgetToArray(offset, outputArray, outputIndex) {}\n\t\tsetFromArray(index, inputArray, inputIndex) {}\n}\nclass VertexIterator {\n\t\tconstructor(vertexBuffer){\n\t\t\t\tthis.vertexBuffer = vertexBuffer;\n\t\t\t\tthis.vertexFormatSize = vertexBuffer.getFormat().size;\n\t\t\t\tthis.buffer = this.vertexBuffer.lock();\n\t\t\t\tthis.accessors = [];\n\t\t\t\tthis.element = {};\n\t\t\t\tconst vertexFormat = this.vertexBuffer.getFormat();\n\t\t\t\tfor(let i = 0; i < vertexFormat.elements.length; i++){\n\t\t\t\t\t\tconst vertexElement = vertexFormat.elements[i];\n\t\t\t\t\t\tthis.accessors[i] = new VertexIteratorAccessor(this.buffer, vertexElement, vertexFormat);\n\t\t\t\t\t\tthis.element[vertexElement.name] = this.accessors[i];\n\t\t\t\t}\n\t\t}\n\t\tnext(count = 1) {\n\t\t\t\tlet i = 0;\n\t\t\t\tconst accessors = this.accessors;\n\t\t\t\tconst numAccessors = this.accessors.length;\n\t\t\t\twhile(i < numAccessors){\n\t\t\t\t\t\tconst accessor = accessors[i++];\n\t\t\t\t\t\taccessor.index += count * accessor.stride;\n\t\t\t\t}\n\t\t}\n\t\tend() {\n\t\t\t\tthis.vertexBuffer.unlock();\n\t\t}\n\t\twriteData(semantic, data, numVertices) {\n\t\t\t\tconst element = this.element[semantic];\n\t\t\t\tif (element) {\n\t\t\t\t\t\tif (numVertices > this.vertexBuffer.numVertices) {\n\t\t\t\t\t\t\t\tnumVertices = this.vertexBuffer.numVertices;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst numComponents = element.numComponents;\n\t\t\t\t\t\tif (this.vertexBuffer.getFormat().interleaved) {\n\t\t\t\t\t\t\t\tlet index = 0;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < numVertices; i++){\n\t\t\t\t\t\t\t\t\t\telement.setFromArray(index, data, i * numComponents);\n\t\t\t\t\t\t\t\t\t\tindex += element.stride;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (data.length > numVertices * numComponents) {\n\t\t\t\t\t\t\t\t\t\tconst copyCount = numVertices * numComponents;\n\t\t\t\t\t\t\t\t\t\tif (ArrayBuffer.isView(data)) {\n\t\t\t\t\t\t\t\t\t\t\t\tdata = data.subarray(0, copyCount);\n\t\t\t\t\t\t\t\t\t\t\t\telement.array.set(data);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < copyCount; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\telement.array[i] = data[i];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\telement.array.set(data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\treadData(semantic, data) {\n\t\t\t\tconst element = this.element[semantic];\n\t\t\t\tlet count = 0;\n\t\t\t\tif (element) {\n\t\t\t\t\t\tcount = this.vertexBuffer.numVertices;\n\t\t\t\t\t\tlet i;\n\t\t\t\t\t\tconst numComponents = element.numComponents;\n\t\t\t\t\t\tif (this.vertexBuffer.getFormat().interleaved) {\n\t\t\t\t\t\t\t\tif (Array.isArray(data)) {\n\t\t\t\t\t\t\t\t\t\tdata.length = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telement.index = 0;\n\t\t\t\t\t\t\t\tlet offset = 0;\n\t\t\t\t\t\t\t\tfor(i = 0; i < count; i++){\n\t\t\t\t\t\t\t\t\t\telement.getToArray(offset, data, i * numComponents);\n\t\t\t\t\t\t\t\t\t\toffset += element.stride;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (ArrayBuffer.isView(data)) {\n\t\t\t\t\t\t\t\t\t\tdata.set(element.array);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdata.length = 0;\n\t\t\t\t\t\t\t\t\t\tconst copyCount = count * numComponents;\n\t\t\t\t\t\t\t\t\t\tfor(i = 0; i < copyCount; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tdata[i] = element.array[i];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn count;\n\t\t}\n}\n\nconst MOUSEBUTTON_NONE = -1;\n\nclass KeyboardEvent {\n\t\tconstructor(keyboard, event){\n\t\t\t\tthis.key = null;\n\t\t\t\tthis.element = null;\n\t\t\t\tthis.event = null;\n\t\t\t\tif (event) {\n\t\t\t\t\t\tthis.key = event.keyCode;\n\t\t\t\t\t\tthis.element = event.target;\n\t\t\t\t\t\tthis.event = event;\n\t\t\t\t}\n\t\t}\n}\n\nconst _keyboardEvent = new KeyboardEvent();\nfunction makeKeyboardEvent(event) {\n\t\t_keyboardEvent.key = event.keyCode;\n\t\t_keyboardEvent.element = event.target;\n\t\t_keyboardEvent.event = event;\n\t\treturn _keyboardEvent;\n}\nfunction toKeyCode(s) {\n\t\tif (typeof s === 'string') {\n\t\t\t\treturn s.toUpperCase().charCodeAt(0);\n\t\t}\n\t\treturn s;\n}\nconst _keyCodeToKeyIdentifier = {\n\t\t'9': 'Tab',\n\t\t'13': 'Enter',\n\t\t'16': 'Shift',\n\t\t'17': 'Control',\n\t\t'18': 'Alt',\n\t\t'27': 'Escape',\n\t\t'37': 'Left',\n\t\t'38': 'Up',\n\t\t'39': 'Right',\n\t\t'40': 'Down',\n\t\t'46': 'Delete',\n\t\t'91': 'Win'\n};\nclass Keyboard extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_KEYDOWN = 'keydown';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_KEYUP = 'keyup';\n\t\t}\n\t\tconstructor(element, options = {}){\n\t\t\t\tsuper(), this._element = null, this._keymap = {}, this._lastmap = {};\n\t\t\t\tthis._keyDownHandler = this._handleKeyDown.bind(this);\n\t\t\t\tthis._keyUpHandler = this._handleKeyUp.bind(this);\n\t\t\t\tthis._keyPressHandler = this._handleKeyPress.bind(this);\n\t\t\t\tthis._visibilityChangeHandler = this._handleVisibilityChange.bind(this);\n\t\t\t\tthis._windowBlurHandler = this._handleWindowBlur.bind(this);\n\t\t\t\tif (element) {\n\t\t\t\t\t\tthis.attach(element);\n\t\t\t\t}\n\t\t\t\tthis.preventDefault = options.preventDefault || false;\n\t\t\t\tthis.stopPropagation = options.stopPropagation || false;\n\t\t}\n\t\tattach(element) {\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis.detach();\n\t\t\t\t}\n\t\t\t\tthis._element = element;\n\t\t\t\tthis._element.addEventListener('keydown', this._keyDownHandler, false);\n\t\t\t\tthis._element.addEventListener('keypress', this._keyPressHandler, false);\n\t\t\t\tthis._element.addEventListener('keyup', this._keyUpHandler, false);\n\t\t\t\tdocument.addEventListener('visibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\twindow.addEventListener('blur', this._windowBlurHandler, false);\n\t\t}\n\t\tdetach() {\n\t\t\t\tif (!this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element.removeEventListener('keydown', this._keyDownHandler);\n\t\t\t\tthis._element.removeEventListener('keypress', this._keyPressHandler);\n\t\t\t\tthis._element.removeEventListener('keyup', this._keyUpHandler);\n\t\t\t\tthis._element = null;\n\t\t\t\tdocument.removeEventListener('visibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\twindow.removeEventListener('blur', this._windowBlurHandler, false);\n\t\t}\n\t\ttoKeyIdentifier(keyCode) {\n\t\t\t\tkeyCode = toKeyCode(keyCode);\n\t\t\t\tconst id = _keyCodeToKeyIdentifier[keyCode.toString()];\n\t\t\t\tif (id) {\n\t\t\t\t\t\treturn id;\n\t\t\t\t}\n\t\t\t\tlet hex = keyCode.toString(16).toUpperCase();\n\t\t\t\tconst length = hex.length;\n\t\t\t\tfor(let count = 0; count < 4 - length; count++){\n\t\t\t\t\t\thex = `0${hex}`;\n\t\t\t\t}\n\t\t\t\treturn `U+${hex}`;\n\t\t}\n\t\t_handleKeyDown(event) {\n\t\t\t\tconst code = event.keyCode || event.charCode;\n\t\t\t\tif (code === undefined) return;\n\t\t\t\tconst id = this.toKeyIdentifier(code);\n\t\t\t\tthis._keymap[id] = true;\n\t\t\t\tthis.fire('keydown', makeKeyboardEvent(event));\n\t\t\t\tif (this.preventDefault) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t\tif (this.stopPropagation) {\n\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}\n\t\t}\n\t\t_handleKeyUp(event) {\n\t\t\t\tconst code = event.keyCode || event.charCode;\n\t\t\t\tif (code === undefined) return;\n\t\t\t\tconst id = this.toKeyIdentifier(code);\n\t\t\t\tdelete this._keymap[id];\n\t\t\t\tthis.fire('keyup', makeKeyboardEvent(event));\n\t\t\t\tif (this.preventDefault) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t\tif (this.stopPropagation) {\n\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}\n\t\t}\n\t\t_handleKeyPress(event) {\n\t\t\t\tthis.fire('keypress', makeKeyboardEvent(event));\n\t\t\t\tif (this.preventDefault) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t\tif (this.stopPropagation) {\n\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}\n\t\t}\n\t\t_handleVisibilityChange() {\n\t\t\t\tif (document.visibilityState === 'hidden') {\n\t\t\t\t\t\tthis._handleWindowBlur();\n\t\t\t\t}\n\t\t}\n\t\t_handleWindowBlur() {\n\t\t\t\tthis._keymap = {};\n\t\t\t\tthis._lastmap = {};\n\t\t}\n\t\tupdate() {\n\t\t\t\tfor(const prop in this._lastmap){\n\t\t\t\t\t\tdelete this._lastmap[prop];\n\t\t\t\t}\n\t\t\t\tfor(const prop in this._keymap){\n\t\t\t\t\t\tif (this._keymap.hasOwnProperty(prop)) {\n\t\t\t\t\t\t\t\tthis._lastmap[prop] = this._keymap[prop];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tisPressed(key) {\n\t\t\t\tconst keyCode = toKeyCode(key);\n\t\t\t\tconst id = this.toKeyIdentifier(keyCode);\n\t\t\t\treturn !!this._keymap[id];\n\t\t}\n\t\twasPressed(key) {\n\t\t\t\tconst keyCode = toKeyCode(key);\n\t\t\t\tconst id = this.toKeyIdentifier(keyCode);\n\t\t\t\treturn !!this._keymap[id] && !!!this._lastmap[id];\n\t\t}\n\t\twasReleased(key) {\n\t\t\t\tconst keyCode = toKeyCode(key);\n\t\t\t\tconst id = this.toKeyIdentifier(keyCode);\n\t\t\t\treturn !!!this._keymap[id] && !!this._lastmap[id];\n\t\t}\n}\n\nfunction isMousePointerLocked() {\n\t\treturn !!(document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement);\n}\nclass MouseEvent {\n\t\tconstructor(mouse, event){\n\t\t\t\tthis.x = 0;\n\t\t\t\tthis.y = 0;\n\t\t\t\tthis.dx = 0;\n\t\t\t\tthis.dy = 0;\n\t\t\t\tthis.button = MOUSEBUTTON_NONE;\n\t\t\t\tthis.wheelDelta = 0;\n\t\t\t\tthis.ctrlKey = false;\n\t\t\t\tthis.altKey = false;\n\t\t\t\tthis.shiftKey = false;\n\t\t\t\tthis.metaKey = false;\n\t\t\t\tlet coords = {\n\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\ty: 0\n\t\t\t\t};\n\t\t\t\tif (event) {\n\t\t\t\t\t\tif (event instanceof MouseEvent) {\n\t\t\t\t\t\t\t\tthrow Error('Expected MouseEvent');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcoords = mouse._getTargetCoords(event);\n\t\t\t\t} else {\n\t\t\t\t\t\tevent = {};\n\t\t\t\t}\n\t\t\t\tif (coords) {\n\t\t\t\t\t\tthis.x = coords.x;\n\t\t\t\t\t\tthis.y = coords.y;\n\t\t\t\t} else if (isMousePointerLocked()) {\n\t\t\t\t\t\tthis.x = 0;\n\t\t\t\t\t\tthis.y = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (event.type === 'wheel') {\n\t\t\t\t\t\tif (event.deltaY > 0) {\n\t\t\t\t\t\t\t\tthis.wheelDelta = 1;\n\t\t\t\t\t\t} else if (event.deltaY < 0) {\n\t\t\t\t\t\t\t\tthis.wheelDelta = -1;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (isMousePointerLocked()) {\n\t\t\t\t\t\tthis.dx = event.movementX || event.webkitMovementX || event.mozMovementX || 0;\n\t\t\t\t\t\tthis.dy = event.movementY || event.webkitMovementY || event.mozMovementY || 0;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.dx = this.x - mouse._lastX;\n\t\t\t\t\t\tthis.dy = this.y - mouse._lastY;\n\t\t\t\t}\n\t\t\t\tif (event.type === 'mousedown' || event.type === 'mouseup') {\n\t\t\t\t\t\tthis.button = event.button;\n\t\t\t\t}\n\t\t\t\tthis.buttons = mouse._buttons.slice(0);\n\t\t\t\tthis.element = event.target;\n\t\t\t\tthis.ctrlKey = event.ctrlKey ?? false;\n\t\t\t\tthis.altKey = event.altKey ?? false;\n\t\t\t\tthis.shiftKey = event.shiftKey ?? false;\n\t\t\t\tthis.metaKey = event.metaKey ?? false;\n\t\t\t\tthis.event = event;\n\t\t}\n}\n\nclass Mouse extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEMOVE = 'mousemove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEDOWN = 'mousedown';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEUP = 'mouseup';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEWHEEL = 'mousewheel';\n\t\t}\n\t\tconstructor(element){\n\t\t\t\tsuper(), this._lastX = 0, this._lastY = 0, this._buttons = [\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse\n\t\t\t\t], this._lastbuttons = [\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse\n\t\t\t\t], this._target = null, this._attached = false;\n\t\t\t\tthis._upHandler = this._handleUp.bind(this);\n\t\t\t\tthis._downHandler = this._handleDown.bind(this);\n\t\t\t\tthis._moveHandler = this._handleMove.bind(this);\n\t\t\t\tthis._wheelHandler = this._handleWheel.bind(this);\n\t\t\t\tthis._contextMenuHandler = (event)=>{\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t};\n\t\t\t\tthis.attach(element);\n\t\t}\n\t\tstatic isPointerLocked() {\n\t\t\t\treturn isMousePointerLocked();\n\t\t}\n\t\tattach(element) {\n\t\t\t\tthis._target = element;\n\t\t\t\tif (this._attached) return;\n\t\t\t\tthis._attached = true;\n\t\t\t\tconst passiveOptions = {\n\t\t\t\t\t\tpassive: false\n\t\t\t\t};\n\t\t\t\tconst options = platform.passiveEvents ? passiveOptions : false;\n\t\t\t\twindow.addEventListener('mouseup', this._upHandler, options);\n\t\t\t\twindow.addEventListener('mousedown', this._downHandler, options);\n\t\t\t\twindow.addEventListener('mousemove', this._moveHandler, options);\n\t\t\t\twindow.addEventListener('wheel', this._wheelHandler, options);\n\t\t}\n\t\tdetach() {\n\t\t\t\tif (!this._attached) return;\n\t\t\t\tthis._attached = false;\n\t\t\t\tthis._target = null;\n\t\t\t\tconst passiveOptions = {\n\t\t\t\t\t\tpassive: false\n\t\t\t\t};\n\t\t\t\tconst options = platform.passiveEvents ? passiveOptions : false;\n\t\t\t\twindow.removeEventListener('mouseup', this._upHandler, options);\n\t\t\t\twindow.removeEventListener('mousedown', this._downHandler, options);\n\t\t\t\twindow.removeEventListener('mousemove', this._moveHandler, options);\n\t\t\t\twindow.removeEventListener('wheel', this._wheelHandler, options);\n\t\t}\n\t\tdisableContextMenu() {\n\t\t\t\tif (!this._target) return;\n\t\t\t\tthis._target.addEventListener('contextmenu', this._contextMenuHandler);\n\t\t}\n\t\tenableContextMenu() {\n\t\t\t\tif (!this._target) return;\n\t\t\t\tthis._target.removeEventListener('contextmenu', this._contextMenuHandler);\n\t\t}\n\t\tenablePointerLock(success, error) {\n\t\t\t\tif (!document.body.requestPointerLock) {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\t\terror();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst s = ()=>{\n\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\tdocument.removeEventListener('pointerlockchange', s);\n\t\t\t\t};\n\t\t\t\tconst e = ()=>{\n\t\t\t\t\t\terror();\n\t\t\t\t\t\tdocument.removeEventListener('pointerlockerror', e);\n\t\t\t\t};\n\t\t\t\tif (success) {\n\t\t\t\t\t\tdocument.addEventListener('pointerlockchange', s, false);\n\t\t\t\t}\n\t\t\t\tif (error) {\n\t\t\t\t\t\tdocument.addEventListener('pointerlockerror', e, false);\n\t\t\t\t}\n\t\t\t\tdocument.body.requestPointerLock();\n\t\t}\n\t\tdisablePointerLock(success) {\n\t\t\t\tif (!document.exitPointerLock) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst s = ()=>{\n\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\tdocument.removeEventListener('pointerlockchange', s);\n\t\t\t\t};\n\t\t\t\tif (success) {\n\t\t\t\t\t\tdocument.addEventListener('pointerlockchange', s, false);\n\t\t\t\t}\n\t\t\t\tdocument.exitPointerLock();\n\t\t}\n\t\tupdate() {\n\t\t\t\tthis._lastbuttons[0] = this._buttons[0];\n\t\t\t\tthis._lastbuttons[1] = this._buttons[1];\n\t\t\t\tthis._lastbuttons[2] = this._buttons[2];\n\t\t}\n\t\tisPressed(button) {\n\t\t\t\treturn this._buttons[button];\n\t\t}\n\t\twasPressed(button) {\n\t\t\t\treturn this._buttons[button] && !this._lastbuttons[button];\n\t\t}\n\t\twasReleased(button) {\n\t\t\t\treturn !this._buttons[button] && this._lastbuttons[button];\n\t\t}\n\t\t_handleUp(event) {\n\t\t\t\tthis._buttons[event.button] = false;\n\t\t\t\tconst e = new MouseEvent(this, event);\n\t\t\t\tif (!e.event) return;\n\t\t\t\tthis.fire('mouseup', e);\n\t\t}\n\t\t_handleDown(event) {\n\t\t\t\tthis._buttons[event.button] = true;\n\t\t\t\tconst e = new MouseEvent(this, event);\n\t\t\t\tif (!e.event) return;\n\t\t\t\tthis.fire('mousedown', e);\n\t\t}\n\t\t_handleMove(event) {\n\t\t\t\tconst e = new MouseEvent(this, event);\n\t\t\t\tif (!e.event) return;\n\t\t\t\tthis.fire('mousemove', e);\n\t\t\t\tthis._lastX = e.x;\n\t\t\t\tthis._lastY = e.y;\n\t\t}\n\t\t_handleWheel(event) {\n\t\t\t\tconst e = new MouseEvent(this, event);\n\t\t\t\tif (!e.event) return;\n\t\t\t\tthis.fire('mousewheel', e);\n\t\t}\n\t\t_getTargetCoords(event) {\n\t\t\t\tconst rect = this._target.getBoundingClientRect();\n\t\t\t\tconst left = Math.floor(rect.left);\n\t\t\t\tconst top = Math.floor(rect.top);\n\t\t\t\tif (event.clientX < left || event.clientX >= left + this._target.clientWidth || event.clientY < top || event.clientY >= top + this._target.clientHeight) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tx: event.clientX - left,\n\t\t\t\t\t\ty: event.clientY - top\n\t\t\t\t};\n\t\t}\n}\n\nclass Http {\n\t\tstatic{\n\t\t\t\tthis.ContentType = {\n\t\t\t\t\t\tAAC: 'audio/aac',\n\t\t\t\t\t\tBASIS: 'image/basis',\n\t\t\t\t\t\tBIN: 'application/octet-stream',\n\t\t\t\t\t\tDDS: 'image/dds',\n\t\t\t\t\t\tFORM_URLENCODED: 'application/x-www-form-urlencoded',\n\t\t\t\t\t\tGIF: 'image/gif',\n\t\t\t\t\t\tGLB: 'model/gltf-binary',\n\t\t\t\t\t\tJPEG: 'image/jpeg',\n\t\t\t\t\t\tJSON: 'application/json',\n\t\t\t\t\t\tMP3: 'audio/mpeg',\n\t\t\t\t\t\tMP4: 'audio/mp4',\n\t\t\t\t\t\tOGG: 'audio/ogg',\n\t\t\t\t\t\tOPUS: 'audio/ogg; codecs=\"opus\"',\n\t\t\t\t\t\tPNG: 'image/png',\n\t\t\t\t\t\tTEXT: 'text/plain',\n\t\t\t\t\t\tWAV: 'audio/x-wav',\n\t\t\t\t\t\tXML: 'application/xml'\n\t\t\t\t};\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ResponseType = {\n\t\t\t\t\t\tTEXT: 'text',\n\t\t\t\t\t\tARRAY_BUFFER: 'arraybuffer',\n\t\t\t\t\t\tBLOB: 'blob',\n\t\t\t\t\t\tDOCUMENT: 'document',\n\t\t\t\t\t\tJSON: 'json'\n\t\t\t\t};\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.binaryExtensions = [\n\t\t\t\t\t\t'.model',\n\t\t\t\t\t\t'.wav',\n\t\t\t\t\t\t'.ogg',\n\t\t\t\t\t\t'.mp3',\n\t\t\t\t\t\t'.mp4',\n\t\t\t\t\t\t'.m4a',\n\t\t\t\t\t\t'.aac',\n\t\t\t\t\t\t'.dds',\n\t\t\t\t\t\t'.basis',\n\t\t\t\t\t\t'.glb',\n\t\t\t\t\t\t'.opus'\n\t\t\t\t];\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.retryDelay = 100;\n\t\t}\n\t\tget(url, options, callback) {\n\t\t\t\tif (typeof options === 'function') {\n\t\t\t\t\t\tcallback = options;\n\t\t\t\t\t\toptions = {};\n\t\t\t\t}\n\t\t\t\tconst result = this.request('GET', url, options, callback);\n\t\t\t\tconst { progress } = options;\n\t\t\t\tif (progress) {\n\t\t\t\t\t\tconst handler = (event)=>{\n\t\t\t\t\t\t\t\tif (event.lengthComputable) {\n\t\t\t\t\t\t\t\t\t\tprogress.fire('progress', event.loaded, event.total);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst endHandler = (event)=>{\n\t\t\t\t\t\t\t\thandler(event);\n\t\t\t\t\t\t\t\tresult.removeEventListener('loadstart', handler);\n\t\t\t\t\t\t\t\tresult.removeEventListener('progress', handler);\n\t\t\t\t\t\t\t\tresult.removeEventListener('loadend', endHandler);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tresult.addEventListener('loadstart', handler);\n\t\t\t\t\t\tresult.addEventListener('progress', handler);\n\t\t\t\t\t\tresult.addEventListener('loadend', endHandler);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tpost(url, data, options, callback) {\n\t\t\t\tif (typeof options === 'function') {\n\t\t\t\t\t\tcallback = options;\n\t\t\t\t\t\toptions = {};\n\t\t\t\t}\n\t\t\t\toptions.postdata = data;\n\t\t\t\treturn this.request('POST', url, options, callback);\n\t\t}\n\t\tput(url, data, options, callback) {\n\t\t\t\tif (typeof options === 'function') {\n\t\t\t\t\t\tcallback = options;\n\t\t\t\t\t\toptions = {};\n\t\t\t\t}\n\t\t\t\toptions.postdata = data;\n\t\t\t\treturn this.request('PUT', url, options, callback);\n\t\t}\n\t\tdel(url, options, callback) {\n\t\t\t\tif (typeof options === 'function') {\n\t\t\t\t\t\tcallback = options;\n\t\t\t\t\t\toptions = {};\n\t\t\t\t}\n\t\t\t\treturn this.request('DELETE', url, options, callback);\n\t\t}\n\t\trequest(method, url, options, callback) {\n\t\t\t\tlet uri, query, postdata;\n\t\t\t\tlet errored = false;\n\t\t\t\tif (typeof options === 'function') {\n\t\t\t\t\t\tcallback = options;\n\t\t\t\t\t\toptions = {};\n\t\t\t\t}\n\t\t\t\tif (options.retry) {\n\t\t\t\t\t\toptions = Object.assign({\n\t\t\t\t\t\t\t\tretries: 0,\n\t\t\t\t\t\t\t\tmaxRetries: 5\n\t\t\t\t\t\t}, options);\n\t\t\t\t}\n\t\t\t\toptions.callback = callback;\n\t\t\t\tif (options.async == null) {\n\t\t\t\t\t\toptions.async = true;\n\t\t\t\t}\n\t\t\t\tif (options.headers == null) {\n\t\t\t\t\t\toptions.headers = {};\n\t\t\t\t}\n\t\t\t\tif (options.postdata != null) {\n\t\t\t\t\t\tif (options.postdata instanceof Document) {\n\t\t\t\t\t\t\t\tpostdata = options.postdata;\n\t\t\t\t\t\t} else if (options.postdata instanceof FormData) {\n\t\t\t\t\t\t\t\tpostdata = options.postdata;\n\t\t\t\t\t\t} else if (options.postdata instanceof Object) {\n\t\t\t\t\t\t\t\tlet contentType = options.headers['Content-Type'];\n\t\t\t\t\t\t\t\tif (contentType === undefined) {\n\t\t\t\t\t\t\t\t\t\toptions.headers['Content-Type'] = Http.ContentType.FORM_URLENCODED;\n\t\t\t\t\t\t\t\t\t\tcontentType = options.headers['Content-Type'];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tswitch(contentType){\n\t\t\t\t\t\t\t\t\t\tcase Http.ContentType.FORM_URLENCODED:\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpostdata = '';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet bFirstItem = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(const key in options.postdata){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (options.postdata.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (bFirstItem) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbFirstItem = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpostdata += '&';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst encodedKey = encodeURIComponent(key);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst encodedValue = encodeURIComponent(options.postdata[key]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpostdata += `${encodedKey}=${encodedValue}`;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tcase Http.ContentType.JSON:\n\t\t\t\t\t\t\t\t\t\t\t\tif (contentType == null) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\toptions.headers['Content-Type'] = Http.ContentType.JSON;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tpostdata = JSON.stringify(options.postdata);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tpostdata = options.postdata;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (options.cache === false) {\n\t\t\t\t\t\tconst timestamp = now();\n\t\t\t\t\t\turi = new URI(url);\n\t\t\t\t\t\tif (!uri.query) {\n\t\t\t\t\t\t\t\turi.query = `ts=${timestamp}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\turi.query = `${uri.query}&ts=${timestamp}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\turl = uri.toString();\n\t\t\t\t}\n\t\t\t\tif (options.query) {\n\t\t\t\t\t\turi = new URI(url);\n\t\t\t\t\t\tquery = extend(uri.getQuery(), options.query);\n\t\t\t\t\t\turi.setQuery(query);\n\t\t\t\t\t\turl = uri.toString();\n\t\t\t\t}\n\t\t\t\tconst xhr = new XMLHttpRequest();\n\t\t\t\txhr.open(method, url, options.async);\n\t\t\t\txhr.withCredentials = options.withCredentials !== undefined ? options.withCredentials : false;\n\t\t\t\txhr.responseType = options.responseType || this._guessResponseType(url);\n\t\t\t\tfor(const header in options.headers){\n\t\t\t\t\t\tif (options.headers.hasOwnProperty(header)) {\n\t\t\t\t\t\t\t\txhr.setRequestHeader(header, options.headers[header]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\txhr.onreadystatechange = ()=>{\n\t\t\t\t\t\tthis._onReadyStateChange(method, url, options, xhr);\n\t\t\t\t};\n\t\t\t\txhr.onerror = ()=>{\n\t\t\t\t\t\tthis._onError(method, url, options, xhr);\n\t\t\t\t\t\terrored = true;\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\t\txhr.send(postdata);\n\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (!errored) {\n\t\t\t\t\t\t\t\toptions.error(xhr.status, xhr, e);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn xhr;\n\t\t}\n\t\t_guessResponseType(url) {\n\t\t\t\tconst uri = new URI(url);\n\t\t\t\tconst ext = path.getExtension(uri.path).toLowerCase();\n\t\t\t\tif (Http.binaryExtensions.indexOf(ext) >= 0) {\n\t\t\t\t\t\treturn Http.ResponseType.ARRAY_BUFFER;\n\t\t\t\t} else if (ext === '.json') {\n\t\t\t\t\t\treturn Http.ResponseType.JSON;\n\t\t\t\t} else if (ext === '.xml') {\n\t\t\t\t\t\treturn Http.ResponseType.DOCUMENT;\n\t\t\t\t}\n\t\t\t\treturn Http.ResponseType.TEXT;\n\t\t}\n\t\t_isBinaryContentType(contentType) {\n\t\t\t\tconst binTypes = [\n\t\t\t\t\t\tHttp.ContentType.BASIS,\n\t\t\t\t\t\tHttp.ContentType.BIN,\n\t\t\t\t\t\tHttp.ContentType.DDS,\n\t\t\t\t\t\tHttp.ContentType.GLB,\n\t\t\t\t\t\tHttp.ContentType.MP3,\n\t\t\t\t\t\tHttp.ContentType.MP4,\n\t\t\t\t\t\tHttp.ContentType.OGG,\n\t\t\t\t\t\tHttp.ContentType.OPUS,\n\t\t\t\t\t\tHttp.ContentType.WAV\n\t\t\t\t];\n\t\t\t\tif (binTypes.indexOf(contentType) >= 0) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\t_isBinaryResponseType(responseType) {\n\t\t\t\treturn responseType === Http.ResponseType.ARRAY_BUFFER || responseType === Http.ResponseType.BLOB || responseType === Http.ResponseType.JSON;\n\t\t}\n\t\t_onReadyStateChange(method, url, options, xhr) {\n\t\t\t\tif (xhr.readyState === 4) {\n\t\t\t\t\t\tswitch(xhr.status){\n\t\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tif (xhr.responseURL && xhr.responseURL.startsWith('file:///')) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._onSuccess(method, url, options, xhr);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._onError(method, url, options, xhr);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase 200:\n\t\t\t\t\t\t\t\tcase 201:\n\t\t\t\t\t\t\t\tcase 206:\n\t\t\t\t\t\t\t\tcase 304:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tthis._onSuccess(method, url, options, xhr);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tthis._onError(method, url, options, xhr);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onSuccess(method, url, options, xhr) {\n\t\t\t\tlet response;\n\t\t\t\tlet contentType;\n\t\t\t\tconst header = xhr.getResponseHeader('Content-Type');\n\t\t\t\tif (header) {\n\t\t\t\t\t\tconst parts = header.split(';');\n\t\t\t\t\t\tcontentType = parts[0].trim();\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\t\tif (this._isBinaryContentType(contentType) || this._isBinaryResponseType(xhr.responseType)) {\n\t\t\t\t\t\t\t\tresponse = xhr.response;\n\t\t\t\t\t\t} else if (contentType === Http.ContentType.JSON || url.split('?')[0].endsWith('.json')) {\n\t\t\t\t\t\t\t\tresponse = JSON.parse(xhr.responseText);\n\t\t\t\t\t\t} else if (xhr.responseType === Http.ResponseType.DOCUMENT || contentType === Http.ContentType.XML) {\n\t\t\t\t\t\t\t\tresponse = xhr.responseXML;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresponse = xhr.responseText;\n\t\t\t\t\t\t}\n\t\t\t\t\t\toptions.callback(null, response);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t\toptions.callback(err);\n\t\t\t\t}\n\t\t}\n\t\t_onError(method, url, options, xhr) {\n\t\t\t\tif (options.retrying) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (options.retry && options.retries < options.maxRetries) {\n\t\t\t\t\t\toptions.retries++;\n\t\t\t\t\t\toptions.retrying = true;\n\t\t\t\t\t\tconst retryDelay = math.clamp(Math.pow(2, options.retries) * Http.retryDelay, 0, options.maxRetryDelay || 5000);\n\t\t\t\t\t\tconsole.log(`${method}: ${url} - Error ${xhr.status}. Retrying in ${retryDelay} ms`);\n\t\t\t\t\t\tsetTimeout(()=>{\n\t\t\t\t\t\t\t\toptions.retrying = false;\n\t\t\t\t\t\t\t\tthis.request(method, url, options, options.callback);\n\t\t\t\t\t\t}, retryDelay);\n\t\t\t\t} else {\n\t\t\t\t\t\toptions.callback(xhr.status === 0 ? 'Network error' : xhr.status, null);\n\t\t\t\t}\n\t\t}\n}\nconst http = new Http();\n\nconst DISTANCE_LINEAR = 'linear';\nconst DISTANCE_INVERSE = 'inverse';\nconst DISTANCE_EXPONENTIAL = 'exponential';\n\nclass Listener {\n\t\tconstructor(manager){\n\t\t\t\tthis.position = new Vec3();\n\t\t\t\tthis.orientation = new Mat4();\n\t\t\t\tthis._manager = manager;\n\t\t}\n\t\tgetPosition() {\n\t\t\t\treturn this.position;\n\t\t}\n\t\tsetPosition(position) {\n\t\t\t\tthis.position.copy(position);\n\t\t\t\tconst listener = this.listener;\n\t\t\t\tif (listener) {\n\t\t\t\t\t\tif ('positionX' in listener) {\n\t\t\t\t\t\t\t\tlistener.positionX.value = position.x;\n\t\t\t\t\t\t\t\tlistener.positionY.value = position.y;\n\t\t\t\t\t\t\t\tlistener.positionZ.value = position.z;\n\t\t\t\t\t\t} else if (listener.setPosition) {\n\t\t\t\t\t\t\t\tlistener.setPosition(position.x, position.y, position.z);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetOrientation(orientation) {\n\t\t\t\tthis.orientation.copy(orientation);\n\t\t\t\tconst listener = this.listener;\n\t\t\t\tif (listener) {\n\t\t\t\t\t\tconst m = orientation.data;\n\t\t\t\t\t\tif ('forwardX' in listener) {\n\t\t\t\t\t\t\t\tlistener.forwardX.value = -m[8];\n\t\t\t\t\t\t\t\tlistener.forwardY.value = -m[9];\n\t\t\t\t\t\t\t\tlistener.forwardZ.value = -m[10];\n\t\t\t\t\t\t\t\tlistener.upX.value = m[4];\n\t\t\t\t\t\t\t\tlistener.upY.value = m[5];\n\t\t\t\t\t\t\t\tlistener.upZ.value = m[6];\n\t\t\t\t\t\t} else if (listener.setOrientation) {\n\t\t\t\t\t\t\t\tlistener.setOrientation(-m[8], -m[9], -m[10], m[4], m[5], m[6]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgetOrientation() {\n\t\t\t\treturn this.orientation;\n\t\t}\n\t\tget listener() {\n\t\t\t\tconst context = this._manager.context;\n\t\t\t\treturn context ? context.listener : null;\n\t\t}\n}\n\nconst CONTEXT_STATE_RUNNING = 'running';\nconst USER_INPUT_EVENTS = [\n\t\t'click',\n\t\t'touchstart',\n\t\t'mousedown'\n];\nclass SoundManager extends EventHandler {\n\t\tconstructor(){\n\t\t\t\tsuper();\n\t\t\t\tthis._context = null;\n\t\t\t\tthis.AudioContext = typeof AudioContext !== 'undefined' && AudioContext || typeof webkitAudioContext !== 'undefined' && webkitAudioContext;\n\t\t\t\tif (!this.AudioContext) ;\n\t\t\t\tthis._unlockHandlerFunc = this._unlockHandler.bind(this);\n\t\t\t\tthis._userSuspended = false;\n\t\t\t\tthis.listener = new Listener(this);\n\t\t\t\tthis._volume = 1;\n\t\t}\n\t\tset volume(volume) {\n\t\t\t\tvolume = math.clamp(volume, 0, 1);\n\t\t\t\tthis._volume = volume;\n\t\t\t\tthis.fire('volumechange', volume);\n\t\t}\n\t\tget volume() {\n\t\t\t\treturn this._volume;\n\t\t}\n\t\tget suspended() {\n\t\t\t\treturn this._userSuspended;\n\t\t}\n\t\tget context() {\n\t\t\t\tif (!this._context && this.AudioContext) {\n\t\t\t\t\t\tthis._context = new this.AudioContext();\n\t\t\t\t\t\tif (this._context.state !== CONTEXT_STATE_RUNNING) {\n\t\t\t\t\t\t\t\tthis._registerUnlockListeners();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this._context;\n\t\t}\n\t\tsuspend() {\n\t\t\t\tif (!this._userSuspended) {\n\t\t\t\t\t\tthis._userSuspended = true;\n\t\t\t\t\t\tif (this._context && this._context.state === CONTEXT_STATE_RUNNING) {\n\t\t\t\t\t\t\t\tthis._suspend();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tresume() {\n\t\t\t\tif (this._userSuspended) {\n\t\t\t\t\t\tthis._userSuspended = false;\n\t\t\t\t\t\tif (this._context && this._context.state !== CONTEXT_STATE_RUNNING) {\n\t\t\t\t\t\t\t\tthis._resume();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.fire('destroy');\n\t\t\t\tif (this._context) {\n\t\t\t\t\t\tthis._removeUnlockListeners();\n\t\t\t\t\t\tthis._context?.close();\n\t\t\t\t\t\tthis._context = null;\n\t\t\t\t}\n\t\t}\n\t\t_resume() {\n\t\t\t\tthis._context.resume().then(()=>{\n\t\t\t\t\t\tconst source = this._context.createBufferSource();\n\t\t\t\t\t\tsource.buffer = this._context.createBuffer(1, 1, this._context.sampleRate);\n\t\t\t\t\t\tsource.connect(this._context.destination);\n\t\t\t\t\t\tsource.start(0);\n\t\t\t\t\t\tsource.onended = (event)=>{\n\t\t\t\t\t\t\t\tsource.disconnect(0);\n\t\t\t\t\t\t\t\tthis.fire('resume');\n\t\t\t\t\t\t};\n\t\t\t\t}, (e)=>{}).catch((e)=>{});\n\t\t}\n\t\t_suspend() {\n\t\t\t\tthis._context.suspend().then(()=>{\n\t\t\t\t\t\tthis.fire('suspend');\n\t\t\t\t}, (e)=>{}).catch((e)=>{});\n\t\t}\n\t\t_unlockHandler() {\n\t\t\t\tthis._removeUnlockListeners();\n\t\t\t\tif (!this._userSuspended && this._context.state !== CONTEXT_STATE_RUNNING) {\n\t\t\t\t\t\tthis._resume();\n\t\t\t\t}\n\t\t}\n\t\t_registerUnlockListeners() {\n\t\t\t\tUSER_INPUT_EVENTS.forEach((eventName)=>{\n\t\t\t\t\t\twindow.addEventListener(eventName, this._unlockHandlerFunc, false);\n\t\t\t\t});\n\t\t}\n\t\t_removeUnlockListeners() {\n\t\t\t\tUSER_INPUT_EVENTS.forEach((eventName)=>{\n\t\t\t\t\t\twindow.removeEventListener(eventName, this._unlockHandlerFunc, false);\n\t\t\t\t});\n\t\t}\n}\n\nclass Sound {\n\t\tconstructor(resource){\n\t\t\t\tif (resource instanceof Audio) {\n\t\t\t\t\t\tthis.audio = resource;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.buffer = resource;\n\t\t\t\t}\n\t\t}\n\t\tget duration() {\n\t\t\t\tlet duration = 0;\n\t\t\t\tif (this.buffer) {\n\t\t\t\t\t\tduration = this.buffer.duration;\n\t\t\t\t} else if (this.audio) {\n\t\t\t\t\t\tduration = this.audio.duration;\n\t\t\t\t}\n\t\t\t\treturn duration || 0;\n\t\t}\n}\n\nfunction hasAudioContext() {\n\t\treturn !!(typeof AudioContext !== 'undefined' || typeof webkitAudioContext !== 'undefined');\n}\n\nconst STATE_PLAYING = 0;\nconst STATE_PAUSED = 1;\nconst STATE_STOPPED = 2;\nfunction capTime(time, duration) {\n\t\treturn time % duration || 0;\n}\nclass SoundInstance extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_PLAY = 'play';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PAUSE = 'pause';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESUME = 'resume';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STOP = 'stop';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_END = 'end';\n\t\t}\n\t\tconstructor(manager, sound, options){\n\t\t\t\tsuper(), this.source = null;\n\t\t\t\tthis._manager = manager;\n\t\t\t\tthis._volume = options.volume !== undefined ? math.clamp(Number(options.volume) || 0, 0, 1) : 1;\n\t\t\t\tthis._pitch = options.pitch !== undefined ? Math.max(0.01, Number(options.pitch) || 0) : 1;\n\t\t\t\tthis._loop = !!(options.loop !== undefined ? options.loop : false);\n\t\t\t\tthis._sound = sound;\n\t\t\t\tthis._state = STATE_STOPPED;\n\t\t\t\tthis._suspended = false;\n\t\t\t\tthis._suspendEndEvent = 0;\n\t\t\t\tthis._suspendInstanceEvents = false;\n\t\t\t\tthis._playWhenLoaded = true;\n\t\t\t\tthis._startTime = Math.max(0, Number(options.startTime) || 0);\n\t\t\t\tthis._duration = Math.max(0, Number(options.duration) || 0);\n\t\t\t\tthis._startOffset = null;\n\t\t\t\tthis._onPlayCallback = options.onPlay;\n\t\t\t\tthis._onPauseCallback = options.onPause;\n\t\t\t\tthis._onResumeCallback = options.onResume;\n\t\t\t\tthis._onStopCallback = options.onStop;\n\t\t\t\tthis._onEndCallback = options.onEnd;\n\t\t\t\tif (hasAudioContext()) {\n\t\t\t\t\t\tthis._startedAt = 0;\n\t\t\t\t\t\tthis._currentTime = 0;\n\t\t\t\t\t\tthis._currentOffset = 0;\n\t\t\t\t\t\tthis._inputNode = null;\n\t\t\t\t\t\tthis._connectorNode = null;\n\t\t\t\t\t\tthis._firstNode = null;\n\t\t\t\t\t\tthis._lastNode = null;\n\t\t\t\t\t\tthis._waitingContextSuspension = false;\n\t\t\t\t\t\tthis._initializeNodes();\n\t\t\t\t\t\tthis._endedHandler = this._onEnded.bind(this);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._isReady = false;\n\t\t\t\t\t\tthis._loadedMetadataHandler = this._onLoadedMetadata.bind(this);\n\t\t\t\t\t\tthis._timeUpdateHandler = this._onTimeUpdate.bind(this);\n\t\t\t\t\t\tthis._endedHandler = this._onEnded.bind(this);\n\t\t\t\t\t\tthis._createSource();\n\t\t\t\t}\n\t\t}\n\t\tset currentTime(value) {\n\t\t\t\tif (value < 0) return;\n\t\t\t\tif (this._state === STATE_PLAYING) {\n\t\t\t\t\t\tconst suspend = this._suspendInstanceEvents;\n\t\t\t\t\t\tthis._suspendInstanceEvents = true;\n\t\t\t\t\t\tthis.stop();\n\t\t\t\t\t\tthis._startOffset = value;\n\t\t\t\t\t\tthis.play();\n\t\t\t\t\t\tthis._suspendInstanceEvents = suspend;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._startOffset = value;\n\t\t\t\t\t\tthis._currentTime = value;\n\t\t\t\t}\n\t\t}\n\t\tget currentTime() {\n\t\t\t\tif (this._startOffset !== null) {\n\t\t\t\t\t\treturn this._startOffset;\n\t\t\t\t}\n\t\t\t\tif (this._state === STATE_PAUSED) {\n\t\t\t\t\t\treturn this._currentTime;\n\t\t\t\t}\n\t\t\t\tif (this._state === STATE_STOPPED || !this.source) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tthis._updateCurrentTime();\n\t\t\t\treturn this._currentTime;\n\t\t}\n\t\tset duration(value) {\n\t\t\t\tthis._duration = Math.max(0, Number(value) || 0);\n\t\t\t\tconst isPlaying = this._state === STATE_PLAYING;\n\t\t\t\tthis.stop();\n\t\t\t\tif (isPlaying) {\n\t\t\t\t\t\tthis.play();\n\t\t\t\t}\n\t\t}\n\t\tget duration() {\n\t\t\t\tif (!this._sound) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tif (this._duration) {\n\t\t\t\t\t\treturn capTime(this._duration, this._sound.duration);\n\t\t\t\t}\n\t\t\t\treturn this._sound.duration;\n\t\t}\n\t\tget isPaused() {\n\t\t\t\treturn this._state === STATE_PAUSED;\n\t\t}\n\t\tget isPlaying() {\n\t\t\t\treturn this._state === STATE_PLAYING;\n\t\t}\n\t\tget isStopped() {\n\t\t\t\treturn this._state === STATE_STOPPED;\n\t\t}\n\t\tget isSuspended() {\n\t\t\t\treturn this._suspended;\n\t\t}\n\t\tset loop(value) {\n\t\t\t\tthis._loop = !!value;\n\t\t\t\tif (this.source) {\n\t\t\t\t\t\tthis.source.loop = this._loop;\n\t\t\t\t}\n\t\t}\n\t\tget loop() {\n\t\t\t\treturn this._loop;\n\t\t}\n\t\tset pitch(pitch) {\n\t\t\t\tthis._currentOffset = this.currentTime;\n\t\t\t\tthis._startedAt = this._manager.context.currentTime;\n\t\t\t\tthis._pitch = Math.max(Number(pitch) || 0, 0.01);\n\t\t\t\tif (this.source) {\n\t\t\t\t\t\tthis.source.playbackRate.value = this._pitch;\n\t\t\t\t}\n\t\t}\n\t\tget pitch() {\n\t\t\t\treturn this._pitch;\n\t\t}\n\t\tset sound(value) {\n\t\t\t\tthis._sound = value;\n\t\t\t\tif (this._state !== STATE_STOPPED) {\n\t\t\t\t\t\tthis.stop();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._createSource();\n\t\t\t\t}\n\t\t}\n\t\tget sound() {\n\t\t\t\treturn this._sound;\n\t\t}\n\t\tset startTime(value) {\n\t\t\t\tthis._startTime = Math.max(0, Number(value) || 0);\n\t\t\t\tconst isPlaying = this._state === STATE_PLAYING;\n\t\t\t\tthis.stop();\n\t\t\t\tif (isPlaying) {\n\t\t\t\t\t\tthis.play();\n\t\t\t\t}\n\t\t}\n\t\tget startTime() {\n\t\t\t\treturn this._startTime;\n\t\t}\n\t\tset volume(volume) {\n\t\t\t\tvolume = math.clamp(volume, 0, 1);\n\t\t\t\tthis._volume = volume;\n\t\t\t\tif (this.gain) {\n\t\t\t\t\t\tthis.gain.gain.value = volume * this._manager.volume;\n\t\t\t\t}\n\t\t}\n\t\tget volume() {\n\t\t\t\treturn this._volume;\n\t\t}\n\t\t_onPlay() {\n\t\t\t\tthis.fire('play');\n\t\t\t\tif (this._onPlayCallback) {\n\t\t\t\t\t\tthis._onPlayCallback(this);\n\t\t\t\t}\n\t\t}\n\t\t_onPause() {\n\t\t\t\tthis.fire('pause');\n\t\t\t\tif (this._onPauseCallback) {\n\t\t\t\t\t\tthis._onPauseCallback(this);\n\t\t\t\t}\n\t\t}\n\t\t_onResume() {\n\t\t\t\tthis.fire('resume');\n\t\t\t\tif (this._onResumeCallback) {\n\t\t\t\t\t\tthis._onResumeCallback(this);\n\t\t\t\t}\n\t\t}\n\t\t_onStop() {\n\t\t\t\tthis.fire('stop');\n\t\t\t\tif (this._onStopCallback) {\n\t\t\t\t\t\tthis._onStopCallback(this);\n\t\t\t\t}\n\t\t}\n\t\t_onEnded() {\n\t\t\t\tif (this._suspendEndEvent > 0) {\n\t\t\t\t\t\tthis._suspendEndEvent--;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.fire('end');\n\t\t\t\tif (this._onEndCallback) {\n\t\t\t\t\t\tthis._onEndCallback(this);\n\t\t\t\t}\n\t\t\t\tthis.stop();\n\t\t}\n\t\t_onManagerVolumeChange() {\n\t\t\t\tthis.volume = this._volume;\n\t\t}\n\t\t_onManagerSuspend() {\n\t\t\t\tif (this._state === STATE_PLAYING && !this._suspended) {\n\t\t\t\t\t\tthis._suspended = true;\n\t\t\t\t\t\tthis.pause();\n\t\t\t\t}\n\t\t}\n\t\t_onManagerResume() {\n\t\t\t\tif (this._suspended) {\n\t\t\t\t\t\tthis._suspended = false;\n\t\t\t\t\t\tthis.resume();\n\t\t\t\t}\n\t\t}\n\t\t_initializeNodes() {\n\t\t\t\tthis.gain = this._manager.context.createGain();\n\t\t\t\tthis._inputNode = this.gain;\n\t\t\t\tthis._connectorNode = this.gain;\n\t\t\t\tthis._connectorNode.connect(this._manager.context.destination);\n\t\t}\n\t\tplay() {\n\t\t\t\tif (this._state !== STATE_STOPPED) {\n\t\t\t\t\t\tthis.stop();\n\t\t\t\t}\n\t\t\t\tthis._state = STATE_PLAYING;\n\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\tif (this._waitingContextSuspension) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (this._manager.suspended) {\n\t\t\t\t\t\tthis._manager.once('resume', this._playAudioImmediate, this);\n\t\t\t\t\t\tthis._waitingContextSuspension = true;\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis._playAudioImmediate();\n\t\t\t\treturn true;\n\t\t}\n\t\t_playAudioImmediate() {\n\t\t\t\tthis._waitingContextSuspension = false;\n\t\t\t\tif (this._state !== STATE_PLAYING) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.source) {\n\t\t\t\t\t\tthis._createSource();\n\t\t\t\t}\n\t\t\t\tlet offset = capTime(this._startOffset, this.duration);\n\t\t\t\toffset = capTime(this._startTime + offset, this._sound.duration);\n\t\t\t\tthis._startOffset = null;\n\t\t\t\tif (this._duration) {\n\t\t\t\t\t\tthis.source.start(0, offset, this._duration);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.source.start(0, offset);\n\t\t\t\t}\n\t\t\t\tthis._startedAt = this._manager.context.currentTime;\n\t\t\t\tthis._currentTime = 0;\n\t\t\t\tthis._currentOffset = offset;\n\t\t\t\tthis.volume = this._volume;\n\t\t\t\tthis.loop = this._loop;\n\t\t\t\tthis.pitch = this._pitch;\n\t\t\t\tthis._manager.on('volumechange', this._onManagerVolumeChange, this);\n\t\t\t\tthis._manager.on('suspend', this._onManagerSuspend, this);\n\t\t\t\tthis._manager.on('resume', this._onManagerResume, this);\n\t\t\t\tthis._manager.on('destroy', this._onManagerDestroy, this);\n\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\tthis._onPlay();\n\t\t\t\t}\n\t\t}\n\t\tpause() {\n\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\tif (this._state !== STATE_PLAYING) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis._state = STATE_PAUSED;\n\t\t\t\tif (this._waitingContextSuspension) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tthis._updateCurrentTime();\n\t\t\t\tthis._suspendEndEvent++;\n\t\t\t\tthis.source.stop(0);\n\t\t\t\tthis.source = null;\n\t\t\t\tthis._startOffset = null;\n\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\tthis._onPause();\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tresume() {\n\t\t\t\tif (this._state !== STATE_PAUSED) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tlet offset = this.currentTime;\n\t\t\t\tthis._state = STATE_PLAYING;\n\t\t\t\tif (this._waitingContextSuspension) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (!this.source) {\n\t\t\t\t\t\tthis._createSource();\n\t\t\t\t}\n\t\t\t\tif (this._startOffset !== null) {\n\t\t\t\t\t\toffset = capTime(this._startOffset, this.duration);\n\t\t\t\t\t\toffset = capTime(this._startTime + offset, this._sound.duration);\n\t\t\t\t\t\tthis._startOffset = null;\n\t\t\t\t}\n\t\t\t\tif (this._duration) {\n\t\t\t\t\t\tthis.source.start(0, offset, this._duration);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.source.start(0, offset);\n\t\t\t\t}\n\t\t\t\tthis._startedAt = this._manager.context.currentTime;\n\t\t\t\tthis._currentOffset = offset;\n\t\t\t\tthis.volume = this._volume;\n\t\t\t\tthis.loop = this._loop;\n\t\t\t\tthis.pitch = this._pitch;\n\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\tthis._onResume();\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tstop() {\n\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\tif (this._state === STATE_STOPPED) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst wasPlaying = this._state === STATE_PLAYING;\n\t\t\t\tthis._state = STATE_STOPPED;\n\t\t\t\tif (this._waitingContextSuspension) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tthis._manager.off('volumechange', this._onManagerVolumeChange, this);\n\t\t\t\tthis._manager.off('suspend', this._onManagerSuspend, this);\n\t\t\t\tthis._manager.off('resume', this._onManagerResume, this);\n\t\t\t\tthis._manager.off('destroy', this._onManagerDestroy, this);\n\t\t\t\tthis._startedAt = 0;\n\t\t\t\tthis._currentTime = 0;\n\t\t\t\tthis._currentOffset = 0;\n\t\t\t\tthis._startOffset = null;\n\t\t\t\tthis._suspendEndEvent++;\n\t\t\t\tif (wasPlaying && this.source) {\n\t\t\t\t\t\tthis.source.stop(0);\n\t\t\t\t}\n\t\t\t\tthis.source = null;\n\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\tthis._onStop();\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tsetExternalNodes(firstNode, lastNode) {\n\t\t\t\tif (!firstNode) {\n\t\t\t\t\t\tconsole.error('The firstNode must be a valid Audio Node');\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!lastNode) {\n\t\t\t\t\t\tlastNode = firstNode;\n\t\t\t\t}\n\t\t\t\tconst speakers = this._manager.context.destination;\n\t\t\t\tif (this._firstNode !== firstNode) {\n\t\t\t\t\t\tif (this._firstNode) {\n\t\t\t\t\t\t\t\tthis._connectorNode.disconnect(this._firstNode);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._connectorNode.disconnect(speakers);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._firstNode = firstNode;\n\t\t\t\t\t\tthis._connectorNode.connect(firstNode);\n\t\t\t\t}\n\t\t\t\tif (this._lastNode !== lastNode) {\n\t\t\t\t\t\tif (this._lastNode) {\n\t\t\t\t\t\t\t\tthis._lastNode.disconnect(speakers);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._lastNode = lastNode;\n\t\t\t\t\t\tthis._lastNode.connect(speakers);\n\t\t\t\t}\n\t\t}\n\t\tclearExternalNodes() {\n\t\t\t\tconst speakers = this._manager.context.destination;\n\t\t\t\tif (this._firstNode) {\n\t\t\t\t\t\tthis._connectorNode.disconnect(this._firstNode);\n\t\t\t\t\t\tthis._firstNode = null;\n\t\t\t\t}\n\t\t\t\tif (this._lastNode) {\n\t\t\t\t\t\tthis._lastNode.disconnect(speakers);\n\t\t\t\t\t\tthis._lastNode = null;\n\t\t\t\t}\n\t\t\t\tthis._connectorNode.connect(speakers);\n\t\t}\n\t\tgetExternalNodes() {\n\t\t\t\treturn [\n\t\t\t\t\t\tthis._firstNode,\n\t\t\t\t\t\tthis._lastNode\n\t\t\t\t];\n\t\t}\n\t\t_createSource() {\n\t\t\t\tif (!this._sound) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst context = this._manager.context;\n\t\t\t\tif (this._sound.buffer) {\n\t\t\t\t\t\tthis.source = context.createBufferSource();\n\t\t\t\t\t\tthis.source.buffer = this._sound.buffer;\n\t\t\t\t\t\tthis.source.connect(this._inputNode);\n\t\t\t\t\t\tthis.source.onended = this._endedHandler;\n\t\t\t\t\t\tthis.source.loopStart = capTime(this._startTime, this.source.buffer.duration);\n\t\t\t\t\t\tif (this._duration) {\n\t\t\t\t\t\t\t\tthis.source.loopEnd = Math.max(this.source.loopStart, capTime(this._startTime + this._duration, this.source.buffer.duration));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this.source;\n\t\t}\n\t\t_updateCurrentTime() {\n\t\t\t\tthis._currentTime = capTime((this._manager.context.currentTime - this._startedAt) * this._pitch + this._currentOffset, this.duration);\n\t\t}\n\t\t_onManagerDestroy() {\n\t\t\t\tif (this.source && this._state === STATE_PLAYING) {\n\t\t\t\t\t\tthis.source.stop(0);\n\t\t\t\t\t\tthis.source = null;\n\t\t\t\t}\n\t\t}\n}\nif (!hasAudioContext()) {\n\t\tObject.assign(SoundInstance.prototype, {\n\t\t\t\tplay: function() {\n\t\t\t\t\t\tif (this._state !== STATE_STOPPED) {\n\t\t\t\t\t\t\t\tthis.stop();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this.source) {\n\t\t\t\t\t\t\t\tif (!this._createSource()) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.volume = this._volume;\n\t\t\t\t\t\tthis.pitch = this._pitch;\n\t\t\t\t\t\tthis.loop = this._loop;\n\t\t\t\t\t\tthis.source.play();\n\t\t\t\t\t\tthis._state = STATE_PLAYING;\n\t\t\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\t\t\tthis._manager.on('volumechange', this._onManagerVolumeChange, this);\n\t\t\t\t\t\tthis._manager.on('suspend', this._onManagerSuspend, this);\n\t\t\t\t\t\tthis._manager.on('resume', this._onManagerResume, this);\n\t\t\t\t\t\tthis._manager.on('destroy', this._onManagerDestroy, this);\n\t\t\t\t\t\tif (this._manager.suspended) {\n\t\t\t\t\t\t\t\tthis._onManagerSuspend();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\t\t\tthis._onPlay();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tpause: function() {\n\t\t\t\t\t\tif (!this.source || this._state !== STATE_PLAYING) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._suspendEndEvent++;\n\t\t\t\t\t\tthis.source.pause();\n\t\t\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\t\t\tthis._state = STATE_PAUSED;\n\t\t\t\t\t\tthis._startOffset = null;\n\t\t\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\t\t\tthis._onPause();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tresume: function() {\n\t\t\t\t\t\tif (!this.source || this._state !== STATE_PAUSED) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._state = STATE_PLAYING;\n\t\t\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\t\t\tif (this.source.paused) {\n\t\t\t\t\t\t\t\tthis.source.play();\n\t\t\t\t\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\t\t\t\t\tthis._onResume();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tstop: function() {\n\t\t\t\t\t\tif (!this.source || this._state === STATE_STOPPED) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._manager.off('volumechange', this._onManagerVolumeChange, this);\n\t\t\t\t\t\tthis._manager.off('suspend', this._onManagerSuspend, this);\n\t\t\t\t\t\tthis._manager.off('resume', this._onManagerResume, this);\n\t\t\t\t\t\tthis._manager.off('destroy', this._onManagerDestroy, this);\n\t\t\t\t\t\tthis._suspendEndEvent++;\n\t\t\t\t\t\tthis.source.pause();\n\t\t\t\t\t\tthis._playWhenLoaded = false;\n\t\t\t\t\t\tthis._state = STATE_STOPPED;\n\t\t\t\t\t\tthis._startOffset = null;\n\t\t\t\t\t\tif (!this._suspendInstanceEvents) {\n\t\t\t\t\t\t\t\tthis._onStop();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tsetExternalNodes: function() {},\n\t\t\t\tclearExternalNodes: function() {},\n\t\t\t\tgetExternalNodes: function() {\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t];\n\t\t\t\t},\n\t\t\t\t_onLoadedMetadata: function() {\n\t\t\t\t\t\tthis.source.removeEventListener('loadedmetadata', this._loadedMetadataHandler);\n\t\t\t\t\t\tthis._isReady = true;\n\t\t\t\t\t\tlet offset = capTime(this._startOffset, this.duration);\n\t\t\t\t\t\toffset = capTime(this._startTime + offset, this._sound.duration);\n\t\t\t\t\t\tthis._startOffset = null;\n\t\t\t\t\t\tthis.source.currentTime = offset;\n\t\t\t\t},\n\t\t\t\t_createSource: function() {\n\t\t\t\t\t\tif (this._sound && this._sound.audio) {\n\t\t\t\t\t\t\t\tthis._isReady = false;\n\t\t\t\t\t\t\t\tthis.source = this._sound.audio.cloneNode(true);\n\t\t\t\t\t\t\t\tthis.source.addEventListener('loadedmetadata', this._loadedMetadataHandler);\n\t\t\t\t\t\t\t\tthis.source.addEventListener('timeupdate', this._timeUpdateHandler);\n\t\t\t\t\t\t\t\tthis.source.onended = this._endedHandler;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn this.source;\n\t\t\t\t},\n\t\t\t\t_onTimeUpdate: function() {\n\t\t\t\t\t\tif (!this._duration) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.source.currentTime > capTime(this._startTime + this._duration, this.source.duration)) {\n\t\t\t\t\t\t\t\tif (this.loop) {\n\t\t\t\t\t\t\t\t\t\tthis.source.currentTime = capTime(this._startTime, this.source.duration);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.source.removeEventListener('timeupdate', this._timeUpdateHandler);\n\t\t\t\t\t\t\t\t\t\tthis.source.pause();\n\t\t\t\t\t\t\t\t\t\tthis._onEnded();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t_onManagerDestroy: function() {\n\t\t\t\t\t\tif (this.source) {\n\t\t\t\t\t\t\t\tthis.source.pause();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance.prototype, 'volume', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._volume;\n\t\t\t\t},\n\t\t\t\tset: function(volume) {\n\t\t\t\t\t\tvolume = math.clamp(volume, 0, 1);\n\t\t\t\t\t\tthis._volume = volume;\n\t\t\t\t\t\tif (this.source) {\n\t\t\t\t\t\t\t\tthis.source.volume = volume * this._manager.volume;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance.prototype, 'pitch', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._pitch;\n\t\t\t\t},\n\t\t\t\tset: function(pitch) {\n\t\t\t\t\t\tthis._pitch = Math.max(Number(pitch) || 0, 0.01);\n\t\t\t\t\t\tif (this.source) {\n\t\t\t\t\t\t\t\tthis.source.playbackRate = this._pitch;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance.prototype, 'sound', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._sound;\n\t\t\t\t},\n\t\t\t\tset: function(value) {\n\t\t\t\t\t\tthis.stop();\n\t\t\t\t\t\tthis._sound = value;\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance.prototype, 'currentTime', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\tif (this._startOffset !== null) {\n\t\t\t\t\t\t\t\treturn this._startOffset;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._state === STATE_STOPPED || !this.source) {\n\t\t\t\t\t\t\t\treturn 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn this.source.currentTime - this._startTime;\n\t\t\t\t},\n\t\t\t\tset: function(value) {\n\t\t\t\t\t\tif (value < 0) return;\n\t\t\t\t\t\tthis._startOffset = value;\n\t\t\t\t\t\tif (this.source && this._isReady) {\n\t\t\t\t\t\t\t\tthis.source.currentTime = capTime(this._startTime + capTime(value, this.duration), this._sound.duration);\n\t\t\t\t\t\t\t\tthis._startOffset = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t});\n}\n\nconst MAX_DISTANCE = 10000;\nclass SoundInstance3d extends SoundInstance {\n\t\tconstructor(manager, sound, options = {}){\n\t\t\t\tsuper(manager, sound, options), this._position = new Vec3(), this._velocity = new Vec3();\n\t\t\t\tif (options.position) {\n\t\t\t\t\t\tthis.position = options.position;\n\t\t\t\t}\n\t\t\t\tthis.maxDistance = options.maxDistance !== undefined ? Number(options.maxDistance) : MAX_DISTANCE;\n\t\t\t\tthis.refDistance = options.refDistance !== undefined ? Number(options.refDistance) : 1;\n\t\t\t\tthis.rollOffFactor = options.rollOffFactor !== undefined ? Number(options.rollOffFactor) : 1;\n\t\t\t\tthis.distanceModel = options.distanceModel !== undefined ? options.distanceModel : DISTANCE_LINEAR;\n\t\t}\n\t\t_initializeNodes() {\n\t\t\t\tthis.gain = this._manager.context.createGain();\n\t\t\t\tthis.panner = this._manager.context.createPanner();\n\t\t\t\tthis.panner.connect(this.gain);\n\t\t\t\tthis._inputNode = this.panner;\n\t\t\t\tthis._connectorNode = this.gain;\n\t\t\t\tthis._connectorNode.connect(this._manager.context.destination);\n\t\t}\n\t\tset position(value) {\n\t\t\t\tthis._position.copy(value);\n\t\t\t\tconst panner = this.panner;\n\t\t\t\tif ('positionX' in panner) {\n\t\t\t\t\t\tpanner.positionX.value = value.x;\n\t\t\t\t\t\tpanner.positionY.value = value.y;\n\t\t\t\t\t\tpanner.positionZ.value = value.z;\n\t\t\t\t} else if (panner.setPosition) {\n\t\t\t\t\t\tpanner.setPosition(value.x, value.y, value.z);\n\t\t\t\t}\n\t\t}\n\t\tget position() {\n\t\t\t\treturn this._position;\n\t\t}\n\t\tset velocity(velocity) {\n\t\t\t\tthis._velocity.copy(velocity);\n\t\t}\n\t\tget velocity() {\n\t\t\t\treturn this._velocity;\n\t\t}\n\t\tset maxDistance(value) {\n\t\t\t\tthis.panner.maxDistance = value;\n\t\t}\n\t\tget maxDistance() {\n\t\t\t\treturn this.panner.maxDistance;\n\t\t}\n\t\tset refDistance(value) {\n\t\t\t\tthis.panner.refDistance = value;\n\t\t}\n\t\tget refDistance() {\n\t\t\t\treturn this.panner.refDistance;\n\t\t}\n\t\tset rollOffFactor(value) {\n\t\t\t\tthis.panner.rolloffFactor = value;\n\t\t}\n\t\tget rollOffFactor() {\n\t\t\t\treturn this.panner.rolloffFactor;\n\t\t}\n\t\tset distanceModel(value) {\n\t\t\t\tthis.panner.distanceModel = value;\n\t\t}\n\t\tget distanceModel() {\n\t\t\t\treturn this.panner.distanceModel;\n\t\t}\n}\nif (!hasAudioContext()) {\n\t\tlet offset = new Vec3();\n\t\tconst fallOff = function(posOne, posTwo, refDistance, maxDistance, rollOffFactor, distanceModel) {\n\t\t\t\toffset = offset.sub2(posOne, posTwo);\n\t\t\t\tconst distance = offset.length();\n\t\t\t\tif (distance < refDistance) {\n\t\t\t\t\t\treturn 1;\n\t\t\t\t} else if (distance > maxDistance) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tlet result = 0;\n\t\t\t\tif (distanceModel === DISTANCE_LINEAR) {\n\t\t\t\t\t\tresult = 1 - rollOffFactor * (distance - refDistance) / (maxDistance - refDistance);\n\t\t\t\t} else if (distanceModel === DISTANCE_INVERSE) {\n\t\t\t\t\t\tresult = refDistance / (refDistance + rollOffFactor * (distance - refDistance));\n\t\t\t\t} else if (distanceModel === DISTANCE_EXPONENTIAL) {\n\t\t\t\t\t\tresult = Math.pow(distance / refDistance, -rollOffFactor);\n\t\t\t\t}\n\t\t\t\treturn math.clamp(result, 0, 1);\n\t\t};\n\t\tObject.defineProperty(SoundInstance3d.prototype, 'position', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._position;\n\t\t\t\t},\n\t\t\t\tset: function(position) {\n\t\t\t\t\t\tthis._position.copy(position);\n\t\t\t\t\t\tif (this.source) {\n\t\t\t\t\t\t\t\tconst listener = this._manager.listener;\n\t\t\t\t\t\t\t\tconst lpos = listener.getPosition();\n\t\t\t\t\t\t\t\tconst factor = fallOff(lpos, this._position, this.refDistance, this.maxDistance, this.rollOffFactor, this.distanceModel);\n\t\t\t\t\t\t\t\tconst v = this.volume;\n\t\t\t\t\t\t\t\tthis.source.volume = v * factor * this._manager.volume;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance3d.prototype, 'maxDistance', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._maxDistance;\n\t\t\t\t},\n\t\t\t\tset: function(value) {\n\t\t\t\t\t\tthis._maxDistance = value;\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance3d.prototype, 'refDistance', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._refDistance;\n\t\t\t\t},\n\t\t\t\tset: function(value) {\n\t\t\t\t\t\tthis._refDistance = value;\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance3d.prototype, 'rollOffFactor', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._rollOffFactor;\n\t\t\t\t},\n\t\t\t\tset: function(value) {\n\t\t\t\t\t\tthis._rollOffFactor = value;\n\t\t\t\t}\n\t\t});\n\t\tObject.defineProperty(SoundInstance3d.prototype, 'distanceModel', {\n\t\t\t\tget: function() {\n\t\t\t\t\t\treturn this._distanceModel;\n\t\t\t\t},\n\t\t\t\tset: function(value) {\n\t\t\t\t\t\tthis._distanceModel = value;\n\t\t\t\t}\n\t\t});\n}\n\nconst BLEND_SUBTRACTIVE = 0;\nconst BLEND_ADDITIVE = 1;\nconst BLEND_NORMAL = 2;\nconst BLEND_NONE = 3;\nconst BLEND_PREMULTIPLIED = 4;\nconst BLEND_MULTIPLICATIVE = 5;\nconst BLEND_ADDITIVEALPHA = 6;\nconst BLEND_MULTIPLICATIVE2X = 7;\nconst BLEND_SCREEN = 8;\nconst BLEND_MIN = 9;\nconst BLEND_MAX = 10;\nconst blendNames = {\n\t\t[BLEND_SUBTRACTIVE]: 'SUBTRACTIVE',\n\t\t[BLEND_ADDITIVE]: 'ADDITIVE',\n\t\t[BLEND_NORMAL]: 'NORMAL',\n\t\t[BLEND_NONE]: 'NONE',\n\t\t[BLEND_PREMULTIPLIED]: 'PREMULTIPLIED',\n\t\t[BLEND_MULTIPLICATIVE]: 'MULTIPLICATIVE',\n\t\t[BLEND_ADDITIVEALPHA]: 'ADDITIVEALPHA',\n\t\t[BLEND_MULTIPLICATIVE2X]: 'MULTIPLICATIVE2X',\n\t\t[BLEND_SCREEN]: 'SCREEN',\n\t\t[BLEND_MIN]: 'MIN',\n\t\t[BLEND_MAX]: 'MAX'\n};\nconst FOG_NONE = 'none';\nconst FOG_LINEAR = 'linear';\nconst FRESNEL_NONE = 0;\nconst FRESNEL_SCHLICK = 2;\nconst fresnelNames = {\n\t\t[FRESNEL_NONE]: 'NONE',\n\t\t[FRESNEL_SCHLICK]: 'SCHLICK'\n};\nconst LAYER_HUD = 0;\nconst LAYER_WORLD = 15;\nconst LAYERID_WORLD = 0;\nconst LAYERID_DEPTH = 1;\nconst LAYERID_SKYBOX = 2;\nconst LAYERID_IMMEDIATE = 3;\nconst LAYERID_UI = 4;\nconst LIGHTTYPE_DIRECTIONAL = 0;\nconst LIGHTTYPE_OMNI = 1;\nconst LIGHTTYPE_SPOT = 2;\nconst lightTypeNames = {\n\t\t[LIGHTTYPE_DIRECTIONAL]: 'DIRECTIONAL',\n\t\t[LIGHTTYPE_OMNI]: 'OMNI',\n\t\t[LIGHTTYPE_SPOT]: 'SPOT'\n};\nconst LIGHT_COLOR_DIVIDER = 100;\nconst LIGHTSHAPE_PUNCTUAL = 0;\nconst LIGHTSHAPE_RECT = 1;\nconst LIGHTSHAPE_DISK = 2;\nconst LIGHTSHAPE_SPHERE = 3;\nconst lightShapeNames = {\n\t\t[LIGHTSHAPE_PUNCTUAL]: 'PUNCTUAL',\n\t\t[LIGHTSHAPE_RECT]: 'RECT',\n\t\t[LIGHTSHAPE_DISK]: 'DISK',\n\t\t[LIGHTSHAPE_SPHERE]: 'SPHERE'\n};\nconst LIGHTFALLOFF_LINEAR = 0;\nconst LIGHTFALLOFF_INVERSESQUARED = 1;\nconst lightFalloffNames = {\n\t\t[LIGHTFALLOFF_LINEAR]: 'LINEAR',\n\t\t[LIGHTFALLOFF_INVERSESQUARED]: 'INVERSESQUARED'\n};\nconst SHADOW_PCF3_32F = 0;\nconst SHADOW_VSM_16F = 2;\nconst SHADOW_VSM_32F = 3;\nconst SHADOW_PCF5_32F = 4;\nconst SHADOW_PCF1_32F = 5;\nconst SHADOW_PCSS_32F = 6;\nconst SHADOW_PCF1_16F = 7;\nconst SHADOW_PCF3_16F = 8;\nconst SHADOW_PCF5_16F = 9;\nconst shadowTypeInfo = new Map([\n\t\t[\n\t\t\t\tSHADOW_PCF1_32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PCF1_32F',\n\t\t\t\t\t\tkind: 'PCF1',\n\t\t\t\t\t\tformat: PIXELFORMAT_DEPTH,\n\t\t\t\t\t\tpcf: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_PCF3_32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PCF3_32F',\n\t\t\t\t\t\tkind: 'PCF3',\n\t\t\t\t\t\tformat: PIXELFORMAT_DEPTH,\n\t\t\t\t\t\tpcf: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_PCF5_32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PCF5_32F',\n\t\t\t\t\t\tkind: 'PCF5',\n\t\t\t\t\t\tformat: PIXELFORMAT_DEPTH,\n\t\t\t\t\t\tpcf: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_PCF1_16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PCF1_16F',\n\t\t\t\t\t\tkind: 'PCF1',\n\t\t\t\t\t\tformat: PIXELFORMAT_DEPTH16,\n\t\t\t\t\t\tpcf: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_PCF3_16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PCF3_16F',\n\t\t\t\t\t\tkind: 'PCF3',\n\t\t\t\t\t\tformat: PIXELFORMAT_DEPTH16,\n\t\t\t\t\t\tpcf: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_PCF5_16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PCF5_16F',\n\t\t\t\t\t\tkind: 'PCF5',\n\t\t\t\t\t\tformat: PIXELFORMAT_DEPTH16,\n\t\t\t\t\t\tpcf: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_VSM_16F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'VSM_16F',\n\t\t\t\t\t\tkind: 'VSM',\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA16F,\n\t\t\t\t\t\tvsm: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_VSM_32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'VSM_32F',\n\t\t\t\t\t\tkind: 'VSM',\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA32F,\n\t\t\t\t\t\tvsm: true\n\t\t\t\t}\n\t\t],\n\t\t[\n\t\t\t\tSHADOW_PCSS_32F,\n\t\t\t\t{\n\t\t\t\t\t\tname: 'PCSS_32F',\n\t\t\t\t\t\tkind: 'PCSS',\n\t\t\t\t\t\tformat: PIXELFORMAT_R32F,\n\t\t\t\t\t\tpcss: true\n\t\t\t\t}\n\t\t]\n]);\nconst SHADOW_CASCADE_ALL = 255;\nconst BLUR_GAUSSIAN = 1;\nconst PARTICLESORT_NONE = 0;\nconst PARTICLEMODE_GPU = 0;\nconst EMITTERSHAPE_BOX = 0;\nconst EMITTERSHAPE_SPHERE = 1;\nconst PARTICLEORIENTATION_SCREEN = 0;\nconst PARTICLEORIENTATION_WORLD = 1;\nconst PROJECTION_PERSPECTIVE = 0;\nconst PROJECTION_ORTHOGRAPHIC = 1;\nconst RENDERSTYLE_SOLID = 0;\nconst RENDERSTYLE_WIREFRAME = 1;\nconst RENDERSTYLE_POINTS = 2;\nconst CUBEPROJ_NONE = 0;\nconst CUBEPROJ_BOX = 1;\nconst cubemaProjectionNames = {\n\t\t[CUBEPROJ_NONE]: 'NONE',\n\t\t[CUBEPROJ_BOX]: 'BOX'\n};\nconst DETAILMODE_MUL = 'mul';\nconst GAMMA_NONE = 0;\nconst GAMMA_SRGB = 1;\nconst gammaNames = {\n\t\t[GAMMA_NONE]: 'NONE',\n\t\t[GAMMA_SRGB]: 'SRGB'\n};\nconst TONEMAP_LINEAR = 0;\nconst TONEMAP_FILMIC = 1;\nconst TONEMAP_HEJL = 2;\nconst TONEMAP_ACES = 3;\nconst TONEMAP_ACES2 = 4;\nconst TONEMAP_NEUTRAL = 5;\nconst TONEMAP_NONE = 6;\nconst tonemapNames = [\n\t\t'LINEAR',\n\t\t'FILMIC',\n\t\t'HEJL',\n\t\t'ACES',\n\t\t'ACES2',\n\t\t'NEUTRAL',\n\t\t'NONE'\n];\nconst SPECOCC_NONE = 0;\nconst SPECOCC_AO = 1;\nconst SPECOCC_GLOSSDEPENDENT = 2;\nconst specularOcclusionNames = {\n\t\t[SPECOCC_NONE]: 'NONE',\n\t\t[SPECOCC_AO]: 'AO',\n\t\t[SPECOCC_GLOSSDEPENDENT]: 'GLOSSDEPENDENT'\n};\nconst REFLECTIONSRC_NONE = 'none';\nconst REFLECTIONSRC_ENVATLAS = 'envAtlas';\nconst REFLECTIONSRC_ENVATLASHQ = 'envAtlasHQ';\nconst REFLECTIONSRC_CUBEMAP = 'cubeMap';\nconst REFLECTIONSRC_SPHEREMAP = 'sphereMap';\nconst reflectionSrcNames = {\n\t\t[REFLECTIONSRC_NONE]: 'NONE',\n\t\t[REFLECTIONSRC_ENVATLAS]: 'ENVATLAS',\n\t\t[REFLECTIONSRC_ENVATLASHQ]: 'ENVATLASHQ',\n\t\t[REFLECTIONSRC_CUBEMAP]: 'CUBEMAP',\n\t\t[REFLECTIONSRC_SPHEREMAP]: 'SPHEREMAP'\n};\nconst AMBIENTSRC_AMBIENTSH = 'ambientSH';\nconst AMBIENTSRC_ENVALATLAS = 'envAtlas';\nconst AMBIENTSRC_CONSTANT = 'constant';\nconst ambientSrcNames = {\n\t\t[AMBIENTSRC_AMBIENTSH]: 'AMBIENTSH',\n\t\t[AMBIENTSRC_ENVALATLAS]: 'ENVALATLAS',\n\t\t[AMBIENTSRC_CONSTANT]: 'CONSTANT'\n};\nconst SHADERDEF_NOSHADOW = 1;\nconst SHADERDEF_SKIN = 2;\nconst SHADERDEF_UV0 = 4;\nconst SHADERDEF_UV1 = 8;\nconst SHADERDEF_VCOLOR = 16;\nconst SHADERDEF_INSTANCING = 32;\nconst SHADERDEF_LM = 64;\nconst SHADERDEF_DIRLM = 128;\nconst SHADERDEF_SCREENSPACE = 256;\nconst SHADERDEF_TANGENTS = 512;\nconst SHADERDEF_MORPH_POSITION = 1024;\nconst SHADERDEF_MORPH_NORMAL = 2048;\nconst SHADERDEF_LMAMBIENT = 4096;\nconst SHADERDEF_MORPH_TEXTURE_BASED_INT = 8192;\nconst SHADERDEF_BATCH = 16384;\nconst SHADOWUPDATE_NONE = 0;\nconst SHADOWUPDATE_THISFRAME = 1;\nconst SHADOWUPDATE_REALTIME = 2;\nconst MASK_AFFECT_DYNAMIC = 1;\nconst MASK_AFFECT_LIGHTMAPPED = 2;\nconst MASK_BAKE = 4;\nconst SHADER_FORWARD = 0;\nconst SHADER_PREPASS = 1;\nconst SHADER_SHADOW = 2;\nconst SHADER_PICK = 3;\nconst SPRITE_RENDERMODE_SIMPLE = 0;\nconst SPRITE_RENDERMODE_SLICED = 1;\nconst SPRITE_RENDERMODE_TILED = 2;\nconst spriteRenderModeNames = {\n\t\t[SPRITE_RENDERMODE_SIMPLE]: 'SIMPLE',\n\t\t[SPRITE_RENDERMODE_SLICED]: 'SLICED',\n\t\t[SPRITE_RENDERMODE_TILED]: 'TILED'\n};\nconst BAKE_COLORDIR = 1;\nconst VIEW_CENTER = 0;\nconst SORTMODE_NONE = 0;\nconst SORTMODE_MANUAL = 1;\nconst SORTMODE_MATERIALMESH = 2;\nconst SORTMODE_BACK2FRONT = 3;\nconst SORTMODE_FRONT2BACK = 4;\nconst SORTMODE_CUSTOM = 5;\nconst ASPECT_AUTO = 0;\nconst ASPECT_MANUAL = 1;\nconst ORIENTATION_HORIZONTAL = 0;\nconst ORIENTATION_VERTICAL = 1;\nconst SKYTYPE_INFINITE = 'infinite';\nconst SKYTYPE_BOX = 'box';\nconst SKYTYPE_DOME = 'dome';\nconst DITHER_NONE = 'none';\nconst DITHER_BAYER8 = 'bayer8';\nconst DITHER_BLUENOISE = 'bluenoise';\nconst DITHER_IGNNOISE = 'ignnoise';\nconst ditherNames = {\n\t\t[DITHER_NONE]: 'NONE',\n\t\t[DITHER_BAYER8]: 'BAYER8',\n\t\t[DITHER_BLUENOISE]: 'BLUENOISE',\n\t\t[DITHER_IGNNOISE]: 'IGNNOISE'\n};\nconst EVENT_PRERENDER = 'prerender';\nconst EVENT_POSTRENDER = 'postrender';\nconst EVENT_PRERENDER_LAYER = 'prerender:layer';\nconst EVENT_POSTRENDER_LAYER = 'postrender:layer';\nconst EVENT_PRECULL = 'precull';\nconst EVENT_POSTCULL = 'postcull';\nconst EVENT_CULL_END = 'cull:end';\n\nclass ShaderProcessorOptions {\n\t\tconstructor(viewUniformFormat, viewBindGroupFormat, vertexFormat){\n\t\t\t\tthis.uniformFormats = [];\n\t\t\t\tthis.bindGroupFormats = [];\n\t\t\t\tthis.uniformFormats[BINDGROUP_VIEW] = viewUniformFormat;\n\t\t\t\tthis.bindGroupFormats[BINDGROUP_VIEW] = viewBindGroupFormat;\n\t\t\t\tthis.vertexFormat = vertexFormat;\n\t\t}\n\t\thasUniform(name) {\n\t\t\t\tfor(let i = 0; i < this.uniformFormats.length; i++){\n\t\t\t\t\t\tconst uniformFormat = this.uniformFormats[i];\n\t\t\t\t\t\tif (uniformFormat?.get(name)) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\thasTexture(name) {\n\t\t\t\tfor(let i = 0; i < this.bindGroupFormats.length; i++){\n\t\t\t\t\t\tconst groupFormat = this.bindGroupFormats[i];\n\t\t\t\t\t\tif (groupFormat?.getTexture(name)) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tgetVertexElement(semantic) {\n\t\t\t\treturn this.vertexFormat?.elements.find((element)=>element.name === semantic);\n\t\t}\n\t\tgenerateKey(device) {\n\t\t\t\tlet key = JSON.stringify(this.uniformFormats) + JSON.stringify(this.bindGroupFormats);\n\t\t\t\tif (device.isWebGPU) {\n\t\t\t\t\t\tkey += this.vertexFormat?.shaderProcessingHashString;\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t}\n}\n\nconst programLibraryDeviceCache = new DeviceCache();\nfunction getProgramLibrary(device) {\n\t\tconst library = programLibraryDeviceCache.get(device);\n\t\treturn library;\n}\nfunction setProgramLibrary(device, library) {\n\t\tprogramLibraryDeviceCache.get(device, ()=>{\n\t\t\t\treturn library;\n\t\t});\n}\n\nclass ShaderGenerator {\n\t\tstatic definesHash(defines) {\n\t\t\t\tconst sortedArray = Array.from(defines).sort((a, b)=>a[0] > b[0] ? 1 : -1);\n\t\t\t\treturn hashCode(JSON.stringify(sortedArray));\n\t\t}\n}\n\nconst shaderPassDeviceCache = new DeviceCache();\nclass ShaderPassInfo {\n\t\tconstructor(name, index, options = {}){\n\t\t\t\tthis.defines = new Map();\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.index = index;\n\t\t\t\tObject.assign(this, options);\n\t\t\t\tthis.buildShaderDefines();\n\t\t}\n\t\tbuildShaderDefines() {\n\t\t\t\tlet keyword;\n\t\t\t\tif (this.isShadow) {\n\t\t\t\t\t\tkeyword = 'SHADOW';\n\t\t\t\t} else if (this.isForward) {\n\t\t\t\t\t\tkeyword = 'FORWARD';\n\t\t\t\t} else if (this.index === SHADER_PICK) {\n\t\t\t\t\t\tkeyword = 'PICK';\n\t\t\t\t}\n\t\t\t\tthis.defines.set(`${keyword}_PASS`, '');\n\t\t\t\tthis.defines.set(`${this.name.toUpperCase()}_PASS`, '');\n\t\t}\n}\nclass ShaderPass {\n\t\tconstructor(){\n\t\t\t\tthis.passesNamed = new Map();\n\t\t\t\tthis.passesIndexed = [];\n\t\t\t\tthis.nextIndex = 0;\n\t\t\t\tconst add = (name, index, options)=>{\n\t\t\t\t\t\tthis.allocate(name, options);\n\t\t\t\t};\n\t\t\t\tadd('forward', SHADER_FORWARD, {\n\t\t\t\t\t\tisForward: true\n\t\t\t\t});\n\t\t\t\tadd('prepass');\n\t\t\t\tadd('shadow');\n\t\t\t\tadd('pick');\n\t\t}\n\t\tstatic get(device) {\n\t\t\t\treturn shaderPassDeviceCache.get(device, ()=>{\n\t\t\t\t\t\treturn new ShaderPass();\n\t\t\t\t});\n\t\t}\n\t\tallocate(name, options) {\n\t\t\t\tlet info = this.passesNamed.get(name);\n\t\t\t\tif (info === undefined) {\n\t\t\t\t\t\tinfo = new ShaderPassInfo(name, this.nextIndex, options);\n\t\t\t\t\t\tthis.passesNamed.set(info.name, info);\n\t\t\t\t\t\tthis.passesIndexed[info.index] = info;\n\t\t\t\t\t\tthis.nextIndex++;\n\t\t\t\t}\n\t\t\t\treturn info;\n\t\t}\n\t\tgetByIndex(index) {\n\t\t\t\tconst info = this.passesIndexed[index];\n\t\t\t\treturn info;\n\t\t}\n\t\tgetByName(name) {\n\t\t\t\treturn this.passesNamed.get(name);\n\t\t}\n}\n\nclass ShaderChunkMap extends Map {\n\t\tset(name, code) {\n\t\t\t\tif (!this.has(name) || this.get(name) !== code) {\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t\t\treturn super.set(name, code);\n\t\t}\n\t\tadd(object) {\n\t\t\t\tfor (const [key, value] of Object.entries(object)){\n\t\t\t\t\t\tthis.set(key, value);\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tdelete(name) {\n\t\t\t\tconst existed = this.has(name);\n\t\t\t\tconst result = super.delete(name);\n\t\t\t\tif (existed && result) {\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tclear() {\n\t\t\t\tif (this.size > 0) {\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t\t\tsuper.clear();\n\t\t}\n\t\tmarkDirty() {\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis._keyDirty = true;\n\t\t}\n\t\tisDirty() {\n\t\t\t\treturn this._dirty;\n\t\t}\n\t\tresetDirty() {\n\t\t\t\tthis._dirty = false;\n\t\t}\n\t\tget key() {\n\t\t\t\tif (this._keyDirty) {\n\t\t\t\t\t\tthis._keyDirty = false;\n\t\t\t\t\t\tthis._key = Array.from(this.entries()).sort(([a], [b])=>a < b ? -1 : a > b ? 1 : 0).map(([k, v])=>`${k}=${hashCode(v)}`).join(',');\n\t\t\t\t}\n\t\t\t\treturn this._key;\n\t\t}\n\t\tcopy(source) {\n\t\t\t\tthis.clear();\n\t\t\t\tfor (const [key, value] of source){\n\t\t\t\t\t\tthis.set(key, value);\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._keyDirty = false, this._key = '';\n\t\t}\n}\n\nconst _chunksCache = new DeviceCache();\nclass ShaderChunks {\n\t\tstatic get(device, shaderLanguage = SHADERLANGUAGE_GLSL) {\n\t\t\t\tconst cache = _chunksCache.get(device, ()=>{\n\t\t\t\t\t\treturn new ShaderChunks();\n\t\t\t\t});\n\t\t\t\treturn shaderLanguage === SHADERLANGUAGE_GLSL ? cache.glsl : cache.wgsl;\n\t\t}\n\t\tget useWGSL() {\n\t\t\t\treturn this.glsl.size === 0 || this.wgsl.size > 0;\n\t\t}\n\t\tget key() {\n\t\t\t\treturn `GLSL:${this.glsl.key}|WGSL:${this.wgsl.key}|API:${this.version}`;\n\t\t}\n\t\tisDirty() {\n\t\t\t\treturn this.glsl.isDirty() || this.wgsl.isDirty();\n\t\t}\n\t\tresetDirty() {\n\t\t\t\tthis.glsl.resetDirty();\n\t\t\t\tthis.wgsl.resetDirty();\n\t\t}\n\t\tcopy(source) {\n\t\t\t\tthis.version = source.version;\n\t\t\t\tthis.glsl.copy(source.glsl);\n\t\t\t\tthis.wgsl.copy(source.wgsl);\n\t\t\t\treturn this;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.glsl = new ShaderChunkMap();\n\t\t\t\tthis.wgsl = new ShaderChunkMap();\n\t\t\t\tthis.version = '';\n\t\t}\n}\n\nclass MapUtils {\n\t\tstatic merge(...maps) {\n\t\t\t\tconst result = new Map(maps[0] ?? []);\n\t\t\t\tfor(let i = 1; i < maps.length; i++){\n\t\t\t\t\t\tconst map = maps[i];\n\t\t\t\t\t\tif (map) {\n\t\t\t\t\t\t\t\tfor (const [key, value] of map){\n\t\t\t\t\t\t\t\t\t\tresult.set(key, value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n}\n\nclass ShaderGeneratorPassThrough extends ShaderGenerator {\n\t\tconstructor(key, shaderDefinition){\n\t\t\t\tsuper();\n\t\t\t\tthis.key = key;\n\t\t\t\tthis.shaderDefinition = shaderDefinition;\n\t\t}\n\t\tgenerateKey(options) {\n\t\t\t\treturn this.key;\n\t\t}\n\t\tcreateShaderDefinition(device, options) {\n\t\t\t\treturn this.shaderDefinition;\n\t\t}\n}\nclass ShaderUtils {\n\t\tstatic createShader(device, options) {\n\t\t\t\tconst programLibrary = getProgramLibrary(device);\n\t\t\t\tlet shader = programLibrary.getCachedShader(options.uniqueName);\n\t\t\t\tif (!shader) {\n\t\t\t\t\t\tconst wgsl = device.isWebGPU && (!!options.vertexWGSL || !!options.vertexChunk) && (!!options.fragmentWGSL || !!options.fragmentChunk);\n\t\t\t\t\t\tconst chunksMap = ShaderChunks.get(device, wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL);\n\t\t\t\t\t\tconst vertexCode = options.vertexChunk ? chunksMap.get(options.vertexChunk) : wgsl ? options.vertexWGSL : options.vertexGLSL;\n\t\t\t\t\t\tconst fragmentCode = options.fragmentChunk ? chunksMap.get(options.fragmentChunk) : wgsl ? options.fragmentWGSL : options.fragmentGLSL;\n\t\t\t\t\t\tconst fragmentIncludes = MapUtils.merge(chunksMap, options.fragmentIncludes);\n\t\t\t\t\t\tconst vertexIncludes = MapUtils.merge(chunksMap, options.vertexIncludes);\n\t\t\t\t\t\tshader = new Shader(device, ShaderDefinitionUtils.createDefinition(device, {\n\t\t\t\t\t\t\t\tname: options.uniqueName,\n\t\t\t\t\t\t\t\tshaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL,\n\t\t\t\t\t\t\t\tattributes: options.attributes,\n\t\t\t\t\t\t\t\tvertexCode: vertexCode,\n\t\t\t\t\t\t\t\tfragmentCode: fragmentCode,\n\t\t\t\t\t\t\t\tuseTransformFeedback: options.useTransformFeedback,\n\t\t\t\t\t\t\t\tvertexIncludes: vertexIncludes,\n\t\t\t\t\t\t\t\tvertexDefines: options.vertexDefines,\n\t\t\t\t\t\t\t\tfragmentIncludes: fragmentIncludes,\n\t\t\t\t\t\t\t\tfragmentDefines: options.fragmentDefines,\n\t\t\t\t\t\t\t\tfragmentOutputTypes: options.fragmentOutputTypes\n\t\t\t\t\t\t}));\n\t\t\t\t\t\tprogramLibrary.setCachedShader(options.uniqueName, shader);\n\t\t\t\t}\n\t\t\t\treturn shader;\n\t\t}\n\t\tstatic getCoreDefines(material, params) {\n\t\t\t\tconst defines = new Map(material.defines);\n\t\t\t\tparams.cameraShaderParams.defines.forEach((value, key)=>defines.set(key, value));\n\t\t\t\tconst shaderPassInfo = ShaderPass.get(params.device).getByIndex(params.pass);\n\t\t\t\tshaderPassInfo.defines.forEach((value, key)=>defines.set(key, value));\n\t\t\t\treturn defines;\n\t\t}\n\t\tstatic processShader(shader, processingOptions) {\n\t\t\t\tconst shaderDefinition = shader.definition;\n\t\t\t\tconst name = shaderDefinition.name ?? 'shader';\n\t\t\t\tconst key = `${name}-id-${shader.id}`;\n\t\t\t\tconst materialGenerator = new ShaderGeneratorPassThrough(key, shaderDefinition);\n\t\t\t\tconst libraryModuleName = 'shader';\n\t\t\t\tconst library = getProgramLibrary(shader.device);\n\t\t\t\tlibrary.register(libraryModuleName, materialGenerator);\n\t\t\t\tconst variant = library.getProgram(libraryModuleName, {}, processingOptions);\n\t\t\t\tlibrary.unregister(libraryModuleName);\n\t\t\t\treturn variant;\n\t\t}\n\t\tstatic addScreenDepthChunkDefines(device, cameraShaderParams, defines) {\n\t\t\t\tif (cameraShaderParams.sceneDepthMapLinear) {\n\t\t\t\t\t\tdefines.set('SCENE_DEPTHMAP_LINEAR', '');\n\t\t\t\t}\n\t\t\t\tif (device.textureFloatRenderable) {\n\t\t\t\t\t\tdefines.set('SCENE_DEPTHMAP_FLOAT', '');\n\t\t\t\t}\n\t\t}\n}\n\nconst _quadPrimitive = {\n\t\ttype: PRIMITIVE_TRISTRIP,\n\t\tbase: 0,\n\t\tbaseVertex: 0,\n\t\tcount: 4,\n\t\tindexed: false\n};\nconst _tempViewport = new Vec4();\nconst _tempScissor = new Vec4();\nconst _dynamicBindGroup$1 = new DynamicBindGroup();\nclass QuadRender {\n\t\tconstructor(shader){\n\t\t\t\tconst device = shader.device;\n\t\t\t\tthis.shader = shader;\n\t\t\t\tif (device.supportsUniformBuffers) {\n\t\t\t\t\t\tconst processingOptions = new ShaderProcessorOptions();\n\t\t\t\t\t\tthis.shader = ShaderUtils.processShader(shader, processingOptions);\n\t\t\t\t\t\tconst ubFormat = this.shader.meshUniformBufferFormat;\n\t\t\t\t\t\tif (ubFormat) {\n\t\t\t\t\t\t\t\tthis.uniformBuffer = new UniformBuffer(device, ubFormat, false);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst bindGroupFormat = this.shader.meshBindGroupFormat;\n\t\t\t\t\t\tthis.bindGroup = new BindGroup(device, bindGroupFormat);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.uniformBuffer?.destroy();\n\t\t\t\tthis.uniformBuffer = null;\n\t\t\t\tthis.bindGroup?.destroy();\n\t\t\t\tthis.bindGroup = null;\n\t\t}\n\t\trender(viewport, scissor) {\n\t\t\t\tconst device = this.shader.device;\n\t\t\t\tif (viewport) {\n\t\t\t\t\t\t_tempViewport.set(device.vx, device.vy, device.vw, device.vh);\n\t\t\t\t\t\t_tempScissor.set(device.sx, device.sy, device.sw, device.sh);\n\t\t\t\t\t\tscissor = scissor ?? viewport;\n\t\t\t\t\t\tdevice.setViewport(viewport.x, viewport.y, viewport.z, viewport.w);\n\t\t\t\t\t\tdevice.setScissor(scissor.x, scissor.y, scissor.z, scissor.w);\n\t\t\t\t}\n\t\t\t\tdevice.setVertexBuffer(device.quadVertexBuffer);\n\t\t\t\tconst shader = this.shader;\n\t\t\t\tdevice.setShader(shader);\n\t\t\t\tif (device.supportsUniformBuffers) {\n\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_VIEW, device.emptyBindGroup);\n\t\t\t\t\t\tconst bindGroup = this.bindGroup;\n\t\t\t\t\t\tbindGroup.update();\n\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_MESH, bindGroup);\n\t\t\t\t\t\tconst uniformBuffer = this.uniformBuffer;\n\t\t\t\t\t\tif (uniformBuffer) {\n\t\t\t\t\t\t\t\tuniformBuffer.update(_dynamicBindGroup$1);\n\t\t\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_MESH_UB, _dynamicBindGroup$1.bindGroup, _dynamicBindGroup$1.offsets);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_MESH_UB, device.emptyBindGroup);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdevice.draw(_quadPrimitive);\n\t\t\t\tif (viewport) {\n\t\t\t\t\t\tdevice.setViewport(_tempViewport.x, _tempViewport.y, _tempViewport.z, _tempViewport.w);\n\t\t\t\t\t\tdevice.setScissor(_tempScissor.x, _tempScissor.y, _tempScissor.z, _tempScissor.w);\n\t\t\t\t}\n\t\t}\n}\n\nclass RenderPassQuad extends RenderPass {\n\t\tconstructor(device, quad, rect, scissorRect){\n\t\t\t\tsuper(device);\n\t\t\t\tthis.quad = quad;\n\t\t\t\tthis.rect = rect;\n\t\t\t\tthis.scissorRect = scissorRect;\n\t\t}\n\t\texecute() {\n\t\t\t\tconst { device } = this;\n\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\tdevice.setStencilState(null, null);\n\t\t\t\tthis.quad.render(this.rect, this.scissorRect);\n\t\t}\n}\n\nconst _tempRect = new Vec4();\nfunction drawQuadWithShader(device, target, shader, rect, scissorRect) {\n\t\tconst quad = new QuadRender(shader);\n\t\tif (!rect) {\n\t\t\t\trect = _tempRect;\n\t\t\t\trect.x = 0;\n\t\t\t\trect.y = 0;\n\t\t\t\trect.z = target ? target.width : device.width;\n\t\t\t\trect.w = target ? target.height : device.height;\n\t\t}\n\t\tconst renderPass = new RenderPassQuad(device, quad, rect, scissorRect);\n\t\trenderPass.init(target);\n\t\trenderPass.colorOps.clear = false;\n\t\trenderPass.depthStencilOps.clearDepth = false;\n\t\tif (device.isWebGPU && target === null && device.samples > 1) {\n\t\t\t\trenderPass.colorOps.store = true;\n\t\t}\n\t\trenderPass.render();\n\t\tquad.destroy();\n}\n\nclass Batch {\n\t\tconstructor(meshInstances, dynamic, batchGroupId){\n\t\t\t\tthis._aabb = new BoundingBox();\n\t\t\t\tthis.meshInstance = null;\n\t\t\t\tthis.origMeshInstances = meshInstances;\n\t\t\t\tthis.dynamic = dynamic;\n\t\t\t\tthis.batchGroupId = batchGroupId;\n\t\t}\n\t\tdestroy(scene, layers) {\n\t\t\t\tif (this.meshInstance) {\n\t\t\t\t\t\tthis.removeFromLayers(scene, layers);\n\t\t\t\t\t\tthis.meshInstance.destroy();\n\t\t\t\t\t\tthis.meshInstance = null;\n\t\t\t\t}\n\t\t}\n\t\taddToLayers(scene, layers) {\n\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\tconst layer = scene.layers.getLayerById(layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.addMeshInstances([\n\t\t\t\t\t\t\t\t\t\tthis.meshInstance\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremoveFromLayers(scene, layers) {\n\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\tconst layer = scene.layers.getLayerById(layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.removeMeshInstances([\n\t\t\t\t\t\t\t\t\t\tthis.meshInstance\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdateBoundingBox() {\n\t\t\t\tthis._aabb.copy(this.origMeshInstances[0].aabb);\n\t\t\t\tfor(let i = 1; i < this.origMeshInstances.length; i++){\n\t\t\t\t\t\tthis._aabb.add(this.origMeshInstances[i].aabb);\n\t\t\t\t}\n\t\t\t\tthis.meshInstance.aabb = this._aabb;\n\t\t\t\tthis.meshInstance._aabbVer = 0;\n\t\t}\n\t\tget model() {\n\t\t\t\treturn undefined;\n\t\t}\n}\n\nclass BatchGroup {\n\t\tconstructor(id, name, dynamic, maxAabbSize, layers = [\n\t\t\t\tLAYERID_WORLD\n\t\t]){\n\t\t\t\tthis._ui = false;\n\t\t\t\tthis._sprite = false;\n\t\t\t\tthis._obj = {\n\t\t\t\t\t\tmodel: [],\n\t\t\t\t\t\telement: [],\n\t\t\t\t\t\tsprite: [],\n\t\t\t\t\t\trender: []\n\t\t\t\t};\n\t\t\t\tthis.id = id;\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.dynamic = dynamic;\n\t\t\t\tthis.maxAabbSize = maxAabbSize;\n\t\t\t\tthis.layers = layers;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.MODEL = 'model';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.ELEMENT = 'element';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.SPRITE = 'sprite';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.RENDER = 'render';\n\t\t}\n}\n\nconst _invMatrix = new Mat4();\nclass SkinInstance {\n\t\tconstructor(skin){\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis._rootBone = null;\n\t\t\t\tthis._skinUpdateIndex = -1;\n\t\t\t\tthis._updateBeforeCull = true;\n\t\t\t\tif (skin) {\n\t\t\t\t\t\tthis.initSkin(skin);\n\t\t\t\t}\n\t\t}\n\t\tset rootBone(rootBone) {\n\t\t\t\tthis._rootBone = rootBone;\n\t\t}\n\t\tget rootBone() {\n\t\t\t\treturn this._rootBone;\n\t\t}\n\t\tinit(device, numBones) {\n\t\t\t\tconst numPixels = numBones * 3;\n\t\t\t\tlet width = Math.ceil(Math.sqrt(numPixels));\n\t\t\t\twidth = math.roundUp(width, 3);\n\t\t\t\tconst height = Math.ceil(numPixels / width);\n\t\t\t\tthis.boneTexture = new Texture(device, {\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA32F,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\tname: 'skin'\n\t\t\t\t});\n\t\t\t\tthis.matrixPalette = this.boneTexture.lock({\n\t\t\t\t\t\tmode: TEXTURELOCK_READ\n\t\t\t\t});\n\t\t\t\tthis.boneTexture.unlock();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this.boneTexture) {\n\t\t\t\t\t\tthis.boneTexture.destroy();\n\t\t\t\t\t\tthis.boneTexture = null;\n\t\t\t\t}\n\t\t}\n\t\tresolve(rootBone, entity) {\n\t\t\t\tthis.rootBone = rootBone;\n\t\t\t\tconst skin = this.skin;\n\t\t\t\tconst bones = [];\n\t\t\t\tfor(let j = 0; j < skin.boneNames.length; j++){\n\t\t\t\t\t\tconst boneName = skin.boneNames[j];\n\t\t\t\t\t\tlet bone = rootBone.findByName(boneName);\n\t\t\t\t\t\tif (!bone) {\n\t\t\t\t\t\t\t\tbone = entity;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbones.push(bone);\n\t\t\t\t}\n\t\t\t\tthis.bones = bones;\n\t\t}\n\t\tinitSkin(skin) {\n\t\t\t\tthis.skin = skin;\n\t\t\t\tthis.bones = [];\n\t\t\t\tconst numBones = skin.inverseBindPose.length;\n\t\t\t\tthis.init(skin.device, numBones);\n\t\t\t\tthis.matrices = [];\n\t\t\t\tfor(let i = 0; i < numBones; i++){\n\t\t\t\t\t\tthis.matrices[i] = new Mat4();\n\t\t\t\t}\n\t\t}\n\t\tuploadBones(device) {\n\t\t\t\tthis.boneTexture.upload();\n\t\t}\n\t\t_updateMatrices(rootNode, skinUpdateIndex) {\n\t\t\t\tif (this._skinUpdateIndex !== skinUpdateIndex) {\n\t\t\t\t\t\tthis._skinUpdateIndex = skinUpdateIndex;\n\t\t\t\t\t\t_invMatrix.copy(rootNode.getWorldTransform()).invert();\n\t\t\t\t\t\tfor(let i = this.bones.length - 1; i >= 0; i--){\n\t\t\t\t\t\t\t\tthis.matrices[i].mulAffine2(_invMatrix, this.bones[i].getWorldTransform());\n\t\t\t\t\t\t\t\tthis.matrices[i].mulAffine2(this.matrices[i], this.skin.inverseBindPose[i]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdateMatrices(rootNode, skinUpdateIndex) {\n\t\t\t\tif (this._updateBeforeCull) {\n\t\t\t\t\t\tthis._updateMatrices(rootNode, skinUpdateIndex);\n\t\t\t\t}\n\t\t}\n\t\tupdateMatrixPalette(rootNode, skinUpdateIndex) {\n\t\t\t\tthis._updateMatrices(rootNode, skinUpdateIndex);\n\t\t\t\tconst mp = this.matrixPalette;\n\t\t\t\tconst count = this.bones.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst pe = this.matrices[i].data;\n\t\t\t\t\t\tconst base = i * 12;\n\t\t\t\t\t\tmp[base] = pe[0];\n\t\t\t\t\t\tmp[base + 1] = pe[4];\n\t\t\t\t\t\tmp[base + 2] = pe[8];\n\t\t\t\t\t\tmp[base + 3] = pe[12];\n\t\t\t\t\t\tmp[base + 4] = pe[1];\n\t\t\t\t\t\tmp[base + 5] = pe[5];\n\t\t\t\t\t\tmp[base + 6] = pe[9];\n\t\t\t\t\t\tmp[base + 7] = pe[13];\n\t\t\t\t\t\tmp[base + 8] = pe[2];\n\t\t\t\t\t\tmp[base + 9] = pe[6];\n\t\t\t\t\t\tmp[base + 10] = pe[10];\n\t\t\t\t\t\tmp[base + 11] = pe[14];\n\t\t\t\t}\n\t\t\t\tthis.uploadBones(this.skin.device);\n\t\t}\n}\n\nclass SkinBatchInstance extends SkinInstance {\n\t\tconstructor(device, nodes, rootNode){\n\t\t\t\tsuper();\n\t\t\t\tconst numBones = nodes.length;\n\t\t\t\tthis.init(device, numBones);\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.rootNode = rootNode;\n\t\t\t\tthis.bones = nodes;\n\t\t}\n\t\tupdateMatrices(rootNode, skinUpdateIndex) {}\n\t\tupdateMatrixPalette(rootNode, skinUpdateIndex) {\n\t\t\t\tconst mp = this.matrixPalette;\n\t\t\t\tconst count = this.bones.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst pe = this.bones[i].getWorldTransform().data;\n\t\t\t\t\t\tconst base = i * 12;\n\t\t\t\t\t\tmp[base] = pe[0];\n\t\t\t\t\t\tmp[base + 1] = pe[4];\n\t\t\t\t\t\tmp[base + 2] = pe[8];\n\t\t\t\t\t\tmp[base + 3] = pe[12];\n\t\t\t\t\t\tmp[base + 4] = pe[1];\n\t\t\t\t\t\tmp[base + 5] = pe[5];\n\t\t\t\t\t\tmp[base + 6] = pe[9];\n\t\t\t\t\t\tmp[base + 7] = pe[13];\n\t\t\t\t\t\tmp[base + 8] = pe[2];\n\t\t\t\t\t\tmp[base + 9] = pe[6];\n\t\t\t\t\t\tmp[base + 10] = pe[10];\n\t\t\t\t\t\tmp[base + 11] = pe[14];\n\t\t\t\t}\n\t\t\t\tthis.uploadBones(this.device);\n\t\t}\n}\n\nlet id$5 = 0;\nclass GeometryData {\n\t\tconstructor(){\n\t\t\t\tthis.initDefaults();\n\t\t}\n\t\tinitDefaults() {\n\t\t\t\tthis.recreate = false;\n\t\t\t\tthis.verticesUsage = BUFFER_STATIC;\n\t\t\t\tthis.indicesUsage = BUFFER_STATIC;\n\t\t\t\tthis.maxVertices = 0;\n\t\t\t\tthis.maxIndices = 0;\n\t\t\t\tthis.vertexCount = 0;\n\t\t\t\tthis.indexCount = 0;\n\t\t\t\tthis.vertexStreamsUpdated = false;\n\t\t\t\tthis.indexStreamUpdated = false;\n\t\t\t\tthis.vertexStreamDictionary = {};\n\t\t\t\tthis.indices = null;\n\t\t}\n\t\t_changeVertexCount(count, semantic) {\n\t\t\t\tif (!this.vertexCount) {\n\t\t\t\t\t\tthis.vertexCount = count;\n\t\t\t\t}\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DEFAULT_COMPONENTS_POSITION = 3;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DEFAULT_COMPONENTS_NORMAL = 3;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DEFAULT_COMPONENTS_UV = 2;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.DEFAULT_COMPONENTS_COLORS = 4;\n\t\t}\n}\nclass GeometryVertexStream {\n\t\tconstructor(data, componentCount, dataType, dataTypeNormalize, asInt){\n\t\t\t\tthis.data = data;\n\t\t\t\tthis.componentCount = componentCount;\n\t\t\t\tthis.dataType = dataType;\n\t\t\t\tthis.dataTypeNormalize = dataTypeNormalize;\n\t\t\t\tthis.asInt = asInt;\n\t\t}\n}\nclass Mesh extends RefCountedObject {\n\t\tconstructor(graphicsDevice, options){\n\t\t\t\tsuper(), this.indexBuffer = [\n\t\t\t\t\t\tnull\n\t\t\t\t], this.vertexBuffer = null, this.primitive = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 0,\n\t\t\t\t\t\t\t\tbase: 0,\n\t\t\t\t\t\t\t\tbaseVertex: 0,\n\t\t\t\t\t\t\t\tcount: 0\n\t\t\t\t\t\t}\n\t\t\t\t], this.skin = null, this.boneAabb = null, this._aabbVer = 0, this._aabb = new BoundingBox(), this._geometryData = null, this._morph = null, this._storageIndex = false, this._storageVertex = false;\n\t\t\t\tthis.id = id$5++;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis._storageIndex = options?.storageIndex || false;\n\t\t\t\tthis._storageVertex = options?.storageVertex || false;\n\t\t}\n\t\tstatic fromGeometry(graphicsDevice, geometry, options = {}) {\n\t\t\t\tconst mesh = new Mesh(graphicsDevice, options);\n\t\t\t\tconst { positions, normals, tangents, colors, uvs, uvs1, blendIndices, blendWeights, indices } = geometry;\n\t\t\t\tif (positions) {\n\t\t\t\t\t\tmesh.setPositions(positions);\n\t\t\t\t}\n\t\t\t\tif (normals) {\n\t\t\t\t\t\tmesh.setNormals(normals);\n\t\t\t\t}\n\t\t\t\tif (tangents) {\n\t\t\t\t\t\tmesh.setVertexStream(SEMANTIC_TANGENT, tangents, 4);\n\t\t\t\t}\n\t\t\t\tif (colors) {\n\t\t\t\t\t\tmesh.setColors32(colors);\n\t\t\t\t}\n\t\t\t\tif (uvs) {\n\t\t\t\t\t\tmesh.setUvs(0, uvs);\n\t\t\t\t}\n\t\t\t\tif (uvs1) {\n\t\t\t\t\t\tmesh.setUvs(1, uvs1);\n\t\t\t\t}\n\t\t\t\tif (blendIndices) {\n\t\t\t\t\t\tmesh.setVertexStream(SEMANTIC_BLENDINDICES, blendIndices, 4, blendIndices.length / 4, TYPE_UINT8);\n\t\t\t\t}\n\t\t\t\tif (blendWeights) {\n\t\t\t\t\t\tmesh.setVertexStream(SEMANTIC_BLENDWEIGHT, blendWeights, 4);\n\t\t\t\t}\n\t\t\t\tif (indices) {\n\t\t\t\t\t\tmesh.setIndices(indices);\n\t\t\t\t}\n\t\t\t\tmesh.update();\n\t\t\t\treturn mesh;\n\t\t}\n\t\tset morph(morph) {\n\t\t\t\tif (morph !== this._morph) {\n\t\t\t\t\t\tif (this._morph) {\n\t\t\t\t\t\t\t\tthis._morph.decRefCount();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._morph = morph;\n\t\t\t\t\t\tif (morph) {\n\t\t\t\t\t\t\t\tmorph.incRefCount();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget morph() {\n\t\t\t\treturn this._morph;\n\t\t}\n\t\tset aabb(aabb) {\n\t\t\t\tthis._aabb = aabb;\n\t\t\t\tthis._aabbVer++;\n\t\t}\n\t\tget aabb() {\n\t\t\t\treturn this._aabb;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst morph = this.morph;\n\t\t\t\tif (morph) {\n\t\t\t\t\t\tthis.morph = null;\n\t\t\t\t\t\tif (morph.refCount < 1) {\n\t\t\t\t\t\t\t\tmorph.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.vertexBuffer) {\n\t\t\t\t\t\tthis.vertexBuffer.destroy();\n\t\t\t\t\t\tthis.vertexBuffer = null;\n\t\t\t\t}\n\t\t\t\tfor(let j = 0; j < this.indexBuffer.length; j++){\n\t\t\t\t\t\tthis._destroyIndexBuffer(j);\n\t\t\t\t}\n\t\t\t\tthis.indexBuffer.length = 0;\n\t\t\t\tthis._geometryData = null;\n\t\t}\n\t\t_destroyIndexBuffer(index) {\n\t\t\t\tif (this.indexBuffer[index]) {\n\t\t\t\t\t\tthis.indexBuffer[index].destroy();\n\t\t\t\t\t\tthis.indexBuffer[index] = null;\n\t\t\t\t}\n\t\t}\n\t\t_initBoneAabbs(morphTargets) {\n\t\t\t\tthis.boneAabb = [];\n\t\t\t\tthis.boneUsed = [];\n\t\t\t\tlet x, y, z;\n\t\t\t\tlet bMax, bMin;\n\t\t\t\tconst boneMin = [];\n\t\t\t\tconst boneMax = [];\n\t\t\t\tconst boneUsed = this.boneUsed;\n\t\t\t\tconst numBones = this.skin.boneNames.length;\n\t\t\t\tlet maxMorphX, maxMorphY, maxMorphZ;\n\t\t\t\tfor(let i = 0; i < numBones; i++){\n\t\t\t\t\t\tboneMin[i] = new Vec3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);\n\t\t\t\t\t\tboneMax[i] = new Vec3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);\n\t\t\t\t}\n\t\t\t\tconst iterator = new VertexIterator(this.vertexBuffer);\n\t\t\t\tconst posElement = iterator.element[SEMANTIC_POSITION];\n\t\t\t\tconst weightsElement = iterator.element[SEMANTIC_BLENDWEIGHT];\n\t\t\t\tconst indicesElement = iterator.element[SEMANTIC_BLENDINDICES];\n\t\t\t\tconst numVerts = this.vertexBuffer.numVertices;\n\t\t\t\tfor(let j = 0; j < numVerts; j++){\n\t\t\t\t\t\tfor(let k = 0; k < 4; k++){\n\t\t\t\t\t\t\t\tconst boneWeight = weightsElement.array[weightsElement.index + k];\n\t\t\t\t\t\t\t\tif (boneWeight > 0) {\n\t\t\t\t\t\t\t\t\t\tconst boneIndex = indicesElement.array[indicesElement.index + k];\n\t\t\t\t\t\t\t\t\t\tboneUsed[boneIndex] = true;\n\t\t\t\t\t\t\t\t\t\tx = posElement.array[posElement.index];\n\t\t\t\t\t\t\t\t\t\ty = posElement.array[posElement.index + 1];\n\t\t\t\t\t\t\t\t\t\tz = posElement.array[posElement.index + 2];\n\t\t\t\t\t\t\t\t\t\tbMax = boneMax[boneIndex];\n\t\t\t\t\t\t\t\t\t\tbMin = boneMin[boneIndex];\n\t\t\t\t\t\t\t\t\t\tif (bMin.x > x) bMin.x = x;\n\t\t\t\t\t\t\t\t\t\tif (bMin.y > y) bMin.y = y;\n\t\t\t\t\t\t\t\t\t\tif (bMin.z > z) bMin.z = z;\n\t\t\t\t\t\t\t\t\t\tif (bMax.x < x) bMax.x = x;\n\t\t\t\t\t\t\t\t\t\tif (bMax.y < y) bMax.y = y;\n\t\t\t\t\t\t\t\t\t\tif (bMax.z < z) bMax.z = z;\n\t\t\t\t\t\t\t\t\t\tif (morphTargets) {\n\t\t\t\t\t\t\t\t\t\t\t\tlet minMorphX = maxMorphX = x;\n\t\t\t\t\t\t\t\t\t\t\t\tlet minMorphY = maxMorphY = y;\n\t\t\t\t\t\t\t\t\t\t\t\tlet minMorphZ = maxMorphZ = z;\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let l = 0; l < morphTargets.length; l++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst target = morphTargets[l];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst dx = target.deltaPositions[j * 3];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst dy = target.deltaPositions[j * 3 + 1];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst dz = target.deltaPositions[j * 3 + 2];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (dx < 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tminMorphX += dx;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmaxMorphX += dx;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (dy < 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tminMorphY += dy;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmaxMorphY += dy;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (dz < 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tminMorphZ += dz;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmaxMorphZ += dz;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (bMin.x > minMorphX) bMin.x = minMorphX;\n\t\t\t\t\t\t\t\t\t\t\t\tif (bMin.y > minMorphY) bMin.y = minMorphY;\n\t\t\t\t\t\t\t\t\t\t\t\tif (bMin.z > minMorphZ) bMin.z = minMorphZ;\n\t\t\t\t\t\t\t\t\t\t\t\tif (bMax.x < maxMorphX) bMax.x = maxMorphX;\n\t\t\t\t\t\t\t\t\t\t\t\tif (bMax.y < maxMorphY) bMax.y = maxMorphY;\n\t\t\t\t\t\t\t\t\t\t\t\tif (bMax.z < maxMorphZ) bMax.z = maxMorphZ;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\titerator.next();\n\t\t\t\t}\n\t\t\t\tconst positionElement = this.vertexBuffer.getFormat().elements.find((e)=>e.name === SEMANTIC_POSITION);\n\t\t\t\tif (positionElement && positionElement.normalize) {\n\t\t\t\t\t\tconst func = (()=>{\n\t\t\t\t\t\t\t\tswitch(positionElement.dataType){\n\t\t\t\t\t\t\t\t\t\tcase TYPE_INT8:\n\t\t\t\t\t\t\t\t\t\t\t\treturn (x)=>Math.max(x / 127.0, -1);\n\t\t\t\t\t\t\t\t\t\tcase TYPE_UINT8:\n\t\t\t\t\t\t\t\t\t\t\t\treturn (x)=>x / 255.0;\n\t\t\t\t\t\t\t\t\t\tcase TYPE_INT16:\n\t\t\t\t\t\t\t\t\t\t\t\treturn (x)=>Math.max(x / 32767.0, -1);\n\t\t\t\t\t\t\t\t\t\tcase TYPE_UINT16:\n\t\t\t\t\t\t\t\t\t\t\t\treturn (x)=>x / 65535.0;\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\treturn (x)=>x;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t\tfor(let i = 0; i < numBones; i++){\n\t\t\t\t\t\t\t\tif (boneUsed[i]) {\n\t\t\t\t\t\t\t\t\t\tconst min = boneMin[i];\n\t\t\t\t\t\t\t\t\t\tconst max = boneMax[i];\n\t\t\t\t\t\t\t\t\t\tmin.set(func(min.x), func(min.y), func(min.z));\n\t\t\t\t\t\t\t\t\t\tmax.set(func(max.x), func(max.y), func(max.z));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < numBones; i++){\n\t\t\t\t\t\tconst aabb = new BoundingBox();\n\t\t\t\t\t\taabb.setMinMax(boneMin[i], boneMax[i]);\n\t\t\t\t\t\tthis.boneAabb.push(aabb);\n\t\t\t\t}\n\t\t}\n\t\t_initGeometryData() {\n\t\t\t\tif (!this._geometryData) {\n\t\t\t\t\t\tthis._geometryData = new GeometryData();\n\t\t\t\t\t\tif (this.vertexBuffer) {\n\t\t\t\t\t\t\t\tthis._geometryData.vertexCount = this.vertexBuffer.numVertices;\n\t\t\t\t\t\t\t\tthis._geometryData.maxVertices = this.vertexBuffer.numVertices;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.indexBuffer.length > 0 && this.indexBuffer[0]) {\n\t\t\t\t\t\t\t\tthis._geometryData.indexCount = this.indexBuffer[0].numIndices;\n\t\t\t\t\t\t\t\tthis._geometryData.maxIndices = this.indexBuffer[0].numIndices;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tclear(verticesDynamic, indicesDynamic, maxVertices = 0, maxIndices = 0) {\n\t\t\t\tthis._initGeometryData();\n\t\t\t\tthis._geometryData.initDefaults();\n\t\t\t\tthis._geometryData.recreate = true;\n\t\t\t\tthis._geometryData.maxVertices = maxVertices;\n\t\t\t\tthis._geometryData.maxIndices = maxIndices;\n\t\t\t\tthis._geometryData.verticesUsage = verticesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC;\n\t\t\t\tthis._geometryData.indicesUsage = indicesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC;\n\t\t}\n\t\tsetVertexStream(semantic, data, componentCount, numVertices, dataType = TYPE_FLOAT32, dataTypeNormalize = false, asInt = false) {\n\t\t\t\tthis._initGeometryData();\n\t\t\t\tconst vertexCount = numVertices || data.length / componentCount;\n\t\t\t\tthis._geometryData._changeVertexCount(vertexCount, semantic);\n\t\t\t\tthis._geometryData.vertexStreamsUpdated = true;\n\t\t\t\tthis._geometryData.vertexStreamDictionary[semantic] = new GeometryVertexStream(data, componentCount, dataType, dataTypeNormalize, asInt);\n\t\t}\n\t\tgetVertexStream(semantic, data) {\n\t\t\t\tlet count = 0;\n\t\t\t\tlet done = false;\n\t\t\t\tif (this._geometryData) {\n\t\t\t\t\t\tconst stream = this._geometryData.vertexStreamDictionary[semantic];\n\t\t\t\t\t\tif (stream) {\n\t\t\t\t\t\t\t\tdone = true;\n\t\t\t\t\t\t\t\tcount = this._geometryData.vertexCount;\n\t\t\t\t\t\t\t\tif (ArrayBuffer.isView(data)) {\n\t\t\t\t\t\t\t\t\t\tdata.set(stream.data);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdata.length = 0;\n\t\t\t\t\t\t\t\t\t\tdata.push(stream.data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!done) {\n\t\t\t\t\t\tif (this.vertexBuffer) {\n\t\t\t\t\t\t\t\tconst iterator = new VertexIterator(this.vertexBuffer);\n\t\t\t\t\t\t\t\tcount = iterator.readData(semantic, data);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn count;\n\t\t}\n\t\tsetPositions(positions, componentCount = GeometryData.DEFAULT_COMPONENTS_POSITION, numVertices) {\n\t\t\t\tthis.setVertexStream(SEMANTIC_POSITION, positions, componentCount, numVertices, TYPE_FLOAT32, false);\n\t\t}\n\t\tsetNormals(normals, componentCount = GeometryData.DEFAULT_COMPONENTS_NORMAL, numVertices) {\n\t\t\t\tthis.setVertexStream(SEMANTIC_NORMAL, normals, componentCount, numVertices, TYPE_FLOAT32, false);\n\t\t}\n\t\tsetUvs(channel, uvs, componentCount = GeometryData.DEFAULT_COMPONENTS_UV, numVertices) {\n\t\t\t\tthis.setVertexStream(SEMANTIC_TEXCOORD + channel, uvs, componentCount, numVertices, TYPE_FLOAT32, false);\n\t\t}\n\t\tsetColors(colors, componentCount = GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices) {\n\t\t\t\tthis.setVertexStream(SEMANTIC_COLOR, colors, componentCount, numVertices, TYPE_FLOAT32, false);\n\t\t}\n\t\tsetColors32(colors, numVertices) {\n\t\t\t\tthis.setVertexStream(SEMANTIC_COLOR, colors, GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices, TYPE_UINT8, true);\n\t\t}\n\t\tsetIndices(indices, numIndices) {\n\t\t\t\tthis._initGeometryData();\n\t\t\t\tthis._geometryData.indexStreamUpdated = true;\n\t\t\t\tthis._geometryData.indices = indices;\n\t\t\t\tthis._geometryData.indexCount = numIndices || indices.length;\n\t\t}\n\t\tgetPositions(positions) {\n\t\t\t\treturn this.getVertexStream(SEMANTIC_POSITION, positions);\n\t\t}\n\t\tgetNormals(normals) {\n\t\t\t\treturn this.getVertexStream(SEMANTIC_NORMAL, normals);\n\t\t}\n\t\tgetUvs(channel, uvs) {\n\t\t\t\treturn this.getVertexStream(SEMANTIC_TEXCOORD + channel, uvs);\n\t\t}\n\t\tgetColors(colors) {\n\t\t\t\treturn this.getVertexStream(SEMANTIC_COLOR, colors);\n\t\t}\n\t\tgetIndices(indices) {\n\t\t\t\tlet count = 0;\n\t\t\t\tif (this._geometryData && this._geometryData.indices) {\n\t\t\t\t\t\tconst streamIndices = this._geometryData.indices;\n\t\t\t\t\t\tcount = this._geometryData.indexCount;\n\t\t\t\t\t\tif (ArrayBuffer.isView(indices)) {\n\t\t\t\t\t\t\t\tindices.set(streamIndices);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tindices.length = 0;\n\t\t\t\t\t\t\t\tfor(let i = 0, il = streamIndices.length; i < il; i++){\n\t\t\t\t\t\t\t\t\t\tindices.push(streamIndices[i]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this.indexBuffer.length > 0 && this.indexBuffer[0]) {\n\t\t\t\t\t\t\t\tconst indexBuffer = this.indexBuffer[0];\n\t\t\t\t\t\t\t\tcount = indexBuffer.readData(indices);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn count;\n\t\t}\n\t\tupdate(primitiveType = PRIMITIVE_TRIANGLES, updateBoundingBox = true) {\n\t\t\t\tif (this._geometryData) {\n\t\t\t\t\t\tif (updateBoundingBox) {\n\t\t\t\t\t\t\t\tconst stream = this._geometryData.vertexStreamDictionary[SEMANTIC_POSITION];\n\t\t\t\t\t\t\t\tif (stream) {\n\t\t\t\t\t\t\t\t\t\tif (stream.componentCount === 3) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._aabb.compute(stream.data, this._geometryData.vertexCount);\n\t\t\t\t\t\t\t\t\t\t\t\tthis._aabbVer++;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet destroyVB = this._geometryData.recreate;\n\t\t\t\t\t\tif (this._geometryData.vertexCount > this._geometryData.maxVertices) {\n\t\t\t\t\t\t\t\tdestroyVB = true;\n\t\t\t\t\t\t\t\tthis._geometryData.maxVertices = this._geometryData.vertexCount;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (destroyVB) {\n\t\t\t\t\t\t\t\tif (this.vertexBuffer) {\n\t\t\t\t\t\t\t\t\t\tthis.vertexBuffer.destroy();\n\t\t\t\t\t\t\t\t\t\tthis.vertexBuffer = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet destroyIB = this._geometryData.recreate;\n\t\t\t\t\t\tif (this._geometryData.indexCount > this._geometryData.maxIndices) {\n\t\t\t\t\t\t\t\tdestroyIB = true;\n\t\t\t\t\t\t\t\tthis._geometryData.maxIndices = this._geometryData.indexCount;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (destroyIB) {\n\t\t\t\t\t\t\t\tif (this.indexBuffer.length > 0 && this.indexBuffer[0]) {\n\t\t\t\t\t\t\t\t\t\tthis.indexBuffer[0].destroy();\n\t\t\t\t\t\t\t\t\t\tthis.indexBuffer[0] = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._geometryData.vertexStreamsUpdated) {\n\t\t\t\t\t\t\t\tthis._updateVertexBuffer();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._geometryData.indexStreamUpdated) {\n\t\t\t\t\t\t\t\tthis._updateIndexBuffer();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.primitive[0].type = primitiveType;\n\t\t\t\t\t\tif (this.indexBuffer.length > 0 && this.indexBuffer[0]) {\n\t\t\t\t\t\t\t\tif (this._geometryData.indexStreamUpdated) {\n\t\t\t\t\t\t\t\t\t\tthis.primitive[0].count = this._geometryData.indexCount;\n\t\t\t\t\t\t\t\t\t\tthis.primitive[0].indexed = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this._geometryData.vertexStreamsUpdated) {\n\t\t\t\t\t\t\t\t\t\tthis.primitive[0].count = this._geometryData.vertexCount;\n\t\t\t\t\t\t\t\t\t\tthis.primitive[0].indexed = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._geometryData.vertexCount = 0;\n\t\t\t\t\t\tthis._geometryData.indexCount = 0;\n\t\t\t\t\t\tthis._geometryData.vertexStreamsUpdated = false;\n\t\t\t\t\t\tthis._geometryData.indexStreamUpdated = false;\n\t\t\t\t\t\tthis._geometryData.recreate = false;\n\t\t\t\t\t\tthis.updateRenderStates();\n\t\t\t\t}\n\t\t}\n\t\t_buildVertexFormat(vertexCount) {\n\t\t\t\tconst vertexDesc = [];\n\t\t\t\tfor(const semantic in this._geometryData.vertexStreamDictionary){\n\t\t\t\t\t\tconst stream = this._geometryData.vertexStreamDictionary[semantic];\n\t\t\t\t\t\tvertexDesc.push({\n\t\t\t\t\t\t\t\tsemantic: semantic,\n\t\t\t\t\t\t\t\tcomponents: stream.componentCount,\n\t\t\t\t\t\t\t\ttype: stream.dataType,\n\t\t\t\t\t\t\t\tnormalize: stream.dataTypeNormalize,\n\t\t\t\t\t\t\t\tasInt: stream.asInt\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn new VertexFormat(this.device, vertexDesc, vertexCount);\n\t\t}\n\t\t_updateVertexBuffer() {\n\t\t\t\tif (!this.vertexBuffer) {\n\t\t\t\t\t\tconst allocateVertexCount = this._geometryData.maxVertices;\n\t\t\t\t\t\tconst format = this._buildVertexFormat(allocateVertexCount);\n\t\t\t\t\t\tthis.vertexBuffer = new VertexBuffer(this.device, format, allocateVertexCount, {\n\t\t\t\t\t\t\t\tusage: this._geometryData.verticesUsage,\n\t\t\t\t\t\t\t\tstorage: this._storageVertex\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst iterator = new VertexIterator(this.vertexBuffer);\n\t\t\t\tconst numVertices = this._geometryData.vertexCount;\n\t\t\t\tfor(const semantic in this._geometryData.vertexStreamDictionary){\n\t\t\t\t\t\tconst stream = this._geometryData.vertexStreamDictionary[semantic];\n\t\t\t\t\t\titerator.writeData(semantic, stream.data, numVertices);\n\t\t\t\t\t\tdelete this._geometryData.vertexStreamDictionary[semantic];\n\t\t\t\t}\n\t\t\t\titerator.end();\n\t\t}\n\t\t_updateIndexBuffer() {\n\t\t\t\tif (this.indexBuffer.length <= 0 || !this.indexBuffer[0]) {\n\t\t\t\t\t\tconst maxVertices = this._geometryData.maxVertices;\n\t\t\t\t\t\tconst createFormat = maxVertices > 0xffff || maxVertices === 0 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16;\n\t\t\t\t\t\tconst options = this._storageIndex ? {\n\t\t\t\t\t\t\t\tstorage: true\n\t\t\t\t\t\t} : undefined;\n\t\t\t\t\t\tthis.indexBuffer[0] = new IndexBuffer(this.device, createFormat, this._geometryData.maxIndices, this._geometryData.indicesUsage, undefined, options);\n\t\t\t\t}\n\t\t\t\tconst srcIndices = this._geometryData.indices;\n\t\t\t\tif (srcIndices) {\n\t\t\t\t\t\tconst indexBuffer = this.indexBuffer[0];\n\t\t\t\t\t\tindexBuffer.writeData(srcIndices, this._geometryData.indexCount);\n\t\t\t\t\t\tthis._geometryData.indices = null;\n\t\t\t\t}\n\t\t}\n\t\tprepareRenderState(renderStyle) {\n\t\t\t\tif (renderStyle === RENDERSTYLE_WIREFRAME) {\n\t\t\t\t\t\tthis.generateWireframe();\n\t\t\t\t} else if (renderStyle === RENDERSTYLE_POINTS) {\n\t\t\t\t\t\tthis.primitive[RENDERSTYLE_POINTS] = {\n\t\t\t\t\t\t\t\ttype: PRIMITIVE_POINTS,\n\t\t\t\t\t\t\t\tbase: 0,\n\t\t\t\t\t\t\t\tbaseVertex: 0,\n\t\t\t\t\t\t\t\tcount: this.vertexBuffer ? this.vertexBuffer.numVertices : 0,\n\t\t\t\t\t\t\t\tindexed: false\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tupdateRenderStates() {\n\t\t\t\tif (this.primitive[RENDERSTYLE_POINTS]) {\n\t\t\t\t\t\tthis.prepareRenderState(RENDERSTYLE_POINTS);\n\t\t\t\t}\n\t\t\t\tif (this.primitive[RENDERSTYLE_WIREFRAME]) {\n\t\t\t\t\t\tthis.prepareRenderState(RENDERSTYLE_WIREFRAME);\n\t\t\t\t}\n\t\t}\n\t\tgenerateWireframe() {\n\t\t\t\tthis._destroyIndexBuffer(RENDERSTYLE_WIREFRAME);\n\t\t\t\tconst numVertices = this.vertexBuffer.numVertices;\n\t\t\t\tconst lines = [];\n\t\t\t\tlet format;\n\t\t\t\tif (this.indexBuffer.length > 0 && this.indexBuffer[0]) {\n\t\t\t\t\t\tconst offsets = [\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t];\n\t\t\t\t\t\tconst base = this.primitive[RENDERSTYLE_SOLID].base;\n\t\t\t\t\t\tconst count = this.primitive[RENDERSTYLE_SOLID].count;\n\t\t\t\t\t\tconst baseVertex = this.primitive[RENDERSTYLE_SOLID].baseVertex || 0;\n\t\t\t\t\t\tconst indexBuffer = this.indexBuffer[RENDERSTYLE_SOLID];\n\t\t\t\t\t\tconst srcIndices = new typedArrayIndexFormats[indexBuffer.format](indexBuffer.storage);\n\t\t\t\t\t\tconst seen = new Set();\n\t\t\t\t\t\tfor(let j = base; j < base + count; j += 3){\n\t\t\t\t\t\t\t\tfor(let k = 0; k < 3; k++){\n\t\t\t\t\t\t\t\t\t\tconst i1 = srcIndices[j + offsets[k][0]] + baseVertex;\n\t\t\t\t\t\t\t\t\t\tconst i2 = srcIndices[j + offsets[k][1]] + baseVertex;\n\t\t\t\t\t\t\t\t\t\tconst hash = i1 > i2 ? i2 * numVertices + i1 : i1 * numVertices + i2;\n\t\t\t\t\t\t\t\t\t\tif (!seen.has(hash)) {\n\t\t\t\t\t\t\t\t\t\t\t\tseen.add(hash);\n\t\t\t\t\t\t\t\t\t\t\t\tlines.push(i1, i2);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tformat = indexBuffer.format;\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(let i = 0; i < numVertices; i += 3){\n\t\t\t\t\t\t\t\tlines.push(i, i + 1, i + 1, i + 2, i + 2, i);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tformat = lines.length > 65535 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16;\n\t\t\t\t}\n\t\t\t\tconst wireBuffer = new IndexBuffer(this.vertexBuffer.device, format, lines.length);\n\t\t\t\tconst dstIndices = new typedArrayIndexFormats[wireBuffer.format](wireBuffer.storage);\n\t\t\t\tdstIndices.set(lines);\n\t\t\t\twireBuffer.unlock();\n\t\t\t\tthis.primitive[RENDERSTYLE_WIREFRAME] = {\n\t\t\t\t\t\ttype: PRIMITIVE_LINES,\n\t\t\t\t\t\tbase: 0,\n\t\t\t\t\t\tbaseVertex: 0,\n\t\t\t\t\t\tcount: lines.length,\n\t\t\t\t\t\tindexed: true\n\t\t\t\t};\n\t\t\t\tthis.indexBuffer[RENDERSTYLE_WIREFRAME] = wireBuffer;\n\t\t}\n}\n\nconst defaultMaterialDeviceCache = new DeviceCache();\nfunction getDefaultMaterial(device) {\n\t\tconst material = defaultMaterialDeviceCache.get(device);\n\t\treturn material;\n}\nfunction setDefaultMaterial(device, material) {\n\t\tdefaultMaterialDeviceCache.get(device, ()=>{\n\t\t\t\treturn material;\n\t\t});\n}\n\nclass RefCountedCache {\n\t\tdestroy() {\n\t\t\t\tthis.cache.forEach((refCount, object)=>{\n\t\t\t\t\t\tobject.destroy();\n\t\t\t\t});\n\t\t\t\tthis.cache.clear();\n\t\t}\n\t\tincRef(object) {\n\t\t\t\tconst refCount = (this.cache.get(object) || 0) + 1;\n\t\t\t\tthis.cache.set(object, refCount);\n\t\t}\n\t\tdecRef(object) {\n\t\t\t\tif (object) {\n\t\t\t\t\t\tlet refCount = this.cache.get(object);\n\t\t\t\t\t\tif (refCount) {\n\t\t\t\t\t\t\t\trefCount--;\n\t\t\t\t\t\t\t\tif (refCount === 0) {\n\t\t\t\t\t\t\t\t\t\tthis.cache.delete(object);\n\t\t\t\t\t\t\t\t\t\tobject.destroy();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.cache.set(object, refCount);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.cache = new Map();\n\t\t}\n}\n\nclass LightmapCache {\n\t\tstatic{\n\t\t\t\tthis.cache = new RefCountedCache();\n\t\t}\n\t\tstatic incRef(texture) {\n\t\t\t\tthis.cache.incRef(texture);\n\t\t}\n\t\tstatic decRef(texture) {\n\t\t\t\tthis.cache.decRef(texture);\n\t\t}\n\t\tstatic destroy() {\n\t\t\t\tthis.cache.destroy();\n\t\t}\n}\n\nlet id$4 = 0;\nconst _tmpAabb = new BoundingBox();\nconst _tempBoneAabb = new BoundingBox();\nconst _tempSphere = new BoundingSphere();\nconst _meshSet = new Set();\nconst lookupHashes = new Uint32Array(4);\nclass InstancingData {\n\t\tconstructor(numObjects){\n\t\t\t\tthis.vertexBuffer = null;\n\t\t\t\tthis._destroyVertexBuffer = false;\n\t\t\t\tthis.count = numObjects;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this._destroyVertexBuffer) {\n\t\t\t\t\t\tthis.vertexBuffer?.destroy();\n\t\t\t\t}\n\t\t\t\tthis.vertexBuffer = null;\n\t\t}\n}\nclass IndirectData {\n\t\tget(camera) {\n\t\t\t\treturn this.map.get(camera) ?? this.map.get(null);\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.map = new Map();\n\t\t\t\tthis.meshMetaData = new Int32Array(4);\n\t\t}\n}\nclass ShaderInstance {\n\t\tgetBindGroup(device) {\n\t\t\t\tif (!this.bindGroup) {\n\t\t\t\t\t\tconst shader = this.shader;\n\t\t\t\t\t\tconst bindGroupFormat = shader.meshBindGroupFormat;\n\t\t\t\t\t\tthis.bindGroup = new BindGroup(device, bindGroupFormat);\n\t\t\t\t}\n\t\t\t\treturn this.bindGroup;\n\t\t}\n\t\tgetUniformBuffer(device) {\n\t\t\t\tif (!this.uniformBuffer) {\n\t\t\t\t\t\tconst shader = this.shader;\n\t\t\t\t\t\tconst ubFormat = shader.meshUniformBufferFormat;\n\t\t\t\t\t\tthis.uniformBuffer = new UniformBuffer(device, ubFormat, false);\n\t\t\t\t}\n\t\t\t\treturn this.uniformBuffer;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.bindGroup?.destroy();\n\t\t\t\tthis.bindGroup = null;\n\t\t\t\tthis.uniformBuffer?.destroy();\n\t\t\t\tthis.uniformBuffer = null;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.bindGroup = null;\n\t\t\t\tthis.uniformBuffer = null;\n\t\t}\n}\nclass MeshInstance {\n\t\tconstructor(mesh, material, node = null){\n\t\t\t\tthis.castShadow = false;\n\t\t\t\tthis.shadowCascadeMask = SHADOW_CASCADE_ALL;\n\t\t\t\tthis.cull = true;\n\t\t\t\tthis.drawOrder = 0;\n\t\t\t\tthis._drawBucket = 127;\n\t\t\t\tthis.visible = true;\n\t\t\t\tthis.visibleThisFrame = false;\n\t\t\t\tthis.flipFacesFactor = 1;\n\t\t\t\tthis.gsplatInstance = null;\n\t\t\t\tthis.id = id$4++;\n\t\t\t\tthis.isVisibleFunc = null;\n\t\t\t\tthis.instancingData = null;\n\t\t\t\tthis.indirectData = null;\n\t\t\t\tthis.parameters = {};\n\t\t\t\tthis.pick = true;\n\t\t\t\tthis.stencilFront = null;\n\t\t\t\tthis.stencilBack = null;\n\t\t\t\tthis.transparent = false;\n\t\t\t\tthis._aabb = new BoundingBox();\n\t\t\t\tthis._aabbVer = -1;\n\t\t\t\tthis._aabbMeshVer = -1;\n\t\t\t\tthis._customAabb = null;\n\t\t\t\tthis._updateAabb = true;\n\t\t\t\tthis._updateAabbFunc = null;\n\t\t\t\tthis._sortKeyShadow = 0;\n\t\t\t\tthis._sortKeyForward = 0;\n\t\t\t\tthis._sortKeyDynamic = 0;\n\t\t\t\tthis._layer = LAYER_WORLD;\n\t\t\t\tthis._material = null;\n\t\t\t\tthis._skinInstance = null;\n\t\t\t\tthis._morphInstance = null;\n\t\t\t\tthis._receiveShadow = true;\n\t\t\t\tthis._renderStyle = RENDERSTYLE_SOLID;\n\t\t\t\tthis._screenSpace = false;\n\t\t\t\tthis._shaderCache = new Map();\n\t\t\t\tthis._shaderDefs = MASK_AFFECT_DYNAMIC << 16;\n\t\t\t\tthis._calculateSortDistance = null;\n\t\t\t\tthis.node = node;\n\t\t\t\tthis._mesh = mesh;\n\t\t\t\tmesh.incRefCount();\n\t\t\t\tthis.material = material;\n\t\t\t\tif (mesh.vertexBuffer) {\n\t\t\t\t\t\tconst format = mesh.vertexBuffer.format;\n\t\t\t\t\t\tthis._shaderDefs |= format.hasUv0 ? SHADERDEF_UV0 : 0;\n\t\t\t\t\t\tthis._shaderDefs |= format.hasUv1 ? SHADERDEF_UV1 : 0;\n\t\t\t\t\t\tthis._shaderDefs |= format.hasColor ? SHADERDEF_VCOLOR : 0;\n\t\t\t\t\t\tthis._shaderDefs |= format.hasTangents ? SHADERDEF_TANGENTS : 0;\n\t\t\t\t}\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tset drawBucket(bucket) {\n\t\t\t\tthis._drawBucket = Math.floor(bucket) & 0xff;\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget drawBucket() {\n\t\t\t\treturn this._drawBucket;\n\t\t}\n\t\tset renderStyle(renderStyle) {\n\t\t\t\tthis._renderStyle = renderStyle;\n\t\t\t\tthis.mesh.prepareRenderState(renderStyle);\n\t\t}\n\t\tget renderStyle() {\n\t\t\t\treturn this._renderStyle;\n\t\t}\n\t\tset mesh(mesh) {\n\t\t\t\tif (mesh === this._mesh) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._mesh) {\n\t\t\t\t\t\tthis._mesh.decRefCount();\n\t\t\t\t}\n\t\t\t\tthis._mesh = mesh;\n\t\t\t\tif (mesh) {\n\t\t\t\t\t\tmesh.incRefCount();\n\t\t\t\t}\n\t\t}\n\t\tget mesh() {\n\t\t\t\treturn this._mesh;\n\t\t}\n\t\tset aabb(aabb) {\n\t\t\t\tthis._aabb = aabb;\n\t\t}\n\t\tget aabb() {\n\t\t\t\tif (!this._updateAabb) {\n\t\t\t\t\t\treturn this._aabb;\n\t\t\t\t}\n\t\t\t\tif (this._updateAabbFunc) {\n\t\t\t\t\t\treturn this._updateAabbFunc(this._aabb);\n\t\t\t\t}\n\t\t\t\tlet localAabb = this._customAabb;\n\t\t\t\tlet toWorldSpace = !!localAabb;\n\t\t\t\tif (!localAabb) {\n\t\t\t\t\t\tlocalAabb = _tmpAabb;\n\t\t\t\t\t\tif (this.skinInstance) {\n\t\t\t\t\t\t\t\tif (!this.mesh.boneAabb) {\n\t\t\t\t\t\t\t\t\t\tconst morphTargets = this._morphInstance ? this._morphInstance.morph._targets : null;\n\t\t\t\t\t\t\t\t\t\tthis.mesh._initBoneAabbs(morphTargets);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst boneUsed = this.mesh.boneUsed;\n\t\t\t\t\t\t\t\tlet first = true;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < this.mesh.boneAabb.length; i++){\n\t\t\t\t\t\t\t\t\t\tif (boneUsed[i]) {\n\t\t\t\t\t\t\t\t\t\t\t\t_tempBoneAabb.setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);\n\t\t\t\t\t\t\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlocalAabb.center.copy(_tempBoneAabb.center);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlocalAabb.halfExtents.copy(_tempBoneAabb.halfExtents);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlocalAabb.add(_tempBoneAabb);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttoWorldSpace = true;\n\t\t\t\t\t\t} else if (this.node._aabbVer !== this._aabbVer || this.mesh._aabbVer !== this._aabbMeshVer) {\n\t\t\t\t\t\t\t\tif (this.mesh) {\n\t\t\t\t\t\t\t\t\t\tlocalAabb.center.copy(this.mesh.aabb.center);\n\t\t\t\t\t\t\t\t\t\tlocalAabb.halfExtents.copy(this.mesh.aabb.halfExtents);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tlocalAabb.center.set(0, 0, 0);\n\t\t\t\t\t\t\t\t\t\tlocalAabb.halfExtents.set(0, 0, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this.mesh && this.mesh.morph) {\n\t\t\t\t\t\t\t\t\t\tconst morphAabb = this.mesh.morph.aabb;\n\t\t\t\t\t\t\t\t\t\tlocalAabb._expand(morphAabb.getMin(), morphAabb.getMax());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttoWorldSpace = true;\n\t\t\t\t\t\t\t\tthis._aabbVer = this.node._aabbVer;\n\t\t\t\t\t\t\t\tthis._aabbMeshVer = this.mesh._aabbVer;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (toWorldSpace) {\n\t\t\t\t\t\tthis._aabb.setFromTransformedAabb(localAabb, this.node.getWorldTransform());\n\t\t\t\t}\n\t\t\t\treturn this._aabb;\n\t\t}\n\t\tclearShaders() {\n\t\t\t\tthis._shaderCache.forEach((shaderInstance)=>{\n\t\t\t\t\t\tshaderInstance.destroy();\n\t\t\t\t});\n\t\t\t\tthis._shaderCache.clear();\n\t\t}\n\t\tgetShaderInstance(shaderPass, lightHash, scene, cameraShaderParams, viewUniformFormat, viewBindGroupFormat, sortedLights) {\n\t\t\t\tconst shaderDefs = this._shaderDefs;\n\t\t\t\tlookupHashes[0] = shaderPass;\n\t\t\t\tlookupHashes[1] = lightHash;\n\t\t\t\tlookupHashes[2] = shaderDefs;\n\t\t\t\tlookupHashes[3] = cameraShaderParams.hash;\n\t\t\t\tconst hash = hash32Fnv1a(lookupHashes);\n\t\t\t\tlet shaderInstance = this._shaderCache.get(hash);\n\t\t\t\tif (!shaderInstance) {\n\t\t\t\t\t\tconst mat = this._material;\n\t\t\t\t\t\tshaderInstance = new ShaderInstance();\n\t\t\t\t\t\tshaderInstance.shader = mat.variants.get(hash);\n\t\t\t\t\t\tshaderInstance.hashes = new Uint32Array(lookupHashes);\n\t\t\t\t\t\tif (!shaderInstance.shader) {\n\t\t\t\t\t\t\t\tconst shader = mat.getShaderVariant({\n\t\t\t\t\t\t\t\t\t\tdevice: this.mesh.device,\n\t\t\t\t\t\t\t\t\t\tscene: scene,\n\t\t\t\t\t\t\t\t\t\tobjDefs: shaderDefs,\n\t\t\t\t\t\t\t\t\t\tcameraShaderParams: cameraShaderParams,\n\t\t\t\t\t\t\t\t\t\tpass: shaderPass,\n\t\t\t\t\t\t\t\t\t\tsortedLights: sortedLights,\n\t\t\t\t\t\t\t\t\t\tviewUniformFormat: viewUniformFormat,\n\t\t\t\t\t\t\t\t\t\tviewBindGroupFormat: viewBindGroupFormat,\n\t\t\t\t\t\t\t\t\t\tvertexFormat: this.mesh.vertexBuffer?.format\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tmat.variants.set(hash, shader);\n\t\t\t\t\t\t\t\tshaderInstance.shader = shader;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._shaderCache.set(hash, shaderInstance);\n\t\t\t\t}\n\t\t\t\treturn shaderInstance;\n\t\t}\n\t\tset material(material) {\n\t\t\t\tthis.clearShaders();\n\t\t\t\tconst prevMat = this._material;\n\t\t\t\tif (prevMat) {\n\t\t\t\t\t\tprevMat.removeMeshInstanceRef(this);\n\t\t\t\t}\n\t\t\t\tthis._material = material;\n\t\t\t\tif (material) {\n\t\t\t\t\t\tmaterial.addMeshInstanceRef(this);\n\t\t\t\t\t\tthis.transparent = material.transparent;\n\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t}\n\t\t}\n\t\tget material() {\n\t\t\t\treturn this._material;\n\t\t}\n\t\t_updateShaderDefs(shaderDefs) {\n\t\t\t\tif (shaderDefs !== this._shaderDefs) {\n\t\t\t\t\t\tthis._shaderDefs = shaderDefs;\n\t\t\t\t\t\tthis.clearShaders();\n\t\t\t\t}\n\t\t}\n\t\tset calculateSortDistance(calculateSortDistance) {\n\t\t\t\tthis._calculateSortDistance = calculateSortDistance;\n\t\t}\n\t\tget calculateSortDistance() {\n\t\t\t\treturn this._calculateSortDistance;\n\t\t}\n\t\tset receiveShadow(val) {\n\t\t\t\tif (this._receiveShadow !== val) {\n\t\t\t\t\t\tthis._receiveShadow = val;\n\t\t\t\t\t\tthis._updateShaderDefs(val ? this._shaderDefs & -2 : this._shaderDefs | SHADERDEF_NOSHADOW);\n\t\t\t\t}\n\t\t}\n\t\tget receiveShadow() {\n\t\t\t\treturn this._receiveShadow;\n\t\t}\n\t\tset batching(val) {\n\t\t\t\tthis._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_BATCH : this._shaderDefs & -16385);\n\t\t}\n\t\tget batching() {\n\t\t\t\treturn (this._shaderDefs & SHADERDEF_BATCH) !== 0;\n\t\t}\n\t\tset skinInstance(val) {\n\t\t\t\tthis._skinInstance = val;\n\t\t\t\tthis._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SKIN : this._shaderDefs & -3);\n\t\t\t\tthis._setupSkinUpdate();\n\t\t}\n\t\tget skinInstance() {\n\t\t\t\treturn this._skinInstance;\n\t\t}\n\t\tset morphInstance(val) {\n\t\t\t\tthis._morphInstance?.destroy();\n\t\t\t\tthis._morphInstance = val;\n\t\t\t\tlet shaderDefs = this._shaderDefs;\n\t\t\t\tshaderDefs = val && val.morph.morphPositions ? shaderDefs | SHADERDEF_MORPH_POSITION : shaderDefs & -1025;\n\t\t\t\tshaderDefs = val && val.morph.morphNormals ? shaderDefs | SHADERDEF_MORPH_NORMAL : shaderDefs & -2049;\n\t\t\t\tshaderDefs = val && val.morph.intRenderFormat ? shaderDefs | SHADERDEF_MORPH_TEXTURE_BASED_INT : shaderDefs & -8193;\n\t\t\t\tthis._updateShaderDefs(shaderDefs);\n\t\t}\n\t\tget morphInstance() {\n\t\t\t\treturn this._morphInstance;\n\t\t}\n\t\tset screenSpace(val) {\n\t\t\t\tif (this._screenSpace !== val) {\n\t\t\t\t\t\tthis._screenSpace = val;\n\t\t\t\t\t\tthis._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SCREENSPACE : this._shaderDefs & -257);\n\t\t\t\t}\n\t\t}\n\t\tget screenSpace() {\n\t\t\t\treturn this._screenSpace;\n\t\t}\n\t\tset key(val) {\n\t\t\t\tthis._sortKeyForward = val;\n\t\t}\n\t\tget key() {\n\t\t\t\treturn this._sortKeyForward;\n\t\t}\n\t\tset mask(val) {\n\t\t\t\tconst toggles = this._shaderDefs & 0x0000FFFF;\n\t\t\t\tthis._updateShaderDefs(toggles | val << 16);\n\t\t}\n\t\tget mask() {\n\t\t\t\treturn this._shaderDefs >> 16;\n\t\t}\n\t\tset instancingCount(value) {\n\t\t\t\tif (this.instancingData) {\n\t\t\t\t\t\tthis.instancingData.count = value;\n\t\t\t\t}\n\t\t}\n\t\tget instancingCount() {\n\t\t\t\treturn this.instancingData ? this.instancingData.count : 0;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst mesh = this.mesh;\n\t\t\t\tif (mesh) {\n\t\t\t\t\t\tthis.mesh = null;\n\t\t\t\t\t\tif (mesh.refCount < 1) {\n\t\t\t\t\t\t\t\tmesh.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);\n\t\t\t\tthis.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);\n\t\t\t\tthis._skinInstance?.destroy();\n\t\t\t\tthis._skinInstance = null;\n\t\t\t\tthis.morphInstance?.destroy();\n\t\t\t\tthis.morphInstance = null;\n\t\t\t\tthis.clearShaders();\n\t\t\t\tthis.material = null;\n\t\t\t\tthis.instancingData?.destroy();\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.lightmapParamNames = [\n\t\t\t\t\t\t'texture_lightMap',\n\t\t\t\t\t\t'texture_dirLightMap'\n\t\t\t\t];\n\t\t}\n\t\tstatic _prepareRenderStyleForArray(meshInstances, renderStyle) {\n\t\t\t\tif (meshInstances) {\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tmeshInstances[i]._renderStyle = renderStyle;\n\t\t\t\t\t\t\t\tconst mesh = meshInstances[i].mesh;\n\t\t\t\t\t\t\t\tif (!_meshSet.has(mesh)) {\n\t\t\t\t\t\t\t\t\t\t_meshSet.add(mesh);\n\t\t\t\t\t\t\t\t\t\tmesh.prepareRenderState(renderStyle);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_meshSet.clear();\n\t\t\t\t}\n\t\t}\n\t\t_isVisible(camera) {\n\t\t\t\tif (this.visible) {\n\t\t\t\t\t\tif (this.isVisibleFunc) {\n\t\t\t\t\t\t\t\treturn this.isVisibleFunc(camera);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_tempSphere.center = this.aabb.center;\n\t\t\t\t\t\t_tempSphere.radius = this._aabb.halfExtents.length();\n\t\t\t\t\t\treturn camera.frustum.containsSphere(_tempSphere) > 0;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tupdateKey() {\n\t\t\t\tconst { material } = this;\n\t\t\t\tthis._sortKeyForward = this._drawBucket << 23 | (material.alphaToCoverage || material.alphaTest ? 0x400000 : 0) | material.id & 0x3fffff;\n\t\t}\n\t\tsetInstancing(vertexBuffer, cull = false) {\n\t\t\t\tif (vertexBuffer) {\n\t\t\t\t\t\tthis.instancingData = new InstancingData(vertexBuffer.numVertices);\n\t\t\t\t\t\tthis.instancingData.vertexBuffer = vertexBuffer;\n\t\t\t\t\t\tvertexBuffer.format.instancing = true;\n\t\t\t\t\t\tthis.cull = cull;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.instancingData = null;\n\t\t\t\t\t\tthis.cull = true;\n\t\t\t\t}\n\t\t\t\tthis._updateShaderDefs(vertexBuffer ? this._shaderDefs | SHADERDEF_INSTANCING : this._shaderDefs & -33);\n\t\t}\n\t\tsetIndirect(camera, slot) {\n\t\t\t\tthis._allocIndirectData();\n\t\t\t\tthis.indirectData.map.set(camera?.camera ?? null, slot);\n\t\t\t\tconst device = this.mesh.device;\n\t\t\t\tdevice.mapsToClear.add(this.indirectData.map);\n\t\t}\n\t\tgetIndirectMetaData() {\n\t\t\t\tthis._allocIndirectData();\n\t\t\t\tconst prim = this.mesh?.primitive[this.renderStyle];\n\t\t\t\tconst data = this.indirectData.meshMetaData;\n\t\t\t\tdata[0] = prim.count;\n\t\t\t\tdata[1] = prim.base;\n\t\t\t\tdata[2] = prim.baseVertex;\n\t\t\t\treturn data;\n\t\t}\n\t\t_allocIndirectData() {\n\t\t\t\tif (!this.indirectData) {\n\t\t\t\t\t\tthis.indirectData = new IndirectData();\n\t\t\t\t}\n\t\t}\n\t\tensureMaterial(device) {\n\t\t\t\tif (!this.material) {\n\t\t\t\t\t\tthis.material = getDefaultMaterial(device);\n\t\t\t\t}\n\t\t}\n\t\tclearParameters() {\n\t\t\t\tthis.parameters = {};\n\t\t}\n\t\tgetParameters() {\n\t\t\t\treturn this.parameters;\n\t\t}\n\t\tgetParameter(name) {\n\t\t\t\treturn this.parameters[name];\n\t\t}\n\t\tsetParameter(name, data, passFlags = 0xFFFFFFFF) {\n\t\t\t\tconst param = this.parameters[name];\n\t\t\t\tif (param) {\n\t\t\t\t\t\tparam.data = data;\n\t\t\t\t\t\tparam.passFlags = passFlags;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.parameters[name] = {\n\t\t\t\t\t\t\t\tscopeId: null,\n\t\t\t\t\t\t\t\tdata: data,\n\t\t\t\t\t\t\t\tpassFlags: passFlags\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tsetRealtimeLightmap(name, texture) {\n\t\t\t\tconst old = this.getParameter(name);\n\t\t\t\tif (old === texture) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (old) {\n\t\t\t\t\t\tLightmapCache.decRef(old.data);\n\t\t\t\t}\n\t\t\t\tif (texture) {\n\t\t\t\t\t\tLightmapCache.incRef(texture);\n\t\t\t\t\t\tthis.setParameter(name, texture);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.deleteParameter(name);\n\t\t\t\t}\n\t\t}\n\t\tdeleteParameter(name) {\n\t\t\t\tif (this.parameters[name]) {\n\t\t\t\t\t\tdelete this.parameters[name];\n\t\t\t\t}\n\t\t}\n\t\tsetParameters(device, passFlag) {\n\t\t\t\tconst parameters = this.parameters;\n\t\t\t\tfor(const paramName in parameters){\n\t\t\t\t\t\tconst parameter = parameters[paramName];\n\t\t\t\t\t\tif (parameter.passFlags & passFlag) {\n\t\t\t\t\t\t\t\tif (!parameter.scopeId) {\n\t\t\t\t\t\t\t\t\t\tparameter.scopeId = device.scope.resolve(paramName);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tparameter.scopeId.setValue(parameter.data);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetLightmapped(value) {\n\t\t\t\tif (value) {\n\t\t\t\t\t\tthis.mask = (this.mask | MASK_AFFECT_LIGHTMAPPED) & -6;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);\n\t\t\t\t\t\tthis.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);\n\t\t\t\t\t\tthis._shaderDefs &= -4289;\n\t\t\t\t\t\tthis.mask = (this.mask | MASK_AFFECT_DYNAMIC) & -7;\n\t\t\t\t}\n\t\t}\n\t\tsetCustomAabb(aabb) {\n\t\t\t\tif (aabb) {\n\t\t\t\t\t\tif (this._customAabb) {\n\t\t\t\t\t\t\t\tthis._customAabb.copy(aabb);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._customAabb = aabb.clone();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._customAabb = null;\n\t\t\t\t\t\tthis._aabbVer = -1;\n\t\t\t\t}\n\t\t\t\tthis._setupSkinUpdate();\n\t\t}\n\t\t_setupSkinUpdate() {\n\t\t\t\tif (this._skinInstance) {\n\t\t\t\t\t\tthis._skinInstance._updateBeforeCull = !this._customAabb;\n\t\t\t\t}\n\t\t}\n}\n\nconst _triFanIndices = [\n\t\t0,\n\t\t1,\n\t\t3,\n\t\t2,\n\t\t3,\n\t\t1\n];\nconst _triStripIndices = [\n\t\t0,\n\t\t1,\n\t\t3,\n\t\t0,\n\t\t3,\n\t\t2\n];\nconst mat3 = new Mat3();\nfunction paramsIdentical(a, b) {\n\t\tif (a && !b) return false;\n\t\tif (!a && b) return false;\n\t\ta = a.data;\n\t\tb = b.data;\n\t\tif (a === b) return true;\n\t\tif (a instanceof Float32Array && b instanceof Float32Array) {\n\t\t\t\tif (a.length !== b.length) return false;\n\t\t\t\tfor(let i = 0; i < a.length; i++){\n\t\t\t\t\t\tif (a[i] !== b[i]) return false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n}\nfunction equalParamSets(params1, params2) {\n\t\tfor(const param in params1){\n\t\t\t\tif (params1.hasOwnProperty(param) && !paramsIdentical(params1[param], params2[param])) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t}\n\t\tfor(const param in params2){\n\t\t\t\tif (params2.hasOwnProperty(param) && !paramsIdentical(params2[param], params1[param])) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t}\n\t\treturn true;\n}\nfunction getScaleSign(mi) {\n\t\treturn mi.node.worldTransform.scaleSign;\n}\nclass BatchManager {\n\t\tconstructor(device, root, scene){\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.rootNode = root;\n\t\t\t\tthis.scene = scene;\n\t\t\t\tthis._init = false;\n\t\t\t\tthis._batchGroups = {};\n\t\t\t\tthis._batchGroupCounter = 0;\n\t\t\t\tthis._batchList = [];\n\t\t\t\tthis._dirtyGroups = [];\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.device = null;\n\t\t\t\tthis.rootNode = null;\n\t\t\t\tthis.scene = null;\n\t\t\t\tthis._batchGroups = {};\n\t\t\t\tthis._batchList = [];\n\t\t\t\tthis._dirtyGroups = [];\n\t\t}\n\t\taddGroup(name, dynamic, maxAabbSize, id, layers) {\n\t\t\t\tif (id === undefined) {\n\t\t\t\t\t\tid = this._batchGroupCounter;\n\t\t\t\t\t\tthis._batchGroupCounter++;\n\t\t\t\t}\n\t\t\t\tif (this._batchGroups[id]) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tconst group = new BatchGroup(id, name, dynamic, maxAabbSize, layers);\n\t\t\t\tthis._batchGroups[id] = group;\n\t\t\t\treturn group;\n\t\t}\n\t\tremoveGroup(id) {\n\t\t\t\tif (!this._batchGroups[id]) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst newBatchList = [];\n\t\t\t\tfor(let i = 0; i < this._batchList.length; i++){\n\t\t\t\t\t\tif (this._batchList[i].batchGroupId === id) {\n\t\t\t\t\t\t\t\tthis.destroyBatch(this._batchList[i]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewBatchList.push(this._batchList[i]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._batchList = newBatchList;\n\t\t\t\tthis._removeModelsFromBatchGroup(this.rootNode, id);\n\t\t\t\tdelete this._batchGroups[id];\n\t\t}\n\t\tmarkGroupDirty(id) {\n\t\t\t\tif (this._dirtyGroups.indexOf(id) < 0) {\n\t\t\t\t\t\tthis._dirtyGroups.push(id);\n\t\t\t\t}\n\t\t}\n\t\tgetGroupByName(name) {\n\t\t\t\tconst groups = this._batchGroups;\n\t\t\t\tfor(const group in groups){\n\t\t\t\t\t\tif (!groups.hasOwnProperty(group)) continue;\n\t\t\t\t\t\tif (groups[group].name === name) {\n\t\t\t\t\t\t\t\treturn groups[group];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tgetBatches(batchGroupId) {\n\t\t\t\tconst results = [];\n\t\t\t\tconst len = this._batchList.length;\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tconst batch = this._batchList[i];\n\t\t\t\t\t\tif (batch.batchGroupId === batchGroupId) {\n\t\t\t\t\t\t\t\tresults.push(batch);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn results;\n\t\t}\n\t\t_removeModelsFromBatchGroup(node, id) {\n\t\t\t\tif (!node.enabled) return;\n\t\t\t\tif (node.model && node.model.batchGroupId === id) {\n\t\t\t\t\t\tnode.model.batchGroupId = -1;\n\t\t\t\t}\n\t\t\t\tif (node.render && node.render.batchGroupId === id) {\n\t\t\t\t\t\tnode.render.batchGroupId = -1;\n\t\t\t\t}\n\t\t\t\tif (node.element && node.element.batchGroupId === id) {\n\t\t\t\t\t\tnode.element.batchGroupId = -1;\n\t\t\t\t}\n\t\t\t\tif (node.sprite && node.sprite.batchGroupId === id) {\n\t\t\t\t\t\tnode.sprite.batchGroupId = -1;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < node._children.length; i++){\n\t\t\t\t\t\tthis._removeModelsFromBatchGroup(node._children[i], id);\n\t\t\t\t}\n\t\t}\n\t\tinsert(type, groupId, node) {\n\t\t\t\tconst group = this._batchGroups[groupId];\n\t\t\t\tif (group) {\n\t\t\t\t\t\tif (group._obj[type].indexOf(node) < 0) {\n\t\t\t\t\t\t\t\tgroup._obj[type].push(node);\n\t\t\t\t\t\t\t\tthis.markGroupDirty(groupId);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremove(type, groupId, node) {\n\t\t\t\tconst group = this._batchGroups[groupId];\n\t\t\t\tif (group) {\n\t\t\t\t\t\tconst idx = group._obj[type].indexOf(node);\n\t\t\t\t\t\tif (idx >= 0) {\n\t\t\t\t\t\t\t\tgroup._obj[type].splice(idx, 1);\n\t\t\t\t\t\t\t\tthis.markGroupDirty(groupId);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_extractRender(node, arr, group, groupMeshInstances) {\n\t\t\t\tif (node.render) {\n\t\t\t\t\t\tarr = groupMeshInstances[node.render.batchGroupId] = arr.concat(node.render.meshInstances);\n\t\t\t\t\t\tnode.render.removeFromLayers();\n\t\t\t\t}\n\t\t\t\treturn arr;\n\t\t}\n\t\t_extractModel(node, arr, group, groupMeshInstances) {\n\t\t\t\tif (node.model && node.model.model) {\n\t\t\t\t\t\tarr = groupMeshInstances[node.model.batchGroupId] = arr.concat(node.model.meshInstances);\n\t\t\t\t\t\tnode.model.removeModelFromLayers();\n\t\t\t\t}\n\t\t\t\treturn arr;\n\t\t}\n\t\t_extractElement(node, arr, group) {\n\t\t\t\tif (!node.element) return;\n\t\t\t\tlet valid = false;\n\t\t\t\tif (node.element._text && node.element._text._model.meshInstances.length > 0) {\n\t\t\t\t\t\tarr.push(node.element._text._model.meshInstances[0]);\n\t\t\t\t\t\tnode.element.removeModelFromLayers(node.element._text._model);\n\t\t\t\t\t\tvalid = true;\n\t\t\t\t} else if (node.element._image) {\n\t\t\t\t\t\tarr.push(node.element._image._renderable.meshInstance);\n\t\t\t\t\t\tnode.element.removeModelFromLayers(node.element._image._renderable.model);\n\t\t\t\t\t\tif (node.element._image._renderable.unmaskMeshInstance) {\n\t\t\t\t\t\t\t\tarr.push(node.element._image._renderable.unmaskMeshInstance);\n\t\t\t\t\t\t\t\tif (!node.element._image._renderable.unmaskMeshInstance.stencilFront || !node.element._image._renderable.unmaskMeshInstance.stencilBack) {\n\t\t\t\t\t\t\t\t\t\tnode.element._dirtifyMask();\n\t\t\t\t\t\t\t\t\t\tnode.element._onPrerender();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvalid = true;\n\t\t\t\t}\n\t\t\t\tif (valid) {\n\t\t\t\t\t\tgroup._ui = true;\n\t\t\t\t}\n\t\t}\n\t\t_collectAndRemoveMeshInstances(groupMeshInstances, groupIds) {\n\t\t\t\tfor(let g = 0; g < groupIds.length; g++){\n\t\t\t\t\t\tconst id = groupIds[g];\n\t\t\t\t\t\tconst group = this._batchGroups[id];\n\t\t\t\t\t\tif (!group) continue;\n\t\t\t\t\t\tlet arr = groupMeshInstances[id];\n\t\t\t\t\t\tif (!arr) arr = groupMeshInstances[id] = [];\n\t\t\t\t\t\tfor(let m = 0; m < group._obj.model.length; m++){\n\t\t\t\t\t\t\t\tarr = this._extractModel(group._obj.model[m], arr, group, groupMeshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let r = 0; r < group._obj.render.length; r++){\n\t\t\t\t\t\t\t\tarr = this._extractRender(group._obj.render[r], arr, group, groupMeshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let e = 0; e < group._obj.element.length; e++){\n\t\t\t\t\t\t\t\tthis._extractElement(group._obj.element[e], arr, group);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let s = 0; s < group._obj.sprite.length; s++){\n\t\t\t\t\t\t\t\tconst node = group._obj.sprite[s];\n\t\t\t\t\t\t\t\tif (node.sprite && node.sprite._meshInstance && (group.dynamic || node.sprite.sprite._renderMode === SPRITE_RENDERMODE_SIMPLE)) {\n\t\t\t\t\t\t\t\t\t\tarr.push(node.sprite._meshInstance);\n\t\t\t\t\t\t\t\t\t\tnode.sprite.removeModelFromLayers();\n\t\t\t\t\t\t\t\t\t\tgroup._sprite = true;\n\t\t\t\t\t\t\t\t\t\tnode.sprite._batchGroup = group;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgenerate(groupIds) {\n\t\t\t\tconst groupMeshInstances = {};\n\t\t\t\tif (!groupIds) {\n\t\t\t\t\t\tgroupIds = Object.keys(this._batchGroups);\n\t\t\t\t}\n\t\t\t\tconst newBatchList = [];\n\t\t\t\tfor(let i = 0; i < this._batchList.length; i++){\n\t\t\t\t\t\tif (groupIds.indexOf(this._batchList[i].batchGroupId) < 0) {\n\t\t\t\t\t\t\t\tnewBatchList.push(this._batchList[i]);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.destroyBatch(this._batchList[i]);\n\t\t\t\t}\n\t\t\t\tthis._batchList = newBatchList;\n\t\t\t\tthis._collectAndRemoveMeshInstances(groupMeshInstances, groupIds);\n\t\t\t\tif (groupIds === this._dirtyGroups) {\n\t\t\t\t\t\tthis._dirtyGroups.length = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst newDirtyGroups = [];\n\t\t\t\t\t\tfor(let i = 0; i < this._dirtyGroups.length; i++){\n\t\t\t\t\t\t\t\tif (groupIds.indexOf(this._dirtyGroups[i]) < 0) newDirtyGroups.push(this._dirtyGroups[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._dirtyGroups = newDirtyGroups;\n\t\t\t\t}\n\t\t\t\tlet group, lists, groupData, batch;\n\t\t\t\tfor(const groupId in groupMeshInstances){\n\t\t\t\t\t\tif (!groupMeshInstances.hasOwnProperty(groupId)) continue;\n\t\t\t\t\t\tgroup = groupMeshInstances[groupId];\n\t\t\t\t\t\tgroupData = this._batchGroups[groupId];\n\t\t\t\t\t\tif (!groupData) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlists = this.prepare(group, groupData.dynamic, groupData.maxAabbSize, groupData._ui || groupData._sprite);\n\t\t\t\t\t\tfor(let i = 0; i < lists.length; i++){\n\t\t\t\t\t\t\t\tbatch = this.create(lists[i], groupData.dynamic, parseInt(groupId, 10));\n\t\t\t\t\t\t\t\tif (batch) {\n\t\t\t\t\t\t\t\t\t\tbatch.addToLayers(this.scene, groupData.layers);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tprepare(meshInstances, dynamic, maxAabbSize = Number.POSITIVE_INFINITY, translucent) {\n\t\t\t\tif (meshInstances.length === 0) return [];\n\t\t\t\tconst halfMaxAabbSize = maxAabbSize * 0.5;\n\t\t\t\tconst maxInstanceCount = 1024;\n\t\t\t\tconst maxNumVertices = 0xffffffff;\n\t\t\t\tconst aabb = new BoundingBox();\n\t\t\t\tconst testAabb = new BoundingBox();\n\t\t\t\tlet skipTranslucentAabb = null;\n\t\t\t\tlet sf;\n\t\t\t\tconst lists = [];\n\t\t\t\tlet j = 0;\n\t\t\t\tif (translucent) {\n\t\t\t\t\t\tmeshInstances.sort((a, b)=>{\n\t\t\t\t\t\t\t\treturn a.drawOrder - b.drawOrder;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tlet meshInstancesLeftA = meshInstances;\n\t\t\t\tlet meshInstancesLeftB;\n\t\t\t\tconst skipMesh = translucent ? function(mi) {\n\t\t\t\t\t\tif (skipTranslucentAabb) {\n\t\t\t\t\t\t\t\tskipTranslucentAabb.add(mi.aabb);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tskipTranslucentAabb = mi.aabb.clone();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmeshInstancesLeftB.push(mi);\n\t\t\t\t} : function(mi) {\n\t\t\t\t\t\tmeshInstancesLeftB.push(mi);\n\t\t\t\t};\n\t\t\t\twhile(meshInstancesLeftA.length > 0){\n\t\t\t\t\t\tlists[j] = [\n\t\t\t\t\t\t\t\tmeshInstancesLeftA[0]\n\t\t\t\t\t\t];\n\t\t\t\t\t\tmeshInstancesLeftB = [];\n\t\t\t\t\t\tconst material = meshInstancesLeftA[0].material;\n\t\t\t\t\t\tconst layer = meshInstancesLeftA[0].layer;\n\t\t\t\t\t\tconst defs = meshInstancesLeftA[0]._shaderDefs;\n\t\t\t\t\t\tconst params = meshInstancesLeftA[0].parameters;\n\t\t\t\t\t\tconst stencil = meshInstancesLeftA[0].stencilFront;\n\t\t\t\t\t\tlet vertCount = meshInstancesLeftA[0].mesh.vertexBuffer.getNumVertices();\n\t\t\t\t\t\tconst drawOrder = meshInstancesLeftA[0].drawOrder;\n\t\t\t\t\t\taabb.copy(meshInstancesLeftA[0].aabb);\n\t\t\t\t\t\tconst scaleSign = getScaleSign(meshInstancesLeftA[0]);\n\t\t\t\t\t\tconst vertexFormatBatchingHash = meshInstancesLeftA[0].mesh.vertexBuffer.format.batchingHash;\n\t\t\t\t\t\tconst indexed = meshInstancesLeftA[0].mesh.primitive[0].indexed;\n\t\t\t\t\t\tskipTranslucentAabb = null;\n\t\t\t\t\t\tfor(let i = 1; i < meshInstancesLeftA.length; i++){\n\t\t\t\t\t\t\t\tconst mi = meshInstancesLeftA[i];\n\t\t\t\t\t\t\t\tif (dynamic && lists[j].length >= maxInstanceCount) {\n\t\t\t\t\t\t\t\t\t\tmeshInstancesLeftB = meshInstancesLeftB.concat(meshInstancesLeftA.slice(i));\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (material !== mi.material || layer !== mi.layer || vertexFormatBatchingHash !== mi.mesh.vertexBuffer.format.batchingHash || indexed !== mi.mesh.primitive[0].indexed || defs !== mi._shaderDefs || vertCount + mi.mesh.vertexBuffer.getNumVertices() > maxNumVertices) {\n\t\t\t\t\t\t\t\t\t\tskipMesh(mi);\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttestAabb.copy(aabb);\n\t\t\t\t\t\t\t\ttestAabb.add(mi.aabb);\n\t\t\t\t\t\t\t\tif (testAabb.halfExtents.x > halfMaxAabbSize || testAabb.halfExtents.y > halfMaxAabbSize || testAabb.halfExtents.z > halfMaxAabbSize) {\n\t\t\t\t\t\t\t\t\t\tskipMesh(mi);\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (stencil) {\n\t\t\t\t\t\t\t\t\t\tif (!(sf = mi.stencilFront) || stencil.func !== sf.func || stencil.zpass !== sf.zpass) {\n\t\t\t\t\t\t\t\t\t\t\t\tskipMesh(mi);\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (scaleSign !== getScaleSign(mi)) {\n\t\t\t\t\t\t\t\t\t\tskipMesh(mi);\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!equalParamSets(params, mi.parameters)) {\n\t\t\t\t\t\t\t\t\t\tskipMesh(mi);\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (translucent && skipTranslucentAabb && skipTranslucentAabb.intersects(mi.aabb) && mi.drawOrder !== drawOrder) {\n\t\t\t\t\t\t\t\t\t\tskipMesh(mi);\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\taabb.add(mi.aabb);\n\t\t\t\t\t\t\t\tvertCount += mi.mesh.vertexBuffer.getNumVertices();\n\t\t\t\t\t\t\t\tlists[j].push(mi);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tj++;\n\t\t\t\t\t\tmeshInstancesLeftA = meshInstancesLeftB;\n\t\t\t\t}\n\t\t\t\treturn lists;\n\t\t}\n\t\tcollectBatchedMeshData(meshInstances, dynamic) {\n\t\t\t\tlet streams = null;\n\t\t\t\tlet batchNumVerts = 0;\n\t\t\t\tlet batchNumIndices = 0;\n\t\t\t\tlet material = null;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tif (meshInstances[i].visible) {\n\t\t\t\t\t\t\t\tconst mesh = meshInstances[i].mesh;\n\t\t\t\t\t\t\t\tconst numVerts = mesh.vertexBuffer.numVertices;\n\t\t\t\t\t\t\t\tbatchNumVerts += numVerts;\n\t\t\t\t\t\t\t\tif (mesh.primitive[0].indexed) {\n\t\t\t\t\t\t\t\t\t\tbatchNumIndices += mesh.primitive[0].count;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst primitiveType = mesh.primitive[0].type;\n\t\t\t\t\t\t\t\t\t\tif (primitiveType === PRIMITIVE_TRIFAN || primitiveType === PRIMITIVE_TRISTRIP) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (mesh.primitive[0].count === 4) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbatchNumIndices += 6;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!streams) {\n\t\t\t\t\t\t\t\t\t\tmaterial = meshInstances[i].material;\n\t\t\t\t\t\t\t\t\t\tstreams = {};\n\t\t\t\t\t\t\t\t\t\tconst elems = mesh.vertexBuffer.format.elements;\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < elems.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst semantic = elems[j].name;\n\t\t\t\t\t\t\t\t\t\t\t\tstreams[semantic] = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnumComponents: elems[j].numComponents,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdataType: elems[j].dataType,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnormalize: elems[j].normalize,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcount: 0\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (dynamic) {\n\t\t\t\t\t\t\t\t\t\t\t\tstreams[SEMANTIC_BLENDINDICES] = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnumComponents: 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdataType: TYPE_FLOAT32,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnormalize: false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcount: 0\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tstreams: streams,\n\t\t\t\t\t\tbatchNumVerts: batchNumVerts,\n\t\t\t\t\t\tbatchNumIndices: batchNumIndices,\n\t\t\t\t\t\tmaterial: material\n\t\t\t\t};\n\t\t}\n\t\tcreate(meshInstances, dynamic, batchGroupId) {\n\t\t\t\tif (!this._init) {\n\t\t\t\t\t\tthis.vertexFormats = {};\n\t\t\t\t\t\tthis._init = true;\n\t\t\t\t}\n\t\t\t\tlet stream = null;\n\t\t\t\tlet semantic;\n\t\t\t\tlet mesh, numVerts;\n\t\t\t\tlet batch = null;\n\t\t\t\tconst batchData = this.collectBatchedMeshData(meshInstances, dynamic);\n\t\t\t\tif (batchData.streams) {\n\t\t\t\t\t\tconst streams = batchData.streams;\n\t\t\t\t\t\tlet material = batchData.material;\n\t\t\t\t\t\tconst batchNumVerts = batchData.batchNumVerts;\n\t\t\t\t\t\tconst batchNumIndices = batchData.batchNumIndices;\n\t\t\t\t\t\tbatch = new Batch(meshInstances, dynamic, batchGroupId);\n\t\t\t\t\t\tthis._batchList.push(batch);\n\t\t\t\t\t\tlet indexBase, indexBaseVertex, numIndices, indexData;\n\t\t\t\t\t\tlet verticesOffset = 0;\n\t\t\t\t\t\tlet indexOffset = 0;\n\t\t\t\t\t\tlet transform;\n\t\t\t\t\t\tconst indexArrayType = batchNumVerts <= 0xffff ? Uint16Array : Uint32Array;\n\t\t\t\t\t\tconst indices = new indexArrayType(batchNumIndices);\n\t\t\t\t\t\tfor(semantic in streams){\n\t\t\t\t\t\t\t\tstream = streams[semantic];\n\t\t\t\t\t\t\t\tstream.typeArrayType = typedArrayTypes[stream.dataType];\n\t\t\t\t\t\t\t\tstream.elementByteSize = typedArrayTypesByteSize[stream.dataType];\n\t\t\t\t\t\t\t\tstream.buffer = new stream.typeArrayType(batchNumVerts * stream.numComponents);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tif (!meshInstances[i].visible) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmesh = meshInstances[i].mesh;\n\t\t\t\t\t\t\t\tnumVerts = mesh.vertexBuffer.numVertices;\n\t\t\t\t\t\t\t\tif (!dynamic) {\n\t\t\t\t\t\t\t\t\t\ttransform = meshInstances[i].node.getWorldTransform();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor(semantic in streams){\n\t\t\t\t\t\t\t\t\t\tif (semantic !== SEMANTIC_BLENDINDICES) {\n\t\t\t\t\t\t\t\t\t\t\t\tstream = streams[semantic];\n\t\t\t\t\t\t\t\t\t\t\t\tconst subarray = new stream.typeArrayType(stream.buffer.buffer, stream.elementByteSize * stream.count);\n\t\t\t\t\t\t\t\t\t\t\t\tconst totalComponents = mesh.getVertexStream(semantic, subarray) * stream.numComponents;\n\t\t\t\t\t\t\t\t\t\t\t\tstream.count += totalComponents;\n\t\t\t\t\t\t\t\t\t\t\t\tif (!dynamic && stream.numComponents >= 3) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (semantic === SEMANTIC_POSITION) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m = transform.data;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m0 = m[0];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m1 = m[1];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m2 = m[2];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m4 = m[4];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m5 = m[5];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m6 = m[6];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m8 = m[8];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m9 = m[9];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m10 = m[10];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m12 = m[12];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m13 = m[13];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst m14 = m[14];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet x, y, z;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < totalComponents; j += stream.numComponents){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tx = subarray[j];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ty = subarray[j + 1];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tz = subarray[j + 2];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsubarray[j] = x * m0 + y * m4 + z * m8 + m12;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsubarray[j + 1] = x * m1 + y * m5 + z * m9 + m13;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsubarray[j + 2] = x * m2 + y * m6 + z * m10 + m14;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else if (semantic === SEMANTIC_NORMAL || semantic === SEMANTIC_TANGENT) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmat3.invertMat4(transform).transpose();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst [m0, m1, m2, m3, m4, m5, m6, m7, m8] = mat3.data;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet x, y, z;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < totalComponents; j += stream.numComponents){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tx = subarray[j];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ty = subarray[j + 1];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tz = subarray[j + 2];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsubarray[j] = x * m0 + y * m3 + z * m6;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsubarray[j + 1] = x * m1 + y * m4 + z * m7;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsubarray[j + 2] = x * m2 + y * m5 + z * m8;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (dynamic) {\n\t\t\t\t\t\t\t\t\t\tstream = streams[SEMANTIC_BLENDINDICES];\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < numVerts; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tstream.buffer[stream.count++] = i;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (mesh.primitive[0].indexed) {\n\t\t\t\t\t\t\t\t\t\tindexBase = mesh.primitive[0].base;\n\t\t\t\t\t\t\t\t\t\tindexBaseVertex = mesh.primitive[0].baseVertex || 0;\n\t\t\t\t\t\t\t\t\t\tnumIndices = mesh.primitive[0].count;\n\t\t\t\t\t\t\t\t\t\tconst srcFormat = mesh.indexBuffer[0].getFormat();\n\t\t\t\t\t\t\t\t\t\tindexData = new typedArrayIndexFormats[srcFormat](mesh.indexBuffer[0].storage);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tindexBaseVertex = 0;\n\t\t\t\t\t\t\t\t\t\tconst primitiveType = mesh.primitive[0].type;\n\t\t\t\t\t\t\t\t\t\tif (primitiveType === PRIMITIVE_TRIFAN || primitiveType === PRIMITIVE_TRISTRIP) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (mesh.primitive[0].count === 4) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tindexBase = 0;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnumIndices = 6;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tindexData = primitiveType === PRIMITIVE_TRIFAN ? _triFanIndices : _triStripIndices;\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnumIndices = 0;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor(let j = 0; j < numIndices; j++){\n\t\t\t\t\t\t\t\t\t\tindices[j + indexOffset] = indexData[indexBase + j] + indexBaseVertex + verticesOffset;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tindexOffset += numIndices;\n\t\t\t\t\t\t\t\tverticesOffset += numVerts;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmesh = new Mesh(this.device);\n\t\t\t\t\t\tfor(semantic in streams){\n\t\t\t\t\t\t\t\tstream = streams[semantic];\n\t\t\t\t\t\t\t\tmesh.setVertexStream(semantic, stream.buffer, stream.numComponents, undefined, stream.dataType, stream.normalize);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (indices.length > 0) {\n\t\t\t\t\t\t\t\tmesh.setIndices(indices);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmesh.update(PRIMITIVE_TRIANGLES, false);\n\t\t\t\t\t\tif (dynamic) {\n\t\t\t\t\t\t\t\tmaterial = material.clone();\n\t\t\t\t\t\t\t\tmaterial.update();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst meshInstance = new MeshInstance(mesh, material, this.rootNode);\n\t\t\t\t\t\tmeshInstance.castShadow = batch.origMeshInstances[0].castShadow;\n\t\t\t\t\t\tmeshInstance.parameters = batch.origMeshInstances[0].parameters;\n\t\t\t\t\t\tmeshInstance.layer = batch.origMeshInstances[0].layer;\n\t\t\t\t\t\tmeshInstance._shaderDefs = batch.origMeshInstances[0]._shaderDefs;\n\t\t\t\t\t\tmeshInstance.batching = true;\n\t\t\t\t\t\tmeshInstance.cull = batch.origMeshInstances[0].cull;\n\t\t\t\t\t\tconst batchGroup = this._batchGroups[batchGroupId];\n\t\t\t\t\t\tif (batchGroup && batchGroup._ui) {\n\t\t\t\t\t\t\t\tmeshInstance.cull = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (dynamic) {\n\t\t\t\t\t\t\t\tconst nodes = [];\n\t\t\t\t\t\t\t\tfor(let i = 0; i < batch.origMeshInstances.length; i++){\n\t\t\t\t\t\t\t\t\t\tnodes.push(batch.origMeshInstances[i].node);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmeshInstance.skinInstance = new SkinBatchInstance(this.device, nodes, this.rootNode);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmeshInstance._updateAabb = false;\n\t\t\t\t\t\tmeshInstance.drawOrder = batch.origMeshInstances[0].drawOrder;\n\t\t\t\t\t\tmeshInstance.stencilFront = batch.origMeshInstances[0].stencilFront;\n\t\t\t\t\t\tmeshInstance.stencilBack = batch.origMeshInstances[0].stencilBack;\n\t\t\t\t\t\tmeshInstance.flipFacesFactor = getScaleSign(batch.origMeshInstances[0]);\n\t\t\t\t\t\tmeshInstance.castShadow = batch.origMeshInstances[0].castShadow;\n\t\t\t\t\t\tbatch.meshInstance = meshInstance;\n\t\t\t\t\t\tbatch.updateBoundingBox();\n\t\t\t\t}\n\t\t\t\treturn batch;\n\t\t}\n\t\tupdateAll() {\n\t\t\t\tif (this._dirtyGroups.length > 0) {\n\t\t\t\t\t\tthis.generate(this._dirtyGroups);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._batchList.length; i++){\n\t\t\t\t\t\tif (!this._batchList[i].dynamic) continue;\n\t\t\t\t\t\tthis._batchList[i].updateBoundingBox();\n\t\t\t\t}\n\t\t}\n\t\tclone(batch, clonedMeshInstances) {\n\t\t\t\tconst batch2 = new Batch(clonedMeshInstances, batch.dynamic, batch.batchGroupId);\n\t\t\t\tthis._batchList.push(batch2);\n\t\t\t\tconst nodes = [];\n\t\t\t\tfor(let i = 0; i < clonedMeshInstances.length; i++){\n\t\t\t\t\t\tnodes.push(clonedMeshInstances[i].node);\n\t\t\t\t}\n\t\t\t\tbatch2.meshInstance = new MeshInstance(batch.meshInstance.mesh, batch.meshInstance.material, batch.meshInstance.node);\n\t\t\t\tbatch2.meshInstance._updateAabb = false;\n\t\t\t\tbatch2.meshInstance.parameters = clonedMeshInstances[0].parameters;\n\t\t\t\tbatch2.meshInstance.cull = clonedMeshInstances[0].cull;\n\t\t\t\tbatch2.meshInstance.layer = clonedMeshInstances[0].layer;\n\t\t\t\tif (batch.dynamic) {\n\t\t\t\t\t\tbatch2.meshInstance.skinInstance = new SkinBatchInstance(this.device, nodes, this.rootNode);\n\t\t\t\t}\n\t\t\t\tbatch2.meshInstance.castShadow = batch.meshInstance.castShadow;\n\t\t\t\treturn batch2;\n\t\t}\n\t\tdestroyBatch(batch) {\n\t\t\t\tbatch.destroy(this.scene, this._batchGroups[batch.batchGroupId].layers);\n\t\t}\n}\n\nconst _colorUniformName = 'uSceneColorMap';\nclass RenderPassColorGrab extends RenderPass {\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.releaseRenderTarget(this.colorRenderTarget);\n\t\t}\n\t\tshouldReallocate(targetRT, sourceTexture, sourceFormat) {\n\t\t\t\tconst targetFormat = targetRT?.colorBuffer.format;\n\t\t\t\tif (targetFormat !== sourceFormat) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconst width = sourceTexture?.width || this.device.width;\n\t\t\t\tconst height = sourceTexture?.height || this.device.height;\n\t\t\t\treturn !targetRT || width !== targetRT.width || height !== targetRT.height;\n\t\t}\n\t\tallocateRenderTarget(renderTarget, sourceRenderTarget, device, format) {\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: _colorUniformName,\n\t\t\t\t\t\tformat,\n\t\t\t\t\t\twidth: sourceRenderTarget ? sourceRenderTarget.colorBuffer.width : device.width,\n\t\t\t\t\t\theight: sourceRenderTarget ? sourceRenderTarget.colorBuffer.height : device.height,\n\t\t\t\t\t\tmipmaps: true,\n\t\t\t\t\t\tminFilter: FILTER_LINEAR_MIPMAP_LINEAR,\n\t\t\t\t\t\tmagFilter: FILTER_LINEAR,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE\n\t\t\t\t});\n\t\t\t\tif (renderTarget) {\n\t\t\t\t\t\trenderTarget.destroyFrameBuffers();\n\t\t\t\t\t\trenderTarget._colorBuffer = texture;\n\t\t\t\t\t\trenderTarget._colorBuffers = [\n\t\t\t\t\t\t\t\ttexture\n\t\t\t\t\t\t];\n\t\t\t\t} else {\n\t\t\t\t\t\trenderTarget = new RenderTarget({\n\t\t\t\t\t\t\t\tname: 'ColorGrabRT',\n\t\t\t\t\t\t\t\tcolorBuffer: texture,\n\t\t\t\t\t\t\t\tdepth: false,\n\t\t\t\t\t\t\t\tstencil: false,\n\t\t\t\t\t\t\t\tautoResolve: false\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn renderTarget;\n\t\t}\n\t\treleaseRenderTarget(rt) {\n\t\t\t\tif (rt) {\n\t\t\t\t\t\trt.destroyTextureBuffers();\n\t\t\t\t\t\trt.destroy();\n\t\t\t\t}\n\t\t}\n\t\tframeUpdate() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst sourceRt = this.source;\n\t\t\t\tconst sourceFormat = sourceRt?.colorBuffer.format ?? this.device.backBufferFormat;\n\t\t\t\tif (this.shouldReallocate(this.colorRenderTarget, sourceRt?.colorBuffer, sourceFormat)) {\n\t\t\t\t\t\tthis.releaseRenderTarget(this.colorRenderTarget);\n\t\t\t\t\t\tthis.colorRenderTarget = this.allocateRenderTarget(this.colorRenderTarget, sourceRt, device, sourceFormat);\n\t\t\t\t}\n\t\t\t\tconst colorBuffer = this.colorRenderTarget.colorBuffer;\n\t\t\t\tdevice.scope.resolve(_colorUniformName).setValue(colorBuffer);\n\t\t}\n\t\texecute() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst sourceRt = this.source;\n\t\t\t\tconst colorBuffer = this.colorRenderTarget.colorBuffer;\n\t\t\t\tif (device.isWebGPU) {\n\t\t\t\t\t\tdevice.copyRenderTarget(sourceRt, this.colorRenderTarget, true, false);\n\t\t\t\t\t\tdevice.mipmapRenderer.generate(this.colorRenderTarget.colorBuffer.impl);\n\t\t\t\t} else {\n\t\t\t\t\t\tdevice.copyRenderTarget(sourceRt, this.colorRenderTarget, true, false);\n\t\t\t\t\t\tdevice.activeTexture(device.maxCombinedTextures - 1);\n\t\t\t\t\t\tdevice.bindTexture(colorBuffer);\n\t\t\t\t\t\tdevice.gl.generateMipmap(colorBuffer.impl._glTarget);\n\t\t\t\t}\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this.colorRenderTarget = null, this.source = null;\n\t\t}\n}\n\nconst _depthUniformName = 'uSceneDepthMap';\nclass RenderPassDepthGrab extends RenderPass {\n\t\tconstructor(device, camera){\n\t\t\t\tsuper(device), this.depthRenderTarget = null, this.camera = null;\n\t\t\t\tthis.camera = camera;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.releaseRenderTarget(this.depthRenderTarget);\n\t\t}\n\t\tshouldReallocate(targetRT, sourceTexture) {\n\t\t\t\tconst width = sourceTexture?.width || this.device.width;\n\t\t\t\tconst height = sourceTexture?.height || this.device.height;\n\t\t\t\treturn !targetRT || width !== targetRT.width || height !== targetRT.height;\n\t\t}\n\t\tallocateRenderTarget(renderTarget, sourceRenderTarget, device, format, isDepth) {\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: _depthUniformName,\n\t\t\t\t\t\tformat,\n\t\t\t\t\t\twidth: sourceRenderTarget ? sourceRenderTarget.colorBuffer.width : device.width,\n\t\t\t\t\t\theight: sourceRenderTarget ? sourceRenderTarget.colorBuffer.height : device.height,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE\n\t\t\t\t});\n\t\t\t\tif (renderTarget) {\n\t\t\t\t\t\trenderTarget.destroyFrameBuffers();\n\t\t\t\t\t\tif (isDepth) {\n\t\t\t\t\t\t\t\trenderTarget._depthBuffer = texture;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trenderTarget._colorBuffer = texture;\n\t\t\t\t\t\t\t\trenderTarget._colorBuffers = [\n\t\t\t\t\t\t\t\t\t\ttexture\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\trenderTarget = new RenderTarget({\n\t\t\t\t\t\t\t\tname: 'DepthGrabRT',\n\t\t\t\t\t\t\t\tcolorBuffer: isDepth ? null : texture,\n\t\t\t\t\t\t\t\tdepthBuffer: isDepth ? texture : null,\n\t\t\t\t\t\t\t\tdepth: !isDepth,\n\t\t\t\t\t\t\t\tstencil: device.supportsStencil,\n\t\t\t\t\t\t\t\tautoResolve: false\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn renderTarget;\n\t\t}\n\t\treleaseRenderTarget(rt) {\n\t\t\t\tif (rt) {\n\t\t\t\t\t\trt.destroyTextureBuffers();\n\t\t\t\t\t\trt.destroy();\n\t\t\t\t}\n\t\t}\n\t\tbefore() {\n\t\t\t\tconst camera = this.camera;\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst destinationRt = camera?.renderTarget ?? device.backBuffer;\n\t\t\t\tlet useDepthBuffer = true;\n\t\t\t\tlet format = destinationRt.stencil ? PIXELFORMAT_DEPTHSTENCIL : PIXELFORMAT_DEPTH;\n\t\t\t\tif (device.isWebGPU) {\n\t\t\t\t\t\tconst numSamples = destinationRt.samples;\n\t\t\t\t\t\tif (numSamples > 1) {\n\t\t\t\t\t\t\t\tformat = PIXELFORMAT_R32F;\n\t\t\t\t\t\t\t\tuseDepthBuffer = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst sourceTexture = camera.renderTarget?.depthBuffer ?? camera.renderTarget?.colorBuffer;\n\t\t\t\tif (this.shouldReallocate(this.depthRenderTarget, sourceTexture)) {\n\t\t\t\t\t\tthis.releaseRenderTarget(this.depthRenderTarget);\n\t\t\t\t\t\tthis.depthRenderTarget = this.allocateRenderTarget(this.depthRenderTarget, camera.renderTarget, device, format, useDepthBuffer);\n\t\t\t\t}\n\t\t\t\tconst colorBuffer = useDepthBuffer ? this.depthRenderTarget.depthBuffer : this.depthRenderTarget.colorBuffer;\n\t\t\t\tdevice.scope.resolve(_depthUniformName).setValue(colorBuffer);\n\t\t}\n\t\texecute() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tif (device.isWebGL2 && device.renderTarget.samples > 1) {\n\t\t\t\t\t\tconst src = device.renderTarget.impl._glFrameBuffer;\n\t\t\t\t\t\tconst dest = this.depthRenderTarget;\n\t\t\t\t\t\tdevice.renderTarget = dest;\n\t\t\t\t\t\tdevice.updateBegin();\n\t\t\t\t\t\tthis.depthRenderTarget.impl.internalResolve(device, src, dest.impl._glFrameBuffer, this.depthRenderTarget, device.gl.DEPTH_BUFFER_BIT);\n\t\t\t\t} else {\n\t\t\t\t\t\tdevice.copyRenderTarget(device.renderTarget, this.depthRenderTarget, false, true);\n\t\t\t\t}\n\t\t}\n}\n\nclass CameraShaderParams {\n\t\tget hash() {\n\t\t\t\tif (this._hash === undefined) {\n\t\t\t\t\t\tconst key = `${this.gammaCorrection}_${this.toneMapping}_${this.srgbRenderTarget}_${this.fog}_${this.ssaoEnabled}_${this.sceneDepthMapLinear}`;\n\t\t\t\t\t\tthis._hash = hashCode(key);\n\t\t\t\t}\n\t\t\t\treturn this._hash;\n\t\t}\n\t\tget defines() {\n\t\t\t\tconst defines = this._defines;\n\t\t\t\tif (this._definesDirty) {\n\t\t\t\t\t\tthis._definesDirty = false;\n\t\t\t\t\t\tdefines.clear();\n\t\t\t\t\t\tif (this._sceneDepthMapLinear) defines.set('SCENE_DEPTHMAP_LINEAR', true);\n\t\t\t\t\t\tdefines.set('FOG', this._fog.toUpperCase());\n\t\t\t\t\t\tdefines.set('TONEMAP', tonemapNames[this._toneMapping]);\n\t\t\t\t\t\tdefines.set('GAMMA', gammaNames[this.shaderOutputGamma]);\n\t\t\t\t}\n\t\t\t\treturn defines;\n\t\t}\n\t\tmarkDirty() {\n\t\t\t\tthis._hash = undefined;\n\t\t\t\tthis._definesDirty = true;\n\t\t}\n\t\tset fog(type) {\n\t\t\t\tif (this._fog !== type) {\n\t\t\t\t\t\tthis._fog = type;\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t}\n\t\tget fog() {\n\t\t\t\treturn this._fog;\n\t\t}\n\t\tset ssaoEnabled(value) {\n\t\t\t\tif (this._ssaoEnabled !== value) {\n\t\t\t\t\t\tthis._ssaoEnabled = value;\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t}\n\t\tget ssaoEnabled() {\n\t\t\t\treturn this._ssaoEnabled;\n\t\t}\n\t\tset gammaCorrection(value) {\n\t\t\t\tthis._gammaCorrectionAssigned = true;\n\t\t\t\tif (this._gammaCorrection !== value) {\n\t\t\t\t\t\tthis._gammaCorrection = value;\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t}\n\t\tget gammaCorrection() {\n\t\t\t\treturn this._gammaCorrection;\n\t\t}\n\t\tset toneMapping(value) {\n\t\t\t\tif (this._toneMapping !== value) {\n\t\t\t\t\t\tthis._toneMapping = value;\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t}\n\t\tget toneMapping() {\n\t\t\t\treturn this._toneMapping;\n\t\t}\n\t\tset srgbRenderTarget(value) {\n\t\t\t\tif (this._srgbRenderTarget !== value) {\n\t\t\t\t\t\tthis._srgbRenderTarget = value;\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t}\n\t\tget srgbRenderTarget() {\n\t\t\t\treturn this._srgbRenderTarget;\n\t\t}\n\t\tset sceneDepthMapLinear(value) {\n\t\t\t\tif (this._sceneDepthMapLinear !== value) {\n\t\t\t\t\t\tthis._sceneDepthMapLinear = value;\n\t\t\t\t\t\tthis.markDirty();\n\t\t\t\t}\n\t\t}\n\t\tget sceneDepthMapLinear() {\n\t\t\t\treturn this._sceneDepthMapLinear;\n\t\t}\n\t\tget shaderOutputGamma() {\n\t\t\t\tconst gammaOutput = this._gammaCorrection === GAMMA_SRGB && !this._srgbRenderTarget;\n\t\t\t\treturn gammaOutput ? GAMMA_SRGB : GAMMA_NONE;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis._gammaCorrection = GAMMA_SRGB;\n\t\t\t\tthis._toneMapping = TONEMAP_LINEAR;\n\t\t\t\tthis._srgbRenderTarget = false;\n\t\t\t\tthis._ssaoEnabled = false;\n\t\t\t\tthis._fog = FOG_NONE;\n\t\t\t\tthis._sceneDepthMapLinear = false;\n\t\t\t\tthis._defines = new Map();\n\t\t\t\tthis._definesDirty = true;\n\t\t}\n}\n\nconst _deviceCoord = new Vec3();\nconst _halfSize = new Vec3();\nconst _point$1 = new Vec3();\nconst _invViewProjMat = new Mat4();\nconst _frustumPoints = [\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3()\n];\nclass Camera {\n\t\tconstructor(){\n\t\t\t\tthis.shaderPassInfo = null;\n\t\t\t\tthis.renderPassColorGrab = null;\n\t\t\t\tthis.renderPassDepthGrab = null;\n\t\t\t\tthis.fogParams = null;\n\t\t\t\tthis.shaderParams = new CameraShaderParams();\n\t\t\t\tthis.renderPasses = [];\n\t\t\t\tthis.jitter = 0;\n\t\t\t\tthis._aspectRatio = 16 / 9;\n\t\t\t\tthis._aspectRatioMode = ASPECT_AUTO;\n\t\t\t\tthis._calculateProjection = null;\n\t\t\t\tthis._calculateTransform = null;\n\t\t\t\tthis._clearColor = new Color(0.75, 0.75, 0.75, 1);\n\t\t\t\tthis._clearColorBuffer = true;\n\t\t\t\tthis._clearDepth = 1;\n\t\t\t\tthis._clearDepthBuffer = true;\n\t\t\t\tthis._clearStencil = 0;\n\t\t\t\tthis._clearStencilBuffer = true;\n\t\t\t\tthis._cullFaces = true;\n\t\t\t\tthis._farClip = 1000;\n\t\t\t\tthis._flipFaces = false;\n\t\t\t\tthis._fov = 45;\n\t\t\t\tthis._frustumCulling = true;\n\t\t\t\tthis._horizontalFov = false;\n\t\t\t\tthis._layers = [\n\t\t\t\t\t\tLAYERID_WORLD,\n\t\t\t\t\t\tLAYERID_DEPTH,\n\t\t\t\t\t\tLAYERID_SKYBOX,\n\t\t\t\t\t\tLAYERID_UI,\n\t\t\t\t\t\tLAYERID_IMMEDIATE\n\t\t\t\t];\n\t\t\t\tthis._layersSet = new Set(this._layers);\n\t\t\t\tthis._nearClip = 0.1;\n\t\t\t\tthis._node = null;\n\t\t\t\tthis._orthoHeight = 10;\n\t\t\t\tthis._projection = PROJECTION_PERSPECTIVE;\n\t\t\t\tthis._rect = new Vec4(0, 0, 1, 1);\n\t\t\t\tthis._renderTarget = null;\n\t\t\t\tthis._scissorRect = new Vec4(0, 0, 1, 1);\n\t\t\t\tthis._scissorRectClear = false;\n\t\t\t\tthis._aperture = 16.0;\n\t\t\t\tthis._shutter = 1.0 / 1000.0;\n\t\t\t\tthis._sensitivity = 1000;\n\t\t\t\tthis._projMat = new Mat4();\n\t\t\t\tthis._projMatDirty = true;\n\t\t\t\tthis._projMatSkybox = new Mat4();\n\t\t\t\tthis._viewMat = new Mat4();\n\t\t\t\tthis._viewMatDirty = true;\n\t\t\t\tthis._viewProjMat = new Mat4();\n\t\t\t\tthis._viewProjMatDirty = true;\n\t\t\t\tthis._shaderMatricesVersion = 0;\n\t\t\t\tthis._viewProjInverse = new Mat4();\n\t\t\t\tthis._viewProjCurrent = null;\n\t\t\t\tthis._viewProjPrevious = new Mat4();\n\t\t\t\tthis._jitters = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.frustum = new Frustum();\n\t\t\t\tthis._xr = null;\n\t\t\t\tthis._xrProperties = {\n\t\t\t\t\t\thorizontalFov: this._horizontalFov,\n\t\t\t\t\t\tfov: this._fov,\n\t\t\t\t\t\taspectRatio: this._aspectRatio,\n\t\t\t\t\t\tfarClip: this._farClip,\n\t\t\t\t\t\tnearClip: this._nearClip\n\t\t\t\t};\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.renderPassColorGrab?.destroy();\n\t\t\t\tthis.renderPassColorGrab = null;\n\t\t\t\tthis.renderPassDepthGrab?.destroy();\n\t\t\t\tthis.renderPassDepthGrab = null;\n\t\t\t\tthis.renderPasses.length = 0;\n\t\t}\n\t\t_storeShaderMatrices(viewProjMat, jitterX, jitterY, renderVersion) {\n\t\t\t\tif (this._shaderMatricesVersion !== renderVersion) {\n\t\t\t\t\t\tthis._shaderMatricesVersion = renderVersion;\n\t\t\t\t\t\tthis._viewProjPrevious.copy(this._viewProjCurrent ?? viewProjMat);\n\t\t\t\t\t\tthis._viewProjCurrent ??= new Mat4();\n\t\t\t\t\t\tthis._viewProjCurrent.copy(viewProjMat);\n\t\t\t\t\t\tthis._viewProjInverse.invert(viewProjMat);\n\t\t\t\t\t\tthis._jitters[2] = this._jitters[0];\n\t\t\t\t\t\tthis._jitters[3] = this._jitters[1];\n\t\t\t\t\t\tthis._jitters[0] = jitterX;\n\t\t\t\t\t\tthis._jitters[1] = jitterY;\n\t\t\t\t}\n\t\t}\n\t\tget fullSizeClearRect() {\n\t\t\t\tconst rect = this._scissorRectClear ? this.scissorRect : this._rect;\n\t\t\t\treturn rect.x === 0 && rect.y === 0 && rect.z === 1 && rect.w === 1;\n\t\t}\n\t\tset aspectRatio(newValue) {\n\t\t\t\tif (this._aspectRatio !== newValue) {\n\t\t\t\t\t\tthis._aspectRatio = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget aspectRatio() {\n\t\t\t\treturn this.xr?.active ? this._xrProperties.aspectRatio : this._aspectRatio;\n\t\t}\n\t\tset aspectRatioMode(newValue) {\n\t\t\t\tif (this._aspectRatioMode !== newValue) {\n\t\t\t\t\t\tthis._aspectRatioMode = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget aspectRatioMode() {\n\t\t\t\treturn this._aspectRatioMode;\n\t\t}\n\t\tset calculateProjection(newValue) {\n\t\t\t\tthis._calculateProjection = newValue;\n\t\t\t\tthis._projMatDirty = true;\n\t\t}\n\t\tget calculateProjection() {\n\t\t\t\treturn this._calculateProjection;\n\t\t}\n\t\tset calculateTransform(newValue) {\n\t\t\t\tthis._calculateTransform = newValue;\n\t\t}\n\t\tget calculateTransform() {\n\t\t\t\treturn this._calculateTransform;\n\t\t}\n\t\tset clearColor(newValue) {\n\t\t\t\tthis._clearColor.copy(newValue);\n\t\t}\n\t\tget clearColor() {\n\t\t\t\treturn this._clearColor;\n\t\t}\n\t\tset clearColorBuffer(newValue) {\n\t\t\t\tthis._clearColorBuffer = newValue;\n\t\t}\n\t\tget clearColorBuffer() {\n\t\t\t\treturn this._clearColorBuffer;\n\t\t}\n\t\tset clearDepth(newValue) {\n\t\t\t\tthis._clearDepth = newValue;\n\t\t}\n\t\tget clearDepth() {\n\t\t\t\treturn this._clearDepth;\n\t\t}\n\t\tset clearDepthBuffer(newValue) {\n\t\t\t\tthis._clearDepthBuffer = newValue;\n\t\t}\n\t\tget clearDepthBuffer() {\n\t\t\t\treturn this._clearDepthBuffer;\n\t\t}\n\t\tset clearStencil(newValue) {\n\t\t\t\tthis._clearStencil = newValue;\n\t\t}\n\t\tget clearStencil() {\n\t\t\t\treturn this._clearStencil;\n\t\t}\n\t\tset clearStencilBuffer(newValue) {\n\t\t\t\tthis._clearStencilBuffer = newValue;\n\t\t}\n\t\tget clearStencilBuffer() {\n\t\t\t\treturn this._clearStencilBuffer;\n\t\t}\n\t\tset cullFaces(newValue) {\n\t\t\t\tthis._cullFaces = newValue;\n\t\t}\n\t\tget cullFaces() {\n\t\t\t\treturn this._cullFaces;\n\t\t}\n\t\tset farClip(newValue) {\n\t\t\t\tif (this._farClip !== newValue) {\n\t\t\t\t\t\tthis._farClip = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget farClip() {\n\t\t\t\treturn this.xr?.active ? this._xrProperties.farClip : this._farClip;\n\t\t}\n\t\tset flipFaces(newValue) {\n\t\t\t\tthis._flipFaces = newValue;\n\t\t}\n\t\tget flipFaces() {\n\t\t\t\treturn this._flipFaces;\n\t\t}\n\t\tset fov(newValue) {\n\t\t\t\tif (this._fov !== newValue) {\n\t\t\t\t\t\tthis._fov = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget fov() {\n\t\t\t\treturn this.xr?.active ? this._xrProperties.fov : this._fov;\n\t\t}\n\t\tset frustumCulling(newValue) {\n\t\t\t\tthis._frustumCulling = newValue;\n\t\t}\n\t\tget frustumCulling() {\n\t\t\t\treturn this._frustumCulling;\n\t\t}\n\t\tset horizontalFov(newValue) {\n\t\t\t\tif (this._horizontalFov !== newValue) {\n\t\t\t\t\t\tthis._horizontalFov = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget horizontalFov() {\n\t\t\t\treturn this.xr?.active ? this._xrProperties.horizontalFov : this._horizontalFov;\n\t\t}\n\t\tset layers(newValue) {\n\t\t\t\tthis._layers = newValue.slice(0);\n\t\t\t\tthis._layersSet = new Set(this._layers);\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tget layersSet() {\n\t\t\t\treturn this._layersSet;\n\t\t}\n\t\tset nearClip(newValue) {\n\t\t\t\tif (this._nearClip !== newValue) {\n\t\t\t\t\t\tthis._nearClip = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget nearClip() {\n\t\t\t\treturn this.xr?.active ? this._xrProperties.nearClip : this._nearClip;\n\t\t}\n\t\tset node(newValue) {\n\t\t\t\tthis._node = newValue;\n\t\t}\n\t\tget node() {\n\t\t\t\treturn this._node;\n\t\t}\n\t\tset orthoHeight(newValue) {\n\t\t\t\tif (this._orthoHeight !== newValue) {\n\t\t\t\t\t\tthis._orthoHeight = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget orthoHeight() {\n\t\t\t\treturn this._orthoHeight;\n\t\t}\n\t\tset projection(newValue) {\n\t\t\t\tif (this._projection !== newValue) {\n\t\t\t\t\t\tthis._projection = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget projection() {\n\t\t\t\treturn this._projection;\n\t\t}\n\t\tget projectionMatrix() {\n\t\t\t\tthis._evaluateProjectionMatrix();\n\t\t\t\treturn this._projMat;\n\t\t}\n\t\tset rect(newValue) {\n\t\t\t\tthis._rect.copy(newValue);\n\t\t}\n\t\tget rect() {\n\t\t\t\treturn this._rect;\n\t\t}\n\t\tset renderTarget(newValue) {\n\t\t\t\tthis._renderTarget = newValue;\n\t\t}\n\t\tget renderTarget() {\n\t\t\t\treturn this._renderTarget;\n\t\t}\n\t\tset scissorRect(newValue) {\n\t\t\t\tthis._scissorRect.copy(newValue);\n\t\t}\n\t\tget scissorRect() {\n\t\t\t\treturn this._scissorRect;\n\t\t}\n\t\tget viewMatrix() {\n\t\t\t\tif (this._viewMatDirty) {\n\t\t\t\t\t\tconst wtm = this._node.getWorldTransform();\n\t\t\t\t\t\tthis._viewMat.copy(wtm).invert();\n\t\t\t\t\t\tthis._viewMatDirty = false;\n\t\t\t\t}\n\t\t\t\treturn this._viewMat;\n\t\t}\n\t\tset aperture(newValue) {\n\t\t\t\tthis._aperture = newValue;\n\t\t}\n\t\tget aperture() {\n\t\t\t\treturn this._aperture;\n\t\t}\n\t\tset sensitivity(newValue) {\n\t\t\t\tthis._sensitivity = newValue;\n\t\t}\n\t\tget sensitivity() {\n\t\t\t\treturn this._sensitivity;\n\t\t}\n\t\tset shutter(newValue) {\n\t\t\t\tthis._shutter = newValue;\n\t\t}\n\t\tget shutter() {\n\t\t\t\treturn this._shutter;\n\t\t}\n\t\tset xr(newValue) {\n\t\t\t\tif (this._xr !== newValue) {\n\t\t\t\t\t\tthis._xr = newValue;\n\t\t\t\t\t\tthis._projMatDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget xr() {\n\t\t\t\treturn this._xr;\n\t\t}\n\t\tclone() {\n\t\t\t\treturn new Camera().copy(this);\n\t\t}\n\t\tcopy(other) {\n\t\t\t\tthis._aspectRatio = other._aspectRatio;\n\t\t\t\tthis._farClip = other._farClip;\n\t\t\t\tthis._fov = other._fov;\n\t\t\t\tthis._horizontalFov = other._horizontalFov;\n\t\t\t\tthis._nearClip = other._nearClip;\n\t\t\t\tthis._xrProperties.aspectRatio = other._xrProperties.aspectRatio;\n\t\t\t\tthis._xrProperties.farClip = other._xrProperties.farClip;\n\t\t\t\tthis._xrProperties.fov = other._xrProperties.fov;\n\t\t\t\tthis._xrProperties.horizontalFov = other._xrProperties.horizontalFov;\n\t\t\t\tthis._xrProperties.nearClip = other._xrProperties.nearClip;\n\t\t\t\tthis.aspectRatioMode = other.aspectRatioMode;\n\t\t\t\tthis.calculateProjection = other.calculateProjection;\n\t\t\t\tthis.calculateTransform = other.calculateTransform;\n\t\t\t\tthis.clearColor = other.clearColor;\n\t\t\t\tthis.clearColorBuffer = other.clearColorBuffer;\n\t\t\t\tthis.clearDepth = other.clearDepth;\n\t\t\t\tthis.clearDepthBuffer = other.clearDepthBuffer;\n\t\t\t\tthis.clearStencil = other.clearStencil;\n\t\t\t\tthis.clearStencilBuffer = other.clearStencilBuffer;\n\t\t\t\tthis.cullFaces = other.cullFaces;\n\t\t\t\tthis.flipFaces = other.flipFaces;\n\t\t\t\tthis.frustumCulling = other.frustumCulling;\n\t\t\t\tthis.layers = other.layers;\n\t\t\t\tthis.orthoHeight = other.orthoHeight;\n\t\t\t\tthis.projection = other.projection;\n\t\t\t\tthis.rect = other.rect;\n\t\t\t\tthis.renderTarget = other.renderTarget;\n\t\t\t\tthis.scissorRect = other.scissorRect;\n\t\t\t\tthis.aperture = other.aperture;\n\t\t\t\tthis.shutter = other.shutter;\n\t\t\t\tthis.sensitivity = other.sensitivity;\n\t\t\t\tthis.shaderPassInfo = other.shaderPassInfo;\n\t\t\t\tthis.jitter = other.jitter;\n\t\t\t\tthis._projMatDirty = true;\n\t\t\t\treturn this;\n\t\t}\n\t\t_enableRenderPassColorGrab(device, enable) {\n\t\t\t\tif (enable) {\n\t\t\t\t\t\tif (!this.renderPassColorGrab) {\n\t\t\t\t\t\t\t\tthis.renderPassColorGrab = new RenderPassColorGrab(device);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.renderPassColorGrab?.destroy();\n\t\t\t\t\t\tthis.renderPassColorGrab = null;\n\t\t\t\t}\n\t\t}\n\t\t_enableRenderPassDepthGrab(device, renderer, enable) {\n\t\t\t\tif (enable) {\n\t\t\t\t\t\tif (!this.renderPassDepthGrab) {\n\t\t\t\t\t\t\t\tthis.renderPassDepthGrab = new RenderPassDepthGrab(device, this);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.renderPassDepthGrab?.destroy();\n\t\t\t\t\t\tthis.renderPassDepthGrab = null;\n\t\t\t\t}\n\t\t}\n\t\t_updateViewProjMat() {\n\t\t\t\tif (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) {\n\t\t\t\t\t\tthis._viewProjMat.mul2(this.projectionMatrix, this.viewMatrix);\n\t\t\t\t\t\tthis._viewProjMatDirty = false;\n\t\t\t\t}\n\t\t}\n\t\tworldToScreen(worldCoord, cw, ch, screenCoord = new Vec3()) {\n\t\t\t\tthis._updateViewProjMat();\n\t\t\t\tthis._viewProjMat.transformPoint(worldCoord, screenCoord);\n\t\t\t\tconst vpm = this._viewProjMat.data;\n\t\t\t\tconst w = worldCoord.x * vpm[3] + worldCoord.y * vpm[7] + worldCoord.z * vpm[11] + 1 * vpm[15];\n\t\t\t\tscreenCoord.x = (screenCoord.x / w + 1) * 0.5;\n\t\t\t\tscreenCoord.y = (1 - screenCoord.y / w) * 0.5;\n\t\t\t\tconst { x: rx, y: ry, z: rw, w: rh } = this._rect;\n\t\t\t\tscreenCoord.x = screenCoord.x * rw * cw + rx * cw;\n\t\t\t\tscreenCoord.y = screenCoord.y * rh * ch + (1 - ry - rh) * ch;\n\t\t\t\treturn screenCoord;\n\t\t}\n\t\tscreenToWorld(x, y, z, cw, ch, worldCoord = new Vec3()) {\n\t\t\t\tconst { x: rx, y: ry, z: rw, w: rh } = this._rect;\n\t\t\t\tconst range = this.farClip - this.nearClip;\n\t\t\t\t_deviceCoord.set((x - rx * cw) / (rw * cw), 1 - (y - (1 - ry - rh) * ch) / (rh * ch), z / range);\n\t\t\t\t_deviceCoord.mulScalar(2);\n\t\t\t\t_deviceCoord.sub(Vec3.ONE);\n\t\t\t\tif (this._projection === PROJECTION_PERSPECTIVE) {\n\t\t\t\t\t\tMat4._getPerspectiveHalfSize(_halfSize, this.fov, this.aspectRatio, this.nearClip, this.horizontalFov);\n\t\t\t\t\t\t_halfSize.x *= _deviceCoord.x;\n\t\t\t\t\t\t_halfSize.y *= _deviceCoord.y;\n\t\t\t\t\t\tconst invView = this._node.getWorldTransform();\n\t\t\t\t\t\t_halfSize.z = -this.nearClip;\n\t\t\t\t\t\tinvView.transformPoint(_halfSize, _point$1);\n\t\t\t\t\t\tconst cameraPos = this._node.getPosition();\n\t\t\t\t\t\tworldCoord.sub2(_point$1, cameraPos);\n\t\t\t\t\t\tworldCoord.normalize();\n\t\t\t\t\t\tworldCoord.mulScalar(z);\n\t\t\t\t\t\tworldCoord.add(cameraPos);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._updateViewProjMat();\n\t\t\t\t\t\t_invViewProjMat.copy(this._viewProjMat).invert();\n\t\t\t\t\t\t_invViewProjMat.transformPoint(_deviceCoord, worldCoord);\n\t\t\t\t}\n\t\t\t\treturn worldCoord;\n\t\t}\n\t\t_evaluateProjectionMatrix() {\n\t\t\t\tif (this._projMatDirty) {\n\t\t\t\t\t\tif (this._projection === PROJECTION_PERSPECTIVE) {\n\t\t\t\t\t\t\t\tthis._projMat.setPerspective(this.fov, this.aspectRatio, this.nearClip, this.farClip, this.horizontalFov);\n\t\t\t\t\t\t\t\tthis._projMatSkybox.copy(this._projMat);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst y = this._orthoHeight;\n\t\t\t\t\t\t\t\tconst x = y * this.aspectRatio;\n\t\t\t\t\t\t\t\tthis._projMat.setOrtho(-x, x, -y, y, this.nearClip, this.farClip);\n\t\t\t\t\t\t\t\tthis._projMatSkybox.setPerspective(this.fov, this.aspectRatio, this.nearClip, this.farClip);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._projMatDirty = false;\n\t\t\t\t}\n\t\t}\n\t\tgetProjectionMatrixSkybox() {\n\t\t\t\tthis._evaluateProjectionMatrix();\n\t\t\t\treturn this._projMatSkybox;\n\t\t}\n\t\tgetExposure() {\n\t\t\t\tconst ev100 = Math.log2(this._aperture * this._aperture / this._shutter * 100.0 / this._sensitivity);\n\t\t\t\treturn 1.0 / (Math.pow(2.0, ev100) * 1.2);\n\t\t}\n\t\tgetScreenSize(sphere) {\n\t\t\t\tif (this._projection === PROJECTION_PERSPECTIVE) {\n\t\t\t\t\t\tconst distance = this._node.getPosition().distance(sphere.center);\n\t\t\t\t\t\tif (distance < sphere.radius) {\n\t\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst viewAngle = Math.asin(sphere.radius / distance);\n\t\t\t\t\t\tconst sphereViewHeight = Math.tan(viewAngle);\n\t\t\t\t\t\tconst screenViewHeight = Math.tan(this.fov / 2 * math.DEG_TO_RAD);\n\t\t\t\t\t\treturn Math.min(sphereViewHeight / screenViewHeight, 1);\n\t\t\t\t}\n\t\t\t\treturn math.clamp(sphere.radius / this._orthoHeight, 0, 1);\n\t\t}\n\t\tgetFrustumCorners(near = this.nearClip, far = this.farClip) {\n\t\t\t\tconst fov = this.fov * Math.PI / 180.0;\n\t\t\t\tlet x, y;\n\t\t\t\tif (this.projection === PROJECTION_PERSPECTIVE) {\n\t\t\t\t\t\tif (this.horizontalFov) {\n\t\t\t\t\t\t\t\tx = near * Math.tan(fov / 2.0);\n\t\t\t\t\t\t\t\ty = x / this.aspectRatio;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ty = near * Math.tan(fov / 2.0);\n\t\t\t\t\t\t\t\tx = y * this.aspectRatio;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\ty = this._orthoHeight;\n\t\t\t\t\t\tx = y * this.aspectRatio;\n\t\t\t\t}\n\t\t\t\tconst points = _frustumPoints;\n\t\t\t\tpoints[0].x = x;\n\t\t\t\tpoints[0].y = -y;\n\t\t\t\tpoints[0].z = -near;\n\t\t\t\tpoints[1].x = x;\n\t\t\t\tpoints[1].y = y;\n\t\t\t\tpoints[1].z = -near;\n\t\t\t\tpoints[2].x = -x;\n\t\t\t\tpoints[2].y = y;\n\t\t\t\tpoints[2].z = -near;\n\t\t\t\tpoints[3].x = -x;\n\t\t\t\tpoints[3].y = -y;\n\t\t\t\tpoints[3].z = -near;\n\t\t\t\tif (this._projection === PROJECTION_PERSPECTIVE) {\n\t\t\t\t\t\tif (this.horizontalFov) {\n\t\t\t\t\t\t\t\tx = far * Math.tan(fov / 2.0);\n\t\t\t\t\t\t\t\ty = x / this.aspectRatio;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ty = far * Math.tan(fov / 2.0);\n\t\t\t\t\t\t\t\tx = y * this.aspectRatio;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpoints[4].x = x;\n\t\t\t\tpoints[4].y = -y;\n\t\t\t\tpoints[4].z = -far;\n\t\t\t\tpoints[5].x = x;\n\t\t\t\tpoints[5].y = y;\n\t\t\t\tpoints[5].z = -far;\n\t\t\t\tpoints[6].x = -x;\n\t\t\t\tpoints[6].y = y;\n\t\t\t\tpoints[6].z = -far;\n\t\t\t\tpoints[7].x = -x;\n\t\t\t\tpoints[7].y = -y;\n\t\t\t\tpoints[7].z = -far;\n\t\t\t\treturn points;\n\t\t}\n\t\tsetXrProperties(properties) {\n\t\t\t\tObject.assign(this._xrProperties, properties);\n\t\t\t\tthis._projMatDirty = true;\n\t\t}\n}\n\nconst scaleCompensatePosTransform = new Mat4();\nconst scaleCompensatePos = new Vec3();\nconst scaleCompensateRot = new Quat();\nconst scaleCompensateRot2 = new Quat();\nconst scaleCompensateScale = new Vec3();\nconst scaleCompensateScaleForParent = new Vec3();\nconst tmpMat4 = new Mat4();\nconst tmpQuat = new Quat();\nconst position$1 = new Vec3();\nconst invParentWtm$1 = new Mat4();\nconst rotation$3 = new Quat();\nconst invParentRot = new Quat();\nconst matrix = new Mat4();\nconst target = new Vec3();\nconst up$1 = new Vec3();\nfunction createTest(attr, value) {\n\t\tif (attr instanceof Function) {\n\t\t\t\treturn attr;\n\t\t}\n\t\treturn (node)=>{\n\t\t\t\tlet x = node[attr];\n\t\t\t\tif (x instanceof Function) {\n\t\t\t\t\t\tx = x();\n\t\t\t\t}\n\t\t\t\treturn x === value;\n\t\t};\n}\nfunction findNode(node, test) {\n\t\tif (test(node)) {\n\t\t\t\treturn node;\n\t\t}\n\t\tconst children = node._children;\n\t\tconst len = children.length;\n\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\tconst result = findNode(children[i], test);\n\t\t\t\tif (result) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t}\n\t\treturn null;\n}\nclass GraphNode extends EventHandler {\n\t\tconstructor(name = 'Untitled'){\n\t\t\t\tsuper(), this.tags = new Tags(this), this.localPosition = new Vec3(), this.localRotation = new Quat(), this.localScale = new Vec3(1, 1, 1), this.localEulerAngles = new Vec3(), this.position = new Vec3(), this.rotation = new Quat(), this.eulerAngles = new Vec3(), this._scale = null, this.localTransform = new Mat4(), this._dirtyLocal = false, this._aabbVer = 0, this._frozen = false, this.worldTransform = new Mat4(), this._dirtyWorld = false, this._worldScaleSign = 0, this._normalMatrix = new Mat3(), this._dirtyNormal = true, this._right = null, this._up = null, this._forward = null, this._parent = null, this._children = [], this._graphDepth = 0, this._enabled = true, this._enabledInHierarchy = false, this.scaleCompensation = false;\n\t\t\t\tthis.name = name;\n\t\t}\n\t\tget right() {\n\t\t\t\tif (!this._right) {\n\t\t\t\t\t\tthis._right = new Vec3();\n\t\t\t\t}\n\t\t\t\treturn this.getWorldTransform().getX(this._right).normalize();\n\t\t}\n\t\tget up() {\n\t\t\t\tif (!this._up) {\n\t\t\t\t\t\tthis._up = new Vec3();\n\t\t\t\t}\n\t\t\t\treturn this.getWorldTransform().getY(this._up).normalize();\n\t\t}\n\t\tget forward() {\n\t\t\t\tif (!this._forward) {\n\t\t\t\t\t\tthis._forward = new Vec3();\n\t\t\t\t}\n\t\t\t\treturn this.getWorldTransform().getZ(this._forward).normalize().mulScalar(-1);\n\t\t}\n\t\tget normalMatrix() {\n\t\t\t\tconst normalMat = this._normalMatrix;\n\t\t\t\tif (this._dirtyNormal) {\n\t\t\t\t\t\tnormalMat.invertMat4(this.getWorldTransform()).transpose();\n\t\t\t\t\t\tthis._dirtyNormal = false;\n\t\t\t\t}\n\t\t\t\treturn normalMat;\n\t\t}\n\t\tset enabled(enabled) {\n\t\t\t\tif (this._enabled !== enabled) {\n\t\t\t\t\t\tthis._enabled = enabled;\n\t\t\t\t\t\tif (enabled && this._parent?.enabled || !enabled) {\n\t\t\t\t\t\t\t\tthis._notifyHierarchyStateChanged(this, enabled);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled && this._enabledInHierarchy;\n\t\t}\n\t\tget parent() {\n\t\t\t\treturn this._parent;\n\t\t}\n\t\tget path() {\n\t\t\t\tlet node = this._parent;\n\t\t\t\tif (!node) {\n\t\t\t\t\t\treturn '';\n\t\t\t\t}\n\t\t\t\tlet result = this.name;\n\t\t\t\twhile(node && node._parent){\n\t\t\t\t\t\tresult = `${node.name}/${result}`;\n\t\t\t\t\t\tnode = node._parent;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tget root() {\n\t\t\t\tlet result = this;\n\t\t\t\twhile(result._parent){\n\t\t\t\t\t\tresult = result._parent;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tget children() {\n\t\t\t\treturn this._children;\n\t\t}\n\t\tget graphDepth() {\n\t\t\t\treturn this._graphDepth;\n\t\t}\n\t\t_notifyHierarchyStateChanged(node, enabled) {\n\t\t\t\tnode._onHierarchyStateChanged(enabled);\n\t\t\t\tconst c = node._children;\n\t\t\t\tfor(let i = 0, len = c.length; i < len; i++){\n\t\t\t\t\t\tif (c[i]._enabled) {\n\t\t\t\t\t\t\t\tthis._notifyHierarchyStateChanged(c[i], enabled);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onHierarchyStateChanged(enabled) {\n\t\t\t\tthis._enabledInHierarchy = enabled;\n\t\t\t\tif (enabled && !this._frozen) {\n\t\t\t\t\t\tthis._unfreezeParentToRoot();\n\t\t\t\t}\n\t\t}\n\t\t_cloneInternal(clone) {\n\t\t\t\tclone.name = this.name;\n\t\t\t\tconst tags = this.tags._list;\n\t\t\t\tclone.tags.clear();\n\t\t\t\tfor(let i = 0; i < tags.length; i++){\n\t\t\t\t\t\tclone.tags.add(tags[i]);\n\t\t\t\t}\n\t\t\t\tclone.localPosition.copy(this.localPosition);\n\t\t\t\tclone.localRotation.copy(this.localRotation);\n\t\t\t\tclone.localScale.copy(this.localScale);\n\t\t\t\tclone.localEulerAngles.copy(this.localEulerAngles);\n\t\t\t\tclone.position.copy(this.position);\n\t\t\t\tclone.rotation.copy(this.rotation);\n\t\t\t\tclone.eulerAngles.copy(this.eulerAngles);\n\t\t\t\tclone.localTransform.copy(this.localTransform);\n\t\t\t\tclone._dirtyLocal = this._dirtyLocal;\n\t\t\t\tclone.worldTransform.copy(this.worldTransform);\n\t\t\t\tclone._dirtyWorld = this._dirtyWorld;\n\t\t\t\tclone._dirtyNormal = this._dirtyNormal;\n\t\t\t\tclone._aabbVer = this._aabbVer + 1;\n\t\t\t\tclone._enabled = this._enabled;\n\t\t\t\tclone.scaleCompensation = this.scaleCompensation;\n\t\t\t\tclone._enabledInHierarchy = false;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst clone = new this.constructor();\n\t\t\t\tthis._cloneInternal(clone);\n\t\t\t\treturn clone;\n\t\t}\n\t\tcopy(source) {\n\t\t\t\tsource._cloneInternal(this);\n\t\t\t\treturn this;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.remove();\n\t\t\t\tconst children = this._children;\n\t\t\t\twhile(children.length){\n\t\t\t\t\t\tconst child = children.pop();\n\t\t\t\t\t\tchild._parent = null;\n\t\t\t\t\t\tchild.destroy();\n\t\t\t\t}\n\t\t\t\tthis.fire('destroy', this);\n\t\t\t\tthis.off();\n\t\t}\n\t\tfind(attr, value) {\n\t\t\t\tconst results = [];\n\t\t\t\tconst test = createTest(attr, value);\n\t\t\t\tthis.forEach((node)=>{\n\t\t\t\t\t\tif (test(node)) {\n\t\t\t\t\t\t\t\tresults.push(node);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn results;\n\t\t}\n\t\tfindOne(attr, value) {\n\t\t\t\tconst test = createTest(attr, value);\n\t\t\t\treturn findNode(this, test);\n\t\t}\n\t\tfindByTag(...query) {\n\t\t\t\tconst results = [];\n\t\t\t\tconst queryNode = (node, checkNode)=>{\n\t\t\t\t\t\tif (checkNode && node.tags.has(...query)) {\n\t\t\t\t\t\t\t\tresults.push(node);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < node._children.length; i++){\n\t\t\t\t\t\t\t\tqueryNode(node._children[i], true);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tqueryNode(this, false);\n\t\t\t\treturn results;\n\t\t}\n\t\tfindByName(name) {\n\t\t\t\treturn this.findOne('name', name);\n\t\t}\n\t\tfindByPath(path) {\n\t\t\t\tconst parts = Array.isArray(path) ? path : path.split('/');\n\t\t\t\tlet result = this;\n\t\t\t\tfor(let i = 0, imax = parts.length; i < imax; ++i){\n\t\t\t\t\t\tresult = result.children.find((c)=>c.name === parts[i]);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tforEach(callback, thisArg) {\n\t\t\t\tcallback.call(thisArg, this);\n\t\t\t\tconst children = this._children;\n\t\t\t\tconst len = children.length;\n\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\tchildren[i].forEach(callback, thisArg);\n\t\t\t\t}\n\t\t}\n\t\tisDescendantOf(node) {\n\t\t\t\tlet parent = this._parent;\n\t\t\t\twhile(parent){\n\t\t\t\t\t\tif (parent === node) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparent = parent._parent;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tisAncestorOf(node) {\n\t\t\t\treturn node.isDescendantOf(this);\n\t\t}\n\t\tgetEulerAngles() {\n\t\t\t\tthis.getWorldTransform().getEulerAngles(this.eulerAngles);\n\t\t\t\treturn this.eulerAngles;\n\t\t}\n\t\tgetLocalEulerAngles() {\n\t\t\t\tthis.localRotation.getEulerAngles(this.localEulerAngles);\n\t\t\t\treturn this.localEulerAngles;\n\t\t}\n\t\tgetLocalPosition() {\n\t\t\t\treturn this.localPosition;\n\t\t}\n\t\tgetLocalRotation() {\n\t\t\t\treturn this.localRotation;\n\t\t}\n\t\tgetLocalScale() {\n\t\t\t\treturn this.localScale;\n\t\t}\n\t\tgetLocalTransform() {\n\t\t\t\tif (this._dirtyLocal) {\n\t\t\t\t\t\tthis.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);\n\t\t\t\t\t\tthis._dirtyLocal = false;\n\t\t\t\t}\n\t\t\t\treturn this.localTransform;\n\t\t}\n\t\tgetPosition() {\n\t\t\t\tthis.getWorldTransform().getTranslation(this.position);\n\t\t\t\treturn this.position;\n\t\t}\n\t\tgetRotation() {\n\t\t\t\tthis.rotation.setFromMat4(this.getWorldTransform());\n\t\t\t\treturn this.rotation;\n\t\t}\n\t\tgetScale() {\n\t\t\t\tif (!this._scale) {\n\t\t\t\t\t\tthis._scale = new Vec3();\n\t\t\t\t}\n\t\t\t\treturn this.getWorldTransform().getScale(this._scale);\n\t\t}\n\t\tgetWorldTransform() {\n\t\t\t\tif (!this._dirtyLocal && !this._dirtyWorld) {\n\t\t\t\t\t\treturn this.worldTransform;\n\t\t\t\t}\n\t\t\t\tif (this._parent) {\n\t\t\t\t\t\tthis._parent.getWorldTransform();\n\t\t\t\t}\n\t\t\t\tthis._sync();\n\t\t\t\treturn this.worldTransform;\n\t\t}\n\t\tget worldScaleSign() {\n\t\t\t\tif (this._worldScaleSign === 0) {\n\t\t\t\t\t\tthis._worldScaleSign = this.getWorldTransform().scaleSign;\n\t\t\t\t}\n\t\t\t\treturn this._worldScaleSign;\n\t\t}\n\t\tremove() {\n\t\t\t\tthis._parent?.removeChild(this);\n\t\t}\n\t\treparent(parent, index) {\n\t\t\t\tthis.remove();\n\t\t\t\tif (parent) {\n\t\t\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\t\t\tparent.insertChild(this, index);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tparent.addChild(this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetLocalEulerAngles(x, y, z) {\n\t\t\t\tthis.localRotation.setFromEulerAngles(x, y, z);\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\tsetLocalPosition(x, y, z) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tthis.localPosition.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.localPosition.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\tsetLocalRotation(x, y, z, w) {\n\t\t\t\tif (x instanceof Quat) {\n\t\t\t\t\t\tthis.localRotation.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.localRotation.set(x, y, z, w);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\tsetLocalScale(x, y, z) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tthis.localScale.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.localScale.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\t_dirtifyLocal() {\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtyLocal = true;\n\t\t\t\t\t\tif (!this._dirtyWorld) {\n\t\t\t\t\t\t\t\tthis._dirtifyWorld();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_unfreezeParentToRoot() {\n\t\t\t\tlet p = this._parent;\n\t\t\t\twhile(p){\n\t\t\t\t\t\tp._frozen = false;\n\t\t\t\t\t\tp = p._parent;\n\t\t\t\t}\n\t\t}\n\t\t_dirtifyWorld() {\n\t\t\t\tif (!this._dirtyWorld) {\n\t\t\t\t\t\tthis._unfreezeParentToRoot();\n\t\t\t\t}\n\t\t\t\tthis._dirtifyWorldInternal();\n\t\t}\n\t\t_dirtifyWorldInternal() {\n\t\t\t\tif (!this._dirtyWorld) {\n\t\t\t\t\t\tthis._frozen = false;\n\t\t\t\t\t\tthis._dirtyWorld = true;\n\t\t\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\t\t\tif (!this._children[i]._dirtyWorld) {\n\t\t\t\t\t\t\t\t\t\tthis._children[i]._dirtifyWorldInternal();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._dirtyNormal = true;\n\t\t\t\tthis._worldScaleSign = 0;\n\t\t\t\tthis._aabbVer++;\n\t\t}\n\t\tsetPosition(x, y, z) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tposition$1.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tposition$1.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tif (this._parent === null) {\n\t\t\t\t\t\tthis.localPosition.copy(position$1);\n\t\t\t\t} else {\n\t\t\t\t\t\tinvParentWtm$1.copy(this._parent.getWorldTransform()).invert();\n\t\t\t\t\t\tinvParentWtm$1.transformPoint(position$1, this.localPosition);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\tsetRotation(x, y, z, w) {\n\t\t\t\tif (x instanceof Quat) {\n\t\t\t\t\t\trotation$3.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\trotation$3.set(x, y, z, w);\n\t\t\t\t}\n\t\t\t\tif (this._parent === null) {\n\t\t\t\t\t\tthis.localRotation.copy(rotation$3);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst parentRot = this._parent.getRotation();\n\t\t\t\t\t\tinvParentRot.copy(parentRot).invert();\n\t\t\t\t\t\tthis.localRotation.copy(invParentRot).mul(rotation$3);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\tsetPositionAndRotation(position, rotation) {\n\t\t\t\tif (this._parent === null) {\n\t\t\t\t\t\tthis.localPosition.copy(position);\n\t\t\t\t\t\tthis.localRotation.copy(rotation);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst parentWtm = this._parent.getWorldTransform();\n\t\t\t\t\t\tinvParentWtm$1.copy(parentWtm).invert();\n\t\t\t\t\t\tinvParentWtm$1.transformPoint(position, this.localPosition);\n\t\t\t\t\t\tthis.localRotation.setFromMat4(invParentWtm$1).mul(rotation);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\tsetEulerAngles(x, y, z) {\n\t\t\t\tthis.localRotation.setFromEulerAngles(x, y, z);\n\t\t\t\tif (this._parent !== null) {\n\t\t\t\t\t\tconst parentRot = this._parent.getRotation();\n\t\t\t\t\t\tinvParentRot.copy(parentRot).invert();\n\t\t\t\t\t\tthis.localRotation.mul2(invParentRot, this.localRotation);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\taddChild(node) {\n\t\t\t\tthis._prepareInsertChild(node);\n\t\t\t\tthis._children.push(node);\n\t\t\t\tthis._onInsertChild(node);\n\t\t}\n\t\taddChildAndSaveTransform(node) {\n\t\t\t\tconst wPos = node.getPosition();\n\t\t\t\tconst wRot = node.getRotation();\n\t\t\t\tthis._prepareInsertChild(node);\n\t\t\t\tnode.setPosition(tmpMat4.copy(this.worldTransform).invert().transformPoint(wPos));\n\t\t\t\tnode.setRotation(tmpQuat.copy(this.getRotation()).invert().mul(wRot));\n\t\t\t\tthis._children.push(node);\n\t\t\t\tthis._onInsertChild(node);\n\t\t}\n\t\tinsertChild(node, index) {\n\t\t\t\tthis._prepareInsertChild(node);\n\t\t\t\tthis._children.splice(index, 0, node);\n\t\t\t\tthis._onInsertChild(node);\n\t\t}\n\t\t_prepareInsertChild(node) {\n\t\t\t\tnode.remove();\n\t\t}\n\t\t_fireOnHierarchy(name, nameHierarchy, parent) {\n\t\t\t\tthis.fire(name, parent);\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tthis._children[i]._fireOnHierarchy(nameHierarchy, nameHierarchy, parent);\n\t\t\t\t}\n\t\t}\n\t\t_onInsertChild(node) {\n\t\t\t\tnode._parent = this;\n\t\t\t\tconst enabledInHierarchy = node._enabled && this.enabled;\n\t\t\t\tif (node._enabledInHierarchy !== enabledInHierarchy) {\n\t\t\t\t\t\tnode._enabledInHierarchy = enabledInHierarchy;\n\t\t\t\t\t\tnode._notifyHierarchyStateChanged(node, enabledInHierarchy);\n\t\t\t\t}\n\t\t\t\tnode._updateGraphDepth();\n\t\t\t\tnode._dirtifyWorld();\n\t\t\t\tif (this._frozen) {\n\t\t\t\t\t\tnode._unfreezeParentToRoot();\n\t\t\t\t}\n\t\t\t\tnode._fireOnHierarchy('insert', 'inserthierarchy', this);\n\t\t\t\tif (this.fire) this.fire('childinsert', node);\n\t\t}\n\t\t_updateGraphDepth() {\n\t\t\t\tthis._graphDepth = this._parent ? this._parent._graphDepth + 1 : 0;\n\t\t\t\tfor(let i = 0, len = this._children.length; i < len; i++){\n\t\t\t\t\t\tthis._children[i]._updateGraphDepth();\n\t\t\t\t}\n\t\t}\n\t\tremoveChild(child) {\n\t\t\t\tconst index = this._children.indexOf(child);\n\t\t\t\tif (index === -1) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._children.splice(index, 1);\n\t\t\t\tchild._parent = null;\n\t\t\t\tchild._fireOnHierarchy('remove', 'removehierarchy', this);\n\t\t\t\tthis.fire('childremove', child);\n\t\t}\n\t\t_sync() {\n\t\t\t\tif (this._dirtyLocal) {\n\t\t\t\t\t\tthis.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);\n\t\t\t\t\t\tthis._dirtyLocal = false;\n\t\t\t\t}\n\t\t\t\tif (this._dirtyWorld) {\n\t\t\t\t\t\tif (this._parent === null) {\n\t\t\t\t\t\t\t\tthis.worldTransform.copy(this.localTransform);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.scaleCompensation) {\n\t\t\t\t\t\t\t\t\t\tlet parentWorldScale;\n\t\t\t\t\t\t\t\t\t\tconst parent = this._parent;\n\t\t\t\t\t\t\t\t\t\tlet scale = this.localScale;\n\t\t\t\t\t\t\t\t\t\tlet parentToUseScaleFrom = parent;\n\t\t\t\t\t\t\t\t\t\tif (parentToUseScaleFrom) {\n\t\t\t\t\t\t\t\t\t\t\t\twhile(parentToUseScaleFrom && parentToUseScaleFrom.scaleCompensation){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tparentToUseScaleFrom = parentToUseScaleFrom._parent;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (parentToUseScaleFrom) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tparentToUseScaleFrom = parentToUseScaleFrom._parent;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (parentToUseScaleFrom) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparentWorldScale = parentToUseScaleFrom.worldTransform.getScale();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tscaleCompensateScale.mul2(parentWorldScale, this.localScale);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tscale = scaleCompensateScale;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tscaleCompensateRot2.setFromMat4(parent.worldTransform);\n\t\t\t\t\t\t\t\t\t\tscaleCompensateRot.mul2(scaleCompensateRot2, this.localRotation);\n\t\t\t\t\t\t\t\t\t\tlet tmatrix = parent.worldTransform;\n\t\t\t\t\t\t\t\t\t\tif (parent.scaleCompensation) {\n\t\t\t\t\t\t\t\t\t\t\t\tscaleCompensateScaleForParent.mul2(parentWorldScale, parent.getLocalScale());\n\t\t\t\t\t\t\t\t\t\t\t\tscaleCompensatePosTransform.setTRS(parent.worldTransform.getTranslation(scaleCompensatePos), scaleCompensateRot2, scaleCompensateScaleForParent);\n\t\t\t\t\t\t\t\t\t\t\t\ttmatrix = scaleCompensatePosTransform;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\ttmatrix.transformPoint(this.localPosition, scaleCompensatePos);\n\t\t\t\t\t\t\t\t\t\tthis.worldTransform.setTRS(scaleCompensatePos, scaleCompensateRot, scale);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.worldTransform.mulAffine2(this._parent.worldTransform, this.localTransform);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._dirtyWorld = false;\n\t\t\t\t}\n\t\t}\n\t\tsyncHierarchy() {\n\t\t\t\tif (!this._enabled) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._frozen) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._frozen = true;\n\t\t\t\tif (this._dirtyLocal || this._dirtyWorld) {\n\t\t\t\t\t\tthis._sync();\n\t\t\t\t}\n\t\t\t\tconst children = this._children;\n\t\t\t\tfor(let i = 0, len = children.length; i < len; i++){\n\t\t\t\t\t\tchildren[i].syncHierarchy();\n\t\t\t\t}\n\t\t}\n\t\tlookAt(x, y, z, ux = 0, uy = 1, uz = 0) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\ttarget.copy(x);\n\t\t\t\t\t\tif (y instanceof Vec3) {\n\t\t\t\t\t\t\t\tup$1.copy(y);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tup$1.copy(Vec3.UP);\n\t\t\t\t\t\t}\n\t\t\t\t} else if (z === undefined) {\n\t\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\t\ttarget.set(x, y, z);\n\t\t\t\t\t\tup$1.set(ux, uy, uz);\n\t\t\t\t}\n\t\t\t\tmatrix.setLookAt(this.getPosition(), target, up$1);\n\t\t\t\trotation$3.setFromMat4(matrix);\n\t\t\t\tthis.setRotation(rotation$3);\n\t\t}\n\t\ttranslate(x, y, z) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tposition$1.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tposition$1.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tposition$1.add(this.getPosition());\n\t\t\t\tthis.setPosition(position$1);\n\t\t}\n\t\ttranslateLocal(x, y, z) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tposition$1.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tposition$1.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tthis.localRotation.transformVector(position$1, position$1);\n\t\t\t\tthis.localPosition.add(position$1);\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\trotate(x, y, z) {\n\t\t\t\trotation$3.setFromEulerAngles(x, y, z);\n\t\t\t\tif (this._parent === null) {\n\t\t\t\t\t\tthis.localRotation.mul2(rotation$3, this.localRotation);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst rot = this.getRotation();\n\t\t\t\t\t\tconst parentRot = this._parent.getRotation();\n\t\t\t\t\t\tinvParentRot.copy(parentRot).invert();\n\t\t\t\t\t\trotation$3.mul2(invParentRot, rotation$3);\n\t\t\t\t\t\tthis.localRotation.mul2(rotation$3, rot);\n\t\t\t\t}\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\trotateLocal(x, y, z) {\n\t\t\t\trotation$3.setFromEulerAngles(x, y, z);\n\t\t\t\tthis.localRotation.mul(rotation$3);\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n}\n\nconst _viewMat$1 = new Mat4();\nconst _viewProjMat = new Mat4();\nconst _viewportMatrix = new Mat4();\nclass LightCamera {\n\t\tstatic{\n\t\t\t\tthis.pointLightRotations = [\n\t\t\t\t\t\tnew Quat().setFromEulerAngles(0, 90, 180),\n\t\t\t\t\t\tnew Quat().setFromEulerAngles(0, -90, 180),\n\t\t\t\t\t\tnew Quat().setFromEulerAngles(90, 0, 0),\n\t\t\t\t\t\tnew Quat().setFromEulerAngles(-90, 0, 0),\n\t\t\t\t\t\tnew Quat().setFromEulerAngles(0, 180, 180),\n\t\t\t\t\t\tnew Quat().setFromEulerAngles(0, 0, 180)\n\t\t\t\t];\n\t\t}\n\t\tstatic create(name, lightType, face) {\n\t\t\t\tconst camera = new Camera();\n\t\t\t\tcamera.node = new GraphNode(name);\n\t\t\t\tcamera.aspectRatio = 1;\n\t\t\t\tcamera.aspectRatioMode = ASPECT_MANUAL;\n\t\t\t\tcamera._scissorRectClear = true;\n\t\t\t\tswitch(lightType){\n\t\t\t\t\t\tcase LIGHTTYPE_OMNI:\n\t\t\t\t\t\t\t\tcamera.node.setRotation(LightCamera.pointLightRotations[face]);\n\t\t\t\t\t\t\t\tcamera.fov = 90;\n\t\t\t\t\t\t\t\tcamera.projection = PROJECTION_PERSPECTIVE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase LIGHTTYPE_SPOT:\n\t\t\t\t\t\t\t\tcamera.projection = PROJECTION_PERSPECTIVE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase LIGHTTYPE_DIRECTIONAL:\n\t\t\t\t\t\t\t\tcamera.projection = PROJECTION_ORTHOGRAPHIC;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\treturn camera;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis._spotCookieCamera = null;\n\t\t}\n\t\tstatic evalSpotCookieMatrix(light) {\n\t\t\t\tlet cookieCamera = LightCamera._spotCookieCamera;\n\t\t\t\tif (!cookieCamera) {\n\t\t\t\t\t\tcookieCamera = LightCamera.create('SpotCookieCamera', LIGHTTYPE_SPOT);\n\t\t\t\t\t\tLightCamera._spotCookieCamera = cookieCamera;\n\t\t\t\t}\n\t\t\t\tcookieCamera.fov = light._outerConeAngle * 2;\n\t\t\t\tconst cookieNode = cookieCamera._node;\n\t\t\t\tcookieNode.setPosition(light._node.getPosition());\n\t\t\t\tcookieNode.setRotation(light._node.getRotation());\n\t\t\t\tcookieNode.rotateLocal(-90, 0, 0);\n\t\t\t\t_viewMat$1.setTRS(cookieNode.getPosition(), cookieNode.getRotation(), Vec3.ONE).invert();\n\t\t\t\t_viewProjMat.mul2(cookieCamera.projectionMatrix, _viewMat$1);\n\t\t\t\tconst cookieMatrix = light.cookieMatrix;\n\t\t\t\tconst rectViewport = light.atlasViewport;\n\t\t\t\t_viewportMatrix.setViewport(rectViewport.x, rectViewport.y, rectViewport.z, rectViewport.w);\n\t\t\t\tcookieMatrix.mul2(_viewportMatrix, _viewProjMat);\n\t\t\t\treturn cookieMatrix;\n\t\t}\n}\n\nconst tempVec3$1 = new Vec3();\nconst tempAreaLightSizes = new Float32Array(6);\nconst areaHalfAxisWidth = new Vec3(-0.5, 0, 0);\nconst areaHalfAxisHeight = new Vec3(0, 0, 0.5);\nconst TextureIndexFloat = {\n\t\tPOSITION_RANGE: 0,\n\t\tDIRECTION_FLAGS: 1,\n\t\tCOLOR_ANGLES_BIAS: 2,\n\t\tPROJ_MAT_0: 3,\n\t\tATLAS_VIEWPORT: 3,\n\t\tPROJ_MAT_1: 4,\n\t\tPROJ_MAT_2: 5,\n\t\tPROJ_MAT_3: 6,\n\t\tAREA_DATA_WIDTH: 7,\n\t\tAREA_DATA_HEIGHT: 8,\n\t\tCOUNT: 9\n};\nconst enums = {\n\t\t'LIGHTSHAPE_PUNCTUAL': `${LIGHTSHAPE_PUNCTUAL}u`,\n\t\t'LIGHTSHAPE_RECT': `${LIGHTSHAPE_RECT}u`,\n\t\t'LIGHTSHAPE_DISK': `${LIGHTSHAPE_DISK}u`,\n\t\t'LIGHTSHAPE_SPHERE': `${LIGHTSHAPE_SPHERE}u`,\n\t\t'LIGHT_COLOR_DIVIDER': `${LIGHT_COLOR_DIVIDER}.0`\n};\nconst buildShaderDefines = (object, prefix)=>{\n\t\treturn Object.keys(object).map((key)=>`#define {${prefix}${key}} ${object[key]}`).join('\\n');\n};\nconst lightBufferDefines = `\\n\n\t\t${buildShaderDefines(TextureIndexFloat, 'CLUSTER_TEXTURE_')}\n\t\t${buildShaderDefines(enums, '')}\n`;\nclass LightsBuffer {\n\t\tconstructor(device){\n\t\t\t\tthis.areaLightsEnabled = false;\n\t\t\t\tthis.device = device;\n\t\t\t\tShaderChunks.get(device, SHADERLANGUAGE_GLSL).set('lightBufferDefinesPS', lightBufferDefines);\n\t\t\t\tShaderChunks.get(device, SHADERLANGUAGE_WGSL).set('lightBufferDefinesPS', lightBufferDefines);\n\t\t\t\tthis.cookiesEnabled = false;\n\t\t\t\tthis.shadowsEnabled = false;\n\t\t\t\tthis.areaLightsEnabled = false;\n\t\t\t\tthis.maxLights = 255;\n\t\t\t\tconst pixelsPerLightFloat = TextureIndexFloat.COUNT;\n\t\t\t\tthis.lightsFloat = new Float32Array(4 * pixelsPerLightFloat * this.maxLights);\n\t\t\t\tthis.lightsUint = new Uint32Array(this.lightsFloat.buffer);\n\t\t\t\tthis.lightsTexture = this.createTexture(this.device, pixelsPerLightFloat, this.maxLights, PIXELFORMAT_RGBA32F, 'LightsTexture');\n\t\t\t\tthis._lightsTextureId = this.device.scope.resolve('lightsTexture');\n\t\t\t\tthis.invMaxColorValue = 0;\n\t\t\t\tthis.invMaxAttenuation = 0;\n\t\t\t\tthis.boundsMin = new Vec3();\n\t\t\t\tthis.boundsDelta = new Vec3();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.lightsTexture?.destroy();\n\t\t\t\tthis.lightsTexture = null;\n\t\t}\n\t\tcreateTexture(device, width, height, format, name) {\n\t\t\t\tconst tex = new Texture(device, {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\ttype: TEXTURETYPE_DEFAULT,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tanisotropy: 1\n\t\t\t\t});\n\t\t\t\treturn tex;\n\t\t}\n\t\tsetBounds(min, delta) {\n\t\t\t\tthis.boundsMin.copy(min);\n\t\t\t\tthis.boundsDelta.copy(delta);\n\t\t}\n\t\tuploadTextures() {\n\t\t\t\tthis.lightsTexture.lock().set(this.lightsFloat);\n\t\t\t\tthis.lightsTexture.unlock();\n\t\t}\n\t\tupdateUniforms() {\n\t\t\t\tthis._lightsTextureId.setValue(this.lightsTexture);\n\t\t}\n\t\tgetSpotDirection(direction, spot) {\n\t\t\t\tconst mat = spot._node.getWorldTransform();\n\t\t\t\tmat.getY(direction).mulScalar(-1);\n\t\t\t\tdirection.normalize();\n\t\t}\n\t\tgetLightAreaSizes(light) {\n\t\t\t\tconst mat = light._node.getWorldTransform();\n\t\t\t\tmat.transformVector(areaHalfAxisWidth, tempVec3$1);\n\t\t\t\ttempAreaLightSizes[0] = tempVec3$1.x;\n\t\t\t\ttempAreaLightSizes[1] = tempVec3$1.y;\n\t\t\t\ttempAreaLightSizes[2] = tempVec3$1.z;\n\t\t\t\tmat.transformVector(areaHalfAxisHeight, tempVec3$1);\n\t\t\t\ttempAreaLightSizes[3] = tempVec3$1.x;\n\t\t\t\ttempAreaLightSizes[4] = tempVec3$1.y;\n\t\t\t\ttempAreaLightSizes[5] = tempVec3$1.z;\n\t\t\t\treturn tempAreaLightSizes;\n\t\t}\n\t\taddLightData(light, lightIndex) {\n\t\t\t\tconst isSpot = light._type === LIGHTTYPE_SPOT;\n\t\t\t\tconst hasAtlasViewport = light.atlasViewportAllocated;\n\t\t\t\tconst isCookie = this.cookiesEnabled && !!light._cookie && hasAtlasViewport;\n\t\t\t\tconst isArea = this.areaLightsEnabled && light.shape !== LIGHTSHAPE_PUNCTUAL;\n\t\t\t\tconst castShadows = this.shadowsEnabled && light.castShadows && hasAtlasViewport;\n\t\t\t\tconst pos = light._node.getPosition();\n\t\t\t\tlet lightProjectionMatrix = null;\n\t\t\t\tlet atlasViewport = null;\n\t\t\t\tif (isSpot) {\n\t\t\t\t\t\tif (castShadows) {\n\t\t\t\t\t\t\t\tconst lightRenderData = light.getRenderData(null, 0);\n\t\t\t\t\t\t\t\tlightProjectionMatrix = lightRenderData.shadowMatrix;\n\t\t\t\t\t\t} else if (isCookie) {\n\t\t\t\t\t\t\t\tlightProjectionMatrix = LightCamera.evalSpotCookieMatrix(light);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (castShadows || isCookie) {\n\t\t\t\t\t\t\t\tatlasViewport = light.atlasViewport;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst dataFloat = this.lightsFloat;\n\t\t\t\tconst dataUint = this.lightsUint;\n\t\t\t\tconst dataFloatStart = lightIndex * this.lightsTexture.width * 4;\n\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 0] = pos.x;\n\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 1] = pos.y;\n\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 2] = pos.z;\n\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 3] = light.attenuationEnd;\n\t\t\t\tconst clusteredData = light.clusteredData;\n\t\t\t\tdataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 0] = clusteredData[0];\n\t\t\t\tdataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 1] = clusteredData[1];\n\t\t\t\tdataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 2] = clusteredData[2];\n\t\t\t\tif (light.castShadows) {\n\t\t\t\t\t\tconst lightRenderData = light.getRenderData(null, 0);\n\t\t\t\t\t\tconst biases = light._getUniformBiasValues(lightRenderData);\n\t\t\t\t\t\tconst biasHalf = FloatPacking.float2Half(biases.bias);\n\t\t\t\t\t\tconst normalBiasHalf = FloatPacking.float2Half(biases.normalBias);\n\t\t\t\t\t\tdataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 3] = biasHalf | normalBiasHalf << 16;\n\t\t\t\t}\n\t\t\t\tif (isSpot) {\n\t\t\t\t\t\tthis.getSpotDirection(tempVec3$1, light);\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 0] = tempVec3$1.x;\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 1] = tempVec3$1.y;\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 2] = tempVec3$1.z;\n\t\t\t\t}\n\t\t\t\tdataUint[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 3] = light.getClusteredFlags(castShadows, isCookie);\n\t\t\t\tif (lightProjectionMatrix) {\n\t\t\t\t\t\tconst matData = lightProjectionMatrix.data;\n\t\t\t\t\t\tfor(let m = 0; m < 16; m++){\n\t\t\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.PROJ_MAT_0 + m] = matData[m];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (atlasViewport) {\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 0] = atlasViewport.x;\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 1] = atlasViewport.y;\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 2] = atlasViewport.z / 3;\n\t\t\t\t}\n\t\t\t\tif (isArea) {\n\t\t\t\t\t\tconst areaSizes = this.getLightAreaSizes(light);\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 0] = areaSizes[0];\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 1] = areaSizes[1];\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 2] = areaSizes[2];\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 0] = areaSizes[3];\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 1] = areaSizes[4];\n\t\t\t\t\t\tdataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 2] = areaSizes[5];\n\t\t\t\t}\n\t\t}\n}\n\nconst tempVec3 = new Vec3();\nconst tempMin3 = new Vec3();\nconst tempMax3 = new Vec3();\nconst tempBox = new BoundingBox();\nclass ClusterLight {\n\t\tconstructor(){\n\t\t\t\tthis.light = null;\n\t\t\t\tthis.min = new Vec3();\n\t\t\t\tthis.max = new Vec3();\n\t\t}\n}\nclass WorldClusters {\n\t\tconstructor(device){\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.name = 'Untitled';\n\t\t\t\tthis.reportCount = 0;\n\t\t\t\tthis.boundsMin = new Vec3();\n\t\t\t\tthis.boundsMax = new Vec3();\n\t\t\t\tthis.boundsDelta = new Vec3();\n\t\t\t\tthis._cells = new Vec3(1, 1, 1);\n\t\t\t\tthis._cellsLimit = new Vec3();\n\t\t\t\tthis.cells = this._cells;\n\t\t\t\tthis.maxCellLightCount = 4;\n\t\t\t\tthis._usedLights = [];\n\t\t\t\tthis._usedLights.push(new ClusterLight());\n\t\t\t\tthis.lightsBuffer = new LightsBuffer(device);\n\t\t\t\tthis.registerUniforms(device);\n\t\t}\n\t\tset maxCellLightCount(count) {\n\t\t\t\tif (count !== this._maxCellLightCount) {\n\t\t\t\t\t\tthis._maxCellLightCount = count;\n\t\t\t\t\t\tthis._cellsDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget maxCellLightCount() {\n\t\t\t\treturn this._maxCellLightCount;\n\t\t}\n\t\tset cells(value) {\n\t\t\t\ttempVec3.copy(value).floor();\n\t\t\t\tif (!this._cells.equals(tempVec3)) {\n\t\t\t\t\t\tthis._cells.copy(tempVec3);\n\t\t\t\t\t\tthis._cellsLimit.copy(tempVec3).sub(Vec3.ONE);\n\t\t\t\t\t\tthis._cellsDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tget cells() {\n\t\t\t\treturn this._cells;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.lightsBuffer.destroy();\n\t\t\t\tthis.releaseClusterTexture();\n\t\t}\n\t\treleaseClusterTexture() {\n\t\t\t\tif (this.clusterTexture) {\n\t\t\t\t\t\tthis.clusterTexture.destroy();\n\t\t\t\t\t\tthis.clusterTexture = null;\n\t\t\t\t}\n\t\t}\n\t\tregisterUniforms(device) {\n\t\t\t\tthis._clusterSkipId = device.scope.resolve('clusterSkip');\n\t\t\t\tthis._clusterMaxCellsId = device.scope.resolve('clusterMaxCells');\n\t\t\t\tthis._clusterWorldTextureId = device.scope.resolve('clusterWorldTexture');\n\t\t\t\tthis._clusterTextureSizeId = device.scope.resolve('clusterTextureSize');\n\t\t\t\tthis._clusterTextureSizeData = new Float32Array(3);\n\t\t\t\tthis._clusterBoundsMinId = device.scope.resolve('clusterBoundsMin');\n\t\t\t\tthis._clusterBoundsMinData = new Float32Array(3);\n\t\t\t\tthis._clusterBoundsDeltaId = device.scope.resolve('clusterBoundsDelta');\n\t\t\t\tthis._clusterBoundsDeltaData = new Float32Array(3);\n\t\t\t\tthis._clusterCellsCountByBoundsSizeId = device.scope.resolve('clusterCellsCountByBoundsSize');\n\t\t\t\tthis._clusterCellsCountByBoundsSizeData = new Float32Array(3);\n\t\t\t\tthis._clusterCellsDotId = device.scope.resolve('clusterCellsDot');\n\t\t\t\tthis._clusterCellsDotData = new Float32Array(3);\n\t\t\t\tthis._clusterCellsMaxId = device.scope.resolve('clusterCellsMax');\n\t\t\t\tthis._clusterCellsMaxData = new Float32Array(3);\n\t\t}\n\t\tupdateParams(lightingParams) {\n\t\t\t\tif (lightingParams) {\n\t\t\t\t\t\tthis.cells = lightingParams.cells;\n\t\t\t\t\t\tthis.maxCellLightCount = lightingParams.maxLightsPerCell;\n\t\t\t\t\t\tthis.lightsBuffer.cookiesEnabled = lightingParams.cookiesEnabled;\n\t\t\t\t\t\tthis.lightsBuffer.shadowsEnabled = lightingParams.shadowsEnabled;\n\t\t\t\t\t\tthis.lightsBuffer.areaLightsEnabled = lightingParams.areaLightsEnabled;\n\t\t\t\t}\n\t\t}\n\t\tupdateCells() {\n\t\t\t\tif (this._cellsDirty) {\n\t\t\t\t\t\tthis._cellsDirty = false;\n\t\t\t\t\t\tconst cx = this._cells.x;\n\t\t\t\t\t\tconst cy = this._cells.y;\n\t\t\t\t\t\tconst cz = this._cells.z;\n\t\t\t\t\t\tconst numCells = cx * cy * cz;\n\t\t\t\t\t\tconst totalPixels = this.maxCellLightCount * numCells;\n\t\t\t\t\t\tlet width = Math.ceil(Math.sqrt(totalPixels));\n\t\t\t\t\t\twidth = math.roundUp(width, this.maxCellLightCount);\n\t\t\t\t\t\tconst height = Math.ceil(totalPixels / width);\n\t\t\t\t\t\tthis._clusterCellsMaxData[0] = cx;\n\t\t\t\t\t\tthis._clusterCellsMaxData[1] = cy;\n\t\t\t\t\t\tthis._clusterCellsMaxData[2] = cz;\n\t\t\t\t\t\tthis._clusterCellsDotData[0] = this.maxCellLightCount;\n\t\t\t\t\t\tthis._clusterCellsDotData[1] = cx * cz * this.maxCellLightCount;\n\t\t\t\t\t\tthis._clusterCellsDotData[2] = cx * this.maxCellLightCount;\n\t\t\t\t\t\tthis.clusters = new Uint8ClampedArray(totalPixels);\n\t\t\t\t\t\tthis.counts = new Int32Array(numCells);\n\t\t\t\t\t\tthis._clusterTextureSizeData[0] = width;\n\t\t\t\t\t\tthis._clusterTextureSizeData[1] = 1.0 / width;\n\t\t\t\t\t\tthis._clusterTextureSizeData[2] = 1.0 / height;\n\t\t\t\t\t\tthis.releaseClusterTexture();\n\t\t\t\t\t\tthis.clusterTexture = this.lightsBuffer.createTexture(this.device, width, height, PIXELFORMAT_R8, 'ClusterTexture');\n\t\t\t\t}\n\t\t}\n\t\tuploadTextures() {\n\t\t\t\tthis.clusterTexture.lock().set(this.clusters);\n\t\t\t\tthis.clusterTexture.unlock();\n\t\t\t\tthis.lightsBuffer.uploadTextures();\n\t\t}\n\t\tupdateUniforms() {\n\t\t\t\tthis._clusterSkipId.setValue(this._usedLights.length > 1 ? 0 : 1);\n\t\t\t\tthis.lightsBuffer.updateUniforms();\n\t\t\t\tthis._clusterWorldTextureId.setValue(this.clusterTexture);\n\t\t\t\tthis._clusterMaxCellsId.setValue(this.maxCellLightCount);\n\t\t\t\tconst boundsDelta = this.boundsDelta;\n\t\t\t\tthis._clusterCellsCountByBoundsSizeData[0] = this._cells.x / boundsDelta.x;\n\t\t\t\tthis._clusterCellsCountByBoundsSizeData[1] = this._cells.y / boundsDelta.y;\n\t\t\t\tthis._clusterCellsCountByBoundsSizeData[2] = this._cells.z / boundsDelta.z;\n\t\t\t\tthis._clusterCellsCountByBoundsSizeId.setValue(this._clusterCellsCountByBoundsSizeData);\n\t\t\t\tthis._clusterBoundsMinData[0] = this.boundsMin.x;\n\t\t\t\tthis._clusterBoundsMinData[1] = this.boundsMin.y;\n\t\t\t\tthis._clusterBoundsMinData[2] = this.boundsMin.z;\n\t\t\t\tthis._clusterBoundsDeltaData[0] = boundsDelta.x;\n\t\t\t\tthis._clusterBoundsDeltaData[1] = boundsDelta.y;\n\t\t\t\tthis._clusterBoundsDeltaData[2] = boundsDelta.z;\n\t\t\t\tthis._clusterTextureSizeId.setValue(this._clusterTextureSizeData);\n\t\t\t\tthis._clusterBoundsMinId.setValue(this._clusterBoundsMinData);\n\t\t\t\tthis._clusterBoundsDeltaId.setValue(this._clusterBoundsDeltaData);\n\t\t\t\tthis._clusterCellsDotId.setValue(this._clusterCellsDotData);\n\t\t\t\tthis._clusterCellsMaxId.setValue(this._clusterCellsMaxData);\n\t\t}\n\t\tevalLightCellMinMax(clusteredLight, min, max) {\n\t\t\t\tmin.copy(clusteredLight.min);\n\t\t\t\tmin.sub(this.boundsMin);\n\t\t\t\tmin.div(this.boundsDelta);\n\t\t\t\tmin.mul2(min, this.cells);\n\t\t\t\tmin.floor();\n\t\t\t\tmax.copy(clusteredLight.max);\n\t\t\t\tmax.sub(this.boundsMin);\n\t\t\t\tmax.div(this.boundsDelta);\n\t\t\t\tmax.mul2(max, this.cells);\n\t\t\t\tmax.ceil();\n\t\t\t\tmin.max(Vec3.ZERO);\n\t\t\t\tmax.min(this._cellsLimit);\n\t\t}\n\t\tcollectLights(lights) {\n\t\t\t\tconst maxLights = this.lightsBuffer.maxLights;\n\t\t\t\tconst usedLights = this._usedLights;\n\t\t\t\tlet lightIndex = 1;\n\t\t\t\tlights.forEach((light)=>{\n\t\t\t\t\t\tconst runtimeLight = !!(light.mask & (MASK_AFFECT_DYNAMIC | MASK_AFFECT_LIGHTMAPPED));\n\t\t\t\t\t\tconst zeroAngleSpotlight = light.type === LIGHTTYPE_SPOT && light._outerConeAngle === 0;\n\t\t\t\t\t\tif (light.enabled && light.type !== LIGHTTYPE_DIRECTIONAL && light.visibleThisFrame && light.intensity > 0 && runtimeLight && !zeroAngleSpotlight) {\n\t\t\t\t\t\t\t\tif (lightIndex < maxLights) {\n\t\t\t\t\t\t\t\t\t\tlet clusteredLight;\n\t\t\t\t\t\t\t\t\t\tif (lightIndex < usedLights.length) {\n\t\t\t\t\t\t\t\t\t\t\t\tclusteredLight = usedLights[lightIndex];\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tclusteredLight = new ClusterLight();\n\t\t\t\t\t\t\t\t\t\t\t\tusedLights.push(clusteredLight);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tclusteredLight.light = light;\n\t\t\t\t\t\t\t\t\t\tlight.getBoundingBox(tempBox);\n\t\t\t\t\t\t\t\t\t\tclusteredLight.min.copy(tempBox.getMin());\n\t\t\t\t\t\t\t\t\t\tclusteredLight.max.copy(tempBox.getMax());\n\t\t\t\t\t\t\t\t\t\tlightIndex++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tusedLights.length = lightIndex;\n\t\t}\n\t\tevaluateBounds() {\n\t\t\t\tconst usedLights = this._usedLights;\n\t\t\t\tconst min = this.boundsMin;\n\t\t\t\tconst max = this.boundsMax;\n\t\t\t\tif (usedLights.length > 1) {\n\t\t\t\t\t\tmin.copy(usedLights[1].min);\n\t\t\t\t\t\tmax.copy(usedLights[1].max);\n\t\t\t\t\t\tfor(let i = 2; i < usedLights.length; i++){\n\t\t\t\t\t\t\t\tmin.min(usedLights[i].min);\n\t\t\t\t\t\t\t\tmax.max(usedLights[i].max);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tmin.set(0, 0, 0);\n\t\t\t\t\t\tmax.set(1, 1, 1);\n\t\t\t\t}\n\t\t\t\tthis.boundsDelta.sub2(max, min);\n\t\t\t\tthis.lightsBuffer.setBounds(min, this.boundsDelta);\n\t\t}\n\t\tupdateClusters(lightingParams) {\n\t\t\t\tthis.counts.fill(0);\n\t\t\t\tthis.clusters.fill(0);\n\t\t\t\tthis.lightsBuffer.areaLightsEnabled = lightingParams ? lightingParams.areaLightsEnabled : false;\n\t\t\t\tconst divX = this._cells.x;\n\t\t\t\tconst divZ = this._cells.z;\n\t\t\t\tconst counts = this.counts;\n\t\t\t\tconst limit = this._maxCellLightCount;\n\t\t\t\tconst clusters = this.clusters;\n\t\t\t\tconst pixelsPerCellCount = this.maxCellLightCount;\n\t\t\t\tconst usedLights = this._usedLights;\n\t\t\t\tfor(let i = 1; i < usedLights.length; i++){\n\t\t\t\t\t\tconst clusteredLight = usedLights[i];\n\t\t\t\t\t\tconst light = clusteredLight.light;\n\t\t\t\t\t\tthis.lightsBuffer.addLightData(light, i);\n\t\t\t\t\t\tthis.evalLightCellMinMax(clusteredLight, tempMin3, tempMax3);\n\t\t\t\t\t\tconst xStart = tempMin3.x;\n\t\t\t\t\t\tconst xEnd = tempMax3.x;\n\t\t\t\t\t\tconst yStart = tempMin3.y;\n\t\t\t\t\t\tconst yEnd = tempMax3.y;\n\t\t\t\t\t\tconst zStart = tempMin3.z;\n\t\t\t\t\t\tconst zEnd = tempMax3.z;\n\t\t\t\t\t\tfor(let x = xStart; x <= xEnd; x++){\n\t\t\t\t\t\t\t\tfor(let z = zStart; z <= zEnd; z++){\n\t\t\t\t\t\t\t\t\t\tfor(let y = yStart; y <= yEnd; y++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst clusterIndex = x + divX * (z + y * divZ);\n\t\t\t\t\t\t\t\t\t\t\t\tconst count = counts[clusterIndex];\n\t\t\t\t\t\t\t\t\t\t\t\tif (count < limit) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclusters[pixelsPerCellCount * clusterIndex + count] = i;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcounts[clusterIndex] = count + 1;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdate(lights, lightingParams = null) {\n\t\t\t\tthis.updateParams(lightingParams);\n\t\t\t\tthis.updateCells();\n\t\t\t\tthis.collectLights(lights);\n\t\t\t\tthis.evaluateBounds();\n\t\t\t\tthis.updateClusters(lightingParams);\n\t\t\t\tthis.uploadTextures();\n\t\t}\n\t\tactivate() {\n\t\t\t\tthis.updateUniforms();\n\t\t}\n}\n\nconst base64String = ' ';\nlet data = null;\nconst initData = ()=>{\n\t\tif (!data) {\n\t\t\t\tconst binaryString = atob(base64String);\n\t\t\t\tdata = Uint8Array.from(binaryString, (char)=>char.charCodeAt(0));\n\t\t}\n};\nconst blueNoiseData = ()=>{\n\t\tinitData();\n\t\treturn data;\n};\nclass BlueNoise {\n\t\tconstructor(seed = 0){\n\t\t\t\tthis.seed = 0;\n\t\t\t\tthis.seed = seed * 4;\n\t\t\t\tinitData();\n\t\t}\n\t\t_next() {\n\t\t\t\tthis.seed = (this.seed + 4) % data.length;\n\t\t}\n\t\tvalue() {\n\t\t\t\tthis._next();\n\t\t\t\treturn data[this.seed] / 255;\n\t\t}\n\t\tvec4(dest = new Vec4()) {\n\t\t\t\tthis._next();\n\t\t\t\treturn dest.set(data[this.seed], data[this.seed + 1], data[this.seed + 2], data[this.seed + 3]).mulScalar(1 / 255);\n\t\t}\n}\n\nconst lightCubeDir = [\n\t\tnew Vec3(-1, 0, 0),\n\t\tnew Vec3(1, 0, 0),\n\t\tnew Vec3(0, -1, 0),\n\t\tnew Vec3(0, 1, 0),\n\t\tnew Vec3(0, 0, -1),\n\t\tnew Vec3(0, 0, 1)\n];\nclass LightCube {\n\t\tupdate(ambientLight, lights) {\n\t\t\t\tconst colors = this.colors;\n\t\t\t\tconst { r, g, b } = ambientLight;\n\t\t\t\tfor(let j = 0; j < 6; j++){\n\t\t\t\t\t\tcolors[j * 3] = r;\n\t\t\t\t\t\tcolors[j * 3 + 1] = g;\n\t\t\t\t\t\tcolors[j * 3 + 2] = b;\n\t\t\t\t}\n\t\t\t\tfor(let j = 0; j < lights.length; j++){\n\t\t\t\t\t\tconst light = lights[j];\n\t\t\t\t\t\tif (light._type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\tfor(let c = 0; c < 6; c++){\n\t\t\t\t\t\t\t\t\t\tconst weight = Math.max(lightCubeDir[c].dot(light._direction), 0) * light._intensity;\n\t\t\t\t\t\t\t\t\t\tconst lightColor = light._color;\n\t\t\t\t\t\t\t\t\t\tcolors[c * 3] += lightColor.r * weight;\n\t\t\t\t\t\t\t\t\t\tcolors[c * 3 + 1] += lightColor.g * weight;\n\t\t\t\t\t\t\t\t\t\tcolors[c * 3 + 2] += lightColor.b * weight;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.colors = new Float32Array(6 * 3);\n\t\t}\n}\n\nconst createTexture = (device, namePrefix, size, data)=>{\n\t\tconst texture = new Texture(device, {\n\t\t\t\tname: `${namePrefix}${size}`,\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tformat: PIXELFORMAT_RGBA8,\n\t\t\t\taddressU: ADDRESS_REPEAT,\n\t\t\t\taddressV: ADDRESS_REPEAT,\n\t\t\t\ttype: TEXTURETYPE_DEFAULT,\n\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\tanisotropy: 1,\n\t\t\t\tmipmaps: false\n\t\t});\n\t\ttexture.lock().set(data);\n\t\ttexture.unlock();\n\t\treturn texture;\n};\nconst deviceCacheBlueNoise = new DeviceCache();\nconst getBlueNoiseTexture = (device)=>{\n\t\treturn deviceCacheBlueNoise.get(device, ()=>{\n\t\t\t\tconst data = blueNoiseData();\n\t\t\t\tconst size = Math.sqrt(data.length / 4);\n\t\t\t\treturn createTexture(device, 'BlueNoise', size, data);\n\t\t});\n};\n\nclass ShadowMap {\n\t\tconstructor(texture, targets){\n\t\t\t\tthis.texture = texture;\n\t\t\t\tthis.cached = false;\n\t\t\t\tthis.renderTargets = targets;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this.texture) {\n\t\t\t\t\t\tthis.texture.destroy();\n\t\t\t\t\t\tthis.texture = null;\n\t\t\t\t}\n\t\t\t\tconst targets = this.renderTargets;\n\t\t\t\tfor(let i = 0; i < targets.length; i++){\n\t\t\t\t\t\ttargets[i].destroy();\n\t\t\t\t}\n\t\t\t\tthis.renderTargets.length = 0;\n\t\t}\n\t\tstatic create(device, light) {\n\t\t\t\tlet shadowMap = null;\n\t\t\t\tif (light._type === LIGHTTYPE_OMNI) {\n\t\t\t\t\t\tshadowMap = this.createCubemap(device, light._shadowResolution, light._shadowType);\n\t\t\t\t} else {\n\t\t\t\t\t\tshadowMap = this.create2dMap(device, light._shadowResolution, light._shadowType);\n\t\t\t\t}\n\t\t\t\treturn shadowMap;\n\t\t}\n\t\tstatic createAtlas(device, resolution, shadowType) {\n\t\t\t\tconst shadowMap = this.create2dMap(device, resolution, shadowType);\n\t\t\t\tconst targets = shadowMap.renderTargets;\n\t\t\t\tconst rt = targets[0];\n\t\t\t\tfor(let i = 0; i < 5; i++){\n\t\t\t\t\t\ttargets.push(rt);\n\t\t\t\t}\n\t\t\t\treturn shadowMap;\n\t\t}\n\t\tstatic create2dMap(device, size, shadowType) {\n\t\t\t\tconst shadowInfo = shadowTypeInfo.get(shadowType);\n\t\t\t\tlet format = shadowInfo.format;\n\t\t\t\tif (format === PIXELFORMAT_R32F && !device.textureFloatRenderable && device.textureHalfFloatRenderable) {\n\t\t\t\t\t\tformat = PIXELFORMAT_R16F;\n\t\t\t\t}\n\t\t\t\tconst formatName = pixelFormatInfo.get(format)?.name;\n\t\t\t\tlet filter = FILTER_LINEAR;\n\t\t\t\tif (shadowType === SHADOW_VSM_32F) {\n\t\t\t\t\t\tfilter = device.extTextureFloatLinear ? FILTER_LINEAR : FILTER_NEAREST;\n\t\t\t\t}\n\t\t\t\tif (shadowType === SHADOW_PCSS_32F) {\n\t\t\t\t\t\tfilter = FILTER_NEAREST;\n\t\t\t\t}\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\twidth: size,\n\t\t\t\t\t\theight: size,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: filter,\n\t\t\t\t\t\tmagFilter: filter,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tname: `ShadowMap2D_${formatName}`\n\t\t\t\t});\n\t\t\t\tlet target = null;\n\t\t\t\tif (shadowInfo?.pcf) {\n\t\t\t\t\t\ttexture.compareOnRead = true;\n\t\t\t\t\t\ttexture.compareFunc = FUNC_LESS;\n\t\t\t\t\t\ttarget = new RenderTarget({\n\t\t\t\t\t\t\t\tdepthBuffer: texture\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\ttarget = new RenderTarget({\n\t\t\t\t\t\t\t\tcolorBuffer: texture,\n\t\t\t\t\t\t\t\tdepth: true\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (device.isWebGPU) {\n\t\t\t\t\t\ttarget.flipY = true;\n\t\t\t\t}\n\t\t\t\treturn new ShadowMap(texture, [\n\t\t\t\t\t\ttarget\n\t\t\t\t]);\n\t\t}\n\t\tstatic createCubemap(device, size, shadowType) {\n\t\t\t\tconst shadowInfo = shadowTypeInfo.get(shadowType);\n\t\t\t\tconst formatName = pixelFormatInfo.get(shadowInfo.format)?.name;\n\t\t\t\tconst isPcss = shadowType === SHADOW_PCSS_32F;\n\t\t\t\tconst filter = isPcss ? FILTER_NEAREST : FILTER_LINEAR;\n\t\t\t\tconst cubemap = new Texture(device, {\n\t\t\t\t\t\tformat: shadowInfo?.format,\n\t\t\t\t\t\twidth: size,\n\t\t\t\t\t\theight: size,\n\t\t\t\t\t\tcubemap: true,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: filter,\n\t\t\t\t\t\tmagFilter: filter,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tname: `ShadowMapCube_${formatName}`\n\t\t\t\t});\n\t\t\t\tif (!isPcss) {\n\t\t\t\t\t\tcubemap.compareOnRead = true;\n\t\t\t\t\t\tcubemap.compareFunc = FUNC_LESS;\n\t\t\t\t}\n\t\t\t\tconst targets = [];\n\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\tif (isPcss) {\n\t\t\t\t\t\t\t\ttargets.push(new RenderTarget({\n\t\t\t\t\t\t\t\t\t\tcolorBuffer: cubemap,\n\t\t\t\t\t\t\t\t\t\tface: i,\n\t\t\t\t\t\t\t\t\t\tdepth: true\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttargets.push(new RenderTarget({\n\t\t\t\t\t\t\t\t\t\tdepthBuffer: cubemap,\n\t\t\t\t\t\t\t\t\t\tface: i\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn new ShadowMap(cubemap, targets);\n\t\t}\n}\n\nconst _tempArray = [];\nconst _tempArray2 = [];\nconst _viewport$1 = new Vec4();\nconst _scissor = new Vec4();\nclass Slot {\n\t\tconstructor(rect){\n\t\t\t\tthis.size = Math.floor(rect.w * 1024);\n\t\t\t\tthis.used = false;\n\t\t\t\tthis.lightId = -1;\n\t\t\t\tthis.rect = rect;\n\t\t}\n}\nclass LightTextureAtlas {\n\t\tconstructor(device){\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.version = 1;\n\t\t\t\tthis.shadowAtlasResolution = 2048;\n\t\t\t\tthis.shadowAtlas = null;\n\t\t\t\tthis.shadowEdgePixels = 3;\n\t\t\t\tthis.cookieAtlasResolution = 4;\n\t\t\t\tthis.cookieAtlas = new Texture(this.device, {\n\t\t\t\t\t\tname: 'CookieAtlas',\n\t\t\t\t\t\twidth: this.cookieAtlasResolution,\n\t\t\t\t\t\theight: this.cookieAtlasResolution,\n\t\t\t\t\t\tformat: PIXELFORMAT_SRGBA8,\n\t\t\t\t\t\tcubemap: false,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE\n\t\t\t\t});\n\t\t\t\tthis.cookieRenderTarget = new RenderTarget({\n\t\t\t\t\t\tcolorBuffer: this.cookieAtlas,\n\t\t\t\t\t\tdepth: false,\n\t\t\t\t\t\tflipY: true\n\t\t\t\t});\n\t\t\t\tthis.slots = [];\n\t\t\t\tthis.atlasSplit = [];\n\t\t\t\tthis.cubeSlotsOffsets = [\n\t\t\t\t\t\tnew Vec2(0, 0),\n\t\t\t\t\t\tnew Vec2(0, 1),\n\t\t\t\t\t\tnew Vec2(1, 0),\n\t\t\t\t\t\tnew Vec2(1, 1),\n\t\t\t\t\t\tnew Vec2(2, 0),\n\t\t\t\t\t\tnew Vec2(2, 1)\n\t\t\t\t];\n\t\t\t\tthis.scissorVec = new Vec4();\n\t\t\t\tthis.allocateShadowAtlas(1);\n\t\t\t\tthis.allocateCookieAtlas(1);\n\t\t\t\tthis.allocateUniforms();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.destroyShadowAtlas();\n\t\t\t\tthis.destroyCookieAtlas();\n\t\t}\n\t\tdestroyShadowAtlas() {\n\t\t\t\tthis.shadowAtlas?.destroy();\n\t\t\t\tthis.shadowAtlas = null;\n\t\t}\n\t\tdestroyCookieAtlas() {\n\t\t\t\tthis.cookieAtlas?.destroy();\n\t\t\t\tthis.cookieAtlas = null;\n\t\t\t\tthis.cookieRenderTarget?.destroy();\n\t\t\t\tthis.cookieRenderTarget = null;\n\t\t}\n\t\tallocateShadowAtlas(resolution, shadowType = SHADOW_PCF3_32F) {\n\t\t\t\tconst existingFormat = this.shadowAtlas?.texture.format;\n\t\t\t\tconst requiredFormat = shadowTypeInfo.get(shadowType).format;\n\t\t\t\tif (!this.shadowAtlas || this.shadowAtlas.texture.width !== resolution || existingFormat !== requiredFormat) {\n\t\t\t\t\t\tthis.version++;\n\t\t\t\t\t\tthis.destroyShadowAtlas();\n\t\t\t\t\t\tthis.shadowAtlas = ShadowMap.createAtlas(this.device, resolution, shadowType);\n\t\t\t\t\t\tthis.shadowAtlas.cached = true;\n\t\t\t\t\t\tconst scissorOffset = 4 / this.shadowAtlasResolution;\n\t\t\t\t\t\tthis.scissorVec.set(scissorOffset, scissorOffset, -2 * scissorOffset, -2 * scissorOffset);\n\t\t\t\t}\n\t\t}\n\t\tallocateCookieAtlas(resolution) {\n\t\t\t\tif (this.cookieAtlas.width !== resolution) {\n\t\t\t\t\t\tthis.cookieRenderTarget.resize(resolution, resolution);\n\t\t\t\t\t\tthis.version++;\n\t\t\t\t}\n\t\t}\n\t\tallocateUniforms() {\n\t\t\t\tthis._shadowAtlasTextureId = this.device.scope.resolve('shadowAtlasTexture');\n\t\t\t\tthis._shadowAtlasParamsId = this.device.scope.resolve('shadowAtlasParams');\n\t\t\t\tthis._shadowAtlasParams = new Float32Array(2);\n\t\t\t\tthis._cookieAtlasTextureId = this.device.scope.resolve('cookieAtlasTexture');\n\t\t}\n\t\tupdateUniforms() {\n\t\t\t\tconst rt = this.shadowAtlas.renderTargets[0];\n\t\t\t\tconst shadowBuffer = rt.depthBuffer;\n\t\t\t\tthis._shadowAtlasTextureId.setValue(shadowBuffer);\n\t\t\t\tthis._shadowAtlasParams[0] = this.shadowAtlasResolution;\n\t\t\t\tthis._shadowAtlasParams[1] = this.shadowEdgePixels;\n\t\t\t\tthis._shadowAtlasParamsId.setValue(this._shadowAtlasParams);\n\t\t\t\tthis._cookieAtlasTextureId.setValue(this.cookieAtlas);\n\t\t}\n\t\tsubdivide(numLights, lightingParams) {\n\t\t\t\tlet atlasSplit = lightingParams.atlasSplit;\n\t\t\t\tif (!atlasSplit) {\n\t\t\t\t\t\tconst gridSize = Math.ceil(Math.sqrt(numLights));\n\t\t\t\t\t\tatlasSplit = _tempArray2;\n\t\t\t\t\t\tatlasSplit[0] = gridSize;\n\t\t\t\t\t\tatlasSplit.length = 1;\n\t\t\t\t}\n\t\t\t\tconst arraysEqual = (a, b)=>a.length === b.length && a.every((v, i)=>v === b[i]);\n\t\t\t\tif (!arraysEqual(atlasSplit, this.atlasSplit)) {\n\t\t\t\t\t\tthis.version++;\n\t\t\t\t\t\tthis.slots.length = 0;\n\t\t\t\t\t\tthis.atlasSplit.length = 0;\n\t\t\t\t\t\tthis.atlasSplit.push(...atlasSplit);\n\t\t\t\t\t\tconst splitCount = this.atlasSplit[0];\n\t\t\t\t\t\tif (splitCount > 1) {\n\t\t\t\t\t\t\t\tconst invSize = 1 / splitCount;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < splitCount; i++){\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < splitCount; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst rect = new Vec4(i * invSize, j * invSize, invSize, invSize);\n\t\t\t\t\t\t\t\t\t\t\t\tconst nextLevelSplit = this.atlasSplit[1 + i * splitCount + j];\n\t\t\t\t\t\t\t\t\t\t\t\tif (nextLevelSplit > 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let x = 0; x < nextLevelSplit; x++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let y = 0; y < nextLevelSplit; y++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst invSizeNext = invSize / nextLevelSplit;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst rectNext = new Vec4(rect.x + x * invSizeNext, rect.y + y * invSizeNext, invSizeNext, invSizeNext);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.slots.push(new Slot(rectNext));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.slots.push(new Slot(rect));\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.slots.push(new Slot(new Vec4(0, 0, 1, 1)));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.slots.sort((a, b)=>{\n\t\t\t\t\t\t\t\treturn b.size - a.size;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tcollectLights(localLights, lightingParams) {\n\t\t\t\tconst cookiesEnabled = lightingParams.cookiesEnabled;\n\t\t\t\tconst shadowsEnabled = lightingParams.shadowsEnabled;\n\t\t\t\tlet needsShadowAtlas = false;\n\t\t\t\tlet needsCookieAtlas = false;\n\t\t\t\tconst lights = _tempArray;\n\t\t\t\tlights.length = 0;\n\t\t\t\tconst processLights = (list)=>{\n\t\t\t\t\t\tfor(let i = 0; i < list.length; i++){\n\t\t\t\t\t\t\t\tconst light = list[i];\n\t\t\t\t\t\t\t\tif (light.visibleThisFrame) {\n\t\t\t\t\t\t\t\t\t\tconst lightShadow = shadowsEnabled && light.castShadows;\n\t\t\t\t\t\t\t\t\t\tconst lightCookie = cookiesEnabled && !!light.cookie;\n\t\t\t\t\t\t\t\t\t\tneedsShadowAtlas ||= lightShadow;\n\t\t\t\t\t\t\t\t\t\tneedsCookieAtlas ||= lightCookie;\n\t\t\t\t\t\t\t\t\t\tif (lightShadow || lightCookie) {\n\t\t\t\t\t\t\t\t\t\t\t\tlights.push(light);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tif (cookiesEnabled || shadowsEnabled) {\n\t\t\t\t\t\tprocessLights(localLights);\n\t\t\t\t}\n\t\t\t\tlights.sort((a, b)=>{\n\t\t\t\t\t\treturn b.maxScreenSize - a.maxScreenSize;\n\t\t\t\t});\n\t\t\t\tif (needsShadowAtlas) {\n\t\t\t\t\t\tthis.allocateShadowAtlas(this.shadowAtlasResolution, lightingParams.shadowType);\n\t\t\t\t}\n\t\t\t\tif (needsCookieAtlas) {\n\t\t\t\t\t\tthis.allocateCookieAtlas(this.cookieAtlasResolution);\n\t\t\t\t}\n\t\t\t\tif (needsShadowAtlas || needsCookieAtlas) {\n\t\t\t\t\t\tthis.subdivide(lights.length, lightingParams);\n\t\t\t\t}\n\t\t\t\treturn lights;\n\t\t}\n\t\tsetupSlot(light, rect) {\n\t\t\t\tlight.atlasViewport.copy(rect);\n\t\t\t\tconst faceCount = light.numShadowFaces;\n\t\t\t\tfor(let face = 0; face < faceCount; face++){\n\t\t\t\t\t\tif (light.castShadows || light._cookie) {\n\t\t\t\t\t\t\t\t_viewport$1.copy(rect);\n\t\t\t\t\t\t\t\t_scissor.copy(rect);\n\t\t\t\t\t\t\t\tif (light._type === LIGHTTYPE_SPOT) {\n\t\t\t\t\t\t\t\t\t\t_viewport$1.add(this.scissorVec);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (light._type === LIGHTTYPE_OMNI) {\n\t\t\t\t\t\t\t\t\t\tconst smallSize = _viewport$1.z / 3;\n\t\t\t\t\t\t\t\t\t\tconst offset = this.cubeSlotsOffsets[face];\n\t\t\t\t\t\t\t\t\t\t_viewport$1.x += smallSize * offset.x;\n\t\t\t\t\t\t\t\t\t\t_viewport$1.y += smallSize * offset.y;\n\t\t\t\t\t\t\t\t\t\t_viewport$1.z = smallSize;\n\t\t\t\t\t\t\t\t\t\t_viewport$1.w = smallSize;\n\t\t\t\t\t\t\t\t\t\t_scissor.copy(_viewport$1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (light.castShadows) {\n\t\t\t\t\t\t\t\t\t\tconst lightRenderData = light.getRenderData(null, face);\n\t\t\t\t\t\t\t\t\t\tlightRenderData.shadowViewport.copy(_viewport$1);\n\t\t\t\t\t\t\t\t\t\tlightRenderData.shadowScissor.copy(_scissor);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tassignSlot(light, slotIndex, slotReassigned) {\n\t\t\t\tlight.atlasViewportAllocated = true;\n\t\t\t\tconst slot = this.slots[slotIndex];\n\t\t\t\tslot.lightId = light.id;\n\t\t\t\tslot.used = true;\n\t\t\t\tif (slotReassigned) {\n\t\t\t\t\t\tlight.atlasSlotUpdated = true;\n\t\t\t\t\t\tlight.atlasVersion = this.version;\n\t\t\t\t\t\tlight.atlasSlotIndex = slotIndex;\n\t\t\t\t}\n\t\t}\n\t\tupdate(localLights, lightingParams) {\n\t\t\t\tthis.shadowAtlasResolution = lightingParams.shadowAtlasResolution;\n\t\t\t\tthis.cookieAtlasResolution = lightingParams.cookieAtlasResolution;\n\t\t\t\tconst lights = this.collectLights(localLights, lightingParams);\n\t\t\t\tif (lights.length > 0) {\n\t\t\t\t\t\tconst slots = this.slots;\n\t\t\t\t\t\tfor(let i = 0; i < slots.length; i++){\n\t\t\t\t\t\t\t\tslots[i].used = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst assignCount = Math.min(lights.length, slots.length);\n\t\t\t\t\t\tfor(let i = 0; i < assignCount; i++){\n\t\t\t\t\t\t\t\tconst light = lights[i];\n\t\t\t\t\t\t\t\tif (light.castShadows) {\n\t\t\t\t\t\t\t\t\t\tlight._shadowMap = this.shadowAtlas;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst previousSlot = slots[light.atlasSlotIndex];\n\t\t\t\t\t\t\t\tif (light.atlasVersion === this.version && light.id === previousSlot?.lightId) {\n\t\t\t\t\t\t\t\t\t\tconst previousSlot = slots[light.atlasSlotIndex];\n\t\t\t\t\t\t\t\t\t\tif (previousSlot.size === slots[i].size && !previousSlot.used) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.assignSlot(light, light.atlasSlotIndex, false);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet usedCount = 0;\n\t\t\t\t\t\tfor(let i = 0; i < assignCount; i++){\n\t\t\t\t\t\t\t\twhile(usedCount < slots.length && slots[usedCount].used){\n\t\t\t\t\t\t\t\t\t\tusedCount++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst light = lights[i];\n\t\t\t\t\t\t\t\tif (!light.atlasViewportAllocated) {\n\t\t\t\t\t\t\t\t\t\tthis.assignSlot(light, usedCount, true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst slot = slots[light.atlasSlotIndex];\n\t\t\t\t\t\t\t\tthis.setupSlot(light, slot.rect);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.updateUniforms();\n\t\t}\n}\n\nconst blendModes = [];\nblendModes[BLEND_SUBTRACTIVE] = {\n\t\tsrc: BLENDMODE_ONE,\n\t\tdst: BLENDMODE_ONE,\n\t\top: BLENDEQUATION_REVERSE_SUBTRACT\n};\nblendModes[BLEND_NONE] = {\n\t\tsrc: BLENDMODE_ONE,\n\t\tdst: BLENDMODE_ZERO,\n\t\top: BLENDEQUATION_ADD\n};\nblendModes[BLEND_NORMAL] = {\n\t\tsrc: BLENDMODE_SRC_ALPHA,\n\t\tdst: BLENDMODE_ONE_MINUS_SRC_ALPHA,\n\t\top: BLENDEQUATION_ADD,\n\t\talphaSrc: BLENDMODE_ONE\n};\nblendModes[BLEND_PREMULTIPLIED] = {\n\t\tsrc: BLENDMODE_ONE,\n\t\tdst: BLENDMODE_ONE_MINUS_SRC_ALPHA,\n\t\top: BLENDEQUATION_ADD\n};\nblendModes[BLEND_ADDITIVE] = {\n\t\tsrc: BLENDMODE_ONE,\n\t\tdst: BLENDMODE_ONE,\n\t\top: BLENDEQUATION_ADD\n};\nblendModes[BLEND_ADDITIVEALPHA] = {\n\t\tsrc: BLENDMODE_SRC_ALPHA,\n\t\tdst: BLENDMODE_ONE,\n\t\top: BLENDEQUATION_ADD\n};\nblendModes[BLEND_MULTIPLICATIVE2X] = {\n\t\tsrc: BLENDMODE_DST_COLOR,\n\t\tdst: BLENDMODE_SRC_COLOR,\n\t\top: BLENDEQUATION_ADD\n};\nblendModes[BLEND_SCREEN] = {\n\t\tsrc: BLENDMODE_ONE_MINUS_DST_COLOR,\n\t\tdst: BLENDMODE_ONE,\n\t\top: BLENDEQUATION_ADD\n};\nblendModes[BLEND_MULTIPLICATIVE] = {\n\t\tsrc: BLENDMODE_DST_COLOR,\n\t\tdst: BLENDMODE_ZERO,\n\t\top: BLENDEQUATION_ADD\n};\nblendModes[BLEND_MIN] = {\n\t\tsrc: BLENDMODE_ONE,\n\t\tdst: BLENDMODE_ONE,\n\t\top: BLENDEQUATION_MIN\n};\nblendModes[BLEND_MAX] = {\n\t\tsrc: BLENDMODE_ONE,\n\t\tdst: BLENDMODE_ONE,\n\t\top: BLENDEQUATION_MAX\n};\nlet id$3 = 0;\nclass Material {\n\t\tconstructor(){\n\t\t\t\tthis.meshInstances = [];\n\t\t\t\tthis.name = 'Untitled';\n\t\t\t\tthis.userId = '';\n\t\t\t\tthis.id = id$3++;\n\t\t\t\tthis.variants = new Map();\n\t\t\t\tthis.defines = new Map();\n\t\t\t\tthis._definesDirty = false;\n\t\t\t\tthis.parameters = {};\n\t\t\t\tthis.alphaTest = 0;\n\t\t\t\tthis.alphaToCoverage = false;\n\t\t\t\tthis._blendState = new BlendState();\n\t\t\t\tthis._depthState = new DepthState();\n\t\t\t\tthis.cull = CULLFACE_BACK;\n\t\t\t\tthis.stencilFront = null;\n\t\t\t\tthis.stencilBack = null;\n\t\t\t\tthis._shaderChunks = null;\n\t\t\t\tthis._oldChunks = {};\n\t\t\t\tthis._dirtyShader = true;\n\t\t\t\tthis._shaderVersion = 0;\n\t\t\t\tthis._scene = null;\n\t\t\t\tthis.dirty = true;\n\t\t}\n\t\tget hasShaderChunks() {\n\t\t\t\treturn this._shaderChunks != null;\n\t\t}\n\t\tget shaderChunks() {\n\t\t\t\tif (!this._shaderChunks) {\n\t\t\t\t\t\tthis._shaderChunks = new ShaderChunks();\n\t\t\t\t}\n\t\t\t\treturn this._shaderChunks;\n\t\t}\n\t\tgetShaderChunks(shaderLanguage = SHADERLANGUAGE_GLSL) {\n\t\t\t\tconst chunks = this.shaderChunks;\n\t\t\t\treturn shaderLanguage === SHADERLANGUAGE_GLSL ? chunks.glsl : chunks.wgsl;\n\t\t}\n\t\tset shaderChunksVersion(value) {\n\t\t\t\tthis.shaderChunks.version = value;\n\t\t}\n\t\tget shaderChunksVersion() {\n\t\t\t\treturn this.shaderChunks.version;\n\t\t}\n\t\tset chunks(value) {\n\t\t\t\tthis._oldChunks = value;\n\t\t}\n\t\tget chunks() {\n\t\t\t\tObject.assign(this._oldChunks, Object.fromEntries(this.shaderChunks.glsl));\n\t\t\t\treturn this._oldChunks;\n\t\t}\n\t\tset depthBias(value) {\n\t\t\t\tthis._depthState.depthBias = value;\n\t\t}\n\t\tget depthBias() {\n\t\t\t\treturn this._depthState.depthBias;\n\t\t}\n\t\tset slopeDepthBias(value) {\n\t\t\t\tthis._depthState.depthBiasSlope = value;\n\t\t}\n\t\tget slopeDepthBias() {\n\t\t\t\treturn this._depthState.depthBiasSlope;\n\t\t}\n\t\tset redWrite(value) {\n\t\t\t\tthis._blendState.redWrite = value;\n\t\t}\n\t\tget redWrite() {\n\t\t\t\treturn this._blendState.redWrite;\n\t\t}\n\t\tset greenWrite(value) {\n\t\t\t\tthis._blendState.greenWrite = value;\n\t\t}\n\t\tget greenWrite() {\n\t\t\t\treturn this._blendState.greenWrite;\n\t\t}\n\t\tset blueWrite(value) {\n\t\t\t\tthis._blendState.blueWrite = value;\n\t\t}\n\t\tget blueWrite() {\n\t\t\t\treturn this._blendState.blueWrite;\n\t\t}\n\t\tset alphaWrite(value) {\n\t\t\t\tthis._blendState.alphaWrite = value;\n\t\t}\n\t\tget alphaWrite() {\n\t\t\t\treturn this._blendState.alphaWrite;\n\t\t}\n\t\tget transparent() {\n\t\t\t\treturn this._blendState.blend;\n\t\t}\n\t\t_updateTransparency() {\n\t\t\t\tconst transparent = this.transparent;\n\t\t\t\tconst meshInstances = this.meshInstances;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tmeshInstances[i].transparent = transparent;\n\t\t\t\t}\n\t\t}\n\t\tset blendState(value) {\n\t\t\t\tthis._blendState.copy(value);\n\t\t\t\tthis._updateTransparency();\n\t\t}\n\t\tget blendState() {\n\t\t\t\treturn this._blendState;\n\t\t}\n\t\tset blendType(type) {\n\t\t\t\tconst blendMode = blendModes[type];\n\t\t\t\tthis._blendState.setColorBlend(blendMode.op, blendMode.src, blendMode.dst);\n\t\t\t\tthis._blendState.setAlphaBlend(blendMode.alphaOp ?? blendMode.op, blendMode.alphaSrc ?? blendMode.src, blendMode.alphaDst ?? blendMode.dst);\n\t\t\t\tconst blend = type !== BLEND_NONE;\n\t\t\t\tif (this._blendState.blend !== blend) {\n\t\t\t\t\t\tthis._blendState.blend = blend;\n\t\t\t\t\t\tthis._updateTransparency();\n\t\t\t\t}\n\t\t\t\tthis._updateMeshInstanceKeys();\n\t\t}\n\t\tget blendType() {\n\t\t\t\tif (!this.transparent) {\n\t\t\t\t\t\treturn BLEND_NONE;\n\t\t\t\t}\n\t\t\t\tconst { colorOp, colorSrcFactor, colorDstFactor, alphaOp, alphaSrcFactor, alphaDstFactor } = this._blendState;\n\t\t\t\tfor(let i = 0; i < blendModes.length; i++){\n\t\t\t\t\t\tconst blendMode = blendModes[i];\n\t\t\t\t\t\tif (blendMode.src === colorSrcFactor && blendMode.dst === colorDstFactor && blendMode.op === colorOp && blendMode.src === alphaSrcFactor && blendMode.dst === alphaDstFactor && blendMode.op === alphaOp) {\n\t\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn BLEND_NORMAL;\n\t\t}\n\t\tset depthState(value) {\n\t\t\t\tthis._depthState.copy(value);\n\t\t}\n\t\tget depthState() {\n\t\t\t\treturn this._depthState;\n\t\t}\n\t\tset depthTest(value) {\n\t\t\t\tthis._depthState.test = value;\n\t\t}\n\t\tget depthTest() {\n\t\t\t\treturn this._depthState.test;\n\t\t}\n\t\tset depthFunc(value) {\n\t\t\t\tthis._depthState.func = value;\n\t\t}\n\t\tget depthFunc() {\n\t\t\t\treturn this._depthState.func;\n\t\t}\n\t\tset depthWrite(value) {\n\t\t\t\tthis._depthState.write = value;\n\t\t}\n\t\tget depthWrite() {\n\t\t\t\treturn this._depthState.write;\n\t\t}\n\t\tcopy(source) {\n\t\t\t\tthis.name = source.name;\n\t\t\t\tthis.alphaTest = source.alphaTest;\n\t\t\t\tthis.alphaToCoverage = source.alphaToCoverage;\n\t\t\t\tthis._blendState.copy(source._blendState);\n\t\t\t\tthis._depthState.copy(source._depthState);\n\t\t\t\tthis.cull = source.cull;\n\t\t\t\tthis.stencilFront = source.stencilFront?.clone();\n\t\t\t\tif (source.stencilBack) {\n\t\t\t\t\t\tthis.stencilBack = source.stencilFront === source.stencilBack ? this.stencilFront : source.stencilBack.clone();\n\t\t\t\t}\n\t\t\t\tthis.clearParameters();\n\t\t\t\tfor(const name in source.parameters){\n\t\t\t\t\t\tif (source.parameters.hasOwnProperty(name)) {\n\t\t\t\t\t\t\t\tthis._setParameterSimple(name, source.parameters[name].data);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.defines.clear();\n\t\t\t\tsource.defines.forEach((value, key)=>this.defines.set(key, value));\n\t\t\t\tthis._shaderChunks = source.hasShaderChunks ? new ShaderChunks() : null;\n\t\t\t\tthis._shaderChunks?.copy(source._shaderChunks);\n\t\t\t\treturn this;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst clone = new this.constructor();\n\t\t\t\treturn clone.copy(this);\n\t\t}\n\t\t_updateMeshInstanceKeys() {\n\t\t\t\tconst meshInstances = this.meshInstances;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tmeshInstances[i].updateKey();\n\t\t\t\t}\n\t\t}\n\t\tupdateUniforms(device, scene) {\n\t\t\t\tif (this._dirtyShader) {\n\t\t\t\t\t\tthis.clearVariants();\n\t\t\t\t}\n\t\t}\n\t\tgetShaderVariant(params) {}\n\t\tupdate() {\n\t\t\t\tif (Object.keys(this._oldChunks).length > 0) {\n\t\t\t\t\t\tfor (const [key, value] of Object.entries(this._oldChunks)){\n\t\t\t\t\t\t\t\tthis.shaderChunks.glsl.set(key, value);\n\t\t\t\t\t\t\t\tdelete this._oldChunks[key];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._definesDirty || this._shaderChunks?.isDirty()) {\n\t\t\t\t\t\tthis._definesDirty = false;\n\t\t\t\t\t\tthis._shaderChunks?.resetDirty();\n\t\t\t\t\t\tthis.clearVariants();\n\t\t\t\t}\n\t\t\t\tthis.dirty = true;\n\t\t}\n\t\tclearParameters() {\n\t\t\t\tthis.parameters = {};\n\t\t}\n\t\tgetParameters() {\n\t\t\t\treturn this.parameters;\n\t\t}\n\t\tclearVariants() {\n\t\t\t\tthis.variants.clear();\n\t\t\t\tconst meshInstances = this.meshInstances;\n\t\t\t\tconst count = meshInstances.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tmeshInstances[i].clearShaders();\n\t\t\t\t}\n\t\t}\n\t\tgetParameter(name) {\n\t\t\t\treturn this.parameters[name];\n\t\t}\n\t\t_setParameterSimple(name, data) {\n\t\t\t\tconst param = this.parameters[name];\n\t\t\t\tif (param) {\n\t\t\t\t\t\tparam.data = data;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.parameters[name] = {\n\t\t\t\t\t\t\t\tscopeId: null,\n\t\t\t\t\t\t\t\tdata: data\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tsetParameter(name, data) {\n\t\t\t\tif (data === undefined && typeof name === 'object') {\n\t\t\t\t\t\tconst uniformObject = name;\n\t\t\t\t\t\tif (uniformObject.length) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < uniformObject.length; i++){\n\t\t\t\t\t\t\t\t\t\tthis.setParameter(uniformObject[i]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tname = uniformObject.name;\n\t\t\t\t\t\tdata = uniformObject.value;\n\t\t\t\t}\n\t\t\t\tthis._setParameterSimple(name, data);\n\t\t}\n\t\tdeleteParameter(name) {\n\t\t\t\tif (this.parameters[name]) {\n\t\t\t\t\t\tdelete this.parameters[name];\n\t\t\t\t}\n\t\t}\n\t\tsetParameters(device, names) {\n\t\t\t\tconst parameters = this.parameters;\n\t\t\t\tif (names === undefined) names = parameters;\n\t\t\t\tfor(const paramName in names){\n\t\t\t\t\t\tconst parameter = parameters[paramName];\n\t\t\t\t\t\tif (parameter) {\n\t\t\t\t\t\t\t\tif (!parameter.scopeId) {\n\t\t\t\t\t\t\t\t\t\tparameter.scopeId = device.scope.resolve(paramName);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tparameter.scopeId.setValue(parameter.data);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetDefine(name, value) {\n\t\t\t\tlet modified = false;\n\t\t\t\tconst { defines } = this;\n\t\t\t\tif (value !== undefined && value !== false) {\n\t\t\t\t\t\tmodified = !defines.has(name) || defines.get(name) !== value;\n\t\t\t\t\t\tdefines.set(name, value);\n\t\t\t\t} else {\n\t\t\t\t\t\tmodified = defines.has(name);\n\t\t\t\t\t\tdefines.delete(name);\n\t\t\t\t}\n\t\t\t\tthis._definesDirty ||= modified;\n\t\t}\n\t\tgetDefine(name) {\n\t\t\t\treturn this.defines.has(name);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.variants.clear();\n\t\t\t\tfor(let i = 0; i < this.meshInstances.length; i++){\n\t\t\t\t\t\tconst meshInstance = this.meshInstances[i];\n\t\t\t\t\t\tmeshInstance.clearShaders();\n\t\t\t\t\t\tmeshInstance._material = null;\n\t\t\t\t\t\tif (meshInstance.mesh) {\n\t\t\t\t\t\t\t\tconst defaultMaterial = getDefaultMaterial(meshInstance.mesh.device);\n\t\t\t\t\t\t\t\tif (this !== defaultMaterial) {\n\t\t\t\t\t\t\t\t\t\tmeshInstance.material = defaultMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.meshInstances.length = 0;\n\t\t}\n\t\taddMeshInstanceRef(meshInstance) {\n\t\t\t\tthis.meshInstances.push(meshInstance);\n\t\t}\n\t\tremoveMeshInstanceRef(meshInstance) {\n\t\t\t\tconst meshInstances = this.meshInstances;\n\t\t\t\tconst i = meshInstances.indexOf(meshInstance);\n\t\t\t\tif (i !== -1) {\n\t\t\t\t\t\tmeshInstances.splice(i, 1);\n\t\t\t\t}\n\t\t}\n}\n\nclass ShadowMapCache {\n\t\tconstructor(){\n\t\t\t\tthis.cache = new Map();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.clear();\n\t\t\t\tthis.cache = null;\n\t\t}\n\t\tclear() {\n\t\t\t\tthis.cache.forEach((shadowMaps)=>{\n\t\t\t\t\t\tshadowMaps.forEach((shadowMap)=>{\n\t\t\t\t\t\t\t\tshadowMap.destroy();\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tthis.cache.clear();\n\t\t}\n\t\tgetKey(light) {\n\t\t\t\tconst isCubeMap = light._type === LIGHTTYPE_OMNI;\n\t\t\t\tconst shadowType = light._shadowType;\n\t\t\t\tconst resolution = light._shadowResolution;\n\t\t\t\treturn `${isCubeMap}-${shadowType}-${resolution}`;\n\t\t}\n\t\tget(device, light) {\n\t\t\t\tconst key = this.getKey(light);\n\t\t\t\tconst shadowMaps = this.cache.get(key);\n\t\t\t\tif (shadowMaps && shadowMaps.length) {\n\t\t\t\t\t\treturn shadowMaps.pop();\n\t\t\t\t}\n\t\t\t\tconst shadowMap = ShadowMap.create(device, light);\n\t\t\t\tshadowMap.cached = true;\n\t\t\t\treturn shadowMap;\n\t\t}\n\t\tadd(light, shadowMap) {\n\t\t\t\tconst key = this.getKey(light);\n\t\t\t\tconst shadowMaps = this.cache.get(key);\n\t\t\t\tif (shadowMaps) {\n\t\t\t\t\t\tshadowMaps.push(shadowMap);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.cache.set(key, [\n\t\t\t\t\t\t\t\tshadowMap\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n}\n\nclass RenderPassShadowLocalNonClustered extends RenderPass {\n\t\tconstructor(device, shadowRenderer, light, face, applyVsm){\n\t\t\t\tsuper(device);\n\t\t\t\tthis.requiresCubemaps = false;\n\t\t\t\tthis.shadowRenderer = shadowRenderer;\n\t\t\t\tthis.light = light;\n\t\t\t\tthis.face = face;\n\t\t\t\tthis.applyVsm = applyVsm;\n\t\t\t\tthis.shadowCamera = shadowRenderer.prepareFace(light, null, face);\n\t\t\t\tshadowRenderer.setupRenderPass(this, this.shadowCamera, true);\n\t\t}\n\t\texecute() {\n\t\t\t\tthis.shadowRenderer.renderFace(this.light, null, this.face, false);\n\t\t}\n\t\tafter() {\n\t\t\t\tif (this.applyVsm) {\n\t\t\t\t\t\tthis.shadowRenderer.renderVsm(this.light, this.shadowCamera);\n\t\t\t\t}\n\t\t}\n}\n\nclass ShadowRendererLocal {\n\t\tconstructor(renderer, shadowRenderer){\n\t\t\t\tthis.shadowLights = [];\n\t\t\t\tthis.renderer = renderer;\n\t\t\t\tthis.shadowRenderer = shadowRenderer;\n\t\t\t\tthis.device = renderer.device;\n\t\t}\n\t\tcull(light, comp, casters = null) {\n\t\t\t\tconst isClustered = this.renderer.scene.clusteredLightingEnabled;\n\t\t\t\tlight.visibleThisFrame = true;\n\t\t\t\tif (!isClustered) {\n\t\t\t\t\t\tif (!light._shadowMap) {\n\t\t\t\t\t\t\t\tlight._shadowMap = ShadowMap.create(this.device, light);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst type = light._type;\n\t\t\t\tconst faceCount = type === LIGHTTYPE_SPOT ? 1 : 6;\n\t\t\t\tfor(let face = 0; face < faceCount; face++){\n\t\t\t\t\t\tconst lightRenderData = light.getRenderData(null, face);\n\t\t\t\t\t\tconst shadowCam = lightRenderData.shadowCamera;\n\t\t\t\t\t\tshadowCam.nearClip = light.attenuationEnd / 1000;\n\t\t\t\t\t\tshadowCam.farClip = light.attenuationEnd;\n\t\t\t\t\t\tconst shadowCamNode = shadowCam._node;\n\t\t\t\t\t\tconst lightNode = light._node;\n\t\t\t\t\t\tshadowCamNode.setPosition(lightNode.getPosition());\n\t\t\t\t\t\tif (type === LIGHTTYPE_SPOT) {\n\t\t\t\t\t\t\t\tshadowCam.fov = light._outerConeAngle * 2;\n\t\t\t\t\t\t\t\tshadowCamNode.setRotation(lightNode.getRotation());\n\t\t\t\t\t\t\t\tshadowCamNode.rotateLocal(-90, 0, 0);\n\t\t\t\t\t\t} else if (type === LIGHTTYPE_OMNI) {\n\t\t\t\t\t\t\t\tif (isClustered) {\n\t\t\t\t\t\t\t\t\t\tconst tileSize = this.shadowRenderer.lightTextureAtlas.shadowAtlasResolution * light.atlasViewport.z / 3;\n\t\t\t\t\t\t\t\t\t\tconst texelSize = 2 / tileSize;\n\t\t\t\t\t\t\t\t\t\tconst filterSize = texelSize * this.shadowRenderer.lightTextureAtlas.shadowEdgePixels;\n\t\t\t\t\t\t\t\t\t\tshadowCam.fov = Math.atan(1 + filterSize) * math.RAD_TO_DEG * 2;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tshadowCam.fov = 90;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.renderer.updateCameraFrustum(shadowCam);\n\t\t\t\t\t\tthis.shadowRenderer.cullShadowCasters(comp, light, lightRenderData.visibleCasters, shadowCam, casters);\n\t\t\t\t}\n\t\t}\n\t\tprepareLights(shadowLights, lights) {\n\t\t\t\tlet shadowCamera;\n\t\t\t\tfor(let i = 0; i < lights.length; i++){\n\t\t\t\t\t\tconst light = lights[i];\n\t\t\t\t\t\tif (this.shadowRenderer.needsShadowRendering(light) && light.atlasViewportAllocated) {\n\t\t\t\t\t\t\t\tshadowLights.push(light);\n\t\t\t\t\t\t\t\tfor(let face = 0; face < light.numShadowFaces; face++){\n\t\t\t\t\t\t\t\t\t\tshadowCamera = this.shadowRenderer.prepareFace(light, null, face);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn shadowCamera;\n\t\t}\n\t\tbuildNonClusteredRenderPasses(frameGraph, localLights) {\n\t\t\t\tfor(let i = 0; i < localLights.length; i++){\n\t\t\t\t\t\tconst light = localLights[i];\n\t\t\t\t\t\tif (this.shadowRenderer.needsShadowRendering(light)) {\n\t\t\t\t\t\t\t\tconst applyVsm = light._type === LIGHTTYPE_SPOT;\n\t\t\t\t\t\t\t\tconst faceCount = light.numShadowFaces;\n\t\t\t\t\t\t\t\tfor(let face = 0; face < faceCount; face++){\n\t\t\t\t\t\t\t\t\t\tconst renderPass = new RenderPassShadowLocalNonClustered(this.device, this.shadowRenderer, light, face, applyVsm);\n\t\t\t\t\t\t\t\t\t\tframeGraph.addRenderPass(renderPass);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass RenderPassShadowDirectional extends RenderPass {\n\t\tconstructor(device, shadowRenderer, light, camera, allCascadesRendering){\n\t\t\t\tsuper(device);\n\t\t\t\tthis.shadowRenderer = shadowRenderer;\n\t\t\t\tthis.light = light;\n\t\t\t\tthis.camera = camera;\n\t\t\t\tthis.allCascadesRendering = allCascadesRendering;\n\t\t}\n\t\texecute() {\n\t\t\t\tconst { light, camera, shadowRenderer, allCascadesRendering } = this;\n\t\t\t\tconst faceCount = light.numShadowFaces;\n\t\t\t\tconst shadowUpdateOverrides = light.shadowUpdateOverrides;\n\t\t\t\tfor(let face = 0; face < faceCount; face++){\n\t\t\t\t\t\tif (shadowUpdateOverrides?.[face] !== SHADOWUPDATE_NONE) {\n\t\t\t\t\t\t\t\tshadowRenderer.renderFace(light, camera, face, !allCascadesRendering);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (shadowUpdateOverrides?.[face] === SHADOWUPDATE_THISFRAME) {\n\t\t\t\t\t\t\t\tshadowUpdateOverrides[face] = SHADOWUPDATE_NONE;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tafter() {\n\t\t\t\tthis.shadowRenderer.renderVsm(this.light, this.camera);\n\t\t}\n}\n\nconst visibleSceneAabb = new BoundingBox();\nconst center = new Vec3();\nconst shadowCamView$1 = new Mat4();\nconst aabbPoints = [\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3(),\n\t\tnew Vec3()\n];\nconst _depthRange = {\n\t\tmin: 0,\n\t\tmax: 0\n};\nfunction getDepthRange(cameraViewMatrix, aabbMin, aabbMax) {\n\t\taabbPoints[0].x = aabbPoints[1].x = aabbPoints[2].x = aabbPoints[3].x = aabbMin.x;\n\t\taabbPoints[1].y = aabbPoints[3].y = aabbPoints[7].y = aabbPoints[5].y = aabbMin.y;\n\t\taabbPoints[2].z = aabbPoints[3].z = aabbPoints[6].z = aabbPoints[7].z = aabbMin.z;\n\t\taabbPoints[4].x = aabbPoints[5].x = aabbPoints[6].x = aabbPoints[7].x = aabbMax.x;\n\t\taabbPoints[0].y = aabbPoints[2].y = aabbPoints[4].y = aabbPoints[6].y = aabbMax.y;\n\t\taabbPoints[0].z = aabbPoints[1].z = aabbPoints[4].z = aabbPoints[5].z = aabbMax.z;\n\t\tlet minz = 9999999999;\n\t\tlet maxz = -9999999999;\n\t\tfor(let i = 0; i < 8; ++i){\n\t\t\t\tcameraViewMatrix.transformPoint(aabbPoints[i], aabbPoints[i]);\n\t\t\t\tconst z = aabbPoints[i].z;\n\t\t\t\tif (z < minz) minz = z;\n\t\t\t\tif (z > maxz) maxz = z;\n\t\t}\n\t\t_depthRange.min = minz;\n\t\t_depthRange.max = maxz;\n\t\treturn _depthRange;\n}\nclass ShadowRendererDirectional {\n\t\tconstructor(renderer, shadowRenderer){\n\t\t\t\tthis.renderer = renderer;\n\t\t\t\tthis.shadowRenderer = shadowRenderer;\n\t\t\t\tthis.device = renderer.device;\n\t\t}\n\t\tcull(light, comp, camera, casters = null) {\n\t\t\t\tlight.visibleThisFrame = true;\n\t\t\t\tif (!light._shadowMap) {\n\t\t\t\t\t\tlight._shadowMap = ShadowMap.create(this.device, light);\n\t\t\t\t}\n\t\t\t\tconst nearDist = camera._nearClip;\n\t\t\t\tthis.generateSplitDistances(light, nearDist, Math.min(camera._farClip, light.shadowDistance));\n\t\t\t\tconst shadowUpdateOverrides = light.shadowUpdateOverrides;\n\t\t\t\tfor(let cascade = 0; cascade < light.numCascades; cascade++){\n\t\t\t\t\t\tif (shadowUpdateOverrides?.[cascade] === SHADOWUPDATE_NONE) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst lightRenderData = light.getRenderData(camera, cascade);\n\t\t\t\t\t\tconst shadowCam = lightRenderData.shadowCamera;\n\t\t\t\t\t\tshadowCam.renderTarget = light._shadowMap.renderTargets[0];\n\t\t\t\t\t\tlightRenderData.shadowViewport.copy(light.cascades[cascade]);\n\t\t\t\t\t\tlightRenderData.shadowScissor.copy(light.cascades[cascade]);\n\t\t\t\t\t\tconst shadowCamNode = shadowCam._node;\n\t\t\t\t\t\tconst lightNode = light._node;\n\t\t\t\t\t\tshadowCamNode.setPosition(lightNode.getPosition());\n\t\t\t\t\t\tshadowCamNode.setRotation(lightNode.getRotation());\n\t\t\t\t\t\tshadowCamNode.rotateLocal(-90, 0, 0);\n\t\t\t\t\t\tconst frustumNearDist = cascade === 0 ? nearDist : light._shadowCascadeDistances[cascade - 1];\n\t\t\t\t\t\tconst frustumFarDist = light._shadowCascadeDistances[cascade];\n\t\t\t\t\t\tconst frustumPoints = camera.getFrustumCorners(frustumNearDist, frustumFarDist);\n\t\t\t\t\t\tcenter.set(0, 0, 0);\n\t\t\t\t\t\tconst cameraWorldMat = camera.node.getWorldTransform();\n\t\t\t\t\t\tfor(let i = 0; i < 8; i++){\n\t\t\t\t\t\t\t\tcameraWorldMat.transformPoint(frustumPoints[i], frustumPoints[i]);\n\t\t\t\t\t\t\t\tcenter.add(frustumPoints[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcenter.mulScalar(1 / 8);\n\t\t\t\t\t\tlet radius = 0;\n\t\t\t\t\t\tfor(let i = 0; i < 8; i++){\n\t\t\t\t\t\t\t\tconst dist = frustumPoints[i].sub(center).length();\n\t\t\t\t\t\t\t\tif (dist > radius) {\n\t\t\t\t\t\t\t\t\t\tradius = dist;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst right = shadowCamNode.right;\n\t\t\t\t\t\tconst up = shadowCamNode.up;\n\t\t\t\t\t\tconst lightDir = shadowCamNode.forward;\n\t\t\t\t\t\tconst sizeRatio = 0.25 * light._shadowResolution / radius;\n\t\t\t\t\t\tconst x = Math.ceil(center.dot(up) * sizeRatio) / sizeRatio;\n\t\t\t\t\t\tconst y = Math.ceil(center.dot(right) * sizeRatio) / sizeRatio;\n\t\t\t\t\t\tconst scaledUp = up.mulScalar(x);\n\t\t\t\t\t\tconst scaledRight = right.mulScalar(y);\n\t\t\t\t\t\tconst dot = center.dot(lightDir);\n\t\t\t\t\t\tconst scaledDir = lightDir.mulScalar(dot);\n\t\t\t\t\t\tcenter.add2(scaledUp, scaledRight).add(scaledDir);\n\t\t\t\t\t\tshadowCamNode.setPosition(center);\n\t\t\t\t\t\tshadowCamNode.translateLocal(0, 0, 1000000);\n\t\t\t\t\t\tshadowCam.nearClip = 0.01;\n\t\t\t\t\t\tshadowCam.farClip = 2000000;\n\t\t\t\t\t\tshadowCam.orthoHeight = radius;\n\t\t\t\t\t\tthis.renderer.updateCameraFrustum(shadowCam);\n\t\t\t\t\t\tthis.shadowRenderer.cullShadowCasters(comp, light, lightRenderData.visibleCasters, shadowCam, casters);\n\t\t\t\t\t\tconst cascadeFlag = 1 << cascade;\n\t\t\t\t\t\tconst visibleCasters = lightRenderData.visibleCasters;\n\t\t\t\t\t\tconst origNumVisibleCasters = visibleCasters.length;\n\t\t\t\t\t\tlet numVisibleCasters = 0;\n\t\t\t\t\t\tfor(let i = 0; i < origNumVisibleCasters; i++){\n\t\t\t\t\t\t\t\tconst meshInstance = visibleCasters[i];\n\t\t\t\t\t\t\t\tif (meshInstance.shadowCascadeMask & cascadeFlag) {\n\t\t\t\t\t\t\t\t\t\tvisibleCasters[numVisibleCasters++] = meshInstance;\n\t\t\t\t\t\t\t\t\t\tif (numVisibleCasters === 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tvisibleSceneAabb.copy(meshInstance.aabb);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tvisibleSceneAabb.add(meshInstance.aabb);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (origNumVisibleCasters !== numVisibleCasters) {\n\t\t\t\t\t\t\t\tvisibleCasters.length = numVisibleCasters;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tshadowCamView$1.copy(shadowCamNode.getWorldTransform()).invert();\n\t\t\t\t\t\tconst depthRange = getDepthRange(shadowCamView$1, visibleSceneAabb.getMin(), visibleSceneAabb.getMax());\n\t\t\t\t\t\tshadowCamNode.translateLocal(0, 0, depthRange.max + 0.1);\n\t\t\t\t\t\tshadowCam.farClip = depthRange.max - depthRange.min + 0.2;\n\t\t\t\t\t\tlightRenderData.projectionCompensation = radius;\n\t\t\t\t}\n\t\t}\n\t\tgenerateSplitDistances(light, nearDist, farDist) {\n\t\t\t\tlight._shadowCascadeDistances.fill(farDist);\n\t\t\t\tfor(let i = 1; i < light.numCascades; i++){\n\t\t\t\t\t\tconst fraction = i / light.numCascades;\n\t\t\t\t\t\tconst linearDist = nearDist + (farDist - nearDist) * fraction;\n\t\t\t\t\t\tconst logDist = nearDist * (farDist / nearDist) ** fraction;\n\t\t\t\t\t\tconst dist = math.lerp(linearDist, logDist, light.cascadeDistribution);\n\t\t\t\t\t\tlight._shadowCascadeDistances[i - 1] = dist;\n\t\t\t\t}\n\t\t}\n\t\tgetLightRenderPass(light, camera) {\n\t\t\t\tlet renderPass = null;\n\t\t\t\tif (this.shadowRenderer.needsShadowRendering(light)) {\n\t\t\t\t\t\tconst faceCount = light.numShadowFaces;\n\t\t\t\t\t\tconst shadowUpdateOverrides = light.shadowUpdateOverrides;\n\t\t\t\t\t\tlet allCascadesRendering = true;\n\t\t\t\t\t\tlet shadowCamera;\n\t\t\t\t\t\tfor(let face = 0; face < faceCount; face++){\n\t\t\t\t\t\t\t\tif (shadowUpdateOverrides?.[face] === SHADOWUPDATE_NONE) {\n\t\t\t\t\t\t\t\t\t\tallCascadesRendering = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tshadowCamera = this.shadowRenderer.prepareFace(light, camera, face);\n\t\t\t\t\t\t}\n\t\t\t\t\t\trenderPass = new RenderPassShadowDirectional(this.device, this.shadowRenderer, light, camera, allCascadesRendering);\n\t\t\t\t\t\tthis.shadowRenderer.setupRenderPass(renderPass, shadowCamera, allCascadesRendering);\n\t\t\t\t}\n\t\t\t\treturn renderPass;\n\t\t}\n}\n\nconst tempSet$1 = new Set();\nconst shadowCamView = new Mat4();\nconst shadowCamViewProj = new Mat4();\nconst pixelOffset = new Float32Array(2);\nconst blurScissorRect = new Vec4(1, 1, 0, 0);\nconst viewportMatrix = new Mat4();\nfunction gauss(x, sigma) {\n\t\treturn Math.exp(-(x * x) / (2.0 * sigma * sigma));\n}\nfunction gaussWeights(kernelSize) {\n\t\tconst sigma = (kernelSize - 1) / (2 * 3);\n\t\tconst halfWidth = (kernelSize - 1) * 0.5;\n\t\tconst values = new Array(kernelSize);\n\t\tlet sum = 0.0;\n\t\tfor(let i = 0; i < kernelSize; ++i){\n\t\t\t\tvalues[i] = gauss(i - halfWidth, sigma);\n\t\t\t\tsum += values[i];\n\t\t}\n\t\tfor(let i = 0; i < kernelSize; ++i){\n\t\t\t\tvalues[i] /= sum;\n\t\t}\n\t\treturn values;\n}\nclass ShadowRenderer {\n\t\tconstructor(renderer, lightTextureAtlas){\n\t\t\t\tthis.shadowPassCache = [];\n\t\t\t\tthis.device = renderer.device;\n\t\t\t\tthis.renderer = renderer;\n\t\t\t\tthis.lightTextureAtlas = lightTextureAtlas;\n\t\t\t\tconst scope = this.device.scope;\n\t\t\t\tthis.sourceId = scope.resolve('source');\n\t\t\t\tthis.pixelOffsetId = scope.resolve('pixelOffset');\n\t\t\t\tthis.weightId = scope.resolve('weight[0]');\n\t\t\t\tthis.blurVsmShader = [\n\t\t\t\t\t\t{},\n\t\t\t\t\t\t{}\n\t\t\t\t];\n\t\t\t\tthis.blurVsmWeights = {};\n\t\t\t\tthis.shadowMapLightRadiusId = scope.resolve('light_radius');\n\t\t\t\tthis.viewUniformFormat = null;\n\t\t\t\tthis.viewBindGroupFormat = null;\n\t\t\t\tthis.blendStateWrite = new BlendState();\n\t\t\t\tthis.blendStateNoWrite = new BlendState();\n\t\t\t\tthis.blendStateNoWrite.setColorWrite(false, false, false, false);\n\t\t}\n\t\tstatic createShadowCamera(shadowType, type, face) {\n\t\t\t\tconst shadowCam = LightCamera.create('ShadowCamera', type, face);\n\t\t\t\tconst shadowInfo = shadowTypeInfo.get(shadowType);\n\t\t\t\tconst isVsm = shadowInfo?.vsm ?? false;\n\t\t\t\tconst isPcf = shadowInfo?.pcf ?? false;\n\t\t\t\tif (isVsm) {\n\t\t\t\t\t\tshadowCam.clearColor = new Color(0, 0, 0, 0);\n\t\t\t\t} else {\n\t\t\t\t\t\tshadowCam.clearColor = new Color(1, 1, 1, 1);\n\t\t\t\t}\n\t\t\t\tshadowCam.clearDepthBuffer = true;\n\t\t\t\tshadowCam.clearStencilBuffer = false;\n\t\t\t\tshadowCam.clearColorBuffer = !isPcf;\n\t\t\t\treturn shadowCam;\n\t\t}\n\t\t_cullShadowCastersInternal(meshInstances, visible, camera) {\n\t\t\t\tconst numInstances = meshInstances.length;\n\t\t\t\tfor(let i = 0; i < numInstances; i++){\n\t\t\t\t\t\tconst meshInstance = meshInstances[i];\n\t\t\t\t\t\tif (meshInstance.castShadow) {\n\t\t\t\t\t\t\t\tif (!meshInstance.cull || meshInstance._isVisible(camera)) {\n\t\t\t\t\t\t\t\t\t\tmeshInstance.visibleThisFrame = true;\n\t\t\t\t\t\t\t\t\t\tvisible.push(meshInstance);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcullShadowCasters(comp, light, visible, camera, casters) {\n\t\t\t\tthis.renderer.scene?.fire(EVENT_PRECULL, camera);\n\t\t\t\tvisible.length = 0;\n\t\t\t\tif (casters) {\n\t\t\t\t\t\tthis._cullShadowCastersInternal(casters, visible, camera);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst layers = comp.layerList;\n\t\t\t\t\t\tconst len = layers.length;\n\t\t\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\t\t\tconst layer = layers[i];\n\t\t\t\t\t\t\t\tif (layer._lightsSet.has(light)) {\n\t\t\t\t\t\t\t\t\t\tif (!tempSet$1.has(layer)) {\n\t\t\t\t\t\t\t\t\t\t\t\ttempSet$1.add(layer);\n\t\t\t\t\t\t\t\t\t\t\t\tthis._cullShadowCastersInternal(layer.shadowCasters, visible, camera);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttempSet$1.clear();\n\t\t\t\t}\n\t\t\t\tvisible.sort(this.sortCompareShader);\n\t\t\t\tthis.renderer.scene?.fire(EVENT_POSTCULL, camera);\n\t\t}\n\t\tsortCompareShader(drawCallA, drawCallB) {\n\t\t\t\tconst keyA = drawCallA._sortKeyShadow;\n\t\t\t\tconst keyB = drawCallB._sortKeyShadow;\n\t\t\t\tif (keyA === keyB) {\n\t\t\t\t\t\treturn drawCallB.mesh.id - drawCallA.mesh.id;\n\t\t\t\t}\n\t\t\t\treturn keyB - keyA;\n\t\t}\n\t\tsetupRenderState(device, light) {\n\t\t\t\tconst isClustered = this.renderer.scene.clusteredLightingEnabled;\n\t\t\t\tconst useShadowSampler = isClustered ? light._isPcf : light._isPcf && light._type !== LIGHTTYPE_OMNI;\n\t\t\t\tdevice.setBlendState(useShadowSampler ? this.blendStateNoWrite : this.blendStateWrite);\n\t\t\t\tdevice.setDepthState(light.shadowDepthState);\n\t\t\t\tdevice.setStencilState(null, null);\n\t\t}\n\t\tdispatchUniforms(light, shadowCam, lightRenderData, face) {\n\t\t\t\tconst shadowCamNode = shadowCam._node;\n\t\t\t\tif (light._type !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\tthis.renderer.dispatchViewPos(shadowCamNode.getPosition());\n\t\t\t\t\t\tthis.shadowMapLightRadiusId.setValue(light.attenuationEnd);\n\t\t\t\t}\n\t\t\t\tshadowCamView.setTRS(shadowCamNode.getPosition(), shadowCamNode.getRotation(), Vec3.ONE).invert();\n\t\t\t\tshadowCamViewProj.mul2(shadowCam.projectionMatrix, shadowCamView);\n\t\t\t\tconst rectViewport = lightRenderData.shadowViewport;\n\t\t\t\tshadowCam.rect = rectViewport;\n\t\t\t\tshadowCam.scissorRect = lightRenderData.shadowScissor;\n\t\t\t\tviewportMatrix.setViewport(rectViewport.x, rectViewport.y, rectViewport.z, rectViewport.w);\n\t\t\t\tlightRenderData.shadowMatrix.mul2(viewportMatrix, shadowCamViewProj);\n\t\t\t\tif (light._type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\tlight._shadowMatrixPalette.set(lightRenderData.shadowMatrix.data, face * 16);\n\t\t\t\t}\n\t\t}\n\t\tgetShadowPass(light) {\n\t\t\t\tconst lightType = light._type;\n\t\t\t\tconst shadowType = light._shadowType;\n\t\t\t\tlet shadowPassInfo = this.shadowPassCache[lightType]?.[shadowType];\n\t\t\t\tif (!shadowPassInfo) {\n\t\t\t\t\t\tconst shadowPassName = `ShadowPass_${lightType}_${shadowType}`;\n\t\t\t\t\t\tshadowPassInfo = ShaderPass.get(this.device).allocate(shadowPassName, {\n\t\t\t\t\t\t\t\tisShadow: true,\n\t\t\t\t\t\t\t\tlightType: lightType,\n\t\t\t\t\t\t\t\tshadowType: shadowType\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!this.shadowPassCache[lightType]) {\n\t\t\t\t\t\t\t\tthis.shadowPassCache[lightType] = [];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.shadowPassCache[lightType][shadowType] = shadowPassInfo;\n\t\t\t\t}\n\t\t\t\treturn shadowPassInfo.index;\n\t\t}\n\t\tsubmitCasters(visibleCasters, light, camera) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst renderer = this.renderer;\n\t\t\t\tconst scene = renderer.scene;\n\t\t\t\tconst passFlags = 1 << SHADER_SHADOW;\n\t\t\t\tconst shadowPass = this.getShadowPass(light);\n\t\t\t\tconst cameraShaderParams = camera.shaderParams;\n\t\t\t\tconst flipFactor = camera.renderTarget.flipY ? -1 : 1;\n\t\t\t\tconst count = visibleCasters.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst meshInstance = visibleCasters[i];\n\t\t\t\t\t\tconst mesh = meshInstance.mesh;\n\t\t\t\t\t\tconst instancingData = meshInstance.instancingData;\n\t\t\t\t\t\tif (instancingData && instancingData.count <= 0) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmeshInstance.ensureMaterial(device);\n\t\t\t\t\t\tconst material = meshInstance.material;\n\t\t\t\t\t\trenderer.setBaseConstants(device, material);\n\t\t\t\t\t\trenderer.setSkinning(device, meshInstance);\n\t\t\t\t\t\tif (material.dirty) {\n\t\t\t\t\t\t\t\tmaterial.updateUniforms(device, scene);\n\t\t\t\t\t\t\t\tmaterial.dirty = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\trenderer.setupCullMode(true, flipFactor, meshInstance);\n\t\t\t\t\t\tmaterial.setParameters(device);\n\t\t\t\t\t\tmeshInstance.setParameters(device, passFlags);\n\t\t\t\t\t\tconst shaderInstance = meshInstance.getShaderInstance(shadowPass, 0, scene, cameraShaderParams, this.viewUniformFormat, this.viewBindGroupFormat);\n\t\t\t\t\t\tconst shadowShader = shaderInstance.shader;\n\t\t\t\t\t\tif (shadowShader.failed) continue;\n\t\t\t\t\t\tmeshInstance._sortKeyShadow = shadowShader.id;\n\t\t\t\t\t\tdevice.setShader(shadowShader);\n\t\t\t\t\t\trenderer.setVertexBuffers(device, mesh);\n\t\t\t\t\t\trenderer.setMorphing(device, meshInstance.morphInstance);\n\t\t\t\t\t\tif (instancingData) {\n\t\t\t\t\t\t\t\tdevice.setVertexBuffer(instancingData.vertexBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\trenderer.setMeshInstanceMatrices(meshInstance);\n\t\t\t\t\t\trenderer.setupMeshUniformBuffers(shaderInstance);\n\t\t\t\t\t\tconst style = meshInstance.renderStyle;\n\t\t\t\t\t\tconst indirectSlot = meshInstance.indirectData?.get(camera);\n\t\t\t\t\t\tdevice.draw(mesh.primitive[style], mesh.indexBuffer[style], instancingData?.count, indirectSlot);\n\t\t\t\t\t\trenderer._shadowDrawCalls++;\n\t\t\t\t\t\tif (instancingData) {\n\t\t\t\t\t\t\t\trenderer._instancedDrawCalls++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tneedsShadowRendering(light) {\n\t\t\t\tconst needs = light.enabled && light.castShadows && light.shadowUpdateMode !== SHADOWUPDATE_NONE && light.visibleThisFrame;\n\t\t\t\tif (light.shadowUpdateMode === SHADOWUPDATE_THISFRAME) {\n\t\t\t\t\t\tlight.shadowUpdateMode = SHADOWUPDATE_NONE;\n\t\t\t\t}\n\t\t\t\tif (needs) {\n\t\t\t\t\t\tthis.renderer._shadowMapUpdates += light.numShadowFaces;\n\t\t\t\t}\n\t\t\t\treturn needs;\n\t\t}\n\t\tgetLightRenderData(light, camera, face) {\n\t\t\t\treturn light.getRenderData(light._type === LIGHTTYPE_DIRECTIONAL ? camera : null, face);\n\t\t}\n\t\tsetupRenderPass(renderPass, shadowCamera, clearRenderTarget) {\n\t\t\t\tconst rt = shadowCamera.renderTarget;\n\t\t\t\trenderPass.init(rt);\n\t\t\t\trenderPass.depthStencilOps.clearDepthValue = 1;\n\t\t\t\trenderPass.depthStencilOps.clearDepth = clearRenderTarget;\n\t\t\t\tif (rt.depthBuffer) {\n\t\t\t\t\t\trenderPass.depthStencilOps.storeDepth = true;\n\t\t\t\t} else {\n\t\t\t\t\t\trenderPass.colorOps.clearValue.copy(shadowCamera.clearColor);\n\t\t\t\t\t\trenderPass.colorOps.clear = clearRenderTarget;\n\t\t\t\t\t\trenderPass.depthStencilOps.storeDepth = false;\n\t\t\t\t}\n\t\t\t\trenderPass.requiresCubemaps = false;\n\t\t}\n\t\tprepareFace(light, camera, face) {\n\t\t\t\tconst type = light._type;\n\t\t\t\tconst lightRenderData = this.getLightRenderData(light, camera, face);\n\t\t\t\tconst shadowCam = lightRenderData.shadowCamera;\n\t\t\t\tconst renderTargetIndex = type === LIGHTTYPE_DIRECTIONAL ? 0 : face;\n\t\t\t\tshadowCam.renderTarget = light._shadowMap.renderTargets[renderTargetIndex];\n\t\t\t\treturn shadowCam;\n\t\t}\n\t\trenderFace(light, camera, face, clear, insideRenderPass = true) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst lightRenderData = this.getLightRenderData(light, camera, face);\n\t\t\t\tconst shadowCam = lightRenderData.shadowCamera;\n\t\t\t\tthis.dispatchUniforms(light, shadowCam, lightRenderData, face);\n\t\t\t\tconst rt = shadowCam.renderTarget;\n\t\t\t\tconst renderer = this.renderer;\n\t\t\t\trenderer.setCameraUniforms(shadowCam, rt);\n\t\t\t\tif (device.supportsUniformBuffers) {\n\t\t\t\t\t\trenderer.setupViewUniformBuffers(lightRenderData.viewBindGroups, this.viewUniformFormat, this.viewBindGroupFormat, null);\n\t\t\t\t}\n\t\t\t\tif (insideRenderPass) {\n\t\t\t\t\t\trenderer.setupViewport(shadowCam, rt);\n\t\t\t\t\t\tif (clear) {\n\t\t\t\t\t\t\t\trenderer.clear(shadowCam);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\trenderer.clearView(shadowCam, rt, true, false);\n\t\t\t\t}\n\t\t\t\tthis.setupRenderState(device, light);\n\t\t\t\tthis.submitCasters(lightRenderData.visibleCasters, light, shadowCam);\n\t\t}\n\t\trender(light, camera, insideRenderPass = true) {\n\t\t\t\tif (this.needsShadowRendering(light)) {\n\t\t\t\t\t\tconst faceCount = light.numShadowFaces;\n\t\t\t\t\t\tfor(let face = 0; face < faceCount; face++){\n\t\t\t\t\t\t\t\tthis.prepareFace(light, camera, face);\n\t\t\t\t\t\t\t\tthis.renderFace(light, camera, face, true, insideRenderPass);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.renderVsm(light, camera);\n\t\t\t\t}\n\t\t}\n\t\trenderVsm(light, camera) {\n\t\t\t\tif (light._isVsm && light._vsmBlurSize > 1) {\n\t\t\t\t\t\tconst isClustered = this.renderer.scene.clusteredLightingEnabled;\n\t\t\t\t\t\tif (!isClustered || light._type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\tthis.applyVsmBlur(light, camera);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgetVsmBlurShader(blurMode, filterSize) {\n\t\t\t\tconst cache = this.blurVsmShader;\n\t\t\t\tlet blurShader = cache[blurMode][filterSize];\n\t\t\t\tif (!blurShader) {\n\t\t\t\t\t\tthis.blurVsmWeights[filterSize] = gaussWeights(filterSize);\n\t\t\t\t\t\tconst defines = new Map();\n\t\t\t\t\t\tdefines.set('{SAMPLES}', filterSize);\n\t\t\t\t\t\tif (blurMode === 1) defines.set('GAUSS', '');\n\t\t\t\t\t\tblurShader = ShaderUtils.createShader(this.device, {\n\t\t\t\t\t\t\t\tuniqueName: `blurVsm${blurMode}${filterSize}`,\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvertexChunk: 'fullscreenQuadVS',\n\t\t\t\t\t\t\t\tfragmentChunk: 'blurVSMPS',\n\t\t\t\t\t\t\t\tfragmentDefines: defines\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcache[blurMode][filterSize] = blurShader;\n\t\t\t\t}\n\t\t\t\treturn blurShader;\n\t\t}\n\t\tapplyVsmBlur(light, camera) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tconst lightRenderData = light.getRenderData(light._type === LIGHTTYPE_DIRECTIONAL ? camera : null, 0);\n\t\t\t\tconst shadowCam = lightRenderData.shadowCamera;\n\t\t\t\tconst origShadowMap = shadowCam.renderTarget;\n\t\t\t\tconst tempShadowMap = this.renderer.shadowMapCache.get(device, light);\n\t\t\t\tconst tempRt = tempShadowMap.renderTargets[0];\n\t\t\t\tconst blurMode = light.vsmBlurMode;\n\t\t\t\tconst filterSize = light._vsmBlurSize;\n\t\t\t\tconst blurShader = this.getVsmBlurShader(blurMode, filterSize);\n\t\t\t\tblurScissorRect.z = light._shadowResolution - 2;\n\t\t\t\tblurScissorRect.w = blurScissorRect.z;\n\t\t\t\tthis.sourceId.setValue(origShadowMap.colorBuffer);\n\t\t\t\tpixelOffset[0] = 1 / light._shadowResolution;\n\t\t\t\tpixelOffset[1] = 0;\n\t\t\t\tthis.pixelOffsetId.setValue(pixelOffset);\n\t\t\t\tif (blurMode === BLUR_GAUSSIAN) this.weightId.setValue(this.blurVsmWeights[filterSize]);\n\t\t\t\tdrawQuadWithShader(device, tempRt, blurShader, null, blurScissorRect);\n\t\t\t\tthis.sourceId.setValue(tempRt.colorBuffer);\n\t\t\t\tpixelOffset[1] = pixelOffset[0];\n\t\t\t\tpixelOffset[0] = 0;\n\t\t\t\tthis.pixelOffsetId.setValue(pixelOffset);\n\t\t\t\tdrawQuadWithShader(device, origShadowMap, blurShader, null, blurScissorRect);\n\t\t\t\tthis.renderer.shadowMapCache.add(light, tempShadowMap);\n\t\t}\n\t\tinitViewBindGroupFormat() {\n\t\t\t\tif (this.device.supportsUniformBuffers && !this.viewUniformFormat) {\n\t\t\t\t\t\tthis.viewUniformFormat = new UniformBufferFormat(this.device, [\n\t\t\t\t\t\t\t\tnew UniformFormat('matrix_viewProjection', UNIFORMTYPE_MAT4)\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis.viewBindGroupFormat = new BindGroupFormat(this.device, [\n\t\t\t\t\t\t\t\tnew BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\tframeUpdate() {\n\t\t\t\tthis.initViewBindGroupFormat();\n\t\t}\n}\n\nconst tempClusterArray = [];\nclass WorldClustersAllocator {\n\t\tconstructor(graphicsDevice){\n\t\t\t\tthis._empty = null;\n\t\t\t\tthis._allocated = [];\n\t\t\t\tthis._clusters = new Map();\n\t\t\t\tthis.device = graphicsDevice;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this._empty) {\n\t\t\t\t\t\tthis._empty.destroy();\n\t\t\t\t\t\tthis._empty = null;\n\t\t\t\t}\n\t\t\t\tthis._allocated.forEach((cluster)=>{\n\t\t\t\t\t\tcluster.destroy();\n\t\t\t\t});\n\t\t\t\tthis._allocated.length = 0;\n\t\t}\n\t\tget count() {\n\t\t\t\treturn this._allocated.length;\n\t\t}\n\t\tget empty() {\n\t\t\t\tif (!this._empty) {\n\t\t\t\t\t\tconst empty = new WorldClusters(this.device);\n\t\t\t\t\t\tempty.name = 'ClusterEmpty';\n\t\t\t\t\t\tempty.update([]);\n\t\t\t\t\t\tthis._empty = empty;\n\t\t\t\t}\n\t\t\t\treturn this._empty;\n\t\t}\n\t\tassign(renderPasses) {\n\t\t\t\ttempClusterArray.push(...this._allocated);\n\t\t\t\tthis._allocated.length = 0;\n\t\t\t\tthis._clusters.clear();\n\t\t\t\tconst passCount = renderPasses.length;\n\t\t\t\tfor(let p = 0; p < passCount; p++){\n\t\t\t\t\t\tconst renderPass = renderPasses[p];\n\t\t\t\t\t\tconst renderActions = renderPass.renderActions;\n\t\t\t\t\t\tif (renderActions) {\n\t\t\t\t\t\t\t\tconst count = renderActions.length;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\t\t\tconst ra = renderActions[i];\n\t\t\t\t\t\t\t\t\t\tra.lightClusters = null;\n\t\t\t\t\t\t\t\t\t\tconst layer = ra.layer;\n\t\t\t\t\t\t\t\t\t\tif (layer.hasClusteredLights && layer.meshInstances.length) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst hash = layer.getLightIdHash();\n\t\t\t\t\t\t\t\t\t\t\t\tconst existingRenderAction = this._clusters.get(hash);\n\t\t\t\t\t\t\t\t\t\t\t\tlet clusters = existingRenderAction?.lightClusters;\n\t\t\t\t\t\t\t\t\t\t\t\tif (!clusters) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclusters = tempClusterArray.pop() ?? new WorldClusters(this.device);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._allocated.push(clusters);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._clusters.set(hash, ra);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tra.lightClusters = clusters;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!ra.lightClusters) {\n\t\t\t\t\t\t\t\t\t\t\t\tra.lightClusters = this.empty;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttempClusterArray.forEach((item)=>item.destroy());\n\t\t\t\ttempClusterArray.length = 0;\n\t\t}\n\t\tupdate(renderPasses, lighting) {\n\t\t\t\tthis.assign(renderPasses);\n\t\t\t\tthis._clusters.forEach((renderAction)=>{\n\t\t\t\t\t\tconst layer = renderAction.layer;\n\t\t\t\t\t\tconst cluster = renderAction.lightClusters;\n\t\t\t\t\t\tcluster.update(layer.clusteredLightsSet, lighting);\n\t\t\t\t});\n\t\t}\n}\n\nconst _viewport = new Vec4();\nconst _invViewProjMatrices = [];\nclass RenderPassCookieRenderer extends RenderPass {\n\t\tconstructor(device, cubeSlotsOffsets){\n\t\t\t\tsuper(device), this._quadRenderer2D = null, this._quadRendererCube = null, this._filteredLights = [], this._forceCopy = false, this._evtDeviceRestored = null;\n\t\t\t\tthis._cubeSlotsOffsets = cubeSlotsOffsets;\n\t\t\t\tthis.requiresCubemaps = false;\n\t\t\t\tthis.blitTextureId = device.scope.resolve('blitTexture');\n\t\t\t\tthis.invViewProjId = device.scope.resolve('invViewProj');\n\t\t\t\tthis._evtDeviceRestored = device.on('devicerestored', this.onDeviceRestored, this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._quadRenderer2D?.destroy();\n\t\t\t\tthis._quadRenderer2D = null;\n\t\t\t\tthis._quadRendererCube?.destroy();\n\t\t\t\tthis._quadRendererCube = null;\n\t\t\t\tthis._evtDeviceRestored?.off();\n\t\t\t\tthis._evtDeviceRestored = null;\n\t\t}\n\t\tstatic create(renderTarget, cubeSlotsOffsets) {\n\t\t\t\tconst renderPass = new RenderPassCookieRenderer(renderTarget.device, cubeSlotsOffsets);\n\t\t\t\trenderPass.init(renderTarget);\n\t\t\t\trenderPass.colorOps.clear = false;\n\t\t\t\trenderPass.depthStencilOps.clearDepth = false;\n\t\t\t\treturn renderPass;\n\t\t}\n\t\tonDeviceRestored() {\n\t\t\t\tthis._forceCopy = true;\n\t\t}\n\t\tupdate(lights) {\n\t\t\t\tconst filteredLights = this._filteredLights;\n\t\t\t\tthis.filter(lights, filteredLights);\n\t\t\t\tthis.executeEnabled = filteredLights.length > 0;\n\t\t}\n\t\tfilter(lights, filteredLights) {\n\t\t\t\tfor(let i = 0; i < lights.length; i++){\n\t\t\t\t\t\tconst light = lights[i];\n\t\t\t\t\t\tif (light._type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!light.atlasViewportAllocated) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!light.atlasSlotUpdated && !this._forceCopy) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (light.enabled && light.cookie && light.visibleThisFrame) {\n\t\t\t\t\t\t\t\tfilteredLights.push(light);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._forceCopy = false;\n\t\t}\n\t\tinitInvViewProjMatrices() {\n\t\t\t\tif (!_invViewProjMatrices.length) {\n\t\t\t\t\t\tfor(let face = 0; face < 6; face++){\n\t\t\t\t\t\t\t\tconst camera = LightCamera.create(null, LIGHTTYPE_OMNI, face);\n\t\t\t\t\t\t\t\tconst projMat = camera.projectionMatrix;\n\t\t\t\t\t\t\t\tconst viewMat = camera.node.getLocalTransform().clone().invert();\n\t\t\t\t\t\t\t\t_invViewProjMatrices[face] = new Mat4().mul2(projMat, viewMat).invert();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget quadRenderer2D() {\n\t\t\t\tif (!this._quadRenderer2D) {\n\t\t\t\t\t\tconst shader = ShaderUtils.createShader(this.device, {\n\t\t\t\t\t\t\t\tuniqueName: 'cookieRenderer2d',\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvertexChunk: 'cookieBlitVS',\n\t\t\t\t\t\t\t\tfragmentChunk: 'cookieBlit2DPS'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis._quadRenderer2D = new QuadRender(shader);\n\t\t\t\t}\n\t\t\t\treturn this._quadRenderer2D;\n\t\t}\n\t\tget quadRendererCube() {\n\t\t\t\tif (!this._quadRendererCube) {\n\t\t\t\t\t\tconst shader = ShaderUtils.createShader(this.device, {\n\t\t\t\t\t\t\t\tuniqueName: 'cookieRendererCube',\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvertexChunk: 'cookieBlitVS',\n\t\t\t\t\t\t\t\tfragmentChunk: 'cookieBlitCubePS'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis._quadRendererCube = new QuadRender(shader);\n\t\t\t\t}\n\t\t\t\treturn this._quadRendererCube;\n\t\t}\n\t\texecute() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\tdevice.setStencilState();\n\t\t\t\tconst renderTargetWidth = this.renderTarget.colorBuffer.width;\n\t\t\t\tconst cubeSlotsOffsets = this._cubeSlotsOffsets;\n\t\t\t\tconst filteredLights = this._filteredLights;\n\t\t\t\tfor(let i = 0; i < filteredLights.length; i++){\n\t\t\t\t\t\tconst light = filteredLights[i];\n\t\t\t\t\t\tconst faceCount = light.numShadowFaces;\n\t\t\t\t\t\tconst quad = faceCount > 1 ? this.quadRendererCube : this.quadRenderer2D;\n\t\t\t\t\t\tif (faceCount > 1) {\n\t\t\t\t\t\t\t\tthis.initInvViewProjMatrices();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.blitTextureId.setValue(light.cookie);\n\t\t\t\t\t\tfor(let face = 0; face < faceCount; face++){\n\t\t\t\t\t\t\t\t_viewport.copy(light.atlasViewport);\n\t\t\t\t\t\t\t\tif (faceCount > 1) {\n\t\t\t\t\t\t\t\t\t\tconst smallSize = _viewport.z / 3;\n\t\t\t\t\t\t\t\t\t\tconst offset = cubeSlotsOffsets[face];\n\t\t\t\t\t\t\t\t\t\t_viewport.x += smallSize * offset.x;\n\t\t\t\t\t\t\t\t\t\t_viewport.y += smallSize * offset.y;\n\t\t\t\t\t\t\t\t\t\t_viewport.z = smallSize;\n\t\t\t\t\t\t\t\t\t\t_viewport.w = smallSize;\n\t\t\t\t\t\t\t\t\t\tthis.invViewProjId.setValue(_invViewProjMatrices[face].data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t_viewport.mulScalar(renderTargetWidth);\n\t\t\t\t\t\t\t\tquad.render(_viewport);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfilteredLights.length = 0;\n\t\t}\n}\n\nclass RenderPassShadowLocalClustered extends RenderPass {\n\t\tconstructor(device, shadowRenderer, shadowRendererLocal){\n\t\t\t\tsuper(device);\n\t\t\t\tthis.requiresCubemaps = false;\n\t\t\t\tthis.shadowRenderer = shadowRenderer;\n\t\t\t\tthis.shadowRendererLocal = shadowRendererLocal;\n\t\t}\n\t\tupdate(localLights) {\n\t\t\t\tconst shadowLights = this.shadowRendererLocal.shadowLights;\n\t\t\t\tconst shadowCamera = this.shadowRendererLocal.prepareLights(shadowLights, localLights);\n\t\t\t\tconst count = shadowLights.length;\n\t\t\t\tthis.enabled = count > 0;\n\t\t\t\tif (count) {\n\t\t\t\t\t\tthis.shadowRenderer.setupRenderPass(this, shadowCamera, false);\n\t\t\t\t}\n\t\t}\n\t\texecute() {\n\t\t\t\tconst shadowLights = this.shadowRendererLocal.shadowLights;\n\t\t\t\tconst count = shadowLights.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst light = shadowLights[i];\n\t\t\t\t\t\tfor(let face = 0; face < light.numShadowFaces; face++){\n\t\t\t\t\t\t\t\tthis.shadowRenderer.renderFace(light, null, face, true);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tshadowLights.length = 0;\n\t\t}\n}\n\nclass RenderPassUpdateClustered extends RenderPass {\n\t\tconstructor(device, renderer, shadowRenderer, shadowRendererLocal, lightTextureAtlas){\n\t\t\t\tsuper(device);\n\t\t\t\tthis.renderer = renderer;\n\t\t\t\tthis.frameGraph = null;\n\t\t\t\tthis.cookiesRenderPass = RenderPassCookieRenderer.create(lightTextureAtlas.cookieRenderTarget, lightTextureAtlas.cubeSlotsOffsets);\n\t\t\t\tthis.beforePasses.push(this.cookiesRenderPass);\n\t\t\t\tthis.shadowRenderPass = new RenderPassShadowLocalClustered(device, shadowRenderer, shadowRendererLocal);\n\t\t\t\tthis.beforePasses.push(this.shadowRenderPass);\n\t\t}\n\t\tupdate(frameGraph, shadowsEnabled, cookiesEnabled, lights, localLights) {\n\t\t\t\tthis.frameGraph = frameGraph;\n\t\t\t\tthis.cookiesRenderPass.enabled = cookiesEnabled;\n\t\t\t\tif (cookiesEnabled) {\n\t\t\t\t\t\tthis.cookiesRenderPass.update(lights);\n\t\t\t\t}\n\t\t\t\tthis.shadowRenderPass.enabled = shadowsEnabled;\n\t\t\t\tif (shadowsEnabled) {\n\t\t\t\t\t\tthis.shadowRenderPass.update(localLights);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.cookiesRenderPass.destroy();\n\t\t\t\tthis.cookiesRenderPass = null;\n\t\t}\n\t\texecute() {\n\t\t\t\tconst { renderer } = this;\n\t\t\t\tconst { scene } = renderer;\n\t\t\t\trenderer.worldClustersAllocator.update(this.frameGraph.renderPasses, scene.lighting);\n\t\t}\n}\n\nlet _skinUpdateIndex = 0;\nconst viewProjMat = new Mat4();\nconst viewInvMat = new Mat4();\nconst viewMat = new Mat4();\nconst viewMat3 = new Mat3();\nconst tempSphere$1 = new BoundingSphere();\nconst _flipYMat = new Mat4().setScale(1, -1, 1);\nconst _tempLightSet = new Set();\nconst _tempLayerSet = new Set();\nconst _dynamicBindGroup = new DynamicBindGroup();\nconst _fixProjRangeMat = new Mat4().set([\n\t\t1,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0.5,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0.5,\n\t\t1\n]);\nconst _haltonSequence = [\n\t\tnew Vec2(0.5, 0.333333),\n\t\tnew Vec2(0.25, 0.666667),\n\t\tnew Vec2(0.75, 0.111111),\n\t\tnew Vec2(0.125, 0.444444),\n\t\tnew Vec2(0.625, 0.777778),\n\t\tnew Vec2(0.375, 0.222222),\n\t\tnew Vec2(0.875, 0.555556),\n\t\tnew Vec2(0.0625, 0.888889),\n\t\tnew Vec2(0.5625, 0.037037),\n\t\tnew Vec2(0.3125, 0.370370),\n\t\tnew Vec2(0.8125, 0.703704),\n\t\tnew Vec2(0.1875, 0.148148),\n\t\tnew Vec2(0.6875, 0.481481),\n\t\tnew Vec2(0.4375, 0.814815),\n\t\tnew Vec2(0.9375, 0.259259),\n\t\tnew Vec2(0.03125, 0.592593)\n];\nconst _tempProjMat0 = new Mat4();\nconst _tempProjMat1 = new Mat4();\nconst _tempProjMat2 = new Mat4();\nconst _tempProjMat3 = new Mat4();\nconst _tempProjMat4 = new Mat4();\nconst _tempProjMat5 = new Mat4();\nconst _tempSet = new Set();\nconst _tempMeshInstances = [];\nconst _tempMeshInstancesSkinned = [];\nclass Renderer {\n\t\tconstructor(graphicsDevice, scene){\n\t\t\t\tthis.clustersDebugRendered = false;\n\t\t\t\tthis.processingMeshInstances = new Set();\n\t\t\t\tthis.lights = [];\n\t\t\t\tthis.localLights = [];\n\t\t\t\tthis.cameraDirShadowLights = new Map();\n\t\t\t\tthis.dirLightShadows = new Map();\n\t\t\t\tthis.blueNoise = new BlueNoise(123);\n\t\t\t\tthis.gsplatDirector = null;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.scene = scene;\n\t\t\t\tthis.worldClustersAllocator = new WorldClustersAllocator(graphicsDevice);\n\t\t\t\tthis.lightTextureAtlas = new LightTextureAtlas(graphicsDevice);\n\t\t\t\tthis.shadowMapCache = new ShadowMapCache();\n\t\t\t\tthis.shadowRenderer = new ShadowRenderer(this, this.lightTextureAtlas);\n\t\t\t\tthis._shadowRendererLocal = new ShadowRendererLocal(this, this.shadowRenderer);\n\t\t\t\tthis._shadowRendererDirectional = new ShadowRendererDirectional(this, this.shadowRenderer);\n\t\t\t\tif (this.scene.clusteredLightingEnabled) {\n\t\t\t\t\t\tthis._renderPassUpdateClustered = new RenderPassUpdateClustered(this.device, this, this.shadowRenderer, this._shadowRendererLocal, this.lightTextureAtlas);\n\t\t\t\t}\n\t\t\t\tthis.viewUniformFormat = null;\n\t\t\t\tthis.viewBindGroupFormat = null;\n\t\t\t\tthis._skinTime = 0;\n\t\t\t\tthis._morphTime = 0;\n\t\t\t\tthis._cullTime = 0;\n\t\t\t\tthis._shadowMapTime = 0;\n\t\t\t\tthis._lightClustersTime = 0;\n\t\t\t\tthis._layerCompositionUpdateTime = 0;\n\t\t\t\tthis._shadowDrawCalls = 0;\n\t\t\t\tthis._skinDrawCalls = 0;\n\t\t\t\tthis._instancedDrawCalls = 0;\n\t\t\t\tthis._shadowMapUpdates = 0;\n\t\t\t\tthis._numDrawCallsCulled = 0;\n\t\t\t\tthis._camerasRendered = 0;\n\t\t\t\tthis._lightClusters = 0;\n\t\t\t\tconst scope = graphicsDevice.scope;\n\t\t\t\tthis.boneTextureId = scope.resolve('texture_poseMap');\n\t\t\t\tthis.modelMatrixId = scope.resolve('matrix_model');\n\t\t\t\tthis.normalMatrixId = scope.resolve('matrix_normal');\n\t\t\t\tthis.viewInvId = scope.resolve('matrix_viewInverse');\n\t\t\t\tthis.viewPos = new Float32Array(3);\n\t\t\t\tthis.viewPosId = scope.resolve('view_position');\n\t\t\t\tthis.projId = scope.resolve('matrix_projection');\n\t\t\t\tthis.projSkyboxId = scope.resolve('matrix_projectionSkybox');\n\t\t\t\tthis.viewId = scope.resolve('matrix_view');\n\t\t\t\tthis.viewId3 = scope.resolve('matrix_view3');\n\t\t\t\tthis.viewProjId = scope.resolve('matrix_viewProjection');\n\t\t\t\tthis.flipYId = scope.resolve('projectionFlipY');\n\t\t\t\tthis.tbnBasis = scope.resolve('tbnBasis');\n\t\t\t\tthis.nearClipId = scope.resolve('camera_near');\n\t\t\t\tthis.farClipId = scope.resolve('camera_far');\n\t\t\t\tthis.cameraParams = new Float32Array(4);\n\t\t\t\tthis.cameraParamsId = scope.resolve('camera_params');\n\t\t\t\tthis.viewIndexId = scope.resolve('view_index');\n\t\t\t\tthis.viewIndexId.setValue(0);\n\t\t\t\tthis.blueNoiseJitterVersion = 0;\n\t\t\t\tthis.blueNoiseJitterVec = new Vec4();\n\t\t\t\tthis.blueNoiseJitterData = new Float32Array(4);\n\t\t\t\tthis.blueNoiseJitterId = scope.resolve('blueNoiseJitter');\n\t\t\t\tthis.blueNoiseTextureId = scope.resolve('blueNoiseTex32');\n\t\t\t\tthis.alphaTestId = scope.resolve('alpha_ref');\n\t\t\t\tthis.opacityMapId = scope.resolve('texture_opacityMap');\n\t\t\t\tthis.exposureId = scope.resolve('exposure');\n\t\t\t\tthis.twoSidedLightingNegScaleFactorId = scope.resolve('twoSidedLightingNegScaleFactor');\n\t\t\t\tthis.twoSidedLightingNegScaleFactorId.setValue(0);\n\t\t\t\tthis.morphPositionTex = scope.resolve('morphPositionTex');\n\t\t\t\tthis.morphNormalTex = scope.resolve('morphNormalTex');\n\t\t\t\tthis.morphTexParams = scope.resolve('morph_tex_params');\n\t\t\t\tthis.lightCube = new LightCube();\n\t\t\t\tthis.constantLightCube = scope.resolve('lightCube[0]');\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.shadowRenderer = null;\n\t\t\t\tthis._shadowRendererLocal = null;\n\t\t\t\tthis._shadowRendererDirectional = null;\n\t\t\t\tthis.shadowMapCache.destroy();\n\t\t\t\tthis.shadowMapCache = null;\n\t\t\t\tthis._renderPassUpdateClustered?.destroy();\n\t\t\t\tthis._renderPassUpdateClustered = null;\n\t\t\t\tthis.lightTextureAtlas.destroy();\n\t\t\t\tthis.lightTextureAtlas = null;\n\t\t}\n\t\tsetupViewport(camera, renderTarget) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst pixelWidth = renderTarget ? renderTarget.width : device.width;\n\t\t\t\tconst pixelHeight = renderTarget ? renderTarget.height : device.height;\n\t\t\t\tconst rect = camera.rect;\n\t\t\t\tlet x = Math.floor(rect.x * pixelWidth);\n\t\t\t\tlet y = Math.floor(rect.y * pixelHeight);\n\t\t\t\tlet w = Math.floor(rect.z * pixelWidth);\n\t\t\t\tlet h = Math.floor(rect.w * pixelHeight);\n\t\t\t\tdevice.setViewport(x, y, w, h);\n\t\t\t\tif (camera._scissorRectClear) {\n\t\t\t\t\t\tconst scissorRect = camera.scissorRect;\n\t\t\t\t\t\tx = Math.floor(scissorRect.x * pixelWidth);\n\t\t\t\t\t\ty = Math.floor(scissorRect.y * pixelHeight);\n\t\t\t\t\t\tw = Math.floor(scissorRect.z * pixelWidth);\n\t\t\t\t\t\th = Math.floor(scissorRect.w * pixelHeight);\n\t\t\t\t}\n\t\t\t\tdevice.setScissor(x, y, w, h);\n\t\t}\n\t\tsetCameraUniforms(camera, target) {\n\t\t\t\tconst flipY = target?.flipY;\n\t\t\t\tlet viewList = null;\n\t\t\t\tif (camera.xr && camera.xr.session) {\n\t\t\t\t\t\tconst transform = camera._node?.parent?.getWorldTransform() || null;\n\t\t\t\t\t\tconst views = camera.xr.views;\n\t\t\t\t\t\tviewList = views.list;\n\t\t\t\t\t\tfor(let v = 0; v < viewList.length; v++){\n\t\t\t\t\t\t\t\tconst view = viewList[v];\n\t\t\t\t\t\t\t\tview.updateTransforms(transform);\n\t\t\t\t\t\t\t\tcamera.frustum.setFromMat4(view.projViewOffMat);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tlet projMat = camera.projectionMatrix;\n\t\t\t\t\t\tif (camera.calculateProjection) {\n\t\t\t\t\t\t\t\tcamera.calculateProjection(projMat, VIEW_CENTER);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet projMatSkybox = camera.getProjectionMatrixSkybox();\n\t\t\t\t\t\tif (flipY) {\n\t\t\t\t\t\t\t\tprojMat = _tempProjMat0.mul2(_flipYMat, projMat);\n\t\t\t\t\t\t\t\tprojMatSkybox = _tempProjMat1.mul2(_flipYMat, projMatSkybox);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.device.isWebGPU) {\n\t\t\t\t\t\t\t\tprojMat = _tempProjMat2.mul2(_fixProjRangeMat, projMat);\n\t\t\t\t\t\t\t\tprojMatSkybox = _tempProjMat3.mul2(_fixProjRangeMat, projMatSkybox);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst { jitter } = camera;\n\t\t\t\t\t\tlet jitterX = 0;\n\t\t\t\t\t\tlet jitterY = 0;\n\t\t\t\t\t\tif (jitter > 0) {\n\t\t\t\t\t\t\t\tconst targetWidth = target ? target.width : this.device.width;\n\t\t\t\t\t\t\t\tconst targetHeight = target ? target.height : this.device.height;\n\t\t\t\t\t\t\t\tconst offset = _haltonSequence[this.device.renderVersion % _haltonSequence.length];\n\t\t\t\t\t\t\t\tjitterX = jitter * (offset.x * 2 - 1) / targetWidth;\n\t\t\t\t\t\t\t\tjitterY = jitter * (offset.y * 2 - 1) / targetHeight;\n\t\t\t\t\t\t\t\tprojMat = _tempProjMat4.copy(projMat);\n\t\t\t\t\t\t\t\tprojMat.data[8] = jitterX;\n\t\t\t\t\t\t\t\tprojMat.data[9] = jitterY;\n\t\t\t\t\t\t\t\tprojMatSkybox = _tempProjMat5.copy(projMatSkybox);\n\t\t\t\t\t\t\t\tprojMatSkybox.data[8] = jitterX;\n\t\t\t\t\t\t\t\tprojMatSkybox.data[9] = jitterY;\n\t\t\t\t\t\t\t\tif (this.blueNoiseJitterVersion !== this.device.renderVersion) {\n\t\t\t\t\t\t\t\t\t\tthis.blueNoiseJitterVersion = this.device.renderVersion;\n\t\t\t\t\t\t\t\t\t\tthis.blueNoise.vec4(this.blueNoiseJitterVec);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst jitterVec = jitter > 0 ? this.blueNoiseJitterVec : Vec4.ZERO;\n\t\t\t\t\t\tthis.blueNoiseJitterData[0] = jitterVec.x;\n\t\t\t\t\t\tthis.blueNoiseJitterData[1] = jitterVec.y;\n\t\t\t\t\t\tthis.blueNoiseJitterData[2] = jitterVec.z;\n\t\t\t\t\t\tthis.blueNoiseJitterData[3] = jitterVec.w;\n\t\t\t\t\t\tthis.blueNoiseJitterId.setValue(this.blueNoiseJitterData);\n\t\t\t\t\t\tthis.projId.setValue(projMat.data);\n\t\t\t\t\t\tthis.projSkyboxId.setValue(projMatSkybox.data);\n\t\t\t\t\t\tif (camera.calculateTransform) {\n\t\t\t\t\t\t\t\tcamera.calculateTransform(viewInvMat, VIEW_CENTER);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst pos = camera._node.getPosition();\n\t\t\t\t\t\t\t\tconst rot = camera._node.getRotation();\n\t\t\t\t\t\t\t\tviewInvMat.setTRS(pos, rot, Vec3.ONE);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.viewInvId.setValue(viewInvMat.data);\n\t\t\t\t\t\tviewMat.copy(viewInvMat).invert();\n\t\t\t\t\t\tthis.viewId.setValue(viewMat.data);\n\t\t\t\t\t\tviewMat3.setFromMat4(viewMat);\n\t\t\t\t\t\tthis.viewId3.setValue(viewMat3.data);\n\t\t\t\t\t\tviewProjMat.mul2(projMat, viewMat);\n\t\t\t\t\t\tthis.viewProjId.setValue(viewProjMat.data);\n\t\t\t\t\t\tcamera._storeShaderMatrices(viewProjMat, jitterX, jitterY, this.device.renderVersion);\n\t\t\t\t\t\tthis.flipYId.setValue(flipY ? -1 : 1);\n\t\t\t\t\t\tthis.dispatchViewPos(camera._node.getPosition());\n\t\t\t\t\t\tcamera.frustum.setFromMat4(viewProjMat);\n\t\t\t\t}\n\t\t\t\tthis.tbnBasis.setValue(flipY ? -1 : 1);\n\t\t\t\tconst n = camera._nearClip;\n\t\t\t\tconst f = camera._farClip;\n\t\t\t\tthis.nearClipId.setValue(n);\n\t\t\t\tthis.farClipId.setValue(f);\n\t\t\t\tthis.cameraParams[0] = 1 / f;\n\t\t\t\tthis.cameraParams[1] = f;\n\t\t\t\tthis.cameraParams[2] = n;\n\t\t\t\tthis.cameraParams[3] = camera.projection === PROJECTION_ORTHOGRAPHIC ? 1 : 0;\n\t\t\t\tthis.cameraParamsId.setValue(this.cameraParams);\n\t\t\t\tthis.exposureId.setValue(this.scene.physicalUnits ? camera.getExposure() : this.scene.exposure);\n\t\t\t\treturn viewList;\n\t\t}\n\t\tclear(camera, clearColor, clearDepth, clearStencil) {\n\t\t\t\tconst flags = (clearColor ?? camera._clearColorBuffer ? CLEARFLAG_COLOR : 0) | (clearDepth ?? camera._clearDepthBuffer ? CLEARFLAG_DEPTH : 0) | (clearStencil ?? camera._clearStencilBuffer ? CLEARFLAG_STENCIL : 0);\n\t\t\t\tif (flags) {\n\t\t\t\t\t\tconst device = this.device;\n\t\t\t\t\t\tdevice.clear({\n\t\t\t\t\t\t\t\tcolor: [\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.r,\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.g,\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.b,\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.a\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdepth: camera._clearDepth,\n\t\t\t\t\t\t\t\tstencil: camera._clearStencil,\n\t\t\t\t\t\t\t\tflags: flags\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tsetCamera(camera, target, clear, renderAction = null) {\n\t\t\t\tthis.setCameraUniforms(camera, target);\n\t\t\t\tthis.clearView(camera, target, clear, false);\n\t\t}\n\t\tclearView(camera, target, clear, forceWrite) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tdevice.setRenderTarget(target);\n\t\t\t\tdevice.updateBegin();\n\t\t\t\tif (forceWrite) {\n\t\t\t\t\t\tdevice.setColorWrite(true, true, true, true);\n\t\t\t\t\t\tdevice.setDepthWrite(true);\n\t\t\t\t}\n\t\t\t\tthis.setupViewport(camera, target);\n\t\t\t\tif (clear) {\n\t\t\t\t\t\tconst options = camera._clearOptions;\n\t\t\t\t\t\tdevice.clear(options ? options : {\n\t\t\t\t\t\t\t\tcolor: [\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.r,\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.g,\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.b,\n\t\t\t\t\t\t\t\t\t\tcamera._clearColor.a\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdepth: camera._clearDepth,\n\t\t\t\t\t\t\t\tflags: (camera._clearColorBuffer ? CLEARFLAG_COLOR : 0) | (camera._clearDepthBuffer ? CLEARFLAG_DEPTH : 0) | (camera._clearStencilBuffer ? CLEARFLAG_STENCIL : 0),\n\t\t\t\t\t\t\t\tstencil: camera._clearStencil\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tsetupCullMode(cullFaces, flipFactor, drawCall) {\n\t\t\t\tconst material = drawCall.material;\n\t\t\t\tlet mode = CULLFACE_NONE;\n\t\t\t\tif (cullFaces) {\n\t\t\t\t\t\tlet flipFaces = 1;\n\t\t\t\t\t\tif (material.cull === CULLFACE_FRONT || material.cull === CULLFACE_BACK) {\n\t\t\t\t\t\t\t\tflipFaces = flipFactor * drawCall.flipFacesFactor * drawCall.node.worldScaleSign;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (flipFaces < 0) {\n\t\t\t\t\t\t\t\tmode = material.cull === CULLFACE_FRONT ? CULLFACE_BACK : CULLFACE_FRONT;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmode = material.cull;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.device.setCullMode(mode);\n\t\t\t\tif (mode === CULLFACE_NONE && material.cull === CULLFACE_NONE) {\n\t\t\t\t\t\tthis.twoSidedLightingNegScaleFactorId.setValue(drawCall.node.worldScaleSign);\n\t\t\t\t}\n\t\t}\n\t\tupdateCameraFrustum(camera) {\n\t\t\t\tif (camera.xr && camera.xr.views.list.length) {\n\t\t\t\t\t\tconst view = camera.xr.views.list[0];\n\t\t\t\t\t\tviewProjMat.mul2(view.projMat, view.viewOffMat);\n\t\t\t\t\t\tcamera.frustum.setFromMat4(viewProjMat);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst projMat = camera.projectionMatrix;\n\t\t\t\tif (camera.calculateProjection) {\n\t\t\t\t\t\tcamera.calculateProjection(projMat, VIEW_CENTER);\n\t\t\t\t}\n\t\t\t\tif (camera.calculateTransform) {\n\t\t\t\t\t\tcamera.calculateTransform(viewInvMat, VIEW_CENTER);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst pos = camera._node.getPosition();\n\t\t\t\t\t\tconst rot = camera._node.getRotation();\n\t\t\t\t\t\tviewInvMat.setTRS(pos, rot, Vec3.ONE);\n\t\t\t\t\t\tthis.viewInvId.setValue(viewInvMat.data);\n\t\t\t\t}\n\t\t\t\tviewMat.copy(viewInvMat).invert();\n\t\t\t\tviewProjMat.mul2(projMat, viewMat);\n\t\t\t\tcamera.frustum.setFromMat4(viewProjMat);\n\t\t}\n\t\tsetBaseConstants(device, material) {\n\t\t\t\tdevice.setCullMode(material.cull);\n\t\t\t\tif (material.opacityMap) {\n\t\t\t\t\t\tthis.opacityMapId.setValue(material.opacityMap);\n\t\t\t\t}\n\t\t\t\tif (material.opacityMap || material.alphaTest > 0) {\n\t\t\t\t\t\tthis.alphaTestId.setValue(material.alphaTest);\n\t\t\t\t}\n\t\t}\n\t\tupdateCpuSkinMatrices(drawCalls) {\n\t\t\t\t_skinUpdateIndex++;\n\t\t\t\tconst drawCallsCount = drawCalls.length;\n\t\t\t\tif (drawCallsCount === 0) return;\n\t\t\t\tfor(let i = 0; i < drawCallsCount; i++){\n\t\t\t\t\t\tconst si = drawCalls[i].skinInstance;\n\t\t\t\t\t\tif (si) {\n\t\t\t\t\t\t\t\tsi.updateMatrices(drawCalls[i].node, _skinUpdateIndex);\n\t\t\t\t\t\t\t\tsi._dirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdateGpuSkinMatrices(drawCalls) {\n\t\t\t\tfor (const drawCall of drawCalls){\n\t\t\t\t\t\tconst skin = drawCall.skinInstance;\n\t\t\t\t\t\tif (skin && skin._dirty) {\n\t\t\t\t\t\t\t\tskin.updateMatrixPalette(drawCall.node, _skinUpdateIndex);\n\t\t\t\t\t\t\t\tskin._dirty = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdateMorphing(drawCalls) {\n\t\t\t\tfor (const drawCall of drawCalls){\n\t\t\t\t\t\tconst morphInst = drawCall.morphInstance;\n\t\t\t\t\t\tif (morphInst && morphInst._dirty) {\n\t\t\t\t\t\t\t\tmorphInst.update();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdateGSplats(drawCalls) {\n\t\t\t\tfor (const drawCall of drawCalls){\n\t\t\t\t\t\tdrawCall.gsplatInstance?.update();\n\t\t\t\t}\n\t\t}\n\t\tgpuUpdate(drawCalls) {\n\t\t\t\tthis.updateGpuSkinMatrices(drawCalls);\n\t\t\t\tthis.updateMorphing(drawCalls);\n\t\t\t\tthis.updateGSplats(drawCalls);\n\t\t}\n\t\tsetVertexBuffers(device, mesh) {\n\t\t\t\tdevice.setVertexBuffer(mesh.vertexBuffer);\n\t\t}\n\t\tsetMorphing(device, morphInstance) {\n\t\t\t\tif (morphInstance) {\n\t\t\t\t\t\tmorphInstance.prepareRendering(device);\n\t\t\t\t\t\tdevice.setVertexBuffer(morphInstance.morph.vertexBufferIds);\n\t\t\t\t\t\tthis.morphPositionTex.setValue(morphInstance.texturePositions);\n\t\t\t\t\t\tthis.morphNormalTex.setValue(morphInstance.textureNormals);\n\t\t\t\t\t\tthis.morphTexParams.setValue(morphInstance._textureParams);\n\t\t\t\t}\n\t\t}\n\t\tsetSkinning(device, meshInstance) {\n\t\t\t\tconst skinInstance = meshInstance.skinInstance;\n\t\t\t\tif (skinInstance) {\n\t\t\t\t\t\tthis._skinDrawCalls++;\n\t\t\t\t\t\tconst boneTexture = skinInstance.boneTexture;\n\t\t\t\t\t\tthis.boneTextureId.setValue(boneTexture);\n\t\t\t\t}\n\t\t}\n\t\tdispatchViewPos(position) {\n\t\t\t\tconst vp = this.viewPos;\n\t\t\t\tvp[0] = position.x;\n\t\t\t\tvp[1] = position.y;\n\t\t\t\tvp[2] = position.z;\n\t\t\t\tthis.viewPosId.setValue(vp);\n\t\t}\n\t\tinitViewBindGroupFormat(isClustered) {\n\t\t\t\tif (this.device.supportsUniformBuffers && !this.viewUniformFormat) {\n\t\t\t\t\t\tconst uniforms = [\n\t\t\t\t\t\t\t\tnew UniformFormat('matrix_view', UNIFORMTYPE_MAT4),\n\t\t\t\t\t\t\t\tnew UniformFormat('matrix_viewInverse', UNIFORMTYPE_MAT4),\n\t\t\t\t\t\t\t\tnew UniformFormat('matrix_projection', UNIFORMTYPE_MAT4),\n\t\t\t\t\t\t\t\tnew UniformFormat('matrix_projectionSkybox', UNIFORMTYPE_MAT4),\n\t\t\t\t\t\t\t\tnew UniformFormat('matrix_viewProjection', UNIFORMTYPE_MAT4),\n\t\t\t\t\t\t\t\tnew UniformFormat('matrix_view3', UNIFORMTYPE_MAT3),\n\t\t\t\t\t\t\t\tnew UniformFormat('cubeMapRotationMatrix', UNIFORMTYPE_MAT3),\n\t\t\t\t\t\t\t\tnew UniformFormat('view_position', UNIFORMTYPE_VEC3),\n\t\t\t\t\t\t\t\tnew UniformFormat('skyboxIntensity', UNIFORMTYPE_FLOAT),\n\t\t\t\t\t\t\t\tnew UniformFormat('exposure', UNIFORMTYPE_FLOAT),\n\t\t\t\t\t\t\t\tnew UniformFormat('textureBias', UNIFORMTYPE_FLOAT),\n\t\t\t\t\t\t\t\tnew UniformFormat('view_index', UNIFORMTYPE_FLOAT)\n\t\t\t\t\t\t];\n\t\t\t\t\t\tif (isClustered) {\n\t\t\t\t\t\t\t\tuniforms.push(...[\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterCellsCountByBoundsSize', UNIFORMTYPE_VEC3),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterTextureSize', UNIFORMTYPE_VEC3),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterBoundsMin', UNIFORMTYPE_VEC3),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterBoundsDelta', UNIFORMTYPE_VEC3),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterCellsDot', UNIFORMTYPE_VEC3),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterCellsMax', UNIFORMTYPE_VEC3),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('shadowAtlasParams', UNIFORMTYPE_VEC2),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterMaxCells', UNIFORMTYPE_INT),\n\t\t\t\t\t\t\t\t\t\tnew UniformFormat('clusterSkip', UNIFORMTYPE_FLOAT)\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.viewUniformFormat = new UniformBufferFormat(this.device, uniforms);\n\t\t\t\t\t\tconst formats = [\n\t\t\t\t\t\t\t\tnew BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)\n\t\t\t\t\t\t];\n\t\t\t\t\t\tthis.viewBindGroupFormat = new BindGroupFormat(this.device, formats);\n\t\t\t\t}\n\t\t}\n\t\tsetupViewUniforms(view, index) {\n\t\t\t\tthis.projId.setValue(view.projMat.data);\n\t\t\t\tthis.projSkyboxId.setValue(view.projMat.data);\n\t\t\t\tthis.viewId.setValue(view.viewOffMat.data);\n\t\t\t\tthis.viewInvId.setValue(view.viewInvOffMat.data);\n\t\t\t\tthis.viewId3.setValue(view.viewMat3.data);\n\t\t\t\tthis.viewProjId.setValue(view.projViewOffMat.data);\n\t\t\t\tthis.viewPosId.setValue(view.positionData);\n\t\t\t\tthis.viewIndexId.setValue(index);\n\t\t}\n\t\tsetupViewUniformBuffers(viewBindGroups, viewUniformFormat, viewBindGroupFormat, viewList) {\n\t\t\t\tconst { device } = this;\n\t\t\t\tconst viewCount = viewList?.length ?? 1;\n\t\t\t\twhile(viewBindGroups.length < viewCount){\n\t\t\t\t\t\tconst ub = new UniformBuffer(device, viewUniformFormat, false);\n\t\t\t\t\t\tconst bg = new BindGroup(device, viewBindGroupFormat, ub);\n\t\t\t\t\t\tviewBindGroups.push(bg);\n\t\t\t\t}\n\t\t\t\tif (viewList) {\n\t\t\t\t\t\tfor(let i = 0; i < viewCount; i++){\n\t\t\t\t\t\t\t\tconst view = viewList[i];\n\t\t\t\t\t\t\t\tthis.setupViewUniforms(view, i);\n\t\t\t\t\t\t\t\tconst viewBindGroup = viewBindGroups[i];\n\t\t\t\t\t\t\t\tviewBindGroup.defaultUniformBuffer.update();\n\t\t\t\t\t\t\t\tviewBindGroup.update();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst viewBindGroup = viewBindGroups[0];\n\t\t\t\t\t\tviewBindGroup.defaultUniformBuffer.update();\n\t\t\t\t\t\tviewBindGroup.update();\n\t\t\t\t}\n\t\t\t\tif (!viewList) {\n\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_VIEW, viewBindGroups[0]);\n\t\t\t\t}\n\t\t}\n\t\tsetupMeshUniformBuffers(shaderInstance) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tif (device.supportsUniformBuffers) {\n\t\t\t\t\t\tconst meshBindGroup = shaderInstance.getBindGroup(device);\n\t\t\t\t\t\tmeshBindGroup.update();\n\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_MESH, meshBindGroup);\n\t\t\t\t\t\tconst meshUniformBuffer = shaderInstance.getUniformBuffer(device);\n\t\t\t\t\t\tmeshUniformBuffer.update(_dynamicBindGroup);\n\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_MESH_UB, _dynamicBindGroup.bindGroup, _dynamicBindGroup.offsets);\n\t\t\t\t}\n\t\t}\n\t\tsetMeshInstanceMatrices(meshInstance, setNormalMatrix = false) {\n\t\t\t\tconst modelMatrix = meshInstance.node.worldTransform;\n\t\t\t\tthis.modelMatrixId.setValue(modelMatrix.data);\n\t\t\t\tif (setNormalMatrix) {\n\t\t\t\t\t\tthis.normalMatrixId.setValue(meshInstance.node.normalMatrix.data);\n\t\t\t\t}\n\t\t}\n\t\tcull(camera, drawCalls, culledInstances) {\n\t\t\t\tconst opaque = culledInstances.opaque;\n\t\t\t\topaque.length = 0;\n\t\t\t\tconst transparent = culledInstances.transparent;\n\t\t\t\ttransparent.length = 0;\n\t\t\t\tconst doCull = camera.frustumCulling;\n\t\t\t\tconst count = drawCalls.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst drawCall = drawCalls[i];\n\t\t\t\t\t\tif (drawCall.visible) {\n\t\t\t\t\t\t\t\tconst visible = !doCull || !drawCall.cull || drawCall._isVisible(camera);\n\t\t\t\t\t\t\t\tif (visible) {\n\t\t\t\t\t\t\t\t\t\tdrawCall.visibleThisFrame = true;\n\t\t\t\t\t\t\t\t\t\tconst bucket = drawCall.transparent ? transparent : opaque;\n\t\t\t\t\t\t\t\t\t\tbucket.push(drawCall);\n\t\t\t\t\t\t\t\t\t\tif (drawCall.skinInstance || drawCall.morphInstance || drawCall.gsplatInstance) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.processingMeshInstances.add(drawCall);\n\t\t\t\t\t\t\t\t\t\t\t\tif (drawCall.gsplatInstance) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdrawCall.gsplatInstance.cameras.push(camera);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcollectLights(comp) {\n\t\t\t\tthis.lights.length = 0;\n\t\t\t\tthis.localLights.length = 0;\n\t\t\t\tconst stats = this.scene._stats;\n\t\t\t\tconst count = comp.layerList.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst layer = comp.layerList[i];\n\t\t\t\t\t\tif (!_tempLayerSet.has(layer)) {\n\t\t\t\t\t\t\t\t_tempLayerSet.add(layer);\n\t\t\t\t\t\t\t\tconst lights = layer._lights;\n\t\t\t\t\t\t\t\tfor(let j = 0; j < lights.length; j++){\n\t\t\t\t\t\t\t\t\t\tconst light = lights[j];\n\t\t\t\t\t\t\t\t\t\tif (!_tempLightSet.has(light)) {\n\t\t\t\t\t\t\t\t\t\t\t\t_tempLightSet.add(light);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.lights.push(light);\n\t\t\t\t\t\t\t\t\t\t\t\tif (light._type !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.localLights.push(light);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstats.lights = this.lights.length;\n\t\t\t\t_tempLightSet.clear();\n\t\t\t\t_tempLayerSet.clear();\n\t\t}\n\t\tcullLights(camera, lights) {\n\t\t\t\tconst clusteredLightingEnabled = this.scene.clusteredLightingEnabled;\n\t\t\t\tconst physicalUnits = this.scene.physicalUnits;\n\t\t\t\tfor(let i = 0; i < lights.length; i++){\n\t\t\t\t\t\tconst light = lights[i];\n\t\t\t\t\t\tif (light.enabled) {\n\t\t\t\t\t\t\t\tif (light._type !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\t\t\tlight.getBoundingSphere(tempSphere$1);\n\t\t\t\t\t\t\t\t\t\tif (camera.frustum.containsSphere(tempSphere$1)) {\n\t\t\t\t\t\t\t\t\t\t\t\tlight.visibleThisFrame = true;\n\t\t\t\t\t\t\t\t\t\t\t\tlight.usePhysicalUnits = physicalUnits;\n\t\t\t\t\t\t\t\t\t\t\t\tconst screenSize = camera.getScreenSize(tempSphere$1);\n\t\t\t\t\t\t\t\t\t\t\t\tlight.maxScreenSize = Math.max(light.maxScreenSize, screenSize);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (light.castShadows && !light.shadowMap) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlight.visibleThisFrame = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tlight.usePhysicalUnits = this.scene.physicalUnits;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcullShadowmaps(comp) {\n\t\t\t\tconst isClustered = this.scene.clusteredLightingEnabled;\n\t\t\t\tfor(let i = 0; i < this.localLights.length; i++){\n\t\t\t\t\t\tconst light = this.localLights[i];\n\t\t\t\t\t\tif (light._type !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\tif (isClustered) {\n\t\t\t\t\t\t\t\t\t\tif (light.atlasSlotUpdated && light.shadowUpdateMode === SHADOWUPDATE_NONE) {\n\t\t\t\t\t\t\t\t\t\t\t\tlight.shadowUpdateMode = SHADOWUPDATE_THISFRAME;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (light.shadowUpdateMode === SHADOWUPDATE_NONE && light.castShadows) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!light.getRenderData(null, 0).shadowCamera.renderTarget) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlight.shadowUpdateMode = SHADOWUPDATE_THISFRAME;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (light.visibleThisFrame && light.castShadows && light.shadowUpdateMode !== SHADOWUPDATE_NONE) {\n\t\t\t\t\t\t\t\t\t\tthis._shadowRendererLocal.cull(light, comp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.cameraDirShadowLights.clear();\n\t\t\t\tconst cameras = comp.cameras;\n\t\t\t\tfor(let i = 0; i < cameras.length; i++){\n\t\t\t\t\t\tconst cameraComponent = cameras[i];\n\t\t\t\t\t\tif (cameraComponent.enabled) {\n\t\t\t\t\t\t\t\tconst camera = cameraComponent.camera;\n\t\t\t\t\t\t\t\tlet lightList;\n\t\t\t\t\t\t\t\tconst cameraLayers = camera.layers;\n\t\t\t\t\t\t\t\tfor(let l = 0; l < cameraLayers.length; l++){\n\t\t\t\t\t\t\t\t\t\tconst cameraLayer = comp.getLayerById(cameraLayers[l]);\n\t\t\t\t\t\t\t\t\t\tif (cameraLayer) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst layerDirLights = cameraLayer.splitLights[LIGHTTYPE_DIRECTIONAL];\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < layerDirLights.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst light = layerDirLights[j];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (light.castShadows && !_tempSet.has(light)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t_tempSet.add(light);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlightList = lightList ?? [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlightList.push(light);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._shadowRendererDirectional.cull(light, comp, camera);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (lightList) {\n\t\t\t\t\t\t\t\t\t\tthis.cameraDirShadowLights.set(camera, lightList);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t_tempSet.clear();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcullComposition(comp) {\n\t\t\t\tconst { scene } = this;\n\t\t\t\tthis.processingMeshInstances.clear();\n\t\t\t\tconst numCameras = comp.cameras.length;\n\t\t\t\tthis._camerasRendered += numCameras;\n\t\t\t\tfor(let i = 0; i < numCameras; i++){\n\t\t\t\t\t\tconst camera = comp.cameras[i];\n\t\t\t\t\t\tscene?.fire(EVENT_PRECULL, camera);\n\t\t\t\t\t\tconst renderTarget = camera.renderTarget;\n\t\t\t\t\t\tcamera.frameUpdate(renderTarget);\n\t\t\t\t\t\tthis.updateCameraFrustum(camera.camera);\n\t\t\t\t\t\tconst layerIds = camera.layers;\n\t\t\t\t\t\tfor(let j = 0; j < layerIds.length; j++){\n\t\t\t\t\t\t\t\tconst layer = comp.getLayerById(layerIds[j]);\n\t\t\t\t\t\t\t\tif (layer && layer.enabled) {\n\t\t\t\t\t\t\t\t\t\tthis.cullLights(camera.camera, layer._lights);\n\t\t\t\t\t\t\t\t\t\tconst culledInstances = layer.getCulledInstances(camera.camera);\n\t\t\t\t\t\t\t\t\t\tthis.cull(camera.camera, layer.meshInstances, culledInstances);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscene?.fire(EVENT_POSTCULL, camera);\n\t\t\t\t}\n\t\t\t\tif (scene.clusteredLightingEnabled) {\n\t\t\t\t\t\tthis.updateLightTextureAtlas();\n\t\t\t\t}\n\t\t\t\tthis.cullShadowmaps(comp);\n\t\t\t\tscene?.fire(EVENT_CULL_END);\n\t\t}\n\t\tupdateShaders(drawCalls, onlyLitShaders) {\n\t\t\t\tconst count = drawCalls.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst mat = drawCalls[i].material;\n\t\t\t\t\t\tif (mat) {\n\t\t\t\t\t\t\t\tif (!_tempSet.has(mat)) {\n\t\t\t\t\t\t\t\t\t\t_tempSet.add(mat);\n\t\t\t\t\t\t\t\t\t\tif (mat.getShaderVariant !== Material.prototype.getShaderVariant) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (onlyLitShaders) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (!mat.useLighting || mat.emitter && !mat.emitter.lighting) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tmat.clearVariants();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_tempSet.clear();\n\t\t}\n\t\tupdateFrameUniforms() {\n\t\t\t\tthis.blueNoiseTextureId.setValue(getBlueNoiseTexture(this.device));\n\t\t}\n\t\tbeginFrame(comp) {\n\t\t\t\tconst scene = this.scene;\n\t\t\t\tconst updateShaders = scene.updateShaders || this.device._shadersDirty;\n\t\t\t\tconst layers = comp.layerList;\n\t\t\t\tconst layerCount = layers.length;\n\t\t\t\tfor(let i = 0; i < layerCount; i++){\n\t\t\t\t\t\tconst layer = layers[i];\n\t\t\t\t\t\tconst meshInstances = layer.meshInstances;\n\t\t\t\t\t\tconst count = meshInstances.length;\n\t\t\t\t\t\tfor(let j = 0; j < count; j++){\n\t\t\t\t\t\t\t\tconst meshInst = meshInstances[j];\n\t\t\t\t\t\t\t\tmeshInst.visibleThisFrame = false;\n\t\t\t\t\t\t\t\tif (updateShaders) {\n\t\t\t\t\t\t\t\t\t\t_tempMeshInstances.push(meshInst);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (meshInst.skinInstance) {\n\t\t\t\t\t\t\t\t\t\t_tempMeshInstancesSkinned.push(meshInst);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (updateShaders) {\n\t\t\t\t\t\tconst onlyLitShaders = !scene.updateShaders || !this.device._shadersDirty;\n\t\t\t\t\t\tthis.updateShaders(_tempMeshInstances, onlyLitShaders);\n\t\t\t\t\t\tscene.updateShaders = false;\n\t\t\t\t\t\tthis.device._shadersDirty = false;\n\t\t\t\t\t\tscene._shaderVersion++;\n\t\t\t\t}\n\t\t\t\tthis.updateFrameUniforms();\n\t\t\t\tthis.updateCpuSkinMatrices(_tempMeshInstancesSkinned);\n\t\t\t\t_tempMeshInstances.length = 0;\n\t\t\t\t_tempMeshInstancesSkinned.length = 0;\n\t\t\t\tconst lights = this.lights;\n\t\t\t\tconst lightCount = lights.length;\n\t\t\t\tfor(let i = 0; i < lightCount; i++){\n\t\t\t\t\t\tlights[i].beginFrame();\n\t\t\t\t}\n\t\t}\n\t\tupdateLightTextureAtlas() {\n\t\t\t\tthis.lightTextureAtlas.update(this.localLights, this.scene.lighting);\n\t\t}\n\t\tupdateLayerComposition(comp) {\n\t\t\t\tconst len = comp.layerList.length;\n\t\t\t\tconst scene = this.scene;\n\t\t\t\tconst shaderVersion = scene._shaderVersion;\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tconst layer = comp.layerList[i];\n\t\t\t\t\t\tlayer._shaderVersion = shaderVersion;\n\t\t\t\t}\n\t\t\t\tcomp._update();\n\t\t}\n\t\tframeUpdate() {\n\t\t\t\tthis.clustersDebugRendered = false;\n\t\t\t\tthis.initViewBindGroupFormat(this.scene.clusteredLightingEnabled);\n\t\t\t\tthis.dirLightShadows.clear();\n\t\t}\n}\n\nclass RenderAction {\n\t\tconstructor(){\n\t\t\t\tthis.camera = null;\n\t\t\t\tthis.layer = null;\n\t\t\t\tthis.transparent = false;\n\t\t\t\tthis.renderTarget = null;\n\t\t\t\tthis.lightClusters = null;\n\t\t\t\tthis.clearColor = false;\n\t\t\t\tthis.clearDepth = false;\n\t\t\t\tthis.clearStencil = false;\n\t\t\t\tthis.triggerPostprocess = false;\n\t\t\t\tthis.firstCameraUse = false;\n\t\t\t\tthis.lastCameraUse = false;\n\t\t\t\tthis.viewBindGroups = [];\n\t\t\t\tthis.useCameraPasses = false;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.viewBindGroups.forEach((bg)=>{\n\t\t\t\t\t\tbg.defaultUniformBuffer.destroy();\n\t\t\t\t\t\tbg.destroy();\n\t\t\t\t});\n\t\t\t\tthis.viewBindGroups.length = 0;\n\t\t}\n\t\tsetupClears(camera, layer) {\n\t\t\t\tthis.clearColor = camera?.clearColorBuffer || layer.clearColorBuffer;\n\t\t\t\tthis.clearDepth = camera?.clearDepthBuffer || layer.clearDepthBuffer;\n\t\t\t\tthis.clearStencil = camera?.clearStencilBuffer || layer.clearStencilBuffer;\n\t\t}\n}\n\nclass RenderPassForward extends RenderPass {\n\t\tconstructor(device, layerComposition, scene, renderer){\n\t\t\t\tsuper(device), this.renderActions = [], this.noDepthClear = false;\n\t\t\t\tthis.layerComposition = layerComposition;\n\t\t\t\tthis.scene = scene;\n\t\t\t\tthis.renderer = renderer;\n\t\t}\n\t\tget rendersAnything() {\n\t\t\t\treturn this.renderActions.length > 0;\n\t\t}\n\t\taddRenderAction(renderAction) {\n\t\t\t\tthis.renderActions.push(renderAction);\n\t\t}\n\t\taddLayer(cameraComponent, layer, transparent, autoClears = true) {\n\t\t\t\tconst ra = new RenderAction();\n\t\t\t\tra.renderTarget = this.renderTarget;\n\t\t\t\tra.camera = cameraComponent;\n\t\t\t\tra.layer = layer;\n\t\t\t\tra.transparent = transparent;\n\t\t\t\tif (autoClears) {\n\t\t\t\t\t\tconst firstRa = this.renderActions.length === 0;\n\t\t\t\t\t\tra.setupClears(firstRa ? cameraComponent : undefined, layer);\n\t\t\t\t}\n\t\t\t\tthis.addRenderAction(ra);\n\t\t}\n\t\taddLayers(composition, cameraComponent, startIndex, firstLayerClears, lastLayerId, lastLayerIsTransparent = true) {\n\t\t\t\tconst { layerList, subLayerList } = composition;\n\t\t\t\tlet clearRenderTarget = firstLayerClears;\n\t\t\t\tlet index = startIndex;\n\t\t\t\twhile(index < layerList.length){\n\t\t\t\t\t\tconst layer = layerList[index];\n\t\t\t\t\t\tconst isTransparent = subLayerList[index];\n\t\t\t\t\t\tconst renderedByCamera = cameraComponent.camera.layersSet.has(layer.id);\n\t\t\t\t\t\tif (renderedByCamera) {\n\t\t\t\t\t\t\t\tthis.addLayer(cameraComponent, layer, isTransparent, clearRenderTarget);\n\t\t\t\t\t\t\t\tclearRenderTarget = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tindex++;\n\t\t\t\t\t\tif (layer.id === lastLayerId && isTransparent === lastLayerIsTransparent) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn index;\n\t\t}\n\t\tupdateDirectionalShadows() {\n\t\t\t\tconst { renderer, renderActions } = this;\n\t\t\t\tfor(let i = 0; i < renderActions.length; i++){\n\t\t\t\t\t\tconst renderAction = renderActions[i];\n\t\t\t\t\t\tconst cameraComp = renderAction.camera;\n\t\t\t\t\t\tconst camera = cameraComp.camera;\n\t\t\t\t\t\tconst shadowDirLights = this.renderer.cameraDirShadowLights.get(camera);\n\t\t\t\t\t\tif (shadowDirLights) {\n\t\t\t\t\t\t\t\tfor(let l = 0; l < shadowDirLights.length; l++){\n\t\t\t\t\t\t\t\t\t\tconst light = shadowDirLights[l];\n\t\t\t\t\t\t\t\t\t\tif (renderer.dirLightShadows.get(light) !== camera) {\n\t\t\t\t\t\t\t\t\t\t\t\trenderer.dirLightShadows.set(light, camera);\n\t\t\t\t\t\t\t\t\t\t\t\tconst shadowPass = renderer._shadowRendererDirectional.getLightRenderPass(light, camera);\n\t\t\t\t\t\t\t\t\t\t\t\tif (shadowPass) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.beforePasses.push(shadowPass);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdateClears() {\n\t\t\t\tconst renderAction = this.renderActions[0];\n\t\t\t\tif (renderAction) {\n\t\t\t\t\t\tconst cameraComponent = renderAction.camera;\n\t\t\t\t\t\tconst camera = cameraComponent.camera;\n\t\t\t\t\t\tconst fullSizeClearRect = camera.fullSizeClearRect;\n\t\t\t\t\t\tthis.setClearColor(fullSizeClearRect && renderAction.clearColor ? camera.clearColor : undefined);\n\t\t\t\t\t\tthis.setClearDepth(fullSizeClearRect && renderAction.clearDepth && !this.noDepthClear ? camera.clearDepth : undefined);\n\t\t\t\t\t\tthis.setClearStencil(fullSizeClearRect && renderAction.clearStencil ? camera.clearStencil : undefined);\n\t\t\t\t}\n\t\t}\n\t\tframeUpdate() {\n\t\t\t\tsuper.frameUpdate();\n\t\t\t\tthis.updateDirectionalShadows();\n\t\t\t\tthis.updateClears();\n\t\t}\n\t\tbefore() {\n\t\t\t\tconst { renderActions } = this;\n\t\t\t\tfor(let i = 0; i < renderActions.length; i++){\n\t\t\t\t\t\tconst ra = renderActions[i];\n\t\t\t\t\t\tif (ra.firstCameraUse) {\n\t\t\t\t\t\t\t\tthis.scene.fire(EVENT_PRERENDER, ra.camera);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\texecute() {\n\t\t\t\tconst { layerComposition, renderActions } = this;\n\t\t\t\tfor(let i = 0; i < renderActions.length; i++){\n\t\t\t\t\t\tconst ra = renderActions[i];\n\t\t\t\t\t\tconst layer = ra.layer;\n\t\t\t\t\t\tif (layerComposition.isEnabled(layer, ra.transparent)) {\n\t\t\t\t\t\t\t\tthis.renderRenderAction(ra, i === 0);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tafter() {\n\t\t\t\tfor(let i = 0; i < this.renderActions.length; i++){\n\t\t\t\t\t\tconst ra = this.renderActions[i];\n\t\t\t\t\t\tif (ra.lastCameraUse) {\n\t\t\t\t\t\t\t\tthis.scene.fire(EVENT_POSTRENDER, ra.camera);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.beforePasses.length = 0;\n\t\t}\n\t\trenderRenderAction(renderAction, firstRenderAction) {\n\t\t\t\tconst { renderer, scene } = this;\n\t\t\t\tconst device = renderer.device;\n\t\t\t\tconst { layer, transparent, camera } = renderAction;\n\t\t\t\tif (camera) {\n\t\t\t\t\t\tconst originalGammaCorrection = camera.gammaCorrection;\n\t\t\t\t\t\tconst originalToneMapping = camera.toneMapping;\n\t\t\t\t\t\tif (this.gammaCorrection !== undefined) camera.gammaCorrection = this.gammaCorrection;\n\t\t\t\t\t\tif (this.toneMapping !== undefined) camera.toneMapping = this.toneMapping;\n\t\t\t\t\t\tscene.fire(EVENT_PRERENDER_LAYER, camera, layer, transparent);\n\t\t\t\t\t\tconst options = {\n\t\t\t\t\t\t\t\tlightClusters: renderAction.lightClusters\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst shaderPass = camera.camera.shaderPassInfo?.index ?? SHADER_FORWARD;\n\t\t\t\t\t\tif (!firstRenderAction || !camera.camera.fullSizeClearRect) {\n\t\t\t\t\t\t\t\toptions.clearColor = renderAction.clearColor;\n\t\t\t\t\t\t\t\toptions.clearDepth = renderAction.clearDepth;\n\t\t\t\t\t\t\t\toptions.clearStencil = renderAction.clearStencil;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst renderTarget = renderAction.renderTarget ?? device.backBuffer;\n\t\t\t\t\t\trenderer.renderForwardLayer(camera.camera, renderTarget, layer, transparent, shaderPass, renderAction.viewBindGroups, options);\n\t\t\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\t\t\tdevice.setStencilState(null, null);\n\t\t\t\t\t\tdevice.setAlphaToCoverage(false);\n\t\t\t\t\t\tscene.fire(EVENT_POSTRENDER_LAYER, camera, layer, transparent);\n\t\t\t\t\t\tif (this.gammaCorrection !== undefined) camera.gammaCorrection = originalGammaCorrection;\n\t\t\t\t\t\tif (this.toneMapping !== undefined) camera.toneMapping = originalToneMapping;\n\t\t\t\t}\n\t\t}\n}\n\nclass RenderPassPostprocessing extends RenderPass {\n\t\tconstructor(device, renderer, renderAction){\n\t\t\t\tsuper(device);\n\t\t\t\tthis.renderer = renderer;\n\t\t\t\tthis.renderAction = renderAction;\n\t\t\t\tthis.requiresCubemaps = false;\n\t\t}\n\t\texecute() {\n\t\t\t\tconst renderAction = this.renderAction;\n\t\t\t\tconst camera = renderAction.camera;\n\t\t\t\tcamera.onPostprocessing();\n\t\t}\n}\n\nconst _noLights = [\n\t\t[],\n\t\t[],\n\t\t[]\n];\nconst tmpColor$1 = new Color();\nconst _drawCallList = {\n\t\tdrawCalls: [],\n\t\tshaderInstances: [],\n\t\tisNewMaterial: [],\n\t\tlightMaskChanged: [],\n\t\tclear: function() {\n\t\t\t\tthis.drawCalls.length = 0;\n\t\t\t\tthis.shaderInstances.length = 0;\n\t\t\t\tthis.isNewMaterial.length = 0;\n\t\t\t\tthis.lightMaskChanged.length = 0;\n\t\t}\n};\nfunction vogelDiskPrecalculationSamples(numSamples) {\n\t\tconst samples = [];\n\t\tfor(let i = 0; i < numSamples; ++i){\n\t\t\t\tconst r = Math.sqrt(i + 0.5) / Math.sqrt(numSamples);\n\t\t\t\tsamples.push(r);\n\t\t}\n\t\treturn samples;\n}\nfunction vogelSpherePrecalculationSamples(numSamples) {\n\t\tconst samples = [];\n\t\tfor(let i = 0; i < numSamples; i++){\n\t\t\t\tconst weight = i / numSamples;\n\t\t\t\tconst radius = Math.sqrt(weight * weight);\n\t\t\t\tsamples.push(radius);\n\t\t}\n\t\treturn samples;\n}\nclass ForwardRenderer extends Renderer {\n\t\tconstructor(graphicsDevice, scene){\n\t\t\t\tsuper(graphicsDevice, scene);\n\t\t\t\tconst device = this.device;\n\t\t\t\tthis._forwardDrawCalls = 0;\n\t\t\t\tthis._materialSwitches = 0;\n\t\t\t\tthis._depthMapTime = 0;\n\t\t\t\tthis._forwardTime = 0;\n\t\t\t\tthis._sortTime = 0;\n\t\t\t\tconst scope = device.scope;\n\t\t\t\tthis.fogColorId = scope.resolve('fog_color');\n\t\t\t\tthis.fogStartId = scope.resolve('fog_start');\n\t\t\t\tthis.fogEndId = scope.resolve('fog_end');\n\t\t\t\tthis.fogDensityId = scope.resolve('fog_density');\n\t\t\t\tthis.ambientId = scope.resolve('light_globalAmbient');\n\t\t\t\tthis.skyboxIntensityId = scope.resolve('skyboxIntensity');\n\t\t\t\tthis.cubeMapRotationMatrixId = scope.resolve('cubeMapRotationMatrix');\n\t\t\t\tthis.pcssDiskSamplesId = scope.resolve('pcssDiskSamples[0]');\n\t\t\t\tthis.pcssSphereSamplesId = scope.resolve('pcssSphereSamples[0]');\n\t\t\t\tthis.lightColorId = [];\n\t\t\t\tthis.lightDir = [];\n\t\t\t\tthis.lightDirId = [];\n\t\t\t\tthis.lightShadowMapId = [];\n\t\t\t\tthis.lightShadowMatrixId = [];\n\t\t\t\tthis.lightShadowParamsId = [];\n\t\t\t\tthis.lightShadowIntensity = [];\n\t\t\t\tthis.lightRadiusId = [];\n\t\t\t\tthis.lightPos = [];\n\t\t\t\tthis.lightPosId = [];\n\t\t\t\tthis.lightWidth = [];\n\t\t\t\tthis.lightWidthId = [];\n\t\t\t\tthis.lightHeight = [];\n\t\t\t\tthis.lightHeightId = [];\n\t\t\t\tthis.lightInAngleId = [];\n\t\t\t\tthis.lightOutAngleId = [];\n\t\t\t\tthis.lightCookieId = [];\n\t\t\t\tthis.lightCookieIntId = [];\n\t\t\t\tthis.lightCookieMatrixId = [];\n\t\t\t\tthis.lightCookieOffsetId = [];\n\t\t\t\tthis.lightShadowSearchAreaId = [];\n\t\t\t\tthis.lightCameraParamsId = [];\n\t\t\t\tthis.lightSoftShadowParamsId = [];\n\t\t\t\tthis.shadowMatrixPaletteId = [];\n\t\t\t\tthis.shadowCascadeDistancesId = [];\n\t\t\t\tthis.shadowCascadeCountId = [];\n\t\t\t\tthis.shadowCascadeBlendId = [];\n\t\t\t\tthis.screenSizeId = scope.resolve('uScreenSize');\n\t\t\t\tthis._screenSize = new Float32Array(4);\n\t\t\t\tthis.fogColor = new Float32Array(3);\n\t\t\t\tthis.ambientColor = new Float32Array(3);\n\t\t\t\tthis.pcssDiskSamples = vogelDiskPrecalculationSamples(16);\n\t\t\t\tthis.pcssSphereSamples = vogelSpherePrecalculationSamples(16);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t}\n\t\tdispatchGlobalLights(scene) {\n\t\t\t\tconst ambientUniform = this.ambientColor;\n\t\t\t\ttmpColor$1.linear(scene.ambientLight);\n\t\t\t\tambientUniform[0] = tmpColor$1.r;\n\t\t\t\tambientUniform[1] = tmpColor$1.g;\n\t\t\t\tambientUniform[2] = tmpColor$1.b;\n\t\t\t\tif (scene.physicalUnits) {\n\t\t\t\t\t\tfor(let i = 0; i < 3; i++){\n\t\t\t\t\t\t\t\tambientUniform[i] *= scene.ambientLuminance;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.ambientId.setValue(ambientUniform);\n\t\t\t\tthis.skyboxIntensityId.setValue(scene.physicalUnits ? scene.skyboxLuminance : scene.skyboxIntensity);\n\t\t\t\tthis.cubeMapRotationMatrixId.setValue(scene._skyboxRotationMat3.data);\n\t\t}\n\t\t_resolveLight(scope, i) {\n\t\t\t\tconst light = `light${i}`;\n\t\t\t\tthis.lightColorId[i] = scope.resolve(`${light}_color`);\n\t\t\t\tthis.lightDir[i] = new Float32Array(3);\n\t\t\t\tthis.lightDirId[i] = scope.resolve(`${light}_direction`);\n\t\t\t\tthis.lightShadowMapId[i] = scope.resolve(`${light}_shadowMap`);\n\t\t\t\tthis.lightShadowMatrixId[i] = scope.resolve(`${light}_shadowMatrix`);\n\t\t\t\tthis.lightShadowParamsId[i] = scope.resolve(`${light}_shadowParams`);\n\t\t\t\tthis.lightShadowIntensity[i] = scope.resolve(`${light}_shadowIntensity`);\n\t\t\t\tthis.lightShadowSearchAreaId[i] = scope.resolve(`${light}_shadowSearchArea`);\n\t\t\t\tthis.lightRadiusId[i] = scope.resolve(`${light}_radius`);\n\t\t\t\tthis.lightPos[i] = new Float32Array(3);\n\t\t\t\tthis.lightPosId[i] = scope.resolve(`${light}_position`);\n\t\t\t\tthis.lightWidth[i] = new Float32Array(3);\n\t\t\t\tthis.lightWidthId[i] = scope.resolve(`${light}_halfWidth`);\n\t\t\t\tthis.lightHeight[i] = new Float32Array(3);\n\t\t\t\tthis.lightHeightId[i] = scope.resolve(`${light}_halfHeight`);\n\t\t\t\tthis.lightInAngleId[i] = scope.resolve(`${light}_innerConeAngle`);\n\t\t\t\tthis.lightOutAngleId[i] = scope.resolve(`${light}_outerConeAngle`);\n\t\t\t\tthis.lightCookieId[i] = scope.resolve(`${light}_cookie`);\n\t\t\t\tthis.lightCookieIntId[i] = scope.resolve(`${light}_cookieIntensity`);\n\t\t\t\tthis.lightCookieMatrixId[i] = scope.resolve(`${light}_cookieMatrix`);\n\t\t\t\tthis.lightCookieOffsetId[i] = scope.resolve(`${light}_cookieOffset`);\n\t\t\t\tthis.lightCameraParamsId[i] = scope.resolve(`${light}_cameraParams`);\n\t\t\t\tthis.lightSoftShadowParamsId[i] = scope.resolve(`${light}_softShadowParams`);\n\t\t\t\tthis.shadowMatrixPaletteId[i] = scope.resolve(`${light}_shadowMatrixPalette[0]`);\n\t\t\t\tthis.shadowCascadeDistancesId[i] = scope.resolve(`${light}_shadowCascadeDistances`);\n\t\t\t\tthis.shadowCascadeCountId[i] = scope.resolve(`${light}_shadowCascadeCount`);\n\t\t\t\tthis.shadowCascadeBlendId[i] = scope.resolve(`${light}_shadowCascadeBlend`);\n\t\t}\n\t\tsetLTCDirectionalLight(wtm, cnt, dir, campos, far) {\n\t\t\t\tthis.lightPos[cnt][0] = campos.x - dir.x * far;\n\t\t\t\tthis.lightPos[cnt][1] = campos.y - dir.y * far;\n\t\t\t\tthis.lightPos[cnt][2] = campos.z - dir.z * far;\n\t\t\t\tthis.lightPosId[cnt].setValue(this.lightPos[cnt]);\n\t\t\t\tconst hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));\n\t\t\t\tthis.lightWidth[cnt][0] = hWidth.x * far;\n\t\t\t\tthis.lightWidth[cnt][1] = hWidth.y * far;\n\t\t\t\tthis.lightWidth[cnt][2] = hWidth.z * far;\n\t\t\t\tthis.lightWidthId[cnt].setValue(this.lightWidth[cnt]);\n\t\t\t\tconst hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));\n\t\t\t\tthis.lightHeight[cnt][0] = hHeight.x * far;\n\t\t\t\tthis.lightHeight[cnt][1] = hHeight.y * far;\n\t\t\t\tthis.lightHeight[cnt][2] = hHeight.z * far;\n\t\t\t\tthis.lightHeightId[cnt].setValue(this.lightHeight[cnt]);\n\t\t}\n\t\tdispatchDirectLights(dirs, mask, camera) {\n\t\t\t\tlet cnt = 0;\n\t\t\t\tconst scope = this.device.scope;\n\t\t\t\tfor(let i = 0; i < dirs.length; i++){\n\t\t\t\t\t\tif (!(dirs[i].mask & mask)) continue;\n\t\t\t\t\t\tconst directional = dirs[i];\n\t\t\t\t\t\tconst wtm = directional._node.getWorldTransform();\n\t\t\t\t\t\tif (!this.lightColorId[cnt]) {\n\t\t\t\t\t\t\t\tthis._resolveLight(scope, cnt);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.lightColorId[cnt].setValue(directional._colorLinear);\n\t\t\t\t\t\twtm.getY(directional._direction).mulScalar(-1);\n\t\t\t\t\t\tdirectional._direction.normalize();\n\t\t\t\t\t\tthis.lightDir[cnt][0] = directional._direction.x;\n\t\t\t\t\t\tthis.lightDir[cnt][1] = directional._direction.y;\n\t\t\t\t\t\tthis.lightDir[cnt][2] = directional._direction.z;\n\t\t\t\t\t\tthis.lightDirId[cnt].setValue(this.lightDir[cnt]);\n\t\t\t\t\t\tif (directional.shape !== LIGHTSHAPE_PUNCTUAL) {\n\t\t\t\t\t\t\t\tthis.setLTCDirectionalLight(wtm, cnt, directional._direction, camera._node.getPosition(), camera.farClip);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (directional.castShadows) {\n\t\t\t\t\t\t\t\tconst lightRenderData = directional.getRenderData(camera, 0);\n\t\t\t\t\t\t\t\tconst biases = directional._getUniformBiasValues(lightRenderData);\n\t\t\t\t\t\t\t\tthis.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);\n\t\t\t\t\t\t\t\tthis.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);\n\t\t\t\t\t\t\t\tthis.shadowMatrixPaletteId[cnt].setValue(directional._shadowMatrixPalette);\n\t\t\t\t\t\t\t\tthis.shadowCascadeDistancesId[cnt].setValue(directional._shadowCascadeDistances);\n\t\t\t\t\t\t\t\tthis.shadowCascadeCountId[cnt].setValue(directional.numCascades);\n\t\t\t\t\t\t\t\tthis.shadowCascadeBlendId[cnt].setValue(1 - directional.cascadeBlend);\n\t\t\t\t\t\t\t\tthis.lightShadowIntensity[cnt].setValue(directional.shadowIntensity);\n\t\t\t\t\t\t\t\tthis.lightSoftShadowParamsId[cnt].setValue(directional._softShadowParams);\n\t\t\t\t\t\t\t\tconst shadowRT = lightRenderData.shadowCamera.renderTarget;\n\t\t\t\t\t\t\t\tif (shadowRT) {\n\t\t\t\t\t\t\t\t\t\tthis.lightShadowSearchAreaId[cnt].setValue(directional.penumbraSize / lightRenderData.shadowCamera.renderTarget.width * lightRenderData.projectionCompensation);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst cameraParams = directional._shadowCameraParams;\n\t\t\t\t\t\t\t\tcameraParams.length = 4;\n\t\t\t\t\t\t\t\tcameraParams[0] = 0;\n\t\t\t\t\t\t\t\tcameraParams[1] = lightRenderData.shadowCamera._farClip;\n\t\t\t\t\t\t\t\tcameraParams[2] = lightRenderData.shadowCamera._nearClip;\n\t\t\t\t\t\t\t\tcameraParams[3] = 1;\n\t\t\t\t\t\t\t\tthis.lightCameraParamsId[cnt].setValue(cameraParams);\n\t\t\t\t\t\t\t\tconst params = directional._shadowRenderParams;\n\t\t\t\t\t\t\t\tparams.length = 4;\n\t\t\t\t\t\t\t\tparams[0] = directional._shadowResolution;\n\t\t\t\t\t\t\t\tparams[1] = biases.normalBias;\n\t\t\t\t\t\t\t\tparams[2] = biases.bias;\n\t\t\t\t\t\t\t\tparams[3] = 0;\n\t\t\t\t\t\t\t\tthis.lightShadowParamsId[cnt].setValue(params);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcnt++;\n\t\t\t\t}\n\t\t\t\treturn cnt;\n\t\t}\n\t\tsetLTCPositionalLight(wtm, cnt) {\n\t\t\t\tconst hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));\n\t\t\t\tthis.lightWidth[cnt][0] = hWidth.x;\n\t\t\t\tthis.lightWidth[cnt][1] = hWidth.y;\n\t\t\t\tthis.lightWidth[cnt][2] = hWidth.z;\n\t\t\t\tthis.lightWidthId[cnt].setValue(this.lightWidth[cnt]);\n\t\t\t\tconst hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));\n\t\t\t\tthis.lightHeight[cnt][0] = hHeight.x;\n\t\t\t\tthis.lightHeight[cnt][1] = hHeight.y;\n\t\t\t\tthis.lightHeight[cnt][2] = hHeight.z;\n\t\t\t\tthis.lightHeightId[cnt].setValue(this.lightHeight[cnt]);\n\t\t}\n\t\tdispatchOmniLight(scope, omni, cnt) {\n\t\t\t\tconst wtm = omni._node.getWorldTransform();\n\t\t\t\tif (!this.lightColorId[cnt]) {\n\t\t\t\t\t\tthis._resolveLight(scope, cnt);\n\t\t\t\t}\n\t\t\t\tthis.lightRadiusId[cnt].setValue(omni.attenuationEnd);\n\t\t\t\tthis.lightColorId[cnt].setValue(omni._colorLinear);\n\t\t\t\twtm.getTranslation(omni._position);\n\t\t\t\tthis.lightPos[cnt][0] = omni._position.x;\n\t\t\t\tthis.lightPos[cnt][1] = omni._position.y;\n\t\t\t\tthis.lightPos[cnt][2] = omni._position.z;\n\t\t\t\tthis.lightPosId[cnt].setValue(this.lightPos[cnt]);\n\t\t\t\tif (omni.shape !== LIGHTSHAPE_PUNCTUAL) {\n\t\t\t\t\t\tthis.setLTCPositionalLight(wtm, cnt);\n\t\t\t\t}\n\t\t\t\tif (omni.castShadows) {\n\t\t\t\t\t\tconst lightRenderData = omni.getRenderData(null, 0);\n\t\t\t\t\t\tthis.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);\n\t\t\t\t\t\tconst biases = omni._getUniformBiasValues(lightRenderData);\n\t\t\t\t\t\tconst params = omni._shadowRenderParams;\n\t\t\t\t\t\tparams.length = 4;\n\t\t\t\t\t\tparams[0] = omni._shadowResolution;\n\t\t\t\t\t\tparams[1] = biases.normalBias;\n\t\t\t\t\t\tparams[2] = biases.bias;\n\t\t\t\t\t\tparams[3] = 1.0 / omni.attenuationEnd;\n\t\t\t\t\t\tthis.lightShadowParamsId[cnt].setValue(params);\n\t\t\t\t\t\tthis.lightShadowIntensity[cnt].setValue(omni.shadowIntensity);\n\t\t\t\t\t\tconst pixelsPerMeter = omni.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;\n\t\t\t\t\t\tthis.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter);\n\t\t\t\t\t\tconst cameraParams = omni._shadowCameraParams;\n\t\t\t\t\t\tcameraParams.length = 4;\n\t\t\t\t\t\tcameraParams[0] = 0;\n\t\t\t\t\t\tcameraParams[1] = lightRenderData.shadowCamera._farClip;\n\t\t\t\t\t\tcameraParams[2] = lightRenderData.shadowCamera._nearClip;\n\t\t\t\t\t\tcameraParams[3] = 0;\n\t\t\t\t\t\tthis.lightCameraParamsId[cnt].setValue(cameraParams);\n\t\t\t\t}\n\t\t\t\tif (omni._cookie) {\n\t\t\t\t\t\tthis.lightCookieId[cnt].setValue(omni._cookie);\n\t\t\t\t\t\tthis.lightShadowMatrixId[cnt].setValue(wtm.data);\n\t\t\t\t\t\tthis.lightCookieIntId[cnt].setValue(omni.cookieIntensity);\n\t\t\t\t}\n\t\t}\n\t\tdispatchSpotLight(scope, spot, cnt) {\n\t\t\t\tconst wtm = spot._node.getWorldTransform();\n\t\t\t\tif (!this.lightColorId[cnt]) {\n\t\t\t\t\t\tthis._resolveLight(scope, cnt);\n\t\t\t\t}\n\t\t\t\tthis.lightInAngleId[cnt].setValue(spot._innerConeAngleCos);\n\t\t\t\tthis.lightOutAngleId[cnt].setValue(spot._outerConeAngleCos);\n\t\t\t\tthis.lightRadiusId[cnt].setValue(spot.attenuationEnd);\n\t\t\t\tthis.lightColorId[cnt].setValue(spot._colorLinear);\n\t\t\t\twtm.getTranslation(spot._position);\n\t\t\t\tthis.lightPos[cnt][0] = spot._position.x;\n\t\t\t\tthis.lightPos[cnt][1] = spot._position.y;\n\t\t\t\tthis.lightPos[cnt][2] = spot._position.z;\n\t\t\t\tthis.lightPosId[cnt].setValue(this.lightPos[cnt]);\n\t\t\t\tif (spot.shape !== LIGHTSHAPE_PUNCTUAL) {\n\t\t\t\t\t\tthis.setLTCPositionalLight(wtm, cnt);\n\t\t\t\t}\n\t\t\t\twtm.getY(spot._direction).mulScalar(-1);\n\t\t\t\tspot._direction.normalize();\n\t\t\t\tthis.lightDir[cnt][0] = spot._direction.x;\n\t\t\t\tthis.lightDir[cnt][1] = spot._direction.y;\n\t\t\t\tthis.lightDir[cnt][2] = spot._direction.z;\n\t\t\t\tthis.lightDirId[cnt].setValue(this.lightDir[cnt]);\n\t\t\t\tif (spot.castShadows) {\n\t\t\t\t\t\tconst lightRenderData = spot.getRenderData(null, 0);\n\t\t\t\t\t\tthis.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);\n\t\t\t\t\t\tthis.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);\n\t\t\t\t\t\tconst biases = spot._getUniformBiasValues(lightRenderData);\n\t\t\t\t\t\tconst params = spot._shadowRenderParams;\n\t\t\t\t\t\tparams.length = 4;\n\t\t\t\t\t\tparams[0] = spot._shadowResolution;\n\t\t\t\t\t\tparams[1] = biases.normalBias;\n\t\t\t\t\t\tparams[2] = biases.bias;\n\t\t\t\t\t\tparams[3] = 1.0 / spot.attenuationEnd;\n\t\t\t\t\t\tthis.lightShadowParamsId[cnt].setValue(params);\n\t\t\t\t\t\tthis.lightShadowIntensity[cnt].setValue(spot.shadowIntensity);\n\t\t\t\t\t\tconst pixelsPerMeter = spot.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;\n\t\t\t\t\t\tconst fov = lightRenderData.shadowCamera._fov * Math.PI / 180.0;\n\t\t\t\t\t\tconst fovRatio = 1.0 / Math.tan(fov / 2.0);\n\t\t\t\t\t\tthis.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter * fovRatio);\n\t\t\t\t\t\tconst cameraParams = spot._shadowCameraParams;\n\t\t\t\t\t\tcameraParams.length = 4;\n\t\t\t\t\t\tcameraParams[0] = 0;\n\t\t\t\t\t\tcameraParams[1] = lightRenderData.shadowCamera._farClip;\n\t\t\t\t\t\tcameraParams[2] = lightRenderData.shadowCamera._nearClip;\n\t\t\t\t\t\tcameraParams[3] = 0;\n\t\t\t\t\t\tthis.lightCameraParamsId[cnt].setValue(cameraParams);\n\t\t\t\t}\n\t\t\t\tif (spot._cookie) {\n\t\t\t\t\t\tif (!spot.castShadows) {\n\t\t\t\t\t\t\t\tconst cookieMatrix = LightCamera.evalSpotCookieMatrix(spot);\n\t\t\t\t\t\t\t\tthis.lightShadowMatrixId[cnt].setValue(cookieMatrix.data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.lightCookieId[cnt].setValue(spot._cookie);\n\t\t\t\t\t\tthis.lightCookieIntId[cnt].setValue(spot.cookieIntensity);\n\t\t\t\t\t\tif (spot._cookieTransform) {\n\t\t\t\t\t\t\t\tspot._cookieTransformUniform[0] = spot._cookieTransform.x;\n\t\t\t\t\t\t\t\tspot._cookieTransformUniform[1] = spot._cookieTransform.y;\n\t\t\t\t\t\t\t\tspot._cookieTransformUniform[2] = spot._cookieTransform.z;\n\t\t\t\t\t\t\t\tspot._cookieTransformUniform[3] = spot._cookieTransform.w;\n\t\t\t\t\t\t\t\tthis.lightCookieMatrixId[cnt].setValue(spot._cookieTransformUniform);\n\t\t\t\t\t\t\t\tspot._cookieOffsetUniform[0] = spot._cookieOffset.x;\n\t\t\t\t\t\t\t\tspot._cookieOffsetUniform[1] = spot._cookieOffset.y;\n\t\t\t\t\t\t\t\tthis.lightCookieOffsetId[cnt].setValue(spot._cookieOffsetUniform);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdispatchLocalLights(sortedLights, mask, usedDirLights) {\n\t\t\t\tlet cnt = usedDirLights;\n\t\t\t\tconst scope = this.device.scope;\n\t\t\t\tconst omnis = sortedLights[LIGHTTYPE_OMNI];\n\t\t\t\tconst numOmnis = omnis.length;\n\t\t\t\tfor(let i = 0; i < numOmnis; i++){\n\t\t\t\t\t\tconst omni = omnis[i];\n\t\t\t\t\t\tif (!(omni.mask & mask)) continue;\n\t\t\t\t\t\tthis.dispatchOmniLight(scope, omni, cnt);\n\t\t\t\t\t\tcnt++;\n\t\t\t\t}\n\t\t\t\tconst spts = sortedLights[LIGHTTYPE_SPOT];\n\t\t\t\tconst numSpts = spts.length;\n\t\t\t\tfor(let i = 0; i < numSpts; i++){\n\t\t\t\t\t\tconst spot = spts[i];\n\t\t\t\t\t\tif (!(spot.mask & mask)) continue;\n\t\t\t\t\t\tthis.dispatchSpotLight(scope, spot, cnt);\n\t\t\t\t\t\tcnt++;\n\t\t\t\t}\n\t\t}\n\t\trenderForwardPrepareMaterials(camera, renderTarget, drawCalls, sortedLights, layer, pass) {\n\t\t\t\tconst fogParams = camera.fogParams ?? this.scene.fog;\n\t\t\t\tconst shaderParams = camera.shaderParams;\n\t\t\t\tshaderParams.fog = fogParams.type;\n\t\t\t\tshaderParams.srgbRenderTarget = renderTarget?.isColorBufferSrgb(0) ?? false;\n\t\t\t\tconst addCall = (drawCall, shaderInstance, isNewMaterial, lightMaskChanged)=>{\n\t\t\t\t\t\t_drawCallList.drawCalls.push(drawCall);\n\t\t\t\t\t\t_drawCallList.shaderInstances.push(shaderInstance);\n\t\t\t\t\t\t_drawCallList.isNewMaterial.push(isNewMaterial);\n\t\t\t\t\t\t_drawCallList.lightMaskChanged.push(lightMaskChanged);\n\t\t\t\t};\n\t\t\t\t_drawCallList.clear();\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst scene = this.scene;\n\t\t\t\tconst clusteredLightingEnabled = scene.clusteredLightingEnabled;\n\t\t\t\tconst lightHash = layer?.getLightHash(clusteredLightingEnabled) ?? 0;\n\t\t\t\tlet prevMaterial = null, prevObjDefs, prevLightMask;\n\t\t\t\tconst drawCallsCount = drawCalls.length;\n\t\t\t\tfor(let i = 0; i < drawCallsCount; i++){\n\t\t\t\t\t\tconst drawCall = drawCalls[i];\n\t\t\t\t\t\tconst instancingData = drawCall.instancingData;\n\t\t\t\t\t\tif (instancingData && instancingData.count <= 0) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdrawCall.ensureMaterial(device);\n\t\t\t\t\t\tconst material = drawCall.material;\n\t\t\t\t\t\tconst objDefs = drawCall._shaderDefs;\n\t\t\t\t\t\tconst lightMask = drawCall.mask;\n\t\t\t\t\t\tif (material && material === prevMaterial && objDefs !== prevObjDefs) {\n\t\t\t\t\t\t\t\tprevMaterial = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (material !== prevMaterial) {\n\t\t\t\t\t\t\t\tthis._materialSwitches++;\n\t\t\t\t\t\t\t\tmaterial._scene = scene;\n\t\t\t\t\t\t\t\tif (material.dirty) {\n\t\t\t\t\t\t\t\t\t\tmaterial.updateUniforms(device, scene);\n\t\t\t\t\t\t\t\t\t\tmaterial.dirty = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst shaderInstance = drawCall.getShaderInstance(pass, lightHash, scene, shaderParams, this.viewUniformFormat, this.viewBindGroupFormat, sortedLights);\n\t\t\t\t\t\taddCall(drawCall, shaderInstance, material !== prevMaterial, !prevMaterial || lightMask !== prevLightMask);\n\t\t\t\t\t\tprevMaterial = material;\n\t\t\t\t\t\tprevObjDefs = objDefs;\n\t\t\t\t\t\tprevLightMask = lightMask;\n\t\t\t\t}\n\t\t\t\treturn _drawCallList;\n\t\t}\n\t\trenderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst scene = this.scene;\n\t\t\t\tconst passFlag = 1 << pass;\n\t\t\t\tconst flipFactor = flipFaces ? -1 : 1;\n\t\t\t\tconst clusteredLightingEnabled = scene.clusteredLightingEnabled;\n\t\t\t\tconst viewList = camera.xr?.session && camera.xr.views.list.length ? camera.xr.views.list : null;\n\t\t\t\tconst preparedCallsCount = preparedCalls.drawCalls.length;\n\t\t\t\tfor(let i = 0; i < preparedCallsCount; i++){\n\t\t\t\t\t\tconst drawCall = preparedCalls.drawCalls[i];\n\t\t\t\t\t\tconst newMaterial = preparedCalls.isNewMaterial[i];\n\t\t\t\t\t\tconst lightMaskChanged = preparedCalls.lightMaskChanged[i];\n\t\t\t\t\t\tconst shaderInstance = preparedCalls.shaderInstances[i];\n\t\t\t\t\t\tconst material = drawCall.material;\n\t\t\t\t\t\tconst lightMask = drawCall.mask;\n\t\t\t\t\t\tif (shaderInstance.shader.failed) continue;\n\t\t\t\t\t\tif (newMaterial) {\n\t\t\t\t\t\t\t\tconst asyncCompile = false;\n\t\t\t\t\t\t\t\tdevice.setShader(shaderInstance.shader, asyncCompile);\n\t\t\t\t\t\t\t\tmaterial.setParameters(device);\n\t\t\t\t\t\t\t\tif (lightMaskChanged) {\n\t\t\t\t\t\t\t\t\t\tconst usedDirLights = this.dispatchDirectLights(sortedLights[LIGHTTYPE_DIRECTIONAL], lightMask, camera);\n\t\t\t\t\t\t\t\t\t\tif (!clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.dispatchLocalLights(sortedLights, lightMask, usedDirLights);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.alphaTestId.setValue(material.alphaTest);\n\t\t\t\t\t\t\t\tdevice.setBlendState(material.blendState);\n\t\t\t\t\t\t\t\tdevice.setDepthState(material.depthState);\n\t\t\t\t\t\t\t\tdevice.setAlphaToCoverage(material.alphaToCoverage);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.setupCullMode(camera._cullFaces, flipFactor, drawCall);\n\t\t\t\t\t\tconst stencilFront = drawCall.stencilFront ?? material.stencilFront;\n\t\t\t\t\t\tconst stencilBack = drawCall.stencilBack ?? material.stencilBack;\n\t\t\t\t\t\tdevice.setStencilState(stencilFront, stencilBack);\n\t\t\t\t\t\tdrawCall.setParameters(device, passFlag);\n\t\t\t\t\t\tdevice.scope.resolve('meshInstanceId').setValue(drawCall.id);\n\t\t\t\t\t\tconst mesh = drawCall.mesh;\n\t\t\t\t\t\tthis.setVertexBuffers(device, mesh);\n\t\t\t\t\t\tthis.setMorphing(device, drawCall.morphInstance);\n\t\t\t\t\t\tthis.setSkinning(device, drawCall);\n\t\t\t\t\t\tconst instancingData = drawCall.instancingData;\n\t\t\t\t\t\tif (instancingData) {\n\t\t\t\t\t\t\t\tdevice.setVertexBuffer(instancingData.vertexBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.setMeshInstanceMatrices(drawCall, true);\n\t\t\t\t\t\tthis.setupMeshUniformBuffers(shaderInstance);\n\t\t\t\t\t\tconst style = drawCall.renderStyle;\n\t\t\t\t\t\tconst indexBuffer = mesh.indexBuffer[style];\n\t\t\t\t\t\tdrawCallback?.(drawCall, i);\n\t\t\t\t\t\tconst indirectSlot = drawCall.indirectData?.get(camera);\n\t\t\t\t\t\tif (viewList) {\n\t\t\t\t\t\t\t\tfor(let v = 0; v < viewList.length; v++){\n\t\t\t\t\t\t\t\t\t\tconst view = viewList[v];\n\t\t\t\t\t\t\t\t\t\tdevice.setViewport(view.viewport.x, view.viewport.y, view.viewport.z, view.viewport.w);\n\t\t\t\t\t\t\t\t\t\tif (device.supportsUniformBuffers) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst viewBindGroup = viewBindGroups[v];\n\t\t\t\t\t\t\t\t\t\t\t\tdevice.setBindGroup(BINDGROUP_VIEW, viewBindGroup);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.setupViewUniforms(view, v);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst first = v === 0;\n\t\t\t\t\t\t\t\t\t\tconst last = v === viewList.length - 1;\n\t\t\t\t\t\t\t\t\t\tdevice.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectSlot, first, last);\n\t\t\t\t\t\t\t\t\t\tthis._forwardDrawCalls++;\n\t\t\t\t\t\t\t\t\t\tif (drawCall.instancingData) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._instancedDrawCalls++;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdevice.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectSlot);\n\t\t\t\t\t\t\t\tthis._forwardDrawCalls++;\n\t\t\t\t\t\t\t\tif (drawCall.instancingData) {\n\t\t\t\t\t\t\t\t\t\tthis._instancedDrawCalls++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (i < preparedCallsCount - 1 && !preparedCalls.isNewMaterial[i + 1]) {\n\t\t\t\t\t\t\t\tmaterial.setParameters(device, drawCall.parameters);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\trenderForward(camera, renderTarget, allDrawCalls, sortedLights, pass, drawCallback, layer, flipFaces, viewBindGroups) {\n\t\t\t\tconst preparedCalls = this.renderForwardPrepareMaterials(camera, renderTarget, allDrawCalls, sortedLights, layer, pass);\n\t\t\t\tthis.renderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups);\n\t\t\t\t_drawCallList.clear();\n\t\t}\n\t\trenderForwardLayer(camera, renderTarget, layer, transparent, shaderPass, viewBindGroups, options = {}) {\n\t\t\t\tconst { scene, device } = this;\n\t\t\t\tconst clusteredLightingEnabled = scene.clusteredLightingEnabled;\n\t\t\t\tthis.setupViewport(camera, renderTarget);\n\t\t\t\tlet visible, splitLights;\n\t\t\t\tif (layer) {\n\t\t\t\t\t\tlayer.sortVisible(camera, transparent);\n\t\t\t\t\t\tconst culledInstances = layer.getCulledInstances(camera);\n\t\t\t\t\t\tvisible = transparent ? culledInstances.transparent : culledInstances.opaque;\n\t\t\t\t\t\tscene.immediate.onPreRenderLayer(layer, visible, transparent);\n\t\t\t\t\t\tif (layer.requiresLightCube) {\n\t\t\t\t\t\t\t\tthis.lightCube.update(scene.ambientLight, layer._lights);\n\t\t\t\t\t\t\t\tthis.constantLightCube.setValue(this.lightCube.colors);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsplitLights = layer.splitLights;\n\t\t\t\t} else {\n\t\t\t\t\t\tvisible = options.meshInstances;\n\t\t\t\t\t\tsplitLights = options.splitLights ?? _noLights;\n\t\t\t\t}\n\t\t\t\tif (clusteredLightingEnabled) {\n\t\t\t\t\t\tconst lightClusters = options.lightClusters ?? this.worldClustersAllocator.empty;\n\t\t\t\t\t\tlightClusters.activate();\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tif (!this.clustersDebugRendered && scene.lighting.debugLayer === layer.id) {\n\t\t\t\t\t\t\t\t\t\tthis.clustersDebugRendered = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tscene._activeCamera = camera;\n\t\t\t\tconst fogParams = camera.fogParams ?? this.scene.fog;\n\t\t\t\tthis.setFogConstants(fogParams);\n\t\t\t\tconst viewList = this.setCameraUniforms(camera, renderTarget);\n\t\t\t\tif (device.supportsUniformBuffers) {\n\t\t\t\t\t\tthis.setupViewUniformBuffers(viewBindGroups, this.viewUniformFormat, this.viewBindGroupFormat, viewList);\n\t\t\t\t}\n\t\t\t\tconst clearColor = options.clearColor ?? false;\n\t\t\t\tconst clearDepth = options.clearDepth ?? false;\n\t\t\t\tconst clearStencil = options.clearStencil ?? false;\n\t\t\t\tif (clearColor || clearDepth || clearStencil) {\n\t\t\t\t\t\tthis.clear(camera, clearColor, clearDepth, clearStencil);\n\t\t\t\t}\n\t\t\t\tconst flipFaces = !!(camera._flipFaces ^ renderTarget?.flipY);\n\t\t\t\tconst forwardDrawCalls = this._forwardDrawCalls;\n\t\t\t\tthis.renderForward(camera, renderTarget, visible, splitLights, shaderPass, null, layer, flipFaces, viewBindGroups);\n\t\t\t\tif (layer) {\n\t\t\t\t\t\tlayer._forwardDrawCalls += this._forwardDrawCalls - forwardDrawCalls;\n\t\t\t\t}\n\t\t}\n\t\tsetFogConstants(fogParams) {\n\t\t\t\tif (fogParams.type !== FOG_NONE) {\n\t\t\t\t\t\ttmpColor$1.linear(fogParams.color);\n\t\t\t\t\t\tconst fogUniform = this.fogColor;\n\t\t\t\t\t\tfogUniform[0] = tmpColor$1.r;\n\t\t\t\t\t\tfogUniform[1] = tmpColor$1.g;\n\t\t\t\t\t\tfogUniform[2] = tmpColor$1.b;\n\t\t\t\t\t\tthis.fogColorId.setValue(fogUniform);\n\t\t\t\t\t\tif (fogParams.type === FOG_LINEAR) {\n\t\t\t\t\t\t\t\tthis.fogStartId.setValue(fogParams.start);\n\t\t\t\t\t\t\t\tthis.fogEndId.setValue(fogParams.end);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.fogDensityId.setValue(fogParams.density);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetSceneConstants() {\n\t\t\t\tconst scene = this.scene;\n\t\t\t\tthis.dispatchGlobalLights(scene);\n\t\t\t\tconst device = this.device;\n\t\t\t\tthis._screenSize[0] = device.width;\n\t\t\t\tthis._screenSize[1] = device.height;\n\t\t\t\tthis._screenSize[2] = 1 / device.width;\n\t\t\t\tthis._screenSize[3] = 1 / device.height;\n\t\t\t\tthis.screenSizeId.setValue(this._screenSize);\n\t\t\t\tthis.pcssDiskSamplesId.setValue(this.pcssDiskSamples);\n\t\t\t\tthis.pcssSphereSamplesId.setValue(this.pcssSphereSamples);\n\t\t}\n\t\tbuildFrameGraph(frameGraph, layerComposition) {\n\t\t\t\tconst scene = this.scene;\n\t\t\t\tframeGraph.reset();\n\t\t\t\tif (scene.clusteredLightingEnabled) {\n\t\t\t\t\t\tconst { shadowsEnabled, cookiesEnabled } = scene.lighting;\n\t\t\t\t\t\tthis._renderPassUpdateClustered.update(frameGraph, shadowsEnabled, cookiesEnabled, this.lights, this.localLights);\n\t\t\t\t\t\tframeGraph.addRenderPass(this._renderPassUpdateClustered);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._shadowRendererLocal.buildNonClusteredRenderPasses(frameGraph, this.localLights);\n\t\t\t\t}\n\t\t\t\tlet startIndex = 0;\n\t\t\t\tlet newStart = true;\n\t\t\t\tlet renderTarget = null;\n\t\t\t\tconst renderActions = layerComposition._renderActions;\n\t\t\t\tfor(let i = startIndex; i < renderActions.length; i++){\n\t\t\t\t\t\tconst renderAction = renderActions[i];\n\t\t\t\t\t\tconst { layer, camera } = renderAction;\n\t\t\t\t\t\tif (renderAction.useCameraPasses) {\n\t\t\t\t\t\t\t\tcamera.camera.renderPasses.forEach((renderPass)=>{\n\t\t\t\t\t\t\t\t\t\tframeGraph.addRenderPass(renderPass);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst isDepthLayer = layer.id === LAYERID_DEPTH;\n\t\t\t\t\t\t\t\tconst isGrabPass = isDepthLayer && (camera.renderSceneColorMap || camera.renderSceneDepthMap);\n\t\t\t\t\t\t\t\tif (newStart) {\n\t\t\t\t\t\t\t\t\t\tnewStart = false;\n\t\t\t\t\t\t\t\t\t\tstartIndex = i;\n\t\t\t\t\t\t\t\t\t\trenderTarget = renderAction.renderTarget;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst nextRenderAction = renderActions[i + 1];\n\t\t\t\t\t\t\t\tconst isNextLayerDepth = nextRenderAction ? !nextRenderAction.useCameraPasses && nextRenderAction.layer.id === LAYERID_DEPTH : false;\n\t\t\t\t\t\t\t\tconst isNextLayerGrabPass = isNextLayerDepth && (camera.renderSceneColorMap || camera.renderSceneDepthMap);\n\t\t\t\t\t\t\t\tconst nextNeedDirShadows = nextRenderAction ? nextRenderAction.firstCameraUse && this.cameraDirShadowLights.has(nextRenderAction.camera.camera) : false;\n\t\t\t\t\t\t\t\tif (!nextRenderAction || nextRenderAction.renderTarget !== renderTarget || nextNeedDirShadows || isNextLayerGrabPass || isGrabPass) {\n\t\t\t\t\t\t\t\t\t\tconst isDepthOnly = isDepthLayer && startIndex === i;\n\t\t\t\t\t\t\t\t\t\tif (!isDepthOnly) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, i);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (isDepthLayer) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (camera.renderSceneColorMap) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst colorGrabPass = camera.camera.renderPassColorGrab;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolorGrabPass.source = camera.renderTarget;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tframeGraph.addRenderPass(colorGrabPass);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (camera.renderSceneDepthMap) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tframeGraph.addRenderPass(camera.camera.renderPassDepthGrab);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (renderAction.triggerPostprocess && camera?.onPostprocessing) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst renderPass = new RenderPassPostprocessing(this.device, this, renderAction);\n\t\t\t\t\t\t\t\t\t\t\t\tframeGraph.addRenderPass(renderPass);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tnewStart = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\taddMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, endIndex) {\n\t\t\t\tconst renderPass = new RenderPassForward(this.device, layerComposition, this.scene, this);\n\t\t\t\trenderPass.init(renderTarget);\n\t\t\t\tconst renderActions = layerComposition._renderActions;\n\t\t\t\tfor(let i = startIndex; i <= endIndex; i++){\n\t\t\t\t\t\trenderPass.addRenderAction(renderActions[i]);\n\t\t\t\t}\n\t\t\t\tframeGraph.addRenderPass(renderPass);\n\t\t}\n\t\tupdate(comp) {\n\t\t\t\tthis.frameUpdate();\n\t\t\t\tthis.shadowRenderer.frameUpdate();\n\t\t\t\tthis.scene._updateSkyMesh();\n\t\t\t\tthis.updateLayerComposition(comp);\n\t\t\t\tthis.collectLights(comp);\n\t\t\t\tthis.beginFrame(comp);\n\t\t\t\tthis.setSceneConstants();\n\t\t\t\tthis.gsplatDirector?.update(comp);\n\t\t\t\tthis.cullComposition(comp);\n\t\t\t\tthis.gpuUpdate(this.processingMeshInstances);\n\t\t}\n}\n\nlet layerCounter = 0;\nconst lightKeys = [];\nconst _tempMaterials = new Set();\nfunction sortManual(drawCallA, drawCallB) {\n\t\treturn drawCallA.drawOrder - drawCallB.drawOrder;\n}\nfunction sortMaterialMesh(drawCallA, drawCallB) {\n\t\tconst keyA = drawCallA._sortKeyForward;\n\t\tconst keyB = drawCallB._sortKeyForward;\n\t\tif (keyA === keyB) {\n\t\t\t\treturn drawCallB.mesh.id - drawCallA.mesh.id;\n\t\t}\n\t\treturn keyB - keyA;\n}\nfunction sortBackToFront(drawCallA, drawCallB) {\n\t\treturn drawCallB._sortKeyDynamic - drawCallA._sortKeyDynamic;\n}\nfunction sortFrontToBack(drawCallA, drawCallB) {\n\t\treturn drawCallA._sortKeyDynamic - drawCallB._sortKeyDynamic;\n}\nconst sortCallbacks = [\n\t\tnull,\n\t\tsortManual,\n\t\tsortMaterialMesh,\n\t\tsortBackToFront,\n\t\tsortFrontToBack\n];\nclass CulledInstances {\n\t\tconstructor(){\n\t\t\t\tthis.opaque = [];\n\t\t\t\tthis.transparent = [];\n\t\t}\n}\nclass Layer {\n\t\tconstructor(options = {}){\n\t\t\t\tthis.meshInstances = [];\n\t\t\t\tthis.meshInstancesSet = new Set();\n\t\t\t\tthis.shadowCasters = [];\n\t\t\t\tthis.shadowCastersSet = new Set();\n\t\t\t\tthis._visibleInstances = new WeakMap();\n\t\t\t\tthis._lights = [];\n\t\t\t\tthis._lightsSet = new Set();\n\t\t\t\tthis._clusteredLightsSet = new Set();\n\t\t\t\tthis._splitLights = [\n\t\t\t\t\t\t[],\n\t\t\t\t\t\t[],\n\t\t\t\t\t\t[]\n\t\t\t\t];\n\t\t\t\tthis._splitLightsDirty = true;\n\t\t\t\tthis.requiresLightCube = false;\n\t\t\t\tthis.cameras = [];\n\t\t\t\tthis.camerasSet = new Set();\n\t\t\t\tthis.gsplatPlacements = [];\n\t\t\t\tthis.gsplatPlacementsDirty = true;\n\t\t\t\tthis._dirtyComposition = false;\n\t\t\t\tif (options.id !== undefined) {\n\t\t\t\t\t\tthis.id = options.id;\n\t\t\t\t\t\tlayerCounter = Math.max(this.id + 1, layerCounter);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.id = layerCounter++;\n\t\t\t\t}\n\t\t\t\tthis.name = options.name;\n\t\t\t\tthis._enabled = options.enabled ?? true;\n\t\t\t\tthis._refCounter = this._enabled ? 1 : 0;\n\t\t\t\tthis.opaqueSortMode = options.opaqueSortMode ?? SORTMODE_MATERIALMESH;\n\t\t\t\tthis.transparentSortMode = options.transparentSortMode ?? SORTMODE_BACK2FRONT;\n\t\t\t\tif (options.renderTarget) {\n\t\t\t\t\t\tthis.renderTarget = options.renderTarget;\n\t\t\t\t}\n\t\t\t\tthis._clearColorBuffer = !!options.clearColorBuffer;\n\t\t\t\tthis._clearDepthBuffer = !!options.clearDepthBuffer;\n\t\t\t\tthis._clearStencilBuffer = !!options.clearStencilBuffer;\n\t\t\t\tthis.onEnable = options.onEnable;\n\t\t\t\tthis.onDisable = options.onDisable;\n\t\t\t\tif (this._enabled && this.onEnable) {\n\t\t\t\t\t\tthis.onEnable();\n\t\t\t\t}\n\t\t\t\tthis.customSortCallback = null;\n\t\t\t\tthis.customCalculateSortValues = null;\n\t\t\t\tthis._lightHash = 0;\n\t\t\t\tthis._lightHashDirty = false;\n\t\t\t\tthis._lightIdHash = 0;\n\t\t\t\tthis._lightIdHashDirty = false;\n\t\t\t\tthis._shaderVersion = -1;\n\t\t}\n\t\tset enabled(val) {\n\t\t\t\tif (val !== this._enabled) {\n\t\t\t\t\t\tthis._dirtyComposition = true;\n\t\t\t\t\t\tthis.gsplatPlacementsDirty = true;\n\t\t\t\t\t\tthis._enabled = val;\n\t\t\t\t\t\tif (val) {\n\t\t\t\t\t\t\t\tthis.incrementCounter();\n\t\t\t\t\t\t\t\tif (this.onEnable) this.onEnable();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.decrementCounter();\n\t\t\t\t\t\t\t\tif (this.onDisable) this.onDisable();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled;\n\t\t}\n\t\tset clearColorBuffer(val) {\n\t\t\t\tthis._clearColorBuffer = val;\n\t\t\t\tthis._dirtyComposition = true;\n\t\t}\n\t\tget clearColorBuffer() {\n\t\t\t\treturn this._clearColorBuffer;\n\t\t}\n\t\tset clearDepthBuffer(val) {\n\t\t\t\tthis._clearDepthBuffer = val;\n\t\t\t\tthis._dirtyComposition = true;\n\t\t}\n\t\tget clearDepthBuffer() {\n\t\t\t\treturn this._clearDepthBuffer;\n\t\t}\n\t\tset clearStencilBuffer(val) {\n\t\t\t\tthis._clearStencilBuffer = val;\n\t\t\t\tthis._dirtyComposition = true;\n\t\t}\n\t\tget clearStencilBuffer() {\n\t\t\t\treturn this._clearStencilBuffer;\n\t\t}\n\t\tget hasClusteredLights() {\n\t\t\t\treturn this._clusteredLightsSet.size > 0;\n\t\t}\n\t\tget clusteredLightsSet() {\n\t\t\t\treturn this._clusteredLightsSet;\n\t\t}\n\t\tincrementCounter() {\n\t\t\t\tif (this._refCounter === 0) {\n\t\t\t\t\t\tthis._enabled = true;\n\t\t\t\t\t\tif (this.onEnable) this.onEnable();\n\t\t\t\t}\n\t\t\t\tthis._refCounter++;\n\t\t}\n\t\tdecrementCounter() {\n\t\t\t\tif (this._refCounter === 1) {\n\t\t\t\t\t\tthis._enabled = false;\n\t\t\t\t\t\tif (this.onDisable) this.onDisable();\n\t\t\t\t} else if (this._refCounter === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._refCounter--;\n\t\t}\n\t\taddGSplatPlacement(placement) {\n\t\t\t\tif (!this.gsplatPlacements.includes(placement)) {\n\t\t\t\t\t\tthis.gsplatPlacements.push(placement);\n\t\t\t\t\t\tthis.gsplatPlacementsDirty = true;\n\t\t\t\t}\n\t\t}\n\t\tremoveGSplatPlacement(placement) {\n\t\t\t\tconst index = this.gsplatPlacements.indexOf(placement);\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\tthis.gsplatPlacements.splice(index, 1);\n\t\t\t\t\t\tthis.gsplatPlacementsDirty = true;\n\t\t\t\t}\n\t\t}\n\t\taddMeshInstances(meshInstances, skipShadowCasters) {\n\t\t\t\tconst destMeshInstances = this.meshInstances;\n\t\t\t\tconst destMeshInstancesSet = this.meshInstancesSet;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tconst mi = meshInstances[i];\n\t\t\t\t\t\tif (!destMeshInstancesSet.has(mi)) {\n\t\t\t\t\t\t\t\tdestMeshInstances.push(mi);\n\t\t\t\t\t\t\t\tdestMeshInstancesSet.add(mi);\n\t\t\t\t\t\t\t\t_tempMaterials.add(mi.material);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!skipShadowCasters) {\n\t\t\t\t\t\tthis.addShadowCasters(meshInstances);\n\t\t\t\t}\n\t\t\t\tif (_tempMaterials.size > 0) {\n\t\t\t\t\t\tconst sceneShaderVer = this._shaderVersion;\n\t\t\t\t\t\t_tempMaterials.forEach((mat)=>{\n\t\t\t\t\t\t\t\tif (sceneShaderVer >= 0 && mat._shaderVersion !== sceneShaderVer) {\n\t\t\t\t\t\t\t\t\t\tif (mat.getShaderVariant !== Material.prototype.getShaderVariant) {\n\t\t\t\t\t\t\t\t\t\t\t\tmat.clearVariants();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tmat._shaderVersion = sceneShaderVer;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\t_tempMaterials.clear();\n\t\t\t\t}\n\t\t}\n\t\tremoveMeshInstances(meshInstances, skipShadowCasters) {\n\t\t\t\tconst destMeshInstances = this.meshInstances;\n\t\t\t\tconst destMeshInstancesSet = this.meshInstancesSet;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tconst mi = meshInstances[i];\n\t\t\t\t\t\tif (destMeshInstancesSet.has(mi)) {\n\t\t\t\t\t\t\t\tdestMeshInstancesSet.delete(mi);\n\t\t\t\t\t\t\t\tconst j = destMeshInstances.indexOf(mi);\n\t\t\t\t\t\t\t\tif (j >= 0) {\n\t\t\t\t\t\t\t\t\t\tdestMeshInstances.splice(j, 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!skipShadowCasters) {\n\t\t\t\t\t\tthis.removeShadowCasters(meshInstances);\n\t\t\t\t}\n\t\t}\n\t\taddShadowCasters(meshInstances) {\n\t\t\t\tconst shadowCasters = this.shadowCasters;\n\t\t\t\tconst shadowCastersSet = this.shadowCastersSet;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tconst mi = meshInstances[i];\n\t\t\t\t\t\tif (mi.castShadow && !shadowCastersSet.has(mi)) {\n\t\t\t\t\t\t\t\tshadowCastersSet.add(mi);\n\t\t\t\t\t\t\t\tshadowCasters.push(mi);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremoveShadowCasters(meshInstances) {\n\t\t\t\tconst shadowCasters = this.shadowCasters;\n\t\t\t\tconst shadowCastersSet = this.shadowCastersSet;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tconst mi = meshInstances[i];\n\t\t\t\t\t\tif (shadowCastersSet.has(mi)) {\n\t\t\t\t\t\t\t\tshadowCastersSet.delete(mi);\n\t\t\t\t\t\t\t\tconst j = shadowCasters.indexOf(mi);\n\t\t\t\t\t\t\t\tif (j >= 0) {\n\t\t\t\t\t\t\t\t\t\tshadowCasters.splice(j, 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tclearMeshInstances(skipShadowCasters = false) {\n\t\t\t\tthis.meshInstances.length = 0;\n\t\t\t\tthis.meshInstancesSet.clear();\n\t\t\t\tif (!skipShadowCasters) {\n\t\t\t\t\t\tthis.shadowCasters.length = 0;\n\t\t\t\t\t\tthis.shadowCastersSet.clear();\n\t\t\t\t}\n\t\t}\n\t\tmarkLightsDirty() {\n\t\t\t\tthis._lightHashDirty = true;\n\t\t\t\tthis._lightIdHashDirty = true;\n\t\t\t\tthis._splitLightsDirty = true;\n\t\t}\n\t\thasLight(light) {\n\t\t\t\treturn this._lightsSet.has(light);\n\t\t}\n\t\taddLight(light) {\n\t\t\t\tconst l = light.light;\n\t\t\t\tif (!this._lightsSet.has(l)) {\n\t\t\t\t\t\tthis._lightsSet.add(l);\n\t\t\t\t\t\tthis._lights.push(l);\n\t\t\t\t\t\tthis.markLightsDirty();\n\t\t\t\t}\n\t\t\t\tif (l.type !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\tthis._clusteredLightsSet.add(l);\n\t\t\t\t}\n\t\t}\n\t\tremoveLight(light) {\n\t\t\t\tconst l = light.light;\n\t\t\t\tif (this._lightsSet.has(l)) {\n\t\t\t\t\t\tthis._lightsSet.delete(l);\n\t\t\t\t\t\tthis._lights.splice(this._lights.indexOf(l), 1);\n\t\t\t\t\t\tthis.markLightsDirty();\n\t\t\t\t}\n\t\t\t\tif (l.type !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\tthis._clusteredLightsSet.delete(l);\n\t\t\t\t}\n\t\t}\n\t\tclearLights() {\n\t\t\t\tthis._lightsSet.forEach((light)=>light.removeLayer(this));\n\t\t\t\tthis._lightsSet.clear();\n\t\t\t\tthis._clusteredLightsSet.clear();\n\t\t\t\tthis._lights.length = 0;\n\t\t\t\tthis.markLightsDirty();\n\t\t}\n\t\tget splitLights() {\n\t\t\t\tif (this._splitLightsDirty) {\n\t\t\t\t\t\tthis._splitLightsDirty = false;\n\t\t\t\t\t\tconst splitLights = this._splitLights;\n\t\t\t\t\t\tfor(let i = 0; i < splitLights.length; i++){\n\t\t\t\t\t\t\t\tsplitLights[i].length = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst lights = this._lights;\n\t\t\t\t\t\tfor(let i = 0; i < lights.length; i++){\n\t\t\t\t\t\t\t\tconst light = lights[i];\n\t\t\t\t\t\t\t\tif (light.enabled) {\n\t\t\t\t\t\t\t\t\t\tsplitLights[light._type].push(light);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < splitLights.length; i++){\n\t\t\t\t\t\t\t\tsplitLights[i].sort((a, b)=>a.key - b.key);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this._splitLights;\n\t\t}\n\t\tevaluateLightHash(localLights, directionalLights, useIds) {\n\t\t\t\tlet hash = 0;\n\t\t\t\tconst lights = this._lights;\n\t\t\t\tfor(let i = 0; i < lights.length; i++){\n\t\t\t\t\t\tconst isLocalLight = lights[i].type !== LIGHTTYPE_DIRECTIONAL;\n\t\t\t\t\t\tif (localLights && isLocalLight || directionalLights && !isLocalLight) {\n\t\t\t\t\t\t\t\tlightKeys.push(useIds ? lights[i].id : lights[i].key);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (lightKeys.length > 0) {\n\t\t\t\t\t\tlightKeys.sort();\n\t\t\t\t\t\thash = hash32Fnv1a(lightKeys);\n\t\t\t\t\t\tlightKeys.length = 0;\n\t\t\t\t}\n\t\t\t\treturn hash;\n\t\t}\n\t\tgetLightHash(isClustered) {\n\t\t\t\tif (this._lightHashDirty) {\n\t\t\t\t\t\tthis._lightHashDirty = false;\n\t\t\t\t\t\tthis._lightHash = this.evaluateLightHash(!isClustered, true, false);\n\t\t\t\t}\n\t\t\t\treturn this._lightHash;\n\t\t}\n\t\tgetLightIdHash() {\n\t\t\t\tif (this._lightIdHashDirty) {\n\t\t\t\t\t\tthis._lightIdHashDirty = false;\n\t\t\t\t\t\tthis._lightIdHash = this.evaluateLightHash(true, false, true);\n\t\t\t\t}\n\t\t\t\treturn this._lightIdHash;\n\t\t}\n\t\taddCamera(camera) {\n\t\t\t\tif (!this.camerasSet.has(camera.camera)) {\n\t\t\t\t\t\tthis.camerasSet.add(camera.camera);\n\t\t\t\t\t\tthis.cameras.push(camera);\n\t\t\t\t\t\tthis._dirtyComposition = true;\n\t\t\t\t}\n\t\t}\n\t\tremoveCamera(camera) {\n\t\t\t\tif (this.camerasSet.has(camera.camera)) {\n\t\t\t\t\t\tthis.camerasSet.delete(camera.camera);\n\t\t\t\t\t\tconst index = this.cameras.indexOf(camera);\n\t\t\t\t\t\tthis.cameras.splice(index, 1);\n\t\t\t\t\t\tthis._dirtyComposition = true;\n\t\t\t\t}\n\t\t}\n\t\tclearCameras() {\n\t\t\t\tthis.cameras.length = 0;\n\t\t\t\tthis.camerasSet.clear();\n\t\t\t\tthis._dirtyComposition = true;\n\t\t}\n\t\t_calculateSortDistances(drawCalls, camPos, camFwd) {\n\t\t\t\tconst count = drawCalls.length;\n\t\t\t\tconst { x: px, y: py, z: pz } = camPos;\n\t\t\t\tconst { x: fx, y: fy, z: fz } = camFwd;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst drawCall = drawCalls[i];\n\t\t\t\t\t\tlet zDist;\n\t\t\t\t\t\tif (drawCall.calculateSortDistance) {\n\t\t\t\t\t\t\t\tzDist = drawCall.calculateSortDistance(drawCall, camPos, camFwd);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst meshPos = drawCall.aabb.center;\n\t\t\t\t\t\t\t\tzDist = (meshPos.x - px) * fx + (meshPos.y - py) * fy + (meshPos.z - pz) * fz;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst bucket = drawCall._drawBucket * 1e9;\n\t\t\t\t\t\tdrawCall._sortKeyDynamic = bucket + zDist;\n\t\t\t\t}\n\t\t}\n\t\tgetCulledInstances(camera) {\n\t\t\t\tlet instances = this._visibleInstances.get(camera);\n\t\t\t\tif (!instances) {\n\t\t\t\t\t\tinstances = new CulledInstances();\n\t\t\t\t\t\tthis._visibleInstances.set(camera, instances);\n\t\t\t\t}\n\t\t\t\treturn instances;\n\t\t}\n\t\tsortVisible(camera, transparent) {\n\t\t\t\tconst sortMode = transparent ? this.transparentSortMode : this.opaqueSortMode;\n\t\t\t\tif (sortMode === SORTMODE_NONE) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst culledInstances = this.getCulledInstances(camera);\n\t\t\t\tconst instances = transparent ? culledInstances.transparent : culledInstances.opaque;\n\t\t\t\tconst cameraNode = camera.node;\n\t\t\t\tif (sortMode === SORTMODE_CUSTOM) {\n\t\t\t\t\t\tconst sortPos = cameraNode.getPosition();\n\t\t\t\t\t\tconst sortDir = cameraNode.forward;\n\t\t\t\t\t\tif (this.customCalculateSortValues) {\n\t\t\t\t\t\t\t\tthis.customCalculateSortValues(instances, instances.length, sortPos, sortDir);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.customSortCallback) {\n\t\t\t\t\t\t\t\tinstances.sort(this.customSortCallback);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (sortMode === SORTMODE_BACK2FRONT || sortMode === SORTMODE_FRONT2BACK) {\n\t\t\t\t\t\t\t\tconst sortPos = cameraNode.getPosition();\n\t\t\t\t\t\t\t\tconst sortDir = cameraNode.forward;\n\t\t\t\t\t\t\t\tthis._calculateSortDistances(instances, sortPos, sortDir);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinstances.sort(sortCallbacks[sortMode]);\n\t\t\t\t}\n\t\t}\n}\n\nconst cmpPriority = (a, b)=>a.priority - b.priority;\nconst sortPriority = (arr)=>arr.sort(cmpPriority);\n\nclass LayerComposition extends EventHandler {\n\t\tconstructor(name = 'Untitled'){\n\t\t\t\tsuper(), this.layerList = [], this.layerIdMap = new Map(), this.layerNameMap = new Map(), this.layerOpaqueIndexMap = new Map(), this.layerTransparentIndexMap = new Map(), this.subLayerList = [], this.subLayerEnabled = [], this.cameras = [], this.camerasSet = new Set(), this._renderActions = [], this._dirty = false;\n\t\t\t\tthis.name = name;\n\t\t\t\tthis._opaqueOrder = {};\n\t\t\t\tthis._transparentOrder = {};\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.destroyRenderActions();\n\t\t}\n\t\tdestroyRenderActions() {\n\t\t\t\tthis._renderActions.forEach((ra)=>ra.destroy());\n\t\t\t\tthis._renderActions.length = 0;\n\t\t}\n\t\tmarkDirty() {\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\t_update() {\n\t\t\t\tconst len = this.layerList.length;\n\t\t\t\tif (!this._dirty) {\n\t\t\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\t\t\tif (this.layerList[i]._dirtyComposition) {\n\t\t\t\t\t\t\t\t\t\tthis._dirty = true;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._dirty) {\n\t\t\t\t\t\tthis._dirty = false;\n\t\t\t\t\t\tthis.cameras.length = 0;\n\t\t\t\t\t\tthis.camerasSet.clear();\n\t\t\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\t\t\tconst layer = this.layerList[i];\n\t\t\t\t\t\t\t\tlayer._dirtyComposition = false;\n\t\t\t\t\t\t\t\tfor(let j = 0; j < layer.cameras.length; j++){\n\t\t\t\t\t\t\t\t\t\tconst cameraComponent = layer.cameras[j];\n\t\t\t\t\t\t\t\t\t\tif (!this.camerasSet.has(cameraComponent.camera)) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.camerasSet.add(cameraComponent.camera);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.cameras.push(cameraComponent);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.cameras.length > 1) {\n\t\t\t\t\t\t\t\tsortPriority(this.cameras);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet renderActionCount = 0;\n\t\t\t\t\t\tthis.destroyRenderActions();\n\t\t\t\t\t\tfor(let i = 0; i < this.cameras.length; i++){\n\t\t\t\t\t\t\t\tconst camera = this.cameras[i];\n\t\t\t\t\t\t\t\tif (camera.camera.renderPasses.length > 0) {\n\t\t\t\t\t\t\t\t\t\tthis.addDummyRenderAction(renderActionCount, camera);\n\t\t\t\t\t\t\t\t\t\trenderActionCount++;\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet cameraFirstRenderAction = true;\n\t\t\t\t\t\t\t\tconst cameraFirstRenderActionIndex = renderActionCount;\n\t\t\t\t\t\t\t\tlet lastRenderAction = null;\n\t\t\t\t\t\t\t\tlet postProcessMarked = false;\n\t\t\t\t\t\t\t\tfor(let j = 0; j < len; j++){\n\t\t\t\t\t\t\t\t\t\tconst layer = this.layerList[j];\n\t\t\t\t\t\t\t\t\t\tconst isLayerEnabled = layer.enabled && this.subLayerEnabled[j];\n\t\t\t\t\t\t\t\t\t\tif (isLayerEnabled) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (layer.cameras.length > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (camera.layers.indexOf(layer.id) >= 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (!postProcessMarked && layer.id === camera.disablePostEffectsLayer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpostProcessMarked = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (lastRenderAction) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlastRenderAction.triggerPostprocess = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst isTransparent = this.subLayerList[j];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlastRenderAction = this.addRenderAction(renderActionCount, layer, isTransparent, camera, cameraFirstRenderAction, postProcessMarked);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trenderActionCount++;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcameraFirstRenderAction = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (cameraFirstRenderActionIndex < renderActionCount) {\n\t\t\t\t\t\t\t\t\t\tlastRenderAction.lastCameraUse = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!postProcessMarked && lastRenderAction) {\n\t\t\t\t\t\t\t\t\t\tlastRenderAction.triggerPostprocess = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (camera.renderTarget && camera.postEffectsEnabled) {\n\t\t\t\t\t\t\t\t\t\tthis.propagateRenderTarget(cameraFirstRenderActionIndex - 1, camera);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._logRenderActions();\n\t\t\t\t}\n\t\t}\n\t\tgetNextRenderAction(renderActionIndex) {\n\t\t\t\tconst renderAction = new RenderAction();\n\t\t\t\tthis._renderActions.push(renderAction);\n\t\t\t\treturn renderAction;\n\t\t}\n\t\taddDummyRenderAction(renderActionIndex, camera) {\n\t\t\t\tconst renderAction = this.getNextRenderAction(renderActionIndex);\n\t\t\t\trenderAction.camera = camera;\n\t\t\t\trenderAction.useCameraPasses = true;\n\t\t}\n\t\taddRenderAction(renderActionIndex, layer, isTransparent, camera, cameraFirstRenderAction, postProcessMarked) {\n\t\t\t\tlet rt = layer.id !== LAYERID_DEPTH ? camera.renderTarget : null;\n\t\t\t\tlet used = false;\n\t\t\t\tconst renderActions = this._renderActions;\n\t\t\t\tfor(let i = renderActionIndex - 1; i >= 0; i--){\n\t\t\t\t\t\tif (renderActions[i].camera === camera && renderActions[i].renderTarget === rt) {\n\t\t\t\t\t\t\t\tused = true;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (postProcessMarked && camera.postEffectsEnabled) {\n\t\t\t\t\t\trt = null;\n\t\t\t\t}\n\t\t\t\tconst renderAction = this.getNextRenderAction(renderActionIndex);\n\t\t\t\trenderAction.triggerPostprocess = false;\n\t\t\t\trenderAction.layer = layer;\n\t\t\t\trenderAction.transparent = isTransparent;\n\t\t\t\trenderAction.camera = camera;\n\t\t\t\trenderAction.renderTarget = rt;\n\t\t\t\trenderAction.firstCameraUse = cameraFirstRenderAction;\n\t\t\t\trenderAction.lastCameraUse = false;\n\t\t\t\tconst needsCameraClear = cameraFirstRenderAction || !used;\n\t\t\t\tconst needsLayerClear = layer.clearColorBuffer || layer.clearDepthBuffer || layer.clearStencilBuffer;\n\t\t\t\tif (needsCameraClear || needsLayerClear) {\n\t\t\t\t\t\trenderAction.setupClears(needsCameraClear ? camera : undefined, layer);\n\t\t\t\t}\n\t\t\t\treturn renderAction;\n\t\t}\n\t\tpropagateRenderTarget(startIndex, fromCamera) {\n\t\t\t\tfor(let a = startIndex; a >= 0; a--){\n\t\t\t\t\t\tconst ra = this._renderActions[a];\n\t\t\t\t\t\tconst layer = ra.layer;\n\t\t\t\t\t\tif (ra.renderTarget && layer.id !== LAYERID_DEPTH) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (layer.id === LAYERID_DEPTH) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ra.useCameraPasses) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst thisCamera = ra?.camera.camera;\n\t\t\t\t\t\tif (thisCamera) {\n\t\t\t\t\t\t\t\tif (!fromCamera.camera.rect.equals(thisCamera.rect) || !fromCamera.camera.scissorRect.equals(thisCamera.scissorRect)) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tra.renderTarget = fromCamera.renderTarget;\n\t\t\t\t}\n\t\t}\n\t\t_logRenderActions() {}\n\t\t_isLayerAdded(layer) {\n\t\t\t\tconst found = this.layerIdMap.get(layer.id) === layer;\n\t\t\t\treturn found;\n\t\t}\n\t\t_isSublayerAdded(layer, transparent) {\n\t\t\t\tconst map = transparent ? this.layerTransparentIndexMap : this.layerOpaqueIndexMap;\n\t\t\t\tif (map.get(layer) !== undefined) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tpush(layer) {\n\t\t\t\tif (this._isLayerAdded(layer)) return;\n\t\t\t\tthis.layerList.push(layer);\n\t\t\t\tthis.layerList.push(layer);\n\t\t\t\tthis._opaqueOrder[layer.id] = this.subLayerList.push(false) - 1;\n\t\t\t\tthis._transparentOrder[layer.id] = this.subLayerList.push(true) - 1;\n\t\t\t\tthis.subLayerEnabled.push(true);\n\t\t\t\tthis.subLayerEnabled.push(true);\n\t\t\t\tthis._updateLayerMaps();\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis.fire('add', layer);\n\t\t}\n\t\tinsert(layer, index) {\n\t\t\t\tif (this._isLayerAdded(layer)) return;\n\t\t\t\tthis.layerList.splice(index, 0, layer, layer);\n\t\t\t\tthis.subLayerList.splice(index, 0, false, true);\n\t\t\t\tconst count = this.layerList.length;\n\t\t\t\tthis._updateOpaqueOrder(index, count - 1);\n\t\t\t\tthis._updateTransparentOrder(index, count - 1);\n\t\t\t\tthis.subLayerEnabled.splice(index, 0, true, true);\n\t\t\t\tthis._updateLayerMaps();\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis.fire('add', layer);\n\t\t}\n\t\tremove(layer) {\n\t\t\t\tlet id = this.layerList.indexOf(layer);\n\t\t\t\tdelete this._opaqueOrder[id];\n\t\t\t\tdelete this._transparentOrder[id];\n\t\t\t\twhile(id >= 0){\n\t\t\t\t\t\tthis.layerList.splice(id, 1);\n\t\t\t\t\t\tthis.subLayerList.splice(id, 1);\n\t\t\t\t\t\tthis.subLayerEnabled.splice(id, 1);\n\t\t\t\t\t\tid = this.layerList.indexOf(layer);\n\t\t\t\t\t\tthis._dirty = true;\n\t\t\t\t\t\tthis.fire('remove', layer);\n\t\t\t\t}\n\t\t\t\tconst count = this.layerList.length;\n\t\t\t\tthis._updateOpaqueOrder(0, count - 1);\n\t\t\t\tthis._updateTransparentOrder(0, count - 1);\n\t\t\t\tthis._updateLayerMaps();\n\t\t}\n\t\tpushOpaque(layer) {\n\t\t\t\tif (this._isSublayerAdded(layer, false)) return;\n\t\t\t\tthis.layerList.push(layer);\n\t\t\t\tthis._opaqueOrder[layer.id] = this.subLayerList.push(false) - 1;\n\t\t\t\tthis.subLayerEnabled.push(true);\n\t\t\t\tthis._updateLayerMaps();\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis.fire('add', layer);\n\t\t}\n\t\tinsertOpaque(layer, index) {\n\t\t\t\tif (this._isSublayerAdded(layer, false)) return;\n\t\t\t\tthis.layerList.splice(index, 0, layer);\n\t\t\t\tthis.subLayerList.splice(index, 0, false);\n\t\t\t\tconst count = this.subLayerList.length;\n\t\t\t\tthis._updateOpaqueOrder(index, count - 1);\n\t\t\t\tthis.subLayerEnabled.splice(index, 0, true);\n\t\t\t\tthis._updateLayerMaps();\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis.fire('add', layer);\n\t\t}\n\t\tremoveOpaque(layer) {\n\t\t\t\tfor(let i = 0, len = this.layerList.length; i < len; i++){\n\t\t\t\t\t\tif (this.layerList[i] === layer && !this.subLayerList[i]) {\n\t\t\t\t\t\t\t\tthis.layerList.splice(i, 1);\n\t\t\t\t\t\t\t\tthis.subLayerList.splice(i, 1);\n\t\t\t\t\t\t\t\tlen--;\n\t\t\t\t\t\t\t\tthis._updateOpaqueOrder(i, len - 1);\n\t\t\t\t\t\t\t\tthis.subLayerEnabled.splice(i, 1);\n\t\t\t\t\t\t\t\tthis._dirty = true;\n\t\t\t\t\t\t\t\tif (this.layerList.indexOf(layer) < 0) {\n\t\t\t\t\t\t\t\t\t\tthis.fire('remove', layer);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._updateLayerMaps();\n\t\t}\n\t\tpushTransparent(layer) {\n\t\t\t\tif (this._isSublayerAdded(layer, true)) return;\n\t\t\t\tthis.layerList.push(layer);\n\t\t\t\tthis._transparentOrder[layer.id] = this.subLayerList.push(true) - 1;\n\t\t\t\tthis.subLayerEnabled.push(true);\n\t\t\t\tthis._updateLayerMaps();\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis.fire('add', layer);\n\t\t}\n\t\tinsertTransparent(layer, index) {\n\t\t\t\tif (this._isSublayerAdded(layer, true)) return;\n\t\t\t\tthis.layerList.splice(index, 0, layer);\n\t\t\t\tthis.subLayerList.splice(index, 0, true);\n\t\t\t\tconst count = this.subLayerList.length;\n\t\t\t\tthis._updateTransparentOrder(index, count - 1);\n\t\t\t\tthis.subLayerEnabled.splice(index, 0, true);\n\t\t\t\tthis._updateLayerMaps();\n\t\t\t\tthis._dirty = true;\n\t\t\t\tthis.fire('add', layer);\n\t\t}\n\t\tremoveTransparent(layer) {\n\t\t\t\tfor(let i = 0, len = this.layerList.length; i < len; i++){\n\t\t\t\t\t\tif (this.layerList[i] === layer && this.subLayerList[i]) {\n\t\t\t\t\t\t\t\tthis.layerList.splice(i, 1);\n\t\t\t\t\t\t\t\tthis.subLayerList.splice(i, 1);\n\t\t\t\t\t\t\t\tlen--;\n\t\t\t\t\t\t\t\tthis._updateTransparentOrder(i, len - 1);\n\t\t\t\t\t\t\t\tthis.subLayerEnabled.splice(i, 1);\n\t\t\t\t\t\t\t\tthis._dirty = true;\n\t\t\t\t\t\t\t\tif (this.layerList.indexOf(layer) < 0) {\n\t\t\t\t\t\t\t\t\t\tthis.fire('remove', layer);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._updateLayerMaps();\n\t\t}\n\t\tgetOpaqueIndex(layer) {\n\t\t\t\treturn this.layerOpaqueIndexMap.get(layer) ?? -1;\n\t\t}\n\t\tgetTransparentIndex(layer) {\n\t\t\t\treturn this.layerTransparentIndexMap.get(layer) ?? -1;\n\t\t}\n\t\tisEnabled(layer, transparent) {\n\t\t\t\tif (layer.enabled) {\n\t\t\t\t\t\tconst index = transparent ? this.getTransparentIndex(layer) : this.getOpaqueIndex(layer);\n\t\t\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\t\t\treturn this.subLayerEnabled[index];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\t_updateLayerMaps() {\n\t\t\t\tthis.layerIdMap.clear();\n\t\t\t\tthis.layerNameMap.clear();\n\t\t\t\tthis.layerOpaqueIndexMap.clear();\n\t\t\t\tthis.layerTransparentIndexMap.clear();\n\t\t\t\tfor(let i = 0; i < this.layerList.length; i++){\n\t\t\t\t\t\tconst layer = this.layerList[i];\n\t\t\t\t\t\tthis.layerIdMap.set(layer.id, layer);\n\t\t\t\t\t\tthis.layerNameMap.set(layer.name, layer);\n\t\t\t\t\t\tconst subLayerIndexMap = this.subLayerList[i] ? this.layerTransparentIndexMap : this.layerOpaqueIndexMap;\n\t\t\t\t\t\tsubLayerIndexMap.set(layer, i);\n\t\t\t\t}\n\t\t}\n\t\tgetLayerById(id) {\n\t\t\t\treturn this.layerIdMap.get(id) ?? null;\n\t\t}\n\t\tgetLayerByName(name) {\n\t\t\t\treturn this.layerNameMap.get(name) ?? null;\n\t\t}\n\t\t_updateOpaqueOrder(startIndex, endIndex) {\n\t\t\t\tfor(let i = startIndex; i <= endIndex; i++){\n\t\t\t\t\t\tif (this.subLayerList[i] === false) {\n\t\t\t\t\t\t\t\tthis._opaqueOrder[this.layerList[i].id] = i;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateTransparentOrder(startIndex, endIndex) {\n\t\t\t\tfor(let i = startIndex; i <= endIndex; i++){\n\t\t\t\t\t\tif (this.subLayerList[i] === true) {\n\t\t\t\t\t\t\t\tthis._transparentOrder[this.layerList[i].id] = i;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_sortLayersDescending(layersA, layersB, order) {\n\t\t\t\tlet topLayerA = -1;\n\t\t\t\tlet topLayerB = -1;\n\t\t\t\tfor(let i = 0, len = layersA.length; i < len; i++){\n\t\t\t\t\t\tconst id = layersA[i];\n\t\t\t\t\t\tif (order.hasOwnProperty(id)) {\n\t\t\t\t\t\t\t\ttopLayerA = Math.max(topLayerA, order[id]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0, len = layersB.length; i < len; i++){\n\t\t\t\t\t\tconst id = layersB[i];\n\t\t\t\t\t\tif (order.hasOwnProperty(id)) {\n\t\t\t\t\t\t\t\ttopLayerB = Math.max(topLayerB, order[id]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (topLayerA === -1 && topLayerB !== -1) {\n\t\t\t\t\t\treturn 1;\n\t\t\t\t} else if (topLayerB === -1 && topLayerA !== -1) {\n\t\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\treturn topLayerB - topLayerA;\n\t\t}\n\t\tsortTransparentLayers(layersA, layersB) {\n\t\t\t\treturn this._sortLayersDescending(layersA, layersB, this._transparentOrder);\n\t\t}\n\t\tsortOpaqueLayers(layersA, layersB) {\n\t\t\t\treturn this._sortLayersDescending(layersA, layersB, this._opaqueOrder);\n\t\t}\n}\n\nconst tmpVec = new Vec3();\nconst tmpBiases = {\n\t\tbias: 0,\n\t\tnormalBias: 0\n};\nconst tmpColor = new Color();\nconst chanId = {\n\t\tr: 0,\n\t\tg: 1,\n\t\tb: 2,\n\t\ta: 3\n};\nconst lightTypes = {\n\t\t'directional': LIGHTTYPE_DIRECTIONAL,\n\t\t'omni': LIGHTTYPE_OMNI,\n\t\t'point': LIGHTTYPE_OMNI,\n\t\t'spot': LIGHTTYPE_SPOT\n};\nconst directionalCascades = [\n\t\t[\n\t\t\t\tnew Vec4(0, 0, 1, 1)\n\t\t],\n\t\t[\n\t\t\t\tnew Vec4(0, 0, 0.5, 0.5),\n\t\t\t\tnew Vec4(0, 0.5, 0.5, 0.5)\n\t\t],\n\t\t[\n\t\t\t\tnew Vec4(0, 0, 0.5, 0.5),\n\t\t\t\tnew Vec4(0, 0.5, 0.5, 0.5),\n\t\t\t\tnew Vec4(0.5, 0, 0.5, 0.5)\n\t\t],\n\t\t[\n\t\t\t\tnew Vec4(0, 0, 0.5, 0.5),\n\t\t\t\tnew Vec4(0, 0.5, 0.5, 0.5),\n\t\t\t\tnew Vec4(0.5, 0, 0.5, 0.5),\n\t\t\t\tnew Vec4(0.5, 0.5, 0.5, 0.5)\n\t\t]\n];\nconst channelMap = {\n\t\t'rrr': 0b0001,\n\t\t'ggg': 0b0010,\n\t\t'bbb': 0b0100,\n\t\t'aaa': 0b1000,\n\t\t'rgb': 0b0111\n};\nlet id$2 = 0;\nclass LightRenderData {\n\t\tconstructor(camera, face, light){\n\t\t\t\tthis.light = light;\n\t\t\t\tthis.camera = camera;\n\t\t\t\tthis.shadowCamera = ShadowRenderer.createShadowCamera(light._shadowType, light._type, face);\n\t\t\t\tthis.shadowMatrix = new Mat4();\n\t\t\t\tthis.shadowViewport = new Vec4(0, 0, 1, 1);\n\t\t\t\tthis.shadowScissor = new Vec4(0, 0, 1, 1);\n\t\t\t\tthis.projectionCompensation = 0;\n\t\t\t\tthis.face = face;\n\t\t\t\tthis.visibleCasters = [];\n\t\t\t\tthis.viewBindGroups = [];\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.viewBindGroups.forEach((bg)=>{\n\t\t\t\t\t\tbg.defaultUniformBuffer.destroy();\n\t\t\t\t\t\tbg.destroy();\n\t\t\t\t});\n\t\t\t\tthis.viewBindGroups.length = 0;\n\t\t}\n\t\tget shadowBuffer() {\n\t\t\t\tconst rt = this.shadowCamera.renderTarget;\n\t\t\t\tif (rt) {\n\t\t\t\t\t\treturn this.light._isPcf ? rt.depthBuffer : rt.colorBuffer;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n}\nclass Light {\n\t\tconstructor(graphicsDevice, clusteredLighting){\n\t\t\t\tthis.layers = new Set();\n\t\t\t\tthis.shadowDepthState = DepthState.DEFAULT.clone();\n\t\t\t\tthis.clusteredFlags = 0;\n\t\t\t\tthis.clusteredData = new Uint32Array(3);\n\t\t\t\tthis.clusteredData16 = new Uint16Array(this.clusteredData.buffer);\n\t\t\t\tthis._evtDeviceRestored = null;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.clusteredLighting = clusteredLighting;\n\t\t\t\tthis.id = id$2++;\n\t\t\t\tthis._evtDeviceRestored = graphicsDevice.on('devicerestored', this.onDeviceRestored, this);\n\t\t\t\tthis._type = LIGHTTYPE_DIRECTIONAL;\n\t\t\t\tthis._color = new Color(0.8, 0.8, 0.8);\n\t\t\t\tthis._intensity = 1;\n\t\t\t\tthis._affectSpecularity = true;\n\t\t\t\tthis._luminance = 0;\n\t\t\t\tthis._castShadows = false;\n\t\t\t\tthis._enabled = false;\n\t\t\t\tthis._mask = MASK_AFFECT_DYNAMIC;\n\t\t\t\tthis.isStatic = false;\n\t\t\t\tthis.key = 0;\n\t\t\t\tthis.bakeDir = true;\n\t\t\t\tthis.bakeNumSamples = 1;\n\t\t\t\tthis.bakeArea = 0;\n\t\t\t\tthis.attenuationStart = 10;\n\t\t\t\tthis.attenuationEnd = 10;\n\t\t\t\tthis._falloffMode = LIGHTFALLOFF_LINEAR;\n\t\t\t\tthis._shadowType = SHADOW_PCF3_32F;\n\t\t\t\tthis._vsmBlurSize = 11;\n\t\t\t\tthis.vsmBlurMode = BLUR_GAUSSIAN;\n\t\t\t\tthis.vsmBias = 0.01 * 0.25;\n\t\t\t\tthis._cookie = null;\n\t\t\t\tthis.cookieIntensity = 1;\n\t\t\t\tthis._cookieFalloff = true;\n\t\t\t\tthis._cookieChannel = 'rgb';\n\t\t\t\tthis._cookieTransform = null;\n\t\t\t\tthis._cookieTransformUniform = new Float32Array(4);\n\t\t\t\tthis._cookieOffset = null;\n\t\t\t\tthis._cookieOffsetUniform = new Float32Array(2);\n\t\t\t\tthis._cookieTransformSet = false;\n\t\t\t\tthis._cookieOffsetSet = false;\n\t\t\t\tthis._innerConeAngle = 40;\n\t\t\t\tthis._outerConeAngle = 45;\n\t\t\t\tthis.cascades = null;\n\t\t\t\tthis._shadowMatrixPalette = null;\n\t\t\t\tthis._shadowCascadeDistances = null;\n\t\t\t\tthis.numCascades = 1;\n\t\t\t\tthis._cascadeBlend = 0;\n\t\t\t\tthis.cascadeDistribution = 0.5;\n\t\t\t\tthis._shape = LIGHTSHAPE_PUNCTUAL;\n\t\t\t\tthis._colorLinear = new Float32Array(3);\n\t\t\t\tthis._updateLinearColor();\n\t\t\t\tthis._position = new Vec3(0, 0, 0);\n\t\t\t\tthis._direction = new Vec3(0, 0, 0);\n\t\t\t\tthis._innerConeAngleCos = Math.cos(this._innerConeAngle * Math.PI / 180);\n\t\t\t\tthis._updateOuterAngle(this._outerConeAngle);\n\t\t\t\tthis._usePhysicalUnits = undefined;\n\t\t\t\tthis._shadowMap = null;\n\t\t\t\tthis._shadowRenderParams = [];\n\t\t\t\tthis._shadowCameraParams = [];\n\t\t\t\tthis.shadowDistance = 40;\n\t\t\t\tthis._shadowResolution = 1024;\n\t\t\t\tthis._shadowBias = -5e-4;\n\t\t\t\tthis._shadowIntensity = 1.0;\n\t\t\t\tthis._normalOffsetBias = 0.0;\n\t\t\t\tthis.shadowUpdateMode = SHADOWUPDATE_REALTIME;\n\t\t\t\tthis.shadowUpdateOverrides = null;\n\t\t\t\tthis._isVsm = false;\n\t\t\t\tthis._isPcf = true;\n\t\t\t\tthis._softShadowParams = new Float32Array(4);\n\t\t\t\tthis.shadowSamples = 16;\n\t\t\t\tthis.shadowBlockerSamples = 16;\n\t\t\t\tthis.penumbraSize = 1.0;\n\t\t\t\tthis.penumbraFalloff = 1.0;\n\t\t\t\tthis._cookieMatrix = null;\n\t\t\t\tthis._atlasViewport = null;\n\t\t\t\tthis.atlasViewportAllocated = false;\n\t\t\t\tthis.atlasVersion = 0;\n\t\t\t\tthis.atlasSlotIndex = 0;\n\t\t\t\tthis.atlasSlotUpdated = false;\n\t\t\t\tthis._node = null;\n\t\t\t\tthis._renderData = [];\n\t\t\t\tthis.visibleThisFrame = false;\n\t\t\t\tthis.maxScreenSize = 0;\n\t\t\t\tthis._updateShadowBias();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._evtDeviceRestored?.off();\n\t\t\t\tthis._evtDeviceRestored = null;\n\t\t\t\tthis._destroyShadowMap();\n\t\t\t\tthis.releaseRenderData();\n\t\t\t\tthis._renderData = null;\n\t\t}\n\t\tonDeviceRestored() {\n\t\t\t\tif (this.shadowUpdateMode === SHADOWUPDATE_NONE) {\n\t\t\t\t\t\tthis.shadowUpdateMode = SHADOWUPDATE_THISFRAME;\n\t\t\t\t}\n\t\t}\n\t\treleaseRenderData() {\n\t\t\t\tif (this._renderData) {\n\t\t\t\t\t\tfor(let i = 0; i < this._renderData.length; i++){\n\t\t\t\t\t\t\t\tthis._renderData[i].destroy();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._renderData.length = 0;\n\t\t\t\t}\n\t\t}\n\t\taddLayer(layer) {\n\t\t\t\tthis.layers.add(layer);\n\t\t}\n\t\tremoveLayer(layer) {\n\t\t\t\tthis.layers.delete(layer);\n\t\t}\n\t\tset shadowSamples(value) {\n\t\t\t\tthis._softShadowParams[0] = value;\n\t\t}\n\t\tget shadowSamples() {\n\t\t\t\treturn this._softShadowParams[0];\n\t\t}\n\t\tset shadowBlockerSamples(value) {\n\t\t\t\tthis._softShadowParams[1] = value;\n\t\t}\n\t\tget shadowBlockerSamples() {\n\t\t\t\treturn this._softShadowParams[1];\n\t\t}\n\t\tset shadowBias(value) {\n\t\t\t\tif (this._shadowBias !== value) {\n\t\t\t\t\t\tthis._shadowBias = value;\n\t\t\t\t\t\tthis._updateShadowBias();\n\t\t\t\t}\n\t\t}\n\t\tget shadowBias() {\n\t\t\t\treturn this._shadowBias;\n\t\t}\n\t\tset numCascades(value) {\n\t\t\t\tif (!this.cascades || this.numCascades !== value) {\n\t\t\t\t\t\tthis.cascades = directionalCascades[value - 1];\n\t\t\t\t\t\tthis._shadowMatrixPalette = new Float32Array(4 * 16);\n\t\t\t\t\t\tthis._shadowCascadeDistances = new Float32Array(4);\n\t\t\t\t\t\tthis._destroyShadowMap();\n\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t}\n\t\t}\n\t\tget numCascades() {\n\t\t\t\treturn this.cascades.length;\n\t\t}\n\t\tset cascadeBlend(value) {\n\t\t\t\tif (this._cascadeBlend !== value) {\n\t\t\t\t\t\tthis._cascadeBlend = value;\n\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t}\n\t\t}\n\t\tget cascadeBlend() {\n\t\t\t\treturn this._cascadeBlend;\n\t\t}\n\t\tset shadowMap(shadowMap) {\n\t\t\t\tif (this._shadowMap !== shadowMap) {\n\t\t\t\t\t\tthis._destroyShadowMap();\n\t\t\t\t\t\tthis._shadowMap = shadowMap;\n\t\t\t\t}\n\t\t}\n\t\tget shadowMap() {\n\t\t\t\treturn this._shadowMap;\n\t\t}\n\t\tset mask(value) {\n\t\t\t\tif (this._mask !== value) {\n\t\t\t\t\t\tthis._mask = value;\n\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t\t\tthis.updateClusteredFlags();\n\t\t\t\t}\n\t\t}\n\t\tget mask() {\n\t\t\t\treturn this._mask;\n\t\t}\n\t\tget numShadowFaces() {\n\t\t\t\tconst type = this._type;\n\t\t\t\tif (type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\treturn this.numCascades;\n\t\t\t\t} else if (type === LIGHTTYPE_OMNI) {\n\t\t\t\t\t\treturn 6;\n\t\t\t\t}\n\t\t\t\treturn 1;\n\t\t}\n\t\tset type(value) {\n\t\t\t\tif (this._type === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._type = value;\n\t\t\t\tthis._destroyShadowMap();\n\t\t\t\tthis._updateShadowBias();\n\t\t\t\tthis.updateKey();\n\t\t\t\tthis.updateClusteredFlags();\n\t\t\t\tconst stype = this._shadowType;\n\t\t\t\tthis._shadowType = null;\n\t\t\t\tthis.shadowUpdateOverrides = null;\n\t\t\t\tthis.shadowType = stype;\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tset shape(value) {\n\t\t\t\tif (this._shape === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._shape = value;\n\t\t\t\tthis._destroyShadowMap();\n\t\t\t\tthis.updateKey();\n\t\t\t\tthis.updateClusteredFlags();\n\t\t\t\tconst stype = this._shadowType;\n\t\t\t\tthis._shadowType = null;\n\t\t\t\tthis.shadowType = stype;\n\t\t}\n\t\tget shape() {\n\t\t\t\treturn this._shape;\n\t\t}\n\t\tset usePhysicalUnits(value) {\n\t\t\t\tif (this._usePhysicalUnits !== value) {\n\t\t\t\t\t\tthis._usePhysicalUnits = value;\n\t\t\t\t\t\tthis._updateLinearColor();\n\t\t\t\t}\n\t\t}\n\t\tget usePhysicalUnits() {\n\t\t\t\treturn this._usePhysicalUnits;\n\t\t}\n\t\tset shadowType(value) {\n\t\t\t\tif (this._shadowType === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet shadowInfo = shadowTypeInfo.get(value);\n\t\t\t\tif (!shadowInfo) {\n\t\t\t\t\t\tvalue = SHADOW_PCF3_32F;\n\t\t\t\t}\n\t\t\t\tconst device = this.device;\n\t\t\t\tif (value === SHADOW_PCSS_32F && !device.textureFloatRenderable && !device.textureHalfFloatRenderable) {\n\t\t\t\t\t\tvalue = SHADOW_PCF3_32F;\n\t\t\t\t}\n\t\t\t\tif (this._type === LIGHTTYPE_OMNI && value !== SHADOW_PCF1_32F && value !== SHADOW_PCF3_32F && value !== SHADOW_PCF1_16F && value !== SHADOW_PCF3_16F && value !== SHADOW_PCSS_32F) {\n\t\t\t\t\t\tvalue = SHADOW_PCF3_32F;\n\t\t\t\t}\n\t\t\t\tif (value === SHADOW_VSM_32F && (!device.textureFloatRenderable || !device.textureFloatFilterable)) {\n\t\t\t\t\t\tvalue = SHADOW_VSM_16F;\n\t\t\t\t}\n\t\t\t\tif (value === SHADOW_VSM_16F && !device.textureHalfFloatRenderable) {\n\t\t\t\t\t\tvalue = SHADOW_PCF3_32F;\n\t\t\t\t}\n\t\t\t\tshadowInfo = shadowTypeInfo.get(value);\n\t\t\t\tthis._isVsm = shadowInfo?.vsm ?? false;\n\t\t\t\tthis._isPcf = shadowInfo?.pcf ?? false;\n\t\t\t\tthis._shadowType = value;\n\t\t\t\tthis._destroyShadowMap();\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget shadowType() {\n\t\t\t\treturn this._shadowType;\n\t\t}\n\t\tset enabled(value) {\n\t\t\t\tif (this._enabled !== value) {\n\t\t\t\t\t\tthis._enabled = value;\n\t\t\t\t\t\tthis.layersDirty();\n\t\t\t\t}\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled;\n\t\t}\n\t\tset castShadows(value) {\n\t\t\t\tif (this._castShadows !== value) {\n\t\t\t\t\t\tthis._castShadows = value;\n\t\t\t\t\t\tthis._destroyShadowMap();\n\t\t\t\t\t\tthis.layersDirty();\n\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t}\n\t\t}\n\t\tget castShadows() {\n\t\t\t\treturn this._castShadows && this._mask !== MASK_BAKE && this._mask !== 0;\n\t\t}\n\t\tset shadowIntensity(value) {\n\t\t\t\tif (this._shadowIntensity !== value) {\n\t\t\t\t\t\tthis._shadowIntensity = value;\n\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t}\n\t\t}\n\t\tget shadowIntensity() {\n\t\t\t\treturn this._shadowIntensity;\n\t\t}\n\t\tget bakeShadows() {\n\t\t\t\treturn this._castShadows && this._mask === MASK_BAKE;\n\t\t}\n\t\tset shadowResolution(value) {\n\t\t\t\tif (this._shadowResolution !== value) {\n\t\t\t\t\t\tif (this._type === LIGHTTYPE_OMNI) {\n\t\t\t\t\t\t\t\tvalue = Math.min(value, this.device.maxCubeMapSize);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tvalue = Math.min(value, this.device.maxTextureSize);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._shadowResolution = value;\n\t\t\t\t\t\tthis._destroyShadowMap();\n\t\t\t\t}\n\t\t}\n\t\tget shadowResolution() {\n\t\t\t\treturn this._shadowResolution;\n\t\t}\n\t\tset vsmBlurSize(value) {\n\t\t\t\tif (this._vsmBlurSize === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (value % 2 === 0) value++;\n\t\t\t\tthis._vsmBlurSize = value;\n\t\t}\n\t\tget vsmBlurSize() {\n\t\t\t\treturn this._vsmBlurSize;\n\t\t}\n\t\tset normalOffsetBias(value) {\n\t\t\t\tif (this._normalOffsetBias !== value) {\n\t\t\t\t\t\tconst dirty = !this._normalOffsetBias && value || this._normalOffsetBias && !value;\n\t\t\t\t\t\tthis._normalOffsetBias = value;\n\t\t\t\t\t\tif (dirty) {\n\t\t\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget normalOffsetBias() {\n\t\t\t\treturn this._normalOffsetBias;\n\t\t}\n\t\tset falloffMode(value) {\n\t\t\t\tif (this._falloffMode === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._falloffMode = value;\n\t\t\t\tthis.updateKey();\n\t\t\t\tthis.updateClusteredFlags();\n\t\t}\n\t\tget falloffMode() {\n\t\t\t\treturn this._falloffMode;\n\t\t}\n\t\tset innerConeAngle(value) {\n\t\t\t\tif (this._innerConeAngle === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._innerConeAngle = value;\n\t\t\t\tthis._innerConeAngleCos = Math.cos(value * Math.PI / 180);\n\t\t\t\tthis.updateClusterData(false, true);\n\t\t\t\tif (this._usePhysicalUnits) {\n\t\t\t\t\t\tthis._updateLinearColor();\n\t\t\t\t}\n\t\t}\n\t\tget innerConeAngle() {\n\t\t\t\treturn this._innerConeAngle;\n\t\t}\n\t\tset outerConeAngle(value) {\n\t\t\t\tif (this._outerConeAngle === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._outerConeAngle = value;\n\t\t\t\tthis._updateOuterAngle(value);\n\t\t\t\tif (this._usePhysicalUnits) {\n\t\t\t\t\t\tthis._updateLinearColor();\n\t\t\t\t}\n\t\t}\n\t\tget outerConeAngle() {\n\t\t\t\treturn this._outerConeAngle;\n\t\t}\n\t\tset penumbraSize(value) {\n\t\t\t\tthis._penumbraSize = value;\n\t\t\t\tthis._softShadowParams[2] = value;\n\t\t}\n\t\tget penumbraSize() {\n\t\t\t\treturn this._penumbraSize;\n\t\t}\n\t\tset penumbraFalloff(value) {\n\t\t\t\tthis._softShadowParams[3] = value;\n\t\t}\n\t\tget penumbraFalloff() {\n\t\t\t\treturn this._softShadowParams[3];\n\t\t}\n\t\t_updateOuterAngle(angle) {\n\t\t\t\tconst radAngle = angle * Math.PI / 180;\n\t\t\t\tthis._outerConeAngleCos = Math.cos(radAngle);\n\t\t\t\tthis._outerConeAngleSin = Math.sin(radAngle);\n\t\t\t\tthis.updateClusterData(false, true);\n\t\t}\n\t\tset intensity(value) {\n\t\t\t\tif (this._intensity !== value) {\n\t\t\t\t\t\tthis._intensity = value;\n\t\t\t\t\t\tthis._updateLinearColor();\n\t\t\t\t}\n\t\t}\n\t\tget intensity() {\n\t\t\t\treturn this._intensity;\n\t\t}\n\t\tset affectSpecularity(value) {\n\t\t\t\tif (this._type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\tthis._affectSpecularity = value;\n\t\t\t\t\t\tthis.updateKey();\n\t\t\t\t}\n\t\t}\n\t\tget affectSpecularity() {\n\t\t\t\treturn this._affectSpecularity;\n\t\t}\n\t\tset luminance(value) {\n\t\t\t\tif (this._luminance !== value) {\n\t\t\t\t\t\tthis._luminance = value;\n\t\t\t\t\t\tthis._updateLinearColor();\n\t\t\t\t}\n\t\t}\n\t\tget luminance() {\n\t\t\t\treturn this._luminance;\n\t\t}\n\t\tget cookieMatrix() {\n\t\t\t\tif (!this._cookieMatrix) {\n\t\t\t\t\t\tthis._cookieMatrix = new Mat4();\n\t\t\t\t}\n\t\t\t\treturn this._cookieMatrix;\n\t\t}\n\t\tget atlasViewport() {\n\t\t\t\tif (!this._atlasViewport) {\n\t\t\t\t\t\tthis._atlasViewport = new Vec4(0, 0, 1, 1);\n\t\t\t\t}\n\t\t\t\treturn this._atlasViewport;\n\t\t}\n\t\tset cookie(value) {\n\t\t\t\tif (this._cookie === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._cookie = value;\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget cookie() {\n\t\t\t\treturn this._cookie;\n\t\t}\n\t\tset cookieFalloff(value) {\n\t\t\t\tif (this._cookieFalloff === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._cookieFalloff = value;\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget cookieFalloff() {\n\t\t\t\treturn this._cookieFalloff;\n\t\t}\n\t\tset cookieChannel(value) {\n\t\t\t\tif (this._cookieChannel === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (value.length < 3) {\n\t\t\t\t\t\tconst chr = value.charAt(value.length - 1);\n\t\t\t\t\t\tconst addLen = 3 - value.length;\n\t\t\t\t\t\tfor(let i = 0; i < addLen; i++){\n\t\t\t\t\t\t\t\tvalue += chr;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._cookieChannel = value;\n\t\t\t\tthis.updateKey();\n\t\t\t\tthis.updateClusteredFlags();\n\t\t}\n\t\tget cookieChannel() {\n\t\t\t\treturn this._cookieChannel;\n\t\t}\n\t\tset cookieTransform(value) {\n\t\t\t\tif (this._cookieTransform === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._cookieTransform = value;\n\t\t\t\tthis._cookieTransformSet = !!value;\n\t\t\t\tif (value && !this._cookieOffset) {\n\t\t\t\t\t\tthis.cookieOffset = new Vec2();\n\t\t\t\t\t\tthis._cookieOffsetSet = false;\n\t\t\t\t}\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget cookieTransform() {\n\t\t\t\treturn this._cookieTransform;\n\t\t}\n\t\tset cookieOffset(value) {\n\t\t\t\tif (this._cookieOffset === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst xformNew = !!(this._cookieTransformSet || value);\n\t\t\t\tif (xformNew && !value && this._cookieOffset) {\n\t\t\t\t\t\tthis._cookieOffset.set(0, 0);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._cookieOffset = value;\n\t\t\t\t}\n\t\t\t\tthis._cookieOffsetSet = !!value;\n\t\t\t\tif (value && !this._cookieTransform) {\n\t\t\t\t\t\tthis.cookieTransform = new Vec4(1, 1, 0, 0);\n\t\t\t\t\t\tthis._cookieTransformSet = false;\n\t\t\t\t}\n\t\t\t\tthis.updateKey();\n\t\t}\n\t\tget cookieOffset() {\n\t\t\t\treturn this._cookieOffset;\n\t\t}\n\t\tbeginFrame() {\n\t\t\t\tthis.visibleThisFrame = this._type === LIGHTTYPE_DIRECTIONAL && this._enabled;\n\t\t\t\tthis.maxScreenSize = 0;\n\t\t\t\tthis.atlasViewportAllocated = false;\n\t\t\t\tthis.atlasSlotUpdated = false;\n\t\t}\n\t\t_destroyShadowMap() {\n\t\t\t\tthis.releaseRenderData();\n\t\t\t\tif (this._shadowMap) {\n\t\t\t\t\t\tif (!this._shadowMap.cached) {\n\t\t\t\t\t\t\t\tthis._shadowMap.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._shadowMap = null;\n\t\t\t\t}\n\t\t\t\tif (this.shadowUpdateMode === SHADOWUPDATE_NONE) {\n\t\t\t\t\t\tthis.shadowUpdateMode = SHADOWUPDATE_THISFRAME;\n\t\t\t\t}\n\t\t\t\tif (this.shadowUpdateOverrides) {\n\t\t\t\t\t\tfor(let i = 0; i < this.shadowUpdateOverrides.length; i++){\n\t\t\t\t\t\t\t\tif (this.shadowUpdateOverrides[i] === SHADOWUPDATE_NONE) {\n\t\t\t\t\t\t\t\t\t\tthis.shadowUpdateOverrides[i] = SHADOWUPDATE_THISFRAME;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgetRenderData(camera, face) {\n\t\t\t\tfor(let i = 0; i < this._renderData.length; i++){\n\t\t\t\t\t\tconst current = this._renderData[i];\n\t\t\t\t\t\tif (current.camera === camera && current.face === face) {\n\t\t\t\t\t\t\t\treturn current;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst rd = new LightRenderData(camera, face, this);\n\t\t\t\tthis._renderData.push(rd);\n\t\t\t\treturn rd;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst clone = new Light(this.device, this.clusteredLighting);\n\t\t\t\tclone.type = this._type;\n\t\t\t\tclone.setColor(this._color);\n\t\t\t\tclone.intensity = this._intensity;\n\t\t\t\tclone.affectSpecularity = this._affectSpecularity;\n\t\t\t\tclone.luminance = this._luminance;\n\t\t\t\tclone.castShadows = this.castShadows;\n\t\t\t\tclone._enabled = this._enabled;\n\t\t\t\tclone.attenuationStart = this.attenuationStart;\n\t\t\t\tclone.attenuationEnd = this.attenuationEnd;\n\t\t\t\tclone.falloffMode = this._falloffMode;\n\t\t\t\tclone.shadowType = this._shadowType;\n\t\t\t\tclone.vsmBlurSize = this._vsmBlurSize;\n\t\t\t\tclone.vsmBlurMode = this.vsmBlurMode;\n\t\t\t\tclone.vsmBias = this.vsmBias;\n\t\t\t\tclone.shadowUpdateMode = this.shadowUpdateMode;\n\t\t\t\tclone.mask = this.mask;\n\t\t\t\tif (this.shadowUpdateOverrides) {\n\t\t\t\t\t\tclone.shadowUpdateOverrides = this.shadowUpdateOverrides.slice();\n\t\t\t\t}\n\t\t\t\tclone.innerConeAngle = this._innerConeAngle;\n\t\t\t\tclone.outerConeAngle = this._outerConeAngle;\n\t\t\t\tclone.numCascades = this.numCascades;\n\t\t\t\tclone.cascadeDistribution = this.cascadeDistribution;\n\t\t\t\tclone.cascadeBlend = this._cascadeBlend;\n\t\t\t\tclone.shape = this._shape;\n\t\t\t\tclone.shadowDepthState.copy(this.shadowDepthState);\n\t\t\t\tclone.shadowBias = this.shadowBias;\n\t\t\t\tclone.normalOffsetBias = this._normalOffsetBias;\n\t\t\t\tclone.shadowResolution = this._shadowResolution;\n\t\t\t\tclone.shadowDistance = this.shadowDistance;\n\t\t\t\tclone.shadowIntensity = this.shadowIntensity;\n\t\t\t\tclone.shadowSamples = this.shadowSamples;\n\t\t\t\tclone.shadowBlockerSamples = this.shadowBlockerSamples;\n\t\t\t\tclone.penumbraSize = this.penumbraSize;\n\t\t\t\tclone.penumbraFalloff = this.penumbraFalloff;\n\t\t\t\treturn clone;\n\t\t}\n\t\tstatic getLightUnitConversion(type, outerAngle = Math.PI / 4, innerAngle = 0) {\n\t\t\t\tswitch(type){\n\t\t\t\t\t\tcase LIGHTTYPE_SPOT:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tconst falloffEnd = Math.cos(outerAngle);\n\t\t\t\t\t\t\t\t\t\tconst falloffStart = Math.cos(innerAngle);\n\t\t\t\t\t\t\t\t\t\treturn 2 * Math.PI * (1 - falloffStart + (falloffStart - falloffEnd) / 2.0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase LIGHTTYPE_OMNI:\n\t\t\t\t\t\t\t\treturn 4 * Math.PI;\n\t\t\t\t\t\tcase LIGHTTYPE_DIRECTIONAL:\n\t\t\t\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t}\n\t\t_getUniformBiasValues(lightRenderData) {\n\t\t\t\tconst farClip = lightRenderData.shadowCamera._farClip;\n\t\t\t\tswitch(this._type){\n\t\t\t\t\t\tcase LIGHTTYPE_OMNI:\n\t\t\t\t\t\t\t\ttmpBiases.bias = this.shadowBias;\n\t\t\t\t\t\t\t\ttmpBiases.normalBias = this._normalOffsetBias;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase LIGHTTYPE_SPOT:\n\t\t\t\t\t\t\t\tif (this._isVsm) {\n\t\t\t\t\t\t\t\t\t\ttmpBiases.bias = -1e-5 * 20;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\ttmpBiases.bias = this.shadowBias * 20;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttmpBiases.normalBias = this._isVsm ? this.vsmBias / (this.attenuationEnd / 7.0) : this._normalOffsetBias;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase LIGHTTYPE_DIRECTIONAL:\n\t\t\t\t\t\t\t\tif (this._isVsm) {\n\t\t\t\t\t\t\t\t\t\ttmpBiases.bias = -1e-5 * 20;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\ttmpBiases.bias = this.shadowBias / farClip * 100;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttmpBiases.normalBias = this._isVsm ? this.vsmBias / (farClip / 7.0) : this._normalOffsetBias;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\treturn tmpBiases;\n\t\t}\n\t\tgetColor() {\n\t\t\t\treturn this._color;\n\t\t}\n\t\tgetBoundingSphere(sphere) {\n\t\t\t\tif (this._type === LIGHTTYPE_SPOT) {\n\t\t\t\t\t\tconst size = this.attenuationEnd;\n\t\t\t\t\t\tconst angle = this._outerConeAngle;\n\t\t\t\t\t\tconst cosAngle = this._outerConeAngleCos;\n\t\t\t\t\t\tconst node = this._node;\n\t\t\t\t\t\ttmpVec.copy(node.up);\n\t\t\t\t\t\tif (angle > 45) {\n\t\t\t\t\t\t\t\tsphere.radius = size * this._outerConeAngleSin;\n\t\t\t\t\t\t\t\ttmpVec.mulScalar(-size * cosAngle);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsphere.radius = size / (2 * cosAngle);\n\t\t\t\t\t\t\t\ttmpVec.mulScalar(-sphere.radius);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsphere.center.add2(node.getPosition(), tmpVec);\n\t\t\t\t} else if (this._type === LIGHTTYPE_OMNI) {\n\t\t\t\t\t\tsphere.center = this._node.getPosition();\n\t\t\t\t\t\tsphere.radius = this.attenuationEnd;\n\t\t\t\t}\n\t\t}\n\t\tgetBoundingBox(box) {\n\t\t\t\tif (this._type === LIGHTTYPE_SPOT) {\n\t\t\t\t\t\tconst range = this.attenuationEnd;\n\t\t\t\t\t\tconst angle = this._outerConeAngle;\n\t\t\t\t\t\tconst node = this._node;\n\t\t\t\t\t\tconst scl = Math.abs(Math.sin(angle * math.DEG_TO_RAD) * range);\n\t\t\t\t\t\tbox.center.set(0, -range * 0.5, 0);\n\t\t\t\t\t\tbox.halfExtents.set(scl, range * 0.5, scl);\n\t\t\t\t\t\tbox.setFromTransformedAabb(box, node.getWorldTransform(), true);\n\t\t\t\t} else if (this._type === LIGHTTYPE_OMNI) {\n\t\t\t\t\t\tbox.center.copy(this._node.getPosition());\n\t\t\t\t\t\tbox.halfExtents.set(this.attenuationEnd, this.attenuationEnd, this.attenuationEnd);\n\t\t\t\t}\n\t\t}\n\t\t_updateShadowBias() {\n\t\t\t\tif (this._type === LIGHTTYPE_OMNI && !this.clusteredLighting) {\n\t\t\t\t\t\tthis.shadowDepthState.depthBias = 0;\n\t\t\t\t\t\tthis.shadowDepthState.depthBiasSlope = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst bias = this.shadowBias * -1e3;\n\t\t\t\t\t\tthis.shadowDepthState.depthBias = bias;\n\t\t\t\t\t\tthis.shadowDepthState.depthBiasSlope = bias;\n\t\t\t\t}\n\t\t}\n\t\t_updateLinearColor() {\n\t\t\t\tlet intensity = this._intensity;\n\t\t\t\tif (this._usePhysicalUnits) {\n\t\t\t\t\t\tintensity = this._luminance / Light.getLightUnitConversion(this._type, this._outerConeAngle * math.DEG_TO_RAD, this._innerConeAngle * math.DEG_TO_RAD);\n\t\t\t\t}\n\t\t\t\tconst color = this._color;\n\t\t\t\tconst colorLinear = this._colorLinear;\n\t\t\t\tif (intensity >= 1) {\n\t\t\t\t\t\ttmpColor.linear(color).mulScalar(intensity);\n\t\t\t\t} else {\n\t\t\t\t\t\ttmpColor.copy(color).mulScalar(intensity).linear();\n\t\t\t\t}\n\t\t\t\tcolorLinear[0] = tmpColor.r;\n\t\t\t\tcolorLinear[1] = tmpColor.g;\n\t\t\t\tcolorLinear[2] = tmpColor.b;\n\t\t\t\tthis.updateClusterData(true);\n\t\t}\n\t\tsetColor() {\n\t\t\t\tif (arguments.length === 1) {\n\t\t\t\t\t\tthis._color.set(arguments[0].r, arguments[0].g, arguments[0].b);\n\t\t\t\t} else if (arguments.length === 3) {\n\t\t\t\t\t\tthis._color.set(arguments[0], arguments[1], arguments[2]);\n\t\t\t\t}\n\t\t\t\tthis._updateLinearColor();\n\t\t}\n\t\tlayersDirty() {\n\t\t\t\tthis.layers.forEach((layer)=>{\n\t\t\t\t\t\tif (layer.hasLight(this)) {\n\t\t\t\t\t\t\t\tlayer.markLightsDirty();\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tupdateKey() {\n\t\t\t\tlet key = this._type << 29 | this._shadowType << 25 | this._falloffMode << 23 | (this._normalOffsetBias !== 0.0 ? 1 : 0) << 22 | (this._cookie ? 1 : 0) << 21 | (this._cookieFalloff ? 1 : 0) << 20 | chanId[this._cookieChannel.charAt(0)] << 18 | (this._cookieTransform ? 1 : 0) << 12 | this._shape << 10 | (this.numCascades > 0 ? 1 : 0) << 9 | (this._cascadeBlend > 0 ? 1 : 0) << 8 | (this.affectSpecularity ? 1 : 0) << 7 | this.mask << 6 | (this._castShadows ? 1 : 0) << 3;\n\t\t\t\tif (this._cookieChannel.length === 3) {\n\t\t\t\t\t\tkey |= chanId[this._cookieChannel.charAt(1)] << 16;\n\t\t\t\t\t\tkey |= chanId[this._cookieChannel.charAt(2)] << 14;\n\t\t\t\t}\n\t\t\t\tif (key !== this.key) {\n\t\t\t\t\t\tthis.layersDirty();\n\t\t\t\t}\n\t\t\t\tthis.key = key;\n\t\t}\n\t\tupdateClusteredFlags() {\n\t\t\t\tconst isDynamic = !!(this.mask & MASK_AFFECT_DYNAMIC);\n\t\t\t\tconst isLightmapped = !!(this.mask & MASK_AFFECT_LIGHTMAPPED);\n\t\t\t\tthis.clusteredFlags = (this.type === LIGHTTYPE_SPOT ? 1 : 0) << 30 | (this._shape & 0x3) << 28 | (this._falloffMode & 0x1) << 27 | (channelMap[this._cookieChannel] ?? 0) << 23 | (isDynamic ? 1 : 0) << 22 | (isLightmapped ? 1 : 0) << 21;\n\t\t}\n\t\tgetClusteredFlags(castShadows, useCookie) {\n\t\t\t\treturn this.clusteredFlags | ((castShadows ? Math.floor(this.shadowIntensity * 255) : 0) & 0xFF) << 0 | ((useCookie ? Math.floor(this.cookieIntensity * 255) : 0) & 0xFF) << 8;\n\t\t}\n\t\tupdateClusterData(updateColor, updateAngles) {\n\t\t\t\tconst { clusteredData16 } = this;\n\t\t\t\tconst float2Half = FloatPacking.float2Half;\n\t\t\t\tif (updateColor) {\n\t\t\t\t\t\tclusteredData16[0] = float2Half(math.clamp(this._colorLinear[0] / LIGHT_COLOR_DIVIDER, 0, 65504));\n\t\t\t\t\t\tclusteredData16[1] = float2Half(math.clamp(this._colorLinear[1] / LIGHT_COLOR_DIVIDER, 0, 65504));\n\t\t\t\t\t\tclusteredData16[2] = float2Half(math.clamp(this._colorLinear[2] / LIGHT_COLOR_DIVIDER, 0, 65504));\n\t\t\t\t}\n\t\t\t\tif (updateAngles) {\n\t\t\t\t\t\tclusteredData16[4] = float2Half(this._innerConeAngleCos);\n\t\t\t\t\t\tclusteredData16[5] = float2Half(this._outerConeAngleCos);\n\t\t\t\t}\n\t\t}\n}\n\nclass LightingParams {\n\t\tconstructor(supportsAreaLights, maxTextureSize, dirtyLightsFnc){\n\t\t\t\tthis._areaLightsEnabled = false;\n\t\t\t\tthis._cells = new Vec3(10, 3, 10);\n\t\t\t\tthis._maxLightsPerCell = 255;\n\t\t\t\tthis._shadowsEnabled = true;\n\t\t\t\tthis._shadowType = SHADOW_PCF3_32F;\n\t\t\t\tthis._shadowAtlasResolution = 2048;\n\t\t\t\tthis._cookiesEnabled = false;\n\t\t\t\tthis._cookieAtlasResolution = 2048;\n\t\t\t\tthis.atlasSplit = null;\n\t\t\t\tthis._supportsAreaLights = supportsAreaLights;\n\t\t\t\tthis._maxTextureSize = maxTextureSize;\n\t\t\t\tthis._dirtyLightsFnc = dirtyLightsFnc;\n\t\t}\n\t\tapplySettings(render) {\n\t\t\t\tthis.shadowsEnabled = render.lightingShadowsEnabled ?? this.shadowsEnabled;\n\t\t\t\tthis.cookiesEnabled = render.lightingCookiesEnabled ?? this.cookiesEnabled;\n\t\t\t\tthis.areaLightsEnabled = render.lightingAreaLightsEnabled ?? this.areaLightsEnabled;\n\t\t\t\tthis.shadowAtlasResolution = render.lightingShadowAtlasResolution ?? this.shadowAtlasResolution;\n\t\t\t\tthis.cookieAtlasResolution = render.lightingCookieAtlasResolution ?? this.cookieAtlasResolution;\n\t\t\t\tthis.maxLightsPerCell = render.lightingMaxLightsPerCell ?? this.maxLightsPerCell;\n\t\t\t\tthis.shadowType = render.lightingShadowType ?? this.shadowType;\n\t\t\t\tif (render.lightingCells) {\n\t\t\t\t\t\tthis.cells = new Vec3(render.lightingCells);\n\t\t\t\t}\n\t\t}\n\t\tset cells(value) {\n\t\t\t\tthis._cells.copy(value);\n\t\t}\n\t\tget cells() {\n\t\t\t\treturn this._cells;\n\t\t}\n\t\tset maxLightsPerCell(value) {\n\t\t\t\tthis._maxLightsPerCell = math.clamp(value, 1, 255);\n\t\t}\n\t\tget maxLightsPerCell() {\n\t\t\t\treturn this._maxLightsPerCell;\n\t\t}\n\t\tset cookieAtlasResolution(value) {\n\t\t\t\tthis._cookieAtlasResolution = math.clamp(value, 32, this._maxTextureSize);\n\t\t}\n\t\tget cookieAtlasResolution() {\n\t\t\t\treturn this._cookieAtlasResolution;\n\t\t}\n\t\tset shadowAtlasResolution(value) {\n\t\t\t\tthis._shadowAtlasResolution = math.clamp(value, 32, this._maxTextureSize);\n\t\t}\n\t\tget shadowAtlasResolution() {\n\t\t\t\treturn this._shadowAtlasResolution;\n\t\t}\n\t\tset shadowType(value) {\n\t\t\t\tif (this._shadowType !== value) {\n\t\t\t\t\t\tthis._shadowType = value;\n\t\t\t\t\t\tthis._dirtyLightsFnc();\n\t\t\t\t}\n\t\t}\n\t\tget shadowType() {\n\t\t\t\treturn this._shadowType;\n\t\t}\n\t\tset cookiesEnabled(value) {\n\t\t\t\tif (this._cookiesEnabled !== value) {\n\t\t\t\t\t\tthis._cookiesEnabled = value;\n\t\t\t\t\t\tthis._dirtyLightsFnc();\n\t\t\t\t}\n\t\t}\n\t\tget cookiesEnabled() {\n\t\t\t\treturn this._cookiesEnabled;\n\t\t}\n\t\tset areaLightsEnabled(value) {\n\t\t\t\tif (this._supportsAreaLights) {\n\t\t\t\t\t\tif (this._areaLightsEnabled !== value) {\n\t\t\t\t\t\t\t\tthis._areaLightsEnabled = value;\n\t\t\t\t\t\t\t\tthis._dirtyLightsFnc();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget areaLightsEnabled() {\n\t\t\t\treturn this._areaLightsEnabled;\n\t\t}\n\t\tset shadowsEnabled(value) {\n\t\t\t\tif (this._shadowsEnabled !== value) {\n\t\t\t\t\t\tthis._shadowsEnabled = value;\n\t\t\t\t\t\tthis._dirtyLightsFnc();\n\t\t\t\t}\n\t\t}\n\t\tget shadowsEnabled() {\n\t\t\t\treturn this._shadowsEnabled;\n\t\t}\n}\n\nclass MorphInstance {\n\t\tconstructor(morph){\n\t\t\t\tthis.morph = morph;\n\t\t\t\tmorph.incRefCount();\n\t\t\t\tthis.device = morph.device;\n\t\t\t\tconst maxNumTargets = morph._targets.length;\n\t\t\t\tthis.shader = this._createShader(maxNumTargets);\n\t\t\t\tthis._weights = [];\n\t\t\t\tthis._weightMap = new Map();\n\t\t\t\tfor(let v = 0; v < morph._targets.length; v++){\n\t\t\t\t\t\tconst target = morph._targets[v];\n\t\t\t\t\t\tif (target.name) {\n\t\t\t\t\t\t\t\tthis._weightMap.set(target.name, v);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.setWeight(v, target.defaultWeight);\n\t\t\t\t}\n\t\t\t\tthis._shaderMorphWeights = new Float32Array(maxNumTargets);\n\t\t\t\tthis._shaderMorphIndex = new Uint32Array(maxNumTargets);\n\t\t\t\tconst createRT = (name, textureVar)=>{\n\t\t\t\t\t\tthis[textureVar] = morph._createTexture(name, morph._renderTextureFormat);\n\t\t\t\t\t\treturn new RenderTarget({\n\t\t\t\t\t\t\t\tcolorBuffer: this[textureVar],\n\t\t\t\t\t\t\t\tdepth: false\n\t\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tif (morph.morphPositions) {\n\t\t\t\t\t\tthis.rtPositions = createRT('MorphRTPos', 'texturePositions');\n\t\t\t\t}\n\t\t\t\tif (morph.morphNormals) {\n\t\t\t\t\t\tthis.rtNormals = createRT('MorphRTNrm', 'textureNormals');\n\t\t\t\t}\n\t\t\t\tthis._textureParams = new Float32Array([\n\t\t\t\t\t\tmorph.morphTextureWidth,\n\t\t\t\t\t\tmorph.morphTextureHeight\n\t\t\t\t]);\n\t\t\t\tconst halfSize = morph.aabb.halfExtents;\n\t\t\t\tthis._aabbSize = new Float32Array([\n\t\t\t\t\t\thalfSize.x * 4,\n\t\t\t\t\t\thalfSize.y * 4,\n\t\t\t\t\t\thalfSize.z * 4\n\t\t\t\t]);\n\t\t\t\tconst min = morph.aabb.getMin();\n\t\t\t\tthis._aabbMin = new Float32Array([\n\t\t\t\t\t\tmin.x * 2,\n\t\t\t\t\t\tmin.y * 2,\n\t\t\t\t\t\tmin.z * 2\n\t\t\t\t]);\n\t\t\t\tthis._aabbNrmSize = new Float32Array([\n\t\t\t\t\t\t2,\n\t\t\t\t\t\t2,\n\t\t\t\t\t\t2\n\t\t\t\t]);\n\t\t\t\tthis._aabbNrmMin = new Float32Array([\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t-1\n\t\t\t\t]);\n\t\t\t\tthis.aabbSizeId = this.device.scope.resolve('aabbSize');\n\t\t\t\tthis.aabbMinId = this.device.scope.resolve('aabbMin');\n\t\t\t\tthis.morphTextureId = this.device.scope.resolve('morphTexture');\n\t\t\t\tthis.morphFactor = this.device.scope.resolve('morphFactor[0]');\n\t\t\t\tthis.morphIndex = this.device.scope.resolve('morphIndex[0]');\n\t\t\t\tthis.countId = this.device.scope.resolve('count');\n\t\t\t\tthis.zeroTextures = false;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.shader = null;\n\t\t\t\tconst morph = this.morph;\n\t\t\t\tif (morph) {\n\t\t\t\t\t\tthis.morph = null;\n\t\t\t\t\t\tmorph.decRefCount();\n\t\t\t\t\t\tif (morph.refCount < 1) {\n\t\t\t\t\t\t\t\tmorph.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.rtPositions?.destroy();\n\t\t\t\tthis.rtPositions = null;\n\t\t\t\tthis.texturePositions?.destroy();\n\t\t\t\tthis.texturePositions = null;\n\t\t\t\tthis.rtNormals?.destroy();\n\t\t\t\tthis.rtNormals = null;\n\t\t\t\tthis.textureNormals?.destroy();\n\t\t\t\tthis.textureNormals = null;\n\t\t}\n\t\tclone() {\n\t\t\t\treturn new MorphInstance(this.morph);\n\t\t}\n\t\t_getWeightIndex(key) {\n\t\t\t\tif (typeof key === 'string') {\n\t\t\t\t\t\tconst index = this._weightMap.get(key);\n\t\t\t\t\t\treturn index;\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t}\n\t\tgetWeight(key) {\n\t\t\t\tconst index = this._getWeightIndex(key);\n\t\t\t\treturn this._weights[index];\n\t\t}\n\t\tsetWeight(key, weight) {\n\t\t\t\tconst index = this._getWeightIndex(key);\n\t\t\t\tthis._weights[index] = weight;\n\t\t\t\tthis._dirty = true;\n\t\t}\n\t\t_createShader(maxCount) {\n\t\t\t\tconst defines = new Map();\n\t\t\t\tdefines.set('{MORPH_TEXTURE_MAX_COUNT}', maxCount);\n\t\t\t\tif (this.morph.intRenderFormat) defines.set('MORPH_INT', '');\n\t\t\t\tconst outputType = this.morph.intRenderFormat ? 'uvec4' : 'vec4';\n\t\t\t\treturn ShaderUtils.createShader(this.device, {\n\t\t\t\t\t\tuniqueName: `TextureMorphShader_${maxCount}-${this.morph.intRenderFormat ? 'int' : 'float'}`,\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvertexChunk: 'morphVS',\n\t\t\t\t\t\tfragmentChunk: 'morphPS',\n\t\t\t\t\t\tfragmentDefines: defines,\n\t\t\t\t\t\tfragmentOutputTypes: [\n\t\t\t\t\t\t\t\toutputType\n\t\t\t\t\t\t]\n\t\t\t\t});\n\t\t}\n\t\t_updateTextureRenderTarget(renderTarget, activeCount, isPos) {\n\t\t\t\tconst { morph, device } = this;\n\t\t\t\tthis.setAabbUniforms(isPos);\n\t\t\t\tthis.morphTextureId.setValue(isPos ? morph.targetsTexturePositions : morph.targetsTextureNormals);\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tthis.countId.setValue(activeCount);\n\t\t\t\tthis.morphFactor.setValue(this._shaderMorphWeights);\n\t\t\t\tthis.morphIndex.setValue(this._shaderMorphIndex);\n\t\t\t\tdrawQuadWithShader(device, renderTarget, this.shader);\n\t\t}\n\t\t_updateTextureMorph(activeCount) {\n\t\t\t\tthis.device;\n\t\t\t\tif (activeCount > 0 || !this.zeroTextures) {\n\t\t\t\t\t\tif (this.rtPositions) {\n\t\t\t\t\t\t\t\tthis._updateTextureRenderTarget(this.rtPositions, activeCount, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.rtNormals) {\n\t\t\t\t\t\t\t\tthis._updateTextureRenderTarget(this.rtNormals, activeCount, false);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.zeroTextures = activeCount === 0;\n\t\t\t\t}\n\t\t}\n\t\tsetAabbUniforms(isPos = true) {\n\t\t\t\tthis.aabbSizeId.setValue(isPos ? this._aabbSize : this._aabbNrmSize);\n\t\t\t\tthis.aabbMinId.setValue(isPos ? this._aabbMin : this._aabbNrmMin);\n\t\t}\n\t\tprepareRendering(device) {\n\t\t\t\tthis.setAabbUniforms();\n\t\t}\n\t\tupdate() {\n\t\t\t\tthis._dirty = false;\n\t\t\t\tconst targets = this.morph._targets;\n\t\t\t\tconst epsilon = 0.00001;\n\t\t\t\tconst weights = this._shaderMorphWeights;\n\t\t\t\tconst indices = this._shaderMorphIndex;\n\t\t\t\tlet activeCount = 0;\n\t\t\t\tfor(let i = 0; i < targets.length; i++){\n\t\t\t\t\t\tif (Math.abs(this.getWeight(i)) > epsilon) {\n\t\t\t\t\t\t\t\tweights[activeCount] = this.getWeight(i);\n\t\t\t\t\t\t\t\tindices[activeCount] = i;\n\t\t\t\t\t\t\t\tactiveCount++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._updateTextureMorph(activeCount);\n\t\t}\n}\n\nclass Model {\n\t\tconstructor(){\n\t\t\t\tthis.graph = null;\n\t\t\t\tthis.meshInstances = [];\n\t\t\t\tthis.skinInstances = [];\n\t\t\t\tthis.morphInstances = [];\n\t\t\t\tthis.cameras = [];\n\t\t\t\tthis.lights = [];\n\t\t\t\tthis._shadersVersion = 0;\n\t\t\t\tthis._immutable = false;\n\t\t}\n\t\tgetGraph() {\n\t\t\t\treturn this.graph;\n\t\t}\n\t\tsetGraph(graph) {\n\t\t\t\tthis.graph = graph;\n\t\t}\n\t\tgetCameras() {\n\t\t\t\treturn this.cameras;\n\t\t}\n\t\tsetCameras(cameras) {\n\t\t\t\tthis.cameras = cameras;\n\t\t}\n\t\tgetLights() {\n\t\t\t\treturn this.lights;\n\t\t}\n\t\tsetLights(lights) {\n\t\t\t\tthis.lights = lights;\n\t\t}\n\t\tgetMaterials() {\n\t\t\t\tconst materials = [];\n\t\t\t\tfor(let i = 0; i < this.meshInstances.length; i++){\n\t\t\t\t\t\tconst meshInstance = this.meshInstances[i];\n\t\t\t\t\t\tif (materials.indexOf(meshInstance.material) === -1) {\n\t\t\t\t\t\t\t\tmaterials.push(meshInstance.material);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn materials;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst srcNodes = [];\n\t\t\t\tconst cloneNodes = [];\n\t\t\t\tconst _duplicate = function(node) {\n\t\t\t\t\t\tconst newNode = node.clone();\n\t\t\t\t\t\tsrcNodes.push(node);\n\t\t\t\t\t\tcloneNodes.push(newNode);\n\t\t\t\t\t\tfor(let idx = 0; idx < node._children.length; idx++){\n\t\t\t\t\t\t\t\tnewNode.addChild(_duplicate(node._children[idx]));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn newNode;\n\t\t\t\t};\n\t\t\t\tconst cloneGraph = _duplicate(this.graph);\n\t\t\t\tconst cloneMeshInstances = [];\n\t\t\t\tconst cloneSkinInstances = [];\n\t\t\t\tconst cloneMorphInstances = [];\n\t\t\t\tfor(let i = 0; i < this.skinInstances.length; i++){\n\t\t\t\t\t\tconst skin = this.skinInstances[i].skin;\n\t\t\t\t\t\tconst cloneSkinInstance = new SkinInstance(skin);\n\t\t\t\t\t\tconst bones = [];\n\t\t\t\t\t\tfor(let j = 0; j < skin.boneNames.length; j++){\n\t\t\t\t\t\t\t\tconst boneName = skin.boneNames[j];\n\t\t\t\t\t\t\t\tconst bone = cloneGraph.findByName(boneName);\n\t\t\t\t\t\t\t\tbones.push(bone);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcloneSkinInstance.bones = bones;\n\t\t\t\t\t\tcloneSkinInstances.push(cloneSkinInstance);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this.morphInstances.length; i++){\n\t\t\t\t\t\tconst morph = this.morphInstances[i].morph;\n\t\t\t\t\t\tconst cloneMorphInstance = new MorphInstance(morph);\n\t\t\t\t\t\tcloneMorphInstances.push(cloneMorphInstance);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this.meshInstances.length; i++){\n\t\t\t\t\t\tconst meshInstance = this.meshInstances[i];\n\t\t\t\t\t\tconst nodeIndex = srcNodes.indexOf(meshInstance.node);\n\t\t\t\t\t\tconst cloneMeshInstance = new MeshInstance(meshInstance.mesh, meshInstance.material, cloneNodes[nodeIndex]);\n\t\t\t\t\t\tif (meshInstance.skinInstance) {\n\t\t\t\t\t\t\t\tconst skinInstanceIndex = this.skinInstances.indexOf(meshInstance.skinInstance);\n\t\t\t\t\t\t\t\tcloneMeshInstance.skinInstance = cloneSkinInstances[skinInstanceIndex];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (meshInstance.morphInstance) {\n\t\t\t\t\t\t\t\tconst morphInstanceIndex = this.morphInstances.indexOf(meshInstance.morphInstance);\n\t\t\t\t\t\t\t\tcloneMeshInstance.morphInstance = cloneMorphInstances[morphInstanceIndex];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcloneMeshInstances.push(cloneMeshInstance);\n\t\t\t\t}\n\t\t\t\tconst clone = new Model();\n\t\t\t\tclone.graph = cloneGraph;\n\t\t\t\tclone.meshInstances = cloneMeshInstances;\n\t\t\t\tclone.skinInstances = cloneSkinInstances;\n\t\t\t\tclone.morphInstances = cloneMorphInstances;\n\t\t\t\tclone.getGraph().syncHierarchy();\n\t\t\t\treturn clone;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst meshInstances = this.meshInstances;\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tmeshInstances[i].destroy();\n\t\t\t\t}\n\t\t\t\tthis.meshInstances.length = 0;\n\t\t}\n\t\tgenerateWireframe() {\n\t\t\t\tMeshInstance._prepareRenderStyleForArray(this.meshInstances, RENDERSTYLE_WIREFRAME);\n\t\t}\n}\n\nclass Morph extends RefCountedObject {\n\t\tconstructor(targets, graphicsDevice, { preferHighPrecision = false } = {}){\n\t\t\t\tsuper();\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tconst device = graphicsDevice;\n\t\t\t\tthis.preferHighPrecision = preferHighPrecision;\n\t\t\t\tthis._targets = targets.slice();\n\t\t\t\tconst renderableHalf = device.textureHalfFloatRenderable ? PIXELFORMAT_RGBA16F : undefined;\n\t\t\t\tconst renderableFloat = device.textureFloatRenderable ? PIXELFORMAT_RGBA32F : undefined;\n\t\t\t\tthis._renderTextureFormat = this.preferHighPrecision ? renderableFloat ?? renderableHalf : renderableHalf ?? renderableFloat;\n\t\t\t\tthis._renderTextureFormat = this._renderTextureFormat ?? PIXELFORMAT_RGBA16U;\n\t\t\t\tthis.intRenderFormat = isIntegerPixelFormat(this._renderTextureFormat);\n\t\t\t\tthis._textureFormat = this.preferHighPrecision ? PIXELFORMAT_RGBA32F : PIXELFORMAT_RGBA16F;\n\t\t\t\tthis._init();\n\t\t\t\tthis._updateMorphFlags();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.vertexBufferIds?.destroy();\n\t\t\t\tthis.vertexBufferIds = null;\n\t\t\t\tthis.targetsTexturePositions?.destroy();\n\t\t\t\tthis.targetsTexturePositions = null;\n\t\t\t\tthis.targetsTextureNormals?.destroy();\n\t\t\t\tthis.targetsTextureNormals = null;\n\t\t}\n\t\tget aabb() {\n\t\t\t\tif (!this._aabb) {\n\t\t\t\t\t\tconst min = new Vec3();\n\t\t\t\t\t\tconst max = new Vec3();\n\t\t\t\t\t\tfor(let i = 0; i < this._targets.length; i++){\n\t\t\t\t\t\t\t\tconst targetAabb = this._targets[i].aabb;\n\t\t\t\t\t\t\t\tmin.min(targetAabb.getMin());\n\t\t\t\t\t\t\t\tmax.max(targetAabb.getMax());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._aabb = new BoundingBox();\n\t\t\t\t\t\tthis._aabb.setMinMax(min, max);\n\t\t\t\t}\n\t\t\t\treturn this._aabb;\n\t\t}\n\t\tget morphPositions() {\n\t\t\t\treturn this._morphPositions;\n\t\t}\n\t\tget morphNormals() {\n\t\t\t\treturn this._morphNormals;\n\t\t}\n\t\t_init() {\n\t\t\t\tthis._initTextureBased();\n\t\t\t\tfor(let i = 0; i < this._targets.length; i++){\n\t\t\t\t\t\tthis._targets[i]._postInit();\n\t\t\t\t}\n\t\t}\n\t\t_findSparseSet(deltaArrays, ids, usedDataIndices) {\n\t\t\t\tlet freeIndex = 1;\n\t\t\t\tconst dataCount = deltaArrays[0].length;\n\t\t\t\tfor(let v = 0; v < dataCount; v += 3){\n\t\t\t\t\t\tlet vertexUsed = false;\n\t\t\t\t\t\tfor(let i = 0; i < deltaArrays.length; i++){\n\t\t\t\t\t\t\t\tconst data = deltaArrays[i];\n\t\t\t\t\t\t\t\tif (data[v] !== 0 || data[v + 1] !== 0 || data[v + 2] !== 0) {\n\t\t\t\t\t\t\t\t\t\tvertexUsed = true;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (vertexUsed) {\n\t\t\t\t\t\t\t\tids.push(freeIndex);\n\t\t\t\t\t\t\t\tusedDataIndices.push(v / 3);\n\t\t\t\t\t\t\t\tfreeIndex++;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tids.push(0);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn freeIndex;\n\t\t}\n\t\t_initTextureBased() {\n\t\t\t\tconst deltaArrays = [], deltaInfos = [];\n\t\t\t\tconst targets = this._targets;\n\t\t\t\tfor(let i = 0; i < targets.length; i++){\n\t\t\t\t\t\tconst target = targets[i];\n\t\t\t\t\t\tif (target.options.deltaPositions) {\n\t\t\t\t\t\t\t\tdeltaArrays.push(target.options.deltaPositions);\n\t\t\t\t\t\t\t\tdeltaInfos.push(true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (target.options.deltaNormals) {\n\t\t\t\t\t\t\t\tdeltaArrays.push(target.options.deltaNormals);\n\t\t\t\t\t\t\t\tdeltaInfos.push(false);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst ids = [], usedDataIndices = [];\n\t\t\t\tconst freeIndex = this._findSparseSet(deltaArrays, ids, usedDataIndices);\n\t\t\t\tconst maxTextureSize = this.device.maxTextureSize;\n\t\t\t\tlet morphTextureWidth = Math.ceil(Math.sqrt(freeIndex));\n\t\t\t\tmorphTextureWidth = Math.min(morphTextureWidth, maxTextureSize);\n\t\t\t\tconst morphTextureHeight = Math.ceil(freeIndex / morphTextureWidth);\n\t\t\t\tif (morphTextureHeight > maxTextureSize) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.morphTextureWidth = morphTextureWidth;\n\t\t\t\tthis.morphTextureHeight = morphTextureHeight;\n\t\t\t\tlet halfFloat = false;\n\t\t\t\tconst float2Half = FloatPacking.float2Half;\n\t\t\t\tif (this._textureFormat === PIXELFORMAT_RGBA16F) {\n\t\t\t\t\t\thalfFloat = true;\n\t\t\t\t}\n\t\t\t\tconst texturesDataPositions = [];\n\t\t\t\tconst texturesDataNormals = [];\n\t\t\t\tconst textureDataSize = morphTextureWidth * morphTextureHeight * 4;\n\t\t\t\tfor(let i = 0; i < deltaArrays.length; i++){\n\t\t\t\t\t\tconst data = deltaArrays[i];\n\t\t\t\t\t\tconst textureData = this._textureFormat === PIXELFORMAT_RGBA16F ? new Uint16Array(textureDataSize) : new Float32Array(textureDataSize);\n\t\t\t\t\t\t(deltaInfos[i] ? texturesDataPositions : texturesDataNormals).push(textureData);\n\t\t\t\t\t\tif (halfFloat) {\n\t\t\t\t\t\t\t\tfor(let v = 0; v < usedDataIndices.length; v++){\n\t\t\t\t\t\t\t\t\t\tconst index = usedDataIndices[v] * 3;\n\t\t\t\t\t\t\t\t\t\tconst dstIndex = v * 4 + 4;\n\t\t\t\t\t\t\t\t\t\ttextureData[dstIndex] = float2Half(data[index]);\n\t\t\t\t\t\t\t\t\t\ttextureData[dstIndex + 1] = float2Half(data[index + 1]);\n\t\t\t\t\t\t\t\t\t\ttextureData[dstIndex + 2] = float2Half(data[index + 2]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(let v = 0; v < usedDataIndices.length; v++){\n\t\t\t\t\t\t\t\t\t\tconst index = usedDataIndices[v] * 3;\n\t\t\t\t\t\t\t\t\t\tconst dstIndex = v * 4 + 4;\n\t\t\t\t\t\t\t\t\t\ttextureData[dstIndex] = data[index];\n\t\t\t\t\t\t\t\t\t\ttextureData[dstIndex + 1] = data[index + 1];\n\t\t\t\t\t\t\t\t\t\ttextureData[dstIndex + 2] = data[index + 2];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (texturesDataPositions.length > 0) {\n\t\t\t\t\t\tthis.targetsTexturePositions = this._createTexture('MorphPositionsTexture', this._textureFormat, targets.length, [\n\t\t\t\t\t\t\t\ttexturesDataPositions\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\tif (texturesDataNormals.length > 0) {\n\t\t\t\t\t\tthis.targetsTextureNormals = this._createTexture('MorphNormalsTexture', this._textureFormat, targets.length, [\n\t\t\t\t\t\t\t\ttexturesDataNormals\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\tconst formatDesc = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR15,\n\t\t\t\t\t\t\t\tcomponents: 1,\n\t\t\t\t\t\t\t\ttype: TYPE_UINT32,\n\t\t\t\t\t\t\t\tasInt: true\n\t\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t\tthis.vertexBufferIds = new VertexBuffer(this.device, new VertexFormat(this.device, formatDesc, ids.length), ids.length, {\n\t\t\t\t\t\tdata: new Uint32Array(ids)\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t}\n\t\tget targets() {\n\t\t\t\treturn this._targets;\n\t\t}\n\t\t_updateMorphFlags() {\n\t\t\t\tthis._morphPositions = false;\n\t\t\t\tthis._morphNormals = false;\n\t\t\t\tfor(let i = 0; i < this._targets.length; i++){\n\t\t\t\t\t\tconst target = this._targets[i];\n\t\t\t\t\t\tif (target.morphPositions) {\n\t\t\t\t\t\t\t\tthis._morphPositions = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (target.morphNormals) {\n\t\t\t\t\t\t\t\tthis._morphNormals = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_createTexture(name, format, arrayLength, levels) {\n\t\t\t\treturn new Texture(this.device, {\n\t\t\t\t\t\tlevels: levels,\n\t\t\t\t\t\tarrayLength: arrayLength,\n\t\t\t\t\t\twidth: this.morphTextureWidth,\n\t\t\t\t\t\theight: this.morphTextureHeight,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: false,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tname: name\n\t\t\t\t});\n\t\t}\n}\n\nclass MorphTarget {\n\t\tconstructor(options){\n\t\t\t\tthis.used = false;\n\t\t\t\tthis.options = options;\n\t\t\t\tthis._name = options.name;\n\t\t\t\tthis._defaultWeight = options.defaultWeight || 0;\n\t\t\t\tthis._aabb = options.aabb;\n\t\t\t\tthis.deltaPositions = options.deltaPositions;\n\t\t\t\tthis.morphPositions = !!options.deltaPositions;\n\t\t\t\tthis.morphNormals = !!options.deltaNormals;\n\t\t}\n\t\tget name() {\n\t\t\t\treturn this._name;\n\t\t}\n\t\tget defaultWeight() {\n\t\t\t\treturn this._defaultWeight;\n\t\t}\n\t\tget aabb() {\n\t\t\t\tif (!this._aabb) {\n\t\t\t\t\t\tthis._aabb = new BoundingBox();\n\t\t\t\t\t\tif (this.deltaPositions) {\n\t\t\t\t\t\t\t\tthis._aabb.compute(this.deltaPositions);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this._aabb;\n\t\t}\n\t\tclone() {\n\t\t\t\treturn new MorphTarget(this.options);\n\t\t}\n\t\t_postInit() {\n\t\t\t\tif (!this.options.preserveData) {\n\t\t\t\t\t\tthis.options = null;\n\t\t\t\t}\n\t\t\t\tthis.used = true;\n\t\t}\n}\n\nlet nonUniformScale;\nlet uniformScale = 1;\nconst particleTexChannels$1 = 4;\nconst rotMat = new Mat4();\nconst rotMatInv = new Mat4();\nconst randomPosTformed = new Vec3();\nconst randomPos = new Vec3();\nconst rndFactor3Vec = new Vec3();\nconst particlePosPrev = new Vec3();\nconst velocityVec = new Vec3();\nconst localVelocityVec = new Vec3();\nconst velocityVec2 = new Vec3();\nconst localVelocityVec2 = new Vec3();\nconst radialVelocityVec = new Vec3();\nconst particlePos = new Vec3();\nconst particleFinalPos = new Vec3();\nconst moveDirVec = new Vec3();\nconst tmpVec3$1 = new Vec3();\nfunction frac(f) {\n\t\treturn f - Math.floor(f);\n}\nfunction saturate$1(x) {\n\t\treturn Math.max(Math.min(x, 1), 0);\n}\nfunction glMod(x, y) {\n\t\treturn x - y * Math.floor(x / y);\n}\nfunction encodeFloatRGBA(v) {\n\t\tlet encX = frac(v);\n\t\tlet encY = frac(255.0 * v);\n\t\tlet encZ = frac(65025.0 * v);\n\t\tlet encW = frac(160581375.0 * v);\n\t\tencX -= encY / 255.0;\n\t\tencY -= encZ / 255.0;\n\t\tencZ -= encW / 255.0;\n\t\tencW -= encW / 255.0;\n\t\treturn [\n\t\t\t\tencX,\n\t\t\t\tencY,\n\t\t\t\tencZ,\n\t\t\t\tencW\n\t\t];\n}\nfunction encodeFloatRG(v) {\n\t\tlet encX = frac(v);\n\t\tlet encY = frac(255.0 * v);\n\t\tencX -= encY / 255.0;\n\t\tencY -= encY / 255.0;\n\t\treturn [\n\t\t\t\tencX,\n\t\t\t\tencY\n\t\t];\n}\nclass ParticleCPUUpdater {\n\t\tconstructor(emitter){\n\t\t\t\tthis._emitter = emitter;\n\t\t}\n\t\tcalcSpawnPosition(particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, i) {\n\t\t\t\tconst emitter = this._emitter;\n\t\t\t\tconst rX = Math.random();\n\t\t\t\tconst rY = Math.random();\n\t\t\t\tconst rZ = Math.random();\n\t\t\t\tconst rW = Math.random();\n\t\t\t\tif (emitter.useCpu) {\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 0 + emitter.numParticlesPot * 2 * particleTexChannels$1] = rX;\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 1 + emitter.numParticlesPot * 2 * particleTexChannels$1] = rY;\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 2 + emitter.numParticlesPot * 2 * particleTexChannels$1] = rZ;\n\t\t\t\t}\n\t\t\t\trandomPos.x = rX - 0.5;\n\t\t\t\trandomPos.y = rY - 0.5;\n\t\t\t\trandomPos.z = rZ - 0.5;\n\t\t\t\tif (emitter.emitterShape === EMITTERSHAPE_BOX) {\n\t\t\t\t\t\tconst max = Math.max(Math.abs(randomPos.x), Math.max(Math.abs(randomPos.y), Math.abs(randomPos.z)));\n\t\t\t\t\t\tconst edgeX = max + (0.5 - max) * extentsInnerRatioUniform[0];\n\t\t\t\t\t\tconst edgeY = max + (0.5 - max) * extentsInnerRatioUniform[1];\n\t\t\t\t\t\tconst edgeZ = max + (0.5 - max) * extentsInnerRatioUniform[2];\n\t\t\t\t\t\trandomPos.x = edgeX * (max === Math.abs(randomPos.x) ? Math.sign(randomPos.x) : 2 * randomPos.x);\n\t\t\t\t\t\trandomPos.y = edgeY * (max === Math.abs(randomPos.y) ? Math.sign(randomPos.y) : 2 * randomPos.y);\n\t\t\t\t\t\trandomPos.z = edgeZ * (max === Math.abs(randomPos.z) ? Math.sign(randomPos.z) : 2 * randomPos.z);\n\t\t\t\t\t\tif (!emitter.localSpace) {\n\t\t\t\t\t\t\t\trandomPosTformed.copy(emitterPos).add(spawnMatrix.transformPoint(randomPos));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trandomPosTformed.copy(spawnMatrix.transformPoint(randomPos));\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\trandomPos.normalize();\n\t\t\t\t\t\tconst spawnBoundsSphereInnerRatio = emitter.emitterRadius === 0 ? 0 : emitter.emitterRadiusInner / emitter.emitterRadius;\n\t\t\t\t\t\tconst r = rW * (1.0 - spawnBoundsSphereInnerRatio) + spawnBoundsSphereInnerRatio;\n\t\t\t\t\t\tif (!emitter.localSpace) {\n\t\t\t\t\t\t\t\trandomPosTformed.copy(emitterPos).add(randomPos.mulScalar(r * emitter.emitterRadius));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trandomPosTformed.copy(randomPos.mulScalar(r * emitter.emitterRadius));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst particleRate = math.lerp(emitter.rate, emitter.rate2, rX);\n\t\t\t\tlet startSpawnTime = -particleRate * i;\n\t\t\t\tif (emitter.pack8) {\n\t\t\t\t\t\tconst packX = (randomPosTformed.x - emitter.worldBounds.center.x) / emitter.worldBoundsSize.x + 0.5;\n\t\t\t\t\t\tconst packY = (randomPosTformed.y - emitter.worldBounds.center.y) / emitter.worldBoundsSize.y + 0.5;\n\t\t\t\t\t\tconst packZ = (randomPosTformed.z - emitter.worldBounds.center.z) / emitter.worldBoundsSize.z + 0.5;\n\t\t\t\t\t\tlet packA = math.lerp(emitter.startAngle * math.DEG_TO_RAD, emitter.startAngle2 * math.DEG_TO_RAD, rX);\n\t\t\t\t\t\tpackA = packA % (Math.PI * 2) / (Math.PI * 2);\n\t\t\t\t\t\tconst rg0 = encodeFloatRG(packX);\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1] = rg0[0];\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 1] = rg0[1];\n\t\t\t\t\t\tconst ba0 = encodeFloatRG(packY);\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 2] = ba0[0];\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 3] = ba0[1];\n\t\t\t\t\t\tconst rg1 = encodeFloatRG(packZ);\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 0 + emitter.numParticlesPot * particleTexChannels$1] = rg1[0];\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 1 + emitter.numParticlesPot * particleTexChannels$1] = rg1[1];\n\t\t\t\t\t\tconst ba1 = encodeFloatRG(packA);\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 2 + emitter.numParticlesPot * particleTexChannels$1] = ba1[0];\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 3 + emitter.numParticlesPot * particleTexChannels$1] = ba1[1];\n\t\t\t\t\t\tconst a2 = 1.0;\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 3 + emitter.numParticlesPot * particleTexChannels$1 * 2] = a2;\n\t\t\t\t\t\tconst maxNegLife = Math.max(emitter.lifetime, (emitter.numParticles - 1.0) * Math.max(emitter.rate, emitter.rate2));\n\t\t\t\t\t\tconst maxPosLife = emitter.lifetime + 1.0;\n\t\t\t\t\t\tstartSpawnTime = (startSpawnTime + maxNegLife) / (maxNegLife + maxPosLife);\n\t\t\t\t\t\tconst rgba3 = encodeFloatRGBA(startSpawnTime);\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 0 + emitter.numParticlesPot * particleTexChannels$1 * 3] = rgba3[0];\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 1 + emitter.numParticlesPot * particleTexChannels$1 * 3] = rgba3[1];\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 2 + emitter.numParticlesPot * particleTexChannels$1 * 3] = rgba3[2];\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 3 + emitter.numParticlesPot * particleTexChannels$1 * 3] = rgba3[3];\n\t\t\t\t} else {\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1] = randomPosTformed.x;\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 1] = randomPosTformed.y;\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 2] = randomPosTformed.z;\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 3] = math.lerp(emitter.startAngle * math.DEG_TO_RAD, emitter.startAngle2 * math.DEG_TO_RAD, rX);\n\t\t\t\t\t\tparticleTex[i * particleTexChannels$1 + 3 + emitter.numParticlesPot * particleTexChannels$1] = startSpawnTime;\n\t\t\t\t}\n\t\t}\n\t\tupdate(data, vbToSort, particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, delta, isOnStop) {\n\t\t\t\tlet a, b, c;\n\t\t\t\tconst emitter = this._emitter;\n\t\t\t\tif (emitter.meshInstance.node) {\n\t\t\t\t\t\tconst fullMat = emitter.meshInstance.node.worldTransform;\n\t\t\t\t\t\tfor(let j = 0; j < 12; j++){\n\t\t\t\t\t\t\t\trotMat.data[j] = fullMat.data[j];\n\t\t\t\t\t\t}\n\t\t\t\t\t\trotMatInv.copy(rotMat);\n\t\t\t\t\t\trotMatInv.invert();\n\t\t\t\t\t\tnonUniformScale = emitter.meshInstance.node.localScale;\n\t\t\t\t\t\tuniformScale = Math.max(Math.max(nonUniformScale.x, nonUniformScale.y), nonUniformScale.z);\n\t\t\t\t}\n\t\t\t\temitterPos = emitter.meshInstance.node === null || emitter.localSpace ? Vec3.ZERO : emitter.meshInstance.node.getPosition();\n\t\t\t\tconst posCam = emitter.camera ? emitter.camera._node.getPosition() : Vec3.ZERO;\n\t\t\t\tconst vertSize = !emitter.useMesh ? 15 : 17;\n\t\t\t\tlet cf, cc;\n\t\t\t\tlet rotSpeed, rotSpeed2, scale2, alpha, alpha2, radialSpeed, radialSpeed2;\n\t\t\t\tconst precision1 = emitter.precision - 1;\n\t\t\t\tfor(let i = 0; i < emitter.numParticles; i++){\n\t\t\t\t\t\tconst id = Math.floor(emitter.vbCPU[i * emitter.numParticleVerts * (emitter.useMesh ? 6 : 4) + 3]);\n\t\t\t\t\t\tconst rndFactor = particleTex[id * particleTexChannels$1 + 0 + emitter.numParticlesPot * 2 * particleTexChannels$1];\n\t\t\t\t\t\trndFactor3Vec.x = rndFactor;\n\t\t\t\t\t\trndFactor3Vec.y = particleTex[id * particleTexChannels$1 + 1 + emitter.numParticlesPot * 2 * particleTexChannels$1];\n\t\t\t\t\t\trndFactor3Vec.z = particleTex[id * particleTexChannels$1 + 2 + emitter.numParticlesPot * 2 * particleTexChannels$1];\n\t\t\t\t\t\tconst particleRate = emitter.rate + (emitter.rate2 - emitter.rate) * rndFactor;\n\t\t\t\t\t\tconst particleLifetime = emitter.lifetime;\n\t\t\t\t\t\tlet life = particleTex[id * particleTexChannels$1 + 3 + emitter.numParticlesPot * particleTexChannels$1] + delta;\n\t\t\t\t\t\tconst nlife = saturate$1(life / particleLifetime);\n\t\t\t\t\t\tlet scale = 0;\n\t\t\t\t\t\tlet alphaDiv = 0;\n\t\t\t\t\t\tconst angle = 0;\n\t\t\t\t\t\tconst respawn = life - delta <= 0.0 || life >= particleLifetime;\n\t\t\t\t\t\tif (respawn) {\n\t\t\t\t\t\t\t\tthis.calcSpawnPosition(particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet particleEnabled = life > 0.0 && life < particleLifetime;\n\t\t\t\t\t\tif (particleEnabled) {\n\t\t\t\t\t\t\t\tc = nlife * precision1;\n\t\t\t\t\t\t\t\tcf = Math.floor(c);\n\t\t\t\t\t\t\t\tcc = Math.ceil(c);\n\t\t\t\t\t\t\t\tc %= 1;\n\t\t\t\t\t\t\t\ta = emitter.qRotSpeed[cf];\n\t\t\t\t\t\t\t\tb = emitter.qRotSpeed[cc];\n\t\t\t\t\t\t\t\trotSpeed = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qRotSpeed2[cf];\n\t\t\t\t\t\t\t\tb = emitter.qRotSpeed2[cc];\n\t\t\t\t\t\t\t\trotSpeed2 = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qScale[cf];\n\t\t\t\t\t\t\t\tb = emitter.qScale[cc];\n\t\t\t\t\t\t\t\tscale = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qScale2[cf];\n\t\t\t\t\t\t\t\tb = emitter.qScale2[cc];\n\t\t\t\t\t\t\t\tscale2 = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qAlpha[cf];\n\t\t\t\t\t\t\t\tb = emitter.qAlpha[cc];\n\t\t\t\t\t\t\t\talpha = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qAlpha2[cf];\n\t\t\t\t\t\t\t\tb = emitter.qAlpha2[cc];\n\t\t\t\t\t\t\t\talpha2 = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qRadialSpeed[cf];\n\t\t\t\t\t\t\t\tb = emitter.qRadialSpeed[cc];\n\t\t\t\t\t\t\t\tradialSpeed = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qRadialSpeed2[cf];\n\t\t\t\t\t\t\t\tb = emitter.qRadialSpeed2[cc];\n\t\t\t\t\t\t\t\tradialSpeed2 = a + (b - a) * c;\n\t\t\t\t\t\t\t\tradialSpeed += (radialSpeed2 - radialSpeed) * (rndFactor * 100.0 % 1.0);\n\t\t\t\t\t\t\t\tparticlePosPrev.x = particleTex[id * particleTexChannels$1];\n\t\t\t\t\t\t\t\tparticlePosPrev.y = particleTex[id * particleTexChannels$1 + 1];\n\t\t\t\t\t\t\t\tparticlePosPrev.z = particleTex[id * particleTexChannels$1 + 2];\n\t\t\t\t\t\t\t\tif (!emitter.localSpace) {\n\t\t\t\t\t\t\t\t\t\tradialVelocityVec.copy(particlePosPrev).sub(emitterPos);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tradialVelocityVec.copy(particlePosPrev);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tradialVelocityVec.normalize().mulScalar(radialSpeed);\n\t\t\t\t\t\t\t\tcf *= 3;\n\t\t\t\t\t\t\t\tcc *= 3;\n\t\t\t\t\t\t\t\ta = emitter.qLocalVelocity[cf];\n\t\t\t\t\t\t\t\tb = emitter.qLocalVelocity[cc];\n\t\t\t\t\t\t\t\tlocalVelocityVec.x = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qLocalVelocity[cf + 1];\n\t\t\t\t\t\t\t\tb = emitter.qLocalVelocity[cc + 1];\n\t\t\t\t\t\t\t\tlocalVelocityVec.y = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qLocalVelocity[cf + 2];\n\t\t\t\t\t\t\t\tb = emitter.qLocalVelocity[cc + 2];\n\t\t\t\t\t\t\t\tlocalVelocityVec.z = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qLocalVelocity2[cf];\n\t\t\t\t\t\t\t\tb = emitter.qLocalVelocity2[cc];\n\t\t\t\t\t\t\t\tlocalVelocityVec2.x = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qLocalVelocity2[cf + 1];\n\t\t\t\t\t\t\t\tb = emitter.qLocalVelocity2[cc + 1];\n\t\t\t\t\t\t\t\tlocalVelocityVec2.y = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qLocalVelocity2[cf + 2];\n\t\t\t\t\t\t\t\tb = emitter.qLocalVelocity2[cc + 2];\n\t\t\t\t\t\t\t\tlocalVelocityVec2.z = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qVelocity[cf];\n\t\t\t\t\t\t\t\tb = emitter.qVelocity[cc];\n\t\t\t\t\t\t\t\tvelocityVec.x = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qVelocity[cf + 1];\n\t\t\t\t\t\t\t\tb = emitter.qVelocity[cc + 1];\n\t\t\t\t\t\t\t\tvelocityVec.y = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qVelocity[cf + 2];\n\t\t\t\t\t\t\t\tb = emitter.qVelocity[cc + 2];\n\t\t\t\t\t\t\t\tvelocityVec.z = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qVelocity2[cf];\n\t\t\t\t\t\t\t\tb = emitter.qVelocity2[cc];\n\t\t\t\t\t\t\t\tvelocityVec2.x = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qVelocity2[cf + 1];\n\t\t\t\t\t\t\t\tb = emitter.qVelocity2[cc + 1];\n\t\t\t\t\t\t\t\tvelocityVec2.y = a + (b - a) * c;\n\t\t\t\t\t\t\t\ta = emitter.qVelocity2[cf + 2];\n\t\t\t\t\t\t\t\tb = emitter.qVelocity2[cc + 2];\n\t\t\t\t\t\t\t\tvelocityVec2.z = a + (b - a) * c;\n\t\t\t\t\t\t\t\tlocalVelocityVec.x += (localVelocityVec2.x - localVelocityVec.x) * rndFactor3Vec.x;\n\t\t\t\t\t\t\t\tlocalVelocityVec.y += (localVelocityVec2.y - localVelocityVec.y) * rndFactor3Vec.y;\n\t\t\t\t\t\t\t\tlocalVelocityVec.z += (localVelocityVec2.z - localVelocityVec.z) * rndFactor3Vec.z;\n\t\t\t\t\t\t\t\tif (emitter.initialVelocity > 0) {\n\t\t\t\t\t\t\t\t\t\tif (emitter.emitterShape === EMITTERSHAPE_SPHERE) {\n\t\t\t\t\t\t\t\t\t\t\t\trandomPos.copy(rndFactor3Vec).mulScalar(2).sub(Vec3.ONE).normalize();\n\t\t\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.add(randomPos.mulScalar(emitter.initialVelocity));\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.add(Vec3.FORWARD.mulScalar(emitter.initialVelocity));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tvelocityVec.x += (velocityVec2.x - velocityVec.x) * rndFactor3Vec.x;\n\t\t\t\t\t\t\t\tvelocityVec.y += (velocityVec2.y - velocityVec.y) * rndFactor3Vec.y;\n\t\t\t\t\t\t\t\tvelocityVec.z += (velocityVec2.z - velocityVec.z) * rndFactor3Vec.z;\n\t\t\t\t\t\t\t\trotSpeed += (rotSpeed2 - rotSpeed) * rndFactor3Vec.y;\n\t\t\t\t\t\t\t\tscale = (scale + (scale2 - scale) * (rndFactor * 10000.0 % 1.0)) * uniformScale;\n\t\t\t\t\t\t\t\talphaDiv = (alpha2 - alpha) * (rndFactor * 1000.0 % 1.0);\n\t\t\t\t\t\t\t\tif (emitter.meshInstance.node) {\n\t\t\t\t\t\t\t\t\t\tif (!emitter.localSpace) {\n\t\t\t\t\t\t\t\t\t\t\t\trotMat.transformPoint(localVelocityVec, localVelocityVec);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.x /= nonUniformScale.x;\n\t\t\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.y /= nonUniformScale.y;\n\t\t\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.z /= nonUniformScale.z;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!emitter.localSpace) {\n\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.add(velocityVec.mul(nonUniformScale));\n\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.add(radialVelocityVec.mul(nonUniformScale));\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\trotMatInv.transformPoint(velocityVec, velocityVec);\n\t\t\t\t\t\t\t\t\t\tlocalVelocityVec.add(velocityVec).add(radialVelocityVec);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmoveDirVec.copy(localVelocityVec);\n\t\t\t\t\t\t\t\tparticlePos.copy(particlePosPrev).add(localVelocityVec.mulScalar(delta));\n\t\t\t\t\t\t\t\tparticleFinalPos.copy(particlePos);\n\t\t\t\t\t\t\t\tparticleTex[id * particleTexChannels$1] = particleFinalPos.x;\n\t\t\t\t\t\t\t\tparticleTex[id * particleTexChannels$1 + 1] = particleFinalPos.y;\n\t\t\t\t\t\t\t\tparticleTex[id * particleTexChannels$1 + 2] = particleFinalPos.z;\n\t\t\t\t\t\t\t\tparticleTex[id * particleTexChannels$1 + 3] += rotSpeed * delta;\n\t\t\t\t\t\t\t\tif (emitter.wrap && emitter.wrapBounds) {\n\t\t\t\t\t\t\t\t\t\tif (!emitter.localSpace) {\n\t\t\t\t\t\t\t\t\t\t\t\tparticleFinalPos.sub(emitterPos);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tparticleFinalPos.x = glMod(particleFinalPos.x, emitter.wrapBounds.x) - emitter.wrapBounds.x * 0.5;\n\t\t\t\t\t\t\t\t\t\tparticleFinalPos.y = glMod(particleFinalPos.y, emitter.wrapBounds.y) - emitter.wrapBounds.y * 0.5;\n\t\t\t\t\t\t\t\t\t\tparticleFinalPos.z = glMod(particleFinalPos.z, emitter.wrapBounds.z) - emitter.wrapBounds.z * 0.5;\n\t\t\t\t\t\t\t\t\t\tif (!emitter.localSpace) {\n\t\t\t\t\t\t\t\t\t\t\t\tparticleFinalPos.add(emitterPos);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (emitter.sort > 0) {\n\t\t\t\t\t\t\t\t\t\tif (emitter.sort === 1) {\n\t\t\t\t\t\t\t\t\t\t\t\ttmpVec3$1.copy(particleFinalPos).sub(posCam);\n\t\t\t\t\t\t\t\t\t\t\t\temitter.particleDistance[id] = -(tmpVec3$1.x * tmpVec3$1.x + tmpVec3$1.y * tmpVec3$1.y + tmpVec3$1.z * tmpVec3$1.z);\n\t\t\t\t\t\t\t\t\t\t} else if (emitter.sort === 2) {\n\t\t\t\t\t\t\t\t\t\t\t\temitter.particleDistance[id] = life;\n\t\t\t\t\t\t\t\t\t\t} else if (emitter.sort === 3) {\n\t\t\t\t\t\t\t\t\t\t\t\temitter.particleDistance[id] = -life;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (isOnStop) {\n\t\t\t\t\t\t\t\tif (life < 0) {\n\t\t\t\t\t\t\t\t\t\tparticleTex[id * particleTexChannels$1 + 3 + emitter.numParticlesPot * 2 * particleTexChannels$1] = -1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (life >= particleLifetime) {\n\t\t\t\t\t\t\t\t\t\tlife -= Math.max(particleLifetime, (emitter.numParticles - 1) * particleRate);\n\t\t\t\t\t\t\t\t\t\tparticleTex[id * particleTexChannels$1 + 3 + emitter.numParticlesPot * 2 * particleTexChannels$1] = emitter.loop ? 1 : -1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (life < 0 && emitter.loop) {\n\t\t\t\t\t\t\t\t\t\tparticleTex[id * particleTexChannels$1 + 3 + emitter.numParticlesPot * 2 * particleTexChannels$1] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (particleTex[id * particleTexChannels$1 + 3 + emitter.numParticlesPot * 2 * particleTexChannels$1] < 0) {\n\t\t\t\t\t\t\t\tparticleEnabled = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparticleTex[id * particleTexChannels$1 + 3 + emitter.numParticlesPot * particleTexChannels$1] = life;\n\t\t\t\t\t\tfor(let v = 0; v < emitter.numParticleVerts; v++){\n\t\t\t\t\t\t\t\tconst vbOffset = (i * emitter.numParticleVerts + v) * (emitter.useMesh ? 6 : 4);\n\t\t\t\t\t\t\t\tlet quadX = emitter.vbCPU[vbOffset];\n\t\t\t\t\t\t\t\tlet quadY = emitter.vbCPU[vbOffset + 1];\n\t\t\t\t\t\t\t\tlet quadZ = emitter.vbCPU[vbOffset + 2];\n\t\t\t\t\t\t\t\tif (!particleEnabled) {\n\t\t\t\t\t\t\t\t\t\tquadX = quadY = quadZ = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst w = i * emitter.numParticleVerts * vertSize + v * vertSize;\n\t\t\t\t\t\t\t\tdata[w] = particleFinalPos.x;\n\t\t\t\t\t\t\t\tdata[w + 1] = particleFinalPos.y;\n\t\t\t\t\t\t\t\tdata[w + 2] = particleFinalPos.z;\n\t\t\t\t\t\t\t\tdata[w + 3] = nlife;\n\t\t\t\t\t\t\t\tdata[w + 4] = emitter.alignToMotion ? angle : particleTex[id * particleTexChannels$1 + 3];\n\t\t\t\t\t\t\t\tdata[w + 5] = scale;\n\t\t\t\t\t\t\t\tdata[w + 6] = alphaDiv;\n\t\t\t\t\t\t\t\tdata[w + 7] = moveDirVec.x;\n\t\t\t\t\t\t\t\tdata[w + 8] = quadX;\n\t\t\t\t\t\t\t\tdata[w + 9] = quadY;\n\t\t\t\t\t\t\t\tdata[w + 10] = quadZ;\n\t\t\t\t\t\t\t\tdata[w + 11] = moveDirVec.y;\n\t\t\t\t\t\t\t\tdata[w + 12] = id;\n\t\t\t\t\t\t\t\tdata[w + 13] = moveDirVec.z;\n\t\t\t\t\t\t\t\tdata[w + 14] = emitter.vbCPU[vbOffset + 3];\n\t\t\t\t\t\t\t\tif (emitter.useMesh) {\n\t\t\t\t\t\t\t\t\t\tdata[w + 15] = emitter.vbCPU[vbOffset + 4];\n\t\t\t\t\t\t\t\t\t\tdata[w + 16] = emitter.vbCPU[vbOffset + 5];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (emitter.sort > PARTICLESORT_NONE && emitter.camera) {\n\t\t\t\t\t\tconst vbStride = emitter.useMesh ? 6 : 4;\n\t\t\t\t\t\tconst particleDistance = emitter.particleDistance;\n\t\t\t\t\t\tfor(let i = 0; i < emitter.numParticles; i++){\n\t\t\t\t\t\t\t\tvbToSort[i][0] = i;\n\t\t\t\t\t\t\t\tvbToSort[i][1] = particleDistance[Math.floor(emitter.vbCPU[i * emitter.numParticleVerts * vbStride + 3])];\n\t\t\t\t\t\t}\n\t\t\t\t\t\temitter.vbOld.set(emitter.vbCPU);\n\t\t\t\t\t\tvbToSort.sort((p1, p2)=>{\n\t\t\t\t\t\t\t\treturn p1[1] - p2[1];\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor(let i = 0; i < emitter.numParticles; i++){\n\t\t\t\t\t\t\t\tconst src = vbToSort[i][0] * emitter.numParticleVerts * vbStride;\n\t\t\t\t\t\t\t\tconst dest = i * emitter.numParticleVerts * vbStride;\n\t\t\t\t\t\t\t\tfor(let j = 0; j < emitter.numParticleVerts * vbStride; j++){\n\t\t\t\t\t\t\t\t\t\temitter.vbCPU[dest + j] = emitter.vbOld[src + j];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nconst spawnMatrix3 = new Mat3();\nconst emitterMatrix3 = new Mat3();\nconst emitterMatrix3Inv = new Mat3();\nclass ParticleGPUUpdater {\n\t\tconstructor(emitter, gd){\n\t\t\t\tthis._emitter = emitter;\n\t\t\t\tthis.frameRandomUniform = new Float32Array(3);\n\t\t\t\tthis.emitterPosUniform = new Float32Array(3);\n\t\t\t\tthis.emitterScaleUniform = new Float32Array([\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1\n\t\t\t\t]);\n\t\t\t\tthis.worldBoundsMulUniform = new Float32Array(3);\n\t\t\t\tthis.worldBoundsAddUniform = new Float32Array(3);\n\t\t\t\tthis.inBoundsSizeUniform = new Float32Array(3);\n\t\t\t\tthis.inBoundsCenterUniform = new Float32Array(3);\n\t\t\t\tthis.constantParticleTexIN = gd.scope.resolve('particleTexIN');\n\t\t\t\tthis.constantParticleTexOUT = gd.scope.resolve('particleTexOUT');\n\t\t\t\tthis.constantEmitterPos = gd.scope.resolve('emitterPos');\n\t\t\t\tthis.constantEmitterScale = gd.scope.resolve('emitterScale');\n\t\t\t\tthis.constantSpawnBounds = gd.scope.resolve('spawnBounds');\n\t\t\t\tthis.constantSpawnPosInnerRatio = gd.scope.resolve('spawnPosInnerRatio');\n\t\t\t\tthis.constantSpawnBoundsSphere = gd.scope.resolve('spawnBoundsSphere');\n\t\t\t\tthis.constantSpawnBoundsSphereInnerRatio = gd.scope.resolve('spawnBoundsSphereInnerRatio');\n\t\t\t\tthis.constantInitialVelocity = gd.scope.resolve('initialVelocity');\n\t\t\t\tthis.constantFrameRandom = gd.scope.resolve('frameRandom');\n\t\t\t\tthis.constantDelta = gd.scope.resolve('delta');\n\t\t\t\tthis.constantRate = gd.scope.resolve('rate');\n\t\t\t\tthis.constantRateDiv = gd.scope.resolve('rateDiv');\n\t\t\t\tthis.constantLifetime = gd.scope.resolve('lifetime');\n\t\t\t\tthis.constantGraphSampleSize = gd.scope.resolve('graphSampleSize');\n\t\t\t\tthis.constantGraphNumSamples = gd.scope.resolve('graphNumSamples');\n\t\t\t\tthis.constantInternalTex0 = gd.scope.resolve('internalTex0');\n\t\t\t\tthis.constantInternalTex1 = gd.scope.resolve('internalTex1');\n\t\t\t\tthis.constantInternalTex2 = gd.scope.resolve('internalTex2');\n\t\t\t\tthis.constantInternalTex3 = gd.scope.resolve('internalTex3');\n\t\t\t\tthis.constantEmitterMatrix = gd.scope.resolve('emitterMatrix');\n\t\t\t\tthis.constantEmitterMatrixInv = gd.scope.resolve('emitterMatrixInv');\n\t\t\t\tthis.constantNumParticles = gd.scope.resolve('numParticles');\n\t\t\t\tthis.constantNumParticlesPot = gd.scope.resolve('numParticlesPot');\n\t\t\t\tthis.constantLocalVelocityDivMult = gd.scope.resolve('localVelocityDivMult');\n\t\t\t\tthis.constantVelocityDivMult = gd.scope.resolve('velocityDivMult');\n\t\t\t\tthis.constantRotSpeedDivMult = gd.scope.resolve('rotSpeedDivMult');\n\t\t\t\tthis.constantSeed = gd.scope.resolve('seed');\n\t\t\t\tthis.constantStartAngle = gd.scope.resolve('startAngle');\n\t\t\t\tthis.constantStartAngle2 = gd.scope.resolve('startAngle2');\n\t\t\t\tthis.constantOutBoundsMul = gd.scope.resolve('outBoundsMul');\n\t\t\t\tthis.constantOutBoundsAdd = gd.scope.resolve('outBoundsAdd');\n\t\t\t\tthis.constantInBoundsSize = gd.scope.resolve('inBoundsSize');\n\t\t\t\tthis.constantInBoundsCenter = gd.scope.resolve('inBoundsCenter');\n\t\t\t\tthis.constantMaxVel = gd.scope.resolve('maxVel');\n\t\t\t\tthis.constantFaceTangent = gd.scope.resolve('faceTangent');\n\t\t\t\tthis.constantFaceBinorm = gd.scope.resolve('faceBinorm');\n\t\t}\n\t\t_setInputBounds() {\n\t\t\t\tthis.inBoundsSizeUniform[0] = this._emitter.prevWorldBoundsSize.x;\n\t\t\t\tthis.inBoundsSizeUniform[1] = this._emitter.prevWorldBoundsSize.y;\n\t\t\t\tthis.inBoundsSizeUniform[2] = this._emitter.prevWorldBoundsSize.z;\n\t\t\t\tthis.constantInBoundsSize.setValue(this.inBoundsSizeUniform);\n\t\t\t\tthis.inBoundsCenterUniform[0] = this._emitter.prevWorldBoundsCenter.x;\n\t\t\t\tthis.inBoundsCenterUniform[1] = this._emitter.prevWorldBoundsCenter.y;\n\t\t\t\tthis.inBoundsCenterUniform[2] = this._emitter.prevWorldBoundsCenter.z;\n\t\t\t\tthis.constantInBoundsCenter.setValue(this.inBoundsCenterUniform);\n\t\t}\n\t\trandomize() {\n\t\t\t\tthis.frameRandomUniform[0] = Math.random();\n\t\t\t\tthis.frameRandomUniform[1] = Math.random();\n\t\t\t\tthis.frameRandomUniform[2] = Math.random();\n\t\t}\n\t\tupdate(device, spawnMatrix, extentsInnerRatioUniform, delta, isOnStop) {\n\t\t\t\tconst emitter = this._emitter;\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\tthis.randomize();\n\t\t\t\tthis.constantGraphSampleSize.setValue(1.0 / emitter.precision);\n\t\t\t\tthis.constantGraphNumSamples.setValue(emitter.precision);\n\t\t\t\tthis.constantNumParticles.setValue(emitter.numParticles);\n\t\t\t\tthis.constantNumParticlesPot.setValue(emitter.numParticlesPot);\n\t\t\t\tthis.constantInternalTex0.setValue(emitter.internalTex0);\n\t\t\t\tthis.constantInternalTex1.setValue(emitter.internalTex1);\n\t\t\t\tthis.constantInternalTex2.setValue(emitter.internalTex2);\n\t\t\t\tthis.constantInternalTex3.setValue(emitter.internalTex3);\n\t\t\t\tconst node = emitter.meshInstance.node;\n\t\t\t\tconst emitterScale = node === null ? Vec3.ONE : node.localScale;\n\t\t\t\tif (emitter.pack8) {\n\t\t\t\t\t\tthis.worldBoundsMulUniform[0] = emitter.worldBoundsMul.x;\n\t\t\t\t\t\tthis.worldBoundsMulUniform[1] = emitter.worldBoundsMul.y;\n\t\t\t\t\t\tthis.worldBoundsMulUniform[2] = emitter.worldBoundsMul.z;\n\t\t\t\t\t\tthis.constantOutBoundsMul.setValue(this.worldBoundsMulUniform);\n\t\t\t\t\t\tthis.worldBoundsAddUniform[0] = emitter.worldBoundsAdd.x;\n\t\t\t\t\t\tthis.worldBoundsAddUniform[1] = emitter.worldBoundsAdd.y;\n\t\t\t\t\t\tthis.worldBoundsAddUniform[2] = emitter.worldBoundsAdd.z;\n\t\t\t\t\t\tthis.constantOutBoundsAdd.setValue(this.worldBoundsAddUniform);\n\t\t\t\t\t\tthis._setInputBounds();\n\t\t\t\t\t\tlet maxVel = emitter.maxVel * Math.max(Math.max(emitterScale.x, emitterScale.y), emitterScale.z);\n\t\t\t\t\t\tmaxVel = Math.max(maxVel, 1);\n\t\t\t\t\t\tthis.constantMaxVel.setValue(maxVel);\n\t\t\t\t}\n\t\t\t\tconst emitterPos = node === null || emitter.localSpace ? Vec3.ZERO : node.getPosition();\n\t\t\t\tconst emitterMatrix = node === null ? Mat4.IDENTITY : node.getWorldTransform();\n\t\t\t\tif (emitter.emitterShape === EMITTERSHAPE_BOX) {\n\t\t\t\t\t\tspawnMatrix3.setFromMat4(spawnMatrix);\n\t\t\t\t\t\tthis.constantSpawnBounds.setValue(spawnMatrix3.data);\n\t\t\t\t\t\tthis.constantSpawnPosInnerRatio.setValue(extentsInnerRatioUniform);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.constantSpawnBoundsSphere.setValue(emitter.emitterRadius);\n\t\t\t\t\t\tthis.constantSpawnBoundsSphereInnerRatio.setValue(emitter.emitterRadius === 0 ? 0 : emitter.emitterRadiusInner / emitter.emitterRadius);\n\t\t\t\t}\n\t\t\t\tthis.constantInitialVelocity.setValue(emitter.initialVelocity);\n\t\t\t\temitterMatrix3.setFromMat4(emitterMatrix);\n\t\t\t\temitterMatrix3Inv.invertMat4(emitterMatrix);\n\t\t\t\tthis.emitterPosUniform[0] = emitterPos.x;\n\t\t\t\tthis.emitterPosUniform[1] = emitterPos.y;\n\t\t\t\tthis.emitterPosUniform[2] = emitterPos.z;\n\t\t\t\tthis.constantEmitterPos.setValue(this.emitterPosUniform);\n\t\t\t\tthis.constantFrameRandom.setValue(this.frameRandomUniform);\n\t\t\t\tthis.constantDelta.setValue(delta);\n\t\t\t\tthis.constantRate.setValue(emitter.rate);\n\t\t\t\tthis.constantRateDiv.setValue(emitter.rate2 - emitter.rate);\n\t\t\t\tthis.constantStartAngle.setValue(emitter.startAngle * math.DEG_TO_RAD);\n\t\t\t\tthis.constantStartAngle2.setValue(emitter.startAngle2 * math.DEG_TO_RAD);\n\t\t\t\tthis.constantSeed.setValue(emitter.seed);\n\t\t\t\tthis.constantLifetime.setValue(emitter.lifetime);\n\t\t\t\tthis.emitterScaleUniform[0] = emitterScale.x;\n\t\t\t\tthis.emitterScaleUniform[1] = emitterScale.y;\n\t\t\t\tthis.emitterScaleUniform[2] = emitterScale.z;\n\t\t\t\tthis.constantEmitterScale.setValue(this.emitterScaleUniform);\n\t\t\t\tthis.constantEmitterMatrix.setValue(emitterMatrix3.data);\n\t\t\t\tthis.constantEmitterMatrixInv.setValue(emitterMatrix3Inv.data);\n\t\t\t\tthis.constantLocalVelocityDivMult.setValue(emitter.localVelocityUMax);\n\t\t\t\tthis.constantVelocityDivMult.setValue(emitter.velocityUMax);\n\t\t\t\tthis.constantRotSpeedDivMult.setValue(emitter.rotSpeedUMax[0]);\n\t\t\t\tlet texIN = emitter.swapTex ? emitter.particleTexOUT : emitter.particleTexIN;\n\t\t\t\ttexIN = emitter.beenReset ? emitter.particleTexStart : texIN;\n\t\t\t\tconst texOUT = emitter.swapTex ? emitter.particleTexIN : emitter.particleTexOUT;\n\t\t\t\tthis.constantParticleTexIN.setValue(texIN);\n\t\t\t\tdrawQuadWithShader(device, emitter.swapTex ? emitter.rtParticleTexIN : emitter.rtParticleTexOUT, !isOnStop ? emitter.loop ? emitter.shaderParticleUpdateRespawn : emitter.shaderParticleUpdateNoRespawn : emitter.shaderParticleUpdateOnStop);\n\t\t\t\temitter.material.setParameter('particleTexOUT', texIN);\n\t\t\t\temitter.material.setParameter('particleTexIN', texOUT);\n\t\t\t\temitter.beenReset = false;\n\t\t\t\temitter.swapTex = !emitter.swapTex;\n\t\t\t\temitter.prevWorldBoundsSize.copy(emitter.worldBoundsSize);\n\t\t\t\temitter.prevWorldBoundsCenter.copy(emitter.worldBounds.center);\n\t\t\t\tif (emitter.pack8) {\n\t\t\t\t\t\tthis._setInputBounds();\n\t\t\t\t}\n\t\t}\n}\n\nconst normalTypeNames = [\n\t\t'NONE',\n\t\t'VERTEX',\n\t\t'MAP'\n];\nclass ShaderGeneratorParticle extends ShaderGenerator {\n\t\tgenerateKey(options) {\n\t\t\t\tconst definesHash = ShaderGenerator.definesHash(options.defines);\n\t\t\t\tlet key = `particle_${definesHash}_`;\n\t\t\t\tfor(const prop in options){\n\t\t\t\t\t\tif (options.hasOwnProperty(prop)) {\n\t\t\t\t\t\t\t\tkey += options[prop];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t}\n\t\tcreateVertexDefines(options, attributes) {\n\t\t\t\tconst vDefines = new Map(options.defines);\n\t\t\t\tif (options.mesh) vDefines.set('USE_MESH', '');\n\t\t\t\tif (options.meshUv) vDefines.set('USE_MESH_UV', '');\n\t\t\t\tif (options.localSpace) vDefines.set('LOCAL_SPACE', '');\n\t\t\t\tif (options.screenSpace) vDefines.set('SCREEN_SPACE', '');\n\t\t\t\tif (options.animTex) vDefines.set('ANIMTEX', '');\n\t\t\t\tif (options.soft > 0) vDefines.set('SOFT', '');\n\t\t\t\tif (options.stretch > 0.0) vDefines.set('STRETCH', '');\n\t\t\t\tif (options.customFace) vDefines.set('CUSTOM_FACE', '');\n\t\t\t\tif (options.pack8) vDefines.set('PACK8', '');\n\t\t\t\tif (options.localSpace) vDefines.set('LOCAL_SPACE', '');\n\t\t\t\tif (options.animTexLoop) vDefines.set('ANIMTEX_LOOP', '');\n\t\t\t\tif (options.wrap) vDefines.set('WRAP', '');\n\t\t\t\tif (options.alignToMotion) vDefines.set('ALIGN_TO_MOTION', '');\n\t\t\t\tvDefines.set('NORMAL', normalTypeNames[options.normal]);\n\t\t\t\tattributes.particle_vertexData = SEMANTIC_POSITION;\n\t\t\t\tif (options.mesh && options.meshUv) {\n\t\t\t\t\t\tattributes.particle_uv = SEMANTIC_TEXCOORD0;\n\t\t\t\t}\n\t\t\t\tif (options.useCpu) {\n\t\t\t\t\t\tattributes.particle_vertexData2 = SEMANTIC_ATTR1;\n\t\t\t\t\t\tattributes.particle_vertexData3 = SEMANTIC_ATTR2;\n\t\t\t\t\t\tattributes.particle_vertexData4 = SEMANTIC_ATTR3;\n\t\t\t\t\t\tattributes.particle_vertexData5 = SEMANTIC_ATTR4;\n\t\t\t\t}\n\t\t\t\treturn vDefines;\n\t\t}\n\t\tcreateFragmentDefines(options) {\n\t\t\t\tconst fDefines = new Map(options.defines);\n\t\t\t\tif (options.soft > 0) fDefines.set('SOFT', '');\n\t\t\t\tif (options.halflambert) fDefines.set('HALF_LAMBERT', '');\n\t\t\t\tfDefines.set('NORMAL', normalTypeNames[options.normal]);\n\t\t\t\tfDefines.set('BLEND', blendNames[options.blend]);\n\t\t\t\treturn fDefines;\n\t\t}\n\t\tcreateShaderDefinition(device, options) {\n\t\t\t\tconst shaderLanguage = device.isWebGPU ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL;\n\t\t\t\tconst engineChunks = ShaderChunks.get(device, shaderLanguage);\n\t\t\t\tconst attributes = {};\n\t\t\t\tconst vDefines = this.createVertexDefines(options, attributes);\n\t\t\t\tconst fDefines = this.createFragmentDefines(options);\n\t\t\t\tconst executionDefine = `PARTICLE_${options.useCpu ? 'CPU' : 'GPU'}\\n`;\n\t\t\t\tvDefines.set(executionDefine, '');\n\t\t\t\tfDefines.set(executionDefine, '');\n\t\t\t\tconst includes = new Map(engineChunks);\n\t\t\t\treturn ShaderDefinitionUtils.createDefinition(device, {\n\t\t\t\t\t\tname: 'ParticleShader',\n\t\t\t\t\t\tshaderLanguage: shaderLanguage,\n\t\t\t\t\t\tattributes: attributes,\n\t\t\t\t\t\tvertexCode: engineChunks.get('particle_shaderVS'),\n\t\t\t\t\t\tfragmentCode: engineChunks.get('particle_shaderPS'),\n\t\t\t\t\t\tfragmentDefines: fDefines,\n\t\t\t\t\t\tfragmentIncludes: includes,\n\t\t\t\t\t\tvertexIncludes: includes,\n\t\t\t\t\t\tvertexDefines: vDefines\n\t\t\t\t});\n\t\t}\n}\nconst particle = new ShaderGeneratorParticle();\n\nclass ParticleMaterial extends Material {\n\t\tconstructor(emitter){\n\t\t\t\tsuper(), this.emitter = null;\n\t\t\t\tthis.emitter = emitter;\n\t\t}\n\t\tgetShaderVariant(params) {\n\t\t\t\tconst { device, scene, cameraShaderParams, objDefs } = params;\n\t\t\t\tconst { emitter } = this;\n\t\t\t\tconst options = {\n\t\t\t\t\t\tdefines: ShaderUtils.getCoreDefines(this, params),\n\t\t\t\t\t\tpass: SHADER_FORWARD,\n\t\t\t\t\t\tuseCpu: this.emitter.useCpu,\n\t\t\t\t\t\tnormal: emitter.lighting ? emitter.normalMap !== null ? 2 : 1 : 0,\n\t\t\t\t\t\thalflambert: this.emitter.halfLambert,\n\t\t\t\t\t\tstretch: this.emitter.stretch,\n\t\t\t\t\t\talignToMotion: this.emitter.alignToMotion,\n\t\t\t\t\t\tsoft: this.emitter.depthSoftening,\n\t\t\t\t\t\tmesh: this.emitter.useMesh,\n\t\t\t\t\t\tmeshUv: objDefs & SHADERDEF_UV0,\n\t\t\t\t\t\tgamma: cameraShaderParams?.shaderOutputGamma ?? GAMMA_NONE,\n\t\t\t\t\t\ttoneMap: cameraShaderParams?.toneMapping ?? TONEMAP_LINEAR,\n\t\t\t\t\t\tfog: scene && !this.emitter.noFog ? scene.fog.type : 'none',\n\t\t\t\t\t\twrap: this.emitter.wrap && this.emitter.wrapBounds,\n\t\t\t\t\t\tlocalSpace: this.emitter.localSpace,\n\t\t\t\t\t\tscreenSpace: emitter.inTools ? false : this.emitter.screenSpace,\n\t\t\t\t\t\tblend: this.emitter.blendType,\n\t\t\t\t\t\tanimTex: this.emitter._isAnimated(),\n\t\t\t\t\t\tanimTexLoop: this.emitter.animLoop,\n\t\t\t\t\t\tpack8: this.emitter.pack8,\n\t\t\t\t\t\tcustomFace: this.emitter.orientation !== PARTICLEORIENTATION_SCREEN\n\t\t\t\t};\n\t\t\t\tconst processingOptions = new ShaderProcessorOptions(params.viewUniformFormat, params.viewBindGroupFormat, params.vertexFormat);\n\t\t\t\tconst library = getProgramLibrary(device);\n\t\t\t\tlibrary.register('particle', particle);\n\t\t\t\treturn library.getProgram('particle', options, processingOptions, this.userId);\n\t\t}\n}\n\nconst particleVerts = [\n\t\t[\n\t\t\t\t-1,\n\t\t\t\t-1\n\t\t],\n\t\t[\n\t\t\t\t1,\n\t\t\t\t-1\n\t\t],\n\t\t[\n\t\t\t\t1,\n\t\t\t\t1\n\t\t],\n\t\t[\n\t\t\t\t-1,\n\t\t\t\t1\n\t\t]\n];\nfunction _createTexture(device, width, height, pixelData, format = PIXELFORMAT_RGBA32F, mult8Bit, filter) {\n\t\tlet mipFilter = FILTER_NEAREST;\n\t\tif (filter && (format === PIXELFORMAT_RGBA8 || format === PIXELFORMAT_SRGBA8)) {\n\t\t\t\tmipFilter = FILTER_LINEAR;\n\t\t}\n\t\tconst texture = new Texture(device, {\n\t\t\t\twidth: width,\n\t\t\t\theight: height,\n\t\t\t\tformat: format,\n\t\t\t\tcubemap: false,\n\t\t\t\tmipmaps: false,\n\t\t\t\tminFilter: mipFilter,\n\t\t\t\tmagFilter: mipFilter,\n\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\tname: 'ParticleSystemTexture'\n\t\t});\n\t\tconst pixels = texture.lock();\n\t\tif (format === PIXELFORMAT_RGBA8 || format === PIXELFORMAT_SRGBA8) {\n\t\t\t\tconst temp = new Uint8Array(pixelData.length);\n\t\t\t\tfor(let i = 0; i < pixelData.length; i++){\n\t\t\t\t\t\ttemp[i] = pixelData[i] * mult8Bit * 255;\n\t\t\t\t}\n\t\t\t\tpixelData = temp;\n\t\t}\n\t\tpixels.set(pixelData);\n\t\ttexture.unlock();\n\t\treturn texture;\n}\nfunction saturate(x) {\n\t\treturn Math.max(Math.min(x, 1), 0);\n}\nconst default0Curve = new Curve([\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0\n]);\nconst default1Curve = new Curve([\n\t\t0,\n\t\t1,\n\t\t1,\n\t\t1\n]);\nconst default0Curve3 = new CurveSet([\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0\n], [\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0\n], [\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0\n]);\nconst default1Curve3 = new CurveSet([\n\t\t0,\n\t\t1,\n\t\t1,\n\t\t1\n], [\n\t\t0,\n\t\t1,\n\t\t1,\n\t\t1\n], [\n\t\t0,\n\t\t1,\n\t\t1,\n\t\t1\n]);\nlet particleTexHeight = 2;\nconst particleTexChannels = 4;\nconst extentsInnerRatioUniform = new Float32Array(3);\nconst spawnMatrix = new Mat4();\nconst tmpVec3 = new Vec3();\nconst bMin = new Vec3();\nconst bMax = new Vec3();\nlet setPropertyTarget;\nlet setPropertyOptions;\nfunction setProperty(pName, defaultVal) {\n\t\tif (setPropertyOptions[pName] !== undefined && setPropertyOptions[pName] !== null) {\n\t\t\t\tsetPropertyTarget[pName] = setPropertyOptions[pName];\n\t\t} else {\n\t\t\t\tsetPropertyTarget[pName] = defaultVal;\n\t\t}\n}\nfunction pack3NFloats(a, b, c) {\n\t\tconst packed = a * 255 << 16 | b * 255 << 8 | c * 255;\n\t\treturn packed / (1 << 24);\n}\nfunction packTextureXYZ_NXYZ(qXYZ, qXYZ2) {\n\t\tconst num = qXYZ.length / 3;\n\t\tconst colors = new Array(num * 4);\n\t\tfor(let i = 0; i < num; i++){\n\t\t\t\tcolors[i * 4] = qXYZ[i * 3];\n\t\t\t\tcolors[i * 4 + 1] = qXYZ[i * 3 + 1];\n\t\t\t\tcolors[i * 4 + 2] = qXYZ[i * 3 + 2];\n\t\t\t\tcolors[i * 4 + 3] = pack3NFloats(qXYZ2[i * 3], qXYZ2[i * 3 + 1], qXYZ2[i * 3 + 2]);\n\t\t}\n\t\treturn colors;\n}\nfunction packTextureRGBA(qRGB, qA) {\n\t\tconst colors = new Array(qA.length * 4);\n\t\tfor(let i = 0; i < qA.length; i++){\n\t\t\t\tcolors[i * 4] = qRGB[i * 3];\n\t\t\t\tcolors[i * 4 + 1] = qRGB[i * 3 + 1];\n\t\t\t\tcolors[i * 4 + 2] = qRGB[i * 3 + 2];\n\t\t\t\tcolors[i * 4 + 3] = qA[i];\n\t\t}\n\t\treturn colors;\n}\nfunction packTexture5Floats(qA, qB, qC, qD, qE) {\n\t\tconst colors = new Array(qA.length * 4);\n\t\tfor(let i = 0; i < qA.length; i++){\n\t\t\t\tcolors[i * 4] = qA[i];\n\t\t\t\tcolors[i * 4 + 1] = qB[i];\n\t\t\t\tcolors[i * 4 + 2] = 0;\n\t\t\t\tcolors[i * 4 + 3] = pack3NFloats(qC[i], qD[i], qE[i]);\n\t\t}\n\t\treturn colors;\n}\nfunction packTexture2Floats(qA, qB) {\n\t\tconst colors = new Array(qA.length * 4);\n\t\tfor(let i = 0; i < qA.length; i++){\n\t\t\t\tcolors[i * 4] = qA[i];\n\t\t\t\tcolors[i * 4 + 1] = qB[i];\n\t\t\t\tcolors[i * 4 + 2] = 0;\n\t\t\t\tcolors[i * 4 + 3] = 0;\n\t\t}\n\t\treturn colors;\n}\nfunction calcEndTime(emitter) {\n\t\tconst interval = Math.max(emitter.rate, emitter.rate2) * emitter.numParticles + emitter.lifetime;\n\t\treturn Date.now() + interval * 1000;\n}\nfunction subGraph(A, B) {\n\t\tconst r = new Float32Array(A.length);\n\t\tfor(let i = 0; i < A.length; i++){\n\t\t\t\tr[i] = A[i] - B[i];\n\t\t}\n\t\treturn r;\n}\nfunction maxUnsignedGraphValue(A, outUMax) {\n\t\tconst chans = outUMax.length;\n\t\tconst values = A.length / chans;\n\t\tfor(let i = 0; i < values; i++){\n\t\t\t\tfor(let j = 0; j < chans; j++){\n\t\t\t\t\t\tconst a = Math.abs(A[i * chans + j]);\n\t\t\t\t\t\toutUMax[j] = Math.max(outUMax[j], a);\n\t\t\t\t}\n\t\t}\n}\nfunction normalizeGraph(A, uMax) {\n\t\tconst chans = uMax.length;\n\t\tconst values = A.length / chans;\n\t\tfor(let i = 0; i < values; i++){\n\t\t\t\tfor(let j = 0; j < chans; j++){\n\t\t\t\t\t\tA[i * chans + j] /= uMax[j] === 0 ? 1 : uMax[j];\n\t\t\t\t\t\tA[i * chans + j] *= 0.5;\n\t\t\t\t\t\tA[i * chans + j] += 0.5;\n\t\t\t\t}\n\t\t}\n}\nfunction divGraphFrom2Curves(curve1, curve2, outUMax) {\n\t\tconst sub = subGraph(curve2, curve1);\n\t\tmaxUnsignedGraphValue(sub, outUMax);\n\t\tnormalizeGraph(sub, outUMax);\n\t\treturn sub;\n}\nconst particleEmitterDeviceCache = new DeviceCache();\nclass ParticleEmitter {\n\t\tconstructor(graphicsDevice, options){\n\t\t\t\tthis.material = null;\n\t\t\t\tthis.internalTex0 = null;\n\t\t\t\tthis.internalTex1 = null;\n\t\t\t\tthis.internalTex2 = null;\n\t\t\t\tthis.colorParam = null;\n\t\t\t\tthis.graphicsDevice = graphicsDevice;\n\t\t\t\tconst gd = graphicsDevice;\n\t\t\t\tconst precision = 32;\n\t\t\t\tthis.precision = precision;\n\t\t\t\tthis._addTimeTime = 0;\n\t\t\t\tsetPropertyTarget = this;\n\t\t\t\tsetPropertyOptions = options;\n\t\t\t\tsetProperty('numParticles', 1);\n\t\t\t\tif (this.numParticles > graphicsDevice.maxTextureSize) {\n\t\t\t\t\t\tthis.numParticles = graphicsDevice.maxTextureSize;\n\t\t\t\t}\n\t\t\t\tsetProperty('rate', 1);\n\t\t\t\tsetProperty('rate2', this.rate);\n\t\t\t\tsetProperty('lifetime', 50);\n\t\t\t\tsetProperty('emitterExtents', new Vec3(0, 0, 0));\n\t\t\t\tsetProperty('emitterExtentsInner', new Vec3(0, 0, 0));\n\t\t\t\tsetProperty('emitterRadius', 0);\n\t\t\t\tsetProperty('emitterRadiusInner', 0);\n\t\t\t\tsetProperty('emitterShape', EMITTERSHAPE_BOX);\n\t\t\t\tsetProperty('initialVelocity', 1);\n\t\t\t\tsetProperty('wrap', false);\n\t\t\t\tsetProperty('localSpace', false);\n\t\t\t\tsetProperty('screenSpace', false);\n\t\t\t\tsetProperty('wrapBounds', null);\n\t\t\t\tsetProperty('colorMap', this.defaultParamTexture);\n\t\t\t\tsetProperty('normalMap', null);\n\t\t\t\tsetProperty('loop', true);\n\t\t\t\tsetProperty('preWarm', false);\n\t\t\t\tsetProperty('sort', PARTICLESORT_NONE);\n\t\t\t\tsetProperty('mode', PARTICLEMODE_GPU);\n\t\t\t\tsetProperty('scene', null);\n\t\t\t\tsetProperty('lighting', false);\n\t\t\t\tsetProperty('halfLambert', false);\n\t\t\t\tsetProperty('intensity', 1.0);\n\t\t\t\tsetProperty('stretch', 0.0);\n\t\t\t\tsetProperty('alignToMotion', false);\n\t\t\t\tsetProperty('depthSoftening', 0);\n\t\t\t\tsetProperty('mesh', null);\n\t\t\t\tsetProperty('particleNormal', new Vec3(0, 1, 0));\n\t\t\t\tsetProperty('orientation', PARTICLEORIENTATION_SCREEN);\n\t\t\t\tsetProperty('depthWrite', false);\n\t\t\t\tsetProperty('noFog', false);\n\t\t\t\tsetProperty('blendType', BLEND_NORMAL);\n\t\t\t\tsetProperty('node', null);\n\t\t\t\tsetProperty('startAngle', 0);\n\t\t\t\tsetProperty('startAngle2', this.startAngle);\n\t\t\t\tsetProperty('animTilesX', 1);\n\t\t\t\tsetProperty('animTilesY', 1);\n\t\t\t\tsetProperty('animStartFrame', 0);\n\t\t\t\tsetProperty('animNumFrames', 1);\n\t\t\t\tsetProperty('animNumAnimations', 1);\n\t\t\t\tsetProperty('animIndex', 0);\n\t\t\t\tsetProperty('randomizeAnimIndex', false);\n\t\t\t\tsetProperty('animSpeed', 1);\n\t\t\t\tsetProperty('animLoop', true);\n\t\t\t\tthis._gpuUpdater = new ParticleGPUUpdater(this, gd);\n\t\t\t\tthis._cpuUpdater = new ParticleCPUUpdater(this);\n\t\t\t\tthis.emitterPosUniform = new Float32Array(3);\n\t\t\t\tthis.wrapBoundsUniform = new Float32Array(3);\n\t\t\t\tthis.emitterScaleUniform = new Float32Array([\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1\n\t\t\t\t]);\n\t\t\t\tsetProperty('colorGraph', default1Curve3);\n\t\t\t\tsetProperty('colorGraph2', this.colorGraph);\n\t\t\t\tsetProperty('scaleGraph', default1Curve);\n\t\t\t\tsetProperty('scaleGraph2', this.scaleGraph);\n\t\t\t\tsetProperty('alphaGraph', default1Curve);\n\t\t\t\tsetProperty('alphaGraph2', this.alphaGraph);\n\t\t\t\tsetProperty('localVelocityGraph', default0Curve3);\n\t\t\t\tsetProperty('localVelocityGraph2', this.localVelocityGraph);\n\t\t\t\tsetProperty('velocityGraph', default0Curve3);\n\t\t\t\tsetProperty('velocityGraph2', this.velocityGraph);\n\t\t\t\tsetProperty('rotationSpeedGraph', default0Curve);\n\t\t\t\tsetProperty('rotationSpeedGraph2', this.rotationSpeedGraph);\n\t\t\t\tsetProperty('radialSpeedGraph', default0Curve);\n\t\t\t\tsetProperty('radialSpeedGraph2', this.radialSpeedGraph);\n\t\t\t\tthis.animTilesParams = new Float32Array(2);\n\t\t\t\tthis.animParams = new Float32Array(4);\n\t\t\t\tthis.animIndexParams = new Float32Array(2);\n\t\t\t\tthis.vbToSort = null;\n\t\t\t\tthis.vbOld = null;\n\t\t\t\tthis.particleDistance = null;\n\t\t\t\tthis.camera = null;\n\t\t\t\tthis.swapTex = false;\n\t\t\t\tthis.useMesh = true;\n\t\t\t\tthis.useCpu = !graphicsDevice.supportsGpuParticles;\n\t\t\t\tthis.pack8 = true;\n\t\t\t\tthis.localBounds = new BoundingBox();\n\t\t\t\tthis.worldBoundsNoTrail = new BoundingBox();\n\t\t\t\tthis.worldBoundsTrail = [\n\t\t\t\t\t\tnew BoundingBox(),\n\t\t\t\t\t\tnew BoundingBox()\n\t\t\t\t];\n\t\t\t\tthis.worldBounds = new BoundingBox();\n\t\t\t\tthis.worldBoundsSize = new Vec3();\n\t\t\t\tthis.prevWorldBoundsSize = new Vec3();\n\t\t\t\tthis.prevWorldBoundsCenter = new Vec3();\n\t\t\t\tthis.prevEmitterExtents = this.emitterExtents;\n\t\t\t\tthis.prevEmitterRadius = this.emitterRadius;\n\t\t\t\tthis.worldBoundsMul = new Vec3();\n\t\t\t\tthis.worldBoundsAdd = new Vec3();\n\t\t\t\tthis.timeToSwitchBounds = 0;\n\t\t\t\tthis.shaderParticleUpdateRespawn = null;\n\t\t\t\tthis.shaderParticleUpdateNoRespawn = null;\n\t\t\t\tthis.shaderParticleUpdateOnStop = null;\n\t\t\t\tthis.numParticleVerts = 0;\n\t\t\t\tthis.numParticleIndices = 0;\n\t\t\t\tthis.material = null;\n\t\t\t\tthis.meshInstance = null;\n\t\t\t\tthis.drawOrder = 0;\n\t\t\t\tthis.seed = Math.random();\n\t\t\t\tthis.fixedTimeStep = 1.0 / 60;\n\t\t\t\tthis.maxSubSteps = 10;\n\t\t\t\tthis.simTime = 0;\n\t\t\t\tthis.simTimeTotal = 0;\n\t\t\t\tthis.beenReset = false;\n\t\t\t\tthis._layer = null;\n\t\t\t\tthis.rebuild();\n\t\t}\n\t\tget defaultParamTexture() {\n\t\t\t\treturn particleEmitterDeviceCache.get(this.graphicsDevice, ()=>{\n\t\t\t\t\t\tconst resolution = 16;\n\t\t\t\t\t\tconst centerPoint = resolution * 0.5 + 0.5;\n\t\t\t\t\t\tconst dtex = new Float32Array(resolution * resolution * 4);\n\t\t\t\t\t\tfor(let y = 0; y < resolution; y++){\n\t\t\t\t\t\t\t\tfor(let x = 0; x < resolution; x++){\n\t\t\t\t\t\t\t\t\t\tconst xgrad = x + 1 - centerPoint;\n\t\t\t\t\t\t\t\t\t\tconst ygrad = y + 1 - centerPoint;\n\t\t\t\t\t\t\t\t\t\tconst c = saturate(1 - saturate(Math.sqrt(xgrad * xgrad + ygrad * ygrad) / resolution) - 0.5);\n\t\t\t\t\t\t\t\t\t\tconst p = y * resolution + x;\n\t\t\t\t\t\t\t\t\t\tdtex[p * 4] = 1;\n\t\t\t\t\t\t\t\t\t\tdtex[p * 4 + 1] = 1;\n\t\t\t\t\t\t\t\t\t\tdtex[p * 4 + 2] = 1;\n\t\t\t\t\t\t\t\t\t\tdtex[p * 4 + 3] = c;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst texture = _createTexture(this.graphicsDevice, resolution, resolution, dtex, PIXELFORMAT_SRGBA8, 1.0, true);\n\t\t\t\t\t\ttexture.minFilter = FILTER_LINEAR;\n\t\t\t\t\t\ttexture.magFilter = FILTER_LINEAR;\n\t\t\t\t\t\treturn texture;\n\t\t\t\t});\n\t\t}\n\t\tonChangeCamera() {\n\t\t\t\tthis.resetMaterial();\n\t\t}\n\t\tcalculateBoundsMad() {\n\t\t\t\tthis.worldBoundsMul.x = 1.0 / this.worldBoundsSize.x;\n\t\t\t\tthis.worldBoundsMul.y = 1.0 / this.worldBoundsSize.y;\n\t\t\t\tthis.worldBoundsMul.z = 1.0 / this.worldBoundsSize.z;\n\t\t\t\tthis.worldBoundsAdd.copy(this.worldBounds.center).mul(this.worldBoundsMul).mulScalar(-1);\n\t\t\t\tthis.worldBoundsAdd.x += 0.5;\n\t\t\t\tthis.worldBoundsAdd.y += 0.5;\n\t\t\t\tthis.worldBoundsAdd.z += 0.5;\n\t\t}\n\t\tcalculateWorldBounds() {\n\t\t\t\tif (!this.node) return;\n\t\t\t\tthis.prevWorldBoundsSize.copy(this.worldBoundsSize);\n\t\t\t\tthis.prevWorldBoundsCenter.copy(this.worldBounds.center);\n\t\t\t\tif (!this.useCpu) {\n\t\t\t\t\t\tlet recalculateLocalBounds = false;\n\t\t\t\t\t\tif (this.emitterShape === EMITTERSHAPE_BOX) {\n\t\t\t\t\t\t\t\trecalculateLocalBounds = !this.emitterExtents.equals(this.prevEmitterExtents);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trecalculateLocalBounds = !(this.emitterRadius === this.prevEmitterRadius);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (recalculateLocalBounds) {\n\t\t\t\t\t\t\t\tthis.calculateLocalBounds();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst nodeWT = this.node.getWorldTransform();\n\t\t\t\tif (this.localSpace) {\n\t\t\t\t\t\tthis.worldBoundsNoTrail.copy(this.localBounds);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.worldBoundsNoTrail.setFromTransformedAabb(this.localBounds, nodeWT);\n\t\t\t\t}\n\t\t\t\tthis.worldBoundsTrail[0].add(this.worldBoundsNoTrail);\n\t\t\t\tthis.worldBoundsTrail[1].add(this.worldBoundsNoTrail);\n\t\t\t\tconst now = this.simTimeTotal;\n\t\t\t\tif (now >= this.timeToSwitchBounds) {\n\t\t\t\t\t\tthis.worldBoundsTrail[0].copy(this.worldBoundsTrail[1]);\n\t\t\t\t\t\tthis.worldBoundsTrail[1].copy(this.worldBoundsNoTrail);\n\t\t\t\t\t\tthis.timeToSwitchBounds = now + this.lifetime;\n\t\t\t\t}\n\t\t\t\tthis.worldBounds.copy(this.worldBoundsTrail[0]);\n\t\t\t\tthis.worldBoundsSize.copy(this.worldBounds.halfExtents).mulScalar(2);\n\t\t\t\tif (this.localSpace) {\n\t\t\t\t\t\tthis.meshInstance.aabb.setFromTransformedAabb(this.worldBounds, nodeWT);\n\t\t\t\t\t\tthis.meshInstance.mesh.aabb.setFromTransformedAabb(this.worldBounds, nodeWT);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.meshInstance.aabb.copy(this.worldBounds);\n\t\t\t\t\t\tthis.meshInstance.mesh.aabb.copy(this.worldBounds);\n\t\t\t\t}\n\t\t\t\tthis.meshInstance._aabbVer = 1 - this.meshInstance._aabbVer;\n\t\t\t\tif (this.pack8) this.calculateBoundsMad();\n\t\t}\n\t\tresetWorldBounds() {\n\t\t\t\tif (!this.node) return;\n\t\t\t\tthis.worldBoundsNoTrail.setFromTransformedAabb(this.localBounds, this.localSpace ? Mat4.IDENTITY : this.node.getWorldTransform());\n\t\t\t\tthis.worldBoundsTrail[0].copy(this.worldBoundsNoTrail);\n\t\t\t\tthis.worldBoundsTrail[1].copy(this.worldBoundsNoTrail);\n\t\t\t\tthis.worldBounds.copy(this.worldBoundsTrail[0]);\n\t\t\t\tthis.worldBoundsSize.copy(this.worldBounds.halfExtents).mulScalar(2);\n\t\t\t\tthis.prevWorldBoundsSize.copy(this.worldBoundsSize);\n\t\t\t\tthis.prevWorldBoundsCenter.copy(this.worldBounds.center);\n\t\t\t\tthis.simTimeTotal = 0;\n\t\t\t\tthis.timeToSwitchBounds = 0;\n\t\t}\n\t\tcalculateLocalBounds() {\n\t\t\t\tlet minx = Number.MAX_VALUE;\n\t\t\t\tlet miny = Number.MAX_VALUE;\n\t\t\t\tlet minz = Number.MAX_VALUE;\n\t\t\t\tlet maxx = -Number.MAX_VALUE;\n\t\t\t\tlet maxy = -Number.MAX_VALUE;\n\t\t\t\tlet maxz = -Number.MAX_VALUE;\n\t\t\t\tlet maxR = 0;\n\t\t\t\tlet maxScale = 0;\n\t\t\t\tconst stepWeight = this.lifetime / this.precision;\n\t\t\t\tconst wVels = [\n\t\t\t\t\t\tthis.qVelocity,\n\t\t\t\t\t\tthis.qVelocity2\n\t\t\t\t];\n\t\t\t\tconst lVels = [\n\t\t\t\t\t\tthis.qLocalVelocity,\n\t\t\t\t\t\tthis.qLocalVelocity2\n\t\t\t\t];\n\t\t\t\tconst accumX = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tconst accumY = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tconst accumZ = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tconst accumR = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tconst accumW = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tlet x, y, z;\n\t\t\t\tfor(let i = 0; i < this.precision + 1; i++){\n\t\t\t\t\t\tconst index = Math.min(i, this.precision - 1);\n\t\t\t\t\t\tfor(let j = 0; j < 2; j++){\n\t\t\t\t\t\t\t\tx = lVels[j][index * 3 + 0] * stepWeight + accumX[j];\n\t\t\t\t\t\t\t\ty = lVels[j][index * 3 + 1] * stepWeight + accumY[j];\n\t\t\t\t\t\t\t\tz = lVels[j][index * 3 + 2] * stepWeight + accumZ[j];\n\t\t\t\t\t\t\t\tminx = Math.min(x, minx);\n\t\t\t\t\t\t\t\tminy = Math.min(y, miny);\n\t\t\t\t\t\t\t\tminz = Math.min(z, minz);\n\t\t\t\t\t\t\t\tmaxx = Math.max(x, maxx);\n\t\t\t\t\t\t\t\tmaxy = Math.max(y, maxy);\n\t\t\t\t\t\t\t\tmaxz = Math.max(z, maxz);\n\t\t\t\t\t\t\t\taccumX[j] = x;\n\t\t\t\t\t\t\t\taccumY[j] = y;\n\t\t\t\t\t\t\t\taccumZ[j] = z;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let j = 0; j < 2; j++){\n\t\t\t\t\t\t\t\taccumW[j] += stepWeight * Math.sqrt(wVels[j][index * 3 + 0] * wVels[j][index * 3 + 0] + wVels[j][index * 3 + 1] * wVels[j][index * 3 + 1] + wVels[j][index * 3 + 2] * wVels[j][index * 3 + 2]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\taccumR[0] += this.qRadialSpeed[index] * stepWeight;\n\t\t\t\t\t\taccumR[1] += this.qRadialSpeed2[index] * stepWeight;\n\t\t\t\t\t\tmaxR = Math.max(maxR, Math.max(Math.abs(accumR[0]), Math.abs(accumR[1])));\n\t\t\t\t\t\tmaxScale = Math.max(maxScale, this.qScale[index]);\n\t\t\t\t}\n\t\t\t\tif (this.emitterShape === EMITTERSHAPE_BOX) {\n\t\t\t\t\t\tx = this.emitterExtents.x * 0.5;\n\t\t\t\t\t\ty = this.emitterExtents.y * 0.5;\n\t\t\t\t\t\tz = this.emitterExtents.z * 0.5;\n\t\t\t\t} else {\n\t\t\t\t\t\tx = this.emitterRadius;\n\t\t\t\t\t\ty = this.emitterRadius;\n\t\t\t\t\t\tz = this.emitterRadius;\n\t\t\t\t}\n\t\t\t\tconst w = Math.max(accumW[0], accumW[1]);\n\t\t\t\tbMin.x = minx - maxScale - x - maxR - w;\n\t\t\t\tbMin.y = miny - maxScale - y - maxR - w;\n\t\t\t\tbMin.z = minz - maxScale - z - maxR - w;\n\t\t\t\tbMax.x = maxx + maxScale + x + maxR + w;\n\t\t\t\tbMax.y = maxy + maxScale + y + maxR + w;\n\t\t\t\tbMax.z = maxz + maxScale + z + maxR + w;\n\t\t\t\tthis.localBounds.setMinMax(bMin, bMax);\n\t\t}\n\t\trebuild() {\n\t\t\t\tconst gd = this.graphicsDevice;\n\t\t\t\tif (this.colorMap === null) this.colorMap = this.defaultParamTexture;\n\t\t\t\tthis.spawnBounds = this.emitterShape === EMITTERSHAPE_BOX ? this.emitterExtents : this.emitterRadius;\n\t\t\t\tthis.useCpu = this.useCpu || this.sort > PARTICLESORT_NONE || gd.maxVertexTextures <= 1 || gd.fragmentUniformsCount < 64 || gd.forceCpuParticles;\n\t\t\t\tconst wasVisible = this._destroyResources();\n\t\t\t\tthis.pack8 = (this.pack8 || !gd.textureFloatRenderable) && !this.useCpu;\n\t\t\t\tparticleTexHeight = this.useCpu || this.pack8 ? 4 : 2;\n\t\t\t\tthis.useMesh = !!this.mesh;\n\t\t\t\tthis.numParticlesPot = math.nextPowerOfTwo(this.numParticles);\n\t\t\t\tthis.rebuildGraphs();\n\t\t\t\tthis.calculateLocalBounds();\n\t\t\t\tthis.resetWorldBounds();\n\t\t\t\tif (this.node) {\n\t\t\t\t\t\tthis.worldBounds.setFromTransformedAabb(this.localBounds, this.localSpace ? Mat4.IDENTITY : this.node.getWorldTransform());\n\t\t\t\t\t\tthis.worldBoundsTrail[0].copy(this.worldBounds);\n\t\t\t\t\t\tthis.worldBoundsTrail[1].copy(this.worldBounds);\n\t\t\t\t\t\tthis.worldBoundsSize.copy(this.worldBounds.halfExtents).mulScalar(2);\n\t\t\t\t\t\tthis.prevWorldBoundsSize.copy(this.worldBoundsSize);\n\t\t\t\t\t\tthis.prevWorldBoundsCenter.copy(this.worldBounds.center);\n\t\t\t\t\t\tif (this.pack8) this.calculateBoundsMad();\n\t\t\t\t}\n\t\t\t\tthis.vbToSort = new Array(this.numParticles);\n\t\t\t\tfor(let iSort = 0; iSort < this.numParticles; iSort++)this.vbToSort[iSort] = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.particleDistance = new Float32Array(this.numParticles);\n\t\t\t\tthis._gpuUpdater.randomize();\n\t\t\t\tthis.particleTex = new Float32Array(this.numParticlesPot * particleTexHeight * particleTexChannels);\n\t\t\t\tconst emitterPos = this.node === null || this.localSpace ? Vec3.ZERO : this.node.getPosition();\n\t\t\t\tif (this.emitterShape === EMITTERSHAPE_BOX) {\n\t\t\t\t\t\tif (this.node === null || this.localSpace) {\n\t\t\t\t\t\t\t\tspawnMatrix.setTRS(Vec3.ZERO, Quat.IDENTITY, this.spawnBounds);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tspawnMatrix.setTRS(Vec3.ZERO, this.node.getRotation(), tmpVec3.copy(this.spawnBounds).mul(this.node.localScale));\n\t\t\t\t\t\t}\n\t\t\t\t\t\textentsInnerRatioUniform[0] = this.emitterExtents.x !== 0 ? this.emitterExtentsInner.x / this.emitterExtents.x : 0;\n\t\t\t\t\t\textentsInnerRatioUniform[1] = this.emitterExtents.y !== 0 ? this.emitterExtentsInner.y / this.emitterExtents.y : 0;\n\t\t\t\t\t\textentsInnerRatioUniform[2] = this.emitterExtents.z !== 0 ? this.emitterExtentsInner.z / this.emitterExtents.z : 0;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this.numParticles; i++){\n\t\t\t\t\t\tthis._cpuUpdater.calcSpawnPosition(this.particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, i);\n\t\t\t\t\t\tif (this.useCpu) this.particleTex[i * particleTexChannels + 3 + this.numParticlesPot * 2 * particleTexChannels] = 1;\n\t\t\t\t}\n\t\t\t\tthis.particleTexStart = new Float32Array(this.numParticlesPot * particleTexHeight * particleTexChannels);\n\t\t\t\tfor(let i = 0; i < this.particleTexStart.length; i++){\n\t\t\t\t\t\tthis.particleTexStart[i] = this.particleTex[i];\n\t\t\t\t}\n\t\t\t\tif (!this.useCpu) {\n\t\t\t\t\t\tif (this.pack8) {\n\t\t\t\t\t\t\t\tthis.particleTexIN = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex, PIXELFORMAT_RGBA8, 1, false);\n\t\t\t\t\t\t\t\tthis.particleTexOUT = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex, PIXELFORMAT_RGBA8, 1, false);\n\t\t\t\t\t\t\t\tthis.particleTexStart = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTexStart, PIXELFORMAT_RGBA8, 1, false);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.particleTexIN = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex);\n\t\t\t\t\t\t\t\tthis.particleTexOUT = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTex);\n\t\t\t\t\t\t\t\tthis.particleTexStart = _createTexture(gd, this.numParticlesPot, particleTexHeight, this.particleTexStart);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.rtParticleTexIN = new RenderTarget({\n\t\t\t\t\t\t\t\tcolorBuffer: this.particleTexIN,\n\t\t\t\t\t\t\t\tdepth: false\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.rtParticleTexOUT = new RenderTarget({\n\t\t\t\t\t\t\t\tcolorBuffer: this.particleTexOUT,\n\t\t\t\t\t\t\t\tdepth: false\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.swapTex = false;\n\t\t\t\t}\n\t\t\t\tconst defines = new Map();\n\t\t\t\tif (this.localSpace) defines.set('LOCAL_SPACE', '');\n\t\t\t\tif (this.pack8) defines.set('PACK8', '');\n\t\t\t\tif (this.emitterShape === EMITTERSHAPE_BOX) defines.set('EMITTERSHAPE_BOX', '');\n\t\t\t\tconst shaderUniqueId = `Shape:${this.emitterShape}-Pack:${this.pack8}-Local:${this.localSpace}`;\n\t\t\t\tconst engineChunks = ShaderChunks.get(gd, gd.isWebGPU ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL);\n\t\t\t\tconst includes = new Map(engineChunks);\n\t\t\t\tconst shaderOptions = {\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvertexChunk: 'fullscreenQuadVS',\n\t\t\t\t\t\tfragmentChunk: 'particle_simulationPS',\n\t\t\t\t\t\tfragmentDefines: defines,\n\t\t\t\t\t\tfragmentIncludes: includes\n\t\t\t\t};\n\t\t\t\tshaderOptions.uniqueName = `ParticleUpdateRespawn-${shaderUniqueId}`;\n\t\t\t\tdefines.set('RESPAWN', '');\n\t\t\t\tthis.shaderParticleUpdateRespawn = ShaderUtils.createShader(gd, shaderOptions);\n\t\t\t\tdefines.delete('RESPAWN');\n\t\t\t\tshaderOptions.uniqueName = `ParticleUpdateNoRespawn-${shaderUniqueId}`;\n\t\t\t\tdefines.set('NO_RESPAWN', '');\n\t\t\t\tthis.shaderParticleUpdateNoRespawn = ShaderUtils.createShader(gd, shaderOptions);\n\t\t\t\tdefines.delete('NO_RESPAWN');\n\t\t\t\tshaderOptions.uniqueName = `ParticleUpdateStop-${shaderUniqueId}`;\n\t\t\t\tdefines.set('ON_STOP', '');\n\t\t\t\tthis.shaderParticleUpdateOnStop = ShaderUtils.createShader(gd, shaderOptions);\n\t\t\t\tthis.numParticleVerts = this.useMesh ? this.mesh.vertexBuffer.numVertices : 4;\n\t\t\t\tthis.numParticleIndices = this.useMesh ? this.mesh.indexBuffer[0].numIndices : 6;\n\t\t\t\tthis._allocate(this.numParticles);\n\t\t\t\tconst mesh = new Mesh(gd);\n\t\t\t\tmesh.vertexBuffer = this.vertexBuffer;\n\t\t\t\tmesh.indexBuffer[0] = this.indexBuffer;\n\t\t\t\tmesh.primitive[0].type = PRIMITIVE_TRIANGLES;\n\t\t\t\tmesh.primitive[0].base = 0;\n\t\t\t\tmesh.primitive[0].count = this.numParticles * this.numParticleIndices;\n\t\t\t\tmesh.primitive[0].indexed = true;\n\t\t\t\tthis.material = this._createMaterial();\n\t\t\t\tthis.resetMaterial();\n\t\t\t\tthis.meshInstance = new MeshInstance(mesh, this.material, this.node);\n\t\t\t\tthis.meshInstance.pick = false;\n\t\t\t\tthis.meshInstance.updateKey();\n\t\t\t\tthis.meshInstance.cull = true;\n\t\t\t\tif (this.localSpace) {\n\t\t\t\t\t\tthis.meshInstance.aabb.setFromTransformedAabb(this.worldBounds, this.node.getWorldTransform());\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.meshInstance.aabb.copy(this.worldBounds);\n\t\t\t\t}\n\t\t\t\tthis.meshInstance._updateAabb = false;\n\t\t\t\tthis.meshInstance.visible = wasVisible;\n\t\t\t\tthis._setMaterialTextures();\n\t\t\t\tthis.resetTime();\n\t\t\t\tthis.addTime(0, false);\n\t\t\t\tif (this.preWarm) this.prewarm(this.lifetime);\n\t\t}\n\t\t_isAnimated() {\n\t\t\t\treturn this.animNumFrames >= 1 && (this.animTilesX > 1 || this.animTilesY > 1) && (this.colorMap && this.colorMap !== this.defaultParamTexture || this.normalMap);\n\t\t}\n\t\trebuildGraphs() {\n\t\t\t\tconst precision = this.precision;\n\t\t\t\tconst gd = this.graphicsDevice;\n\t\t\t\tthis.qLocalVelocity = this.localVelocityGraph.quantize(precision);\n\t\t\t\tthis.qVelocity = this.velocityGraph.quantize(precision);\n\t\t\t\tthis.qColor = this.colorGraph.quantizeClamped(precision, 0, 1);\n\t\t\t\tthis.qRotSpeed = this.rotationSpeedGraph.quantize(precision);\n\t\t\t\tthis.qScale = this.scaleGraph.quantize(precision);\n\t\t\t\tthis.qAlpha = this.alphaGraph.quantize(precision);\n\t\t\t\tthis.qRadialSpeed = this.radialSpeedGraph.quantize(precision);\n\t\t\t\tthis.qLocalVelocity2 = this.localVelocityGraph2.quantize(precision);\n\t\t\t\tthis.qVelocity2 = this.velocityGraph2.quantize(precision);\n\t\t\t\tthis.qColor2 = this.colorGraph2.quantizeClamped(precision, 0, 1);\n\t\t\t\tthis.qRotSpeed2 = this.rotationSpeedGraph2.quantize(precision);\n\t\t\t\tthis.qScale2 = this.scaleGraph2.quantize(precision);\n\t\t\t\tthis.qAlpha2 = this.alphaGraph2.quantize(precision);\n\t\t\t\tthis.qRadialSpeed2 = this.radialSpeedGraph2.quantize(precision);\n\t\t\t\tfor(let i = 0; i < precision; i++){\n\t\t\t\t\t\tthis.qRotSpeed[i] *= math.DEG_TO_RAD;\n\t\t\t\t\t\tthis.qRotSpeed2[i] *= math.DEG_TO_RAD;\n\t\t\t\t}\n\t\t\t\tthis.localVelocityUMax = new Float32Array(3);\n\t\t\t\tthis.velocityUMax = new Float32Array(3);\n\t\t\t\tthis.colorUMax = new Float32Array(3);\n\t\t\t\tthis.rotSpeedUMax = [\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.scaleUMax = [\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.alphaUMax = [\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.radialSpeedUMax = [\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.qLocalVelocityDiv = divGraphFrom2Curves(this.qLocalVelocity, this.qLocalVelocity2, this.localVelocityUMax);\n\t\t\t\tthis.qVelocityDiv = divGraphFrom2Curves(this.qVelocity, this.qVelocity2, this.velocityUMax);\n\t\t\t\tthis.qColorDiv = divGraphFrom2Curves(this.qColor, this.qColor2, this.colorUMax);\n\t\t\t\tthis.qRotSpeedDiv = divGraphFrom2Curves(this.qRotSpeed, this.qRotSpeed2, this.rotSpeedUMax);\n\t\t\t\tthis.qScaleDiv = divGraphFrom2Curves(this.qScale, this.qScale2, this.scaleUMax);\n\t\t\t\tthis.qAlphaDiv = divGraphFrom2Curves(this.qAlpha, this.qAlpha2, this.alphaUMax);\n\t\t\t\tthis.qRadialSpeedDiv = divGraphFrom2Curves(this.qRadialSpeed, this.qRadialSpeed2, this.radialSpeedUMax);\n\t\t\t\tif (this.pack8) {\n\t\t\t\t\t\tconst umax = [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t];\n\t\t\t\t\t\tmaxUnsignedGraphValue(this.qVelocity, umax);\n\t\t\t\t\t\tconst umax2 = [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t];\n\t\t\t\t\t\tmaxUnsignedGraphValue(this.qVelocity2, umax2);\n\t\t\t\t\t\tconst lumax = [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t];\n\t\t\t\t\t\tmaxUnsignedGraphValue(this.qLocalVelocity, lumax);\n\t\t\t\t\t\tconst lumax2 = [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t];\n\t\t\t\t\t\tmaxUnsignedGraphValue(this.qLocalVelocity2, lumax2);\n\t\t\t\t\t\tconst rumax = [\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t];\n\t\t\t\t\t\tmaxUnsignedGraphValue(this.qRadialSpeed, rumax);\n\t\t\t\t\t\tconst rumax2 = [\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t];\n\t\t\t\t\t\tmaxUnsignedGraphValue(this.qRadialSpeed2, rumax2);\n\t\t\t\t\t\tlet maxVel = Math.max(umax[0], umax2[0]);\n\t\t\t\t\t\tmaxVel = Math.max(maxVel, umax[1]);\n\t\t\t\t\t\tmaxVel = Math.max(maxVel, umax2[1]);\n\t\t\t\t\t\tmaxVel = Math.max(maxVel, umax[2]);\n\t\t\t\t\t\tmaxVel = Math.max(maxVel, umax2[2]);\n\t\t\t\t\t\tlet lmaxVel = Math.max(lumax[0], lumax2[0]);\n\t\t\t\t\t\tlmaxVel = Math.max(lmaxVel, lumax[1]);\n\t\t\t\t\t\tlmaxVel = Math.max(lmaxVel, lumax2[1]);\n\t\t\t\t\t\tlmaxVel = Math.max(lmaxVel, lumax[2]);\n\t\t\t\t\t\tlmaxVel = Math.max(lmaxVel, lumax2[2]);\n\t\t\t\t\t\tconst maxRad = Math.max(rumax[0], rumax2[0]);\n\t\t\t\t\t\tthis.maxVel = maxVel + lmaxVel + maxRad;\n\t\t\t\t}\n\t\t\t\tif (!this.useCpu) {\n\t\t\t\t\t\tthis.internalTex0 = _createTexture(gd, precision, 1, packTextureXYZ_NXYZ(this.qLocalVelocity, this.qLocalVelocityDiv));\n\t\t\t\t\t\tthis.internalTex1 = _createTexture(gd, precision, 1, packTextureXYZ_NXYZ(this.qVelocity, this.qVelocityDiv));\n\t\t\t\t\t\tthis.internalTex2 = _createTexture(gd, precision, 1, packTexture5Floats(this.qRotSpeed, this.qScale, this.qScaleDiv, this.qRotSpeedDiv, this.qAlphaDiv));\n\t\t\t\t\t\tthis.internalTex3 = _createTexture(gd, precision, 1, packTexture2Floats(this.qRadialSpeed, this.qRadialSpeedDiv));\n\t\t\t\t}\n\t\t\t\tthis.colorParam = _createTexture(gd, precision, 1, packTextureRGBA(this.qColor, this.qAlpha), PIXELFORMAT_SRGBA8, 1.0, true);\n\t\t}\n\t\t_setMaterialTextures() {\n\t\t\t\tif (this.colorMap) {\n\t\t\t\t\t\tthis.material.setParameter('colorMap', this.colorMap);\n\t\t\t\t\t\tif (this.lighting && this.normalMap) {\n\t\t\t\t\t\t\t\tthis.material.setParameter('normalMap', this.normalMap);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_createMaterial() {\n\t\t\t\tconst material = new ParticleMaterial(this);\n\t\t\t\tmaterial.name = `EmitterMaterial:${this.node.name}`;\n\t\t\t\tmaterial.cull = CULLFACE_NONE;\n\t\t\t\tmaterial.alphaWrite = false;\n\t\t\t\tmaterial.blendType = this.blendType;\n\t\t\t\tmaterial.depthWrite = this.depthWrite;\n\t\t\t\treturn material;\n\t\t}\n\t\tresetMaterial() {\n\t\t\t\tconst material = this.material;\n\t\t\t\tmaterial.setParameter('stretch', this.stretch);\n\t\t\t\tif (this._isAnimated()) {\n\t\t\t\t\t\tmaterial.setParameter('animTexTilesParams', this.animTilesParams);\n\t\t\t\t\t\tmaterial.setParameter('animTexParams', this.animParams);\n\t\t\t\t\t\tmaterial.setParameter('animTexIndexParams', this.animIndexParams);\n\t\t\t\t}\n\t\t\t\tmaterial.setParameter('colorMult', this.intensity);\n\t\t\t\tif (!this.useCpu) {\n\t\t\t\t\t\tmaterial.setParameter('internalTex0', this.internalTex0);\n\t\t\t\t\t\tmaterial.setParameter('internalTex1', this.internalTex1);\n\t\t\t\t\t\tmaterial.setParameter('internalTex2', this.internalTex2);\n\t\t\t\t\t\tmaterial.setParameter('internalTex3', this.internalTex3);\n\t\t\t\t}\n\t\t\t\tmaterial.setParameter('colorParam', this.colorParam);\n\t\t\t\tmaterial.setParameter('numParticles', this.numParticles);\n\t\t\t\tmaterial.setParameter('numParticlesPot', this.numParticlesPot);\n\t\t\t\tmaterial.setParameter('lifetime', this.lifetime);\n\t\t\t\tmaterial.setParameter('rate', this.rate);\n\t\t\t\tmaterial.setParameter('rateDiv', this.rate2 - this.rate);\n\t\t\t\tmaterial.setParameter('seed', this.seed);\n\t\t\t\tmaterial.setParameter('scaleDivMult', this.scaleUMax[0]);\n\t\t\t\tmaterial.setParameter('alphaDivMult', this.alphaUMax[0]);\n\t\t\t\tmaterial.setParameter('radialSpeedDivMult', this.radialSpeedUMax[0]);\n\t\t\t\tmaterial.setParameter('graphNumSamples', this.precision);\n\t\t\t\tmaterial.setParameter('graphSampleSize', 1.0 / this.precision);\n\t\t\t\tmaterial.setParameter('emitterScale', new Float32Array([\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1\n\t\t\t\t]));\n\t\t\t\tif (this.pack8) {\n\t\t\t\t\t\tthis._gpuUpdater._setInputBounds();\n\t\t\t\t\t\tmaterial.setParameter('inBoundsSize', this._gpuUpdater.inBoundsSizeUniform);\n\t\t\t\t\t\tmaterial.setParameter('inBoundsCenter', this._gpuUpdater.inBoundsCenterUniform);\n\t\t\t\t\t\tmaterial.setParameter('maxVel', this.maxVel);\n\t\t\t\t}\n\t\t\t\tif (this.wrap && this.wrapBounds) {\n\t\t\t\t\t\tthis.wrapBoundsUniform[0] = this.wrapBounds.x;\n\t\t\t\t\t\tthis.wrapBoundsUniform[1] = this.wrapBounds.y;\n\t\t\t\t\t\tthis.wrapBoundsUniform[2] = this.wrapBounds.z;\n\t\t\t\t\t\tmaterial.setParameter('wrapBounds', this.wrapBoundsUniform);\n\t\t\t\t}\n\t\t\t\tthis._setMaterialTextures();\n\t\t\t\tif (this.depthSoftening > 0) {\n\t\t\t\t\t\tmaterial.setParameter('softening', 1.0 / (this.depthSoftening * this.depthSoftening * 100));\n\t\t\t\t}\n\t\t\t\tif (this.stretch > 0.0) material.cull = CULLFACE_NONE;\n\t\t\t\tthis._compParticleFaceParams();\n\t\t}\n\t\t_compParticleFaceParams() {\n\t\t\t\tlet tangent, binormal;\n\t\t\t\tif (this.orientation === PARTICLEORIENTATION_SCREEN) {\n\t\t\t\t\t\ttangent = new Float32Array([\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tbinormal = new Float32Array([\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t]);\n\t\t\t\t} else {\n\t\t\t\t\t\tlet n;\n\t\t\t\t\t\tif (this.orientation === PARTICLEORIENTATION_WORLD) {\n\t\t\t\t\t\t\t\tn = this.particleNormal.normalize();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst emitterMat = this.node === null ? Mat4.IDENTITY : this.node.getWorldTransform();\n\t\t\t\t\t\t\t\tn = emitterMat.transformVector(this.particleNormal).normalize();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst t = new Vec3(1, 0, 0);\n\t\t\t\t\t\tif (Math.abs(t.dot(n)) === 1) {\n\t\t\t\t\t\t\t\tt.set(0, 0, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst b = new Vec3().cross(n, t).normalize();\n\t\t\t\t\t\tt.cross(b, n).normalize();\n\t\t\t\t\t\ttangent = new Float32Array([\n\t\t\t\t\t\t\t\tt.x,\n\t\t\t\t\t\t\t\tt.y,\n\t\t\t\t\t\t\t\tt.z\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tbinormal = new Float32Array([\n\t\t\t\t\t\t\t\tb.x,\n\t\t\t\t\t\t\t\tb.y,\n\t\t\t\t\t\t\t\tb.z\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\tthis.material.setParameter('faceTangent', tangent);\n\t\t\t\tthis.material.setParameter('faceBinorm', binormal);\n\t\t}\n\t\tgetVertexInfo() {\n\t\t\t\tconst elements = [];\n\t\t\t\tif (!this.useCpu) {\n\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR0,\n\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (this.useMesh) {\n\t\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR1,\n\t\t\t\t\t\t\t\t\t\tcomponents: 2,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR0,\n\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR1,\n\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR2,\n\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR3,\n\t\t\t\t\t\t\t\tcomponents: 1,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR4,\n\t\t\t\t\t\t\t\tcomponents: this.useMesh ? 4 : 2,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn elements;\n\t\t}\n\t\t_allocate(numParticles) {\n\t\t\t\tconst psysVertCount = numParticles * this.numParticleVerts;\n\t\t\t\tconst psysIndexCount = numParticles * this.numParticleIndices;\n\t\t\t\tif (this.vertexBuffer === undefined || this.vertexBuffer.getNumVertices() !== psysVertCount) {\n\t\t\t\t\t\tconst elements = this.getVertexInfo();\n\t\t\t\t\t\tconst vertexFormat = new VertexFormat(this.graphicsDevice, elements);\n\t\t\t\t\t\tthis.vertexBuffer = new VertexBuffer(this.graphicsDevice, vertexFormat, psysVertCount, {\n\t\t\t\t\t\t\t\tusage: BUFFER_DYNAMIC\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.indexBuffer = new IndexBuffer(this.graphicsDevice, INDEXFORMAT_UINT32, psysIndexCount);\n\t\t\t\t\t\tconst data = new Float32Array(this.vertexBuffer.lock());\n\t\t\t\t\t\tlet meshData, stride, texCoordOffset;\n\t\t\t\t\t\tif (this.useMesh) {\n\t\t\t\t\t\t\t\tmeshData = new Float32Array(this.mesh.vertexBuffer.lock());\n\t\t\t\t\t\t\t\tstride = meshData.length / this.mesh.vertexBuffer.numVertices;\n\t\t\t\t\t\t\t\tfor(let elem = 0; elem < this.mesh.vertexBuffer.format.elements.length; elem++){\n\t\t\t\t\t\t\t\t\t\tif (this.mesh.vertexBuffer.format.elements[elem].name === SEMANTIC_TEXCOORD0) {\n\t\t\t\t\t\t\t\t\t\t\t\ttexCoordOffset = this.mesh.vertexBuffer.format.elements[elem].offset / 4;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < psysVertCount; i++){\n\t\t\t\t\t\t\t\tconst id = Math.floor(i / this.numParticleVerts);\n\t\t\t\t\t\t\t\tif (!this.useMesh) {\n\t\t\t\t\t\t\t\t\t\tconst vertID = i % 4;\n\t\t\t\t\t\t\t\t\t\tdata[i * 4] = particleVerts[vertID][0];\n\t\t\t\t\t\t\t\t\t\tdata[i * 4 + 1] = particleVerts[vertID][1];\n\t\t\t\t\t\t\t\t\t\tdata[i * 4 + 2] = 0;\n\t\t\t\t\t\t\t\t\t\tdata[i * 4 + 3] = id;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst vert = i % this.numParticleVerts;\n\t\t\t\t\t\t\t\t\t\tdata[i * 6] = meshData[vert * stride];\n\t\t\t\t\t\t\t\t\t\tdata[i * 6 + 1] = meshData[vert * stride + 1];\n\t\t\t\t\t\t\t\t\t\tdata[i * 6 + 2] = meshData[vert * stride + 2];\n\t\t\t\t\t\t\t\t\t\tdata[i * 6 + 3] = id;\n\t\t\t\t\t\t\t\t\t\tdata[i * 6 + 4] = meshData[vert * stride + texCoordOffset + 0];\n\t\t\t\t\t\t\t\t\t\tdata[i * 6 + 5] = 1.0 - meshData[vert * stride + texCoordOffset + 1];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.useCpu) {\n\t\t\t\t\t\t\t\tthis.vbCPU = new Float32Array(data);\n\t\t\t\t\t\t\t\tthis.vbOld = new Float32Array(this.vbCPU.length);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.vertexBuffer.unlock();\n\t\t\t\t\t\tif (this.useMesh) {\n\t\t\t\t\t\t\t\tthis.mesh.vertexBuffer.unlock();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet dst = 0;\n\t\t\t\t\t\tconst indices = new Uint32Array(this.indexBuffer.lock());\n\t\t\t\t\t\tif (this.useMesh) {\n\t\t\t\t\t\t\t\tconst ib = this.mesh.indexBuffer[0];\n\t\t\t\t\t\t\t\tmeshData = new typedArrayIndexFormats[ib.format](ib.lock());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < numParticles; i++){\n\t\t\t\t\t\t\t\tif (!this.useMesh) {\n\t\t\t\t\t\t\t\t\t\tconst baseIndex = i * 4;\n\t\t\t\t\t\t\t\t\t\tindices[dst++] = baseIndex;\n\t\t\t\t\t\t\t\t\t\tindices[dst++] = baseIndex + 1;\n\t\t\t\t\t\t\t\t\t\tindices[dst++] = baseIndex + 2;\n\t\t\t\t\t\t\t\t\t\tindices[dst++] = baseIndex;\n\t\t\t\t\t\t\t\t\t\tindices[dst++] = baseIndex + 2;\n\t\t\t\t\t\t\t\t\t\tindices[dst++] = baseIndex + 3;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < this.numParticleIndices; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tindices[i * this.numParticleIndices + j] = meshData[j] + i * this.numParticleVerts;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.indexBuffer.unlock();\n\t\t\t\t\t\tif (this.useMesh) this.mesh.indexBuffer[0].unlock();\n\t\t\t\t}\n\t\t}\n\t\treset() {\n\t\t\t\tthis.beenReset = true;\n\t\t\t\tthis.seed = Math.random();\n\t\t\t\tthis.material.setParameter('seed', this.seed);\n\t\t\t\tif (this.useCpu) {\n\t\t\t\t\t\tfor(let i = 0; i < this.particleTexStart.length; i++){\n\t\t\t\t\t\t\t\tthis.particleTex[i] = this.particleTexStart[i];\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._setMaterialTextures();\n\t\t\t\t}\n\t\t\t\tthis.resetWorldBounds();\n\t\t\t\tthis.resetTime();\n\t\t\t\tconst origLoop = this.loop;\n\t\t\t\tthis.loop = true;\n\t\t\t\tthis.addTime(0, false);\n\t\t\t\tthis.loop = origLoop;\n\t\t\t\tif (this.preWarm) {\n\t\t\t\t\t\tthis.prewarm(this.lifetime);\n\t\t\t\t}\n\t\t}\n\t\tprewarm(time) {\n\t\t\t\tconst lifetimeFraction = time / this.lifetime;\n\t\t\t\tconst iterations = Math.min(Math.floor(lifetimeFraction * this.precision), this.precision);\n\t\t\t\tconst stepDelta = time / iterations;\n\t\t\t\tfor(let i = 0; i < iterations; i++){\n\t\t\t\t\t\tthis.addTime(stepDelta, false);\n\t\t\t\t}\n\t\t}\n\t\tresetTime() {\n\t\t\t\tthis.endTime = calcEndTime(this);\n\t\t}\n\t\tfinishFrame() {\n\t\t\t\tif (this.useCpu) this.vertexBuffer.unlock();\n\t\t}\n\t\taddTime(delta, isOnStop) {\n\t\t\t\tconst device = this.graphicsDevice;\n\t\t\t\tthis.simTimeTotal += delta;\n\t\t\t\tthis.calculateWorldBounds();\n\t\t\t\tif (this._isAnimated()) {\n\t\t\t\t\t\tconst tilesParams = this.animTilesParams;\n\t\t\t\t\t\ttilesParams[0] = 1.0 / this.animTilesX;\n\t\t\t\t\t\ttilesParams[1] = 1.0 / this.animTilesY;\n\t\t\t\t\t\tconst params = this.animParams;\n\t\t\t\t\t\tparams[0] = this.animStartFrame;\n\t\t\t\t\t\tparams[1] = this.animNumFrames * this.animSpeed;\n\t\t\t\t\t\tparams[2] = this.animNumFrames - 1;\n\t\t\t\t\t\tparams[3] = this.animNumAnimations - 1;\n\t\t\t\t\t\tconst animIndexParams = this.animIndexParams;\n\t\t\t\t\t\tanimIndexParams[0] = this.animIndex;\n\t\t\t\t\t\tanimIndexParams[1] = this.randomizeAnimIndex;\n\t\t\t\t}\n\t\t\t\tif (this.scene) {\n\t\t\t\t\t\tif (this.camera !== this.scene._activeCamera) {\n\t\t\t\t\t\t\t\tthis.camera = this.scene._activeCamera;\n\t\t\t\t\t\t\t\tthis.onChangeCamera();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.emitterShape === EMITTERSHAPE_BOX) {\n\t\t\t\t\t\textentsInnerRatioUniform[0] = this.emitterExtents.x !== 0 ? this.emitterExtentsInner.x / this.emitterExtents.x : 0;\n\t\t\t\t\t\textentsInnerRatioUniform[1] = this.emitterExtents.y !== 0 ? this.emitterExtentsInner.y / this.emitterExtents.y : 0;\n\t\t\t\t\t\textentsInnerRatioUniform[2] = this.emitterExtents.z !== 0 ? this.emitterExtentsInner.z / this.emitterExtents.z : 0;\n\t\t\t\t\t\tif (this.meshInstance.node === null) {\n\t\t\t\t\t\t\t\tspawnMatrix.setTRS(Vec3.ZERO, Quat.IDENTITY, this.emitterExtents);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tspawnMatrix.setTRS(Vec3.ZERO, this.meshInstance.node.getRotation(), tmpVec3.copy(this.emitterExtents).mul(this.meshInstance.node.localScale));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet emitterPos;\n\t\t\t\tconst emitterScale = this.meshInstance.node === null ? Vec3.ONE : this.meshInstance.node.localScale;\n\t\t\t\tthis.emitterScaleUniform[0] = emitterScale.x;\n\t\t\t\tthis.emitterScaleUniform[1] = emitterScale.y;\n\t\t\t\tthis.emitterScaleUniform[2] = emitterScale.z;\n\t\t\t\tthis.material.setParameter('emitterScale', this.emitterScaleUniform);\n\t\t\t\tif (this.localSpace && this.meshInstance.node) {\n\t\t\t\t\t\temitterPos = this.meshInstance.node.getPosition();\n\t\t\t\t\t\tthis.emitterPosUniform[0] = emitterPos.x;\n\t\t\t\t\t\tthis.emitterPosUniform[1] = emitterPos.y;\n\t\t\t\t\t\tthis.emitterPosUniform[2] = emitterPos.z;\n\t\t\t\t\t\tthis.material.setParameter('emitterPos', this.emitterPosUniform);\n\t\t\t\t}\n\t\t\t\tthis._compParticleFaceParams();\n\t\t\t\tif (!this.useCpu) {\n\t\t\t\t\t\tthis._gpuUpdater.update(device, spawnMatrix, extentsInnerRatioUniform, delta, isOnStop);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst data = new Float32Array(this.vertexBuffer.lock());\n\t\t\t\t\t\tthis._cpuUpdater.update(data, this.vbToSort, this.particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, delta, isOnStop);\n\t\t\t\t}\n\t\t\t\tif (!this.loop) {\n\t\t\t\t\t\tif (Date.now() > this.endTime) {\n\t\t\t\t\t\t\t\tif (this.onFinished) this.onFinished();\n\t\t\t\t\t\t\t\tthis.meshInstance.visible = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.meshInstance) {\n\t\t\t\t\t\tthis.meshInstance.drawOrder = this.drawOrder;\n\t\t\t\t}\n\t\t}\n\t\t_destroyResources() {\n\t\t\t\tthis.particleTexIN?.destroy();\n\t\t\t\tthis.particleTexIN = null;\n\t\t\t\tthis.particleTexOUT?.destroy();\n\t\t\t\tthis.particleTexOUT = null;\n\t\t\t\tif (this.particleTexStart && this.particleTexStart.destroy) {\n\t\t\t\t\t\tthis.particleTexStart.destroy();\n\t\t\t\t\t\tthis.particleTexStart = null;\n\t\t\t\t}\n\t\t\t\tthis.rtParticleTexIN?.destroy();\n\t\t\t\tthis.rtParticleTexIN = null;\n\t\t\t\tthis.rtParticleTexOUT?.destroy();\n\t\t\t\tthis.rtParticleTexOUT = null;\n\t\t\t\tthis.internalTex0?.destroy();\n\t\t\t\tthis.internalTex0 = null;\n\t\t\t\tthis.internalTex1?.destroy();\n\t\t\t\tthis.internalTex1 = null;\n\t\t\t\tthis.internalTex2?.destroy();\n\t\t\t\tthis.internalTex2 = null;\n\t\t\t\tthis.internalTex3?.destroy();\n\t\t\t\tthis.internalTex3 = null;\n\t\t\t\tthis.colorParam?.destroy();\n\t\t\t\tthis.colorParam = null;\n\t\t\t\tthis.vertexBuffer = undefined;\n\t\t\t\tthis.indexBuffer = undefined;\n\t\t\t\tconst wasVisible = this.meshInstance?.visible ?? true;\n\t\t\t\tthis.meshInstance?.destroy();\n\t\t\t\tthis.meshInstance = null;\n\t\t\t\tthis.material?.destroy();\n\t\t\t\tthis.material = null;\n\t\t\t\treturn wasVisible;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.camera = null;\n\t\t\t\tthis._destroyResources();\n\t\t}\n}\n\nclass ShaderGeneratorShader extends ShaderGenerator {\n\t\tgenerateKey(options) {\n\t\t\t\tconst desc = options.shaderDesc;\n\t\t\t\tconst vsHashGLSL = desc.vertexGLSL ? hashCode(desc.vertexGLSL) : 0;\n\t\t\t\tconst fsHashGLSL = desc.fragmentGLSL ? hashCode(desc.fragmentGLSL) : 0;\n\t\t\t\tconst vsHashWGSL = desc.vertexWGSL ? hashCode(desc.vertexWGSL) : 0;\n\t\t\t\tconst fsHashWGSL = desc.fragmentWGSL ? hashCode(desc.fragmentWGSL) : 0;\n\t\t\t\tconst definesHash = ShaderGenerator.definesHash(options.defines);\n\t\t\t\tconst chunksKey = options.shaderChunks?.key ?? '';\n\t\t\t\tlet key = `${desc.uniqueName}_${definesHash}_${vsHashGLSL}_${fsHashGLSL}_${vsHashWGSL}_${fsHashWGSL}_${chunksKey}`;\n\t\t\t\tif (options.skin) key += '_skin';\n\t\t\t\tif (options.useInstancing) key += '_inst';\n\t\t\t\tif (options.useMorphPosition) key += '_morphp';\n\t\t\t\tif (options.useMorphNormal) key += '_morphn';\n\t\t\t\tif (options.useMorphTextureBasedInt) key += '_morphi';\n\t\t\t\treturn key;\n\t\t}\n\t\tcreateAttributesDefinition(definitionOptions, options) {\n\t\t\t\tconst srcAttributes = options.shaderDesc.attributes;\n\t\t\t\tconst attributes = srcAttributes ? {\n\t\t\t\t\t\t...srcAttributes\n\t\t\t\t} : undefined;\n\t\t\t\tif (options.skin) {\n\t\t\t\t\t\tattributes.vertex_boneWeights = SEMANTIC_BLENDWEIGHT;\n\t\t\t\t\t\tattributes.vertex_boneIndices = SEMANTIC_BLENDINDICES;\n\t\t\t\t}\n\t\t\t\tif (options.useMorphPosition || options.useMorphNormal) {\n\t\t\t\t\t\tattributes.morph_vertex_id = SEMANTIC_ATTR15;\n\t\t\t\t}\n\t\t\t\tdefinitionOptions.attributes = attributes;\n\t\t}\n\t\tcreateVertexDefinition(definitionOptions, options, sharedIncludes, wgsl) {\n\t\t\t\tconst desc = options.shaderDesc;\n\t\t\t\tconst includes = new Map(sharedIncludes);\n\t\t\t\tincludes.set('transformInstancingVS', '');\n\t\t\t\tconst defines = new Map(options.defines);\n\t\t\t\tif (options.skin) defines.set('SKIN', true);\n\t\t\t\tif (options.useInstancing) defines.set('INSTANCING', true);\n\t\t\t\tif (options.useMorphPosition || options.useMorphNormal) {\n\t\t\t\t\t\tdefines.set('MORPHING', true);\n\t\t\t\t\t\tif (options.useMorphTextureBasedInt) defines.set('MORPHING_INT', true);\n\t\t\t\t\t\tif (options.useMorphPosition) defines.set('MORPHING_POSITION', true);\n\t\t\t\t\t\tif (options.useMorphNormal) defines.set('MORPHING_NORMAL', true);\n\t\t\t\t}\n\t\t\t\tdefinitionOptions.vertexCode = wgsl ? desc.vertexWGSL : desc.vertexGLSL;\n\t\t\t\tdefinitionOptions.vertexIncludes = includes;\n\t\t\t\tdefinitionOptions.vertexDefines = defines;\n\t\t}\n\t\tcreateFragmentDefinition(definitionOptions, options, sharedIncludes, wgsl) {\n\t\t\t\tconst desc = options.shaderDesc;\n\t\t\t\tconst includes = new Map(sharedIncludes);\n\t\t\t\tconst defines = new Map(options.defines);\n\t\t\t\tdefinitionOptions.fragmentCode = wgsl ? desc.fragmentWGSL : desc.fragmentGLSL;\n\t\t\t\tdefinitionOptions.fragmentIncludes = includes;\n\t\t\t\tdefinitionOptions.fragmentDefines = defines;\n\t\t}\n\t\tcreateShaderDefinition(device, options) {\n\t\t\t\tconst desc = options.shaderDesc;\n\t\t\t\tconst wgsl = device.isWebGPU && !!desc.vertexWGSL && !!desc.fragmentWGSL && (options.shaderChunks?.useWGSL ?? true);\n\t\t\t\tconst definitionOptions = {\n\t\t\t\t\t\tname: `ShaderMaterial-${desc.uniqueName}`,\n\t\t\t\t\t\tshaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL,\n\t\t\t\t\t\tfragmentOutputTypes: desc.fragmentOutputTypes,\n\t\t\t\t\t\tmeshUniformBufferFormat: desc.meshUniformBufferFormat,\n\t\t\t\t\t\tmeshBindGroupFormat: desc.meshBindGroupFormat\n\t\t\t\t};\n\t\t\t\tconst shaderLanguage = wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL;\n\t\t\t\tconst sharedIncludes = MapUtils.merge(ShaderChunks.get(device, shaderLanguage), options.shaderChunks[shaderLanguage]);\n\t\t\t\tthis.createAttributesDefinition(definitionOptions, options);\n\t\t\t\tthis.createVertexDefinition(definitionOptions, options, sharedIncludes, wgsl);\n\t\t\t\tthis.createFragmentDefinition(definitionOptions, options, sharedIncludes, wgsl);\n\t\t\t\treturn ShaderDefinitionUtils.createDefinition(device, definitionOptions);\n\t\t}\n}\nconst shaderGeneratorShader = new ShaderGeneratorShader();\n\nclass ShaderMaterial extends Material {\n\t\tconstructor(shaderDesc){\n\t\t\t\tsuper();\n\t\t\t\tthis.shaderDesc = shaderDesc;\n\t\t}\n\t\tset shaderDesc(value) {\n\t\t\t\tthis._shaderDesc = undefined;\n\t\t\t\tif (value) {\n\t\t\t\t\t\tthis._shaderDesc = {\n\t\t\t\t\t\t\t\tuniqueName: value.uniqueName,\n\t\t\t\t\t\t\t\tattributes: value.attributes,\n\t\t\t\t\t\t\t\tfragmentOutputTypes: value.fragmentOutputTypes,\n\t\t\t\t\t\t\t\tvertexGLSL: value.vertexGLSL,\n\t\t\t\t\t\t\t\tfragmentGLSL: value.fragmentGLSL,\n\t\t\t\t\t\t\t\tvertexWGSL: value.vertexWGSL,\n\t\t\t\t\t\t\t\tfragmentWGSL: value.fragmentWGSL\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (value.vertexCode || value.fragmentCode || value.shaderLanguage) {\n\t\t\t\t\t\t\t\tconst language = value.shaderLanguage ?? SHADERLANGUAGE_GLSL;\n\t\t\t\t\t\t\t\tif (language === SHADERLANGUAGE_GLSL) {\n\t\t\t\t\t\t\t\t\t\tthis._shaderDesc.vertexGLSL = value.vertexCode;\n\t\t\t\t\t\t\t\t\t\tthis._shaderDesc.fragmentGLSL = value.fragmentCode;\n\t\t\t\t\t\t\t\t} else if (language === SHADERLANGUAGE_WGSL) {\n\t\t\t\t\t\t\t\t\t\tthis._shaderDesc.vertexWGSL = value.vertexCode;\n\t\t\t\t\t\t\t\t\t\tthis._shaderDesc.fragmentWGSL = value.fragmentCode;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.clearVariants();\n\t\t}\n\t\tget shaderDesc() {\n\t\t\t\treturn this._shaderDesc;\n\t\t}\n\t\tcopy(source) {\n\t\t\t\tsuper.copy(source);\n\t\t\t\tthis.shaderDesc = source.shaderDesc;\n\t\t\t\treturn this;\n\t\t}\n\t\tgetShaderVariant(params) {\n\t\t\t\tconst { objDefs } = params;\n\t\t\t\tconst options = {\n\t\t\t\t\t\tdefines: ShaderUtils.getCoreDefines(this, params),\n\t\t\t\t\t\tskin: (objDefs & SHADERDEF_SKIN) !== 0,\n\t\t\t\t\t\tuseInstancing: (objDefs & SHADERDEF_INSTANCING) !== 0,\n\t\t\t\t\t\tuseMorphPosition: (objDefs & SHADERDEF_MORPH_POSITION) !== 0,\n\t\t\t\t\t\tuseMorphNormal: (objDefs & SHADERDEF_MORPH_NORMAL) !== 0,\n\t\t\t\t\t\tuseMorphTextureBasedInt: (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0,\n\t\t\t\t\t\tpass: params.pass,\n\t\t\t\t\t\tgamma: params.cameraShaderParams.shaderOutputGamma,\n\t\t\t\t\t\ttoneMapping: params.cameraShaderParams.toneMapping,\n\t\t\t\t\t\tfog: params.cameraShaderParams.fog,\n\t\t\t\t\t\tshaderDesc: this.shaderDesc,\n\t\t\t\t\t\tshaderChunks: this.shaderChunks\n\t\t\t\t};\n\t\t\t\tconst processingOptions = new ShaderProcessorOptions(params.viewUniformFormat, params.viewBindGroupFormat, params.vertexFormat);\n\t\t\t\tconst library = getProgramLibrary(params.device);\n\t\t\t\tlibrary.register('shader-material', shaderGeneratorShader);\n\t\t\t\treturn library.getProgram('shader-material', options, processingOptions, this.userId);\n\t\t}\n}\n\nconst decodeTable = {\n\t\t'linear': 'decodeLinear',\n\t\t'srgb': 'decodeGamma',\n\t\t'rgbm': 'decodeRGBM',\n\t\t'rgbe': 'decodeRGBE',\n\t\t'rgbp': 'decodeRGBP',\n\t\t'xy': 'unpackNormalXY',\n\t\t'xyz': 'unpackNormalXYZ'\n};\nconst encodeTable = {\n\t\t'linear': 'encodeLinear',\n\t\t'srgb': 'encodeGamma',\n\t\t'rgbm': 'encodeRGBM',\n\t\t'rgbe': 'encodeRGBE',\n\t\t'rgbp': 'encodeRGBP'\n};\nclass ChunkUtils {\n\t\tstatic decodeFunc(encoding) {\n\t\t\t\treturn decodeTable[encoding] ?? 'decodeGamma';\n\t\t}\n\t\tstatic encodeFunc(encoding) {\n\t\t\t\treturn encodeTable[encoding] ?? 'encodeGamma';\n\t\t}\n}\n\nconst calculateNormals = (positions, indices)=>{\n\t\tconst triangleCount = indices.length / 3;\n\t\tconst vertexCount = positions.length / 3;\n\t\tconst p1 = new Vec3();\n\t\tconst p2 = new Vec3();\n\t\tconst p3 = new Vec3();\n\t\tconst p1p2 = new Vec3();\n\t\tconst p1p3 = new Vec3();\n\t\tconst faceNormal = new Vec3();\n\t\tconst normals = [];\n\t\tfor(let i = 0; i < positions.length; i++){\n\t\t\t\tnormals[i] = 0;\n\t\t}\n\t\tfor(let i = 0; i < triangleCount; i++){\n\t\t\t\tconst i1 = indices[i * 3];\n\t\t\t\tconst i2 = indices[i * 3 + 1];\n\t\t\t\tconst i3 = indices[i * 3 + 2];\n\t\t\t\tp1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);\n\t\t\t\tp2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);\n\t\t\t\tp3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);\n\t\t\t\tp1p2.sub2(p2, p1);\n\t\t\t\tp1p3.sub2(p3, p1);\n\t\t\t\tfaceNormal.cross(p1p2, p1p3).normalize();\n\t\t\t\tnormals[i1 * 3] += faceNormal.x;\n\t\t\t\tnormals[i1 * 3 + 1] += faceNormal.y;\n\t\t\t\tnormals[i1 * 3 + 2] += faceNormal.z;\n\t\t\t\tnormals[i2 * 3] += faceNormal.x;\n\t\t\t\tnormals[i2 * 3 + 1] += faceNormal.y;\n\t\t\t\tnormals[i2 * 3 + 2] += faceNormal.z;\n\t\t\t\tnormals[i3 * 3] += faceNormal.x;\n\t\t\t\tnormals[i3 * 3 + 1] += faceNormal.y;\n\t\t\t\tnormals[i3 * 3 + 2] += faceNormal.z;\n\t\t}\n\t\tfor(let i = 0; i < vertexCount; i++){\n\t\t\t\tconst nx = normals[i * 3];\n\t\t\t\tconst ny = normals[i * 3 + 1];\n\t\t\t\tconst nz = normals[i * 3 + 2];\n\t\t\t\tconst invLen = 1 / Math.sqrt(nx * nx + ny * ny + nz * nz);\n\t\t\t\tnormals[i * 3] *= invLen;\n\t\t\t\tnormals[i * 3 + 1] *= invLen;\n\t\t\t\tnormals[i * 3 + 2] *= invLen;\n\t\t}\n\t\treturn normals;\n};\nconst calculateTangents = (positions, normals, uvs, indices)=>{\n\t\tconst triangleCount = indices.length / 3;\n\t\tconst vertexCount = positions.length / 3;\n\t\tconst v1 = new Vec3();\n\t\tconst v2 = new Vec3();\n\t\tconst v3 = new Vec3();\n\t\tconst w1 = new Vec2();\n\t\tconst w2 = new Vec2();\n\t\tconst w3 = new Vec2();\n\t\tconst sdir = new Vec3();\n\t\tconst tdir = new Vec3();\n\t\tconst tan1 = new Float32Array(vertexCount * 3);\n\t\tconst tan2 = new Float32Array(vertexCount * 3);\n\t\tconst tangents = [];\n\t\tfor(let i = 0; i < triangleCount; i++){\n\t\t\t\tconst i1 = indices[i * 3];\n\t\t\t\tconst i2 = indices[i * 3 + 1];\n\t\t\t\tconst i3 = indices[i * 3 + 2];\n\t\t\t\tv1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);\n\t\t\t\tv2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);\n\t\t\t\tv3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);\n\t\t\t\tw1.set(uvs[i1 * 2], uvs[i1 * 2 + 1]);\n\t\t\t\tw2.set(uvs[i2 * 2], uvs[i2 * 2 + 1]);\n\t\t\t\tw3.set(uvs[i3 * 2], uvs[i3 * 2 + 1]);\n\t\t\t\tconst x1 = v2.x - v1.x;\n\t\t\t\tconst x2 = v3.x - v1.x;\n\t\t\t\tconst y1 = v2.y - v1.y;\n\t\t\t\tconst y2 = v3.y - v1.y;\n\t\t\t\tconst z1 = v2.z - v1.z;\n\t\t\t\tconst z2 = v3.z - v1.z;\n\t\t\t\tconst s1 = w2.x - w1.x;\n\t\t\t\tconst s2 = w3.x - w1.x;\n\t\t\t\tconst t1 = w2.y - w1.y;\n\t\t\t\tconst t2 = w3.y - w1.y;\n\t\t\t\tconst area = s1 * t2 - s2 * t1;\n\t\t\t\tif (area === 0) {\n\t\t\t\t\t\tsdir.set(0, 1, 0);\n\t\t\t\t\t\ttdir.set(1, 0, 0);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst r = 1 / area;\n\t\t\t\t\t\tsdir.set((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);\n\t\t\t\t\t\ttdir.set((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);\n\t\t\t\t}\n\t\t\t\ttan1[i1 * 3 + 0] += sdir.x;\n\t\t\t\ttan1[i1 * 3 + 1] += sdir.y;\n\t\t\t\ttan1[i1 * 3 + 2] += sdir.z;\n\t\t\t\ttan1[i2 * 3 + 0] += sdir.x;\n\t\t\t\ttan1[i2 * 3 + 1] += sdir.y;\n\t\t\t\ttan1[i2 * 3 + 2] += sdir.z;\n\t\t\t\ttan1[i3 * 3 + 0] += sdir.x;\n\t\t\t\ttan1[i3 * 3 + 1] += sdir.y;\n\t\t\t\ttan1[i3 * 3 + 2] += sdir.z;\n\t\t\t\ttan2[i1 * 3 + 0] += tdir.x;\n\t\t\t\ttan2[i1 * 3 + 1] += tdir.y;\n\t\t\t\ttan2[i1 * 3 + 2] += tdir.z;\n\t\t\t\ttan2[i2 * 3 + 0] += tdir.x;\n\t\t\t\ttan2[i2 * 3 + 1] += tdir.y;\n\t\t\t\ttan2[i2 * 3 + 2] += tdir.z;\n\t\t\t\ttan2[i3 * 3 + 0] += tdir.x;\n\t\t\t\ttan2[i3 * 3 + 1] += tdir.y;\n\t\t\t\ttan2[i3 * 3 + 2] += tdir.z;\n\t\t}\n\t\tconst t1 = new Vec3();\n\t\tconst t2 = new Vec3();\n\t\tconst n = new Vec3();\n\t\tconst temp = new Vec3();\n\t\tfor(let i = 0; i < vertexCount; i++){\n\t\t\t\tn.set(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);\n\t\t\t\tt1.set(tan1[i * 3], tan1[i * 3 + 1], tan1[i * 3 + 2]);\n\t\t\t\tt2.set(tan2[i * 3], tan2[i * 3 + 1], tan2[i * 3 + 2]);\n\t\t\t\tconst ndott = n.dot(t1);\n\t\t\t\ttemp.copy(n).mulScalar(ndott);\n\t\t\t\ttemp.sub2(t1, temp).normalize();\n\t\t\t\ttangents[i * 4] = temp.x;\n\t\t\t\ttangents[i * 4 + 1] = temp.y;\n\t\t\t\ttangents[i * 4 + 2] = temp.z;\n\t\t\t\ttemp.cross(n, t1);\n\t\t\t\ttangents[i * 4 + 3] = temp.dot(t2) < 0.0 ? -1 : 1.0;\n\t\t}\n\t\treturn tangents;\n};\n\nclass Geometry {\n\t\tcalculateNormals() {\n\t\t\t\tthis.normals = calculateNormals(this.positions, this.indices);\n\t\t}\n\t\tcalculateTangents() {\n\t\t\t\tthis.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);\n\t\t}\n}\n\nconst primitiveUv1Padding$1 = 4.0 / 64;\nconst primitiveUv1PaddingScale$1 = 1.0 - primitiveUv1Padding$1 * 2;\nclass BoxGeometry extends Geometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tsuper();\n\t\t\t\tconst he = opts.halfExtents ?? new Vec3(0.5, 0.5, 0.5);\n\t\t\t\tconst ws = opts.widthSegments ?? 1;\n\t\t\t\tconst ls = opts.lengthSegments ?? 1;\n\t\t\t\tconst hs = opts.heightSegments ?? 1;\n\t\t\t\tconst yOffset = opts.yOffset ?? 0;\n\t\t\t\tconst minY = -he.y + yOffset;\n\t\t\t\tconst maxY = he.y + yOffset;\n\t\t\t\tconst corners = [\n\t\t\t\t\t\tnew Vec3(-he.x, minY, he.z),\n\t\t\t\t\t\tnew Vec3(he.x, minY, he.z),\n\t\t\t\t\t\tnew Vec3(he.x, maxY, he.z),\n\t\t\t\t\t\tnew Vec3(-he.x, maxY, he.z),\n\t\t\t\t\t\tnew Vec3(he.x, minY, -he.z),\n\t\t\t\t\t\tnew Vec3(-he.x, minY, -he.z),\n\t\t\t\t\t\tnew Vec3(-he.x, maxY, -he.z),\n\t\t\t\t\t\tnew Vec3(he.x, maxY, -he.z)\n\t\t\t\t];\n\t\t\t\tconst faceAxes = [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t3\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t4,\n\t\t\t\t\t\t\t\t5,\n\t\t\t\t\t\t\t\t7\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t3,\n\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t6\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t4\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t4,\n\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t5,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t6\n\t\t\t\t\t\t]\n\t\t\t\t];\n\t\t\t\tconst faceNormals = [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t-1\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t]\n\t\t\t\t];\n\t\t\t\tconst sides = {\n\t\t\t\t\t\tFRONT: 0,\n\t\t\t\t\t\tBACK: 1,\n\t\t\t\t\t\tTOP: 2,\n\t\t\t\t\t\tBOTTOM: 3,\n\t\t\t\t\t\tRIGHT: 4,\n\t\t\t\t\t\tLEFT: 5\n\t\t\t\t};\n\t\t\t\tconst positions = [];\n\t\t\t\tconst normals = [];\n\t\t\t\tconst uvs = [];\n\t\t\t\tconst indices = [];\n\t\t\t\tlet vcounter = 0;\n\t\t\t\tconst generateFace = (side, uSegments, vSegments)=>{\n\t\t\t\t\t\tconst temp1 = new Vec3();\n\t\t\t\t\t\tconst temp2 = new Vec3();\n\t\t\t\t\t\tconst temp3 = new Vec3();\n\t\t\t\t\t\tconst r = new Vec3();\n\t\t\t\t\t\tfor(let i = 0; i <= uSegments; i++){\n\t\t\t\t\t\t\t\tfor(let j = 0; j <= vSegments; j++){\n\t\t\t\t\t\t\t\t\t\ttemp1.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][1]], i / uSegments);\n\t\t\t\t\t\t\t\t\t\ttemp2.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][2]], j / vSegments);\n\t\t\t\t\t\t\t\t\t\ttemp3.sub2(temp2, corners[faceAxes[side][0]]);\n\t\t\t\t\t\t\t\t\t\tr.add2(temp1, temp3);\n\t\t\t\t\t\t\t\t\t\tlet u = i / uSegments;\n\t\t\t\t\t\t\t\t\t\tlet v = j / vSegments;\n\t\t\t\t\t\t\t\t\t\tpositions.push(r.x, r.y, r.z);\n\t\t\t\t\t\t\t\t\t\tnormals.push(faceNormals[side][0], faceNormals[side][1], faceNormals[side][2]);\n\t\t\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tu = u * primitiveUv1PaddingScale$1 + primitiveUv1Padding$1;\n\t\t\t\t\t\t\t\t\t\tv = v * primitiveUv1PaddingScale$1 + primitiveUv1Padding$1;\n\t\t\t\t\t\t\t\t\t\tu /= 3;\n\t\t\t\t\t\t\t\t\t\tv /= 3;\n\t\t\t\t\t\t\t\t\t\tu += side % 3 / 3;\n\t\t\t\t\t\t\t\t\t\tv += Math.floor(side / 3) / 3;\n\t\t\t\t\t\t\t\t\t\tif (i < uSegments && j < vSegments) {\n\t\t\t\t\t\t\t\t\t\t\t\tindices.push(vcounter + vSegments + 1, vcounter + 1, vcounter);\n\t\t\t\t\t\t\t\t\t\t\t\tindices.push(vcounter + vSegments + 1, vcounter + vSegments + 2, vcounter + 1);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tvcounter++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tgenerateFace(sides.FRONT, ws, hs);\n\t\t\t\tgenerateFace(sides.BACK, ws, hs);\n\t\t\t\tgenerateFace(sides.TOP, ws, ls);\n\t\t\t\tgenerateFace(sides.BOTTOM, ws, ls);\n\t\t\t\tgenerateFace(sides.RIGHT, ls, hs);\n\t\t\t\tgenerateFace(sides.LEFT, ls, hs);\n\t\t\t\tthis.positions = positions;\n\t\t\t\tthis.normals = normals;\n\t\t\t\tthis.uvs = uvs;\n\t\t\t\tthis.uvs1 = uvs;\n\t\t\t\tthis.indices = indices;\n\t\t\t\tif (opts.calculateTangents) {\n\t\t\t\t\t\tthis.tangents = calculateTangents(positions, normals, uvs, indices);\n\t\t\t\t}\n\t\t}\n}\n\nclass SphereGeometry extends Geometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tsuper();\n\t\t\t\tconst radius = opts.radius ?? 0.5;\n\t\t\t\tconst latitudeBands = opts.latitudeBands ?? 16;\n\t\t\t\tconst longitudeBands = opts.longitudeBands ?? 16;\n\t\t\t\tconst positions = [];\n\t\t\t\tconst normals = [];\n\t\t\t\tconst uvs = [];\n\t\t\t\tconst indices = [];\n\t\t\t\tfor(let lat = 0; lat <= latitudeBands; lat++){\n\t\t\t\t\t\tconst theta = lat * Math.PI / latitudeBands;\n\t\t\t\t\t\tconst sinTheta = Math.sin(theta);\n\t\t\t\t\t\tconst cosTheta = Math.cos(theta);\n\t\t\t\t\t\tfor(let lon = 0; lon <= longitudeBands; lon++){\n\t\t\t\t\t\t\t\tconst phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;\n\t\t\t\t\t\t\t\tconst sinPhi = Math.sin(phi);\n\t\t\t\t\t\t\t\tconst cosPhi = Math.cos(phi);\n\t\t\t\t\t\t\t\tconst x = cosPhi * sinTheta;\n\t\t\t\t\t\t\t\tconst y = cosTheta;\n\t\t\t\t\t\t\t\tconst z = sinPhi * sinTheta;\n\t\t\t\t\t\t\t\tconst u = 1 - lon / longitudeBands;\n\t\t\t\t\t\t\t\tconst v = 1 - lat / latitudeBands;\n\t\t\t\t\t\t\t\tpositions.push(x * radius, y * radius, z * radius);\n\t\t\t\t\t\t\t\tnormals.push(x, y, z);\n\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let lat = 0; lat < latitudeBands; ++lat){\n\t\t\t\t\t\tfor(let lon = 0; lon < longitudeBands; ++lon){\n\t\t\t\t\t\t\t\tconst first = lat * (longitudeBands + 1) + lon;\n\t\t\t\t\t\t\t\tconst second = first + longitudeBands + 1;\n\t\t\t\t\t\t\t\tindices.push(first + 1, second, first);\n\t\t\t\t\t\t\t\tindices.push(first + 1, second + 1, second);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.positions = positions;\n\t\t\t\tthis.normals = normals;\n\t\t\t\tthis.uvs = uvs;\n\t\t\t\tthis.uvs1 = uvs;\n\t\t\t\tthis.indices = indices;\n\t\t\t\tif (opts.calculateTangents) {\n\t\t\t\t\t\tthis.tangents = calculateTangents(positions, normals, uvs, indices);\n\t\t\t\t}\n\t\t}\n}\n\nclass DomeGeometry extends SphereGeometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tconst radius = 0.5;\n\t\t\t\tconst latitudeBands = opts.latitudeBands ?? 16;\n\t\t\t\tconst longitudeBands = opts.longitudeBands ?? 16;\n\t\t\t\tsuper({\n\t\t\t\t\t\tradius,\n\t\t\t\t\t\tlatitudeBands,\n\t\t\t\t\t\tlongitudeBands\n\t\t\t\t});\n\t\t\t\tconst bottomLimit = 0.1;\n\t\t\t\tconst curvatureRadius = 0.95;\n\t\t\t\tconst curvatureRadiusSq = curvatureRadius * curvatureRadius;\n\t\t\t\tconst positions = this.positions;\n\t\t\t\tfor(let i = 0; i < positions.length; i += 3){\n\t\t\t\t\t\tconst x = positions[i] / radius;\n\t\t\t\t\t\tlet y = positions[i + 1] / radius;\n\t\t\t\t\t\tconst z = positions[i + 2] / radius;\n\t\t\t\t\t\tif (y < 0) {\n\t\t\t\t\t\t\t\ty *= 0.3;\n\t\t\t\t\t\t\t\tif (x * x + z * z < curvatureRadiusSq) {\n\t\t\t\t\t\t\t\t\t\ty = -0.1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ty += bottomLimit;\n\t\t\t\t\t\ty *= radius;\n\t\t\t\t\t\tpositions[i + 1] = y;\n\t\t\t\t}\n\t\t}\n}\n\nclass SkyGeometry {\n\t\tstatic create(device, type) {\n\t\t\t\tswitch(type){\n\t\t\t\t\t\tcase SKYTYPE_BOX:\n\t\t\t\t\t\t\t\treturn SkyGeometry.box(device);\n\t\t\t\t\t\tcase SKYTYPE_DOME:\n\t\t\t\t\t\t\t\treturn SkyGeometry.dome(device);\n\t\t\t\t}\n\t\t\t\treturn SkyGeometry.infinite(device);\n\t\t}\n\t\tstatic infinite(device) {\n\t\t\t\treturn Mesh.fromGeometry(device, new BoxGeometry(device));\n\t\t}\n\t\tstatic box(device) {\n\t\t\t\treturn Mesh.fromGeometry(device, new BoxGeometry({\n\t\t\t\t\t\tyOffset: 0.5\n\t\t\t\t}));\n\t\t}\n\t\tstatic dome(device) {\n\t\t\t\tconst geom = new DomeGeometry({\n\t\t\t\t\t\tlatitudeBands: 50,\n\t\t\t\t\t\tlongitudeBands: 50\n\t\t\t\t});\n\t\t\t\tgeom.normals = undefined;\n\t\t\t\tgeom.uvs = undefined;\n\t\t\t\treturn Mesh.fromGeometry(device, geom);\n\t\t}\n}\n\nclass SkyMesh {\n\t\tconstructor(device, scene, node, texture, type){\n\t\t\t\tthis.meshInstance = null;\n\t\t\t\tthis._depthWrite = false;\n\t\t\t\tconst material = new ShaderMaterial({\n\t\t\t\t\t\tuniqueName: 'SkyMaterial',\n\t\t\t\t\t\tvertexGLSL: ShaderChunks.get(device, SHADERLANGUAGE_GLSL).get('skyboxVS'),\n\t\t\t\t\t\tfragmentGLSL: ShaderChunks.get(device, SHADERLANGUAGE_GLSL).get('skyboxPS'),\n\t\t\t\t\t\tvertexWGSL: ShaderChunks.get(device, SHADERLANGUAGE_WGSL).get('skyboxVS'),\n\t\t\t\t\t\tfragmentWGSL: ShaderChunks.get(device, SHADERLANGUAGE_WGSL).get('skyboxPS'),\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\taPosition: SEMANTIC_POSITION\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tmaterial.setDefine('{SKYBOX_DECODE_FNC}', ChunkUtils.decodeFunc(texture.encoding));\n\t\t\t\tif (type !== SKYTYPE_INFINITE) material.setDefine('SKYMESH', '');\n\t\t\t\tif (texture.cubemap) material.setDefine('SKY_CUBEMAP', '');\n\t\t\t\tmaterial.setParameter('skyboxHighlightMultiplier', scene.skyboxHighlightMultiplier);\n\t\t\t\tif (texture.cubemap) {\n\t\t\t\t\t\tmaterial.setParameter('texture_cubeMap', texture);\n\t\t\t\t} else {\n\t\t\t\t\t\tmaterial.setParameter('texture_envAtlas', texture);\n\t\t\t\t\t\tmaterial.setParameter('mipLevel', scene.skyboxMip);\n\t\t\t\t}\n\t\t\t\tmaterial.cull = CULLFACE_FRONT;\n\t\t\t\tmaterial.depthWrite = this._depthWrite;\n\t\t\t\tconst skyLayer = scene.layers.getLayerById(LAYERID_SKYBOX);\n\t\t\t\tif (skyLayer) {\n\t\t\t\t\t\tconst mesh = SkyGeometry.create(device, type);\n\t\t\t\t\t\tconst meshInstance = new MeshInstance(mesh, material, node);\n\t\t\t\t\t\tthis.meshInstance = meshInstance;\n\t\t\t\t\t\tmeshInstance.cull = false;\n\t\t\t\t\t\tmeshInstance.pick = false;\n\t\t\t\t\t\tskyLayer.addMeshInstances([\n\t\t\t\t\t\t\t\tmeshInstance\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis.skyLayer = skyLayer;\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this.meshInstance) {\n\t\t\t\t\t\tif (this.skyLayer) {\n\t\t\t\t\t\t\t\tthis.skyLayer.removeMeshInstances([\n\t\t\t\t\t\t\t\t\t\tthis.meshInstance\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.meshInstance.destroy();\n\t\t\t\t\t\tthis.meshInstance = null;\n\t\t\t\t}\n\t\t}\n\t\tset depthWrite(value) {\n\t\t\t\tthis._depthWrite = value;\n\t\t\t\tif (this.meshInstance) {\n\t\t\t\t\t\tthis.meshInstance.material.depthWrite = value;\n\t\t\t\t}\n\t\t}\n\t\tget depthWrite() {\n\t\t\t\treturn this._depthWrite;\n\t\t}\n}\n\nclass Sky {\n\t\tconstructor(scene){\n\t\t\t\tthis._type = SKYTYPE_INFINITE;\n\t\t\t\tthis._center = new Vec3(0, 1, 0);\n\t\t\t\tthis.skyMesh = null;\n\t\t\t\tthis._depthWrite = false;\n\t\t\t\tthis.node = new GraphNode('SkyMeshNode');\n\t\t\t\tthis.device = scene.device;\n\t\t\t\tthis.scene = scene;\n\t\t\t\tthis.center = new Vec3(0, 1, 0);\n\t\t\t\tthis.centerArray = new Float32Array(3);\n\t\t\t\tthis.projectedSkydomeCenterId = this.device.scope.resolve('projectedSkydomeCenter');\n\t\t}\n\t\tapplySettings(render) {\n\t\t\t\tthis.type = render.skyType ?? SKYTYPE_INFINITE;\n\t\t\t\tthis.node.setLocalPosition(new Vec3(render.skyMeshPosition ?? [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t]));\n\t\t\t\tthis.node.setLocalEulerAngles(new Vec3(render.skyMeshRotation ?? [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t]));\n\t\t\t\tthis.node.setLocalScale(new Vec3(render.skyMeshScale ?? [\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1\n\t\t\t\t]));\n\t\t\t\tif (render.skyCenter) {\n\t\t\t\t\t\tthis._center = new Vec3(render.skyCenter);\n\t\t\t\t}\n\t\t}\n\t\tset type(value) {\n\t\t\t\tif (this._type !== value) {\n\t\t\t\t\t\tthis._type = value;\n\t\t\t\t\t\tthis.scene.updateShaders = true;\n\t\t\t\t\t\tthis.updateSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tset center(value) {\n\t\t\t\tthis._center.copy(value);\n\t\t}\n\t\tget center() {\n\t\t\t\treturn this._center;\n\t\t}\n\t\tset depthWrite(value) {\n\t\t\t\tif (this._depthWrite !== value) {\n\t\t\t\t\t\tthis._depthWrite = value;\n\t\t\t\t\t\tif (this.skyMesh) {\n\t\t\t\t\t\t\t\tthis.skyMesh.depthWrite = value;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget depthWrite() {\n\t\t\t\treturn this._depthWrite;\n\t\t}\n\t\tupdateSkyMesh() {\n\t\t\t\tconst texture = this.scene._getSkyboxTex();\n\t\t\t\tif (texture) {\n\t\t\t\t\t\tthis.resetSkyMesh();\n\t\t\t\t\t\tthis.skyMesh = new SkyMesh(this.device, this.scene, this.node, texture, this.type);\n\t\t\t\t\t\tthis.skyMesh.depthWrite = this._depthWrite;\n\t\t\t\t\t\tthis.scene.fire('set:skybox', texture);\n\t\t\t\t}\n\t\t}\n\t\tresetSkyMesh() {\n\t\t\t\tthis.skyMesh?.destroy();\n\t\t\t\tthis.skyMesh = null;\n\t\t}\n\t\tupdate() {\n\t\t\t\tif (this.type !== SKYTYPE_INFINITE) {\n\t\t\t\t\t\tconst { center, centerArray } = this;\n\t\t\t\t\t\tconst temp = new Vec3();\n\t\t\t\t\t\tthis.node.getWorldTransform().transformPoint(center, temp);\n\t\t\t\t\t\tcenterArray[0] = temp.x;\n\t\t\t\t\t\tcenterArray[1] = temp.y;\n\t\t\t\t\t\tcenterArray[2] = temp.z;\n\t\t\t\t\t\tthis.projectedSkydomeCenterId.setValue(centerArray);\n\t\t\t\t}\n\t\t}\n}\n\nconst identityGraphNode = new GraphNode();\nidentityGraphNode.worldTransform = Mat4.IDENTITY;\nidentityGraphNode._dirtyWorld = identityGraphNode._dirtyNormal = false;\nclass ImmediateBatch {\n\t\tconstructor(device, material, layer){\n\t\t\t\tthis.material = material;\n\t\t\t\tthis.layer = layer;\n\t\t\t\tthis.positions = [];\n\t\t\t\tthis.colors = [];\n\t\t\t\tthis.mesh = new Mesh(device);\n\t\t\t\tthis.meshInstance = null;\n\t\t}\n\t\taddLines(positions, color) {\n\t\t\t\tconst destPos = this.positions;\n\t\t\t\tconst count = positions.length;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst pos = positions[i];\n\t\t\t\t\t\tdestPos.push(pos.x, pos.y, pos.z);\n\t\t\t\t}\n\t\t\t\tconst destCol = this.colors;\n\t\t\t\tif (color.length) {\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\tconst col = color[i];\n\t\t\t\t\t\t\t\tdestCol.push(col.r, col.g, col.b, col.a);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\tdestCol.push(color.r, color.g, color.b, color.a);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\taddLinesArrays(positions, color) {\n\t\t\t\tconst destPos = this.positions;\n\t\t\t\tfor(let i = 0; i < positions.length; i += 3){\n\t\t\t\t\t\tdestPos.push(positions[i], positions[i + 1], positions[i + 2]);\n\t\t\t\t}\n\t\t\t\tconst destCol = this.colors;\n\t\t\t\tif (color.length) {\n\t\t\t\t\t\tfor(let i = 0; i < color.length; i += 4){\n\t\t\t\t\t\t\t\tdestCol.push(color[i], color[i + 1], color[i + 2], color[i + 3]);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst count = positions.length / 3;\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\tdestCol.push(color.r, color.g, color.b, color.a);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonPreRender(visibleList, transparent) {\n\t\t\t\tif (this.positions.length > 0 && this.material.transparent === transparent) {\n\t\t\t\t\t\tthis.mesh.setPositions(this.positions);\n\t\t\t\t\t\tthis.mesh.setColors(this.colors);\n\t\t\t\t\t\tthis.mesh.update(PRIMITIVE_LINES, false);\n\t\t\t\t\t\tif (!this.meshInstance) {\n\t\t\t\t\t\t\t\tthis.meshInstance = new MeshInstance(this.mesh, this.material, identityGraphNode);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvisibleList.push(this.meshInstance);\n\t\t\t\t}\n\t\t}\n\t\tclear() {\n\t\t\t\tthis.positions.length = 0;\n\t\t\t\tthis.colors.length = 0;\n\t\t}\n}\n\nclass ImmediateBatches {\n\t\tconstructor(device){\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.map = new Map();\n\t\t}\n\t\tgetBatch(material, layer) {\n\t\t\t\tlet batch = this.map.get(material);\n\t\t\t\tif (!batch) {\n\t\t\t\t\t\tbatch = new ImmediateBatch(this.device, material, layer);\n\t\t\t\t\t\tthis.map.set(material, batch);\n\t\t\t\t}\n\t\t\t\treturn batch;\n\t\t}\n\t\tonPreRender(visibleList, transparent) {\n\t\t\t\tthis.map.forEach((batch)=>{\n\t\t\t\t\t\tbatch.onPreRender(visibleList, transparent);\n\t\t\t\t});\n\t\t}\n\t\tclear() {\n\t\t\t\tthis.map.forEach((batch)=>batch.clear());\n\t\t}\n}\n\nconst tempPoints = [];\nconst vec = new Vec3();\nclass Immediate {\n\t\tconstructor(device){\n\t\t\t\tthis.shaderDescs = new Map();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.quadMesh = null;\n\t\t\t\tthis.textureShader = null;\n\t\t\t\tthis.depthTextureShader = null;\n\t\t\t\tthis.cubeLocalPos = null;\n\t\t\t\tthis.cubeWorldPos = null;\n\t\t\t\tthis.batchesMap = new Map();\n\t\t\t\tthis.allBatches = new Set();\n\t\t\t\tthis.updatedLayers = new Set();\n\t\t\t\tthis._materialDepth = null;\n\t\t\t\tthis._materialNoDepth = null;\n\t\t\t\tthis.layerMeshInstances = new Map();\n\t\t}\n\t\tcreateMaterial(depthTest) {\n\t\t\t\tconst material = new ShaderMaterial({\n\t\t\t\t\t\tuniqueName: 'ImmediateLine',\n\t\t\t\t\t\tvertexGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('immediateLineVS'),\n\t\t\t\t\t\tfragmentGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('immediateLinePS'),\n\t\t\t\t\t\tvertexWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('immediateLineVS'),\n\t\t\t\t\t\tfragmentWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('immediateLinePS'),\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION,\n\t\t\t\t\t\t\t\tvertex_color: SEMANTIC_COLOR\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tmaterial.blendType = BLEND_NORMAL;\n\t\t\t\tmaterial.depthTest = depthTest;\n\t\t\t\tmaterial.update();\n\t\t\t\treturn material;\n\t\t}\n\t\tget materialDepth() {\n\t\t\t\tif (!this._materialDepth) {\n\t\t\t\t\t\tthis._materialDepth = this.createMaterial(true);\n\t\t\t\t}\n\t\t\t\treturn this._materialDepth;\n\t\t}\n\t\tget materialNoDepth() {\n\t\t\t\tif (!this._materialNoDepth) {\n\t\t\t\t\t\tthis._materialNoDepth = this.createMaterial(false);\n\t\t\t\t}\n\t\t\t\treturn this._materialNoDepth;\n\t\t}\n\t\tgetBatch(layer, depthTest) {\n\t\t\t\tlet batches = this.batchesMap.get(layer);\n\t\t\t\tif (!batches) {\n\t\t\t\t\t\tbatches = new ImmediateBatches(this.device);\n\t\t\t\t\t\tthis.batchesMap.set(layer, batches);\n\t\t\t\t}\n\t\t\t\tthis.allBatches.add(batches);\n\t\t\t\tconst material = depthTest ? this.materialDepth : this.materialNoDepth;\n\t\t\t\treturn batches.getBatch(material, layer);\n\t\t}\n\t\tgetShaderDesc(id, fragmentGLSL, fragmentWGSL) {\n\t\t\t\tif (!this.shaderDescs.has(id)) {\n\t\t\t\t\t\tthis.shaderDescs.set(id, {\n\t\t\t\t\t\t\t\tuniqueName: `DebugShader:${id}`,\n\t\t\t\t\t\t\t\tvertexGLSL: `\n\t\t\t\t\tattribute vec2 vertex_position;\n\t\t\t\t\tuniform mat4 matrix_model;\n\t\t\t\t\tvarying vec2 uv0;\n\t\t\t\t\tvoid main(void) {\n\t\t\t\t\t\tgl_Position = matrix_model * vec4(vertex_position, 0, 1);\n\t\t\t\t\t\tuv0 = vertex_position.xy + 0.5;\n\t\t\t\t\t}\n\t\t\t\t`,\n\t\t\t\t\t\t\t\tvertexWGSL: `\n\t\t\t\t\tattribute vertex_position: vec2f;\n\t\t\t\t\tuniform matrix_model: mat4x4f;\n\t\t\t\t\tvarying uv0: vec2f;\n\t\t\t\t\t@vertex fn vertexMain(input: VertexInput) -> VertexOutput {\n\t\t\t\t\t\tvar output: VertexOutput;\n\t\t\t\t\t\toutput.position = uniform.matrix_model * vec4f(input.vertex_position, 0.0, 1.0);\n\t\t\t\t\t\toutput.uv0 = input.vertex_position.xy + vec2f(0.5);\n\t\t\t\t\t\treturn output;\n\t\t\t\t\t}\n\t\t\t\t`,\n\t\t\t\t\t\t\t\tfragmentGLSL: fragmentGLSL,\n\t\t\t\t\t\t\t\tfragmentWGSL: fragmentWGSL,\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this.shaderDescs.get(id);\n\t\t}\n\t\tgetTextureShaderDesc(encoding) {\n\t\t\t\tconst decodeFunc = ChunkUtils.decodeFunc(encoding);\n\t\t\t\treturn this.getShaderDesc(`textureShader-${encoding}`, `\n\t\t\t#include \"gammaPS\"\n\t\t\tvarying vec2 uv0;\n\t\t\tuniform sampler2D colorMap;\n\t\t\tvoid main (void) {\n\t\t\t\tvec3 linearColor = ${decodeFunc}(texture2D(colorMap, uv0));\n\t\t\t\tgl_FragColor = vec4(gammaCorrectOutput(linearColor), 1);\n\t\t\t}\n\t\t`, `\n\t\t\t#include \"gammaPS\"\n\t\t\tvarying uv0: vec2f;\n\t\t\tvar colorMap: texture_2d<f32>;\n\t\t\tvar colorMapSampler: sampler;\n\t\t\t@fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\t\t\tvar output: FragmentOutput;\n\t\t\t\tlet sampledTex = textureSample(colorMap, colorMapSampler, input.uv0);\n\t\t\t\tlet linearColor: vec3f = ${decodeFunc}(sampledTex);\n\t\t\t\toutput.color = vec4f(gammaCorrectOutput(linearColor), 1.0);\n\t\t\t\treturn output;\n\t\t\t}\n\t\t`);\n\t\t}\n\t\tgetUnfilterableTextureShaderDesc() {\n\t\t\t\treturn this.getShaderDesc('textureShaderUnfilterable', `\n\t\t\tvarying vec2 uv0;\n\t\t\tuniform highp sampler2D colorMap;\n\t\t\tvoid main (void) {\n\t\t\t\tivec2 uv = ivec2(uv0 * textureSize(colorMap, 0));\n\t\t\t\tgl_FragColor = vec4(texelFetch(colorMap, uv, 0).xyz, 1);\n\t\t\t}\n\t\t`, `\n\t\t\tvarying uv0: vec2f;\n\t\t\tvar colorMap: texture_2d<uff>;\n\t\t\t@fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\t\t\tvar output: FragmentOutput;\n\t\t\t\tlet uv : vec2<i32> = vec2<i32>(input.uv0 * vec2f(textureDimensions(colorMap, 0)));\n\t\t\t\tlet fetchedColor : vec4f = textureLoad(colorMap, uv, 0);\n\t\t\t\toutput.color = vec4f(fetchedColor.xyz, 1.0);\n\t\t\t\treturn output;\n\t\t\t}\n\t\t`);\n\t\t}\n\t\tgetDepthTextureShaderDesc() {\n\t\t\t\treturn this.getShaderDesc('depthTextureShader', `\n\t\t\t#include \"screenDepthPS\"\n\t\t\t#include \"gammaPS\"\n\t\t\tvarying vec2 uv0;\n\t\t\tvoid main() {\n\t\t\t\tfloat depth = getLinearScreenDepth(getImageEffectUV(uv0)) * camera_params.x;\n\t\t\t\tgl_FragColor = vec4(gammaCorrectOutput(vec3(depth)), 1.0);\n\t\t\t}\n\t\t`, `\n\t\t\t#include \"screenDepthPS\"\n\t\t\t#include \"gammaPS\"\n\t\t\tvarying uv0: vec2f;\n\t\t\t@fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\t\t\t\tvar output: FragmentOutput;\n\t\t\t\tlet depth: f32 = getLinearScreenDepth(getImageEffectUV(input.uv0)) * uniform.camera_params.x;\n\t\t\t\toutput.color = vec4f(gammaCorrectOutput(vec3f(depth)), 1.0);\n\t\t\t\treturn output;\n\t\t\t}\n\t\t`);\n\t\t}\n\t\tgetQuadMesh() {\n\t\t\t\tif (!this.quadMesh) {\n\t\t\t\t\t\tthis.quadMesh = new Mesh(this.device);\n\t\t\t\t\t\tthis.quadMesh.setPositions([\n\t\t\t\t\t\t\t\t-0.5,\n\t\t\t\t\t\t\t\t-0.5,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0.5,\n\t\t\t\t\t\t\t\t-0.5,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t-0.5,\n\t\t\t\t\t\t\t\t0.5,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0.5,\n\t\t\t\t\t\t\t\t0.5,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis.quadMesh.update(PRIMITIVE_TRISTRIP);\n\t\t\t\t}\n\t\t\t\treturn this.quadMesh;\n\t\t}\n\t\tdrawMesh(material, matrix, mesh, meshInstance, layer) {\n\t\t\t\tif (!meshInstance) {\n\t\t\t\t\t\tconst graphNode = this.getGraphNode(matrix);\n\t\t\t\t\t\tmeshInstance = new MeshInstance(mesh, material, graphNode);\n\t\t\t\t}\n\t\t\t\tlet layerMeshInstances = this.layerMeshInstances.get(layer);\n\t\t\t\tif (!layerMeshInstances) {\n\t\t\t\t\t\tlayerMeshInstances = [];\n\t\t\t\t\t\tthis.layerMeshInstances.set(layer, layerMeshInstances);\n\t\t\t\t}\n\t\t\t\tlayerMeshInstances.push(meshInstance);\n\t\t}\n\t\tdrawWireAlignedBox(min, max, color, depthTest, layer, mat) {\n\t\t\t\tif (mat) {\n\t\t\t\t\t\tconst mulPoint = (x, y, z)=>{\n\t\t\t\t\t\t\t\tvec.set(x, y, z);\n\t\t\t\t\t\t\t\tmat.transformPoint(vec, vec);\n\t\t\t\t\t\t\t\ttempPoints.push(vec.x, vec.y, vec.z);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tmulPoint(min.x, min.y, min.z);\n\t\t\t\t\t\tmulPoint(min.x, max.y, min.z);\n\t\t\t\t\t\tmulPoint(min.x, max.y, min.z);\n\t\t\t\t\t\tmulPoint(max.x, max.y, min.z);\n\t\t\t\t\t\tmulPoint(max.x, max.y, min.z);\n\t\t\t\t\t\tmulPoint(max.x, min.y, min.z);\n\t\t\t\t\t\tmulPoint(max.x, min.y, min.z);\n\t\t\t\t\t\tmulPoint(min.x, min.y, min.z);\n\t\t\t\t\t\tmulPoint(min.x, min.y, max.z);\n\t\t\t\t\t\tmulPoint(min.x, max.y, max.z);\n\t\t\t\t\t\tmulPoint(min.x, max.y, max.z);\n\t\t\t\t\t\tmulPoint(max.x, max.y, max.z);\n\t\t\t\t\t\tmulPoint(max.x, max.y, max.z);\n\t\t\t\t\t\tmulPoint(max.x, min.y, max.z);\n\t\t\t\t\t\tmulPoint(max.x, min.y, max.z);\n\t\t\t\t\t\tmulPoint(min.x, min.y, max.z);\n\t\t\t\t\t\tmulPoint(min.x, min.y, min.z);\n\t\t\t\t\t\tmulPoint(min.x, min.y, max.z);\n\t\t\t\t\t\tmulPoint(min.x, max.y, min.z);\n\t\t\t\t\t\tmulPoint(min.x, max.y, max.z);\n\t\t\t\t\t\tmulPoint(max.x, max.y, min.z);\n\t\t\t\t\t\tmulPoint(max.x, max.y, max.z);\n\t\t\t\t\t\tmulPoint(max.x, min.y, min.z);\n\t\t\t\t\t\tmulPoint(max.x, min.y, max.z);\n\t\t\t\t} else {\n\t\t\t\t\t\ttempPoints.push(min.x, min.y, min.z, min.x, max.y, min.z, min.x, max.y, min.z, max.x, max.y, min.z, max.x, max.y, min.z, max.x, min.y, min.z, max.x, min.y, min.z, min.x, min.y, min.z, min.x, min.y, max.z, min.x, max.y, max.z, min.x, max.y, max.z, max.x, max.y, max.z, max.x, max.y, max.z, max.x, min.y, max.z, max.x, min.y, max.z, min.x, min.y, max.z, min.x, min.y, min.z, min.x, min.y, max.z, min.x, max.y, min.z, min.x, max.y, max.z, max.x, max.y, min.z, max.x, max.y, max.z, max.x, min.y, min.z, max.x, min.y, max.z);\n\t\t\t\t}\n\t\t\t\tconst batch = this.getBatch(layer, depthTest);\n\t\t\t\tbatch.addLinesArrays(tempPoints, color);\n\t\t\t\ttempPoints.length = 0;\n\t\t}\n\t\tdrawWireSphere(center, radius, color, numSegments, depthTest, layer) {\n\t\t\t\tconst step = 2 * Math.PI / numSegments;\n\t\t\t\tlet angle = 0;\n\t\t\t\tfor(let i = 0; i < numSegments; i++){\n\t\t\t\t\t\tconst sin0 = Math.sin(angle);\n\t\t\t\t\t\tconst cos0 = Math.cos(angle);\n\t\t\t\t\t\tangle += step;\n\t\t\t\t\t\tconst sin1 = Math.sin(angle);\n\t\t\t\t\t\tconst cos1 = Math.cos(angle);\n\t\t\t\t\t\ttempPoints.push(center.x + radius * sin0, center.y, center.z + radius * cos0);\n\t\t\t\t\t\ttempPoints.push(center.x + radius * sin1, center.y, center.z + radius * cos1);\n\t\t\t\t\t\ttempPoints.push(center.x + radius * sin0, center.y + radius * cos0, center.z);\n\t\t\t\t\t\ttempPoints.push(center.x + radius * sin1, center.y + radius * cos1, center.z);\n\t\t\t\t\t\ttempPoints.push(center.x, center.y + radius * sin0, center.z + radius * cos0);\n\t\t\t\t\t\ttempPoints.push(center.x, center.y + radius * sin1, center.z + radius * cos1);\n\t\t\t\t}\n\t\t\t\tconst batch = this.getBatch(layer, depthTest);\n\t\t\t\tbatch.addLinesArrays(tempPoints, color);\n\t\t\t\ttempPoints.length = 0;\n\t\t}\n\t\tgetGraphNode(matrix) {\n\t\t\t\tconst graphNode = new GraphNode('ImmediateDebug');\n\t\t\t\tgraphNode.worldTransform = matrix;\n\t\t\t\tgraphNode._dirtyWorld = graphNode._dirtyNormal = false;\n\t\t\t\treturn graphNode;\n\t\t}\n\t\tonPreRenderLayer(layer, visibleList, transparent) {\n\t\t\t\tthis.batchesMap.forEach((batches, batchLayer)=>{\n\t\t\t\t\t\tif (batchLayer === layer) {\n\t\t\t\t\t\t\t\tbatches.onPreRender(visibleList, transparent);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (!this.updatedLayers.has(layer)) {\n\t\t\t\t\t\tthis.updatedLayers.add(layer);\n\t\t\t\t\t\tconst meshInstances = this.layerMeshInstances.get(layer);\n\t\t\t\t\t\tif (meshInstances) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\t\t\tvisibleList.push(meshInstances[i]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmeshInstances.length = 0;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonPostRender() {\n\t\t\t\tthis.allBatches.forEach((batch)=>batch.clear());\n\t\t\t\tthis.allBatches.clear();\n\t\t\t\tthis.updatedLayers.clear();\n\t\t}\n}\n\nconst _goldenAngle = 2.399963229728653;\nconst random = {\n\t\tcirclePoint (point) {\n\t\t\t\tconst r = Math.sqrt(Math.random());\n\t\t\t\tconst theta = Math.random() * 2 * Math.PI;\n\t\t\t\tpoint.x = r * Math.cos(theta);\n\t\t\t\tpoint.y = r * Math.sin(theta);\n\t\t},\n\t\tcirclePointDeterministic (point, index, numPoints) {\n\t\t\t\tconst theta = index * _goldenAngle;\n\t\t\t\tconst r = Math.sqrt(index) / Math.sqrt(numPoints);\n\t\t\t\tpoint.x = r * Math.cos(theta);\n\t\t\t\tpoint.y = r * Math.sin(theta);\n\t\t},\n\t\tspherePointDeterministic (point, index, numPoints, start = 0, end = 1) {\n\t\t\t\tstart = 1 - 2 * start;\n\t\t\t\tend = 1 - 2 * end;\n\t\t\t\tconst y = math.lerp(start, end, index / numPoints);\n\t\t\t\tconst radius = Math.sqrt(1 - y * y);\n\t\t\t\tconst theta = _goldenAngle * index;\n\t\t\t\tpoint.x = Math.cos(theta) * radius;\n\t\t\t\tpoint.y = y;\n\t\t\t\tpoint.z = Math.sin(theta) * radius;\n\t\t},\n\t\tradicalInverse (i) {\n\t\t\t\tlet bits = (i << 16 | i >>> 16) >>> 0;\n\t\t\t\tbits = ((bits & 0x55555555) << 1 | (bits & 0xAAAAAAAA) >>> 1) >>> 0;\n\t\t\t\tbits = ((bits & 0x33333333) << 2 | (bits & 0xCCCCCCCC) >>> 2) >>> 0;\n\t\t\t\tbits = ((bits & 0x0F0F0F0F) << 4 | (bits & 0xF0F0F0F0) >>> 4) >>> 0;\n\t\t\t\tbits = ((bits & 0x00FF00FF) << 8 | (bits & 0xFF00FF00) >>> 8) >>> 0;\n\t\t\t\treturn bits * 2.3283064365386963e-10;\n\t\t}\n};\n\nconst getProjectionName = (projection)=>{\n\t\tswitch(projection){\n\t\t\t\tcase TEXTUREPROJECTION_CUBE:\n\t\t\t\t\t\treturn 'Cubemap';\n\t\t\t\tcase TEXTUREPROJECTION_OCTAHEDRAL:\n\t\t\t\t\t\treturn 'Octahedral';\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn 'Equirect';\n\t\t}\n};\nconst packFloat32ToRGBA8 = (value, array, offset)=>{\n\t\tif (value <= 0) {\n\t\t\t\tarray[offset + 0] = 0;\n\t\t\t\tarray[offset + 1] = 0;\n\t\t\t\tarray[offset + 2] = 0;\n\t\t\t\tarray[offset + 3] = 0;\n\t\t} else if (value >= 1.0) {\n\t\t\t\tarray[offset + 0] = 255;\n\t\t\t\tarray[offset + 1] = 0;\n\t\t\t\tarray[offset + 2] = 0;\n\t\t\t\tarray[offset + 3] = 0;\n\t\t} else {\n\t\t\t\tlet encX = 1 * value % 1;\n\t\t\t\tlet encY = 255 * value % 1;\n\t\t\t\tlet encZ = 65025 * value % 1;\n\t\t\t\tconst encW = 16581375.0 * value % 1;\n\t\t\t\tencX -= encY / 255;\n\t\t\t\tencY -= encZ / 255;\n\t\t\t\tencZ -= encW / 255;\n\t\t\t\tarray[offset + 0] = Math.min(255, Math.floor(encX * 256));\n\t\t\t\tarray[offset + 1] = Math.min(255, Math.floor(encY * 256));\n\t\t\t\tarray[offset + 2] = Math.min(255, Math.floor(encZ * 256));\n\t\t\t\tarray[offset + 3] = Math.min(255, Math.floor(encW * 256));\n\t\t}\n};\nconst packSamples = (samples)=>{\n\t\tconst numSamples = samples.length;\n\t\tconst w = Math.min(numSamples, 512);\n\t\tconst h = Math.ceil(numSamples / w);\n\t\tconst data = new Uint8Array(w * h * 4);\n\t\tlet off = 0;\n\t\tfor(let i = 0; i < numSamples; i += 4){\n\t\t\t\tpackFloat32ToRGBA8(samples[i + 0] * 0.5 + 0.5, data, off + 0);\n\t\t\t\tpackFloat32ToRGBA8(samples[i + 1] * 0.5 + 0.5, data, off + 4);\n\t\t\t\tpackFloat32ToRGBA8(samples[i + 2] * 0.5 + 0.5, data, off + 8);\n\t\t\t\tpackFloat32ToRGBA8(samples[i + 3] / 8, data, off + 12);\n\t\t\t\toff += 16;\n\t\t}\n\t\treturn {\n\t\t\t\twidth: w,\n\t\t\t\theight: h,\n\t\t\t\tdata: data\n\t\t};\n};\nconst hemisphereSamplePhong = (dstVec, x, y, specularPower)=>{\n\t\tconst phi = y * 2 * Math.PI;\n\t\tconst cosTheta = Math.pow(1 - x, 1 / (specularPower + 1));\n\t\tconst sinTheta = Math.sqrt(1 - cosTheta * cosTheta);\n\t\tdstVec.set(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta).normalize();\n};\nconst hemisphereSampleLambert = (dstVec, x, y)=>{\n\t\tconst phi = y * 2 * Math.PI;\n\t\tconst cosTheta = Math.sqrt(1 - x);\n\t\tconst sinTheta = Math.sqrt(x);\n\t\tdstVec.set(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta).normalize();\n};\nconst hemisphereSampleGGX = (dstVec, x, y, a)=>{\n\t\tconst phi = y * 2 * Math.PI;\n\t\tconst cosTheta = Math.sqrt((1 - x) / (1 + (a * a - 1) * x));\n\t\tconst sinTheta = Math.sqrt(1 - cosTheta * cosTheta);\n\t\tdstVec.set(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta).normalize();\n};\nconst D_GGX = (NoH, linearRoughness)=>{\n\t\tconst a = NoH * linearRoughness;\n\t\tconst k = linearRoughness / (1.0 - NoH * NoH + a * a);\n\t\treturn k * k * (1 / Math.PI);\n};\nconst generatePhongSamples = (numSamples, specularPower)=>{\n\t\tconst H = new Vec3();\n\t\tconst result = [];\n\t\tfor(let i = 0; i < numSamples; ++i){\n\t\t\t\themisphereSamplePhong(H, i / numSamples, random.radicalInverse(i), specularPower);\n\t\t\t\tresult.push(H.x, H.y, H.z, 0);\n\t\t}\n\t\treturn result;\n};\nconst generateLambertSamples = (numSamples, sourceTotalPixels)=>{\n\t\tconst pixelsPerSample = sourceTotalPixels / numSamples;\n\t\tconst H = new Vec3();\n\t\tconst result = [];\n\t\tfor(let i = 0; i < numSamples; ++i){\n\t\t\t\themisphereSampleLambert(H, i / numSamples, random.radicalInverse(i));\n\t\t\t\tconst pdf = H.z / Math.PI;\n\t\t\t\tconst mipLevel = 0.5 * Math.log2(pixelsPerSample / pdf);\n\t\t\t\tresult.push(H.x, H.y, H.z, mipLevel);\n\t\t}\n\t\treturn result;\n};\nconst requiredSamplesGGX = {\n\t\t'16': {\n\t\t\t\t'2': 26,\n\t\t\t\t'8': 20,\n\t\t\t\t'32': 17,\n\t\t\t\t'128': 16,\n\t\t\t\t'512': 16\n\t\t},\n\t\t'32': {\n\t\t\t\t'2': 53,\n\t\t\t\t'8': 40,\n\t\t\t\t'32': 34,\n\t\t\t\t'128': 32,\n\t\t\t\t'512': 32\n\t\t},\n\t\t'128': {\n\t\t\t\t'2': 214,\n\t\t\t\t'8': 163,\n\t\t\t\t'32': 139,\n\t\t\t\t'128': 130,\n\t\t\t\t'512': 128\n\t\t},\n\t\t'1024': {\n\t\t\t\t'2': 1722,\n\t\t\t\t'8': 1310,\n\t\t\t\t'32': 1114,\n\t\t\t\t'128': 1041,\n\t\t\t\t'512': 1025\n\t\t}\n};\nconst getRequiredSamplesGGX = (numSamples, specularPower)=>{\n\t\tconst table = requiredSamplesGGX[numSamples];\n\t\treturn table && table[specularPower] || numSamples;\n};\nconst generateGGXSamples = (numSamples, specularPower, sourceTotalPixels)=>{\n\t\tconst pixelsPerSample = sourceTotalPixels / numSamples;\n\t\tconst roughness = 1 - Math.log2(specularPower) / 11.0;\n\t\tconst a = roughness * roughness;\n\t\tconst H = new Vec3();\n\t\tconst L = new Vec3();\n\t\tconst N = new Vec3(0, 0, 1);\n\t\tconst result = [];\n\t\tconst requiredSamples = getRequiredSamplesGGX(numSamples, specularPower);\n\t\tfor(let i = 0; i < requiredSamples; ++i){\n\t\t\t\themisphereSampleGGX(H, i / requiredSamples, random.radicalInverse(i), a);\n\t\t\t\tconst NoH = H.z;\n\t\t\t\tL.set(H.x, H.y, H.z).mulScalar(2 * NoH).sub(N);\n\t\t\t\tif (L.z > 0) {\n\t\t\t\t\t\tconst pdf = D_GGX(Math.min(1, NoH), a) / 4 + 0.001;\n\t\t\t\t\t\tconst mipLevel = 0.5 * Math.log2(pixelsPerSample / pdf);\n\t\t\t\t\t\tresult.push(L.x, L.y, L.z, mipLevel);\n\t\t\t\t}\n\t\t}\n\t\twhile(result.length < numSamples * 4){\n\t\t\t\tresult.push(0, 0, 0, 0);\n\t\t}\n\t\treturn result;\n};\nconst createSamplesTex = (device, name, samples)=>{\n\t\tconst packedSamples = packSamples(samples);\n\t\treturn new Texture(device, {\n\t\t\t\tname: name,\n\t\t\t\twidth: packedSamples.width,\n\t\t\t\theight: packedSamples.height,\n\t\t\t\tmipmaps: false,\n\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\tlevels: [\n\t\t\t\t\t\tpackedSamples.data\n\t\t\t\t]\n\t\t});\n};\nclass SimpleCache {\n\t\tconstructor(destroyContent = true){\n\t\t\t\tthis.map = new Map();\n\t\t\t\tthis.destroyContent = destroyContent;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this.destroyContent) {\n\t\t\t\t\t\tthis.map.forEach((value, key)=>{\n\t\t\t\t\t\t\t\tvalue.destroy();\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tget(key, missFunc) {\n\t\t\t\tif (!this.map.has(key)) {\n\t\t\t\t\t\tconst result = missFunc();\n\t\t\t\t\t\tthis.map.set(key, result);\n\t\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\treturn this.map.get(key);\n\t\t}\n}\nconst samplesCache = new SimpleCache(false);\nconst deviceCache$1 = new DeviceCache();\nconst getCachedTexture = (device, key, getSamplesFnc)=>{\n\t\tconst cache = deviceCache$1.get(device, ()=>{\n\t\t\t\treturn new SimpleCache();\n\t\t});\n\t\treturn cache.get(key, ()=>{\n\t\t\t\treturn createSamplesTex(device, key, samplesCache.get(key, getSamplesFnc));\n\t\t});\n};\nconst generateLambertSamplesTex = (device, numSamples, sourceTotalPixels)=>{\n\t\tconst key = `lambert-samples-${numSamples}-${sourceTotalPixels}`;\n\t\treturn getCachedTexture(device, key, ()=>{\n\t\t\t\treturn generateLambertSamples(numSamples, sourceTotalPixels);\n\t\t});\n};\nconst generatePhongSamplesTex = (device, numSamples, specularPower)=>{\n\t\tconst key = `phong-samples-${numSamples}-${specularPower}`;\n\t\treturn getCachedTexture(device, key, ()=>{\n\t\t\t\treturn generatePhongSamples(numSamples, specularPower);\n\t\t});\n};\nconst generateGGXSamplesTex = (device, numSamples, specularPower, sourceTotalPixels)=>{\n\t\tconst key = `ggx-samples-${numSamples}-${specularPower}-${sourceTotalPixels}`;\n\t\treturn getCachedTexture(device, key, ()=>{\n\t\t\t\treturn generateGGXSamples(numSamples, specularPower, sourceTotalPixels);\n\t\t});\n};\nfunction reprojectTexture(source, target, options = {}) {\n\t\tconst seamPixels = options.seamPixels ?? 0;\n\t\tconst innerWidth = (options.rect?.z ?? target.width) - seamPixels * 2;\n\t\tconst innerHeight = (options.rect?.w ?? target.height) - seamPixels * 2;\n\t\tif (innerWidth < 1 || innerHeight < 1) {\n\t\t\t\treturn false;\n\t\t}\n\t\tconst funcNames = {\n\t\t\t\t'none': 'reproject',\n\t\t\t\t'lambert': 'prefilterSamplesUnweighted',\n\t\t\t\t'phong': 'prefilterSamplesUnweighted',\n\t\t\t\t'ggx': 'prefilterSamples'\n\t\t};\n\t\tconst specularPower = options.hasOwnProperty('specularPower') ? options.specularPower : 1;\n\t\tconst face = options.hasOwnProperty('face') ? options.face : null;\n\t\tconst distribution = options.hasOwnProperty('distribution') ? options.distribution : specularPower === 1 ? 'none' : 'phong';\n\t\tconst processFunc = funcNames[distribution] || 'reproject';\n\t\tconst prefilterSamples = processFunc.startsWith('prefilterSamples');\n\t\tconst decodeFunc = ChunkUtils.decodeFunc(source.encoding);\n\t\tconst encodeFunc = ChunkUtils.encodeFunc(target.encoding);\n\t\tconst sourceFunc = `sample${getProjectionName(source.projection)}`;\n\t\tconst targetFunc = `getDirection${getProjectionName(target.projection)}`;\n\t\tconst numSamples = options.hasOwnProperty('numSamples') ? options.numSamples : 1024;\n\t\tconst shaderKey = `ReprojectShader:${processFunc}_${decodeFunc}_${encodeFunc}_${sourceFunc}_${targetFunc}_${numSamples}`;\n\t\tconst device = source.device;\n\t\tlet shader = getProgramLibrary(device).getCachedShader(shaderKey);\n\t\tif (!shader) {\n\t\t\t\tconst defines = new Map();\n\t\t\t\tif (prefilterSamples) defines.set('USE_SAMPLES_TEX', '');\n\t\t\t\tif (source.cubemap) defines.set('CUBEMAP_SOURCE', '');\n\t\t\t\tdefines.set('{PROCESS_FUNC}', processFunc);\n\t\t\t\tdefines.set('{DECODE_FUNC}', decodeFunc);\n\t\t\t\tdefines.set('{ENCODE_FUNC}', encodeFunc);\n\t\t\t\tdefines.set('{SOURCE_FUNC}', sourceFunc);\n\t\t\t\tdefines.set('{TARGET_FUNC}', targetFunc);\n\t\t\t\tdefines.set('{NUM_SAMPLES}', numSamples);\n\t\t\t\tdefines.set('{NUM_SAMPLES_SQRT}', Math.round(Math.sqrt(numSamples)).toFixed(1));\n\t\t\t\tconst wgsl = device.isWebGPU;\n\t\t\t\tconst chunks = ShaderChunks.get(device, wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL);\n\t\t\t\tconst includes = new Map();\n\t\t\t\tincludes.set('decodePS', chunks.get('decodePS'));\n\t\t\t\tincludes.set('encodePS', chunks.get('encodePS'));\n\t\t\t\tshader = ShaderUtils.createShader(device, {\n\t\t\t\t\t\tuniqueName: shaderKey,\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvertexChunk: 'reprojectVS',\n\t\t\t\t\t\tfragmentChunk: 'reprojectPS',\n\t\t\t\t\t\tfragmentIncludes: includes,\n\t\t\t\t\t\tfragmentDefines: defines\n\t\t\t\t});\n\t\t}\n\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\tconst constantSource = device.scope.resolve(source.cubemap ? 'sourceCube' : 'sourceTex');\n\t\tconstantSource.setValue(source);\n\t\tconst constantParams = device.scope.resolve('params');\n\t\tconst uvModParam = device.scope.resolve('uvMod');\n\t\tif (seamPixels > 0) {\n\t\t\t\tuvModParam.setValue([\n\t\t\t\t\t\t(innerWidth + seamPixels * 2) / innerWidth,\n\t\t\t\t\t\t(innerHeight + seamPixels * 2) / innerHeight,\n\t\t\t\t\t\t-seamPixels / innerWidth,\n\t\t\t\t\t\t-seamPixels / innerHeight\n\t\t\t\t]);\n\t\t} else {\n\t\t\t\tuvModParam.setValue([\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t]);\n\t\t}\n\t\tconst params = [\n\t\t\t\t0,\n\t\t\t\ttarget.width * target.height * (target.cubemap ? 6 : 1),\n\t\t\t\tsource.width * source.height * (source.cubemap ? 6 : 1)\n\t\t];\n\t\tif (prefilterSamples) {\n\t\t\t\tconst sourceTotalPixels = source.width * source.height * (source.cubemap ? 6 : 1);\n\t\t\t\tconst samplesTex = distribution === 'ggx' ? generateGGXSamplesTex(device, numSamples, specularPower, sourceTotalPixels) : distribution === 'lambert' ? generateLambertSamplesTex(device, numSamples, sourceTotalPixels) : generatePhongSamplesTex(device, numSamples, specularPower);\n\t\t\t\tdevice.scope.resolve('samplesTex').setValue(samplesTex);\n\t\t\t\tdevice.scope.resolve('samplesTexInverseSize').setValue([\n\t\t\t\t\t\t1.0 / samplesTex.width,\n\t\t\t\t\t\t1.0 / samplesTex.height\n\t\t\t\t]);\n\t\t}\n\t\tfor(let f = 0; f < (target.cubemap ? 6 : 1); f++){\n\t\t\t\tif (face === null || f === face) {\n\t\t\t\t\t\tconst renderTarget = new RenderTarget({\n\t\t\t\t\t\t\t\tcolorBuffer: target,\n\t\t\t\t\t\t\t\tface: f,\n\t\t\t\t\t\t\t\tdepth: false,\n\t\t\t\t\t\t\t\tflipY: device.isWebGPU\n\t\t\t\t\t\t});\n\t\t\t\t\t\tparams[0] = f;\n\t\t\t\t\t\tconstantParams.setValue(params);\n\t\t\t\t\t\tdrawQuadWithShader(device, renderTarget, shader, options?.rect);\n\t\t\t\t\t\trenderTarget.destroy();\n\t\t\t\t}\n\t\t}\n\t\treturn true;\n}\n\nconst calcLevels = (width, height = 0)=>{\n\t\treturn 1 + Math.floor(Math.log2(Math.max(width, height)));\n};\nconst supportsFloat16 = (device)=>{\n\t\treturn device.textureHalfFloatRenderable;\n};\nconst supportsFloat32 = (device)=>{\n\t\treturn device.textureFloatRenderable;\n};\nconst lightingSourcePixelFormat = (device)=>{\n\t\treturn supportsFloat16(device) ? PIXELFORMAT_RGBA16F : supportsFloat32(device) ? PIXELFORMAT_RGBA32F : PIXELFORMAT_RGBA8;\n};\nconst lightingPixelFormat = (device)=>{\n\t\treturn PIXELFORMAT_RGBA8;\n};\nconst createCubemap = (device, size, format, mipmaps)=>{\n\t\treturn new Texture(device, {\n\t\t\t\tname: `lighting-${size}`,\n\t\t\t\tcubemap: true,\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tformat: format,\n\t\t\t\ttype: TEXTURETYPE_RGBP ,\n\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\tmipmaps: false\n\t\t});\n};\nclass EnvLighting {\n\t\tstatic generateSkyboxCubemap(source, size) {\n\t\t\t\tconst device = source.device;\n\t\t\t\tconst result = createCubemap(device, size || (source.cubemap ? source.width : source.width / 4), PIXELFORMAT_RGBA8);\n\t\t\t\treprojectTexture(source, result, {\n\t\t\t\t\t\tnumSamples: 1024\n\t\t\t\t});\n\t\t\t\treturn result;\n\t\t}\n\t\tstatic generateLightingSource(source, options) {\n\t\t\t\tconst device = source.device;\n\t\t\t\tconst format = lightingSourcePixelFormat(device);\n\t\t\t\tconst result = options?.target || new Texture(device, {\n\t\t\t\t\t\tname: 'lighting-source',\n\t\t\t\t\t\tcubemap: true,\n\t\t\t\t\t\twidth: options?.size || 128,\n\t\t\t\t\t\theight: options?.size || 128,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\ttype: format === PIXELFORMAT_RGBA8 ? TEXTURETYPE_RGBP : TEXTURETYPE_DEFAULT,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tmipmaps: true\n\t\t\t\t});\n\t\t\t\treprojectTexture(source, result, {\n\t\t\t\t\t\tnumSamples: source.mipmaps ? 1 : 1024\n\t\t\t\t});\n\t\t\t\treturn result;\n\t\t}\n\t\tstatic generateAtlas(source, options) {\n\t\t\t\tconst device = source.device;\n\t\t\t\tconst format = lightingPixelFormat();\n\t\t\t\tconst result = options?.target || new Texture(device, {\n\t\t\t\t\t\tname: 'envAtlas',\n\t\t\t\t\t\twidth: options?.size || 512,\n\t\t\t\t\t\theight: options?.size || 512,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\ttype: TEXTURETYPE_RGBP ,\n\t\t\t\t\t\tprojection: TEXTUREPROJECTION_EQUIRECT,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tmipmaps: false\n\t\t\t\t});\n\t\t\t\tconst s = result.width / 512;\n\t\t\t\tconst rect = new Vec4(0, 0, 512 * s, 256 * s);\n\t\t\t\tconst levels = calcLevels(256) - calcLevels(4);\n\t\t\t\tfor(let i = 0; i < levels; ++i){\n\t\t\t\t\t\treprojectTexture(source, result, {\n\t\t\t\t\t\t\t\tnumSamples: 1,\n\t\t\t\t\t\t\t\trect: rect,\n\t\t\t\t\t\t\t\tseamPixels: s\n\t\t\t\t\t\t});\n\t\t\t\t\t\trect.x += rect.w;\n\t\t\t\t\t\trect.y += rect.w;\n\t\t\t\t\t\trect.z = Math.max(1, Math.floor(rect.z * 0.5));\n\t\t\t\t\t\trect.w = Math.max(1, Math.floor(rect.w * 0.5));\n\t\t\t\t}\n\t\t\t\trect.set(0, 256 * s, 256 * s, 128 * s);\n\t\t\t\tfor(let i = 1; i < 7; ++i){\n\t\t\t\t\t\treprojectTexture(source, result, {\n\t\t\t\t\t\t\t\tnumSamples: options?.numReflectionSamples || 1024,\n\t\t\t\t\t\t\t\tdistribution: options?.distribution || 'ggx',\n\t\t\t\t\t\t\t\tspecularPower: Math.max(1, 2048 >> i * 2),\n\t\t\t\t\t\t\t\trect: rect,\n\t\t\t\t\t\t\t\tseamPixels: s\n\t\t\t\t\t\t});\n\t\t\t\t\t\trect.y += rect.w;\n\t\t\t\t\t\trect.z = Math.max(1, Math.floor(rect.z * 0.5));\n\t\t\t\t\t\trect.w = Math.max(1, Math.floor(rect.w * 0.5));\n\t\t\t\t}\n\t\t\t\trect.set(128 * s, (256 + 128) * s, 64 * s, 32 * s);\n\t\t\t\treprojectTexture(source, result, {\n\t\t\t\t\t\tnumSamples: options?.numAmbientSamples || 2048,\n\t\t\t\t\t\tdistribution: 'lambert',\n\t\t\t\t\t\trect: rect,\n\t\t\t\t\t\tseamPixels: s\n\t\t\t\t});\n\t\t\t\treturn result;\n\t\t}\n\t\tstatic generatePrefilteredAtlas(sources, options) {\n\t\t\t\tconst device = sources[0].device;\n\t\t\t\tconst format = sources[0].format;\n\t\t\t\tconst type = sources[0].type;\n\t\t\t\tconst result = options?.target || new Texture(device, {\n\t\t\t\t\t\tname: 'envPrefilteredAtlas',\n\t\t\t\t\t\twidth: options?.size || 512,\n\t\t\t\t\t\theight: options?.size || 512,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\ttype: type,\n\t\t\t\t\t\tprojection: TEXTUREPROJECTION_EQUIRECT,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tmipmaps: false\n\t\t\t\t});\n\t\t\t\tconst s = result.width / 512;\n\t\t\t\tconst rect = new Vec4(0, 0, 512 * s, 256 * s);\n\t\t\t\tconst levels = calcLevels(512);\n\t\t\t\tfor(let i = 0; i < levels; ++i){\n\t\t\t\t\t\treprojectTexture(sources[0], result, {\n\t\t\t\t\t\t\t\tnumSamples: 1,\n\t\t\t\t\t\t\t\trect: rect,\n\t\t\t\t\t\t\t\tseamPixels: s\n\t\t\t\t\t\t});\n\t\t\t\t\t\trect.x += rect.w;\n\t\t\t\t\t\trect.y += rect.w;\n\t\t\t\t\t\trect.z = Math.max(1, Math.floor(rect.z * 0.5));\n\t\t\t\t\t\trect.w = Math.max(1, Math.floor(rect.w * 0.5));\n\t\t\t\t}\n\t\t\t\trect.set(0, 256 * s, 256 * s, 128 * s);\n\t\t\t\tfor(let i = 1; i < sources.length; ++i){\n\t\t\t\t\t\treprojectTexture(sources[i], result, {\n\t\t\t\t\t\t\t\tnumSamples: 1,\n\t\t\t\t\t\t\t\trect: rect,\n\t\t\t\t\t\t\t\tseamPixels: s\n\t\t\t\t\t\t});\n\t\t\t\t\t\trect.y += rect.w;\n\t\t\t\t\t\trect.z = Math.max(1, Math.floor(rect.z * 0.5));\n\t\t\t\t\t\trect.w = Math.max(1, Math.floor(rect.w * 0.5));\n\t\t\t\t}\n\t\t\t\trect.set(128 * s, (256 + 128) * s, 64 * s, 32 * s);\n\t\t\t\tif (options?.legacyAmbient) {\n\t\t\t\t\t\treprojectTexture(sources[5], result, {\n\t\t\t\t\t\t\t\tnumSamples: 1,\n\t\t\t\t\t\t\t\trect: rect,\n\t\t\t\t\t\t\t\tseamPixels: s\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\treprojectTexture(sources[0], result, {\n\t\t\t\t\t\t\t\tnumSamples: options?.numSamples || 2048,\n\t\t\t\t\t\t\t\tdistribution: 'lambert',\n\t\t\t\t\t\t\t\trect: rect,\n\t\t\t\t\t\t\t\tseamPixels: s\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n}\n\nclass FogParams {\n\t\tconstructor(){\n\t\t\t\tthis.type = FOG_NONE;\n\t\t\t\tthis.color = new Color(0, 0, 0);\n\t\t\t\tthis.density = 0;\n\t\t\t\tthis.start = 1;\n\t\t\t\tthis.end = 1000;\n\t\t}\n}\n\nclass Scene extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_SETLAYERS = 'set:layers';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SETSKYBOX = 'set:skybox';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PRERENDER = 'prerender';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_POSTRENDER = 'postrender';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PRERENDER_LAYER = 'prerender:layer';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_POSTRENDER_LAYER = 'postrender:layer';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PRECULL = 'precull';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_POSTCULL = 'postcull';\n\t\t}\n\t\tconstructor(graphicsDevice){\n\t\t\t\tsuper(), this.ambientBake = false, this.ambientBakeOcclusionBrightness = 0, this.ambientBakeOcclusionContrast = 0, this.ambientLight = new Color(0, 0, 0), this.ambientLuminance = 0, this.exposure = 1, this.lightmapSizeMultiplier = 1, this.lightmapMaxResolution = 2048, this.lightmapMode = BAKE_COLORDIR, this.lightmapFilterEnabled = false, this.lightmapHDR = false, this.root = null, this.physicalUnits = false, this._envAtlas = null, this._skyboxCubeMap = null, this._fogParams = new FogParams(), this.forcePassThroughSpecular = false;\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis._gravity = new Vec3(0, -9.8, 0);\n\t\t\t\tthis._layers = null;\n\t\t\t\tthis._prefilteredCubemaps = [];\n\t\t\t\tthis._internalEnvAtlas = null;\n\t\t\t\tthis._skyboxIntensity = 1;\n\t\t\t\tthis._skyboxLuminance = 0;\n\t\t\t\tthis._skyboxMip = 0;\n\t\t\t\tthis._skyboxHighlightMultiplier = 1;\n\t\t\t\tthis._skyboxRotationShaderInclude = false;\n\t\t\t\tthis._skyboxRotation = new Quat();\n\t\t\t\tthis._skyboxRotationMat3 = new Mat3();\n\t\t\t\tthis._skyboxRotationMat4 = new Mat4();\n\t\t\t\tthis._ambientBakeNumSamples = 1;\n\t\t\t\tthis._ambientBakeSpherePart = 0.4;\n\t\t\t\tthis._lightmapFilterRange = 10;\n\t\t\t\tthis._lightmapFilterSmoothness = 0.2;\n\t\t\t\tthis._clusteredLightingEnabled = true;\n\t\t\t\tthis._lightingParams = new LightingParams(this.device.supportsAreaLights, this.device.maxTextureSize, ()=>{\n\t\t\t\t\t\tthis.updateShaders = true;\n\t\t\t\t});\n\t\t\t\tthis._sky = new Sky(this);\n\t\t\t\tthis._stats = {\n\t\t\t\t\t\tmeshInstances: 0,\n\t\t\t\t\t\tlights: 0,\n\t\t\t\t\t\tdynamicLights: 0,\n\t\t\t\t\t\tbakedLights: 0,\n\t\t\t\t\t\tupdateShadersTime: 0\n\t\t\t\t};\n\t\t\t\tthis.updateShaders = true;\n\t\t\t\tthis._shaderVersion = 0;\n\t\t\t\tthis.immediate = new Immediate(this.device);\n\t\t}\n\t\tget defaultDrawLayer() {\n\t\t\t\treturn this.layers.getLayerById(LAYERID_IMMEDIATE);\n\t\t}\n\t\tset ambientBakeNumSamples(value) {\n\t\t\t\tthis._ambientBakeNumSamples = math.clamp(Math.floor(value), 1, 255);\n\t\t}\n\t\tget ambientBakeNumSamples() {\n\t\t\t\treturn this._ambientBakeNumSamples;\n\t\t}\n\t\tset ambientBakeSpherePart(value) {\n\t\t\t\tthis._ambientBakeSpherePart = math.clamp(value, 0.001, 1);\n\t\t}\n\t\tget ambientBakeSpherePart() {\n\t\t\t\treturn this._ambientBakeSpherePart;\n\t\t}\n\t\tset clusteredLightingEnabled(value) {\n\t\t\t\tif (this.device.isWebGPU && !value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._clusteredLightingEnabled && value) {\n\t\t\t\t\t\tconsole.error('Turning on disabled clustered lighting is not currently supported');\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._clusteredLightingEnabled = value;\n\t\t}\n\t\tget clusteredLightingEnabled() {\n\t\t\t\treturn this._clusteredLightingEnabled;\n\t\t}\n\t\tset envAtlas(value) {\n\t\t\t\tif (value !== this._envAtlas) {\n\t\t\t\t\t\tthis._envAtlas = value;\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\tvalue.addressU = ADDRESS_CLAMP_TO_EDGE;\n\t\t\t\t\t\t\t\tvalue.addressV = ADDRESS_CLAMP_TO_EDGE;\n\t\t\t\t\t\t\t\tvalue.minFilter = FILTER_LINEAR;\n\t\t\t\t\t\t\t\tvalue.magFilter = FILTER_LINEAR;\n\t\t\t\t\t\t\t\tvalue.mipmaps = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._prefilteredCubemaps = [];\n\t\t\t\t\t\tif (this._internalEnvAtlas) {\n\t\t\t\t\t\t\t\tthis._internalEnvAtlas.destroy();\n\t\t\t\t\t\t\t\tthis._internalEnvAtlas = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget envAtlas() {\n\t\t\t\treturn this._envAtlas;\n\t\t}\n\t\tset layers(layers) {\n\t\t\t\tconst prev = this._layers;\n\t\t\t\tthis._layers = layers;\n\t\t\t\tthis.fire('set:layers', prev, layers);\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tget sky() {\n\t\t\t\treturn this._sky;\n\t\t}\n\t\tget lighting() {\n\t\t\t\treturn this._lightingParams;\n\t\t}\n\t\tget fog() {\n\t\t\t\treturn this._fogParams;\n\t\t}\n\t\tset lightmapFilterRange(value) {\n\t\t\t\tthis._lightmapFilterRange = Math.max(value, 0.001);\n\t\t}\n\t\tget lightmapFilterRange() {\n\t\t\t\treturn this._lightmapFilterRange;\n\t\t}\n\t\tset lightmapFilterSmoothness(value) {\n\t\t\t\tthis._lightmapFilterSmoothness = Math.max(value, 0.001);\n\t\t}\n\t\tget lightmapFilterSmoothness() {\n\t\t\t\treturn this._lightmapFilterSmoothness;\n\t\t}\n\t\tset prefilteredCubemaps(value) {\n\t\t\t\tvalue = value || [];\n\t\t\t\tconst cubemaps = this._prefilteredCubemaps;\n\t\t\t\tconst changed = cubemaps.length !== value.length || cubemaps.some((c, i)=>c !== value[i]);\n\t\t\t\tif (changed) {\n\t\t\t\t\t\tconst complete = value.length === 6 && value.every((c)=>!!c);\n\t\t\t\t\t\tif (complete) {\n\t\t\t\t\t\t\t\tthis._internalEnvAtlas = EnvLighting.generatePrefilteredAtlas(value, {\n\t\t\t\t\t\t\t\t\t\ttarget: this._internalEnvAtlas\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tthis._envAtlas = this._internalEnvAtlas;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this._internalEnvAtlas) {\n\t\t\t\t\t\t\t\t\t\tthis._internalEnvAtlas.destroy();\n\t\t\t\t\t\t\t\t\t\tthis._internalEnvAtlas = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._envAtlas = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._prefilteredCubemaps = value.slice();\n\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget prefilteredCubemaps() {\n\t\t\t\treturn this._prefilteredCubemaps;\n\t\t}\n\t\tset skybox(value) {\n\t\t\t\tif (value !== this._skyboxCubeMap) {\n\t\t\t\t\t\tthis._skyboxCubeMap = value;\n\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget skybox() {\n\t\t\t\treturn this._skyboxCubeMap;\n\t\t}\n\t\tset skyboxIntensity(value) {\n\t\t\t\tif (value !== this._skyboxIntensity) {\n\t\t\t\t\t\tthis._skyboxIntensity = value;\n\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget skyboxIntensity() {\n\t\t\t\treturn this._skyboxIntensity;\n\t\t}\n\t\tset skyboxLuminance(value) {\n\t\t\t\tif (value !== this._skyboxLuminance) {\n\t\t\t\t\t\tthis._skyboxLuminance = value;\n\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget skyboxLuminance() {\n\t\t\t\treturn this._skyboxLuminance;\n\t\t}\n\t\tset skyboxMip(value) {\n\t\t\t\tif (value !== this._skyboxMip) {\n\t\t\t\t\t\tthis._skyboxMip = value;\n\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget skyboxMip() {\n\t\t\t\treturn this._skyboxMip;\n\t\t}\n\t\tset skyboxHighlightMultiplier(value) {\n\t\t\t\tif (value !== this._skyboxHighlightMultiplier) {\n\t\t\t\t\t\tthis._skyboxHighlightMultiplier = value;\n\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t}\n\t\t}\n\t\tget skyboxHighlightMultiplier() {\n\t\t\t\treturn this._skyboxHighlightMultiplier;\n\t\t}\n\t\tset skyboxRotation(value) {\n\t\t\t\tif (!this._skyboxRotation.equals(value)) {\n\t\t\t\t\t\tconst isIdentity = value.equals(Quat.IDENTITY);\n\t\t\t\t\t\tthis._skyboxRotation.copy(value);\n\t\t\t\t\t\tif (isIdentity) {\n\t\t\t\t\t\t\t\tthis._skyboxRotationMat3.setIdentity();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._skyboxRotationMat4.setTRS(Vec3.ZERO, value, Vec3.ONE);\n\t\t\t\t\t\t\t\tthis._skyboxRotationMat3.invertMat4(this._skyboxRotationMat4);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this._skyboxRotationShaderInclude && !isIdentity) {\n\t\t\t\t\t\t\t\tthis._skyboxRotationShaderInclude = true;\n\t\t\t\t\t\t\t\tthis._resetSkyMesh();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget skyboxRotation() {\n\t\t\t\treturn this._skyboxRotation;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._resetSkyMesh();\n\t\t\t\tthis.root = null;\n\t\t\t\tthis.off();\n\t\t}\n\t\tdrawLine(start, end, color = Color.WHITE, depthTest = true, layer = this.defaultDrawLayer) {\n\t\t\t\tconst batch = this.immediate.getBatch(layer, depthTest);\n\t\t\t\tbatch.addLines([\n\t\t\t\t\t\tstart,\n\t\t\t\t\t\tend\n\t\t\t\t], [\n\t\t\t\t\t\tcolor,\n\t\t\t\t\t\tcolor\n\t\t\t\t]);\n\t\t}\n\t\tdrawLines(positions, colors, depthTest = true, layer = this.defaultDrawLayer) {\n\t\t\t\tconst batch = this.immediate.getBatch(layer, depthTest);\n\t\t\t\tbatch.addLines(positions, colors);\n\t\t}\n\t\tdrawLineArrays(positions, colors, depthTest = true, layer = this.defaultDrawLayer) {\n\t\t\t\tconst batch = this.immediate.getBatch(layer, depthTest);\n\t\t\t\tbatch.addLinesArrays(positions, colors);\n\t\t}\n\t\tapplySettings(settings) {\n\t\t\t\tconst physics = settings.physics;\n\t\t\t\tconst render = settings.render;\n\t\t\t\tthis._gravity.set(physics.gravity[0], physics.gravity[1], physics.gravity[2]);\n\t\t\t\tthis.ambientLight.set(render.global_ambient[0], render.global_ambient[1], render.global_ambient[2]);\n\t\t\t\tthis.ambientLuminance = render.ambientLuminance;\n\t\t\t\tthis.fog.type = render.fog;\n\t\t\t\tthis.fog.color.set(render.fog_color[0], render.fog_color[1], render.fog_color[2]);\n\t\t\t\tthis.fog.start = render.fog_start;\n\t\t\t\tthis.fog.end = render.fog_end;\n\t\t\t\tthis.fog.density = render.fog_density;\n\t\t\t\tthis.lightmapSizeMultiplier = render.lightmapSizeMultiplier;\n\t\t\t\tthis.lightmapMaxResolution = render.lightmapMaxResolution;\n\t\t\t\tthis.lightmapMode = render.lightmapMode;\n\t\t\t\tthis.exposure = render.exposure;\n\t\t\t\tthis._skyboxIntensity = render.skyboxIntensity ?? 1;\n\t\t\t\tthis._skyboxLuminance = render.skyboxLuminance ?? 20000;\n\t\t\t\tthis._skyboxMip = render.skyboxMip ?? 0;\n\t\t\t\tif (render.skyboxRotation) {\n\t\t\t\t\t\tthis.skyboxRotation = new Quat().setFromEulerAngles(render.skyboxRotation[0], render.skyboxRotation[1], render.skyboxRotation[2]);\n\t\t\t\t}\n\t\t\t\tthis.sky.applySettings(render);\n\t\t\t\tthis.clusteredLightingEnabled = render.clusteredLightingEnabled ?? false;\n\t\t\t\tthis.lighting.applySettings(render);\n\t\t\t\t[\n\t\t\t\t\t\t'lightmapFilterEnabled',\n\t\t\t\t\t\t'lightmapFilterRange',\n\t\t\t\t\t\t'lightmapFilterSmoothness',\n\t\t\t\t\t\t'ambientBake',\n\t\t\t\t\t\t'ambientBakeNumSamples',\n\t\t\t\t\t\t'ambientBakeSpherePart',\n\t\t\t\t\t\t'ambientBakeOcclusionBrightness',\n\t\t\t\t\t\t'ambientBakeOcclusionContrast'\n\t\t\t\t].forEach((setting)=>{\n\t\t\t\t\t\tif (render.hasOwnProperty(setting)) {\n\t\t\t\t\t\t\t\tthis[setting] = render[setting];\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis._resetSkyMesh();\n\t\t}\n\t\t_getSkyboxTex() {\n\t\t\t\tconst cubemaps = this._prefilteredCubemaps;\n\t\t\t\tif (this._skyboxMip) {\n\t\t\t\t\t\tconst skyboxMapping = [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t3,\n\t\t\t\t\t\t\t\t4,\n\t\t\t\t\t\t\t\t5,\n\t\t\t\t\t\t\t\t6\n\t\t\t\t\t\t];\n\t\t\t\t\t\treturn cubemaps[skyboxMapping[this._skyboxMip]] || this._envAtlas || cubemaps[0] || this._skyboxCubeMap;\n\t\t\t\t}\n\t\t\t\treturn this._skyboxCubeMap || cubemaps[0] || this._envAtlas;\n\t\t}\n\t\t_updateSkyMesh() {\n\t\t\t\tif (!this.sky.skyMesh) {\n\t\t\t\t\t\tthis.sky.updateSkyMesh();\n\t\t\t\t}\n\t\t\t\tthis.sky.update();\n\t\t}\n\t\t_resetSkyMesh() {\n\t\t\t\tthis.sky.resetSkyMesh();\n\t\t\t\tthis.updateShaders = true;\n\t\t}\n\t\tsetSkybox(cubemaps) {\n\t\t\t\tif (!cubemaps) {\n\t\t\t\t\t\tthis.skybox = null;\n\t\t\t\t\t\tthis.envAtlas = null;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.skybox = cubemaps[0] || null;\n\t\t\t\t\t\tif (cubemaps[1] && !cubemaps[1].cubemap) {\n\t\t\t\t\t\t\t\tthis.envAtlas = cubemaps[1];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.prefilteredCubemaps = cubemaps.slice(1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget lightmapPixelFormat() {\n\t\t\t\treturn this.lightmapHDR && this.device.getRenderableHdrFormat() || PIXELFORMAT_RGBA8;\n\t\t}\n}\n\nclass Skin {\n\t\tconstructor(graphicsDevice, ibp, boneNames){\n\t\t\t\tthis.device = graphicsDevice;\n\t\t\t\tthis.inverseBindPose = ibp;\n\t\t\t\tthis.boneNames = boneNames;\n\t\t}\n}\n\nconst spriteNormals = [\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0,\n\t\t0,\n\t\t1,\n\t\t0,\n\t\t0,\n\t\t1\n];\nconst spriteIndices = [\n\t\t0,\n\t\t1,\n\t\t3,\n\t\t2,\n\t\t3,\n\t\t1\n];\nclass Sprite extends EventHandler {\n\t\tconstructor(device, options){\n\t\t\t\tsuper();\n\t\t\t\tthis._device = device;\n\t\t\t\tthis._pixelsPerUnit = options && options.pixelsPerUnit !== undefined ? options.pixelsPerUnit : 1;\n\t\t\t\tthis._renderMode = options && options.renderMode !== undefined ? options.renderMode : SPRITE_RENDERMODE_SIMPLE;\n\t\t\t\tthis._atlas = options && options.atlas !== undefined ? options.atlas : null;\n\t\t\t\tthis._frameKeys = options && options.frameKeys !== undefined ? options.frameKeys : null;\n\t\t\t\tthis._meshes = [];\n\t\t\t\tthis._updatingProperties = false;\n\t\t\t\tthis._meshesDirty = false;\n\t\t\t\tif (this._atlas && this._frameKeys) {\n\t\t\t\t\t\tthis._createMeshes();\n\t\t\t\t}\n\t\t}\n\t\tset frameKeys(value) {\n\t\t\t\tthis._frameKeys = value;\n\t\t\t\tif (this._atlas && this._frameKeys) {\n\t\t\t\t\t\tif (this._updatingProperties) {\n\t\t\t\t\t\t\t\tthis._meshesDirty = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._createMeshes();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.fire('set:frameKeys', value);\n\t\t}\n\t\tget frameKeys() {\n\t\t\t\treturn this._frameKeys;\n\t\t}\n\t\tset atlas(value) {\n\t\t\t\tif (value === this._atlas) return;\n\t\t\t\tif (this._atlas) {\n\t\t\t\t\t\tthis._atlas.off('set:frames', this._onSetFrames, this);\n\t\t\t\t\t\tthis._atlas.off('set:frame', this._onFrameChanged, this);\n\t\t\t\t\t\tthis._atlas.off('remove:frame', this._onFrameRemoved, this);\n\t\t\t\t}\n\t\t\t\tthis._atlas = value;\n\t\t\t\tif (this._atlas && this._frameKeys) {\n\t\t\t\t\t\tthis._atlas.on('set:frames', this._onSetFrames, this);\n\t\t\t\t\t\tthis._atlas.on('set:frame', this._onFrameChanged, this);\n\t\t\t\t\t\tthis._atlas.on('remove:frame', this._onFrameRemoved, this);\n\t\t\t\t\t\tif (this._updatingProperties) {\n\t\t\t\t\t\t\t\tthis._meshesDirty = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._createMeshes();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.fire('set:atlas', value);\n\t\t}\n\t\tget atlas() {\n\t\t\t\treturn this._atlas;\n\t\t}\n\t\tset pixelsPerUnit(value) {\n\t\t\t\tif (this._pixelsPerUnit === value) return;\n\t\t\t\tthis._pixelsPerUnit = value;\n\t\t\t\tthis.fire('set:pixelsPerUnit', value);\n\t\t\t\tif (this._atlas && this._frameKeys && this.renderMode === SPRITE_RENDERMODE_SIMPLE) {\n\t\t\t\t\t\tif (this._updatingProperties) {\n\t\t\t\t\t\t\t\tthis._meshesDirty = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._createMeshes();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget pixelsPerUnit() {\n\t\t\t\treturn this._pixelsPerUnit;\n\t\t}\n\t\tset renderMode(value) {\n\t\t\t\tif (this._renderMode === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst prev = this._renderMode;\n\t\t\t\tthis._renderMode = value;\n\t\t\t\tthis.fire('set:renderMode', value);\n\t\t\t\tif (prev === SPRITE_RENDERMODE_SIMPLE || value === SPRITE_RENDERMODE_SIMPLE) {\n\t\t\t\t\t\tif (this._atlas && this._frameKeys) {\n\t\t\t\t\t\t\t\tif (this._updatingProperties) {\n\t\t\t\t\t\t\t\t\t\tthis._meshesDirty = true;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._createMeshes();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget renderMode() {\n\t\t\t\treturn this._renderMode;\n\t\t}\n\t\tget meshes() {\n\t\t\t\treturn this._meshes;\n\t\t}\n\t\t_createMeshes() {\n\t\t\t\tconst len = this._meshes.length;\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tconst mesh = this._meshes[i];\n\t\t\t\t\t\tif (mesh) {\n\t\t\t\t\t\t\t\tmesh.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst count = this._frameKeys.length;\n\t\t\t\tthis._meshes = new Array(count);\n\t\t\t\tconst createMeshFunc = this.renderMode === SPRITE_RENDERMODE_SLICED || this._renderMode === SPRITE_RENDERMODE_TILED ? this._create9SliceMesh : this._createSimpleMesh;\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst frame = this._atlas.frames[this._frameKeys[i]];\n\t\t\t\t\t\tthis._meshes[i] = frame ? createMeshFunc.call(this, frame) : null;\n\t\t\t\t}\n\t\t\t\tthis.fire('set:meshes');\n\t\t}\n\t\t_createSimpleMesh(frame) {\n\t\t\t\tconst rect = frame.rect;\n\t\t\t\tconst texWidth = this._atlas.texture.width;\n\t\t\t\tconst texHeight = this._atlas.texture.height;\n\t\t\t\tconst w = rect.z / this._pixelsPerUnit;\n\t\t\t\tconst h = rect.w / this._pixelsPerUnit;\n\t\t\t\tconst hp = frame.pivot.x;\n\t\t\t\tconst vp = frame.pivot.y;\n\t\t\t\tconst positions = [\n\t\t\t\t\t\t-hp * w,\n\t\t\t\t\t\t-vp * h,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t(1 - hp) * w,\n\t\t\t\t\t\t-vp * h,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t(1 - hp) * w,\n\t\t\t\t\t\t(1 - vp) * h,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t-hp * w,\n\t\t\t\t\t\t(1 - vp) * h,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tconst lu = rect.x / texWidth;\n\t\t\t\tconst bv = 1.0 - rect.y / texHeight;\n\t\t\t\tconst ru = (rect.x + rect.z) / texWidth;\n\t\t\t\tconst tv = 1.0 - (rect.y + rect.w) / texHeight;\n\t\t\t\tconst uvs = [\n\t\t\t\t\t\tlu,\n\t\t\t\t\t\tbv,\n\t\t\t\t\t\tru,\n\t\t\t\t\t\tbv,\n\t\t\t\t\t\tru,\n\t\t\t\t\t\ttv,\n\t\t\t\t\t\tlu,\n\t\t\t\t\t\ttv\n\t\t\t\t];\n\t\t\t\tconst geom = new Geometry();\n\t\t\t\tgeom.positions = positions;\n\t\t\t\tgeom.normals = spriteNormals;\n\t\t\t\tgeom.uvs = uvs;\n\t\t\t\tgeom.indices = spriteIndices;\n\t\t\t\treturn Mesh.fromGeometry(this._device, geom);\n\t\t}\n\t\t_create9SliceMesh() {\n\t\t\t\tconst he = Vec2.ONE;\n\t\t\t\tconst ws = 3;\n\t\t\t\tconst ls = 3;\n\t\t\t\tconst positions = [];\n\t\t\t\tconst normals = [];\n\t\t\t\tconst uvs = [];\n\t\t\t\tconst indices = [];\n\t\t\t\tlet vcounter = 0;\n\t\t\t\tfor(let i = 0; i <= ws; i++){\n\t\t\t\t\t\tconst u = i === 0 || i === ws ? 0 : 1;\n\t\t\t\t\t\tfor(let j = 0; j <= ls; j++){\n\t\t\t\t\t\t\t\tconst x = -he.x + 2.0 * he.x * (i <= 1 ? 0 : 3) / ws;\n\t\t\t\t\t\t\t\tconst y = 0.0;\n\t\t\t\t\t\t\t\tconst z = -(-he.y + 2.0 * he.y * (j <= 1 ? 0 : 3) / ls);\n\t\t\t\t\t\t\t\tconst v = j === 0 || j === ls ? 0 : 1;\n\t\t\t\t\t\t\t\tpositions.push(-x, y, z);\n\t\t\t\t\t\t\t\tnormals.push(0.0, 1.0, 0.0);\n\t\t\t\t\t\t\t\tuvs.push(u, v);\n\t\t\t\t\t\t\t\tif (i < ws && j < ls) {\n\t\t\t\t\t\t\t\t\t\tindices.push(vcounter + ls + 1, vcounter + 1, vcounter);\n\t\t\t\t\t\t\t\t\t\tindices.push(vcounter + ls + 1, vcounter + ls + 2, vcounter + 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tvcounter++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst geom = new Geometry();\n\t\t\t\tgeom.positions = positions;\n\t\t\t\tgeom.normals = normals;\n\t\t\t\tgeom.uvs = uvs;\n\t\t\t\tgeom.indices = indices;\n\t\t\t\treturn Mesh.fromGeometry(this._device, geom);\n\t\t}\n\t\t_onSetFrames(frames) {\n\t\t\t\tif (this._updatingProperties) {\n\t\t\t\t\t\tthis._meshesDirty = true;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._createMeshes();\n\t\t\t\t}\n\t\t}\n\t\t_onFrameChanged(frameKey, frame) {\n\t\t\t\tconst idx = this._frameKeys.indexOf(frameKey);\n\t\t\t\tif (idx < 0) return;\n\t\t\t\tif (frame) {\n\t\t\t\t\t\tif (this.renderMode === SPRITE_RENDERMODE_SIMPLE) {\n\t\t\t\t\t\t\t\tthis._meshes[idx] = this._createSimpleMesh(frame);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._meshes[idx] = null;\n\t\t\t\t}\n\t\t\t\tthis.fire('set:meshes');\n\t\t}\n\t\t_onFrameRemoved(frameKey) {\n\t\t\t\tconst idx = this._frameKeys.indexOf(frameKey);\n\t\t\t\tif (idx < 0) return;\n\t\t\t\tthis._meshes[idx] = null;\n\t\t\t\tthis.fire('set:meshes');\n\t\t}\n\t\tstartUpdate() {\n\t\t\t\tthis._updatingProperties = true;\n\t\t\t\tthis._meshesDirty = false;\n\t\t}\n\t\tendUpdate() {\n\t\t\t\tthis._updatingProperties = false;\n\t\t\t\tif (this._meshesDirty && this._atlas && this._frameKeys) {\n\t\t\t\t\t\tthis._createMeshes();\n\t\t\t\t}\n\t\t\t\tthis._meshesDirty = false;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tfor (const mesh of this._meshes){\n\t\t\t\t\t\tif (mesh) {\n\t\t\t\t\t\t\t\tmesh.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._meshes.length = 0;\n\t\t}\n}\n\nclass TextureAtlas extends EventHandler {\n\t\tconstructor(){\n\t\t\t\tsuper();\n\t\t\t\tthis._texture = null;\n\t\t\t\tthis._frames = null;\n\t\t}\n\t\tset texture(value) {\n\t\t\t\tthis._texture = value;\n\t\t\t\tthis.fire('set:texture', value);\n\t\t}\n\t\tget texture() {\n\t\t\t\treturn this._texture;\n\t\t}\n\t\tset frames(value) {\n\t\t\t\tthis._frames = value;\n\t\t\t\tthis.fire('set:frames', value);\n\t\t}\n\t\tget frames() {\n\t\t\t\treturn this._frames;\n\t\t}\n\t\tsetFrame(key, data) {\n\t\t\t\tlet frame = this._frames[key];\n\t\t\t\tif (!frame) {\n\t\t\t\t\t\tframe = {\n\t\t\t\t\t\t\t\trect: data.rect.clone(),\n\t\t\t\t\t\t\t\tpivot: data.pivot.clone(),\n\t\t\t\t\t\t\t\tborder: data.border.clone()\n\t\t\t\t\t\t};\n\t\t\t\t\t\tthis._frames[key] = frame;\n\t\t\t\t} else {\n\t\t\t\t\t\tframe.rect.copy(data.rect);\n\t\t\t\t\t\tframe.pivot.copy(data.pivot);\n\t\t\t\t\t\tframe.border.copy(data.border);\n\t\t\t\t}\n\t\t\t\tthis.fire('set:frame', key.toString(), frame);\n\t\t}\n\t\tremoveFrame(key) {\n\t\t\t\tconst frame = this._frames[key];\n\t\t\t\tif (frame) {\n\t\t\t\t\t\tdelete this._frames[key];\n\t\t\t\t\t\tthis.fire('remove:frame', key.toString(), frame);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this._texture) {\n\t\t\t\t\t\tthis._texture.destroy();\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimationKey {\n\t\tconstructor(time, position, rotation, scale){\n\t\t\t\tthis.time = time;\n\t\t\t\tthis.position = position;\n\t\t\t\tthis.rotation = rotation;\n\t\t\t\tthis.scale = scale;\n\t\t}\n}\nclass AnimationNode {\n\t\tconstructor(){\n\t\t\t\tthis._name = '';\n\t\t\t\tthis._keys = [];\n\t\t}\n}\nclass Animation {\n\t\tconstructor(){\n\t\t\t\tthis.name = '';\n\t\t\t\tthis.duration = 0;\n\t\t\t\tthis._nodes = [];\n\t\t\t\tthis._nodeDict = {};\n\t\t}\n\t\tgetNode(name) {\n\t\t\t\treturn this._nodeDict[name];\n\t\t}\n\t\taddNode(node) {\n\t\t\t\tthis._nodes.push(node);\n\t\t\t\tthis._nodeDict[node._name] = node;\n\t\t}\n\t\tget nodes() {\n\t\t\t\treturn this._nodes;\n\t\t}\n}\n\nclass InterpolatedKey {\n\t\tconstructor(){\n\t\t\t\tthis._written = false;\n\t\t\t\tthis._name = '';\n\t\t\t\tthis._keyFrames = [];\n\t\t\t\tthis._quat = new Quat();\n\t\t\t\tthis._pos = new Vec3();\n\t\t\t\tthis._scale = new Vec3();\n\t\t\t\tthis._targetNode = null;\n\t\t}\n\t\tgetTarget() {\n\t\t\t\treturn this._targetNode;\n\t\t}\n\t\tsetTarget(node) {\n\t\t\t\tthis._targetNode = node;\n\t\t}\n}\nclass Skeleton {\n\t\tconstructor(graph){\n\t\t\t\tthis.looping = true;\n\t\t\t\tthis._animation = null;\n\t\t\t\tthis._time = 0;\n\t\t\t\tthis._interpolatedKeys = [];\n\t\t\t\tthis._interpolatedKeyDict = {};\n\t\t\t\tthis._currKeyIndices = {};\n\t\t\t\tthis.graph = null;\n\t\t\t\tconst addInterpolatedKeys = (node)=>{\n\t\t\t\t\t\tconst interpKey = new InterpolatedKey();\n\t\t\t\t\t\tinterpKey._name = node.name;\n\t\t\t\t\t\tthis._interpolatedKeys.push(interpKey);\n\t\t\t\t\t\tthis._interpolatedKeyDict[node.name] = interpKey;\n\t\t\t\t\t\tthis._currKeyIndices[node.name] = 0;\n\t\t\t\t\t\tfor(let i = 0; i < node._children.length; i++){\n\t\t\t\t\t\t\t\taddInterpolatedKeys(node._children[i]);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\taddInterpolatedKeys(graph);\n\t\t}\n\t\tset animation(value) {\n\t\t\t\tthis._animation = value;\n\t\t\t\tthis.currentTime = 0;\n\t\t}\n\t\tget animation() {\n\t\t\t\treturn this._animation;\n\t\t}\n\t\tset currentTime(value) {\n\t\t\t\tthis._time = value;\n\t\t\t\tconst numNodes = this._interpolatedKeys.length;\n\t\t\t\tfor(let i = 0; i < numNodes; i++){\n\t\t\t\t\t\tconst node = this._interpolatedKeys[i];\n\t\t\t\t\t\tconst nodeName = node._name;\n\t\t\t\t\t\tthis._currKeyIndices[nodeName] = 0;\n\t\t\t\t}\n\t\t\t\tthis.addTime(0);\n\t\t\t\tthis.updateGraph();\n\t\t}\n\t\tget currentTime() {\n\t\t\t\treturn this._time;\n\t\t}\n\t\tget numNodes() {\n\t\t\t\treturn this._interpolatedKeys.length;\n\t\t}\n\t\taddTime(delta) {\n\t\t\t\tif (this._animation !== null) {\n\t\t\t\t\t\tconst nodes = this._animation._nodes;\n\t\t\t\t\t\tconst duration = this._animation.duration;\n\t\t\t\t\t\tif (this._time === duration && !this.looping) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._time += delta;\n\t\t\t\t\t\tif (this._time > duration) {\n\t\t\t\t\t\t\t\tthis._time = this.looping ? 0.0 : duration;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < nodes.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst node = nodes[i];\n\t\t\t\t\t\t\t\t\t\tconst nodeName = node._name;\n\t\t\t\t\t\t\t\t\t\tthis._currKeyIndices[nodeName] = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (this._time < 0) {\n\t\t\t\t\t\t\t\tthis._time = this.looping ? duration : 0.0;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < nodes.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst node = nodes[i];\n\t\t\t\t\t\t\t\t\t\tconst nodeName = node._name;\n\t\t\t\t\t\t\t\t\t\tthis._currKeyIndices[nodeName] = node._keys.length - 2;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst offset = delta >= 0 ? 1 : -1;\n\t\t\t\t\t\tfor(let i = 0; i < nodes.length; i++){\n\t\t\t\t\t\t\t\tconst node = nodes[i];\n\t\t\t\t\t\t\t\tconst nodeName = node._name;\n\t\t\t\t\t\t\t\tconst keys = node._keys;\n\t\t\t\t\t\t\t\tconst interpKey = this._interpolatedKeyDict[nodeName];\n\t\t\t\t\t\t\t\tif (interpKey === undefined) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet foundKey = false;\n\t\t\t\t\t\t\t\tif (keys.length !== 1) {\n\t\t\t\t\t\t\t\t\t\tfor(let currKeyIndex = this._currKeyIndices[nodeName]; currKeyIndex < keys.length - 1 && currKeyIndex >= 0; currKeyIndex += offset){\n\t\t\t\t\t\t\t\t\t\t\t\tconst k1 = keys[currKeyIndex];\n\t\t\t\t\t\t\t\t\t\t\t\tconst k2 = keys[currKeyIndex + 1];\n\t\t\t\t\t\t\t\t\t\t\t\tif (k1.time <= this._time && k2.time >= this._time) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst alpha = (this._time - k1.time) / (k2.time - k1.time);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinterpKey._pos.lerp(k1.position, k2.position, alpha);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinterpKey._quat.slerp(k1.rotation, k2.rotation, alpha);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinterpKey._scale.lerp(k1.scale, k2.scale, alpha);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinterpKey._written = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._currKeyIndices[nodeName] = currKeyIndex;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfoundKey = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (keys.length === 1 || !foundKey && this._time === 0.0 && this.looping) {\n\t\t\t\t\t\t\t\t\t\tinterpKey._pos.copy(keys[0].position);\n\t\t\t\t\t\t\t\t\t\tinterpKey._quat.copy(keys[0].rotation);\n\t\t\t\t\t\t\t\t\t\tinterpKey._scale.copy(keys[0].scale);\n\t\t\t\t\t\t\t\t\t\tinterpKey._written = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tblend(skel1, skel2, alpha) {\n\t\t\t\tconst numNodes = this._interpolatedKeys.length;\n\t\t\t\tfor(let i = 0; i < numNodes; i++){\n\t\t\t\t\t\tconst key1 = skel1._interpolatedKeys[i];\n\t\t\t\t\t\tconst key2 = skel2._interpolatedKeys[i];\n\t\t\t\t\t\tconst dstKey = this._interpolatedKeys[i];\n\t\t\t\t\t\tif (key1._written && key2._written) {\n\t\t\t\t\t\t\t\tdstKey._quat.slerp(key1._quat, skel2._interpolatedKeys[i]._quat, alpha);\n\t\t\t\t\t\t\t\tdstKey._pos.lerp(key1._pos, skel2._interpolatedKeys[i]._pos, alpha);\n\t\t\t\t\t\t\t\tdstKey._scale.lerp(key1._scale, key2._scale, alpha);\n\t\t\t\t\t\t\t\tdstKey._written = true;\n\t\t\t\t\t\t} else if (key1._written) {\n\t\t\t\t\t\t\t\tdstKey._quat.copy(key1._quat);\n\t\t\t\t\t\t\t\tdstKey._pos.copy(key1._pos);\n\t\t\t\t\t\t\t\tdstKey._scale.copy(key1._scale);\n\t\t\t\t\t\t\t\tdstKey._written = true;\n\t\t\t\t\t\t} else if (key2._written) {\n\t\t\t\t\t\t\t\tdstKey._quat.copy(key2._quat);\n\t\t\t\t\t\t\t\tdstKey._pos.copy(key2._pos);\n\t\t\t\t\t\t\t\tdstKey._scale.copy(key2._scale);\n\t\t\t\t\t\t\t\tdstKey._written = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetGraph(graph) {\n\t\t\t\tthis.graph = graph;\n\t\t\t\tif (graph) {\n\t\t\t\t\t\tfor(let i = 0; i < this._interpolatedKeys.length; i++){\n\t\t\t\t\t\t\t\tconst interpKey = this._interpolatedKeys[i];\n\t\t\t\t\t\t\t\tconst graphNode = graph.findByName(interpKey._name);\n\t\t\t\t\t\t\t\tthis._interpolatedKeys[i].setTarget(graphNode);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(let i = 0; i < this._interpolatedKeys.length; i++){\n\t\t\t\t\t\t\t\tthis._interpolatedKeys[i].setTarget(null);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdateGraph() {\n\t\t\t\tif (this.graph) {\n\t\t\t\t\t\tfor(let i = 0; i < this._interpolatedKeys.length; i++){\n\t\t\t\t\t\t\t\tconst interpKey = this._interpolatedKeys[i];\n\t\t\t\t\t\t\t\tif (interpKey._written) {\n\t\t\t\t\t\t\t\t\t\tconst transform = interpKey.getTarget();\n\t\t\t\t\t\t\t\t\t\ttransform.localPosition.copy(interpKey._pos);\n\t\t\t\t\t\t\t\t\t\ttransform.localRotation.copy(interpKey._quat);\n\t\t\t\t\t\t\t\t\t\ttransform.localScale.copy(interpKey._scale);\n\t\t\t\t\t\t\t\t\t\tif (!transform._dirtyLocal) {\n\t\t\t\t\t\t\t\t\t\t\t\ttransform._dirtifyLocal();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tinterpKey._written = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass LitShaderOptions {\n\t\tconstructor(){\n\t\t\t\tthis.hasTangents = false;\n\t\t\t\tthis.shaderChunks = null;\n\t\t\t\tthis.pass = 0;\n\t\t\t\tthis.alphaTest = false;\n\t\t\t\tthis.blendType = BLEND_NONE;\n\t\t\t\tthis.separateAmbient = false;\n\t\t\t\tthis.screenSpace = false;\n\t\t\t\tthis.skin = false;\n\t\t\t\tthis.batch = false;\n\t\t\t\tthis.useInstancing = false;\n\t\t\t\tthis.useMorphPosition = false;\n\t\t\t\tthis.useMorphNormal = false;\n\t\t\t\tthis.useMorphTextureBasedInt = false;\n\t\t\t\tthis.nineSlicedMode = 0;\n\t\t\t\tthis.clusteredLightingEnabled = true;\n\t\t\t\tthis.clusteredLightingCookiesEnabled = false;\n\t\t\t\tthis.clusteredLightingShadowsEnabled = false;\n\t\t\t\tthis.clusteredLightingShadowType = 0;\n\t\t\t\tthis.clusteredLightingAreaLightsEnabled = false;\n\t\t\t\tthis.vertexColors = false;\n\t\t\t\tthis.lightMapEnabled = false;\n\t\t\t\tthis.dirLightMapEnabled = false;\n\t\t\t\tthis.useHeights = false;\n\t\t\t\tthis.useNormals = false;\n\t\t\t\tthis.useClearCoatNormals = false;\n\t\t\t\tthis.useAo = false;\n\t\t\t\tthis.diffuseMapEnabled = false;\n\t\t\t\tthis.pixelSnap = false;\n\t\t\t\tthis.ambientSH = false;\n\t\t\t\tthis.ssao = false;\n\t\t\t\tthis.twoSidedLighting = false;\n\t\t\t\tthis.occludeDirect = false;\n\t\t\t\tthis.occludeSpecular = 0;\n\t\t\t\tthis.occludeSpecularFloat = false;\n\t\t\t\tthis.useMsdf = false;\n\t\t\t\tthis.msdfTextAttribute = false;\n\t\t\t\tthis.alphaToCoverage = false;\n\t\t\t\tthis.opacityFadesSpecular = false;\n\t\t\t\tthis.opacityDither = DITHER_NONE;\n\t\t\t\tthis.opacityShadowDither = DITHER_NONE;\n\t\t\t\tthis.cubeMapProjection = 0;\n\t\t\t\tthis.useSpecular = false;\n\t\t\t\tthis.useSpecularityFactor = false;\n\t\t\t\tthis.enableGGXSpecular = false;\n\t\t\t\tthis.fresnelModel = 0;\n\t\t\t\tthis.useRefraction = false;\n\t\t\t\tthis.useClearCoat = false;\n\t\t\t\tthis.useSheen = false;\n\t\t\t\tthis.useIridescence = false;\n\t\t\t\tthis.useMetalness = false;\n\t\t\t\tthis.useDynamicRefraction = false;\n\t\t\t\tthis.dispersion = false;\n\t\t\t\tthis.fog = FOG_NONE;\n\t\t\t\tthis.gamma = GAMMA_NONE;\n\t\t\t\tthis.toneMap = -1;\n\t\t\t\tthis.reflectionSource = REFLECTIONSRC_NONE;\n\t\t\t\tthis.reflectionEncoding = null;\n\t\t\t\tthis.reflectionCubemapEncoding = null;\n\t\t\t\tthis.ambientSource = 'constant';\n\t\t\t\tthis.ambientEncoding = null;\n\t\t\t\tthis.skyboxIntensity = 1.0;\n\t\t\t\tthis.useCubeMapRotation = false;\n\t\t\t\tthis.lightMapWithoutAmbient = false;\n\t\t\t\tthis.lights = [];\n\t\t\t\tthis.noShadow = false;\n\t\t\t\tthis.lightMaskDynamic = 0x0;\n\t\t\t\tthis.userAttributes = {};\n\t\t\t\tthis.linearDepth = false;\n\t\t\t\tthis.shadowCatcher = false;\n\t\t}\n}\n\nclass LitMaterialOptionsBuilder {\n\t\tstatic update(litOptions, material, scene, renderParams, objDefs, pass, sortedLights) {\n\t\t\t\tLitMaterialOptionsBuilder.updateSharedOptions(litOptions, material, scene, objDefs, pass);\n\t\t\t\tLitMaterialOptionsBuilder.updateMaterialOptions(litOptions, material);\n\t\t\t\tLitMaterialOptionsBuilder.updateEnvOptions(litOptions, material, scene, renderParams);\n\t\t\t\tLitMaterialOptionsBuilder.updateLightingOptions(litOptions, material, scene, objDefs, sortedLights);\n\t\t}\n\t\tstatic updateSharedOptions(litOptions, material, scene, objDefs, pass) {\n\t\t\t\tlitOptions.shaderChunks = material.shaderChunks;\n\t\t\t\tlitOptions.pass = pass;\n\t\t\t\tlitOptions.alphaTest = material.alphaTest > 0;\n\t\t\t\tlitOptions.blendType = material.blendType;\n\t\t\t\tlitOptions.screenSpace = objDefs && (objDefs & SHADERDEF_SCREENSPACE) !== 0;\n\t\t\t\tlitOptions.skin = objDefs && (objDefs & SHADERDEF_SKIN) !== 0;\n\t\t\t\tlitOptions.useInstancing = objDefs && (objDefs & SHADERDEF_INSTANCING) !== 0;\n\t\t\t\tlitOptions.useMorphPosition = objDefs && (objDefs & SHADERDEF_MORPH_POSITION) !== 0;\n\t\t\t\tlitOptions.useMorphNormal = objDefs && (objDefs & SHADERDEF_MORPH_NORMAL) !== 0;\n\t\t\t\tlitOptions.useMorphTextureBasedInt = objDefs && (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0;\n\t\t\t\tlitOptions.hasTangents = objDefs && (objDefs & SHADERDEF_TANGENTS) !== 0;\n\t\t\t\tlitOptions.nineSlicedMode = material.nineSlicedMode || SPRITE_RENDERMODE_SIMPLE;\n\t\t\t\tif (material.useLighting && scene.clusteredLightingEnabled) {\n\t\t\t\t\t\tlitOptions.clusteredLightingEnabled = true;\n\t\t\t\t\t\tlitOptions.clusteredLightingCookiesEnabled = scene.lighting.cookiesEnabled;\n\t\t\t\t\t\tlitOptions.clusteredLightingShadowsEnabled = scene.lighting.shadowsEnabled;\n\t\t\t\t\t\tlitOptions.clusteredLightingShadowType = scene.lighting.shadowType;\n\t\t\t\t\t\tlitOptions.clusteredLightingAreaLightsEnabled = scene.lighting.areaLightsEnabled;\n\t\t\t\t} else {\n\t\t\t\t\t\tlitOptions.clusteredLightingEnabled = false;\n\t\t\t\t\t\tlitOptions.clusteredLightingCookiesEnabled = false;\n\t\t\t\t\t\tlitOptions.clusteredLightingShadowsEnabled = false;\n\t\t\t\t\t\tlitOptions.clusteredLightingAreaLightsEnabled = false;\n\t\t\t\t}\n\t\t}\n\t\tstatic updateMaterialOptions(litOptions, material) {\n\t\t\t\tlitOptions.separateAmbient = false;\n\t\t\t\tlitOptions.pixelSnap = material.pixelSnap;\n\t\t\t\tlitOptions.ambientSH = material.ambientSH;\n\t\t\t\tlitOptions.twoSidedLighting = material.twoSidedLighting;\n\t\t\t\tlitOptions.occludeDirect = material.occludeDirect;\n\t\t\t\tlitOptions.occludeSpecular = material.occludeSpecular;\n\t\t\t\tlitOptions.occludeSpecularFloat = material.occludeSpecularIntensity !== 1.0;\n\t\t\t\tlitOptions.useMsdf = false;\n\t\t\t\tlitOptions.msdfTextAttribute = false;\n\t\t\t\tlitOptions.alphaToCoverage = material.alphaToCoverage;\n\t\t\t\tlitOptions.opacityFadesSpecular = material.opacityFadesSpecular;\n\t\t\t\tlitOptions.opacityDither = material.opacityDither;\n\t\t\t\tlitOptions.cubeMapProjection = CUBEPROJ_NONE;\n\t\t\t\tlitOptions.useSpecular = material.hasSpecular;\n\t\t\t\tlitOptions.useSpecularityFactor = material.hasSpecularityFactor;\n\t\t\t\tlitOptions.enableGGXSpecular = material.ggxSpecular;\n\t\t\t\tlitOptions.fresnelModel = material.fresnelModel;\n\t\t\t\tlitOptions.useRefraction = material.hasRefraction;\n\t\t\t\tlitOptions.useClearCoat = material.hasClearCoat;\n\t\t\t\tlitOptions.useSheen = material.hasSheen;\n\t\t\t\tlitOptions.useIridescence = material.hasIrridescence;\n\t\t\t\tlitOptions.useMetalness = material.hasMetalness;\n\t\t\t\tlitOptions.useDynamicRefraction = material.dynamicRefraction;\n\t\t\t\tlitOptions.dispersion = material.dispersion > 0;\n\t\t\t\tlitOptions.vertexColors = false;\n\t\t\t\tlitOptions.lightMapEnabled = material.hasLighting;\n\t\t\t\tlitOptions.dirLightMapEnabled = material.dirLightMap;\n\t\t\t\tlitOptions.useHeights = material.hasHeights;\n\t\t\t\tlitOptions.useNormals = material.hasNormals;\n\t\t\t\tlitOptions.useClearCoatNormals = material.hasClearCoatNormals;\n\t\t\t\tlitOptions.useAo = material.hasAo;\n\t\t\t\tlitOptions.diffuseMapEnabled = material.hasDiffuseMap;\n\t\t}\n\t\tstatic updateEnvOptions(litOptions, material, scene, renderParams) {\n\t\t\t\tlitOptions.fog = material.useFog ? renderParams.fog : FOG_NONE;\n\t\t\t\tlitOptions.gamma = renderParams.shaderOutputGamma;\n\t\t\t\tlitOptions.toneMap = material.useTonemap ? renderParams.toneMapping : TONEMAP_NONE;\n\t\t\t\tif (material.useSkybox && scene.envAtlas && scene.skybox) {\n\t\t\t\t\t\tlitOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;\n\t\t\t\t\t\tlitOptions.reflectionEncoding = scene.envAtlas.encoding;\n\t\t\t\t\t\tlitOptions.reflectionCubemapEncoding = scene.skybox.encoding;\n\t\t\t\t} else if (material.useSkybox && scene.envAtlas) {\n\t\t\t\t\t\tlitOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;\n\t\t\t\t\t\tlitOptions.reflectionEncoding = scene.envAtlas.encoding;\n\t\t\t\t} else if (material.useSkybox && scene.skybox) {\n\t\t\t\t\t\tlitOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;\n\t\t\t\t\t\tlitOptions.reflectionEncoding = scene.skybox.encoding;\n\t\t\t\t} else {\n\t\t\t\t\t\tlitOptions.reflectionSource = REFLECTIONSRC_NONE;\n\t\t\t\t\t\tlitOptions.reflectionEncoding = null;\n\t\t\t\t}\n\t\t\t\tif (material.ambientSH) {\n\t\t\t\t\t\tlitOptions.ambientSource = AMBIENTSRC_AMBIENTSH;\n\t\t\t\t\t\tlitOptions.ambientEncoding = null;\n\t\t\t\t} else if (litOptions.reflectionSource !== REFLECTIONSRC_NONE && scene.envAtlas) {\n\t\t\t\t\t\tlitOptions.ambientSource = AMBIENTSRC_ENVALATLAS;\n\t\t\t\t\t\tlitOptions.ambientEncoding = scene.envAtlas.encoding;\n\t\t\t\t} else {\n\t\t\t\t\t\tlitOptions.ambientSource = AMBIENTSRC_CONSTANT;\n\t\t\t\t\t\tlitOptions.ambientEncoding = null;\n\t\t\t\t}\n\t\t\t\tconst hasSkybox = litOptions.reflectionSource !== REFLECTIONSRC_NONE;\n\t\t\t\tlitOptions.skyboxIntensity = hasSkybox;\n\t\t\t\tlitOptions.useCubeMapRotation = hasSkybox && scene._skyboxRotationShaderInclude;\n\t\t}\n\t\tstatic updateLightingOptions(litOptions, material, scene, objDefs, sortedLights) {\n\t\t\t\tlitOptions.lightMapWithoutAmbient = false;\n\t\t\t\tif (material.useLighting) {\n\t\t\t\t\t\tconst lightsFiltered = [];\n\t\t\t\t\t\tconst mask = objDefs ? objDefs >> 16 : MASK_AFFECT_DYNAMIC;\n\t\t\t\t\t\tlitOptions.lightMaskDynamic = !!(mask & MASK_AFFECT_DYNAMIC);\n\t\t\t\t\t\tlitOptions.lightMapWithoutAmbient = false;\n\t\t\t\t\t\tif (sortedLights) {\n\t\t\t\t\t\t\t\tLitMaterialOptionsBuilder.collectLights(LIGHTTYPE_DIRECTIONAL, sortedLights[LIGHTTYPE_DIRECTIONAL], lightsFiltered, mask);\n\t\t\t\t\t\t\t\tif (!scene.clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\tLitMaterialOptionsBuilder.collectLights(LIGHTTYPE_OMNI, sortedLights[LIGHTTYPE_OMNI], lightsFiltered, mask);\n\t\t\t\t\t\t\t\t\t\tLitMaterialOptionsBuilder.collectLights(LIGHTTYPE_SPOT, sortedLights[LIGHTTYPE_SPOT], lightsFiltered, mask);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlitOptions.lights = lightsFiltered;\n\t\t\t\t} else {\n\t\t\t\t\t\tlitOptions.lights = [];\n\t\t\t\t}\n\t\t\t\tif (litOptions.lights.length === 0 && !scene.clusteredLightingEnabled || (objDefs & SHADERDEF_NOSHADOW) !== 0) {\n\t\t\t\t\t\tlitOptions.noShadow = true;\n\t\t\t\t}\n\t\t}\n\t\tstatic collectLights(lType, lights, lightsFiltered, mask) {\n\t\t\t\tfor(let i = 0; i < lights.length; i++){\n\t\t\t\t\t\tconst light = lights[i];\n\t\t\t\t\t\tif (light.enabled) {\n\t\t\t\t\t\t\t\tif (light.mask & mask) {\n\t\t\t\t\t\t\t\t\t\tlightsFiltered.push(light);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nconst builtinAttributes = {\n\t\tvertex_normal: SEMANTIC_NORMAL,\n\t\tvertex_tangent: SEMANTIC_TANGENT,\n\t\tvertex_texCoord0: SEMANTIC_TEXCOORD0,\n\t\tvertex_texCoord1: SEMANTIC_TEXCOORD1,\n\t\tvertex_color: SEMANTIC_COLOR,\n\t\tvertex_boneWeights: SEMANTIC_BLENDWEIGHT,\n\t\tvertex_boneIndices: SEMANTIC_BLENDINDICES\n};\nclass LitShader {\n\t\tconstructor(device, options, allowWGSL = true){\n\t\t\t\tthis.varyingsCode = '';\n\t\t\t\tthis.vDefines = new Map();\n\t\t\t\tthis.fDefines = new Map();\n\t\t\t\tthis.includes = new Map();\n\t\t\t\tthis.chunks = null;\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.options = options;\n\t\t\t\tconst userChunks = options.shaderChunks;\n\t\t\t\tthis.shaderLanguage = device.isWebGPU && allowWGSL && userChunks?.useWGSL ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL;\n\t\t\t\tthis.attributes = {\n\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t};\n\t\t\t\tif (options.userAttributes) {\n\t\t\t\t\t\tfor (const [semantic, name] of Object.entries(options.userAttributes)){\n\t\t\t\t\t\t\t\tthis.attributes[name] = semantic;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst engineChunks = ShaderChunks.get(device, this.shaderLanguage);\n\t\t\t\tthis.chunks = new Map(engineChunks);\n\t\t\t\tif (userChunks) {\n\t\t\t\t\t\tconst userChunkMap = this.shaderLanguage === SHADERLANGUAGE_GLSL ? userChunks.glsl : userChunks.wgsl;\n\t\t\t\t\t\tuserChunkMap.forEach((chunk, chunkName)=>{\n\t\t\t\t\t\t\t\tfor(const a in builtinAttributes){\n\t\t\t\t\t\t\t\t\t\tif (builtinAttributes.hasOwnProperty(a) && chunk.indexOf(a) >= 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.attributes[a] = builtinAttributes[a];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.chunks.set(chunkName, chunk);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthis.shaderPassInfo = ShaderPass.get(this.device).getByIndex(options.pass);\n\t\t\t\tthis.shadowPass = this.shaderPassInfo.isShadow;\n\t\t\t\tthis.lighting = options.lights.length > 0 || options.dirLightMapEnabled || options.clusteredLightingEnabled;\n\t\t\t\tthis.reflections = options.reflectionSource !== REFLECTIONSRC_NONE;\n\t\t\t\tthis.needsNormal = this.lighting || this.reflections || options.useSpecular || options.ambientSH || options.useHeights || options.enableGGXSpecular || options.clusteredLightingEnabled && !this.shadowPass || options.useClearCoatNormals;\n\t\t\t\tthis.needsNormal = this.needsNormal && !this.shadowPass;\n\t\t\t\tthis.needsSceneColor = options.useDynamicRefraction;\n\t\t\t\tthis.needsScreenSize = options.useDynamicRefraction;\n\t\t\t\tthis.needsTransforms = options.useDynamicRefraction;\n\t\t\t\tthis.vshader = null;\n\t\t\t\tthis.fshader = null;\n\t\t}\n\t\tfDefineSet(condition, name, value = '') {\n\t\t\t\tif (condition) {\n\t\t\t\t\t\tthis.fDefines.set(name, value);\n\t\t\t\t}\n\t\t}\n\t\tgenerateVertexShader(useUv, useUnmodifiedUv, mapTransforms) {\n\t\t\t\tconst { options, vDefines, attributes } = this;\n\t\t\t\tconst varyings = new Map();\n\t\t\t\tvaryings.set('vPositionW', 'vec3');\n\t\t\t\tif (options.nineSlicedMode === SPRITE_RENDERMODE_SLICED || options.nineSlicedMode === SPRITE_RENDERMODE_TILED) {\n\t\t\t\t\t\tvDefines.set('NINESLICED', true);\n\t\t\t\t}\n\t\t\t\tif (this.options.linearDepth) {\n\t\t\t\t\t\tvDefines.set('LINEAR_DEPTH', true);\n\t\t\t\t\t\tvaryings.set('vLinearDepth', 'float');\n\t\t\t\t}\n\t\t\t\tif (this.needsNormal) vDefines.set('NORMALS', true);\n\t\t\t\tif (this.options.useInstancing) {\n\t\t\t\t\t\tconst languageChunks = ShaderChunks.get(this.device, this.shaderLanguage);\n\t\t\t\t\t\tif (this.chunks.get('transformInstancingVS') === languageChunks.get('transformInstancingVS')) {\n\t\t\t\t\t\t\t\tattributes.instance_line1 = SEMANTIC_ATTR11;\n\t\t\t\t\t\t\t\tattributes.instance_line2 = SEMANTIC_ATTR12;\n\t\t\t\t\t\t\t\tattributes.instance_line3 = SEMANTIC_ATTR14;\n\t\t\t\t\t\t\t\tattributes.instance_line4 = SEMANTIC_ATTR15;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.needsNormal) {\n\t\t\t\t\t\tattributes.vertex_normal = SEMANTIC_NORMAL;\n\t\t\t\t\t\tvaryings.set('vNormalW', 'vec3');\n\t\t\t\t\t\tif (options.hasTangents && (options.useHeights || options.useNormals || options.useClearCoatNormals || options.enableGGXSpecular)) {\n\t\t\t\t\t\t\t\tvDefines.set('TANGENTS', true);\n\t\t\t\t\t\t\t\tattributes.vertex_tangent = SEMANTIC_TANGENT;\n\t\t\t\t\t\t\t\tvaryings.set('vTangentW', 'vec3');\n\t\t\t\t\t\t\t\tvaryings.set('vBinormalW', 'vec3');\n\t\t\t\t\t\t} else if (options.enableGGXSpecular) {\n\t\t\t\t\t\t\t\tvDefines.set('GGX_SPECULAR', true);\n\t\t\t\t\t\t\t\tvaryings.set('vObjectSpaceUpW', 'vec3');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst maxUvSets = 2;\n\t\t\t\tfor(let i = 0; i < maxUvSets; i++){\n\t\t\t\t\t\tif (useUv[i]) {\n\t\t\t\t\t\t\t\tvDefines.set(`UV${i}`, true);\n\t\t\t\t\t\t\t\tattributes[`vertex_texCoord${i}`] = `TEXCOORD${i}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (useUnmodifiedUv[i]) {\n\t\t\t\t\t\t\t\tvDefines.set(`UV${i}_UNMODIFIED`, true);\n\t\t\t\t\t\t\t\tvaryings.set(`vUv${i}`, 'vec2');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet numTransforms = 0;\n\t\t\t\tconst transformDone = new Set();\n\t\t\t\tmapTransforms.forEach((mapTransform)=>{\n\t\t\t\t\t\tconst { id, uv, name } = mapTransform;\n\t\t\t\t\t\tconst checkId = id + uv * 100;\n\t\t\t\t\t\tif (!transformDone.has(checkId)) {\n\t\t\t\t\t\t\t\ttransformDone.add(checkId);\n\t\t\t\t\t\t\t\tvaryings.set(`vUV${uv}_${id}`, 'vec2');\n\t\t\t\t\t\t\t\tconst varName = `texture_${name}MapTransform`;\n\t\t\t\t\t\t\t\tvDefines.set(`{TRANSFORM_NAME_${numTransforms}}`, varName);\n\t\t\t\t\t\t\t\tvDefines.set(`{TRANSFORM_UV_${numTransforms}}`, uv);\n\t\t\t\t\t\t\t\tvDefines.set(`{TRANSFORM_ID_${numTransforms}}`, id);\n\t\t\t\t\t\t\t\tnumTransforms++;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tvDefines.set('UV_TRANSFORMS_COUNT', numTransforms);\n\t\t\t\tif (options.vertexColors) {\n\t\t\t\t\t\tattributes.vertex_color = SEMANTIC_COLOR;\n\t\t\t\t\t\tvDefines.set('VERTEX_COLOR', true);\n\t\t\t\t\t\tvaryings.set('vVertexColor', 'vec4');\n\t\t\t\t}\n\t\t\t\tif (options.useMsdf && options.msdfTextAttribute) {\n\t\t\t\t\t\tattributes.vertex_outlineParameters = SEMANTIC_ATTR8;\n\t\t\t\t\t\tattributes.vertex_shadowParameters = SEMANTIC_ATTR9;\n\t\t\t\t\t\tvDefines.set('MSDF', true);\n\t\t\t\t}\n\t\t\t\tif (options.useMorphPosition || options.useMorphNormal) {\n\t\t\t\t\t\tvDefines.set('MORPHING', true);\n\t\t\t\t\t\tif (options.useMorphTextureBasedInt) vDefines.set('MORPHING_INT', true);\n\t\t\t\t\t\tif (options.useMorphPosition) vDefines.set('MORPHING_POSITION', true);\n\t\t\t\t\t\tif (options.useMorphNormal) vDefines.set('MORPHING_NORMAL', true);\n\t\t\t\t\t\tattributes.morph_vertex_id = SEMANTIC_ATTR15;\n\t\t\t\t}\n\t\t\t\tif (options.skin) {\n\t\t\t\t\t\tattributes.vertex_boneIndices = SEMANTIC_BLENDINDICES;\n\t\t\t\t\t\tif (options.batch) {\n\t\t\t\t\t\t\t\tvDefines.set('BATCH', true);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tattributes.vertex_boneWeights = SEMANTIC_BLENDWEIGHT;\n\t\t\t\t\t\t\t\tvDefines.set('SKIN', true);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (options.useInstancing) vDefines.set('INSTANCING', true);\n\t\t\t\tif (options.screenSpace) vDefines.set('SCREENSPACE', true);\n\t\t\t\tif (options.pixelSnap) vDefines.set('PIXELSNAP', true);\n\t\t\t\tvaryings.forEach((type, name)=>{\n\t\t\t\t\t\tthis.varyingsCode += `#define VARYING_${name.toUpperCase()}\\n`;\n\t\t\t\t\t\tthis.varyingsCode += this.shaderLanguage === SHADERLANGUAGE_WGSL ? `varying ${name}: ${primitiveGlslToWgslTypeMap.get(type)};\\n` : `varying ${type} ${name};\\n`;\n\t\t\t\t});\n\t\t\t\tthis.includes.set('varyingsVS', this.varyingsCode);\n\t\t\t\tthis.includes.set('varyingsPS', this.varyingsCode);\n\t\t\t\tthis.vshader = `\n\t\t\t\t\t\t#include \"litMainVS\"\n\t\t\t\t`;\n\t\t}\n\t\t_setupLightingDefines(hasAreaLights, clusteredLightingEnabled) {\n\t\t\t\tconst fDefines = this.fDefines;\n\t\t\t\tconst options = this.options;\n\t\t\t\tthis.fDefines.set('LIGHT_COUNT', options.lights.length);\n\t\t\t\tif (hasAreaLights) fDefines.set('AREA_LIGHTS', true);\n\t\t\t\tif (clusteredLightingEnabled && this.lighting) {\n\t\t\t\t\t\tfDefines.set('LIT_CLUSTERED_LIGHTS', true);\n\t\t\t\t\t\tif (options.clusteredLightingCookiesEnabled) fDefines.set('CLUSTER_COOKIES', true);\n\t\t\t\t\t\tif (options.clusteredLightingAreaLightsEnabled) fDefines.set('CLUSTER_AREALIGHTS', true);\n\t\t\t\t\t\tif (options.lightMaskDynamic) fDefines.set('CLUSTER_MESH_DYNAMIC_LIGHTS', true);\n\t\t\t\t\t\tif (options.clusteredLightingShadowsEnabled && !options.noShadow) {\n\t\t\t\t\t\t\t\tconst clusteredShadowInfo = shadowTypeInfo.get(options.clusteredLightingShadowType);\n\t\t\t\t\t\t\t\tfDefines.set('CLUSTER_SHADOWS', true);\n\t\t\t\t\t\t\t\tfDefines.set(`SHADOW_KIND_${clusteredShadowInfo.kind}`, true);\n\t\t\t\t\t\t\t\tfDefines.set(`CLUSTER_SHADOW_TYPE_${clusteredShadowInfo.kind}`, true);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < options.lights.length; i++){\n\t\t\t\t\t\tconst light = options.lights[i];\n\t\t\t\t\t\tconst lightType = light._type;\n\t\t\t\t\t\tif (clusteredLightingEnabled && lightType !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst lightShape = hasAreaLights && light._shape ? light._shape : LIGHTSHAPE_PUNCTUAL;\n\t\t\t\t\t\tconst shadowType = light._shadowType;\n\t\t\t\t\t\tconst castShadow = light.castShadows && !options.noShadow;\n\t\t\t\t\t\tconst shadowInfo = shadowTypeInfo.get(shadowType);\n\t\t\t\t\t\tfDefines.set(`LIGHT${i}`, true);\n\t\t\t\t\t\tfDefines.set(`LIGHT${i}TYPE`, `${lightTypeNames[lightType]}`);\n\t\t\t\t\t\tfDefines.set(`LIGHT${i}SHADOWTYPE`, `${shadowInfo.name}`);\n\t\t\t\t\t\tfDefines.set(`LIGHT${i}SHAPE`, `${lightShapeNames[lightShape]}`);\n\t\t\t\t\t\tfDefines.set(`LIGHT${i}FALLOFF`, `${lightFalloffNames[light._falloffMode]}`);\n\t\t\t\t\t\tif (light.affectSpecularity) fDefines.set(`LIGHT${i}AFFECT_SPECULARITY`, true);\n\t\t\t\t\t\tif (light._cookie) {\n\t\t\t\t\t\t\t\tif (lightType === LIGHTTYPE_SPOT && !light._cookie._cubemap || lightType === LIGHTTYPE_OMNI && light._cookie._cubemap) {\n\t\t\t\t\t\t\t\t\t\tfDefines.set(`LIGHT${i}COOKIE`, true);\n\t\t\t\t\t\t\t\t\t\tfDefines.set(`{LIGHT${i}COOKIE_CHANNEL}`, light._cookieChannel);\n\t\t\t\t\t\t\t\t\t\tif (lightType === LIGHTTYPE_SPOT) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (light._cookieTransform) fDefines.set(`LIGHT${i}COOKIE_TRANSFORM`, true);\n\t\t\t\t\t\t\t\t\t\t\t\tif (light._cookieFalloff) fDefines.set(`LIGHT${i}COOKIE_FALLOFF`, true);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (castShadow) {\n\t\t\t\t\t\t\t\tfDefines.set(`LIGHT${i}CASTSHADOW`, true);\n\t\t\t\t\t\t\t\tif (shadowInfo.pcf) fDefines.set(`LIGHT${i}SHADOW_PCF`, true);\n\t\t\t\t\t\t\t\tif (light._normalOffsetBias && !light._isVsm) fDefines.set(`LIGHT${i}_SHADOW_SAMPLE_NORMAL_OFFSET`, true);\n\t\t\t\t\t\t\t\tif (lightType === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\t\t\tfDefines.set(`LIGHT${i}_SHADOW_SAMPLE_ORTHO`, true);\n\t\t\t\t\t\t\t\t\t\tif (light.cascadeBlend > 0) fDefines.set(`LIGHT${i}_SHADOW_CASCADE_BLEND`, true);\n\t\t\t\t\t\t\t\t\t\tif (light.numCascades > 1) fDefines.set(`LIGHT${i}_SHADOW_CASCADES`, true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (shadowInfo.pcf || shadowInfo.pcss || this.device.isWebGPU) fDefines.set(`LIGHT${i}_SHADOW_SAMPLE_SOURCE_ZBUFFER`, true);\n\t\t\t\t\t\t\t\tif (lightType === LIGHTTYPE_OMNI) fDefines.set(`LIGHT${i}_SHADOW_SAMPLE_POINT`, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (castShadow) {\n\t\t\t\t\t\t\t\tfDefines.set(`SHADOW_KIND_${shadowInfo.kind}`, true);\n\t\t\t\t\t\t\t\tif (lightType === LIGHTTYPE_DIRECTIONAL) fDefines.set('SHADOW_DIRECTIONAL', true);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tprepareForwardPass(lightingUv) {\n\t\t\t\tconst { options } = this;\n\t\t\t\tconst clusteredAreaLights = options.clusteredLightingEnabled && options.clusteredLightingAreaLightsEnabled;\n\t\t\t\tconst hasAreaLights = clusteredAreaLights || options.lights.some((light)=>{\n\t\t\t\t\t\treturn light._shape && light._shape !== LIGHTSHAPE_PUNCTUAL;\n\t\t\t\t});\n\t\t\t\tconst addAmbient = !options.lightMapEnabled || options.lightMapWithoutAmbient;\n\t\t\t\tconst hasTBN = this.needsNormal && (options.useNormals || options.useClearCoatNormals || options.enableGGXSpecular && !options.useHeights);\n\t\t\t\tif (options.useSpecular) {\n\t\t\t\t\t\tthis.fDefineSet(true, 'LIT_SPECULAR');\n\t\t\t\t\t\tthis.fDefineSet(this.reflections, 'LIT_REFLECTIONS');\n\t\t\t\t\t\tthis.fDefineSet(options.useClearCoat, 'LIT_CLEARCOAT');\n\t\t\t\t\t\tthis.fDefineSet(options.fresnelModel > 0, 'LIT_SPECULAR_FRESNEL');\n\t\t\t\t\t\tthis.fDefineSet(options.useSheen, 'LIT_SHEEN');\n\t\t\t\t\t\tthis.fDefineSet(options.useIridescence, 'LIT_IRIDESCENCE');\n\t\t\t\t}\n\t\t\t\tthis.fDefineSet(this.lighting && options.useSpecular || this.reflections, 'LIT_SPECULAR_OR_REFLECTION');\n\t\t\t\tthis.fDefineSet(this.needsSceneColor, 'LIT_SCENE_COLOR');\n\t\t\t\tthis.fDefineSet(this.needsScreenSize, 'LIT_SCREEN_SIZE');\n\t\t\t\tthis.fDefineSet(this.needsTransforms, 'LIT_TRANSFORMS');\n\t\t\t\tthis.fDefineSet(this.needsNormal, 'LIT_NEEDS_NORMAL');\n\t\t\t\tthis.fDefineSet(this.lighting, 'LIT_LIGHTING');\n\t\t\t\tthis.fDefineSet(options.useMetalness, 'LIT_METALNESS');\n\t\t\t\tthis.fDefineSet(options.enableGGXSpecular, 'LIT_GGX_SPECULAR');\n\t\t\t\tthis.fDefineSet(options.useSpecularityFactor, 'LIT_SPECULARITY_FACTOR');\n\t\t\t\tthis.fDefineSet(options.useCubeMapRotation, 'CUBEMAP_ROTATION');\n\t\t\t\tthis.fDefineSet(options.occludeSpecularFloat, 'LIT_OCCLUDE_SPECULAR_FLOAT');\n\t\t\t\tthis.fDefineSet(options.separateAmbient, 'LIT_SEPARATE_AMBIENT');\n\t\t\t\tthis.fDefineSet(options.twoSidedLighting, 'LIT_TWO_SIDED_LIGHTING');\n\t\t\t\tthis.fDefineSet(options.lightMapEnabled, 'LIT_LIGHTMAP');\n\t\t\t\tthis.fDefineSet(options.dirLightMapEnabled, 'LIT_DIR_LIGHTMAP');\n\t\t\t\tthis.fDefineSet(options.skyboxIntensity > 0, 'LIT_SKYBOX_INTENSITY');\n\t\t\t\tthis.fDefineSet(options.clusteredLightingShadowsEnabled, 'LIT_CLUSTERED_SHADOWS');\n\t\t\t\tthis.fDefineSet(options.clusteredLightingAreaLightsEnabled, 'LIT_CLUSTERED_AREA_LIGHTS');\n\t\t\t\tthis.fDefineSet(hasTBN, 'LIT_TBN');\n\t\t\t\tthis.fDefineSet(addAmbient, 'LIT_ADD_AMBIENT');\n\t\t\t\tthis.fDefineSet(options.hasTangents, 'LIT_TANGENTS');\n\t\t\t\tthis.fDefineSet(options.useNormals, 'LIT_USE_NORMALS');\n\t\t\t\tthis.fDefineSet(options.useClearCoatNormals, 'LIT_USE_CLEARCOAT_NORMALS');\n\t\t\t\tthis.fDefineSet(options.useRefraction, 'LIT_REFRACTION');\n\t\t\t\tthis.fDefineSet(options.useDynamicRefraction, 'LIT_DYNAMIC_REFRACTION');\n\t\t\t\tthis.fDefineSet(options.dispersion, 'LIT_DISPERSION');\n\t\t\t\tthis.fDefineSet(options.useHeights, 'LIT_HEIGHTS');\n\t\t\t\tthis.fDefineSet(options.opacityFadesSpecular, 'LIT_OPACITY_FADES_SPECULAR');\n\t\t\t\tthis.fDefineSet(options.alphaToCoverage, 'LIT_ALPHA_TO_COVERAGE');\n\t\t\t\tthis.fDefineSet(options.alphaTest, 'LIT_ALPHA_TEST');\n\t\t\t\tthis.fDefineSet(options.useMsdf, 'LIT_MSDF');\n\t\t\t\tthis.fDefineSet(options.ssao, 'LIT_SSAO');\n\t\t\t\tthis.fDefineSet(options.useAo, 'LIT_AO');\n\t\t\t\tthis.fDefineSet(options.occludeDirect, 'LIT_OCCLUDE_DIRECT');\n\t\t\t\tthis.fDefineSet(options.msdfTextAttribute, 'LIT_MSDF_TEXT_ATTRIBUTE');\n\t\t\t\tthis.fDefineSet(options.diffuseMapEnabled, 'LIT_DIFFUSE_MAP');\n\t\t\t\tthis.fDefineSet(options.shadowCatcher, 'LIT_SHADOW_CATCHER');\n\t\t\t\tthis.fDefineSet(true, 'LIT_FRESNEL_MODEL', fresnelNames[options.fresnelModel]);\n\t\t\t\tthis.fDefineSet(true, 'LIT_NONE_SLICE_MODE', spriteRenderModeNames[options.nineSlicedMode]);\n\t\t\t\tthis.fDefineSet(true, 'LIT_BLEND_TYPE', blendNames[options.blendType]);\n\t\t\t\tthis.fDefineSet(true, 'LIT_CUBEMAP_PROJECTION', cubemaProjectionNames[options.cubeMapProjection]);\n\t\t\t\tthis.fDefineSet(true, 'LIT_OCCLUDE_SPECULAR', specularOcclusionNames[options.occludeSpecular]);\n\t\t\t\tthis.fDefineSet(true, 'LIT_REFLECTION_SOURCE', reflectionSrcNames[options.reflectionSource]);\n\t\t\t\tthis.fDefineSet(true, 'LIT_AMBIENT_SOURCE', ambientSrcNames[options.ambientSource]);\n\t\t\t\tthis.fDefineSet(true, '{lightingUv}', lightingUv ?? '');\n\t\t\t\tthis.fDefineSet(true, '{reflectionDecode}', ChunkUtils.decodeFunc(options.reflectionEncoding));\n\t\t\t\tthis.fDefineSet(true, '{reflectionCubemapDecode}', ChunkUtils.decodeFunc(options.reflectionCubemapEncoding));\n\t\t\t\tthis.fDefineSet(true, '{ambientDecode}', ChunkUtils.decodeFunc(options.ambientEncoding));\n\t\t\t\tthis._setupLightingDefines(hasAreaLights, options.clusteredLightingEnabled);\n\t\t}\n\t\tprepareShadowPass() {\n\t\t\t\tconst { options } = this;\n\t\t\t\tconst lightType = this.shaderPassInfo.lightType;\n\t\t\t\tconst shadowType = this.shaderPassInfo.shadowType;\n\t\t\t\tconst shadowInfo = shadowTypeInfo.get(shadowType);\n\t\t\t\tconst usePerspectiveDepth = lightType === LIGHTTYPE_DIRECTIONAL || !shadowInfo.vsm && lightType === LIGHTTYPE_SPOT;\n\t\t\t\tthis.fDefineSet(usePerspectiveDepth, 'PERSPECTIVE_DEPTH');\n\t\t\t\tthis.fDefineSet(true, 'LIGHT_TYPE', `${lightTypeNames[lightType]}`);\n\t\t\t\tthis.fDefineSet(true, 'SHADOW_TYPE', `${shadowInfo.name}`);\n\t\t\t\tthis.fDefineSet(options.alphaTest, 'LIT_ALPHA_TEST');\n\t\t}\n\t\tgenerateFragmentShader(frontendDecl, frontendCode, lightingUv) {\n\t\t\t\tconst options = this.options;\n\t\t\t\tthis.includes.set('frontendDeclPS', frontendDecl ?? '');\n\t\t\t\tthis.includes.set('frontendCodePS', frontendCode ?? '');\n\t\t\t\tif (options.pass === SHADER_PICK || options.pass === SHADER_PREPASS) ; else if (this.shadowPass) {\n\t\t\t\t\t\tthis.prepareShadowPass();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.prepareForwardPass(lightingUv);\n\t\t\t\t}\n\t\t\t\tthis.fshader = `\n\t\t\t\t\t\t#include \"litMainPS\"\n\t\t\t\t`;\n\t\t}\n}\n\nconst LitOptionsUtils = {\n\t\tgenerateKey (options) {\n\t\t\t\treturn `lit${Object.keys(options).sort().map((key)=>{\n\t\t\t\t\t\tif (key === 'shaderChunks') {\n\t\t\t\t\t\t\t\treturn options.shaderChunks?.key ?? '';\n\t\t\t\t\t\t} else if (key === 'lights') {\n\t\t\t\t\t\t\t\treturn LitOptionsUtils.generateLightsKey(options);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn key + options[key];\n\t\t\t\t}).join('\\n')}`;\n\t\t},\n\t\tgenerateLightsKey (options) {\n\t\t\t\treturn `lights:${options.lights.map((light)=>{\n\t\t\t\t\t\treturn !options.clusteredLightingEnabled || light._type === LIGHTTYPE_DIRECTIONAL ? `${light.key},` : '';\n\t\t\t\t}).join('')}`;\n\t\t}\n};\n\nclass StandardMaterialOptions {\n\t\tget pass() {\n\t\t\t\treturn this.litOptions.pass;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.defines = new Map();\n\t\t\t\tthis.forceUv1 = false;\n\t\t\t\tthis.specularTint = false;\n\t\t\t\tthis.metalnessTint = false;\n\t\t\t\tthis.glossTint = false;\n\t\t\t\tthis.emissiveEncoding = 'linear';\n\t\t\t\tthis.lightMapEncoding = 'linear';\n\t\t\t\tthis.packedNormal = false;\n\t\t\t\tthis.normalDetailPackedNormal = false;\n\t\t\t\tthis.clearCoatPackedNormal = false;\n\t\t\t\tthis.glossInvert = false;\n\t\t\t\tthis.sheenGlossInvert = false;\n\t\t\t\tthis.clearCoatGlossInvert = false;\n\t\t\t\tthis.useAO = false;\n\t\t\t\tthis.litOptions = new LitShaderOptions();\n\t\t}\n}\n\nconst _matTex2D = [];\nconst buildPropertiesList = (options)=>{\n\t\treturn Object.keys(options).filter((key)=>key !== 'litOptions').sort();\n};\nclass ShaderGeneratorStandard extends ShaderGenerator {\n\t\tgenerateKey(options) {\n\t\t\t\tlet props;\n\t\t\t\tif (options === this.optionsContextMin) {\n\t\t\t\t\t\tif (!this.propsMin) this.propsMin = buildPropertiesList(options);\n\t\t\t\t\t\tprops = this.propsMin;\n\t\t\t\t} else if (options === this.optionsContext) {\n\t\t\t\t\t\tif (!this.props) this.props = buildPropertiesList(options);\n\t\t\t\t\t\tprops = this.props;\n\t\t\t\t} else {\n\t\t\t\t\t\tprops = buildPropertiesList(options);\n\t\t\t\t}\n\t\t\t\tconst definesHash = ShaderGenerator.definesHash(options.defines);\n\t\t\t\tconst key = `standard:\\n${definesHash}\\n${props.map((prop)=>prop + options[prop]).join('\\n')}${LitOptionsUtils.generateKey(options.litOptions)}`;\n\t\t\t\treturn key;\n\t\t}\n\t\t_getUvSourceExpression(transformPropName, uVPropName, options) {\n\t\t\t\tconst transformId = options[transformPropName];\n\t\t\t\tconst uvChannel = options[uVPropName];\n\t\t\t\tconst isMainPass = options.litOptions.pass === SHADER_FORWARD;\n\t\t\t\tlet expression;\n\t\t\t\tif (isMainPass && options.litOptions.nineSlicedMode === SPRITE_RENDERMODE_SLICED) {\n\t\t\t\t\t\texpression = 'nineSlicedUv';\n\t\t\t\t} else if (isMainPass && options.litOptions.nineSlicedMode === SPRITE_RENDERMODE_TILED) {\n\t\t\t\t\t\texpression = 'nineSlicedUv';\n\t\t\t\t} else {\n\t\t\t\t\t\tif (transformId === 0) {\n\t\t\t\t\t\t\t\texpression = `vUv${uvChannel}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\texpression = `vUV${uvChannel}_${transformId}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.heightMap && transformPropName !== 'heightMapTransform') {\n\t\t\t\t\t\t\t\texpression += ' + dUvOffset';\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn expression;\n\t\t}\n\t\t_validateMapChunk(propName, chunkName, chunks) {}\n\t\t_addMapDefines(fDefines, propName, chunkName, options, chunks, mapping, encoding = null) {\n\t\t\t\tconst mapPropName = `${propName}Map`;\n\t\t\t\tconst propNameCaps = propName.toUpperCase();\n\t\t\t\tconst uVPropName = `${mapPropName}Uv`;\n\t\t\t\tconst identifierPropName = `${mapPropName}Identifier`;\n\t\t\t\tconst transformPropName = `${mapPropName}Transform`;\n\t\t\t\tconst channelPropName = `${mapPropName}Channel`;\n\t\t\t\tconst vertexColorChannelPropName = `${propName}VertexColorChannel`;\n\t\t\t\tconst tintPropName = `${propName}Tint`;\n\t\t\t\tconst vertexColorPropName = `${propName}VertexColor`;\n\t\t\t\tconst detailModePropName = `${propName}Mode`;\n\t\t\t\tconst invertName = `${propName}Invert`;\n\t\t\t\tconst tintOption = options[tintPropName];\n\t\t\t\tconst vertexColorOption = options[vertexColorPropName];\n\t\t\t\tconst textureOption = options[mapPropName];\n\t\t\t\tconst textureIdentifier = options[identifierPropName];\n\t\t\t\tconst detailModeOption = options[detailModePropName];\n\t\t\t\tconst chunkCode = chunks.get(chunkName);\n\t\t\t\tif (textureOption) {\n\t\t\t\t\t\tfDefines.set(`STD_${propNameCaps}_TEXTURE`, '');\n\t\t\t\t\t\tconst uv = this._getUvSourceExpression(transformPropName, uVPropName, options);\n\t\t\t\t\t\tfDefines.set(`{STD_${propNameCaps}_TEXTURE_UV}`, uv);\n\t\t\t\t\t\tfDefines.set(`{STD_${propNameCaps}_TEXTURE_CHANNEL}`, options[channelPropName]);\n\t\t\t\t\t\tconst textureId = `{STD_${propNameCaps}_TEXTURE_NAME}`;\n\t\t\t\t\t\tif (chunkCode.includes(textureId)) {\n\t\t\t\t\t\t\t\tlet samplerName = `texture_${mapPropName}`;\n\t\t\t\t\t\t\t\tconst alias = mapping[textureIdentifier];\n\t\t\t\t\t\t\t\tif (alias) {\n\t\t\t\t\t\t\t\t\t\tsamplerName = alias;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmapping[textureIdentifier] = samplerName;\n\t\t\t\t\t\t\t\t\t\tfDefines.set(`STD_${propNameCaps}_TEXTURE_ALLOCATE`, '');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfDefines.set(textureId, samplerName);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (encoding) {\n\t\t\t\t\t\t\t\tconst textureDecode = options[channelPropName] === 'aaa' ? 'passThrough' : ChunkUtils.decodeFunc(encoding);\n\t\t\t\t\t\t\t\tfDefines.set(`{STD_${propNameCaps}_TEXTURE_DECODE}`, textureDecode);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (vertexColorOption) {\n\t\t\t\t\t\tfDefines.set(`STD_${propNameCaps}_VERTEX`, '');\n\t\t\t\t\t\tfDefines.set(`{STD_${propNameCaps}_VERTEX_CHANNEL}`, options[vertexColorChannelPropName]);\n\t\t\t\t}\n\t\t\t\tif (detailModeOption) {\n\t\t\t\t\t\tfDefines.set(`{STD_${propNameCaps}_DETAILMODE}`, detailModeOption);\n\t\t\t\t}\n\t\t\t\tif (tintOption) {\n\t\t\t\t\t\tfDefines.set(`STD_${propNameCaps}_CONSTANT`, '');\n\t\t\t\t}\n\t\t\t\tif (!!options[invertName]) {\n\t\t\t\t\t\tfDefines.set(`STD_${propNameCaps}_INVERT`, '');\n\t\t\t\t}\n\t\t}\n\t\t_correctChannel(p, chan, _matTex2D) {\n\t\t\t\tif (_matTex2D[p] > 0) {\n\t\t\t\t\t\tif (_matTex2D[p] < chan.length) {\n\t\t\t\t\t\t\t\treturn chan.substring(0, _matTex2D[p]);\n\t\t\t\t\t\t} else if (_matTex2D[p] > chan.length) {\n\t\t\t\t\t\t\t\tlet str = chan;\n\t\t\t\t\t\t\t\tconst chr = str.charAt(str.length - 1);\n\t\t\t\t\t\t\t\tconst addLen = _matTex2D[p] - str.length;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < addLen; i++)str += chr;\n\t\t\t\t\t\t\t\treturn str;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn chan;\n\t\t\t\t}\n\t\t}\n\t\tcreateVertexShader(litShader, options) {\n\t\t\t\tconst useUv = [];\n\t\t\t\tconst useUnmodifiedUv = [];\n\t\t\t\tconst mapTransforms = [];\n\t\t\t\tconst maxUvSets = 2;\n\t\t\t\tfor(const p in _matTex2D){\n\t\t\t\t\t\tconst mapName = `${p}Map`;\n\t\t\t\t\t\tif (options[`${p}VertexColor`]) {\n\t\t\t\t\t\t\t\tconst colorChannelName = `${p}VertexColorChannel`;\n\t\t\t\t\t\t\t\toptions[colorChannelName] = this._correctChannel(p, options[colorChannelName], _matTex2D);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options[mapName]) {\n\t\t\t\t\t\t\t\tconst channelName = `${mapName}Channel`;\n\t\t\t\t\t\t\t\tconst transformName = `${mapName}Transform`;\n\t\t\t\t\t\t\t\tconst uvName = `${mapName}Uv`;\n\t\t\t\t\t\t\t\toptions[uvName] = Math.min(options[uvName], maxUvSets - 1);\n\t\t\t\t\t\t\t\toptions[channelName] = this._correctChannel(p, options[channelName], _matTex2D);\n\t\t\t\t\t\t\t\tconst uvSet = options[uvName];\n\t\t\t\t\t\t\t\tuseUv[uvSet] = true;\n\t\t\t\t\t\t\t\tuseUnmodifiedUv[uvSet] = useUnmodifiedUv[uvSet] || options[mapName] && !options[transformName];\n\t\t\t\t\t\t\t\tif (options[transformName]) {\n\t\t\t\t\t\t\t\t\t\tmapTransforms.push({\n\t\t\t\t\t\t\t\t\t\t\t\tname: p,\n\t\t\t\t\t\t\t\t\t\t\t\tid: options[transformName],\n\t\t\t\t\t\t\t\t\t\t\t\tuv: options[uvName]\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (options.forceUv1) {\n\t\t\t\t\t\tuseUv[1] = true;\n\t\t\t\t\t\tuseUnmodifiedUv[1] = useUnmodifiedUv[1] !== undefined ? useUnmodifiedUv[1] : true;\n\t\t\t\t}\n\t\t\t\tlitShader.generateVertexShader(useUv, useUnmodifiedUv, mapTransforms);\n\t\t}\n\t\tprepareFragmentDefines(options, fDefines, shaderPassInfo) {\n\t\t\t\tconst fDefineSet = (condition, name, value = '')=>{\n\t\t\t\t\t\tif (condition) {\n\t\t\t\t\t\t\t\tfDefines.set(name, value);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tfDefineSet(options.lightMap, 'STD_LIGHTMAP', '');\n\t\t\t\tfDefineSet(options.lightVertexColor, 'STD_LIGHT_VERTEX_COLOR', '');\n\t\t\t\tfDefineSet(options.dirLightMap && options.litOptions.useSpecular, 'STD_LIGHTMAP_DIR', '');\n\t\t\t\tfDefineSet(options.heightMap, 'STD_HEIGHT_MAP', '');\n\t\t\t\tfDefineSet(options.useSpecularColor, 'STD_SPECULAR_COLOR', '');\n\t\t\t\tfDefineSet(options.aoMap || options.aoVertexColor || options.useAO, 'STD_AO', '');\n\t\t\t\tfDefineSet(true, 'STD_OPACITY_DITHER', ditherNames[shaderPassInfo.isForward ? options.litOptions.opacityDither : options.litOptions.opacityShadowDither]);\n\t\t}\n\t\tcreateShaderDefinition(device, options) {\n\t\t\t\tconst shaderPassInfo = ShaderPass.get(device).getByIndex(options.litOptions.pass);\n\t\t\t\tconst isForwardPass = shaderPassInfo.isForward;\n\t\t\t\tconst litShader = new LitShader(device, options.litOptions);\n\t\t\t\tthis.createVertexShader(litShader, options);\n\t\t\t\tconst textureMapping = {};\n\t\t\t\toptions.litOptions.fresnelModel = options.litOptions.fresnelModel === 0 ? FRESNEL_SCHLICK : options.litOptions.fresnelModel;\n\t\t\t\tconst fDefines = litShader.fDefines;\n\t\t\t\tthis.prepareFragmentDefines(options, fDefines, shaderPassInfo);\n\t\t\t\tlet lightingUv = '';\n\t\t\t\tif (isForwardPass) {\n\t\t\t\t\t\tif (options.heightMap) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'height', 'parallaxPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.litOptions.blendType !== BLEND_NONE || options.litOptions.alphaTest || options.litOptions.alphaToCoverage || options.litOptions.opacityDither !== DITHER_NONE) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'opacity', 'opacityPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (litShader.needsNormal) {\n\t\t\t\t\t\t\t\tif (options.normalMap || options.clearCoatNormalMap) {\n\t\t\t\t\t\t\t\t\t\tif (!options.litOptions.hasTangents) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst baseName = options.normalMap ? 'normalMap' : 'clearCoatNormalMap';\n\t\t\t\t\t\t\t\t\t\t\t\tlightingUv = this._getUvSourceExpression(`${baseName}Transform`, `${baseName}Uv`, options);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'normalDetail', 'normalMapPS', options, litShader.chunks, textureMapping, options.normalDetailPackedNormal ? 'xy' : 'xyz');\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'normal', 'normalMapPS', options, litShader.chunks, textureMapping, options.packedNormal ? 'xy' : 'xyz');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.diffuseDetail) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'diffuseDetail', 'diffusePS', options, litShader.chunks, textureMapping, options.diffuseDetailEncoding);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._addMapDefines(fDefines, 'diffuse', 'diffusePS', options, litShader.chunks, textureMapping, options.diffuseEncoding);\n\t\t\t\t\t\tif (options.litOptions.useRefraction) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'refraction', 'transmissionPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'thickness', 'thicknessPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.litOptions.useIridescence) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'iridescence', 'iridescencePS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'iridescenceThickness', 'iridescenceThicknessPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (litShader.lighting && options.litOptions.useSpecular || litShader.reflections) {\n\t\t\t\t\t\t\t\tif (options.litOptions.useSheen) {\n\t\t\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'sheen', 'sheenPS', options, litShader.chunks, textureMapping, options.sheenEncoding);\n\t\t\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'sheenGloss', 'sheenGlossPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.litOptions.useMetalness) {\n\t\t\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'metalness', 'metalnessPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'ior', 'iorPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.litOptions.useSpecularityFactor) {\n\t\t\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'specularityFactor', 'specularityFactorPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.useSpecularColor) {\n\t\t\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'specular', 'specularPS', options, litShader.chunks, textureMapping, options.specularEncoding);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'gloss', 'glossPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.aoDetail) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'aoDetail', 'aoPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.aoMap || options.aoVertexColor || options.useAO) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'ao', 'aoPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._addMapDefines(fDefines, 'emissive', 'emissivePS', options, litShader.chunks, textureMapping, options.emissiveEncoding);\n\t\t\t\t\t\tif (options.litOptions.useClearCoat) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'clearCoat', 'clearCoatPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'clearCoatGloss', 'clearCoatGlossPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'clearCoatNormal', 'clearCoatNormalPS', options, litShader.chunks, textureMapping, options.clearCoatPackedNormal ? 'xy' : 'xyz');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.litOptions.enableGGXSpecular) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'anisotropy', 'anisotropyPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.lightMap || options.lightVertexColor) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'light', 'lightmapPS', options, litShader.chunks, textureMapping, options.lightMapEncoding);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst opacityShadowDither = options.litOptions.opacityShadowDither;\n\t\t\t\t\t\tif (options.litOptions.alphaTest || opacityShadowDither) {\n\t\t\t\t\t\t\t\tthis._addMapDefines(fDefines, 'opacity', 'opacityPS', options, litShader.chunks, textureMapping);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlitShader.generateFragmentShader(litShader.chunks.get('stdDeclarationPS'), litShader.chunks.get('stdFrontEndPS'), lightingUv);\n\t\t\t\tconst includes = MapUtils.merge(litShader.chunks, litShader.includes);\n\t\t\t\tconst vDefines = litShader.vDefines;\n\t\t\t\toptions.defines.forEach((value, key)=>vDefines.set(key, value));\n\t\t\t\toptions.defines.forEach((value, key)=>fDefines.set(key, value));\n\t\t\t\tconst definition = ShaderDefinitionUtils.createDefinition(device, {\n\t\t\t\t\t\tname: 'StandardShader',\n\t\t\t\t\t\tattributes: litShader.attributes,\n\t\t\t\t\t\tshaderLanguage: litShader.shaderLanguage,\n\t\t\t\t\t\tvertexCode: litShader.vshader,\n\t\t\t\t\t\tfragmentCode: litShader.fshader,\n\t\t\t\t\t\tvertexIncludes: includes,\n\t\t\t\t\t\tfragmentIncludes: includes,\n\t\t\t\t\t\tfragmentDefines: fDefines,\n\t\t\t\t\t\tvertexDefines: vDefines\n\t\t\t\t});\n\t\t\t\tif (litShader.shaderPassInfo.isForward) {\n\t\t\t\t\t\tdefinition.tag = SHADERTAG_MATERIAL;\n\t\t\t\t}\n\t\t\t\treturn definition;\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this.optionsContext = new StandardMaterialOptions(), this.optionsContextMin = new StandardMaterialOptions();\n\t\t}\n}\nconst standard = new ShaderGeneratorStandard();\n\nconst arraysEqual = (a, b)=>{\n\t\tif (a.length !== b.length) {\n\t\t\t\treturn false;\n\t\t}\n\t\tfor(let i = 0; i < a.length; ++i){\n\t\t\t\tif (a[i] !== b[i]) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t}\n\t\treturn true;\n};\nconst notWhite = (color)=>{\n\t\treturn color.r !== 1 || color.g !== 1 || color.b !== 1;\n};\nconst notBlack = (color)=>{\n\t\treturn color.r !== 0 || color.g !== 0 || color.b !== 0;\n};\nclass StandardMaterialOptionsBuilder {\n\t\tconstructor(){\n\t\t\t\tthis._mapXForms = null;\n\t\t}\n\t\tupdateMinRef(options, scene, stdMat, objDefs, pass, sortedLights) {\n\t\t\t\tthis._updateSharedOptions(options, scene, stdMat, objDefs, pass);\n\t\t\t\tthis._updateMinOptions(options, stdMat, pass);\n\t\t\t\tthis._updateUVOptions(options, stdMat, objDefs, true);\n\t\t}\n\t\tupdateRef(options, scene, cameraShaderParams, stdMat, objDefs, pass, sortedLights) {\n\t\t\t\tthis._updateSharedOptions(options, scene, stdMat, objDefs, pass);\n\t\t\t\tthis._updateEnvOptions(options, stdMat, scene, cameraShaderParams);\n\t\t\t\tthis._updateMaterialOptions(options, stdMat, scene);\n\t\t\t\toptions.litOptions.hasTangents = objDefs && (objDefs & SHADERDEF_TANGENTS) !== 0;\n\t\t\t\tthis._updateLightOptions(options, scene, stdMat, objDefs, sortedLights);\n\t\t\t\tthis._updateUVOptions(options, stdMat, objDefs, false, cameraShaderParams);\n\t\t}\n\t\t_updateSharedOptions(options, scene, stdMat, objDefs, pass) {\n\t\t\t\toptions.forceUv1 = stdMat.forceUv1;\n\t\t\t\tif (stdMat.userAttributes) {\n\t\t\t\t\t\toptions.litOptions.userAttributes = Object.fromEntries(stdMat.userAttributes.entries());\n\t\t\t\t}\n\t\t\t\toptions.litOptions.shaderChunks = stdMat.shaderChunks;\n\t\t\t\toptions.litOptions.pass = pass;\n\t\t\t\toptions.litOptions.alphaTest = stdMat.alphaTest > 0;\n\t\t\t\toptions.litOptions.blendType = stdMat.blendType;\n\t\t\t\toptions.litOptions.screenSpace = objDefs && (objDefs & SHADERDEF_SCREENSPACE) !== 0;\n\t\t\t\toptions.litOptions.skin = objDefs && (objDefs & SHADERDEF_SKIN) !== 0;\n\t\t\t\toptions.litOptions.batch = objDefs && (objDefs & SHADERDEF_BATCH) !== 0;\n\t\t\t\toptions.litOptions.useInstancing = objDefs && (objDefs & SHADERDEF_INSTANCING) !== 0;\n\t\t\t\toptions.litOptions.useMorphPosition = objDefs && (objDefs & SHADERDEF_MORPH_POSITION) !== 0;\n\t\t\t\toptions.litOptions.useMorphNormal = objDefs && (objDefs & SHADERDEF_MORPH_NORMAL) !== 0;\n\t\t\t\toptions.litOptions.useMorphTextureBasedInt = objDefs && (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0;\n\t\t\t\toptions.litOptions.nineSlicedMode = stdMat.nineSlicedMode || 0;\n\t\t\t\tif (scene.clusteredLightingEnabled && stdMat.useLighting) {\n\t\t\t\t\t\toptions.litOptions.clusteredLightingEnabled = true;\n\t\t\t\t\t\toptions.litOptions.clusteredLightingCookiesEnabled = scene.lighting.cookiesEnabled;\n\t\t\t\t\t\toptions.litOptions.clusteredLightingShadowsEnabled = scene.lighting.shadowsEnabled;\n\t\t\t\t\t\toptions.litOptions.clusteredLightingShadowType = scene.lighting.shadowType;\n\t\t\t\t\t\toptions.litOptions.clusteredLightingAreaLightsEnabled = scene.lighting.areaLightsEnabled;\n\t\t\t\t} else {\n\t\t\t\t\t\toptions.litOptions.clusteredLightingEnabled = false;\n\t\t\t\t\t\toptions.litOptions.clusteredLightingCookiesEnabled = false;\n\t\t\t\t\t\toptions.litOptions.clusteredLightingShadowsEnabled = false;\n\t\t\t\t\t\toptions.litOptions.clusteredLightingAreaLightsEnabled = false;\n\t\t\t\t}\n\t\t}\n\t\t_updateUVOptions(options, stdMat, objDefs, minimalOptions, cameraShaderParams) {\n\t\t\t\tlet hasUv0 = false;\n\t\t\t\tlet hasUv1 = false;\n\t\t\t\tlet hasVcolor = false;\n\t\t\t\tif (objDefs) {\n\t\t\t\t\t\thasUv0 = (objDefs & SHADERDEF_UV0) !== 0;\n\t\t\t\t\t\thasUv1 = (objDefs & SHADERDEF_UV1) !== 0;\n\t\t\t\t\t\thasVcolor = (objDefs & SHADERDEF_VCOLOR) !== 0;\n\t\t\t\t}\n\t\t\t\toptions.litOptions.vertexColors = false;\n\t\t\t\tthis._mapXForms = [];\n\t\t\t\tconst uniqueTextureMap = {};\n\t\t\t\tfor(const p in _matTex2D){\n\t\t\t\t\t\tthis._updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap);\n\t\t\t\t}\n\t\t\t\tthis._mapXForms = null;\n\t\t\t\toptions.litOptions.ssao = cameraShaderParams?.ssaoEnabled;\n\t\t\t\toptions.useAO = options.litOptions.ssao;\n\t\t\t\toptions.litOptions.lightMapEnabled = options.lightMap;\n\t\t\t\toptions.litOptions.dirLightMapEnabled = options.dirLightMap;\n\t\t\t\toptions.litOptions.useHeights = options.heightMap;\n\t\t\t\toptions.litOptions.useNormals = options.normalMap;\n\t\t\t\toptions.litOptions.useClearCoatNormals = options.clearCoatNormalMap;\n\t\t\t\toptions.litOptions.useAo = options.aoMap || options.aoVertexColor || options.litOptions.ssao;\n\t\t\t\toptions.litOptions.diffuseMapEnabled = options.diffuseMap;\n\t\t}\n\t\t_updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap) {\n\t\t\t\tconst isOpacity = p === 'opacity';\n\t\t\t\tif (!minimalOptions || isOpacity) {\n\t\t\t\t\t\tconst mname = `${p}Map`;\n\t\t\t\t\t\tconst vname = `${p}VertexColor`;\n\t\t\t\t\t\tconst vcname = `${p}VertexColorChannel`;\n\t\t\t\t\t\tconst cname = `${mname}Channel`;\n\t\t\t\t\t\tconst tname = `${mname}Transform`;\n\t\t\t\t\t\tconst uname = `${mname}Uv`;\n\t\t\t\t\t\tconst iname = `${mname}Identifier`;\n\t\t\t\t\t\tif (p !== 'light') {\n\t\t\t\t\t\t\t\toptions[mname] = false;\n\t\t\t\t\t\t\t\toptions[iname] = undefined;\n\t\t\t\t\t\t\t\toptions[cname] = '';\n\t\t\t\t\t\t\t\toptions[tname] = 0;\n\t\t\t\t\t\t\t\toptions[uname] = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\toptions[vname] = false;\n\t\t\t\t\t\toptions[vcname] = '';\n\t\t\t\t\t\tif (isOpacity && stdMat.blendType === BLEND_NONE && stdMat.alphaTest === 0.0 && !stdMat.alphaToCoverage && stdMat.opacityDither === DITHER_NONE) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (p !== 'height' && stdMat[vname]) {\n\t\t\t\t\t\t\t\tif (hasVcolor) {\n\t\t\t\t\t\t\t\t\t\toptions[vname] = stdMat[vname];\n\t\t\t\t\t\t\t\t\t\toptions[vcname] = stdMat[vcname];\n\t\t\t\t\t\t\t\t\t\toptions.litOptions.vertexColors = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (stdMat[mname]) {\n\t\t\t\t\t\t\t\tlet allow = true;\n\t\t\t\t\t\t\t\tif (stdMat[uname] === 0 && !hasUv0) allow = false;\n\t\t\t\t\t\t\t\tif (stdMat[uname] === 1 && !hasUv1) allow = false;\n\t\t\t\t\t\t\t\tif (allow) {\n\t\t\t\t\t\t\t\t\t\tconst mapId = stdMat[mname].id;\n\t\t\t\t\t\t\t\t\t\tlet identifier = uniqueTextureMap[mapId];\n\t\t\t\t\t\t\t\t\t\tif (identifier === undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\tuniqueTextureMap[mapId] = p;\n\t\t\t\t\t\t\t\t\t\t\t\tidentifier = p;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\toptions[mname] = !!stdMat[mname];\n\t\t\t\t\t\t\t\t\t\toptions[iname] = identifier;\n\t\t\t\t\t\t\t\t\t\toptions[tname] = this._getMapTransformID(stdMat.getUniform(tname), stdMat[uname]);\n\t\t\t\t\t\t\t\t\t\toptions[cname] = stdMat[cname];\n\t\t\t\t\t\t\t\t\t\toptions[uname] = stdMat[uname];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateMinOptions(options, stdMat, pass) {\n\t\t\t\tconst isPrepass = pass === SHADER_PREPASS;\n\t\t\t\toptions.litOptions.opacityShadowDither = isPrepass ? stdMat.opacityDither : stdMat.opacityShadowDither;\n\t\t\t\toptions.litOptions.linearDepth = isPrepass;\n\t\t\t\toptions.litOptions.lights = [];\n\t\t}\n\t\t_updateMaterialOptions(options, stdMat, scene) {\n\t\t\t\tconst useSpecular = !!(stdMat.useMetalness || stdMat.specularMap || stdMat.sphereMap || stdMat.cubeMap || notBlack(stdMat.specular) || stdMat.specularityFactor > 0 && stdMat.useMetalness || stdMat.enableGGXSpecular || stdMat.clearCoat > 0);\n\t\t\t\tconst useSpecularColor = !stdMat.useMetalness || stdMat.useMetalnessSpecularColor;\n\t\t\t\tconst specularTint = useSpecular && (stdMat.specularTint || !stdMat.specularMap && !stdMat.specularVertexColor) && notWhite(stdMat.specular);\n\t\t\t\tconst specularityFactorTint = useSpecular && stdMat.useMetalnessSpecularColor && (stdMat.specularityFactorTint || stdMat.specularityFactor < 1 && !stdMat.specularityFactorMap);\n\t\t\t\tconst isPackedNormalMap = (texture)=>texture ? texture.format === PIXELFORMAT_DXT5 || texture.type === TEXTURETYPE_SWIZZLEGGGR : false;\n\t\t\t\tconst equalish = (a, b)=>Math.abs(a - b) < 1e-4;\n\t\t\t\toptions.specularTint = specularTint;\n\t\t\t\toptions.specularityFactorTint = specularityFactorTint;\n\t\t\t\toptions.metalnessTint = stdMat.useMetalness && stdMat.metalness < 1;\n\t\t\t\toptions.glossTint = true;\n\t\t\t\toptions.diffuseEncoding = stdMat.diffuseMap?.encoding;\n\t\t\t\toptions.diffuseDetailEncoding = stdMat.diffuseDetailMap?.encoding;\n\t\t\t\toptions.emissiveEncoding = stdMat.emissiveMap?.encoding;\n\t\t\t\toptions.lightMapEncoding = stdMat.lightMap?.encoding;\n\t\t\t\toptions.packedNormal = isPackedNormalMap(stdMat.normalMap);\n\t\t\t\toptions.refractionTint = equalish(stdMat.refraction, 1.0);\n\t\t\t\toptions.refractionIndexTint = equalish(stdMat.refractionIndex, 1.0 / 1.5);\n\t\t\t\toptions.thicknessTint = stdMat.useDynamicRefraction && stdMat.thickness !== 1.0;\n\t\t\t\toptions.specularEncoding = stdMat.specularMap?.encoding;\n\t\t\t\toptions.sheenEncoding = stdMat.sheenMap?.encoding;\n\t\t\t\toptions.aoMapUv = stdMat.aoUvSet;\n\t\t\t\toptions.aoDetail = !!stdMat.aoDetailMap;\n\t\t\t\toptions.diffuseDetail = !!stdMat.diffuseDetailMap;\n\t\t\t\toptions.normalDetail = !!stdMat.normalMap;\n\t\t\t\toptions.normalDetailPackedNormal = isPackedNormalMap(stdMat.normalDetailMap);\n\t\t\t\toptions.diffuseDetailMode = stdMat.diffuseDetailMode;\n\t\t\t\toptions.aoDetailMode = stdMat.aoDetailMode;\n\t\t\t\toptions.clearCoatGloss = !!stdMat.clearCoatGloss;\n\t\t\t\toptions.clearCoatPackedNormal = isPackedNormalMap(stdMat.clearCoatNormalMap);\n\t\t\t\toptions.iorTint = equalish(stdMat.refractionIndex, 1.0 / 1.5);\n\t\t\t\tif (scene.forcePassThroughSpecular) {\n\t\t\t\t\t\toptions.specularEncoding = 'linear';\n\t\t\t\t\t\toptions.sheenEncoding = 'linear';\n\t\t\t\t}\n\t\t\t\toptions.iridescenceTint = stdMat.iridescence !== 1.0;\n\t\t\t\toptions.glossInvert = stdMat.glossInvert;\n\t\t\t\toptions.sheenGlossInvert = stdMat.sheenGlossInvert;\n\t\t\t\toptions.clearCoatGlossInvert = stdMat.clearCoatGlossInvert;\n\t\t\t\toptions.useSpecularColor = useSpecularColor;\n\t\t\t\toptions.litOptions.separateAmbient = false;\n\t\t\t\toptions.litOptions.pixelSnap = stdMat.pixelSnap;\n\t\t\t\toptions.litOptions.ambientSH = !!stdMat.ambientSH;\n\t\t\t\toptions.litOptions.twoSidedLighting = stdMat.twoSidedLighting;\n\t\t\t\toptions.litOptions.occludeSpecular = stdMat.occludeSpecular;\n\t\t\t\toptions.litOptions.occludeSpecularFloat = stdMat.occludeSpecularIntensity !== 1.0;\n\t\t\t\toptions.litOptions.useMsdf = !!stdMat.msdfMap;\n\t\t\t\toptions.litOptions.msdfTextAttribute = !!stdMat.msdfTextAttribute;\n\t\t\t\toptions.litOptions.alphaToCoverage = stdMat.alphaToCoverage;\n\t\t\t\toptions.litOptions.opacityFadesSpecular = stdMat.opacityFadesSpecular;\n\t\t\t\toptions.litOptions.opacityDither = stdMat.opacityDither;\n\t\t\t\toptions.litOptions.cubeMapProjection = stdMat.cubeMapProjection;\n\t\t\t\toptions.litOptions.occludeDirect = stdMat.occludeDirect;\n\t\t\t\toptions.litOptions.useSpecular = useSpecular;\n\t\t\t\toptions.litOptions.useSpecularityFactor = (specularityFactorTint || !!stdMat.specularityFactorMap) && stdMat.useMetalnessSpecularColor;\n\t\t\t\toptions.litOptions.enableGGXSpecular = stdMat.enableGGXSpecular;\n\t\t\t\toptions.litOptions.fresnelModel = stdMat.fresnelModel;\n\t\t\t\toptions.litOptions.useRefraction = (stdMat.refraction || !!stdMat.refractionMap) && (stdMat.useDynamicRefraction || options.litOptions.reflectionSource !== REFLECTIONSRC_NONE);\n\t\t\t\toptions.litOptions.useClearCoat = !!stdMat.clearCoat;\n\t\t\t\toptions.litOptions.useSheen = stdMat.useSheen;\n\t\t\t\toptions.litOptions.useIridescence = stdMat.useIridescence && stdMat.iridescence !== 0.0;\n\t\t\t\toptions.litOptions.useMetalness = stdMat.useMetalness;\n\t\t\t\toptions.litOptions.useDynamicRefraction = stdMat.useDynamicRefraction;\n\t\t\t\toptions.litOptions.dispersion = stdMat.dispersion > 0;\n\t\t\t\toptions.litOptions.shadowCatcher = stdMat.shadowCatcher;\n\t\t}\n\t\t_updateEnvOptions(options, stdMat, scene, cameraShaderParams) {\n\t\t\t\toptions.litOptions.fog = stdMat.useFog ? cameraShaderParams.fog : FOG_NONE;\n\t\t\t\toptions.litOptions.gamma = cameraShaderParams.shaderOutputGamma;\n\t\t\t\toptions.litOptions.toneMap = stdMat.useTonemap ? cameraShaderParams.toneMapping : TONEMAP_NONE;\n\t\t\t\tlet usingSceneEnv = false;\n\t\t\t\tif (stdMat.envAtlas && stdMat.cubeMap) {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;\n\t\t\t\t\t\toptions.litOptions.reflectionCubemapEncoding = stdMat.cubeMap.encoding;\n\t\t\t\t} else if (stdMat.envAtlas) {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;\n\t\t\t\t} else if (stdMat.cubeMap) {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = stdMat.cubeMap.encoding;\n\t\t\t\t} else if (stdMat.sphereMap) {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_SPHEREMAP;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = stdMat.sphereMap.encoding;\n\t\t\t\t} else if (stdMat.useSkybox && scene.envAtlas && scene.skybox) {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = scene.envAtlas.encoding;\n\t\t\t\t\t\toptions.litOptions.reflectionCubemapEncoding = scene.skybox.encoding;\n\t\t\t\t\t\tusingSceneEnv = true;\n\t\t\t\t} else if (stdMat.useSkybox && scene.envAtlas) {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = scene.envAtlas.encoding;\n\t\t\t\t\t\tusingSceneEnv = true;\n\t\t\t\t} else if (stdMat.useSkybox && scene.skybox) {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = scene.skybox.encoding;\n\t\t\t\t\t\tusingSceneEnv = true;\n\t\t\t\t} else {\n\t\t\t\t\t\toptions.litOptions.reflectionSource = REFLECTIONSRC_NONE;\n\t\t\t\t\t\toptions.litOptions.reflectionEncoding = null;\n\t\t\t\t}\n\t\t\t\tif (stdMat.ambientSH) {\n\t\t\t\t\t\toptions.litOptions.ambientSource = AMBIENTSRC_AMBIENTSH;\n\t\t\t\t\t\toptions.litOptions.ambientEncoding = null;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst envAtlas = stdMat.envAtlas || (stdMat.useSkybox && scene.envAtlas ? scene.envAtlas : null);\n\t\t\t\t\t\tif (envAtlas && !stdMat.sphereMap) {\n\t\t\t\t\t\t\t\toptions.litOptions.ambientSource = AMBIENTSRC_ENVALATLAS;\n\t\t\t\t\t\t\t\toptions.litOptions.ambientEncoding = envAtlas.encoding;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toptions.litOptions.ambientSource = AMBIENTSRC_CONSTANT;\n\t\t\t\t\t\t\t\toptions.litOptions.ambientEncoding = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\toptions.litOptions.skyboxIntensity = usingSceneEnv;\n\t\t\t\toptions.litOptions.useCubeMapRotation = usingSceneEnv && scene._skyboxRotationShaderInclude;\n\t\t}\n\t\t_updateLightOptions(options, scene, stdMat, objDefs, sortedLights) {\n\t\t\t\toptions.lightMap = false;\n\t\t\t\toptions.lightMapChannel = '';\n\t\t\t\toptions.lightMapUv = 0;\n\t\t\t\toptions.lightMapTransform = 0;\n\t\t\t\toptions.litOptions.lightMapWithoutAmbient = false;\n\t\t\t\toptions.dirLightMap = false;\n\t\t\t\tif (objDefs) {\n\t\t\t\t\t\toptions.litOptions.noShadow = (objDefs & SHADERDEF_NOSHADOW) !== 0;\n\t\t\t\t\t\tif ((objDefs & SHADERDEF_LM) !== 0) {\n\t\t\t\t\t\t\t\toptions.lightMapEncoding = scene.lightmapPixelFormat === PIXELFORMAT_RGBA8 ? 'rgbm' : 'linear';\n\t\t\t\t\t\t\t\toptions.lightMap = true;\n\t\t\t\t\t\t\t\toptions.lightMapChannel = 'rgb';\n\t\t\t\t\t\t\t\toptions.lightMapUv = 1;\n\t\t\t\t\t\t\t\toptions.lightMapTransform = 0;\n\t\t\t\t\t\t\t\toptions.litOptions.lightMapWithoutAmbient = !stdMat.lightMap;\n\t\t\t\t\t\t\t\tif ((objDefs & SHADERDEF_DIRLM) !== 0) {\n\t\t\t\t\t\t\t\t\t\toptions.dirLightMap = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ((objDefs & SHADERDEF_LMAMBIENT) !== 0) {\n\t\t\t\t\t\t\t\t\t\toptions.litOptions.lightMapWithoutAmbient = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (stdMat.useLighting) {\n\t\t\t\t\t\tconst lightsFiltered = [];\n\t\t\t\t\t\tconst mask = objDefs ? objDefs >> 16 : MASK_AFFECT_DYNAMIC;\n\t\t\t\t\t\toptions.litOptions.lightMaskDynamic = !!(mask & MASK_AFFECT_DYNAMIC);\n\t\t\t\t\t\tif (sortedLights) {\n\t\t\t\t\t\t\t\tLitMaterialOptionsBuilder.collectLights(LIGHTTYPE_DIRECTIONAL, sortedLights[LIGHTTYPE_DIRECTIONAL], lightsFiltered, mask);\n\t\t\t\t\t\t\t\tif (!scene.clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\tLitMaterialOptionsBuilder.collectLights(LIGHTTYPE_OMNI, sortedLights[LIGHTTYPE_OMNI], lightsFiltered, mask);\n\t\t\t\t\t\t\t\t\t\tLitMaterialOptionsBuilder.collectLights(LIGHTTYPE_SPOT, sortedLights[LIGHTTYPE_SPOT], lightsFiltered, mask);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\toptions.litOptions.lights = lightsFiltered;\n\t\t\t\t} else {\n\t\t\t\t\t\toptions.litOptions.lights = [];\n\t\t\t\t}\n\t\t\t\tif (options.litOptions.lights.length === 0 && !scene.clusteredLightingEnabled) {\n\t\t\t\t\t\toptions.litOptions.noShadow = true;\n\t\t\t\t}\n\t\t}\n\t\t_getMapTransformID(xform, uv) {\n\t\t\t\tif (!xform) return 0;\n\t\t\t\tlet xforms = this._mapXForms[uv];\n\t\t\t\tif (!xforms) {\n\t\t\t\t\t\txforms = [];\n\t\t\t\t\t\tthis._mapXForms[uv] = xforms;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < xforms.length; i++){\n\t\t\t\t\t\tif (arraysEqual(xforms[i][0].value, xform[0].value) && arraysEqual(xforms[i][1].value, xform[1].value)) {\n\t\t\t\t\t\t\t\treturn i + 1;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn xforms.push(xform);\n\t\t}\n}\n\nfunction _textureParameter(name, channel = true, vertexColor = true) {\n\t\tconst result = {};\n\t\tresult[`${name}Map`] = 'texture';\n\t\tresult[`${name}MapTiling`] = 'vec2';\n\t\tresult[`${name}MapOffset`] = 'vec2';\n\t\tresult[`${name}MapRotation`] = 'number';\n\t\tresult[`${name}MapUv`] = 'number';\n\t\tif (channel) {\n\t\t\t\tresult[`${name}MapChannel`] = 'string';\n\t\t\t\tif (vertexColor) {\n\t\t\t\t\t\tresult[`${name}VertexColor`] = 'boolean';\n\t\t\t\t\t\tresult[`${name}VertexColorChannel`] = 'string';\n\t\t\t\t}\n\t\t}\n\t\treturn result;\n}\nconst standardMaterialParameterTypes = {\n\t\tname: 'string',\n\t\tchunks: 'chunks',\n\t\tmappingFormat: 'string',\n\t\t_engine: 'boolean',\n\t\tambient: 'rgb',\n\t\t..._textureParameter('ao'),\n\t\t..._textureParameter('aoDetail', true, false),\n\t\taoDetailMode: 'string',\n\t\taoIntensity: 'number',\n\t\tdiffuse: 'rgb',\n\t\t..._textureParameter('diffuse'),\n\t\t..._textureParameter('diffuseDetail', true, false),\n\t\tdiffuseDetailMode: 'string',\n\t\tspecular: 'rgb',\n\t\tspecularTint: 'boolean',\n\t\t..._textureParameter('specular'),\n\t\toccludeSpecular: 'enum:occludeSpecular',\n\t\tspecularityFactor: 'number',\n\t\tspecularityFactorTint: 'boolean',\n\t\t..._textureParameter('specularityFactor'),\n\t\tuseMetalness: 'boolean',\n\t\tmetalness: 'number',\n\t\tenableGGXSpecular: 'boolean',\n\t\tmetalnessTint: 'boolean',\n\t\t..._textureParameter('metalness'),\n\t\tuseMetalnessSpecularColor: 'boolean',\n\t\tanisotropyIntensity: 'number',\n\t\tanisotropyRotation: 'number',\n\t\t..._textureParameter('anisotropy'),\n\t\tshininess: 'number',\n\t\tgloss: 'number',\n\t\tglossInvert: 'boolean',\n\t\t..._textureParameter('gloss'),\n\t\tclearCoat: 'number',\n\t\t..._textureParameter('clearCoat'),\n\t\tclearCoatGloss: 'number',\n\t\tclearCoatGlossInvert: 'boolean',\n\t\t..._textureParameter('clearCoatGloss'),\n\t\tclearCoatBumpiness: 'number',\n\t\t..._textureParameter('clearCoatNormal', false),\n\t\tuseSheen: 'boolean',\n\t\tsheen: 'rgb',\n\t\t..._textureParameter('sheen'),\n\t\tsheenGloss: 'number',\n\t\tsheenGlossInvert: 'boolean',\n\t\t..._textureParameter('sheenGloss'),\n\t\tfresnelModel: 'number',\n\t\temissive: 'rgb',\n\t\t..._textureParameter('emissive'),\n\t\temissiveIntensity: 'number',\n\t\t..._textureParameter('normal', false),\n\t\tbumpiness: 'number',\n\t\t..._textureParameter('normalDetail', false),\n\t\tnormalDetailMapBumpiness: 'number',\n\t\t..._textureParameter('height', true, false),\n\t\theightMapFactor: 'number',\n\t\talphaToCoverage: 'boolean',\n\t\talphaTest: 'number',\n\t\talphaFade: 'number',\n\t\topacity: 'number',\n\t\t..._textureParameter('opacity'),\n\t\topacityFadesSpecular: 'boolean',\n\t\topacityDither: 'string',\n\t\topacityShadowDither: 'string',\n\t\treflectivity: 'number',\n\t\trefraction: 'number',\n\t\trefractionTint: 'boolean',\n\t\t..._textureParameter('refraction'),\n\t\trefractionIndex: 'number',\n\t\tdispersion: 'number',\n\t\tthickness: 'number',\n\t\tthicknessTint: 'boolean',\n\t\t..._textureParameter('thickness'),\n\t\tattenuation: 'rgb',\n\t\tattenuationDistance: 'number',\n\t\tuseDynamicRefraction: 'boolean',\n\t\tsphereMap: 'texture',\n\t\tcubeMap: 'cubemap',\n\t\tcubeMapProjection: 'number',\n\t\tcubeMapProjectionBox: 'boundingbox',\n\t\tuseIridescence: 'boolean',\n\t\tiridescence: 'number',\n\t\tiridescenceTint: 'boolean',\n\t\t..._textureParameter('iridescence'),\n\t\tiridescenceThicknessTint: 'boolean',\n\t\tiridescenceThicknessMin: 'number',\n\t\tiridescenceThicknessMax: 'number',\n\t\tiridescenceRefractionIndex: 'number',\n\t\t..._textureParameter('iridescenceThickness'),\n\t\t..._textureParameter('light'),\n\t\tdepthTest: 'boolean',\n\t\tdepthFunc: 'enum:depthFunc',\n\t\tdepthWrite: 'boolean',\n\t\tdepthBias: 'number',\n\t\tslopeDepthBias: 'number',\n\t\tcull: 'enum:cull',\n\t\tblendType: 'enum:blendType',\n\t\tuseFog: 'boolean',\n\t\tuseLighting: 'boolean',\n\t\tuseSkybox: 'boolean',\n\t\tuseTonemap: 'boolean',\n\t\tenvAtlas: 'texture',\n\t\ttwoSidedLighting: 'boolean',\n\t\tshadowCatcher: 'boolean'\n};\nconst standardMaterialTextureParameters = [];\nfor(const key in standardMaterialParameterTypes){\n\t\tconst type = standardMaterialParameterTypes[key];\n\t\tif (type === 'texture') {\n\t\t\t\tstandardMaterialTextureParameters.push(key);\n\t\t}\n}\nconst standardMaterialCubemapParameters = [];\nfor(const key in standardMaterialParameterTypes){\n\t\tconst type = standardMaterialParameterTypes[key];\n\t\tif (type === 'cubemap') {\n\t\t\t\tstandardMaterialCubemapParameters.push(key);\n\t\t}\n}\nconst standardMaterialRemovedParameters = {\n\t\taoMapVertexColor: 'boolean',\n\t\tdiffuseMapTint: 'boolean',\n\t\tdiffuseMapVertexColor: 'boolean',\n\t\temissiveMapTint: 'boolean',\n\t\temissiveMapVertexColor: 'boolean',\n\t\tglossMapVertexColor: 'boolean',\n\t\tmetalnessMapVertexColor: 'boolean',\n\t\topacityMapVertexColor: 'boolean',\n\t\tspecularAntialias: 'boolean',\n\t\tspecularMapTint: 'boolean',\n\t\tspecularMapVertexColor: 'boolean',\n\t\tambientTint: 'boolean',\n\t\temissiveTint: 'boolean',\n\t\tdiffuseTint: 'boolean',\n\t\tsheenTint: 'boolean',\n\t\tconserveEnergy: 'boolean',\n\t\tuseGamma: 'boolean',\n\t\tuseGammaTonemap: 'boolean',\n\t\tsheenGlossTint: 'boolean',\n\t\tanisotropy: 'boolean'\n};\n\nconst _props = {};\nconst _uniforms = {};\nlet _params = new Set();\nconst _tempColor$2 = new Color();\nclass StandardMaterial extends Material {\n\t\tstatic{\n\t\t\t\tthis.TEXTURE_PARAMETERS = standardMaterialTextureParameters;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.CUBEMAP_PARAMETERS = standardMaterialCubemapParameters;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tsuper(), this.userAttributes = new Map();\n\t\t\t\tthis._assetReferences = {};\n\t\t\t\tthis._activeParams = new Set();\n\t\t\t\tthis._activeLightingParams = new Set();\n\t\t\t\tthis.shaderOptBuilder = new StandardMaterialOptionsBuilder();\n\t\t\t\tthis.reset();\n\t\t}\n\t\treset() {\n\t\t\t\tObject.keys(_props).forEach((name)=>{\n\t\t\t\t\t\tthis[`_${name}`] = _props[name].value();\n\t\t\t\t});\n\t\t\t\tthis._uniformCache = {};\n\t\t}\n\t\tcopy(source) {\n\t\t\t\tsuper.copy(source);\n\t\t\t\tObject.keys(_props).forEach((k)=>{\n\t\t\t\t\t\tthis[k] = source[k];\n\t\t\t\t});\n\t\t\t\tthis.userAttributes = new Map(source.userAttributes);\n\t\t\t\treturn this;\n\t\t}\n\t\tsetAttribute(name, semantic) {\n\t\t\t\tthis.userAttributes.set(semantic, name);\n\t\t}\n\t\t_setParameter(name, value) {\n\t\t\t\t_params.add(name);\n\t\t\t\tthis.setParameter(name, value);\n\t\t}\n\t\t_setParameters(parameters) {\n\t\t\t\tparameters.forEach((v)=>{\n\t\t\t\t\t\tthis._setParameter(v.name, v.value);\n\t\t\t\t});\n\t\t}\n\t\t_processParameters(paramsName) {\n\t\t\t\tconst prevParams = this[paramsName];\n\t\t\t\tprevParams.forEach((param)=>{\n\t\t\t\t\t\tif (!_params.has(param)) {\n\t\t\t\t\t\t\t\tdelete this.parameters[param];\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis[paramsName] = _params;\n\t\t\t\t_params = prevParams;\n\t\t\t\t_params.clear();\n\t\t}\n\t\t_updateMap(p) {\n\t\t\t\tconst mname = `${p}Map`;\n\t\t\t\tconst map = this[mname];\n\t\t\t\tif (map) {\n\t\t\t\t\t\tthis._setParameter(`texture_${mname}`, map);\n\t\t\t\t\t\tconst tname = `${mname}Transform`;\n\t\t\t\t\t\tconst uniform = this.getUniform(tname);\n\t\t\t\t\t\tif (uniform) {\n\t\t\t\t\t\t\t\tthis._setParameters(uniform);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_allocUniform(name, allocFunc) {\n\t\t\t\tlet uniform = this._uniformCache[name];\n\t\t\t\tif (!uniform) {\n\t\t\t\t\t\tuniform = allocFunc();\n\t\t\t\t\t\tthis._uniformCache[name] = uniform;\n\t\t\t\t}\n\t\t\t\treturn uniform;\n\t\t}\n\t\tgetUniform(name, device, scene) {\n\t\t\t\treturn _uniforms[name](this, device, scene);\n\t\t}\n\t\tupdateUniforms(device, scene) {\n\t\t\t\tconst getUniform = (name)=>{\n\t\t\t\t\t\treturn this.getUniform(name, device, scene);\n\t\t\t\t};\n\t\t\t\tthis._setParameter('material_ambient', getUniform('ambient'));\n\t\t\t\tthis._setParameter('material_diffuse', getUniform('diffuse'));\n\t\t\t\tthis._setParameter('material_aoIntensity', this.aoIntensity);\n\t\t\t\tif (this.useMetalness) {\n\t\t\t\t\t\tif (!this.metalnessMap || this.metalness < 1) {\n\t\t\t\t\t\t\t\tthis._setParameter('material_metalness', this.metalness);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this.specularMap || this.specularTint) {\n\t\t\t\t\t\t\t\tthis._setParameter('material_specular', getUniform('specular'));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this.specularityFactorMap || this.specularityFactorTint) {\n\t\t\t\t\t\t\t\tthis._setParameter('material_specularityFactor', this.specularityFactor);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._setParameter('material_sheen', getUniform('sheen'));\n\t\t\t\t\t\tthis._setParameter('material_sheenGloss', this.sheenGloss);\n\t\t\t\t\t\tthis._setParameter('material_refractionIndex', this.refractionIndex);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.specularMap || this.specularTint) {\n\t\t\t\t\t\t\t\tthis._setParameter('material_specular', getUniform('specular'));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.enableGGXSpecular) {\n\t\t\t\t\t\tthis._setParameter('material_anisotropyIntensity', this.anisotropyIntensity);\n\t\t\t\t\t\tthis._setParameter('material_anisotropyRotation', [\n\t\t\t\t\t\t\t\tMath.cos(this.anisotropyRotation * math.DEG_TO_RAD),\n\t\t\t\t\t\t\t\tMath.sin(this.anisotropyRotation * math.DEG_TO_RAD)\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\tif (this.clearCoat > 0) {\n\t\t\t\t\t\tthis._setParameter('material_clearCoat', this.clearCoat);\n\t\t\t\t\t\tthis._setParameter('material_clearCoatGloss', this.clearCoatGloss);\n\t\t\t\t\t\tthis._setParameter('material_clearCoatBumpiness', this.clearCoatBumpiness);\n\t\t\t\t}\n\t\t\t\tthis._setParameter('material_gloss', this.gloss);\n\t\t\t\tthis._setParameter('material_emissive', getUniform('emissive'));\n\t\t\t\tthis._setParameter('material_emissiveIntensity', this.emissiveIntensity);\n\t\t\t\tif (this.refraction > 0) {\n\t\t\t\t\t\tthis._setParameter('material_refraction', this.refraction);\n\t\t\t\t}\n\t\t\t\tif (this.dispersion > 0) {\n\t\t\t\t\t\tthis._setParameter('material_dispersion', this.dispersion);\n\t\t\t\t}\n\t\t\t\tif (this.useDynamicRefraction) {\n\t\t\t\t\t\tthis._setParameter('material_thickness', this.thickness);\n\t\t\t\t\t\tthis._setParameter('material_attenuation', getUniform('attenuation'));\n\t\t\t\t\t\tthis._setParameter('material_invAttenuationDistance', this.attenuationDistance === 0 ? 0 : 1.0 / this.attenuationDistance);\n\t\t\t\t}\n\t\t\t\tif (this.useIridescence) {\n\t\t\t\t\t\tthis._setParameter('material_iridescence', this.iridescence);\n\t\t\t\t\t\tthis._setParameter('material_iridescenceRefractionIndex', this.iridescenceRefractionIndex);\n\t\t\t\t\t\tthis._setParameter('material_iridescenceThicknessMin', this.iridescenceThicknessMin);\n\t\t\t\t\t\tthis._setParameter('material_iridescenceThicknessMax', this.iridescenceThicknessMax);\n\t\t\t\t}\n\t\t\t\tthis._setParameter('material_opacity', this.opacity);\n\t\t\t\tif (this.opacityFadesSpecular === false) {\n\t\t\t\t\t\tthis._setParameter('material_alphaFade', this.alphaFade);\n\t\t\t\t}\n\t\t\t\tif (this.occludeSpecular) {\n\t\t\t\t\t\tthis._setParameter('material_occludeSpecularIntensity', this.occludeSpecularIntensity);\n\t\t\t\t}\n\t\t\t\tif (this.cubeMapProjection === CUBEPROJ_BOX) {\n\t\t\t\t\t\tthis._setParameter(getUniform('cubeMapProjectionBox'));\n\t\t\t\t}\n\t\t\t\tfor(const p in _matTex2D){\n\t\t\t\t\t\tthis._updateMap(p);\n\t\t\t\t}\n\t\t\t\tif (this.ambientSH) {\n\t\t\t\t\t\tthis._setParameter('ambientSH[0]', this.ambientSH);\n\t\t\t\t}\n\t\t\t\tif (this.normalMap) {\n\t\t\t\t\t\tthis._setParameter('material_bumpiness', this.bumpiness);\n\t\t\t\t}\n\t\t\t\tif (this.normalMap && this.normalDetailMap) {\n\t\t\t\t\t\tthis._setParameter('material_normalDetailMapBumpiness', this.normalDetailMapBumpiness);\n\t\t\t\t}\n\t\t\t\tif (this.heightMap) {\n\t\t\t\t\t\tthis._setParameter('material_heightMapFactor', getUniform('heightMapFactor'));\n\t\t\t\t}\n\t\t\t\tif (this.envAtlas && this.cubeMap) {\n\t\t\t\t\t\tthis._setParameter('texture_envAtlas', this.envAtlas);\n\t\t\t\t\t\tthis._setParameter('texture_cubeMap', this.cubeMap);\n\t\t\t\t} else if (this.envAtlas) {\n\t\t\t\t\t\tthis._setParameter('texture_envAtlas', this.envAtlas);\n\t\t\t\t} else if (this.cubeMap) {\n\t\t\t\t\t\tthis._setParameter('texture_cubeMap', this.cubeMap);\n\t\t\t\t} else if (this.sphereMap) {\n\t\t\t\t\t\tthis._setParameter('texture_sphereMap', this.sphereMap);\n\t\t\t\t}\n\t\t\t\tthis._setParameter('material_reflectivity', this.reflectivity);\n\t\t\t\tthis._processParameters('_activeParams');\n\t\t\t\tsuper.updateUniforms(device, scene);\n\t\t}\n\t\tupdateEnvUniforms(device, scene) {\n\t\t\t\tconst hasLocalEnvOverride = this.envAtlas || this.cubeMap || this.sphereMap;\n\t\t\t\tif (!hasLocalEnvOverride && this.useSkybox) {\n\t\t\t\t\t\tif (scene.envAtlas && scene.skybox) {\n\t\t\t\t\t\t\t\tthis._setParameter('texture_envAtlas', scene.envAtlas);\n\t\t\t\t\t\t\t\tthis._setParameter('texture_cubeMap', scene.skybox);\n\t\t\t\t\t\t} else if (scene.envAtlas) {\n\t\t\t\t\t\t\t\tthis._setParameter('texture_envAtlas', scene.envAtlas);\n\t\t\t\t\t\t} else if (scene.skybox) {\n\t\t\t\t\t\t\t\tthis._setParameter('texture_cubeMap', scene.skybox);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._processParameters('_activeLightingParams');\n\t\t}\n\t\tgetShaderVariant(params) {\n\t\t\t\tconst { device, scene, pass, objDefs, sortedLights, cameraShaderParams } = params;\n\t\t\t\tthis.updateEnvUniforms(device, scene);\n\t\t\t\tconst shaderPassInfo = ShaderPass.get(device).getByIndex(pass);\n\t\t\t\tconst minimalOptions = pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow;\n\t\t\t\tlet options = minimalOptions ? standard.optionsContextMin : standard.optionsContext;\n\t\t\t\toptions.defines = ShaderUtils.getCoreDefines(this, params);\n\t\t\t\tif (minimalOptions) {\n\t\t\t\t\t\tthis.shaderOptBuilder.updateMinRef(options, scene, this, objDefs, pass, sortedLights);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.shaderOptBuilder.updateRef(options, scene, cameraShaderParams, this, objDefs, pass, sortedLights);\n\t\t\t\t}\n\t\t\t\tif (!this.useFog) options.defines.set('FOG', 'NONE');\n\t\t\t\toptions.defines.set('TONEMAP', tonemapNames[options.litOptions.toneMap]);\n\t\t\t\tif (this.onUpdateShader) {\n\t\t\t\t\t\toptions = this.onUpdateShader(options);\n\t\t\t\t}\n\t\t\t\tconst processingOptions = new ShaderProcessorOptions(params.viewUniformFormat, params.viewBindGroupFormat, params.vertexFormat);\n\t\t\t\tconst library = getProgramLibrary(device);\n\t\t\t\tlibrary.register('standard', standard);\n\t\t\t\tconst shader = library.getProgram('standard', options, processingOptions, this.userId);\n\t\t\t\tthis._dirtyShader = false;\n\t\t\t\treturn shader;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tfor(const asset in this._assetReferences){\n\t\t\t\t\t\tthis._assetReferences[asset]._unbind();\n\t\t\t\t}\n\t\t\t\tthis._assetReferences = null;\n\t\t\t\tsuper.destroy();\n\t\t}\n}\nconst defineUniform = (name, getUniformFunc)=>{\n\t\t_uniforms[name] = getUniformFunc;\n};\nconst definePropInternal = (name, constructorFunc, setterFunc, getterFunc)=>{\n\t\tObject.defineProperty(StandardMaterial.prototype, name, {\n\t\t\t\tget: getterFunc || function() {\n\t\t\t\t\t\treturn this[`_${name}`];\n\t\t\t\t},\n\t\t\t\tset: setterFunc\n\t\t});\n\t\t_props[name] = {\n\t\t\t\tvalue: constructorFunc\n\t\t};\n};\nconst defineValueProp = (prop)=>{\n\t\tconst internalName = `_${prop.name}`;\n\t\tconst dirtyShaderFunc = prop.dirtyShaderFunc || (()=>true);\n\t\tconst setterFunc = function(value) {\n\t\t\t\tconst oldValue = this[internalName];\n\t\t\t\tif (oldValue !== value) {\n\t\t\t\t\t\tthis._dirtyShader = this._dirtyShader || dirtyShaderFunc(oldValue, value);\n\t\t\t\t\t\tthis[internalName] = value;\n\t\t\t\t}\n\t\t};\n\t\tdefinePropInternal(prop.name, ()=>prop.defaultValue, setterFunc, prop.getterFunc);\n};\nconst defineAggProp = (prop)=>{\n\t\tconst internalName = `_${prop.name}`;\n\t\tconst dirtyShaderFunc = prop.dirtyShaderFunc || (()=>true);\n\t\tconst setterFunc = function(value) {\n\t\t\t\tconst oldValue = this[internalName];\n\t\t\t\tif (!oldValue.equals(value)) {\n\t\t\t\t\t\tthis._dirtyShader = this._dirtyShader || dirtyShaderFunc(oldValue, value);\n\t\t\t\t\t\tthis[internalName] = oldValue.copy(value);\n\t\t\t\t}\n\t\t};\n\t\tdefinePropInternal(prop.name, ()=>prop.defaultValue.clone(), setterFunc, prop.getterFunc);\n};\nconst defineProp = (prop)=>{\n\t\treturn prop.defaultValue && prop.defaultValue.clone ? defineAggProp(prop) : defineValueProp(prop);\n};\nfunction _defineTex2D(name, channel = 'rgb', vertexColor = true, uv = 0) {\n\t\t_matTex2D[name] = channel.length || -1;\n\t\tdefineProp({\n\t\t\t\tname: `${name}Map`,\n\t\t\t\tdefaultValue: null,\n\t\t\t\tdirtyShaderFunc: (oldValue, newValue)=>{\n\t\t\t\t\t\treturn !!oldValue !== !!newValue || oldValue && (oldValue.type !== newValue.type || oldValue.format !== newValue.format);\n\t\t\t\t}\n\t\t});\n\t\tdefineProp({\n\t\t\t\tname: `${name}MapTiling`,\n\t\t\t\tdefaultValue: new Vec2(1, 1)\n\t\t});\n\t\tdefineProp({\n\t\t\t\tname: `${name}MapOffset`,\n\t\t\t\tdefaultValue: new Vec2(0, 0)\n\t\t});\n\t\tdefineProp({\n\t\t\t\tname: `${name}MapRotation`,\n\t\t\t\tdefaultValue: 0\n\t\t});\n\t\tdefineProp({\n\t\t\t\tname: `${name}MapUv`,\n\t\t\t\tdefaultValue: uv\n\t\t});\n\t\tif (channel) {\n\t\t\t\tdefineProp({\n\t\t\t\t\t\tname: `${name}MapChannel`,\n\t\t\t\t\t\tdefaultValue: channel\n\t\t\t\t});\n\t\t\t\tif (vertexColor) {\n\t\t\t\t\t\tdefineProp({\n\t\t\t\t\t\t\t\tname: `${name}VertexColor`,\n\t\t\t\t\t\t\t\tdefaultValue: false\n\t\t\t\t\t\t});\n\t\t\t\t\t\tdefineProp({\n\t\t\t\t\t\t\t\tname: `${name}VertexColorChannel`,\n\t\t\t\t\t\t\t\tdefaultValue: channel\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tconst mapTiling = `${name}MapTiling`;\n\t\tconst mapOffset = `${name}MapOffset`;\n\t\tconst mapRotation = `${name}MapRotation`;\n\t\tconst mapTransform = `${name}MapTransform`;\n\t\tdefineUniform(mapTransform, (material, device, scene)=>{\n\t\t\t\tconst tiling = material[mapTiling];\n\t\t\t\tconst offset = material[mapOffset];\n\t\t\t\tconst rotation = material[mapRotation];\n\t\t\t\tif (tiling.x === 1 && tiling.y === 1 && offset.x === 0 && offset.y === 0 && rotation === 0) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst uniform = material._allocUniform(mapTransform, ()=>{\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: `texture_${mapTransform}0`,\n\t\t\t\t\t\t\t\t\t\tvalue: new Float32Array(3)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: `texture_${mapTransform}1`,\n\t\t\t\t\t\t\t\t\t\tvalue: new Float32Array(3)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t];\n\t\t\t\t});\n\t\t\t\tconst cr = Math.cos(rotation * math.DEG_TO_RAD);\n\t\t\t\tconst sr = Math.sin(rotation * math.DEG_TO_RAD);\n\t\t\t\tconst uniform0 = uniform[0].value;\n\t\t\t\tuniform0[0] = cr * tiling.x;\n\t\t\t\tuniform0[1] = -sr * tiling.y;\n\t\t\t\tuniform0[2] = offset.x;\n\t\t\t\tconst uniform1 = uniform[1].value;\n\t\t\t\tuniform1[0] = sr * tiling.x;\n\t\t\t\tuniform1[1] = cr * tiling.y;\n\t\t\t\tuniform1[2] = 1.0 - tiling.y - offset.y;\n\t\t\t\treturn uniform;\n\t\t});\n}\nfunction _defineColor(name, defaultValue) {\n\t\tdefineProp({\n\t\t\t\tname: name,\n\t\t\t\tdefaultValue: defaultValue,\n\t\t\t\tgetterFunc: function() {\n\t\t\t\t\t\tthis._dirtyShader = true;\n\t\t\t\t\t\treturn this[`_${name}`];\n\t\t\t\t}\n\t\t});\n\t\tdefineUniform(name, (material, device, scene)=>{\n\t\t\t\tconst uniform = material._allocUniform(name, ()=>new Float32Array(3));\n\t\t\t\tconst color = material[name];\n\t\t\t\t_tempColor$2.linear(color);\n\t\t\t\tuniform[0] = _tempColor$2.r;\n\t\t\t\tuniform[1] = _tempColor$2.g;\n\t\t\t\tuniform[2] = _tempColor$2.b;\n\t\t\t\treturn uniform;\n\t\t});\n}\nfunction _defineFloat(name, defaultValue, getUniformFunc) {\n\t\tdefineProp({\n\t\t\t\tname: name,\n\t\t\t\tdefaultValue: defaultValue,\n\t\t\t\tdirtyShaderFunc: (oldValue, newValue)=>{\n\t\t\t\t\t\treturn (oldValue === 0 || oldValue === 1) !== (newValue === 0 || newValue === 1);\n\t\t\t\t}\n\t\t});\n\t\tdefineUniform(name, getUniformFunc);\n}\nfunction _defineObject(name, getUniformFunc) {\n\t\tdefineProp({\n\t\t\t\tname: name,\n\t\t\t\tdefaultValue: null,\n\t\t\t\tdirtyShaderFunc: (oldValue, newValue)=>{\n\t\t\t\t\t\treturn !!oldValue === !!newValue;\n\t\t\t\t}\n\t\t});\n\t\tdefineUniform(name, getUniformFunc);\n}\nfunction _defineFlag(name, defaultValue) {\n\t\tdefineProp({\n\t\t\t\tname: name,\n\t\t\t\tdefaultValue: defaultValue\n\t\t});\n}\nfunction _defineMaterialProps() {\n\t\t_defineColor('ambient', new Color(1, 1, 1));\n\t\t_defineColor('diffuse', new Color(1, 1, 1));\n\t\t_defineColor('specular', new Color(0, 0, 0));\n\t\t_defineColor('emissive', new Color(0, 0, 0));\n\t\t_defineColor('sheen', new Color(1, 1, 1));\n\t\t_defineColor('attenuation', new Color(1, 1, 1));\n\t\t_defineFloat('emissiveIntensity', 1);\n\t\t_defineFloat('specularityFactor', 1);\n\t\t_defineFloat('sheenGloss', 0.0);\n\t\t_defineFloat('gloss', 0.25);\n\t\t_defineFloat('aoIntensity', 1);\n\t\t_defineFloat('heightMapFactor', 1, (material, device, scene)=>{\n\t\t\t\treturn material.heightMapFactor * 0.025;\n\t\t});\n\t\t_defineFloat('opacity', 1);\n\t\t_defineFloat('alphaFade', 1);\n\t\t_defineFloat('alphaTest', 0);\n\t\t_defineFloat('bumpiness', 1);\n\t\t_defineFloat('normalDetailMapBumpiness', 1);\n\t\t_defineFloat('reflectivity', 1);\n\t\t_defineFloat('occludeSpecularIntensity', 1);\n\t\t_defineFloat('refraction', 0);\n\t\t_defineFloat('refractionIndex', 1.0 / 1.5);\n\t\t_defineFloat('dispersion', 0);\n\t\t_defineFloat('thickness', 0);\n\t\t_defineFloat('attenuationDistance', 0);\n\t\t_defineFloat('metalness', 1);\n\t\t_defineFloat('anisotropyIntensity', 0);\n\t\t_defineFloat('anisotropyRotation', 0);\n\t\t_defineFloat('clearCoat', 0);\n\t\t_defineFloat('clearCoatGloss', 1);\n\t\t_defineFloat('clearCoatBumpiness', 1);\n\t\t_defineFloat('aoUvSet', 0, null);\n\t\t_defineFloat('iridescence', 0);\n\t\t_defineFloat('iridescenceRefractionIndex', 1.0 / 1.5);\n\t\t_defineFloat('iridescenceThicknessMin', 0);\n\t\t_defineFloat('iridescenceThicknessMax', 0);\n\t\t_defineObject('ambientSH');\n\t\t_defineObject('cubeMapProjectionBox', (material, device, scene)=>{\n\t\t\t\tconst uniform = material._allocUniform('cubeMapProjectionBox', ()=>{\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: 'envBoxMin',\n\t\t\t\t\t\t\t\t\t\tvalue: new Float32Array(3)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: 'envBoxMax',\n\t\t\t\t\t\t\t\t\t\tvalue: new Float32Array(3)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t];\n\t\t\t\t});\n\t\t\t\tconst bboxMin = material.cubeMapProjectionBox.getMin();\n\t\t\t\tconst minUniform = uniform[0].value;\n\t\t\t\tminUniform[0] = bboxMin.x;\n\t\t\t\tminUniform[1] = bboxMin.y;\n\t\t\t\tminUniform[2] = bboxMin.z;\n\t\t\t\tconst bboxMax = material.cubeMapProjectionBox.getMax();\n\t\t\t\tconst maxUniform = uniform[1].value;\n\t\t\t\tmaxUniform[0] = bboxMax.x;\n\t\t\t\tmaxUniform[1] = bboxMax.y;\n\t\t\t\tmaxUniform[2] = bboxMax.z;\n\t\t\t\treturn uniform;\n\t\t});\n\t\t_defineFlag('specularTint', false);\n\t\t_defineFlag('specularityFactorTint', false);\n\t\t_defineFlag('useMetalness', false);\n\t\t_defineFlag('useMetalnessSpecularColor', false);\n\t\t_defineFlag('useSheen', false);\n\t\t_defineFlag('enableGGXSpecular', false);\n\t\t_defineFlag('occludeDirect', false);\n\t\t_defineFlag('opacityFadesSpecular', true);\n\t\t_defineFlag('occludeSpecular', SPECOCC_AO);\n\t\t_defineFlag('fresnelModel', FRESNEL_SCHLICK);\n\t\t_defineFlag('useDynamicRefraction', false);\n\t\t_defineFlag('cubeMapProjection', CUBEPROJ_NONE);\n\t\t_defineFlag('useFog', true);\n\t\t_defineFlag('useLighting', true);\n\t\t_defineFlag('useTonemap', true);\n\t\t_defineFlag('useSkybox', true);\n\t\t_defineFlag('forceUv1', false);\n\t\t_defineFlag('pixelSnap', false);\n\t\t_defineFlag('twoSidedLighting', false);\n\t\t_defineFlag('nineSlicedMode', undefined);\n\t\t_defineFlag('msdfTextAttribute', false);\n\t\t_defineFlag('useIridescence', false);\n\t\t_defineFlag('glossInvert', false);\n\t\t_defineFlag('sheenGlossInvert', false);\n\t\t_defineFlag('clearCoatGlossInvert', false);\n\t\t_defineFlag('opacityDither', DITHER_NONE);\n\t\t_defineFlag('opacityShadowDither', DITHER_NONE);\n\t\t_defineFlag('shadowCatcher', false);\n\t\t_defineTex2D('diffuse');\n\t\t_defineTex2D('specular');\n\t\t_defineTex2D('emissive');\n\t\t_defineTex2D('thickness', 'g');\n\t\t_defineTex2D('specularityFactor', 'g');\n\t\t_defineTex2D('normal', '');\n\t\t_defineTex2D('metalness', 'g');\n\t\t_defineTex2D('gloss', 'g');\n\t\t_defineTex2D('opacity', 'a');\n\t\t_defineTex2D('refraction', 'g');\n\t\t_defineTex2D('height', 'g', false);\n\t\t_defineTex2D('ao', 'g');\n\t\t_defineTex2D('light', 'rgb', true, 1);\n\t\t_defineTex2D('msdf', '');\n\t\t_defineTex2D('diffuseDetail', 'rgb', false);\n\t\t_defineTex2D('normalDetail', '');\n\t\t_defineTex2D('aoDetail', 'g', false);\n\t\t_defineTex2D('clearCoat', 'g');\n\t\t_defineTex2D('clearCoatGloss', 'g');\n\t\t_defineTex2D('clearCoatNormal', '');\n\t\t_defineTex2D('sheen', 'rgb');\n\t\t_defineTex2D('sheenGloss', 'g');\n\t\t_defineTex2D('iridescence', 'g');\n\t\t_defineTex2D('iridescenceThickness', 'g');\n\t\t_defineTex2D('anisotropy', '');\n\t\t_defineFlag('diffuseDetailMode', DETAILMODE_MUL);\n\t\t_defineFlag('aoDetailMode', DETAILMODE_MUL);\n\t\t_defineObject('cubeMap');\n\t\t_defineObject('sphereMap');\n\t\t_defineObject('envAtlas');\n\t\tconst getterFunc = function() {\n\t\t\t\treturn this._prefilteredCubemaps;\n\t\t};\n\t\tconst setterFunc = function(value) {\n\t\t\t\tconst cubemaps = this._prefilteredCubemaps;\n\t\t\t\tvalue = value || [];\n\t\t\t\tlet changed = false;\n\t\t\t\tlet complete = true;\n\t\t\t\tfor(let i = 0; i < 6; ++i){\n\t\t\t\t\t\tconst v = value[i] || null;\n\t\t\t\t\t\tif (cubemaps[i] !== v) {\n\t\t\t\t\t\t\t\tcubemaps[i] = v;\n\t\t\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcomplete = complete && !!cubemaps[i];\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\t\tif (complete) {\n\t\t\t\t\t\t\t\tthis.envAtlas = EnvLighting.generatePrefilteredAtlas(cubemaps, {\n\t\t\t\t\t\t\t\t\t\ttarget: this.envAtlas\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.envAtlas) {\n\t\t\t\t\t\t\t\t\t\tthis.envAtlas.destroy();\n\t\t\t\t\t\t\t\t\t\tthis.envAtlas = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._dirtyShader = true;\n\t\t\t\t}\n\t\t};\n\t\tconst empty = [\n\t\t\t\tnull,\n\t\t\t\tnull,\n\t\t\t\tnull,\n\t\t\t\tnull,\n\t\t\t\tnull,\n\t\t\t\tnull\n\t\t];\n\t\tdefinePropInternal('prefilteredCubemaps', ()=>empty.slice(), setterFunc, getterFunc);\n}\n_defineMaterialProps();\n\nconst primitiveUv1Padding = 4.0 / 64;\nconst primitiveUv1PaddingScale = 1.0 - primitiveUv1Padding * 2;\nclass ConeBaseGeometry extends Geometry {\n\t\tconstructor(baseRadius, peakRadius, height, heightSegments, capSegments, roundedCaps){\n\t\t\t\tsuper();\n\t\t\t\tconst pos = new Vec3();\n\t\t\t\tconst bottomToTop = new Vec3();\n\t\t\t\tconst norm = new Vec3();\n\t\t\t\tconst top = new Vec3();\n\t\t\t\tconst bottom = new Vec3();\n\t\t\t\tconst tangent = new Vec3();\n\t\t\t\tconst positions = [];\n\t\t\t\tconst normals = [];\n\t\t\t\tconst uvs = [];\n\t\t\t\tconst uvs1 = [];\n\t\t\t\tconst indices = [];\n\t\t\t\tlet offset;\n\t\t\t\tif (height > 0) {\n\t\t\t\t\t\tfor(let i = 0; i <= heightSegments; i++){\n\t\t\t\t\t\t\t\tfor(let j = 0; j <= capSegments; j++){\n\t\t\t\t\t\t\t\t\t\tconst theta = j / capSegments * 2 * Math.PI - Math.PI;\n\t\t\t\t\t\t\t\t\t\tconst sinTheta = Math.sin(theta);\n\t\t\t\t\t\t\t\t\t\tconst cosTheta = Math.cos(theta);\n\t\t\t\t\t\t\t\t\t\tbottom.set(sinTheta * baseRadius, -height / 2, cosTheta * baseRadius);\n\t\t\t\t\t\t\t\t\t\ttop.set(sinTheta * peakRadius, height / 2, cosTheta * peakRadius);\n\t\t\t\t\t\t\t\t\t\tpos.lerp(bottom, top, i / heightSegments);\n\t\t\t\t\t\t\t\t\t\tbottomToTop.sub2(top, bottom).normalize();\n\t\t\t\t\t\t\t\t\t\ttangent.set(cosTheta, 0, -sinTheta);\n\t\t\t\t\t\t\t\t\t\tnorm.cross(tangent, bottomToTop).normalize();\n\t\t\t\t\t\t\t\t\t\tpositions.push(pos.x, pos.y, pos.z);\n\t\t\t\t\t\t\t\t\t\tnormals.push(norm.x, norm.y, norm.z);\n\t\t\t\t\t\t\t\t\t\tlet u = j / capSegments;\n\t\t\t\t\t\t\t\t\t\tlet v = i / heightSegments;\n\t\t\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tconst _v = v;\n\t\t\t\t\t\t\t\t\t\tv = u;\n\t\t\t\t\t\t\t\t\t\tu = _v;\n\t\t\t\t\t\t\t\t\t\tu = u * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tv = v * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tu /= 3;\n\t\t\t\t\t\t\t\t\t\tuvs1.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tif (i < heightSegments && j < capSegments) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst first = i * (capSegments + 1) + j;\n\t\t\t\t\t\t\t\t\t\t\t\tconst second = i * (capSegments + 1) + (j + 1);\n\t\t\t\t\t\t\t\t\t\t\t\tconst third = (i + 1) * (capSegments + 1) + j;\n\t\t\t\t\t\t\t\t\t\t\t\tconst fourth = (i + 1) * (capSegments + 1) + (j + 1);\n\t\t\t\t\t\t\t\t\t\t\t\tindices.push(first, second, third);\n\t\t\t\t\t\t\t\t\t\t\t\tindices.push(second, fourth, third);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (roundedCaps) {\n\t\t\t\t\t\tconst latitudeBands = Math.floor(capSegments / 2);\n\t\t\t\t\t\tconst longitudeBands = capSegments;\n\t\t\t\t\t\tconst capOffset = height / 2;\n\t\t\t\t\t\tfor(let lat = 0; lat <= latitudeBands; lat++){\n\t\t\t\t\t\t\t\tconst theta = lat * Math.PI * 0.5 / latitudeBands;\n\t\t\t\t\t\t\t\tconst sinTheta = Math.sin(theta);\n\t\t\t\t\t\t\t\tconst cosTheta = Math.cos(theta);\n\t\t\t\t\t\t\t\tfor(let lon = 0; lon <= longitudeBands; lon++){\n\t\t\t\t\t\t\t\t\t\tconst phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;\n\t\t\t\t\t\t\t\t\t\tconst sinPhi = Math.sin(phi);\n\t\t\t\t\t\t\t\t\t\tconst cosPhi = Math.cos(phi);\n\t\t\t\t\t\t\t\t\t\tconst x = cosPhi * sinTheta;\n\t\t\t\t\t\t\t\t\t\tconst y = cosTheta;\n\t\t\t\t\t\t\t\t\t\tconst z = sinPhi * sinTheta;\n\t\t\t\t\t\t\t\t\t\tlet u = 1 - lon / longitudeBands;\n\t\t\t\t\t\t\t\t\t\tlet v = 1 - lat / latitudeBands;\n\t\t\t\t\t\t\t\t\t\tpositions.push(x * peakRadius, y * peakRadius + capOffset, z * peakRadius);\n\t\t\t\t\t\t\t\t\t\tnormals.push(x, y, z);\n\t\t\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tu = u * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tv = v * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tu /= 3;\n\t\t\t\t\t\t\t\t\t\tv /= 3;\n\t\t\t\t\t\t\t\t\t\tu += 1.0 / 3;\n\t\t\t\t\t\t\t\t\t\tuvs1.push(u, 1 - v);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\toffset = (heightSegments + 1) * (capSegments + 1);\n\t\t\t\t\t\tfor(let lat = 0; lat < latitudeBands; ++lat){\n\t\t\t\t\t\t\t\tfor(let lon = 0; lon < longitudeBands; ++lon){\n\t\t\t\t\t\t\t\t\t\tconst first = lat * (longitudeBands + 1) + lon;\n\t\t\t\t\t\t\t\t\t\tconst second = first + longitudeBands + 1;\n\t\t\t\t\t\t\t\t\t\tindices.push(offset + first + 1, offset + second, offset + first);\n\t\t\t\t\t\t\t\t\t\tindices.push(offset + first + 1, offset + second + 1, offset + second);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let lat = 0; lat <= latitudeBands; lat++){\n\t\t\t\t\t\t\t\tconst theta = Math.PI * 0.5 + lat * Math.PI * 0.5 / latitudeBands;\n\t\t\t\t\t\t\t\tconst sinTheta = Math.sin(theta);\n\t\t\t\t\t\t\t\tconst cosTheta = Math.cos(theta);\n\t\t\t\t\t\t\t\tfor(let lon = 0; lon <= longitudeBands; lon++){\n\t\t\t\t\t\t\t\t\t\tconst phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2;\n\t\t\t\t\t\t\t\t\t\tconst sinPhi = Math.sin(phi);\n\t\t\t\t\t\t\t\t\t\tconst cosPhi = Math.cos(phi);\n\t\t\t\t\t\t\t\t\t\tconst x = cosPhi * sinTheta;\n\t\t\t\t\t\t\t\t\t\tconst y = cosTheta;\n\t\t\t\t\t\t\t\t\t\tconst z = sinPhi * sinTheta;\n\t\t\t\t\t\t\t\t\t\tlet u = 1 - lon / longitudeBands;\n\t\t\t\t\t\t\t\t\t\tlet v = 1 - lat / latitudeBands;\n\t\t\t\t\t\t\t\t\t\tpositions.push(x * peakRadius, y * peakRadius - capOffset, z * peakRadius);\n\t\t\t\t\t\t\t\t\t\tnormals.push(x, y, z);\n\t\t\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tu = u * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tv = v * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tu /= 3;\n\t\t\t\t\t\t\t\t\t\tv /= 3;\n\t\t\t\t\t\t\t\t\t\tu += 2.0 / 3;\n\t\t\t\t\t\t\t\t\t\tuvs1.push(u, 1 - v);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\toffset = (heightSegments + 1) * (capSegments + 1) + (longitudeBands + 1) * (latitudeBands + 1);\n\t\t\t\t\t\tfor(let lat = 0; lat < latitudeBands; ++lat){\n\t\t\t\t\t\t\t\tfor(let lon = 0; lon < longitudeBands; ++lon){\n\t\t\t\t\t\t\t\t\t\tconst first = lat * (longitudeBands + 1) + lon;\n\t\t\t\t\t\t\t\t\t\tconst second = first + longitudeBands + 1;\n\t\t\t\t\t\t\t\t\t\tindices.push(offset + first + 1, offset + second, offset + first);\n\t\t\t\t\t\t\t\t\t\tindices.push(offset + first + 1, offset + second + 1, offset + second);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\toffset = (heightSegments + 1) * (capSegments + 1);\n\t\t\t\t\t\tif (baseRadius > 0) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < capSegments; i++){\n\t\t\t\t\t\t\t\t\t\tconst theta = i / capSegments * 2 * Math.PI;\n\t\t\t\t\t\t\t\t\t\tconst x = Math.sin(theta);\n\t\t\t\t\t\t\t\t\t\tconst y = -height / 2;\n\t\t\t\t\t\t\t\t\t\tconst z = Math.cos(theta);\n\t\t\t\t\t\t\t\t\t\tlet u = 1 - (x + 1) / 2;\n\t\t\t\t\t\t\t\t\t\tlet v = (z + 1) / 2;\n\t\t\t\t\t\t\t\t\t\tpositions.push(x * baseRadius, y, z * baseRadius);\n\t\t\t\t\t\t\t\t\t\tnormals.push(0, -1, 0);\n\t\t\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tu = u * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tv = v * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tu /= 3;\n\t\t\t\t\t\t\t\t\t\tv /= 3;\n\t\t\t\t\t\t\t\t\t\tu += 1 / 3;\n\t\t\t\t\t\t\t\t\t\tuvs1.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tif (i > 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tindices.push(offset, offset + i, offset + i - 1);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\toffset += capSegments;\n\t\t\t\t\t\tif (peakRadius > 0) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < capSegments; i++){\n\t\t\t\t\t\t\t\t\t\tconst theta = i / capSegments * 2 * Math.PI;\n\t\t\t\t\t\t\t\t\t\tconst x = Math.sin(theta);\n\t\t\t\t\t\t\t\t\t\tconst y = height / 2;\n\t\t\t\t\t\t\t\t\t\tconst z = Math.cos(theta);\n\t\t\t\t\t\t\t\t\t\tlet u = 1 - (x + 1) / 2;\n\t\t\t\t\t\t\t\t\t\tlet v = (z + 1) / 2;\n\t\t\t\t\t\t\t\t\t\tpositions.push(x * peakRadius, y, z * peakRadius);\n\t\t\t\t\t\t\t\t\t\tnormals.push(0, 1, 0);\n\t\t\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tu = u * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tv = v * primitiveUv1PaddingScale + primitiveUv1Padding;\n\t\t\t\t\t\t\t\t\t\tu /= 3;\n\t\t\t\t\t\t\t\t\t\tv /= 3;\n\t\t\t\t\t\t\t\t\t\tu += 2 / 3;\n\t\t\t\t\t\t\t\t\t\tuvs1.push(u, 1 - v);\n\t\t\t\t\t\t\t\t\t\tif (i > 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tindices.push(offset, offset + i - 1, offset + i);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.positions = positions;\n\t\t\t\tthis.normals = normals;\n\t\t\t\tthis.uvs = uvs;\n\t\t\t\tthis.uvs1 = uvs1;\n\t\t\t\tthis.indices = indices;\n\t\t}\n}\n\nclass CapsuleGeometry extends ConeBaseGeometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tconst radius = opts.radius ?? 0.3;\n\t\t\t\tconst height = opts.height ?? 1;\n\t\t\t\tconst heightSegments = opts.heightSegments ?? 1;\n\t\t\t\tconst sides = opts.sides ?? 20;\n\t\t\t\tsuper(radius, radius, height - 2 * radius, heightSegments, sides, true);\n\t\t\t\tif (opts.calculateTangents) {\n\t\t\t\t\t\tthis.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);\n\t\t\t\t}\n\t\t}\n}\n\nclass ConeGeometry extends ConeBaseGeometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tconst baseRadius = opts.baseRadius ?? 0.5;\n\t\t\t\tconst peakRadius = opts.peakRadius ?? 0;\n\t\t\t\tconst height = opts.height ?? 1;\n\t\t\t\tconst heightSegments = opts.heightSegments ?? 5;\n\t\t\t\tconst capSegments = opts.capSegments ?? 18;\n\t\t\t\tsuper(baseRadius, peakRadius, height, heightSegments, capSegments, false);\n\t\t\t\tif (opts.calculateTangents) {\n\t\t\t\t\t\tthis.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);\n\t\t\t\t}\n\t\t}\n}\n\nclass CylinderGeometry extends ConeBaseGeometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tconst radius = opts.radius ?? 0.5;\n\t\t\t\tconst height = opts.height ?? 1;\n\t\t\t\tconst heightSegments = opts.heightSegments ?? 5;\n\t\t\t\tconst capSegments = opts.capSegments ?? 20;\n\t\t\t\tsuper(radius, radius, height, heightSegments, capSegments, false);\n\t\t\t\tif (opts.calculateTangents) {\n\t\t\t\t\t\tthis.tangents = calculateTangents(this.positions, this.normals, this.uvs, this.indices);\n\t\t\t\t}\n\t\t}\n}\n\nclass PlaneGeometry extends Geometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tsuper();\n\t\t\t\tconst he = opts.halfExtents ?? new Vec2(0.5, 0.5);\n\t\t\t\tconst ws = opts.widthSegments ?? 5;\n\t\t\t\tconst ls = opts.lengthSegments ?? 5;\n\t\t\t\tconst positions = [];\n\t\t\t\tconst normals = [];\n\t\t\t\tconst uvs = [];\n\t\t\t\tconst indices = [];\n\t\t\t\tlet vcounter = 0;\n\t\t\t\tfor(let i = 0; i <= ws; i++){\n\t\t\t\t\t\tfor(let j = 0; j <= ls; j++){\n\t\t\t\t\t\t\t\tconst x = -he.x + 2 * he.x * i / ws;\n\t\t\t\t\t\t\t\tconst y = 0.0;\n\t\t\t\t\t\t\t\tconst z = -(-he.y + 2 * he.y * j / ls);\n\t\t\t\t\t\t\t\tconst u = i / ws;\n\t\t\t\t\t\t\t\tconst v = j / ls;\n\t\t\t\t\t\t\t\tpositions.push(x, y, z);\n\t\t\t\t\t\t\t\tnormals.push(0, 1, 0);\n\t\t\t\t\t\t\t\tuvs.push(u, 1 - v);\n\t\t\t\t\t\t\t\tif (i < ws && j < ls) {\n\t\t\t\t\t\t\t\t\t\tindices.push(vcounter + ls + 1, vcounter + 1, vcounter);\n\t\t\t\t\t\t\t\t\t\tindices.push(vcounter + ls + 1, vcounter + ls + 2, vcounter + 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tvcounter++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.positions = positions;\n\t\t\t\tthis.normals = normals;\n\t\t\t\tthis.uvs = uvs;\n\t\t\t\tthis.uvs1 = uvs;\n\t\t\t\tthis.indices = indices;\n\t\t\t\tif (opts.calculateTangents) {\n\t\t\t\t\t\tthis.tangents = calculateTangents(positions, normals, uvs, indices);\n\t\t\t\t}\n\t\t}\n}\n\nclass TorusGeometry extends Geometry {\n\t\tconstructor(opts = {}){\n\t\t\t\tsuper();\n\t\t\t\tconst rc = opts.tubeRadius ?? 0.2;\n\t\t\t\tconst rt = opts.ringRadius ?? 0.3;\n\t\t\t\tconst sectorAngle = (opts.sectorAngle ?? 360) * math.DEG_TO_RAD;\n\t\t\t\tconst segments = opts.segments ?? 30;\n\t\t\t\tconst sides = opts.sides ?? 20;\n\t\t\t\tconst positions = [];\n\t\t\t\tconst normals = [];\n\t\t\t\tconst uvs = [];\n\t\t\t\tconst indices = [];\n\t\t\t\tfor(let i = 0; i <= sides; i++){\n\t\t\t\t\t\tfor(let j = 0; j <= segments; j++){\n\t\t\t\t\t\t\t\tconst x = Math.cos(sectorAngle * j / segments) * (rt + rc * Math.cos(2 * Math.PI * i / sides));\n\t\t\t\t\t\t\t\tconst y = Math.sin(2 * Math.PI * i / sides) * rc;\n\t\t\t\t\t\t\t\tconst z = Math.sin(sectorAngle * j / segments) * (rt + rc * Math.cos(2 * Math.PI * i / sides));\n\t\t\t\t\t\t\t\tconst nx = Math.cos(sectorAngle * j / segments) * Math.cos(2 * Math.PI * i / sides);\n\t\t\t\t\t\t\t\tconst ny = Math.sin(2 * Math.PI * i / sides);\n\t\t\t\t\t\t\t\tconst nz = Math.sin(sectorAngle * j / segments) * Math.cos(2 * Math.PI * i / sides);\n\t\t\t\t\t\t\t\tconst u = i / sides;\n\t\t\t\t\t\t\t\tconst v = 1 - j / segments;\n\t\t\t\t\t\t\t\tpositions.push(x, y, z);\n\t\t\t\t\t\t\t\tnormals.push(nx, ny, nz);\n\t\t\t\t\t\t\t\tuvs.push(u, 1.0 - v);\n\t\t\t\t\t\t\t\tif (i < sides && j < segments) {\n\t\t\t\t\t\t\t\t\t\tconst first = i * (segments + 1) + j;\n\t\t\t\t\t\t\t\t\t\tconst second = (i + 1) * (segments + 1) + j;\n\t\t\t\t\t\t\t\t\t\tconst third = i * (segments + 1) + (j + 1);\n\t\t\t\t\t\t\t\t\t\tconst fourth = (i + 1) * (segments + 1) + (j + 1);\n\t\t\t\t\t\t\t\t\t\tindices.push(first, second, third);\n\t\t\t\t\t\t\t\t\t\tindices.push(second, fourth, third);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.positions = positions;\n\t\t\t\tthis.normals = normals;\n\t\t\t\tthis.uvs = uvs;\n\t\t\t\tthis.uvs1 = uvs;\n\t\t\t\tthis.indices = indices;\n\t\t\t\tif (opts.calculateTangents) {\n\t\t\t\t\t\tthis.tangents = calculateTangents(positions, normals, uvs, indices);\n\t\t\t\t}\n\t\t}\n}\n\nclass ProgramLibrary {\n\t\tconstructor(device, standardMaterial){\n\t\t\t\tthis.processedCache = new Map();\n\t\t\t\tthis.definitionsCache = new Map();\n\t\t\t\tthis._generators = new Map();\n\t\t\t\tthis._device = device;\n\t\t\t\tthis._isClearingCache = false;\n\t\t\t\tthis._precached = false;\n\t\t\t\tthis._programsCollection = [];\n\t\t\t\tthis._defaultStdMatOption = new StandardMaterialOptions();\n\t\t\t\tthis._defaultStdMatOptionMin = new StandardMaterialOptions();\n\t\t\t\tconst defaultCameraShaderParams = new CameraShaderParams();\n\t\t\t\tstandardMaterial.shaderOptBuilder.updateRef(this._defaultStdMatOption, {}, defaultCameraShaderParams, standardMaterial, null, [], SHADER_FORWARD, null);\n\t\t\t\tstandardMaterial.shaderOptBuilder.updateMinRef(this._defaultStdMatOptionMin, {}, standardMaterial, null, SHADER_SHADOW, null);\n\t\t\t\tdevice.on('destroy:shader', (shader)=>{\n\t\t\t\t\t\tthis.removeFromCache(shader);\n\t\t\t\t});\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.clearCache();\n\t\t}\n\t\tregister(name, generator) {\n\t\t\t\tif (!this._generators.has(name)) {\n\t\t\t\t\t\tthis._generators.set(name, generator);\n\t\t\t\t}\n\t\t}\n\t\tunregister(name) {\n\t\t\t\tif (this._generators.has(name)) {\n\t\t\t\t\t\tthis._generators.delete(name);\n\t\t\t\t}\n\t\t}\n\t\tisRegistered(name) {\n\t\t\t\treturn this._generators.has(name);\n\t\t}\n\t\tgenerateShaderDefinition(generator, name, key, options) {\n\t\t\t\tlet def = this.definitionsCache.get(key);\n\t\t\t\tif (!def) {\n\t\t\t\t\t\tlet lights;\n\t\t\t\t\t\tif (options.litOptions?.lights) {\n\t\t\t\t\t\t\t\tlights = options.litOptions.lights;\n\t\t\t\t\t\t\t\toptions.litOptions.lights = lights.map((l)=>{\n\t\t\t\t\t\t\t\t\t\tconst lcopy = l.clone ? l.clone() : l;\n\t\t\t\t\t\t\t\t\t\tlcopy.key = l.key;\n\t\t\t\t\t\t\t\t\t\treturn lcopy;\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.storeNewProgram(name, options);\n\t\t\t\t\t\tif (options.litOptions?.lights) {\n\t\t\t\t\t\t\t\toptions.litOptions.lights = lights;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._precached) ;\n\t\t\t\t\t\tconst device = this._device;\n\t\t\t\t\t\tdef = generator.createShaderDefinition(device, options);\n\t\t\t\t\t\tdef.name = def.name ?? (options.pass ? `${name}-pass:${options.pass}` : name);\n\t\t\t\t\t\tthis.definitionsCache.set(key, def);\n\t\t\t\t}\n\t\t\t\treturn def;\n\t\t}\n\t\tgetCachedShader(key) {\n\t\t\t\treturn this.processedCache.get(key);\n\t\t}\n\t\tsetCachedShader(key, shader) {\n\t\t\t\tthis.processedCache.set(key, shader);\n\t\t}\n\t\tgetProgram(name, options, processingOptions, userMaterialId) {\n\t\t\t\tconst generator = this._generators.get(name);\n\t\t\t\tif (!generator) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst generationKeyString = generator.generateKey(options);\n\t\t\t\tconst generationKey = hashCode(generationKeyString);\n\t\t\t\tconst processingKeyString = processingOptions.generateKey(this._device);\n\t\t\t\tconst processingKey = hashCode(processingKeyString);\n\t\t\t\tconst totalKey = `${generationKey}#${processingKey}`;\n\t\t\t\tlet processedShader = this.getCachedShader(totalKey);\n\t\t\t\tif (!processedShader) {\n\t\t\t\t\t\tconst generatedShaderDef = this.generateShaderDefinition(generator, name, generationKey, options);\n\t\t\t\t\t\tlet passName = '';\n\t\t\t\t\t\tlet shaderPassInfo;\n\t\t\t\t\t\tif (options.pass !== undefined) {\n\t\t\t\t\t\t\t\tshaderPassInfo = ShaderPass.get(this._device).getByIndex(options.pass);\n\t\t\t\t\t\t\t\tpassName = `-${shaderPassInfo.name}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._device.fire('shader:generate', {\n\t\t\t\t\t\t\t\tuserMaterialId,\n\t\t\t\t\t\t\t\tshaderPassInfo,\n\t\t\t\t\t\t\t\tdefinition: generatedShaderDef\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst shaderDefinition = {\n\t\t\t\t\t\t\t\tname: `${generatedShaderDef.name}${passName}-proc`,\n\t\t\t\t\t\t\t\tattributes: generatedShaderDef.attributes,\n\t\t\t\t\t\t\t\tvshader: generatedShaderDef.vshader,\n\t\t\t\t\t\t\t\tvincludes: generatedShaderDef.vincludes,\n\t\t\t\t\t\t\t\tfincludes: generatedShaderDef.fincludes,\n\t\t\t\t\t\t\t\tfshader: generatedShaderDef.fshader,\n\t\t\t\t\t\t\t\tprocessingOptions: processingOptions,\n\t\t\t\t\t\t\t\tshaderLanguage: generatedShaderDef.shaderLanguage,\n\t\t\t\t\t\t\t\tmeshUniformBufferFormat: generatedShaderDef.meshUniformBufferFormat,\n\t\t\t\t\t\t\t\tmeshBindGroupFormat: generatedShaderDef.meshBindGroupFormat\n\t\t\t\t\t\t};\n\t\t\t\t\t\tprocessedShader = new Shader(this._device, shaderDefinition);\n\t\t\t\t\t\tthis.setCachedShader(totalKey, processedShader);\n\t\t\t\t}\n\t\t\t\treturn processedShader;\n\t\t}\n\t\tstoreNewProgram(name, options) {\n\t\t\t\tlet opt = {};\n\t\t\t\tif (name === 'standard') {\n\t\t\t\t\t\tconst defaultMat = this._getDefaultStdMatOptions(options.pass);\n\t\t\t\t\t\tfor(const p in options){\n\t\t\t\t\t\t\t\tif (options.hasOwnProperty(p) && defaultMat[p] !== options[p] || p === 'pass') {\n\t\t\t\t\t\t\t\t\t\topt[p] = options[p];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(const p in options.litOptions){\n\t\t\t\t\t\t\t\topt[p] = options.litOptions[p];\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\topt = options;\n\t\t\t\t}\n\t\t\t\tthis._programsCollection.push(JSON.stringify({\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\toptions: opt\n\t\t\t\t}));\n\t\t}\n\t\tdumpPrograms() {\n\t\t\t\tlet text = 'let device = pc.app ? pc.app.graphicsDevice : pc.Application.getApplication().graphicsDevice;\\n';\n\t\t\t\ttext += 'let shaders = [';\n\t\t\t\tif (this._programsCollection[0]) {\n\t\t\t\t\t\ttext += `\\n\\t${this._programsCollection[0]}`;\n\t\t\t\t}\n\t\t\t\tfor(let i = 1; i < this._programsCollection.length; ++i){\n\t\t\t\t\t\ttext += `,\\n\\t${this._programsCollection[i]}`;\n\t\t\t\t}\n\t\t\t\ttext += '\\n];\\n';\n\t\t\t\ttext += 'pc.getProgramLibrary(device).precompile(shaders);\\n';\n\t\t\t\ttext += `if (pc.version != \\\"${version}\\\" || pc.revision != \\\"${revision}\\\")\\n`;\n\t\t\t\ttext += '\\tconsole.warn(\\\"precompile-shaders.js: engine version mismatch, rebuild shaders lib with current engine\\\");';\n\t\t\t\tconst element = document.createElement('a');\n\t\t\t\telement.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`);\n\t\t\t\telement.setAttribute('download', 'precompile-shaders.js');\n\t\t\t\telement.style.display = 'none';\n\t\t\t\tdocument.body.appendChild(element);\n\t\t\t\telement.click();\n\t\t\t\tdocument.body.removeChild(element);\n\t\t}\n\t\tclearCache() {\n\t\t\t\tthis._isClearingCache = true;\n\t\t\t\tthis.processedCache.forEach((shader)=>{\n\t\t\t\t\t\tshader.destroy();\n\t\t\t\t});\n\t\t\t\tthis.processedCache.clear();\n\t\t\t\tthis._isClearingCache = false;\n\t\t}\n\t\tremoveFromCache(shader) {\n\t\t\t\tif (this._isClearingCache) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.processedCache.forEach((cachedShader, key)=>{\n\t\t\t\t\t\tif (shader === cachedShader) {\n\t\t\t\t\t\t\t\tthis.processedCache.delete(key);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\t_getDefaultStdMatOptions(pass) {\n\t\t\t\tconst shaderPassInfo = ShaderPass.get(this._device).getByIndex(pass);\n\t\t\t\treturn pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow ? this._defaultStdMatOptionMin : this._defaultStdMatOption;\n\t\t}\n\t\tprecompile(cache) {\n\t\t\t\tif (cache) {\n\t\t\t\t\t\tconst shaders = new Array(cache.length);\n\t\t\t\t\t\tfor(let i = 0; i < cache.length; i++){\n\t\t\t\t\t\t\t\tif (cache[i].name === 'standard') {\n\t\t\t\t\t\t\t\t\t\tconst opt = cache[i].options;\n\t\t\t\t\t\t\t\t\t\tconst defaultMat = this._getDefaultStdMatOptions(opt.pass);\n\t\t\t\t\t\t\t\t\t\tfor(const p in defaultMat){\n\t\t\t\t\t\t\t\t\t\t\t\tif (defaultMat.hasOwnProperty(p) && opt[p] === undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\topt[p] = defaultMat[p];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tshaders[i] = this.getProgram(cache[i].name, cache[i].options);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._precached = true;\n\t\t}\n}\n\nconst mat4$1 = new Mat4();\nconst quat$2 = new Quat();\nconst aabb = new BoundingBox();\nconst aabb2 = new BoundingBox();\nconst debugColor = new Color(1, 1, 0, 0.4);\nconst SH_C0$2 = 0.28209479177387814;\nclass SplatIterator {\n\t\tconstructor(gsplatData, p, r, s, c){\n\t\t\t\tconst x = gsplatData.getProp('x');\n\t\t\t\tconst y = gsplatData.getProp('y');\n\t\t\t\tconst z = gsplatData.getProp('z');\n\t\t\t\tconst rx = gsplatData.getProp('rot_1');\n\t\t\t\tconst ry = gsplatData.getProp('rot_2');\n\t\t\t\tconst rz = gsplatData.getProp('rot_3');\n\t\t\t\tconst rw = gsplatData.getProp('rot_0');\n\t\t\t\tconst sx = gsplatData.getProp('scale_0');\n\t\t\t\tconst sy = gsplatData.getProp('scale_1');\n\t\t\t\tconst sz = gsplatData.getProp('scale_2');\n\t\t\t\tconst cr = gsplatData.getProp('f_dc_0');\n\t\t\t\tconst cg = gsplatData.getProp('f_dc_1');\n\t\t\t\tconst cb = gsplatData.getProp('f_dc_2');\n\t\t\t\tconst ca = gsplatData.getProp('opacity');\n\t\t\t\tconst sigmoid = (v)=>{\n\t\t\t\t\t\tif (v > 0) {\n\t\t\t\t\t\t\t\treturn 1 / (1 + Math.exp(-v));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst t = Math.exp(v);\n\t\t\t\t\t\treturn t / (1 + t);\n\t\t\t\t};\n\t\t\t\tthis.read = (i)=>{\n\t\t\t\t\t\tif (p) {\n\t\t\t\t\t\t\t\tp.x = x[i];\n\t\t\t\t\t\t\t\tp.y = y[i];\n\t\t\t\t\t\t\t\tp.z = z[i];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (r) {\n\t\t\t\t\t\t\t\tr.set(rx[i], ry[i], rz[i], rw[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (s) {\n\t\t\t\t\t\t\t\ts.set(Math.exp(sx[i]), Math.exp(sy[i]), Math.exp(sz[i]));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (c) {\n\t\t\t\t\t\t\t\tc.set(0.5 + cr[i] * SH_C0$2, 0.5 + cg[i] * SH_C0$2, 0.5 + cb[i] * SH_C0$2, sigmoid(ca[i]));\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t}\n}\nconst calcSplatMat = (result, p, r)=>{\n\t\tquat$2.set(r.x, r.y, r.z, r.w).normalize();\n\t\tresult.setTRS(p, quat$2, Vec3.ONE);\n};\nclass GSplatData {\n\t\tconstructor(elements, comments = []){\n\t\t\t\tthis.elements = elements;\n\t\t\t\tthis.numSplats = this.getElement('vertex').count;\n\t\t\t\tthis.comments = comments;\n\t\t}\n\t\tstatic calcSplatAabb(result, p, r, s) {\n\t\t\t\tcalcSplatMat(mat4$1, p, r);\n\t\t\t\taabb.center.set(0, 0, 0);\n\t\t\t\taabb.halfExtents.set(s.x * 2, s.y * 2, s.z * 2);\n\t\t\t\tresult.setFromTransformedAabb(aabb, mat4$1);\n\t\t}\n\t\tgetProp(name, elementName = 'vertex') {\n\t\t\t\treturn this.getElement(elementName)?.properties.find((p)=>p.name === name)?.storage;\n\t\t}\n\t\tgetElement(name) {\n\t\t\t\treturn this.elements.find((e)=>e.name === name);\n\t\t}\n\t\taddProp(name, storage) {\n\t\t\t\tthis.getElement('vertex').properties.push({\n\t\t\t\t\t\ttype: 'float',\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tstorage,\n\t\t\t\t\t\tbyteSize: 4\n\t\t\t\t});\n\t\t}\n\t\tcreateIter(p, r, s, c) {\n\t\t\t\treturn new SplatIterator(this, p, r, s, c);\n\t\t}\n\t\tcalcAabb(result, pred) {\n\t\t\t\tlet mx, my, mz, Mx, My, Mz;\n\t\t\t\tlet first = true;\n\t\t\t\tconst x = this.getProp('x');\n\t\t\t\tconst y = this.getProp('y');\n\t\t\t\tconst z = this.getProp('z');\n\t\t\t\tconst sx = this.getProp('scale_0');\n\t\t\t\tconst sy = this.getProp('scale_1');\n\t\t\t\tconst sz = this.getProp('scale_2');\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\tif (pred && !pred(i)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst px = x[i];\n\t\t\t\t\t\tconst py = y[i];\n\t\t\t\t\t\tconst pz = z[i];\n\t\t\t\t\t\tconst scale = Math.max(sx[i], sy[i], sz[i]);\n\t\t\t\t\t\tif (!isFinite(px) || !isFinite(py) || !isFinite(pz) || !isFinite(scale)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst scaleVal = 2.0 * Math.exp(scale);\n\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t\t\t\tmx = px - scaleVal;\n\t\t\t\t\t\t\t\tmy = py - scaleVal;\n\t\t\t\t\t\t\t\tmz = pz - scaleVal;\n\t\t\t\t\t\t\t\tMx = px + scaleVal;\n\t\t\t\t\t\t\t\tMy = py + scaleVal;\n\t\t\t\t\t\t\t\tMz = pz + scaleVal;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmx = Math.min(mx, px - scaleVal);\n\t\t\t\t\t\t\t\tmy = Math.min(my, py - scaleVal);\n\t\t\t\t\t\t\t\tmz = Math.min(mz, pz - scaleVal);\n\t\t\t\t\t\t\t\tMx = Math.max(Mx, px + scaleVal);\n\t\t\t\t\t\t\t\tMy = Math.max(My, py + scaleVal);\n\t\t\t\t\t\t\t\tMz = Math.max(Mz, pz + scaleVal);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!first) {\n\t\t\t\t\t\tresult.center.set((mx + Mx) * 0.5, (my + My) * 0.5, (mz + Mz) * 0.5);\n\t\t\t\t\t\tresult.halfExtents.set((Mx - mx) * 0.5, (My - my) * 0.5, (Mz - mz) * 0.5);\n\t\t\t\t}\n\t\t\t\treturn !first;\n\t\t}\n\t\tcalcAabbExact(result, pred) {\n\t\t\t\tconst p = new Vec3();\n\t\t\t\tconst r = new Quat();\n\t\t\t\tconst s = new Vec3();\n\t\t\t\tconst iter = this.createIter(p, r, s);\n\t\t\t\tlet first = true;\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\tif (pred && !pred(i)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\titer.read(i);\n\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t\t\t\tGSplatData.calcSplatAabb(result, p, r, s);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tGSplatData.calcSplatAabb(aabb2, p, r, s);\n\t\t\t\t\t\t\t\tresult.add(aabb2);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn !first;\n\t\t}\n\t\tgetCenters(result) {\n\t\t\t\tconst x = this.getProp('x');\n\t\t\t\tconst y = this.getProp('y');\n\t\t\t\tconst z = this.getProp('z');\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\tresult[i * 3 + 0] = x[i];\n\t\t\t\t\t\tresult[i * 3 + 1] = y[i];\n\t\t\t\t\t\tresult[i * 3 + 2] = z[i];\n\t\t\t\t}\n\t\t}\n\t\tcalcFocalPoint(result, pred) {\n\t\t\t\tconst x = this.getProp('x');\n\t\t\t\tconst y = this.getProp('y');\n\t\t\t\tconst z = this.getProp('z');\n\t\t\t\tconst sx = this.getProp('scale_0');\n\t\t\t\tconst sy = this.getProp('scale_1');\n\t\t\t\tconst sz = this.getProp('scale_2');\n\t\t\t\tresult.x = 0;\n\t\t\t\tresult.y = 0;\n\t\t\t\tresult.z = 0;\n\t\t\t\tlet sum = 0;\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\tif (pred && !pred(i)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst px = x[i];\n\t\t\t\t\t\tconst py = y[i];\n\t\t\t\t\t\tconst pz = z[i];\n\t\t\t\t\t\tif (!isFinite(px) || !isFinite(py) || !isFinite(pz)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst weight = 1.0 / (1.0 + Math.exp(Math.max(sx[i], sy[i], sz[i])));\n\t\t\t\t\t\tresult.x += px * weight;\n\t\t\t\t\t\tresult.y += py * weight;\n\t\t\t\t\t\tresult.z += pz * weight;\n\t\t\t\t\t\tsum += weight;\n\t\t\t\t}\n\t\t\t\tresult.mulScalar(1 / sum);\n\t\t}\n\t\trenderWireframeBounds(scene, worldMat) {\n\t\t\t\tconst p = new Vec3();\n\t\t\t\tconst r = new Quat();\n\t\t\t\tconst s = new Vec3();\n\t\t\t\tconst min = new Vec3();\n\t\t\t\tconst max = new Vec3();\n\t\t\t\tconst iter = this.createIter(p, r, s);\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\titer.read(i);\n\t\t\t\t\t\tcalcSplatMat(mat4$1, p, r);\n\t\t\t\t\t\tmat4$1.mul2(worldMat, mat4$1);\n\t\t\t\t\t\tmin.set(s.x * -2, s.y * -2, s.z * -2);\n\t\t\t\t\t\tmax.set(s.x * 2.0, s.y * 2.0, s.z * 2.0);\n\t\t\t\t\t\tscene.immediate.drawWireAlignedBox(min, max, debugColor, true, scene.defaultDrawLayer, mat4$1);\n\t\t\t\t}\n\t\t}\n\t\tget isCompressed() {\n\t\t\t\treturn false;\n\t\t}\n\t\tget shBands() {\n\t\t\t\tconst numProps = ()=>{\n\t\t\t\t\t\tfor(let i = 0; i < 45; ++i){\n\t\t\t\t\t\t\t\tif (!this.getProp(`f_rest_${i}`)) {\n\t\t\t\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn 45;\n\t\t\t\t};\n\t\t\t\tconst sizes = {\n\t\t\t\t\t\t9: 1,\n\t\t\t\t\t\t24: 2,\n\t\t\t\t\t\t45: 3\n\t\t\t\t};\n\t\t\t\treturn sizes[numProps()] ?? 0;\n\t\t}\n\t\tcalcMortonOrder() {\n\t\t\t\tconst calcMinMax = (arr)=>{\n\t\t\t\t\t\tlet min = arr[0];\n\t\t\t\t\t\tlet max = arr[0];\n\t\t\t\t\t\tfor(let i = 1; i < arr.length; i++){\n\t\t\t\t\t\t\t\tif (arr[i] < min) min = arr[i];\n\t\t\t\t\t\t\t\tif (arr[i] > max) max = arr[i];\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tmin,\n\t\t\t\t\t\t\t\tmax\n\t\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t\tconst encodeMorton3 = (x, y, z)=>{\n\t\t\t\t\t\tconst Part1By2 = (x)=>{\n\t\t\t\t\t\t\t\tx &= 0x000003ff;\n\t\t\t\t\t\t\t\tx = (x ^ x << 16) & 0xff0000ff;\n\t\t\t\t\t\t\t\tx = (x ^ x << 8) & 0x0300f00f;\n\t\t\t\t\t\t\t\tx = (x ^ x << 4) & 0x030c30c3;\n\t\t\t\t\t\t\t\tx = (x ^ x << 2) & 0x09249249;\n\t\t\t\t\t\t\t\treturn x;\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn (Part1By2(z) << 2) + (Part1By2(y) << 1) + Part1By2(x);\n\t\t\t\t};\n\t\t\t\tconst x = this.getProp('x');\n\t\t\t\tconst y = this.getProp('y');\n\t\t\t\tconst z = this.getProp('z');\n\t\t\t\tconst { min: minX, max: maxX } = calcMinMax(x);\n\t\t\t\tconst { min: minY, max: maxY } = calcMinMax(y);\n\t\t\t\tconst { min: minZ, max: maxZ } = calcMinMax(z);\n\t\t\t\tconst sizeX = minX === maxX ? 0 : 1024 / (maxX - minX);\n\t\t\t\tconst sizeY = minY === maxY ? 0 : 1024 / (maxY - minY);\n\t\t\t\tconst sizeZ = minZ === maxZ ? 0 : 1024 / (maxZ - minZ);\n\t\t\t\tconst codes = new Map();\n\t\t\t\tfor(let i = 0; i < this.numSplats; i++){\n\t\t\t\t\t\tconst ix = Math.min(1023, Math.floor((x[i] - minX) * sizeX));\n\t\t\t\t\t\tconst iy = Math.min(1023, Math.floor((y[i] - minY) * sizeY));\n\t\t\t\t\t\tconst iz = Math.min(1023, Math.floor((z[i] - minZ) * sizeZ));\n\t\t\t\t\t\tconst code = encodeMorton3(ix, iy, iz);\n\t\t\t\t\t\tconst val = codes.get(code);\n\t\t\t\t\t\tif (val) {\n\t\t\t\t\t\t\t\tval.push(i);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcodes.set(code, [\n\t\t\t\t\t\t\t\t\t\ti\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst keys = Array.from(codes.keys()).sort((a, b)=>a - b);\n\t\t\t\tconst indices = new Uint32Array(this.numSplats);\n\t\t\t\tlet idx = 0;\n\t\t\t\tfor(let i = 0; i < keys.length; ++i){\n\t\t\t\t\t\tconst val = codes.get(keys[i]);\n\t\t\t\t\t\tfor(let j = 0; j < val.length; ++j){\n\t\t\t\t\t\t\t\tindices[idx++] = val[j];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn indices;\n\t\t}\n\t\treorder(order) {\n\t\t\t\tconst cache = new Map();\n\t\t\t\tconst getStorage = (size)=>{\n\t\t\t\t\t\tif (cache.has(size)) {\n\t\t\t\t\t\t\t\tconst buffer = cache.get(size);\n\t\t\t\t\t\t\t\tcache.delete(size);\n\t\t\t\t\t\t\t\treturn buffer;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn new ArrayBuffer(size);\n\t\t\t\t};\n\t\t\t\tconst returnStorage = (buffer)=>{\n\t\t\t\t\t\tcache.set(buffer.byteLength, buffer);\n\t\t\t\t};\n\t\t\t\tconst reorder = (data)=>{\n\t\t\t\t\t\tconst result = new data.constructor(getStorage(data.byteLength));\n\t\t\t\t\t\tfor(let i = 0; i < order.length; i++){\n\t\t\t\t\t\t\t\tresult[i] = data[order[i]];\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturnStorage(data.buffer);\n\t\t\t\t\t\treturn result;\n\t\t\t\t};\n\t\t\t\tthis.elements.forEach((element)=>{\n\t\t\t\t\t\telement.properties.forEach((property)=>{\n\t\t\t\t\t\t\t\tif (property.storage) {\n\t\t\t\t\t\t\t\t\t\tproperty.storage = reorder(property.storage);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t}\n\t\treorderData() {\n\t\t\t\tthis.reorder(this.calcMortonOrder());\n\t\t}\n}\n\nvar glslGsplatCopyToWorkBufferPS = `\n#define GSPLAT_CENTER_NOPROJ\n#include \"gsplatStructsVS\"\n#include \"gsplatCenterVS\"\n#include \"gsplatEvalSHVS\"\n#include \"gsplatQuatToMat3VS\"\n#include \"gsplatSourceFormatVS\"\nuniform mat4 uTransform;\nuniform int uStartLine;\nuniform int uViewportWidth;\n#ifdef GSPLAT_LOD\n\tuniform usampler2D uIntervalsTexture;\n#endif\n#ifdef GSPLAT_COLORIZE\n\tuniform vec3 uLodColor;\n#endif\nuniform int uActiveSplats;\nvoid main(void) {\n\tivec2 localFragCoords = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y) - uStartLine);\n\tint targetIndex = localFragCoords.y * uViewportWidth + localFragCoords.x;\n\tif (targetIndex >= uActiveSplats) {\n\t\tpcFragColor0 = vec4(0.0);\n\t\tpcFragColor1 = vec4(0.0);\n\t\tpcFragColor2 = vec4(0.0);\n\t\tpcFragColor3 = vec4(0.0);\n\t} else {\n\t\t#ifdef GSPLAT_LOD\n\t\t\tint intervalsSize = int(textureSize(uIntervalsTexture, 0).x);\n\t\t\tivec2 intervalUV = ivec2(targetIndex % intervalsSize, targetIndex / intervalsSize);\n\t\t\tuint originalIndex = texelFetch(uIntervalsTexture, intervalUV, 0).r;\n\t\t#else\n\t\t\tuint originalIndex = uint(targetIndex);\n\t\t#endif\n\t\t\n\t\t#if defined(GSPLAT_SOGS_DATA) || defined(GSPLAT_COMPRESSED_DATA)\n\t\t\tuint srcSize = uint(textureSize(packedTexture, 0).x);\n\t\t#else\n\t\t\tuint srcSize = uint(textureSize(splatColor, 0).x);\n\t\t#endif\n\t\t\n\t\tSplatSource source;\n\t\tsource.id = uint(originalIndex);\n\t\tsource.uv = ivec2(source.id % srcSize, source.id / srcSize);\n\t\tvec3 modelCenter = readCenter(source);\n\t\tmodelCenter = (uTransform * vec4(modelCenter, 1.0)).xyz;\n\t\tSplatCenter center;\n\t\tinitCenter(modelCenter, center);\n\t\tvec3 covA, covB;\n\t\treadCovariance(source, covA, covB);\n\t\tmat3 C = mat3(\n\t\t\tcovA.x, covA.y, covA.z,\n\t\t\tcovA.y, covB.x, covB.y,\n\t\t\tcovA.z, covB.y, covB.z\n\t\t);\n\t\tmat3 linear = mat3(uTransform);\n\t\tmat3 Ct = linear * C * transpose(linear);\n\t\tcovA = Ct[0];\n\t\tcovB = vec3(Ct[1][1], Ct[1][2], Ct[2][2]);\n\t\tvec4 color = readColor(source);\n\t\t#if SH_BANDS > 0\n\t\t\tvec3 dir = normalize(center.view * mat3(center.modelView));\n\t\t\tvec3 sh[SH_COEFFS];\n\t\t\tfloat scale;\n\t\t\treadSHData(source, sh, scale);\n\t\t\tcolor.xyz += evalSH(sh, dir) * scale;\n\t\t#endif\n\t\t#ifdef GSPLAT_COLORIZE\n\t\t\tcolor.xyz *= uLodColor;\n\t\t#endif\n\t\tpcFragColor0 = color;\n\t\tpcFragColor1 = vec4(modelCenter, 1.0);\n\t\tpcFragColor2 = vec4(covA, 1.0);\n\t\tpcFragColor3 = vec4(covB, 1.0);\n\t}\n}\n`;\n\nvar wgslGsplatCopyToWorkBufferPS = `\n#define GSPLAT_CENTER_NOPROJ\n#include \"gsplatStructsVS\"\n#include \"gsplatCenterVS\"\n#include \"gsplatEvalSHVS\"\n#include \"gsplatQuatToMat3VS\"\n#include \"gsplatSourceFormatVS\"\nuniform uTransform: mat4x4f;\nuniform uStartLine: i32;\nuniform uViewportWidth: i32;\n#ifdef GSPLAT_LOD\n\tvar uIntervalsTexture: texture_2d<u32>;\n#endif\n#ifdef GSPLAT_COLORIZE\n\tuniform uLodColor: vec3f;\n#endif\nuniform uActiveSplats: i32;\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\t\n\tlet localFragCoords = vec2i(i32(input.position.x), i32(input.position.y) - uniform.uStartLine);\n\tlet targetIndex = localFragCoords.y * uniform.uViewportWidth + localFragCoords.x;\n\t\n\tif (targetIndex >= uniform.uActiveSplats) {\n\t\toutput.color = vec4f(0.0);\n\t\toutput.color1 = vec4f(0.0);\n\t\toutput.color2 = vec4f(0.0);\n\t\toutput.color3 = vec4f(0.0);\n\t} else {\n\t\t#ifdef GSPLAT_LOD\n\t\t\tlet intervalsSize = i32(textureDimensions(uIntervalsTexture, 0).x);\n\t\t\tlet intervalUV = vec2i(targetIndex % intervalsSize, targetIndex / intervalsSize);\n\t\t\tlet originalIndex = textureLoad(uIntervalsTexture, intervalUV, 0).r;\n\t\t#else\n\t\t\tlet originalIndex = targetIndex;\n\t\t#endif\n\t\t\n\t\tvar srcSize: u32;\n\t\t#if defined(GSPLAT_SOGS_DATA) || defined(GSPLAT_COMPRESSED_DATA)\n\t\t\tsrcSize = u32(textureDimensions(packedTexture, 0).x);\n\t\t#else\n\t\t\tsrcSize = u32(textureDimensions(splatColor, 0).x);\n\t\t#endif\n\t\t\n\t\tvar source: SplatSource;\n\t\tsource.id = u32(originalIndex);\n\t\tsource.uv = vec2i(i32(source.id % srcSize), i32(source.id / srcSize));\n\t\tvar modelCenter = readCenter(&source);\n\t\tmodelCenter = (uniform.uTransform * vec4f(modelCenter, 1.0)).xyz;\n\t\tvar center: SplatCenter;\n\t\tinitCenter(modelCenter, &center);\n\t\tvar covA: vec3f;\n\t\tvar covB: vec3f;\n\t\treadCovariance(&source, &covA, &covB);\n\t\tlet C = mat3x3f(\n\t\t\tvec3f(covA.x, covA.y, covA.z),\n\t\t\tvec3f(covA.y, covB.x, covB.y),\n\t\t\tvec3f(covA.z, covB.y, covB.z)\n\t\t);\n\t\tlet linear = mat3x3f(uniform.uTransform[0].xyz, uniform.uTransform[1].xyz, uniform.uTransform[2].xyz);\n\t\tlet Ct = linear * C * transpose(linear);\n\t\tcovA = Ct[0];\n\t\tcovB = vec3f(Ct[1][1], Ct[1][2], Ct[2][2]);\n\t\tvar color = readColor(&source);\n\t\t#if SH_BANDS > 0\n\t\t\tlet dir = normalize(center.view * mat3x3f(center.modelView[0].xyz, center.modelView[1].xyz, center.modelView[2].xyz));\n\t\t\tvar sh: array<vec3f, SH_COEFFS>;\n\t\t\tvar scale: f32;\n\t\t\treadSHData(&source, &sh, &scale);\n\t\t\tcolor = vec4f(color.xyz + evalSH(&sh, dir) * scale, color.w);\n\t\t#endif\n\t\t#ifdef GSPLAT_COLORIZE\n\t\t\tcolor = vec4f(color.xyz * uniform.uLodColor, color.w);\n\t\t#endif\n\t\toutput.color = color;\n\t\toutput.color1 = vec4f(modelCenter, 1.0);\n\t\toutput.color2 = vec4f(covA, 1.0);\n\t\toutput.color3 = vec4f(covB, 1.0);\n\t}\n\t\n\treturn output;\n}\n`;\n\nlet id$1 = 0;\nconst tempMap = new Map();\nclass WorkBufferRenderInfo {\n\t\tconstructor(device, key, material){\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.material = material;\n\t\t\t\tconst clonedDefines = new Map(material.defines);\n\t\t\t\tconst shader = ShaderUtils.createShader(this.device, {\n\t\t\t\t\t\tuniqueName: `SplatCopyToWorkBuffer:${key}`,\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvertexDefines: clonedDefines,\n\t\t\t\t\t\tfragmentDefines: clonedDefines,\n\t\t\t\t\t\tvertexChunk: 'fullscreenQuadVS',\n\t\t\t\t\t\tfragmentGLSL: glslGsplatCopyToWorkBufferPS,\n\t\t\t\t\t\tfragmentWGSL: wgslGsplatCopyToWorkBufferPS\n\t\t\t\t});\n\t\t\t\tthis.quadRender = new QuadRender(shader);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.material?.destroy();\n\t\t\t\tthis.quadRender?.destroy();\n\t\t}\n}\nclass GSplatResourceBase {\n\t\tconstructor(device, gsplatData){\n\t\t\t\tthis.id = id$1++;\n\t\t\t\tthis.workBufferRenderInfos = new Map();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.gsplatData = gsplatData;\n\t\t\t\tthis.centers = new Float32Array(gsplatData.numSplats * 3);\n\t\t\t\tgsplatData.getCenters(this.centers);\n\t\t\t\tthis.aabb = new BoundingBox();\n\t\t\t\tgsplatData.calcAabb(this.aabb);\n\t\t\t\tthis.mesh = GSplatResourceBase.createMesh(device);\n\t\t\t\tthis.instanceIndices = GSplatResourceBase.createInstanceIndices(device, gsplatData.numSplats);\n\t\t\t\tthis.mesh.incRefCount();\n\t\t\t\tthis.mesh.aabb.copy(this.aabb);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.mesh?.destroy();\n\t\t\t\tthis.instanceIndices?.destroy();\n\t\t\t\tthis.workBufferRenderInfos.forEach((info)=>info.destroy());\n\t\t\t\tthis.workBufferRenderInfos.clear();\n\t\t}\n\t\tgetWorkBufferRenderInfo(useIntervals, colorizeLod) {\n\t\t\t\tthis.configureMaterialDefines(tempMap);\n\t\t\t\tif (useIntervals) tempMap.set('GSPLAT_LOD', '');\n\t\t\t\tif (colorizeLod) tempMap.set('GSPLAT_COLORIZE', '');\n\t\t\t\tconst key = Array.from(tempMap.entries()).map(([k, v])=>`${k}=${v}`).join(';');\n\t\t\t\tlet info = this.workBufferRenderInfos.get(key);\n\t\t\t\tif (!info) {\n\t\t\t\t\t\tconst material = new ShaderMaterial();\n\t\t\t\t\t\tthis.configureMaterial(material);\n\t\t\t\t\t\ttempMap.forEach((v, k)=>material.setDefine(k, v));\n\t\t\t\t\t\tinfo = new WorkBufferRenderInfo(this.device, key, material);\n\t\t\t\t\t\tthis.workBufferRenderInfos.set(key, info);\n\t\t\t\t}\n\t\t\t\ttempMap.clear();\n\t\t\t\treturn info;\n\t\t}\n\t\tstatic createMesh(device) {\n\t\t\t\tconst splatInstanceSize = GSplatResourceBase.instanceSize;\n\t\t\t\tconst meshPositions = new Float32Array(12 * splatInstanceSize);\n\t\t\t\tconst meshIndices = new Uint32Array(6 * splatInstanceSize);\n\t\t\t\tfor(let i = 0; i < splatInstanceSize; ++i){\n\t\t\t\t\t\tmeshPositions.set([\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\ti,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\ti,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\ti,\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\ti\n\t\t\t\t\t\t], i * 12);\n\t\t\t\t\t\tconst b = i * 4;\n\t\t\t\t\t\tmeshIndices.set([\n\t\t\t\t\t\t\t\t0 + b,\n\t\t\t\t\t\t\t\t1 + b,\n\t\t\t\t\t\t\t\t2 + b,\n\t\t\t\t\t\t\t\t0 + b,\n\t\t\t\t\t\t\t\t2 + b,\n\t\t\t\t\t\t\t\t3 + b\n\t\t\t\t\t\t], i * 6);\n\t\t\t\t}\n\t\t\t\tconst mesh = new Mesh(device);\n\t\t\t\tmesh.setPositions(meshPositions, 3);\n\t\t\t\tmesh.setIndices(meshIndices);\n\t\t\t\tmesh.update();\n\t\t\t\treturn mesh;\n\t\t}\n\t\tstatic createInstanceIndices(device, splatCount) {\n\t\t\t\tconst splatInstanceSize = GSplatResourceBase.instanceSize;\n\t\t\t\tconst numSplats = Math.ceil(splatCount / splatInstanceSize) * splatInstanceSize;\n\t\t\t\tconst numSplatInstances = numSplats / splatInstanceSize;\n\t\t\t\tconst indexData = new Uint32Array(numSplatInstances);\n\t\t\t\tfor(let i = 0; i < numSplatInstances; ++i){\n\t\t\t\t\t\tindexData[i] = i * splatInstanceSize;\n\t\t\t\t}\n\t\t\t\tconst vertexFormat = new VertexFormat(device, [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_ATTR13,\n\t\t\t\t\t\t\t\tcomponents: 1,\n\t\t\t\t\t\t\t\ttype: TYPE_UINT32,\n\t\t\t\t\t\t\t\tasInt: true\n\t\t\t\t\t\t}\n\t\t\t\t]);\n\t\t\t\tconst instanceIndices = new VertexBuffer(device, vertexFormat, numSplatInstances, {\n\t\t\t\t\t\tusage: BUFFER_STATIC,\n\t\t\t\t\t\tdata: indexData.buffer\n\t\t\t\t});\n\t\t\t\treturn instanceIndices;\n\t\t}\n\t\tstatic get instanceSize() {\n\t\t\t\treturn 128;\n\t\t}\n\t\tget numSplats() {\n\t\t\t\treturn this.gsplatData.numSplats;\n\t\t}\n\t\tconfigureMaterial(material) {}\n\t\tconfigureMaterialDefines(defines) {}\n\t\tevalTextureSize(count) {\n\t\t\t\treturn Vec2.ZERO;\n\t\t}\n\t\tcreateTexture(name, format, size, data) {\n\t\t\t\treturn new Texture(this.device, {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\twidth: size.x,\n\t\t\t\t\t\theight: size.y,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: false,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t...data ? {\n\t\t\t\t\t\t\t\tlevels: [\n\t\t\t\t\t\t\t\t\t\tdata\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t} : {}\n\t\t\t\t});\n\t\t}\n\t\tinstantiate() {}\n}\n\nconst getSHData = (gsplatData, numCoeffs)=>{\n\t\tconst result = [];\n\t\tfor(let i = 0; i < numCoeffs; ++i){\n\t\t\t\tresult.push(gsplatData.getProp(`f_rest_${i}`));\n\t\t}\n\t\treturn result;\n};\nclass GSplatResource extends GSplatResourceBase {\n\t\tconstructor(device, gsplatData){\n\t\t\t\tsuper(device, gsplatData);\n\t\t\t\tconst numSplats = gsplatData.numSplats;\n\t\t\t\tconst size = this.evalTextureSize(numSplats);\n\t\t\t\tthis.colorTexture = this.createTexture('splatColor', PIXELFORMAT_RGBA16F, size);\n\t\t\t\tthis.transformATexture = this.createTexture('transformA', PIXELFORMAT_RGBA32U, size);\n\t\t\t\tthis.transformBTexture = this.createTexture('transformB', PIXELFORMAT_RGBA16F, size);\n\t\t\t\tthis.updateColorData(gsplatData);\n\t\t\t\tthis.updateTransformData(gsplatData);\n\t\t\t\tthis.shBands = gsplatData.shBands;\n\t\t\t\tif (this.shBands > 0) {\n\t\t\t\t\t\tthis.sh1to3Texture = this.createTexture('splatSH_1to3', PIXELFORMAT_RGBA32U, size);\n\t\t\t\t\t\tif (this.shBands > 1) {\n\t\t\t\t\t\t\t\tthis.sh4to7Texture = this.createTexture('splatSH_4to7', PIXELFORMAT_RGBA32U, size);\n\t\t\t\t\t\t\t\tif (this.shBands > 2) {\n\t\t\t\t\t\t\t\t\t\tthis.sh8to11Texture = this.createTexture('splatSH_8to11', PIXELFORMAT_RGBA32U, size);\n\t\t\t\t\t\t\t\t\t\tthis.sh12to15Texture = this.createTexture('splatSH_12to15', PIXELFORMAT_RGBA32U, size);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.sh8to11Texture = this.createTexture('splatSH_8to11', PIXELFORMAT_R32U, size);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.updateSHData(gsplatData);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.colorTexture?.destroy();\n\t\t\t\tthis.transformATexture?.destroy();\n\t\t\t\tthis.transformBTexture?.destroy();\n\t\t\t\tthis.sh1to3Texture?.destroy();\n\t\t\t\tthis.sh4to7Texture?.destroy();\n\t\t\t\tthis.sh8to11Texture?.destroy();\n\t\t\t\tthis.sh12to15Texture?.destroy();\n\t\t\t\tsuper.destroy();\n\t\t}\n\t\tconfigureMaterialDefines(defines) {\n\t\t\t\tdefines.set('SH_BANDS', this.shBands);\n\t\t}\n\t\tconfigureMaterial(material) {\n\t\t\t\tthis.configureMaterialDefines(material.defines);\n\t\t\t\tmaterial.setParameter('splatColor', this.colorTexture);\n\t\t\t\tmaterial.setParameter('transformA', this.transformATexture);\n\t\t\t\tmaterial.setParameter('transformB', this.transformBTexture);\n\t\t\t\tif (this.sh1to3Texture) material.setParameter('splatSH_1to3', this.sh1to3Texture);\n\t\t\t\tif (this.sh4to7Texture) material.setParameter('splatSH_4to7', this.sh4to7Texture);\n\t\t\t\tif (this.sh8to11Texture) material.setParameter('splatSH_8to11', this.sh8to11Texture);\n\t\t\t\tif (this.sh12to15Texture) material.setParameter('splatSH_12to15', this.sh12to15Texture);\n\t\t}\n\t\tevalTextureSize(count) {\n\t\t\t\tconst width = Math.ceil(Math.sqrt(count));\n\t\t\t\tconst height = Math.ceil(count / width);\n\t\t\t\treturn new Vec2(width, height);\n\t\t}\n\t\tupdateColorData(gsplatData) {\n\t\t\t\tconst texture = this.colorTexture;\n\t\t\t\tif (!texture) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst float2Half = FloatPacking.float2Half;\n\t\t\t\tconst data = texture.lock();\n\t\t\t\tconst cr = gsplatData.getProp('f_dc_0');\n\t\t\t\tconst cg = gsplatData.getProp('f_dc_1');\n\t\t\t\tconst cb = gsplatData.getProp('f_dc_2');\n\t\t\t\tconst ca = gsplatData.getProp('opacity');\n\t\t\t\tconst SH_C0 = 0.28209479177387814;\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\tconst r = cr[i] * SH_C0 + 0.5;\n\t\t\t\t\t\tconst g = cg[i] * SH_C0 + 0.5;\n\t\t\t\t\t\tconst b = cb[i] * SH_C0 + 0.5;\n\t\t\t\t\t\tconst a = 1 / (1 + Math.exp(-ca[i]));\n\t\t\t\t\t\tdata[i * 4 + 0] = float2Half(r);\n\t\t\t\t\t\tdata[i * 4 + 1] = float2Half(g);\n\t\t\t\t\t\tdata[i * 4 + 2] = float2Half(b);\n\t\t\t\t\t\tdata[i * 4 + 3] = float2Half(a);\n\t\t\t\t}\n\t\t\t\ttexture.unlock();\n\t\t}\n\t\tupdateTransformData(gsplatData) {\n\t\t\t\tconst float2Half = FloatPacking.float2Half;\n\t\t\t\tif (!this.transformATexture) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst dataA = this.transformATexture.lock();\n\t\t\t\tconst dataAFloat32 = new Float32Array(dataA.buffer);\n\t\t\t\tconst dataB = this.transformBTexture.lock();\n\t\t\t\tconst p = new Vec3();\n\t\t\t\tconst r = new Quat();\n\t\t\t\tconst s = new Vec3();\n\t\t\t\tconst iter = gsplatData.createIter(p, r, s);\n\t\t\t\tfor(let i = 0; i < this.numSplats; i++){\n\t\t\t\t\t\titer.read(i);\n\t\t\t\t\t\tr.normalize();\n\t\t\t\t\t\tif (r.w < 0) {\n\t\t\t\t\t\t\t\tr.mulScalar(-1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdataAFloat32[i * 4 + 0] = p.x;\n\t\t\t\t\t\tdataAFloat32[i * 4 + 1] = p.y;\n\t\t\t\t\t\tdataAFloat32[i * 4 + 2] = p.z;\n\t\t\t\t\t\tdataA[i * 4 + 3] = float2Half(r.x) | float2Half(r.y) << 16;\n\t\t\t\t\t\tdataB[i * 4 + 0] = float2Half(s.x);\n\t\t\t\t\t\tdataB[i * 4 + 1] = float2Half(s.y);\n\t\t\t\t\t\tdataB[i * 4 + 2] = float2Half(s.z);\n\t\t\t\t\t\tdataB[i * 4 + 3] = float2Half(r.z);\n\t\t\t\t}\n\t\t\t\tthis.transformATexture.unlock();\n\t\t\t\tthis.transformBTexture.unlock();\n\t\t}\n\t\tupdateSHData(gsplatData) {\n\t\t\t\tconst sh1to3Data = this.sh1to3Texture.lock();\n\t\t\t\tconst sh4to7Data = this.sh4to7Texture?.lock();\n\t\t\t\tconst sh8to11Data = this.sh8to11Texture?.lock();\n\t\t\t\tconst sh12to15Data = this.sh12to15Texture?.lock();\n\t\t\t\tconst numCoeffs = {\n\t\t\t\t\t\t1: 3,\n\t\t\t\t\t\t2: 8,\n\t\t\t\t\t\t3: 15\n\t\t\t\t}[this.shBands];\n\t\t\t\tconst src = getSHData(gsplatData, numCoeffs * 3);\n\t\t\t\tconst t11 = (1 << 11) - 1;\n\t\t\t\tconst t10 = (1 << 10) - 1;\n\t\t\t\tconst float32 = new Float32Array(1);\n\t\t\t\tconst uint32 = new Uint32Array(float32.buffer);\n\t\t\t\tconst c = new Array(numCoeffs * 3).fill(0);\n\t\t\t\tfor(let i = 0; i < gsplatData.numSplats; ++i){\n\t\t\t\t\t\tfor(let j = 0; j < numCoeffs; ++j){\n\t\t\t\t\t\t\t\tc[j * 3] = src[j][i];\n\t\t\t\t\t\t\t\tc[j * 3 + 1] = src[j + numCoeffs][i];\n\t\t\t\t\t\t\t\tc[j * 3 + 2] = src[j + numCoeffs * 2][i];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet max = c[0];\n\t\t\t\t\t\tfor(let j = 1; j < numCoeffs * 3; ++j){\n\t\t\t\t\t\t\t\tmax = Math.max(max, Math.abs(c[j]));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (max === 0) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let j = 0; j < numCoeffs; ++j){\n\t\t\t\t\t\t\t\tc[j * 3 + 0] = Math.max(0, Math.min(t11, Math.floor((c[j * 3 + 0] / max * 0.5 + 0.5) * t11 + 0.5)));\n\t\t\t\t\t\t\t\tc[j * 3 + 1] = Math.max(0, Math.min(t10, Math.floor((c[j * 3 + 1] / max * 0.5 + 0.5) * t10 + 0.5)));\n\t\t\t\t\t\t\t\tc[j * 3 + 2] = Math.max(0, Math.min(t11, Math.floor((c[j * 3 + 2] / max * 0.5 + 0.5) * t11 + 0.5)));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfloat32[0] = max;\n\t\t\t\t\t\tsh1to3Data[i * 4 + 0] = uint32[0];\n\t\t\t\t\t\tsh1to3Data[i * 4 + 1] = c[0] << 21 | c[1] << 11 | c[2];\n\t\t\t\t\t\tsh1to3Data[i * 4 + 2] = c[3] << 21 | c[4] << 11 | c[5];\n\t\t\t\t\t\tsh1to3Data[i * 4 + 3] = c[6] << 21 | c[7] << 11 | c[8];\n\t\t\t\t\t\tif (this.shBands > 1) {\n\t\t\t\t\t\t\t\tsh4to7Data[i * 4 + 0] = c[9] << 21 | c[10] << 11 | c[11];\n\t\t\t\t\t\t\t\tsh4to7Data[i * 4 + 1] = c[12] << 21 | c[13] << 11 | c[14];\n\t\t\t\t\t\t\t\tsh4to7Data[i * 4 + 2] = c[15] << 21 | c[16] << 11 | c[17];\n\t\t\t\t\t\t\t\tsh4to7Data[i * 4 + 3] = c[18] << 21 | c[19] << 11 | c[20];\n\t\t\t\t\t\t\t\tif (this.shBands > 2) {\n\t\t\t\t\t\t\t\t\t\tsh8to11Data[i * 4 + 0] = c[21] << 21 | c[22] << 11 | c[23];\n\t\t\t\t\t\t\t\t\t\tsh8to11Data[i * 4 + 1] = c[24] << 21 | c[25] << 11 | c[26];\n\t\t\t\t\t\t\t\t\t\tsh8to11Data[i * 4 + 2] = c[27] << 21 | c[28] << 11 | c[29];\n\t\t\t\t\t\t\t\t\t\tsh8to11Data[i * 4 + 3] = c[30] << 21 | c[31] << 11 | c[32];\n\t\t\t\t\t\t\t\t\t\tsh12to15Data[i * 4 + 0] = c[33] << 21 | c[34] << 11 | c[35];\n\t\t\t\t\t\t\t\t\t\tsh12to15Data[i * 4 + 1] = c[36] << 21 | c[37] << 11 | c[38];\n\t\t\t\t\t\t\t\t\t\tsh12to15Data[i * 4 + 2] = c[39] << 21 | c[40] << 11 | c[41];\n\t\t\t\t\t\t\t\t\t\tsh12to15Data[i * 4 + 3] = c[42] << 21 | c[43] << 11 | c[44];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tsh8to11Data[i] = c[21] << 21 | c[22] << 11 | c[23];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.sh1to3Texture.unlock();\n\t\t\t\tthis.sh4to7Texture?.unlock();\n\t\t\t\tthis.sh8to11Texture?.unlock();\n\t\t\t\tthis.sh12to15Texture?.unlock();\n\t\t}\n}\n\nconst vertexGLSL = `\n\tattribute vec2 vertex_position;\n\tvoid main(void) {\n\t\tgl_Position = vec4(vertex_position, 0.0, 1.0);\n\t}\n`;\nconst fragmentGLSL = `\n\t#include \"gsplatEvalSHVS\"\n\tvec4 packRgb(vec3 v) {\n\t\tuvec3 vb = uvec3(clamp(v, vec3(0.0), vec3(1.0)) * vec3(2047.0, 2047.0, 1023.0));\n\t\tuint bits = (vb.x << 21) | (vb.y << 10) | vb.z;\n\t\treturn vec4((uvec4(bits) >> uvec4(24, 16, 8, 0)) & uvec4(0xff)) / vec4(255.0);\n\t}\n\tuniform mediump vec3 dir;\n\tuniform mediump sampler2D centroids;\n\tuniform mediump float shN_mins;\n\tuniform mediump float shN_maxs;\n\tvoid main(void) {\n\t\tivec2 uv = ivec2(gl_FragCoord.xy) * ivec2(SH_COEFFS, 1);\n\t\tmediump vec3 coefficients[SH_COEFFS];\n\t\tfor (int i = 0; i < SH_COEFFS; i++) {\n\t\t\tvec3 s = texelFetch(centroids, ivec2(uv.x + i, uv.y), 0).xyz;\n\t\t\tcoefficients[i] = mix(vec3(shN_mins), vec3(shN_maxs), s);\n\t\t}\n\t\tgl_FragColor = packRgb(evalSH(coefficients, dir) * 0.25 + 0.5);\n\t}\n`;\nconst vertexWGSL = `\n\tattribute vertex_position: vec2f;\n\t@vertex\n\tfn vertexMain(input: VertexInput) -> VertexOutput {\n\t\tvar output: VertexOutput;\n\t\toutput.position = vec4f(vertex_position, 0.0, 1.0);\n\t\treturn output;\n\t}\n`;\nconst fragmentWGSL = `\n\t#include \"gsplatEvalSHVS\"\n\tfn packRgb(v: vec3f) -> vec4f {\n\t\tlet vb = vec3u(clamp(v, vec3f(0.0), vec3f(1.0)) * vec3f(2047.0, 2047.0, 1023.0));\n\t\tlet bits = dot(vb, vec3u(1 << 21, 1 << 10, 1));\n\t\treturn vec4f((vec4u(bits) >> vec4u(24, 16, 8, 0)) & vec4u(0xff)) / vec4f(255.0);\n\t}\n\tuniform dir: vec3f;\n\tuniform shN_mins: f32;\n\tuniform shN_maxs: f32;\n\tvar centroids: texture_2d<f32>;\n\t@fragment\n\tfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\t\tvar output: FragmentOutput;\n\t\tvar uv = vec2i(input.position.xy) * vec2i(SH_COEFFS, 1);\n\t\tvar coefficients: array<vec3f, SH_COEFFS>;\n\t\tfor (var i: i32 = 0; i < SH_COEFFS; i++) {\n\t\t\tlet s: vec3f = textureLoad(centroids, vec2i(uv.x + i, uv.y), 0).xyz;\n\t\t\tcoefficients[i] = mix(vec3f(uniform.shN_mins), vec3f(uniform.shN_maxs), s);\n\t\t}\n\t\toutput.color = packRgb(evalSH(&coefficients, uniform.dir) * 0.25 + 0.5);\n\t\treturn output;\n\t}\n`;\nconst gsplatSogsColorGLSL = `\n\tuniform mediump sampler2D sh0;\n\tuniform highp sampler2D sh_labels;\n\tuniform mediump sampler2D sh_result;\n\tuniform vec4 sh0_mins;\n\tuniform vec4 sh0_maxs;\n\tfloat SH_C0 = 0.28209479177387814;\n\tvec3 unpackRgb(vec4 v) {\n\t\tuvec4 uv = uvec4(v * 255.0);\n\t\tuint bits = (uv.x << 24) | (uv.y << 16) | (uv.z << 8) | uv.w;\n\t\tuvec3 vb = (uvec3(bits) >> uvec3(21, 10, 0)) & uvec3(0x7ffu, 0x7ffu, 0x3ffu);\n\t\treturn vec3(vb) / vec3(2047.0, 2047.0, 1023.0);\n\t}\n\tvec4 readColor(in SplatSource source) {\n\t\tvec4 baseSample = mix(sh0_mins, sh0_maxs, texelFetch(sh0, source.uv, 0));\n\t\tvec4 base = vec4(vec3(0.5) + baseSample.xyz * SH_C0, 1.0 / (1.0 + exp(-baseSample.w)));\n\t\tivec2 labelSample = ivec2(texelFetch(sh_labels, source.uv, 0).xy * 255.0);\n\t\tint n = labelSample.x + labelSample.y * 256;\n\t\tvec4 shSample = texelFetch(sh_result, ivec2(n % 64, n / 64), 0);\n\t\tvec3 sh = (unpackRgb(shSample) - vec3(0.5)) * 4.0;\n\t\treturn vec4(base.xyz + sh, base.w);\n\t}\n`;\nconst gsplatSogsColorWGSL = `\n\tvar sh0: texture_2d<f32>;\n\tvar sh_labels: texture_2d<f32>;\n\tvar sh_result: texture_2d<f32>;\n\tuniform sh0_mins: vec4f;\n\tuniform sh0_maxs: vec4f;\n\tconst SH_C0: f32 = 0.28209479177387814;\n\tfn unpackRgb(v: vec4f) -> vec3f {\n\t\tlet bits = dot(vec4u(v * 255.0), vec4u(1u << 24, 1u << 16, 1u << 8, 1u));\n\t\tlet vb = (vec3u(bits) >> vec3u(21, 10, 0)) & vec3u(0x7ffu, 0x7ffu, 0x3ffu);\n\t\treturn vec3f(vb) / vec3f(2047.0, 2047.0, 1023.0);\n\t}\n\tfn readColor(source: ptr<function, SplatSource>) -> vec4f {\n\t\tlet baseSample: vec4f = mix(uniform.sh0_mins, uniform.sh0_maxs, textureLoad(sh0, source.uv, 0));\n\t\tlet base = vec4f(vec3f(0.5) + baseSample.xyz * SH_C0, 1.0 / (1.0 + exp(-baseSample.w)));\n\t\tlet labelSample: vec2i = vec2i(textureLoad(sh_labels, source.uv, 0).xy * 255.0);\n\t\tlet n = labelSample.x + labelSample.y * 256;\n\t\tlet shSample: vec4f = textureLoad(sh_result, vec2i(n % 64, n / 64), 0);\n\t\tlet sh: vec3f = (unpackRgb(shSample) - vec3f(0.5)) * 4.0;\n\t\treturn vec4f(base.xyz + sh, base.w);\n\t}\n`;\nconst resolve$1 = (scope, values)=>{\n\t\tfor(const key in values){\n\t\t\t\tscope.resolve(key).setValue(values[key]);\n\t\t}\n};\nclass CustomRenderPass extends RenderPass {\n\t\texecute() {\n\t\t\t\tthis.executeCallback?.();\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this.executeCallback = null;\n\t\t}\n}\nconst invModelMat$1 = new Mat4();\nconst dir$1 = new Vec3();\nclass GSplatResolveSH {\n\t\tconstructor(device, gsplatInstance){\n\t\t\t\tthis.prevDir = new Vec3();\n\t\t\t\tthis.updateMode = 'enable';\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.gsplatInstance = gsplatInstance;\n\t\t\t\tconst { resource } = gsplatInstance;\n\t\t\t\tconst includes = new Map(ShaderChunks.get(device, device.isWebGPU ? 'wgsl' : 'glsl'));\n\t\t\t\tthis.shader = ShaderUtils.createShader(device, {\n\t\t\t\t\t\tuniqueName: 'gsplatResolveSH',\n\t\t\t\t\t\tvertexGLSL,\n\t\t\t\t\t\tfragmentGLSL,\n\t\t\t\t\t\tvertexWGSL,\n\t\t\t\t\t\tfragmentWGSL,\n\t\t\t\t\t\tvertexIncludes: includes,\n\t\t\t\t\t\tfragmentIncludes: includes,\n\t\t\t\t\t\tfragmentDefines: new Map([\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t'SH_BANDS',\n\t\t\t\t\t\t\t\t\t\tresource.gsplatData.shBands.toString()\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis.texture = resource.createTexture('centroids', PIXELFORMAT_RGBA8, new Vec2(64, 1024));\n\t\t\t\tthis.renderTarget = new RenderTarget({\n\t\t\t\t\t\tcolorBuffer: this.texture,\n\t\t\t\t\t\tdepth: false\n\t\t\t\t});\n\t\t\t\tthis.renderPass = new CustomRenderPass(device);\n\t\t\t\tthis.renderPass.init(this.renderTarget, {});\n\t\t\t\tthis.renderPass.colorOps.clear = true;\n\t\t\t\tthis.quadRender = new QuadRender(this.shader);\n\t\t\t\tconst { material } = gsplatInstance;\n\t\t\t\tmaterial.setDefine('SH_BANDS', '0');\n\t\t\t\tconst { shaderChunks } = material;\n\t\t\t\tshaderChunks.glsl.set('gsplatSogsColorVS', gsplatSogsColorGLSL);\n\t\t\t\tshaderChunks.wgsl.set('gsplatSogsColorVS', gsplatSogsColorWGSL);\n\t\t\t\tmaterial.update();\n\t\t\t\tdevice.scope.resolve('sh_result').setValue(this.texture);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst { gsplatInstance } = this;\n\t\t\t\tconst { material } = gsplatInstance;\n\t\t\t\tmaterial.setDefine('SH_BANDS', gsplatInstance.resource.gsplatData.shBands.toString());\n\t\t\t\tconst { shaderChunks } = material;\n\t\t\t\tshaderChunks.glsl.delete('gsplatSogsColorVS');\n\t\t\t\tshaderChunks.wgsl.delete('gsplatSogsColorVS');\n\t\t\t\tmaterial.update();\n\t\t\t\tthis.quadRender.destroy();\n\t\t\t\tthis.renderPass.destroy();\n\t\t\t\tthis.renderTarget.destroy();\n\t\t\t\tthis.texture.destroy();\n\t\t\t\tthis.shader.destroy();\n\t\t}\n\t\trender(camera, modelMat) {\n\t\t\t\tconst { prevDir, updateMode } = this;\n\t\t\t\tif (updateMode === 'disable') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tinvModelMat$1.invert(modelMat);\n\t\t\t\tinvModelMat$1.transformVector(camera.forward, dir$1);\n\t\t\t\tdir$1.normalize();\n\t\t\t\tif (updateMode === 'enable' && dir$1.equalsApprox(prevDir, 1e-3)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tprevDir.copy(dir$1);\n\t\t\t\tconst execute = ()=>{\n\t\t\t\t\t\tconst { device } = this;\n\t\t\t\t\t\tconst { sh_centroids, meta } = this.gsplatInstance.resource.gsplatData;\n\t\t\t\t\t\tresolve$1(device.scope, {\n\t\t\t\t\t\t\t\tdir: dir$1.toArray(),\n\t\t\t\t\t\t\t\tcentroids: sh_centroids,\n\t\t\t\t\t\t\t\tshN_mins: meta.shN.mins,\n\t\t\t\t\t\t\t\tshN_maxs: meta.shN.maxs\n\t\t\t\t\t\t});\n\t\t\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\t\t\tdevice.setStencilState(null, null);\n\t\t\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\t\t\tthis.quadRender.render();\n\t\t\t\t};\n\t\t\t\tthis.renderPass.executeCallback = execute;\n\t\t\t\tthis.renderPass.render();\n\t\t}\n}\n\nfunction SortWorker() {\n\t\tconst myself = typeof self !== 'undefined' && self || require('node:worker_threads').parentPort;\n\t\tlet order;\n\t\tlet centers;\n\t\tlet chunks;\n\t\tlet mapping;\n\t\tlet cameraPosition;\n\t\tlet cameraDirection;\n\t\tlet forceUpdate = false;\n\t\tconst lastCameraPosition = {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tz: 0\n\t\t};\n\t\tconst lastCameraDirection = {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tz: 0\n\t\t};\n\t\tconst boundMin = {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tz: 0\n\t\t};\n\t\tconst boundMax = {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tz: 0\n\t\t};\n\t\tlet distances;\n\t\tlet countBuffer;\n\t\tconst numBins = 32;\n\t\tconst binCount = new Array(numBins).fill(0);\n\t\tconst binBase = new Array(numBins).fill(0);\n\t\tconst binDivider = new Array(numBins).fill(0);\n\t\tconst binarySearch = (m, n, compare_fn)=>{\n\t\t\t\twhile(m <= n){\n\t\t\t\t\t\tconst k = n + m >> 1;\n\t\t\t\t\t\tconst cmp = compare_fn(k);\n\t\t\t\t\t\tif (cmp > 0) {\n\t\t\t\t\t\t\t\tm = k + 1;\n\t\t\t\t\t\t} else if (cmp < 0) {\n\t\t\t\t\t\t\t\tn = k - 1;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn k;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn ~m;\n\t\t};\n\t\tconst update = ()=>{\n\t\t\t\tif (!order || !centers || centers.length === 0 || !cameraPosition || !cameraDirection) return;\n\t\t\t\tconst px = cameraPosition.x;\n\t\t\t\tconst py = cameraPosition.y;\n\t\t\t\tconst pz = cameraPosition.z;\n\t\t\t\tconst dx = cameraDirection.x;\n\t\t\t\tconst dy = cameraDirection.y;\n\t\t\t\tconst dz = cameraDirection.z;\n\t\t\t\tconst epsilon = 0.001;\n\t\t\t\tif (!forceUpdate && Math.abs(px - lastCameraPosition.x) < epsilon && Math.abs(py - lastCameraPosition.y) < epsilon && Math.abs(pz - lastCameraPosition.z) < epsilon && Math.abs(dx - lastCameraDirection.x) < epsilon && Math.abs(dy - lastCameraDirection.y) < epsilon && Math.abs(dz - lastCameraDirection.z) < epsilon) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tforceUpdate = false;\n\t\t\t\tlastCameraPosition.x = px;\n\t\t\t\tlastCameraPosition.y = py;\n\t\t\t\tlastCameraPosition.z = pz;\n\t\t\t\tlastCameraDirection.x = dx;\n\t\t\t\tlastCameraDirection.y = dy;\n\t\t\t\tlastCameraDirection.z = dz;\n\t\t\t\tlet minDist;\n\t\t\t\tlet maxDist;\n\t\t\t\tfor(let i = 0; i < 8; ++i){\n\t\t\t\t\t\tconst x = i & 1 ? boundMin.x : boundMax.x;\n\t\t\t\t\t\tconst y = i & 2 ? boundMin.y : boundMax.y;\n\t\t\t\t\t\tconst z = i & 4 ? boundMin.z : boundMax.z;\n\t\t\t\t\t\tconst d = x * dx + y * dy + z * dz;\n\t\t\t\t\t\tif (i === 0) {\n\t\t\t\t\t\t\t\tminDist = maxDist = d;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tminDist = Math.min(minDist, d);\n\t\t\t\t\t\t\t\tmaxDist = Math.max(maxDist, d);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst numVertices = centers.length / 3;\n\t\t\t\tconst compareBits = Math.max(10, Math.min(20, Math.round(Math.log2(numVertices / 4))));\n\t\t\t\tconst bucketCount = 2 ** compareBits + 1;\n\t\t\t\tif (distances?.length !== numVertices) {\n\t\t\t\t\t\tdistances = new Uint32Array(numVertices);\n\t\t\t\t}\n\t\t\t\tif (!countBuffer || countBuffer.length !== bucketCount) {\n\t\t\t\t\t\tcountBuffer = new Uint32Array(bucketCount);\n\t\t\t\t} else {\n\t\t\t\t\t\tcountBuffer.fill(0);\n\t\t\t\t}\n\t\t\t\tconst range = maxDist - minDist;\n\t\t\t\tif (range < 1e-6) {\n\t\t\t\t\t\tfor(let i = 0; i < numVertices; ++i){\n\t\t\t\t\t\t\t\tdistances[i] = 0;\n\t\t\t\t\t\t\t\tcountBuffer[0]++;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst numChunks = chunks.length / 4;\n\t\t\t\t\t\tbinCount.fill(0);\n\t\t\t\t\t\tfor(let i = 0; i < numChunks; ++i){\n\t\t\t\t\t\t\t\tconst x = chunks[i * 4 + 0];\n\t\t\t\t\t\t\t\tconst y = chunks[i * 4 + 1];\n\t\t\t\t\t\t\t\tconst z = chunks[i * 4 + 2];\n\t\t\t\t\t\t\t\tconst r = chunks[i * 4 + 3];\n\t\t\t\t\t\t\t\tconst d = x * dx + y * dy + z * dz - minDist;\n\t\t\t\t\t\t\t\tconst binMin = Math.max(0, Math.floor((d - r) * numBins / range));\n\t\t\t\t\t\t\t\tconst binMax = Math.min(numBins, Math.ceil((d + r) * numBins / range));\n\t\t\t\t\t\t\t\tfor(let j = binMin; j < binMax; ++j){\n\t\t\t\t\t\t\t\t\t\tbinCount[j]++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst binTotal = binCount.reduce((a, b)=>a + b, 0);\n\t\t\t\t\t\tfor(let i = 0; i < numBins; ++i){\n\t\t\t\t\t\t\t\tbinDivider[i] = binCount[i] / binTotal * bucketCount >>> 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < numBins; ++i){\n\t\t\t\t\t\t\t\tbinBase[i] = i === 0 ? 0 : binBase[i - 1] + binDivider[i - 1];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst binRange = range / numBins;\n\t\t\t\t\t\tlet ii = 0;\n\t\t\t\t\t\tfor(let i = 0; i < numVertices; ++i){\n\t\t\t\t\t\t\t\tconst x = centers[ii++];\n\t\t\t\t\t\t\t\tconst y = centers[ii++];\n\t\t\t\t\t\t\t\tconst z = centers[ii++];\n\t\t\t\t\t\t\t\tconst d = (x * dx + y * dy + z * dz - minDist) / binRange;\n\t\t\t\t\t\t\t\tconst bin = d >>> 0;\n\t\t\t\t\t\t\t\tconst sortKey = binBase[bin] + binDivider[bin] * (d - bin) >>> 0;\n\t\t\t\t\t\t\t\tdistances[i] = sortKey;\n\t\t\t\t\t\t\t\tcountBuffer[sortKey]++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 1; i < bucketCount; i++){\n\t\t\t\t\t\tcountBuffer[i] += countBuffer[i - 1];\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < numVertices; i++){\n\t\t\t\t\t\tconst distance = distances[i];\n\t\t\t\t\t\tconst destIndex = --countBuffer[distance];\n\t\t\t\t\t\torder[destIndex] = i;\n\t\t\t\t}\n\t\t\t\tconst cameraDist = px * dx + py * dy + pz * dz;\n\t\t\t\tconst dist = (i)=>{\n\t\t\t\t\t\tlet o = order[i] * 3;\n\t\t\t\t\t\treturn centers[o++] * dx + centers[o++] * dy + centers[o] * dz - cameraDist;\n\t\t\t\t};\n\t\t\t\tconst findZero = ()=>{\n\t\t\t\t\t\tconst result = binarySearch(0, numVertices - 1, (i)=>-dist(i));\n\t\t\t\t\t\treturn Math.min(numVertices, Math.abs(result));\n\t\t\t\t};\n\t\t\t\tconst count = dist(numVertices - 1) >= 0 ? findZero() : numVertices;\n\t\t\t\tif (mapping) {\n\t\t\t\t\t\tfor(let i = 0; i < numVertices; ++i){\n\t\t\t\t\t\t\t\torder[i] = mapping[order[i]];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmyself.postMessage({\n\t\t\t\t\t\torder: order.buffer,\n\t\t\t\t\t\tcount\n\t\t\t\t}, [\n\t\t\t\t\t\torder.buffer\n\t\t\t\t]);\n\t\t\t\torder = null;\n\t\t};\n\t\tmyself.addEventListener('message', (message)=>{\n\t\t\t\tconst msgData = message.data ?? message;\n\t\t\t\tif (msgData.order) {\n\t\t\t\t\t\torder = new Uint32Array(msgData.order);\n\t\t\t\t}\n\t\t\t\tif (msgData.centers) {\n\t\t\t\t\t\tcenters = new Float32Array(msgData.centers);\n\t\t\t\t\t\tforceUpdate = true;\n\t\t\t\t\t\tif (msgData.chunks) {\n\t\t\t\t\t\t\t\tconst chunksSrc = new Float32Array(msgData.chunks);\n\t\t\t\t\t\t\t\tchunks = new Float32Array(msgData.chunks, 0, chunksSrc.length * 4 / 6);\n\t\t\t\t\t\t\t\tboundMin.x = chunksSrc[0];\n\t\t\t\t\t\t\t\tboundMin.y = chunksSrc[1];\n\t\t\t\t\t\t\t\tboundMin.z = chunksSrc[2];\n\t\t\t\t\t\t\t\tboundMax.x = chunksSrc[3];\n\t\t\t\t\t\t\t\tboundMax.y = chunksSrc[4];\n\t\t\t\t\t\t\t\tboundMax.z = chunksSrc[5];\n\t\t\t\t\t\t\t\tfor(let i = 0; i < chunksSrc.length / 6; ++i){\n\t\t\t\t\t\t\t\t\t\tconst mx = chunksSrc[i * 6 + 0];\n\t\t\t\t\t\t\t\t\t\tconst my = chunksSrc[i * 6 + 1];\n\t\t\t\t\t\t\t\t\t\tconst mz = chunksSrc[i * 6 + 2];\n\t\t\t\t\t\t\t\t\t\tconst Mx = chunksSrc[i * 6 + 3];\n\t\t\t\t\t\t\t\t\t\tconst My = chunksSrc[i * 6 + 4];\n\t\t\t\t\t\t\t\t\t\tconst Mz = chunksSrc[i * 6 + 5];\n\t\t\t\t\t\t\t\t\t\tchunks[i * 4 + 0] = (mx + Mx) * 0.5;\n\t\t\t\t\t\t\t\t\t\tchunks[i * 4 + 1] = (my + My) * 0.5;\n\t\t\t\t\t\t\t\t\t\tchunks[i * 4 + 2] = (mz + Mz) * 0.5;\n\t\t\t\t\t\t\t\t\t\tchunks[i * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;\n\t\t\t\t\t\t\t\t\t\tif (mx < boundMin.x) boundMin.x = mx;\n\t\t\t\t\t\t\t\t\t\tif (my < boundMin.y) boundMin.y = my;\n\t\t\t\t\t\t\t\t\t\tif (mz < boundMin.z) boundMin.z = mz;\n\t\t\t\t\t\t\t\t\t\tif (Mx > boundMax.x) boundMax.x = Mx;\n\t\t\t\t\t\t\t\t\t\tif (My > boundMax.y) boundMax.y = My;\n\t\t\t\t\t\t\t\t\t\tif (Mz > boundMax.z) boundMax.z = Mz;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst numVertices = centers.length / 3;\n\t\t\t\t\t\t\t\tconst numChunks = Math.ceil(numVertices / 256);\n\t\t\t\t\t\t\t\tchunks = new Float32Array(numChunks * 4);\n\t\t\t\t\t\t\t\tboundMin.x = boundMin.y = boundMin.z = Infinity;\n\t\t\t\t\t\t\t\tboundMax.x = boundMax.y = boundMax.z = -Infinity;\n\t\t\t\t\t\t\t\tlet mx, my, mz, Mx, My, Mz;\n\t\t\t\t\t\t\t\tfor(let c = 0; c < numChunks; ++c){\n\t\t\t\t\t\t\t\t\t\tmx = my = mz = Infinity;\n\t\t\t\t\t\t\t\t\t\tMx = My = Mz = -Infinity;\n\t\t\t\t\t\t\t\t\t\tconst start = c * 256;\n\t\t\t\t\t\t\t\t\t\tconst end = Math.min(numVertices, (c + 1) * 256);\n\t\t\t\t\t\t\t\t\t\tfor(let i = start; i < end; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tconst x = centers[i * 3 + 0];\n\t\t\t\t\t\t\t\t\t\t\t\tconst y = centers[i * 3 + 1];\n\t\t\t\t\t\t\t\t\t\t\t\tconst z = centers[i * 3 + 2];\n\t\t\t\t\t\t\t\t\t\t\t\tconst validX = Number.isFinite(x);\n\t\t\t\t\t\t\t\t\t\t\t\tconst validY = Number.isFinite(y);\n\t\t\t\t\t\t\t\t\t\t\t\tconst validZ = Number.isFinite(z);\n\t\t\t\t\t\t\t\t\t\t\t\tif (!validX) centers[i * 3 + 0] = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tif (!validY) centers[i * 3 + 1] = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tif (!validZ) centers[i * 3 + 2] = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tif (!validX || !validY || !validZ) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (x < mx) mx = x;\n\t\t\t\t\t\t\t\t\t\t\t\telse if (x > Mx) Mx = x;\n\t\t\t\t\t\t\t\t\t\t\t\tif (y < my) my = y;\n\t\t\t\t\t\t\t\t\t\t\t\telse if (y > My) My = y;\n\t\t\t\t\t\t\t\t\t\t\t\tif (z < mz) mz = z;\n\t\t\t\t\t\t\t\t\t\t\t\telse if (z > Mz) Mz = z;\n\t\t\t\t\t\t\t\t\t\t\t\tif (x < boundMin.x) boundMin.x = x;\n\t\t\t\t\t\t\t\t\t\t\t\telse if (x > boundMax.x) boundMax.x = x;\n\t\t\t\t\t\t\t\t\t\t\t\tif (y < boundMin.y) boundMin.y = y;\n\t\t\t\t\t\t\t\t\t\t\t\telse if (y > boundMax.y) boundMax.y = y;\n\t\t\t\t\t\t\t\t\t\t\t\tif (z < boundMin.z) boundMin.z = z;\n\t\t\t\t\t\t\t\t\t\t\t\telse if (z > boundMax.z) boundMax.z = z;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tchunks[c * 4 + 0] = (mx + Mx) * 0.5;\n\t\t\t\t\t\t\t\t\t\tchunks[c * 4 + 1] = (my + My) * 0.5;\n\t\t\t\t\t\t\t\t\t\tchunks[c * 4 + 2] = (mz + Mz) * 0.5;\n\t\t\t\t\t\t\t\t\t\tchunks[c * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (msgData.hasOwnProperty('mapping')) {\n\t\t\t\t\t\tmapping = msgData.mapping ? new Uint32Array(msgData.mapping) : null;\n\t\t\t\t\t\tforceUpdate = true;\n\t\t\t\t}\n\t\t\t\tif (msgData.cameraPosition) cameraPosition = msgData.cameraPosition;\n\t\t\t\tif (msgData.cameraDirection) cameraDirection = msgData.cameraDirection;\n\t\t\t\tupdate();\n\t\t});\n}\n\nclass GSplatSorter extends EventHandler {\n\t\tconstructor(){\n\t\t\t\tsuper();\n\t\t\t\tconst messageHandler = (message)=>{\n\t\t\t\t\t\tconst msgData = message.data ?? message;\n\t\t\t\t\t\tconst newOrder = msgData.order;\n\t\t\t\t\t\tconst oldOrder = this.orderTexture._levels[0].buffer;\n\t\t\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\t\t\torder: oldOrder\n\t\t\t\t\t\t}, [\n\t\t\t\t\t\t\t\toldOrder\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis.orderTexture._levels[0] = new Uint32Array(newOrder);\n\t\t\t\t\t\tthis.orderTexture.upload();\n\t\t\t\t\t\tthis.fire('updated', msgData.count);\n\t\t\t\t};\n\t\t\t\tconst workerSource = `(${SortWorker.toString()})()`;\n\t\t\t\tif (platform.environment === 'node') {\n\t\t\t\t\t\tthis.worker = new Worker(workerSource, {\n\t\t\t\t\t\t\t\teval: true\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.worker.on('message', messageHandler);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.worker = new Worker(URL.createObjectURL(new Blob([\n\t\t\t\t\t\t\t\tworkerSource\n\t\t\t\t\t\t], {\n\t\t\t\t\t\t\t\ttype: \"application/javascript\"\n\t\t\t\t\t\t})));\n\t\t\t\t\t\tthis.worker.addEventListener('message', messageHandler);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.worker.terminate();\n\t\t\t\tthis.worker = null;\n\t\t}\n\t\tinit(orderTexture, centers, chunks) {\n\t\t\t\tthis.orderTexture = orderTexture;\n\t\t\t\tthis.centers = centers.slice();\n\t\t\t\tconst orderBuffer = this.orderTexture.lock({\n\t\t\t\t\t\tmode: TEXTURELOCK_READ\n\t\t\t\t}).slice();\n\t\t\t\tthis.orderTexture.unlock();\n\t\t\t\tfor(let i = 0; i < orderBuffer.length; ++i){\n\t\t\t\t\t\torderBuffer[i] = i;\n\t\t\t\t}\n\t\t\t\tconst obj = {\n\t\t\t\t\t\torder: orderBuffer.buffer,\n\t\t\t\t\t\tcenters: centers.buffer,\n\t\t\t\t\t\tchunks: chunks?.buffer\n\t\t\t\t};\n\t\t\t\tconst transfer = [\n\t\t\t\t\t\torderBuffer.buffer,\n\t\t\t\t\t\tcenters.buffer\n\t\t\t\t].concat(chunks ? [\n\t\t\t\t\t\tchunks.buffer\n\t\t\t\t] : []);\n\t\t\t\tthis.worker.postMessage(obj, transfer);\n\t\t}\n\t\tsetMapping(mapping) {\n\t\t\t\tif (mapping) {\n\t\t\t\t\t\tconst centers = new Float32Array(mapping.length * 3);\n\t\t\t\t\t\tfor(let i = 0; i < mapping.length; ++i){\n\t\t\t\t\t\t\t\tconst src = mapping[i] * 3;\n\t\t\t\t\t\t\t\tconst dst = i * 3;\n\t\t\t\t\t\t\t\tcenters[dst + 0] = this.centers[src + 0];\n\t\t\t\t\t\t\t\tcenters[dst + 1] = this.centers[src + 1];\n\t\t\t\t\t\t\t\tcenters[dst + 2] = this.centers[src + 2];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\t\t\tcenters: centers.buffer,\n\t\t\t\t\t\t\t\tmapping: mapping.buffer\n\t\t\t\t\t\t}, [\n\t\t\t\t\t\t\t\tcenters.buffer,\n\t\t\t\t\t\t\t\tmapping.buffer\n\t\t\t\t\t\t]);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst centers = this.centers.slice();\n\t\t\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\t\t\tcenters: centers.buffer,\n\t\t\t\t\t\t\t\tmapping: null\n\t\t\t\t\t\t}, [\n\t\t\t\t\t\t\t\tcenters.buffer\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\tsetCamera(pos, dir) {\n\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\tcameraPosition: {\n\t\t\t\t\t\t\t\tx: pos.x,\n\t\t\t\t\t\t\t\ty: pos.y,\n\t\t\t\t\t\t\t\tz: pos.z\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcameraDirection: {\n\t\t\t\t\t\t\t\tx: dir.x,\n\t\t\t\t\t\t\t\ty: dir.y,\n\t\t\t\t\t\t\t\tz: dir.z\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n}\n\nvar glslGsplatSogsReorderPS = `\n#include \"gsplatPackingPS\"\nuniform highp sampler2D means_l;\nuniform highp sampler2D means_u;\nuniform highp sampler2D quats;\nuniform highp sampler2D scales;\nuniform highp sampler2D sh0;\nuniform highp sampler2D sh_labels;\nuniform highp uint numSplats;\n#ifdef REORDER_V1\n\tfloat sigmoid(float x) { return 1.0 / (1.0 + exp(-x)); }\n\tvec3 vmin(vec3 v) { return vec3(min(min(v.x, v.y), v.z)); }\n\tvec3 vmax(vec3 v) { return vec3(max(max(v.x, v.y), v.z)); }\n\tvec3 resolve(vec3 m, vec3 M, vec3 v) { return (mix(m, M, v) - vmin(m)) / (vmax(M) - vmin(m)); }\n\t\n\tuniform vec3 scalesMins;\n\tuniform vec3 scalesMaxs;\n\tuniform vec4 sh0Mins;\n\tuniform vec4 sh0Maxs;\n#else\n\tuniform vec4 scales_codebook[64];\n\tuniform vec4 sh0_codebook[64];\n#endif\nvoid main(void) {\n\tint w = int(textureSize(means_l, 0).x);\n\tivec2 uv = ivec2(gl_FragCoord.xy);\n\tif (uint(uv.x + uv.y * w) >= numSplats) {\n\t\tdiscard;\n\t}\n\tvec3 meansLSample   = texelFetch(means_l, uv, 0).xyz;\n\tvec3 meansUSample   = texelFetch(means_u, uv, 0).xyz;\n\tvec4 quatsSample\t= texelFetch(quats, uv, 0);\n\tvec3 scalesSample   = texelFetch(scales, uv, 0).xyz;\n\tvec4 sh0Sample\t  = texelFetch(sh0, uv, 0);\n\tvec2 shLabelsSample = texelFetch(sh_labels, uv, 0).xy;\n\t#ifdef REORDER_V1\n\t\tuint scale = pack101010(resolve(scalesMins, scalesMaxs, scalesSample));\n\t\tuint sh0 = pack111110(resolve(sh0Mins.xyz, sh0Maxs.xyz, sh0Sample.xyz));\n\t\tfloat alpha = sigmoid(mix(sh0Mins.w, sh0Maxs.w, sh0Sample.w));\n\t#else\n\t\tuint scale = pack101010(resolveCodebook(scalesSample, scales_codebook));\n\t\tuint sh0 = pack111110(resolveCodebook(sh0Sample.xyz, sh0_codebook));\n\t\tfloat alpha = sh0Sample.w;\n\t#endif\n\tuint qmode = uint(quatsSample.w * 255.0) - 252u;\n\tpcFragColor0 = uvec4(\n\t\tpack8888(vec4(meansLSample, shLabelsSample.x)),\n\t\tpack8888(vec4(meansUSample, shLabelsSample.y)),\n\t\tpack8888(vec4(quatsSample.xyz, alpha)),\n\t\t(scale << 2u) | qmode\n\t);\n\tpcFragColor1 = unpack8888(sh0);\n}\n`;\n\nvar wgslGsplatSogsReorderPS = `\n#include \"gsplatPackingPS\"\nvar means_l: texture_2d<f32>;\nvar means_u: texture_2d<f32>;\nvar quats: texture_2d<f32>;\nvar scales: texture_2d<f32>;\nvar sh0: texture_2d<f32>;\nvar sh_labels: texture_2d<f32>;\nuniform numSplats: u32;\n#ifdef REORDER_V1\n\tfn sigmoid(x: f32) -> f32 { return 1.0 / (1.0 + exp(-x)); }\n\tfn vmin(v: vec3f) -> vec3f { return vec3f(min(min(v.x, v.y), v.z)); }\n\tfn vmax(v: vec3f) -> vec3f { return vec3f(max(max(v.x, v.y), v.z)); }\n\tfn resolve(m: vec3f, M: vec3f, v: vec3f) -> vec3f { return (mix(m, M, v) - vmin(m)) / (vmax(M) - vmin(m)); }\n\tuniform scalesMins: vec3f;\n\tuniform scalesMaxs: vec3f;\n\tuniform sh0Mins: vec4f;\n\tuniform sh0Maxs: vec4f;\n#else\n\tuniform scales_codebook: array<vec4f, 64>;\n\tuniform sh0_codebook: array<vec4f, 64>;\n#endif\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\tlet w: u32 = textureDimensions(means_l, 0).x;\n\tlet uv: vec2<u32> = vec2<u32>(input.position.xy);\n\tif (uv.x + uv.y * w >= uniform.numSplats) {\n\t\tdiscard;\n\t\treturn output;\n\t}\n\tlet meansLSample: vec3<f32> = textureLoad(means_l, uv, 0).xyz;\n\tlet meansUSample: vec3<f32> = textureLoad(means_u, uv, 0).xyz;\n\tlet quatsSample: vec4<f32> = textureLoad(quats, uv, 0);\n\tlet scalesSample: vec3<f32> = textureLoad(scales, uv, 0).xyz;\n\tlet sh0Sample: vec4f = textureLoad(sh0, uv, 0);\n\tlet shLabelsSample: vec2<f32> = textureLoad(sh_labels, uv, 0).xy;\n\t#ifdef REORDER_V1\n\t\tlet scale = pack101010(resolve(uniform.scalesMins, uniform.scalesMaxs, scalesSample));\n\t\tlet sh0 = pack111110(resolve(uniform.sh0Mins.xyz, uniform.sh0Maxs.xyz, sh0Sample.xyz));\n\t\tlet alpha = sigmoid(mix(uniform.sh0Mins.w, uniform.sh0Maxs.w, sh0Sample.w));\n\t#else\n\t\tlet scale = pack101010(resolveCodebook(scalesSample, &uniform.scales_codebook));\n\t\tlet sh0 = pack111110(resolveCodebook(sh0Sample.xyz, &uniform.sh0_codebook));\n\t\tlet alpha = sh0Sample.w;\n\t#endif\n\tlet qmode = u32(quatsSample.w * 255.0) - 252u;\n\toutput.color = vec4u(\n\t\tpack8888(vec4f(meansLSample, shLabelsSample.x)),\n\t\tpack8888(vec4f(meansUSample, shLabelsSample.y)),\n\t\tpack8888(vec4f(quatsSample.xyz, alpha)),\n\t\t(scale << 2u) | qmode\n\t);\n\toutput.color1 = unpack8888(sh0);\n\treturn output;\n}\n`;\n\nvar glslGsplatSogsReorderSh = `\n#include \"gsplatPackingPS\"\nuniform highp sampler2D sh_centroids;\nuniform vec4 shN_codebook[64];\nvoid main(void) {\n\tivec2 uv = ivec2(gl_FragCoord.xy);\n\tvec3 shNSample = texelFetch(sh_centroids, uv, 0).xyz;\n#ifdef REORDER_V1\n\tpcFragColor0 = unpack8888(pack111110(shNSample));\n#else\n\tpcFragColor0 = unpack8888(pack111110(resolveCodebook(shNSample, shN_codebook)));\n#endif\n}\n`;\n\nvar gsplatPackingPS$1 = `\nuint pack8888(vec4 v) {\n\tuvec4 t = uvec4(v * 255.0) << uvec4(24u, 16u, 8u, 0u);\n\treturn t.x | t.y | t.z | t.w;\n}\nuint pack101010(vec3 v) {\n\tuvec3 t = uvec3(v * 1023.0) << uvec3(20u, 10u, 0u);\n\treturn t.x | t.y | t.z;\n}\nuint pack111110(vec3 v) {\n\tuvec3 t = uvec3(v * vec3(2047.0, 2047.0, 1023.0)) << uvec3(21u, 10u, 0u);\n\treturn t.x | t.y | t.z;\n}\nvec4 unpack8888(uint v) {\n\treturn vec4((uvec4(v) >> uvec4(24u, 16u, 8u, 0u)) & 0xffu) / 255.0;\n}\nvec3 unpack101010(uint v) {\n\treturn vec3((uvec3(v) >> uvec3(20u, 10u, 0u)) & 0x3ffu) / 1023.0;\n}\nvec3 unpack111110(uint v) {\n\treturn vec3((uvec3(v) >> uvec3(21u, 10u, 0u)) & uvec3(0x7ffu, 0x7ffu, 0x3ffu)) / vec3(2047.0, 2047.0, 1023.0);\n}\nvec3 resolveCodebook(vec3 s, vec4 codebook[64]) {\n\tuvec3 idx = uvec3(s * 255.0);\n\tvec3 v = vec3(\n\t\tcodebook[idx.x >> 2u][idx.x & 3u],\n\t\tcodebook[idx.y >> 2u][idx.y & 3u],\n\t\tcodebook[idx.z >> 2u][idx.z & 3u]\n\t);\n\treturn (v - codebook[0].x) / (codebook[63].w - codebook[0].x);\n}\n`;\n\nvar wgslGsplatSogsReorderSH = `\n#include \"gsplatPackingPS\"\nvar sh_centroids: texture_2d<f32>;\nuniform shN_codebook: array<vec4f, 64>;\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\tvar uv = vec2i(input.position.xy);\n\tvar shNSample = textureLoad(sh_centroids, uv, 0).xyz;\n#ifdef REORDER_V1\n\toutput.color = unpack8888(pack111110(shNSample));\n#else\n\toutput.color = unpack8888(pack111110(resolveCodebook(shNSample, &uniform.shN_codebook)));\n#endif\n\treturn output;\n}\n`;\n\nvar gsplatPackingPS = `\nfn pack8888(v: vec4f) -> u32 {\n\tlet t = vec4u(v * 255.0) << vec4u(24u, 16u, 8u, 0u);\n\treturn t.x | t.y | t.z | t.w;\n}\nfn pack101010(v: vec3f) -> u32 {\n\tlet t = vec3u(v * vec3f(1023.0, 1023.0, 1023.0)) << vec3u(20u, 10u, 0u);\n\treturn t.x | t.y | t.z;\n}\nfn pack111110(v: vec3f) -> u32 {\n\tlet t = vec3u(v * vec3f(2047.0, 2047.0, 1023.0)) << vec3u(21u, 10u, 0u);\n\treturn t.x | t.y | t.z;\n}\nfn unpack8888(v: u32) -> vec4f {\n\treturn vec4f((vec4u(v) >> vec4u(24u, 16u, 8u, 0u)) & vec4u(0xffu)) / 255.0;\n}\nfn unpack101010(v: u32) -> vec3f {\n\treturn vec3f((vec3u(v) >> vec3u(20u, 10u, 0u)) & vec3u(0x3ffu)) / 1023.0;\n}\nfn unpack111110(v: u32) -> vec3f {\n\treturn vec3f((vec3u(v) >> vec3u(21u, 10u, 0u)) & vec3u(0x7ffu, 0x7ffu, 0x3ffu)) / vec3f(2047.0, 2047.0, 1023.0);\n}\nfn resolveCodebook(s: vec3f, codebook: ptr<uniform, array<vec4f, 64>>) -> vec3f {\n\tlet idx = vec3u(s * 255.0);\n\tlet v = vec3f(\n\t\tcodebook[idx.x >> 2u][idx.x & 3u],\n\t\tcodebook[idx.y >> 2u][idx.y & 3u],\n\t\tcodebook[idx.z >> 2u][idx.z & 3u]\n\t);\n\treturn (v - codebook[0].x) / (codebook[63].w - codebook[0].x);\n}\n`;\n\nconst SH_C0$1 = 0.28209479177387814;\nconst readImageDataAsync = (texture)=>{\n\t\treturn texture.read(0, 0, texture.width, texture.height, {\n\t\t\t\tmipLevel: 0,\n\t\t\t\tface: 0,\n\t\t\t\timmediate: true\n\t\t});\n};\nconst resolve = (scope, values)=>{\n\t\tfor(const key in values){\n\t\t\t\tscope.resolve(key).setValue(values[key]);\n\t\t}\n};\nclass GSplatSogsIterator {\n\t\tconstructor(data, p, r, s, c, sh){\n\t\t\t\tconst lerp = (a, b, t)=>a * (1 - t) + b * t;\n\t\t\t\tconst { meta, shBands } = data;\n\t\t\t\tconst { means, scales, sh0, shN } = meta;\n\t\t\t\tconst means_l_data = p && data.means_l._levels[0];\n\t\t\t\tconst means_u_data = p && data.means_u._levels[0];\n\t\t\t\tconst quats_data = r && data.quats._levels[0];\n\t\t\t\tconst scales_data = s && data.scales._levels[0];\n\t\t\t\tconst sh0_data = c && data.sh0._levels[0];\n\t\t\t\tconst sh_labels_data = sh && data.sh_labels._levels[0];\n\t\t\t\tconst sh_centroids_data = sh && data.sh_centroids._levels[0];\n\t\t\t\tconst norm = 2.0 / Math.sqrt(2.0);\n\t\t\t\tconst coeffs = {\n\t\t\t\t\t\t1: 3,\n\t\t\t\t\t\t2: 8,\n\t\t\t\t\t\t3: 15\n\t\t\t\t}[shBands] ?? 0;\n\t\t\t\tthis.read = (i)=>{\n\t\t\t\t\t\tif (p) {\n\t\t\t\t\t\t\t\tconst nx = lerp(means.mins[0], means.maxs[0], ((means_u_data[i * 4 + 0] << 8) + means_l_data[i * 4 + 0]) / 65535);\n\t\t\t\t\t\t\t\tconst ny = lerp(means.mins[1], means.maxs[1], ((means_u_data[i * 4 + 1] << 8) + means_l_data[i * 4 + 1]) / 65535);\n\t\t\t\t\t\t\t\tconst nz = lerp(means.mins[2], means.maxs[2], ((means_u_data[i * 4 + 2] << 8) + means_l_data[i * 4 + 2]) / 65535);\n\t\t\t\t\t\t\t\tp.x = Math.sign(nx) * (Math.exp(Math.abs(nx)) - 1);\n\t\t\t\t\t\t\t\tp.y = Math.sign(ny) * (Math.exp(Math.abs(ny)) - 1);\n\t\t\t\t\t\t\t\tp.z = Math.sign(nz) * (Math.exp(Math.abs(nz)) - 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (r) {\n\t\t\t\t\t\t\t\tconst a = (quats_data[i * 4 + 0] / 255 - 0.5) * norm;\n\t\t\t\t\t\t\t\tconst b = (quats_data[i * 4 + 1] / 255 - 0.5) * norm;\n\t\t\t\t\t\t\t\tconst c = (quats_data[i * 4 + 2] / 255 - 0.5) * norm;\n\t\t\t\t\t\t\t\tconst d = Math.sqrt(Math.max(0, 1 - (a * a + b * b + c * c)));\n\t\t\t\t\t\t\t\tconst mode = quats_data[i * 4 + 3] - 252;\n\t\t\t\t\t\t\t\tswitch(mode){\n\t\t\t\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\t\t\t\t\tr.set(a, b, c, d);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\t\t\t\t\tr.set(d, b, c, a);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\t\t\t\t\tr.set(b, d, c, a);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase 3:\n\t\t\t\t\t\t\t\t\t\t\t\tr.set(b, c, d, a);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (s) {\n\t\t\t\t\t\t\t\tif (meta.version === 2) {\n\t\t\t\t\t\t\t\t\t\tconst sx = scales.codebook[scales_data[i * 4 + 0]];\n\t\t\t\t\t\t\t\t\t\tconst sy = scales.codebook[scales_data[i * 4 + 1]];\n\t\t\t\t\t\t\t\t\t\tconst sz = scales.codebook[scales_data[i * 4 + 2]];\n\t\t\t\t\t\t\t\t\t\ts.set(sx, sy, sz);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst sx = lerp(scales.mins[0], scales.maxs[0], scales_data[i * 4 + 0] / 255);\n\t\t\t\t\t\t\t\t\t\tconst sy = lerp(scales.mins[1], scales.maxs[1], scales_data[i * 4 + 1] / 255);\n\t\t\t\t\t\t\t\t\t\tconst sz = lerp(scales.mins[2], scales.maxs[2], scales_data[i * 4 + 2] / 255);\n\t\t\t\t\t\t\t\t\t\ts.set(sx, sy, sz);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (c) {\n\t\t\t\t\t\t\t\tif (meta.version === 2) {\n\t\t\t\t\t\t\t\t\t\tconst r = sh0.codebook[sh0_data[i * 4 + 0]];\n\t\t\t\t\t\t\t\t\t\tconst g = sh0.codebook[sh0_data[i * 4 + 1]];\n\t\t\t\t\t\t\t\t\t\tconst b = sh0.codebook[sh0_data[i * 4 + 2]];\n\t\t\t\t\t\t\t\t\t\tconst a = sh0_data[i * 4 + 3] / 255;\n\t\t\t\t\t\t\t\t\t\tc.set(0.5 + r * SH_C0$1, 0.5 + g * SH_C0$1, 0.5 + b * SH_C0$1, a);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst r = lerp(sh0.mins[0], sh0.maxs[0], sh0_data[i * 4 + 0] / 255);\n\t\t\t\t\t\t\t\t\t\tconst g = lerp(sh0.mins[1], sh0.maxs[1], sh0_data[i * 4 + 1] / 255);\n\t\t\t\t\t\t\t\t\t\tconst b = lerp(sh0.mins[2], sh0.maxs[2], sh0_data[i * 4 + 2] / 255);\n\t\t\t\t\t\t\t\t\t\tconst a = lerp(sh0.mins[3], sh0.maxs[3], sh0_data[i * 4 + 3] / 255);\n\t\t\t\t\t\t\t\t\t\tc.set(0.5 + r * SH_C0$1, 0.5 + g * SH_C0$1, 0.5 + b * SH_C0$1, 1.0 / (1.0 + Math.exp(-a)));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sh) {\n\t\t\t\t\t\t\t\tconst n = sh_labels_data[i * 4 + 0] + (sh_labels_data[i * 4 + 1] << 8);\n\t\t\t\t\t\t\t\tconst u = n % 64 * coeffs;\n\t\t\t\t\t\t\t\tconst v = Math.floor(n / 64);\n\t\t\t\t\t\t\t\tif (meta.version === 2) {\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < 3; ++j){\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let k = 0; k < coeffs; ++k){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsh[j * 15 + k] = shN.codebook[sh_centroids_data[(u + k) * 4 + j + v * data.sh_centroids.width * 4]];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < 3; ++j){\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let k = 0; k < coeffs; ++k){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsh[j * 15 + k] = lerp(shN.mins, shN.maxs, sh_centroids_data[(u + k) * 4 + j + v * data.sh_centroids.width * 4] / 255);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t}\n}\nclass GSplatSogsData {\n\t\tdestroy() {\n\t\t\t\tthis.means_l?.destroy();\n\t\t\t\tthis.means_u?.destroy();\n\t\t\t\tthis.quats?.destroy();\n\t\t\t\tthis.scales?.destroy();\n\t\t\t\tthis.sh0?.destroy();\n\t\t\t\tthis.sh_centroids?.destroy();\n\t\t\t\tthis.sh_labels?.destroy();\n\t\t\t\tthis.packedTexture?.destroy();\n\t\t\t\tthis.packedSh0?.destroy();\n\t\t\t\tthis.packedShN?.destroy();\n\t\t}\n\t\tcreateIter(p, r, s, c, sh) {\n\t\t\t\treturn new GSplatSogsIterator(this, p, r, s, c, sh);\n\t\t}\n\t\tcalcAabb(result) {\n\t\t\t\tconst { mins, maxs } = this.meta.means;\n\t\t\t\tconst map = (v)=>Math.sign(v) * (Math.exp(Math.abs(v)) - 1);\n\t\t\t\tresult.center.set((map(mins[0]) + map(maxs[0])) * 0.5, (map(mins[1]) + map(maxs[1])) * 0.5, (map(mins[2]) + map(maxs[2])) * 0.5);\n\t\t\t\tresult.halfExtents.set((map(maxs[0]) - map(mins[0])) * 0.5, (map(maxs[1]) - map(mins[1])) * 0.5, (map(maxs[2]) - map(mins[2])) * 0.5);\n\t\t}\n\t\tgetCenters(result) {\n\t\t\t\tconst { meta, means_l, means_u, numSplats } = this;\n\t\t\t\tconst { means } = meta;\n\t\t\t\tconst means_u_data = new Uint32Array(means_u._levels[0].buffer);\n\t\t\t\tconst means_l_data = new Uint32Array(means_l._levels[0].buffer);\n\t\t\t\tconst mx = means.mins[0] / 65535;\n\t\t\t\tconst my = means.mins[1] / 65535;\n\t\t\t\tconst mz = means.mins[2] / 65535;\n\t\t\t\tconst Mx = means.maxs[0] / 65535;\n\t\t\t\tconst My = means.maxs[1] / 65535;\n\t\t\t\tconst Mz = means.maxs[2] / 65535;\n\t\t\t\tfor(let i = 0; i < numSplats; i++){\n\t\t\t\t\t\tconst idx = i;\n\t\t\t\t\t\tconst means_u = means_u_data[idx];\n\t\t\t\t\t\tconst means_l = means_l_data[idx];\n\t\t\t\t\t\tconst wx = means_u << 8 & 0xff00 | means_l & 0xff;\n\t\t\t\t\t\tconst wy = means_u & 0xff00 | means_l >>> 8 & 0xff;\n\t\t\t\t\t\tconst wz = means_u >>> 8 & 0xff00 | means_l >>> 16 & 0xff;\n\t\t\t\t\t\tconst nx = mx * (65535 - wx) + Mx * wx;\n\t\t\t\t\t\tconst ny = my * (65535 - wy) + My * wy;\n\t\t\t\t\t\tconst nz = mz * (65535 - wz) + Mz * wz;\n\t\t\t\t\t\tconst ax = nx < 0 ? -nx : nx;\n\t\t\t\t\t\tconst ay = ny < 0 ? -ny : ny;\n\t\t\t\t\t\tconst az = nz < 0 ? -nz : nz;\n\t\t\t\t\t\tresult[i * 3] = (nx < 0 ? -1 : 1) * (Math.exp(ax) - 1);\n\t\t\t\t\t\tresult[i * 3 + 1] = (ny < 0 ? -1 : 1) * (Math.exp(ay) - 1);\n\t\t\t\t\t\tresult[i * 3 + 2] = (nz < 0 ? -1 : 1) * (Math.exp(az) - 1);\n\t\t\t\t}\n\t\t}\n\t\tcalcFocalPoint(result, pred) {\n\t\t\t\tconst { mins, maxs } = this.meta.means;\n\t\t\t\tconst map = (v)=>Math.sign(v) * (Math.exp(Math.abs(v)) - 1);\n\t\t\t\tresult.set((map(mins[0]) + map(maxs[0])) * 0.5, (map(mins[1]) + map(maxs[1])) * 0.5, (map(mins[2]) + map(maxs[2])) * 0.5);\n\t\t}\n\t\tget isSogs() {\n\t\t\t\treturn true;\n\t\t}\n\t\tget shBands() {\n\t\t\t\tconst widths = {\n\t\t\t\t\t\t192: 1,\n\t\t\t\t\t\t512: 2,\n\t\t\t\t\t\t960: 3\n\t\t\t\t};\n\t\t\t\treturn widths[this.sh_centroids?.width] ?? 0;\n\t\t}\n\t\tasync decompress() {\n\t\t\t\tconst members = [\n\t\t\t\t\t\t'x',\n\t\t\t\t\t\t'y',\n\t\t\t\t\t\t'z',\n\t\t\t\t\t\t'f_dc_0',\n\t\t\t\t\t\t'f_dc_1',\n\t\t\t\t\t\t'f_dc_2',\n\t\t\t\t\t\t'opacity',\n\t\t\t\t\t\t'scale_0',\n\t\t\t\t\t\t'scale_1',\n\t\t\t\t\t\t'scale_2',\n\t\t\t\t\t\t'rot_0',\n\t\t\t\t\t\t'rot_1',\n\t\t\t\t\t\t'rot_2',\n\t\t\t\t\t\t'rot_3'\n\t\t\t\t];\n\t\t\t\tconst { shBands } = this;\n\t\t\t\tconst { means_l, means_u, quats, scales, sh0, sh_labels, sh_centroids } = this;\n\t\t\t\tmeans_l._levels[0] = await readImageDataAsync(means_l);\n\t\t\t\tmeans_u._levels[0] = await readImageDataAsync(means_u);\n\t\t\t\tquats._levels[0] = await readImageDataAsync(quats);\n\t\t\t\tscales._levels[0] = await readImageDataAsync(scales);\n\t\t\t\tsh0._levels[0] = await readImageDataAsync(sh0);\n\t\t\t\tif (shBands > 0) {\n\t\t\t\t\t\tsh_labels._levels[0] = await readImageDataAsync(sh_labels);\n\t\t\t\t\t\tsh_centroids._levels[0] = await readImageDataAsync(sh_centroids);\n\t\t\t\t\t\tconst shMembers = [];\n\t\t\t\t\t\tfor(let i = 0; i < 45; ++i){\n\t\t\t\t\t\t\t\tshMembers.push(`f_rest_${i}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmembers.splice(members.indexOf('f_dc_0') + 1, 0, ...shMembers);\n\t\t\t\t}\n\t\t\t\tconst data = {};\n\t\t\t\tmembers.forEach((name)=>{\n\t\t\t\t\t\tdata[name] = new Float32Array(this.numSplats);\n\t\t\t\t});\n\t\t\t\tconst p = new Vec3();\n\t\t\t\tconst r = new Quat();\n\t\t\t\tconst s = new Vec3();\n\t\t\t\tconst c = new Vec4();\n\t\t\t\tconst sh = shBands > 0 ? new Float32Array(45) : null;\n\t\t\t\tconst iter = this.createIter(p, r, s, c, sh);\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\titer.read(i);\n\t\t\t\t\t\tdata.x[i] = p.x;\n\t\t\t\t\t\tdata.y[i] = p.y;\n\t\t\t\t\t\tdata.z[i] = p.z;\n\t\t\t\t\t\tdata.rot_1[i] = r.x;\n\t\t\t\t\t\tdata.rot_2[i] = r.y;\n\t\t\t\t\t\tdata.rot_3[i] = r.z;\n\t\t\t\t\t\tdata.rot_0[i] = r.w;\n\t\t\t\t\t\tdata.scale_0[i] = s.x;\n\t\t\t\t\t\tdata.scale_1[i] = s.y;\n\t\t\t\t\t\tdata.scale_2[i] = s.z;\n\t\t\t\t\t\tdata.f_dc_0[i] = (c.x - 0.5) / SH_C0$1;\n\t\t\t\t\t\tdata.f_dc_1[i] = (c.y - 0.5) / SH_C0$1;\n\t\t\t\t\t\tdata.f_dc_2[i] = (c.z - 0.5) / SH_C0$1;\n\t\t\t\t\t\tdata.opacity[i] = c.w <= 0 ? -40 : c.w >= 1 ? 40 : -Math.log(1 / c.w - 1);\n\t\t\t\t\t\tif (sh) {\n\t\t\t\t\t\t\t\tfor(let c = 0; c < 45; ++c){\n\t\t\t\t\t\t\t\t\t\tdata[`f_rest_${c}`][i] = sh[c];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn new GSplatData([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: 'vertex',\n\t\t\t\t\t\t\t\tcount: this.numSplats,\n\t\t\t\t\t\t\t\tproperties: members.map((name)=>{\n\t\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\t\t\tname: name,\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'float',\n\t\t\t\t\t\t\t\t\t\t\t\tbyteSize: 4,\n\t\t\t\t\t\t\t\t\t\t\t\tstorage: data[name]\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t]);\n\t\t}\n\t\tpackGpuMemory() {\n\t\t\t\tconst { meta, means_l, means_u, quats, scales, sh0, sh_labels, numSplats } = this;\n\t\t\t\tconst { device } = means_l;\n\t\t\t\tconst { scope } = device;\n\t\t\t\tconst shaderKey = meta.version === 2 ? 'v2' : 'v1';\n\t\t\t\tconst shader = ShaderUtils.createShader(device, {\n\t\t\t\t\t\tuniqueName: `GsplatSogsReorderShader-${shaderKey}`,\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvertexChunk: 'fullscreenQuadVS',\n\t\t\t\t\t\tfragmentGLSL: glslGsplatSogsReorderPS,\n\t\t\t\t\t\tfragmentWGSL: wgslGsplatSogsReorderPS,\n\t\t\t\t\t\tfragmentOutputTypes: [\n\t\t\t\t\t\t\t\t'uvec4',\n\t\t\t\t\t\t\t\t'vec4'\n\t\t\t\t\t\t],\n\t\t\t\t\t\tfragmentIncludes: new Map([\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t'gsplatPackingPS',\n\t\t\t\t\t\t\t\t\t\tdevice.isWebGPU ? gsplatPackingPS : gsplatPackingPS$1\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tfragmentDefines: meta.version === 2 ? undefined : new Map([\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t'REORDER_V1',\n\t\t\t\t\t\t\t\t\t\t'1'\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t])\n\t\t\t\t});\n\t\t\t\tconst renderTarget = new RenderTarget({\n\t\t\t\t\t\tcolorBuffers: [\n\t\t\t\t\t\t\t\tthis.packedTexture,\n\t\t\t\t\t\t\t\tthis.packedSh0\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdepth: false,\n\t\t\t\t\t\tmipLevel: 0\n\t\t\t\t});\n\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\tresolve(scope, {\n\t\t\t\t\t\tmeans_l,\n\t\t\t\t\t\tmeans_u,\n\t\t\t\t\t\tquats,\n\t\t\t\t\t\tscales,\n\t\t\t\t\t\tsh0,\n\t\t\t\t\t\tsh_labels: sh_labels ?? means_l,\n\t\t\t\t\t\tnumSplats,\n\t\t\t\t\t\t'scales_codebook[0]': this.meta.scales.codebook,\n\t\t\t\t\t\t'sh0_codebook[0]': this.meta.sh0.codebook,\n\t\t\t\t\t\tscalesMins: meta.scales.mins,\n\t\t\t\t\t\tscalesMaxs: meta.scales.maxs,\n\t\t\t\t\t\tsh0Mins: meta.sh0.mins,\n\t\t\t\t\t\tsh0Maxs: meta.sh0.maxs\n\t\t\t\t});\n\t\t\t\tdrawQuadWithShader(device, renderTarget, shader);\n\t\t\t\trenderTarget.destroy();\n\t\t}\n\t\tpackShMemory() {\n\t\t\t\tconst { meta, sh_centroids } = this;\n\t\t\t\tconst { device } = sh_centroids;\n\t\t\t\tconst { scope } = device;\n\t\t\t\tconst shaderKey = meta.version === 2 ? 'v2' : 'v1';\n\t\t\t\tconst shader = ShaderUtils.createShader(device, {\n\t\t\t\t\t\tuniqueName: `GsplatSogsReorderShShader-${shaderKey}`,\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvertexChunk: 'fullscreenQuadVS',\n\t\t\t\t\t\tfragmentGLSL: glslGsplatSogsReorderSh,\n\t\t\t\t\t\tfragmentWGSL: wgslGsplatSogsReorderSH,\n\t\t\t\t\t\tfragmentIncludes: new Map([\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t'gsplatPackingPS',\n\t\t\t\t\t\t\t\t\t\tdevice.isWebGPU ? gsplatPackingPS : gsplatPackingPS$1\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tfragmentDefines: meta.version === 2 ? undefined : new Map([\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t'REORDER_V1',\n\t\t\t\t\t\t\t\t\t\t'1'\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t])\n\t\t\t\t});\n\t\t\t\tconst renderTarget = new RenderTarget({\n\t\t\t\t\t\tcolorBuffer: this.packedShN,\n\t\t\t\t\t\tdepth: false,\n\t\t\t\t\t\tmipLevel: 0\n\t\t\t\t});\n\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\tresolve(scope, {\n\t\t\t\t\t\tsh_centroids,\n\t\t\t\t\t\t'shN_codebook[0]': this.meta.shN.codebook\n\t\t\t\t});\n\t\t\t\tdrawQuadWithShader(device, renderTarget, shader);\n\t\t\t\trenderTarget.destroy();\n\t\t}\n\t\tasync prepareGpuData() {\n\t\t\t\tconst { device, height, width } = this.means_l;\n\t\t\t\tthis.means_l._levels[0] = await readImageDataAsync(this.means_l);\n\t\t\t\tthis.means_u._levels[0] = await readImageDataAsync(this.means_u);\n\t\t\t\tthis.packedTexture = new Texture(device, {\n\t\t\t\t\t\tname: 'sogsPackedTexture',\n\t\t\t\t\t\twidth,\n\t\t\t\t\t\theight,\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA32U,\n\t\t\t\t\t\tmipmaps: false\n\t\t\t\t});\n\t\t\t\tthis.packedSh0 = new Texture(device, {\n\t\t\t\t\t\tname: 'sogsPackedSh0',\n\t\t\t\t\t\twidth,\n\t\t\t\t\t\theight,\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA8,\n\t\t\t\t\t\tmipmaps: false\n\t\t\t\t});\n\t\t\t\tthis.packedShN = this.sh_centroids && new Texture(device, {\n\t\t\t\t\t\tname: 'sogsPackedShN',\n\t\t\t\t\t\twidth: this.sh_centroids.width,\n\t\t\t\t\t\theight: this.sh_centroids.height,\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA8,\n\t\t\t\t\t\tmipmaps: false\n\t\t\t\t});\n\t\t\t\tdevice.on('devicerestored', ()=>{\n\t\t\t\t\t\tthis.packGpuMemory();\n\t\t\t\t\t\tif (this.packedShN) {\n\t\t\t\t\t\t\t\tthis.packShMemory();\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis.packGpuMemory();\n\t\t\t\tif (this.packedShN) {\n\t\t\t\t\t\tthis.packShMemory();\n\t\t\t\t}\n\t\t}\n\t\treorderData() {\n\t\t\t\treturn this.prepareGpuData();\n\t\t}\n}\n\nconst mat = new Mat4();\nconst cameraPosition$1 = new Vec3();\nconst cameraDirection$1 = new Vec3();\nconst viewport = [\n\t\t0,\n\t\t0\n];\nclass GSplatInstance {\n\t\tconstructor(resource, options = {}){\n\t\t\t\tthis.options = {};\n\t\t\t\tthis.sorter = null;\n\t\t\t\tthis.lastCameraPosition = new Vec3();\n\t\t\t\tthis.lastCameraDirection = new Vec3();\n\t\t\t\tthis.resolveSH = null;\n\t\t\t\tthis.cameras = [];\n\t\t\t\tthis.resource = resource;\n\t\t\t\tthis.orderTexture = resource.createTexture('splatOrder', PIXELFORMAT_R32U, resource.evalTextureSize(resource.numSplats));\n\t\t\t\tif (options.material) {\n\t\t\t\t\t\tthis._material = options.material;\n\t\t\t\t\t\tthis._material.setParameter('splatOrder', this.orderTexture);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._material = new ShaderMaterial({\n\t\t\t\t\t\t\t\tuniqueName: 'SplatMaterial',\n\t\t\t\t\t\t\t\tvertexGLSL: '#include \"gsplatVS\"',\n\t\t\t\t\t\t\t\tfragmentGLSL: '#include \"gsplatPS\"',\n\t\t\t\t\t\t\t\tvertexWGSL: '#include \"gsplatVS\"',\n\t\t\t\t\t\t\t\tfragmentWGSL: '#include \"gsplatPS\"',\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION,\n\t\t\t\t\t\t\t\t\t\tvertex_id_attrib: SEMANTIC_ATTR13\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.configureMaterial(this._material);\n\t\t\t\t\t\tthis._material.update();\n\t\t\t\t}\n\t\t\t\tthis.meshInstance = new MeshInstance(resource.mesh, this._material);\n\t\t\t\tthis.meshInstance.setInstancing(resource.instanceIndices, true);\n\t\t\t\tthis.meshInstance.gsplatInstance = this;\n\t\t\t\tthis.meshInstance.instancingCount = 0;\n\t\t\t\tconst centers = resource.centers.slice();\n\t\t\t\tconst chunks = resource.chunks?.slice();\n\t\t\t\tthis.sorter = new GSplatSorter();\n\t\t\t\tthis.sorter.init(this.orderTexture, centers, chunks);\n\t\t\t\tthis.sorter.on('updated', (count)=>{\n\t\t\t\t\t\tthis.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize);\n\t\t\t\t\t\tthis.material.setParameter('numSplats', count);\n\t\t\t\t});\n\t\t\t\tthis.setHighQualitySH(options.highQualitySH ?? false);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.resolveSH?.destroy();\n\t\t\t\tthis.material?.destroy();\n\t\t\t\tthis.meshInstance?.destroy();\n\t\t\t\tthis.sorter?.destroy();\n\t\t}\n\t\tset material(value) {\n\t\t\t\tif (this._material !== value) {\n\t\t\t\t\t\tthis._material = value;\n\t\t\t\t\t\tthis._material.setParameter('splatOrder', this.orderTexture);\n\t\t\t\t\t\tif (this.meshInstance) {\n\t\t\t\t\t\t\t\tthis.meshInstance.material = value;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget material() {\n\t\t\t\treturn this._material;\n\t\t}\n\t\tconfigureMaterial(material, options = {}) {\n\t\t\t\tthis.resource.configureMaterial(material);\n\t\t\t\tmaterial.setParameter('numSplats', 0);\n\t\t\t\tmaterial.setParameter('splatOrder', this.orderTexture);\n\t\t\t\tmaterial.setParameter('alphaClip', 0.3);\n\t\t\t\tmaterial.setDefine(`DITHER_${options.dither ? 'BLUENOISE' : 'NONE'}`, '');\n\t\t\t\tmaterial.cull = CULLFACE_NONE;\n\t\t\t\tmaterial.blendType = options.dither ? BLEND_NONE : BLEND_PREMULTIPLIED;\n\t\t\t\tmaterial.depthWrite = !!options.dither;\n\t\t}\n\t\tupdateViewport(cameraNode) {\n\t\t\t\tconst camera = cameraNode?.camera;\n\t\t\t\tconst renderTarget = camera?.renderTarget;\n\t\t\t\tconst { width, height } = renderTarget ?? this.resource.device;\n\t\t\t\tviewport[0] = width;\n\t\t\t\tviewport[1] = height;\n\t\t\t\tconst xr = camera?.camera?.xr;\n\t\t\t\tif (xr?.active && xr.views.list.length === 2) {\n\t\t\t\t\t\tviewport[0] *= 0.5;\n\t\t\t\t}\n\t\t\t\tthis.material.setParameter('viewport', viewport);\n\t\t}\n\t\tsort(cameraNode) {\n\t\t\t\tif (this.sorter) {\n\t\t\t\t\t\tconst cameraMat = cameraNode.getWorldTransform();\n\t\t\t\t\t\tcameraMat.getTranslation(cameraPosition$1);\n\t\t\t\t\t\tcameraMat.getZ(cameraDirection$1);\n\t\t\t\t\t\tconst modelMat = this.meshInstance.node.getWorldTransform();\n\t\t\t\t\t\tconst invModelMat = mat.invert(modelMat);\n\t\t\t\t\t\tinvModelMat.transformPoint(cameraPosition$1, cameraPosition$1);\n\t\t\t\t\t\tinvModelMat.transformVector(cameraDirection$1, cameraDirection$1);\n\t\t\t\t\t\tif (!cameraPosition$1.equalsApprox(this.lastCameraPosition) || !cameraDirection$1.equalsApprox(this.lastCameraDirection)) {\n\t\t\t\t\t\t\t\tthis.lastCameraPosition.copy(cameraPosition$1);\n\t\t\t\t\t\t\t\tthis.lastCameraDirection.copy(cameraDirection$1);\n\t\t\t\t\t\t\t\tthis.sorter.setCamera(cameraPosition$1, cameraDirection$1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.updateViewport(cameraNode);\n\t\t}\n\t\tupdate() {\n\t\t\t\tif (this.cameras.length > 0) {\n\t\t\t\t\t\tconst camera = this.cameras[0];\n\t\t\t\t\t\tthis.sort(camera._node);\n\t\t\t\t\t\tthis.resolveSH?.render(camera._node, this.meshInstance.node.getWorldTransform());\n\t\t\t\t\t\tthis.cameras.length = 0;\n\t\t\t\t}\n\t\t}\n\t\tsetHighQualitySH(value) {\n\t\t\t\tconst { resource } = this;\n\t\t\t\tconst { gsplatData } = resource;\n\t\t\t\tif (gsplatData instanceof GSplatSogsData && gsplatData.shBands > 0 && value === !!this.resolveSH) {\n\t\t\t\t\t\tif (this.resolveSH) {\n\t\t\t\t\t\t\t\tthis.resolveSH.destroy();\n\t\t\t\t\t\t\t\tthis.resolveSH = null;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.resolveSH = new GSplatResolveSH(resource.device, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass GSplatSogsResource extends GSplatResourceBase {\n\t\tdestroy() {\n\t\t\t\tthis.gsplatData.destroy();\n\t\t\t\tsuper.destroy();\n\t\t}\n\t\tconfigureMaterialDefines(defines) {\n\t\t\t\tdefines.set('GSPLAT_SOGS_DATA', true);\n\t\t\t\tdefines.set('SH_BANDS', this.gsplatData.shBands);\n\t\t}\n\t\tconfigureMaterial(material) {\n\t\t\t\tconst { gsplatData } = this;\n\t\t\t\tconst { meta } = gsplatData;\n\t\t\t\tthis.configureMaterialDefines(material.defines);\n\t\t\t\t[\n\t\t\t\t\t\t'packedTexture',\n\t\t\t\t\t\t'packedSh0',\n\t\t\t\t\t\t'packedShN'\n\t\t\t\t].forEach((name)=>{\n\t\t\t\t\t\tif (gsplatData[name]) {\n\t\t\t\t\t\t\t\tmaterial.setParameter(name, gsplatData[name]);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\t[\n\t\t\t\t\t\t'means'\n\t\t\t\t].forEach((name)=>{\n\t\t\t\t\t\tconst v = meta[name];\n\t\t\t\t\t\tif (v) {\n\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_mins`, v.mins);\n\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_maxs`, v.maxs);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (meta.version === 2) {\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'scales',\n\t\t\t\t\t\t\t\t'sh0',\n\t\t\t\t\t\t\t\t'shN'\n\t\t\t\t\t\t].forEach((name)=>{\n\t\t\t\t\t\t\t\tconst v = meta[name];\n\t\t\t\t\t\t\t\tif (v) {\n\t\t\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_mins`, v.codebook[0]);\n\t\t\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_maxs`, v.codebook[255]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'scales',\n\t\t\t\t\t\t\t\t'sh0'\n\t\t\t\t\t\t].forEach((name)=>{\n\t\t\t\t\t\t\t\tconst v = meta[name];\n\t\t\t\t\t\t\t\tif (v) {\n\t\t\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_mins`, Math.min(...v.mins.slice(0, 3)));\n\t\t\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_maxs`, Math.max(...v.maxs.slice(0, 3)));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'shN'\n\t\t\t\t\t\t].forEach((name)=>{\n\t\t\t\t\t\t\t\tconst v = meta[name];\n\t\t\t\t\t\t\t\tif (v) {\n\t\t\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_mins`, v.mins);\n\t\t\t\t\t\t\t\t\t\tmaterial.setParameter(`${name}_maxs`, v.maxs);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tevalTextureSize(count) {\n\t\t\t\treturn new Vec2(this.gsplatData.means_l.width, this.gsplatData.means_l.height);\n\t\t}\n}\n\nconst FILLMODE_FILL_WINDOW = 'FILL_WINDOW';\nconst FILLMODE_KEEP_ASPECT = 'KEEP_ASPECT';\nconst RESOLUTION_AUTO = 'AUTO';\nconst RESOLUTION_FIXED = 'FIXED';\n\nlet currentApplication;\nfunction getApplication() {\n\t\treturn currentApplication;\n}\nfunction setApplication(app) {\n\t\tcurrentApplication = app;\n}\n\nclass FrameGraph {\n\t\taddRenderPass(renderPass) {\n\t\t\t\trenderPass.frameUpdate();\n\t\t\t\tconst beforePasses = renderPass.beforePasses;\n\t\t\t\tfor(let i = 0; i < beforePasses.length; i++){\n\t\t\t\t\t\tconst pass = beforePasses[i];\n\t\t\t\t\t\tif (pass.enabled) {\n\t\t\t\t\t\t\t\tthis.addRenderPass(pass);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (renderPass.enabled) {\n\t\t\t\t\t\tthis.renderPasses.push(renderPass);\n\t\t\t\t}\n\t\t\t\tconst afterPasses = renderPass.afterPasses;\n\t\t\t\tfor(let i = 0; i < afterPasses.length; i++){\n\t\t\t\t\t\tconst pass = afterPasses[i];\n\t\t\t\t\t\tif (pass.enabled) {\n\t\t\t\t\t\t\t\tthis.addRenderPass(pass);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\treset() {\n\t\t\t\tthis.renderPasses.length = 0;\n\t\t}\n\t\tcompile() {\n\t\t\t\tconst renderTargetMap = this.renderTargetMap;\n\t\t\t\tconst renderPasses = this.renderPasses;\n\t\t\t\tfor(let i = 0; i < renderPasses.length; i++){\n\t\t\t\t\t\tconst renderPass = renderPasses[i];\n\t\t\t\t\t\tconst renderTarget = renderPass.renderTarget;\n\t\t\t\t\t\tif (renderTarget !== undefined) {\n\t\t\t\t\t\t\t\tconst prevPass = renderTargetMap.get(renderTarget);\n\t\t\t\t\t\t\t\tif (prevPass) {\n\t\t\t\t\t\t\t\t\t\tconst count = renderPass.colorArrayOps.length;\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < count; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst colorOps = renderPass.colorArrayOps[j];\n\t\t\t\t\t\t\t\t\t\t\t\tif (!colorOps.clear) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tprevPass.colorArrayOps[j].store = true;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!renderPass.depthStencilOps.clearDepth) {\n\t\t\t\t\t\t\t\t\t\t\t\tprevPass.depthStencilOps.storeDepth = true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!renderPass.depthStencilOps.clearStencil) {\n\t\t\t\t\t\t\t\t\t\t\t\tprevPass.depthStencilOps.storeStencil = true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\trenderTargetMap.set(renderTarget, renderPass);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < renderPasses.length - 1; i++){\n\t\t\t\t\t\tconst firstPass = renderPasses[i];\n\t\t\t\t\t\tconst firstRT = firstPass.renderTarget;\n\t\t\t\t\t\tconst secondPass = renderPasses[i + 1];\n\t\t\t\t\t\tconst secondRT = secondPass.renderTarget;\n\t\t\t\t\t\tif (firstRT !== secondRT || firstRT === undefined) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (secondPass.depthStencilOps.clearDepth || secondPass.depthStencilOps.clearStencil || secondPass.colorArrayOps.some((colorOps)=>colorOps.clear)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (firstPass.afterPasses.length > 0) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (secondPass.beforePasses.length > 0) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfirstPass._skipEnd = true;\n\t\t\t\t\t\tsecondPass._skipStart = true;\n\t\t\t\t}\n\t\t\t\tlet lastCubeTexture = null;\n\t\t\t\tlet lastCubeRenderPass = null;\n\t\t\t\tfor(let i = 0; i < renderPasses.length; i++){\n\t\t\t\t\t\tconst renderPass = renderPasses[i];\n\t\t\t\t\t\tconst renderTarget = renderPass.renderTarget;\n\t\t\t\t\t\tconst thisTexture = renderTarget?.colorBuffer;\n\t\t\t\t\t\tif (thisTexture?.cubemap) {\n\t\t\t\t\t\t\t\tif (lastCubeTexture === thisTexture) {\n\t\t\t\t\t\t\t\t\t\tconst count = lastCubeRenderPass.colorArrayOps.length;\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < count; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tlastCubeRenderPass.colorArrayOps[j].mipmaps = false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlastCubeTexture = renderTarget.colorBuffer;\n\t\t\t\t\t\t\t\tlastCubeRenderPass = renderPass;\n\t\t\t\t\t\t} else if (renderPass.requiresCubemaps) {\n\t\t\t\t\t\t\t\tlastCubeTexture = null;\n\t\t\t\t\t\t\t\tlastCubeRenderPass = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trenderTargetMap.clear();\n\t\t}\n\t\trender(device) {\n\t\t\t\tthis.compile();\n\t\t\t\tconst renderPasses = this.renderPasses;\n\t\t\t\tfor(let i = 0; i < renderPasses.length; i++){\n\t\t\t\t\t\trenderPasses[i].render();\n\t\t\t\t}\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.renderPasses = [];\n\t\t\t\tthis.renderTargetMap = new Map();\n\t\t}\n}\n\nclass AreaLightCacheEntry {\n\t\tconstructor(texture0, texture1){\n\t\t\t\tthis.texture0 = texture0;\n\t\t\t\tthis.texture1 = texture1;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.texture0?.destroy();\n\t\t\t\tthis.texture1?.destroy();\n\t\t}\n}\nconst deviceCache = new DeviceCache();\nclass AreaLightLuts {\n\t\tstatic createTexture(device, format, size, postfix = '') {\n\t\t\t\tconst tex = new Texture(device, {\n\t\t\t\t\t\tname: `AreaLightLUT${postfix}`,\n\t\t\t\t\t\twidth: size,\n\t\t\t\t\t\theight: size,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\ttype: TEXTURETYPE_DEFAULT,\n\t\t\t\t\t\tmagFilter: FILTER_LINEAR,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tanisotropy: 1,\n\t\t\t\t\t\tmipmaps: false\n\t\t\t\t});\n\t\t\t\treturn tex;\n\t\t}\n\t\tstatic applyTextures(device, texture1, texture2) {\n\t\t\t\tdeviceCache.remove(device);\n\t\t\t\tdeviceCache.get(device, ()=>{\n\t\t\t\t\t\treturn new AreaLightCacheEntry(texture1, texture1 === texture2 ? null : texture2);\n\t\t\t\t});\n\t\t\t\tdevice.scope.resolve('areaLightsLutTex1').setValue(texture1);\n\t\t\t\tdevice.scope.resolve('areaLightsLutTex2').setValue(texture2);\n\t\t}\n\t\tstatic createPlaceholder(device) {\n\t\t\t\tconst texture = AreaLightLuts.createTexture(device, PIXELFORMAT_RGBA16F, 2, 'placeholder');\n\t\t\t\tconst pixels = texture.lock();\n\t\t\t\tpixels.fill(0);\n\t\t\t\ttexture.unlock();\n\t\t\t\tAreaLightLuts.applyTextures(device, texture, texture);\n\t\t}\n\t\tstatic set(device, ltcMat1, ltcMat2) {\n\t\t\t\tfunction buildTexture(device, data, format) {\n\t\t\t\t\t\tconst texture = AreaLightLuts.createTexture(device, format, 64);\n\t\t\t\t\t\ttexture.lock().set(data);\n\t\t\t\t\t\ttexture.unlock();\n\t\t\t\t\t\treturn texture;\n\t\t\t\t}\n\t\t\t\tfunction convertToHalfFloat(data) {\n\t\t\t\t\t\tconst count = data.length;\n\t\t\t\t\t\tconst ret = new Uint16Array(count);\n\t\t\t\t\t\tconst float2Half = FloatPacking.float2Half;\n\t\t\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\t\t\tret[i] = float2Half(data[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn ret;\n\t\t\t\t}\n\t\t\t\tconst srcData1 = ltcMat1;\n\t\t\t\tconst srcData2 = ltcMat2;\n\t\t\t\tconst data1 = convertToHalfFloat(srcData1);\n\t\t\t\tconst data2 = convertToHalfFloat(srcData2);\n\t\t\t\tconst tex1 = buildTexture(device, data1, PIXELFORMAT_RGBA16F);\n\t\t\t\tconst tex2 = buildTexture(device, data2, PIXELFORMAT_RGBA16F);\n\t\t\t\tAreaLightLuts.applyTextures(device, tex1, tex2);\n\t\t}\n}\n\nconst DEFAULT_LOCALE = 'en-US';\nconst DEFAULT_LOCALE_FALLBACKS = {\n\t\t'en': 'en-US',\n\t\t'es': 'en-ES',\n\t\t'zh': 'zh-CN',\n\t\t'zh-HK': 'zh-TW',\n\t\t'zh-TW': 'zh-HK',\n\t\t'zh-MO': 'zh-HK',\n\t\t'fr': 'fr-FR',\n\t\t'de': 'de-DE',\n\t\t'it': 'it-IT',\n\t\t'ru': 'ru-RU',\n\t\t'ja': 'ja-JP'\n};\n\nconst PLURALS = {};\nfunction definePluralFn(locales, fn) {\n\t\tfor(let i = 0, len = locales.length; i < len; i++){\n\t\t\t\tPLURALS[locales[i]] = fn;\n\t\t}\n}\nfunction getLang(locale) {\n\t\tconst idx = locale.indexOf('-');\n\t\tif (idx !== -1) {\n\t\t\t\treturn locale.substring(0, idx);\n\t\t}\n\t\treturn locale;\n}\nfunction replaceLang(locale, desiredLang) {\n\t\tconst idx = locale.indexOf('-');\n\t\tif (idx !== -1) {\n\t\t\t\treturn desiredLang + locale.substring(idx);\n\t\t}\n\t\treturn desiredLang;\n}\nfunction findAvailableLocale(desiredLocale, availableLocales) {\n\t\tif (availableLocales[desiredLocale]) {\n\t\t\t\treturn desiredLocale;\n\t\t}\n\t\tlet fallback = DEFAULT_LOCALE_FALLBACKS[desiredLocale];\n\t\tif (fallback && availableLocales[fallback]) {\n\t\t\t\treturn fallback;\n\t\t}\n\t\tconst lang = getLang(desiredLocale);\n\t\tfallback = DEFAULT_LOCALE_FALLBACKS[lang];\n\t\tif (availableLocales[fallback]) {\n\t\t\t\treturn fallback;\n\t\t}\n\t\tif (availableLocales[lang]) {\n\t\t\t\treturn lang;\n\t\t}\n\t\treturn DEFAULT_LOCALE;\n}\ndefinePluralFn([\n\t\t'ja',\n\t\t'ko',\n\t\t'th',\n\t\t'vi',\n\t\t'zh',\n\t\t'id'\n], (n)=>{\n\t\treturn 0;\n});\ndefinePluralFn([\n\t\t'fa',\n\t\t'hi'\n], (n)=>{\n\t\tif (n >= 0 && n <= 1) {\n\t\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n});\ndefinePluralFn([\n\t\t'fr',\n\t\t'pt'\n], (n)=>{\n\t\tif (n >= 0 && n < 2) {\n\t\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n});\ndefinePluralFn([\n\t\t'da'\n], (n)=>{\n\t\tif (n === 1 || !Number.isInteger(n) && n >= 0 && n <= 1) {\n\t\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n});\ndefinePluralFn([\n\t\t'de',\n\t\t'en',\n\t\t'it',\n\t\t'el',\n\t\t'es',\n\t\t'tr',\n\t\t'fi',\n\t\t'sv',\n\t\t'nb',\n\t\t'no',\n\t\t'ur'\n], (n)=>{\n\t\tif (n === 1) {\n\t\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n});\ndefinePluralFn([\n\t\t'ru',\n\t\t'uk'\n], (n)=>{\n\t\tif (Number.isInteger(n)) {\n\t\t\t\tconst mod10 = n % 10;\n\t\t\t\tconst mod100 = n % 100;\n\t\t\t\tif (mod10 === 1 && mod100 !== 11) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t} else if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) {\n\t\t\t\t\t\treturn 1;\n\t\t\t\t} else if (mod10 === 0 || mod10 >= 5 && mod10 <= 9 || mod100 >= 11 && mod100 <= 14) {\n\t\t\t\t\t\treturn 2;\n\t\t\t\t}\n\t\t}\n\t\treturn 3;\n});\ndefinePluralFn([\n\t\t'pl'\n], (n)=>{\n\t\tif (Number.isInteger(n)) {\n\t\t\t\tif (n === 1) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tconst mod10 = n % 10;\n\t\t\t\tconst mod100 = n % 100;\n\t\t\t\tif (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) {\n\t\t\t\t\t\treturn 1;\n\t\t\t\t} else if (mod10 >= 0 && mod10 <= 1 || mod10 >= 5 && mod10 <= 9 || mod100 >= 12 && mod100 <= 14) {\n\t\t\t\t\t\treturn 2;\n\t\t\t\t}\n\t\t}\n\t\treturn 3;\n});\ndefinePluralFn([\n\t\t'ar'\n], (n)=>{\n\t\tif (n === 0) {\n\t\t\t\treturn 0;\n\t\t} else if (n === 1) {\n\t\t\t\treturn 1;\n\t\t} else if (n === 2) {\n\t\t\t\treturn 2;\n\t\t}\n\t\tif (Number.isInteger(n)) {\n\t\t\t\tconst mod100 = n % 100;\n\t\t\t\tif (mod100 >= 3 && mod100 <= 10) {\n\t\t\t\t\t\treturn 3;\n\t\t\t\t} else if (mod100 >= 11 && mod100 <= 99) {\n\t\t\t\t\t\treturn 4;\n\t\t\t\t}\n\t\t}\n\t\treturn 5;\n});\nconst DEFAULT_PLURAL_FN = PLURALS[getLang(DEFAULT_LOCALE)];\nfunction getPluralFn(lang) {\n\t\treturn PLURALS[lang] || DEFAULT_PLURAL_FN;\n}\n\nconst ABSOLUTE_URL = new RegExp('^' + '\\\\s*' + '(?:' + '(?:' + '[a-z]+[a-z0-9\\\\-+.]*' + ':' + ')?' + '//' + '|' + 'data:' + '|blob:' + ')', 'i');\n\nclass AssetFile {\n\t\tconstructor(url = '', filename = '', hash = null, size = null, opt = null, contents = null){\n\t\t\t\tthis.url = url;\n\t\t\t\tthis.filename = filename;\n\t\t\t\tthis.hash = hash;\n\t\t\t\tthis.size = size;\n\t\t\t\tthis.opt = opt;\n\t\t\t\tthis.contents = contents;\n\t\t}\n\t\tequals(other) {\n\t\t\t\treturn this.url === other.url && this.filename === other.filename && this.hash === other.hash && this.size === other.size && this.opt === other.opt && this.contents === other.contents;\n\t\t}\n}\n\nlet assetIdCounter = -1;\nconst VARIANT_SUPPORT = {\n\t\tpvr: 'extCompressedTexturePVRTC',\n\t\tdxt: 'extCompressedTextureS3TC',\n\t\tetc2: 'extCompressedTextureETC',\n\t\tetc1: 'extCompressedTextureETC1',\n\t\tbasis: 'canvas'\n};\nconst VARIANT_DEFAULT_PRIORITY = [\n\t\t'pvr',\n\t\t'dxt',\n\t\t'etc2',\n\t\t'etc1',\n\t\t'basis'\n];\nclass Asset extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_LOAD = 'load';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_UNLOAD = 'unload';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_CHANGE = 'change';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PROGRESS = 'progress';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADDLOCALIZED = 'add:localized';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVELOCALIZED = 'remove:localized';\n\t\t}\n\t\tconstructor(name, type, file, data = {}, options = {}){\n\t\t\t\tsuper(), this._file = null, this._i18n = {}, this._preload = false, this._resources = [], this.id = assetIdCounter--, this.loaded = false, this.loading = false, this.options = {}, this.registry = null, this.tags = new Tags(this), this.urlObject = null;\n\t\t\t\tthis._name = name || '';\n\t\t\t\tthis.type = type;\n\t\t\t\tthis._data = data || {};\n\t\t\t\tthis.options = options || {};\n\t\t\t\tif (file) this.file = file;\n\t\t}\n\t\tset name(value) {\n\t\t\t\tif (this._name === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst old = this._name;\n\t\t\t\tthis._name = value;\n\t\t\t\tthis.fire('name', this, this._name, old);\n\t\t}\n\t\tget name() {\n\t\t\t\treturn this._name;\n\t\t}\n\t\tset file(value) {\n\t\t\t\tif (value && value.variants && [\n\t\t\t\t\t\t'texture',\n\t\t\t\t\t\t'textureatlas',\n\t\t\t\t\t\t'bundle'\n\t\t\t\t].indexOf(this.type) !== -1) {\n\t\t\t\t\t\tconst app = this.registry?._loader?._app || getApplication();\n\t\t\t\t\t\tconst device = app?.graphicsDevice;\n\t\t\t\t\t\tif (device) {\n\t\t\t\t\t\t\t\tfor(let i = 0, len = VARIANT_DEFAULT_PRIORITY.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tconst variant = VARIANT_DEFAULT_PRIORITY[i];\n\t\t\t\t\t\t\t\t\t\tif (value.variants[variant] && device[VARIANT_SUPPORT[variant]]) {\n\t\t\t\t\t\t\t\t\t\t\t\tvalue = value.variants[variant];\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (app.enableBundles) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst bundles = app.bundles.listBundlesForAsset(this);\n\t\t\t\t\t\t\t\t\t\t\t\tif (bundles && bundles.find((b)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn b?.file?.variants[variant];\n\t\t\t\t\t\t\t\t\t\t\t\t})) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst oldFile = this._file;\n\t\t\t\tconst newFile = value ? new AssetFile(value.url, value.filename, value.hash, value.size, value.opt, value.contents) : null;\n\t\t\t\tif (!!newFile !== !!oldFile || newFile && !newFile.equals(oldFile)) {\n\t\t\t\t\t\tthis._file = newFile;\n\t\t\t\t\t\tthis.fire('change', this, 'file', newFile, oldFile);\n\t\t\t\t\t\tthis.reload();\n\t\t\t\t}\n\t\t}\n\t\tget file() {\n\t\t\t\treturn this._file;\n\t\t}\n\t\tset data(value) {\n\t\t\t\tconst old = this._data;\n\t\t\t\tthis._data = value;\n\t\t\t\tif (value !== old) {\n\t\t\t\t\t\tthis.fire('change', this, 'data', value, old);\n\t\t\t\t\t\tif (this.loaded) {\n\t\t\t\t\t\t\t\tthis.registry._loader.patch(this, this.registry);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget data() {\n\t\t\t\treturn this._data;\n\t\t}\n\t\tset resource(value) {\n\t\t\t\tconst _old = this._resources[0];\n\t\t\t\tthis._resources[0] = value;\n\t\t\t\tthis.fire('change', this, 'resource', value, _old);\n\t\t}\n\t\tget resource() {\n\t\t\t\treturn this._resources[0];\n\t\t}\n\t\tset resources(value) {\n\t\t\t\tconst _old = this._resources;\n\t\t\t\tthis._resources = value;\n\t\t\t\tthis.fire('change', this, 'resources', value, _old);\n\t\t}\n\t\tget resources() {\n\t\t\t\treturn this._resources;\n\t\t}\n\t\tset preload(value) {\n\t\t\t\tvalue = !!value;\n\t\t\t\tif (this._preload === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._preload = value;\n\t\t\t\tif (this._preload && !this.loaded && !this.loading && this.registry) {\n\t\t\t\t\t\tthis.registry.load(this);\n\t\t\t\t}\n\t\t}\n\t\tget preload() {\n\t\t\t\treturn this._preload;\n\t\t}\n\t\tset loadFaces(value) {\n\t\t\t\tvalue = !!value;\n\t\t\t\tif (!this.hasOwnProperty('_loadFaces') || value !== this._loadFaces) {\n\t\t\t\t\t\tthis._loadFaces = value;\n\t\t\t\t\t\tif (this.loaded) {\n\t\t\t\t\t\t\t\tthis.registry._loader.patch(this, this.registry);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget loadFaces() {\n\t\t\t\treturn this._loadFaces;\n\t\t}\n\t\tgetFileUrl() {\n\t\t\t\tconst file = this.file;\n\t\t\t\tif (!file || !file.url) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tlet url = file.url;\n\t\t\t\tif (this.registry && this.registry.prefix && !ABSOLUTE_URL.test(url)) {\n\t\t\t\t\t\turl = this.registry.prefix + url;\n\t\t\t\t}\n\t\t\t\tif (this.type !== \"script\" && file.hash) {\n\t\t\t\t\t\tconst separator = url.indexOf('?') !== -1 ? '&' : '?';\n\t\t\t\t\t\turl += `${separator}t=${file.hash}`;\n\t\t\t\t}\n\t\t\t\treturn url;\n\t\t}\n\t\tgetAbsoluteUrl(relativePath) {\n\t\t\t\tif (relativePath.startsWith('blob:') || relativePath.startsWith('data:')) {\n\t\t\t\t\t\treturn relativePath;\n\t\t\t\t}\n\t\t\t\tconst base = path.getDirectory(this.file.url);\n\t\t\t\treturn path.join(base, relativePath);\n\t\t}\n\t\tgetLocalizedAssetId(locale) {\n\t\t\t\tlocale = findAvailableLocale(locale, this._i18n);\n\t\t\t\treturn this._i18n[locale] || null;\n\t\t}\n\t\taddLocalizedAssetId(locale, assetId) {\n\t\t\t\tthis._i18n[locale] = assetId;\n\t\t\t\tthis.fire('add:localized', locale, assetId);\n\t\t}\n\t\tremoveLocalizedAssetId(locale) {\n\t\t\t\tconst assetId = this._i18n[locale];\n\t\t\t\tif (assetId) {\n\t\t\t\t\t\tdelete this._i18n[locale];\n\t\t\t\t\t\tthis.fire('remove:localized', locale, assetId);\n\t\t\t\t}\n\t\t}\n\t\tready(callback, scope) {\n\t\t\t\tscope = scope || this;\n\t\t\t\tif (this.loaded) {\n\t\t\t\t\t\tcallback.call(scope, this);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.once('load', (asset)=>{\n\t\t\t\t\t\t\t\tcallback.call(scope, asset);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\treload() {\n\t\t\t\tif (this.loaded) {\n\t\t\t\t\t\tthis.loaded = false;\n\t\t\t\t\t\tthis.registry.load(this);\n\t\t\t\t}\n\t\t}\n\t\tunload() {\n\t\t\t\tif (!this.loaded && this._resources.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.fire('unload', this);\n\t\t\t\tthis.registry.fire(`unload:${this.id}`, this);\n\t\t\t\tconst old = this._resources;\n\t\t\t\tif (this.urlObject) {\n\t\t\t\t\t\tURL.revokeObjectURL(this.urlObject);\n\t\t\t\t\t\tthis.urlObject = null;\n\t\t\t\t}\n\t\t\t\tthis.resources = [];\n\t\t\t\tthis.loaded = false;\n\t\t\t\tif (this.file) {\n\t\t\t\t\t\tthis.registry._loader.clearCache(this.getFileUrl(), this.type);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < old.length; ++i){\n\t\t\t\t\t\tconst resource = old[i];\n\t\t\t\t\t\tif (resource && resource.destroy) {\n\t\t\t\t\t\t\t\tresource.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tstatic fetchArrayBuffer(loadUrl, callback, asset, maxRetries = 0) {\n\t\t\t\tif (asset?.file?.contents) {\n\t\t\t\t\t\tsetTimeout(()=>{\n\t\t\t\t\t\t\t\tcallback(null, asset.file.contents);\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\thttp.get(loadUrl, {\n\t\t\t\t\t\t\t\tcache: true,\n\t\t\t\t\t\t\t\tresponseType: 'arraybuffer',\n\t\t\t\t\t\t\t\tretry: maxRetries > 0,\n\t\t\t\t\t\t\t\tmaxRetries: maxRetries,\n\t\t\t\t\t\t\t\tprogress: asset\n\t\t\t\t\t\t}, callback);\n\t\t\t\t}\n\t\t}\n}\n\nclass TagsCache {\n\t\tconstructor(key = null){\n\t\t\t\tthis._index = {};\n\t\t\t\tthis._key = key;\n\t\t}\n\t\taddItem(item) {\n\t\t\t\tconst tags = item.tags._list;\n\t\t\t\tfor (const tag of tags){\n\t\t\t\t\t\tthis.add(tag, item);\n\t\t\t\t}\n\t\t}\n\t\tremoveItem(item) {\n\t\t\t\tconst tags = item.tags._list;\n\t\t\t\tfor (const tag of tags){\n\t\t\t\t\t\tthis.remove(tag, item);\n\t\t\t\t}\n\t\t}\n\t\tadd(tag, item) {\n\t\t\t\tif (this._index[tag] && this._index[tag].list.indexOf(item) !== -1) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._index[tag]) {\n\t\t\t\t\t\tthis._index[tag] = {\n\t\t\t\t\t\t\t\tlist: []\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (this._key) {\n\t\t\t\t\t\t\t\tthis._index[tag].keys = {};\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._index[tag].list.push(item);\n\t\t\t\tif (this._key) {\n\t\t\t\t\t\tthis._index[tag].keys[item[this._key]] = item;\n\t\t\t\t}\n\t\t}\n\t\tremove(tag, item) {\n\t\t\t\tif (!this._index[tag]) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._key) {\n\t\t\t\t\t\tif (!this._index[tag].keys[item[this._key]]) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst ind = this._index[tag].list.indexOf(item);\n\t\t\t\tif (ind === -1) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._index[tag].list.splice(ind, 1);\n\t\t\t\tif (this._key) {\n\t\t\t\t\t\tdelete this._index[tag].keys[item[this._key]];\n\t\t\t\t}\n\t\t\t\tif (this._index[tag].list.length === 0) {\n\t\t\t\t\t\tdelete this._index[tag];\n\t\t\t\t}\n\t\t}\n\t\tfind(args) {\n\t\t\t\tconst index = {};\n\t\t\t\tconst items = [];\n\t\t\t\tlet item, tag, tags, tagsRest, missingIndex;\n\t\t\t\tconst sort = (a, b)=>{\n\t\t\t\t\t\treturn this._index[a].list.length - this._index[b].list.length;\n\t\t\t\t};\n\t\t\t\tfor(let i = 0; i < args.length; i++){\n\t\t\t\t\t\ttag = args[i];\n\t\t\t\t\t\tif (tag instanceof Array) {\n\t\t\t\t\t\t\t\tif (tag.length === 0) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (tag.length === 1) {\n\t\t\t\t\t\t\t\t\t\ttag = tag[0];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmissingIndex = false;\n\t\t\t\t\t\t\t\t\t\tfor(let t = 0; t < tag.length; t++){\n\t\t\t\t\t\t\t\t\t\t\t\tif (!this._index[tag[t]]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmissingIndex = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (missingIndex) {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\ttags = tag.slice(0).sort(sort);\n\t\t\t\t\t\t\t\t\t\ttagsRest = tags.slice(1);\n\t\t\t\t\t\t\t\t\t\tif (tagsRest.length === 1) {\n\t\t\t\t\t\t\t\t\t\t\t\ttagsRest = tagsRest[0];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tfor(let n = 0; n < this._index[tags[0]].list.length; n++){\n\t\t\t\t\t\t\t\t\t\t\t\titem = this._index[tags[0]].list[n];\n\t\t\t\t\t\t\t\t\t\t\t\tif ((this._key ? !index[item[this._key]] : items.indexOf(item) === -1) && item.tags.has(tagsRest)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this._key) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tindex[item[this._key]] = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titems.push(item);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (tag && typeof tag === 'string' && this._index[tag]) {\n\t\t\t\t\t\t\t\tfor(let n = 0; n < this._index[tag].list.length; n++){\n\t\t\t\t\t\t\t\t\t\titem = this._index[tag].list[n];\n\t\t\t\t\t\t\t\t\t\tif (this._key) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!index[item[this._key]]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tindex[item[this._key]] = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titems.push(item);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else if (items.indexOf(item) === -1) {\n\t\t\t\t\t\t\t\t\t\t\t\titems.push(item);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn items;\n\t\t}\n}\n\nclass AssetRegistry extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_LOAD = 'load';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tconstructor(loader){\n\t\t\t\tsuper(), this._assets = new Set(), this._idToAsset = new Map(), this._urlToAsset = new Map(), this._nameToAsset = new Map(), this._tags = new TagsCache('id'), this.prefix = null, this.bundles = null;\n\t\t\t\tthis._loader = loader;\n\t\t}\n\t\tlist(filters = {}) {\n\t\t\t\tconst assets = Array.from(this._assets);\n\t\t\t\tif (filters.preload !== undefined) {\n\t\t\t\t\t\treturn assets.filter((asset)=>asset.preload === filters.preload);\n\t\t\t\t}\n\t\t\t\treturn assets;\n\t\t}\n\t\tadd(asset) {\n\t\t\t\tif (this._assets.has(asset)) return;\n\t\t\t\tthis._assets.add(asset);\n\t\t\t\tthis._idToAsset.set(asset.id, asset);\n\t\t\t\tif (asset.file?.url) {\n\t\t\t\t\t\tthis._urlToAsset.set(asset.file.url, asset);\n\t\t\t\t}\n\t\t\t\tif (!this._nameToAsset.has(asset.name)) {\n\t\t\t\t\t\tthis._nameToAsset.set(asset.name, new Set());\n\t\t\t\t}\n\t\t\t\tthis._nameToAsset.get(asset.name).add(asset);\n\t\t\t\tasset.on('name', this._onNameChange, this);\n\t\t\t\tasset.registry = this;\n\t\t\t\tthis._tags.addItem(asset);\n\t\t\t\tasset.tags.on('add', this._onTagAdd, this);\n\t\t\t\tasset.tags.on('remove', this._onTagRemove, this);\n\t\t\t\tthis.fire('add', asset);\n\t\t\t\tthis.fire(`add:${asset.id}`, asset);\n\t\t\t\tif (asset.file?.url) {\n\t\t\t\t\t\tthis.fire(`add:url:${asset.file.url}`, asset);\n\t\t\t\t}\n\t\t\t\tif (asset.preload) {\n\t\t\t\t\t\tthis.load(asset);\n\t\t\t\t}\n\t\t}\n\t\tremove(asset) {\n\t\t\t\tif (!this._assets.has(asset)) return false;\n\t\t\t\tthis._assets.delete(asset);\n\t\t\t\tthis._idToAsset.delete(asset.id);\n\t\t\t\tif (asset.file?.url) {\n\t\t\t\t\t\tthis._urlToAsset.delete(asset.file.url);\n\t\t\t\t}\n\t\t\t\tasset.off('name', this._onNameChange, this);\n\t\t\t\tif (this._nameToAsset.has(asset.name)) {\n\t\t\t\t\t\tconst items = this._nameToAsset.get(asset.name);\n\t\t\t\t\t\titems.delete(asset);\n\t\t\t\t\t\tif (items.size === 0) {\n\t\t\t\t\t\t\t\tthis._nameToAsset.delete(asset.name);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._tags.removeItem(asset);\n\t\t\t\tasset.tags.off('add', this._onTagAdd, this);\n\t\t\t\tasset.tags.off('remove', this._onTagRemove, this);\n\t\t\t\tasset.fire('remove', asset);\n\t\t\t\tthis.fire('remove', asset);\n\t\t\t\tthis.fire(`remove:${asset.id}`, asset);\n\t\t\t\tif (asset.file?.url) {\n\t\t\t\t\t\tthis.fire(`remove:url:${asset.file.url}`, asset);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tget(id) {\n\t\t\t\treturn this._idToAsset.get(Number(id));\n\t\t}\n\t\tgetByUrl(url) {\n\t\t\t\treturn this._urlToAsset.get(url);\n\t\t}\n\t\tload(asset, options) {\n\t\t\t\tif ((asset.loading || asset.loaded) && !options?.force) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst file = asset.file;\n\t\t\t\tconst _fireLoad = ()=>{\n\t\t\t\t\t\tthis.fire('load', asset);\n\t\t\t\t\t\tthis.fire(`load:${asset.id}`, asset);\n\t\t\t\t\t\tif (file && file.url) {\n\t\t\t\t\t\t\t\tthis.fire(`load:url:${file.url}`, asset);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tasset.fire('load', asset);\n\t\t\t\t};\n\t\t\t\tconst _opened = (resource)=>{\n\t\t\t\t\t\tif (resource instanceof Array) {\n\t\t\t\t\t\t\t\tasset.resources = resource;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tasset.resource = resource;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._loader.patch(asset, this);\n\t\t\t\t\t\tif (asset.type === 'bundle') {\n\t\t\t\t\t\t\t\tconst assetIds = asset.data.assets;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < assetIds.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst assetInBundle = this._idToAsset.get(assetIds[i]);\n\t\t\t\t\t\t\t\t\t\tif (assetInBundle && !assetInBundle.loaded) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.load(assetInBundle, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tforce: true\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (asset.resource.loaded) {\n\t\t\t\t\t\t\t\t\t\t_fireLoad();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.fire('load:start', asset);\n\t\t\t\t\t\t\t\t\t\tthis.fire(`load:start:${asset.id}`, asset);\n\t\t\t\t\t\t\t\t\t\tif (file && file.url) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.fire(`load:start:url:${file.url}`, asset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tasset.fire('load:start', asset);\n\t\t\t\t\t\t\t\t\t\tasset.resource.on('load', _fireLoad);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t_fireLoad();\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst _loaded = (err, resource, extra)=>{\n\t\t\t\t\t\tasset.loaded = true;\n\t\t\t\t\t\tasset.loading = false;\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tthis.fire('error', err, asset);\n\t\t\t\t\t\t\t\tthis.fire(`error:${asset.id}`, err, asset);\n\t\t\t\t\t\t\t\tasset.fire('error', err, asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (asset.type === \"script\") {\n\t\t\t\t\t\t\t\t\t\tconst handler = this._loader.getHandler(\"script\");\n\t\t\t\t\t\t\t\t\t\tif (handler._cache[asset.id] && handler._cache[asset.id].parentNode === document.head) {\n\t\t\t\t\t\t\t\t\t\t\t\tdocument.head.removeChild(handler._cache[asset.id]);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\thandler._cache[asset.id] = extra;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t_opened(resource);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tif (file || asset.type === 'cubemap') {\n\t\t\t\t\t\tthis.fire('load:start', asset);\n\t\t\t\t\t\tthis.fire(`load:${asset.id}:start`, asset);\n\t\t\t\t\t\tasset.loading = true;\n\t\t\t\t\t\tconst fileUrl = asset.getFileUrl();\n\t\t\t\t\t\tif (asset.type === 'bundle') {\n\t\t\t\t\t\t\t\tconst assetIds = asset.data.assets;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < assetIds.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst assetInBundle = this._idToAsset.get(assetIds[i]);\n\t\t\t\t\t\t\t\t\t\tif (!assetInBundle) {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (assetInBundle.loaded || assetInBundle.resource || assetInBundle.loading) {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tassetInBundle.loading = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._loader.load(fileUrl, asset.type, _loaded, asset, options);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst resource = this._loader.open(asset.type, asset.data);\n\t\t\t\t\t\tasset.loaded = true;\n\t\t\t\t\t\t_opened(resource);\n\t\t\t\t}\n\t\t}\n\t\tloadFromUrl(url, type, callback) {\n\t\t\t\tthis.loadFromUrlAndFilename(url, null, type, callback);\n\t\t}\n\t\tloadFromUrlAndFilename(url, filename, type, callback) {\n\t\t\t\tconst name = path.getBasename(filename || url);\n\t\t\t\tconst file = {\n\t\t\t\t\t\tfilename: filename || name,\n\t\t\t\t\t\turl: url\n\t\t\t\t};\n\t\t\t\tlet asset = this.getByUrl(url);\n\t\t\t\tif (!asset) {\n\t\t\t\t\t\tasset = new Asset(name, type, file);\n\t\t\t\t\t\tthis.add(asset);\n\t\t\t\t} else if (asset.loaded) {\n\t\t\t\t\t\tcallback(asset.loadFromUrlError || null, asset);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst startLoad = (asset)=>{\n\t\t\t\t\t\tasset.once('load', (loadedAsset)=>{\n\t\t\t\t\t\t\t\tif (type === 'material') {\n\t\t\t\t\t\t\t\t\t\tthis._loadTextures(loadedAsset, (err, textures)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tcallback(err, loadedAsset);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcallback(null, loadedAsset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tasset.once('error', (err)=>{\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tthis.loadFromUrlError = err;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcallback(err, asset);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.load(asset);\n\t\t\t\t};\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tcallback(null, asset);\n\t\t\t\t} else if (type === 'model') {\n\t\t\t\t\t\tthis._loadModel(asset, startLoad);\n\t\t\t\t} else {\n\t\t\t\t\t\tstartLoad(asset);\n\t\t\t\t}\n\t\t}\n\t\t_loadModel(modelAsset, continuation) {\n\t\t\t\tconst url = modelAsset.getFileUrl();\n\t\t\t\tconst ext = path.getExtension(url);\n\t\t\t\tif (ext === '.json' || ext === '.glb') {\n\t\t\t\t\t\tconst dir = path.getDirectory(url);\n\t\t\t\t\t\tconst basename = path.getBasename(url);\n\t\t\t\t\t\tconst mappingUrl = path.join(dir, basename.replace(ext, '.mapping.json'));\n\t\t\t\t\t\tthis._loader.load(mappingUrl, 'json', (err, data)=>{\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tmodelAsset.data = {\n\t\t\t\t\t\t\t\t\t\t\t\tmapping: []\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcontinuation(modelAsset);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._loadMaterials(modelAsset, data, (e, materials)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tmodelAsset.data = data;\n\t\t\t\t\t\t\t\t\t\t\t\tcontinuation(modelAsset);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tcontinuation(modelAsset);\n\t\t\t\t}\n\t\t}\n\t\t_loadMaterials(modelAsset, mapping, callback) {\n\t\t\t\tconst materials = [];\n\t\t\t\tlet count = 0;\n\t\t\t\tconst onMaterialLoaded = (err, materialAsset)=>{\n\t\t\t\t\t\tthis._loadTextures(materialAsset, (err, textures)=>{\n\t\t\t\t\t\t\t\tmaterials.push(materialAsset);\n\t\t\t\t\t\t\t\tif (materials.length === count) {\n\t\t\t\t\t\t\t\t\t\tcallback(null, materials);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tfor(let i = 0; i < mapping.mapping.length; i++){\n\t\t\t\t\t\tconst path = mapping.mapping[i].path;\n\t\t\t\t\t\tif (path) {\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\tconst url = modelAsset.getAbsoluteUrl(path);\n\t\t\t\t\t\t\t\tthis.loadFromUrl(url, 'material', onMaterialLoaded);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (count === 0) {\n\t\t\t\t\t\tcallback(null, materials);\n\t\t\t\t}\n\t\t}\n\t\t_loadTextures(materialAsset, callback) {\n\t\t\t\tconst textures = [];\n\t\t\t\tlet count = 0;\n\t\t\t\tconst data = materialAsset.data;\n\t\t\t\tif (data.mappingFormat !== 'path') {\n\t\t\t\t\t\tcallback(null, textures);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst onTextureLoaded = (err, texture)=>{\n\t\t\t\t\t\tif (err) console.error(err);\n\t\t\t\t\t\ttextures.push(texture);\n\t\t\t\t\t\tif (textures.length === count) {\n\t\t\t\t\t\t\t\tcallback(null, textures);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst texParams = standardMaterialTextureParameters;\n\t\t\t\tfor(let i = 0; i < texParams.length; i++){\n\t\t\t\t\t\tconst path = data[texParams[i]];\n\t\t\t\t\t\tif (path && typeof path === 'string') {\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\tconst url = materialAsset.getAbsoluteUrl(path);\n\t\t\t\t\t\t\t\tthis.loadFromUrl(url, 'texture', onTextureLoaded);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (count === 0) {\n\t\t\t\t\t\tcallback(null, textures);\n\t\t\t\t}\n\t\t}\n\t\t_onTagAdd(tag, asset) {\n\t\t\t\tthis._tags.add(tag, asset);\n\t\t}\n\t\t_onTagRemove(tag, asset) {\n\t\t\t\tthis._tags.remove(tag, asset);\n\t\t}\n\t\t_onNameChange(asset, name, nameOld) {\n\t\t\t\tif (this._nameToAsset.has(nameOld)) {\n\t\t\t\t\t\tconst items = this._nameToAsset.get(nameOld);\n\t\t\t\t\t\titems.delete(asset);\n\t\t\t\t\t\tif (items.size === 0) {\n\t\t\t\t\t\t\t\tthis._nameToAsset.delete(nameOld);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!this._nameToAsset.has(asset.name)) {\n\t\t\t\t\t\tthis._nameToAsset.set(asset.name, new Set());\n\t\t\t\t}\n\t\t\t\tthis._nameToAsset.get(asset.name).add(asset);\n\t\t}\n\t\tfindByTag(...query) {\n\t\t\t\treturn this._tags.find(query);\n\t\t}\n\t\tfilter(callback) {\n\t\t\t\treturn Array.from(this._assets).filter((asset)=>callback(asset));\n\t\t}\n\t\tfind(name, type) {\n\t\t\t\tconst items = this._nameToAsset.get(name);\n\t\t\t\tif (!items) return null;\n\t\t\t\tfor (const asset of items){\n\t\t\t\t\t\tif (!type || asset.type === type) {\n\t\t\t\t\t\t\t\treturn asset;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tfindAll(name, type) {\n\t\t\t\tconst items = this._nameToAsset.get(name);\n\t\t\t\tif (!items) return [];\n\t\t\t\tconst results = Array.from(items);\n\t\t\t\tif (!type) return results;\n\t\t\t\treturn results.filter((asset)=>asset.type === type);\n\t\t}\n}\n\nclass BundleRegistry {\n\t\tconstructor(assets){\n\t\t\t\tthis._idToBundle = new Map();\n\t\t\t\tthis._assetToBundles = new Map();\n\t\t\t\tthis._urlsToBundles = new Map();\n\t\t\t\tthis._fileRequests = new Map();\n\t\t\t\tthis._assets = assets;\n\t\t\t\tthis._assets.bundles = this;\n\t\t\t\tthis._assets.on('add', this._onAssetAdd, this);\n\t\t\t\tthis._assets.on('remove', this._onAssetRemove, this);\n\t\t}\n\t\t_onAssetAdd(asset) {\n\t\t\t\tif (asset.type === 'bundle') {\n\t\t\t\t\t\tthis._idToBundle.set(asset.id, asset);\n\t\t\t\t\t\tthis._assets.on(`load:start:${asset.id}`, this._onBundleLoadStart, this);\n\t\t\t\t\t\tthis._assets.on(`load:${asset.id}`, this._onBundleLoad, this);\n\t\t\t\t\t\tthis._assets.on(`error:${asset.id}`, this._onBundleError, this);\n\t\t\t\t\t\tconst assetIds = asset.data.assets;\n\t\t\t\t\t\tfor(let i = 0; i < assetIds.length; i++){\n\t\t\t\t\t\t\t\tthis._indexAssetInBundle(assetIds[i], asset);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this._assetToBundles.has(asset.id)) {\n\t\t\t\t\t\t\t\tthis._indexAssetFileUrls(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_unbindAssetEvents(id) {\n\t\t\t\tthis._assets.off(`load:start:${id}`, this._onBundleLoadStart, this);\n\t\t\t\tthis._assets.off(`load:${id}`, this._onBundleLoad, this);\n\t\t\t\tthis._assets.off(`error:${id}`, this._onBundleError, this);\n\t\t}\n\t\t_indexAssetInBundle(id, bundle) {\n\t\t\t\tlet bundles = this._assetToBundles.get(id);\n\t\t\t\tif (!bundles) {\n\t\t\t\t\t\tbundles = new Set();\n\t\t\t\t\t\tthis._assetToBundles.set(id, bundles);\n\t\t\t\t}\n\t\t\t\tbundles.add(bundle);\n\t\t\t\tconst asset = this._assets.get(id);\n\t\t\t\tif (asset) this._indexAssetFileUrls(asset);\n\t\t}\n\t\t_indexAssetFileUrls(asset) {\n\t\t\t\tconst urls = this._getAssetFileUrls(asset);\n\t\t\t\tif (!urls) return;\n\t\t\t\tfor(let i = 0; i < urls.length; i++){\n\t\t\t\t\t\tconst bundles = this._assetToBundles.get(asset.id);\n\t\t\t\t\t\tif (!bundles) continue;\n\t\t\t\t\t\tthis._urlsToBundles.set(urls[i], bundles);\n\t\t\t\t}\n\t\t}\n\t\t_getAssetFileUrls(asset) {\n\t\t\t\tlet url = asset.getFileUrl();\n\t\t\t\tif (!url) return null;\n\t\t\t\turl = url.split('?')[0];\n\t\t\t\tconst urls = [\n\t\t\t\t\t\turl\n\t\t\t\t];\n\t\t\t\tif (asset.type === 'font') {\n\t\t\t\t\t\tconst numFiles = asset.data.info.maps.length;\n\t\t\t\t\t\tfor(let i = 1; i < numFiles; i++){\n\t\t\t\t\t\t\t\turls.push(url.replace('.png', `${i}.png`));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn urls;\n\t\t}\n\t\t_onAssetRemove(asset) {\n\t\t\t\tif (asset.type === 'bundle') {\n\t\t\t\t\t\tthis._idToBundle.delete(asset.id);\n\t\t\t\t\t\tthis._unbindAssetEvents(asset.id);\n\t\t\t\t\t\tconst assetIds = asset.data.assets;\n\t\t\t\t\t\tfor(let i = 0; i < assetIds.length; i++){\n\t\t\t\t\t\t\t\tconst bundles = this._assetToBundles.get(assetIds[i]);\n\t\t\t\t\t\t\t\tif (!bundles) continue;\n\t\t\t\t\t\t\t\tbundles.delete(asset);\n\t\t\t\t\t\t\t\tif (bundles.size === 0) {\n\t\t\t\t\t\t\t\t\t\tthis._assetToBundles.delete(assetIds[i]);\n\t\t\t\t\t\t\t\t\t\tfor (const [url, otherBundles] of this._urlsToBundles){\n\t\t\t\t\t\t\t\t\t\t\t\tif (otherBundles !== bundles) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tthis._urlsToBundles.delete(url);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._onBundleError(`Bundle ${asset.id} was removed`);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst bundles = this._assetToBundles.get(asset.id);\n\t\t\t\t\t\tif (!bundles) return;\n\t\t\t\t\t\tthis._assetToBundles.delete(asset.id);\n\t\t\t\t\t\tconst urls = this._getAssetFileUrls(asset);\n\t\t\t\t\t\tif (!urls) return;\n\t\t\t\t\t\tfor(let i = 0; i < urls.length; i++){\n\t\t\t\t\t\t\t\tthis._urlsToBundles.delete(urls[i]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onBundleLoadStart(asset) {\n\t\t\t\tasset.resource.on('add', (url, data)=>{\n\t\t\t\t\t\tconst callbacks = this._fileRequests.get(url);\n\t\t\t\t\t\tif (!callbacks) return;\n\t\t\t\t\t\tfor(let i = 0; i < callbacks.length; i++){\n\t\t\t\t\t\t\t\tcallbacks[i](null, data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._fileRequests.delete(url);\n\t\t\t\t});\n\t\t}\n\t\t_onBundleLoad(asset) {\n\t\t\t\tif (!asset.resource) {\n\t\t\t\t\t\tthis._onBundleError(`Bundle ${asset.id} failed to load`);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._fileRequests) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor (const [url, requests] of this._fileRequests){\n\t\t\t\t\t\tconst bundles = this._urlsToBundles.get(url);\n\t\t\t\t\t\tif (!bundles || !bundles.has(asset)) continue;\n\t\t\t\t\t\tconst decodedUrl = decodeURIComponent(url);\n\t\t\t\t\t\tlet err, data;\n\t\t\t\t\t\tif (asset.resource.has(decodedUrl)) {\n\t\t\t\t\t\t\t\tdata = asset.resource.get(decodedUrl);\n\t\t\t\t\t\t} else if (asset.resource.loaded) {\n\t\t\t\t\t\t\t\terr = `Bundle ${asset.id} does not contain URL ${url}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < requests.length; i++){\n\t\t\t\t\t\t\t\trequests[i](err, err || data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._fileRequests.delete(url);\n\t\t\t\t}\n\t\t}\n\t\t_onBundleError(err) {\n\t\t\t\tfor (const [url, requests] of this._fileRequests){\n\t\t\t\t\t\tconst bundle = this._findLoadedOrLoadingBundleForUrl(url);\n\t\t\t\t\t\tif (!bundle) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < requests.length; i++){\n\t\t\t\t\t\t\t\t\t\trequests[i](err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._fileRequests.delete(url);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_findLoadedOrLoadingBundleForUrl(url) {\n\t\t\t\tconst bundles = this._urlsToBundles.get(url);\n\t\t\t\tif (!bundles) return null;\n\t\t\t\tlet candidate = null;\n\t\t\t\tfor (const bundle of bundles){\n\t\t\t\t\t\tif (bundle.loaded && bundle.resource) {\n\t\t\t\t\t\t\t\treturn bundle;\n\t\t\t\t\t\t} else if (bundle.loading) {\n\t\t\t\t\t\t\t\tcandidate = bundle;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn candidate;\n\t\t}\n\t\tlistBundlesForAsset(asset) {\n\t\t\t\tconst bundles = this._assetToBundles.get(asset.id);\n\t\t\t\tif (bundles) return Array.from(bundles);\n\t\t\t\treturn null;\n\t\t}\n\t\tlist() {\n\t\t\t\treturn Array.from(this._idToBundle.values());\n\t\t}\n\t\thasUrl(url) {\n\t\t\t\treturn this._urlsToBundles.has(url);\n\t\t}\n\t\turlIsLoadedOrLoading(url) {\n\t\t\t\treturn !!this._findLoadedOrLoadingBundleForUrl(url);\n\t\t}\n\t\tloadUrl(url, callback) {\n\t\t\t\tconst bundle = this._findLoadedOrLoadingBundleForUrl(url);\n\t\t\t\tif (!bundle) {\n\t\t\t\t\t\tcallback(`URL ${url} not found in any bundles`);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (bundle.loaded) {\n\t\t\t\t\t\tconst decodedUrl = decodeURIComponent(url);\n\t\t\t\t\t\tif (bundle.resource.has(decodedUrl)) {\n\t\t\t\t\t\t\t\tcallback(null, bundle.resource.get(decodedUrl));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} else if (bundle.resource.loaded) {\n\t\t\t\t\t\t\t\tcallback(`Bundle ${bundle.id} does not contain URL ${url}`);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet callbacks = this._fileRequests.get(url);\n\t\t\t\tif (!callbacks) {\n\t\t\t\t\t\tcallbacks = [];\n\t\t\t\t\t\tthis._fileRequests.set(url, callbacks);\n\t\t\t\t}\n\t\t\t\tcallbacks.push(callback);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._assets.off('add', this._onAssetAdd, this);\n\t\t\t\tthis._assets.off('remove', this._onAssetRemove, this);\n\t\t\t\tfor (const id of this._idToBundle.keys()){\n\t\t\t\t\t\tthis._unbindAssetEvents(id);\n\t\t\t\t}\n\t\t\t\tthis._assets = null;\n\t\t\t\tthis._idToBundle.clear();\n\t\t\t\tthis._idToBundle = null;\n\t\t\t\tthis._assetToBundles.clear();\n\t\t\t\tthis._assetToBundles = null;\n\t\t\t\tthis._urlsToBundles.clear();\n\t\t\t\tthis._urlsToBundles = null;\n\t\t\t\tthis._fileRequests.clear();\n\t\t\t\tthis._fileRequests = null;\n\t\t}\n}\n\nclass ComponentSystemRegistry extends EventHandler {\n\t\tconstructor(){\n\t\t\t\tsuper();\n\t\t\t\tthis.list = [];\n\t\t}\n\t\tadd(system) {\n\t\t\t\tconst id = system.id;\n\t\t\t\tif (this[id]) {\n\t\t\t\t\t\tthrow new Error(`ComponentSystem name '${id}' already registered or not allowed`);\n\t\t\t\t}\n\t\t\t\tthis[id] = system;\n\t\t\t\tthis.list.push(system);\n\t\t}\n\t\tremove(system) {\n\t\t\t\tconst id = system.id;\n\t\t\t\tif (!this[id]) {\n\t\t\t\t\t\tthrow new Error(`No ComponentSystem named '${id}' registered`);\n\t\t\t\t}\n\t\t\t\tdelete this[id];\n\t\t\t\tconst index = this.list.indexOf(this[id]);\n\t\t\t\tif (index !== -1) {\n\t\t\t\t\t\tthis.list.splice(index, 1);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.off();\n\t\t\t\tfor(let i = 0; i < this.list.length; i++){\n\t\t\t\t\t\tthis.list[i].destroy();\n\t\t\t\t}\n\t\t}\n}\n\nclass Bundle extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_LOAD = 'load';\n\t\t}\n\t\taddFile(url, data) {\n\t\t\t\tif (this._index.has(url)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._index.set(url, data);\n\t\t\t\tthis.fire('add', url, data);\n\t\t}\n\t\thas(url) {\n\t\t\t\treturn this._index.has(url);\n\t\t}\n\t\tget(url) {\n\t\t\t\treturn this._index.get(url) || null;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._index.clear();\n\t\t}\n\t\tset loaded(value) {\n\t\t\t\tif (!value || this._loaded) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._loaded = true;\n\t\t\t\tthis.fire('load');\n\t\t}\n\t\tget loaded() {\n\t\t\t\treturn this._loaded;\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._index = new Map(), this._loaded = false;\n\t\t}\n}\n\nclass Untar extends EventHandler {\n\t\tconstructor(fetchPromise, assetsPrefix = ''){\n\t\t\t\tsuper(), this.headerSize = 512, this.paddingSize = 512, this.bytesRead = 0, this.bytesReceived = 0, this.headerRead = false, this.reader = null, this.data = new Uint8Array(0), this.decoder = null, this.prefix = '', this.fileName = '', this.fileSize = 0, this.fileType = '', this.ustarFormat = '';\n\t\t\t\tthis.prefix = assetsPrefix || '';\n\t\t\t\tthis.reader = fetchPromise.body.getReader();\n\t\t\t\tthis.reader.read().then((res)=>{\n\t\t\t\t\t\tthis.pump(res.done, res.value);\n\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\tthis.fire('error', err);\n\t\t\t\t});\n\t\t}\n\t\tpump(done, value) {\n\t\t\t\tif (done) {\n\t\t\t\t\t\tthis.fire('done');\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tthis.bytesReceived += value.byteLength;\n\t\t\t\tconst data = new Uint8Array(this.data.length + value.length);\n\t\t\t\tdata.set(this.data);\n\t\t\t\tdata.set(value, this.data.length);\n\t\t\t\tthis.data = data;\n\t\t\t\twhile(this.readFile());\n\t\t\t\treturn this.reader.read().then((res)=>{\n\t\t\t\t\t\tthis.pump(res.done, res.value);\n\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\tthis.fire('error', err);\n\t\t\t\t});\n\t\t}\n\t\treadFile() {\n\t\t\t\tif (!this.headerRead && this.bytesReceived > this.bytesRead + this.headerSize) {\n\t\t\t\t\t\tthis.headerRead = true;\n\t\t\t\t\t\tconst view = new DataView(this.data.buffer, this.bytesRead, this.headerSize);\n\t\t\t\t\t\tthis.decoder ??= new TextDecoder('windows-1252');\n\t\t\t\t\t\tconst headers = this.decoder.decode(view);\n\t\t\t\t\t\tthis.fileName = headers.substring(0, 100).replace(/\\0/g, '');\n\t\t\t\t\t\tthis.fileSize = parseInt(headers.substring(124, 136), 8);\n\t\t\t\t\t\tthis.fileType = headers.substring(156, 157);\n\t\t\t\t\t\tthis.ustarFormat = headers.substring(257, 263);\n\t\t\t\t\t\tif (this.ustarFormat.indexOf('ustar') !== -1) {\n\t\t\t\t\t\t\t\tconst prefix = headers.substring(345, 500).replace(/\\0/g, '');\n\t\t\t\t\t\t\t\tif (prefix.length > 0) {\n\t\t\t\t\t\t\t\t\t\tthis.fileName = prefix.trim() + this.fileName.trim();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.bytesRead += 512;\n\t\t\t\t}\n\t\t\t\tif (this.headerRead) {\n\t\t\t\t\t\tif (this.bytesReceived < this.bytesRead + this.fileSize) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.fileType === '' || this.fileType === '0') {\n\t\t\t\t\t\t\t\tconst dataView = new DataView(this.data.buffer, this.bytesRead, this.fileSize);\n\t\t\t\t\t\t\t\tconst file = {\n\t\t\t\t\t\t\t\t\t\tname: this.prefix + this.fileName,\n\t\t\t\t\t\t\t\t\t\tsize: this.fileSize,\n\t\t\t\t\t\t\t\t\t\tdata: dataView\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tthis.fire('file', file);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.bytesRead += this.fileSize;\n\t\t\t\t\t\tthis.headerRead = false;\n\t\t\t\t\t\tconst bytesRemained = this.bytesRead % this.paddingSize;\n\t\t\t\t\t\tif (bytesRemained !== 0) {\n\t\t\t\t\t\t\t\tthis.bytesRead += this.paddingSize - bytesRemained;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n}\n\nclass ResourceHandler {\n\t\tconstructor(app, handlerType){\n\t\t\t\tthis.handlerType = '';\n\t\t\t\tthis._maxRetries = 0;\n\t\t\t\tthis._app = app;\n\t\t\t\tthis.handlerType = handlerType;\n\t\t}\n\t\tset maxRetries(value) {\n\t\t\t\tthis._maxRetries = value;\n\t\t}\n\t\tget maxRetries() {\n\t\t\t\treturn this._maxRetries;\n\t\t}\n\t\tload(url, callback, asset) {}\n\t\topen(url, data, asset) {\n\t\t\t\treturn data;\n\t\t}\n\t\tpatch(asset, assets) {}\n}\n\nclass BundleHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'bundle');\n\t\t\t\tthis._assets = app.assets;\n\t\t}\n\t\t_fetchRetries(url, options, retries = 0) {\n\t\t\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\t\t\tconst tryFetch = ()=>{\n\t\t\t\t\t\t\t\tfetch(url, options).then(resolve).catch((err)=>{\n\t\t\t\t\t\t\t\t\t\tretries++;\n\t\t\t\t\t\t\t\t\t\tif (retries < this.maxRetries) {\n\t\t\t\t\t\t\t\t\t\t\t\ttryFetch();\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t};\n\t\t\t\t\t\ttryFetch();\n\t\t\t\t});\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tthis._fetchRetries(url.load, {\n\t\t\t\t\t\tmode: 'cors'\n\t\t\t\t}, this.maxRetries).then((res)=>{\n\t\t\t\t\t\tconst bundle = new Bundle();\n\t\t\t\t\t\tcallback(null, bundle);\n\t\t\t\t\t\tconst untar = new Untar(res, this._assets.prefix);\n\t\t\t\t\t\tuntar.on('file', (file)=>{\n\t\t\t\t\t\t\t\tbundle.addFile(file.name, file.data);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tuntar.on('done', ()=>{\n\t\t\t\t\t\t\t\tbundle.loaded = true;\n\t\t\t\t\t\t});\n\t\t\t\t\t\tuntar.on('error', (err)=>{\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t});\n\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\tcallback(err);\n\t\t\t\t});\n\t\t}\n\t\topen(url, bundle) {\n\t\t\t\treturn bundle;\n\t\t}\n}\n\nclass ResourceLoader {\n\t\tconstructor(app){\n\t\t\t\tthis._handlers = {};\n\t\t\t\tthis._requests = {};\n\t\t\t\tthis._cache = {};\n\t\t\t\tthis._app = app;\n\t\t}\n\t\taddHandler(type, handler) {\n\t\t\t\tthis._handlers[type] = handler;\n\t\t\t\thandler._loader = this;\n\t\t}\n\t\tremoveHandler(type) {\n\t\t\t\tdelete this._handlers[type];\n\t\t}\n\t\tgetHandler(type) {\n\t\t\t\treturn this._handlers[type];\n\t\t}\n\t\tstatic makeKey(url, type) {\n\t\t\t\treturn `${url}-${type}`;\n\t\t}\n\t\tload(url, type, callback, asset, options) {\n\t\t\t\tconst handler = this._handlers[type];\n\t\t\t\tif (!handler) {\n\t\t\t\t\t\tconst err = `No resource handler for asset type: '${type}' when loading [${url}]`;\n\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!url) {\n\t\t\t\t\t\tthis._loadNull(handler, callback, asset);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst key = ResourceLoader.makeKey(url, type);\n\t\t\t\tif (this._cache[key] !== undefined) {\n\t\t\t\t\t\tcallback(null, this._cache[key]);\n\t\t\t\t} else if (this._requests[key]) {\n\t\t\t\t\t\tthis._requests[key].push(callback);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._requests[key] = [\n\t\t\t\t\t\t\t\tcallback\n\t\t\t\t\t\t];\n\t\t\t\t\t\tconst self = this;\n\t\t\t\t\t\tconst handleLoad = function(err, urlObj) {\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tself._onFailure(key, err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (urlObj.load instanceof DataView) {\n\t\t\t\t\t\t\t\t\t\tif (handler.openBinary) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!self._requests[key]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst data = handler.openBinary(urlObj.load);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tself._onSuccess(key, data);\n\t\t\t\t\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tself._onFailure(key, err);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\turlObj.load = URL.createObjectURL(new Blob([\n\t\t\t\t\t\t\t\t\t\t\t\turlObj.load\n\t\t\t\t\t\t\t\t\t\t]));\n\t\t\t\t\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (asset.urlObject) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tURL.revokeObjectURL(asset.urlObject);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tasset.urlObject = urlObj.load;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\thandler.load(urlObj, (err, data, extra)=>{\n\t\t\t\t\t\t\t\t\t\tif (!self._requests[key]) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\tself._onFailure(key, err);\n\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\tself._onSuccess(key, handler.open(urlObj.original, data, asset), extra);\n\t\t\t\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\t\t\t\t\tself._onFailure(key, e);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}, asset);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst normalizedUrl = url.split('?')[0];\n\t\t\t\t\t\tif (this._app.enableBundles && this._app.bundles.hasUrl(normalizedUrl) && !(options && options.bundlesIgnore)) {\n\t\t\t\t\t\t\t\tif (!this._app.bundles.urlIsLoadedOrLoading(normalizedUrl)) {\n\t\t\t\t\t\t\t\t\t\tconst bundles = this._app.bundles.listBundlesForAsset(asset);\n\t\t\t\t\t\t\t\t\t\tlet bundle;\n\t\t\t\t\t\t\t\t\t\tif (options && options.bundlesFilter) {\n\t\t\t\t\t\t\t\t\t\t\t\tbundle = options.bundlesFilter(bundles);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!bundle) {\n\t\t\t\t\t\t\t\t\t\t\t\tbundles?.sort((a, b)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn a.file.size - b.file.size;\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\tbundle = bundles?.[0];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (bundle) this._app.assets?.load(bundle);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._app.bundles.loadUrl(normalizedUrl, (err, fileUrlFromBundle)=>{\n\t\t\t\t\t\t\t\t\t\thandleLoad(err, {\n\t\t\t\t\t\t\t\t\t\t\t\tload: fileUrlFromBundle,\n\t\t\t\t\t\t\t\t\t\t\t\toriginal: normalizedUrl\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\thandleLoad(null, {\n\t\t\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\t\t\toriginal: asset && asset.file.filename || url\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_loadNull(handler, callback, asset) {\n\t\t\t\tconst onLoad = function(err, data, extra) {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tcallback(null, handler.open(null, data, asset), extra);\n\t\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\t\t\tcallback(e);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\thandler.load(null, onLoad, asset);\n\t\t}\n\t\t_onSuccess(key, result, extra) {\n\t\t\t\tif (result !== null) {\n\t\t\t\t\t\tthis._cache[key] = result;\n\t\t\t\t} else {\n\t\t\t\t\t\tdelete this._cache[key];\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._requests[key].length; i++){\n\t\t\t\t\t\tthis._requests[key][i](null, result, extra);\n\t\t\t\t}\n\t\t\t\tdelete this._requests[key];\n\t\t}\n\t\t_onFailure(key, err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\tif (this._requests[key]) {\n\t\t\t\t\t\tfor(let i = 0; i < this._requests[key].length; i++){\n\t\t\t\t\t\t\t\tthis._requests[key][i](err);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdelete this._requests[key];\n\t\t\t\t}\n\t\t}\n\t\topen(type, data) {\n\t\t\t\tconst handler = this._handlers[type];\n\t\t\t\tif (!handler) {\n\t\t\t\t\t\tconsole.warn(`No resource handler found for: ${type}`);\n\t\t\t\t\t\treturn data;\n\t\t\t\t}\n\t\t\t\treturn handler.open(null, data);\n\t\t}\n\t\tpatch(asset, assets) {\n\t\t\t\tconst handler = this._handlers[asset.type];\n\t\t\t\tif (!handler) {\n\t\t\t\t\t\tconsole.warn(`No resource handler found for: ${asset.type}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (handler.patch) {\n\t\t\t\t\t\thandler.patch(asset, assets);\n\t\t\t\t}\n\t\t}\n\t\tclearCache(url, type) {\n\t\t\t\tconst key = ResourceLoader.makeKey(url, type);\n\t\t\t\tdelete this._cache[key];\n\t\t}\n\t\tgetFromCache(url, type) {\n\t\t\t\tconst key = ResourceLoader.makeKey(url, type);\n\t\t\t\tif (this._cache[key]) {\n\t\t\t\t\t\treturn this._cache[key];\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\tenableRetry(maxRetries = 5) {\n\t\t\t\tmaxRetries = Math.max(0, maxRetries) || 0;\n\t\t\t\tfor(const key in this._handlers){\n\t\t\t\t\t\tthis._handlers[key].maxRetries = maxRetries;\n\t\t\t\t}\n\t\t}\n\t\tdisableRetry() {\n\t\t\t\tfor(const key in this._handlers){\n\t\t\t\t\t\tthis._handlers[key].maxRetries = 0;\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._handlers = {};\n\t\t\t\tthis._requests = {};\n\t\t\t\tthis._cache = {};\n\t\t}\n}\n\nclass I18nParser {\n\t\t_validate(data) {\n\t\t\t\tif (!data.header) {\n\t\t\t\t\t\tthrow new Error('pc.I18n#addData: Missing \"header\" field');\n\t\t\t\t}\n\t\t\t\tif (!data.header.version) {\n\t\t\t\t\t\tthrow new Error('pc.I18n#addData: Missing \"header.version\" field');\n\t\t\t\t}\n\t\t\t\tif (data.header.version !== 1) {\n\t\t\t\t\t\tthrow new Error('pc.I18n#addData: Invalid \"header.version\" field');\n\t\t\t\t}\n\t\t\t\tif (!data.data) {\n\t\t\t\t\t\tthrow new Error('pc.I18n#addData: Missing \"data\" field');\n\t\t\t\t} else if (!Array.isArray(data.data)) {\n\t\t\t\t\t\tthrow new Error('pc.I18n#addData: \"data\" field must be an array');\n\t\t\t\t}\n\t\t\t\tfor(let i = 0, len = data.data.length; i < len; i++){\n\t\t\t\t\t\tconst entry = data.data[i];\n\t\t\t\t\t\tif (!entry.info) {\n\t\t\t\t\t\t\t\tthrow new Error(`pc.I18n#addData: missing \"data[${i}].info\" field`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!entry.info.locale) {\n\t\t\t\t\t\t\t\tthrow new Error(`pc.I18n#addData: missing \"data[${i}].info.locale\" field`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (typeof entry.info.locale !== 'string') {\n\t\t\t\t\t\t\t\tthrow new Error(`pc.I18n#addData: \"data[${i}].info.locale\" must be a string`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!entry.messages) {\n\t\t\t\t\t\t\t\tthrow new Error(`pc.I18n#addData: missing \"data[${i}].messages\" field`);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tparse(data) {\n\t\t\t\treturn data.data;\n\t\t}\n}\n\nclass I18n extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_CHANGE = 'change';\n\t\t}\n\t\tconstructor(app){\n\t\t\t\tsuper();\n\t\t\t\tthis.locale = DEFAULT_LOCALE;\n\t\t\t\tthis._translations = {};\n\t\t\t\tthis._availableLangs = {};\n\t\t\t\tthis._app = app;\n\t\t\t\tthis._assets = [];\n\t\t\t\tthis._parser = new I18nParser();\n\t\t}\n\t\tset assets(value) {\n\t\t\t\tconst index = {};\n\t\t\t\tfor(let i = 0, len = value.length; i < len; i++){\n\t\t\t\t\t\tconst id = value[i] instanceof Asset ? value[i].id : value[i];\n\t\t\t\t\t\tindex[id] = true;\n\t\t\t\t}\n\t\t\t\tlet i = this._assets.length;\n\t\t\t\twhile(i--){\n\t\t\t\t\t\tconst id = this._assets[i];\n\t\t\t\t\t\tif (!index[id]) {\n\t\t\t\t\t\t\t\tthis._app.assets.off(`add:${id}`, this._onAssetAdd, this);\n\t\t\t\t\t\t\t\tconst asset = this._app.assets.get(id);\n\t\t\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\t\t\tthis._onAssetRemove(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._assets.splice(i, 1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(const id in index){\n\t\t\t\t\t\tconst idNum = parseInt(id, 10);\n\t\t\t\t\t\tif (this._assets.indexOf(idNum) !== -1) continue;\n\t\t\t\t\t\tthis._assets.push(idNum);\n\t\t\t\t\t\tconst asset = this._app.assets.get(idNum);\n\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\tthis._app.assets.once(`add:${idNum}`, this._onAssetAdd, this);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._onAssetAdd(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget assets() {\n\t\t\t\treturn this._assets;\n\t\t}\n\t\tset locale(value) {\n\t\t\t\tif (this._locale === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet lang = getLang(value);\n\t\t\t\tif (lang === 'in') {\n\t\t\t\t\t\tlang = 'id';\n\t\t\t\t\t\tvalue = replaceLang(value, lang);\n\t\t\t\t\t\tif (this._locale === value) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst old = this._locale;\n\t\t\t\tthis._locale = value;\n\t\t\t\tthis._lang = lang;\n\t\t\t\tthis._pluralFn = getPluralFn(this._lang);\n\t\t\t\tthis.fire(I18n.EVENT_CHANGE, value, old);\n\t\t}\n\t\tget locale() {\n\t\t\t\treturn this._locale;\n\t\t}\n\t\tstatic findAvailableLocale(desiredLocale, availableLocales) {\n\t\t\t\treturn findAvailableLocale(desiredLocale, availableLocales);\n\t\t}\n\t\tfindAvailableLocale(desiredLocale) {\n\t\t\t\tif (this._translations[desiredLocale]) {\n\t\t\t\t\t\treturn desiredLocale;\n\t\t\t\t}\n\t\t\t\tconst lang = getLang(desiredLocale);\n\t\t\t\treturn this._findFallbackLocale(desiredLocale, lang);\n\t\t}\n\t\tgetText(key, locale) {\n\t\t\t\tlet result = key;\n\t\t\t\tlet lang;\n\t\t\t\tif (!locale) {\n\t\t\t\t\t\tlocale = this._locale;\n\t\t\t\t\t\tlang = this._lang;\n\t\t\t\t}\n\t\t\t\tlet translations = this._translations[locale];\n\t\t\t\tif (!translations) {\n\t\t\t\t\t\tif (!lang) {\n\t\t\t\t\t\t\t\tlang = getLang(locale);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlocale = this._findFallbackLocale(locale, lang);\n\t\t\t\t\t\ttranslations = this._translations[locale];\n\t\t\t\t}\n\t\t\t\tif (translations && translations.hasOwnProperty(key)) {\n\t\t\t\t\t\tresult = translations[key];\n\t\t\t\t\t\tif (Array.isArray(result)) {\n\t\t\t\t\t\t\t\tresult = result[0];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result === null || result === undefined) {\n\t\t\t\t\t\t\t\tresult = key;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tgetPluralText(key, n, locale) {\n\t\t\t\tlet result = key;\n\t\t\t\tlet lang;\n\t\t\t\tlet pluralFn;\n\t\t\t\tif (!locale) {\n\t\t\t\t\t\tlocale = this._locale;\n\t\t\t\t\t\tlang = this._lang;\n\t\t\t\t\t\tpluralFn = this._pluralFn;\n\t\t\t\t} else {\n\t\t\t\t\t\tlang = getLang(locale);\n\t\t\t\t\t\tpluralFn = getPluralFn(lang);\n\t\t\t\t}\n\t\t\t\tlet translations = this._translations[locale];\n\t\t\t\tif (!translations) {\n\t\t\t\t\t\tlocale = this._findFallbackLocale(locale, lang);\n\t\t\t\t\t\tlang = getLang(locale);\n\t\t\t\t\t\tpluralFn = getPluralFn(lang);\n\t\t\t\t\t\ttranslations = this._translations[locale];\n\t\t\t\t}\n\t\t\t\tif (translations && translations[key] && pluralFn) {\n\t\t\t\t\t\tconst index = pluralFn(n);\n\t\t\t\t\t\tresult = translations[key][index];\n\t\t\t\t\t\tif (result === null || result === undefined) {\n\t\t\t\t\t\t\t\tresult = key;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\taddData(data) {\n\t\t\t\tlet parsed;\n\t\t\t\ttry {\n\t\t\t\t\t\tparsed = this._parser.parse(data);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0, len = parsed.length; i < len; i++){\n\t\t\t\t\t\tconst entry = parsed[i];\n\t\t\t\t\t\tconst locale = entry.info.locale;\n\t\t\t\t\t\tconst messages = entry.messages;\n\t\t\t\t\t\tif (!this._translations[locale]) {\n\t\t\t\t\t\t\t\tthis._translations[locale] = {};\n\t\t\t\t\t\t\t\tconst lang = getLang(locale);\n\t\t\t\t\t\t\t\tif (!this._availableLangs[lang]) {\n\t\t\t\t\t\t\t\t\t\tthis._availableLangs[lang] = locale;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tObject.assign(this._translations[locale], messages);\n\t\t\t\t\t\tthis.fire('data:add', locale, messages);\n\t\t\t\t}\n\t\t}\n\t\tremoveData(data) {\n\t\t\t\tlet parsed;\n\t\t\t\ttry {\n\t\t\t\t\t\tparsed = this._parser.parse(data);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0, len = parsed.length; i < len; i++){\n\t\t\t\t\t\tconst entry = parsed[i];\n\t\t\t\t\t\tconst locale = entry.info.locale;\n\t\t\t\t\t\tconst translations = this._translations[locale];\n\t\t\t\t\t\tif (!translations) continue;\n\t\t\t\t\t\tconst messages = entry.messages;\n\t\t\t\t\t\tfor(const key in messages){\n\t\t\t\t\t\t\t\tdelete translations[key];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (Object.keys(translations).length === 0) {\n\t\t\t\t\t\t\t\tdelete this._translations[locale];\n\t\t\t\t\t\t\t\tdelete this._availableLangs[getLang(locale)];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.fire('data:remove', locale, messages);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._translations = null;\n\t\t\t\tthis._availableLangs = null;\n\t\t\t\tthis._assets = null;\n\t\t\t\tthis._parser = null;\n\t\t\t\tthis.off();\n\t\t}\n\t\t_findFallbackLocale(locale, lang) {\n\t\t\t\tlet result = DEFAULT_LOCALE_FALLBACKS[locale];\n\t\t\t\tif (result && this._translations[result]) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\tresult = DEFAULT_LOCALE_FALLBACKS[lang];\n\t\t\t\tif (result && this._translations[result]) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\tresult = this._availableLangs[lang];\n\t\t\t\tif (result && this._translations[result]) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\treturn DEFAULT_LOCALE;\n\t\t}\n\t\t_onAssetAdd(asset) {\n\t\t\t\tasset.on('load', this._onAssetLoad, this);\n\t\t\t\tasset.on('change', this._onAssetChange, this);\n\t\t\t\tasset.on('remove', this._onAssetRemove, this);\n\t\t\t\tasset.on('unload', this._onAssetUnload, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onAssetLoad(asset);\n\t\t\t\t}\n\t\t}\n\t\t_onAssetLoad(asset) {\n\t\t\t\tthis.addData(asset.resource);\n\t\t}\n\t\t_onAssetChange(asset) {\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis.addData(asset.resource);\n\t\t\t\t}\n\t\t}\n\t\t_onAssetRemove(asset) {\n\t\t\t\tasset.off('load', this._onAssetLoad, this);\n\t\t\t\tasset.off('change', this._onAssetChange, this);\n\t\t\t\tasset.off('remove', this._onAssetRemove, this);\n\t\t\t\tasset.off('unload', this._onAssetUnload, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis.removeData(asset.resource);\n\t\t\t\t}\n\t\t\t\tthis._app.assets.once(`add:${asset.id}`, this._onAssetAdd, this);\n\t\t}\n\t\t_onAssetUnload(asset) {\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis.removeData(asset.resource);\n\t\t\t\t}\n\t\t}\n}\n\nclass ScriptRegistry extends EventHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(), this._scripts = {}, this._list = [], this._scriptSchemas = new Map();\n\t\t\t\tthis.app = app;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.app = null;\n\t\t\t\tthis.off();\n\t\t}\n\t\taddSchema(id, schema) {\n\t\t\t\tif (!schema) return;\n\t\t\t\tthis._scriptSchemas.set(id, schema);\n\t\t}\n\t\tgetSchema(id) {\n\t\t\t\treturn this._scriptSchemas.get(id);\n\t\t}\n\t\tadd(script) {\n\t\t\t\tconst scriptName = script.__name;\n\t\t\t\tif (this._scripts.hasOwnProperty(scriptName)) {\n\t\t\t\t\t\tsetTimeout(()=>{\n\t\t\t\t\t\t\t\tif (script.prototype.swap) {\n\t\t\t\t\t\t\t\t\t\tconst old = this._scripts[scriptName];\n\t\t\t\t\t\t\t\t\t\tconst ind = this._list.indexOf(old);\n\t\t\t\t\t\t\t\t\t\tthis._list[ind] = script;\n\t\t\t\t\t\t\t\t\t\tthis._scripts[scriptName] = script;\n\t\t\t\t\t\t\t\t\t\tthis.fire('swap', scriptName, script);\n\t\t\t\t\t\t\t\t\t\tthis.fire(`swap:${scriptName}`, script);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconsole.warn(`script registry already has '${scriptName}' script, define 'swap' method for new script type to enable code hot swapping`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis._scripts[scriptName] = script;\n\t\t\t\tthis._list.push(script);\n\t\t\t\tthis.fire('add', scriptName, script);\n\t\t\t\tthis.fire(`add:${scriptName}`, script);\n\t\t\t\tsetTimeout(()=>{\n\t\t\t\t\t\tif (!this._scripts.hasOwnProperty(scriptName)) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this.app || !this.app.systems || !this.app.systems.script) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst components = this.app.systems.script._components;\n\t\t\t\t\t\tlet attributes;\n\t\t\t\t\t\tconst scriptInstances = [];\n\t\t\t\t\t\tconst scriptInstancesInitialized = [];\n\t\t\t\t\t\tfor(components.loopIndex = 0; components.loopIndex < components.length; components.loopIndex++){\n\t\t\t\t\t\t\t\tconst component = components.items[components.loopIndex];\n\t\t\t\t\t\t\t\tif (component._scriptsIndex[scriptName] && component._scriptsIndex[scriptName].awaiting) {\n\t\t\t\t\t\t\t\t\t\tif (component._scriptsData && component._scriptsData[scriptName]) {\n\t\t\t\t\t\t\t\t\t\t\t\tattributes = component._scriptsData[scriptName].attributes;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst scriptInstance = component.create(scriptName, {\n\t\t\t\t\t\t\t\t\t\t\t\tpreloading: true,\n\t\t\t\t\t\t\t\t\t\t\t\tind: component._scriptsIndex[scriptName].ind,\n\t\t\t\t\t\t\t\t\t\t\t\tattributes: attributes\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tif (scriptInstance) {\n\t\t\t\t\t\t\t\t\t\t\t\tscriptInstances.push(scriptInstance);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tfor (const script of component.scripts){\n\t\t\t\t\t\t\t\t\t\t\t\tcomponent.initializeAttributes(script);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < scriptInstances.length; i++){\n\t\t\t\t\t\t\t\tif (scriptInstances[i].enabled) {\n\t\t\t\t\t\t\t\t\t\tscriptInstances[i]._initialized = true;\n\t\t\t\t\t\t\t\t\t\tscriptInstancesInitialized.push(scriptInstances[i]);\n\t\t\t\t\t\t\t\t\t\tif (scriptInstances[i].initialize) {\n\t\t\t\t\t\t\t\t\t\t\t\tscriptInstances[i].initialize();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < scriptInstancesInitialized.length; i++){\n\t\t\t\t\t\t\t\tif (!scriptInstancesInitialized[i].enabled || scriptInstancesInitialized[i]._postInitialized) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tscriptInstancesInitialized[i]._postInitialized = true;\n\t\t\t\t\t\t\t\tif (scriptInstancesInitialized[i].postInitialize) {\n\t\t\t\t\t\t\t\t\t\tscriptInstancesInitialized[i].postInitialize();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t}\n\t\tremove(nameOrType) {\n\t\t\t\tlet scriptType = nameOrType;\n\t\t\t\tlet scriptName = nameOrType;\n\t\t\t\tif (typeof scriptName !== 'string') {\n\t\t\t\t\t\tscriptName = scriptType.__name;\n\t\t\t\t} else {\n\t\t\t\t\t\tscriptType = this.get(scriptName);\n\t\t\t\t}\n\t\t\t\tif (this.get(scriptName) !== scriptType) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tdelete this._scripts[scriptName];\n\t\t\t\tconst ind = this._list.indexOf(scriptType);\n\t\t\t\tthis._list.splice(ind, 1);\n\t\t\t\tthis.fire('remove', scriptName, scriptType);\n\t\t\t\tthis.fire(`remove:${scriptName}`, scriptType);\n\t\t\t\treturn true;\n\t\t}\n\t\tget(name) {\n\t\t\t\treturn this._scripts[name] || null;\n\t\t}\n\t\thas(nameOrType) {\n\t\t\t\tif (typeof nameOrType === 'string') {\n\t\t\t\t\t\treturn this._scripts.hasOwnProperty(nameOrType);\n\t\t\t\t}\n\t\t\t\tif (!nameOrType) return false;\n\t\t\t\tconst scriptName = nameOrType.__name;\n\t\t\t\treturn this._scripts[scriptName] === nameOrType;\n\t\t}\n\t\tlist() {\n\t\t\t\treturn this._list;\n\t\t}\n}\n\nconst cmpStaticOrder = (a, b)=>a.constructor.order - b.constructor.order;\nconst sortStaticOrder = (arr)=>arr.sort(cmpStaticOrder);\nconst _enableList = [];\nconst tmpPool = [];\nconst getTempArray = ()=>{\n\t\treturn tmpPool.pop() ?? [];\n};\nconst releaseTempArray = (a)=>{\n\t\ta.length = 0;\n\t\ttmpPool.push(a);\n};\nclass Entity extends GraphNode {\n\t\tstatic{\n\t\t\t\tthis.EVENT_DESTROY = 'destroy';\n\t\t}\n\t\tconstructor(name, app = getApplication()){\n\t\t\t\tsuper(name), this.c = {}, this._destroying = false, this._guid = null, this._template = false;\n\t\t\t\tthis._app = app;\n\t\t}\n\t\taddComponent(type, data) {\n\t\t\t\tconst system = this._app.systems[type];\n\t\t\t\tif (!system) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tif (this.c[type]) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn system.addComponent(this, data);\n\t\t}\n\t\tremoveComponent(type) {\n\t\t\t\tconst system = this._app.systems[type];\n\t\t\t\tif (!system) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.c[type]) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsystem.removeComponent(this);\n\t\t}\n\t\tfindComponent(type) {\n\t\t\t\tconst entity = this.findOne((entity)=>entity.c?.[type]);\n\t\t\t\treturn entity && entity.c[type];\n\t\t}\n\t\tfindComponents(type) {\n\t\t\t\treturn this.find((entity)=>entity.c?.[type]).map((entity)=>entity.c[type]);\n\t\t}\n\t\tfindScript(nameOrType) {\n\t\t\t\tconst entity = this.findOne((node)=>node.c?.script?.has(nameOrType));\n\t\t\t\treturn entity?.c.script.get(nameOrType);\n\t\t}\n\t\tfindScripts(nameOrType) {\n\t\t\t\tconst entities = this.find((node)=>node.c?.script?.has(nameOrType));\n\t\t\t\treturn entities.map((entity)=>entity.c.script.get(nameOrType));\n\t\t}\n\t\tgetGuid() {\n\t\t\t\tif (!this._guid) {\n\t\t\t\t\t\tthis.setGuid(guid.create());\n\t\t\t\t}\n\t\t\t\treturn this._guid;\n\t\t}\n\t\tsetGuid(guid) {\n\t\t\t\tconst index = this._app._entityIndex;\n\t\t\t\tif (this._guid) {\n\t\t\t\t\t\tdelete index[this._guid];\n\t\t\t\t}\n\t\t\t\tthis._guid = guid;\n\t\t\t\tindex[this._guid] = this;\n\t\t}\n\t\t_notifyHierarchyStateChanged(node, enabled) {\n\t\t\t\tlet enableFirst = false;\n\t\t\t\tif (node === this && _enableList.length === 0) {\n\t\t\t\t\t\tenableFirst = true;\n\t\t\t\t}\n\t\t\t\tnode._beingEnabled = true;\n\t\t\t\tnode._onHierarchyStateChanged(enabled);\n\t\t\t\tif (node._onHierarchyStatePostChanged) {\n\t\t\t\t\t\t_enableList.push(node);\n\t\t\t\t}\n\t\t\t\tconst c = node._children;\n\t\t\t\tfor(let i = 0, len = c.length; i < len; i++){\n\t\t\t\t\t\tif (c[i]._enabled) {\n\t\t\t\t\t\t\t\tthis._notifyHierarchyStateChanged(c[i], enabled);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tnode._beingEnabled = false;\n\t\t\t\tif (enableFirst) {\n\t\t\t\t\t\tfor(let i = 0; i < _enableList.length; i++){\n\t\t\t\t\t\t\t\t_enableList[i]._onHierarchyStatePostChanged();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_enableList.length = 0;\n\t\t\t\t}\n\t\t}\n\t\t_onHierarchyStateChanged(enabled) {\n\t\t\t\tsuper._onHierarchyStateChanged(enabled);\n\t\t\t\tconst components = this._getSortedComponents();\n\t\t\t\tfor(let i = 0; i < components.length; i++){\n\t\t\t\t\t\tconst component = components[i];\n\t\t\t\t\t\tif (component.enabled) {\n\t\t\t\t\t\t\t\tif (enabled) {\n\t\t\t\t\t\t\t\t\t\tcomponent.onEnable();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcomponent.onDisable();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treleaseTempArray(components);\n\t\t}\n\t\t_onHierarchyStatePostChanged() {\n\t\t\t\tconst components = this._getSortedComponents();\n\t\t\t\tfor(let i = 0; i < components.length; i++){\n\t\t\t\t\t\tcomponents[i].onPostStateChange();\n\t\t\t\t}\n\t\t\t\treleaseTempArray(components);\n\t\t}\n\t\tfindByGuid(guid) {\n\t\t\t\tif (this._guid === guid) return this;\n\t\t\t\tconst e = this._app._entityIndex[guid];\n\t\t\t\tif (e && (e === this || e.isDescendantOf(this))) {\n\t\t\t\t\t\treturn e;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._destroying = true;\n\t\t\t\tfor(const name in this.c){\n\t\t\t\t\t\tthis.c[name].enabled = false;\n\t\t\t\t}\n\t\t\t\tfor(const name in this.c){\n\t\t\t\t\t\tthis.c[name].system.removeComponent(this);\n\t\t\t\t}\n\t\t\t\tsuper.destroy();\n\t\t\t\tif (this._guid) {\n\t\t\t\t\t\tdelete this._app._entityIndex[this._guid];\n\t\t\t\t}\n\t\t\t\tthis._destroying = false;\n\t\t}\n\t\tclone() {\n\t\t\t\tconst duplicatedIdsMap = {};\n\t\t\t\tconst clone = this._cloneRecursively(duplicatedIdsMap);\n\t\t\t\tduplicatedIdsMap[this.getGuid()] = clone;\n\t\t\t\tresolveDuplicatedEntityReferenceProperties(this, this, clone, duplicatedIdsMap);\n\t\t\t\treturn clone;\n\t\t}\n\t\t_getSortedComponents() {\n\t\t\t\tconst components = this.c;\n\t\t\t\tconst sortedArray = getTempArray();\n\t\t\t\tlet needSort = 0;\n\t\t\t\tfor(const type in components){\n\t\t\t\t\t\tif (components.hasOwnProperty(type)) {\n\t\t\t\t\t\t\t\tconst component = components[type];\n\t\t\t\t\t\t\t\tneedSort |= component.constructor.order !== 0;\n\t\t\t\t\t\t\t\tsortedArray.push(component);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (needSort && sortedArray.length > 1) {\n\t\t\t\t\t\tsortStaticOrder(sortedArray);\n\t\t\t\t}\n\t\t\t\treturn sortedArray;\n\t\t}\n\t\t_cloneRecursively(duplicatedIdsMap) {\n\t\t\t\tconst clone = new this.constructor(undefined, this._app);\n\t\t\t\tsuper._cloneInternal(clone);\n\t\t\t\tfor(const type in this.c){\n\t\t\t\t\t\tconst component = this.c[type];\n\t\t\t\t\t\tcomponent.system.cloneComponent(this, clone);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst oldChild = this._children[i];\n\t\t\t\t\t\tif (oldChild instanceof Entity) {\n\t\t\t\t\t\t\t\tconst newChild = oldChild._cloneRecursively(duplicatedIdsMap);\n\t\t\t\t\t\t\t\tclone.addChild(newChild);\n\t\t\t\t\t\t\t\tduplicatedIdsMap[oldChild.getGuid()] = newChild;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn clone;\n\t\t}\n}\nfunction resolveDuplicatedEntityReferenceProperties(oldSubtreeRoot, oldEntity, newEntity, duplicatedIdsMap) {\n\t\tif (oldEntity instanceof Entity) {\n\t\t\t\tconst components = oldEntity.c;\n\t\t\t\tfor(const componentName in components){\n\t\t\t\t\t\tconst component = components[componentName];\n\t\t\t\t\t\tconst entityProperties = component.system.getPropertiesOfType('entity');\n\t\t\t\t\t\tfor(let i = 0, len = entityProperties.length; i < len; i++){\n\t\t\t\t\t\t\t\tconst propertyDescriptor = entityProperties[i];\n\t\t\t\t\t\t\t\tconst propertyName = propertyDescriptor.name;\n\t\t\t\t\t\t\t\tconst oldEntityReferenceId = component[propertyName];\n\t\t\t\t\t\t\t\tconst entityIsWithinOldSubtree = !!oldSubtreeRoot.findByGuid(oldEntityReferenceId);\n\t\t\t\t\t\t\t\tif (entityIsWithinOldSubtree) {\n\t\t\t\t\t\t\t\t\t\tconst newEntityReferenceId = duplicatedIdsMap[oldEntityReferenceId].getGuid();\n\t\t\t\t\t\t\t\t\t\tif (newEntityReferenceId) {\n\t\t\t\t\t\t\t\t\t\t\t\tnewEntity.c[componentName][propertyName] = newEntityReferenceId;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (components.script) {\n\t\t\t\t\t\tnewEntity.script.resolveDuplicatedEntityReferenceProperties(components.script, duplicatedIdsMap);\n\t\t\t\t}\n\t\t\t\tif (components.render) {\n\t\t\t\t\t\tnewEntity.render.resolveDuplicatedEntityReferenceProperties(components.render, duplicatedIdsMap);\n\t\t\t\t}\n\t\t\t\tif (components.button) {\n\t\t\t\t\t\tnewEntity.button.resolveDuplicatedEntityReferenceProperties(components.button, duplicatedIdsMap);\n\t\t\t\t}\n\t\t\t\tif (components.scrollview) {\n\t\t\t\t\t\tnewEntity.scrollview.resolveDuplicatedEntityReferenceProperties(components.scrollview, duplicatedIdsMap);\n\t\t\t\t}\n\t\t\t\tif (components.scrollbar) {\n\t\t\t\t\t\tnewEntity.scrollbar.resolveDuplicatedEntityReferenceProperties(components.scrollbar, duplicatedIdsMap);\n\t\t\t\t}\n\t\t\t\tif (components.anim) {\n\t\t\t\t\t\tnewEntity.anim.resolveDuplicatedEntityReferenceProperties(components.anim, duplicatedIdsMap);\n\t\t\t\t}\n\t\t\t\tconst _old = oldEntity.children.filter((e)=>e instanceof Entity);\n\t\t\t\tconst _new = newEntity.children.filter((e)=>e instanceof Entity);\n\t\t\t\tfor(let i = 0, len = _old.length; i < len; i++){\n\t\t\t\t\t\tresolveDuplicatedEntityReferenceProperties(oldSubtreeRoot, _old[i], _new[i], duplicatedIdsMap);\n\t\t\t\t}\n\t\t}\n}\n\nclass SceneRegistryItem {\n\t\tconstructor(name, url){\n\t\t\t\tthis.data = null;\n\t\t\t\tthis._loading = false;\n\t\t\t\tthis._onLoadedCallbacks = [];\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.url = url;\n\t\t}\n\t\tget loaded() {\n\t\t\t\treturn !!this.data;\n\t\t}\n\t\tget loading() {\n\t\t\t\treturn this._loading;\n\t\t}\n}\n\nclass SceneRegistry {\n\t\tconstructor(app){\n\t\t\t\tthis._list = [];\n\t\t\t\tthis._index = {};\n\t\t\t\tthis._urlIndex = {};\n\t\t\t\tthis._app = app;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._app = null;\n\t\t}\n\t\tlist() {\n\t\t\t\treturn this._list;\n\t\t}\n\t\tadd(name, url) {\n\t\t\t\tif (this._index.hasOwnProperty(name)) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst item = new SceneRegistryItem(name, url);\n\t\t\t\tconst i = this._list.push(item);\n\t\t\t\tthis._index[item.name] = i - 1;\n\t\t\t\tthis._urlIndex[item.url] = i - 1;\n\t\t\t\treturn true;\n\t\t}\n\t\tfind(name) {\n\t\t\t\tif (this._index.hasOwnProperty(name)) {\n\t\t\t\t\t\treturn this._list[this._index[name]];\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tfindByUrl(url) {\n\t\t\t\tif (this._urlIndex.hasOwnProperty(url)) {\n\t\t\t\t\t\treturn this._list[this._urlIndex[url]];\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tremove(name) {\n\t\t\t\tif (this._index.hasOwnProperty(name)) {\n\t\t\t\t\t\tconst idx = this._index[name];\n\t\t\t\t\t\tlet item = this._list[idx];\n\t\t\t\t\t\tdelete this._urlIndex[item.url];\n\t\t\t\t\t\tdelete this._index[name];\n\t\t\t\t\t\tthis._list.splice(idx, 1);\n\t\t\t\t\t\tfor(let i = 0; i < this._list.length; i++){\n\t\t\t\t\t\t\t\titem = this._list[i];\n\t\t\t\t\t\t\t\tthis._index[item.name] = i;\n\t\t\t\t\t\t\t\tthis._urlIndex[item.url] = i;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_loadSceneData(sceneItem, storeInCache, callback) {\n\t\t\t\tconst app = this._app;\n\t\t\t\tlet url = sceneItem;\n\t\t\t\tif (typeof sceneItem === 'string') {\n\t\t\t\t\t\tsceneItem = this.findByUrl(url) || this.find(url) || new SceneRegistryItem('Untitled', url);\n\t\t\t\t}\n\t\t\t\turl = sceneItem.url;\n\t\t\t\tif (!url) {\n\t\t\t\t\t\tcallback('Cannot find scene to load');\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sceneItem.loaded) {\n\t\t\t\t\t\tcallback(null, sceneItem);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (app.assets && app.assets.prefix && !ABSOLUTE_URL.test(url)) {\n\t\t\t\t\t\turl = path.join(app.assets.prefix, url);\n\t\t\t\t}\n\t\t\t\tsceneItem._onLoadedCallbacks.push(callback);\n\t\t\t\tif (!sceneItem._loading) {\n\t\t\t\t\t\tconst handler = app.loader.getHandler('hierarchy');\n\t\t\t\t\t\thandler.load(url, (err, data)=>{\n\t\t\t\t\t\t\t\tsceneItem.data = data;\n\t\t\t\t\t\t\t\tsceneItem._loading = false;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < sceneItem._onLoadedCallbacks.length; i++){\n\t\t\t\t\t\t\t\t\t\tsceneItem._onLoadedCallbacks[i](err, sceneItem);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!storeInCache) {\n\t\t\t\t\t\t\t\t\t\tsceneItem.data = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsceneItem._onLoadedCallbacks.length = 0;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tsceneItem._loading = true;\n\t\t}\n\t\tloadSceneData(sceneItem, callback) {\n\t\t\t\tthis._loadSceneData(sceneItem, true, callback);\n\t\t}\n\t\tunloadSceneData(sceneItem) {\n\t\t\t\tif (typeof sceneItem === 'string') {\n\t\t\t\t\t\tsceneItem = this.findByUrl(sceneItem);\n\t\t\t\t}\n\t\t\t\tif (sceneItem) {\n\t\t\t\t\t\tsceneItem.data = null;\n\t\t\t\t}\n\t\t}\n\t\t_loadSceneHierarchy(sceneItem, onBeforeAddHierarchy, callback) {\n\t\t\t\tthis._loadSceneData(sceneItem, false, (err, sceneItem)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (onBeforeAddHierarchy) {\n\t\t\t\t\t\t\t\tonBeforeAddHierarchy(sceneItem);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst app = this._app;\n\t\t\t\t\t\tconst _loaded = ()=>{\n\t\t\t\t\t\t\t\tconst handler = app.loader.getHandler('hierarchy');\n\t\t\t\t\t\t\t\tapp.systems.script.preloading = true;\n\t\t\t\t\t\t\t\tconst entity = handler.open(sceneItem.url, sceneItem.data);\n\t\t\t\t\t\t\t\tapp.systems.script.preloading = false;\n\t\t\t\t\t\t\t\tapp.loader.clearCache(sceneItem.url, 'hierarchy');\n\t\t\t\t\t\t\t\tapp.root.addChild(entity);\n\t\t\t\t\t\t\t\tapp.systems.fire('initialize', entity);\n\t\t\t\t\t\t\t\tapp.systems.fire('postInitialize', entity);\n\t\t\t\t\t\t\t\tapp.systems.fire('postPostInitialize', entity);\n\t\t\t\t\t\t\t\tif (callback) callback(null, entity);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tapp._preloadScripts(sceneItem.data, _loaded);\n\t\t\t\t});\n\t\t}\n\t\tloadSceneHierarchy(sceneItem, callback) {\n\t\t\t\tthis._loadSceneHierarchy(sceneItem, null, callback);\n\t\t}\n\t\tloadSceneSettings(sceneItem, callback) {\n\t\t\t\tthis._loadSceneData(sceneItem, false, (err, sceneItem)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tthis._app.applySceneSettings(sceneItem.data.settings);\n\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\tcallback(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tchangeScene(sceneItem, callback) {\n\t\t\t\tconst app = this._app;\n\t\t\t\tconst onBeforeAddHierarchy = (sceneItem)=>{\n\t\t\t\t\t\tconst { children } = app.root;\n\t\t\t\t\t\twhile(children.length){\n\t\t\t\t\t\t\t\tchildren[0].destroy();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tapp.applySceneSettings(sceneItem.data.settings);\n\t\t\t\t};\n\t\t\t\tthis._loadSceneHierarchy(sceneItem, onBeforeAddHierarchy, callback);\n\t\t}\n\t\tloadScene(url, callback) {\n\t\t\t\tconst app = this._app;\n\t\t\t\tconst handler = app.loader.getHandler('scene');\n\t\t\t\tif (app.assets && app.assets.prefix && !ABSOLUTE_URL.test(url)) {\n\t\t\t\t\t\turl = path.join(app.assets.prefix, url);\n\t\t\t\t}\n\t\t\t\thandler.load(url, (err, data)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tconst _loaded = ()=>{\n\t\t\t\t\t\t\t\t\t\tapp.systems.script.preloading = true;\n\t\t\t\t\t\t\t\t\t\tconst scene = handler.open(url, data);\n\t\t\t\t\t\t\t\t\t\tconst sceneItem = this.findByUrl(url);\n\t\t\t\t\t\t\t\t\t\tif (sceneItem && !sceneItem.loaded) {\n\t\t\t\t\t\t\t\t\t\t\t\tsceneItem.data = data;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tapp.systems.script.preloading = false;\n\t\t\t\t\t\t\t\t\t\tapp.loader.clearCache(url, 'scene');\n\t\t\t\t\t\t\t\t\t\tapp.loader.patch({\n\t\t\t\t\t\t\t\t\t\t\t\tresource: scene,\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'scene'\n\t\t\t\t\t\t\t\t\t\t}, app.assets);\n\t\t\t\t\t\t\t\t\t\tapp.root.addChild(scene.root);\n\t\t\t\t\t\t\t\t\t\tif (app.systems.rigidbody && typeof Ammo !== 'undefined') {\n\t\t\t\t\t\t\t\t\t\t\t\tapp.systems.rigidbody.gravity.set(scene._gravity.x, scene._gravity.y, scene._gravity.z);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\t\t\tcallback(null, scene);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tapp._preloadScripts(data, _loaded);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n}\n\nclass ApplicationStats {\n\t\tconstructor(device){\n\t\t\t\tthis.frame = {\n\t\t\t\t\t\tfps: 0,\n\t\t\t\t\t\tms: 0,\n\t\t\t\t\t\tdt: 0,\n\t\t\t\t\t\tupdateStart: 0,\n\t\t\t\t\t\tupdateTime: 0,\n\t\t\t\t\t\trenderStart: 0,\n\t\t\t\t\t\trenderTime: 0,\n\t\t\t\t\t\tphysicsStart: 0,\n\t\t\t\t\t\tphysicsTime: 0,\n\t\t\t\t\t\tcullTime: 0,\n\t\t\t\t\t\tsortTime: 0,\n\t\t\t\t\t\tskinTime: 0,\n\t\t\t\t\t\tmorphTime: 0,\n\t\t\t\t\t\tinstancingTime: 0,\n\t\t\t\t\t\ttriangles: 0,\n\t\t\t\t\t\totherPrimitives: 0,\n\t\t\t\t\t\tshaders: 0,\n\t\t\t\t\t\tmaterials: 0,\n\t\t\t\t\t\tcameras: 0,\n\t\t\t\t\t\tshadowMapUpdates: 0,\n\t\t\t\t\t\tshadowMapTime: 0,\n\t\t\t\t\t\tdepthMapTime: 0,\n\t\t\t\t\t\tforwardTime: 0,\n\t\t\t\t\t\tlightClustersTime: 0,\n\t\t\t\t\t\tlightClusters: 0,\n\t\t\t\t\t\t_timeToCountFrames: 0,\n\t\t\t\t\t\t_fpsAccum: 0\n\t\t\t\t};\n\t\t\t\tthis.drawCalls = {\n\t\t\t\t\t\tforward: 0,\n\t\t\t\t\t\tdepth: 0,\n\t\t\t\t\t\tshadow: 0,\n\t\t\t\t\t\timmediate: 0,\n\t\t\t\t\t\tmisc: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tskinned: 0,\n\t\t\t\t\t\tinstanced: 0,\n\t\t\t\t\t\tremovedByInstancing: 0\n\t\t\t\t};\n\t\t\t\tthis.misc = {\n\t\t\t\t\t\trenderTargetCreationTime: 0\n\t\t\t\t};\n\t\t\t\tthis.particles = {\n\t\t\t\t\t\tupdatesPerFrame: 0,\n\t\t\t\t\t\t_updatesPerFrame: 0,\n\t\t\t\t\t\tframeTime: 0,\n\t\t\t\t\t\t_frameTime: 0\n\t\t\t\t};\n\t\t\t\tthis.shaders = device._shaderStats;\n\t\t\t\tthis.vram = device._vram;\n\t\t\t\tObject.defineProperty(this.vram, 'totalUsed', {\n\t\t\t\t\t\tget: function() {\n\t\t\t\t\t\t\t\treturn this.tex + this.vb + this.ib;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tObject.defineProperty(this.vram, 'geom', {\n\t\t\t\t\t\tget: function() {\n\t\t\t\t\t\t\t\treturn this.vb + this.ib;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tget scene() {\n\t\t\t\treturn getApplication().scene._stats;\n\t\t}\n\t\tget lightmapper() {\n\t\t\t\treturn getApplication().lightmapper?.stats;\n\t\t}\n\t\tget batcher() {\n\t\t\t\tconst batcher = getApplication()._batcher;\n\t\t\t\treturn batcher ? batcher._stats : null;\n\t\t}\n}\n\nvar alphaTestPS$1 = `\nuniform float alpha_ref;\nvoid alphaTest(float a) {\n\tif (a < alpha_ref) discard;\n}\n`;\n\nvar ambientPS$1 = `\n#ifdef LIT_AMBIENT_SOURCE == AMBIENTSH\n\tuniform vec3 ambientSH[9];\n#endif\n#if LIT_AMBIENT_SOURCE == ENVALATLAS\n\t#include \"envAtlasPS\"\n\t#ifndef ENV_ATLAS\n\t#define ENV_ATLAS\n\t\tuniform sampler2D texture_envAtlas;\n\t#endif\n#endif\nvoid addAmbient(vec3 worldNormal) {\n\t#ifdef LIT_AMBIENT_SOURCE == AMBIENTSH\n\t\tvec3 n = cubeMapRotate(worldNormal);\n\t\tvec3 color =\n\t\t\tambientSH[0] +\n\t\t\tambientSH[1] * n.x +\n\t\t\tambientSH[2] * n.y +\n\t\t\tambientSH[3] * n.z +\n\t\t\tambientSH[4] * n.x * n.z +\n\t\t\tambientSH[5] * n.z * n.y +\n\t\t\tambientSH[6] * n.y * n.x +\n\t\t\tambientSH[7] * (3.0 * n.z * n.z - 1.0) +\n\t\t\tambientSH[8] * (n.x * n.x - n.y * n.y);\n\t\tdDiffuseLight += processEnvironment(max(color, vec3(0.0)));\n\t#endif\n\t#if LIT_AMBIENT_SOURCE == ENVALATLAS\n\t\tvec3 dir = normalize(cubeMapRotate(worldNormal) * vec3(-1.0, 1.0, 1.0));\n\t\tvec2 uv = mapUv(toSphericalUv(dir), vec4(128.0, 256.0 + 128.0, 64.0, 32.0) / atlasSize);\n\t\tvec4 raw = texture2D(texture_envAtlas, uv);\n\t\tvec3 linear = {ambientDecode}(raw);\n\t\tdDiffuseLight += processEnvironment(linear);\n\t#endif\n\t#if LIT_AMBIENT_SOURCE == CONSTANT\n\t\tdDiffuseLight += light_globalAmbient;\n\t#endif\n}\n`;\n\nvar anisotropyPS$1 = `\n#ifdef LIT_GGX_SPECULAR\n\tuniform float material_anisotropyIntensity;\n\tuniform vec2 material_anisotropyRotation;\n#endif\nvoid getAnisotropy() {\n\tdAnisotropy = 0.0;\n\tdAnisotropyRotation = vec2(1.0, 0.0);\n#ifdef LIT_GGX_SPECULAR\n\tdAnisotropy = material_anisotropyIntensity;\n\tdAnisotropyRotation = material_anisotropyRotation;\n#endif\n\t#ifdef STD_ANISOTROPY_TEXTURE\n\tvec3 anisotropyTex = texture2DBias({STD_ANISOTROPY_TEXTURE_NAME}, {STD_ANISOTROPY_TEXTURE_UV}, textureBias).rgb;\n\tdAnisotropy *= anisotropyTex.b;\n\tvec2 anisotropyRotationFromTex = anisotropyTex.rg * 2.0 - vec2(1.0);\n\tmat2 rotationMatrix = mat2(dAnisotropyRotation.x, dAnisotropyRotation.y, -dAnisotropyRotation.y, dAnisotropyRotation.x);\n\tdAnisotropyRotation = rotationMatrix * anisotropyRotationFromTex;\n\t#endif\n\t\n\tdAnisotropy = clamp(dAnisotropy, 0.0, 1.0);\n}\n`;\n\nvar aoPS$1 = `\n#if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)\n\tuniform float material_aoIntensity;\n#endif\n#ifdef STD_AODETAIL_TEXTURE\n\t#include \"detailModesPS\"\n#endif\nvoid getAO() {\n\tdAo = 1.0;\n\t#ifdef STD_AO_TEXTURE\n\t\tfloat aoBase = texture2DBias({STD_AO_TEXTURE_NAME}, {STD_AO_TEXTURE_UV}, textureBias).{STD_AO_TEXTURE_CHANNEL};\n\t\t#ifdef STD_AODETAIL_TEXTURE\n\t\t\tfloat aoDetail = texture2DBias({STD_AODETAIL_TEXTURE_NAME}, {STD_AODETAIL_TEXTURE_UV}, textureBias).{STD_AODETAIL_TEXTURE_CHANNEL};\n\t\t\taoBase = detailMode_{STD_AODETAIL_DETAILMODE}(vec3(aoBase), vec3(aoDetail)).r;\n\t\t#endif\n\t\tdAo *= aoBase;\n\t#endif\n\t#ifdef STD_AO_VERTEX\n\t\tdAo *= saturate(vVertexColor.{STD_AO_VERTEX_CHANNEL});\n\t#endif\n\t#if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)\n\t\tdAo = mix(1.0, dAo, material_aoIntensity);\n\t#endif\n}\n`;\n\nvar aoDiffuseOccPS$1 = `\nvoid occludeDiffuse(float ao) {\n\tdDiffuseLight *= ao;\n}\n`;\n\nvar aoSpecOccPS$1 = `\n#if LIT_OCCLUDE_SPECULAR != NONE\n\t#ifdef LIT_OCCLUDE_SPECULAR_FLOAT\n\t\tuniform float material_occludeSpecularIntensity;\n\t#endif\n#endif\nvoid occludeSpecular(float gloss, float ao, vec3 worldNormal, vec3 viewDir) {\n\t#if LIT_OCCLUDE_SPECULAR == AO\n\t\t#ifdef LIT_OCCLUDE_SPECULAR_FLOAT\n\t\t\tfloat specOcc = mix(1.0, ao, material_occludeSpecularIntensity);\n\t\t#else\n\t\t\tfloat specOcc = ao;\n\t\t#endif\n\t#endif\n\t#if LIT_OCCLUDE_SPECULAR == GLOSSDEPENDENT\n\t\tfloat specPow = exp2(gloss * 11.0);\n\t\tfloat specOcc = saturate(pow(dot(worldNormal, viewDir) + ao, 0.01 * specPow) - 1.0 + ao);\n\t\t#ifdef LIT_OCCLUDE_SPECULAR_FLOAT\n\t\t\tspecOcc = mix(1.0, specOcc, material_occludeSpecularIntensity);\n\t\t#endif\n\t#endif\n\t#if LIT_OCCLUDE_SPECULAR != NONE\n\t\tdSpecularLight *= specOcc;\n\t\tdReflection *= specOcc;\n\t\t#ifdef LIT_SHEEN\n\t\t\tsSpecularLight *= specOcc;\n\t\t\tsReflection *= specOcc;\n\t\t#endif\n\t#endif\n}\n`;\n\nvar bakeDirLmEndPS$1 = `\n\tvec4 dirLm = texture2D(texture_dirLightMap, vUv1);\n\tif (bakeDir > 0.5) {\n\t\tif (dAtten > 0.00001) {\n\t\t\tdirLm.xyz = dirLm.xyz * 2.0 - vec3(1.0);\n\t\t\tdAtten = saturate(dAtten);\n\t\t\tgl_FragColor.rgb = normalize(dLightDirNormW.xyz*dAtten + dirLm.xyz*dirLm.w) * 0.5 + vec3(0.5);\n\t\t\tgl_FragColor.a = dirLm.w + dAtten;\n\t\t\tgl_FragColor.a = max(gl_FragColor.a, 1.0 / 255.0);\n\t\t} else {\n\t\t\tgl_FragColor = dirLm;\n\t\t}\n\t} else {\n\t\tgl_FragColor.rgb = dirLm.xyz;\n\t\tgl_FragColor.a = max(dirLm.w, dAtten > 0.00001 ? (1.0/255.0) : 0.0);\n\t}\n`;\n\nvar bakeLmEndPS$1 = `\n#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT\n\tdDiffuseLight = ((dDiffuseLight - 0.5) * max(ambientBakeOcclusionContrast + 1.0, 0.0)) + 0.5;\n\tdDiffuseLight += vec3(ambientBakeOcclusionBrightness);\n\tdDiffuseLight = saturate(dDiffuseLight);\n\tdDiffuseLight *= dAmbientLight;\n#endif\n#ifdef LIGHTMAP_RGBM\n\tgl_FragColor.rgb = dDiffuseLight;\n\tgl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(0.5));\n\tgl_FragColor.rgb /= 8.0;\n\tgl_FragColor.a = clamp( max( max( gl_FragColor.r, gl_FragColor.g ), max( gl_FragColor.b, 1.0 / 255.0 ) ), 0.0,1.0 );\n\tgl_FragColor.a = ceil(gl_FragColor.a * 255.0) / 255.0;\n\tgl_FragColor.rgb /= gl_FragColor.a;\n#else\n\tgl_FragColor = vec4(dDiffuseLight, 1.0);\n#endif\n`;\n\nvar basePS$1 = `\nuniform vec3 view_position;\nuniform vec3 light_globalAmbient;\nfloat square(float x) {\n\treturn x*x;\n}\nfloat saturate(float x) {\n\treturn clamp(x, 0.0, 1.0);\n}\nvec3 saturate(vec3 x) {\n\treturn clamp(x, vec3(0.0), vec3(1.0));\n}\n`;\n\nvar baseNineSlicedPS$1 = `\n#define NINESLICED\nvarying vec2 vMask;\nvarying vec2 vTiledUv;\nuniform mediump vec4 innerOffset;\nuniform mediump vec2 outerScale;\nuniform mediump vec4 atlasRect;\nvec2 nineSlicedUv;\n`;\n\nvar baseNineSlicedTiledPS$1 = `\n#define NINESLICED\n#define NINESLICETILED\nvarying vec2 vMask;\nvarying vec2 vTiledUv;\nuniform mediump vec4 innerOffset;\nuniform mediump vec2 outerScale;\nuniform mediump vec4 atlasRect;\nvec2 nineSlicedUv;\n`;\n\nvar bayerPS$1 = `\nfloat bayer2(vec2 p) {\n\treturn mod(2.0 * p.y + p.x + 1.0, 4.0);\n}\nfloat bayer4(vec2 p) {\n\tvec2 p1 = mod(p, 2.0);\n\tvec2 p2 = floor(0.5 * mod(p, 4.0));\n\treturn 4.0 * bayer2(p1) + bayer2(p2);\n}\nfloat bayer8(vec2 p) {\n\tvec2 p1 = mod(p, 2.0);\n\tvec2 p2 = floor(0.5 * mod(p, 4.0));\n\tvec2 p4 = floor(0.25 * mod(p, 8.0));\n\treturn 4.0 * (4.0 * bayer2(p1) + bayer2(p2)) + bayer2(p4);\n}\n`;\n\nvar blurVSMPS$1 = `\nvarying vec2 vUv0;\nuniform sampler2D source;\nuniform vec2 pixelOffset;\n#ifdef GAUSS\n\tuniform float weight[{SAMPLES}];\n#endif\nvoid main(void) {\n\tvec3 moments = vec3(0.0);\n\tvec2 uv = vUv0 - pixelOffset * (float({SAMPLES}) * 0.5);\n\tfor (int i = 0; i < {SAMPLES}; i++) {\n\t\tvec4 c = texture2D(source, uv + pixelOffset * float(i));\n\t\t#ifdef GAUSS\n\t\t\tmoments += c.xyz * weight[i];\n\t\t#else\n\t\t\tmoments += c.xyz;\n\t\t#endif\n\t}\n\t#ifndef GAUSS\n\t\tmoments *= 1.0 / float({SAMPLES});\n\t#endif\n\tgl_FragColor = vec4(moments.x, moments.y, moments.z, 1.0);\n}\n`;\n\nvar clearCoatPS$1 = `\nuniform float material_clearCoat;\nvoid getClearCoat() {\n\tccSpecularity = material_clearCoat;\n\t#ifdef STD_CLEARCOAT_TEXTURE\n\tccSpecularity *= texture2DBias({STD_CLEARCOAT_TEXTURE_NAME}, {STD_CLEARCOAT_TEXTURE_UV}, textureBias).{STD_CLEARCOAT_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_CLEARCOAT_VERTEX\n\tccSpecularity *= saturate(vVertexColor.{STD_CLEARCOAT_VERTEX_CHANNEL});\n\t#endif\n}\n`;\n\nvar clearCoatGlossPS$1 = `\nuniform float material_clearCoatGloss;\nvoid getClearCoatGlossiness() {\n\tccGlossiness = material_clearCoatGloss;\n\t#ifdef STD_CLEARCOATGLOSS_TEXTURE\n\tccGlossiness *= texture2DBias({STD_CLEARCOATGLOSS_TEXTURE_NAME}, {STD_CLEARCOATGLOSS_TEXTURE_UV}, textureBias).{STD_CLEARCOATGLOSS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_CLEARCOATGLOSS_VERTEX\n\tccGlossiness *= saturate(vVertexColor.{STD_CLEARCOATGLOSS_VERTEX_CHANNEL});\n\t#endif\n\t#ifdef STD_CLEARCOATGLOSS_INVERT\n\tccGlossiness = 1.0 - ccGlossiness;\n\t#endif\n\tccGlossiness += 0.0000001;\n}\n`;\n\nvar clearCoatNormalPS$1 = `\n#ifdef STD_CLEARCOATNORMAL_TEXTURE\nuniform float material_clearCoatBumpiness;\n#endif\nvoid getClearCoatNormal() {\n#ifdef STD_CLEARCOATNORMAL_TEXTURE\n\tvec3 normalMap = {STD_CLEARCOATNORMAL_TEXTURE_DECODE}(texture2DBias({STD_CLEARCOATNORMAL_TEXTURE_NAME}, {STD_CLEARCOATNORMAL_TEXTURE_UV}, textureBias));\n\tnormalMap = mix(vec3(0.0, 0.0, 1.0), normalMap, material_clearCoatBumpiness);\n\tccNormalW = normalize(dTBN * normalMap);\n#else\n\tccNormalW = dVertexNormalW;\n#endif\n}\n`;\n\nvar clusteredLightUtilsPS$1 = `\nvec2 getCubemapFaceCoordinates(const vec3 dir, out float faceIndex, out vec2 tileOffset)\n{\n\tvec3 vAbs = abs(dir);\n\tfloat ma;\n\tvec2 uv;\n\tif (vAbs.z >= vAbs.x && vAbs.z >= vAbs.y) {\n\t\tfaceIndex = dir.z < 0.0 ? 5.0 : 4.0;\n\t\tma = 0.5 / vAbs.z;\n\t\tuv = vec2(dir.z < 0.0 ? -dir.x : dir.x, -dir.y);\n\t\ttileOffset.x = 2.0;\n\t\ttileOffset.y = dir.z < 0.0 ? 1.0 : 0.0;\n\t} else if(vAbs.y >= vAbs.x) {\n\t\tfaceIndex = dir.y < 0.0 ? 3.0 : 2.0;\n\t\tma = 0.5 / vAbs.y;\n\t\tuv = vec2(dir.x, dir.y < 0.0 ? -dir.z : dir.z);\n\t\ttileOffset.x = 1.0;\n\t\ttileOffset.y = dir.y < 0.0 ? 1.0 : 0.0;\n\t} else {\n\t\tfaceIndex = dir.x < 0.0 ? 1.0 : 0.0;\n\t\tma = 0.5 / vAbs.x;\n\t\tuv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y);\n\t\ttileOffset.x = 0.0;\n\t\ttileOffset.y = dir.x < 0.0 ? 1.0 : 0.0;\n\t}\n\treturn uv * ma + 0.5;\n}\nvec2 getCubemapAtlasCoordinates(const vec3 omniAtlasViewport, float shadowEdgePixels, float shadowTextureResolution, const vec3 dir) {\n\tfloat faceIndex;\n\tvec2 tileOffset;\n\tvec2 uv = getCubemapFaceCoordinates(dir, faceIndex, tileOffset);\n\tfloat atlasFaceSize = omniAtlasViewport.z;\n\tfloat tileSize = shadowTextureResolution * atlasFaceSize;\n\tfloat offset = shadowEdgePixels / tileSize;\n\tuv = uv * vec2(1.0 - offset * 2.0) + vec2(offset * 1.0);\n\tuv *= atlasFaceSize;\n\tuv += tileOffset * atlasFaceSize;\n\tuv += omniAtlasViewport.xy;\n\treturn uv;\n}\n`;\n\nvar clusteredLightCookiesPS$1 = `\nvec3 _getCookieClustered(TEXTURE_ACCEPT(tex), vec2 uv, float intensity, vec4 cookieChannel) {\n\tvec4 pixel = mix(vec4(1.0), texture2DLod(tex, uv, 0.0), intensity);\n\tbool isRgb = dot(cookieChannel.rgb, vec3(1.0)) == 3.0;\n\treturn isRgb ? pixel.rgb : vec3(dot(pixel, cookieChannel));\n}\nvec3 getCookie2DClustered(TEXTURE_ACCEPT(tex), mat4 transform, vec3 worldPosition, float intensity, vec4 cookieChannel) {\n\tvec4 projPos = transform * vec4(worldPosition, 1.0);\n\treturn _getCookieClustered(TEXTURE_PASS(tex), projPos.xy / projPos.w, intensity, cookieChannel);\n}\nvec3 getCookieCubeClustered(TEXTURE_ACCEPT(tex), vec3 dir, float intensity, vec4 cookieChannel, float shadowTextureResolution, float shadowEdgePixels, vec3 omniAtlasViewport) {\n\tvec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, dir);\n\treturn _getCookieClustered(TEXTURE_PASS(tex), uv, intensity, cookieChannel);\n}\n`;\n\nvar clusteredLightShadowsPS$1 = `\nvec3 _getShadowCoordPerspZbuffer(mat4 shadowMatrix, vec4 shadowParams, vec3 wPos) {\n\tvec4 projPos = shadowMatrix * vec4(wPos, 1.0);\n\tprojPos.xyz /= projPos.w;\n\treturn projPos.xyz;\n}\nvec3 getShadowCoordPerspZbufferNormalOffset(mat4 shadowMatrix, vec4 shadowParams, vec3 normal) {\n\tvec3 wPos = vPositionW + normal * shadowParams.y;\n\treturn _getShadowCoordPerspZbuffer(shadowMatrix, shadowParams, wPos);\n}\nvec3 normalOffsetPointShadow(vec4 shadowParams, vec3 lightPos, vec3 lightDir, vec3 lightDirNorm, vec3 normal) {\n\tfloat distScale = length(lightDir);\n\tvec3 wPos = vPositionW + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;\n\tvec3 dir = wPos - lightPos;\n\treturn dir;\n}\n#if defined(CLUSTER_SHADOW_TYPE_PCF1)\nfloat getShadowOmniClusteredPCF1(SHADOWMAP_ACCEPT(shadowMap), vec4 shadowParams, vec3 omniAtlasViewport, float shadowEdgePixels, vec3 lightDir) {\n\tfloat shadowTextureResolution = shadowParams.x;\n\tvec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);\n\tfloat shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;\n\treturn textureShadow(shadowMap, vec3(uv, shadowZ));\n}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF3)\nfloat getShadowOmniClusteredPCF3(SHADOWMAP_ACCEPT(shadowMap), vec4 shadowParams, vec3 omniAtlasViewport, float shadowEdgePixels, vec3 lightDir) {\n\tfloat shadowTextureResolution = shadowParams.x;\n\tvec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);\n\tfloat shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;\n\tvec3 shadowCoord = vec3(uv, shadowZ);\n\treturn getShadowPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);\n}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF5)\nfloat getShadowOmniClusteredPCF5(SHADOWMAP_ACCEPT(shadowMap), vec4 shadowParams, vec3 omniAtlasViewport, float shadowEdgePixels, vec3 lightDir) {\n\tfloat shadowTextureResolution = shadowParams.x;\n\tvec2 uv = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);\n\tfloat shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;\n\tvec3 shadowCoord = vec3(uv, shadowZ);\n\treturn getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);\n}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF1)\nfloat getShadowSpotClusteredPCF1(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn textureShadow(shadowMap, shadowCoord);\n}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF3)\nfloat getShadowSpotClusteredPCF3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn getShadowSpotPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);\n}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF5)\nfloat getShadowSpotClusteredPCF5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams);\n}\n#endif\n`;\n\nvar clusteredLightPS$1 = `\n#include \"lightBufferDefinesPS\"\n#include \"clusteredLightUtilsPS\"\n#ifdef CLUSTER_COOKIES\n\t#include \"clusteredLightCookiesPS\"\n#endif\n#ifdef CLUSTER_SHADOWS\n\t#include \"clusteredLightShadowsPS\"\n#endif\nuniform highp sampler2D clusterWorldTexture;\nuniform highp sampler2D lightsTexture;\n#ifdef CLUSTER_SHADOWS\n\tuniform sampler2DShadow shadowAtlasTexture;\n#endif\n#ifdef CLUSTER_COOKIES\n\tuniform sampler2D cookieAtlasTexture;\n#endif\nuniform int clusterMaxCells;\nuniform float clusterSkip;\nuniform vec3 clusterCellsCountByBoundsSize;\nuniform vec3 clusterTextureSize;\nuniform vec3 clusterBoundsMin;\nuniform vec3 clusterBoundsDelta;\nuniform vec3 clusterCellsDot;\nuniform vec3 clusterCellsMax;\nuniform vec2 shadowAtlasParams;\nstruct ClusterLightData {\n\tuint flags;\n\tvec3 halfWidth;\n\tbool isSpot;\n\tvec3 halfHeight;\n\tint lightIndex;\n\tvec3 position;\n\tuint shape;\n\tvec3 direction;\n\tbool falloffModeLinear;\n\tvec3 color;\n\tfloat shadowIntensity;\n\tvec3 omniAtlasViewport;\n\tfloat range;\n\tvec4 cookieChannelMask;\n\tfloat biasesData;\n\tfloat shadowBias;\n\tfloat shadowNormalBias;\n\tfloat anglesData;\n\tfloat innerConeAngleCos;\n\tfloat outerConeAngleCos;\n\tfloat cookieIntensity;\n\tbool isDynamic;\n\tbool isLightmapped;\n};\nmat4 lightProjectionMatrix;\nvec4 sampleLightTextureF(const ClusterLightData clusterLightData, int index) {\n\treturn texelFetch(lightsTexture, ivec2(index, clusterLightData.lightIndex), 0);\n}\nvoid decodeClusterLightCore(inout ClusterLightData clusterLightData, float lightIndex) {\n\tclusterLightData.lightIndex = int(lightIndex);\n\tvec4 halfData = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS});\n\tclusterLightData.anglesData = halfData.z;\n\tclusterLightData.biasesData = halfData.w;\n\tvec2 colorRG = unpackHalf2x16(floatBitsToUint(halfData.x));\n\tvec2 colorB_ = unpackHalf2x16(floatBitsToUint(halfData.y));\n\tclusterLightData.color = vec3(colorRG, colorB_.x) * {LIGHT_COLOR_DIVIDER};\n\tvec4 lightPosRange = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_POSITION_RANGE});\n\tclusterLightData.position = lightPosRange.xyz;\n\tclusterLightData.range = lightPosRange.w;\n\tvec4 lightDir_Flags = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_DIRECTION_FLAGS});\n\tclusterLightData.direction = lightDir_Flags.xyz;\n\tclusterLightData.flags = floatBitsToUint(lightDir_Flags.w);\n\tclusterLightData.isSpot = (clusterLightData.flags & (1u << 30u)) != 0u;\n\tclusterLightData.shape = (clusterLightData.flags >> 28u) & 0x3u;\n\tclusterLightData.falloffModeLinear = (clusterLightData.flags & (1u << 27u)) == 0u;\n\tclusterLightData.shadowIntensity = float((clusterLightData.flags >> 0u) & 0xFFu) / 255.0;\n\tclusterLightData.cookieIntensity = float((clusterLightData.flags >> 8u) & 0xFFu) / 255.0;\n\tclusterLightData.isDynamic = (clusterLightData.flags & (1u << 22u)) != 0u;\n\tclusterLightData.isLightmapped = (clusterLightData.flags & (1u << 21u)) != 0u;\n}\nvoid decodeClusterLightSpot(inout ClusterLightData clusterLightData) {\n\tvec2 angles = unpackHalf2x16(floatBitsToUint(clusterLightData.anglesData));\n\tclusterLightData.innerConeAngleCos = angles.x;\n\tclusterLightData.outerConeAngleCos = angles.y;\n}\nvoid decodeClusterLightOmniAtlasViewport(inout ClusterLightData clusterLightData) {\n\tclusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;\n}\nvoid decodeClusterLightAreaData(inout ClusterLightData clusterLightData) {\n\tclusterLightData.halfWidth = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz;\n\tclusterLightData.halfHeight = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz;\n}\nvoid decodeClusterLightProjectionMatrixData(inout ClusterLightData clusterLightData) {\n\t\n\tvec4 m0 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_0});\n\tvec4 m1 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_1});\n\tvec4 m2 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_2});\n\tvec4 m3 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_3});\n\tlightProjectionMatrix = mat4(m0, m1, m2, m3);\n}\nvoid decodeClusterLightShadowData(inout ClusterLightData clusterLightData) {\n\t\n\tvec2 biases = unpackHalf2x16(floatBitsToUint(clusterLightData.biasesData));\n\tclusterLightData.shadowBias = biases.x;\n\tclusterLightData.shadowNormalBias = biases.y;\n}\nvoid decodeClusterLightCookieData(inout ClusterLightData clusterLightData) {\n\tuint cookieFlags = (clusterLightData.flags >> 23u) & 0x0Fu;\n\tclusterLightData.cookieChannelMask = vec4(uvec4(cookieFlags) & uvec4(1u, 2u, 4u, 8u));\n\tclusterLightData.cookieChannelMask = step(1.0, clusterLightData.cookieChannelMask);\n}\nvoid evaluateLight(\n\tClusterLightData light, \n\tvec3 worldNormal, \n\tvec3 viewDir, \n\tvec3 reflectionDir,\n#if defined(LIT_CLEARCOAT)\n\tvec3 clearcoatReflectionDir,\n#endif\n\tfloat gloss, \n\tvec3 specularity, \n\tvec3 geometricNormal, \n\tmat3 tbn, \n#if defined(LIT_IRIDESCENCE)\n\tvec3 iridescenceFresnel,\n#endif\n\tvec3 clearcoat_worldNormal,\n\tfloat clearcoat_gloss,\n\tfloat sheen_gloss,\n\tfloat iridescence_intensity\n) {\n\tvec3 cookieAttenuation = vec3(1.0);\n\tfloat diffuseAttenuation = 1.0;\n\tfloat falloffAttenuation = 1.0;\n\tvec3 lightDirW = evalOmniLight(light.position);\n\tvec3 lightDirNormW = normalize(lightDirW);\n\t#ifdef CLUSTER_AREALIGHTS\n\tif (light.shape != {LIGHTSHAPE_PUNCTUAL}) {\n\t\tdecodeClusterLightAreaData(light);\n\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\tcalcRectLightValues(light.position, light.halfWidth, light.halfHeight);\n\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\tcalcDiskLightValues(light.position, light.halfWidth, light.halfHeight);\n\t\t} else {\n\t\t\tcalcSphereLightValues(light.position, light.halfWidth, light.halfHeight);\n\t\t}\n\t\tfalloffAttenuation = getFalloffWindow(light.range, lightDirW);\n\t} else\n\t#endif\n\t{\n\t\tif (light.falloffModeLinear)\n\t\t\tfalloffAttenuation = getFalloffLinear(light.range, lightDirW);\n\t\telse\n\t\t\tfalloffAttenuation = getFalloffInvSquared(light.range, lightDirW);\n\t}\n\tif (falloffAttenuation > 0.00001) {\n\t\t#ifdef CLUSTER_AREALIGHTS\n\t\tif (light.shape != {LIGHTSHAPE_PUNCTUAL}) {\n\t\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\t\tdiffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n\t\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\t\tdiffuseAttenuation = getDiskLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n\t\t\t} else {\n\t\t\t\tdiffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n\t\t\t}\n\t\t} else\n\t\t#endif\n\t\t{\n\t\t\tfalloffAttenuation *= getLightDiffuse(worldNormal, viewDir, lightDirNormW); \n\t\t}\n\t\tif (light.isSpot) {\n\t\t\tdecodeClusterLightSpot(light);\n\t\t\tfalloffAttenuation *= getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);\n\t\t}\n\t\t#if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)\n\t\tif (falloffAttenuation > 0.00001) {\n\t\t\tif (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {\n\t\t\t\tif (light.isSpot) {\n\t\t\t\t\tdecodeClusterLightProjectionMatrixData(light);\n\t\t\t\t} else {\n\t\t\t\t\tdecodeClusterLightOmniAtlasViewport(light);\n\t\t\t\t}\n\t\t\t\tfloat shadowTextureResolution = shadowAtlasParams.x;\n\t\t\t\tfloat shadowEdgePixels = shadowAtlasParams.y;\n\t\t\t\t#ifdef CLUSTER_COOKIES\n\t\t\t\tif (light.cookieIntensity > 0.0) {\n\t\t\t\t\tdecodeClusterLightCookieData(light);\n\t\t\t\t\tif (light.isSpot) {\n\t\t\t\t\t\tcookieAttenuation = getCookie2DClustered(TEXTURE_PASS(cookieAtlasTexture), lightProjectionMatrix, vPositionW, light.cookieIntensity, light.cookieChannelMask);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcookieAttenuation = getCookieCubeClustered(TEXTURE_PASS(cookieAtlasTexture), lightDirW, light.cookieIntensity, light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t#endif\n\t\t\t\t#ifdef CLUSTER_SHADOWS\n\t\t\t\tif (light.shadowIntensity > 0.0) {\n\t\t\t\t\tdecodeClusterLightShadowData(light);\n\t\t\t\t\tvec4 shadowParams = vec4(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);\n\t\t\t\t\tif (light.isSpot) {\n\t\t\t\t\t\tvec3 shadowCoord = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);\n\t\t\t\t\t\t\n\t\t\t\t\t\t#if defined(CLUSTER_SHADOW_TYPE_PCF1)\n\t\t\t\t\t\t\tfloat shadow = getShadowSpotClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n\t\t\t\t\t\t\tfloat shadow = getShadowSpotClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n\t\t\t\t\t\t\tfloat shadow = getShadowSpotClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCSS)\n\t\t\t\t\t\t\tfloat shadow = getShadowSpotClusteredPCSS(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n\t\t\t\t\t\t#endif\n\t\t\t\t\t\tfalloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvec3 dir = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, geometricNormal);\n\t\t\t\t\t\t#if defined(CLUSTER_SHADOW_TYPE_PCF1)\n\t\t\t\t\t\t\tfloat shadow = getShadowOmniClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n\t\t\t\t\t\t\tfloat shadow = getShadowOmniClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n\t\t\t\t\t\t\tfloat shadow = getShadowOmniClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);\n\t\t\t\t\t\t#endif\n\t\t\t\t\t\tfalloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t#endif\n\t\t\t}\n\t\t}\n\t\t#endif\n\t\t#ifdef CLUSTER_AREALIGHTS\n\t\tif (light.shape != {LIGHTSHAPE_PUNCTUAL}) {\n\t\t\t{\n\t\t\t\tvec3 areaDiffuse = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;\n\t\t\t\t#if defined(LIT_SPECULAR)\n\t\t\t\t\tareaDiffuse = mix(areaDiffuse, vec3(0), dLTCSpecFres);\n\t\t\t\t#endif\n\t\t\t\tdDiffuseLight += areaDiffuse;\n\t\t\t}\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tfloat areaLightSpecular;\n\t\t\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\t\t\tareaLightSpecular = getRectLightSpecular(worldNormal, viewDir);\n\t\t\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\t\t\tareaLightSpecular = getDiskLightSpecular(worldNormal, viewDir);\n\t\t\t\t} else {\n\t\t\t\t\tareaLightSpecular = getSphereLightSpecular(worldNormal, viewDir);\n\t\t\t\t}\n\t\t\t\tdSpecularLight += dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;\n\t\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\t\tfloat areaLightSpecularCC;\n\t\t\t\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\t\t\t\tareaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir);\n\t\t\t\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\t\t\t\tareaLightSpecularCC = getDiskLightSpecular(clearcoat_worldNormal, viewDir);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tareaLightSpecularCC = getSphereLightSpecular(clearcoat_worldNormal, viewDir);\n\t\t\t\t\t}\n\t\t\t\t\tccSpecularLight += ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color  * cookieAttenuation;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t} else\n\t\t#endif\n\t\t{\n\t\t\t{\n\t\t\t\tvec3 punctualDiffuse = falloffAttenuation * light.color * cookieAttenuation;\n\t\t\t\t#if defined(CLUSTER_AREALIGHTS)\n\t\t\t\t#if defined(LIT_SPECULAR)\n\t\t\t\t\tpunctualDiffuse = mix(punctualDiffuse, vec3(0), specularity);\n\t\t\t\t#endif\n\t\t\t\t#endif\n\t\t\t\tdDiffuseLight += punctualDiffuse;\n\t\t\t}\n\t \n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tvec3 halfDir = normalize(-lightDirNormW + viewDir);\n\t\t\t\t\n\t\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\t\tdSpecularLight += \n\t\t\t\t\t\tgetLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * \n\t\t\t\t\t\tgetFresnel(\n\t\t\t\t\t\t\tdot(viewDir, halfDir), \n\t\t\t\t\t\t\tgloss, \n\t\t\t\t\t\t\tspecularity\n\t\t\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t\t\t, iridescenceFresnel,\n\t\t\t\t\t\t\tiridescence_intensity\n\t\t\t\t\t\t#endif\n\t\t\t\t\t\t\t);\n\t\t\t\t#else\n\t\t\t\t\tdSpecularLight += getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity;\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\t\t\tccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));\n\t\t\t\t\t#else\n\t\t\t\t\t\tccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation; \n\t\t\t\t\t#endif\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_SHEEN\n\t\t\t\t\tsSpecularLight += getLightSpecularSheen(halfDir, worldNormal, viewDir, lightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t}\n\t}\n\tdAtten = falloffAttenuation;\n\tdLightDirNormW = lightDirNormW;\n}\nvoid evaluateClusterLight(\n\tfloat lightIndex, \n\tvec3 worldNormal, \n\tvec3 viewDir, \n\tvec3 reflectionDir, \n#if defined(LIT_CLEARCOAT)\n\tvec3 clearcoatReflectionDir,\n#endif\n\tfloat gloss, \n\tvec3 specularity, \n\tvec3 geometricNormal, \n\tmat3 tbn, \n#if defined(LIT_IRIDESCENCE)\n\tvec3 iridescenceFresnel,\n#endif\n\tvec3 clearcoat_worldNormal,\n\tfloat clearcoat_gloss,\n\tfloat sheen_gloss,\n\tfloat iridescence_intensity\n) {\n\tClusterLightData clusterLightData;\n\tdecodeClusterLightCore(clusterLightData, lightIndex);\n\t#ifdef CLUSTER_MESH_DYNAMIC_LIGHTS\n\t\tbool acceptLightMask = clusterLightData.isDynamic;\n\t#else\n\t\tbool acceptLightMask = clusterLightData.isLightmapped;\n\t#endif\n\tif (acceptLightMask)\n\t\tevaluateLight(\n\t\t\tclusterLightData, \n\t\t\tworldNormal, \n\t\t\tviewDir, \n\t\t\treflectionDir, \n#if defined(LIT_CLEARCOAT)\n\t\t\tclearcoatReflectionDir, \n#endif\n\t\t\tgloss, \n\t\t\tspecularity, \n\t\t\tgeometricNormal, \n\t\t\ttbn, \n#if defined(LIT_IRIDESCENCE)\n\t\t\tiridescenceFresnel,\n#endif\n\t\t\tclearcoat_worldNormal,\n\t\t\tclearcoat_gloss,\n\t\t\tsheen_gloss,\n\t\t\tiridescence_intensity\n\t\t);\n}\nvoid addClusteredLights(\n\tvec3 worldNormal, \n\tvec3 viewDir, \n\tvec3 reflectionDir, \n#if defined(LIT_CLEARCOAT)\n\tvec3 clearcoatReflectionDir,\n#endif\n\tfloat gloss, \n\tvec3 specularity, \n\tvec3 geometricNormal, \n\tmat3 tbn, \n#if defined(LIT_IRIDESCENCE)\n\tvec3 iridescenceFresnel,\n#endif\n\tvec3 clearcoat_worldNormal,\n\tfloat clearcoat_gloss,\n\tfloat sheen_gloss,\n\tfloat iridescence_intensity\n) {\n\tif (clusterSkip > 0.5)\n\t\treturn;\n\tvec3 cellCoords = floor((vPositionW - clusterBoundsMin) * clusterCellsCountByBoundsSize);\n\tif (!(any(lessThan(cellCoords, vec3(0.0))) || any(greaterThanEqual(cellCoords, clusterCellsMax)))) {\n\t\tfloat cellIndex = dot(clusterCellsDot, cellCoords);\n\t\tfloat clusterV = floor(cellIndex * clusterTextureSize.y);\n\t\tfloat clusterU = cellIndex - (clusterV * clusterTextureSize.x);\n\t\tfor (int lightCellIndex = 0; lightCellIndex < clusterMaxCells; lightCellIndex++) {\n\t\t\tfloat lightIndex = texelFetch(clusterWorldTexture, ivec2(int(clusterU) + lightCellIndex, clusterV), 0).x;\n\t\t\tif (lightIndex <= 0.0)\n\t\t\t\tbreak;\n\t\t\tevaluateClusterLight(\n\t\t\t\tlightIndex * 255.0, \n\t\t\t\tworldNormal, \n\t\t\t\tviewDir, \n\t\t\t\treflectionDir,\n#if defined(LIT_CLEARCOAT)\n\t\t\t\tclearcoatReflectionDir,\n#endif\n\t\t\t\tgloss, \n\t\t\t\tspecularity, \n\t\t\t\tgeometricNormal, \n\t\t\t\ttbn, \n#if defined(LIT_IRIDESCENCE)\n\t\t\t\tiridescenceFresnel,\n#endif\n\t\t\t\tclearcoat_worldNormal,\n\t\t\t\tclearcoat_gloss,\n\t\t\t\tsheen_gloss,\n\t\t\t\tiridescence_intensity\n\t\t\t); \n\t\t}\n\t}\n}\n`;\n\nvar combinePS$1 = `\nvec3 combineColor(vec3 albedo, vec3 sheenSpecularity, float clearcoatSpecularity) {\n\tvec3 ret = vec3(0);\n#ifdef LIT_OLD_AMBIENT\n\tret += (dDiffuseLight - light_globalAmbient) * albedo + material_ambient * light_globalAmbient;\n#else\n\tret += albedo * dDiffuseLight;\n#endif\n#ifdef LIT_SPECULAR\n\tret += dSpecularLight;\n#endif\n#ifdef LIT_REFLECTIONS\n\tret += dReflection.rgb * dReflection.a;\n#endif\n#ifdef LIT_SHEEN\n\tfloat sheenScaling = 1.0 - max(max(sheenSpecularity.r, sheenSpecularity.g), sheenSpecularity.b) * 0.157;\n\tret = ret * sheenScaling + (sSpecularLight + sReflection.rgb) * sheenSpecularity;\n#endif\n#ifdef LIT_CLEARCOAT\n\tfloat clearCoatScaling = 1.0 - ccFresnel * clearcoatSpecularity;\n\tret = ret * clearCoatScaling + (ccSpecularLight + ccReflection) * clearcoatSpecularity;\n#endif\n\treturn ret;\n}\n`;\n\nvar cookieBlit2DPS$1 = `\n\tvarying vec2 uv0;\n\tuniform sampler2D blitTexture;\n\tvoid main(void) {\n\t\tgl_FragColor = texture2D(blitTexture, uv0);\n\t}\n`;\n\nvar cookieBlitCubePS$1 = `\n\tvarying vec2 uv0;\n\tuniform samplerCube blitTexture;\n\tuniform mat4 invViewProj;\n\tvoid main(void) {\n\t\tvec4 projPos = vec4(uv0 * 2.0 - 1.0, 0.5, 1.0);\n\t\tvec4 worldPos = invViewProj * projPos;\n\t\tgl_FragColor = textureCube(blitTexture, worldPos.xyz);\n\t}\n`;\n\nvar cookieBlitVS$1 = `\n\tattribute vec2 vertex_position;\n\tvarying vec2 uv0;\n\tvoid main(void) {\n\t\tgl_Position = vec4(vertex_position, 0.5, 1.0);\n\t\tuv0 = vertex_position.xy * 0.5 + 0.5;\n\t\t#ifndef WEBGPU\n\t\t\tuv0.y = 1.0 - uv0.y;\n\t\t#endif\n\t}\n`;\n\nvar cookiePS = `\nvec4 getCookie2D(sampler2D tex, mat4 transform, float intensity) {\n\tvec4 projPos = transform * vec4(vPositionW, 1.0);\n\tprojPos.xy /= projPos.w;\n\treturn mix(vec4(1.0), texture2D(tex, projPos.xy), intensity);\n}\nvec4 getCookie2DClip(sampler2D tex, mat4 transform, float intensity) {\n\tvec4 projPos = transform * vec4(vPositionW, 1.0);\n\tprojPos.xy /= projPos.w;\n\tif (projPos.x < 0.0 || projPos.x > 1.0 || projPos.y < 0.0 || projPos.y > 1.0 || projPos.z < 0.0) return vec4(0.0);\n\treturn mix(vec4(1.0), texture2D(tex, projPos.xy), intensity);\n}\nvec4 getCookie2DXform(sampler2D tex, mat4 transform, float intensity, vec4 cookieMatrix, vec2 cookieOffset) {\n\tvec4 projPos = transform * vec4(vPositionW, 1.0);\n\tprojPos.xy /= projPos.w;\n\tprojPos.xy += cookieOffset;\n\tvec2 uv = mat2(cookieMatrix) * (projPos.xy-vec2(0.5)) + vec2(0.5);\n\treturn mix(vec4(1.0), texture2D(tex, uv), intensity);\n}\nvec4 getCookie2DClipXform(sampler2D tex, mat4 transform, float intensity, vec4 cookieMatrix, vec2 cookieOffset) {\n\tvec4 projPos = transform * vec4(vPositionW, 1.0);\n\tprojPos.xy /= projPos.w;\n\tprojPos.xy += cookieOffset;\n\tif (projPos.x < 0.0 || projPos.x > 1.0 || projPos.y < 0.0 || projPos.y > 1.0 || projPos.z < 0.0) return vec4(0.0);\n\tvec2 uv = mat2(cookieMatrix) * (projPos.xy-vec2(0.5)) + vec2(0.5);\n\treturn mix(vec4(1.0), texture2D(tex, uv), intensity);\n}\nvec4 getCookieCube(samplerCube tex, mat4 transform, float intensity) {\n\treturn mix(vec4(1.0), textureCube(tex, dLightDirNormW * mat3(transform)), intensity);\n}\n`;\n\nvar cubeMapProjectPS$1 = `\n#if LIT_CUBEMAP_PROJECTION == BOX\n\tuniform vec3 envBoxMin;\n\tuniform vec3 envBoxMax;\n#endif\nvec3 cubeMapProject(vec3 nrdir) {\n\t#if LIT_CUBEMAP_PROJECTION == NONE\n\t\treturn cubeMapRotate(nrdir);\n\t#endif\n\t#if LIT_CUBEMAP_PROJECTION == BOX\n\t\tnrdir = cubeMapRotate(nrdir);\n\t\tvec3 rbmax = (envBoxMax - vPositionW) / nrdir;\n\t\tvec3 rbmin = (envBoxMin - vPositionW) / nrdir;\n\t\tvec3 rbminmax = mix(rbmin, rbmax, vec3(greaterThan(nrdir, vec3(0.0))));\n\t\tfloat fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);\n\t\tvec3 posonbox = vPositionW + nrdir * fa;\n\t\tvec3 envBoxPos = (envBoxMin + envBoxMax) * 0.5;\n\t\treturn normalize(posonbox - envBoxPos);\n\t#endif\n}\n`;\n\nvar cubeMapRotatePS$1 = `\n#ifdef CUBEMAP_ROTATION\nuniform mat3 cubeMapRotationMatrix;\n#endif\nvec3 cubeMapRotate(vec3 refDir) {\n#ifdef CUBEMAP_ROTATION\n\treturn refDir * cubeMapRotationMatrix;\n#else\n\treturn refDir;\n#endif\n}\n`;\n\nvar debugOutputPS$1 = `\n#ifdef DEBUG_ALBEDO_PASS\ngl_FragColor = vec4(gammaCorrectOutput(dAlbedo), 1.0);\n#endif\n#ifdef DEBUG_UV0_PASS\ngl_FragColor = vec4(litArgs_albedo , 1.0);\n#endif\n#ifdef DEBUG_WORLD_NORMAL_PASS\ngl_FragColor = vec4(litArgs_worldNormal * 0.5 + 0.5, 1.0);\n#endif\n#ifdef DEBUG_OPACITY_PASS\ngl_FragColor = vec4(vec3(litArgs_opacity) , 1.0);\n#endif\n#ifdef DEBUG_SPECULARITY_PASS\ngl_FragColor = vec4(litArgs_specularity, 1.0);\n#endif\n#ifdef DEBUG_GLOSS_PASS\ngl_FragColor = vec4(vec3(litArgs_gloss) , 1.0);\n#endif\n#ifdef DEBUG_METALNESS_PASS\ngl_FragColor = vec4(vec3(litArgs_metalness) , 1.0);\n#endif\n#ifdef DEBUG_AO_PASS\ngl_FragColor = vec4(vec3(litArgs_ao) , 1.0);\n#endif\n#ifdef DEBUG_EMISSION_PASS\ngl_FragColor = vec4(gammaCorrectOutput(litArgs_emission), 1.0);\n#endif\n`;\n\nvar debugProcessFrontendPS$1 = `\n#ifdef DEBUG_LIGHTING_PASS\nlitArgs_albedo = vec3(0.5);\n#endif\n#ifdef DEBUG_UV0_PASS\n#ifdef VARYING_VUV0\nlitArgs_albedo = vec3(vUv0, 0);\n#else\nlitArgs_albedo = vec3(0);\n#endif\n#endif\n`;\n\nvar decodePS$1 = `\n#ifndef _DECODE_INCLUDED_\n#define _DECODE_INCLUDED_\nvec3 decodeLinear(vec4 raw) {\n\treturn raw.rgb;\n}\nfloat decodeGamma(float raw) {\n\treturn pow(raw, 2.2);\n}\nvec3 decodeGamma(vec3 raw) {\n\treturn pow(raw, vec3(2.2));\n}\nvec3 decodeGamma(vec4 raw) {\n\treturn pow(raw.xyz, vec3(2.2));\n}\nvec3 decodeRGBM(vec4 raw) {\n\tvec3 color = (8.0 * raw.a) * raw.rgb;\n\treturn color * color;\n}\nvec3 decodeRGBP(vec4 raw) {\n\tvec3 color = raw.rgb * (-raw.a * 7.0 + 8.0);\n\treturn color * color;\n}\nvec3 decodeRGBE(vec4 raw) {\n\tif (raw.a == 0.0) {\n\t\treturn vec3(0.0, 0.0, 0.0);\n\t} else {\n\t\treturn raw.xyz * pow(2.0, raw.w * 255.0 - 128.0);\n\t}\n}\nvec4 passThrough(vec4 raw) {\n\treturn raw;\n}\nvec3 unpackNormalXYZ(vec4 nmap) {\n\treturn nmap.xyz * 2.0 - 1.0;\n}\nvec3 unpackNormalXY(vec4 nmap) {\n\tvec3 normal;\n\tnormal.xy = nmap.wy * 2.0 - 1.0;\n\tnormal.z = sqrt(1.0 - clamp(dot(normal.xy, normal.xy), 0.0, 1.0));\n\treturn normal;\n}\n#endif\n`;\n\nvar detailModesPS$1 = `\n#ifndef _DETAILMODES_INCLUDED_\n#define _DETAILMODES_INCLUDED_\nvec3 detailMode_mul(vec3 c1, vec3 c2) {\n\treturn c1 * c2;\n}\nvec3 detailMode_add(vec3 c1, vec3 c2) {\n\treturn c1 + c2;\n}\nvec3 detailMode_screen(vec3 c1, vec3 c2) {\n\treturn 1.0 - (1.0 - c1)*(1.0 - c2);\n}\nvec3 detailMode_overlay(vec3 c1, vec3 c2) {\n\treturn mix(1.0 - 2.0 * (1.0 - c1)*(1.0 - c2), 2.0 * c1 * c2, step(c1, vec3(0.5)));\n}\nvec3 detailMode_min(vec3 c1, vec3 c2) {\n\treturn min(c1, c2);\n}\nvec3 detailMode_max(vec3 c1, vec3 c2) {\n\treturn max(c1, c2);\n}\n#endif\n`;\n\nvar diffusePS$1 = `\nuniform vec3 material_diffuse;\n#ifdef STD_DIFFUSEDETAIL_TEXTURE\n\t#include \"detailModesPS\"\n#endif\nvoid getAlbedo() {\n\tdAlbedo = material_diffuse.rgb;\n\t#ifdef STD_DIFFUSE_TEXTURE\n\t\tvec3 albedoTexture = {STD_DIFFUSE_TEXTURE_DECODE}(texture2DBias({STD_DIFFUSE_TEXTURE_NAME}, {STD_DIFFUSE_TEXTURE_UV}, textureBias)).{STD_DIFFUSE_TEXTURE_CHANNEL};\n\t\t#ifdef STD_DIFFUSEDETAIL_TEXTURE\n\t\t\tvec3 albedoDetail = {STD_DIFFUSEDETAIL_TEXTURE_DECODE}(texture2DBias({STD_DIFFUSEDETAIL_TEXTURE_NAME}, {STD_DIFFUSEDETAIL_TEXTURE_UV}, textureBias)).{STD_DIFFUSEDETAIL_TEXTURE_CHANNEL};\n\t\t\talbedoTexture = detailMode_{STD_DIFFUSEDETAIL_DETAILMODE}(albedoTexture, albedoDetail);\n\t\t#endif\n\t\tdAlbedo *= albedoTexture;\n\t#endif\n\t#ifdef STD_DIFFUSE_VERTEX\n\t\tdAlbedo *= gammaCorrectInput(saturate(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}));\n\t#endif\n}\n`;\n\nvar emissivePS$1 = `\nuniform vec3 material_emissive;\nuniform float material_emissiveIntensity;\nvoid getEmission() {\n\tdEmission = material_emissive * material_emissiveIntensity;\n\t#ifdef STD_EMISSIVE_TEXTURE\n\tdEmission *= {STD_EMISSIVE_TEXTURE_DECODE}(texture2DBias({STD_EMISSIVE_TEXTURE_NAME}, {STD_EMISSIVE_TEXTURE_UV}, textureBias)).{STD_EMISSIVE_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_EMISSIVE_VERTEX\n\tdEmission *= gammaCorrectInput(saturate(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}));\n\t#endif\n}\n`;\n\nvar encodePS$1 = `\nvec4 encodeLinear(vec3 source) {\n\treturn vec4(source, 1.0);\n}\nvec4 encodeGamma(vec3 source) {\n\treturn vec4(pow(source + 0.0000001, vec3(1.0 / 2.2)), 1.0);\n}\nvec4 encodeRGBM(vec3 source) {\n\tvec4 result;\n\tresult.rgb = pow(source.rgb, vec3(0.5));\n\tresult.rgb *= 1.0 / 8.0;\n\tresult.a = saturate( max( max( result.r, result.g ), max( result.b, 1.0 / 255.0 ) ) );\n\tresult.a = ceil(result.a * 255.0) / 255.0;\n\tresult.rgb /= result.a;\n\treturn result;\n}\nvec4 encodeRGBP(vec3 source) {\n\tvec3 gamma = pow(source, vec3(0.5));\n\tfloat maxVal = min(8.0, max(1.0, max(gamma.x, max(gamma.y, gamma.z))));\n\tfloat v = 1.0 - ((maxVal - 1.0) / 7.0);\n\tv = ceil(v * 255.0) / 255.0;\n\treturn vec4(gamma / (-v * 7.0 + 8.0), v);\t\n}\nvec4 encodeRGBE(vec3 source) {\n\tfloat maxVal = max(source.x, max(source.y, source.z));\n\tif (maxVal < 1e-32) {\n\t\treturn vec4(0, 0, 0, 0);\n\t} else {\n\t\tfloat e = ceil(log2(maxVal));\n\t\treturn vec4(source / pow(2.0, e), (e + 128.0) / 255.0);\n\t}\n}\n`;\n\nvar endPS$1 = `\n\tgl_FragColor.rgb = combineColor(litArgs_albedo, litArgs_sheen_specularity, litArgs_clearcoat_specularity);\n\tgl_FragColor.rgb += litArgs_emission;\n\tgl_FragColor.rgb = addFog(gl_FragColor.rgb);\n\tgl_FragColor.rgb = toneMap(gl_FragColor.rgb);\n\tgl_FragColor.rgb = gammaCorrectOutput(gl_FragColor.rgb);\n`;\n\nvar envAtlasPS$1 = `\n#ifndef _ENVATLAS_INCLUDED_\n#define _ENVATLAS_INCLUDED_\nconst float atlasSize = 512.0;\nconst float seamSize = 1.0 / atlasSize;\nvec2 mapUv(vec2 uv, vec4 rect) {\n\treturn vec2(mix(rect.x + seamSize, rect.x + rect.z - seamSize, uv.x),\n\t\t\t\tmix(rect.y + seamSize, rect.y + rect.w - seamSize, uv.y));\n}\nvec2 mapRoughnessUv(vec2 uv, float level) {\n\tfloat t = 1.0 / exp2(level);\n\treturn mapUv(uv, vec4(0, 1.0 - t, t, t * 0.5));\n}\nvec2 mapShinyUv(vec2 uv, float level) {\n\tfloat t = 1.0 / exp2(level);\n\treturn mapUv(uv, vec4(1.0 - t, 1.0 - t, t, t * 0.5));\n}\n#endif\n`;\n\nvar envProcPS$1 = `\n#ifdef LIT_SKYBOX_INTENSITY\n\tuniform float skyboxIntensity;\n#endif\nvec3 processEnvironment(vec3 color) {\n\t#ifdef LIT_SKYBOX_INTENSITY\n\t\treturn color * skyboxIntensity;\n\t#else\n\t\treturn color;\n\t#endif\n}\n`;\n\nvar falloffInvSquaredPS$1 = `\nfloat getFalloffWindow(float lightRadius, vec3 lightDir) {\n\tfloat sqrDist = dot(lightDir, lightDir);\n\tfloat invRadius = 1.0 / lightRadius;\n\treturn square(saturate(1.0 - square(sqrDist * square(invRadius))));\n}\nfloat getFalloffInvSquared(float lightRadius, vec3 lightDir) {\n\tfloat sqrDist = dot(lightDir, lightDir);\n\tfloat falloff = 1.0 / (sqrDist + 1.0);\n\tfloat invRadius = 1.0 / lightRadius;\n\tfalloff *= 16.0;\n\tfalloff *= square(saturate(1.0 - square(sqrDist * square(invRadius))));\n\treturn falloff;\n}\n`;\n\nvar falloffLinearPS$1 = `\nfloat getFalloffLinear(float lightRadius, vec3 lightDir) {\n\tfloat d = length(lightDir);\n\treturn max(((lightRadius - d) / lightRadius), 0.0);\n}\n`;\n\nvar floatAsUintPS$1 = `\n#ifndef FLOAT_AS_UINT\n#define FLOAT_AS_UINT\nvec4 float2uint(float value) {\n\tuint intBits = floatBitsToUint(value);\n\treturn vec4(\n\t\tfloat((intBits >> 24u) & 0xFFu) / 255.0,\n\t\tfloat((intBits >> 16u) & 0xFFu) / 255.0,\n\t\tfloat((intBits >> 8u) & 0xFFu) / 255.0,\n\t\tfloat(intBits & 0xFFu) / 255.0\n\t);\n}\nfloat uint2float(vec4 value) {\n\tuint intBits = \n\t\t(uint(value.r * 255.0) << 24u) |\n\t\t(uint(value.g * 255.0) << 16u) |\n\t\t(uint(value.b * 255.0) << 8u) |\n\t\tuint(value.a * 255.0);\n\treturn uintBitsToFloat(intBits);\n}\nvec4 float2vec4(float value) {\n\t#if defined(CAPS_TEXTURE_FLOAT_RENDERABLE)\n\t\treturn vec4(value, 1.0, 1.0, 1.0);\n\t#else\n\t\treturn float2uint(value);\n\t#endif\n}\n#endif\n`;\n\nvar fogPS$1 = `\nfloat dBlendModeFogFactor = 1.0;\n#if (FOG != NONE)\n\tuniform vec3 fog_color;\n\t#if (FOG == LINEAR)\n\t\tuniform float fog_start;\n\t\tuniform float fog_end;\n\t#else\n\t\tuniform float fog_density;\n\t#endif\n#endif\nfloat getFogFactor() {\n\tfloat depth = gl_FragCoord.z / gl_FragCoord.w;\n\tfloat fogFactor = 0.0;\n\t#if (FOG == LINEAR)\n\t\tfogFactor = (fog_end - depth) / (fog_end - fog_start);\n\t#elif (FOG == EXP)\n\t\tfogFactor = exp(-depth * fog_density);\n\t#elif (FOG == EXP2)\n\t\tfogFactor = exp(-depth * depth * fog_density * fog_density);\n\t#endif\n\treturn clamp(fogFactor, 0.0, 1.0);\n}\nvec3 addFog(vec3 color) {\n\t#if (FOG != NONE)\n\t\treturn mix(fog_color * dBlendModeFogFactor, color, getFogFactor());\n\t#endif\n\treturn color;\n}\n`;\n\nvar fresnelSchlickPS$1 = `\nvec3 getFresnel(\n\t\tfloat cosTheta, \n\t\tfloat gloss, \n\t\tvec3 specularity\n#if defined(LIT_IRIDESCENCE)\n\t\t, vec3 iridescenceFresnel, \n\t\tfloat iridescenceIntensity\n#endif\n\t) {\n\tfloat fresnel = pow(1.0 - saturate(cosTheta), 5.0);\n\tfloat glossSq = gloss * gloss;\n\tvec3 ret = specularity + (max(vec3(glossSq), specularity) - specularity) * fresnel;\n#if defined(LIT_IRIDESCENCE)\n\treturn mix(ret, iridescenceFresnel, iridescenceIntensity);\n#else\n\treturn ret;\n#endif\t\n}\nfloat getFresnelCC(float cosTheta) {\n\tfloat fresnel = pow(1.0 - saturate(cosTheta), 5.0);\n\treturn 0.04 + (1.0 - 0.04) * fresnel;\n}\n`;\n\nvar fullscreenQuadVS$1 = `\nattribute vec2 vertex_position;\nvarying vec2 vUv0;\nvoid main(void)\n{\n\tgl_Position = vec4(vertex_position, 0.5, 1.0);\n\tvUv0 = vertex_position.xy * 0.5 + 0.5;\n}\n`;\n\nvar gammaPS$1 = `\n#include \"decodePS\"\n#if (GAMMA == SRGB)\n\tfloat gammaCorrectInput(float color) {\n\t\treturn decodeGamma(color);\n\t}\n\tvec3 gammaCorrectInput(vec3 color) {\n\t\treturn decodeGamma(color);\n\t}\n\tvec4 gammaCorrectInput(vec4 color) {\n\t\treturn vec4(decodeGamma(color.xyz), color.w);\n\t}\n\tvec3 gammaCorrectOutput(vec3 color) {\n\t\treturn pow(color + 0.0000001, vec3(1.0 / 2.2));\n\t}\n#else\n\tfloat gammaCorrectInput(float color) {\n\t\treturn color;\n\t}\n\tvec3 gammaCorrectInput(vec3 color) {\n\t\treturn color;\n\t}\n\tvec4 gammaCorrectInput(vec4 color) {\n\t\treturn color;\n\t}\n\tvec3 gammaCorrectOutput(vec3 color) {\n\t\treturn color;\n\t}\n#endif\n`;\n\nvar glossPS$1 = `\n#ifdef STD_GLOSS_CONSTANT\nuniform float material_gloss;\n#endif\nvoid getGlossiness() {\n\tdGlossiness = 1.0;\n\t#ifdef STD_GLOSS_CONSTANT\n\tdGlossiness *= material_gloss;\n\t#endif\n\t#ifdef STD_GLOSS_TEXTURE\n\tdGlossiness *= texture2DBias({STD_GLOSS_TEXTURE_NAME}, {STD_GLOSS_TEXTURE_UV}, textureBias).{STD_GLOSS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_GLOSS_VERTEX\n\tdGlossiness *= saturate(vVertexColor.{STD_GLOSS_VERTEX_CHANNEL});\n\t#endif\n\t#ifdef STD_GLOSS_INVERT\n\tdGlossiness = 1.0 - dGlossiness;\n\t#endif\n\tdGlossiness += 0.0000001;\n}\n`;\n\nvar gsplatCenterVS$1 = `\nuniform mat4 matrix_model;\nuniform mat4 matrix_view;\n#ifndef GSPLAT_CENTER_NOPROJ\n\tuniform mat4 matrix_projection;\n#endif\nbool initCenter(vec3 modelCenter, out SplatCenter center) {\n\tmat4 modelView = matrix_view * matrix_model;\n\tvec4 centerView = modelView * vec4(modelCenter, 1.0);\n\t#ifndef GSPLAT_CENTER_NOPROJ\n\t\tif (centerView.z > 0.0) {\n\t\t\treturn false;\n\t\t}\n\t\tvec4 centerProj = matrix_projection * centerView;\n\t\t#if WEBGPU\n\t\t\tcenterProj.z = clamp(centerProj.z, 0, abs(centerProj.w));\n\t\t#else\n\t\t\tcenterProj.z = clamp(centerProj.z, -abs(centerProj.w), abs(centerProj.w));\n\t\t#endif\n\t\tcenter.proj = centerProj;\n\t\tcenter.projMat00 = matrix_projection[0][0];\n\t#endif\n\tcenter.view = centerView.xyz / centerView.w;\n\tcenter.modelView = modelView;\n\treturn true;\n}\n`;\n\nvar gsplatColorVS$1 = `\nuniform mediump sampler2D splatColor;\nvec4 readColor(in SplatSource source) {\n\treturn texelFetch(splatColor, source.uv, 0);\n}\n`;\n\nvar gsplatCommonVS$1 = `\n#include \"gsplatStructsVS\"\n#include \"gsplatEvalSHVS\"\n#include \"gsplatQuatToMat3VS\"\n#include \"gsplatSourceFormatVS\"\n#include \"gsplatSourceVS\"\n#include \"gsplatCenterVS\"\n#include \"gsplatCornerVS\"\n#include \"gsplatOutputVS\"\nvoid clipCorner(inout SplatCorner corner, float alpha) {\n\tfloat clip = min(1.0, sqrt(-log(1.0 / 255.0 / alpha)) / 2.0);\n\tcorner.offset *= clip;\n\tcorner.uv *= clip;\n}\n`;\n\nvar gsplatCompressedDataVS$1 = `\nuniform highp usampler2D packedTexture;\nuniform highp sampler2D chunkTexture;\nvec4 chunkDataA;\nvec4 chunkDataB;\nvec4 chunkDataC;\nvec4 chunkDataD;\nvec4 chunkDataE;\nuvec4 packedData;\nvec3 unpack111011(uint bits) {\n\treturn vec3(\n\t\tfloat(bits >> 21u) / 2047.0,\n\t\tfloat((bits >> 11u) & 0x3ffu) / 1023.0,\n\t\tfloat(bits & 0x7ffu) / 2047.0\n\t);\n}\nvec4 unpack8888(uint bits) {\n\treturn vec4(\n\t\tfloat(bits >> 24u) / 255.0,\n\t\tfloat((bits >> 16u) & 0xffu) / 255.0,\n\t\tfloat((bits >> 8u) & 0xffu) / 255.0,\n\t\tfloat(bits & 0xffu) / 255.0\n\t);\n}\nconst float norm = 1.0 / (sqrt(2.0) * 0.5);\nvec4 unpackRotation(uint bits) {\n\tfloat a = (float((bits >> 20u) & 0x3ffu) / 1023.0 - 0.5) * norm;\n\tfloat b = (float((bits >> 10u) & 0x3ffu) / 1023.0 - 0.5) * norm;\n\tfloat c = (float(bits & 0x3ffu) / 1023.0 - 0.5) * norm;\n\tfloat m = sqrt(1.0 - (a * a + b * b + c * c));\n\tuint mode = bits >> 30u;\n\tif (mode == 0u) return vec4(m, a, b, c);\n\tif (mode == 1u) return vec4(a, m, b, c);\n\tif (mode == 2u) return vec4(a, b, m, c);\n\treturn vec4(a, b, c, m);\n}\nvec3 readCenter(SplatSource source) {\n\tuint w = uint(textureSize(chunkTexture, 0).x) / 5u;\n\tuint chunkId = source.id / 256u;\n\tivec2 chunkUV = ivec2((chunkId % w) * 5u, chunkId / w);\n\tchunkDataA = texelFetch(chunkTexture, chunkUV, 0);\n\tchunkDataB = texelFetch(chunkTexture, chunkUV + ivec2(1, 0), 0);\n\tchunkDataC = texelFetch(chunkTexture, chunkUV + ivec2(2, 0), 0);\n\tchunkDataD = texelFetch(chunkTexture, chunkUV + ivec2(3, 0), 0);\n\tchunkDataE = texelFetch(chunkTexture, chunkUV + ivec2(4, 0), 0);\n\tpackedData = texelFetch(packedTexture, source.uv, 0);\n\treturn mix(chunkDataA.xyz, vec3(chunkDataA.w, chunkDataB.xy), unpack111011(packedData.x));\n}\nvec4 readColor(in SplatSource source) {\n\tvec4 r = unpack8888(packedData.w);\n\treturn vec4(mix(chunkDataD.xyz, vec3(chunkDataD.w, chunkDataE.xy), r.rgb), r.w);\n}\nvec4 getRotation() {\n\treturn unpackRotation(packedData.y);\n}\nvec3 getScale() {\n\treturn exp(mix(vec3(chunkDataB.zw, chunkDataC.x), chunkDataC.yzw, unpack111011(packedData.z)));\n}\nvoid readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) {\n\tmat3 rot = quatToMat3(getRotation());\n\tvec3 scale = getScale();\n\tmat3 M = transpose(mat3(\n\t\tscale.x * rot[0],\n\t\tscale.y * rot[1],\n\t\tscale.z * rot[2]\n\t));\n\tcovA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));\n\tcovB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));\n}\n`;\n\nvar gsplatCompressedSHVS$1 = `\n#if SH_BANDS > 0\nuniform highp usampler2D shTexture0;\nuniform highp usampler2D shTexture1;\nuniform highp usampler2D shTexture2;\nvec4 unpack8888s(in uint bits) {\n\treturn vec4((uvec4(bits) >> uvec4(0u, 8u, 16u, 24u)) & 0xffu) * (8.0 / 255.0) - 4.0;\n}\nvoid readSHData(in SplatSource source, out vec3 sh[15], out float scale) {\n\tuvec4 shData0 = texelFetch(shTexture0, source.uv, 0);\n\tuvec4 shData1 = texelFetch(shTexture1, source.uv, 0);\n\tuvec4 shData2 = texelFetch(shTexture2, source.uv, 0);\n\tvec4 r0 = unpack8888s(shData0.x);\n\tvec4 r1 = unpack8888s(shData0.y);\n\tvec4 r2 = unpack8888s(shData0.z);\n\tvec4 r3 = unpack8888s(shData0.w);\n\tvec4 g0 = unpack8888s(shData1.x);\n\tvec4 g1 = unpack8888s(shData1.y);\n\tvec4 g2 = unpack8888s(shData1.z);\n\tvec4 g3 = unpack8888s(shData1.w);\n\tvec4 b0 = unpack8888s(shData2.x);\n\tvec4 b1 = unpack8888s(shData2.y);\n\tvec4 b2 = unpack8888s(shData2.z);\n\tvec4 b3 = unpack8888s(shData2.w);\n\tsh[0] =  vec3(r0.x, g0.x, b0.x);\n\tsh[1] =  vec3(r0.y, g0.y, b0.y);\n\tsh[2] =  vec3(r0.z, g0.z, b0.z);\n\tsh[3] =  vec3(r0.w, g0.w, b0.w);\n\tsh[4] =  vec3(r1.x, g1.x, b1.x);\n\tsh[5] =  vec3(r1.y, g1.y, b1.y);\n\tsh[6] =  vec3(r1.z, g1.z, b1.z);\n\tsh[7] =  vec3(r1.w, g1.w, b1.w);\n\tsh[8] =  vec3(r2.x, g2.x, b2.x);\n\tsh[9] =  vec3(r2.y, g2.y, b2.y);\n\tsh[10] = vec3(r2.z, g2.z, b2.z);\n\tsh[11] = vec3(r2.w, g2.w, b2.w);\n\tsh[12] = vec3(r3.x, g3.x, b3.x);\n\tsh[13] = vec3(r3.y, g3.y, b3.y);\n\tsh[14] = vec3(r3.z, g3.z, b3.z);\n\tscale = 1.0;\n}\n#endif\n`;\n\nvar gsplatEvalSHVS$1 = `\n\t#if SH_BANDS == 1\n\t\t#define SH_COEFFS 3\n\t#elif SH_BANDS == 2\n\t\t#define SH_COEFFS 8\n\t#elif SH_BANDS == 3\n\t\t#define SH_COEFFS 15\n\t#else\n\t\t#define SH_COEFFS 0\n\t#endif\n\t#if SH_BANDS > 0\n\tconst float SH_C1 = 0.4886025119029199f;\n\t#if SH_BANDS > 1\n\t\tconst float SH_C2_0 = 1.0925484305920792f;\n\t\tconst float SH_C2_1 = -1.0925484305920792f;\n\t\tconst float SH_C2_2 = 0.31539156525252005f;\n\t\tconst float SH_C2_3 = -1.0925484305920792f;\n\t\tconst float SH_C2_4 = 0.5462742152960396f;\n\t#endif\n\t#if SH_BANDS > 2\n\t\tconst float SH_C3_0 = -0.5900435899266435f;\n\t\tconst float SH_C3_1 = 2.890611442640554f;\n\t\tconst float SH_C3_2 = -0.4570457994644658f;\n\t\tconst float SH_C3_3 = 0.3731763325901154f;\n\t\tconst float SH_C3_4 = -0.4570457994644658f;\n\t\tconst float SH_C3_5 = 1.445305721320277f;\n\t\tconst float SH_C3_6 = -0.5900435899266435f;\n\t#endif\n\tvec3 evalSH(in vec3 sh[SH_COEFFS], in vec3 dir) {\n\t\tfloat x = dir.x;\n\t\tfloat y = dir.y;\n\t\tfloat z = dir.z;\n\t\tvec3 result = SH_C1 * (-sh[0] * y + sh[1] * z - sh[2] * x);\n\t\t#if SH_BANDS > 1\n\t\t\tfloat xx = x * x;\n\t\t\tfloat yy = y * y;\n\t\t\tfloat zz = z * z;\n\t\t\tfloat xy = x * y;\n\t\t\tfloat yz = y * z;\n\t\t\tfloat xz = x * z;\n\t\t\tresult +=\n\t\t\t\tsh[3] * (SH_C2_0 * xy) +\n\t\t\t\tsh[4] * (SH_C2_1 * yz) +\n\t\t\t\tsh[5] * (SH_C2_2 * (2.0 * zz - xx - yy)) +\n\t\t\t\tsh[6] * (SH_C2_3 * xz) +\n\t\t\t\tsh[7] * (SH_C2_4 * (xx - yy));\n\t\t#endif\n\t\t#if SH_BANDS > 2\n\t\t\tresult +=\n\t\t\t\tsh[8]  * (SH_C3_0 * y * (3.0 * xx - yy)) +\n\t\t\t\tsh[9]  * (SH_C3_1 * xy * z) +\n\t\t\t\tsh[10] * (SH_C3_2 * y * (4.0 * zz - xx - yy)) +\n\t\t\t\tsh[11] * (SH_C3_3 * z * (2.0 * zz - 3.0 * xx - 3.0 * yy)) +\n\t\t\t\tsh[12] * (SH_C3_4 * x * (4.0 * zz - xx - yy)) +\n\t\t\t\tsh[13] * (SH_C3_5 * z * (xx - yy)) +\n\t\t\t\tsh[14] * (SH_C3_6 * x * (xx - 3.0 * yy));\n\t\t#endif\n\t\treturn result;\n\t}\n\t#endif\n`;\n\nvar gsplatQuatToMat3VS$1 = `\nmat3 quatToMat3(vec4 R) {\n\tvec4 R2 = R + R;\n\tfloat X = R2.x * R.w;\n\tvec4 Y  = R2.y * R;\n\tvec4 Z  = R2.z * R;\n\tfloat W = R2.w * R.w;\n\treturn mat3(\n\t\t1.0 - Z.z - W,\n\t\t\t  Y.z + X,\n\t\t\t  Y.w - Z.x,\n\t\t\t  Y.z - X,\n\t\t1.0 - Y.y - W,\n\t\t\t  Z.w + Y.x,\n\t\t\t  Y.w + Z.x,\n\t\t\t  Z.w - Y.x,\n\t\t1.0 - Y.y - Z.z\n\t);\n}\n`;\n\nvar gsplatSogsColorVS$1 = `\nuniform highp sampler2D packedSh0;\nuniform float sh0_mins;\nuniform float sh0_maxs;\nfloat SH_C0 = 0.28209479177387814;\nvec4 readColor(in SplatSource source) {\n\tvec3 clr = mix(vec3(sh0_mins), vec3(sh0_maxs), unpack111110(pack8888(texelFetch(packedSh0, source.uv, 0))));\n\tfloat alpha = float(packedSample.z & 0xffu) / 255.0;\n\treturn vec4(vec3(0.5) + clr.xyz * SH_C0, alpha);\n}\n`;\n\nvar gsplatSogsDataVS$1 = `\n#include \"gsplatPackingPS\"\nuniform highp usampler2D packedTexture;\nuniform vec3 means_mins;\nuniform vec3 means_maxs;\nuniform float scales_mins;\nuniform float scales_maxs;\nuvec4 packedSample;\nvec3 readCenter(SplatSource source) {\n\tpackedSample = texelFetch(packedTexture, source.uv, 0);\n\tvec3 l = unpack8888(packedSample.x).xyz;\n\tvec3 u = unpack8888(packedSample.y).xyz;\n\tvec3 n = (l * 255.0 + u * 255.0 * 256.0) / 65535.0;\n\tvec3 v = mix(means_mins, means_maxs, n);\n\treturn sign(v) * (exp(abs(v)) - 1.0);\n}\nconst float norm = 2.0 / sqrt(2.0);\nvoid readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) {\n\tvec3 qdata = unpack8888(packedSample.z).xyz;\n\tvec3 sdata = unpack101010(packedSample.w >> 2u);\n\tuint qmode = packedSample.w & 0x3u;\n\tvec3 abc = (qdata - 0.5) * norm;\n\tfloat d = sqrt(max(0.0, 1.0 - dot(abc, abc)));\n\tvec4 quat = (qmode == 0u) ? vec4(d, abc) :\n\t\t\t\t((qmode == 1u) ? vec4(abc.x, d, abc.yz) :\n\t\t\t\t((qmode == 2u) ? vec4(abc.xy, d, abc.z) : vec4(abc, d)));\n\tmat3 rot = quatToMat3(quat);\n\tvec3 scale = exp(mix(vec3(scales_mins), vec3(scales_maxs), sdata));\n\tmat3 M = transpose(mat3(\n\t\tscale.x * rot[0],\n\t\tscale.y * rot[1],\n\t\tscale.z * rot[2]\n\t));\n\tcovA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));\n\tcovB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));\n}\n`;\n\nvar gsplatSogsSHVS$1 = `\nuniform highp sampler2D packedShN;\nuniform float shN_mins;\nuniform float shN_maxs;\nvoid readSHData(in SplatSource source, out vec3 sh[SH_COEFFS], out float scale) {\n\tivec2 t = ivec2(packedSample.xy & 255u);\n\tint n = t.x + t.y * 256;\n\tint u = (n % 64) * SH_COEFFS;\n\tint v = n / 64;\n\tfor (int i = 0; i < SH_COEFFS; i++) {\n\t\tsh[i] = mix(vec3(shN_mins), vec3(shN_maxs), unpack111110(pack8888(texelFetch(packedShN, ivec2(u + i, v), 0))));\n\t}\n\tscale = 1.0;\n}\n`;\n\nvar gsplatSourceFormatVS$1 = `\n#if defined(GSPLAT_WORKBUFFER_DATA)\n\t#include \"gsplatWorkBufferVS\"\n#elif GSPLAT_COMPRESSED_DATA == true\n\t#include \"gsplatCompressedDataVS\"\n\t#if SH_COEFFS > 0\n\t\t#include \"gsplatCompressedSHVS\"\n\t#endif\n#elif GSPLAT_SOGS_DATA == true\n\t#include \"gsplatSogsDataVS\"\n\t#include \"gsplatSogsColorVS\"\n\t#if SH_COEFFS > 0\n\t\t#include \"gsplatSogsSHVS\"\n\t#endif\n#else\n\t#include \"gsplatDataVS\"\n\t#include \"gsplatColorVS\"\n\t#if SH_COEFFS > 0\n\t\t#include \"gsplatSHVS\"\n\t#endif\n#endif\n`;\n\nvar gsplatStructsVS$1 = `\nstruct SplatSource {\n\tuint order;\n\tuint id;\n\tivec2 uv;\n\tvec2 cornerUV;\n};\nstruct SplatCenter {\n\tvec3 view;\n\tvec4 proj;\n\tmat4 modelView;\n\tfloat projMat00;\n};\nstruct SplatCorner {\n\tvec2 offset;\n\tvec2 uv;\n\t#if GSPLAT_AA\n\t\tfloat aaFactor;\n\t#endif\n\tvec2 v;\n\tfloat dlen;\n};\n`;\n\nvar gsplatCornerVS$1 = `\nuniform vec2 viewport;\nuniform vec4 camera_params;\nbool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corner, vec3 covA, vec3 covB) {\n\tmat3 Vrk = mat3(\n\t\tcovA.x, covA.y, covA.z, \n\t\tcovA.y, covB.x, covB.y,\n\t\tcovA.z, covB.y, covB.z\n\t);\n\tfloat focal = viewport.x * center.projMat00;\n\tvec3 v = camera_params.w == 1.0 ? vec3(0.0, 0.0, 1.0) : center.view.xyz;\n\tfloat J1 = focal / v.z;\n\tvec2 J2 = -J1 / v.z * v.xy;\n\tmat3 J = mat3(\n\t\tJ1, 0.0, J2.x, \n\t\t0.0, J1, J2.y, \n\t\t0.0, 0.0, 0.0\n\t);\n\tmat3 W = transpose(mat3(center.modelView));\n\tmat3 T = W * J;\n\tmat3 cov = transpose(T) * Vrk * T;\n\t#if GSPLAT_AA\n\t\tfloat detOrig = cov[0][0] * cov[1][1] - cov[0][1] * cov[0][1];\n\t\tfloat detBlur = (cov[0][0] + 0.3) * (cov[1][1] + 0.3) - cov[0][1] * cov[0][1];\n\t\tcorner.aaFactor = sqrt(max(detOrig / detBlur, 0.0));\n\t#endif\n\tfloat diagonal1 = cov[0][0] + 0.3;\n\tfloat offDiagonal = cov[0][1];\n\tfloat diagonal2 = cov[1][1] + 0.3;\n\tfloat mid = 0.5 * (diagonal1 + diagonal2);\n\tfloat radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal));\n\tfloat lambda1 = mid + radius;\n\tfloat lambda2 = max(mid - radius, 0.1);\n\tfloat vmin = min(1024.0, min(viewport.x, viewport.y));\n\tfloat l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);\n\tfloat l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);\n\tif (l1 < 2.0 && l2 < 2.0) {\n\t\treturn false;\n\t}\n\tvec2 c = center.proj.ww / viewport;\n\tif (any(greaterThan(abs(center.proj.xy) - vec2(max(l1, l2)) * c, center.proj.ww))) {\n\t\treturn false;\n\t}\n\tvec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1));\n\tvec2 v1 = l1 * diagonalVector;\n\tvec2 v2 = l2 * vec2(diagonalVector.y, -diagonalVector.x);\n\tcorner.offset = (source.cornerUV.x * v1 + source.cornerUV.y * v2) * c;\n\tcorner.uv = source.cornerUV;\n\treturn true;\n}\nbool initCorner(SplatSource source, SplatCenter center, out SplatCorner corner) {\n\tvec3 covA, covB;\n\treadCovariance(source, covA, covB);\n\treturn initCornerCov(source, center, corner, covA, covB);\n}\n`;\n\nvar gsplatDataVS$1 = `\nuniform highp usampler2D transformA;\nuniform highp sampler2D transformB;\nuint tAw;\nvec3 readCenter(SplatSource source) {\n\tuvec4 tA = texelFetch(transformA, source.uv, 0);\n\ttAw = tA.w;\n\treturn uintBitsToFloat(tA.xyz);\n}\nvec4 unpackRotation(vec3 packed) {\n\treturn vec4(packed.xyz, sqrt(max(0.0, 1.0 - dot(packed, packed))));\n}\nvoid readCovariance(in SplatSource source, out vec3 covA, out vec3 covB) {\n\tvec4 tB = texelFetch(transformB, source.uv, 0);\n\tmat3 rot = quatToMat3(unpackRotation(vec3(unpackHalf2x16(tAw), tB.w)).wxyz);\n\tvec3 scale = tB.xyz;\n\t\n\tmat3 M = transpose(mat3(\n\t\tscale.x * rot[0],\n\t\tscale.y * rot[1],\n\t\tscale.z * rot[2]\n\t));\n\tcovA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));\n\tcovB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));\n}\n`;\n\nvar gsplatOutputVS$1 = `\n#include \"tonemappingPS\"\n#include \"decodePS\"\n#include \"gammaPS\"\nvec3 prepareOutputFromGamma(vec3 gammaColor) {\n\t#if TONEMAP == NONE\n\t\t#if GAMMA == NONE\n\t\t\treturn decodeGamma(gammaColor);\n\t\t#else\n\t\t\treturn gammaColor;\n\t\t#endif\n\t#else\n\t\treturn gammaCorrectOutput(toneMap(decodeGamma(gammaColor)));\n\t#endif\n}\n`;\n\nvar gsplatPS$1 = `\n#ifndef DITHER_NONE\n\t#include \"bayerPS\"\n\t#include \"opacityDitherPS\"\n\tvarying float id;\n#endif\n#ifdef PICK_PASS\n\t#include \"pickPS\"\n#endif\n#if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)\n\tuniform float alphaClip;\n#endif\n#ifdef PREPASS_PASS\n\tvarying float vLinearDepth;\n\t#include \"floatAsUintPS\"\n#endif\nconst float  EXP_A\t  = 12102203.0;\nconst int\tEXP_BC_RMS = 1064866808;\nfloat fastExp(float x) {\n\tint i = int(EXP_A * x) + EXP_BC_RMS;\n\treturn intBitsToFloat(i);\n}\nvarying mediump vec2 gaussianUV;\nvarying mediump vec4 gaussianColor;\nvoid main(void) {\n\tmediump float A = dot(gaussianUV, gaussianUV);\n\tif (A > 1.0) {\n\t\tdiscard;\n\t}\n\tmediump float alpha = fastExp(-A * 4.0) * gaussianColor.a;\n\t#if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)\n\t\tif (alpha < alphaClip) {\n\t\t\tdiscard;\n\t\t}\n\t#endif\n\t#ifdef PICK_PASS\n\t\tgl_FragColor = getPickOutput();\n\t#elif SHADOW_PASS\n\t\tgl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n\t#elif PREPASS_PASS\n\t\tgl_FragColor = float2vec4(vLinearDepth);\n\t#else\n\t\tif (alpha < 1.0 / 255.0) {\n\t\t\tdiscard;\n\t\t}\n\t\t#ifndef DITHER_NONE\n\t\t\topacityDither(alpha, id * 0.013);\n\t\t#endif\n\t\tgl_FragColor = vec4(gaussianColor.xyz * alpha, alpha);\n\t#endif\n}\n`;\n\nvar gsplatSHVS$1 = `\n#if SH_BANDS > 0\nvec3 unpack111011s(uint bits) {\n\treturn vec3((uvec3(bits) >> uvec3(21u, 11u, 0u)) & uvec3(0x7ffu, 0x3ffu, 0x7ffu)) / vec3(2047.0, 1023.0, 2047.0) * 2.0 - 1.0;\n}\nvoid fetchScale(in uvec4 t, out float scale, out vec3 a, out vec3 b, out vec3 c) {\n\tscale = uintBitsToFloat(t.x);\n\ta = unpack111011s(t.y);\n\tb = unpack111011s(t.z);\n\tc = unpack111011s(t.w);\n}\nvoid fetch(in uvec4 t, out vec3 a, out vec3 b, out vec3 c, out vec3 d) {\n\ta = unpack111011s(t.x);\n\tb = unpack111011s(t.y);\n\tc = unpack111011s(t.z);\n\td = unpack111011s(t.w);\n}\nvoid fetch(in uint t, out vec3 a) {\n\ta = unpack111011s(t);\n}\n#if SH_BANDS == 1\n\tuniform highp usampler2D splatSH_1to3;\n\tvoid readSHData(in SplatSource source, out vec3 sh[3], out float scale) {\n\t\tfetchScale(texelFetch(splatSH_1to3, source.uv, 0), scale, sh[0], sh[1], sh[2]);\n\t}\n#elif SH_BANDS == 2\n\tuniform highp usampler2D splatSH_1to3;\n\tuniform highp usampler2D splatSH_4to7;\n\tuniform highp usampler2D splatSH_8to11;\n\tvoid readSHData(in SplatSource source, out vec3 sh[8], out float scale) {\n\t\tfetchScale(texelFetch(splatSH_1to3, source.uv, 0), scale, sh[0], sh[1], sh[2]);\n\t\tfetch(texelFetch(splatSH_4to7, source.uv, 0), sh[3], sh[4], sh[5], sh[6]);\n\t\tfetch(texelFetch(splatSH_8to11, source.uv, 0).x, sh[7]);\n\t}\n#else\n\tuniform highp usampler2D splatSH_1to3;\n\tuniform highp usampler2D splatSH_4to7;\n\tuniform highp usampler2D splatSH_8to11;\n\tuniform highp usampler2D splatSH_12to15;\n\tvoid readSHData(in SplatSource source, out vec3 sh[15], out float scale) {\n\t\tfetchScale(texelFetch(splatSH_1to3, source.uv, 0), scale, sh[0], sh[1], sh[2]);\n\t\tfetch(texelFetch(splatSH_4to7, source.uv, 0), sh[3], sh[4], sh[5], sh[6]);\n\t\tfetch(texelFetch(splatSH_8to11, source.uv, 0), sh[7], sh[8], sh[9], sh[10]);\n\t\tfetch(texelFetch(splatSH_12to15, source.uv, 0), sh[11], sh[12], sh[13], sh[14]);\n\t}\n#endif\n#endif\n`;\n\nvar gsplatSourceVS$1 = `\nattribute vec3 vertex_position;\nattribute uint vertex_id_attrib;\nuniform uint numSplats;\nuniform highp usampler2D splatOrder;\nbool initSource(out SplatSource source) {\n\tuint w = uint(textureSize(splatOrder, 0).x);\n\tsource.order = vertex_id_attrib + uint(vertex_position.z);\n\tif (source.order >= numSplats) {\n\t\treturn false;\n\t}\n\tivec2 orderUV = ivec2(source.order % w, source.order / w);\n\tsource.id = texelFetch(splatOrder, orderUV, 0).r;\n\tsource.uv = ivec2(source.id % w, source.id / w);\n\tsource.cornerUV = vertex_position.xy;\n\treturn true;\n}\n`;\n\nvar gsplatVS$1 = `\n#include \"gsplatCommonVS\"\nvarying mediump vec2 gaussianUV;\nvarying mediump vec4 gaussianColor;\n#ifndef DITHER_NONE\n\tvarying float id;\n#endif\nmediump vec4 discardVec = vec4(0.0, 0.0, 2.0, 1.0);\n#ifdef PREPASS_PASS\n\tvarying float vLinearDepth;\n#endif\nvoid main(void) {\n\tSplatSource source;\n\tif (!initSource(source)) {\n\t\tgl_Position = discardVec;\n\t\treturn;\n\t}\n\tvec3 modelCenter = readCenter(source);\n\tSplatCenter center;\n\tif (!initCenter(modelCenter, center)) {\n\t\tgl_Position = discardVec;\n\t\treturn;\n\t}\n\tSplatCorner corner;\n\tif (!initCorner(source, center, corner)) {\n\t\tgl_Position = discardVec;\n\t\treturn;\n\t}\n\tvec4 clr = readColor(source);\n\t#if GSPLAT_AA\n\t\tclr.a *= corner.aaFactor;\n\t#endif\n\t#if SH_BANDS > 0\n\t\tvec3 dir = normalize(center.view * mat3(center.modelView));\n\t\tvec3 sh[SH_COEFFS];\n\t\tfloat scale;\n\t\treadSHData(source, sh, scale);\n\t\tclr.xyz += evalSH(sh, dir) * scale;\n\t#endif\n\tclipCorner(corner, clr.w);\n\tgl_Position = center.proj + vec4(corner.offset, 0, 0);\n\tgaussianUV = corner.uv;\n\tgaussianColor = vec4(prepareOutputFromGamma(max(clr.xyz, 0.0)), clr.w);\n\t#ifndef DITHER_NONE\n\t\tid = float(source.id);\n\t#endif\n\t#ifdef PREPASS_PASS\n\t\tvLinearDepth = -center.view.z;\n\t#endif\n}\n`;\n\nvar gsplatWorkBufferVS$1 = `\nuniform highp sampler2D center;\nuniform highp sampler2D covA;\nuniform highp sampler2D covB;\nuniform mediump sampler2D splatColor;\nvec3 readCenter(SplatSource source) {\n\treturn texelFetch(center, source.uv, 0).xyz;\n}\nvoid readCovariance(in SplatSource source, out vec3 cov_A, out vec3 cov_B) {\n\tcov_A = texelFetch(covA, source.uv, 0).xyz;\n\tcov_B = texelFetch(covB, source.uv, 0).xyz;\n}\nvec4 readColor(in SplatSource source) {\n\treturn texelFetch(splatColor, source.uv, 0);\n}\n`;\n\nvar quadVS$1 = `\n\tattribute vec2 aPosition;\n\tvarying vec2 uv0;\n\tvoid main(void)\n\t{\n\t\tgl_Position = vec4(aPosition, 0.0, 1.0);\n\t\tuv0 = getImageEffectUV((aPosition.xy + 1.0) * 0.5);\n\t}\n`;\n\nvar immediateLinePS$1 = `\n\t\t#include \"gammaPS\"\n\t\tvarying vec4 color;\n\t\tvoid main(void) {\n\t\t\tgl_FragColor = vec4(gammaCorrectOutput(decodeGamma(color.rgb)), color.a);\n\t\t}\n`;\n\nvar immediateLineVS$1 = `\n\tattribute vec4 vertex_position;\n\tattribute vec4 vertex_color;\n\tuniform mat4 matrix_model;\n\tuniform mat4 matrix_viewProjection;\n\tvarying vec4 color;\n\tvoid main(void) {\n\t\tcolor = vertex_color;\n\t\tgl_Position = matrix_viewProjection * matrix_model * vertex_position;\n\t}\n`;\n\nvar iridescenceDiffractionPS$1 = `\nuniform float material_iridescenceRefractionIndex;\nfloat iridescence_iorToFresnel(float transmittedIor, float incidentIor) {\n\treturn pow((transmittedIor - incidentIor) / (transmittedIor + incidentIor), 2.0);\n}\nvec3 iridescence_iorToFresnel(vec3 transmittedIor, float incidentIor) {\n\treturn pow((transmittedIor - vec3(incidentIor)) / (transmittedIor + vec3(incidentIor)), vec3(2.0));\n}\nvec3 iridescence_fresnelToIor(vec3 f0) {\n\tvec3 sqrtF0 = sqrt(f0);\n\treturn (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0);\n}\nvec3 iridescence_sensitivity(float opd, vec3 shift) {\n\tfloat PI = 3.141592653589793;\n\tfloat phase = 2.0 * PI * opd * 1.0e-9;\n\tconst vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13);\n\tconst vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06);\n\tconst vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09);\n\tvec3 xyz = val * sqrt(2.0 * PI * var) * cos(pos * phase + shift) * exp(-pow(phase, 2.0) * var);\n\txyz.x += 9.7470e-14 * sqrt(2.0 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(-4.5282e+09 * pow(phase, 2.0));\n\txyz /= vec3(1.0685e-07);\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t3.2404542, -0.9692660,  0.0556434,\n\t   -1.5371385,  1.8760108, -0.2040259,\n\t   -0.4985314,  0.0415560,  1.0572252\n\t);\n\treturn XYZ_TO_REC709 * xyz;\n}\nfloat iridescence_fresnel(float cosTheta, float f0) {\n\tfloat x = clamp(1.0 - cosTheta, 0.0, 1.0);\n\tfloat x2 = x * x;\n\tfloat x5 = x * x2 * x2;\n\treturn f0 + (1.0 - f0) * x5;\n} \nvec3 iridescence_fresnel(float cosTheta, vec3 f0) {\n\tfloat x = clamp(1.0 - cosTheta, 0.0, 1.0);\n\tfloat x2 = x * x;\n\tfloat x5 = x * x2 * x2; \n\treturn f0 + (vec3(1.0) - f0) * x5;\n}\nvec3 calcIridescence(float outsideIor, float cosTheta, vec3 base_f0, float iridescenceThickness) {\n\tfloat PI = 3.141592653589793;\n\tfloat iridescenceIor = mix(outsideIor, material_iridescenceRefractionIndex, smoothstep(0.0, 0.03, iridescenceThickness));\n\tfloat sinTheta2Sq = pow(outsideIor / iridescenceIor, 2.0) * (1.0 - pow(cosTheta, 2.0));\n\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\tif (cosTheta2Sq < 0.0) {\n\t\treturn vec3(1.0);\n\t}\n\tfloat cosTheta2 = sqrt(cosTheta2Sq);\n\tfloat r0 = iridescence_iorToFresnel(iridescenceIor, outsideIor);\n\tfloat r12 = iridescence_fresnel(cosTheta, r0);\n\tfloat r21 = r12;\n\tfloat t121 = 1.0 - r12;\n\tfloat phi12 = iridescenceIor < outsideIor ? PI : 0.0;\n\tfloat phi21 = PI - phi12;\n\tvec3 baseIor = iridescence_fresnelToIor(base_f0 + vec3(0.0001));\n\tvec3 r1 = iridescence_iorToFresnel(baseIor, iridescenceIor);\n\tvec3 r23 = iridescence_fresnel(cosTheta2, r1);\n\tvec3 phi23 = vec3(0.0);\n\tif (baseIor[0] < iridescenceIor) phi23[0] = PI;\n\tif (baseIor[1] < iridescenceIor) phi23[1] = PI;\n\tif (baseIor[2] < iridescenceIor) phi23[2] = PI;\n\tfloat opd = 2.0 * iridescenceIor * iridescenceThickness * cosTheta2;\n\tvec3 phi = vec3(phi21) + phi23; \n\tvec3 r123Sq = clamp(r12 * r23, 1e-5, 0.9999);\n\tvec3 r123 = sqrt(r123Sq);\n\tvec3 rs = pow(t121, 2.0) * r23 / (1.0 - r123Sq);\n\tvec3 c0 = r12 + rs;\n\tvec3 i = c0;\n\tvec3 cm = rs - t121;\n\tfor (int m = 1; m <= 2; m++) {\n\t\tcm *= r123;\n\t\tvec3 sm = 2.0 * iridescence_sensitivity(float(m) * opd, float(m) * phi);\n\t\ti += cm * sm;\n\t}\n\treturn max(i, vec3(0.0));\n}\nvec3 getIridescence(float cosTheta, vec3 specularity, float iridescenceThickness) {\n\treturn calcIridescence(1.0, cosTheta, specularity, iridescenceThickness);\n}\n`;\n\nvar iridescencePS$1 = `\n#ifdef STD_IRIDESCENCE_CONSTANT\nuniform float material_iridescence;\n#endif\nvoid getIridescence() {\n\tfloat iridescence = 1.0;\n\t#ifdef STD_IRIDESCENCE_CONSTANT\n\tiridescence *= material_iridescence;\n\t#endif\n\t#ifdef STD_IRIDESCENCE_TEXTURE\n\tiridescence *= texture2DBias({STD_IRIDESCENCE_TEXTURE_NAME}, {STD_IRIDESCENCE_TEXTURE_UV}, textureBias).{STD_IRIDESCENCE_TEXTURE_CHANNEL};\n\t#endif\n\tdIridescence = iridescence; \n}\n`;\n\nvar iridescenceThicknessPS$1 = `\nuniform float material_iridescenceThicknessMax;\n#ifdef STD_IRIDESCENCETHICKNESS_TEXTURE\nuniform float material_iridescenceThicknessMin;\n#endif\nvoid getIridescenceThickness() {\n\t#ifdef STD_IRIDESCENCETHICKNESS_TEXTURE\n\t\tfloat blend = texture2DBias({STD_IRIDESCENCETHICKNESS_TEXTURE_NAME}, {STD_IRIDESCENCETHICKNESS_TEXTURE_UV}, textureBias).{STD_IRIDESCENCETHICKNESS_TEXTURE_CHANNEL};\n\t\tfloat iridescenceThickness = mix(material_iridescenceThicknessMin, material_iridescenceThicknessMax, blend);\n\t#else\n\t\tfloat iridescenceThickness = material_iridescenceThicknessMax;\n\t#endif\n\tdIridescenceThickness = iridescenceThickness; \n}\n`;\n\nvar iorPS$1 = `\n#ifdef STD_IOR_CONSTANT\nuniform float material_refractionIndex;\n#endif\nvoid getIor() {\n#ifdef STD_IOR_CONSTANT\n\tdIor = material_refractionIndex;\n#else\n\tdIor = 1.0 / 1.5;\n#endif\n}\n`;\n\nvar lightDeclarationPS$1 = `\n#if defined(LIGHT{i})\n\tuniform vec3 light{i}_color;\n\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\tuniform vec3 light{i}_direction;\n\t#else\n\t\t#define LIT_CODE_LIGHTS_POINT\n\t\tuniform vec3 light{i}_position;\n\t\tuniform float light{i}_radius;\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t#define LIT_CODE_LIGHTS_SPOT\n\t\t\tuniform vec3 light{i}_direction;\n\t\t\tuniform float light{i}_innerConeAngle;\n\t\t\tuniform float light{i}_outerConeAngle;\n\t\t#endif\n\t#endif\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#define LIT_CODE_FALLOFF_SQUARED\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tuniform vec3 light{i}_position;\n\t\t#endif\n\t\tuniform vec3 light{i}_halfWidth;\n\t\tuniform vec3 light{i}_halfHeight;\n\t#else\n\t\t#if LIGHT{i}FALLOFF == LINEAR\n\t\t\t#define LIT_CODE_FALLOFF_LINEAR\n\t\t#endif\n\t\t#if LIGHT{i}FALLOFF == INVERSESQUARED\n\t\t\t#define LIT_CODE_FALLOFF_SQUARED\n\t\t#endif\n\t#endif\n\t#if defined(LIGHT{i}CASTSHADOW)\n\t\tuniform mat4 light{i}_shadowMatrix;\n\t\tuniform float light{i}_shadowIntensity;\n\t\tuniform vec4 light{i}_shadowParams;\n\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\tuniform float light{i}_shadowSearchArea;\n\t\t\tuniform vec4 light{i}_cameraParams;\n\t\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\t\tuniform vec4 light{i}_softShadowParams;\n\t\t\t#endif\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tuniform mat4 light{i}_shadowMatrixPalette[4];\n\t\t\tuniform vec4 light{i}_shadowCascadeDistances;\n\t\t\tuniform int light{i}_shadowCascadeCount;\n\t\t\tuniform float light{i}_shadowCascadeBlend;\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\t#if defined(LIGHT{i}SHADOW_PCF)\n\t\t\t\tuniform samplerCubeShadow light{i}_shadowMap;\n\t\t\t#else\n\t\t\t\tuniform samplerCube light{i}_shadowMap;\n\t\t\t#endif\n\t\t#else\n\t\t\t#if defined(LIGHT{i}SHADOW_PCF)\n\t\t\t\tuniform sampler2DShadow light{i}_shadowMap;\n\t\t\t#else\n\t\t\t\tuniform sampler2D light{i}_shadowMap;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\t#if defined(LIGHT{i}COOKIE)\n\t\t#define LIT_CODE_COOKIE\n\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\tuniform samplerCube light{i}_cookie;\n\t\t\tuniform float light{i}_cookieIntensity;\n\t\t\t#if !defined(LIGHT{i}CASTSHADOW)\n\t\t\t\tuniform mat4 light{i}_shadowMatrix;\n\t\t\t#endif\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\tuniform sampler2D light{i}_cookie;\n\t\t\tuniform float light{i}_cookieIntensity;\n\t\t\t#if !defined(LIGHT{i}CASTSHADOW)\n\t\t\t\tuniform mat4 light{i}_shadowMatrix;\n\t\t\t#endif\n\t\t\t#if defined(LIGHT{i}COOKIE_TRANSFORM)\n\t\t\t\tuniform vec4 light{i}_cookieMatrix;\n\t\t\t\tuniform vec2 light{i}_cookieOffset;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n#endif\n`;\n\nvar lightDiffuseLambertPS$1 = `\nfloat getLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm) {\n\treturn max(dot(worldNormal, -lightDirNorm), 0.0);\n}\n`;\n\nvar lightDirPointPS$1 = `\nvec3 evalOmniLight(vec3 lightPosW) {\n\treturn vPositionW - lightPosW;\n}\n`;\n\nvar lightEvaluationPS$1 = `\n#if defined(LIGHT{i})\n\tevaluateLight{i}(\n\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\tiridescenceFresnel\n\t\t#endif\n\t);\n#endif\n`;\n\nvar lightFunctionLightPS$1 = `\n#if defined(LIGHT{i})\nvoid evaluateLight{i}(\n\t#if defined(LIT_IRIDESCENCE)\n\t\tvec3 iridescenceFresnel\n\t#endif\n) {\n\tvec3 lightColor = light{i}_color;\n\t#if LIGHT{i}TYPE == DIRECTIONAL && !defined(LIT_SHADOW_CATCHER)\n\t\tif (all(equal(lightColor, vec3(0.0)))) {\n\t\t\treturn;\n\t\t}\n\t#endif\n\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\tdLightDirNormW = light{i}_direction;\n\t\tdAtten = 1.0;\n\t#else\n\t\t\n\t\tvec3 lightDirW = evalOmniLight(light{i}_position);\n\t\tdLightDirNormW = normalize(lightDirW);\n\t\t#if defined(LIGHT{i}COOKIE)\n\t\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t\t#ifdef LIGHT{i}COOKIE_FALLOFF\n\t\t\t\t\t#ifdef LIGHT{i}COOKIE_TRANSFORM\n\t\t\t\t\t\tvec3 cookieAttenuation = getCookie2DXform(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity, light{i}_cookieMatrix, light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#else\n\t\t\t\t\t\tvec3 cookieAttenuation = getCookie2D(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#endif\n\t\t\t\t#else\n\t\t\t\t\t#ifdef LIGHT{i}COOKIE_TRANSFORM\n\t\t\t\t\t\tvec3 cookieAttenuation = getCookie2DClipXform(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity, light{i}_cookieMatrix, light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#else\n\t\t\t\t\t\tvec3 cookieAttenuation = getCookie2DClip(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#endif\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\t\tvec3 cookieAttenuation = getCookieCube(light{i}_cookie, light{i}_shadowMatrix, light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t#endif\n\t\t\tlightColor *= cookieAttenuation;\n\t\t#endif\n\t\t#if LIGHT{i}SHAPE == PUNCTUAL\n\t\t\t#if LIGHT{i}FALLOFF == LINEAR\n\t\t\t\tdAtten = getFalloffLinear(light{i}_radius, lightDirW);\n\t\t\t#else\n\t\t\t\tdAtten = getFalloffInvSquared(light{i}_radius, lightDirW);\n\t\t\t#endif\n\t\t#else\n\t\t\tdAtten = getFalloffWindow(light{i}_radius, lightDirW);\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t#if !defined(LIGHT{i}COOKIE) || defined(LIGHT{i}COOKIE_FALLOFF)\n\t\t\t\tdAtten *= getSpotEffect(light{i}_direction, light{i}_innerConeAngle, light{i}_outerConeAngle, dLightDirNormW);\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\tif (dAtten < 0.00001) {\n\t\treturn;\n\t}\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\tcalcRectLightValues(light{i}_position, light{i}_halfWidth, light{i}_halfHeight);\n\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\tcalcDiskLightValues(light{i}_position, light{i}_halfWidth, light{i}_halfHeight);\n\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\tcalcSphereLightValues(light{i}_position, light{i}_halfWidth, light{i}_halfHeight);\n\t\t#endif\n\t#endif\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tfloat attenDiffuse = getLightDiffuse(litArgs_worldNormal, dViewDirW, dLightDirNormW);\n\t\t#else\n\t\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\t\tfloat attenDiffuse = getRectLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;\n\t\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\t\tfloat attenDiffuse = getDiskLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;\n\t\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\t\tfloat attenDiffuse = getSphereLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;\n\t\t\t#endif\n\t\t#endif\n\t#else\n\t\tdAtten *= getLightDiffuse(litArgs_worldNormal, vec3(0.0), dLightDirNormW);\n\t#endif\n\t#ifdef LIGHT{i}CASTSHADOW\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tfloat shadow = getShadow{i}(vec3(0.0));\n\t\t#else\n\t\t\tfloat shadow = getShadow{i}(lightDirW);\n\t\t#endif\n\t\tshadow = mix(1.0, shadow, light{i}_shadowIntensity);\n\t\tdAtten *= shadow;\n\t\t#if defined(LIT_SHADOW_CATCHER) && LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tdShadowCatcher *= shadow;\n\t\t#endif\t\t\t\n\t#endif\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#ifdef LIT_SPECULAR\n\t\t\tdDiffuseLight += ((attenDiffuse * dAtten) * lightColor) * (1.0 - dLTCSpecFres);\n\t\t#else\n\t\t\tdDiffuseLight += (attenDiffuse * dAtten) * lightColor;\n\t\t#endif\t\t\t\t\t\t\n\t#else\n\t\t#if defined(AREA_LIGHTS) && defined(LIT_SPECULAR)\n\t\t\tdDiffuseLight += (dAtten * lightColor) * (1.0 - litArgs_specularity);\n\t\t#else\n\t\t\tdDiffuseLight += dAtten * lightColor;\n\t\t#endif\n\t#endif\n\t#ifdef LIGHT{i}AFFECT_SPECULARITY\n\t\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\t\t\tccSpecularLight += ccLTCSpecFres * getRectLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor;\n\t\t\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\t\t\tccSpecularLight += ccLTCSpecFres * getDiskLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor;\n\t\t\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\t\t\tccSpecularLight += ccLTCSpecFres * getSphereLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\t\t\tdSpecularLight += dLTCSpecFres * getRectLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor;\n\t\t\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\t\t\tdSpecularLight += dLTCSpecFres * getDiskLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor;\n\t\t\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\t\t\tdSpecularLight += dLTCSpecFres * getSphereLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t#else\n\t\t\t#if LIGHT{i}TYPE == DIRECTIONAL && LIT_FRESNEL_MODEL != NONE\n\t\t\t\t#define LIGHT{i}FRESNEL\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tvec3 halfDirW = normalize(-dLightDirNormW + dViewDirW);\n\t\t\t#endif\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tvec3 lightspecularCC = getLightSpecular(halfDirW, ccReflDirW, litArgs_clearcoat_worldNormal, dViewDirW, dLightDirNormW, litArgs_clearcoat_gloss, dTBN) * dAtten * lightColor;\n\t\t\t\t#ifdef LIGHT{i}FRESNEL\n\t\t\t\t\tlightspecularCC *= getFresnelCC(dot(dViewDirW, halfDirW));\n\t\t\t\t#endif\n\t\t\t\tccSpecularLight += lightspecularCC;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\tsSpecularLight += getLightSpecularSheen(halfDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_sheen_gloss) * dAtten * lightColor;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tvec3 lightSpecular = getLightSpecular(halfDirW, dReflDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_gloss, dTBN) * dAtten * lightColor;\n\t\t\t\t#ifdef LIGHT{i}FRESNEL\n\t\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t\tlightSpecular *= getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity, iridescenceFresnel, litArgs_iridescence_intensity);\n\t\t\t\t\t#else\n\t\t\t\t\t\tlightSpecular *= getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity);\n\t\t\t\t\t#endif\n\t\t\t\t#else\n\t\t\t\t\tlightSpecular *= litArgs_specularity;\n\t\t\t\t#endif\n\t\t\t\t\n\t\t\t\tdSpecularLight += lightSpecular;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n}\n#endif\n`;\n\nvar lightFunctionShadowPS$1 = `\n#ifdef LIGHT{i}CASTSHADOW\n\tvec3 getShadowSampleCoord{i}(mat4 shadowTransform, vec4 shadowParams, vec3 worldPosition, vec3 lightPos, inout vec3 lightDir, vec3 lightDirNorm, vec3 normal) {\n\t\tvec3 surfacePosition = worldPosition;\n\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_POINT\n\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET\n\t\t\t\tfloat distScale = length(lightDir);\n\t\t\t\tsurfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;\n\t\t\t\tlightDir = surfacePosition - lightPos;\n\t\t\t\treturn lightDir;\n\t\t\t#endif\n\t\t#else\n\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER\n\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET\n\t\t\t\t\tsurfacePosition = surfacePosition + normal * shadowParams.y;\n\t\t\t\t#endif\n\t\t\t#else\n\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET\n\t\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO\n\t\t\t\t\t\tfloat distScale = 1.0;\n\t\t\t\t\t#else\n\t\t\t\t\t\tfloat distScale = abs(dot(vPositionW - lightPos, lightDirNorm));\n\t\t\t\t\t#endif\n\t\t\t\t\tsurfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\tvec4 positionInShadowSpace = shadowTransform * vec4(surfacePosition, 1.0);\n\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO\n\t\t\t\tpositionInShadowSpace.z = saturate(positionInShadowSpace.z) - 0.0001;\n\t\t\t#else\n\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER\n\t\t\t\t\tpositionInShadowSpace.xyz /= positionInShadowSpace.w;\n\t\t\t\t#else\n\t\t\t\t\tpositionInShadowSpace.xy /= positionInShadowSpace.w;\n\t\t\t\t\tpositionInShadowSpace.z = length(lightDir) * shadowParams.w;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef SHADOW_SAMPLE_Z_BIAS\n\t\t\t#endif\n\t\t\tsurfacePosition = positionInShadowSpace.xyz;\n\t\t#endif\n\t\treturn surfacePosition;\n\t}\n\tfloat getShadow{i}(vec3 lightDirW) {\n\t\t#ifdef LIGHT{i}_SHADOW_CASCADES\n\t\t\tint cascadeIndex = getShadowCascadeIndex(light{i}_shadowCascadeDistances, light{i}_shadowCascadeCount);\n\t\t\t#ifdef LIGHT{i}_SHADOW_CASCADE_BLEND\n\t\t\t\tcascadeIndex = ditherShadowCascadeIndex(cascadeIndex, light{i}_shadowCascadeDistances, light{i}_shadowCascadeCount, light{i}_shadowCascadeBlend);\n\t\t\t#endif\n\t\t\tmat4 shadowMatrix = light{i}_shadowMatrixPalette[cascadeIndex];\n\t\t#else\n\t\t\tmat4 shadowMatrix = light{i}_shadowMatrix;\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tvec3 shadowCoord = getShadowSampleCoord{i}(shadowMatrix, light{i}_shadowParams, vPositionW, vec3(0.0), lightDirW, dLightDirNormW, dVertexNormalW);\n\t\t#else\n\t\t\tvec3 shadowCoord = getShadowSampleCoord{i}(shadowMatrix, light{i}_shadowParams, vPositionW, light{i}_position, lightDirW, dLightDirNormW, dVertexNormalW);\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tshadowCoord = fadeShadow(shadowCoord, light{i}_shadowCascadeDistances);\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_16F\n\t\t\t\treturn getShadowVSM16(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 5.54);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_32F\n\t\t\t\treturn getShadowVSM32(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 15.0);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\t\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t\t\tvec2 shadowSearchArea = vec2(length(light{i}_halfWidth), length(light{i}_halfHeight)) * light{i}_shadowSearchArea;\n\t\t\t\t\treturn getShadowPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, shadowSearchArea, lightDirW);\n\t\t\t\t#else\n\t\t\t\t\treturn getShadowPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, light{i}_softShadowParams, lightDirW);\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F\n\t\t\t\treturn getShadowPCF1x1(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F\n\t\t\t\treturn getShadowPCF3x3(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F\n\t\t\t\treturn getShadowPCF5x5(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);\n\t\t\t#endif\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_16F\n\t\t\t\treturn getShadowSpotVSM16(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 5.54, lightDirW);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_32F\n\t\t\t\treturn getShadowSpotVSM32(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, 15.0, lightDirW);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\t\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t\t\tvec2 shadowSearchArea = vec2(length(light{i}_halfWidth), length(light{i}_halfHeight)) * light{i}_shadowSearchArea;\n\t\t\t\t#else\n\t\t\t\t\tvec2 shadowSearchArea = vec2(light{i}_shadowSearchArea);\n\t\t\t\t#endif\n\t\t\t\treturn getShadowSpotPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, shadowSearchArea, lightDirW);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F\n\t\t\t\treturn getShadowSpotPCF1x1(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F\n\t\t\t\treturn getShadowSpotPCF3x3(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F\n\t\t\t\treturn getShadowSpotPCF5x5(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams);\n\t\t\t#endif\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\t\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t\t\tvec2 shadowSearchArea = vec2(length(light{i}_halfWidth), length(light{i}_halfHeight)) * light{i}_shadowSearchArea;\n\t\t\t\t#else\n\t\t\t\t\tvec2 shadowSearchArea = vec2(light{i}_shadowSearchArea);\n\t\t\t\t#endif\n\t\t\t\treturn getShadowOmniPCSS(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, light{i}_cameraParams, shadowSearchArea, lightDirW);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F\n\t\t\t\treturn getShadowOmniPCF1x1(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, lightDirW);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F\n\t\t\t\treturn getShadowOmniPCF3x3(SHADOWMAP_PASS(light{i}_shadowMap), shadowCoord, light{i}_shadowParams, lightDirW);\n\t\t\t#endif\n\t\t#endif\n\t}\n#endif\n`;\n\nvar lightingPS$1 = `\n#ifdef LIT_CLUSTERED_LIGHTS\n\t#define LIT_CODE_FALLOFF_LINEAR\n\t#define LIT_CODE_FALLOFF_SQUARED\n\t#define LIT_CODE_LIGHTS_POINT\n\t#define LIT_CODE_LIGHTS_SPOT\n#endif\n#ifdef AREA_LIGHTS\n\tuniform highp sampler2D areaLightsLutTex1;\n\tuniform highp sampler2D areaLightsLutTex2;\n#endif\n#ifdef LIT_LIGHTING\n\t#include \"lightDiffuseLambertPS\"\n\t#if defined(AREA_LIGHTS) || defined(LIT_CLUSTERED_AREA_LIGHTS)\n\t\t#include \"ltcPS\"\n\t#endif\n#endif\n#ifdef SHADOW_DIRECTIONAL\n\t#include \"shadowCascadesPS\"\n#endif\n#if defined(SHADOW_KIND_PCF1)\n\t#include \"shadowPCF1PS\"\n#endif\n#if defined(SHADOW_KIND_PCF3)\n\t#include \"shadowPCF3PS\"\n#endif\n#if defined(SHADOW_KIND_PCF5)\n\t#include \"shadowPCF5PS\"\n#endif\n#if defined(SHADOW_KIND_PCSS)\n\t#include \"linearizeDepthPS\"\n\t#include \"shadowPCSSPS\"\n\t#include \"shadowSoftPS\"\n#endif\n#if defined(SHADOW_KIND_VSM)\n\t#include \"shadowEVSMPS\"\n#endif\n#ifdef LIT_CODE_FALLOFF_LINEAR\n\t#include \"falloffLinearPS\"\n#endif\n#ifdef LIT_CODE_FALLOFF_SQUARED\n\t#include \"falloffInvSquaredPS\"\n#endif\n#ifdef LIT_CODE_LIGHTS_POINT\n\t#include \"lightDirPointPS\"\n#endif\n#ifdef LIT_CODE_LIGHTS_SPOT\n\t#include \"spotPS\"\n#endif\n#ifdef LIT_CODE_COOKIE\n\t#include \"cookiePS\"\n#endif\n#ifdef LIT_CLUSTERED_LIGHTS\n\t#include \"clusteredLightPS\"\n#endif\n#ifdef LIGHT_COUNT > 0\n\t#include \"lightFunctionShadowPS, LIGHT_COUNT\"\n\t#include \"lightFunctionLightPS, LIGHT_COUNT\"\n#endif\n`;\n\nvar lightmapAddPS$1 = `\nvoid addLightMap(\n\tvec3 lightmap, \n\tvec3 dir, \n\tvec3 worldNormal, \n\tvec3 viewDir, \n\tvec3 reflectionDir, \n\tfloat gloss, \n\tvec3 specularity, \n\tvec3 vertexNormal, \n\tmat3 tbn\n#if defined(LIT_IRIDESCENCE)\n\tvec3 iridescenceFresnel, \n\tfloat iridescenceIntensity\n#endif\n) {\n\t#if defined(LIT_SPECULAR) && defined(LIT_DIR_LIGHTMAP)\n\t\tif (dot(dir, dir) < 0.0001) {\n\t\t\t\tdDiffuseLight += lightmap;\n\t\t} else {\n\t\t\tfloat vlight = saturate(dot(dir, -vertexNormal));\n\t\t\tfloat flight = saturate(dot(dir, -worldNormal));\n\t\t\tfloat nlight = (flight / max(vlight, 0.01)) * 0.5;\n\t\t\tdDiffuseLight += lightmap * nlight * 2.0;\n\t\t\tvec3 halfDir = normalize(-dir + viewDir);\n\t\t\tvec3 specularLight = lightmap * getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, dir, gloss, tbn);\n\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\tspecularLight *= \n\t\t\t\t\tgetFresnel(dot(viewDir, halfDir), \n\t\t\t\t\tgloss, \n\t\t\t\t\tspecularity\n\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t, iridescenceFresnel,\n\t\t\t\t\tiridescenceIntensity\n\t\t\t\t#endif\n\t\t\t\t\t);\n\t\t\t#endif\n\t\t\tdSpecularLight += specularLight;\n\t\t}\n\t#else\n\t\tdDiffuseLight += lightmap;\n\t#endif\n}\n`;\n\nvar lightmapPS$1 = `\n#ifdef STD_LIGHTMAP_DIR\n\tvec3 dLightmapDir;\n\tuniform sampler2D texture_dirLightMap;\n#endif\nvoid getLightMap() {\n\tdLightmap = vec3(1.0);\n\t#ifdef STD_LIGHT_TEXTURE\n\t\tdLightmap *= {STD_LIGHT_TEXTURE_DECODE}(texture2DBias({STD_LIGHT_TEXTURE_NAME}, {STD_LIGHT_TEXTURE_UV}, textureBias)).{STD_LIGHT_TEXTURE_CHANNEL};\n\t\t#ifdef STD_LIGHTMAP_DIR\n\t\t\tvec3 dir = texture2DBias(texture_dirLightMap, {STD_LIGHT_TEXTURE_UV}, textureBias).xyz * 2.0 - 1.0;\n\t\t\tfloat dirDot = dot(dir, dir);\n\t\t\tdLightmapDir = (dirDot > 0.001) ? dir / sqrt(dirDot) : vec3(0.0);\n\t\t#endif\n\t#endif\n\t#ifdef STD_LIGHT_VERTEX\n\t\tdLightmap *= saturate(vVertexColor.{STD_LIGHT_VERTEX_CHANNEL});\n\t#endif\n}\n`;\n\nvar lightSpecularAnisoGGXPS$1 = `\nfloat calcLightSpecular(float gloss, vec3 worldNormal, vec3 viewDir, vec3 h, vec3 lightDirNorm, mat3 tbn) {\n\tfloat PI = 3.141592653589793;\n\tfloat roughness = max((1.0 - gloss) * (1.0 - gloss), 0.001);\n\tfloat alphaRoughness = roughness * roughness;\n\tfloat anisotropy = dAnisotropy;\n\tvec2 direction = dAnisotropyRotation;\n\tfloat at = mix(alphaRoughness, 1.0, anisotropy * anisotropy);\n\tfloat ab = clamp(alphaRoughness, 0.001, 1.0);\n\tvec3 anisotropicT = normalize(tbn * vec3(direction, 0.0));\n\tvec3 anisotropicB = normalize(cross(tbn[2], anisotropicT));\n\tfloat NoH = dot(worldNormal, h);\n\tfloat ToH = dot(anisotropicT, h);\n\tfloat BoH = dot(anisotropicB, h);\n\tfloat a2 = at * ab;\n\tvec3 v = vec3(ab * ToH, at * BoH, a2 * NoH);\n\tfloat v2 = dot(v, v);\n\tfloat w2 = a2 / v2;\n\tfloat D = a2 * w2 * w2 * (1.0 / PI);\n\tfloat ToV = dot(anisotropicT, viewDir);\n\tfloat BoV = dot(anisotropicB, viewDir);\n\tfloat ToL = dot(anisotropicT, -lightDirNorm);\n\tfloat BoL = dot(anisotropicB, -lightDirNorm);\n\tfloat NoV = dot(worldNormal, viewDir);\n\tfloat NoL = dot(worldNormal, -lightDirNorm);\n\tfloat lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV));\n\tfloat lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL));\n\tfloat G = 0.5 / (lambdaV + lambdaL);\n\treturn D * G;\n}\nfloat getLightSpecular(vec3 h, vec3 reflDir, vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm, float gloss, mat3 tbn) {\n\treturn calcLightSpecular(gloss, worldNormal, viewDir, h, lightDirNorm, tbn);\n}\n`;\n\nvar lightSpecularBlinnPS$1 = `\nfloat calcLightSpecular(float gloss, vec3 worldNormal, vec3 h) {\n\tfloat nh = max( dot( h, worldNormal ), 0.0 );\n\tfloat specPow = exp2(gloss * 11.0);\n\tspecPow = max(specPow, 0.0001);\n\treturn pow(nh, specPow) * (specPow + 2.0) / 8.0;\n}\nfloat getLightSpecular(vec3 h, vec3 reflDir, vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm, float gloss, mat3 tbn) {\n\treturn calcLightSpecular(gloss, worldNormal, h);\n}\n`;\n\nvar lightSheenPS$1 = `\nfloat sheenD(vec3 normal, vec3 h, float roughness) {\n\tconst float PI = 3.141592653589793;\n\tfloat invR = 1.0 / (roughness * roughness);\n\tfloat cos2h = max(dot(normal, h), 0.0);\n\tcos2h *= cos2h;\n\tfloat sin2h = max(1.0 - cos2h, 0.0078125);\n\treturn (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * PI);\n}\nfloat sheenV(vec3 normal, vec3 viewDir, vec3 light) {\n\tfloat NoV = max(dot(normal, viewDir), 0.000001);\n\tfloat NoL = max(dot(normal, light), 0.000001);\n\treturn 1.0 / (4.0 * (NoL + NoV - NoL * NoV));\n}\nfloat getLightSpecularSheen(vec3 h, vec3 worldNormal, vec3 viewDir, vec3 lightDirNorm, float sheenGloss) {\n\tfloat D = sheenD(worldNormal, h, sheenGloss);\n\tfloat V = sheenV(worldNormal, viewDir, -lightDirNorm);\n\treturn D * V;\n}\n`;\n\nvar linearizeDepthPS$1 = `\n#ifndef LINEARIZE_DEPTH\n#define LINEARIZE_DEPTH\nfloat linearizeDepthWithParams(float z, vec4 cameraParams) {\n\tif (cameraParams.w == 0.0)\n\t\treturn (cameraParams.z * cameraParams.y) / (cameraParams.y + z * (cameraParams.z - cameraParams.y));\n\telse\n\t\treturn cameraParams.z + z * (cameraParams.y - cameraParams.z);\n}\n#ifndef CAMERAPLANES\n\t#define CAMERAPLANES\n\tuniform vec4 camera_params;\n#endif\nfloat linearizeDepth(float z) {\n\treturn linearizeDepthWithParams(z, camera_params);\n}\n#endif\n`;\n\nvar litForwardBackendPS$1 = `\nvoid evaluateBackend() {\n\t#ifdef LIT_SSAO\n\t\tlitArgs_ao *= texture2DLod(ssaoTexture, gl_FragCoord.xy * ssaoTextureSizeInv, 0.0).r;\n\t#endif\n\t#ifdef LIT_NEEDS_NORMAL\n\t\t#ifdef LIT_SPECULAR\n\t\t\tgetReflDir(litArgs_worldNormal, dViewDirW, litArgs_gloss, dTBN);\n\t\t#endif\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\tccReflDirW = normalize(-reflect(dViewDirW, litArgs_clearcoat_worldNormal));\n\t\t#endif\n\t#endif\n\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t#ifdef LIT_METALNESS\n\t\t\tfloat f0 = 1.0 / litArgs_ior;\n\t\t\tf0 = (f0 - 1.0) / (f0 + 1.0);\n\t\t\tf0 *= f0;\n\t\t\tlitArgs_specularity = getSpecularModulate(litArgs_specularity, litArgs_albedo, litArgs_metalness, f0);\n\t\t\tlitArgs_albedo = getAlbedoModulate(litArgs_albedo, litArgs_metalness);\n\t\t#endif\n\t\t#ifdef LIT_IRIDESCENCE\n\t\t\tvec3 iridescenceFresnel = getIridescence(saturate(dot(dViewDirW, litArgs_worldNormal)), litArgs_specularity, litArgs_iridescence_thickness);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_ADD_AMBIENT\n\t\taddAmbient(litArgs_worldNormal);\n\t\t#ifdef LIT_SPECULAR\n\t\t\tdDiffuseLight = dDiffuseLight * (1.0 - litArgs_specularity);\n\t\t#endif\n\t\t#ifdef LIT_SEPARATE_AMBIENT\n\t\t\tvec3 dAmbientLight = dDiffuseLight;\n\t\t\tdDiffuseLight = vec3(0);\n\t\t#endif\n\t#endif\n\t#ifndef LIT_OLD_AMBIENT\n\t\tdDiffuseLight *= material_ambient;\n\t#endif\n\t#ifdef LIT_AO\n\t\t#ifndef LIT_OCCLUDE_DIRECT\n\t\t\toccludeDiffuse(litArgs_ao);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_LIGHTMAP\n\t\taddLightMap(\n\t\t\tlitArgs_lightmap, \n\t\t\tlitArgs_lightmapDir, \n\t\t\tlitArgs_worldNormal, \n\t\t\tdViewDirW, \n\t\t\tdReflDirW, \n\t\t\tlitArgs_gloss, \n\t\t\tlitArgs_specularity, \n\t\t\tdVertexNormalW,\n\t\t\tdTBN\n\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t, iridescenceFresnel,\n\t\t\tlitArgs_iridescence_intensity\n\t\t#endif\n\t\t);\n\t#endif\n\t#ifdef LIT_LIGHTING || LIT_REFLECTIONS\n\t\t#ifdef LIT_REFLECTIONS\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\taddReflectionCC(ccReflDirW, litArgs_clearcoat_gloss);\n\t\t\t\n\t\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\t\tccFresnel = getFresnelCC(dot(dViewDirW, litArgs_clearcoat_worldNormal));\n\t\t\t\t\tccReflection *= ccFresnel;\n\t\t\t\t#else\n\t\t\t\t\tccFresnel = 0.0;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\tccReflection *= litArgs_specularityFactor;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\taddReflectionSheen(litArgs_worldNormal, dViewDirW, litArgs_sheen_gloss);\n\t\t\t#endif\n\t\t\taddReflection(dReflDirW, litArgs_gloss);\n\t\t\t#ifdef LIT_FRESNEL_MODEL\n\t\t\t\tdReflection.rgb *= getFresnel(\n\t\t\t\t\tdot(dViewDirW, litArgs_worldNormal), \n\t\t\t\t\tlitArgs_gloss, \n\t\t\t\t\tlitArgs_specularity\n\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t, iridescenceFresnel,\n\t\t\t\t\tlitArgs_iridescence_intensity\n\t\t\t\t#endif\n\t\t\t\t\t);\n\t\t\t#else\n\t\t\t\tdReflection.rgb *= litArgs_specularity;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\tdReflection.rgb *= litArgs_specularityFactor;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef AREA_LIGHTS\n\t\t\tdSpecularLight *= litArgs_specularity;\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tcalcLTCLightValues(litArgs_gloss, litArgs_worldNormal, dViewDirW, litArgs_specularity, litArgs_clearcoat_gloss, litArgs_clearcoat_worldNormal, litArgs_clearcoat_specularity);\n\t\t\t#endif\n\t\t#endif\n\t\t\n\t\t#ifdef LIGHT_COUNT > 0\n\t\t\t#include \"lightEvaluationPS, LIGHT_COUNT\"\n\t\t#endif\n\t\t#ifdef LIT_CLUSTERED_LIGHTS\n\t\t\taddClusteredLights(litArgs_worldNormal, dViewDirW, dReflDirW,\n\t\t\t\t#if defined(LIT_CLEARCOAT)\n\t\t\t\t\t\tccReflDirW,\n\t\t\t\t#endif\n\t\t\t\t\t\tlitArgs_gloss, litArgs_specularity, dVertexNormalW, dTBN, \n\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t\tiridescenceFresnel,\n\t\t\t\t#endif\n\t\t\t\t\t\tlitArgs_clearcoat_worldNormal, litArgs_clearcoat_gloss, litArgs_sheen_gloss, litArgs_iridescence_intensity\n\t\t\t);\n\t\t#endif\n\t\t#ifdef AREA_LIGHTS\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tlitArgs_clearcoat_specularity = 1.0;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tlitArgs_specularity = vec3(1);\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef LIT_REFRACTION\n\t\t\taddRefraction(\n\t\t\t\tlitArgs_worldNormal, \n\t\t\t\tdViewDirW, \n\t\t\t\tlitArgs_thickness, \n\t\t\t\tlitArgs_gloss, \n\t\t\t\tlitArgs_specularity, \n\t\t\t\tlitArgs_albedo, \n\t\t\t\tlitArgs_transmission,\n\t\t\t\tlitArgs_ior,\n\t\t\t\tlitArgs_dispersion\n\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t, iridescenceFresnel, \n\t\t\t\t\tlitArgs_iridescence_intensity\n\t\t\t\t#endif\n\t\t\t);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_AO\n\t\t#ifdef LIT_OCCLUDE_DIRECT\n\t\t\toccludeDiffuse(litArgs_ao);\n\t\t#endif\n\t\t#if LIT_OCCLUDE_SPECULAR != NONE\n\t\t\toccludeSpecular(litArgs_gloss, litArgs_ao, litArgs_worldNormal, dViewDirW);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_SPECULARITY_FACTOR\n\t\tdSpecularLight *= litArgs_specularityFactor;\n\t#endif\n\t#if !defined(LIT_OPACITY_FADES_SPECULAR)\n\t\t#if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == PREMULTIPLIED\n\t\t\tfloat specLum = dot((dSpecularLight + dReflection.rgb * dReflection.a), vec3( 0.2126, 0.7152, 0.0722 ));\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tspecLum += dot(ccSpecularLight * litArgs_clearcoat_specularity + ccReflection * litArgs_clearcoat_specularity, vec3( 0.2126, 0.7152, 0.0722 ));\n\t\t\t#endif\n\t\t\tlitArgs_opacity = clamp(litArgs_opacity + gammaCorrectInput(specLum), 0.0, 1.0);\n\t\t#endif\n\t\tlitArgs_opacity *= material_alphaFade;\n\t#endif\n\t#ifdef LIT_LIGHTMAP_BAKING\n\t\t#ifdef LIT_LIGHTMAP_BAKING_COLOR\n\t\t\t#include \"bakeLmEndPS\"\n\t\t#endif\n\t\t#ifdef LIT_LIGHTMAP_BAKING_DIR\n\t\t\t#include \"bakeDirLmEndPS\"\n\t\t#endif\n\t#else\n\t\t#include \"endPS\"\n\t\t#include \"outputAlphaPS\"\n\t#endif\n\t#ifdef LIT_MSDF\n\t\tgl_FragColor = applyMsdf(gl_FragColor);\n\t#endif\n\t#include \"outputPS\"\n\t#include \"debugOutputPS\"\n\t#ifdef LIT_SHADOW_CATCHER\n\t\tgl_FragColor.rgb = vec3(dShadowCatcher);\n\t#endif\n}\n`;\n\nvar litForwardDeclarationPS$1 = `\nvec3 sReflection;\nvec3 dVertexNormalW;\nvec3 dTangentW;\nvec3 dBinormalW;\nvec3 dViewDirW;\nvec3 dReflDirW;\nvec3 ccReflDirW;\nvec3 dLightDirNormW;\nfloat dAtten;\nmat3 dTBN;\nvec4 dReflection;\nvec3 dDiffuseLight;\nvec3 dSpecularLight;\nfloat ccFresnel;\nvec3 ccReflection;\nvec3 ccSpecularLight;\nfloat ccSpecularityNoFres;\nvec3 sSpecularLight;\n#ifdef LIT_DISPERSION\n\tuniform float material_dispersion;\n#endif\n#ifndef LIT_OPACITY_FADES_SPECULAR\n\tuniform float material_alphaFade;\n#endif\n#ifdef LIT_SSAO\n\tuniform sampler2D ssaoTexture;\n\tuniform vec2 ssaoTextureSizeInv;\n#endif\n#ifdef LIT_SHADOW_CATCHER\n\tfloat dShadowCatcher = 1.0;\n#endif\n#if LIGHT_COUNT > 0\n\t#include \"lightDeclarationPS, LIGHT_COUNT\"\n#endif\n#ifdef LIT_SPECULAR\n\t#if LIT_FRESNEL_MODEL == NONE && !defined(LIT_REFLECTIONS) && !defined(LIT_DIFFUSE_MAP) \n\t\t#define LIT_OLD_AMBIENT\n\t#endif\n#endif\n#ifdef STD_LIGHTMAP_DIR\n\tuniform float bakeDir;\n#endif\n#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT\n\tuniform float ambientBakeOcclusionContrast;\n\tuniform float ambientBakeOcclusionBrightness;\n#endif\n`;\n\nvar litForwardMainPS$1 = `\nvoid main(void) {\n\t#include \"litUserMainStartPS\"\n\tdReflection = vec4(0);\n\t#ifdef LIT_CLEARCOAT\n\t\tccSpecularLight = vec3(0);\n\t\tccReflection = vec3(0);\n\t#endif\n\t#if LIT_NONE_SLICE_MODE == SLICED\n\t\t#include \"startNineSlicedPS\"\n\t#elif LIT_NONE_SLICE_MODE == TILED\n\t\t#include \"startNineSlicedTiledPS\"\n\t#endif\n\t#ifdef LIT_NEEDS_NORMAL\n\t\tdVertexNormalW = normalize(vNormalW);\n\t\t#ifdef LIT_TANGENTS\n\t\t\t#if defined(LIT_HEIGHTS) || defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS) || defined(LIT_GGX_SPECULAR)\n\t\t\t\tdTangentW = vTangentW;\n\t\t\t\tdBinormalW = vBinormalW;\n\t\t\t#endif\n\t\t#endif\n\t\tgetViewDir();\n\t\t#ifdef LIT_TBN\n\t\t\tgetTBN(dTangentW, dBinormalW, dVertexNormalW);\n\t\t\t#ifdef LIT_TWO_SIDED_LIGHTING\n\t\t\t\thandleTwoSidedLighting();\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\tevaluateFrontend();\n\t#include \"debugProcessFrontendPS\"\n\tevaluateBackend();\n\t#include \"litUserMainEndPS\"\n}\n`;\n\nvar litForwardPostCodePS$1 = `\n#ifdef LIT_NEEDS_NORMAL\n\t#include \"cubeMapRotatePS\"\n\t#include \"cubeMapProjectPS\"\n\t#include \"envProcPS\"\n#endif\n#ifdef LIT_SPECULAR_OR_REFLECTION\n\t#ifdef LIT_METALNESS\n\t\t#include \"metalnessModulatePS\"\n\t#endif\n\t#if LIT_FRESNEL_MODEL == SCHLICK\n\t\t#include \"fresnelSchlickPS\"\n\t#endif\n\t#ifdef LIT_IRIDESCENCE\n\t\t#include \"iridescenceDiffractionPS\"\n\t#endif\n#endif\n#ifdef LIT_AO\n\t#include \"aoDiffuseOccPS\"\n\t#include \"aoSpecOccPS\"\n#endif\n#if LIT_REFLECTION_SOURCE == ENVATLASHQ\n\t#include \"envAtlasPS\"\n\t#include \"reflectionEnvHQPS\"\n#elif LIT_REFLECTION_SOURCE == ENVATLAS\n\t#include \"envAtlasPS\"\n\t#include \"reflectionEnvPS\"\n#elif LIT_REFLECTION_SOURCE == CUBEMAP\n\t#include \"reflectionCubePS\"\n#elif LIT_REFLECTION_SOURCE == SPHEREMAP\n\t#include \"reflectionSpherePS\"\n#endif\n#ifdef LIT_REFLECTIONS\n\t#ifdef LIT_CLEARCOAT\n\t\t#include \"reflectionCCPS\"\n\t#endif\n\t#ifdef LIT_SHEEN\n\t\t#include \"reflectionSheenPS\"\n\t#endif\n#endif\n#ifdef LIT_REFRACTION\n\t#if defined(LIT_DYNAMIC_REFRACTION)\n\t\t#include \"refractionDynamicPS\"\n\t#elif defined(LIT_REFLECTIONS)\n\t\t#include \"refractionCubePS\"\n\t#endif\n#endif\n#ifdef LIT_SHEEN\n\t#include \"lightSheenPS\"\n#endif\nuniform vec3 material_ambient;\n#ifdef LIT_SPECULAR\n\t#ifdef LIT_LIGHTING\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\t#include \"lightSpecularAnisoGGXPS\"\n\t\t#else\n\t\t\t#include \"lightSpecularBlinnPS\"\n\t\t#endif\n\t#endif\n#endif\n#include \"combinePS\"\n#ifdef LIT_LIGHTMAP\n\t#include \"lightmapAddPS\"\n#endif\n#ifdef LIT_ADD_AMBIENT\n\t#include \"ambientPS\"\n#endif\n#ifdef LIT_MSDF\n\t#include \"msdfPS\"\n#endif\n#ifdef LIT_NEEDS_NORMAL\n\t#include \"viewDirPS\"\n\t#ifdef LIT_SPECULAR\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\t#include \"reflDirAnisoPS\"\n\t\t#else\n\t\t\t#include \"reflDirPS\"\n\t\t#endif\n\t#endif\n#endif\n#include \"lightingPS\"\n`;\n\nvar litForwardPreCodePS$1 = `\n#include \"basePS\"\n#include \"sphericalPS\"\n#include \"decodePS\"\n#include \"gammaPS\"\n#include \"tonemappingPS\"\n#include \"fogPS\"\n#if LIT_NONE_SLICE_MODE == SLICED\n\t#include \"baseNineSlicedPS\"\n#elif LIT_NONE_SLICE_MODE == TILED\n\t#include \"baseNineSlicedTiledPS\"\n#endif\n#ifdef LIT_TBN\n\t#include \"TBNPS\"\n\t#ifdef LIT_TWO_SIDED_LIGHTING\n\t\t#include \"twoSidedLightingPS\"\n\t#endif\n#endif\n`;\n\nvar litMainPS$1 = `\n#include \"varyingsPS\"\n#include \"litUserDeclarationPS\"\n#include \"frontendDeclPS\"\n#if defined(PICK_PASS) || defined(PREPASS_PASS)\n\t#include \"frontendCodePS\"\n\t#include \"litUserCodePS\"\n\t#include \"litOtherMainPS\"\n#elif defined(SHADOW_PASS)\n\t#include \"frontendCodePS\"\n\t#include \"litUserCodePS\"\n\t#include \"litShadowMainPS\"\n#else\n\t#include \"litForwardDeclarationPS\"\n\t#include \"litForwardPreCodePS\"\n\t#include \"frontendCodePS\"\n\t#include \"litForwardPostCodePS\"\n\t#include \"litForwardBackendPS\"\n\t#include \"litUserCodePS\"\n\t#include \"litForwardMainPS\"\n#endif\n`;\n\nvar litMainVS$1 = `\n#include \"varyingsVS\"\n#include  \"litUserDeclarationVS\"\n#ifdef VERTEX_COLOR\n\tattribute vec4 vertex_color;\n#endif\n#ifdef NINESLICED\n\tvarying vec2 vMask;\n\tvarying vec2 vTiledUv;\n\tuniform mediump vec4 innerOffset;\n\tuniform mediump vec2 outerScale;\n\tuniform mediump vec4 atlasRect;\n#endif\nvec3 dPositionW;\nmat4 dModelMatrix;\n#include \"transformCoreVS\"\n#ifdef UV0\n\tattribute vec2 vertex_texCoord0;\n\t#include \"uv0VS\"\n#endif\n#ifdef UV1\n\tattribute vec2 vertex_texCoord1;\n\t#include \"uv1VS\"\n#endif\n#ifdef LINEAR_DEPTH\n\t#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\t\tuniform mat4 matrix_view;\n\t#endif\n#endif\n#include \"transformVS\"\n#ifdef NORMALS\n\t#include \"normalCoreVS\"\n\t#include \"normalVS\"\n#endif\n#ifdef TANGENTS\n\tattribute vec4 vertex_tangent;\n#endif\n#include \"uvTransformUniformsPS, UV_TRANSFORMS_COUNT\"\n#ifdef MSDF\n\t#include \"msdfVS\"\n#endif\n#include  \"litUserCodeVS\"\nvoid main(void) {\n\t#include \"litUserMainStartVS\"\n\tgl_PointSize = 1.0;\n\tgl_Position = getPosition();\n\tvPositionW = getWorldPosition();\n\t#ifdef NORMALS\n\t\tvNormalW = getNormal();\n\t#endif\n\t#ifdef TANGENTS\n\t\tvTangentW = normalize(dNormalMatrix * vertex_tangent.xyz);\n\t\tvBinormalW = cross(vNormalW, vTangentW) * vertex_tangent.w;\n\t#elif defined(GGX_SPECULAR)\n\t\tvObjectSpaceUpW = normalize(dNormalMatrix * vec3(0, 1, 0));\n\t#endif\n\t#ifdef UV0\n\t\tvec2 uv0 = getUv0();\n\t\t#ifdef UV0_UNMODIFIED\n\t\t\tvUv0 = uv0;\n\t\t#endif\n\t#endif\n\t#ifdef UV1\n\t\tvec2 uv1 = getUv1();\n\t\t#ifdef UV1_UNMODIFIED\n\t\t\tvUv1 = uv1;\n\t\t#endif\n\t#endif\n\t#include \"uvTransformVS, UV_TRANSFORMS_COUNT\"\n\t#ifdef VERTEX_COLOR\n\t\tvVertexColor = vertex_color;\n\t#endif\n\t#ifdef LINEAR_DEPTH\n\t\tvLinearDepth = -(matrix_view * vec4(vPositionW, 1.0)).z;\n\t#endif\n\t#ifdef MSDF\n\t\tunpackMsdfParams();\n\t#endif\n\t#include \"litUserMainEndVS\"\n}\n`;\n\nvar litOtherMainPS$1 = `\n#ifdef PICK_PASS\n\t#include \"pickPS\"\n#endif\n#ifdef PREPASS_PASS\n\t#include \"floatAsUintPS\"\n#endif\nvoid main(void) {\n\t#include \"litUserMainStartPS\"\n\tevaluateFrontend();\n\t#ifdef PICK_PASS\n\t\tgl_FragColor = getPickOutput();\n\t#endif\n\t#ifdef PREPASS_PASS\n\t\tgl_FragColor = float2vec4(vLinearDepth);\n\t#endif\n\t#include \"litUserMainEndPS\"\n}\n`;\n\nvar litShaderArgsPS$1 = `\nvec3 litArgs_albedo;\nfloat litArgs_opacity;\nvec3 litArgs_emission;\nvec3 litArgs_worldNormal;\nfloat litArgs_ao;\nvec3 litArgs_lightmap;\nvec3 litArgs_lightmapDir;\nfloat litArgs_metalness;\nvec3 litArgs_specularity;\nfloat litArgs_specularityFactor;\nfloat litArgs_gloss;\nfloat litArgs_sheen_gloss;\nvec3 litArgs_sheen_specularity;\nfloat litArgs_transmission;\nfloat litArgs_thickness;\nfloat litArgs_ior;\nfloat litArgs_dispersion;\nfloat litArgs_iridescence_intensity;\nfloat litArgs_iridescence_thickness;\nvec3 litArgs_clearcoat_worldNormal;\nfloat litArgs_clearcoat_specularity;\nfloat litArgs_clearcoat_gloss;\n`;\n\nvar litShaderCorePS$1 = `\n\t#if LIT_NONE_SLICE_MODE == TILED\n\t\tconst float textureBias = -1000.0;\n\t#else\n\t\tuniform float textureBias;\n\t#endif\n\t#include \"litShaderArgsPS\"\n`;\n\nvar litShadowMainPS$1 = `\n#if LIGHT_TYPE != DIRECTIONAL\n\tuniform vec3 view_position;\n\tuniform float light_radius;\n#endif\n#if SHADOW_TYPE == PCSS_32F\n\t#include \"linearizeDepthPS\"\n#endif\nvoid main(void) {\n\t#include \"litUserMainStartPS\"\n\tevaluateFrontend();\n\t#ifdef PERSPECTIVE_DEPTH\n\t\tfloat depth = gl_FragCoord.z;\n\t\t#if SHADOW_TYPE == PCSS_32F\n\t\t\t#if LIGHT_TYPE != DIRECTIONAL\n\t\t\t\tdepth = linearizeDepthWithParams(depth, camera_params);\n\t\t\t#endif\n\t\t#endif\n\t#else\n\t\tfloat depth = min(distance(view_position, vPositionW) / light_radius, 0.99999);\n\t\t#define MODIFIED_DEPTH\n\t#endif\n\t#if SHADOW_TYPE == VSM_16F || SHADOW_TYPE == VSM_32F\n\t\t#if SHADOW_TYPE == VSM_32F\n\t\t\tfloat exponent = 15.0;\n\t\t#else\n\t\t\tfloat exponent = 5.54;\n\t\t#endif\n\t\tdepth = 2.0 * depth - 1.0;\n\t\tdepth =  exp(exponent * depth);\n\t\tgl_FragColor = vec4(depth, depth*depth, 1.0, 1.0);\n\t#else\n\t\t#if SHADOW_TYPE == PCSS_32F\n\t\t\tgl_FragColor.r = depth;\n\t\t#else\n\t\t\t#ifdef MODIFIED_DEPTH\n\t\t\t\tgl_FragDepth = depth;\n\t\t\t#endif\n\t\t\tgl_FragColor = vec4(1.0);\n\t\t#endif\n\t#endif\n\t#include \"litUserMainEndPS\"\n}\n`;\n\nvar ltcPS$1 = `\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nstruct Coords {\n\tvec3 coord0;\n\tvec3 coord1;\n\tvec3 coord2;\n\tvec3 coord3;\n};\nfloat LTC_EvaluateRect( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in Coords rectCoords) {\n\tvec3 v1 = rectCoords.coord1 - rectCoords.coord0;\n\tvec3 v2 = rectCoords.coord3 - rectCoords.coord0;\n\t\n\tvec3 lightNormal = cross( v1, v2 );\n\tfloat factor = sign(-dot( lightNormal, P - rectCoords.coord0 ));\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 =  factor * cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords.coord0 - P );\n\tcoords[ 1 ] = mat * ( rectCoords.coord1 - P );\n\tcoords[ 2 ] = mat * ( rectCoords.coord2 - P );\n\tcoords[ 3 ] = mat * ( rectCoords.coord3 - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn result;\n}\nCoords dLTCCoords;\nCoords getLTCLightCoords(vec3 lightPos, vec3 halfWidth, vec3 halfHeight){\n\tCoords coords;\n\tcoords.coord0 = lightPos + halfWidth - halfHeight;\n\tcoords.coord1 = lightPos - halfWidth - halfHeight;\n\tcoords.coord2 = lightPos - halfWidth + halfHeight;\n\tcoords.coord3 = lightPos + halfWidth + halfHeight;\n\treturn coords;\n}\nfloat dSphereRadius;\nCoords getSphereLightCoords(vec3 lightPos, vec3 halfWidth, vec3 halfHeight){\n\tdSphereRadius = max(length(halfWidth), length(halfHeight));\n\tvec3 f = reflect(normalize(lightPos - view_position), vNormalW);\n\tvec3 w = normalize(cross(f, halfHeight));\n\tvec3 h = normalize(cross(f, w));\n\treturn getLTCLightCoords(lightPos, w * dSphereRadius, h * dSphereRadius);\n}\nvec2 dLTCUV;\n#ifdef LIT_CLEARCOAT\n\tvec2 ccLTCUV;\n#endif\nvec2 getLTCLightUV(float gloss, vec3 worldNormal, vec3 viewDir)\n{\n\tfloat roughness = max((1.0 - gloss) * (1.0 - gloss), 0.001);\n\treturn LTC_Uv( worldNormal, viewDir, roughness );\n}\nvec3 dLTCSpecFres;\n#ifdef LIT_CLEARCOAT\n\tvec3 ccLTCSpecFres;\n#endif\nvec3 getLTCLightSpecFres(vec2 uv, vec3 specularity)\n{\n\tvec4 t2 = texture2DLod(areaLightsLutTex2, uv, 0.0);\n\treturn specularity * t2.x + ( vec3( 1.0 ) - specularity) * t2.y;\n}\nvoid calcLTCLightValues(float gloss, vec3 worldNormal, vec3 viewDir, vec3 specularity, float clearcoatGloss, vec3 clearcoatWorldNormal, float clearcoatSpecularity)\n{\n\tdLTCUV = getLTCLightUV(gloss, worldNormal, viewDir);\n\tdLTCSpecFres = getLTCLightSpecFres(dLTCUV, specularity); \n#ifdef LIT_CLEARCOAT\n\tccLTCUV = getLTCLightUV(clearcoatGloss, clearcoatWorldNormal, viewDir);\n\tccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3(clearcoatSpecularity));\n#endif\n}\nvoid calcRectLightValues(vec3 lightPos, vec3 halfWidth, vec3 halfHeight) {\n\tdLTCCoords = getLTCLightCoords(lightPos, halfWidth, halfHeight);\n}\nvoid calcDiskLightValues(vec3 lightPos, vec3 halfWidth, vec3 halfHeight) {\n\tcalcRectLightValues(lightPos, halfWidth, halfHeight);\n}\nvoid calcSphereLightValues(vec3 lightPos, vec3 halfWidth, vec3 halfHeight) {\n\tdLTCCoords = getSphereLightCoords(lightPos, halfWidth, halfHeight);\n}\nvec3 SolveCubic(vec4 Coefficient)\n{\n\tfloat pi = 3.14159;\n\tCoefficient.xyz /= Coefficient.w;\n\tCoefficient.yz /= 3.0;\n\tfloat A = Coefficient.w;\n\tfloat B = Coefficient.z;\n\tfloat C = Coefficient.y;\n\tfloat D = Coefficient.x;\n\tvec3 Delta = vec3(\n\t\t-Coefficient.z * Coefficient.z + Coefficient.y,\n\t\t-Coefficient.y * Coefficient.z + Coefficient.x,\n\t\tdot(vec2(Coefficient.z, -Coefficient.y), Coefficient.xy)\n\t);\n\tfloat Discriminant = dot(vec2(4.0 * Delta.x, -Delta.y), Delta.zy);\n\tvec2 xlc, xsc;\n\t{\n\t\tfloat A_a = 1.0;\n\t\tfloat C_a = Delta.x;\n\t\tfloat D_a = -2.0 * B * Delta.x + Delta.y;\n\t\tfloat Theta = atan(sqrt(Discriminant), -D_a) / 3.0;\n\t\tfloat x_1a = 2.0 * sqrt(-C_a) * cos(Theta);\n\t\tfloat x_3a = 2.0 * sqrt(-C_a) * cos(Theta + (2.0 / 3.0) * pi);\n\t\tfloat xl;\n\t\tif ((x_1a + x_3a) > 2.0 * B)\n\t\t\txl = x_1a;\n\t\telse\n\t\t\txl = x_3a;\n\t\txlc = vec2(xl - B, A);\n\t}\n\t{\n\t\tfloat A_d = D;\n\t\tfloat C_d = Delta.z;\n\t\tfloat D_d = -D * Delta.y + 2.0 * C * Delta.z;\n\t\tfloat Theta = atan(D * sqrt(Discriminant), -D_d) / 3.0;\n\t\tfloat x_1d = 2.0 * sqrt(-C_d) * cos(Theta);\n\t\tfloat x_3d = 2.0 * sqrt(-C_d) * cos(Theta + (2.0 / 3.0) * pi);\n\t\tfloat xs;\n\t\tif (x_1d + x_3d < 2.0 * C)\n\t\t\txs = x_1d;\n\t\telse\n\t\t\txs = x_3d;\n\t\txsc = vec2(-D, xs + C);\n\t}\n\tfloat E =  xlc.y * xsc.y;\n\tfloat F = -xlc.x * xsc.y - xlc.y * xsc.x;\n\tfloat G =  xlc.x * xsc.x;\n\tvec2 xmc = vec2(C * F - B * G, -B * F + C * E);\n\tvec3 Root = vec3(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y);\n\tif (Root.x < Root.y && Root.x < Root.z)\n\t\tRoot.xyz = Root.yxz;\n\telse if (Root.z < Root.x && Root.z < Root.y)\n\t\tRoot.xyz = Root.xzy;\n\treturn Root;\n}\nfloat LTC_EvaluateDisk(vec3 N, vec3 V, vec3 P, mat3 Minv, Coords points)\n{\n\tvec3 T1 = normalize(V - N * dot(V, N));\n\tvec3 T2 = cross(N, T1);\n\tmat3 R = transposeMat3( mat3( T1, T2, N ) );\n\tvec3 L_[ 3 ];\n\tL_[ 0 ] = R * ( points.coord0 - P );\n\tL_[ 1 ] = R * ( points.coord1 - P );\n\tL_[ 2 ] = R * ( points.coord2 - P );\n\tvec3 C  = 0.5 * (L_[0] + L_[2]);\n\tvec3 V1 = 0.5 * (L_[1] - L_[2]);\n\tvec3 V2 = 0.5 * (L_[1] - L_[0]);\n\tC  = Minv * C;\n\tV1 = Minv * V1;\n\tV2 = Minv * V2;\n\tfloat a, b;\n\tfloat d11 = dot(V1, V1);\n\tfloat d22 = dot(V2, V2);\n\tfloat d12 = dot(V1, V2);\n\tif (abs(d12) / sqrt(d11 * d22) > 0.0001)\n\t{\n\t\tfloat tr = d11 + d22;\n\t\tfloat det = -d12 * d12 + d11 * d22;\n\t\tdet = sqrt(det);\n\t\tfloat u = 0.5 * sqrt(tr - 2.0 * det);\n\t\tfloat v = 0.5 * sqrt(tr + 2.0 * det);\n\t\tfloat e_max = (u + v) * (u + v);\n\t\tfloat e_min = (u - v) * (u - v);\n\t\tvec3 V1_, V2_;\n\t\tif (d11 > d22)\n\t\t{\n\t\t\tV1_ = d12 * V1 + (e_max - d11) * V2;\n\t\t\tV2_ = d12 * V1 + (e_min - d11) * V2;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tV1_ = d12*V2 + (e_max - d22)*V1;\n\t\t\tV2_ = d12*V2 + (e_min - d22)*V1;\n\t\t}\n\t\ta = 1.0 / e_max;\n\t\tb = 1.0 / e_min;\n\t\tV1 = normalize(V1_);\n\t\tV2 = normalize(V2_);\n\t}\n\telse\n\t{\n\t\ta = 1.0 / dot(V1, V1);\n\t\tb = 1.0 / dot(V2, V2);\n\t\tV1 *= sqrt(a);\n\t\tV2 *= sqrt(b);\n\t}\n\tvec3 V3 = normalize(cross(V1, V2));\n\tif (dot(C, V3) < 0.0)\n\t\tV3 *= -1.0;\n\tfloat L  = dot(V3, C);\n\tfloat x0 = dot(V1, C) / L;\n\tfloat y0 = dot(V2, C) / L;\n\tfloat E1 = inversesqrt(a);\n\tfloat E2 = inversesqrt(b);\n\ta *= L * L;\n\tb *= L * L;\n\tfloat c0 = a * b;\n\tfloat c1 = a * b * (1.0 + x0 * x0 + y0 * y0) - a - b;\n\tfloat c2 = 1.0 - a * (1.0 + x0 * x0) - b * (1.0 + y0 * y0);\n\tfloat c3 = 1.0;\n\tvec3 roots = SolveCubic(vec4(c0, c1, c2, c3));\n\tfloat e1 = roots.x;\n\tfloat e2 = roots.y;\n\tfloat e3 = roots.z;\n\tvec3 avgDir = vec3(a * x0 / (a - e2), b * y0 / (b - e2), 1.0);\n\tmat3 rotate = mat3(V1, V2, V3);\n\tavgDir = rotate * avgDir;\n\tavgDir = normalize(avgDir);\n\tfloat L1 = sqrt(-e2 / e3);\n\tfloat L2 = sqrt(-e2 / e1);\n\tfloat formFactor = max(0.0, L1 * L2 * inversesqrt((1.0 + L1 * L1) * (1.0 + L2 * L2)));\n\t\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tvec2 uv = vec2(avgDir.z * 0.5 + 0.5, formFactor);\n\tuv = uv*LUT_SCALE + LUT_BIAS;\n\tfloat scale = texture2DLod(areaLightsLutTex2, uv, 0.0).w;\n\treturn formFactor*scale;\n}\nfloat FixNan(float value) {\n\t#ifdef WEBGPU\n\t\treturn value != value ? 0.0 : value;\n\t#else\n\t\treturn isnan(value) ? 0.0 : value;\n\t#endif\n}\nfloat getRectLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDir, vec3 lightDirNorm) {\n\treturn LTC_EvaluateRect( worldNormal, viewDir, vPositionW, mat3( 1.0 ), dLTCCoords );\n}\nfloat getDiskLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDir, vec3 lightDirNorm) {\n\treturn FixNan(LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, mat3( 1.0 ), dLTCCoords ));\n}\nfloat getSphereLightDiffuse(vec3 worldNormal, vec3 viewDir, vec3 lightDir, vec3 lightDirNorm) {\n\tfloat falloff = dSphereRadius / (dot(lightDir, lightDir) + dSphereRadius);\n\treturn FixNan(getLightDiffuse(worldNormal, viewDir, lightDirNorm) * falloff);\n}\nmat3 getLTCLightInvMat(vec2 uv)\n{\n\tvec4 t1 = texture2DLod(areaLightsLutTex1, uv, 0.0);\n\treturn mat3(\n\t\tvec3( t1.x, 0, t1.y ),\n\t\tvec3(\t0, 1,\t0 ),\n\t\tvec3( t1.z, 0, t1.w )\n\t);\n}\nfloat calcRectLightSpecular(vec3 worldNormal, vec3 viewDir, vec2 uv) {\n\tmat3 mInv = getLTCLightInvMat(uv);\n\treturn LTC_EvaluateRect( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );\n}\nfloat getRectLightSpecular(vec3 worldNormal, vec3 viewDir) {\n\treturn calcRectLightSpecular(worldNormal, viewDir, dLTCUV);\n}\nfloat calcDiskLightSpecular(vec3 worldNormal, vec3 viewDir, vec2 uv) {\n\tmat3 mInv = getLTCLightInvMat(uv);\n\treturn LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );\n}\nfloat getDiskLightSpecular(vec3 worldNormal, vec3 viewDir) {\n\treturn calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);\n}\nfloat getSphereLightSpecular(vec3 worldNormal, vec3 viewDir) {\n\treturn calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);\n}\n`;\n\nvar metalnessPS$1 = `\n#ifdef STD_METALNESS_CONSTANT\nuniform float material_metalness;\n#endif\nvoid getMetalness() {\n\tfloat metalness = 1.0;\n\t#ifdef STD_METALNESS_CONSTANT\n\tmetalness *= material_metalness;\n\t#endif\n\t#ifdef STD_METALNESS_TEXTURE\n\tmetalness *= texture2DBias({STD_METALNESS_TEXTURE_NAME}, {STD_METALNESS_TEXTURE_UV}, textureBias).{STD_METALNESS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_METALNESS_VERTEX\n\tmetalness *= saturate(vVertexColor.{STD_METALNESS_VERTEX_CHANNEL});\n\t#endif\n\tdMetalness = metalness;\n}\n`;\n\nvar msdfPS$1 = `\nuniform sampler2D texture_msdfMap;\nfloat median(float r, float g, float b) {\n\treturn max(min(r, g), min(max(r, g), b));\n}\nfloat map (float min, float max, float v) {\n\treturn (v - min) / (max - min);\n}\nuniform float font_sdfIntensity;\nuniform float font_pxrange;\nuniform float font_textureWidth;\n#ifndef LIT_MSDF_TEXT_ATTRIBUTE\n\tuniform vec4 outline_color;\n\tuniform float outline_thickness;\n\tuniform vec4 shadow_color;\n\tuniform vec2 shadow_offset;\n#else\n\tvarying vec4 outline_color;\n\tvarying float outline_thickness;\n\tvarying vec4 shadow_color;\n\tvarying vec2 shadow_offset;\n#endif\nvec4 applyMsdf(vec4 color) {\n\tcolor.rgb = gammaCorrectInput(color.rgb);\n\tvec3 tsample = texture2D(texture_msdfMap, vUv0).rgb;\n\tvec2 uvShdw = vUv0 - shadow_offset;\n\tvec3 ssample = texture2D(texture_msdfMap, uvShdw).rgb;\n\tfloat sigDist = median(tsample.r, tsample.g, tsample.b);\n\tfloat sigDistShdw = median(ssample.r, ssample.g, ssample.b);\n\tfloat smoothingMax = 0.2;\n\tvec2 w = fwidth(vUv0);\n\tfloat smoothing = clamp(w.x * font_textureWidth / font_pxrange, 0.0, smoothingMax);\n\tfloat mapMin = 0.05;\n\tfloat mapMax = clamp(1.0 - font_sdfIntensity, mapMin, 1.0);\n\tfloat sigDistInner = map(mapMin, mapMax, sigDist);\n\tfloat sigDistOutline = map(mapMin, mapMax, sigDist + outline_thickness);\n\tsigDistShdw = map(mapMin, mapMax, sigDistShdw + outline_thickness);\n\tfloat center = 0.5;\n\tfloat inside = smoothstep(center-smoothing, center+smoothing, sigDistInner);\n\tfloat outline = smoothstep(center-smoothing, center+smoothing, sigDistOutline);\n\tfloat shadow = smoothstep(center-smoothing, center+smoothing, sigDistShdw);\n\tvec4 tcolor = (outline > inside) ? outline * vec4(outline_color.a * outline_color.rgb, outline_color.a) : vec4(0.0);\n\ttcolor = mix(tcolor, color, inside);\n\tvec4 scolor = (shadow > outline) ? shadow * vec4(shadow_color.a * shadow_color.rgb, shadow_color.a) : tcolor;\n\ttcolor = mix(scolor, tcolor, outline);\n\ttcolor.rgb = gammaCorrectOutput(tcolor.rgb);\n\t\n\treturn tcolor;\n}\n`;\n\nvar metalnessModulatePS$1 = `\nvec3 getSpecularModulate(in vec3 specularity, in vec3 albedo, in float metalness, in float f0) {\n\tvec3 dielectricF0 = f0 * specularity;\n\treturn mix(dielectricF0, albedo, metalness);\n}\nvec3 getAlbedoModulate(in vec3 albedo, in float metalness) {\n\treturn albedo * (1.0 - metalness);\n}\n`;\n\nvar morphPS$1 = `\n\tvarying vec2 uv0;\n\tuniform sampler2DArray morphTexture;\n\tuniform highp float morphFactor[{MORPH_TEXTURE_MAX_COUNT}];\n\tuniform highp uint morphIndex[{MORPH_TEXTURE_MAX_COUNT}];\n\tuniform int count;\n\t#ifdef MORPH_INT\n\t\tuniform vec3 aabbSize;\n\t\tuniform vec3 aabbMin;\n\t#endif\n\tvoid main (void) {\n\t\thighp vec3 color = vec3(0, 0, 0);\n\t\tivec2 pixelCoords = ivec2(uv0 * vec2(textureSize(morphTexture, 0).xy));\n\t\t\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tuint textureIndex = morphIndex[i];\n\t\t\tvec3 delta = texelFetch(morphTexture, ivec3(pixelCoords, int(textureIndex)), 0).xyz;\n\t\t\tcolor += morphFactor[i] * delta;\n\t\t}\n\t\t#ifdef MORPH_INT\n\t\t\tcolor = (color - aabbMin) / aabbSize * 65535.0;\n\t\t\tgl_FragColor = uvec4(color, 1u);\n\t\t#else\n\t\t\tgl_FragColor = vec4(color, 1.0);\n\t\t#endif\n\t}\n`;\n\nvar morphVS$1 = `\n\tattribute vec2 vertex_position;\n\tvarying vec2 uv0;\n\tvoid main(void) {\n\t\tgl_Position = vec4(vertex_position, 0.5, 1.0);\n\t\tuv0 = vertex_position.xy * 0.5 + 0.5;\n\t}\n`;\n\nvar msdfVS$1 = `\nattribute vec3 vertex_outlineParameters;\nattribute vec3 vertex_shadowParameters;\nvarying vec4 outline_color;\nvarying float outline_thickness;\nvarying vec4 shadow_color;\nvarying vec2 shadow_offset;\nvoid unpackMsdfParams() {\n\tvec3 little = mod(vertex_outlineParameters, 256.);\n\tvec3 big = (vertex_outlineParameters - little) / 256.;\n\toutline_color.rb = little.xy / 255.;\n\toutline_color.ga = big.xy / 255.;\n\toutline_thickness = little.z / 255. * 0.2;\n\tlittle = mod(vertex_shadowParameters, 256.);\n\tbig = (vertex_shadowParameters - little) / 256.;\n\tshadow_color.rb = little.xy / 255.;\n\tshadow_color.ga = big.xy / 255.;\n\tshadow_offset = (vec2(little.z, big.z) / 127. - 1.) * 0.005;\n}\n`;\n\nvar normalVS$1 = `\nmat3 dNormalMatrix;\nvec3 getNormal() {\n\tdNormalMatrix = getNormalMatrix(dModelMatrix);\n\tvec3 localNormal = getLocalNormal(vertex_normal);\n\treturn normalize(dNormalMatrix * localNormal);\n}\n`;\n\nvar normalCoreVS$1 = `\nattribute vec3 vertex_normal;\nuniform mat3 matrix_normal;\n#ifdef MORPHING_NORMAL\n\t#ifdef MORPHING_INT\n\t\tuniform highp usampler2D morphNormalTex;\n\t#else\n\t\tuniform highp sampler2D morphNormalTex;\n\t#endif\n#endif\nvec3 getLocalNormal(vec3 vertexNormal) {\n\tvec3 localNormal = vertex_normal;\n\t#ifdef MORPHING_NORMAL\n\t\tivec2 morphUV = getTextureMorphCoords();\n\t\t#ifdef MORPHING_INT\n\t\t\tvec3 morphNormal = vec3(texelFetch(morphNormalTex, ivec2(morphUV), 0).xyz) / 65535.0 * 2.0 - 1.0;\n\t\t#else\n\t\t\tvec3 morphNormal = texelFetch(morphNormalTex, ivec2(morphUV), 0).xyz;\n\t\t#endif\n\t\tlocalNormal += morphNormal;\n\t#endif\n\treturn localNormal;\n}\n#if defined(SKIN) || defined(BATCH)\n\tmat3 getNormalMatrix(mat4 modelMatrix) {\n\t\treturn mat3(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);\n\t}\n#elif defined(INSTANCING)\n\tmat3 getNormalMatrix(mat4 modelMatrix) {\n\t\treturn mat3(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);\n\t}\n#else\n\tmat3 getNormalMatrix(mat4 modelMatrix) {\n\t\treturn matrix_normal;\n\t}\n#endif\n`;\n\nvar normalMapPS$1 = `\n#ifdef STD_NORMAL_TEXTURE\n\tuniform float material_bumpiness;\n#endif\n#ifdef STD_NORMALDETAIL_TEXTURE\n\tuniform float material_normalDetailMapBumpiness;\n\tvec3 blendNormals(vec3 n1, vec3 n2) {\n\t\tn1 += vec3(0, 0, 1);\n\t\tn2 *= vec3(-1, -1, 1);\n\t\treturn n1 * dot(n1, n2) / n1.z - n2;\n\t}\n#endif\nvoid getNormal() {\n#ifdef STD_NORMAL_TEXTURE\n\tvec3 normalMap = {STD_NORMAL_TEXTURE_DECODE}(texture2DBias({STD_NORMAL_TEXTURE_NAME}, {STD_NORMAL_TEXTURE_UV}, textureBias));\n\tnormalMap = mix(vec3(0.0, 0.0, 1.0), normalMap, material_bumpiness);\n\t#ifdef STD_NORMALDETAIL_TEXTURE\n\t\tvec3 normalDetailMap = {STD_NORMALDETAIL_TEXTURE_DECODE}(texture2DBias({STD_NORMALDETAIL_TEXTURE_NAME}, {STD_NORMALDETAIL_TEXTURE_UV}, textureBias));\n\t\tnormalDetailMap = mix(vec3(0.0, 0.0, 1.0), normalDetailMap, material_normalDetailMapBumpiness);\n\t\tnormalMap = blendNormals(normalMap, normalDetailMap);\n\t#endif\n\tdNormalW = normalize(dTBN * normalMap);\n#else\n\tdNormalW = dVertexNormalW;\n#endif\n}\n`;\n\nvar opacityPS$1 = `\nuniform float material_opacity;\nvoid getOpacity() {\n\tdAlpha = material_opacity;\n\t#ifdef STD_OPACITY_TEXTURE\n\tdAlpha *= texture2DBias({STD_OPACITY_TEXTURE_NAME}, {STD_OPACITY_TEXTURE_UV}, textureBias).{STD_OPACITY_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_OPACITY_VERTEX\n\tdAlpha *= clamp(vVertexColor.{STD_OPACITY_VERTEX_CHANNEL}, 0.0, 1.0);\n\t#endif\n}\n`;\n\nvar opacityDitherPS$1 = `\n#if STD_OPACITY_DITHER == BAYER8\n\t#include \"bayerPS\"\n#endif\nuniform vec4 blueNoiseJitter;\n#if STD_OPACITY_DITHER == BLUENOISE\n\tuniform sampler2D blueNoiseTex32;\n#endif\nvoid opacityDither(float alpha, float id) {\n\t#if STD_OPACITY_DITHER == BAYER8\n\t\tfloat noise = bayer8(floor(mod(gl_FragCoord.xy + blueNoiseJitter.xy + id, 8.0))) / 64.0;\n\t#else\n\t\t#if STD_OPACITY_DITHER == BLUENOISE\n\t\t\tvec2 uv = fract(gl_FragCoord.xy / 32.0 + blueNoiseJitter.xy + id);\n\t\t\tfloat noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;\n\t\t#endif\n\t\t#if STD_OPACITY_DITHER == IGNNOISE\n\t\t\tvec3 magic = vec3(0.06711056, 0.00583715, 52.9829189);\n\t\t\tfloat noise = fract(magic.z * fract(dot(gl_FragCoord.xy + blueNoiseJitter.xy + id, magic.xy)));\n\t\t#endif\n\t#endif\n\tnoise = pow(noise, 2.2);\n\tif (alpha < noise)\n\t\tdiscard;\n}\n`;\n\nvar outputPS$1 = `\n`;\n\nvar outputAlphaPS$1 = `\n#if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == ADDITIVEALPHA || defined(LIT_ALPHA_TO_COVERAGE)\n\tgl_FragColor.a = litArgs_opacity;\n#elif LIT_BLEND_TYPE == PREMULTIPLIED\n\tgl_FragColor.rgb *= litArgs_opacity;\n\tgl_FragColor.a = litArgs_opacity;\n#else\n\tgl_FragColor.a = 1.0;\n#endif\n`;\n\nvar outputTex2DPS$1 = `\nvarying vec2 vUv0;\nuniform sampler2D source;\nvoid main(void) {\n\tgl_FragColor = texture2D(source, vUv0);\n}\n`;\n\nvar sheenPS$1 = `\nuniform vec3 material_sheen;\nvoid getSheen() {\n\tvec3 sheenColor = material_sheen;\n\t#ifdef STD_SHEEN_TEXTURE\n\tsheenColor *= {STD_SHEEN_TEXTURE_DECODE}(texture2DBias({STD_SHEEN_TEXTURE_NAME}, {STD_SHEEN_TEXTURE_UV}, textureBias)).{STD_SHEEN_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SHEEN_VERTEX\n\tsheenColor *= saturate(vVertexColor.{STD_SHEEN_VERTEX_CHANNEL});\n\t#endif\n\tsSpecularity = sheenColor;\n}\n`;\n\nvar sheenGlossPS$1 = `\nuniform float material_sheenGloss;\nvoid getSheenGlossiness() {\n\tfloat sheenGlossiness = material_sheenGloss;\n\t#ifdef STD_SHEENGLOSS_TEXTURE\n\tsheenGlossiness *= texture2DBias({STD_SHEENGLOSS_TEXTURE_NAME}, {STD_SHEENGLOSS_TEXTURE_UV}, textureBias).{STD_SHEENGLOSS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SHEENGLOSS_VERTEX\n\tsheenGlossiness *= saturate(vVertexColor.{STD_SHEENGLOSS_VERTEX_CHANNEL});\n\t#endif\n\t#ifdef STD_SHEENGLOSS_INVERT\n\tsheenGlossiness = 1.0 - sheenGlossiness;\n\t#endif\n\tsGlossiness = sheenGlossiness + 0.0000001;\n}\n`;\n\nvar parallaxPS$1 = `\nuniform float material_heightMapFactor;\nvoid getParallax() {\n\tfloat parallaxScale = material_heightMapFactor;\n\tfloat height = texture2DBias({STD_HEIGHT_TEXTURE_NAME}, {STD_HEIGHT_TEXTURE_UV}, textureBias).{STD_HEIGHT_TEXTURE_CHANNEL};\n\theight = height * parallaxScale - parallaxScale * 0.5;\n\tvec3 viewDirT = dViewDirW * dTBN;\n\tviewDirT.z += 0.42;\n\tdUvOffset = height * (viewDirT.xy / viewDirT.z);\n}\n`;\n\nvar pickPS$1 = `\nuniform uint meshInstanceId;\nvec4 getPickOutput() {\n\tconst vec4 inv = vec4(1.0 / 255.0);\n\tconst uvec4 shifts = uvec4(16, 8, 0, 24);\n\tuvec4 col = (uvec4(meshInstanceId) >> shifts) & uvec4(0xff);\n\treturn vec4(col) * inv;\n}\n`;\n\nvar reflDirPS$1 = `\nvoid getReflDir(vec3 worldNormal, vec3 viewDir, float gloss, mat3 tbn) {\n\tdReflDirW = normalize(-reflect(viewDir, worldNormal));\n}\n`;\n\nvar reflDirAnisoPS$1 = `\nvoid getReflDir(vec3 worldNormal, vec3 viewDir, float gloss, mat3 tbn) {\n\tfloat roughness = sqrt(1.0 - min(gloss, 1.0));\n\tvec2 direction = dAnisotropyRotation;\n\tvec3 anisotropicT = normalize(tbn * vec3(direction, 0.0));\n\tvec3 anisotropicB = normalize(cross(tbn[2], anisotropicT));\n\tfloat anisotropy = dAnisotropy;\n\tvec3 anisotropicDirection = anisotropicB;\n\tvec3 anisotropicTangent = cross(anisotropicDirection, viewDir);\n\tvec3 anisotropicNormal = cross(anisotropicTangent, anisotropicDirection);\n\tfloat bendFactor = 1.0 - anisotropy * (1.0 - roughness);\n\tfloat bendFactor4 = bendFactor * bendFactor * bendFactor * bendFactor;\n\tvec3 bentNormal = normalize(mix(normalize(anisotropicNormal), normalize(worldNormal), bendFactor4));\n\tdReflDirW = reflect(-viewDir, bentNormal);\n}\n`;\n\nvar reflectionCCPS$1 = `\n#ifdef LIT_CLEARCOAT\nvoid addReflectionCC(vec3 reflDir, float gloss) {\n\tccReflection += calcReflection(reflDir, gloss);\n}\n#endif\n`;\n\nvar reflectionCubePS$1 = `\nuniform samplerCube texture_cubeMap;\nuniform float material_reflectivity;\nvec3 calcReflection(vec3 reflDir, float gloss) {\n\tvec3 lookupVec = cubeMapProject(reflDir);\n\tlookupVec.x *= -1.0;\n\treturn {reflectionDecode}(textureCube(texture_cubeMap, lookupVec));\n}\nvoid addReflection(vec3 reflDir, float gloss) {   \n\tdReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);\n}\n`;\n\nvar reflectionEnvHQPS$1 = `\n#ifndef ENV_ATLAS\n\t#define ENV_ATLAS\n\tuniform sampler2D texture_envAtlas;\n#endif\nuniform samplerCube texture_cubeMap;\nuniform float material_reflectivity;\nvec3 calcReflection(vec3 reflDir, float gloss) {\n\tvec3 dir = cubeMapProject(reflDir) * vec3(-1.0, 1.0, 1.0);\n\tvec2 uv = toSphericalUv(dir);\n\tfloat level = saturate(1.0 - gloss) * 5.0;\n\tfloat ilevel = floor(level);\n\tfloat flevel = level - ilevel;\n\tvec3 sharp = {reflectionCubemapDecode}(textureCube(texture_cubeMap, dir));\n\tvec3 roughA = {reflectionDecode}(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel)));\n\tvec3 roughB = {reflectionDecode}(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel + 1.0)));\n\treturn processEnvironment(mix(sharp, mix(roughA, roughB, flevel), min(level, 1.0)));\n}\nvoid addReflection(vec3 reflDir, float gloss) {   \n\tdReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);\n}\n`;\n\nvar reflectionEnvPS$1 = `\n#ifndef ENV_ATLAS\n#define ENV_ATLAS\n\tuniform sampler2D texture_envAtlas;\n#endif\nuniform float material_reflectivity;\nfloat shinyMipLevel(vec2 uv) {\n\tvec2 dx = dFdx(uv);\n\tvec2 dy = dFdy(uv);\n\tvec2 uv2 = vec2(fract(uv.x + 0.5), uv.y);\n\tvec2 dx2 = dFdx(uv2);\n\tvec2 dy2 = dFdy(uv2);\n\tfloat maxd = min(max(dot(dx, dx), dot(dy, dy)), max(dot(dx2, dx2), dot(dy2, dy2)));\n\treturn clamp(0.5 * log2(maxd) - 1.0 + textureBias, 0.0, 5.0);\n}\nvec3 calcReflection(vec3 reflDir, float gloss) {\n\tvec3 dir = cubeMapProject(reflDir) * vec3(-1.0, 1.0, 1.0);\n\tvec2 uv = toSphericalUv(dir);\n\tfloat level = saturate(1.0 - gloss) * 5.0;\n\tfloat ilevel = floor(level);\n\tfloat level2 = shinyMipLevel(uv * atlasSize);\n\tfloat ilevel2 = floor(level2);\n\tvec2 uv0, uv1;\n\tfloat weight;\n\tif (ilevel == 0.0) {\n\t\tuv0 = mapShinyUv(uv, ilevel2);\n\t\tuv1 = mapShinyUv(uv, ilevel2 + 1.0);\n\t\tweight = level2 - ilevel2;\n\t} else {\n\t\tuv0 = uv1 = mapRoughnessUv(uv, ilevel);\n\t\tweight = 0.0;\n\t}\n\tvec3 linearA = {reflectionDecode}(texture2D(texture_envAtlas, uv0));\n\tvec3 linearB = {reflectionDecode}(texture2D(texture_envAtlas, uv1));\n\tvec3 linear0 = mix(linearA, linearB, weight);\n\tvec3 linear1 = {reflectionDecode}(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel + 1.0)));\n\treturn processEnvironment(mix(linear0, linear1, level - ilevel));\n}\nvoid addReflection(vec3 reflDir, float gloss) {   \n\tdReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);\n}\n`;\n\nvar reflectionSpherePS$1 = `\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform mat4 matrix_view;\n#endif\nuniform sampler2D texture_sphereMap;\nuniform float material_reflectivity;\nvec3 calcReflection(vec3 reflDir, float gloss) {\n\tvec3 reflDirV = (mat3(matrix_view) * reflDir);\n\tfloat m = 2.0 * sqrt(dot(reflDirV.xy, reflDirV.xy) + (reflDirV.z + 1.0) * (reflDirV.z + 1.0));\n\tvec2 sphereMapUv = reflDirV.xy / m + 0.5;\n\treturn {reflectionDecode}(texture2D(texture_sphereMap, sphereMapUv));\n}\nvoid addReflection(vec3 reflDir, float gloss) {   \n\tdReflection += vec4(calcReflection(reflDir, gloss), material_reflectivity);\n}\n`;\n\nvar reflectionSheenPS$1 = `\nvoid addReflectionSheen(vec3 worldNormal, vec3 viewDir, float gloss) {\n\tfloat NoV = dot(worldNormal, viewDir);\n\tfloat alphaG = gloss * gloss;\n\tfloat a = gloss < 0.25 ? -339.2 * alphaG + 161.4 * gloss - 25.9 : -8.48 * alphaG + 14.3 * gloss - 9.95;\n\tfloat b = gloss < 0.25 ? 44.0 * alphaG - 23.7 * gloss + 3.26 : 1.97 * alphaG - 3.27 * gloss + 0.72;\n\tfloat DG = exp( a * NoV + b ) + ( gloss < 0.25 ? 0.0 : 0.1 * ( gloss - 0.25 ) );\n\tsReflection += calcReflection(worldNormal, 0.0) * saturate(DG);\n}\n`;\n\nvar refractionCubePS$1 = `\nvec3 refract2(vec3 viewVec, vec3 normal, float IOR) {\n\tfloat vn = dot(viewVec, normal);\n\tfloat k = 1.0 - IOR * IOR * (1.0 - vn * vn);\n\tvec3 refrVec = IOR * viewVec - (IOR * vn + sqrt(k)) * normal;\n\treturn refrVec;\n}\nvoid addRefraction(\n\tvec3 worldNormal, \n\tvec3 viewDir, \n\tfloat thickness, \n\tfloat gloss, \n\tvec3 specularity, \n\tvec3 albedo, \n\tfloat transmission,\n\tfloat refractionIndex,\n\tfloat dispersion\n#if defined(LIT_IRIDESCENCE)\n\t, vec3 iridescenceFresnel,\n\tfloat iridescenceIntensity\n#endif \n) {\n\tvec4 tmpRefl = dReflection;\n\tvec3 reflectionDir = refract2(-viewDir, worldNormal, refractionIndex);\n\tdReflection = vec4(0);\n\taddReflection(reflectionDir, gloss);\n\tdDiffuseLight = mix(dDiffuseLight, dReflection.rgb * albedo, transmission);\n\tdReflection = tmpRefl;\n}\n`;\n\nvar refractionDynamicPS$1 = `\nuniform float material_invAttenuationDistance;\nuniform vec3 material_attenuation;\nvec3 evalRefractionColor(vec3 refractionVector, float gloss, float refractionIndex) {\n\tvec4 pointOfRefraction = vec4(vPositionW + refractionVector, 1.0);\n\tvec4 projectionPoint = matrix_viewProjection * pointOfRefraction;\n\tvec2 uv = getGrabScreenPos(projectionPoint);\n\tfloat iorToRoughness = (1.0 - gloss) * clamp((1.0 / refractionIndex) * 2.0 - 2.0, 0.0, 1.0);\n\tfloat refractionLod = log2(uScreenSize.x) * iorToRoughness;\n\tvec3 refraction = texture2DLod(uSceneColorMap, uv, refractionLod).rgb;\n\treturn refraction;\n}\nvoid addRefraction(\n\tvec3 worldNormal, \n\tvec3 viewDir, \n\tfloat thickness, \n\tfloat gloss, \n\tvec3 specularity, \n\tvec3 albedo, \n\tfloat transmission,\n\tfloat refractionIndex,\n\tfloat dispersion\n#if defined(LIT_IRIDESCENCE)\n\t, vec3 iridescenceFresnel,\n\tfloat iridescenceIntensity\n#endif\n) {\n\tvec3 modelScale;\n\tmodelScale.x = length(vec3(matrix_model[0].xyz));\n\tmodelScale.y = length(vec3(matrix_model[1].xyz));\n\tmodelScale.z = length(vec3(matrix_model[2].xyz));\n\tvec3 scale = thickness * modelScale;\n\tvec3 refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndex)) * scale;\n\tvec3 refraction = evalRefractionColor(refractionVector, gloss, refractionIndex);\n\t#ifdef LIT_DISPERSION\n\t\tfloat halfSpread = (1.0 / refractionIndex - 1.0) * 0.025 * dispersion;\n\t\tfloat refractionIndexR = refractionIndex - halfSpread;\n\t\trefractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexR)) * scale;\n\t\trefraction.r = evalRefractionColor(refractionVector, gloss, refractionIndexR).r;\n\t\tfloat refractionIndexB = refractionIndex + halfSpread;\n\t\trefractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexB)) * scale;\n\t\trefraction.b = evalRefractionColor(refractionVector, gloss, refractionIndexB).b;\n\t#endif\n\tvec3 transmittance;\n\tif (material_invAttenuationDistance != 0.0)\n\t{\n\t\tvec3 attenuation = -log(material_attenuation) * material_invAttenuationDistance;\n\t\ttransmittance = exp(-attenuation * length(refractionVector));\n\t}\n\telse\n\t{\n\t\ttransmittance = refraction;\n\t}\n\tvec3 fresnel = vec3(1.0) - \n\t\tgetFresnel(\n\t\t\tdot(viewDir, worldNormal), \n\t\t\tgloss, \n\t\t\tspecularity\n\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t, iridescenceFresnel,\n\t\t\tiridescenceIntensity\n\t\t#endif\n\t\t);\n\tdDiffuseLight = mix(dDiffuseLight, refraction * transmittance * fresnel, transmission);\n}\n`;\n\nvar reprojectPS$1 = `\nvarying vec2 vUv0;\n#ifdef CUBEMAP_SOURCE\n\tuniform samplerCube sourceCube;\n#else\n\tuniform sampler2D sourceTex;\n#endif\n#ifdef USE_SAMPLES_TEX\n\tuniform sampler2D samplesTex;\n\tuniform vec2 samplesTexInverseSize;\n#endif\nuniform vec3 params;\nfloat targetFace() { return params.x; }\nfloat targetTotalPixels() { return params.y; }\nfloat sourceTotalPixels() { return params.z; }\nfloat PI = 3.141592653589793;\nfloat saturate(float x) {\n\treturn clamp(x, 0.0, 1.0);\n}\n#include \"decodePS\"\n#include \"encodePS\"\nvec3 modifySeams(vec3 dir, float scale) {\n\tvec3 adir = abs(dir);\n\tfloat M = max(max(adir.x, adir.y), adir.z);\n\treturn dir / M * vec3(\n\t\tadir.x == M ? 1.0 : scale,\n\t\tadir.y == M ? 1.0 : scale,\n\t\tadir.z == M ? 1.0 : scale\n\t);\n}\nvec2 toSpherical(vec3 dir) {\n\treturn vec2(dir.xz == vec2(0.0) ? 0.0 : atan(dir.x, dir.z), asin(dir.y));\n}\nvec3 fromSpherical(vec2 uv) {\n\treturn vec3(cos(uv.y) * sin(uv.x),\n\t\t\t\tsin(uv.y),\n\t\t\t\tcos(uv.y) * cos(uv.x));\n}\nvec3 getDirectionEquirect() {\n\treturn fromSpherical((vec2(vUv0.x, 1.0 - vUv0.y) * 2.0 - 1.0) * vec2(PI, PI * 0.5));\n}\nfloat signNotZero(float k){\n\treturn(k >= 0.0) ? 1.0 : -1.0;\n}\nvec2 signNotZero(vec2 v) {\n\treturn vec2(signNotZero(v.x), signNotZero(v.y));\n}\nvec3 octDecode(vec2 o) {\n\tvec3 v = vec3(o.x, 1.0 - abs(o.x) - abs(o.y), o.y);\n\tif (v.y < 0.0) {\n\t\tv.xz = (1.0 - abs(v.zx)) * signNotZero(v.xz);\n\t}\n\treturn normalize(v);\n}\nvec3 getDirectionOctahedral() {\n\treturn octDecode(vec2(vUv0.x, 1.0 - vUv0.y) * 2.0 - 1.0);\n}\nvec2 octEncode(in vec3 v) {\n\tfloat l1norm = abs(v.x) + abs(v.y) + abs(v.z);\n\tvec2 result = v.xz * (1.0 / l1norm);\n\tif (v.y < 0.0) {\n\t\tresult = (1.0 - abs(result.yx)) * signNotZero(result.xy);\n\t}\n\treturn result;\n}\n#ifdef CUBEMAP_SOURCE\n\tvec4 sampleCubemap(vec3 dir) {\n\t\treturn textureCube(sourceCube, modifySeams(dir, 1.0));\n\t}\n\tvec4 sampleCubemap(vec2 sph) {\n\t\treturn sampleCubemap(fromSpherical(sph));\n\t}\n\tvec4 sampleCubemap(vec3 dir, float mipLevel) {\n\t\treturn textureCubeLod(sourceCube, modifySeams(dir, 1.0), mipLevel);\n\t}\n\tvec4 sampleCubemap(vec2 sph, float mipLevel) {\n\t\treturn sampleCubemap(fromSpherical(sph), mipLevel);\n\t}\n#else\n\tvec4 sampleEquirect(vec2 sph) {\n\t\tvec2 uv = sph / vec2(PI * 2.0, PI) + 0.5;\n\t\treturn texture2D(sourceTex, vec2(uv.x, 1.0 - uv.y));\n\t}\n\tvec4 sampleEquirect(vec3 dir) {\n\t\treturn sampleEquirect(toSpherical(dir));\n\t}\n\tvec4 sampleEquirect(vec2 sph, float mipLevel) {\n\t\tvec2 uv = sph / vec2(PI * 2.0, PI) + 0.5;\n\t\treturn texture2DLod(sourceTex, vec2(uv.x, 1.0 - uv.y), mipLevel);\n\t}\n\tvec4 sampleEquirect(vec3 dir, float mipLevel) {\n\t\treturn sampleEquirect(toSpherical(dir), mipLevel);\n\t}\n\tvec4 sampleOctahedral(vec3 dir) {\n\t\tvec2 uv = octEncode(dir) * 0.5 + 0.5;\n\t\treturn texture2D(sourceTex, vec2(uv.x, 1.0 - uv.y));\n\t}\n\tvec4 sampleOctahedral(vec2 sph) {\n\t\treturn sampleOctahedral(fromSpherical(sph));\n\t}\n\tvec4 sampleOctahedral(vec3 dir, float mipLevel) {\n\t\tvec2 uv = octEncode(dir) * 0.5 + 0.5;\n\t\treturn texture2DLod(sourceTex, vec2(uv.x, 1.0 - uv.y), mipLevel);\n\t}\n\tvec4 sampleOctahedral(vec2 sph, float mipLevel) {\n\t\treturn sampleOctahedral(fromSpherical(sph), mipLevel);\n\t}\n#endif\nvec3 getDirectionCubemap() {\n\tvec2 st = vUv0 * 2.0 - 1.0;\n\tfloat face = targetFace();\n\tvec3 vec;\n\tif (face == 0.0) {\n\t\tvec = vec3(1, -st.y, -st.x);\n\t} else if (face == 1.0) {\n\t\tvec = vec3(-1, -st.y, st.x);\n\t} else if (face == 2.0) {\n\t\tvec = vec3(st.x, 1, st.y);\n\t} else if (face == 3.0) {\n\t\tvec = vec3(st.x, -1, -st.y);\n\t} else if (face == 4.0) {\n\t\tvec = vec3(st.x, -st.y, 1);\n\t} else {\n\t\tvec = vec3(-st.x, -st.y, -1);\n\t}\n\treturn normalize(modifySeams(vec, 1.0));\n}\nmat3 matrixFromVector(vec3 n) {\n\tfloat a = 1.0 / (1.0 + n.z);\n\tfloat b = -n.x * n.y * a;\n\tvec3 b1 = vec3(1.0 - n.x * n.x * a, b, -n.x);\n\tvec3 b2 = vec3(b, 1.0 - n.y * n.y * a, -n.y);\n\treturn mat3(b1, b2, n);\n}\nmat3 matrixFromVectorSlow(vec3 n) {\n\tvec3 up = (1.0 - abs(n.y) <= 0.0000001) ? vec3(0.0, 0.0, n.y > 0.0 ? 1.0 : -1.0) : vec3(0.0, 1.0, 0.0);\n\tvec3 x = normalize(cross(up, n));\n\tvec3 y = cross(n, x);\n\treturn mat3(x, y, n);\n}\nvec4 reproject() {\n\tif ({NUM_SAMPLES} <= 1) {\n\t\treturn {ENCODE_FUNC}({DECODE_FUNC}({SOURCE_FUNC}({TARGET_FUNC}())));\n\t} else {\n\t\tvec3 t = {TARGET_FUNC}();\n\t\tvec3 tu = dFdx(t);\n\t\tvec3 tv = dFdy(t);\n\t\tvec3 result = vec3(0.0);\n\t\tfor (float u = 0.0; u < {NUM_SAMPLES_SQRT}; ++u) {\n\t\t\tfor (float v = 0.0; v < {NUM_SAMPLES_SQRT}; ++v) {\n\t\t\t\tresult += {DECODE_FUNC}({SOURCE_FUNC}(normalize(t +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttu * (u / {NUM_SAMPLES_SQRT} - 0.5) +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttv * (v / {NUM_SAMPLES_SQRT} - 0.5))));\n\t\t\t}\n\t\t}\n\t\treturn {ENCODE_FUNC}(result / ({NUM_SAMPLES_SQRT} * {NUM_SAMPLES_SQRT}));\n\t}\n}\nvec4 unpackFloat = vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0);\n#ifdef USE_SAMPLES_TEX\n\tvoid unpackSample(int i, out vec3 L, out float mipLevel) {\n\t\tfloat u = (float(i * 4) + 0.5) * samplesTexInverseSize.x;\n\t\tfloat v = (floor(u) + 0.5) * samplesTexInverseSize.y;\n\t\tvec4 raw;\n\t\traw.x = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat); u += samplesTexInverseSize.x;\n\t\traw.y = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat); u += samplesTexInverseSize.x;\n\t\traw.z = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat); u += samplesTexInverseSize.x;\n\t\traw.w = dot(texture2D(samplesTex, vec2(u, v)), unpackFloat);\n\t\tL.xyz = raw.xyz * 2.0 - 1.0;\n\t\tmipLevel = raw.w * 8.0;\n\t}\n\tvec4 prefilterSamples() {\n\t\tmat3 vecSpace = matrixFromVectorSlow({TARGET_FUNC}());\n\t\tvec3 L;\n\t\tfloat mipLevel;\n\t\tvec3 result = vec3(0.0);\n\t\tfloat totalWeight = 0.0;\n\t\tfor (int i = 0; i < {NUM_SAMPLES}; ++i) {\n\t\t\tunpackSample(i, L, mipLevel);\n\t\t\tresult += {DECODE_FUNC}({SOURCE_FUNC}(vecSpace * L, mipLevel)) * L.z;\n\t\t\ttotalWeight += L.z;\n\t\t}\n\t\treturn {ENCODE_FUNC}(result / totalWeight);\n\t}\n\tvec4 prefilterSamplesUnweighted() {\n\t\tmat3 vecSpace = matrixFromVectorSlow({TARGET_FUNC}());\n\t\tvec3 L;\n\t\tfloat mipLevel;\n\t\tvec3 result = vec3(0.0);\n\t\tfloat totalWeight = 0.0;\n\t\tfor (int i = 0; i < {NUM_SAMPLES}; ++i) {\n\t\t\tunpackSample(i, L, mipLevel);\n\t\t\tresult += {DECODE_FUNC}({SOURCE_FUNC}(vecSpace * L, mipLevel));\n\t\t}\n\t\treturn {ENCODE_FUNC}(result / float({NUM_SAMPLES}));\n\t}\n#endif\nvoid main(void) {\n\tgl_FragColor = {PROCESS_FUNC}();\n}\n`;\n\nvar reprojectVS$1 = `\nattribute vec2 vertex_position;\nuniform vec4 uvMod;\nvarying vec2 vUv0;\nvoid main(void) {\n\tgl_Position = vec4(vertex_position, 0.5, 1.0);\n\tvUv0 = getImageEffectUV((vertex_position.xy * 0.5 + 0.5) * uvMod.xy + uvMod.zw);\n}\n`;\n\nvar screenDepthPS$1 = `\nuniform highp sampler2D uSceneDepthMap;\n#ifndef SCREENSIZE\n\t#define SCREENSIZE\n\tuniform vec4 uScreenSize;\n#endif\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform mat4 matrix_view;\n#endif\n#ifndef LINEARIZE_DEPTH\n\t#define LINEARIZE_DEPTH\n\t\n\t#ifndef CAMERAPLANES\n\t\t#define CAMERAPLANES\n\t\tuniform vec4 camera_params;\n\t#endif\n\tfloat linearizeDepth(float z) {\n\t\tif (camera_params.w == 0.0)\n\t\t\treturn (camera_params.z * camera_params.y) / (camera_params.y + z * (camera_params.z - camera_params.y));\n\t\telse\n\t\t\treturn camera_params.z + z * (camera_params.y - camera_params.z);\n\t}\n#endif\nfloat delinearizeDepth(float linearDepth) {\n\tif (camera_params.w == 0.0) {\n\t\treturn (camera_params.y * (camera_params.z - linearDepth)) / (linearDepth * (camera_params.z - camera_params.y));\n\t} else {\n\t\treturn (linearDepth - camera_params.z) / (camera_params.y - camera_params.z);\n\t}\n}\nfloat getLinearScreenDepth(vec2 uv) {\n\t#ifdef SCENE_DEPTHMAP_LINEAR\n\t\t#ifdef SCENE_DEPTHMAP_FLOAT\n\t\t\treturn texture2D(uSceneDepthMap, uv).r;\n\t\t#else\n\t\t\tivec2 textureSize = textureSize(uSceneDepthMap, 0);\n\t\t\tivec2 texel = ivec2(uv * vec2(textureSize));\n\t\t\tvec4 data = texelFetch(uSceneDepthMap, texel, 0);\n\t\t\tuint intBits = \n\t\t\t\t(uint(data.r * 255.0) << 24u) |\n\t\t\t\t(uint(data.g * 255.0) << 16u) |\n\t\t\t\t(uint(data.b * 255.0) << 8u) |\n\t\t\t\tuint(data.a * 255.0);\n\t\t\treturn uintBitsToFloat(intBits);\n\t\t#endif\n\t#else\n\t\treturn linearizeDepth(texture2D(uSceneDepthMap, uv).r);\n\t#endif\n}\n#ifndef VERTEXSHADER\n\tfloat getLinearScreenDepth() {\n\t\tvec2 uv = gl_FragCoord.xy * uScreenSize.zw;\n\t\treturn getLinearScreenDepth(uv);\n\t}\n#endif\nfloat getLinearDepth(vec3 pos) {\n\treturn -(matrix_view * vec4(pos, 1.0)).z;\n}\n`;\n\nvar shadowCascadesPS$1 = `\nint getShadowCascadeIndex(vec4 shadowCascadeDistances, int shadowCascadeCount) {\n\tfloat depth = 1.0 / gl_FragCoord.w;\n\tvec4 comparisons = step(shadowCascadeDistances, vec4(depth));\n\tint cascadeIndex = int(dot(comparisons, vec4(1.0)));\n\treturn min(cascadeIndex, shadowCascadeCount - 1);\n}\nint ditherShadowCascadeIndex(int cascadeIndex, vec4 shadowCascadeDistances, int shadowCascadeCount, float blendFactor) {\n \n\tif (cascadeIndex < shadowCascadeCount - 1) {\n\t\tfloat currentRangeEnd = shadowCascadeDistances[cascadeIndex];\n\t\tfloat transitionStart = blendFactor * currentRangeEnd;\n\t\tfloat depth = 1.0 / gl_FragCoord.w;\n\t\tif (depth > transitionStart) {\n\t\t\tfloat transitionFactor = smoothstep(transitionStart, currentRangeEnd, depth);\n\t\t\tfloat dither = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453);\n\t\t\tif (dither < transitionFactor) {\n\t\t\t\tcascadeIndex += 1;\n\t\t\t}\n\t\t}\n\t}\n\treturn cascadeIndex;\n}\nvec3 fadeShadow(vec3 shadowCoord, vec4 shadowCascadeDistances) {\t\t\t\t  \n\tfloat depth = 1.0 / gl_FragCoord.w;\n\tif (depth > shadowCascadeDistances.w) {\n\t\tshadowCoord.z = -9999999.0;\n\t}\n\treturn shadowCoord;\n}\n`;\n\nvar shadowEVSMPS$1 = `\nfloat linstep(float a, float b, float v) {\n\treturn saturate((v - a) / (b - a));\n}\nfloat reduceLightBleeding(float pMax, float amount) {\n\t return linstep(amount, 1.0, pMax);\n}\nfloat chebyshevUpperBound(vec2 moments, float mean, float minVariance, float lightBleedingReduction) {\n\tfloat variance = moments.y - (moments.x * moments.x);\n\tvariance = max(variance, minVariance);\n\tfloat d = mean - moments.x;\n\tfloat pMax = variance / (variance + (d * d));\n\tpMax = reduceLightBleeding(pMax, lightBleedingReduction);\n\treturn (mean <= moments.x ? 1.0 : pMax);\n}\nfloat calculateEVSM(vec3 moments, float Z, float vsmBias, float exponent) {\n\tZ = 2.0 * Z - 1.0;\n\tfloat warpedDepth = exp(exponent * Z);\n\tmoments.xy += vec2(warpedDepth, warpedDepth*warpedDepth) * (1.0 - moments.z);\n\tfloat VSMBias = vsmBias;\n\tfloat depthScale = VSMBias * exponent * warpedDepth;\n\tfloat minVariance1 = depthScale * depthScale;\n\treturn chebyshevUpperBound(moments.xy, warpedDepth, minVariance1, 0.1);\n}\nfloat VSM16(TEXTURE_ACCEPT(tex), vec2 texCoords, float resolution, float Z, float vsmBias, float exponent) {\n\tvec3 moments = texture2DLod(tex, texCoords, 0.0).xyz;\n\treturn calculateEVSM(moments, Z, vsmBias, exponent);\n}\nfloat getShadowVSM16(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent) {\n\treturn VSM16(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);\n}\nfloat getShadowSpotVSM16(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent, vec3 lightDir) {\n\treturn VSM16(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, length(lightDir) * shadowParams.w + shadowParams.z, shadowParams.y, exponent);\n}\nfloat VSM32(TEXTURE_ACCEPT(tex), vec2 texCoords, float resolution, float Z, float vsmBias, float exponent) {\n\t#ifdef CAPS_TEXTURE_FLOAT_FILTERABLE\n\t\tvec3 moments = texture2DLod(tex, texCoords, 0.0).xyz;\n\t#else\n\t\tfloat pixelSize = 1.0 / resolution;\n\t\ttexCoords -= vec2(pixelSize);\n\t\tvec3 s00 = texture2DLod(tex, texCoords, 0.0).xyz;\n\t\tvec3 s10 = texture2DLod(tex, texCoords + vec2(pixelSize, 0), 0.0).xyz;\n\t\tvec3 s01 = texture2DLod(tex, texCoords + vec2(0, pixelSize), 0.0).xyz;\n\t\tvec3 s11 = texture2DLod(tex, texCoords + vec2(pixelSize), 0.0).xyz;\n\t\tvec2 fr = fract(texCoords * resolution);\n\t\tvec3 h0 = mix(s00, s10, fr.x);\n\t\tvec3 h1 = mix(s01, s11, fr.x);\n\t\tvec3 moments = mix(h0, h1, fr.y);\n\t#endif\n\treturn calculateEVSM(moments, Z, vsmBias, exponent);\n}\nfloat getShadowVSM32(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent) {\n\treturn VSM32(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);\n}\nfloat getShadowSpotVSM32(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent, vec3 lightDir) {\n\tfloat Z = length(lightDir) * shadowParams.w + shadowParams.z;\n\treturn VSM32(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);\n}\n`;\n\nvar shadowPCF1PS$1 = `\nfloat getShadowPCF1x1(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn textureShadow(shadowMap, shadowCoord);\n}\nfloat getShadowSpotPCF1x1(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn textureShadow(shadowMap, shadowCoord);\n}\n#ifndef WEBGPU\nfloat getShadowOmniPCF1x1(samplerCubeShadow shadowMap, vec3 shadowCoord, vec4 shadowParams, vec3 lightDir) {\n\tfloat shadowZ = length(lightDir) * shadowParams.w + shadowParams.z;\n\treturn texture(shadowMap, vec4(lightDir, shadowZ));\n}\n#endif\n`;\n\nvar shadowPCF3PS$1 = `\nfloat _getShadowPCF3x3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec3 shadowParams) {\n\tfloat z = shadowCoord.z;\n\tvec2 uv = shadowCoord.xy * shadowParams.x;\n\tfloat shadowMapSizeInv = 1.0 / shadowParams.x;\n\tvec2 base_uv = floor(uv + 0.5);\n\tfloat s = (uv.x + 0.5 - base_uv.x);\n\tfloat t = (uv.y + 0.5 - base_uv.y); \n\tbase_uv -= vec2(0.5);\n\tbase_uv *= shadowMapSizeInv;\n\tfloat sum = 0.0;\n\tfloat uw0 = (3.0 - 2.0 * s);\n\tfloat uw1 = (1.0 + 2.0 * s);\n\tfloat u0 = (2.0 - s) / uw0 - 1.0;\n\tfloat u1 = s / uw1 + 1.0;\n\tfloat vw0 = (3.0 - 2.0 * t);\n\tfloat vw1 = (1.0 + 2.0 * t);\n\tfloat v0 = (2.0 - t) / vw0 - 1.0;\n\tfloat v1 = t / vw1 + 1.0;\n\tu0 = u0 * shadowMapSizeInv + base_uv.x;\n\tv0 = v0 * shadowMapSizeInv + base_uv.y;\n\tu1 = u1 * shadowMapSizeInv + base_uv.x;\n\tv1 = v1 * shadowMapSizeInv + base_uv.y;\n\tsum += uw0 * vw0 * textureShadow(shadowMap, vec3(u0, v0, z));\n\tsum += uw1 * vw0 * textureShadow(shadowMap, vec3(u1, v0, z));\n\tsum += uw0 * vw1 * textureShadow(shadowMap, vec3(u0, v1, z));\n\tsum += uw1 * vw1 * textureShadow(shadowMap, vec3(u1, v1, z));\n\tsum *= 1.0f / 16.0;\n\treturn sum;\n}\nfloat getShadowPCF3x3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn _getShadowPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);\n}\nfloat getShadowSpotPCF3x3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn _getShadowPCF3x3(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);\n}\n#ifndef WEBGPU\nfloat getShadowOmniPCF3x3(samplerCubeShadow shadowMap, vec4 shadowParams, vec3 dir) {\n\t\n\tfloat shadowZ = length(dir) * shadowParams.w + shadowParams.z;\n\tfloat z = 1.0 / float(textureSize(shadowMap, 0));\n\tvec3 tc = normalize(dir);\n\tmediump vec4 shadows;\n\tshadows.x = texture(shadowMap, vec4(tc + vec3( z, z, z), shadowZ));\n\tshadows.y = texture(shadowMap, vec4(tc + vec3(-z,-z, z), shadowZ));\n\tshadows.z = texture(shadowMap, vec4(tc + vec3(-z, z,-z), shadowZ));\n\tshadows.w = texture(shadowMap, vec4(tc + vec3( z,-z,-z), shadowZ));\n\treturn dot(shadows, vec4(0.25));\n}\nfloat getShadowOmniPCF3x3(samplerCubeShadow shadowMap, vec3 shadowCoord, vec4 shadowParams, vec3 lightDir) {\n\treturn getShadowOmniPCF3x3(shadowMap, shadowParams, lightDir);\n}\n#endif\n`;\n\nvar shadowPCF5PS$1 = `\nfloat _getShadowPCF5x5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec3 shadowParams) {\n\tfloat z = shadowCoord.z;\n\tvec2 uv = shadowCoord.xy * shadowParams.x;\n\tfloat shadowMapSizeInv = 1.0 / shadowParams.x;\n\tvec2 base_uv = floor(uv + 0.5);\n\tfloat s = (uv.x + 0.5 - base_uv.x);\n\tfloat t = (uv.y + 0.5 - base_uv.y);\n\tbase_uv -= vec2(0.5);\n\tbase_uv *= shadowMapSizeInv;\n\tfloat uw0 = (4.0 - 3.0 * s);\n\tfloat uw1 = 7.0;\n\tfloat uw2 = (1.0 + 3.0 * s);\n\tfloat u0 = (3.0 - 2.0 * s) / uw0 - 2.0;\n\tfloat u1 = (3.0 + s) / uw1;\n\tfloat u2 = s / uw2 + 2.0;\n\tfloat vw0 = (4.0 - 3.0 * t);\n\tfloat vw1 = 7.0;\n\tfloat vw2 = (1.0 + 3.0 * t);\n\tfloat v0 = (3.0 - 2.0 * t) / vw0 - 2.0;\n\tfloat v1 = (3.0 + t) / vw1;\n\tfloat v2 = t / vw2 + 2.0;\n\tfloat sum = 0.0;\n\tu0 = u0 * shadowMapSizeInv + base_uv.x;\n\tv0 = v0 * shadowMapSizeInv + base_uv.y;\n\tu1 = u1 * shadowMapSizeInv + base_uv.x;\n\tv1 = v1 * shadowMapSizeInv + base_uv.y;\n\tu2 = u2 * shadowMapSizeInv + base_uv.x;\n\tv2 = v2 * shadowMapSizeInv + base_uv.y;\n\tsum += uw0 * vw0 * textureShadow(shadowMap, vec3(u0, v0, z));\n\tsum += uw1 * vw0 * textureShadow(shadowMap, vec3(u1, v0, z));\n\tsum += uw2 * vw0 * textureShadow(shadowMap, vec3(u2, v0, z));\n\tsum += uw0 * vw1 * textureShadow(shadowMap, vec3(u0, v1, z));\n\tsum += uw1 * vw1 * textureShadow(shadowMap, vec3(u1, v1, z));\n\tsum += uw2 * vw1 * textureShadow(shadowMap, vec3(u2, v1, z));\n\tsum += uw0 * vw2 * textureShadow(shadowMap, vec3(u0, v2, z));\n\tsum += uw1 * vw2 * textureShadow(shadowMap, vec3(u1, v2, z));\n\tsum += uw2 * vw2 * textureShadow(shadowMap, vec3(u2, v2, z));\n\tsum *= 1.0f / 144.0;\n\tsum = saturate(sum);\n\treturn sum;\n}\nfloat getShadowPCF5x5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn _getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);\n}\nfloat getShadowSpotPCF5x5(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams) {\n\treturn _getShadowPCF5x5(SHADOWMAP_PASS(shadowMap), shadowCoord, shadowParams.xyz);\n}\n`;\n\nvar shadowPCSSPS = `\n#define PCSS_SAMPLE_COUNT 16\nuniform float pcssDiskSamples[PCSS_SAMPLE_COUNT];\nuniform float pcssSphereSamples[PCSS_SAMPLE_COUNT];\nvec2 vogelDisk(int sampleIndex, float count, float phi, float r) {\n\tconst float GoldenAngle = 2.4;\n\tfloat theta = float(sampleIndex) * GoldenAngle + phi;\n\tfloat sine = sin(theta);\n\tfloat cosine = cos(theta);\n\treturn vec2(r * cosine, r * sine);\n}\nvec3 vogelSphere(int sampleIndex, float count, float phi, float r) {\n\tconst float GoldenAngle = 2.4;\n\tfloat theta = float(sampleIndex) * GoldenAngle + phi;\n\tfloat weight = float(sampleIndex) / count;\n\treturn vec3(cos(theta) * r, weight, sin(theta) * r);\n}\nfloat noise(vec2 screenPos) {\n\tconst float PHI = 1.61803398874989484820459;\n\treturn fract(sin(dot(screenPos * PHI, screenPos)) * screenPos.x);\n}\nfloat viewSpaceDepth(float depth, mat4 invProjection) {\n\tfloat z = depth * 2.0 - 1.0;\n\tvec4 clipSpace = vec4(0.0, 0.0, z, 1.0);\n\tvec4 viewSpace = invProjection * clipSpace;\n\treturn viewSpace.z;\n}\nfloat PCSSBlockerDistance(TEXTURE_ACCEPT(shadowMap), vec2 sampleCoords[PCSS_SAMPLE_COUNT], vec2 shadowCoords, vec2 searchSize, float z, vec4 cameraParams) {\n\tfloat blockers = 0.0;\n\tfloat averageBlocker = 0.0;\n\tfor (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {\n\t\tvec2 offset = sampleCoords[i] * searchSize;\n\t\tvec2 sampleUV = shadowCoords + offset;\n\t\tfloat blocker = texture2DLod(shadowMap, sampleUV, 0.0).r;\n\t\tfloat isBlocking = step(blocker, z);\n\t\tblockers += isBlocking;\n\t\taverageBlocker += blocker * isBlocking;\n\t}\n\tif (blockers > 0.0)\n\t\treturn averageBlocker / blockers;\n\treturn -1.0;\n}\nfloat PCSS(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoords, vec4 cameraParams, vec2 shadowSearchArea) {\n\tfloat receiverDepth = linearizeDepthWithParams(shadowCoords.z, cameraParams);\n\tvec2 samplePoints[PCSS_SAMPLE_COUNT];\n\tconst float PI = 3.141592653589793;\n\tfloat noise = noise( gl_FragCoord.xy ) * 2.0 * PI;\n\tfor (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {\n\t\tfloat pcssPresample = pcssDiskSamples[i];\n\t\tsamplePoints[i] = vogelDisk(i, float(PCSS_SAMPLE_COUNT), noise, pcssPresample);\n\t}\n\tfloat averageBlocker = PCSSBlockerDistance(TEXTURE_PASS(shadowMap), samplePoints, shadowCoords.xy, shadowSearchArea, receiverDepth, cameraParams);\n\tif (averageBlocker == -1.0) {\n\t\treturn 1.0;\n\t} else {\n\t\tfloat depthDifference = (receiverDepth - averageBlocker) / 3.0;\n\t\tvec2 filterRadius = depthDifference * shadowSearchArea;\n\t\tfloat shadow = 0.0;\n\t\tfor (int i = 0; i < PCSS_SAMPLE_COUNT; i ++)\n\t\t{\n\t\t\tvec2 sampleUV = samplePoints[i] * filterRadius;\n\t\t\tsampleUV = shadowCoords.xy + sampleUV;\n\t\t\tfloat depth = texture2DLod(shadowMap, sampleUV, 0.0).r;\n\t\t\tshadow += step(receiverDepth, depth);\n\t\t}\n\t\treturn shadow / float(PCSS_SAMPLE_COUNT);\n\t} \n}\n#ifndef WEBGPU\nfloat PCSSCubeBlockerDistance(samplerCube shadowMap, vec3 lightDirNorm, vec3 samplePoints[PCSS_SAMPLE_COUNT], float z, float shadowSearchArea) {\n\tfloat blockers = 0.0;\n\tfloat averageBlocker = 0.0;\n\tfor (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {\n\t\tvec3 sampleDir = lightDirNorm + samplePoints[i] * shadowSearchArea;\n\t\tsampleDir = normalize(sampleDir);\n\t\tfloat blocker = textureCubeLod(shadowMap, sampleDir, 0.0).r;\n\t\tfloat isBlocking = step(blocker, z);\n\t\tblockers += isBlocking;\n\t\taverageBlocker += blocker * isBlocking;\n\t}\n\tif (blockers > 0.0)\n\t\treturn averageBlocker / blockers;\n\treturn -1.0;\n}\nfloat PCSSCube(samplerCube shadowMap, vec4 shadowParams, vec3 shadowCoords, vec4 cameraParams, float shadowSearchArea, vec3 lightDir) {\n\t\n\tvec3 samplePoints[PCSS_SAMPLE_COUNT];\n\tconst float PI = 3.141592653589793;\n\tfloat noise = noise( gl_FragCoord.xy ) * 2.0 * PI;\n\tfor (int i = 0; i < PCSS_SAMPLE_COUNT; i++) {\n\t\tfloat r = pcssSphereSamples[i];\n\t\tsamplePoints[i] = vogelSphere(i, float(PCSS_SAMPLE_COUNT), noise, r);\n\t}\n\tfloat receiverDepth = length(lightDir) * shadowParams.w + shadowParams.z;\n\tvec3 lightDirNorm = normalize(lightDir);\n\t\n\tfloat averageBlocker = PCSSCubeBlockerDistance(shadowMap, lightDirNorm, samplePoints, receiverDepth, shadowSearchArea);\n\tif (averageBlocker == -1.0) {\n\t\treturn 1.0;\n\t} else {\n\t\tfloat filterRadius = ((receiverDepth - averageBlocker) / averageBlocker) * shadowSearchArea;\n\t\tfloat shadow = 0.0;\n\t\tfor (int i = 0; i < PCSS_SAMPLE_COUNT; i++)\n\t\t{\n\t\t\tvec3 offset = samplePoints[i] * filterRadius;\n\t\t\tvec3 sampleDir = lightDirNorm + offset;\n\t\t\tsampleDir = normalize(sampleDir);\n\t\t\tfloat depth = textureCubeLod(shadowMap, sampleDir, 0.0).r;\n\t\t\tshadow += step(receiverDepth, depth);\n\t\t}\n\t\treturn shadow / float(PCSS_SAMPLE_COUNT);\n\t}\n}\nfloat getShadowOmniPCSS(samplerCube shadowMap, vec3 shadowCoord, vec4 shadowParams, vec4 cameraParams, vec2 shadowSearchArea, vec3 lightDir) {\n\treturn PCSSCube(shadowMap, shadowParams, shadowCoord, cameraParams, shadowSearchArea.x, lightDir);\n}\n#endif\nfloat getShadowSpotPCSS(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, vec4 cameraParams, vec2 shadowSearchArea, vec3 lightDir) {\n\treturn PCSS(TEXTURE_PASS(shadowMap), shadowCoord, cameraParams, shadowSearchArea);\n}\n`;\n\nvar shadowSoftPS$1 = `\nhighp float fractSinRand(const in vec2 uv) {\n\tconst float PI = 3.141592653589793;\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot(uv.xy, vec2(a, b)), sn = mod(dt, PI);\n\treturn fract(sin(sn) * c);\n}\nstruct VogelDiskData {\n\tfloat invNumSamples;\n\tfloat initialAngle;\n\tfloat currentPointId;\n};\nvoid prepareDiskConstants(out VogelDiskData data, int sampleCount, float randomSeed) {\n\tconst float pi2 = 6.28318530718;\n\tdata.invNumSamples = 1.0 / float(sampleCount);\n\tdata.initialAngle = randomSeed * pi2;\n\tdata.currentPointId = 0.0;\n}\nvec2 generateDiskSample(inout VogelDiskData data) {\n\tconst float GOLDEN_ANGLE = 2.399963;\n\tfloat r = sqrt((data.currentPointId + 0.5) * data.invNumSamples);\n\tfloat theta = data.currentPointId * GOLDEN_ANGLE + data.initialAngle;\n\tvec2 offset = vec2(cos(theta), sin(theta)) * pow(r, 1.33);\n\tdata.currentPointId += 1.0;\n\treturn offset;\n}\nvoid PCSSFindBlocker(TEXTURE_ACCEPT(shadowMap), out float avgBlockerDepth, out int numBlockers,\n\tvec2 shadowCoords, float z, int shadowBlockerSamples, float penumbraSize, float invShadowMapSize, float randomSeed) {\n\tVogelDiskData diskData;\n\tprepareDiskConstants(diskData, shadowBlockerSamples, randomSeed);\n\tfloat searchWidth = penumbraSize * invShadowMapSize;\n\tfloat blockerSum = 0.0;\n\tnumBlockers = 0;\n\tfor( int i = 0; i < shadowBlockerSamples; ++i ) {\n\t\tvec2 diskUV = generateDiskSample(diskData);\n\t\tvec2 sampleUV = shadowCoords + diskUV * searchWidth;\n\t\tfloat shadowMapDepth = texture2DLod(shadowMap, sampleUV, 0.0).r;\n\t\tif ( shadowMapDepth < z ) {\n\t\t\tblockerSum += shadowMapDepth;\n\t\t\tnumBlockers++;\n\t\t}\n\t}\n\tavgBlockerDepth = blockerSum / float(numBlockers);\n}\nfloat PCSSFilter(TEXTURE_ACCEPT(shadowMap), vec2 uv, float receiverDepth, int shadowSamples, float filterRadius, float randomSeed) {\n\tVogelDiskData diskData;\n\tprepareDiskConstants(diskData, shadowSamples, randomSeed);\n\tfloat sum = 0.0;\n\tfor (int i = 0; i < shadowSamples; i++) {\n\t\tvec2 offsetUV = generateDiskSample(diskData) * filterRadius;\n\t\tfloat depth = texture2DLod(shadowMap, uv + offsetUV, 0.0).r;\n\t\tsum += step(receiverDepth, depth);\n\t}\n\treturn sum / float(shadowSamples);\n}\nfloat getPenumbra(float dblocker, float dreceiver, float penumbraSize, float penumbraFalloff) {\n\tfloat dist = dreceiver - dblocker;\n\tfloat penumbra = 1.0 - pow(1.0 - dist, penumbraFalloff);\n\treturn penumbra * penumbraSize;\n}\nfloat PCSSDirectional(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoords, vec4 cameraParams, vec4 softShadowParams) {\n\tfloat receiverDepth = shadowCoords.z;\n\tfloat randomSeed = fractSinRand(gl_FragCoord.xy);\n\tint shadowSamples = int(softShadowParams.x);\n\tint shadowBlockerSamples = int(softShadowParams.y);\n\tfloat penumbraSize = softShadowParams.z;\n\tfloat penumbraFalloff = softShadowParams.w;\n\tint shadowMapSize = textureSize(shadowMap, 0).x;\n\tfloat invShadowMapSize = 1.0 / float(shadowMapSize);\n\tinvShadowMapSize *= float(shadowMapSize) / 2048.0;\n\tfloat penumbra;\n\tif (shadowBlockerSamples > 0) {\n\t\tfloat avgBlockerDepth = 0.0;\n\t\tint numBlockers = 0;\n\t\tPCSSFindBlocker(TEXTURE_PASS(shadowMap), avgBlockerDepth, numBlockers, shadowCoords.xy, receiverDepth, shadowBlockerSamples, penumbraSize, invShadowMapSize, randomSeed);\n\t\tif (numBlockers < 1)\n\t\t\treturn 1.0f;\n\t\tpenumbra = getPenumbra(avgBlockerDepth, shadowCoords.z, penumbraSize, penumbraFalloff);\n\t} else {\n\t\tpenumbra = penumbraSize;\n\t}\n\tfloat filterRadius = penumbra * invShadowMapSize;\n\treturn PCSSFilter(TEXTURE_PASS(shadowMap), shadowCoords.xy, receiverDepth, shadowSamples, filterRadius, randomSeed);\n}\nfloat getShadowPCSS(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, vec4 cameraParams, vec4 softShadowParams, vec3 lightDir) {\n\treturn PCSSDirectional(TEXTURE_PASS(shadowMap), shadowCoord, cameraParams, softShadowParams);\n}\n`;\n\nvar skinBatchVS$1 = `\nattribute float vertex_boneIndices;\nuniform highp sampler2D texture_poseMap;\nmat4 getBoneMatrix(const in float indexFloat) {\n\tint width = textureSize(texture_poseMap, 0).x;\n\tint index = int(indexFloat + 0.5) * 3;\n\tint iy = index / width;\n\tint ix = index % width;\n\tvec4 v1 = texelFetch(texture_poseMap, ivec2(ix + 0, iy), 0);\n\tvec4 v2 = texelFetch(texture_poseMap, ivec2(ix + 1, iy), 0);\n\tvec4 v3 = texelFetch(texture_poseMap, ivec2(ix + 2, iy), 0);\n\treturn mat4(\n\t\tv1.x, v2.x, v3.x, 0,\n\t\tv1.y, v2.y, v3.y, 0,\n\t\tv1.z, v2.z, v3.z, 0,\n\t\tv1.w, v2.w, v3.w, 1\n\t);\n}\n`;\n\nvar skinVS$1 = `\nattribute vec4 vertex_boneWeights;\nattribute vec4 vertex_boneIndices;\nuniform highp sampler2D texture_poseMap;\nvoid getBoneMatrix(const in int width, const in int index, out vec4 v1, out vec4 v2, out vec4 v3) {\n\tint v = index / width;\n\tint u = index % width;\n\tv1 = texelFetch(texture_poseMap, ivec2(u + 0, v), 0);\n\tv2 = texelFetch(texture_poseMap, ivec2(u + 1, v), 0);\n\tv3 = texelFetch(texture_poseMap, ivec2(u + 2, v), 0);\n}\nmat4 getSkinMatrix(const in vec4 indicesFloat, const in vec4 weights) {\n\tint width = textureSize(texture_poseMap, 0).x;\n\tivec4 indices = ivec4(indicesFloat + 0.5) * 3;\n\tvec4 a1, a2, a3;\n\tgetBoneMatrix(width, indices.x, a1, a2, a3);\n\tvec4 b1, b2, b3;\n\tgetBoneMatrix(width, indices.y, b1, b2, b3);\n\tvec4 c1, c2, c3;\n\tgetBoneMatrix(width, indices.z, c1, c2, c3);\n\tvec4 d1, d2, d3;\n\tgetBoneMatrix(width, indices.w, d1, d2, d3);\n\tvec4 v1 = a1 * weights.x + b1 * weights.y + c1 * weights.z + d1 * weights.w;\n\tvec4 v2 = a2 * weights.x + b2 * weights.y + c2 * weights.z + d2 * weights.w;\n\tvec4 v3 = a3 * weights.x + b3 * weights.y + c3 * weights.z + d3 * weights.w;\n\tfloat one = dot(weights, vec4(1.0));\n\treturn mat4(\n\t\tv1.x, v2.x, v3.x, 0,\n\t\tv1.y, v2.y, v3.y, 0,\n\t\tv1.z, v2.z, v3.z, 0,\n\t\tv1.w, v2.w, v3.w, one\n\t);\n}\n`;\n\nvar skyboxPS$1 = `\n\t#define LIT_SKYBOX_INTENSITY\n\t#include \"envProcPS\"\n\t#include \"gammaPS\"\n\t#include \"tonemappingPS\"\n\t#ifdef PREPASS_PASS\n\t\tvarying float vLinearDepth;\n\t\t#include \"floatAsUintPS\"\n\t#endif\n\tvarying vec3 vViewDir;\n\tuniform float skyboxHighlightMultiplier;\n\t#ifdef SKY_CUBEMAP\n\t\tuniform samplerCube texture_cubeMap;\n\t\t#ifdef SKYMESH\n\t\t\tvarying vec3 vWorldPos;\n\t\t\tuniform mat3 cubeMapRotationMatrix;\n\t\t\tuniform vec3 projectedSkydomeCenter;\n\t\t#endif\n\t#else\n\t\t#include \"sphericalPS\"\n\t\t#include \"envAtlasPS\"\n\t\tuniform sampler2D texture_envAtlas;\n\t\tuniform float mipLevel;\n\t#endif\n\tvoid main(void) {\n\t\t#ifdef PREPASS_PASS\n\t\t\tgl_FragColor = float2vec4(vLinearDepth);\n\t\t#else\n\t\t\t#ifdef SKY_CUBEMAP\n\t\t\t\t#ifdef SKYMESH\n\t\t\t\t\tvec3 envDir = normalize(vWorldPos - projectedSkydomeCenter);\n\t\t\t\t\tvec3 dir = envDir * cubeMapRotationMatrix;\n\t\t\t\t#else\n\t\t\t\t\tvec3 dir = vViewDir;\n\t\t\t\t#endif\n\t\t\t\tdir.x *= -1.0;\n\t\t\t\tvec3 linear = {SKYBOX_DECODE_FNC}(textureCube(texture_cubeMap, dir));\n\t\t\t#else\n\t\t\t\tvec3 dir = vViewDir * vec3(-1.0, 1.0, 1.0);\n\t\t\t\tvec2 uv = toSphericalUv(normalize(dir));\n\t\t\t\tvec3 linear = {SKYBOX_DECODE_FNC}(texture2D(texture_envAtlas, mapRoughnessUv(uv, mipLevel)));\n\t\t\t#endif\n\t\t\tif (any(greaterThanEqual(linear, vec3(64.0)))) {\n\t\t\t\tlinear *= skyboxHighlightMultiplier;\n\t\t\t}\n\t\t\tgl_FragColor = vec4(gammaCorrectOutput(toneMap(processEnvironment(linear))), 1.0);\n\t\t#endif\n\t}\n`;\n\nvar skyboxVS$1 = `\nattribute vec4 aPosition;\nuniform mat4 matrix_view;\nuniform mat4 matrix_projectionSkybox;\nuniform mat3 cubeMapRotationMatrix;\nvarying vec3 vViewDir;\n#ifdef PREPASS_PASS\n\tvarying float vLinearDepth;\n#endif\n#ifdef SKYMESH\n\tuniform mat4 matrix_model;\n\tvarying vec3 vWorldPos;\n#endif\nvoid main(void) {\n\tmat4 view = matrix_view;\n\t#ifdef SKYMESH\n\t\tvec4 worldPos = matrix_model * aPosition;\n\t\tvWorldPos = worldPos.xyz;\n\t\tgl_Position = matrix_projectionSkybox * (view * worldPos);\n\t\t#ifdef PREPASS_PASS\n\t\t\tvLinearDepth = -(matrix_view * vec4(vWorldPos, 1.0)).z;\n\t\t#endif\n\t#else\n\t\tview[3][0] = view[3][1] = view[3][2] = 0.0;\n\t\tgl_Position = matrix_projectionSkybox * (view * aPosition);\n\t\tvViewDir = aPosition.xyz * cubeMapRotationMatrix;\n\t\t#ifdef PREPASS_PASS\n\t\t\tvLinearDepth = -gl_Position.w;\n\t\t#endif\n\t#endif\n\tgl_Position.z = gl_Position.w - 1.0e-7;\n}\n`;\n\nvar specularPS$1 = `\n#ifdef STD_SPECULAR_CONSTANT\nuniform vec3 material_specular;\n#endif\nvoid getSpecularity() {\n\tvec3 specularColor = vec3(1,1,1);\n\t#ifdef STD_SPECULAR_CONSTANT\n\tspecularColor *= material_specular;\n\t#endif\n\t#ifdef STD_SPECULAR_TEXTURE\n\tspecularColor *= {STD_SPECULAR_TEXTURE_DECODE}(texture2DBias({STD_SPECULAR_TEXTURE_NAME}, {STD_SPECULAR_TEXTURE_UV}, textureBias)).{STD_SPECULAR_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SPECULAR_VERTEX\n\tspecularColor *= saturate(vVertexColor.{STD_SPECULAR_VERTEX_CHANNEL});\n\t#endif\n\tdSpecularity = specularColor;\n}\n`;\n\nvar sphericalPS$1 = `\nvec2 toSpherical(vec3 dir) {\n\treturn vec2(dir.xz == vec2(0.0) ? 0.0 : atan(dir.x, dir.z), asin(dir.y));\n}\nvec2 toSphericalUv(vec3 dir) {\n\tconst float PI = 3.141592653589793;\n\tvec2 uv = toSpherical(dir) / vec2(PI * 2.0, PI) + 0.5;\n\treturn vec2(uv.x, 1.0 - uv.y);\n}\n`;\n\nvar specularityFactorPS$1 = `\n#ifdef STD_SPECULARITYFACTOR_CONSTANT\nuniform float material_specularityFactor;\n#endif\nvoid getSpecularityFactor() {\n\tfloat specularityFactor = 1.0;\n\t#ifdef STD_SPECULARITYFACTOR_CONSTANT\n\tspecularityFactor *= material_specularityFactor;\n\t#endif\n\t#ifdef STD_SPECULARITYFACTOR_TEXTURE\n\tspecularityFactor *= texture2DBias({STD_SPECULARITYFACTOR_TEXTURE_NAME}, {STD_SPECULARITYFACTOR_TEXTURE_UV}, textureBias).{STD_SPECULARITYFACTOR_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SPECULARITYFACTOR_VERTEX\n\tspecularityFactor *= saturate(vVertexColor.{STD_SPECULARITYFACTOR_VERTEX_CHANNEL});\n\t#endif\n\tdSpecularityFactor = specularityFactor;\n}\n`;\n\nvar spotPS$1 = `\nfloat getSpotEffect(vec3 lightSpotDir, float lightInnerConeAngle, float lightOuterConeAngle, vec3 lightDirNorm) {\n\tfloat cosAngle = dot(lightDirNorm, lightSpotDir);\n\treturn smoothstep(lightOuterConeAngle, lightInnerConeAngle, cosAngle);\n}\n`;\n\nvar startNineSlicedPS$1 = `\n\tnineSlicedUv = vec2(vUv0.x, 1.0 - vUv0.y);\n`;\n\nvar startNineSlicedTiledPS$1 = `\n\tvec2 tileMask = step(vMask, vec2(0.99999));\n\tvec2 tileSize = 0.5 * (innerOffset.xy + innerOffset.zw);\n\tvec2 tileScale = vec2(1.0) / (vec2(1.0) - tileSize);\n\tvec2 clampedUv = mix(innerOffset.xy * 0.5, vec2(1.0) - innerOffset.zw * 0.5, fract((vTiledUv - tileSize) * tileScale));\n\tclampedUv = clampedUv * atlasRect.zw + atlasRect.xy;\n\tnineSlicedUv = vUv0 * tileMask + clampedUv * (vec2(1.0) - tileMask);\n\tnineSlicedUv.y = 1.0 - nineSlicedUv.y;\n\t\n`;\n\nvar stdDeclarationPS$1 = `\n\tfloat dAlpha = 1.0;\n\t#if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE\n\t\t#ifdef STD_OPACITY_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_opacityMap;\n\t\t#endif\n\t#endif\n\t#ifdef FORWARD_PASS\n\t\tvec3 dAlbedo;\n\t\tvec3 dNormalW;\n\t\tvec3 dSpecularity = vec3(0.0);\n\t\tfloat dGlossiness = 0.0;\n\t\t#ifdef LIT_REFRACTION\n\t\t\tfloat dTransmission;\n\t\t\tfloat dThickness;\n\t\t#endif\n\t\t#ifdef LIT_SCENE_COLOR\n\t\t\tuniform sampler2D uSceneColorMap;\n\t\t#endif\n\t\t#ifdef LIT_SCREEN_SIZE\n\t\t\tuniform vec4 uScreenSize;\n\t\t#endif\n\t\t#ifdef LIT_TRANSFORMS\n\t\t\tuniform mat4 matrix_viewProjection;\n\t\t\tuniform mat4 matrix_model;\n\t\t#endif\n\t\t#ifdef STD_HEIGHT_MAP\n\t\t\tvec2 dUvOffset;\n\t\t\t#ifdef STD_HEIGHT_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_heightMap;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef STD_DIFFUSE_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_diffuseMap;\n\t\t#endif\n\t\t#ifdef STD_DIFFUSEDETAIL_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_diffuseDetailMap;\n\t\t#endif\n\t\t#ifdef STD_NORMAL_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_normalMap;\n\t\t#endif\n\t\t#ifdef STD_NORMALDETAIL_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_normalDetailMap;\n\t\t#endif\n\t\t#ifdef STD_THICKNESS_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_thicknessMap;\n\t\t#endif\n\t\t#ifdef STD_REFRACTION_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_refractionMap;\n\t\t#endif\n\t\t#ifdef LIT_IRIDESCENCE\n\t\t\tfloat dIridescence;\n\t\t\tfloat dIridescenceThickness;\n\t\t\t#ifdef STD_IRIDESCENCE_THICKNESS_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_iridescenceThicknessMap;\n\t\t\t#endif\n\t\t\t#ifdef STD_IRIDESCENCE_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_iridescenceMap;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\tfloat ccSpecularity;\n\t\t\tfloat ccGlossiness;\n\t\t\tvec3 ccNormalW;\n\t\t#endif\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\tfloat dAnisotropy;\n\t\t\tvec2 dAnisotropyRotation;\n\t\t#endif\n\t\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\tvec3 sSpecularity;\n\t\t\t\tfloat sGlossiness;\n\t\t\t\t#ifdef STD_SHEEN_TEXTURE_ALLOCATE\n\t\t\t\t\tuniform sampler2D texture_sheenMap;\n\t\t\t\t#endif\n\t\t\t\t#ifdef STD_SHEENGLOSS_TEXTURE_ALLOCATE\n\t\t\t\t\tuniform sampler2D texture_sheenGlossMap;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_METALNESS\n\t\t\t\tfloat dMetalness;\n\t\t\t\tfloat dIor;\n\t\t\t\t#ifdef STD_METALNESS_TEXTURE_ALLOCATE\n\t\t\t\t\tuniform sampler2D texture_metalnessMap;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\tfloat dSpecularityFactor;\n\t\t\t\t#ifdef STD_SPECULARITYFACTOR_TEXTURE_ALLOCATE\n\t\t\t\t\tuniform sampler2D texture_specularityFactorMap;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef STD_SPECULAR_COLOR\n\t\t\t\t#ifdef STD_SPECULAR_TEXTURE_ALLOCATE\n\t\t\t\t\tuniform sampler2D texture_specularMap;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef STD_GLOSS_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_glossMap;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef STD_AO\n\t\t\tfloat dAo;\n\t\t\t#ifdef STD_AO_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_aoMap;\n\t\t\t#endif\n\t\t\t#ifdef STD_AODETAIL_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_aoDetailMap;\n\t\t\t#endif\n\t\t#endif\n\t\tvec3 dEmission;\n\t\t#ifdef STD_EMISSIVE_TEXTURE_ALLOCATE\n\t\t\tuniform sampler2D texture_emissiveMap;\n\t\t#endif\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\t#ifdef STD_CLEARCOAT_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_clearCoatMap;\n\t\t\t#endif\n\t\t\t#ifdef STD_CLEARCOATGLOSS_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_clearCoatGlossMap;\n\t\t\t#endif\n\t\t\t#ifdef STD_CLEARCOATNORMAL_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_clearCoatNormalMap;\n\t\t\t#endif\n\t\t#endif\n\t\t\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\t#ifdef STD_ANISOTROPY_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_anisotropyMap;\n\t\t\t#endif\n\t\t#endif\n\t\t#if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)\n\t\t\tvec3 dLightmap;\n\t\t\t#ifdef STD_LIGHT_TEXTURE_ALLOCATE\n\t\t\t\tuniform sampler2D texture_lightMap;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\t#include \"litShaderCorePS\"\n`;\n\nvar stdFrontEndPS$1 = `\n\t#if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE\n\t\t#include \"opacityPS\"\n\t\t#if defined(LIT_ALPHA_TEST)\n\t\t\t#include \"alphaTestPS\"\n\t\t#endif\n\t\t#if STD_OPACITY_DITHER != NONE\n\t\t\t#include \"opacityDitherPS\"\n\t\t#endif\n\t#endif\n\t#ifdef FORWARD_PASS\n\t\t#ifdef STD_HEIGHT_MAP\n\t\t\t#include \"parallaxPS\"\n\t\t#endif\n\t\t#include  \"diffusePS\"\n\t\t#ifdef LIT_NEEDS_NORMAL\n\t\t\t#include \"normalMapPS\"\n\t\t#endif\n\t\t#ifdef LIT_REFRACTION\n\t\t\t#include \"transmissionPS\"\n\t\t\t#include \"thicknessPS\"\n\t\t#endif\n\t\t#ifdef LIT_IRIDESCENCE\n\t\t\t#include \"iridescencePS\"\n\t\t\t#include \"iridescenceThicknessPS\"\n\t\t#endif\n\t\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\t#include \"sheenPS\"\n\t\t\t\t#include \"sheenGlossPS\"\n\t\t\t#endif\n\t\t\t#ifdef LIT_METALNESS\n\t\t\t\t#include \"metalnessPS\"\n\t\t\t\t#include \"iorPS\"\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\t#include \"specularityFactorPS\"\n\t\t\t#endif\n\t\t\t#ifdef STD_SPECULAR_COLOR\n\t\t\t\t#include \"specularPS\"\n\t\t\t#else\n\t\t\t\tvoid getSpecularity() { \n\t\t\t\t\tdSpecularity = vec3(1);\n\t\t\t\t}\n\t\t\t#endif\n\t\t\t#include \"glossPS\"\n\t\t#endif\n\t\t#ifdef STD_AO\n\t\t\t#include \"aoPS\"\n\t\t#endif\n\t\t#include \"emissivePS\"\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\t#include \"clearCoatPS\"\n\t\t\t#include \"clearCoatGlossPS\"\n\t\t\t#include \"clearCoatNormalPS\"\n\t\t#endif\n\t\t#if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)\n\t\t\t#include \"anisotropyPS\"\n\t\t#endif\n\t\t#if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)\n\t\t\t#include \"lightmapPS\"\n\t\t#endif\n\t#endif\n\tvoid evaluateFrontend() {\n\t\t#if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE\n\t\t\tgetOpacity();\n\t\t\t#if defined(LIT_ALPHA_TEST)\n\t\t\t\talphaTest(dAlpha);\n\t\t\t#endif\n\t\t\t#if STD_OPACITY_DITHER != NONE\n\t\t\t\topacityDither(dAlpha, 0.0);\n\t\t\t#endif\n\t\t\tlitArgs_opacity = dAlpha;\n\t\t#endif\n\t\t#ifdef FORWARD_PASS\n\t\t\t#ifdef STD_HEIGHT_MAP\n\t\t\t\tgetParallax();\n\t\t\t#endif\n\t\t\tgetAlbedo();\n\t\t\tlitArgs_albedo = dAlbedo;\n\t\t\t#ifdef LIT_NEEDS_NORMAL\n\t\t\t\tgetNormal();\n\t\t\t\tlitArgs_worldNormal = dNormalW;\n\t\t\t#endif\n\t\t\t#ifdef LIT_REFRACTION\n\t\t\t\tgetRefraction();\n\t\t\t\tlitArgs_transmission = dTransmission;\n\t\t\t\tgetThickness();\n\t\t\t\tlitArgs_thickness = dThickness;\n\t\t\t\t#ifdef LIT_DISPERSION\n\t\t\t\t\tlitArgs_dispersion = material_dispersion;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_IRIDESCENCE\n\t\t\t\tgetIridescence();\n\t\t\t\tgetIridescenceThickness();\n\t\t\t\tlitArgs_iridescence_intensity = dIridescence;\n\t\t\t\tlitArgs_iridescence_thickness = dIridescenceThickness;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t\t\t#ifdef LIT_SHEEN\n\t\t\t\t\tgetSheen();\n\t\t\t\t\tlitArgs_sheen_specularity = sSpecularity;\n\t\t\t\t\tgetSheenGlossiness();\n\t\t\t\t\tlitArgs_sheen_gloss = sGlossiness;\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_METALNESS\n\t\t\t\t\tgetMetalness();\n\t\t\t\t\tlitArgs_metalness = dMetalness;\n\t\t\t\t\tgetIor();\n\t\t\t\t\tlitArgs_ior = dIor;\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\t\tgetSpecularityFactor();\n\t\t\t\t\tlitArgs_specularityFactor = dSpecularityFactor;\n\t\t\t\t#endif\n\t\t\t\tgetGlossiness();\n\t\t\t\tgetSpecularity();\n\t\t\t\tlitArgs_specularity = dSpecularity;\n\t\t\t\tlitArgs_gloss = dGlossiness;\n\t\t\t#endif\n\t\t\t#ifdef STD_AO\n\t\t\t\tgetAO();\n\t\t\t\tlitArgs_ao = dAo;\n\t\t\t#endif\n\t\t\tgetEmission();\n\t\t\tlitArgs_emission = dEmission;\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tgetClearCoat();\n\t\t\t\tgetClearCoatGlossiness();\n\t\t\t\tgetClearCoatNormal();\n\t\t\t\tlitArgs_clearcoat_specularity = ccSpecularity;\n\t\t\t\tlitArgs_clearcoat_gloss = ccGlossiness;\n\t\t\t\tlitArgs_clearcoat_worldNormal = ccNormalW;\n\t\t\t#endif\n\t\t\t#if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)\n\t\t\t\tgetAnisotropy();\n\t\t\t#endif\n\t\t\t#if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)\n\t\t\t\tgetLightMap();\n\t\t\t\tlitArgs_lightmap = dLightmap;\n\t\t\t\t#ifdef STD_LIGHTMAP_DIR\n\t\t\t\t\tlitArgs_lightmapDir = dLightmapDir;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t#endif\n\t}\n`;\n\nvar TBNPS$1 = `\n#ifdef LIT_TANGENTS\n\t#define TBN_TANGENTS\n#else\n\t#if defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS)\n\t\t#define TBN_DERIVATIVES\n\t#endif\n#endif\n#if defined(TBN_DERIVATIVES)\n\tuniform float tbnBasis;\n#endif\nvoid getTBN(vec3 tangent, vec3 binormal, vec3 normal) {\n\t#ifdef TBN_TANGENTS\n\t\tdTBN = mat3(normalize(tangent), normalize(binormal), normalize(normal));\n\t#elif defined(TBN_DERIVATIVES)\n\t\tvec2 uv = {lightingUv};\n\t\tvec3 dp1 = dFdx( vPositionW );\n\t\tvec3 dp2 = dFdy( vPositionW );\n\t\tvec2 duv1 = dFdx( uv );\n\t\tvec2 duv2 = dFdy( uv );\n\t\tvec3 dp2perp = cross( dp2, normal );\n\t\tvec3 dp1perp = cross( normal, dp1 );\n\t\tvec3 T = dp2perp * duv1.x + dp1perp * duv2.x;\n\t\tvec3 B = dp2perp * duv1.y + dp1perp * duv2.y;\n\t\tfloat denom = max( dot(T,T), dot(B,B) );\n\t\tfloat invmax = (denom == 0.0) ? 0.0 : tbnBasis / sqrt( denom );\n\t\tdTBN = mat3(T * invmax, -B * invmax, normal );\n\t#else\n\t\tvec3 B = cross(normal, vObjectSpaceUpW);\n\t\tvec3 T = cross(normal, B);\n\t\tif (dot(B,B)==0.0)\n\t\t{\n\t\t\tfloat major=max(max(normal.x, normal.y), normal.z);\n\t\t\tif (normal.x == major)\n\t\t\t{\n\t\t\t\tB = cross(normal, vec3(0,1,0));\n\t\t\t\tT = cross(normal, B);\n\t\t\t}\n\t\t\telse if (normal.y == major)\n\t\t\t{\n\t\t\t\tB = cross(normal, vec3(0,0,1));\n\t\t\t\tT = cross(normal, B);\n\t\t\t}\n\t\t\telse if (normal.z == major)\n\t\t\t{\n\t\t\t\tB = cross(normal, vec3(1,0,0));\n\t\t\t\tT = cross(normal, B);\n\t\t\t}\n\t\t}\n\t\tdTBN = mat3(normalize(T), normalize(B), normalize(normal));\n\t#endif\n}\n`;\n\nvar thicknessPS$1 = `\n#ifdef STD_THICKNESS_CONSTANT\nuniform float material_thickness;\n#endif\nvoid getThickness() {\n\tdThickness = 1.0;\n\t#ifdef STD_THICKNESS_CONSTANT\n\tdThickness *= material_thickness;\n\t#endif\n\t#ifdef STD_THICKNESS_TEXTURE\n\tdThickness *= texture2DBias({STD_THICKNESS_TEXTURE_NAME}, {STD_THICKNESS_TEXTURE_UV}, textureBias).{STD_THICKNESS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_THICKNESS_VERTEX\n\tdThickness *= saturate(vVertexColor.{STD_THICKNESS_VERTEX_CHANNEL});\n\t#endif\n}\n`;\n\nvar tonemappingPS$1 = `\n#if (TONEMAP == NONE)\n\t#include \"tonemappingNonePS\"\n#elif TONEMAP == FILMIC\n\t#include \"tonemappingFilmicPS\"\n#elif TONEMAP == LINEAR\n\t#include \"tonemappingLinearPS\"\n#elif TONEMAP == HEJL\n\t#include \"tonemappingHejlPS\"\n#elif TONEMAP == ACES\n\t#include \"tonemappingAcesPS\"\n#elif TONEMAP == ACES2\n\t#include \"tonemappingAces2PS\"\n#elif TONEMAP == NEUTRAL\n\t#include \"tonemappingNeutralPS\"\n#endif\n`;\n\nvar tonemappingAcesPS$1 = `\nuniform float exposure;\nvec3 toneMap(vec3 color) {\n\tfloat tA = 2.51;\n\tfloat tB = 0.03;\n\tfloat tC = 2.43;\n\tfloat tD = 0.59;\n\tfloat tE = 0.14;\n\tvec3 x = color * exposure;\n\treturn (x*(tA*x+tB))/(x*(tC*x+tD)+tE);\n}\n`;\n\nvar tonemappingAces2PS$1 = `\nuniform float exposure;\nconst mat3 ACESInputMat = mat3(\n\t0.59719, 0.35458, 0.04823,\n\t0.07600, 0.90834, 0.01566,\n\t0.02840, 0.13383, 0.83777\n);\nconst mat3 ACESOutputMat = mat3(\n\t 1.60475, -0.53108, -0.07367,\n\t-0.10208,  1.10813, -0.00605,\n\t-0.00327, -0.07276,  1.07602\n);\nvec3 RRTAndODTFit(vec3 v) {\n\tvec3 a = v * (v + 0.0245786) - 0.000090537;\n\tvec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n\treturn a / b;\n}\nvec3 toneMap(vec3 color) {\n\tcolor *= exposure / 0.6;\n\tcolor = color * ACESInputMat;\n\tcolor = RRTAndODTFit(color);\n\tcolor = color * ACESOutputMat;\n\tcolor = clamp(color, 0.0, 1.0);\n\treturn color;\n}\n`;\n\nvar tonemappingFilmicPS$1 = `\nconst float A =  0.15;\nconst float B =  0.50;\nconst float C =  0.10;\nconst float D =  0.20;\nconst float E =  0.02;\nconst float F =  0.30;\nconst float W =  11.2;\nuniform float exposure;\nvec3 uncharted2Tonemap(vec3 x) {\n\t return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;\n}\nvec3 toneMap(vec3 color) {\n\tcolor = uncharted2Tonemap(color * exposure);\n\tvec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W,W,W));\n\tcolor = color * whiteScale;\n\treturn color;\n}\n`;\n\nvar tonemappingHejlPS$1 = `\nuniform float exposure;\nvec3 toneMap(vec3 color) {\n\tcolor *= exposure;\n\tconst float  A = 0.22, B = 0.3, C = .1, D = 0.2, E = .01, F = 0.3;\n\tconst float Scl = 1.25;\n\tvec3 h = max( vec3(0.0), color - vec3(0.004) );\n\treturn (h*((Scl*A)*h+Scl*vec3(C*B,C*B,C*B))+Scl*vec3(D*E,D*E,D*E)) / (h*(A*h+vec3(B,B,B))+vec3(D*F,D*F,D*F)) - Scl*vec3(E/F,E/F,E/F);\n}\n`;\n\nvar tonemappingLinearPS$1 = `\nuniform float exposure;\nvec3 toneMap(vec3 color) {\n\treturn color * exposure;\n}\n`;\n\nvar tonemappingNeutralPS$1 = `\nuniform float exposure;\nvec3 toneMap(vec3 color) {\n\tcolor *= exposure;\n\tfloat startCompression = 0.8 - 0.04;\n\tfloat desaturation = 0.15;\n\tfloat x = min(color.r, min(color.g, color.b));\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max(color.r, max(color.g, color.b));\n\tif (peak < startCompression) return color;\n\tfloat d = 1. - startCompression;\n\tfloat newPeak = 1. - d * d / (peak + d - startCompression);\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);\n\treturn mix(color, newPeak * vec3(1, 1, 1), g);\n}\n`;\n\nvar tonemappingNonePS$1 = `\nvec3 toneMap(vec3 color) {\n\treturn color;\n}\n`;\n\nvar transformVS$1 = `\n#ifdef PIXELSNAP\nuniform vec4 uScreenSize;\n#endif\n#ifdef SCREENSPACE\nuniform float projectionFlipY;\n#endif\nvec4 evalWorldPosition(vec3 vertexPosition, mat4 modelMatrix) {\n\tvec3 localPos = getLocalPosition(vertexPosition);\n\t#ifdef NINESLICED\n\t\tlocalPos.xz *= outerScale;\n\t\tvec2 positiveUnitOffset = clamp(vertexPosition.xz, vec2(0.0), vec2(1.0));\n\t\tvec2 negativeUnitOffset = clamp(-vertexPosition.xz, vec2(0.0), vec2(1.0));\n\t\tlocalPos.xz += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;\n\t\tvTiledUv = (localPos.xz - outerScale + innerOffset.xy) * -0.5 + 1.0;\n\t\tlocalPos.xz *= -0.5;\n\t\tlocalPos = localPos.xzy;\n\t#endif\n\tvec4 posW = modelMatrix * vec4(localPos, 1.0);\n\t#ifdef SCREENSPACE\n\t\tposW.zw = vec2(0.0, 1.0);\n\t#endif\n\treturn posW;\n}\nvec4 getPosition() {\n\tdModelMatrix = getModelMatrix();\n\tvec4 posW = evalWorldPosition(vertex_position.xyz, dModelMatrix);\n\tdPositionW = posW.xyz;\n\tvec4 screenPos;\n\t#ifdef UV1LAYOUT\n\t\tscreenPos = vec4(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1);\n\t\t#ifdef WEBGPU\n\t\t\tscreenPos.y *= -1.0;\n\t\t#endif\n\t#else\n\t\t#ifdef SCREENSPACE\n\t\t\tscreenPos = posW;\n\t\t\tscreenPos.y *= projectionFlipY;\n\t\t#else\n\t\t\tscreenPos = matrix_viewProjection * posW;\n\t\t#endif\n\t\t#ifdef PIXELSNAP\n\t\t\tscreenPos.xy = (screenPos.xy * 0.5) + 0.5;\n\t\t\tscreenPos.xy *= uScreenSize.xy;\n\t\t\tscreenPos.xy = floor(screenPos.xy);\n\t\t\tscreenPos.xy *= uScreenSize.zw;\n\t\t\tscreenPos.xy = (screenPos.xy * 2.0) - 1.0;\n\t\t#endif\n\t#endif\n\treturn screenPos;\n}\nvec3 getWorldPosition() {\n\treturn dPositionW;\n}\n`;\n\nvar transformCoreVS$1 = `\nattribute vec4 vertex_position;\nuniform mat4 matrix_viewProjection;\nuniform mat4 matrix_model;\n#ifdef MORPHING\n\tuniform vec2 morph_tex_params;\n\tattribute uint morph_vertex_id;\n\tivec2 getTextureMorphCoords() {\n\t\tivec2 textureSize = ivec2(morph_tex_params);\n\t\tint morphGridV = int(morph_vertex_id) / textureSize.x;\n\t\tint morphGridU = int(morph_vertex_id) - (morphGridV * textureSize.x);\n\t\t#ifdef WEBGPU\n\t\t\tmorphGridV = textureSize.y - morphGridV - 1;\n\t\t#endif\n\t\treturn ivec2(morphGridU, morphGridV);\n\t}\n\t#ifdef MORPHING_POSITION\n\t\t#ifdef MORPHING_INT\n\t\t\tuniform vec3 aabbSize;\n\t\t\tuniform vec3 aabbMin;\n\t\t\tuniform usampler2D morphPositionTex;\n\t\t#else\n\t\t\tuniform highp sampler2D morphPositionTex;\n\t\t#endif\n\t#endif\n#endif\n#ifdef defined(BATCH)\n\t#include \"skinBatchVS\"\n\tmat4 getModelMatrix() {\n\t\treturn getBoneMatrix(vertex_boneIndices);\n\t}\n#elif defined(SKIN)\n\t#include \"skinVS\"\n\tmat4 getModelMatrix() {\n\t\treturn matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);\n\t}\n#elif defined(INSTANCING)\n\t#include \"transformInstancingVS\"\n#else\n\tmat4 getModelMatrix() {\n\t\treturn matrix_model;\n\t}\n#endif\nvec3 getLocalPosition(vec3 vertexPosition) {\n\tvec3 localPos = vertexPosition;\n\t#ifdef MORPHING_POSITION\n\t\tivec2 morphUV = getTextureMorphCoords();\n\t\t#ifdef MORPHING_INT\n\t\t\tvec3 morphPos = vec3(texelFetch(morphPositionTex, ivec2(morphUV), 0).xyz) / 65535.0 * aabbSize + aabbMin;\n\t\t#else\n\t\t\tvec3 morphPos = texelFetch(morphPositionTex, ivec2(morphUV), 0).xyz;\n\t\t#endif\n\t\tlocalPos += morphPos;\n\t#endif\n\treturn localPos;\n}\n`;\n\nvar transformInstancingVS$1 = `\nattribute vec4 instance_line1;\nattribute vec4 instance_line2;\nattribute vec4 instance_line3;\nattribute vec4 instance_line4;\nmat4 getModelMatrix() {\n\treturn matrix_model * mat4(instance_line1, instance_line2, instance_line3, instance_line4);\n}\n`;\n\nvar transmissionPS$1 = `\n#ifdef STD_REFRACTION_CONSTANT\nuniform float material_refraction;\n#endif\nvoid getRefraction() {\n\tfloat refraction = 1.0;\n\t#ifdef STD_REFRACTION_CONSTANT\n\trefraction = material_refraction;\n\t#endif\n\t#ifdef STD_REFRACTION_TEXTURE\n\trefraction *= texture2DBias({STD_REFRACTION_TEXTURE_NAME}, {STD_REFRACTION_TEXTURE_UV}, textureBias).{STD_REFRACTION_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_REFRACTION_VERTEX\n\trefraction *= saturate(vVertexColor.{STD_REFRACTION_VERTEX_CHANNEL});\n\t#endif\n\tdTransmission = refraction;\n}\n`;\n\nvar twoSidedLightingPS$1 = `\nuniform float twoSidedLightingNegScaleFactor;\nvoid handleTwoSidedLighting() {\n\tdTBN[2] *= gl_FrontFacing ? twoSidedLightingNegScaleFactor : -twoSidedLightingNegScaleFactor;\n}\n`;\n\nvar uv0VS$1 = `\n#ifdef NINESLICED\n\tvec2 getUv0() {\n\t\tvec2 uv = vertex_position.xz;\n\t\tvec2 positiveUnitOffset = clamp(vertex_position.xz, vec2(0.0), vec2(1.0));\n\t\tvec2 negativeUnitOffset = clamp(-vertex_position.xz, vec2(0.0), vec2(1.0));\n\t\tuv += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;\n\t\tuv = uv * -0.5 + 0.5;\n\t\tuv = uv * atlasRect.zw + atlasRect.xy;\n\t\tvMask = vertex_texCoord0.xy;\n\t\treturn uv;\n\t}\n#else\n\tvec2 getUv0() {\n\t\treturn vertex_texCoord0;\n\t}\n#endif\n`;\n\nvar uv1VS$1 = `\nvec2 getUv1() {\n\treturn vertex_texCoord1;\n}\n`;\n\nvar uvTransformVS$1 = `\nvUV{TRANSFORM_UV_{i}}_{TRANSFORM_ID_{i}} = vec2(\n\tdot(vec3(uv{TRANSFORM_UV_{i}}, 1), {TRANSFORM_NAME_{i}}0),\n\tdot(vec3(uv{TRANSFORM_UV_{i}}, 1), {TRANSFORM_NAME_{i}}1)\n);\n`;\n\nvar uvTransformUniformsPS$1 = `\n\tuniform vec3 {TRANSFORM_NAME_{i}}0;\n\tuniform vec3 {TRANSFORM_NAME_{i}}1;\n`;\n\nvar viewDirPS$1 = `\nvoid getViewDir() {\n\tdViewDirW = normalize(view_position - vPositionW);\n}\n`;\n\nconst shaderChunksGLSL = {\n\t\talphaTestPS: alphaTestPS$1,\n\t\tambientPS: ambientPS$1,\n\t\tanisotropyPS: anisotropyPS$1,\n\t\taoPS: aoPS$1,\n\t\taoDiffuseOccPS: aoDiffuseOccPS$1,\n\t\taoSpecOccPS: aoSpecOccPS$1,\n\t\tbakeDirLmEndPS: bakeDirLmEndPS$1,\n\t\tbakeLmEndPS: bakeLmEndPS$1,\n\t\tbasePS: basePS$1,\n\t\tbaseNineSlicedPS: baseNineSlicedPS$1,\n\t\tbaseNineSlicedTiledPS: baseNineSlicedTiledPS$1,\n\t\tbayerPS: bayerPS$1,\n\t\tblurVSMPS: blurVSMPS$1,\n\t\tclearCoatPS: clearCoatPS$1,\n\t\tclearCoatGlossPS: clearCoatGlossPS$1,\n\t\tclearCoatNormalPS: clearCoatNormalPS$1,\n\t\tclusteredLightCookiesPS: clusteredLightCookiesPS$1,\n\t\tclusteredLightShadowsPS: clusteredLightShadowsPS$1,\n\t\tclusteredLightUtilsPS: clusteredLightUtilsPS$1,\n\t\tclusteredLightPS: clusteredLightPS$1,\n\t\tcombinePS: combinePS$1,\n\t\tcookieBlit2DPS: cookieBlit2DPS$1,\n\t\tcookieBlitCubePS: cookieBlitCubePS$1,\n\t\tcookieBlitVS: cookieBlitVS$1,\n\t\tcookiePS,\n\t\tcubeMapProjectPS: cubeMapProjectPS$1,\n\t\tcubeMapRotatePS: cubeMapRotatePS$1,\n\t\tdebugOutputPS: debugOutputPS$1,\n\t\tdebugProcessFrontendPS: debugProcessFrontendPS$1,\n\t\tdetailModesPS: detailModesPS$1,\n\t\tdiffusePS: diffusePS$1,\n\t\tdecodePS: decodePS$1,\n\t\temissivePS: emissivePS$1,\n\t\tencodePS: encodePS$1,\n\t\tendPS: endPS$1,\n\t\tenvAtlasPS: envAtlasPS$1,\n\t\tenvProcPS: envProcPS$1,\n\t\tfalloffInvSquaredPS: falloffInvSquaredPS$1,\n\t\tfalloffLinearPS: falloffLinearPS$1,\n\t\tfloatAsUintPS: floatAsUintPS$1,\n\t\tfogPS: fogPS$1,\n\t\tfresnelSchlickPS: fresnelSchlickPS$1,\n\t\tfrontendCodePS: '',\n\t\tfrontendDeclPS: '',\n\t\tfullscreenQuadVS: fullscreenQuadVS$1,\n\t\tgammaPS: gammaPS$1,\n\t\tgles3PS,\n\t\tgles3VS,\n\t\tglossPS: glossPS$1,\n\t\tgsplatCenterVS: gsplatCenterVS$1,\n\t\tgsplatCornerVS: gsplatCornerVS$1,\n\t\tgsplatColorVS: gsplatColorVS$1,\n\t\tgsplatCommonVS: gsplatCommonVS$1,\n\t\tgsplatCompressedDataVS: gsplatCompressedDataVS$1,\n\t\tgsplatCompressedSHVS: gsplatCompressedSHVS$1,\n\t\tgsplatEvalSHVS: gsplatEvalSHVS$1,\n\t\tgsplatQuatToMat3VS: gsplatQuatToMat3VS$1,\n\t\tgsplatSogsColorVS: gsplatSogsColorVS$1,\n\t\tgsplatSogsDataVS: gsplatSogsDataVS$1,\n\t\tgsplatSogsSHVS: gsplatSogsSHVS$1,\n\t\tgsplatSourceFormatVS: gsplatSourceFormatVS$1,\n\t\tgsplatStructsVS: gsplatStructsVS$1,\n\t\tgsplatDataVS: gsplatDataVS$1,\n\t\tgsplatOutputVS: gsplatOutputVS$1,\n\t\tgsplatPS: gsplatPS$1,\n\t\tgsplatSHVS: gsplatSHVS$1,\n\t\tgsplatSourceVS: gsplatSourceVS$1,\n\t\tgsplatVS: gsplatVS$1,\n\t\tgsplatWorkBufferVS: gsplatWorkBufferVS$1,\n\t\tgsplatPackingPS: gsplatPackingPS$1,\n\t\tquadVS: quadVS$1,\n\t\timmediateLinePS: immediateLinePS$1,\n\t\timmediateLineVS: immediateLineVS$1,\n\t\tiridescenceDiffractionPS: iridescenceDiffractionPS$1,\n\t\tiridescencePS: iridescencePS$1,\n\t\tiridescenceThicknessPS: iridescenceThicknessPS$1,\n\t\tiorPS: iorPS$1,\n\t\tlightDeclarationPS: lightDeclarationPS$1,\n\t\tlightDiffuseLambertPS: lightDiffuseLambertPS$1,\n\t\tlightDirPointPS: lightDirPointPS$1,\n\t\tlightEvaluationPS: lightEvaluationPS$1,\n\t\tlightFunctionLightPS: lightFunctionLightPS$1,\n\t\tlightFunctionShadowPS: lightFunctionShadowPS$1,\n\t\tlightingPS: lightingPS$1,\n\t\tlightmapAddPS: lightmapAddPS$1,\n\t\tlightmapPS: lightmapPS$1,\n\t\tlightSpecularAnisoGGXPS: lightSpecularAnisoGGXPS$1,\n\t\tlightSpecularBlinnPS: lightSpecularBlinnPS$1,\n\t\tlightSheenPS: lightSheenPS$1,\n\t\tlinearizeDepthPS: linearizeDepthPS$1,\n\t\tlitForwardBackendPS: litForwardBackendPS$1,\n\t\tlitForwardDeclarationPS: litForwardDeclarationPS$1,\n\t\tlitForwardMainPS: litForwardMainPS$1,\n\t\tlitForwardPostCodePS: litForwardPostCodePS$1,\n\t\tlitForwardPreCodePS: litForwardPreCodePS$1,\n\t\tlitMainPS: litMainPS$1,\n\t\tlitMainVS: litMainVS$1,\n\t\tlitOtherMainPS: litOtherMainPS$1,\n\t\tlitShaderArgsPS: litShaderArgsPS$1,\n\t\tlitShaderCorePS: litShaderCorePS$1,\n\t\tlitShadowMainPS: litShadowMainPS$1,\n\t\tlitUserDeclarationPS: '',\n\t\tlitUserDeclarationVS: '',\n\t\tlitUserCodePS: '',\n\t\tlitUserCodeVS: '',\n\t\tlitUserMainStartPS: '',\n\t\tlitUserMainStartVS: '',\n\t\tlitUserMainEndPS: '',\n\t\tlitUserMainEndVS: '',\n\t\tltcPS: ltcPS$1,\n\t\tmetalnessPS: metalnessPS$1,\n\t\tmetalnessModulatePS: metalnessModulatePS$1,\n\t\tmorphPS: morphPS$1,\n\t\tmorphVS: morphVS$1,\n\t\tmsdfPS: msdfPS$1,\n\t\tmsdfVS: msdfVS$1,\n\t\tnormalVS: normalVS$1,\n\t\tnormalCoreVS: normalCoreVS$1,\n\t\tnormalMapPS: normalMapPS$1,\n\t\topacityPS: opacityPS$1,\n\t\topacityDitherPS: opacityDitherPS$1,\n\t\toutputPS: outputPS$1,\n\t\toutputAlphaPS: outputAlphaPS$1,\n\t\toutputTex2DPS: outputTex2DPS$1,\n\t\tsheenPS: sheenPS$1,\n\t\tsheenGlossPS: sheenGlossPS$1,\n\t\tparallaxPS: parallaxPS$1,\n\t\tpickPS: pickPS$1,\n\t\treflDirPS: reflDirPS$1,\n\t\treflDirAnisoPS: reflDirAnisoPS$1,\n\t\treflectionCCPS: reflectionCCPS$1,\n\t\treflectionCubePS: reflectionCubePS$1,\n\t\treflectionEnvHQPS: reflectionEnvHQPS$1,\n\t\treflectionEnvPS: reflectionEnvPS$1,\n\t\treflectionSpherePS: reflectionSpherePS$1,\n\t\treflectionSheenPS: reflectionSheenPS$1,\n\t\trefractionCubePS: refractionCubePS$1,\n\t\trefractionDynamicPS: refractionDynamicPS$1,\n\t\treprojectPS: reprojectPS$1,\n\t\treprojectVS: reprojectVS$1,\n\t\tscreenDepthPS: screenDepthPS$1,\n\t\tshadowCascadesPS: shadowCascadesPS$1,\n\t\tshadowEVSMPS: shadowEVSMPS$1,\n\t\tshadowPCF1PS: shadowPCF1PS$1,\n\t\tshadowPCF3PS: shadowPCF3PS$1,\n\t\tshadowPCF5PS: shadowPCF5PS$1,\n\t\tshadowPCSSPS,\n\t\tshadowSoftPS: shadowSoftPS$1,\n\t\tskinBatchVS: skinBatchVS$1,\n\t\tskinVS: skinVS$1,\n\t\tskyboxPS: skyboxPS$1,\n\t\tskyboxVS: skyboxVS$1,\n\t\tspecularPS: specularPS$1,\n\t\tsphericalPS: sphericalPS$1,\n\t\tspecularityFactorPS: specularityFactorPS$1,\n\t\tspotPS: spotPS$1,\n\t\tstartNineSlicedPS: startNineSlicedPS$1,\n\t\tstartNineSlicedTiledPS: startNineSlicedTiledPS$1,\n\t\tstdDeclarationPS: stdDeclarationPS$1,\n\t\tstdFrontEndPS: stdFrontEndPS$1,\n\t\tTBNPS: TBNPS$1,\n\t\tthicknessPS: thicknessPS$1,\n\t\ttonemappingPS: tonemappingPS$1,\n\t\ttonemappingAcesPS: tonemappingAcesPS$1,\n\t\ttonemappingAces2PS: tonemappingAces2PS$1,\n\t\ttonemappingFilmicPS: tonemappingFilmicPS$1,\n\t\ttonemappingHejlPS: tonemappingHejlPS$1,\n\t\ttonemappingLinearPS: tonemappingLinearPS$1,\n\t\ttonemappingNeutralPS: tonemappingNeutralPS$1,\n\t\ttonemappingNonePS: tonemappingNonePS$1,\n\t\ttransformVS: transformVS$1,\n\t\ttransformCoreVS: transformCoreVS$1,\n\t\ttransformInstancingVS: transformInstancingVS$1,\n\t\ttransmissionPS: transmissionPS$1,\n\t\ttwoSidedLightingPS: twoSidedLightingPS$1,\n\t\tuv0VS: uv0VS$1,\n\t\tuv1VS: uv1VS$1,\n\t\tuvTransformVS: uvTransformVS$1,\n\t\tuvTransformUniformsPS: uvTransformUniformsPS$1,\n\t\tviewDirPS: viewDirPS$1,\n\t\twebgpuPS: webgpuPS$1,\n\t\twebgpuVS: webgpuVS$1\n};\n\nvar alphaTestPS = `\nuniform alpha_ref: f32;\nfn alphaTest(a: f32) {\n\tif (a < uniform.alpha_ref) {\n\t\tdiscard;\n\t}\n}\n`;\n\nvar ambientPS = `\n#if LIT_AMBIENT_SOURCE == AMBIENTSH\n\tuniform ambientSH: array<vec3f, 9>;\n#endif\n#if LIT_AMBIENT_SOURCE == ENVALATLAS\n\t#include \"envAtlasPS\"\n\t#ifndef ENV_ATLAS\n\t\t#define ENV_ATLAS\n\t\tvar texture_envAtlas: texture_2d<f32>;\n\t\tvar texture_envAtlasSampler: sampler;\n\t#endif\n#endif\nfn addAmbient(worldNormal: vec3f) {\n\t#ifdef LIT_AMBIENT_SOURCE == AMBIENTSH\n\t\tlet n: vec3f = cubeMapRotate(worldNormal);\n\t\tlet color: vec3f =\n\t\t\tuniform.ambientSH[0] +\n\t\t\tuniform.ambientSH[1] * n.x +\n\t\t\tuniform.ambientSH[2] * n.y +\n\t\t\tuniform.ambientSH[3] * n.z +\n\t\t\tuniform.ambientSH[4] * n.x * n.z +\n\t\t\tuniform.ambientSH[5] * n.z * n.y +\n\t\t\tuniform.ambientSH[6] * n.y * n.x +\n\t\t\tuniform.ambientSH[7] * (3.0 * n.z * n.z - 1.0) +\n\t\t\tuniform.ambientSH[8] * (n.x * n.x - n.y * n.y);\n\t\tdDiffuseLight += processEnvironment(max(color, vec3f(0.0)));\n\t#endif\n\t#if LIT_AMBIENT_SOURCE == ENVALATLAS\n\t\tlet dir: vec3f = normalize(cubeMapRotate(worldNormal) * vec3f(-1.0, 1.0, 1.0));\n\t\tlet uv: vec2f = mapUv(toSphericalUv(dir), vec4f(128.0, 256.0 + 128.0, 64.0, 32.0) / atlasSize);\n\t\tlet raw: vec4f = textureSample(texture_envAtlas, texture_envAtlasSampler, uv);\n\t\tlet linear: vec3f = {ambientDecode}(raw);\n\t\tdDiffuseLight += processEnvironment(linear);\n\t#endif\n\t#if LIT_AMBIENT_SOURCE == CONSTANT\n\t\tdDiffuseLight += uniform.light_globalAmbient;\n\t#endif\n}\n`;\n\nvar anisotropyPS = `\n#ifdef LIT_GGX_SPECULAR\n\tuniform material_anisotropyIntensity: f32;\n\tuniform material_anisotropyRotation: vec2f;\n#endif\nfn getAnisotropy() {\n\tdAnisotropy = 0.0;\n\tdAnisotropyRotation = vec2f(1.0, 0.0);\n#ifdef LIT_GGX_SPECULAR\n\tdAnisotropy = uniform.material_anisotropyIntensity;\n\tdAnisotropyRotation = uniform.material_anisotropyRotation;\n#endif\n#ifdef STD_ANISOTROPY_TEXTURE\n\tlet anisotropyTex: vec3f = textureSampleBias({STD_ANISOTROPY_TEXTURE_NAME}, {STD_ANISOTROPY_TEXTURE_NAME}Sampler, {STD_ANISOTROPY_TEXTURE_UV}, uniform.textureBias).rgb;\n\tdAnisotropy *= anisotropyTex.b;\n\tlet anisotropyRotationFromTex: vec2f = anisotropyTex.rg * 2.0 - vec2f(1.0);\n\tlet rotationMatrix: mat2x2f = mat2x2f(dAnisotropyRotation.x, dAnisotropyRotation.y, -dAnisotropyRotation.y, dAnisotropyRotation.x);\n\tdAnisotropyRotation = rotationMatrix * anisotropyRotationFromTex;\n#endif\n\tdAnisotropy = clamp(dAnisotropy, 0.0, 1.0);\n}\n`;\n\nvar aoPS = `\n#if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)\n\tuniform material_aoIntensity: f32;\n#endif\n#ifdef STD_AODETAIL_TEXTURE\n\t#include \"detailModesPS\"\n#endif\nfn getAO() {\n\tdAo = 1.0;\n\t#ifdef STD_AO_TEXTURE\n\t\tvar aoBase: f32 = textureSampleBias({STD_AO_TEXTURE_NAME}, {STD_AO_TEXTURE_NAME}Sampler, {STD_AO_TEXTURE_UV}, uniform.textureBias).{STD_AO_TEXTURE_CHANNEL};\n\t\t#ifdef STD_AODETAIL_TEXTURE\n\t\t\tvar aoDetail: f32 = textureSampleBias({STD_AODETAIL_TEXTURE_NAME}, {STD_AODETAIL_TEXTURE_NAME}Sampler, {STD_AODETAIL_TEXTURE_UV}, uniform.textureBias).{STD_AODETAIL_TEXTURE_CHANNEL};\n\t\t\taoBase = detailMode_{STD_AODETAIL_DETAILMODE}(vec3f(aoBase), vec3f(aoDetail)).r;\n\t\t#endif\n\t\tdAo = dAo * aoBase;\n\t#endif\n\t#ifdef STD_AO_VERTEX\n\t\tdAo = dAo * saturate(vVertexColor.{STD_AO_VERTEX_CHANNEL});\n\t#endif\n\t#if defined(STD_AO_TEXTURE) || defined(STD_AO_VERTEX)\n\t\tdAo = mix(1.0, dAo, uniform.material_aoIntensity);\n\t#endif\n}\n`;\n\nvar aoDiffuseOccPS = `\nfn occludeDiffuse(ao: f32) {\n\tdDiffuseLight = dDiffuseLight * ao;\n}\n`;\n\nvar aoSpecOccPS = `\n#if LIT_OCCLUDE_SPECULAR != NONE\n\t#ifdef LIT_OCCLUDE_SPECULAR_FLOAT\n\t\tuniform material_occludeSpecularIntensity: f32;\n\t#endif\n#endif\nfn occludeSpecular(gloss: f32, ao: f32, worldNormal: vec3f, viewDir: vec3f) {\n\t#if LIT_OCCLUDE_SPECULAR == AO\n\t\t#ifdef LIT_OCCLUDE_SPECULAR_FLOAT\n\t\t\tvar specOcc: f32 = mix(1.0, ao, uniform.material_occludeSpecularIntensity);\n\t\t#else\n\t\t\tvar specOcc: f32 = ao;\n\t\t#endif\n\t#endif\n\t#if LIT_OCCLUDE_SPECULAR == GLOSSDEPENDENT\n\t\tvar specPow: f32 = exp2(gloss * 11.0);\n\t\tvar specOcc: f32 = saturate(pow(dot(worldNormal, viewDir) + ao, 0.01 * specPow) - 1.0 + ao);\n\t\t#ifdef LIT_OCCLUDE_SPECULAR_FLOAT\n\t\t\tspecOcc = mix(1.0, specOcc, uniform.material_occludeSpecularIntensity);\n\t\t#endif\n\t#endif\n\t#if LIT_OCCLUDE_SPECULAR != NONE\n\t\tdSpecularLight = dSpecularLight * specOcc;\n\t\tdReflection = dReflection * specOcc;\n\t\t#ifdef LIT_SHEEN\n\t\t\tsSpecularLight = sSpecularLight * specOcc;\n\t\t\tsReflection = sReflection * specOcc;\n\t\t#endif\n\t#endif\n}\n`;\n\nvar bakeDirLmEndPS = `\n\tlet dirLm = textureSample(texture_dirLightMap, texture_dirLightMapSampler, vUv1);\n\tif (uniform.bakeDir > 0.5) {\n\t\tif (dAtten > 0.00001) {\n\t\t\tlet unpacked_dir = dirLm.xyz * 2.0 - vec3f(1.0);\n\t\t\tdAtten = clamp(dAtten, 0.0, 1.0);\n\t\t\tlet combined_dir = dLightDirNormW.xyz * dAtten + unpacked_dir * dirLm.w;\n\t\t\tlet finalRgb = normalize(combined_dir) * 0.5 + vec3f(0.5);\n\t\t\tlet finalA = max(dirLm.w + dAtten, 1.0 / 255.0);\n\t\t\toutput.color = vec4f(finalRgb, finalA);\n\t\t} else {\n\t\t\toutput.color = dirLm;\n\t\t}\n\t} else {\n\t\tlet alpha_min = select(0.0, 1.0 / 255.0, dAtten > 0.00001);\n\t\tlet finalA = max(dirLm.w, alpha_min);\n\t\toutput.color = vec4f(dirLm.rgb, finalA);\n\t}\n`;\n\nvar bakeLmEndPS = `\n#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT\n\tdDiffuseLight = ((dDiffuseLight - 0.5) * max(uniform.ambientBakeOcclusionContrast + 1.0, 0.0)) + 0.5;\n\tdDiffuseLight = dDiffuseLight + vec3f(uniform.ambientBakeOcclusionBrightness);\n\tdDiffuseLight = saturate3(dDiffuseLight);\n\tdDiffuseLight = dDiffuseLight * dAmbientLight;\n#endif\n#ifdef LIGHTMAP_RGBM\n\tvar temp_color_rgbm = vec4f(dDiffuseLight, 1.0);\n\ttemp_color_rgbm = vec4f(pow(temp_color_rgbm.rgb, vec3f(0.5)), temp_color_rgbm.a);\n\ttemp_color_rgbm = vec4f(temp_color_rgbm.rgb / 8.0, temp_color_rgbm.a);\n\tlet max_g_b = max(temp_color_rgbm.g, max(temp_color_rgbm.b, 1.0 / 255.0));\n\tlet max_rgb = max(temp_color_rgbm.r, max_g_b);\n\ttemp_color_rgbm.a = clamp(max_rgb, 0.0, 1.0);\n\ttemp_color_rgbm.a = ceil(temp_color_rgbm.a * 255.0) / 255.0;\n\ttemp_color_rgbm = vec4f(temp_color_rgbm.rgb / temp_color_rgbm.a, temp_color_rgbm.a);\n\toutput.color = temp_color_rgbm;\n#else\n\toutput.color = vec4f(dDiffuseLight, 1.0);\n#endif\n`;\n\nvar basePS = `\nuniform view_position: vec3f;\nuniform light_globalAmbient: vec3f;\nfn square(x: f32) -> f32 {\n\treturn x*x;\n}\nfn saturate(x: f32) -> f32 {\n\treturn clamp(x, 0.0, 1.0);\n}\nfn saturate3(x: vec3f) -> vec3f {\n\treturn clamp(x, vec3f(0.0), vec3f(1.0));\n}\n`;\n\nvar baseNineSlicedPS = `\n#define NINESLICED\nvarying vMask: vec2f;\nvarying vTiledUv: vec2f;\nuniform innerOffset: vec4f;\nuniform outerScale: vec2f;\nuniform atlasRect: vec4f;\nvar<private> nineSlicedUv: vec2f;\n`;\n\nvar baseNineSlicedTiledPS = `\n#define NINESLICED\n#define NINESLICETILED\nvarying vMask: vec2f;\nvarying vTiledUv: vec2f;\nuniform innerOffset: vec4f;\nuniform outerScale: vec2f;\nuniform atlasRect: vec4f;\nvar<private> nineSlicedUv: vec2f;\n`;\n\nvar bayerPS = `\nfn bayer2(p: vec2f) -> f32 {\n\treturn (2.0 * p.y + p.x + 1.0) % 4.0;\n}\nfn bayer4(p: vec2f) -> f32 {\n\tlet p1: vec2f = p % vec2f(2.0);\n\tlet p2: vec2f = floor(0.5 * (p % vec2f(4.0)));\n\treturn 4.0 * bayer2(p1) + bayer2(p2);\n}\nfn bayer8(p: vec2f) -> f32 {\n\tlet p1: vec2f = p % vec2f(2.0);\n\tlet p2: vec2f = floor(0.5 * (p % vec2f(4.0)));\n\tlet p4: vec2f = floor(0.25 * (p % vec2f(8.0)));\n\treturn 4.0 * (4.0 * bayer2(p1) + bayer2(p2)) + bayer2(p4);\n}\n`;\n\nvar blurVSMPS = `\nvarying vUv0: vec2f;\nvar source: texture_2d<f32>;\nvar sourceSampler: sampler;\n#ifdef GAUSS\n\tuniform weight: array<f32, {SAMPLES}>;\n#endif\nuniform pixelOffset: vec2f;\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\tvar moments: vec3f = vec3f(0.0);\n\tlet uv: vec2f = input.vUv0 - uniform.pixelOffset * (f32({SAMPLES}) * 0.5);\n\tfor (var i: i32 = 0; i < {SAMPLES}; i = i + 1) {\n\t\tlet c: vec4f = textureSample(source, sourceSampler, uv + uniform.pixelOffset * f32(i));\n\t\t#ifdef GAUSS\n\t\t\tmoments = moments + c.xyz * uniform.weight[i].element;\n\t\t#else\n\t\t\tmoments = moments + c.xyz;\n\t\t#endif\n\t}\n\t#ifndef GAUSS\n\t\tmoments = moments * (1.0 / f32({SAMPLES}));\n\t#endif\n\toutput.color = vec4f(moments, 1.0);\n\treturn output;\n}\n`;\n\nvar clearCoatPS = `\nuniform material_clearCoat: f32;\nfn getClearCoat() {\n\tccSpecularity = uniform.material_clearCoat;\n\t#ifdef STD_CLEARCOAT_TEXTURE\n\tccSpecularity = ccSpecularity * textureSampleBias({STD_CLEARCOAT_TEXTURE_NAME}, {STD_CLEARCOAT_TEXTURE_NAME}Sampler, {STD_CLEARCOAT_TEXTURE_UV}, uniform.textureBias).{STD_CLEARCOAT_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_CLEARCOAT_VERTEX\n\tccSpecularity = ccSpecularity * saturate(vVertexColor.{STD_CLEARCOAT_VERTEX_CHANNEL});\n\t#endif\n}\n`;\n\nvar clearCoatGlossPS = `\n\tuniform material_clearCoatGloss: f32;\nfn getClearCoatGlossiness() {\n\tccGlossiness = uniform.material_clearCoatGloss;\n\t#ifdef STD_CLEARCOATGLOSS_TEXTURE\n\tccGlossiness = ccGlossiness * textureSampleBias({STD_CLEARCOATGLOSS_TEXTURE_NAME}, {STD_CLEARCOATGLOSS_TEXTURE_NAME}Sampler, {STD_CLEARCOATGLOSS_TEXTURE_UV}, uniform.textureBias).{STD_CLEARCOATGLOSS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_CLEARCOATGLOSS_VERTEX\n\tccGlossiness = ccGlossiness * saturate(vVertexColor.{STD_CLEARCOATGLOSS_VERTEX_CHANNEL});\n\t#endif\n\t#ifdef STD_CLEARCOATGLOSS_INVERT\n\tccGlossiness = 1.0 - ccGlossiness;\n\t#endif\n\tccGlossiness += 0.0000001;\n}\n`;\n\nvar clearCoatNormalPS = `\n#ifdef STD_CLEARCOATNORMAL_TEXTURE\n\tuniform material_clearCoatBumpiness: f32;\n#endif\nfn getClearCoatNormal() {\n#ifdef STD_CLEARCOATNORMAL_TEXTURE\n\tvar normalMap: vec3f = {STD_CLEARCOATNORMAL_TEXTURE_DECODE}(textureSampleBias({STD_CLEARCOATNORMAL_TEXTURE_NAME}, {STD_CLEARCOATNORMAL_TEXTURE_NAME}Sampler, {STD_CLEARCOATNORMAL_TEXTURE_UV}, uniform.textureBias));\n\tnormalMap = mix(vec3f(0.0, 0.0, 1.0), normalMap, uniform.material_clearCoatBumpiness);\n\tccNormalW = normalize(dTBN * normalMap);\n#else\n\tccNormalW = dVertexNormalW;\n#endif\n}\n`;\n\nvar clusteredLightUtilsPS = `\nstruct FaceCoords {\n\tuv: vec2f,\n\tfaceIndex: f32,\n\ttileOffset: vec2f,\n}\nfn getCubemapFaceCoordinates(dir: vec3f) -> FaceCoords {\n\tvar faceIndex: f32;\n\tvar tileOffset: vec2f;\n\tvar uv: vec2f;\n\tlet vAbs: vec3f = abs(dir);\n\tvar ma: f32;\n\tif (vAbs.z >= vAbs.x && vAbs.z >= vAbs.y) {\n\t\tlet is_neg_z = dir.z < 0.0;\n\t\tfaceIndex = select(4.0, 5.0, is_neg_z);\n\t\tma = 0.5 / vAbs.z;\n\t\tuv = vec2f(select(dir.x, -dir.x, is_neg_z), -dir.y);\n\t\ttileOffset = vec2f(2.0, select(0.0, 1.0, is_neg_z));\n\t} else if (vAbs.y >= vAbs.x) {\n\t\tlet is_neg_y = dir.y < 0.0;\n\t\tfaceIndex = select(2.0, 3.0, is_neg_y);\n\t\tma = 0.5 / vAbs.y;\n\t\tuv = vec2f(dir.x, select(dir.z, -dir.z, is_neg_y));\n\t\ttileOffset = vec2f(1.0, select(0.0, 1.0, is_neg_y));\n\t} else {\n\t\tlet is_neg_x = dir.x < 0.0;\n\t\tfaceIndex = select(0.0, 1.0, is_neg_x);\n\t\tma = 0.5 / vAbs.x;\n\t\tuv = vec2f(select(-dir.z, dir.z, is_neg_x), -dir.y);\n\t\ttileOffset = vec2f(0.0, select(0.0, 1.0, is_neg_x));\n\t}\n\tuv = uv * ma + 0.5;\n\treturn FaceCoords(uv, faceIndex, tileOffset);\n}\nfn getCubemapAtlasCoordinates(omniAtlasViewport: vec3f, shadowEdgePixels: f32, shadowTextureResolution: f32, dir: vec3f) -> vec2f {\n\tlet faceData: FaceCoords = getCubemapFaceCoordinates(dir);\n\tvar uv: vec2f = faceData.uv;\n\tlet tileOffset: vec2f = faceData.tileOffset;\n\tlet atlasFaceSize: f32 = omniAtlasViewport.z;\n\tlet tileSize: f32 = shadowTextureResolution * atlasFaceSize;\n\tvar offset: f32 = shadowEdgePixels / tileSize;\n\tuv = uv * (1.0 - offset * 2.0) + offset;\n\tuv = uv * atlasFaceSize;\n\tuv = uv + tileOffset * atlasFaceSize;\n\tuv = uv + omniAtlasViewport.xy;\n\treturn uv;\n}\n`;\n\nvar clusteredLightCookiesPS = `\nfn _getCookieClustered(tex: texture_2d<f32>, texSampler: sampler, uv: vec2f, intensity: f32, cookieChannel: vec4f) -> vec3f {\n\tlet pixel: vec4f = mix(vec4f(1.0), textureSampleLevel(tex, texSampler, uv, 0.0), intensity);\n\tlet isRgb: bool = dot(cookieChannel.rgb, vec3f(1.0)) == 3.0;\n\treturn select(vec3f(dot(pixel, cookieChannel)), pixel.rgb, isRgb);\n}\nfn getCookie2DClustered(tex: texture_2d<f32>, texSampler: sampler, transform: mat4x4f, worldPosition: vec3f, intensity: f32, cookieChannel: vec4f) -> vec3f {\n\tlet projPos: vec4f = transform * vec4f(worldPosition, 1.0);\n\treturn _getCookieClustered(tex, texSampler, projPos.xy / projPos.w, intensity, cookieChannel);\n}\nfn getCookieCubeClustered(tex: texture_2d<f32>, texSampler: sampler, dir: vec3f, intensity: f32, cookieChannel: vec4f, shadowTextureResolution: f32, shadowEdgePixels: f32, omniAtlasViewport: vec3f) -> vec3f {\n\tlet uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, dir);\n\treturn _getCookieClustered(tex, texSampler, uv, intensity, cookieChannel);\n}\n`;\n\nvar clusteredLightShadowsPS = `\nfn _getShadowCoordPerspZbuffer(shadowMatrix: mat4x4f, shadowParams: vec4f, wPos: vec3f) -> vec3f {\n\tvar projPos = shadowMatrix * vec4f(wPos, 1.0);\n\treturn projPos.xyz / projPos.w;\n}\nfn getShadowCoordPerspZbufferNormalOffset(shadowMatrix: mat4x4f, shadowParams: vec4f, normal: vec3f) -> vec3f {\n\tlet wPos: vec3f = vPositionW + normal * shadowParams.y;\n\treturn _getShadowCoordPerspZbuffer(shadowMatrix, shadowParams, wPos);\n}\nfn normalOffsetPointShadow(shadowParams: vec4f, lightPos: vec3f, lightDir: vec3f, lightDirNorm: vec3f, normal: vec3f) -> vec3f {\n\tlet distScale: f32 = length(lightDir);\n\tlet wPos: vec3f = vPositionW + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;\n\tlet dir: vec3f = wPos - lightPos;\n\treturn dir;\n}\n#if defined(CLUSTER_SHADOW_TYPE_PCF1)\n\tfn getShadowOmniClusteredPCF1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowParams: vec4f, omniAtlasViewport: vec3f, shadowEdgePixels: f32, lightDir: vec3f) -> f32 {\n\t\tlet shadowTextureResolution: f32 = shadowParams.x;\n\t\tlet uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);\n\t\tlet shadowZ: f32 = length(lightDir) * shadowParams.w + shadowParams.z;\n\t\treturn textureSampleCompareLevel(shadowMap, shadowMapSampler, uv, shadowZ);\n\t}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF3)\n\tfn getShadowOmniClusteredPCF3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowParams: vec4f, omniAtlasViewport: vec3f, shadowEdgePixels: f32, lightDir: vec3f) -> f32 {\n\t\tlet shadowTextureResolution: f32 = shadowParams.x;\n\t\tlet uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);\n\t\tlet shadowZ: f32 = length(lightDir) * shadowParams.w + shadowParams.z;\n\t\tlet shadowCoord: vec3f = vec3f(uv, shadowZ);\n\t\treturn getShadowPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams);\n\t}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF5)\n\tfn getShadowOmniClusteredPCF5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowParams: vec4f, omniAtlasViewport: vec3f, shadowEdgePixels: f32, lightDir: vec3f) -> f32 {\n\t\tlet shadowTextureResolution: f32 = shadowParams.x;\n\t\tlet uv: vec2f = getCubemapAtlasCoordinates(omniAtlasViewport, shadowEdgePixels, shadowTextureResolution, lightDir);\n\t\tlet shadowZ: f32 = length(lightDir) * shadowParams.w + shadowParams.z;\n\t\tlet shadowCoord: vec3f = vec3f(uv, shadowZ);\n\t\treturn getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams);\n\t}\n#endif\n#if defined(CLUSTER_SHADOW_TYPE_PCF1)\n\tfn getShadowSpotClusteredPCF1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\t\treturn textureSampleCompareLevel(shadowMap, shadowMapSampler, shadowCoord.xy, shadowCoord.z);\n\t}\n#endif\n\t#if defined(CLUSTER_SHADOW_TYPE_PCF3)\n\tfn getShadowSpotClusteredPCF3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\t\treturn getShadowSpotPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams);\n\t}\n#endif\n\t#if defined(CLUSTER_SHADOW_TYPE_PCF5)\n\tfn getShadowSpotClusteredPCF5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\t\treturn getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams);\n\t}\n#endif\n`;\n\nvar clusteredLightPS = `\n#include \"lightBufferDefinesPS\"\n#include \"clusteredLightUtilsPS\"\n#ifdef CLUSTER_COOKIES\n\t#include \"clusteredLightCookiesPS\"\n#endif\n#ifdef CLUSTER_SHADOWS\n\t#include \"clusteredLightShadowsPS\"\n#endif\nvar clusterWorldTexture: texture_2d<f32>;\nvar lightsTexture: texture_2d<f32>;\n#ifdef CLUSTER_SHADOWS\n\tvar shadowAtlasTexture: texture_depth_2d;\n\tvar shadowAtlasTextureSampler: sampler_comparison;\n#endif\n#ifdef CLUSTER_COOKIES\n\tvar cookieAtlasTexture: texture_2d<f32>;\n\tvar cookieAtlasTextureSampler: sampler;\n#endif\nuniform clusterMaxCells: i32;\nuniform clusterSkip: f32;\nuniform clusterCellsCountByBoundsSize: vec3f;\nuniform clusterTextureSize: vec3f;\nuniform clusterBoundsMin: vec3f;\nuniform clusterBoundsDelta: vec3f;\nuniform clusterCellsDot: vec3f;\nuniform clusterCellsMax: vec3f;\nuniform shadowAtlasParams: vec2f;\nstruct ClusterLightData {\n\tflags: u32,\n\thalfWidth: vec3f,\n\tisSpot: bool,\n\thalfHeight: vec3f,\n\tlightIndex: i32,\n\tposition: vec3f,\n\tshape: u32,\n\tdirection: vec3f,\n\tfalloffModeLinear: bool,\n\tcolor: vec3f,\n\tshadowIntensity: f32,\n\tomniAtlasViewport: vec3f,\n\trange: f32,\n\tcookieChannelMask: vec4f,\n\tbiasesData: f32,\n\tshadowBias: f32,\n\tshadowNormalBias: f32,\n\tanglesData: f32,\n\tinnerConeAngleCos: f32,\n\touterConeAngleCos: f32,\n\tcookieIntensity: f32,\n\tisDynamic: bool,\n\tisLightmapped: bool\n}\nvar<private> lightProjectionMatrix: mat4x4f;\nfn sampleLightTextureF(lightIndex: i32, index: i32) -> vec4f {\n\treturn textureLoad(lightsTexture, vec2<i32>(index, lightIndex), 0);\n}\nfn decodeClusterLightCore(clusterLightData: ptr<function, ClusterLightData>, lightIndex: f32) {\n\tclusterLightData.lightIndex = i32(lightIndex);\n\tlet halfData: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS});\n\tclusterLightData.anglesData = halfData.z;\n\tclusterLightData.biasesData = halfData.w;\n\tlet colorRG: vec2f = unpack2x16float(bitcast<u32>(halfData.x));\n\tlet colorB_: vec2f = unpack2x16float(bitcast<u32>(halfData.y));\n\tclusterLightData.color = vec3f(colorRG, colorB_.x) * {LIGHT_COLOR_DIVIDER};\n\tlet lightPosRange: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_POSITION_RANGE});\n\tclusterLightData.position = lightPosRange.xyz;\n\tclusterLightData.range = lightPosRange.w;\n\tlet lightDir_Flags: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_DIRECTION_FLAGS});\n\tclusterLightData.direction = lightDir_Flags.xyz;\n\tlet flags_uint: u32 = bitcast<u32>(lightDir_Flags.w);\n\tclusterLightData.flags = flags_uint;\n\tclusterLightData.isSpot = (flags_uint & (1u << 30u)) != 0u;\n\tclusterLightData.shape = (flags_uint >> 28u) & 0x3u;\n\tclusterLightData.falloffModeLinear = (flags_uint & (1u << 27u)) == 0u;\n\tclusterLightData.shadowIntensity = f32((flags_uint >> 0u) & 0xFFu) / 255.0;\n\tclusterLightData.cookieIntensity = f32((flags_uint >> 8u) & 0xFFu) / 255.0;\n\tclusterLightData.isDynamic = (flags_uint & (1u << 22u)) != 0u;\n\tclusterLightData.isLightmapped = (flags_uint & (1u << 21u)) != 0u;\n}\nfn decodeClusterLightSpot(clusterLightData: ptr<function, ClusterLightData>) {\n\tlet angles: vec2f = unpack2x16float(bitcast<u32>(clusterLightData.anglesData));\n\tclusterLightData.innerConeAngleCos = angles.x;\n\tclusterLightData.outerConeAngleCos = angles.y;\n}\nfn decodeClusterLightOmniAtlasViewport(clusterLightData: ptr<function, ClusterLightData>) {\n\tclusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;\n}\nfn decodeClusterLightAreaData(clusterLightData: ptr<function, ClusterLightData>) {\n\tclusterLightData.halfWidth = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz;\n\tclusterLightData.halfHeight = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz;\n}\nfn decodeClusterLightProjectionMatrixData(clusterLightData: ptr<function, ClusterLightData>) {\n\tlet m0: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0});\n\tlet m1: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_1});\n\tlet m2: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_2});\n\tlet m3: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_3});\n\tlightProjectionMatrix = mat4x4f(m0, m1, m2, m3);\n}\nfn decodeClusterLightShadowData(clusterLightData: ptr<function, ClusterLightData>) {\n\tlet biases: vec2f = unpack2x16float(bitcast<u32>(clusterLightData.biasesData));\n\tclusterLightData.shadowBias = biases.x;\n\tclusterLightData.shadowNormalBias = biases.y;\n}\nfn decodeClusterLightCookieData(clusterLightData: ptr<function, ClusterLightData>) {\n\tlet cookieFlags: u32 = (clusterLightData.flags >> 23u) & 0x0Fu;\n\tlet mask_uvec: vec4<u32> = vec4<u32>(cookieFlags) & vec4<u32>(1u, 2u, 4u, 8u);\n\tclusterLightData.cookieChannelMask = step(vec4f(1.0), vec4f(mask_uvec));\n}\nfn evaluateLight(\n\tlight: ptr<function, ClusterLightData>,\n\tworldNormal: vec3f,\n\tviewDir: vec3f,\n\treflectionDir: vec3f,\n#if defined(LIT_CLEARCOAT)\n\tclearcoatReflectionDir: vec3f,\n#endif\n\tgloss: f32,\n\tspecularity: vec3f,\n\tgeometricNormal: vec3f,\n\ttbn: mat3x3f,\n#if defined(LIT_IRIDESCENCE)\n\tiridescenceFresnel: vec3f,\n#endif\n\tclearcoat_worldNormal: vec3f,\n\tclearcoat_gloss: f32,\n\tsheen_gloss: f32,\n\tiridescence_intensity: f32\n) {\n\tvar cookieAttenuation: vec3f = vec3f(1.0);\n\tvar diffuseAttenuation: f32 = 1.0;\n\tvar falloffAttenuation: f32 = 1.0;\n\tlet lightDirW: vec3f = evalOmniLight(light.position);\n\tlet lightDirNormW: vec3f = normalize(lightDirW);\n\t#ifdef CLUSTER_AREALIGHTS\n\tif (light.shape != {LIGHTSHAPE_PUNCTUAL}) {\n\t\tdecodeClusterLightAreaData(light);\n\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\tcalcRectLightValues(light.position, light.halfWidth, light.halfHeight);\n\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\tcalcDiskLightValues(light.position, light.halfWidth, light.halfHeight);\n\t\t} else {\n\t\t\tcalcSphereLightValues(light.position, light.halfWidth, light.halfHeight);\n\t\t}\n\t\tfalloffAttenuation = getFalloffWindow(light.range, lightDirW);\n\t} else\n\t#endif\n\t{\n\t\tif (light.falloffModeLinear) {\n\t\t\tfalloffAttenuation = getFalloffLinear(light.range, lightDirW);\n\t\t} else {\n\t\t\tfalloffAttenuation = getFalloffInvSquared(light.range, lightDirW);\n\t\t}\n\t}\n\tif (falloffAttenuation > 0.00001) {\n\t\t#ifdef CLUSTER_AREALIGHTS\n\t\tif (light.shape != {LIGHTSHAPE_PUNCTUAL}) {\n\t\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\t\tdiffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n\t\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\t\tdiffuseAttenuation = getDiskLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n\t\t\t} else {\n\t\t\t\tdiffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n\t\t\t}\n\t\t} else\n\t\t#endif\n\t\t{\n\t\t\tfalloffAttenuation = falloffAttenuation * getLightDiffuse(worldNormal, viewDir, lightDirNormW);\n\t\t}\n\t\tif (light.isSpot) {\n\t\t\tdecodeClusterLightSpot(light);\n\t\t\tfalloffAttenuation = falloffAttenuation * getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);\n\t\t}\n\t\t#if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)\n\t\tif (falloffAttenuation > 0.00001) {\n\t\t\tif (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {\n\t\t\t\tif (light.isSpot) {\n\t\t\t\t\tdecodeClusterLightProjectionMatrixData(light);\n\t\t\t\t} else {\n\t\t\t\t\tdecodeClusterLightOmniAtlasViewport(light);\n\t\t\t\t}\n\t\t\t\tlet shadowTextureResolution: f32 = uniform.shadowAtlasParams.x;\n\t\t\t\tlet shadowEdgePixels: f32 = uniform.shadowAtlasParams.y;\n\t\t\t\t#ifdef CLUSTER_COOKIES\n\t\t\t\tif (light.cookieIntensity > 0.0) {\n\t\t\t\t\tdecodeClusterLightCookieData(light);\n\t\t\t\t\tif (light.isSpot) {\n\t\t\t\t\t\tcookieAttenuation = getCookie2DClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightProjectionMatrix, vPositionW, light.cookieIntensity, light.cookieChannelMask);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcookieAttenuation = getCookieCubeClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightDirW, light.cookieIntensity, light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t#endif\n\t\t\t\t#ifdef CLUSTER_SHADOWS\n\t\t\t\tif (light.shadowIntensity > 0.0) {\n\t\t\t\t\tdecodeClusterLightShadowData(light);\n\t\t\t\t\tlet shadowParams: vec4f = vec4f(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);\n\t\t\t\t\tif (light.isSpot) {\n\t\t\t\t\t\tlet shadowCoord: vec3f = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);\n\t\t\t\t\t\t#if defined(CLUSTER_SHADOW_TYPE_PCF1)\n\t\t\t\t\t\t\tlet shadow: f32 = getShadowSpotClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n\t\t\t\t\t\t\tlet shadow: f32 = getShadowSpotClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n\t\t\t\t\t\t\tlet shadow: f32 = getShadowSpotClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCSS)\n\t\t\t\t\t\t\tlet shadow: f32 = getShadowSpotClusteredPCSS(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n\t\t\t\t\t\t#endif\n\t\t\t\t\t\tfalloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet dir: vec3f = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, geometricNormal);\n\t\t\t\t\t\t#if defined(CLUSTER_SHADOW_TYPE_PCF1)\n\t\t\t\t\t\t\tlet shadow: f32 = getShadowOmniClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n\t\t\t\t\t\t\tlet shadow: f32 = getShadowOmniClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);\n\t\t\t\t\t\t#elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n\t\t\t\t\t\t\tlet shadow: f32 = getShadowOmniClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);\n\t\t\t\t\t\t#endif\n\t\t\t\t\t\tfalloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t#endif\n\t\t\t}\n\t\t}\n\t\t#endif\n\t\t#ifdef CLUSTER_AREALIGHTS\n\t\tif (light.shape != {LIGHTSHAPE_PUNCTUAL}) {\n\t\t\t{\n\t\t\t\tvar areaDiffuse: vec3f = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;\n\t\t\t\t#if defined(LIT_SPECULAR)\n\t\t\t\t\tareaDiffuse = mix(areaDiffuse, vec3f(0.0), dLTCSpecFres);\n\t\t\t\t#endif\n\t\t\t\tdDiffuseLight = dDiffuseLight + areaDiffuse;\n\t\t\t}\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tvar areaLightSpecular: f32;\n\t\t\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\t\t\tareaLightSpecular = getRectLightSpecular(worldNormal, viewDir);\n\t\t\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\t\t\tareaLightSpecular = getDiskLightSpecular(worldNormal, viewDir);\n\t\t\t\t} else {\n\t\t\t\t\tareaLightSpecular = getSphereLightSpecular(worldNormal, viewDir);\n\t\t\t\t}\n\t\t\t\tdSpecularLight = dSpecularLight + dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;\n\t\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\t\tvar areaLightSpecularCC: f32;\n\t\t\t\t\tif (light.shape == {LIGHTSHAPE_RECT}) {\n\t\t\t\t\t\tareaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir);\n\t\t\t\t\t} else if (light.shape == {LIGHTSHAPE_DISK}) {\n\t\t\t\t\t\tareaLightSpecularCC = getDiskLightSpecular(clearcoat_worldNormal, viewDir);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tareaLightSpecularCC = getSphereLightSpecular(clearcoat_worldNormal, viewDir);\n\t\t\t\t\t}\n\t\t\t\t\tccSpecularLight = ccSpecularLight + ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color  * cookieAttenuation;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t} else\n\t\t#endif\n\t\t{\n\t\t\t{\n\t\t\t\tvar punctualDiffuse: vec3f = falloffAttenuation * light.color * cookieAttenuation;\n\t\t\t\t#if defined(CLUSTER_AREALIGHTS)\n\t\t\t\t#if defined(LIT_SPECULAR)\n\t\t\t\t\tpunctualDiffuse = mix(punctualDiffuse, vec3f(0.0), specularity);\n\t\t\t\t#endif\n\t\t\t\t#endif\n\t\t\t\tdDiffuseLight = dDiffuseLight + punctualDiffuse;\n\t\t\t}\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tlet halfDir: vec3f = normalize(-lightDirNormW + viewDir);\n\t\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\t\tdSpecularLight = dSpecularLight +\n\t\t\t\t\t\tgetLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation *\n\t\t\t\t\t\tgetFresnel(\n\t\t\t\t\t\t\tdot(viewDir, halfDir),\n\t\t\t\t\t\t\tgloss,\n\t\t\t\t\t\t\tspecularity\n\t\t\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t\t\t, iridescenceFresnel,\n\t\t\t\t\t\t\tiridescence_intensity\n\t\t\t\t\t\t#endif\n\t\t\t\t\t\t\t);\n\t\t\t\t#else\n\t\t\t\t\tdSpecularLight = dSpecularLight + getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity;\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\t\t\tccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));\n\t\t\t\t\t#else\n\t\t\t\t\t\tccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation;\n\t\t\t\t\t#endif\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_SHEEN\n\t\t\t\t\tsSpecularLight = sSpecularLight + getLightSpecularSheen(halfDir, worldNormal, viewDir, lightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t}\n\t}\n\tdAtten = falloffAttenuation;\n\tdLightDirNormW = lightDirNormW;\n}\nfn evaluateClusterLight(\n\tlightIndex: f32,\n\tworldNormal: vec3f,\n\tviewDir: vec3f,\n\treflectionDir: vec3f,\n#if defined(LIT_CLEARCOAT)\n\tclearcoatReflectionDir: vec3f,\n#endif\n\tgloss: f32,\n\tspecularity: vec3f,\n\tgeometricNormal: vec3f,\n\ttbn: mat3x3f,\n#if defined(LIT_IRIDESCENCE)\n\tiridescenceFresnel: vec3f,\n#endif\n\tclearcoat_worldNormal: vec3f,\n\tclearcoat_gloss: f32,\n\tsheen_gloss: f32,\n\tiridescence_intensity: f32\n) {\n\tvar clusterLightData: ClusterLightData;\n\tdecodeClusterLightCore(&clusterLightData, lightIndex);\n\t#ifdef CLUSTER_MESH_DYNAMIC_LIGHTS\n\t\tlet acceptLightMask: bool = clusterLightData.isDynamic;\n\t#else\n\t\tlet acceptLightMask: bool = clusterLightData.isLightmapped;\n\t#endif\n\tif (acceptLightMask) {\n\t\tevaluateLight(\n\t\t\t&clusterLightData,\n\t\t\tworldNormal,\n\t\t\tviewDir,\n\t\t\treflectionDir,\n#if defined(LIT_CLEARCOAT)\n\t\t\tclearcoatReflectionDir,\n#endif\n\t\t\tgloss,\n\t\t\tspecularity,\n\t\t\tgeometricNormal,\n\t\t\ttbn,\n#if defined(LIT_IRIDESCENCE)\n\t\t\tiridescenceFresnel,\n#endif\n\t\t\tclearcoat_worldNormal,\n\t\t\tclearcoat_gloss,\n\t\t\tsheen_gloss,\n\t\t\tiridescence_intensity\n\t\t);\n\t}\n}\nfn addClusteredLights(\n\tworldNormal: vec3f,\n\tviewDir: vec3f,\n\treflectionDir: vec3f,\n#if defined(LIT_CLEARCOAT)\n\tclearcoatReflectionDir: vec3f,\n#endif\n\tgloss: f32,\n\tspecularity: vec3f,\n\tgeometricNormal: vec3f,\n\ttbn: mat3x3f,\n#if defined(LIT_IRIDESCENCE)\n\tiridescenceFresnel: vec3f,\n#endif\n\tclearcoat_worldNormal: vec3f,\n\tclearcoat_gloss: f32,\n\tsheen_gloss: f32,\n\tiridescence_intensity: f32\n) {\n\tif (uniform.clusterSkip > 0.5) {\n\t\treturn;\n\t}\n\tlet cellCoords: vec3f = floor((vPositionW - uniform.clusterBoundsMin) * uniform.clusterCellsCountByBoundsSize);\n\tif (!(any(cellCoords < vec3f(0.0)) || any(cellCoords >= uniform.clusterCellsMax))) {\n\t\tlet cellIndex: f32 = dot(uniform.clusterCellsDot, cellCoords);\n\t\tlet clusterV: f32 = floor(cellIndex * uniform.clusterTextureSize.y);\n\t\tlet clusterU: f32 = cellIndex - (clusterV * uniform.clusterTextureSize.x);\n\t\tfor (var lightCellIndex: i32 = 0; lightCellIndex < uniform.clusterMaxCells; lightCellIndex = lightCellIndex + 1) {\n\t\t\tlet lightIndexPacked: f32 = textureLoad(clusterWorldTexture, vec2<i32>(i32(clusterU) + lightCellIndex, i32(clusterV)), 0).r;\n\t\t\tif (lightIndexPacked <= 0.0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tevaluateClusterLight(\n\t\t\t\tlightIndexPacked * 255.0,\n\t\t\t\tworldNormal,\n\t\t\t\tviewDir,\n\t\t\t\treflectionDir,\n#if defined(LIT_CLEARCOAT)\n\t\t\t\tclearcoatReflectionDir,\n#endif\n\t\t\t\tgloss,\n\t\t\t\tspecularity,\n\t\t\t\tgeometricNormal,\n\t\t\t\ttbn,\n#if defined(LIT_IRIDESCENCE)\n\t\t\t\tiridescenceFresnel,\n#endif\n\t\t\t\tclearcoat_worldNormal,\n\t\t\t\tclearcoat_gloss,\n\t\t\t\tsheen_gloss,\n\t\t\t\tiridescence_intensity\n\t\t\t);\n\t\t}\n\t}\n}`;\n\nvar combinePS = `\nfn combineColor(albedo: vec3f, sheenSpecularity: vec3f, clearcoatSpecularity: f32) -> vec3f {\n\tvar ret: vec3f = vec3f(0.0);\n\t#ifdef LIT_OLD_AMBIENT\n\t\tret = ret + ((dDiffuseLight - uniform.light_globalAmbient) * albedo + uniform.material_ambient * uniform.light_globalAmbient);\n\t#else\n\t\tret = ret + (albedo * dDiffuseLight);\n\t#endif\n\t#ifdef LIT_SPECULAR\n\t\tret = ret + dSpecularLight;\n\t#endif\n\t#ifdef LIT_REFLECTIONS\n\t\tret = ret + (dReflection.rgb * dReflection.a);\n\t#endif\n\t#ifdef LIT_SHEEN\n\t\tlet sheenScaling: f32 = 1.0 - max(max(sheenSpecularity.r, sheenSpecularity.g), sheenSpecularity.b) * 0.157;\n\t\tret = ret * sheenScaling + (sSpecularLight + sReflection.rgb) * sheenSpecularity;\n\t#endif\n\t#ifdef LIT_CLEARCOAT\n\t\tlet clearCoatScaling: f32 = 1.0 - ccFresnel * clearcoatSpecularity;\n\t\tret = ret * clearCoatScaling + (ccSpecularLight + ccReflection) * clearcoatSpecularity;\n\t#endif\n\treturn ret;\n}\n`;\n\nvar cookieBlit2DPS = `\n\tvarying uv0: vec2f;\n\tvar blitTexture: texture_2d<f32>;\n\tvar blitTextureSampler : sampler;\n\t@fragment\n\tfn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\tvar output: FragmentOutput;\n\t\toutput.color = textureSample(blitTexture, blitTextureSampler, input.uv0);\n\t\treturn output;\n\t}\n`;\n\nvar cookieBlitCubePS = `\n\tvarying uv0: vec2f;\n\tuniform invViewProj: mat4x4<f32>;\n\tvar blitTexture: texture_cube<f32>;\n\tvar blitTextureSampler : sampler;\n\t@fragment\n\tfn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\tvar output: FragmentOutput;\n\t\tvar projPos = vec4f(input.uv0 * 2.0 - 1.0, 0.5, 1.0);\n\t\tvar worldPos = uniform.invViewProj * projPos;\n\t\toutput.color = textureSample(blitTexture, blitTextureSampler, worldPos.xyz);\n\t\treturn output;\n\t}\n`;\n\nvar cookieBlitVS = `\n\tattribute vertex_position: vec2f;\n\tvarying uv0: vec2f;\n\t@vertex\n\tfn vertexMain(input: VertexInput) -> VertexOutput {\n\t\tvar output: VertexOutput;\n\t\toutput.position = vec4f(input.vertex_position, 0.5, 1.0);\n\t\toutput.uv0 = input.vertex_position * 0.5 + vec2f(0.5, 0.5);\n\t\toutput.uv0.y = 1.0 - output.uv0.y;\n\t\treturn output;\n\t}\n`;\n\nvar cubeMapProjectPS = `\n#if LIT_CUBEMAP_PROJECTION == BOX\n\tuniform envBoxMin: vec3f;\n\tuniform envBoxMax: vec3f;\n#endif\nfn cubeMapProject(nrdir: vec3f) -> vec3f {\n\t#if LIT_CUBEMAP_PROJECTION == NONE\n\t\treturn cubeMapRotate(nrdir);\n\t#endif\n\t#if LIT_CUBEMAP_PROJECTION == BOX\n\t\tlet nrdir_rotated: vec3f = cubeMapRotate(nrdir);\n\t\tlet rbmax: vec3f = (uniform.envBoxMax - vPositionW) / nrdir_rotated;\n\t\tlet rbmin: vec3f = (uniform.envBoxMin - vPositionW) / nrdir_rotated;\n\t\tlet rbminmax: vec3f = select(rbmin, rbmax, nrdir_rotated > vec3f(0.0));\n\t\tlet fa: f32 = min(min(rbminmax.x, rbminmax.y), rbminmax.z);\n\t\tlet posonbox: vec3f = vPositionW + nrdir_rotated * fa;\n\t\tlet envBoxPos: vec3f = (uniform.envBoxMin + uniform.envBoxMax) * 0.5;\n\t\treturn normalize(posonbox - envBoxPos);\n\t#endif\n}\n`;\n\nvar cubeMapRotatePS = `\n#ifdef CUBEMAP_ROTATION\nuniform cubeMapRotationMatrix: mat3x3f;\n#endif\nfn cubeMapRotate(refDir: vec3f) -> vec3f {\n#ifdef CUBEMAP_ROTATION\n\treturn refDir * uniform.cubeMapRotationMatrix;\n#else\n\treturn refDir;\n#endif\n}\n`;\n\nvar debugOutputPS = `\n#ifdef DEBUG_ALBEDO_PASS\noutput.color = vec4(gammaCorrectOutput(dAlbedo), 1.0);\n#endif\n#ifdef DEBUG_UV0_PASS\noutput.color = vec4f(litArgs_albedo , 1.0);\n#endif\n#ifdef DEBUG_WORLD_NORMAL_PASS\noutput.color = vec4f(litArgs_worldNormal * 0.5 + 0.5, 1.0);\n#endif\n#ifdef DEBUG_OPACITY_PASS\noutput.color = vec4f(vec3f(litArgs_opacity) , 1.0);\n#endif\n#ifdef DEBUG_SPECULARITY_PASS\noutput.color = vec4f(litArgs_specularity, 1.0);\n#endif\n#ifdef DEBUG_GLOSS_PASS\noutput.color = vec4f(vec3f(litArgs_gloss) , 1.0);\n#endif\n#ifdef DEBUG_METALNESS_PASS\noutput.color = vec4f(vec3f(litArgs_metalness) , 1.0);\n#endif\n#ifdef DEBUG_AO_PASS\noutput.color = vec4f(vec3f(litArgs_ao) , 1.0);\n#endif\n#ifdef DEBUG_EMISSION_PASS\noutput.color = vec4f(gammaCorrectOutput(litArgs_emission), 1.0);\n#endif\n`;\n\nvar debugProcessFrontendPS = `\n#ifdef DEBUG_LIGHTING_PASS\n\tlitArgs_albedo = vec3f(0.5);\n#endif\n#ifdef DEBUG_UV0_PASS\n#ifdef VARYING_VUV0\n\tlitArgs_albedo = vec3f(vUv0, 0.0);\n#else\n\tlitArgs_albedo = vec3f(0.0);\n#endif\n#endif\n`;\n\nvar decodePS = `\n#ifndef _DECODE_INCLUDED_\n#define _DECODE_INCLUDED_\nfn decodeLinear(raw: vec4f) -> vec3f {\n\treturn raw.rgb;\n}\nfn decodeGammaFloat(raw: f32) -> f32 {\n\treturn pow(raw, 2.2);\n}\nfn decodeGamma3(raw: vec3f) -> vec3f {\n\treturn pow(raw, vec3f(2.2));\n}\nfn decodeGamma(raw: vec4f) -> vec3f {\n\treturn pow(raw.xyz, vec3f(2.2));\n}\nfn decodeRGBM(raw: vec4f) -> vec3f {\n\tlet color = (8.0 * raw.a) * raw.rgb;\n\treturn color * color;\n}\nfn decodeRGBP(raw: vec4f) -> vec3f {\n\tlet color = raw.rgb * (-raw.a * 7.0 + 8.0);\n\treturn color * color;\n}\nfn decodeRGBE(raw: vec4f) -> vec3f {\n\treturn select(vec3f(0.0), raw.xyz * pow(2.0, raw.w * 255.0 - 128.0), raw.a != 0.0);\n}\nfn passThrough(raw: vec4f) -> vec4f {\n\treturn raw;\n}\nfn unpackNormalXYZ(nmap: vec4f) -> vec3f {\n\treturn nmap.xyz * 2.0 - 1.0;\n}\nfn unpackNormalXY(nmap: vec4f) -> vec3f {\n\tvar xy = nmap.wy * 2.0 - 1.0;\n\treturn vec3f(xy, sqrt(1.0 - clamp(dot(xy, xy), 0.0, 1.0)));\n}\n#endif\n`;\n\nvar detailModesPS = `\n#ifndef _DETAILMODES_INCLUDED_\n#define _DETAILMODES_INCLUDED_\nfn detailMode_mul(c1: vec3f, c2: vec3f) -> vec3f {\n\treturn c1 * c2;\n}\nfn detailMode_add(c1: vec3f, c2: vec3f) -> vec3f {\n\treturn c1 + c2;\n}\nfn detailMode_screen(c1: vec3f, c2: vec3f) -> vec3f {\n\treturn 1.0 - (1.0 - c1)*(1.0 - c2);\n}\nfn detailMode_overlay(c1: vec3f, c2: vec3f) -> vec3f {\n\treturn mix(1.0 - 2.0 * (1.0 - c1)*(1.0 - c2), 2.0 * c1 * c2, step(c1, vec3f(0.5)));\n}\nfn detailMode_min(c1: vec3f, c2: vec3f) -> vec3f {\n\treturn min(c1, c2);\n}\nfn detailMode_max(c1: vec3f, c2: vec3f) -> vec3f {\n\treturn max(c1, c2);\n}\n#endif\n`;\n\nvar diffusePS = `\nuniform material_diffuse: vec3f;\n#ifdef STD_DIFFUSEDETAIL_TEXTURE\n\t#include \"detailModesPS\"\n#endif\nfn getAlbedo() {\n\tdAlbedo = uniform.material_diffuse.rgb;\n\t#ifdef STD_DIFFUSE_TEXTURE\n\t\tvar albedoTexture: vec3f = {STD_DIFFUSE_TEXTURE_DECODE}(textureSampleBias({STD_DIFFUSE_TEXTURE_NAME}, {STD_DIFFUSE_TEXTURE_NAME}Sampler, {STD_DIFFUSE_TEXTURE_UV}, uniform.textureBias)).{STD_DIFFUSE_TEXTURE_CHANNEL};\n\t\t#ifdef STD_DIFFUSEDETAIL_TEXTURE\n\t\t\tvar albedoDetail: vec3f = {STD_DIFFUSEDETAIL_TEXTURE_DECODE}(textureSampleBias({STD_DIFFUSEDETAIL_TEXTURE_NAME}, {STD_DIFFUSEDETAIL_TEXTURE_NAME}Sampler, {STD_DIFFUSEDETAIL_TEXTURE_UV}, uniform.textureBias)).{STD_DIFFUSEDETAIL_TEXTURE_CHANNEL};\n\t\t\talbedoTexture = detailMode_{STD_DIFFUSEDETAIL_DETAILMODE}(albedoTexture, albedoDetail);\n\t\t#endif\n\t\tdAlbedo = dAlbedo * albedoTexture;\n\t#endif\n\t#ifdef STD_DIFFUSE_VERTEX\n\t\tdAlbedo = dAlbedo * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}));\n\t#endif\n}\n`;\n\nvar emissivePS = `\nuniform material_emissive: vec3f;\nuniform material_emissiveIntensity: f32;\nfn getEmission() {\n\tdEmission = uniform.material_emissive * uniform.material_emissiveIntensity;\n\t#ifdef STD_EMISSIVE_TEXTURE\n\tdEmission *= {STD_EMISSIVE_TEXTURE_DECODE}(textureSampleBias({STD_EMISSIVE_TEXTURE_NAME}, {STD_EMISSIVE_TEXTURE_NAME}Sampler, {STD_EMISSIVE_TEXTURE_UV}, uniform.textureBias)).{STD_EMISSIVE_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_EMISSIVE_VERTEX\n\tdEmission = dEmission * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}));\n\t#endif\n}\n`;\n\nvar encodePS = `\nfn encodeLinear(source: vec3f) -> vec4f {\n\treturn vec4f(source, 1.0);\n}\nfn encodeGamma(source: vec3f) -> vec4f {\n\treturn vec4f(pow(source + vec3f(0.0000001), vec3f(1.0 / 2.2)), 1.0);\n}\nfn encodeRGBM(source: vec3f) -> vec4f {\n\tvar color: vec3f = pow(source, vec3f(0.5));\n\tcolor *= 1.0 / 8.0;\n\tvar a: f32 = saturate(max(max(color.r, color.g), max(color.b, 1.0 / 255.0)));\n\ta = ceil(a * 255.0) / 255.0;\n\tcolor /= a;\n\treturn vec4f(color, a);\n}\nfn encodeRGBP(source: vec3f) -> vec4f {\n\tvar gamma: vec3f = pow(source, vec3f(0.5));\n\tvar maxVal: f32 = min(8.0, max(1.0, max(gamma.x, max(gamma.y, gamma.z))));\n\tvar v: f32 = 1.0 - ((maxVal - 1.0) / 7.0);\n\tv = ceil(v * 255.0) / 255.0;\n\treturn vec4f(gamma / (-v * 7.0 + 8.0), v);\n}\nfn encodeRGBE(source: vec3f) -> vec4f {\n\tvar maxVal: f32 = max(source.x, max(source.y, source.z));\n\tif (maxVal < 1e-32) {\n\t\treturn vec4f(0.0, 0.0, 0.0, 0.0);\n\t} else {\n\t\tvar e: f32 = ceil(log2(maxVal));\n\t\treturn vec4f(source / pow(2.0, e), (e + 128.0) / 255.0);\n\t}\n}\n`;\n\nvar endPS = `\n\tvar finalRgb: vec3f = combineColor(litArgs_albedo, litArgs_sheen_specularity, litArgs_clearcoat_specularity);\n\tfinalRgb = finalRgb + litArgs_emission;\n\tfinalRgb = addFog(finalRgb);\n\tfinalRgb = toneMap(finalRgb);\n\tfinalRgb = gammaCorrectOutput(finalRgb);\n\toutput.color = vec4f(finalRgb, output.color.a);\n`;\n\nvar envAtlasPS = `\n#ifndef _ENVATLAS_INCLUDED_\n#define _ENVATLAS_INCLUDED_\nconst atlasSize : f32 = 512.0;\nconst seamSize : f32 = 1.0 / atlasSize;\nfn mapUv(uv : vec2f, rect : vec4f) -> vec2f {\n\treturn vec2f(mix(rect.x + seamSize, rect.x + rect.z - seamSize, uv.x),\n\t\t\t\t mix(rect.y + seamSize, rect.y + rect.w - seamSize, uv.y));\n}\nfn mapRoughnessUv(uv : vec2f, level : f32) -> vec2f {\n\tlet t : f32 = 1.0 / exp2(level);\n\treturn mapUv(uv, vec4f(0.0, 1.0 - t, t, t * 0.5));\n}\nfn mapShinyUv(uv : vec2f, level : f32) -> vec2f {\n\tlet t : f32 = 1.0 / exp2(level);\n\treturn mapUv(uv, vec4f(1.0 - t, 1.0 - t, t, t * 0.5));\n}\n#endif\n`;\n\nvar envProcPS = `\n#ifdef LIT_SKYBOX_INTENSITY\n\tuniform skyboxIntensity : f32;\n#endif\nfn processEnvironment(color : vec3f) -> vec3f {\n\t#ifdef LIT_SKYBOX_INTENSITY\n\t\treturn color * uniform.skyboxIntensity;\n\t#else\n\t\treturn color;\n\t#endif\n}\n`;\n\nvar falloffInvSquaredPS = `\nfn getFalloffWindow(lightRadius: f32, lightDir: vec3f) -> f32 {\n\tlet sqrDist: f32 = dot(lightDir, lightDir);\n\tlet invRadius: f32 = 1.0 / lightRadius;\n\treturn square(saturate(1.0 - square(sqrDist * square(invRadius))));\n}\nfn getFalloffInvSquared(lightRadius: f32, lightDir: vec3f) -> f32 {\n\tlet sqrDist: f32 = dot(lightDir, lightDir);\n\tvar falloff: f32 = 1.0 / (sqrDist + 1.0);\n\tlet invRadius: f32 = 1.0 / lightRadius;\n\tfalloff = falloff * 16.0;\n\tfalloff = falloff * square(saturate(1.0 - square(sqrDist * square(invRadius))));\n\treturn falloff;\n}\n`;\n\nvar falloffLinearPS = `\nfn getFalloffLinear(lightRadius: f32, lightDir: vec3f) -> f32 {\n\tlet d: f32 = length(lightDir);\n\treturn max(((lightRadius - d) / lightRadius), 0.0);\n}\n`;\n\nvar floatAsUintPS = `\n#ifndef FLOAT_AS_UINT\n#define FLOAT_AS_UINT\nfn float2uint(value: f32) -> vec4f {\n\tlet intBits = bitcast<u32>(value);\n\treturn vec4f(\n\t\tf32((intBits >> 24u) & 0xffu),\n\t\tf32((intBits >> 16u) & 0xffu),\n\t\tf32((intBits >> 8u) & 0xffu),\n\t\tf32(intBits & 0xffu)\n\t) / 255.0;\n}\nfn uint2float(value: vec4f) -> f32 {\n\tlet rgba_u32 = vec4<u32>(value * 255.0);\n\tlet intBits: u32 =\n\t\t(rgba_u32.r << 24u) |\n\t\t(rgba_u32.g << 16u) |\n\t\t(rgba_u32.b << 8u)  |\n\t\t rgba_u32.a;\n\treturn bitcast<f32>(intBits);\n}\nfn float2vec4(value: f32) -> vec4f {\n\t#if defined(CAPS_TEXTURE_FLOAT_RENDERABLE)\n\t\treturn vec4f(value, 1.0, 1.0, 1.0);\n\t#else\n\t\treturn float2uint(value);\n\t#endif\n}\n#endif\n`;\n\nvar fogPS = `\nvar<private> dBlendModeFogFactor : f32 = 1.0;\n#if (FOG != NONE)\n\tuniform fog_color : vec3f;\n\t\n\t#if (FOG == LINEAR)\n\t\tuniform fog_start : f32;\n\t\tuniform fog_end : f32;\n\t#else\n\t\tuniform fog_density : f32;\n\t#endif\n#endif\nfn getFogFactor() -> f32 {\n\tlet depth = pcPosition.z / pcPosition.w;\n\tvar fogFactor : f32 = 0.0;\n\t#if (FOG == LINEAR)\n\t\tfogFactor = (uniform.fog_end - depth) / (uniform.fog_end - uniform.fog_start);\n\t#elif (FOG == EXP)\n\t\tfogFactor = exp(-depth * uniform.fog_density);\n\t#elif (FOG == EXP2)\n\t\tfogFactor = exp(-depth * depth * uniform.fog_density * uniform.fog_density);\n\t#endif\n\treturn clamp(fogFactor, 0.0, 1.0);\n}\nfn addFog(color : vec3f) -> vec3f {\n\t#if (FOG != NONE)\n\t\treturn mix(uniform.fog_color * dBlendModeFogFactor, color, getFogFactor());\n\t#else\n\t\treturn color;\n\t#endif\n}\n`;\n\nvar fresnelSchlickPS = `\nfn getFresnel(\n\t\tcosTheta: f32,\n\t\tgloss: f32,\n\t\tspecularity: vec3f\n\t#if defined(LIT_IRIDESCENCE)\n\t\t, iridescenceFresnel: vec3f,\n\t\tiridescenceIntensity: f32\n\t#endif\n) -> vec3f {\n\tlet fresnel: f32 = pow(1.0 - saturate(cosTheta), 5.0);\n\tlet glossSq: f32 = gloss * gloss;\n\tlet ret: vec3f = specularity + (max(vec3f(glossSq), specularity) - specularity) * fresnel;\n\t#if defined(LIT_IRIDESCENCE)\n\t\treturn mix(ret, iridescenceFresnel, iridescenceIntensity);\n\t#else\n\t\treturn ret;\n\t#endif\n}\nfn getFresnelCC(cosTheta: f32) -> f32 {\n\tlet fresnel: f32 = pow(1.0 - saturate(cosTheta), 5.0);\n\treturn 0.04 + (1.0 - 0.04) * fresnel;\n}`;\n\nvar fullscreenQuadVS = `\nattribute vertex_position: vec2f;\nvarying vUv0: vec2f;\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n\tvar output: VertexOutput;\n\toutput.position = vec4f(input.vertex_position, 0.5, 1.0);\n\toutput.vUv0 = input.vertex_position.xy * 0.5 + vec2f(0.5);\n\treturn output;\n}\n`;\n\nvar gammaPS = `\n#include \"decodePS\"\n#if (GAMMA == SRGB)\n\tfn gammaCorrectInput(color: f32) -> f32 {\n\t\treturn decodeGammaFloat(color);\n\t}\n\tfn gammaCorrectInputVec3(color: vec3f) -> vec3f {\n\t\treturn decodeGamma3(color);\n\t}\n\tfn gammaCorrectInputVec4(color: vec4f) -> vec4f {\n\t\treturn vec4f(decodeGamma3(color.xyz), color.w);\n\t}\n\tfn gammaCorrectOutput(color: vec3f) -> vec3f {\n\t\treturn pow(color + 0.0000001, vec3f(1.0 / 2.2));\n\t}\n#else\n\tfn gammaCorrectInput(color: f32) -> f32 {\n\t\treturn color;\n\t}\n\tfn gammaCorrectInputVec3(color: vec3f) -> vec3f {\n\t\treturn color;\n\t}\n\tfn gammaCorrectInputVec4(color: vec4f) -> vec4f {\n\t\treturn color;\n\t}\n\tfn gammaCorrectOutput(color: vec3f) -> vec3f {\n\t\treturn color;\n\t}\n#endif\n`;\n\nvar glossPS = `\n#ifdef STD_GLOSS_CONSTANT\n\tuniform material_gloss: f32;\n#endif\nfn getGlossiness() {\n\tdGlossiness = 1.0;\n\t#ifdef STD_GLOSS_CONSTANT\n\tdGlossiness = dGlossiness * uniform.material_gloss;\n\t#endif\n\t#ifdef STD_GLOSS_TEXTURE\n\tdGlossiness = dGlossiness * textureSampleBias({STD_GLOSS_TEXTURE_NAME}, {STD_GLOSS_TEXTURE_NAME}Sampler, {STD_GLOSS_TEXTURE_UV}, uniform.textureBias).{STD_GLOSS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_GLOSS_VERTEX\n\tdGlossiness = dGlossiness * saturate(vVertexColor.{STD_GLOSS_VERTEX_CHANNEL});\n\t#endif\n\t#ifdef STD_GLOSS_INVERT\n\tdGlossiness = 1.0 - dGlossiness;\n\t#endif\n\tdGlossiness = dGlossiness + 0.0000001;\n}\n`;\n\nvar gsplatCenterVS = `\nuniform matrix_model: mat4x4f;\nuniform matrix_view: mat4x4f;\n#ifndef GSPLAT_CENTER_NOPROJ\n\tuniform matrix_projection: mat4x4f;\n#endif\nfn initCenter(modelCenter: vec3f, center: ptr<function, SplatCenter>) -> bool {\n\tlet modelView: mat4x4f = uniform.matrix_view * uniform.matrix_model;\n\tlet centerView: vec4f = modelView * vec4f(modelCenter, 1.0);\n\t#ifndef GSPLAT_CENTER_NOPROJ\n\t\tif (centerView.z > 0.0) {\n\t\t\treturn false;\n\t\t}\n\t\tvar centerProj: vec4f = uniform.matrix_projection * centerView;\n\t\tcenterProj.z = clamp(centerProj.z, 0.0, abs(centerProj.w));\n\t\tcenter.proj = centerProj;\n\t\tcenter.projMat00 = uniform.matrix_projection[0][0];\n\t#endif\n\tcenter.view = centerView.xyz / centerView.w;\n\tcenter.modelView = modelView;\n\treturn true;\n}\n`;\n\nvar gsplatColorVS = `\nvar splatColor: texture_2d<f32>;\nfn readColor(source: ptr<function, SplatSource>) -> vec4f {\n\treturn textureLoad(splatColor, source.uv, 0);\n}\n`;\n\nvar gsplatCommonVS = `\n#include \"gsplatStructsVS\"\n#include \"gsplatEvalSHVS\"\n#include \"gsplatQuatToMat3VS\"\n#include \"gsplatSourceFormatVS\"\n#include \"gsplatSourceVS\"\n#include \"gsplatCenterVS\"\n#include \"gsplatCornerVS\"\n#include \"gsplatOutputVS\"\nfn clipCorner(corner: ptr<function, SplatCorner>, alpha: f32) {\n\tlet clip: f32 = min(1.0, sqrt(-log(1.0 / (255.0 * alpha))) / 2.0);\n\tcorner.offset = corner.offset * clip;\n\tcorner.uv = corner.uv * clip;\n}\n`;\n\nvar gsplatCompressedDataVS = `\nvar packedTexture: texture_2d<u32>;\nvar chunkTexture: texture_2d<f32>;\nvar<private> chunkDataA: vec4f;\nvar<private> chunkDataB: vec4f;\nvar<private> chunkDataC: vec4f;\nvar<private> chunkDataD: vec4f;\nvar<private> chunkDataE: vec4f;\nvar<private> packedData: vec4u;\nfn unpack111011(bits: u32) -> vec3f {\n\treturn (vec3f((vec3<u32>(bits) >> vec3<u32>(21u, 11u, 0u)) & vec3<u32>(0x7ffu, 0x3ffu, 0x7ffu))) / vec3f(2047.0, 1023.0, 2047.0);\n}\nfn unpack8888(bits: u32) -> vec4f {\n\treturn vec4f(\n\t\tf32((bits >> 24u) & 0xffu),\n\t\tf32((bits >> 16u) & 0xffu),\n\t\tf32((bits >> 8u)  & 0xffu),\n\t\tf32(bits\t\t & 0xffu)\n\t) / 255.0;\n}\nconst norm_const: f32 = 1.0 / (sqrt(2.0) * 0.5);\nfn unpackRotation(bits: u32) -> vec4f {\n\tlet a = (f32((bits >> 20u) & 0x3ffu) / 1023.0 - 0.5) * norm_const;\n\tlet b = (f32((bits >> 10u) & 0x3ffu) / 1023.0 - 0.5) * norm_const;\n\tlet c = (f32(bits & 0x3ffu) / 1023.0 - 0.5) * norm_const;\n\tlet m = sqrt(1.0 - (a * a + b * b + c * c));\n\tlet mode = bits >> 30u;\n\tif (mode == 0u) { return vec4f(m, a, b, c); }\n\tif (mode == 1u) { return vec4f(a, m, b, c); }\n\tif (mode == 2u) { return vec4f(a, b, m, c); }\n\treturn vec4f(a, b, c, m);\n}\nfn readCenter(source: ptr<function, SplatSource>) -> vec3f {\n\tlet tex_size_u = textureDimensions(chunkTexture, 0);\n\tlet w: u32 = tex_size_u.x / 5u;\n\tlet chunkId: u32 = source.id / 256u;\n\tlet chunkUV: vec2<i32> = vec2<i32>(i32((chunkId % w) * 5u), i32(chunkId / w));\n\tchunkDataA = textureLoad(chunkTexture, chunkUV + vec2<i32>(0, 0), 0);\n\tchunkDataB = textureLoad(chunkTexture, chunkUV + vec2<i32>(1, 0), 0);\n\tchunkDataC = textureLoad(chunkTexture, chunkUV + vec2<i32>(2, 0), 0);\n\tchunkDataD = textureLoad(chunkTexture, chunkUV + vec2<i32>(3, 0), 0);\n\tchunkDataE = textureLoad(chunkTexture, chunkUV + vec2<i32>(4, 0), 0);\n\tpackedData = textureLoad(packedTexture, source.uv, 0);\n\treturn mix(chunkDataA.xyz, vec3f(chunkDataA.w, chunkDataB.xy), unpack111011(packedData.x));\n}\nfn readColor(source: ptr<function, SplatSource>) -> vec4f {\n\tlet r = unpack8888(packedData.w);\n\treturn vec4f(mix(chunkDataD.xyz, vec3f(chunkDataD.w, chunkDataE.xy), r.rgb), r.w);\n}\nfn getRotation() -> vec4f {\n\treturn unpackRotation(packedData.y);\n}\nfn getScale() -> vec3f {\n\treturn exp(mix(vec3f(chunkDataB.zw, chunkDataC.x), chunkDataC.yzw, unpack111011(packedData.z)));\n}\nfn readCovariance(source: ptr<function, SplatSource>, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {\n\tlet rot = quatToMat3(getRotation());\n\tlet scale = getScale();\n\tlet M = transpose(mat3x3f(\n\t\tscale.x * rot[0],\n\t\tscale.y * rot[1],\n\t\tscale.z * rot[2]\n\t));\n\t*covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));\n\t*covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));\n}\n`;\n\nvar gsplatCompressedSHVS = `\n#if SH_BANDS > 0\nvar shTexture0: texture_2d<u32>;\nvar shTexture1: texture_2d<u32>;\nvar shTexture2: texture_2d<u32>;\nfn unpack8888s(bits: u32) -> vec4f {\n\tlet unpacked_u = (vec4<u32>(bits) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(0xffu);\n\treturn vec4f(unpacked_u) * (8.0 / 255.0) - 4.0;\n}\nfn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 15>>, scale: ptr<function, f32>) {\n\tlet shData0: vec4<u32> = textureLoad(shTexture0, source.uv, 0);\n\tlet shData1: vec4<u32> = textureLoad(shTexture1, source.uv, 0);\n\tlet shData2: vec4<u32> = textureLoad(shTexture2, source.uv, 0);\n\tlet r0 = unpack8888s(shData0.x);\n\tlet r1 = unpack8888s(shData0.y);\n\tlet r2 = unpack8888s(shData0.z);\n\tlet r3 = unpack8888s(shData0.w);\n\tlet g0 = unpack8888s(shData1.x);\n\tlet g1 = unpack8888s(shData1.y);\n\tlet g2 = unpack8888s(shData1.z);\n\tlet g3 = unpack8888s(shData1.w);\n\tlet b0 = unpack8888s(shData2.x);\n\tlet b1 = unpack8888s(shData2.y);\n\tlet b2 = unpack8888s(shData2.z);\n\tlet b3 = unpack8888s(shData2.w);\n\tsh[0] =  vec3f(r0.x, g0.x, b0.x);\n\tsh[1] =  vec3f(r0.y, g0.y, b0.y);\n\tsh[2] =  vec3f(r0.z, g0.z, b0.z);\n\tsh[3] =  vec3f(r0.w, g0.w, b0.w);\n\tsh[4] =  vec3f(r1.x, g1.x, b1.x);\n\tsh[5] =  vec3f(r1.y, g1.y, b1.y);\n\tsh[6] =  vec3f(r1.z, g1.z, b1.z);\n\tsh[7] =  vec3f(r1.w, g1.w, b1.w);\n\tsh[8] =  vec3f(r2.x, g2.x, b2.x);\n\tsh[9] =  vec3f(r2.y, g2.y, b2.y);\n\tsh[10] = vec3f(r2.z, g2.z, b2.z);\n\tsh[11] = vec3f(r2.w, g2.w, b2.w);\n\tsh[12] = vec3f(r3.x, g3.x, b3.x);\n\tsh[13] = vec3f(r3.y, g3.y, b3.y);\n\tsh[14] = vec3f(r3.z, g3.z, b3.z);\n\t*scale = 1.0;\n}\n#endif\n`;\n\nvar gsplatEvalSHVS = `\n\t#if SH_BANDS == 1\n\t\tconst SH_COEFFS: i32 = 3;\n\t#elif SH_BANDS == 2\n\t\tconst SH_COEFFS: i32 = 8;\n\t#elif SH_BANDS == 3\n\t\tconst SH_COEFFS: i32 = 15;\n\t#else\n\t\tconst SH_COEFFS: i32 = 0;\n\t#endif\n\t#if SH_BANDS > 0\n\tconst SH_C1: f32 = 0.4886025119029199;\n\t#if SH_BANDS > 1\n\t\tconst SH_C2_0: f32 = 1.0925484305920792;\n\t\tconst SH_C2_1: f32 = -1.0925484305920792;\n\t\tconst SH_C2_2: f32 = 0.31539156525252005;\n\t\tconst SH_C2_3: f32 = -1.0925484305920792;\n\t\tconst SH_C2_4: f32 = 0.5462742152960396;\n\t#endif\n\t#if SH_BANDS > 2\n\t\tconst SH_C3_0: f32 = -0.5900435899266435;\n\t\tconst SH_C3_1: f32 = 2.890611442640554;\n\t\tconst SH_C3_2: f32 = -0.4570457994644658;\n\t\tconst SH_C3_3: f32 = 0.3731763325901154;\n\t\tconst SH_C3_4: f32 = -0.4570457994644658;\n\t\tconst SH_C3_5: f32 = 1.445305721320277;\n\t\tconst SH_C3_6: f32 = -0.5900435899266435;\n\t#endif\n\tfn evalSH(sh: ptr<function, array<vec3f, SH_COEFFS>>, dir: vec3f) -> vec3f {\n\t\tlet x = dir.x;\n\t\tlet y = dir.y;\n\t\tlet z = dir.z;\n\t\tvar result = SH_C1 * (-sh[0] * y + sh[1] * z - sh[2] * x);\n\t\t#if SH_BANDS > 1\n\t\t\tlet xx = x * x;\n\t\t\tlet yy = y * y;\n\t\t\tlet zz = z * z;\n\t\t\tlet xy = x * y;\n\t\t\tlet yz = y * z;\n\t\t\tlet xz = x * z;\n\t\t\tresult = result + (\n\t\t\t\tsh[3] * (SH_C2_0 * xy) +\n\t\t\t\tsh[4] * (SH_C2_1 * yz) +\n\t\t\t\tsh[5] * (SH_C2_2 * (2.0 * zz - xx - yy)) +\n\t\t\t\tsh[6] * (SH_C2_3 * xz) +\n\t\t\t\tsh[7] * (SH_C2_4 * (xx - yy))\n\t\t\t);\n\t\t#endif\n\t\t#if SH_BANDS > 2\n\t\t\tresult = result + (\n\t\t\t\tsh[8]  * (SH_C3_0 * y * (3.0 * xx - yy)) +\n\t\t\t\tsh[9]  * (SH_C3_1 * xy * z) +\n\t\t\t\tsh[10] * (SH_C3_2 * y * (4.0 * zz - xx - yy)) +\n\t\t\t\tsh[11] * (SH_C3_3 * z * (2.0 * zz - 3.0 * xx - 3.0 * yy)) +\n\t\t\t\tsh[12] * (SH_C3_4 * x * (4.0 * zz - xx - yy)) +\n\t\t\t\tsh[13] * (SH_C3_5 * z * (xx - yy)) +\n\t\t\t\tsh[14] * (SH_C3_6 * x * (xx - 3.0 * yy))\n\t\t\t);\n\t\t#endif\n\t\treturn result;\n\t}\n\t#endif\n`;\n\nvar gsplatQuatToMat3VS = `\nfn quatToMat3(R: vec4<f32>) -> mat3x3<f32> {\n\tlet R2: vec4<f32> = R + R;\n\tlet X: f32\t   = R2.x * R.w;\n\tlet Y: vec4<f32> = R2.y * R;\n\tlet Z: vec4<f32> = R2.z * R;\n\tlet W: f32\t   = R2.w * R.w;\n\treturn mat3x3<f32>(\n\t\t1.0 - Z.z - W,  Y.z + X,\t  Y.w - Z.x,\n\t\tY.z - X,\t\t1.0 - Y.y - W, Z.w + Y.x,\n\t\tY.w + Z.x,\t  Z.w - Y.x,\t 1.0 - Y.y - Z.z\n\t);\n}\n`;\n\nvar gsplatSogsColorVS = `\nvar packedSh0: texture_2d<f32>;\nuniform sh0_mins: f32;\nuniform sh0_maxs: f32;\nconst SH_C0: f32 = 0.28209479177387814;\nfn readColor(source: ptr<function, SplatSource>) -> vec4f {\n\tlet clr = mix(vec3f(uniform.sh0_mins), vec3f(uniform.sh0_maxs), unpack111110(pack8888(textureLoad(packedSh0, source.uv, 0))));\n\tlet alpha = f32(packedSample.z & 0xffu) / 255.0;\n\treturn vec4f(vec3f(0.5) + clr.xyz * SH_C0, alpha);\n}\n`;\n\nvar gsplatSogsDataVS = `\n#include \"gsplatPackingPS\"\nvar packedTexture: texture_2d<u32>;\nuniform means_mins: vec3f;\nuniform means_maxs: vec3f;\nuniform scales_mins: f32;\nuniform scales_maxs: f32;\nvar<private> packedSample: vec4<u32>;\nfn readCenter(source: ptr<function, SplatSource>) -> vec3f {\n\tpackedSample = textureLoad(packedTexture, source.uv, 0);\n\tlet l = unpack8888(packedSample.x).xyz;\n\tlet u = unpack8888(packedSample.y).xyz;\n\tlet n = (l * 255.0 + u * 255.0 * 256.0) / 65535.0;\n\tlet v = mix(uniform.means_mins, uniform.means_maxs, n);\n\treturn sign(v) * (exp(abs(v)) - 1.0);\n}\nconst norm: f32 = 2.0 / sqrt(2.0);\nfn readCovariance(source: ptr<function, SplatSource>, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {\n\tlet qdata = unpack8888(packedSample.z).xyz;\n\tlet sdata = unpack101010(packedSample.w >> 2u);\n\tlet qmode = packedSample.w & 0x3u;\n\tlet abc = (qdata - 0.5) * norm;\n\tlet d = sqrt(max(0.0, 1.0 - dot(abc, abc)));\n\tvar quat: vec4f;\n\tif (qmode == 0u) {\n\t\tquat = vec4f(d, abc);\n\t} else if (qmode == 1u) {\n\t\tquat = vec4f(abc.x, d, abc.y, abc.z);\n\t} else if (qmode == 2u) {\n\t\tquat = vec4f(abc.x, abc.y, d, abc.z);\n\t} else {\n\t\tquat = vec4f(abc.x, abc.y, abc.z, d);\n\t}\n\tlet rot = quatToMat3(quat);\n\tlet scale = exp(mix(vec3f(uniform.scales_mins), vec3f(uniform.scales_maxs), sdata));\n\tlet M = transpose(mat3x3f(\n\t\tscale.x * rot[0],\n\t\tscale.y * rot[1],\n\t\tscale.z * rot[2]\n\t));\n\t*covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));\n\t*covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));\n}\n`;\n\nvar gsplatSogsSHVS = `\nvar packedShN: texture_2d<f32>;\nuniform shN_mins: f32;\nuniform shN_maxs: f32;\nfn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, SH_COEFFS>>, scale: ptr<function, f32>) {\n\tlet t = vec2i(packedSample.xy & vec2u(255u));\n\tlet n = t.x + t.y * 256;\n\tlet u = (n % 64) * SH_COEFFS;\n\tlet v = n / 64;\n\tfor (var i: i32 = 0; i < SH_COEFFS; i = i + 1) {\n\t\tsh[i] = mix(vec3f(uniform.shN_mins), vec3f(uniform.shN_maxs), unpack111110(pack8888(textureLoad(packedShN, vec2i(u + i, v), 0))));\n\t}\n\t*scale = 1.0;\n}\n`;\n\nvar gsplatSourceFormatVS = `\n#if defined(GSPLAT_WORKBUFFER_DATA)\n\t#include \"gsplatWorkBufferVS\"\n#elif GSPLAT_COMPRESSED_DATA == true\n\t#include \"gsplatCompressedDataVS\"\n\t#if SH_BANDS > 0\n\t\t#include \"gsplatCompressedSHVS\"\n\t#endif\n#elif GSPLAT_SOGS_DATA\n\t#include \"gsplatSogsDataVS\"\n\t#include \"gsplatSogsColorVS\"\n\t#if SH_BANDS > 0\n\t\t#include \"gsplatSogsSHVS\"\n\t#endif\n#else\n\t#include \"gsplatDataVS\"\n\t#include \"gsplatColorVS\"\n\t#if SH_BANDS > 0\n\t\t#include \"gsplatSHVS\"\n\t#endif\n#endif\n`;\n\nvar gsplatStructsVS = `\nstruct SplatSource {\n\torder: u32,\n\tid: u32,\n\tuv: vec2<i32>,\n\tcornerUV: vec2f,\n}\nstruct SplatCenter {\n\tview: vec3f,\n\tproj: vec4f,\n\tmodelView: mat4x4f,\n\tprojMat00: f32,\n}\nstruct SplatCorner {\n\toffset: vec2f,\n\tuv: vec2f,\n\t#if GSPLAT_AA\n\t\taaFactor: f32,\n\t#endif\n}\n`;\n\nvar gsplatCornerVS = `\nuniform viewport: vec2f;\nuniform camera_params: vec4f;\nfn initCornerCov(source: ptr<function, SplatSource>, center: ptr<function, SplatCenter>, corner: ptr<function, SplatCorner>, covA: vec3f, covB: vec3f) -> bool {\n\tlet Vrk = mat3x3f(\n\t\tvec3f(covA.x, covA.y, covA.z),\n\t\tvec3f(covA.y, covB.x, covB.y),\n\t\tvec3f(covA.z, covB.y, covB.z)\n\t);\n\tlet focal = uniform.viewport.x * center.projMat00;\n\tlet v = select(center.view.xyz, vec3f(0.0, 0.0, 1.0), uniform.camera_params.w == 1.0);\n\tlet J1 = focal / v.z;\n\tlet J2 = -J1 / v.z * v.xy;\n\tlet J = mat3x3f(\n\t\tvec3f(J1, 0.0, J2.x),\n\t\tvec3f(0.0, J1, J2.y),\n\t\tvec3f(0.0, 0.0, 0.0)\n\t);\n\tlet W = transpose(mat3x3f(center.modelView[0].xyz, center.modelView[1].xyz, center.modelView[2].xyz));\n\tlet T = W * J;\n\tlet cov = transpose(T) * Vrk * T;\n\t#if GSPLAT_AA\n\t\tlet detOrig = cov[0][0] * cov[1][1] - cov[0][1] * cov[1][0];\n\t\tlet detBlur = (cov[0][0] + 0.3) * (cov[1][1] + 0.3) - cov[0][1] * cov[1][0];\n\t\tcorner.aaFactor = sqrt(detOrig / detBlur);\n\t#endif\n\tlet diagonal1 = cov[0][0] + 0.3;\n\tlet offDiagonal = cov[0][1];\n\tlet diagonal2 = cov[1][1] + 0.3;\n\tlet mid = 0.5 * (diagonal1 + diagonal2);\n\tlet radius = length(vec2f((diagonal1 - diagonal2) / 2.0, offDiagonal));\n\tlet lambda1 = mid + radius;\n\tlet lambda2 = max(mid - radius, 0.1);\n\tlet vmin = min(1024.0, min(uniform.viewport.x, uniform.viewport.y));\n\tlet l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);\n\tlet l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);\n\tif (l1 < 2.0 && l2 < 2.0) {\n\t\treturn false;\n\t}\n\tlet c = center.proj.ww / uniform.viewport;\n\tif (any((abs(center.proj.xy) - vec2f(max(l1, l2)) * c) > center.proj.ww)) {\n\t\treturn false;\n\t}\n\tlet diagonalVector = normalize(vec2f(offDiagonal, lambda1 - diagonal1));\n\tlet v1 = l1 * diagonalVector;\n\tlet v2 = l2 * vec2f(diagonalVector.y, -diagonalVector.x);\n\tcorner.offset = (source.cornerUV.x * v1 + source.cornerUV.y * v2) * c;\n\tcorner.uv = source.cornerUV;\n\treturn true;\n}\nfn initCorner(source: ptr<function, SplatSource>, center: ptr<function, SplatCenter>, corner: ptr<function, SplatCorner>) -> bool {\n\tvar covA: vec3f;\n\tvar covB: vec3f;\n\treadCovariance(source, &covA, &covB);\n\treturn initCornerCov(source, center, corner, covA, covB);\n}\n`;\n\nvar gsplatDataVS = `\nvar transformA: texture_2d<u32>;\nvar transformB: texture_2d<f32>;\nvar<private> tAw: u32;\nfn readCenter(source: ptr<function, SplatSource>) -> vec3f {\n\tlet tA: vec4<u32> = textureLoad(transformA, source.uv, 0);\n\ttAw = tA.w;\n\treturn bitcast<vec3f>(tA.xyz);\n}\nfn unpackRotation(packed: vec3f) -> vec4f {\n\treturn vec4f(packed.xyz, sqrt(max(0.0, 1.0 - dot(packed, packed))));\n}\nfn readCovariance(source: ptr<function, SplatSource>, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {\n\tlet tB: vec4f = textureLoad(transformB, source.uv, 0);\n\tlet rot: mat3x3f = quatToMat3(unpackRotation(vec3f(unpack2x16float(bitcast<u32>(tAw)), tB.w)).wxyz);\n\tlet scale: vec3f = tB.xyz;\n\tlet M = transpose(mat3x3f(\n\t\tscale.x * rot[0],\n\t\tscale.y * rot[1],\n\t\tscale.z * rot[2]\n\t));\n\t*covA_ptr = vec3f(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));\n\t*covB_ptr = vec3f(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));\n}\n`;\n\nvar gsplatOutputVS = `\n#include \"tonemappingPS\"\n#include \"decodePS\"\n#include \"gammaPS\"\nfn prepareOutputFromGamma(gammaColor: vec3f) -> vec3f {\n\t#if TONEMAP == NONE\n\t\t#if GAMMA == NONE\n\t\t\treturn decodeGamma3(gammaColor);\n\t\t#else \n\t\t\treturn gammaColor;\n\t\t#endif\n\t#else\n\t\treturn gammaCorrectOutput(toneMap(decodeGamma3(gammaColor)));\n\t#endif\n}\n`;\n\nvar gsplatPS = `\n#ifndef DITHER_NONE\n\t#include \"bayerPS\"\n\t#include \"opacityDitherPS\"\n\tvarying id: f32;\n#endif\n#ifdef PICK_PASS\n\t#include \"pickPS\"\n#endif\n#if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)\n\tuniform alphaClip: f32;\n#endif\n#ifdef PREPASS_PASS\n\tvarying vLinearDepth: f32;\n\t#include \"floatAsUintPS\"\n#endif\nconst EXP_A: f32\t  = 12102203.0;\nconst EXP_BC_RMS: i32 = 1064866808;\nfn fastExp(x: f32) -> f32 {\n\tvar i: i32 = i32(EXP_A * x) + EXP_BC_RMS;\n\treturn bitcast<f32>(i);\n}\nvarying gaussianUV: vec2f;\nvarying gaussianColor: vec4f;\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\tlet A: f32 = dot(gaussianUV, gaussianUV);\n\tif (A > 1.0) {\n\t\tdiscard;\n\t\treturn output;\n\t}\n\tvar alpha: f32 = fastExp(-A * 4.0) * gaussianColor.a;\n\t#if defined(SHADOW_PASS) || defined(PICK_PASS) || defined(PREPASS_PASS)\n\t\tif (alpha < uniform.alphaClip) {\n\t\t\tdiscard;\n\t\t\treturn output;\n\t\t}\n\t#endif\n\t#ifdef PICK_PASS\n\t\toutput.color = getPickOutput();\n\t#elif SHADOW_PASS\n\t\toutput.color = vec4f(0.0, 0.0, 0.0, 1.0);\n\t#elif PREPASS_PASS\n\t\toutput.color = float2vec4(vLinearDepth);\n\t#else\n\t\tif (alpha < (1.0 / 255.0)) {\n\t\t\tdiscard;\n\t\t\treturn output;\n\t\t}\n\t\t#ifndef DITHER_NONE\n\t\t\topacityDither(&alpha, id * 0.013);\n\t\t#endif\n\t\toutput.color = vec4f(input.gaussianColor.xyz * alpha, alpha);\n\t#endif\n\treturn output;\n}`;\n\nvar gsplatSHVS = `\n#if SH_BANDS > 0\nfn unpack111011s(bits: u32) -> vec3f {\n\treturn (vec3f((vec3<u32>(bits) >> vec3<u32>(21u, 11u, 0u)) & vec3<u32>(0x7ffu, 0x3ffu, 0x7ffu)) / vec3f(2047.0, 1023.0, 2047.0)) * 2.0 - 1.0;\n}\nstruct ScaleAndSH {\n\tscale: f32,\n\ta: vec3f,\n\tb: vec3f,\n\tc: vec3f\n};\nfn fetchScale(t_in: vec4<u32>) -> ScaleAndSH {\n\tvar result: ScaleAndSH;\n\tresult.scale = bitcast<f32>(t_in.x);\n\tresult.a = unpack111011s(t_in.y);\n\tresult.b = unpack111011s(t_in.z);\n\tresult.c = unpack111011s(t_in.w);\n\treturn result;\n}\nstruct SH {\n\ta: vec3f,\n\tb: vec3f,\n\tc: vec3f,\n\td: vec3f\n};\nfn fetch4(t_in: vec4<u32>) -> SH {\n\tvar result: SH;\n\tresult.a = unpack111011s(t_in.x);\n\tresult.b = unpack111011s(t_in.y);\n\tresult.c = unpack111011s(t_in.z);\n\tresult.d = unpack111011s(t_in.w);\n\treturn result;\n}\nfn fetch1(t_in: u32) -> vec3f {\n\treturn unpack111011s(t_in);\n}\n#if SH_BANDS == 1\n\tvar splatSH_1to3: texture_2d<u32>;\n\tfn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 3>>, scale: ptr<function, f32>) {\n\t\tlet result = fetchScale(textureLoad(splatSH_1to3, source.uv, 0));\n\t\t*scale = result.scale;\n\t\tsh[0] = result.a;\n\t\tsh[1] = result.b;\n\t\tsh[2] = result.c;\n\t}\n#elif SH_BANDS == 2\n\tvar splatSH_1to3: texture_2d<u32>;\n\tvar splatSH_4to7: texture_2d<u32>;\n\tvar splatSH_8to11: texture_2d<u32>;\n\tfn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 8>>, scale: ptr<function, f32>) {\n\t\tlet first: ScaleAndSH = fetchScale(textureLoad(splatSH_1to3, source.uv, 0));\n\t\t*scale = first.scale;\n\t\tsh[0] = first.a;\n\t\tsh[1] = first.b;\n\t\tsh[2] = first.c;\n\t\tlet second: SH = fetch4(textureLoad(splatSH_4to7, source.uv, 0));\n\t\tsh[3] = second.a;\n\t\tsh[4] = second.b;\n\t\tsh[5] = second.c;\n\t\tsh[6] = second.d;\n\t\tsh[7] = fetch1(textureLoad(splatSH_8to11, source.uv, 0).x);\n\t}\n#else\n\tvar splatSH_1to3: texture_2d<u32>;\n\tvar splatSH_4to7: texture_2d<u32>;\n\tvar splatSH_8to11: texture_2d<u32>;\n\tvar splatSH_12to15: texture_2d<u32>;\n\tfn readSHData(source: ptr<function, SplatSource>, sh: ptr<function, array<vec3f, 15>>, scale: ptr<function, f32>) {\n\t\tlet first: ScaleAndSH = fetchScale(textureLoad(splatSH_1to3, source.uv, 0));\n\t\t*scale = first.scale;\n\t\tsh[0] = first.a;\n\t\tsh[1] = first.b;\n\t\tsh[2] = first.c;\n\t\tlet second: SH = fetch4(textureLoad(splatSH_4to7, source.uv, 0));\n\t\tsh[3] = second.a;\n\t\tsh[4] = second.b;\n\t\tsh[5] = second.c;\n\t\tsh[6] = second.d;\n\t\tlet third: SH = fetch4(textureLoad(splatSH_8to11, source.uv, 0));\n\t\tsh[7] = third.a;\n\t\tsh[8] = third.b;\n\t\tsh[9] = third.c;\n\t\tsh[10] = third.d;\n\t\tlet fourth: SH = fetch4(textureLoad(splatSH_12to15, source.uv, 0));\n\t\tsh[11] = fourth.a;\n\t\tsh[12] = fourth.b;\n\t\tsh[13] = fourth.c;\n\t\tsh[14] = fourth.d;\n\t}\n#endif\n#endif\n`;\n\nvar gsplatSourceVS = `\nattribute vertex_position: vec3f;\nattribute vertex_id_attrib: u32;\nuniform numSplats: u32;\nvar splatOrder: texture_2d<u32>;\nfn initSource(source: ptr<function, SplatSource>) -> bool {\n\tlet w: u32 = textureDimensions(splatOrder, 0).x;\n\tsource.order = vertex_id_attrib + u32(vertex_position.z);\n\tif (source.order >= uniform.numSplats) {\n\t\treturn false;\n\t}\n\tlet orderUV = vec2i(vec2u(source.order % w, source.order / w));\n\tsource.id = textureLoad(splatOrder, orderUV, 0).r;\n\tsource.uv = vec2i(vec2u(source.id % w, source.id / w));\n\tsource.cornerUV = vertex_position.xy;\n\treturn true;\n}\n`;\n\nvar gsplatVS = `\n#include \"gsplatCommonVS\"\nvarying gaussianUV: vec2f;\nvarying gaussianColor: vec4f;\n#ifndef DITHER_NONE\n\tvarying id: f32;\n#endif\nconst discardVec: vec4f = vec4f(0.0, 0.0, 2.0, 1.0);\n#ifdef PREPASS_PASS\n\tvarying vLinearDepth: f32;\n#endif\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n\tvar output: VertexOutput;\n\tvar source: SplatSource;\n\tif (!initSource(&source)) {\n\t\toutput.position = discardVec;\n\t\treturn output;\n\t}\n\tlet modelCenter: vec3f = readCenter(&source);\n\tvar center: SplatCenter;\n\tif (!initCenter(modelCenter, &center)) {\n\t\toutput.position = discardVec;\n\t\treturn output;\n\t}\n\tvar corner: SplatCorner;\n\tif (!initCorner(&source, &center, &corner)) {\n\t\toutput.position = discardVec;\n\t\treturn output;\n\t}\n\tvar clr: vec4f = readColor(&source);\n\t#if GSPLAT_AA\n\t\tclr.a = clr.a * corner.aaFactor;\n\t#endif\n\t#if SH_BANDS > 0\n\t\tlet modelView3x3 = mat3x3f(center.modelView[0].xyz, center.modelView[1].xyz, center.modelView[2].xyz);\n\t\tlet dir = normalize(center.view * modelView3x3);\n\t\tvar sh: array<vec3f, SH_COEFFS>;\n\t\tvar scale: f32;\n\t\treadSHData(&source, &sh, &scale);\n\t\tclr = vec4f(clr.xyz + evalSH(&sh, dir) * scale, clr.a);\n\t#endif\n\tclipCorner(&corner, clr.w);\n\toutput.position = center.proj + vec4f(corner.offset, 0.0, 0.0);\n\toutput.gaussianUV = corner.uv;\n\toutput.gaussianColor = vec4f(prepareOutputFromGamma(max(clr.xyz, vec3f(0.0))), clr.w);\n\t#ifndef DITHER_NONE\n\t\toutput.id = f32(source.id);\n\t#endif\n\t#ifdef PREPASS_PASS\n\t\toutput.vLinearDepth = -center.view.z;\n\t#endif\n\treturn output;\n}\n`;\n\nvar gsplatWorkBufferVS = `\nvar center: texture_2d<f32>;\nvar covA: texture_2d<f32>;\nvar covB: texture_2d<f32>;\nvar splatColor: texture_2d<f32>;\nfn readCenter(source: ptr<function, SplatSource>) -> vec3f {\n\treturn textureLoad(center, source.uv, 0).xyz;\n}\nfn readCovariance(source: ptr<function, SplatSource>, cov_A: ptr<function, vec3f>, cov_B: ptr<function, vec3f>) {\n\t*cov_A = textureLoad(covA, source.uv, 0).xyz;\n\t*cov_B = textureLoad(covB, source.uv, 0).xyz;\n}\nfn readColor(source: ptr<function, SplatSource>) -> vec4f {\n\treturn textureLoad(splatColor, source.uv, 0);\n}   \n`;\n\nvar quadVS = `\n\tattribute aPosition: vec2f;\n\tvarying uv0: vec2f;\n\t@vertex fn vertexMain(input: VertexInput) -> VertexOutput {\n\t\tvar output: VertexOutput;\n\t\toutput.position = vec4f(input.aPosition, 0.0, 1.0);\n\t\toutput.uv0 = getImageEffectUV((input.aPosition + 1.0) * 0.5);\n\t\treturn output;\n\t}\n`;\n\nvar indirectCoreCS = `\nstruct DrawIndexedIndirectArgs {\n\tindexCount: u32,\n\tinstanceCount: u32,\n\tfirstIndex: u32,\n\tbaseVertex: i32,\n\tfirstInstance: u32\n};\nstruct DrawIndirectArgs {\n\tvertexCount: u32,\n\tinstanceCount: u32,\n\tfirstVertex: u32,\n\tfirstInstance: u32,\n\t_pad: u32\n};\n`;\n\nvar immediateLinePS = `\n\t#include \"gammaPS\"\n\tvarying color: vec4f;\n\t@fragment\n\tfn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\tvar output: FragmentOutput;\n\t\toutput.color = vec4f(gammaCorrectOutput(decodeGamma3(input.color.rgb)), input.color.a);\n\t\treturn output;\n\t}\n`;\n\nvar immediateLineVS = `\n\tattribute vertex_position: vec4f;\n\tattribute vertex_color: vec4f;\n\tuniform matrix_model: mat4x4f;\n\tuniform matrix_viewProjection: mat4x4f;\n\tvarying color: vec4f;\n\t@vertex\n\tfn vertexMain(input : VertexInput) -> VertexOutput {\n\t\tvar output : VertexOutput;\n\t\toutput.color = input.vertex_color;\n\t\toutput.position = uniform.matrix_viewProjection * uniform.matrix_model * input.vertex_position;\n\t\treturn output;\n\t}\n`;\n\nvar iridescenceDiffractionPS = `\nuniform material_iridescenceRefractionIndex: f32;\nfn iridescence_iorToFresnelScalar(transmittedIor: f32, incidentIor: f32) -> f32 {\n\treturn pow((transmittedIor - incidentIor) / (transmittedIor + incidentIor), 2.0);\n}\nfn iridescence_iorToFresnelVec3(transmittedIor: vec3f, incidentIor: f32) -> vec3f {\n\treturn pow((transmittedIor - vec3f(incidentIor)) / (transmittedIor + vec3f(incidentIor)), vec3f(2.0));\n}\nfn iridescence_fresnelToIor(f0: vec3f) -> vec3f {\n\tlet sqrtF0: vec3f = sqrt(f0);\n\treturn (vec3f(1.0) + sqrtF0) / (vec3f(1.0) - sqrtF0);\n}\nconst XYZ_TO_REC709: mat3x3f = mat3x3f(\n\tvec3f(3.2404542, -1.5371385, -0.4985314),\n\tvec3f(-0.9692660,  1.8760108,  0.0415560),\n\tvec3f(0.0556434, -0.2040259,  1.0572252)\n);\nfn iridescence_sensitivity(opd: f32, shift: vec3f) -> vec3f {\n\tlet PI: f32 = 3.141592653589793;\n\tlet phase: f32 = 2.0 * PI * opd * 1.0e-9;\n\tconst val: vec3f = vec3f(5.4856e-13, 4.4201e-13, 5.2481e-13);\n\tconst pos: vec3f = vec3f(1.6810e+06, 1.7953e+06, 2.2084e+06);\n\tconst var_: vec3f = vec3f(4.3278e+09, 9.3046e+09, 6.6121e+09);\n\tvar xyz: vec3f = val * sqrt(2.0 * PI * var_) * cos(pos * phase + shift) * exp(-pow(phase, 2.0) * var_);\n\txyz.x = xyz.x + 9.7470e-14 * sqrt(2.0 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(-4.5282e+09 * pow(phase, 2.0));\n\txyz = xyz / vec3f(1.0685e-07);\n\treturn XYZ_TO_REC709 * xyz;\n}\nfn iridescence_fresnelScalar(cosTheta: f32, f0: f32) -> f32 {\n\tlet x: f32 = clamp(1.0 - cosTheta, 0.0, 1.0);\n\tlet x2: f32 = x * x;\n\tlet x5: f32 = x * x2 * x2;\n\treturn f0 + (1.0 - f0) * x5;\n}\nfn iridescence_fresnelVec3(cosTheta: f32, f0: vec3f) -> vec3f {\n\tlet x: f32 = clamp(1.0 - cosTheta, 0.0, 1.0);\n\tlet x2: f32 = x * x;\n\tlet x5: f32 = x * x2 * x2;\n\treturn f0 + (vec3f(1.0) - f0) * x5;\n}\nfn calcIridescence(outsideIor: f32, cosTheta: f32, base_f0: vec3f, iridescenceThickness: f32) -> vec3f {\n\tlet PI: f32 = 3.141592653589793;\n\tlet iridescenceIor: f32 = mix(outsideIor, uniform.material_iridescenceRefractionIndex, smoothstep(0.0, 0.03, iridescenceThickness));\n\tlet sinTheta2Sq: f32 = pow(outsideIor / iridescenceIor, 2.0) * (1.0 - pow(cosTheta, 2.0));\n\tlet cosTheta2Sq: f32 = 1.0 - sinTheta2Sq;\n\tif (cosTheta2Sq < 0.0) {\n\t\treturn vec3f(1.0);\n\t}\n\tlet cosTheta2: f32 = sqrt(cosTheta2Sq);\n\tlet r0: f32 = iridescence_iorToFresnelScalar(iridescenceIor, outsideIor);\n\tlet r12: f32 = iridescence_fresnelScalar(cosTheta, r0);\n\tlet r21: f32 = r12;\n\tlet t121: f32 = 1.0 - r12;\n\tlet phi12: f32 = select(0.0, PI, iridescenceIor < outsideIor);\n\tlet phi21: f32 = PI - phi12;\n\tlet baseIor: vec3f = iridescence_fresnelToIor(base_f0 + vec3f(0.0001));\n\tlet r1: vec3f = iridescence_iorToFresnelVec3(baseIor, iridescenceIor);\n\tlet r23: vec3f = iridescence_fresnelVec3(cosTheta2, r1);\n\tlet phi23: vec3f = select(vec3f(0.0), vec3f(PI), baseIor < vec3f(iridescenceIor));\n\tlet opd: f32 = 2.0 * iridescenceIor * iridescenceThickness * cosTheta2;\n\tlet phi: vec3f = vec3f(phi21) + phi23;\n\tlet r123Sq: vec3f = clamp(vec3f(r12) * r23, vec3f(1e-5), vec3f(0.9999));\n\tlet r123: vec3f = sqrt(r123Sq);\n\tlet rs: vec3f = pow(vec3f(t121), vec3f(2.0)) * r23 / (vec3f(1.0) - r123Sq);\n\tlet c0: vec3f = vec3f(r12) + rs;\n\tvar i_irid: vec3f = c0;\n\tvar cm: vec3f = rs - vec3f(t121);\n\tcm = cm * r123;\n\tlet sm1: vec3f = 2.0 * iridescence_sensitivity(1.0 * opd, 1.0 * phi);\n\ti_irid = i_irid + cm * sm1;\n\tcm = cm * r123;\n\tlet sm2: vec3f = 2.0 * iridescence_sensitivity(2.0 * opd, 2.0 * phi);\n\ti_irid = i_irid + cm * sm2;\n\treturn max(i_irid, vec3f(0.0));\n}\nfn getIridescenceDiffraction(cosTheta: f32, specularity: vec3f, iridescenceThickness: f32) -> vec3f {\n\treturn calcIridescence(1.0, cosTheta, specularity, iridescenceThickness);\n}\n`;\n\nvar iridescencePS = `\n#ifdef STD_IRIDESCENCE_CONSTANT\n\tuniform material_iridescence: f32;\n#endif\nfn getIridescence() {\n\tvar iridescence = 1.0;\n\t#ifdef STD_IRIDESCENCE_CONSTANT\n\tiridescence = iridescence * uniform.material_iridescence;\n\t#endif\n\t#ifdef STD_IRIDESCENCE_TEXTURE\n\tiridescence = iridescence * textureSampleBias({STD_IRIDESCENCE_TEXTURE_NAME}, {STD_IRIDESCENCE_TEXTURE_NAME}Sampler, {STD_IRIDESCENCE_TEXTURE_UV}, uniform.textureBias).{STD_IRIDESCENCE_TEXTURE_CHANNEL};\n\t#endif\n\tdIridescence = iridescence; \n}\n`;\n\nvar iridescenceThicknessPS = `\nuniform material_iridescenceThicknessMax: f32;\n#ifdef STD_IRIDESCENCETHICKNESS_TEXTURE\n\tuniform material_iridescenceThicknessMin: f32;\n#endif\nfn getIridescenceThickness() {\n\t#ifdef STD_IRIDESCENCETHICKNESS_TEXTURE\n\t\tvar blend: f32 = textureSampleBias({STD_IRIDESCENCETHICKNESS_TEXTURE_NAME}, {STD_IRIDESCENCETHICKNESS_TEXTURE_NAME}Sampler, {STD_IRIDESCENCETHICKNESS_TEXTURE_UV}, uniform.textureBias).{STD_IRIDESCENCETHICKNESS_TEXTURE_CHANNEL};\n\t\tvar iridescenceThickness: f32 = mix(uniform.material_iridescenceThicknessMin, uniform.material_iridescenceThicknessMax, blend);\n\t#else\n\t\tvar iridescenceThickness: f32 = uniform.material_iridescenceThicknessMax;\n\t#endif\n\tdIridescenceThickness = iridescenceThickness; \n}\n`;\n\nvar iorPS = `\n#ifdef STD_IOR_CONSTANT\n\tuniform material_refractionIndex: f32;\n#endif\nfn getIor() {\n#ifdef STD_IOR_CONSTANT\n\tdIor = uniform.material_refractionIndex;\n#else\n\tdIor = 1.0 / 1.5;\n#endif\n}\n`;\n\nvar lightDeclarationPS = `\n#if defined(LIGHT{i})\n\tuniform light{i}_color: vec3f;\n\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\tuniform light{i}_direction: vec3f;\n\t#else\n\t\t#define LIT_CODE_LIGHTS_POINT\n\t\tuniform light{i}_position: vec3f;\n\t\tuniform light{i}_radius: f32;\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t#define LIT_CODE_LIGHTS_SPOT\n\t\t\tuniform light{i}_direction: vec3f;\n\t\t\tuniform light{i}_innerConeAngle: f32;\n\t\t\tuniform light{i}_outerConeAngle: f32;\n\t\t#endif\n\t#endif\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#define LIT_CODE_FALLOFF_SQUARED\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tuniform light{i}_position: vec3f;\n\t\t#endif\n\t\tuniform light{i}_halfWidth: vec3f;\n\t\tuniform light{i}_halfHeight: vec3f;\n\t#else\n\t\t#if LIGHT{i}FALLOFF == LINEAR\n\t\t\t#define LIT_CODE_FALLOFF_LINEAR\n\t\t#endif\n\t\t#if LIGHT{i}FALLOFF == INVERSESQUARED\n\t\t\t#define LIT_CODE_FALLOFF_SQUARED\n\t\t#endif\n\t#endif\n\t#if defined(LIGHT{i}CASTSHADOW)\n\t\tuniform light{i}_shadowMatrix: mat4x4f;\n\t\tuniform light{i}_shadowIntensity: f32;\n\t\tuniform light{i}_shadowParams: vec4f;\n\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\tuniform light{i}_shadowSearchArea: f32;\n\t\t\tuniform light{i}_cameraParams: vec4f;\n\t\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\t\tuniform light{i}_softShadowParams: vec4f;\n\t\t\t#endif\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tuniform light{i}_shadowMatrixPalette: array<mat4x4f, 4>;\n\t\t\tuniform light{i}_shadowCascadeDistances: vec4f;\n\t\t\tuniform light{i}_shadowCascadeCount: i32;\n\t\t\tuniform light{i}_shadowCascadeBlend: f32;\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\tNOT SUPPORTED\n\t\t\t\n\t\t#else\n\t\t\t#if defined(LIGHT{i}SHADOW_PCF)\n\t\t\t\tvar light{i}_shadowMap: texture_depth_2d;\n\t\t\t\tvar light{i}_shadowMapSampler: sampler_comparison;\n\t\t\t#else\n\t\t\t\tvar light{i}_shadowMap: texture_2d<f32>;\n\t\t\t\tvar light{i}_shadowMapSampler: sampler;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\t#if defined(LIGHT{i}COOKIE)\n\t\t#define LIT_CODE_COOKIE\n\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\tNOT SUPPORTED\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\tNOT SUPPORTED\n\t\t#endif\n\t#endif\n#endif\n`;\n\nvar lightDiffuseLambertPS = `\nfn getLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f) -> f32 {\n\treturn max(dot(worldNormal, -lightDirNorm), 0.0);\n}\n`;\n\nvar lightDirPointPS = `\nfn evalOmniLight(lightPosW: vec3f) -> vec3f {\n\treturn vPositionW - lightPosW;\n}\n`;\n\nvar lightEvaluationPS = `\n#if defined(LIGHT{i})\n\tevaluateLight{i}(\n\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\tiridescenceFresnel\n\t\t#endif\n\t);\n#endif\n`;\n\nvar lightFunctionLightPS = `\n#if defined(LIGHT{i})\nfn evaluateLight{i}(\n\t#if defined(LIT_IRIDESCENCE)\n\t\tiridescenceFresnel: vec3f\n\t#endif\n) {\n\tvar lightColor: vec3f = uniform.light{i}_color;\n\t#if LIGHT{i}TYPE == DIRECTIONAL && !defined(LIT_SHADOW_CATCHER)\n\t\tif (all(lightColor == vec3f(0.0, 0.0, 0.0))) {\n\t\t\treturn;\n\t\t}\n\t#endif\n\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\tdLightDirNormW = uniform.light{i}_direction;\n\t\tdAtten = 1.0;\n\t#else\n\t\tvar lightDirW: vec3f = evalOmniLight(uniform.light{i}_position);\n\t\tdLightDirNormW = normalize(lightDirW);\n\t\t#if defined(LIGHT{i}COOKIE)\n\t\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t\t#ifdef LIGHT{i}COOKIE_FALLOFF\n\t\t\t\t\t#ifdef LIGHT{i}COOKIE_TRANSFORM\n\t\t\t\t\t\tvar cookieAttenuation: vec3f = getCookie2DXform(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity, uniform.light{i}_cookieMatrix, uniform.light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#else\n\t\t\t\t\t\tvar cookieAttenuation: vec3f = getCookie2D(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#endif\n\t\t\t\t#else\n\t\t\t\t\t#ifdef LIGHT{i}COOKIE_TRANSFORM\n\t\t\t\t\t\tvar cookieAttenuation: vec3f = getCookie2DClipXform(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity, uniform.light{i}_cookieMatrix, uniform.light{i}_cookieOffset).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#else\n\t\t\t\t\t\tvar cookieAttenuation: vec3f = getCookie2DClip(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t\t\t#endif\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\t\tvar cookieAttenuation: vec3f = getCookieCube(uniform.light{i}_cookie, uniform.light{i}_shadowMatrix, uniform.light{i}_cookieIntensity).{LIGHT{i}COOKIE_CHANNEL};\n\t\t\t#endif\n\t\t\tlightColor = lightColor * cookieAttenuation;\n\t\t#endif\n\t\t#if LIGHT{i}SHAPE == PUNCTUAL\n\t\t\t#if LIGHT{i}FALLOFF == LINEAR\n\t\t\t\tdAtten = getFalloffLinear(uniform.light{i}_radius, lightDirW);\n\t\t\t#else\n\t\t\t\tdAtten = getFalloffInvSquared(uniform.light{i}_radius, lightDirW);\n\t\t\t#endif\n\t\t#else\n\t\t\tdAtten = getFalloffWindow(uniform.light{i}_radius, lightDirW);\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t#if !defined(LIGHT{i}COOKIE) || defined(LIGHT{i}COOKIE_FALLOFF)\n\t\t\t\tdAtten = dAtten * getSpotEffect(uniform.light{i}_direction, uniform.light{i}_innerConeAngle, uniform.light{i}_outerConeAngle, dLightDirNormW);\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\tif (dAtten < 0.00001) {\n\t\treturn;\n\t}\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\tcalcRectLightValues(uniform.light{i}_position, uniform.light{i}_halfWidth, uniform.light{i}_halfHeight);\n\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\tcalcDiskLightValues(uniform.light{i}_position, uniform.light{i}_halfWidth, uniform.light{i}_halfHeight);\n\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\tcalcSphereLightValues(uniform.light{i}_position, uniform.light{i}_halfWidth, uniform.light{i}_halfHeight);\n\t\t#endif\n\t#endif\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tvar attenDiffuse: f32 = getLightDiffuse(litArgs_worldNormal, dViewDirW, dLightDirNormW);\n\t\t#else\n\t\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\t\tvar attenDiffuse: f32 = getRectLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;\n\t\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\t\tvar attenDiffuse: f32 = getDiskLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;\n\t\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\t\tvar attenDiffuse: f32 = getSphereLightDiffuse(litArgs_worldNormal, dViewDirW, lightDirW, dLightDirNormW) * 16.0;\n\t\t\t#endif\n\t\t#endif\n\t#else\n\t\tdAtten = dAtten * getLightDiffuse(litArgs_worldNormal, vec3(0.0), dLightDirNormW);\n\t#endif\n\t#ifdef LIGHT{i}CASTSHADOW\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tvar shadow: f32 = getShadow{i}(vec3(0.0));\n\t\t#else\n\t\t\tvar shadow: f32 = getShadow{i}(lightDirW);\n\t\t#endif\n\t\tshadow = mix(1.0, shadow, uniform.light{i}_shadowIntensity);\n\t\tdAtten = dAtten * shadow;\n\t\t#if defined(LIT_SHADOW_CATCHER) && LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tdShadowCatcher = dShadowCatcher * shadow;\n\t\t#endif\t\t\t\n\t#endif\n\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t#ifdef LIT_SPECULAR\n\t\t\tdDiffuseLight = dDiffuseLight + (((attenDiffuse * dAtten) * lightColor) * (1.0 - dLTCSpecFres));\n\t\t#else\n\t\t\tdDiffuseLight = dDiffuseLight + ((attenDiffuse * dAtten) * lightColor);\n\t\t#endif\t\t\t\t\t\t\n\t#else\n\t\t#if defined(AREA_LIGHTS) && defined(LIT_SPECULAR)\n\t\t\tdDiffuseLight = dDiffuseLight + ((dAtten * lightColor) * (1.0 - litArgs_specularity));\n\t\t#else\n\t\t\tdDiffuseLight = dDiffuseLight + (dAtten * lightColor);\n\t\t#endif\n\t#endif\n\t#ifdef LIGHT{i}AFFECT_SPECULARITY\n\t\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\t\t\tccSpecularLight = ccSpecularLight + (ccLTCSpecFres * getRectLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor);\n\t\t\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\t\t\tccSpecularLight = ccSpecularLight + (ccLTCSpecFres * getDiskLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor);\n\t\t\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\t\t\tccSpecularLight = ccSpecularLight + (ccLTCSpecFres * getSphereLightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * lightColor);\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\t#if LIGHT{i}SHAPE == RECT\n\t\t\t\t\tdSpecularLight = dSpecularLight + (dLTCSpecFres * getRectLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor);\n\t\t\t\t#elif LIGHT{i}SHAPE == DISK\n\t\t\t\t\tdSpecularLight = dSpecularLight + (dLTCSpecFres * getDiskLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor);\n\t\t\t\t#elif LIGHT{i}SHAPE == SPHERE\n\t\t\t\t\tdSpecularLight = dSpecularLight + (dLTCSpecFres * getSphereLightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * lightColor);\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t#else\n\t\t\t#if LIGHT{i}TYPE == DIRECTIONAL && LIT_FRESNEL_MODEL != NONE\n\t\t\t\t#define LIGHT{i}FRESNEL\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tvar halfDirW: vec3f = normalize(-dLightDirNormW + dViewDirW);\n\t\t\t#endif\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tvar lightspecularCC: vec3f = getLightSpecular(halfDirW, ccReflDirW, litArgs_clearcoat_worldNormal, dViewDirW, dLightDirNormW, litArgs_clearcoat_gloss, dTBN) * dAtten * lightColor;\n\t\t\t\t#ifdef LIGHT{i}FRESNEL\n\t\t\t\t\tlightspecularCC = lightspecularCC * getFresnelCC(dot(dViewDirW, halfDirW));\n\t\t\t\t#endif\n\t\t\t\tccSpecularLight = ccSpecularLight + lightspecularCC;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\tsSpecularLight = sSpecularLight + (getLightSpecularSheen(halfDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_sheen_gloss) * dAtten * lightColor);\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tvar lightSpecular: vec3f = getLightSpecular(halfDirW, dReflDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_gloss, dTBN) * dAtten * lightColor;\n\t\t\t\t#ifdef LIGHT{i}FRESNEL\n\t\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t\tlightSpecular = lightSpecular * getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity, iridescenceFresnel, litArgs_iridescence_intensity);\n\t\t\t\t\t#else\n\t\t\t\t\t\tlightSpecular = lightSpecular * getFresnel(dot(dViewDirW, halfDirW), litArgs_gloss, litArgs_specularity);\n\t\t\t\t\t#endif\n\t\t\t\t#else\n\t\t\t\t\tlightSpecular = lightSpecular * litArgs_specularity;\n\t\t\t\t#endif\n\t\t\t\t\n\t\t\t\tdSpecularLight = dSpecularLight + lightSpecular;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n}\n#endif\n`;\n\nvar lightFunctionShadowPS = `\n#ifdef LIGHT{i}CASTSHADOW\n\tfn getShadowSampleCoord{i}(shadowTransform: mat4x4f, shadowParams: vec4f, worldPosition: vec3f, lightPos: vec3f, lightDir: ptr<function, vec3f>, lightDirNorm: vec3f, normal: vec3f) -> vec3f {\n\t\tvar surfacePosition = worldPosition;\n\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_POINT\n\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET\n\t\t\t\tlet distScale: f32 = length(*lightDir);\n\t\t\t\tsurfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;\n\t\t\t\t*lightDir = surfacePosition - lightPos;\n\t\t\t\treturn *lightDir;\n\t\t\t#endif\n\t\t#else\n\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER\n\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET\n\t\t\t\t\tsurfacePosition = surfacePosition + normal * shadowParams.y;\n\t\t\t\t#endif\n\t\t\t#else\n\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_NORMAL_OFFSET\n\t\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO\n\t\t\t\t\t\tvar distScale: f32 = 1.0;\n\t\t\t\t\t#else\n\t\t\t\t\t\tvar distScale: f32 = abs(dot(vPositionW - lightPos, lightDirNorm));\n\t\t\t\t\t#endif\n\t\t\t\t\tsurfacePosition = surfacePosition + normal * shadowParams.y * clamp(1.0 - dot(normal, -lightDirNorm), 0.0, 1.0) * distScale;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\tvar positionInShadowSpace: vec4f = shadowTransform * vec4f(surfacePosition, 1.0);\n\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_ORTHO\n\t\t\t\tpositionInShadowSpace.z = saturate(positionInShadowSpace.z) - 0.0001;\n\t\t\t#else\n\t\t\t\t#ifdef LIGHT{i}_SHADOW_SAMPLE_SOURCE_ZBUFFER\n\t\t\t\t\tpositionInShadowSpace.xyz = positionInShadowSpace.xyz / positionInShadowSpace.w;\n\t\t\t\t#else\n\t\t\t\t\tpositionInShadowSpace.xy = positionInShadowSpace.xy / positionInShadowSpace.w;\n\t\t\t\t\tpositionInShadowSpace.z = length(*lightDir) * shadowParams.w;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef SHADOW_SAMPLE_Z_BIAS\n\t\t\t#endif\n\t\t\tsurfacePosition = positionInShadowSpace.xyz;\n\t\t#endif\n\t\treturn surfacePosition;\n\t}\n\tfn getShadow{i}(lightDirW_in: vec3f) -> f32 {\n\t\t#ifdef LIGHT{i}_SHADOW_CASCADES\n\t\t\tvar cascadeIndex: i32 = getShadowCascadeIndex(uniform.light{i}_shadowCascadeDistances, uniform.light{i}_shadowCascadeCount);\n\t\t\t#ifdef LIGHT{i}_SHADOW_CASCADE_BLEND\n\t\t\t\tcascadeIndex = ditherShadowCascadeIndex(cascadeIndex, uniform.light{i}_shadowCascadeDistances, uniform.light{i}_shadowCascadeCount, uniform.light{i}_shadowCascadeBlend);\n\t\t\t#endif\n\t\t\tvar shadowMatrix: mat4x4f = uniform.light{i}_shadowMatrixPalette[cascadeIndex];\n\t\t#else\n\t\t\tvar shadowMatrix: mat4x4f = uniform.light{i}_shadowMatrix;\n\t\t#endif\n\t\tvar lightDirArg = lightDirW_in;\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tvar shadowCoord: vec3f = getShadowSampleCoord{i}(shadowMatrix, uniform.light{i}_shadowParams, vPositionW, vec3f(0.0), &lightDirArg, dLightDirNormW, dVertexNormalW);\n\t\t#else\n\t\t\t var shadowCoord: vec3f = getShadowSampleCoord{i}(shadowMatrix, uniform.light{i}_shadowParams, vPositionW, uniform.light{i}_position, &lightDirArg, dLightDirNormW, dVertexNormalW);\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\tshadowCoord = fadeShadow(shadowCoord, uniform.light{i}_shadowCascadeDistances);\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == DIRECTIONAL\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_16F\n\t\t\t\treturn getShadowVSM16(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 5.54);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_32F\n\t\t\t\treturn getShadowVSM32(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 15.0);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\t\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t\t\tlet shadowSearchArea = vec2f(length(uniform.light{i}_halfWidth), length(uniform.light{i}_halfHeight)) * uniform.light{i}_shadowSearchArea;\n\t\t\t\t\treturn getShadowPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, shadowSearchArea, lightDirW_in);\n\t\t\t\t#else\n\t\t\t\t\treturn getShadowPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, uniform.light{i}_softShadowParams, lightDirW_in);\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F\n\t\t\t\treturn getShadowPCF1x1(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F\n\t\t\t\treturn getShadowPCF3x3(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F\n\t\t\t\treturn getShadowPCF5x5(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);\n\t\t\t#endif\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == SPOT\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_16F\n\t\t\t\treturn getShadowSpotVSM16(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 5.54, lightDirW_in);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == VSM_32F\n\t\t\t\treturn getShadowSpotVSM32(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, 15.0, lightDirW_in);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\t\t#if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t\t\tvar shadowSearchArea: vec2f = vec2f(length(uniform.light{i}_halfWidth), length(uniform.light{i}_halfHeight)) * uniform.light{i}_shadowSearchArea;\n\t\t\t\t#else\n\t\t\t\t\tvar shadowSearchArea: vec2f = vec2f(uniform.light{i}_shadowSearchArea);\n\t\t\t\t#endif\n\t\t\t\treturn getShadowSpotPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, shadowSearchArea, lightDirW_in);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F\n\t\t\t\treturn getShadowSpotPCF1x1(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F\n\t\t\t\treturn getShadowSpotPCF3x3(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF5_16F || LIGHT{i}SHADOWTYPE == PCF5_32F\n\t\t\t\treturn getShadowSpotPCF5x5(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams);\n\t\t\t#endif\n\t\t#endif\n\t\t#if LIGHT{i}TYPE == OMNI\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCSS_32F\n\t\t\t\t var shadowSearchArea: vec2f;\n\t\t\t\t #if LIGHT{i}SHAPE != PUNCTUAL\n\t\t\t\t\tvar shadowSearchArea: vec2f = vec2f(length(uniform.light{i}_halfWidth), length(uniform.light{i}_halfHeight)) * uniform.light{i}_shadowSearchArea;\n\t\t\t\t#else\n\t\t\t\t\tvar shadowSearchArea: vec2f = vec2f(uniform.light{i}_shadowSearchArea);\n\t\t\t\t#endif\n\t\t\t\treturn getShadowOmniPCSS(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, uniform.light{i}_cameraParams, shadowSearchArea, lightDirW_in);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF1_16F || LIGHT{i}SHADOWTYPE == PCF1_32F\n\t\t\t\treturn getShadowOmniPCF1x1(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, lightDirW_in);\n\t\t\t#endif\n\t\t\t#if LIGHT{i}SHADOWTYPE == PCF3_16F || LIGHT{i}SHADOWTYPE == PCF3_32F\n\t\t\t\treturn getShadowOmniPCF3x3(light{i}_shadowMap, light{i}_shadowMapSampler, shadowCoord, uniform.light{i}_shadowParams, lightDirW_in);\n\t\t\t#endif\n\t\t#endif\n\t}\n#endif\n`;\n\nvar lightingPS = `\n#ifdef LIT_CLUSTERED_LIGHTS\n\t#define LIT_CODE_FALLOFF_LINEAR\n\t#define LIT_CODE_FALLOFF_SQUARED\n\t#define LIT_CODE_LIGHTS_POINT\n\t#define LIT_CODE_LIGHTS_SPOT\n#endif\n#ifdef AREA_LIGHTS\n\tvar areaLightsLutTex1: texture_2d<f32>;\n\tvar areaLightsLutTex1Sampler: sampler;\n\tvar areaLightsLutTex2: texture_2d<f32>;\n\tvar areaLightsLutTex2Sampler: sampler;\n#endif\n#ifdef LIT_LIGHTING\n\t#include \"lightDiffuseLambertPS\"\n\t#if defined(AREA_LIGHTS) || defined(LIT_CLUSTERED_AREA_LIGHTS)\n\t\t#include \"ltcPS\"\n\t#endif\n#endif\n#ifdef SHADOW_DIRECTIONAL\n\t#include \"shadowCascadesPS\"\n#endif\n#if defined(SHADOW_KIND_PCF1)\n\t#include \"shadowPCF1PS\"\n#endif\n#if defined(SHADOW_KIND_PCF3)\n\t#include \"shadowPCF3PS\"\n#endif\n#if defined(SHADOW_KIND_PCF5)\n\t#include \"shadowPCF5PS\"\n#endif\n#if defined(SHADOW_KIND_PCSS)\n\t#include \"linearizeDepthPS\"\n\t#include \"shadowSoftPS\"\n#endif\n#if defined(SHADOW_KIND_VSM)\n\t#include \"shadowEVSMPS\"\n#endif\n#ifdef LIT_CODE_FALLOFF_LINEAR\n\t#include \"falloffLinearPS\"\n#endif\n#ifdef LIT_CODE_FALLOFF_SQUARED\n\t#include \"falloffInvSquaredPS\"\n#endif\n#ifdef LIT_CODE_LIGHTS_POINT\n\t#include \"lightDirPointPS\"\n#endif\n#ifdef LIT_CODE_LIGHTS_SPOT\n\t#include \"spotPS\"\n#endif\n#ifdef LIT_CODE_COOKIE\n\t#include \"cookiePS\"\n#endif\n#ifdef LIT_CLUSTERED_LIGHTS\n\t#include \"clusteredLightPS\"\n#endif\n#ifdef LIGHT_COUNT > 0\n\t#include \"lightFunctionShadowPS, LIGHT_COUNT\"\n\t#include \"lightFunctionLightPS, LIGHT_COUNT\"\n#endif\n`;\n\nvar lightmapAddPS = `\nfn addLightMap(\n\tlightmap: vec3f,\n\tdir: vec3f,\n\tworldNormal: vec3f,\n\tviewDir: vec3f,\n\treflectionDir: vec3f,\n\tgloss: f32,\n\tspecularity: vec3f,\n\tvertexNormal: vec3f,\n\ttbn: mat3x3f\n#if defined(LIT_IRIDESCENCE)\n\t, iridescenceFresnel: vec3f,\n\tiridescenceIntensity: f32\n#endif\n) {\n\t#if defined(LIT_SPECULAR) && defined(LIT_DIR_LIGHTMAP)\n\t\tif (dot(dir, dir) < 0.0001) {\n\t\t\t\tdDiffuseLight = dDiffuseLight + lightmap;\n\t\t} else {\n\t\t\tlet vlight: f32 = saturate(dot(dir, -vertexNormal));\n\t\t\tlet flight: f32 = saturate(dot(dir, -worldNormal));\n\t\t\tlet nlight: f32 = (flight / max(vlight, 0.01)) * 0.5;\n\t\t\tdDiffuseLight = dDiffuseLight + lightmap * nlight * 2.0;\n\t\t\tlet halfDir: vec3f = normalize(-dir + viewDir);\n\t\t\tvar specularLight: vec3f = lightmap * getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, dir, gloss, tbn);\n\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\tspecularLight = specularLight *\n\t\t\t\t\tgetFresnel(dot(viewDir, halfDir),\n\t\t\t\t\tgloss,\n\t\t\t\t\tspecularity\n\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t, iridescenceFresnel,\n\t\t\t\t\tiridescenceIntensity\n\t\t\t\t#endif\n\t\t\t\t\t);\n\t\t\t#endif\n\t\t\tdSpecularLight = dSpecularLight + specularLight;\n\t\t}\n\t#else\n\t\tdDiffuseLight = dDiffuseLight + lightmap;\n\t#endif\n}\n`;\n\nvar lightmapPS = `\n#ifdef STD_LIGHTMAP_DIR\n\tvar<private> dLightmapDir: vec3f;\n\tvar texture_dirLightMap: texture_2d<f32>;\n\tvar texture_dirLightMapSampler: sampler;\n#endif\nfn getLightMap() {\n\tdLightmap = vec3f(1.0);\n\t#ifdef STD_LIGHT_TEXTURE\n\t\tdLightmap = dLightmap * {STD_LIGHT_TEXTURE_DECODE}(textureSampleBias({STD_LIGHT_TEXTURE_NAME}, {STD_LIGHT_TEXTURE_NAME}Sampler, {STD_LIGHT_TEXTURE_UV}, uniform.textureBias)).{STD_LIGHT_TEXTURE_CHANNEL};\n\t\t#ifdef STD_LIGHTMAP_DIR\n\t\t\tvar dir: vec3f = textureSampleBias(texture_dirLightMap, texture_dirLightMapSampler, {STD_LIGHT_TEXTURE_UV}, uniform.textureBias).xyz * 2.0 - 1.0;\n\t\t\tvar dirDot = dot(dir, dir);\n\t\t\tdLightmapDir = select(vec3(0.0), dir / sqrt(dirDot), dirDot > 0.001);\n\t\t#endif\n\t#endif\n\t#ifdef STD_LIGHT_VERTEX\n\t\tdLightmap = dLightmap * saturate(vVertexColor.{STD_LIGHT_VERTEX_CHANNEL});\n\t#endif\n}\n`;\n\nvar lightSpecularAnisoGGXPS = `\nfn calcLightSpecular(gloss: f32, worldNormal: vec3f, viewDir: vec3f, h: vec3f, lightDirNorm: vec3f, tbn: mat3x3f) -> f32 {\n\tlet PI: f32 = 3.141592653589793;\n\tlet roughness: f32 = max((1.0 - gloss) * (1.0 - gloss), 0.001);\n\tlet alphaRoughness: f32 = roughness * roughness;\n\tlet anisotropy: f32 = dAnisotropy;\n\tlet direction: vec2f = dAnisotropyRotation;\n\tlet at: f32 = mix(alphaRoughness, 1.0, anisotropy * anisotropy);\n\tlet ab: f32 = clamp(alphaRoughness, 0.001, 1.0);\n\tlet anisotropicT: vec3f = normalize(tbn * vec3f(direction, 0.0));\n\tlet anisotropicB: vec3f = normalize(cross(tbn[2], anisotropicT));\n\tlet NoH: f32 = dot(worldNormal, h);\n\tlet ToH: f32 = dot(anisotropicT, h);\n\tlet BoH: f32 = dot(anisotropicB, h);\n\tlet a2: f32 = at * ab;\n\tlet v: vec3f = vec3f(ab * ToH, at * BoH, a2 * NoH);\n\tlet v2: f32 = dot(v, v);\n\tlet w2: f32 = a2 / v2;\n\tlet D: f32 = a2 * w2 * w2 * (1.0 / PI);\n\tlet ToV: f32 = dot(anisotropicT, viewDir);\n\tlet BoV: f32 = dot(anisotropicB, viewDir);\n\tlet ToL: f32 = dot(anisotropicT, -lightDirNorm);\n\tlet BoL: f32 = dot(anisotropicB, -lightDirNorm);\n\tlet NoV: f32 = dot(worldNormal, viewDir);\n\tlet NoL: f32 = dot(worldNormal, -lightDirNorm);\n\tlet lambdaV: f32 = NoL * length(vec3f(at * ToV, ab * BoV, NoV));\n\tlet lambdaL: f32 = NoV * length(vec3f(at * ToL, ab * BoL, NoL));\n\tlet G: f32 = 0.5 / (lambdaV + lambdaL);\n\treturn D * G;\n}\nfn getLightSpecular(h: vec3f, reflDir: vec3f, worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f, gloss: f32, tbn: mat3x3f) -> f32 {\n\treturn calcLightSpecular(gloss, worldNormal, viewDir, h, lightDirNorm, tbn);\n}\n`;\n\nvar lightSpecularBlinnPS = `\nfn calcLightSpecular(gloss: f32, worldNormal: vec3f, h: vec3f) -> f32 {\n\tlet nh: f32 = max( dot( h, worldNormal ), 0.0 );\n\tvar specPow: f32 = exp2(gloss * 11.0);\n\tspecPow = max(specPow, 0.0001);\n\treturn pow(nh, specPow) * (specPow + 2.0) / 8.0;\n}\nfn getLightSpecular(h: vec3f, reflDir: vec3f, worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f, gloss: f32, tbn: mat3x3f) -> f32 {\n\treturn calcLightSpecular(gloss, worldNormal, h);\n}\n`;\n\nvar lightSheenPS = `\nfn sheenD(normal: vec3f, h: vec3f, roughness: f32) -> f32 {\n\tlet PI: f32 = 3.141592653589793;\n\tlet invR: f32 = 1.0 / (roughness * roughness);\n\tvar cos2h: f32 = max(dot(normal, h), 0.0);\n\tcos2h = cos2h * cos2h;\n\tlet sin2h: f32 = max(1.0 - cos2h, 0.0078125);\n\treturn (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * PI);\n}\nfn sheenV(normal: vec3f, viewDir: vec3f, light: vec3f) -> f32 {\n\tlet NoV: f32 = max(dot(normal, viewDir), 0.000001);\n\tlet NoL: f32 = max(dot(normal, light), 0.000001);\n\treturn 1.0 / (4.0 * (NoL + NoV - NoL * NoV));\n}\nfn getLightSpecularSheen(h: vec3f, worldNormal: vec3f, viewDir: vec3f, lightDirNorm: vec3f, sheenGloss: f32) -> f32 {\n\tlet D: f32 = sheenD(worldNormal, h, sheenGloss);\n\tlet V: f32 = sheenV(worldNormal, viewDir, -lightDirNorm);\n\treturn D * V;\n}`;\n\nvar linearizeDepthPS = `\n#ifndef LINEARIZE_DEPTH\n#define LINEARIZE_DEPTH\nfn linearizeDepthWithParams(z: f32, cameraParams: vec4f) -> f32 {\n\tif (cameraParams.w == 0.0) {\n\t\treturn (cameraParams.z * cameraParams.y) / (cameraParams.y + z * (cameraParams.z - cameraParams.y));\n\t} else {\n\t\treturn cameraParams.z + z * (cameraParams.y - cameraParams.z);\n\t}\n}\n#ifndef CAMERAPLANES\n\t#define CAMERAPLANES\n\tuniform camera_params: vec4f;\n#endif\nfn linearizeDepth(z: f32) -> f32 {\n\treturn linearizeDepthWithParams(z, uniform.camera_params);\n}\n#endif\n`;\n\nvar litForwardBackendPS = `\nfn evaluateBackend() -> FragmentOutput {\n\tvar output: FragmentOutput;\n\t#ifdef LIT_SSAO\n\t\tlitArgs_ao = litArgs_ao * textureSampleLevel(ssaoTexture, ssaoTextureSampler, pcPosition.xy * uniform.ssaoTextureSizeInv, 0.0).r;\n\t#endif\n\t#ifdef LIT_NEEDS_NORMAL\n\t\t#ifdef LIT_SPECULAR\n\t\t\tgetReflDir(litArgs_worldNormal, dViewDirW, litArgs_gloss, dTBN);\n\t\t#endif\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\tccReflDirW = normalize(-reflect(dViewDirW, litArgs_clearcoat_worldNormal));\n\t\t#endif\n\t#endif\n\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t#ifdef LIT_METALNESS\n\t\t\tvar f0: f32 = 1.0 / litArgs_ior;\n\t\t\tf0 = (f0 - 1.0) / (f0 + 1.0);\n\t\t\tf0 = f0 * f0;\n\t\t\tlitArgs_specularity = getSpecularModulate(litArgs_specularity, litArgs_albedo, litArgs_metalness, f0);\n\t\t\tlitArgs_albedo = getAlbedoModulate(litArgs_albedo, litArgs_metalness);\n\t\t#endif\n\t\t#ifdef LIT_IRIDESCENCE\n\t\t\tvar iridescenceFresnel: vec3f = getIridescenceDiffraction(saturate(dot(dViewDirW, litArgs_worldNormal)), litArgs_specularity, litArgs_iridescence_thickness);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_ADD_AMBIENT\n\t\taddAmbient(litArgs_worldNormal);\n\t\t#ifdef LIT_SPECULAR\n\t\t\tdDiffuseLight = dDiffuseLight * (1.0 - litArgs_specularity);\n\t\t#endif\n\t\t#ifdef LIT_SEPARATE_AMBIENT\n\t\t\tvar dAmbientLight: vec3f = dDiffuseLight;\n\t\t\tdDiffuseLight = vec3(0.0);\n\t\t#endif\n\t#endif\n\t#ifndef LIT_OLD_AMBIENT\n\t\tdDiffuseLight = dDiffuseLight * uniform.material_ambient;\n\t#endif\n\t#ifdef LIT_AO\n\t\t#ifndef LIT_OCCLUDE_DIRECT\n\t\t\toccludeDiffuse(litArgs_ao);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_LIGHTMAP\n\t\taddLightMap(\n\t\t\tlitArgs_lightmap, \n\t\t\tlitArgs_lightmapDir, \n\t\t\tlitArgs_worldNormal, \n\t\t\tdViewDirW, \n\t\t\tdReflDirW, \n\t\t\tlitArgs_gloss, \n\t\t\tlitArgs_specularity, \n\t\t\tdVertexNormalW,\n\t\t\tdTBN\n\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t, iridescenceFresnel,\n\t\t\tlitArgs_iridescence_intensity\n\t\t#endif\n\t\t);\n\t#endif\n\t#ifdef LIT_LIGHTING || LIT_REFLECTIONS\n\t\t#ifdef LIT_REFLECTIONS\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\taddReflectionCC(ccReflDirW, litArgs_clearcoat_gloss);\n\t\t\t\n\t\t\t\t#ifdef LIT_SPECULAR_FRESNEL\n\t\t\t\t\tccFresnel = getFresnelCC(dot(dViewDirW, litArgs_clearcoat_worldNormal));\n\t\t\t\t\tccReflection = ccReflection * ccFresnel;\n\t\t\t\t#else\n\t\t\t\t\tccFresnel = 0.0;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\tccReflection = ccReflection * litArgs_specularityFactor;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\taddReflectionSheen(litArgs_worldNormal, dViewDirW, litArgs_sheen_gloss);\n\t\t\t#endif\n\t\t\taddReflection(dReflDirW, litArgs_gloss);\n\t\t\t#ifdef LIT_FRESNEL_MODEL\n\t\t\t\tdReflection = vec4f(\n\t\t\t\t\tdReflection.rgb * getFresnel(\n\t\t\t\t\t\tdot(dViewDirW, litArgs_worldNormal),\n\t\t\t\t\t\tlitArgs_gloss,\n\t\t\t\t\t\tlitArgs_specularity\n\t\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t\t, iridescenceFresnel,\n\t\t\t\t\t\tlitArgs_iridescence_intensity\n\t\t\t\t\t#endif\n\t\t\t\t\t\t),\n\t\t\t\t\tdReflection.a\n\t\t\t\t);\n\t\t\t#else\n\t\t\t\tdReflection = vec4f(dReflection.rgb * litArgs_specularity, dReflection.a);\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\tdReflection = vec4f(dReflection.rgb * litArgs_specularityFactor, dReflection.a);\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef AREA_LIGHTS\n\t\t\tdSpecularLight = dSpecularLight * litArgs_specularity;\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tcalcLTCLightValues(litArgs_gloss, litArgs_worldNormal, dViewDirW, litArgs_specularity, litArgs_clearcoat_gloss, litArgs_clearcoat_worldNormal, litArgs_clearcoat_specularity);\n\t\t\t#endif\n\t\t#endif\n\t\t\n\t\t#ifdef LIGHT_COUNT > 0\n\t\t\t#include \"lightEvaluationPS, LIGHT_COUNT\"\n\t\t#endif\n\t\t#ifdef LIT_CLUSTERED_LIGHTS\n\t\t\taddClusteredLights(litArgs_worldNormal, dViewDirW, dReflDirW,\n\t\t\t\t#if defined(LIT_CLEARCOAT)\n\t\t\t\t\t\tccReflDirW,\n\t\t\t\t#endif\n\t\t\t\t\t\tlitArgs_gloss, litArgs_specularity, dVertexNormalW, dTBN, \n\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t\tiridescenceFresnel,\n\t\t\t\t#endif\n\t\t\t\t\t\tlitArgs_clearcoat_worldNormal, litArgs_clearcoat_gloss, litArgs_sheen_gloss, litArgs_iridescence_intensity\n\t\t\t);\n\t\t#endif\n\t\t#ifdef AREA_LIGHTS\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tlitArgs_clearcoat_specularity = 1.0;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR\n\t\t\t\tlitArgs_specularity = vec3(1.0);\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef LIT_REFRACTION\n\t\t\taddRefraction(\n\t\t\t\tlitArgs_worldNormal, \n\t\t\t\tdViewDirW, \n\t\t\t\tlitArgs_thickness, \n\t\t\t\tlitArgs_gloss, \n\t\t\t\tlitArgs_specularity, \n\t\t\t\tlitArgs_albedo, \n\t\t\t\tlitArgs_transmission,\n\t\t\t\tlitArgs_ior,\n\t\t\t\tlitArgs_dispersion\n\t\t\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t\t\t, iridescenceFresnel, \n\t\t\t\t\tlitArgs_iridescence_intensity\n\t\t\t\t#endif\n\t\t\t);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_AO\n\t\t#ifdef LIT_OCCLUDE_DIRECT\n\t\t\toccludeDiffuse(litArgs_ao);\n\t\t#endif\n\t\t#if LIT_OCCLUDE_SPECULAR != NONE\n\t\t\toccludeSpecular(litArgs_gloss, litArgs_ao, litArgs_worldNormal, dViewDirW);\n\t\t#endif\n\t#endif\n\t#ifdef LIT_SPECULARITY_FACTOR\n\t\tdSpecularLight = dSpecularLight * litArgs_specularityFactor;\n\t#endif\n\t#if !defined(LIT_OPACITY_FADES_SPECULAR)\n\t\t#if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == PREMULTIPLIED\n\t\t\tvar specLum: f32 = dot((dSpecularLight + dReflection.rgb * dReflection.a), vec3f( 0.2126, 0.7152, 0.0722 ));\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tspecLum = specLum + dot(ccSpecularLight * litArgs_clearcoat_specularity + ccReflection * litArgs_clearcoat_specularity, vec3f( 0.2126, 0.7152, 0.0722 ));\n\t\t\t#endif\n\t\t\tlitArgs_opacity = clamp(litArgs_opacity + gammaCorrectInput(specLum), 0.0, 1.0);\n\t\t#endif\n\t\tlitArgs_opacity = litArgs_opacity * uniform.material_alphaFade;\n\t#endif\n\t#ifdef LIT_LIGHTMAP_BAKING\n\t\t#ifdef LIT_LIGHTMAP_BAKING_COLOR\n\t\t\t#include \"bakeLmEndPS\"\n\t\t#endif\n\t\t#ifdef LIT_LIGHTMAP_BAKING_DIR\n\t\t\t#include \"bakeDirLmEndPS\"\n\t\t#endif\n\t#else\n\t\t#include \"endPS\"\n\t\t#include \"outputAlphaPS\"\n\t#endif\n\t#ifdef LIT_MSDF\n\t\toutput.color = applyMsdf(output.color);\n\t#endif\n\t#include \"outputPS\"\n\t#include \"debugOutputPS\"\n\t#ifdef LIT_SHADOW_CATCHER\n\t\toutput.color = vec4f(vec3f(dShadowCatcher), output.color.a);\n\t#endif\n\treturn output;\n}\n`;\n\nvar litForwardDeclarationPS = `\nvar<private> sReflection: vec3f;\nvar<private> dVertexNormalW: vec3f;\nvar<private> dTangentW: vec3f;\nvar<private> dBinormalW: vec3f;\nvar<private> dViewDirW: vec3f;\nvar<private> dReflDirW: vec3f;\nvar<private> ccReflDirW: vec3f;\nvar<private> dLightDirNormW: vec3f;\nvar<private> dAtten: f32;\nvar<private> dTBN: mat3x3f;\nvar<private> dReflection: vec4f;\nvar<private> dDiffuseLight: vec3f;\nvar<private> dSpecularLight: vec3f;\nvar<private> ccFresnel: f32;\nvar<private> ccReflection: vec3f;\nvar<private> ccSpecularLight: vec3f;\nvar<private> ccSpecularityNoFres: f32;\nvar<private> sSpecularLight: vec3f;\n#ifdef LIT_DISPERSION\n\tuniform material_dispersion: f32;\n#endif\n#ifndef LIT_OPACITY_FADES_SPECULAR\n\tuniform material_alphaFade: f32;\n#endif\n#ifdef LIT_SSAO\n\tvar ssaoTexture : texture_2d<f32>;\n\tvar ssaoTextureSampler : sampler;\n\tuniform ssaoTextureSizeInv: vec2f;\n#endif\n#ifdef LIT_SHADOW_CATCHER\n\tvar<private> dShadowCatcher: f32 = 1.0;\n#endif\n#if LIGHT_COUNT > 0\n\t#include \"lightDeclarationPS, LIGHT_COUNT\"\n#endif\n#ifdef LIT_SPECULAR\n\t#if LIT_FRESNEL_MODEL == NONE && !defined(LIT_REFLECTIONS) && !defined(LIT_DIFFUSE_MAP) \n\t\t#define LIT_OLD_AMBIENT\n\t#endif\n#endif\n#ifdef STD_LIGHTMAP_DIR\n\tuniform bakeDir: f32;\n#endif\n#ifdef LIT_LIGHTMAP_BAKING_ADD_AMBIENT\n\tuniform ambientBakeOcclusionContrast: f32;\n\tuniform ambientBakeOcclusionBrightness: f32;\n#endif\n`;\n\nvar litForwardMainPS = `\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\t#include \"litUserMainStartPS\"\n\tdReflection = vec4f(0.0);\n\t#ifdef LIT_CLEARCOAT\n\t\tccSpecularLight = vec3f(0.0);\n\t\tccReflection = vec3f(0.0);\n\t#endif\n\t#if LIT_NONE_SLICE_MODE == SLICED\n\t\t#include \"startNineSlicedPS\"\n\t#elif LIT_NONE_SLICE_MODE == TILED\n\t\t#include \"startNineSlicedTiledPS\"\n\t#endif\n\t#ifdef LIT_NEEDS_NORMAL\n\t\tdVertexNormalW = normalize(vNormalW);\n\t\t#ifdef LIT_TANGENTS\n\t\t\t#if defined(LIT_HEIGHTS) || defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS) || defined(LIT_GGX_SPECULAR)\n\t\t\t\tdTangentW = vTangentW;\n\t\t\t\tdBinormalW = vBinormalW;\n\t\t\t#endif\n\t\t#endif\n\t\tgetViewDir();\n\t\t#ifdef LIT_TBN\n\t\t\tgetTBN(dTangentW, dBinormalW, dVertexNormalW);\n\t\t\t#ifdef LIT_TWO_SIDED_LIGHTING\n\t\t\t\thandleTwoSidedLighting();\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\tevaluateFrontend();\n\t#include \"debugProcessFrontendPS\"\n\tvar output: FragmentOutput = evaluateBackend();\n\t#include \"litUserMainEndPS\"\n\treturn output;\n}\n`;\n\nvar litForwardPostCodePS = `\n#ifdef LIT_NEEDS_NORMAL\n\t#include \"cubeMapRotatePS\"\n\t#include \"cubeMapProjectPS\"\n\t#include \"envProcPS\"\n#endif\n#ifdef LIT_SPECULAR_OR_REFLECTION\n\t#ifdef LIT_METALNESS\n\t\t#include \"metalnessModulatePS\"\n\t#endif\n\t#if LIT_FRESNEL_MODEL == SCHLICK\n\t\t#include \"fresnelSchlickPS\"\n\t#endif\n\t#ifdef LIT_IRIDESCENCE\n\t\t#include \"iridescenceDiffractionPS\"\n\t#endif\n#endif\n#ifdef LIT_AO\n\t#include \"aoDiffuseOccPS\"\n\t#include \"aoSpecOccPS\"\n#endif\n#if LIT_REFLECTION_SOURCE == ENVATLASHQ\n\t#include \"envAtlasPS\"\n\t#include \"reflectionEnvHQPS\"\n#elif LIT_REFLECTION_SOURCE == ENVATLAS\n\t#include \"envAtlasPS\"\n\t#include \"reflectionEnvPS\"\n#elif LIT_REFLECTION_SOURCE == CUBEMAP\n\t#include \"reflectionCubePS\"\n#elif LIT_REFLECTION_SOURCE == SPHEREMAP\n\t#include \"reflectionSpherePS\"\n#endif\n#ifdef LIT_REFLECTIONS\n\t#ifdef LIT_CLEARCOAT\n\t\t#include \"reflectionCCPS\"\n\t#endif\n\t#ifdef LIT_SHEEN\n\t\t#include \"reflectionSheenPS\"\n\t#endif\n#endif\n#ifdef LIT_REFRACTION\n\t#if defined(LIT_DYNAMIC_REFRACTION)\n\t\t#include \"refractionDynamicPS\"\n\t#elif defined(LIT_REFLECTIONS)\n\t\t#include \"refractionCubePS\"\n\t#endif\n#endif\n#ifdef LIT_SHEEN\n\t#include \"lightSheenPS\"\n#endif\nuniform material_ambient: vec3f;\n#ifdef LIT_SPECULAR\n\t#ifdef LIT_LIGHTING\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\t#include \"lightSpecularAnisoGGXPS\"\n\t\t#else\n\t\t\t#include \"lightSpecularBlinnPS\"\n\t\t#endif\n\t#endif\n#endif\n#include \"combinePS\"\n#ifdef LIT_LIGHTMAP\n\t#include \"lightmapAddPS\"\n#endif\n#ifdef LIT_ADD_AMBIENT\n\t#include \"ambientPS\"\n#endif\n#ifdef LIT_MSDF\n\t#include \"msdfPS\"\n#endif\n#ifdef LIT_NEEDS_NORMAL\n\t#include \"viewDirPS\"\n\t#ifdef LIT_SPECULAR\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\t#include \"reflDirAnisoPS\"\n\t\t#else\n\t\t\t#include \"reflDirPS\"\n\t\t#endif\n\t#endif\n#endif\n#include \"lightingPS\"\n`;\n\nvar litForwardPreCodePS = `\n#include \"basePS\"\n#include \"sphericalPS\"\n#include \"decodePS\"\n#include \"gammaPS\"\n#include \"tonemappingPS\"\n#include \"fogPS\"\n#if LIT_NONE_SLICE_MODE == SLICED\n\t#include \"baseNineSlicedPS\"\n#elif LIT_NONE_SLICE_MODE == TILED\n\t#include \"baseNineSlicedTiledPS\"\n#endif\n#ifdef LIT_TBN\n\t#include \"TBNPS\"\n\t#ifdef LIT_TWO_SIDED_LIGHTING\n\t\t#include \"twoSidedLightingPS\"\n\t#endif\n#endif\n`;\n\nvar litMainPS = `\n#include \"varyingsPS\"\n#include \"litUserDeclarationPS\"\n#include \"frontendDeclPS\"\n#if defined(PICK_PASS) || defined(PREPASS_PASS)\n\t#include \"frontendCodePS\"\n\t#include \"litUserCodePS\"\n\t#include \"litOtherMainPS\"\n#elif defined(SHADOW_PASS)\n\t#include \"frontendCodePS\"\n\t#include \"litUserCodePS\"\n\t#include \"litShadowMainPS\"\n#else\n\t#include \"litForwardDeclarationPS\"\n\t#include \"litForwardPreCodePS\"\n\t#include \"frontendCodePS\"\n\t#include \"litForwardPostCodePS\"\n\t#include \"litForwardBackendPS\"\n\t#include \"litUserCodePS\"\n\t#include \"litForwardMainPS\"\n#endif\n`;\n\nvar litMainVS = `\n#include \"varyingsVS\"\n#include  \"litUserDeclarationVS\"\n#ifdef VERTEX_COLOR\n\tattribute vertex_color: vec4f;\n#endif\n#ifdef NINESLICED\n\tvarying vMask: vec2f;\n\tvarying vTiledUv: vec2f;\n\tvar<private> dMaskGlobal: vec2f;\n\tvar<private> dTiledUvGlobal: vec2f;\n\tuniform innerOffset: vec4f;\n\tuniform outerScale: vec2f;\n\tuniform atlasRect: vec4f;\n#endif\nvar<private> dPositionW: vec3f;\nvar<private> dModelMatrix: mat4x4f;\n#include \"transformCoreVS\"\n#ifdef UV0\n\tattribute vertex_texCoord0: vec2f;\n\t#include \"uv0VS\"\n#endif\n#ifdef UV1\n\tattribute vertex_texCoord1: vec2f;\n\t#include \"uv1VS\"\n#endif\n#ifdef LINEAR_DEPTH\n\t#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\t\tuniform matrix_view: mat4x4f;\n\t#endif\n#endif\n#include \"transformVS\"\n#ifdef NORMALS\n\t#include \"normalCoreVS\"\n\t#include \"normalVS\"\n#endif\n#ifdef TANGENTS\n\tattribute vertex_tangent: vec4f;\n#endif\n#include \"uvTransformUniformsPS, UV_TRANSFORMS_COUNT\"\n#ifdef MSDF\n\t#include \"msdfVS\"\n#endif\n#include  \"litUserCodeVS\"\n@vertex\nfn vertexMain(input : VertexInput) -> VertexOutput {\n\t#include \"litUserMainStartVS\"\n\tvar output : VertexOutput;\n\toutput.position = getPosition();\n\toutput.vPositionW = getWorldPosition();\n\t#ifdef NORMALS\n\t\toutput.vNormalW = getNormal();\n\t#endif\n\t#ifdef TANGENTS\n\t\toutput.vTangentW = normalize(dNormalMatrix * vertex_tangent.xyz);\n\t\toutput.vBinormalW = cross(output.vNormalW, output.vTangentW) * vertex_tangent.w;\n\t#elif defined(GGX_SPECULAR)\n\t\toutput.vObjectSpaceUpW = normalize(dNormalMatrix * vec3f(0.0, 1.0, 0.0));\n\t#endif\n\t#ifdef UV0\n\t\tvar uv0: vec2f = getUv0();\n\t\t#ifdef UV0_UNMODIFIED\n\t\t\toutput.vUv0 = uv0;\n\t\t#endif\n\t#endif\n\t#ifdef UV1\n\t\tvar uv1: vec2f = getUv1();\n\t\t#ifdef UV1_UNMODIFIED\n\t\t\toutput.vUv1 = uv1;\n\t\t#endif\n\t#endif\n\t#include \"uvTransformVS, UV_TRANSFORMS_COUNT\"\n\t#ifdef VERTEX_COLOR\n\t\toutput.vVertexColor = vertex_color;\n\t#endif\n\t#ifdef LINEAR_DEPTH\n\t\toutput.vLinearDepth = -(uniform.matrix_view * vec4f(output.vPositionW, 1.0)).z;\n\t#endif\n\t#ifdef MSDF\n\t\tunpackMsdfParams();\n\t\toutput.outline_color = dOutlineColor;\n\t\toutput.outline_thickness = dOutlineThickness;\n\t\toutput.shadow_color = dShadowColor;\n\t\toutput.shadow_offset = dShadowOffset;\n\t#endif\n\t#ifdef NINESLICED\n\t\toutput.vMask = dMaskGlobal;\n\t\toutput.vTiledUv = dTiledUvGlobal;\n\t#endif\n\t#include \"litUserMainEndVS\"\n\treturn output;\n}\n`;\n\nvar litOtherMainPS = `\n#ifdef PICK_PASS\n\t#include \"pickPS\"\n#endif\n#ifdef PREPASS_PASS\n\t#include \"floatAsUintPS\"\n#endif\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\t#include \"litUserMainStartPS\"\n\tvar output: FragmentOutput;\n\t\n\tevaluateFrontend();\n\t#ifdef PICK_PASS\n\t\toutput.color = getPickOutput();\n\t#endif\n\t#ifdef PREPASS_PASS\n\t\toutput.color = float2vec4(vLinearDepth);\n\t#endif\n\t#include \"litUserMainEndPS\"\n\treturn output;\n}\n`;\n\nvar litShaderArgsPS = `\nvar<private> litArgs_albedo: vec3f;\nvar<private> litArgs_opacity: f32;\nvar<private> litArgs_emission: vec3f;\nvar<private> litArgs_worldNormal: vec3f;\nvar<private> litArgs_ao: f32;\nvar<private> litArgs_lightmap: vec3f;\nvar<private> litArgs_lightmapDir: vec3f;\nvar<private> litArgs_metalness: f32;\nvar<private> litArgs_specularity: vec3f;\nvar<private> litArgs_specularityFactor: f32;\nvar<private> litArgs_gloss: f32;\nvar<private> litArgs_sheen_gloss: f32;\nvar<private> litArgs_sheen_specularity: vec3f;\nvar<private> litArgs_transmission: f32;\nvar<private> litArgs_thickness: f32;\nvar<private> litArgs_ior: f32;\nvar<private> litArgs_dispersion: f32;\nvar<private> litArgs_iridescence_intensity: f32;\nvar<private> litArgs_iridescence_thickness: f32;\nvar<private> litArgs_clearcoat_worldNormal: vec3f;\nvar<private> litArgs_clearcoat_specularity: f32;\nvar<private> litArgs_clearcoat_gloss: f32;\n`;\n\nvar litShaderCorePS = `\n\t#if LIT_NONE_SLICE_MODE == TILED\n\t\tvar<private> textureBias: f32 = -1000.0;\n\t#else\n\t\tuniform textureBias: f32;\n\t#endif\n\t#include \"litShaderArgsPS\"\n`;\n\nvar litShadowMainPS = `\n#if LIGHT_TYPE != DIRECTIONAL\n\tuniform view_position: vec3f;\n\tuniform light_radius: f32;\n#endif\n#if SHADOW_TYPE == PCSS_32F\n\t#include \"linearizeDepthPS\"\n#endif\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\t#include \"litUserMainStartPS\"\n\tvar output: FragmentOutput;\n\tevaluateFrontend();\n\t#ifdef PERSPECTIVE_DEPTH\n\t\tvar depth: f32 = input.position.z;\n\t\t#if SHADOW_TYPE == PCSS_32F\n\t\t\t#if LIGHT_TYPE != DIRECTIONAL\n\t\t\t\tdepth = linearizeDepthWithParams(depth, camera_params);\n\t\t\t#endif\n\t\t#endif\n\t#else\n\t\tvar depth: f32 = min(distance(uniform.view_position, input.vPositionW) / uniform.light_radius, 0.99999);\n\t\t#define MODIFIED_DEPTH\n\t#endif\n\t#if SHADOW_TYPE == VSM_16F || SHADOW_TYPE == VSM_32F\n\t\t#if SHADOW_TYPE == VSM_32F\n\t\t\tvar exponent: f32 = 15.0;\n\t\t#else\n\t\t\tvar exponent: f32 = 5.54;\n\t\t#endif\n\t\tvar depth_vsm = 2.0 * depth - 1.0;\n\t\tdepth_vsm = exp(exponent * depth_vsm);\n\t\toutput.color = vec4f(depth_vsm, depth_vsm * depth_vsm, 1.0, 1.0);\n\t#else\n\t\t#if SHADOW_TYPE == PCSS_32F\n\t\t\toutput.color = vec4f(depth, 0.0, 0.0, 1.0);\n\t\t#else\n\t\t\t#ifdef MODIFIED_DEPTH\n\t\t\t\toutput.fragDepth = depth;\n\t\t\t#endif\n\t\t\toutput.color = vec4f(1.0);\n\t\t#endif\n\t#endif\n\t#include \"litUserMainEndPS\"\n\t\n\treturn output;\n}\n`;\n\nvar ltcPS = `\nfn LTC_Uv(N: vec3f, V: vec3f, roughness: f32) -> vec2f {\n\tconst LUT_SIZE: f32 = 64.0;\n\tconst LUT_SCALE: f32 = (LUT_SIZE - 1.0) / LUT_SIZE;\n\tconst LUT_BIAS: f32 = 0.5 / LUT_SIZE;\n\tlet dotNV: f32 = saturate(dot( N, V ));\n\tlet uv: vec2f = vec2f( roughness, sqrt( 1.0 - dotNV ) );\n\treturn uv * LUT_SCALE + LUT_BIAS;\n}\nfn LTC_ClippedSphereFormFactor( f: vec3f ) -> f32 {\n\tlet l: f32 = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nfn LTC_EdgeVectorFormFactor( v1: vec3f, v2: vec3f ) -> vec3f {\n\tlet x: f32 = dot( v1, v2 );\n\tlet y: f32 = abs( x );\n\tlet a: f32 = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tlet b: f32 = 3.4175940 + ( 4.1616724 + y ) * y;\n\tlet v: f32 = a / b;\n\tlet inv_sqrt_term = inverseSqrt( max( 1.0 - x * x, 1e-7f ) );\n\tlet theta_sintheta: f32 = select( (0.5 * inv_sqrt_term - v), v, x > 0.0 );\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nstruct Coords {\n\tcoord0: vec3f,\n\tcoord1: vec3f,\n\tcoord2: vec3f,\n\tcoord3: vec3f,\n}\nfn LTC_EvaluateRect( N: vec3f, V: vec3f, P: vec3f, mInv: mat3x3f, rectCoords: Coords) -> f32 {\n\tlet v1: vec3f = rectCoords.coord1 - rectCoords.coord0;\n\tlet v2: vec3f = rectCoords.coord3 - rectCoords.coord0;\n\tlet lightNormal: vec3f = cross( v1, v2 );\n\tlet factor: f32 = sign(-dot( lightNormal, P - rectCoords.coord0 ));\n\tlet T1: vec3f = normalize( V - N * dot( V, N ) );\n\tlet T2: vec3f = factor * cross( N, T1 );\n\tlet mat: mat3x3f = mInv * transpose( mat3x3f( T1, T2, N ) );\n\tvar coords: array<vec3f, 4>;\n\tcoords[0] = mat * ( rectCoords.coord0 - P );\n\tcoords[1] = mat * ( rectCoords.coord1 - P );\n\tcoords[2] = mat * ( rectCoords.coord2 - P );\n\tcoords[3] = mat * ( rectCoords.coord3 - P );\n\tcoords[0] = normalize( coords[0] );\n\tcoords[1] = normalize( coords[1] );\n\tcoords[2] = normalize( coords[2] );\n\tcoords[3] = normalize( coords[3] );\n\tvar vectorFormFactor: vec3f = vec3f( 0.0 );\n\tvectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[0], coords[1] );\n\tvectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[1], coords[2] );\n\tvectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[2], coords[3] );\n\tvectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[3], coords[0] );\n\tlet result: f32 = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn result;\n}\nvar<private> dLTCCoords: Coords;\nfn getLTCLightCoords(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) -> Coords {\n\tvar coords: Coords;\n\tcoords.coord0 = lightPos + halfWidth - halfHeight;\n\tcoords.coord1 = lightPos - halfWidth - halfHeight;\n\tcoords.coord2 = lightPos - halfWidth + halfHeight;\n\tcoords.coord3 = lightPos + halfWidth + halfHeight;\n\treturn coords;\n}\nvar<private> dSphereRadius: f32;\nfn getSphereLightCoords(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) -> Coords {\n\tdSphereRadius = max(length(halfWidth), length(halfHeight));\n\tlet f: vec3f = reflect(normalize(lightPos - uniform.view_position), vNormalW);\n\tlet w: vec3f = normalize(cross(f, halfHeight));\n\tlet h: vec3f = normalize(cross(f, w));\n\treturn getLTCLightCoords(lightPos, w * dSphereRadius, h * dSphereRadius);\n}\nvar<private> dLTCUV: vec2f;\n#ifdef LIT_CLEARCOAT\n\tvar<private> ccLTCUV: vec2f;\n#endif\nfn getLTCLightUV(gloss: f32, worldNormal: vec3f, viewDir: vec3f) -> vec2f {\n\tlet roughness: f32 = max((1.0 - gloss) * (1.0 - gloss), 0.001);\n\treturn LTC_Uv( worldNormal, viewDir, roughness );\n}\nvar<private> dLTCSpecFres: vec3f;\n#ifdef LIT_CLEARCOAT\n\tvar<private> ccLTCSpecFres: vec3f;\n#endif\nfn getLTCLightSpecFres(uv: vec2f, specularity: vec3f) -> vec3f {\n\tlet t2: vec4f = textureSampleLevel(areaLightsLutTex2, areaLightsLutTex2Sampler, uv, 0.0);\n\treturn specularity * t2.x + ( vec3f( 1.0 ) - specularity) * t2.y;\n}\nfn calcLTCLightValues(gloss: f32, worldNormal: vec3f, viewDir: vec3f, specularity: vec3f, clearcoatGloss: f32, clearcoatWorldNormal: vec3f, clearcoatSpecularity: f32) {\n\tdLTCUV = getLTCLightUV(gloss, worldNormal, viewDir);\n\tdLTCSpecFres = getLTCLightSpecFres(dLTCUV, specularity);\n\t#ifdef LIT_CLEARCOAT\n\t\tccLTCUV = getLTCLightUV(clearcoatGloss, clearcoatWorldNormal, viewDir);\n\t\tccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3f(clearcoatSpecularity));\n\t#endif\n}\nfn calcRectLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) {\n\tdLTCCoords = getLTCLightCoords(lightPos, halfWidth, halfHeight);\n}\nfn calcDiskLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) {\n\tcalcRectLightValues(lightPos, halfWidth, halfHeight);\n}\nfn calcSphereLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) {\n\tdLTCCoords = getSphereLightCoords(lightPos, halfWidth, halfHeight);\n}\nfn SolveCubic(Coefficient_in: vec4f) -> vec3f {\n\tlet pi: f32 = 3.14159;\n\tvar Coefficient = Coefficient_in;\n\tCoefficient = vec4f(Coefficient.xyz / Coefficient.w, Coefficient.w);\n\tlet new_yz: vec2f = Coefficient.yz / 3.0;\n\tCoefficient = vec4f(Coefficient.x, new_yz.x, new_yz.y, Coefficient.w);\n\t\n\tlet A: f32 = Coefficient.w;\n\tlet B: f32 = Coefficient.z;\n\tlet C: f32 = Coefficient.y;\n\tlet D: f32 = Coefficient.x;\n\tlet Delta: vec3f = vec3f(\n\t\t-Coefficient.z * Coefficient.z + Coefficient.y,\n\t\t-Coefficient.y * Coefficient.z + Coefficient.x,\n\t\tdot(vec2f(Coefficient.z, -Coefficient.y), Coefficient.xy)\n\t);\n\tlet Discriminant: f32 = dot(vec2f(4.0 * Delta.x, -Delta.y), Delta.zy);\n\tvar xlc: vec2f;\n\tvar xsc: vec2f;\n\t{\n\t\tlet A_a: f32 = 1.0;\n\t\tlet C_a: f32 = Delta.x;\n\t\tlet D_a: f32 = -2.0 * B * Delta.x + Delta.y;\n\t\tlet Theta: f32 = atan2(sqrt(Discriminant), -D_a) / 3.0;\n\t\tlet sqrt_neg_Ca = sqrt(-C_a);\n\t\tlet x_1a: f32 = 2.0 * sqrt_neg_Ca * cos(Theta);\n\t\tlet x_3a: f32 = 2.0 * sqrt_neg_Ca * cos(Theta + (2.0 / 3.0) * pi);\n\t\tlet xl: f32 = select(x_3a, x_1a, (x_1a + x_3a) > 2.0 * B);\n\t\txlc = vec2f(xl - B, A);\n\t}\n\t{\n\t\tlet A_d: f32 = D;\n\t\tlet C_d: f32 = Delta.z;\n\t\tlet D_d: f32 = -D * Delta.y + 2.0 * C * Delta.z;\n\t\tlet Theta: f32 = atan2(D * sqrt(Discriminant), -D_d) / 3.0;\n\t\tlet sqrt_neg_Cd = sqrt(-C_d);\n\t\tlet x_1d: f32 = 2.0 * sqrt_neg_Cd * cos(Theta);\n\t\tlet x_3d: f32 = 2.0 * sqrt_neg_Cd * cos(Theta + (2.0 / 3.0) * pi);\n\t\tlet xs: f32 = select(x_3d, x_1d, x_1d + x_3d < 2.0 * C);\n\t\txsc = vec2f(-D, xs + C);\n\t}\n\tlet E: f32 =  xlc.y * xsc.y;\n\tlet F: f32 = -xlc.x * xsc.y - xlc.y * xsc.x;\n\tlet G: f32 =  xlc.x * xsc.x;\n\tlet xmc: vec2f = vec2f(C * F - B * G, -B * F + C * E);\n\tvar Root: vec3f = vec3f(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y);\n\tif (Root.x < Root.y && Root.x < Root.z) {\n\t\tRoot = Root.yxz;\n\t} else if (Root.z < Root.x && Root.z < Root.y) {\n\t\tRoot = Root.xzy;\n\t}\n\treturn Root;\n}\nfn LTC_EvaluateDisk(N: vec3f, V: vec3f, P: vec3f, Minv: mat3x3f, points: Coords) -> f32 {\n\tlet T1: vec3f = normalize(V - N * dot(V, N));\n\tlet T2: vec3f = cross(N, T1);\n\tlet R: mat3x3f = transpose( mat3x3f( T1, T2, N ) );\n\tvar L_: array<vec3f, 3>;\n\tL_[0] = R * ( points.coord0 - P );\n\tL_[1] = R * ( points.coord1 - P );\n\tL_[2] = R * ( points.coord2 - P );\n\tlet C: vec3f  = 0.5 * (L_[0] + L_[2]);\n\tvar V1: vec3f = 0.5 * (L_[1] - L_[2]);\n\tvar V2: vec3f = 0.5 * (L_[1] - L_[0]);\n\tlet C_Minv: vec3f  = Minv * C;\n\tlet V1_Minv: vec3f = Minv * V1;\n\tlet V2_Minv: vec3f = Minv * V2;\n\tvar a: f32;\n\tvar b: f32;\n\tlet d11: f32 = dot(V1_Minv, V1_Minv);\n\tlet d22: f32 = dot(V2_Minv, V2_Minv);\n\tlet d12: f32 = dot(V1_Minv, V2_Minv);\n\tif (abs(d12) / sqrt(d11 * d22) > 0.0001) {\n\t\tlet tr: f32 = d11 + d22;\n\t\tlet det_inner: f32 = -d12 * d12 + d11 * d22;\n\t\tlet det: f32 = sqrt(det_inner);\n\t\tlet u: f32 = 0.5 * sqrt(tr - 2.0 * det);\n\t\tlet v: f32 = 0.5 * sqrt(tr + 2.0 * det);\n\t\tlet e_max: f32 = (u + v) * (u + v);\n\t\tlet e_min: f32 = (u - v) * (u - v);\n\t\tvar V1_: vec3f;\n\t\tvar V2_: vec3f;\n\t\tif (d11 > d22) {\n\t\t\tV1_ = d12 * V1_Minv + (e_max - d11) * V2_Minv;\n\t\t\tV2_ = d12 * V1_Minv + (e_min - d11) * V2_Minv;\n\t\t} else {\n\t\t\tV1_ = d12*V2_Minv + (e_max - d22)*V1_Minv;\n\t\t\tV2_ = d12*V2_Minv + (e_min - d22)*V1_Minv;\n\t\t}\n\t\ta = 1.0 / e_max;\n\t\tb = 1.0 / e_min;\n\t\tV1 = normalize(V1_);\n\t\tV2 = normalize(V2_);\n\t} else {\n\t\ta = 1.0 / dot(V1_Minv, V1_Minv);\n\t\tb = 1.0 / dot(V2_Minv, V2_Minv);\n\t\tV1 = V1_Minv * sqrt(a);\n\t\tV2 = V2_Minv * sqrt(b);\n\t}\n\tvar V3: vec3f = normalize(cross(V1, V2));\n\tif (dot(C_Minv, V3) < 0.0) {\n\t\tV3 = V3 * -1.0;\n\t}\n\tlet L: f32  = dot(V3, C_Minv);\n\tlet x0: f32 = dot(V1, C_Minv) / L;\n\tlet y0: f32 = dot(V2, C_Minv) / L;\n\tlet E1: f32 = inverseSqrt(a);\n\tlet E2: f32 = inverseSqrt(b);\n\tlet a_scaled = a * L * L;\n\tlet b_scaled = b * L * L;\n\tlet c0: f32 = a_scaled * b_scaled;\n\tlet c1: f32 = a_scaled * b_scaled * (1.0 + x0 * x0 + y0 * y0) - a_scaled - b_scaled;\n\tlet c2: f32 = 1.0 - a_scaled * (1.0 + x0 * x0) - b_scaled * (1.0 + y0 * y0);\n\tlet c3: f32 = 1.0;\n\tlet roots: vec3f = SolveCubic(vec4f(c0, c1, c2, c3));\n\tlet e1: f32 = roots.x;\n\tlet e2: f32 = roots.y;\n\tlet e3: f32 = roots.z;\n\tvar avgDir: vec3f = vec3f(a_scaled * x0 / (a_scaled - e2), b_scaled * y0 / (b_scaled - e2), 1.0);\n\tlet rotate: mat3x3f = mat3x3f(V1, V2, V3);\n\tavgDir = rotate * avgDir;\n\tavgDir = normalize(avgDir);\n\tlet L1: f32 = sqrt(-e2 / e3);\n\tlet L2: f32 = sqrt(-e2 / e1);\n\tlet formFactor: f32 = max(0.0, L1 * L2 * inverseSqrt((1.0 + L1 * L1) * (1.0 + L2 * L2)));\n\tconst LUT_SIZE_disk: f32 = 64.0;\n\tconst LUT_SCALE_disk: f32 = ( LUT_SIZE_disk - 1.0 ) / LUT_SIZE_disk;\n\tconst LUT_BIAS_disk: f32 = 0.5 / LUT_SIZE_disk;\n\tvar uv: vec2f = vec2f(avgDir.z * 0.5 + 0.5, formFactor);\n\tuv = uv * LUT_SCALE_disk + LUT_BIAS_disk;\n\tlet scale: f32 = textureSampleLevel(areaLightsLutTex2, areaLightsLutTex2Sampler, uv, 0.0).w;\n\treturn formFactor * scale;\n}\nfn FixNan(value: f32) -> f32 {\n\treturn select(value, 0.0, value != value);\n}\nfn getRectLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 {\n\tlet identityMat = mat3x3f(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0));\n\treturn LTC_EvaluateRect( worldNormal, viewDir, vPositionW, identityMat, dLTCCoords );\n}\nfn getDiskLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 {\n\tlet identityMat = mat3x3f(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0));\n\treturn FixNan(LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, identityMat, dLTCCoords ));\n}\nfn getSphereLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 {\n\tlet falloff: f32 = dSphereRadius / (dot(lightDir, lightDir) + dSphereRadius);\n\treturn FixNan(getLightDiffuse(worldNormal, viewDir, lightDirNorm) * falloff);\n}\nfn getLTCLightInvMat(uv: vec2f) -> mat3x3f {\n\tlet t1: vec4f = textureSampleLevel(areaLightsLutTex1, areaLightsLutTex1Sampler, uv, 0.0);\n\treturn mat3x3f(\n\t\tvec3f( t1.x, 0.0, t1.y ),\n\t\tvec3f( 0.0, 1.0, 0.0 ),\n\t\tvec3f( t1.z, 0.0, t1.w )\n\t);\n}\nfn calcRectLightSpecular(worldNormal: vec3f, viewDir: vec3f, uv: vec2f) -> f32 {\n\tlet mInv: mat3x3f = getLTCLightInvMat(uv);\n\treturn LTC_EvaluateRect( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );\n}\nfn getRectLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 {\n\treturn calcRectLightSpecular(worldNormal, viewDir, dLTCUV);\n}\nfn calcDiskLightSpecular(worldNormal: vec3f, viewDir: vec3f, uv: vec2f) -> f32 {\n\tlet mInv: mat3x3f = getLTCLightInvMat(uv);\n\treturn LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, mInv, dLTCCoords );\n}\nfn getDiskLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 {\n\treturn calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);\n}\nfn getSphereLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 {\n\treturn calcDiskLightSpecular(worldNormal, viewDir, dLTCUV);\n}\n`;\n\nvar metalnessPS = `\n#ifdef STD_METALNESS_CONSTANT\nuniform material_metalness: f32;\n#endif\nfn getMetalness() {\n\tvar metalness: f32 = 1.0;\n\t#ifdef STD_METALNESS_CONSTANT\n\t\tmetalness = metalness * uniform.material_metalness;\n\t#endif\n\t#ifdef STD_METALNESS_TEXTURE\n\t\tmetalness = metalness * textureSampleBias({STD_METALNESS_TEXTURE_NAME}, {STD_METALNESS_TEXTURE_NAME}Sampler, {STD_METALNESS_TEXTURE_UV}, uniform.textureBias).{STD_METALNESS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_METALNESS_VERTEX\n\tmetalness = metalness * saturate(vVertexColor.{STD_METALNESS_VERTEX_CHANNEL});\n\t#endif\n\tdMetalness = metalness;\n}\n`;\n\nvar msdfPS = `\nvar texture_msdfMap: texture_2d<f32>;\nvar texture_msdfMapSampler: sampler;\nfn median(r: f32, g: f32, b: f32) -> f32 {\n\treturn max(min(r, g), min(max(r, g), b));\n}\nfn map(min: f32, max: f32, v: f32) -> f32 {\n\treturn (v - min) / (max - min);\n}\nuniform font_sdfIntensity: f32;\nuniform font_pxrange: f32;\nuniform font_textureWidth: f32;\n#ifndef LIT_MSDF_TEXT_ATTRIBUTE\n\tuniform outline_color: vec4f;\n\tuniform outline_thickness: f32;\n\tuniform shadow_color: vec4f;\n\tuniform shadow_offset: vec2f;\n#else\n\tvarying outline_color: vec4f;\n\tvarying outline_thickness: f32;\n\tvarying shadow_color: vec4f;\n\tvarying shadow_offset: vec2f;\n#endif\nfn applyMsdf(color_in: vec4f) -> vec4f {\n\t#ifndef LIT_MSDF_TEXT_ATTRIBUTE\n\t\tvar outline_colorValue = uniform.outline_color;\n\t\tvar outline_thicknessValue = uniform.outline_thickness;\n\t\tvar shadow_colorValue = uniform.shadow_color;\n\t\tvar shadow_offsetValue = uniform.shadow_offset;\n\t#else\n\t\tvar outline_colorValue = outline_color;\n\t\tvar outline_thicknessValue = outline_thickness;\n\t\tvar shadow_colorValue = shadow_color;\n\t\tvar shadow_offsetValue = shadow_offset;\n\t#endif\n\tvar color = vec4f(gammaCorrectInputVec3(color_in.rgb), color_in.a);\n\tlet tsample: vec3f = textureSample(texture_msdfMap, texture_msdfMapSampler, vUv0).rgb;\n\tlet uvShdw: vec2f = vUv0 - shadow_offsetValue;\n\tlet ssample: vec3f = textureSample(texture_msdfMap, texture_msdfMapSampler, uvShdw).rgb;\n\tlet sigDist: f32 = median(tsample.r, tsample.g, tsample.b);\n\tvar sigDistShdw: f32 = median(ssample.r, ssample.g, ssample.b);\n\tlet smoothingMax: f32 = 0.2;\n\tlet w: vec2f = abs(dpdx(vUv0)) + abs(dpdy(vUv0));\n\tlet smoothing: f32 = clamp(w.x * uniform.font_textureWidth / uniform.font_pxrange, 0.0, smoothingMax);\n\tlet mapMin: f32 = 0.05;\n\tlet mapMax: f32 = clamp(1.0 - uniform.font_sdfIntensity, mapMin, 1.0);\n\tlet sigDistInner: f32 = map(mapMin, mapMax, sigDist);\n\tlet sigDistOutline: f32 = map(mapMin, mapMax, sigDist + outline_thicknessValue);\n\tsigDistShdw = map(mapMin, mapMax, sigDistShdw + outline_thicknessValue);\n\tlet center: f32 = 0.5;\n\tlet inside: f32 = smoothstep(center - smoothing, center + smoothing, sigDistInner);\n\tlet outline: f32 = smoothstep(center - smoothing, center + smoothing, sigDistOutline);\n\tlet shadow: f32 = smoothstep(center - smoothing, center + smoothing, sigDistShdw);\n\tlet tcolor_outline: vec4f = outline * vec4f(outline_colorValue.a * outline_colorValue.rgb, outline_colorValue.a);\n\tvar tcolor: vec4f = select(vec4f(0.0), tcolor_outline, outline > inside);\n\ttcolor = mix(tcolor, color, inside);\n\tlet scolor_shadow: vec4f = shadow * vec4f(shadow_colorValue.a * shadow_colorValue.rgb, shadow_colorValue.a);\n\tlet scolor: vec4f = select(tcolor, scolor_shadow, shadow > outline);\n\ttcolor = mix(scolor, tcolor, outline);\n\ttcolor = vec4f(gammaCorrectOutput(tcolor.rgb), tcolor.a);\n\treturn tcolor;\n}\n`;\n\nvar metalnessModulatePS = `\nfn getSpecularModulate(specularity: vec3f, albedo: vec3f, metalness: f32, f0: f32) -> vec3f {\n\tlet dielectricF0: vec3f = f0 * specularity;\n\treturn mix(dielectricF0, albedo, metalness);\n}\nfn getAlbedoModulate(albedo: vec3f, metalness: f32) -> vec3f {\n\treturn albedo * (1.0 - metalness);\n}\n`;\n\nvar morphPS = `\n\tvarying uv0: vec2f;\n\tvar morphTexture: texture_2d_array<f32>;\n\tuniform morphFactor: array<f32, {MORPH_TEXTURE_MAX_COUNT}>;\n\tuniform morphIndex: array<u32, {MORPH_TEXTURE_MAX_COUNT}>;\n\tuniform count: u32;\n\t@fragment\n\tfn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\tvar color = vec3f(0, 0, 0);\n\t\tlet textureDims = textureDimensions(morphTexture);\n\t\tlet pixelCoords = vec2i(input.uv0 * vec2f(textureDims));\n\t\t\n\t\tfor (var i: u32 = 0; i < uniform.count; i = i + 1) {\n\t\t\tvar textureIndex: u32 = uniform.morphIndex[i].element;\n\t\t\tvar delta = textureLoad(morphTexture, pixelCoords, textureIndex, 0).xyz;\n\t\t\tcolor += uniform.morphFactor[i].element * delta;\n\t\t}\n\t\tvar output: FragmentOutput;\n\t\toutput.color = vec4f(color, 1.0);\n\t\treturn output;\n\t}\n`;\n\nvar morphVS = `\n\tattribute vertex_position: vec2f;\n\tvarying uv0: vec2f;\n\t@vertex\n\tfn vertexMain(input: VertexInput) -> VertexOutput {\n\t\tvar output: VertexOutput;\n\t\toutput.position = vec4f(input.vertex_position, 0.5, 1.0);\n\t\toutput.uv0 = input.vertex_position * 0.5 + vec2f(0.5, 0.5);\n\t\treturn output;\n\t}\n`;\n\nvar msdfVS = `\nattribute vertex_outlineParameters: vec3f;\nattribute vertex_shadowParameters: vec3f;\nvarying outline_color: vec4f;\nvarying outline_thickness: f32;\nvarying shadow_color: vec4f;\nvarying shadow_offset: vec2f;\nvar<private> dOutlineColor: vec4f;\nvar<private> dOutlineThickness: f32;\nvar<private> dShadowColor: vec4f;\nvar<private> dShadowOffset: vec2f;\nfn unpackMsdfParams() {\n\tlet little: vec3f = vertex_outlineParameters % vec3f(256.0);\n\tlet big: vec3f = (vertex_outlineParameters - little) / 256.0;\n\tdOutlineColor = vec4f(little.x, big.x, little.y, big.y) / 255.0;\n\tdOutlineThickness = little.z / 255.0 * 0.2;\n\tlet little_shadow = vertex_shadowParameters % vec3f(256.0);\n\tlet big_shadow = (vertex_shadowParameters - little_shadow) / 256.0;\n\tdShadowColor = vec4f(little_shadow.x, big_shadow.x, little_shadow.y, big_shadow.y) / 255.0;\n\tdShadowOffset = (vec2f(little_shadow.z, big_shadow.z) / 127.0 - 1.0) * 0.005;\n}\n`;\n\nvar normalVS = `\nvar<private> dNormalMatrix: mat3x3f;\nfn getNormal() -> vec3f {\n\tdNormalMatrix = getNormalMatrix(dModelMatrix);\n\tlet localNormal: vec3f = getLocalNormal(vertex_normal);\n\treturn normalize(dNormalMatrix * localNormal);\n}`;\n\nvar normalCoreVS = `\nattribute vertex_normal: vec3f;\nuniform matrix_normal: mat3x3f;\n#ifdef MORPHING_NORMAL\n\t#ifdef MORPHING_INT\n\t\tvar morphNormalTex: texture_2d<u32>;\n\t\tvar morphNormalTexSampler: sampler;\n\t#else\n\t\tvar morphNormalTex: texture_2d<f32>;\n\t\tvar morphNormalTexSampler: sampler;\n\t#endif\n#endif\nfn getLocalNormal(vertexNormal: vec3f) -> vec3f {\n\tvar localNormal: vec3f = vertexNormal;\n\t#ifdef MORPHING_NORMAL\n\t\tlet morphUV: vec2i = getTextureMorphCoords();\n\t\t#ifdef MORPHING_INT\n\t\t\tlet morphNormalInt: vec4u = textureLoad(morphNormalTex, morphUV, 0);\n\t\t\tlet morphNormalF: vec3f = vec3f(morphNormalInt.xyz) / 65535.0 * 2.0 - 1.0;\n\t\t\tlocalNormal = localNormal + morphNormalF;\n\t\t#else\n\t\t\tlet morphNormal: vec3f = textureLoad(morphNormalTex, morphUV, 0).xyz;\n\t\t\tlocalNormal = localNormal + morphNormal;\n\t\t#endif\n\t#endif\n\treturn localNormal;\n}\n#if defined(SKIN) || defined(BATCH)\n\tfn getNormalMatrix(modelMatrix: mat4x4f) -> mat3x3f {\n\t\treturn mat3x3f(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);\n\t}\n#elif defined(INSTANCING)\n\tfn getNormalMatrix(modelMatrix: mat4x4f) -> mat3x3f {\n\t\treturn mat3x3f(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);\n\t}\n#else\n\tfn getNormalMatrix(modelMatrix: mat4x4f) -> mat3x3f {\n\t\treturn uniform.matrix_normal;\n\t}\n#endif\n`;\n\nvar normalMapPS = `\n#ifdef STD_NORMAL_TEXTURE\n\tuniform material_bumpiness: f32;\n#endif\n#ifdef STD_NORMALDETAIL_TEXTURE\n\tuniform material_normalDetailMapBumpiness: f32;\n\tfn blendNormals(inN1: vec3f, inN2: vec3f) -> vec3f {\n\t\tlet n1: vec3f = inN1 + vec3f(0.0, 0.0, 1.0);\n\t\tlet n2: vec3f = inN2 * vec3f(-1.0, -1.0, 1.0);\n\t\treturn n1 * dot(n1, n2) / n1.z - n2;\n\t}\n#endif\nfn getNormal() {\n#ifdef STD_NORMAL_TEXTURE\n\tvar normalMap: vec3f = {STD_NORMAL_TEXTURE_DECODE}(textureSampleBias({STD_NORMAL_TEXTURE_NAME}, {STD_NORMAL_TEXTURE_NAME}Sampler, {STD_NORMAL_TEXTURE_UV}, uniform.textureBias));\n\tnormalMap = mix(vec3f(0.0, 0.0, 1.0), normalMap, uniform.material_bumpiness);\n\t#ifdef STD_NORMALDETAIL_TEXTURE\n\t\tvar normalDetailMap: vec3f = {STD_NORMALDETAIL_TEXTURE_DECODE}(textureSampleBias({STD_NORMALDETAIL_TEXTURE_NAME}, {STD_NORMALDETAIL_TEXTURE_NAME}Sampler, {STD_NORMALDETAIL_TEXTURE_UV}, uniform.textureBias));\n\t\tnormalDetailMap = mix(vec3f(0.0, 0.0, 1.0), normalDetailMap, uniform.material_normalDetailMapBumpiness);\n\t\tnormalMap = blendNormals(normalMap, normalDetailMap);\n\t#endif\n\tdNormalW = normalize(dTBN * normalMap);\n#else\n\tdNormalW = dVertexNormalW;\n#endif\n}\n`;\n\nvar opacityPS = `\nuniform material_opacity: f32;\nfn getOpacity() {\n\tdAlpha = uniform.material_opacity;\n\t#ifdef STD_OPACITY_TEXTURE\n\tdAlpha = dAlpha * textureSampleBias({STD_OPACITY_TEXTURE_NAME}, {STD_OPACITY_TEXTURE_NAME}Sampler, {STD_OPACITY_TEXTURE_UV}, uniform.textureBias).{STD_OPACITY_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_OPACITY_VERTEX\n\tdAlpha = dAlpha * clamp(vVertexColor.{STD_OPACITY_VERTEX_CHANNEL}, 0.0, 1.0);\n\t#endif\n}\n`;\n\nvar opacityDitherPS = `\n#if STD_OPACITY_DITHER == BAYER8\n\t#include \"bayerPS\"\n#endif\nuniform blueNoiseJitter: vec4f;\n#if STD_OPACITY_DITHER == BLUENOISE\n\tvar blueNoiseTex32 : texture_2d<f32>;\n\tvar blueNoiseTex32Sampler : sampler;\n#endif\nfn opacityDither(alpha: f32, id: f32) {\n\t#if STD_OPACITY_DITHER == BAYER8\n\t\tvar noise: f32 = bayer8(floor((pcPosition.xy + uniform.blueNoiseJitter.xy + id) % vec2f(8.0))) / 64.0;\n\t#else\n\t\t#if STD_OPACITY_DITHER == BLUENOISE\n\t\t\tvar uv = fract(pcPosition.xy / 32.0 + uniform.blueNoiseJitter.xy + id);\n\t\t\tvar noise: f32 = textureSampleLevel(blueNoiseTex32, blueNoiseTex32Sampler, uv, 0.0).y;\n\t\t#endif\n\t\t#if STD_OPACITY_DITHER == IGNNOISE\n\t\t\tvar magic = vec3f(0.06711056, 0.00583715, 52.9829189);\n\t\t\tvar noise: f32 = fract(magic.z * fract(dot(pcPosition.xy + uniform.blueNoiseJitter.xy + id, magic.xy)));\n\t\t#endif\n\t#endif\n\tnoise = pow(noise, 2.2);\n\tif (alpha < noise) {\n\t\tdiscard;\n\t}\n}\n`;\n\nvar outputPS = `\n`;\n\nvar outputAlphaPS = `\n#if LIT_BLEND_TYPE == NORMAL || LIT_BLEND_TYPE == ADDITIVEALPHA || defined(LIT_ALPHA_TO_COVERAGE)\n\toutput.color = vec4f(output.color.rgb, litArgs_opacity);\n#elif LIT_BLEND_TYPE == PREMULTIPLIED\n\toutput.color = vec4f(output.color.rgb * litArgs_opacity, litArgs_opacity);\n#else\n\toutput.color = vec4f(output.color.rgb, 1.0);\n#endif\n`;\n\nvar outputTex2DPS = `\nvarying vUv0: vec2f;\nvar source: texture_2d<f32>;\nvar sourceSampler: sampler;\n@fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\toutput.color = textureSample(source, sourceSampler, input.vUv0);\n\treturn output;\n}\n`;\n\nvar sheenPS = `\nuniform material_sheen: vec3f;\nfn getSheen() {\n\tvar sheenColor = uniform.material_sheen;\n\t#ifdef STD_SHEEN_TEXTURE\n\tsheenColor = sheenColor * {STD_SHEEN_TEXTURE_DECODE}(textureSampleBias({STD_SHEEN_TEXTURE_NAME}, {STD_SHEEN_TEXTURE_NAME}Sampler, {STD_SHEEN_TEXTURE_UV}, uniform.textureBias)).{STD_SHEEN_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SHEEN_VERTEX\n\tsheenColor = sheenColor * saturate3(vVertexColor.{STD_SHEEN_VERTEX_CHANNEL});\n\t#endif\n\tsSpecularity = sheenColor;\n}\n`;\n\nvar sheenGlossPS = `\nuniform material_sheenGloss: f32;\nfn getSheenGlossiness() {\n\tvar sheenGlossiness = uniform.material_sheenGloss;\n\t#ifdef STD_SHEENGLOSS_TEXTURE\n\tsheenGlossiness = sheenGlossiness * textureSampleBias({STD_SHEENGLOSS_TEXTURE_NAME}, {STD_SHEENGLOSS_TEXTURE_NAME}Sampler, {STD_SHEENGLOSS_TEXTURE_UV}, uniform.textureBias).{STD_SHEENGLOSS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SHEENGLOSS_VERTEX\n\tsheenGlossiness = sheenGlossiness * saturate(vVertexColor.{STD_SHEENGLOSS_VERTEX_CHANNEL});\n\t#endif\n\t#ifdef STD_SHEENGLOSS_INVERT\n\tsheenGlossiness = 1.0 - sheenGlossiness;\n\t#endif\n\tsGlossiness = sheenGlossiness + 0.0000001;\n}\n`;\n\nvar parallaxPS = `\nuniform material_heightMapFactor: f32;\nfn getParallax() {\n\tvar parallaxScale = uniform.material_heightMapFactor;\n\tvar height: f32 = textureSampleBias({STD_HEIGHT_TEXTURE_NAME}, {STD_HEIGHT_TEXTURE_NAME}Sampler, {STD_HEIGHT_TEXTURE_UV}, uniform.textureBias).{STD_HEIGHT_TEXTURE_CHANNEL};\n\theight = height * parallaxScale - parallaxScale * 0.5;\n\tvar viewDirT: vec3f = dViewDirW * dTBN;\n\tviewDirT.z = viewDirT.z + 0.42;\n\tdUvOffset = height * (viewDirT.xy / viewDirT.z);\n}\n`;\n\nvar pickPS = `\nuniform meshInstanceId: u32;\nfn getPickOutput() -> vec4f {\n\tlet inv: vec4f = vec4f(1.0 / 255.0);\n\tlet shifts: vec4u = vec4u(16u, 8u, 0u, 24u);\n\tlet col: vec4u = (vec4u(uniform.meshInstanceId) >> shifts) & vec4u(0xffu);\n\treturn vec4f(col) * inv;\n}`;\n\nvar reflDirPS = `\nfn getReflDir(worldNormal: vec3f, viewDir: vec3f, gloss: f32, tbn: mat3x3f) {\n\tdReflDirW = normalize(-reflect(viewDir, worldNormal));\n}\n`;\n\nvar reflDirAnisoPS = `\nfn getReflDir(worldNormal: vec3f, viewDir: vec3f, gloss: f32, tbn: mat3x3f) {\n\tlet roughness: f32 = sqrt(1.0 - min(gloss, 1.0));\n\tlet direction: vec2f = dAnisotropyRotation;\n\tlet anisotropicT: vec3f = normalize(tbn * vec3f(direction, 0.0));\n\tlet anisotropicB: vec3f = normalize(cross(tbn[2], anisotropicT));\n\tlet anisotropy: f32 = dAnisotropy;\n\tlet anisotropicDirection: vec3f = anisotropicB;\n\tlet anisotropicTangent: vec3f = cross(anisotropicDirection, viewDir);\n\tlet anisotropicNormal: vec3f = cross(anisotropicTangent, anisotropicDirection);\n\tlet bendFactor: f32 = 1.0 - anisotropy * (1.0 - roughness);\n\tlet bendFactor4: f32 = bendFactor * bendFactor * bendFactor * bendFactor;\n\tlet bentNormal: vec3f = normalize(mix(normalize(anisotropicNormal), normalize(worldNormal), bendFactor4));\n\tdReflDirW = reflect(-viewDir, bentNormal);\n}`;\n\nvar reflectionCCPS = `\n#ifdef LIT_CLEARCOAT\nfn addReflectionCC(reflDir: vec3f, gloss: f32) {\n\tccReflection = ccReflection + calcReflection(reflDir, gloss);\n}\n#endif\n`;\n\nvar reflectionCubePS = `\nvar texture_cubeMap: texture_cube<f32>;\nvar texture_cubeMapSampler: sampler;\nuniform material_reflectivity: f32;\nfn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {\n\tvar lookupVec: vec3f = cubeMapProject(reflDir);\n\tlookupVec.x = lookupVec.x * -1.0;\n\treturn {reflectionDecode}(textureSample(texture_cubeMap, texture_cubeMapSampler, lookupVec));\n}\nfn addReflection(reflDir: vec3f, gloss: f32) {\n\tdReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);\n}\n`;\n\nvar reflectionEnvHQPS = `\n#ifndef ENV_ATLAS\n\t#define ENV_ATLAS\n\tvar texture_envAtlas: texture_2d<f32>;\n\tvar texture_envAtlasSampler: sampler;\n#endif\nvar texture_cubeMap: texture_cube<f32>;\nvar texture_cubeMapSampler: sampler;\nuniform material_reflectivity: f32;\nfn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {\n\tlet dir: vec3f = cubeMapProject(reflDir) * vec3f(-1.0, 1.0, 1.0);\n\tlet uv: vec2f = toSphericalUv(dir);\n\tlet level: f32 = saturate(1.0 - gloss) * 5.0;\n\tlet ilevel: f32 = floor(level);\n\tlet flevel: f32 = level - ilevel;\n\tlet sharp: vec3f = {reflectionCubemapDecode}(textureSample(texture_cubeMap, texture_cubeMapSampler, dir));\n\tlet roughA: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, mapRoughnessUv(uv, ilevel)));\n\tlet roughB: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, mapRoughnessUv(uv, ilevel + 1.0)));\n\treturn processEnvironment(mix(sharp, mix(roughA, roughB, flevel), min(level, 1.0)));\n}\nfn addReflection(reflDir: vec3f, gloss: f32) {\n\tdReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);\n}\n`;\n\nvar reflectionEnvPS = `\n#ifndef ENV_ATLAS\n#define ENV_ATLAS\n\tvar texture_envAtlas: texture_2d<f32>;\n\tvar texture_envAtlasSampler: sampler;\n#endif\nuniform material_reflectivity: f32;\nfn shinyMipLevel(uv: vec2f) -> f32 {\n\tlet dx: vec2f = dpdx(uv);\n\tlet dy: vec2f = dpdy(uv);\n\tlet uv2: vec2f = vec2f(fract(uv.x + 0.5), uv.y);\n\tlet dx2: vec2f = dpdx(uv2);\n\tlet dy2: vec2f = dpdy(uv2);\n\tlet maxd: f32 = min(max(dot(dx, dx), dot(dy, dy)), max(dot(dx2, dx2), dot(dy2, dy2)));\n\treturn clamp(0.5 * log2(maxd) - 1.0 + uniform.textureBias, 0.0, 5.0);\n}\nfn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {\n\tlet dir: vec3f = cubeMapProject(reflDir) * vec3f(-1.0, 1.0, 1.0);\n\tlet uv: vec2f = toSphericalUv(dir);\n\tlet level: f32 = saturate(1.0 - gloss) * 5.0;\n\tlet ilevel: f32 = floor(level);\n\tlet level2: f32 = shinyMipLevel(uv * atlasSize);\n\tlet ilevel2: f32 = floor(level2);\n\tvar uv0: vec2f;\n\tvar uv1: vec2f;\n\tvar weight: f32;\n\tif (ilevel == 0.0) {\n\t\tuv0 = mapShinyUv(uv, ilevel2);\n\t\tuv1 = mapShinyUv(uv, ilevel2 + 1.0);\n\t\tweight = level2 - ilevel2;\n\t} else {\n\t\tuv0 = mapRoughnessUv(uv, ilevel);\n\t\tuv1 = uv0;\n\t\tweight = 0.0;\n\t}\n\tlet linearA: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, uv0));\n\tlet linearB: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, uv1));\n\tlet linear0: vec3f = mix(linearA, linearB, weight);\n\tlet linear1: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, mapRoughnessUv(uv, ilevel + 1.0)));\n\treturn processEnvironment(mix(linear0, linear1, level - ilevel));\n}\nfn addReflection(reflDir: vec3f, gloss: f32) {\n\tdReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);\n}\n`;\n\nvar reflectionSpherePS = `\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform matrix_view: mat4x4f;\n#endif\nvar texture_sphereMap: texture_2d<f32>;\nvar texture_sphereMapSampler: sampler;\nuniform material_reflectivity: f32;\nfn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f {\n\tlet viewRotationMatrix = mat3x3f(uniform.matrix_view[0].xyz, uniform.matrix_view[1].xyz, uniform.matrix_view[2].xyz);\n\tlet reflDirV: vec3f = viewRotationMatrix * reflDir;\n\tlet m: f32 = 2.0 * sqrt(dot(reflDirV.xy, reflDirV.xy) + (reflDirV.z + 1.0) * (reflDirV.z + 1.0));\n\tlet sphereMapUv: vec2f = reflDirV.xy / m + 0.5;\n\treturn {reflectionDecode}(textureSample(texture_sphereMap, texture_sphereMapSampler, sphereMapUv));\n}\nfn addReflection(reflDir: vec3f, gloss: f32) {\n\tdReflection = dReflection + vec4f(calcReflection(reflDir, gloss), uniform.material_reflectivity);\n}\n`;\n\nvar reflectionSheenPS = `\nfn addReflectionSheen(worldNormal: vec3f, viewDir: vec3f, gloss: f32) {\n\tlet NoV: f32 = dot(worldNormal, viewDir);\n\tlet alphaG: f32 = gloss * gloss;\n\tlet a: f32 = select(\n\t\t-8.48 * alphaG + 14.3 * gloss - 9.95,\n\t\t-339.2 * alphaG + 161.4 * gloss - 25.9,\n\t\tgloss < 0.25\n\t);\n\tlet b: f32 = select(\n\t\t1.97 * alphaG - 3.27 * gloss + 0.72,\n\t\t44.0 * alphaG - 23.7 * gloss + 3.26,\n\t\tgloss < 0.25\n\t);\n\tlet dg_add: f32 = select(\n\t\t0.1 * ( gloss - 0.25 ),\n\t\t0.0,\n\t\tgloss < 0.25\n\t);\n\tlet dg: f32 = exp( a * NoV + b ) + dg_add;\n\tsReflection = sReflection + (calcReflection(worldNormal, 0.0) * saturate(dg));\n}`;\n\nvar refractionCubePS = `\nfn refract2(viewVec: vec3f, normal: vec3f, IOR: f32) -> vec3f {\n\tlet vn: f32 = dot(viewVec, normal);\n\tlet k: f32 = 1.0 - IOR * IOR * (1.0 - vn * vn);\n\tlet refrVec: vec3f = IOR * viewVec - (IOR * vn + sqrt(k)) * normal;\n\treturn refrVec;\n}\nfn addRefraction(\n\tworldNormal: vec3f,\n\tviewDir: vec3f,\n\tthickness: f32,\n\tgloss: f32,\n\tspecularity: vec3f,\n\talbedo: vec3f,\n\ttransmission: f32,\n\trefractionIndex: f32,\n\tdispersion: f32\n#if defined(LIT_IRIDESCENCE)\n\t, iridescenceFresnel: vec3f,\n\tiridescenceIntensity: f32\n#endif\n) {\n\tlet tmpRefl: vec4f = dReflection;\n\tlet reflectionDir: vec3f = refract2(-viewDir, worldNormal, refractionIndex);\n\tdReflection = vec4f(0.0);\n\taddReflection(reflectionDir, gloss);\n\tdDiffuseLight = mix(dDiffuseLight, dReflection.rgb * albedo, transmission);\n\tdReflection = tmpRefl;\n}\n`;\n\nvar refractionDynamicPS = `\nuniform material_invAttenuationDistance: f32;\nuniform material_attenuation: vec3f;\nfn evalRefractionColor(refractionVector: vec3f, gloss: f32, refractionIndex: f32) -> vec3f {\n\tlet pointOfRefraction: vec4f = vec4f(vPositionW + refractionVector, 1.0);\n\tlet projectionPoint: vec4f = uniform.matrix_viewProjection * pointOfRefraction;\n\tlet uv: vec2f = getGrabScreenPos(projectionPoint);\n\tlet iorToRoughness: f32 = (1.0 - gloss) * clamp((1.0 / refractionIndex) * 2.0 - 2.0, 0.0, 1.0);\n\tlet refractionLod: f32 = log2(uniform.uScreenSize.x) * iorToRoughness;\n\tlet refraction: vec3f = textureSampleLevel(uSceneColorMap, uSceneColorMapSampler, uv, refractionLod).rgb;\n\treturn refraction;\n}\nfn addRefraction(\n\tworldNormal: vec3f,\n\tviewDir: vec3f,\n\tthickness: f32,\n\tgloss: f32,\n\tspecularity: vec3f,\n\talbedo: vec3f,\n\ttransmission: f32,\n\trefractionIndex: f32,\n\tdispersion: f32,\n#if defined(LIT_IRIDESCENCE)\n\tiridescenceFresnel: vec3f,\n\tiridescenceIntensity: f32\n#endif\n) {\n\tvar modelScale: vec3f;\n\tmodelScale.x = length(uniform.matrix_model[0].xyz);\n\tmodelScale.y = length(uniform.matrix_model[1].xyz);\n\tmodelScale.z = length(uniform.matrix_model[2].xyz);\n\tlet scale: vec3f = thickness * modelScale;\n\tvar refractionVector = normalize(refract(-viewDir, worldNormal, refractionIndex)) * scale;\n\tvar refraction = evalRefractionColor(refractionVector, gloss, refractionIndex);\n\t#ifdef LIT_DISPERSION\n\t\tlet halfSpread: f32 = (1.0 / refractionIndex - 1.0) * 0.025 * dispersion;\n\t\tlet refractionIndexR: f32 = refractionIndex - halfSpread;\n\t\trefractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexR)) * scale;\n\t\trefraction.r = evalRefractionColor(refractionVector, gloss, refractionIndexR).r;\n\t\tlet refractionIndexB: f32 = refractionIndex + halfSpread;\n\t\trefractionVector = normalize(refract(-viewDir, worldNormal, refractionIndexB)) * scale;\n\t\trefraction.b = evalRefractionColor(refractionVector, gloss, refractionIndexB).b;\n\t#endif\n\tvar transmittance: vec3f;\n\tif (uniform.material_invAttenuationDistance != 0.0)\n\t{\n\t\tlet attenuation: vec3f = -log(uniform.material_attenuation) * uniform.material_invAttenuationDistance;\n\t\ttransmittance = exp(-attenuation * length(refractionVector));\n\t}\n\telse\n\t{\n\t\ttransmittance = refraction;\n\t}\n\tlet fresnel: vec3f = vec3f(1.0) -\n\t\tgetFresnel(\n\t\t\tdot(viewDir, worldNormal),\n\t\t\tgloss,\n\t\t\tspecularity\n\t\t#if defined(LIT_IRIDESCENCE)\n\t\t\t, iridescenceFresnel,\n\t\t\tiridescenceIntensity\n\t\t#endif\n\t\t);\n\tdDiffuseLight = mix(dDiffuseLight, refraction * transmittance * fresnel, transmission);\n}\n`;\n\nvar reprojectPS = `\nvarying vUv0: vec2f;\n#ifdef CUBEMAP_SOURCE\n\tvar sourceCube: texture_cube<f32>;\n\tvar sourceCubeSampler : sampler;\n#else\n\tvar sourceTex: texture_2d<f32>;\n\tvar sourceTexSampler : sampler;\n#endif\n#ifdef USE_SAMPLES_TEX\n\tvar samplesTex: texture_2d<f32>;\n\tvar samplesTexSampler : sampler;\n\tuniform samplesTexInverseSize: vec2f;\n#endif\nuniform params: vec3f;\nfn targetFace() -> f32 { return uniform.params.x; }\nfn targetTotalPixels() -> f32 { return uniform.params.y; }\nfn sourceTotalPixels() -> f32 { return uniform.params.z; }\nconst PI: f32 = 3.141592653589793;\nfn saturate(x: f32) -> f32 {\n\treturn clamp(x, 0.0, 1.0);\n}\n#include \"decodePS\"\n#include \"encodePS\"\nfn modifySeams(dir: vec3f, scale: f32) -> vec3f {\n\tlet adir = abs(dir);\n\tlet M = max(max(adir.x, adir.y), adir.z);\n\treturn dir / M * vec3f(\n\t\tselect(scale, 1.0, adir.x == M),\n\t\tselect(scale, 1.0, adir.y == M),\n\t\tselect(scale, 1.0, adir.z == M)\n\t);\n}\nfn toSpherical(dir: vec3f) -> vec2f {\n\tlet nonZeroXZ = any(dir.xz != vec2f(0.0, 0.0));\n\treturn vec2f(select(0.0, atan2(dir.x, dir.z), nonZeroXZ), asin(dir.y));\n}\nfn fromSpherical(uv: vec2f) -> vec3f {\n\treturn vec3f(cos(uv.y) * sin(uv.x),\n\t\t\t\tsin(uv.y),\n\t\t\t\tcos(uv.y) * cos(uv.x));\n}\nfn getDirectionEquirect(uv: vec2f) -> vec3f {\n\treturn fromSpherical((vec2f(uv.x, 1.0 - uv.y) * 2.0 - 1.0) * vec2f(PI, PI * 0.5));\n}\nfn signNotZero(k: f32) -> f32 {\n\treturn select(-1.0, 1.0, k >= 0.0);\n}\nfn signNotZeroVec2(v: vec2f) -> vec2f {\n\treturn vec2f(signNotZero(v.x), signNotZero(v.y));\n}\nfn octDecode(o: vec2f) -> vec3f {\n\tvar v = vec3f(o.x, 1.0 - abs(o.x) - abs(o.y), o.y);\n\tif (v.y < 0.0) {\n\t\tvar temp: vec2f = (1.0 - abs(v.zx)) * signNotZeroVec2(v.xz);\n\t\tv = vec3f(temp.x, v.y, temp.y);\n\t}\n\treturn normalize(v);\n}\nfn getDirectionOctahedral(uv: vec2f) -> vec3f {\n\treturn octDecode(vec2f(uv.x, 1.0 - uv.y) * 2.0 - 1.0);\n}\nfn octEncode(v: vec3f) -> vec2f {\n\tlet l1norm = abs(v.x) + abs(v.y) + abs(v.z);\n\tvar result = v.xz * (1.0 / l1norm);\n\tif (v.y < 0.0) {\n\t\tresult = (1.0 - abs(result.yx)) * signNotZeroVec2(result.xy);\n\t}\n\treturn result;\n}\n#ifdef CUBEMAP_SOURCE\n\tfn sampleCubemapDir(dir: vec3f) -> vec4f {\n\t\treturn textureSample(sourceCube, sourceCubeSampler, modifySeams(dir, 1.0));\n\t}\n\tfn sampleCubemapSph(sph: vec2f) -> vec4f {\n\t\treturn sampleCubemapDir(fromSpherical(sph));\n\t}\n\tfn sampleCubemapDirLod(dir: vec3f, mipLevel: f32) -> vec4f {\n\t\treturn textureSampleLevel(sourceCube, sourceCubeSampler, modifySeams(dir, 1.0), mipLevel);\n\t}\n\tfn sampleCubemapSphLod(sph: vec2f, mipLevel: f32) -> vec4f {\n\t\treturn sampleCubemapDirLod(fromSpherical(sph), mipLevel);\n\t}\n#else\n\tfn sampleEquirectSph(sph: vec2f) -> vec4f {\n\t\tlet uv = sph / vec2f(PI * 2.0, PI) + 0.5;\n\t\treturn textureSample(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y));\n\t}\n\tfn sampleEquirectDir(dir: vec3f) -> vec4f {\n\t\treturn sampleEquirectSph(toSpherical(dir));\n\t}\n\tfn sampleEquirectSphLod(sph: vec2f, mipLevel: f32) -> vec4f {\n\t\tlet uv = sph / vec2f(PI * 2.0, PI) + 0.5;\n\t\treturn textureSampleLevel(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y), mipLevel);\n\t}\n\tfn sampleEquirectDirLod(dir: vec3f, mipLevel: f32) -> vec4f {\n\t\treturn sampleEquirectSphLod(toSpherical(dir), mipLevel);\n\t}\n\tfn sampleOctahedralDir(dir: vec3f) -> vec4f {\n\t\tlet uv = octEncode(dir) * 0.5 + 0.5;\n\t\treturn textureSample(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y));\n\t}\n\tfn sampleOctahedralSph(sph: vec2f) -> vec4f {\n\t\treturn sampleOctahedralDir(fromSpherical(sph));\n\t}\n\tfn sampleOctahedralDirLod(dir: vec3f, mipLevel: f32) -> vec4f {\n\t\tlet uv = octEncode(dir) * 0.5 + 0.5;\n\t\treturn textureSampleLevel(sourceTex, sourceTexSampler, vec2f(uv.x, 1.0 - uv.y), mipLevel);\n\t}\n\tfn sampleOctahedralSphLod(sph: vec2f, mipLevel: f32) -> vec4f {\n\t\treturn sampleOctahedralDirLod(fromSpherical(sph), mipLevel);\n\t}\n#endif\nfn getDirectionCubemap(uv: vec2f) -> vec3f {\n\tlet st = uv * 2.0 - 1.0;\n\tlet face = targetFace();\n\tvar vec: vec3f;\n\tif (face == 0.0) {\n\t\tvec = vec3f(1, -st.y, -st.x);\n\t} else if (face == 1.0) {\n\t\tvec = vec3f(-1, -st.y, st.x);\n\t} else if (face == 2.0) {\n\t\tvec = vec3f(st.x, 1, st.y);\n\t} else if (face == 3.0) {\n\t\tvec = vec3f(st.x, -1, -st.y);\n\t} else if (face == 4.0) {\n\t\tvec = vec3f(st.x, -st.y, 1);\n\t} else {\n\t\tvec = vec3f(-st.x, -st.y, -1);\n\t}\n\treturn normalize(modifySeams(vec, 1.0));\n}\nfn matrixFromVector(n: vec3f) -> mat3x3f {\n\tlet a = 1.0 / (1.0 + n.z);\n\tlet b = -n.x * n.y * a;\n\tlet b1 = vec3f(1.0 - n.x * n.x * a, b, -n.x);\n\tlet b2 = vec3f(b, 1.0 - n.y * n.y * a, -n.y);\n\treturn mat3x3f(b1, b2, n);\n}\nfn matrixFromVectorSlow(n: vec3f) -> mat3x3f {\n\tlet up = select(vec3f(0.0, 0.0, select(-1.0, 1.0, n.y > 0.0)), vec3f(0.0, 1.0, 0.0), abs(n.y) > 0.0000001);\n\tlet x = normalize(cross(up, n));\n\tlet y = cross(n, x);\n\treturn mat3x3f(x, y, n);\n}\nfn reproject(uv: vec2f) -> vec4f {\n\tif ({NUM_SAMPLES} <= 1) {\n\t\treturn {ENCODE_FUNC}({DECODE_FUNC}({SOURCE_FUNC}Dir({TARGET_FUNC}(uv))));\n\t} else {\n\t\tlet t = {TARGET_FUNC}(uv);\n\t\tlet tu = dpdx(t);\n\t\tlet tv = dpdy(t);\n\t\tvar result = vec3f(0.0);\n\t\tfor (var u = 0.0; u < {NUM_SAMPLES_SQRT}; u += 1.0) {\n\t\t\tfor (var v = 0.0; v < {NUM_SAMPLES_SQRT}; v += 1.0) {\n\t\t\t\tresult += {DECODE_FUNC}({SOURCE_FUNC}Dir(normalize(t +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttu * (u / {NUM_SAMPLES_SQRT} - 0.5) +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttv * (v / {NUM_SAMPLES_SQRT} - 0.5))));\n\t\t\t}\n\t\t}\n\t\treturn {ENCODE_FUNC}(result / ({NUM_SAMPLES_SQRT} * {NUM_SAMPLES_SQRT}));\n\t}\n}\nconst unpackFloat: vec4f = vec4f(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0);\n#ifdef USE_SAMPLES_TEX\n\tfn unpackSample(i: i32, L: ptr<function, vec3f>, mipLevel: ptr<function, f32>) {\n\t\tvar u = (f32(i * 4) + 0.5) * uniform.samplesTexInverseSize.x;\n\t\tvar v = (floor(u) + 0.5) * uniform.samplesTexInverseSize.y;\n\t\tvar raw: vec4f;\n\t\traw.x = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat); u += uniform.samplesTexInverseSize.x;\n\t\traw.y = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat); u += uniform.samplesTexInverseSize.x;\n\t\traw.z = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat); u += uniform.samplesTexInverseSize.x;\n\t\traw.w = dot(textureSample(samplesTex, samplesTexSampler, vec2f(u, v)), unpackFloat);\n\t\t*L = raw.xyz * 2.0 - 1.0;\n\t\t*mipLevel = raw.w * 8.0;\n\t}\n\tfn prefilterSamples(uv: vec2f) -> vec4f {\n\t\tlet vecSpace = matrixFromVectorSlow({TARGET_FUNC}(uv));\n\t\tvar L: vec3f;\n\t\tvar mipLevel: f32;\n\t\tvar result = vec3f(0.0);\n\t\tvar totalWeight = 0.0;\n\t\tfor (var i = 0; i < {NUM_SAMPLES}; i += 1) {\n\t\t\tunpackSample(i, &L, &mipLevel);\n\t\t\tresult += {DECODE_FUNC}({SOURCE_FUNC}DirLod(vecSpace * L, mipLevel)) * L.z;\n\t\t\ttotalWeight += L.z;\n\t\t}\n\t\treturn {ENCODE_FUNC}(result / totalWeight);\n\t}\n\tfn prefilterSamplesUnweighted(uv: vec2f) -> vec4f {\n\t\tlet vecSpace = matrixFromVectorSlow({TARGET_FUNC}(uv));\n\t\tvar L: vec3f;\n\t\tvar mipLevel: f32;\n\t\tvar result = vec3f(0.0);\n\t\tfor (var i = 0; i < {NUM_SAMPLES}; i += 1) {\n\t\t\tunpackSample(i, &L, &mipLevel);\n\t\t\tresult += {DECODE_FUNC}({SOURCE_FUNC}DirLod(vecSpace * L, mipLevel));\n\t\t}\n\t\treturn {ENCODE_FUNC}(result / f32({NUM_SAMPLES}));\n\t}\n#endif\n@fragment\nfn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\toutput.color = {PROCESS_FUNC}(input.vUv0);\n\treturn output;\n}\n`;\n\nvar reprojectVS = `\nattribute vertex_position: vec2f;\nuniform uvMod: vec4f;\nvarying vUv0: vec2f;\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n\tvar output: VertexOutput;\n\toutput.position = vec4f(input.vertex_position, 0.5, 1.0);\n\toutput.vUv0 = getImageEffectUV((input.vertex_position * 0.5 + vec2f(0.5, 0.5)) * uniform.uvMod.xy + uniform.uvMod.zw);\n\treturn output;\n}\n`;\n\nvar screenDepthPS = `\nvar uSceneDepthMap: texture_2d<f32>;\nvar uSceneDepthMapSampler: sampler;\n#ifndef SCREENSIZE\n\t#define SCREENSIZE\n\tuniform uScreenSize: vec4f;\n#endif\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform matrix_view: mat4x4f;\n#endif\n#ifndef LINEARIZE_DEPTH\n\t#define LINEARIZE_DEPTH\n\t#ifndef CAMERAPLANES\n\t\t#define CAMERAPLANES\n\t\tuniform camera_params: vec4f;\n\t#endif\n\tfn linearizeDepth(z: f32) -> f32 {\n\t\tif (uniform.camera_params.w == 0.0) {\n\t\t\treturn (uniform.camera_params.z * uniform.camera_params.y) / (uniform.camera_params.y + z * (uniform.camera_params.z - uniform.camera_params.y));\n\t\t} else {\n\t\t\treturn uniform.camera_params.z + z * (uniform.camera_params.y - uniform.camera_params.z);\n\t\t}\n\t}\n#endif\nfn delinearizeDepth(linearDepth: f32) -> f32 {\n\tif (uniform.camera_params.w == 0.0) {\n\t\treturn (uniform.camera_params.y * (uniform.camera_params.z - linearDepth)) / (linearDepth * (uniform.camera_params.z - uniform.camera_params.y));\n\t} else {\n\t\treturn (linearDepth - uniform.camera_params.z) / (uniform.camera_params.y - uniform.camera_params.z);\n\t}\n}\nfn getLinearScreenDepth(uv: vec2f) -> f32 {\n\t#ifdef SCENE_DEPTHMAP_LINEAR\n\t\t#ifdef SCENE_DEPTHMAP_FLOAT\n\t\t\treturn textureSample(uSceneDepthMap, uSceneDepthMapSampler, uv).r;\n\t\t#else\n\t\t\tlet textureSize = textureDimensions(uSceneDepthMap, 0);\n\t\t\tlet texel: vec2i = vec2i(uv * vec2f(textureSize));\n\t\t\tlet data: vec4f = textureLoad(uSceneDepthMap, texel, 0);\n\t\t\tlet data_u32: vec4u = vec4u(data * 255.0);\n\t\t\tlet intBits: u32 = (data_u32.r << 24u) | (data_u32.g << 16u) | (data_u32.b << 8u) | data_u32.a;\n\t\t\treturn bitcast<f32>(intBits);\n\t\t#endif\n\t#else\n\t\treturn linearizeDepth(textureSample(uSceneDepthMap, uSceneDepthMapSampler, uv).r);\n\t#endif\n}\n#ifndef VERTEXSHADER\n\tfn getLinearScreenDepthFrag() -> f32 {\n\t\tlet uv: vec2f = pcPosition.xy * uniform.uScreenSize.zw;\n\t\treturn getLinearScreenDepth(uv);\n\t}\n#endif\nfn getLinearDepth(pos: vec3f) -> f32 {\n\treturn -(uniform.matrix_view * vec4f(pos, 1.0)).z;\n}\n`;\n\nvar shadowCascadesPS = `\nfn getShadowCascadeIndex(shadowCascadeDistances: vec4f, shadowCascadeCount: i32) -> i32 {\n\tlet depth: f32 = 1.0 / pcPosition.w;\n\tlet comparisons: vec4f = step(shadowCascadeDistances, vec4f(depth));\n\tlet cascadeIndex: i32 = i32(dot(comparisons, vec4f(1.0)));\n\treturn min(cascadeIndex, shadowCascadeCount - 1);\n}\nfn ditherShadowCascadeIndex(cascadeIndex_in: i32, shadowCascadeDistances: vec4f, shadowCascadeCount: i32, blendFactor: f32) -> i32 {\n\tvar cascadeIndex: i32 = cascadeIndex_in;\n\tif (cascadeIndex < shadowCascadeCount - 1) {\n\t\tlet currentRangeEnd: f32 = shadowCascadeDistances[cascadeIndex];\n\t\tlet transitionStart: f32 = blendFactor * currentRangeEnd;\n\t\tlet depth: f32 = 1.0 / pcPosition.w;\n\t\tif (depth > transitionStart) {\n\t\t\tlet transitionFactor: f32 = smoothstep(transitionStart, currentRangeEnd, depth);\n\t\t\tlet dither: f32 = fract(sin(dot(pcPosition.xy, vec2f(12.9898, 78.233))) * 43758.5453);\n\t\t\tif (dither < transitionFactor) {\n\t\t\t\tcascadeIndex = cascadeIndex + 1;\n\t\t\t}\n\t\t}\n\t}\n\treturn cascadeIndex;\n}\nfn fadeShadow(shadowCoord_in: vec3f, shadowCascadeDistances: vec4f) -> vec3f {\n\tvar shadowCoord: vec3f = shadowCoord_in;\n\tlet depth: f32 = 1.0 / pcPosition.w;\n\tif (depth > shadowCascadeDistances.w) {\n\t\tshadowCoord.z = -9999999.0;\n\t}\n\treturn shadowCoord;\n}\n`;\n\nvar shadowEVSMPS = `\nfn linstep(a: f32, b: f32, v: f32) -> f32 {\n\treturn clamp((v - a) / (b - a), 0.0, 1.0);\n}\nfn reduceLightBleeding(pMax: f32, amount: f32) -> f32 {\n\t return linstep(amount, 1.0, pMax);\n}\nfn chebyshevUpperBound(moments: vec2f, mean: f32, minVariance: f32, lightBleedingReduction: f32) -> f32 {\n\tvar variance: f32 = moments.y - (moments.x * moments.x);\n\tvariance = max(variance, minVariance);\n\tlet d: f32 = mean - moments.x;\n\tvar pMax: f32 = variance / (variance + (d * d));\n\tpMax = reduceLightBleeding(pMax, lightBleedingReduction);\n\treturn select(pMax, 1.0, mean <= moments.x);\n}\nfn calculateEVSM(moments_in: vec3f, Z_in: f32, vsmBias: f32, exponent: f32) -> f32 {\n\tlet Z: f32 = 2.0 * Z_in - 1.0;\n\tlet warpedDepth: f32 = exp(exponent * Z);\n\tlet moments: vec2f = moments_in.xy + vec2f(warpedDepth, warpedDepth*warpedDepth) * (1.0 - moments_in.z);\n\tlet VSMBias: f32 = vsmBias;\n\tlet depthScale: f32 = VSMBias * exponent * warpedDepth;\n\tlet minVariance1: f32 = depthScale * depthScale;\n\treturn chebyshevUpperBound(moments, warpedDepth, minVariance1, 0.1);\n}\nfn VSM16(tex: texture_2d<f32>, texSampler: sampler, texCoords: vec2f, resolution: f32, Z: f32, vsmBias: f32, exponent: f32) -> f32 {\n\tlet moments: vec3f = textureSampleLevel(tex, texSampler, texCoords, 0.0).xyz;\n\treturn calculateEVSM(moments, Z, vsmBias, exponent);\n}\nfn getShadowVSM16(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32) -> f32 {\n\treturn VSM16(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);\n}\nfn getShadowSpotVSM16(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32, lightDir: vec3f) -> f32 {\n\tlet Z: f32 = length(lightDir) * shadowParams.w + shadowParams.z;\n\treturn VSM16(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);\n}\nfn VSM32(tex: texture_2d<f32>, texSampler: sampler, texCoords_in: vec2f, resolution: f32, Z: f32, vsmBias: f32, exponent: f32) -> f32 {\n\t#ifdef CAPS_TEXTURE_FLOAT_FILTERABLE\n\t\tvar moments: vec3f = textureSampleLevel(tex, texSampler, texCoords_in, 0.0).xyz;\n\t#else\n\t\tvar pixelSize : f32 = 1.0 / resolution;\n\t\tlet texCoords: vec2f = texCoords_in - vec2f(pixelSize);\n\t\tlet s00: vec3f = textureSampleLevel(tex, texSampler, texCoords, 0.0).xyz;\n\t\tlet s10: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(pixelSize, 0.0), 0.0).xyz;\n\t\tlet s01: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(0.0, pixelSize), 0.0).xyz;\n\t\tlet s11: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(pixelSize), 0.0).xyz;\n\t\tlet fr: vec2f = fract(texCoords * resolution);\n\t\tlet h0: vec3f = mix(s00, s10, fr.x);\n\t\tlet h1: vec3f = mix(s01, s11, fr.x);\n\t\tvar moments: vec3f = mix(h0, h1, fr.y);\n\t#endif\n\treturn calculateEVSM(moments, Z, vsmBias, exponent);\n}\nfn getShadowVSM32(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32) -> f32 {\n\treturn VSM32(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);\n}\nfn getShadowSpotVSM32(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32, lightDir: vec3f) -> f32 {\n\tlet Z: f32 = length(lightDir) * shadowParams.w + shadowParams.z;\n\treturn VSM32(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);\n}\n`;\n\nvar shadowPCF1PS = `\nfn getShadowPCF1x1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\treturn textureSampleCompareLevel(shadowMap, shadowMapSampler, shadowCoord.xy, shadowCoord.z);\n}\nfn getShadowSpotPCF1x1(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\treturn textureSampleCompareLevel(shadowMap, shadowMapSampler, shadowCoord.xy, shadowCoord.z);\n}\n`;\n\nvar shadowPCF3PS = `\nfn _getShadowPCF3x3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec3f) -> f32 {\n\tlet z: f32 = shadowCoord.z;\n\tlet uv: vec2f = shadowCoord.xy * shadowParams.x;\n\tlet shadowMapSizeInv: f32 = 1.0 / shadowParams.x;\n\tlet base_uv_temp: vec2f = floor(uv + 0.5);\n\tlet s: f32 = (uv.x + 0.5 - base_uv_temp.x);\n\tlet t: f32 = (uv.y + 0.5 - base_uv_temp.y);\n\tlet base_uv: vec2f = (base_uv_temp - vec2f(0.5)) * shadowMapSizeInv;\n\tvar sum: f32 = 0.0;\n\tlet uw0: f32 = (3.0 - 2.0 * s);\n\tlet uw1: f32 = (1.0 + 2.0 * s);\n\tlet u0_offset: f32 = (2.0 - s) / uw0 - 1.0;\n\tlet u1_offset: f32 = s / uw1 + 1.0;\n\tlet vw0: f32 = (3.0 - 2.0 * t);\n\tlet vw1: f32 = (1.0 + 2.0 * t);\n\tlet v0_offset: f32 = (2.0 - t) / vw0 - 1.0;\n\tlet v1_offset: f32 = t / vw1 + 1.0;\n\tlet u0: f32 = u0_offset * shadowMapSizeInv + base_uv.x;\n\tlet v0: f32 = v0_offset * shadowMapSizeInv + base_uv.y;\n\tlet u1: f32 = u1_offset * shadowMapSizeInv + base_uv.x;\n\tlet v1: f32 = v1_offset * shadowMapSizeInv + base_uv.y;\n\tsum = sum + uw0 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v0), z);\n\tsum = sum + uw1 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v0), z);\n\tsum = sum + uw0 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v1), z);\n\tsum = sum + uw1 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v1), z);\n\tsum = sum * (1.0 / 16.0);\n\treturn sum;\n}\nfn getShadowPCF3x3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\treturn _getShadowPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);\n}\nfn getShadowSpotPCF3x3(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\treturn _getShadowPCF3x3(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);\n}\n`;\n\nvar shadowPCF5PS = `\nfn _getShadowPCF5x5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec3f) -> f32 {\n\tlet z: f32 = shadowCoord.z;\n\tlet uv: vec2f = shadowCoord.xy * shadowParams.x;\n\tlet shadowMapSizeInv: f32 = 1.0 / shadowParams.x;\n\tlet base_uv_temp: vec2f = floor(uv + 0.5);\n\tlet s: f32 = (uv.x + 0.5 - base_uv_temp.x);\n\tlet t: f32 = (uv.y + 0.5 - base_uv_temp.y);\n\tlet base_uv: vec2f = (base_uv_temp - vec2f(0.5)) * shadowMapSizeInv;\n\tlet uw0: f32 = (4.0 - 3.0 * s);\n\tlet uw1: f32 = 7.0;\n\tlet uw2: f32 = (1.0 + 3.0 * s);\n\tlet u0_offset: f32 = (3.0 - 2.0 * s) / uw0 - 2.0;\n\tlet u1_offset: f32 = (3.0 + s) / uw1;\n\tlet u2_offset: f32 = s / uw2 + 2.0;\n\tlet vw0: f32 = (4.0 - 3.0 * t);\n\tlet vw1: f32 = 7.0;\n\tlet vw2: f32 = (1.0 + 3.0 * t);\n\tlet v0_offset: f32 = (3.0 - 2.0 * t) / vw0 - 2.0;\n\tlet v1_offset: f32 = (3.0 + t) / vw1;\n\tlet v2_offset: f32 = t / vw2 + 2.0;\n\tvar sum: f32 = 0.0;\n\tlet u0: f32 = u0_offset * shadowMapSizeInv + base_uv.x;\n\tlet v0: f32 = v0_offset * shadowMapSizeInv + base_uv.y;\n\tlet u1: f32 = u1_offset * shadowMapSizeInv + base_uv.x;\n\tlet v1: f32 = v1_offset * shadowMapSizeInv + base_uv.y;\n\tlet u2: f32 = u2_offset * shadowMapSizeInv + base_uv.x;\n\tlet v2: f32 = v2_offset * shadowMapSizeInv + base_uv.y;\n\tsum = sum + uw0 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v0), z);\n\tsum = sum + uw1 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v0), z);\n\tsum = sum + uw2 * vw0 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u2, v0), z);\n\tsum = sum + uw0 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v1), z);\n\tsum = sum + uw1 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v1), z);\n\tsum = sum + uw2 * vw1 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u2, v1), z);\n\tsum = sum + uw0 * vw2 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u0, v2), z);\n\tsum = sum + uw1 * vw2 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u1, v2), z);\n\tsum = sum + uw2 * vw2 * textureSampleCompareLevel(shadowMap, shadowMapSampler, vec2f(u2, v2), z);\n\tsum = sum * (1.0 / 144.0);\n\tsum = saturate(sum);\n\treturn sum;\n}\nfn getShadowPCF5x5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\treturn _getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);\n}\nfn getShadowSpotPCF5x5(shadowMap: texture_depth_2d, shadowMapSampler: sampler_comparison, shadowCoord: vec3f, shadowParams: vec4f) -> f32 {\n\treturn _getShadowPCF5x5(shadowMap, shadowMapSampler, shadowCoord, shadowParams.xyz);\n}\n`;\n\nvar shadowSoftPS = `\nfn fractSinRand(uv: vec2f) -> f32 {\n\tlet PI: f32 = 3.141592653589793;\n\tlet a: f32 = 12.9898; let b: f32 = 78.233; let c: f32 = 43758.5453;\n\tlet dt: f32 = dot(uv.xy, vec2f(a, b));\n\tlet sn: f32 = dt % PI;\n\treturn fract(sin(sn) * c);\n}\nstruct VogelDiskData {\n\tinvNumSamples: f32,\n\tinitialAngle: f32,\n\tcurrentPointId: f32,\n}\nfn prepareDiskConstants(data: ptr<function, VogelDiskData>, sampleCount: i32, randomSeed: f32) {\n\tlet pi2: f32 = 6.28318530718;\n\tdata.invNumSamples = 1.0 / f32(sampleCount);\n\tdata.initialAngle = randomSeed * pi2;\n\tdata.currentPointId = 0.0;\n}\nfn generateDiskSample(data: ptr<function, VogelDiskData>) -> vec2f {\n\tlet GOLDEN_ANGLE: f32 = 2.399963;\n\tlet r: f32 = sqrt((data.currentPointId + 0.5) * data.invNumSamples);\n\tlet theta: f32 = data.currentPointId * GOLDEN_ANGLE + data.initialAngle;\n\tlet offset: vec2f = vec2f(cos(theta), sin(theta)) * pow(r, 1.33);\n\tdata.currentPointId = data.currentPointId + 1.0;\n\treturn offset;\n}\nfn PCSSFindBlocker(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, avgBlockerDepth: ptr<function, f32>, numBlockers: ptr<function, i32>,\n\tshadowCoords: vec2f, z: f32, shadowBlockerSamples: i32, penumbraSize: f32, invShadowMapSize: f32, randomSeed: f32) {\n\tvar diskData: VogelDiskData;\n\tprepareDiskConstants(&diskData, shadowBlockerSamples, randomSeed);\n\tlet searchWidth: f32 = penumbraSize * invShadowMapSize;\n\tvar blockerSum: f32 = 0.0;\n\tvar numBlockers_local: i32 = 0;\n\tfor( var i: i32 = 0; i < shadowBlockerSamples; i = i + 1 ) {\n\t\tlet diskUV: vec2f = generateDiskSample(&diskData);\n\t\tlet sampleUV: vec2f = shadowCoords + diskUV * searchWidth;\n\t\tlet shadowMapDepth: f32 = textureSampleLevel(shadowMap, shadowMapSampler, sampleUV, 0.0).r;\n\t\tif ( shadowMapDepth < z ) {\n\t\t\tblockerSum = blockerSum + shadowMapDepth;\n\t\t\tnumBlockers_local = numBlockers_local + 1;\n\t\t}\n\t}\n\t*avgBlockerDepth = blockerSum / f32(numBlockers_local);\n\t*numBlockers = numBlockers_local;\n}\nfn PCSSFilter(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, uv: vec2f, receiverDepth: f32, shadowSamples: i32, filterRadius: f32, randomSeed: f32) -> f32 {\n\tvar diskData: VogelDiskData;\n\tprepareDiskConstants(&diskData, shadowSamples, randomSeed);\n\tvar sum: f32 = 0.0;\n\tfor (var i: i32 = 0; i < shadowSamples; i = i + 1) {\n\t\tlet offsetUV: vec2f = generateDiskSample(&diskData) * filterRadius;\n\t\tlet depth: f32 = textureSampleLevel(shadowMap, shadowMapSampler, uv + offsetUV, 0.0).r;\n\t\tsum = sum + step(receiverDepth, depth);\n\t}\n\treturn sum / f32(shadowSamples);\n}\nfn getPenumbra(dblocker: f32, dreceiver: f32, penumbraSize: f32, penumbraFalloff: f32) -> f32 {\n\tlet dist: f32 = dreceiver - dblocker;\n\tlet penumbra: f32 = 1.0 - pow(1.0 - dist, penumbraFalloff);\n\treturn penumbra * penumbraSize;\n}\nfn PCSSDirectional(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoords: vec3f, cameraParams: vec4f, softShadowParams: vec4f) -> f32 {\n\tlet receiverDepth: f32 = shadowCoords.z;\n\tlet randomSeed: f32 = fractSinRand(pcPosition.xy);\n\tlet shadowSamples: i32 = i32(softShadowParams.x);\n\tlet shadowBlockerSamples: i32 = i32(softShadowParams.y);\n\tlet penumbraSize: f32 = softShadowParams.z;\n\tlet penumbraFalloff: f32 = softShadowParams.w;\n\tlet shadowMapSize: i32 = i32(textureDimensions(shadowMap, 0).x);\n\tvar invShadowMapSize: f32 = 1.0 / f32(shadowMapSize);\n\tinvShadowMapSize = invShadowMapSize * (f32(shadowMapSize) / 2048.0);\n\tvar penumbra: f32;\n\tif (shadowBlockerSamples > 0) {\n\t\tvar avgBlockerDepth: f32 = 0.0;\n\t\tvar numBlockers: i32 = 0;\n\t\tPCSSFindBlocker(shadowMap, shadowMapSampler, &avgBlockerDepth, &numBlockers, shadowCoords.xy, receiverDepth, shadowBlockerSamples, penumbraSize, invShadowMapSize, randomSeed);\n\t\tif (numBlockers < 1) {\n\t\t\treturn 1.0;\n\t\t}\n\t\tpenumbra = getPenumbra(avgBlockerDepth, shadowCoords.z, penumbraSize, penumbraFalloff);\n\t} else {\n\t\tpenumbra = penumbraSize;\n\t}\n\tlet filterRadius: f32 = penumbra * invShadowMapSize;\n\treturn PCSSFilter(shadowMap, shadowMapSampler, shadowCoords.xy, receiverDepth, shadowSamples, filterRadius, randomSeed);\n}\nfn getShadowPCSS(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, cameraParams: vec4f, softShadowParams: vec4f, lightDir: vec3f) -> f32 {\n\treturn PCSSDirectional(shadowMap, shadowMapSampler, shadowCoord, cameraParams, softShadowParams);\n}\n`;\n\nvar skinBatchVS = `\nattribute vertex_boneIndices: f32;\nvar texture_poseMap: texture_2d<f32>;\nfn getBoneMatrix(indexFloat: f32) -> mat4x4f {\n\tlet width = i32(textureDimensions(texture_poseMap).x);\n\tlet index: i32 = i32(indexFloat + 0.5) * 3;\n\tlet iy: i32 = index / width;\n\tlet ix: i32 = index % width;\n\tlet v1: vec4f = textureLoad(texture_poseMap, vec2i(ix + 0, iy), 0);\n\tlet v2: vec4f = textureLoad(texture_poseMap, vec2i(ix + 1, iy), 0);\n\tlet v3: vec4f = textureLoad(texture_poseMap, vec2i(ix + 2, iy), 0);\n\treturn mat4x4f(\n\t\tv1.x, v2.x, v3.x, 0,\n\t\tv1.y, v2.y, v3.y, 0,\n\t\tv1.z, v2.z, v3.z, 0,\n\t\tv1.w, v2.w, v3.w, 1.0\n\t);\n}\n`;\n\nvar skinVS = `\nattribute vertex_boneWeights: vec4f;\nattribute vertex_boneIndices: vec4f;\nvar texture_poseMap: texture_2d<f32>;\nstruct BoneMatrix {\n\tv1: vec4f,\n\tv2: vec4f,\n\tv3: vec4f,\n}\nfn getBoneMatrix(width: i32, index: i32) -> BoneMatrix {\n\tlet v = index / width;\n\tlet u = index % width;\n\tvar result: BoneMatrix;\n\tresult.v1 = textureLoad(texture_poseMap, vec2i(u + 0, v), 0);\n\tresult.v2 = textureLoad(texture_poseMap, vec2i(u + 1, v), 0);\n\tresult.v3 = textureLoad(texture_poseMap, vec2i(u + 2, v), 0);\n\treturn result;\n}\nfn getSkinMatrix(indicesFloat: vec4f, weights: vec4f) -> mat4x4f {\n\tlet width = i32(textureDimensions(texture_poseMap).x);\n\tvar indices = vec4i(indicesFloat + 0.5) * 3;\n\tlet boneA = getBoneMatrix(width, indices.x);\n\tlet boneB = getBoneMatrix(width, indices.y);\n\tlet boneC = getBoneMatrix(width, indices.z);\n\tlet boneD = getBoneMatrix(width, indices.w);\n\tlet v1 = boneA.v1 * weights.x + boneB.v1 * weights.y + boneC.v1 * weights.z + boneD.v1 * weights.w;\n\tlet v2 = boneA.v2 * weights.x + boneB.v2 * weights.y + boneC.v2 * weights.z + boneD.v2 * weights.w;\n\tlet v3 = boneA.v3 * weights.x + boneB.v3 * weights.y + boneC.v3 * weights.z + boneD.v3 * weights.w;\n\tlet one = dot(weights, vec4f(1.0, 1.0, 1.0, 1.0));\n\treturn mat4x4f(\n\t\tv1.x, v2.x, v3.x, 0,\n\t\tv1.y, v2.y, v3.y, 0,\n\t\tv1.z, v2.z, v3.z, 0,\n\t\tv1.w, v2.w, v3.w, one\n\t);\n}\n`;\n\nvar skyboxPS = `\n\t#define LIT_SKYBOX_INTENSITY\n\t#include \"envProcPS\"\n\t#include \"gammaPS\"\n\t#include \"tonemappingPS\"\n\t#ifdef PREPASS_PASS\n\t\tvarying vLinearDepth: f32;\n\t\t#include \"floatAsUintPS\"\n\t#endif\n\tvarying vViewDir : vec3f;\n\tuniform skyboxHighlightMultiplier : f32;\n\t#ifdef SKY_CUBEMAP\n\t\tvar texture_cubeMap : texture_cube<f32>;\n\t\tvar texture_cubeMap_sampler : sampler;\n\t\t#ifdef SKYMESH\n\t\t\tvarying vWorldPos : vec3f;\n\t\t\tuniform cubeMapRotationMatrix : mat3x3f;\n\t\t\tuniform projectedSkydomeCenter : vec3f;\n\t\t#endif\n\t#else\n\t\t#include \"sphericalPS\"\n\t\t#include \"envAtlasPS\"\n\t\tvar texture_envAtlas : texture_2d<f32>;\n\t\tvar texture_envAtlas_sampler : sampler;\n\t\tuniform mipLevel : f32;\n\t#endif\n\t@fragment\n\tfn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\tvar output: FragmentOutput;\n\t\t#ifdef PREPASS_PASS\n\t\t\toutput.color = float2vec4(vLinearDepth);\n\t\t#else\n\t\t\tvar linear : vec3f;\n\t\t\tvar dir : vec3f;\n\t\t\t#ifdef SKY_CUBEMAP\n\t\t\t\t#ifdef SKYMESH\n\t\t\t\t\tvar envDir : vec3f = normalize(input.vWorldPos - uniform.projectedSkydomeCenter);\n\t\t\t\t\tdir = envDir * uniform.cubeMapRotationMatrix;\n\t\t\t\t#else\n\t\t\t\t\tdir = input.vViewDir;\n\t\t\t\t#endif\n\t\t\t\tdir.x *= -1.0;\n\t\t\t\tlinear = {SKYBOX_DECODE_FNC}(textureSample(texture_cubeMap, texture_cubeMap_sampler, dir));\n\t\t\t#else\n\t\t\t\tdir = input.vViewDir * vec3f(-1.0, 1.0, 1.0);\n\t\t\t\tlet uv : vec2f = toSphericalUv(normalize(dir));\n\t\t\t\tlinear = {SKYBOX_DECODE_FNC}(textureSample(texture_envAtlas, texture_envAtlas_sampler, mapRoughnessUv(uv, uniform.mipLevel)));\n\t\t\t#endif\n\t\t\tif (any(linear >= vec3f(64.0))) {\n\t\t\t\tlinear *= uniform.skyboxHighlightMultiplier;\n\t\t\t}\n\t\t\t\n\t\t\toutput.color = vec4f(gammaCorrectOutput(toneMap(processEnvironment(linear))), 1.0);\n\t\t#endif\n\t\treturn output;\n\t}\n`;\n\nvar skyboxVS = `\n\tattribute aPosition : vec4f;\n\tuniform matrix_view : mat4x4f;\n\tuniform matrix_projectionSkybox : mat4x4f;\n\tuniform cubeMapRotationMatrix : mat3x3f;\n\tvarying vViewDir : vec3f;\n\t#ifdef PREPASS_PASS\n\t\tvarying vLinearDepth: f32;\n\t#endif\n\t#ifdef SKYMESH\n\t\tuniform matrix_model : mat4x4f;\n\t\tvarying vWorldPos : vec3f;\n\t#endif\n\t@vertex\n\tfn vertexMain(input : VertexInput) -> VertexOutput {\n\t\tvar output : VertexOutput;\n\t\tvar view : mat4x4f = uniform.matrix_view;\n\t\t#ifdef SKYMESH\n\t\t\tvar worldPos : vec4f = uniform.matrix_model * input.aPosition;\n\t\t\toutput.vWorldPos = worldPos.xyz;\n\t\t\toutput.position = uniform.matrix_projectionSkybox * (view * worldPos);\n\t\t\t#ifdef PREPASS_PASS\n\t\t\t\toutput.vLinearDepth = -(uniform.matrix_view * vec4f(worldPos.xyz, 1.0)).z;\n\t\t\t#endif\n\t\t#else\n\t\t\tview[3][0] = 0.0;\n\t\t\tview[3][1] = 0.0;\n\t\t\tview[3][2] = 0.0;\n\t\t\toutput.position = uniform.matrix_projectionSkybox * (view * input.aPosition);\n\t\t\toutput.vViewDir = input.aPosition.xyz * uniform.cubeMapRotationMatrix;\n\t\t\t#ifdef PREPASS_PASS\n\t\t\t\toutput.vLinearDepth = -pcPosition.w;\n\t\t\t#endif\n\t\t#endif\n\t\toutput.position.z = output.position.w - 1.0e-7;\n\t\treturn output;\n\t}\n`;\n\nvar specularPS = `\n#ifdef STD_SPECULAR_CONSTANT\n\tuniform material_specular: vec3f;\n#endif\nfn getSpecularity() {\n\tvar specularColor = vec3f(1.0, 1.0, 1.0);\n\t#ifdef STD_SPECULAR_CONSTANT\n\tspecularColor = specularColor * uniform.material_specular;\n\t#endif\n\t#ifdef STD_SPECULAR_TEXTURE\n\tspecularColor = specularColor * {STD_SPECULAR_TEXTURE_DECODE}(textureSampleBias({STD_SPECULAR_TEXTURE_NAME}, {STD_SPECULAR_TEXTURE_NAME}Sampler, {STD_SPECULAR_TEXTURE_UV}, uniform.textureBias)).{STD_SPECULAR_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SPECULAR_VERTEX\n\tspecularColor = specularColor * saturate3(vVertexColor.{STD_SPECULAR_VERTEX_CHANNEL});\n\t#endif\n\tdSpecularity = specularColor;\n}\n`;\n\nvar sphericalPS = `\nfn toSpherical(dir: vec3f) -> vec2f {\n\tlet angle_xz = select(0.0, atan2(dir.x, dir.z), any(dir.xz != vec2f(0.0)));\n\treturn vec2f(angle_xz, asin(dir.y));\n}\nfn toSphericalUv(dir : vec3f) -> vec2f {\n\tconst PI : f32 = 3.141592653589793;\n\tlet uv : vec2f = toSpherical(dir) / vec2f(PI * 2.0, PI) + vec2f(0.5, 0.5);\n\treturn vec2f(uv.x, 1.0 - uv.y);\n}\n`;\n\nvar specularityFactorPS = `\n#ifdef STD_SPECULARITYFACTOR_CONSTANT\n\tuniform material_specularityFactor: f32;\n#endif\nfn getSpecularityFactor() {\n\tvar specularityFactor = 1.0;\n\t#ifdef STD_SPECULARITYFACTOR_CONSTANT\n\tspecularityFactor = specularityFactor * uniform.material_specularityFactor;\n\t#endif\n\t#ifdef STD_SPECULARITYFACTOR_TEXTURE\n\tspecularityFactor = specularityFactor * textureSampleBias({STD_SPECULARITYFACTOR_TEXTURE_NAME}, {STD_SPECULARITYFACTOR_TEXTURE_NAME}Sampler, {STD_SPECULARITYFACTOR_TEXTURE_UV}, uniform.textureBias).{STD_SPECULARITYFACTOR_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_SPECULARITYFACTOR_VERTEX\n\tspecularityFactor = specularityFactor * saturate(vVertexColor.{STD_SPECULARITYFACTOR_VERTEX_CHANNEL});\n\t#endif\n\tdSpecularityFactor = specularityFactor;\n}\n`;\n\nvar spotPS = `\nfn getSpotEffect(lightSpotDir: vec3f, lightInnerConeAngle: f32, lightOuterConeAngle: f32, lightDirNorm: vec3f) -> f32 {\n\tlet cosAngle: f32 = dot(lightDirNorm, lightSpotDir);\n\treturn smoothstep(lightOuterConeAngle, lightInnerConeAngle, cosAngle);\n}`;\n\nvar startNineSlicedPS = `\n\tnineSlicedUv = vec2f(vUv0.x, 1.0 - vUv0.y);\n`;\n\nvar startNineSlicedTiledPS = `\n\tlet tileMask: vec2f = step(vMask, vec2f(0.99999));\n\tlet tileSize: vec2f = 0.5 * (innerOffset.xy + innerOffset.zw);\n\tlet tileScale: vec2f = vec2f(1.0) / (vec2f(1.0) - tileSize);\n\tvar clampedUv: vec2f = mix(innerOffset.xy * 0.5, vec2f(1.0) - innerOffset.zw * 0.5, fract((vTiledUv - tileSize) * tileScale));\n\tclampedUv = clampedUv * atlasRect.zw + atlasRect.xy;\n\tvar nineSlicedUv: vec2f = vUv0 * tileMask + clampedUv * (vec2f(1.0) - tileMask);\n\tnineSlicedUv.y = 1.0 - nineSlicedUv.y;\n`;\n\nvar stdDeclarationPS = `\n\tvar<private> dAlpha: f32 = 1.0;\n\t#if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE\n\t\t#ifdef STD_OPACITY_TEXTURE_ALLOCATE\n\t\t\tvar texture_opacityMap : texture_2d<f32>;\n\t\t\tvar texture_opacityMapSampler : sampler;\n\t\t#endif\n\t#endif\n\t#ifdef FORWARD_PASS\n\t\tvar<private> dAlbedo: vec3f;\n\t\tvar<private> dNormalW: vec3f;\n\t\tvar<private> dSpecularity: vec3f = vec3f(0.0, 0.0, 0.0);\n\t\tvar<private> dGlossiness: f32 = 0.0;\n\t\t#ifdef LIT_REFRACTION\n\t\t\tvar<private> dTransmission: f32;\n\t\t\tvar<private> dThickness: f32;\n\t\t#endif\n\t\t#ifdef LIT_SCENE_COLOR\n\t\t\tvar uSceneColorMap : texture_2d<f32>;\n\t\t\tvar uSceneColorMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef LIT_SCREEN_SIZE\n\t\t\tuniform uScreenSize: vec4f;\n\t\t#endif\n\t\t#ifdef LIT_TRANSFORMS\n\t\t\tvar<private> matrix_viewProjection: mat4x4f;\n\t\t\tvar<private> matrix_model: mat4x4f;\n\t\t#endif\n\t\t#ifdef STD_HEIGHT_MAP\n\t\t\tvar<private> dUvOffset: vec2f;\n\t\t\t#ifdef STD_HEIGHT_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_heightMap : texture_2d<f32>;\n\t\t\t\tvar texture_heightMapSampler : sampler;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef STD_DIFFUSE_TEXTURE_ALLOCATE\n\t\t\tvar texture_diffuseMap : texture_2d<f32>;\n\t\t\tvar texture_diffuseMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef STD_DIFFUSEDETAIL_TEXTURE_ALLOCATE\n\t\t\tvar texture_diffuseDetailMap : texture_2d<f32>;\n\t\t\tvar texture_diffuseDetailMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef STD_NORMAL_TEXTURE_ALLOCATE\n\t\t\tvar texture_normalMap : texture_2d<f32>;\n\t\t\tvar texture_normalMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef STD_NORMALDETAIL_TEXTURE_ALLOCATE\n\t\t\tvar texture_normalDetailMap : texture_2d<f32>;\n\t\t\tvar texture_normalDetailMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef STD_THICKNESS_TEXTURE_ALLOCATE\n\t\t\tvar texture_thicknessMap : texture_2d<f32>;\n\t\t\tvar texture_thicknessMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef STD_REFRACTION_TEXTURE_ALLOCATE\n\t\t\tvar texture_refractionMap : texture_2d<f32>;\n\t\t\tvar texture_refractionMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef LIT_IRIDESCENCE\n\t\t\tvar<private> dIridescence: f32;\n\t\t\tvar<private> dIridescenceThickness: f32;\n\t\t\t#ifdef STD_IRIDESCENCE_THICKNESS_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_iridescenceThicknessMap : texture_2d<f32>;\n\t\t\t\tvar texture_iridescenceThicknessMapSampler : sampler;\n\t\t\t#endif\n\t\t\t#ifdef STD_IRIDESCENCE_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_iridescenceMap : texture_2d<f32>;\n\t\t\t\tvar texture_iridescenceMapSampler : sampler;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\tvar<private> ccSpecularity: f32;\n\t\t\tvar<private> ccGlossiness: f32;\n\t\t\tvar<private> ccNormalW: vec3f;\n\t\t#endif\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\tvar<private> dAnisotropy: f32;\n\t\t\tvar<private> dAnisotropyRotation: vec2f;\n\t\t#endif\n\t\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\tvar<private> sSpecularity: vec3f;\n\t\t\t\tvar<private> sGlossiness: f32;\n\t\t\t\t#ifdef STD_SHEEN_TEXTURE_ALLOCATE\n\t\t\t\t\tvar texture_sheenMap : texture_2d<f32>;\n\t\t\t\t\tvar texture_sheenMapSampler : sampler;\n\t\t\t\t#endif\n\t\t\t\t#ifdef STD_SHEENGLOSS_TEXTURE_ALLOCATE\n\t\t\t\t\tvar texture_sheenGlossMap : texture_2d<f32>;\n\t\t\t\t\tvar texture_sheenGlossMapSampler : sampler;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_METALNESS\n\t\t\t\tvar<private> dMetalness: f32;\n\t\t\t\tvar<private> dIor: f32;\n\t\t\t\t#ifdef STD_METALNESS_TEXTURE_ALLOCATE\n\t\t\t\t\tvar texture_metalnessMap : texture_2d<f32>;\n\t\t\t\t\tvar texture_metalnessMapSampler : sampler;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\tvar<private> dSpecularityFactor: f32;\n\t\t\t\t#ifdef STD_SPECULARITYFACTOR_TEXTURE_ALLOCATE\n\t\t\t\t\tvar texture_specularityFactorMap : texture_2d<f32>;\n\t\t\t\t\tvar texture_specularityFactorMapSampler : sampler;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef STD_SPECULAR_COLOR\n\t\t\t\t#ifdef STD_SPECULAR_TEXTURE_ALLOCATE\n\t\t\t\t\tvar texture_specularMap : texture_2d<f32>;\n\t\t\t\t\tvar texture_specularMapSampler : sampler;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef STD_GLOSS_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_glossMap : texture_2d<f32>;\n\t\t\t\tvar texture_glossMapSampler : sampler;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef STD_AO\n\t\t\tvar <private> dAo: f32;\n\t\t\t#ifdef STD_AO_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_aoMap : texture_2d<f32>;\n\t\t\t\tvar texture_aoMapSampler : sampler;\n\t\t\t#endif\n\t\t\t#ifdef STD_AODETAIL_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_aoDetailMap : texture_2d<f32>;\n\t\t\t\tvar texture_aoDetailMapSampler : sampler;\n\t\t\t#endif\n\t\t#endif\n\t\tvar <private> dEmission: vec3f;\n\t\t#ifdef STD_EMISSIVE_TEXTURE_ALLOCATE\n\t\t\tvar texture_emissiveMap : texture_2d<f32>;\n\t\t\tvar texture_emissiveMapSampler : sampler;\n\t\t#endif\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\t#ifdef STD_CLEARCOAT_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_clearCoatMap : texture_2d<f32>;\n\t\t\t\tvar texture_clearCoatMapSampler : sampler;\n\t\t\t#endif\n\t\t\t#ifdef STD_CLEARCOATGLOSS_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_clearCoatGlossMap : texture_2d<f32>;\n\t\t\t\tvar texture_clearCoatGlossMapSampler : sampler;\n\t\t\t#endif\n\t\t\t#ifdef STD_CLEARCOATNORMAL_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_clearCoatNormalMap : texture_2d<f32>;\n\t\t\t\tvar texture_clearCoatNormalMapSampler : sampler;\n\t\t\t#endif\n\t\t#endif\n\t\t#ifdef LIT_GGX_SPECULAR\n\t\t\t#ifdef STD_ANISOTROPY_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_anisotropyMap : texture_2d<f32>;\n\t\t\t\tvar texture_anisotropyMapSampler : sampler;\n\t\t\t#endif\n\t\t#endif\n\t\t#if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)\n\t\t\tvar<private> dLightmap: vec3f;\n\t\t\t#ifdef STD_LIGHT_TEXTURE_ALLOCATE\n\t\t\t\tvar texture_lightMap : texture_2d<f32>;\n\t\t\t\tvar texture_lightMapSampler : sampler;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\t#include \"litShaderCorePS\"\n`;\n\nvar stdFrontEndPS = `\n\t#if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE\n\t\t#include \"opacityPS\"\n\t\t#if defined(LIT_ALPHA_TEST)\n\t\t\t#include \"alphaTestPS\"\n\t\t#endif\n\t\t#if STD_OPACITY_DITHER != NONE\n\t\t\t#include \"opacityDitherPS\"\n\t\t#endif\n\t#endif\n\t#ifdef FORWARD_PASS\n\t\t#ifdef STD_HEIGHT_MAP\n\t\t\t#include \"parallaxPS\"\n\t\t#endif\n\t\t#include  \"diffusePS\"\n\t\t#ifdef LIT_NEEDS_NORMAL\n\t\t\t#include \"normalMapPS\"\n\t\t#endif\n\t\t#ifdef LIT_REFRACTION\n\t\t\t#include \"transmissionPS\"\n\t\t\t#include \"thicknessPS\"\n\t\t#endif\n\t\t#ifdef LIT_IRIDESCENCE\n\t\t\t#include \"iridescencePS\"\n\t\t\t#include \"iridescenceThicknessPS\"\n\t\t#endif\n\t\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t\t#ifdef LIT_SHEEN\n\t\t\t\t#include \"sheenPS\"\n\t\t\t\t#include \"sheenGlossPS\"\n\t\t\t#endif\n\t\t\t#ifdef LIT_METALNESS\n\t\t\t\t#include \"metalnessPS\"\n\t\t\t\t#include \"iorPS\"\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\t#include \"specularityFactorPS\"\n\t\t\t#endif\n\t\t\t#ifdef STD_SPECULAR_COLOR\n\t\t\t\t#include \"specularPS\"\n\t\t\t#else\n\t\t\t\tfn getSpecularity() { \n\t\t\t\t\tdSpecularity = vec3f(1.0, 1.0, 1.0);\n\t\t\t\t}\n\t\t\t#endif\n\t\t\t#include \"glossPS\"\n\t\t#endif\n\t\t#ifdef STD_AO\n\t\t\t#include \"aoPS\"\n\t\t#endif\n\t\t#include \"emissivePS\"\n\t\t#ifdef LIT_CLEARCOAT\n\t\t\t#include \"clearCoatPS\"\n\t\t\t#include \"clearCoatGlossPS\"\n\t\t\t#include \"clearCoatNormalPS\"\n\t\t#endif\n\t\t#if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)\n\t\t\t#include \"anisotropyPS\"\n\t\t#endif\n\t\t#if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)\n\t\t\t#include \"lightmapPS\"\n\t\t#endif\n\t#endif\n\tfn evaluateFrontend() {\n\t\t#if LIT_BLEND_TYPE != NONE || defined(LIT_ALPHA_TEST) || defined(LIT_ALPHA_TO_COVERAGE) || STD_OPACITY_DITHER != NONE\n\t\t\tgetOpacity();\n\t\t\t#if defined(LIT_ALPHA_TEST)\n\t\t\t\talphaTest(dAlpha);\n\t\t\t#endif\n\t\t\t#if STD_OPACITY_DITHER != NONE\n\t\t\t\topacityDither(dAlpha, 0.0);\n\t\t\t#endif\n\t\t\tlitArgs_opacity = dAlpha;\n\t\t#endif\n\t\t#ifdef FORWARD_PASS\n\t\t\t#ifdef STD_HEIGHT_MAP\n\t\t\t\tgetParallax();\n\t\t\t#endif\n\t\t\tgetAlbedo();\n\t\t\tlitArgs_albedo = dAlbedo;\n\t\t\t#ifdef LIT_NEEDS_NORMAL\n\t\t\t\tgetNormal();\n\t\t\t\tlitArgs_worldNormal = dNormalW;\n\t\t\t#endif\n\t\t\t#ifdef LIT_REFRACTION\n\t\t\t\tgetRefraction();\n\t\t\t\tlitArgs_transmission = dTransmission;\n\t\t\t\tgetThickness();\n\t\t\t\tlitArgs_thickness = dThickness;\n\t\t\t\t#ifdef LIT_DISPERSION\n\t\t\t\t\tlitArgs_dispersion = uniform.material_dispersion;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t\t#ifdef LIT_IRIDESCENCE\n\t\t\t\tgetIridescence();\n\t\t\t\tgetIridescenceThickness();\n\t\t\t\tlitArgs_iridescence_intensity = dIridescence;\n\t\t\t\tlitArgs_iridescence_thickness = dIridescenceThickness;\n\t\t\t#endif\n\t\t\t#ifdef LIT_SPECULAR_OR_REFLECTION\n\t\t\t\t#ifdef LIT_SHEEN\n\t\t\t\t\tgetSheen();\n\t\t\t\t\tlitArgs_sheen_specularity = sSpecularity;\n\t\t\t\t\tgetSheenGlossiness();\n\t\t\t\t\tlitArgs_sheen_gloss = sGlossiness;\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_METALNESS\n\t\t\t\t\tgetMetalness();\n\t\t\t\t\tlitArgs_metalness = dMetalness;\n\t\t\t\t\tgetIor();\n\t\t\t\t\tlitArgs_ior = dIor;\n\t\t\t\t#endif\n\t\t\t\t#ifdef LIT_SPECULARITY_FACTOR\n\t\t\t\t\tgetSpecularityFactor();\n\t\t\t\t\tlitArgs_specularityFactor = dSpecularityFactor;\n\t\t\t\t#endif\n\t\t\t\tgetGlossiness();\n\t\t\t\tgetSpecularity();\n\t\t\t\tlitArgs_specularity = dSpecularity;\n\t\t\t\tlitArgs_gloss = dGlossiness;\n\t\t\t#endif\n\t\t\t#ifdef STD_AO\n\t\t\t\tgetAO();\n\t\t\t\tlitArgs_ao = dAo;\n\t\t\t#endif\n\t\t\tgetEmission();\n\t\t\tlitArgs_emission = dEmission;\n\t\t\t#ifdef LIT_CLEARCOAT\n\t\t\t\tgetClearCoat();\n\t\t\t\tgetClearCoatGlossiness();\n\t\t\t\tgetClearCoatNormal();\n\t\t\t\tlitArgs_clearcoat_specularity = ccSpecularity;\n\t\t\t\tlitArgs_clearcoat_gloss = ccGlossiness;\n\t\t\t\tlitArgs_clearcoat_worldNormal = ccNormalW;\n\t\t\t#endif\n\t\t\t#if defined(LIT_SPECULAR) && defined(LIT_LIGHTING) && defined(LIT_GGX_SPECULAR)\n\t\t\t\tgetAnisotropy();\n\t\t\t#endif\n\t\t\t#if defined(STD_LIGHTMAP) || defined(STD_LIGHT_VERTEX_COLOR)\n\t\t\t\tgetLightMap();\n\t\t\t\tlitArgs_lightmap = dLightmap;\n\t\t\t\t#ifdef STD_LIGHTMAP_DIR\n\t\t\t\t\tlitArgs_lightmapDir = dLightmapDir;\n\t\t\t\t#endif\n\t\t\t#endif\n\t\t#endif\n\t}\n`;\n\nvar TBNPS = `\n#ifdef LIT_TANGENTS\n\t#define TBN_TANGENTS\n#else\n\t#if defined(LIT_USE_NORMALS) || defined(LIT_USE_CLEARCOAT_NORMALS)\n\t\t#define TBN_DERIVATIVES\n\t#endif\n#endif\n#if defined(TBN_DERIVATIVES)\n\tuniform tbnBasis: f32;\n#endif\nfn getTBN(tangent: vec3f, binormal: vec3f, normal: vec3f) {\n\t#ifdef TBN_TANGENTS\n\t\tdTBN = mat3x3f(normalize(tangent), normalize(binormal), normalize(normal));\n\t#elif defined(TBN_DERIVATIVES)\n\t\tlet uv: vec2f = {lightingUv};\n\t\tlet dp1: vec3f = dpdx( vPositionW );\n\t\tlet dp2: vec3f = dpdy( vPositionW );\n\t\tlet duv1: vec2f = dpdx( uv );\n\t\tlet duv2: vec2f = dpdy( uv );\n\t\tlet dp2perp: vec3f = cross( dp2, normal );\n\t\tlet dp1perp: vec3f = cross( normal, dp1 );\n\t\tlet T: vec3f = dp2perp * duv1.x + dp1perp * duv2.x;\n\t\tlet B: vec3f = dp2perp * duv1.y + dp1perp * duv2.y;\n\t\tlet denom: f32 = max( dot(T, T), dot(B, B) );\n\t\tlet invmax: f32 = select(uniform.tbnBasis / sqrt( denom ), 0.0, denom == 0.0);\n\t\tdTBN = mat3x3f(T * invmax, -B * invmax, normal );\n\t#else\n\t\tvar B: vec3f = cross(normal, vObjectSpaceUpW);\n\t\tvar T: vec3f = cross(normal, B);\n\t\tif (dot(B,B) == 0.0)\n\t\t{\n\t\t\tlet major: f32 = max(max(normal.x, normal.y), normal.z);\n\t\t\tif (normal.x == major)\n\t\t\t{\n\t\t\t\tB = cross(normal, vec3f(0.0, 1.0, 0.0));\n\t\t\t\tT = cross(normal, B);\n\t\t\t}\n\t\t\telse if (normal.y == major)\n\t\t\t{\n\t\t\t\tB = cross(normal, vec3f(0.0, 0.0, 1.0));\n\t\t\t\tT = cross(normal, B);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tB = cross(normal, vec3f(1.0, 0.0, 0.0));\n\t\t\t\tT = cross(normal, B);\n\t\t\t}\n\t\t}\n\t\tdTBN = mat3x3f(normalize(T), normalize(B), normalize(normal));\n\t#endif\n}`;\n\nvar thicknessPS = `\n#ifdef STD_THICKNESS_CONSTANT\nuniform material_thickness: f32;\n#endif\nfn getThickness() {\n\tdThickness = 1.0;\n\t#ifdef STD_THICKNESS_CONSTANT\n\tdThickness = dThickness * uniform.material_thickness;\n\t#endif\n\t#ifdef STD_THICKNESS_TEXTURE\n\tdThickness = dThickness * textureSampleBias({STD_THICKNESS_TEXTURE_NAME}, {STD_THICKNESS_TEXTURE_NAME}Sampler, {STD_THICKNESS_TEXTURE_UV}, uniform.textureBias).{STD_THICKNESS_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_THICKNESS_VERTEX\n\tdThickness = dThickness * saturate(vVertexColor.{STD_THICKNESS_VERTEX_CHANNEL});\n\t#endif\n}\n`;\n\nvar tonemappingPS = `\n#if (TONEMAP == NONE)\n\t#include \"tonemappingNonePS\"\n#elif TONEMAP == FILMIC\n\t#include \"tonemappingFilmicPS\"\n#elif TONEMAP == LINEAR\n\t#include \"tonemappingLinearPS\"\n#elif TONEMAP == HEJL\n\t#include \"tonemappingHejlPS\"\n#elif TONEMAP == ACES\n\t#include \"tonemappingAcesPS\"\n#elif TONEMAP == ACES2\n\t#include \"tonemappingAces2PS\"\n#elif TONEMAP == NEUTRAL\n\t#include \"tonemappingNeutralPS\"\n#endif\n`;\n\nvar tonemappingAcesPS = `\nuniform exposure: f32;\nfn toneMap(color: vec3f) -> vec3f {\n\tlet tA: f32 = 2.51;\n\tlet tB: f32 = 0.03;\n\tlet tC: f32 = 2.43;\n\tlet tD: f32 = 0.59;\n\tlet tE: f32 = 0.14;\n\tlet x: vec3f = color * uniform.exposure;\n\treturn (x * (tA * x + tB)) / (x * (tC * x + tD) + tE);\n}\n`;\n\nvar tonemappingAces2PS = `\nuniform exposure: f32;\nconst ACESInputMat: mat3x3f = mat3x3f(\n\tvec3f(0.59719, 0.35458, 0.04823),\n\tvec3f(0.07600, 0.90834, 0.01566),\n\tvec3f(0.02840, 0.13383, 0.83777)\n);\nconst ACESOutputMat: mat3x3f = mat3x3f(\n\tvec3f( 1.60475, -0.53108, -0.07367),\n\tvec3f(-0.10208,  1.10813, -0.00605),\n\tvec3f(-0.00327, -0.07276,  1.07602)\n);\nfn RRTAndODTFit(v: vec3f) -> vec3f {\n\tlet a: vec3f = v * (v + vec3f(0.0245786)) - vec3f(0.000090537);\n\tlet b: vec3f = v * (vec3f(0.983729) * v + vec3f(0.4329510)) + vec3f(0.238081);\n\treturn a / b;\n}\nfn toneMap(color: vec3f) -> vec3f {\n\tvar c: vec3f = color * (uniform.exposure / 0.6);\n\tc = c * ACESInputMat;\n\tc = RRTAndODTFit(c);\n\tc = c * ACESOutputMat;\n\treturn clamp(c, vec3f(0.0), vec3f(1.0));\n}\n`;\n\nvar tonemappingFilmicPS = `\nconst A: f32 = 0.15;\nconst B: f32 = 0.50;\nconst C: f32 = 0.10;\nconst D: f32 = 0.20;\nconst E: f32 = 0.02;\nconst F: f32 = 0.30;\nconst W: f32 = 11.2;\nuniform exposure: f32;\nfn uncharted2Tonemap(x: vec3f) -> vec3f {\n\treturn ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - vec3f(E / F);\n}\nfn toneMap(color: vec3f) -> vec3f {\n\tvar c: vec3f = uncharted2Tonemap(color * uniform.exposure);\n\tlet whiteScale: vec3f = vec3f(1.0) / uncharted2Tonemap(vec3f(W, W, W));\n\tc *= whiteScale;\n\treturn c;\n}\n`;\n\nvar tonemappingHejlPS = `\nuniform exposure: f32;\nfn toneMap(color: vec3f) -> vec3f {\n\tlet A: f32 = 0.22;\n\tlet B: f32 = 0.3;\n\tlet C: f32 = 0.1;\n\tlet D: f32 = 0.2;\n\tlet E: f32 = 0.01;\n\tlet F: f32 = 0.3;\n\tlet Scl: f32 = 1.25;\n\tlet adjusted_color = color * uniform.exposure;\n\tlet h = max(vec3f(0.0), adjusted_color - vec3f(0.004));\n\treturn (h * ((Scl * A) * h + Scl * vec3f(C * B)) + Scl * vec3f(D * E)) /\n\t\t   (h * (A * h + vec3f(B)) + vec3f(D * F)) -\n\t\t   Scl * vec3f(E / F);\n}\n`;\n\nvar tonemappingLinearPS = `\nuniform exposure: f32;\nfn toneMap(color: vec3f) -> vec3f {\n\treturn color * uniform.exposure;\n}\n`;\n\nvar tonemappingNeutralPS = `\nuniform exposure: f32;\nfn toneMap(col: vec3f) -> vec3f {\n\tvar color = col * uniform.exposure;\n\tlet startCompression = 0.8 - 0.04;\n\tlet desaturation = 0.15;\n\tlet x = min(color.r, min(color.g, color.b));\n\tlet offset = select(0.04, x - 6.25 * x * x, x < 0.08);\n\tcolor -= vec3f(offset);\n\tlet peak = max(color.r, max(color.g, color.b));\n\tif (peak < startCompression) {\n\t\treturn color;\n\t}\n\tlet d = 1.0 - startCompression;\n\tlet newPeak = 1.0 - d * d / (peak + d - startCompression);\n\tcolor *= newPeak / peak;\n\tlet g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);\n\treturn mix(color, vec3f(newPeak), vec3f(g));\n}\n`;\n\nvar tonemappingNonePS = `\nfn toneMap(color: vec3f) -> vec3f {\n\treturn color;\n}\n`;\n\nvar transformVS = `\n#ifdef PIXELSNAP\n\tuniform uScreenSize: vec4f;\n#endif\n#ifdef SCREENSPACE\n\tuniform projectionFlipY: f32;\n#endif\nfn evalWorldPosition(vertexPosition: vec3f, modelMatrix: mat4x4f) -> vec4f {\n\tvar localPos: vec3f = getLocalPosition(vertexPosition);\n\t#ifdef NINESLICED\n\t\tvar localPosXZ: vec2f = localPos.xz;\n\t\tlocalPosXZ = localPosXZ * uniform.outerScale;\n\t\tlet positiveUnitOffset: vec2f = clamp(vertexPosition.xz, vec2f(0.0), vec2f(1.0));\n\t\tlet negativeUnitOffset: vec2f = clamp(-vertexPosition.xz, vec2f(0.0), vec2f(1.0));\n\t\tlocalPosXZ = localPosXZ + (-positiveUnitOffset * uniform.innerOffset.xy + negativeUnitOffset * uniform.innerOffset.zw) * vertex_texCoord0.xy;\n\t\tdTiledUvGlobal = (localPosXZ - uniform.outerScale + uniform.innerOffset.xy) * -0.5 + 1.0;\n\t\tlocalPosXZ = localPosXZ * -0.5;\n\t\tlocalPos = vec3f(localPosXZ.x, localPosXZ.y, localPos.y);\n\t#endif\n\tvar posW: vec4f = modelMatrix * vec4f(localPos, 1.0);\n\t#ifdef SCREENSPACE\n\t\tposW = vec4f(posW.xy, 0.0, 1.0);\n\t#endif\n\treturn posW;\n}\nfn getPosition() -> vec4f {\n\tdModelMatrix = getModelMatrix();\n\tlet posW: vec4f = evalWorldPosition(vertex_position.xyz, dModelMatrix);\n\tdPositionW = posW.xyz;\n\tvar screenPos: vec4f;\n\t#ifdef UV1LAYOUT\n\t\tscreenPos = vec4f(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1.0);\n\t\tscreenPos.y *= -1.0;\n\t#else\n\t\t#ifdef SCREENSPACE\n\t\t\tscreenPos = posW;\n\t\t\tscreenPos.y *= uniform.projectionFlipY;\n\t\t#else\n\t\t\tscreenPos = uniform.matrix_viewProjection * posW;\n\t\t#endif\n\t\t#ifdef PIXELSNAP\n\t\t\tscreenPos.xy = (screenPos.xy * 0.5) + 0.5;\n\t\t\tscreenPos.xy *= uniforms.uScreenSize.xy;\n\t\t\tscreenPos.xy = floor(screenPos.xy);\n\t\t\tscreenPos.xy *= uniforms.uScreenSize.zw;\n\t\t\tscreenPos.xy = (screenPos.xy * 2.0) - 1.0;\n\t\t#endif\n\t#endif\n\treturn screenPos;\n}\nfn getWorldPosition() -> vec3f {\n\treturn dPositionW;\n}\n`;\n\nvar transformCoreVS = `\n\tattribute vertex_position: vec4f;\n\tuniform matrix_viewProjection: mat4x4f;\n\tuniform matrix_model: mat4x4f;\n\t\n\t#ifdef MORPHING\n\t\tuniform morph_tex_params: vec2f;\n\t\tattribute morph_vertex_id: u32;\n\t\tfn getTextureMorphCoords() -> vec2i {\n\t\t\tvar textureSize: vec2i = vec2i(uniform.morph_tex_params);\n\t\t\tvar morphGridV: i32 = i32(morph_vertex_id) / textureSize.x;\n\t\t\tvar morphGridU: i32 = i32(morph_vertex_id) - (morphGridV * textureSize.x);\n\t\t\tmorphGridV = textureSize.y - morphGridV - 1;\n\t\t\treturn vec2i(morphGridU, morphGridV);\n\t\t}\n\t\t#ifdef MORPHING_POSITION\n\t\t\t#ifdef MORPHING_INT\n\t\t\t\tuniform aabbSize: vec3f;\n\t\t\t\tuniform aabbMin: vec3f;\n\t\t\t\tvar morphPositionTex: texture_2d<u32>;\n\t\t\t#else\n\t\t\t\tvar morphPositionTex: texture_2d<f32>;\n\t\t\t#endif\n\t\t#endif\n\t#endif\n\t#ifdef defined(BATCH)\n\t\t#include \"skinBatchVS\"\n\t\tfn getModelMatrix() -> mat4x4f {\n\t\t\treturn getBoneMatrix(vertex_boneIndices);\n\t\t}\n\t#elif defined(SKIN)\n\t\t#include \"skinVS\"\n\t\tfn getModelMatrix() -> mat4x4f {\n\t\t\treturn uniform.matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);\n\t\t}\n\t#elif defined(INSTANCING)\n\t\t#include \"transformInstancingVS\"\n\t#else\n\t\tfn getModelMatrix() -> mat4x4f {\n\t\t\treturn uniform.matrix_model;\n\t\t}\n\t#endif\n\tfn getLocalPosition(vertexPosition: vec3f) -> vec3f {\n\t\tvar localPos: vec3f = vertexPosition;\n\t\t#ifdef MORPHING_POSITION\n\t\t\tvar morphUV: vec2i = getTextureMorphCoords();\n\t\t\t#ifdef MORPHING_INT\n\t\t\t\tvar morphPos: vec3f = vec3f(textureLoad(morphPositionTex, morphUV, 0).xyz) / 65535.0 * uniform.aabbSize + uniform.aabbMin;\n\t\t\t#else\n\t\t\t\tvar morphPos: vec3f = textureLoad(morphPositionTex, morphUV, 0).xyz;\n\t\t\t#endif\n\t\t\tlocalPos += morphPos;\n\t\t#endif\n\t\treturn localPos;\n\t}\n`;\n\nvar transformInstancingVS = `\nattribute instance_line1: vec4f;\nattribute instance_line2: vec4f;\nattribute instance_line3: vec4f;\nattribute instance_line4: vec4f;\nfn getModelMatrix() -> mat4x4f {\n\treturn uniform.matrix_model * mat4x4f(instance_line1, instance_line2, instance_line3, instance_line4);\n}\n`;\n\nvar transmissionPS = `\n#ifdef STD_REFRACTION_CONSTANT\n\tuniform material_refraction: f32;\n#endif\nfn getRefraction() {\n\tvar refraction: f32 = 1.0;\n\t#ifdef STD_REFRACTION_CONSTANT\n\trefraction = uniform.material_refraction;\n\t#endif\n\t#ifdef STD_REFRACTION_TEXTURE\n\trefraction = refraction * textureSampleBias({STD_REFRACTION_TEXTURE_NAME}, {STD_REFRACTION_TEXTURE_NAME}Sampler, {STD_REFRACTION_TEXTURE_UV}, uniform.textureBias).{STD_REFRACTION_TEXTURE_CHANNEL};\n\t#endif\n\t#ifdef STD_REFRACTION_VERTEX\n\trefraction = refraction * saturate(vVertexColor.{STD_REFRACTION_VERTEX_CHANNEL});\n\t#endif\n\tdTransmission = refraction;\n}\n`;\n\nvar twoSidedLightingPS = `\nuniform twoSidedLightingNegScaleFactor: f32;\nfn handleTwoSidedLighting() {\n\tdTBN[2] = dTBN[2] * select(-uniform.twoSidedLightingNegScaleFactor, uniform.twoSidedLightingNegScaleFactor, pcFrontFacing);\n}\n`;\n\nvar uv0VS = `\n#ifdef NINESLICED\n\tfn getUv0() -> vec2f {\n\t\tvar uv = vertex_position.xz;\n\t\tlet positiveUnitOffset = clamp(vertex_position.xz, vec2f(0.0, 0.0), vec2f(1.0, 1.0));\n\t\tlet negativeUnitOffset = clamp(-vertex_position.xz, vec2f(0.0, 0.0), vec2f(1.0, 1.0));\n\t\tuv = uv + ((-positiveUnitOffset * uniform.innerOffset.xy) + (negativeUnitOffset * uniform.innerOffset.zw)) * vertex_texCoord0.xy;\n\t\tuv = uv * -0.5 + vec2f(0.5, 0.5);\n\t\tuv = uv * uniform.atlasRect.zw + uniform.atlasRect.xy;\n\t\tdMaskGlobal = vertex_texCoord0.xy;\n\t\treturn uv;\n\t}\n#else\n\tfn getUv0() -> vec2f {\n\t\treturn vertex_texCoord0;\n\t}\n#endif\n`;\n\nvar uv1VS = `\nfn getUv1() -> vec2f {\n\treturn vertex_texCoord1;\n}\n`;\n\nvar uvTransformVS = `\noutput.vUV{TRANSFORM_UV_{i}}_{TRANSFORM_ID_{i}} = vec2f(\n\tdot(vec3f(uv{TRANSFORM_UV_{i}}, 1), uniform.{TRANSFORM_NAME_{i}}0),\n\tdot(vec3f(uv{TRANSFORM_UV_{i}}, 1), uniform.{TRANSFORM_NAME_{i}}1)\n);\n`;\n\nvar uvTransformUniformsPS = `\n\tuniform {TRANSFORM_NAME_{i}}0: vec3f;\n\tuniform {TRANSFORM_NAME_{i}}1: vec3f;\n`;\n\nvar viewDirPS = `\nfn getViewDir() {\n\tdViewDirW = normalize(uniform.view_position - vPositionW);\n}\n`;\n\nconst shaderChunksWGSL = {\n\t\talphaTestPS,\n\t\tambientPS,\n\t\tanisotropyPS,\n\t\taoPS,\n\t\taoDiffuseOccPS,\n\t\taoSpecOccPS,\n\t\tbakeDirLmEndPS,\n\t\tbakeLmEndPS,\n\t\tbasePS,\n\t\tbaseNineSlicedPS,\n\t\tbaseNineSlicedTiledPS,\n\t\tbayerPS,\n\t\tblurVSMPS,\n\t\tclearCoatPS,\n\t\tclearCoatGlossPS,\n\t\tclearCoatNormalPS,\n\t\tclusteredLightCookiesPS,\n\t\tclusteredLightShadowsPS,\n\t\tclusteredLightUtilsPS,\n\t\tclusteredLightPS,\n\t\tcombinePS,\n\t\tcookieBlit2DPS,\n\t\tcookieBlitCubePS,\n\t\tcookieBlitVS,\n\t\tcubeMapProjectPS,\n\t\tcubeMapRotatePS,\n\t\tdebugOutputPS,\n\t\tdebugProcessFrontendPS,\n\t\tdetailModesPS,\n\t\tdiffusePS,\n\t\tdecodePS,\n\t\temissivePS,\n\t\tencodePS,\n\t\tendPS,\n\t\tenvAtlasPS,\n\t\tenvProcPS,\n\t\tfalloffInvSquaredPS,\n\t\tfalloffLinearPS,\n\t\tfloatAsUintPS,\n\t\tfogPS,\n\t\tfresnelSchlickPS,\n\t\tfrontendCodePS: '',\n\t\tfrontendDeclPS: '',\n\t\tfullscreenQuadVS,\n\t\tgammaPS,\n\t\tglossPS,\n\t\tgsplatCenterVS,\n\t\tgsplatCornerVS,\n\t\tgsplatColorVS,\n\t\tgsplatCommonVS,\n\t\tgsplatCompressedDataVS,\n\t\tgsplatCompressedSHVS,\n\t\tgsplatEvalSHVS,\n\t\tgsplatSourceFormatVS,\n\t\tgsplatStructsVS,\n\t\tgsplatQuatToMat3VS,\n\t\tgsplatSogsColorVS,\n\t\tgsplatSogsDataVS,\n\t\tgsplatSogsSHVS,\n\t\tgsplatDataVS,\n\t\tgsplatOutputVS,\n\t\tgsplatPS,\n\t\tgsplatSHVS,\n\t\tgsplatSourceVS,\n\t\tgsplatVS,\n\t\tgsplatWorkBufferVS,\n\t\tgsplatPackingPS,\n\t\tquadVS,\n\t\tindirectCoreCS,\n\t\timmediateLinePS,\n\t\timmediateLineVS,\n\t\tiridescenceDiffractionPS,\n\t\tiridescencePS,\n\t\tiridescenceThicknessPS,\n\t\tiorPS,\n\t\tlightDeclarationPS,\n\t\tlightDiffuseLambertPS,\n\t\tlightDirPointPS,\n\t\tlightEvaluationPS,\n\t\tlightFunctionLightPS,\n\t\tlightFunctionShadowPS,\n\t\tlightingPS,\n\t\tlightmapAddPS,\n\t\tlightmapPS,\n\t\tlightSpecularAnisoGGXPS,\n\t\tlightSpecularBlinnPS,\n\t\tlightSheenPS,\n\t\tlinearizeDepthPS,\n\t\tlitForwardBackendPS,\n\t\tlitForwardDeclarationPS,\n\t\tlitForwardMainPS,\n\t\tlitForwardPostCodePS,\n\t\tlitForwardPreCodePS,\n\t\tlitMainPS,\n\t\tlitMainVS,\n\t\tlitOtherMainPS,\n\t\tlitShaderArgsPS,\n\t\tlitShaderCorePS,\n\t\tlitShadowMainPS,\n\t\tlitUserDeclarationPS: '',\n\t\tlitUserDeclarationVS: '',\n\t\tlitUserCodePS: '',\n\t\tlitUserCodeVS: '',\n\t\tlitUserMainStartPS: '',\n\t\tlitUserMainStartVS: '',\n\t\tlitUserMainEndPS: '',\n\t\tlitUserMainEndVS: '',\n\t\tltcPS,\n\t\tmetalnessPS,\n\t\tmetalnessModulatePS,\n\t\tmorphPS,\n\t\tmorphVS,\n\t\tmsdfPS,\n\t\tmsdfVS,\n\t\tnormalVS,\n\t\tnormalCoreVS,\n\t\tnormalMapPS,\n\t\topacityPS,\n\t\topacityDitherPS,\n\t\toutputPS,\n\t\toutputAlphaPS,\n\t\toutputTex2DPS,\n\t\tsheenPS,\n\t\tsheenGlossPS,\n\t\tparallaxPS,\n\t\tpickPS,\n\t\treflDirPS,\n\t\treflDirAnisoPS,\n\t\treflectionCCPS,\n\t\treflectionCubePS,\n\t\treflectionEnvHQPS,\n\t\treflectionEnvPS,\n\t\treflectionSpherePS,\n\t\treflectionSheenPS,\n\t\trefractionCubePS,\n\t\trefractionDynamicPS,\n\t\treprojectPS,\n\t\treprojectVS,\n\t\tscreenDepthPS,\n\t\tshadowCascadesPS,\n\t\tshadowEVSMPS,\n\t\tshadowPCF1PS,\n\t\tshadowPCF3PS,\n\t\tshadowPCF5PS,\n\t\tshadowSoftPS,\n\t\tskinBatchVS,\n\t\tskinVS,\n\t\tskyboxPS,\n\t\tskyboxVS,\n\t\tspecularPS,\n\t\tsphericalPS,\n\t\tspecularityFactorPS,\n\t\tspotPS,\n\t\tstartNineSlicedPS,\n\t\tstartNineSlicedTiledPS,\n\t\tstdDeclarationPS,\n\t\tstdFrontEndPS,\n\t\tTBNPS,\n\t\tthicknessPS,\n\t\ttonemappingPS,\n\t\ttonemappingAcesPS,\n\t\ttonemappingAces2PS,\n\t\ttonemappingFilmicPS,\n\t\ttonemappingHejlPS,\n\t\ttonemappingLinearPS,\n\t\ttonemappingNeutralPS,\n\t\ttonemappingNonePS,\n\t\ttransformVS,\n\t\ttransformCoreVS,\n\t\ttransformInstancingVS,\n\t\ttransmissionPS,\n\t\ttwoSidedLightingPS,\n\t\tuv0VS,\n\t\tuv1VS,\n\t\tuvTransformVS,\n\t\tuvTransformUniformsPS,\n\t\tviewDirPS,\n\t\twebgpuPS,\n\t\twebgpuVS\n};\n\nclass AppBase extends EventHandler {\n\t\tconstructor(canvas){\n\t\t\t\tsuper(), this._batcher = null, this._destroyRequested = false, this._inFrameUpdate = false, this._librariesLoaded = false, this._fillMode = FILLMODE_KEEP_ASPECT, this._resolutionMode = RESOLUTION_FIXED, this._allowResize = true, this._skyboxAsset = null, this._entityIndex = {}, this._inTools = false, this._scriptPrefix = '', this._time = 0, this.enableBundles = typeof TextDecoder !== 'undefined', this.timeScale = 1, this.maxDeltaTime = 0.1, this.frame = 0, this.frameGraph = new FrameGraph(), this.scriptsOrder = [], this.autoRender = true, this.renderNextFrame = false, this.lightmapper = null, this.loader = new ResourceLoader(this), this.scenes = new SceneRegistry(this), this.scripts = new ScriptRegistry(this), this.systems = new ComponentSystemRegistry(), this.i18n = new I18n(this), this.keyboard = null, this.mouse = null, this.touch = null, this.gamepads = null, this.elementInput = null, this.xr = null;\n\t\t\t\tAppBase._applications[canvas.id] = this;\n\t\t\t\tsetApplication(this);\n\t\t\t\tthis.root = new Entity();\n\t\t\t\tthis.root._enabledInHierarchy = true;\n\t\t}\n\t\tinit(appOptions) {\n\t\t\t\tconst { assetPrefix, batchManager, componentSystems, elementInput, gamepads, graphicsDevice, keyboard, lightmapper, mouse, resourceHandlers, scriptsOrder, scriptPrefix, soundManager, touch, xr } = appOptions;\n\t\t\t\tthis.graphicsDevice = graphicsDevice;\n\t\t\t\tShaderChunks.get(graphicsDevice, SHADERLANGUAGE_GLSL).add(shaderChunksGLSL);\n\t\t\t\tShaderChunks.get(graphicsDevice, SHADERLANGUAGE_WGSL).add(shaderChunksWGSL);\n\t\t\t\tthis._initDefaultMaterial();\n\t\t\t\tthis._initProgramLibrary();\n\t\t\t\tthis.stats = new ApplicationStats(graphicsDevice);\n\t\t\t\tthis._soundManager = soundManager;\n\t\t\t\tthis.scene = new Scene(graphicsDevice);\n\t\t\t\tthis._registerSceneImmediate(this.scene);\n\t\t\t\tthis.assets = new AssetRegistry(this.loader);\n\t\t\t\tif (assetPrefix) this.assets.prefix = assetPrefix;\n\t\t\t\tthis.bundles = new BundleRegistry(this.assets);\n\t\t\t\tthis.scriptsOrder = scriptsOrder || [];\n\t\t\t\tthis.defaultLayerWorld = new Layer({\n\t\t\t\t\t\tname: 'World',\n\t\t\t\t\t\tid: LAYERID_WORLD\n\t\t\t\t});\n\t\t\t\tthis.defaultLayerDepth = new Layer({\n\t\t\t\t\t\tname: 'Depth',\n\t\t\t\t\t\tid: LAYERID_DEPTH,\n\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t\topaqueSortMode: SORTMODE_NONE\n\t\t\t\t});\n\t\t\t\tthis.defaultLayerSkybox = new Layer({\n\t\t\t\t\t\tname: 'Skybox',\n\t\t\t\t\t\tid: LAYERID_SKYBOX,\n\t\t\t\t\t\topaqueSortMode: SORTMODE_NONE\n\t\t\t\t});\n\t\t\t\tthis.defaultLayerUi = new Layer({\n\t\t\t\t\t\tname: 'UI',\n\t\t\t\t\t\tid: LAYERID_UI,\n\t\t\t\t\t\ttransparentSortMode: SORTMODE_MANUAL\n\t\t\t\t});\n\t\t\t\tthis.defaultLayerImmediate = new Layer({\n\t\t\t\t\t\tname: 'Immediate',\n\t\t\t\t\t\tid: LAYERID_IMMEDIATE,\n\t\t\t\t\t\topaqueSortMode: SORTMODE_NONE\n\t\t\t\t});\n\t\t\t\tconst defaultLayerComposition = new LayerComposition('default');\n\t\t\t\tdefaultLayerComposition.pushOpaque(this.defaultLayerWorld);\n\t\t\t\tdefaultLayerComposition.pushOpaque(this.defaultLayerDepth);\n\t\t\t\tdefaultLayerComposition.pushOpaque(this.defaultLayerSkybox);\n\t\t\t\tdefaultLayerComposition.pushTransparent(this.defaultLayerWorld);\n\t\t\t\tdefaultLayerComposition.pushOpaque(this.defaultLayerImmediate);\n\t\t\t\tdefaultLayerComposition.pushTransparent(this.defaultLayerImmediate);\n\t\t\t\tdefaultLayerComposition.pushTransparent(this.defaultLayerUi);\n\t\t\t\tthis.scene.layers = defaultLayerComposition;\n\t\t\t\tAreaLightLuts.createPlaceholder(graphicsDevice);\n\t\t\t\tthis.renderer = new ForwardRenderer(graphicsDevice, this.scene);\n\t\t\t\tif (lightmapper) {\n\t\t\t\t\t\tthis.lightmapper = new lightmapper(graphicsDevice, this.root, this.scene, this.renderer, this.assets);\n\t\t\t\t\t\tthis.once('prerender', this._firstBake, this);\n\t\t\t\t}\n\t\t\t\tif (batchManager) {\n\t\t\t\t\t\tthis._batcher = new batchManager(graphicsDevice, this.root, this.scene);\n\t\t\t\t\t\tthis.once('prerender', this._firstBatch, this);\n\t\t\t\t}\n\t\t\t\tthis.keyboard = keyboard || null;\n\t\t\t\tthis.mouse = mouse || null;\n\t\t\t\tthis.touch = touch || null;\n\t\t\t\tthis.gamepads = gamepads || null;\n\t\t\t\tif (elementInput) {\n\t\t\t\t\t\tthis.elementInput = elementInput;\n\t\t\t\t\t\tthis.elementInput.app = this;\n\t\t\t\t}\n\t\t\t\tthis.xr = xr ? new xr(this) : null;\n\t\t\t\tif (this.elementInput) this.elementInput.attachSelectEvents();\n\t\t\t\tthis._scriptPrefix = scriptPrefix || '';\n\t\t\t\tif (this.enableBundles) {\n\t\t\t\t\t\tthis.loader.addHandler('bundle', new BundleHandler(this));\n\t\t\t\t}\n\t\t\t\tresourceHandlers.forEach((resourceHandler)=>{\n\t\t\t\t\t\tconst handler = new resourceHandler(this);\n\t\t\t\t\t\tthis.loader.addHandler(handler.handlerType, handler);\n\t\t\t\t});\n\t\t\t\tcomponentSystems.forEach((componentSystem)=>{\n\t\t\t\t\t\tthis.systems.add(new componentSystem(this));\n\t\t\t\t});\n\t\t\t\tthis._visibilityChangeHandler = this.onVisibilityChange.bind(this);\n\t\t\t\tif (typeof document !== 'undefined') {\n\t\t\t\t\t\tif (document.hidden !== undefined) {\n\t\t\t\t\t\t\t\tthis._hiddenAttr = 'hidden';\n\t\t\t\t\t\t\t\tdocument.addEventListener('visibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t\t\t} else if (document.mozHidden !== undefined) {\n\t\t\t\t\t\t\t\tthis._hiddenAttr = 'mozHidden';\n\t\t\t\t\t\t\t\tdocument.addEventListener('mozvisibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t\t\t} else if (document.msHidden !== undefined) {\n\t\t\t\t\t\t\t\tthis._hiddenAttr = 'msHidden';\n\t\t\t\t\t\t\t\tdocument.addEventListener('msvisibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t\t\t} else if (document.webkitHidden !== undefined) {\n\t\t\t\t\t\t\t\tthis._hiddenAttr = 'webkitHidden';\n\t\t\t\t\t\t\t\tdocument.addEventListener('webkitvisibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.tick = makeTick(this);\n\t\t}\n\t\tstatic{\n\t\t\t\tthis._applications = {};\n\t\t}\n\t\tstatic getApplication(id) {\n\t\t\t\treturn id ? AppBase._applications[id] : getApplication();\n\t\t}\n\t\t_initDefaultMaterial() {\n\t\t\t\tconst material = new StandardMaterial();\n\t\t\t\tmaterial.name = 'Default Material';\n\t\t\t\tsetDefaultMaterial(this.graphicsDevice, material);\n\t\t}\n\t\t_initProgramLibrary() {\n\t\t\t\tconst library = new ProgramLibrary(this.graphicsDevice, new StandardMaterial());\n\t\t\t\tsetProgramLibrary(this.graphicsDevice, library);\n\t\t}\n\t\tget soundManager() {\n\t\t\t\treturn this._soundManager;\n\t\t}\n\t\tget batcher() {\n\t\t\t\treturn this._batcher;\n\t\t}\n\t\tget fillMode() {\n\t\t\t\treturn this._fillMode;\n\t\t}\n\t\tget resolutionMode() {\n\t\t\t\treturn this._resolutionMode;\n\t\t}\n\t\tconfigure(url, callback) {\n\t\t\t\thttp.get(url, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst props = response.application_properties;\n\t\t\t\t\t\tconst scenes = response.scenes;\n\t\t\t\t\t\tconst assets = response.assets;\n\t\t\t\t\t\tthis._parseApplicationProperties(props, (err)=>{\n\t\t\t\t\t\t\t\tthis._parseScenes(scenes);\n\t\t\t\t\t\t\t\tthis._parseAssets(assets);\n\t\t\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\t\t\tcallback(null);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t}\n\t\tpreload(callback) {\n\t\t\t\tthis.fire('preload:start');\n\t\t\t\tconst assets = this.assets.list({\n\t\t\t\t\t\tpreload: true\n\t\t\t\t});\n\t\t\t\tif (assets.length === 0) {\n\t\t\t\t\t\tthis.fire('preload:end');\n\t\t\t\t\t\tcallback();\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet loadedCount = 0;\n\t\t\t\tconst onAssetLoadOrError = ()=>{\n\t\t\t\t\t\tloadedCount++;\n\t\t\t\t\t\tthis.fire('preload:progress', loadedCount / assets.length);\n\t\t\t\t\t\tif (loadedCount === assets.length) {\n\t\t\t\t\t\t\t\tthis.fire('preload:end');\n\t\t\t\t\t\t\t\tcallback();\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tassets.forEach((asset)=>{\n\t\t\t\t\t\tif (!asset.loaded) {\n\t\t\t\t\t\t\t\tasset.once('load', onAssetLoadOrError);\n\t\t\t\t\t\t\t\tasset.once('error', onAssetLoadOrError);\n\t\t\t\t\t\t\t\tthis.assets.load(asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tonAssetLoadOrError();\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\t_preloadScripts(sceneData, callback) {\n\t\t\t\tcallback();\n\t\t}\n\t\t_parseApplicationProperties(props, callback) {\n\t\t\t\tif (typeof props.maxAssetRetries === 'number' && props.maxAssetRetries > 0) {\n\t\t\t\t\t\tthis.loader.enableRetry(props.maxAssetRetries);\n\t\t\t\t}\n\t\t\t\tif (!props.useDevicePixelRatio) {\n\t\t\t\t\t\tprops.useDevicePixelRatio = props.use_device_pixel_ratio;\n\t\t\t\t}\n\t\t\t\tif (!props.resolutionMode) {\n\t\t\t\t\t\tprops.resolutionMode = props.resolution_mode;\n\t\t\t\t}\n\t\t\t\tif (!props.fillMode) {\n\t\t\t\t\t\tprops.fillMode = props.fill_mode;\n\t\t\t\t}\n\t\t\t\tthis._width = props.width;\n\t\t\t\tthis._height = props.height;\n\t\t\t\tif (props.useDevicePixelRatio) {\n\t\t\t\t\t\tthis.graphicsDevice.maxPixelRatio = window.devicePixelRatio;\n\t\t\t\t}\n\t\t\t\tthis.setCanvasResolution(props.resolutionMode, this._width, this._height);\n\t\t\t\tthis.setCanvasFillMode(props.fillMode, this._width, this._height);\n\t\t\t\tif (props.layers && props.layerOrder) {\n\t\t\t\t\t\tconst composition = new LayerComposition('application');\n\t\t\t\t\t\tconst layers = {};\n\t\t\t\t\t\tfor(const key in props.layers){\n\t\t\t\t\t\t\t\tconst data = props.layers[key];\n\t\t\t\t\t\t\t\tdata.id = parseInt(key, 10);\n\t\t\t\t\t\t\t\tdata.enabled = data.id !== LAYERID_DEPTH;\n\t\t\t\t\t\t\t\tlayers[key] = new Layer(data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0, len = props.layerOrder.length; i < len; i++){\n\t\t\t\t\t\t\t\tconst sublayer = props.layerOrder[i];\n\t\t\t\t\t\t\t\tconst layer = layers[sublayer.layer];\n\t\t\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\t\t\tif (sublayer.transparent) {\n\t\t\t\t\t\t\t\t\t\tcomposition.pushTransparent(layer);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcomposition.pushOpaque(layer);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcomposition.subLayerEnabled[i] = sublayer.enabled;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.scene.layers = composition;\n\t\t\t\t}\n\t\t\t\tif (props.batchGroups) {\n\t\t\t\t\t\tconst batcher = this.batcher;\n\t\t\t\t\t\tif (batcher) {\n\t\t\t\t\t\t\t\tfor(let i = 0, len = props.batchGroups.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tconst grp = props.batchGroups[i];\n\t\t\t\t\t\t\t\t\t\tbatcher.addGroup(grp.name, grp.dynamic, grp.maxAabbSize, grp.id, grp.layers);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (props.i18nAssets) {\n\t\t\t\t\t\tthis.i18n.assets = props.i18nAssets;\n\t\t\t\t}\n\t\t\t\tthis._loadLibraries(props.libraries, callback);\n\t\t}\n\t\t_loadLibraries(urls, callback) {\n\t\t\t\tconst len = urls.length;\n\t\t\t\tlet count = len;\n\t\t\t\tconst regex = /^https?:\\/\\//;\n\t\t\t\tif (len) {\n\t\t\t\t\t\tconst onLoad = (err, script)=>{\n\t\t\t\t\t\t\t\tcount--;\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t} else if (count === 0) {\n\t\t\t\t\t\t\t\t\t\tthis.onLibrariesLoaded();\n\t\t\t\t\t\t\t\t\t\tcallback(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\t\t\tlet url = urls[i];\n\t\t\t\t\t\t\t\tif (!regex.test(url.toLowerCase()) && this._scriptPrefix) {\n\t\t\t\t\t\t\t\t\t\turl = path.join(this._scriptPrefix, url);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.loader.load(url, \"script\", onLoad);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.onLibrariesLoaded();\n\t\t\t\t\t\tcallback(null);\n\t\t\t\t}\n\t\t}\n\t\t_parseScenes(scenes) {\n\t\t\t\tif (!scenes) return;\n\t\t\t\tfor(let i = 0; i < scenes.length; i++){\n\t\t\t\t\t\tthis.scenes.add(scenes[i].name, scenes[i].url);\n\t\t\t\t}\n\t\t}\n\t\t_parseAssets(assets) {\n\t\t\t\tconst list = [];\n\t\t\t\tconst scriptsIndex = {};\n\t\t\t\tconst bundlesIndex = {};\n\t\t\t\tfor(let i = 0; i < this.scriptsOrder.length; i++){\n\t\t\t\t\t\tconst id = this.scriptsOrder[i];\n\t\t\t\t\t\tif (!assets[id]) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscriptsIndex[id] = true;\n\t\t\t\t\t\tlist.push(assets[id]);\n\t\t\t\t}\n\t\t\t\tif (this.enableBundles) {\n\t\t\t\t\t\tfor(const id in assets){\n\t\t\t\t\t\t\t\tif (assets[id].type === 'bundle') {\n\t\t\t\t\t\t\t\t\t\tbundlesIndex[id] = true;\n\t\t\t\t\t\t\t\t\t\tlist.push(assets[id]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(const id in assets){\n\t\t\t\t\t\tif (scriptsIndex[id] || bundlesIndex[id]) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlist.push(assets[id]);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < list.length; i++){\n\t\t\t\t\t\tconst data = list[i];\n\t\t\t\t\t\tconst asset = new Asset(data.name, data.type, data.file, data.data);\n\t\t\t\t\t\tasset.id = parseInt(data.id, 10);\n\t\t\t\t\t\tasset.preload = data.preload ? data.preload : false;\n\t\t\t\t\t\tasset.loaded = data.type === \"script\" && data.data && data.data.loadingType > 0;\n\t\t\t\t\t\tasset.tags.add(data.tags);\n\t\t\t\t\t\tif (data.i18n) {\n\t\t\t\t\t\t\t\tfor(const locale in data.i18n){\n\t\t\t\t\t\t\t\t\t\tasset.addLocalizedAssetId(locale, data.i18n[locale]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.assets.add(asset);\n\t\t\t\t}\n\t\t}\n\t\tstart() {\n\t\t\t\tthis.frame = 0;\n\t\t\t\tthis.fire('start', {\n\t\t\t\t\t\ttimestamp: now(),\n\t\t\t\t\t\ttarget: this\n\t\t\t\t});\n\t\t\t\tif (!this._librariesLoaded) {\n\t\t\t\t\t\tthis.onLibrariesLoaded();\n\t\t\t\t}\n\t\t\t\tthis.systems.fire('initialize', this.root);\n\t\t\t\tthis.fire('initialize');\n\t\t\t\tthis.systems.fire('postInitialize', this.root);\n\t\t\t\tthis.systems.fire('postPostInitialize', this.root);\n\t\t\t\tthis.fire('postinitialize');\n\t\t\t\tthis.tick();\n\t\t}\n\t\tinputUpdate(dt) {\n\t\t\t\tif (this.controller) {\n\t\t\t\t\t\tthis.controller.update(dt);\n\t\t\t\t}\n\t\t\t\tif (this.mouse) {\n\t\t\t\t\t\tthis.mouse.update();\n\t\t\t\t}\n\t\t\t\tif (this.keyboard) {\n\t\t\t\t\t\tthis.keyboard.update();\n\t\t\t\t}\n\t\t\t\tif (this.gamepads) {\n\t\t\t\t\t\tthis.gamepads.update();\n\t\t\t\t}\n\t\t}\n\t\tupdate(dt) {\n\t\t\t\tthis.frame++;\n\t\t\t\tthis.graphicsDevice.update();\n\t\t\t\tthis.systems.fire(this._inTools ? 'toolsUpdate' : 'update', dt);\n\t\t\t\tthis.systems.fire('animationUpdate', dt);\n\t\t\t\tthis.systems.fire('postUpdate', dt);\n\t\t\t\tthis.fire('update', dt);\n\t\t\t\tthis.inputUpdate(dt);\n\t\t}\n\t\trender() {\n\t\t\t\tthis.updateCanvasSize();\n\t\t\t\tthis.graphicsDevice.frameStart();\n\t\t\t\tthis.fire('prerender');\n\t\t\t\tthis.root.syncHierarchy();\n\t\t\t\tif (this._batcher) {\n\t\t\t\t\t\tthis._batcher.updateAll();\n\t\t\t\t}\n\t\t\t\tthis.renderComposition(this.scene.layers);\n\t\t\t\tthis.fire('postrender');\n\t\t\t\tthis.graphicsDevice.frameEnd();\n\t\t}\n\t\trenderComposition(layerComposition) {\n\t\t\t\tthis.renderer.update(layerComposition);\n\t\t\t\tthis.renderer.buildFrameGraph(this.frameGraph, layerComposition);\n\t\t\t\tthis.frameGraph.render(this.graphicsDevice);\n\t\t}\n\t\t_fillFrameStatsBasic(now, dt, ms) {\n\t\t\t\tconst stats = this.stats.frame;\n\t\t\t\tstats.dt = dt;\n\t\t\t\tstats.ms = ms;\n\t\t\t\tif (now > stats._timeToCountFrames) {\n\t\t\t\t\t\tstats.fps = stats._fpsAccum;\n\t\t\t\t\t\tstats._fpsAccum = 0;\n\t\t\t\t\t\tstats._timeToCountFrames = now + 1000;\n\t\t\t\t} else {\n\t\t\t\t\t\tstats._fpsAccum++;\n\t\t\t\t}\n\t\t\t\tthis.stats.drawCalls.total = this.graphicsDevice._drawCallsPerFrame;\n\t\t\t\tthis.graphicsDevice._drawCallsPerFrame = 0;\n\t\t}\n\t\t_fillFrameStats() {\n\t\t\t\tlet stats = this.stats.frame;\n\t\t\t\tstats.cameras = this.renderer._camerasRendered;\n\t\t\t\tstats.materials = this.renderer._materialSwitches;\n\t\t\t\tstats.shaders = this.graphicsDevice._shaderSwitchesPerFrame;\n\t\t\t\tstats.shadowMapUpdates = this.renderer._shadowMapUpdates;\n\t\t\t\tstats.shadowMapTime = this.renderer._shadowMapTime;\n\t\t\t\tstats.depthMapTime = this.renderer._depthMapTime;\n\t\t\t\tstats.forwardTime = this.renderer._forwardTime;\n\t\t\t\tconst prims = this.graphicsDevice._primsPerFrame;\n\t\t\t\tstats.triangles = prims[PRIMITIVE_TRIANGLES] / 3 + Math.max(prims[PRIMITIVE_TRISTRIP] - 2, 0) + Math.max(prims[PRIMITIVE_TRIFAN] - 2, 0);\n\t\t\t\tstats.cullTime = this.renderer._cullTime;\n\t\t\t\tstats.sortTime = this.renderer._sortTime;\n\t\t\t\tstats.skinTime = this.renderer._skinTime;\n\t\t\t\tstats.morphTime = this.renderer._morphTime;\n\t\t\t\tstats.lightClusters = this.renderer._lightClusters;\n\t\t\t\tstats.lightClustersTime = this.renderer._lightClustersTime;\n\t\t\t\tstats.otherPrimitives = 0;\n\t\t\t\tfor(let i = 0; i < prims.length; i++){\n\t\t\t\t\t\tif (i < PRIMITIVE_TRIANGLES) {\n\t\t\t\t\t\t\t\tstats.otherPrimitives += prims[i];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprims[i] = 0;\n\t\t\t\t}\n\t\t\t\tthis.renderer._camerasRendered = 0;\n\t\t\t\tthis.renderer._materialSwitches = 0;\n\t\t\t\tthis.renderer._shadowMapUpdates = 0;\n\t\t\t\tthis.graphicsDevice._shaderSwitchesPerFrame = 0;\n\t\t\t\tthis.renderer._cullTime = 0;\n\t\t\t\tthis.renderer._layerCompositionUpdateTime = 0;\n\t\t\t\tthis.renderer._lightClustersTime = 0;\n\t\t\t\tthis.renderer._sortTime = 0;\n\t\t\t\tthis.renderer._skinTime = 0;\n\t\t\t\tthis.renderer._morphTime = 0;\n\t\t\t\tthis.renderer._shadowMapTime = 0;\n\t\t\t\tthis.renderer._depthMapTime = 0;\n\t\t\t\tthis.renderer._forwardTime = 0;\n\t\t\t\tstats = this.stats.drawCalls;\n\t\t\t\tstats.forward = this.renderer._forwardDrawCalls;\n\t\t\t\tstats.culled = this.renderer._numDrawCallsCulled;\n\t\t\t\tstats.depth = 0;\n\t\t\t\tstats.shadow = this.renderer._shadowDrawCalls;\n\t\t\t\tstats.skinned = this.renderer._skinDrawCalls;\n\t\t\t\tstats.immediate = 0;\n\t\t\t\tstats.instanced = 0;\n\t\t\t\tstats.removedByInstancing = 0;\n\t\t\t\tstats.misc = stats.total - (stats.forward + stats.shadow);\n\t\t\t\tthis.renderer._depthDrawCalls = 0;\n\t\t\t\tthis.renderer._shadowDrawCalls = 0;\n\t\t\t\tthis.renderer._forwardDrawCalls = 0;\n\t\t\t\tthis.renderer._numDrawCallsCulled = 0;\n\t\t\t\tthis.renderer._skinDrawCalls = 0;\n\t\t\t\tthis.renderer._immediateRendered = 0;\n\t\t\t\tthis.renderer._instancedDrawCalls = 0;\n\t\t\t\tthis.stats.misc.renderTargetCreationTime = this.graphicsDevice.renderTargetCreationTime;\n\t\t\t\tstats = this.stats.particles;\n\t\t\t\tstats.updatesPerFrame = stats._updatesPerFrame;\n\t\t\t\tstats.frameTime = stats._frameTime;\n\t\t\t\tstats._updatesPerFrame = 0;\n\t\t\t\tstats._frameTime = 0;\n\t\t}\n\t\tsetCanvasFillMode(mode, width, height) {\n\t\t\t\tthis._fillMode = mode;\n\t\t\t\tthis.resizeCanvas(width, height);\n\t\t}\n\t\tsetCanvasResolution(mode, width, height) {\n\t\t\t\tthis._resolutionMode = mode;\n\t\t\t\tif (mode === RESOLUTION_AUTO && width === undefined) {\n\t\t\t\t\t\twidth = this.graphicsDevice.canvas.clientWidth;\n\t\t\t\t\t\theight = this.graphicsDevice.canvas.clientHeight;\n\t\t\t\t}\n\t\t\t\tthis.graphicsDevice.resizeCanvas(width, height);\n\t\t}\n\t\tisHidden() {\n\t\t\t\treturn document[this._hiddenAttr];\n\t\t}\n\t\tonVisibilityChange() {\n\t\t\t\tif (this.isHidden()) {\n\t\t\t\t\t\tif (this._soundManager) {\n\t\t\t\t\t\t\t\tthis._soundManager.suspend();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this._soundManager) {\n\t\t\t\t\t\t\t\tthis._soundManager.resume();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tresizeCanvas(width, height) {\n\t\t\t\tif (!this._allowResize) return undefined;\n\t\t\t\tif (this.xr && this.xr.session) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tconst windowWidth = window.innerWidth;\n\t\t\t\tconst windowHeight = window.innerHeight;\n\t\t\t\tif (this._fillMode === FILLMODE_KEEP_ASPECT) {\n\t\t\t\t\t\tconst r = this.graphicsDevice.canvas.width / this.graphicsDevice.canvas.height;\n\t\t\t\t\t\tconst winR = windowWidth / windowHeight;\n\t\t\t\t\t\tif (r > winR) {\n\t\t\t\t\t\t\t\twidth = windowWidth;\n\t\t\t\t\t\t\t\theight = width / r;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\theight = windowHeight;\n\t\t\t\t\t\t\t\twidth = height * r;\n\t\t\t\t\t\t}\n\t\t\t\t} else if (this._fillMode === FILLMODE_FILL_WINDOW) {\n\t\t\t\t\t\twidth = windowWidth;\n\t\t\t\t\t\theight = windowHeight;\n\t\t\t\t}\n\t\t\t\tthis.graphicsDevice.canvas.style.width = `${width}px`;\n\t\t\t\tthis.graphicsDevice.canvas.style.height = `${height}px`;\n\t\t\t\tthis.updateCanvasSize();\n\t\t\t\treturn {\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height\n\t\t\t\t};\n\t\t}\n\t\tupdateCanvasSize() {\n\t\t\t\tif (!this._allowResize || this.xr?.active) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._resolutionMode === RESOLUTION_AUTO) {\n\t\t\t\t\t\tconst canvas = this.graphicsDevice.canvas;\n\t\t\t\t\t\tthis.graphicsDevice.resizeCanvas(canvas.clientWidth, canvas.clientHeight);\n\t\t\t\t}\n\t\t}\n\t\tonLibrariesLoaded() {\n\t\t\t\tthis._librariesLoaded = true;\n\t\t\t\tif (this.systems.rigidbody) {\n\t\t\t\t\t\tthis.systems.rigidbody.onLibraryLoaded();\n\t\t\t\t}\n\t\t}\n\t\tapplySceneSettings(settings) {\n\t\t\t\tlet asset;\n\t\t\t\tif (this.systems.rigidbody && typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tconst [x, y, z] = settings.physics.gravity;\n\t\t\t\t\t\tthis.systems.rigidbody.gravity.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tthis.scene.applySettings(settings);\n\t\t\t\tif (settings.render.hasOwnProperty('skybox')) {\n\t\t\t\t\t\tif (settings.render.skybox) {\n\t\t\t\t\t\t\t\tasset = this.assets.get(settings.render.skybox);\n\t\t\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\t\t\tthis.setSkybox(asset);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.assets.once(`add:${settings.render.skybox}`, this.setSkybox, this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.setSkybox(null);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetAreaLightLuts(ltcMat1, ltcMat2) {\n\t\t\t\tif (ltcMat1 && ltcMat2) {\n\t\t\t\t\t\tAreaLightLuts.set(this.graphicsDevice, ltcMat1, ltcMat2);\n\t\t\t\t}\n\t\t}\n\t\tsetSkybox(asset) {\n\t\t\t\tif (asset !== this._skyboxAsset) {\n\t\t\t\t\t\tconst onSkyboxRemoved = ()=>{\n\t\t\t\t\t\t\t\tthis.setSkybox(null);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onSkyboxChanged = ()=>{\n\t\t\t\t\t\t\t\tthis.scene.setSkybox(this._skyboxAsset ? this._skyboxAsset.resources : null);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (this._skyboxAsset) {\n\t\t\t\t\t\t\t\tthis.assets.off(`load:${this._skyboxAsset.id}`, onSkyboxChanged, this);\n\t\t\t\t\t\t\t\tthis.assets.off(`remove:${this._skyboxAsset.id}`, onSkyboxRemoved, this);\n\t\t\t\t\t\t\t\tthis._skyboxAsset.off('change', onSkyboxChanged, this);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._skyboxAsset = asset;\n\t\t\t\t\t\tif (this._skyboxAsset) {\n\t\t\t\t\t\t\t\tthis.assets.on(`load:${this._skyboxAsset.id}`, onSkyboxChanged, this);\n\t\t\t\t\t\t\t\tthis.assets.once(`remove:${this._skyboxAsset.id}`, onSkyboxRemoved, this);\n\t\t\t\t\t\t\t\tthis._skyboxAsset.on('change', onSkyboxChanged, this);\n\t\t\t\t\t\t\t\tif (this.scene.skyboxMip === 0 && !this._skyboxAsset.loadFaces) {\n\t\t\t\t\t\t\t\t\t\tthis._skyboxAsset.loadFaces = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.assets.load(this._skyboxAsset);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonSkyboxChanged();\n\t\t\t\t}\n\t\t}\n\t\t_firstBake() {\n\t\t\t\tthis.lightmapper?.bake(null, this.scene.lightmapMode);\n\t\t}\n\t\t_firstBatch() {\n\t\t\t\tthis.batcher?.generate();\n\t\t}\n\t\t_processTimestamp(timestamp) {\n\t\t\t\treturn timestamp;\n\t\t}\n\t\tdrawLine(start, end, color, depthTest, layer) {\n\t\t\t\tthis.scene.drawLine(start, end, color, depthTest, layer);\n\t\t}\n\t\tdrawLines(positions, colors, depthTest = true, layer = this.scene.defaultDrawLayer) {\n\t\t\t\tthis.scene.drawLines(positions, colors, depthTest, layer);\n\t\t}\n\t\tdrawLineArrays(positions, colors, depthTest = true, layer = this.scene.defaultDrawLayer) {\n\t\t\t\tthis.scene.drawLineArrays(positions, colors, depthTest, layer);\n\t\t}\n\t\tdrawWireSphere(center, radius, color = Color.WHITE, segments = 20, depthTest = true, layer = this.scene.defaultDrawLayer) {\n\t\t\t\tthis.scene.immediate.drawWireSphere(center, radius, color, segments, depthTest, layer);\n\t\t}\n\t\tdrawWireAlignedBox(minPoint, maxPoint, color = Color.WHITE, depthTest = true, layer = this.scene.defaultDrawLayer, mat) {\n\t\t\t\tthis.scene.immediate.drawWireAlignedBox(minPoint, maxPoint, color, depthTest, layer, mat);\n\t\t}\n\t\tdrawMeshInstance(meshInstance, layer = this.scene.defaultDrawLayer) {\n\t\t\t\tthis.scene.immediate.drawMesh(null, null, null, meshInstance, layer);\n\t\t}\n\t\tdrawMesh(mesh, material, matrix, layer = this.scene.defaultDrawLayer) {\n\t\t\t\tthis.scene.immediate.drawMesh(material, matrix, mesh, null, layer);\n\t\t}\n\t\tdrawQuad(matrix, material, layer = this.scene.defaultDrawLayer) {\n\t\t\t\tthis.scene.immediate.drawMesh(material, matrix, this.scene.immediate.getQuadMesh(), null, layer);\n\t\t}\n\t\tdrawTexture(x, y, width, height, texture, material, layer = this.scene.defaultDrawLayer, filterable = true) {\n\t\t\t\tif (filterable === false && !this.graphicsDevice.isWebGPU) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst matrix = new Mat4();\n\t\t\t\tmatrix.setTRS(new Vec3(x, y, 0.0), Quat.IDENTITY, new Vec3(width, -height, 0.0));\n\t\t\t\tif (!material) {\n\t\t\t\t\t\tmaterial = new ShaderMaterial();\n\t\t\t\t\t\tmaterial.cull = CULLFACE_NONE;\n\t\t\t\t\t\tmaterial.setParameter('colorMap', texture);\n\t\t\t\t\t\tmaterial.shaderDesc = filterable ? this.scene.immediate.getTextureShaderDesc(texture.encoding) : this.scene.immediate.getUnfilterableTextureShaderDesc();\n\t\t\t\t\t\tmaterial.update();\n\t\t\t\t}\n\t\t\t\tthis.drawQuad(matrix, material, layer);\n\t\t}\n\t\tdrawDepthTexture(x, y, width, height, layer = this.scene.defaultDrawLayer) {\n\t\t\t\tconst material = new ShaderMaterial();\n\t\t\t\tmaterial.cull = CULLFACE_NONE;\n\t\t\t\tmaterial.shaderDesc = this.scene.immediate.getDepthTextureShaderDesc();\n\t\t\t\tmaterial.update();\n\t\t\t\tthis.drawTexture(x, y, width, height, null, material, layer);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (this._inFrameUpdate) {\n\t\t\t\t\t\tthis._destroyRequested = true;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst canvasId = this.graphicsDevice.canvas.id;\n\t\t\t\tthis.fire('destroy', this);\n\t\t\t\tthis.off('librariesloaded');\n\t\t\t\tif (typeof document !== 'undefined') {\n\t\t\t\t\t\tdocument.removeEventListener('visibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t\t\tdocument.removeEventListener('mozvisibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t\t\tdocument.removeEventListener('msvisibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t\t\tdocument.removeEventListener('webkitvisibilitychange', this._visibilityChangeHandler, false);\n\t\t\t\t}\n\t\t\t\tthis._visibilityChangeHandler = null;\n\t\t\t\tthis.root.destroy();\n\t\t\t\tthis.root = null;\n\t\t\t\tif (this.mouse) {\n\t\t\t\t\t\tthis.mouse.off();\n\t\t\t\t\t\tthis.mouse.detach();\n\t\t\t\t\t\tthis.mouse = null;\n\t\t\t\t}\n\t\t\t\tif (this.keyboard) {\n\t\t\t\t\t\tthis.keyboard.off();\n\t\t\t\t\t\tthis.keyboard.detach();\n\t\t\t\t\t\tthis.keyboard = null;\n\t\t\t\t}\n\t\t\t\tif (this.touch) {\n\t\t\t\t\t\tthis.touch.off();\n\t\t\t\t\t\tthis.touch.detach();\n\t\t\t\t\t\tthis.touch = null;\n\t\t\t\t}\n\t\t\t\tif (this.elementInput) {\n\t\t\t\t\t\tthis.elementInput.detach();\n\t\t\t\t\t\tthis.elementInput = null;\n\t\t\t\t}\n\t\t\t\tif (this.gamepads) {\n\t\t\t\t\t\tthis.gamepads.destroy();\n\t\t\t\t\t\tthis.gamepads = null;\n\t\t\t\t}\n\t\t\t\tif (this.controller) {\n\t\t\t\t\t\tthis.controller = null;\n\t\t\t\t}\n\t\t\t\tthis.systems.destroy();\n\t\t\t\tif (this.scene.layers) {\n\t\t\t\t\t\tthis.scene.layers.destroy();\n\t\t\t\t}\n\t\t\t\tconst assets = this.assets.list();\n\t\t\t\tfor(let i = 0; i < assets.length; i++){\n\t\t\t\t\t\tassets[i].unload();\n\t\t\t\t\t\tassets[i].off();\n\t\t\t\t}\n\t\t\t\tthis.assets.off();\n\t\t\t\tthis.bundles.destroy();\n\t\t\t\tthis.bundles = null;\n\t\t\t\tthis.i18n.destroy();\n\t\t\t\tthis.i18n = null;\n\t\t\t\tconst scriptHandler = this.loader.getHandler(\"script\");\n\t\t\t\tscriptHandler?.clearCache();\n\t\t\t\tthis.loader.destroy();\n\t\t\t\tthis.loader = null;\n\t\t\t\tthis.scene.destroy();\n\t\t\t\tthis.scene = null;\n\t\t\t\tthis.systems = null;\n\t\t\t\tthis.context = null;\n\t\t\t\tthis.scripts.destroy();\n\t\t\t\tthis.scripts = null;\n\t\t\t\tthis.scenes.destroy();\n\t\t\t\tthis.scenes = null;\n\t\t\t\tthis.lightmapper?.destroy();\n\t\t\t\tthis.lightmapper = null;\n\t\t\t\tif (this._batcher) {\n\t\t\t\t\t\tthis._batcher.destroy();\n\t\t\t\t\t\tthis._batcher = null;\n\t\t\t\t}\n\t\t\t\tthis._entityIndex = {};\n\t\t\t\tthis.defaultLayerDepth.onDisable = null;\n\t\t\t\tthis.defaultLayerDepth.onEnable = null;\n\t\t\t\tthis.defaultLayerDepth = null;\n\t\t\t\tthis.defaultLayerWorld = null;\n\t\t\t\tthis.xr?.end();\n\t\t\t\tthis.xr?.destroy();\n\t\t\t\tthis.renderer.destroy();\n\t\t\t\tthis.renderer = null;\n\t\t\t\tthis.graphicsDevice.destroy();\n\t\t\t\tthis.graphicsDevice = null;\n\t\t\t\tthis.tick = null;\n\t\t\t\tthis.off();\n\t\t\t\tthis._soundManager?.destroy();\n\t\t\t\tthis._soundManager = null;\n\t\t\t\tAppBase._applications[canvasId] = null;\n\t\t\t\tif (getApplication() === this) {\n\t\t\t\t\t\tsetApplication(null);\n\t\t\t\t}\n\t\t\t\tAppBase.cancelTick(this);\n\t\t}\n\t\tstatic cancelTick(app) {\n\t\t\t\tif (app.frameRequestId) {\n\t\t\t\t\t\twindow.cancelAnimationFrame(app.frameRequestId);\n\t\t\t\t\t\tapp.frameRequestId = undefined;\n\t\t\t\t}\n\t\t}\n\t\tgetEntityFromIndex(guid) {\n\t\t\t\treturn this._entityIndex[guid];\n\t\t}\n\t\t_registerSceneImmediate(scene) {\n\t\t\t\tthis.on('postrender', scene.immediate.onPostRender, scene.immediate);\n\t\t}\n}\nconst makeTick = function(_app) {\n\t\tconst application = _app;\n\t\treturn function(timestamp, xrFrame) {\n\t\t\t\tif (!application.graphicsDevice) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (application.frameRequestId) {\n\t\t\t\t\t\tapplication.xr?.session?.cancelAnimationFrame(application.frameRequestId);\n\t\t\t\t\t\tcancelAnimationFrame(application.frameRequestId);\n\t\t\t\t\t\tapplication.frameRequestId = null;\n\t\t\t\t}\n\t\t\t\tapplication._inFrameUpdate = true;\n\t\t\t\tsetApplication(application);\n\t\t\t\tconst currentTime = application._processTimestamp(timestamp) || now();\n\t\t\t\tconst ms = currentTime - (application._time || currentTime);\n\t\t\t\tlet dt = ms / 1000.0;\n\t\t\t\tdt = math.clamp(dt, 0, application.maxDeltaTime);\n\t\t\t\tdt *= application.timeScale;\n\t\t\t\tapplication._time = currentTime;\n\t\t\t\tif (application.xr?.session) {\n\t\t\t\t\t\tapplication.frameRequestId = application.xr.session.requestAnimationFrame(application.tick);\n\t\t\t\t} else {\n\t\t\t\t\t\tapplication.frameRequestId = platform.browser || platform.worker ? requestAnimationFrame(application.tick) : null;\n\t\t\t\t}\n\t\t\t\tif (application.graphicsDevice.contextLost) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tapplication._fillFrameStatsBasic(currentTime, dt, ms);\n\t\t\t\tapplication.fire('frameupdate', ms);\n\t\t\t\tlet skipUpdate = false;\n\t\t\t\tif (xrFrame) {\n\t\t\t\t\t\tskipUpdate = !application.xr?.update(xrFrame);\n\t\t\t\t\t\tapplication.graphicsDevice.defaultFramebuffer = xrFrame.session.renderState.baseLayer.framebuffer;\n\t\t\t\t} else {\n\t\t\t\t\t\tapplication.graphicsDevice.defaultFramebuffer = null;\n\t\t\t\t}\n\t\t\t\tif (!skipUpdate) {\n\t\t\t\t\t\tapplication.update(dt);\n\t\t\t\t\t\tapplication.fire('framerender');\n\t\t\t\t\t\tif (application.autoRender || application.renderNextFrame) {\n\t\t\t\t\t\t\t\tapplication.render();\n\t\t\t\t\t\t\t\tapplication.renderNextFrame = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tapplication.fire('frameend');\n\t\t\t\t}\n\t\t\t\tapplication._inFrameUpdate = false;\n\t\t\t\tif (application._destroyRequested) {\n\t\t\t\t\t\tapplication.destroy();\n\t\t\t\t}\n\t\t};\n};\n\nclass AppOptions {\n\t\tconstructor(){\n\t\t\t\tthis.componentSystems = [];\n\t\t\t\tthis.resourceHandlers = [];\n\t\t}\n}\n\nconst tempSphere = new BoundingSphere();\nclass BakeLight {\n\t\tconstructor(scene, light, lightingParams){\n\t\t\t\tthis.scene = scene;\n\t\t\t\tthis.light = light;\n\t\t\t\tthis.store();\n\t\t\t\tlight.numCascades = 1;\n\t\t\t\tif (this.scene.clusteredLightingEnabled && !lightingParams.shadowsEnabled) {\n\t\t\t\t\t\tlight.castShadows = false;\n\t\t\t\t}\n\t\t\t\tif (light.type !== LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\tlight._node.getWorldTransform();\n\t\t\t\t\t\tlight.getBoundingSphere(tempSphere);\n\t\t\t\t\t\tthis.lightBounds = new BoundingBox();\n\t\t\t\t\t\tthis.lightBounds.center.copy(tempSphere.center);\n\t\t\t\t\t\tthis.lightBounds.halfExtents.set(tempSphere.radius, tempSphere.radius, tempSphere.radius);\n\t\t\t\t}\n\t\t}\n\t\tstore() {\n\t\t\t\tthis.mask = this.light.mask;\n\t\t\t\tthis.shadowUpdateMode = this.light.shadowUpdateMode;\n\t\t\t\tthis.enabled = this.light.enabled;\n\t\t\t\tthis.intensity = this.light.intensity;\n\t\t\t\tthis.rotation = this.light._node.getLocalRotation().clone();\n\t\t\t\tthis.numCascades = this.light.numCascades;\n\t\t\t\tthis.castShadows = this.light._castShadows;\n\t\t}\n\t\trestore() {\n\t\t\t\tconst light = this.light;\n\t\t\t\tlight.mask = this.mask;\n\t\t\t\tlight.shadowUpdateMode = this.shadowUpdateMode;\n\t\t\t\tlight.enabled = this.enabled;\n\t\t\t\tlight.intensity = this.intensity;\n\t\t\t\tlight._node.setLocalRotation(this.rotation);\n\t\t\t\tlight.numCascades = this.numCascades;\n\t\t\t\tlight._castShadows = this.castShadows;\n\t\t}\n\t\tstartBake() {\n\t\t\t\tthis.light.enabled = true;\n\t\t\t\tthis.light._destroyShadowMap();\n\t\t\t\tthis.light.beginFrame();\n\t\t}\n\t\tendBake(shadowMapCache) {\n\t\t\t\tconst light = this.light;\n\t\t\t\tlight.enabled = false;\n\t\t\t\tif (light.shadowMap) {\n\t\t\t\t\t\tif (light.shadowMap.cached) {\n\t\t\t\t\t\t\t\tshadowMapCache.add(light, light.shadowMap);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlight.shadowMap = null;\n\t\t\t\t}\n\t\t}\n}\n\nconst _tempPoint$1 = new Vec2();\nclass BakeLightSimple extends BakeLight {\n\t\tconstructor(lightmapper, light){\n\t\t\t\tsuper(lightmapper.scene, light, lightmapper.lightingParams);\n\t\t}\n\t\tget numVirtualLights() {\n\t\t\t\tif (this.light.type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\treturn this.light.bakeNumSamples;\n\t\t\t\t}\n\t\t\t\treturn 1;\n\t\t}\n\t\tprepareVirtualLight(index, numVirtualLights) {\n\t\t\t\tconst light = this.light;\n\t\t\t\tlight._node.setLocalRotation(this.rotation);\n\t\t\t\tif (index > 0) {\n\t\t\t\t\t\tconst directionalSpreadAngle = light.bakeArea;\n\t\t\t\t\t\trandom.circlePointDeterministic(_tempPoint$1, index, numVirtualLights);\n\t\t\t\t\t\t_tempPoint$1.mulScalar(directionalSpreadAngle * 0.5);\n\t\t\t\t\t\tlight._node.rotateLocal(_tempPoint$1.x, 0, _tempPoint$1.y);\n\t\t\t\t}\n\t\t\t\tlight._node.getWorldTransform();\n\t\t\t\tconst gamma = 2.2;\n\t\t\t\tconst linearIntensity = Math.pow(this.intensity, gamma);\n\t\t\t\tlight.intensity = Math.pow(linearIntensity / numVirtualLights, 1 / gamma);\n\t\t}\n}\n\nconst _tempPoint = new Vec3();\nclass BakeLightAmbient extends BakeLight {\n\t\tconstructor(lightmapper){\n\t\t\t\tconst scene = lightmapper.scene;\n\t\t\t\tconst lightEntity = new Entity('AmbientLight');\n\t\t\t\tlightEntity.addComponent('light', {\n\t\t\t\t\t\ttype: 'directional',\n\t\t\t\t\t\taffectDynamic: true,\n\t\t\t\t\t\taffectLightmapped: false,\n\t\t\t\t\t\tbake: true,\n\t\t\t\t\t\tbakeNumSamples: scene.ambientBakeNumSamples,\n\t\t\t\t\t\tcastShadows: true,\n\t\t\t\t\t\tnormalOffsetBias: 0.05,\n\t\t\t\t\t\tshadowBias: 0.2,\n\t\t\t\t\t\tshadowDistance: 1,\n\t\t\t\t\t\tshadowResolution: 2048,\n\t\t\t\t\t\tshadowType: SHADOW_PCF3_32F,\n\t\t\t\t\t\tcolor: Color.WHITE,\n\t\t\t\t\t\tintensity: 1,\n\t\t\t\t\t\tbakeDir: false\n\t\t\t\t});\n\t\t\t\tsuper(scene, lightEntity.light.light, lightmapper.lightingParams);\n\t\t}\n\t\tget numVirtualLights() {\n\t\t\t\treturn this.light.bakeNumSamples;\n\t\t}\n\t\tprepareVirtualLight(index, numVirtualLights) {\n\t\t\t\trandom.spherePointDeterministic(_tempPoint, index, numVirtualLights, 0, this.scene.ambientBakeSpherePart);\n\t\t\t\tthis.light._node.lookAt(_tempPoint.mulScalar(-1));\n\t\t\t\tthis.light._node.rotateLocal(90, 0, 0);\n\t\t\t\tconst gamma = 2.2;\n\t\t\t\tconst fullIntensity = 2 * Math.PI * this.scene.ambientBakeSpherePart;\n\t\t\t\tconst linearIntensity = Math.pow(fullIntensity, gamma);\n\t\t\t\tthis.light.intensity = Math.pow(linearIntensity / numVirtualLights, 1 / gamma);\n\t\t}\n}\n\nclass BakeMeshNode {\n\t\tconstructor(node, meshInstances = null){\n\t\t\t\tthis.node = node;\n\t\t\t\tthis.component = node.render || node.model;\n\t\t\t\tmeshInstances = meshInstances || this.component.meshInstances;\n\t\t\t\tthis.store();\n\t\t\t\tthis.meshInstances = meshInstances;\n\t\t\t\tthis.bounds = null;\n\t\t\t\tthis.renderTargets = [];\n\t\t}\n\t\tstore() {\n\t\t\t\tthis.castShadows = this.component.castShadows;\n\t\t}\n\t\trestore() {\n\t\t\t\tthis.component.castShadows = this.castShadows;\n\t\t}\n}\n\nvar glslBilateralDeNoisePS = `\nfloat normpdf3(in vec3 v, in float sigma) {\n\treturn 0.39894 * exp(-0.5 * dot(v, v) / (sigma * sigma)) / sigma;\n}\nvec3 decodeRGBM(vec4 rgbm) {\n\tvec3 color = (8.0 * rgbm.a) * rgbm.rgb;\n\treturn color * color;\n}\nfloat saturate(float x) {\n\treturn clamp(x, 0.0, 1.0);\n}\nvec4 encodeRGBM(vec3 color) {\n\tvec4 encoded;\n\tencoded.rgb = pow(color.rgb, vec3(0.5));\n\tencoded.rgb *= 1.0 / 8.0;\n\tencoded.a = saturate( max( max( encoded.r, encoded.g ), max( encoded.b, 1.0 / 255.0 ) ) );\n\tencoded.a = ceil(encoded.a * 255.0) / 255.0;\n\tencoded.rgb /= encoded.a;\n\treturn encoded;\n}\nvec3 decode(vec4 pixel) {\n\t#if HDR\n\t\treturn pixel.rgb;\n\t#else\n\t\treturn decodeRGBM(pixel);\n\t#endif\n}\nbool isUsed(vec4 pixel) {\n\t#if HDR\n\t\treturn any(greaterThan(pixel.rgb, vec3(0.0)));\n\t#else\n\t\treturn pixel.a > 0.0;\n\t#endif\n}\nvarying vec2 vUv0;\nuniform sampler2D source;\nuniform vec2 pixelOffset;\nuniform vec2 sigmas;\nuniform float bZnorm;\nuniform float kernel[{MSIZE}];\nvoid main(void) {\n\t\n\tvec4 pixel = texture2DLod(source, vUv0, 0.0);\n\tif (!isUsed(pixel)) {\n\t\tgl_FragColor = pixel;\n\t\treturn ;\n\t}\n\tfloat sigma = sigmas.x;\n\tfloat bSigma = sigmas.y;\n\tvec3 pixelHdr = decode(pixel);\n\tvec3 accumulatedHdr = vec3(0.0);\n\tfloat accumulatedFactor = 0.000001;\n\tconst int kSize = ({MSIZE} - 1) / 2;\n\tfor (int i = -kSize; i <= kSize; ++i) {\n\t\tfor (int j = -kSize; j <= kSize; ++j) {\n\t\t\t\n\t\t\tvec2 coord = vUv0 + vec2(float(i), float(j)) * pixelOffset;\n\t\t\tvec4 pix = texture2DLod(source, coord, 0.0);\n\t\t\tif (isUsed(pix)) {\n\t\t\t\tvec3 hdr = decode(pix);\n\t\t\t\tfloat factor = kernel[kSize + j] * kernel[kSize + i];\n\t\t\t\tfactor *= normpdf3(hdr - pixelHdr, bSigma) * bZnorm;\n\t\t\t\taccumulatedHdr += factor * hdr;\n\t\t\t\taccumulatedFactor += factor;\n\t\t\t}\n\t\t}\n\t}\n\tvec3 finalHDR = accumulatedHdr / accumulatedFactor;\n\t#if HDR\n\t\tgl_FragColor = vec4(finalHDR, 1.0);\n\t#else\n\t\tgl_FragColor = encodeRGBM(finalHDR);\n\t#endif\n}\n`;\n\nvar glslDilatePS = `\nvarying vec2 vUv0;\nuniform sampler2D source;\nuniform vec2 pixelOffset;\nbool isUsed(vec4 pixel) {\n\t#if HDR\n\t\treturn any(greaterThan(pixel.rgb, vec3(0.0)));\n\t#else\n\t\treturn pixel.a > 0.0;\n\t#endif\n}\nvoid main(void) {\n\tvec4 c = texture2DLod(source, vUv0, 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 - pixelOffset, 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(0, -pixelOffset.y), 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(pixelOffset.x, -pixelOffset.y), 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(-pixelOffset.x, 0), 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(pixelOffset.x, 0), 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(-pixelOffset.x, pixelOffset.y), 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(0, pixelOffset.y), 0.0);\n\tc = isUsed(c) ? c : texture2DLod(source, vUv0 + pixelOffset, 0.0);\n\tgl_FragColor = c;\n}\n`;\n\nvar wgslBilateralDeNoisePS = `\nfn normpdf3(v: vec3f, sigma: f32) -> f32 {\n\treturn 0.39894 * exp(-0.5 * dot(v, v) / (sigma * sigma)) / sigma;\n}\nfn decodeRGBM(rgbm: vec4f) -> vec3f {\n\tlet color = (8.0 * rgbm.a) * rgbm.rgb;\n\treturn color * color;\n}\nfn saturate(x: f32) -> f32 {\n\treturn clamp(x, 0.0, 1.0);\n}\nfn encodeRGBM(color: vec3f) -> vec4f {\n\tvar encoded: vec4f;\n\tlet rgb_processed = pow(color.rgb, vec3f(0.5)) * (1.0 / 8.0);\n\tencoded = vec4f(rgb_processed, 0.0);\n\tlet max_g_b = max( encoded.g, max( encoded.b, 1.0 / 255.0 ) );\n\tlet max_rgb = max( encoded.r, max_g_b );\n\tencoded.a = clamp(max_rgb, 0.0, 1.0);\n\tencoded.a = ceil(encoded.a * 255.0) / 255.0;\n\tencoded = vec4f(encoded.rgb / encoded.a, encoded.a);\n\treturn encoded;\n}\nfn decode(pixel: vec4f) -> vec3f {\n\t#if HDR\n\t\treturn pixel.rgb;\n\t#else\n\t\treturn decodeRGBM(pixel);\n\t#endif\n}\nfn isUsed(pixel: vec4f) -> bool {\n\t#if HDR\n\t\treturn any(pixel.rgb > vec3f(0.0));\n\t#else\n\t\treturn pixel.a > 0.0;\n\t#endif\n}\nvarying vUv0: vec2f;\nvar source: texture_2d<f32>;\nvar sourceSampler: sampler;\nuniform kernel: array<f32, {MSIZE}>;\nuniform pixelOffset: vec2f;\nuniform sigmas: vec2f;\nuniform bZnorm: f32;\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\tlet pixel = textureSampleLevel(source, sourceSampler, input.vUv0, 0.0);\n\tif (!isUsed(pixel)) {\n\t\toutput.color = pixel;\n\t\treturn output;\n\t}\n\tlet sigma = uniform.sigmas.x;\n\tlet bSigma = uniform.sigmas.y;\n\tlet pixelHdr = decode(pixel);\n\tvar accumulatedHdr = vec3f(0.0);\n\tvar accumulatedFactor = 0.000001;\n\tconst kSize = ({MSIZE} - 1) / 2;\n\tfor (var i: i32 = -kSize; i <= kSize; i = i + 1) {\n\t\tfor (var j: i32 = -kSize; j <= kSize; j = j + 1) {\n\t\t\tlet coord = input.vUv0 + vec2f(f32(i), f32(j)) * uniform.pixelOffset;\n\t\t\tlet pix = textureSampleLevel(source, sourceSampler, coord, 0.0);\n\t\t\tif (isUsed(pix)) {\n\t\t\t\tlet hdr = decode(pix);\n\t\t\t\tvar factor = uniform.kernel[u32(kSize + j)].element * uniform.kernel[u32(kSize + i)].element;\n\t\t\t\tfactor = factor * normpdf3(hdr - pixelHdr, bSigma) * uniform.bZnorm;\n\t\t\t\taccumulatedHdr = accumulatedHdr + factor * hdr;\n\t\t\t\taccumulatedFactor = accumulatedFactor + factor;\n\t\t\t}\n\t\t}\n\t}\n\tlet finalHDR = accumulatedHdr / accumulatedFactor;\n\t#if HDR\n\t\toutput.color = vec4f(finalHDR, 1.0);\n\t#else\n\t\toutput.color = encodeRGBM(finalHDR);\n\t#endif\n\treturn output;\n}\n`;\n\nvar wgslDilatePS = `\nvarying vUv0: vec2f;\nvar source: texture_2d<f32>;\nvar sourceSampler: sampler;\nuniform pixelOffset: vec2f;\nfn isUsed(pixel: vec4f) -> bool {\n\t#ifdef HDR\n\t\treturn any(pixel.rgb > vec3f(0.0));\n\t#else\n\t\treturn pixel.a > 0.0;\n\t#endif\n}\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar c: vec4f = textureSampleLevel(source, sourceSampler, input.vUv0, 0.0);\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 - uniform.pixelOffset, 0.0), c, isUsed(c));\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 + vec2f(0.0, -uniform.pixelOffset.y), 0.0), c, isUsed(c));\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 + vec2f(uniform.pixelOffset.x, -uniform.pixelOffset.y), 0.0), c, isUsed(c));\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 + vec2f(-uniform.pixelOffset.x, 0.0), 0.0), c, isUsed(c));\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 + vec2f(uniform.pixelOffset.x, 0.0), 0.0), c, isUsed(c));\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 + vec2f(-uniform.pixelOffset.x, uniform.pixelOffset.y), 0.0), c, isUsed(c));\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 + vec2f(0.0, uniform.pixelOffset.y), 0.0), c, isUsed(c));\n\tc = select(textureSampleLevel(source, sourceSampler, input.vUv0 + uniform.pixelOffset, 0.0), c, isUsed(c));\n\tvar output: FragmentOutput;\n\toutput.color = c;\n\treturn output;\n}\n`;\n\nconst DENOISE_FILTER_SIZE = 15;\nconst lightmapFiltersChunksGLSL = {\n\t\tglslBilateralDeNoisePS,\n\t\tglslDilatePS\n};\nconst lightmapFiltersChunksWLSL = {\n\t\twgslBilateralDeNoisePS,\n\t\twgslDilatePS\n};\nclass LightmapFilters {\n\t\tconstructor(device){\n\t\t\t\tthis.shaderDilate = [];\n\t\t\t\tthis.shaderDenoise = [];\n\t\t\t\tthis.device = device;\n\t\t\t\tShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).add(lightmapFiltersChunksGLSL);\n\t\t\t\tShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).add(lightmapFiltersChunksWLSL);\n\t\t\t\tthis.constantTexSource = device.scope.resolve('source');\n\t\t\t\tthis.constantPixelOffset = device.scope.resolve('pixelOffset');\n\t\t\t\tthis.pixelOffset = new Float32Array(2);\n\t\t\t\tthis.sigmas = null;\n\t\t\t\tthis.constantSigmas = null;\n\t\t\t\tthis.kernel = null;\n\t\t}\n\t\tsetSourceTexture(texture) {\n\t\t\t\tthis.constantTexSource.setValue(texture);\n\t\t}\n\t\tprepare(textureWidth, textureHeight) {\n\t\t\t\tthis.pixelOffset[0] = 1 / textureWidth;\n\t\t\t\tthis.pixelOffset[1] = 1 / textureHeight;\n\t\t\t\tthis.constantPixelOffset.setValue(this.pixelOffset);\n\t\t}\n\t\tprepareDenoise(filterRange, filterSmoothness, bakeHDR) {\n\t\t\t\tconst index = bakeHDR ? 0 : 1;\n\t\t\t\tif (!this.shaderDenoise[index]) {\n\t\t\t\t\t\tconst defines = new Map();\n\t\t\t\t\t\tdefines.set('{MSIZE}', 15);\n\t\t\t\t\t\tif (bakeHDR) defines.set('HDR', '');\n\t\t\t\t\t\tthis.shaderDenoise[index] = ShaderUtils.createShader(this.device, {\n\t\t\t\t\t\t\t\tuniqueName: `lmBilateralDeNoise-${bakeHDR ? 'hdr' : 'rgbm'}`,\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvertexGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('fullscreenQuadVS'),\n\t\t\t\t\t\t\t\tvertexWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('fullscreenQuadVS'),\n\t\t\t\t\t\t\t\tfragmentGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('glslBilateralDeNoisePS'),\n\t\t\t\t\t\t\t\tfragmentWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('wgslBilateralDeNoisePS'),\n\t\t\t\t\t\t\t\tfragmentDefines: defines\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.sigmas = new Float32Array(2);\n\t\t\t\t\t\tthis.constantSigmas = this.device.scope.resolve('sigmas');\n\t\t\t\t\t\tthis.constantKernel = this.device.scope.resolve('kernel[0]');\n\t\t\t\t\t\tthis.bZnorm = this.device.scope.resolve('bZnorm');\n\t\t\t\t}\n\t\t\t\tthis.sigmas[0] = filterRange;\n\t\t\t\tthis.sigmas[1] = filterSmoothness;\n\t\t\t\tthis.constantSigmas.setValue(this.sigmas);\n\t\t\t\tthis.evaluateDenoiseUniforms(filterRange, filterSmoothness);\n\t\t}\n\t\tgetDenoise(bakeHDR) {\n\t\t\t\tconst index = bakeHDR ? 0 : 1;\n\t\t\t\treturn this.shaderDenoise[index];\n\t\t}\n\t\tgetDilate(device, bakeHDR) {\n\t\t\t\tconst index = bakeHDR ? 0 : 1;\n\t\t\t\tif (!this.shaderDilate[index]) {\n\t\t\t\t\t\tconst define = bakeHDR ? '#define HDR\\n' : '';\n\t\t\t\t\t\tthis.shaderDilate[index] = ShaderUtils.createShader(device, {\n\t\t\t\t\t\t\t\tuniqueName: `lmDilate-${bakeHDR ? 'hdr' : 'rgbm'}`,\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvertexGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('fullscreenQuadVS'),\n\t\t\t\t\t\t\t\tvertexWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('fullscreenQuadVS'),\n\t\t\t\t\t\t\t\tfragmentGLSL: define + ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get('glslDilatePS'),\n\t\t\t\t\t\t\t\tfragmentWGSL: define + ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get('wgslDilatePS')\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this.shaderDilate[index];\n\t\t}\n\t\tevaluateDenoiseUniforms(filterRange, filterSmoothness) {\n\t\t\t\tfunction normpdf(x, sigma) {\n\t\t\t\t\t\treturn 0.39894 * Math.exp(-0.5 * x * x / (sigma * sigma)) / sigma;\n\t\t\t\t}\n\t\t\t\tthis.kernel = this.kernel || new Float32Array(DENOISE_FILTER_SIZE);\n\t\t\t\tconst kernel = this.kernel;\n\t\t\t\tconst kSize = Math.floor((DENOISE_FILTER_SIZE - 1) / 2);\n\t\t\t\tfor(let j = 0; j <= kSize; ++j){\n\t\t\t\t\t\tconst value = normpdf(j, filterRange);\n\t\t\t\t\t\tkernel[kSize + j] = value;\n\t\t\t\t\t\tkernel[kSize - j] = value;\n\t\t\t\t}\n\t\t\t\tthis.constantKernel.setValue(this.kernel);\n\t\t\t\tconst bZnorm = 1 / normpdf(0.0, filterSmoothness);\n\t\t\t\tthis.bZnorm.setValue(bZnorm);\n\t\t}\n}\n\nclass RenderPassLightmapper extends RenderPass {\n\t\tconstructor(device, renderer, camera, worldClusters, receivers, lightArray){\n\t\t\t\tsuper(device), this.viewBindGroups = [];\n\t\t\t\tthis.renderer = renderer;\n\t\t\t\tthis.camera = camera;\n\t\t\t\tthis.worldClusters = worldClusters;\n\t\t\t\tthis.receivers = receivers;\n\t\t\t\tthis.lightArray = lightArray;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.viewBindGroups.forEach((bg)=>{\n\t\t\t\t\t\tbg.defaultUniformBuffer.destroy();\n\t\t\t\t\t\tbg.destroy();\n\t\t\t\t});\n\t\t\t\tthis.viewBindGroups.length = 0;\n\t\t}\n\t\texecute() {\n\t\t\t\tthis.device;\n\t\t\t\tconst { renderer, camera, receivers, renderTarget, worldClusters, lightArray } = this;\n\t\t\t\trenderer.renderForwardLayer(camera, renderTarget, null, undefined, SHADER_FORWARD, this.viewBindGroups, {\n\t\t\t\t\t\tmeshInstances: receivers,\n\t\t\t\t\t\tsplitLights: lightArray,\n\t\t\t\t\t\tlightClusters: worldClusters\n\t\t\t\t});\n\t\t}\n}\n\nconst MAX_LIGHTMAP_SIZE = 2048;\nconst PASS_COLOR = 0;\nconst PASS_DIR = 1;\nconst tempVec$1 = new Vec3();\nclass Lightmapper {\n\t\tconstructor(device, root, scene, renderer, assets){\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.root = root;\n\t\t\t\tthis.scene = scene;\n\t\t\t\tthis.renderer = renderer;\n\t\t\t\tthis.assets = assets;\n\t\t\t\tthis.shadowMapCache = renderer.shadowMapCache;\n\t\t\t\tthis._tempSet = new Set();\n\t\t\t\tthis._initCalled = false;\n\t\t\t\tthis.passMaterials = [];\n\t\t\t\tthis.ambientAOMaterial = null;\n\t\t\t\tthis.fog = '';\n\t\t\t\tthis.ambientLight = new Color();\n\t\t\t\tthis.renderTargets = new Map();\n\t\t\t\tthis.stats = {\n\t\t\t\t\t\trenderPasses: 0,\n\t\t\t\t\t\tlightmapCount: 0,\n\t\t\t\t\t\ttotalRenderTime: 0,\n\t\t\t\t\t\tforwardTime: 0,\n\t\t\t\t\t\tfboTime: 0,\n\t\t\t\t\t\tshadowMapTime: 0,\n\t\t\t\t\t\tcompileTime: 0,\n\t\t\t\t\t\tshadersLinked: 0\n\t\t\t\t};\n\t\t}\n\t\tdestroy() {\n\t\t\t\tLightmapCache.decRef(this.blackTex);\n\t\t\t\tthis.blackTex = null;\n\t\t\t\tLightmapCache.destroy();\n\t\t\t\tthis.device = null;\n\t\t\t\tthis.root = null;\n\t\t\t\tthis.scene = null;\n\t\t\t\tthis.renderer = null;\n\t\t\t\tthis.assets = null;\n\t\t\t\tthis.camera?.destroy();\n\t\t\t\tthis.camera = null;\n\t\t}\n\t\tinitBake(device) {\n\t\t\t\tthis.bakeHDR = this.scene.lightmapPixelFormat !== PIXELFORMAT_RGBA8;\n\t\t\t\tif (!this._initCalled) {\n\t\t\t\t\t\tthis._initCalled = true;\n\t\t\t\t\t\tthis.lightmapFilters = new LightmapFilters(device);\n\t\t\t\t\t\tthis.constantBakeDir = device.scope.resolve('bakeDir');\n\t\t\t\t\t\tthis.materials = [];\n\t\t\t\t\t\tthis.blackTex = new Texture(this.device, {\n\t\t\t\t\t\t\t\twidth: 4,\n\t\t\t\t\t\t\t\theight: 4,\n\t\t\t\t\t\t\t\tformat: PIXELFORMAT_RGBA8,\n\t\t\t\t\t\t\t\ttype: TEXTURETYPE_RGBM,\n\t\t\t\t\t\t\t\tname: 'lightmapBlack'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tLightmapCache.incRef(this.blackTex);\n\t\t\t\t\t\tconst camera = new Camera();\n\t\t\t\t\t\tcamera.clearColor.set(0, 0, 0, 0);\n\t\t\t\t\t\tcamera.clearColorBuffer = true;\n\t\t\t\t\t\tcamera.clearDepthBuffer = false;\n\t\t\t\t\t\tcamera.clearStencilBuffer = false;\n\t\t\t\t\t\tcamera.frustumCulling = false;\n\t\t\t\t\t\tcamera.projection = PROJECTION_ORTHOGRAPHIC;\n\t\t\t\t\t\tcamera.aspectRatio = 1;\n\t\t\t\t\t\tcamera.node = new GraphNode();\n\t\t\t\t\t\tthis.camera = camera;\n\t\t\t\t\t\tthis.camera.shaderParams.gammaCorrection = GAMMA_NONE;\n\t\t\t\t\t\tthis.camera.shaderParams.toneMapping = TONEMAP_LINEAR;\n\t\t\t\t}\n\t\t\t\tif (this.scene.clusteredLightingEnabled) {\n\t\t\t\t\t\tconst lightingParams = new LightingParams(device.supportsAreaLights, device.maxTextureSize, ()=>{});\n\t\t\t\t\t\tthis.lightingParams = lightingParams;\n\t\t\t\t\t\tconst srcParams = this.scene.lighting;\n\t\t\t\t\t\tlightingParams.shadowsEnabled = srcParams.shadowsEnabled;\n\t\t\t\t\t\tlightingParams.shadowAtlasResolution = srcParams.shadowAtlasResolution;\n\t\t\t\t\t\tlightingParams.cookiesEnabled = srcParams.cookiesEnabled;\n\t\t\t\t\t\tlightingParams.cookieAtlasResolution = srcParams.cookieAtlasResolution;\n\t\t\t\t\t\tlightingParams.areaLightsEnabled = srcParams.areaLightsEnabled;\n\t\t\t\t\t\tlightingParams.cells = new Vec3(3, 3, 3);\n\t\t\t\t\t\tlightingParams.maxLightsPerCell = 4;\n\t\t\t\t\t\tthis.worldClusters = new WorldClusters(device);\n\t\t\t\t\t\tthis.worldClusters.name = 'ClusterLightmapper';\n\t\t\t\t}\n\t\t}\n\t\tfinishBake(bakeNodes) {\n\t\t\t\tthis.materials = [];\n\t\t\t\tfunction destroyRT(rt) {\n\t\t\t\t\t\tLightmapCache.decRef(rt.colorBuffer);\n\t\t\t\t\t\trt.destroy();\n\t\t\t\t}\n\t\t\t\tthis.renderTargets.forEach((rt)=>{\n\t\t\t\t\t\tdestroyRT(rt);\n\t\t\t\t});\n\t\t\t\tthis.renderTargets.clear();\n\t\t\t\tbakeNodes.forEach((node)=>{\n\t\t\t\t\t\tnode.renderTargets.forEach((rt)=>{\n\t\t\t\t\t\t\t\tdestroyRT(rt);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tnode.renderTargets.length = 0;\n\t\t\t\t});\n\t\t\t\tthis.ambientAOMaterial = null;\n\t\t\t\tif (this.worldClusters) {\n\t\t\t\t\t\tthis.worldClusters.destroy();\n\t\t\t\t\t\tthis.worldClusters = null;\n\t\t\t\t}\n\t\t}\n\t\tcreateMaterialForPass(scene, pass, addAmbient) {\n\t\t\t\tconst material = new StandardMaterial();\n\t\t\t\tmaterial.name = `lmMaterial-pass:${pass}-ambient:${addAmbient}`;\n\t\t\t\tmaterial.setDefine('UV1LAYOUT', '');\n\t\t\t\tmaterial.setDefine('LIT_LIGHTMAP_BAKING', '');\n\t\t\t\tif (pass === PASS_COLOR) {\n\t\t\t\t\t\tmaterial.setDefine('LIT_LIGHTMAP_BAKING_COLOR', '');\n\t\t\t\t\t\tif (addAmbient) {\n\t\t\t\t\t\t\t\tmaterial.setDefine('LIT_LIGHTMAP_BAKING_ADD_AMBIENT', '');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmaterial.ambient = new Color(0, 0, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this.bakeHDR) material.setDefine('LIGHTMAP_RGBM', '');\n\t\t\t\t\t\tmaterial.lightMap = this.blackTex;\n\t\t\t\t} else {\n\t\t\t\t\t\tmaterial.setDefine('LIT_LIGHTMAP_BAKING_DIR', '');\n\t\t\t\t\t\tmaterial.setDefine('STD_LIGHTMAP_DIR', '');\n\t\t\t\t}\n\t\t\t\tmaterial.cull = CULLFACE_NONE;\n\t\t\t\tmaterial.forceUv1 = true;\n\t\t\t\tmaterial.update();\n\t\t\t\treturn material;\n\t\t}\n\t\tcreateMaterials(device, scene, passCount) {\n\t\t\t\tfor(let pass = 0; pass < passCount; pass++){\n\t\t\t\t\t\tif (!this.passMaterials[pass]) {\n\t\t\t\t\t\t\t\tthis.passMaterials[pass] = this.createMaterialForPass(scene, pass, false);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!this.ambientAOMaterial) {\n\t\t\t\t\t\tthis.ambientAOMaterial = this.createMaterialForPass(scene, 0, true);\n\t\t\t\t\t\tthis.ambientAOMaterial.onUpdateShader = function(options) {\n\t\t\t\t\t\t\t\toptions.litOptions.lightMapWithoutAmbient = true;\n\t\t\t\t\t\t\t\toptions.litOptions.separateAmbient = true;\n\t\t\t\t\t\t\t\treturn options;\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tcreateTexture(size, name) {\n\t\t\t\treturn new Texture(this.device, {\n\t\t\t\t\t\twidth: size,\n\t\t\t\t\t\theight: size,\n\t\t\t\t\t\tformat: this.scene.lightmapPixelFormat,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\ttype: this.bakeHDR ? TEXTURETYPE_DEFAULT : TEXTURETYPE_RGBM,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tname: name\n\t\t\t\t});\n\t\t}\n\t\tcollectModels(node, bakeNodes, allNodes) {\n\t\t\t\tif (!node.enabled) return;\n\t\t\t\tlet meshInstances;\n\t\t\t\tif (node.model?.model && node.model?.enabled) {\n\t\t\t\t\t\tif (allNodes) allNodes.push(new BakeMeshNode(node));\n\t\t\t\t\t\tif (node.model.lightmapped) {\n\t\t\t\t\t\t\t\tif (bakeNodes) {\n\t\t\t\t\t\t\t\t\t\tmeshInstances = node.model.model.meshInstances;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (node.render?.enabled) {\n\t\t\t\t\t\tif (allNodes) allNodes.push(new BakeMeshNode(node));\n\t\t\t\t\t\tif (node.render.lightmapped) {\n\t\t\t\t\t\t\t\tif (bakeNodes) {\n\t\t\t\t\t\t\t\t\t\tmeshInstances = node.render.meshInstances;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (meshInstances) {\n\t\t\t\t\t\tlet hasUv1 = true;\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tif (!meshInstances[i].mesh.vertexBuffer.format.hasUv1) {\n\t\t\t\t\t\t\t\t\t\thasUv1 = false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (hasUv1) {\n\t\t\t\t\t\t\t\tconst notInstancedMeshInstances = [];\n\t\t\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst mesh = meshInstances[i].mesh;\n\t\t\t\t\t\t\t\t\t\tif (this._tempSet.has(mesh)) {\n\t\t\t\t\t\t\t\t\t\t\t\tbakeNodes.push(new BakeMeshNode(node, [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmeshInstances[i]\n\t\t\t\t\t\t\t\t\t\t\t\t]));\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tnotInstancedMeshInstances.push(meshInstances[i]);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis._tempSet.add(mesh);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._tempSet.clear();\n\t\t\t\t\t\t\t\tif (notInstancedMeshInstances.length > 0) {\n\t\t\t\t\t\t\t\t\t\tbakeNodes.push(new BakeMeshNode(node, notInstancedMeshInstances));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < node._children.length; i++){\n\t\t\t\t\t\tthis.collectModels(node._children[i], bakeNodes, allNodes);\n\t\t\t\t}\n\t\t}\n\t\tprepareShadowCasters(nodes) {\n\t\t\t\tconst casters = [];\n\t\t\t\tfor(let n = 0; n < nodes.length; n++){\n\t\t\t\t\t\tconst component = nodes[n].component;\n\t\t\t\t\t\tcomponent.castShadows = component.castShadowsLightmap;\n\t\t\t\t\t\tif (component.castShadowsLightmap) {\n\t\t\t\t\t\t\t\tconst meshes = nodes[n].meshInstances;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < meshes.length; i++){\n\t\t\t\t\t\t\t\t\t\tmeshes[i].visibleThisFrame = true;\n\t\t\t\t\t\t\t\t\t\tcasters.push(meshes[i]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn casters;\n\t\t}\n\t\tupdateTransforms(nodes) {\n\t\t\t\tfor(let i = 0; i < nodes.length; i++){\n\t\t\t\t\t\tconst meshInstances = nodes[i].meshInstances;\n\t\t\t\t\t\tfor(let j = 0; j < meshInstances.length; j++){\n\t\t\t\t\t\t\t\tmeshInstances[j].node.getWorldTransform();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcalculateLightmapSize(node) {\n\t\t\t\tlet data;\n\t\t\t\tconst sizeMult = this.scene.lightmapSizeMultiplier || 16;\n\t\t\t\tconst scale = tempVec$1;\n\t\t\t\tlet srcArea, lightmapSizeMultiplier;\n\t\t\t\tif (node.model) {\n\t\t\t\t\t\tlightmapSizeMultiplier = node.model.lightmapSizeMultiplier;\n\t\t\t\t\t\tif (node.model.asset) {\n\t\t\t\t\t\t\t\tdata = this.assets.get(node.model.asset).data;\n\t\t\t\t\t\t\t\tif (data.area) {\n\t\t\t\t\t\t\t\t\t\tsrcArea = data.area;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (node.model._area) {\n\t\t\t\t\t\t\t\tdata = node.model;\n\t\t\t\t\t\t\t\tif (data._area) {\n\t\t\t\t\t\t\t\t\t\tsrcArea = data._area;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else if (node.render) {\n\t\t\t\t\t\tlightmapSizeMultiplier = node.render.lightmapSizeMultiplier;\n\t\t\t\t\t\tif (node.render.type !== 'asset') {\n\t\t\t\t\t\t\t\tif (node.render._area) {\n\t\t\t\t\t\t\t\t\t\tdata = node.render;\n\t\t\t\t\t\t\t\t\t\tif (data._area) {\n\t\t\t\t\t\t\t\t\t\t\t\tsrcArea = data._area;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst area = {\n\t\t\t\t\t\tx: 1,\n\t\t\t\t\t\ty: 1,\n\t\t\t\t\t\tz: 1,\n\t\t\t\t\t\tuv: 1\n\t\t\t\t};\n\t\t\t\tif (srcArea) {\n\t\t\t\t\t\tarea.x = srcArea.x;\n\t\t\t\t\t\tarea.y = srcArea.y;\n\t\t\t\t\t\tarea.z = srcArea.z;\n\t\t\t\t\t\tarea.uv = srcArea.uv;\n\t\t\t\t}\n\t\t\t\tconst areaMult = lightmapSizeMultiplier || 1;\n\t\t\t\tarea.x *= areaMult;\n\t\t\t\tarea.y *= areaMult;\n\t\t\t\tarea.z *= areaMult;\n\t\t\t\tconst component = node.render || node.model;\n\t\t\t\tconst bounds = this.computeNodeBounds(component.meshInstances);\n\t\t\t\tscale.copy(bounds.halfExtents);\n\t\t\t\tlet totalArea = area.x * scale.y * scale.z + area.y * scale.x * scale.z + area.z * scale.x * scale.y;\n\t\t\t\ttotalArea /= area.uv;\n\t\t\t\ttotalArea = Math.sqrt(totalArea);\n\t\t\t\tconst lightmapSize = Math.min(math.nextPowerOfTwo(totalArea * sizeMult), this.scene.lightmapMaxResolution || MAX_LIGHTMAP_SIZE);\n\t\t\t\treturn lightmapSize;\n\t\t}\n\t\tsetLightmapping(nodes, value, passCount, shaderDefs) {\n\t\t\t\tfor(let i = 0; i < nodes.length; i++){\n\t\t\t\t\t\tconst node = nodes[i];\n\t\t\t\t\t\tconst meshInstances = node.meshInstances;\n\t\t\t\t\t\tfor(let j = 0; j < meshInstances.length; j++){\n\t\t\t\t\t\t\t\tconst meshInstance = meshInstances[j];\n\t\t\t\t\t\t\t\tmeshInstance.setLightmapped(value);\n\t\t\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\t\t\tif (shaderDefs) {\n\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance._shaderDefs |= shaderDefs;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tmeshInstance.mask = MASK_AFFECT_LIGHTMAPPED;\n\t\t\t\t\t\t\t\t\t\tfor(let pass = 0; pass < passCount; pass++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst tex = node.renderTargets[pass].colorBuffer;\n\t\t\t\t\t\t\t\t\t\t\t\ttex.minFilter = FILTER_LINEAR;\n\t\t\t\t\t\t\t\t\t\t\t\ttex.magFilter = FILTER_LINEAR;\n\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance.setRealtimeLightmap(MeshInstance.lightmapParamNames[pass], tex);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tbake(nodes, mode = BAKE_COLORDIR) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst startTime = now();\n\t\t\t\tthis.scene._updateSkyMesh();\n\t\t\t\tthis.stats.renderPasses = 0;\n\t\t\t\tthis.stats.shadowMapTime = 0;\n\t\t\t\tthis.stats.forwardTime = 0;\n\t\t\t\tconst startShaders = device._shaderStats.linked;\n\t\t\t\tconst startFboTime = device._renderTargetCreationTime;\n\t\t\t\tconst startCompileTime = device._shaderStats.compileTime;\n\t\t\t\tconst bakeNodes = [];\n\t\t\t\tconst allNodes = [];\n\t\t\t\tif (nodes) {\n\t\t\t\t\t\tfor(let i = 0; i < nodes.length; i++){\n\t\t\t\t\t\t\t\tthis.collectModels(nodes[i], bakeNodes, null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.collectModels(this.root, null, allNodes);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.collectModels(this.root, bakeNodes, allNodes);\n\t\t\t\t}\n\t\t\t\tif (bakeNodes.length > 0) {\n\t\t\t\t\t\tthis.renderer.shadowRenderer.frameUpdate();\n\t\t\t\t\t\tconst passCount = mode === BAKE_COLORDIR ? 2 : 1;\n\t\t\t\t\t\tthis.setLightmapping(bakeNodes, false, passCount);\n\t\t\t\t\t\tthis.initBake(device);\n\t\t\t\t\t\tthis.bakeInternal(passCount, bakeNodes, allNodes);\n\t\t\t\t\t\tlet shaderDefs = SHADERDEF_LM;\n\t\t\t\t\t\tif (mode === BAKE_COLORDIR) {\n\t\t\t\t\t\t\t\tshaderDefs |= SHADERDEF_DIRLM;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.scene.ambientBake) {\n\t\t\t\t\t\t\t\tshaderDefs |= SHADERDEF_LMAMBIENT;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.setLightmapping(bakeNodes, true, passCount, shaderDefs);\n\t\t\t\t\t\tthis.finishBake(bakeNodes);\n\t\t\t\t}\n\t\t\t\tconst nowTime = now();\n\t\t\t\tthis.stats.totalRenderTime = nowTime - startTime;\n\t\t\t\tthis.stats.shadersLinked = device._shaderStats.linked - startShaders;\n\t\t\t\tthis.stats.compileTime = device._shaderStats.compileTime - startCompileTime;\n\t\t\t\tthis.stats.fboTime = device._renderTargetCreationTime - startFboTime;\n\t\t\t\tthis.stats.lightmapCount = bakeNodes.length;\n\t\t}\n\t\tallocateTextures(bakeNodes, passCount) {\n\t\t\t\tfor(let i = 0; i < bakeNodes.length; i++){\n\t\t\t\t\t\tconst bakeNode = bakeNodes[i];\n\t\t\t\t\t\tconst size = this.calculateLightmapSize(bakeNode.node);\n\t\t\t\t\t\tfor(let pass = 0; pass < passCount; pass++){\n\t\t\t\t\t\t\t\tconst tex = this.createTexture(size, `lightmapper_lightmap_${i}`);\n\t\t\t\t\t\t\t\tLightmapCache.incRef(tex);\n\t\t\t\t\t\t\t\tbakeNode.renderTargets[pass] = new RenderTarget({\n\t\t\t\t\t\t\t\t\t\tcolorBuffer: tex,\n\t\t\t\t\t\t\t\t\t\tdepth: false\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this.renderTargets.has(size)) {\n\t\t\t\t\t\t\t\tconst tex = this.createTexture(size, `lightmapper_temp_lightmap_${size}`);\n\t\t\t\t\t\t\t\tLightmapCache.incRef(tex);\n\t\t\t\t\t\t\t\tthis.renderTargets.set(size, new RenderTarget({\n\t\t\t\t\t\t\t\t\t\tcolorBuffer: tex,\n\t\t\t\t\t\t\t\t\t\tdepth: false\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tprepareLightsToBake(allLights, bakeLights) {\n\t\t\t\tif (this.scene.ambientBake) {\n\t\t\t\t\t\tconst ambientLight = new BakeLightAmbient(this);\n\t\t\t\t\t\tbakeLights.push(ambientLight);\n\t\t\t\t}\n\t\t\t\tconst sceneLights = this.renderer.lights;\n\t\t\t\tfor(let i = 0; i < sceneLights.length; i++){\n\t\t\t\t\t\tconst light = sceneLights[i];\n\t\t\t\t\t\tconst bakeLight = new BakeLightSimple(this, light);\n\t\t\t\t\t\tallLights.push(bakeLight);\n\t\t\t\t\t\tif (light.enabled && (light.mask & MASK_BAKE) !== 0) {\n\t\t\t\t\t\t\t\tlight.mask = MASK_BAKE | MASK_AFFECT_LIGHTMAPPED | MASK_AFFECT_DYNAMIC;\n\t\t\t\t\t\t\t\tlight.shadowUpdateMode = light.type === LIGHTTYPE_DIRECTIONAL ? SHADOWUPDATE_REALTIME : SHADOWUPDATE_THISFRAME;\n\t\t\t\t\t\t\t\tbakeLights.push(bakeLight);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbakeLights.sort();\n\t\t}\n\t\trestoreLights(allLights) {\n\t\t\t\tfor(let i = 0; i < allLights.length; i++){\n\t\t\t\t\t\tallLights[i].restore();\n\t\t\t\t}\n\t\t}\n\t\tsetupScene() {\n\t\t\t\tthis.ambientLight.copy(this.scene.ambientLight);\n\t\t\t\tif (!this.scene.ambientBake) {\n\t\t\t\t\t\tthis.scene.ambientLight.set(0, 0, 0);\n\t\t\t\t}\n\t\t\t\tthis.renderer.setSceneConstants();\n\t\t\t\tthis.device.scope.resolve('ambientBakeOcclusionContrast').setValue(this.scene.ambientBakeOcclusionContrast);\n\t\t\t\tthis.device.scope.resolve('ambientBakeOcclusionBrightness').setValue(this.scene.ambientBakeOcclusionBrightness);\n\t\t}\n\t\trestoreScene() {\n\t\t\t\tthis.scene.ambientLight.copy(this.ambientLight);\n\t\t}\n\t\tcomputeNodeBounds(meshInstances) {\n\t\t\t\tconst bounds = new BoundingBox();\n\t\t\t\tif (meshInstances.length > 0) {\n\t\t\t\t\t\tbounds.copy(meshInstances[0].aabb);\n\t\t\t\t\t\tfor(let m = 1; m < meshInstances.length; m++){\n\t\t\t\t\t\t\t\tbounds.add(meshInstances[m].aabb);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn bounds;\n\t\t}\n\t\tcomputeNodesBounds(nodes) {\n\t\t\t\tfor(let i = 0; i < nodes.length; i++){\n\t\t\t\t\t\tconst meshInstances = nodes[i].meshInstances;\n\t\t\t\t\t\tnodes[i].bounds = this.computeNodeBounds(meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tcomputeBounds(meshInstances) {\n\t\t\t\tconst bounds = new BoundingBox();\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tbounds.copy(meshInstances[0].aabb);\n\t\t\t\t\t\tfor(let m = 1; m < meshInstances.length; m++){\n\t\t\t\t\t\t\t\tbounds.add(meshInstances[m].aabb);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn bounds;\n\t\t}\n\t\tbackupMaterials(meshInstances) {\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tthis.materials[i] = meshInstances[i].material;\n\t\t\t\t}\n\t\t}\n\t\trestoreMaterials(meshInstances) {\n\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\tmeshInstances[i].material = this.materials[i];\n\t\t\t\t}\n\t\t}\n\t\tlightCameraPrepare(device, bakeLight) {\n\t\t\t\tconst light = bakeLight.light;\n\t\t\t\tlet shadowCam;\n\t\t\t\tif (light.type === LIGHTTYPE_SPOT) {\n\t\t\t\t\t\tconst lightRenderData = light.getRenderData(null, 0);\n\t\t\t\t\t\tshadowCam = lightRenderData.shadowCamera;\n\t\t\t\t\t\tshadowCam._node.setPosition(light._node.getPosition());\n\t\t\t\t\t\tshadowCam._node.setRotation(light._node.getRotation());\n\t\t\t\t\t\tshadowCam._node.rotateLocal(-90, 0, 0);\n\t\t\t\t\t\tshadowCam.projection = PROJECTION_PERSPECTIVE;\n\t\t\t\t\t\tshadowCam.nearClip = light.attenuationEnd / 1000;\n\t\t\t\t\t\tshadowCam.farClip = light.attenuationEnd;\n\t\t\t\t\t\tshadowCam.aspectRatio = 1;\n\t\t\t\t\t\tshadowCam.fov = light._outerConeAngle * 2;\n\t\t\t\t\t\tthis.renderer.updateCameraFrustum(shadowCam);\n\t\t\t\t}\n\t\t\t\treturn shadowCam;\n\t\t}\n\t\tlightCameraPrepareAndCull(bakeLight, bakeNode, shadowCam, casterBounds) {\n\t\t\t\tconst light = bakeLight.light;\n\t\t\t\tlet lightAffectsNode = true;\n\t\t\t\tif (light.type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\ttempVec$1.copy(casterBounds.center);\n\t\t\t\t\t\ttempVec$1.y += casterBounds.halfExtents.y;\n\t\t\t\t\t\tthis.camera.node.setPosition(tempVec$1);\n\t\t\t\t\t\tthis.camera.node.setEulerAngles(-90, 0, 0);\n\t\t\t\t\t\tthis.camera.nearClip = 0;\n\t\t\t\t\t\tthis.camera.farClip = casterBounds.halfExtents.y * 2;\n\t\t\t\t\t\tconst frustumSize = Math.max(casterBounds.halfExtents.x, casterBounds.halfExtents.z);\n\t\t\t\t\t\tthis.camera.orthoHeight = frustumSize;\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!bakeLight.lightBounds.intersects(bakeNode.bounds)) {\n\t\t\t\t\t\t\t\tlightAffectsNode = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (light.type === LIGHTTYPE_SPOT) {\n\t\t\t\t\t\tlet nodeVisible = false;\n\t\t\t\t\t\tconst meshInstances = bakeNode.meshInstances;\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tif (meshInstances[i]._isVisible(shadowCam)) {\n\t\t\t\t\t\t\t\t\t\tnodeVisible = true;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!nodeVisible) {\n\t\t\t\t\t\t\t\tlightAffectsNode = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn lightAffectsNode;\n\t\t}\n\t\tsetupLightArray(lightArray, light) {\n\t\t\t\tlightArray[LIGHTTYPE_DIRECTIONAL].length = 0;\n\t\t\t\tlightArray[LIGHTTYPE_OMNI].length = 0;\n\t\t\t\tlightArray[LIGHTTYPE_SPOT].length = 0;\n\t\t\t\tlightArray[light.type][0] = light;\n\t\t\t\tlight.visibleThisFrame = true;\n\t\t}\n\t\trenderShadowMap(comp, shadowMapRendered, casters, bakeLight) {\n\t\t\t\tconst light = bakeLight.light;\n\t\t\t\tconst isClustered = this.scene.clusteredLightingEnabled;\n\t\t\t\tconst castShadow = light.castShadows && (!isClustered || this.scene.lighting.shadowsEnabled);\n\t\t\t\tif (!shadowMapRendered && castShadow) {\n\t\t\t\t\t\tif (!light.shadowMap && !isClustered) {\n\t\t\t\t\t\t\t\tlight.shadowMap = this.shadowMapCache.get(this.device, light);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (light.type === LIGHTTYPE_DIRECTIONAL) {\n\t\t\t\t\t\t\t\tthis.renderer._shadowRendererDirectional.cull(light, comp, this.camera, casters);\n\t\t\t\t\t\t\t\tconst shadowPass = this.renderer._shadowRendererDirectional.getLightRenderPass(light, this.camera);\n\t\t\t\t\t\t\t\tshadowPass?.render();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.device.isWebGPU) {\n\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.renderer._shadowRendererLocal.cull(light, comp, casters);\n\t\t\t\t\t\t\t\tconst insideRenderPass = false;\n\t\t\t\t\t\t\t\tthis.renderer.shadowRenderer.render(light, this.camera, insideRenderPass);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tpostprocessTextures(device, bakeNodes, passCount) {\n\t\t\t\tconst numDilates2x = 1;\n\t\t\t\tconst dilateShader = this.lightmapFilters.getDilate(device, this.bakeHDR);\n\t\t\t\tlet denoiseShader;\n\t\t\t\tconst filterLightmap = this.scene.lightmapFilterEnabled;\n\t\t\t\tif (filterLightmap) {\n\t\t\t\t\t\tthis.lightmapFilters.prepareDenoise(this.scene.lightmapFilterRange, this.scene.lightmapFilterSmoothness, this.bakeHDR);\n\t\t\t\t\t\tdenoiseShader = this.lightmapFilters.getDenoise(this.bakeHDR);\n\t\t\t\t}\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\tdevice.setStencilState(null, null);\n\t\t\t\tfor(let node = 0; node < bakeNodes.length; node++){\n\t\t\t\t\t\tconst bakeNode = bakeNodes[node];\n\t\t\t\t\t\tfor(let pass = 0; pass < passCount; pass++){\n\t\t\t\t\t\t\t\tconst nodeRT = bakeNode.renderTargets[pass];\n\t\t\t\t\t\t\t\tconst lightmap = nodeRT.colorBuffer;\n\t\t\t\t\t\t\t\tconst tempRT = this.renderTargets.get(lightmap.width);\n\t\t\t\t\t\t\t\tconst tempTex = tempRT.colorBuffer;\n\t\t\t\t\t\t\t\tthis.lightmapFilters.prepare(lightmap.width, lightmap.height);\n\t\t\t\t\t\t\t\tfor(let i = 0; i < numDilates2x; i++){\n\t\t\t\t\t\t\t\t\t\tthis.lightmapFilters.setSourceTexture(lightmap);\n\t\t\t\t\t\t\t\t\t\tconst bilateralFilterEnabled = filterLightmap && pass === 0 && i === 0;\n\t\t\t\t\t\t\t\t\t\tdrawQuadWithShader(device, tempRT, bilateralFilterEnabled ? denoiseShader : dilateShader);\n\t\t\t\t\t\t\t\t\t\tthis.lightmapFilters.setSourceTexture(tempTex);\n\t\t\t\t\t\t\t\t\t\tdrawQuadWithShader(device, nodeRT, dilateShader);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tbakeInternal(passCount, bakeNodes, allNodes) {\n\t\t\t\tconst scene = this.scene;\n\t\t\t\tconst comp = scene.layers;\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst clusteredLightingEnabled = scene.clusteredLightingEnabled;\n\t\t\t\tthis.createMaterials(device, scene, passCount);\n\t\t\t\tthis.setupScene();\n\t\t\t\tcomp._update();\n\t\t\t\tthis.computeNodesBounds(bakeNodes);\n\t\t\t\tthis.allocateTextures(bakeNodes, passCount);\n\t\t\t\tthis.renderer.collectLights(comp);\n\t\t\t\tconst allLights = [], bakeLights = [];\n\t\t\t\tthis.prepareLightsToBake(allLights, bakeLights);\n\t\t\t\tthis.updateTransforms(allNodes);\n\t\t\t\tconst casters = this.prepareShadowCasters(allNodes);\n\t\t\t\tthis.renderer.updateCpuSkinMatrices(casters);\n\t\t\t\tthis.renderer.gpuUpdate(casters);\n\t\t\t\tconst casterBounds = this.computeBounds(casters);\n\t\t\t\tlet i, j, rcv, m;\n\t\t\t\tfor(i = 0; i < bakeNodes.length; i++){\n\t\t\t\t\t\tconst bakeNode = bakeNodes[i];\n\t\t\t\t\t\trcv = bakeNode.meshInstances;\n\t\t\t\t\t\tfor(j = 0; j < rcv.length; j++){\n\t\t\t\t\t\t\t\tm = rcv[j];\n\t\t\t\t\t\t\t\tm.setLightmapped(false);\n\t\t\t\t\t\t\t\tm.mask = MASK_BAKE;\n\t\t\t\t\t\t\t\tm.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], this.blackTex);\n\t\t\t\t\t\t\t\tm.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], this.blackTex);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(j = 0; j < bakeLights.length; j++){\n\t\t\t\t\t\tbakeLights[j].light.enabled = false;\n\t\t\t\t}\n\t\t\t\tconst lightArray = [\n\t\t\t\t\t\t[],\n\t\t\t\t\t\t[],\n\t\t\t\t\t\t[]\n\t\t\t\t];\n\t\t\t\tlet pass, node;\n\t\t\t\tlet shadersUpdatedOn1stPass = false;\n\t\t\t\tfor(i = 0; i < bakeLights.length; i++){\n\t\t\t\t\t\tconst bakeLight = bakeLights[i];\n\t\t\t\t\t\tconst isAmbientLight = bakeLight instanceof BakeLightAmbient;\n\t\t\t\t\t\tconst isDirectional = bakeLight.light.type === LIGHTTYPE_DIRECTIONAL;\n\t\t\t\t\t\tlet numVirtualLights = bakeLight.numVirtualLights;\n\t\t\t\t\t\tif (passCount > 1 && numVirtualLights > 1 && bakeLight.light.bakeDir) {\n\t\t\t\t\t\t\t\tnumVirtualLights = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let virtualLightIndex = 0; virtualLightIndex < numVirtualLights; virtualLightIndex++){\n\t\t\t\t\t\t\t\tif (numVirtualLights > 1) {\n\t\t\t\t\t\t\t\t\t\tbakeLight.prepareVirtualLight(virtualLightIndex, numVirtualLights);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbakeLight.startBake();\n\t\t\t\t\t\t\t\tlet shadowMapRendered = false;\n\t\t\t\t\t\t\t\tconst shadowCam = this.lightCameraPrepare(device, bakeLight);\n\t\t\t\t\t\t\t\tfor(node = 0; node < bakeNodes.length; node++){\n\t\t\t\t\t\t\t\t\t\tconst bakeNode = bakeNodes[node];\n\t\t\t\t\t\t\t\t\t\trcv = bakeNode.meshInstances;\n\t\t\t\t\t\t\t\t\t\tconst lightAffectsNode = this.lightCameraPrepareAndCull(bakeLight, bakeNode, shadowCam, casterBounds);\n\t\t\t\t\t\t\t\t\t\tif (!lightAffectsNode) {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.setupLightArray(lightArray, bakeLight.light);\n\t\t\t\t\t\t\t\t\t\tconst clusterLights = isDirectional ? [] : [\n\t\t\t\t\t\t\t\t\t\t\t\tbakeLight.light\n\t\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t\t\tif (clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.renderer.lightTextureAtlas.update(clusterLights, this.lightingParams);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tshadowMapRendered = this.renderShadowMap(comp, shadowMapRendered, casters, bakeLight);\n\t\t\t\t\t\t\t\t\t\tif (clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.worldClusters.update(clusterLights, this.lightingParams);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.backupMaterials(rcv);\n\t\t\t\t\t\t\t\t\t\tfor(pass = 0; pass < passCount; pass++){\n\t\t\t\t\t\t\t\t\t\t\t\tif (pass > 0 && virtualLightIndex > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (isAmbientLight && pass > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst nodeRT = bakeNode.renderTargets[pass];\n\t\t\t\t\t\t\t\t\t\t\t\tconst lightmapSize = bakeNode.renderTargets[pass].colorBuffer.width;\n\t\t\t\t\t\t\t\t\t\t\t\tconst tempRT = this.renderTargets.get(lightmapSize);\n\t\t\t\t\t\t\t\t\t\t\t\tconst tempTex = tempRT.colorBuffer;\n\t\t\t\t\t\t\t\t\t\t\t\tif (pass === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tshadersUpdatedOn1stPass = scene.updateShaders;\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (shadersUpdatedOn1stPass) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tscene.updateShaders = true;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tlet passMaterial = this.passMaterials[pass];\n\t\t\t\t\t\t\t\t\t\t\t\tif (isAmbientLight) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst lastVirtualLightForPass = virtualLightIndex + 1 === numVirtualLights;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (lastVirtualLightForPass && pass === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpassMaterial = this.ambientAOMaterial;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tfor(j = 0; j < rcv.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trcv[j].material = passMaterial;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tthis.renderer.updateShaders(rcv);\n\t\t\t\t\t\t\t\t\t\t\t\tif (pass === PASS_DIR) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.constantBakeDir.setValue(bakeLight.light.bakeDir ? 1 : 0);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (device.isWebGPU) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst renderPass = new RenderPassLightmapper(device, this.renderer, this.camera, clusteredLightingEnabled ? this.worldClusters : null, rcv, lightArray);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trenderPass.init(tempRT);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trenderPass.render();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trenderPass.destroy();\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.renderer.setCamera(this.camera, tempRT, true);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.worldClusters.activate();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.renderer._forwardTime = 0;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.renderer._shadowMapTime = 0;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.renderer.renderForward(this.camera, tempRT, rcv, lightArray, SHADER_FORWARD);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdevice.updateEnd();\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbakeNode.renderTargets[pass] = tempRT;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.renderTargets.set(lightmapSize, nodeRT);\n\t\t\t\t\t\t\t\t\t\t\t\tfor(j = 0; j < rcv.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tm = rcv[j];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tm.setRealtimeLightmap(MeshInstance.lightmapParamNames[pass], tempTex);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tm._shaderDefs |= SHADERDEF_LM;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.restoreMaterials(rcv);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbakeLight.endBake(this.shadowMapCache);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.postprocessTextures(device, bakeNodes, passCount);\n\t\t\t\tfor(node = 0; node < allNodes.length; node++){\n\t\t\t\t\t\tallNodes[node].restore();\n\t\t\t\t}\n\t\t\t\tthis.restoreLights(allLights);\n\t\t\t\tthis.restoreScene();\n\t\t\t\tif (!clusteredLightingEnabled) {\n\t\t\t\t\t\tthis.shadowMapCache.clear();\n\t\t\t\t}\n\t\t}\n}\n\nclass Component extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.order = 0;\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper();\n\t\t\t\tthis.system = system;\n\t\t\t\tthis.entity = entity;\n\t\t\t\tif (this.system.schema && !this._accessorsBuilt) {\n\t\t\t\t\t\tthis.buildAccessors(this.system.schema);\n\t\t\t\t}\n\t\t\t\tthis.on('set', function(name, oldValue, newValue) {\n\t\t\t\t\t\tthis.fire(`set_${name}`, name, oldValue, newValue);\n\t\t\t\t});\n\t\t\t\tthis.on('set_enabled', this.onSetEnabled, this);\n\t\t}\n\t\tstatic _buildAccessors(obj, schema) {\n\t\t\t\tschema.forEach((descriptor)=>{\n\t\t\t\t\t\tconst name = typeof descriptor === 'object' ? descriptor.name : descriptor;\n\t\t\t\t\t\tObject.defineProperty(obj, name, {\n\t\t\t\t\t\t\t\tget: function() {\n\t\t\t\t\t\t\t\t\t\treturn this.data[name];\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tset: function(value) {\n\t\t\t\t\t\t\t\t\t\tconst data = this.data;\n\t\t\t\t\t\t\t\t\t\tconst oldValue = data[name];\n\t\t\t\t\t\t\t\t\t\tdata[name] = value;\n\t\t\t\t\t\t\t\t\t\tthis.fire('set', name, oldValue, value);\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tconfigurable: true\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tobj._accessorsBuilt = true;\n\t\t}\n\t\tbuildAccessors(schema) {\n\t\t\t\tComponent._buildAccessors(this, schema);\n\t\t}\n\t\tonSetEnabled(name, oldValue, newValue) {\n\t\t\t\tif (oldValue !== newValue) {\n\t\t\t\t\t\tif (this.entity.enabled) {\n\t\t\t\t\t\t\t\tif (newValue) {\n\t\t\t\t\t\t\t\t\t\tthis.onEnable();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.onDisable();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {}\n\t\tonDisable() {}\n\t\tonPostStateChange() {}\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(arg) {}\n\t\tget enabled() {\n\t\t\t\treturn true;\n\t\t}\n}\n\nclass ComponentSystem extends EventHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper();\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.store = {};\n\t\t\t\tthis.schema = [];\n\t\t}\n\t\taddComponent(entity, data = {}) {\n\t\t\t\tconst component = new this.ComponentType(this, entity);\n\t\t\t\tconst componentData = new this.DataType();\n\t\t\t\tthis.store[entity.getGuid()] = {\n\t\t\t\t\t\tentity: entity,\n\t\t\t\t\t\tdata: componentData\n\t\t\t\t};\n\t\t\t\tentity[this.id] = component;\n\t\t\t\tentity.c[this.id] = component;\n\t\t\t\tthis.initializeComponentData(component, data, []);\n\t\t\t\tthis.fire('add', entity, component);\n\t\t\t\treturn component;\n\t\t}\n\t\tremoveComponent(entity) {\n\t\t\t\tconst id = this.id;\n\t\t\t\tconst record = this.store[entity.getGuid()];\n\t\t\t\tconst component = entity.c[id];\n\t\t\t\tcomponent.fire('beforeremove');\n\t\t\t\tthis.fire('beforeremove', entity, component);\n\t\t\t\tdelete this.store[entity.getGuid()];\n\t\t\t\tentity[id] = undefined;\n\t\t\t\tdelete entity.c[id];\n\t\t\t\tthis.fire('remove', entity, record.data);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst src = this.store[entity.getGuid()];\n\t\t\t\treturn this.addComponent(clone, src.data);\n\t\t}\n\t\tinitializeComponentData(component, data = {}, properties) {\n\t\t\t\tfor(let i = 0, len = properties.length; i < len; i++){\n\t\t\t\t\t\tconst descriptor = properties[i];\n\t\t\t\t\t\tlet name, type;\n\t\t\t\t\t\tif (typeof descriptor === 'object') {\n\t\t\t\t\t\t\t\tname = descriptor.name;\n\t\t\t\t\t\t\t\ttype = descriptor.type;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tname = descriptor;\n\t\t\t\t\t\t\t\ttype = undefined;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet value = data[name];\n\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\t\tif (type !== undefined) {\n\t\t\t\t\t\t\t\t\t\tvalue = convertValue(value, type);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcomponent[name] = value;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomponent[name] = component.data[name];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (component.enabled && component.entity.enabled) {\n\t\t\t\t\t\tcomponent.onEnable();\n\t\t\t\t}\n\t\t}\n\t\tgetPropertiesOfType(type) {\n\t\t\t\tconst matchingProperties = [];\n\t\t\t\tconst schema = this.schema || [];\n\t\t\t\tschema.forEach((descriptor)=>{\n\t\t\t\t\t\tif (descriptor && typeof descriptor === 'object' && descriptor.type === type) {\n\t\t\t\t\t\t\t\tmatchingProperties.push(descriptor);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn matchingProperties;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.off();\n\t\t}\n}\nfunction convertValue(value, type) {\n\t\tif (!value) {\n\t\t\t\treturn value;\n\t\t}\n\t\tswitch(type){\n\t\t\t\tcase 'rgb':\n\t\t\t\t\t\tif (value instanceof Color) {\n\t\t\t\t\t\t\t\treturn value.clone();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn new Color(value[0], value[1], value[2]);\n\t\t\t\tcase 'rgba':\n\t\t\t\t\t\tif (value instanceof Color) {\n\t\t\t\t\t\t\t\treturn value.clone();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn new Color(value[0], value[1], value[2], value[3]);\n\t\t\t\tcase 'vec2':\n\t\t\t\t\t\tif (value instanceof Vec2) {\n\t\t\t\t\t\t\t\treturn value.clone();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn new Vec2(value[0], value[1]);\n\t\t\t\tcase 'vec3':\n\t\t\t\t\t\tif (value instanceof Vec3) {\n\t\t\t\t\t\t\t\treturn value.clone();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn new Vec3(value[0], value[1], value[2]);\n\t\t\t\tcase 'vec4':\n\t\t\t\t\t\tif (value instanceof Vec4) {\n\t\t\t\t\t\t\t\treturn value.clone();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn new Vec4(value[0], value[1], value[2], value[3]);\n\t\t\t\tcase 'boolean':\n\t\t\t\tcase 'number':\n\t\t\t\tcase 'string':\n\t\t\t\t\t\treturn value;\n\t\t\t\tcase 'entity':\n\t\t\t\t\t\treturn value;\n\t\t\t\tdefault:\n\t\t\t\t\t\tthrow new Error(`Could not convert unhandled type: ${type}`);\n\t\t}\n}\n\nconst INTERPOLATION_STEP = 0;\nconst INTERPOLATION_LINEAR = 1;\nconst INTERPOLATION_CUBIC = 2;\n\nclass AnimCache {\n\t\tconstructor(){\n\t\t\t\tthis._left = Infinity;\n\t\t\t\tthis._right = -Infinity;\n\t\t\t\tthis._len = 0;\n\t\t\t\tthis._recip = 0;\n\t\t\t\tthis._p0 = 0;\n\t\t\t\tthis._p1 = 0;\n\t\t\t\tthis._t = 0;\n\t\t\t\tthis._hermite = {\n\t\t\t\t\t\tvalid: false,\n\t\t\t\t\t\tp0: 0,\n\t\t\t\t\t\tm0: 0,\n\t\t\t\t\t\tp1: 0,\n\t\t\t\t\t\tm1: 0\n\t\t\t\t};\n\t\t}\n\t\tupdate(time, input) {\n\t\t\t\tif (time < this._left || time >= this._right) {\n\t\t\t\t\t\tconst len = input.length;\n\t\t\t\t\t\tif (!len) {\n\t\t\t\t\t\t\t\tthis._left = -Infinity;\n\t\t\t\t\t\t\t\tthis._right = Infinity;\n\t\t\t\t\t\t\t\tthis._len = 0;\n\t\t\t\t\t\t\t\tthis._recip = 0;\n\t\t\t\t\t\t\t\tthis._p0 = this._p1 = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (time < input[0]) {\n\t\t\t\t\t\t\t\t\t\tthis._left = -Infinity;\n\t\t\t\t\t\t\t\t\t\tthis._right = input[0];\n\t\t\t\t\t\t\t\t\t\tthis._len = 0;\n\t\t\t\t\t\t\t\t\t\tthis._recip = 0;\n\t\t\t\t\t\t\t\t\t\tthis._p0 = this._p1 = 0;\n\t\t\t\t\t\t\t\t} else if (time >= input[len - 1]) {\n\t\t\t\t\t\t\t\t\t\tthis._left = input[len - 1];\n\t\t\t\t\t\t\t\t\t\tthis._right = Infinity;\n\t\t\t\t\t\t\t\t\t\tthis._len = 0;\n\t\t\t\t\t\t\t\t\t\tthis._recip = 0;\n\t\t\t\t\t\t\t\t\t\tthis._p0 = this._p1 = len - 1;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst index = this._findKey(time, input);\n\t\t\t\t\t\t\t\t\t\tthis._left = input[index];\n\t\t\t\t\t\t\t\t\t\tthis._right = input[index + 1];\n\t\t\t\t\t\t\t\t\t\tthis._len = this._right - this._left;\n\t\t\t\t\t\t\t\t\t\tconst diff = 1.0 / this._len;\n\t\t\t\t\t\t\t\t\t\tthis._recip = isFinite(diff) ? diff : 0;\n\t\t\t\t\t\t\t\t\t\tthis._p0 = index;\n\t\t\t\t\t\t\t\t\t\tthis._p1 = index + 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._t = this._recip === 0 ? 0 : (time - this._left) * this._recip;\n\t\t\t\tthis._hermite.valid = false;\n\t\t}\n\t\t_findKey(time, input) {\n\t\t\t\tlet index = 0;\n\t\t\t\twhile(time >= input[index + 1]){\n\t\t\t\t\t\tindex++;\n\t\t\t\t}\n\t\t\t\treturn index;\n\t\t}\n\t\teval(result, interpolation, output) {\n\t\t\t\tconst data = output._data;\n\t\t\t\tconst comp = output._components;\n\t\t\t\tconst idx0 = this._p0 * comp;\n\t\t\t\tif (interpolation === INTERPOLATION_STEP) {\n\t\t\t\t\t\tfor(let i = 0; i < comp; ++i){\n\t\t\t\t\t\t\t\tresult[i] = data[idx0 + i];\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst t = this._t;\n\t\t\t\t\t\tconst idx1 = this._p1 * comp;\n\t\t\t\t\t\tswitch(interpolation){\n\t\t\t\t\t\t\t\tcase INTERPOLATION_LINEAR:\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < comp; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tresult[i] = math.lerp(data[idx0 + i], data[idx1 + i], t);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase INTERPOLATION_CUBIC:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tconst hermite = this._hermite;\n\t\t\t\t\t\t\t\t\t\t\t\tif (!hermite.valid) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst t2 = t * t;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst twot = t + t;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst omt = 1 - t;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst omt2 = omt * omt;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thermite.valid = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thermite.p0 = (1 + twot) * omt2;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thermite.m0 = t * omt2;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thermite.p1 = t2 * (3 - twot);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thermite.m1 = t2 * (t - 1);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst p0 = (this._p0 * 3 + 1) * comp;\n\t\t\t\t\t\t\t\t\t\t\t\tconst m0 = (this._p0 * 3 + 2) * comp;\n\t\t\t\t\t\t\t\t\t\t\t\tconst p1 = (this._p1 * 3 + 1) * comp;\n\t\t\t\t\t\t\t\t\t\t\t\tconst m1 = (this._p1 * 3 + 0) * comp;\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < comp; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult[i] = hermite.p0 * data[p0 + i] + hermite.m0 * data[m0 + i] * this._len + hermite.p1 * data[p1 + i] + hermite.m1 * data[m1 + i] * this._len;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimSnapshot {\n\t\tconstructor(animTrack){\n\t\t\t\tthis._name = `${animTrack.name}Snapshot`;\n\t\t\t\tthis._time = -1;\n\t\t\t\tthis._cache = [];\n\t\t\t\tthis._results = [];\n\t\t\t\tfor(let i = 0; i < animTrack._inputs.length; ++i){\n\t\t\t\t\t\tthis._cache[i] = new AnimCache();\n\t\t\t\t}\n\t\t\t\tconst curves = animTrack._curves;\n\t\t\t\tconst outputs = animTrack._outputs;\n\t\t\t\tfor(let i = 0; i < curves.length; ++i){\n\t\t\t\t\t\tconst curve = curves[i];\n\t\t\t\t\t\tconst output = outputs[curve._output];\n\t\t\t\t\t\tconst storage = [];\n\t\t\t\t\t\tfor(let j = 0; j < output._components; ++j){\n\t\t\t\t\t\t\t\tstorage[j] = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._results[i] = storage;\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimClip {\n\t\tstatic{\n\t\t\t\tthis.eventFrame = {\n\t\t\t\t\t\tstart: 0,\n\t\t\t\t\t\tend: 0,\n\t\t\t\t\t\tresidual: 0\n\t\t\t\t};\n\t\t}\n\t\tconstructor(track, time, speed, playing, loop, eventHandler){\n\t\t\t\tthis._name = track.name;\n\t\t\t\tthis._track = track;\n\t\t\t\tthis._snapshot = new AnimSnapshot(track);\n\t\t\t\tthis._playing = playing;\n\t\t\t\tthis._time = time;\n\t\t\t\tthis._speed = speed;\n\t\t\t\tthis._loop = loop;\n\t\t\t\tthis._blendWeight = 1.0;\n\t\t\t\tthis._blendOrder = 0.0;\n\t\t\t\tthis._eventHandler = eventHandler;\n\t\t\t\tthis.alignCursorToCurrentTime();\n\t\t}\n\t\tset name(name) {\n\t\t\t\tthis._name = name;\n\t\t}\n\t\tget name() {\n\t\t\t\treturn this._name;\n\t\t}\n\t\tset track(track) {\n\t\t\t\tthis._track = track;\n\t\t\t\tthis._snapshot = new AnimSnapshot(track);\n\t\t}\n\t\tget track() {\n\t\t\t\treturn this._track;\n\t\t}\n\t\tget snapshot() {\n\t\t\t\treturn this._snapshot;\n\t\t}\n\t\tset time(time) {\n\t\t\t\tthis._time = time;\n\t\t\t\tthis.alignCursorToCurrentTime();\n\t\t}\n\t\tget time() {\n\t\t\t\treturn this._time;\n\t\t}\n\t\tset speed(speed) {\n\t\t\t\tconst signChanged = Math.sign(speed) !== Math.sign(this._speed);\n\t\t\t\tthis._speed = speed;\n\t\t\t\tif (signChanged) {\n\t\t\t\t\t\tthis.alignCursorToCurrentTime();\n\t\t\t\t}\n\t\t}\n\t\tget speed() {\n\t\t\t\treturn this._speed;\n\t\t}\n\t\tset loop(loop) {\n\t\t\t\tthis._loop = loop;\n\t\t}\n\t\tget loop() {\n\t\t\t\treturn this._loop;\n\t\t}\n\t\tset blendWeight(blendWeight) {\n\t\t\t\tthis._blendWeight = blendWeight;\n\t\t}\n\t\tget blendWeight() {\n\t\t\t\treturn this._blendWeight;\n\t\t}\n\t\tset blendOrder(blendOrder) {\n\t\t\t\tthis._blendOrder = blendOrder;\n\t\t}\n\t\tget blendOrder() {\n\t\t\t\treturn this._blendOrder;\n\t\t}\n\t\tset eventCursor(value) {\n\t\t\t\tthis._eventCursor = value;\n\t\t}\n\t\tget eventCursor() {\n\t\t\t\treturn this._eventCursor;\n\t\t}\n\t\tget eventCursorEnd() {\n\t\t\t\treturn this.isReverse ? 0 : this._track.events.length - 1;\n\t\t}\n\t\tget nextEvent() {\n\t\t\t\treturn this._track.events[this._eventCursor];\n\t\t}\n\t\tget isReverse() {\n\t\t\t\treturn this._speed < 0;\n\t\t}\n\t\tnextEventAheadOfTime(time) {\n\t\t\t\tif (!this.nextEvent) return false;\n\t\t\t\treturn this.isReverse ? this.nextEvent.time <= time : this.nextEvent.time >= time;\n\t\t}\n\t\tnextEventBehindTime(time) {\n\t\t\t\tif (!this.nextEvent) return false;\n\t\t\t\tif (time === this.track.duration) {\n\t\t\t\t\t\treturn this.isReverse ? this.nextEvent.time >= time : this.nextEvent.time <= time;\n\t\t\t\t}\n\t\t\t\treturn this.isReverse ? this.nextEvent.time > time : this.nextEvent.time < time;\n\t\t}\n\t\tresetEventCursor() {\n\t\t\t\tthis._eventCursor = this.isReverse ? this._track.events.length - 1 : 0;\n\t\t}\n\t\tmoveEventCursor() {\n\t\t\t\tthis._eventCursor += this.isReverse ? -1 : 1;\n\t\t\t\tif (this._eventCursor >= this.track.events.length) {\n\t\t\t\t\t\tthis._eventCursor = 0;\n\t\t\t\t} else if (this._eventCursor < 0) {\n\t\t\t\t\t\tthis._eventCursor = this.track.events.length - 1;\n\t\t\t\t}\n\t\t}\n\t\tclipFrameTime(frameEndTime) {\n\t\t\t\tconst eventFrame = AnimClip.eventFrame;\n\t\t\t\teventFrame.start = 0;\n\t\t\t\teventFrame.end = frameEndTime;\n\t\t\t\teventFrame.residual = 0;\n\t\t\t\tif (this.isReverse) {\n\t\t\t\t\t\tif (frameEndTime < 0) {\n\t\t\t\t\t\t\t\teventFrame.start = this.track.duration;\n\t\t\t\t\t\t\t\teventFrame.end = 0;\n\t\t\t\t\t\t\t\teventFrame.residual = frameEndTime + this.track.duration;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (frameEndTime > this.track.duration) {\n\t\t\t\t\t\t\t\teventFrame.start = 0;\n\t\t\t\t\t\t\t\teventFrame.end = this.track.duration;\n\t\t\t\t\t\t\t\teventFrame.residual = frameEndTime - this.track.duration;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\talignCursorToCurrentTime() {\n\t\t\t\tthis.resetEventCursor();\n\t\t\t\twhile(this.nextEventBehindTime(this._time) && this._eventCursor !== this.eventCursorEnd){\n\t\t\t\t\t\tthis.moveEventCursor();\n\t\t\t\t}\n\t\t}\n\t\tfireNextEvent() {\n\t\t\t\tthis._eventHandler.fire(this.nextEvent.name, {\n\t\t\t\t\t\ttrack: this.track,\n\t\t\t\t\t\t...this.nextEvent\n\t\t\t\t});\n\t\t\t\tthis.moveEventCursor();\n\t\t}\n\t\tfireNextEventInFrame(frameStartTime, frameEndTime) {\n\t\t\t\tif (this.nextEventAheadOfTime(frameStartTime) && this.nextEventBehindTime(frameEndTime)) {\n\t\t\t\t\t\tthis.fireNextEvent();\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tactiveEventsForFrame(frameStartTime, frameEndTime) {\n\t\t\t\tconst eventFrame = AnimClip.eventFrame;\n\t\t\t\tthis.clipFrameTime(frameEndTime);\n\t\t\t\tconst initialCursor = this.eventCursor;\n\t\t\t\twhile(this.fireNextEventInFrame(frameStartTime, eventFrame.end)){\n\t\t\t\t\t\tif (initialCursor === this.eventCursor) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.loop && Math.abs(eventFrame.residual) > 0) {\n\t\t\t\t\t\tthis.activeEventsForFrame(eventFrame.start, eventFrame.residual);\n\t\t\t\t}\n\t\t}\n\t\tprogressForTime(time) {\n\t\t\t\treturn time * this._speed / this._track.duration;\n\t\t}\n\t\t_update(deltaTime) {\n\t\t\t\tif (this._playing) {\n\t\t\t\t\t\tlet time = this._time;\n\t\t\t\t\t\tconst duration = this._track.duration;\n\t\t\t\t\t\tconst speed = this._speed;\n\t\t\t\t\t\tconst loop = this._loop;\n\t\t\t\t\t\tif (this._track.events.length > 0 && duration > 0) {\n\t\t\t\t\t\t\t\tthis.activeEventsForFrame(time, time + speed * deltaTime);\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttime += speed * deltaTime;\n\t\t\t\t\t\tif (speed >= 0) {\n\t\t\t\t\t\t\t\tif (time > duration) {\n\t\t\t\t\t\t\t\t\t\tif (loop) {\n\t\t\t\t\t\t\t\t\t\t\t\ttime = time % duration || 0;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\ttime = this._track.duration;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.pause();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (time < 0) {\n\t\t\t\t\t\t\t\t\t\tif (loop) {\n\t\t\t\t\t\t\t\t\t\t\t\ttime = duration + (time % duration || 0);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\ttime = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.pause();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._time = time;\n\t\t\t\t}\n\t\t\t\tif (this._time !== this._snapshot._time) {\n\t\t\t\t\t\tthis._track.eval(this._time, this._snapshot);\n\t\t\t\t}\n\t\t}\n\t\tplay() {\n\t\t\t\tthis._playing = true;\n\t\t\t\tthis._time = 0;\n\t\t}\n\t\tstop() {\n\t\t\t\tthis._playing = false;\n\t\t\t\tthis._time = 0;\n\t\t}\n\t\tpause() {\n\t\t\t\tthis._playing = false;\n\t\t}\n\t\tresume() {\n\t\t\t\tthis._playing = true;\n\t\t}\n\t\treset() {\n\t\t\t\tthis._time = 0;\n\t\t}\n}\n\nconst ANIM_INTERRUPTION_NONE = 'NONE';\nconst ANIM_INTERRUPTION_PREV = 'PREV_STATE';\nconst ANIM_INTERRUPTION_NEXT = 'NEXT_STATE';\nconst ANIM_INTERRUPTION_PREV_NEXT = 'PREV_STATE_NEXT_STATE';\nconst ANIM_INTERRUPTION_NEXT_PREV = 'NEXT_STATE_PREV_STATE';\nconst ANIM_GREATER_THAN = 'GREATER_THAN';\nconst ANIM_LESS_THAN = 'LESS_THAN';\nconst ANIM_GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO';\nconst ANIM_LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO';\nconst ANIM_EQUAL_TO = 'EQUAL_TO';\nconst ANIM_NOT_EQUAL_TO = 'NOT_EQUAL_TO';\nconst ANIM_PARAMETER_INTEGER = 'INTEGER';\nconst ANIM_PARAMETER_FLOAT = 'FLOAT';\nconst ANIM_PARAMETER_BOOLEAN = 'BOOLEAN';\nconst ANIM_PARAMETER_TRIGGER = 'TRIGGER';\nconst ANIM_BLEND_1D = '1D';\nconst ANIM_BLEND_2D_DIRECTIONAL = '2D_DIRECTIONAL';\nconst ANIM_BLEND_2D_CARTESIAN = '2D_CARTESIAN';\nconst ANIM_BLEND_DIRECT = 'DIRECT';\nconst ANIM_STATE_START = 'START';\nconst ANIM_STATE_END = 'END';\nconst ANIM_STATE_ANY = 'ANY';\nconst ANIM_CONTROL_STATES = [\n\t\tANIM_STATE_START,\n\t\tANIM_STATE_END,\n\t\tANIM_STATE_ANY\n];\nconst ANIM_LAYER_OVERWRITE = 'OVERWRITE';\nconst ANIM_LAYER_ADDITIVE = 'ADDITIVE';\n\nclass AnimBlend {\n\t\tstatic dot(a, b) {\n\t\t\t\tconst len = a.length;\n\t\t\t\tlet result = 0;\n\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\tresult += a[i] * b[i];\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tstatic normalize(a) {\n\t\t\t\tlet l = AnimBlend.dot(a, a);\n\t\t\t\tif (l > 0) {\n\t\t\t\t\t\tl = 1.0 / Math.sqrt(l);\n\t\t\t\t\t\tconst len = a.length;\n\t\t\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\t\t\ta[i] *= l;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tstatic set(a, b, type) {\n\t\t\t\tconst len = a.length;\n\t\t\t\tif (type === 'quaternion') {\n\t\t\t\t\t\tlet l = AnimBlend.dot(b, b);\n\t\t\t\t\t\tif (l > 0) {\n\t\t\t\t\t\t\t\tl = 1.0 / Math.sqrt(l);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\t\t\ta[i] = b[i] * l;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\t\t\ta[i] = b[i];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tstatic blendVec(a, b, t, additive) {\n\t\t\t\tconst it = additive ? 1.0 : 1.0 - t;\n\t\t\t\tconst len = a.length;\n\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\ta[i] = a[i] * it + b[i] * t;\n\t\t\t\t}\n\t\t}\n\t\tstatic blendQuat(a, b, t, additive) {\n\t\t\t\tconst len = a.length;\n\t\t\t\tconst it = additive ? 1.0 : 1.0 - t;\n\t\t\t\tif (AnimBlend.dot(a, b) < 0) {\n\t\t\t\t\t\tt = -t;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\t\t\ta[i] = a[i] * it + b[i] * t;\n\t\t\t\t}\n\t\t\t\tif (!additive) {\n\t\t\t\t\t\tAnimBlend.normalize(a);\n\t\t\t\t}\n\t\t}\n\t\tstatic blend(a, b, t, type, additive) {\n\t\t\t\tif (type === 'quaternion') {\n\t\t\t\t\t\tAnimBlend.blendQuat(a, b, t, additive);\n\t\t\t\t} else {\n\t\t\t\t\t\tAnimBlend.blendVec(a, b, t, additive);\n\t\t\t\t}\n\t\t}\n\t\tstatic stableSort(a, lessFunc) {\n\t\t\t\tconst len = a.length;\n\t\t\t\tfor(let i = 0; i < len - 1; ++i){\n\t\t\t\t\t\tfor(let j = i + 1; j < len; ++j){\n\t\t\t\t\t\t\t\tif (lessFunc(a[j], a[i])) {\n\t\t\t\t\t\t\t\t\t\tconst tmp = a[i];\n\t\t\t\t\t\t\t\t\t\ta[i] = a[j];\n\t\t\t\t\t\t\t\t\t\ta[j] = tmp;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimTargetValue {\n\t\tstatic{\n\t\t\t\tthis.TYPE_QUAT = 'quaternion';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.TYPE_VEC3 = 'vector3';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.q1 = new Quat();\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.q2 = new Quat();\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.q3 = new Quat();\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.quatArr = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1\n\t\t\t\t];\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.vecArr = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.IDENTITY_QUAT_ARR = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1\n\t\t\t\t];\n\t\t}\n\t\tconstructor(component, type){\n\t\t\t\tthis._component = component;\n\t\t\t\tthis.mask = new Int8Array(component.layers.length);\n\t\t\t\tthis.weights = new Float32Array(component.layers.length);\n\t\t\t\tthis.totalWeight = 0;\n\t\t\t\tthis.counter = 0;\n\t\t\t\tthis.layerCounter = 0;\n\t\t\t\tthis.valueType = type;\n\t\t\t\tthis.dirty = true;\n\t\t\t\tthis.value = type === AnimTargetValue.TYPE_QUAT ? [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1\n\t\t\t\t] : [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.baseValue = null;\n\t\t\t\tthis.setter = null;\n\t\t}\n\t\tget _normalizeWeights() {\n\t\t\t\treturn this._component.normalizeWeights;\n\t\t}\n\t\tgetWeight(index) {\n\t\t\t\tif (this.dirty) this.updateWeights();\n\t\t\t\tif (this._normalizeWeights && this.totalWeight === 0 || !this.mask[index]) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t} else if (this._normalizeWeights) {\n\t\t\t\t\t\treturn this.weights[index] / this.totalWeight;\n\t\t\t\t}\n\t\t\t\treturn math.clamp(this.weights[index], 0, 1);\n\t\t}\n\t\t_layerBlendType(index) {\n\t\t\t\treturn this._component.layers[index].blendType;\n\t\t}\n\t\tsetMask(index, value) {\n\t\t\t\tthis.mask[index] = value;\n\t\t\t\tif (this._normalizeWeights) {\n\t\t\t\t\t\tif (this._component.layers[index].blendType === ANIM_LAYER_OVERWRITE) {\n\t\t\t\t\t\t\t\tthis.mask = this.mask.fill(0, 0, index);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.dirty = true;\n\t\t\t\t}\n\t\t}\n\t\tupdateWeights() {\n\t\t\t\tthis.totalWeight = 0;\n\t\t\t\tfor(let i = 0; i < this.weights.length; i++){\n\t\t\t\t\t\tthis.weights[i] = this._component.layers[i].weight;\n\t\t\t\t\t\tthis.totalWeight += this.mask[i] * this.weights[i];\n\t\t\t\t}\n\t\t\t\tthis.dirty = false;\n\t\t}\n\t\tupdateValue(index, value) {\n\t\t\t\tif (this.counter === 0) {\n\t\t\t\t\t\tAnimBlend.set(this.value, AnimTargetValue.IDENTITY_QUAT_ARR, this.valueType);\n\t\t\t\t\t\tif (!this._normalizeWeights) {\n\t\t\t\t\t\t\t\tAnimBlend.blend(this.value, this.baseValue, 1, this.valueType);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!this.mask[index] || this.getWeight(index) === 0) return;\n\t\t\t\tif (this._layerBlendType(index) === ANIM_LAYER_ADDITIVE && !this._normalizeWeights) {\n\t\t\t\t\t\tif (this.valueType === AnimTargetValue.TYPE_QUAT) {\n\t\t\t\t\t\t\t\tconst v = AnimTargetValue.q1.set(this.value[0], this.value[1], this.value[2], this.value[3]);\n\t\t\t\t\t\t\t\tconst aV1 = AnimTargetValue.q2.set(this.baseValue[0], this.baseValue[1], this.baseValue[2], this.baseValue[3]);\n\t\t\t\t\t\t\t\tconst aV2 = AnimTargetValue.q3.set(value[0], value[1], value[2], value[3]);\n\t\t\t\t\t\t\t\tconst aV = aV1.invert().mul(aV2);\n\t\t\t\t\t\t\t\taV.slerp(Quat.IDENTITY, aV, this.getWeight(index));\n\t\t\t\t\t\t\t\tv.mul(aV);\n\t\t\t\t\t\t\t\tAnimTargetValue.quatArr[0] = v.x;\n\t\t\t\t\t\t\t\tAnimTargetValue.quatArr[1] = v.y;\n\t\t\t\t\t\t\t\tAnimTargetValue.quatArr[2] = v.z;\n\t\t\t\t\t\t\t\tAnimTargetValue.quatArr[3] = v.w;\n\t\t\t\t\t\t\t\tAnimBlend.set(this.value, AnimTargetValue.quatArr, this.valueType);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tAnimTargetValue.vecArr[0] = value[0] - this.baseValue[0];\n\t\t\t\t\t\t\t\tAnimTargetValue.vecArr[1] = value[1] - this.baseValue[1];\n\t\t\t\t\t\t\t\tAnimTargetValue.vecArr[2] = value[2] - this.baseValue[2];\n\t\t\t\t\t\t\t\tAnimBlend.blend(this.value, AnimTargetValue.vecArr, this.getWeight(index), this.valueType, true);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tAnimBlend.blend(this.value, value, this.getWeight(index), this.valueType);\n\t\t\t\t}\n\t\t\t\tif (this.setter) this.setter(this.value);\n\t\t}\n\t\tunbind() {\n\t\t\t\tif (this.setter) {\n\t\t\t\t\t\tthis.setter(this.baseValue);\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimEvaluator {\n\t\tconstructor(binder){\n\t\t\t\tthis._binder = binder;\n\t\t\t\tthis._clips = [];\n\t\t\t\tthis._inputs = [];\n\t\t\t\tthis._outputs = [];\n\t\t\t\tthis._targets = {};\n\t\t}\n\t\tget clips() {\n\t\t\t\treturn this._clips;\n\t\t}\n\t\taddClip(clip) {\n\t\t\t\tconst targets = this._targets;\n\t\t\t\tconst binder = this._binder;\n\t\t\t\tconst curves = clip.track.curves;\n\t\t\t\tconst snapshot = clip.snapshot;\n\t\t\t\tconst inputs = [];\n\t\t\t\tconst outputs = [];\n\t\t\t\tfor(let i = 0; i < curves.length; ++i){\n\t\t\t\t\t\tconst curve = curves[i];\n\t\t\t\t\t\tconst paths = curve.paths;\n\t\t\t\t\t\tfor(let j = 0; j < paths.length; ++j){\n\t\t\t\t\t\t\t\tconst path = paths[j];\n\t\t\t\t\t\t\t\tconst resolved = binder.resolve(path);\n\t\t\t\t\t\t\t\tlet target = targets[resolved && resolved.targetPath || null];\n\t\t\t\t\t\t\t\tif (!target && resolved) {\n\t\t\t\t\t\t\t\t\t\ttarget = {\n\t\t\t\t\t\t\t\t\t\t\t\ttarget: resolved,\n\t\t\t\t\t\t\t\t\t\t\t\tvalue: [],\n\t\t\t\t\t\t\t\t\t\t\t\tcurves: 0,\n\t\t\t\t\t\t\t\t\t\t\t\tblendCounter: 0\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tfor(let k = 0; k < target.target.components; ++k){\n\t\t\t\t\t\t\t\t\t\t\t\ttarget.value.push(0);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\ttargets[resolved.targetPath] = target;\n\t\t\t\t\t\t\t\t\t\tif (binder.animComponent) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!binder.animComponent.targets[resolved.targetPath]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet type;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (resolved.targetPath.substring(resolved.targetPath.length - 13) === 'localRotation') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype = AnimTargetValue.TYPE_QUAT;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype = AnimTargetValue.TYPE_VEC3;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbinder.animComponent.targets[resolved.targetPath] = new AnimTargetValue(binder.animComponent, type);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbinder.animComponent.targets[resolved.targetPath].layerCounter++;\n\t\t\t\t\t\t\t\t\t\t\t\tbinder.animComponent.targets[resolved.targetPath].setMask(binder.layerIndex, 1);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (target) {\n\t\t\t\t\t\t\t\t\t\ttarget.curves++;\n\t\t\t\t\t\t\t\t\t\tinputs.push(snapshot._results[i]);\n\t\t\t\t\t\t\t\t\t\toutputs.push(target);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._clips.push(clip);\n\t\t\t\tthis._inputs.push(inputs);\n\t\t\t\tthis._outputs.push(outputs);\n\t\t}\n\t\tremoveClip(index) {\n\t\t\t\tconst targets = this._targets;\n\t\t\t\tconst binder = this._binder;\n\t\t\t\tconst clips = this._clips;\n\t\t\t\tconst clip = clips[index];\n\t\t\t\tconst curves = clip.track.curves;\n\t\t\t\tfor(let i = 0; i < curves.length; ++i){\n\t\t\t\t\t\tconst curve = curves[i];\n\t\t\t\t\t\tconst paths = curve.paths;\n\t\t\t\t\t\tfor(let j = 0; j < paths.length; ++j){\n\t\t\t\t\t\t\t\tconst path = paths[j];\n\t\t\t\t\t\t\t\tconst target = this._binder.resolve(path);\n\t\t\t\t\t\t\t\tif (target) {\n\t\t\t\t\t\t\t\t\t\ttarget.curves--;\n\t\t\t\t\t\t\t\t\t\tif (target.curves === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tbinder.unresolve(path);\n\t\t\t\t\t\t\t\t\t\t\t\tdelete targets[target.targetPath];\n\t\t\t\t\t\t\t\t\t\t\t\tif (binder.animComponent) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbinder.animComponent.targets[target.targetPath].layerCounter--;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tclips.splice(index, 1);\n\t\t\t\tthis._inputs.splice(index, 1);\n\t\t\t\tthis._outputs.splice(index, 1);\n\t\t}\n\t\tremoveClips() {\n\t\t\t\twhile(this._clips.length > 0){\n\t\t\t\t\t\tthis.removeClip(0);\n\t\t\t\t}\n\t\t}\n\t\tupdateClipTrack(name, animTrack) {\n\t\t\t\tthis._clips.forEach((clip)=>{\n\t\t\t\t\t\tif (clip.name.includes(name)) {\n\t\t\t\t\t\t\t\tclip.track = animTrack;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis.rebind();\n\t\t}\n\t\tfindClip(name) {\n\t\t\t\tconst clips = this._clips;\n\t\t\t\tfor(let i = 0; i < clips.length; ++i){\n\t\t\t\t\t\tconst clip = clips[i];\n\t\t\t\t\t\tif (clip.name === name) {\n\t\t\t\t\t\t\t\treturn clip;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\trebind() {\n\t\t\t\tthis._binder.rebind();\n\t\t\t\tthis._targets = {};\n\t\t\t\tconst clips = [\n\t\t\t\t\t\t...this.clips\n\t\t\t\t];\n\t\t\t\tthis.removeClips();\n\t\t\t\tclips.forEach((clip)=>{\n\t\t\t\t\t\tthis.addClip(clip);\n\t\t\t\t});\n\t\t}\n\t\tassignMask(mask) {\n\t\t\t\treturn this._binder.assignMask(mask);\n\t\t}\n\t\tupdate(deltaTime, outputAnimation = true) {\n\t\t\t\tconst clips = this._clips;\n\t\t\t\tconst order = clips.map((c, i)=>{\n\t\t\t\t\t\treturn i;\n\t\t\t\t});\n\t\t\t\tAnimBlend.stableSort(order, (a, b)=>{\n\t\t\t\t\t\treturn clips[a].blendOrder < clips[b].blendOrder;\n\t\t\t\t});\n\t\t\t\tfor(let i = 0; i < order.length; ++i){\n\t\t\t\t\t\tconst index = order[i];\n\t\t\t\t\t\tconst clip = clips[index];\n\t\t\t\t\t\tconst inputs = this._inputs[index];\n\t\t\t\t\t\tconst outputs = this._outputs[index];\n\t\t\t\t\t\tconst blendWeight = clip.blendWeight;\n\t\t\t\t\t\tif (blendWeight > 0.0) {\n\t\t\t\t\t\t\t\tclip._update(deltaTime);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!outputAnimation) break;\n\t\t\t\t\t\tlet input;\n\t\t\t\t\t\tlet output;\n\t\t\t\t\t\tlet value;\n\t\t\t\t\t\tif (blendWeight >= 1.0) {\n\t\t\t\t\t\t\t\tfor(let j = 0; j < inputs.length; ++j){\n\t\t\t\t\t\t\t\t\t\tinput = inputs[j];\n\t\t\t\t\t\t\t\t\t\toutput = outputs[j];\n\t\t\t\t\t\t\t\t\t\tvalue = output.value;\n\t\t\t\t\t\t\t\t\t\tAnimBlend.set(value, input, output.target.type);\n\t\t\t\t\t\t\t\t\t\toutput.blendCounter++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (blendWeight > 0.0) {\n\t\t\t\t\t\t\t\tfor(let j = 0; j < inputs.length; ++j){\n\t\t\t\t\t\t\t\t\t\tinput = inputs[j];\n\t\t\t\t\t\t\t\t\t\toutput = outputs[j];\n\t\t\t\t\t\t\t\t\t\tvalue = output.value;\n\t\t\t\t\t\t\t\t\t\tif (output.blendCounter === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tAnimBlend.set(value, input, output.target.type);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tAnimBlend.blend(value, input, blendWeight, output.target.type);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\toutput.blendCounter++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst targets = this._targets;\n\t\t\t\tconst binder = this._binder;\n\t\t\t\tfor(const path in targets){\n\t\t\t\t\t\tif (targets.hasOwnProperty(path)) {\n\t\t\t\t\t\t\t\tconst target = targets[path];\n\t\t\t\t\t\t\t\tif (binder.animComponent && target.target.isTransform) {\n\t\t\t\t\t\t\t\t\t\tconst animTarget = binder.animComponent.targets[path];\n\t\t\t\t\t\t\t\t\t\tif (animTarget.counter === animTarget.layerCounter) {\n\t\t\t\t\t\t\t\t\t\t\t\tanimTarget.counter = 0;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!animTarget.path) {\n\t\t\t\t\t\t\t\t\t\t\t\tanimTarget.path = path;\n\t\t\t\t\t\t\t\t\t\t\t\tanimTarget.baseValue = target.target.get();\n\t\t\t\t\t\t\t\t\t\t\t\tanimTarget.setter = target.target.set;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tanimTarget.updateValue(binder.layerIndex, target.value);\n\t\t\t\t\t\t\t\t\t\tanimTarget.counter++;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\ttarget.target.set(target.value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttarget.blendCounter = 0;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._binder.update(deltaTime);\n\t\t}\n}\n\nclass AnimEvents {\n\t\tconstructor(events){\n\t\t\t\tthis._events = [\n\t\t\t\t\t\t...events\n\t\t\t\t];\n\t\t\t\tthis._events.sort((a, b)=>a.time - b.time);\n\t\t}\n\t\tget events() {\n\t\t\t\treturn this._events;\n\t\t}\n}\n\nclass AnimTrack {\n\t\tstatic{\n\t\t\t\tthis.EMPTY = Object.freeze(new AnimTrack('empty', Number.MAX_VALUE, [], [], []));\n\t\t}\n\t\tconstructor(name, duration, inputs, outputs, curves, animEvents = new AnimEvents([])){\n\t\t\t\tthis._name = name;\n\t\t\t\tthis._duration = duration;\n\t\t\t\tthis._inputs = inputs;\n\t\t\t\tthis._outputs = outputs;\n\t\t\t\tthis._curves = curves;\n\t\t\t\tthis._animEvents = animEvents;\n\t\t}\n\t\tget name() {\n\t\t\t\treturn this._name;\n\t\t}\n\t\tget duration() {\n\t\t\t\treturn this._duration;\n\t\t}\n\t\tget inputs() {\n\t\t\t\treturn this._inputs;\n\t\t}\n\t\tget outputs() {\n\t\t\t\treturn this._outputs;\n\t\t}\n\t\tget curves() {\n\t\t\t\treturn this._curves;\n\t\t}\n\t\tset events(animEvents) {\n\t\t\t\tthis._animEvents = animEvents;\n\t\t}\n\t\tget events() {\n\t\t\t\treturn this._animEvents.events;\n\t\t}\n\t\teval(time, snapshot) {\n\t\t\t\tsnapshot._time = time;\n\t\t\t\tconst inputs = this._inputs;\n\t\t\t\tconst outputs = this._outputs;\n\t\t\t\tconst curves = this._curves;\n\t\t\t\tconst cache = snapshot._cache;\n\t\t\t\tconst results = snapshot._results;\n\t\t\t\tfor(let i = 0; i < inputs.length; ++i){\n\t\t\t\t\t\tcache[i].update(time, inputs[i]._data);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < curves.length; ++i){\n\t\t\t\t\t\tconst curve = curves[i];\n\t\t\t\t\t\tconst output = outputs[curve._output];\n\t\t\t\t\t\tconst result = results[i];\n\t\t\t\t\t\tcache[curve._input].eval(result, curve._interpolation, output);\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimBinder {\n\t\tstatic joinPath(pathSegments, character) {\n\t\t\t\tcharacter = character || '.';\n\t\t\t\tconst escape = function(string) {\n\t\t\t\t\t\treturn string.replace(/\\\\/g, '\\\\\\\\').replace(new RegExp(`\\\\${character}`, 'g'), `\\\\${character}`);\n\t\t\t\t};\n\t\t\t\treturn pathSegments.map(escape).join(character);\n\t\t}\n\t\tstatic splitPath(path, character) {\n\t\t\t\tcharacter = character || '.';\n\t\t\t\tconst result = [];\n\t\t\t\tlet curr = '';\n\t\t\t\tlet i = 0;\n\t\t\t\twhile(i < path.length){\n\t\t\t\t\t\tlet c = path[i++];\n\t\t\t\t\t\tif (c === '\\\\' && i < path.length) {\n\t\t\t\t\t\t\t\tc = path[i++];\n\t\t\t\t\t\t\t\tif (c === '\\\\' || c === character) {\n\t\t\t\t\t\t\t\t\t\tcurr += c;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcurr += `\\\\${c}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (c === character) {\n\t\t\t\t\t\t\t\tresult.push(curr);\n\t\t\t\t\t\t\t\tcurr = '';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcurr += c;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (curr.length > 0) {\n\t\t\t\t\t\tresult.push(curr);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tstatic encode(entityPath, component, propertyPath) {\n\t\t\t\treturn `${Array.isArray(entityPath) ? entityPath.join('/') : entityPath}/${component}/${Array.isArray(propertyPath) ? propertyPath.join('/') : propertyPath}`;\n\t\t}\n\t\tresolve(path) {\n\t\t\t\treturn null;\n\t\t}\n\t\tunresolve(path) {}\n\t\tupdate(deltaTime) {}\n}\n\nclass AnimTarget {\n\t\tconstructor(func, type, components, targetPath){\n\t\t\t\tif (func.set) {\n\t\t\t\t\t\tthis._set = func.set;\n\t\t\t\t\t\tthis._get = func.get;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._set = func;\n\t\t\t\t}\n\t\t\t\tthis._type = type;\n\t\t\t\tthis._components = components;\n\t\t\t\tthis._targetPath = targetPath;\n\t\t\t\tthis._isTransform = this._targetPath.substring(this._targetPath.length - 13) === 'localRotation' || this._targetPath.substring(this._targetPath.length - 13) === 'localPosition' || this._targetPath.substring(this._targetPath.length - 10) === 'localScale';\n\t\t}\n\t\tget set() {\n\t\t\t\treturn this._set;\n\t\t}\n\t\tget get() {\n\t\t\t\treturn this._get;\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tget components() {\n\t\t\t\treturn this._components;\n\t\t}\n\t\tget targetPath() {\n\t\t\t\treturn this._targetPath;\n\t\t}\n\t\tget isTransform() {\n\t\t\t\treturn this._isTransform;\n\t\t}\n}\n\nclass DefaultAnimBinder {\n\t\tconstructor(graph){\n\t\t\t\tthis._isPathInMask = (path, checkMaskValue)=>{\n\t\t\t\t\t\tconst maskItem = this._mask[path];\n\t\t\t\t\t\tif (!maskItem) return false;\n\t\t\t\t\t\telse if (maskItem.children || checkMaskValue && maskItem.value !== false) return true;\n\t\t\t\t\t\treturn false;\n\t\t\t\t};\n\t\t\t\tthis.graph = graph;\n\t\t\t\tif (!graph) return;\n\t\t\t\tthis._mask = null;\n\t\t\t\tconst nodes = {};\n\t\t\t\tconst flatten = function(node) {\n\t\t\t\t\t\tnodes[node.name] = node;\n\t\t\t\t\t\tfor(let i = 0; i < node.children.length; ++i){\n\t\t\t\t\t\t\t\tflatten(node.children[i]);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tflatten(graph);\n\t\t\t\tthis.nodes = nodes;\n\t\t\t\tthis.targetCache = {};\n\t\t\t\tconst findMeshInstances = function(node) {\n\t\t\t\t\t\tlet object = node;\n\t\t\t\t\t\twhile(object && !(object instanceof Entity)){\n\t\t\t\t\t\t\t\tobject = object.parent;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet meshInstances;\n\t\t\t\t\t\tif (object) {\n\t\t\t\t\t\t\t\tif (object.render) {\n\t\t\t\t\t\t\t\t\t\tmeshInstances = object.render.meshInstances;\n\t\t\t\t\t\t\t\t} else if (object.model) {\n\t\t\t\t\t\t\t\t\t\tmeshInstances = object.model.meshInstances;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn meshInstances;\n\t\t\t\t};\n\t\t\t\tthis.nodeCounts = {};\n\t\t\t\tthis.activeNodes = [];\n\t\t\t\tthis.handlers = {\n\t\t\t\t\t\t'localPosition': function(node) {\n\t\t\t\t\t\t\t\tconst object = node.localPosition;\n\t\t\t\t\t\t\t\tconst func = function(value) {\n\t\t\t\t\t\t\t\t\t\tobject.set(...value);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\treturn DefaultAnimBinder.createAnimTarget(func, 'vector', 3, node, 'localPosition');\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'localRotation': function(node) {\n\t\t\t\t\t\t\t\tconst object = node.localRotation;\n\t\t\t\t\t\t\t\tconst func = function(value) {\n\t\t\t\t\t\t\t\t\t\tobject.set(...value);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\treturn DefaultAnimBinder.createAnimTarget(func, 'quaternion', 4, node, 'localRotation');\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'localScale': function(node) {\n\t\t\t\t\t\t\t\tconst object = node.localScale;\n\t\t\t\t\t\t\t\tconst func = function(value) {\n\t\t\t\t\t\t\t\t\t\tobject.set(...value);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\treturn DefaultAnimBinder.createAnimTarget(func, 'vector', 3, node, 'localScale');\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'weight': function(node, weightName) {\n\t\t\t\t\t\t\t\tif (weightName.indexOf('name.') === 0) {\n\t\t\t\t\t\t\t\t\t\tweightName = weightName.replace('name.', '');\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tweightName = Number(weightName);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst meshInstances = findMeshInstances(node);\n\t\t\t\t\t\t\t\tlet setters;\n\t\t\t\t\t\t\t\tif (meshInstances) {\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tif (meshInstances[i].node.name === node.name && meshInstances[i].morphInstance) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst morphInstance = meshInstances[i].morphInstance;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst func = (value)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmorphInstance.setWeight(weightName, value[0]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (!setters) setters = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetters.push(func);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (setters) {\n\t\t\t\t\t\t\t\t\t\tconst callSetters = (value)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < setters.length; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetters[i](value);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\treturn DefaultAnimBinder.createAnimTarget(callSetters, 'number', 1, node, `weight.${weightName}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'materialTexture': (node, textureName)=>{\n\t\t\t\t\t\t\t\tconst meshInstances = findMeshInstances(node);\n\t\t\t\t\t\t\t\tif (meshInstances) {\n\t\t\t\t\t\t\t\t\t\tlet meshInstance;\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tif (meshInstances[i].node.name === node.name) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance = meshInstances[i];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (meshInstance) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst func = (value)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst textureAsset = this.animComponent.system.app.assets.get(value[0]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (textureAsset && textureAsset.resource && textureAsset.type === 'texture') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance.material[textureName] = textureAsset.resource;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance.material.update();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\treturn DefaultAnimBinder.createAnimTarget(func, 'vector', 1, node, 'materialTexture', 'material');\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t}\n\t\t_isPathActive(path) {\n\t\t\t\tif (!this._mask) return true;\n\t\t\t\tconst rootNodeNames = [\n\t\t\t\t\t\tpath.entityPath[0],\n\t\t\t\t\t\tthis.graph.name\n\t\t\t\t];\n\t\t\t\tfor(let j = 0; j < rootNodeNames.length; ++j){\n\t\t\t\t\t\tlet currEntityPath = rootNodeNames[j];\n\t\t\t\t\t\tif (this._isPathInMask(currEntityPath, path.entityPath.length === 1)) return true;\n\t\t\t\t\t\tfor(let i = 1; i < path.entityPath.length; i++){\n\t\t\t\t\t\t\t\tcurrEntityPath += `/${path.entityPath[i]}`;\n\t\t\t\t\t\t\t\tif (this._isPathInMask(currEntityPath, i === path.entityPath.length - 1)) return true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tfindNode(path) {\n\t\t\t\tif (!this._isPathActive(path)) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tlet node;\n\t\t\t\tif (this.graph) {\n\t\t\t\t\t\tnode = this.graph.findByPath(path.entityPath);\n\t\t\t\t\t\tif (!node) {\n\t\t\t\t\t\t\t\tnode = this.graph.findByPath(path.entityPath.slice(1));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!node) {\n\t\t\t\t\t\tnode = this.nodes[path.entityPath[path.entityPath.length - 1] || ''];\n\t\t\t\t}\n\t\t\t\treturn node;\n\t\t}\n\t\tstatic createAnimTarget(func, type, valueCount, node, propertyPath, componentType) {\n\t\t\t\tconst targetPath = AnimBinder.encode(node.path, componentType ? componentType : 'entity', propertyPath);\n\t\t\t\treturn new AnimTarget(func, type, valueCount, targetPath);\n\t\t}\n\t\tresolve(path) {\n\t\t\t\tconst encodedPath = AnimBinder.encode(path.entityPath, path.component, path.propertyPath);\n\t\t\t\tlet target = this.targetCache[encodedPath];\n\t\t\t\tif (target) return target;\n\t\t\t\tconst node = this.findNode(path);\n\t\t\t\tif (!node) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst handler = this.handlers[path.propertyPath];\n\t\t\t\tif (!handler) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\ttarget = handler(node);\n\t\t\t\tif (!target) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tthis.targetCache[encodedPath] = target;\n\t\t\t\tif (!this.nodeCounts[node.path]) {\n\t\t\t\t\t\tthis.activeNodes.push(node);\n\t\t\t\t\t\tthis.nodeCounts[node.path] = 1;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.nodeCounts[node.path]++;\n\t\t\t\t}\n\t\t\t\treturn target;\n\t\t}\n\t\tunresolve(path) {\n\t\t\t\tif (path.component !== 'graph') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst node = this.nodes[path.entityPath[path.entityPath.length - 1] || ''];\n\t\t\t\tthis.nodeCounts[node.path]--;\n\t\t\t\tif (this.nodeCounts[node.path] === 0) {\n\t\t\t\t\t\tconst activeNodes = this.activeNodes;\n\t\t\t\t\t\tconst i = activeNodes.indexOf(node.node);\n\t\t\t\t\t\tconst len = activeNodes.length;\n\t\t\t\t\t\tif (i < len - 1) {\n\t\t\t\t\t\t\t\tactiveNodes[i] = activeNodes[len - 1];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tactiveNodes.pop();\n\t\t\t\t}\n\t\t}\n\t\tupdate(deltaTime) {\n\t\t\t\tconst activeNodes = this.activeNodes;\n\t\t\t\tfor(let i = 0; i < activeNodes.length; ++i){\n\t\t\t\t\t\tactiveNodes[i]._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\tassignMask(mask) {\n\t\t\t\tif (mask !== this._mask) {\n\t\t\t\t\t\tthis._mask = mask;\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n}\n\nclass AnimationComponent extends Component {\n\t\tset animations(value) {\n\t\t\t\tthis._animations = value;\n\t\t\t\tthis.onSetAnimations();\n\t\t}\n\t\tget animations() {\n\t\t\t\treturn this._animations;\n\t\t}\n\t\tset assets(value) {\n\t\t\t\tconst assets = this._assets;\n\t\t\t\tif (assets && assets.length) {\n\t\t\t\t\t\tfor(let i = 0; i < assets.length; i++){\n\t\t\t\t\t\t\t\tif (assets[i]) {\n\t\t\t\t\t\t\t\t\t\tconst asset = this.system.app.assets.get(assets[i]);\n\t\t\t\t\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\t\t\t\t\tasset.off('change', this.onAssetChanged, this);\n\t\t\t\t\t\t\t\t\t\t\t\tasset.off('remove', this.onAssetRemoved, this);\n\t\t\t\t\t\t\t\t\t\t\t\tconst animName = this.animationsIndex[asset.id];\n\t\t\t\t\t\t\t\t\t\t\t\tif (this.currAnim === animName) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._stopCurrentAnimation();\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tdelete this.animations[animName];\n\t\t\t\t\t\t\t\t\t\t\t\tdelete this.animationsIndex[asset.id];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._assets = value;\n\t\t\t\tconst assetIds = value.map((value)=>{\n\t\t\t\t\t\treturn value instanceof Asset ? value.id : value;\n\t\t\t\t});\n\t\t\t\tthis.loadAnimationAssets(assetIds);\n\t\t}\n\t\tget assets() {\n\t\t\t\treturn this._assets;\n\t\t}\n\t\tset currentTime(currentTime) {\n\t\t\t\tif (this.skeleton) {\n\t\t\t\t\t\tthis.skeleton.currentTime = currentTime;\n\t\t\t\t\t\tthis.skeleton.addTime(0);\n\t\t\t\t\t\tthis.skeleton.updateGraph();\n\t\t\t\t}\n\t\t\t\tif (this.animEvaluator) {\n\t\t\t\t\t\tconst clips = this.animEvaluator.clips;\n\t\t\t\t\t\tfor(let i = 0; i < clips.length; ++i){\n\t\t\t\t\t\t\t\tclips[i].time = currentTime;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget currentTime() {\n\t\t\t\tif (this.skeleton) {\n\t\t\t\t\t\treturn this.skeleton._time;\n\t\t\t\t}\n\t\t\t\tif (this.animEvaluator) {\n\t\t\t\t\t\tconst clips = this.animEvaluator.clips;\n\t\t\t\t\t\tif (clips.length > 0) {\n\t\t\t\t\t\t\t\treturn clips[clips.length - 1].time;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t}\n\t\tget duration() {\n\t\t\t\tif (this.currAnim) {\n\t\t\t\t\t\treturn this.animations[this.currAnim].duration;\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t}\n\t\tset loop(value) {\n\t\t\t\tthis._loop = value;\n\t\t\t\tif (this.skeleton) {\n\t\t\t\t\t\tthis.skeleton.looping = value;\n\t\t\t\t}\n\t\t\t\tif (this.animEvaluator) {\n\t\t\t\t\t\tfor(let i = 0; i < this.animEvaluator.clips.length; ++i){\n\t\t\t\t\t\t\t\tthis.animEvaluator.clips[i].loop = value;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget loop() {\n\t\t\t\treturn this._loop;\n\t\t}\n\t\tplay(name, blendTime = 0) {\n\t\t\t\tif (!this.enabled || !this.entity.enabled) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.animations[name]) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.prevAnim = this.currAnim;\n\t\t\t\tthis.currAnim = name;\n\t\t\t\tif (this.model) {\n\t\t\t\t\t\tif (!this.skeleton && !this.animEvaluator) {\n\t\t\t\t\t\t\t\tthis._createAnimationController();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst prevAnim = this.animations[this.prevAnim];\n\t\t\t\t\t\tconst currAnim = this.animations[this.currAnim];\n\t\t\t\t\t\tthis.blending = blendTime > 0 && !!this.prevAnim;\n\t\t\t\t\t\tif (this.blending) {\n\t\t\t\t\t\t\t\tthis.blend = 0;\n\t\t\t\t\t\t\t\tthis.blendSpeed = 1 / blendTime;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.skeleton) {\n\t\t\t\t\t\t\t\tif (this.blending) {\n\t\t\t\t\t\t\t\t\t\tthis.fromSkel.animation = prevAnim;\n\t\t\t\t\t\t\t\t\t\tthis.fromSkel.addTime(this.skeleton._time);\n\t\t\t\t\t\t\t\t\t\tthis.toSkel.animation = currAnim;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.skeleton.animation = currAnim;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.animEvaluator) {\n\t\t\t\t\t\t\t\tconst animEvaluator = this.animEvaluator;\n\t\t\t\t\t\t\t\tif (this.blending) {\n\t\t\t\t\t\t\t\t\t\twhile(animEvaluator.clips.length > 1){\n\t\t\t\t\t\t\t\t\t\t\t\tanimEvaluator.removeClip(0);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.animEvaluator.removeClips();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst clip = new AnimClip(this.animations[this.currAnim], 0, 1.0, true, this.loop);\n\t\t\t\t\t\t\t\tclip.name = this.currAnim;\n\t\t\t\t\t\t\t\tclip.blendWeight = this.blending ? 0 : 1;\n\t\t\t\t\t\t\t\tclip.reset();\n\t\t\t\t\t\t\t\tthis.animEvaluator.addClip(clip);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.playing = true;\n\t\t}\n\t\tgetAnimation(name) {\n\t\t\t\treturn this.animations[name];\n\t\t}\n\t\tsetModel(model) {\n\t\t\t\tif (model !== this.model) {\n\t\t\t\t\t\tthis._resetAnimationController();\n\t\t\t\t\t\tthis.model = model;\n\t\t\t\t\t\tif (this.animations && this.currAnim && this.animations[this.currAnim]) {\n\t\t\t\t\t\t\t\tthis.play(this.currAnim);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonSetAnimations() {\n\t\t\t\tconst modelComponent = this.entity.model;\n\t\t\t\tif (modelComponent) {\n\t\t\t\t\t\tconst m = modelComponent.model;\n\t\t\t\t\t\tif (m && m !== this.model) {\n\t\t\t\t\t\t\t\tthis.setModel(m);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!this.currAnim && this.activate && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tconst animationNames = Object.keys(this._animations);\n\t\t\t\t\t\tif (animationNames.length > 0) {\n\t\t\t\t\t\t\t\tthis.play(animationNames[0]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_resetAnimationController() {\n\t\t\t\tthis.skeleton = null;\n\t\t\t\tthis.fromSkel = null;\n\t\t\t\tthis.toSkel = null;\n\t\t\t\tthis.animEvaluator = null;\n\t\t}\n\t\t_createAnimationController() {\n\t\t\t\tconst model = this.model;\n\t\t\t\tconst animations = this.animations;\n\t\t\t\tlet hasJson = false;\n\t\t\t\tlet hasGlb = false;\n\t\t\t\tfor(const animation in animations){\n\t\t\t\t\t\tif (animations.hasOwnProperty(animation)) {\n\t\t\t\t\t\t\t\tconst anim = animations[animation];\n\t\t\t\t\t\t\t\tif (anim.constructor === AnimTrack) {\n\t\t\t\t\t\t\t\t\t\thasGlb = true;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\thasJson = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst graph = model.getGraph();\n\t\t\t\tif (hasJson) {\n\t\t\t\t\t\tthis.fromSkel = new Skeleton(graph);\n\t\t\t\t\t\tthis.toSkel = new Skeleton(graph);\n\t\t\t\t\t\tthis.skeleton = new Skeleton(graph);\n\t\t\t\t\t\tthis.skeleton.looping = this.loop;\n\t\t\t\t\t\tthis.skeleton.setGraph(graph);\n\t\t\t\t} else if (hasGlb) {\n\t\t\t\t\t\tthis.animEvaluator = new AnimEvaluator(new DefaultAnimBinder(this.entity));\n\t\t\t\t}\n\t\t}\n\t\tloadAnimationAssets(ids) {\n\t\t\t\tif (!ids || !ids.length) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tconst onAssetReady = (asset)=>{\n\t\t\t\t\t\tif (asset.resources.length > 1) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < asset.resources.length; i++){\n\t\t\t\t\t\t\t\t\t\tthis.animations[asset.resources[i].name] = asset.resources[i];\n\t\t\t\t\t\t\t\t\t\tthis.animationsIndex[asset.id] = asset.resources[i].name;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.animations[asset.name] = asset.resource;\n\t\t\t\t\t\t\t\tthis.animationsIndex[asset.id] = asset.name;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.animations = this.animations;\n\t\t\t\t};\n\t\t\t\tconst onAssetAdd = (asset)=>{\n\t\t\t\t\t\tasset.off('change', this.onAssetChanged, this);\n\t\t\t\t\t\tasset.on('change', this.onAssetChanged, this);\n\t\t\t\t\t\tasset.off('remove', this.onAssetRemoved, this);\n\t\t\t\t\t\tasset.on('remove', this.onAssetRemoved, this);\n\t\t\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\t\t\tonAssetReady(asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tasset.once('load', onAssetReady, this);\n\t\t\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tassets.load(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tfor(let i = 0, l = ids.length; i < l; i++){\n\t\t\t\t\t\tconst asset = assets.get(ids[i]);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tonAssetAdd(asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassets.on(`add:${ids[i]}`, onAssetAdd);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonAssetChanged(asset, attribute, newValue, oldValue) {\n\t\t\t\tif (attribute === 'resource' || attribute === 'resources') {\n\t\t\t\t\t\tif (attribute === 'resources' && newValue && newValue.length === 0) {\n\t\t\t\t\t\t\t\tnewValue = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (newValue) {\n\t\t\t\t\t\t\t\tlet restarted = false;\n\t\t\t\t\t\t\t\tif (newValue.length > 1) {\n\t\t\t\t\t\t\t\t\t\tif (oldValue && oldValue.length > 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < oldValue.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdelete this.animations[oldValue[i].name];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tdelete this.animations[asset.name];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\trestarted = false;\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < newValue.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tthis.animations[newValue[i].name] = newValue[i];\n\t\t\t\t\t\t\t\t\t\t\t\tif (!restarted && this.currAnim === newValue[i].name) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this.playing && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trestarted = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.play(newValue[i].name);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!restarted) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._stopCurrentAnimation();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.onSetAnimations();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (oldValue && oldValue.length > 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < oldValue.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdelete this.animations[oldValue[i].name];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.animations[asset.name] = newValue[0] || newValue;\n\t\t\t\t\t\t\t\t\t\trestarted = false;\n\t\t\t\t\t\t\t\t\t\tif (this.currAnim === asset.name) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (this.playing && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trestarted = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.play(asset.name);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!restarted) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._stopCurrentAnimation();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.onSetAnimations();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.animationsIndex[asset.id] = asset.name;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (oldValue.length > 1) {\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < oldValue.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tdelete this.animations[oldValue[i].name];\n\t\t\t\t\t\t\t\t\t\t\t\tif (this.currAnim === oldValue[i].name) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._stopCurrentAnimation();\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdelete this.animations[asset.name];\n\t\t\t\t\t\t\t\t\t\tif (this.currAnim === asset.name) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._stopCurrentAnimation();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdelete this.animationsIndex[asset.id];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonAssetRemoved(asset) {\n\t\t\t\tasset.off('remove', this.onAssetRemoved, this);\n\t\t\t\tif (this.animations) {\n\t\t\t\t\t\tif (asset.resources.length > 1) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < asset.resources.length; i++){\n\t\t\t\t\t\t\t\t\t\tdelete this.animations[asset.resources[i].name];\n\t\t\t\t\t\t\t\t\t\tif (this.currAnim === asset.resources[i].name) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._stopCurrentAnimation();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdelete this.animations[asset.name];\n\t\t\t\t\t\t\t\tif (this.currAnim === asset.name) {\n\t\t\t\t\t\t\t\t\t\tthis._stopCurrentAnimation();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdelete this.animationsIndex[asset.id];\n\t\t\t\t}\n\t\t}\n\t\t_stopCurrentAnimation() {\n\t\t\t\tthis.currAnim = null;\n\t\t\t\tthis.playing = false;\n\t\t\t\tif (this.skeleton) {\n\t\t\t\t\t\tthis.skeleton.currentTime = 0;\n\t\t\t\t\t\tthis.skeleton.animation = null;\n\t\t\t\t}\n\t\t\t\tif (this.animEvaluator) {\n\t\t\t\t\t\tfor(let i = 0; i < this.animEvaluator.clips.length; ++i){\n\t\t\t\t\t\t\t\tthis.animEvaluator.clips[i].stop();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.animEvaluator.update(0);\n\t\t\t\t\t\tthis.animEvaluator.removeClips();\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tsuper.onEnable();\n\t\t\t\tconst assets = this.assets;\n\t\t\t\tconst registry = this.system.app.assets;\n\t\t\t\tif (assets) {\n\t\t\t\t\t\tfor(let i = 0, len = assets.length; i < len; i++){\n\t\t\t\t\t\t\t\tlet asset = assets[i];\n\t\t\t\t\t\t\t\tif (!(asset instanceof Asset)) {\n\t\t\t\t\t\t\t\t\t\tasset = registry.get(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (asset && !asset.resource) {\n\t\t\t\t\t\t\t\t\t\tregistry.load(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.activate && !this.currAnim) {\n\t\t\t\t\t\tconst animationNames = Object.keys(this.animations);\n\t\t\t\t\t\tif (animationNames.length > 0) {\n\t\t\t\t\t\t\t\tthis.play(animationNames[0]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonBeforeRemove() {\n\t\t\t\tfor(let i = 0; i < this.assets.length; i++){\n\t\t\t\t\t\tlet asset = this.assets[i];\n\t\t\t\t\t\tif (typeof asset === 'number') {\n\t\t\t\t\t\t\t\tasset = this.system.app.assets.get(asset);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!asset) continue;\n\t\t\t\t\t\tasset.off('change', this.onAssetChanged, this);\n\t\t\t\t\t\tasset.off('remove', this.onAssetRemoved, this);\n\t\t\t\t}\n\t\t\t\tthis.skeleton = null;\n\t\t\t\tthis.fromSkel = null;\n\t\t\t\tthis.toSkel = null;\n\t\t\t\tthis.animEvaluator = null;\n\t\t}\n\t\tupdate(dt) {\n\t\t\t\tif (this.blending) {\n\t\t\t\t\t\tthis.blend += dt * this.blendSpeed;\n\t\t\t\t\t\tif (this.blend >= 1) {\n\t\t\t\t\t\t\t\tthis.blend = 1;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.playing) {\n\t\t\t\t\t\tconst skeleton = this.skeleton;\n\t\t\t\t\t\tif (skeleton !== null && this.model !== null) {\n\t\t\t\t\t\t\t\tif (this.blending) {\n\t\t\t\t\t\t\t\t\t\tskeleton.blend(this.fromSkel, this.toSkel, this.blend);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst delta = dt * this.speed;\n\t\t\t\t\t\t\t\t\t\tskeleton.addTime(delta);\n\t\t\t\t\t\t\t\t\t\tif (this.speed > 0 && skeleton._time === skeleton.animation.duration && !this.loop) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.playing = false;\n\t\t\t\t\t\t\t\t\t\t} else if (this.speed < 0 && skeleton._time === 0 && !this.loop) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.playing = false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this.blending && this.blend === 1) {\n\t\t\t\t\t\t\t\t\t\tskeleton.animation = this.toSkel.animation;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tskeleton.updateGraph();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst animEvaluator = this.animEvaluator;\n\t\t\t\tif (animEvaluator) {\n\t\t\t\t\t\tfor(let i = 0; i < animEvaluator.clips.length; ++i){\n\t\t\t\t\t\t\t\tconst clip = animEvaluator.clips[i];\n\t\t\t\t\t\t\t\tclip.speed = this.speed;\n\t\t\t\t\t\t\t\tif (!this.playing) {\n\t\t\t\t\t\t\t\t\t\tclip.pause();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tclip.resume();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.blending && animEvaluator.clips.length > 1) {\n\t\t\t\t\t\t\t\tanimEvaluator.clips[1].blendWeight = this.blend;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tanimEvaluator.update(dt);\n\t\t\t\t}\n\t\t\t\tif (this.blending && this.blend === 1) {\n\t\t\t\t\t\tthis.blending = false;\n\t\t\t\t}\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._animations = {}, this._assets = [], this._loop = true, this.animEvaluator = null, this.model = null, this.skeleton = null, this.fromSkel = null, this.toSkel = null, this.animationsIndex = {}, this.prevAnim = null, this.currAnim = null, this.blend = 0, this.blending = false, this.blendSpeed = 0, this.activate = true, this.speed = 1;\n\t\t}\n}\n\nclass AnimationComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$k = [\n\t\t'enabled'\n];\nclass AnimationComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'animation';\n\t\t\t\tthis.ComponentType = AnimationComponent;\n\t\t\t\tthis.DataType = AnimationComponentData;\n\t\t\t\tthis.schema = _schema$k;\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tproperties = [\n\t\t\t\t\t\t'activate',\n\t\t\t\t\t\t'enabled',\n\t\t\t\t\t\t'loop',\n\t\t\t\t\t\t'speed',\n\t\t\t\t\t\t'assets'\n\t\t\t\t];\n\t\t\t\tfor (const property of properties){\n\t\t\t\t\t\tif (data.hasOwnProperty(property)) {\n\t\t\t\t\t\t\t\tcomponent[property] = data[property];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, data, _schema$k);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tthis.addComponent(clone, {});\n\t\t\t\tclone.animation.assets = entity.animation.assets.slice();\n\t\t\t\tclone.animation.speed = entity.animation.speed;\n\t\t\t\tclone.animation.loop = entity.animation.loop;\n\t\t\t\tclone.animation.activate = entity.animation.activate;\n\t\t\t\tclone.animation.enabled = entity.animation.enabled;\n\t\t\t\tconst clonedAnimations = {};\n\t\t\t\tconst animations = entity.animation.animations;\n\t\t\t\tfor(const key in animations){\n\t\t\t\t\t\tif (animations.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tclonedAnimations[key] = animations[key];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tclone.animation.animations = clonedAnimations;\n\t\t\t\tconst clonedAnimationsIndex = {};\n\t\t\t\tconst animationsIndex = entity.animation.animationsIndex;\n\t\t\t\tfor(const key in animationsIndex){\n\t\t\t\t\t\tif (animationsIndex.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tclonedAnimationsIndex[key] = animationsIndex[key];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tclone.animation.animationsIndex = clonedAnimationsIndex;\n\t\t\t\treturn clone.animation;\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tcomponent.onBeforeRemove();\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tconst components = this.store;\n\t\t\t\tfor(const id in components){\n\t\t\t\t\t\tif (components.hasOwnProperty(id)) {\n\t\t\t\t\t\t\t\tconst component = components[id];\n\t\t\t\t\t\t\t\tif (component.data.enabled && component.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tcomponent.entity.animation.update(dt);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t}\n}\nComponent._buildAccessors(AnimationComponent.prototype, _schema$k);\n\nclass AnimNode {\n\t\tconstructor(state, parent, name, point, speed = 1){\n\t\t\t\tthis._state = state;\n\t\t\t\tthis._parent = parent;\n\t\t\t\tthis._name = name;\n\t\t\t\tif (Array.isArray(point)) {\n\t\t\t\t\t\tthis._point = new Vec2(point[0], point[1]);\n\t\t\t\t\t\tthis._pointLength = this._point.length();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._point = point;\n\t\t\t\t\t\tthis._pointLength = point;\n\t\t\t\t}\n\t\t\t\tthis._speed = speed;\n\t\t\t\tthis._weightedSpeed = 1.0;\n\t\t\t\tthis._weight = 1.0;\n\t\t\t\tthis._animTrack = null;\n\t\t}\n\t\tget parent() {\n\t\t\t\treturn this._parent;\n\t\t}\n\t\tget name() {\n\t\t\t\treturn this._name;\n\t\t}\n\t\tget path() {\n\t\t\t\treturn this._parent ? `${this._parent.path}.${this._name}` : this._name;\n\t\t}\n\t\tget point() {\n\t\t\t\treturn this._point;\n\t\t}\n\t\tget pointLength() {\n\t\t\t\treturn this._pointLength;\n\t\t}\n\t\tset weight(value) {\n\t\t\t\tthis._weight = value;\n\t\t}\n\t\tget weight() {\n\t\t\t\treturn this._parent ? this._parent.weight * this._weight : this._weight;\n\t\t}\n\t\tget normalizedWeight() {\n\t\t\t\tconst totalWeight = this._state.totalWeight;\n\t\t\t\tif (totalWeight === 0.0) return 0.0;\n\t\t\t\treturn this.weight / totalWeight;\n\t\t}\n\t\tget speed() {\n\t\t\t\treturn this._weightedSpeed * this._speed;\n\t\t}\n\t\tget absoluteSpeed() {\n\t\t\t\treturn Math.abs(this._speed);\n\t\t}\n\t\tset weightedSpeed(weightedSpeed) {\n\t\t\t\tthis._weightedSpeed = weightedSpeed;\n\t\t}\n\t\tget weightedSpeed() {\n\t\t\t\treturn this._weightedSpeed;\n\t\t}\n\t\tset animTrack(value) {\n\t\t\t\tthis._animTrack = value;\n\t\t}\n\t\tget animTrack() {\n\t\t\t\treturn this._animTrack;\n\t\t}\n}\n\nclass AnimBlendTree extends AnimNode {\n\t\tconstructor(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter){\n\t\t\t\tsuper(state, parent, name, point);\n\t\t\t\tthis._parameters = parameters;\n\t\t\t\tthis._parameterValues = new Array(parameters.length);\n\t\t\t\tthis._children = [];\n\t\t\t\tthis._findParameter = findParameter;\n\t\t\t\tthis._syncAnimations = syncAnimations !== false;\n\t\t\t\tthis._pointCache = {};\n\t\t\t\tfor(let i = 0; i < children.length; i++){\n\t\t\t\t\t\tconst child = children[i];\n\t\t\t\t\t\tif (child.children) {\n\t\t\t\t\t\t\t\tthis._children.push(createTree(child.type, state, this, child.name, 1.0, child.parameter ? [\n\t\t\t\t\t\t\t\t\t\tchild.parameter\n\t\t\t\t\t\t\t\t] : child.parameters, child.children, child.syncAnimations, createTree, findParameter));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._children.push(new AnimNode(state, this, child.name, child.point, child.speed));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget weight() {\n\t\t\t\tthis.calculateWeights();\n\t\t\t\treturn this._parent ? this._parent.weight * this._weight : this._weight;\n\t\t}\n\t\tget syncAnimations() {\n\t\t\t\treturn this._syncAnimations;\n\t\t}\n\t\tgetChild(name) {\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tif (this._children[i].name === name) return this._children[i];\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tupdateParameterValues() {\n\t\t\t\tlet paramsEqual = true;\n\t\t\t\tfor(let i = 0; i < this._parameterValues.length; i++){\n\t\t\t\t\t\tconst updatedParameter = this._findParameter(this._parameters[i]).value;\n\t\t\t\t\t\tif (this._parameterValues[i] !== updatedParameter) {\n\t\t\t\t\t\t\t\tthis._parameterValues[i] = updatedParameter;\n\t\t\t\t\t\t\t\tparamsEqual = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn paramsEqual;\n\t\t}\n\t\tgetNodeWeightedDuration(i) {\n\t\t\t\treturn this._children[i].animTrack.duration / this._children[i].speedMultiplier * this._children[i].weight;\n\t\t}\n\t\tgetNodeCount() {\n\t\t\t\tlet count = 0;\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\tif (child.constructor === AnimBlendTree) {\n\t\t\t\t\t\t\t\tcount += this._children[i].getNodeCount();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn count;\n\t\t}\n}\n\nclass AnimBlendTree1D extends AnimBlendTree {\n\t\tconstructor(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter){\n\t\t\t\tchildren.sort((a, b)=>a.point - b.point);\n\t\t\t\tsuper(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);\n\t\t}\n\t\tcalculateWeights() {\n\t\t\t\tif (this.updateParameterValues()) return;\n\t\t\t\tlet weightedDurationSum = 0.0;\n\t\t\t\tthis._children[0].weight = 0.0;\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst c1 = this._children[i];\n\t\t\t\t\t\tif (i !== this._children.length - 1) {\n\t\t\t\t\t\t\t\tconst c2 = this._children[i + 1];\n\t\t\t\t\t\t\t\tif (c1.point === c2.point) {\n\t\t\t\t\t\t\t\t\t\tc1.weight = 0.5;\n\t\t\t\t\t\t\t\t\t\tc2.weight = 0.5;\n\t\t\t\t\t\t\t\t} else if (math.between(this._parameterValues[0], c1.point, c2.point, true)) {\n\t\t\t\t\t\t\t\t\t\tconst child2Distance = Math.abs(c1.point - c2.point);\n\t\t\t\t\t\t\t\t\t\tconst parameterDistance = Math.abs(c1.point - this._parameterValues[0]);\n\t\t\t\t\t\t\t\t\t\tconst weight = (child2Distance - parameterDistance) / child2Distance;\n\t\t\t\t\t\t\t\t\t\tc1.weight = weight;\n\t\t\t\t\t\t\t\t\t\tc2.weight = 1.0 - weight;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tc2.weight = 0.0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\tweightedDurationSum += c1.animTrack.duration / c1.absoluteSpeed * c1.weight;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\t\t\tchild.weightedSpeed = child.animTrack.duration / child.absoluteSpeed / weightedDurationSum;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimBlendTreeCartesian2D extends AnimBlendTree {\n\t\tstatic{\n\t\t\t\tthis._p = new Vec2();\n\t\t}\n\t\tstatic{\n\t\t\t\tthis._pip = new Vec2();\n\t\t}\n\t\tpointDistanceCache(i, j) {\n\t\t\t\tconst pointKey = `${i}${j}`;\n\t\t\t\tif (!this._pointCache[pointKey]) {\n\t\t\t\t\t\tthis._pointCache[pointKey] = this._children[j].point.clone().sub(this._children[i].point);\n\t\t\t\t}\n\t\t\t\treturn this._pointCache[pointKey];\n\t\t}\n\t\tcalculateWeights() {\n\t\t\t\tif (this.updateParameterValues()) return;\n\t\t\t\tlet weightSum, weightedDurationSum;\n\t\t\t\tAnimBlendTreeCartesian2D._p.set(...this._parameterValues);\n\t\t\t\tweightSum = 0.0;\n\t\t\t\tweightedDurationSum = 0.0;\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\tconst pi = child.point;\n\t\t\t\t\t\tAnimBlendTreeCartesian2D._pip.set(AnimBlendTreeCartesian2D._p.x, AnimBlendTreeCartesian2D._p.y).sub(pi);\n\t\t\t\t\t\tlet minj = Number.MAX_VALUE;\n\t\t\t\t\t\tfor(let j = 0; j < this._children.length; j++){\n\t\t\t\t\t\t\t\tif (i === j) continue;\n\t\t\t\t\t\t\t\tconst pipj = this.pointDistanceCache(i, j);\n\t\t\t\t\t\t\t\tconst result = math.clamp(1.0 - AnimBlendTreeCartesian2D._pip.dot(pipj) / pipj.lengthSq(), 0.0, 1.0);\n\t\t\t\t\t\t\t\tif (result < minj) minj = result;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchild.weight = minj;\n\t\t\t\t\t\tweightSum += minj;\n\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\tweightedDurationSum += child.animTrack.duration / child.absoluteSpeed * child.weight;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\tchild.weight = child._weight / weightSum;\n\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\tchild.weightedSpeed = child.animTrack.duration / child.absoluteSpeed / weightedDurationSum;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimBlendTreeDirectional2D extends AnimBlendTree {\n\t\tstatic{\n\t\t\t\tthis._p = new Vec2();\n\t\t}\n\t\tstatic{\n\t\t\t\tthis._pip = new Vec2();\n\t\t}\n\t\tpointCache(i, j) {\n\t\t\t\tconst pointKey = `${i}${j}`;\n\t\t\t\tif (!this._pointCache[pointKey]) {\n\t\t\t\t\t\tthis._pointCache[pointKey] = new Vec2((this._children[j].pointLength - this._children[i].pointLength) / ((this._children[j].pointLength + this._children[i].pointLength) / 2), Vec2.angleRad(this._children[i].point, this._children[j].point) * 2.0);\n\t\t\t\t}\n\t\t\t\treturn this._pointCache[pointKey];\n\t\t}\n\t\tcalculateWeights() {\n\t\t\t\tif (this.updateParameterValues()) return;\n\t\t\t\tlet weightSum, weightedDurationSum;\n\t\t\t\tAnimBlendTreeDirectional2D._p.set(...this._parameterValues);\n\t\t\t\tconst pLength = AnimBlendTreeDirectional2D._p.length();\n\t\t\t\tweightSum = 0.0;\n\t\t\t\tweightedDurationSum = 0.0;\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\tconst pi = child.point;\n\t\t\t\t\t\tconst piLength = child.pointLength;\n\t\t\t\t\t\tlet minj = Number.MAX_VALUE;\n\t\t\t\t\t\tfor(let j = 0; j < this._children.length; j++){\n\t\t\t\t\t\t\t\tif (i === j) continue;\n\t\t\t\t\t\t\t\tconst pipj = this.pointCache(i, j);\n\t\t\t\t\t\t\t\tconst pjLength = this._children[j].pointLength;\n\t\t\t\t\t\t\t\tAnimBlendTreeDirectional2D._pip.set((pLength - piLength) / ((pjLength + piLength) / 2), Vec2.angleRad(pi, AnimBlendTreeDirectional2D._p) * 2.0);\n\t\t\t\t\t\t\t\tconst result = math.clamp(1.0 - Math.abs(AnimBlendTreeDirectional2D._pip.dot(pipj) / pipj.lengthSq()), 0.0, 1.0);\n\t\t\t\t\t\t\t\tif (result < minj) minj = result;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchild.weight = minj;\n\t\t\t\t\t\tweightSum += minj;\n\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\tweightedDurationSum += child.animTrack.duration / child.absoluteSpeed * child.weight;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\tchild.weight = child._weight / weightSum;\n\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\tconst weightedChildDuration = child.animTrack.duration / weightedDurationSum * weightSum;\n\t\t\t\t\t\t\t\tchild.weightedSpeed = child.absoluteSpeed * weightedChildDuration;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimBlendTreeDirect extends AnimBlendTree {\n\t\tcalculateWeights() {\n\t\t\t\tif (this.updateParameterValues()) return;\n\t\t\t\tlet weightSum = 0.0;\n\t\t\t\tlet weightedDurationSum = 0.0;\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tweightSum += Math.max(this._parameterValues[i], 0.0);\n\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\t\t\tweightedDurationSum += child.animTrack.duration / child.absoluteSpeed * child.weight;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._children.length; i++){\n\t\t\t\t\t\tconst child = this._children[i];\n\t\t\t\t\t\tconst weight = Math.max(this._parameterValues[i], 0.0);\n\t\t\t\t\t\tif (weightSum) {\n\t\t\t\t\t\t\t\tchild.weight = weight / weightSum;\n\t\t\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\t\t\tchild.weightedSpeed = child.animTrack.duration / child.absoluteSpeed / weightedDurationSum;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tchild.weight = 0.0;\n\t\t\t\t\t\t\t\tif (this._syncAnimations) {\n\t\t\t\t\t\t\t\t\t\tchild.weightedSpeed = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass AnimState {\n\t\tconstructor(controller, name, speed = 1, loop = true, blendTree){\n\t\t\t\tthis._animations = {};\n\t\t\t\tthis._animationList = [];\n\t\t\t\tthis._controller = controller;\n\t\t\t\tthis._name = name;\n\t\t\t\tthis._speed = speed;\n\t\t\t\tthis._loop = loop;\n\t\t\t\tthis._hasAnimations = false;\n\t\t\t\tif (blendTree) {\n\t\t\t\t\t\tthis._blendTree = this._createTree(blendTree.type, this, null, name, 1.0, blendTree.parameter ? [\n\t\t\t\t\t\t\t\tblendTree.parameter\n\t\t\t\t\t\t] : blendTree.parameters, blendTree.children, blendTree.syncAnimations, this._createTree, this._controller.findParameter);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._blendTree = new AnimNode(this, null, name, 1.0, speed);\n\t\t\t\t}\n\t\t}\n\t\t_createTree(type, state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter) {\n\t\t\t\tswitch(type){\n\t\t\t\t\t\tcase ANIM_BLEND_1D:\n\t\t\t\t\t\t\t\treturn new AnimBlendTree1D(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);\n\t\t\t\t\t\tcase ANIM_BLEND_2D_CARTESIAN:\n\t\t\t\t\t\t\t\treturn new AnimBlendTreeCartesian2D(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);\n\t\t\t\t\t\tcase ANIM_BLEND_2D_DIRECTIONAL:\n\t\t\t\t\t\t\t\treturn new AnimBlendTreeDirectional2D(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);\n\t\t\t\t\t\tcase ANIM_BLEND_DIRECT:\n\t\t\t\t\t\t\t\treturn new AnimBlendTreeDirect(state, parent, name, point, parameters, children, syncAnimations, createTree, findParameter);\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\t_getNodeFromPath(path) {\n\t\t\t\tlet currNode = this._blendTree;\n\t\t\t\tfor(let i = 1; i < path.length; i++){\n\t\t\t\t\t\tcurrNode = currNode.getChild(path[i]);\n\t\t\t\t}\n\t\t\t\treturn currNode;\n\t\t}\n\t\taddAnimation(path, animTrack) {\n\t\t\t\tconst pathString = path.join('.');\n\t\t\t\tconst indexOfAnimation = this._animationList.findIndex((animation)=>{\n\t\t\t\t\t\treturn animation.path === pathString;\n\t\t\t\t});\n\t\t\t\tif (indexOfAnimation >= 0) {\n\t\t\t\t\t\tthis._animationList[indexOfAnimation].animTrack = animTrack;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst node = this._getNodeFromPath(path);\n\t\t\t\t\t\tnode.animTrack = animTrack;\n\t\t\t\t\t\tthis._animationList.push(node);\n\t\t\t\t}\n\t\t\t\tthis._updateHasAnimations();\n\t\t}\n\t\t_updateHasAnimations() {\n\t\t\t\tthis._hasAnimations = this._animationList.length > 0 && this._animationList.every((animation)=>animation.animTrack && animation.animTrack !== AnimTrack.EMPTY);\n\t\t}\n\t\tget name() {\n\t\t\t\treturn this._name;\n\t\t}\n\t\tset animations(value) {\n\t\t\t\tthis._animationList = value;\n\t\t\t\tthis._updateHasAnimations();\n\t\t}\n\t\tget animations() {\n\t\t\t\treturn this._animationList;\n\t\t}\n\t\tget hasAnimations() {\n\t\t\t\treturn this._hasAnimations;\n\t\t}\n\t\tset speed(value) {\n\t\t\t\tthis._speed = value;\n\t\t}\n\t\tget speed() {\n\t\t\t\treturn this._speed;\n\t\t}\n\t\tset loop(value) {\n\t\t\t\tthis._loop = value;\n\t\t}\n\t\tget loop() {\n\t\t\t\treturn this._loop;\n\t\t}\n\t\tget nodeCount() {\n\t\t\t\tif (!this._blendTree || this._blendTree.constructor === AnimNode) return 1;\n\t\t\t\treturn this._blendTree.getNodeCount();\n\t\t}\n\t\tget playable() {\n\t\t\t\treturn ANIM_CONTROL_STATES.indexOf(this.name) !== -1 || this.animations.length === this.nodeCount;\n\t\t}\n\t\tget looping() {\n\t\t\t\tif (this.animations.length > 0) {\n\t\t\t\t\t\tconst trackClipName = `${this.name}.${this.animations[0].animTrack.name}`;\n\t\t\t\t\t\tconst trackClip = this._controller.animEvaluator.findClip(trackClipName);\n\t\t\t\t\t\tif (trackClip) {\n\t\t\t\t\t\t\t\treturn trackClip.loop;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tget totalWeight() {\n\t\t\t\tlet sum = 0;\n\t\t\t\tfor(let i = 0; i < this.animations.length; i++){\n\t\t\t\t\t\tsum += this.animations[i].weight;\n\t\t\t\t}\n\t\t\t\treturn sum;\n\t\t}\n\t\tget timelineDuration() {\n\t\t\t\tlet duration = 0;\n\t\t\t\tfor(let i = 0; i < this.animations.length; i++){\n\t\t\t\t\t\tconst animation = this.animations[i];\n\t\t\t\t\t\tif (animation.animTrack.duration > duration) {\n\t\t\t\t\t\t\t\tduration = animation.animTrack.duration;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn duration;\n\t\t}\n}\n\nclass AnimTransition {\n\t\tconstructor({ from, to, time = 0, priority = 0, conditions = [], exitTime = null, transitionOffset = null, interruptionSource = ANIM_INTERRUPTION_NONE }){\n\t\t\t\tthis._from = from;\n\t\t\t\tthis._to = to;\n\t\t\t\tthis._time = time;\n\t\t\t\tthis._priority = priority;\n\t\t\t\tthis._conditions = conditions;\n\t\t\t\tthis._exitTime = exitTime;\n\t\t\t\tthis._transitionOffset = transitionOffset;\n\t\t\t\tthis._interruptionSource = interruptionSource;\n\t\t}\n\t\tget from() {\n\t\t\t\treturn this._from;\n\t\t}\n\t\tset to(value) {\n\t\t\t\tthis._to = value;\n\t\t}\n\t\tget to() {\n\t\t\t\treturn this._to;\n\t\t}\n\t\tget time() {\n\t\t\t\treturn this._time;\n\t\t}\n\t\tget priority() {\n\t\t\t\treturn this._priority;\n\t\t}\n\t\tget conditions() {\n\t\t\t\treturn this._conditions;\n\t\t}\n\t\tget exitTime() {\n\t\t\t\treturn this._exitTime;\n\t\t}\n\t\tget transitionOffset() {\n\t\t\t\treturn this._transitionOffset;\n\t\t}\n\t\tget interruptionSource() {\n\t\t\t\treturn this._interruptionSource;\n\t\t}\n\t\tget hasExitTime() {\n\t\t\t\treturn !!this.exitTime;\n\t\t}\n}\n\nlet AnimController$1 = class AnimController {\n\t\tconstructor(animEvaluator, states, transitions, activate, eventHandler, findParameter, consumeTrigger){\n\t\t\t\tthis._states = {};\n\t\t\t\tthis._stateNames = [];\n\t\t\t\tthis._findTransitionsFromStateCache = {};\n\t\t\t\tthis._findTransitionsBetweenStatesCache = {};\n\t\t\t\tthis._previousStateName = null;\n\t\t\t\tthis._activeStateName = ANIM_STATE_START;\n\t\t\t\tthis._activeStateDuration = 0;\n\t\t\t\tthis._activeStateDurationDirty = true;\n\t\t\t\tthis._playing = false;\n\t\t\t\tthis._currTransitionTime = 1;\n\t\t\t\tthis._totalTransitionTime = 1;\n\t\t\t\tthis._isTransitioning = false;\n\t\t\t\tthis._transitionInterruptionSource = ANIM_INTERRUPTION_NONE;\n\t\t\t\tthis._transitionPreviousStates = [];\n\t\t\t\tthis._timeInState = 0;\n\t\t\t\tthis._timeInStateBefore = 0;\n\t\t\t\tthis.findParameter = (name)=>{\n\t\t\t\t\t\treturn this._findParameter(name);\n\t\t\t\t};\n\t\t\t\tthis._animEvaluator = animEvaluator;\n\t\t\t\tthis._eventHandler = eventHandler;\n\t\t\t\tthis._findParameter = findParameter;\n\t\t\t\tthis._consumeTrigger = consumeTrigger;\n\t\t\t\tfor(let i = 0; i < states.length; i++){\n\t\t\t\t\t\tthis._states[states[i].name] = new AnimState(this, states[i].name, states[i].speed, states[i].loop, states[i].blendTree);\n\t\t\t\t\t\tthis._stateNames.push(states[i].name);\n\t\t\t\t}\n\t\t\t\tthis._transitions = transitions.map((transition)=>{\n\t\t\t\t\t\treturn new AnimTransition({\n\t\t\t\t\t\t\t\t...transition\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tthis._activate = activate;\n\t\t}\n\t\tget animEvaluator() {\n\t\t\t\treturn this._animEvaluator;\n\t\t}\n\t\tset activeState(stateName) {\n\t\t\t\tthis._activeStateName = stateName;\n\t\t}\n\t\tget activeState() {\n\t\t\t\treturn this._findState(this._activeStateName);\n\t\t}\n\t\tget activeStateName() {\n\t\t\t\treturn this._activeStateName;\n\t\t}\n\t\tget activeStateAnimations() {\n\t\t\t\treturn this.activeState.animations;\n\t\t}\n\t\tset previousState(stateName) {\n\t\t\t\tthis._previousStateName = stateName;\n\t\t}\n\t\tget previousState() {\n\t\t\t\treturn this._findState(this._previousStateName);\n\t\t}\n\t\tget previousStateName() {\n\t\t\t\treturn this._previousStateName;\n\t\t}\n\t\tget playable() {\n\t\t\t\tlet playable = true;\n\t\t\t\tfor(let i = 0; i < this._stateNames.length; i++){\n\t\t\t\t\t\tif (!this._states[this._stateNames[i]].playable) {\n\t\t\t\t\t\t\t\tplayable = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn playable;\n\t\t}\n\t\tset playing(value) {\n\t\t\t\tthis._playing = value;\n\t\t}\n\t\tget playing() {\n\t\t\t\treturn this._playing;\n\t\t}\n\t\tget activeStateProgress() {\n\t\t\t\treturn this._getActiveStateProgressForTime(this._timeInState);\n\t\t}\n\t\tget activeStateDuration() {\n\t\t\t\tif (this._activeStateDurationDirty) {\n\t\t\t\t\t\tlet maxDuration = 0.0;\n\t\t\t\t\t\tfor(let i = 0; i < this.activeStateAnimations.length; i++){\n\t\t\t\t\t\t\t\tconst activeClip = this._animEvaluator.findClip(this.activeStateAnimations[i].name);\n\t\t\t\t\t\t\t\tif (activeClip) {\n\t\t\t\t\t\t\t\t\t\tmaxDuration = Math.max(maxDuration, activeClip.track.duration);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._activeStateDuration = maxDuration;\n\t\t\t\t\t\tthis._activeStateDurationDirty = false;\n\t\t\t\t}\n\t\t\t\treturn this._activeStateDuration;\n\t\t}\n\t\tset activeStateCurrentTime(time) {\n\t\t\t\tthis._timeInStateBefore = time;\n\t\t\t\tthis._timeInState = time;\n\t\t\t\tfor(let i = 0; i < this.activeStateAnimations.length; i++){\n\t\t\t\t\t\tconst clip = this.animEvaluator.findClip(this.activeStateAnimations[i].name);\n\t\t\t\t\t\tif (clip) {\n\t\t\t\t\t\t\t\tclip.time = time;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget activeStateCurrentTime() {\n\t\t\t\treturn this._timeInState;\n\t\t}\n\t\tget transitioning() {\n\t\t\t\treturn this._isTransitioning;\n\t\t}\n\t\tget transitionProgress() {\n\t\t\t\treturn this._currTransitionTime / this._totalTransitionTime;\n\t\t}\n\t\tget states() {\n\t\t\t\treturn this._stateNames;\n\t\t}\n\t\tassignMask(mask) {\n\t\t\t\treturn this._animEvaluator.assignMask(mask);\n\t\t}\n\t\t_findState(stateName) {\n\t\t\t\treturn this._states[stateName];\n\t\t}\n\t\t_getActiveStateProgressForTime(time) {\n\t\t\t\tif (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END || this.activeStateName === ANIM_STATE_ANY) {\n\t\t\t\t\t\treturn 1.0;\n\t\t\t\t}\n\t\t\t\tconst activeClip = this._animEvaluator.findClip(this.activeStateAnimations[0].name);\n\t\t\t\tif (activeClip) {\n\t\t\t\t\t\treturn activeClip.progressForTime(time);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\t_findTransitionsFromState(stateName) {\n\t\t\t\tlet transitions = this._findTransitionsFromStateCache[stateName];\n\t\t\t\tif (!transitions) {\n\t\t\t\t\t\ttransitions = this._transitions.filter((transition)=>{\n\t\t\t\t\t\t\t\treturn transition.from === stateName;\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsortPriority(transitions);\n\t\t\t\t\t\tthis._findTransitionsFromStateCache[stateName] = transitions;\n\t\t\t\t}\n\t\t\t\treturn transitions;\n\t\t}\n\t\t_findTransitionsBetweenStates(sourceStateName, destinationStateName) {\n\t\t\t\tlet transitions = this._findTransitionsBetweenStatesCache[`${sourceStateName}->${destinationStateName}`];\n\t\t\t\tif (!transitions) {\n\t\t\t\t\t\ttransitions = this._transitions.filter((transition)=>{\n\t\t\t\t\t\t\t\treturn transition.from === sourceStateName && transition.to === destinationStateName;\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsortPriority(transitions);\n\t\t\t\t\t\tthis._findTransitionsBetweenStatesCache[`${sourceStateName}->${destinationStateName}`] = transitions;\n\t\t\t\t}\n\t\t\t\treturn transitions;\n\t\t}\n\t\t_transitionHasConditionsMet(transition) {\n\t\t\t\tconst conditions = transition.conditions;\n\t\t\t\tfor(let i = 0; i < conditions.length; i++){\n\t\t\t\t\t\tconst condition = conditions[i];\n\t\t\t\t\t\tconst parameter = this._findParameter(condition.parameterName);\n\t\t\t\t\t\tswitch(condition.predicate){\n\t\t\t\t\t\t\t\tcase ANIM_GREATER_THAN:\n\t\t\t\t\t\t\t\t\t\tif (!(parameter.value > condition.value)) return false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase ANIM_LESS_THAN:\n\t\t\t\t\t\t\t\t\t\tif (!(parameter.value < condition.value)) return false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase ANIM_GREATER_THAN_EQUAL_TO:\n\t\t\t\t\t\t\t\t\t\tif (!(parameter.value >= condition.value)) return false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase ANIM_LESS_THAN_EQUAL_TO:\n\t\t\t\t\t\t\t\t\t\tif (!(parameter.value <= condition.value)) return false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase ANIM_EQUAL_TO:\n\t\t\t\t\t\t\t\t\t\tif (!(parameter.value === condition.value)) return false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase ANIM_NOT_EQUAL_TO:\n\t\t\t\t\t\t\t\t\t\tif (!(parameter.value !== condition.value)) return false;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\t_findTransition(from, to) {\n\t\t\t\tlet transitions = [];\n\t\t\t\tif (from && to) {\n\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsBetweenStates(from, to));\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this._isTransitioning) {\n\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));\n\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tswitch(this._transitionInterruptionSource){\n\t\t\t\t\t\t\t\t\t\tcase ANIM_INTERRUPTION_PREV:\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(this._previousStateName));\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase ANIM_INTERRUPTION_NEXT:\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase ANIM_INTERRUPTION_PREV_NEXT:\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(this._previousStateName));\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase ANIM_INTERRUPTION_NEXT_PREV:\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(this._activeStateName));\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(this._previousStateName));\n\t\t\t\t\t\t\t\t\t\t\t\ttransitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY));\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttransitions = transitions.filter((transition)=>{\n\t\t\t\t\t\tif (transition.to === this.activeStateName) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (transition.hasExitTime) {\n\t\t\t\t\t\t\t\tlet progressBefore = this._getActiveStateProgressForTime(this._timeInStateBefore);\n\t\t\t\t\t\t\t\tlet progress = this._getActiveStateProgressForTime(this._timeInState);\n\t\t\t\t\t\t\t\tif (transition.exitTime < 1.0 && this.activeState.loop) {\n\t\t\t\t\t\t\t\t\t\tprogressBefore -= Math.floor(progressBefore);\n\t\t\t\t\t\t\t\t\t\tprogress -= Math.floor(progress);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (progress === progressBefore) {\n\t\t\t\t\t\t\t\t\t\tif (progress !== transition.exitTime) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (!(transition.exitTime > progressBefore && transition.exitTime <= progress)) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn this._transitionHasConditionsMet(transition);\n\t\t\t\t});\n\t\t\t\tif (transitions.length > 0) {\n\t\t\t\t\t\tconst transition = transitions[0];\n\t\t\t\t\t\tif (transition.to === ANIM_STATE_END) {\n\t\t\t\t\t\t\t\tconst startTransition = this._findTransitionsFromState(ANIM_STATE_START)[0];\n\t\t\t\t\t\t\t\ttransition.to = startTransition.to;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn transition;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tupdateStateFromTransition(transition) {\n\t\t\t\tlet state;\n\t\t\t\tlet animation;\n\t\t\t\tlet clip;\n\t\t\t\tthis.previousState = transition.from ? this.activeStateName : null;\n\t\t\t\tthis.activeState = transition.to;\n\t\t\t\tthis._activeStateDurationDirty = true;\n\t\t\t\tfor(let i = 0; i < transition.conditions.length; i++){\n\t\t\t\t\t\tconst condition = transition.conditions[i];\n\t\t\t\t\t\tconst parameter = this._findParameter(condition.parameterName);\n\t\t\t\t\t\tif (parameter.type === ANIM_PARAMETER_TRIGGER) {\n\t\t\t\t\t\t\t\tthis._consumeTrigger(condition.parameterName);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.previousState) {\n\t\t\t\t\t\tif (!this._isTransitioning) {\n\t\t\t\t\t\t\t\tthis._transitionPreviousStates = [];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._transitionPreviousStates.push({\n\t\t\t\t\t\t\t\tname: this._previousStateName,\n\t\t\t\t\t\t\t\tweight: 1\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst interpolatedTime = Math.min(this._totalTransitionTime !== 0 ? this._currTransitionTime / this._totalTransitionTime : 1, 1.0);\n\t\t\t\t\t\tfor(let i = 0; i < this._transitionPreviousStates.length; i++){\n\t\t\t\t\t\t\t\tif (!this._isTransitioning) {\n\t\t\t\t\t\t\t\t\t\tthis._transitionPreviousStates[i].weight = 1.0;\n\t\t\t\t\t\t\t\t} else if (i !== this._transitionPreviousStates.length - 1) {\n\t\t\t\t\t\t\t\t\t\tthis._transitionPreviousStates[i].weight *= 1.0 - interpolatedTime;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._transitionPreviousStates[i].weight = interpolatedTime;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstate = this._findState(this._transitionPreviousStates[i].name);\n\t\t\t\t\t\t\t\tfor(let j = 0; j < state.animations.length; j++){\n\t\t\t\t\t\t\t\t\t\tanimation = state.animations[j];\n\t\t\t\t\t\t\t\t\t\tclip = this._animEvaluator.findClip(`${animation.name}.previous.${i}`);\n\t\t\t\t\t\t\t\t\t\tif (!clip) {\n\t\t\t\t\t\t\t\t\t\t\t\tclip = this._animEvaluator.findClip(animation.name);\n\t\t\t\t\t\t\t\t\t\t\t\tclip.name = `${animation.name}.previous.${i}`;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (i !== this._transitionPreviousStates.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tclip.pause();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._isTransitioning = true;\n\t\t\t\tthis._totalTransitionTime = transition.time;\n\t\t\t\tthis._currTransitionTime = 0;\n\t\t\t\tthis._transitionInterruptionSource = transition.interruptionSource;\n\t\t\t\tconst activeState = this.activeState;\n\t\t\t\tconst hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0;\n\t\t\t\tlet timeInState = 0;\n\t\t\t\tlet timeInStateBefore = 0;\n\t\t\t\tif (hasTransitionOffset) {\n\t\t\t\t\t\tconst offsetTime = activeState.timelineDuration * transition.transitionOffset;\n\t\t\t\t\t\ttimeInState = offsetTime;\n\t\t\t\t\t\ttimeInStateBefore = offsetTime;\n\t\t\t\t}\n\t\t\t\tthis._timeInState = timeInState;\n\t\t\t\tthis._timeInStateBefore = timeInStateBefore;\n\t\t\t\tfor(let i = 0; i < activeState.animations.length; i++){\n\t\t\t\t\t\tclip = this._animEvaluator.findClip(activeState.animations[i].name);\n\t\t\t\t\t\tif (!clip) {\n\t\t\t\t\t\t\t\tconst speed = Number.isFinite(activeState.animations[i].speed) ? activeState.animations[i].speed : activeState.speed;\n\t\t\t\t\t\t\t\tclip = new AnimClip(activeState.animations[i].animTrack, this._timeInState, speed, true, activeState.loop, this._eventHandler);\n\t\t\t\t\t\t\t\tclip.name = activeState.animations[i].name;\n\t\t\t\t\t\t\t\tthis._animEvaluator.addClip(clip);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tclip.reset();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (transition.time > 0) {\n\t\t\t\t\t\t\t\tclip.blendWeight = 0.0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tclip.blendWeight = activeState.animations[i].normalizedWeight;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tclip.play();\n\t\t\t\t\t\tif (hasTransitionOffset) {\n\t\t\t\t\t\t\t\tclip.time = activeState.timelineDuration * transition.transitionOffset;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst startTime = activeState.speed >= 0 ? 0 : this.activeStateDuration;\n\t\t\t\t\t\t\t\tclip.time = startTime;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_transitionToState(newStateName) {\n\t\t\t\tif (!this._findState(newStateName)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet transition = this._findTransition(this._activeStateName, newStateName);\n\t\t\t\tif (!transition) {\n\t\t\t\t\t\tthis._animEvaluator.removeClips();\n\t\t\t\t\t\ttransition = new AnimTransition({\n\t\t\t\t\t\t\t\tfrom: null,\n\t\t\t\t\t\t\t\tto: newStateName\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthis.updateStateFromTransition(transition);\n\t\t}\n\t\tassignAnimation(pathString, animTrack, speed, loop) {\n\t\t\t\tconst path = pathString.split('.');\n\t\t\t\tlet state = this._findState(path[0]);\n\t\t\t\tif (!state) {\n\t\t\t\t\t\tstate = new AnimState(this, path[0], speed);\n\t\t\t\t\t\tthis._states[path[0]] = state;\n\t\t\t\t\t\tthis._stateNames.push(path[0]);\n\t\t\t\t}\n\t\t\t\tstate.addAnimation(path, animTrack);\n\t\t\t\tthis._animEvaluator.updateClipTrack(state.name, animTrack);\n\t\t\t\tif (speed !== undefined) {\n\t\t\t\t\t\tstate.speed = speed;\n\t\t\t\t}\n\t\t\t\tif (loop !== undefined) {\n\t\t\t\t\t\tstate.loop = loop;\n\t\t\t\t}\n\t\t\t\tif (!this._playing && this._activate && this.playable) {\n\t\t\t\t\t\tthis.play();\n\t\t\t\t}\n\t\t\t\tthis._activeStateDurationDirty = true;\n\t\t}\n\t\tremoveNodeAnimations(nodeName) {\n\t\t\t\tif (ANIM_CONTROL_STATES.indexOf(nodeName) !== -1) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst state = this._findState(nodeName);\n\t\t\t\tif (!state) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tstate.animations = [];\n\t\t\t\treturn true;\n\t\t}\n\t\tplay(stateName) {\n\t\t\t\tif (stateName) {\n\t\t\t\t\t\tthis._transitionToState(stateName);\n\t\t\t\t}\n\t\t\t\tthis._playing = true;\n\t\t}\n\t\tpause() {\n\t\t\t\tthis._playing = false;\n\t\t}\n\t\treset() {\n\t\t\t\tthis._previousStateName = null;\n\t\t\t\tthis._activeStateName = ANIM_STATE_START;\n\t\t\t\tthis._playing = false;\n\t\t\t\tthis._currTransitionTime = 1.0;\n\t\t\t\tthis._totalTransitionTime = 1.0;\n\t\t\t\tthis._isTransitioning = false;\n\t\t\t\tthis._timeInState = 0;\n\t\t\t\tthis._timeInStateBefore = 0;\n\t\t\t\tthis._animEvaluator.removeClips();\n\t\t}\n\t\trebind() {\n\t\t\t\tthis._animEvaluator.rebind();\n\t\t}\n\t\tupdate(dt) {\n\t\t\t\tif (!this._playing) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet state;\n\t\t\t\tlet animation;\n\t\t\t\tlet clip;\n\t\t\t\tif (this.activeState.loop || this._timeInState < this.activeStateDuration) {\n\t\t\t\t\t\tthis._timeInStateBefore = this._timeInState;\n\t\t\t\t\t\tthis._timeInState += dt * this.activeState.speed;\n\t\t\t\t\t\tif (!this.activeState.loop && this._timeInState > this.activeStateDuration) {\n\t\t\t\t\t\t\t\tthis._timeInState = this.activeStateDuration;\n\t\t\t\t\t\t\t\tdt = this.activeStateDuration - this._timeInStateBefore;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst transition = this._findTransition(this._activeStateName);\n\t\t\t\tif (transition) {\n\t\t\t\t\t\tthis.updateStateFromTransition(transition);\n\t\t\t\t}\n\t\t\t\tif (this._isTransitioning) {\n\t\t\t\t\t\tthis._currTransitionTime += dt;\n\t\t\t\t\t\tif (this._currTransitionTime <= this._totalTransitionTime) {\n\t\t\t\t\t\t\t\tconst interpolatedTime = this._totalTransitionTime !== 0 ? this._currTransitionTime / this._totalTransitionTime : 1;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < this._transitionPreviousStates.length; i++){\n\t\t\t\t\t\t\t\t\t\tstate = this._findState(this._transitionPreviousStates[i].name);\n\t\t\t\t\t\t\t\t\t\tconst stateWeight = this._transitionPreviousStates[i].weight;\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < state.animations.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tanimation = state.animations[j];\n\t\t\t\t\t\t\t\t\t\t\t\tclip = this._animEvaluator.findClip(`${animation.name}.previous.${i}`);\n\t\t\t\t\t\t\t\t\t\t\t\tif (clip) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclip.blendWeight = (1.0 - interpolatedTime) * animation.normalizedWeight * stateWeight;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstate = this.activeState;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < state.animations.length; i++){\n\t\t\t\t\t\t\t\t\t\tanimation = state.animations[i];\n\t\t\t\t\t\t\t\t\t\tthis._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.normalizedWeight;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._isTransitioning = false;\n\t\t\t\t\t\t\t\tconst activeClips = this.activeStateAnimations.length;\n\t\t\t\t\t\t\t\tconst totalClips = this._animEvaluator.clips.length;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < totalClips - activeClips; i++){\n\t\t\t\t\t\t\t\t\t\tthis._animEvaluator.removeClip(0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._transitionPreviousStates = [];\n\t\t\t\t\t\t\t\tstate = this.activeState;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < state.animations.length; i++){\n\t\t\t\t\t\t\t\t\t\tanimation = state.animations[i];\n\t\t\t\t\t\t\t\t\t\tclip = this._animEvaluator.findClip(animation.name);\n\t\t\t\t\t\t\t\t\t\tif (clip) {\n\t\t\t\t\t\t\t\t\t\t\t\tclip.blendWeight = animation.normalizedWeight;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this.activeState._blendTree.constructor !== AnimNode) {\n\t\t\t\t\t\t\t\tstate = this.activeState;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < state.animations.length; i++){\n\t\t\t\t\t\t\t\t\t\tanimation = state.animations[i];\n\t\t\t\t\t\t\t\t\t\tclip = this._animEvaluator.findClip(animation.name);\n\t\t\t\t\t\t\t\t\t\tif (clip) {\n\t\t\t\t\t\t\t\t\t\t\t\tclip.blendWeight = animation.normalizedWeight;\n\t\t\t\t\t\t\t\t\t\t\t\tif (animation.parent.syncAnimations) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclip.speed = animation.speed;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._animEvaluator.update(dt, this.activeState.hasAnimations);\n\t\t}\n};\n\nconst v2 = new Vec2();\nconst v3 = new Vec3();\nconst v4 = new Vec4();\nconst c = new Color();\nconst q = new Quat();\nclass AnimComponentBinder extends DefaultAnimBinder {\n\t\tconstructor(animComponent, graph, layerName, mask, layerIndex){\n\t\t\t\tsuper(graph);\n\t\t\t\tthis.animComponent = animComponent;\n\t\t\t\tthis._mask = mask;\n\t\t\t\tthis.layerName = layerName;\n\t\t\t\tthis.layerIndex = layerIndex;\n\t\t}\n\t\tstatic _packFloat(values) {\n\t\t\t\treturn values[0];\n\t\t}\n\t\tstatic _packBoolean(values) {\n\t\t\t\treturn !!values[0];\n\t\t}\n\t\tstatic _packVec2(values) {\n\t\t\t\tv2.x = values[0];\n\t\t\t\tv2.y = values[1];\n\t\t\t\treturn v2;\n\t\t}\n\t\tstatic _packVec3(values) {\n\t\t\t\tv3.x = values[0];\n\t\t\t\tv3.y = values[1];\n\t\t\t\tv3.z = values[2];\n\t\t\t\treturn v3;\n\t\t}\n\t\tstatic _packVec4(values) {\n\t\t\t\tv4.x = values[0];\n\t\t\t\tv4.y = values[1];\n\t\t\t\tv4.z = values[2];\n\t\t\t\tv4.w = values[3];\n\t\t\t\treturn v4;\n\t\t}\n\t\tstatic _packColor(values) {\n\t\t\t\tc.r = values[0];\n\t\t\t\tc.g = values[1];\n\t\t\t\tc.b = values[2];\n\t\t\t\tc.a = values[3];\n\t\t\t\treturn c;\n\t\t}\n\t\tstatic _packQuat(values) {\n\t\t\t\tq.x = values[0];\n\t\t\t\tq.y = values[1];\n\t\t\t\tq.z = values[2];\n\t\t\t\tq.w = values[3];\n\t\t\t\treturn q;\n\t\t}\n\t\tresolve(path) {\n\t\t\t\tconst encodedPath = AnimBinder.encode(path.entityPath, path.component, path.propertyPath);\n\t\t\t\tlet target = this.targetCache[encodedPath];\n\t\t\t\tif (target) return target;\n\t\t\t\tlet entity;\n\t\t\t\tlet propertyComponent;\n\t\t\t\tlet targetPath;\n\t\t\t\tswitch(path.component){\n\t\t\t\t\t\tcase 'entity':\n\t\t\t\t\t\t\t\tentity = this._getEntityFromHierarchy(path.entityPath);\n\t\t\t\t\t\t\t\ttargetPath = AnimBinder.encode(entity.path, 'entity', path.propertyPath);\n\t\t\t\t\t\t\t\tpropertyComponent = entity;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'graph':\n\t\t\t\t\t\t\t\tpropertyComponent = this.findNode(path);\n\t\t\t\t\t\t\t\tif (!propertyComponent) return null;\n\t\t\t\t\t\t\t\ttargetPath = AnimBinder.encode(propertyComponent.path, 'graph', path.propertyPath);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tentity = this._getEntityFromHierarchy(path.entityPath);\n\t\t\t\t\t\t\t\tpropertyComponent = entity.findComponent(path.component);\n\t\t\t\t\t\t\t\tif (!propertyComponent) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttargetPath = AnimBinder.encode(entity.path, path.component, path.propertyPath);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttarget = this._createAnimTargetForProperty(propertyComponent, path.propertyPath, targetPath);\n\t\t\t\tthis.targetCache[encodedPath] = target;\n\t\t\t\treturn target;\n\t\t}\n\t\tupdate(deltaTime) {\n\t\t\t\tconst activeNodes = this.activeNodes;\n\t\t\t\tif (activeNodes) {\n\t\t\t\t\t\tfor(let i = 0; i < activeNodes.length; i++){\n\t\t\t\t\t\t\t\tactiveNodes[i]._dirtifyLocal();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_getEntityFromHierarchy(entityHierarchy) {\n\t\t\t\tif (!this.animComponent.entity.name === entityHierarchy[0]) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst currEntity = this.animComponent.entity;\n\t\t\t\tif (entityHierarchy.length === 1) {\n\t\t\t\t\t\treturn currEntity;\n\t\t\t\t}\n\t\t\t\treturn currEntity._parent.findByPath(entityHierarchy);\n\t\t}\n\t\t_resolvePath(object, path, resolveLeaf) {\n\t\t\t\tconst steps = path.length - (resolveLeaf ? 0 : 1);\n\t\t\t\tfor(let i = 0; i < steps; i++){\n\t\t\t\t\t\tobject = object[path[i]];\n\t\t\t\t}\n\t\t\t\treturn object;\n\t\t}\n\t\t_setter(object, path, packFunc) {\n\t\t\t\tconst obj = this._resolvePath(object, path);\n\t\t\t\tconst key = path[path.length - 1];\n\t\t\t\tconst setterFuncName = `set${key.substring(0, 1).toUpperCase()}${key.substring(1)}`;\n\t\t\t\tif (obj[setterFuncName]) {\n\t\t\t\t\t\tconst getterFunc = obj[`get${key.substring(0, 1).toUpperCase()}${key.substring(1)}`].bind(obj);\n\t\t\t\t\t\tlet baseValues = getterFunc();\n\t\t\t\t\t\tbaseValues = [\n\t\t\t\t\t\t\t\tbaseValues.x,\n\t\t\t\t\t\t\t\tbaseValues.y,\n\t\t\t\t\t\t\t\tbaseValues.z,\n\t\t\t\t\t\t\t\tbaseValues.w\n\t\t\t\t\t\t];\n\t\t\t\t\t\tconst setterFunc = obj[setterFuncName].bind(obj);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tset: (values)=>{\n\t\t\t\t\t\t\t\t\t\tsetterFunc(packFunc(values));\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tget: ()=>baseValues\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst prop = obj[key];\n\t\t\t\tif (typeof prop === 'object' && prop.hasOwnProperty('copy')) {\n\t\t\t\t\t\treturn function(values) {\n\t\t\t\t\t\t\t\tprop.copy(packFunc(values));\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif ([\n\t\t\t\t\t\tVec2,\n\t\t\t\t\t\tVec3,\n\t\t\t\t\t\tVec4,\n\t\t\t\t\t\tColor,\n\t\t\t\t\t\tQuat\n\t\t\t\t].indexOf(obj.constructor) !== -1 && path.length > 1) {\n\t\t\t\t\t\tconst parent = path.length > 2 ? this._resolvePath(object, path.slice(0, -1)) : object;\n\t\t\t\t\t\tconst objKey = path[path.length - 2];\n\t\t\t\t\t\treturn function(values) {\n\t\t\t\t\t\t\t\tobj[key] = packFunc(values);\n\t\t\t\t\t\t\t\tparent[objKey] = obj;\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn function(values) {\n\t\t\t\t\t\tobj[key] = packFunc(values);\n\t\t\t\t};\n\t\t}\n\t\t_createAnimTargetForProperty(propertyComponent, propertyHierarchy, targetPath) {\n\t\t\t\tif (this.handlers && propertyHierarchy[0].startsWith('weight.')) {\n\t\t\t\t\t\treturn this.handlers.weight(propertyComponent, propertyHierarchy[0].replace('weight.', ''));\n\t\t\t\t} else if (this.handlers && propertyHierarchy[0] === 'material' && propertyHierarchy.length === 2) {\n\t\t\t\t\t\tconst materialPropertyName = propertyHierarchy[1];\n\t\t\t\t\t\tif (materialPropertyName.endsWith('Map')) {\n\t\t\t\t\t\t\t\treturn this.handlers.materialTexture(propertyComponent, materialPropertyName);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst property = this._resolvePath(propertyComponent, propertyHierarchy, true);\n\t\t\t\tif (typeof property === 'undefined') {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tlet setter;\n\t\t\t\tlet animDataType;\n\t\t\t\tlet animDataComponents;\n\t\t\t\tif (typeof property === 'number') {\n\t\t\t\t\t\tsetter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packFloat);\n\t\t\t\t\t\tanimDataType = 'vector';\n\t\t\t\t\t\tanimDataComponents = 1;\n\t\t\t\t} else if (typeof property === 'boolean') {\n\t\t\t\t\t\tsetter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packBoolean);\n\t\t\t\t\t\tanimDataType = 'vector';\n\t\t\t\t\t\tanimDataComponents = 1;\n\t\t\t\t} else if (typeof property === 'object') {\n\t\t\t\t\t\tswitch(property.constructor){\n\t\t\t\t\t\t\t\tcase Vec2:\n\t\t\t\t\t\t\t\t\t\tsetter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packVec2);\n\t\t\t\t\t\t\t\t\t\tanimDataType = 'vector';\n\t\t\t\t\t\t\t\t\t\tanimDataComponents = 2;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase Vec3:\n\t\t\t\t\t\t\t\t\t\tsetter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packVec3);\n\t\t\t\t\t\t\t\t\t\tanimDataType = 'vector';\n\t\t\t\t\t\t\t\t\t\tanimDataComponents = 3;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase Vec4:\n\t\t\t\t\t\t\t\t\t\tsetter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packVec4);\n\t\t\t\t\t\t\t\t\t\tanimDataType = 'vector';\n\t\t\t\t\t\t\t\t\t\tanimDataComponents = 4;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase Color:\n\t\t\t\t\t\t\t\t\t\tsetter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packColor);\n\t\t\t\t\t\t\t\t\t\tanimDataType = 'vector';\n\t\t\t\t\t\t\t\t\t\tanimDataComponents = 4;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase Quat:\n\t\t\t\t\t\t\t\t\t\tsetter = this._setter(propertyComponent, propertyHierarchy, AnimComponentBinder._packQuat);\n\t\t\t\t\t\t\t\t\t\tanimDataType = 'quaternion';\n\t\t\t\t\t\t\t\t\t\tanimDataComponents = 4;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (propertyHierarchy.indexOf('material') !== -1) {\n\t\t\t\t\t\treturn new AnimTarget((values)=>{\n\t\t\t\t\t\t\t\tsetter(values);\n\t\t\t\t\t\t\t\tpropertyComponent.material.update();\n\t\t\t\t\t\t}, animDataType, animDataComponents, targetPath);\n\t\t\t\t}\n\t\t\t\treturn new AnimTarget(setter, animDataType, animDataComponents, targetPath);\n\t\t}\n\t\trebind() {\n\t\t\t\tthis.targetCache = {};\n\t\t\t\tif (this.animComponent.rootBone) {\n\t\t\t\t\t\tthis.graph = this.animComponent.rootBone;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.graph = this.animComponent.entity;\n\t\t\t\t}\n\t\t\t\tconst nodes = {};\n\t\t\t\tconst flatten = function(node) {\n\t\t\t\t\t\tnodes[node.name] = node;\n\t\t\t\t\t\tfor(let i = 0; i < node.children.length; ++i){\n\t\t\t\t\t\t\t\tflatten(node.children[i]);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tflatten(this.graph);\n\t\t\t\tthis.nodes = nodes;\n\t\t}\n}\n\nclass AnimComponentLayer {\n\t\tconstructor(name, controller, component, weight = 1, blendType = ANIM_LAYER_OVERWRITE){\n\t\t\t\tthis._mask = null;\n\t\t\t\tthis._blendTime = 0;\n\t\t\t\tthis._blendTimeElapsed = 0;\n\t\t\t\tthis._startingWeight = 0;\n\t\t\t\tthis._targetWeight = 0;\n\t\t\t\tthis._name = name;\n\t\t\t\tthis._controller = controller;\n\t\t\t\tthis._component = component;\n\t\t\t\tthis._weight = weight;\n\t\t\t\tthis._blendType = blendType;\n\t\t}\n\t\tget name() {\n\t\t\t\treturn this._name;\n\t\t}\n\t\tset playing(value) {\n\t\t\t\tthis._controller.playing = value;\n\t\t}\n\t\tget playing() {\n\t\t\t\treturn this._controller.playing;\n\t\t}\n\t\tget playable() {\n\t\t\t\treturn this._controller.playable;\n\t\t}\n\t\tget activeState() {\n\t\t\t\treturn this._controller.activeStateName;\n\t\t}\n\t\tget previousState() {\n\t\t\t\treturn this._controller.previousStateName;\n\t\t}\n\t\tget activeStateProgress() {\n\t\t\t\treturn this._controller.activeStateProgress;\n\t\t}\n\t\tget activeStateDuration() {\n\t\t\t\treturn this._controller.activeStateDuration;\n\t\t}\n\t\tset activeStateCurrentTime(time) {\n\t\t\t\tconst controller = this._controller;\n\t\t\t\tconst layerPlaying = controller.playing;\n\t\t\t\tcontroller.playing = true;\n\t\t\t\tcontroller.activeStateCurrentTime = time;\n\t\t\t\tif (!layerPlaying) {\n\t\t\t\t\t\tcontroller.update(0);\n\t\t\t\t}\n\t\t\t\tcontroller.playing = layerPlaying;\n\t\t}\n\t\tget activeStateCurrentTime() {\n\t\t\t\treturn this._controller.activeStateCurrentTime;\n\t\t}\n\t\tget transitioning() {\n\t\t\t\treturn this._controller.transitioning;\n\t\t}\n\t\tget transitionProgress() {\n\t\t\t\tif (this.transitioning) {\n\t\t\t\t\t\treturn this._controller.transitionProgress;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tget states() {\n\t\t\t\treturn this._controller.states;\n\t\t}\n\t\tset weight(value) {\n\t\t\t\tthis._weight = value;\n\t\t\t\tthis._component.dirtifyTargets();\n\t\t}\n\t\tget weight() {\n\t\t\t\treturn this._weight;\n\t\t}\n\t\tset blendType(value) {\n\t\t\t\tif (value !== this._blendType) {\n\t\t\t\t\t\tthis._blendType = value;\n\t\t\t\t\t\tif (this._controller.normalizeWeights) {\n\t\t\t\t\t\t\t\tthis._component.rebind();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget blendType() {\n\t\t\t\treturn this._blendType;\n\t\t}\n\t\tset mask(value) {\n\t\t\t\tif (this._controller.assignMask(value)) {\n\t\t\t\t\t\tthis._component.rebind();\n\t\t\t\t}\n\t\t\t\tthis._mask = value;\n\t\t}\n\t\tget mask() {\n\t\t\t\treturn this._mask;\n\t\t}\n\t\tplay(name) {\n\t\t\t\tthis._controller.play(name);\n\t\t}\n\t\tpause() {\n\t\t\t\tthis._controller.pause();\n\t\t}\n\t\treset() {\n\t\t\t\tthis._controller.reset();\n\t\t}\n\t\trebind() {\n\t\t\t\tthis._controller.rebind();\n\t\t}\n\t\tupdate(dt) {\n\t\t\t\tif (this._blendTime) {\n\t\t\t\t\t\tif (this._blendTimeElapsed < this._blendTime) {\n\t\t\t\t\t\t\t\tthis.weight = math.lerp(this._startingWeight, this._targetWeight, this._blendTimeElapsed / this._blendTime);\n\t\t\t\t\t\t\t\tthis._blendTimeElapsed += dt;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.weight = this._targetWeight;\n\t\t\t\t\t\t\t\tthis._blendTime = 0;\n\t\t\t\t\t\t\t\tthis._blendTimeElapsed = 0;\n\t\t\t\t\t\t\t\tthis._startingWeight = 0;\n\t\t\t\t\t\t\t\tthis._targetWeight = 0;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._controller.update(dt);\n\t\t}\n\t\tblendToWeight(weight, time) {\n\t\t\t\tthis._startingWeight = this.weight;\n\t\t\t\tthis._targetWeight = weight;\n\t\t\t\tthis._blendTime = Math.max(0, time);\n\t\t\t\tthis._blendTimeElapsed = 0;\n\t\t}\n\t\tassignAnimation(nodePath, animTrack, speed, loop) {\n\t\t\t\tif (!(animTrack instanceof AnimTrack)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._controller.assignAnimation(nodePath, animTrack, speed, loop);\n\t\t\t\tif (this._controller._transitions.length === 0) {\n\t\t\t\t\t\tthis._controller._transitions.push(new AnimTransition({\n\t\t\t\t\t\t\t\tfrom: 'START',\n\t\t\t\t\t\t\t\tto: nodePath\n\t\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t\tif (this._component.activate && this._component.playable) {\n\t\t\t\t\t\tthis._component.playing = true;\n\t\t\t\t}\n\t\t}\n\t\tremoveNodeAnimations(nodeName) {\n\t\t\t\tif (this._controller.removeNodeAnimations(nodeName)) {\n\t\t\t\t\t\tthis._component.playing = false;\n\t\t\t\t}\n\t\t}\n\t\tgetAnimationAsset(stateName) {\n\t\t\t\treturn this._component.animationAssets[`${this.name}:${stateName}`];\n\t\t}\n\t\ttransition(to, time = 0, transitionOffset = null) {\n\t\t\t\tthis._controller.updateStateFromTransition(new AnimTransition({\n\t\t\t\t\t\tfrom: this._controller.activeStateName,\n\t\t\t\t\t\tto,\n\t\t\t\t\t\ttime,\n\t\t\t\t\t\ttransitionOffset\n\t\t\t\t}));\n\t\t}\n}\n\nclass AnimStateGraph {\n\t\tconstructor(data){\n\t\t\t\tthis._layers = [];\n\t\t\t\tthis._parameters = {};\n\t\t\t\tif (!Array.isArray(data.layers)) {\n\t\t\t\t\t\tfor(const layerId in data.layers){\n\t\t\t\t\t\t\t\tconst dataLayer = data.layers[layerId];\n\t\t\t\t\t\t\t\tconst layer = {\n\t\t\t\t\t\t\t\t\t\tname: dataLayer.name,\n\t\t\t\t\t\t\t\t\t\tblendType: dataLayer.blendType,\n\t\t\t\t\t\t\t\t\t\tweight: dataLayer.weight,\n\t\t\t\t\t\t\t\t\t\tstates: [],\n\t\t\t\t\t\t\t\t\t\ttransitions: []\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor(let i = 0; i < dataLayer.states.length; i++){\n\t\t\t\t\t\t\t\t\t\tlayer.states.push(data.states[dataLayer.states[i]]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor(let i = 0; i < dataLayer.transitions.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst dataLayerTransition = data.transitions[dataLayer.transitions[i]];\n\t\t\t\t\t\t\t\t\t\tif (dataLayerTransition.conditions && !Array.isArray(dataLayerTransition.conditions)) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst conditionKeys = Object.keys(dataLayerTransition.conditions);\n\t\t\t\t\t\t\t\t\t\t\t\tconst conditions = [];\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < conditionKeys.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst condition = dataLayerTransition.conditions[conditionKeys[j]];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (condition.parameterName) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconditions.push(condition);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tdataLayerTransition.conditions = conditions;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (Number.isInteger(dataLayerTransition.from)) {\n\t\t\t\t\t\t\t\t\t\t\t\tdataLayerTransition.from = data.states[dataLayerTransition.from].name;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (Number.isInteger(dataLayerTransition.to)) {\n\t\t\t\t\t\t\t\t\t\t\t\tdataLayerTransition.to = data.states[dataLayerTransition.to].name;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tlayer.transitions.push(dataLayerTransition);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._layers.push(layer);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._layers = data.layers;\n\t\t\t\t}\n\t\t\t\tfor(const paramId in data.parameters){\n\t\t\t\t\t\tconst param = data.parameters[paramId];\n\t\t\t\t\t\tthis._parameters[param.name] = {\n\t\t\t\t\t\t\t\ttype: param.type,\n\t\t\t\t\t\t\t\tvalue: param.value\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tget parameters() {\n\t\t\t\treturn Object.assign({}, this._parameters);\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n}\n\nclass AnimComponent extends Component {\n\t\tset stateGraphAsset(value) {\n\t\t\t\tif (value === null) {\n\t\t\t\t\t\tthis.removeStateGraph();\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._stateGraphAsset) {\n\t\t\t\t\t\tconst stateGraphAsset = this.system.app.assets.get(this._stateGraphAsset);\n\t\t\t\t\t\tstateGraphAsset.off('change', this._onStateGraphAssetChangeEvent, this);\n\t\t\t\t}\n\t\t\t\tlet _id;\n\t\t\t\tlet _asset;\n\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\t_id = value.id;\n\t\t\t\t\t\t_asset = this.system.app.assets.get(_id);\n\t\t\t\t\t\tif (!_asset) {\n\t\t\t\t\t\t\t\tthis.system.app.assets.add(value);\n\t\t\t\t\t\t\t\t_asset = this.system.app.assets.get(_id);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\t_id = value;\n\t\t\t\t\t\t_asset = this.system.app.assets.get(_id);\n\t\t\t\t}\n\t\t\t\tif (!_asset || this._stateGraphAsset === _id) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (_asset.resource) {\n\t\t\t\t\t\tthis._stateGraph = _asset.resource;\n\t\t\t\t\t\tthis.loadStateGraph(this._stateGraph);\n\t\t\t\t\t\t_asset.on('change', this._onStateGraphAssetChangeEvent, this);\n\t\t\t\t} else {\n\t\t\t\t\t\t_asset.once('load', (asset)=>{\n\t\t\t\t\t\t\t\tthis._stateGraph = asset.resource;\n\t\t\t\t\t\t\t\tthis.loadStateGraph(this._stateGraph);\n\t\t\t\t\t\t});\n\t\t\t\t\t\t_asset.on('change', this._onStateGraphAssetChangeEvent, this);\n\t\t\t\t\t\tthis.system.app.assets.load(_asset);\n\t\t\t\t}\n\t\t\t\tthis._stateGraphAsset = _id;\n\t\t}\n\t\tget stateGraphAsset() {\n\t\t\t\treturn this._stateGraphAsset;\n\t\t}\n\t\tset normalizeWeights(value) {\n\t\t\t\tthis._normalizeWeights = value;\n\t\t\t\tthis.unbind();\n\t\t}\n\t\tget normalizeWeights() {\n\t\t\t\treturn this._normalizeWeights;\n\t\t}\n\t\tset animationAssets(value) {\n\t\t\t\tthis._animationAssets = value;\n\t\t\t\tthis.loadAnimationAssets();\n\t\t}\n\t\tget animationAssets() {\n\t\t\t\treturn this._animationAssets;\n\t\t}\n\t\tset speed(value) {\n\t\t\t\tthis._speed = value;\n\t\t}\n\t\tget speed() {\n\t\t\t\treturn this._speed;\n\t\t}\n\t\tset activate(value) {\n\t\t\t\tthis._activate = value;\n\t\t}\n\t\tget activate() {\n\t\t\t\treturn this._activate;\n\t\t}\n\t\tset playing(value) {\n\t\t\t\tthis._playing = value;\n\t\t}\n\t\tget playing() {\n\t\t\t\treturn this._playing;\n\t\t}\n\t\tset rootBone(value) {\n\t\t\t\tif (typeof value === 'string') {\n\t\t\t\t\t\tconst entity = this.entity.root.findByGuid(value);\n\t\t\t\t\t\tthis._rootBone = entity;\n\t\t\t\t} else if (value instanceof Entity) {\n\t\t\t\t\t\tthis._rootBone = value;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._rootBone = null;\n\t\t\t\t}\n\t\t\t\tthis.rebind();\n\t\t}\n\t\tget rootBone() {\n\t\t\t\treturn this._rootBone;\n\t\t}\n\t\tset stateGraph(value) {\n\t\t\t\tthis._stateGraph = value;\n\t\t}\n\t\tget stateGraph() {\n\t\t\t\treturn this._stateGraph;\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tset layerIndices(value) {\n\t\t\t\tthis._layerIndices = value;\n\t\t}\n\t\tget layerIndices() {\n\t\t\t\treturn this._layerIndices;\n\t\t}\n\t\tset parameters(value) {\n\t\t\t\tthis._parameters = value;\n\t\t}\n\t\tget parameters() {\n\t\t\t\treturn this._parameters;\n\t\t}\n\t\tset targets(value) {\n\t\t\t\tthis._targets = value;\n\t\t}\n\t\tget targets() {\n\t\t\t\treturn this._targets;\n\t\t}\n\t\tget playable() {\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tif (!this._layers[i].playable) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tget baseLayer() {\n\t\t\t\tif (this._layers.length > 0) {\n\t\t\t\t\t\treturn this._layers[0];\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\t_onStateGraphAssetChangeEvent(asset) {\n\t\t\t\tconst prevAnimationAssets = this.animationAssets;\n\t\t\t\tconst prevMasks = this.layers.map((layer)=>layer.mask);\n\t\t\t\tthis.removeStateGraph();\n\t\t\t\tthis._stateGraph = new AnimStateGraph(asset._data);\n\t\t\t\tthis.loadStateGraph(this._stateGraph);\n\t\t\t\tthis.animationAssets = prevAnimationAssets;\n\t\t\t\tthis.loadAnimationAssets();\n\t\t\t\tthis.layers.forEach((layer, i)=>{\n\t\t\t\t\t\tlayer.mask = prevMasks[i];\n\t\t\t\t});\n\t\t\t\tthis.rebind();\n\t\t}\n\t\tdirtifyTargets() {\n\t\t\t\tconst targets = Object.values(this._targets);\n\t\t\t\tfor(let i = 0; i < targets.length; i++){\n\t\t\t\t\t\ttargets[i].dirty = true;\n\t\t\t\t}\n\t\t}\n\t\t_addLayer({ name, states, transitions, weight, mask, blendType }) {\n\t\t\t\tlet graph;\n\t\t\t\tif (this.rootBone) {\n\t\t\t\t\t\tgraph = this.rootBone;\n\t\t\t\t} else {\n\t\t\t\t\t\tgraph = this.entity;\n\t\t\t\t}\n\t\t\t\tconst layerIndex = this._layers.length;\n\t\t\t\tconst animBinder = new AnimComponentBinder(this, graph, name, mask, layerIndex);\n\t\t\t\tconst animEvaluator = new AnimEvaluator(animBinder);\n\t\t\t\tconst controller = new AnimController$1(animEvaluator, states, transitions, this._activate, this, this.findParameter, this.consumeTrigger);\n\t\t\t\tthis._layers.push(new AnimComponentLayer(name, controller, this, weight, blendType));\n\t\t\t\tthis._layerIndices[name] = layerIndex;\n\t\t\t\treturn this._layers[layerIndex];\n\t\t}\n\t\taddLayer(name, weight, mask, blendType) {\n\t\t\t\tconst layer = this.findAnimationLayer(name);\n\t\t\t\tif (layer) return layer;\n\t\t\t\tconst states = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t'name': 'START',\n\t\t\t\t\t\t\t\t'speed': 1\n\t\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t\tconst transitions = [];\n\t\t\t\treturn this._addLayer({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tstates,\n\t\t\t\t\t\ttransitions,\n\t\t\t\t\t\tweight,\n\t\t\t\t\t\tmask,\n\t\t\t\t\t\tblendType\n\t\t\t\t});\n\t\t}\n\t\t_assignParameters(stateGraph) {\n\t\t\t\tthis._parameters = {};\n\t\t\t\tconst paramKeys = Object.keys(stateGraph.parameters);\n\t\t\t\tfor(let i = 0; i < paramKeys.length; i++){\n\t\t\t\t\t\tconst paramKey = paramKeys[i];\n\t\t\t\t\t\tthis._parameters[paramKey] = {\n\t\t\t\t\t\t\t\ttype: stateGraph.parameters[paramKey].type,\n\t\t\t\t\t\t\t\tvalue: stateGraph.parameters[paramKey].value\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tloadStateGraph(stateGraph) {\n\t\t\t\tthis._stateGraph = stateGraph;\n\t\t\t\tthis._assignParameters(stateGraph);\n\t\t\t\tthis._layers = [];\n\t\t\t\tlet containsBlendTree = false;\n\t\t\t\tfor(let i = 0; i < stateGraph.layers.length; i++){\n\t\t\t\t\t\tconst layer = stateGraph.layers[i];\n\t\t\t\t\t\tthis._addLayer({\n\t\t\t\t\t\t\t\t...layer\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (layer.states.some((state)=>state.blendTree)) {\n\t\t\t\t\t\t\t\tcontainsBlendTree = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!containsBlendTree) {\n\t\t\t\t\t\tthis.setupAnimationAssets();\n\t\t\t\t}\n\t\t}\n\t\tsetupAnimationAssets() {\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layer = this._layers[i];\n\t\t\t\t\t\tconst layerName = layer.name;\n\t\t\t\t\t\tfor(let j = 0; j < layer.states.length; j++){\n\t\t\t\t\t\t\t\tconst stateName = layer.states[j];\n\t\t\t\t\t\t\t\tif (ANIM_CONTROL_STATES.indexOf(stateName) === -1) {\n\t\t\t\t\t\t\t\t\t\tconst stateKey = `${layerName}:${stateName}`;\n\t\t\t\t\t\t\t\t\t\tif (!this._animationAssets[stateKey]) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._animationAssets[stateKey] = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tasset: null\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.loadAnimationAssets();\n\t\t}\n\t\tloadAnimationAssets() {\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layer = this._layers[i];\n\t\t\t\t\t\tfor(let j = 0; j < layer.states.length; j++){\n\t\t\t\t\t\t\t\tconst stateName = layer.states[j];\n\t\t\t\t\t\t\t\tif (ANIM_CONTROL_STATES.indexOf(stateName) !== -1) continue;\n\t\t\t\t\t\t\t\tconst animationAsset = this._animationAssets[`${layer.name}:${stateName}`];\n\t\t\t\t\t\t\t\tif (!animationAsset || !animationAsset.asset) {\n\t\t\t\t\t\t\t\t\t\tthis.findAnimationLayer(layer.name).assignAnimation(stateName, AnimTrack.EMPTY);\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst assetId = animationAsset.asset;\n\t\t\t\t\t\t\t\tconst asset = this.system.app.assets.get(assetId);\n\t\t\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.onAnimationAssetLoaded(layer.name, stateName, asset);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tasset.once('load', (function(layerName, stateName) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn (function(asset) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.onAnimationAssetLoaded(layerName, stateName, asset);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}).bind(this);\n\t\t\t\t\t\t\t\t\t\t\t\t}).bind(this)(layer.name, stateName));\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonAnimationAssetLoaded(layerName, stateName, asset) {\n\t\t\t\tthis.findAnimationLayer(layerName).assignAnimation(stateName, asset.resource);\n\t\t}\n\t\tremoveStateGraph() {\n\t\t\t\tthis._stateGraph = null;\n\t\t\t\tthis._stateGraphAsset = null;\n\t\t\t\tthis._animationAssets = {};\n\t\t\t\tthis._layers = [];\n\t\t\t\tthis._layerIndices = {};\n\t\t\t\tthis._parameters = {};\n\t\t\t\tthis._playing = false;\n\t\t\t\tthis.unbind();\n\t\t\t\tthis._targets = {};\n\t\t}\n\t\treset() {\n\t\t\t\tthis._assignParameters(this._stateGraph);\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layerPlaying = this._layers[i].playing;\n\t\t\t\t\t\tthis._layers[i].reset();\n\t\t\t\t\t\tthis._layers[i].playing = layerPlaying;\n\t\t\t\t}\n\t\t}\n\t\tunbind() {\n\t\t\t\tif (!this._normalizeWeights) {\n\t\t\t\t\t\tObject.keys(this._targets).forEach((targetKey)=>{\n\t\t\t\t\t\t\t\tthis._targets[targetKey].unbind();\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\trebind() {\n\t\t\t\tthis._targets = {};\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tthis._layers[i].rebind();\n\t\t\t\t}\n\t\t}\n\t\tfindAnimationLayer(name) {\n\t\t\t\tconst layerIndex = this._layerIndices[name];\n\t\t\t\treturn this._layers[layerIndex] || null;\n\t\t}\n\t\taddAnimationState(nodeName, animTrack, speed = 1, loop = true, layerName = 'Base') {\n\t\t\t\tif (!this._stateGraph) {\n\t\t\t\t\t\tthis.loadStateGraph(new AnimStateGraph({\n\t\t\t\t\t\t\t\t'layers': [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t'name': layerName,\n\t\t\t\t\t\t\t\t\t\t\t\t'states': [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'name': 'START',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'speed': 1\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'name': nodeName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'speed': speed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'loop': loop,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'defaultState': true\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\t'transitions': [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'from': 'START',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'to': nodeName\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t'parameters': {}\n\t\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t\tconst layer = this.findAnimationLayer(layerName);\n\t\t\t\tif (layer) {\n\t\t\t\t\t\tlayer.assignAnimation(nodeName, animTrack, speed, loop);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.addLayer(layerName)?.assignAnimation(nodeName, animTrack, speed, loop);\n\t\t\t\t}\n\t\t}\n\t\tassignAnimation(nodePath, animTrack, layerName, speed = 1, loop = true) {\n\t\t\t\tif (!this._stateGraph && nodePath.indexOf('.') === -1) {\n\t\t\t\t\t\tthis.loadStateGraph(new AnimStateGraph({\n\t\t\t\t\t\t\t\t'layers': [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t'name': 'Base',\n\t\t\t\t\t\t\t\t\t\t\t\t'states': [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'name': 'START',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'speed': 1\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'name': nodePath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'speed': speed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'loop': loop,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'defaultState': true\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\t'transitions': [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'from': 'START',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'to': nodePath\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t'parameters': {}\n\t\t\t\t\t\t}));\n\t\t\t\t\t\tthis.baseLayer.assignAnimation(nodePath, animTrack);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst layer = layerName ? this.findAnimationLayer(layerName) : this.baseLayer;\n\t\t\t\tif (!layer) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlayer.assignAnimation(nodePath, animTrack, speed, loop);\n\t\t}\n\t\tremoveNodeAnimations(nodeName, layerName) {\n\t\t\t\tconst layer = layerName ? this.findAnimationLayer(layerName) : this.baseLayer;\n\t\t\t\tif (!layer) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlayer.removeNodeAnimations(nodeName);\n\t\t}\n\t\tgetParameterValue(name, type) {\n\t\t\t\tconst param = this._parameters[name];\n\t\t\t\tif (param && param.type === type) {\n\t\t\t\t\t\treturn param.value;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\tsetParameterValue(name, type, value) {\n\t\t\t\tconst param = this._parameters[name];\n\t\t\t\tif (param && param.type === type) {\n\t\t\t\t\t\tparam.value = value;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t}\n\t\tgetFloat(name) {\n\t\t\t\treturn this.getParameterValue(name, ANIM_PARAMETER_FLOAT);\n\t\t}\n\t\tsetFloat(name, value) {\n\t\t\t\tthis.setParameterValue(name, ANIM_PARAMETER_FLOAT, value);\n\t\t}\n\t\tgetInteger(name) {\n\t\t\t\treturn this.getParameterValue(name, ANIM_PARAMETER_INTEGER);\n\t\t}\n\t\tsetInteger(name, value) {\n\t\t\t\tif (typeof value === 'number' && value % 1 === 0) {\n\t\t\t\t\t\tthis.setParameterValue(name, ANIM_PARAMETER_INTEGER, value);\n\t\t\t\t}\n\t\t}\n\t\tgetBoolean(name) {\n\t\t\t\treturn this.getParameterValue(name, ANIM_PARAMETER_BOOLEAN);\n\t\t}\n\t\tsetBoolean(name, value) {\n\t\t\t\tthis.setParameterValue(name, ANIM_PARAMETER_BOOLEAN, !!value);\n\t\t}\n\t\tgetTrigger(name) {\n\t\t\t\treturn this.getParameterValue(name, ANIM_PARAMETER_TRIGGER);\n\t\t}\n\t\tsetTrigger(name, singleFrame = false) {\n\t\t\t\tthis.setParameterValue(name, ANIM_PARAMETER_TRIGGER, true);\n\t\t\t\tif (singleFrame) {\n\t\t\t\t\t\tthis._consumedTriggers.add(name);\n\t\t\t\t}\n\t\t}\n\t\tresetTrigger(name) {\n\t\t\t\tthis.setParameterValue(name, ANIM_PARAMETER_TRIGGER, false);\n\t\t}\n\t\tonBeforeRemove() {\n\t\t\t\tif (Number.isFinite(this._stateGraphAsset)) {\n\t\t\t\t\t\tconst stateGraphAsset = this.system.app.assets.get(this._stateGraphAsset);\n\t\t\t\t\t\tstateGraphAsset.off('change', this._onStateGraphAssetChangeEvent, this);\n\t\t\t\t}\n\t\t}\n\t\tupdate(dt) {\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tthis.layers[i].update(dt * this.speed);\n\t\t\t\t}\n\t\t\t\tthis._consumedTriggers.forEach((trigger)=>{\n\t\t\t\t\t\tthis.parameters[trigger].value = false;\n\t\t\t\t});\n\t\t\t\tthis._consumedTriggers.clear();\n\t\t}\n\t\tresolveDuplicatedEntityReferenceProperties(oldAnim, duplicatedIdsMap) {\n\t\t\t\tif (oldAnim.rootBone && duplicatedIdsMap[oldAnim.rootBone.getGuid()]) {\n\t\t\t\t\t\tthis.rootBone = duplicatedIdsMap[oldAnim.rootBone.getGuid()];\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.rebind();\n\t\t\t\t}\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._stateGraphAsset = null, this._animationAssets = {}, this._speed = 1, this._activate = true, this._playing = false, this._rootBone = null, this._stateGraph = null, this._layers = [], this._layerIndices = {}, this._parameters = {}, this._targets = {}, this._consumedTriggers = new Set(), this._normalizeWeights = false, this.findParameter = (name)=>{\n\t\t\t\t\t\treturn this._parameters[name];\n\t\t\t\t}, this.consumeTrigger = (name)=>{\n\t\t\t\t\t\tthis._consumedTriggers.add(name);\n\t\t\t\t};\n\t\t}\n}\n\nclass AnimComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$j = [\n\t\t'enabled'\n];\nclass AnimComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'anim';\n\t\t\t\tthis.ComponentType = AnimComponent;\n\t\t\t\tthis.DataType = AnimComponentData;\n\t\t\t\tthis.schema = _schema$j;\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t\t\tthis.app.systems.on('animationUpdate', this.onAnimationUpdate, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tsuper.initializeComponentData(component, data, _schema$j);\n\t\t\t\tconst complexProperties = [\n\t\t\t\t\t\t'animationAssets',\n\t\t\t\t\t\t'stateGraph',\n\t\t\t\t\t\t'layers',\n\t\t\t\t\t\t'masks'\n\t\t\t\t];\n\t\t\t\tObject.keys(data).forEach((key)=>{\n\t\t\t\t\t\tif (complexProperties.includes(key)) return;\n\t\t\t\t\t\tcomponent[key] = data[key];\n\t\t\t\t});\n\t\t\t\tif (data.stateGraph) {\n\t\t\t\t\t\tcomponent.stateGraph = data.stateGraph;\n\t\t\t\t\t\tcomponent.loadStateGraph(component.stateGraph);\n\t\t\t\t}\n\t\t\t\tif (data.layers) {\n\t\t\t\t\t\tdata.layers.forEach((layer, i)=>{\n\t\t\t\t\t\t\t\tlayer._controller.states.forEach((stateKey)=>{\n\t\t\t\t\t\t\t\t\t\tlayer._controller._states[stateKey]._animationList.forEach((node)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tif (!node.animTrack || node.animTrack === AnimTrack.EMPTY) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst animationAsset = this.app.assets.get(layer._component._animationAssets[`${layer.name}:${node.name}`].asset);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (animationAsset && !animationAsset.loaded) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanimationAsset.once('load', ()=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent.layers[i].assignAnimation(node.name, animationAsset.resource);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent.layers[i].assignAnimation(node.name, node.animTrack);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (data.animationAssets) {\n\t\t\t\t\t\tcomponent.animationAssets = Object.assign(component.animationAssets, data.animationAssets);\n\t\t\t\t}\n\t\t\t\tif (data.masks) {\n\t\t\t\t\t\tObject.keys(data.masks).forEach((key)=>{\n\t\t\t\t\t\t\t\tif (component.layers[key]) {\n\t\t\t\t\t\t\t\t\t\tconst maskData = data.masks[key].mask;\n\t\t\t\t\t\t\t\t\t\tconst mask = {};\n\t\t\t\t\t\t\t\t\t\tObject.keys(maskData).forEach((maskKey)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tmask[decodeURI(maskKey)] = maskData[maskKey];\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tcomponent.layers[key].mask = mask;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tonAnimationUpdate(dt) {\n\t\t\t\tconst components = this.store;\n\t\t\t\tfor(const id in components){\n\t\t\t\t\t\tif (components.hasOwnProperty(id)) {\n\t\t\t\t\t\t\t\tconst component = components[id].entity.anim;\n\t\t\t\t\t\t\t\tconst componentData = component.data;\n\t\t\t\t\t\t\t\tif (componentData.enabled && component.entity.enabled && component.playing) {\n\t\t\t\t\t\t\t\t\t\tcomponent.update(dt);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tlet masks;\n\t\t\t\tif (!entity.anim.rootBone || entity.anim.rootBone === entity) {\n\t\t\t\t\t\tmasks = {};\n\t\t\t\t\t\tentity.anim.layers.forEach((layer, i)=>{\n\t\t\t\t\t\t\t\tif (layer.mask) {\n\t\t\t\t\t\t\t\t\t\tconst mask = {};\n\t\t\t\t\t\t\t\t\t\tObject.keys(layer.mask).forEach((path)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tconst pathArr = path.split('/');\n\t\t\t\t\t\t\t\t\t\t\t\tpathArr.shift();\n\t\t\t\t\t\t\t\t\t\t\t\tconst clonePath = [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclone.name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t...pathArr\n\t\t\t\t\t\t\t\t\t\t\t\t].join('/');\n\t\t\t\t\t\t\t\t\t\t\t\tmask[clonePath] = layer.mask[path];\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tmasks[i] = {\n\t\t\t\t\t\t\t\t\t\t\t\tmask\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst data = {\n\t\t\t\t\t\tenabled: entity.anim.enabled,\n\t\t\t\t\t\tstateGraphAsset: entity.anim.stateGraphAsset,\n\t\t\t\t\t\tanimationAssets: entity.anim.animationAssets,\n\t\t\t\t\t\tspeed: entity.anim.speed,\n\t\t\t\t\t\tactivate: entity.anim.activate,\n\t\t\t\t\t\tplaying: entity.anim.playing,\n\t\t\t\t\t\trootBone: entity.anim.rootBone,\n\t\t\t\t\t\tstateGraph: entity.anim.stateGraph,\n\t\t\t\t\t\tlayers: entity.anim.layers,\n\t\t\t\t\t\tlayerIndices: entity.anim.layerIndices,\n\t\t\t\t\t\tparameters: entity.anim.parameters,\n\t\t\t\t\t\tnormalizeWeights: entity.anim.normalizeWeights,\n\t\t\t\t\t\tmasks\n\t\t\t\t};\n\t\t\t\treturn this.addComponent(clone, data);\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tcomponent.onBeforeRemove();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('animationUpdate', this.onAnimationUpdate, this);\n\t\t}\n}\nComponent._buildAccessors(AnimComponent.prototype, _schema$j);\n\nclass AudioListenerComponent extends Component {\n\t\tsetCurrentListener() {\n\t\t\t\tif (this.enabled && this.entity.audiolistener && this.entity.enabled) {\n\t\t\t\t\t\tthis.system.current = this.entity;\n\t\t\t\t\t\tconst position = this.system.current.getPosition();\n\t\t\t\t\t\tthis.system.manager.listener.setPosition(position);\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis.setCurrentListener();\n\t\t}\n\t\tonDisable() {\n\t\t\t\tif (this.system.current === this.entity) {\n\t\t\t\t\t\tthis.system.current = null;\n\t\t\t\t}\n\t\t}\n}\n\nclass AudioListenerComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$i = [\n\t\t'enabled'\n];\nclass AudioListenerComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'audiolistener';\n\t\t\t\tthis.ComponentType = AudioListenerComponent;\n\t\t\t\tthis.DataType = AudioListenerComponentData;\n\t\t\t\tthis.schema = _schema$i;\n\t\t\t\tthis.manager = app.soundManager;\n\t\t\t\tthis.current = null;\n\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tproperties = [\n\t\t\t\t\t\t'enabled'\n\t\t\t\t];\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tif (this.current) {\n\t\t\t\t\t\tconst position = this.current.getPosition();\n\t\t\t\t\t\tthis.manager.listener.setPosition(position);\n\t\t\t\t\t\tconst wtm = this.current.getWorldTransform();\n\t\t\t\t\t\tthis.manager.listener.setOrientation(wtm);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t}\n}\nComponent._buildAccessors(AudioListenerComponent.prototype, _schema$i);\n\nconst BUTTON_TRANSITION_MODE_TINT = 0;\nconst BUTTON_TRANSITION_MODE_SPRITE_CHANGE = 1;\n\nconst ELEMENTTYPE_GROUP = 'group';\nconst ELEMENTTYPE_IMAGE = 'image';\nconst ELEMENTTYPE_TEXT = 'text';\nconst FITMODE_STRETCH = 'stretch';\nconst FITMODE_CONTAIN = 'contain';\nconst FITMODE_COVER = 'cover';\n\nconst VisualState = {\n\t\tDEFAULT: 'DEFAULT',\n\t\tHOVER: 'HOVER',\n\t\tPRESSED: 'PRESSED',\n\t\tINACTIVE: 'INACTIVE'\n};\nconst STATES_TO_TINT_NAMES = {};\nSTATES_TO_TINT_NAMES[VisualState.DEFAULT] = '_defaultTint';\nSTATES_TO_TINT_NAMES[VisualState.HOVER] = 'hoverTint';\nSTATES_TO_TINT_NAMES[VisualState.PRESSED] = 'pressedTint';\nSTATES_TO_TINT_NAMES[VisualState.INACTIVE] = 'inactiveTint';\nconst STATES_TO_SPRITE_ASSET_NAMES = {};\nSTATES_TO_SPRITE_ASSET_NAMES[VisualState.DEFAULT] = '_defaultSpriteAsset';\nSTATES_TO_SPRITE_ASSET_NAMES[VisualState.HOVER] = 'hoverSpriteAsset';\nSTATES_TO_SPRITE_ASSET_NAMES[VisualState.PRESSED] = 'pressedSpriteAsset';\nSTATES_TO_SPRITE_ASSET_NAMES[VisualState.INACTIVE] = 'inactiveSpriteAsset';\nconst STATES_TO_SPRITE_FRAME_NAMES = {};\nSTATES_TO_SPRITE_FRAME_NAMES[VisualState.DEFAULT] = '_defaultSpriteFrame';\nSTATES_TO_SPRITE_FRAME_NAMES[VisualState.HOVER] = 'hoverSpriteFrame';\nSTATES_TO_SPRITE_FRAME_NAMES[VisualState.PRESSED] = 'pressedSpriteFrame';\nSTATES_TO_SPRITE_FRAME_NAMES[VisualState.INACTIVE] = 'inactiveSpriteFrame';\nclass ButtonComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEDOWN = 'mousedown';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEUP = 'mouseup';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEENTER = 'mouseenter';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSELEAVE = 'mouseleave';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_CLICK = 'click';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHSTART = 'touchstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHEND = 'touchend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHCANCEL = 'touchcancel';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHLEAVE = 'touchleave';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTSTART = 'selectstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTEND = 'selectend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTENTER = 'selectenter';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTLEAVE = 'selectleave';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_HOVERSTART = 'hoverstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_HOVEREND = 'hoverend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PRESSEDSTART = 'pressedstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PRESSEDEND = 'pressedend';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._visualState = VisualState.DEFAULT, this._isHovering = false, this._hoveringCounter = 0, this._isPressed = false, this._defaultTint = new Color(1, 1, 1, 1), this._defaultSpriteAsset = null, this._defaultSpriteFrame = 0, this._imageEntity = null, this._evtElementAdd = null, this._evtImageEntityElementAdd = null, this._evtImageEntityElementRemove = null, this._evtImageEntityElementColor = null, this._evtImageEntityElementOpacity = null, this._evtImageEntityElementSpriteAsset = null, this._evtImageEntityElementSpriteFrame = null;\n\t\t\t\tthis._visualState = VisualState.DEFAULT;\n\t\t\t\tthis._isHovering = false;\n\t\t\t\tthis._hoveringCounter = 0;\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._defaultTint = new Color(1, 1, 1, 1);\n\t\t\t\tthis._defaultSpriteAsset = null;\n\t\t\t\tthis._defaultSpriteFrame = 0;\n\t\t\t\tthis._toggleLifecycleListeners('on', system);\n\t\t}\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(arg) {\n\t\t\t\tthis._setValue('enabled', arg);\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this.data.enabled;\n\t\t}\n\t\tset active(arg) {\n\t\t\t\tthis._setValue('active', arg);\n\t\t}\n\t\tget active() {\n\t\t\t\treturn this.data.active;\n\t\t}\n\t\tset imageEntity(arg) {\n\t\t\t\tif (this._imageEntity !== arg) {\n\t\t\t\t\t\tconst isString = typeof arg === 'string';\n\t\t\t\t\t\tif (this._imageEntity && isString && this._imageEntity.getGuid() === arg) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._imageEntity) {\n\t\t\t\t\t\t\t\tthis._imageEntityUnsubscribe();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (arg instanceof GraphNode) {\n\t\t\t\t\t\t\t\tthis._imageEntity = arg;\n\t\t\t\t\t\t} else if (isString) {\n\t\t\t\t\t\t\t\tthis._imageEntity = this.system.app.getEntityFromIndex(arg) || null;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._imageEntity = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._imageEntity) {\n\t\t\t\t\t\t\t\tthis._imageEntitySubscribe();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._imageEntity) {\n\t\t\t\t\t\t\t\tthis.data.imageEntity = this._imageEntity.getGuid();\n\t\t\t\t\t\t} else if (isString && arg) {\n\t\t\t\t\t\t\t\tthis.data.imageEntity = arg;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget imageEntity() {\n\t\t\t\treturn this._imageEntity;\n\t\t}\n\t\tset hitPadding(arg) {\n\t\t\t\tthis._setValue('hitPadding', arg);\n\t\t}\n\t\tget hitPadding() {\n\t\t\t\treturn this.data.hitPadding;\n\t\t}\n\t\tset transitionMode(arg) {\n\t\t\t\tthis._setValue('transitionMode', arg);\n\t\t}\n\t\tget transitionMode() {\n\t\t\t\treturn this.data.transitionMode;\n\t\t}\n\t\tset hoverTint(arg) {\n\t\t\t\tthis._setValue('hoverTint', arg);\n\t\t}\n\t\tget hoverTint() {\n\t\t\t\treturn this.data.hoverTint;\n\t\t}\n\t\tset pressedTint(arg) {\n\t\t\t\tthis._setValue('pressedTint', arg);\n\t\t}\n\t\tget pressedTint() {\n\t\t\t\treturn this.data.pressedTint;\n\t\t}\n\t\tset inactiveTint(arg) {\n\t\t\t\tthis._setValue('inactiveTint', arg);\n\t\t}\n\t\tget inactiveTint() {\n\t\t\t\treturn this.data.inactiveTint;\n\t\t}\n\t\tset fadeDuration(arg) {\n\t\t\t\tthis._setValue('fadeDuration', arg);\n\t\t}\n\t\tget fadeDuration() {\n\t\t\t\treturn this.data.fadeDuration;\n\t\t}\n\t\tset hoverSpriteAsset(arg) {\n\t\t\t\tthis._setValue('hoverSpriteAsset', arg);\n\t\t}\n\t\tget hoverSpriteAsset() {\n\t\t\t\treturn this.data.hoverSpriteAsset;\n\t\t}\n\t\tset hoverSpriteFrame(arg) {\n\t\t\t\tthis._setValue('hoverSpriteFrame', arg);\n\t\t}\n\t\tget hoverSpriteFrame() {\n\t\t\t\treturn this.data.hoverSpriteFrame;\n\t\t}\n\t\tset pressedSpriteAsset(arg) {\n\t\t\t\tthis._setValue('pressedSpriteAsset', arg);\n\t\t}\n\t\tget pressedSpriteAsset() {\n\t\t\t\treturn this.data.pressedSpriteAsset;\n\t\t}\n\t\tset pressedSpriteFrame(arg) {\n\t\t\t\tthis._setValue('pressedSpriteFrame', arg);\n\t\t}\n\t\tget pressedSpriteFrame() {\n\t\t\t\treturn this.data.pressedSpriteFrame;\n\t\t}\n\t\tset inactiveSpriteAsset(arg) {\n\t\t\t\tthis._setValue('inactiveSpriteAsset', arg);\n\t\t}\n\t\tget inactiveSpriteAsset() {\n\t\t\t\treturn this.data.inactiveSpriteAsset;\n\t\t}\n\t\tset inactiveSpriteFrame(arg) {\n\t\t\t\tthis._setValue('inactiveSpriteFrame', arg);\n\t\t}\n\t\tget inactiveSpriteFrame() {\n\t\t\t\treturn this.data.inactiveSpriteFrame;\n\t\t}\n\t\t_setValue(name, value) {\n\t\t\t\tconst data = this.data;\n\t\t\t\tconst oldValue = data[name];\n\t\t\t\tdata[name] = value;\n\t\t\t\tthis.fire('set', name, oldValue, value);\n\t\t}\n\t\t_toggleLifecycleListeners(onOrOff, system) {\n\t\t\t\tthis[onOrOff]('set_active', this._onSetActive, this);\n\t\t\t\tthis[onOrOff]('set_transitionMode', this._onSetTransitionMode, this);\n\t\t\t\tthis[onOrOff]('set_hoverTint', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_pressedTint', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_inactiveTint', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_hoverSpriteAsset', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_hoverSpriteFrame', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_pressedSpriteAsset', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_pressedSpriteFrame', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_inactiveSpriteAsset', this._onSetTransitionValue, this);\n\t\t\t\tthis[onOrOff]('set_inactiveSpriteFrame', this._onSetTransitionValue, this);\n\t\t\t\tif (onOrOff === 'on') {\n\t\t\t\t\t\tthis._evtElementAdd = this.entity.on('element:add', this._onElementComponentAdd, this);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._evtElementAdd?.off();\n\t\t\t\t\t\tthis._evtElementAdd = null;\n\t\t\t\t}\n\t\t}\n\t\t_onSetActive(name, oldValue, newValue) {\n\t\t\t\tif (oldValue !== newValue) {\n\t\t\t\t\t\tthis._updateVisualState();\n\t\t\t\t}\n\t\t}\n\t\t_onSetTransitionMode(name, oldValue, newValue) {\n\t\t\t\tif (oldValue !== newValue) {\n\t\t\t\t\t\tthis._cancelTween();\n\t\t\t\t\t\tthis._resetToDefaultVisualState(oldValue);\n\t\t\t\t\t\tthis._forceReapplyVisualState();\n\t\t\t\t}\n\t\t}\n\t\t_onSetTransitionValue(name, oldValue, newValue) {\n\t\t\t\tif (oldValue !== newValue) {\n\t\t\t\t\t\tthis._forceReapplyVisualState();\n\t\t\t\t}\n\t\t}\n\t\t_imageEntitySubscribe() {\n\t\t\t\tthis._evtImageEntityElementAdd = this._imageEntity.on('element:add', this._onImageElementGain, this);\n\t\t\t\tif (this._imageEntity.element) {\n\t\t\t\t\t\tthis._onImageElementGain();\n\t\t\t\t}\n\t\t}\n\t\t_imageEntityUnsubscribe() {\n\t\t\t\tthis._evtImageEntityElementAdd?.off();\n\t\t\t\tthis._evtImageEntityElementAdd = null;\n\t\t\t\tif (this._imageEntity?.element) {\n\t\t\t\t\t\tthis._onImageElementLose();\n\t\t\t\t}\n\t\t}\n\t\t_imageEntityElementSubscribe() {\n\t\t\t\tconst element = this._imageEntity.element;\n\t\t\t\tthis._evtImageEntityElementRemove = element.once('beforeremove', this._onImageElementLose, this);\n\t\t\t\tthis._evtImageEntityElementColor = element.on('set:color', this._onSetColor, this);\n\t\t\t\tthis._evtImageEntityElementOpacity = element.on('set:opacity', this._onSetOpacity, this);\n\t\t\t\tthis._evtImageEntityElementSpriteAsset = element.on('set:spriteAsset', this._onSetSpriteAsset, this);\n\t\t\t\tthis._evtImageEntityElementSpriteFrame = element.on('set:spriteFrame', this._onSetSpriteFrame, this);\n\t\t}\n\t\t_imageEntityElementUnsubscribe() {\n\t\t\t\tthis._evtImageEntityElementRemove?.off();\n\t\t\t\tthis._evtImageEntityElementRemove = null;\n\t\t\t\tthis._evtImageEntityElementColor?.off();\n\t\t\t\tthis._evtImageEntityElementColor = null;\n\t\t\t\tthis._evtImageEntityElementOpacity?.off();\n\t\t\t\tthis._evtImageEntityElementOpacity = null;\n\t\t\t\tthis._evtImageEntityElementSpriteAsset?.off();\n\t\t\t\tthis._evtImageEntityElementSpriteAsset = null;\n\t\t\t\tthis._evtImageEntityElementSpriteFrame?.off();\n\t\t\t\tthis._evtImageEntityElementSpriteFrame = null;\n\t\t}\n\t\t_onElementComponentRemove() {\n\t\t\t\tthis._toggleHitElementListeners('off');\n\t\t}\n\t\t_onElementComponentAdd() {\n\t\t\t\tthis._toggleHitElementListeners('on');\n\t\t}\n\t\t_onImageElementLose() {\n\t\t\t\tthis._imageEntityElementUnsubscribe();\n\t\t\t\tthis._cancelTween();\n\t\t\t\tthis._resetToDefaultVisualState(this.transitionMode);\n\t\t}\n\t\t_onImageElementGain() {\n\t\t\t\tthis._imageEntityElementSubscribe();\n\t\t\t\tthis._storeDefaultVisualState();\n\t\t\t\tthis._forceReapplyVisualState();\n\t\t}\n\t\t_toggleHitElementListeners(onOrOff) {\n\t\t\t\tif (this.entity.element) {\n\t\t\t\t\t\tconst isAdding = onOrOff === 'on';\n\t\t\t\t\t\tif (isAdding && this._hasHitElementListeners) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.entity.element[onOrOff]('beforeremove', this._onElementComponentRemove, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('mouseenter', this._onMouseEnter, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('mouseleave', this._onMouseLeave, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('mousedown', this._onMouseDown, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('mouseup', this._onMouseUp, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('touchstart', this._onTouchStart, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('touchend', this._onTouchEnd, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('touchleave', this._onTouchLeave, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('touchcancel', this._onTouchCancel, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('selectstart', this._onSelectStart, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('selectend', this._onSelectEnd, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('selectenter', this._onSelectEnter, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('selectleave', this._onSelectLeave, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('click', this._onClick, this);\n\t\t\t\t\t\tthis._hasHitElementListeners = isAdding;\n\t\t\t\t}\n\t\t}\n\t\t_storeDefaultVisualState() {\n\t\t\t\tconst element = this._imageEntity?.element;\n\t\t\t\tif (!element || element.type === ELEMENTTYPE_GROUP) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._storeDefaultColor(element.color);\n\t\t\t\tthis._storeDefaultOpacity(element.opacity);\n\t\t\t\tthis._storeDefaultSpriteAsset(element.spriteAsset);\n\t\t\t\tthis._storeDefaultSpriteFrame(element.spriteFrame);\n\t\t}\n\t\t_storeDefaultColor(color) {\n\t\t\t\tthis._defaultTint.r = color.r;\n\t\t\t\tthis._defaultTint.g = color.g;\n\t\t\t\tthis._defaultTint.b = color.b;\n\t\t}\n\t\t_storeDefaultOpacity(opacity) {\n\t\t\t\tthis._defaultTint.a = opacity;\n\t\t}\n\t\t_storeDefaultSpriteAsset(spriteAsset) {\n\t\t\t\tthis._defaultSpriteAsset = spriteAsset;\n\t\t}\n\t\t_storeDefaultSpriteFrame(spriteFrame) {\n\t\t\t\tthis._defaultSpriteFrame = spriteFrame;\n\t\t}\n\t\t_onSetColor(color) {\n\t\t\t\tif (!this._isApplyingTint) {\n\t\t\t\t\t\tthis._storeDefaultColor(color);\n\t\t\t\t\t\tthis._forceReapplyVisualState();\n\t\t\t\t}\n\t\t}\n\t\t_onSetOpacity(opacity) {\n\t\t\t\tif (!this._isApplyingTint) {\n\t\t\t\t\t\tthis._storeDefaultOpacity(opacity);\n\t\t\t\t\t\tthis._forceReapplyVisualState();\n\t\t\t\t}\n\t\t}\n\t\t_onSetSpriteAsset(spriteAsset) {\n\t\t\t\tif (!this._isApplyingSprite) {\n\t\t\t\t\t\tthis._storeDefaultSpriteAsset(spriteAsset);\n\t\t\t\t\t\tthis._forceReapplyVisualState();\n\t\t\t\t}\n\t\t}\n\t\t_onSetSpriteFrame(spriteFrame) {\n\t\t\t\tif (!this._isApplyingSprite) {\n\t\t\t\t\t\tthis._storeDefaultSpriteFrame(spriteFrame);\n\t\t\t\t\t\tthis._forceReapplyVisualState();\n\t\t\t\t}\n\t\t}\n\t\t_onMouseEnter(event) {\n\t\t\t\tthis._isHovering = true;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('mouseenter', event);\n\t\t}\n\t\t_onMouseLeave(event) {\n\t\t\t\tthis._isHovering = false;\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('mouseleave', event);\n\t\t}\n\t\t_onMouseDown(event) {\n\t\t\t\tthis._isPressed = true;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('mousedown', event);\n\t\t}\n\t\t_onMouseUp(event) {\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('mouseup', event);\n\t\t}\n\t\t_onTouchStart(event) {\n\t\t\t\tthis._isPressed = true;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('touchstart', event);\n\t\t}\n\t\t_onTouchEnd(event) {\n\t\t\t\tevent.event.preventDefault();\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('touchend', event);\n\t\t}\n\t\t_onTouchLeave(event) {\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('touchleave', event);\n\t\t}\n\t\t_onTouchCancel(event) {\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('touchcancel', event);\n\t\t}\n\t\t_onSelectStart(event) {\n\t\t\t\tthis._isPressed = true;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('selectstart', event);\n\t\t}\n\t\t_onSelectEnd(event) {\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._updateVisualState();\n\t\t\t\tthis._fireIfActive('selectend', event);\n\t\t}\n\t\t_onSelectEnter(event) {\n\t\t\t\tthis._hoveringCounter++;\n\t\t\t\tif (this._hoveringCounter === 1) {\n\t\t\t\t\t\tthis._isHovering = true;\n\t\t\t\t\t\tthis._updateVisualState();\n\t\t\t\t}\n\t\t\t\tthis._fireIfActive('selectenter', event);\n\t\t}\n\t\t_onSelectLeave(event) {\n\t\t\t\tthis._hoveringCounter--;\n\t\t\t\tif (this._hoveringCounter === 0) {\n\t\t\t\t\t\tthis._isHovering = false;\n\t\t\t\t\t\tthis._isPressed = false;\n\t\t\t\t\t\tthis._updateVisualState();\n\t\t\t\t}\n\t\t\t\tthis._fireIfActive('selectleave', event);\n\t\t}\n\t\t_onClick(event) {\n\t\t\t\tthis._fireIfActive('click', event);\n\t\t}\n\t\t_fireIfActive(name, event) {\n\t\t\t\tif (this.data.active) {\n\t\t\t\t\t\tthis.fire(name, event);\n\t\t\t\t}\n\t\t}\n\t\t_updateVisualState(force) {\n\t\t\t\tconst oldVisualState = this._visualState;\n\t\t\t\tconst newVisualState = this._determineVisualState();\n\t\t\t\tif ((oldVisualState !== newVisualState || force) && this.enabled) {\n\t\t\t\t\t\tthis._visualState = newVisualState;\n\t\t\t\t\t\tif (oldVisualState === VisualState.HOVER) {\n\t\t\t\t\t\t\t\tthis._fireIfActive('hoverend');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (oldVisualState === VisualState.PRESSED) {\n\t\t\t\t\t\t\t\tthis._fireIfActive('pressedend');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (newVisualState === VisualState.HOVER) {\n\t\t\t\t\t\t\t\tthis._fireIfActive('hoverstart');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (newVisualState === VisualState.PRESSED) {\n\t\t\t\t\t\t\t\tthis._fireIfActive('pressedstart');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tswitch(this.transitionMode){\n\t\t\t\t\t\t\t\tcase BUTTON_TRANSITION_MODE_TINT:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tconst tintName = STATES_TO_TINT_NAMES[this._visualState];\n\t\t\t\t\t\t\t\t\t\t\t\tconst tintColor = this[tintName];\n\t\t\t\t\t\t\t\t\t\t\t\tthis._applyTint(tintColor);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase BUTTON_TRANSITION_MODE_SPRITE_CHANGE:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tconst spriteAssetName = STATES_TO_SPRITE_ASSET_NAMES[this._visualState];\n\t\t\t\t\t\t\t\t\t\t\t\tconst spriteFrameName = STATES_TO_SPRITE_FRAME_NAMES[this._visualState];\n\t\t\t\t\t\t\t\t\t\t\t\tconst spriteAsset = this[spriteAssetName];\n\t\t\t\t\t\t\t\t\t\t\t\tconst spriteFrame = this[spriteFrameName];\n\t\t\t\t\t\t\t\t\t\t\t\tthis._applySprite(spriteAsset, spriteFrame);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_forceReapplyVisualState() {\n\t\t\t\tthis._updateVisualState(true);\n\t\t}\n\t\t_resetToDefaultVisualState(transitionMode) {\n\t\t\t\tif (!this._imageEntity?.element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tswitch(transitionMode){\n\t\t\t\t\t\tcase BUTTON_TRANSITION_MODE_TINT:\n\t\t\t\t\t\t\t\tthis._cancelTween();\n\t\t\t\t\t\t\t\tthis._applyTintImmediately(this._defaultTint);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase BUTTON_TRANSITION_MODE_SPRITE_CHANGE:\n\t\t\t\t\t\t\t\tthis._applySprite(this._defaultSpriteAsset, this._defaultSpriteFrame);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\t_determineVisualState() {\n\t\t\t\tif (!this.active) {\n\t\t\t\t\t\treturn VisualState.INACTIVE;\n\t\t\t\t} else if (this._isPressed) {\n\t\t\t\t\t\treturn VisualState.PRESSED;\n\t\t\t\t} else if (this._isHovering) {\n\t\t\t\t\t\treturn VisualState.HOVER;\n\t\t\t\t}\n\t\t\t\treturn VisualState.DEFAULT;\n\t\t}\n\t\t_applySprite(spriteAsset, spriteFrame) {\n\t\t\t\tconst element = this._imageEntity?.element;\n\t\t\t\tif (!element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tspriteFrame = spriteFrame || 0;\n\t\t\t\tthis._isApplyingSprite = true;\n\t\t\t\tif (element.spriteAsset !== spriteAsset) {\n\t\t\t\t\t\telement.spriteAsset = spriteAsset;\n\t\t\t\t}\n\t\t\t\tif (element.spriteFrame !== spriteFrame) {\n\t\t\t\t\t\telement.spriteFrame = spriteFrame;\n\t\t\t\t}\n\t\t\t\tthis._isApplyingSprite = false;\n\t\t}\n\t\t_applyTint(tintColor) {\n\t\t\t\tthis._cancelTween();\n\t\t\t\tif (this.fadeDuration === 0) {\n\t\t\t\t\t\tthis._applyTintImmediately(tintColor);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._applyTintWithTween(tintColor);\n\t\t\t\t}\n\t\t}\n\t\t_applyTintImmediately(tintColor) {\n\t\t\t\tconst element = this._imageEntity?.element;\n\t\t\t\tif (!tintColor || !element || element.type === ELEMENTTYPE_GROUP) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst color3 = toColor3(tintColor);\n\t\t\t\tthis._isApplyingTint = true;\n\t\t\t\tif (!color3.equals(element.color)) {\n\t\t\t\t\t\telement.color = color3;\n\t\t\t\t}\n\t\t\t\tif (element.opacity !== tintColor.a) {\n\t\t\t\t\t\telement.opacity = tintColor.a;\n\t\t\t\t}\n\t\t\t\tthis._isApplyingTint = false;\n\t\t}\n\t\t_applyTintWithTween(tintColor) {\n\t\t\t\tconst element = this._imageEntity?.element;\n\t\t\t\tif (!tintColor || !element || element.type === ELEMENTTYPE_GROUP) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst color3 = toColor3(tintColor);\n\t\t\t\tconst color = element.color;\n\t\t\t\tconst opacity = element.opacity;\n\t\t\t\tif (color3.equals(color) && tintColor.a === opacity) return;\n\t\t\t\tthis._tweenInfo = {\n\t\t\t\t\t\tstartTime: now(),\n\t\t\t\t\t\tfrom: new Color(color.r, color.g, color.b, opacity),\n\t\t\t\t\t\tto: tintColor.clone(),\n\t\t\t\t\t\tlerpColor: new Color()\n\t\t\t\t};\n\t\t}\n\t\t_updateTintTween() {\n\t\t\t\tconst elapsedTime = now() - this._tweenInfo.startTime;\n\t\t\t\tlet elapsedProportion = this.fadeDuration === 0 ? 1 : elapsedTime / this.fadeDuration;\n\t\t\t\telapsedProportion = math.clamp(elapsedProportion, 0, 1);\n\t\t\t\tif (Math.abs(elapsedProportion - 1) > 1e-5) {\n\t\t\t\t\t\tconst lerpColor = this._tweenInfo.lerpColor;\n\t\t\t\t\t\tlerpColor.lerp(this._tweenInfo.from, this._tweenInfo.to, elapsedProportion);\n\t\t\t\t\t\tthis._applyTintImmediately(new Color(lerpColor.r, lerpColor.g, lerpColor.b, lerpColor.a));\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._applyTintImmediately(this._tweenInfo.to);\n\t\t\t\t\t\tthis._cancelTween();\n\t\t\t\t}\n\t\t}\n\t\t_cancelTween() {\n\t\t\t\tdelete this._tweenInfo;\n\t\t}\n\t\tonUpdate() {\n\t\t\t\tif (this._tweenInfo) {\n\t\t\t\t\t\tthis._updateTintTween();\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._isHovering = false;\n\t\t\t\tthis._hoveringCounter = 0;\n\t\t\t\tthis._isPressed = false;\n\t\t\t\tthis._toggleHitElementListeners('on');\n\t\t\t\tthis._forceReapplyVisualState();\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._toggleHitElementListeners('off');\n\t\t\t\tthis._resetToDefaultVisualState(this.transitionMode);\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis._imageEntityUnsubscribe();\n\t\t\t\tthis._toggleLifecycleListeners('off', this.system);\n\t\t\t\tthis.onDisable();\n\t\t}\n\t\tresolveDuplicatedEntityReferenceProperties(oldButton, duplicatedIdsMap) {\n\t\t\t\tif (oldButton.imageEntity) {\n\t\t\t\t\t\tthis.imageEntity = duplicatedIdsMap[oldButton.imageEntity.getGuid()];\n\t\t\t\t}\n\t\t}\n}\nfunction toColor3(color4) {\n\t\treturn new Color(color4.r, color4.g, color4.b);\n}\n\nclass ButtonComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t\t\tthis.active = true;\n\t\t\t\tthis.imageEntity = null;\n\t\t\t\tthis.hitPadding = new Vec4();\n\t\t\t\tthis.transitionMode = BUTTON_TRANSITION_MODE_TINT;\n\t\t\t\tthis.hoverTint = new Color(0.75, 0.75, 0.75);\n\t\t\t\tthis.pressedTint = new Color(0.5, 0.5, 0.5);\n\t\t\t\tthis.inactiveTint = new Color(0.25, 0.25, 0.25);\n\t\t\t\tthis.fadeDuration = 0;\n\t\t\t\tthis.hoverSpriteAsset = null;\n\t\t\t\tthis.hoverSpriteFrame = 0;\n\t\t\t\tthis.pressedSpriteAsset = null;\n\t\t\t\tthis.pressedSpriteFrame = 0;\n\t\t\t\tthis.inactiveSpriteAsset = null;\n\t\t\t\tthis.inactiveSpriteFrame = 0;\n\t\t}\n}\n\nconst _schema$h = [\n\t\t'enabled',\n\t\t'active',\n\t\t{\n\t\t\t\tname: 'hitPadding',\n\t\t\t\ttype: 'vec4'\n\t\t},\n\t\t'transitionMode',\n\t\t{\n\t\t\t\tname: 'hoverTint',\n\t\t\t\ttype: 'rgba'\n\t\t},\n\t\t{\n\t\t\t\tname: 'pressedTint',\n\t\t\t\ttype: 'rgba'\n\t\t},\n\t\t{\n\t\t\t\tname: 'inactiveTint',\n\t\t\t\ttype: 'rgba'\n\t\t},\n\t\t'fadeDuration',\n\t\t'hoverSpriteAsset',\n\t\t'hoverSpriteFrame',\n\t\t'pressedSpriteAsset',\n\t\t'pressedSpriteFrame',\n\t\t'inactiveSpriteAsset',\n\t\t'inactiveSpriteFrame'\n];\nclass ButtonComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'button';\n\t\t\t\tthis.ComponentType = ButtonComponent;\n\t\t\t\tthis.DataType = ButtonComponentData;\n\t\t\t\tthis.schema = _schema$h;\n\t\t\t\tthis.on('beforeremove', this._onRemoveComponent, this);\n\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tcomponent.imageEntity = data.imageEntity;\n\t\t\t\tsuper.initializeComponentData(component, data, _schema$h);\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tconst components = this.store;\n\t\t\t\tfor(const id in components){\n\t\t\t\t\t\tconst entity = components[id].entity;\n\t\t\t\t\t\tconst component = entity.button;\n\t\t\t\t\t\tif (component.enabled && entity.enabled) {\n\t\t\t\t\t\t\t\tcomponent.onUpdate();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onRemoveComponent(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t}\n}\n\nconst _vec3$1 = new Vec3();\nconst _quat = new Quat();\nclass CollisionComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_CONTACT = 'contact';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_COLLISIONSTART = 'collisionstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_COLLISIONEND = 'collisionend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TRIGGERENTER = 'triggerenter';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TRIGGERLEAVE = 'triggerleave';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._compoundParent = null, this._hasOffset = false;\n\t\t\t\tthis.entity.on('insert', this._onInsert, this);\n\t\t\t\tthis.on('set_type', this.onSetType, this);\n\t\t\t\tthis.on('set_convexHull', this.onSetModel, this);\n\t\t\t\tthis.on('set_halfExtents', this.onSetHalfExtents, this);\n\t\t\t\tthis.on('set_linearOffset', this.onSetOffset, this);\n\t\t\t\tthis.on('set_angularOffset', this.onSetOffset, this);\n\t\t\t\tthis.on('set_radius', this.onSetRadius, this);\n\t\t\t\tthis.on('set_height', this.onSetHeight, this);\n\t\t\t\tthis.on('set_axis', this.onSetAxis, this);\n\t\t\t\tthis.on('set_asset', this.onSetAsset, this);\n\t\t\t\tthis.on('set_renderAsset', this.onSetRenderAsset, this);\n\t\t\t\tthis.on('set_model', this.onSetModel, this);\n\t\t\t\tthis.on('set_render', this.onSetRender, this);\n\t\t}\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(arg) {\n\t\t\t\tthis._setValue('enabled', arg);\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this.data.enabled;\n\t\t}\n\t\tset type(arg) {\n\t\t\t\tthis._setValue('type', arg);\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this.data.type;\n\t\t}\n\t\tset halfExtents(arg) {\n\t\t\t\tthis._setValue('halfExtents', arg);\n\t\t}\n\t\tget halfExtents() {\n\t\t\t\treturn this.data.halfExtents;\n\t\t}\n\t\tset linearOffset(arg) {\n\t\t\t\tthis._setValue('linearOffset', arg);\n\t\t}\n\t\tget linearOffset() {\n\t\t\t\treturn this.data.linearOffset;\n\t\t}\n\t\tset angularOffset(arg) {\n\t\t\t\tthis._setValue('angularOffset', arg);\n\t\t}\n\t\tget angularOffset() {\n\t\t\t\treturn this.data.angularOffset;\n\t\t}\n\t\tset radius(arg) {\n\t\t\t\tthis._setValue('radius', arg);\n\t\t}\n\t\tget radius() {\n\t\t\t\treturn this.data.radius;\n\t\t}\n\t\tset axis(arg) {\n\t\t\t\tthis._setValue('axis', arg);\n\t\t}\n\t\tget axis() {\n\t\t\t\treturn this.data.axis;\n\t\t}\n\t\tset height(arg) {\n\t\t\t\tthis._setValue('height', arg);\n\t\t}\n\t\tget height() {\n\t\t\t\treturn this.data.height;\n\t\t}\n\t\tset asset(arg) {\n\t\t\t\tthis._setValue('asset', arg);\n\t\t}\n\t\tget asset() {\n\t\t\t\treturn this.data.asset;\n\t\t}\n\t\tset renderAsset(arg) {\n\t\t\t\tthis._setValue('renderAsset', arg);\n\t\t}\n\t\tget renderAsset() {\n\t\t\t\treturn this.data.renderAsset;\n\t\t}\n\t\tset convexHull(arg) {\n\t\t\t\tthis._setValue('convexHull', arg);\n\t\t}\n\t\tget convexHull() {\n\t\t\t\treturn this.data.convexHull;\n\t\t}\n\t\tset shape(arg) {\n\t\t\t\tthis._setValue('shape', arg);\n\t\t}\n\t\tget shape() {\n\t\t\t\treturn this.data.shape;\n\t\t}\n\t\tset model(arg) {\n\t\t\t\tthis._setValue('model', arg);\n\t\t}\n\t\tget model() {\n\t\t\t\treturn this.data.model;\n\t\t}\n\t\tset render(arg) {\n\t\t\t\tthis._setValue('render', arg);\n\t\t}\n\t\tget render() {\n\t\t\t\treturn this.data.render;\n\t\t}\n\t\tset checkVertexDuplicates(arg) {\n\t\t\t\tthis._setValue('checkVertexDuplicates', arg);\n\t\t}\n\t\tget checkVertexDuplicates() {\n\t\t\t\treturn this.data.checkVertexDuplicates;\n\t\t}\n\t\t_setValue(name, value) {\n\t\t\t\tconst data = this.data;\n\t\t\t\tconst oldValue = data[name];\n\t\t\t\tdata[name] = value;\n\t\t\t\tthis.fire('set', name, oldValue, value);\n\t\t}\n\t\tonSetType(name, oldValue, newValue) {\n\t\t\t\tif (oldValue !== newValue) {\n\t\t\t\t\t\tthis.system.changeType(this, oldValue, newValue);\n\t\t\t\t}\n\t\t}\n\t\tonSetHalfExtents(name, oldValue, newValue) {\n\t\t\t\tconst t = this.data.type;\n\t\t\t\tif (this.data.initialized && t === 'box') {\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetOffset(name, oldValue, newValue) {\n\t\t\t\tthis._hasOffset = !this.data.linearOffset.equals(Vec3.ZERO) || !this.data.angularOffset.equals(Quat.IDENTITY);\n\t\t\t\tif (this.data.initialized) {\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetRadius(name, oldValue, newValue) {\n\t\t\t\tconst t = this.data.type;\n\t\t\t\tif (this.data.initialized && (t === 'sphere' || t === 'capsule' || t === 'cylinder' || t === 'cone')) {\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetHeight(name, oldValue, newValue) {\n\t\t\t\tconst t = this.data.type;\n\t\t\t\tif (this.data.initialized && (t === 'capsule' || t === 'cylinder' || t === 'cone')) {\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetAxis(name, oldValue, newValue) {\n\t\t\t\tconst t = this.data.type;\n\t\t\t\tif (this.data.initialized && (t === 'capsule' || t === 'cylinder' || t === 'cone')) {\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetAsset(name, oldValue, newValue) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (oldValue) {\n\t\t\t\t\t\tconst asset = assets.get(oldValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tasset.off('remove', this.onAssetRemoved, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (newValue) {\n\t\t\t\t\t\tif (newValue instanceof Asset) {\n\t\t\t\t\t\t\t\tthis.data.asset = newValue.id;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst asset = assets.get(this.data.asset);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tasset.off('remove', this.onAssetRemoved, this);\n\t\t\t\t\t\t\t\tasset.on('remove', this.onAssetRemoved, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.data.initialized && this.data.type === 'mesh') {\n\t\t\t\t\t\tif (!newValue) {\n\t\t\t\t\t\t\t\tthis.data.model = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetRenderAsset(name, oldValue, newValue) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (oldValue) {\n\t\t\t\t\t\tconst asset = assets.get(oldValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tasset.off('remove', this.onRenderAssetRemoved, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (newValue) {\n\t\t\t\t\t\tif (newValue instanceof Asset) {\n\t\t\t\t\t\t\t\tthis.data.renderAsset = newValue.id;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst asset = assets.get(this.data.renderAsset);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tasset.off('remove', this.onRenderAssetRemoved, this);\n\t\t\t\t\t\t\t\tasset.on('remove', this.onRenderAssetRemoved, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.data.initialized && this.data.type === 'mesh') {\n\t\t\t\t\t\tif (!newValue) {\n\t\t\t\t\t\t\t\tthis.data.render = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetModel(name, oldValue, newValue) {\n\t\t\t\tif (this.data.initialized && this.data.type === 'mesh') {\n\t\t\t\t\t\tthis.system.implementations.mesh.doRecreatePhysicalShape(this);\n\t\t\t\t}\n\t\t}\n\t\tonSetRender(name, oldValue, newValue) {\n\t\t\t\tthis.onSetModel(name, oldValue, newValue);\n\t\t}\n\t\tonAssetRemoved(asset) {\n\t\t\t\tasset.off('remove', this.onAssetRemoved, this);\n\t\t\t\tif (this.data.asset === asset.id) {\n\t\t\t\t\t\tthis.asset = null;\n\t\t\t\t}\n\t\t}\n\t\tonRenderAssetRemoved(asset) {\n\t\t\t\tasset.off('remove', this.onRenderAssetRemoved, this);\n\t\t\t\tif (this.data.renderAsset === asset.id) {\n\t\t\t\t\t\tthis.renderAsset = null;\n\t\t\t\t}\n\t\t}\n\t\tgetCompoundChildShapeIndex(shape) {\n\t\t\t\tconst compound = this.data.shape;\n\t\t\t\tconst shapes = compound.getNumChildShapes();\n\t\t\t\tfor(let i = 0; i < shapes; i++){\n\t\t\t\t\t\tconst childShape = compound.getChildShape(i);\n\t\t\t\t\t\tif (Ammo.getPointer(childShape) === Ammo.getPointer(shape)) {\n\t\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\t_onInsert(parent) {\n\t\t\t\tif (typeof Ammo === 'undefined') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._compoundParent) {\n\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t} else if (!this.entity.rigidbody) {\n\t\t\t\t\t\tlet ancestor = this.entity.parent;\n\t\t\t\t\t\twhile(ancestor){\n\t\t\t\t\t\t\t\tif (ancestor.collision && ancestor.collision.type === 'compound') {\n\t\t\t\t\t\t\t\t\t\tif (ancestor.collision.shape.getNumChildShapes() === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system.recreatePhysicalShapes(ancestor.collision);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tancestor = ancestor.parent;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateCompound() {\n\t\t\t\tconst entity = this.entity;\n\t\t\t\tif (entity._dirtyWorld) {\n\t\t\t\t\t\tlet dirty = entity._dirtyLocal;\n\t\t\t\t\t\tlet parent = entity;\n\t\t\t\t\t\twhile(parent && !dirty){\n\t\t\t\t\t\t\t\tif (parent.collision && parent.collision === this._compoundParent) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (parent._dirtyLocal) {\n\t\t\t\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tparent = parent.parent;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (dirty) {\n\t\t\t\t\t\t\t\tentity.forEach(this.system.implementations.compound._updateEachDescendantTransform, entity);\n\t\t\t\t\t\t\t\tconst bodyComponent = this._compoundParent.entity.rigidbody;\n\t\t\t\t\t\t\t\tif (bodyComponent) {\n\t\t\t\t\t\t\t\t\t\tbodyComponent.activate();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgetShapePosition() {\n\t\t\t\tconst pos = this.entity.getPosition();\n\t\t\t\tif (this._hasOffset) {\n\t\t\t\t\t\tconst rot = this.entity.getRotation();\n\t\t\t\t\t\tconst lo = this.data.linearOffset;\n\t\t\t\t\t\t_quat.copy(rot).transformVector(lo, _vec3$1);\n\t\t\t\t\t\treturn _vec3$1.add(pos);\n\t\t\t\t}\n\t\t\t\treturn pos;\n\t\t}\n\t\tgetShapeRotation() {\n\t\t\t\tconst rot = this.entity.getRotation();\n\t\t\t\tif (this._hasOffset) {\n\t\t\t\t\t\treturn _quat.copy(rot).mul(this.data.angularOffset);\n\t\t\t\t}\n\t\t\t\treturn rot;\n\t\t}\n\t\tonEnable() {\n\t\t\t\tif (this.data.type === 'mesh' && (this.data.asset || this.data.renderAsset) && this.data.initialized) {\n\t\t\t\t\t\tconst asset = this.system.app.assets.get(this.data.asset || this.data.renderAsset);\n\t\t\t\t\t\tif (asset && (!asset.resource || !this.data.shape)) {\n\t\t\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.entity.rigidbody) {\n\t\t\t\t\t\tif (this.entity.rigidbody.enabled) {\n\t\t\t\t\t\t\t\tthis.entity.rigidbody.enableSimulation();\n\t\t\t\t\t\t}\n\t\t\t\t} else if (this._compoundParent && this !== this._compoundParent) {\n\t\t\t\t\t\tif (this._compoundParent.shape.getNumChildShapes() === 0) {\n\t\t\t\t\t\t\t\tthis.system.recreatePhysicalShapes(this._compoundParent);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst transform = this.system._getNodeTransform(this.entity, this._compoundParent.entity);\n\t\t\t\t\t\t\t\tthis._compoundParent.shape.addChildShape(transform, this.data.shape);\n\t\t\t\t\t\t\t\tAmmo.destroy(transform);\n\t\t\t\t\t\t\t\tif (this._compoundParent.entity.rigidbody) {\n\t\t\t\t\t\t\t\t\t\tthis._compoundParent.entity.rigidbody.activate();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else if (this.entity.trigger) {\n\t\t\t\t\t\tthis.entity.trigger.enable();\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tif (this.entity.rigidbody) {\n\t\t\t\t\t\tthis.entity.rigidbody.disableSimulation();\n\t\t\t\t} else if (this._compoundParent && this !== this._compoundParent) {\n\t\t\t\t\t\tif (!this._compoundParent.entity._destroying) {\n\t\t\t\t\t\t\t\tthis.system._removeCompoundChild(this._compoundParent, this.data.shape);\n\t\t\t\t\t\t\t\tif (this._compoundParent.entity.rigidbody) {\n\t\t\t\t\t\t\t\t\t\tthis._compoundParent.entity.rigidbody.activate();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else if (this.entity.trigger) {\n\t\t\t\t\t\tthis.entity.trigger.disable();\n\t\t\t\t}\n\t\t}\n\t\tonBeforeRemove() {\n\t\t\t\tif (this.asset) {\n\t\t\t\t\t\tthis.asset = null;\n\t\t\t\t}\n\t\t\t\tif (this.renderAsset) {\n\t\t\t\t\t\tthis.renderAsset = null;\n\t\t\t\t}\n\t\t\t\tthis.entity.off('insert', this._onInsert, this);\n\t\t\t\tthis.off();\n\t\t}\n}\n\nclass CollisionComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t\t\tthis.type = 'box';\n\t\t\t\tthis.halfExtents = new Vec3(0.5, 0.5, 0.5);\n\t\t\t\tthis.linearOffset = new Vec3();\n\t\t\t\tthis.angularOffset = new Quat();\n\t\t\t\tthis.radius = 0.5;\n\t\t\t\tthis.axis = 1;\n\t\t\t\tthis.height = 2;\n\t\t\t\tthis.convexHull = false;\n\t\t\t\tthis.asset = null;\n\t\t\t\tthis.renderAsset = null;\n\t\t\t\tthis.checkVertexDuplicates = true;\n\t\t\t\tthis.shape = null;\n\t\t\t\tthis.model = null;\n\t\t\t\tthis.render = null;\n\t\t\t\tthis.initialized = false;\n\t\t}\n}\n\nconst BODYTYPE_STATIC = 'static';\nconst BODYTYPE_DYNAMIC = 'dynamic';\nconst BODYTYPE_KINEMATIC = 'kinematic';\nconst BODYFLAG_KINEMATIC_OBJECT = 2;\nconst BODYFLAG_NORESPONSE_OBJECT = 4;\nconst BODYSTATE_ACTIVE_TAG = 1;\nconst BODYSTATE_DISABLE_DEACTIVATION = 4;\nconst BODYSTATE_DISABLE_SIMULATION = 5;\nconst BODYGROUP_DYNAMIC = 1;\nconst BODYGROUP_STATIC = 2;\nconst BODYGROUP_KINEMATIC = 4;\nconst BODYGROUP_TRIGGER = 16;\nconst BODYMASK_ALL = 65535;\nconst BODYMASK_NOT_STATIC = 65535 ^ 2;\n\nlet _ammoVec1$1, _ammoQuat$1, _ammoTransform$1;\nclass Trigger {\n\t\tconstructor(app, component, data){\n\t\t\t\tthis.entity = component.entity;\n\t\t\t\tthis.component = component;\n\t\t\t\tthis.app = app;\n\t\t\t\tif (typeof Ammo !== 'undefined' && !_ammoVec1$1) {\n\t\t\t\t\t\t_ammoVec1$1 = new Ammo.btVector3();\n\t\t\t\t\t\t_ammoQuat$1 = new Ammo.btQuaternion();\n\t\t\t\t\t\t_ammoTransform$1 = new Ammo.btTransform();\n\t\t\t\t}\n\t\t\t\tthis.initialize(data);\n\t\t}\n\t\tinitialize(data) {\n\t\t\t\tconst entity = this.entity;\n\t\t\t\tconst shape = data.shape;\n\t\t\t\tif (shape && typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tif (entity.trigger) {\n\t\t\t\t\t\t\t\tentity.trigger.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst mass = 1;\n\t\t\t\t\t\tconst component = this.component;\n\t\t\t\t\t\tif (component) {\n\t\t\t\t\t\t\t\tconst bodyPos = component.getShapePosition();\n\t\t\t\t\t\t\t\tconst bodyRot = component.getShapeRotation();\n\t\t\t\t\t\t\t\t_ammoVec1$1.setValue(bodyPos.x, bodyPos.y, bodyPos.z);\n\t\t\t\t\t\t\t\t_ammoQuat$1.setValue(bodyRot.x, bodyRot.y, bodyRot.z, bodyRot.w);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst pos = entity.getPosition();\n\t\t\t\t\t\t\t\tconst rot = entity.getRotation();\n\t\t\t\t\t\t\t\t_ammoVec1$1.setValue(pos.x, pos.y, pos.z);\n\t\t\t\t\t\t\t\t_ammoQuat$1.setValue(rot.x, rot.y, rot.z, rot.w);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_ammoTransform$1.setOrigin(_ammoVec1$1);\n\t\t\t\t\t\t_ammoTransform$1.setRotation(_ammoQuat$1);\n\t\t\t\t\t\tconst body = this.app.systems.rigidbody.createBody(mass, shape, _ammoTransform$1);\n\t\t\t\t\t\tbody.setRestitution(0);\n\t\t\t\t\t\tbody.setFriction(0);\n\t\t\t\t\t\tbody.setDamping(0, 0);\n\t\t\t\t\t\t_ammoVec1$1.setValue(0, 0, 0);\n\t\t\t\t\t\tbody.setLinearFactor(_ammoVec1$1);\n\t\t\t\t\t\tbody.setAngularFactor(_ammoVec1$1);\n\t\t\t\t\t\tbody.setCollisionFlags(body.getCollisionFlags() | BODYFLAG_NORESPONSE_OBJECT);\n\t\t\t\t\t\tbody.entity = entity;\n\t\t\t\t\t\tthis.body = body;\n\t\t\t\t\t\tif (this.component.enabled && entity.enabled) {\n\t\t\t\t\t\t\t\tthis.enable();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (!this.body) return;\n\t\t\t\tthis.disable();\n\t\t\t\tthis.app.systems.rigidbody.destroyBody(this.body);\n\t\t\t\tthis.body = null;\n\t\t}\n\t\t_getEntityTransform(transform) {\n\t\t\t\tconst component = this.component;\n\t\t\t\tif (component) {\n\t\t\t\t\t\tconst bodyPos = component.getShapePosition();\n\t\t\t\t\t\tconst bodyRot = component.getShapeRotation();\n\t\t\t\t\t\t_ammoVec1$1.setValue(bodyPos.x, bodyPos.y, bodyPos.z);\n\t\t\t\t\t\t_ammoQuat$1.setValue(bodyRot.x, bodyRot.y, bodyRot.z, bodyRot.w);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst pos = this.entity.getPosition();\n\t\t\t\t\t\tconst rot = this.entity.getRotation();\n\t\t\t\t\t\t_ammoVec1$1.setValue(pos.x, pos.y, pos.z);\n\t\t\t\t\t\t_ammoQuat$1.setValue(rot.x, rot.y, rot.z, rot.w);\n\t\t\t\t}\n\t\t\t\ttransform.setOrigin(_ammoVec1$1);\n\t\t\t\ttransform.setRotation(_ammoQuat$1);\n\t\t}\n\t\tupdateTransform() {\n\t\t\t\tthis._getEntityTransform(_ammoTransform$1);\n\t\t\t\tconst body = this.body;\n\t\t\t\tbody.setWorldTransform(_ammoTransform$1);\n\t\t\t\tbody.activate();\n\t\t}\n\t\tenable() {\n\t\t\t\tconst body = this.body;\n\t\t\t\tif (!body) return;\n\t\t\t\tconst system = this.app.systems.rigidbody;\n\t\t\t\tconst idx = system._triggers.indexOf(this);\n\t\t\t\tif (idx < 0) {\n\t\t\t\t\t\tsystem.addBody(body, BODYGROUP_TRIGGER, BODYMASK_NOT_STATIC ^ BODYGROUP_TRIGGER);\n\t\t\t\t\t\tsystem._triggers.push(this);\n\t\t\t\t}\n\t\t\t\tbody.forceActivationState(BODYSTATE_ACTIVE_TAG);\n\t\t\t\tthis.updateTransform();\n\t\t}\n\t\tdisable() {\n\t\t\t\tconst body = this.body;\n\t\t\t\tif (!body) return;\n\t\t\t\tconst system = this.app.systems.rigidbody;\n\t\t\t\tconst idx = system._triggers.indexOf(this);\n\t\t\t\tif (idx > -1) {\n\t\t\t\t\t\tsystem.removeBody(body);\n\t\t\t\t\t\tsystem._triggers.splice(idx, 1);\n\t\t\t\t}\n\t\t\t\tbody.forceActivationState(BODYSTATE_DISABLE_SIMULATION);\n\t\t}\n}\n\nconst mat4 = new Mat4();\nconst p1 = new Vec3();\nconst p2 = new Vec3();\nconst quat$1 = new Quat();\nconst tempGraphNode = new GraphNode();\nconst _schema$g = [\n\t\t'enabled',\n\t\t'type',\n\t\t'halfExtents',\n\t\t'linearOffset',\n\t\t'angularOffset',\n\t\t'radius',\n\t\t'axis',\n\t\t'height',\n\t\t'convexHull',\n\t\t'asset',\n\t\t'renderAsset',\n\t\t'shape',\n\t\t'model',\n\t\t'render',\n\t\t'checkVertexDuplicates'\n];\nclass CollisionSystemImpl {\n\t\tconstructor(system){\n\t\t\t\tthis.system = system;\n\t\t}\n\t\tbeforeInitialize(component, data) {\n\t\t\t\tdata.shape = null;\n\t\t\t\tdata.model = new Model();\n\t\t\t\tdata.model.graph = new GraphNode();\n\t\t}\n\t\tafterInitialize(component, data) {\n\t\t\t\tthis.recreatePhysicalShapes(component);\n\t\t\t\tcomponent.data.initialized = true;\n\t\t}\n\t\treset(component, data) {\n\t\t\t\tthis.beforeInitialize(component, data);\n\t\t\t\tthis.afterInitialize(component, data);\n\t\t}\n\t\trecreatePhysicalShapes(component) {\n\t\t\t\tconst entity = component.entity;\n\t\t\t\tconst data = component.data;\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tif (entity.trigger) {\n\t\t\t\t\t\t\t\tentity.trigger.destroy();\n\t\t\t\t\t\t\t\tdelete entity.trigger;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.shape) {\n\t\t\t\t\t\t\t\tif (component._compoundParent) {\n\t\t\t\t\t\t\t\t\t\tif (component !== component._compoundParent) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system._removeCompoundChild(component._compoundParent, data.shape);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (component._compoundParent.entity.rigidbody) {\n\t\t\t\t\t\t\t\t\t\t\t\tcomponent._compoundParent.entity.rigidbody.activate();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.destroyShape(data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdata.shape = this.createPhysicalShape(component.entity, data);\n\t\t\t\t\t\tconst firstCompoundChild = !component._compoundParent;\n\t\t\t\t\t\tif (data.type === 'compound' && (!component._compoundParent || component === component._compoundParent)) {\n\t\t\t\t\t\t\t\tcomponent._compoundParent = component;\n\t\t\t\t\t\t\t\tentity.forEach(this._addEachDescendant, component);\n\t\t\t\t\t\t} else if (data.type !== 'compound') {\n\t\t\t\t\t\t\t\tif (!component.rigidbody) {\n\t\t\t\t\t\t\t\t\t\tcomponent._compoundParent = null;\n\t\t\t\t\t\t\t\t\t\tlet parent = entity.parent;\n\t\t\t\t\t\t\t\t\t\twhile(parent){\n\t\t\t\t\t\t\t\t\t\t\t\tif (parent.collision && parent.collision.type === 'compound') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent._compoundParent = parent.collision;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tparent = parent.parent;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (component._compoundParent) {\n\t\t\t\t\t\t\t\tif (component !== component._compoundParent) {\n\t\t\t\t\t\t\t\t\t\tif (firstCompoundChild && component._compoundParent.shape.getNumChildShapes() === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system.recreatePhysicalShapes(component._compoundParent);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system.updateCompoundChildTransform(entity, true);\n\t\t\t\t\t\t\t\t\t\t\t\tif (component._compoundParent.entity.rigidbody) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent._compoundParent.entity.rigidbody.activate();\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (entity.rigidbody) {\n\t\t\t\t\t\t\t\tentity.rigidbody.disableSimulation();\n\t\t\t\t\t\t\t\tentity.rigidbody.createBody();\n\t\t\t\t\t\t\t\tif (entity.enabled && entity.rigidbody.enabled) {\n\t\t\t\t\t\t\t\t\t\tentity.rigidbody.enableSimulation();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (!component._compoundParent) {\n\t\t\t\t\t\t\t\tif (!entity.trigger) {\n\t\t\t\t\t\t\t\t\t\tentity.trigger = new Trigger(this.system.app, component, data);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tentity.trigger.initialize(data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\treturn undefined;\n\t\t}\n\t\tupdateTransform(component, position, rotation, scale) {\n\t\t\t\tif (component.entity.trigger) {\n\t\t\t\t\t\tcomponent.entity.trigger.updateTransform();\n\t\t\t\t}\n\t\t}\n\t\tdestroyShape(data) {\n\t\t\t\tif (data.shape) {\n\t\t\t\t\t\tAmmo.destroy(data.shape);\n\t\t\t\t\t\tdata.shape = null;\n\t\t\t\t}\n\t\t}\n\t\tbeforeRemove(entity, component) {\n\t\t\t\tif (component.data.shape) {\n\t\t\t\t\t\tif (component._compoundParent && !component._compoundParent.entity._destroying) {\n\t\t\t\t\t\t\t\tthis.system._removeCompoundChild(component._compoundParent, component.data.shape);\n\t\t\t\t\t\t\t\tif (component._compoundParent.entity.rigidbody) {\n\t\t\t\t\t\t\t\t\t\tcomponent._compoundParent.entity.rigidbody.activate();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcomponent._compoundParent = null;\n\t\t\t\t\t\tthis.destroyShape(component.data);\n\t\t\t\t}\n\t\t}\n\t\tremove(entity, data) {\n\t\t\t\tif (entity.rigidbody && entity.rigidbody.body) {\n\t\t\t\t\t\tentity.rigidbody.disableSimulation();\n\t\t\t\t}\n\t\t\t\tif (entity.trigger) {\n\t\t\t\t\t\tentity.trigger.destroy();\n\t\t\t\t\t\tdelete entity.trigger;\n\t\t\t\t}\n\t\t}\n\t\tclone(entity, clone) {\n\t\t\t\tconst src = this.system.store[entity.getGuid()];\n\t\t\t\tconst data = {\n\t\t\t\t\t\tenabled: src.data.enabled,\n\t\t\t\t\t\ttype: src.data.type,\n\t\t\t\t\t\thalfExtents: [\n\t\t\t\t\t\t\t\tsrc.data.halfExtents.x,\n\t\t\t\t\t\t\t\tsrc.data.halfExtents.y,\n\t\t\t\t\t\t\t\tsrc.data.halfExtents.z\n\t\t\t\t\t\t],\n\t\t\t\t\t\tlinearOffset: [\n\t\t\t\t\t\t\t\tsrc.data.linearOffset.x,\n\t\t\t\t\t\t\t\tsrc.data.linearOffset.y,\n\t\t\t\t\t\t\t\tsrc.data.linearOffset.z\n\t\t\t\t\t\t],\n\t\t\t\t\t\tangularOffset: [\n\t\t\t\t\t\t\t\tsrc.data.angularOffset.x,\n\t\t\t\t\t\t\t\tsrc.data.angularOffset.y,\n\t\t\t\t\t\t\t\tsrc.data.angularOffset.z,\n\t\t\t\t\t\t\t\tsrc.data.angularOffset.w\n\t\t\t\t\t\t],\n\t\t\t\t\t\tradius: src.data.radius,\n\t\t\t\t\t\taxis: src.data.axis,\n\t\t\t\t\t\theight: src.data.height,\n\t\t\t\t\t\tconvexHull: src.data.convexHull,\n\t\t\t\t\t\tasset: src.data.asset,\n\t\t\t\t\t\trenderAsset: src.data.renderAsset,\n\t\t\t\t\t\tmodel: src.data.model,\n\t\t\t\t\t\trender: src.data.render,\n\t\t\t\t\t\tcheckVertexDuplicates: src.data.checkVertexDuplicates\n\t\t\t\t};\n\t\t\t\treturn this.system.addComponent(clone, data);\n\t\t}\n}\nclass CollisionBoxSystemImpl extends CollisionSystemImpl {\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tconst he = data.halfExtents;\n\t\t\t\t\t\tconst ammoHe = new Ammo.btVector3(he ? he.x : 0.5, he ? he.y : 0.5, he ? he.z : 0.5);\n\t\t\t\t\t\tconst shape = new Ammo.btBoxShape(ammoHe);\n\t\t\t\t\t\tAmmo.destroy(ammoHe);\n\t\t\t\t\t\treturn shape;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n}\nclass CollisionSphereSystemImpl extends CollisionSystemImpl {\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\treturn new Ammo.btSphereShape(data.radius);\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n}\nclass CollisionCapsuleSystemImpl extends CollisionSystemImpl {\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\tconst axis = data.axis ?? 1;\n\t\t\t\tconst radius = data.radius ?? 0.5;\n\t\t\t\tconst height = Math.max((data.height ?? 2) - 2 * radius, 0);\n\t\t\t\tlet shape = null;\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tswitch(axis){\n\t\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btCapsuleShapeX(radius, height);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btCapsuleShape(radius, height);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btCapsuleShapeZ(radius, height);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn shape;\n\t\t}\n}\nclass CollisionCylinderSystemImpl extends CollisionSystemImpl {\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\tconst axis = data.axis ?? 1;\n\t\t\t\tconst radius = data.radius ?? 0.5;\n\t\t\t\tconst height = data.height ?? 1;\n\t\t\t\tlet halfExtents = null;\n\t\t\t\tlet shape = null;\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tswitch(axis){\n\t\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\t\t\thalfExtents = new Ammo.btVector3(height * 0.5, radius, radius);\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btCylinderShapeX(halfExtents);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\t\t\thalfExtents = new Ammo.btVector3(radius, height * 0.5, radius);\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btCylinderShape(halfExtents);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\t\t\thalfExtents = new Ammo.btVector3(radius, radius, height * 0.5);\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btCylinderShapeZ(halfExtents);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (halfExtents) {\n\t\t\t\t\t\tAmmo.destroy(halfExtents);\n\t\t\t\t}\n\t\t\t\treturn shape;\n\t\t}\n}\nclass CollisionConeSystemImpl extends CollisionSystemImpl {\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\tconst axis = data.axis ?? 1;\n\t\t\t\tconst radius = data.radius ?? 0.5;\n\t\t\t\tconst height = data.height ?? 1;\n\t\t\t\tlet shape = null;\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tswitch(axis){\n\t\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btConeShapeX(radius, height);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btConeShape(radius, height);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\t\t\tshape = new Ammo.btConeShapeZ(radius, height);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn shape;\n\t\t}\n}\nclass CollisionMeshSystemImpl extends CollisionSystemImpl {\n\t\tbeforeInitialize(component, data) {}\n\t\tcreateAmmoHull(mesh, node, shape, scale) {\n\t\t\t\tconst hull = new Ammo.btConvexHullShape();\n\t\t\t\tconst point = new Ammo.btVector3();\n\t\t\t\tconst positions = [];\n\t\t\t\tmesh.getPositions(positions);\n\t\t\t\tfor(let i = 0; i < positions.length; i += 3){\n\t\t\t\t\t\tpoint.setValue(positions[i] * scale.x, positions[i + 1] * scale.y, positions[i + 2] * scale.z);\n\t\t\t\t\t\thull.addPoint(point, false);\n\t\t\t\t}\n\t\t\t\tAmmo.destroy(point);\n\t\t\t\thull.recalcLocalAabb();\n\t\t\t\thull.setMargin(0.01);\n\t\t\t\tconst transform = this.system._getNodeTransform(node);\n\t\t\t\tshape.addChildShape(transform, hull);\n\t\t\t\tAmmo.destroy(transform);\n\t\t}\n\t\tcreateAmmoMesh(mesh, node, shape, scale, checkDupes = true) {\n\t\t\t\tconst system = this.system;\n\t\t\t\tlet triMesh;\n\t\t\t\tif (system._triMeshCache[mesh.id]) {\n\t\t\t\t\t\ttriMesh = system._triMeshCache[mesh.id];\n\t\t\t\t} else {\n\t\t\t\t\t\tconst vb = mesh.vertexBuffer;\n\t\t\t\t\t\tconst format = vb.getFormat();\n\t\t\t\t\t\tlet stride, positions;\n\t\t\t\t\t\tfor(let i = 0; i < format.elements.length; i++){\n\t\t\t\t\t\t\t\tconst element = format.elements[i];\n\t\t\t\t\t\t\t\tif (element.name === SEMANTIC_POSITION) {\n\t\t\t\t\t\t\t\t\t\tpositions = new Float32Array(vb.lock(), element.offset);\n\t\t\t\t\t\t\t\t\t\tstride = element.stride / 4;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst indices = [];\n\t\t\t\t\t\tmesh.getIndices(indices);\n\t\t\t\t\t\tconst numTriangles = mesh.primitive[0].count / 3;\n\t\t\t\t\t\tconst v1 = new Ammo.btVector3();\n\t\t\t\t\t\tlet i1, i2, i3;\n\t\t\t\t\t\tconst base = mesh.primitive[0].base;\n\t\t\t\t\t\ttriMesh = new Ammo.btTriangleMesh();\n\t\t\t\t\t\tsystem._triMeshCache[mesh.id] = triMesh;\n\t\t\t\t\t\tconst vertexCache = new Map();\n\t\t\t\t\t\tconst indexedArray = triMesh.getIndexedMeshArray();\n\t\t\t\t\t\tindexedArray.at(0).m_numTriangles = numTriangles;\n\t\t\t\t\t\tconst sx = scale ? scale.x : 1;\n\t\t\t\t\t\tconst sy = scale ? scale.y : 1;\n\t\t\t\t\t\tconst sz = scale ? scale.z : 1;\n\t\t\t\t\t\tconst addVertex = (index)=>{\n\t\t\t\t\t\t\t\tconst x = positions[index * stride] * sx;\n\t\t\t\t\t\t\t\tconst y = positions[index * stride + 1] * sy;\n\t\t\t\t\t\t\t\tconst z = positions[index * stride + 2] * sz;\n\t\t\t\t\t\t\t\tlet idx;\n\t\t\t\t\t\t\t\tif (checkDupes) {\n\t\t\t\t\t\t\t\t\t\tconst str = `${x}:${y}:${z}`;\n\t\t\t\t\t\t\t\t\t\tidx = vertexCache.get(str);\n\t\t\t\t\t\t\t\t\t\tif (idx !== undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn idx;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tv1.setValue(x, y, z);\n\t\t\t\t\t\t\t\t\t\tidx = triMesh.findOrAddVertex(v1, false);\n\t\t\t\t\t\t\t\t\t\tvertexCache.set(str, idx);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tv1.setValue(x, y, z);\n\t\t\t\t\t\t\t\t\t\tidx = triMesh.findOrAddVertex(v1, false);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn idx;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tfor(let i = 0; i < numTriangles; i++){\n\t\t\t\t\t\t\t\ti1 = addVertex(indices[base + i * 3]);\n\t\t\t\t\t\t\t\ti2 = addVertex(indices[base + i * 3 + 1]);\n\t\t\t\t\t\t\t\ti3 = addVertex(indices[base + i * 3 + 2]);\n\t\t\t\t\t\t\t\ttriMesh.addIndex(i1);\n\t\t\t\t\t\t\t\ttriMesh.addIndex(i2);\n\t\t\t\t\t\t\t\ttriMesh.addIndex(i3);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tAmmo.destroy(v1);\n\t\t\t\t}\n\t\t\t\tconst triMeshShape = new Ammo.btBvhTriangleMeshShape(triMesh, true);\n\t\t\t\tif (!scale) {\n\t\t\t\t\t\tconst scaling = system._getNodeScaling(node);\n\t\t\t\t\t\ttriMeshShape.setLocalScaling(scaling);\n\t\t\t\t\t\tAmmo.destroy(scaling);\n\t\t\t\t}\n\t\t\t\tconst transform = system._getNodeTransform(node);\n\t\t\t\tshape.addChildShape(transform, triMeshShape);\n\t\t\t\tAmmo.destroy(transform);\n\t\t}\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\tif (typeof Ammo === 'undefined') return undefined;\n\t\t\t\tif (data.model || data.render) {\n\t\t\t\t\t\tconst shape = new Ammo.btCompoundShape();\n\t\t\t\t\t\tconst entityTransform = entity.getWorldTransform();\n\t\t\t\t\t\tconst scale = entityTransform.getScale();\n\t\t\t\t\t\tif (data.render) {\n\t\t\t\t\t\t\t\tconst meshes = data.render.meshes;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < meshes.length; i++){\n\t\t\t\t\t\t\t\t\t\tif (data.convexHull) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.createAmmoHull(meshes[i], tempGraphNode, shape, scale);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.createAmmoMesh(meshes[i], tempGraphNode, shape, scale, data.checkVertexDuplicates);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (data.model) {\n\t\t\t\t\t\t\t\tconst meshInstances = data.model.meshInstances;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\t\t\tthis.createAmmoMesh(meshInstances[i].mesh, meshInstances[i].node, shape, null, data.checkVertexDuplicates);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst vec = new Ammo.btVector3(scale.x, scale.y, scale.z);\n\t\t\t\t\t\t\t\tshape.setLocalScaling(vec);\n\t\t\t\t\t\t\t\tAmmo.destroy(vec);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn shape;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\trecreatePhysicalShapes(component) {\n\t\t\t\tconst data = component.data;\n\t\t\t\tif (data.renderAsset || data.asset) {\n\t\t\t\t\t\tif (component.enabled && component.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.loadAsset(component, data.renderAsset || data.asset, data.renderAsset ? 'render' : 'model');\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.doRecreatePhysicalShape(component);\n\t\t}\n\t\tloadAsset(component, id, property) {\n\t\t\t\tconst data = component.data;\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tconst previousPropertyValue = data[property];\n\t\t\t\tconst onAssetFullyReady = (asset)=>{\n\t\t\t\t\t\tif (data[property] !== previousPropertyValue) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdata[property] = asset.resource;\n\t\t\t\t\t\tthis.doRecreatePhysicalShape(component);\n\t\t\t\t};\n\t\t\t\tconst loadAndHandleAsset = (asset)=>{\n\t\t\t\t\t\tasset.ready((asset)=>{\n\t\t\t\t\t\t\t\tif (asset.data.containerAsset) {\n\t\t\t\t\t\t\t\t\t\tconst containerAsset = assets.get(asset.data.containerAsset);\n\t\t\t\t\t\t\t\t\t\tif (containerAsset.loaded) {\n\t\t\t\t\t\t\t\t\t\t\t\tonAssetFullyReady(asset);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tcontainerAsset.ready(()=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonAssetFullyReady(asset);\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\tassets.load(containerAsset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tonAssetFullyReady(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tassets.load(asset);\n\t\t\t\t};\n\t\t\t\tconst asset = assets.get(id);\n\t\t\t\tif (asset) {\n\t\t\t\t\t\tloadAndHandleAsset(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tassets.once(`add:${id}`, loadAndHandleAsset);\n\t\t\t\t}\n\t\t}\n\t\tdoRecreatePhysicalShape(component) {\n\t\t\t\tconst entity = component.entity;\n\t\t\t\tconst data = component.data;\n\t\t\t\tif (data.model || data.render) {\n\t\t\t\t\t\tthis.destroyShape(data);\n\t\t\t\t\t\tdata.shape = this.createPhysicalShape(entity, data);\n\t\t\t\t\t\tif (entity.rigidbody) {\n\t\t\t\t\t\t\t\tentity.rigidbody.disableSimulation();\n\t\t\t\t\t\t\t\tentity.rigidbody.createBody();\n\t\t\t\t\t\t\t\tif (entity.enabled && entity.rigidbody.enabled) {\n\t\t\t\t\t\t\t\t\t\tentity.rigidbody.enableSimulation();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (!entity.trigger) {\n\t\t\t\t\t\t\t\t\t\tentity.trigger = new Trigger(this.system.app, component, data);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tentity.trigger.initialize(data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.beforeRemove(entity, component);\n\t\t\t\t\t\tthis.remove(entity, data);\n\t\t\t\t}\n\t\t}\n\t\tupdateTransform(component, position, rotation, scale) {\n\t\t\t\tif (component.shape) {\n\t\t\t\t\t\tconst entityTransform = component.entity.getWorldTransform();\n\t\t\t\t\t\tconst worldScale = entityTransform.getScale();\n\t\t\t\t\t\tconst previousScale = component.shape.getLocalScaling();\n\t\t\t\t\t\tif (worldScale.x !== previousScale.x() || worldScale.y !== previousScale.y() || worldScale.z !== previousScale.z()) {\n\t\t\t\t\t\t\t\tthis.doRecreatePhysicalShape(component);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsuper.updateTransform(component, position, rotation, scale);\n\t\t}\n\t\tdestroyShape(data) {\n\t\t\t\tif (!data.shape) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst numShapes = data.shape.getNumChildShapes();\n\t\t\t\tfor(let i = 0; i < numShapes; i++){\n\t\t\t\t\t\tconst shape = data.shape.getChildShape(i);\n\t\t\t\t\t\tAmmo.destroy(shape);\n\t\t\t\t}\n\t\t\t\tAmmo.destroy(data.shape);\n\t\t\t\tdata.shape = null;\n\t\t}\n}\nclass CollisionCompoundSystemImpl extends CollisionSystemImpl {\n\t\tcreatePhysicalShape(entity, data) {\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\treturn new Ammo.btCompoundShape();\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\t_addEachDescendant(entity) {\n\t\t\t\tif (!entity.collision || entity.rigidbody) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tentity.collision._compoundParent = this;\n\t\t\t\tif (entity !== this.entity) {\n\t\t\t\t\t\tentity.collision.system.recreatePhysicalShapes(entity.collision);\n\t\t\t\t}\n\t\t}\n\t\t_updateEachDescendant(entity) {\n\t\t\t\tif (!entity.collision) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (entity.collision._compoundParent !== this) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tentity.collision._compoundParent = null;\n\t\t\t\tif (entity !== this.entity && !entity.rigidbody) {\n\t\t\t\t\t\tentity.collision.system.recreatePhysicalShapes(entity.collision);\n\t\t\t\t}\n\t\t}\n\t\t_updateEachDescendantTransform(entity) {\n\t\t\t\tif (!entity.collision || entity.collision._compoundParent !== this.collision._compoundParent) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.collision.system.updateCompoundChildTransform(entity, false);\n\t\t}\n}\nclass CollisionComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'collision';\n\t\t\t\tthis.ComponentType = CollisionComponent;\n\t\t\t\tthis.DataType = CollisionComponentData;\n\t\t\t\tthis.schema = _schema$g;\n\t\t\t\tthis.implementations = {};\n\t\t\t\tthis._triMeshCache = {};\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t\t\tthis.on('remove', this.onRemove, this);\n\t\t}\n\t\tinitializeComponentData(component, _data, properties) {\n\t\t\t\tproperties = [\n\t\t\t\t\t\t'type',\n\t\t\t\t\t\t'halfExtents',\n\t\t\t\t\t\t'radius',\n\t\t\t\t\t\t'axis',\n\t\t\t\t\t\t'height',\n\t\t\t\t\t\t'convexHull',\n\t\t\t\t\t\t'shape',\n\t\t\t\t\t\t'model',\n\t\t\t\t\t\t'asset',\n\t\t\t\t\t\t'render',\n\t\t\t\t\t\t'renderAsset',\n\t\t\t\t\t\t'enabled',\n\t\t\t\t\t\t'linearOffset',\n\t\t\t\t\t\t'angularOffset',\n\t\t\t\t\t\t'checkVertexDuplicates'\n\t\t\t\t];\n\t\t\t\tconst data = {};\n\t\t\t\tfor(let i = 0, len = properties.length; i < len; i++){\n\t\t\t\t\t\tconst property = properties[i];\n\t\t\t\t\t\tdata[property] = _data[property];\n\t\t\t\t}\n\t\t\t\tlet idx;\n\t\t\t\tif (_data.hasOwnProperty('asset')) {\n\t\t\t\t\t\tidx = properties.indexOf('model');\n\t\t\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\t\t\tproperties.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tidx = properties.indexOf('render');\n\t\t\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\t\t\tproperties.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t} else if (_data.hasOwnProperty('model')) {\n\t\t\t\t\t\tidx = properties.indexOf('asset');\n\t\t\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\t\t\tproperties.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!data.type) {\n\t\t\t\t\t\tdata.type = component.data.type;\n\t\t\t\t}\n\t\t\t\tcomponent.data.type = data.type;\n\t\t\t\tif (Array.isArray(data.halfExtents)) {\n\t\t\t\t\t\tdata.halfExtents = new Vec3(data.halfExtents);\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.linearOffset)) {\n\t\t\t\t\t\tdata.linearOffset = new Vec3(data.linearOffset);\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.angularOffset)) {\n\t\t\t\t\t\tconst values = data.angularOffset;\n\t\t\t\t\t\tif (values.length === 3) {\n\t\t\t\t\t\t\t\tdata.angularOffset = new Quat().setFromEulerAngles(values[0], values[1], values[2]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdata.angularOffset = new Quat(data.angularOffset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst impl = this._createImplementation(data.type);\n\t\t\t\timpl.beforeInitialize(component, data);\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t\t\timpl.afterInitialize(component, data);\n\t\t}\n\t\t_createImplementation(type) {\n\t\t\t\tif (this.implementations[type] === undefined) {\n\t\t\t\t\t\tlet impl;\n\t\t\t\t\t\tswitch(type){\n\t\t\t\t\t\t\t\tcase 'box':\n\t\t\t\t\t\t\t\t\t\timpl = new CollisionBoxSystemImpl(this);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'sphere':\n\t\t\t\t\t\t\t\t\t\timpl = new CollisionSphereSystemImpl(this);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'capsule':\n\t\t\t\t\t\t\t\t\t\timpl = new CollisionCapsuleSystemImpl(this);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'cylinder':\n\t\t\t\t\t\t\t\t\t\timpl = new CollisionCylinderSystemImpl(this);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'cone':\n\t\t\t\t\t\t\t\t\t\timpl = new CollisionConeSystemImpl(this);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'mesh':\n\t\t\t\t\t\t\t\t\t\timpl = new CollisionMeshSystemImpl(this);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'compound':\n\t\t\t\t\t\t\t\t\t\timpl = new CollisionCompoundSystemImpl(this);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.implementations[type] = impl;\n\t\t\t\t}\n\t\t\t\treturn this.implementations[type];\n\t\t}\n\t\t_getImplementation(entity) {\n\t\t\t\treturn this.implementations[entity.collision.data.type];\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\treturn this._getImplementation(entity).clone(entity, clone);\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tthis.implementations[component.data.type].beforeRemove(entity, component);\n\t\t\t\tcomponent.onBeforeRemove();\n\t\t}\n\t\tonRemove(entity, data) {\n\t\t\t\tthis.implementations[data.type].remove(entity, data);\n\t\t}\n\t\tupdateCompoundChildTransform(entity, forceUpdate) {\n\t\t\t\tconst parentComponent = entity.collision._compoundParent;\n\t\t\t\tif (parentComponent === entity.collision) return;\n\t\t\t\tif (entity.enabled && entity.collision.enabled && (entity._dirtyLocal || forceUpdate)) {\n\t\t\t\t\t\tconst transform = this._getNodeTransform(entity, parentComponent.entity);\n\t\t\t\t\t\tconst idx = parentComponent.getCompoundChildShapeIndex(entity.collision.shape);\n\t\t\t\t\t\tif (idx === null) {\n\t\t\t\t\t\t\t\tparentComponent.shape.addChildShape(transform, entity.collision.data.shape);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tparentComponent.shape.updateChildTransform(idx, transform, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tAmmo.destroy(transform);\n\t\t\t\t}\n\t\t}\n\t\t_removeCompoundChild(collision, shape) {\n\t\t\t\tif (collision.shape.getNumChildShapes() === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (collision.shape.removeChildShape) {\n\t\t\t\t\t\tcollision.shape.removeChildShape(shape);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst ind = collision.getCompoundChildShapeIndex(shape);\n\t\t\t\t\t\tif (ind !== null) {\n\t\t\t\t\t\t\t\tcollision.shape.removeChildShapeByIndex(ind);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonTransformChanged(component, position, rotation, scale) {\n\t\t\t\tthis.implementations[component.data.type].updateTransform(component, position, rotation, scale);\n\t\t}\n\t\tchangeType(component, previousType, newType) {\n\t\t\t\tthis.implementations[previousType].beforeRemove(component.entity, component);\n\t\t\t\tthis.implementations[previousType].remove(component.entity, component.data);\n\t\t\t\tthis._createImplementation(newType).reset(component, component.data);\n\t\t}\n\t\trecreatePhysicalShapes(component) {\n\t\t\t\tthis.implementations[component.data.type].recreatePhysicalShapes(component);\n\t\t}\n\t\t_calculateNodeRelativeTransform(node, relative) {\n\t\t\t\tif (node === relative) {\n\t\t\t\t\t\tconst scale = node.getWorldTransform().getScale();\n\t\t\t\t\t\tmat4.setScale(scale.x, scale.y, scale.z);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._calculateNodeRelativeTransform(node.parent, relative);\n\t\t\t\t\t\tmat4.mul(node.getLocalTransform());\n\t\t\t\t}\n\t\t}\n\t\t_getNodeScaling(node) {\n\t\t\t\tconst wtm = node.getWorldTransform();\n\t\t\t\tconst scl = wtm.getScale();\n\t\t\t\treturn new Ammo.btVector3(scl.x, scl.y, scl.z);\n\t\t}\n\t\t_getNodeTransform(node, relative) {\n\t\t\t\tlet pos, rot;\n\t\t\t\tif (relative) {\n\t\t\t\t\t\tthis._calculateNodeRelativeTransform(node, relative);\n\t\t\t\t\t\tpos = p1;\n\t\t\t\t\t\trot = quat$1;\n\t\t\t\t\t\tmat4.getTranslation(pos);\n\t\t\t\t\t\trot.setFromMat4(mat4);\n\t\t\t\t} else {\n\t\t\t\t\t\tpos = node.getPosition();\n\t\t\t\t\t\trot = node.getRotation();\n\t\t\t\t}\n\t\t\t\tconst ammoQuat = new Ammo.btQuaternion();\n\t\t\t\tconst transform = new Ammo.btTransform();\n\t\t\t\ttransform.setIdentity();\n\t\t\t\tconst origin = transform.getOrigin();\n\t\t\t\tconst component = node.collision;\n\t\t\t\tif (component && component._hasOffset) {\n\t\t\t\t\t\tconst lo = component.data.linearOffset;\n\t\t\t\t\t\tconst ao = component.data.angularOffset;\n\t\t\t\t\t\tconst newOrigin = p2;\n\t\t\t\t\t\tquat$1.copy(rot).transformVector(lo, newOrigin);\n\t\t\t\t\t\tnewOrigin.add(pos);\n\t\t\t\t\t\tquat$1.copy(rot).mul(ao);\n\t\t\t\t\t\torigin.setValue(newOrigin.x, newOrigin.y, newOrigin.z);\n\t\t\t\t\t\tammoQuat.setValue(quat$1.x, quat$1.y, quat$1.z, quat$1.w);\n\t\t\t\t} else {\n\t\t\t\t\t\torigin.setValue(pos.x, pos.y, pos.z);\n\t\t\t\t\t\tammoQuat.setValue(rot.x, rot.y, rot.z, rot.w);\n\t\t\t\t}\n\t\t\t\ttransform.setRotation(ammoQuat);\n\t\t\t\tAmmo.destroy(ammoQuat);\n\t\t\t\treturn transform;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tfor(const key in this._triMeshCache){\n\t\t\t\t\t\tAmmo.destroy(this._triMeshCache[key]);\n\t\t\t\t}\n\t\t\t\tthis._triMeshCache = null;\n\t\t\t\tsuper.destroy();\n\t\t}\n}\n\nconst _tempColor$1 = new Color();\nconst _vertexFormatDeviceCache = new DeviceCache();\nclass ImageRenderable {\n\t\tconstructor(entity, mesh, material){\n\t\t\t\tthis._entity = entity;\n\t\t\t\tthis._element = entity.element;\n\t\t\t\tthis.model = new Model();\n\t\t\t\tthis.node = new GraphNode();\n\t\t\t\tthis.model.graph = this.node;\n\t\t\t\tthis.mesh = mesh;\n\t\t\t\tthis.meshInstance = new MeshInstance(this.mesh, material, this.node);\n\t\t\t\tthis.meshInstance.name = `ImageElement: ${entity.name}`;\n\t\t\t\tthis.meshInstance.castShadow = false;\n\t\t\t\tthis.meshInstance.receiveShadow = false;\n\t\t\t\tthis._meshDirty = false;\n\t\t\t\tthis.model.meshInstances.push(this.meshInstance);\n\t\t\t\tthis._entity.addChild(this.model.graph);\n\t\t\t\tthis.model._entity = this._entity;\n\t\t\t\tthis.unmaskMeshInstance = null;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.setMaterial(null);\n\t\t\t\tthis._element.removeModelFromLayers(this.model);\n\t\t\t\tthis.model.destroy();\n\t\t\t\tthis.model = null;\n\t\t\t\tthis.node = null;\n\t\t\t\tthis.mesh = null;\n\t\t\t\tthis.meshInstance?.destroy();\n\t\t\t\tthis.meshInstance = null;\n\t\t\t\tthis.unmaskMeshInstance?.destroy();\n\t\t\t\tthis.unmaskMeshInstance = null;\n\t\t\t\tthis._entity = null;\n\t\t\t\tthis._element = null;\n\t\t}\n\t\tsetMesh(mesh) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.mesh = mesh;\n\t\t\t\tthis.meshInstance.mesh = mesh;\n\t\t\t\tthis.meshInstance.visible = !!mesh;\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance.mesh = mesh;\n\t\t\t\t}\n\t\t\t\tthis.forceUpdateAabb();\n\t\t}\n\t\tsetMask(mask) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tif (this._entity.enabled && this._element.enabled) {\n\t\t\t\t\t\tthis._element.removeModelFromLayers(this.model);\n\t\t\t\t}\n\t\t\t\tif (mask) {\n\t\t\t\t\t\tthis.unmaskMeshInstance = new MeshInstance(this.mesh, this.meshInstance.material, this.node);\n\t\t\t\t\t\tthis.unmaskMeshInstance.name = `Unmask: ${this._entity.name}`;\n\t\t\t\t\t\tthis.unmaskMeshInstance.castShadow = false;\n\t\t\t\t\t\tthis.unmaskMeshInstance.receiveShadow = false;\n\t\t\t\t\t\tthis.unmaskMeshInstance.pick = false;\n\t\t\t\t\t\tthis.model.meshInstances.push(this.unmaskMeshInstance);\n\t\t\t\t\t\tfor(const name in this.meshInstance.parameters){\n\t\t\t\t\t\t\t\tthis.unmaskMeshInstance.setParameter(name, this.meshInstance.parameters[name].data);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst idx = this.model.meshInstances.indexOf(this.unmaskMeshInstance);\n\t\t\t\t\t\tif (idx >= 0) {\n\t\t\t\t\t\t\t\tthis.model.meshInstances.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._entity.enabled && this._element.enabled) {\n\t\t\t\t\t\tthis._element.addModelToLayers(this.model);\n\t\t\t\t}\n\t\t\t\tif (!mask) {\n\t\t\t\t\t\tthis.unmaskMeshInstance?.destroy();\n\t\t\t\t\t\tthis.unmaskMeshInstance = null;\n\t\t\t\t}\n\t\t}\n\t\tsetMaterial(material) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.meshInstance.material = material;\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance.material = material;\n\t\t\t\t}\n\t\t}\n\t\tsetParameter(name, value) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.meshInstance.setParameter(name, value);\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance.setParameter(name, value);\n\t\t\t\t}\n\t\t}\n\t\tdeleteParameter(name) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.meshInstance.deleteParameter(name);\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance.deleteParameter(name);\n\t\t\t\t}\n\t\t}\n\t\tsetUnmaskDrawOrder() {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tconst getLastChild = function(e) {\n\t\t\t\t\t\tlet last;\n\t\t\t\t\t\tconst c = e.children;\n\t\t\t\t\t\tconst l = c.length;\n\t\t\t\t\t\tif (l) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < l; i++){\n\t\t\t\t\t\t\t\t\t\tif (c[i].element) {\n\t\t\t\t\t\t\t\t\t\t\t\tlast = c[i];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!last) return null;\n\t\t\t\t\t\t\t\tconst child = getLastChild(last);\n\t\t\t\t\t\t\t\tif (child) {\n\t\t\t\t\t\t\t\t\t\treturn child;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn last;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t};\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tconst lastChild = getLastChild(this._entity);\n\t\t\t\t\t\tif (lastChild && lastChild.element) {\n\t\t\t\t\t\t\t\tthis.unmaskMeshInstance.drawOrder = lastChild.element.drawOrder + lastChild.element.getMaskOffset();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.unmaskMeshInstance.drawOrder = this.meshInstance.drawOrder + this._element.getMaskOffset();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetDrawOrder(drawOrder) {\n\t\t\t\tif (!this.meshInstance) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.meshInstance.drawOrder = drawOrder;\n\t\t}\n\t\tsetCull(cull) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tconst element = this._element;\n\t\t\t\tlet visibleFn = null;\n\t\t\t\tif (cull && element._isScreenSpace()) {\n\t\t\t\t\t\tvisibleFn = function(camera) {\n\t\t\t\t\t\t\t\treturn element.isVisibleForCamera(camera);\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tthis.meshInstance.cull = cull;\n\t\t\t\tthis.meshInstance.isVisibleFunc = visibleFn;\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance.cull = cull;\n\t\t\t\t\t\tthis.unmaskMeshInstance.isVisibleFunc = visibleFn;\n\t\t\t\t}\n\t\t}\n\t\tsetScreenSpace(screenSpace) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.meshInstance.screenSpace = screenSpace;\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance.screenSpace = screenSpace;\n\t\t\t\t}\n\t\t}\n\t\tsetLayer(layer) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.meshInstance.layer = layer;\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance.layer = layer;\n\t\t\t\t}\n\t\t}\n\t\tforceUpdateAabb(mask) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.meshInstance._aabbVer = -1;\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance._aabbVer = -1;\n\t\t\t\t}\n\t\t}\n\t\tsetAabbFunc(fn) {\n\t\t\t\tif (!this.meshInstance) return;\n\t\t\t\tthis.meshInstance._updateAabbFunc = fn;\n\t\t\t\tif (this.unmaskMeshInstance) {\n\t\t\t\t\t\tthis.unmaskMeshInstance._updateAabbFunc = fn;\n\t\t\t\t}\n\t\t}\n}\nclass ImageElement {\n\t\tconstructor(element){\n\t\t\t\tthis._evtSetMeshes = null;\n\t\t\t\tthis._element = element;\n\t\t\t\tthis._entity = element.entity;\n\t\t\t\tthis._system = element.system;\n\t\t\t\tthis._textureAsset = null;\n\t\t\t\tthis._texture = null;\n\t\t\t\tthis._materialAsset = null;\n\t\t\t\tthis._material = null;\n\t\t\t\tthis._spriteAsset = null;\n\t\t\t\tthis._sprite = null;\n\t\t\t\tthis._spriteFrame = 0;\n\t\t\t\tthis._pixelsPerUnit = null;\n\t\t\t\tthis._targetAspectRatio = -1;\n\t\t\t\tthis._rect = new Vec4(0, 0, 1, 1);\n\t\t\t\tthis._mask = false;\n\t\t\t\tthis._maskRef = 0;\n\t\t\t\tthis._outerScale = new Vec2();\n\t\t\t\tthis._outerScaleUniform = new Float32Array(2);\n\t\t\t\tthis._innerOffset = new Vec4();\n\t\t\t\tthis._innerOffsetUniform = new Float32Array(4);\n\t\t\t\tthis._atlasRect = new Vec4();\n\t\t\t\tthis._atlasRectUniform = new Float32Array(4);\n\t\t\t\tthis._defaultMesh = this._createMesh();\n\t\t\t\tthis._renderable = new ImageRenderable(this._entity, this._defaultMesh, this._material);\n\t\t\t\tthis._color = new Color(1, 1, 1, 1);\n\t\t\t\tthis._colorUniform = new Float32Array([\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1\n\t\t\t\t]);\n\t\t\t\tthis._updateRenderableEmissive();\n\t\t\t\tthis._renderable.setParameter('material_opacity', 1);\n\t\t\t\tthis._updateAabbFunc = this._updateAabb.bind(this);\n\t\t\t\tthis._onScreenChange(this._element.screen);\n\t\t\t\tthis._element.on('resize', this._onParentResizeOrPivotChange, this);\n\t\t\t\tthis._element.on('set:pivot', this._onParentResizeOrPivotChange, this);\n\t\t\t\tthis._element.on('screen:set:screenspace', this._onScreenSpaceChange, this);\n\t\t\t\tthis._element.on('set:screen', this._onScreenChange, this);\n\t\t\t\tthis._element.on('set:draworder', this._onDrawOrderChange, this);\n\t\t\t\tthis._element.on('screen:set:resolution', this._onResolutionChange, this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.textureAsset = null;\n\t\t\t\tthis.spriteAsset = null;\n\t\t\t\tthis.materialAsset = null;\n\t\t\t\tthis._renderable.setMesh(this._defaultMesh);\n\t\t\t\tthis._renderable.destroy();\n\t\t\t\tthis._defaultMesh = null;\n\t\t\t\tthis._element.off('resize', this._onParentResizeOrPivotChange, this);\n\t\t\t\tthis._element.off('set:pivot', this._onParentResizeOrPivotChange, this);\n\t\t\t\tthis._element.off('screen:set:screenspace', this._onScreenSpaceChange, this);\n\t\t\t\tthis._element.off('set:screen', this._onScreenChange, this);\n\t\t\t\tthis._element.off('set:draworder', this._onDrawOrderChange, this);\n\t\t\t\tthis._element.off('screen:set:resolution', this._onResolutionChange, this);\n\t\t}\n\t\t_onResolutionChange(res) {}\n\t\t_onParentResizeOrPivotChange() {\n\t\t\t\tif (this._renderable.mesh) {\n\t\t\t\t\t\tthis._updateMesh(this._renderable.mesh);\n\t\t\t\t}\n\t\t}\n\t\t_onScreenSpaceChange(value) {\n\t\t\t\tthis._updateMaterial(value);\n\t\t}\n\t\t_onScreenChange(screen, previous) {\n\t\t\t\tif (screen) {\n\t\t\t\t\t\tthis._updateMaterial(screen.screen.screenSpace);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._updateMaterial(false);\n\t\t\t\t}\n\t\t}\n\t\t_onDrawOrderChange(order) {\n\t\t\t\tthis._renderable.setDrawOrder(order);\n\t\t\t\tif (this.mask && this._element.screen) {\n\t\t\t\t\t\tthis._element.screen.screen.once('syncdraworder', function() {\n\t\t\t\t\t\t\t\tthis._renderable.setUnmaskDrawOrder();\n\t\t\t\t\t\t}, this);\n\t\t\t\t}\n\t\t}\n\t\t_hasUserMaterial() {\n\t\t\t\treturn !!this._materialAsset || !!this._material && this._system.defaultImageMaterials.indexOf(this._material) === -1;\n\t\t}\n\t\t_use9Slicing() {\n\t\t\t\treturn this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED || this.sprite.renderMode === SPRITE_RENDERMODE_TILED);\n\t\t}\n\t\t_updateMaterial(screenSpace) {\n\t\t\t\tconst mask = !!this._mask;\n\t\t\t\tconst nineSliced = !!(this.sprite && this.sprite.renderMode === SPRITE_RENDERMODE_SLICED);\n\t\t\t\tconst nineTiled = !!(this.sprite && this.sprite.renderMode === SPRITE_RENDERMODE_TILED);\n\t\t\t\tif (!this._hasUserMaterial()) {\n\t\t\t\t\t\tthis._material = this._system.getImageElementMaterial(screenSpace, mask, nineSliced, nineTiled);\n\t\t\t\t}\n\t\t\t\tif (this._renderable) {\n\t\t\t\t\t\tthis._renderable.setCull(!this._element._isScreenSpace() || this._element._isScreenCulled());\n\t\t\t\t\t\tthis._renderable.setMaterial(this._material);\n\t\t\t\t\t\tthis._renderable.setScreenSpace(screenSpace);\n\t\t\t\t\t\tthis._renderable.setLayer(screenSpace ? LAYER_HUD : LAYER_WORLD);\n\t\t\t\t}\n\t\t}\n\t\t_createMesh() {\n\t\t\t\tconst element = this._element;\n\t\t\t\tconst w = element.calculatedWidth;\n\t\t\t\tconst h = element.calculatedHeight;\n\t\t\t\tconst r = this._rect;\n\t\t\t\tconst device = this._system.app.graphicsDevice;\n\t\t\t\tconst vertexData = new Float32Array([\n\t\t\t\t\t\tw,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tr.x + r.z,\n\t\t\t\t\t\t1.0 - r.y,\n\t\t\t\t\t\tw,\n\t\t\t\t\t\th,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tr.x + r.z,\n\t\t\t\t\t\t1.0 - (r.y + r.w),\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tr.x,\n\t\t\t\t\t\t1.0 - r.y,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\th,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tr.x,\n\t\t\t\t\t\t1.0 - (r.y + r.w)\n\t\t\t\t]);\n\t\t\t\tconst vertexFormat = _vertexFormatDeviceCache.get(device, ()=>{\n\t\t\t\t\t\treturn new VertexFormat(device, [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_POSITION,\n\t\t\t\t\t\t\t\t\t\tcomponents: 3,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_NORMAL,\n\t\t\t\t\t\t\t\t\t\tcomponents: 3,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsemantic: SEMANTIC_TEXCOORD0,\n\t\t\t\t\t\t\t\t\t\tcomponents: 2,\n\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]);\n\t\t\t\t});\n\t\t\t\tconst vertexBuffer = new VertexBuffer(device, vertexFormat, 4, {\n\t\t\t\t\t\tdata: vertexData.buffer\n\t\t\t\t});\n\t\t\t\tconst mesh = new Mesh(device);\n\t\t\t\tmesh.vertexBuffer = vertexBuffer;\n\t\t\t\tmesh.primitive[0].type = PRIMITIVE_TRISTRIP;\n\t\t\t\tmesh.primitive[0].base = 0;\n\t\t\t\tmesh.primitive[0].count = 4;\n\t\t\t\tmesh.primitive[0].indexed = false;\n\t\t\t\tmesh.aabb.setMinMax(Vec3.ZERO, new Vec3(w, h, 0));\n\t\t\t\tthis._updateMesh(mesh);\n\t\t\t\treturn mesh;\n\t\t}\n\t\t_updateMesh(mesh) {\n\t\t\t\tconst element = this._element;\n\t\t\t\tlet w = element.calculatedWidth;\n\t\t\t\tlet h = element.calculatedHeight;\n\t\t\t\tif (element.fitMode !== FITMODE_STRETCH && this._targetAspectRatio > 0) {\n\t\t\t\t\t\tconst actualRatio = element.calculatedWidth / element.calculatedHeight;\n\t\t\t\t\t\tif (element.fitMode === FITMODE_CONTAIN && actualRatio > this._targetAspectRatio || element.fitMode === FITMODE_COVER && actualRatio < this._targetAspectRatio) {\n\t\t\t\t\t\t\t\tw = element.calculatedHeight * this._targetAspectRatio;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\th = element.calculatedWidth / this._targetAspectRatio;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst screenSpace = element._isScreenSpace();\n\t\t\t\tthis._updateMaterial(screenSpace);\n\t\t\t\tif (this._renderable) this._renderable.forceUpdateAabb();\n\t\t\t\tif (this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED || this.sprite.renderMode === SPRITE_RENDERMODE_TILED)) {\n\t\t\t\t\t\tconst frameData = this._sprite.atlas.frames[this._sprite.frameKeys[this._spriteFrame]];\n\t\t\t\t\t\tconst borderWidthScale = 2 / frameData.rect.z;\n\t\t\t\t\t\tconst borderHeightScale = 2 / frameData.rect.w;\n\t\t\t\t\t\tthis._innerOffset.set(frameData.border.x * borderWidthScale, frameData.border.y * borderHeightScale, frameData.border.z * borderWidthScale, frameData.border.w * borderHeightScale);\n\t\t\t\t\t\tconst tex = this.sprite.atlas.texture;\n\t\t\t\t\t\tthis._atlasRect.set(frameData.rect.x / tex.width, frameData.rect.y / tex.height, frameData.rect.z / tex.width, frameData.rect.w / tex.height);\n\t\t\t\t\t\tconst ppu = this._pixelsPerUnit !== null ? this._pixelsPerUnit : this.sprite.pixelsPerUnit;\n\t\t\t\t\t\tconst scaleMulX = frameData.rect.z / ppu;\n\t\t\t\t\t\tconst scaleMulY = frameData.rect.w / ppu;\n\t\t\t\t\t\tthis._outerScale.set(Math.max(w, this._innerOffset.x * scaleMulX), Math.max(h, this._innerOffset.y * scaleMulY));\n\t\t\t\t\t\tlet scaleX = scaleMulX;\n\t\t\t\t\t\tlet scaleY = scaleMulY;\n\t\t\t\t\t\tthis._outerScale.x /= scaleMulX;\n\t\t\t\t\t\tthis._outerScale.y /= scaleMulY;\n\t\t\t\t\t\tscaleX *= math.clamp(w / (this._innerOffset.x * scaleMulX), 0.0001, 1);\n\t\t\t\t\t\tscaleY *= math.clamp(h / (this._innerOffset.y * scaleMulY), 0.0001, 1);\n\t\t\t\t\t\tif (this._renderable) {\n\t\t\t\t\t\t\t\tthis._innerOffsetUniform[0] = this._innerOffset.x;\n\t\t\t\t\t\t\t\tthis._innerOffsetUniform[1] = this._innerOffset.y;\n\t\t\t\t\t\t\t\tthis._innerOffsetUniform[2] = this._innerOffset.z;\n\t\t\t\t\t\t\t\tthis._innerOffsetUniform[3] = this._innerOffset.w;\n\t\t\t\t\t\t\t\tthis._renderable.setParameter('innerOffset', this._innerOffsetUniform);\n\t\t\t\t\t\t\t\tthis._atlasRectUniform[0] = this._atlasRect.x;\n\t\t\t\t\t\t\t\tthis._atlasRectUniform[1] = this._atlasRect.y;\n\t\t\t\t\t\t\t\tthis._atlasRectUniform[2] = this._atlasRect.z;\n\t\t\t\t\t\t\t\tthis._atlasRectUniform[3] = this._atlasRect.w;\n\t\t\t\t\t\t\t\tthis._renderable.setParameter('atlasRect', this._atlasRectUniform);\n\t\t\t\t\t\t\t\tthis._outerScaleUniform[0] = this._outerScale.x;\n\t\t\t\t\t\t\t\tthis._outerScaleUniform[1] = this._outerScale.y;\n\t\t\t\t\t\t\t\tthis._renderable.setParameter('outerScale', this._outerScaleUniform);\n\t\t\t\t\t\t\t\tthis._renderable.setAabbFunc(this._updateAabbFunc);\n\t\t\t\t\t\t\t\tthis._renderable.node.setLocalScale(scaleX, scaleY, 1);\n\t\t\t\t\t\t\t\tthis._renderable.node.setLocalPosition((0.5 - element.pivot.x) * w, (0.5 - element.pivot.y) * h, 0);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst vb = mesh.vertexBuffer;\n\t\t\t\t\t\tconst vertexDataF32 = new Float32Array(vb.lock());\n\t\t\t\t\t\tconst hp = element.pivot.x;\n\t\t\t\t\t\tconst vp = element.pivot.y;\n\t\t\t\t\t\tvertexDataF32[0] = w - hp * w;\n\t\t\t\t\t\tvertexDataF32[1] = 0 - vp * h;\n\t\t\t\t\t\tvertexDataF32[8] = w - hp * w;\n\t\t\t\t\t\tvertexDataF32[9] = h - vp * h;\n\t\t\t\t\t\tvertexDataF32[16] = 0 - hp * w;\n\t\t\t\t\t\tvertexDataF32[17] = 0 - vp * h;\n\t\t\t\t\t\tvertexDataF32[24] = 0 - hp * w;\n\t\t\t\t\t\tvertexDataF32[25] = h - vp * h;\n\t\t\t\t\t\tlet atlasTextureWidth = 1;\n\t\t\t\t\t\tlet atlasTextureHeight = 1;\n\t\t\t\t\t\tlet rect = this._rect;\n\t\t\t\t\t\tif (this._sprite && this._sprite.frameKeys[this._spriteFrame] && this._sprite.atlas) {\n\t\t\t\t\t\t\t\tconst frame = this._sprite.atlas.frames[this._sprite.frameKeys[this._spriteFrame]];\n\t\t\t\t\t\t\t\tif (frame) {\n\t\t\t\t\t\t\t\t\t\trect = frame.rect;\n\t\t\t\t\t\t\t\t\t\tatlasTextureWidth = this._sprite.atlas.texture.width;\n\t\t\t\t\t\t\t\t\t\tatlasTextureHeight = this._sprite.atlas.texture.height;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvertexDataF32[6] = (rect.x + rect.z) / atlasTextureWidth;\n\t\t\t\t\t\tvertexDataF32[7] = 1.0 - rect.y / atlasTextureHeight;\n\t\t\t\t\t\tvertexDataF32[14] = (rect.x + rect.z) / atlasTextureWidth;\n\t\t\t\t\t\tvertexDataF32[15] = 1.0 - (rect.y + rect.w) / atlasTextureHeight;\n\t\t\t\t\t\tvertexDataF32[22] = rect.x / atlasTextureWidth;\n\t\t\t\t\t\tvertexDataF32[23] = 1.0 - rect.y / atlasTextureHeight;\n\t\t\t\t\t\tvertexDataF32[30] = rect.x / atlasTextureWidth;\n\t\t\t\t\t\tvertexDataF32[31] = 1.0 - (rect.y + rect.w) / atlasTextureHeight;\n\t\t\t\t\t\tvb.unlock();\n\t\t\t\t\t\tconst min = new Vec3(0 - hp * w, 0 - vp * h, 0);\n\t\t\t\t\t\tconst max = new Vec3(w - hp * w, h - vp * h, 0);\n\t\t\t\t\t\tmesh.aabb.setMinMax(min, max);\n\t\t\t\t\t\tif (this._renderable) {\n\t\t\t\t\t\t\t\tthis._renderable.node.setLocalScale(1, 1, 1);\n\t\t\t\t\t\t\t\tthis._renderable.node.setLocalPosition(0, 0, 0);\n\t\t\t\t\t\t\t\tthis._renderable.setAabbFunc(null);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._meshDirty = false;\n\t\t}\n\t\t_updateSprite() {\n\t\t\t\tlet nineSlice = false;\n\t\t\t\tlet mesh = null;\n\t\t\t\tthis._targetAspectRatio = -1;\n\t\t\t\tif (this._sprite && this._sprite.atlas) {\n\t\t\t\t\t\tmesh = this._sprite.meshes[this.spriteFrame];\n\t\t\t\t\t\tnineSlice = this._sprite.renderMode === SPRITE_RENDERMODE_SLICED || this._sprite.renderMode === SPRITE_RENDERMODE_TILED;\n\t\t\t\t\t\tconst frameData = this._sprite.atlas.frames[this._sprite.frameKeys[this._spriteFrame]];\n\t\t\t\t\t\tif (frameData?.rect.w > 0) {\n\t\t\t\t\t\t\t\tthis._targetAspectRatio = frameData.rect.z / frameData.rect.w;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.mesh = nineSlice ? mesh : this._defaultMesh;\n\t\t\t\tthis.refreshMesh();\n\t\t}\n\t\trefreshMesh() {\n\t\t\t\tif (this.mesh) {\n\t\t\t\t\t\tif (!this._element._beingInitialized) {\n\t\t\t\t\t\t\t\tthis._updateMesh(this.mesh);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._meshDirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateAabb(aabb) {\n\t\t\t\taabb.center.set(0, 0, 0);\n\t\t\t\taabb.halfExtents.set(this._outerScale.x * 0.5, this._outerScale.y * 0.5, 0.001);\n\t\t\t\taabb.setFromTransformedAabb(aabb, this._renderable.node.getWorldTransform());\n\t\t\t\treturn aabb;\n\t\t}\n\t\t_toggleMask() {\n\t\t\t\tthis._element._dirtifyMask();\n\t\t\t\tconst screenSpace = this._element._isScreenSpace();\n\t\t\t\tthis._updateMaterial(screenSpace);\n\t\t\t\tthis._renderable.setMask(!!this._mask);\n\t\t}\n\t\t_onMaterialLoad(asset) {\n\t\t\t\tthis.material = asset.resource;\n\t\t}\n\t\t_onMaterialAdded(asset) {\n\t\t\t\tthis._system.app.assets.off(`add:${asset.id}`, this._onMaterialAdded, this);\n\t\t\t\tif (this._materialAsset === asset.id) {\n\t\t\t\t\t\tthis._bindMaterialAsset(asset);\n\t\t\t\t}\n\t\t}\n\t\t_bindMaterialAsset(asset) {\n\t\t\t\tif (!this._entity.enabled) return;\n\t\t\t\tasset.on('load', this._onMaterialLoad, this);\n\t\t\t\tasset.on('change', this._onMaterialChange, this);\n\t\t\t\tasset.on('remove', this._onMaterialRemove, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onMaterialLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindMaterialAsset(asset) {\n\t\t\t\tasset.off('load', this._onMaterialLoad, this);\n\t\t\t\tasset.off('change', this._onMaterialChange, this);\n\t\t\t\tasset.off('remove', this._onMaterialRemove, this);\n\t\t}\n\t\t_onMaterialChange() {}\n\t\t_onMaterialRemove() {}\n\t\t_onTextureAdded(asset) {\n\t\t\t\tthis._system.app.assets.off(`add:${asset.id}`, this._onTextureAdded, this);\n\t\t\t\tif (this._textureAsset === asset.id) {\n\t\t\t\t\t\tthis._bindTextureAsset(asset);\n\t\t\t\t}\n\t\t}\n\t\t_bindTextureAsset(asset) {\n\t\t\t\tif (!this._entity.enabled) return;\n\t\t\t\tasset.on('load', this._onTextureLoad, this);\n\t\t\t\tasset.on('change', this._onTextureChange, this);\n\t\t\t\tasset.on('remove', this._onTextureRemove, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onTextureLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindTextureAsset(asset) {\n\t\t\t\tasset.off('load', this._onTextureLoad, this);\n\t\t\t\tasset.off('change', this._onTextureChange, this);\n\t\t\t\tasset.off('remove', this._onTextureRemove, this);\n\t\t}\n\t\t_onTextureLoad(asset) {\n\t\t\t\tthis.texture = asset.resource;\n\t\t}\n\t\t_onTextureChange(asset) {}\n\t\t_onTextureRemove(asset) {}\n\t\t_onSpriteAssetAdded(asset) {\n\t\t\t\tthis._system.app.assets.off(`add:${asset.id}`, this._onSpriteAssetAdded, this);\n\t\t\t\tif (this._spriteAsset === asset.id) {\n\t\t\t\t\t\tthis._bindSpriteAsset(asset);\n\t\t\t\t}\n\t\t}\n\t\t_bindSpriteAsset(asset) {\n\t\t\t\tif (!this._entity.enabled) return;\n\t\t\t\tasset.on('load', this._onSpriteAssetLoad, this);\n\t\t\t\tasset.on('change', this._onSpriteAssetChange, this);\n\t\t\t\tasset.on('remove', this._onSpriteAssetRemove, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onSpriteAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindSpriteAsset(asset) {\n\t\t\t\tasset.off('load', this._onSpriteAssetLoad, this);\n\t\t\t\tasset.off('change', this._onSpriteAssetChange, this);\n\t\t\t\tasset.off('remove', this._onSpriteAssetRemove, this);\n\t\t\t\tif (asset.data.textureAtlasAsset) {\n\t\t\t\t\t\tthis._system.app.assets.off(`load:${asset.data.textureAtlasAsset}`, this._onTextureAtlasLoad, this);\n\t\t\t\t}\n\t\t}\n\t\t_onSpriteAssetLoad(asset) {\n\t\t\t\tif (!asset || !asset.resource) {\n\t\t\t\t\t\tthis.sprite = null;\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!asset.resource.atlas) {\n\t\t\t\t\t\t\t\tconst atlasAssetId = asset.data.textureAtlasAsset;\n\t\t\t\t\t\t\t\tif (atlasAssetId) {\n\t\t\t\t\t\t\t\t\t\tconst assets = this._system.app.assets;\n\t\t\t\t\t\t\t\t\t\tassets.off(`load:${atlasAssetId}`, this._onTextureAtlasLoad, this);\n\t\t\t\t\t\t\t\t\t\tassets.once(`load:${atlasAssetId}`, this._onTextureAtlasLoad, this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.sprite = asset.resource;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onSpriteAssetChange(asset) {\n\t\t\t\tthis._onSpriteAssetLoad(asset);\n\t\t}\n\t\t_onSpriteAssetRemove(asset) {}\n\t\t_bindSprite(sprite) {\n\t\t\t\tthis._evtSetMeshes = sprite.on('set:meshes', this._onSpriteMeshesChange, this);\n\t\t\t\tsprite.on('set:pixelsPerUnit', this._onSpritePpuChange, this);\n\t\t\t\tsprite.on('set:atlas', this._onAtlasTextureChange, this);\n\t\t\t\tif (sprite.atlas) {\n\t\t\t\t\t\tsprite.atlas.on('set:texture', this._onAtlasTextureChange, this);\n\t\t\t\t}\n\t\t}\n\t\t_unbindSprite(sprite) {\n\t\t\t\tthis._evtSetMeshes?.off();\n\t\t\t\tthis._evtSetMeshes = null;\n\t\t\t\tsprite.off('set:pixelsPerUnit', this._onSpritePpuChange, this);\n\t\t\t\tsprite.off('set:atlas', this._onAtlasTextureChange, this);\n\t\t\t\tif (sprite.atlas) {\n\t\t\t\t\t\tsprite.atlas.off('set:texture', this._onAtlasTextureChange, this);\n\t\t\t\t}\n\t\t}\n\t\t_onSpriteMeshesChange() {\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._spriteFrame = math.clamp(this._spriteFrame, 0, this._sprite.frameKeys.length - 1);\n\t\t\t\t}\n\t\t\t\tthis._updateSprite();\n\t\t}\n\t\t_onSpritePpuChange() {\n\t\t\t\tif (this.sprite.renderMode !== SPRITE_RENDERMODE_SIMPLE && this._pixelsPerUnit === null) {\n\t\t\t\t\t\tthis._updateSprite();\n\t\t\t\t}\n\t\t}\n\t\t_onAtlasTextureChange() {\n\t\t\t\tif (this.sprite && this.sprite.atlas && this.sprite.atlas.texture) {\n\t\t\t\t\t\tthis._renderable.setParameter('texture_emissiveMap', this._sprite.atlas.texture);\n\t\t\t\t\t\tthis._renderable.setParameter('texture_opacityMap', this._sprite.atlas.texture);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._renderable.deleteParameter('texture_emissiveMap');\n\t\t\t\t\t\tthis._renderable.deleteParameter('texture_opacityMap');\n\t\t\t\t}\n\t\t}\n\t\t_onTextureAtlasLoad(atlasAsset) {\n\t\t\t\tconst spriteAsset = this._spriteAsset;\n\t\t\t\tif (spriteAsset instanceof Asset) {\n\t\t\t\t\t\tthis._onSpriteAssetLoad(spriteAsset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._onSpriteAssetLoad(this._system.app.assets.get(spriteAsset));\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tif (this._materialAsset) {\n\t\t\t\t\t\tconst asset = this._system.app.assets.get(this._materialAsset);\n\t\t\t\t\t\tif (asset && asset.resource !== this._material) {\n\t\t\t\t\t\t\t\tthis._bindMaterialAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._textureAsset) {\n\t\t\t\t\t\tconst asset = this._system.app.assets.get(this._textureAsset);\n\t\t\t\t\t\tif (asset && asset.resource !== this._texture) {\n\t\t\t\t\t\t\t\tthis._bindTextureAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\tconst asset = this._system.app.assets.get(this._spriteAsset);\n\t\t\t\t\t\tif (asset && asset.resource !== this._sprite) {\n\t\t\t\t\t\t\t\tthis._bindSpriteAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._element.addModelToLayers(this._renderable.model);\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._element.removeModelFromLayers(this._renderable.model);\n\t\t}\n\t\t_setStencil(stencilParams) {\n\t\t\t\tthis._renderable.meshInstance.stencilFront = stencilParams;\n\t\t\t\tthis._renderable.meshInstance.stencilBack = stencilParams;\n\t\t\t\tlet ref = 0;\n\t\t\t\tif (this._element.maskedBy) {\n\t\t\t\t\t\tref = this._element.maskedBy.element._image._maskRef;\n\t\t\t\t}\n\t\t\t\tif (this._renderable.unmaskMeshInstance) {\n\t\t\t\t\t\tconst sp = new StencilParameters({\n\t\t\t\t\t\t\t\tref: ref + 1,\n\t\t\t\t\t\t\t\tfunc: FUNC_EQUAL,\n\t\t\t\t\t\t\t\tzpass: STENCILOP_DECREMENT\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis._renderable.unmaskMeshInstance.stencilFront = sp;\n\t\t\t\t\t\tthis._renderable.unmaskMeshInstance.stencilBack = sp;\n\t\t\t\t}\n\t\t}\n\t\t_updateRenderableEmissive() {\n\t\t\t\t_tempColor$1.linear(this._color);\n\t\t\t\tthis._colorUniform[0] = _tempColor$1.r;\n\t\t\t\tthis._colorUniform[1] = _tempColor$1.g;\n\t\t\t\tthis._colorUniform[2] = _tempColor$1.b;\n\t\t\t\tthis._renderable.setParameter('material_emissive', this._colorUniform);\n\t\t}\n\t\tset color(value) {\n\t\t\t\tconst { r, g, b } = value;\n\t\t\t\tif (this._color.r !== r || this._color.g !== g || this._color.b !== b) {\n\t\t\t\t\t\tthis._color.r = r;\n\t\t\t\t\t\tthis._color.g = g;\n\t\t\t\t\t\tthis._color.b = b;\n\t\t\t\t\t\tthis._updateRenderableEmissive();\n\t\t\t\t}\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis._element.fire('set:color', this._color);\n\t\t\t\t}\n\t\t}\n\t\tget color() {\n\t\t\t\treturn this._color;\n\t\t}\n\t\tset opacity(value) {\n\t\t\t\tif (value !== this._color.a) {\n\t\t\t\t\t\tthis._color.a = value;\n\t\t\t\t\t\tthis._renderable.setParameter('material_opacity', value);\n\t\t\t\t}\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis._element.fire('set:opacity', value);\n\t\t\t\t}\n\t\t}\n\t\tget opacity() {\n\t\t\t\treturn this._color.a;\n\t\t}\n\t\tset rect(value) {\n\t\t\t\tlet x, y, z, w;\n\t\t\t\tif (value instanceof Vec4) {\n\t\t\t\t\t\tx = value.x;\n\t\t\t\t\t\ty = value.y;\n\t\t\t\t\t\tz = value.z;\n\t\t\t\t\t\tw = value.w;\n\t\t\t\t} else {\n\t\t\t\t\t\tx = value[0];\n\t\t\t\t\t\ty = value[1];\n\t\t\t\t\t\tz = value[2];\n\t\t\t\t\t\tw = value[3];\n\t\t\t\t}\n\t\t\t\tif (x === this._rect.x && y === this._rect.y && z === this._rect.z && w === this._rect.w) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._rect.set(x, y, z, w);\n\t\t\t\tif (this._renderable.mesh) {\n\t\t\t\t\t\tif (!this._element._beingInitialized) {\n\t\t\t\t\t\t\t\tthis._updateMesh(this._renderable.mesh);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._meshDirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget rect() {\n\t\t\t\treturn this._rect;\n\t\t}\n\t\t_removeMaterialAssetEvents() {\n\t\t\t\tif (this._materialAsset) {\n\t\t\t\t\t\tconst assets = this._system.app.assets;\n\t\t\t\t\t\tassets.off(`add:${this._materialAsset}`, this._onMaterialAdded, this);\n\t\t\t\t\t\tconst asset = assets.get(this._materialAsset);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tasset.off('load', this._onMaterialLoad, this);\n\t\t\t\t\t\t\t\tasset.off('change', this._onMaterialChange, this);\n\t\t\t\t\t\t\t\tasset.off('remove', this._onMaterialRemove, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tset material(value) {\n\t\t\t\tif (this._material === value) return;\n\t\t\t\tif (!value) {\n\t\t\t\t\t\tconst screenSpace = this._element._isScreenSpace();\n\t\t\t\t\t\tif (this.mask) {\n\t\t\t\t\t\t\t\tvalue = screenSpace ? this._system.defaultScreenSpaceImageMaskMaterial : this._system.defaultImageMaskMaterial;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tvalue = screenSpace ? this._system.defaultScreenSpaceImageMaterial : this._system.defaultImageMaterial;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._material = value;\n\t\t\t\tif (this._materialAsset) {\n\t\t\t\t\t\tconst asset = this._system.app.assets.get(this._materialAsset);\n\t\t\t\t\t\tif (!asset || asset.resource !== value) {\n\t\t\t\t\t\t\t\tthis._removeMaterialAssetEvents();\n\t\t\t\t\t\t\t\tthis._materialAsset = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (value) {\n\t\t\t\t\t\tthis._renderable.setMaterial(value);\n\t\t\t\t\t\tif (this._hasUserMaterial()) {\n\t\t\t\t\t\t\t\tthis._renderable.deleteParameter('material_opacity');\n\t\t\t\t\t\t\t\tthis._renderable.deleteParameter('material_emissive');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._updateRenderableEmissive();\n\t\t\t\t\t\t\t\tthis._renderable.setParameter('material_opacity', this._color.a);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget material() {\n\t\t\t\treturn this._material;\n\t\t}\n\t\tset materialAsset(value) {\n\t\t\t\tconst assets = this._system.app.assets;\n\t\t\t\tlet _id = value;\n\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\t_id = value.id;\n\t\t\t\t}\n\t\t\t\tif (this._materialAsset !== _id) {\n\t\t\t\t\t\tthis._removeMaterialAssetEvents();\n\t\t\t\t\t\tthis._materialAsset = _id;\n\t\t\t\t\t\tif (this._materialAsset) {\n\t\t\t\t\t\t\t\tconst asset = assets.get(this._materialAsset);\n\t\t\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\t\t\tthis._materialAsset = null;\n\t\t\t\t\t\t\t\t\t\tthis.material = null;\n\t\t\t\t\t\t\t\t\t\tthis._materialAsset = _id;\n\t\t\t\t\t\t\t\t\t\tassets.on(`add:${this._materialAsset}`, this._onMaterialAdded, this);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._bindMaterialAsset(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._materialAsset = null;\n\t\t\t\t\t\t\t\tthis.material = null;\n\t\t\t\t\t\t\t\tthis._materialAsset = _id;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget materialAsset() {\n\t\t\t\treturn this._materialAsset;\n\t\t}\n\t\tset texture(value) {\n\t\t\t\tif (this._texture === value) return;\n\t\t\t\tif (this._textureAsset) {\n\t\t\t\t\t\tconst textureAsset = this._system.app.assets.get(this._textureAsset);\n\t\t\t\t\t\tif (textureAsset && textureAsset.resource !== value) {\n\t\t\t\t\t\t\t\tthis.textureAsset = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._texture = value;\n\t\t\t\tif (value) {\n\t\t\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\t\t\tthis.spriteAsset = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._renderable.setParameter('texture_emissiveMap', this._texture);\n\t\t\t\t\t\tthis._renderable.setParameter('texture_opacityMap', this._texture);\n\t\t\t\t\t\tthis._updateRenderableEmissive();\n\t\t\t\t\t\tthis._renderable.setParameter('material_opacity', this._color.a);\n\t\t\t\t\t\tconst newAspectRatio = this._texture.width / this._texture.height;\n\t\t\t\t\t\tif (newAspectRatio !== this._targetAspectRatio) {\n\t\t\t\t\t\t\t\tthis._targetAspectRatio = newAspectRatio;\n\t\t\t\t\t\t\t\tif (this._element.fitMode !== FITMODE_STRETCH) {\n\t\t\t\t\t\t\t\t\t\tthis.refreshMesh();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._renderable.deleteParameter('texture_emissiveMap');\n\t\t\t\t\t\tthis._renderable.deleteParameter('texture_opacityMap');\n\t\t\t\t\t\tthis._targetAspectRatio = -1;\n\t\t\t\t\t\tif (this._element.fitMode !== FITMODE_STRETCH) {\n\t\t\t\t\t\t\t\tthis.refreshMesh();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget texture() {\n\t\t\t\treturn this._texture;\n\t\t}\n\t\tset textureAsset(value) {\n\t\t\t\tconst assets = this._system.app.assets;\n\t\t\t\tlet _id = value;\n\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\t_id = value.id;\n\t\t\t\t}\n\t\t\t\tif (this._textureAsset !== _id) {\n\t\t\t\t\t\tif (this._textureAsset) {\n\t\t\t\t\t\t\t\tassets.off(`add:${this._textureAsset}`, this._onTextureAdded, this);\n\t\t\t\t\t\t\t\tconst _prev = assets.get(this._textureAsset);\n\t\t\t\t\t\t\t\tif (_prev) {\n\t\t\t\t\t\t\t\t\t\t_prev.off('load', this._onTextureLoad, this);\n\t\t\t\t\t\t\t\t\t\t_prev.off('change', this._onTextureChange, this);\n\t\t\t\t\t\t\t\t\t\t_prev.off('remove', this._onTextureRemove, this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._textureAsset = _id;\n\t\t\t\t\t\tif (this._textureAsset) {\n\t\t\t\t\t\t\t\tconst asset = assets.get(this._textureAsset);\n\t\t\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\t\t\tthis.texture = null;\n\t\t\t\t\t\t\t\t\t\tassets.on(`add:${this._textureAsset}`, this._onTextureAdded, this);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._bindTextureAsset(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.texture = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget textureAsset() {\n\t\t\t\treturn this._textureAsset;\n\t\t}\n\t\tset spriteAsset(value) {\n\t\t\t\tconst assets = this._system.app.assets;\n\t\t\t\tlet _id = value;\n\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\t_id = value.id;\n\t\t\t\t}\n\t\t\t\tif (this._spriteAsset !== _id) {\n\t\t\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\t\t\tassets.off(`add:${this._spriteAsset}`, this._onSpriteAssetAdded, this);\n\t\t\t\t\t\t\t\tconst _prev = assets.get(this._spriteAsset);\n\t\t\t\t\t\t\t\tif (_prev) {\n\t\t\t\t\t\t\t\t\t\tthis._unbindSpriteAsset(_prev);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._spriteAsset = _id;\n\t\t\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\t\t\tconst asset = assets.get(this._spriteAsset);\n\t\t\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\t\t\tthis.sprite = null;\n\t\t\t\t\t\t\t\t\t\tassets.on(`add:${this._spriteAsset}`, this._onSpriteAssetAdded, this);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._bindSpriteAsset(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.sprite = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis._element.fire('set:spriteAsset', _id);\n\t\t\t\t}\n\t\t}\n\t\tget spriteAsset() {\n\t\t\t\treturn this._spriteAsset;\n\t\t}\n\t\tset sprite(value) {\n\t\t\t\tif (this._sprite === value) return;\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._unbindSprite(this._sprite);\n\t\t\t\t}\n\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\tconst spriteAsset = this._system.app.assets.get(this._spriteAsset);\n\t\t\t\t\t\tif (spriteAsset && spriteAsset.resource !== value) {\n\t\t\t\t\t\t\t\tthis.spriteAsset = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._sprite = value;\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._bindSprite(this._sprite);\n\t\t\t\t\t\tif (this._textureAsset) {\n\t\t\t\t\t\t\t\tthis.textureAsset = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._sprite && this._sprite.atlas && this._sprite.atlas.texture) {\n\t\t\t\t\t\tthis._renderable.setParameter('texture_emissiveMap', this._sprite.atlas.texture);\n\t\t\t\t\t\tthis._renderable.setParameter('texture_opacityMap', this._sprite.atlas.texture);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._renderable.deleteParameter('texture_emissiveMap');\n\t\t\t\t\t\tthis._renderable.deleteParameter('texture_opacityMap');\n\t\t\t\t}\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._spriteFrame = math.clamp(this._spriteFrame, 0, this._sprite.frameKeys.length - 1);\n\t\t\t\t}\n\t\t\t\tthis._updateSprite();\n\t\t}\n\t\tget sprite() {\n\t\t\t\treturn this._sprite;\n\t\t}\n\t\tset spriteFrame(value) {\n\t\t\t\tconst oldValue = this._spriteFrame;\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._spriteFrame = math.clamp(value, 0, this._sprite.frameKeys.length - 1);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._spriteFrame = value;\n\t\t\t\t}\n\t\t\t\tif (this._spriteFrame !== oldValue) {\n\t\t\t\t\t\tthis._updateSprite();\n\t\t\t\t}\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis._element.fire('set:spriteFrame', value);\n\t\t\t\t}\n\t\t}\n\t\tget spriteFrame() {\n\t\t\t\treturn this._spriteFrame;\n\t\t}\n\t\tset mesh(value) {\n\t\t\t\tthis._renderable.setMesh(value);\n\t\t\t\tif (this._defaultMesh === value) {\n\t\t\t\t\t\tthis._renderable.setAabbFunc(null);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._renderable.setAabbFunc(this._updateAabbFunc);\n\t\t\t\t}\n\t\t}\n\t\tget mesh() {\n\t\t\t\treturn this._renderable.mesh;\n\t\t}\n\t\tset mask(value) {\n\t\t\t\tif (this._mask !== value) {\n\t\t\t\t\t\tthis._mask = value;\n\t\t\t\t\t\tthis._toggleMask();\n\t\t\t\t}\n\t\t}\n\t\tget mask() {\n\t\t\t\treturn this._mask;\n\t\t}\n\t\tset pixelsPerUnit(value) {\n\t\t\t\tif (this._pixelsPerUnit === value) return;\n\t\t\t\tthis._pixelsPerUnit = value;\n\t\t\t\tif (this._sprite && (this._sprite.renderMode === SPRITE_RENDERMODE_SLICED || this._sprite.renderMode === SPRITE_RENDERMODE_TILED)) {\n\t\t\t\t\t\tthis._updateSprite();\n\t\t\t\t}\n\t\t}\n\t\tget pixelsPerUnit() {\n\t\t\t\treturn this._pixelsPerUnit;\n\t\t}\n\t\tget aabb() {\n\t\t\t\tif (this._renderable.meshInstance) {\n\t\t\t\t\t\treturn this._renderable.meshInstance.aabb;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n}\n\nclass LocalizedAsset extends EventHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper();\n\t\t\t\tthis._app = app;\n\t\t\t\tapp.i18n.on(I18n.EVENT_CHANGE, this._onSetLocale, this);\n\t\t\t\tthis._autoLoad = false;\n\t\t\t\tthis._disableLocalization = false;\n\t\t\t\tthis._defaultAsset = null;\n\t\t\t\tthis._localizedAsset = null;\n\t\t}\n\t\tset defaultAsset(value) {\n\t\t\t\tconst id = value instanceof Asset ? value.id : value;\n\t\t\t\tif (this._defaultAsset === id) return;\n\t\t\t\tif (this._defaultAsset) {\n\t\t\t\t\t\tthis._unbindDefaultAsset();\n\t\t\t\t}\n\t\t\t\tthis._defaultAsset = id;\n\t\t\t\tif (this._defaultAsset) {\n\t\t\t\t\t\tthis._bindDefaultAsset();\n\t\t\t\t}\n\t\t\t\tthis._onSetLocale(this._app.i18n.locale);\n\t\t}\n\t\tget defaultAsset() {\n\t\t\t\treturn this._defaultAsset;\n\t\t}\n\t\tset localizedAsset(value) {\n\t\t\t\tconst id = value instanceof Asset ? value.id : value;\n\t\t\t\tif (this._localizedAsset === id) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._localizedAsset) {\n\t\t\t\t\t\tthis._app.assets.off(`add:${this._localizedAsset}`, this._onLocalizedAssetAdd, this);\n\t\t\t\t\t\tthis._unbindLocalizedAsset();\n\t\t\t\t}\n\t\t\t\tthis._localizedAsset = id;\n\t\t\t\tif (this._localizedAsset) {\n\t\t\t\t\t\tconst asset = this._app.assets.get(this._localizedAsset);\n\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\tthis._app.assets.once(`add:${this._localizedAsset}`, this._onLocalizedAssetAdd, this);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._bindLocalizedAsset();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget localizedAsset() {\n\t\t\t\treturn this._localizedAsset;\n\t\t}\n\t\tset autoLoad(value) {\n\t\t\t\tif (this._autoLoad === value) return;\n\t\t\t\tthis._autoLoad = value;\n\t\t\t\tif (this._autoLoad && this._localizedAsset) {\n\t\t\t\t\t\tthis._unbindLocalizedAsset();\n\t\t\t\t\t\tthis._bindLocalizedAsset();\n\t\t\t\t}\n\t\t}\n\t\tget autoLoad() {\n\t\t\t\treturn this._autoLoad;\n\t\t}\n\t\tset disableLocalization(value) {\n\t\t\t\tif (this._disableLocalization === value) return;\n\t\t\t\tthis._disableLocalization = value;\n\t\t\t\tthis._onSetLocale(this._app.i18n.locale);\n\t\t}\n\t\tget disableLocalization() {\n\t\t\t\treturn this._disableLocalization;\n\t\t}\n\t\t_bindDefaultAsset() {\n\t\t\t\tconst asset = this._app.assets.get(this._defaultAsset);\n\t\t\t\tif (!asset) {\n\t\t\t\t\t\tthis._app.assets.once(`add:${this._defaultAsset}`, this._onDefaultAssetAdd, this);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._onDefaultAssetAdd(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindDefaultAsset() {\n\t\t\t\tif (!this._defaultAsset) return;\n\t\t\t\tthis._app.assets.off(`add:${this._defaultAsset}`, this._onDefaultAssetAdd, this);\n\t\t\t\tconst asset = this._app.assets.get(this._defaultAsset);\n\t\t\t\tif (!asset) return;\n\t\t\t\tasset.off('add:localized', this._onLocaleAdd, this);\n\t\t\t\tasset.off('remove:localized', this._onLocaleRemove, this);\n\t\t\t\tasset.off('remove', this._onDefaultAssetRemove, this);\n\t\t}\n\t\t_onDefaultAssetAdd(asset) {\n\t\t\t\tif (this._defaultAsset !== asset.id) return;\n\t\t\t\tasset.on('add:localized', this._onLocaleAdd, this);\n\t\t\t\tasset.on('remove:localized', this._onLocaleRemove, this);\n\t\t\t\tasset.once('remove', this._onDefaultAssetRemove, this);\n\t\t}\n\t\t_onDefaultAssetRemove(asset) {\n\t\t\t\tif (this._defaultAsset !== asset.id) return;\n\t\t\t\tasset.off('add:localized', this._onLocaleAdd, this);\n\t\t\t\tasset.off('remove:localized', this._onLocaleAdd, this);\n\t\t\t\tthis._app.assets.once(`add:${this._defaultAsset}`, this._onDefaultAssetAdd, this);\n\t\t}\n\t\t_bindLocalizedAsset() {\n\t\t\t\tif (!this._autoLoad) return;\n\t\t\t\tconst asset = this._app.assets.get(this._localizedAsset);\n\t\t\t\tif (!asset) return;\n\t\t\t\tasset.on('load', this._onLocalizedAssetLoad, this);\n\t\t\t\tasset.on('change', this._onLocalizedAssetChange, this);\n\t\t\t\tasset.on('remove', this._onLocalizedAssetRemove, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onLocalizedAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindLocalizedAsset() {\n\t\t\t\tconst asset = this._app.assets.get(this._localizedAsset);\n\t\t\t\tif (!asset) return;\n\t\t\t\tasset.off('load', this._onLocalizedAssetLoad, this);\n\t\t\t\tasset.off('change', this._onLocalizedAssetChange, this);\n\t\t\t\tasset.off('remove', this._onLocalizedAssetRemove, this);\n\t\t}\n\t\t_onLocalizedAssetAdd(asset) {\n\t\t\t\tif (this._localizedAsset !== asset.id) return;\n\t\t\t\tthis._bindLocalizedAsset();\n\t\t}\n\t\t_onLocalizedAssetLoad(asset) {\n\t\t\t\tthis.fire('load', asset);\n\t\t}\n\t\t_onLocalizedAssetChange(asset, name, newValue, oldValue) {\n\t\t\t\tthis.fire('change', asset, name, newValue, oldValue);\n\t\t}\n\t\t_onLocalizedAssetRemove(asset) {\n\t\t\t\tif (this._localizedAsset === asset.id) {\n\t\t\t\t\t\tthis.localizedAsset = this._defaultAsset;\n\t\t\t\t}\n\t\t\t\tthis.fire('remove', asset);\n\t\t}\n\t\t_onLocaleAdd(locale, assetId) {\n\t\t\t\tif (this._app.i18n.locale !== locale) return;\n\t\t\t\tthis._onSetLocale(locale);\n\t\t}\n\t\t_onLocaleRemove(locale, assetId) {\n\t\t\t\tif (this._app.i18n.locale !== locale) return;\n\t\t\t\tthis._onSetLocale(locale);\n\t\t}\n\t\t_onSetLocale(locale) {\n\t\t\t\tif (!this._defaultAsset) {\n\t\t\t\t\t\tthis.localizedAsset = null;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst asset = this._app.assets.get(this._defaultAsset);\n\t\t\t\tif (!asset || this._disableLocalization) {\n\t\t\t\t\t\tthis.localizedAsset = this._defaultAsset;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst localizedAssetId = asset.getLocalizedAssetId(locale);\n\t\t\t\tif (!localizedAssetId) {\n\t\t\t\t\t\tthis.localizedAsset = this._defaultAsset;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.localizedAsset = localizedAssetId;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.defaultAsset = null;\n\t\t\t\tthis._app.i18n.off(I18n.EVENT_CHANGE, this._onSetLocale, this);\n\t\t\t\tthis.off();\n\t\t}\n}\n\nconst FONT_MSDF = 'msdf';\nconst FONT_BITMAP = 'bitmap';\n\nconst EOF_TOKEN = 0;\nconst ERROR_TOKEN = 1;\nconst TEXT_TOKEN = 2;\nconst OPEN_BRACKET_TOKEN = 3;\nconst CLOSE_BRACKET_TOKEN = 4;\nconst EQUALS_TOKEN = 5;\nconst STRING_TOKEN = 6;\nconst IDENTIFIER_TOKEN = 7;\nconst WHITESPACE_TOKEN = 8;\nconst WHITESPACE_CHARS = ' \\t\\n\\r\\v\\f';\nconst IDENTIFIER_REGEX = /[\\w|/]/;\nclass Scanner {\n\t\tconstructor(symbols){\n\t\t\t\tthis._symbols = symbols;\n\t\t\t\tthis._index = 0;\n\t\t\t\tthis._last = 0;\n\t\t\t\tthis._cur = this._symbols.length > 0 ? this._symbols[0] : null;\n\t\t\t\tthis._buf = [];\n\t\t\t\tthis._mode = 'text';\n\t\t\t\tthis._error = null;\n\t\t}\n\t\tread() {\n\t\t\t\tlet token = this._read();\n\t\t\t\twhile(token === WHITESPACE_TOKEN){\n\t\t\t\t\t\ttoken = this._read();\n\t\t\t\t}\n\t\t\t\tif (token !== EOF_TOKEN && token !== ERROR_TOKEN) {\n\t\t\t\t\t\tthis._last = this._index;\n\t\t\t\t}\n\t\t\t\treturn token;\n\t\t}\n\t\tbuf() {\n\t\t\t\treturn this._buf;\n\t\t}\n\t\tlast() {\n\t\t\t\treturn this._last;\n\t\t}\n\t\terror() {\n\t\t\t\treturn this._error;\n\t\t}\n\t\tdebugPrint() {\n\t\t\t\tconst tokenStrings = [\n\t\t\t\t\t\t'EOF',\n\t\t\t\t\t\t'ERROR',\n\t\t\t\t\t\t'TEXT',\n\t\t\t\t\t\t'OPEN_BRACKET',\n\t\t\t\t\t\t'CLOSE_BRACKET',\n\t\t\t\t\t\t'EQUALS',\n\t\t\t\t\t\t'STRING',\n\t\t\t\t\t\t'IDENTIFIER',\n\t\t\t\t\t\t'WHITESPACE'\n\t\t\t\t];\n\t\t\t\tlet token = this.read();\n\t\t\t\tlet result = '';\n\t\t\t\twhile(true){\n\t\t\t\t\t\tresult += `${(result.length > 0 ? '\\n' : '') + tokenStrings[token]} '${this.buf().join('')}'`;\n\t\t\t\t\t\tif (token === EOF_TOKEN || token === ERROR_TOKEN) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttoken = this.read();\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\t_read() {\n\t\t\t\tthis._buf = [];\n\t\t\t\tif (this._eof()) {\n\t\t\t\t\t\treturn EOF_TOKEN;\n\t\t\t\t}\n\t\t\t\treturn this._mode === 'text' ? this._text() : this._tag();\n\t\t}\n\t\t_text() {\n\t\t\t\twhile(true){\n\t\t\t\t\t\tswitch(this._cur){\n\t\t\t\t\t\t\t\tcase null:\n\t\t\t\t\t\t\t\t\t\treturn this._buf.length > 0 ? TEXT_TOKEN : EOF_TOKEN;\n\t\t\t\t\t\t\t\tcase '[':\n\t\t\t\t\t\t\t\t\t\tthis._mode = 'tag';\n\t\t\t\t\t\t\t\t\t\treturn this._buf.length > 0 ? TEXT_TOKEN : this._tag();\n\t\t\t\t\t\t\t\tcase '\\\\':\n\t\t\t\t\t\t\t\t\t\tthis._next();\n\t\t\t\t\t\t\t\t\t\tswitch(this._cur){\n\t\t\t\t\t\t\t\t\t\t\t\tcase '[':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._store();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._output('\\\\');\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tthis._store();\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_tag() {\n\t\t\t\tswitch(this._cur){\n\t\t\t\t\t\tcase null:\n\t\t\t\t\t\t\t\tthis._error = 'unexpected end of input reading tag';\n\t\t\t\t\t\t\t\treturn ERROR_TOKEN;\n\t\t\t\t\t\tcase '[':\n\t\t\t\t\t\t\t\tthis._store();\n\t\t\t\t\t\t\t\treturn OPEN_BRACKET_TOKEN;\n\t\t\t\t\t\tcase ']':\n\t\t\t\t\t\t\t\tthis._store();\n\t\t\t\t\t\t\t\tthis._mode = 'text';\n\t\t\t\t\t\t\t\treturn CLOSE_BRACKET_TOKEN;\n\t\t\t\t\t\tcase '=':\n\t\t\t\t\t\t\t\tthis._store();\n\t\t\t\t\t\t\t\treturn EQUALS_TOKEN;\n\t\t\t\t\t\tcase ' ':\n\t\t\t\t\t\tcase '\\t':\n\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\tcase '\\r':\n\t\t\t\t\t\tcase '\\v':\n\t\t\t\t\t\tcase '\\f':\n\t\t\t\t\t\t\t\treturn this._whitespace();\n\t\t\t\t\t\tcase '\"':\n\t\t\t\t\t\t\t\treturn this._string();\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tif (!this._isIdentifierSymbol(this._cur)) {\n\t\t\t\t\t\t\t\t\t\tthis._error = 'unrecognized character';\n\t\t\t\t\t\t\t\t\t\treturn ERROR_TOKEN;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn this._identifier();\n\t\t\t\t}\n\t\t}\n\t\t_whitespace() {\n\t\t\t\tthis._store();\n\t\t\t\twhile(WHITESPACE_CHARS.indexOf(this._cur) !== -1){\n\t\t\t\t\t\tthis._store();\n\t\t\t\t}\n\t\t\t\treturn WHITESPACE_TOKEN;\n\t\t}\n\t\t_string() {\n\t\t\t\tthis._next();\n\t\t\t\twhile(true){\n\t\t\t\t\t\tswitch(this._cur){\n\t\t\t\t\t\t\t\tcase null:\n\t\t\t\t\t\t\t\t\t\tthis._error = 'unexpected end of input reading string';\n\t\t\t\t\t\t\t\t\t\treturn ERROR_TOKEN;\n\t\t\t\t\t\t\t\tcase '\"':\n\t\t\t\t\t\t\t\t\t\tthis._next();\n\t\t\t\t\t\t\t\t\t\treturn STRING_TOKEN;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tthis._store();\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_identifier() {\n\t\t\t\tthis._store();\n\t\t\t\twhile(this._cur !== null && this._isIdentifierSymbol(this._cur)){\n\t\t\t\t\t\tthis._store();\n\t\t\t\t}\n\t\t\t\treturn IDENTIFIER_TOKEN;\n\t\t}\n\t\t_isIdentifierSymbol(s) {\n\t\t\t\treturn s.length === 1 && s.match(IDENTIFIER_REGEX) !== null;\n\t\t}\n\t\t_eof() {\n\t\t\t\treturn this._cur === null;\n\t\t}\n\t\t_next() {\n\t\t\t\tif (!this._eof()) {\n\t\t\t\t\t\tthis._index++;\n\t\t\t\t\t\tthis._cur = this._index < this._symbols.length ? this._symbols[this._index] : null;\n\t\t\t\t}\n\t\t\t\treturn this._cur;\n\t\t}\n\t\t_store() {\n\t\t\t\tthis._buf.push(this._cur);\n\t\t\t\treturn this._next();\n\t\t}\n\t\t_output(c) {\n\t\t\t\tthis._buf.push(c);\n\t\t}\n}\nclass Parser {\n\t\tconstructor(symbols){\n\t\t\t\tthis._scanner = new Scanner(symbols);\n\t\t\t\tthis._error = null;\n\t\t}\n\t\tparse(symbols, tags) {\n\t\t\t\twhile(true){\n\t\t\t\t\t\tconst token = this._scanner.read();\n\t\t\t\t\t\tswitch(token){\n\t\t\t\t\t\t\t\tcase EOF_TOKEN:\n\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\tcase ERROR_TOKEN:\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\tcase TEXT_TOKEN:\n\t\t\t\t\t\t\t\t\t\tArray.prototype.push.apply(symbols, this._scanner.buf());\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase OPEN_BRACKET_TOKEN:\n\t\t\t\t\t\t\t\t\t\tif (!this._parseTag(symbols, tags)) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\terror() {\n\t\t\t\treturn `Error evaluating markup at #${this._scanner.last().toString()} (${this._scanner.error() || this._error})`;\n\t\t}\n\t\t_parseTag(symbols, tags) {\n\t\t\t\tlet token = this._scanner.read();\n\t\t\t\tif (token !== IDENTIFIER_TOKEN) {\n\t\t\t\t\t\tthis._error = 'expected identifier';\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst name = this._scanner.buf().join('');\n\t\t\t\tif (name[0] === '/') {\n\t\t\t\t\t\tfor(let index = tags.length - 1; index >= 0; --index){\n\t\t\t\t\t\t\t\tif (name === `/${tags[index].name}` && tags[index].end === null) {\n\t\t\t\t\t\t\t\t\t\ttags[index].end = symbols.length;\n\t\t\t\t\t\t\t\t\t\ttoken = this._scanner.read();\n\t\t\t\t\t\t\t\t\t\tif (token !== CLOSE_BRACKET_TOKEN) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._error = 'expected close bracket';\n\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._error = 'failed to find matching tag';\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst tag = {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\tvalue: null,\n\t\t\t\t\t\tattributes: {},\n\t\t\t\t\t\tstart: symbols.length,\n\t\t\t\t\t\tend: null\n\t\t\t\t};\n\t\t\t\ttoken = this._scanner.read();\n\t\t\t\tif (token === EQUALS_TOKEN) {\n\t\t\t\t\t\ttoken = this._scanner.read();\n\t\t\t\t\t\tif (token !== STRING_TOKEN) {\n\t\t\t\t\t\t\t\tthis._error = 'expected string';\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttag.value = this._scanner.buf().join('');\n\t\t\t\t\t\ttoken = this._scanner.read();\n\t\t\t\t}\n\t\t\t\twhile(true){\n\t\t\t\t\t\tswitch(token){\n\t\t\t\t\t\t\t\tcase CLOSE_BRACKET_TOKEN:\n\t\t\t\t\t\t\t\t\t\ttags.push(tag);\n\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\tcase IDENTIFIER_TOKEN:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tconst identifier = this._scanner.buf().join('');\n\t\t\t\t\t\t\t\t\t\t\t\ttoken = this._scanner.read();\n\t\t\t\t\t\t\t\t\t\t\t\tif (token !== EQUALS_TOKEN) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._error = 'expected equals';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\ttoken = this._scanner.read();\n\t\t\t\t\t\t\t\t\t\t\t\tif (token !== STRING_TOKEN) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._error = 'expected string';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst value = this._scanner.buf().join('');\n\t\t\t\t\t\t\t\t\t\t\t\ttag.attributes[identifier] = value;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tthis._error = 'expected close bracket or identifier';\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttoken = this._scanner.read();\n\t\t\t\t}\n\t\t}\n}\nfunction merge(target, source) {\n\t\tfor(const key in source){\n\t\t\t\tif (!source.hasOwnProperty(key)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst value = source[key];\n\t\t\t\tif (value instanceof Object) {\n\t\t\t\t\t\tif (!target.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\ttarget[key] = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmerge(target[key], source[key]);\n\t\t\t\t} else {\n\t\t\t\t\t\ttarget[key] = value;\n\t\t\t\t}\n\t\t}\n}\nfunction combineTags(tags) {\n\t\tif (tags.length === 0) {\n\t\t\t\treturn null;\n\t\t}\n\t\tconst result = {};\n\t\tfor(let index = 0; index < tags.length; ++index){\n\t\t\t\tconst tag = tags[index];\n\t\t\t\tconst tmp = {};\n\t\t\t\ttmp[tag.name] = {\n\t\t\t\t\t\tvalue: tag.value,\n\t\t\t\t\t\tattributes: tag.attributes\n\t\t\t\t};\n\t\t\t\tmerge(result, tmp);\n\t\t}\n\t\treturn result;\n}\nfunction resolveMarkupTags(tags, numSymbols) {\n\t\tif (tags.length === 0) {\n\t\t\t\treturn null;\n\t\t}\n\t\tconst edges = {};\n\t\tfor(let index = 0; index < tags.length; ++index){\n\t\t\t\tconst tag = tags[index];\n\t\t\t\tif (!edges.hasOwnProperty(tag.start)) {\n\t\t\t\t\t\tedges[tag.start] = {\n\t\t\t\t\t\t\t\topen: [\n\t\t\t\t\t\t\t\t\t\ttag\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tclose: null\n\t\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\t\tif (edges[tag.start].open === null) {\n\t\t\t\t\t\t\t\tedges[tag.start].open = [\n\t\t\t\t\t\t\t\t\t\ttag\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tedges[tag.start].open.push(tag);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!edges.hasOwnProperty(tag.end)) {\n\t\t\t\t\t\tedges[tag.end] = {\n\t\t\t\t\t\t\t\topen: null,\n\t\t\t\t\t\t\t\tclose: [\n\t\t\t\t\t\t\t\t\t\ttag\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\t\tif (edges[tag.end].close === null) {\n\t\t\t\t\t\t\t\tedges[tag.end].close = [\n\t\t\t\t\t\t\t\t\t\ttag\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tedges[tag.end].close.push(tag);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tlet tagStack = [];\n\t\tfunction removeTags(tags) {\n\t\t\t\ttagStack = tagStack.filter((tag)=>{\n\t\t\t\t\t\treturn tags.find((t)=>{\n\t\t\t\t\t\t\t\treturn t === tag;\n\t\t\t\t\t\t}) === undefined;\n\t\t\t\t});\n\t\t}\n\t\tfunction addTags(tags) {\n\t\t\t\tfor(let index = 0; index < tags.length; ++index){\n\t\t\t\t\t\ttagStack.push(tags[index]);\n\t\t\t\t}\n\t\t}\n\t\tconst edgeKeys = Object.keys(edges).sort((a, b)=>{\n\t\t\t\treturn a - b;\n\t\t});\n\t\tconst resolvedTags = [];\n\t\tfor(let index = 0; index < edgeKeys.length; ++index){\n\t\t\t\tconst edge = edges[edgeKeys[index]];\n\t\t\t\tif (edge.close !== null) {\n\t\t\t\t\t\tremoveTags(edge.close);\n\t\t\t\t}\n\t\t\t\tif (edge.open !== null) {\n\t\t\t\t\t\taddTags(edge.open);\n\t\t\t\t}\n\t\t\t\tresolvedTags.push({\n\t\t\t\t\t\tstart: edgeKeys[index],\n\t\t\t\t\t\ttags: combineTags(tagStack)\n\t\t\t\t});\n\t\t}\n\t\tconst result = [];\n\t\tlet prevTag = null;\n\t\tfor(let index = 0; index < resolvedTags.length; ++index){\n\t\t\t\tconst resolvedTag = resolvedTags[index];\n\t\t\t\twhile(result.length < resolvedTag.start){\n\t\t\t\t\t\tresult.push(prevTag ? prevTag.tags : null);\n\t\t\t\t}\n\t\t\t\tprevTag = resolvedTag;\n\t\t}\n\t\twhile(result.length < numSymbols){\n\t\t\t\tresult.push(null);\n\t\t}\n\t\treturn result;\n}\nfunction evaluateMarkup(symbols) {\n\t\tconst parser = new Parser(symbols);\n\t\tconst stripped_symbols = [];\n\t\tconst tags = [];\n\t\tif (!parser.parse(stripped_symbols, tags)) {\n\t\t\t\tconsole.warn(parser.error());\n\t\t\t\treturn {\n\t\t\t\t\t\tsymbols: symbols,\n\t\t\t\t\t\ttags: null\n\t\t\t\t};\n\t\t}\n\t\tconst invalidTag = tags.find((t)=>{\n\t\t\t\treturn t.end === null;\n\t\t});\n\t\tif (invalidTag) {\n\t\t\t\tconsole.warn(`Markup error: found unclosed tag='${invalidTag.name}'`);\n\t\t\t\treturn {\n\t\t\t\t\t\tsymbols: symbols,\n\t\t\t\t\t\ttags: null\n\t\t\t\t};\n\t\t}\n\t\tconst resolved_tags = resolveMarkupTags(tags, stripped_symbols.length);\n\t\treturn {\n\t\t\t\tsymbols: stripped_symbols,\n\t\t\t\ttags: resolved_tags\n\t\t};\n}\nclass Markup {\n\t\tstatic evaluate(symbols) {\n\t\t\t\treturn evaluateMarkup(symbols);\n\t\t}\n}\n\nclass MeshInfo {\n\t\tconstructor(){\n\t\t\t\tthis.count = 0;\n\t\t\t\tthis.quad = 0;\n\t\t\t\tthis.lines = {};\n\t\t\t\tthis.positions = [];\n\t\t\t\tthis.normals = [];\n\t\t\t\tthis.uvs = [];\n\t\t\t\tthis.colors = [];\n\t\t\t\tthis.indices = [];\n\t\t\t\tthis.outlines = [];\n\t\t\t\tthis.shadows = [];\n\t\t\t\tthis.meshInstance = null;\n\t\t}\n}\nfunction createTextMesh(device, meshInfo) {\n\t\tconst mesh = new Mesh(device);\n\t\tmesh.setPositions(meshInfo.positions);\n\t\tmesh.setNormals(meshInfo.normals);\n\t\tmesh.setColors32(meshInfo.colors);\n\t\tmesh.setUvs(0, meshInfo.uvs);\n\t\tmesh.setIndices(meshInfo.indices);\n\t\tmesh.setVertexStream(SEMANTIC_ATTR8, meshInfo.outlines, 3, undefined, TYPE_FLOAT32, false);\n\t\tmesh.setVertexStream(SEMANTIC_ATTR9, meshInfo.shadows, 3, undefined, TYPE_FLOAT32, false);\n\t\tmesh.update();\n\t\treturn mesh;\n}\nconst LINE_BREAK_CHAR = /^[\\r\\n]$/;\nconst WHITESPACE_CHAR = /^[ \\t]$/;\nconst WORD_BOUNDARY_CHAR = /^[ \\t\\-]|\\u200b$/;\nconst ALPHANUMERIC_CHAR = /^[a-z0-9]$/i;\nconst CJK_CHAR = /^[\\u1100-\\u11ff]|[\\u3000-\\u9fff\\ua960-\\ua97f]|[\\uac00-\\ud7ff]$/;\nconst NO_LINE_BREAK_CJK_CHAR = /^[〕〉》」』】〙〗〟ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻]$/;\nconst CONTROL_CHARS = [\n\t\t'\\u200B',\n\t\t'\\u061C',\n\t\t'\\u200E',\n\t\t'\\u200F',\n\t\t'\\u202A',\n\t\t'\\u202B',\n\t\t'\\u202C',\n\t\t'\\u202D',\n\t\t'\\u202E',\n\t\t'\\u2066',\n\t\t'\\u2067',\n\t\t'\\u2068',\n\t\t'\\u2069'\n];\nconst CONTROL_GLYPH_DATA = {\n\t\twidth: 0,\n\t\theight: 0,\n\t\txadvance: 0,\n\t\txoffset: 0,\n\t\tyoffset: 0\n};\nconst colorTmp = new Color();\nconst vec2Tmp = new Vec2();\nconst _tempColor = new Color();\nclass TextElement {\n\t\tconstructor(element){\n\t\t\t\tthis._element = element;\n\t\t\t\tthis._system = element.system;\n\t\t\t\tthis._entity = element.entity;\n\t\t\t\tthis._text = '';\n\t\t\t\tthis._symbols = [];\n\t\t\t\tthis._colorPalette = [];\n\t\t\t\tthis._outlinePalette = [];\n\t\t\t\tthis._shadowPalette = [];\n\t\t\t\tthis._symbolColors = null;\n\t\t\t\tthis._symbolOutlineParams = null;\n\t\t\t\tthis._symbolShadowParams = null;\n\t\t\t\tthis._i18nKey = null;\n\t\t\t\tthis._fontAsset = new LocalizedAsset(this._system.app);\n\t\t\t\tthis._fontAsset.disableLocalization = true;\n\t\t\t\tthis._fontAsset.on('load', this._onFontLoad, this);\n\t\t\t\tthis._fontAsset.on('change', this._onFontChange, this);\n\t\t\t\tthis._fontAsset.on('remove', this._onFontRemove, this);\n\t\t\t\tthis._font = null;\n\t\t\t\tthis._color = new Color(1, 1, 1, 1);\n\t\t\t\tthis._colorUniform = new Float32Array(3);\n\t\t\t\tthis._spacing = 1;\n\t\t\t\tthis._fontSize = 32;\n\t\t\t\tthis._fontMinY = 0;\n\t\t\t\tthis._fontMaxY = 0;\n\t\t\t\tthis._originalFontSize = 32;\n\t\t\t\tthis._maxFontSize = 32;\n\t\t\t\tthis._minFontSize = 8;\n\t\t\t\tthis._autoFitWidth = false;\n\t\t\t\tthis._autoFitHeight = false;\n\t\t\t\tthis._maxLines = -1;\n\t\t\t\tthis._lineHeight = 32;\n\t\t\t\tthis._scaledLineHeight = 32;\n\t\t\t\tthis._wrapLines = false;\n\t\t\t\tthis._drawOrder = 0;\n\t\t\t\tthis._alignment = new Vec2(0.5, 0.5);\n\t\t\t\tthis._autoWidth = true;\n\t\t\t\tthis._autoHeight = true;\n\t\t\t\tthis.width = 0;\n\t\t\t\tthis.height = 0;\n\t\t\t\tthis._node = new GraphNode();\n\t\t\t\tthis._model = new Model();\n\t\t\t\tthis._model.graph = this._node;\n\t\t\t\tthis._entity.addChild(this._node);\n\t\t\t\tthis._meshInfo = [];\n\t\t\t\tthis._material = null;\n\t\t\t\tthis._aabbDirty = true;\n\t\t\t\tthis._aabb = new BoundingBox();\n\t\t\t\tthis._noResize = false;\n\t\t\t\tthis._currentMaterialType = null;\n\t\t\t\tthis._maskedMaterialSrc = null;\n\t\t\t\tthis._rtlReorder = false;\n\t\t\t\tthis._unicodeConverter = false;\n\t\t\t\tthis._rtl = false;\n\t\t\t\tthis._outlineColor = new Color(0, 0, 0, 1);\n\t\t\t\tthis._outlineColorUniform = new Float32Array(4);\n\t\t\t\tthis._outlineThicknessScale = 0.2;\n\t\t\t\tthis._outlineThickness = 0.0;\n\t\t\t\tthis._shadowColor = new Color(0, 0, 0, 1);\n\t\t\t\tthis._shadowColorUniform = new Float32Array(4);\n\t\t\t\tthis._shadowOffsetScale = 0.005;\n\t\t\t\tthis._shadowOffset = new Vec2(0, 0);\n\t\t\t\tthis._shadowOffsetUniform = new Float32Array(2);\n\t\t\t\tthis._enableMarkup = false;\n\t\t\t\tthis._onScreenChange(this._element.screen);\n\t\t\t\telement.on('resize', this._onParentResize, this);\n\t\t\t\telement.on('set:screen', this._onScreenChange, this);\n\t\t\t\telement.on('screen:set:screenspace', this._onScreenSpaceChange, this);\n\t\t\t\telement.on('set:draworder', this._onDrawOrderChange, this);\n\t\t\t\telement.on('set:pivot', this._onPivotChange, this);\n\t\t\t\tthis._system.app.i18n.on(I18n.EVENT_CHANGE, this._onLocaleSet, this);\n\t\t\t\tthis._system.app.i18n.on('data:add', this._onLocalizationData, this);\n\t\t\t\tthis._system.app.i18n.on('data:remove', this._onLocalizationData, this);\n\t\t\t\tthis._rangeStart = 0;\n\t\t\t\tthis._rangeEnd = 0;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._setMaterial(null);\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis._element.removeModelFromLayers(this._model);\n\t\t\t\t\t\tthis._model.destroy();\n\t\t\t\t\t\tthis._model = null;\n\t\t\t\t}\n\t\t\t\tthis._fontAsset.destroy();\n\t\t\t\tthis.font = null;\n\t\t\t\tthis._element.off('resize', this._onParentResize, this);\n\t\t\t\tthis._element.off('set:screen', this._onScreenChange, this);\n\t\t\t\tthis._element.off('screen:set:screenspace', this._onScreenSpaceChange, this);\n\t\t\t\tthis._element.off('set:draworder', this._onDrawOrderChange, this);\n\t\t\t\tthis._element.off('set:pivot', this._onPivotChange, this);\n\t\t\t\tthis._system.app.i18n.off(I18n.EVENT_CHANGE, this._onLocaleSet, this);\n\t\t\t\tthis._system.app.i18n.off('data:add', this._onLocalizationData, this);\n\t\t\t\tthis._system.app.i18n.off('data:remove', this._onLocalizationData, this);\n\t\t}\n\t\t_onParentResize(width, height) {\n\t\t\t\tif (this._noResize) return;\n\t\t\t\tif (this._font) this._updateText();\n\t\t}\n\t\t_onScreenChange(screen) {\n\t\t\t\tif (screen) {\n\t\t\t\t\t\tthis._updateMaterial(screen.screen.screenSpace);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._updateMaterial(false);\n\t\t\t\t}\n\t\t}\n\t\t_onScreenSpaceChange(value) {\n\t\t\t\tthis._updateMaterial(value);\n\t\t}\n\t\t_onDrawOrderChange(order) {\n\t\t\t\tthis._drawOrder = order;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tthis._model.meshInstances[i].drawOrder = order;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onPivotChange(pivot) {\n\t\t\t\tif (this._font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\t_onLocaleSet(locale) {\n\t\t\t\tif (!this._i18nKey) return;\n\t\t\t\tif (this.fontAsset) {\n\t\t\t\t\t\tconst asset = this._system.app.assets.get(this.fontAsset);\n\t\t\t\t\t\tif (!asset || !asset.resource || asset.resource !== this._font) {\n\t\t\t\t\t\t\t\tthis.font = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._resetLocalizedText();\n\t\t}\n\t\t_onLocalizationData(locale, messages) {\n\t\t\t\tif (this._i18nKey && messages[this._i18nKey]) {\n\t\t\t\t\t\tthis._resetLocalizedText();\n\t\t\t\t}\n\t\t}\n\t\t_resetLocalizedText() {\n\t\t\t\tthis._setText(this._system.app.i18n.getText(this._i18nKey));\n\t\t}\n\t\t_setText(text) {\n\t\t\t\tif (this.unicodeConverter) {\n\t\t\t\t\t\tconst unicodeConverterFunc = this._system.getUnicodeConverter();\n\t\t\t\t\t\tif (unicodeConverterFunc) {\n\t\t\t\t\t\t\t\ttext = unicodeConverterFunc(text);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.warn('Element created with unicodeConverter option but no unicodeConverter function registered');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._text !== text) {\n\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\tthis._updateText(text);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._text = text;\n\t\t\t\t}\n\t\t}\n\t\t_updateText(text) {\n\t\t\t\tlet tags;\n\t\t\t\tif (text === undefined) text = this._text;\n\t\t\t\tthis._symbols = string.getSymbols(text.normalize ? text.normalize('NFC') : text);\n\t\t\t\tif (this._symbols.length === 0) {\n\t\t\t\t\t\tthis._symbols = [\n\t\t\t\t\t\t\t\t' '\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tif (this._enableMarkup) {\n\t\t\t\t\t\tconst results = Markup.evaluate(this._symbols);\n\t\t\t\t\t\tthis._symbols = results.symbols;\n\t\t\t\t\t\ttags = results.tags || [];\n\t\t\t\t}\n\t\t\t\tif (this._rtlReorder) {\n\t\t\t\t\t\tconst rtlReorderFunc = this._system.app.systems.element.getRtlReorder();\n\t\t\t\t\t\tif (rtlReorderFunc) {\n\t\t\t\t\t\t\t\tconst results = rtlReorderFunc(this._symbols);\n\t\t\t\t\t\t\t\tthis._rtl = results.rtl;\n\t\t\t\t\t\t\t\tthis._symbols = results.mapping.map(function(v) {\n\t\t\t\t\t\t\t\t\t\treturn this._symbols[v];\n\t\t\t\t\t\t\t\t}, this);\n\t\t\t\t\t\t\t\tif (tags) {\n\t\t\t\t\t\t\t\t\t\ttags = results.mapping.map((v)=>{\n\t\t\t\t\t\t\t\t\t\t\t\treturn tags[v];\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.warn('Element created with rtlReorder option but no rtlReorder function registered');\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._rtl = false;\n\t\t\t\t}\n\t\t\t\tconst getColorThicknessHash = (color, thickness)=>{\n\t\t\t\t\t\treturn `${color.toString(true).toLowerCase()}:${thickness.toFixed(2)}`;\n\t\t\t\t};\n\t\t\t\tconst getColorOffsetHash = (color, offset)=>{\n\t\t\t\t\t\treturn `${color.toString(true).toLowerCase()}:${offset.x.toFixed(2)}:${offset.y.toFixed(2)}`;\n\t\t\t\t};\n\t\t\t\tif (tags) {\n\t\t\t\t\t\tconst paletteMap = {};\n\t\t\t\t\t\tconst outlinePaletteMap = {};\n\t\t\t\t\t\tconst shadowPaletteMap = {};\n\t\t\t\t\t\tthis._colorPalette = [\n\t\t\t\t\t\t\t\tMath.round(this._color.r * 255),\n\t\t\t\t\t\t\t\tMath.round(this._color.g * 255),\n\t\t\t\t\t\t\t\tMath.round(this._color.b * 255)\n\t\t\t\t\t\t];\n\t\t\t\t\t\tthis._outlinePalette = [\n\t\t\t\t\t\t\t\tMath.round(this._outlineColor.r * 255),\n\t\t\t\t\t\t\t\tMath.round(this._outlineColor.g * 255),\n\t\t\t\t\t\t\t\tMath.round(this._outlineColor.b * 255),\n\t\t\t\t\t\t\t\tMath.round(this._outlineColor.a * 255),\n\t\t\t\t\t\t\t\tMath.round(this._outlineThickness * 255)\n\t\t\t\t\t\t];\n\t\t\t\t\t\tthis._shadowPalette = [\n\t\t\t\t\t\t\t\tMath.round(this._shadowColor.r * 255),\n\t\t\t\t\t\t\t\tMath.round(this._shadowColor.g * 255),\n\t\t\t\t\t\t\t\tMath.round(this._shadowColor.b * 255),\n\t\t\t\t\t\t\t\tMath.round(this._shadowColor.a * 255),\n\t\t\t\t\t\t\t\tMath.round(this._shadowOffset.x * 127),\n\t\t\t\t\t\t\t\tMath.round(this._shadowOffset.y * 127)\n\t\t\t\t\t\t];\n\t\t\t\t\t\tthis._symbolColors = [];\n\t\t\t\t\t\tthis._symbolOutlineParams = [];\n\t\t\t\t\t\tthis._symbolShadowParams = [];\n\t\t\t\t\t\tpaletteMap[this._color.toString(false).toLowerCase()] = 0;\n\t\t\t\t\t\toutlinePaletteMap[getColorThicknessHash(this._outlineColor, this._outlineThickness)] = 0;\n\t\t\t\t\t\tshadowPaletteMap[getColorOffsetHash(this._shadowColor, this._shadowOffset)] = 0;\n\t\t\t\t\t\tfor(let i = 0, len = this._symbols.length; i < len; ++i){\n\t\t\t\t\t\t\t\tconst tag = tags[i];\n\t\t\t\t\t\t\t\tlet color = 0;\n\t\t\t\t\t\t\t\tif (tag && tag.color && tag.color.value) {\n\t\t\t\t\t\t\t\t\t\tconst c = tag.color.value;\n\t\t\t\t\t\t\t\t\t\tif (c.length === 7 && c[0] === '#') {\n\t\t\t\t\t\t\t\t\t\t\t\tconst hex = c.substring(1).toLowerCase();\n\t\t\t\t\t\t\t\t\t\t\t\tif (paletteMap.hasOwnProperty(hex)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolor = paletteMap[hex];\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (/^[0-9a-f]{6}$/.test(hex)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolor = this._colorPalette.length / 3;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpaletteMap[hex] = color;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._colorPalette.push(parseInt(hex.substring(0, 2), 16));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._colorPalette.push(parseInt(hex.substring(2, 4), 16));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._colorPalette.push(parseInt(hex.substring(4, 6), 16));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._symbolColors.push(color);\n\t\t\t\t\t\t\t\tlet outline = 0;\n\t\t\t\t\t\t\t\tif (tag && tag.outline && (tag.outline.attributes.color || tag.outline.attributes.thickness)) {\n\t\t\t\t\t\t\t\t\t\tlet color = tag.outline.attributes.color ? colorTmp.fromString(tag.outline.attributes.color) : this._outlineColor;\n\t\t\t\t\t\t\t\t\t\tlet thickness = Number(tag.outline.attributes.thickness);\n\t\t\t\t\t\t\t\t\t\tif (Number.isNaN(color.r) || Number.isNaN(color.g) || Number.isNaN(color.b) || Number.isNaN(color.a)) {\n\t\t\t\t\t\t\t\t\t\t\t\tcolor = this._outlineColor;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (Number.isNaN(thickness)) {\n\t\t\t\t\t\t\t\t\t\t\t\tthickness = this._outlineThickness;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst outlineHash = getColorThicknessHash(color, thickness);\n\t\t\t\t\t\t\t\t\t\tif (outlinePaletteMap.hasOwnProperty(outlineHash)) {\n\t\t\t\t\t\t\t\t\t\t\t\toutline = outlinePaletteMap[outlineHash];\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\toutline = this._outlinePalette.length / 5;\n\t\t\t\t\t\t\t\t\t\t\t\toutlinePaletteMap[outlineHash] = outline;\n\t\t\t\t\t\t\t\t\t\t\t\tthis._outlinePalette.push(Math.round(color.r * 255), Math.round(color.g * 255), Math.round(color.b * 255), Math.round(color.a * 255), Math.round(thickness * 255));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._symbolOutlineParams.push(outline);\n\t\t\t\t\t\t\t\tlet shadow = 0;\n\t\t\t\t\t\t\t\tif (tag && tag.shadow && (tag.shadow.attributes.color || tag.shadow.attributes.offset || tag.shadow.attributes.offsetX || tag.shadow.attributes.offsetY)) {\n\t\t\t\t\t\t\t\t\t\tlet color = tag.shadow.attributes.color ? colorTmp.fromString(tag.shadow.attributes.color) : this._shadowColor;\n\t\t\t\t\t\t\t\t\t\tconst off = Number(tag.shadow.attributes.offset);\n\t\t\t\t\t\t\t\t\t\tconst offX = Number(tag.shadow.attributes.offsetX);\n\t\t\t\t\t\t\t\t\t\tconst offY = Number(tag.shadow.attributes.offsetY);\n\t\t\t\t\t\t\t\t\t\tif (Number.isNaN(color.r) || Number.isNaN(color.g) || Number.isNaN(color.b) || Number.isNaN(color.a)) {\n\t\t\t\t\t\t\t\t\t\t\t\tcolor = this._shadowColor;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst offset = vec2Tmp.set(!Number.isNaN(offX) ? offX : !Number.isNaN(off) ? off : this._shadowOffset.x, !Number.isNaN(offY) ? offY : !Number.isNaN(off) ? off : this._shadowOffset.y);\n\t\t\t\t\t\t\t\t\t\tconst shadowHash = getColorOffsetHash(color, offset);\n\t\t\t\t\t\t\t\t\t\tif (shadowPaletteMap.hasOwnProperty(shadowHash)) {\n\t\t\t\t\t\t\t\t\t\t\t\tshadow = shadowPaletteMap[shadowHash];\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tshadow = this._shadowPalette.length / 6;\n\t\t\t\t\t\t\t\t\t\t\t\tshadowPaletteMap[shadowHash] = shadow;\n\t\t\t\t\t\t\t\t\t\t\t\tthis._shadowPalette.push(Math.round(color.r * 255), Math.round(color.g * 255), Math.round(color.b * 255), Math.round(color.a * 255), Math.round(offset.x * 127), Math.round(offset.y * 127));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._symbolShadowParams.push(shadow);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._colorPalette = [];\n\t\t\t\t\t\tthis._symbolColors = null;\n\t\t\t\t\t\tthis._symbolOutlineParams = null;\n\t\t\t\t\t\tthis._symbolShadowParams = null;\n\t\t\t\t}\n\t\t\t\tthis._updateMaterialEmissive();\n\t\t\t\tthis._updateMaterialOutline();\n\t\t\t\tthis._updateMaterialShadow();\n\t\t\t\tconst charactersPerTexture = this._calculateCharsPerTexture();\n\t\t\t\tlet removedModel = false;\n\t\t\t\tconst element = this._element;\n\t\t\t\tconst screenSpace = element._isScreenSpace();\n\t\t\t\tconst screenCulled = element._isScreenCulled();\n\t\t\t\tconst visibleFn = function(camera) {\n\t\t\t\t\t\treturn element.isVisibleForCamera(camera);\n\t\t\t\t};\n\t\t\t\tfor(let i = 0, len = this._meshInfo.length; i < len; i++){\n\t\t\t\t\t\tconst l = charactersPerTexture[i] || 0;\n\t\t\t\t\t\tconst meshInfo = this._meshInfo[i];\n\t\t\t\t\t\tif (meshInfo.count !== l) {\n\t\t\t\t\t\t\t\tif (!removedModel) {\n\t\t\t\t\t\t\t\t\t\telement.removeModelFromLayers(this._model);\n\t\t\t\t\t\t\t\t\t\tremovedModel = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmeshInfo.count = l;\n\t\t\t\t\t\t\t\tmeshInfo.positions.length = meshInfo.normals.length = l * 3 * 4;\n\t\t\t\t\t\t\t\tmeshInfo.indices.length = l * 3 * 2;\n\t\t\t\t\t\t\t\tmeshInfo.uvs.length = l * 2 * 4;\n\t\t\t\t\t\t\t\tmeshInfo.colors.length = l * 4 * 4;\n\t\t\t\t\t\t\t\tmeshInfo.outlines.length = l * 4 * 3;\n\t\t\t\t\t\t\t\tmeshInfo.shadows.length = l * 4 * 3;\n\t\t\t\t\t\t\t\tif (meshInfo.meshInstance) {\n\t\t\t\t\t\t\t\t\t\tthis._removeMeshInstance(meshInfo.meshInstance);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (l === 0) {\n\t\t\t\t\t\t\t\t\t\tmeshInfo.meshInstance = null;\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor(let v = 0; v < l; v++){\n\t\t\t\t\t\t\t\t\t\tmeshInfo.indices[v * 3 * 2 + 0] = v * 4;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.indices[v * 3 * 2 + 1] = v * 4 + 1;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.indices[v * 3 * 2 + 2] = v * 4 + 3;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.indices[v * 3 * 2 + 3] = v * 4 + 2;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.indices[v * 3 * 2 + 4] = v * 4 + 3;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.indices[v * 3 * 2 + 5] = v * 4 + 1;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 0] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 1] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 2] = -1;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 3] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 4] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 5] = -1;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 6] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 7] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 8] = -1;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 9] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 10] = 0;\n\t\t\t\t\t\t\t\t\t\tmeshInfo.normals[v * 4 * 3 + 11] = -1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst mesh = createTextMesh(this._system.app.graphicsDevice, meshInfo);\n\t\t\t\t\t\t\t\tconst mi = new MeshInstance(mesh, this._material, this._node);\n\t\t\t\t\t\t\t\tmi.name = `Text Element: ${this._entity.name}`;\n\t\t\t\t\t\t\t\tmi.castShadow = false;\n\t\t\t\t\t\t\t\tmi.receiveShadow = false;\n\t\t\t\t\t\t\t\tmi.cull = !screenSpace;\n\t\t\t\t\t\t\t\tmi.screenSpace = screenSpace;\n\t\t\t\t\t\t\t\tmi.drawOrder = this._drawOrder;\n\t\t\t\t\t\t\t\tif (screenCulled) {\n\t\t\t\t\t\t\t\t\t\tmi.cull = true;\n\t\t\t\t\t\t\t\t\t\tmi.isVisibleFunc = visibleFn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._setTextureParams(mi, this._font.textures[i]);\n\t\t\t\t\t\t\t\tmi.setParameter('material_emissive', this._colorUniform);\n\t\t\t\t\t\t\t\tmi.setParameter('material_opacity', this._color.a);\n\t\t\t\t\t\t\t\tmi.setParameter('font_sdfIntensity', this._font.intensity);\n\t\t\t\t\t\t\t\tmi.setParameter('font_pxrange', this._getPxRange(this._font));\n\t\t\t\t\t\t\t\tmi.setParameter('font_textureWidth', this._font.data.info.maps[i].width);\n\t\t\t\t\t\t\t\tmi.setParameter('outline_color', this._outlineColorUniform);\n\t\t\t\t\t\t\t\tmi.setParameter('outline_thickness', this._outlineThicknessScale * this._outlineThickness);\n\t\t\t\t\t\t\t\tmi.setParameter('shadow_color', this._shadowColorUniform);\n\t\t\t\t\t\t\t\tif (this._symbolShadowParams) {\n\t\t\t\t\t\t\t\t\t\tthis._shadowOffsetUniform[0] = 0;\n\t\t\t\t\t\t\t\t\t\tthis._shadowOffsetUniform[1] = 0;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst ratio = -this._font.data.info.maps[i].width / this._font.data.info.maps[i].height;\n\t\t\t\t\t\t\t\t\t\tthis._shadowOffsetUniform[0] = this._shadowOffsetScale * this._shadowOffset.x;\n\t\t\t\t\t\t\t\t\t\tthis._shadowOffsetUniform[1] = ratio * this._shadowOffsetScale * this._shadowOffset.y;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmi.setParameter('shadow_offset', this._shadowOffsetUniform);\n\t\t\t\t\t\t\t\tmeshInfo.meshInstance = mi;\n\t\t\t\t\t\t\t\tthis._model.meshInstances.push(mi);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._element.maskedBy) {\n\t\t\t\t\t\tthis._element._setMaskedBy(this._element.maskedBy);\n\t\t\t\t}\n\t\t\t\tif (removedModel && this._element.enabled && this._entity.enabled) {\n\t\t\t\t\t\tthis._element.addModelToLayers(this._model);\n\t\t\t\t}\n\t\t\t\tthis._updateMeshes();\n\t\t\t\tthis._rangeStart = 0;\n\t\t\t\tthis._rangeEnd = this._symbols.length;\n\t\t\t\tthis._updateRenderRange();\n\t\t}\n\t\t_removeMeshInstance(meshInstance) {\n\t\t\t\tmeshInstance.destroy();\n\t\t\t\tconst idx = this._model.meshInstances.indexOf(meshInstance);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tthis._model.meshInstances.splice(idx, 1);\n\t\t\t\t}\n\t\t}\n\t\t_setMaterial(material) {\n\t\t\t\tthis._material = material;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\tmi.material = material;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateMaterial(screenSpace) {\n\t\t\t\tconst element = this._element;\n\t\t\t\tconst screenCulled = element._isScreenCulled();\n\t\t\t\tconst visibleFn = function(camera) {\n\t\t\t\t\t\treturn element.isVisibleForCamera(camera);\n\t\t\t\t};\n\t\t\t\tconst msdf = this._font && this._font.type === FONT_MSDF;\n\t\t\t\tthis._material = this._system.getTextElementMaterial(screenSpace, msdf, this._enableMarkup);\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\tmi.cull = !screenSpace;\n\t\t\t\t\t\t\t\tmi.material = this._material;\n\t\t\t\t\t\t\t\tmi.screenSpace = screenSpace;\n\t\t\t\t\t\t\t\tif (screenCulled) {\n\t\t\t\t\t\t\t\t\t\tmi.cull = true;\n\t\t\t\t\t\t\t\t\t\tmi.isVisibleFunc = visibleFn;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmi.isVisibleFunc = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateMaterialEmissive() {\n\t\t\t\tif (this._symbolColors) {\n\t\t\t\t\t\tthis._colorUniform[0] = 1;\n\t\t\t\t\t\tthis._colorUniform[1] = 1;\n\t\t\t\t\t\tthis._colorUniform[2] = 1;\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempColor.linear(this._color);\n\t\t\t\t\t\tthis._colorUniform[0] = _tempColor.r;\n\t\t\t\t\t\tthis._colorUniform[1] = _tempColor.g;\n\t\t\t\t\t\tthis._colorUniform[2] = _tempColor.b;\n\t\t\t\t}\n\t\t}\n\t\t_updateMaterialOutline() {\n\t\t\t\tif (this._symbolOutlineParams) {\n\t\t\t\t\t\tthis._outlineColorUniform[0] = 0;\n\t\t\t\t\t\tthis._outlineColorUniform[1] = 0;\n\t\t\t\t\t\tthis._outlineColorUniform[2] = 0;\n\t\t\t\t\t\tthis._outlineColorUniform[3] = 1;\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempColor.linear(this._outlineColor);\n\t\t\t\t\t\tthis._outlineColorUniform[0] = _tempColor.r;\n\t\t\t\t\t\tthis._outlineColorUniform[1] = _tempColor.g;\n\t\t\t\t\t\tthis._outlineColorUniform[2] = _tempColor.b;\n\t\t\t\t\t\tthis._outlineColorUniform[3] = _tempColor.a;\n\t\t\t\t}\n\t\t}\n\t\t_updateMaterialShadow() {\n\t\t\t\tif (this._symbolOutlineParams) {\n\t\t\t\t\t\tthis._shadowColorUniform[0] = 0;\n\t\t\t\t\t\tthis._shadowColorUniform[1] = 0;\n\t\t\t\t\t\tthis._shadowColorUniform[2] = 0;\n\t\t\t\t\t\tthis._shadowColorUniform[3] = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempColor.linear(this._shadowColor);\n\t\t\t\t\t\tthis._shadowColorUniform[0] = _tempColor.r;\n\t\t\t\t\t\tthis._shadowColorUniform[1] = _tempColor.g;\n\t\t\t\t\t\tthis._shadowColorUniform[2] = _tempColor.b;\n\t\t\t\t\t\tthis._shadowColorUniform[3] = _tempColor.a;\n\t\t\t\t}\n\t\t}\n\t\t_isWordBoundary(char) {\n\t\t\t\treturn WORD_BOUNDARY_CHAR.test(char);\n\t\t}\n\t\t_isValidNextChar(nextchar) {\n\t\t\t\treturn nextchar !== null && !NO_LINE_BREAK_CJK_CHAR.test(nextchar);\n\t\t}\n\t\t_isNextCJKBoundary(char, nextchar) {\n\t\t\t\treturn CJK_CHAR.test(char) && (WORD_BOUNDARY_CHAR.test(nextchar) || ALPHANUMERIC_CHAR.test(nextchar));\n\t\t}\n\t\t_isNextCJKWholeWord(nextchar) {\n\t\t\t\treturn CJK_CHAR.test(nextchar);\n\t\t}\n\t\t_updateMeshes() {\n\t\t\t\tconst json = this._font.data;\n\t\t\t\tconst self = this;\n\t\t\t\tconst minFont = Math.min(this._minFontSize, this._maxFontSize);\n\t\t\t\tconst maxFont = this._maxFontSize;\n\t\t\t\tconst autoFit = this._shouldAutoFit();\n\t\t\t\tif (autoFit) {\n\t\t\t\t\t\tthis._fontSize = this._maxFontSize;\n\t\t\t\t}\n\t\t\t\tconst MAGIC = 32;\n\t\t\t\tconst l = this._symbols.length;\n\t\t\t\tlet _x = 0;\n\t\t\t\tlet _y = 0;\n\t\t\t\tlet _z = 0;\n\t\t\t\tlet _xMinusTrailingWhitespace = 0;\n\t\t\t\tlet lines = 1;\n\t\t\t\tlet wordStartX = 0;\n\t\t\t\tlet wordStartIndex = 0;\n\t\t\t\tlet lineStartIndex = 0;\n\t\t\t\tlet numWordsThisLine = 0;\n\t\t\t\tlet numCharsThisLine = 0;\n\t\t\t\tlet numBreaksThisLine = 0;\n\t\t\t\tconst splitHorizontalAnchors = Math.abs(this._element.anchor.x - this._element.anchor.z) >= 0.0001;\n\t\t\t\tlet maxLineWidth = this._element.calculatedWidth;\n\t\t\t\tif (this.autoWidth && !splitHorizontalAnchors || !this._wrapLines) {\n\t\t\t\t\t\tmaxLineWidth = Number.POSITIVE_INFINITY;\n\t\t\t\t}\n\t\t\t\tlet fontMinY = 0;\n\t\t\t\tlet fontMaxY = 0;\n\t\t\t\tlet char, data, quad, nextchar;\n\t\t\t\tfunction breakLine(symbols, lineBreakIndex, lineBreakX) {\n\t\t\t\t\t\tself._lineWidths.push(Math.abs(lineBreakX));\n\t\t\t\t\t\tconst sliceStart = lineStartIndex > lineBreakIndex ? lineBreakIndex + 1 : lineStartIndex;\n\t\t\t\t\t\tconst sliceEnd = lineStartIndex > lineBreakIndex ? lineStartIndex + 1 : lineBreakIndex;\n\t\t\t\t\t\tconst chars = symbols.slice(sliceStart, sliceEnd);\n\t\t\t\t\t\tif (numBreaksThisLine) {\n\t\t\t\t\t\t\t\tlet i = chars.length;\n\t\t\t\t\t\t\t\twhile(i-- && numBreaksThisLine > 0){\n\t\t\t\t\t\t\t\t\t\tif (LINE_BREAK_CHAR.test(chars[i])) {\n\t\t\t\t\t\t\t\t\t\t\t\tchars.splice(i, 1);\n\t\t\t\t\t\t\t\t\t\t\t\tnumBreaksThisLine--;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tself._lineContents.push(chars.join(''));\n\t\t\t\t\t\t_x = 0;\n\t\t\t\t\t\t_y -= self._scaledLineHeight;\n\t\t\t\t\t\tlines++;\n\t\t\t\t\t\tnumWordsThisLine = 0;\n\t\t\t\t\t\tnumCharsThisLine = 0;\n\t\t\t\t\t\tnumBreaksThisLine = 0;\n\t\t\t\t\t\twordStartX = 0;\n\t\t\t\t\t\tlineStartIndex = lineBreakIndex;\n\t\t\t\t}\n\t\t\t\tlet retryUpdateMeshes = true;\n\t\t\t\twhile(retryUpdateMeshes){\n\t\t\t\t\t\tretryUpdateMeshes = false;\n\t\t\t\t\t\tif (autoFit) {\n\t\t\t\t\t\t\t\tthis._scaledLineHeight = this._lineHeight * this._fontSize / (this._maxFontSize || 0.0001);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._scaledLineHeight = this._lineHeight;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.width = 0;\n\t\t\t\t\t\tthis.height = 0;\n\t\t\t\t\t\tthis._lineWidths = [];\n\t\t\t\t\t\tthis._lineContents = [];\n\t\t\t\t\t\t_x = 0;\n\t\t\t\t\t\t_y = 0;\n\t\t\t\t\t\t_z = 0;\n\t\t\t\t\t\t_xMinusTrailingWhitespace = 0;\n\t\t\t\t\t\tlines = 1;\n\t\t\t\t\t\twordStartX = 0;\n\t\t\t\t\t\twordStartIndex = 0;\n\t\t\t\t\t\tlineStartIndex = 0;\n\t\t\t\t\t\tnumWordsThisLine = 0;\n\t\t\t\t\t\tnumCharsThisLine = 0;\n\t\t\t\t\t\tnumBreaksThisLine = 0;\n\t\t\t\t\t\tconst scale = this._fontSize / MAGIC;\n\t\t\t\t\t\tfontMinY = this._fontMinY * scale;\n\t\t\t\t\t\tfontMaxY = this._fontMaxY * scale;\n\t\t\t\t\t\tfor(let i = 0; i < this._meshInfo.length; i++){\n\t\t\t\t\t\t\t\tthis._meshInfo[i].quad = 0;\n\t\t\t\t\t\t\t\tthis._meshInfo[i].lines = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet color_r = 255;\n\t\t\t\t\t\tlet color_g = 255;\n\t\t\t\t\t\tlet color_b = 255;\n\t\t\t\t\t\tlet outline_color_rg = 255 + 255 * 256;\n\t\t\t\t\t\tlet outline_color_ba = 255 + 255 * 256;\n\t\t\t\t\t\tlet outline_thickness = 0;\n\t\t\t\t\t\tlet shadow_color_rg = 255 + 255 * 256;\n\t\t\t\t\t\tlet shadow_color_ba = 255 + 255 * 256;\n\t\t\t\t\t\tlet shadow_offset_xy = 127 + 127 * 256;\n\t\t\t\t\t\tfor(let i = 0; i < l; i++){\n\t\t\t\t\t\t\t\tchar = this._symbols[i];\n\t\t\t\t\t\t\t\tnextchar = i + 1 >= l ? null : this._symbols[i + 1];\n\t\t\t\t\t\t\t\tconst isLineBreak = LINE_BREAK_CHAR.test(char);\n\t\t\t\t\t\t\t\tif (isLineBreak) {\n\t\t\t\t\t\t\t\t\t\tnumBreaksThisLine++;\n\t\t\t\t\t\t\t\t\t\tif (!this._wrapLines || this._maxLines < 0 || lines < this._maxLines) {\n\t\t\t\t\t\t\t\t\t\t\t\tbreakLine(this._symbols, i, _xMinusTrailingWhitespace);\n\t\t\t\t\t\t\t\t\t\t\t\twordStartIndex = i + 1;\n\t\t\t\t\t\t\t\t\t\t\t\tlineStartIndex = i + 1;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet x = 0;\n\t\t\t\t\t\t\t\tlet y = 0;\n\t\t\t\t\t\t\t\tlet advance = 0;\n\t\t\t\t\t\t\t\tlet quadsize = 1;\n\t\t\t\t\t\t\t\tlet dataScale, size;\n\t\t\t\t\t\t\t\tdata = json.chars[char];\n\t\t\t\t\t\t\t\tif (!data) {\n\t\t\t\t\t\t\t\t\t\tif (CONTROL_CHARS.indexOf(char) !== -1) {\n\t\t\t\t\t\t\t\t\t\t\t\tdata = CONTROL_GLYPH_DATA;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tif (json.chars[' ']) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata = json.chars[' '];\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(const key in json.chars){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata = json.chars[key];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (data) {\n\t\t\t\t\t\t\t\t\t\tlet kerning = 0;\n\t\t\t\t\t\t\t\t\t\tif (numCharsThisLine > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst kernTable = this._font.data.kerning;\n\t\t\t\t\t\t\t\t\t\t\t\tif (kernTable) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst kernLeft = kernTable[string.getCodePoint(this._symbols[i - 1]) || 0];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (kernLeft) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tkerning = kernLeft[string.getCodePoint(this._symbols[i]) || 0] || 0;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdataScale = data.scale || 1;\n\t\t\t\t\t\t\t\t\t\tsize = (data.width + data.height) / 2;\n\t\t\t\t\t\t\t\t\t\tquadsize = scale * size / dataScale;\n\t\t\t\t\t\t\t\t\t\tadvance = (data.xadvance + kerning) * scale;\n\t\t\t\t\t\t\t\t\t\tx = (data.xoffset - kerning) * scale;\n\t\t\t\t\t\t\t\t\t\ty = data.yoffset * scale;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconsole.error(`Couldn't substitute missing character: '${char}'`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst isWhitespace = WHITESPACE_CHAR.test(char);\n\t\t\t\t\t\t\t\tconst meshInfoId = data && data.map || 0;\n\t\t\t\t\t\t\t\tconst ratio = -this._font.data.info.maps[meshInfoId].width / this._font.data.info.maps[meshInfoId].height;\n\t\t\t\t\t\t\t\tconst meshInfo = this._meshInfo[meshInfoId];\n\t\t\t\t\t\t\t\tconst candidateLineWidth = _x + this._spacing * advance;\n\t\t\t\t\t\t\t\tif (candidateLineWidth > maxLineWidth && numCharsThisLine > 0 && !isWhitespace) {\n\t\t\t\t\t\t\t\t\t\tif (this._maxLines < 0 || lines < this._maxLines) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (numWordsThisLine === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\twordStartIndex = i;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreakLine(this._symbols, i, _xMinusTrailingWhitespace);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst backtrack = Math.max(i - wordStartIndex, 0);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this._meshInfo.length <= 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmeshInfo.lines[lines - 1] -= backtrack;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmeshInfo.quad -= backtrack;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst backtrackStart = wordStartIndex;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst backtrackEnd = i;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = backtrackStart; j < backtrackEnd; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst backChar = this._symbols[j];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst backCharData = json.chars[backChar];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst backMeshInfo = this._meshInfo[backCharData && backCharData.map || 0];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbackMeshInfo.lines[lines - 1] -= 1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbackMeshInfo.quad -= 1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ti -= backtrack + 1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreakLine(this._symbols, wordStartIndex, wordStartX);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tquad = meshInfo.quad;\n\t\t\t\t\t\t\t\tmeshInfo.lines[lines - 1] = quad;\n\t\t\t\t\t\t\t\tlet left = _x - x;\n\t\t\t\t\t\t\t\tlet right = left + quadsize;\n\t\t\t\t\t\t\t\tconst bottom = _y - y;\n\t\t\t\t\t\t\t\tconst top = bottom + quadsize;\n\t\t\t\t\t\t\t\tif (this._rtl) {\n\t\t\t\t\t\t\t\t\t\tconst shift = quadsize - x - this._spacing * advance - x;\n\t\t\t\t\t\t\t\t\t\tleft -= shift;\n\t\t\t\t\t\t\t\t\t\tright -= shift;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 0] = left;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 1] = bottom;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 2] = _z;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 3] = right;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 4] = bottom;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 5] = _z;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 6] = right;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 7] = top;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 8] = _z;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 9] = left;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 10] = top;\n\t\t\t\t\t\t\t\tmeshInfo.positions[quad * 4 * 3 + 11] = _z;\n\t\t\t\t\t\t\t\tthis.width = Math.max(this.width, candidateLineWidth);\n\t\t\t\t\t\t\t\tlet fontSize;\n\t\t\t\t\t\t\t\tif (this._shouldAutoFitWidth() && this.width > this._element.calculatedWidth) {\n\t\t\t\t\t\t\t\t\t\tfontSize = Math.floor(this._element.fontSize * this._element.calculatedWidth / (this.width || 0.0001));\n\t\t\t\t\t\t\t\t\t\tfontSize = math.clamp(fontSize, minFont, maxFont);\n\t\t\t\t\t\t\t\t\t\tif (fontSize !== this._element.fontSize) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._fontSize = fontSize;\n\t\t\t\t\t\t\t\t\t\t\t\tretryUpdateMeshes = true;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.height = Math.max(this.height, fontMaxY - (_y + fontMinY));\n\t\t\t\t\t\t\t\tif (this._shouldAutoFitHeight() && this.height > this._element.calculatedHeight) {\n\t\t\t\t\t\t\t\t\t\tfontSize = math.clamp(this._fontSize - 1, minFont, maxFont);\n\t\t\t\t\t\t\t\t\t\tif (fontSize !== this._element.fontSize) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._fontSize = fontSize;\n\t\t\t\t\t\t\t\t\t\t\t\tretryUpdateMeshes = true;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t_x += this._spacing * advance;\n\t\t\t\t\t\t\t\tif (!isWhitespace) {\n\t\t\t\t\t\t\t\t\t\t_xMinusTrailingWhitespace = _x;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this._isWordBoundary(char) || this._isValidNextChar(nextchar) && (this._isNextCJKBoundary(char, nextchar) || this._isNextCJKWholeWord(nextchar))) {\n\t\t\t\t\t\t\t\t\t\tnumWordsThisLine++;\n\t\t\t\t\t\t\t\t\t\twordStartX = _xMinusTrailingWhitespace;\n\t\t\t\t\t\t\t\t\t\twordStartIndex = i + 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tnumCharsThisLine++;\n\t\t\t\t\t\t\t\tconst uv = this._getUv(char);\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 0] = uv[0];\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 1] = 1.0 - uv[1];\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 2] = uv[2];\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 3] = 1.0 - uv[1];\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 4] = uv[2];\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 5] = 1.0 - uv[3];\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 6] = uv[0];\n\t\t\t\t\t\t\t\tmeshInfo.uvs[quad * 4 * 2 + 7] = 1.0 - uv[3];\n\t\t\t\t\t\t\t\tif (this._symbolColors) {\n\t\t\t\t\t\t\t\t\t\tconst colorIdx = this._symbolColors[i] * 3;\n\t\t\t\t\t\t\t\t\t\tcolor_r = this._colorPalette[colorIdx];\n\t\t\t\t\t\t\t\t\t\tcolor_g = this._colorPalette[colorIdx + 1];\n\t\t\t\t\t\t\t\t\t\tcolor_b = this._colorPalette[colorIdx + 2];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 0] = color_r;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 1] = color_g;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 2] = color_b;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 3] = 255;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 4] = color_r;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 5] = color_g;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 6] = color_b;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 7] = 255;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 8] = color_r;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 9] = color_g;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 10] = color_b;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 11] = 255;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 12] = color_r;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 13] = color_g;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 14] = color_b;\n\t\t\t\t\t\t\t\tmeshInfo.colors[quad * 4 * 4 + 15] = 255;\n\t\t\t\t\t\t\t\tif (this._symbolOutlineParams) {\n\t\t\t\t\t\t\t\t\t\tconst outlineIdx = this._symbolOutlineParams[i] * 5;\n\t\t\t\t\t\t\t\t\t\toutline_color_rg = this._outlinePalette[outlineIdx] + this._outlinePalette[outlineIdx + 1] * 256;\n\t\t\t\t\t\t\t\t\t\toutline_color_ba = this._outlinePalette[outlineIdx + 2] + this._outlinePalette[outlineIdx + 3] * 256;\n\t\t\t\t\t\t\t\t\t\toutline_thickness = this._outlinePalette[outlineIdx + 4];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 0] = outline_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 1] = outline_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 2] = outline_thickness;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 3] = outline_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 4] = outline_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 5] = outline_thickness;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 6] = outline_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 7] = outline_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 8] = outline_thickness;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 9] = outline_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 10] = outline_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.outlines[quad * 4 * 3 + 11] = outline_thickness;\n\t\t\t\t\t\t\t\tif (this._symbolShadowParams) {\n\t\t\t\t\t\t\t\t\t\tconst shadowIdx = this._symbolShadowParams[i] * 6;\n\t\t\t\t\t\t\t\t\t\tshadow_color_rg = this._shadowPalette[shadowIdx] + this._shadowPalette[shadowIdx + 1] * 256;\n\t\t\t\t\t\t\t\t\t\tshadow_color_ba = this._shadowPalette[shadowIdx + 2] + this._shadowPalette[shadowIdx + 3] * 256;\n\t\t\t\t\t\t\t\t\t\tshadow_offset_xy = this._shadowPalette[shadowIdx + 4] + 127 + Math.round(ratio * this._shadowPalette[shadowIdx + 5] + 127) * 256;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 0] = shadow_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 1] = shadow_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 2] = shadow_offset_xy;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 3] = shadow_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 4] = shadow_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 5] = shadow_offset_xy;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 6] = shadow_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 7] = shadow_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 8] = shadow_offset_xy;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 9] = shadow_color_rg;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 10] = shadow_color_ba;\n\t\t\t\t\t\t\t\tmeshInfo.shadows[quad * 4 * 3 + 11] = shadow_offset_xy;\n\t\t\t\t\t\t\t\tmeshInfo.quad++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (retryUpdateMeshes) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (lineStartIndex < l) {\n\t\t\t\t\t\t\t\tbreakLine(this._symbols, l, _x);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._noResize = true;\n\t\t\t\tthis.autoWidth = this._autoWidth;\n\t\t\t\tthis.autoHeight = this._autoHeight;\n\t\t\t\tthis._noResize = false;\n\t\t\t\tconst hp = this._element.pivot.x;\n\t\t\t\tconst vp = this._element.pivot.y;\n\t\t\t\tconst ha = this._alignment.x;\n\t\t\t\tconst va = this._alignment.y;\n\t\t\t\tfor(let i = 0; i < this._meshInfo.length; i++){\n\t\t\t\t\t\tif (this._meshInfo[i].count === 0) continue;\n\t\t\t\t\t\tlet prevQuad = 0;\n\t\t\t\t\t\tfor(const line in this._meshInfo[i].lines){\n\t\t\t\t\t\t\t\tconst index = this._meshInfo[i].lines[line];\n\t\t\t\t\t\t\t\tconst lw = this._lineWidths[parseInt(line, 10)];\n\t\t\t\t\t\t\t\tconst hoffset = -hp * this._element.calculatedWidth + ha * (this._element.calculatedWidth - lw) * (this._rtl ? -1 : 1);\n\t\t\t\t\t\t\t\tconst voffset = (1 - vp) * this._element.calculatedHeight - fontMaxY - (1 - va) * (this._element.calculatedHeight - this.height);\n\t\t\t\t\t\t\t\tfor(let quad = prevQuad; quad <= index; quad++){\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3] += hoffset;\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3 + 3] += hoffset;\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3 + 6] += hoffset;\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3 + 9] += hoffset;\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3 + 1] += voffset;\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3 + 4] += voffset;\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3 + 7] += voffset;\n\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[quad * 4 * 3 + 10] += voffset;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this._rtl) {\n\t\t\t\t\t\t\t\t\t\tfor(let quad = prevQuad; quad <= index; quad++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst idx = quad * 4 * 3;\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let vert = 0; vert < 4; ++vert){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[idx + vert * 3] = this._element.calculatedWidth - this._meshInfo[i].positions[idx + vert * 3] + hoffset * 2;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst tmp0 = this._meshInfo[i].positions[idx + 3];\n\t\t\t\t\t\t\t\t\t\t\t\tconst tmp1 = this._meshInfo[i].positions[idx + 6];\n\t\t\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[idx + 3] = this._meshInfo[i].positions[idx + 0];\n\t\t\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[idx + 6] = this._meshInfo[i].positions[idx + 9];\n\t\t\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[idx + 0] = tmp0;\n\t\t\t\t\t\t\t\t\t\t\t\tthis._meshInfo[i].positions[idx + 9] = tmp1;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tprevQuad = index + 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst numVertices = this._meshInfo[i].count * 4;\n\t\t\t\t\t\tconst vertMax = this._meshInfo[i].quad * 4;\n\t\t\t\t\t\tconst it = new VertexIterator(this._meshInfo[i].meshInstance.mesh.vertexBuffer);\n\t\t\t\t\t\tfor(let v = 0; v < numVertices; v++){\n\t\t\t\t\t\t\t\tif (v >= vertMax) {\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_POSITION].set(0, 0, 0);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_TEXCOORD0].set(0, 0);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_COLOR].set(0, 0, 0, 0);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_ATTR8].set(0, 0, 0, 0);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_ATTR9].set(0, 0, 0, 0);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_POSITION].set(this._meshInfo[i].positions[v * 3 + 0], this._meshInfo[i].positions[v * 3 + 1], this._meshInfo[i].positions[v * 3 + 2]);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_TEXCOORD0].set(this._meshInfo[i].uvs[v * 2 + 0], this._meshInfo[i].uvs[v * 2 + 1]);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_COLOR].set(this._meshInfo[i].colors[v * 4 + 0], this._meshInfo[i].colors[v * 4 + 1], this._meshInfo[i].colors[v * 4 + 2], this._meshInfo[i].colors[v * 4 + 3]);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_ATTR8].set(this._meshInfo[i].outlines[v * 3 + 0], this._meshInfo[i].outlines[v * 3 + 1], this._meshInfo[i].outlines[v * 3 + 2]);\n\t\t\t\t\t\t\t\t\t\tit.element[SEMANTIC_ATTR9].set(this._meshInfo[i].shadows[v * 3 + 0], this._meshInfo[i].shadows[v * 3 + 1], this._meshInfo[i].shadows[v * 3 + 2]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tit.next();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tit.end();\n\t\t\t\t\t\tthis._meshInfo[i].meshInstance.mesh.aabb.compute(this._meshInfo[i].positions);\n\t\t\t\t\t\tthis._meshInfo[i].meshInstance._aabbVer = -1;\n\t\t\t\t}\n\t\t\t\tthis._aabbDirty = true;\n\t\t}\n\t\t_onFontRender() {\n\t\t\t\tthis.font = this._font;\n\t\t}\n\t\t_onFontLoad(asset) {\n\t\t\t\tif (this.font !== asset.resource) {\n\t\t\t\t\t\tthis.font = asset.resource;\n\t\t\t\t}\n\t\t}\n\t\t_onFontChange(asset, name, _new, _old) {\n\t\t\t\tif (name === 'data') {\n\t\t\t\t\t\tthis._font.data = _new;\n\t\t\t\t\t\tconst maps = this._font.data.info.maps.length;\n\t\t\t\t\t\tfor(let i = 0; i < maps; i++){\n\t\t\t\t\t\t\t\tif (!this._meshInfo[i]) continue;\n\t\t\t\t\t\t\t\tconst mi = this._meshInfo[i].meshInstance;\n\t\t\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('font_sdfIntensity', this._font.intensity);\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('font_pxrange', this._getPxRange(this._font));\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('font_textureWidth', this._font.data.info.maps[i].width);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onFontRemove(asset) {}\n\t\t_setTextureParams(mi, texture) {\n\t\t\t\tif (this._font) {\n\t\t\t\t\t\tif (this._font.type === FONT_MSDF) {\n\t\t\t\t\t\t\t\tmi.deleteParameter('texture_emissiveMap');\n\t\t\t\t\t\t\t\tmi.deleteParameter('texture_opacityMap');\n\t\t\t\t\t\t\t\tmi.setParameter('texture_msdfMap', texture);\n\t\t\t\t\t\t} else if (this._font.type === FONT_BITMAP) {\n\t\t\t\t\t\t\t\tmi.deleteParameter('texture_msdfMap');\n\t\t\t\t\t\t\t\tmi.setParameter('texture_emissiveMap', texture);\n\t\t\t\t\t\t\t\tmi.setParameter('texture_opacityMap', texture);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_getPxRange(font) {\n\t\t\t\tconst keys = Object.keys(this._font.data.chars);\n\t\t\t\tfor(let i = 0; i < keys.length; i++){\n\t\t\t\t\t\tconst char = this._font.data.chars[keys[i]];\n\t\t\t\t\t\tif (char.range) {\n\t\t\t\t\t\t\t\treturn (char.scale || 1) * char.range;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn 2;\n\t\t}\n\t\t_getUv(char) {\n\t\t\t\tconst data = this._font.data;\n\t\t\t\tif (!data.chars[char]) {\n\t\t\t\t\t\tconst space = ' ';\n\t\t\t\t\t\tif (data.chars[space]) {\n\t\t\t\t\t\t\t\treturn this._getUv(space);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tconst map = data.chars[char].map;\n\t\t\t\tconst width = data.info.maps[map].width;\n\t\t\t\tconst height = data.info.maps[map].height;\n\t\t\t\tconst x = data.chars[char].x;\n\t\t\t\tconst y = data.chars[char].y;\n\t\t\t\tconst x1 = x;\n\t\t\t\tconst y1 = y;\n\t\t\t\tconst x2 = x + data.chars[char].width;\n\t\t\t\tconst y2 = y - data.chars[char].height;\n\t\t\t\tconst edge = 1 - data.chars[char].height / height;\n\t\t\t\treturn [\n\t\t\t\t\t\tx1 / width,\n\t\t\t\t\t\tedge - y1 / height,\n\t\t\t\t\t\tx2 / width,\n\t\t\t\t\t\tedge - y2 / height\n\t\t\t\t];\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._fontAsset.autoLoad = true;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis._element.addModelToLayers(this._model);\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._fontAsset.autoLoad = false;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis._element.removeModelFromLayers(this._model);\n\t\t\t\t}\n\t\t}\n\t\t_setStencil(stencilParams) {\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tconst instances = this._model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0; i < instances.length; i++){\n\t\t\t\t\t\t\t\tinstances[i].stencilFront = stencilParams;\n\t\t\t\t\t\t\t\tinstances[i].stencilBack = stencilParams;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_shouldAutoFitWidth() {\n\t\t\t\treturn this._autoFitWidth && !this._autoWidth;\n\t\t}\n\t\t_shouldAutoFitHeight() {\n\t\t\t\treturn this._autoFitHeight && !this._autoHeight;\n\t\t}\n\t\t_shouldAutoFit() {\n\t\t\t\treturn this._autoFitWidth && !this._autoWidth || this._autoFitHeight && !this._autoHeight;\n\t\t}\n\t\t_calculateCharsPerTexture(symbolIndex) {\n\t\t\t\tconst charactersPerTexture = {};\n\t\t\t\tif (symbolIndex === undefined) {\n\t\t\t\t\t\tsymbolIndex = this._symbols.length;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0, len = symbolIndex; i < len; i++){\n\t\t\t\t\t\tconst char = this._symbols[i];\n\t\t\t\t\t\tlet info = this._font.data.chars[char];\n\t\t\t\t\t\tif (!info) {\n\t\t\t\t\t\t\t\tinfo = this._font.data.chars[' '];\n\t\t\t\t\t\t\t\tif (!info) {\n\t\t\t\t\t\t\t\t\t\tinfo = this._font.data.chars[Object.keys(this._font.data.chars)[0]];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst map = info.map;\n\t\t\t\t\t\tif (!charactersPerTexture[map]) {\n\t\t\t\t\t\t\t\tcharactersPerTexture[map] = 1;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcharactersPerTexture[map]++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn charactersPerTexture;\n\t\t}\n\t\t_updateRenderRange() {\n\t\t\t\tconst startChars = this._rangeStart === 0 ? 0 : this._calculateCharsPerTexture(this._rangeStart);\n\t\t\t\tconst endChars = this._rangeEnd === 0 ? 0 : this._calculateCharsPerTexture(this._rangeEnd);\n\t\t\t\tfor(let i = 0, len = this._meshInfo.length; i < len; i++){\n\t\t\t\t\t\tconst start = startChars[i] || 0;\n\t\t\t\t\t\tconst end = endChars[i] || 0;\n\t\t\t\t\t\tconst instance = this._meshInfo[i].meshInstance;\n\t\t\t\t\t\tif (instance) {\n\t\t\t\t\t\t\t\tconst mesh = instance.mesh;\n\t\t\t\t\t\t\t\tif (mesh) {\n\t\t\t\t\t\t\t\t\t\tmesh.primitive[0].base = start * 3 * 2;\n\t\t\t\t\t\t\t\t\t\tmesh.primitive[0].count = (end - start) * 3 * 2;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tset text(value) {\n\t\t\t\tthis._i18nKey = null;\n\t\t\t\tconst str = value != null && value.toString() || '';\n\t\t\t\tthis._setText(str);\n\t\t}\n\t\tget text() {\n\t\t\t\treturn this._text;\n\t\t}\n\t\tset key(value) {\n\t\t\t\tconst str = value !== null ? value.toString() : null;\n\t\t\t\tif (this._i18nKey === str) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._i18nKey = str;\n\t\t\t\tif (str) {\n\t\t\t\t\t\tthis._fontAsset.disableLocalization = false;\n\t\t\t\t\t\tthis._resetLocalizedText();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._fontAsset.disableLocalization = true;\n\t\t\t\t}\n\t\t}\n\t\tget key() {\n\t\t\t\treturn this._i18nKey;\n\t\t}\n\t\tset color(value) {\n\t\t\t\tconst r = value.r;\n\t\t\t\tconst g = value.g;\n\t\t\t\tconst b = value.b;\n\t\t\t\tif (this._color.r === r && this._color.g === g && this._color.b === b) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._color.r = r;\n\t\t\t\tthis._color.g = g;\n\t\t\t\tthis._color.b = b;\n\t\t\t\tif (!this._model) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._symbolColors) {\n\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempColor.linear(this._color);\n\t\t\t\t\t\tthis._colorUniform[0] = _tempColor.r;\n\t\t\t\t\t\tthis._colorUniform[1] = _tempColor.g;\n\t\t\t\t\t\tthis._colorUniform[2] = _tempColor.b;\n\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\tmi.setParameter('material_emissive', this._colorUniform);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis._element.fire('set:color', this._color);\n\t\t\t\t}\n\t\t}\n\t\tget color() {\n\t\t\t\treturn this._color;\n\t\t}\n\t\tset opacity(value) {\n\t\t\t\tif (this._color.a !== value) {\n\t\t\t\t\t\tthis._color.a = value;\n\t\t\t\t\t\tif (this._model) {\n\t\t\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('material_opacity', value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis._element.fire('set:opacity', value);\n\t\t\t\t}\n\t\t}\n\t\tget opacity() {\n\t\t\t\treturn this._color.a;\n\t\t}\n\t\tset lineHeight(value) {\n\t\t\t\tconst _prev = this._lineHeight;\n\t\t\t\tthis._lineHeight = value;\n\t\t\t\tthis._scaledLineHeight = value;\n\t\t\t\tif (_prev !== value && this._font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget lineHeight() {\n\t\t\t\treturn this._lineHeight;\n\t\t}\n\t\tset wrapLines(value) {\n\t\t\t\tconst _prev = this._wrapLines;\n\t\t\t\tthis._wrapLines = value;\n\t\t\t\tif (_prev !== value && this._font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget wrapLines() {\n\t\t\t\treturn this._wrapLines;\n\t\t}\n\t\tget lines() {\n\t\t\t\treturn this._lineContents;\n\t\t}\n\t\tset spacing(value) {\n\t\t\t\tconst _prev = this._spacing;\n\t\t\t\tthis._spacing = value;\n\t\t\t\tif (_prev !== value && this._font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget spacing() {\n\t\t\t\treturn this._spacing;\n\t\t}\n\t\tset fontSize(value) {\n\t\t\t\tconst _prev = this._fontSize;\n\t\t\t\tthis._fontSize = value;\n\t\t\t\tthis._originalFontSize = value;\n\t\t\t\tif (_prev !== value && this._font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget fontSize() {\n\t\t\t\treturn this._fontSize;\n\t\t}\n\t\tset fontAsset(value) {\n\t\t\t\tthis._fontAsset.defaultAsset = value;\n\t\t}\n\t\tget fontAsset() {\n\t\t\t\treturn this._fontAsset.localizedAsset;\n\t\t}\n\t\tset font(value) {\n\t\t\t\tlet previousFontType;\n\t\t\t\tif (this._font) {\n\t\t\t\t\t\tpreviousFontType = this._font.type;\n\t\t\t\t\t\tif (this._font.off) this._font.off('render', this._onFontRender, this);\n\t\t\t\t}\n\t\t\t\tthis._font = value;\n\t\t\t\tthis._fontMinY = 0;\n\t\t\t\tthis._fontMaxY = 0;\n\t\t\t\tif (!value) return;\n\t\t\t\tconst json = this._font.data;\n\t\t\t\tfor(const charId in json.chars){\n\t\t\t\t\t\tconst data = json.chars[charId];\n\t\t\t\t\t\tif (data.bounds) {\n\t\t\t\t\t\t\t\tthis._fontMinY = Math.min(this._fontMinY, data.bounds[1]);\n\t\t\t\t\t\t\t\tthis._fontMaxY = Math.max(this._fontMaxY, data.bounds[3]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._font.on) this._font.on('render', this._onFontRender, this);\n\t\t\t\tif (this._fontAsset.localizedAsset) {\n\t\t\t\t\t\tconst asset = this._system.app.assets.get(this._fontAsset.localizedAsset);\n\t\t\t\t\t\tif (asset.resource !== this._font) {\n\t\t\t\t\t\t\t\tthis._fontAsset.defaultAsset = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (value.type !== previousFontType) {\n\t\t\t\t\t\tconst screenSpace = this._element._isScreenSpace();\n\t\t\t\t\t\tthis._updateMaterial(screenSpace);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0, len = this._font.textures.length; i < len; i++){\n\t\t\t\t\t\tif (!this._meshInfo[i]) {\n\t\t\t\t\t\t\t\tthis._meshInfo[i] = new MeshInfo();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst mi = this._meshInfo[i].meshInstance;\n\t\t\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('font_sdfIntensity', this._font.intensity);\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('font_pxrange', this._getPxRange(this._font));\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('font_textureWidth', this._font.data.info.maps[i].width);\n\t\t\t\t\t\t\t\t\t\tthis._setTextureParams(mi, this._font.textures[i]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet removedModel = false;\n\t\t\t\tfor(let i = this._font.textures.length; i < this._meshInfo.length; i++){\n\t\t\t\t\t\tif (this._meshInfo[i].meshInstance) {\n\t\t\t\t\t\t\t\tif (!removedModel) {\n\t\t\t\t\t\t\t\t\t\tthis._element.removeModelFromLayers(this._model);\n\t\t\t\t\t\t\t\t\t\tremovedModel = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._removeMeshInstance(this._meshInfo[i].meshInstance);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._meshInfo.length > this._font.textures.length) {\n\t\t\t\t\t\tthis._meshInfo.length = this._font.textures.length;\n\t\t\t\t}\n\t\t\t\tthis._updateText();\n\t\t}\n\t\tget font() {\n\t\t\t\treturn this._font;\n\t\t}\n\t\tset alignment(value) {\n\t\t\t\tif (value instanceof Vec2) {\n\t\t\t\t\t\tthis._alignment.set(value.x, value.y);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._alignment.set(value[0], value[1]);\n\t\t\t\t}\n\t\t\t\tif (this._font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget alignment() {\n\t\t\t\treturn this._alignment;\n\t\t}\n\t\tset autoWidth(value) {\n\t\t\t\tconst old = this._autoWidth;\n\t\t\t\tthis._autoWidth = value;\n\t\t\t\tif (value && Math.abs(this._element.anchor.x - this._element.anchor.z) < 0.0001) {\n\t\t\t\t\t\tthis._element.width = this.width;\n\t\t\t\t}\n\t\t\t\tif (old !== value) {\n\t\t\t\t\t\tconst newFontSize = this._shouldAutoFit() ? this._maxFontSize : this._originalFontSize;\n\t\t\t\t\t\tif (newFontSize !== this._fontSize) {\n\t\t\t\t\t\t\t\tthis._fontSize = newFontSize;\n\t\t\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget autoWidth() {\n\t\t\t\treturn this._autoWidth;\n\t\t}\n\t\tset autoHeight(value) {\n\t\t\t\tconst old = this._autoHeight;\n\t\t\t\tthis._autoHeight = value;\n\t\t\t\tif (value && Math.abs(this._element.anchor.y - this._element.anchor.w) < 0.0001) {\n\t\t\t\t\t\tthis._element.height = this.height;\n\t\t\t\t}\n\t\t\t\tif (old !== value) {\n\t\t\t\t\t\tconst newFontSize = this._shouldAutoFit() ? this._maxFontSize : this._originalFontSize;\n\t\t\t\t\t\tif (newFontSize !== this._fontSize) {\n\t\t\t\t\t\t\t\tthis._fontSize = newFontSize;\n\t\t\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget autoHeight() {\n\t\t\t\treturn this._autoHeight;\n\t\t}\n\t\tset rtlReorder(value) {\n\t\t\t\tif (this._rtlReorder !== value) {\n\t\t\t\t\t\tthis._rtlReorder = value;\n\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget rtlReorder() {\n\t\t\t\treturn this._rtlReorder;\n\t\t}\n\t\tset unicodeConverter(value) {\n\t\t\t\tif (this._unicodeConverter !== value) {\n\t\t\t\t\t\tthis._unicodeConverter = value;\n\t\t\t\t\t\tthis._setText(this._text);\n\t\t\t\t}\n\t\t}\n\t\tget unicodeConverter() {\n\t\t\t\treturn this._unicodeConverter;\n\t\t}\n\t\tget aabb() {\n\t\t\t\tif (this._aabbDirty) {\n\t\t\t\t\t\tlet initialized = false;\n\t\t\t\t\t\tfor(let i = 0; i < this._meshInfo.length; i++){\n\t\t\t\t\t\t\t\tif (!this._meshInfo[i].meshInstance) continue;\n\t\t\t\t\t\t\t\tif (!initialized) {\n\t\t\t\t\t\t\t\t\t\tthis._aabb.copy(this._meshInfo[i].meshInstance.aabb);\n\t\t\t\t\t\t\t\t\t\tinitialized = true;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._aabb.add(this._meshInfo[i].meshInstance.aabb);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._aabbDirty = false;\n\t\t\t\t}\n\t\t\t\treturn this._aabb;\n\t\t}\n\t\tset outlineColor(value) {\n\t\t\t\tconst r = value instanceof Color ? value.r : value[0];\n\t\t\t\tconst g = value instanceof Color ? value.g : value[1];\n\t\t\t\tconst b = value instanceof Color ? value.b : value[2];\n\t\t\t\tconst a = value instanceof Color ? value.a : value[3];\n\t\t\t\tif (this._outlineColor.r === r && this._outlineColor.g === g && this._outlineColor.b === b && this._outlineColor.a === a) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._outlineColor.r = r;\n\t\t\t\tthis._outlineColor.g = g;\n\t\t\t\tthis._outlineColor.b = b;\n\t\t\t\tthis._outlineColor.a = a;\n\t\t\t\tif (!this._model) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._symbolOutlineParams) {\n\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempColor.linear(this._outlineColor);\n\t\t\t\t\t\tthis._outlineColorUniform[0] = _tempColor.r;\n\t\t\t\t\t\tthis._outlineColorUniform[1] = _tempColor.g;\n\t\t\t\t\t\tthis._outlineColorUniform[2] = _tempColor.b;\n\t\t\t\t\t\tthis._outlineColorUniform[3] = _tempColor.a;\n\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\tmi.setParameter('outline_color', this._outlineColorUniform);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis._element.fire('set:outline', this._color);\n\t\t\t\t}\n\t\t}\n\t\tget outlineColor() {\n\t\t\t\treturn this._outlineColor;\n\t\t}\n\t\tset outlineThickness(value) {\n\t\t\t\tconst _prev = this._outlineThickness;\n\t\t\t\tthis._outlineThickness = value;\n\t\t\t\tif (_prev !== value && this._font) {\n\t\t\t\t\t\tif (!this._model) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._symbolOutlineParams) {\n\t\t\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('outline_thickness', this._outlineThicknessScale * this._outlineThickness);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget outlineThickness() {\n\t\t\t\treturn this._outlineThickness;\n\t\t}\n\t\tset shadowColor(value) {\n\t\t\t\tconst r = value instanceof Color ? value.r : value[0];\n\t\t\t\tconst g = value instanceof Color ? value.g : value[1];\n\t\t\t\tconst b = value instanceof Color ? value.b : value[2];\n\t\t\t\tconst a = value instanceof Color ? value.a : value[3];\n\t\t\t\tif (this._shadowColor.r === r && this._shadowColor.g === g && this._shadowColor.b === b && this._shadowColor.a === a) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._shadowColor.r = r;\n\t\t\t\tthis._shadowColor.g = g;\n\t\t\t\tthis._shadowColor.b = b;\n\t\t\t\tthis._shadowColor.a = a;\n\t\t\t\tif (!this._model) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._symbolShadowParams) {\n\t\t\t\t\t\tif (this._font) {\n\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempColor.linear(this._shadowColor);\n\t\t\t\t\t\tthis._shadowColorUniform[0] = _tempColor.r;\n\t\t\t\t\t\tthis._shadowColorUniform[1] = _tempColor.g;\n\t\t\t\t\t\tthis._shadowColorUniform[2] = _tempColor.b;\n\t\t\t\t\t\tthis._shadowColorUniform[3] = _tempColor.a;\n\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\tmi.setParameter('shadow_color', this._shadowColorUniform);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget shadowColor() {\n\t\t\t\treturn this._shadowColor;\n\t\t}\n\t\tset shadowOffset(value) {\n\t\t\t\tconst x = value instanceof Vec2 ? value.x : value[0], y = value instanceof Vec2 ? value.y : value[1];\n\t\t\t\tif (this._shadowOffset.x === x && this._shadowOffset.y === y) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._shadowOffset.set(x, y);\n\t\t\t\tif (this._font && this._model) {\n\t\t\t\t\t\tif (this._symbolShadowParams) {\n\t\t\t\t\t\t\t\tthis._updateText();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(let i = 0, len = this._model.meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tconst ratio = -this._font.data.info.maps[i].width / this._font.data.info.maps[i].height;\n\t\t\t\t\t\t\t\t\t\tthis._shadowOffsetUniform[0] = this._shadowOffsetScale * this._shadowOffset.x;\n\t\t\t\t\t\t\t\t\t\tthis._shadowOffsetUniform[1] = ratio * this._shadowOffsetScale * this._shadowOffset.y;\n\t\t\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances[i];\n\t\t\t\t\t\t\t\t\t\tmi.setParameter('shadow_offset', this._shadowOffsetUniform);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget shadowOffset() {\n\t\t\t\treturn this._shadowOffset;\n\t\t}\n\t\tset minFontSize(value) {\n\t\t\t\tif (this._minFontSize === value) return;\n\t\t\t\tthis._minFontSize = value;\n\t\t\t\tif (this.font && this._shouldAutoFit()) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget minFontSize() {\n\t\t\t\treturn this._minFontSize;\n\t\t}\n\t\tset maxFontSize(value) {\n\t\t\t\tif (this._maxFontSize === value) return;\n\t\t\t\tthis._maxFontSize = value;\n\t\t\t\tif (this.font && this._shouldAutoFit()) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget maxFontSize() {\n\t\t\t\treturn this._maxFontSize;\n\t\t}\n\t\tset autoFitWidth(value) {\n\t\t\t\tif (this._autoFitWidth === value) return;\n\t\t\t\tthis._autoFitWidth = value;\n\t\t\t\tthis._fontSize = this._shouldAutoFit() ? this._maxFontSize : this._originalFontSize;\n\t\t\t\tif (this.font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget autoFitWidth() {\n\t\t\t\treturn this._autoFitWidth;\n\t\t}\n\t\tset autoFitHeight(value) {\n\t\t\t\tif (this._autoFitHeight === value) return;\n\t\t\t\tthis._autoFitHeight = value;\n\t\t\t\tthis._fontSize = this._shouldAutoFit() ? this._maxFontSize : this._originalFontSize;\n\t\t\t\tif (this.font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget autoFitHeight() {\n\t\t\t\treturn this._autoFitHeight;\n\t\t}\n\t\tset maxLines(value) {\n\t\t\t\tif (this._maxLines === value) return;\n\t\t\t\tif (value === null && this._maxLines === -1) return;\n\t\t\t\tthis._maxLines = value === null ? -1 : value;\n\t\t\t\tif (this.font && this._wrapLines) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t}\n\t\tget maxLines() {\n\t\t\t\treturn this._maxLines;\n\t\t}\n\t\tset enableMarkup(value) {\n\t\t\t\tvalue = !!value;\n\t\t\t\tif (this._enableMarkup === value) return;\n\t\t\t\tthis._enableMarkup = value;\n\t\t\t\tif (this.font) {\n\t\t\t\t\t\tthis._updateText();\n\t\t\t\t}\n\t\t\t\tconst screenSpace = this._element._isScreenSpace();\n\t\t\t\tthis._updateMaterial(screenSpace);\n\t\t}\n\t\tget enableMarkup() {\n\t\t\t\treturn this._enableMarkup;\n\t\t}\n\t\tget symbols() {\n\t\t\t\treturn this._symbols;\n\t\t}\n\t\tget symbolColors() {\n\t\t\t\tif (this._symbolColors === null) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._symbolColors.map(function(c) {\n\t\t\t\t\t\treturn this._colorPalette.slice(c * 3, c * 3 + 3);\n\t\t\t\t}, this);\n\t\t}\n\t\tget symbolOutlineParams() {\n\t\t\t\tif (this._symbolOutlineParams === null) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._symbolOutlineParams.map(function(paramId) {\n\t\t\t\t\t\treturn this._outlinePalette.slice(paramId * 5, paramId * 5 + 5);\n\t\t\t\t}, this);\n\t\t}\n\t\tget symbolShadowParams() {\n\t\t\t\tif (this._symbolShadowParams === null) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._symbolShadowParams.map(function(paramId) {\n\t\t\t\t\t\treturn this._shadowPalette.slice(paramId * 6, paramId * 6 + 6);\n\t\t\t\t}, this);\n\t\t}\n\t\tget rtl() {\n\t\t\t\treturn this._rtl;\n\t\t}\n\t\tset rangeStart(rangeStart) {\n\t\t\t\trangeStart = Math.max(0, Math.min(rangeStart, this._symbols.length));\n\t\t\t\tif (rangeStart !== this._rangeStart) {\n\t\t\t\t\t\tthis._rangeStart = rangeStart;\n\t\t\t\t\t\tthis._updateRenderRange();\n\t\t\t\t}\n\t\t}\n\t\tget rangeStart() {\n\t\t\t\treturn this._rangeStart;\n\t\t}\n\t\tset rangeEnd(rangeEnd) {\n\t\t\t\trangeEnd = Math.max(this._rangeStart, Math.min(rangeEnd, this._symbols.length));\n\t\t\t\tif (rangeEnd !== this._rangeEnd) {\n\t\t\t\t\t\tthis._rangeEnd = rangeEnd;\n\t\t\t\t\t\tthis._updateRenderRange();\n\t\t\t\t}\n\t\t}\n\t\tget rangeEnd() {\n\t\t\t\treturn this._rangeEnd;\n\t\t}\n}\n\nconst position = new Vec3();\nconst invParentWtm = new Mat4();\nconst vecA$1 = new Vec3();\nconst vecB$1 = new Vec3();\nconst matA = new Mat4();\nconst matB = new Mat4();\nconst matC = new Mat4();\nconst matD = new Mat4();\nclass ElementComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEDOWN = 'mousedown';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEUP = 'mouseup';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEENTER = 'mouseenter';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSELEAVE = 'mouseleave';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEMOVE = 'mousemove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOUSEWHEEL = 'mousewheel';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_CLICK = 'click';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHSTART = 'touchstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHEND = 'touchend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHMOVE = 'touchmove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TOUCHCANCEL = 'touchcancel';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null;\n\t\t\t\tthis._beingInitialized = false;\n\t\t\t\tthis._anchor = new Vec4();\n\t\t\t\tthis._localAnchor = new Vec4();\n\t\t\t\tthis._pivot = new Vec2();\n\t\t\t\tthis._width = this._calculatedWidth = 32;\n\t\t\t\tthis._height = this._calculatedHeight = 32;\n\t\t\t\tthis._margin = new Vec4(0, 0, -32, -32);\n\t\t\t\tthis._modelTransform = new Mat4();\n\t\t\t\tthis._screenToWorld = new Mat4();\n\t\t\t\tthis._anchorTransform = new Mat4();\n\t\t\t\tthis._anchorDirty = true;\n\t\t\t\tthis._parentWorldTransform = new Mat4();\n\t\t\t\tthis._screenTransform = new Mat4();\n\t\t\t\tthis._screenCorners = [\n\t\t\t\t\t\tnew Vec3(),\n\t\t\t\t\t\tnew Vec3(),\n\t\t\t\t\t\tnew Vec3(),\n\t\t\t\t\t\tnew Vec3()\n\t\t\t\t];\n\t\t\t\tthis._canvasCorners = [\n\t\t\t\t\t\tnew Vec2(),\n\t\t\t\t\t\tnew Vec2(),\n\t\t\t\t\t\tnew Vec2(),\n\t\t\t\t\t\tnew Vec2()\n\t\t\t\t];\n\t\t\t\tthis._worldCorners = [\n\t\t\t\t\t\tnew Vec3(),\n\t\t\t\t\t\tnew Vec3(),\n\t\t\t\t\t\tnew Vec3(),\n\t\t\t\t\t\tnew Vec3()\n\t\t\t\t];\n\t\t\t\tthis._cornersDirty = true;\n\t\t\t\tthis._canvasCornersDirty = true;\n\t\t\t\tthis._worldCornersDirty = true;\n\t\t\t\tthis.entity.on('insert', this._onInsert, this);\n\t\t\t\tthis._patch();\n\t\t\t\tthis.screen = null;\n\t\t\t\tthis._type = ELEMENTTYPE_GROUP;\n\t\t\t\tthis._image = null;\n\t\t\t\tthis._text = null;\n\t\t\t\tthis._group = null;\n\t\t\t\tthis._drawOrder = 0;\n\t\t\t\tthis._fitMode = FITMODE_STRETCH;\n\t\t\t\tthis._useInput = false;\n\t\t\t\tthis._layers = [\n\t\t\t\t\t\tLAYERID_UI\n\t\t\t\t];\n\t\t\t\tthis._addedModels = [];\n\t\t\t\tthis._batchGroupId = -1;\n\t\t\t\tthis._offsetReadAt = 0;\n\t\t\t\tthis._maskOffset = 0.5;\n\t\t\t\tthis._maskedBy = null;\n\t\t}\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(value) {\n\t\t\t\tconst data = this.data;\n\t\t\t\tconst oldValue = data.enabled;\n\t\t\t\tdata.enabled = value;\n\t\t\t\tthis.fire('set', 'enabled', oldValue, value);\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this.data.enabled;\n\t\t}\n\t\tget _absLeft() {\n\t\t\t\treturn this._localAnchor.x + this._margin.x;\n\t\t}\n\t\tget _absRight() {\n\t\t\t\treturn this._localAnchor.z - this._margin.z;\n\t\t}\n\t\tget _absTop() {\n\t\t\t\treturn this._localAnchor.w - this._margin.w;\n\t\t}\n\t\tget _absBottom() {\n\t\t\t\treturn this._localAnchor.y + this._margin.y;\n\t\t}\n\t\tget _hasSplitAnchorsX() {\n\t\t\t\treturn Math.abs(this._anchor.x - this._anchor.z) > 0.001;\n\t\t}\n\t\tget _hasSplitAnchorsY() {\n\t\t\t\treturn Math.abs(this._anchor.y - this._anchor.w) > 0.001;\n\t\t}\n\t\tget aabb() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.aabb;\n\t\t\t\t}\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.aabb;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset anchor(value) {\n\t\t\t\tif (value instanceof Vec4) {\n\t\t\t\t\t\tthis._anchor.copy(value);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._anchor.set(...value);\n\t\t\t\t}\n\t\t\t\tif (!this.entity._parent && !this.screen) {\n\t\t\t\t\t\tthis._calculateLocalAnchors();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._calculateSize(this._hasSplitAnchorsX, this._hasSplitAnchorsY);\n\t\t\t\t}\n\t\t\t\tthis._anchorDirty = true;\n\t\t\t\tif (!this.entity._dirtyLocal) {\n\t\t\t\t\t\tthis.entity._dirtifyLocal();\n\t\t\t\t}\n\t\t\t\tthis.fire('set:anchor', this._anchor);\n\t\t}\n\t\tget anchor() {\n\t\t\t\treturn this._anchor;\n\t\t}\n\t\tset batchGroupId(value) {\n\t\t\t\tif (this._batchGroupId === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this.entity.enabled && this._batchGroupId >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.remove(BatchGroup.ELEMENT, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t\t\tif (this.entity.enabled && value >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.insert(BatchGroup.ELEMENT, value, this.entity);\n\t\t\t\t}\n\t\t\t\tif (value < 0 && this._batchGroupId >= 0 && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tif (this._image && this._image._renderable.model) {\n\t\t\t\t\t\t\t\tthis.addModelToLayers(this._image._renderable.model);\n\t\t\t\t\t\t} else if (this._text && this._text._model) {\n\t\t\t\t\t\t\t\tthis.addModelToLayers(this._text._model);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._batchGroupId = value;\n\t\t}\n\t\tget batchGroupId() {\n\t\t\t\treturn this._batchGroupId;\n\t\t}\n\t\tset bottom(value) {\n\t\t\t\tthis._margin.y = value;\n\t\t\t\tconst p = this.entity.getLocalPosition();\n\t\t\t\tconst wt = this._absTop;\n\t\t\t\tconst wb = this._localAnchor.y + value;\n\t\t\t\tthis._setHeight(wt - wb);\n\t\t\t\tp.y = value + this._calculatedHeight * this._pivot.y;\n\t\t\t\tthis.entity.setLocalPosition(p);\n\t\t}\n\t\tget bottom() {\n\t\t\t\treturn this._margin.y;\n\t\t}\n\t\tset calculatedWidth(value) {\n\t\t\t\tthis._setCalculatedWidth(value, true);\n\t\t}\n\t\tget calculatedWidth() {\n\t\t\t\treturn this._calculatedWidth;\n\t\t}\n\t\tset calculatedHeight(value) {\n\t\t\t\tthis._setCalculatedHeight(value, true);\n\t\t}\n\t\tget calculatedHeight() {\n\t\t\t\treturn this._calculatedHeight;\n\t\t}\n\t\tget canvasCorners() {\n\t\t\t\tif (!this._canvasCornersDirty || !this.screen || !this.screen.screen.screenSpace) {\n\t\t\t\t\t\treturn this._canvasCorners;\n\t\t\t\t}\n\t\t\t\tconst device = this.system.app.graphicsDevice;\n\t\t\t\tconst screenCorners = this.screenCorners;\n\t\t\t\tconst sx = device.canvas.clientWidth / device.width;\n\t\t\t\tconst sy = device.canvas.clientHeight / device.height;\n\t\t\t\tfor(let i = 0; i < 4; i++){\n\t\t\t\t\t\tthis._canvasCorners[i].set(screenCorners[i].x * sx, (device.height - screenCorners[i].y) * sy);\n\t\t\t\t}\n\t\t\t\tthis._canvasCornersDirty = false;\n\t\t\t\treturn this._canvasCorners;\n\t\t}\n\t\tset drawOrder(value) {\n\t\t\t\tlet priority = 0;\n\t\t\t\tif (this.screen) {\n\t\t\t\t\t\tpriority = this.screen.screen.priority;\n\t\t\t\t}\n\t\t\t\tif (value > 0xFFFFFF) {\n\t\t\t\t\t\tvalue = 0xFFFFFF;\n\t\t\t\t}\n\t\t\t\tthis._drawOrder = (priority << 24) + value;\n\t\t\t\tthis.fire('set:draworder', this._drawOrder);\n\t\t}\n\t\tget drawOrder() {\n\t\t\t\treturn this._drawOrder;\n\t\t}\n\t\tset height(value) {\n\t\t\t\tthis._height = value;\n\t\t\t\tif (!this._hasSplitAnchorsY) {\n\t\t\t\t\t\tthis._setCalculatedHeight(value, true);\n\t\t\t\t}\n\t\t\t\tthis.fire('set:height', this._height);\n\t\t}\n\t\tget height() {\n\t\t\t\treturn this._height;\n\t\t}\n\t\tset layers(value) {\n\t\t\t\tif (this._addedModels.length) {\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < this._addedModels.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tlayer.removeMeshInstances(this._addedModels[j].meshInstances);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._layers = value;\n\t\t\t\tif (!this.enabled || !this.entity.enabled || !this._addedModels.length) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tfor(let j = 0; j < this._addedModels.length; j++){\n\t\t\t\t\t\t\t\t\t\tlayer.addMeshInstances(this._addedModels[j].meshInstances);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tset left(value) {\n\t\t\t\tthis._margin.x = value;\n\t\t\t\tconst p = this.entity.getLocalPosition();\n\t\t\t\tconst wr = this._absRight;\n\t\t\t\tconst wl = this._localAnchor.x + value;\n\t\t\t\tthis._setWidth(wr - wl);\n\t\t\t\tp.x = value + this._calculatedWidth * this._pivot.x;\n\t\t\t\tthis.entity.setLocalPosition(p);\n\t\t}\n\t\tget left() {\n\t\t\t\treturn this._margin.x;\n\t\t}\n\t\tset margin(value) {\n\t\t\t\tthis._margin.copy(value);\n\t\t\t\tthis._calculateSize(true, true);\n\t\t\t\tthis.fire('set:margin', this._margin);\n\t\t}\n\t\tget margin() {\n\t\t\t\treturn this._margin;\n\t\t}\n\t\tget maskedBy() {\n\t\t\t\treturn this._maskedBy;\n\t\t}\n\t\tset pivot(value) {\n\t\t\t\tconst { pivot, margin } = this;\n\t\t\t\tconst prevX = pivot.x;\n\t\t\t\tconst prevY = pivot.y;\n\t\t\t\tif (value instanceof Vec2) {\n\t\t\t\t\t\tpivot.copy(value);\n\t\t\t\t} else {\n\t\t\t\t\t\tpivot.set(...value);\n\t\t\t\t}\n\t\t\t\tconst mx = margin.x + margin.z;\n\t\t\t\tconst dx = pivot.x - prevX;\n\t\t\t\tmargin.x += mx * dx;\n\t\t\t\tmargin.z -= mx * dx;\n\t\t\t\tconst my = margin.y + margin.w;\n\t\t\t\tconst dy = pivot.y - prevY;\n\t\t\t\tmargin.y += my * dy;\n\t\t\t\tmargin.w -= my * dy;\n\t\t\t\tthis._anchorDirty = true;\n\t\t\t\tthis._cornersDirty = true;\n\t\t\t\tthis._worldCornersDirty = true;\n\t\t\t\tthis._calculateSize(false, false);\n\t\t\t\tthis._flagChildrenAsDirty();\n\t\t\t\tthis.fire('set:pivot', pivot);\n\t\t}\n\t\tget pivot() {\n\t\t\t\treturn this._pivot;\n\t\t}\n\t\tset right(value) {\n\t\t\t\tthis._margin.z = value;\n\t\t\t\tconst p = this.entity.getLocalPosition();\n\t\t\t\tconst wl = this._absLeft;\n\t\t\t\tconst wr = this._localAnchor.z - value;\n\t\t\t\tthis._setWidth(wr - wl);\n\t\t\t\tp.x = this._localAnchor.z - this._localAnchor.x - value - this._calculatedWidth * (1 - this._pivot.x);\n\t\t\t\tthis.entity.setLocalPosition(p);\n\t\t}\n\t\tget right() {\n\t\t\t\treturn this._margin.z;\n\t\t}\n\t\tget screenCorners() {\n\t\t\t\tif (!this._cornersDirty || !this.screen) {\n\t\t\t\t\t\treturn this._screenCorners;\n\t\t\t\t}\n\t\t\t\tconst parentBottomLeft = this.entity.parent && this.entity.parent.element && this.entity.parent.element.screenCorners[0];\n\t\t\t\tthis._screenCorners[0].set(this._absLeft, this._absBottom, 0);\n\t\t\t\tthis._screenCorners[1].set(this._absRight, this._absBottom, 0);\n\t\t\t\tthis._screenCorners[2].set(this._absRight, this._absTop, 0);\n\t\t\t\tthis._screenCorners[3].set(this._absLeft, this._absTop, 0);\n\t\t\t\tconst screenSpace = this.screen.screen.screenSpace;\n\t\t\t\tfor(let i = 0; i < 4; i++){\n\t\t\t\t\t\tthis._screenTransform.transformPoint(this._screenCorners[i], this._screenCorners[i]);\n\t\t\t\t\t\tif (screenSpace) {\n\t\t\t\t\t\t\t\tthis._screenCorners[i].mulScalar(this.screen.screen.scale);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (parentBottomLeft) {\n\t\t\t\t\t\t\t\tthis._screenCorners[i].add(parentBottomLeft);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._cornersDirty = false;\n\t\t\t\tthis._canvasCornersDirty = true;\n\t\t\t\tthis._worldCornersDirty = true;\n\t\t\t\treturn this._screenCorners;\n\t\t}\n\t\tget textWidth() {\n\t\t\t\treturn this._text ? this._text.width : 0;\n\t\t}\n\t\tget textHeight() {\n\t\t\t\treturn this._text ? this._text.height : 0;\n\t\t}\n\t\tset top(value) {\n\t\t\t\tthis._margin.w = value;\n\t\t\t\tconst p = this.entity.getLocalPosition();\n\t\t\t\tconst wb = this._absBottom;\n\t\t\t\tconst wt = this._localAnchor.w - value;\n\t\t\t\tthis._setHeight(wt - wb);\n\t\t\t\tp.y = this._localAnchor.w - this._localAnchor.y - value - this._calculatedHeight * (1 - this._pivot.y);\n\t\t\t\tthis.entity.setLocalPosition(p);\n\t\t}\n\t\tget top() {\n\t\t\t\treturn this._margin.w;\n\t\t}\n\t\tset type(value) {\n\t\t\t\tif (value !== this._type) {\n\t\t\t\t\t\tthis._type = value;\n\t\t\t\t\t\tif (this._image) {\n\t\t\t\t\t\t\t\tthis._image.destroy();\n\t\t\t\t\t\t\t\tthis._image = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._text) {\n\t\t\t\t\t\t\t\tthis._text.destroy();\n\t\t\t\t\t\t\t\tthis._text = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (value === ELEMENTTYPE_IMAGE) {\n\t\t\t\t\t\t\t\tthis._image = new ImageElement(this);\n\t\t\t\t\t\t} else if (value === ELEMENTTYPE_TEXT) {\n\t\t\t\t\t\t\t\tthis._text = new TextElement(this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tset useInput(value) {\n\t\t\t\tif (this._useInput === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._useInput = value;\n\t\t\t\tif (this.system.app.elementInput) {\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tthis.system.app.elementInput.addElement(this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.system.app.elementInput.removeElement(this);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this._useInput === true) ;\n\t\t\t\t}\n\t\t\t\tthis.fire('set:useInput', value);\n\t\t}\n\t\tget useInput() {\n\t\t\t\treturn this._useInput;\n\t\t}\n\t\tset fitMode(value) {\n\t\t\t\tthis._fitMode = value;\n\t\t\t\tthis._calculateSize(true, true);\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\tthis._image.refreshMesh();\n\t\t\t\t}\n\t\t}\n\t\tget fitMode() {\n\t\t\t\treturn this._fitMode;\n\t\t}\n\t\tset width(value) {\n\t\t\t\tthis._width = value;\n\t\t\t\tif (!this._hasSplitAnchorsX) {\n\t\t\t\t\t\tthis._setCalculatedWidth(value, true);\n\t\t\t\t}\n\t\t\t\tthis.fire('set:width', this._width);\n\t\t}\n\t\tget width() {\n\t\t\t\treturn this._width;\n\t\t}\n\t\tget worldCorners() {\n\t\t\t\tif (!this._worldCornersDirty) {\n\t\t\t\t\t\treturn this._worldCorners;\n\t\t\t\t}\n\t\t\t\tif (this.screen) {\n\t\t\t\t\t\tconst screenCorners = this.screenCorners;\n\t\t\t\t\t\tif (!this.screen.screen.screenSpace) {\n\t\t\t\t\t\t\t\tmatA.copy(this.screen.screen._screenMatrix);\n\t\t\t\t\t\t\t\tmatA.data[13] = -matA.data[13];\n\t\t\t\t\t\t\t\tmatA.mul2(this.screen.getWorldTransform(), matA);\n\t\t\t\t\t\t\t\tfor(let i = 0; i < 4; i++){\n\t\t\t\t\t\t\t\t\t\tmatA.transformPoint(screenCorners[i], this._worldCorners[i]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst localPos = this.entity.getLocalPosition();\n\t\t\t\t\t\tmatA.setTranslate(-localPos.x, -localPos.y, -localPos.z);\n\t\t\t\t\t\tmatB.setTRS(Vec3.ZERO, this.entity.getLocalRotation(), this.entity.getLocalScale());\n\t\t\t\t\t\tmatC.setTranslate(localPos.x, localPos.y, localPos.z);\n\t\t\t\t\t\tconst entity = this.entity.parent ? this.entity.parent : this.entity;\n\t\t\t\t\t\tmatD.copy(entity.getWorldTransform());\n\t\t\t\t\t\tmatD.mul(matC).mul(matB).mul(matA);\n\t\t\t\t\t\tvecA$1.set(localPos.x - this.pivot.x * this.calculatedWidth, localPos.y - this.pivot.y * this.calculatedHeight, localPos.z);\n\t\t\t\t\t\tmatD.transformPoint(vecA$1, this._worldCorners[0]);\n\t\t\t\t\t\tvecA$1.set(localPos.x + (1 - this.pivot.x) * this.calculatedWidth, localPos.y - this.pivot.y * this.calculatedHeight, localPos.z);\n\t\t\t\t\t\tmatD.transformPoint(vecA$1, this._worldCorners[1]);\n\t\t\t\t\t\tvecA$1.set(localPos.x + (1 - this.pivot.x) * this.calculatedWidth, localPos.y + (1 - this.pivot.y) * this.calculatedHeight, localPos.z);\n\t\t\t\t\t\tmatD.transformPoint(vecA$1, this._worldCorners[2]);\n\t\t\t\t\t\tvecA$1.set(localPos.x - this.pivot.x * this.calculatedWidth, localPos.y + (1 - this.pivot.y) * this.calculatedHeight, localPos.z);\n\t\t\t\t\t\tmatD.transformPoint(vecA$1, this._worldCorners[3]);\n\t\t\t\t}\n\t\t\t\tthis._worldCornersDirty = false;\n\t\t\t\treturn this._worldCorners;\n\t\t}\n\t\tset fontSize(arg) {\n\t\t\t\tthis._setValue('fontSize', arg);\n\t\t}\n\t\tget fontSize() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.fontSize;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset minFontSize(arg) {\n\t\t\t\tthis._setValue('minFontSize', arg);\n\t\t}\n\t\tget minFontSize() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.minFontSize;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset maxFontSize(arg) {\n\t\t\t\tthis._setValue('maxFontSize', arg);\n\t\t}\n\t\tget maxFontSize() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.maxFontSize;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset maxLines(arg) {\n\t\t\t\tthis._setValue('maxLines', arg);\n\t\t}\n\t\tget maxLines() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.maxLines;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset autoFitWidth(arg) {\n\t\t\t\tthis._setValue('autoFitWidth', arg);\n\t\t}\n\t\tget autoFitWidth() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.autoFitWidth;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset autoFitHeight(arg) {\n\t\t\t\tthis._setValue('autoFitHeight', arg);\n\t\t}\n\t\tget autoFitHeight() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.autoFitHeight;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset color(arg) {\n\t\t\t\tthis._setValue('color', arg);\n\t\t}\n\t\tget color() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.color;\n\t\t\t\t}\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.color;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset font(arg) {\n\t\t\t\tthis._setValue('font', arg);\n\t\t}\n\t\tget font() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.font;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset fontAsset(arg) {\n\t\t\t\tthis._setValue('fontAsset', arg);\n\t\t}\n\t\tget fontAsset() {\n\t\t\t\tif (this._text && typeof this._text.fontAsset === 'number') {\n\t\t\t\t\t\treturn this._text.fontAsset;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset spacing(arg) {\n\t\t\t\tthis._setValue('spacing', arg);\n\t\t}\n\t\tget spacing() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.spacing;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset lineHeight(arg) {\n\t\t\t\tthis._setValue('lineHeight', arg);\n\t\t}\n\t\tget lineHeight() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.lineHeight;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset wrapLines(arg) {\n\t\t\t\tthis._setValue('wrapLines', arg);\n\t\t}\n\t\tget wrapLines() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.wrapLines;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset lines(arg) {\n\t\t\t\tthis._setValue('lines', arg);\n\t\t}\n\t\tget lines() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.lines;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset alignment(arg) {\n\t\t\t\tthis._setValue('alignment', arg);\n\t\t}\n\t\tget alignment() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.alignment;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset autoWidth(arg) {\n\t\t\t\tthis._setValue('autoWidth', arg);\n\t\t}\n\t\tget autoWidth() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.autoWidth;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset autoHeight(arg) {\n\t\t\t\tthis._setValue('autoHeight', arg);\n\t\t}\n\t\tget autoHeight() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.autoHeight;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset rtlReorder(arg) {\n\t\t\t\tthis._setValue('rtlReorder', arg);\n\t\t}\n\t\tget rtlReorder() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.rtlReorder;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset unicodeConverter(arg) {\n\t\t\t\tthis._setValue('unicodeConverter', arg);\n\t\t}\n\t\tget unicodeConverter() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.unicodeConverter;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset text(arg) {\n\t\t\t\tthis._setValue('text', arg);\n\t\t}\n\t\tget text() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.text;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset key(arg) {\n\t\t\t\tthis._setValue('key', arg);\n\t\t}\n\t\tget key() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.key;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset texture(arg) {\n\t\t\t\tthis._setValue('texture', arg);\n\t\t}\n\t\tget texture() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.texture;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset textureAsset(arg) {\n\t\t\t\tthis._setValue('textureAsset', arg);\n\t\t}\n\t\tget textureAsset() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.textureAsset;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset material(arg) {\n\t\t\t\tthis._setValue('material', arg);\n\t\t}\n\t\tget material() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.material;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset materialAsset(arg) {\n\t\t\t\tthis._setValue('materialAsset', arg);\n\t\t}\n\t\tget materialAsset() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.materialAsset;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset sprite(arg) {\n\t\t\t\tthis._setValue('sprite', arg);\n\t\t}\n\t\tget sprite() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.sprite;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset spriteAsset(arg) {\n\t\t\t\tthis._setValue('spriteAsset', arg);\n\t\t}\n\t\tget spriteAsset() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.spriteAsset;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset spriteFrame(arg) {\n\t\t\t\tthis._setValue('spriteFrame', arg);\n\t\t}\n\t\tget spriteFrame() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.spriteFrame;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset pixelsPerUnit(arg) {\n\t\t\t\tthis._setValue('pixelsPerUnit', arg);\n\t\t}\n\t\tget pixelsPerUnit() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.pixelsPerUnit;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset opacity(arg) {\n\t\t\t\tthis._setValue('opacity', arg);\n\t\t}\n\t\tget opacity() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.opacity;\n\t\t\t\t}\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.opacity;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset rect(arg) {\n\t\t\t\tthis._setValue('rect', arg);\n\t\t}\n\t\tget rect() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.rect;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset mask(arg) {\n\t\t\t\tthis._setValue('mask', arg);\n\t\t}\n\t\tget mask() {\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\treturn this._image.mask;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset outlineColor(arg) {\n\t\t\t\tthis._setValue('outlineColor', arg);\n\t\t}\n\t\tget outlineColor() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.outlineColor;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset outlineThickness(arg) {\n\t\t\t\tthis._setValue('outlineThickness', arg);\n\t\t}\n\t\tget outlineThickness() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.outlineThickness;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset shadowColor(arg) {\n\t\t\t\tthis._setValue('shadowColor', arg);\n\t\t}\n\t\tget shadowColor() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.shadowColor;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset shadowOffset(arg) {\n\t\t\t\tthis._setValue('shadowOffset', arg);\n\t\t}\n\t\tget shadowOffset() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.shadowOffset;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset enableMarkup(arg) {\n\t\t\t\tthis._setValue('enableMarkup', arg);\n\t\t}\n\t\tget enableMarkup() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.enableMarkup;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset rangeStart(arg) {\n\t\t\t\tthis._setValue('rangeStart', arg);\n\t\t}\n\t\tget rangeStart() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.rangeStart;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tset rangeEnd(arg) {\n\t\t\t\tthis._setValue('rangeEnd', arg);\n\t\t}\n\t\tget rangeEnd() {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\treturn this._text.rangeEnd;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\t_setValue(name, value) {\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\tif (this._text[name] !== value) {\n\t\t\t\t\t\t\t\tthis._dirtyBatch();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._text[name] = value;\n\t\t\t\t} else if (this._image) {\n\t\t\t\t\t\tif (this._image[name] !== value) {\n\t\t\t\t\t\t\t\tthis._dirtyBatch();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._image[name] = value;\n\t\t\t\t}\n\t\t}\n\t\t_patch() {\n\t\t\t\tthis.entity._sync = this._sync;\n\t\t\t\tthis.entity.setPosition = this._setPosition;\n\t\t\t\tthis.entity.setLocalPosition = this._setLocalPosition;\n\t\t}\n\t\t_unpatch() {\n\t\t\t\tthis.entity._sync = Entity.prototype._sync;\n\t\t\t\tthis.entity.setPosition = Entity.prototype.setPosition;\n\t\t\t\tthis.entity.setLocalPosition = Entity.prototype.setLocalPosition;\n\t\t}\n\t\t_setPosition(x, y, z) {\n\t\t\t\tif (!this.element.screen) {\n\t\t\t\t\t\tEntity.prototype.setPosition.call(this, x, y, z);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tposition.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tposition.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tthis.getWorldTransform();\n\t\t\t\tinvParentWtm.copy(this.element._screenToWorld).invert();\n\t\t\t\tinvParentWtm.transformPoint(position, this.localPosition);\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\t_setLocalPosition(x, y, z) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tthis.localPosition.copy(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.localPosition.set(x, y, z);\n\t\t\t\t}\n\t\t\t\tconst element = this.element;\n\t\t\t\tconst p = this.localPosition;\n\t\t\t\tconst pvt = element._pivot;\n\t\t\t\telement._margin.x = p.x - element._calculatedWidth * pvt.x;\n\t\t\t\telement._margin.z = element._localAnchor.z - element._localAnchor.x - element._calculatedWidth - element._margin.x;\n\t\t\t\telement._margin.y = p.y - element._calculatedHeight * pvt.y;\n\t\t\t\telement._margin.w = element._localAnchor.w - element._localAnchor.y - element._calculatedHeight - element._margin.y;\n\t\t\t\tif (!this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtifyLocal();\n\t\t\t\t}\n\t\t}\n\t\t_sync() {\n\t\t\t\tconst element = this.element;\n\t\t\t\tconst screen = element.screen;\n\t\t\t\tif (screen) {\n\t\t\t\t\t\tif (element._anchorDirty) {\n\t\t\t\t\t\t\t\tlet resx = 0;\n\t\t\t\t\t\t\t\tlet resy = 0;\n\t\t\t\t\t\t\t\tlet px = 0;\n\t\t\t\t\t\t\t\tlet py = 1;\n\t\t\t\t\t\t\t\tif (this._parent && this._parent.element) {\n\t\t\t\t\t\t\t\t\t\tresx = this._parent.element.calculatedWidth;\n\t\t\t\t\t\t\t\t\t\tresy = this._parent.element.calculatedHeight;\n\t\t\t\t\t\t\t\t\t\tpx = this._parent.element.pivot.x;\n\t\t\t\t\t\t\t\t\t\tpy = this._parent.element.pivot.y;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst resolution = screen.screen.resolution;\n\t\t\t\t\t\t\t\t\t\tresx = resolution.x / screen.screen.scale;\n\t\t\t\t\t\t\t\t\t\tresy = resolution.y / screen.screen.scale;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telement._anchorTransform.setTranslate(resx * (element.anchor.x - px), -(resy * (py - element.anchor.y)), 0);\n\t\t\t\t\t\t\t\telement._anchorDirty = false;\n\t\t\t\t\t\t\t\telement._calculateLocalAnchors();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (element._sizeDirty) {\n\t\t\t\t\t\t\t\telement._calculateSize(false, false);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._dirtyLocal) {\n\t\t\t\t\t\tthis.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);\n\t\t\t\t\t\tconst p = this.localPosition;\n\t\t\t\t\t\tconst pvt = element._pivot;\n\t\t\t\t\t\telement._margin.x = p.x - element._calculatedWidth * pvt.x;\n\t\t\t\t\t\telement._margin.z = element._localAnchor.z - element._localAnchor.x - element._calculatedWidth - element._margin.x;\n\t\t\t\t\t\telement._margin.y = p.y - element._calculatedHeight * pvt.y;\n\t\t\t\t\t\telement._margin.w = element._localAnchor.w - element._localAnchor.y - element._calculatedHeight - element._margin.y;\n\t\t\t\t\t\tthis._dirtyLocal = false;\n\t\t\t\t}\n\t\t\t\tif (!screen) {\n\t\t\t\t\t\tif (this._dirtyWorld) {\n\t\t\t\t\t\t\t\telement._cornersDirty = true;\n\t\t\t\t\t\t\t\telement._canvasCornersDirty = true;\n\t\t\t\t\t\t\t\telement._worldCornersDirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tEntity.prototype._sync.call(this);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._dirtyWorld) {\n\t\t\t\t\t\tif (this._parent === null) {\n\t\t\t\t\t\t\t\tthis.worldTransform.copy(this.localTransform);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this._parent.element) {\n\t\t\t\t\t\t\t\t\t\telement._screenToWorld.mul2(this._parent.element._modelTransform, element._anchorTransform);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\telement._screenToWorld.copy(element._anchorTransform);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telement._modelTransform.mul2(element._screenToWorld, this.localTransform);\n\t\t\t\t\t\t\t\tif (screen) {\n\t\t\t\t\t\t\t\t\t\telement._screenToWorld.mul2(screen.screen._screenMatrix, element._screenToWorld);\n\t\t\t\t\t\t\t\t\t\tif (!screen.screen.screenSpace) {\n\t\t\t\t\t\t\t\t\t\t\t\telement._screenToWorld.mul2(screen.worldTransform, element._screenToWorld);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.worldTransform.mul2(element._screenToWorld, this.localTransform);\n\t\t\t\t\t\t\t\t\t\tconst parentWorldTransform = element._parentWorldTransform;\n\t\t\t\t\t\t\t\t\t\tparentWorldTransform.setIdentity();\n\t\t\t\t\t\t\t\t\t\tconst parent = this._parent;\n\t\t\t\t\t\t\t\t\t\tif (parent && parent.element && parent !== screen) {\n\t\t\t\t\t\t\t\t\t\t\t\tmatA.setTRS(Vec3.ZERO, parent.getLocalRotation(), parent.getLocalScale());\n\t\t\t\t\t\t\t\t\t\t\t\tparentWorldTransform.mul2(parent.element._parentWorldTransform, matA);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst depthOffset = vecA$1;\n\t\t\t\t\t\t\t\t\t\tdepthOffset.set(0, 0, this.localPosition.z);\n\t\t\t\t\t\t\t\t\t\tconst pivotOffset = vecB$1;\n\t\t\t\t\t\t\t\t\t\tpivotOffset.set(element._absLeft + element._pivot.x * element.calculatedWidth, element._absBottom + element._pivot.y * element.calculatedHeight, 0);\n\t\t\t\t\t\t\t\t\t\tmatA.setTranslate(-pivotOffset.x, -pivotOffset.y, -pivotOffset.z);\n\t\t\t\t\t\t\t\t\t\tmatB.setTRS(depthOffset, this.getLocalRotation(), this.getLocalScale());\n\t\t\t\t\t\t\t\t\t\tmatC.setTranslate(pivotOffset.x, pivotOffset.y, pivotOffset.z);\n\t\t\t\t\t\t\t\t\t\telement._screenTransform.mul2(element._parentWorldTransform, matC).mul(matB).mul(matA);\n\t\t\t\t\t\t\t\t\t\telement._cornersDirty = true;\n\t\t\t\t\t\t\t\t\t\telement._canvasCornersDirty = true;\n\t\t\t\t\t\t\t\t\t\telement._worldCornersDirty = true;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.worldTransform.copy(element._modelTransform);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._dirtyWorld = false;\n\t\t\t\t}\n\t\t}\n\t\t_onInsert(parent) {\n\t\t\t\tconst result = this._parseUpToScreen();\n\t\t\t\tthis.entity._dirtifyWorld();\n\t\t\t\tthis._updateScreen(result.screen);\n\t\t\t\tthis._dirtifyMask();\n\t\t}\n\t\t_dirtifyMask() {\n\t\t\t\tlet current = this.entity;\n\t\t\t\twhile(current){\n\t\t\t\t\t\tconst next = current.parent;\n\t\t\t\t\t\tif ((next === null || next.screen) && current.element) {\n\t\t\t\t\t\t\t\tif (!this.system._prerender || !this.system._prerender.length) {\n\t\t\t\t\t\t\t\t\t\tthis.system._prerender = [];\n\t\t\t\t\t\t\t\t\t\tthis.system.app.once('prerender', this._onPrerender, this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst i = this.system._prerender.indexOf(this.entity);\n\t\t\t\t\t\t\t\tif (i >= 0) {\n\t\t\t\t\t\t\t\t\t\tthis.system._prerender.splice(i, 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst j = this.system._prerender.indexOf(current);\n\t\t\t\t\t\t\t\tif (j < 0) {\n\t\t\t\t\t\t\t\t\t\tthis.system._prerender.push(current);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrent = next;\n\t\t\t\t}\n\t\t}\n\t\t_onPrerender() {\n\t\t\t\tfor(let i = 0; i < this.system._prerender.length; i++){\n\t\t\t\t\t\tconst mask = this.system._prerender[i];\n\t\t\t\t\t\tif (mask.element) {\n\t\t\t\t\t\t\t\tconst depth = 1;\n\t\t\t\t\t\t\t\tmask.element.syncMask(depth);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.system._prerender.length = 0;\n\t\t}\n\t\t_bindScreen(screen) {\n\t\t\t\tscreen._bindElement(this);\n\t\t}\n\t\t_unbindScreen(screen) {\n\t\t\t\tscreen._unbindElement(this);\n\t\t}\n\t\t_updateScreen(screen) {\n\t\t\t\tif (this.screen && this.screen !== screen) {\n\t\t\t\t\t\tthis._unbindScreen(this.screen.screen);\n\t\t\t\t}\n\t\t\t\tconst previousScreen = this.screen;\n\t\t\t\tthis.screen = screen;\n\t\t\t\tif (this.screen) {\n\t\t\t\t\t\tthis._bindScreen(this.screen.screen);\n\t\t\t\t}\n\t\t\t\tthis._calculateSize(this._hasSplitAnchorsX, this._hasSplitAnchorsY);\n\t\t\t\tthis.fire('set:screen', this.screen, previousScreen);\n\t\t\t\tthis._anchorDirty = true;\n\t\t\t\tconst children = this.entity.children;\n\t\t\t\tfor(let i = 0, l = children.length; i < l; i++){\n\t\t\t\t\t\tif (children[i].element) {\n\t\t\t\t\t\t\t\tchildren[i].element._updateScreen(screen);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.screen) {\n\t\t\t\t\t\tthis.screen.screen.syncDrawOrder();\n\t\t\t\t}\n\t\t}\n\t\tsyncMask(depth) {\n\t\t\t\tconst result = this._parseUpToScreen();\n\t\t\t\tthis._updateMask(result.mask, depth);\n\t\t}\n\t\t_setMaskedBy(mask) {\n\t\t\t\tconst renderableElement = this._image || this._text;\n\t\t\t\tif (mask) {\n\t\t\t\t\t\tconst ref = mask.element._image._maskRef;\n\t\t\t\t\t\trenderableElement?._setStencil(new StencilParameters({\n\t\t\t\t\t\t\t\tref: ref,\n\t\t\t\t\t\t\t\tfunc: FUNC_EQUAL\n\t\t\t\t\t\t}));\n\t\t\t\t\t\tthis._maskedBy = mask;\n\t\t\t\t} else {\n\t\t\t\t\t\trenderableElement?._setStencil(null);\n\t\t\t\t\t\tthis._maskedBy = null;\n\t\t\t\t}\n\t\t}\n\t\t_updateMask(currentMask, depth) {\n\t\t\t\tif (currentMask) {\n\t\t\t\t\t\tthis._setMaskedBy(currentMask);\n\t\t\t\t\t\tif (this.mask) {\n\t\t\t\t\t\t\t\tconst ref = currentMask.element._image._maskRef;\n\t\t\t\t\t\t\t\tconst sp = new StencilParameters({\n\t\t\t\t\t\t\t\t\t\tref: ref,\n\t\t\t\t\t\t\t\t\t\tfunc: FUNC_EQUAL,\n\t\t\t\t\t\t\t\t\t\tzpass: STENCILOP_INCREMENT\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tthis._image._setStencil(sp);\n\t\t\t\t\t\t\t\tthis._image._maskRef = depth;\n\t\t\t\t\t\t\t\tdepth++;\n\t\t\t\t\t\t\t\tcurrentMask = this.entity;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst children = this.entity.children;\n\t\t\t\t\t\tfor(let i = 0, l = children.length; i < l; i++){\n\t\t\t\t\t\t\t\tchildren[i].element?._updateMask(currentMask, depth);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.mask) depth--;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._setMaskedBy(null);\n\t\t\t\t\t\tif (this.mask) {\n\t\t\t\t\t\t\t\tconst sp = new StencilParameters({\n\t\t\t\t\t\t\t\t\t\tref: depth,\n\t\t\t\t\t\t\t\t\t\tfunc: FUNC_ALWAYS,\n\t\t\t\t\t\t\t\t\t\tzpass: STENCILOP_REPLACE\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tthis._image._setStencil(sp);\n\t\t\t\t\t\t\t\tthis._image._maskRef = depth;\n\t\t\t\t\t\t\t\tdepth++;\n\t\t\t\t\t\t\t\tcurrentMask = this.entity;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst children = this.entity.children;\n\t\t\t\t\t\tfor(let i = 0, l = children.length; i < l; i++){\n\t\t\t\t\t\t\t\tchildren[i].element?._updateMask(currentMask, depth);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.mask) {\n\t\t\t\t\t\t\t\tdepth--;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_parseUpToScreen() {\n\t\t\t\tconst result = {\n\t\t\t\t\t\tscreen: null,\n\t\t\t\t\t\tmask: null\n\t\t\t\t};\n\t\t\t\tlet parent = this.entity._parent;\n\t\t\t\twhile(parent && !parent.screen){\n\t\t\t\t\t\tif (parent.element && parent.element.mask) {\n\t\t\t\t\t\t\t\tif (!result.mask) result.mask = parent;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparent = parent.parent;\n\t\t\t\t}\n\t\t\t\tif (parent && parent.screen) {\n\t\t\t\t\t\tresult.screen = parent;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\t_onScreenResize(res) {\n\t\t\t\tthis._anchorDirty = true;\n\t\t\t\tthis._cornersDirty = true;\n\t\t\t\tthis._worldCornersDirty = true;\n\t\t\t\tthis._calculateSize(this._hasSplitAnchorsX, this._hasSplitAnchorsY);\n\t\t\t\tthis.fire('screen:set:resolution', res);\n\t\t}\n\t\t_onScreenSpaceChange() {\n\t\t\t\tthis.fire('screen:set:screenspace', this.screen.screen.screenSpace);\n\t\t}\n\t\t_onScreenRemove() {\n\t\t\t\tif (this.screen) {\n\t\t\t\t\t\tif (this.screen._destroying) {\n\t\t\t\t\t\t\t\tthis.screen = null;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._updateScreen(null);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_calculateLocalAnchors() {\n\t\t\t\tlet resx = 1000;\n\t\t\t\tlet resy = 1000;\n\t\t\t\tconst parent = this.entity._parent;\n\t\t\t\tif (parent && parent.element) {\n\t\t\t\t\t\tresx = parent.element.calculatedWidth;\n\t\t\t\t\t\tresy = parent.element.calculatedHeight;\n\t\t\t\t} else if (this.screen) {\n\t\t\t\t\t\tconst res = this.screen.screen.resolution;\n\t\t\t\t\t\tconst scale = this.screen.screen.scale;\n\t\t\t\t\t\tresx = res.x / scale;\n\t\t\t\t\t\tresy = res.y / scale;\n\t\t\t\t}\n\t\t\t\tthis._localAnchor.set(this._anchor.x * resx, this._anchor.y * resy, this._anchor.z * resx, this._anchor.w * resy);\n\t\t}\n\t\tgetOffsetPosition(x, y) {\n\t\t\t\tconst p = this.entity.getLocalPosition().clone();\n\t\t\t\tp.x += x;\n\t\t\t\tp.y += y;\n\t\t\t\tthis._screenToWorld.transformPoint(p, p);\n\t\t\t\treturn p;\n\t\t}\n\t\tonLayersChanged(oldComp, newComp) {\n\t\t\t\tthis.addModelToLayers(this._image ? this._image._renderable.model : this._text._model);\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t}\n\t\tonLayerAdded(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\tlayer.addMeshInstances(this._image._renderable.model.meshInstances);\n\t\t\t\t} else if (this._text) {\n\t\t\t\t\t\tlayer.addMeshInstances(this._text._model.meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tonLayerRemoved(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\tlayer.removeMeshInstances(this._image._renderable.model.meshInstances);\n\t\t\t\t} else if (this._text) {\n\t\t\t\t\t\tlayer.removeMeshInstances(this._text._model.meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\tthis._image.onEnable();\n\t\t\t\t}\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\tthis._text.onEnable();\n\t\t\t\t}\n\t\t\t\tif (this._group) {\n\t\t\t\t\t\tthis._group.onEnable();\n\t\t\t\t}\n\t\t\t\tif (this.useInput && this.system.app.elementInput) {\n\t\t\t\t\t\tthis.system.app.elementInput.addElement(this);\n\t\t\t\t}\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this.onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.insert(BatchGroup.ELEMENT, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t\t\tthis.fire('enableelement');\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tif (this._image) this._image.onDisable();\n\t\t\t\tif (this._text) this._text.onDisable();\n\t\t\t\tif (this._group) this._group.onDisable();\n\t\t\t\tif (this.system.app.elementInput && this.useInput) {\n\t\t\t\t\t\tthis.system.app.elementInput.removeElement(this);\n\t\t\t\t}\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.remove(BatchGroup.ELEMENT, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t\t\tthis.fire('disableelement');\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.entity.off('insert', this._onInsert, this);\n\t\t\t\tthis._unpatch();\n\t\t\t\tif (this._image) {\n\t\t\t\t\t\tthis._image.destroy();\n\t\t\t\t}\n\t\t\t\tif (this._text) {\n\t\t\t\t\t\tthis._text.destroy();\n\t\t\t\t}\n\t\t\t\tif (this.system.app.elementInput && this.useInput) {\n\t\t\t\t\t\tthis.system.app.elementInput.removeElement(this);\n\t\t\t\t}\n\t\t\t\tif (this.screen && this.screen.screen) {\n\t\t\t\t\t\tthis._unbindScreen(this.screen.screen);\n\t\t\t\t\t\tthis.screen.screen.syncDrawOrder();\n\t\t\t\t}\n\t\t\t\tthis.off();\n\t\t}\n\t\t_calculateSize(propagateCalculatedWidth, propagateCalculatedHeight) {\n\t\t\t\tif (!this.entity._parent && !this.screen) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._calculateLocalAnchors();\n\t\t\t\tconst newWidth = this._absRight - this._absLeft;\n\t\t\t\tconst newHeight = this._absTop - this._absBottom;\n\t\t\t\tif (propagateCalculatedWidth) {\n\t\t\t\t\t\tthis._setWidth(newWidth);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._setCalculatedWidth(newWidth, false);\n\t\t\t\t}\n\t\t\t\tif (propagateCalculatedHeight) {\n\t\t\t\t\t\tthis._setHeight(newHeight);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._setCalculatedHeight(newHeight, false);\n\t\t\t\t}\n\t\t\t\tconst p = this.entity.getLocalPosition();\n\t\t\t\tp.x = this._margin.x + this._calculatedWidth * this._pivot.x;\n\t\t\t\tp.y = this._margin.y + this._calculatedHeight * this._pivot.y;\n\t\t\t\tthis.entity.setLocalPosition(p);\n\t\t\t\tthis._sizeDirty = false;\n\t\t}\n\t\t_setWidth(w) {\n\t\t\t\tthis._width = w;\n\t\t\t\tthis._setCalculatedWidth(w, false);\n\t\t\t\tthis.fire('set:width', this._width);\n\t\t}\n\t\t_setHeight(h) {\n\t\t\t\tthis._height = h;\n\t\t\t\tthis._setCalculatedHeight(h, false);\n\t\t\t\tthis.fire('set:height', this._height);\n\t\t}\n\t\t_setCalculatedWidth(value, updateMargins) {\n\t\t\t\tif (Math.abs(value - this._calculatedWidth) <= 1e-4) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._calculatedWidth = value;\n\t\t\t\tthis.entity._dirtifyLocal();\n\t\t\t\tif (updateMargins) {\n\t\t\t\t\t\tconst p = this.entity.getLocalPosition();\n\t\t\t\t\t\tconst pvt = this._pivot;\n\t\t\t\t\t\tthis._margin.x = p.x - this._calculatedWidth * pvt.x;\n\t\t\t\t\t\tthis._margin.z = this._localAnchor.z - this._localAnchor.x - this._calculatedWidth - this._margin.x;\n\t\t\t\t}\n\t\t\t\tthis._flagChildrenAsDirty();\n\t\t\t\tthis.fire('set:calculatedWidth', this._calculatedWidth);\n\t\t\t\tthis.fire('resize', this._calculatedWidth, this._calculatedHeight);\n\t\t}\n\t\t_setCalculatedHeight(value, updateMargins) {\n\t\t\t\tif (Math.abs(value - this._calculatedHeight) <= 1e-4) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._calculatedHeight = value;\n\t\t\t\tthis.entity._dirtifyLocal();\n\t\t\t\tif (updateMargins) {\n\t\t\t\t\t\tconst p = this.entity.getLocalPosition();\n\t\t\t\t\t\tconst pvt = this._pivot;\n\t\t\t\t\t\tthis._margin.y = p.y - this._calculatedHeight * pvt.y;\n\t\t\t\t\t\tthis._margin.w = this._localAnchor.w - this._localAnchor.y - this._calculatedHeight - this._margin.y;\n\t\t\t\t}\n\t\t\t\tthis._flagChildrenAsDirty();\n\t\t\t\tthis.fire('set:calculatedHeight', this._calculatedHeight);\n\t\t\t\tthis.fire('resize', this._calculatedWidth, this._calculatedHeight);\n\t\t}\n\t\t_flagChildrenAsDirty() {\n\t\t\t\tconst c = this.entity._children;\n\t\t\t\tfor(let i = 0, l = c.length; i < l; i++){\n\t\t\t\t\t\tif (c[i].element) {\n\t\t\t\t\t\t\t\tc[i].element._anchorDirty = true;\n\t\t\t\t\t\t\t\tc[i].element._sizeDirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\taddModelToLayers(model) {\n\t\t\t\tthis._addedModels.push(model);\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.addMeshInstances(model.meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tremoveModelFromLayers(model) {\n\t\t\t\tconst idx = this._addedModels.indexOf(model);\n\t\t\t\tif (idx >= 0) {\n\t\t\t\t\t\tthis._addedModels.splice(idx, 1);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\tif (!layer) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlayer.removeMeshInstances(model.meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tgetMaskOffset() {\n\t\t\t\tconst frame = this.system.app.frame;\n\t\t\t\tif (this._offsetReadAt !== frame) {\n\t\t\t\t\t\tthis._maskOffset = 0.5;\n\t\t\t\t\t\tthis._offsetReadAt = frame;\n\t\t\t\t}\n\t\t\t\tconst mo = this._maskOffset;\n\t\t\t\tthis._maskOffset -= 0.001;\n\t\t\t\treturn mo;\n\t\t}\n\t\tisVisibleForCamera(camera) {\n\t\t\t\tlet clipL, clipR, clipT, clipB;\n\t\t\t\tif (this.maskedBy) {\n\t\t\t\t\t\tconst corners = this.maskedBy.element.screenCorners;\n\t\t\t\t\t\tclipL = Math.min(Math.min(corners[0].x, corners[1].x), Math.min(corners[2].x, corners[3].x));\n\t\t\t\t\t\tclipR = Math.max(Math.max(corners[0].x, corners[1].x), Math.max(corners[2].x, corners[3].x));\n\t\t\t\t\t\tclipB = Math.min(Math.min(corners[0].y, corners[1].y), Math.min(corners[2].y, corners[3].y));\n\t\t\t\t\t\tclipT = Math.max(Math.max(corners[0].y, corners[1].y), Math.max(corners[2].y, corners[3].y));\n\t\t\t\t} else {\n\t\t\t\t\t\tconst sw = this.system.app.graphicsDevice.width;\n\t\t\t\t\t\tconst sh = this.system.app.graphicsDevice.height;\n\t\t\t\t\t\tconst cameraWidth = camera._rect.z * sw;\n\t\t\t\t\t\tconst cameraHeight = camera._rect.w * sh;\n\t\t\t\t\t\tclipL = camera._rect.x * sw;\n\t\t\t\t\t\tclipR = clipL + cameraWidth;\n\t\t\t\t\t\tclipT = (1 - camera._rect.y) * sh;\n\t\t\t\t\t\tclipB = clipT - cameraHeight;\n\t\t\t\t}\n\t\t\t\tconst hitCorners = this.screenCorners;\n\t\t\t\tconst left = Math.min(Math.min(hitCorners[0].x, hitCorners[1].x), Math.min(hitCorners[2].x, hitCorners[3].x));\n\t\t\t\tconst right = Math.max(Math.max(hitCorners[0].x, hitCorners[1].x), Math.max(hitCorners[2].x, hitCorners[3].x));\n\t\t\t\tconst bottom = Math.min(Math.min(hitCorners[0].y, hitCorners[1].y), Math.min(hitCorners[2].y, hitCorners[3].y));\n\t\t\t\tconst top = Math.max(Math.max(hitCorners[0].y, hitCorners[1].y), Math.max(hitCorners[2].y, hitCorners[3].y));\n\t\t\t\tif (right < clipL || left > clipR || bottom > clipT || top < clipB) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\t_isScreenSpace() {\n\t\t\t\tif (this.screen && this.screen.screen) {\n\t\t\t\t\t\treturn this.screen.screen.screenSpace;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\t_isScreenCulled() {\n\t\t\t\tif (this.screen && this.screen.screen) {\n\t\t\t\t\t\treturn this.screen.screen.cull;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\t_dirtyBatch() {\n\t\t\t\tif (this.batchGroupId !== -1) {\n\t\t\t\t\t\tthis.system.app.batcher?.markGroupDirty(this.batchGroupId);\n\t\t\t\t}\n\t\t}\n}\n\nclass ElementComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$f = [\n\t\t'enabled'\n];\nclass ElementComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'element';\n\t\t\t\tthis.ComponentType = ElementComponent;\n\t\t\t\tthis.DataType = ElementComponentData;\n\t\t\t\tthis.schema = _schema$f;\n\t\t\t\tthis._unicodeConverter = null;\n\t\t\t\tthis._rtlReorder = null;\n\t\t\t\tthis._defaultTexture = new Texture(app.graphicsDevice, {\n\t\t\t\t\t\twidth: 1,\n\t\t\t\t\t\theight: 1,\n\t\t\t\t\t\tformat: PIXELFORMAT_SRGBA8,\n\t\t\t\t\t\tname: 'element-system'\n\t\t\t\t});\n\t\t\t\tconst pixels = this._defaultTexture.lock();\n\t\t\t\tconst pixelData = new Uint8Array(4);\n\t\t\t\tpixelData[0] = 255.0;\n\t\t\t\tpixelData[1] = 255.0;\n\t\t\t\tpixelData[2] = 255.0;\n\t\t\t\tpixelData[3] = 255.0;\n\t\t\t\tpixels.set(pixelData);\n\t\t\t\tthis._defaultTexture.unlock();\n\t\t\t\tthis.defaultImageMaterial = null;\n\t\t\t\tthis.defaultImage9SlicedMaterial = null;\n\t\t\t\tthis.defaultImage9TiledMaterial = null;\n\t\t\t\tthis.defaultImageMaskMaterial = null;\n\t\t\t\tthis.defaultImage9SlicedMaskMaterial = null;\n\t\t\t\tthis.defaultImage9TiledMaskMaterial = null;\n\t\t\t\tthis.defaultScreenSpaceImageMaterial = null;\n\t\t\t\tthis.defaultScreenSpaceImage9SlicedMaterial = null;\n\t\t\t\tthis.defaultScreenSpaceImage9TiledMaterial = null;\n\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial = null;\n\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial = null;\n\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial = null;\n\t\t\t\tthis._defaultTextMaterials = {};\n\t\t\t\tthis.defaultImageMaterials = [];\n\t\t\t\tthis.on('add', this.onAddComponent, this);\n\t\t\t\tthis.on('beforeremove', this.onRemoveComponent, this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis._defaultTexture.destroy();\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tcomponent._beingInitialized = true;\n\t\t\t\tif (data.anchor !== undefined) {\n\t\t\t\t\t\tif (data.anchor instanceof Vec4) {\n\t\t\t\t\t\t\t\tcomponent.anchor.copy(data.anchor);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomponent.anchor.set(data.anchor[0], data.anchor[1], data.anchor[2], data.anchor[3]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (data.pivot !== undefined) {\n\t\t\t\t\t\tif (data.pivot instanceof Vec2) {\n\t\t\t\t\t\t\t\tcomponent.pivot.copy(data.pivot);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomponent.pivot.set(data.pivot[0], data.pivot[1]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst splitHorAnchors = Math.abs(component.anchor.x - component.anchor.z) > 0.001;\n\t\t\t\tconst splitVerAnchors = Math.abs(component.anchor.y - component.anchor.w) > 0.001;\n\t\t\t\tlet _marginChange = false;\n\t\t\t\tlet color;\n\t\t\t\tif (data.margin !== undefined) {\n\t\t\t\t\t\tif (data.margin instanceof Vec4) {\n\t\t\t\t\t\t\t\tcomponent.margin.copy(data.margin);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomponent._margin.set(data.margin[0], data.margin[1], data.margin[2], data.margin[3]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_marginChange = true;\n\t\t\t\t}\n\t\t\t\tif (data.left !== undefined) {\n\t\t\t\t\t\tcomponent._margin.x = data.left;\n\t\t\t\t\t\t_marginChange = true;\n\t\t\t\t}\n\t\t\t\tif (data.bottom !== undefined) {\n\t\t\t\t\t\tcomponent._margin.y = data.bottom;\n\t\t\t\t\t\t_marginChange = true;\n\t\t\t\t}\n\t\t\t\tif (data.right !== undefined) {\n\t\t\t\t\t\tcomponent._margin.z = data.right;\n\t\t\t\t\t\t_marginChange = true;\n\t\t\t\t}\n\t\t\t\tif (data.top !== undefined) {\n\t\t\t\t\t\tcomponent._margin.w = data.top;\n\t\t\t\t\t\t_marginChange = true;\n\t\t\t\t}\n\t\t\t\tif (_marginChange) {\n\t\t\t\t\t\tcomponent.margin = component._margin;\n\t\t\t\t}\n\t\t\t\tlet shouldForceSetAnchor = false;\n\t\t\t\tif (data.width !== undefined && !splitHorAnchors) {\n\t\t\t\t\t\tcomponent.width = data.width;\n\t\t\t\t} else if (splitHorAnchors) {\n\t\t\t\t\t\tshouldForceSetAnchor = true;\n\t\t\t\t}\n\t\t\t\tif (data.height !== undefined && !splitVerAnchors) {\n\t\t\t\t\t\tcomponent.height = data.height;\n\t\t\t\t} else if (splitVerAnchors) {\n\t\t\t\t\t\tshouldForceSetAnchor = true;\n\t\t\t\t}\n\t\t\t\tif (shouldForceSetAnchor) {\n\t\t\t\t\t\tcomponent.anchor = component.anchor;\n\t\t\t\t}\n\t\t\t\tif (data.enabled !== undefined) {\n\t\t\t\t\t\tcomponent.enabled = data.enabled;\n\t\t\t\t}\n\t\t\t\tif (data.useInput !== undefined) {\n\t\t\t\t\t\tcomponent.useInput = data.useInput;\n\t\t\t\t}\n\t\t\t\tif (data.fitMode !== undefined) {\n\t\t\t\t\t\tcomponent.fitMode = data.fitMode;\n\t\t\t\t}\n\t\t\t\tcomponent.batchGroupId = data.batchGroupId === undefined || data.batchGroupId === null ? -1 : data.batchGroupId;\n\t\t\t\tif (data.layers && Array.isArray(data.layers)) {\n\t\t\t\t\t\tcomponent.layers = data.layers.slice(0);\n\t\t\t\t}\n\t\t\t\tif (data.type !== undefined) {\n\t\t\t\t\t\tcomponent.type = data.type;\n\t\t\t\t}\n\t\t\t\tif (component.type === ELEMENTTYPE_IMAGE) {\n\t\t\t\t\t\tif (data.rect !== undefined) {\n\t\t\t\t\t\t\t\tcomponent.rect = data.rect;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.color !== undefined) {\n\t\t\t\t\t\t\t\tcolor = data.color;\n\t\t\t\t\t\t\t\tif (!(color instanceof Color)) {\n\t\t\t\t\t\t\t\t\t\tcolor = new Color(data.color[0], data.color[1], data.color[2]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcomponent.color = color;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.opacity !== undefined) component.opacity = data.opacity;\n\t\t\t\t\t\tif (data.textureAsset !== undefined) component.textureAsset = data.textureAsset;\n\t\t\t\t\t\tif (data.texture) component.texture = data.texture;\n\t\t\t\t\t\tif (data.spriteAsset !== undefined) component.spriteAsset = data.spriteAsset;\n\t\t\t\t\t\tif (data.sprite) component.sprite = data.sprite;\n\t\t\t\t\t\tif (data.spriteFrame !== undefined) component.spriteFrame = data.spriteFrame;\n\t\t\t\t\t\tif (data.pixelsPerUnit !== undefined && data.pixelsPerUnit !== null) component.pixelsPerUnit = data.pixelsPerUnit;\n\t\t\t\t\t\tif (data.materialAsset !== undefined) component.materialAsset = data.materialAsset;\n\t\t\t\t\t\tif (data.material) component.material = data.material;\n\t\t\t\t\t\tif (data.mask !== undefined) {\n\t\t\t\t\t\t\t\tcomponent.mask = data.mask;\n\t\t\t\t\t\t}\n\t\t\t\t} else if (component.type === ELEMENTTYPE_TEXT) {\n\t\t\t\t\t\tif (data.autoWidth !== undefined) component.autoWidth = data.autoWidth;\n\t\t\t\t\t\tif (data.autoHeight !== undefined) component.autoHeight = data.autoHeight;\n\t\t\t\t\t\tif (data.rtlReorder !== undefined) component.rtlReorder = data.rtlReorder;\n\t\t\t\t\t\tif (data.unicodeConverter !== undefined) component.unicodeConverter = data.unicodeConverter;\n\t\t\t\t\t\tif (data.text !== null && data.text !== undefined) {\n\t\t\t\t\t\t\t\tcomponent.text = data.text;\n\t\t\t\t\t\t} else if (data.key !== null && data.key !== undefined) {\n\t\t\t\t\t\t\t\tcomponent.key = data.key;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.color !== undefined) {\n\t\t\t\t\t\t\t\tcolor = data.color;\n\t\t\t\t\t\t\t\tif (!(color instanceof Color)) {\n\t\t\t\t\t\t\t\t\t\tcolor = new Color(color[0], color[1], color[2]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcomponent.color = color;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.opacity !== undefined) {\n\t\t\t\t\t\t\t\tcomponent.opacity = data.opacity;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.spacing !== undefined) component.spacing = data.spacing;\n\t\t\t\t\t\tif (data.fontSize !== undefined) {\n\t\t\t\t\t\t\t\tcomponent.fontSize = data.fontSize;\n\t\t\t\t\t\t\t\tif (!data.lineHeight) component.lineHeight = data.fontSize;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.lineHeight !== undefined) component.lineHeight = data.lineHeight;\n\t\t\t\t\t\tif (data.maxLines !== undefined) component.maxLines = data.maxLines;\n\t\t\t\t\t\tif (data.wrapLines !== undefined) component.wrapLines = data.wrapLines;\n\t\t\t\t\t\tif (data.minFontSize !== undefined) component.minFontSize = data.minFontSize;\n\t\t\t\t\t\tif (data.maxFontSize !== undefined) component.maxFontSize = data.maxFontSize;\n\t\t\t\t\t\tif (data.autoFitWidth) component.autoFitWidth = data.autoFitWidth;\n\t\t\t\t\t\tif (data.autoFitHeight) component.autoFitHeight = data.autoFitHeight;\n\t\t\t\t\t\tif (data.fontAsset !== undefined) component.fontAsset = data.fontAsset;\n\t\t\t\t\t\tif (data.font !== undefined) component.font = data.font;\n\t\t\t\t\t\tif (data.alignment !== undefined) component.alignment = data.alignment;\n\t\t\t\t\t\tif (data.outlineColor !== undefined) component.outlineColor = data.outlineColor;\n\t\t\t\t\t\tif (data.outlineThickness !== undefined) component.outlineThickness = data.outlineThickness;\n\t\t\t\t\t\tif (data.shadowColor !== undefined) component.shadowColor = data.shadowColor;\n\t\t\t\t\t\tif (data.shadowOffset !== undefined) component.shadowOffset = data.shadowOffset;\n\t\t\t\t\t\tif (data.enableMarkup !== undefined) component.enableMarkup = data.enableMarkup;\n\t\t\t\t}\n\t\t\t\tconst result = component._parseUpToScreen();\n\t\t\t\tif (result.screen) {\n\t\t\t\t\t\tcomponent._updateScreen(result.screen);\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t\t\tcomponent._beingInitialized = false;\n\t\t\t\tif (component.type === ELEMENTTYPE_IMAGE && component._image._meshDirty) {\n\t\t\t\t\t\tcomponent._image._updateMesh(component._image.mesh);\n\t\t\t\t}\n\t\t}\n\t\tonAddComponent(entity, component) {\n\t\t\t\tentity.fire('element:add');\n\t\t}\n\t\tonRemoveComponent(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst source = entity.element;\n\t\t\t\tconst data = {\n\t\t\t\t\t\tenabled: source.enabled,\n\t\t\t\t\t\twidth: source.width,\n\t\t\t\t\t\theight: source.height,\n\t\t\t\t\t\tanchor: source.anchor.clone(),\n\t\t\t\t\t\tpivot: source.pivot.clone(),\n\t\t\t\t\t\tmargin: source.margin.clone(),\n\t\t\t\t\t\talignment: source.alignment && source.alignment.clone() || source.alignment,\n\t\t\t\t\t\tautoWidth: source.autoWidth,\n\t\t\t\t\t\tautoHeight: source.autoHeight,\n\t\t\t\t\t\ttype: source.type,\n\t\t\t\t\t\trect: source.rect && source.rect.clone() || source.rect,\n\t\t\t\t\t\trtlReorder: source.rtlReorder,\n\t\t\t\t\t\tunicodeConverter: source.unicodeConverter,\n\t\t\t\t\t\tmaterialAsset: source.materialAsset,\n\t\t\t\t\t\tmaterial: source.material,\n\t\t\t\t\t\tcolor: source.color && source.color.clone() || source.color,\n\t\t\t\t\t\topacity: source.opacity,\n\t\t\t\t\t\ttextureAsset: source.textureAsset,\n\t\t\t\t\t\ttexture: source.texture,\n\t\t\t\t\t\tspriteAsset: source.spriteAsset,\n\t\t\t\t\t\tsprite: source.sprite,\n\t\t\t\t\t\tspriteFrame: source.spriteFrame,\n\t\t\t\t\t\tpixelsPerUnit: source.pixelsPerUnit,\n\t\t\t\t\t\tspacing: source.spacing,\n\t\t\t\t\t\tlineHeight: source.lineHeight,\n\t\t\t\t\t\twrapLines: source.wrapLines,\n\t\t\t\t\t\tlayers: source.layers,\n\t\t\t\t\t\tfontSize: source.fontSize,\n\t\t\t\t\t\tminFontSize: source.minFontSize,\n\t\t\t\t\t\tmaxFontSize: source.maxFontSize,\n\t\t\t\t\t\tautoFitWidth: source.autoFitWidth,\n\t\t\t\t\t\tautoFitHeight: source.autoFitHeight,\n\t\t\t\t\t\tmaxLines: source.maxLines,\n\t\t\t\t\t\tfontAsset: source.fontAsset,\n\t\t\t\t\t\tfont: source.font,\n\t\t\t\t\t\tuseInput: source.useInput,\n\t\t\t\t\t\tfitMode: source.fitMode,\n\t\t\t\t\t\tbatchGroupId: source.batchGroupId,\n\t\t\t\t\t\tmask: source.mask,\n\t\t\t\t\t\toutlineColor: source.outlineColor && source.outlineColor.clone() || source.outlineColor,\n\t\t\t\t\t\toutlineThickness: source.outlineThickness,\n\t\t\t\t\t\tshadowColor: source.shadowColor && source.shadowColor.clone() || source.shadowColor,\n\t\t\t\t\t\tshadowOffset: source.shadowOffset && source.shadowOffset.clone() || source.shadowOffset,\n\t\t\t\t\t\tenableMarkup: source.enableMarkup\n\t\t\t\t};\n\t\t\t\tif (source.key !== undefined && source.key !== null) {\n\t\t\t\t\t\tdata.key = source.key;\n\t\t\t\t} else {\n\t\t\t\t\t\tdata.text = source.text;\n\t\t\t\t}\n\t\t\t\treturn this.addComponent(clone, data);\n\t\t}\n\t\tgetTextElementMaterial(screenSpace, msdf, textAttibutes) {\n\t\t\t\tconst hash = (screenSpace && 1 << 0) | (msdf && 1 << 1) | (textAttibutes && 1 << 2);\n\t\t\t\tlet material = this._defaultTextMaterials[hash];\n\t\t\t\tif (material) {\n\t\t\t\t\t\treturn material;\n\t\t\t\t}\n\t\t\t\tlet name = 'TextMaterial';\n\t\t\t\tmaterial = new StandardMaterial();\n\t\t\t\tif (msdf) {\n\t\t\t\t\t\tmaterial.msdfMap = this._defaultTexture;\n\t\t\t\t\t\tmaterial.msdfTextAttribute = textAttibutes;\n\t\t\t\t\t\tmaterial.emissive.set(1, 1, 1);\n\t\t\t\t} else {\n\t\t\t\t\t\tname = `Bitmap${name}`;\n\t\t\t\t\t\tmaterial.emissive.set(1, 1, 1);\n\t\t\t\t\t\tmaterial.emissiveMap = this._defaultTexture;\n\t\t\t\t\t\tmaterial.opacityMap = this._defaultTexture;\n\t\t\t\t\t\tmaterial.opacityMapChannel = 'a';\n\t\t\t\t}\n\t\t\t\tif (screenSpace) {\n\t\t\t\t\t\tname = `ScreenSpace${name}`;\n\t\t\t\t\t\tmaterial.depthTest = false;\n\t\t\t\t}\n\t\t\t\tmaterial.name = `default${name}`;\n\t\t\t\tmaterial.useLighting = false;\n\t\t\t\tmaterial.useTonemap = false;\n\t\t\t\tmaterial.useFog = false;\n\t\t\t\tmaterial.useSkybox = false;\n\t\t\t\tmaterial.diffuse.set(0, 0, 0);\n\t\t\t\tmaterial.opacity = 0.5;\n\t\t\t\tmaterial.blendType = BLEND_PREMULTIPLIED;\n\t\t\t\tmaterial.depthWrite = false;\n\t\t\t\tmaterial.emissiveVertexColor = true;\n\t\t\t\tmaterial.update();\n\t\t\t\tthis._defaultTextMaterials[hash] = material;\n\t\t\t\treturn material;\n\t\t}\n\t\t_createBaseImageMaterial() {\n\t\t\t\tconst material = new StandardMaterial();\n\t\t\t\tmaterial.diffuse.set(0, 0, 0);\n\t\t\t\tmaterial.emissive.set(1, 1, 1);\n\t\t\t\tmaterial.emissiveMap = this._defaultTexture;\n\t\t\t\tmaterial.opacityMap = this._defaultTexture;\n\t\t\t\tmaterial.opacityMapChannel = 'a';\n\t\t\t\tmaterial.useLighting = false;\n\t\t\t\tmaterial.useTonemap = false;\n\t\t\t\tmaterial.useFog = false;\n\t\t\t\tmaterial.useSkybox = false;\n\t\t\t\tmaterial.blendType = BLEND_PREMULTIPLIED;\n\t\t\t\tmaterial.depthWrite = false;\n\t\t\t\treturn material;\n\t\t}\n\t\tgetImageElementMaterial(screenSpace, mask, nineSliced, nineSliceTiled) {\n\t\t\t\tif (screenSpace) {\n\t\t\t\t\t\tif (mask) {\n\t\t\t\t\t\t\t\tif (nineSliced) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultScreenSpaceImageMask9SlicedMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.name = 'defaultScreenSpaceImageMask9SlicedMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.depthTest = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.alphaTest = 1;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.redWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.greenWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.blueWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.alphaWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9SlicedMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultScreenSpaceImageMask9SlicedMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultScreenSpaceImageMask9SlicedMaterial;\n\t\t\t\t\t\t\t\t} else if (nineSliceTiled) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultScreenSpaceImageMask9TiledMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial = this.defaultScreenSpaceImage9TiledMaterial.clone();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.name = 'defaultScreenSpaceImageMask9TiledMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.depthTest = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.alphaTest = 1;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.redWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.greenWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.blueWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.alphaWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMask9TiledMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultScreenSpaceImageMask9TiledMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultScreenSpaceImageMask9TiledMaterial;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultScreenSpaceImageMaskMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.name = 'defaultScreenSpaceImageMaskMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.depthTest = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.alphaTest = 1;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.redWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.greenWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.blueWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.alphaWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaskMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultScreenSpaceImageMaskMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultScreenSpaceImageMaskMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (nineSliced) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultScreenSpaceImage9SlicedMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9SlicedMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9SlicedMaterial.name = 'defaultScreenSpaceImage9SlicedMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9SlicedMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9SlicedMaterial.depthTest = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9SlicedMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultScreenSpaceImage9SlicedMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultScreenSpaceImage9SlicedMaterial;\n\t\t\t\t\t\t\t\t} else if (nineSliceTiled) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultScreenSpaceImage9TiledMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9TiledMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9TiledMaterial.name = 'defaultScreenSpaceImage9TiledMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9TiledMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9TiledMaterial.depthTest = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImage9TiledMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultScreenSpaceImage9TiledMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultScreenSpaceImage9TiledMaterial;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultScreenSpaceImageMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaterial.name = 'defaultScreenSpaceImageMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaterial.depthTest = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultScreenSpaceImageMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultScreenSpaceImageMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultScreenSpaceImageMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (mask) {\n\t\t\t\t\t\t\t\tif (nineSliced) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultImage9SlicedMaskMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.name = 'defaultImage9SlicedMaskMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.alphaTest = 1;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.redWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.greenWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.blueWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.alphaWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaskMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultImage9SlicedMaskMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultImage9SlicedMaskMaterial;\n\t\t\t\t\t\t\t\t} else if (nineSliceTiled) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultImage9TiledMaskMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.name = 'defaultImage9TiledMaskMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.alphaTest = 1;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.redWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.greenWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.blueWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.alphaWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaskMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultImage9TiledMaskMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultImage9TiledMaskMaterial;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultImageMaskMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial.name = 'defaultImageMaskMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial.alphaTest = 1;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial.redWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial.greenWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial.blueWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial.alphaWrite = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaskMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultImageMaskMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultImageMaskMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (nineSliced) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultImage9SlicedMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaterial.name = 'defaultImage9SlicedMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9SlicedMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultImage9SlicedMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultImage9SlicedMaterial;\n\t\t\t\t\t\t\t\t} else if (nineSliceTiled) {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultImage9TiledMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaterial.name = 'defaultImage9TiledMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImage9TiledMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultImage9TiledMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultImage9TiledMaterial;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!this.defaultImageMaterial) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterial = this._createBaseImageMaterial();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterial.name = 'defaultImageMaterial';\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterial.update();\n\t\t\t\t\t\t\t\t\t\t\t\tthis.defaultImageMaterials.push(this.defaultImageMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn this.defaultImageMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tregisterUnicodeConverter(func) {\n\t\t\t\tthis._unicodeConverter = func;\n\t\t}\n\t\tregisterRtlReorder(func) {\n\t\t\t\tthis._rtlReorder = func;\n\t\t}\n\t\tgetUnicodeConverter() {\n\t\t\t\treturn this._unicodeConverter;\n\t\t}\n\t\tgetRtlReorder() {\n\t\t\t\treturn this._rtlReorder;\n\t\t}\n}\n\nconst MOTION_FREE = 'free';\nconst MOTION_LIMITED = 'limited';\nconst MOTION_LOCKED = 'locked';\n\nconst properties$1 = [\n\t\t'angularDampingX',\n\t\t'angularDampingY',\n\t\t'angularDampingZ',\n\t\t'angularEquilibriumX',\n\t\t'angularEquilibriumY',\n\t\t'angularEquilibriumZ',\n\t\t'angularLimitsX',\n\t\t'angularLimitsY',\n\t\t'angularLimitsZ',\n\t\t'angularMotionX',\n\t\t'angularMotionY',\n\t\t'angularMotionZ',\n\t\t'angularSpringX',\n\t\t'angularSpringY',\n\t\t'angularSpringZ',\n\t\t'angularStiffnessX',\n\t\t'angularStiffnessY',\n\t\t'angularStiffnessZ',\n\t\t'breakForce',\n\t\t'enableCollision',\n\t\t'enabled',\n\t\t'entityA',\n\t\t'entityB',\n\t\t'linearDampingX',\n\t\t'linearDampingY',\n\t\t'linearDampingZ',\n\t\t'linearEquilibriumX',\n\t\t'linearEquilibriumY',\n\t\t'linearEquilibriumZ',\n\t\t'linearLimitsX',\n\t\t'linearLimitsY',\n\t\t'linearLimitsZ',\n\t\t'linearMotionX',\n\t\t'linearMotionY',\n\t\t'linearMotionZ',\n\t\t'linearSpringX',\n\t\t'linearSpringY',\n\t\t'linearSpringZ',\n\t\t'linearStiffnessX',\n\t\t'linearStiffnessY',\n\t\t'linearStiffnessZ'\n];\nclass JointComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity);\n\t\t\t\tthis._constraint = null;\n\t\t\t\tthis._entityA = null;\n\t\t\t\tthis._entityB = null;\n\t\t\t\tthis._breakForce = 3.4e+38;\n\t\t\t\tthis._enableCollision = true;\n\t\t\t\tthis._linearMotionX = MOTION_LOCKED;\n\t\t\t\tthis._linearLimitsX = new Vec2(0, 0);\n\t\t\t\tthis._linearSpringX = false;\n\t\t\t\tthis._linearStiffnessX = 0;\n\t\t\t\tthis._linearDampingX = 1;\n\t\t\t\tthis._linearEquilibriumX = 0;\n\t\t\t\tthis._linearMotionY = MOTION_LOCKED;\n\t\t\t\tthis._linearLimitsY = new Vec2(0, 0);\n\t\t\t\tthis._linearSpringY = false;\n\t\t\t\tthis._linearStiffnessY = 0;\n\t\t\t\tthis._linearDampingY = 1;\n\t\t\t\tthis._linearEquilibriumY = 0;\n\t\t\t\tthis._linearMotionZ = MOTION_LOCKED;\n\t\t\t\tthis._linearLimitsZ = new Vec2(0, 0);\n\t\t\t\tthis._linearSpringZ = false;\n\t\t\t\tthis._linearStiffnessZ = 0;\n\t\t\t\tthis._linearDampingZ = 1;\n\t\t\t\tthis._linearEquilibriumZ = 0;\n\t\t\t\tthis._angularMotionX = MOTION_LOCKED;\n\t\t\t\tthis._angularLimitsX = new Vec2(0, 0);\n\t\t\t\tthis._angularSpringX = false;\n\t\t\t\tthis._angularStiffnessX = 0;\n\t\t\t\tthis._angularDampingX = 1;\n\t\t\t\tthis._angularEquilibriumX = 0;\n\t\t\t\tthis._angularMotionY = MOTION_LOCKED;\n\t\t\t\tthis._angularLimitsY = new Vec2(0, 0);\n\t\t\t\tthis._angularSpringY = false;\n\t\t\t\tthis._angularStiffnessY = 0;\n\t\t\t\tthis._angularDampingY = 1;\n\t\t\t\tthis._angularEquilibriumY = 0;\n\t\t\t\tthis._angularMotionZ = MOTION_LOCKED;\n\t\t\t\tthis._angularLimitsZ = new Vec2(0, 0);\n\t\t\t\tthis._angularSpringZ = false;\n\t\t\t\tthis._angularEquilibriumZ = 0;\n\t\t\t\tthis._angularDampingZ = 1;\n\t\t\t\tthis._angularStiffnessZ = 0;\n\t\t\t\tthis.on('set_enabled', this._onSetEnabled, this);\n\t\t}\n\t\tset entityA(body) {\n\t\t\t\tthis._destroyConstraint();\n\t\t\t\tthis._entityA = body;\n\t\t\t\tthis._createConstraint();\n\t\t}\n\t\tget entityA() {\n\t\t\t\treturn this._entityA;\n\t\t}\n\t\tset entityB(body) {\n\t\t\t\tthis._destroyConstraint();\n\t\t\t\tthis._entityB = body;\n\t\t\t\tthis._createConstraint();\n\t\t}\n\t\tget entityB() {\n\t\t\t\treturn this._entityB;\n\t\t}\n\t\tset breakForce(force) {\n\t\t\t\tif (this._constraint && this._breakForce !== force) {\n\t\t\t\t\t\tthis._constraint.setBreakingImpulseThreshold(force);\n\t\t\t\t\t\tthis._breakForce = force;\n\t\t\t\t}\n\t\t}\n\t\tget breakForce() {\n\t\t\t\treturn this._breakForce;\n\t\t}\n\t\tset enableCollision(enableCollision) {\n\t\t\t\tthis._destroyConstraint();\n\t\t\t\tthis._enableCollision = enableCollision;\n\t\t\t\tthis._createConstraint();\n\t\t}\n\t\tget enableCollision() {\n\t\t\t\treturn this._enableCollision;\n\t\t}\n\t\tset angularLimitsX(limits) {\n\t\t\t\tif (!this._angularLimitsX.equals(limits)) {\n\t\t\t\t\t\tthis._angularLimitsX.copy(limits);\n\t\t\t\t\t\tthis._updateAngularLimits();\n\t\t\t\t}\n\t\t}\n\t\tget angularLimitsX() {\n\t\t\t\treturn this._angularLimitsX;\n\t\t}\n\t\tset angularMotionX(value) {\n\t\t\t\tif (this._angularMotionX !== value) {\n\t\t\t\t\t\tthis._angularMotionX = value;\n\t\t\t\t\t\tthis._updateAngularLimits();\n\t\t\t\t}\n\t\t}\n\t\tget angularMotionX() {\n\t\t\t\treturn this._angularMotionX;\n\t\t}\n\t\tset angularLimitsY(limits) {\n\t\t\t\tif (!this._angularLimitsY.equals(limits)) {\n\t\t\t\t\t\tthis._angularLimitsY.copy(limits);\n\t\t\t\t\t\tthis._updateAngularLimits();\n\t\t\t\t}\n\t\t}\n\t\tget angularLimitsY() {\n\t\t\t\treturn this._angularLimitsY;\n\t\t}\n\t\tset angularMotionY(value) {\n\t\t\t\tif (this._angularMotionY !== value) {\n\t\t\t\t\t\tthis._angularMotionY = value;\n\t\t\t\t\t\tthis._updateAngularLimits();\n\t\t\t\t}\n\t\t}\n\t\tget angularMotionY() {\n\t\t\t\treturn this._angularMotionY;\n\t\t}\n\t\tset angularLimitsZ(limits) {\n\t\t\t\tif (!this._angularLimitsZ.equals(limits)) {\n\t\t\t\t\t\tthis._angularLimitsZ.copy(limits);\n\t\t\t\t\t\tthis._updateAngularLimits();\n\t\t\t\t}\n\t\t}\n\t\tget angularLimitsZ() {\n\t\t\t\treturn this._angularLimitsZ;\n\t\t}\n\t\tset angularMotionZ(value) {\n\t\t\t\tif (this._angularMotionZ !== value) {\n\t\t\t\t\t\tthis._angularMotionZ = value;\n\t\t\t\t\t\tthis._updateAngularLimits();\n\t\t\t\t}\n\t\t}\n\t\tget angularMotionZ() {\n\t\t\t\treturn this._angularMotionZ;\n\t\t}\n\t\tset linearLimitsX(limits) {\n\t\t\t\tif (!this._linearLimitsX.equals(limits)) {\n\t\t\t\t\t\tthis._linearLimitsX.copy(limits);\n\t\t\t\t\t\tthis._updateLinearLimits();\n\t\t\t\t}\n\t\t}\n\t\tget linearLimitsX() {\n\t\t\t\treturn this._linearLimitsX;\n\t\t}\n\t\tset linearMotionX(value) {\n\t\t\t\tif (this._linearMotionX !== value) {\n\t\t\t\t\t\tthis._linearMotionX = value;\n\t\t\t\t\t\tthis._updateLinearLimits();\n\t\t\t\t}\n\t\t}\n\t\tget linearMotionX() {\n\t\t\t\treturn this._linearMotionX;\n\t\t}\n\t\tset linearLimitsY(limits) {\n\t\t\t\tif (!this._linearLimitsY.equals(limits)) {\n\t\t\t\t\t\tthis._linearLimitsY.copy(limits);\n\t\t\t\t\t\tthis._updateLinearLimits();\n\t\t\t\t}\n\t\t}\n\t\tget linearLimitsY() {\n\t\t\t\treturn this._linearLimitsY;\n\t\t}\n\t\tset linearMotionY(value) {\n\t\t\t\tif (this._linearMotionY !== value) {\n\t\t\t\t\t\tthis._linearMotionY = value;\n\t\t\t\t\t\tthis._updateLinearLimits();\n\t\t\t\t}\n\t\t}\n\t\tget linearMotionY() {\n\t\t\t\treturn this._linearMotionY;\n\t\t}\n\t\tset linearLimitsZ(limits) {\n\t\t\t\tif (!this._linearLimitsZ.equals(limits)) {\n\t\t\t\t\t\tthis._linearLimitsZ.copy(limits);\n\t\t\t\t\t\tthis._updateLinearLimits();\n\t\t\t\t}\n\t\t}\n\t\tget linearLimitsZ() {\n\t\t\t\treturn this._linearLimitsZ;\n\t\t}\n\t\tset linearMotionZ(value) {\n\t\t\t\tif (this._linearMotionZ !== value) {\n\t\t\t\t\t\tthis._linearMotionZ = value;\n\t\t\t\t\t\tthis._updateLinearLimits();\n\t\t\t\t}\n\t\t}\n\t\tget linearMotionZ() {\n\t\t\t\treturn this._linearMotionZ;\n\t\t}\n\t\t_convertTransform(pcTransform, ammoTransform) {\n\t\t\t\tconst pos = pcTransform.getTranslation();\n\t\t\t\tconst rot = new Quat();\n\t\t\t\trot.setFromMat4(pcTransform);\n\t\t\t\tconst ammoVec = new Ammo.btVector3(pos.x, pos.y, pos.z);\n\t\t\t\tconst ammoQuat = new Ammo.btQuaternion(rot.x, rot.y, rot.z, rot.w);\n\t\t\t\tammoTransform.setOrigin(ammoVec);\n\t\t\t\tammoTransform.setRotation(ammoQuat);\n\t\t\t\tAmmo.destroy(ammoVec);\n\t\t\t\tAmmo.destroy(ammoQuat);\n\t\t}\n\t\t_updateAngularLimits() {\n\t\t\t\tconst constraint = this._constraint;\n\t\t\t\tif (constraint) {\n\t\t\t\t\t\tlet lx, ly, lz, ux, uy, uz;\n\t\t\t\t\t\tif (this._angularMotionX === MOTION_LIMITED) {\n\t\t\t\t\t\t\t\tlx = this._angularLimitsX.x * math.DEG_TO_RAD;\n\t\t\t\t\t\t\t\tux = this._angularLimitsX.y * math.DEG_TO_RAD;\n\t\t\t\t\t\t} else if (this._angularMotionX === MOTION_FREE) {\n\t\t\t\t\t\t\t\tlx = 1;\n\t\t\t\t\t\t\t\tux = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlx = ux = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._angularMotionY === MOTION_LIMITED) {\n\t\t\t\t\t\t\t\tly = this._angularLimitsY.x * math.DEG_TO_RAD;\n\t\t\t\t\t\t\t\tuy = this._angularLimitsY.y * math.DEG_TO_RAD;\n\t\t\t\t\t\t} else if (this._angularMotionY === MOTION_FREE) {\n\t\t\t\t\t\t\t\tly = 1;\n\t\t\t\t\t\t\t\tuy = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tly = uy = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._angularMotionZ === MOTION_LIMITED) {\n\t\t\t\t\t\t\t\tlz = this._angularLimitsZ.x * math.DEG_TO_RAD;\n\t\t\t\t\t\t\t\tuz = this._angularLimitsZ.y * math.DEG_TO_RAD;\n\t\t\t\t\t\t} else if (this._angularMotionZ === MOTION_FREE) {\n\t\t\t\t\t\t\t\tlz = 1;\n\t\t\t\t\t\t\t\tuz = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlz = uz = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst limits = new Ammo.btVector3(lx, ly, lz);\n\t\t\t\t\t\tconstraint.setAngularLowerLimit(limits);\n\t\t\t\t\t\tlimits.setValue(ux, uy, uz);\n\t\t\t\t\t\tconstraint.setAngularUpperLimit(limits);\n\t\t\t\t\t\tAmmo.destroy(limits);\n\t\t\t\t}\n\t\t}\n\t\t_updateLinearLimits() {\n\t\t\t\tconst constraint = this._constraint;\n\t\t\t\tif (constraint) {\n\t\t\t\t\t\tlet lx, ly, lz, ux, uy, uz;\n\t\t\t\t\t\tif (this._linearMotionX === MOTION_LIMITED) {\n\t\t\t\t\t\t\t\tlx = this._linearLimitsX.x;\n\t\t\t\t\t\t\t\tux = this._linearLimitsX.y;\n\t\t\t\t\t\t} else if (this._linearMotionX === MOTION_FREE) {\n\t\t\t\t\t\t\t\tlx = 1;\n\t\t\t\t\t\t\t\tux = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlx = ux = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._linearMotionY === MOTION_LIMITED) {\n\t\t\t\t\t\t\t\tly = this._linearLimitsY.x;\n\t\t\t\t\t\t\t\tuy = this._linearLimitsY.y;\n\t\t\t\t\t\t} else if (this._linearMotionY === MOTION_FREE) {\n\t\t\t\t\t\t\t\tly = 1;\n\t\t\t\t\t\t\t\tuy = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tly = uy = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._linearMotionZ === MOTION_LIMITED) {\n\t\t\t\t\t\t\t\tlz = this._linearLimitsZ.x;\n\t\t\t\t\t\t\t\tuz = this._linearLimitsZ.y;\n\t\t\t\t\t\t} else if (this._linearMotionZ === MOTION_FREE) {\n\t\t\t\t\t\t\t\tlz = 1;\n\t\t\t\t\t\t\t\tuz = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlz = uz = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst limits = new Ammo.btVector3(lx, ly, lz);\n\t\t\t\t\t\tconstraint.setLinearLowerLimit(limits);\n\t\t\t\t\t\tlimits.setValue(ux, uy, uz);\n\t\t\t\t\t\tconstraint.setLinearUpperLimit(limits);\n\t\t\t\t\t\tAmmo.destroy(limits);\n\t\t\t\t}\n\t\t}\n\t\t_createConstraint() {\n\t\t\t\tif (this._entityA && this._entityA.rigidbody) {\n\t\t\t\t\t\tthis._destroyConstraint();\n\t\t\t\t\t\tconst mat = new Mat4();\n\t\t\t\t\t\tconst bodyA = this._entityA.rigidbody.body;\n\t\t\t\t\t\tbodyA.activate();\n\t\t\t\t\t\tconst jointWtm = this.entity.getWorldTransform();\n\t\t\t\t\t\tconst entityAWtm = this._entityA.getWorldTransform();\n\t\t\t\t\t\tconst invEntityAWtm = entityAWtm.clone().invert();\n\t\t\t\t\t\tmat.mul2(invEntityAWtm, jointWtm);\n\t\t\t\t\t\tconst frameA = new Ammo.btTransform();\n\t\t\t\t\t\tthis._convertTransform(mat, frameA);\n\t\t\t\t\t\tif (this._entityB && this._entityB.rigidbody) {\n\t\t\t\t\t\t\t\tconst bodyB = this._entityB.rigidbody.body;\n\t\t\t\t\t\t\t\tbodyB.activate();\n\t\t\t\t\t\t\t\tconst entityBWtm = this._entityB.getWorldTransform();\n\t\t\t\t\t\t\t\tconst invEntityBWtm = entityBWtm.clone().invert();\n\t\t\t\t\t\t\t\tmat.mul2(invEntityBWtm, jointWtm);\n\t\t\t\t\t\t\t\tconst frameB = new Ammo.btTransform();\n\t\t\t\t\t\t\t\tthis._convertTransform(mat, frameB);\n\t\t\t\t\t\t\t\tthis._constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA, bodyB, frameA, frameB, !this._enableCollision);\n\t\t\t\t\t\t\t\tAmmo.destroy(frameB);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA, frameA, !this._enableCollision);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tAmmo.destroy(frameA);\n\t\t\t\t\t\tconst axis = [\n\t\t\t\t\t\t\t\t'X',\n\t\t\t\t\t\t\t\t'Y',\n\t\t\t\t\t\t\t\t'Z',\n\t\t\t\t\t\t\t\t'X',\n\t\t\t\t\t\t\t\t'Y',\n\t\t\t\t\t\t\t\t'Z'\n\t\t\t\t\t\t];\n\t\t\t\t\t\tfor(let i = 0; i < 6; i++){\n\t\t\t\t\t\t\t\tconst type = i < 3 ? '_linear' : '_angular';\n\t\t\t\t\t\t\t\tthis._constraint.enableSpring(i, this[`${type}Spring${axis[i]}`]);\n\t\t\t\t\t\t\t\tthis._constraint.setDamping(i, this[`${type}Damping${axis[i]}`]);\n\t\t\t\t\t\t\t\tthis._constraint.setEquilibriumPoint(i, this[`${type}Equilibrium${axis[i]}`]);\n\t\t\t\t\t\t\t\tthis._constraint.setStiffness(i, this[`${type}Stiffness${axis[i]}`]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._constraint.setBreakingImpulseThreshold(this._breakForce);\n\t\t\t\t\t\tthis._updateLinearLimits();\n\t\t\t\t\t\tthis._updateAngularLimits();\n\t\t\t\t\t\tconst app = this.system.app;\n\t\t\t\t\t\tconst dynamicsWorld = app.systems.rigidbody.dynamicsWorld;\n\t\t\t\t\t\tdynamicsWorld.addConstraint(this._constraint, !this._enableCollision);\n\t\t\t\t}\n\t\t}\n\t\t_destroyConstraint() {\n\t\t\t\tif (this._constraint) {\n\t\t\t\t\t\tconst app = this.system.app;\n\t\t\t\t\t\tconst dynamicsWorld = app.systems.rigidbody.dynamicsWorld;\n\t\t\t\t\t\tdynamicsWorld.removeConstraint(this._constraint);\n\t\t\t\t\t\tAmmo.destroy(this._constraint);\n\t\t\t\t\t\tthis._constraint = null;\n\t\t\t\t}\n\t\t}\n\t\tinitFromData(data) {\n\t\t\t\tfor (const prop of properties$1){\n\t\t\t\t\t\tif (data.hasOwnProperty(prop)) {\n\t\t\t\t\t\t\t\tif (data[prop] instanceof Vec2) {\n\t\t\t\t\t\t\t\t\t\tthis[`_${prop}`].copy(data[prop]);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis[`_${prop}`] = data[prop];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._createConstraint();\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._createConstraint();\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._destroyConstraint();\n\t\t}\n\t\t_onSetEnabled(prop, old, value) {}\n\t\t_onBeforeRemove() {\n\t\t\t\tthis.fire('remove');\n\t\t}\n}\nconst functionMap = {\n\t\tDamping: 'setDamping',\n\t\tEquilibrium: 'setEquilibriumPoint',\n\t\tSpring: 'enableSpring',\n\t\tStiffness: 'setStiffness'\n};\n[\n\t\t'linear',\n\t\t'angular'\n].forEach((type)=>{\n\t\t[\n\t\t\t\t'Damping',\n\t\t\t\t'Equilibrium',\n\t\t\t\t'Spring',\n\t\t\t\t'Stiffness'\n\t\t].forEach((name)=>{\n\t\t\t\t[\n\t\t\t\t\t\t'X',\n\t\t\t\t\t\t'Y',\n\t\t\t\t\t\t'Z'\n\t\t\t\t].forEach((axis)=>{\n\t\t\t\t\t\tconst prop = type + name + axis;\n\t\t\t\t\t\tconst propInternal = `_${prop}`;\n\t\t\t\t\t\tlet index = type === 'linear' ? 0 : 3;\n\t\t\t\t\t\tif (axis === 'Y') index += 1;\n\t\t\t\t\t\tif (axis === 'Z') index += 2;\n\t\t\t\t\t\tObject.defineProperty(JointComponent.prototype, prop, {\n\t\t\t\t\t\t\t\tget: function() {\n\t\t\t\t\t\t\t\t\t\treturn this[propInternal];\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tset: function(value) {\n\t\t\t\t\t\t\t\t\t\tif (this[propInternal] !== value) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis[propInternal] = value;\n\t\t\t\t\t\t\t\t\t\t\t\tthis._constraint[functionMap[name]](index, value);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t});\n});\n\nclass JointComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$e = [\n\t\t'enabled'\n];\nclass JointComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'joint';\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.ComponentType = JointComponent;\n\t\t\t\tthis.DataType = JointComponentData;\n\t\t\t\tthis.schema = _schema$e;\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tcomponent.initFromData(data);\n\t\t\t\tsuper.initializeComponentData(component, data, _schema$e);\n\t\t}\n}\nComponent._buildAccessors(JointComponent.prototype, _schema$e);\n\nclass LayoutChildComponent extends Component {\n\t\tset minWidth(value) {\n\t\t\t\tif (value !== this._minWidth) {\n\t\t\t\t\t\tthis._minWidth = value;\n\t\t\t\t\t\tthis.fire('resize');\n\t\t\t\t}\n\t\t}\n\t\tget minWidth() {\n\t\t\t\treturn this._minWidth;\n\t\t}\n\t\tset minHeight(value) {\n\t\t\t\tif (value !== this._minHeight) {\n\t\t\t\t\t\tthis._minHeight = value;\n\t\t\t\t\t\tthis.fire('resize');\n\t\t\t\t}\n\t\t}\n\t\tget minHeight() {\n\t\t\t\treturn this._minHeight;\n\t\t}\n\t\tset maxWidth(value) {\n\t\t\t\tif (value !== this._maxWidth) {\n\t\t\t\t\t\tthis._maxWidth = value;\n\t\t\t\t\t\tthis.fire('resize');\n\t\t\t\t}\n\t\t}\n\t\tget maxWidth() {\n\t\t\t\treturn this._maxWidth;\n\t\t}\n\t\tset maxHeight(value) {\n\t\t\t\tif (value !== this._maxHeight) {\n\t\t\t\t\t\tthis._maxHeight = value;\n\t\t\t\t\t\tthis.fire('resize');\n\t\t\t\t}\n\t\t}\n\t\tget maxHeight() {\n\t\t\t\treturn this._maxHeight;\n\t\t}\n\t\tset fitWidthProportion(value) {\n\t\t\t\tif (value !== this._fitWidthProportion) {\n\t\t\t\t\t\tthis._fitWidthProportion = value;\n\t\t\t\t\t\tthis.fire('resize');\n\t\t\t\t}\n\t\t}\n\t\tget fitWidthProportion() {\n\t\t\t\treturn this._fitWidthProportion;\n\t\t}\n\t\tset fitHeightProportion(value) {\n\t\t\t\tif (value !== this._fitHeightProportion) {\n\t\t\t\t\t\tthis._fitHeightProportion = value;\n\t\t\t\t\t\tthis.fire('resize');\n\t\t\t\t}\n\t\t}\n\t\tget fitHeightProportion() {\n\t\t\t\treturn this._fitHeightProportion;\n\t\t}\n\t\tset excludeFromLayout(value) {\n\t\t\t\tif (value !== this._excludeFromLayout) {\n\t\t\t\t\t\tthis._excludeFromLayout = value;\n\t\t\t\t\t\tthis.fire('resize');\n\t\t\t\t}\n\t\t}\n\t\tget excludeFromLayout() {\n\t\t\t\treturn this._excludeFromLayout;\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._minWidth = 0, this._minHeight = 0, this._maxWidth = null, this._maxHeight = null, this._fitWidthProportion = 0, this._fitHeightProportion = 0, this._excludeFromLayout = false;\n\t\t}\n}\n\nclass LayoutChildComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$d = [\n\t\t'enabled'\n];\nclass LayoutChildComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'layoutchild';\n\t\t\t\tthis.ComponentType = LayoutChildComponent;\n\t\t\t\tthis.DataType = LayoutChildComponentData;\n\t\t\t\tthis.schema = _schema$d;\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tif (data.enabled !== undefined) component.enabled = data.enabled;\n\t\t\t\tif (data.minWidth !== undefined) component.minWidth = data.minWidth;\n\t\t\t\tif (data.minHeight !== undefined) component.minHeight = data.minHeight;\n\t\t\t\tif (data.maxWidth !== undefined) component.maxWidth = data.maxWidth;\n\t\t\t\tif (data.maxHeight !== undefined) component.maxHeight = data.maxHeight;\n\t\t\t\tif (data.fitWidthProportion !== undefined) component.fitWidthProportion = data.fitWidthProportion;\n\t\t\t\tif (data.fitHeightProportion !== undefined) component.fitHeightProportion = data.fitHeightProportion;\n\t\t\t\tif (data.excludeFromLayout !== undefined) component.excludeFromLayout = data.excludeFromLayout;\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst layoutChild = entity.layoutchild;\n\t\t\t\treturn this.addComponent(clone, {\n\t\t\t\t\t\tenabled: layoutChild.enabled,\n\t\t\t\t\t\tminWidth: layoutChild.minWidth,\n\t\t\t\t\t\tminHeight: layoutChild.minHeight,\n\t\t\t\t\t\tmaxWidth: layoutChild.maxWidth,\n\t\t\t\t\t\tmaxHeight: layoutChild.maxHeight,\n\t\t\t\t\t\tfitWidthProportion: layoutChild.fitWidthProportion,\n\t\t\t\t\t\tfitHeightProportion: layoutChild.fitHeightProportion,\n\t\t\t\t\t\texcludeFromLayout: layoutChild.excludeFromLayout\n\t\t\t\t});\n\t\t}\n}\nComponent._buildAccessors(LayoutChildComponent.prototype, _schema$d);\n\nconst FITTING_NONE = 0;\nconst FITTING_STRETCH = 1;\nconst FITTING_SHRINK = 2;\nconst FITTING_BOTH = 3;\n\nconst AXIS_MAPPINGS = {};\nAXIS_MAPPINGS[ORIENTATION_HORIZONTAL] = {\n\t\taxis: 'x',\n\t\tsize: 'width',\n\t\tcalculatedSize: 'calculatedWidth',\n\t\tminSize: 'minWidth',\n\t\tmaxSize: 'maxWidth',\n\t\tfitting: 'widthFitting',\n\t\tfittingProportion: 'fitWidthProportion'\n};\nAXIS_MAPPINGS[ORIENTATION_VERTICAL] = {\n\t\taxis: 'y',\n\t\tsize: 'height',\n\t\tcalculatedSize: 'calculatedHeight',\n\t\tminSize: 'minHeight',\n\t\tmaxSize: 'maxHeight',\n\t\tfitting: 'heightFitting',\n\t\tfittingProportion: 'fitHeightProportion'\n};\nconst OPPOSITE_ORIENTATION = {};\nOPPOSITE_ORIENTATION[ORIENTATION_HORIZONTAL] = ORIENTATION_VERTICAL;\nOPPOSITE_ORIENTATION[ORIENTATION_VERTICAL] = ORIENTATION_HORIZONTAL;\nconst PROPERTY_DEFAULTS = {\n\t\tminWidth: 0,\n\t\tminHeight: 0,\n\t\tmaxWidth: Number.POSITIVE_INFINITY,\n\t\tmaxHeight: Number.POSITIVE_INFINITY,\n\t\twidth: null,\n\t\theight: null,\n\t\tfitWidthProportion: 0,\n\t\tfitHeightProportion: 0\n};\nconst FITTING_ACTION = {\n\t\tNONE: 'NONE',\n\t\tAPPLY_STRETCHING: 'APPLY_STRETCHING',\n\t\tAPPLY_SHRINKING: 'APPLY_SHRINKING'\n};\nconst availableSpace = new Vec2();\nfunction createCalculator(orientation) {\n\t\tlet options;\n\t\tconst a = AXIS_MAPPINGS[orientation];\n\t\tconst b = AXIS_MAPPINGS[OPPOSITE_ORIENTATION[orientation]];\n\t\tfunction minExtentA(element, size) {\n\t\t\t\treturn -size[a.size] * element.pivot[a.axis];\n\t\t}\n\t\tfunction minExtentB(element, size) {\n\t\t\t\treturn -size[b.size] * element.pivot[b.axis];\n\t\t}\n\t\tfunction maxExtentA(element, size) {\n\t\t\t\treturn size[a.size] * (1 - element.pivot[a.axis]);\n\t\t}\n\t\tfunction calculateAll(allElements, layoutOptions) {\n\t\t\t\tallElements = allElements.filter(shouldIncludeInLayout);\n\t\t\t\toptions = layoutOptions;\n\t\t\t\tavailableSpace.x = options.containerSize.x - options.padding.x - options.padding.z;\n\t\t\t\tavailableSpace.y = options.containerSize.y - options.padding.y - options.padding.w;\n\t\t\t\tresetAnchors(allElements);\n\t\t\t\tconst lines = reverseLinesIfRequired(splitLines(allElements));\n\t\t\t\tconst sizes = calculateSizesOnAxisB(lines, calculateSizesOnAxisA(lines));\n\t\t\t\tconst positions = calculateBasePositions(lines, sizes);\n\t\t\t\tapplyAlignmentAndPadding(lines, sizes, positions);\n\t\t\t\tapplySizesAndPositions(lines, sizes, positions);\n\t\t\t\treturn createLayoutInfo(lines);\n\t\t}\n\t\tfunction shouldIncludeInLayout(element) {\n\t\t\t\tconst layoutChildComponent = element.entity.layoutchild;\n\t\t\t\treturn !layoutChildComponent || !layoutChildComponent.enabled || !layoutChildComponent.excludeFromLayout;\n\t\t}\n\t\tfunction resetAnchors(allElements) {\n\t\t\t\tfor(let i = 0; i < allElements.length; ++i){\n\t\t\t\t\t\tconst element = allElements[i];\n\t\t\t\t\t\tconst anchor = element.anchor;\n\t\t\t\t\t\tif (anchor.x !== 0 || anchor.y !== 0 || anchor.z !== 0 || anchor.w !== 0) {\n\t\t\t\t\t\t\t\telement.anchor = Vec4.ZERO;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tfunction splitLines(allElements) {\n\t\t\t\tif (!options.wrap) {\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\tallElements\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tconst lines = [\n\t\t\t\t\t\t[]\n\t\t\t\t];\n\t\t\t\tconst sizes = getElementSizeProperties(allElements);\n\t\t\t\tlet runningSize = 0;\n\t\t\t\tconst allowOverrun = options[a.fitting] === FITTING_SHRINK;\n\t\t\t\tfor(let i = 0; i < allElements.length; ++i){\n\t\t\t\t\t\tif (lines[lines.length - 1].length > 0) {\n\t\t\t\t\t\t\t\trunningSize += options.spacing[a.axis];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst idealElementSize = sizes[i][a.size];\n\t\t\t\t\t\trunningSize += idealElementSize;\n\t\t\t\t\t\tif (!allowOverrun && runningSize > availableSpace[a.axis] && lines[lines.length - 1].length !== 0) {\n\t\t\t\t\t\t\t\trunningSize = idealElementSize;\n\t\t\t\t\t\t\t\tlines.push([]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlines[lines.length - 1].push(allElements[i]);\n\t\t\t\t\t\tif (allowOverrun && runningSize > availableSpace[a.axis] && i !== allElements.length - 1) {\n\t\t\t\t\t\t\t\trunningSize = 0;\n\t\t\t\t\t\t\t\tlines.push([]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn lines;\n\t\t}\n\t\tfunction reverseLinesIfRequired(lines) {\n\t\t\t\tconst reverseAxisA = options.orientation === ORIENTATION_HORIZONTAL && options.reverseX || options.orientation === ORIENTATION_VERTICAL && options.reverseY;\n\t\t\t\tconst reverseAxisB = options.orientation === ORIENTATION_HORIZONTAL && options.reverseY || options.orientation === ORIENTATION_VERTICAL && options.reverseX;\n\t\t\t\tif (reverseAxisA) {\n\t\t\t\t\t\tfor(let lineIndex = 0; lineIndex < lines.length; ++lineIndex){\n\t\t\t\t\t\t\t\tif (reverseAxisA) {\n\t\t\t\t\t\t\t\t\t\tlines[lineIndex].reverse();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (reverseAxisB) {\n\t\t\t\t\t\tlines.reverse();\n\t\t\t\t}\n\t\t\t\treturn lines;\n\t\t}\n\t\tfunction calculateSizesOnAxisA(lines) {\n\t\t\t\tconst sizesAllLines = [];\n\t\t\t\tfor(let lineIndex = 0; lineIndex < lines.length; ++lineIndex){\n\t\t\t\t\t\tconst line = lines[lineIndex];\n\t\t\t\t\t\tconst sizesThisLine = getElementSizeProperties(line);\n\t\t\t\t\t\tconst idealRequiredSpace = calculateTotalSpace(sizesThisLine, a);\n\t\t\t\t\t\tconst fittingAction = determineFittingAction(options[a.fitting], idealRequiredSpace, availableSpace[a.axis]);\n\t\t\t\t\t\tif (fittingAction === FITTING_ACTION.APPLY_STRETCHING) {\n\t\t\t\t\t\t\t\tstretchSizesToFitContainer(sizesThisLine, idealRequiredSpace, a);\n\t\t\t\t\t\t} else if (fittingAction === FITTING_ACTION.APPLY_SHRINKING) {\n\t\t\t\t\t\t\t\tshrinkSizesToFitContainer(sizesThisLine, idealRequiredSpace, a);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsizesAllLines.push(sizesThisLine);\n\t\t\t\t}\n\t\t\t\treturn sizesAllLines;\n\t\t}\n\t\tfunction calculateSizesOnAxisB(lines, sizesAllLines) {\n\t\t\t\tconst largestElementsForEachLine = [];\n\t\t\t\tconst largestSizesForEachLine = [];\n\t\t\t\tfor(let lineIndex = 0; lineIndex < lines.length; ++lineIndex){\n\t\t\t\t\t\tconst line = lines[lineIndex];\n\t\t\t\t\t\tline.largestElement = null;\n\t\t\t\t\t\tline.largestSize = {\n\t\t\t\t\t\t\t\twidth: Number.NEGATIVE_INFINITY,\n\t\t\t\t\t\t\t\theight: Number.NEGATIVE_INFINITY\n\t\t\t\t\t\t};\n\t\t\t\t\t\tfor(let elementIndex = 0; elementIndex < line.length; ++elementIndex){\n\t\t\t\t\t\t\t\tconst sizesThisElement = sizesAllLines[lineIndex][elementIndex];\n\t\t\t\t\t\t\t\tif (sizesThisElement[b.size] > line.largestSize[b.size]) {\n\t\t\t\t\t\t\t\t\t\tline.largestElement = line[elementIndex];\n\t\t\t\t\t\t\t\t\t\tline.largestSize = sizesThisElement;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlargestElementsForEachLine.push(line.largestElement);\n\t\t\t\t\t\tlargestSizesForEachLine.push(line.largestSize);\n\t\t\t\t}\n\t\t\t\tconst idealRequiredSpace = calculateTotalSpace(largestSizesForEachLine, b);\n\t\t\t\tconst fittingAction = determineFittingAction(options[b.fitting], idealRequiredSpace, availableSpace[b.axis]);\n\t\t\t\tif (fittingAction === FITTING_ACTION.APPLY_STRETCHING) {\n\t\t\t\t\t\tstretchSizesToFitContainer(largestSizesForEachLine, idealRequiredSpace, b);\n\t\t\t\t} else if (fittingAction === FITTING_ACTION.APPLY_SHRINKING) {\n\t\t\t\t\t\tshrinkSizesToFitContainer(largestSizesForEachLine, idealRequiredSpace, b);\n\t\t\t\t}\n\t\t\t\tfor(let lineIndex = 0; lineIndex < lines.length; ++lineIndex){\n\t\t\t\t\t\tconst line = lines[lineIndex];\n\t\t\t\t\t\tfor(let elementIndex = 0; elementIndex < line.length; ++elementIndex){\n\t\t\t\t\t\t\t\tconst sizesForThisElement = sizesAllLines[lineIndex][elementIndex];\n\t\t\t\t\t\t\t\tconst currentSize = sizesForThisElement[b.size];\n\t\t\t\t\t\t\t\tconst availableSize = lines.length === 1 ? availableSpace[b.axis] : line.largestSize[b.size];\n\t\t\t\t\t\t\t\tconst elementFittingAction = determineFittingAction(options[b.fitting], currentSize, availableSize);\n\t\t\t\t\t\t\t\tif (elementFittingAction === FITTING_ACTION.APPLY_STRETCHING) {\n\t\t\t\t\t\t\t\t\t\tsizesForThisElement[b.size] = Math.min(availableSize, sizesForThisElement[b.maxSize]);\n\t\t\t\t\t\t\t\t} else if (elementFittingAction === FITTING_ACTION.APPLY_SHRINKING) {\n\t\t\t\t\t\t\t\t\t\tsizesForThisElement[b.size] = Math.max(availableSize, sizesForThisElement[b.minSize]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn sizesAllLines;\n\t\t}\n\t\tfunction determineFittingAction(fittingMode, currentSize, availableSize) {\n\t\t\t\tswitch(fittingMode){\n\t\t\t\t\t\tcase FITTING_NONE:\n\t\t\t\t\t\t\t\treturn FITTING_ACTION.NONE;\n\t\t\t\t\t\tcase FITTING_STRETCH:\n\t\t\t\t\t\t\t\tif (currentSize < availableSize) {\n\t\t\t\t\t\t\t\t\t\treturn FITTING_ACTION.APPLY_STRETCHING;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn FITTING_ACTION.NONE;\n\t\t\t\t\t\tcase FITTING_SHRINK:\n\t\t\t\t\t\t\t\tif (currentSize >= availableSize) {\n\t\t\t\t\t\t\t\t\t\treturn FITTING_ACTION.APPLY_SHRINKING;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn FITTING_ACTION.NONE;\n\t\t\t\t\t\tcase FITTING_BOTH:\n\t\t\t\t\t\t\t\tif (currentSize < availableSize) {\n\t\t\t\t\t\t\t\t\t\treturn FITTING_ACTION.APPLY_STRETCHING;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn FITTING_ACTION.APPLY_SHRINKING;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tthrow new Error(`Unrecognized fitting mode: ${fittingMode}`);\n\t\t\t\t}\n\t\t}\n\t\tfunction calculateTotalSpace(sizes, axis) {\n\t\t\t\tconst totalSizes = sumValues(sizes, axis.size);\n\t\t\t\tconst totalSpacing = (sizes.length - 1) * options.spacing[axis.axis];\n\t\t\t\treturn totalSizes + totalSpacing;\n\t\t}\n\t\tfunction stretchSizesToFitContainer(sizesThisLine, idealRequiredSpace, axis) {\n\t\t\t\tconst ascendingMaxSizeOrder = getTraversalOrder(sizesThisLine, axis.maxSize);\n\t\t\t\tconst fittingProportions = getNormalizedValues(sizesThisLine, axis.fittingProportion);\n\t\t\t\tconst fittingProportionSums = createSumArray(fittingProportions, ascendingMaxSizeOrder);\n\t\t\t\tlet remainingUndershoot = availableSpace[axis.axis] - idealRequiredSpace;\n\t\t\t\tfor(let i = 0; i < sizesThisLine.length; ++i){\n\t\t\t\t\t\tconst index = ascendingMaxSizeOrder[i];\n\t\t\t\t\t\tconst targetIncrease = calculateAdjustment(index, remainingUndershoot, fittingProportions, fittingProportionSums);\n\t\t\t\t\t\tconst targetSize = sizesThisLine[index][axis.size] + targetIncrease;\n\t\t\t\t\t\tconst maxSize = sizesThisLine[index][axis.maxSize];\n\t\t\t\t\t\tconst actualSize = Math.min(targetSize, maxSize);\n\t\t\t\t\t\tsizesThisLine[index][axis.size] = actualSize;\n\t\t\t\t\t\tconst actualIncrease = Math.max(targetSize - actualSize, 0);\n\t\t\t\t\t\tconst appliedIncrease = targetIncrease - actualIncrease;\n\t\t\t\t\t\tremainingUndershoot -= appliedIncrease;\n\t\t\t\t}\n\t\t}\n\t\tfunction shrinkSizesToFitContainer(sizesThisLine, idealRequiredSpace, axis) {\n\t\t\t\tconst descendingMinSizeOrder = getTraversalOrder(sizesThisLine, axis.minSize, true);\n\t\t\t\tconst fittingProportions = getNormalizedValues(sizesThisLine, axis.fittingProportion);\n\t\t\t\tconst inverseFittingProportions = invertNormalizedValues(fittingProportions);\n\t\t\t\tconst inverseFittingProportionSums = createSumArray(inverseFittingProportions, descendingMinSizeOrder);\n\t\t\t\tlet remainingOvershoot = idealRequiredSpace - availableSpace[axis.axis];\n\t\t\t\tfor(let i = 0; i < sizesThisLine.length; ++i){\n\t\t\t\t\t\tconst index = descendingMinSizeOrder[i];\n\t\t\t\t\t\tconst targetReduction = calculateAdjustment(index, remainingOvershoot, inverseFittingProportions, inverseFittingProportionSums);\n\t\t\t\t\t\tconst targetSize = sizesThisLine[index][axis.size] - targetReduction;\n\t\t\t\t\t\tconst minSize = sizesThisLine[index][axis.minSize];\n\t\t\t\t\t\tconst actualSize = Math.max(targetSize, minSize);\n\t\t\t\t\t\tsizesThisLine[index][axis.size] = actualSize;\n\t\t\t\t\t\tconst actualReduction = Math.max(actualSize - targetSize, 0);\n\t\t\t\t\t\tconst appliedReduction = targetReduction - actualReduction;\n\t\t\t\t\t\tremainingOvershoot -= appliedReduction;\n\t\t\t\t}\n\t\t}\n\t\tfunction calculateAdjustment(index, remainingAdjustment, fittingProportions, fittingProportionSums) {\n\t\t\t\tconst proportion = fittingProportions[index];\n\t\t\t\tconst sumOfRemainingProportions = fittingProportionSums[index];\n\t\t\t\tif (Math.abs(proportion) < 1e-5 && Math.abs(sumOfRemainingProportions) < 1e-5) {\n\t\t\t\t\t\treturn remainingAdjustment;\n\t\t\t\t}\n\t\t\t\treturn remainingAdjustment * proportion / sumOfRemainingProportions;\n\t\t}\n\t\tfunction calculateBasePositions(lines, sizes) {\n\t\t\t\tconst cursor = {};\n\t\t\t\tcursor[a.axis] = 0;\n\t\t\t\tcursor[b.axis] = 0;\n\t\t\t\tlines[a.size] = Number.NEGATIVE_INFINITY;\n\t\t\t\tconst positionsAllLines = [];\n\t\t\t\tfor(let lineIndex = 0; lineIndex < lines.length; ++lineIndex){\n\t\t\t\t\t\tconst line = lines[lineIndex];\n\t\t\t\t\t\tif (line.length === 0) {\n\t\t\t\t\t\t\t\tpositionsAllLines.push([]);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst positionsThisLine = [];\n\t\t\t\t\t\tconst sizesThisLine = sizes[lineIndex];\n\t\t\t\t\t\tfor(let elementIndex = 0; elementIndex < line.length; ++elementIndex){\n\t\t\t\t\t\t\t\tconst element = line[elementIndex];\n\t\t\t\t\t\t\t\tconst sizesThisElement = sizesThisLine[elementIndex];\n\t\t\t\t\t\t\t\tcursor[b.axis] -= minExtentB(element, sizesThisElement);\n\t\t\t\t\t\t\t\tcursor[a.axis] -= minExtentA(element, sizesThisElement);\n\t\t\t\t\t\t\t\tpositionsThisLine[elementIndex] = {};\n\t\t\t\t\t\t\t\tpositionsThisLine[elementIndex][a.axis] = cursor[a.axis];\n\t\t\t\t\t\t\t\tpositionsThisLine[elementIndex][b.axis] = cursor[b.axis];\n\t\t\t\t\t\t\t\tcursor[b.axis] += minExtentB(element, sizesThisElement);\n\t\t\t\t\t\t\t\tcursor[a.axis] += maxExtentA(element, sizesThisElement) + options.spacing[a.axis];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tline[a.size] = cursor[a.axis] - options.spacing[a.axis];\n\t\t\t\t\t\tline[b.size] = line.largestSize[b.size];\n\t\t\t\t\t\tlines[a.size] = Math.max(lines[a.size], line[a.size]);\n\t\t\t\t\t\tcursor[a.axis] = 0;\n\t\t\t\t\t\tcursor[b.axis] += line[b.size] + options.spacing[b.axis];\n\t\t\t\t\t\tpositionsAllLines.push(positionsThisLine);\n\t\t\t\t}\n\t\t\t\tlines[b.size] = cursor[b.axis] - options.spacing[b.axis];\n\t\t\t\treturn positionsAllLines;\n\t\t}\n\t\tfunction applyAlignmentAndPadding(lines, sizes, positions) {\n\t\t\t\tconst alignmentA = options.alignment[a.axis];\n\t\t\t\tconst alignmentB = options.alignment[b.axis];\n\t\t\t\tconst paddingA = options.padding[a.axis];\n\t\t\t\tconst paddingB = options.padding[b.axis];\n\t\t\t\tfor(let lineIndex = 0; lineIndex < lines.length; ++lineIndex){\n\t\t\t\t\t\tconst line = lines[lineIndex];\n\t\t\t\t\t\tconst sizesThisLine = sizes[lineIndex];\n\t\t\t\t\t\tconst positionsThisLine = positions[lineIndex];\n\t\t\t\t\t\tconst axisAOffset = (availableSpace[a.axis] - line[a.size]) * alignmentA + paddingA;\n\t\t\t\t\t\tconst axisBOffset = (availableSpace[b.axis] - lines[b.size]) * alignmentB + paddingB;\n\t\t\t\t\t\tfor(let elementIndex = 0; elementIndex < line.length; ++elementIndex){\n\t\t\t\t\t\t\t\tconst withinLineAxisBOffset = (line[b.size] - sizesThisLine[elementIndex][b.size]) * options.alignment[b.axis];\n\t\t\t\t\t\t\t\tpositionsThisLine[elementIndex][a.axis] += axisAOffset;\n\t\t\t\t\t\t\t\tpositionsThisLine[elementIndex][b.axis] += axisBOffset + withinLineAxisBOffset;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tfunction applySizesAndPositions(lines, sizes, positions) {\n\t\t\t\tfor(let lineIndex = 0; lineIndex < lines.length; ++lineIndex){\n\t\t\t\t\t\tconst line = lines[lineIndex];\n\t\t\t\t\t\tconst sizesThisLine = sizes[lineIndex];\n\t\t\t\t\t\tconst positionsThisLine = positions[lineIndex];\n\t\t\t\t\t\tfor(let elementIndex = 0; elementIndex < line.length; ++elementIndex){\n\t\t\t\t\t\t\t\tconst element = line[elementIndex];\n\t\t\t\t\t\t\t\telement[a.calculatedSize] = sizesThisLine[elementIndex][a.size];\n\t\t\t\t\t\t\t\telement[b.calculatedSize] = sizesThisLine[elementIndex][b.size];\n\t\t\t\t\t\t\t\tif (options.orientation === ORIENTATION_HORIZONTAL) {\n\t\t\t\t\t\t\t\t\t\telement.entity.setLocalPosition(positionsThisLine[elementIndex][a.axis], positionsThisLine[elementIndex][b.axis], element.entity.getLocalPosition().z);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\telement.entity.setLocalPosition(positionsThisLine[elementIndex][b.axis], positionsThisLine[elementIndex][a.axis], element.entity.getLocalPosition().z);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tfunction createLayoutInfo(lines) {\n\t\t\t\tconst layoutWidth = lines.width;\n\t\t\t\tconst layoutHeight = lines.height;\n\t\t\t\tconst xOffset = (availableSpace.x - layoutWidth) * options.alignment.x + options.padding.x;\n\t\t\t\tconst yOffset = (availableSpace.y - layoutHeight) * options.alignment.y + options.padding.y;\n\t\t\t\treturn {\n\t\t\t\t\t\tbounds: new Vec4(xOffset, yOffset, layoutWidth, layoutHeight)\n\t\t\t\t};\n\t\t}\n\t\tfunction getElementSizeProperties(elements) {\n\t\t\t\tconst sizeProperties = [];\n\t\t\t\tfor(let i = 0; i < elements.length; ++i){\n\t\t\t\t\t\tconst element = elements[i];\n\t\t\t\t\t\tconst minWidth = Math.max(getProperty(element, 'minWidth'), 0);\n\t\t\t\t\t\tconst minHeight = Math.max(getProperty(element, 'minHeight'), 0);\n\t\t\t\t\t\tconst maxWidth = Math.max(getProperty(element, 'maxWidth'), minWidth);\n\t\t\t\t\t\tconst maxHeight = Math.max(getProperty(element, 'maxHeight'), minHeight);\n\t\t\t\t\t\tconst width = clamp(getProperty(element, 'width'), minWidth, maxWidth);\n\t\t\t\t\t\tconst height = clamp(getProperty(element, 'height'), minHeight, maxHeight);\n\t\t\t\t\t\tconst fitWidthProportion = getProperty(element, 'fitWidthProportion');\n\t\t\t\t\t\tconst fitHeightProportion = getProperty(element, 'fitHeightProportion');\n\t\t\t\t\t\tsizeProperties.push({\n\t\t\t\t\t\t\t\tminWidth: minWidth,\n\t\t\t\t\t\t\t\tminHeight: minHeight,\n\t\t\t\t\t\t\t\tmaxWidth: maxWidth,\n\t\t\t\t\t\t\t\tmaxHeight: maxHeight,\n\t\t\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\t\t\theight: height,\n\t\t\t\t\t\t\t\tfitWidthProportion: fitWidthProportion,\n\t\t\t\t\t\t\t\tfitHeightProportion: fitHeightProportion\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn sizeProperties;\n\t\t}\n\t\tfunction getProperty(element, propertyName) {\n\t\t\t\tconst layoutChildComponent = element.entity.layoutchild;\n\t\t\t\tif (layoutChildComponent && layoutChildComponent.enabled && layoutChildComponent[propertyName] !== undefined && layoutChildComponent[propertyName] !== null) {\n\t\t\t\t\t\treturn layoutChildComponent[propertyName];\n\t\t\t\t} else if (element[propertyName] !== undefined) {\n\t\t\t\t\t\treturn element[propertyName];\n\t\t\t\t}\n\t\t\t\treturn PROPERTY_DEFAULTS[propertyName];\n\t\t}\n\t\tfunction clamp(value, min, max) {\n\t\t\t\treturn Math.min(Math.max(value, min), max);\n\t\t}\n\t\tfunction sumValues(items, propertyName) {\n\t\t\t\treturn items.reduce((accumulator, current)=>{\n\t\t\t\t\t\treturn accumulator + current[propertyName];\n\t\t\t\t}, 0);\n\t\t}\n\t\tfunction getNormalizedValues(items, propertyName) {\n\t\t\t\tconst sum = sumValues(items, propertyName);\n\t\t\t\tconst normalizedValues = [];\n\t\t\t\tconst numItems = items.length;\n\t\t\t\tif (sum === 0) {\n\t\t\t\t\t\tfor(let i = 0; i < numItems; ++i){\n\t\t\t\t\t\t\t\tnormalizedValues.push(1 / numItems);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(let i = 0; i < numItems; ++i){\n\t\t\t\t\t\t\t\tnormalizedValues.push(items[i][propertyName] / sum);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn normalizedValues;\n\t\t}\n\t\tfunction invertNormalizedValues(values) {\n\t\t\t\tif (values.length === 1) {\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tconst invertedValues = [];\n\t\t\t\tconst numValues = values.length;\n\t\t\t\tfor(let i = 0; i < numValues; ++i){\n\t\t\t\t\t\tinvertedValues.push((1 - values[i]) / (numValues - 1));\n\t\t\t\t}\n\t\t\t\treturn invertedValues;\n\t\t}\n\t\tfunction getTraversalOrder(items, orderBy, descending) {\n\t\t\t\titems.forEach(assignIndex);\n\t\t\t\treturn items.slice().sort((itemA, itemB)=>{\n\t\t\t\t\t\treturn descending ? itemB[orderBy] - itemA[orderBy] : itemA[orderBy] - itemB[orderBy];\n\t\t\t\t}).map(getIndex);\n\t\t}\n\t\tfunction assignIndex(item, index) {\n\t\t\t\titem.index = index;\n\t\t}\n\t\tfunction getIndex(item) {\n\t\t\t\treturn item.index;\n\t\t}\n\t\tfunction createSumArray(values, order) {\n\t\t\t\tconst sumArray = [];\n\t\t\t\tsumArray[order[values.length - 1]] = values[order[values.length - 1]];\n\t\t\t\tfor(let i = values.length - 2; i >= 0; --i){\n\t\t\t\t\t\tsumArray[order[i]] = sumArray[order[i + 1]] + values[order[i]];\n\t\t\t\t}\n\t\t\t\treturn sumArray;\n\t\t}\n\t\treturn calculateAll;\n}\nconst CALCULATE_FNS = {};\nCALCULATE_FNS[ORIENTATION_HORIZONTAL] = createCalculator(ORIENTATION_HORIZONTAL);\nCALCULATE_FNS[ORIENTATION_VERTICAL] = createCalculator(ORIENTATION_VERTICAL);\nclass LayoutCalculator {\n\t\tcalculateLayout(elements, options) {\n\t\t\t\tconst calculateFn = CALCULATE_FNS[options.orientation];\n\t\t\t\tif (!calculateFn) {\n\t\t\t\t\t\tthrow new Error(`Unrecognized orientation value: ${options.orientation}`);\n\t\t\t\t} else {\n\t\t\t\t\t\treturn calculateFn(elements, options);\n\t\t\t\t}\n\t\t}\n}\n\nfunction getElement(entity) {\n\t\treturn entity.element;\n}\nfunction isEnabledAndHasEnabledElement(entity) {\n\t\treturn entity.enabled && entity.element && entity.element.enabled;\n}\nclass LayoutGroupComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._orientation = ORIENTATION_HORIZONTAL, this._reverseX = false, this._reverseY = true, this._alignment = new Vec2(0, 1), this._padding = new Vec4(), this._spacing = new Vec2(), this._widthFitting = FITTING_NONE, this._heightFitting = FITTING_NONE, this._wrap = false, this._layoutCalculator = new LayoutCalculator();\n\t\t\t\tthis._listenForReflowEvents(this.entity, 'on');\n\t\t\t\tthis.entity.children.forEach((child)=>{\n\t\t\t\t\t\tthis._listenForReflowEvents(child, 'on');\n\t\t\t\t});\n\t\t\t\tthis.entity.on('childinsert', this._onChildInsert, this);\n\t\t\t\tthis.entity.on('childremove', this._onChildRemove, this);\n\t\t\t\tsystem.app.systems.element.on('add', this._onElementOrLayoutComponentAdd, this);\n\t\t\t\tsystem.app.systems.element.on('beforeremove', this._onElementOrLayoutComponentRemove, this);\n\t\t\t\tsystem.app.systems.layoutchild.on('add', this._onElementOrLayoutComponentAdd, this);\n\t\t\t\tsystem.app.systems.layoutchild.on('beforeremove', this._onElementOrLayoutComponentRemove, this);\n\t\t}\n\t\tset orientation(value) {\n\t\t\t\tif (value !== this._orientation) {\n\t\t\t\t\t\tthis._orientation = value;\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget orientation() {\n\t\t\t\treturn this._orientation;\n\t\t}\n\t\tset reverseX(value) {\n\t\t\t\tif (value !== this._reverseX) {\n\t\t\t\t\t\tthis._reverseX = value;\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget reverseX() {\n\t\t\t\treturn this._reverseX;\n\t\t}\n\t\tset reverseY(value) {\n\t\t\t\tif (value !== this._reverseY) {\n\t\t\t\t\t\tthis._reverseY = value;\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget reverseY() {\n\t\t\t\treturn this._reverseY;\n\t\t}\n\t\tset alignment(value) {\n\t\t\t\tif (!value.equals(this._alignment)) {\n\t\t\t\t\t\tthis._alignment.copy(value);\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget alignment() {\n\t\t\t\treturn this._alignment;\n\t\t}\n\t\tset padding(value) {\n\t\t\t\tif (!value.equals(this._padding)) {\n\t\t\t\t\t\tthis._padding.copy(value);\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget padding() {\n\t\t\t\treturn this._padding;\n\t\t}\n\t\tset spacing(value) {\n\t\t\t\tif (!value.equals(this._spacing)) {\n\t\t\t\t\t\tthis._spacing.copy(value);\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget spacing() {\n\t\t\t\treturn this._spacing;\n\t\t}\n\t\tset widthFitting(value) {\n\t\t\t\tif (value !== this._widthFitting) {\n\t\t\t\t\t\tthis._widthFitting = value;\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget widthFitting() {\n\t\t\t\treturn this._widthFitting;\n\t\t}\n\t\tset heightFitting(value) {\n\t\t\t\tif (value !== this._heightFitting) {\n\t\t\t\t\t\tthis._heightFitting = value;\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget heightFitting() {\n\t\t\t\treturn this._heightFitting;\n\t\t}\n\t\tset wrap(value) {\n\t\t\t\tif (value !== this._wrap) {\n\t\t\t\t\t\tthis._wrap = value;\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\tget wrap() {\n\t\t\t\treturn this._wrap;\n\t\t}\n\t\t_isSelfOrChild(entity) {\n\t\t\t\treturn entity === this.entity || this.entity.children.indexOf(entity) !== -1;\n\t\t}\n\t\t_listenForReflowEvents(target, onOff) {\n\t\t\t\tif (target.element) {\n\t\t\t\t\t\ttarget.element[onOff]('enableelement', this._scheduleReflow, this);\n\t\t\t\t\t\ttarget.element[onOff]('disableelement', this._scheduleReflow, this);\n\t\t\t\t\t\ttarget.element[onOff]('resize', this._scheduleReflow, this);\n\t\t\t\t\t\ttarget.element[onOff]('set:pivot', this._scheduleReflow, this);\n\t\t\t\t}\n\t\t\t\tif (target.layoutchild) {\n\t\t\t\t\t\ttarget.layoutchild[onOff]('set_enabled', this._scheduleReflow, this);\n\t\t\t\t\t\ttarget.layoutchild[onOff]('resize', this._scheduleReflow, this);\n\t\t\t\t}\n\t\t}\n\t\t_onElementOrLayoutComponentAdd(entity) {\n\t\t\t\tif (this._isSelfOrChild(entity)) {\n\t\t\t\t\t\tthis._listenForReflowEvents(entity, 'on');\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\t_onElementOrLayoutComponentRemove(entity) {\n\t\t\t\tif (this._isSelfOrChild(entity)) {\n\t\t\t\t\t\tthis._listenForReflowEvents(entity, 'off');\n\t\t\t\t\t\tthis._scheduleReflow();\n\t\t\t\t}\n\t\t}\n\t\t_onChildInsert(child) {\n\t\t\t\tthis._listenForReflowEvents(child, 'on');\n\t\t\t\tthis._scheduleReflow();\n\t\t}\n\t\t_onChildRemove(child) {\n\t\t\t\tthis._listenForReflowEvents(child, 'off');\n\t\t\t\tthis._scheduleReflow();\n\t\t}\n\t\t_scheduleReflow() {\n\t\t\t\tif (this.enabled && this.entity && this.entity.enabled && !this._isPerformingReflow) {\n\t\t\t\t\t\tthis.system.scheduleReflow(this);\n\t\t\t\t}\n\t\t}\n\t\treflow() {\n\t\t\t\tconst container = getElement(this.entity);\n\t\t\t\tconst elements = this.entity.children.filter(isEnabledAndHasEnabledElement).map(getElement);\n\t\t\t\tif (!container || elements.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst containerWidth = Math.max(container.calculatedWidth, 0);\n\t\t\t\tconst containerHeight = Math.max(container.calculatedHeight, 0);\n\t\t\t\tconst options = {\n\t\t\t\t\t\torientation: this._orientation,\n\t\t\t\t\t\treverseX: this._reverseX,\n\t\t\t\t\t\treverseY: this._reverseY,\n\t\t\t\t\t\talignment: this._alignment,\n\t\t\t\t\t\tpadding: this._padding,\n\t\t\t\t\t\tspacing: this._spacing,\n\t\t\t\t\t\twidthFitting: this._widthFitting,\n\t\t\t\t\t\theightFitting: this._heightFitting,\n\t\t\t\t\t\twrap: this._wrap,\n\t\t\t\t\t\tcontainerSize: new Vec2(containerWidth, containerHeight)\n\t\t\t\t};\n\t\t\t\tthis._isPerformingReflow = true;\n\t\t\t\tconst layoutInfo = this._layoutCalculator.calculateLayout(elements, options);\n\t\t\t\tthis._isPerformingReflow = false;\n\t\t\t\tthis.fire('reflow', layoutInfo);\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._scheduleReflow();\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.entity.off('childinsert', this._onChildInsert, this);\n\t\t\t\tthis.entity.off('childremove', this._onChildRemove, this);\n\t\t\t\tthis._listenForReflowEvents(this.entity, 'off');\n\t\t\t\tthis.entity.children.forEach((child)=>{\n\t\t\t\t\t\tthis._listenForReflowEvents(child, 'off');\n\t\t\t\t});\n\t\t\t\tthis.system.app.systems.element.off('add', this._onElementOrLayoutComponentAdd, this);\n\t\t\t\tthis.system.app.systems.element.off('beforeremove', this._onElementOrLayoutComponentRemove, this);\n\t\t\t\tthis.system.app.systems.layoutchild.off('add', this._onElementOrLayoutComponentAdd, this);\n\t\t\t\tthis.system.app.systems.layoutchild.off('beforeremove', this._onElementOrLayoutComponentRemove, this);\n\t\t}\n}\n\nclass LayoutGroupComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$c = [\n\t\t'enabled'\n];\nconst MAX_ITERATIONS = 100;\nclass LayoutGroupComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'layoutgroup';\n\t\t\t\tthis.ComponentType = LayoutGroupComponent;\n\t\t\t\tthis.DataType = LayoutGroupComponentData;\n\t\t\t\tthis.schema = _schema$c;\n\t\t\t\tthis._reflowQueue = [];\n\t\t\t\tthis.on('beforeremove', this._onRemoveComponent, this);\n\t\t\t\tthis.app.systems.on('postUpdate', this._onPostUpdate, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tif (data.enabled !== undefined) component.enabled = data.enabled;\n\t\t\t\tif (data.orientation !== undefined) component.orientation = data.orientation;\n\t\t\t\tif (data.reverseX !== undefined) component.reverseX = data.reverseX;\n\t\t\t\tif (data.reverseY !== undefined) component.reverseY = data.reverseY;\n\t\t\t\tif (data.alignment !== undefined) {\n\t\t\t\t\t\tcomponent.alignment = Array.isArray(data.alignment) ? new Vec2(data.alignment) : data.alignment;\n\t\t\t\t}\n\t\t\t\tif (data.padding !== undefined) {\n\t\t\t\t\t\tcomponent.padding = Array.isArray(data.padding) ? new Vec4(data.padding) : data.padding;\n\t\t\t\t}\n\t\t\t\tif (data.spacing !== undefined) {\n\t\t\t\t\t\tcomponent.spacing = Array.isArray(data.spacing) ? new Vec2(data.spacing) : data.spacing;\n\t\t\t\t}\n\t\t\t\tif (data.widthFitting !== undefined) component.widthFitting = data.widthFitting;\n\t\t\t\tif (data.heightFitting !== undefined) component.heightFitting = data.heightFitting;\n\t\t\t\tif (data.wrap !== undefined) component.wrap = data.wrap;\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst layoutGroup = entity.layoutgroup;\n\t\t\t\treturn this.addComponent(clone, {\n\t\t\t\t\t\tenabled: layoutGroup.enabled,\n\t\t\t\t\t\torientation: layoutGroup.orientation,\n\t\t\t\t\t\treverseX: layoutGroup.reverseX,\n\t\t\t\t\t\treverseY: layoutGroup.reverseY,\n\t\t\t\t\t\talignment: layoutGroup.alignment,\n\t\t\t\t\t\tpadding: layoutGroup.padding,\n\t\t\t\t\t\tspacing: layoutGroup.spacing,\n\t\t\t\t\t\twidthFitting: layoutGroup.widthFitting,\n\t\t\t\t\t\theightFitting: layoutGroup.heightFitting,\n\t\t\t\t\t\twrap: layoutGroup.wrap\n\t\t\t\t});\n\t\t}\n\t\tscheduleReflow(component) {\n\t\t\t\tif (this._reflowQueue.indexOf(component) === -1) {\n\t\t\t\t\t\tthis._reflowQueue.push(component);\n\t\t\t\t}\n\t\t}\n\t\t_onPostUpdate() {\n\t\t\t\tthis._processReflowQueue();\n\t\t}\n\t\t_processReflowQueue() {\n\t\t\t\tif (this._reflowQueue.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet iterationCount = 0;\n\t\t\t\twhile(this._reflowQueue.length > 0){\n\t\t\t\t\t\tconst queue = this._reflowQueue.slice();\n\t\t\t\t\t\tthis._reflowQueue.length = 0;\n\t\t\t\t\t\tqueue.sort((componentA, componentB)=>{\n\t\t\t\t\t\t\t\treturn componentA.entity.graphDepth - componentB.entity.graphDepth;\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor(let i = 0; i < queue.length; ++i){\n\t\t\t\t\t\t\t\tqueue[i].reflow();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (++iterationCount >= MAX_ITERATIONS) {\n\t\t\t\t\t\t\t\tconsole.warn('Max reflow iterations limit reached, bailing.');\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onRemoveComponent(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('postUpdate', this._onPostUpdate, this);\n\t\t}\n}\nComponent._buildAccessors(LayoutGroupComponent.prototype, _schema$c);\n\nclass PrimitivesCache {\n\t\tdestroy(device) {\n\t\t\t\tthis.map.forEach((primData)=>primData.mesh.destroy());\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.map = new Map();\n\t\t}\n}\nconst _primitivesCache = new DeviceCache();\nconst getShapePrimitive = (device, type)=>{\n\t\tconst cache = _primitivesCache.get(device, ()=>{\n\t\t\t\treturn new PrimitivesCache();\n\t\t});\n\t\tlet primData = cache.map.get(type);\n\t\tif (!primData) {\n\t\t\t\tlet mesh, area;\n\t\t\t\tswitch(type){\n\t\t\t\t\t\tcase 'box':\n\t\t\t\t\t\t\t\tmesh = Mesh.fromGeometry(device, new BoxGeometry());\n\t\t\t\t\t\t\t\tarea = {\n\t\t\t\t\t\t\t\t\t\tx: 2,\n\t\t\t\t\t\t\t\t\t\ty: 2,\n\t\t\t\t\t\t\t\t\t\tz: 2,\n\t\t\t\t\t\t\t\t\t\tuv: 2.0 / 3\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'capsule':\n\t\t\t\t\t\t\t\tmesh = Mesh.fromGeometry(device, new CapsuleGeometry({\n\t\t\t\t\t\t\t\t\t\tradius: 0.5,\n\t\t\t\t\t\t\t\t\t\theight: 2\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\tarea = {\n\t\t\t\t\t\t\t\t\t\tx: Math.PI * 2,\n\t\t\t\t\t\t\t\t\t\ty: Math.PI,\n\t\t\t\t\t\t\t\t\t\tz: Math.PI * 2,\n\t\t\t\t\t\t\t\t\t\tuv: 1.0 / 3 + 1.0 / 3 / 3 * 2\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'cone':\n\t\t\t\t\t\t\t\tmesh = Mesh.fromGeometry(device, new ConeGeometry({\n\t\t\t\t\t\t\t\t\t\tbaseRadius: 0.5,\n\t\t\t\t\t\t\t\t\t\tpeakRadius: 0,\n\t\t\t\t\t\t\t\t\t\theight: 1\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\tarea = {\n\t\t\t\t\t\t\t\t\t\tx: 2.54,\n\t\t\t\t\t\t\t\t\t\ty: 2.54,\n\t\t\t\t\t\t\t\t\t\tz: 2.54,\n\t\t\t\t\t\t\t\t\t\tuv: 1.0 / 3 + 1.0 / 3 / 3\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'cylinder':\n\t\t\t\t\t\t\t\tmesh = Mesh.fromGeometry(device, new CylinderGeometry({\n\t\t\t\t\t\t\t\t\t\tradius: 0.5,\n\t\t\t\t\t\t\t\t\t\theight: 1\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\tarea = {\n\t\t\t\t\t\t\t\t\t\tx: Math.PI,\n\t\t\t\t\t\t\t\t\t\ty: 0.79 * 2,\n\t\t\t\t\t\t\t\t\t\tz: Math.PI,\n\t\t\t\t\t\t\t\t\t\tuv: 1.0 / 3 + 1.0 / 3 / 3 * 2\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'plane':\n\t\t\t\t\t\t\t\tmesh = Mesh.fromGeometry(device, new PlaneGeometry({\n\t\t\t\t\t\t\t\t\t\thalfExtents: new Vec2(0.5, 0.5),\n\t\t\t\t\t\t\t\t\t\twidthSegments: 1,\n\t\t\t\t\t\t\t\t\t\tlengthSegments: 1\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\tarea = {\n\t\t\t\t\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\t\t\t\t\ty: 1,\n\t\t\t\t\t\t\t\t\t\tz: 0,\n\t\t\t\t\t\t\t\t\t\tuv: 1\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'sphere':\n\t\t\t\t\t\t\t\tmesh = Mesh.fromGeometry(device, new SphereGeometry({\n\t\t\t\t\t\t\t\t\t\tradius: 0.5\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\tarea = {\n\t\t\t\t\t\t\t\t\t\tx: Math.PI,\n\t\t\t\t\t\t\t\t\t\ty: Math.PI,\n\t\t\t\t\t\t\t\t\t\tz: Math.PI,\n\t\t\t\t\t\t\t\t\t\tuv: 1\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'torus':\n\t\t\t\t\t\t\t\tmesh = Mesh.fromGeometry(device, new TorusGeometry({\n\t\t\t\t\t\t\t\t\t\ttubeRadius: 0.2,\n\t\t\t\t\t\t\t\t\t\tringRadius: 0.3\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\tarea = {\n\t\t\t\t\t\t\t\t\t\tx: Math.PI * 0.5 * 0.5 - Math.PI * 0.1 * 0.1,\n\t\t\t\t\t\t\t\t\t\ty: 0.4,\n\t\t\t\t\t\t\t\t\t\tz: 0.4,\n\t\t\t\t\t\t\t\t\t\tuv: 1\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tthrow new Error(`Invalid primitive type: ${type}`);\n\t\t\t\t}\n\t\t\t\tmesh.incRefCount();\n\t\t\t\tprimData = {\n\t\t\t\t\t\tmesh: mesh,\n\t\t\t\t\t\tarea: area\n\t\t\t\t};\n\t\t\t\tcache.map.set(type, primData);\n\t\t}\n\t\treturn primData;\n};\n\nclass ModelComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._type = 'asset', this._asset = null, this._model = null, this._mapping = {}, this._castShadows = true, this._receiveShadows = true, this._materialAsset = null, this._castShadowsLightmap = true, this._lightmapped = false, this._lightmapSizeMultiplier = 1, this.isStatic = false, this._layers = [\n\t\t\t\t\t\tLAYERID_WORLD\n\t\t\t\t], this._batchGroupId = -1, this._customAabb = null, this._area = null, this._materialEvents = null, this._clonedModel = false, this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null;\n\t\t\t\tthis._material = system.defaultMaterial;\n\t\t\t\tentity.on('remove', this.onRemoveChild, this);\n\t\t\t\tentity.on('removehierarchy', this.onRemoveChild, this);\n\t\t\t\tentity.on('insert', this.onInsertChild, this);\n\t\t\t\tentity.on('inserthierarchy', this.onInsertChild, this);\n\t\t}\n\t\tset meshInstances(value) {\n\t\t\t\tif (!this._model) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._model.meshInstances = value;\n\t\t}\n\t\tget meshInstances() {\n\t\t\t\tif (!this._model) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._model.meshInstances;\n\t\t}\n\t\tset customAabb(value) {\n\t\t\t\tthis._customAabb = value;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tconst mi = this._model.meshInstances;\n\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < mi.length; i++){\n\t\t\t\t\t\t\t\t\t\tmi[i].setCustomAabb(this._customAabb);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget customAabb() {\n\t\t\t\treturn this._customAabb;\n\t\t}\n\t\tset type(value) {\n\t\t\t\tif (this._type === value) return;\n\t\t\t\tthis._area = null;\n\t\t\t\tthis._type = value;\n\t\t\t\tif (value === 'asset') {\n\t\t\t\t\t\tif (this._asset !== null) {\n\t\t\t\t\t\t\t\tthis._bindModelAsset(this._asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.model = null;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst primData = getShapePrimitive(this.system.app.graphicsDevice, value);\n\t\t\t\t\t\tthis._area = primData.area;\n\t\t\t\t\t\tconst mesh = primData.mesh;\n\t\t\t\t\t\tconst node = new GraphNode();\n\t\t\t\t\t\tconst model = new Model();\n\t\t\t\t\t\tmodel.graph = node;\n\t\t\t\t\t\tmodel.meshInstances = [\n\t\t\t\t\t\t\t\tnew MeshInstance(mesh, this._material, node)\n\t\t\t\t\t\t];\n\t\t\t\t\t\tthis.model = model;\n\t\t\t\t\t\tthis._asset = null;\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tset asset(value) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tlet _id = value;\n\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\t_id = value.id;\n\t\t\t\t}\n\t\t\t\tif (this._asset !== _id) {\n\t\t\t\t\t\tif (this._asset) {\n\t\t\t\t\t\t\t\tassets.off(`add:${this._asset}`, this._onModelAssetAdded, this);\n\t\t\t\t\t\t\t\tconst _prev = assets.get(this._asset);\n\t\t\t\t\t\t\t\tif (_prev) {\n\t\t\t\t\t\t\t\t\t\tthis._unbindModelAsset(_prev);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._asset = _id;\n\t\t\t\t\t\tif (this._asset) {\n\t\t\t\t\t\t\t\tconst asset = assets.get(this._asset);\n\t\t\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\t\t\tthis.model = null;\n\t\t\t\t\t\t\t\t\t\tassets.on(`add:${this._asset}`, this._onModelAssetAdded, this);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._bindModelAsset(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.model = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget asset() {\n\t\t\t\treturn this._asset;\n\t\t}\n\t\tset model(value) {\n\t\t\t\tif (this._model === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (value && value._immutable) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis._model._immutable = false;\n\t\t\t\t\t\tthis.removeModelFromLayers();\n\t\t\t\t\t\tthis._model.getGraph().destroy();\n\t\t\t\t\t\tdelete this._model._entity;\n\t\t\t\t\t\tif (this._clonedModel) {\n\t\t\t\t\t\t\t\tthis._model.destroy();\n\t\t\t\t\t\t\t\tthis._clonedModel = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._model = value;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis._model._immutable = true;\n\t\t\t\t\t\tconst meshInstances = this._model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tmeshInstances[i].castShadow = this._castShadows;\n\t\t\t\t\t\t\t\tmeshInstances[i].receiveShadow = this._receiveShadows;\n\t\t\t\t\t\t\t\tmeshInstances[i].setCustomAabb(this._customAabb);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.lightmapped = this._lightmapped;\n\t\t\t\t\t\tthis.entity.addChild(this._model.graph);\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.addModelToLayers();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._model._entity = this.entity;\n\t\t\t\t\t\tif (this.entity.animation) {\n\t\t\t\t\t\t\t\tthis.entity.animation.setModel(this._model);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.entity.anim) {\n\t\t\t\t\t\t\t\tthis.entity.anim.rebind();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.type === 'asset') {\n\t\t\t\t\t\t\t\tthis.mapping = this._mapping;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._unsetMaterialEvents();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget model() {\n\t\t\t\treturn this._model;\n\t\t}\n\t\tset lightmapped(value) {\n\t\t\t\tif (value !== this._lightmapped) {\n\t\t\t\t\t\tthis._lightmapped = value;\n\t\t\t\t\t\tif (this._model) {\n\t\t\t\t\t\t\t\tconst mi = this._model.meshInstances;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < mi.length; i++){\n\t\t\t\t\t\t\t\t\t\tmi[i].setLightmapped(value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget lightmapped() {\n\t\t\t\treturn this._lightmapped;\n\t\t}\n\t\tset castShadows(value) {\n\t\t\t\tif (this._castShadows === value) return;\n\t\t\t\tconst model = this._model;\n\t\t\t\tif (model) {\n\t\t\t\t\t\tconst layers = this.layers;\n\t\t\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\t\t\tif (this._castShadows && !value) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\t\t\t\t\tlayer.removeShadowCasters(model.meshInstances);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst meshInstances = model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tmeshInstances[i].castShadow = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this._castShadows && value) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst layer = scene.layers.getLayerById(layers[i]);\n\t\t\t\t\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\t\t\t\t\tlayer.addShadowCasters(model.meshInstances);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._castShadows = value;\n\t\t}\n\t\tget castShadows() {\n\t\t\t\treturn this._castShadows;\n\t\t}\n\t\tset receiveShadows(value) {\n\t\t\t\tif (this._receiveShadows === value) return;\n\t\t\t\tthis._receiveShadows = value;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tconst meshInstances = this._model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0, len = meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tmeshInstances[i].receiveShadow = value;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget receiveShadows() {\n\t\t\t\treturn this._receiveShadows;\n\t\t}\n\t\tset castShadowsLightmap(value) {\n\t\t\t\tthis._castShadowsLightmap = value;\n\t\t}\n\t\tget castShadowsLightmap() {\n\t\t\t\treturn this._castShadowsLightmap;\n\t\t}\n\t\tset lightmapSizeMultiplier(value) {\n\t\t\t\tthis._lightmapSizeMultiplier = value;\n\t\t}\n\t\tget lightmapSizeMultiplier() {\n\t\t\t\treturn this._lightmapSizeMultiplier;\n\t\t}\n\t\tset layers(value) {\n\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\tif (this.meshInstances) {\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tconst layer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\t\t\tlayer.removeMeshInstances(this.meshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._layers.length = 0;\n\t\t\t\tfor(let i = 0; i < value.length; i++){\n\t\t\t\t\t\tthis._layers[i] = value[i];\n\t\t\t\t}\n\t\t\t\tif (!this.enabled || !this.entity.enabled || !this.meshInstances) return;\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.addMeshInstances(this.meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tset batchGroupId(value) {\n\t\t\t\tif (this._batchGroupId === value) return;\n\t\t\t\tif (this.entity.enabled && this._batchGroupId >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.remove(BatchGroup.MODEL, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t\t\tif (this.entity.enabled && value >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.insert(BatchGroup.MODEL, value, this.entity);\n\t\t\t\t}\n\t\t\t\tif (value < 0 && this._batchGroupId >= 0 && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.addModelToLayers();\n\t\t\t\t}\n\t\t\t\tthis._batchGroupId = value;\n\t\t}\n\t\tget batchGroupId() {\n\t\t\t\treturn this._batchGroupId;\n\t\t}\n\t\tset materialAsset(value) {\n\t\t\t\tlet _id = value;\n\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\t_id = value.id;\n\t\t\t\t}\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (_id !== this._materialAsset) {\n\t\t\t\t\t\tif (this._materialAsset) {\n\t\t\t\t\t\t\t\tassets.off(`add:${this._materialAsset}`, this._onMaterialAssetAdd, this);\n\t\t\t\t\t\t\t\tconst _prev = assets.get(this._materialAsset);\n\t\t\t\t\t\t\t\tif (_prev) {\n\t\t\t\t\t\t\t\t\t\tthis._unbindMaterialAsset(_prev);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._materialAsset = _id;\n\t\t\t\t\t\tif (this._materialAsset) {\n\t\t\t\t\t\t\t\tconst asset = assets.get(this._materialAsset);\n\t\t\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\t\t\tthis._setMaterial(this.system.defaultMaterial);\n\t\t\t\t\t\t\t\t\t\tassets.on(`add:${this._materialAsset}`, this._onMaterialAssetAdd, this);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._bindMaterialAsset(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._setMaterial(this.system.defaultMaterial);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget materialAsset() {\n\t\t\t\treturn this._materialAsset;\n\t\t}\n\t\tset material(value) {\n\t\t\t\tif (this._material === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.materialAsset = null;\n\t\t\t\tthis._setMaterial(value);\n\t\t}\n\t\tget material() {\n\t\t\t\treturn this._material;\n\t\t}\n\t\tset mapping(value) {\n\t\t\t\tif (this._type !== 'asset') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._unsetMaterialEvents();\n\t\t\t\tif (!value) {\n\t\t\t\t\t\tvalue = {};\n\t\t\t\t}\n\t\t\t\tthis._mapping = value;\n\t\t\t\tif (!this._model) return;\n\t\t\t\tconst meshInstances = this._model.meshInstances;\n\t\t\t\tconst modelAsset = this.asset ? this.system.app.assets.get(this.asset) : null;\n\t\t\t\tconst assetMapping = modelAsset ? modelAsset.data.mapping : null;\n\t\t\t\tlet asset = null;\n\t\t\t\tfor(let i = 0, len = meshInstances.length; i < len; i++){\n\t\t\t\t\t\tif (value[i] !== undefined) {\n\t\t\t\t\t\t\t\tif (value[i]) {\n\t\t\t\t\t\t\t\t\t\tasset = this.system.app.assets.get(value[i]);\n\t\t\t\t\t\t\t\t\t\tthis._loadAndSetMeshInstanceMaterial(asset, meshInstances[i], i);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmeshInstances[i].material = this.system.defaultMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (assetMapping) {\n\t\t\t\t\t\t\t\tif (assetMapping[i] && (assetMapping[i].material || assetMapping[i].path)) {\n\t\t\t\t\t\t\t\t\t\tif (assetMapping[i].material !== undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\tasset = this.system.app.assets.get(assetMapping[i].material);\n\t\t\t\t\t\t\t\t\t\t} else if (assetMapping[i].path !== undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst url = this._getMaterialAssetUrl(assetMapping[i].path);\n\t\t\t\t\t\t\t\t\t\t\t\tif (url) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tasset = this.system.app.assets.getByUrl(url);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis._loadAndSetMeshInstanceMaterial(asset, meshInstances[i], i);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmeshInstances[i].material = this.system.defaultMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget mapping() {\n\t\t\t\treturn this._mapping;\n\t\t}\n\t\taddModelToLayers() {\n\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.addMeshInstances(this.meshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremoveModelFromLayers() {\n\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.removeMeshInstances(this.meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tonRemoveChild() {\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis.removeModelFromLayers();\n\t\t\t\t}\n\t\t}\n\t\tonInsertChild() {\n\t\t\t\tif (this._model && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.addModelToLayers();\n\t\t\t\t}\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.asset = null;\n\t\t\t\tthis.model = null;\n\t\t\t\tthis.materialAsset = null;\n\t\t\t\tthis._unsetMaterialEvents();\n\t\t\t\tthis.entity.off('remove', this.onRemoveChild, this);\n\t\t\t\tthis.entity.off('insert', this.onInsertChild, this);\n\t\t}\n\t\tonLayersChanged(oldComp, newComp) {\n\t\t\t\tthis.addModelToLayers();\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t}\n\t\tonLayerAdded(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.addMeshInstances(this.meshInstances);\n\t\t}\n\t\tonLayerRemoved(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.removeMeshInstances(this.meshInstances);\n\t\t}\n\t\t_setMaterialEvent(index, event, id, handler) {\n\t\t\t\tconst evt = `${event}:${id}`;\n\t\t\t\tthis.system.app.assets.on(evt, handler, this);\n\t\t\t\tif (!this._materialEvents) {\n\t\t\t\t\t\tthis._materialEvents = [];\n\t\t\t\t}\n\t\t\t\tif (!this._materialEvents[index]) {\n\t\t\t\t\t\tthis._materialEvents[index] = {};\n\t\t\t\t}\n\t\t\t\tthis._materialEvents[index][evt] = {\n\t\t\t\t\t\tid: id,\n\t\t\t\t\t\thandler: handler\n\t\t\t\t};\n\t\t}\n\t\t_unsetMaterialEvents() {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tconst events = this._materialEvents;\n\t\t\t\tif (!events) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0, len = events.length; i < len; i++){\n\t\t\t\t\t\tif (!events[i]) continue;\n\t\t\t\t\t\tconst evt = events[i];\n\t\t\t\t\t\tfor(const key in evt){\n\t\t\t\t\t\t\t\tassets.off(key, evt[key].handler, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._materialEvents = null;\n\t\t}\n\t\t_getAssetByIdOrPath(idOrPath) {\n\t\t\t\tlet asset = null;\n\t\t\t\tconst isPath = isNaN(parseInt(idOrPath, 10));\n\t\t\t\tif (!isPath) {\n\t\t\t\t\t\tasset = this.system.app.assets.get(idOrPath);\n\t\t\t\t} else if (this.asset) {\n\t\t\t\t\t\tconst url = this._getMaterialAssetUrl(idOrPath);\n\t\t\t\t\t\tif (url) {\n\t\t\t\t\t\t\t\tasset = this.system.app.assets.getByUrl(url);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn asset;\n\t\t}\n\t\t_getMaterialAssetUrl(path) {\n\t\t\t\tif (!this.asset) return null;\n\t\t\t\tconst modelAsset = this.system.app.assets.get(this.asset);\n\t\t\t\treturn modelAsset ? modelAsset.getAbsoluteUrl(path) : null;\n\t\t}\n\t\t_loadAndSetMeshInstanceMaterial(materialAsset, meshInstance, index) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (!materialAsset) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (materialAsset.resource) {\n\t\t\t\t\t\tmeshInstance.material = materialAsset.resource;\n\t\t\t\t\t\tthis._setMaterialEvent(index, 'remove', materialAsset.id, function() {\n\t\t\t\t\t\t\t\tmeshInstance.material = this.system.defaultMaterial;\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._setMaterialEvent(index, 'load', materialAsset.id, function(asset) {\n\t\t\t\t\t\t\t\tmeshInstance.material = asset.resource;\n\t\t\t\t\t\t\t\tthis._setMaterialEvent(index, 'remove', materialAsset.id, function() {\n\t\t\t\t\t\t\t\t\t\tmeshInstance.material = this.system.defaultMaterial;\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tassets.load(materialAsset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst app = this.system.app;\n\t\t\t\tconst scene = app.scene;\n\t\t\t\tconst layers = scene?.layers;\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this.onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tconst isAsset = this._type === 'asset';\n\t\t\t\tlet asset;\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis.addModelToLayers();\n\t\t\t\t} else if (isAsset && this._asset) {\n\t\t\t\t\t\tasset = app.assets.get(this._asset);\n\t\t\t\t\t\tif (asset && asset.resource !== this._model) {\n\t\t\t\t\t\t\t\tthis._bindModelAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._materialAsset) {\n\t\t\t\t\t\tasset = app.assets.get(this._materialAsset);\n\t\t\t\t\t\tif (asset && asset.resource !== this._material) {\n\t\t\t\t\t\t\t\tthis._bindMaterialAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (isAsset) {\n\t\t\t\t\t\tif (this._mapping) {\n\t\t\t\t\t\t\t\tfor(const index in this._mapping){\n\t\t\t\t\t\t\t\t\t\tif (this._mapping[index]) {\n\t\t\t\t\t\t\t\t\t\t\t\tasset = this._getAssetByIdOrPath(this._mapping[index]);\n\t\t\t\t\t\t\t\t\t\t\t\tif (asset && !asset.resource) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tapp.assets.load(asset);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tapp.batcher?.insert(BatchGroup.MODEL, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst app = this.system.app;\n\t\t\t\tconst scene = app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tapp.batcher?.remove(BatchGroup.MODEL, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tthis.removeModelFromLayers();\n\t\t\t\t}\n\t\t}\n\t\thide() {\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tconst instances = this._model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0, l = instances.length; i < l; i++){\n\t\t\t\t\t\t\t\tinstances[i].visible = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tshow() {\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tconst instances = this._model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0, l = instances.length; i < l; i++){\n\t\t\t\t\t\t\t\tinstances[i].visible = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_bindMaterialAsset(asset) {\n\t\t\t\tasset.on('load', this._onMaterialAssetLoad, this);\n\t\t\t\tasset.on('unload', this._onMaterialAssetUnload, this);\n\t\t\t\tasset.on('remove', this._onMaterialAssetRemove, this);\n\t\t\t\tasset.on('change', this._onMaterialAssetChange, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onMaterialAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.enabled || !this.entity.enabled) return;\n\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindMaterialAsset(asset) {\n\t\t\t\tasset.off('load', this._onMaterialAssetLoad, this);\n\t\t\t\tasset.off('unload', this._onMaterialAssetUnload, this);\n\t\t\t\tasset.off('remove', this._onMaterialAssetRemove, this);\n\t\t\t\tasset.off('change', this._onMaterialAssetChange, this);\n\t\t}\n\t\t_onMaterialAssetAdd(asset) {\n\t\t\t\tthis.system.app.assets.off(`add:${asset.id}`, this._onMaterialAssetAdd, this);\n\t\t\t\tif (this._materialAsset === asset.id) {\n\t\t\t\t\t\tthis._bindMaterialAsset(asset);\n\t\t\t\t}\n\t\t}\n\t\t_onMaterialAssetLoad(asset) {\n\t\t\t\tthis._setMaterial(asset.resource);\n\t\t}\n\t\t_onMaterialAssetUnload(asset) {\n\t\t\t\tthis._setMaterial(this.system.defaultMaterial);\n\t\t}\n\t\t_onMaterialAssetRemove(asset) {\n\t\t\t\tthis._onMaterialAssetUnload(asset);\n\t\t}\n\t\t_onMaterialAssetChange(asset) {}\n\t\t_bindModelAsset(asset) {\n\t\t\t\tthis._unbindModelAsset(asset);\n\t\t\t\tasset.on('load', this._onModelAssetLoad, this);\n\t\t\t\tasset.on('unload', this._onModelAssetUnload, this);\n\t\t\t\tasset.on('change', this._onModelAssetChange, this);\n\t\t\t\tasset.on('remove', this._onModelAssetRemove, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onModelAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.enabled || !this.entity.enabled) return;\n\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindModelAsset(asset) {\n\t\t\t\tasset.off('load', this._onModelAssetLoad, this);\n\t\t\t\tasset.off('unload', this._onModelAssetUnload, this);\n\t\t\t\tasset.off('change', this._onModelAssetChange, this);\n\t\t\t\tasset.off('remove', this._onModelAssetRemove, this);\n\t\t}\n\t\t_onModelAssetAdded(asset) {\n\t\t\t\tthis.system.app.assets.off(`add:${asset.id}`, this._onModelAssetAdded, this);\n\t\t\t\tif (asset.id === this._asset) {\n\t\t\t\t\t\tthis._bindModelAsset(asset);\n\t\t\t\t}\n\t\t}\n\t\t_onModelAssetLoad(asset) {\n\t\t\t\tthis.model = asset.resource.clone();\n\t\t\t\tthis._clonedModel = true;\n\t\t}\n\t\t_onModelAssetUnload(asset) {\n\t\t\t\tthis.model = null;\n\t\t}\n\t\t_onModelAssetChange(asset, attr, _new, _old) {\n\t\t\t\tif (attr === 'data') {\n\t\t\t\t\t\tthis.mapping = this._mapping;\n\t\t\t\t}\n\t\t}\n\t\t_onModelAssetRemove(asset) {\n\t\t\t\tthis.model = null;\n\t\t}\n\t\t_setMaterial(material) {\n\t\t\t\tif (this._material === material) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._material = material;\n\t\t\t\tconst model = this._model;\n\t\t\t\tif (model && this._type !== 'asset') {\n\t\t\t\t\t\tconst meshInstances = model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0, len = meshInstances.length; i < len; i++){\n\t\t\t\t\t\t\t\tmeshInstances[i].material = material;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass ModelComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$b = [\n\t\t'enabled'\n];\nclass ModelComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'model';\n\t\t\t\tthis.ComponentType = ModelComponent;\n\t\t\t\tthis.DataType = ModelComponentData;\n\t\t\t\tthis.schema = _schema$b;\n\t\t\t\tthis.defaultMaterial = getDefaultMaterial(app.graphicsDevice);\n\t\t\t\tthis.on('beforeremove', this.onRemove, this);\n\t\t}\n\t\tinitializeComponentData(component, _data, properties) {\n\t\t\t\tproperties = [\n\t\t\t\t\t\t'material',\n\t\t\t\t\t\t'materialAsset',\n\t\t\t\t\t\t'asset',\n\t\t\t\t\t\t'castShadows',\n\t\t\t\t\t\t'receiveShadows',\n\t\t\t\t\t\t'castShadowsLightmap',\n\t\t\t\t\t\t'lightmapped',\n\t\t\t\t\t\t'lightmapSizeMultiplier',\n\t\t\t\t\t\t'type',\n\t\t\t\t\t\t'mapping',\n\t\t\t\t\t\t'layers',\n\t\t\t\t\t\t'isStatic',\n\t\t\t\t\t\t'batchGroupId'\n\t\t\t\t];\n\t\t\t\tif (_data.batchGroupId === null || _data.batchGroupId === undefined) {\n\t\t\t\t\t\t_data.batchGroupId = -1;\n\t\t\t\t}\n\t\t\t\tif (_data.layers && _data.layers.length) {\n\t\t\t\t\t\t_data.layers = _data.layers.slice(0);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < properties.length; i++){\n\t\t\t\t\t\tif (_data.hasOwnProperty(properties[i])) {\n\t\t\t\t\t\t\t\tcomponent[properties[i]] = _data[properties[i]];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (_data.aabbCenter && _data.aabbHalfExtents) {\n\t\t\t\t\t\tcomponent.customAabb = new BoundingBox(new Vec3(_data.aabbCenter), new Vec3(_data.aabbHalfExtents));\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, _data, [\n\t\t\t\t\t\t'enabled'\n\t\t\t\t]);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst data = {\n\t\t\t\t\t\ttype: entity.model.type,\n\t\t\t\t\t\tasset: entity.model.asset,\n\t\t\t\t\t\tcastShadows: entity.model.castShadows,\n\t\t\t\t\t\treceiveShadows: entity.model.receiveShadows,\n\t\t\t\t\t\tcastShadowsLightmap: entity.model.castShadowsLightmap,\n\t\t\t\t\t\tlightmapped: entity.model.lightmapped,\n\t\t\t\t\t\tlightmapSizeMultiplier: entity.model.lightmapSizeMultiplier,\n\t\t\t\t\t\tisStatic: entity.model.isStatic,\n\t\t\t\t\t\tenabled: entity.model.enabled,\n\t\t\t\t\t\tlayers: entity.model.layers,\n\t\t\t\t\t\tbatchGroupId: entity.model.batchGroupId,\n\t\t\t\t\t\tmapping: extend({}, entity.model.mapping)\n\t\t\t\t};\n\t\t\t\tlet materialAsset = entity.model.materialAsset;\n\t\t\t\tif (!(materialAsset instanceof Asset) && materialAsset != null) {\n\t\t\t\t\t\tmaterialAsset = this.app.assets.get(materialAsset);\n\t\t\t\t}\n\t\t\t\tconst material = entity.model.material;\n\t\t\t\tif (!material || material === this.defaultMaterial || !materialAsset || material === materialAsset.resource) {\n\t\t\t\t\t\tdata.materialAsset = materialAsset;\n\t\t\t\t}\n\t\t\t\tconst component = this.addComponent(clone, data);\n\t\t\t\tif (entity.model.model && entity.model.type === 'asset' && !entity.model.asset) {\n\t\t\t\t\t\tcomponent.model = entity.model.model.clone();\n\t\t\t\t\t\tcomponent._clonedModel = true;\n\t\t\t\t}\n\t\t\t\tif (!data.materialAsset) {\n\t\t\t\t\t\tcomponent.material = material;\n\t\t\t\t}\n\t\t\t\tif (entity.model.model) {\n\t\t\t\t\t\tconst meshInstances = entity.model.model.meshInstances;\n\t\t\t\t\t\tconst meshInstancesClone = component.model.meshInstances;\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tmeshInstancesClone[i].mask = meshInstances[i].mask;\n\t\t\t\t\t\t\t\tmeshInstancesClone[i].material = meshInstances[i].material;\n\t\t\t\t\t\t\t\tmeshInstancesClone[i].layer = meshInstances[i].layer;\n\t\t\t\t\t\t\t\tmeshInstancesClone[i].receiveShadow = meshInstances[i].receiveShadow;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (entity.model.customAabb) {\n\t\t\t\t\t\tcomponent.customAabb = entity.model.customAabb.clone();\n\t\t\t\t}\n\t\t\t\treturn component;\n\t\t}\n\t\tonRemove(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n}\nComponent._buildAccessors(ModelComponent.prototype, _schema$b);\n\nconst SIMPLE_PROPERTIES = [\n\t\t'emitterExtents',\n\t\t'emitterRadius',\n\t\t'emitterExtentsInner',\n\t\t'emitterRadiusInner',\n\t\t'loop',\n\t\t'initialVelocity',\n\t\t'animSpeed',\n\t\t'normalMap',\n\t\t'particleNormal'\n];\nconst COMPLEX_PROPERTIES = [\n\t\t'numParticles',\n\t\t'lifetime',\n\t\t'rate',\n\t\t'rate2',\n\t\t'startAngle',\n\t\t'startAngle2',\n\t\t'lighting',\n\t\t'halfLambert',\n\t\t'intensity',\n\t\t'wrap',\n\t\t'wrapBounds',\n\t\t'depthWrite',\n\t\t'noFog',\n\t\t'sort',\n\t\t'stretch',\n\t\t'alignToMotion',\n\t\t'preWarm',\n\t\t'emitterShape',\n\t\t'animTilesX',\n\t\t'animTilesY',\n\t\t'animStartFrame',\n\t\t'animNumFrames',\n\t\t'animNumAnimations',\n\t\t'animIndex',\n\t\t'randomizeAnimIndex',\n\t\t'animLoop',\n\t\t'colorMap',\n\t\t'localSpace',\n\t\t'screenSpace',\n\t\t'orientation'\n];\nconst GRAPH_PROPERTIES = [\n\t\t'scaleGraph',\n\t\t'scaleGraph2',\n\t\t'colorGraph',\n\t\t'colorGraph2',\n\t\t'alphaGraph',\n\t\t'alphaGraph2',\n\t\t'velocityGraph',\n\t\t'velocityGraph2',\n\t\t'localVelocityGraph',\n\t\t'localVelocityGraph2',\n\t\t'rotationSpeedGraph',\n\t\t'rotationSpeedGraph2',\n\t\t'radialSpeedGraph',\n\t\t'radialSpeedGraph2'\n];\nconst ASSET_PROPERTIES = [\n\t\t'colorMapAsset',\n\t\t'normalMapAsset',\n\t\t'meshAsset',\n\t\t'renderAsset'\n];\nlet depthLayer;\nclass ParticleSystemComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._requestedDepth = false, this._drawOrder = 0, this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null, this._evtSetMeshes = null;\n\t\t\t\tthis.on('set_colorMapAsset', this.onSetColorMapAsset, this);\n\t\t\t\tthis.on('set_normalMapAsset', this.onSetNormalMapAsset, this);\n\t\t\t\tthis.on('set_meshAsset', this.onSetMeshAsset, this);\n\t\t\t\tthis.on('set_mesh', this.onSetMesh, this);\n\t\t\t\tthis.on('set_renderAsset', this.onSetRenderAsset, this);\n\t\t\t\tthis.on('set_loop', this.onSetLoop, this);\n\t\t\t\tthis.on('set_blendType', this.onSetBlendType, this);\n\t\t\t\tthis.on('set_depthSoftening', this.onSetDepthSoftening, this);\n\t\t\t\tthis.on('set_layers', this.onSetLayers, this);\n\t\t\t\tSIMPLE_PROPERTIES.forEach((prop)=>{\n\t\t\t\t\t\tthis.on(`set_${prop}`, this.onSetSimpleProperty, this);\n\t\t\t\t});\n\t\t\t\tCOMPLEX_PROPERTIES.forEach((prop)=>{\n\t\t\t\t\t\tthis.on(`set_${prop}`, this.onSetComplexProperty, this);\n\t\t\t\t});\n\t\t\t\tGRAPH_PROPERTIES.forEach((prop)=>{\n\t\t\t\t\t\tthis.on(`set_${prop}`, this.onSetGraphProperty, this);\n\t\t\t\t});\n\t\t}\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(arg) {\n\t\t\t\tthis._setValue('enabled', arg);\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this.data.enabled;\n\t\t}\n\t\tset autoPlay(arg) {\n\t\t\t\tthis._setValue('autoPlay', arg);\n\t\t}\n\t\tget autoPlay() {\n\t\t\t\treturn this.data.autoPlay;\n\t\t}\n\t\tset numParticles(arg) {\n\t\t\t\tthis._setValue('numParticles', arg);\n\t\t}\n\t\tget numParticles() {\n\t\t\t\treturn this.data.numParticles;\n\t\t}\n\t\tset lifetime(arg) {\n\t\t\t\tthis._setValue('lifetime', arg);\n\t\t}\n\t\tget lifetime() {\n\t\t\t\treturn this.data.lifetime;\n\t\t}\n\t\tset rate(arg) {\n\t\t\t\tthis._setValue('rate', arg);\n\t\t}\n\t\tget rate() {\n\t\t\t\treturn this.data.rate;\n\t\t}\n\t\tset rate2(arg) {\n\t\t\t\tthis._setValue('rate2', arg);\n\t\t}\n\t\tget rate2() {\n\t\t\t\treturn this.data.rate2;\n\t\t}\n\t\tset startAngle(arg) {\n\t\t\t\tthis._setValue('startAngle', arg);\n\t\t}\n\t\tget startAngle() {\n\t\t\t\treturn this.data.startAngle;\n\t\t}\n\t\tset startAngle2(arg) {\n\t\t\t\tthis._setValue('startAngle2', arg);\n\t\t}\n\t\tget startAngle2() {\n\t\t\t\treturn this.data.startAngle2;\n\t\t}\n\t\tset loop(arg) {\n\t\t\t\tthis._setValue('loop', arg);\n\t\t}\n\t\tget loop() {\n\t\t\t\treturn this.data.loop;\n\t\t}\n\t\tset preWarm(arg) {\n\t\t\t\tthis._setValue('preWarm', arg);\n\t\t}\n\t\tget preWarm() {\n\t\t\t\treturn this.data.preWarm;\n\t\t}\n\t\tset lighting(arg) {\n\t\t\t\tthis._setValue('lighting', arg);\n\t\t}\n\t\tget lighting() {\n\t\t\t\treturn this.data.lighting;\n\t\t}\n\t\tset halfLambert(arg) {\n\t\t\t\tthis._setValue('halfLambert', arg);\n\t\t}\n\t\tget halfLambert() {\n\t\t\t\treturn this.data.halfLambert;\n\t\t}\n\t\tset intensity(arg) {\n\t\t\t\tthis._setValue('intensity', arg);\n\t\t}\n\t\tget intensity() {\n\t\t\t\treturn this.data.intensity;\n\t\t}\n\t\tset depthWrite(arg) {\n\t\t\t\tthis._setValue('depthWrite', arg);\n\t\t}\n\t\tget depthWrite() {\n\t\t\t\treturn this.data.depthWrite;\n\t\t}\n\t\tset noFog(arg) {\n\t\t\t\tthis._setValue('noFog', arg);\n\t\t}\n\t\tget noFog() {\n\t\t\t\treturn this.data.noFog;\n\t\t}\n\t\tset depthSoftening(arg) {\n\t\t\t\tthis._setValue('depthSoftening', arg);\n\t\t}\n\t\tget depthSoftening() {\n\t\t\t\treturn this.data.depthSoftening;\n\t\t}\n\t\tset sort(arg) {\n\t\t\t\tthis._setValue('sort', arg);\n\t\t}\n\t\tget sort() {\n\t\t\t\treturn this.data.sort;\n\t\t}\n\t\tset blendType(arg) {\n\t\t\t\tthis._setValue('blendType', arg);\n\t\t}\n\t\tget blendType() {\n\t\t\t\treturn this.data.blendType;\n\t\t}\n\t\tset stretch(arg) {\n\t\t\t\tthis._setValue('stretch', arg);\n\t\t}\n\t\tget stretch() {\n\t\t\t\treturn this.data.stretch;\n\t\t}\n\t\tset alignToMotion(arg) {\n\t\t\t\tthis._setValue('alignToMotion', arg);\n\t\t}\n\t\tget alignToMotion() {\n\t\t\t\treturn this.data.alignToMotion;\n\t\t}\n\t\tset emitterShape(arg) {\n\t\t\t\tthis._setValue('emitterShape', arg);\n\t\t}\n\t\tget emitterShape() {\n\t\t\t\treturn this.data.emitterShape;\n\t\t}\n\t\tset emitterExtents(arg) {\n\t\t\t\tthis._setValue('emitterExtents', arg);\n\t\t}\n\t\tget emitterExtents() {\n\t\t\t\treturn this.data.emitterExtents;\n\t\t}\n\t\tset emitterExtentsInner(arg) {\n\t\t\t\tthis._setValue('emitterExtentsInner', arg);\n\t\t}\n\t\tget emitterExtentsInner() {\n\t\t\t\treturn this.data.emitterExtentsInner;\n\t\t}\n\t\tset emitterRadius(arg) {\n\t\t\t\tthis._setValue('emitterRadius', arg);\n\t\t}\n\t\tget emitterRadius() {\n\t\t\t\treturn this.data.emitterRadius;\n\t\t}\n\t\tset emitterRadiusInner(arg) {\n\t\t\t\tthis._setValue('emitterRadiusInner', arg);\n\t\t}\n\t\tget emitterRadiusInner() {\n\t\t\t\treturn this.data.emitterRadiusInner;\n\t\t}\n\t\tset initialVelocity(arg) {\n\t\t\t\tthis._setValue('initialVelocity', arg);\n\t\t}\n\t\tget initialVelocity() {\n\t\t\t\treturn this.data.initialVelocity;\n\t\t}\n\t\tset wrap(arg) {\n\t\t\t\tthis._setValue('wrap', arg);\n\t\t}\n\t\tget wrap() {\n\t\t\t\treturn this.data.wrap;\n\t\t}\n\t\tset wrapBounds(arg) {\n\t\t\t\tthis._setValue('wrapBounds', arg);\n\t\t}\n\t\tget wrapBounds() {\n\t\t\t\treturn this.data.wrapBounds;\n\t\t}\n\t\tset localSpace(arg) {\n\t\t\t\tthis._setValue('localSpace', arg);\n\t\t}\n\t\tget localSpace() {\n\t\t\t\treturn this.data.localSpace;\n\t\t}\n\t\tset screenSpace(arg) {\n\t\t\t\tthis._setValue('screenSpace', arg);\n\t\t}\n\t\tget screenSpace() {\n\t\t\t\treturn this.data.screenSpace;\n\t\t}\n\t\tset colorMapAsset(arg) {\n\t\t\t\tthis._setValue('colorMapAsset', arg);\n\t\t}\n\t\tget colorMapAsset() {\n\t\t\t\treturn this.data.colorMapAsset;\n\t\t}\n\t\tset normalMapAsset(arg) {\n\t\t\t\tthis._setValue('normalMapAsset', arg);\n\t\t}\n\t\tget normalMapAsset() {\n\t\t\t\treturn this.data.normalMapAsset;\n\t\t}\n\t\tset mesh(arg) {\n\t\t\t\tthis._setValue('mesh', arg);\n\t\t}\n\t\tget mesh() {\n\t\t\t\treturn this.data.mesh;\n\t\t}\n\t\tset meshAsset(arg) {\n\t\t\t\tthis._setValue('meshAsset', arg);\n\t\t}\n\t\tget meshAsset() {\n\t\t\t\treturn this.data.meshAsset;\n\t\t}\n\t\tset renderAsset(arg) {\n\t\t\t\tthis._setValue('renderAsset', arg);\n\t\t}\n\t\tget renderAsset() {\n\t\t\t\treturn this.data.renderAsset;\n\t\t}\n\t\tset orientation(arg) {\n\t\t\t\tthis._setValue('orientation', arg);\n\t\t}\n\t\tget orientation() {\n\t\t\t\treturn this.data.orientation;\n\t\t}\n\t\tset particleNormal(arg) {\n\t\t\t\tthis._setValue('particleNormal', arg);\n\t\t}\n\t\tget particleNormal() {\n\t\t\t\treturn this.data.particleNormal;\n\t\t}\n\t\tset localVelocityGraph(arg) {\n\t\t\t\tthis._setValue('localVelocityGraph', arg);\n\t\t}\n\t\tget localVelocityGraph() {\n\t\t\t\treturn this.data.localVelocityGraph;\n\t\t}\n\t\tset localVelocityGraph2(arg) {\n\t\t\t\tthis._setValue('localVelocityGraph2', arg);\n\t\t}\n\t\tget localVelocityGraph2() {\n\t\t\t\treturn this.data.localVelocityGraph2;\n\t\t}\n\t\tset velocityGraph(arg) {\n\t\t\t\tthis._setValue('velocityGraph', arg);\n\t\t}\n\t\tget velocityGraph() {\n\t\t\t\treturn this.data.velocityGraph;\n\t\t}\n\t\tset velocityGraph2(arg) {\n\t\t\t\tthis._setValue('velocityGraph2', arg);\n\t\t}\n\t\tget velocityGraph2() {\n\t\t\t\treturn this.data.velocityGraph2;\n\t\t}\n\t\tset rotationSpeedGraph(arg) {\n\t\t\t\tthis._setValue('rotationSpeedGraph', arg);\n\t\t}\n\t\tget rotationSpeedGraph() {\n\t\t\t\treturn this.data.rotationSpeedGraph;\n\t\t}\n\t\tset rotationSpeedGraph2(arg) {\n\t\t\t\tthis._setValue('rotationSpeedGraph2', arg);\n\t\t}\n\t\tget rotationSpeedGraph2() {\n\t\t\t\treturn this.data.rotationSpeedGraph2;\n\t\t}\n\t\tset radialSpeedGraph(arg) {\n\t\t\t\tthis._setValue('radialSpeedGraph', arg);\n\t\t}\n\t\tget radialSpeedGraph() {\n\t\t\t\treturn this.data.radialSpeedGraph;\n\t\t}\n\t\tset radialSpeedGraph2(arg) {\n\t\t\t\tthis._setValue('radialSpeedGraph2', arg);\n\t\t}\n\t\tget radialSpeedGraph2() {\n\t\t\t\treturn this.data.radialSpeedGraph2;\n\t\t}\n\t\tset scaleGraph(arg) {\n\t\t\t\tthis._setValue('scaleGraph', arg);\n\t\t}\n\t\tget scaleGraph() {\n\t\t\t\treturn this.data.scaleGraph;\n\t\t}\n\t\tset scaleGraph2(arg) {\n\t\t\t\tthis._setValue('scaleGraph2', arg);\n\t\t}\n\t\tget scaleGraph2() {\n\t\t\t\treturn this.data.scaleGraph2;\n\t\t}\n\t\tset colorGraph(arg) {\n\t\t\t\tthis._setValue('colorGraph', arg);\n\t\t}\n\t\tget colorGraph() {\n\t\t\t\treturn this.data.colorGraph;\n\t\t}\n\t\tset colorGraph2(arg) {\n\t\t\t\tthis._setValue('colorGraph2', arg);\n\t\t}\n\t\tget colorGraph2() {\n\t\t\t\treturn this.data.colorGraph2;\n\t\t}\n\t\tset alphaGraph(arg) {\n\t\t\t\tthis._setValue('alphaGraph', arg);\n\t\t}\n\t\tget alphaGraph() {\n\t\t\t\treturn this.data.alphaGraph;\n\t\t}\n\t\tset alphaGraph2(arg) {\n\t\t\t\tthis._setValue('alphaGraph2', arg);\n\t\t}\n\t\tget alphaGraph2() {\n\t\t\t\treturn this.data.alphaGraph2;\n\t\t}\n\t\tset colorMap(arg) {\n\t\t\t\tthis._setValue('colorMap', arg);\n\t\t}\n\t\tget colorMap() {\n\t\t\t\treturn this.data.colorMap;\n\t\t}\n\t\tset normalMap(arg) {\n\t\t\t\tthis._setValue('normalMap', arg);\n\t\t}\n\t\tget normalMap() {\n\t\t\t\treturn this.data.normalMap;\n\t\t}\n\t\tset animTilesX(arg) {\n\t\t\t\tthis._setValue('animTilesX', arg);\n\t\t}\n\t\tget animTilesX() {\n\t\t\t\treturn this.data.animTilesX;\n\t\t}\n\t\tset animTilesY(arg) {\n\t\t\t\tthis._setValue('animTilesY', arg);\n\t\t}\n\t\tget animTilesY() {\n\t\t\t\treturn this.data.animTilesY;\n\t\t}\n\t\tset animStartFrame(arg) {\n\t\t\t\tthis._setValue('animStartFrame', arg);\n\t\t}\n\t\tget animStartFrame() {\n\t\t\t\treturn this.data.animStartFrame;\n\t\t}\n\t\tset animNumFrames(arg) {\n\t\t\t\tthis._setValue('animNumFrames', arg);\n\t\t}\n\t\tget animNumFrames() {\n\t\t\t\treturn this.data.animNumFrames;\n\t\t}\n\t\tset animNumAnimations(arg) {\n\t\t\t\tthis._setValue('animNumAnimations', arg);\n\t\t}\n\t\tget animNumAnimations() {\n\t\t\t\treturn this.data.animNumAnimations;\n\t\t}\n\t\tset animIndex(arg) {\n\t\t\t\tthis._setValue('animIndex', arg);\n\t\t}\n\t\tget animIndex() {\n\t\t\t\treturn this.data.animIndex;\n\t\t}\n\t\tset randomizeAnimIndex(arg) {\n\t\t\t\tthis._setValue('randomizeAnimIndex', arg);\n\t\t}\n\t\tget randomizeAnimIndex() {\n\t\t\t\treturn this.data.randomizeAnimIndex;\n\t\t}\n\t\tset animSpeed(arg) {\n\t\t\t\tthis._setValue('animSpeed', arg);\n\t\t}\n\t\tget animSpeed() {\n\t\t\t\treturn this.data.animSpeed;\n\t\t}\n\t\tset animLoop(arg) {\n\t\t\t\tthis._setValue('animLoop', arg);\n\t\t}\n\t\tget animLoop() {\n\t\t\t\treturn this.data.animLoop;\n\t\t}\n\t\tset layers(arg) {\n\t\t\t\tthis._setValue('layers', arg);\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this.data.layers;\n\t\t}\n\t\tset drawOrder(drawOrder) {\n\t\t\t\tthis._drawOrder = drawOrder;\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter.drawOrder = drawOrder;\n\t\t\t\t}\n\t\t}\n\t\tget drawOrder() {\n\t\t\t\treturn this._drawOrder;\n\t\t}\n\t\t_setValue(name, value) {\n\t\t\t\tconst data = this.data;\n\t\t\t\tconst oldValue = data[name];\n\t\t\t\tdata[name] = value;\n\t\t\t\tthis.fire('set', name, oldValue, value);\n\t\t}\n\t\taddMeshInstanceToLayers() {\n\t\t\t\tif (!this.emitter) return;\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.addMeshInstances([\n\t\t\t\t\t\t\t\tthis.emitter.meshInstance\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis.emitter._layer = layer;\n\t\t\t\t}\n\t\t}\n\t\tremoveMeshInstanceFromLayers() {\n\t\t\t\tif (!this.emitter) return;\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.removeMeshInstances([\n\t\t\t\t\t\t\t\tthis.emitter.meshInstance\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\tonSetLayers(name, oldValue, newValue) {\n\t\t\t\tif (!this.emitter) return;\n\t\t\t\tfor(let i = 0; i < oldValue.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(oldValue[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.removeMeshInstances([\n\t\t\t\t\t\t\t\tthis.emitter.meshInstance\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\tif (!this.enabled || !this.entity.enabled) return;\n\t\t\t\tfor(let i = 0; i < newValue.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(newValue[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.addMeshInstances([\n\t\t\t\t\t\t\t\tthis.emitter.meshInstance\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\tonLayersChanged(oldComp, newComp) {\n\t\t\t\tthis.addMeshInstanceToLayers();\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t}\n\t\tonLayerAdded(layer) {\n\t\t\t\tif (!this.emitter) return;\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.addMeshInstances([\n\t\t\t\t\t\tthis.emitter.meshInstance\n\t\t\t\t]);\n\t\t}\n\t\tonLayerRemoved(layer) {\n\t\t\t\tif (!this.emitter) return;\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.removeMeshInstances([\n\t\t\t\t\t\tthis.emitter.meshInstance\n\t\t\t\t]);\n\t\t}\n\t\t_bindColorMapAsset(asset) {\n\t\t\t\tasset.on('load', this._onColorMapAssetLoad, this);\n\t\t\t\tasset.on('unload', this._onColorMapAssetUnload, this);\n\t\t\t\tasset.on('remove', this._onColorMapAssetRemove, this);\n\t\t\t\tasset.on('change', this._onColorMapAssetChange, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onColorMapAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.enabled || !this.entity.enabled) return;\n\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindColorMapAsset(asset) {\n\t\t\t\tasset.off('load', this._onColorMapAssetLoad, this);\n\t\t\t\tasset.off('unload', this._onColorMapAssetUnload, this);\n\t\t\t\tasset.off('remove', this._onColorMapAssetRemove, this);\n\t\t\t\tasset.off('change', this._onColorMapAssetChange, this);\n\t\t}\n\t\t_onColorMapAssetLoad(asset) {\n\t\t\t\tthis.colorMap = asset.resource;\n\t\t}\n\t\t_onColorMapAssetUnload(asset) {\n\t\t\t\tthis.colorMap = null;\n\t\t}\n\t\t_onColorMapAssetRemove(asset) {\n\t\t\t\tthis._onColorMapAssetUnload(asset);\n\t\t}\n\t\t_onColorMapAssetChange(asset) {}\n\t\tonSetColorMapAsset(name, oldValue, newValue) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (oldValue) {\n\t\t\t\t\t\tconst asset = assets.get(oldValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._unbindColorMapAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (newValue) {\n\t\t\t\t\t\tif (newValue instanceof Asset) {\n\t\t\t\t\t\t\t\tthis.data.colorMapAsset = newValue.id;\n\t\t\t\t\t\t\t\tnewValue = newValue.id;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst asset = assets.get(newValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._bindColorMapAsset(asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassets.once(`add:${newValue}`, (asset)=>{\n\t\t\t\t\t\t\t\t\t\tthis._bindColorMapAsset(asset);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.colorMap = null;\n\t\t\t\t}\n\t\t}\n\t\t_bindNormalMapAsset(asset) {\n\t\t\t\tasset.on('load', this._onNormalMapAssetLoad, this);\n\t\t\t\tasset.on('unload', this._onNormalMapAssetUnload, this);\n\t\t\t\tasset.on('remove', this._onNormalMapAssetRemove, this);\n\t\t\t\tasset.on('change', this._onNormalMapAssetChange, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onNormalMapAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.enabled || !this.entity.enabled) return;\n\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindNormalMapAsset(asset) {\n\t\t\t\tasset.off('load', this._onNormalMapAssetLoad, this);\n\t\t\t\tasset.off('unload', this._onNormalMapAssetUnload, this);\n\t\t\t\tasset.off('remove', this._onNormalMapAssetRemove, this);\n\t\t\t\tasset.off('change', this._onNormalMapAssetChange, this);\n\t\t}\n\t\t_onNormalMapAssetLoad(asset) {\n\t\t\t\tthis.normalMap = asset.resource;\n\t\t}\n\t\t_onNormalMapAssetUnload(asset) {\n\t\t\t\tthis.normalMap = null;\n\t\t}\n\t\t_onNormalMapAssetRemove(asset) {\n\t\t\t\tthis._onNormalMapAssetUnload(asset);\n\t\t}\n\t\t_onNormalMapAssetChange(asset) {}\n\t\tonSetNormalMapAsset(name, oldValue, newValue) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (oldValue) {\n\t\t\t\t\t\tconst asset = assets.get(oldValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._unbindNormalMapAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (newValue) {\n\t\t\t\t\t\tif (newValue instanceof Asset) {\n\t\t\t\t\t\t\t\tthis.data.normalMapAsset = newValue.id;\n\t\t\t\t\t\t\t\tnewValue = newValue.id;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst asset = assets.get(newValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._bindNormalMapAsset(asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassets.once(`add:${newValue}`, (asset)=>{\n\t\t\t\t\t\t\t\t\t\tthis._bindNormalMapAsset(asset);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.normalMap = null;\n\t\t\t\t}\n\t\t}\n\t\t_bindMeshAsset(asset) {\n\t\t\t\tasset.on('load', this._onMeshAssetLoad, this);\n\t\t\t\tasset.on('unload', this._onMeshAssetUnload, this);\n\t\t\t\tasset.on('remove', this._onMeshAssetRemove, this);\n\t\t\t\tasset.on('change', this._onMeshAssetChange, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onMeshAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.enabled || !this.entity.enabled) return;\n\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindMeshAsset(asset) {\n\t\t\t\tasset.off('load', this._onMeshAssetLoad, this);\n\t\t\t\tasset.off('unload', this._onMeshAssetUnload, this);\n\t\t\t\tasset.off('remove', this._onMeshAssetRemove, this);\n\t\t\t\tasset.off('change', this._onMeshAssetChange, this);\n\t\t}\n\t\t_onMeshAssetLoad(asset) {\n\t\t\t\tthis._onMeshChanged(asset.resource);\n\t\t}\n\t\t_onMeshAssetUnload(asset) {\n\t\t\t\tthis.mesh = null;\n\t\t}\n\t\t_onMeshAssetRemove(asset) {\n\t\t\t\tthis._onMeshAssetUnload(asset);\n\t\t}\n\t\t_onMeshAssetChange(asset) {}\n\t\tonSetMeshAsset(name, oldValue, newValue) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (oldValue) {\n\t\t\t\t\t\tconst asset = assets.get(oldValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._unbindMeshAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (newValue) {\n\t\t\t\t\t\tif (newValue instanceof Asset) {\n\t\t\t\t\t\t\t\tthis.data.meshAsset = newValue.id;\n\t\t\t\t\t\t\t\tnewValue = newValue.id;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst asset = assets.get(newValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._bindMeshAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._onMeshChanged(null);\n\t\t\t\t}\n\t\t}\n\t\tonSetMesh(name, oldValue, newValue) {\n\t\t\t\tif (!newValue || newValue instanceof Asset || typeof newValue === 'number') {\n\t\t\t\t\t\tthis.meshAsset = newValue;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._onMeshChanged(newValue);\n\t\t\t\t}\n\t\t}\n\t\t_onMeshChanged(mesh) {\n\t\t\t\tif (mesh && !(mesh instanceof Mesh)) {\n\t\t\t\t\t\tif (mesh.meshInstances[0]) {\n\t\t\t\t\t\t\t\tmesh = mesh.meshInstances[0].mesh;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmesh = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.data.mesh = mesh;\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter.mesh = mesh;\n\t\t\t\t\t\tthis.emitter.resetMaterial();\n\t\t\t\t\t\tthis.rebuild();\n\t\t\t\t}\n\t\t}\n\t\tonSetRenderAsset(name, oldValue, newValue) {\n\t\t\t\tconst assets = this.system.app.assets;\n\t\t\t\tif (oldValue) {\n\t\t\t\t\t\tconst asset = assets.get(oldValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._unbindRenderAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (newValue) {\n\t\t\t\t\t\tif (newValue instanceof Asset) {\n\t\t\t\t\t\t\t\tthis.data.renderAsset = newValue.id;\n\t\t\t\t\t\t\t\tnewValue = newValue.id;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst asset = assets.get(newValue);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._bindRenderAsset(asset);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._onRenderChanged(null);\n\t\t\t\t}\n\t\t}\n\t\t_bindRenderAsset(asset) {\n\t\t\t\tasset.on('load', this._onRenderAssetLoad, this);\n\t\t\t\tasset.on('unload', this._onRenderAssetUnload, this);\n\t\t\t\tasset.on('remove', this._onRenderAssetRemove, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onRenderAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.enabled || !this.entity.enabled) return;\n\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindRenderAsset(asset) {\n\t\t\t\tasset.off('load', this._onRenderAssetLoad, this);\n\t\t\t\tasset.off('unload', this._onRenderAssetUnload, this);\n\t\t\t\tasset.off('remove', this._onRenderAssetRemove, this);\n\t\t\t\tthis._evtSetMeshes?.off();\n\t\t\t\tthis._evtSetMeshes = null;\n\t\t}\n\t\t_onRenderAssetLoad(asset) {\n\t\t\t\tthis._onRenderChanged(asset.resource);\n\t\t}\n\t\t_onRenderAssetUnload(asset) {\n\t\t\t\tthis._onRenderChanged(null);\n\t\t}\n\t\t_onRenderAssetRemove(asset) {\n\t\t\t\tthis._onRenderAssetUnload(asset);\n\t\t}\n\t\t_onRenderChanged(render) {\n\t\t\t\tif (!render) {\n\t\t\t\t\t\tthis._onMeshChanged(null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._evtSetMeshes?.off();\n\t\t\t\tthis._evtSetMeshes = render.on('set:meshes', this._onRenderSetMeshes, this);\n\t\t\t\tif (render.meshes) {\n\t\t\t\t\t\tthis._onRenderSetMeshes(render.meshes);\n\t\t\t\t}\n\t\t}\n\t\t_onRenderSetMeshes(meshes) {\n\t\t\t\tthis._onMeshChanged(meshes && meshes[0]);\n\t\t}\n\t\tonSetLoop(name, oldValue, newValue) {\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter[name] = newValue;\n\t\t\t\t\t\tthis.emitter.resetTime();\n\t\t\t\t}\n\t\t}\n\t\tonSetBlendType(name, oldValue, newValue) {\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter[name] = newValue;\n\t\t\t\t\t\tthis.emitter.material.blendType = newValue;\n\t\t\t\t\t\tthis.emitter.resetMaterial();\n\t\t\t\t\t\tthis.rebuild();\n\t\t\t\t}\n\t\t}\n\t\t_requestDepth() {\n\t\t\t\tif (this._requestedDepth) return;\n\t\t\t\tif (!depthLayer) depthLayer = this.system.app.scene.layers.getLayerById(LAYERID_DEPTH);\n\t\t\t\tif (depthLayer) {\n\t\t\t\t\t\tdepthLayer.incrementCounter();\n\t\t\t\t\t\tthis._requestedDepth = true;\n\t\t\t\t}\n\t\t}\n\t\t_releaseDepth() {\n\t\t\t\tif (!this._requestedDepth) return;\n\t\t\t\tif (depthLayer) {\n\t\t\t\t\t\tdepthLayer.decrementCounter();\n\t\t\t\t\t\tthis._requestedDepth = false;\n\t\t\t\t}\n\t\t}\n\t\tonSetDepthSoftening(name, oldValue, newValue) {\n\t\t\t\tif (oldValue !== newValue) {\n\t\t\t\t\t\tif (newValue) {\n\t\t\t\t\t\t\t\tif (this.enabled && this.entity.enabled) this._requestDepth();\n\t\t\t\t\t\t\t\tif (this.emitter) this.emitter[name] = newValue;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.enabled && this.entity.enabled) this._releaseDepth();\n\t\t\t\t\t\t\t\tif (this.emitter) this.emitter[name] = newValue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\t\t\tthis.reset();\n\t\t\t\t\t\t\t\tthis.emitter.resetMaterial();\n\t\t\t\t\t\t\t\tthis.rebuild();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonSetSimpleProperty(name, oldValue, newValue) {\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter[name] = newValue;\n\t\t\t\t\t\tthis.emitter.resetMaterial();\n\t\t\t\t}\n\t\t}\n\t\tonSetComplexProperty(name, oldValue, newValue) {\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter[name] = newValue;\n\t\t\t\t\t\tthis.emitter.resetMaterial();\n\t\t\t\t\t\tthis.rebuild();\n\t\t\t\t\t\tthis.reset();\n\t\t\t\t}\n\t\t}\n\t\tonSetGraphProperty(name, oldValue, newValue) {\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter[name] = newValue;\n\t\t\t\t\t\tthis.emitter.rebuildGraphs();\n\t\t\t\t\t\tthis.emitter.resetMaterial();\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tconst data = this.data;\n\t\t\t\tfor(let i = 0, len = ASSET_PROPERTIES.length; i < len; i++){\n\t\t\t\t\t\tlet asset = data[ASSET_PROPERTIES[i]];\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tif (!(asset instanceof Asset)) {\n\t\t\t\t\t\t\t\t\t\tconst id = parseInt(asset, 10);\n\t\t\t\t\t\t\t\t\t\tif (id >= 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tasset = this.system.app.assets.get(asset);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (asset && !asset.resource) {\n\t\t\t\t\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.system.app.graphicsDevice.disableParticleSystem) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.emitter) {\n\t\t\t\t\t\tlet mesh = data.mesh;\n\t\t\t\t\t\tif (!(mesh instanceof Mesh)) {\n\t\t\t\t\t\t\t\tmesh = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.emitter = new ParticleEmitter(this.system.app.graphicsDevice, {\n\t\t\t\t\t\t\t\tnumParticles: data.numParticles,\n\t\t\t\t\t\t\t\temitterExtents: data.emitterExtents,\n\t\t\t\t\t\t\t\temitterExtentsInner: data.emitterExtentsInner,\n\t\t\t\t\t\t\t\temitterRadius: data.emitterRadius,\n\t\t\t\t\t\t\t\temitterRadiusInner: data.emitterRadiusInner,\n\t\t\t\t\t\t\t\temitterShape: data.emitterShape,\n\t\t\t\t\t\t\t\tinitialVelocity: data.initialVelocity,\n\t\t\t\t\t\t\t\twrap: data.wrap,\n\t\t\t\t\t\t\t\tlocalSpace: data.localSpace,\n\t\t\t\t\t\t\t\tscreenSpace: data.screenSpace,\n\t\t\t\t\t\t\t\twrapBounds: data.wrapBounds,\n\t\t\t\t\t\t\t\tlifetime: data.lifetime,\n\t\t\t\t\t\t\t\trate: data.rate,\n\t\t\t\t\t\t\t\trate2: data.rate2,\n\t\t\t\t\t\t\t\torientation: data.orientation,\n\t\t\t\t\t\t\t\tparticleNormal: data.particleNormal,\n\t\t\t\t\t\t\t\tanimTilesX: data.animTilesX,\n\t\t\t\t\t\t\t\tanimTilesY: data.animTilesY,\n\t\t\t\t\t\t\t\tanimStartFrame: data.animStartFrame,\n\t\t\t\t\t\t\t\tanimNumFrames: data.animNumFrames,\n\t\t\t\t\t\t\t\tanimNumAnimations: data.animNumAnimations,\n\t\t\t\t\t\t\t\tanimIndex: data.animIndex,\n\t\t\t\t\t\t\t\trandomizeAnimIndex: data.randomizeAnimIndex,\n\t\t\t\t\t\t\t\tanimSpeed: data.animSpeed,\n\t\t\t\t\t\t\t\tanimLoop: data.animLoop,\n\t\t\t\t\t\t\t\tstartAngle: data.startAngle,\n\t\t\t\t\t\t\t\tstartAngle2: data.startAngle2,\n\t\t\t\t\t\t\t\tscaleGraph: data.scaleGraph,\n\t\t\t\t\t\t\t\tscaleGraph2: data.scaleGraph2,\n\t\t\t\t\t\t\t\tcolorGraph: data.colorGraph,\n\t\t\t\t\t\t\t\tcolorGraph2: data.colorGraph2,\n\t\t\t\t\t\t\t\talphaGraph: data.alphaGraph,\n\t\t\t\t\t\t\t\talphaGraph2: data.alphaGraph2,\n\t\t\t\t\t\t\t\tlocalVelocityGraph: data.localVelocityGraph,\n\t\t\t\t\t\t\t\tlocalVelocityGraph2: data.localVelocityGraph2,\n\t\t\t\t\t\t\t\tvelocityGraph: data.velocityGraph,\n\t\t\t\t\t\t\t\tvelocityGraph2: data.velocityGraph2,\n\t\t\t\t\t\t\t\trotationSpeedGraph: data.rotationSpeedGraph,\n\t\t\t\t\t\t\t\trotationSpeedGraph2: data.rotationSpeedGraph2,\n\t\t\t\t\t\t\t\tradialSpeedGraph: data.radialSpeedGraph,\n\t\t\t\t\t\t\t\tradialSpeedGraph2: data.radialSpeedGraph2,\n\t\t\t\t\t\t\t\tcolorMap: data.colorMap,\n\t\t\t\t\t\t\t\tnormalMap: data.normalMap,\n\t\t\t\t\t\t\t\tloop: data.loop,\n\t\t\t\t\t\t\t\tpreWarm: data.preWarm,\n\t\t\t\t\t\t\t\tsort: data.sort,\n\t\t\t\t\t\t\t\tstretch: data.stretch,\n\t\t\t\t\t\t\t\talignToMotion: data.alignToMotion,\n\t\t\t\t\t\t\t\tlighting: data.lighting,\n\t\t\t\t\t\t\t\thalfLambert: data.halfLambert,\n\t\t\t\t\t\t\t\tintensity: data.intensity,\n\t\t\t\t\t\t\t\tdepthSoftening: data.depthSoftening,\n\t\t\t\t\t\t\t\tscene: this.system.app.scene,\n\t\t\t\t\t\t\t\tmesh: mesh,\n\t\t\t\t\t\t\t\tdepthWrite: data.depthWrite,\n\t\t\t\t\t\t\t\tnoFog: data.noFog,\n\t\t\t\t\t\t\t\tnode: this.entity,\n\t\t\t\t\t\t\t\tblendType: data.blendType\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.emitter.meshInstance.node = this.entity;\n\t\t\t\t\t\tthis.emitter.drawOrder = this.drawOrder;\n\t\t\t\t\t\tif (!data.autoPlay) {\n\t\t\t\t\t\t\t\tthis.pause();\n\t\t\t\t\t\t\t\tthis.emitter.meshInstance.visible = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.emitter.colorMap) {\n\t\t\t\t\t\tthis.addMeshInstanceToLayers();\n\t\t\t\t}\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this.onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tif (this.enabled && this.entity.enabled && data.depthSoftening) {\n\t\t\t\t\t\tthis._requestDepth();\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.removeMeshInstanceFromLayers();\n\t\t\t\t\t\tif (this.data.depthSoftening) this._releaseDepth();\n\t\t\t\t\t\tthis.emitter.camera = null;\n\t\t\t\t}\n\t\t}\n\t\tonBeforeRemove() {\n\t\t\t\tif (this.enabled) {\n\t\t\t\t\t\tthis.enabled = false;\n\t\t\t\t}\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter.destroy();\n\t\t\t\t\t\tthis.emitter = null;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < ASSET_PROPERTIES.length; i++){\n\t\t\t\t\t\tconst prop = ASSET_PROPERTIES[i];\n\t\t\t\t\t\tif (this.data[prop]) {\n\t\t\t\t\t\t\t\tthis[prop] = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.off();\n\t\t}\n\t\treset() {\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter.reset();\n\t\t\t\t}\n\t\t}\n\t\tstop() {\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter.loop = false;\n\t\t\t\t\t\tthis.emitter.resetTime();\n\t\t\t\t\t\tthis.emitter.addTime(0, true);\n\t\t\t\t}\n\t\t}\n\t\tpause() {\n\t\t\t\tthis.data.paused = true;\n\t\t}\n\t\tunpause() {\n\t\t\t\tthis.data.paused = false;\n\t\t}\n\t\tplay() {\n\t\t\t\tthis.data.paused = false;\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter.meshInstance.visible = true;\n\t\t\t\t\t\tthis.emitter.loop = this.data.loop;\n\t\t\t\t\t\tthis.emitter.resetTime();\n\t\t\t\t}\n\t\t}\n\t\tisPlaying() {\n\t\t\t\tif (this.data.paused) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (this.emitter && this.emitter.loop) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn Date.now() <= this.emitter.endTime;\n\t\t}\n\t\tsetInTools() {\n\t\t\t\tconst { emitter } = this;\n\t\t\t\tif (emitter && !emitter.inTools) {\n\t\t\t\t\t\temitter.inTools = true;\n\t\t\t\t\t\tthis.rebuild();\n\t\t\t\t}\n\t\t}\n\t\trebuild() {\n\t\t\t\tconst enabled = this.enabled;\n\t\t\t\tthis.enabled = false;\n\t\t\t\tif (this.emitter) {\n\t\t\t\t\t\tthis.emitter.rebuild();\n\t\t\t\t}\n\t\t\t\tthis.enabled = enabled;\n\t\t}\n}\n\nclass ParticleSystemComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.numParticles = 1;\n\t\t\t\tthis.rate = 1;\n\t\t\t\tthis.rate2 = null;\n\t\t\t\tthis.startAngle = 0;\n\t\t\t\tthis.startAngle2 = null;\n\t\t\t\tthis.lifetime = 50;\n\t\t\t\tthis.emitterExtents = new Vec3();\n\t\t\t\tthis.emitterExtentsInner = new Vec3();\n\t\t\t\tthis.emitterRadius = 0;\n\t\t\t\tthis.emitterRadiusInner = 0;\n\t\t\t\tthis.emitterShape = EMITTERSHAPE_BOX;\n\t\t\t\tthis.initialVelocity = 0;\n\t\t\t\tthis.wrap = false;\n\t\t\t\tthis.wrapBounds = new Vec3();\n\t\t\t\tthis.localSpace = false;\n\t\t\t\tthis.screenSpace = false;\n\t\t\t\tthis.colorMap = null;\n\t\t\t\tthis.colorMapAsset = null;\n\t\t\t\tthis.normalMap = null;\n\t\t\t\tthis.normalMapAsset = null;\n\t\t\t\tthis.loop = true;\n\t\t\t\tthis.preWarm = false;\n\t\t\t\tthis.sort = 0;\n\t\t\t\tthis.mode = PARTICLEMODE_GPU;\n\t\t\t\tthis.scene = null;\n\t\t\t\tthis.lighting = false;\n\t\t\t\tthis.halfLambert = false;\n\t\t\t\tthis.intensity = 1;\n\t\t\t\tthis.stretch = 0.0;\n\t\t\t\tthis.alignToMotion = false;\n\t\t\t\tthis.depthSoftening = 0;\n\t\t\t\tthis.renderAsset = null;\n\t\t\t\tthis.meshAsset = null;\n\t\t\t\tthis.mesh = null;\n\t\t\t\tthis.depthWrite = false;\n\t\t\t\tthis.noFog = false;\n\t\t\t\tthis.orientation = PARTICLEORIENTATION_SCREEN;\n\t\t\t\tthis.particleNormal = new Vec3(0, 1, 0);\n\t\t\t\tthis.animTilesX = 1;\n\t\t\t\tthis.animTilesY = 1;\n\t\t\t\tthis.animStartFrame = 0;\n\t\t\t\tthis.animNumFrames = 1;\n\t\t\t\tthis.animNumAnimations = 1;\n\t\t\t\tthis.animIndex = 0;\n\t\t\t\tthis.randomizeAnimIndex = false;\n\t\t\t\tthis.animSpeed = 1;\n\t\t\t\tthis.animLoop = true;\n\t\t\t\tthis.scaleGraph = null;\n\t\t\t\tthis.scaleGraph2 = null;\n\t\t\t\tthis.colorGraph = null;\n\t\t\t\tthis.colorGraph2 = null;\n\t\t\t\tthis.alphaGraph = null;\n\t\t\t\tthis.alphaGraph2 = null;\n\t\t\t\tthis.localVelocityGraph = null;\n\t\t\t\tthis.localVelocityGraph2 = null;\n\t\t\t\tthis.velocityGraph = null;\n\t\t\t\tthis.velocityGraph2 = null;\n\t\t\t\tthis.rotationSpeedGraph = null;\n\t\t\t\tthis.rotationSpeedGraph2 = null;\n\t\t\t\tthis.radialSpeedGraph = null;\n\t\t\t\tthis.radialSpeedGraph2 = null;\n\t\t\t\tthis.blendType = BLEND_NORMAL;\n\t\t\t\tthis.enabled = true;\n\t\t\t\tthis.paused = false;\n\t\t\t\tthis.autoPlay = true;\n\t\t\t\tthis.layers = [\n\t\t\t\t\t\tLAYERID_WORLD\n\t\t\t\t];\n\t\t}\n}\n\nvar particlePS$1 = `\nvarying vec4 texCoordsAlphaLife;\nuniform sampler2D colorMap;\nuniform sampler2D colorParam;\nuniform float graphSampleSize;\nuniform float graphNumSamples;\n#ifndef CAMERAPLANES\n\t#define CAMERAPLANES\n\tuniform vec4 camera_params;\n#endif\nuniform float softening;\nuniform float colorMult;\nfloat saturate(float x) {\n\treturn clamp(x, 0.0, 1.0);\n}\nvoid main(void) {\n\tvec4 tex  = texture2D(colorMap, vec2(texCoordsAlphaLife.x, 1.0 - texCoordsAlphaLife.y));\n\tvec4 ramp = texture2D(colorParam, vec2(texCoordsAlphaLife.w, 0.0));\n\tramp.rgb *= colorMult;\n\tramp.a += texCoordsAlphaLife.z;\n\tvec3 rgb = tex.rgb * ramp.rgb;\n\tfloat a  = tex.a * ramp.a;\n`;\n\nvar particleVS$1 = `\nvec3 unpack3NFloats(float src) {\n\tfloat r = fract(src);\n\tfloat g = fract(src * 256.0);\n\tfloat b = fract(src * 65536.0);\n\treturn vec3(r, g, b);\n}\nfloat saturate(float x) {\n\treturn clamp(x, 0.0, 1.0);\n}\nvec4 tex1Dlod_lerp(TEXTURE_ACCEPT_HIGHP(tex), vec2 tc) {\n\treturn mix( texture2D(tex,tc), texture2D(tex,tc + graphSampleSize), fract(tc.x*graphNumSamples) );\n}\nvec4 tex1Dlod_lerp(TEXTURE_ACCEPT_HIGHP(tex), vec2 tc, out vec3 w) {\n\tvec4 a = texture2D(tex,tc);\n\tvec4 b = texture2D(tex,tc + graphSampleSize);\n\tfloat c = fract(tc.x*graphNumSamples);\n\tvec3 unpackedA = unpack3NFloats(a.w);\n\tvec3 unpackedB = unpack3NFloats(b.w);\n\tw = mix(unpackedA, unpackedB, c);\n\treturn mix(a, b, c);\n}\nvec2 rotate(vec2 quadXY, float pRotation, out mat2 rotMatrix) {\n\tfloat c = cos(pRotation);\n\tfloat s = sin(pRotation);\n\tmat2 m = mat2(c, -s, s, c);\n\trotMatrix = m;\n\treturn m * quadXY;\n}\nvec3 billboard(vec3 InstanceCoords, vec2 quadXY) {\n\t#ifdef SCREEN_SPACE\n\t\tvec3 pos = vec3(-1, 0, 0) * quadXY.x + vec3(0, -1, 0) * quadXY.y;\n\t#else\n\t\tvec3 pos = -matrix_viewInverse[0].xyz * quadXY.x + -matrix_viewInverse[1].xyz * quadXY.y;\n\t#endif\n\treturn pos;\n}\nvec3 customFace(vec3 InstanceCoords, vec2 quadXY) {\n\tvec3 pos = faceTangent * quadXY.x + faceBinorm * quadXY.y;\n\treturn pos;\n}\nvec2 safeNormalize(vec2 v) {\n\tfloat l = length(v);\n\treturn (l > 1e-06) ? v / l : v;\n}\nvoid main(void) {\n\tvec3 meshLocalPos = particle_vertexData.xyz;\n\tfloat id = floor(particle_vertexData.w);\n\tfloat rndFactor = fract(sin(id + 1.0 + seed));\n\tvec3 rndFactor3 = vec3(rndFactor, fract(rndFactor*10.0), fract(rndFactor*100.0));\n\tfloat uv = id / numParticlesPot;\n\treadInput(uv);\n#ifdef LOCAL_SPACE\n\tinVel = mat3(matrix_model) * inVel;\n#endif\n\tvec2 velocityV = safeNormalize((mat3(matrix_view) * inVel).xy);\n\tfloat particleLifetime = lifetime;\n\tif (inLife <= 0.0 || inLife > particleLifetime || !inShow)\n\t\tmeshLocalPos = vec3(0.0);\n\tvec2 quadXY = meshLocalPos.xy;\n\tfloat nlife = clamp(inLife / particleLifetime, 0.0, 1.0);\n\tvec3 paramDiv;\n\tvec4 params = tex1Dlod_lerp(TEXTURE_PASS(internalTex2), vec2(nlife, 0), paramDiv);\n\tfloat scale = params.y;\n\tfloat scaleDiv = paramDiv.x;\n\tfloat alphaDiv = paramDiv.z;\n\tscale += (scaleDiv * 2.0 - 1.0) * scaleDivMult * fract(rndFactor*10000.0);\n#ifndef USE_MESH\n\ttexCoordsAlphaLife = vec4(quadXY * -0.5 + 0.5, (alphaDiv * 2.0 - 1.0) * alphaDivMult * fract(rndFactor*1000.0), nlife);\n#else\n\ttexCoordsAlphaLife = vec4(particle_uv, (alphaDiv * 2.0 - 1.0) * alphaDivMult * fract(rndFactor*1000.0), nlife);\n#endif\n\tvec3 particlePos = inPos;\n\tvec3 particlePosMoved = vec3(0.0);\n\tmat2 rotMatrix;\n`;\n\nvar particleAnimFrameClampVS$1 = `\n\tfloat animFrame = min(floor(texCoordsAlphaLife.w * animTexParams.y) + animTexParams.x, animTexParams.z);\n`;\n\nvar particleAnimFrameLoopVS$1 = `\n\tfloat animFrame = floor(mod(texCoordsAlphaLife.w * animTexParams.y + animTexParams.x, animTexParams.z + 1.0));\n`;\n\nvar particleAnimTexVS$1 = `\n\tfloat animationIndex;\n\tif (animTexIndexParams.y == 1.0) {\n\t\tanimationIndex = floor((animTexParams.w + 1.0) * rndFactor3.z) * (animTexParams.z + 1.0);\n\t} else {\n\t\tanimationIndex = animTexIndexParams.x * (animTexParams.z + 1.0);\n\t}\n\tfloat atlasX = (animationIndex + animFrame) * animTexTilesParams.x;\n\tfloat atlasY = 1.0 - floor(atlasX + 1.0) * animTexTilesParams.y;\n\tatlasX = fract(atlasX);\n\ttexCoordsAlphaLife.xy *= animTexTilesParams.xy;\n\ttexCoordsAlphaLife.xy += vec2(atlasX, atlasY);\n`;\n\nvar particleInputFloatPS$1 = `\nvoid readInput(float uv) {\n\tvec4 tex = texture2D(particleTexIN, vec2(uv, 0.25));\n\tvec4 tex2 = texture2D(particleTexIN, vec2(uv, 0.75));\n\tinPos = tex.xyz;\n\tinVel = tex2.xyz;\n\tinAngle = (tex.w < 0.0? -tex.w : tex.w) - 1000.0;\n\tinShow = tex.w >= 0.0;\n\tinLife = tex2.w;\n}\n`;\n\nvar particleInputRgba8PS$1 = `\n#define PI2 6.283185307179586\nuniform vec3 inBoundsSize;\nuniform vec3 inBoundsCenter;\nuniform float maxVel;\nfloat decodeFloatRG(vec2 rg) {\n\treturn rg.y * (1.0 / 255.0) + rg.x;\n}\nfloat decodeFloatRGBA( vec4 rgba ) {\n\treturn dot(rgba, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0));\n}\nvoid readInput(float uv) {\n\tvec4 tex0 = texture2D(particleTexIN, vec2(uv, 0.125));\n\tvec4 tex1 = texture2D(particleTexIN, vec2(uv, 0.375));\n\tvec4 tex2 = texture2D(particleTexIN, vec2(uv, 0.625));\n\tvec4 tex3 = texture2D(particleTexIN, vec2(uv, 0.875));\n\tinPos = vec3(decodeFloatRG(tex0.rg), decodeFloatRG(tex0.ba), decodeFloatRG(tex1.rg));\n\tinPos = (inPos - vec3(0.5)) * inBoundsSize + inBoundsCenter;\n\tinVel = tex2.xyz;\n\tinVel = (inVel - vec3(0.5)) * maxVel;\n\tinAngle = decodeFloatRG(tex1.ba) * PI2;\n\tinShow = tex2.a > 0.5;\n\tinLife = decodeFloatRGBA(tex3);\n\tfloat maxNegLife = max(lifetime, (numParticles - 1.0) * (rate+rateDiv));\n\tfloat maxPosLife = lifetime+1.0;\n\tinLife = inLife * (maxNegLife + maxPosLife) - maxNegLife;\n}\n`;\n\nvar particleOutputFloatPS$1 = `\nvoid writeOutput() {\n\tif (gl_FragCoord.y<1.0) {\n\t\tgl_FragColor = vec4(outPos, (outAngle + 1000.0) * visMode);\n\t} else {\n\t\tgl_FragColor = vec4(outVel, outLife);\n\t}\n}\n`;\n\nvar particleOutputRgba8PS$1 = `\nuniform vec3 outBoundsMul;\nuniform vec3 outBoundsAdd;\nvec2 encodeFloatRG( float v ) {\n\tvec2 enc = vec2(1.0, 255.0) * v;\n\tenc = fract(enc);\n\tenc -= enc.yy * vec2(1.0/255.0, 1.0/255.0);\n\treturn enc;\n}\nvec4 encodeFloatRGBA( float v ) {\n\tvec4 enc = vec4(1.0, 255.0, 65025.0, 160581375.0) * v;\n\tenc = fract(enc);\n\tenc -= enc.yzww * vec4(1.0/255.0,1.0/255.0,1.0/255.0,0.0);\n\treturn enc;\n}\nvoid writeOutput() {\n\toutPos = outPos * outBoundsMul + outBoundsAdd;\n\toutAngle = fract(outAngle / PI2);\n\toutVel = (outVel / maxVel) + vec3(0.5);\n\tfloat maxNegLife = max(lifetime, (numParticles - 1.0) * (rate+rateDiv));\n\tfloat maxPosLife = lifetime+1.0;\n\toutLife = (outLife + maxNegLife) / (maxNegLife + maxPosLife);\n\tif (gl_FragCoord.y < 1.0) {\n\t\tgl_FragColor = vec4(encodeFloatRG(outPos.x), encodeFloatRG(outPos.y));\n\t} else if (gl_FragCoord.y < 2.0) {\n\t\tgl_FragColor = vec4(encodeFloatRG(outPos.z), encodeFloatRG(outAngle));\n\t} else if (gl_FragCoord.y < 3.0) {\n\t\tgl_FragColor = vec4(outVel, visMode*0.5+0.5);\n\t} else {\n\t\tgl_FragColor = encodeFloatRGBA(outLife);\n\t}\n}\n`;\n\nvar particleUpdaterAABBPS$1 = `\nuniform mat3 spawnBounds;\nuniform vec3 spawnPosInnerRatio;\nvec3 calcSpawnPosition(vec3 inBounds, float rndFactor) {\n\tvec3 pos = inBounds - vec3(0.5);\n\tvec3 posAbs = abs(pos);\n\tvec3 maxPos = vec3(max(posAbs.x, max(posAbs.y, posAbs.z)));\n\tvec3 edge = maxPos + (vec3(0.5) - maxPos) * spawnPosInnerRatio;\n\tpos.x = edge.x * (maxPos.x == posAbs.x ? sign(pos.x) : 2.0 * pos.x);\n\tpos.y = edge.y * (maxPos.y == posAbs.y ? sign(pos.y) : 2.0 * pos.y);\n\tpos.z = edge.z * (maxPos.z == posAbs.z ? sign(pos.z) : 2.0 * pos.z);\n#ifndef LOCAL_SPACE\n\treturn emitterPos + spawnBounds * pos;\n#else\n\treturn spawnBounds * pos;\n#endif\n}\nvoid addInitialVelocity(inout vec3 localVelocity, vec3 inBounds) {\n\tlocalVelocity -= vec3(0, 0, initialVelocity);\n}\n`;\n\nvar particleUpdaterEndPS$1 = `\n\twriteOutput();\n}\n`;\n\nvar particleUpdaterInitPS$1 = `\nvarying vec2 vUv0;\nuniform highp sampler2D particleTexIN;\nuniform highp sampler2D internalTex0;\nuniform highp sampler2D internalTex1;\nuniform highp sampler2D internalTex2;\nuniform highp sampler2D internalTex3;\nuniform mat3 emitterMatrix;\nuniform mat3 emitterMatrixInv;\nuniform vec3 emitterScale;\nuniform vec3 emitterPos;\nuniform vec3 frameRandom;\nuniform vec3 localVelocityDivMult;\nuniform vec3 velocityDivMult;\nuniform float delta;\nuniform float rate;\nuniform float rateDiv;\nuniform float lifetime;\nuniform float numParticles;\nuniform float rotSpeedDivMult;\nuniform float radialSpeedDivMult;\nuniform float seed;\nuniform float startAngle;\nuniform float startAngle2;\nuniform float initialVelocity;\nuniform float graphSampleSize;\nuniform float graphNumSamples;\nvec3 inPos;\nvec3 inVel;\nfloat inAngle;\nbool inShow;\nfloat inLife;\nfloat visMode;\nvec3 outPos;\nvec3 outVel;\nfloat outAngle;\nbool outShow;\nfloat outLife;\n`;\n\nvar particleUpdaterNoRespawnPS$1 = `\n\tif (outLife >= lifetime) {\n\t\toutLife -= max(lifetime, (numParticles - 1.0) * particleRate);\n\t\tvisMode = -1.0;\n\t}\n`;\n\nvar particleUpdaterOnStopPS$1 = `\n\tvisMode = outLife < 0.0? -1.0: visMode;\n`;\n\nvar particleUpdaterRespawnPS$1 = `\n\tif (outLife >= lifetime) {\n\t\toutLife -= max(lifetime, (numParticles - 1.0) * particleRate);\n\t\tvisMode = 1.0;\n\t}\n\tvisMode = outLife < 0.0? 1.0: visMode;\n`;\n\nvar particleUpdaterSpherePS$1 = `\nuniform float spawnBoundsSphere;\nuniform float spawnBoundsSphereInnerRatio;\nvec3 calcSpawnPosition(vec3 inBounds, float rndFactor) {\n\tfloat rnd4 = fract(rndFactor * 1000.0);\n\tvec3 norm = normalize(inBounds.xyz - vec3(0.5));\n\tfloat r = rnd4 * (1.0 - spawnBoundsSphereInnerRatio) + spawnBoundsSphereInnerRatio;\n#ifndef LOCAL_SPACE\n\treturn emitterPos + norm * r * spawnBoundsSphere;\n#else\n\treturn norm * r * spawnBoundsSphere;\n#endif\n}\nvoid addInitialVelocity(inout vec3 localVelocity, vec3 inBounds) {\n\tlocalVelocity += normalize(inBounds - vec3(0.5)) * initialVelocity;\n}\n`;\n\nvar particleUpdaterStartPS$1 = `\nfloat saturate(float x) {\n\treturn clamp(x, 0.0, 1.0);\n}\nvec3 unpack3NFloats(float src) {\n\tfloat r = fract(src);\n\tfloat g = fract(src * 256.0);\n\tfloat b = fract(src * 65536.0);\n\treturn vec3(r, g, b);\n}\nvec3 tex1Dlod_lerp(TEXTURE_ACCEPT_HIGHP(tex), vec2 tc, out vec3 w) {\n\tvec4 a = texture2D(tex, tc);\n\tvec4 b = texture2D(tex, tc + graphSampleSize);\n\tfloat c = fract(tc.x * graphNumSamples);\n\tvec3 unpackedA = unpack3NFloats(a.w);\n\tvec3 unpackedB = unpack3NFloats(b.w);\n\tw = mix(unpackedA, unpackedB, c);\n\treturn mix(a.xyz, b.xyz, c);\n}\n#define HASHSCALE4 vec4(1031, .1030, .0973, .1099)\nvec4 hash41(float p) {\n\tvec4 p4 = fract(vec4(p) * HASHSCALE4);\n\tp4 += dot(p4, p4.wzxy+19.19);\n\treturn fract(vec4((p4.x + p4.y)*p4.z, (p4.x + p4.z)*p4.y, (p4.y + p4.z)*p4.w, (p4.z + p4.w)*p4.x));\n}\nvoid main(void) {\n\tif (gl_FragCoord.x > numParticles) discard;\n\treadInput(vUv0.x);\n\tvisMode = inShow? 1.0 : -1.0;\n\tvec4 rndFactor = hash41(gl_FragCoord.x + seed);\n\tfloat particleRate = rate + rateDiv * rndFactor.x;\n\toutLife = inLife + delta;\n\tfloat nlife = clamp(outLife / lifetime, 0.0, 1.0);\n\tvec3 localVelocityDiv;\n\tvec3 velocityDiv;\n\tvec3 paramDiv;\n\tvec3 localVelocity = tex1Dlod_lerp(TEXTURE_PASS(internalTex0), vec2(nlife, 0), localVelocityDiv);\n\tvec3 velocity =\t  tex1Dlod_lerp(TEXTURE_PASS(internalTex1), vec2(nlife, 0), velocityDiv);\n\tvec3 params =\t\ttex1Dlod_lerp(TEXTURE_PASS(internalTex2), vec2(nlife, 0), paramDiv);\n\tfloat rotSpeed = params.x;\n\tfloat rotSpeedDiv = paramDiv.y;\n\tvec3 radialParams = tex1Dlod_lerp(TEXTURE_PASS(internalTex3), vec2(nlife, 0), paramDiv);\n\tfloat radialSpeed = radialParams.x;\n\tfloat radialSpeedDiv = radialParams.y;\n\tbool respawn = inLife <= 0.0 || outLife >= lifetime;\n\tinPos = respawn ? calcSpawnPosition(rndFactor.xyz, rndFactor.x) : inPos;\n\tinAngle = respawn ? mix(startAngle, startAngle2, rndFactor.x) : inAngle;\n#ifndef LOCAL_SPACE\n\tvec3 radialVel = inPos - emitterPos;\n#else\n\tvec3 radialVel = inPos;\n#endif\n\tradialVel = (dot(radialVel, radialVel) > 1.0E-8) ? radialSpeed * normalize(radialVel) : vec3(0.0);\n\tradialVel += (radialSpeedDiv * vec3(2.0) - vec3(1.0)) * radialSpeedDivMult * rndFactor.xyz;\n\tlocalVelocity +=\t(localVelocityDiv * vec3(2.0) - vec3(1.0)) * localVelocityDivMult * rndFactor.xyz;\n\tvelocity +=\t\t (velocityDiv * vec3(2.0) - vec3(1.0)) * velocityDivMult * rndFactor.xyz;\n\trotSpeed +=\t\t (rotSpeedDiv * 2.0 - 1.0) * rotSpeedDivMult * rndFactor.y;\n\taddInitialVelocity(localVelocity, rndFactor.xyz);\n#ifndef LOCAL_SPACE\n\toutVel = emitterMatrix * localVelocity + (radialVel + velocity) * emitterScale;\n#else\n\toutVel = (localVelocity + radialVel) / emitterScale + emitterMatrixInv * velocity;\n#endif\n\toutPos = inPos + outVel * delta;\n\toutAngle = inAngle + rotSpeed * delta;\n`;\n\nvar particle_billboardVS$1 = `\n\tquadXY = rotate(quadXY, inAngle, rotMatrix);\n\tvec3 localPos = billboard(particlePos, quadXY);\n`;\n\nvar particle_blendAddPS$1 = `\n\tdBlendModeFogFactor = 0.0;\n\trgb *= saturate(gammaCorrectInput(max(a, 0.0)));\n\tif ((rgb.r + rgb.g + rgb.b) < 0.000001) discard;\n`;\n\nvar particle_blendMultiplyPS$1 = `\n\trgb = mix(vec3(1.0), rgb, vec3(a));\n\tif (rgb.r + rgb.g + rgb.b > 2.99) discard;\n`;\n\nvar particle_blendNormalPS$1 = `\n\tif (a < 0.01) discard;\n`;\n\nvar particle_cpuVS$1 = `\nattribute vec4 particle_vertexData;\nattribute vec4 particle_vertexData2;\nattribute vec4 particle_vertexData3;\nattribute float particle_vertexData4;\n#ifndef USE_MESH\n\tattribute vec2 particle_vertexData5;\n#else\n\tattribute vec4 particle_vertexData5;\n#endif\nuniform mat4 matrix_viewProjection;\nuniform mat4 matrix_model;\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform mat4 matrix_view;\n#endif\nuniform mat3 matrix_normal;\nuniform mat4 matrix_viewInverse;\nuniform float numParticles;\nuniform float lifetime;\nuniform float stretch;\nuniform float seed;\nuniform vec3 wrapBounds;\nuniform vec3 emitterScale;\nuniform vec3 faceTangent;\nuniform vec3 faceBinorm;\n#ifdef PARTICLE_GPU\n\tuniform highp sampler2D internalTex0;\n\tuniform highp sampler2D internalTex1;\n\tuniform highp sampler2D internalTex2;\n#endif\nuniform vec3 emitterPos;\nvarying vec4 texCoordsAlphaLife;\nvec2 rotate(vec2 quadXY, float pRotation, out mat2 rotMatrix)\n{\n\tfloat c = cos(pRotation);\n\tfloat s = sin(pRotation);\n\tmat2 m = mat2(c, -s, s, c);\n\trotMatrix = m;\n\treturn m * quadXY;\n}\nvec3 billboard(vec3 InstanceCoords, vec2 quadXY)\n{\n\tvec3 pos = -matrix_viewInverse[0].xyz * quadXY.x + -matrix_viewInverse[1].xyz * quadXY.y;\n\treturn pos;\n}\nvec3 customFace(vec3 InstanceCoords, vec2 quadXY)\n{\n\tvec3 pos = faceTangent * quadXY.x + faceBinorm * quadXY.y;\n\treturn pos;\n}\nvoid main(void)\n{\n\tvec3 particlePos = particle_vertexData.xyz;\n\tvec3 inPos = particlePos;\n\tvec3 vertPos = particle_vertexData3.xyz;\n\tvec3 inVel = vec3(particle_vertexData2.w, particle_vertexData3.w, particle_vertexData5.x);\n\tfloat id = floor(particle_vertexData4);\n\tfloat rndFactor = fract(sin(id + 1.0 + seed));\n\tvec3 rndFactor3 = vec3(rndFactor, fract(rndFactor*10.0), fract(rndFactor*100.0));\n#ifdef LOCAL_SPACE\n\tinVel = mat3(matrix_model) * inVel;\n#endif\n\tvec2 velocityV = normalize((mat3(matrix_view) * inVel).xy);\n\tvec2 quadXY = vertPos.xy;\n#ifdef USE_MESH\n\ttexCoordsAlphaLife = vec4(particle_vertexData5.zw, particle_vertexData2.z, particle_vertexData.w);\n#else\n\ttexCoordsAlphaLife = vec4(quadXY * -0.5 + 0.5, particle_vertexData2.z, particle_vertexData.w);\n#endif\n\tmat2 rotMatrix;\n\tfloat inAngle = particle_vertexData2.x;\n\tvec3 particlePosMoved = vec3(0.0);\n\tvec3 meshLocalPos = particle_vertexData3.xyz;\n`;\n\nvar particle_cpu_endVS$1 = `\n\tlocalPos *= particle_vertexData2.y * emitterScale;\n\tlocalPos += particlePos;\n\tgl_Position = matrix_viewProjection * vec4(localPos, 1.0);\n`;\n\nvar particle_customFaceVS$1 = `\n\tquadXY = rotate(quadXY, inAngle, rotMatrix);\n\tvec3 localPos = customFace(particlePos, quadXY);\n`;\n\nvar particle_endPS$1 = `\n\trgb = addFog(rgb);\n\trgb = toneMap(rgb);\n\trgb = gammaCorrectOutput(rgb);\n\tgl_FragColor = vec4(rgb, a);\n}\n`;\n\nvar particle_endVS$1 = `\n\tlocalPos *= scale * emitterScale;\n\tlocalPos += particlePos;\n\t#ifdef SCREEN_SPACE\n\tgl_Position = vec4(localPos.x, localPos.y, 0.0, 1.0);\n\t#else\n\tgl_Position = matrix_viewProjection * vec4(localPos.xyz, 1.0);\n\t#endif\n`;\n\nvar particle_halflambertPS$1 = `\n\tvec3 negNormal = normal * 0.5 + 0.5;\n\tvec3 posNormal = -normal * 0.5 + 0.5;\n\tnegNormal *= negNormal;\n\tposNormal *= posNormal;\n`;\n\nvar particle_initVS$1 = `\nattribute vec4 particle_vertexData;\n#if defined(USE_MESH)\n\t#if defined(USE_MESH_UV)\n\t\tattribute vec2 particle_uv;\n\t#else\n\t\tvec2 particle_uv = vec2(0.0, 0.0);\n\t#endif\n#endif\nuniform mat4 matrix_viewProjection;\nuniform mat4 matrix_model;\nuniform mat3 matrix_normal;\nuniform mat4 matrix_viewInverse;\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform mat4 matrix_view;\n#endif\nuniform float numParticles;\nuniform float numParticlesPot;\nuniform float graphSampleSize;\nuniform float graphNumSamples;\nuniform float stretch;\nuniform vec3 wrapBounds;\nuniform vec3 emitterScale;\nuniform vec3 emitterPos;\nuniform vec3 faceTangent;\nuniform vec3 faceBinorm;\nuniform float rate;\nuniform float rateDiv;\nuniform float lifetime;\nuniform float deltaRandomnessStatic;\nuniform float scaleDivMult;\nuniform float alphaDivMult;\nuniform float seed;\nuniform float delta;\nuniform sampler2D particleTexOUT;\nuniform sampler2D particleTexIN;\n#ifdef PARTICLE_GPU\n\tuniform highp sampler2D internalTex0;\n\tuniform highp sampler2D internalTex1;\n\tuniform highp sampler2D internalTex2;\n#endif\n#ifndef CAMERAPLANES\n\t#define CAMERAPLANES\n\tuniform vec4 camera_params;\n#endif\nvarying vec4 texCoordsAlphaLife;\nvec3 inPos;\nvec3 inVel;\nfloat inAngle;\nbool inShow;\nfloat inLife;\n`;\n\nvar particle_lambertPS$1 = `\n\tvec3 negNormal = max(normal, vec3(0.0));\n\tvec3 posNormal = max(-normal, vec3(0.0));\n`;\n\nvar particle_lightingPS$1 = `\n\tvec3 light = negNormal.x*lightCube[0] + posNormal.x*lightCube[1] +\n\t\t\t\t\t\tnegNormal.y*lightCube[2] + posNormal.y*lightCube[3] +\n\t\t\t\t\t\tnegNormal.z*lightCube[4] + posNormal.z*lightCube[5];\n\trgb *= light;\n`;\n\nvar particle_localShiftVS$1 = `\n\tparticlePos = (matrix_model * vec4(particlePos, 1.0)).xyz;\n`;\n\nvar particle_meshVS$1 = `\n\tvec3 localPos = meshLocalPos;\n\tlocalPos.xy = rotate(localPos.xy, inAngle, rotMatrix);\n\tlocalPos.yz = rotate(localPos.yz, inAngle, rotMatrix);\n\tbillboard(particlePos, quadXY);\n`;\n\nvar particle_normalVS$1 = `\n\tNormal = normalize(localPos + matrix_viewInverse[2].xyz);\n`;\n\nvar particle_normalMapPS$1 = `\n\tvec3 normalMap = normalize(texture2D(normalMap, vec2(texCoordsAlphaLife.x, 1.0 - texCoordsAlphaLife.y)).xyz * 2.0 - 1.0);\n\tvec3 normal = ParticleMat * normalMap;\n`;\n\nvar particle_pointAlongVS$1 = `\n\tinAngle = atan(velocityV.x, velocityV.y);\n`;\n\nvar particle_simulationPS$1 = `\n\t#include \"particleUpdaterInitPS\"\n\t#ifdef PACK8\n\t\t#include \"particleInputRgba8PS\"\n\t\t#include \"particleOutputRgba8PS\"\n\t#else\n\t\t#include \"particleInputFloatPS\"\n\t\t#include \"particleOutputFloatPS\"\n\t#endif\n\t#ifdef EMITTERSHAPE_BOX\n\t\t#include \"particleUpdaterAABBPS\"\n\t#else\n\t\t#include \"particleUpdaterSpherePS\"\n\t#endif\n\t#include \"particleUpdaterStartPS\"\n\t#ifdef RESPAWN\n\t\t#include \"particleUpdaterRespawnPS\"\n\t#endif\n\t#ifdef NO_RESPAWN\n\t\t#include \"particleUpdaterNoRespawnPS\"\n\t#endif\n\t#ifdef ON_STOP\n\t\t#include \"particleUpdaterOnStopPS\"\n\t#endif\n\t#include \"particleUpdaterEndPS\"\n`;\n\nvar particle_shaderPS$1 = `\n\t#if NORMAL != NONE\n\t\t#if NORMAL == VERTEX\n\t\t\tvarying vec3 Normal;\n\t\t#endif\n\t\t#if NORMAL == MAP\n\t\t\tvarying mat3 ParticleMat;\n\t\t#endif\n\t\tuniform vec3 lightCube[6];\n\t#endif\n\t#ifdef SOFT\n\t\tvarying float vDepth;\n\t\t#include \"screenDepthPS\"\n\t#endif\n\t#include \"gammaPS\"\n\t#include \"tonemappingPS\"\n\t#include \"fogPS\"\n\t#if NORMAL == MAP\n\t\tuniform sampler2D normalMap;\n\t#endif\n\t#include \"particlePS\"\n\t#ifdef SOFT\n\t\t#include \"particle_softPS\"\n\t#endif\n\t#if NORMAL == VERTEX\n\t\tvec3 normal = Normal;\n\t#endif\n\t#if NORMAL == MAP\n\t\t#include \"particle_normalMapPS\"\n\t#endif\n\t#if NORMAL != NONE\n\t\t#ifdef HALF_LAMBERT\n\t\t\t#include \"particle_halflambertPS\"\n\t\t#else\n\t\t\t#include \"particle_lambertPS\"\n\t\t#endif\n\t\t#include \"particle_lightingPS\"\n\t#endif\n\t#if BLEND == NORMAL\n\t\t#include \"particle_blendNormalPS\"\n\t#elif BLEND == ADDITIVE\n\t\t#include \"particle_blendAddPS\"\n\t#elif BLEND == MULTIPLICATIVE\n\t\t#include \"particle_blendMultiplyPS\"\n\t#endif\n\t#include \"particle_endPS\"\n`;\n\nvar particle_shaderVS$1 = `\n\t#ifdef ANIMTEX\n\t\tuniform vec2 animTexTilesParams;\n\t\tuniform vec4 animTexParams;\n\t\tuniform vec2 animTexIndexParams;\n\t#endif\n\t#if NORMAL == MAP\n\t\tvarying mat3 ParticleMat;\n\t#endif\n\t#if NORMAL == VERTEX\n\t\tvarying vec3 Normal;\n\t#endif\n\t#ifdef SOFT\n\t\tvarying float vDepth;\n\t#endif\n\t#ifdef PARTICLE_GPU\n\t\t#include \"particle_initVS\"\n\t\t#ifdef PACK8\n\t\t\t#include \"particleInputRgba8PS\"\n\t\t#else\n\t\t\t#include  \"particleInputFloatPS\"\n\t\t#endif\n\t\t#ifdef SOFT\n\t\t\t#include \"screenDepthPS\"\n\t\t#endif\n\t\t#include \"particleVS\"\n\t#else\n\t\t#ifdef SOFT\n\t\t\t#include \"screenDepthPS\"\n\t\t#endif\n\t\t#include \"particle_cpuVS\"\n\t#endif\n\t#ifdef LOCAL_SPACE\n\t\t#include \"particle_localShiftVS\"\n\t#endif\n\t#ifdef ANIMTEX\n\t\t#ifdef ANIMTEX_LOOP\n\t\t\t#include \"particleAnimFrameLoopVS\"\n\t\t#else\n\t\t\t#include \"particleAnimFrameClampVS\"\n\t\t#endif\n\t\t#include \"particleAnimTexVS\"\n\t#endif\n\t#ifdef PARTICLE_GPU\n\t\t#ifdef WRAP\n\t\t\t#include \"particle_wrapVS\"\n\t\t#endif\n\t#endif\n\t#ifdef ALIGN_TO_MOTION\n\t\t#include \"particle_pointAlongVS\"\n\t#endif\n\t#ifdef USE_MESH\n\t\t#include \"particle_meshVS\"\n\t#else\n\t\t#ifdef CUSTOM_FACE\n\t\t\t#include \"particle_customFaceVS\"\n\t\t#else\n\t\t\t#include \"particle_billboardVS\"\n\t\t#endif\n\t#endif\n\t#if NORMAL == VERTEX\n\t\t#include \"particle_normalVS\"\n\t#endif\n\t#if NORMAL == MAP\n\t\t#include \"particle_TBNVS\"\n\t#endif\n\t#ifdef STRETCH\n\t\t#include \"particle_stretchVS\"\n\t#endif\n\t#ifdef PARTICLE_GPU\n\t\t#include \"particle_endVS\"\n\t#else\n\t\t#include \"particle_cpu_endVS\"\n\t#endif\n\t#ifdef SOFT\n\t\t#include \"particle_softVS\"\n\t#endif\n\t}\n`;\n\nvar particle_softPS$1 = `\n\tfloat depth = getLinearScreenDepth();\n\tfloat particleDepth = vDepth;\n\tfloat depthDiff = saturate(abs(particleDepth - depth) * softening);\n\ta *= depthDiff;\n`;\n\nvar particle_softVS$1 = `\n\tvDepth = getLinearDepth(localPos);\n`;\n\nvar particle_stretchVS$1 = `\n\tvec3 moveDir = inVel * stretch;\n\tvec3 posPrev = particlePos - moveDir;\n\tposPrev += particlePosMoved;\n\tvec2 centerToVertexV = normalize((mat3(matrix_view) * localPos).xy);\n\tfloat interpolation = dot(-velocityV, centerToVertexV) * 0.5 + 0.5;\n\tparticlePos = mix(particlePos, posPrev, interpolation);\n`;\n\nvar particle_TBNVS$1 = `\n\tmat3 rot3 = mat3(rotMatrix[0][0], rotMatrix[0][1], 0.0, rotMatrix[1][0], rotMatrix[1][1], 0.0, 0.0, 0.0, 1.0);\n\tParticleMat = mat3(-matrix_viewInverse[0].xyz, -matrix_viewInverse[1].xyz, matrix_viewInverse[2].xyz) * rot3;\n`;\n\nvar particle_wrapVS$1 = `\n\tvec3 origParticlePos = particlePos;\n\tparticlePos -= matrix_model[3].xyz;\n\tparticlePos = mod(particlePos, wrapBounds) - wrapBounds * 0.5;\n\tparticlePos += matrix_model[3].xyz;\n\tparticlePosMoved = particlePos - origParticlePos;\n`;\n\nconst particleChunksGLSL = {\n\t\tparticlePS: particlePS$1,\n\t\tparticleVS: particleVS$1,\n\t\tparticleAnimFrameClampVS: particleAnimFrameClampVS$1,\n\t\tparticleAnimFrameLoopVS: particleAnimFrameLoopVS$1,\n\t\tparticleAnimTexVS: particleAnimTexVS$1,\n\t\tparticleInputFloatPS: particleInputFloatPS$1,\n\t\tparticleInputRgba8PS: particleInputRgba8PS$1,\n\t\tparticleOutputFloatPS: particleOutputFloatPS$1,\n\t\tparticleOutputRgba8PS: particleOutputRgba8PS$1,\n\t\tparticleUpdaterAABBPS: particleUpdaterAABBPS$1,\n\t\tparticleUpdaterEndPS: particleUpdaterEndPS$1,\n\t\tparticleUpdaterInitPS: particleUpdaterInitPS$1,\n\t\tparticleUpdaterNoRespawnPS: particleUpdaterNoRespawnPS$1,\n\t\tparticleUpdaterOnStopPS: particleUpdaterOnStopPS$1,\n\t\tparticleUpdaterRespawnPS: particleUpdaterRespawnPS$1,\n\t\tparticleUpdaterSpherePS: particleUpdaterSpherePS$1,\n\t\tparticleUpdaterStartPS: particleUpdaterStartPS$1,\n\t\tparticle_billboardVS: particle_billboardVS$1,\n\t\tparticle_blendAddPS: particle_blendAddPS$1,\n\t\tparticle_blendMultiplyPS: particle_blendMultiplyPS$1,\n\t\tparticle_blendNormalPS: particle_blendNormalPS$1,\n\t\tparticle_cpuVS: particle_cpuVS$1,\n\t\tparticle_cpu_endVS: particle_cpu_endVS$1,\n\t\tparticle_customFaceVS: particle_customFaceVS$1,\n\t\tparticle_endPS: particle_endPS$1,\n\t\tparticle_endVS: particle_endVS$1,\n\t\tparticle_halflambertPS: particle_halflambertPS$1,\n\t\tparticle_initVS: particle_initVS$1,\n\t\tparticle_lambertPS: particle_lambertPS$1,\n\t\tparticle_lightingPS: particle_lightingPS$1,\n\t\tparticle_localShiftVS: particle_localShiftVS$1,\n\t\tparticle_meshVS: particle_meshVS$1,\n\t\tparticle_normalVS: particle_normalVS$1,\n\t\tparticle_normalMapPS: particle_normalMapPS$1,\n\t\tparticle_pointAlongVS: particle_pointAlongVS$1,\n\t\tparticle_simulationPS: particle_simulationPS$1,\n\t\tparticle_shaderPS: particle_shaderPS$1,\n\t\tparticle_shaderVS: particle_shaderVS$1,\n\t\tparticle_softPS: particle_softPS$1,\n\t\tparticle_softVS: particle_softVS$1,\n\t\tparticle_stretchVS: particle_stretchVS$1,\n\t\tparticle_TBNVS: particle_TBNVS$1,\n\t\tparticle_wrapVS: particle_wrapVS$1\n};\n\nvar particlePS = `\nvarying texCoordsAlphaLife: vec4f;\nvar colorMap: texture_2d<f32>;\nvar colorMapSampler: sampler;\nvar colorParam: texture_2d<f32>;\nvar colorParamSampler: sampler;\nuniform graphSampleSize: f32;\nuniform graphNumSamples: f32;\n#ifndef CAMERAPLANES\n\t#define CAMERAPLANES\n\tuniform camera_params: vec4f;\n#endif\nuniform softening: f32;\nuniform colorMult: f32;\nfn saturate(x: f32) -> f32 {\n\treturn clamp(x, 0.0, 1.0);\n}\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\tlet tex: vec4f  = textureSample(colorMap, colorMapSampler, vec2f(input.texCoordsAlphaLife.x, 1.0 - input.texCoordsAlphaLife.y));\n\tvar ramp: vec4f = textureSample(colorParam, colorParamSampler, vec2f(input.texCoordsAlphaLife.w, 0.0));\n\tramp = vec4f(ramp.rgb * uniform.colorMult, ramp.a);\n\tramp.a = ramp.a + input.texCoordsAlphaLife.z;\n\tvar rgb: vec3f = tex.rgb * ramp.rgb;\n\tvar a: f32 = tex.a * ramp.a;\n`;\n\nvar particleVS = `\nfn unpack3NFloats(src: f32) -> vec3f {\n\tlet r = fract(src);\n\tlet g = fract(src * 256.0);\n\tlet b = fract(src * 65536.0);\n\treturn vec3f(r, g, b);\n}\nfn saturate(x: f32) -> f32 {\n\treturn clamp(x, 0.0, 1.0);\n}\nstruct TexLerpUnpackResult {\n\tresult: vec4f,\n\tunpacked: vec3f\n}\nfn tex1Dlod_lerp_simple(tex: texture_2d<f32>, texSampler: sampler, tc: vec2f) -> vec4f {\n\tlet tc_next = tc + vec2f(uniform.graphSampleSize);\n\treturn mix( textureSample(tex, texSampler, tc), textureSample(tex, texSampler, tc_next), fract(tc.x * uniform.graphNumSamples) );\n}\nfn tex1Dlod_lerp_unpack(tex: texture_2d<f32>, texSampler: sampler, tc: vec2f) -> TexLerpUnpackResult {\n\tlet tc_next = tc + vec2f(uniform.graphSampleSize);\n\tlet a = textureSampleLevel(tex, texSampler, tc, 0.0);\n\tlet b = textureSampleLevel(tex, texSampler, tc_next, 0.0);\n\tlet c = fract(tc.x * uniform.graphNumSamples);\n\tlet unpackedA = unpack3NFloats(a.w);\n\tlet unpackedB = unpack3NFloats(b.w);\n\tlet w_out = mix(unpackedA, unpackedB, c);\n\treturn TexLerpUnpackResult(mix(a, b, c), w_out);\n}\nstruct RotateResult {\n\trotatedVec: vec2f,\n\tmatrix: mat2x2f\n}\nfn rotateWithMatrix(quadXY: vec2f, pRotation: f32) -> RotateResult {\n\tlet c = cos(pRotation);\n\tlet s = sin(pRotation);\n\tlet m = mat2x2f(vec2f(c, -s), vec2f(s, c));\n\treturn RotateResult(m * quadXY, m);\n}\nfn billboard(InstanceCoords: vec3f, quadXY: vec2f) -> vec3f {\n\tvar pos: vec3f;\n\t#ifdef SCREEN_SPACE\n\t\tpos = vec3f(-1.0, 0.0, 0.0) * quadXY.x + vec3f(0.0, -1.0, 0.0) * quadXY.y;\n\t#else\n\t\tpos = -uniform.matrix_viewInverse[0].xyz * quadXY.x + -uniform.matrix_viewInverse[1].xyz * quadXY.y;\n\t#endif\n\treturn pos;\n}\nfn customFace(InstanceCoords: vec3f, quadXY: vec2f) -> vec3f {\n\tlet pos = uniform.faceTangent * quadXY.x + uniform.faceBinorm * quadXY.y;\n\treturn pos;\n}\nfn safeNormalize(v: vec2f) -> vec2f {\n\tlet l = length(v);\n\treturn select(v, v / l, l > 1e-06);\n}\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n\tvar output: VertexOutput;\n\tlet meshLocalPos_in = input.particle_vertexData.xyz;\n\tlet id = floor(input.particle_vertexData.w);\n\tlet rndFactor = fract(sin(id + 1.0 + uniform.seed));\n\tlet rndFactor3 = vec3f(rndFactor, fract(rndFactor*10.0), fract(rndFactor*100.0));\n\tlet uv = id / uniform.numParticlesPot;\n\treadInput(uv);\n\t#ifdef LOCAL_SPACE\n\t\tlet modelRotation = mat3x3f(uniform.matrix_model[0].xyz, uniform.matrix_model[1].xyz, uniform.matrix_model[2].xyz);\n\t\tinVel = modelRotation * inVel;\n\t#endif\n\tlet viewRotation = mat3x3f(uniform.matrix_view[0].xyz, uniform.matrix_view[1].xyz, uniform.matrix_view[2].xyz);\n\tlet velocityV = safeNormalize((viewRotation * inVel).xy);\n\tlet particleLifetime = uniform.lifetime;\n\tvar meshLocalPos = meshLocalPos_in;\n\tif (inLife <= 0.0 || inLife > particleLifetime || !inShow) {\n\t\t meshLocalPos = vec3f(0.0);\n\t}\n\tlet quadXY = meshLocalPos.xy;\n\tlet nlife = clamp(inLife / particleLifetime, 0.0, 1.0);\n\tlet lerp_result = tex1Dlod_lerp_unpack(internalTex2, internalTex2Sampler, vec2f(nlife, 0.0));\n\tlet params = lerp_result.result;\n\tlet paramDiv = lerp_result.unpacked;\n\tvar scale = params.y;\n\tlet scaleDiv = paramDiv.x;\n\tlet alphaDiv = paramDiv.z;\n\tscale = scale + (scaleDiv * 2.0 - 1.0) * uniform.scaleDivMult * fract(rndFactor*10000.0);\n\t#ifndef USE_MESH\n\t\toutput.texCoordsAlphaLife = vec4f(quadXY * -0.5 + 0.5, (alphaDiv * 2.0 - 1.0) * uniform.alphaDivMult * fract(rndFactor*1000.0), nlife);\n\t#else\n\t\toutput.texCoordsAlphaLife = vec4f(particle_uv, (alphaDiv * 2.0 - 1.0) * uniform.alphaDivMult * fract(rndFactor*1000.0), nlife);\n\t#endif\n\tvar particlePos = inPos;\n\tvar particlePosMoved = vec3f(0.0);\n\tvar rotMatrix: mat2x2f;\n`;\n\nvar particleAnimFrameClampVS = `\n\tlet animFrame: f32 = min(floor(input.texCoordsAlphaLife.w * uniform.animTexParams.y) + uniform.animTexParams.x, uniform.animTexParams.z);\n`;\n\nvar particleAnimFrameLoopVS = `\n\tlet animFrame: f32 = floor((output.texCoordsAlphaLife.w * uniform.animTexParams.y + uniform.animTexParams.x) % (uniform.animTexParams.z + 1.0));\t\n`;\n\nvar particleAnimTexVS = `\n\tvar animationIndex: f32;\n\tif (uniform.animTexIndexParams.y == 1.0) {\n\t\tanimationIndex = floor((uniform.animTexParams.w + 1.0) * rndFactor3.z) * (uniform.animTexParams.z + 1.0);\n\t} else {\n\t\tanimationIndex = uniform.animTexIndexParams.x * (uniform.animTexParams.z + 1.0);\n\t}\n\tvar atlasX: f32 = (animationIndex + animFrame) * uniform.animTexTilesParams.x;\n\tlet atlasY: f32 = 1.0 - floor(atlasX + 1.0) * uniform.animTexTilesParams.y;\n\tatlasX = fract(atlasX);\n\tlet current_tcal_xy = output.texCoordsAlphaLife.xy;\n\tlet scaled_tcal_xy = current_tcal_xy * uniform.animTexTilesParams.xy;\n\tlet final_tcal_xy = scaled_tcal_xy + vec2f(atlasX, atlasY);\n\toutput.texCoordsAlphaLife = vec4f(final_tcal_xy, output.texCoordsAlphaLife.z, output.texCoordsAlphaLife.w);\n`;\n\nvar particleInputFloatPS = `\nfn readInput(uv: f32) {\n\tlet tex: vec4f = textureSampleLevel(particleTexIN, particleTexINSampler, vec2f(uv, 0.25), 0.0);\n\tlet tex2: vec4f = textureSampleLevel(particleTexIN, particleTexINSampler, vec2f(uv, 0.75), 0.0);\n\tinPos = tex.xyz;\n\tinVel = tex2.xyz;\n\tinAngle = abs(tex.w) - 1000.0;\n\tinShow = tex.w >= 0.0;\n\tinLife = tex2.w;\n}\n`;\n\nvar particleInputRgba8PS = `\nconst PI2: f32 = 6.283185307179586;\nuniform inBoundsSize: vec3f;\nuniform inBoundsCenter: vec3f;\nuniform maxVel: f32;\nfn decodeFloatRG(rg: vec2f) -> f32 {\n\treturn rg.y * (1.0 / 255.0) + rg.x;\n}\nfn decodeFloatRGBA( rgba: vec4f ) -> f32 {\n\treturn dot(rgba, vec4f(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0));\n}\nfn readInput(uv: f32) {\n\tlet tex0 = textureSampleLevel(particleTexIN, particleTexINSampler, vec2f(uv, 0.125), 0.0);\n\tlet tex1 = textureSampleLevel(particleTexIN, particleTexINSampler, vec2f(uv, 0.375), 0.0);\n\tlet tex2 = textureSampleLevel(particleTexIN, particleTexINSampler, vec2f(uv, 0.625), 0.0);\n\tlet tex3 = textureSampleLevel(particleTexIN, particleTexINSampler, vec2f(uv, 0.875), 0.0);\n\tinPos = vec3f(decodeFloatRG(tex0.rg), decodeFloatRG(tex0.ba), decodeFloatRG(tex1.rg));\n\tinPos = (inPos - vec3f(0.5)) * uniform.inBoundsSize + uniform.inBoundsCenter;\n\tinVel = tex2.xyz;\n\tinVel = (inVel - vec3f(0.5)) * uniform.maxVel;\n\tinAngle = decodeFloatRG(tex1.ba) * PI2;\n\tinShow = tex2.a > 0.5;\n\tlet life_decoded = decodeFloatRGBA(tex3);\n\tlet maxNegLife = max(uniform.lifetime, (uniform.numParticles - 1.0) * (uniform.rate + uniform.rateDiv));\n\tlet maxPosLife = uniform.lifetime + 1.0;\n\tinLife = life_decoded * (maxNegLife + maxPosLife) - maxNegLife;\n}`;\n\nvar particleOutputFloatPS = `\nfn getOutput() -> vec4f {\n\tif (pcPosition.y < 1.0) {\n\t\treturn vec4f(outPos, (outAngle + 1000.0) * visMode);\n\t} else {\n\t\treturn vec4f(outVel, outLife);\n\t}\n}\n`;\n\nvar particleOutputRgba8PS = `\nuniform outBoundsMul: vec3f;\nuniform outBoundsAdd: vec3f;\nfn encodeFloatRG( v: f32 ) -> vec2f {\n\tvar enc: vec2f = vec2f(1.0, 255.0) * v;\n\tenc = fract(enc);\n\tenc = enc - enc.yy * (1.0 / 255.0);\n\treturn enc;\n}\nfn encodeFloatRGBA( v: f32 ) -> vec4f {\n\tlet factors = vec4f(1.0, 255.0, 65025.0, 160581375.0);\n\tvar enc: vec4f = factors * v;\n\tenc = fract(enc);\n\tenc = enc - enc.yzww * vec4f(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0);\n\treturn enc;\n}\nfn getOutput() -> vec4f {\n\toutPos = outPos * uniform.outBoundsMul + uniform.outBoundsAdd;\n\toutAngle = fract(outAngle / PI2);\n\toutVel = (outVel / uniform.maxVel) + vec3f(0.5);\n\tlet maxNegLife = max(uniform.lifetime, (uniform.numParticles - 1.0) * (uniform.rate + uniform.rateDiv));\n\tlet maxPosLife = uniform.lifetime + 1.0;\n\toutLife = (outLife + maxNegLife) / (maxNegLife + maxPosLife);\n\tif (pcPosition.y < 1.0) {\n\t\treturn vec4f(encodeFloatRG(outPos.x), encodeFloatRG(outPos.y));\n\t} else if (pcPosition.y < 2.0) {\n\t\treturn vec4f(encodeFloatRG(outPos.z), encodeFloatRG(outAngle));\n\t} else if (pcPosition.y < 3.0) {\n\t\treturn vec4f(outVel, visMode * 0.5 + 0.5);\n\t} else {\n\t\treturn encodeFloatRGBA(outLife);\n\t}\n}\n`;\n\nvar particleUpdaterAABBPS = `\nuniform spawnBounds: mat3x3f;\nuniform spawnPosInnerRatio: vec3f;\nfn calcSpawnPosition(inBounds: vec3f, rndFactor: f32) -> vec3f {\n\tvar pos = inBounds - vec3f(0.5);\n\tlet posAbs = abs(pos);\n\tlet maxComp = max(posAbs.x, max(posAbs.y, posAbs.z));\n\tlet maxPos = vec3f(maxComp);\n\tlet edge = maxPos + (vec3f(0.5) - maxPos) * uniform.spawnPosInnerRatio;\n\tpos.x = edge.x * select(2.0 * pos.x, sign(pos.x), maxPos.x == posAbs.x);\n\tpos.y = edge.y * select(2.0 * pos.y, sign(pos.y), maxPos.y == posAbs.y);\n\tpos.z = edge.z * select(2.0 * pos.z, sign(pos.z), maxPos.z == posAbs.z);\n\t#ifndef LOCAL_SPACE\n\t\treturn uniform.emitterPos + uniform.spawnBounds * pos;\n\t#else\n\t\treturn uniform.spawnBounds * pos;\n\t#endif\n}\nfn addInitialVelocity(localVelocity: ptr<function, vec3f>, inBounds: vec3f) {\n\t*localVelocity = *localVelocity - vec3f(0.0, 0.0, uniform.initialVelocity);\n}\n`;\n\nvar particleUpdaterEndPS = `\n\toutput.color = getOutput();\n\treturn output;\n}\n`;\n\nvar particleUpdaterInitPS = `\nvarying vUv0: vec2f;\nvar particleTexIN: texture_2d<f32>;\nvar particleTexINSampler: sampler;\nvar internalTex0: texture_2d<f32>;\nvar internalTex0Sampler: sampler;\nvar internalTex1: texture_2d<f32>;\nvar internalTex1Sampler: sampler;\nvar internalTex2: texture_2d<f32>;\nvar internalTex2Sampler: sampler;\nvar internalTex3: texture_2d<f32>;\nvar internalTex3Sampler: sampler;\nuniform emitterMatrix: mat3x3f;\nuniform emitterMatrixInv: mat3x3f;\nuniform emitterScale: vec3f;\nuniform emitterPos: vec3f;\nuniform frameRandom: vec3f;\nuniform localVelocityDivMult: vec3f;\nuniform velocityDivMult: vec3f;\nuniform delta: f32;\nuniform rate: f32;\nuniform rateDiv: f32;\nuniform lifetime: f32;\nuniform numParticles: f32;\nuniform rotSpeedDivMult: f32;\nuniform radialSpeedDivMult: f32;\nuniform seed: f32;\nuniform startAngle: f32;\nuniform startAngle2: f32;\nuniform initialVelocity: f32;\nuniform graphSampleSize: f32;\nuniform graphNumSamples: f32;\nvar<private> inPos: vec3f;\nvar<private> inVel: vec3f;\nvar<private> inAngle: f32;\nvar<private> inShow: bool;\nvar<private> inLife: f32;\nvar<private> visMode: f32;\nvar<private> outPos: vec3f;\nvar<private> outVel: vec3f;\nvar<private> outAngle: f32;\nvar<private> outShow: bool;\nvar<private> outLife: f32;\n`;\n\nvar particleUpdaterNoRespawnPS = `\n\tif (outLife >= uniform.lifetime) {\n\t\toutLife = outLife - max(uniform.lifetime, (uniform.numParticles - 1.0) * particleRate);\n\t\tvisMode = -1.0;\n\t}\n`;\n\nvar particleUpdaterOnStopPS = `\n\tvisMode = select(visMode, -1.0, outLife < 0.0);\n`;\n\nvar particleUpdaterRespawnPS = `\n\tif (outLife >= uniform.lifetime) {\n\t\tlet subtractAmount = max(uniform.lifetime, (uniform.numParticles - 1.0) * particleRate);\n\t\toutLife = outLife - subtractAmount;\n\t\tvisMode = 1.0;\n\t}\n\tvisMode = select(visMode, 1.0, outLife < 0.0);\n`;\n\nvar particleUpdaterSpherePS = `\nuniform spawnBoundsSphere: f32;\nuniform spawnBoundsSphereInnerRatio: f32;\nfn calcSpawnPosition(inBounds: vec3f, rndFactor: f32) -> vec3f {\n\tlet rnd4: f32 = fract(rndFactor * 1000.0);\n\tlet norm: vec3f = normalize(inBounds.xyz - vec3f(0.5));\n\tlet r: f32 = rnd4 * (1.0 - uniform.spawnBoundsSphereInnerRatio) + uniform.spawnBoundsSphereInnerRatio;\n\t#ifndef LOCAL_SPACE\n\t\treturn uniform.emitterPos + norm * r * uniform.spawnBoundsSphere;\n\t#else\n\t\treturn norm * r * uniform.spawnBoundsSphere;\n\t#endif\n}\nfn addInitialVelocity(localVelocity: ptr<function, vec3f>, inBounds: vec3f) {\n\tlet initialVelOffset: vec3f = normalize(inBounds - vec3f(0.5)) * uniform.initialVelocity;\n\t*localVelocity = *localVelocity + initialVelOffset;\n}\n`;\n\nvar particleUpdaterStartPS = `\nfn saturate(x: f32) -> f32 {\n\treturn clamp(x, 0.0, 1.0);\n}\nfn unpack3NFloats(src: f32) -> vec3f {\n\tlet r = fract(src);\n\tlet g = fract(src * 256.0);\n\tlet b = fract(src * 65536.0);\n\treturn vec3f(r, g, b);\n}\nstruct TexLerpUnpackResult {\n\tresult: vec3f,\n\tunpacked: vec3f\n}\nfn tex1Dlod_lerp(tex: texture_2d<f32>, texSampler: sampler, tc: vec2f) -> TexLerpUnpackResult {\n\tlet tc_next = tc + vec2f(uniform.graphSampleSize);\n\tlet a = textureSampleLevel(tex, texSampler, tc, 0.0);\n\tlet b = textureSampleLevel(tex, texSampler, tc_next, 0.0);\n\tlet c = fract(tc.x * uniform.graphNumSamples);\n\tlet unpackedA = unpack3NFloats(a.w);\n\tlet unpackedB = unpack3NFloats(b.w);\n\tlet w_out = mix(unpackedA, unpackedB, c);\n\treturn TexLerpUnpackResult(mix(a.xyz, b.xyz, c), w_out);\n}\nconst HASHSCALE4: vec4f = vec4f(1031.0, 0.1030, 0.0973, 0.1099);\nfn hash41(p: f32) -> vec4f {\n\tvar p4 = fract(vec4f(p) * HASHSCALE4);\n\tp4 = p4 + dot(p4, p4.wzxy + 19.19);\n\treturn fract(vec4f((p4.x + p4.y)*p4.z, (p4.x + p4.z)*p4.y, (p4.y + p4.z)*p4.w, (p4.z + p4.w)*p4.x));\n}\n@fragment\nfn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\tif (pcPosition.x > uniform.numParticles) {\n\t\tdiscard;\n\t\treturn output;\n\t}\n\treadInput(input.vUv0.x);\n\tvisMode = select(-1.0, 1.0, inShow);\n\tlet rndFactor = hash41(pcPosition.x + uniform.seed);\n\tlet particleRate = uniform.rate + uniform.rateDiv * rndFactor.x;\n\toutLife = inLife + uniform.delta;\n\tlet nlife = clamp(outLife / uniform.lifetime, 0.0, 1.0);\n\tlet lerpResult0 = tex1Dlod_lerp(internalTex0, internalTex0Sampler, vec2f(nlife, 0.0));\n\tvar localVelocity = lerpResult0.result;\n\tlet localVelocityDiv = lerpResult0.unpacked;\n\tlet lerpResult1 = tex1Dlod_lerp(internalTex1, internalTex1Sampler, vec2f(nlife, 0.0));\n\tvar velocity = lerpResult1.result;\n\tlet velocityDiv = lerpResult1.unpacked;\n\tlet lerpResult2 = tex1Dlod_lerp(internalTex2, internalTex2Sampler, vec2f(nlife, 0.0));\n\tlet params = lerpResult2.result;\n\tlet paramDiv = lerpResult2.unpacked;\n\tvar rotSpeed = params.x;\n\tlet rotSpeedDiv = paramDiv.y;\n\tlet lerpResult3 = tex1Dlod_lerp(internalTex3, internalTex3Sampler, vec2f(nlife, 0.0));\n\tlet radialParams = lerpResult3.result;\n\tlet radialParamDiv = lerpResult3.unpacked;\n\tlet radialSpeed = radialParams.x;\n\tlet radialSpeedDiv = radialParamDiv.y;\n\tlet respawn = inLife <= 0.0 || outLife >= uniform.lifetime;\n\tinPos = select(inPos, calcSpawnPosition(rndFactor.xyz, rndFactor.x), respawn);\n\tinAngle = select(inAngle, mix(uniform.startAngle, uniform.startAngle2, rndFactor.x), respawn);\n\t#ifndef LOCAL_SPACE\n\t\tvar radialVel: vec3f = inPos - uniform.emitterPos;\n\t#else\n\t\tvar radialVel: vec3f = inPos;\n\t#endif\n\tradialVel = select(vec3f(0.0), radialSpeed * normalize(radialVel), dot(radialVel, radialVel) > 1.0E-8);\n\tradialVel = radialVel + (radialSpeedDiv * vec3f(2.0) - vec3f(1.0)) * uniform.radialSpeedDivMult * rndFactor.xyz;\n\tlocalVelocity = localVelocity + (localVelocityDiv * vec3f(2.0) - vec3f(1.0)) * uniform.localVelocityDivMult * rndFactor.xyz;\n\tvelocity = velocity + (velocityDiv * vec3f(2.0) - vec3f(1.0)) * uniform.velocityDivMult * rndFactor.xyz;\n\trotSpeed = rotSpeed + (rotSpeedDiv * 2.0 - 1.0) * uniform.rotSpeedDivMult * rndFactor.y;\n\taddInitialVelocity(&localVelocity, rndFactor.xyz);\n\t#ifndef LOCAL_SPACE\n\t\toutVel = uniform.emitterMatrix * localVelocity + (radialVel + velocity) * uniform.emitterScale;\n\t#else\n\t\toutVel = (localVelocity + radialVel) / uniform.emitterScale + uniform.emitterMatrixInv * velocity;\n\t#endif\n\toutPos = inPos + outVel * uniform.delta;\n\toutAngle = inAngle + rotSpeed * uniform.delta;\n`;\n\nvar particle_billboardVS = `\n\tlet rotationResult = rotateWithMatrix(quadXY, inAngle);\n\tlet rotatedQuadXY = rotationResult.rotatedVec;\n\trotMatrix = rotationResult.matrix;\n\tvar localPos = billboard(particlePos, rotatedQuadXY);\n`;\n\nvar particle_blendAddPS = `\n\tdBlendModeFogFactor = 0.0;\n\trgb = rgb * saturate(gammaCorrectInput(max(a, 0.0)));\n\tif ((rgb.r + rgb.g + rgb.b) < 0.000001) {\n\t\tdiscard;\n\t}\t\n`;\n\nvar particle_blendMultiplyPS = `\n\trgb = mix(vec3f(1.0), rgb, a);\n\tif ((rgb.r + rgb.g + rgb.b) > 2.99) {\n\t\tdiscard;\n\t}\n`;\n\nvar particle_blendNormalPS = `\n\tif (a < 0.01) {\n\t\tdiscard;\n\t}\n`;\n\nvar particle_cpuVS = `\nattribute particle_vertexData: vec4f;\nattribute particle_vertexData2: vec4f;\nattribute particle_vertexData3: vec4f;\nattribute particle_vertexData4: f32;\n#ifndef USE_MESH\n\tattribute particle_vertexData5: vec2f;\n#else\n\tattribute particle_vertexData5: vec4f;\n#endif\nuniform matrix_viewProjection: mat4x4f;\nuniform matrix_model: mat4x4f;\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform matrix_view: mat4x4f;\n#endif\nuniform matrix_normal: mat3x3f;\nuniform matrix_viewInverse: mat4x4f;\nuniform numParticles: f32;\nuniform lifetime: f32;\nuniform stretch: f32;\nuniform seed: f32;\nuniform wrapBounds: vec3f;\nuniform emitterScale: vec3f;\nuniform faceTangent: vec3f;\nuniform faceBinorm: vec3f;\n#ifdef PARTICLE_GPU\n\tvar internalTex0: texture_2d<f32>;\n\tvar internalTex0Sampler: sampler;\n\tvar internalTex1: texture_2d<f32>;\n\tvar internalTex1Sampler: sampler;\n\tvar internalTex2: texture_2d<f32>;\n\tvar internalTex2Sampler: sampler;\n#endif\nuniform emitterPos: vec3f;\nvarying texCoordsAlphaLife: vec4f;\nstruct RotateResult {\n\trotatedVec: vec2f,\n\tmatrix: mat2x2f\n}\nfn rotateWithMatrix(quadXY: vec2f, pRotation: f32) -> RotateResult {\n\tlet c = cos(pRotation);\n\tlet s = sin(pRotation);\n\tlet m = mat2x2f(vec2f(c, -s), vec2f(s, c));\n\treturn RotateResult(m * quadXY, m);\n}\nfn billboard(InstanceCoords: vec3f, quadXY: vec2f) -> vec3f {\n\tlet pos = -uniform.matrix_viewInverse[0].xyz * quadXY.x + -uniform.matrix_viewInverse[1].xyz * quadXY.y;\n\treturn pos;\n}\nfn customFace(InstanceCoords: vec3f, quadXY: vec2f) -> vec3f {\n\tlet pos = uniform.faceTangent * quadXY.x + uniform.faceBinorm * quadXY.y;\n\treturn pos;\n}\nfn safeNormalize(v: vec2f) -> vec2f {\n\tlet l = length(v);\n\treturn select(v, v / l, l > 1e-06);\n}\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n\tvar output: VertexOutput;\n\tvar particlePos = input.particle_vertexData.xyz;\n\tlet inPos = particlePos;\n\tlet vertPos = input.particle_vertexData3.xyz;\n\tvar inVel = vec3f(input.particle_vertexData2.w, input.particle_vertexData3.w, input.particle_vertexData5.x);\n\tlet id = floor(input.particle_vertexData4);\n\tlet rndFactor = fract(sin(id + 1.0 + uniform.seed));\n\tlet rndFactor3 = vec3f(rndFactor, fract(rndFactor*10.0), fract(rndFactor*100.0));\n\t#ifdef LOCAL_SPACE\n\t\tlet modelRotation = mat3x3f(uniform.matrix_model[0].xyz, uniform.matrix_model[1].xyz, uniform.matrix_model[2].xyz);\n\t\tinVel = modelRotation * inVel;\n\t#endif\n\tlet velocityV = safeNormalize((mat3x3f(uniform.matrix_view[0].xyz, uniform.matrix_view[1].xyz, uniform.matrix_view[2].xyz) * inVel).xy);\n\tlet quadXY = vertPos.xy;\n\t#ifdef USE_MESH\n\t\toutput.texCoordsAlphaLife = vec4f(input.particle_vertexData5.zw, input.particle_vertexData2.z, input.particle_vertexData.w);\n\t#else\n\t\toutput.texCoordsAlphaLife = vec4f(quadXY * -0.5 + 0.5, input.particle_vertexData2.z, input.particle_vertexData.w);\n\t#endif\n\tvar rotMatrix: mat2x2f;\n\tvar inAngle = input.particle_vertexData2.x;\n\tvar particlePosMoved = vec3f(0.0);\n\tlet meshLocalPos = input.particle_vertexData3.xyz;\n`;\n\nvar particle_cpu_endVS = `\n\tlocalPos = localPos * input.particle_vertexData2.y * uniform.emitterScale;\n\tlocalPos = localPos + particlePos;\n\toutput.position = uniform.matrix_viewProjection * vec4f(localPos, 1.0);\n`;\n\nvar particle_customFaceVS = `\n\tlet rotationResult = rotateWithMatrix(quadXY, inAngle);\n\tlet rotatedQuadXY = rotationResult.rotatedVec;\n\trotMatrix = rotationResult.matrix;\n\tvar localPos = customFace(particlePos, rotatedQuadXY);\n`;\n\nvar particle_endPS = `\n\trgb = addFog(rgb);\n\trgb = toneMap(rgb);\n\trgb = gammaCorrectOutput(rgb);\n\toutput.color = vec4f(rgb, a);\n\treturn output;\n}\n`;\n\nvar particle_endVS = `\n\tlocalPos = localPos * scale * uniform.emitterScale;\n\tlocalPos = localPos + particlePos;\n\t#ifdef SCREEN_SPACE\n\t\toutput.position = vec4f(localPos.x, localPos.y, 0.0, 1.0);\n\t#else\n\t\toutput.position = uniform.matrix_viewProjection * vec4f(localPos.xyz, 1.0);\n\t#endif\n`;\n\nvar particle_halflambertPS = `\n\tvar negNormal: vec3f = normal * 0.5 + 0.5;\n\tvar posNormal: vec3f = -normal * 0.5 + 0.5;\n\tnegNormal = negNormal * negNormal;\n\tposNormal = posNormal * posNormal;\n`;\n\nvar particle_initVS = `\nattribute particle_vertexData: vec4f;\n#if defined(USE_MESH)\n\t#if defined(USE_MESH_UV)\n\t\tattribute particle_uv: vec2f;\n\t#else\n\t\tvar<private> particle_uv: vec2f = vec2f(0.0, 0.0);\n\t#endif\n#endif\nuniform matrix_viewProjection: mat4x4f;\nuniform matrix_model: mat4x4f;\nuniform matrix_normal: mat3x3f;\nuniform matrix_viewInverse: mat4x4f;\n#ifndef VIEWMATRIX\n\t#define VIEWMATRIX\n\tuniform matrix_view: mat4x4f;\n#endif\nuniform numParticles: f32;\nuniform numParticlesPot: f32;\nuniform graphSampleSize: f32;\nuniform graphNumSamples: f32;\nuniform stretch: f32;\nuniform wrapBounds: vec3f;\nuniform emitterScale: vec3f;\nuniform emitterPos: vec3f;\nuniform faceTangent: vec3f;\nuniform faceBinorm: vec3f;\nuniform rate: f32;\nuniform rateDiv: f32;\nuniform lifetime: f32;\nuniform deltaRandomnessStatic: f32;\nuniform scaleDivMult: f32;\nuniform alphaDivMult: f32;\nuniform seed: f32;\nuniform delta: f32;\nvar particleTexOUT: texture_2d<f32>;\nvar particleTexOUTSampler: sampler;\nvar particleTexIN: texture_2d<f32>;\nvar particleTexINSampler: sampler;\n#ifdef PARTICLE_GPU\n\tvar internalTex0: texture_2d<f32>;\n\tvar internalTex0Sampler: sampler;\n\tvar internalTex1: texture_2d<f32>;\n\tvar internalTex1Sampler: sampler;\n\tvar internalTex2: texture_2d<f32>;\n\tvar internalTex2Sampler: sampler;\n#endif\n#ifndef CAMERAPLANES\n\t#define CAMERAPLANES\n\tuniform camera_params: vec4f;\n#endif\nvarying texCoordsAlphaLife: vec4f;\nvar<private> inPos: vec3f;\nvar<private> inVel: vec3f;\nvar<private> inAngle: f32;\nvar<private> inShow: bool;\nvar<private> inLife: f32;\n`;\n\nvar particle_lambertPS = `\n\tvar negNormal: vec3f = max(normal, vec3(0.0));\n\tvar posNormal: vec3f = max(-normal, vec3(0.0));\n`;\n\nvar particle_lightingPS = `\n\tlet light: vec3f = negNormal.x * uniform.lightCube[0] + posNormal.x * uniform.lightCube[1] +\n\t\t\t\t\t   negNormal.y * uniform.lightCube[2] + posNormal.y * uniform.lightCube[3] +\n\t\t\t\t\t   negNormal.z * uniform.lightCube[4] + posNormal.z * uniform.lightCube[5];\n\trgb = rgb * light;\n`;\n\nvar particle_localShiftVS = `\nparticlePos = (uniform.matrix_model * vec4f(particlePos, 1.0)).xyz;\n`;\n\nvar particle_meshVS = `\nvar localPos = meshLocalPos;\nlet rotResultXY = rotateWithMatrix(localPos.xy, inAngle);\nlocalPos = vec3f(rotResultXY.rotatedVec, localPos.z);\nrotMatrix = rotResultXY.matrix;\nlet rotResultYZ = rotateWithMatrix(localPos.yz, inAngle);\nlocalPos = vec3f(localPos.x, rotResultYZ.rotatedVec);\nrotMatrix = rotResultYZ.matrix;\nbillboard(particlePos, quadXY);\n`;\n\nvar particle_normalVS = `\noutput.Normal = normalize(localPos + uniform.matrix_viewInverse[2].xyz);\n`;\n\nvar particle_normalMapPS = `\n\tlet sampledNormal: vec4f = textureSample(normalMap, normalMapSampler, vec2f(input.texCoordsAlphaLife.x, 1.0 - input.texCoordsAlphaLife.y));\n\tlet normalMap: vec3f = normalize(sampledNormal.xyz * 2.0 - 1.0);\n\tlet ParticleMat = mat3x3<f32>(ParticleMat0, ParticleMat1, ParticleMat2);\n\tlet normal: vec3f = ParticleMat * normalMap;\n`;\n\nvar particle_pointAlongVS = `\n\tinAngle = atan2(velocityV.x, velocityV.y);\n`;\n\nvar particle_simulationPS = `\n\t#include \"particleUpdaterInitPS\"\n\t#ifdef PACK8\n\t\t#include \"particleInputRgba8PS\"\n\t\t#include \"particleOutputRgba8PS\"\n\t#else\n\t\t#include \"particleInputFloatPS\"\n\t\t#include \"particleOutputFloatPS\"\n\t#endif\n\t#ifdef EMITTERSHAPE_BOX\n\t\t#include \"particleUpdaterAABBPS\"\n\t#else\n\t\t#include \"particleUpdaterSpherePS\"\n\t#endif\n\t#include \"particleUpdaterStartPS\"\n\t#ifdef RESPAWN\n\t\t#include \"particleUpdaterRespawnPS\"\n\t#endif\n\t#ifdef NO_RESPAWN\n\t\t#include \"particleUpdaterNoRespawnPS\"\n\t#endif\n\t#ifdef ON_STOP\n\t\t#include \"particleUpdaterOnStopPS\"\n\t#endif\n\t#include \"particleUpdaterEndPS\"\n`;\n\nvar particle_shaderPS = `\n\t#if NORMAL != NONE\n\t\t#if NORMAL == VERTEX\n\t\t\tvarying Normal: vec3f;\n\t\t#endif\n\t\t#if NORMAL == MAP\n\t\t\tvarying ParticleMat0: vec3f;\n\t\t\tvarying ParticleMat1: vec3f;\n\t\t\tvarying ParticleMat2: vec3f;\n\t\t#endif\n\t\tuniform lightCube: array<vec3f, 6>;\n\t#endif\n\t#ifdef SOFT\n\t\tvarying vDepth: f32;\n\t\t#include \"screenDepthPS\"\n\t#endif\n\t#include \"gammaPS\"\n\t#include \"tonemappingPS\"\n\t#include \"fogPS\"\n\t#if NORMAL == MAP\n\t\tvar normalMap: texture_2d<f32>;\n\t\tvar normalMapSampler: sampler;\n\t#endif\n\t#include \"particlePS\"\n\t#ifdef SOFT\n\t\t#include \"particle_softPS\"\n\t#endif\n\t#if NORMAL == VERTEX\n\t\tvar normal: vec3f = Normal;\n\t#endif\n\t#if NORMAL == MAP\n\t\t#include \"particle_normalMapPS\"\n\t#endif\n\t#if NORMAL != NONE\n\t\t#ifdef HALF_LAMBERT\n\t\t\t#include \"particle_halflambertPS\"\n\t\t#else\n\t\t\t#include \"particle_lambertPS\"\n\t\t#endif\n\t\t#include \"particle_lightingPS\"\n\t#endif\n\t#if BLEND == NORMAL\n\t\t#include \"particle_blendNormalPS\"\n\t#elif BLEND == ADDITIVE\n\t\t#include \"particle_blendAddPS\"\n\t#elif BLEND == MULTIPLICATIVE\n\t\t#include \"particle_blendMultiplyPS\"\n\t#endif\n\t#include \"particle_endPS\"\n`;\n\nvar particle_shaderVS = `\n\t#ifdef ANIMTEX\n\t\tuniform animTexTilesParams: vec2f;\n\t\tuniform animTexParams: vec4f;\n\t\tuniform animTexIndexParams: vec2f;\n\t#endif\n\t#if NORMAL == MAP\n\t\tvarying ParticleMat0: vec3f;\n\t\tvarying ParticleMat1: vec3f;\n\t\tvarying ParticleMat2: vec3f;\n\t#endif\n\t#if NORMAL == VERTEX\n\t\tvarying Normal: vec3f;\n\t#endif\n\t#ifdef SOFT\n\t\tvarying vDepth: f32;\n\t#endif\n\t#ifdef PARTICLE_GPU\n\t\t#include \"particle_initVS\"\n\t\t#ifdef PACK8\n\t\t\t#include \"particleInputRgba8PS\"\n\t\t#else\n\t\t\t#include  \"particleInputFloatPS\"\n\t\t#endif\n\t\t#ifdef SOFT\n\t\t\t#include \"screenDepthPS\"\n\t\t#endif\n\t\t#include \"particleVS\"\n\t#else\n\t\t#ifdef SOFT\n\t\t\t#include \"screenDepthPS\"\n\t\t#endif\n\t\t#include \"particle_cpuVS\"\n\t#endif\n\t#ifdef LOCAL_SPACE\n\t\t#include \"particle_localShiftVS\"\n\t#endif\n\t#ifdef ANIMTEX\n\t\t#ifdef ANIMTEX_LOOP\n\t\t\t#include \"particleAnimFrameLoopVS\"\n\t\t#else\n\t\t\t#include \"particleAnimFrameClampVS\"\n\t\t#endif\n\t\t#include \"particleAnimTexVS\"\n\t#endif\n\t#ifdef PARTICLE_GPU\n\t\t#ifdef WRAP\n\t\t\t#include \"particle_wrapVS\"\n\t\t#endif\n\t#endif\n\t#ifdef ALIGN_TO_MOTION\n\t\t#include \"particle_pointAlongVS\"\n\t#endif\n\t#ifdef USE_MESH\n\t\t#include \"particle_meshVS\"\n\t#else\n\t\t#ifdef CUSTOM_FACE\n\t\t\t#include \"particle_customFaceVS\"\n\t\t#else\n\t\t\t#include \"particle_billboardVS\"\n\t\t#endif\n\t#endif\n\t#if NORMAL == VERTEX\n\t\t#include \"particle_normalVS\"\n\t#endif\n\t#if NORMAL == MAP\n\t\t#include \"particle_TBNVS\"\n\t#endif\n\t#ifdef STRETCH\n\t\t#include \"particle_stretchVS\"\n\t#endif\n\t#ifdef PARTICLE_GPU\n\t\t#include \"particle_endVS\"\n\t#else\n\t\t#include \"particle_cpu_endVS\"\n\t#endif\n\t#ifdef SOFT\n\t\t#include \"particle_softVS\"\n\t#endif\n\treturn output;\n}\n`;\n\nvar particle_softPS = `\n\tvar depth: f32 = getLinearScreenDepthFrag();\n\tvar particleDepth: f32 = vDepth;\n\tvar depthDiff: f32 = saturate(abs(particleDepth - depth) * uniform.softening);\n\ta = a * depthDiff;\n`;\n\nvar particle_softVS = `\n\toutput.vDepth = getLinearDepth(localPos);\n`;\n\nvar particle_stretchVS = `\n\tlet moveDir: vec3f = inVel * uniform.stretch;\n\tvar posPrev: vec3f = particlePos - moveDir;\n\tposPrev = posPrev + particlePosMoved;\n\tlet viewRotationTemp: mat3x3f = mat3x3f(uniform.matrix_view[0].xyz, uniform.matrix_view[1].xyz, uniform.matrix_view[2].xyz);\n\tlet centerToVertexV: vec2f = normalize((viewRotationTemp * localPos).xy);\n\tlet interpolation: f32 = dot(-velocityV, centerToVertexV) * 0.5 + 0.5;\n\tparticlePos = mix(particlePos, posPrev, interpolation);\n`;\n\nvar particle_TBNVS = `\n\tlet rot3 = mat3x3f(\n\t\tvec3f(rotMatrix[0][0], rotMatrix[1][0], 0.0),\n\t\tvec3f(rotMatrix[0][1], rotMatrix[1][1], 0.0),\n\t\tvec3f(0.0, 0.0, 1.0)\n\t);\n\tlet viewBasis = mat3x3f(\n\t\t-uniform.matrix_viewInverse[0].xyz,\n\t\t-uniform.matrix_viewInverse[1].xyz,\n\t\tuniform.matrix_viewInverse[2].xyz\n\t);\n\tlet tempMat = viewBasis * rot3;\n\toutput.ParticleMat0 = tempMat[0];\n\toutput.ParticleMat1 = tempMat[1];\n\toutput.ParticleMat2 = tempMat[2];\n`;\n\nvar particle_wrapVS = `\n\tlet origParticlePos: vec3f = particlePos;\n\tparticlePos = particlePos - uniform.matrix_model[3].xyz;\n\tparticlePos = (particlePos % uniform.wrapBounds) - uniform.wrapBounds * 0.5;\n\tparticlePos = particlePos + uniform.matrix_model[3].xyz;\n\tparticlePosMoved = particlePos - origParticlePos;\n`;\n\nconst particleChunksWGSL = {\n\t\tparticlePS,\n\t\tparticleVS,\n\t\tparticleAnimFrameClampVS,\n\t\tparticleAnimFrameLoopVS,\n\t\tparticleAnimTexVS,\n\t\tparticleInputFloatPS,\n\t\tparticleInputRgba8PS,\n\t\tparticleOutputFloatPS,\n\t\tparticleOutputRgba8PS,\n\t\tparticleUpdaterAABBPS,\n\t\tparticleUpdaterEndPS,\n\t\tparticleUpdaterInitPS,\n\t\tparticleUpdaterNoRespawnPS,\n\t\tparticleUpdaterOnStopPS,\n\t\tparticleUpdaterRespawnPS,\n\t\tparticleUpdaterSpherePS,\n\t\tparticleUpdaterStartPS,\n\t\tparticle_billboardVS,\n\t\tparticle_blendAddPS,\n\t\tparticle_blendMultiplyPS,\n\t\tparticle_blendNormalPS,\n\t\tparticle_cpuVS,\n\t\tparticle_cpu_endVS,\n\t\tparticle_customFaceVS,\n\t\tparticle_endPS,\n\t\tparticle_endVS,\n\t\tparticle_halflambertPS,\n\t\tparticle_initVS,\n\t\tparticle_lambertPS,\n\t\tparticle_lightingPS,\n\t\tparticle_localShiftVS,\n\t\tparticle_meshVS,\n\t\tparticle_normalVS,\n\t\tparticle_normalMapPS,\n\t\tparticle_pointAlongVS,\n\t\tparticle_simulationPS,\n\t\tparticle_shaderPS,\n\t\tparticle_shaderVS,\n\t\tparticle_softPS,\n\t\tparticle_softVS,\n\t\tparticle_stretchVS,\n\t\tparticle_TBNVS,\n\t\tparticle_wrapVS\n};\n\nconst _schema$a = [\n\t\t'enabled',\n\t\t'autoPlay',\n\t\t'numParticles',\n\t\t'lifetime',\n\t\t'rate',\n\t\t'rate2',\n\t\t'startAngle',\n\t\t'startAngle2',\n\t\t'loop',\n\t\t'preWarm',\n\t\t'lighting',\n\t\t'halfLambert',\n\t\t'intensity',\n\t\t'depthWrite',\n\t\t'noFog',\n\t\t'depthSoftening',\n\t\t'sort',\n\t\t'blendType',\n\t\t'stretch',\n\t\t'alignToMotion',\n\t\t'emitterShape',\n\t\t'emitterExtents',\n\t\t'emitterExtentsInner',\n\t\t'emitterRadius',\n\t\t'emitterRadiusInner',\n\t\t'initialVelocity',\n\t\t'wrap',\n\t\t'wrapBounds',\n\t\t'localSpace',\n\t\t'screenSpace',\n\t\t'colorMapAsset',\n\t\t'normalMapAsset',\n\t\t'mesh',\n\t\t'meshAsset',\n\t\t'renderAsset',\n\t\t'orientation',\n\t\t'particleNormal',\n\t\t'localVelocityGraph',\n\t\t'localVelocityGraph2',\n\t\t'velocityGraph',\n\t\t'velocityGraph2',\n\t\t'rotationSpeedGraph',\n\t\t'rotationSpeedGraph2',\n\t\t'radialSpeedGraph',\n\t\t'radialSpeedGraph2',\n\t\t'scaleGraph',\n\t\t'scaleGraph2',\n\t\t'colorGraph',\n\t\t'colorGraph2',\n\t\t'alphaGraph',\n\t\t'alphaGraph2',\n\t\t'colorMap',\n\t\t'normalMap',\n\t\t'animTilesX',\n\t\t'animTilesY',\n\t\t'animStartFrame',\n\t\t'animNumFrames',\n\t\t'animNumAnimations',\n\t\t'animIndex',\n\t\t'randomizeAnimIndex',\n\t\t'animSpeed',\n\t\t'animLoop',\n\t\t'layers'\n];\nclass ParticleSystemComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'particlesystem';\n\t\t\t\tthis.ComponentType = ParticleSystemComponent;\n\t\t\t\tthis.DataType = ParticleSystemComponentData;\n\t\t\t\tthis.schema = _schema$a;\n\t\t\t\tthis.propertyTypes = {\n\t\t\t\t\t\temitterExtents: 'vec3',\n\t\t\t\t\t\temitterExtentsInner: 'vec3',\n\t\t\t\t\t\tparticleNormal: 'vec3',\n\t\t\t\t\t\twrapBounds: 'vec3',\n\t\t\t\t\t\tlocalVelocityGraph: 'curveset',\n\t\t\t\t\t\tlocalVelocityGraph2: 'curveset',\n\t\t\t\t\t\tvelocityGraph: 'curveset',\n\t\t\t\t\t\tvelocityGraph2: 'curveset',\n\t\t\t\t\t\tcolorGraph: 'curveset',\n\t\t\t\t\t\tcolorGraph2: 'curveset',\n\t\t\t\t\t\talphaGraph: 'curve',\n\t\t\t\t\t\talphaGraph2: 'curve',\n\t\t\t\t\t\trotationSpeedGraph: 'curve',\n\t\t\t\t\t\trotationSpeedGraph2: 'curve',\n\t\t\t\t\t\tradialSpeedGraph: 'curve',\n\t\t\t\t\t\tradialSpeedGraph2: 'curve',\n\t\t\t\t\t\tscaleGraph: 'curve',\n\t\t\t\t\t\tscaleGraph2: 'curve'\n\t\t\t\t};\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t\t\tShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_GLSL).add(particleChunksGLSL);\n\t\t\t\tShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_WGSL).add(particleChunksWGSL);\n\t\t}\n\t\tinitializeComponentData(component, _data, properties) {\n\t\t\t\tconst data = {};\n\t\t\t\tproperties = [];\n\t\t\t\tconst types = this.propertyTypes;\n\t\t\t\tif (_data.mesh instanceof Asset || typeof _data.mesh === 'number') {\n\t\t\t\t\t\t_data.meshAsset = _data.mesh;\n\t\t\t\t\t\tdelete _data.mesh;\n\t\t\t\t}\n\t\t\t\tfor(const prop in _data){\n\t\t\t\t\t\tif (_data.hasOwnProperty(prop)) {\n\t\t\t\t\t\t\t\tproperties.push(prop);\n\t\t\t\t\t\t\t\tdata[prop] = _data[prop];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (types[prop] === 'vec3') {\n\t\t\t\t\t\t\t\tif (Array.isArray(data[prop])) {\n\t\t\t\t\t\t\t\t\t\tdata[prop] = new Vec3(data[prop][0], data[prop][1], data[prop][2]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (types[prop] === 'curve') {\n\t\t\t\t\t\t\t\tif (!(data[prop] instanceof Curve)) {\n\t\t\t\t\t\t\t\t\t\tconst t = data[prop].type;\n\t\t\t\t\t\t\t\t\t\tdata[prop] = new Curve(data[prop].keys);\n\t\t\t\t\t\t\t\t\t\tdata[prop].type = t;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (types[prop] === 'curveset') {\n\t\t\t\t\t\t\t\tif (!(data[prop] instanceof CurveSet)) {\n\t\t\t\t\t\t\t\t\t\tconst t = data[prop].type;\n\t\t\t\t\t\t\t\t\t\tdata[prop] = new CurveSet(data[prop].keys);\n\t\t\t\t\t\t\t\t\t\tdata[prop].type = t;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (data.layers && Array.isArray(data.layers)) {\n\t\t\t\t\t\t\t\tdata.layers = data.layers.slice(0);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst source = entity.particlesystem.data;\n\t\t\t\tconst schema = this.schema;\n\t\t\t\tconst data = {};\n\t\t\t\tfor(let i = 0, len = schema.length; i < len; i++){\n\t\t\t\t\t\tconst prop = schema[i];\n\t\t\t\t\t\tlet sourceProp = source[prop];\n\t\t\t\t\t\tif (sourceProp instanceof Vec3 || sourceProp instanceof Curve || sourceProp instanceof CurveSet) {\n\t\t\t\t\t\t\t\tsourceProp = sourceProp.clone();\n\t\t\t\t\t\t\t\tdata[prop] = sourceProp;\n\t\t\t\t\t\t} else if (prop === 'layers') {\n\t\t\t\t\t\t\t\tdata.layers = source.layers.slice(0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (sourceProp !== null && sourceProp !== undefined) {\n\t\t\t\t\t\t\t\t\t\tdata[prop] = sourceProp;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this.addComponent(clone, data);\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tconst components = this.store;\n\t\t\t\tconst stats = this.app.stats.particles;\n\t\t\t\tconst composition = this.app.scene.layers;\n\t\t\t\tfor(let i = 0; i < composition.layerList.length; i++){\n\t\t\t\t\t\tcomposition.layerList[i].requiresLightCube = false;\n\t\t\t\t}\n\t\t\t\tfor(const id in components){\n\t\t\t\t\t\tif (components.hasOwnProperty(id)) {\n\t\t\t\t\t\t\t\tconst component = components[id];\n\t\t\t\t\t\t\t\tconst entity = component.entity;\n\t\t\t\t\t\t\t\tconst data = component.data;\n\t\t\t\t\t\t\t\tif (data.enabled && entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tconst emitter = entity.particlesystem.emitter;\n\t\t\t\t\t\t\t\t\t\tif (!emitter?.meshInstance.visible) continue;\n\t\t\t\t\t\t\t\t\t\tif (emitter.lighting) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst layers = data.layers;\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst layer = composition.getLayerById(layers[i]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlayer.requiresLightCube = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!data.paused) {\n\t\t\t\t\t\t\t\t\t\t\t\tlet numSteps = 0;\n\t\t\t\t\t\t\t\t\t\t\t\temitter.simTime += dt;\n\t\t\t\t\t\t\t\t\t\t\t\tif (emitter.simTime >= emitter.fixedTimeStep) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnumSteps = Math.floor(emitter.simTime / emitter.fixedTimeStep);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\temitter.simTime -= numSteps * emitter.fixedTimeStep;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (numSteps) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnumSteps = Math.min(numSteps, emitter.maxSubSteps);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < numSteps; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\temitter.addTime(emitter.fixedTimeStep, false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstats._updatesPerFrame += numSteps;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstats._frameTime += emitter._addTimeTime;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\temitter._addTimeTime = 0;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\temitter.finishFrame();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tcomponent.onBeforeRemove();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t}\n}\n\nclass SkinInstanceCachedObject extends RefCountedObject {\n\t\tconstructor(skin, skinInstance){\n\t\t\t\tsuper();\n\t\t\t\tthis.skin = skin;\n\t\t\t\tthis.skinInstance = skinInstance;\n\t\t}\n}\nclass SkinInstanceCache {\n\t\tstatic{\n\t\t\t\tthis._skinInstanceCache = new Map();\n\t\t}\n\t\tstatic createCachedSkinInstance(skin, rootBone, entity) {\n\t\t\t\tlet skinInst = SkinInstanceCache.getCachedSkinInstance(skin, rootBone);\n\t\t\t\tif (!skinInst) {\n\t\t\t\t\t\tskinInst = new SkinInstance(skin);\n\t\t\t\t\t\tskinInst.resolve(rootBone, entity);\n\t\t\t\t\t\tSkinInstanceCache.addCachedSkinInstance(skin, rootBone, skinInst);\n\t\t\t\t}\n\t\t\t\treturn skinInst;\n\t\t}\n\t\tstatic getCachedSkinInstance(skin, rootBone) {\n\t\t\t\tlet skinInstance = null;\n\t\t\t\tconst cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);\n\t\t\t\tif (cachedObjArray) {\n\t\t\t\t\t\tconst cachedObj = cachedObjArray.find((element)=>element.skin === skin);\n\t\t\t\t\t\tif (cachedObj) {\n\t\t\t\t\t\t\t\tcachedObj.incRefCount();\n\t\t\t\t\t\t\t\tskinInstance = cachedObj.skinInstance;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn skinInstance;\n\t\t}\n\t\tstatic addCachedSkinInstance(skin, rootBone, skinInstance) {\n\t\t\t\tlet cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);\n\t\t\t\tif (!cachedObjArray) {\n\t\t\t\t\t\tcachedObjArray = [];\n\t\t\t\t\t\tSkinInstanceCache._skinInstanceCache.set(rootBone, cachedObjArray);\n\t\t\t\t}\n\t\t\t\tlet cachedObj = cachedObjArray.find((element)=>element.skin === skin);\n\t\t\t\tif (!cachedObj) {\n\t\t\t\t\t\tcachedObj = new SkinInstanceCachedObject(skin, skinInstance);\n\t\t\t\t\t\tcachedObjArray.push(cachedObj);\n\t\t\t\t}\n\t\t\t\tcachedObj.incRefCount();\n\t\t}\n\t\tstatic removeCachedSkinInstance(skinInstance) {\n\t\t\t\tif (skinInstance) {\n\t\t\t\t\t\tconst rootBone = skinInstance.rootBone;\n\t\t\t\t\t\tif (rootBone) {\n\t\t\t\t\t\t\t\tconst cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);\n\t\t\t\t\t\t\t\tif (cachedObjArray) {\n\t\t\t\t\t\t\t\t\t\tconst cachedObjIndex = cachedObjArray.findIndex((element)=>element.skinInstance === skinInstance);\n\t\t\t\t\t\t\t\t\t\tif (cachedObjIndex >= 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst cachedObj = cachedObjArray[cachedObjIndex];\n\t\t\t\t\t\t\t\t\t\t\t\tcachedObj.decRefCount();\n\t\t\t\t\t\t\t\t\t\t\t\tif (cachedObj.refCount === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcachedObjArray.splice(cachedObjIndex, 1);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (!cachedObjArray.length) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tSkinInstanceCache._skinInstanceCache.delete(rootBone);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (skinInstance) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tskinInstance.destroy();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcachedObj.skinInstance = null;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass AssetReference {\n\t\tconstructor(propertyName, parent, registry, callbacks, scope){\n\t\t\t\tthis._evtLoadById = null;\n\t\t\t\tthis._evtUnloadById = null;\n\t\t\t\tthis._evtAddById = null;\n\t\t\t\tthis._evtRemoveById = null;\n\t\t\t\tthis._evtLoadByUrl = null;\n\t\t\t\tthis._evtAddByUrl = null;\n\t\t\t\tthis._evtRemoveByUrl = null;\n\t\t\t\tthis.propertyName = propertyName;\n\t\t\t\tthis.parent = parent;\n\t\t\t\tthis._scope = scope;\n\t\t\t\tthis._registry = registry;\n\t\t\t\tthis.id = null;\n\t\t\t\tthis.url = null;\n\t\t\t\tthis.asset = null;\n\t\t\t\tthis._onAssetLoad = callbacks.load;\n\t\t\t\tthis._onAssetAdd = callbacks.add;\n\t\t\t\tthis._onAssetRemove = callbacks.remove;\n\t\t\t\tthis._onAssetUnload = callbacks.unload;\n\t\t}\n\t\tset id(value) {\n\t\t\t\tif (this.url) throw Error('Can\\'t set id and url');\n\t\t\t\tthis._unbind();\n\t\t\t\tthis._id = value;\n\t\t\t\tthis.asset = this._registry.get(this._id);\n\t\t\t\tthis._bind();\n\t\t}\n\t\tget id() {\n\t\t\t\treturn this._id;\n\t\t}\n\t\tset url(value) {\n\t\t\t\tif (this.id) throw Error('Can\\'t set id and url');\n\t\t\t\tthis._unbind();\n\t\t\t\tthis._url = value;\n\t\t\t\tthis.asset = this._registry.getByUrl(this._url);\n\t\t\t\tthis._bind();\n\t\t}\n\t\tget url() {\n\t\t\t\treturn this._url;\n\t\t}\n\t\t_bind() {\n\t\t\t\tif (this.id) {\n\t\t\t\t\t\tif (this._onAssetLoad) this._evtLoadById = this._registry.on(`load:${this.id}`, this._onLoad, this);\n\t\t\t\t\t\tif (this._onAssetAdd) this._evtAddById = this._registry.once(`add:${this.id}`, this._onAdd, this);\n\t\t\t\t\t\tif (this._onAssetRemove) this._evtRemoveById = this._registry.on(`remove:${this.id}`, this._onRemove, this);\n\t\t\t\t\t\tif (this._onAssetUnload) this._evtUnloadById = this._registry.on(`unload:${this.id}`, this._onUnload, this);\n\t\t\t\t}\n\t\t\t\tif (this.url) {\n\t\t\t\t\t\tif (this._onAssetLoad) this._evtLoadByUrl = this._registry.on(`load:url:${this.url}`, this._onLoad, this);\n\t\t\t\t\t\tif (this._onAssetAdd) this._evtAddByUrl = this._registry.once(`add:url:${this.url}`, this._onAdd, this);\n\t\t\t\t\t\tif (this._onAssetRemove) this._evtRemoveByUrl = this._registry.on(`remove:url:${this.url}`, this._onRemove, this);\n\t\t\t\t}\n\t\t}\n\t\t_unbind() {\n\t\t\t\tif (this.id) {\n\t\t\t\t\t\tthis._evtLoadById?.off();\n\t\t\t\t\t\tthis._evtLoadById = null;\n\t\t\t\t\t\tthis._evtAddById?.off();\n\t\t\t\t\t\tthis._evtAddById = null;\n\t\t\t\t\t\tthis._evtRemoveById?.off();\n\t\t\t\t\t\tthis._evtRemoveById = null;\n\t\t\t\t\t\tthis._evtUnloadById?.off();\n\t\t\t\t\t\tthis._evtUnloadById = null;\n\t\t\t\t}\n\t\t\t\tif (this.url) {\n\t\t\t\t\t\tthis._evtLoadByUrl?.off();\n\t\t\t\t\t\tthis._evtLoadByUrl = null;\n\t\t\t\t\t\tthis._evtAddByUrl?.off();\n\t\t\t\t\t\tthis._evtAddByUrl = null;\n\t\t\t\t\t\tthis._evtRemoveByUrl?.off();\n\t\t\t\t\t\tthis._evtRemoveByUrl = null;\n\t\t\t\t}\n\t\t}\n\t\t_onLoad(asset) {\n\t\t\t\tthis._onAssetLoad.call(this._scope, this.propertyName, this.parent, asset);\n\t\t}\n\t\t_onAdd(asset) {\n\t\t\t\tthis.asset = asset;\n\t\t\t\tthis._onAssetAdd.call(this._scope, this.propertyName, this.parent, asset);\n\t\t}\n\t\t_onRemove(asset) {\n\t\t\t\tthis._onAssetRemove.call(this._scope, this.propertyName, this.parent, asset);\n\t\t\t\tthis.asset = null;\n\t\t}\n\t\t_onUnload(asset) {\n\t\t\t\tthis._onAssetUnload.call(this._scope, this.propertyName, this.parent, asset);\n\t\t}\n}\n\nclass RenderComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._type = 'asset', this._castShadows = true, this._receiveShadows = true, this._castShadowsLightmap = true, this._lightmapped = false, this._lightmapSizeMultiplier = 1, this.isStatic = false, this._batchGroupId = -1, this._layers = [\n\t\t\t\t\t\tLAYERID_WORLD\n\t\t\t\t], this._renderStyle = RENDERSTYLE_SOLID, this._meshInstances = [], this._customAabb = null, this._area = null, this._materialReferences = [], this._rootBone = null, this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null, this._evtSetMeshes = null;\n\t\t\t\tthis._assetReference = new AssetReference('asset', this, system.app.assets, {\n\t\t\t\t\t\tadd: this._onRenderAssetAdded,\n\t\t\t\t\t\tload: this._onRenderAssetLoad,\n\t\t\t\t\t\tremove: this._onRenderAssetRemove,\n\t\t\t\t\t\tunload: this._onRenderAssetUnload\n\t\t\t\t}, this);\n\t\t\t\tthis._material = system.defaultMaterial;\n\t\t\t\tentity.on('remove', this.onRemoveChild, this);\n\t\t\t\tentity.on('removehierarchy', this.onRemoveChild, this);\n\t\t\t\tentity.on('insert', this.onInsertChild, this);\n\t\t\t\tentity.on('inserthierarchy', this.onInsertChild, this);\n\t\t}\n\t\tset renderStyle(renderStyle) {\n\t\t\t\tif (this._renderStyle !== renderStyle) {\n\t\t\t\t\t\tthis._renderStyle = renderStyle;\n\t\t\t\t\t\tMeshInstance._prepareRenderStyleForArray(this._meshInstances, renderStyle);\n\t\t\t\t}\n\t\t}\n\t\tget renderStyle() {\n\t\t\t\treturn this._renderStyle;\n\t\t}\n\t\tset customAabb(value) {\n\t\t\t\tthis._customAabb = value;\n\t\t\t\tconst mi = this._meshInstances;\n\t\t\t\tif (mi) {\n\t\t\t\t\t\tfor(let i = 0; i < mi.length; i++){\n\t\t\t\t\t\t\t\tmi[i].setCustomAabb(this._customAabb);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget customAabb() {\n\t\t\t\treturn this._customAabb;\n\t\t}\n\t\tset type(value) {\n\t\t\t\tif (this._type !== value) {\n\t\t\t\t\t\tthis._area = null;\n\t\t\t\t\t\tthis._type = value;\n\t\t\t\t\t\tthis.destroyMeshInstances();\n\t\t\t\t\t\tif (value !== 'asset') {\n\t\t\t\t\t\t\t\tlet material = this._material;\n\t\t\t\t\t\t\t\tif (!material || material === this.system.defaultMaterial) {\n\t\t\t\t\t\t\t\t\t\tmaterial = this._materialReferences[0] && this._materialReferences[0].asset && this._materialReferences[0].asset.resource;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst primData = getShapePrimitive(this.system.app.graphicsDevice, value);\n\t\t\t\t\t\t\t\tthis._area = primData.area;\n\t\t\t\t\t\t\t\tthis.meshInstances = [\n\t\t\t\t\t\t\t\t\t\tnew MeshInstance(primData.mesh, material || this.system.defaultMaterial, this.entity)\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tset meshInstances(value) {\n\t\t\t\tthis.destroyMeshInstances();\n\t\t\t\tthis._meshInstances = value;\n\t\t\t\tif (this._meshInstances) {\n\t\t\t\t\t\tconst mi = this._meshInstances;\n\t\t\t\t\t\tfor(let i = 0; i < mi.length; i++){\n\t\t\t\t\t\t\t\tif (!mi[i].node) {\n\t\t\t\t\t\t\t\t\t\tmi[i].node = this.entity;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmi[i].castShadow = this._castShadows;\n\t\t\t\t\t\t\t\tmi[i].receiveShadow = this._receiveShadows;\n\t\t\t\t\t\t\t\tmi[i].renderStyle = this._renderStyle;\n\t\t\t\t\t\t\t\tmi[i].setLightmapped(this._lightmapped);\n\t\t\t\t\t\t\t\tmi[i].setCustomAabb(this._customAabb);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.addToLayers();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget meshInstances() {\n\t\t\t\treturn this._meshInstances;\n\t\t}\n\t\tset lightmapped(value) {\n\t\t\t\tif (value !== this._lightmapped) {\n\t\t\t\t\t\tthis._lightmapped = value;\n\t\t\t\t\t\tconst mi = this._meshInstances;\n\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < mi.length; i++){\n\t\t\t\t\t\t\t\t\t\tmi[i].setLightmapped(value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget lightmapped() {\n\t\t\t\treturn this._lightmapped;\n\t\t}\n\t\tset castShadows(value) {\n\t\t\t\tif (this._castShadows !== value) {\n\t\t\t\t\t\tconst mi = this._meshInstances;\n\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\tconst layers = this.layers;\n\t\t\t\t\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\t\t\t\t\tif (this._castShadows && !value) {\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst layer = scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlayer.removeShadowCasters(mi);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor(let i = 0; i < mi.length; i++){\n\t\t\t\t\t\t\t\t\t\tmi[i].castShadow = value;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!this._castShadows && value) {\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst layer = scene.layers.getLayerById(layers[i]);\n\t\t\t\t\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlayer.addShadowCasters(mi);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._castShadows = value;\n\t\t\t\t}\n\t\t}\n\t\tget castShadows() {\n\t\t\t\treturn this._castShadows;\n\t\t}\n\t\tset receiveShadows(value) {\n\t\t\t\tif (this._receiveShadows !== value) {\n\t\t\t\t\t\tthis._receiveShadows = value;\n\t\t\t\t\t\tconst mi = this._meshInstances;\n\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < mi.length; i++){\n\t\t\t\t\t\t\t\t\t\tmi[i].receiveShadow = value;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget receiveShadows() {\n\t\t\t\treturn this._receiveShadows;\n\t\t}\n\t\tset castShadowsLightmap(value) {\n\t\t\t\tthis._castShadowsLightmap = value;\n\t\t}\n\t\tget castShadowsLightmap() {\n\t\t\t\treturn this._castShadowsLightmap;\n\t\t}\n\t\tset lightmapSizeMultiplier(value) {\n\t\t\t\tthis._lightmapSizeMultiplier = value;\n\t\t}\n\t\tget lightmapSizeMultiplier() {\n\t\t\t\treturn this._lightmapSizeMultiplier;\n\t\t}\n\t\tset layers(value) {\n\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\tlet layer;\n\t\t\t\tif (this._meshInstances) {\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tlayer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\tlayer.removeMeshInstances(this._meshInstances);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._layers.length = 0;\n\t\t\t\tfor(let i = 0; i < value.length; i++){\n\t\t\t\t\t\tthis._layers[i] = value[i];\n\t\t\t\t}\n\t\t\t\tif (!this.enabled || !this.entity.enabled || !this._meshInstances) return;\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tlayer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.addMeshInstances(this._meshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tset batchGroupId(value) {\n\t\t\t\tif (this._batchGroupId !== value) {\n\t\t\t\t\t\tif (this.entity.enabled && this._batchGroupId >= 0) {\n\t\t\t\t\t\t\t\tthis.system.app.batcher?.remove(BatchGroup.RENDER, this.batchGroupId, this.entity);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.entity.enabled && value >= 0) {\n\t\t\t\t\t\t\t\tthis.system.app.batcher?.insert(BatchGroup.RENDER, value, this.entity);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (value < 0 && this._batchGroupId >= 0 && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.addToLayers();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._batchGroupId = value;\n\t\t\t\t}\n\t\t}\n\t\tget batchGroupId() {\n\t\t\t\treturn this._batchGroupId;\n\t\t}\n\t\tset material(value) {\n\t\t\t\tif (this._material !== value) {\n\t\t\t\t\t\tthis._material = value;\n\t\t\t\t\t\tif (this._meshInstances && this._type !== 'asset') {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < this._meshInstances.length; i++){\n\t\t\t\t\t\t\t\t\t\tthis._meshInstances[i].material = value;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget material() {\n\t\t\t\treturn this._material;\n\t\t}\n\t\tset materialAssets(value = []) {\n\t\t\t\tif (this._materialReferences.length > value.length) {\n\t\t\t\t\t\tfor(let i = value.length; i < this._materialReferences.length; i++){\n\t\t\t\t\t\t\t\tthis._materialReferences[i].id = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._materialReferences.length = value.length;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < value.length; i++){\n\t\t\t\t\t\tif (!this._materialReferences[i]) {\n\t\t\t\t\t\t\t\tthis._materialReferences.push(new AssetReference(i, this, this.system.app.assets, {\n\t\t\t\t\t\t\t\t\t\tadd: this._onMaterialAdded,\n\t\t\t\t\t\t\t\t\t\tload: this._onMaterialLoad,\n\t\t\t\t\t\t\t\t\t\tremove: this._onMaterialRemove,\n\t\t\t\t\t\t\t\t\t\tunload: this._onMaterialUnload\n\t\t\t\t\t\t\t\t}, this));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (value[i]) {\n\t\t\t\t\t\t\t\tconst id = value[i] instanceof Asset ? value[i].id : value[i];\n\t\t\t\t\t\t\t\tif (this._materialReferences[i].id !== id) {\n\t\t\t\t\t\t\t\t\t\tthis._materialReferences[i].id = id;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this._materialReferences[i].asset) {\n\t\t\t\t\t\t\t\t\t\tthis._onMaterialAdded(i, this, this._materialReferences[i].asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._materialReferences[i].id = null;\n\t\t\t\t\t\t\t\tif (this._meshInstances[i]) {\n\t\t\t\t\t\t\t\t\t\tthis._meshInstances[i].material = this.system.defaultMaterial;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget materialAssets() {\n\t\t\t\treturn this._materialReferences.map((ref)=>{\n\t\t\t\t\t\treturn ref.id;\n\t\t\t\t});\n\t\t}\n\t\tset asset(value) {\n\t\t\t\tconst id = value instanceof Asset ? value.id : value;\n\t\t\t\tif (this._assetReference.id === id) return;\n\t\t\t\tif (this._assetReference.asset && this._assetReference.asset.resource) {\n\t\t\t\t\t\tthis._onRenderAssetRemove();\n\t\t\t\t}\n\t\t\t\tthis._assetReference.id = id;\n\t\t\t\tif (this._assetReference.asset) {\n\t\t\t\t\t\tthis._onRenderAssetAdded();\n\t\t\t\t}\n\t\t}\n\t\tget asset() {\n\t\t\t\treturn this._assetReference.id;\n\t\t}\n\t\tassignAsset(asset) {\n\t\t\t\tconst id = asset instanceof Asset ? asset.id : asset;\n\t\t\t\tthis._assetReference.id = id;\n\t\t}\n\t\tset rootBone(value) {\n\t\t\t\tif (this._rootBone !== value) {\n\t\t\t\t\t\tconst isString = typeof value === 'string';\n\t\t\t\t\t\tif (this._rootBone && isString && this._rootBone.getGuid() === value) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._rootBone) {\n\t\t\t\t\t\t\t\tthis._clearSkinInstances();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (value instanceof GraphNode) {\n\t\t\t\t\t\t\t\tthis._rootBone = value;\n\t\t\t\t\t\t} else if (isString) {\n\t\t\t\t\t\t\t\tthis._rootBone = this.system.app.getEntityFromIndex(value) || null;\n\t\t\t\t\t\t\t\tif (!this._rootBone) ;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._rootBone = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._rootBone) {\n\t\t\t\t\t\t\t\tthis._cloneSkinInstances();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget rootBone() {\n\t\t\t\treturn this._rootBone;\n\t\t}\n\t\tdestroyMeshInstances() {\n\t\t\t\tconst meshInstances = this._meshInstances;\n\t\t\t\tif (meshInstances) {\n\t\t\t\t\t\tthis.removeFromLayers();\n\t\t\t\t\t\tthis._clearSkinInstances();\n\t\t\t\t\t\tfor(let i = 0; i < meshInstances.length; i++){\n\t\t\t\t\t\t\t\tmeshInstances[i].destroy();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._meshInstances.length = 0;\n\t\t\t\t}\n\t\t}\n\t\taddToLayers() {\n\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\tconst layer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.addMeshInstances(this._meshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremoveFromLayers() {\n\t\t\t\tif (this._meshInstances && this._meshInstances.length) {\n\t\t\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tconst layer = layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\tlayer.removeMeshInstances(this._meshInstances);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonRemoveChild() {\n\t\t\t\tthis.removeFromLayers();\n\t\t}\n\t\tonInsertChild() {\n\t\t\t\tif (this._meshInstances && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.addToLayers();\n\t\t\t\t}\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.destroyMeshInstances();\n\t\t\t\tthis.asset = null;\n\t\t\t\tthis.materialAsset = null;\n\t\t\t\tthis._assetReference.id = null;\n\t\t\t\tfor(let i = 0; i < this._materialReferences.length; i++){\n\t\t\t\t\t\tthis._materialReferences[i].id = null;\n\t\t\t\t}\n\t\t\t\tthis.entity.off('remove', this.onRemoveChild, this);\n\t\t\t\tthis.entity.off('insert', this.onInsertChild, this);\n\t\t}\n\t\tonLayersChanged(oldComp, newComp) {\n\t\t\t\tthis.addToLayers();\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t}\n\t\tonLayerAdded(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.addMeshInstances(this._meshInstances);\n\t\t}\n\t\tonLayerRemoved(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.removeMeshInstances(this._meshInstances);\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst app = this.system.app;\n\t\t\t\tconst scene = app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tif (this._rootBone) {\n\t\t\t\t\t\tthis._cloneSkinInstances();\n\t\t\t\t}\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this.onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tconst isAsset = this._type === 'asset';\n\t\t\t\tif (this._meshInstances && this._meshInstances.length) {\n\t\t\t\t\t\tthis.addToLayers();\n\t\t\t\t} else if (isAsset && this.asset) {\n\t\t\t\t\t\tthis._onRenderAssetAdded();\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._materialReferences.length; i++){\n\t\t\t\t\t\tif (this._materialReferences[i].asset) {\n\t\t\t\t\t\t\t\tthis.system.app.assets.load(this._materialReferences[i].asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tapp.batcher?.insert(BatchGroup.RENDER, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst app = this.system.app;\n\t\t\t\tconst scene = app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (this._rootBone) {\n\t\t\t\t\t\tthis._clearSkinInstances();\n\t\t\t\t}\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tapp.batcher?.remove(BatchGroup.RENDER, this.batchGroupId, this.entity);\n\t\t\t\t}\n\t\t\t\tthis.removeFromLayers();\n\t\t}\n\t\thide() {\n\t\t\t\tif (this._meshInstances) {\n\t\t\t\t\t\tfor(let i = 0; i < this._meshInstances.length; i++){\n\t\t\t\t\t\t\t\tthis._meshInstances[i].visible = false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tshow() {\n\t\t\t\tif (this._meshInstances) {\n\t\t\t\t\t\tfor(let i = 0; i < this._meshInstances.length; i++){\n\t\t\t\t\t\t\t\tthis._meshInstances[i].visible = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onRenderAssetAdded() {\n\t\t\t\tif (!this._assetReference.asset) return;\n\t\t\t\tif (this._assetReference.asset.resource) {\n\t\t\t\t\t\tthis._onRenderAssetLoad();\n\t\t\t\t} else if (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.system.app.assets.load(this._assetReference.asset);\n\t\t\t\t}\n\t\t}\n\t\t_onRenderAssetLoad() {\n\t\t\t\tthis.destroyMeshInstances();\n\t\t\t\tif (this._assetReference.asset) {\n\t\t\t\t\t\tconst render = this._assetReference.asset.resource;\n\t\t\t\t\t\tthis._evtSetMeshes?.off();\n\t\t\t\t\t\tthis._evtSetMeshes = render.on('set:meshes', this._onSetMeshes, this);\n\t\t\t\t\t\tif (render.meshes) {\n\t\t\t\t\t\t\t\tthis._onSetMeshes(render.meshes);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onSetMeshes(meshes) {\n\t\t\t\tthis._cloneMeshes(meshes);\n\t\t}\n\t\t_clearSkinInstances() {\n\t\t\t\tfor(let i = 0; i < this._meshInstances.length; i++){\n\t\t\t\t\t\tconst meshInstance = this._meshInstances[i];\n\t\t\t\t\t\tSkinInstanceCache.removeCachedSkinInstance(meshInstance.skinInstance);\n\t\t\t\t\t\tmeshInstance.skinInstance = null;\n\t\t\t\t}\n\t\t}\n\t\t_cloneSkinInstances() {\n\t\t\t\tif (this._meshInstances.length && this._rootBone instanceof GraphNode) {\n\t\t\t\t\t\tfor(let i = 0; i < this._meshInstances.length; i++){\n\t\t\t\t\t\t\t\tconst meshInstance = this._meshInstances[i];\n\t\t\t\t\t\t\t\tconst mesh = meshInstance.mesh;\n\t\t\t\t\t\t\t\tif (mesh.skin && !meshInstance.skinInstance) {\n\t\t\t\t\t\t\t\t\t\tmeshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(mesh.skin, this._rootBone, this.entity);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_cloneMeshes(meshes) {\n\t\t\t\tif (meshes && meshes.length) {\n\t\t\t\t\t\tconst meshInstances = [];\n\t\t\t\t\t\tfor(let i = 0; i < meshes.length; i++){\n\t\t\t\t\t\t\t\tconst mesh = meshes[i];\n\t\t\t\t\t\t\t\tconst material = this._materialReferences[i] && this._materialReferences[i].asset && this._materialReferences[i].asset.resource;\n\t\t\t\t\t\t\t\tconst meshInst = new MeshInstance(mesh, material || this.system.defaultMaterial, this.entity);\n\t\t\t\t\t\t\t\tmeshInstances.push(meshInst);\n\t\t\t\t\t\t\t\tif (mesh.morph) {\n\t\t\t\t\t\t\t\t\t\tmeshInst.morphInstance = new MorphInstance(mesh.morph);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.meshInstances = meshInstances;\n\t\t\t\t\t\tthis._cloneSkinInstances();\n\t\t\t\t}\n\t\t}\n\t\t_onRenderAssetUnload() {\n\t\t\t\tif (this._type === 'asset') {\n\t\t\t\t\t\tthis.destroyMeshInstances();\n\t\t\t\t}\n\t\t}\n\t\t_onRenderAssetRemove() {\n\t\t\t\tthis._evtSetMeshes?.off();\n\t\t\t\tthis._evtSetMeshes = null;\n\t\t\t\tthis._onRenderAssetUnload();\n\t\t}\n\t\t_onMaterialAdded(index, component, asset) {\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onMaterialLoad(index, component, asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.system.app.assets.load(asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateMainMaterial(index, material) {\n\t\t\t\tif (index === 0) {\n\t\t\t\t\t\tthis.material = material;\n\t\t\t\t}\n\t\t}\n\t\t_onMaterialLoad(index, component, asset) {\n\t\t\t\tif (this._meshInstances[index]) {\n\t\t\t\t\t\tthis._meshInstances[index].material = asset.resource;\n\t\t\t\t}\n\t\t\t\tthis._updateMainMaterial(index, asset.resource);\n\t\t}\n\t\t_onMaterialRemove(index, component, asset) {\n\t\t\t\tif (this._meshInstances[index]) {\n\t\t\t\t\t\tthis._meshInstances[index].material = this.system.defaultMaterial;\n\t\t\t\t}\n\t\t\t\tthis._updateMainMaterial(index, this.system.defaultMaterial);\n\t\t}\n\t\t_onMaterialUnload(index, component, asset) {\n\t\t\t\tif (this._meshInstances[index]) {\n\t\t\t\t\t\tthis._meshInstances[index].material = this.system.defaultMaterial;\n\t\t\t\t}\n\t\t\t\tthis._updateMainMaterial(index, this.system.defaultMaterial);\n\t\t}\n\t\tresolveDuplicatedEntityReferenceProperties(oldRender, duplicatedIdsMap) {\n\t\t\t\tif (oldRender.rootBone) {\n\t\t\t\t\t\tthis.rootBone = duplicatedIdsMap[oldRender.rootBone.getGuid()];\n\t\t\t\t}\n\t\t}\n}\n\nclass RenderComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$9 = [\n\t\t'enabled'\n];\nconst _properties$1 = [\n\t\t'material',\n\t\t'meshInstances',\n\t\t'asset',\n\t\t'materialAssets',\n\t\t'castShadows',\n\t\t'receiveShadows',\n\t\t'castShadowsLightmap',\n\t\t'lightmapped',\n\t\t'lightmapSizeMultiplier',\n\t\t'renderStyle',\n\t\t'type',\n\t\t'layers',\n\t\t'isStatic',\n\t\t'batchGroupId',\n\t\t'rootBone'\n];\nclass RenderComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'render';\n\t\t\t\tthis.ComponentType = RenderComponent;\n\t\t\t\tthis.DataType = RenderComponentData;\n\t\t\t\tthis.schema = _schema$9;\n\t\t\t\tthis.defaultMaterial = getDefaultMaterial(app.graphicsDevice);\n\t\t\t\tthis.on('beforeremove', this.onRemove, this);\n\t\t}\n\t\tinitializeComponentData(component, _data, properties) {\n\t\t\t\tif (_data.batchGroupId === null || _data.batchGroupId === undefined) {\n\t\t\t\t\t\t_data.batchGroupId = -1;\n\t\t\t\t}\n\t\t\t\tif (_data.layers && _data.layers.length) {\n\t\t\t\t\t\t_data.layers = _data.layers.slice(0);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < _properties$1.length; i++){\n\t\t\t\t\t\tif (_data.hasOwnProperty(_properties$1[i])) {\n\t\t\t\t\t\t\t\tcomponent[_properties$1[i]] = _data[_properties$1[i]];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (_data.aabbCenter && _data.aabbHalfExtents) {\n\t\t\t\t\t\tcomponent.customAabb = new BoundingBox(new Vec3(_data.aabbCenter), new Vec3(_data.aabbHalfExtents));\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, _data, _schema$9);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst data = {};\n\t\t\t\tfor(let i = 0; i < _properties$1.length; i++){\n\t\t\t\t\t\tdata[_properties$1[i]] = entity.render[_properties$1[i]];\n\t\t\t\t}\n\t\t\t\tdata.enabled = entity.render.enabled;\n\t\t\t\tdelete data.meshInstances;\n\t\t\t\tconst component = this.addComponent(clone, data);\n\t\t\t\tconst srcMeshInstances = entity.render.meshInstances;\n\t\t\t\tconst meshes = srcMeshInstances.map((mi)=>mi.mesh);\n\t\t\t\tcomponent._onSetMeshes(meshes);\n\t\t\t\tfor(let m = 0; m < srcMeshInstances.length; m++){\n\t\t\t\t\t\tcomponent.meshInstances[m].material = srcMeshInstances[m].material;\n\t\t\t\t}\n\t\t\t\tif (entity.render.customAabb) {\n\t\t\t\t\t\tcomponent.customAabb = entity.render.customAabb.clone();\n\t\t\t\t}\n\t\t\t\treturn component;\n\t\t}\n\t\tonRemove(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n}\nComponent._buildAccessors(RenderComponent.prototype, _schema$9);\n\nclass ObjectPool {\n\t\tconstructor(constructorFunc, size){\n\t\t\t\tthis._pool = [];\n\t\t\t\tthis._count = 0;\n\t\t\t\tthis._constructor = constructorFunc;\n\t\t\t\tthis._resize(size);\n\t\t}\n\t\t_resize(size) {\n\t\t\t\tif (size > this._pool.length) {\n\t\t\t\t\t\tfor(let i = this._pool.length; i < size; i++){\n\t\t\t\t\t\t\t\tthis._pool[i] = new this._constructor();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tallocate() {\n\t\t\t\tif (this._count >= this._pool.length) {\n\t\t\t\t\t\tthis._resize(this._pool.length * 2);\n\t\t\t\t}\n\t\t\t\treturn this._pool[this._count++];\n\t\t}\n\t\tfreeAll() {\n\t\t\t\tthis._count = 0;\n\t\t}\n}\n\nlet _ammoTransform;\nlet _ammoVec1, _ammoVec2, _ammoQuat;\nconst _quat1 = new Quat();\nconst _quat2 = new Quat();\nconst _vec3 = new Vec3();\nclass RigidBodyComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_CONTACT = 'contact';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_COLLISIONSTART = 'collisionstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_COLLISIONEND = 'collisionend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TRIGGERENTER = 'triggerenter';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TRIGGERLEAVE = 'triggerleave';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.order = -1;\n\t\t}\n\t\tstatic onLibraryLoaded() {\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\t_ammoTransform = new Ammo.btTransform();\n\t\t\t\t\t\t_ammoVec1 = new Ammo.btVector3();\n\t\t\t\t\t\t_ammoVec2 = new Ammo.btVector3();\n\t\t\t\t\t\t_ammoQuat = new Ammo.btQuaternion();\n\t\t\t\t}\n\t\t}\n\t\tstatic onAppDestroy() {\n\t\t\t\tAmmo.destroy(_ammoTransform);\n\t\t\t\tAmmo.destroy(_ammoVec1);\n\t\t\t\tAmmo.destroy(_ammoVec2);\n\t\t\t\tAmmo.destroy(_ammoQuat);\n\t\t\t\t_ammoTransform = null;\n\t\t\t\t_ammoVec1 = null;\n\t\t\t\t_ammoVec2 = null;\n\t\t\t\t_ammoQuat = null;\n\t\t}\n\t\tset angularDamping(damping) {\n\t\t\t\tif (this._angularDamping !== damping) {\n\t\t\t\t\t\tthis._angularDamping = damping;\n\t\t\t\t\t\tif (this._body) {\n\t\t\t\t\t\t\t\tthis._body.setDamping(this._linearDamping, damping);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget angularDamping() {\n\t\t\t\treturn this._angularDamping;\n\t\t}\n\t\tset angularFactor(factor) {\n\t\t\t\tif (!this._angularFactor.equals(factor)) {\n\t\t\t\t\t\tthis._angularFactor.copy(factor);\n\t\t\t\t\t\tif (this._body && this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(factor.x, factor.y, factor.z);\n\t\t\t\t\t\t\t\tthis._body.setAngularFactor(_ammoVec1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget angularFactor() {\n\t\t\t\treturn this._angularFactor;\n\t\t}\n\t\tset angularVelocity(velocity) {\n\t\t\t\tif (this._body && this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\tthis._body.activate();\n\t\t\t\t\t\t_ammoVec1.setValue(velocity.x, velocity.y, velocity.z);\n\t\t\t\t\t\tthis._body.setAngularVelocity(_ammoVec1);\n\t\t\t\t\t\tthis._angularVelocity.copy(velocity);\n\t\t\t\t}\n\t\t}\n\t\tget angularVelocity() {\n\t\t\t\tif (this._body && this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\tconst velocity = this._body.getAngularVelocity();\n\t\t\t\t\t\tthis._angularVelocity.set(velocity.x(), velocity.y(), velocity.z());\n\t\t\t\t}\n\t\t\t\treturn this._angularVelocity;\n\t\t}\n\t\tset body(body) {\n\t\t\t\tif (this._body !== body) {\n\t\t\t\t\t\tthis._body = body;\n\t\t\t\t\t\tif (body && this._simulationEnabled) {\n\t\t\t\t\t\t\t\tbody.activate();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget body() {\n\t\t\t\treturn this._body;\n\t\t}\n\t\tset friction(friction) {\n\t\t\t\tif (this._friction !== friction) {\n\t\t\t\t\t\tthis._friction = friction;\n\t\t\t\t\t\tif (this._body) {\n\t\t\t\t\t\t\t\tthis._body.setFriction(friction);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget friction() {\n\t\t\t\treturn this._friction;\n\t\t}\n\t\tset group(group) {\n\t\t\t\tif (this._group !== group) {\n\t\t\t\t\t\tthis._group = group;\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.disableSimulation();\n\t\t\t\t\t\t\t\tthis.enableSimulation();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget group() {\n\t\t\t\treturn this._group;\n\t\t}\n\t\tset linearDamping(damping) {\n\t\t\t\tif (this._linearDamping !== damping) {\n\t\t\t\t\t\tthis._linearDamping = damping;\n\t\t\t\t\t\tif (this._body) {\n\t\t\t\t\t\t\t\tthis._body.setDamping(damping, this._angularDamping);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget linearDamping() {\n\t\t\t\treturn this._linearDamping;\n\t\t}\n\t\tset linearFactor(factor) {\n\t\t\t\tif (!this._linearFactor.equals(factor)) {\n\t\t\t\t\t\tthis._linearFactor.copy(factor);\n\t\t\t\t\t\tif (this._body && this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(factor.x, factor.y, factor.z);\n\t\t\t\t\t\t\t\tthis._body.setLinearFactor(_ammoVec1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget linearFactor() {\n\t\t\t\treturn this._linearFactor;\n\t\t}\n\t\tset linearVelocity(velocity) {\n\t\t\t\tif (this._body && this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\tthis._body.activate();\n\t\t\t\t\t\t_ammoVec1.setValue(velocity.x, velocity.y, velocity.z);\n\t\t\t\t\t\tthis._body.setLinearVelocity(_ammoVec1);\n\t\t\t\t\t\tthis._linearVelocity.copy(velocity);\n\t\t\t\t}\n\t\t}\n\t\tget linearVelocity() {\n\t\t\t\tif (this._body && this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\tconst velocity = this._body.getLinearVelocity();\n\t\t\t\t\t\tthis._linearVelocity.set(velocity.x(), velocity.y(), velocity.z());\n\t\t\t\t}\n\t\t\t\treturn this._linearVelocity;\n\t\t}\n\t\tset mask(mask) {\n\t\t\t\tif (this._mask !== mask) {\n\t\t\t\t\t\tthis._mask = mask;\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.disableSimulation();\n\t\t\t\t\t\t\t\tthis.enableSimulation();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget mask() {\n\t\t\t\treturn this._mask;\n\t\t}\n\t\tset mass(mass) {\n\t\t\t\tif (this._mass !== mass) {\n\t\t\t\t\t\tthis._mass = mass;\n\t\t\t\t\t\tif (this._body && this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\t\t\tconst enabled = this.enabled && this.entity.enabled;\n\t\t\t\t\t\t\t\tif (enabled) {\n\t\t\t\t\t\t\t\t\t\tthis.disableSimulation();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._body.getCollisionShape().calculateLocalInertia(mass, _ammoVec1);\n\t\t\t\t\t\t\t\tthis._body.setMassProps(mass, _ammoVec1);\n\t\t\t\t\t\t\t\tthis._body.updateInertiaTensor();\n\t\t\t\t\t\t\t\tif (enabled) {\n\t\t\t\t\t\t\t\t\t\tthis.enableSimulation();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget mass() {\n\t\t\t\treturn this._mass;\n\t\t}\n\t\tset restitution(restitution) {\n\t\t\t\tif (this._restitution !== restitution) {\n\t\t\t\t\t\tthis._restitution = restitution;\n\t\t\t\t\t\tif (this._body) {\n\t\t\t\t\t\t\t\tthis._body.setRestitution(restitution);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget restitution() {\n\t\t\t\treturn this._restitution;\n\t\t}\n\t\tset rollingFriction(friction) {\n\t\t\t\tif (this._rollingFriction !== friction) {\n\t\t\t\t\t\tthis._rollingFriction = friction;\n\t\t\t\t\t\tif (this._body) {\n\t\t\t\t\t\t\t\tthis._body.setRollingFriction(friction);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget rollingFriction() {\n\t\t\t\treturn this._rollingFriction;\n\t\t}\n\t\tset type(type) {\n\t\t\t\tif (this._type !== type) {\n\t\t\t\t\t\tthis._type = type;\n\t\t\t\t\t\tthis.disableSimulation();\n\t\t\t\t\t\tswitch(type){\n\t\t\t\t\t\t\t\tcase BODYTYPE_DYNAMIC:\n\t\t\t\t\t\t\t\t\t\tthis._group = BODYGROUP_DYNAMIC;\n\t\t\t\t\t\t\t\t\t\tthis._mask = BODYMASK_ALL;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase BODYTYPE_KINEMATIC:\n\t\t\t\t\t\t\t\t\t\tthis._group = BODYGROUP_KINEMATIC;\n\t\t\t\t\t\t\t\t\t\tthis._mask = BODYMASK_ALL;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase BODYTYPE_STATIC:\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tthis._group = BODYGROUP_STATIC;\n\t\t\t\t\t\t\t\t\t\tthis._mask = BODYMASK_NOT_STATIC;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.createBody();\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tcreateBody() {\n\t\t\t\tconst entity = this.entity;\n\t\t\t\tlet shape;\n\t\t\t\tif (entity.collision) {\n\t\t\t\t\t\tshape = entity.collision.shape;\n\t\t\t\t\t\tif (entity.trigger) {\n\t\t\t\t\t\t\t\tentity.trigger.destroy();\n\t\t\t\t\t\t\t\tdelete entity.trigger;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (shape) {\n\t\t\t\t\t\tif (this._body) {\n\t\t\t\t\t\t\t\tthis.system.removeBody(this._body);\n\t\t\t\t\t\t\t\tthis.system.destroyBody(this._body);\n\t\t\t\t\t\t\t\tthis._body = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst mass = this._type === BODYTYPE_DYNAMIC ? this._mass : 0;\n\t\t\t\t\t\tthis._getEntityTransform(_ammoTransform);\n\t\t\t\t\t\tconst body = this.system.createBody(mass, shape, _ammoTransform);\n\t\t\t\t\t\tbody.setRestitution(this._restitution);\n\t\t\t\t\t\tbody.setFriction(this._friction);\n\t\t\t\t\t\tbody.setRollingFriction(this._rollingFriction);\n\t\t\t\t\t\tbody.setDamping(this._linearDamping, this._angularDamping);\n\t\t\t\t\t\tif (this._type === BODYTYPE_DYNAMIC) {\n\t\t\t\t\t\t\t\tconst linearFactor = this._linearFactor;\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(linearFactor.x, linearFactor.y, linearFactor.z);\n\t\t\t\t\t\t\t\tbody.setLinearFactor(_ammoVec1);\n\t\t\t\t\t\t\t\tconst angularFactor = this._angularFactor;\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(angularFactor.x, angularFactor.y, angularFactor.z);\n\t\t\t\t\t\t\t\tbody.setAngularFactor(_ammoVec1);\n\t\t\t\t\t\t} else if (this._type === BODYTYPE_KINEMATIC) {\n\t\t\t\t\t\t\t\tbody.setCollisionFlags(body.getCollisionFlags() | BODYFLAG_KINEMATIC_OBJECT);\n\t\t\t\t\t\t\t\tbody.setActivationState(BODYSTATE_DISABLE_DEACTIVATION);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody.entity = entity;\n\t\t\t\t\t\tthis.body = body;\n\t\t\t\t\t\tif (this.enabled && entity.enabled) {\n\t\t\t\t\t\t\t\tthis.enableSimulation();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tisActive() {\n\t\t\t\treturn this._body ? this._body.isActive() : false;\n\t\t}\n\t\tactivate() {\n\t\t\t\tif (this._body) {\n\t\t\t\t\t\tthis._body.activate();\n\t\t\t\t}\n\t\t}\n\t\tenableSimulation() {\n\t\t\t\tconst entity = this.entity;\n\t\t\t\tif (entity.collision && entity.collision.enabled && !this._simulationEnabled) {\n\t\t\t\t\t\tconst body = this._body;\n\t\t\t\t\t\tif (body) {\n\t\t\t\t\t\t\t\tthis.system.addBody(body, this._group, this._mask);\n\t\t\t\t\t\t\t\tswitch(this._type){\n\t\t\t\t\t\t\t\t\t\tcase BODYTYPE_DYNAMIC:\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system._dynamic.push(this);\n\t\t\t\t\t\t\t\t\t\t\t\tbody.forceActivationState(BODYSTATE_ACTIVE_TAG);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.syncEntityToBody();\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase BODYTYPE_KINEMATIC:\n\t\t\t\t\t\t\t\t\t\t\t\tthis.system._kinematic.push(this);\n\t\t\t\t\t\t\t\t\t\t\t\tbody.forceActivationState(BODYSTATE_DISABLE_DEACTIVATION);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase BODYTYPE_STATIC:\n\t\t\t\t\t\t\t\t\t\t\t\tbody.forceActivationState(BODYSTATE_ACTIVE_TAG);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.syncEntityToBody();\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (entity.collision.type === 'compound') {\n\t\t\t\t\t\t\t\t\t\tthis.system._compounds.push(entity.collision);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbody.activate();\n\t\t\t\t\t\t\t\tthis._simulationEnabled = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdisableSimulation() {\n\t\t\t\tconst body = this._body;\n\t\t\t\tif (body && this._simulationEnabled) {\n\t\t\t\t\t\tconst system = this.system;\n\t\t\t\t\t\tlet idx = system._compounds.indexOf(this.entity.collision);\n\t\t\t\t\t\tif (idx > -1) {\n\t\t\t\t\t\t\t\tsystem._compounds.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tidx = system._dynamic.indexOf(this);\n\t\t\t\t\t\tif (idx > -1) {\n\t\t\t\t\t\t\t\tsystem._dynamic.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tidx = system._kinematic.indexOf(this);\n\t\t\t\t\t\tif (idx > -1) {\n\t\t\t\t\t\t\t\tsystem._kinematic.splice(idx, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsystem.removeBody(body);\n\t\t\t\t\t\tbody.forceActivationState(BODYSTATE_DISABLE_SIMULATION);\n\t\t\t\t\t\tthis._simulationEnabled = false;\n\t\t\t\t}\n\t\t}\n\t\tapplyForce(x, y, z, px, py, pz) {\n\t\t\t\tconst body = this._body;\n\t\t\t\tif (body) {\n\t\t\t\t\t\tbody.activate();\n\t\t\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x.x, x.y, x.z);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x, y, z);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (y instanceof Vec3) {\n\t\t\t\t\t\t\t\t_ammoVec2.setValue(y.x, y.y, y.z);\n\t\t\t\t\t\t} else if (px !== undefined) {\n\t\t\t\t\t\t\t\t_ammoVec2.setValue(px, py, pz);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t_ammoVec2.setValue(0, 0, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody.applyForce(_ammoVec1, _ammoVec2);\n\t\t\t\t}\n\t\t}\n\t\tapplyTorque(x, y, z) {\n\t\t\t\tconst body = this._body;\n\t\t\t\tif (body) {\n\t\t\t\t\t\tbody.activate();\n\t\t\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x.x, x.y, x.z);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x, y, z);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody.applyTorque(_ammoVec1);\n\t\t\t\t}\n\t\t}\n\t\tapplyImpulse(x, y, z, px, py, pz) {\n\t\t\t\tconst body = this._body;\n\t\t\t\tif (body) {\n\t\t\t\t\t\tbody.activate();\n\t\t\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x.x, x.y, x.z);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x, y, z);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (y instanceof Vec3) {\n\t\t\t\t\t\t\t\t_ammoVec2.setValue(y.x, y.y, y.z);\n\t\t\t\t\t\t} else if (px !== undefined) {\n\t\t\t\t\t\t\t\t_ammoVec2.setValue(px, py, pz);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t_ammoVec2.setValue(0, 0, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody.applyImpulse(_ammoVec1, _ammoVec2);\n\t\t\t\t}\n\t\t}\n\t\tapplyTorqueImpulse(x, y, z) {\n\t\t\t\tconst body = this._body;\n\t\t\t\tif (body) {\n\t\t\t\t\t\tbody.activate();\n\t\t\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x.x, x.y, x.z);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t_ammoVec1.setValue(x, y, z);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody.applyTorqueImpulse(_ammoVec1);\n\t\t\t\t}\n\t\t}\n\t\tisStatic() {\n\t\t\t\treturn this._type === BODYTYPE_STATIC;\n\t\t}\n\t\tisStaticOrKinematic() {\n\t\t\t\treturn this._type === BODYTYPE_STATIC || this._type === BODYTYPE_KINEMATIC;\n\t\t}\n\t\tisKinematic() {\n\t\t\t\treturn this._type === BODYTYPE_KINEMATIC;\n\t\t}\n\t\t_getEntityTransform(transform) {\n\t\t\t\tconst entity = this.entity;\n\t\t\t\tconst component = entity.collision;\n\t\t\t\tif (component) {\n\t\t\t\t\t\tconst bodyPos = component.getShapePosition();\n\t\t\t\t\t\tconst bodyRot = component.getShapeRotation();\n\t\t\t\t\t\t_ammoVec1.setValue(bodyPos.x, bodyPos.y, bodyPos.z);\n\t\t\t\t\t\t_ammoQuat.setValue(bodyRot.x, bodyRot.y, bodyRot.z, bodyRot.w);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst pos = entity.getPosition();\n\t\t\t\t\t\tconst rot = entity.getRotation();\n\t\t\t\t\t\t_ammoVec1.setValue(pos.x, pos.y, pos.z);\n\t\t\t\t\t\t_ammoQuat.setValue(rot.x, rot.y, rot.z, rot.w);\n\t\t\t\t}\n\t\t\t\ttransform.setOrigin(_ammoVec1);\n\t\t\t\ttransform.setRotation(_ammoQuat);\n\t\t}\n\t\tsyncEntityToBody() {\n\t\t\t\tconst body = this._body;\n\t\t\t\tif (body) {\n\t\t\t\t\t\tthis._getEntityTransform(_ammoTransform);\n\t\t\t\t\t\tbody.setWorldTransform(_ammoTransform);\n\t\t\t\t\t\tif (this._type === BODYTYPE_KINEMATIC) {\n\t\t\t\t\t\t\t\tconst motionState = body.getMotionState();\n\t\t\t\t\t\t\t\tif (motionState) {\n\t\t\t\t\t\t\t\t\t\tmotionState.setWorldTransform(_ammoTransform);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody.activate();\n\t\t\t\t}\n\t\t}\n\t\t_updateDynamic() {\n\t\t\t\tconst body = this._body;\n\t\t\t\tif (body.isActive()) {\n\t\t\t\t\t\tconst motionState = body.getMotionState();\n\t\t\t\t\t\tif (motionState) {\n\t\t\t\t\t\t\t\tconst entity = this.entity;\n\t\t\t\t\t\t\t\tmotionState.getWorldTransform(_ammoTransform);\n\t\t\t\t\t\t\t\tconst p = _ammoTransform.getOrigin();\n\t\t\t\t\t\t\t\tconst q = _ammoTransform.getRotation();\n\t\t\t\t\t\t\t\tconst component = entity.collision;\n\t\t\t\t\t\t\t\tif (component && component._hasOffset) {\n\t\t\t\t\t\t\t\t\t\tconst lo = component.data.linearOffset;\n\t\t\t\t\t\t\t\t\t\tconst ao = component.data.angularOffset;\n\t\t\t\t\t\t\t\t\t\tconst invertedAo = _quat2.copy(ao).invert();\n\t\t\t\t\t\t\t\t\t\tconst entityRot = _quat1.set(q.x(), q.y(), q.z(), q.w()).mul(invertedAo);\n\t\t\t\t\t\t\t\t\t\tentityRot.transformVector(lo, _vec3);\n\t\t\t\t\t\t\t\t\t\tentity.setPosition(p.x() - _vec3.x, p.y() - _vec3.y, p.z() - _vec3.z);\n\t\t\t\t\t\t\t\t\t\tentity.setRotation(entityRot);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tentity.setPosition(p.x(), p.y(), p.z());\n\t\t\t\t\t\t\t\t\t\tentity.setRotation(q.x(), q.y(), q.z(), q.w());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateKinematic() {\n\t\t\t\tconst motionState = this._body.getMotionState();\n\t\t\t\tif (motionState) {\n\t\t\t\t\t\tthis._getEntityTransform(_ammoTransform);\n\t\t\t\t\t\tmotionState.setWorldTransform(_ammoTransform);\n\t\t\t\t}\n\t\t}\n\t\tteleport(x, y, z, rx, ry, rz) {\n\t\t\t\tif (x instanceof Vec3) {\n\t\t\t\t\t\tthis.entity.setPosition(x);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.entity.setPosition(x, y, z);\n\t\t\t\t}\n\t\t\t\tif (y instanceof Quat) {\n\t\t\t\t\t\tthis.entity.setRotation(y);\n\t\t\t\t} else if (y instanceof Vec3) {\n\t\t\t\t\t\tthis.entity.setEulerAngles(y);\n\t\t\t\t} else if (rx !== undefined) {\n\t\t\t\t\t\tthis.entity.setEulerAngles(rx, ry, rz);\n\t\t\t\t}\n\t\t\t\tthis.syncEntityToBody();\n\t\t}\n\t\tonEnable() {\n\t\t\t\tif (!this._body) {\n\t\t\t\t\t\tthis.createBody();\n\t\t\t\t}\n\t\t\t\tthis.enableSimulation();\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis.disableSimulation();\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._angularDamping = 0, this._angularFactor = new Vec3(1, 1, 1), this._angularVelocity = new Vec3(), this._body = null, this._friction = 0.5, this._group = BODYGROUP_STATIC, this._linearDamping = 0, this._linearFactor = new Vec3(1, 1, 1), this._linearVelocity = new Vec3(), this._mask = BODYMASK_NOT_STATIC, this._mass = 1, this._restitution = 0, this._rollingFriction = 0, this._simulationEnabled = false, this._type = BODYTYPE_STATIC;\n\t\t}\n}\n\nclass RigidBodyComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nlet ammoRayStart, ammoRayEnd;\nclass RaycastResult {\n\t\tconstructor(entity, point, normal, hitFraction){\n\t\t\t\tthis.entity = entity;\n\t\t\t\tthis.point = point;\n\t\t\t\tthis.normal = normal;\n\t\t\t\tthis.hitFraction = hitFraction;\n\t\t}\n}\nclass SingleContactResult {\n\t\tconstructor(a, b, contactPoint){\n\t\t\t\tif (arguments.length !== 0) {\n\t\t\t\t\t\tthis.a = a;\n\t\t\t\t\t\tthis.b = b;\n\t\t\t\t\t\tthis.impulse = contactPoint.impulse;\n\t\t\t\t\t\tthis.localPointA = contactPoint.localPoint;\n\t\t\t\t\t\tthis.localPointB = contactPoint.localPointOther;\n\t\t\t\t\t\tthis.pointA = contactPoint.point;\n\t\t\t\t\t\tthis.pointB = contactPoint.pointOther;\n\t\t\t\t\t\tthis.normal = contactPoint.normal;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.a = null;\n\t\t\t\t\t\tthis.b = null;\n\t\t\t\t\t\tthis.impulse = 0;\n\t\t\t\t\t\tthis.localPointA = new Vec3();\n\t\t\t\t\t\tthis.localPointB = new Vec3();\n\t\t\t\t\t\tthis.pointA = new Vec3();\n\t\t\t\t\t\tthis.pointB = new Vec3();\n\t\t\t\t\t\tthis.normal = new Vec3();\n\t\t\t\t}\n\t\t}\n}\nclass ContactPoint {\n\t\tconstructor(localPoint = new Vec3(), localPointOther = new Vec3(), point = new Vec3(), pointOther = new Vec3(), normal = new Vec3(), impulse = 0){\n\t\t\t\tthis.localPoint = localPoint;\n\t\t\t\tthis.localPointOther = localPointOther;\n\t\t\t\tthis.point = point;\n\t\t\t\tthis.pointOther = pointOther;\n\t\t\t\tthis.normal = normal;\n\t\t\t\tthis.impulse = impulse;\n\t\t}\n}\nclass ContactResult {\n\t\tconstructor(other, contacts){\n\t\t\t\tthis.other = other;\n\t\t\t\tthis.contacts = contacts;\n\t\t}\n}\nconst _schema$8 = [\n\t\t'enabled'\n];\nclass RigidBodyComponentSystem extends ComponentSystem {\n\t\tstatic{\n\t\t\t\tthis.EVENT_CONTACT = 'contact';\n\t\t}\n\t\tconstructor(app){\n\t\t\t\tsuper(app), this.maxSubSteps = 10, this.fixedTimeStep = 1 / 60, this.gravity = new Vec3(0, -9.81, 0), this._gravityFloat32 = new Float32Array(3), this._dynamic = [], this._kinematic = [], this._triggers = [], this._compounds = [];\n\t\t\t\tthis.id = 'rigidbody';\n\t\t\t\tthis._stats = app.stats.frame;\n\t\t\t\tthis.ComponentType = RigidBodyComponent;\n\t\t\t\tthis.DataType = RigidBodyComponentData;\n\t\t\t\tthis.contactPointPool = null;\n\t\t\t\tthis.contactResultPool = null;\n\t\t\t\tthis.singleContactResultPool = null;\n\t\t\t\tthis.schema = _schema$8;\n\t\t\t\tthis.collisions = {};\n\t\t\t\tthis.frameCollisions = {};\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t}\n\t\tonLibraryLoaded() {\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tthis.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();\n\t\t\t\t\t\tthis.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration);\n\t\t\t\t\t\tthis.overlappingPairCache = new Ammo.btDbvtBroadphase();\n\t\t\t\t\t\tthis.solver = new Ammo.btSequentialImpulseConstraintSolver();\n\t\t\t\t\t\tthis.dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(this.dispatcher, this.overlappingPairCache, this.solver, this.collisionConfiguration);\n\t\t\t\t\t\tif (this.dynamicsWorld.setInternalTickCallback) {\n\t\t\t\t\t\t\t\tconst checkForCollisionsPointer = Ammo.addFunction(this._checkForCollisions.bind(this), 'vif');\n\t\t\t\t\t\t\t\tthis.dynamicsWorld.setInternalTickCallback(checkForCollisionsPointer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tammoRayStart = new Ammo.btVector3();\n\t\t\t\t\t\tammoRayEnd = new Ammo.btVector3();\n\t\t\t\t\t\tRigidBodyComponent.onLibraryLoaded();\n\t\t\t\t\t\tthis.contactPointPool = new ObjectPool(ContactPoint, 1);\n\t\t\t\t\t\tthis.contactResultPool = new ObjectPool(ContactResult, 1);\n\t\t\t\t\t\tthis.singleContactResultPool = new ObjectPool(SingleContactResult, 1);\n\t\t\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t\t\t}\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tconst props = [\n\t\t\t\t\t\t'mass',\n\t\t\t\t\t\t'linearDamping',\n\t\t\t\t\t\t'angularDamping',\n\t\t\t\t\t\t'linearFactor',\n\t\t\t\t\t\t'angularFactor',\n\t\t\t\t\t\t'friction',\n\t\t\t\t\t\t'rollingFriction',\n\t\t\t\t\t\t'restitution',\n\t\t\t\t\t\t'type',\n\t\t\t\t\t\t'group',\n\t\t\t\t\t\t'mask'\n\t\t\t\t];\n\t\t\t\tfor (const property of props){\n\t\t\t\t\t\tif (data.hasOwnProperty(property)) {\n\t\t\t\t\t\t\t\tconst value = data[property];\n\t\t\t\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\t\t\t\t\tcomponent[property] = new Vec3(value[0], value[1], value[2]);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcomponent[property] = value;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, data, [\n\t\t\t\t\t\t'enabled'\n\t\t\t\t]);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst rigidbody = entity.rigidbody;\n\t\t\t\tconst data = {\n\t\t\t\t\t\tenabled: rigidbody.enabled,\n\t\t\t\t\t\tmass: rigidbody.mass,\n\t\t\t\t\t\tlinearDamping: rigidbody.linearDamping,\n\t\t\t\t\t\tangularDamping: rigidbody.angularDamping,\n\t\t\t\t\t\tlinearFactor: [\n\t\t\t\t\t\t\t\trigidbody.linearFactor.x,\n\t\t\t\t\t\t\t\trigidbody.linearFactor.y,\n\t\t\t\t\t\t\t\trigidbody.linearFactor.z\n\t\t\t\t\t\t],\n\t\t\t\t\t\tangularFactor: [\n\t\t\t\t\t\t\t\trigidbody.angularFactor.x,\n\t\t\t\t\t\t\t\trigidbody.angularFactor.y,\n\t\t\t\t\t\t\t\trigidbody.angularFactor.z\n\t\t\t\t\t\t],\n\t\t\t\t\t\tfriction: rigidbody.friction,\n\t\t\t\t\t\trollingFriction: rigidbody.rollingFriction,\n\t\t\t\t\t\trestitution: rigidbody.restitution,\n\t\t\t\t\t\ttype: rigidbody.type,\n\t\t\t\t\t\tgroup: rigidbody.group,\n\t\t\t\t\t\tmask: rigidbody.mask\n\t\t\t\t};\n\t\t\t\treturn this.addComponent(clone, data);\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tif (component.enabled) {\n\t\t\t\t\t\tcomponent.enabled = false;\n\t\t\t\t}\n\t\t\t\tif (component.body) {\n\t\t\t\t\t\tthis.destroyBody(component.body);\n\t\t\t\t\t\tcomponent.body = null;\n\t\t\t\t}\n\t\t}\n\t\taddBody(body, group, mask) {\n\t\t\t\tif (group !== undefined && mask !== undefined) {\n\t\t\t\t\t\tthis.dynamicsWorld.addRigidBody(body, group, mask);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.dynamicsWorld.addRigidBody(body);\n\t\t\t\t}\n\t\t}\n\t\tremoveBody(body) {\n\t\t\t\tthis.dynamicsWorld.removeRigidBody(body);\n\t\t}\n\t\tcreateBody(mass, shape, transform) {\n\t\t\t\tconst localInertia = new Ammo.btVector3(0, 0, 0);\n\t\t\t\tif (mass !== 0) {\n\t\t\t\t\t\tshape.calculateLocalInertia(mass, localInertia);\n\t\t\t\t}\n\t\t\t\tconst motionState = new Ammo.btDefaultMotionState(transform);\n\t\t\t\tconst bodyInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);\n\t\t\t\tconst body = new Ammo.btRigidBody(bodyInfo);\n\t\t\t\tAmmo.destroy(bodyInfo);\n\t\t\t\tAmmo.destroy(localInertia);\n\t\t\t\treturn body;\n\t\t}\n\t\tdestroyBody(body) {\n\t\t\t\tconst motionState = body.getMotionState();\n\t\t\t\tif (motionState) {\n\t\t\t\t\t\tAmmo.destroy(motionState);\n\t\t\t\t}\n\t\t\t\tAmmo.destroy(body);\n\t\t}\n\t\traycastFirst(start, end, options = {}) {\n\t\t\t\tif (options.filterTags || options.filterCallback) {\n\t\t\t\t\t\toptions.sort = true;\n\t\t\t\t\t\treturn this.raycastAll(start, end, options)[0] || null;\n\t\t\t\t}\n\t\t\t\tlet result = null;\n\t\t\t\tammoRayStart.setValue(start.x, start.y, start.z);\n\t\t\t\tammoRayEnd.setValue(end.x, end.y, end.z);\n\t\t\t\tconst rayCallback = new Ammo.ClosestRayResultCallback(ammoRayStart, ammoRayEnd);\n\t\t\t\tif (typeof options.filterCollisionGroup === 'number') {\n\t\t\t\t\t\trayCallback.set_m_collisionFilterGroup(options.filterCollisionGroup);\n\t\t\t\t}\n\t\t\t\tif (typeof options.filterCollisionMask === 'number') {\n\t\t\t\t\t\trayCallback.set_m_collisionFilterMask(options.filterCollisionMask);\n\t\t\t\t}\n\t\t\t\tthis.dynamicsWorld.rayTest(ammoRayStart, ammoRayEnd, rayCallback);\n\t\t\t\tif (rayCallback.hasHit()) {\n\t\t\t\t\t\tconst collisionObj = rayCallback.get_m_collisionObject();\n\t\t\t\t\t\tconst body = Ammo.castObject(collisionObj, Ammo.btRigidBody);\n\t\t\t\t\t\tif (body) {\n\t\t\t\t\t\t\t\tconst point = rayCallback.get_m_hitPointWorld();\n\t\t\t\t\t\t\t\tconst normal = rayCallback.get_m_hitNormalWorld();\n\t\t\t\t\t\t\t\tresult = new RaycastResult(body.entity, new Vec3(point.x(), point.y(), point.z()), new Vec3(normal.x(), normal.y(), normal.z()), rayCallback.get_m_closestHitFraction());\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tAmmo.destroy(rayCallback);\n\t\t\t\treturn result;\n\t\t}\n\t\traycastAll(start, end, options = {}) {\n\t\t\t\tconst results = [];\n\t\t\t\tammoRayStart.setValue(start.x, start.y, start.z);\n\t\t\t\tammoRayEnd.setValue(end.x, end.y, end.z);\n\t\t\t\tconst rayCallback = new Ammo.AllHitsRayResultCallback(ammoRayStart, ammoRayEnd);\n\t\t\t\tif (typeof options.filterCollisionGroup === 'number') {\n\t\t\t\t\t\trayCallback.set_m_collisionFilterGroup(options.filterCollisionGroup);\n\t\t\t\t}\n\t\t\t\tif (typeof options.filterCollisionMask === 'number') {\n\t\t\t\t\t\trayCallback.set_m_collisionFilterMask(options.filterCollisionMask);\n\t\t\t\t}\n\t\t\t\tthis.dynamicsWorld.rayTest(ammoRayStart, ammoRayEnd, rayCallback);\n\t\t\t\tif (rayCallback.hasHit()) {\n\t\t\t\t\t\tconst collisionObjs = rayCallback.get_m_collisionObjects();\n\t\t\t\t\t\tconst points = rayCallback.get_m_hitPointWorld();\n\t\t\t\t\t\tconst normals = rayCallback.get_m_hitNormalWorld();\n\t\t\t\t\t\tconst hitFractions = rayCallback.get_m_hitFractions();\n\t\t\t\t\t\tconst numHits = collisionObjs.size();\n\t\t\t\t\t\tfor(let i = 0; i < numHits; i++){\n\t\t\t\t\t\t\t\tconst body = Ammo.castObject(collisionObjs.at(i), Ammo.btRigidBody);\n\t\t\t\t\t\t\t\tif (body && body.entity) {\n\t\t\t\t\t\t\t\t\t\tif (options.filterTags && !body.entity.tags.has(...options.filterTags) || options.filterCallback && !options.filterCallback(body.entity)) {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst point = points.at(i);\n\t\t\t\t\t\t\t\t\t\tconst normal = normals.at(i);\n\t\t\t\t\t\t\t\t\t\tconst result = new RaycastResult(body.entity, new Vec3(point.x(), point.y(), point.z()), new Vec3(normal.x(), normal.y(), normal.z()), hitFractions.at(i));\n\t\t\t\t\t\t\t\t\t\tresults.push(result);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.sort) {\n\t\t\t\t\t\t\t\tresults.sort((a, b)=>a.hitFraction - b.hitFraction);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tAmmo.destroy(rayCallback);\n\t\t\t\treturn results;\n\t\t}\n\t\t_storeCollision(entity, other) {\n\t\t\t\tlet isNewCollision = false;\n\t\t\t\tconst guid = entity.getGuid();\n\t\t\t\tthis.collisions[guid] = this.collisions[guid] || {\n\t\t\t\t\t\tothers: [],\n\t\t\t\t\t\tentity: entity\n\t\t\t\t};\n\t\t\t\tif (this.collisions[guid].others.indexOf(other) < 0) {\n\t\t\t\t\t\tthis.collisions[guid].others.push(other);\n\t\t\t\t\t\tisNewCollision = true;\n\t\t\t\t}\n\t\t\t\tthis.frameCollisions[guid] = this.frameCollisions[guid] || {\n\t\t\t\t\t\tothers: [],\n\t\t\t\t\t\tentity: entity\n\t\t\t\t};\n\t\t\t\tthis.frameCollisions[guid].others.push(other);\n\t\t\t\treturn isNewCollision;\n\t\t}\n\t\t_createContactPointFromAmmo(contactPoint) {\n\t\t\t\tconst localPointA = contactPoint.get_m_localPointA();\n\t\t\t\tconst localPointB = contactPoint.get_m_localPointB();\n\t\t\t\tconst positionWorldOnA = contactPoint.getPositionWorldOnA();\n\t\t\t\tconst positionWorldOnB = contactPoint.getPositionWorldOnB();\n\t\t\t\tconst normalWorldOnB = contactPoint.get_m_normalWorldOnB();\n\t\t\t\tconst contact = this.contactPointPool.allocate();\n\t\t\t\tcontact.localPoint.set(localPointA.x(), localPointA.y(), localPointA.z());\n\t\t\t\tcontact.localPointOther.set(localPointB.x(), localPointB.y(), localPointB.z());\n\t\t\t\tcontact.point.set(positionWorldOnA.x(), positionWorldOnA.y(), positionWorldOnA.z());\n\t\t\t\tcontact.pointOther.set(positionWorldOnB.x(), positionWorldOnB.y(), positionWorldOnB.z());\n\t\t\t\tcontact.normal.set(normalWorldOnB.x(), normalWorldOnB.y(), normalWorldOnB.z());\n\t\t\t\tcontact.impulse = contactPoint.getAppliedImpulse();\n\t\t\t\treturn contact;\n\t\t}\n\t\t_createReverseContactPointFromAmmo(contactPoint) {\n\t\t\t\tconst localPointA = contactPoint.get_m_localPointA();\n\t\t\t\tconst localPointB = contactPoint.get_m_localPointB();\n\t\t\t\tconst positionWorldOnA = contactPoint.getPositionWorldOnA();\n\t\t\t\tconst positionWorldOnB = contactPoint.getPositionWorldOnB();\n\t\t\t\tconst normalWorldOnB = contactPoint.get_m_normalWorldOnB();\n\t\t\t\tconst contact = this.contactPointPool.allocate();\n\t\t\t\tcontact.localPointOther.set(localPointA.x(), localPointA.y(), localPointA.z());\n\t\t\t\tcontact.localPoint.set(localPointB.x(), localPointB.y(), localPointB.z());\n\t\t\t\tcontact.pointOther.set(positionWorldOnA.x(), positionWorldOnA.y(), positionWorldOnA.z());\n\t\t\t\tcontact.point.set(positionWorldOnB.x(), positionWorldOnB.y(), positionWorldOnB.z());\n\t\t\t\tcontact.normal.set(normalWorldOnB.x(), normalWorldOnB.y(), normalWorldOnB.z());\n\t\t\t\tcontact.impulse = contactPoint.getAppliedImpulse();\n\t\t\t\treturn contact;\n\t\t}\n\t\t_createSingleContactResult(a, b, contactPoint) {\n\t\t\t\tconst result = this.singleContactResultPool.allocate();\n\t\t\t\tresult.a = a;\n\t\t\t\tresult.b = b;\n\t\t\t\tresult.localPointA = contactPoint.localPoint;\n\t\t\t\tresult.localPointB = contactPoint.localPointOther;\n\t\t\t\tresult.pointA = contactPoint.point;\n\t\t\t\tresult.pointB = contactPoint.pointOther;\n\t\t\t\tresult.normal = contactPoint.normal;\n\t\t\t\tresult.impulse = contactPoint.impulse;\n\t\t\t\treturn result;\n\t\t}\n\t\t_createContactResult(other, contacts) {\n\t\t\t\tconst result = this.contactResultPool.allocate();\n\t\t\t\tresult.other = other;\n\t\t\t\tresult.contacts = contacts;\n\t\t\t\treturn result;\n\t\t}\n\t\t_cleanOldCollisions() {\n\t\t\t\tfor(const guid in this.collisions){\n\t\t\t\t\t\tif (this.collisions.hasOwnProperty(guid)) {\n\t\t\t\t\t\t\t\tconst frameCollision = this.frameCollisions[guid];\n\t\t\t\t\t\t\t\tconst collision = this.collisions[guid];\n\t\t\t\t\t\t\t\tconst entity = collision.entity;\n\t\t\t\t\t\t\t\tconst entityCollision = entity.collision;\n\t\t\t\t\t\t\t\tconst entityRigidbody = entity.rigidbody;\n\t\t\t\t\t\t\t\tconst others = collision.others;\n\t\t\t\t\t\t\t\tconst length = others.length;\n\t\t\t\t\t\t\t\tlet i = length;\n\t\t\t\t\t\t\t\twhile(i--){\n\t\t\t\t\t\t\t\t\t\tconst other = others[i];\n\t\t\t\t\t\t\t\t\t\tif (!frameCollision || frameCollision.others.indexOf(other) < 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tothers.splice(i, 1);\n\t\t\t\t\t\t\t\t\t\t\t\tif (entity.trigger) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (entityCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentityCollision.fire('triggerleave', other);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (other.rigidbody) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tother.rigidbody.fire('triggerleave', entity);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (!other.trigger) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (entityRigidbody) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentityRigidbody.fire('collisionend', other);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (entityCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentityCollision.fire('collisionend', other);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (others.length === 0) {\n\t\t\t\t\t\t\t\t\t\tdelete this.collisions[guid];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_hasContactEvent(entity) {\n\t\t\t\tconst c = entity.collision;\n\t\t\t\tif (c && (c.hasEvent('collisionstart') || c.hasEvent('collisionend') || c.hasEvent('contact'))) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconst r = entity.rigidbody;\n\t\t\t\treturn r && (r.hasEvent('collisionstart') || r.hasEvent('collisionend') || r.hasEvent('contact'));\n\t\t}\n\t\t_checkForCollisions(world, timeStep) {\n\t\t\t\tconst dynamicsWorld = Ammo.wrapPointer(world, Ammo.btDynamicsWorld);\n\t\t\t\tconst dispatcher = dynamicsWorld.getDispatcher();\n\t\t\t\tconst numManifolds = dispatcher.getNumManifolds();\n\t\t\t\tthis.frameCollisions = {};\n\t\t\t\tfor(let i = 0; i < numManifolds; i++){\n\t\t\t\t\t\tconst manifold = dispatcher.getManifoldByIndexInternal(i);\n\t\t\t\t\t\tconst body0 = manifold.getBody0();\n\t\t\t\t\t\tconst body1 = manifold.getBody1();\n\t\t\t\t\t\tconst wb0 = Ammo.castObject(body0, Ammo.btRigidBody);\n\t\t\t\t\t\tconst wb1 = Ammo.castObject(body1, Ammo.btRigidBody);\n\t\t\t\t\t\tconst e0 = wb0.entity;\n\t\t\t\t\t\tconst e1 = wb1.entity;\n\t\t\t\t\t\tif (!e0 || !e1) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst flags0 = wb0.getCollisionFlags();\n\t\t\t\t\t\tconst flags1 = wb1.getCollisionFlags();\n\t\t\t\t\t\tconst numContacts = manifold.getNumContacts();\n\t\t\t\t\t\tconst forwardContacts = [];\n\t\t\t\t\t\tconst reverseContacts = [];\n\t\t\t\t\t\tlet newCollision;\n\t\t\t\t\t\tif (numContacts > 0) {\n\t\t\t\t\t\t\t\tif (flags0 & BODYFLAG_NORESPONSE_OBJECT || flags1 & BODYFLAG_NORESPONSE_OBJECT) {\n\t\t\t\t\t\t\t\t\t\tconst e0Events = e0.collision && (e0.collision.hasEvent('triggerenter') || e0.collision.hasEvent('triggerleave'));\n\t\t\t\t\t\t\t\t\t\tconst e1Events = e1.collision && (e1.collision.hasEvent('triggerenter') || e1.collision.hasEvent('triggerleave'));\n\t\t\t\t\t\t\t\t\t\tconst e0BodyEvents = e0.rigidbody && (e0.rigidbody.hasEvent('triggerenter') || e0.rigidbody.hasEvent('triggerleave'));\n\t\t\t\t\t\t\t\t\t\tconst e1BodyEvents = e1.rigidbody && (e1.rigidbody.hasEvent('triggerenter') || e1.rigidbody.hasEvent('triggerleave'));\n\t\t\t\t\t\t\t\t\t\tif (e0Events) {\n\t\t\t\t\t\t\t\t\t\t\t\tnewCollision = this._storeCollision(e0, e1);\n\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision && !(flags1 & BODYFLAG_NORESPONSE_OBJECT)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\te0.collision.fire('triggerenter', e1);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (e1Events) {\n\t\t\t\t\t\t\t\t\t\t\t\tnewCollision = this._storeCollision(e1, e0);\n\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision && !(flags0 & BODYFLAG_NORESPONSE_OBJECT)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\te1.collision.fire('triggerenter', e0);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (e0BodyEvents) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnewCollision = this._storeCollision(e1, e0);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\te0.rigidbody.fire('triggerenter', e1);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (e1BodyEvents) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnewCollision = this._storeCollision(e0, e1);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\te1.rigidbody.fire('triggerenter', e0);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst e0Events = this._hasContactEvent(e0);\n\t\t\t\t\t\t\t\t\t\tconst e1Events = this._hasContactEvent(e1);\n\t\t\t\t\t\t\t\t\t\tconst globalEvents = this.hasEvent('contact');\n\t\t\t\t\t\t\t\t\t\tif (globalEvents || e0Events || e1Events) {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < numContacts; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst btContactPoint = manifold.getContactPoint(j);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst contactPoint = this._createContactPointFromAmmo(btContactPoint);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (e0Events || e1Events) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tforwardContacts.push(contactPoint);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst reverseContactPoint = this._createReverseContactPointFromAmmo(btContactPoint);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treverseContacts.push(reverseContactPoint);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (globalEvents) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst result = this._createSingleContactResult(e0, e1, contactPoint);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.fire('contact', result);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (e0Events) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst forwardResult = this._createContactResult(e1, forwardContacts);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnewCollision = this._storeCollision(e0, e1);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (e0.collision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te0.collision.fire('contact', forwardResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te0.collision.fire('collisionstart', forwardResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (e0.rigidbody) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te0.rigidbody.fire('contact', forwardResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te0.rigidbody.fire('collisionstart', forwardResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (e1Events) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst reverseResult = this._createContactResult(e0, reverseContacts);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnewCollision = this._storeCollision(e1, e0);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (e1.collision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te1.collision.fire('contact', reverseResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te1.collision.fire('collisionstart', reverseResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (e1.rigidbody) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te1.rigidbody.fire('contact', reverseResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (newCollision) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\te1.rigidbody.fire('collisionstart', reverseResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._cleanOldCollisions();\n\t\t\t\tthis.contactPointPool.freeAll();\n\t\t\t\tthis.contactResultPool.freeAll();\n\t\t\t\tthis.singleContactResultPool.freeAll();\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tlet i, len;\n\t\t\t\tthis._gravityFloat32[0] = this.gravity.x;\n\t\t\t\tthis._gravityFloat32[1] = this.gravity.y;\n\t\t\t\tthis._gravityFloat32[2] = this.gravity.z;\n\t\t\t\tconst gravity = this.dynamicsWorld.getGravity();\n\t\t\t\tif (gravity.x() !== this._gravityFloat32[0] || gravity.y() !== this._gravityFloat32[1] || gravity.z() !== this._gravityFloat32[2]) {\n\t\t\t\t\t\tgravity.setValue(this.gravity.x, this.gravity.y, this.gravity.z);\n\t\t\t\t\t\tthis.dynamicsWorld.setGravity(gravity);\n\t\t\t\t}\n\t\t\t\tconst triggers = this._triggers;\n\t\t\t\tfor(i = 0, len = triggers.length; i < len; i++){\n\t\t\t\t\t\ttriggers[i].updateTransform();\n\t\t\t\t}\n\t\t\t\tconst compounds = this._compounds;\n\t\t\t\tfor(i = 0, len = compounds.length; i < len; i++){\n\t\t\t\t\t\tcompounds[i]._updateCompound();\n\t\t\t\t}\n\t\t\t\tconst kinematic = this._kinematic;\n\t\t\t\tfor(i = 0, len = kinematic.length; i < len; i++){\n\t\t\t\t\t\tkinematic[i]._updateKinematic();\n\t\t\t\t}\n\t\t\t\tthis.dynamicsWorld.stepSimulation(dt, this.maxSubSteps, this.fixedTimeStep);\n\t\t\t\tconst dynamic = this._dynamic;\n\t\t\t\tfor(i = 0, len = dynamic.length; i < len; i++){\n\t\t\t\t\t\tdynamic[i]._updateDynamic();\n\t\t\t\t}\n\t\t\t\tif (!this.dynamicsWorld.setInternalTickCallback) {\n\t\t\t\t\t\tthis._checkForCollisions(Ammo.getPointer(this.dynamicsWorld), dt);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t\t\tif (typeof Ammo !== 'undefined') {\n\t\t\t\t\t\tAmmo.destroy(this.dynamicsWorld);\n\t\t\t\t\t\tAmmo.destroy(this.solver);\n\t\t\t\t\t\tAmmo.destroy(this.overlappingPairCache);\n\t\t\t\t\t\tAmmo.destroy(this.dispatcher);\n\t\t\t\t\t\tAmmo.destroy(this.collisionConfiguration);\n\t\t\t\t\t\tAmmo.destroy(ammoRayStart);\n\t\t\t\t\t\tAmmo.destroy(ammoRayEnd);\n\t\t\t\t\t\tthis.dynamicsWorld = null;\n\t\t\t\t\t\tthis.solver = null;\n\t\t\t\t\t\tthis.overlappingPairCache = null;\n\t\t\t\t\t\tthis.dispatcher = null;\n\t\t\t\t\t\tthis.collisionConfiguration = null;\n\t\t\t\t\t\tammoRayStart = null;\n\t\t\t\t\t\tammoRayEnd = null;\n\t\t\t\t\t\tRigidBodyComponent.onAppDestroy();\n\t\t\t\t}\n\t\t}\n}\nComponent._buildAccessors(RigidBodyComponent.prototype, _schema$8);\n\nconst SCALEMODE_NONE = 'none';\nconst SCALEMODE_BLEND = 'blend';\n\nconst _transform = new Mat4();\nclass ScreenComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity);\n\t\t\t\tthis._resolution = new Vec2(640, 320);\n\t\t\t\tthis._referenceResolution = new Vec2(640, 320);\n\t\t\t\tthis._scaleMode = SCALEMODE_NONE;\n\t\t\t\tthis.scale = 1;\n\t\t\t\tthis._scaleBlend = 0.5;\n\t\t\t\tthis._priority = 0;\n\t\t\t\tthis._screenSpace = false;\n\t\t\t\tthis.cull = this._screenSpace;\n\t\t\t\tthis._screenMatrix = new Mat4();\n\t\t\t\tthis._elements = new Set();\n\t\t\t\tsystem.app.graphicsDevice.on('resizecanvas', this._onResize, this);\n\t\t}\n\t\tsyncDrawOrder() {\n\t\t\t\tthis.system.queueDrawOrderSync(this.entity.getGuid(), this._processDrawOrderSync, this);\n\t\t}\n\t\t_recurseDrawOrderSync(e, i) {\n\t\t\t\tif (!(e instanceof Entity)) {\n\t\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t\tif (e.element) {\n\t\t\t\t\t\tconst prevDrawOrder = e.element.drawOrder;\n\t\t\t\t\t\te.element.drawOrder = i++;\n\t\t\t\t\t\tif (e.element._batchGroupId >= 0 && prevDrawOrder !== e.element.drawOrder) {\n\t\t\t\t\t\t\t\tthis.system.app.batcher?.markGroupDirty(e.element._batchGroupId);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (e.particlesystem) {\n\t\t\t\t\t\te.particlesystem.drawOrder = i++;\n\t\t\t\t}\n\t\t\t\tconst children = e.children;\n\t\t\t\tfor(let j = 0; j < children.length; j++){\n\t\t\t\t\t\ti = this._recurseDrawOrderSync(children[j], i);\n\t\t\t\t}\n\t\t\t\treturn i;\n\t\t}\n\t\t_processDrawOrderSync() {\n\t\t\t\tconst i = 1;\n\t\t\t\tthis._recurseDrawOrderSync(this.entity, i);\n\t\t\t\tthis.fire('syncdraworder');\n\t\t}\n\t\t_calcProjectionMatrix() {\n\t\t\t\tconst w = this._resolution.x / this.scale;\n\t\t\t\tconst h = this._resolution.y / this.scale;\n\t\t\t\tconst left = 0;\n\t\t\t\tconst right = w;\n\t\t\t\tconst bottom = -h;\n\t\t\t\tconst top = 0;\n\t\t\t\tconst near = 1;\n\t\t\t\tconst far = -1;\n\t\t\t\tthis._screenMatrix.setOrtho(left, right, bottom, top, near, far);\n\t\t\t\tif (!this._screenSpace) {\n\t\t\t\t\t\t_transform.setScale(0.5 * w, 0.5 * h, 1);\n\t\t\t\t\t\tthis._screenMatrix.mul2(_transform, this._screenMatrix);\n\t\t\t\t}\n\t\t}\n\t\t_updateScale() {\n\t\t\t\tthis.scale = this._calcScale(this._resolution, this.referenceResolution);\n\t\t}\n\t\t_calcScale(resolution, referenceResolution) {\n\t\t\t\tconst lx = Math.log2((resolution.x || 1) / referenceResolution.x);\n\t\t\t\tconst ly = Math.log2((resolution.y || 1) / referenceResolution.y);\n\t\t\t\treturn Math.pow(2, lx * (1 - this._scaleBlend) + ly * this._scaleBlend);\n\t\t}\n\t\t_onResize(width, height) {\n\t\t\t\tif (this._screenSpace) {\n\t\t\t\t\t\tthis._resolution.set(width, height);\n\t\t\t\t\t\tthis.resolution = this._resolution;\n\t\t\t\t}\n\t\t}\n\t\t_bindElement(element) {\n\t\t\t\tthis._elements.add(element);\n\t\t}\n\t\t_unbindElement(element) {\n\t\t\t\tthis._elements.delete(element);\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.system.app.graphicsDevice.off('resizecanvas', this._onResize, this);\n\t\t\t\tthis.fire('remove');\n\t\t\t\tthis._elements.forEach((element)=>element._onScreenRemove());\n\t\t\t\tthis._elements.clear();\n\t\t\t\tthis.off();\n\t\t}\n\t\tset resolution(value) {\n\t\t\t\tif (!this._screenSpace) {\n\t\t\t\t\t\tthis._resolution.set(value.x, value.y);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._resolution.set(this.system.app.graphicsDevice.width, this.system.app.graphicsDevice.height);\n\t\t\t\t}\n\t\t\t\tthis._updateScale();\n\t\t\t\tthis._calcProjectionMatrix();\n\t\t\t\tif (!this.entity._dirtyLocal) {\n\t\t\t\t\t\tthis.entity._dirtifyLocal();\n\t\t\t\t}\n\t\t\t\tthis.fire('set:resolution', this._resolution);\n\t\t\t\tthis._elements.forEach((element)=>element._onScreenResize(this._resolution));\n\t\t}\n\t\tget resolution() {\n\t\t\t\treturn this._resolution;\n\t\t}\n\t\tset referenceResolution(value) {\n\t\t\t\tthis._referenceResolution.set(value.x, value.y);\n\t\t\t\tthis._updateScale();\n\t\t\t\tthis._calcProjectionMatrix();\n\t\t\t\tif (!this.entity._dirtyLocal) {\n\t\t\t\t\t\tthis.entity._dirtifyLocal();\n\t\t\t\t}\n\t\t\t\tthis.fire('set:referenceresolution', this._resolution);\n\t\t\t\tthis._elements.forEach((element)=>element._onScreenResize(this._resolution));\n\t\t}\n\t\tget referenceResolution() {\n\t\t\t\tif (this._scaleMode === SCALEMODE_NONE) {\n\t\t\t\t\t\treturn this._resolution;\n\t\t\t\t}\n\t\t\t\treturn this._referenceResolution;\n\t\t}\n\t\tset screenSpace(value) {\n\t\t\t\tthis._screenSpace = value;\n\t\t\t\tif (this._screenSpace) {\n\t\t\t\t\t\tthis._resolution.set(this.system.app.graphicsDevice.width, this.system.app.graphicsDevice.height);\n\t\t\t\t}\n\t\t\t\tthis.resolution = this._resolution;\n\t\t\t\tif (!this.entity._dirtyLocal) {\n\t\t\t\t\t\tthis.entity._dirtifyLocal();\n\t\t\t\t}\n\t\t\t\tthis.fire('set:screenspace', this._screenSpace);\n\t\t\t\tthis._elements.forEach((element)=>element._onScreenSpaceChange());\n\t\t}\n\t\tget screenSpace() {\n\t\t\t\treturn this._screenSpace;\n\t\t}\n\t\tset scaleMode(value) {\n\t\t\t\tif (value !== SCALEMODE_NONE && value !== SCALEMODE_BLEND) {\n\t\t\t\t\t\tvalue = SCALEMODE_NONE;\n\t\t\t\t}\n\t\t\t\tif (!this._screenSpace && value !== SCALEMODE_NONE) {\n\t\t\t\t\t\tvalue = SCALEMODE_NONE;\n\t\t\t\t}\n\t\t\t\tthis._scaleMode = value;\n\t\t\t\tthis.resolution = this._resolution;\n\t\t\t\tthis.fire('set:scalemode', this._scaleMode);\n\t\t}\n\t\tget scaleMode() {\n\t\t\t\treturn this._scaleMode;\n\t\t}\n\t\tset scaleBlend(value) {\n\t\t\t\tthis._scaleBlend = value;\n\t\t\t\tthis._updateScale();\n\t\t\t\tthis._calcProjectionMatrix();\n\t\t\t\tif (!this.entity._dirtyLocal) {\n\t\t\t\t\t\tthis.entity._dirtifyLocal();\n\t\t\t\t}\n\t\t\t\tthis.fire('set:scaleblend', this._scaleBlend);\n\t\t\t\tthis._elements.forEach((element)=>element._onScreenResize(this._resolution));\n\t\t}\n\t\tget scaleBlend() {\n\t\t\t\treturn this._scaleBlend;\n\t\t}\n\t\tset priority(value) {\n\t\t\t\tif (value > 0xFF) {\n\t\t\t\t\t\tvalue = 0xFF;\n\t\t\t\t}\n\t\t\t\tif (this._priority === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._priority = value;\n\t\t\t\tthis.syncDrawOrder();\n\t\t}\n\t\tget priority() {\n\t\t\t\treturn this._priority;\n\t\t}\n}\n\nclass ScreenComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$7 = [\n\t\t'enabled'\n];\nclass ScreenComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'screen';\n\t\t\t\tthis.ComponentType = ScreenComponent;\n\t\t\t\tthis.DataType = ScreenComponentData;\n\t\t\t\tthis.schema = _schema$7;\n\t\t\t\tthis.windowResolution = new Vec2();\n\t\t\t\tthis._drawOrderSyncQueue = new IndexedList();\n\t\t\t\tthis.app.graphicsDevice.on('resizecanvas', this._onResize, this);\n\t\t\t\tthis.app.systems.on('update', this._onUpdate, this);\n\t\t\t\tthis.on('beforeremove', this.onRemoveComponent, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tif (data.priority !== undefined) component.priority = data.priority;\n\t\t\t\tif (data.screenSpace !== undefined) component.screenSpace = data.screenSpace;\n\t\t\t\tcomponent.cull = component.screenSpace;\n\t\t\t\tif (data.scaleMode !== undefined) component.scaleMode = data.scaleMode;\n\t\t\t\tif (data.scaleBlend !== undefined) component.scaleBlend = data.scaleBlend;\n\t\t\t\tif (data.resolution !== undefined) {\n\t\t\t\t\t\tif (data.resolution instanceof Vec2) {\n\t\t\t\t\t\t\t\tcomponent._resolution.copy(data.resolution);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomponent._resolution.set(data.resolution[0], data.resolution[1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcomponent.resolution = component._resolution;\n\t\t\t\t}\n\t\t\t\tif (data.referenceResolution !== undefined) {\n\t\t\t\t\t\tif (data.referenceResolution instanceof Vec2) {\n\t\t\t\t\t\t\t\tcomponent._referenceResolution.copy(data.referenceResolution);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomponent._referenceResolution.set(data.referenceResolution[0], data.referenceResolution[1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcomponent.referenceResolution = component._referenceResolution;\n\t\t\t\t}\n\t\t\t\tcomponent.syncDrawOrder();\n\t\t\t\tsuper.initializeComponentData(component, data, _schema$7);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.graphicsDevice.off('resizecanvas', this._onResize, this);\n\t\t\t\tthis.app.systems.off('update', this._onUpdate, this);\n\t\t}\n\t\t_onUpdate(dt) {\n\t\t\t\tconst components = this.store;\n\t\t\t\tfor(const id in components){\n\t\t\t\t\t\tif (components[id].entity.screen.update) components[id].entity.screen.update(dt);\n\t\t\t\t}\n\t\t}\n\t\t_onResize(width, height) {\n\t\t\t\tthis.windowResolution.x = width;\n\t\t\t\tthis.windowResolution.y = height;\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst screen = entity.screen;\n\t\t\t\treturn this.addComponent(clone, {\n\t\t\t\t\t\tenabled: screen.enabled,\n\t\t\t\t\t\tscreenSpace: screen.screenSpace,\n\t\t\t\t\t\tscaleMode: screen.scaleMode,\n\t\t\t\t\t\tscaleBlend: screen.scaleBlend,\n\t\t\t\t\t\tpriority: screen.priority,\n\t\t\t\t\t\tresolution: screen.resolution.clone(),\n\t\t\t\t\t\treferenceResolution: screen.referenceResolution.clone()\n\t\t\t\t});\n\t\t}\n\t\tonRemoveComponent(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tprocessDrawOrderSyncQueue() {\n\t\t\t\tconst list = this._drawOrderSyncQueue.list();\n\t\t\t\tfor(let i = 0; i < list.length; i++){\n\t\t\t\t\t\tconst item = list[i];\n\t\t\t\t\t\titem.callback.call(item.scope);\n\t\t\t\t}\n\t\t\t\tthis._drawOrderSyncQueue.clear();\n\t\t}\n\t\tqueueDrawOrderSync(id, fn, scope) {\n\t\t\t\tif (!this._drawOrderSyncQueue.list().length) {\n\t\t\t\t\t\tthis.app.once('prerender', this.processDrawOrderSyncQueue, this);\n\t\t\t\t}\n\t\t\t\tif (!this._drawOrderSyncQueue.has(id)) {\n\t\t\t\t\t\tthis._drawOrderSyncQueue.push(id, {\n\t\t\t\t\t\t\t\tcallback: fn,\n\t\t\t\t\t\t\t\tscope: scope\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n}\nComponent._buildAccessors(ScreenComponent.prototype, _schema$7);\n\nconst _inputScreenPosition = new Vec2();\nconst _inputWorldPosition = new Vec3();\nconst _ray = new Ray();\nconst _plane = new Plane();\nconst _normal = new Vec3();\nconst _point = new Vec3();\nconst _entityRotation = new Quat();\nconst OPPOSITE_AXIS = {\n\t\tx: 'y',\n\t\ty: 'x'\n};\nclass ElementDragHelper extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_DRAGSTART = 'drag:start';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DRAGEND = 'drag:end';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DRAGMOVE = 'drag:move';\n\t\t}\n\t\tconstructor(element, axis){\n\t\t\t\tsuper();\n\t\t\t\tif (!element || !(element instanceof ElementComponent)) {\n\t\t\t\t\t\tthrow new Error('Element was null or not an ElementComponent');\n\t\t\t\t}\n\t\t\t\tif (axis && axis !== 'x' && axis !== 'y') {\n\t\t\t\t\t\tthrow new Error(`Unrecognized axis: ${axis}`);\n\t\t\t\t}\n\t\t\t\tthis._element = element;\n\t\t\t\tthis._app = element.system.app;\n\t\t\t\tthis._axis = axis || null;\n\t\t\t\tthis._enabled = true;\n\t\t\t\tthis._dragScale = new Vec3();\n\t\t\t\tthis._dragStartMousePosition = new Vec3();\n\t\t\t\tthis._dragStartHandlePosition = new Vec3();\n\t\t\t\tthis._deltaMousePosition = new Vec3();\n\t\t\t\tthis._deltaHandlePosition = new Vec3();\n\t\t\t\tthis._isDragging = false;\n\t\t\t\tthis._toggleLifecycleListeners('on');\n\t\t}\n\t\t_toggleLifecycleListeners(onOrOff) {\n\t\t\t\tthis._element[onOrOff]('mousedown', this._onMouseDownOrTouchStart, this);\n\t\t\t\tthis._element[onOrOff]('touchstart', this._onMouseDownOrTouchStart, this);\n\t\t\t\tthis._element[onOrOff]('selectstart', this._onMouseDownOrTouchStart, this);\n\t\t}\n\t\t_toggleDragListeners(onOrOff) {\n\t\t\t\tconst isOn = onOrOff === 'on';\n\t\t\t\tif (this._hasDragListeners && isOn) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._app.mouse) {\n\t\t\t\t\t\tthis._element[onOrOff]('mousemove', this._onMove, this);\n\t\t\t\t\t\tthis._element[onOrOff]('mouseup', this._onMouseUpOrTouchEnd, this);\n\t\t\t\t}\n\t\t\t\tif (platform.touch) {\n\t\t\t\t\t\tthis._element[onOrOff]('touchmove', this._onMove, this);\n\t\t\t\t\t\tthis._element[onOrOff]('touchend', this._onMouseUpOrTouchEnd, this);\n\t\t\t\t\t\tthis._element[onOrOff]('touchcancel', this._onMouseUpOrTouchEnd, this);\n\t\t\t\t}\n\t\t\t\tthis._element[onOrOff]('selectmove', this._onMove, this);\n\t\t\t\tthis._element[onOrOff]('selectend', this._onMouseUpOrTouchEnd, this);\n\t\t\t\tthis._hasDragListeners = isOn;\n\t\t}\n\t\t_onMouseDownOrTouchStart(event) {\n\t\t\t\tif (this._element && !this._isDragging && this.enabled) {\n\t\t\t\t\t\tthis._dragCamera = event.camera;\n\t\t\t\t\t\tthis._calculateDragScale();\n\t\t\t\t\t\tconst currentMousePosition = this._screenToLocal(event);\n\t\t\t\t\t\tif (currentMousePosition) {\n\t\t\t\t\t\t\t\tthis._toggleDragListeners('on');\n\t\t\t\t\t\t\t\tthis._isDragging = true;\n\t\t\t\t\t\t\t\tthis._dragStartMousePosition.copy(currentMousePosition);\n\t\t\t\t\t\t\t\tthis._dragStartHandlePosition.copy(this._element.entity.getLocalPosition());\n\t\t\t\t\t\t\t\tthis.fire('drag:start');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onMouseUpOrTouchEnd() {\n\t\t\t\tif (this._isDragging) {\n\t\t\t\t\t\tthis._isDragging = false;\n\t\t\t\t\t\tthis._toggleDragListeners('off');\n\t\t\t\t\t\tthis.fire('drag:end');\n\t\t\t\t}\n\t\t}\n\t\t_screenToLocal(event) {\n\t\t\t\tif (event.inputSource) {\n\t\t\t\t\t\t_ray.set(event.inputSource.getOrigin(), event.inputSource.getDirection());\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._determineInputPosition(event);\n\t\t\t\t\t\tthis._chooseRayOriginAndDirection();\n\t\t\t\t}\n\t\t\t\t_normal.copy(this._element.entity.forward).mulScalar(-1);\n\t\t\t\t_plane.setFromPointNormal(this._element.entity.getPosition(), _normal);\n\t\t\t\tif (_plane.intersectsRay(_ray, _point)) {\n\t\t\t\t\t\t_entityRotation.copy(this._element.entity.getRotation()).invert().transformVector(_point, _point);\n\t\t\t\t\t\t_point.mul(this._dragScale);\n\t\t\t\t\t\treturn _point;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\t_determineInputPosition(event) {\n\t\t\t\tconst devicePixelRatio = this._app.graphicsDevice.maxPixelRatio;\n\t\t\t\tif (typeof event.x !== 'undefined' && typeof event.y !== 'undefined') {\n\t\t\t\t\t\t_inputScreenPosition.x = event.x * devicePixelRatio;\n\t\t\t\t\t\t_inputScreenPosition.y = event.y * devicePixelRatio;\n\t\t\t\t} else if (event.changedTouches) {\n\t\t\t\t\t\t_inputScreenPosition.x = event.changedTouches[0].x * devicePixelRatio;\n\t\t\t\t\t\t_inputScreenPosition.y = event.changedTouches[0].y * devicePixelRatio;\n\t\t\t\t} else {\n\t\t\t\t\t\tconsole.warn('Could not determine position from input event');\n\t\t\t\t}\n\t\t}\n\t\t_chooseRayOriginAndDirection() {\n\t\t\t\tif (this._element.screen && this._element.screen.screen.screenSpace) {\n\t\t\t\t\t\t_ray.origin.set(_inputScreenPosition.x, -_inputScreenPosition.y, 0);\n\t\t\t\t\t\t_ray.direction.copy(Vec3.FORWARD);\n\t\t\t\t} else {\n\t\t\t\t\t\t_inputWorldPosition.copy(this._dragCamera.screenToWorld(_inputScreenPosition.x, _inputScreenPosition.y, 1));\n\t\t\t\t\t\t_ray.origin.copy(this._dragCamera.entity.getPosition());\n\t\t\t\t\t\t_ray.direction.copy(_inputWorldPosition).sub(_ray.origin).normalize();\n\t\t\t\t}\n\t\t}\n\t\t_calculateDragScale() {\n\t\t\t\tlet current = this._element.entity.parent;\n\t\t\t\tconst screen = this._element.screen && this._element.screen.screen;\n\t\t\t\tconst isWithin2DScreen = screen && screen.screenSpace;\n\t\t\t\tconst screenScale = isWithin2DScreen ? screen.scale : 1;\n\t\t\t\tconst dragScale = this._dragScale;\n\t\t\t\tdragScale.set(screenScale, screenScale, screenScale);\n\t\t\t\twhile(current){\n\t\t\t\t\t\tdragScale.mul(current.getLocalScale());\n\t\t\t\t\t\tcurrent = current.parent;\n\t\t\t\t\t\tif (isWithin2DScreen && current.screen) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdragScale.x = 1 / dragScale.x;\n\t\t\t\tdragScale.y = 1 / dragScale.y;\n\t\t\t\tdragScale.z = 0;\n\t\t}\n\t\t_onMove(event) {\n\t\t\t\tconst { _element: element, _deltaMousePosition: deltaMousePosition, _deltaHandlePosition: deltaHandlePosition, _axis: axis } = this;\n\t\t\t\tif (element && this._isDragging && this.enabled && element.enabled && element.entity.enabled) {\n\t\t\t\t\t\tconst currentMousePosition = this._screenToLocal(event);\n\t\t\t\t\t\tif (currentMousePosition) {\n\t\t\t\t\t\t\t\tdeltaMousePosition.sub2(currentMousePosition, this._dragStartMousePosition);\n\t\t\t\t\t\t\t\tdeltaHandlePosition.add2(this._dragStartHandlePosition, deltaMousePosition);\n\t\t\t\t\t\t\t\tif (axis) {\n\t\t\t\t\t\t\t\t\t\tconst currentPosition = element.entity.getLocalPosition();\n\t\t\t\t\t\t\t\t\t\tconst constrainedAxis = OPPOSITE_AXIS[axis];\n\t\t\t\t\t\t\t\t\t\tdeltaHandlePosition[constrainedAxis] = currentPosition[constrainedAxis];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telement.entity.setLocalPosition(deltaHandlePosition);\n\t\t\t\t\t\t\t\tthis.fire('drag:move', deltaHandlePosition);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._toggleLifecycleListeners('off');\n\t\t\t\tthis._toggleDragListeners('off');\n\t\t}\n\t\tset enabled(value) {\n\t\t\t\tthis._enabled = value;\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled;\n\t\t}\n\t\tget isDragging() {\n\t\t\t\treturn this._isDragging;\n\t\t}\n}\n\nconst SCROLL_MODE_CLAMP = 0;\nconst SCROLL_MODE_BOUNCE = 1;\nconst SCROLL_MODE_INFINITE = 2;\nconst SCROLLBAR_VISIBILITY_SHOW_ALWAYS = 0;\nconst SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED = 1;\n\nconst _tempScrollValue = new Vec2();\nclass ScrollViewComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_SETSCROLL = 'set:scroll';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._viewportEntity = null, this._contentEntity = null, this._horizontalScrollbarEntity = null, this._verticalScrollbarEntity = null, this._evtElementRemove = null, this._evtViewportElementRemove = null, this._evtViewportResize = null, this._evtContentEntityElementAdd = null, this._evtContentElementRemove = null, this._evtContentResize = null, this._evtHorizontalScrollbarAdd = null, this._evtHorizontalScrollbarRemove = null, this._evtHorizontalScrollbarValue = null, this._evtVerticalScrollbarAdd = null, this._evtVerticalScrollbarRemove = null, this._evtVerticalScrollbarValue = null;\n\t\t\t\tthis._scrollbarUpdateFlags = {};\n\t\t\t\tthis._scrollbarEntities = {};\n\t\t\t\tthis._prevContentSizes = {};\n\t\t\t\tthis._prevContentSizes[ORIENTATION_HORIZONTAL] = null;\n\t\t\t\tthis._prevContentSizes[ORIENTATION_VERTICAL] = null;\n\t\t\t\tthis._scroll = new Vec2();\n\t\t\t\tthis._velocity = new Vec3();\n\t\t\t\tthis._dragStartPosition = new Vec3();\n\t\t\t\tthis._disabledContentInput = false;\n\t\t\t\tthis._disabledContentInputEntities = [];\n\t\t\t\tthis._toggleLifecycleListeners('on');\n\t\t\t\tthis._toggleElementListeners('on');\n\t\t}\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(arg) {\n\t\t\t\tthis._setValue('enabled', arg);\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this.data.enabled;\n\t\t}\n\t\tset horizontal(arg) {\n\t\t\t\tthis._setValue('horizontal', arg);\n\t\t}\n\t\tget horizontal() {\n\t\t\t\treturn this.data.horizontal;\n\t\t}\n\t\tset vertical(arg) {\n\t\t\t\tthis._setValue('vertical', arg);\n\t\t}\n\t\tget vertical() {\n\t\t\t\treturn this.data.vertical;\n\t\t}\n\t\tset scrollMode(arg) {\n\t\t\t\tthis._setValue('scrollMode', arg);\n\t\t}\n\t\tget scrollMode() {\n\t\t\t\treturn this.data.scrollMode;\n\t\t}\n\t\tset bounceAmount(arg) {\n\t\t\t\tthis._setValue('bounceAmount', arg);\n\t\t}\n\t\tget bounceAmount() {\n\t\t\t\treturn this.data.bounceAmount;\n\t\t}\n\t\tset friction(arg) {\n\t\t\t\tthis._setValue('friction', arg);\n\t\t}\n\t\tget friction() {\n\t\t\t\treturn this.data.friction;\n\t\t}\n\t\tset dragThreshold(arg) {\n\t\t\t\tthis._setValue('dragThreshold', arg);\n\t\t}\n\t\tget dragThreshold() {\n\t\t\t\treturn this.data.dragThreshold;\n\t\t}\n\t\tset useMouseWheel(arg) {\n\t\t\t\tthis._setValue('useMouseWheel', arg);\n\t\t}\n\t\tget useMouseWheel() {\n\t\t\t\treturn this.data.useMouseWheel;\n\t\t}\n\t\tset mouseWheelSensitivity(arg) {\n\t\t\t\tthis._setValue('mouseWheelSensitivity', arg);\n\t\t}\n\t\tget mouseWheelSensitivity() {\n\t\t\t\treturn this.data.mouseWheelSensitivity;\n\t\t}\n\t\tset horizontalScrollbarVisibility(arg) {\n\t\t\t\tthis._setValue('horizontalScrollbarVisibility', arg);\n\t\t}\n\t\tget horizontalScrollbarVisibility() {\n\t\t\t\treturn this.data.horizontalScrollbarVisibility;\n\t\t}\n\t\tset verticalScrollbarVisibility(arg) {\n\t\t\t\tthis._setValue('verticalScrollbarVisibility', arg);\n\t\t}\n\t\tget verticalScrollbarVisibility() {\n\t\t\t\treturn this.data.verticalScrollbarVisibility;\n\t\t}\n\t\tset viewportEntity(arg) {\n\t\t\t\tif (this._viewportEntity === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst isString = typeof arg === 'string';\n\t\t\t\tif (this._viewportEntity && isString && this._viewportEntity.getGuid() === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._viewportEntity) {\n\t\t\t\t\t\tthis._viewportEntityUnsubscribe();\n\t\t\t\t}\n\t\t\t\tif (arg instanceof GraphNode) {\n\t\t\t\t\t\tthis._viewportEntity = arg;\n\t\t\t\t} else if (isString) {\n\t\t\t\t\t\tthis._viewportEntity = this.system.app.getEntityFromIndex(arg) || null;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._viewportEntity = null;\n\t\t\t\t}\n\t\t\t\tif (this._viewportEntity) {\n\t\t\t\t\t\tthis._viewportEntitySubscribe();\n\t\t\t\t}\n\t\t\t\tif (this._viewportEntity) {\n\t\t\t\t\t\tthis.data.viewportEntity = this._viewportEntity.getGuid();\n\t\t\t\t} else if (isString && arg) {\n\t\t\t\t\t\tthis.data.viewportEntity = arg;\n\t\t\t\t}\n\t\t}\n\t\tget viewportEntity() {\n\t\t\t\treturn this._viewportEntity;\n\t\t}\n\t\tset contentEntity(arg) {\n\t\t\t\tif (this._contentEntity === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst isString = typeof arg === 'string';\n\t\t\t\tif (this._contentEntity && isString && this._contentEntity.getGuid() === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._contentEntity) {\n\t\t\t\t\t\tthis._contentEntityUnsubscribe();\n\t\t\t\t}\n\t\t\t\tif (arg instanceof GraphNode) {\n\t\t\t\t\t\tthis._contentEntity = arg;\n\t\t\t\t} else if (isString) {\n\t\t\t\t\t\tthis._contentEntity = this.system.app.getEntityFromIndex(arg) || null;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._contentEntity = null;\n\t\t\t\t}\n\t\t\t\tif (this._contentEntity) {\n\t\t\t\t\t\tthis._contentEntitySubscribe();\n\t\t\t\t}\n\t\t\t\tif (this._contentEntity) {\n\t\t\t\t\t\tthis.data.contentEntity = this._contentEntity.getGuid();\n\t\t\t\t} else if (isString && arg) {\n\t\t\t\t\t\tthis.data.contentEntity = arg;\n\t\t\t\t}\n\t\t}\n\t\tget contentEntity() {\n\t\t\t\treturn this._contentEntity;\n\t\t}\n\t\tset horizontalScrollbarEntity(arg) {\n\t\t\t\tif (this._horizontalScrollbarEntity === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst isString = typeof arg === 'string';\n\t\t\t\tif (this._horizontalScrollbarEntity && isString && this._horizontalScrollbarEntity.getGuid() === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._horizontalScrollbarEntity) {\n\t\t\t\t\t\tthis._horizontalScrollbarEntityUnsubscribe();\n\t\t\t\t}\n\t\t\t\tif (arg instanceof GraphNode) {\n\t\t\t\t\t\tthis._horizontalScrollbarEntity = arg;\n\t\t\t\t} else if (isString) {\n\t\t\t\t\t\tthis._horizontalScrollbarEntity = this.system.app.getEntityFromIndex(arg) || null;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._horizontalScrollbarEntity = null;\n\t\t\t\t}\n\t\t\t\tthis._scrollbarEntities[ORIENTATION_HORIZONTAL] = this._horizontalScrollbarEntity;\n\t\t\t\tif (this._horizontalScrollbarEntity) {\n\t\t\t\t\t\tthis._horizontalScrollbarEntitySubscribe();\n\t\t\t\t}\n\t\t\t\tif (this._horizontalScrollbarEntity) {\n\t\t\t\t\t\tthis.data.horizontalScrollbarEntity = this._horizontalScrollbarEntity.getGuid();\n\t\t\t\t} else if (isString && arg) {\n\t\t\t\t\t\tthis.data.horizontalScrollbarEntity = arg;\n\t\t\t\t}\n\t\t}\n\t\tget horizontalScrollbarEntity() {\n\t\t\t\treturn this._horizontalScrollbarEntity;\n\t\t}\n\t\tset verticalScrollbarEntity(arg) {\n\t\t\t\tif (this._verticalScrollbarEntity === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst isString = typeof arg === 'string';\n\t\t\t\tif (this._verticalScrollbarEntity && isString && this._verticalScrollbarEntity.getGuid() === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._verticalScrollbarEntity) {\n\t\t\t\t\t\tthis._verticalScrollbarEntityUnsubscribe();\n\t\t\t\t}\n\t\t\t\tif (arg instanceof GraphNode) {\n\t\t\t\t\t\tthis._verticalScrollbarEntity = arg;\n\t\t\t\t} else if (isString) {\n\t\t\t\t\t\tthis._verticalScrollbarEntity = this.system.app.getEntityFromIndex(arg) || null;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._verticalScrollbarEntity = null;\n\t\t\t\t}\n\t\t\t\tthis._scrollbarEntities[ORIENTATION_VERTICAL] = this._verticalScrollbarEntity;\n\t\t\t\tif (this._verticalScrollbarEntity) {\n\t\t\t\t\t\tthis._verticalScrollbarEntitySubscribe();\n\t\t\t\t}\n\t\t\t\tif (this._verticalScrollbarEntity) {\n\t\t\t\t\t\tthis.data.verticalScrollbarEntity = this._verticalScrollbarEntity.getGuid();\n\t\t\t\t} else if (isString && arg) {\n\t\t\t\t\t\tthis.data.verticalScrollbarEntity = arg;\n\t\t\t\t}\n\t\t}\n\t\tget verticalScrollbarEntity() {\n\t\t\t\treturn this._verticalScrollbarEntity;\n\t\t}\n\t\tset scroll(value) {\n\t\t\t\tthis._onSetScroll(value.x, value.y);\n\t\t}\n\t\tget scroll() {\n\t\t\t\treturn this._scroll;\n\t\t}\n\t\t_setValue(name, value) {\n\t\t\t\tconst data = this.data;\n\t\t\t\tconst oldValue = data[name];\n\t\t\t\tdata[name] = value;\n\t\t\t\tthis.fire('set', name, oldValue, value);\n\t\t}\n\t\t_toggleLifecycleListeners(onOrOff) {\n\t\t\t\tthis[onOrOff]('set_horizontal', this._onSetHorizontalScrollingEnabled, this);\n\t\t\t\tthis[onOrOff]('set_vertical', this._onSetVerticalScrollingEnabled, this);\n\t\t\t\tthis.entity[onOrOff]('element:add', this._onElementComponentAdd, this);\n\t\t}\n\t\t_toggleElementListeners(onOrOff) {\n\t\t\t\tif (this.entity.element) {\n\t\t\t\t\t\tif (onOrOff === 'on' && this._hasElementListeners) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.entity.element[onOrOff]('resize', this._syncAll, this);\n\t\t\t\t\t\tthis.entity.element[onOrOff]('mousewheel', this._onMouseWheel, this);\n\t\t\t\t\t\tthis._hasElementListeners = onOrOff === 'on';\n\t\t\t\t}\n\t\t}\n\t\t_onElementComponentAdd(entity) {\n\t\t\t\tthis._evtElementRemove = this.entity.element.once('beforeremove', this._onElementComponentRemove, this);\n\t\t\t\tthis._toggleElementListeners('on');\n\t\t}\n\t\t_onElementComponentRemove(entity) {\n\t\t\t\tthis._evtElementRemove?.off();\n\t\t\t\tthis._evtElementRemove = null;\n\t\t\t\tthis._toggleElementListeners('off');\n\t\t}\n\t\t_viewportEntitySubscribe() {\n\t\t\t\tthis._evtViewportEntityElementAdd = this._viewportEntity.on('element:add', this._onViewportElementGain, this);\n\t\t\t\tif (this._viewportEntity.element) {\n\t\t\t\t\t\tthis._onViewportElementGain();\n\t\t\t\t}\n\t\t}\n\t\t_viewportEntityUnsubscribe() {\n\t\t\t\tthis._evtViewportEntityElementAdd?.off();\n\t\t\t\tthis._evtViewportEntityElementAdd = null;\n\t\t\t\tif (this._viewportEntity?.element) {\n\t\t\t\t\t\tthis._onViewportElementLose();\n\t\t\t\t}\n\t\t}\n\t\t_viewportEntityElementSubscribe() {\n\t\t\t\tconst element = this._viewportEntity.element;\n\t\t\t\tthis._evtViewportElementRemove = element.once('beforeremove', this._onViewportElementLose, this);\n\t\t\t\tthis._evtViewportResize = element.on('resize', this._syncAll, this);\n\t\t}\n\t\t_viewportEntityElementUnsubscribe() {\n\t\t\t\tthis._evtViewportElementRemove?.off();\n\t\t\t\tthis._evtViewportElementRemove = null;\n\t\t\t\tthis._evtViewportResize?.off();\n\t\t\t\tthis._evtViewportResize = null;\n\t\t}\n\t\t_onViewportElementGain() {\n\t\t\t\tthis._viewportEntityElementSubscribe();\n\t\t\t\tthis._syncAll();\n\t\t}\n\t\t_onViewportElementLose() {\n\t\t\t\tthis._viewportEntityElementUnsubscribe();\n\t\t}\n\t\t_contentEntitySubscribe() {\n\t\t\t\tthis._evtContentEntityElementAdd = this._contentEntity.on('element:add', this._onContentElementGain, this);\n\t\t\t\tif (this._contentEntity.element) {\n\t\t\t\t\t\tthis._onContentElementGain();\n\t\t\t\t}\n\t\t}\n\t\t_contentEntityUnsubscribe() {\n\t\t\t\tthis._evtContentEntityElementAdd?.off();\n\t\t\t\tthis._evtContentEntityElementAdd = null;\n\t\t\t\tif (this._contentEntity?.element) {\n\t\t\t\t\t\tthis._onContentElementLose();\n\t\t\t\t}\n\t\t}\n\t\t_contentEntityElementSubscribe() {\n\t\t\t\tconst element = this._contentEntity.element;\n\t\t\t\tthis._evtContentElementRemove = element.once('beforeremove', this._onContentElementLose, this);\n\t\t\t\tthis._evtContentResize = element.on('resize', this._syncAll, this);\n\t\t}\n\t\t_contentEntityElementUnsubscribe() {\n\t\t\t\tthis._evtContentElementRemove?.off();\n\t\t\t\tthis._evtContentElementRemove = null;\n\t\t\t\tthis._evtContentResize?.off();\n\t\t\t\tthis._evtContentResize = null;\n\t\t}\n\t\t_onContentElementGain() {\n\t\t\t\tthis._contentEntityElementSubscribe();\n\t\t\t\tthis._destroyDragHelper();\n\t\t\t\tthis._contentDragHelper = new ElementDragHelper(this._contentEntity.element);\n\t\t\t\tthis._contentDragHelper.on('drag:start', this._onContentDragStart, this);\n\t\t\t\tthis._contentDragHelper.on('drag:end', this._onContentDragEnd, this);\n\t\t\t\tthis._contentDragHelper.on('drag:move', this._onContentDragMove, this);\n\t\t\t\tthis._prevContentSizes[ORIENTATION_HORIZONTAL] = null;\n\t\t\t\tthis._prevContentSizes[ORIENTATION_VERTICAL] = null;\n\t\t\t\tthis._syncAll();\n\t\t}\n\t\t_onContentElementLose() {\n\t\t\t\tthis._contentEntityElementUnsubscribe();\n\t\t\t\tthis._destroyDragHelper();\n\t\t}\n\t\t_onContentDragStart() {\n\t\t\t\tif (this._contentEntity && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis._dragStartPosition.copy(this._contentEntity.getLocalPosition());\n\t\t\t\t}\n\t\t}\n\t\t_onContentDragEnd() {\n\t\t\t\tthis._prevContentDragPosition = null;\n\t\t\t\tthis._enableContentInput();\n\t\t}\n\t\t_onContentDragMove(position) {\n\t\t\t\tif (this._contentEntity && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis._wasDragged = true;\n\t\t\t\t\t\tthis._setScrollFromContentPosition(position);\n\t\t\t\t\t\tthis._setVelocityFromContentPositionDelta(position);\n\t\t\t\t\t\tif (!this._disabledContentInput) {\n\t\t\t\t\t\t\t\tconst dx = position.x - this._dragStartPosition.x;\n\t\t\t\t\t\t\t\tconst dy = position.y - this._dragStartPosition.y;\n\t\t\t\t\t\t\t\tif (Math.abs(dx) > this.dragThreshold || Math.abs(dy) > this.dragThreshold) {\n\t\t\t\t\t\t\t\t\t\tthis._disableContentInput();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_horizontalScrollbarEntitySubscribe() {\n\t\t\t\tthis._evtHorizontalScrollbarAdd = this._horizontalScrollbarEntity.on('scrollbar:add', this._onHorizontalScrollbarGain, this);\n\t\t\t\tif (this._horizontalScrollbarEntity.scrollbar) {\n\t\t\t\t\t\tthis._onHorizontalScrollbarGain();\n\t\t\t\t}\n\t\t}\n\t\t_verticalScrollbarEntitySubscribe() {\n\t\t\t\tthis._evtVerticalScrollbarAdd = this._verticalScrollbarEntity.on('scrollbar:add', this._onVerticalScrollbarGain, this);\n\t\t\t\tif (this._verticalScrollbarEntity.scrollbar) {\n\t\t\t\t\t\tthis._onVerticalScrollbarGain();\n\t\t\t\t}\n\t\t}\n\t\t_horizontalScrollbarEntityUnsubscribe() {\n\t\t\t\tthis._evtHorizontalScrollbarAdd?.off();\n\t\t\t\tthis._evtHorizontalScrollbarAdd = null;\n\t\t\t\tif (this._horizontalScrollbarEntity.scrollbar) {\n\t\t\t\t\t\tthis._onHorizontalScrollbarLose();\n\t\t\t\t}\n\t\t}\n\t\t_verticalScrollbarEntityUnsubscribe() {\n\t\t\t\tthis._evtVerticalScrollbarAdd?.off();\n\t\t\t\tthis._evtVerticalScrollbarAdd = null;\n\t\t\t\tif (this._verticalScrollbarEntity.scrollbar) {\n\t\t\t\t\t\tthis._onVerticalScrollbarLose();\n\t\t\t\t}\n\t\t}\n\t\t_onSetHorizontalScrollbarValue(scrollValueX) {\n\t\t\t\tif (!this._scrollbarUpdateFlags[ORIENTATION_HORIZONTAL] && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis._onSetScroll(scrollValueX, null);\n\t\t\t\t}\n\t\t}\n\t\t_onSetVerticalScrollbarValue(scrollValueY) {\n\t\t\t\tif (!this._scrollbarUpdateFlags[ORIENTATION_VERTICAL] && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis._onSetScroll(null, scrollValueY);\n\t\t\t\t}\n\t\t}\n\t\t_onHorizontalScrollbarGain() {\n\t\t\t\tconst scrollbar = this._horizontalScrollbarEntity?.scrollbar;\n\t\t\t\tthis._evtHorizontalScrollbarRemove = scrollbar.on('beforeremove', this._onHorizontalScrollbarLose, this);\n\t\t\t\tthis._evtHorizontalScrollbarValue = scrollbar.on('set:value', this._onSetHorizontalScrollbarValue, this);\n\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_HORIZONTAL);\n\t\t\t\tthis._syncScrollbarPosition(ORIENTATION_HORIZONTAL);\n\t\t}\n\t\t_onVerticalScrollbarGain() {\n\t\t\t\tconst scrollbar = this._verticalScrollbarEntity?.scrollbar;\n\t\t\t\tthis._evtVerticalScrollbarRemove = scrollbar.on('beforeremove', this._onVerticalScrollbarLose, this);\n\t\t\t\tthis._evtVerticalScrollbarValue = scrollbar.on('set:value', this._onSetVerticalScrollbarValue, this);\n\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_VERTICAL);\n\t\t\t\tthis._syncScrollbarPosition(ORIENTATION_VERTICAL);\n\t\t}\n\t\t_onHorizontalScrollbarLose() {\n\t\t\t\tthis._evtHorizontalScrollbarRemove?.off();\n\t\t\t\tthis._evtHorizontalScrollbarRemove = null;\n\t\t\t\tthis._evtHorizontalScrollbarValue?.off();\n\t\t\t\tthis._evtHorizontalScrollbarValue = null;\n\t\t}\n\t\t_onVerticalScrollbarLose() {\n\t\t\t\tthis._evtVerticalScrollbarRemove?.off();\n\t\t\t\tthis._evtVerticalScrollbarRemove = null;\n\t\t\t\tthis._evtVerticalScrollbarValue?.off();\n\t\t\t\tthis._evtVerticalScrollbarValue = null;\n\t\t}\n\t\t_onSetHorizontalScrollingEnabled() {\n\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_HORIZONTAL);\n\t\t}\n\t\t_onSetVerticalScrollingEnabled() {\n\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_VERTICAL);\n\t\t}\n\t\t_onSetScroll(x, y, resetVelocity) {\n\t\t\t\tif (resetVelocity !== false) {\n\t\t\t\t\t\tthis._velocity.set(0, 0, 0);\n\t\t\t\t}\n\t\t\t\tconst xChanged = this._updateAxis(x, 'x', ORIENTATION_HORIZONTAL);\n\t\t\t\tconst yChanged = this._updateAxis(y, 'y', ORIENTATION_VERTICAL);\n\t\t\t\tif (xChanged || yChanged) {\n\t\t\t\t\t\tthis.fire('set:scroll', this._scroll);\n\t\t\t\t}\n\t\t}\n\t\t_updateAxis(scrollValue, axis, orientation) {\n\t\t\t\tconst hasChanged = scrollValue !== null && Math.abs(scrollValue - this._scroll[axis]) > 1e-5;\n\t\t\t\tif (hasChanged || this._isDragging() || scrollValue === 0) {\n\t\t\t\t\t\tthis._scroll[axis] = this._determineNewScrollValue(scrollValue, axis, orientation);\n\t\t\t\t\t\tthis._syncContentPosition(orientation);\n\t\t\t\t\t\tthis._syncScrollbarPosition(orientation);\n\t\t\t\t}\n\t\t\t\treturn hasChanged;\n\t\t}\n\t\t_determineNewScrollValue(scrollValue, axis, orientation) {\n\t\t\t\tif (!this._getScrollingEnabled(orientation)) {\n\t\t\t\t\t\treturn this._scroll[axis];\n\t\t\t\t}\n\t\t\t\tswitch(this.scrollMode){\n\t\t\t\t\t\tcase SCROLL_MODE_CLAMP:\n\t\t\t\t\t\t\t\treturn math.clamp(scrollValue, 0, this._getMaxScrollValue(orientation));\n\t\t\t\t\t\tcase SCROLL_MODE_BOUNCE:\n\t\t\t\t\t\t\t\tthis._setVelocityFromOvershoot(scrollValue, axis, orientation);\n\t\t\t\t\t\t\t\treturn scrollValue;\n\t\t\t\t\t\tcase SCROLL_MODE_INFINITE:\n\t\t\t\t\t\t\t\treturn scrollValue;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tconsole.warn(`Unhandled scroll mode:${this.scrollMode}`);\n\t\t\t\t\t\t\t\treturn scrollValue;\n\t\t\t\t}\n\t\t}\n\t\t_syncAll() {\n\t\t\t\tthis._syncContentPosition(ORIENTATION_HORIZONTAL);\n\t\t\t\tthis._syncContentPosition(ORIENTATION_VERTICAL);\n\t\t\t\tthis._syncScrollbarPosition(ORIENTATION_HORIZONTAL);\n\t\t\t\tthis._syncScrollbarPosition(ORIENTATION_VERTICAL);\n\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_HORIZONTAL);\n\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_VERTICAL);\n\t\t}\n\t\t_syncContentPosition(orientation) {\n\t\t\t\tif (!this._contentEntity) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst axis = this._getAxis(orientation);\n\t\t\t\tconst sign = this._getSign(orientation);\n\t\t\t\tconst prevContentSize = this._prevContentSizes[orientation];\n\t\t\t\tconst currContentSize = this._getContentSize(orientation);\n\t\t\t\tif (prevContentSize !== null && Math.abs(prevContentSize - currContentSize) > 1e-4) {\n\t\t\t\t\t\tconst prevMaxOffset = this._getMaxOffset(orientation, prevContentSize);\n\t\t\t\t\t\tconst currMaxOffset = this._getMaxOffset(orientation, currContentSize);\n\t\t\t\t\t\tif (currMaxOffset === 0) {\n\t\t\t\t\t\t\t\tthis._scroll[axis] = 1;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._scroll[axis] = math.clamp(this._scroll[axis] * prevMaxOffset / currMaxOffset, 0, 1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst offset = this._scroll[axis] * this._getMaxOffset(orientation);\n\t\t\t\tconst contentPosition = this._contentEntity.getLocalPosition();\n\t\t\t\tcontentPosition[axis] = offset * sign;\n\t\t\t\tthis._contentEntity.setLocalPosition(contentPosition);\n\t\t\t\tthis._prevContentSizes[orientation] = currContentSize;\n\t\t}\n\t\t_syncScrollbarPosition(orientation) {\n\t\t\t\tconst scrollbarEntity = this._scrollbarEntities[orientation];\n\t\t\t\tif (!scrollbarEntity?.scrollbar) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst axis = this._getAxis(orientation);\n\t\t\t\tthis._scrollbarUpdateFlags[orientation] = true;\n\t\t\t\tscrollbarEntity.scrollbar.value = this._scroll[axis];\n\t\t\t\tscrollbarEntity.scrollbar.handleSize = this._getScrollbarHandleSize(axis, orientation);\n\t\t\t\tthis._scrollbarUpdateFlags[orientation] = false;\n\t\t}\n\t\t_syncScrollbarEnabledState(orientation) {\n\t\t\t\tconst entity = this._scrollbarEntities[orientation];\n\t\t\t\tif (!entity) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst isScrollingEnabled = this._getScrollingEnabled(orientation);\n\t\t\t\tconst requestedVisibility = this._getScrollbarVisibility(orientation);\n\t\t\t\tswitch(requestedVisibility){\n\t\t\t\t\t\tcase SCROLLBAR_VISIBILITY_SHOW_ALWAYS:\n\t\t\t\t\t\t\t\tentity.enabled = isScrollingEnabled;\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tcase SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED:\n\t\t\t\t\t\t\t\tentity.enabled = isScrollingEnabled && this._contentIsLargerThanViewport(orientation);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tconsole.warn(`Unhandled scrollbar visibility:${requestedVisibility}`);\n\t\t\t\t\t\t\t\tentity.enabled = isScrollingEnabled;\n\t\t\t\t}\n\t\t}\n\t\t_contentIsLargerThanViewport(orientation) {\n\t\t\t\treturn this._getContentSize(orientation) > this._getViewportSize(orientation);\n\t\t}\n\t\t_contentPositionToScrollValue(contentPosition) {\n\t\t\t\tconst maxOffsetH = this._getMaxOffset(ORIENTATION_HORIZONTAL);\n\t\t\t\tconst maxOffsetV = this._getMaxOffset(ORIENTATION_VERTICAL);\n\t\t\t\tif (maxOffsetH === 0) {\n\t\t\t\t\t\t_tempScrollValue.x = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempScrollValue.x = contentPosition.x / maxOffsetH;\n\t\t\t\t}\n\t\t\t\tif (maxOffsetV === 0) {\n\t\t\t\t\t\t_tempScrollValue.y = 0;\n\t\t\t\t} else {\n\t\t\t\t\t\t_tempScrollValue.y = contentPosition.y / -maxOffsetV;\n\t\t\t\t}\n\t\t\t\treturn _tempScrollValue;\n\t\t}\n\t\t_getMaxOffset(orientation, contentSize) {\n\t\t\t\tcontentSize = contentSize === undefined ? this._getContentSize(orientation) : contentSize;\n\t\t\t\tconst viewportSize = this._getViewportSize(orientation);\n\t\t\t\tif (contentSize < viewportSize) {\n\t\t\t\t\t\treturn -this._getViewportSize(orientation);\n\t\t\t\t}\n\t\t\t\treturn viewportSize - contentSize;\n\t\t}\n\t\t_getMaxScrollValue(orientation) {\n\t\t\t\treturn this._contentIsLargerThanViewport(orientation) ? 1 : 0;\n\t\t}\n\t\t_getScrollbarHandleSize(axis, orientation) {\n\t\t\t\tconst viewportSize = this._getViewportSize(orientation);\n\t\t\t\tconst contentSize = this._getContentSize(orientation);\n\t\t\t\tif (Math.abs(contentSize) < 0.001) {\n\t\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tconst handleSize = Math.min(viewportSize / contentSize, 1);\n\t\t\t\tconst overshoot = this._toOvershoot(this._scroll[axis], orientation);\n\t\t\t\tif (overshoot === 0) {\n\t\t\t\t\t\treturn handleSize;\n\t\t\t\t}\n\t\t\t\treturn handleSize / (1 + Math.abs(overshoot));\n\t\t}\n\t\t_getViewportSize(orientation) {\n\t\t\t\treturn this._getSize(orientation, this._viewportEntity);\n\t\t}\n\t\t_getContentSize(orientation) {\n\t\t\t\treturn this._getSize(orientation, this._contentEntity);\n\t\t}\n\t\t_getSize(orientation, entity) {\n\t\t\t\tif (entity?.element) {\n\t\t\t\t\t\treturn entity.element[this._getCalculatedDimension(orientation)];\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t}\n\t\t_getScrollingEnabled(orientation) {\n\t\t\t\tif (orientation === ORIENTATION_HORIZONTAL) {\n\t\t\t\t\t\treturn this.horizontal;\n\t\t\t\t} else if (orientation === ORIENTATION_VERTICAL) {\n\t\t\t\t\t\treturn this.vertical;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\t_getScrollbarVisibility(orientation) {\n\t\t\t\tif (orientation === ORIENTATION_HORIZONTAL) {\n\t\t\t\t\t\treturn this.horizontalScrollbarVisibility;\n\t\t\t\t} else if (orientation === ORIENTATION_VERTICAL) {\n\t\t\t\t\t\treturn this.verticalScrollbarVisibility;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\t_getSign(orientation) {\n\t\t\t\treturn orientation === ORIENTATION_HORIZONTAL ? 1 : -1;\n\t\t}\n\t\t_getAxis(orientation) {\n\t\t\t\treturn orientation === ORIENTATION_HORIZONTAL ? 'x' : 'y';\n\t\t}\n\t\t_getCalculatedDimension(orientation) {\n\t\t\t\treturn orientation === ORIENTATION_HORIZONTAL ? 'calculatedWidth' : 'calculatedHeight';\n\t\t}\n\t\t_destroyDragHelper() {\n\t\t\t\tif (this._contentDragHelper) {\n\t\t\t\t\t\tthis._contentDragHelper.destroy();\n\t\t\t\t}\n\t\t}\n\t\tonUpdate() {\n\t\t\t\tif (this._contentEntity) {\n\t\t\t\t\t\tthis._updateVelocity();\n\t\t\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_HORIZONTAL);\n\t\t\t\t\t\tthis._syncScrollbarEnabledState(ORIENTATION_VERTICAL);\n\t\t\t\t}\n\t\t}\n\t\t_updateVelocity() {\n\t\t\t\tif (!this._isDragging()) {\n\t\t\t\t\t\tif (this.scrollMode === SCROLL_MODE_BOUNCE) {\n\t\t\t\t\t\t\t\tif (this._hasOvershoot('x', ORIENTATION_HORIZONTAL)) {\n\t\t\t\t\t\t\t\t\t\tthis._setVelocityFromOvershoot(this.scroll.x, 'x', ORIENTATION_HORIZONTAL);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this._hasOvershoot('y', ORIENTATION_VERTICAL)) {\n\t\t\t\t\t\t\t\t\t\tthis._setVelocityFromOvershoot(this.scroll.y, 'y', ORIENTATION_VERTICAL);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (Math.abs(this._velocity.x) > 1e-4 || Math.abs(this._velocity.y) > 1e-4) {\n\t\t\t\t\t\t\t\tconst position = this._contentEntity.getLocalPosition();\n\t\t\t\t\t\t\t\tposition.x += this._velocity.x;\n\t\t\t\t\t\t\t\tposition.y += this._velocity.y;\n\t\t\t\t\t\t\t\tthis._contentEntity.setLocalPosition(position);\n\t\t\t\t\t\t\t\tthis._setScrollFromContentPosition(position);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._velocity.x *= 1 - this.friction;\n\t\t\t\t\t\tthis._velocity.y *= 1 - this.friction;\n\t\t\t\t}\n\t\t}\n\t\t_hasOvershoot(axis, orientation) {\n\t\t\t\treturn Math.abs(this._toOvershoot(this.scroll[axis], orientation)) > 0.001;\n\t\t}\n\t\t_toOvershoot(scrollValue, orientation) {\n\t\t\t\tconst maxScrollValue = this._getMaxScrollValue(orientation);\n\t\t\t\tif (scrollValue < 0) {\n\t\t\t\t\t\treturn scrollValue;\n\t\t\t\t} else if (scrollValue > maxScrollValue) {\n\t\t\t\t\t\treturn scrollValue - maxScrollValue;\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t}\n\t\t_setVelocityFromOvershoot(scrollValue, axis, orientation) {\n\t\t\t\tconst overshootValue = this._toOvershoot(scrollValue, orientation);\n\t\t\t\tconst overshootPixels = overshootValue * this._getMaxOffset(orientation) * this._getSign(orientation);\n\t\t\t\tif (Math.abs(overshootPixels) > 0) {\n\t\t\t\t\t\tthis._velocity[axis] = -overshootPixels / (this.bounceAmount * 50 + 1);\n\t\t\t\t}\n\t\t}\n\t\t_setVelocityFromContentPositionDelta(position) {\n\t\t\t\tif (this._prevContentDragPosition) {\n\t\t\t\t\t\tthis._velocity.sub2(position, this._prevContentDragPosition);\n\t\t\t\t\t\tthis._prevContentDragPosition.copy(position);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._velocity.set(0, 0, 0);\n\t\t\t\t\t\tthis._prevContentDragPosition = position.clone();\n\t\t\t\t}\n\t\t}\n\t\t_setScrollFromContentPosition(position) {\n\t\t\t\tlet scrollValue = this._contentPositionToScrollValue(position);\n\t\t\t\tif (this._isDragging()) {\n\t\t\t\t\t\tscrollValue = this._applyScrollValueTension(scrollValue);\n\t\t\t\t}\n\t\t\t\tthis._onSetScroll(scrollValue.x, scrollValue.y, false);\n\t\t}\n\t\t_applyScrollValueTension(scrollValue) {\n\t\t\t\tconst factor = 1;\n\t\t\t\tlet max = this._getMaxScrollValue(ORIENTATION_HORIZONTAL);\n\t\t\t\tlet overshoot = this._toOvershoot(scrollValue.x, ORIENTATION_HORIZONTAL);\n\t\t\t\tif (overshoot > 0) {\n\t\t\t\t\t\tscrollValue.x = max + factor * Math.log10(1 + overshoot);\n\t\t\t\t} else if (overshoot < 0) {\n\t\t\t\t\t\tscrollValue.x = -1 * Math.log10(1 - overshoot);\n\t\t\t\t}\n\t\t\t\tmax = this._getMaxScrollValue(ORIENTATION_VERTICAL);\n\t\t\t\tovershoot = this._toOvershoot(scrollValue.y, ORIENTATION_VERTICAL);\n\t\t\t\tif (overshoot > 0) {\n\t\t\t\t\t\tscrollValue.y = max + factor * Math.log10(1 + overshoot);\n\t\t\t\t} else if (overshoot < 0) {\n\t\t\t\t\t\tscrollValue.y = -1 * Math.log10(1 - overshoot);\n\t\t\t\t}\n\t\t\t\treturn scrollValue;\n\t\t}\n\t\t_isDragging() {\n\t\t\t\treturn this._contentDragHelper && this._contentDragHelper.isDragging;\n\t\t}\n\t\t_setScrollbarComponentsEnabled(enabled) {\n\t\t\t\tif (this._horizontalScrollbarEntity?.scrollbar) {\n\t\t\t\t\t\tthis._horizontalScrollbarEntity.scrollbar.enabled = enabled;\n\t\t\t\t}\n\t\t\t\tif (this._verticalScrollbarEntity?.scrollbar) {\n\t\t\t\t\t\tthis._verticalScrollbarEntity.scrollbar.enabled = enabled;\n\t\t\t\t}\n\t\t}\n\t\t_setContentDraggingEnabled(enabled) {\n\t\t\t\tif (this._contentDragHelper) {\n\t\t\t\t\t\tthis._contentDragHelper.enabled = enabled;\n\t\t\t\t}\n\t\t}\n\t\t_onMouseWheel(event) {\n\t\t\t\tif (!this.useMouseWheel || !this._contentEntity?.element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst wheelEvent = event.event;\n\t\t\t\tconst normalizedDeltaX = wheelEvent.deltaX / this._contentEntity.element.calculatedWidth * this.mouseWheelSensitivity.x;\n\t\t\t\tconst normalizedDeltaY = wheelEvent.deltaY / this._contentEntity.element.calculatedHeight * this.mouseWheelSensitivity.y;\n\t\t\t\tconst scrollX = math.clamp(this._scroll.x + normalizedDeltaX, 0, this._getMaxScrollValue(ORIENTATION_HORIZONTAL));\n\t\t\t\tconst scrollY = math.clamp(this._scroll.y + normalizedDeltaY, 0, this._getMaxScrollValue(ORIENTATION_VERTICAL));\n\t\t\t\tthis.scroll = new Vec2(scrollX, scrollY);\n\t\t}\n\t\t_enableContentInput() {\n\t\t\t\twhile(this._disabledContentInputEntities.length){\n\t\t\t\t\t\tconst e = this._disabledContentInputEntities.pop();\n\t\t\t\t\t\tif (e.element) {\n\t\t\t\t\t\t\t\te.element.useInput = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._disabledContentInput = false;\n\t\t}\n\t\t_disableContentInput() {\n\t\t\t\tconst _disableInput = (e)=>{\n\t\t\t\t\t\tif (e.element && e.element.useInput) {\n\t\t\t\t\t\t\t\tthis._disabledContentInputEntities.push(e);\n\t\t\t\t\t\t\t\te.element.useInput = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst children = e.children;\n\t\t\t\t\t\tfor(let i = 0, l = children.length; i < l; i++){\n\t\t\t\t\t\t\t\t_disableInput(children[i]);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tif (this._contentEntity) {\n\t\t\t\t\t\tconst children = this._contentEntity.children;\n\t\t\t\t\t\tfor(let i = 0, l = children.length; i < l; i++){\n\t\t\t\t\t\t\t\t_disableInput(children[i]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._disabledContentInput = true;\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._setScrollbarComponentsEnabled(true);\n\t\t\t\tthis._setContentDraggingEnabled(true);\n\t\t\t\tthis._syncAll();\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._setScrollbarComponentsEnabled(false);\n\t\t\t\tthis._setContentDraggingEnabled(false);\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis._toggleLifecycleListeners('off');\n\t\t\t\tthis._toggleElementListeners('off');\n\t\t\t\tthis._destroyDragHelper();\n\t\t}\n\t\tresolveDuplicatedEntityReferenceProperties(oldScrollView, duplicatedIdsMap) {\n\t\t\t\tif (oldScrollView.viewportEntity) {\n\t\t\t\t\t\tthis.viewportEntity = duplicatedIdsMap[oldScrollView.viewportEntity.getGuid()];\n\t\t\t\t}\n\t\t\t\tif (oldScrollView.contentEntity) {\n\t\t\t\t\t\tthis.contentEntity = duplicatedIdsMap[oldScrollView.contentEntity.getGuid()];\n\t\t\t\t}\n\t\t\t\tif (oldScrollView.horizontalScrollbarEntity) {\n\t\t\t\t\t\tthis.horizontalScrollbarEntity = duplicatedIdsMap[oldScrollView.horizontalScrollbarEntity.getGuid()];\n\t\t\t\t}\n\t\t\t\tif (oldScrollView.verticalScrollbarEntity) {\n\t\t\t\t\t\tthis.verticalScrollbarEntity = duplicatedIdsMap[oldScrollView.verticalScrollbarEntity.getGuid()];\n\t\t\t\t}\n\t\t}\n}\n\nconst DEFAULT_DRAG_THRESHOLD$1 = 10;\nclass ScrollViewComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t\t\tthis.dragThreshold = DEFAULT_DRAG_THRESHOLD$1;\n\t\t\t\tthis.useMouseWheel = true;\n\t\t\t\tthis.mouseWheelSensitivity = new Vec2(1, 1);\n\t\t\t\tthis.horizontalScrollbarVisibility = 0;\n\t\t\t\tthis.verticalScrollbarVisibility = 0;\n\t\t\t\tthis.viewportEntity = null;\n\t\t\t\tthis.contentEntity = null;\n\t\t\t\tthis.horizontalScrollbarEntity = null;\n\t\t\t\tthis.verticalScrollbarEntity = null;\n\t\t}\n}\n\nconst _schema$6 = [\n\t\t{\n\t\t\t\tname: 'enabled',\n\t\t\t\ttype: 'boolean'\n\t\t},\n\t\t{\n\t\t\t\tname: 'horizontal',\n\t\t\t\ttype: 'boolean'\n\t\t},\n\t\t{\n\t\t\t\tname: 'vertical',\n\t\t\t\ttype: 'boolean'\n\t\t},\n\t\t{\n\t\t\t\tname: 'scrollMode',\n\t\t\t\ttype: 'number'\n\t\t},\n\t\t{\n\t\t\t\tname: 'bounceAmount',\n\t\t\t\ttype: 'number'\n\t\t},\n\t\t{\n\t\t\t\tname: 'friction',\n\t\t\t\ttype: 'number'\n\t\t},\n\t\t{\n\t\t\t\tname: 'dragThreshold',\n\t\t\t\ttype: 'number'\n\t\t},\n\t\t{\n\t\t\t\tname: 'useMouseWheel',\n\t\t\t\ttype: 'boolean'\n\t\t},\n\t\t{\n\t\t\t\tname: 'mouseWheelSensitivity',\n\t\t\t\ttype: 'vec2'\n\t\t},\n\t\t{\n\t\t\t\tname: 'horizontalScrollbarVisibility',\n\t\t\t\ttype: 'number'\n\t\t},\n\t\t{\n\t\t\t\tname: 'verticalScrollbarVisibility',\n\t\t\t\ttype: 'number'\n\t\t}\n];\nconst DEFAULT_DRAG_THRESHOLD = 10;\nclass ScrollViewComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'scrollview';\n\t\t\t\tthis.ComponentType = ScrollViewComponent;\n\t\t\t\tthis.DataType = ScrollViewComponentData;\n\t\t\t\tthis.schema = _schema$6;\n\t\t\t\tthis.on('beforeremove', this._onRemoveComponent, this);\n\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tif (data.dragThreshold === undefined) {\n\t\t\t\t\t\tdata.dragThreshold = DEFAULT_DRAG_THRESHOLD;\n\t\t\t\t}\n\t\t\t\tif (data.useMouseWheel === undefined) {\n\t\t\t\t\t\tdata.useMouseWheel = true;\n\t\t\t\t}\n\t\t\t\tif (data.mouseWheelSensitivity === undefined) {\n\t\t\t\t\t\tdata.mouseWheelSensitivity = new Vec2(1, 1);\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, data, _schema$6);\n\t\t\t\tcomponent.viewportEntity = data.viewportEntity;\n\t\t\t\tcomponent.contentEntity = data.contentEntity;\n\t\t\t\tcomponent.horizontalScrollbarEntity = data.horizontalScrollbarEntity;\n\t\t\t\tcomponent.verticalScrollbarEntity = data.verticalScrollbarEntity;\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tconst components = this.store;\n\t\t\t\tfor(const id in components){\n\t\t\t\t\t\tconst entity = components[id].entity;\n\t\t\t\t\t\tconst component = entity.scrollview;\n\t\t\t\t\t\tif (component.enabled && entity.enabled) {\n\t\t\t\t\t\t\t\tcomponent.onUpdate();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onRemoveComponent(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t}\n}\n\nclass ScrollbarComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_SETVALUE = 'set:value';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._handleEntity = null, this._evtHandleEntityElementAdd = null, this._evtHandleEntityChanges = [];\n\t\t\t\tthis._toggleLifecycleListeners('on');\n\t\t}\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(arg) {\n\t\t\t\tthis._setValue('enabled', arg);\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this.data.enabled;\n\t\t}\n\t\tset orientation(arg) {\n\t\t\t\tthis._setValue('orientation', arg);\n\t\t}\n\t\tget orientation() {\n\t\t\t\treturn this.data.orientation;\n\t\t}\n\t\tset value(arg) {\n\t\t\t\tthis._setValue('value', arg);\n\t\t}\n\t\tget value() {\n\t\t\t\treturn this.data.value;\n\t\t}\n\t\tset handleSize(arg) {\n\t\t\t\tthis._setValue('handleSize', arg);\n\t\t}\n\t\tget handleSize() {\n\t\t\t\treturn this.data.handleSize;\n\t\t}\n\t\tset handleEntity(arg) {\n\t\t\t\tif (this._handleEntity === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst isString = typeof arg === 'string';\n\t\t\t\tif (this._handleEntity && isString && this._handleEntity.getGuid() === arg) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._handleEntity) {\n\t\t\t\t\t\tthis._handleEntityUnsubscribe();\n\t\t\t\t}\n\t\t\t\tif (arg instanceof GraphNode) {\n\t\t\t\t\t\tthis._handleEntity = arg;\n\t\t\t\t} else if (isString) {\n\t\t\t\t\t\tthis._handleEntity = this.system.app.getEntityFromIndex(arg) || null;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._handleEntity = null;\n\t\t\t\t}\n\t\t\t\tif (this._handleEntity) {\n\t\t\t\t\t\tthis._handleEntitySubscribe();\n\t\t\t\t}\n\t\t\t\tif (this._handleEntity) {\n\t\t\t\t\t\tthis.data.handleEntity = this._handleEntity.getGuid();\n\t\t\t\t} else if (isString && arg) {\n\t\t\t\t\t\tthis.data.handleEntity = arg;\n\t\t\t\t}\n\t\t}\n\t\tget handleEntity() {\n\t\t\t\treturn this._handleEntity;\n\t\t}\n\t\t_setValue(name, value) {\n\t\t\t\tconst data = this.data;\n\t\t\t\tconst oldValue = data[name];\n\t\t\t\tdata[name] = value;\n\t\t\t\tthis.fire('set', name, oldValue, value);\n\t\t}\n\t\t_toggleLifecycleListeners(onOrOff) {\n\t\t\t\tthis[onOrOff]('set_value', this._onSetValue, this);\n\t\t\t\tthis[onOrOff]('set_handleSize', this._onSetHandleSize, this);\n\t\t\t\tthis[onOrOff]('set_orientation', this._onSetOrientation, this);\n\t\t}\n\t\t_handleEntitySubscribe() {\n\t\t\t\tthis._evtHandleEntityElementAdd = this._handleEntity.on('element:add', this._onHandleElementGain, this);\n\t\t\t\tif (this._handleEntity.element) {\n\t\t\t\t\t\tthis._onHandleElementGain();\n\t\t\t\t}\n\t\t}\n\t\t_handleEntityUnsubscribe() {\n\t\t\t\tthis._evtHandleEntityElementAdd?.off();\n\t\t\t\tthis._evtHandleEntityElementAdd = null;\n\t\t\t\tif (this._handleEntity?.element) {\n\t\t\t\t\t\tthis._onHandleElementLose();\n\t\t\t\t}\n\t\t}\n\t\t_handleEntityElementSubscribe() {\n\t\t\t\tconst element = this._handleEntity.element;\n\t\t\t\tconst handles = this._evtHandleEntityChanges;\n\t\t\t\thandles.push(element.once('beforeremove', this._onHandleElementLose, this));\n\t\t\t\thandles.push(element.on('set:anchor', this._onSetHandleAlignment, this));\n\t\t\t\thandles.push(element.on('set:margin', this._onSetHandleAlignment, this));\n\t\t\t\thandles.push(element.on('set:pivot', this._onSetHandleAlignment, this));\n\t\t}\n\t\t_handleEntityElementUnsubscribe() {\n\t\t\t\tfor(let i = 0; i < this._evtHandleEntityChanges.length; i++){\n\t\t\t\t\t\tthis._evtHandleEntityChanges[i].off();\n\t\t\t\t}\n\t\t\t\tthis._evtHandleEntityChanges.length = 0;\n\t\t}\n\t\t_onHandleElementGain() {\n\t\t\t\tthis._handleEntityElementSubscribe();\n\t\t\t\tthis._destroyDragHelper();\n\t\t\t\tthis._handleDragHelper = new ElementDragHelper(this._handleEntity.element, this._getAxis());\n\t\t\t\tthis._handleDragHelper.on('drag:move', this._onHandleDrag, this);\n\t\t\t\tthis._updateHandlePositionAndSize();\n\t\t}\n\t\t_onHandleElementLose() {\n\t\t\t\tthis._handleEntityElementUnsubscribe();\n\t\t\t\tthis._destroyDragHelper();\n\t\t}\n\t\t_onHandleDrag(position) {\n\t\t\t\tif (this._handleEntity && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.value = this._handlePositionToScrollValue(position[this._getAxis()]);\n\t\t\t\t}\n\t\t}\n\t\t_onSetValue(name, oldValue, newValue) {\n\t\t\t\tif (Math.abs(newValue - oldValue) > 1e-5) {\n\t\t\t\t\t\tthis.data.value = math.clamp(newValue, 0, 1);\n\t\t\t\t\t\tthis._updateHandlePositionAndSize();\n\t\t\t\t\t\tthis.fire('set:value', this.data.value);\n\t\t\t\t}\n\t\t}\n\t\t_onSetHandleSize(name, oldValue, newValue) {\n\t\t\t\tif (Math.abs(newValue - oldValue) > 1e-5) {\n\t\t\t\t\t\tthis.data.handleSize = math.clamp(newValue, 0, 1);\n\t\t\t\t\t\tthis._updateHandlePositionAndSize();\n\t\t\t\t}\n\t\t}\n\t\t_onSetHandleAlignment() {\n\t\t\t\tthis._updateHandlePositionAndSize();\n\t\t}\n\t\t_onSetOrientation(name, oldValue, newValue) {\n\t\t\t\tif (newValue !== oldValue && this._handleEntity?.element) {\n\t\t\t\t\t\tthis._handleEntity.element[this._getOppositeDimension()] = 0;\n\t\t\t\t}\n\t\t}\n\t\t_updateHandlePositionAndSize() {\n\t\t\t\tconst handleEntity = this._handleEntity;\n\t\t\t\tconst handleElement = handleEntity?.element;\n\t\t\t\tif (handleEntity) {\n\t\t\t\t\t\tconst position = handleEntity.getLocalPosition();\n\t\t\t\t\t\tposition[this._getAxis()] = this._getHandlePosition();\n\t\t\t\t\t\thandleEntity.setLocalPosition(position);\n\t\t\t\t}\n\t\t\t\tif (handleElement) {\n\t\t\t\t\t\thandleElement[this._getDimension()] = this._getHandleLength();\n\t\t\t\t}\n\t\t}\n\t\t_handlePositionToScrollValue(handlePosition) {\n\t\t\t\treturn handlePosition * this._getSign() / this._getUsableTrackLength();\n\t\t}\n\t\t_scrollValueToHandlePosition(value) {\n\t\t\t\treturn value * this._getSign() * this._getUsableTrackLength();\n\t\t}\n\t\t_getUsableTrackLength() {\n\t\t\t\treturn Math.max(this._getTrackLength() - this._getHandleLength(), 0.001);\n\t\t}\n\t\t_getTrackLength() {\n\t\t\t\tif (this.entity.element) {\n\t\t\t\t\t\treturn this.orientation === ORIENTATION_HORIZONTAL ? this.entity.element.calculatedWidth : this.entity.element.calculatedHeight;\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t}\n\t\t_getHandleLength() {\n\t\t\t\treturn this._getTrackLength() * this.handleSize;\n\t\t}\n\t\t_getHandlePosition() {\n\t\t\t\treturn this._scrollValueToHandlePosition(this.value);\n\t\t}\n\t\t_getSign() {\n\t\t\t\treturn this.orientation === ORIENTATION_HORIZONTAL ? 1 : -1;\n\t\t}\n\t\t_getAxis() {\n\t\t\t\treturn this.orientation === ORIENTATION_HORIZONTAL ? 'x' : 'y';\n\t\t}\n\t\t_getDimension() {\n\t\t\t\treturn this.orientation === ORIENTATION_HORIZONTAL ? 'width' : 'height';\n\t\t}\n\t\t_getOppositeDimension() {\n\t\t\t\treturn this.orientation === ORIENTATION_HORIZONTAL ? 'height' : 'width';\n\t\t}\n\t\t_destroyDragHelper() {\n\t\t\t\tif (this._handleDragHelper) {\n\t\t\t\t\t\tthis._handleDragHelper.destroy();\n\t\t\t\t}\n\t\t}\n\t\t_setHandleDraggingEnabled(enabled) {\n\t\t\t\tif (this._handleDragHelper) {\n\t\t\t\t\t\tthis._handleDragHelper.enabled = enabled;\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._setHandleDraggingEnabled(true);\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._setHandleDraggingEnabled(false);\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis._destroyDragHelper();\n\t\t\t\tthis._toggleLifecycleListeners('off');\n\t\t}\n\t\tresolveDuplicatedEntityReferenceProperties(oldScrollbar, duplicatedIdsMap) {\n\t\t\t\tif (oldScrollbar.handleEntity) {\n\t\t\t\t\t\tthis.handleEntity = duplicatedIdsMap[oldScrollbar.handleEntity.getGuid()];\n\t\t\t\t}\n\t\t}\n}\n\nclass ScrollbarComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t\t\tthis.orientation = ORIENTATION_HORIZONTAL;\n\t\t\t\tthis.value = 0;\n\t\t\t\tthis.handleSize = 0;\n\t\t\t\tthis.handleEntity = null;\n\t\t}\n}\n\nconst _schema$5 = [\n\t\t{\n\t\t\t\tname: 'enabled',\n\t\t\t\ttype: 'boolean'\n\t\t},\n\t\t{\n\t\t\t\tname: 'orientation',\n\t\t\t\ttype: 'number'\n\t\t},\n\t\t{\n\t\t\t\tname: 'value',\n\t\t\t\ttype: 'number'\n\t\t},\n\t\t{\n\t\t\t\tname: 'handleSize',\n\t\t\t\ttype: 'number'\n\t\t}\n];\nclass ScrollbarComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'scrollbar';\n\t\t\t\tthis.ComponentType = ScrollbarComponent;\n\t\t\t\tthis.DataType = ScrollbarComponentData;\n\t\t\t\tthis.schema = _schema$5;\n\t\t\t\tthis.on('add', this._onAddComponent, this);\n\t\t\t\tthis.on('beforeremove', this._onRemoveComponent, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tsuper.initializeComponentData(component, data, _schema$5);\n\t\t\t\tcomponent.handleEntity = data.handleEntity;\n\t\t}\n\t\t_onAddComponent(entity) {\n\t\t\t\tentity.fire('scrollbar:add');\n\t\t}\n\t\t_onRemoveComponent(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n}\n\nconst instanceOptions = {\n\t\tvolume: 0,\n\t\tpitch: 0,\n\t\tloop: false,\n\t\tstartTime: 0,\n\t\tduration: 0,\n\t\tposition: new Vec3(),\n\t\tmaxDistance: 0,\n\t\trefDistance: 0,\n\t\trollOffFactor: 0,\n\t\tdistanceModel: 0,\n\t\tonPlay: null,\n\t\tonPause: null,\n\t\tonResume: null,\n\t\tonStop: null,\n\t\tonEnd: null\n};\nclass SoundSlot extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_PLAY = 'play';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PAUSE = 'pause';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESUME = 'resume';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STOP = 'stop';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_END = 'end';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_LOAD = 'load';\n\t\t}\n\t\tconstructor(component, name = 'Untitled', options = {}){\n\t\t\t\tsuper(), this.instances = [];\n\t\t\t\tthis._component = component;\n\t\t\t\tthis._assets = component.system.app.assets;\n\t\t\t\tthis._manager = component.system.manager;\n\t\t\t\tthis.name = name;\n\t\t\t\tthis._volume = options.volume !== undefined ? math.clamp(Number(options.volume) || 0, 0, 1) : 1;\n\t\t\t\tthis._pitch = options.pitch !== undefined ? Math.max(0.01, Number(options.pitch) || 0) : 1;\n\t\t\t\tthis._loop = !!(options.loop !== undefined ? options.loop : false);\n\t\t\t\tthis._duration = options.duration > 0 ? options.duration : null;\n\t\t\t\tthis._startTime = Math.max(0, Number(options.startTime) || 0);\n\t\t\t\tthis._overlap = !!options.overlap;\n\t\t\t\tthis._autoPlay = !!options.autoPlay;\n\t\t\t\tthis._firstNode = null;\n\t\t\t\tthis._lastNode = null;\n\t\t\t\tthis._asset = options.asset;\n\t\t\t\tif (this._asset instanceof Asset) {\n\t\t\t\t\t\tthis._asset = this._asset.id;\n\t\t\t\t}\n\t\t\t\tthis._onInstancePlayHandler = this._onInstancePlay.bind(this);\n\t\t\t\tthis._onInstancePauseHandler = this._onInstancePause.bind(this);\n\t\t\t\tthis._onInstanceResumeHandler = this._onInstanceResume.bind(this);\n\t\t\t\tthis._onInstanceStopHandler = this._onInstanceStop.bind(this);\n\t\t\t\tthis._onInstanceEndHandler = this._onInstanceEnd.bind(this);\n\t\t}\n\t\tplay() {\n\t\t\t\tif (!this.overlap) {\n\t\t\t\t\t\tthis.stop();\n\t\t\t\t}\n\t\t\t\tif (!this.isLoaded && !this._hasAsset()) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tconst instance = this._createInstance();\n\t\t\t\tthis.instances.push(instance);\n\t\t\t\tif (!this.isLoaded) {\n\t\t\t\t\t\tconst onLoad = function(sound) {\n\t\t\t\t\t\t\t\tconst playWhenLoaded = instance._playWhenLoaded;\n\t\t\t\t\t\t\t\tinstance.sound = sound;\n\t\t\t\t\t\t\t\tif (playWhenLoaded) {\n\t\t\t\t\t\t\t\t\t\tinstance.play();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tthis.off('load', onLoad);\n\t\t\t\t\t\tthis.once('load', onLoad);\n\t\t\t\t\t\tthis.load();\n\t\t\t\t} else {\n\t\t\t\t\t\tinstance.play();\n\t\t\t\t}\n\t\t\t\treturn instance;\n\t\t}\n\t\tpause() {\n\t\t\t\tlet paused = false;\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\tif (instances[i].pause()) {\n\t\t\t\t\t\t\t\tpaused = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn paused;\n\t\t}\n\t\tresume() {\n\t\t\t\tlet resumed = false;\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\tif (instances[i].resume()) {\n\t\t\t\t\t\t\t\tresumed = true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn resumed;\n\t\t}\n\t\tstop() {\n\t\t\t\tlet stopped = false;\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tlet i = instances.length;\n\t\t\t\twhile(i--){\n\t\t\t\t\t\tinstances[i].stop();\n\t\t\t\t\t\tstopped = true;\n\t\t\t\t}\n\t\t\t\tinstances.length = 0;\n\t\t\t\treturn stopped;\n\t\t}\n\t\tload() {\n\t\t\t\tif (!this._hasAsset()) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst asset = this._assets.get(this._asset);\n\t\t\t\tif (!asset) {\n\t\t\t\t\t\tthis._assets.off(`add:${this._asset}`, this._onAssetAdd, this);\n\t\t\t\t\t\tthis._assets.once(`add:${this._asset}`, this._onAssetAdd, this);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tasset.off('remove', this._onAssetRemoved, this);\n\t\t\t\tasset.on('remove', this._onAssetRemoved, this);\n\t\t\t\tif (!asset.resource) {\n\t\t\t\t\t\tasset.off('load', this._onAssetLoad, this);\n\t\t\t\t\t\tasset.once('load', this._onAssetLoad, this);\n\t\t\t\t\t\tthis._assets.load(asset);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.fire('load', asset.resource);\n\t\t}\n\t\tsetExternalNodes(firstNode, lastNode) {\n\t\t\t\tif (!firstNode) {\n\t\t\t\t\t\tconsole.error('The firstNode must have a valid AudioNode');\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!lastNode) {\n\t\t\t\t\t\tlastNode = firstNode;\n\t\t\t\t}\n\t\t\t\tthis._firstNode = firstNode;\n\t\t\t\tthis._lastNode = lastNode;\n\t\t\t\tif (!this._overlap) {\n\t\t\t\t\t\tconst instances = this.instances;\n\t\t\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\t\t\tinstances[i].setExternalNodes(firstNode, lastNode);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tclearExternalNodes() {\n\t\t\t\tthis._firstNode = null;\n\t\t\t\tthis._lastNode = null;\n\t\t\t\tif (!this._overlap) {\n\t\t\t\t\t\tconst instances = this.instances;\n\t\t\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\t\t\tinstances[i].clearExternalNodes();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgetExternalNodes() {\n\t\t\t\treturn [\n\t\t\t\t\t\tthis._firstNode,\n\t\t\t\t\t\tthis._lastNode\n\t\t\t\t];\n\t\t}\n\t\t_hasAsset() {\n\t\t\t\treturn this._asset != null;\n\t\t}\n\t\t_createInstance() {\n\t\t\t\tlet instance = null;\n\t\t\t\tconst component = this._component;\n\t\t\t\tlet sound = null;\n\t\t\t\tif (this._hasAsset()) {\n\t\t\t\t\t\tconst asset = this._assets.get(this._asset);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tsound = asset.resource;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst data = instanceOptions;\n\t\t\t\tdata.volume = this._volume * component.volume;\n\t\t\t\tdata.pitch = this._pitch * component.pitch;\n\t\t\t\tdata.loop = this._loop;\n\t\t\t\tdata.startTime = this._startTime;\n\t\t\t\tdata.duration = this._duration;\n\t\t\t\tdata.onPlay = this._onInstancePlayHandler;\n\t\t\t\tdata.onPause = this._onInstancePauseHandler;\n\t\t\t\tdata.onResume = this._onInstanceResumeHandler;\n\t\t\t\tdata.onStop = this._onInstanceStopHandler;\n\t\t\t\tdata.onEnd = this._onInstanceEndHandler;\n\t\t\t\tif (component.positional) {\n\t\t\t\t\t\tdata.position.copy(component.entity.getPosition());\n\t\t\t\t\t\tdata.maxDistance = component.maxDistance;\n\t\t\t\t\t\tdata.refDistance = component.refDistance;\n\t\t\t\t\t\tdata.rollOffFactor = component.rollOffFactor;\n\t\t\t\t\t\tdata.distanceModel = component.distanceModel;\n\t\t\t\t\t\tinstance = new SoundInstance3d(this._manager, sound, data);\n\t\t\t\t} else {\n\t\t\t\t\t\tinstance = new SoundInstance(this._manager, sound, data);\n\t\t\t\t}\n\t\t\t\tif (this._firstNode) {\n\t\t\t\t\t\tinstance.setExternalNodes(this._firstNode, this._lastNode);\n\t\t\t\t}\n\t\t\t\treturn instance;\n\t\t}\n\t\t_onInstancePlay(instance) {\n\t\t\t\tthis.fire('play', instance);\n\t\t\t\tthis._component.fire('play', this, instance);\n\t\t}\n\t\t_onInstancePause(instance) {\n\t\t\t\tthis.fire('pause', instance);\n\t\t\t\tthis._component.fire('pause', this, instance);\n\t\t}\n\t\t_onInstanceResume(instance) {\n\t\t\t\tthis.fire('resume', instance);\n\t\t\t\tthis._component.fire('resume', this, instance);\n\t\t}\n\t\t_onInstanceStop(instance) {\n\t\t\t\tconst idx = this.instances.indexOf(instance);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tthis.instances.splice(idx, 1);\n\t\t\t\t}\n\t\t\t\tthis.fire('stop', instance);\n\t\t\t\tthis._component.fire('stop', this, instance);\n\t\t}\n\t\t_onInstanceEnd(instance) {\n\t\t\t\tconst idx = this.instances.indexOf(instance);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tthis.instances.splice(idx, 1);\n\t\t\t\t}\n\t\t\t\tthis.fire('end', instance);\n\t\t\t\tthis._component.fire('end', this, instance);\n\t\t}\n\t\t_onAssetAdd(asset) {\n\t\t\t\tthis.load();\n\t\t}\n\t\t_onAssetLoad(asset) {\n\t\t\t\tthis.load();\n\t\t}\n\t\t_onAssetRemoved(asset) {\n\t\t\t\tasset.off('remove', this._onAssetRemoved, this);\n\t\t\t\tthis._assets.off(`add:${asset.id}`, this._onAssetAdd, this);\n\t\t\t\tthis.stop();\n\t\t}\n\t\tupdatePosition(position) {\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\tinstances[i].position = position;\n\t\t\t\t}\n\t\t}\n\t\tset asset(value) {\n\t\t\t\tconst old = this._asset;\n\t\t\t\tif (old) {\n\t\t\t\t\t\tthis._assets.off(`add:${old}`, this._onAssetAdd, this);\n\t\t\t\t\t\tconst oldAsset = this._assets.get(old);\n\t\t\t\t\t\tif (oldAsset) {\n\t\t\t\t\t\t\t\toldAsset.off('remove', this._onAssetRemoved, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._asset = value;\n\t\t\t\tif (this._asset instanceof Asset) {\n\t\t\t\t\t\tthis._asset = this._asset.id;\n\t\t\t\t}\n\t\t\t\tif (this._hasAsset() && this._component.enabled && this._component.entity.enabled) {\n\t\t\t\t\t\tthis.load();\n\t\t\t\t}\n\t\t}\n\t\tget asset() {\n\t\t\t\treturn this._asset;\n\t\t}\n\t\tset autoPlay(value) {\n\t\t\t\tthis._autoPlay = !!value;\n\t\t}\n\t\tget autoPlay() {\n\t\t\t\treturn this._autoPlay;\n\t\t}\n\t\tset duration(value) {\n\t\t\t\tthis._duration = Math.max(0, Number(value) || 0) || null;\n\t\t\t\tif (!this._overlap) {\n\t\t\t\t\t\tconst instances = this.instances;\n\t\t\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\t\t\tinstances[i].duration = this._duration;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget duration() {\n\t\t\t\tlet assetDuration = 0;\n\t\t\t\tif (this._hasAsset()) {\n\t\t\t\t\t\tconst asset = this._assets.get(this._asset);\n\t\t\t\t\t\tassetDuration = asset?.resource ? asset.resource.duration : 0;\n\t\t\t\t}\n\t\t\t\tif (this._duration != null) {\n\t\t\t\t\t\treturn this._duration % (assetDuration || 1);\n\t\t\t\t}\n\t\t\t\treturn assetDuration;\n\t\t}\n\t\tget isLoaded() {\n\t\t\t\tif (this._hasAsset()) {\n\t\t\t\t\t\tconst asset = this._assets.get(this._asset);\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\treturn !!asset.resource;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tget isPaused() {\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tconst len = instances.length;\n\t\t\t\tif (len === 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tif (!instances[i].isPaused) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tget isPlaying() {\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\tif (instances[i].isPlaying) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tget isStopped() {\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\tif (!instances[i].isStopped) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tset loop(value) {\n\t\t\t\tthis._loop = !!value;\n\t\t\t\tconst instances = this.instances;\n\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\tinstances[i].loop = this._loop;\n\t\t\t\t}\n\t\t}\n\t\tget loop() {\n\t\t\t\treturn this._loop;\n\t\t}\n\t\tset overlap(value) {\n\t\t\t\tthis._overlap = !!value;\n\t\t}\n\t\tget overlap() {\n\t\t\t\treturn this._overlap;\n\t\t}\n\t\tset pitch(value) {\n\t\t\t\tthis._pitch = Math.max(Number(value) || 0, 0.01);\n\t\t\t\tif (!this._overlap) {\n\t\t\t\t\t\tconst instances = this.instances;\n\t\t\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\t\t\tinstances[i].pitch = this.pitch * this._component.pitch;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget pitch() {\n\t\t\t\treturn this._pitch;\n\t\t}\n\t\tset startTime(value) {\n\t\t\t\tthis._startTime = Math.max(0, Number(value) || 0);\n\t\t\t\tif (!this._overlap) {\n\t\t\t\t\t\tconst instances = this.instances;\n\t\t\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\t\t\tinstances[i].startTime = this._startTime;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget startTime() {\n\t\t\t\treturn this._startTime;\n\t\t}\n\t\tset volume(value) {\n\t\t\t\tthis._volume = math.clamp(Number(value) || 0, 0, 1);\n\t\t\t\tif (!this._overlap) {\n\t\t\t\t\t\tconst instances = this.instances;\n\t\t\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\t\t\tinstances[i].volume = this._volume * this._component.volume;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget volume() {\n\t\t\t\treturn this._volume;\n\t\t}\n}\n\nclass SoundComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_PLAY = 'play';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PAUSE = 'pause';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESUME = 'resume';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STOP = 'stop';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_END = 'end';\n\t\t}\n\t\t_updateSoundInstances(property, value, isFactor) {\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\tconst slot = slots[key];\n\t\t\t\t\t\tif (!slot.overlap) {\n\t\t\t\t\t\t\t\tconst instances = slot.instances;\n\t\t\t\t\t\t\t\tfor(let i = 0, len = instances.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\tinstances[i][property] = isFactor ? slot[property] * value : value;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tset distanceModel(value) {\n\t\t\t\tthis._distanceModel = value;\n\t\t\t\tthis._updateSoundInstances('distanceModel', value, false);\n\t\t}\n\t\tget distanceModel() {\n\t\t\t\treturn this._distanceModel;\n\t\t}\n\t\tset maxDistance(value) {\n\t\t\t\tthis._maxDistance = value;\n\t\t\t\tthis._updateSoundInstances('maxDistance', value, false);\n\t\t}\n\t\tget maxDistance() {\n\t\t\t\treturn this._maxDistance;\n\t\t}\n\t\tset refDistance(value) {\n\t\t\t\tthis._refDistance = value;\n\t\t\t\tthis._updateSoundInstances('refDistance', value, false);\n\t\t}\n\t\tget refDistance() {\n\t\t\t\treturn this._refDistance;\n\t\t}\n\t\tset rollOffFactor(value) {\n\t\t\t\tthis._rollOffFactor = value;\n\t\t\t\tthis._updateSoundInstances('rollOffFactor', value, false);\n\t\t}\n\t\tget rollOffFactor() {\n\t\t\t\treturn this._rollOffFactor;\n\t\t}\n\t\tset pitch(value) {\n\t\t\t\tthis._pitch = value;\n\t\t\t\tthis._updateSoundInstances('pitch', value, true);\n\t\t}\n\t\tget pitch() {\n\t\t\t\treturn this._pitch;\n\t\t}\n\t\tset volume(value) {\n\t\t\t\tthis._volume = value;\n\t\t\t\tthis._updateSoundInstances('volume', value, true);\n\t\t}\n\t\tget volume() {\n\t\t\t\treturn this._volume;\n\t\t}\n\t\tset positional(newValue) {\n\t\t\t\tthis._positional = newValue;\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\tconst slot = slots[key];\n\t\t\t\t\t\tif (!slot.overlap) {\n\t\t\t\t\t\t\t\tconst instances = slot.instances;\n\t\t\t\t\t\t\t\tconst oldLength = instances.length;\n\t\t\t\t\t\t\t\tfor(let i = oldLength - 1; i >= 0; i--){\n\t\t\t\t\t\t\t\t\t\tconst isPlaying = instances[i].isPlaying || instances[i].isSuspended;\n\t\t\t\t\t\t\t\t\t\tconst currentTime = instances[i].currentTime;\n\t\t\t\t\t\t\t\t\t\tif (isPlaying) {\n\t\t\t\t\t\t\t\t\t\t\t\tinstances[i].stop();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst instance = slot._createInstance();\n\t\t\t\t\t\t\t\t\t\tif (isPlaying) {\n\t\t\t\t\t\t\t\t\t\t\t\tinstance.play();\n\t\t\t\t\t\t\t\t\t\t\t\tinstance.currentTime = currentTime;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tinstances.push(instance);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget positional() {\n\t\t\t\treturn this._positional;\n\t\t}\n\t\tset slots(newValue) {\n\t\t\t\tconst oldValue = this._slots;\n\t\t\t\tif (oldValue) {\n\t\t\t\t\t\tfor(const key in oldValue){\n\t\t\t\t\t\t\t\toldValue[key].stop();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst slots = {};\n\t\t\t\tfor(const key in newValue){\n\t\t\t\t\t\tif (!(newValue[key] instanceof SoundSlot)) {\n\t\t\t\t\t\t\t\tif (newValue[key].name) {\n\t\t\t\t\t\t\t\t\t\tslots[newValue[key].name] = new SoundSlot(this, newValue[key].name, newValue[key]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tslots[newValue[key].name] = newValue[key];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._slots = slots;\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.onEnable();\n\t\t\t\t}\n\t\t}\n\t\tget slots() {\n\t\t\t\treturn this._slots;\n\t\t}\n\t\tonEnable() {\n\t\t\t\tif (this.system._inTools) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tconst playingBeforeDisable = this._playingBeforeDisable;\n\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\tconst slot = slots[key];\n\t\t\t\t\t\tif (slot.autoPlay && slot.isStopped) {\n\t\t\t\t\t\t\t\tslot.play();\n\t\t\t\t\t\t} else if (playingBeforeDisable[key]) {\n\t\t\t\t\t\t\t\tslot.resume();\n\t\t\t\t\t\t} else if (!slot.isLoaded) {\n\t\t\t\t\t\t\t\tslot.load();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tconst playingBeforeDisable = {};\n\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\tif (!slots[key].overlap) {\n\t\t\t\t\t\t\t\tif (slots[key].isPlaying) {\n\t\t\t\t\t\t\t\t\t\tslots[key].pause();\n\t\t\t\t\t\t\t\t\t\tplayingBeforeDisable[key] = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._playingBeforeDisable = playingBeforeDisable;\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.off();\n\t\t}\n\t\taddSlot(name, options) {\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tif (slots[name]) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst slot = new SoundSlot(this, name, options);\n\t\t\t\tslots[name] = slot;\n\t\t\t\tif (slot.autoPlay && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tslot.play();\n\t\t\t\t}\n\t\t\t\treturn slot;\n\t\t}\n\t\tremoveSlot(name) {\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tif (slots[name]) {\n\t\t\t\t\t\tslots[name].stop();\n\t\t\t\t\t\tdelete slots[name];\n\t\t\t\t}\n\t\t}\n\t\tslot(name) {\n\t\t\t\treturn this._slots[name];\n\t\t}\n\t\t_getSlotProperty(name, property) {\n\t\t\t\tif (!this.enabled || !this.entity.enabled) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tconst slot = this._slots[name];\n\t\t\t\tif (!slot) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn slot[property];\n\t\t}\n\t\tisPlaying(name) {\n\t\t\t\treturn this._getSlotProperty(name, 'isPlaying') || false;\n\t\t}\n\t\tisLoaded(name) {\n\t\t\t\treturn this._getSlotProperty(name, 'isLoaded') || false;\n\t\t}\n\t\tisPaused(name) {\n\t\t\t\treturn this._getSlotProperty(name, 'isPaused') || false;\n\t\t}\n\t\tisStopped(name) {\n\t\t\t\treturn this._getSlotProperty(name, 'isStopped') || false;\n\t\t}\n\t\tplay(name) {\n\t\t\t\tif (!this.enabled || !this.entity.enabled) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst slot = this._slots[name];\n\t\t\t\tif (!slot) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn slot.play();\n\t\t}\n\t\tpause(name) {\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tif (name) {\n\t\t\t\t\t\tconst slot = slots[name];\n\t\t\t\t\t\tif (!slot) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tslot.pause();\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\t\t\tslots[key].pause();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tresume(name) {\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tif (name) {\n\t\t\t\t\t\tconst slot = slots[name];\n\t\t\t\t\t\tif (!slot) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (slot.isPaused) {\n\t\t\t\t\t\t\t\tslot.resume();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\t\t\tslots[key].resume();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tstop(name) {\n\t\t\t\tconst slots = this._slots;\n\t\t\t\tif (name) {\n\t\t\t\t\t\tconst slot = slots[name];\n\t\t\t\t\t\tif (!slot) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tslot.stop();\n\t\t\t\t} else {\n\t\t\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\t\t\tslots[key].stop();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._volume = 1, this._pitch = 1, this._positional = true, this._refDistance = 1, this._maxDistance = 10000, this._rollOffFactor = 1, this._distanceModel = DISTANCE_LINEAR, this._slots = {}, this._playingBeforeDisable = {};\n\t\t}\n}\n\nclass SoundComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$4 = [\n\t\t'enabled'\n];\nclass SoundComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'sound';\n\t\t\t\tthis.ComponentType = SoundComponent;\n\t\t\t\tthis.DataType = SoundComponentData;\n\t\t\t\tthis.schema = _schema$4;\n\t\t\t\tthis.manager = app.soundManager;\n\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t}\n\t\tset volume(volume) {\n\t\t\t\tthis.manager.volume = volume;\n\t\t}\n\t\tget volume() {\n\t\t\t\treturn this.manager.volume;\n\t\t}\n\t\tget context() {\n\t\t\t\tif (!hasAudioContext()) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this.manager.context;\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tproperties = [\n\t\t\t\t\t\t'volume',\n\t\t\t\t\t\t'pitch',\n\t\t\t\t\t\t'positional',\n\t\t\t\t\t\t'refDistance',\n\t\t\t\t\t\t'maxDistance',\n\t\t\t\t\t\t'rollOffFactor',\n\t\t\t\t\t\t'distanceModel',\n\t\t\t\t\t\t'slots'\n\t\t\t\t];\n\t\t\t\tfor(let i = 0; i < properties.length; i++){\n\t\t\t\t\t\tif (data.hasOwnProperty(properties[i])) {\n\t\t\t\t\t\t\t\tcomponent[properties[i]] = data[properties[i]];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, data, [\n\t\t\t\t\t\t'enabled'\n\t\t\t\t]);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst srcComponent = entity.sound;\n\t\t\t\tconst srcSlots = srcComponent.slots;\n\t\t\t\tconst slots = {};\n\t\t\t\tfor(const key in srcSlots){\n\t\t\t\t\t\tconst srcSlot = srcSlots[key];\n\t\t\t\t\t\tslots[key] = {\n\t\t\t\t\t\t\t\tname: srcSlot.name,\n\t\t\t\t\t\t\t\tvolume: srcSlot.volume,\n\t\t\t\t\t\t\t\tpitch: srcSlot.pitch,\n\t\t\t\t\t\t\t\tloop: srcSlot.loop,\n\t\t\t\t\t\t\t\tduration: srcSlot.duration,\n\t\t\t\t\t\t\t\tstartTime: srcSlot.startTime,\n\t\t\t\t\t\t\t\toverlap: srcSlot.overlap,\n\t\t\t\t\t\t\t\tautoPlay: srcSlot.autoPlay,\n\t\t\t\t\t\t\t\tasset: srcSlot.asset\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst cloneData = {\n\t\t\t\t\t\tdistanceModel: srcComponent.distanceModel,\n\t\t\t\t\t\tenabled: srcComponent.enabled,\n\t\t\t\t\t\tmaxDistance: srcComponent.maxDistance,\n\t\t\t\t\t\tpitch: srcComponent.pitch,\n\t\t\t\t\t\tpositional: srcComponent.positional,\n\t\t\t\t\t\trefDistance: srcComponent.refDistance,\n\t\t\t\t\t\trollOffFactor: srcComponent.rollOffFactor,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\tvolume: srcComponent.volume\n\t\t\t\t};\n\t\t\t\treturn this.addComponent(clone, cloneData);\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tconst store = this.store;\n\t\t\t\tfor(const id in store){\n\t\t\t\t\t\tif (store.hasOwnProperty(id)) {\n\t\t\t\t\t\t\t\tconst item = store[id];\n\t\t\t\t\t\t\t\tconst entity = item.entity;\n\t\t\t\t\t\t\t\tif (entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tconst component = entity.sound;\n\t\t\t\t\t\t\t\t\t\tif (component.enabled && component.positional) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst position = entity.getPosition();\n\t\t\t\t\t\t\t\t\t\t\t\tconst slots = component.slots;\n\t\t\t\t\t\t\t\t\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tslots[key].updatePosition(position);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tconst slots = component.slots;\n\t\t\t\tfor(const key in slots){\n\t\t\t\t\t\tif (!slots[key].overlap) {\n\t\t\t\t\t\t\t\tslots[key].stop();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t}\n}\nComponent._buildAccessors(SoundComponent.prototype, _schema$4);\n\nconst SPRITETYPE_SIMPLE = 'simple';\nconst SPRITETYPE_ANIMATED = 'animated';\n\nclass SpriteAnimationClip extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_PLAY = 'play';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PAUSE = 'pause';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESUME = 'resume';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STOP = 'stop';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_END = 'end';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_LOOP = 'loop';\n\t\t}\n\t\tconstructor(component, data){\n\t\t\t\tsuper(), this._evtSetMeshes = null;\n\t\t\t\tthis._component = component;\n\t\t\t\tthis._frame = 0;\n\t\t\t\tthis._sprite = null;\n\t\t\t\tthis._spriteAsset = null;\n\t\t\t\tthis.spriteAsset = data.spriteAsset;\n\t\t\t\tthis.name = data.name;\n\t\t\t\tthis.fps = data.fps || 0;\n\t\t\t\tthis.loop = data.loop || false;\n\t\t\t\tthis._playing = false;\n\t\t\t\tthis._paused = false;\n\t\t\t\tthis._time = 0;\n\t\t}\n\t\tget duration() {\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tconst fps = this.fps || Number.MIN_VALUE;\n\t\t\t\t\t\treturn this._sprite.frameKeys.length / Math.abs(fps);\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t}\n\t\tset frame(value) {\n\t\t\t\tthis._setFrame(value);\n\t\t\t\tconst fps = this.fps || Number.MIN_VALUE;\n\t\t\t\tthis._setTime(this._frame / fps);\n\t\t}\n\t\tget frame() {\n\t\t\t\treturn this._frame;\n\t\t}\n\t\tget isPaused() {\n\t\t\t\treturn this._paused;\n\t\t}\n\t\tget isPlaying() {\n\t\t\t\treturn this._playing;\n\t\t}\n\t\tset sprite(value) {\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._evtSetMeshes?.off();\n\t\t\t\t\t\tthis._evtSetMeshes = null;\n\t\t\t\t\t\tthis._sprite.off('set:pixelsPerUnit', this._onSpritePpuChanged, this);\n\t\t\t\t\t\tthis._sprite.off('set:atlas', this._onSpriteMeshesChange, this);\n\t\t\t\t\t\tif (this._sprite.atlas) {\n\t\t\t\t\t\t\t\tthis._sprite.atlas.off('set:texture', this._onSpriteMeshesChange, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._sprite = value;\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._evtSetMeshes = this._sprite.on('set:meshes', this._onSpriteMeshesChange, this);\n\t\t\t\t\t\tthis._sprite.on('set:pixelsPerUnit', this._onSpritePpuChanged, this);\n\t\t\t\t\t\tthis._sprite.on('set:atlas', this._onSpriteMeshesChange, this);\n\t\t\t\t\t\tif (this._sprite.atlas) {\n\t\t\t\t\t\t\t\tthis._sprite.atlas.on('set:texture', this._onSpriteMeshesChange, this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._component.currentClip === this) {\n\t\t\t\t\t\tlet mi;\n\t\t\t\t\t\tif (!value || !value.atlas) {\n\t\t\t\t\t\t\t\tmi = this._component._meshInstance;\n\t\t\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\t\t\tmi.deleteParameter('texture_emissiveMap');\n\t\t\t\t\t\t\t\t\t\tmi.deleteParameter('texture_opacityMap');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._component._hideModel();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (value.atlas.texture) {\n\t\t\t\t\t\t\t\t\t\tmi = this._component._meshInstance;\n\t\t\t\t\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\t\t\t\t\tmi.setParameter('texture_emissiveMap', value.atlas.texture);\n\t\t\t\t\t\t\t\t\t\t\t\tmi.setParameter('texture_opacityMap', value.atlas.texture);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (this._component.enabled && this._component.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._component._showModel();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (this.time && this.fps) {\n\t\t\t\t\t\t\t\t\t\tthis.time = this.time;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.frame = this.frame;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget sprite() {\n\t\t\t\treturn this._sprite;\n\t\t}\n\t\tset spriteAsset(value) {\n\t\t\t\tconst assets = this._component.system.app.assets;\n\t\t\t\tlet id = value;\n\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\tid = value.id;\n\t\t\t\t}\n\t\t\t\tif (this._spriteAsset !== id) {\n\t\t\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\t\t\tconst prev = assets.get(this._spriteAsset);\n\t\t\t\t\t\t\t\tif (prev) {\n\t\t\t\t\t\t\t\t\t\tthis._unbindSpriteAsset(prev);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._spriteAsset = id;\n\t\t\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\t\t\tconst asset = assets.get(this._spriteAsset);\n\t\t\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\t\t\tthis.sprite = null;\n\t\t\t\t\t\t\t\t\t\tassets.on(`add:${this._spriteAsset}`, this._onSpriteAssetAdded, this);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._bindSpriteAsset(asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.sprite = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget spriteAsset() {\n\t\t\t\treturn this._spriteAsset;\n\t\t}\n\t\tset time(value) {\n\t\t\t\tthis._setTime(value);\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis.frame = Math.min(this._sprite.frameKeys.length - 1, Math.floor(this._time * Math.abs(this.fps)));\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.frame = 0;\n\t\t\t\t}\n\t\t}\n\t\tget time() {\n\t\t\t\treturn this._time;\n\t\t}\n\t\t_onSpriteAssetAdded(asset) {\n\t\t\t\tthis._component.system.app.assets.off(`add:${asset.id}`, this._onSpriteAssetAdded, this);\n\t\t\t\tif (this._spriteAsset === asset.id) {\n\t\t\t\t\t\tthis._bindSpriteAsset(asset);\n\t\t\t\t}\n\t\t}\n\t\t_bindSpriteAsset(asset) {\n\t\t\t\tasset.on('load', this._onSpriteAssetLoad, this);\n\t\t\t\tasset.on('remove', this._onSpriteAssetRemove, this);\n\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\tthis._onSpriteAssetLoad(asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._component.system.app.assets.load(asset);\n\t\t\t\t}\n\t\t}\n\t\t_unbindSpriteAsset(asset) {\n\t\t\t\tif (!asset) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tasset.off('load', this._onSpriteAssetLoad, this);\n\t\t\t\tasset.off('remove', this._onSpriteAssetRemove, this);\n\t\t\t\tif (asset.resource && !asset.resource.atlas) {\n\t\t\t\t\t\tthis._component.system.app.assets.off(`load:${asset.data.textureAtlasAsset}`, this._onTextureAtlasLoad, this);\n\t\t\t\t}\n\t\t}\n\t\t_onSpriteAssetLoad(asset) {\n\t\t\t\tif (!asset.resource) {\n\t\t\t\t\t\tthis.sprite = null;\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!asset.resource.atlas) {\n\t\t\t\t\t\t\t\tconst atlasAssetId = asset.data.textureAtlasAsset;\n\t\t\t\t\t\t\t\tconst assets = this._component.system.app.assets;\n\t\t\t\t\t\t\t\tassets.off(`load:${atlasAssetId}`, this._onTextureAtlasLoad, this);\n\t\t\t\t\t\t\t\tassets.once(`load:${atlasAssetId}`, this._onTextureAtlasLoad, this);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.sprite = asset.resource;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onTextureAtlasLoad(atlasAsset) {\n\t\t\t\tconst spriteAsset = this._spriteAsset;\n\t\t\t\tif (spriteAsset instanceof Asset) {\n\t\t\t\t\t\tthis._onSpriteAssetLoad(spriteAsset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._onSpriteAssetLoad(this._component.system.app.assets.get(spriteAsset));\n\t\t\t\t}\n\t\t}\n\t\t_onSpriteAssetRemove(asset) {\n\t\t\t\tthis.sprite = null;\n\t\t}\n\t\t_onSpriteMeshesChange() {\n\t\t\t\tif (this._component.currentClip === this) {\n\t\t\t\t\t\tthis._component._showFrame(this.frame);\n\t\t\t\t}\n\t\t}\n\t\t_onSpritePpuChanged() {\n\t\t\t\tif (this._component.currentClip === this) {\n\t\t\t\t\t\tif (this.sprite.renderMode !== SPRITE_RENDERMODE_SIMPLE) {\n\t\t\t\t\t\t\t\tthis._component._showFrame(this.frame);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_update(dt) {\n\t\t\t\tif (this.fps === 0) return;\n\t\t\t\tif (!this._playing || this._paused || !this._sprite) return;\n\t\t\t\tconst dir = this.fps < 0 ? -1 : 1;\n\t\t\t\tconst time = this._time + dt * this._component.speed * dir;\n\t\t\t\tconst duration = this.duration;\n\t\t\t\tconst end = time > duration || time < 0;\n\t\t\t\tthis._setTime(time);\n\t\t\t\tlet frame = this.frame;\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tframe = Math.floor(this._sprite.frameKeys.length * this._time / duration);\n\t\t\t\t} else {\n\t\t\t\t\t\tframe = 0;\n\t\t\t\t}\n\t\t\t\tif (frame !== this._frame) {\n\t\t\t\t\t\tthis._setFrame(frame);\n\t\t\t\t}\n\t\t\t\tif (end) {\n\t\t\t\t\t\tif (this.loop) {\n\t\t\t\t\t\t\t\tthis.fire('loop');\n\t\t\t\t\t\t\t\tthis._component.fire('loop', this);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._playing = false;\n\t\t\t\t\t\t\t\tthis._paused = false;\n\t\t\t\t\t\t\t\tthis.fire('end');\n\t\t\t\t\t\t\t\tthis._component.fire('end', this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_setTime(value) {\n\t\t\t\tthis._time = value;\n\t\t\t\tconst duration = this.duration;\n\t\t\t\tif (this._time < 0) {\n\t\t\t\t\t\tif (this.loop) {\n\t\t\t\t\t\t\t\tthis._time = this._time % duration + duration;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._time = 0;\n\t\t\t\t\t\t}\n\t\t\t\t} else if (this._time > duration) {\n\t\t\t\t\t\tif (this.loop) {\n\t\t\t\t\t\t\t\tthis._time %= duration;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._time = duration;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_setFrame(value) {\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis._frame = math.clamp(value, 0, this._sprite.frameKeys.length - 1);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._frame = value;\n\t\t\t\t}\n\t\t\t\tif (this._component.currentClip === this) {\n\t\t\t\t\t\tthis._component._showFrame(this._frame);\n\t\t\t\t}\n\t\t}\n\t\t_destroy() {\n\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\tconst assets = this._component.system.app.assets;\n\t\t\t\t\t\tthis._unbindSpriteAsset(assets.get(this._spriteAsset));\n\t\t\t\t}\n\t\t\t\tif (this._sprite) {\n\t\t\t\t\t\tthis.sprite = null;\n\t\t\t\t}\n\t\t\t\tif (this._spriteAsset) {\n\t\t\t\t\t\tthis.spriteAsset = null;\n\t\t\t\t}\n\t\t}\n\t\tplay() {\n\t\t\t\tif (this._playing) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._playing = true;\n\t\t\t\tthis._paused = false;\n\t\t\t\tthis.frame = 0;\n\t\t\t\tthis.fire('play');\n\t\t\t\tthis._component.fire('play', this);\n\t\t}\n\t\tpause() {\n\t\t\t\tif (!this._playing || this._paused) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._paused = true;\n\t\t\t\tthis.fire('pause');\n\t\t\t\tthis._component.fire('pause', this);\n\t\t}\n\t\tresume() {\n\t\t\t\tif (!this._paused) return;\n\t\t\t\tthis._paused = false;\n\t\t\t\tthis.fire('resume');\n\t\t\t\tthis._component.fire('resume', this);\n\t\t}\n\t\tstop() {\n\t\t\t\tif (!this._playing) return;\n\t\t\t\tthis._playing = false;\n\t\t\t\tthis._paused = false;\n\t\t\t\tthis._time = 0;\n\t\t\t\tthis.frame = 0;\n\t\t\t\tthis.fire('stop');\n\t\t\t\tthis._component.fire('stop', this);\n\t\t}\n}\n\nconst PARAM_EMISSIVE_MAP = 'texture_emissiveMap';\nconst PARAM_OPACITY_MAP = 'texture_opacityMap';\nconst PARAM_EMISSIVE = 'material_emissive';\nconst PARAM_OPACITY = 'material_opacity';\nconst PARAM_INNER_OFFSET = 'innerOffset';\nconst PARAM_OUTER_SCALE = 'outerScale';\nconst PARAM_ATLAS_RECT = 'atlasRect';\nclass SpriteComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_PLAY = 'play';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PAUSE = 'pause';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESUME = 'resume';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STOP = 'stop';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_END = 'end';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_LOOP = 'loop';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null;\n\t\t\t\tthis._type = SPRITETYPE_SIMPLE;\n\t\t\t\tthis._material = system.defaultMaterial;\n\t\t\t\tthis._color = new Color(1, 1, 1, 1);\n\t\t\t\tthis._colorUniform = new Float32Array(3);\n\t\t\t\tthis._speed = 1;\n\t\t\t\tthis._flipX = false;\n\t\t\t\tthis._flipY = false;\n\t\t\t\tthis._width = 1;\n\t\t\t\tthis._height = 1;\n\t\t\t\tthis._drawOrder = 0;\n\t\t\t\tthis._layers = [\n\t\t\t\t\t\tLAYERID_WORLD\n\t\t\t\t];\n\t\t\t\tthis._outerScale = new Vec2(1, 1);\n\t\t\t\tthis._outerScaleUniform = new Float32Array(2);\n\t\t\t\tthis._innerOffset = new Vec4();\n\t\t\t\tthis._innerOffsetUniform = new Float32Array(4);\n\t\t\t\tthis._atlasRect = new Vec4();\n\t\t\t\tthis._atlasRectUniform = new Float32Array(4);\n\t\t\t\tthis._batchGroupId = -1;\n\t\t\t\tthis._batchGroup = null;\n\t\t\t\tthis._node = new GraphNode();\n\t\t\t\tthis._model = new Model();\n\t\t\t\tthis._model.graph = this._node;\n\t\t\t\tthis._meshInstance = null;\n\t\t\t\tentity.addChild(this._model.graph);\n\t\t\t\tthis._model._entity = entity;\n\t\t\t\tthis._updateAabbFunc = this._updateAabb.bind(this);\n\t\t\t\tthis._addedModel = false;\n\t\t\t\tthis._autoPlayClip = null;\n\t\t\t\tthis._clips = {};\n\t\t\t\tthis._defaultClip = new SpriteAnimationClip(this, {\n\t\t\t\t\t\tname: this.entity.name,\n\t\t\t\t\t\tfps: 0,\n\t\t\t\t\t\tloop: false,\n\t\t\t\t\t\tspriteAsset: null\n\t\t\t\t});\n\t\t\t\tthis._currentClip = this._defaultClip;\n\t\t}\n\t\tset type(value) {\n\t\t\t\tif (this._type === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._type = value;\n\t\t\t\tif (this._type === SPRITETYPE_SIMPLE) {\n\t\t\t\t\t\tthis.stop();\n\t\t\t\t\t\tthis._currentClip = this._defaultClip;\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis._currentClip.frame = this.frame;\n\t\t\t\t\t\t\t\tif (this._currentClip.sprite) {\n\t\t\t\t\t\t\t\t\t\tthis._showModel();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._hideModel();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else if (this._type === SPRITETYPE_ANIMATED) {\n\t\t\t\t\t\tthis.stop();\n\t\t\t\t\t\tif (this._autoPlayClip) {\n\t\t\t\t\t\t\t\tthis._tryAutoPlay();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this._currentClip && this._currentClip.isPlaying && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis._showModel();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._hideModel();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tset frame(value) {\n\t\t\t\tthis._currentClip.frame = value;\n\t\t}\n\t\tget frame() {\n\t\t\t\treturn this._currentClip.frame;\n\t\t}\n\t\tset spriteAsset(value) {\n\t\t\t\tthis._defaultClip.spriteAsset = value;\n\t\t}\n\t\tget spriteAsset() {\n\t\t\t\treturn this._defaultClip._spriteAsset;\n\t\t}\n\t\tset sprite(value) {\n\t\t\t\tthis._currentClip.sprite = value;\n\t\t}\n\t\tget sprite() {\n\t\t\t\treturn this._currentClip.sprite;\n\t\t}\n\t\tset material(value) {\n\t\t\t\tthis._material = value;\n\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\tthis._meshInstance.material = value;\n\t\t\t\t}\n\t\t}\n\t\tget material() {\n\t\t\t\treturn this._material;\n\t\t}\n\t\tset color(value) {\n\t\t\t\tthis._color.r = value.r;\n\t\t\t\tthis._color.g = value.g;\n\t\t\t\tthis._color.b = value.b;\n\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\tthis._colorUniform[0] = this._color.r;\n\t\t\t\t\t\tthis._colorUniform[1] = this._color.g;\n\t\t\t\t\t\tthis._colorUniform[2] = this._color.b;\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_EMISSIVE, this._colorUniform);\n\t\t\t\t}\n\t\t}\n\t\tget color() {\n\t\t\t\treturn this._color;\n\t\t}\n\t\tset opacity(value) {\n\t\t\t\tthis._color.a = value;\n\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_OPACITY, value);\n\t\t\t\t}\n\t\t}\n\t\tget opacity() {\n\t\t\t\treturn this._color.a;\n\t\t}\n\t\tset clips(value) {\n\t\t\t\tif (!value) {\n\t\t\t\t\t\tfor(const name in this._clips){\n\t\t\t\t\t\t\t\tthis.removeClip(name);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor(const name in this._clips){\n\t\t\t\t\t\tlet found = false;\n\t\t\t\t\t\tfor(const key in value){\n\t\t\t\t\t\t\t\tif (value[key].name === name) {\n\t\t\t\t\t\t\t\t\t\tfound = true;\n\t\t\t\t\t\t\t\t\t\tthis._clips[name].fps = value[key].fps;\n\t\t\t\t\t\t\t\t\t\tthis._clips[name].loop = value[key].loop;\n\t\t\t\t\t\t\t\t\t\tif (value[key].hasOwnProperty('sprite')) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._clips[name].sprite = value[key].sprite;\n\t\t\t\t\t\t\t\t\t\t} else if (value[key].hasOwnProperty('spriteAsset')) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._clips[name].spriteAsset = value[key].spriteAsset;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!found) {\n\t\t\t\t\t\t\t\tthis.removeClip(name);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(const key in value){\n\t\t\t\t\t\tif (this._clips[value[key].name]) continue;\n\t\t\t\t\t\tthis.addClip(value[key]);\n\t\t\t\t}\n\t\t\t\tif (this._autoPlayClip) {\n\t\t\t\t\t\tthis._tryAutoPlay();\n\t\t\t\t}\n\t\t\t\tif (!this._currentClip || !this._currentClip.sprite) {\n\t\t\t\t\t\tthis._hideModel();\n\t\t\t\t}\n\t\t}\n\t\tget clips() {\n\t\t\t\treturn this._clips;\n\t\t}\n\t\tget currentClip() {\n\t\t\t\treturn this._currentClip;\n\t\t}\n\t\tset speed(value) {\n\t\t\t\tthis._speed = value;\n\t\t}\n\t\tget speed() {\n\t\t\t\treturn this._speed;\n\t\t}\n\t\tset flipX(value) {\n\t\t\t\tif (this._flipX === value) return;\n\t\t\t\tthis._flipX = value;\n\t\t\t\tthis._updateTransform();\n\t\t}\n\t\tget flipX() {\n\t\t\t\treturn this._flipX;\n\t\t}\n\t\tset flipY(value) {\n\t\t\t\tif (this._flipY === value) return;\n\t\t\t\tthis._flipY = value;\n\t\t\t\tthis._updateTransform();\n\t\t}\n\t\tget flipY() {\n\t\t\t\treturn this._flipY;\n\t\t}\n\t\tset width(value) {\n\t\t\t\tif (value === this._width) return;\n\t\t\t\tthis._width = value;\n\t\t\t\tthis._outerScale.x = this._width;\n\t\t\t\tif (this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_TILED || this.sprite.renderMode === SPRITE_RENDERMODE_SLICED)) {\n\t\t\t\t\t\tthis._updateTransform();\n\t\t\t\t}\n\t\t}\n\t\tget width() {\n\t\t\t\treturn this._width;\n\t\t}\n\t\tset height(value) {\n\t\t\t\tif (value === this._height) return;\n\t\t\t\tthis._height = value;\n\t\t\t\tthis._outerScale.y = this.height;\n\t\t\t\tif (this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_TILED || this.sprite.renderMode === SPRITE_RENDERMODE_SLICED)) {\n\t\t\t\t\t\tthis._updateTransform();\n\t\t\t\t}\n\t\t}\n\t\tget height() {\n\t\t\t\treturn this._height;\n\t\t}\n\t\tset batchGroupId(value) {\n\t\t\t\tif (this._batchGroupId === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst prev = this._batchGroupId;\n\t\t\t\tthis._batchGroupId = value;\n\t\t\t\tif (this.entity.enabled && prev >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.remove(BatchGroup.SPRITE, prev, this.entity);\n\t\t\t\t}\n\t\t\t\tif (this.entity.enabled && value >= 0) {\n\t\t\t\t\t\tthis.system.app.batcher?.insert(BatchGroup.SPRITE, value, this.entity);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (prev >= 0) {\n\t\t\t\t\t\t\t\tif (this._currentClip && this._currentClip.sprite && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tthis._showModel();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget batchGroupId() {\n\t\t\t\treturn this._batchGroupId;\n\t\t}\n\t\tset autoPlayClip(value) {\n\t\t\t\tthis._autoPlayClip = value instanceof SpriteAnimationClip ? value.name : value;\n\t\t\t\tthis._tryAutoPlay();\n\t\t}\n\t\tget autoPlayClip() {\n\t\t\t\treturn this._autoPlayClip;\n\t\t}\n\t\tset drawOrder(value) {\n\t\t\t\tthis._drawOrder = value;\n\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\tthis._meshInstance.drawOrder = value;\n\t\t\t\t}\n\t\t}\n\t\tget drawOrder() {\n\t\t\t\treturn this._drawOrder;\n\t\t}\n\t\tset layers(value) {\n\t\t\t\tif (this._addedModel) {\n\t\t\t\t\t\tthis._hideModel();\n\t\t\t\t}\n\t\t\t\tthis._layers = value;\n\t\t\t\tif (!this._meshInstance) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis._showModel();\n\t\t\t\t}\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tget aabb() {\n\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\treturn this._meshInstance.aabb;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst app = this.system.app;\n\t\t\t\tconst scene = app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this._onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this._onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this._onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tthis._showModel();\n\t\t\t\tif (this._autoPlayClip) {\n\t\t\t\t\t\tthis._tryAutoPlay();\n\t\t\t\t}\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tapp.batcher?.insert(BatchGroup.SPRITE, this._batchGroupId, this.entity);\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst app = this.system.app;\n\t\t\t\tconst scene = app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tthis.stop();\n\t\t\t\tthis._hideModel();\n\t\t\t\tif (this._batchGroupId >= 0) {\n\t\t\t\t\t\tapp.batcher?.remove(BatchGroup.SPRITE, this._batchGroupId, this.entity);\n\t\t\t\t}\n\t\t}\n\t\tonDestroy() {\n\t\t\t\tthis._currentClip = null;\n\t\t\t\tif (this._defaultClip) {\n\t\t\t\t\t\tthis._defaultClip._destroy();\n\t\t\t\t\t\tthis._defaultClip = null;\n\t\t\t\t}\n\t\t\t\tfor(const key in this._clips){\n\t\t\t\t\t\tthis._clips[key]._destroy();\n\t\t\t\t}\n\t\t\t\tthis._clips = null;\n\t\t\t\tthis._hideModel();\n\t\t\t\tthis._model = null;\n\t\t\t\tthis._node?.remove();\n\t\t\t\tthis._node = null;\n\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\tthis._meshInstance.material = null;\n\t\t\t\t\t\tthis._meshInstance.mesh = null;\n\t\t\t\t\t\tthis._meshInstance = null;\n\t\t\t\t}\n\t\t}\n\t\t_showModel() {\n\t\t\t\tif (this._addedModel) return;\n\t\t\t\tif (!this._meshInstance) return;\n\t\t\t\tconst meshInstances = [\n\t\t\t\t\t\tthis._meshInstance\n\t\t\t\t];\n\t\t\t\tfor(let i = 0, len = this._layers.length; i < len; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.addMeshInstances(meshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._addedModel = true;\n\t\t}\n\t\t_hideModel() {\n\t\t\t\tif (!this._addedModel || !this._meshInstance) return;\n\t\t\t\tconst meshInstances = [\n\t\t\t\t\t\tthis._meshInstance\n\t\t\t\t];\n\t\t\t\tfor(let i = 0, len = this._layers.length; i < len; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this._layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.removeMeshInstances(meshInstances);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._addedModel = false;\n\t\t}\n\t\t_showFrame(frame) {\n\t\t\t\tif (!this.sprite) return;\n\t\t\t\tconst mesh = this.sprite.meshes[frame];\n\t\t\t\tif (!mesh) {\n\t\t\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\t\t\tthis._meshInstance.mesh = null;\n\t\t\t\t\t\t\t\tthis._meshInstance.visible = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet material;\n\t\t\t\tif (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED) {\n\t\t\t\t\t\tmaterial = this.system.default9SlicedMaterialSlicedMode;\n\t\t\t\t} else if (this.sprite.renderMode === SPRITE_RENDERMODE_TILED) {\n\t\t\t\t\t\tmaterial = this.system.default9SlicedMaterialTiledMode;\n\t\t\t\t} else {\n\t\t\t\t\t\tmaterial = this.system.defaultMaterial;\n\t\t\t\t}\n\t\t\t\tif (!this._meshInstance) {\n\t\t\t\t\t\tthis._meshInstance = new MeshInstance(mesh, this._material, this._node);\n\t\t\t\t\t\tthis._meshInstance.castShadow = false;\n\t\t\t\t\t\tthis._meshInstance.receiveShadow = false;\n\t\t\t\t\t\tthis._meshInstance.drawOrder = this._drawOrder;\n\t\t\t\t\t\tthis._model.meshInstances.push(this._meshInstance);\n\t\t\t\t\t\tthis._colorUniform[0] = this._color.r;\n\t\t\t\t\t\tthis._colorUniform[1] = this._color.g;\n\t\t\t\t\t\tthis._colorUniform[2] = this._color.b;\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_EMISSIVE, this._colorUniform);\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_OPACITY, this._color.a);\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis._showModel();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._meshInstance.material !== material) {\n\t\t\t\t\t\tthis._meshInstance.material = material;\n\t\t\t\t}\n\t\t\t\tif (this._meshInstance.mesh !== mesh) {\n\t\t\t\t\t\tthis._meshInstance.mesh = mesh;\n\t\t\t\t\t\tthis._meshInstance.visible = true;\n\t\t\t\t\t\tthis._meshInstance._aabbVer = -1;\n\t\t\t\t}\n\t\t\t\tif (this.sprite.atlas && this.sprite.atlas.texture) {\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_EMISSIVE_MAP, this.sprite.atlas.texture);\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_OPACITY_MAP, this.sprite.atlas.texture);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._meshInstance.deleteParameter(PARAM_EMISSIVE_MAP);\n\t\t\t\t\t\tthis._meshInstance.deleteParameter(PARAM_OPACITY_MAP);\n\t\t\t\t}\n\t\t\t\tif (this.sprite.atlas && (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED || this.sprite.renderMode === SPRITE_RENDERMODE_TILED)) {\n\t\t\t\t\t\tthis._meshInstance._updateAabbFunc = this._updateAabbFunc;\n\t\t\t\t\t\tconst frameData = this.sprite.atlas.frames[this.sprite.frameKeys[frame]];\n\t\t\t\t\t\tif (frameData) {\n\t\t\t\t\t\t\t\tconst borderWidthScale = 2 / frameData.rect.z;\n\t\t\t\t\t\t\t\tconst borderHeightScale = 2 / frameData.rect.w;\n\t\t\t\t\t\t\t\tthis._innerOffset.set(frameData.border.x * borderWidthScale, frameData.border.y * borderHeightScale, frameData.border.z * borderWidthScale, frameData.border.w * borderHeightScale);\n\t\t\t\t\t\t\t\tconst tex = this.sprite.atlas.texture;\n\t\t\t\t\t\t\t\tthis._atlasRect.set(frameData.rect.x / tex.width, frameData.rect.y / tex.height, frameData.rect.z / tex.width, frameData.rect.w / tex.height);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._innerOffset.set(0, 0, 0, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._innerOffsetUniform[0] = this._innerOffset.x;\n\t\t\t\t\t\tthis._innerOffsetUniform[1] = this._innerOffset.y;\n\t\t\t\t\t\tthis._innerOffsetUniform[2] = this._innerOffset.z;\n\t\t\t\t\t\tthis._innerOffsetUniform[3] = this._innerOffset.w;\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_INNER_OFFSET, this._innerOffsetUniform);\n\t\t\t\t\t\tthis._atlasRectUniform[0] = this._atlasRect.x;\n\t\t\t\t\t\tthis._atlasRectUniform[1] = this._atlasRect.y;\n\t\t\t\t\t\tthis._atlasRectUniform[2] = this._atlasRect.z;\n\t\t\t\t\t\tthis._atlasRectUniform[3] = this._atlasRect.w;\n\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_ATLAS_RECT, this._atlasRectUniform);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._meshInstance._updateAabbFunc = null;\n\t\t\t\t}\n\t\t\t\tthis._updateTransform();\n\t\t}\n\t\t_updateTransform() {\n\t\t\t\tlet scaleX = this.flipX ? -1 : 1;\n\t\t\t\tlet scaleY = this.flipY ? -1 : 1;\n\t\t\t\tlet posX = 0;\n\t\t\t\tlet posY = 0;\n\t\t\t\tif (this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED || this.sprite.renderMode === SPRITE_RENDERMODE_TILED)) {\n\t\t\t\t\t\tlet w = 1;\n\t\t\t\t\t\tlet h = 1;\n\t\t\t\t\t\tif (this.sprite.atlas) {\n\t\t\t\t\t\t\t\tconst frameData = this.sprite.atlas.frames[this.sprite.frameKeys[this.frame]];\n\t\t\t\t\t\t\t\tif (frameData) {\n\t\t\t\t\t\t\t\t\t\tw = frameData.rect.z;\n\t\t\t\t\t\t\t\t\t\th = frameData.rect.w;\n\t\t\t\t\t\t\t\t\t\tposX = (0.5 - frameData.pivot.x) * this._width;\n\t\t\t\t\t\t\t\t\t\tposY = (0.5 - frameData.pivot.y) * this._height;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst scaleMulX = w / this.sprite.pixelsPerUnit;\n\t\t\t\t\t\tconst scaleMulY = h / this.sprite.pixelsPerUnit;\n\t\t\t\t\t\tthis._outerScale.set(Math.max(this._width, this._innerOffset.x * scaleMulX), Math.max(this._height, this._innerOffset.y * scaleMulY));\n\t\t\t\t\t\tscaleX *= scaleMulX;\n\t\t\t\t\t\tscaleY *= scaleMulY;\n\t\t\t\t\t\tthis._outerScale.x /= scaleMulX;\n\t\t\t\t\t\tthis._outerScale.y /= scaleMulY;\n\t\t\t\t\t\tscaleX *= math.clamp(this._width / (this._innerOffset.x * scaleMulX), 0.0001, 1);\n\t\t\t\t\t\tscaleY *= math.clamp(this._height / (this._innerOffset.y * scaleMulY), 0.0001, 1);\n\t\t\t\t\t\tif (this._meshInstance) {\n\t\t\t\t\t\t\t\tthis._outerScaleUniform[0] = this._outerScale.x;\n\t\t\t\t\t\t\t\tthis._outerScaleUniform[1] = this._outerScale.y;\n\t\t\t\t\t\t\t\tthis._meshInstance.setParameter(PARAM_OUTER_SCALE, this._outerScaleUniform);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._node.setLocalScale(scaleX, scaleY, 1);\n\t\t\t\tthis._node.setLocalPosition(posX, posY, 0);\n\t\t}\n\t\t_updateAabb(aabb) {\n\t\t\t\taabb.center.set(0, 0, 0);\n\t\t\t\taabb.halfExtents.set(this._outerScale.x * 0.5, this._outerScale.y * 0.5, 0.001);\n\t\t\t\taabb.setFromTransformedAabb(aabb, this._node.getWorldTransform());\n\t\t\t\treturn aabb;\n\t\t}\n\t\t_tryAutoPlay() {\n\t\t\t\tif (!this._autoPlayClip) return;\n\t\t\t\tif (this.type !== SPRITETYPE_ANIMATED) return;\n\t\t\t\tconst clip = this._clips[this._autoPlayClip];\n\t\t\t\tif (clip && !clip.isPlaying && (!this._currentClip || !this._currentClip.isPlaying)) {\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.play(clip.name);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onLayersChanged(oldComp, newComp) {\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis._showModel();\n\t\t\t\t}\n\t\t}\n\t\t_onLayerAdded(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tif (this._addedModel && this.enabled && this.entity.enabled && this._meshInstance) {\n\t\t\t\t\t\tlayer.addMeshInstances([\n\t\t\t\t\t\t\t\tthis._meshInstance\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\t_onLayerRemoved(layer) {\n\t\t\t\tif (!this._meshInstance) return;\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.removeMeshInstances([\n\t\t\t\t\t\tthis._meshInstance\n\t\t\t\t]);\n\t\t}\n\t\tremoveModelFromLayers() {\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\tlayer.removeMeshInstances([\n\t\t\t\t\t\t\t\tthis._meshInstance\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\taddClip(data) {\n\t\t\t\tconst clip = new SpriteAnimationClip(this, {\n\t\t\t\t\t\tname: data.name,\n\t\t\t\t\t\tfps: data.fps,\n\t\t\t\t\t\tloop: data.loop,\n\t\t\t\t\t\tspriteAsset: data.spriteAsset\n\t\t\t\t});\n\t\t\t\tthis._clips[data.name] = clip;\n\t\t\t\tif (clip.name && clip.name === this._autoPlayClip) {\n\t\t\t\t\t\tthis._tryAutoPlay();\n\t\t\t\t}\n\t\t\t\treturn clip;\n\t\t}\n\t\tremoveClip(name) {\n\t\t\t\tdelete this._clips[name];\n\t\t}\n\t\tclip(name) {\n\t\t\t\treturn this._clips[name];\n\t\t}\n\t\tplay(name) {\n\t\t\t\tconst clip = this._clips[name];\n\t\t\t\tconst current = this._currentClip;\n\t\t\t\tif (current && current !== clip) {\n\t\t\t\t\t\tcurrent._playing = false;\n\t\t\t\t}\n\t\t\t\tthis._currentClip = clip;\n\t\t\t\tif (this._currentClip) {\n\t\t\t\t\t\tthis._currentClip = clip;\n\t\t\t\t\t\tthis._currentClip.play();\n\t\t\t\t}\n\t\t\t\treturn clip;\n\t\t}\n\t\tpause() {\n\t\t\t\tif (this._currentClip === this._defaultClip) return;\n\t\t\t\tif (this._currentClip.isPlaying) {\n\t\t\t\t\t\tthis._currentClip.pause();\n\t\t\t\t}\n\t\t}\n\t\tresume() {\n\t\t\t\tif (this._currentClip === this._defaultClip) return;\n\t\t\t\tif (this._currentClip.isPaused) {\n\t\t\t\t\t\tthis._currentClip.resume();\n\t\t\t\t}\n\t\t}\n\t\tstop() {\n\t\t\t\tif (this._currentClip === this._defaultClip) return;\n\t\t\t\tthis._currentClip.stop();\n\t\t}\n}\n\nclass SpriteComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$3 = [\n\t\t'enabled'\n];\nclass SpriteComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'sprite';\n\t\t\t\tthis.ComponentType = SpriteComponent;\n\t\t\t\tthis.DataType = SpriteComponentData;\n\t\t\t\tthis.schema = _schema$3;\n\t\t\t\tthis._defaultTexture = null;\n\t\t\t\tthis._defaultMaterial = null;\n\t\t\t\tthis._default9SlicedMaterialSlicedMode = null;\n\t\t\t\tthis._default9SlicedMaterialTiledMode = null;\n\t\t\t\tthis.app.systems.on('update', this.onUpdate, this);\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t}\n\t\tset defaultMaterial(material) {\n\t\t\t\tthis._defaultMaterial = material;\n\t\t}\n\t\tget defaultMaterial() {\n\t\t\t\tif (!this._defaultMaterial) {\n\t\t\t\t\t\tconst texture = new Texture(this.app.graphicsDevice, {\n\t\t\t\t\t\t\t\twidth: 1,\n\t\t\t\t\t\t\t\theight: 1,\n\t\t\t\t\t\t\t\tformat: PIXELFORMAT_SRGBA8,\n\t\t\t\t\t\t\t\tname: 'sprite'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst pixels = new Uint8Array(texture.lock());\n\t\t\t\t\t\tpixels[0] = pixels[1] = pixels[2] = pixels[3] = 255;\n\t\t\t\t\t\ttexture.unlock();\n\t\t\t\t\t\tconst material = new StandardMaterial();\n\t\t\t\t\t\tmaterial.diffuse.set(0, 0, 0);\n\t\t\t\t\t\tmaterial.emissive.set(1, 1, 1);\n\t\t\t\t\t\tmaterial.emissiveMap = texture;\n\t\t\t\t\t\tmaterial.opacityMap = texture;\n\t\t\t\t\t\tmaterial.opacityMapChannel = 'a';\n\t\t\t\t\t\tmaterial.useLighting = false;\n\t\t\t\t\t\tmaterial.useTonemap = false;\n\t\t\t\t\t\tmaterial.useFog = false;\n\t\t\t\t\t\tmaterial.useSkybox = false;\n\t\t\t\t\t\tmaterial.blendType = BLEND_PREMULTIPLIED;\n\t\t\t\t\t\tmaterial.depthWrite = false;\n\t\t\t\t\t\tmaterial.pixelSnap = false;\n\t\t\t\t\t\tmaterial.cull = CULLFACE_NONE;\n\t\t\t\t\t\tmaterial.update();\n\t\t\t\t\t\tthis._defaultTexture = texture;\n\t\t\t\t\t\tthis._defaultMaterial = material;\n\t\t\t\t}\n\t\t\t\treturn this._defaultMaterial;\n\t\t}\n\t\tset default9SlicedMaterialSlicedMode(material) {\n\t\t\t\tthis._default9SlicedMaterialSlicedMode = material;\n\t\t}\n\t\tget default9SlicedMaterialSlicedMode() {\n\t\t\t\tif (!this._default9SlicedMaterialSlicedMode) {\n\t\t\t\t\t\tconst material = this.defaultMaterial.clone();\n\t\t\t\t\t\tmaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;\n\t\t\t\t\t\tmaterial.update();\n\t\t\t\t\t\tthis._default9SlicedMaterialSlicedMode = material;\n\t\t\t\t}\n\t\t\t\treturn this._default9SlicedMaterialSlicedMode;\n\t\t}\n\t\tset default9SlicedMaterialTiledMode(material) {\n\t\t\t\tthis._default9SlicedMaterialTiledMode = material;\n\t\t}\n\t\tget default9SlicedMaterialTiledMode() {\n\t\t\t\tif (!this._default9SlicedMaterialTiledMode) {\n\t\t\t\t\t\tconst material = this.defaultMaterial.clone();\n\t\t\t\t\t\tmaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;\n\t\t\t\t\t\tmaterial.update();\n\t\t\t\t\t\tthis._default9SlicedMaterialTiledMode = material;\n\t\t\t\t}\n\t\t\t\treturn this._default9SlicedMaterialTiledMode;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('update', this.onUpdate, this);\n\t\t\t\tif (this._defaultTexture) {\n\t\t\t\t\t\tthis._defaultTexture.destroy();\n\t\t\t\t\t\tthis._defaultTexture = null;\n\t\t\t\t}\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tif (data.enabled !== undefined) {\n\t\t\t\t\t\tcomponent.enabled = data.enabled;\n\t\t\t\t}\n\t\t\t\tcomponent.type = data.type;\n\t\t\t\tif (data.layers && Array.isArray(data.layers)) {\n\t\t\t\t\t\tcomponent.layers = data.layers.slice(0);\n\t\t\t\t}\n\t\t\t\tif (data.drawOrder !== undefined) {\n\t\t\t\t\t\tcomponent.drawOrder = data.drawOrder;\n\t\t\t\t}\n\t\t\t\tif (data.color !== undefined) {\n\t\t\t\t\t\tif (data.color instanceof Color) {\n\t\t\t\t\t\t\t\tcomponent.color.set(data.color.r, data.color.g, data.color.b, data.opacity ?? 1);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomponent.color.set(data.color[0], data.color[1], data.color[2], data.opacity ?? 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcomponent.color = component.color;\n\t\t\t\t}\n\t\t\t\tif (data.opacity !== undefined) {\n\t\t\t\t\t\tcomponent.opacity = data.opacity;\n\t\t\t\t}\n\t\t\t\tif (data.flipX !== undefined) {\n\t\t\t\t\t\tcomponent.flipX = data.flipX;\n\t\t\t\t}\n\t\t\t\tif (data.flipY !== undefined) {\n\t\t\t\t\t\tcomponent.flipY = data.flipY;\n\t\t\t\t}\n\t\t\t\tif (data.width !== undefined) {\n\t\t\t\t\t\tcomponent.width = data.width;\n\t\t\t\t}\n\t\t\t\tif (data.height !== undefined) {\n\t\t\t\t\t\tcomponent.height = data.height;\n\t\t\t\t}\n\t\t\t\tif (data.spriteAsset !== undefined) {\n\t\t\t\t\t\tcomponent.spriteAsset = data.spriteAsset;\n\t\t\t\t}\n\t\t\t\tif (data.sprite) {\n\t\t\t\t\t\tcomponent.sprite = data.sprite;\n\t\t\t\t}\n\t\t\t\tif (data.frame !== undefined) {\n\t\t\t\t\t\tcomponent.frame = data.frame;\n\t\t\t\t}\n\t\t\t\tif (data.clips) {\n\t\t\t\t\t\tfor(const name in data.clips){\n\t\t\t\t\t\t\t\tcomponent.addClip(data.clips[name]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (data.speed !== undefined) {\n\t\t\t\t\t\tcomponent.speed = data.speed;\n\t\t\t\t}\n\t\t\t\tif (data.autoPlayClip) {\n\t\t\t\t\t\tcomponent.autoPlayClip = data.autoPlayClip;\n\t\t\t\t}\n\t\t\t\tcomponent.batchGroupId = data.batchGroupId === undefined || data.batchGroupId === null ? -1 : data.batchGroupId;\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst source = entity.sprite;\n\t\t\t\treturn this.addComponent(clone, {\n\t\t\t\t\t\tenabled: source.enabled,\n\t\t\t\t\t\ttype: source.type,\n\t\t\t\t\t\tspriteAsset: source.spriteAsset,\n\t\t\t\t\t\tsprite: source.sprite,\n\t\t\t\t\t\twidth: source.width,\n\t\t\t\t\t\theight: source.height,\n\t\t\t\t\t\tframe: source.frame,\n\t\t\t\t\t\tcolor: source.color.clone(),\n\t\t\t\t\t\topacity: source.opacity,\n\t\t\t\t\t\tflipX: source.flipX,\n\t\t\t\t\t\tflipY: source.flipY,\n\t\t\t\t\t\tspeed: source.speed,\n\t\t\t\t\t\tclips: source.clips,\n\t\t\t\t\t\tautoPlayClip: source.autoPlayClip,\n\t\t\t\t\t\tbatchGroupId: source.batchGroupId,\n\t\t\t\t\t\tdrawOrder: source.drawOrder,\n\t\t\t\t\t\tlayers: source.layers.slice(0)\n\t\t\t\t});\n\t\t}\n\t\tonUpdate(dt) {\n\t\t\t\tconst components = this.store;\n\t\t\t\tfor(const id in components){\n\t\t\t\t\t\tif (components.hasOwnProperty(id)) {\n\t\t\t\t\t\t\t\tconst component = components[id];\n\t\t\t\t\t\t\t\tif (component.data.enabled && component.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tconst sprite = component.entity.sprite;\n\t\t\t\t\t\t\t\t\t\tif (sprite._currentClip) {\n\t\t\t\t\t\t\t\t\t\t\t\tsprite._currentClip._update(dt);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tcomponent.onDestroy();\n\t\t}\n}\nComponent._buildAccessors(SpriteComponent.prototype, _schema$3);\n\nclass ZoneComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_ENABLE = 'enable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DISABLE = 'disable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STATE = 'state';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity);\n\t\t\t\tthis._oldState = true;\n\t\t\t\tthis._size = new Vec3();\n\t\t\t\tthis.on('set_enabled', this._onSetEnabled, this);\n\t\t}\n\t\tset size(data) {\n\t\t\t\tif (data instanceof Vec3) {\n\t\t\t\t\t\tthis._size.copy(data);\n\t\t\t\t} else if (data instanceof Array && data.length >= 3) {\n\t\t\t\t\t\tthis.size.set(data[0], data[1], data[2]);\n\t\t\t\t}\n\t\t}\n\t\tget size() {\n\t\t\t\treturn this._size;\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._checkState();\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._checkState();\n\t\t}\n\t\t_onSetEnabled(prop, old, value) {\n\t\t\t\tthis._checkState();\n\t\t}\n\t\t_checkState() {\n\t\t\t\tconst state = this.enabled && this.entity.enabled;\n\t\t\t\tif (state === this._oldState) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._oldState = state;\n\t\t\t\tthis.fire('enable');\n\t\t\t\tthis.fire('state', this.enabled);\n\t\t}\n\t\t_onBeforeRemove() {\n\t\t\t\tthis.fire('remove');\n\t\t}\n}\n\nclass ZoneComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$2 = [\n\t\t'enabled'\n];\nclass ZoneComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'zone';\n\t\t\t\tthis.ComponentType = ZoneComponent;\n\t\t\t\tthis.DataType = ZoneComponentData;\n\t\t\t\tthis.schema = _schema$2;\n\t\t\t\tthis.on('beforeremove', this._onBeforeRemove, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tcomponent.enabled = data.hasOwnProperty('enabled') ? !!data.enabled : true;\n\t\t\t\tif (data.size) {\n\t\t\t\t\t\tif (data.size instanceof Vec3) {\n\t\t\t\t\t\t\t\tcomponent.size.copy(data.size);\n\t\t\t\t\t\t} else if (data.size instanceof Array && data.size.length >= 3) {\n\t\t\t\t\t\t\t\tcomponent.size.set(data.size[0], data.size[1], data.size[2]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst data = {\n\t\t\t\t\t\tenabled: entity.zone.enabled,\n\t\t\t\t\t\tsize: entity.zone.size\n\t\t\t\t};\n\t\t\t\treturn this.addComponent(clone, data);\n\t\t}\n\t\t_onBeforeRemove(entity, component) {\n\t\t\t\tcomponent._onBeforeRemove();\n\t\t}\n}\nComponent._buildAccessors(ZoneComponent.prototype, _schema$2);\n\nclass PostEffectEntry {\n\t\tconstructor(effect, inputTarget){\n\t\t\t\tthis.effect = effect;\n\t\t\t\tthis.inputTarget = inputTarget;\n\t\t\t\tthis.outputTarget = null;\n\t\t\t\tthis.name = effect.constructor.name;\n\t\t}\n}\nclass PostEffectQueue {\n\t\tconstructor(app, camera){\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.camera = camera;\n\t\t\t\tthis.destinationRenderTarget = null;\n\t\t\t\tthis.effects = [];\n\t\t\t\tthis.enabled = false;\n\t\t\t\tthis.depthTarget = null;\n\t\t\t\tcamera.on('set:rect', this.onCameraRectChanged, this);\n\t\t}\n\t\t_allocateColorBuffer(format, name) {\n\t\t\t\tconst rect = this.camera.rect;\n\t\t\t\tconst renderTarget = this.destinationRenderTarget;\n\t\t\t\tconst device = this.app.graphicsDevice;\n\t\t\t\tconst width = Math.floor(rect.z * (renderTarget?.width ?? device.width));\n\t\t\t\tconst height = Math.floor(rect.w * (renderTarget?.height ?? device.height));\n\t\t\t\tconst colorBuffer = new Texture(device, {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE\n\t\t\t\t});\n\t\t\t\treturn colorBuffer;\n\t\t}\n\t\t_createOffscreenTarget(useDepth, hdr) {\n\t\t\t\tconst device = this.app.graphicsDevice;\n\t\t\t\tconst outputRt = this.destinationRenderTarget ?? device.backBuffer;\n\t\t\t\tconst srgb = outputRt.isColorBufferSrgb(0);\n\t\t\t\tconst format = (hdr && device.getRenderableHdrFormat([\n\t\t\t\t\t\tPIXELFORMAT_RGBA16F,\n\t\t\t\t\t\tPIXELFORMAT_RGBA32F\n\t\t\t\t], true)) ?? (srgb ? PIXELFORMAT_SRGBA8 : PIXELFORMAT_RGBA8);\n\t\t\t\tconst name = `${this.camera.entity.name}-posteffect-${this.effects.length}`;\n\t\t\t\tconst colorBuffer = this._allocateColorBuffer(format, name);\n\t\t\t\treturn new RenderTarget({\n\t\t\t\t\t\tcolorBuffer: colorBuffer,\n\t\t\t\t\t\tdepth: useDepth,\n\t\t\t\t\t\tstencil: useDepth && this.app.graphicsDevice.supportsStencil,\n\t\t\t\t\t\tsamples: useDepth ? device.samples : 1\n\t\t\t\t});\n\t\t}\n\t\t_resizeOffscreenTarget(rt) {\n\t\t\t\tconst format = rt.colorBuffer.format;\n\t\t\t\tconst name = rt.colorBuffer.name;\n\t\t\t\trt.destroyFrameBuffers();\n\t\t\t\trt.destroyTextureBuffers();\n\t\t\t\trt._colorBuffer = this._allocateColorBuffer(format, name);\n\t\t\t\trt._colorBuffers = [\n\t\t\t\t\t\trt._colorBuffer\n\t\t\t\t];\n\t\t}\n\t\t_destroyOffscreenTarget(rt) {\n\t\t\t\trt.destroyTextureBuffers();\n\t\t\t\trt.destroy();\n\t\t}\n\t\taddEffect(effect) {\n\t\t\t\tconst effects = this.effects;\n\t\t\t\tconst isFirstEffect = effects.length === 0;\n\t\t\t\tconst inputTarget = this._createOffscreenTarget(isFirstEffect, effect.hdr);\n\t\t\t\tconst newEntry = new PostEffectEntry(effect, inputTarget);\n\t\t\t\teffects.push(newEntry);\n\t\t\t\tthis._sourceTarget = newEntry.inputTarget;\n\t\t\t\tif (effects.length > 1) {\n\t\t\t\t\t\teffects[effects.length - 2].outputTarget = newEntry.inputTarget;\n\t\t\t\t}\n\t\t\t\tthis._newPostEffect = effect;\n\t\t\t\tif (effect.needsDepthBuffer) {\n\t\t\t\t\t\tthis._requestDepthMap();\n\t\t\t\t}\n\t\t\t\tthis.enable();\n\t\t\t\tthis._newPostEffect = undefined;\n\t\t}\n\t\tremoveEffect(effect) {\n\t\t\t\tlet index = -1;\n\t\t\t\tfor(let i = 0, len = this.effects.length; i < len; i++){\n\t\t\t\t\t\tif (this.effects[i].effect === effect) {\n\t\t\t\t\t\t\t\tindex = i;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\tif (index > 0) {\n\t\t\t\t\t\t\t\tthis.effects[index - 1].outputTarget = index + 1 < this.effects.length ? this.effects[index + 1].inputTarget : null;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.effects.length > 1) {\n\t\t\t\t\t\t\t\t\t\tif (!this.effects[1].inputTarget._depth) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._destroyOffscreenTarget(this.effects[1].inputTarget);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.effects[1].inputTarget = this._createOffscreenTarget(true, this.effects[1].hdr);\n\t\t\t\t\t\t\t\t\t\t\t\tthis._sourceTarget = this.effects[1].inputTarget;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.camera.renderTarget = this.effects[1].inputTarget;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._destroyOffscreenTarget(this.effects[index].inputTarget);\n\t\t\t\t\t\tthis.effects.splice(index, 1);\n\t\t\t\t}\n\t\t\t\tif (this.enabled) {\n\t\t\t\t\t\tif (effect.needsDepthBuffer) {\n\t\t\t\t\t\t\t\tthis._releaseDepthMap();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.effects.length === 0) {\n\t\t\t\t\t\tthis.disable();\n\t\t\t\t}\n\t\t}\n\t\t_requestDepthMaps() {\n\t\t\t\tfor(let i = 0, len = this.effects.length; i < len; i++){\n\t\t\t\t\t\tconst effect = this.effects[i].effect;\n\t\t\t\t\t\tif (this._newPostEffect === effect) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (effect.needsDepthBuffer) {\n\t\t\t\t\t\t\t\tthis._requestDepthMap();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_releaseDepthMaps() {\n\t\t\t\tfor(let i = 0, len = this.effects.length; i < len; i++){\n\t\t\t\t\t\tconst effect = this.effects[i].effect;\n\t\t\t\t\t\tif (effect.needsDepthBuffer) {\n\t\t\t\t\t\t\t\tthis._releaseDepthMap();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_requestDepthMap() {\n\t\t\t\tconst depthLayer = this.app.scene.layers.getLayerById(LAYERID_DEPTH);\n\t\t\t\tif (depthLayer) {\n\t\t\t\t\t\tdepthLayer.incrementCounter();\n\t\t\t\t\t\tthis.camera.requestSceneDepthMap(true);\n\t\t\t\t}\n\t\t}\n\t\t_releaseDepthMap() {\n\t\t\t\tconst depthLayer = this.app.scene.layers.getLayerById(LAYERID_DEPTH);\n\t\t\t\tif (depthLayer) {\n\t\t\t\t\t\tdepthLayer.decrementCounter();\n\t\t\t\t\t\tthis.camera.requestSceneDepthMap(false);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tfor(let i = 0, len = this.effects.length; i < len; i++){\n\t\t\t\t\t\tthis.effects[i].inputTarget.destroy();\n\t\t\t\t}\n\t\t\t\tthis.effects.length = 0;\n\t\t\t\tthis.disable();\n\t\t}\n\t\tenable() {\n\t\t\t\tif (!this.enabled && this.effects.length) {\n\t\t\t\t\t\tthis.enabled = true;\n\t\t\t\t\t\tthis._requestDepthMaps();\n\t\t\t\t\t\tthis.app.graphicsDevice.on('resizecanvas', this._onCanvasResized, this);\n\t\t\t\t\t\tthis.destinationRenderTarget = this.camera.renderTarget;\n\t\t\t\t\t\tthis.camera.renderTarget = this.effects[0].inputTarget;\n\t\t\t\t\t\tthis.camera.onPostprocessing = ()=>{\n\t\t\t\t\t\t\t\tif (this.enabled) {\n\t\t\t\t\t\t\t\t\t\tlet rect = null;\n\t\t\t\t\t\t\t\t\t\tconst len = this.effects.length;\n\t\t\t\t\t\t\t\t\t\tif (len) {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst fx = this.effects[i];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet destTarget = fx.outputTarget;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (i === len - 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trect = this.camera.rect;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (this.destinationRenderTarget) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdestTarget = this.destinationRenderTarget;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfx.effect.render(fx.inputTarget, destTarget, rect);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tdisable() {\n\t\t\t\tif (this.enabled) {\n\t\t\t\t\t\tthis.enabled = false;\n\t\t\t\t\t\tthis.app.graphicsDevice.off('resizecanvas', this._onCanvasResized, this);\n\t\t\t\t\t\tthis._releaseDepthMaps();\n\t\t\t\t\t\tthis._destroyOffscreenTarget(this._sourceTarget);\n\t\t\t\t\t\tthis.camera.renderTarget = this.destinationRenderTarget;\n\t\t\t\t\t\tthis.camera.onPostprocessing = null;\n\t\t\t\t}\n\t\t}\n\t\t_onCanvasResized(width, height) {\n\t\t\t\tconst rect = this.camera.rect;\n\t\t\t\tconst renderTarget = this.destinationRenderTarget;\n\t\t\t\twidth = renderTarget?.width ?? width;\n\t\t\t\theight = renderTarget?.height ?? height;\n\t\t\t\tthis.camera.camera.aspectRatio = width * rect.z / (height * rect.w);\n\t\t\t\tthis.resizeRenderTargets();\n\t\t}\n\t\tresizeRenderTargets() {\n\t\t\t\tconst device = this.app.graphicsDevice;\n\t\t\t\tconst renderTarget = this.destinationRenderTarget;\n\t\t\t\tconst width = renderTarget?.width ?? device.width;\n\t\t\t\tconst height = renderTarget?.height ?? device.height;\n\t\t\t\tconst rect = this.camera.rect;\n\t\t\t\tconst desiredWidth = Math.floor(rect.z * width);\n\t\t\t\tconst desiredHeight = Math.floor(rect.w * height);\n\t\t\t\tconst effects = this.effects;\n\t\t\t\tfor(let i = 0, len = effects.length; i < len; i++){\n\t\t\t\t\t\tconst fx = effects[i];\n\t\t\t\t\t\tif (fx.inputTarget.width !== desiredWidth || fx.inputTarget.height !== desiredHeight) {\n\t\t\t\t\t\t\t\tthis._resizeOffscreenTarget(fx.inputTarget);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonCameraRectChanged(name, oldValue, newValue) {\n\t\t\t\tif (this.enabled) {\n\t\t\t\t\t\tthis.resizeRenderTargets();\n\t\t\t\t}\n\t\t}\n}\n\nclass CameraComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this.onPostprocessing = null, this._renderSceneDepthMap = 0, this._renderSceneColorMap = 0, this._sceneDepthMapRequested = false, this._sceneColorMapRequested = false, this._priority = 0, this._disablePostEffectsLayer = LAYERID_UI, this._camera = new Camera(), this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null;\n\t\t\t\tthis._camera.node = entity;\n\t\t\t\tthis._postEffects = new PostEffectQueue(system.app, this);\n\t\t}\n\t\tsetShaderPass(name) {\n\t\t\t\tconst shaderPass = ShaderPass.get(this.system.app.graphicsDevice);\n\t\t\t\tconst shaderPassInfo = name ? shaderPass.allocate(name, {\n\t\t\t\t\t\tisForward: true\n\t\t\t\t}) : null;\n\t\t\t\tthis._camera.shaderPassInfo = shaderPassInfo;\n\t\t\t\treturn shaderPassInfo.index;\n\t\t}\n\t\tgetShaderPass() {\n\t\t\t\treturn this._camera.shaderPassInfo?.name;\n\t\t}\n\t\tset renderPasses(passes) {\n\t\t\t\tthis._camera.renderPasses = passes || [];\n\t\t\t\tthis.dirtyLayerCompositionCameras();\n\t\t\t\tthis.system.app.scene.updateShaders = true;\n\t\t}\n\t\tget renderPasses() {\n\t\t\t\treturn this._camera.renderPasses;\n\t\t}\n\t\tget shaderParams() {\n\t\t\t\treturn this._camera.shaderParams;\n\t\t}\n\t\tset gammaCorrection(value) {\n\t\t\t\tthis.camera.shaderParams.gammaCorrection = value;\n\t\t}\n\t\tget gammaCorrection() {\n\t\t\t\treturn this.camera.shaderParams.gammaCorrection;\n\t\t}\n\t\tset toneMapping(value) {\n\t\t\t\tthis.camera.shaderParams.toneMapping = value;\n\t\t}\n\t\tget toneMapping() {\n\t\t\t\treturn this.camera.shaderParams.toneMapping;\n\t\t}\n\t\tset fog(value) {\n\t\t\t\tthis._camera.fogParams = value;\n\t\t}\n\t\tget fog() {\n\t\t\t\treturn this._camera.fogParams;\n\t\t}\n\t\tset aperture(value) {\n\t\t\t\tthis._camera.aperture = value;\n\t\t}\n\t\tget aperture() {\n\t\t\t\treturn this._camera.aperture;\n\t\t}\n\t\tset aspectRatio(value) {\n\t\t\t\tthis._camera.aspectRatio = value;\n\t\t}\n\t\tget aspectRatio() {\n\t\t\t\treturn this._camera.aspectRatio;\n\t\t}\n\t\tset aspectRatioMode(value) {\n\t\t\t\tthis._camera.aspectRatioMode = value;\n\t\t}\n\t\tget aspectRatioMode() {\n\t\t\t\treturn this._camera.aspectRatioMode;\n\t\t}\n\t\tset calculateProjection(value) {\n\t\t\t\tthis._camera.calculateProjection = value;\n\t\t}\n\t\tget calculateProjection() {\n\t\t\t\treturn this._camera.calculateProjection;\n\t\t}\n\t\tset calculateTransform(value) {\n\t\t\t\tthis._camera.calculateTransform = value;\n\t\t}\n\t\tget calculateTransform() {\n\t\t\t\treturn this._camera.calculateTransform;\n\t\t}\n\t\tget camera() {\n\t\t\t\treturn this._camera;\n\t\t}\n\t\tset clearColor(value) {\n\t\t\t\tthis._camera.clearColor = value;\n\t\t}\n\t\tget clearColor() {\n\t\t\t\treturn this._camera.clearColor;\n\t\t}\n\t\tset clearColorBuffer(value) {\n\t\t\t\tthis._camera.clearColorBuffer = value;\n\t\t\t\tthis.dirtyLayerCompositionCameras();\n\t\t}\n\t\tget clearColorBuffer() {\n\t\t\t\treturn this._camera.clearColorBuffer;\n\t\t}\n\t\tset clearDepthBuffer(value) {\n\t\t\t\tthis._camera.clearDepthBuffer = value;\n\t\t\t\tthis.dirtyLayerCompositionCameras();\n\t\t}\n\t\tget clearDepthBuffer() {\n\t\t\t\treturn this._camera.clearDepthBuffer;\n\t\t}\n\t\tset clearStencilBuffer(value) {\n\t\t\t\tthis._camera.clearStencilBuffer = value;\n\t\t\t\tthis.dirtyLayerCompositionCameras();\n\t\t}\n\t\tget clearStencilBuffer() {\n\t\t\t\treturn this._camera.clearStencilBuffer;\n\t\t}\n\t\tset cullFaces(value) {\n\t\t\t\tthis._camera.cullFaces = value;\n\t\t}\n\t\tget cullFaces() {\n\t\t\t\treturn this._camera.cullFaces;\n\t\t}\n\t\tset disablePostEffectsLayer(layer) {\n\t\t\t\tthis._disablePostEffectsLayer = layer;\n\t\t\t\tthis.dirtyLayerCompositionCameras();\n\t\t}\n\t\tget disablePostEffectsLayer() {\n\t\t\t\treturn this._disablePostEffectsLayer;\n\t\t}\n\t\tset farClip(value) {\n\t\t\t\tthis._camera.farClip = value;\n\t\t}\n\t\tget farClip() {\n\t\t\t\treturn this._camera.farClip;\n\t\t}\n\t\tset flipFaces(value) {\n\t\t\t\tthis._camera.flipFaces = value;\n\t\t}\n\t\tget flipFaces() {\n\t\t\t\treturn this._camera.flipFaces;\n\t\t}\n\t\tset fov(value) {\n\t\t\t\tthis._camera.fov = value;\n\t\t}\n\t\tget fov() {\n\t\t\t\treturn this._camera.fov;\n\t\t}\n\t\tget frustum() {\n\t\t\t\treturn this._camera.frustum;\n\t\t}\n\t\tset frustumCulling(value) {\n\t\t\t\tthis._camera.frustumCulling = value;\n\t\t}\n\t\tget frustumCulling() {\n\t\t\t\treturn this._camera.frustumCulling;\n\t\t}\n\t\tset horizontalFov(value) {\n\t\t\t\tthis._camera.horizontalFov = value;\n\t\t}\n\t\tget horizontalFov() {\n\t\t\t\treturn this._camera.horizontalFov;\n\t\t}\n\t\tset layers(newValue) {\n\t\t\t\tconst oldLayers = this._camera.layers;\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\toldLayers.forEach((layerId)=>{\n\t\t\t\t\t\tconst layer = scene.layers.getLayerById(layerId);\n\t\t\t\t\t\tlayer?.removeCamera(this);\n\t\t\t\t});\n\t\t\t\tthis._camera.layers = newValue;\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tnewValue.forEach((layerId)=>{\n\t\t\t\t\t\t\t\tconst layer = scene.layers.getLayerById(layerId);\n\t\t\t\t\t\t\t\tlayer?.addCamera(this);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthis.fire('set:layers');\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._camera.layers;\n\t\t}\n\t\tget layersSet() {\n\t\t\t\treturn this._camera.layersSet;\n\t\t}\n\t\tset jitter(value) {\n\t\t\t\tthis._camera.jitter = value;\n\t\t}\n\t\tget jitter() {\n\t\t\t\treturn this._camera.jitter;\n\t\t}\n\t\tset nearClip(value) {\n\t\t\t\tthis._camera.nearClip = value;\n\t\t}\n\t\tget nearClip() {\n\t\t\t\treturn this._camera.nearClip;\n\t\t}\n\t\tset orthoHeight(value) {\n\t\t\t\tthis._camera.orthoHeight = value;\n\t\t}\n\t\tget orthoHeight() {\n\t\t\t\treturn this._camera.orthoHeight;\n\t\t}\n\t\tget postEffects() {\n\t\t\t\treturn this._postEffects;\n\t\t}\n\t\tget postEffectsEnabled() {\n\t\t\t\treturn this._postEffects.enabled;\n\t\t}\n\t\tset priority(newValue) {\n\t\t\t\tthis._priority = newValue;\n\t\t\t\tthis.dirtyLayerCompositionCameras();\n\t\t}\n\t\tget priority() {\n\t\t\t\treturn this._priority;\n\t\t}\n\t\tset projection(value) {\n\t\t\t\tthis._camera.projection = value;\n\t\t}\n\t\tget projection() {\n\t\t\t\treturn this._camera.projection;\n\t\t}\n\t\tget projectionMatrix() {\n\t\t\t\treturn this._camera.projectionMatrix;\n\t\t}\n\t\tset rect(value) {\n\t\t\t\tthis._camera.rect = value;\n\t\t\t\tthis.fire('set:rect', this._camera.rect);\n\t\t}\n\t\tget rect() {\n\t\t\t\treturn this._camera.rect;\n\t\t}\n\t\tset renderSceneColorMap(value) {\n\t\t\t\tif (value && !this._sceneColorMapRequested) {\n\t\t\t\t\t\tthis.requestSceneColorMap(true);\n\t\t\t\t\t\tthis._sceneColorMapRequested = true;\n\t\t\t\t} else if (this._sceneColorMapRequested) {\n\t\t\t\t\t\tthis.requestSceneColorMap(false);\n\t\t\t\t\t\tthis._sceneColorMapRequested = false;\n\t\t\t\t}\n\t\t}\n\t\tget renderSceneColorMap() {\n\t\t\t\treturn this._renderSceneColorMap > 0;\n\t\t}\n\t\tset renderSceneDepthMap(value) {\n\t\t\t\tif (value && !this._sceneDepthMapRequested) {\n\t\t\t\t\t\tthis.requestSceneDepthMap(true);\n\t\t\t\t\t\tthis._sceneDepthMapRequested = true;\n\t\t\t\t} else if (this._sceneDepthMapRequested) {\n\t\t\t\t\t\tthis.requestSceneDepthMap(false);\n\t\t\t\t\t\tthis._sceneDepthMapRequested = false;\n\t\t\t\t}\n\t\t}\n\t\tget renderSceneDepthMap() {\n\t\t\t\treturn this._renderSceneDepthMap > 0;\n\t\t}\n\t\tset renderTarget(value) {\n\t\t\t\tthis._camera.renderTarget = value;\n\t\t\t\tthis.dirtyLayerCompositionCameras();\n\t\t}\n\t\tget renderTarget() {\n\t\t\t\treturn this._camera.renderTarget;\n\t\t}\n\t\tset scissorRect(value) {\n\t\t\t\tthis._camera.scissorRect = value;\n\t\t}\n\t\tget scissorRect() {\n\t\t\t\treturn this._camera.scissorRect;\n\t\t}\n\t\tset sensitivity(value) {\n\t\t\t\tthis._camera.sensitivity = value;\n\t\t}\n\t\tget sensitivity() {\n\t\t\t\treturn this._camera.sensitivity;\n\t\t}\n\t\tset shutter(value) {\n\t\t\t\tthis._camera.shutter = value;\n\t\t}\n\t\tget shutter() {\n\t\t\t\treturn this._camera.shutter;\n\t\t}\n\t\tget viewMatrix() {\n\t\t\t\treturn this._camera.viewMatrix;\n\t\t}\n\t\t_enableDepthLayer(value) {\n\t\t\t\tconst hasDepthLayer = this.layers.find((layerId)=>layerId === LAYERID_DEPTH);\n\t\t\t\tif (hasDepthLayer) {\n\t\t\t\t\t\tconst depthLayer = this.system.app.scene.layers.getLayerById(LAYERID_DEPTH);\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\tdepthLayer?.incrementCounter();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdepthLayer?.decrementCounter();\n\t\t\t\t\t\t}\n\t\t\t\t} else if (value) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\trequestSceneColorMap(enabled) {\n\t\t\t\tthis._renderSceneColorMap += enabled ? 1 : -1;\n\t\t\t\tthis._enableDepthLayer(enabled);\n\t\t\t\tthis.camera._enableRenderPassColorGrab(this.system.app.graphicsDevice, this.renderSceneColorMap);\n\t\t\t\tthis.system.app.scene.layers.markDirty();\n\t\t}\n\t\trequestSceneDepthMap(enabled) {\n\t\t\t\tthis._renderSceneDepthMap += enabled ? 1 : -1;\n\t\t\t\tthis._enableDepthLayer(enabled);\n\t\t\t\tthis.camera._enableRenderPassDepthGrab(this.system.app.graphicsDevice, this.system.app.renderer, this.renderSceneDepthMap);\n\t\t\t\tthis.system.app.scene.layers.markDirty();\n\t\t}\n\t\tdirtyLayerCompositionCameras() {\n\t\t\t\tconst layerComp = this.system.app.scene.layers;\n\t\t\t\tlayerComp._dirty = true;\n\t\t}\n\t\tscreenToWorld(screenx, screeny, cameraz, worldCoord) {\n\t\t\t\tconst device = this.system.app.graphicsDevice;\n\t\t\t\tconst { width, height } = device.clientRect;\n\t\t\t\treturn this._camera.screenToWorld(screenx, screeny, cameraz, width, height, worldCoord);\n\t\t}\n\t\tworldToScreen(worldCoord, screenCoord) {\n\t\t\t\tconst device = this.system.app.graphicsDevice;\n\t\t\t\tconst { width, height } = device.clientRect;\n\t\t\t\treturn this._camera.worldToScreen(worldCoord, width, height, screenCoord);\n\t\t}\n\t\tonAppPrerender() {\n\t\t\t\tthis._camera._viewMatDirty = true;\n\t\t\t\tthis._camera._viewProjMatDirty = true;\n\t\t}\n\t\taddCameraToLayers() {\n\t\t\t\tconst layers = this.layers;\n\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.addCamera(this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremoveCameraFromLayers() {\n\t\t\t\tconst layers = this.layers;\n\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.removeCamera(this);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonLayersChanged(oldComp, newComp) {\n\t\t\t\tthis.addCameraToLayers();\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t}\n\t\tonLayerAdded(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.addCamera(this);\n\t\t}\n\t\tonLayerRemoved(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tlayer.removeCamera(this);\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis.system.addCamera(this);\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this.onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.addCameraToLayers();\n\t\t\t\t}\n\t\t\t\tthis.postEffects.enable();\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis.postEffects.disable();\n\t\t\t\tthis.removeCameraFromLayers();\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tthis.system.removeCamera(this);\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.onDisable();\n\t\t\t\tthis.off();\n\t\t\t\tthis.camera.destroy();\n\t\t}\n\t\tcalculateAspectRatio(rt) {\n\t\t\t\tconst device = this.system.app.graphicsDevice;\n\t\t\t\tconst width = rt ? rt.width : device.width;\n\t\t\t\tconst height = rt ? rt.height : device.height;\n\t\t\t\treturn width * this.rect.z / (height * this.rect.w);\n\t\t}\n\t\tframeUpdate(rt) {\n\t\t\t\tif (this.aspectRatioMode === ASPECT_AUTO) {\n\t\t\t\t\t\tthis.aspectRatio = this.calculateAspectRatio(rt);\n\t\t\t\t}\n\t\t}\n\t\tstartXr(type, spaceType, options) {\n\t\t\t\tthis.system.app.xr.start(this, type, spaceType, options);\n\t\t}\n\t\tendXr(callback) {\n\t\t\t\tif (!this._camera.xr) {\n\t\t\t\t\t\tif (callback) callback(new Error('Camera is not in XR'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._camera.xr.end(callback);\n\t\t}\n\t\tcopy(source) {\n\t\t\t\tthis.aperture = source.aperture;\n\t\t\t\tthis.aspectRatio = source.aspectRatio;\n\t\t\t\tthis.aspectRatioMode = source.aspectRatioMode;\n\t\t\t\tthis.calculateProjection = source.calculateProjection;\n\t\t\t\tthis.calculateTransform = source.calculateTransform;\n\t\t\t\tthis.clearColor = source.clearColor;\n\t\t\t\tthis.clearColorBuffer = source.clearColorBuffer;\n\t\t\t\tthis.clearDepthBuffer = source.clearDepthBuffer;\n\t\t\t\tthis.clearStencilBuffer = source.clearStencilBuffer;\n\t\t\t\tthis.cullFaces = source.cullFaces;\n\t\t\t\tthis.disablePostEffectsLayer = source.disablePostEffectsLayer;\n\t\t\t\tthis.farClip = source.farClip;\n\t\t\t\tthis.flipFaces = source.flipFaces;\n\t\t\t\tthis.fov = source.fov;\n\t\t\t\tthis.frustumCulling = source.frustumCulling;\n\t\t\t\tthis.horizontalFov = source.horizontalFov;\n\t\t\t\tthis.layers = source.layers;\n\t\t\t\tthis.nearClip = source.nearClip;\n\t\t\t\tthis.orthoHeight = source.orthoHeight;\n\t\t\t\tthis.priority = source.priority;\n\t\t\t\tthis.projection = source.projection;\n\t\t\t\tthis.rect = source.rect;\n\t\t\t\tthis.renderTarget = source.renderTarget;\n\t\t\t\tthis.scissorRect = source.scissorRect;\n\t\t\t\tthis.sensitivity = source.sensitivity;\n\t\t\t\tthis.shutter = source.shutter;\n\t\t}\n}\n\nclass CameraComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst _schema$1 = [\n\t\t'enabled'\n];\nclass CameraComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app), this.cameras = [];\n\t\t\t\tthis.id = 'camera';\n\t\t\t\tthis.ComponentType = CameraComponent;\n\t\t\t\tthis.DataType = CameraComponentData;\n\t\t\t\tthis.schema = _schema$1;\n\t\t\t\tthis.on('beforeremove', this.onBeforeRemove, this);\n\t\t\t\tthis.app.on('prerender', this.onAppPrerender, this);\n\t\t}\n\t\tinitializeComponentData(component, data, properties) {\n\t\t\t\tproperties = [\n\t\t\t\t\t\t'aspectRatio',\n\t\t\t\t\t\t'aspectRatioMode',\n\t\t\t\t\t\t'calculateProjection',\n\t\t\t\t\t\t'calculateTransform',\n\t\t\t\t\t\t'clearColor',\n\t\t\t\t\t\t'clearColorBuffer',\n\t\t\t\t\t\t'clearDepthBuffer',\n\t\t\t\t\t\t'clearStencilBuffer',\n\t\t\t\t\t\t'renderSceneColorMap',\n\t\t\t\t\t\t'renderSceneDepthMap',\n\t\t\t\t\t\t'cullFaces',\n\t\t\t\t\t\t'farClip',\n\t\t\t\t\t\t'flipFaces',\n\t\t\t\t\t\t'fog',\n\t\t\t\t\t\t'fov',\n\t\t\t\t\t\t'frustumCulling',\n\t\t\t\t\t\t'horizontalFov',\n\t\t\t\t\t\t'layers',\n\t\t\t\t\t\t'renderTarget',\n\t\t\t\t\t\t'nearClip',\n\t\t\t\t\t\t'orthoHeight',\n\t\t\t\t\t\t'projection',\n\t\t\t\t\t\t'priority',\n\t\t\t\t\t\t'rect',\n\t\t\t\t\t\t'scissorRect',\n\t\t\t\t\t\t'aperture',\n\t\t\t\t\t\t'shutter',\n\t\t\t\t\t\t'sensitivity',\n\t\t\t\t\t\t'gammaCorrection',\n\t\t\t\t\t\t'toneMapping'\n\t\t\t\t];\n\t\t\t\tfor(let i = 0; i < properties.length; i++){\n\t\t\t\t\t\tconst property = properties[i];\n\t\t\t\t\t\tif (data.hasOwnProperty(property)) {\n\t\t\t\t\t\t\t\tconst value = data[property];\n\t\t\t\t\t\t\t\tswitch(property){\n\t\t\t\t\t\t\t\t\t\tcase 'rect':\n\t\t\t\t\t\t\t\t\t\tcase 'scissorRect':\n\t\t\t\t\t\t\t\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent[property] = new Vec4(value[0], value[1], value[2], value[3]);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent[property] = value;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase 'clearColor':\n\t\t\t\t\t\t\t\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent[property] = new Color(value[0], value[1], value[2], value[3]);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcomponent[property] = value;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\tcomponent[property] = value;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, data, [\n\t\t\t\t\t\t'enabled'\n\t\t\t\t]);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst c = entity.camera;\n\t\t\t\treturn this.addComponent(clone, {\n\t\t\t\t\t\taspectRatio: c.aspectRatio,\n\t\t\t\t\t\taspectRatioMode: c.aspectRatioMode,\n\t\t\t\t\t\tcalculateProjection: c.calculateProjection,\n\t\t\t\t\t\tcalculateTransform: c.calculateTransform,\n\t\t\t\t\t\tclearColor: c.clearColor,\n\t\t\t\t\t\tclearColorBuffer: c.clearColorBuffer,\n\t\t\t\t\t\tclearDepthBuffer: c.clearDepthBuffer,\n\t\t\t\t\t\tclearStencilBuffer: c.clearStencilBuffer,\n\t\t\t\t\t\trenderSceneDepthMap: c.renderSceneDepthMap,\n\t\t\t\t\t\trenderSceneColorMap: c.renderSceneColorMap,\n\t\t\t\t\t\tcullFaces: c.cullFaces,\n\t\t\t\t\t\tenabled: c.enabled,\n\t\t\t\t\t\tfarClip: c.farClip,\n\t\t\t\t\t\tflipFaces: c.flipFaces,\n\t\t\t\t\t\tfov: c.fov,\n\t\t\t\t\t\tfrustumCulling: c.frustumCulling,\n\t\t\t\t\t\thorizontalFov: c.horizontalFov,\n\t\t\t\t\t\tlayers: c.layers,\n\t\t\t\t\t\trenderTarget: c.renderTarget,\n\t\t\t\t\t\tnearClip: c.nearClip,\n\t\t\t\t\t\torthoHeight: c.orthoHeight,\n\t\t\t\t\t\tprojection: c.projection,\n\t\t\t\t\t\tpriority: c.priority,\n\t\t\t\t\t\trect: c.rect,\n\t\t\t\t\t\tscissorRect: c.scissorRect,\n\t\t\t\t\t\taperture: c.aperture,\n\t\t\t\t\t\tsensitivity: c.sensitivity,\n\t\t\t\t\t\tshutter: c.shutter,\n\t\t\t\t\t\tgammaCorrection: c.gammaCorrection,\n\t\t\t\t\t\ttoneMapping: c.toneMapping\n\t\t\t\t});\n\t\t}\n\t\tonBeforeRemove(entity, component) {\n\t\t\t\tthis.removeCamera(component);\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tonAppPrerender() {\n\t\t\t\tfor(let i = 0, len = this.cameras.length; i < len; i++){\n\t\t\t\t\t\tthis.cameras[i].onAppPrerender();\n\t\t\t\t}\n\t\t}\n\t\taddCamera(camera) {\n\t\t\t\tthis.cameras.push(camera);\n\t\t\t\tsortPriority(this.cameras);\n\t\t}\n\t\tremoveCamera(camera) {\n\t\t\t\tconst index = this.cameras.indexOf(camera);\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\tthis.cameras.splice(index, 1);\n\t\t\t\t\t\tsortPriority(this.cameras);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.app.off('prerender', this.onAppPrerender, this);\n\t\t\t\tsuper.destroy();\n\t\t}\n}\nComponent._buildAccessors(CameraComponent.prototype, _schema$1);\n\nclass LightComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t\t\tthis.type = 'directional';\n\t\t\t\tthis.color = new Color(1, 1, 1);\n\t\t\t\tthis.intensity = 1;\n\t\t\t\tthis.luminance = 0;\n\t\t\t\tthis.shape = LIGHTSHAPE_PUNCTUAL;\n\t\t\t\tthis.affectSpecularity = true;\n\t\t\t\tthis.castShadows = false;\n\t\t\t\tthis.shadowDistance = 40;\n\t\t\t\tthis.shadowIntensity = 1;\n\t\t\t\tthis.shadowResolution = 1024;\n\t\t\t\tthis.shadowBias = 0.05;\n\t\t\t\tthis.numCascades = 1;\n\t\t\t\tthis.cascadeBlend = 0;\n\t\t\t\tthis.bakeNumSamples = 1;\n\t\t\t\tthis.bakeArea = 0;\n\t\t\t\tthis.cascadeDistribution = 0.5;\n\t\t\t\tthis.normalOffsetBias = 0;\n\t\t\t\tthis.range = 10;\n\t\t\t\tthis.innerConeAngle = 40;\n\t\t\t\tthis.outerConeAngle = 45;\n\t\t\t\tthis.falloffMode = LIGHTFALLOFF_LINEAR;\n\t\t\t\tthis.shadowType = SHADOW_PCF3_32F;\n\t\t\t\tthis.vsmBlurSize = 11;\n\t\t\t\tthis.vsmBlurMode = BLUR_GAUSSIAN;\n\t\t\t\tthis.vsmBias = 0.01 * 0.25;\n\t\t\t\tthis.cookieAsset = null;\n\t\t\t\tthis.cookie = null;\n\t\t\t\tthis.cookieIntensity = 1;\n\t\t\t\tthis.cookieFalloff = true;\n\t\t\t\tthis.cookieChannel = 'rgb';\n\t\t\t\tthis.cookieAngle = 0;\n\t\t\t\tthis.cookieScale = null;\n\t\t\t\tthis.cookieOffset = null;\n\t\t\t\tthis.shadowUpdateMode = SHADOWUPDATE_REALTIME;\n\t\t\t\tthis.mask = 1;\n\t\t\t\tthis.affectDynamic = true;\n\t\t\t\tthis.affectLightmapped = false;\n\t\t\t\tthis.bake = false;\n\t\t\t\tthis.bakeDir = true;\n\t\t\t\tthis.isStatic = false;\n\t\t\t\tthis.layers = [\n\t\t\t\t\t\tLAYERID_WORLD\n\t\t\t\t];\n\t\t\t\tthis.penumbraSize = 1;\n\t\t\t\tthis.penumbraFalloff = 1;\n\t\t\t\tthis.shadowSamples = 16;\n\t\t\t\tthis.shadowBlockerSamples = 16;\n\t\t}\n}\nconst properties = Object.keys(new LightComponentData());\n\nclass LightComponent extends Component {\n\t\tget data() {\n\t\t\t\tconst record = this.system.store[this.entity.getGuid()];\n\t\t\t\treturn record ? record.data : null;\n\t\t}\n\t\tset enabled(arg) {\n\t\t\t\tthis._setValue('enabled', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.onSetEnabled(null, oldValue, newValue);\n\t\t\t\t});\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this.data.enabled;\n\t\t}\n\t\tset light(arg) {\n\t\t\t\tthis._setValue('light', arg);\n\t\t}\n\t\tget light() {\n\t\t\t\treturn this.data.light;\n\t\t}\n\t\tset type(arg) {\n\t\t\t\tthis._setValue('type', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.system.changeType(this, oldValue, newValue);\n\t\t\t\t\t\tthis.refreshProperties();\n\t\t\t\t});\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this.data.type;\n\t\t}\n\t\tset color(arg) {\n\t\t\t\tthis._setValue('color', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.setColor(newValue);\n\t\t\t\t}, true);\n\t\t}\n\t\tget color() {\n\t\t\t\treturn this.data.color;\n\t\t}\n\t\tset intensity(arg) {\n\t\t\t\tthis._setValue('intensity', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.intensity = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget intensity() {\n\t\t\t\treturn this.data.intensity;\n\t\t}\n\t\tset luminance(arg) {\n\t\t\t\tthis._setValue('luminance', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.luminance = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget luminance() {\n\t\t\t\treturn this.data.luminance;\n\t\t}\n\t\tset shape(arg) {\n\t\t\t\tthis._setValue('shape', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.shape = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget shape() {\n\t\t\t\treturn this.data.shape;\n\t\t}\n\t\tset affectSpecularity(arg) {\n\t\t\t\tthis._setValue('affectSpecularity', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.affectSpecularity = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget affectSpecularity() {\n\t\t\t\treturn this.data.affectSpecularity;\n\t\t}\n\t\tset castShadows(arg) {\n\t\t\t\tthis._setValue('castShadows', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.castShadows = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget castShadows() {\n\t\t\t\treturn this.data.castShadows;\n\t\t}\n\t\tset shadowDistance(arg) {\n\t\t\t\tthis._setValue('shadowDistance', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.shadowDistance = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget shadowDistance() {\n\t\t\t\treturn this.data.shadowDistance;\n\t\t}\n\t\tset shadowIntensity(arg) {\n\t\t\t\tthis._setValue('shadowIntensity', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.shadowIntensity = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget shadowIntensity() {\n\t\t\t\treturn this.data.shadowIntensity;\n\t\t}\n\t\tset shadowResolution(arg) {\n\t\t\t\tthis._setValue('shadowResolution', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.shadowResolution = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget shadowResolution() {\n\t\t\t\treturn this.data.shadowResolution;\n\t\t}\n\t\tset shadowBias(arg) {\n\t\t\t\tthis._setValue('shadowBias', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.shadowBias = -0.01 * math.clamp(newValue, 0, 1);\n\t\t\t\t});\n\t\t}\n\t\tget shadowBias() {\n\t\t\t\treturn this.data.shadowBias;\n\t\t}\n\t\tset numCascades(arg) {\n\t\t\t\tthis._setValue('numCascades', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.numCascades = math.clamp(Math.floor(newValue), 1, 4);\n\t\t\t\t});\n\t\t}\n\t\tget numCascades() {\n\t\t\t\treturn this.data.numCascades;\n\t\t}\n\t\tset cascadeBlend(value) {\n\t\t\t\tthis._setValue('cascadeBlend', value, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.cascadeBlend = math.clamp(newValue, 0, 1);\n\t\t\t\t});\n\t\t}\n\t\tget cascadeBlend() {\n\t\t\t\treturn this.data.cascadeBlend;\n\t\t}\n\t\tset bakeNumSamples(arg) {\n\t\t\t\tthis._setValue('bakeNumSamples', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.bakeNumSamples = math.clamp(Math.floor(newValue), 1, 255);\n\t\t\t\t});\n\t\t}\n\t\tget bakeNumSamples() {\n\t\t\t\treturn this.data.bakeNumSamples;\n\t\t}\n\t\tset bakeArea(arg) {\n\t\t\t\tthis._setValue('bakeArea', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.bakeArea = math.clamp(newValue, 0, 180);\n\t\t\t\t});\n\t\t}\n\t\tget bakeArea() {\n\t\t\t\treturn this.data.bakeArea;\n\t\t}\n\t\tset cascadeDistribution(arg) {\n\t\t\t\tthis._setValue('cascadeDistribution', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.cascadeDistribution = math.clamp(newValue, 0, 1);\n\t\t\t\t});\n\t\t}\n\t\tget cascadeDistribution() {\n\t\t\t\treturn this.data.cascadeDistribution;\n\t\t}\n\t\tset normalOffsetBias(arg) {\n\t\t\t\tthis._setValue('normalOffsetBias', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.normalOffsetBias = math.clamp(newValue, 0, 1);\n\t\t\t\t});\n\t\t}\n\t\tget normalOffsetBias() {\n\t\t\t\treturn this.data.normalOffsetBias;\n\t\t}\n\t\tset range(arg) {\n\t\t\t\tthis._setValue('range', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.attenuationEnd = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget range() {\n\t\t\t\treturn this.data.range;\n\t\t}\n\t\tset innerConeAngle(arg) {\n\t\t\t\tthis._setValue('innerConeAngle', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.innerConeAngle = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget innerConeAngle() {\n\t\t\t\treturn this.data.innerConeAngle;\n\t\t}\n\t\tset outerConeAngle(arg) {\n\t\t\t\tthis._setValue('outerConeAngle', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.outerConeAngle = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget outerConeAngle() {\n\t\t\t\treturn this.data.outerConeAngle;\n\t\t}\n\t\tset falloffMode(arg) {\n\t\t\t\tthis._setValue('falloffMode', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.falloffMode = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget falloffMode() {\n\t\t\t\treturn this.data.falloffMode;\n\t\t}\n\t\tset shadowType(arg) {\n\t\t\t\tthis._setValue('shadowType', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.shadowType = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget shadowType() {\n\t\t\t\treturn this.data.shadowType;\n\t\t}\n\t\tset vsmBlurSize(arg) {\n\t\t\t\tthis._setValue('vsmBlurSize', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.vsmBlurSize = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget vsmBlurSize() {\n\t\t\t\treturn this.data.vsmBlurSize;\n\t\t}\n\t\tset vsmBlurMode(arg) {\n\t\t\t\tthis._setValue('vsmBlurMode', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.vsmBlurMode = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget vsmBlurMode() {\n\t\t\t\treturn this.data.vsmBlurMode;\n\t\t}\n\t\tset vsmBias(arg) {\n\t\t\t\tthis._setValue('vsmBias', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.vsmBias = math.clamp(newValue, 0, 1);\n\t\t\t\t});\n\t\t}\n\t\tget vsmBias() {\n\t\t\t\treturn this.data.vsmBias;\n\t\t}\n\t\tset cookieAsset(arg) {\n\t\t\t\tthis._setValue('cookieAsset', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tif (this._cookieAssetId && (newValue instanceof Asset && newValue.id === this._cookieAssetId || newValue === this._cookieAssetId)) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.onCookieAssetRemove();\n\t\t\t\t\t\tthis._cookieAssetId = null;\n\t\t\t\t\t\tif (newValue instanceof Asset) {\n\t\t\t\t\t\t\t\tthis.data.cookieAsset = newValue.id;\n\t\t\t\t\t\t\t\tthis._cookieAssetId = newValue.id;\n\t\t\t\t\t\t\t\tthis.onCookieAssetAdd(newValue);\n\t\t\t\t\t\t} else if (typeof newValue === 'number') {\n\t\t\t\t\t\t\t\tthis._cookieAssetId = newValue;\n\t\t\t\t\t\t\t\tconst asset = this.system.app.assets.get(newValue);\n\t\t\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\t\t\tthis.onCookieAssetAdd(asset);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._cookieAssetAdd = true;\n\t\t\t\t\t\t\t\t\t\tthis.system.app.assets.on(`add:${this._cookieAssetId}`, this.onCookieAssetAdd, this);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tget cookieAsset() {\n\t\t\t\treturn this.data.cookieAsset;\n\t\t}\n\t\tset cookie(arg) {\n\t\t\t\tthis._setValue('cookie', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.cookie = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget cookie() {\n\t\t\t\treturn this.data.cookie;\n\t\t}\n\t\tset cookieIntensity(arg) {\n\t\t\t\tthis._setValue('cookieIntensity', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.cookieIntensity = math.clamp(newValue, 0, 1);\n\t\t\t\t});\n\t\t}\n\t\tget cookieIntensity() {\n\t\t\t\treturn this.data.cookieIntensity;\n\t\t}\n\t\tset cookieFalloff(arg) {\n\t\t\t\tthis._setValue('cookieFalloff', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.cookieFalloff = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget cookieFalloff() {\n\t\t\t\treturn this.data.cookieFalloff;\n\t\t}\n\t\tset cookieChannel(arg) {\n\t\t\t\tthis._setValue('cookieChannel', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.cookieChannel = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget cookieChannel() {\n\t\t\t\treturn this.data.cookieChannel;\n\t\t}\n\t\tset cookieAngle(arg) {\n\t\t\t\tthis._setValue('cookieAngle', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tif (newValue !== 0 || this.cookieScale !== null) {\n\t\t\t\t\t\t\t\tif (!this._cookieMatrix) this._cookieMatrix = new Vec4();\n\t\t\t\t\t\t\t\tlet scx = 1;\n\t\t\t\t\t\t\t\tlet scy = 1;\n\t\t\t\t\t\t\t\tif (this.cookieScale) {\n\t\t\t\t\t\t\t\t\t\tscx = this.cookieScale.x;\n\t\t\t\t\t\t\t\t\t\tscy = this.cookieScale.y;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst c = Math.cos(newValue * math.DEG_TO_RAD);\n\t\t\t\t\t\t\t\tconst s = Math.sin(newValue * math.DEG_TO_RAD);\n\t\t\t\t\t\t\t\tthis._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);\n\t\t\t\t\t\t\t\tthis.light.cookieTransform = this._cookieMatrix;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.light.cookieTransform = null;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tget cookieAngle() {\n\t\t\t\treturn this.data.cookieAngle;\n\t\t}\n\t\tset cookieScale(arg) {\n\t\t\t\tthis._setValue('cookieScale', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tif (newValue !== null || this.cookieAngle !== 0) {\n\t\t\t\t\t\t\t\tif (!this._cookieMatrix) this._cookieMatrix = new Vec4();\n\t\t\t\t\t\t\t\tconst scx = newValue.x;\n\t\t\t\t\t\t\t\tconst scy = newValue.y;\n\t\t\t\t\t\t\t\tconst c = Math.cos(this.cookieAngle * math.DEG_TO_RAD);\n\t\t\t\t\t\t\t\tconst s = Math.sin(this.cookieAngle * math.DEG_TO_RAD);\n\t\t\t\t\t\t\t\tthis._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);\n\t\t\t\t\t\t\t\tthis.light.cookieTransform = this._cookieMatrix;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.light.cookieTransform = null;\n\t\t\t\t\t\t}\n\t\t\t\t}, true);\n\t\t}\n\t\tget cookieScale() {\n\t\t\t\treturn this.data.cookieScale;\n\t\t}\n\t\tset cookieOffset(arg) {\n\t\t\t\tthis._setValue('cookieOffset', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.cookieOffset = newValue;\n\t\t\t\t}, true);\n\t\t}\n\t\tget cookieOffset() {\n\t\t\t\treturn this.data.cookieOffset;\n\t\t}\n\t\tset shadowUpdateMode(arg) {\n\t\t\t\tthis._setValue('shadowUpdateMode', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.shadowUpdateMode = newValue;\n\t\t\t\t}, true);\n\t\t}\n\t\tget shadowUpdateMode() {\n\t\t\t\treturn this.data.shadowUpdateMode;\n\t\t}\n\t\tset mask(arg) {\n\t\t\t\tthis._setValue('mask', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.mask = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget mask() {\n\t\t\t\treturn this.data.mask;\n\t\t}\n\t\tset affectDynamic(arg) {\n\t\t\t\tthis._setValue('affectDynamic', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tif (newValue) {\n\t\t\t\t\t\t\t\tthis.light.mask |= MASK_AFFECT_DYNAMIC;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.light.mask &= -2;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.light.layersDirty();\n\t\t\t\t});\n\t\t}\n\t\tget affectDynamic() {\n\t\t\t\treturn this.data.affectDynamic;\n\t\t}\n\t\tset affectLightmapped(arg) {\n\t\t\t\tthis._setValue('affectLightmapped', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tif (newValue) {\n\t\t\t\t\t\t\t\tthis.light.mask |= MASK_AFFECT_LIGHTMAPPED;\n\t\t\t\t\t\t\t\tif (this.bake) this.light.mask &= -5;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.light.mask &= -3;\n\t\t\t\t\t\t\t\tif (this.bake) this.light.mask |= MASK_BAKE;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tget affectLightmapped() {\n\t\t\t\treturn this.data.affectLightmapped;\n\t\t}\n\t\tset bake(arg) {\n\t\t\t\tthis._setValue('bake', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tif (newValue) {\n\t\t\t\t\t\t\t\tthis.light.mask |= MASK_BAKE;\n\t\t\t\t\t\t\t\tif (this.affectLightmapped) this.light.mask &= -3;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.light.mask &= -5;\n\t\t\t\t\t\t\t\tif (this.affectLightmapped) this.light.mask |= MASK_AFFECT_LIGHTMAPPED;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.light.layersDirty();\n\t\t\t\t});\n\t\t}\n\t\tget bake() {\n\t\t\t\treturn this.data.bake;\n\t\t}\n\t\tset bakeDir(arg) {\n\t\t\t\tthis._setValue('bakeDir', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.bakeDir = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget bakeDir() {\n\t\t\t\treturn this.data.bakeDir;\n\t\t}\n\t\tset isStatic(arg) {\n\t\t\t\tthis._setValue('isStatic', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tthis.light.isStatic = newValue;\n\t\t\t\t});\n\t\t}\n\t\tget isStatic() {\n\t\t\t\treturn this.data.isStatic;\n\t\t}\n\t\tset layers(arg) {\n\t\t\t\tthis._setValue('layers', arg, function(newValue, oldValue) {\n\t\t\t\t\t\tfor(let i = 0; i < oldValue.length; i++){\n\t\t\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(oldValue[i]);\n\t\t\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\t\t\tlayer.removeLight(this);\n\t\t\t\t\t\t\t\tthis.light.removeLayer(layer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < newValue.length; i++){\n\t\t\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(newValue[i]);\n\t\t\t\t\t\t\t\tif (!layer) continue;\n\t\t\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\t\t\tlayer.addLight(this);\n\t\t\t\t\t\t\t\t\t\tthis.light.addLayer(layer);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this.data.layers;\n\t\t}\n\t\tset shadowUpdateOverrides(values) {\n\t\t\t\tthis.light.shadowUpdateOverrides = values;\n\t\t}\n\t\tget shadowUpdateOverrides() {\n\t\t\t\treturn this.light.shadowUpdateOverrides;\n\t\t}\n\t\tset shadowSamples(value) {\n\t\t\t\tthis.light.shadowSamples = value;\n\t\t}\n\t\tget shadowSamples() {\n\t\t\t\treturn this.light.shadowSamples;\n\t\t}\n\t\tset shadowBlockerSamples(value) {\n\t\t\t\tthis.light.shadowBlockerSamples = value;\n\t\t}\n\t\tget shadowBlockerSamples() {\n\t\t\t\treturn this.light.shadowBlockerSamples;\n\t\t}\n\t\tset penumbraSize(value) {\n\t\t\t\tthis.light.penumbraSize = value;\n\t\t}\n\t\tget penumbraSize() {\n\t\t\t\treturn this.light.penumbraSize;\n\t\t}\n\t\tset penumbraFalloff(value) {\n\t\t\t\tthis.light.penumbraFalloff = value;\n\t\t}\n\t\tget penumbraFalloff() {\n\t\t\t\treturn this.light.penumbraFalloff;\n\t\t}\n\t\t_setValue(name, value, setFunc, skipEqualsCheck) {\n\t\t\t\tconst data = this.data;\n\t\t\t\tconst oldValue = data[name];\n\t\t\t\tif (!skipEqualsCheck && oldValue === value) return;\n\t\t\t\tdata[name] = value;\n\t\t\t\tif (setFunc) setFunc.call(this, value, oldValue);\n\t\t}\n\t\taddLightToLayers() {\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.addLight(this);\n\t\t\t\t\t\t\t\tthis.light.addLayer(layer);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremoveLightFromLayers() {\n\t\t\t\tfor(let i = 0; i < this.layers.length; i++){\n\t\t\t\t\t\tconst layer = this.system.app.scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\tlayer.removeLight(this);\n\t\t\t\t\t\t\t\tthis.light.removeLayer(layer);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonLayersChanged(oldComp, newComp) {\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.addLightToLayers();\n\t\t\t\t}\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t}\n\t\tonLayerAdded(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index >= 0 && this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tlayer.addLight(this);\n\t\t\t\t\t\tthis.light.addLayer(layer);\n\t\t\t\t}\n\t\t}\n\t\tonLayerRemoved(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\tlayer.removeLight(this);\n\t\t\t\t\t\tthis.light.removeLayer(layer);\n\t\t\t\t}\n\t\t}\n\t\trefreshProperties() {\n\t\t\t\tfor(let i = 0; i < properties.length; i++){\n\t\t\t\t\t\tconst name = properties[i];\n\t\t\t\t\t\tthis[name] = this[name];\n\t\t\t\t}\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.onEnable();\n\t\t\t\t}\n\t\t}\n\t\tonCookieAssetSet() {\n\t\t\t\tlet forceLoad = false;\n\t\t\t\tif (this._cookieAsset.type === 'cubemap' && !this._cookieAsset.loadFaces) {\n\t\t\t\t\t\tthis._cookieAsset.loadFaces = true;\n\t\t\t\t\t\tforceLoad = true;\n\t\t\t\t}\n\t\t\t\tif (!this._cookieAsset.resource || forceLoad) this.system.app.assets.load(this._cookieAsset);\n\t\t\t\tif (this._cookieAsset.resource) {\n\t\t\t\t\t\tthis.onCookieAssetLoad();\n\t\t\t\t}\n\t\t}\n\t\tonCookieAssetAdd(asset) {\n\t\t\t\tif (this._cookieAssetId !== asset.id) return;\n\t\t\t\tthis._cookieAsset = asset;\n\t\t\t\tif (this.light.enabled) {\n\t\t\t\t\t\tthis.onCookieAssetSet();\n\t\t\t\t}\n\t\t\t\tthis._cookieAsset.on('load', this.onCookieAssetLoad, this);\n\t\t\t\tthis._cookieAsset.on('remove', this.onCookieAssetRemove, this);\n\t\t}\n\t\tonCookieAssetLoad() {\n\t\t\t\tif (!this._cookieAsset || !this._cookieAsset.resource) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.cookie = this._cookieAsset.resource;\n\t\t}\n\t\tonCookieAssetRemove() {\n\t\t\t\tif (!this._cookieAssetId) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._cookieAssetAdd) {\n\t\t\t\t\t\tthis.system.app.assets.off(`add:${this._cookieAssetId}`, this.onCookieAssetAdd, this);\n\t\t\t\t\t\tthis._cookieAssetAdd = false;\n\t\t\t\t}\n\t\t\t\tif (this._cookieAsset) {\n\t\t\t\t\t\tthis._cookieAsset.off('load', this.onCookieAssetLoad, this);\n\t\t\t\t\t\tthis._cookieAsset.off('remove', this.onCookieAssetRemove, this);\n\t\t\t\t\t\tthis._cookieAsset = null;\n\t\t\t\t}\n\t\t\t\tthis.cookie = null;\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis.light.enabled = true;\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this.onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.addLightToLayers();\n\t\t\t\t}\n\t\t\t\tif (this._cookieAsset && !this.cookie) {\n\t\t\t\t\t\tthis.onCookieAssetSet();\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis.light.enabled = false;\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tthis.removeLightFromLayers();\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.onDisable();\n\t\t\t\tthis.light.destroy();\n\t\t\t\tthis.cookieAsset = null;\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null, this._cookieAsset = null, this._cookieAssetId = null, this._cookieAssetAdd = false, this._cookieMatrix = null;\n\t\t}\n}\n\nclass LightComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'light';\n\t\t\t\tthis.ComponentType = LightComponent;\n\t\t\t\tthis.DataType = LightComponentData;\n\t\t\t\tthis.on('beforeremove', this._onRemoveComponent, this);\n\t\t}\n\t\tinitializeComponentData(component, _data) {\n\t\t\t\tconst data = {\n\t\t\t\t\t\t..._data\n\t\t\t\t};\n\t\t\t\tif (!data.type) {\n\t\t\t\t\t\tdata.type = component.data.type;\n\t\t\t\t}\n\t\t\t\tcomponent.data.type = data.type;\n\t\t\t\tif (data.layers && Array.isArray(data.layers)) {\n\t\t\t\t\t\tdata.layers = data.layers.slice(0);\n\t\t\t\t}\n\t\t\t\tif (data.color && Array.isArray(data.color)) {\n\t\t\t\t\t\tdata.color = new Color(data.color[0], data.color[1], data.color[2]);\n\t\t\t\t}\n\t\t\t\tif (data.cookieOffset && data.cookieOffset instanceof Array) {\n\t\t\t\t\t\tdata.cookieOffset = new Vec2(data.cookieOffset[0], data.cookieOffset[1]);\n\t\t\t\t}\n\t\t\t\tif (data.cookieScale && data.cookieScale instanceof Array) {\n\t\t\t\t\t\tdata.cookieScale = new Vec2(data.cookieScale[0], data.cookieScale[1]);\n\t\t\t\t}\n\t\t\t\tif (data.enable) {\n\t\t\t\t\t\tconsole.warn('WARNING: enable: Property is deprecated. Set enabled property instead.');\n\t\t\t\t\t\tdata.enabled = data.enable;\n\t\t\t\t}\n\t\t\t\tif (!data.shape) {\n\t\t\t\t\t\tdata.shape = LIGHTSHAPE_PUNCTUAL;\n\t\t\t\t}\n\t\t\t\tconst light = new Light(this.app.graphicsDevice, this.app.scene.clusteredLightingEnabled);\n\t\t\t\tlight.type = lightTypes[data.type];\n\t\t\t\tlight._node = component.entity;\n\t\t\t\tcomponent.data.light = light;\n\t\t\t\tsuper.initializeComponentData(component, data, properties);\n\t\t}\n\t\t_onRemoveComponent(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst light = entity.light;\n\t\t\t\tconst data = [];\n\t\t\t\tlet name;\n\t\t\t\tfor(let i = 0; i < properties.length; i++){\n\t\t\t\t\t\tname = properties[i];\n\t\t\t\t\t\tif (name === 'light') {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (light[name] && light[name].clone) {\n\t\t\t\t\t\t\t\tdata[name] = light[name].clone();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdata[name] = light[name];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this.addComponent(clone, data);\n\t\t}\n\t\tchangeType(component, oldValue, newValue) {\n\t\t\t\tif (oldValue !== newValue) {\n\t\t\t\t\t\tcomponent.light.type = lightTypes[newValue];\n\t\t\t\t}\n\t\t}\n}\n\nconst components = [\n\t\t'x',\n\t\t'y',\n\t\t'z',\n\t\t'w'\n];\nconst vecLookup = [\n\t\tundefined,\n\t\tundefined,\n\t\tVec2,\n\t\tVec3,\n\t\tVec4\n];\nfunction rawToValue(app, args, value, old) {\n\t\tswitch(args.type){\n\t\t\t\tcase 'boolean':\n\t\t\t\t\t\treturn !!value;\n\t\t\t\tcase 'number':\n\t\t\t\t\t\tif (typeof value === 'number') {\n\t\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t} else if (typeof value === 'string') {\n\t\t\t\t\t\t\t\tconst v = parseInt(value, 10);\n\t\t\t\t\t\t\t\tif (isNaN(v)) return null;\n\t\t\t\t\t\t\t\treturn v;\n\t\t\t\t\t\t} else if (typeof value === 'boolean') {\n\t\t\t\t\t\t\t\treturn 0 + value;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\tcase 'json':\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tconst result = {};\n\t\t\t\t\t\t\t\tif (Array.isArray(args.schema)) {\n\t\t\t\t\t\t\t\t\t\tif (!value || typeof value !== 'object') {\n\t\t\t\t\t\t\t\t\t\t\t\tvalue = {};\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < args.schema.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst field = args.schema[i];\n\t\t\t\t\t\t\t\t\t\t\t\tif (!field.name) continue;\n\t\t\t\t\t\t\t\t\t\t\t\tif (field.array) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult[field.name] = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst arr = Array.isArray(value[field.name]) ? value[field.name] : [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < arr.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult[field.name].push(rawToValue(app, field, arr[j]));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst val = value.hasOwnProperty(field.name) ? value[field.name] : field.default;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult[field.name] = rawToValue(app, field, val);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t}\n\t\t\t\tcase 'asset':\n\t\t\t\t\t\tif (value instanceof Asset) {\n\t\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t} else if (typeof value === 'number') {\n\t\t\t\t\t\t\t\treturn app.assets.get(value) || null;\n\t\t\t\t\t\t} else if (typeof value === 'string') {\n\t\t\t\t\t\t\t\treturn app.assets.get(parseInt(value, 10)) || null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\tcase 'entity':\n\t\t\t\t\t\tif (value instanceof GraphNode) {\n\t\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t} else if (typeof value === 'string') {\n\t\t\t\t\t\t\t\treturn app.getEntityFromIndex(value);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\tcase 'rgb':\n\t\t\t\tcase 'rgba':\n\t\t\t\t\t\tif (value instanceof Color) {\n\t\t\t\t\t\t\t\tif (old instanceof Color) {\n\t\t\t\t\t\t\t\t\t\told.copy(value);\n\t\t\t\t\t\t\t\t\t\treturn old;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn value.clone();\n\t\t\t\t\t\t} else if (value instanceof Array && value.length >= 3 && value.length <= 4) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < value.length; i++){\n\t\t\t\t\t\t\t\t\t\tif (typeof value[i] !== 'number') {\n\t\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!old) old = new Color();\n\t\t\t\t\t\t\t\told.r = value[0];\n\t\t\t\t\t\t\t\told.g = value[1];\n\t\t\t\t\t\t\t\told.b = value[2];\n\t\t\t\t\t\t\t\told.a = value.length === 3 ? 1 : value[3];\n\t\t\t\t\t\t\t\treturn old;\n\t\t\t\t\t\t} else if (typeof value === 'string' && /#(?:[0-9a-f]{2}){3,4}/i.test(value)) {\n\t\t\t\t\t\t\t\tif (!old) {\n\t\t\t\t\t\t\t\t\t\told = new Color();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\told.fromString(value);\n\t\t\t\t\t\t\t\treturn old;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\tcase 'vec2':\n\t\t\t\tcase 'vec3':\n\t\t\t\tcase 'vec4':\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tconst len = parseInt(args.type.slice(3), 10);\n\t\t\t\t\t\t\t\tconst vecType = vecLookup[len];\n\t\t\t\t\t\t\t\tif (value instanceof vecType) {\n\t\t\t\t\t\t\t\t\t\tif (old instanceof vecType) {\n\t\t\t\t\t\t\t\t\t\t\t\told.copy(value);\n\t\t\t\t\t\t\t\t\t\t\t\treturn old;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn value.clone();\n\t\t\t\t\t\t\t\t} else if (value instanceof Array && value.length === len) {\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < value.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tif (typeof value[i] !== 'number') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (!old) old = new vecType();\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\t\t\t\t\t\t\told[components[i]] = value[i];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn old;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\tcase 'curve':\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\tlet curve;\n\t\t\t\t\t\t\t\tif (value instanceof Curve || value instanceof CurveSet) {\n\t\t\t\t\t\t\t\t\t\tcurve = value.clone();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst CurveType = value.keys[0] instanceof Array ? CurveSet : Curve;\n\t\t\t\t\t\t\t\t\t\tcurve = new CurveType(value.keys);\n\t\t\t\t\t\t\t\t\t\tcurve.type = value.type;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn curve;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t}\n\t\treturn value;\n}\nfunction attributeToValue(app, schema, value, current) {\n\t\tif (schema.array) {\n\t\t\t\treturn value.map((item, index)=>rawToValue(app, schema, item, current ? current[index] : null));\n\t\t}\n\t\treturn rawToValue(app, schema, value, current);\n}\nfunction assignAttributesToScript(app, attributeSchemaMap, data, script) {\n\t\tif (!data) return;\n\t\tfor(const attributeName in attributeSchemaMap){\n\t\t\t\tconst attributeSchema = attributeSchemaMap[attributeName];\n\t\t\t\tconst dataToAssign = data[attributeName];\n\t\t\t\tif (dataToAssign === undefined) continue;\n\t\t\t\tscript[attributeName] = attributeToValue(app, attributeSchema, dataToAssign, script[attributeName]);\n\t\t}\n}\nclass ScriptAttributes {\n\t\tstatic{\n\t\t\t\tthis.assignAttributesToScript = assignAttributesToScript;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.attributeToValue = attributeToValue;\n\t\t}\n\t\tconstructor(scriptType){\n\t\t\t\tthis.scriptType = scriptType;\n\t\t\t\tthis.index = {};\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.reservedNames = new Set([\n\t\t\t\t\t\t'app',\n\t\t\t\t\t\t'entity',\n\t\t\t\t\t\t'enabled',\n\t\t\t\t\t\t'_enabled',\n\t\t\t\t\t\t'_enabledOld',\n\t\t\t\t\t\t'_destroyed',\n\t\t\t\t\t\t'__attributes',\n\t\t\t\t\t\t'__attributesRaw',\n\t\t\t\t\t\t\"__scriptType\",\n\t\t\t\t\t\t'__executionOrder',\n\t\t\t\t\t\t'_callbacks',\n\t\t\t\t\t\t'_callbackActive',\n\t\t\t\t\t\t'has',\n\t\t\t\t\t\t'get',\n\t\t\t\t\t\t'on',\n\t\t\t\t\t\t'off',\n\t\t\t\t\t\t'fire',\n\t\t\t\t\t\t'once',\n\t\t\t\t\t\t'hasEvent'\n\t\t\t\t]);\n\t\t}\n\t\tadd(name, args) {\n\t\t\t\tif (this.index[name]) {\n\t\t\t\t\t\treturn;\n\t\t\t\t} else if (ScriptAttributes.reservedNames.has(name)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.index[name] = args;\n\t\t\t\tObject.defineProperty(this.scriptType.prototype, name, {\n\t\t\t\t\t\tget: function() {\n\t\t\t\t\t\t\t\treturn this.__attributes[name];\n\t\t\t\t\t\t},\n\t\t\t\t\t\tset: function(raw) {\n\t\t\t\t\t\t\t\tconst evt = 'attr';\n\t\t\t\t\t\t\t\tconst evtName = `attr:${name}`;\n\t\t\t\t\t\t\t\tconst old = this.__attributes[name];\n\t\t\t\t\t\t\t\tlet oldCopy = old;\n\t\t\t\t\t\t\t\tif (old && args.type !== 'json' && args.type !== 'entity' && old.clone) {\n\t\t\t\t\t\t\t\t\t\tif (this.hasEvent(evt) || this.hasEvent(evtName)) {\n\t\t\t\t\t\t\t\t\t\t\t\toldCopy = old.clone();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (args.array) {\n\t\t\t\t\t\t\t\t\t\tthis.__attributes[name] = [];\n\t\t\t\t\t\t\t\t\t\tif (raw) {\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0, len = raw.length; i < len; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.__attributes[name].push(rawToValue(this.app, args, raw[i], old ? old[i] : null));\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.__attributes[name] = rawToValue(this.app, args, raw, old);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.fire(evt, name, this.__attributes[name], oldCopy);\n\t\t\t\t\t\t\t\tthis.fire(evtName, this.__attributes[name], oldCopy);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tremove(name) {\n\t\t\t\tif (!this.index[name]) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tdelete this.index[name];\n\t\t\t\tdelete this.scriptType.prototype[name];\n\t\t\t\treturn true;\n\t\t}\n\t\thas(name) {\n\t\t\t\treturn !!this.index[name];\n\t\t}\n\t\tget(name) {\n\t\t\t\treturn this.index[name] || null;\n\t\t}\n}\n\nconst SCRIPT_INITIALIZE = 'initialize';\nconst SCRIPT_POST_INITIALIZE = 'postInitialize';\nconst SCRIPT_UPDATE = 'update';\nconst SCRIPT_POST_UPDATE = 'postUpdate';\nconst SCRIPT_SWAP = 'swap';\n\nclass Script extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_ENABLE = 'enable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DISABLE = 'disable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STATE = 'state';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DESTROY = 'destroy';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ATTR = 'attr';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tconstructor(args){\n\t\t\t\tsuper();\n\t\t\t\tthis.initScript(args);\n\t\t}\n\t\tset enabled(value) {\n\t\t\t\tthis._enabled = !!value;\n\t\t\t\tif (this.enabled === this._enabledOld) return;\n\t\t\t\tthis._enabledOld = this.enabled;\n\t\t\t\tthis.fire(this.enabled ? 'enable' : 'disable');\n\t\t\t\tthis.fire('state', this.enabled);\n\t\t\t\tif (!this._initialized && this.enabled) {\n\t\t\t\t\t\tthis._initialized = true;\n\t\t\t\t\t\tthis.fire('preInitialize');\n\t\t\t\t\t\tif (this.initialize) {\n\t\t\t\t\t\t\t\tthis.entity.script._scriptMethod(this, SCRIPT_INITIALIZE);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._initialized && !this._postInitialized && this.enabled && !this.entity.script._beingEnabled) {\n\t\t\t\t\t\tthis._postInitialized = true;\n\t\t\t\t\t\tif (this.postInitialize) {\n\t\t\t\t\t\t\t\tthis.entity.script._scriptMethod(this, SCRIPT_POST_INITIALIZE);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled && !this._destroyed && this.entity.script.enabled && this.entity.enabled;\n\t\t}\n\t\tinitScript(args) {\n\t\t\t\tconst script = this.constructor;\n\t\t\t\tthis.app = args.app;\n\t\t\t\tthis.entity = args.entity;\n\t\t\t\tthis._enabled = typeof args.enabled === 'boolean' ? args.enabled : true;\n\t\t\t\tthis._enabledOld = this.enabled;\n\t\t\t\tthis.__destroyed = false;\n\t\t\t\tthis.__scriptType = script;\n\t\t\t\tthis.__executionOrder = -1;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.__name = null;\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.__getScriptName = getScriptName;\n\t\t}\n\t\tstatic get scriptName() {\n\t\t\t\treturn this.__name;\n\t\t}\n}\nconst funcNameRegex = /^\\s*function(?:\\s|\\s*\\/\\*.*\\*\\/\\s*)+([^(\\s\\/]*)\\s*/;\nfunction getScriptName(constructorFn) {\n\t\tif (typeof constructorFn !== 'function') return undefined;\n\t\tif (constructorFn.scriptName) return constructorFn.scriptName;\n\t\tif ('name' in Function.prototype) return constructorFn.name;\n\t\tif (constructorFn === Function || constructorFn === Function.prototype.constructor) return 'Function';\n\t\tconst match = `${constructorFn}`.match(funcNameRegex);\n\t\treturn match ? match[1] : undefined;\n}\n\nclass ScriptType extends Script {\n\t\tconstructor(args){\n\t\t\t\tsuper(args);\n\t\t\t\tthis.initScriptType(args);\n\t\t}\n\t\tstatic get attributes() {\n\t\t\t\tif (!this.hasOwnProperty('__attributes')) this.__attributes = new ScriptAttributes(this);\n\t\t\t\treturn this.__attributes;\n\t\t}\n\t\tinitScript(args) {\n\t\t\t\tScript.prototype.initScript.call(this, args);\n\t\t\t\tthis.__attributes = {};\n\t\t\t\tthis.__attributesRaw = args.attributes || {};\n\t\t}\n\t\tinitScriptType(args) {\n\t\t\t\tthis.initScript(args);\n\t\t}\n\t\t__initializeAttributes(force) {\n\t\t\t\tif (!force && !this.__attributesRaw) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor(const key in this.__scriptType.attributes.index){\n\t\t\t\t\t\tif (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tthis[key] = this.__attributesRaw[key];\n\t\t\t\t\t\t} else if (!this.__attributes.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tif (this.__scriptType.attributes.index[key].hasOwnProperty('default')) {\n\t\t\t\t\t\t\t\t\t\tthis[key] = this.__scriptType.attributes.index[key].default;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis[key] = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.__attributesRaw = null;\n\t\t}\n\t\tstatic extend(methods) {\n\t\t\t\tfor(const key in methods){\n\t\t\t\t\t\tif (!methods.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.prototype[key] = methods[key];\n\t\t\t\t}\n\t\t}\n}\n\nconst toLowerCamelCase$1 = (str)=>str[0].toLowerCase() + str.substring(1);\nclass ScriptComponent extends Component {\n\t\tstatic{\n\t\t\t\tthis.EVENT_CREATE = 'create';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DESTROY = 'destroy';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ENABLE = 'enable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DISABLE = 'disable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_STATE = 'state';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_MOVE = 'move';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._attributeDataMap = new Map();\n\t\t\t\tthis._scripts = [];\n\t\t\t\tthis._updateList = new SortedLoopArray({\n\t\t\t\t\t\tsortBy: '__executionOrder'\n\t\t\t\t});\n\t\t\t\tthis._postUpdateList = new SortedLoopArray({\n\t\t\t\t\t\tsortBy: '__executionOrder'\n\t\t\t\t});\n\t\t\t\tthis._scriptsIndex = {};\n\t\t\t\tthis._destroyedScripts = [];\n\t\t\t\tthis._destroyed = false;\n\t\t\t\tthis._scriptsData = null;\n\t\t\t\tthis._oldState = true;\n\t\t\t\tthis._enabled = true;\n\t\t\t\tthis._beingEnabled = false;\n\t\t\t\tthis._isLoopingThroughScripts = false;\n\t\t\t\tthis._executionOrder = -1;\n\t\t\t\tthis.on('set_enabled', this._onSetEnabled, this);\n\t\t}\n\t\tset scripts(value) {\n\t\t\t\tthis._scriptsData = value;\n\t\t\t\tfor(const key in value){\n\t\t\t\t\t\tif (!value.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst script = this._scriptsIndex[key];\n\t\t\t\t\t\tif (script) {\n\t\t\t\t\t\t\t\tif (typeof value[key].enabled === 'boolean') {\n\t\t\t\t\t\t\t\t\t\tscript.once('preInitialize', ()=>{\n\t\t\t\t\t\t\t\t\t\t\t\tthis.initializeAttributes(script);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tscript.enabled = !!value[key].enabled;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (typeof value[key].attributes === 'object') {\n\t\t\t\t\t\t\t\t\t\tfor(const attr in value[key].attributes){\n\t\t\t\t\t\t\t\t\t\t\t\tif (ScriptAttributes.reservedNames.has(attr)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (!script.__attributes.hasOwnProperty(attr)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst scriptType = this.system.app.scripts.get(key);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (scriptType) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tscriptType.attributes.add(attr, {});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tscript[attr] = value[key].attributes[attr];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.log(this.order);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget scripts() {\n\t\t\t\treturn this._scripts;\n\t\t}\n\t\tset enabled(value) {\n\t\t\t\tconst oldValue = this._enabled;\n\t\t\t\tthis._enabled = value;\n\t\t\t\tthis.fire('set', 'enabled', oldValue, value);\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled;\n\t\t}\n\t\tonEnable() {\n\t\t\t\tthis._beingEnabled = true;\n\t\t\t\tthis._checkState();\n\t\t\t\tif (!this.entity._beingEnabled) {\n\t\t\t\t\t\tthis.onPostStateChange();\n\t\t\t\t}\n\t\t\t\tthis._beingEnabled = false;\n\t\t}\n\t\tonDisable() {\n\t\t\t\tthis._checkState();\n\t\t}\n\t\tonPostStateChange() {\n\t\t\t\tconst wasLooping = this._beginLooping();\n\t\t\t\tfor(let i = 0, len = this.scripts.length; i < len; i++){\n\t\t\t\t\t\tconst script = this.scripts[i];\n\t\t\t\t\t\tif (script._initialized && !script._postInitialized && script.enabled) {\n\t\t\t\t\t\t\t\tscript._postInitialized = true;\n\t\t\t\t\t\t\t\tif (script.postInitialize) {\n\t\t\t\t\t\t\t\t\t\tthis._scriptMethod(script, SCRIPT_POST_INITIALIZE);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._endLooping(wasLooping);\n\t\t}\n\t\t_beginLooping() {\n\t\t\t\tconst looping = this._isLoopingThroughScripts;\n\t\t\t\tthis._isLoopingThroughScripts = true;\n\t\t\t\treturn looping;\n\t\t}\n\t\t_endLooping(wasLoopingBefore) {\n\t\t\t\tthis._isLoopingThroughScripts = wasLoopingBefore;\n\t\t\t\tif (!this._isLoopingThroughScripts) {\n\t\t\t\t\t\tthis._removeDestroyedScripts();\n\t\t\t\t}\n\t\t}\n\t\t_onSetEnabled(prop, old, value) {\n\t\t\t\tthis._beingEnabled = true;\n\t\t\t\tthis._checkState();\n\t\t\t\tthis._beingEnabled = false;\n\t\t}\n\t\t_checkState() {\n\t\t\t\tconst state = this.enabled && this.entity.enabled;\n\t\t\t\tif (state === this._oldState) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._oldState = state;\n\t\t\t\tthis.fire(state ? 'enable' : 'disable');\n\t\t\t\tthis.fire('state', state);\n\t\t\t\tif (state) {\n\t\t\t\t\t\tthis.system._addComponentToEnabled(this);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.system._removeComponentFromEnabled(this);\n\t\t\t\t}\n\t\t\t\tconst wasLooping = this._beginLooping();\n\t\t\t\tfor(let i = 0, len = this.scripts.length; i < len; i++){\n\t\t\t\t\t\tconst script = this.scripts[i];\n\t\t\t\t\t\tscript.once('preInitialize', ()=>{\n\t\t\t\t\t\t\t\tthis.initializeAttributes(script);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tscript.enabled = script._enabled;\n\t\t\t\t}\n\t\t\t\tthis._endLooping(wasLooping);\n\t\t}\n\t\t_onBeforeRemove() {\n\t\t\t\tthis.fire('remove');\n\t\t\t\tconst wasLooping = this._beginLooping();\n\t\t\t\tfor(let i = 0; i < this.scripts.length; i++){\n\t\t\t\t\t\tconst script = this.scripts[i];\n\t\t\t\t\t\tif (!script) continue;\n\t\t\t\t\t\tthis.destroy(script.__scriptType.__name);\n\t\t\t\t}\n\t\t\t\tthis._endLooping(wasLooping);\n\t\t}\n\t\t_removeDestroyedScripts() {\n\t\t\t\tconst len = this._destroyedScripts.length;\n\t\t\t\tif (!len) return;\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tconst script = this._destroyedScripts[i];\n\t\t\t\t\t\tthis._removeScriptInstance(script);\n\t\t\t\t}\n\t\t\t\tthis._destroyedScripts.length = 0;\n\t\t\t\tthis._resetExecutionOrder(0, this._scripts.length);\n\t\t}\n\t\t_onInitializeAttributes() {\n\t\t\t\tfor(let i = 0, len = this.scripts.length; i < len; i++){\n\t\t\t\t\t\tconst script = this.scripts[i];\n\t\t\t\t\t\tthis.initializeAttributes(script);\n\t\t\t\t}\n\t\t}\n\t\tinitializeAttributes(script) {\n\t\t\t\tif (script instanceof ScriptType) {\n\t\t\t\t\t\tscript.__initializeAttributes();\n\t\t\t\t} else {\n\t\t\t\t\t\tconst name = script.__scriptType.__name;\n\t\t\t\t\t\tconst data = this._attributeDataMap.get(name);\n\t\t\t\t\t\tif (!data) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst schema = this.system.app.scripts?.getSchema(name);\n\t\t\t\t\t\tassignAttributesToScript(this.system.app, schema.attributes, data, script);\n\t\t\t\t}\n\t\t}\n\t\t_scriptMethod(script, method, arg) {\n\t\t\t\tscript[method](arg);\n\t\t}\n\t\t_onInitialize() {\n\t\t\t\tconst scripts = this._scripts;\n\t\t\t\tconst wasLooping = this._beginLooping();\n\t\t\t\tfor(let i = 0, len = scripts.length; i < len; i++){\n\t\t\t\t\t\tconst script = scripts[i];\n\t\t\t\t\t\tif (!script._initialized && script.enabled) {\n\t\t\t\t\t\t\t\tscript._initialized = true;\n\t\t\t\t\t\t\t\tif (script.initialize) {\n\t\t\t\t\t\t\t\t\t\tthis._scriptMethod(script, SCRIPT_INITIALIZE);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._endLooping(wasLooping);\n\t\t}\n\t\t_onPostInitialize() {\n\t\t\t\tthis.onPostStateChange();\n\t\t}\n\t\t_onUpdate(dt) {\n\t\t\t\tconst list = this._updateList;\n\t\t\t\tif (!list.length) return;\n\t\t\t\tconst wasLooping = this._beginLooping();\n\t\t\t\tfor(list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++){\n\t\t\t\t\t\tconst script = list.items[list.loopIndex];\n\t\t\t\t\t\tif (script.enabled) {\n\t\t\t\t\t\t\t\tthis._scriptMethod(script, SCRIPT_UPDATE, dt);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._endLooping(wasLooping);\n\t\t}\n\t\t_onPostUpdate(dt) {\n\t\t\t\tconst list = this._postUpdateList;\n\t\t\t\tif (!list.length) return;\n\t\t\t\tconst wasLooping = this._beginLooping();\n\t\t\t\tfor(list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++){\n\t\t\t\t\t\tconst script = list.items[list.loopIndex];\n\t\t\t\t\t\tif (script.enabled) {\n\t\t\t\t\t\t\t\tthis._scriptMethod(script, SCRIPT_POST_UPDATE, dt);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._endLooping(wasLooping);\n\t\t}\n\t\t_insertScriptInstance(scriptInstance, index, scriptsLength) {\n\t\t\t\tif (index === -1) {\n\t\t\t\t\t\tthis._scripts.push(scriptInstance);\n\t\t\t\t\t\tscriptInstance.__executionOrder = scriptsLength;\n\t\t\t\t\t\tif (scriptInstance.update) {\n\t\t\t\t\t\t\t\tthis._updateList.append(scriptInstance);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (scriptInstance.postUpdate) {\n\t\t\t\t\t\t\t\tthis._postUpdateList.append(scriptInstance);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._scripts.splice(index, 0, scriptInstance);\n\t\t\t\t\t\tscriptInstance.__executionOrder = index;\n\t\t\t\t\t\tthis._resetExecutionOrder(index + 1, scriptsLength + 1);\n\t\t\t\t\t\tif (scriptInstance.update) {\n\t\t\t\t\t\t\t\tthis._updateList.insert(scriptInstance);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (scriptInstance.postUpdate) {\n\t\t\t\t\t\t\t\tthis._postUpdateList.insert(scriptInstance);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_removeScriptInstance(scriptInstance) {\n\t\t\t\tconst idx = this._scripts.indexOf(scriptInstance);\n\t\t\t\tif (idx === -1) return idx;\n\t\t\t\tthis._scripts.splice(idx, 1);\n\t\t\t\tif (scriptInstance.update) {\n\t\t\t\t\t\tthis._updateList.remove(scriptInstance);\n\t\t\t\t}\n\t\t\t\tif (scriptInstance.postUpdate) {\n\t\t\t\t\t\tthis._postUpdateList.remove(scriptInstance);\n\t\t\t\t}\n\t\t\t\treturn idx;\n\t\t}\n\t\t_resetExecutionOrder(startIndex, scriptsLength) {\n\t\t\t\tfor(let i = startIndex; i < scriptsLength; i++){\n\t\t\t\t\t\tthis._scripts[i].__executionOrder = i;\n\t\t\t\t}\n\t\t}\n\t\t_resolveEntityScriptAttribute(attribute, attributeName, oldValue, useGuid, newAttributes, duplicatedIdsMap) {\n\t\t\t\tif (attribute.array) {\n\t\t\t\t\t\tconst len = oldValue.length;\n\t\t\t\t\t\tif (!len) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst newGuidArray = oldValue.slice();\n\t\t\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\t\t\tconst guid = newGuidArray[i] instanceof Entity ? newGuidArray[i].getGuid() : newGuidArray[i];\n\t\t\t\t\t\t\t\tif (duplicatedIdsMap[guid]) {\n\t\t\t\t\t\t\t\t\t\tnewGuidArray[i] = useGuid ? duplicatedIdsMap[guid].getGuid() : duplicatedIdsMap[guid];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnewAttributes[attributeName] = newGuidArray;\n\t\t\t\t} else {\n\t\t\t\t\t\tif (oldValue instanceof Entity) {\n\t\t\t\t\t\t\t\toldValue = oldValue.getGuid();\n\t\t\t\t\t\t} else if (typeof oldValue !== 'string') {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (duplicatedIdsMap[oldValue]) {\n\t\t\t\t\t\t\t\tnewAttributes[attributeName] = duplicatedIdsMap[oldValue];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\thas(nameOrType) {\n\t\t\t\tif (typeof nameOrType === 'string') {\n\t\t\t\t\t\treturn !!this._scriptsIndex[nameOrType];\n\t\t\t\t}\n\t\t\t\tif (!nameOrType) return false;\n\t\t\t\tconst scriptType = nameOrType;\n\t\t\t\tconst scriptName = scriptType.__name;\n\t\t\t\tconst scriptData = this._scriptsIndex[scriptName];\n\t\t\t\tconst scriptInstance = scriptData && scriptData.instance;\n\t\t\t\treturn scriptInstance instanceof scriptType;\n\t\t}\n\t\tget(nameOrType) {\n\t\t\t\tif (typeof nameOrType === 'string') {\n\t\t\t\t\t\tconst data = this._scriptsIndex[nameOrType];\n\t\t\t\t\t\treturn data ? data.instance : null;\n\t\t\t\t}\n\t\t\t\tif (!nameOrType) return null;\n\t\t\t\tconst scriptType = nameOrType;\n\t\t\t\tconst scriptName = scriptType.__name;\n\t\t\t\tconst scriptData = this._scriptsIndex[scriptName];\n\t\t\t\tconst scriptInstance = scriptData && scriptData.instance;\n\t\t\t\treturn scriptInstance instanceof scriptType ? scriptInstance : null;\n\t\t}\n\t\tcreate(nameOrType, args = {}) {\n\t\t\t\tconst self = this;\n\t\t\t\tlet scriptType = nameOrType;\n\t\t\t\tlet scriptName = nameOrType;\n\t\t\t\tif (typeof scriptType === 'string') {\n\t\t\t\t\t\tscriptType = this.system.app.scripts.get(scriptType);\n\t\t\t\t} else if (scriptType) {\n\t\t\t\t\t\tconst inferredScriptName = getScriptName(scriptType);\n\t\t\t\t\t\tconst lowerInferredScriptName = toLowerCamelCase$1(inferredScriptName);\n\t\t\t\t\t\tif (!(scriptType.prototype instanceof ScriptType) && !scriptType.scriptName) ;\n\t\t\t\t\t\tscriptType.__name ??= scriptType.scriptName ?? lowerInferredScriptName;\n\t\t\t\t\t\tscriptName = scriptType.__name;\n\t\t\t\t}\n\t\t\t\tif (scriptType) {\n\t\t\t\t\t\tif (!this._scriptsIndex[scriptName] || !this._scriptsIndex[scriptName].instance) {\n\t\t\t\t\t\t\t\tconst scriptInstance = new scriptType({\n\t\t\t\t\t\t\t\t\t\tapp: this.system.app,\n\t\t\t\t\t\t\t\t\t\tentity: this.entity,\n\t\t\t\t\t\t\t\t\t\tenabled: args.hasOwnProperty('enabled') ? args.enabled : true,\n\t\t\t\t\t\t\t\t\t\tattributes: args.attributes || {}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (args.properties && typeof args.properties === 'object') {\n\t\t\t\t\t\t\t\t\t\tObject.assign(scriptInstance, args.properties);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!(scriptInstance instanceof ScriptType) && args.attributes) {\n\t\t\t\t\t\t\t\t\t\tthis._attributeDataMap.set(scriptName, {\n\t\t\t\t\t\t\t\t\t\t\t\t...args.attributes\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst len = this._scripts.length;\n\t\t\t\t\t\t\t\tlet ind = -1;\n\t\t\t\t\t\t\t\tif (typeof args.ind === 'number' && args.ind !== -1 && len > args.ind) {\n\t\t\t\t\t\t\t\t\t\tind = args.ind;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._insertScriptInstance(scriptInstance, ind, len);\n\t\t\t\t\t\t\t\tthis._scriptsIndex[scriptName] = {\n\t\t\t\t\t\t\t\t\t\tinstance: scriptInstance,\n\t\t\t\t\t\t\t\t\t\tonSwap: function() {\n\t\t\t\t\t\t\t\t\t\t\t\tself.swap(scriptName);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tthis[scriptName] = scriptInstance;\n\t\t\t\t\t\t\t\tif (!args.preloading) {\n\t\t\t\t\t\t\t\t\t\tthis.initializeAttributes(scriptInstance);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.fire('create', scriptName, scriptInstance);\n\t\t\t\t\t\t\t\tthis.fire(`create:${scriptName}`, scriptInstance);\n\t\t\t\t\t\t\t\tthis.system.app.scripts.on(`swap:${scriptName}`, this._scriptsIndex[scriptName].onSwap);\n\t\t\t\t\t\t\t\tif (!args.preloading) {\n\t\t\t\t\t\t\t\t\t\tif (scriptInstance.enabled && !scriptInstance._initialized) {\n\t\t\t\t\t\t\t\t\t\t\t\tscriptInstance._initialized = true;\n\t\t\t\t\t\t\t\t\t\t\t\tif (scriptInstance.initialize) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._scriptMethod(scriptInstance, SCRIPT_INITIALIZE);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (scriptInstance.enabled && !scriptInstance._postInitialized) {\n\t\t\t\t\t\t\t\t\t\t\t\tscriptInstance._postInitialized = true;\n\t\t\t\t\t\t\t\t\t\t\t\tif (scriptInstance.postInitialize) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._scriptMethod(scriptInstance, SCRIPT_POST_INITIALIZE);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn scriptInstance;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._scriptsIndex[scriptName] = {\n\t\t\t\t\t\t\t\tawaiting: true,\n\t\t\t\t\t\t\t\tind: this._scripts.length\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\tdestroy(nameOrType) {\n\t\t\t\tlet scriptName = nameOrType;\n\t\t\t\tlet scriptType = nameOrType;\n\t\t\t\tif (typeof scriptType === 'string') {\n\t\t\t\t\t\tscriptType = this.system.app.scripts.get(scriptType);\n\t\t\t\t} else if (scriptType) {\n\t\t\t\t\t\tscriptName = scriptType.__name;\n\t\t\t\t}\n\t\t\t\tconst scriptData = this._scriptsIndex[scriptName];\n\t\t\t\tdelete this._scriptsIndex[scriptName];\n\t\t\t\tif (!scriptData) return false;\n\t\t\t\tthis._attributeDataMap.delete(scriptName);\n\t\t\t\tconst scriptInstance = scriptData.instance;\n\t\t\t\tif (scriptInstance && !scriptInstance._destroyed) {\n\t\t\t\t\t\tscriptInstance.enabled = false;\n\t\t\t\t\t\tscriptInstance._destroyed = true;\n\t\t\t\t\t\tif (!this._isLoopingThroughScripts) {\n\t\t\t\t\t\t\t\tconst ind = this._removeScriptInstance(scriptInstance);\n\t\t\t\t\t\t\t\tif (ind >= 0) {\n\t\t\t\t\t\t\t\t\t\tthis._resetExecutionOrder(ind, this._scripts.length);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._destroyedScripts.push(scriptInstance);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.system.app.scripts.off(`swap:${scriptName}`, scriptData.onSwap);\n\t\t\t\tdelete this[scriptName];\n\t\t\t\tthis.fire('destroy', scriptName, scriptInstance || null);\n\t\t\t\tthis.fire(`destroy:${scriptName}`, scriptInstance || null);\n\t\t\t\tif (scriptInstance) {\n\t\t\t\t\t\tscriptInstance.fire('destroy');\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tswap(nameOrType) {\n\t\t\t\tlet scriptName = nameOrType;\n\t\t\t\tlet scriptType = nameOrType;\n\t\t\t\tif (typeof scriptType === 'string') {\n\t\t\t\t\t\tscriptType = this.system.app.scripts.get(scriptType);\n\t\t\t\t} else if (scriptType) {\n\t\t\t\t\t\tscriptName = scriptType.__name;\n\t\t\t\t}\n\t\t\t\tconst old = this._scriptsIndex[scriptName];\n\t\t\t\tif (!old || !old.instance) return false;\n\t\t\t\tconst scriptInstanceOld = old.instance;\n\t\t\t\tconst ind = this._scripts.indexOf(scriptInstanceOld);\n\t\t\t\tconst scriptInstance = new scriptType({\n\t\t\t\t\t\tapp: this.system.app,\n\t\t\t\t\t\tentity: this.entity,\n\t\t\t\t\t\tenabled: scriptInstanceOld.enabled,\n\t\t\t\t\t\tattributes: scriptInstanceOld.__attributes\n\t\t\t\t});\n\t\t\t\tif (!scriptInstance.swap) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis.initializeAttributes(scriptInstance);\n\t\t\t\tthis._scripts[ind] = scriptInstance;\n\t\t\t\tthis._scriptsIndex[scriptName].instance = scriptInstance;\n\t\t\t\tthis[scriptName] = scriptInstance;\n\t\t\t\tscriptInstance.__executionOrder = ind;\n\t\t\t\tif (scriptInstanceOld.update) {\n\t\t\t\t\t\tthis._updateList.remove(scriptInstanceOld);\n\t\t\t\t}\n\t\t\t\tif (scriptInstanceOld.postUpdate) {\n\t\t\t\t\t\tthis._postUpdateList.remove(scriptInstanceOld);\n\t\t\t\t}\n\t\t\t\tif (scriptInstance.update) {\n\t\t\t\t\t\tthis._updateList.insert(scriptInstance);\n\t\t\t\t}\n\t\t\t\tif (scriptInstance.postUpdate) {\n\t\t\t\t\t\tthis._postUpdateList.insert(scriptInstance);\n\t\t\t\t}\n\t\t\t\tthis._scriptMethod(scriptInstance, SCRIPT_SWAP, scriptInstanceOld);\n\t\t\t\tthis.fire('swap', scriptName, scriptInstance);\n\t\t\t\tthis.fire(`swap:${scriptName}`, scriptInstance);\n\t\t\t\treturn true;\n\t\t}\n\t\tresolveDuplicatedEntityReferenceProperties(oldScriptComponent, duplicatedIdsMap) {\n\t\t\t\tconst newScriptComponent = this.entity.script;\n\t\t\t\tfor(const scriptName in oldScriptComponent._scriptsIndex){\n\t\t\t\t\t\tconst scriptType = this.system.app.scripts.get(scriptName);\n\t\t\t\t\t\tif (!scriptType) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst script = oldScriptComponent._scriptsIndex[scriptName];\n\t\t\t\t\t\tif (!script || !script.instance) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst newAttributesRaw = newScriptComponent[scriptName].__attributesRaw ?? newScriptComponent._attributeDataMap.get(scriptName);\n\t\t\t\t\t\tconst newAttributes = newScriptComponent[scriptName].__attributes;\n\t\t\t\t\t\tif (!newAttributesRaw && !newAttributes) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst useGuid = !!newAttributesRaw;\n\t\t\t\t\t\tconst oldAttributes = script.instance.__attributes ?? newScriptComponent._attributeDataMap.get(scriptName);\n\t\t\t\t\t\tfor(const attributeName in oldAttributes){\n\t\t\t\t\t\t\t\tif (!oldAttributes[attributeName]) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst attribute = scriptType.attributes?.get(attributeName) ?? this.system.app.scripts.getSchema(scriptName)?.attributes?.[attributeName];\n\t\t\t\t\t\t\t\tif (!attribute) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (attribute.type === 'entity') {\n\t\t\t\t\t\t\t\t\t\tthis._resolveEntityScriptAttribute(attribute, attributeName, oldAttributes[attributeName], useGuid, newAttributesRaw || newAttributes, duplicatedIdsMap);\n\t\t\t\t\t\t\t\t} else if (attribute.type === 'json' && Array.isArray(attribute.schema)) {\n\t\t\t\t\t\t\t\t\t\tconst oldValue = oldAttributes[attributeName];\n\t\t\t\t\t\t\t\t\t\tconst newJsonValue = newAttributesRaw ? newAttributesRaw[attributeName] : newAttributes[attributeName];\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < attribute.schema.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst field = attribute.schema[i];\n\t\t\t\t\t\t\t\t\t\t\t\tif (field.type !== 'entity') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (attribute.array) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < oldValue.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._resolveEntityScriptAttribute(field, field.name, oldValue[j][field.name], useGuid, newJsonValue[j], duplicatedIdsMap);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._resolveEntityScriptAttribute(field, field.name, oldValue[field.name], useGuid, newJsonValue, duplicatedIdsMap);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tmove(nameOrType, ind) {\n\t\t\t\tconst len = this._scripts.length;\n\t\t\t\tif (ind >= len || ind < 0) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tlet scriptType = nameOrType;\n\t\t\t\tlet scriptName = nameOrType;\n\t\t\t\tif (typeof scriptName !== 'string') {\n\t\t\t\t\t\tscriptName = nameOrType.__name;\n\t\t\t\t} else {\n\t\t\t\t\t\tscriptType = null;\n\t\t\t\t}\n\t\t\t\tconst scriptData = this._scriptsIndex[scriptName];\n\t\t\t\tif (!scriptData || !scriptData.instance) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst scriptInstance = scriptData.instance;\n\t\t\t\tif (scriptType && !(scriptInstance instanceof scriptType)) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst indOld = this._scripts.indexOf(scriptInstance);\n\t\t\t\tif (indOld === -1 || indOld === ind) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis._scripts.splice(ind, 0, this._scripts.splice(indOld, 1)[0]);\n\t\t\t\tthis._resetExecutionOrder(0, len);\n\t\t\t\tthis._updateList.sort();\n\t\t\t\tthis._postUpdateList.sort();\n\t\t\t\tthis.fire('move', scriptName, scriptInstance, ind, indOld);\n\t\t\t\tthis.fire(`move:${scriptName}`, scriptInstance, ind, indOld);\n\t\t\t\treturn true;\n\t\t}\n}\n\nclass ScriptComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nconst METHOD_INITIALIZE_ATTRIBUTES = '_onInitializeAttributes';\nconst METHOD_INITIALIZE = '_onInitialize';\nconst METHOD_POST_INITIALIZE = '_onPostInitialize';\nconst METHOD_UPDATE = '_onUpdate';\nconst METHOD_POST_UPDATE = '_onPostUpdate';\nlet executionOrderCounter = 0;\nclass ScriptComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = \"script\";\n\t\t\t\tthis.ComponentType = ScriptComponent;\n\t\t\t\tthis.DataType = ScriptComponentData;\n\t\t\t\tthis._components = new SortedLoopArray({\n\t\t\t\t\t\tsortBy: '_executionOrder'\n\t\t\t\t});\n\t\t\t\tthis._enabledComponents = new SortedLoopArray({\n\t\t\t\t\t\tsortBy: '_executionOrder'\n\t\t\t\t});\n\t\t\t\tthis.preloading = true;\n\t\t\t\tthis.on('beforeremove', this._onBeforeRemove, this);\n\t\t\t\tthis.app.systems.on('initialize', this._onInitialize, this);\n\t\t\t\tthis.app.systems.on('postInitialize', this._onPostInitialize, this);\n\t\t\t\tthis.app.systems.on('update', this._onUpdate, this);\n\t\t\t\tthis.app.systems.on('postUpdate', this._onPostUpdate, this);\n\t\t}\n\t\tinitializeComponentData(component, data) {\n\t\t\t\tcomponent._executionOrder = executionOrderCounter++;\n\t\t\t\tthis._components.append(component);\n\t\t\t\tif (executionOrderCounter > Number.MAX_SAFE_INTEGER) {\n\t\t\t\t\t\tthis._resetExecutionOrder();\n\t\t\t\t}\n\t\t\t\tcomponent.enabled = data.hasOwnProperty('enabled') ? !!data.enabled : true;\n\t\t\t\tif (component.enabled && component.entity.enabled) {\n\t\t\t\t\t\tthis._enabledComponents.append(component);\n\t\t\t\t}\n\t\t\t\tif (data.hasOwnProperty('order') && data.hasOwnProperty(\"scripts\")) {\n\t\t\t\t\t\tcomponent._scriptsData = data.scripts;\n\t\t\t\t\t\tfor(let i = 0; i < data.order.length; i++){\n\t\t\t\t\t\t\t\tcomponent.create(data.order[i], {\n\t\t\t\t\t\t\t\t\t\tenabled: data.scripts[data.order[i]].enabled,\n\t\t\t\t\t\t\t\t\t\tattributes: data.scripts[data.order[i]].attributes,\n\t\t\t\t\t\t\t\t\t\tpreloading: this.preloading\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst order = [];\n\t\t\t\tconst scripts = {};\n\t\t\t\tfor(let i = 0; i < entity.script._scripts.length; i++){\n\t\t\t\t\t\tconst scriptInstance = entity.script._scripts[i];\n\t\t\t\t\t\tconst scriptName = scriptInstance.__scriptType.__name;\n\t\t\t\t\t\torder.push(scriptName);\n\t\t\t\t\t\tconst attributes = entity.script._attributeDataMap?.get(scriptName) || {};\n\t\t\t\t\t\tfor(const key in scriptInstance.__attributes){\n\t\t\t\t\t\t\t\tattributes[key] = scriptInstance.__attributes[key];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscripts[scriptName] = {\n\t\t\t\t\t\t\t\tenabled: scriptInstance._enabled,\n\t\t\t\t\t\t\t\tattributes: attributes\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tfor(const key in entity.script._scriptsIndex){\n\t\t\t\t\t\tif (key.awaiting) {\n\t\t\t\t\t\t\t\torder.splice(key.ind, 0, key);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst data = {\n\t\t\t\t\t\tenabled: entity.script.enabled,\n\t\t\t\t\t\torder: order,\n\t\t\t\t\t\tscripts: scripts\n\t\t\t\t};\n\t\t\t\treturn this.addComponent(clone, data);\n\t\t}\n\t\t_resetExecutionOrder() {\n\t\t\t\texecutionOrderCounter = 0;\n\t\t\t\tfor(let i = 0, len = this._components.length; i < len; i++){\n\t\t\t\t\t\tthis._components.items[i]._executionOrder = executionOrderCounter++;\n\t\t\t\t}\n\t\t}\n\t\t_callComponentMethod(components, name, dt) {\n\t\t\t\tfor(components.loopIndex = 0; components.loopIndex < components.length; components.loopIndex++){\n\t\t\t\t\t\tcomponents.items[components.loopIndex][name](dt);\n\t\t\t\t}\n\t\t}\n\t\t_onInitialize() {\n\t\t\t\tthis.preloading = false;\n\t\t\t\tthis._callComponentMethod(this._components, METHOD_INITIALIZE_ATTRIBUTES);\n\t\t\t\tthis._callComponentMethod(this._enabledComponents, METHOD_INITIALIZE);\n\t\t}\n\t\t_onPostInitialize() {\n\t\t\t\tthis._callComponentMethod(this._enabledComponents, METHOD_POST_INITIALIZE);\n\t\t}\n\t\t_onUpdate(dt) {\n\t\t\t\tthis._callComponentMethod(this._enabledComponents, METHOD_UPDATE, dt);\n\t\t}\n\t\t_onPostUpdate(dt) {\n\t\t\t\tthis._callComponentMethod(this._enabledComponents, METHOD_POST_UPDATE, dt);\n\t\t}\n\t\t_addComponentToEnabled(component) {\n\t\t\t\tthis._enabledComponents.insert(component);\n\t\t}\n\t\t_removeComponentFromEnabled(component) {\n\t\t\t\tthis._enabledComponents.remove(component);\n\t\t}\n\t\t_onBeforeRemove(entity, component) {\n\t\t\t\tconst ind = this._components.items.indexOf(component);\n\t\t\t\tif (ind >= 0) {\n\t\t\t\t\t\tcomponent._onBeforeRemove();\n\t\t\t\t}\n\t\t\t\tthis._removeComponentFromEnabled(component);\n\t\t\t\tthis._components.remove(component);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tsuper.destroy();\n\t\t\t\tthis.app.systems.off('initialize', this._onInitialize, this);\n\t\t\t\tthis.app.systems.off('postInitialize', this._onPostInitialize, this);\n\t\t\t\tthis.app.systems.off('update', this._onUpdate, this);\n\t\t\t\tthis.app.systems.off('postUpdate', this._onPostUpdate, this);\n\t\t}\n}\n\nvar gsplatIntervalTextureGLSL = `\nprecision highp usampler2D;\nuniform usampler2D uIntervalsTexture;\nuniform int uNumIntervals;\nuniform int uTextureWidth;\nuniform int uActiveSplats;\nivec2 getCoordFromIndex(int index, int textureWidth) {\n\treturn ivec2(index % textureWidth, index / textureWidth);\n}\nvoid main() {\n\tivec2 coord = ivec2(gl_FragCoord.xy);\n\tint targetIndex = coord.y * uTextureWidth + coord.x;\n\t\n\tif (targetIndex >= uActiveSplats) {\n\t\tgl_FragColor = 0u;\n\t\treturn;\n\t}\n\t\n\tint left = 0;\n\tint right = uNumIntervals - 1;\n\tint intervalIndex = 0;\n\t\n\twhile (left <= right) {\n\t\tint mid = (left + right) / 2;\n\t\t\n\t\tint textureWidth = textureSize(uIntervalsTexture, 0).x;\n\t\tivec2 intervalCoord = getCoordFromIndex(mid, textureWidth);\n\t\tuvec2 intervalData = texelFetch(uIntervalsTexture, intervalCoord, 0).rg;\n\t\t\n\t\tuint accumulatedSum = intervalData.g;\n\t\t\n\t\tif (uint(targetIndex) < accumulatedSum) {\n\t\t\tintervalIndex = mid;\n\t\t\tright = mid - 1;\n\t\t} else {\n\t\t\tleft = mid + 1;\n\t\t}\n\t}\n\t\n\tint textureWidth = textureSize(uIntervalsTexture, 0).x;\n\tivec2 intervalCoord = getCoordFromIndex(intervalIndex, textureWidth);\n\tuvec2 intervalData = texelFetch(uIntervalsTexture, intervalCoord, 0).rg;\n\t\n\tuint intervalStart = intervalData.r;\n\tuint currentAccSum = intervalData.g;\n\t\n\tuint prevAccSum = 0u;\n\tif (intervalIndex > 0) {\n\t\tivec2 prevCoord = getCoordFromIndex(intervalIndex - 1, textureWidth);\n\t\tprevAccSum = texelFetch(uIntervalsTexture, prevCoord, 0).g;\n\t}\n\t\n\tuint offsetInInterval = uint(targetIndex) - prevAccSum;\n\tuint originalIndex = intervalStart + offsetInInterval;\n\t\n\tgl_FragColor = originalIndex;\n}\n`;\n\nvar gsplatIntervalTextureWGSL = `\nvar uIntervalsTexture: texture_2d<u32>;\nuniform uNumIntervals: i32;\nuniform uTextureWidth: i32;\nuniform uActiveSplats: i32;\nfn getCoordFromIndex(index: i32, textureWidth: i32) -> vec2i {\n\treturn vec2i(index % textureWidth, index / textureWidth);\n}\n@fragment\nfn fragmentMain(input: FragmentInput) -> FragmentOutput {\n\tvar output: FragmentOutput;\n\t\n\tlet coord = vec2i(i32(input.position.x), i32(input.position.y));\n\tlet targetIndex = coord.y * uniform.uTextureWidth + coord.x;\n\t\n\tif (targetIndex >= uniform.uActiveSplats) {\n\t\toutput.color = 0u;\n\t\treturn output;\n\t}\n\t\n\tvar left = 0i;\n\tvar right = uniform.uNumIntervals - 1;\n\tvar intervalIndex = 0i;\n\t\n\twhile (left <= right) {\n\t\tlet mid = (left + right) / 2;\n\t\t\n\t\tlet textureWidth = i32(textureDimensions(uIntervalsTexture, 0).x);\n\t\tlet intervalCoord = getCoordFromIndex(mid, textureWidth);\n\t\tlet intervalData = textureLoad(uIntervalsTexture, intervalCoord, 0).rg;\n\t\t\n\t\tlet accumulatedSum = intervalData.g;\n\t\t\n\t\tif (u32(targetIndex) < accumulatedSum) {\n\t\t\tintervalIndex = mid;\n\t\t\tright = mid - 1;\n\t\t} else {\n\t\t\tleft = mid + 1;\n\t\t}\n\t}\n\t\n\tlet textureWidth = i32(textureDimensions(uIntervalsTexture, 0).x);\n\tlet intervalCoord = getCoordFromIndex(intervalIndex, textureWidth);\n\tlet intervalData = textureLoad(uIntervalsTexture, intervalCoord, 0).rg;\n\t\n\tlet intervalStart = intervalData.r;\n\tlet currentAccSum = intervalData.g;\n\t\n\tvar prevAccSum = 0u;\n\tif (intervalIndex > 0) {\n\t\tlet prevCoord = getCoordFromIndex(intervalIndex - 1, textureWidth);\n\t\tprevAccSum = textureLoad(uIntervalsTexture, prevCoord, 0).g;\n\t}\n\t\n\tlet offsetInInterval = u32(targetIndex) - prevAccSum;\n\tlet originalIndex = intervalStart + offsetInInterval;\n\t\n\toutput.color = originalIndex;\n\treturn output;\n}\n`;\n\nclass GSplatIntervalTexture {\n\t\tconstructor(device){\n\t\t\t\tthis.texture = null;\n\t\t\t\tthis.rt = null;\n\t\t\t\tthis.intervalsDataTexture = null;\n\t\t\t\tthis.shader = null;\n\t\t\t\tthis.device = device;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.texture?.destroy();\n\t\t\t\tthis.texture = null;\n\t\t\t\tthis.rt?.destroy();\n\t\t\t\tthis.rt = null;\n\t\t\t\tthis.intervalsDataTexture?.destroy();\n\t\t\t\tthis.intervalsDataTexture = null;\n\t\t\t\tthis.shader = null;\n\t\t}\n\t\tgetShader() {\n\t\t\t\tif (!this.shader) {\n\t\t\t\t\t\tthis.shader = ShaderUtils.createShader(this.device, {\n\t\t\t\t\t\t\t\tuniqueName: 'GSplatIntervalsShader',\n\t\t\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\t\t\taPosition: SEMANTIC_POSITION\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvertexChunk: 'quadVS',\n\t\t\t\t\t\t\t\tfragmentGLSL: gsplatIntervalTextureGLSL,\n\t\t\t\t\t\t\t\tfragmentWGSL: gsplatIntervalTextureWGSL,\n\t\t\t\t\t\t\t\tfragmentOutputTypes: [\n\t\t\t\t\t\t\t\t\t\t'uint'\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this.shader;\n\t\t}\n\t\tcreateTexture(name, format, width, height) {\n\t\t\t\treturn new Texture(this.device, {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: false,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE\n\t\t\t\t});\n\t\t}\n\t\tupdate(intervals) {\n\t\t\t\tif (!intervals || intervals.length === 0) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tlet totalSplats = 0;\n\t\t\t\tfor(let i = 0; i < intervals.length; i += 2){\n\t\t\t\t\t\tconst start = intervals[i];\n\t\t\t\t\t\tconst end = intervals[i + 1];\n\t\t\t\t\t\ttotalSplats += end - start;\n\t\t\t\t}\n\t\t\t\tconst maxTextureSize = this.device.maxTextureSize;\n\t\t\t\tlet textureWidth = Math.ceil(Math.sqrt(totalSplats));\n\t\t\t\ttextureWidth = Math.min(textureWidth, maxTextureSize);\n\t\t\t\tconst textureHeight = Math.ceil(totalSplats / textureWidth);\n\t\t\t\tif (!this.texture) {\n\t\t\t\t\t\tthis.texture = this.createTexture('intervalsTexture', PIXELFORMAT_R32U, textureWidth, textureHeight);\n\t\t\t\t}\n\t\t\t\tif (!this.rt) {\n\t\t\t\t\t\tthis.rt = new RenderTarget({\n\t\t\t\t\t\t\t\tcolorBuffer: this.texture,\n\t\t\t\t\t\t\t\tdepth: false\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (this.rt.width !== textureWidth || this.rt.height !== textureHeight) {\n\t\t\t\t\t\tthis.rt.resize(textureWidth, textureHeight);\n\t\t\t\t}\n\t\t\t\tconst numIntervals = intervals.length / 2;\n\t\t\t\tconst dataTextureSize = Math.ceil(Math.sqrt(numIntervals));\n\t\t\t\tif (!this.intervalsDataTexture) {\n\t\t\t\t\t\tthis.intervalsDataTexture = this.createTexture('intervalsData', PIXELFORMAT_RG32U, dataTextureSize, dataTextureSize);\n\t\t\t\t}\n\t\t\t\tif (this.intervalsDataTexture.width !== dataTextureSize) {\n\t\t\t\t\t\tthis.intervalsDataTexture.resize(dataTextureSize, dataTextureSize);\n\t\t\t\t}\n\t\t\t\tconst intervalsData = this.intervalsDataTexture.lock();\n\t\t\t\tlet runningSum = 0;\n\t\t\t\tfor(let i = 0; i < numIntervals; i++){\n\t\t\t\t\t\tconst start = intervals[i * 2];\n\t\t\t\t\t\tconst end = intervals[i * 2 + 1];\n\t\t\t\t\t\tconst intervalSize = end - start;\n\t\t\t\t\t\trunningSum += intervalSize;\n\t\t\t\t\t\tintervalsData[i * 2] = start;\n\t\t\t\t\t\tintervalsData[i * 2 + 1] = runningSum;\n\t\t\t\t}\n\t\t\t\tthis.intervalsDataTexture.unlock();\n\t\t\t\tconst scope = this.device.scope;\n\t\t\t\tscope.resolve('uIntervalsTexture').setValue(this.intervalsDataTexture);\n\t\t\t\tscope.resolve('uNumIntervals').setValue(numIntervals);\n\t\t\t\tscope.resolve('uTextureWidth').setValue(textureWidth);\n\t\t\t\tscope.resolve('uActiveSplats').setValue(totalSplats);\n\t\t\t\tthis.device.setCullMode(CULLFACE_NONE);\n\t\t\t\tthis.device.setBlendState(BlendState.NOBLEND);\n\t\t\t\tthis.device.setDepthState(DepthState.NODEPTH);\n\t\t\t\tdrawQuadWithShader(this.device, this.rt, this.getShader());\n\t\t\t\treturn totalSplats;\n\t\t}\n}\n\nclass GSplatInfo {\n\t\tconstructor(device, resource, placement){\n\t\t\t\tthis.activeSplats = 0;\n\t\t\t\tthis.intervals = [];\n\t\t\t\tthis.lineStart = 0;\n\t\t\t\tthis.lineCount = 0;\n\t\t\t\tthis.padding = 0;\n\t\t\t\tthis.viewport = new Vec4();\n\t\t\t\tthis.previousWorldTransform = new Mat4();\n\t\t\t\tthis.aabb = new BoundingBox();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.resource = resource;\n\t\t\t\tthis.node = placement.node;\n\t\t\t\tthis.lodIndex = placement.lodIndex;\n\t\t\t\tthis.numSplats = resource.centers.length / 3;\n\t\t\t\tthis.aabb.copy(placement.aabb);\n\t\t\t\tthis.intervalTexture = new GSplatIntervalTexture(device);\n\t\t\t\tthis.updateIntervals(placement.intervals);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.intervals.length = 0;\n\t\t\t\tthis.intervalTexture.destroy();\n\t\t}\n\t\tsetLines(start, count, textureSize, activeSplats) {\n\t\t\t\tthis.lineStart = start;\n\t\t\t\tthis.lineCount = count;\n\t\t\t\tthis.padding = textureSize * count - activeSplats;\n\t\t\t\tthis.viewport.set(0, start, textureSize, count);\n\t\t}\n\t\tupdateIntervals(intervals) {\n\t\t\t\tconst resource = this.resource;\n\t\t\t\tthis.intervals.length = 0;\n\t\t\t\tthis.activeSplats = resource.numSplats;\n\t\t\t\tif (intervals.size > 0) {\n\t\t\t\t\t\tfor (const interval of intervals.values()){\n\t\t\t\t\t\t\t\tthis.intervals.push(interval.x, interval.y + 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.activeSplats = this.intervalTexture.update(this.intervals);\n\t\t\t\t}\n\t\t}\n\t\tupdate() {\n\t\t\t\tconst worldMatrix = this.node.getWorldTransform();\n\t\t\t\tconst worldMatrixChanged = !this.previousWorldTransform.equals(worldMatrix);\n\t\t\t\tif (worldMatrixChanged) {\n\t\t\t\t\t\tthis.previousWorldTransform.copy(worldMatrix);\n\t\t\t\t}\n\t\t\t\treturn worldMatrixChanged;\n\t\t}\n}\n\nfunction UnifiedSortWorker() {\n\t\tconst myself = typeof self !== 'undefined' && self || require('node:worker_threads').parentPort;\n\t\tconst centersMap = new Map();\n\t\tlet centersData;\n\t\tlet distances;\n\t\tlet countBuffer;\n\t\tconst evaluateSortKeys = (sortParams, minDist, divider, distances, countBuffer, centersData, bucketCount)=>{\n\t\t\t\tconst { ids, lineStarts, padding, intervals, textureSize } = centersData;\n\t\t\t\tfor(let paramIdx = 0; paramIdx < sortParams.length; paramIdx++){\n\t\t\t\t\t\tconst params = sortParams[paramIdx];\n\t\t\t\t\t\tconst { transformedDirection, offset, scale } = params;\n\t\t\t\t\t\tconst dx = transformedDirection.x;\n\t\t\t\t\t\tconst dy = transformedDirection.y;\n\t\t\t\t\t\tconst dz = transformedDirection.z;\n\t\t\t\t\t\tconst sdx = dx * scale * divider;\n\t\t\t\t\t\tconst sdy = dy * scale * divider;\n\t\t\t\t\t\tconst sdz = dz * scale * divider;\n\t\t\t\t\t\tconst add = (offset - minDist) * divider;\n\t\t\t\t\t\tconst id = ids[paramIdx];\n\t\t\t\t\t\tconst centers = centersMap.get(id);\n\t\t\t\t\t\tif (!centers) {\n\t\t\t\t\t\t\t\tconsole.error('UnifiedSortWorker: No centers found for id', id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet targetIndex = lineStarts[paramIdx] * textureSize;\n\t\t\t\t\t\tconst intervalsArray = intervals[paramIdx].length > 0 ? intervals[paramIdx] : [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tcenters.length / 3\n\t\t\t\t\t\t];\n\t\t\t\t\t\tfor(let i = 0; i < intervalsArray.length; i += 2){\n\t\t\t\t\t\t\t\tconst intervalStart = intervalsArray[i] * 3;\n\t\t\t\t\t\t\t\tconst intervalEnd = intervalsArray[i + 1] * 3;\n\t\t\t\t\t\t\t\tfor(let srcIndex = intervalStart; srcIndex < intervalEnd; srcIndex += 3){\n\t\t\t\t\t\t\t\t\t\tconst x = centers[srcIndex];\n\t\t\t\t\t\t\t\t\t\tconst y = centers[srcIndex + 1];\n\t\t\t\t\t\t\t\t\t\tconst z = centers[srcIndex + 2];\n\t\t\t\t\t\t\t\t\t\tconst sortKey = Math.floor(x * sdx + y * sdy + z * sdz + add);\n\t\t\t\t\t\t\t\t\t\tdistances[targetIndex++] = sortKey;\n\t\t\t\t\t\t\t\t\t\tcountBuffer[sortKey]++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst pad = padding[paramIdx];\n\t\t\t\t\t\tcountBuffer[0] += pad;\n\t\t\t\t\t\tdistances.fill(0, targetIndex, targetIndex + pad);\n\t\t\t\t\t\ttargetIndex += pad;\n\t\t\t\t}\n\t\t};\n\t\tconst countingSort = (bucketCount, countBuffer, numVertices, distances, order)=>{\n\t\t\t\tfor(let i = 1; i < bucketCount; i++){\n\t\t\t\t\t\tcountBuffer[i] += countBuffer[i - 1];\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < numVertices; i++){\n\t\t\t\t\t\tconst distance = distances[i];\n\t\t\t\t\t\tconst destIndex = --countBuffer[distance];\n\t\t\t\t\t\torder[destIndex] = i;\n\t\t\t\t}\n\t\t};\n\t\tconst computeEffectiveDistanceRange = (sortParams)=>{\n\t\t\t\tlet minDist = Infinity;\n\t\t\t\tlet maxDist = -Infinity;\n\t\t\t\tfor(let paramIdx = 0; paramIdx < sortParams.length; paramIdx++){\n\t\t\t\t\t\tconst params = sortParams[paramIdx];\n\t\t\t\t\t\tconst { transformedDirection, offset, scale, aabbMin, aabbMax } = params;\n\t\t\t\t\t\tconst dx = transformedDirection.x;\n\t\t\t\t\t\tconst dy = transformedDirection.y;\n\t\t\t\t\t\tconst dz = transformedDirection.z;\n\t\t\t\t\t\tconst pxMin = dx >= 0 ? aabbMin[0] : aabbMax[0];\n\t\t\t\t\t\tconst pyMin = dy >= 0 ? aabbMin[1] : aabbMax[1];\n\t\t\t\t\t\tconst pzMin = dz >= 0 ? aabbMin[2] : aabbMax[2];\n\t\t\t\t\t\tconst pxMax = dx >= 0 ? aabbMax[0] : aabbMin[0];\n\t\t\t\t\t\tconst pyMax = dy >= 0 ? aabbMax[1] : aabbMin[1];\n\t\t\t\t\t\tconst pzMax = dz >= 0 ? aabbMax[2] : aabbMin[2];\n\t\t\t\t\t\tconst dMin = pxMin * dx + pyMin * dy + pzMin * dz;\n\t\t\t\t\t\tconst dMax = pxMax * dx + pyMax * dy + pzMax * dz;\n\t\t\t\t\t\tconst eMin = dMin * scale + offset;\n\t\t\t\t\t\tconst eMax = dMax * scale + offset;\n\t\t\t\t\t\tconst localMin = Math.min(eMin, eMax);\n\t\t\t\t\t\tconst localMax = Math.max(eMin, eMax);\n\t\t\t\t\t\tif (localMin < minDist) minDist = localMin;\n\t\t\t\t\t\tif (localMax > maxDist) maxDist = localMax;\n\t\t\t\t}\n\t\t\t\tif (minDist === Infinity) {\n\t\t\t\t\t\tminDist = 0;\n\t\t\t\t\t\tmaxDist = 0;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tminDist,\n\t\t\t\t\t\tmaxDist\n\t\t\t\t};\n\t\t};\n\t\tconst sort = (sortParams, order, centersData)=>{\n\t\t\t\tconst { minDist, maxDist } = computeEffectiveDistanceRange(sortParams);\n\t\t\t\tconst numVertices = centersData.totalUsedPixels;\n\t\t\t\tconst compareBits = Math.max(10, Math.min(20, Math.round(Math.log2(numVertices / 4))));\n\t\t\t\tconst bucketCount = 2 ** compareBits + 1;\n\t\t\t\tif (distances?.length !== numVertices) {\n\t\t\t\t\t\tdistances = new Uint32Array(numVertices);\n\t\t\t\t}\n\t\t\t\tif (!countBuffer || countBuffer.length !== bucketCount) {\n\t\t\t\t\t\tcountBuffer = new Uint32Array(bucketCount);\n\t\t\t\t} else {\n\t\t\t\t\t\tcountBuffer.fill(0);\n\t\t\t\t}\n\t\t\t\tconst range = maxDist - minDist;\n\t\t\t\tconst divider = range < 1e-6 ? 0 : 1 / range * 2 ** compareBits;\n\t\t\t\tevaluateSortKeys(sortParams, minDist, divider, distances, countBuffer, centersData);\n\t\t\t\tcountingSort(bucketCount, countBuffer, numVertices, distances, order);\n\t\t\t\tconst count = numVertices;\n\t\t\t\tconst transferList = [\n\t\t\t\t\t\torder.buffer\n\t\t\t\t];\n\t\t\t\tconst response = {\n\t\t\t\t\t\torder: order.buffer,\n\t\t\t\t\t\tcount,\n\t\t\t\t\t\tversion: centersData.version\n\t\t\t\t};\n\t\t\t\tmyself.postMessage(response, transferList);\n\t\t};\n\t\tmyself.addEventListener('message', (message)=>{\n\t\t\t\tconst msgData = message.data ?? message;\n\t\t\t\tswitch(msgData.command){\n\t\t\t\t\t\tcase 'addCenters':\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tcentersMap.set(msgData.id, new Float32Array(msgData.centers));\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'removeCenters':\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tcentersMap.delete(msgData.id);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'sort':\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tconst order = new Uint32Array(msgData.order);\n\t\t\t\t\t\t\t\t\t\tsort(msgData.sortParams, order, centersData);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'intervals':\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tcentersData = msgData;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t});\n}\n\nclass GSplatUnifiedSorter extends EventHandler {\n\t\tconstructor(){\n\t\t\t\tsuper(), this.bufferLength = 0, this.availableOrderData = [], this.jobsInFlight = 0, this.hasNewVersion = false, this.centersSet = new Set();\n\t\t\t\tconst workerSource = `(${UnifiedSortWorker.toString()})()`;\n\t\t\t\tif (platform.environment === 'node') {\n\t\t\t\t\t\tthis.worker = new Worker(workerSource, {\n\t\t\t\t\t\t\t\teval: true\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.worker.on('message', this.onSorted.bind(this));\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.worker = new Worker(URL.createObjectURL(new Blob([\n\t\t\t\t\t\t\t\tworkerSource\n\t\t\t\t\t\t], {\n\t\t\t\t\t\t\t\ttype: \"application/javascript\"\n\t\t\t\t\t\t})));\n\t\t\t\t\t\tthis.worker.addEventListener('message', this.onSorted.bind(this));\n\t\t\t\t}\n\t\t}\n\t\tonSorted(message) {\n\t\t\t\tconst msgData = message.data ?? message;\n\t\t\t\tconst orderData = new Uint32Array(msgData.order);\n\t\t\t\tthis.jobsInFlight--;\n\t\t\t\tthis.fire('sorted', msgData.count, msgData.version, orderData);\n\t\t\t\tif (orderData.length === this.bufferLength) {\n\t\t\t\t\t\tthis.availableOrderData.push(orderData);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.worker.terminate();\n\t\t\t\tthis.worker = null;\n\t\t}\n\t\tsetCenters(id, centers) {\n\t\t\t\tif (centers) {\n\t\t\t\t\t\tif (!this.centersSet.has(id)) {\n\t\t\t\t\t\t\t\tthis.centersSet.add(id);\n\t\t\t\t\t\t\t\tconst centersBuffer = centers.buffer.slice();\n\t\t\t\t\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\t\t\t\t\tcommand: 'addCenters',\n\t\t\t\t\t\t\t\t\t\tid: id,\n\t\t\t\t\t\t\t\t\t\tcenters: centersBuffer\n\t\t\t\t\t\t\t\t}, [\n\t\t\t\t\t\t\t\t\t\tcentersBuffer\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this.centersSet.has(id)) {\n\t\t\t\t\t\t\t\tthis.centersSet.delete(id);\n\t\t\t\t\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\t\t\t\t\tcommand: 'removeCenters',\n\t\t\t\t\t\t\t\t\t\tid: id\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsetSortParameters(payload) {\n\t\t\t\tthis.hasNewVersion = true;\n\t\t\t\tconst { textureSize } = payload;\n\t\t\t\tconst newLength = textureSize * textureSize;\n\t\t\t\tif (newLength !== this.bufferLength) {\n\t\t\t\t\t\tthis.bufferLength = newLength;\n\t\t\t\t\t\tthis.availableOrderData.length = 0;\n\t\t\t\t}\n\t\t\t\tthis.worker.postMessage(payload);\n\t\t}\n\t\tsetSortParams(params) {\n\t\t\t\tif (this.hasNewVersion || this.jobsInFlight === 0) {\n\t\t\t\t\t\tlet orderData = this.availableOrderData.pop();\n\t\t\t\t\t\tif (!orderData) {\n\t\t\t\t\t\t\t\torderData = new Uint32Array(this.bufferLength);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.jobsInFlight++;\n\t\t\t\t\t\tthis.hasNewVersion = false;\n\t\t\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\t\t\tcommand: 'sort',\n\t\t\t\t\t\t\t\tsortParams: params,\n\t\t\t\t\t\t\t\torder: orderData.buffer\n\t\t\t\t\t\t}, [\n\t\t\t\t\t\t\t\torderData.buffer\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n}\n\nconst _viewMat = new Mat4();\nconst colorizeLod = false;\nclass GSplatWorkBufferRenderPass extends RenderPass {\n\t\tinit(renderTarget) {\n\t\t\t\tsuper.init(renderTarget);\n\t\t\t\tthis.colorOps.clear = false;\n\t\t\t\tthis.depthStencilOps.clearDepth = false;\n\t\t}\n\t\tupdate(splats, cameraNode) {\n\t\t\t\tthis.splats.length = 0;\n\t\t\t\tfor(let i = 0; i < splats.length; i++){\n\t\t\t\t\t\tconst splatInfo = splats[i];\n\t\t\t\t\t\tif (splatInfo.activeSplats > 0) {\n\t\t\t\t\t\t\t\tthis.splats.push(splatInfo);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.cameraNode = cameraNode;\n\t\t\t\treturn this.splats.length > 0;\n\t\t}\n\t\texecute() {\n\t\t\t\tconst { device, splats, cameraNode } = this;\n\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\tdevice.setCullMode(CULLFACE_NONE);\n\t\t\t\tdevice.setDepthState(DepthState.NODEPTH);\n\t\t\t\tdevice.setStencilState();\n\t\t\t\tconst viewInvMat = cameraNode.getWorldTransform();\n\t\t\t\tconst viewMat = _viewMat.copy(viewInvMat).invert();\n\t\t\t\tdevice.scope.resolve('matrix_view').setValue(viewMat.data);\n\t\t\t\tfor(let i = 0; i < splats.length; i++){\n\t\t\t\t\t\tthis.renderSplat(splats[i]);\n\t\t\t\t}\n\t\t}\n\t\trenderSplat(splatInfo) {\n\t\t\t\tconst { device, resource } = splatInfo;\n\t\t\t\tconst scope = device.scope;\n\t\t\t\tconst { intervals, activeSplats, lineStart, viewport, intervalTexture } = splatInfo;\n\t\t\t\tconst workBufferRenderInfo = resource.getWorkBufferRenderInfo(intervals.length > 0, colorizeLod);\n\t\t\t\tworkBufferRenderInfo.material.setParameters(device);\n\t\t\t\tscope.resolve('uTransform').setValue(splatInfo.node.getWorldTransform().data);\n\t\t\t\tif (intervalTexture.texture) {\n\t\t\t\t\t\tscope.resolve('uIntervalsTexture').setValue(intervalTexture.texture);\n\t\t\t\t}\n\t\t\t\tscope.resolve('uActiveSplats').setValue(activeSplats);\n\t\t\t\tscope.resolve('uStartLine').setValue(lineStart);\n\t\t\t\tscope.resolve('uViewportWidth').setValue(viewport.z);\n\t\t\t\tscope.resolve('matrix_model').setValue(splatInfo.node.getWorldTransform().data);\n\t\t\t\tworkBufferRenderInfo.quadRender.render(viewport);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.splats.length = 0;\n\t\t\t\tsuper.destroy();\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this.splats = [];\n\t\t}\n}\n\nlet id = 0;\nclass GSplatWorkBuffer {\n\t\tconstructor(device){\n\t\t\t\tthis.id = id++;\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.colorTexture = this.createTexture('splatColor', PIXELFORMAT_RGBA16F, 1, 1);\n\t\t\t\tthis.covATexture = this.createTexture('covA', PIXELFORMAT_RGBA16F, 1, 1);\n\t\t\t\tthis.covBTexture = this.createTexture('covB', PIXELFORMAT_RGBA16F, 1, 1);\n\t\t\t\tthis.centerTexture = this.createTexture('center', PIXELFORMAT_RGBA16F, 1, 1);\n\t\t\t\tthis.renderTarget = new RenderTarget({\n\t\t\t\t\t\tname: `GsplatWorkBuffer-MRT-${this.id}`,\n\t\t\t\t\t\tcolorBuffers: [\n\t\t\t\t\t\t\t\tthis.colorTexture,\n\t\t\t\t\t\t\t\tthis.centerTexture,\n\t\t\t\t\t\t\t\tthis.covATexture,\n\t\t\t\t\t\t\t\tthis.covBTexture\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdepth: false,\n\t\t\t\t\t\tflipY: true\n\t\t\t\t});\n\t\t\t\tthis.orderTexture = this.createTexture('SplatGlobalOrder', PIXELFORMAT_R32U, 1, 1);\n\t\t\t\tthis.renderPass = new GSplatWorkBufferRenderPass(device);\n\t\t\t\tthis.renderPass.init(this.renderTarget);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.renderPass?.destroy();\n\t\t\t\tthis.colorTexture?.destroy();\n\t\t\t\tthis.covATexture?.destroy();\n\t\t\t\tthis.covBTexture?.destroy();\n\t\t\t\tthis.centerTexture?.destroy();\n\t\t\t\tthis.orderTexture?.destroy();\n\t\t\t\tthis.renderTarget?.destroy();\n\t\t}\n\t\tget textureSize() {\n\t\t\t\treturn this.orderTexture.width;\n\t\t}\n\t\tsetOrderData(data) {\n\t\t\t\tconst len = this.orderTexture.width * this.orderTexture.height;\n\t\t\t\tif (len !== data.length) ;\n\t\t\t\tthis.orderTexture._levels[0] = data;\n\t\t\t\tthis.orderTexture.upload();\n\t\t}\n\t\tcreateTexture(name, format, w, h) {\n\t\t\t\treturn new Texture(this.device, {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\twidth: w,\n\t\t\t\t\t\theight: h,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: false,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE\n\t\t\t\t});\n\t\t}\n\t\tresize(textureSize) {\n\t\t\t\tthis.renderTarget.resize(textureSize, textureSize);\n\t\t\t\tthis.orderTexture.resize(textureSize, textureSize);\n\t\t}\n\t\trender(splats, cameraNode) {\n\t\t\t\tif (this.renderPass.update(splats, cameraNode)) {\n\t\t\t\t\t\tthis.renderPass.render();\n\t\t\t\t}\n\t\t}\n}\n\nclass GSplatRenderer {\n\t\tconstructor(device, node, cameraNode, layer, workBuffer){\n\t\t\t\tthis.maxNumSplats = 0;\n\t\t\t\tthis.instanceIndices = null;\n\t\t\t\tthis.viewportParams = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.node = node;\n\t\t\t\tthis.cameraNode = cameraNode;\n\t\t\t\tthis.layer = layer;\n\t\t\t\tthis.workBuffer = workBuffer;\n\t\t\t\tthis._material = new ShaderMaterial({\n\t\t\t\t\t\tuniqueName: 'UnifiedSplatMaterial',\n\t\t\t\t\t\tvertexGLSL: '#include \"gsplatVS\"',\n\t\t\t\t\t\tfragmentGLSL: '#include \"gsplatPS\"',\n\t\t\t\t\t\tvertexWGSL: '#include \"gsplatVS\"',\n\t\t\t\t\t\tfragmentWGSL: '#include \"gsplatPS\"',\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION,\n\t\t\t\t\t\t\t\tvertex_id_attrib: SEMANTIC_ATTR13\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis._material.setDefine('GSPLAT_WORKBUFFER_DATA', true);\n\t\t\t\tthis._material.setParameter('splatColor', workBuffer.colorTexture);\n\t\t\t\tthis._material.setParameter('covA', workBuffer.covATexture);\n\t\t\t\tthis._material.setParameter('covB', workBuffer.covBTexture);\n\t\t\t\tthis._material.setParameter('center', workBuffer.centerTexture);\n\t\t\t\tthis._material.setDefine('SH_BANDS', '0');\n\t\t\t\tthis._material.setParameter('numSplats', 0);\n\t\t\t\tthis._material.setParameter('splatOrder', workBuffer.orderTexture);\n\t\t\t\tthis._material.setParameter('alphaClip', 0.3);\n\t\t\t\tthis._material.setDefine(`DITHER_${'NONE'}`, '');\n\t\t\t\tthis._material.cull = CULLFACE_NONE;\n\t\t\t\tthis._material.blendType = BLEND_PREMULTIPLIED;\n\t\t\t\tthis._material.depthWrite = false;\n\t\t\t\tthis._material.update();\n\t\t\t\tthis.meshInstance = this.createMeshInstance();\n\t\t\t\tlayer.addMeshInstances([\n\t\t\t\t\t\tthis.meshInstance\n\t\t\t\t]);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.layer.removeMeshInstances([\n\t\t\t\t\t\tthis.meshInstance\n\t\t\t\t]);\n\t\t\t\tthis._material.destroy();\n\t\t\t\tthis.meshInstance.destroy();\n\t\t}\n\t\tsetNumSplats(count) {\n\t\t\t\tthis.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize);\n\t\t\t\tthis._material.setParameter('numSplats', count);\n\t\t\t\tthis.meshInstance.visible = count > 0;\n\t\t}\n\t\tsetMaxNumSplats(numSplats) {\n\t\t\t\tif (this.maxNumSplats !== numSplats) {\n\t\t\t\t\t\tthis.maxNumSplats = numSplats;\n\t\t\t\t\t\tthis.instanceIndices?.destroy();\n\t\t\t\t\t\tthis.instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, numSplats);\n\t\t\t\t\t\tthis.meshInstance.setInstancing(this.instanceIndices, true);\n\t\t\t\t}\n\t\t}\n\t\tcreateMeshInstance() {\n\t\t\t\tconst mesh = GSplatResourceBase.createMesh(this.device);\n\t\t\t\tconst textureSize = this.workBuffer.textureSize;\n\t\t\t\tconst instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, textureSize * textureSize);\n\t\t\t\tconst meshInstance = new MeshInstance(mesh, this._material);\n\t\t\t\tmeshInstance.node = this.node;\n\t\t\t\tmeshInstance.setInstancing(instanceIndices, true);\n\t\t\t\tmeshInstance.instancingCount = 0;\n\t\t\t\tconst thisCamera = this.cameraNode.camera;\n\t\t\t\tmeshInstance.isVisibleFunc = (camera)=>{\n\t\t\t\t\t\tconst vis = thisCamera.camera === camera;\n\t\t\t\t\t\treturn vis;\n\t\t\t\t};\n\t\t\t\treturn meshInstance;\n\t\t}\n\t\tupdateViewport(cameraNode) {\n\t\t\t\tconst camera = cameraNode.camera;\n\t\t\t\tconst cameraRect = camera.rect;\n\t\t\t\tconst renderTarget = camera?.renderTarget;\n\t\t\t\tconst { width, height } = renderTarget ?? this.device;\n\t\t\t\tconst viewport = this.viewportParams;\n\t\t\t\tviewport[0] = width * cameraRect.z;\n\t\t\t\tviewport[1] = height * cameraRect.w;\n\t\t\t\tconst xr = camera?.camera?.xr;\n\t\t\t\tif (xr?.active && xr.views.list.length === 2) {\n\t\t\t\t\t\tviewport[0] *= 0.5;\n\t\t\t\t}\n\t\t\t\tthis._material.setParameter('viewport', viewport);\n\t\t}\n}\n\nclass GSplatPlacement {\n\t\tconstructor(resource, node, lodIndex = 0){\n\t\t\t\tthis.intervals = new Map();\n\t\t\t\tthis.lodIndex = 0;\n\t\t\t\tthis._aabb = new BoundingBox();\n\t\t\t\tthis.resource = resource;\n\t\t\t\tthis.node = node;\n\t\t\t\tthis.lodIndex = lodIndex;\n\t\t}\n\t\tset aabb(aabb) {\n\t\t\t\tthis._aabb.copy(aabb);\n\t\t}\n\t\tget aabb() {\n\t\t\t\treturn this._aabb;\n\t\t}\n}\n\nconst _invWorldMat = new Mat4();\nconst _localCameraPos = new Vec3();\nconst _tempCompletedUrls = [];\nclass GSplatOctreeInstance {\n\t\tconstructor(octree, placement, assetLoader){\n\t\t\t\tthis.activePlacements = new Set();\n\t\t\t\tthis.dirtyModifiedPlacements = false;\n\t\t\t\tthis.pending = new Set();\n\t\t\t\tthis.pendingDecrements = new Map();\n\t\t\t\tthis.previousPosition = new Vec3();\n\t\t\t\tthis.octree = octree;\n\t\t\t\tthis.placement = placement;\n\t\t\t\tthis.assetLoader = assetLoader;\n\t\t\t\tthis.nodeLods = new Int32Array(octree.nodes.length);\n\t\t\t\tthis.nodeLods.fill(-1);\n\t\t\t\tconst numFiles = octree.files.length;\n\t\t\t\tthis.filePlacements = new Array(numFiles).fill(null);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.pending.clear();\n\t\t\t\tthis.pendingDecrements.clear();\n\t\t\t\tthis.filePlacements.length = 0;\n\t\t}\n\t\tcalculateNodeLod(localCameraPosition, nodeIndex, maxLod) {\n\t\t\t\tconst node = this.octree.nodes[nodeIndex];\n\t\t\t\tconst distance = localCameraPosition.distance(node.bounds.center);\n\t\t\t\tconst lodDistances = [\n\t\t\t\t\t\t2,\n\t\t\t\t\t\t4,\n\t\t\t\t\t\t6,\n\t\t\t\t\t\t8,\n\t\t\t\t\t\t10\n\t\t\t\t];\n\t\t\t\tfor(let lod = 0; lod < maxLod; lod++){\n\t\t\t\t\t\tif (distance < lodDistances[lod]) {\n\t\t\t\t\t\t\t\treturn lod;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn maxLod;\n\t\t}\n\t\tupdateLod(cameraNode) {\n\t\t\t\tconst worldCameraPosition = cameraNode.getPosition();\n\t\t\t\tconst octreeWorldTransform = this.placement.node.getWorldTransform();\n\t\t\t\t_invWorldMat.copy(octreeWorldTransform).invert();\n\t\t\t\tconst localCameraPosition = _invWorldMat.transformPoint(worldCameraPosition, _localCameraPos);\n\t\t\t\tconst maxLod = this.octree.lodLevels - 1;\n\t\t\t\tconst nodes = this.octree.nodes;\n\t\t\t\tfor(let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++){\n\t\t\t\t\t\tconst node = nodes[nodeIndex];\n\t\t\t\t\t\tconst newLodIndex = this.calculateNodeLod(localCameraPosition, nodeIndex, maxLod);\n\t\t\t\t\t\tconst currentLodIndex = this.nodeLods[nodeIndex];\n\t\t\t\t\t\tif (newLodIndex !== currentLodIndex) {\n\t\t\t\t\t\t\t\tconst pendingOldFileIndex = this.pendingDecrements.get(nodeIndex);\n\t\t\t\t\t\t\t\tif (pendingOldFileIndex !== undefined) {\n\t\t\t\t\t\t\t\t\t\tthis.decrementFileRef(pendingOldFileIndex, nodeIndex);\n\t\t\t\t\t\t\t\t\t\tthis.pendingDecrements.delete(nodeIndex);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.nodeLods[nodeIndex] = newLodIndex;\n\t\t\t\t\t\t\t\tconst wasVisible = currentLodIndex >= 0;\n\t\t\t\t\t\t\t\tconst willBeVisible = newLodIndex >= 0;\n\t\t\t\t\t\t\t\tif (!wasVisible && willBeVisible) {\n\t\t\t\t\t\t\t\t\t\tconst newFileIndex = node.lods[newLodIndex].fileIndex;\n\t\t\t\t\t\t\t\t\t\tthis.incrementFileRef(newFileIndex, nodeIndex, newLodIndex);\n\t\t\t\t\t\t\t\t} else if (wasVisible && !willBeVisible) {\n\t\t\t\t\t\t\t\t\t\tconst oldFileIndex = node.lods[currentLodIndex].fileIndex;\n\t\t\t\t\t\t\t\t\t\tthis.decrementFileRef(oldFileIndex, nodeIndex);\n\t\t\t\t\t\t\t\t} else if (wasVisible && willBeVisible) {\n\t\t\t\t\t\t\t\t\t\tconst newFileIndex = node.lods[newLodIndex].fileIndex;\n\t\t\t\t\t\t\t\t\t\tconst oldFileIndex = node.lods[currentLodIndex].fileIndex;\n\t\t\t\t\t\t\t\t\t\tthis.incrementFileRef(newFileIndex, nodeIndex, newLodIndex);\n\t\t\t\t\t\t\t\t\t\tconst newPlacement = this.filePlacements[newFileIndex];\n\t\t\t\t\t\t\t\t\t\tif (newPlacement?.resource) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.decrementFileRef(oldFileIndex, nodeIndex);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.pendingDecrements.set(nodeIndex, oldFileIndex);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tincrementFileRef(fileIndex, nodeIndex, lodIndex) {\n\t\t\t\tif (fileIndex === -1) return;\n\t\t\t\tlet placement = this.filePlacements[fileIndex];\n\t\t\t\tif (!placement) {\n\t\t\t\t\t\tplacement = new GSplatPlacement(null, this.placement.node, lodIndex);\n\t\t\t\t\t\tthis.filePlacements[fileIndex] = placement;\n\t\t\t\t\t\tif (!this.addFilePlacement(fileIndex)) {\n\t\t\t\t\t\t\t\tconst fileUrl = this.octree.files[fileIndex];\n\t\t\t\t\t\t\t\tthis.octree.ensureFileResource(fileUrl, this.assetLoader);\n\t\t\t\t\t\t\t\tthis.pending.add(fileIndex);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst nodes = this.octree.nodes;\n\t\t\t\tconst node = nodes[nodeIndex];\n\t\t\t\tconst lod = node.lods[lodIndex];\n\t\t\t\tconst interval = new Vec2(lod.offset, lod.offset + lod.count - 1);\n\t\t\t\tplacement.intervals.set(nodeIndex, interval);\n\t\t\t\tthis.dirtyModifiedPlacements = true;\n\t\t}\n\t\tdecrementFileRef(fileIndex, nodeIndex) {\n\t\t\t\tif (fileIndex === -1) return;\n\t\t\t\tconst placement = this.filePlacements[fileIndex];\n\t\t\t\tif (placement) {\n\t\t\t\t\t\tplacement.intervals.delete(nodeIndex);\n\t\t\t\t\t\tthis.dirtyModifiedPlacements = true;\n\t\t\t\t\t\tif (placement.intervals.size === 0) {\n\t\t\t\t\t\t\t\tif (placement.resource) {\n\t\t\t\t\t\t\t\t\t\tthis.activePlacements.delete(placement);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.filePlacements[fileIndex] = null;\n\t\t\t\t\t\t\t\tthis.pending.delete(fileIndex);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\taddFilePlacement(fileIndex) {\n\t\t\t\tconst fileUrl = this.octree.files[fileIndex];\n\t\t\t\tconst res = this.octree.getFileResource(fileUrl);\n\t\t\t\tif (res) {\n\t\t\t\t\t\tconst placement = this.filePlacements[fileIndex];\n\t\t\t\t\t\tif (placement) {\n\t\t\t\t\t\t\t\tplacement.resource = res;\n\t\t\t\t\t\t\t\tplacement.aabb.copy(res.aabb);\n\t\t\t\t\t\t\t\tthis.activePlacements.add(placement);\n\t\t\t\t\t\t\t\tthis.dirtyModifiedPlacements = true;\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\ttestMoved() {\n\t\t\t\tconst position = this.placement.node.getPosition();\n\t\t\t\tconst length = position.distance(this.previousPosition);\n\t\t\t\tif (length > 1) {\n\t\t\t\t\t\tthis.previousPosition.copy(position);\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t}\n\t\tupdate() {\n\t\t\t\tif (this.pending.size) {\n\t\t\t\t\t\tfor (const fileIndex of this.pending){\n\t\t\t\t\t\t\t\tconst fileUrl = this.octree.files[fileIndex];\n\t\t\t\t\t\t\t\tthis.octree.ensureFileResource(fileUrl, this.assetLoader);\n\t\t\t\t\t\t\t\tconst placement = this.filePlacements[fileIndex];\n\t\t\t\t\t\t\t\tif (placement && placement.intervals.size > 0 && this.addFilePlacement(fileIndex)) {\n\t\t\t\t\t\t\t\t\t\t_tempCompletedUrls.push(fileIndex);\n\t\t\t\t\t\t\t\t\t\tfor (const [nodeIndex] of placement.intervals){\n\t\t\t\t\t\t\t\t\t\t\t\tconst oldFileIndex = this.pendingDecrements.get(nodeIndex);\n\t\t\t\t\t\t\t\t\t\t\t\tif (oldFileIndex !== undefined) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.decrementFileRef(oldFileIndex, nodeIndex);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.pendingDecrements.delete(nodeIndex);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const fileIndex of _tempCompletedUrls){\n\t\t\t\t\t\t\t\tthis.pending.delete(fileIndex);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_tempCompletedUrls.length = 0;\n\t\t\t\t}\n\t\t\t\tconst dirty = this.dirtyModifiedPlacements;\n\t\t\t\tthis.dirtyModifiedPlacements = false;\n\t\t\t\treturn dirty;\n\t\t}\n}\n\nconst tmpMin = new Vec3();\nconst tmpMax = new Vec3();\nclass GSplatOctreeNode {\n\t\tconstructor(lods, boundData){\n\t\t\t\tthis.bounds = new BoundingBox();\n\t\t\t\tthis.lods = lods;\n\t\t\t\ttmpMin.set(boundData.min[0], boundData.min[1], boundData.min[2]);\n\t\t\t\ttmpMax.set(boundData.max[0], boundData.max[1], boundData.max[2]);\n\t\t\t\tthis.bounds.setMinMax(tmpMin, tmpMax);\n\t\t}\n}\n\nclass GSplatOctree {\n\t\tconstructor(assetFileUrl, data){\n\t\t\t\tthis.fileResources = new Map();\n\t\t\t\tthis.files = data.filenames;\n\t\t\t\tthis.lodLevels = data.lodLevels;\n\t\t\t\tthis.assetFileUrl = assetFileUrl;\n\t\t\t\tthis.baseDir = path.getDirectory(assetFileUrl);\n\t\t\t\tconst leafNodes = [];\n\t\t\t\tthis._extractLeafNodes(data.tree, leafNodes);\n\t\t\t\tthis.nodes = leafNodes.map((nodeData)=>{\n\t\t\t\t\t\tconst lods = [];\n\t\t\t\t\t\tfor(let i = 0; i < this.lodLevels; i++){\n\t\t\t\t\t\t\t\tconst lodData = nodeData.lods[i.toString()];\n\t\t\t\t\t\t\t\tif (lodData) {\n\t\t\t\t\t\t\t\t\t\tlods.push({\n\t\t\t\t\t\t\t\t\t\t\t\tfile: this.files[lodData.file] || '',\n\t\t\t\t\t\t\t\t\t\t\t\tfileIndex: lodData.file,\n\t\t\t\t\t\t\t\t\t\t\t\toffset: lodData.offset || 0,\n\t\t\t\t\t\t\t\t\t\t\t\tcount: lodData.count || 0\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tlods.push({\n\t\t\t\t\t\t\t\t\t\t\t\tfile: '',\n\t\t\t\t\t\t\t\t\t\t\t\tfileIndex: -1,\n\t\t\t\t\t\t\t\t\t\t\t\toffset: 0,\n\t\t\t\t\t\t\t\t\t\t\t\tcount: 0\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn new GSplatOctreeNode(lods, nodeData.bound);\n\t\t\t\t});\n\t\t}\n\t\t_extractLeafNodes(node, leafNodes) {\n\t\t\t\tif (node.lods) {\n\t\t\t\t\t\tleafNodes.push({\n\t\t\t\t\t\t\t\tlods: node.lods,\n\t\t\t\t\t\t\t\tbound: node.bound\n\t\t\t\t\t\t});\n\t\t\t\t} else if (node.children) {\n\t\t\t\t\t\tfor (const child of node.children){\n\t\t\t\t\t\t\t\tthis._extractLeafNodes(child, leafNodes);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgetFullUrl(url) {\n\t\t\t\treturn path.join(this.baseDir, url);\n\t\t}\n\t\tgetFileResource(url) {\n\t\t\t\treturn this.fileResources.get(url);\n\t\t}\n\t\tensureFileResource(url, assetLoader) {\n\t\t\t\tif (this.fileResources.has(url)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst fullUrl = this.getFullUrl(url);\n\t\t\t\tconst res = assetLoader.getResource(fullUrl);\n\t\t\t\tif (res) {\n\t\t\t\t\t\tthis.fileResources.set(url, res);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tassetLoader.load(fullUrl);\n\t\t}\n}\n\nclass GSplatOctreeResource {\n\t\tconstructor(assetFileUrl, data){\n\t\t\t\tthis.aabb = new BoundingBox();\n\t\t\t\tthis.octree = new GSplatOctree(assetFileUrl, data);\n\t\t\t\tthis.aabb.setMinMax(new Vec3(data.tree.bound.min), new Vec3(data.tree.bound.max));\n\t\t}\n}\n\nclass GSplatWorldState {\n\t\tconstructor(device, version, splats){\n\t\t\t\tthis.version = 0;\n\t\t\t\tthis.sortParametersSet = false;\n\t\t\t\tthis.sortedBefore = false;\n\t\t\t\tthis.splats = [];\n\t\t\t\tthis.textureSize = 0;\n\t\t\t\tthis.totalUsedPixels = 0;\n\t\t\t\tthis.version = version;\n\t\t\t\tthis.splats = splats;\n\t\t\t\tthis.estimateTextureSize(this.splats, device.maxTextureSize);\n\t\t\t\tthis.assignLines(this.splats, this.textureSize);\n\t\t}\n\t\testimateTextureSize(splats, maxSize) {\n\t\t\t\tconst fits = (size)=>{\n\t\t\t\t\t\tlet rows = 0;\n\t\t\t\t\t\tfor (const splat of splats){\n\t\t\t\t\t\t\t\trows += Math.ceil(splat.activeSplats / size);\n\t\t\t\t\t\t\t\tif (rows > size) return false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t};\n\t\t\t\tlet low = 1;\n\t\t\t\tlet high = maxSize;\n\t\t\t\tlet bestSize = null;\n\t\t\t\twhile(low <= high){\n\t\t\t\t\t\tconst mid = Math.floor((low + high) / 2);\n\t\t\t\t\t\tif (fits(mid)) {\n\t\t\t\t\t\t\t\tbestSize = mid;\n\t\t\t\t\t\t\t\thigh = mid - 1;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlow = mid + 1;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (bestSize === null) {\n\t\t\t\t\t\tthis.textureSize = 0;\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tthis.textureSize = bestSize;\n\t\t\t\treturn true;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.splats.forEach((splat)=>splat.destroy());\n\t\t\t\tthis.splats.length = 0;\n\t\t}\n\t\tassignLines(splats, size) {\n\t\t\t\tif (splats.length === 0) {\n\t\t\t\t\t\tthis.totalUsedPixels = 0;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet start = 0;\n\t\t\t\tfor (const splat of splats){\n\t\t\t\t\t\tconst activeSplats = splat.activeSplats;\n\t\t\t\t\t\tconst numLines = Math.ceil(activeSplats / size);\n\t\t\t\t\t\tsplat.setLines(start, numLines, size, activeSplats);\n\t\t\t\t\t\tstart += numLines;\n\t\t\t\t}\n\t\t\t\tthis.totalUsedPixels = start * size;\n\t\t}\n}\n\nconst cameraPosition = new Vec3();\nconst cameraDirection = new Vec3();\nconst translation = new Vec3();\nconst invModelMat = new Mat4();\nconst tempNonOctreePlacements = new Set();\nconst tempOctreePlacements = new Set();\nconst _updatedSplats = [];\nclass GSplatManager {\n\t\tconstructor(device, director, layer, cameraNode){\n\t\t\t\tthis.node = new GraphNode('GSplatManager');\n\t\t\t\tthis.worldStates = new Map();\n\t\t\t\tthis.lastWorldStateVersion = 0;\n\t\t\t\tthis.sortedVersion = 0;\n\t\t\t\tthis.lastCameraPos = new Vec3(Infinity, Infinity, Infinity);\n\t\t\t\tthis.layerPlacements = [];\n\t\t\t\tthis.layerPlacementsDirty = false;\n\t\t\t\tthis.octreeInstances = new Map();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.director = director;\n\t\t\t\tthis.cameraNode = cameraNode;\n\t\t\t\tthis.workBuffer = new GSplatWorkBuffer(device);\n\t\t\t\tthis.renderer = new GSplatRenderer(device, this.node, this.cameraNode, layer, this.workBuffer);\n\t\t\t\tthis.sorter = this.createSorter();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.workBuffer.destroy();\n\t\t\t\tthis.renderer.destroy();\n\t\t\t\tthis.sorter.destroy();\n\t\t}\n\t\tcreateSorter() {\n\t\t\t\tconst sorter = new GSplatUnifiedSorter();\n\t\t\t\tsorter.on('sorted', (count, version, orderData)=>{\n\t\t\t\t\t\tthis.onSorted(count, version, orderData);\n\t\t\t\t});\n\t\t\t\treturn sorter;\n\t\t}\n\t\treconcile(placements) {\n\t\t\t\ttempNonOctreePlacements.clear();\n\t\t\t\tfor (const p of placements){\n\t\t\t\t\t\tif (p.resource instanceof GSplatOctreeResource) {\n\t\t\t\t\t\t\t\tif (!this.octreeInstances.has(p)) {\n\t\t\t\t\t\t\t\t\t\tthis.octreeInstances.set(p, new GSplatOctreeInstance(p.resource.octree, p, this.director.assetLoader));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttempOctreePlacements.add(p);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttempNonOctreePlacements.add(p);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [placement, inst] of this.octreeInstances){\n\t\t\t\t\t\tif (!tempOctreePlacements.has(placement)) {\n\t\t\t\t\t\t\t\tthis.octreeInstances.delete(placement);\n\t\t\t\t\t\t\t\tinst.destroy();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.layerPlacementsDirty = this.layerPlacements.length !== tempNonOctreePlacements.size;\n\t\t\t\tif (!this.layerPlacementsDirty) {\n\t\t\t\t\t\tfor(let i = 0; i < this.layerPlacements.length; i++){\n\t\t\t\t\t\t\t\tconst existing = this.layerPlacements[i];\n\t\t\t\t\t\t\t\tif (!tempNonOctreePlacements.has(existing)) {\n\t\t\t\t\t\t\t\t\t\tthis.layerPlacementsDirty = true;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.layerPlacements.length = 0;\n\t\t\t\tfor (const p of tempNonOctreePlacements){\n\t\t\t\t\t\tthis.layerPlacements.push(p);\n\t\t\t\t}\n\t\t\t\ttempNonOctreePlacements.clear();\n\t\t\t\ttempOctreePlacements.clear();\n\t\t}\n\t\tupdateWorldState() {\n\t\t\t\tfor (const [, inst] of this.octreeInstances){\n\t\t\t\t\t\tthis.layerPlacementsDirty ||= inst.update();\n\t\t\t\t}\n\t\t\t\tconst worldChanged = this.layerPlacementsDirty || this.worldStates.size === 0;\n\t\t\t\tif (worldChanged) {\n\t\t\t\t\t\tthis.lastWorldStateVersion++;\n\t\t\t\t\t\tconst splats = [];\n\t\t\t\t\t\tfor (const p of this.layerPlacements){\n\t\t\t\t\t\t\t\tconst splatInfo = new GSplatInfo(this.device, p.resource, p);\n\t\t\t\t\t\t\t\tsplats.push(splatInfo);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const [, inst] of this.octreeInstances){\n\t\t\t\t\t\t\t\tinst.activePlacements.forEach((p)=>{\n\t\t\t\t\t\t\t\t\t\tsplats.push(new GSplatInfo(this.device, p.resource, p));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsplats.forEach((splat)=>{\n\t\t\t\t\t\t\t\tthis.sorter.setCenters(splat.resource.id, splat.resource.centers);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst newState = new GSplatWorldState(this.device, this.lastWorldStateVersion, splats);\n\t\t\t\t\t\tthis.worldStates.set(this.lastWorldStateVersion, newState);\n\t\t\t\t\t\tthis.layerPlacementsDirty = false;\n\t\t\t\t}\n\t\t}\n\t\tonSorted(count, version, orderData) {\n\t\t\t\tthis.sortedVersion = version;\n\t\t\t\tconst oldState = this.worldStates.get(version - 1);\n\t\t\t\tif (oldState) {\n\t\t\t\t\t\tthis.worldStates.delete(version - 1);\n\t\t\t\t\t\toldState.destroy();\n\t\t\t\t}\n\t\t\t\tconst worldState = this.worldStates.get(version);\n\t\t\t\tif (worldState) {\n\t\t\t\t\t\tif (!worldState.sortedBefore) {\n\t\t\t\t\t\t\t\tworldState.sortedBefore = true;\n\t\t\t\t\t\t\t\tconst textureSize = worldState.textureSize;\n\t\t\t\t\t\t\t\tif (textureSize !== this.workBuffer.textureSize) {\n\t\t\t\t\t\t\t\t\t\tthis.workBuffer.resize(textureSize);\n\t\t\t\t\t\t\t\t\t\tthis.renderer.setMaxNumSplats(textureSize * textureSize);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.workBuffer.render(worldState.splats, this.cameraNode);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.workBuffer.setOrderData(orderData);\n\t\t\t\t\t\tthis.renderer.setNumSplats(count);\n\t\t\t\t}\n\t\t}\n\t\tupdate(scene) {\n\t\t\t\tlet anyOctreeMoved = false;\n\t\t\t\tfor (const [, inst] of this.octreeInstances){\n\t\t\t\t\t\tanyOctreeMoved ||= inst.testMoved();\n\t\t\t\t}\n\t\t\t\tconst currentCameraPos = this.cameraNode.getPosition();\n\t\t\t\tconst distance = this.lastCameraPos.distance(currentCameraPos);\n\t\t\t\tconst cameraMoved = distance > 1.0;\n\t\t\t\tif (cameraMoved || anyOctreeMoved) {\n\t\t\t\t\t\tthis.lastCameraPos.copy(currentCameraPos);\n\t\t\t\t\t\tfor (const [, inst] of this.octreeInstances){\n\t\t\t\t\t\t\t\tinst.updateLod(this.cameraNode);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.updateWorldState();\n\t\t\t\tconst lastState = this.worldStates.get(this.lastWorldStateVersion);\n\t\t\t\tif (lastState) {\n\t\t\t\t\t\tif (!lastState.sortParametersSet) {\n\t\t\t\t\t\t\t\tlastState.sortParametersSet = true;\n\t\t\t\t\t\t\t\tconst payload = this.prepareSortParameters(lastState);\n\t\t\t\t\t\t\t\tthis.sorter.setSortParameters(payload);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.sort(lastState);\n\t\t\t\t}\n\t\t\t\tconst sortedState = this.worldStates.get(this.sortedVersion);\n\t\t\t\tif (sortedState) {\n\t\t\t\t\t\tsortedState.splats.forEach((splat)=>{\n\t\t\t\t\t\t\t\tif (splat.update()) {\n\t\t\t\t\t\t\t\t\t\t_updatedSplats.push(splat);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (_updatedSplats.length > 0) {\n\t\t\t\t\t\t\t\tthis.workBuffer.render(_updatedSplats, this.cameraNode);\n\t\t\t\t\t\t\t\t_updatedSplats.length = 0;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tsort(lastState) {\n\t\t\t\tconst cameraNode = this.cameraNode;\n\t\t\t\tconst cameraMat = cameraNode.getWorldTransform();\n\t\t\t\tcameraMat.getTranslation(cameraPosition);\n\t\t\t\tcameraMat.getZ(cameraDirection).normalize();\n\t\t\t\tconst sorterRequest = [];\n\t\t\t\tlastState.splats.forEach((splat)=>{\n\t\t\t\t\t\tconst modelMat = splat.node.getWorldTransform();\n\t\t\t\t\t\tinvModelMat.copy(modelMat).invert();\n\t\t\t\t\t\tconst uniformScale = modelMat.getScale().x;\n\t\t\t\t\t\tconst transformedDirection = invModelMat.transformVector(cameraDirection).normalize();\n\t\t\t\t\t\tmodelMat.getTranslation(translation);\n\t\t\t\t\t\tconst offset = translation.sub(cameraPosition).dot(cameraDirection);\n\t\t\t\t\t\tconst aabbMin = splat.aabb.getMin();\n\t\t\t\t\t\tconst aabbMax = splat.aabb.getMax();\n\t\t\t\t\t\tsorterRequest.push({\n\t\t\t\t\t\t\t\ttransformedDirection,\n\t\t\t\t\t\t\t\toffset,\n\t\t\t\t\t\t\t\tscale: uniformScale,\n\t\t\t\t\t\t\t\tmodelMat: modelMat.data.slice(),\n\t\t\t\t\t\t\t\taabbMin: [\n\t\t\t\t\t\t\t\t\t\taabbMin.x,\n\t\t\t\t\t\t\t\t\t\taabbMin.y,\n\t\t\t\t\t\t\t\t\t\taabbMin.z\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\taabbMax: [\n\t\t\t\t\t\t\t\t\t\taabbMax.x,\n\t\t\t\t\t\t\t\t\t\taabbMax.y,\n\t\t\t\t\t\t\t\t\t\taabbMax.z\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tthis.sorter.setSortParams(sorterRequest);\n\t\t\t\tthis.renderer.updateViewport(cameraNode);\n\t\t}\n\t\tprepareSortParameters(worldState) {\n\t\t\t\treturn {\n\t\t\t\t\t\tcommand: 'intervals',\n\t\t\t\t\t\ttextureSize: worldState.textureSize,\n\t\t\t\t\t\ttotalUsedPixels: worldState.totalUsedPixels,\n\t\t\t\t\t\tversion: worldState.version,\n\t\t\t\t\t\tids: worldState.splats.map((splat)=>splat.resource.id),\n\t\t\t\t\t\tlineStarts: worldState.splats.map((splat)=>splat.lineStart),\n\t\t\t\t\t\tpadding: worldState.splats.map((splat)=>splat.padding),\n\t\t\t\t\t\tintervals: worldState.splats.map((splat)=>splat.intervals)\n\t\t\t\t};\n\t\t}\n}\n\nclass GSplatLayerData {\n\t\tconstructor(device, director, layer, cameraNode){\n\t\t\t\tthis.gsplatManager = new GSplatManager(device, director, layer, cameraNode);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.gsplatManager.destroy();\n\t\t}\n}\nclass GSplatCameraData {\n\t\tdestroy() {\n\t\t\t\tthis.layersMap.forEach((layerData)=>layerData.destroy());\n\t\t\t\tthis.layersMap.clear();\n\t\t}\n\t\tremoveLayerData(layer) {\n\t\t\t\tconst layerData = this.layersMap.get(layer);\n\t\t\t\tif (layerData) {\n\t\t\t\t\t\tlayerData.destroy();\n\t\t\t\t\t\tthis.layersMap.delete(layer);\n\t\t\t\t}\n\t\t}\n\t\tgetLayerData(device, director, layer, cameraNode) {\n\t\t\t\tlet layerData = this.layersMap.get(layer);\n\t\t\t\tif (!layerData) {\n\t\t\t\t\t\tlayerData = new GSplatLayerData(device, director, layer, cameraNode);\n\t\t\t\t\t\tthis.layersMap.set(layer, layerData);\n\t\t\t\t}\n\t\t\t\treturn layerData;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tthis.layersMap = new Map();\n\t\t}\n}\nclass GSplatDirector {\n\t\tconstructor(device, scene, assetLoader){\n\t\t\t\tthis.camerasMap = new Map();\n\t\t\t\tthis.octrees = new Map();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.assetLoader = assetLoader;\n\t\t\t\tthis.scene = scene;\n\t\t}\n\t\tgetCameraData(camera) {\n\t\t\t\tlet cameraData = this.camerasMap.get(camera);\n\t\t\t\tif (!cameraData) {\n\t\t\t\t\t\tcameraData = new GSplatCameraData();\n\t\t\t\t\t\tthis.camerasMap.set(camera, cameraData);\n\t\t\t\t}\n\t\t\t\treturn cameraData;\n\t\t}\n\t\tupdate(comp) {\n\t\t\t\tthis.camerasMap.forEach((cameraData, camera)=>{\n\t\t\t\t\t\tif (!comp.camerasSet.has(camera)) {\n\t\t\t\t\t\t\t\tcameraData.destroy();\n\t\t\t\t\t\t\t\tthis.camerasMap.delete(camera);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcameraData.layersMap.forEach((layerData, layer)=>{\n\t\t\t\t\t\t\t\t\t\tif (!camera.layersSet.has(layer.id) || !layer.enabled) {\n\t\t\t\t\t\t\t\t\t\t\t\tlayerData.destroy();\n\t\t\t\t\t\t\t\t\t\t\t\tcameraData.layersMap.delete(layer);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst camerasComponents = comp.cameras;\n\t\t\t\tfor(let i = 0; i < camerasComponents.length; i++){\n\t\t\t\t\t\tconst camera = camerasComponents[i].camera;\n\t\t\t\t\t\tlet cameraData = this.camerasMap.get(camera);\n\t\t\t\t\t\tconst layerIds = camera.layers;\n\t\t\t\t\t\tfor(let j = 0; j < layerIds.length; j++){\n\t\t\t\t\t\t\t\tconst layer = comp.getLayerById(layerIds[j]);\n\t\t\t\t\t\t\t\tif (layer?.enabled) {\n\t\t\t\t\t\t\t\t\t\tif (layer.gsplatPlacementsDirty || !cameraData) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst placements = layer.gsplatPlacements;\n\t\t\t\t\t\t\t\t\t\t\t\tif (placements.length === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (cameraData) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcameraData.removeLayerData(layer);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcameraData ??= this.getCameraData(camera);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst layerData = cameraData.getLayerData(this.device, this, layer, camera.node);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlayerData.gsplatManager.reconcile(placements);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcameraData?.layersMap.forEach((layerData)=>{\n\t\t\t\t\t\t\t\tlayerData.gsplatManager.update(this.scene);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < comp.layerList.length; i++){\n\t\t\t\t\t\tcomp.layerList[i].gsplatPlacementsDirty = false;\n\t\t\t\t}\n\t\t}\n}\n\nclass GSplatComponent extends Component {\n\t\tconstructor(system, entity){\n\t\t\t\tsuper(system, entity), this._layers = [\n\t\t\t\t\t\tLAYERID_WORLD\n\t\t\t\t], this._instance = null, this._placement = null, this._materialTmp = null, this._highQualitySH = true, this._customAabb = null, this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null, this._castShadows = false, this._unified = false;\n\t\t\t\tthis._assetReference = new AssetReference('asset', this, system.app.assets, {\n\t\t\t\t\t\tadd: this._onGSplatAssetAdded,\n\t\t\t\t\t\tload: this._onGSplatAssetLoad,\n\t\t\t\t\t\tremove: this._onGSplatAssetRemove,\n\t\t\t\t\t\tunload: this._onGSplatAssetUnload\n\t\t\t\t}, this);\n\t\t\t\tentity.on('remove', this.onRemoveChild, this);\n\t\t\t\tentity.on('removehierarchy', this.onRemoveChild, this);\n\t\t\t\tentity.on('insert', this.onInsertChild, this);\n\t\t\t\tentity.on('inserthierarchy', this.onInsertChild, this);\n\t\t}\n\t\tset customAabb(value) {\n\t\t\t\tthis._customAabb = value;\n\t\t\t\tthis._instance?.meshInstance?.setCustomAabb(this._customAabb);\n\t\t\t\tif (this._placement && this._customAabb) {\n\t\t\t\t\t\tthis._placement.aabb = this._customAabb;\n\t\t\t\t}\n\t\t}\n\t\tget customAabb() {\n\t\t\t\treturn this._customAabb;\n\t\t}\n\t\tset instance(value) {\n\t\t\t\tthis.destroyInstance();\n\t\t\t\tthis._instance = value;\n\t\t\t\tif (this._instance) {\n\t\t\t\t\t\tconst mi = this._instance.meshInstance;\n\t\t\t\t\t\tif (!mi.node) {\n\t\t\t\t\t\t\t\tmi.node = this.entity;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmi.castShadow = this._castShadows;\n\t\t\t\t\t\tmi.setCustomAabb(this._customAabb);\n\t\t\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\t\t\tthis.addToLayers();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget instance() {\n\t\t\t\treturn this._instance;\n\t\t}\n\t\tset material(value) {\n\t\t\t\tif (this._instance) {\n\t\t\t\t\t\tthis._instance.material = value;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._materialTmp = value;\n\t\t\t\t}\n\t\t}\n\t\tget material() {\n\t\t\t\treturn this._instance?.material ?? this._materialTmp ?? null;\n\t\t}\n\t\tset highQualitySH(value) {\n\t\t\t\tif (value !== this._highQualitySH) {\n\t\t\t\t\t\tthis._highQualitySH = value;\n\t\t\t\t\t\tthis._instance?.setHighQualitySH(value);\n\t\t\t\t}\n\t\t}\n\t\tget highQualitySH() {\n\t\t\t\treturn this._highQualitySH;\n\t\t}\n\t\tset castShadows(value) {\n\t\t\t\tif (this._castShadows !== value) {\n\t\t\t\t\t\tconst mi = this.instance?.meshInstance;\n\t\t\t\t\t\tif (mi) {\n\t\t\t\t\t\t\t\tconst layers = this.layers;\n\t\t\t\t\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\t\t\t\t\tif (this._castShadows && !value) {\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst layer = scene.layers.getLayerById(this.layers[i]);\n\t\t\t\t\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlayer.removeShadowCasters([\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmi\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmi.castShadow = value;\n\t\t\t\t\t\t\t\tif (!this._castShadows && value) {\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < layers.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst layer = scene.layers.getLayerById(layers[i]);\n\t\t\t\t\t\t\t\t\t\t\t\tif (layer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlayer.addShadowCasters([\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmi\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._castShadows = value;\n\t\t\t\t}\n\t\t}\n\t\tget castShadows() {\n\t\t\t\treturn this._castShadows;\n\t\t}\n\t\tset unified(value) {\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._unified = value;\n\t\t\t\tthis._onGSplatAssetAdded();\n\t\t}\n\t\tget unified() {\n\t\t\t\treturn this._unified;\n\t\t}\n\t\tset layers(value) {\n\t\t\t\tthis.removeFromLayers();\n\t\t\t\tthis._layers.length = 0;\n\t\t\t\tfor(let i = 0; i < value.length; i++){\n\t\t\t\t\t\tthis._layers[i] = value[i];\n\t\t\t\t}\n\t\t\t\tif (!this.enabled || !this.entity.enabled) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.addToLayers();\n\t\t}\n\t\tget layers() {\n\t\t\t\treturn this._layers;\n\t\t}\n\t\tset asset(value) {\n\t\t\t\tconst id = value instanceof Asset ? value.id : value;\n\t\t\t\tif (this._assetReference.id === id) return;\n\t\t\t\tif (this._assetReference.asset && this._assetReference.asset.resource) {\n\t\t\t\t\t\tthis._onGSplatAssetRemove();\n\t\t\t\t}\n\t\t\t\tthis._assetReference.id = id;\n\t\t\t\tif (this._assetReference.asset) {\n\t\t\t\t\t\tthis._onGSplatAssetAdded();\n\t\t\t\t}\n\t\t}\n\t\tget asset() {\n\t\t\t\treturn this._assetReference.id;\n\t\t}\n\t\tdestroyInstance() {\n\t\t\t\tif (this._placement) {\n\t\t\t\t\t\tthis.removeFromLayers();\n\t\t\t\t\t\tthis._placement = null;\n\t\t\t\t}\n\t\t\t\tif (this._instance) {\n\t\t\t\t\t\tthis.removeFromLayers();\n\t\t\t\t\t\tthis._instance?.destroy();\n\t\t\t\t\t\tthis._instance = null;\n\t\t\t\t}\n\t\t}\n\t\taddToLayers() {\n\t\t\t\tif (this._placement) {\n\t\t\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tlayers.getLayerById(this._layers[i])?.addGSplatPlacement(this._placement);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst meshInstance = this.instance?.meshInstance;\n\t\t\t\tif (meshInstance) {\n\t\t\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tlayers.getLayerById(this._layers[i])?.addMeshInstances([\n\t\t\t\t\t\t\t\t\t\tmeshInstance\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tremoveFromLayers() {\n\t\t\t\tif (this._placement) {\n\t\t\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tlayers.getLayerById(this._layers[i])?.removeGSplatPlacement(this._placement);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst meshInstance = this.instance?.meshInstance;\n\t\t\t\tif (meshInstance) {\n\t\t\t\t\t\tconst layers = this.system.app.scene.layers;\n\t\t\t\t\t\tfor(let i = 0; i < this._layers.length; i++){\n\t\t\t\t\t\t\t\tlayers.getLayerById(this._layers[i])?.removeMeshInstances([\n\t\t\t\t\t\t\t\t\t\tmeshInstance\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonRemoveChild() {\n\t\t\t\tthis.removeFromLayers();\n\t\t}\n\t\tonInsertChild() {\n\t\t\t\tif (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tif (this._instance || this._placement) {\n\t\t\t\t\t\t\t\tthis.addToLayers();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tonRemove() {\n\t\t\t\tthis.destroyInstance();\n\t\t\t\tthis.asset = null;\n\t\t\t\tthis._assetReference.id = null;\n\t\t\t\tthis.entity.off('remove', this.onRemoveChild, this);\n\t\t\t\tthis.entity.off('insert', this.onInsertChild, this);\n\t\t}\n\t\tonLayersChanged(oldComp, newComp) {\n\t\t\t\tthis.addToLayers();\n\t\t\t\toldComp.off('add', this.onLayerAdded, this);\n\t\t\t\toldComp.off('remove', this.onLayerRemoved, this);\n\t\t\t\tnewComp.on('add', this.onLayerAdded, this);\n\t\t\t\tnewComp.on('remove', this.onLayerRemoved, this);\n\t\t}\n\t\tonLayerAdded(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tif (this._instance) {\n\t\t\t\t\t\tlayer.addMeshInstances(this._instance.meshInstance);\n\t\t\t\t}\n\t\t}\n\t\tonLayerRemoved(layer) {\n\t\t\t\tconst index = this.layers.indexOf(layer.id);\n\t\t\t\tif (index < 0) return;\n\t\t\t\tif (this._instance) {\n\t\t\t\t\t\tlayer.removeMeshInstances(this._instance.meshInstance);\n\t\t\t\t}\n\t\t}\n\t\tonEnable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded = layers.on('add', this.onLayerAdded, this);\n\t\t\t\t\t\tthis._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);\n\t\t\t\t}\n\t\t\t\tif (this._instance || this._placement) {\n\t\t\t\t\t\tthis.addToLayers();\n\t\t\t\t} else if (this.asset) {\n\t\t\t\t\t\tthis._onGSplatAssetAdded();\n\t\t\t\t}\n\t\t}\n\t\tonDisable() {\n\t\t\t\tconst scene = this.system.app.scene;\n\t\t\t\tconst layers = scene.layers;\n\t\t\t\tthis._evtLayersChanged?.off();\n\t\t\t\tthis._evtLayersChanged = null;\n\t\t\t\tif (layers) {\n\t\t\t\t\t\tthis._evtLayerAdded?.off();\n\t\t\t\t\t\tthis._evtLayerAdded = null;\n\t\t\t\t\t\tthis._evtLayerRemoved?.off();\n\t\t\t\t\t\tthis._evtLayerRemoved = null;\n\t\t\t\t}\n\t\t\t\tthis.removeFromLayers();\n\t\t}\n\t\thide() {\n\t\t\t\tif (this._instance) {\n\t\t\t\t\t\tthis._instance.meshInstance.visible = false;\n\t\t\t\t}\n\t\t}\n\t\tshow() {\n\t\t\t\tif (this._instance) {\n\t\t\t\t\t\tthis._instance.meshInstance.visible = true;\n\t\t\t\t}\n\t\t}\n\t\t_onGSplatAssetAdded() {\n\t\t\t\tif (!this._assetReference.asset) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._assetReference.asset.resource) {\n\t\t\t\t\t\tthis._onGSplatAssetLoad();\n\t\t\t\t} else if (this.enabled && this.entity.enabled) {\n\t\t\t\t\t\tthis.system.app.assets.load(this._assetReference.asset);\n\t\t\t\t}\n\t\t}\n\t\t_onGSplatAssetLoad() {\n\t\t\t\tthis.destroyInstance();\n\t\t\t\tconst asset = this._assetReference.asset;\n\t\t\t\tif (this.unified) {\n\t\t\t\t\t\tthis._placement = null;\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis._placement = new GSplatPlacement(asset.resource, this.entity);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\tthis.instance = new GSplatInstance(asset.resource, {\n\t\t\t\t\t\t\t\t\t\tmaterial: this._materialTmp,\n\t\t\t\t\t\t\t\t\t\thighQualitySH: this._highQualitySH\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tthis._materialTmp = null;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (asset) {\n\t\t\t\t\t\tthis.customAabb = asset.resource.aabb.clone();\n\t\t\t\t}\n\t\t}\n\t\t_onGSplatAssetUnload() {\n\t\t\t\tthis.destroyInstance();\n\t\t}\n\t\t_onGSplatAssetRemove() {\n\t\t\t\tthis._onGSplatAssetUnload();\n\t\t}\n}\n\nclass GSplatComponentData {\n\t\tconstructor(){\n\t\t\t\tthis.enabled = true;\n\t\t}\n}\n\nclass GSplatAssetLoaderBase {\n\t\tload(url) {}\n\t\tunload(url) {}\n\t\tgetResource(url) {}\n}\n\nclass GSplatAssetLoader extends GSplatAssetLoaderBase {\n\t\tconstructor(registry){\n\t\t\t\tsuper(), this._urlToAsset = new Map();\n\t\t\t\tthis._registry = registry;\n\t\t}\n\t\tload(url) {\n\t\t\t\tlet asset = this._urlToAsset.get(url);\n\t\t\t\tif (!asset) {\n\t\t\t\t\t\tasset = this._registry.getByUrl(url);\n\t\t\t\t\t\tif (!asset) {\n\t\t\t\t\t\t\t\tasset = new Asset(url, 'gsplat', {\n\t\t\t\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tthis._registry.add(asset);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._urlToAsset.set(url, asset);\n\t\t\t\t}\n\t\t\t\tif (!asset.loaded && !asset.loading) {\n\t\t\t\t\t\tthis._registry.load(asset);\n\t\t\t\t}\n\t\t}\n\t\tunload(url) {\n\t\t\t\tconst asset = this._urlToAsset.get(url);\n\t\t\t\tif (asset) {\n\t\t\t\t\t\tasset.unload();\n\t\t\t\t\t\tthis._urlToAsset.delete(url);\n\t\t\t\t}\n\t\t}\n\t\tgetResource(url) {\n\t\t\t\tconst asset = this._urlToAsset.get(url);\n\t\t\t\treturn asset?.resource;\n\t\t}\n}\n\nconst _schema = [\n\t\t'enabled'\n];\nconst _properties = [\n\t\t'unified',\n\t\t'castShadows',\n\t\t'material',\n\t\t'highQualitySH',\n\t\t'asset',\n\t\t'layers'\n];\nclass GSplatComponentSystem extends ComponentSystem {\n\t\tconstructor(app){\n\t\t\t\tsuper(app);\n\t\t\t\tthis.id = 'gsplat';\n\t\t\t\tthis.ComponentType = GSplatComponent;\n\t\t\t\tthis.DataType = GSplatComponentData;\n\t\t\t\tthis.schema = _schema;\n\t\t\t\tconst gsplatAssetLoader = new GSplatAssetLoader(app.assets);\n\t\t\t\tapp.renderer.gsplatDirector = new GSplatDirector(app.graphicsDevice, app.scene, gsplatAssetLoader);\n\t\t\t\tthis.on('beforeremove', this.onRemove, this);\n\t\t}\n\t\tinitializeComponentData(component, _data, properties) {\n\t\t\t\tif (_data.layers && _data.layers.length) {\n\t\t\t\t\t\t_data.layers = _data.layers.slice(0);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < _properties.length; i++){\n\t\t\t\t\t\tif (_data.hasOwnProperty(_properties[i])) {\n\t\t\t\t\t\t\t\tcomponent[_properties[i]] = _data[_properties[i]];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (_data.aabbCenter && _data.aabbHalfExtents) {\n\t\t\t\t\t\tcomponent.customAabb = new BoundingBox(new Vec3(_data.aabbCenter), new Vec3(_data.aabbHalfExtents));\n\t\t\t\t}\n\t\t\t\tsuper.initializeComponentData(component, _data, _schema);\n\t\t}\n\t\tcloneComponent(entity, clone) {\n\t\t\t\tconst gSplatComponent = entity.gsplat;\n\t\t\t\tconst data = {};\n\t\t\t\t_properties.forEach((prop)=>{\n\t\t\t\t\t\tif (prop === 'material') {\n\t\t\t\t\t\t\t\tconst srcMaterial = gSplatComponent[prop];\n\t\t\t\t\t\t\t\tif (srcMaterial) {\n\t\t\t\t\t\t\t\t\t\tdata[prop] = srcMaterial.clone();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdata[prop] = gSplatComponent[prop];\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tdata.enabled = gSplatComponent.enabled;\n\t\t\t\tconst component = this.addComponent(clone, data);\n\t\t\t\tif (gSplatComponent.customAabb) {\n\t\t\t\t\t\tcomponent.customAabb = gSplatComponent.customAabb.clone();\n\t\t\t\t}\n\t\t\t\treturn component;\n\t\t}\n\t\tonRemove(entity, component) {\n\t\t\t\tcomponent.onRemove();\n\t\t}\n}\nComponent._buildAccessors(GSplatComponent.prototype, _schema);\n\nclass Render extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_SETMESHES = 'set:meshes';\n\t\t}\n\t\tset meshes(value) {\n\t\t\t\tthis.decRefMeshes();\n\t\t\t\tthis._meshes = value;\n\t\t\t\tthis.incRefMeshes();\n\t\t\t\tthis.fire('set:meshes', value);\n\t\t}\n\t\tget meshes() {\n\t\t\t\treturn this._meshes;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.meshes = null;\n\t\t}\n\t\tdecRefMeshes() {\n\t\t\t\tthis._meshes?.forEach((mesh, index)=>{\n\t\t\t\t\t\tif (mesh) {\n\t\t\t\t\t\t\t\tmesh.decRefCount();\n\t\t\t\t\t\t\t\tif (mesh.refCount < 1) {\n\t\t\t\t\t\t\t\t\t\tmesh.destroy();\n\t\t\t\t\t\t\t\t\t\tthis._meshes[index] = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tincRefMeshes() {\n\t\t\t\tthis._meshes?.forEach((mesh)=>{\n\t\t\t\t\t\tmesh?.incRefCount();\n\t\t\t\t});\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._meshes = null;\n\t\t}\n}\n\nfunction onContainerAssetLoaded(containerAsset) {\n\t\tconst renderAsset = this;\n\t\tif (!renderAsset.resource) return;\n\t\tconst containerResource = containerAsset.resource;\n\t\tconst render = containerResource.renders && containerResource.renders[renderAsset.data.renderIndex];\n\t\tif (render) {\n\t\t\t\trenderAsset.resource.meshes = render.resource.meshes;\n\t\t}\n}\nfunction onContainerAssetAdded(containerAsset) {\n\t\tconst renderAsset = this;\n\t\trenderAsset.registry.off(`load:${containerAsset.id}`, onContainerAssetLoaded, renderAsset);\n\t\trenderAsset.registry.on(`load:${containerAsset.id}`, onContainerAssetLoaded, renderAsset);\n\t\trenderAsset.registry.off(`remove:${containerAsset.id}`, onContainerAssetRemoved, renderAsset);\n\t\trenderAsset.registry.once(`remove:${containerAsset.id}`, onContainerAssetRemoved, renderAsset);\n\t\tif (!containerAsset.resource) {\n\t\t\t\trenderAsset.registry.load(containerAsset);\n\t\t} else {\n\t\t\t\tonContainerAssetLoaded.call(renderAsset, containerAsset);\n\t\t}\n}\nfunction onContainerAssetRemoved(containerAsset) {\n\t\tconst renderAsset = this;\n\t\trenderAsset.registry.off(`load:${containerAsset.id}`, onContainerAssetLoaded, renderAsset);\n\t\tif (renderAsset.resource) {\n\t\t\t\trenderAsset.resource.destroy();\n\t\t}\n}\nclass RenderHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'render');\n\t\t\t\tthis._registry = app.assets;\n\t\t}\n\t\topen(url, data) {\n\t\t\t\treturn new Render();\n\t\t}\n\t\tpatch(asset, registry) {\n\t\t\t\tif (!asset.data.containerAsset) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst containerAsset = registry.get(asset.data.containerAsset);\n\t\t\t\tif (!containerAsset) {\n\t\t\t\t\t\tregistry.once(`add:${asset.data.containerAsset}`, onContainerAssetAdded, asset);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tonContainerAssetAdded.call(asset, containerAsset);\n\t\t}\n}\n\nclass AnimCurve {\n\t\tconstructor(paths, input, output, interpolation){\n\t\t\t\tthis._paths = paths;\n\t\t\t\tthis._input = input;\n\t\t\t\tthis._output = output;\n\t\t\t\tthis._interpolation = interpolation;\n\t\t}\n\t\tget paths() {\n\t\t\t\treturn this._paths;\n\t\t}\n\t\tget input() {\n\t\t\t\treturn this._input;\n\t\t}\n\t\tget output() {\n\t\t\t\treturn this._output;\n\t\t}\n\t\tget interpolation() {\n\t\t\t\treturn this._interpolation;\n\t\t}\n}\n\nclass AnimData {\n\t\tconstructor(components, data){\n\t\t\t\tthis._components = components;\n\t\t\t\tthis._data = data;\n\t\t}\n\t\tget components() {\n\t\t\t\treturn this._components;\n\t\t}\n\t\tget data() {\n\t\t\t\treturn this._data;\n\t\t}\n}\n\nfunction DracoWorker(jsUrl, wasmUrl) {\n\t\tlet draco;\n\t\tconst POSITION_ATTRIBUTE = 0;\n\t\tconst NORMAL_ATTRIBUTE = 1;\n\t\tconst wrap = (typedArray, dataType)=>{\n\t\t\t\tswitch(dataType){\n\t\t\t\t\t\tcase draco.DT_INT8:\n\t\t\t\t\t\t\t\treturn new Int8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);\n\t\t\t\t\t\tcase draco.DT_INT16:\n\t\t\t\t\t\t\t\treturn new Int16Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 2);\n\t\t\t\t\t\tcase draco.DT_INT32:\n\t\t\t\t\t\t\t\treturn new Int32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);\n\t\t\t\t\t\tcase draco.DT_UINT8:\n\t\t\t\t\t\t\t\treturn new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);\n\t\t\t\t\t\tcase draco.DT_UINT16:\n\t\t\t\t\t\t\t\treturn new Uint16Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 2);\n\t\t\t\t\t\tcase draco.DT_UINT32:\n\t\t\t\t\t\t\t\treturn new Uint32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);\n\t\t\t\t\t\tcase draco.DT_FLOAT32:\n\t\t\t\t\t\t\t\treturn new Float32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t};\n\t\tconst componentSizeInBytes = (dataType)=>{\n\t\t\t\tswitch(dataType){\n\t\t\t\t\t\tcase draco.DT_INT8:\n\t\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\tcase draco.DT_INT16:\n\t\t\t\t\t\t\t\treturn 2;\n\t\t\t\t\t\tcase draco.DT_INT32:\n\t\t\t\t\t\t\t\treturn 4;\n\t\t\t\t\t\tcase draco.DT_UINT8:\n\t\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\tcase draco.DT_UINT16:\n\t\t\t\t\t\t\t\treturn 2;\n\t\t\t\t\t\tcase draco.DT_UINT32:\n\t\t\t\t\t\t\t\treturn 4;\n\t\t\t\t\t\tcase draco.DT_FLOAT32:\n\t\t\t\t\t\t\t\treturn 4;\n\t\t\t\t}\n\t\t\t\treturn 1;\n\t\t};\n\t\tconst attributeSizeInBytes = (attribute)=>{\n\t\t\t\treturn attribute.num_components() * componentSizeInBytes(attribute.data_type());\n\t\t};\n\t\tconst attributeOrder = {\n\t\t\t\t0: 0,\n\t\t\t\t1: 1,\n\t\t\t\t5: 2,\n\t\t\t\t2: 3,\n\t\t\t\t7: 4,\n\t\t\t\t8: 5,\n\t\t\t\t4: 6,\n\t\t\t\t3: 7\n\t\t};\n\t\tconst generateNormals = (vertices, indices)=>{\n\t\t\t\tconst subtract = (dst, a, b)=>{\n\t\t\t\t\t\tdst[0] = a[0] - b[0];\n\t\t\t\t\t\tdst[1] = a[1] - b[1];\n\t\t\t\t\t\tdst[2] = a[2] - b[2];\n\t\t\t\t};\n\t\t\t\tconst cross = (dst, a, b)=>{\n\t\t\t\t\t\tdst[0] = a[1] * b[2] - b[1] * a[2];\n\t\t\t\t\t\tdst[1] = a[2] * b[0] - b[2] * a[0];\n\t\t\t\t\t\tdst[2] = a[0] * b[1] - b[0] * a[1];\n\t\t\t\t};\n\t\t\t\tconst normalize = (dst, offset)=>{\n\t\t\t\t\t\tconst a = dst[offset + 0];\n\t\t\t\t\t\tconst b = dst[offset + 1];\n\t\t\t\t\t\tconst c = dst[offset + 2];\n\t\t\t\t\t\tconst l = 1.0 / Math.sqrt(a * a + b * b + c * c);\n\t\t\t\t\t\tdst[offset + 0] *= l;\n\t\t\t\t\t\tdst[offset + 1] *= l;\n\t\t\t\t\t\tdst[offset + 2] *= l;\n\t\t\t\t};\n\t\t\t\tconst copy = (dst, src, srcOffset)=>{\n\t\t\t\t\t\tfor(let i = 0; i < 3; ++i){\n\t\t\t\t\t\t\t\tdst[i] = src[srcOffset + i];\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst numTriangles = indices.length / 3;\n\t\t\t\tconst numVertices = vertices.length / 3;\n\t\t\t\tconst result = new Float32Array(vertices.length);\n\t\t\t\tconst a = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t], b = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t], c = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t], t1 = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t], t2 = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t], n = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\tfor(let i = 0; i < numTriangles; ++i){\n\t\t\t\t\t\tconst v0 = indices[i * 3 + 0] * 3;\n\t\t\t\t\t\tconst v1 = indices[i * 3 + 1] * 3;\n\t\t\t\t\t\tconst v2 = indices[i * 3 + 2] * 3;\n\t\t\t\t\t\tcopy(a, vertices, v0);\n\t\t\t\t\t\tcopy(b, vertices, v1);\n\t\t\t\t\t\tcopy(c, vertices, v2);\n\t\t\t\t\t\tsubtract(t1, b, a);\n\t\t\t\t\t\tsubtract(t2, c, a);\n\t\t\t\t\t\tcross(n, t1, t2);\n\t\t\t\t\t\tnormalize(n, 0);\n\t\t\t\t\t\tfor(let j = 0; j < 3; ++j){\n\t\t\t\t\t\t\t\tresult[v0 + j] += n[j];\n\t\t\t\t\t\t\t\tresult[v1 + j] += n[j];\n\t\t\t\t\t\t\t\tresult[v2 + j] += n[j];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < numVertices; ++i){\n\t\t\t\t\t\tnormalize(result, i * 3);\n\t\t\t\t}\n\t\t\t\treturn new Uint8Array(result.buffer);\n\t\t};\n\t\tconst decodeMesh = (inputBuffer)=>{\n\t\t\t\tconst result = {};\n\t\t\t\tconst buffer = new draco.DecoderBuffer();\n\t\t\t\tbuffer.Init(inputBuffer, inputBuffer.length);\n\t\t\t\tconst decoder = new draco.Decoder();\n\t\t\t\tif (decoder.GetEncodedGeometryType(buffer) !== draco.TRIANGULAR_MESH) {\n\t\t\t\t\t\tresult.error = 'Failed to decode draco mesh: not a mesh';\n\t\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\tconst mesh = new draco.Mesh();\n\t\t\t\tconst status = decoder.DecodeBufferToMesh(buffer, mesh);\n\t\t\t\tif (!status || !status.ok() || draco.getPointer(mesh) === 0) {\n\t\t\t\t\t\tresult.error = 'Failed to decode draco asset';\n\t\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\tconst numIndices = mesh.num_faces() * 3;\n\t\t\t\tconst shortIndices = mesh.num_points() <= 65535;\n\t\t\t\tconst indicesSize = numIndices * (shortIndices ? 2 : 4);\n\t\t\t\tconst indicesPtr = draco._malloc(indicesSize);\n\t\t\t\tif (shortIndices) {\n\t\t\t\t\t\tdecoder.GetTrianglesUInt16Array(mesh, indicesSize, indicesPtr);\n\t\t\t\t\t\tresult.indices = new Uint16Array(draco.HEAPU16.buffer, indicesPtr, numIndices).slice().buffer;\n\t\t\t\t} else {\n\t\t\t\t\t\tdecoder.GetTrianglesUInt32Array(mesh, indicesSize, indicesPtr);\n\t\t\t\t\t\tresult.indices = new Uint32Array(draco.HEAPU32.buffer, indicesPtr, numIndices).slice().buffer;\n\t\t\t\t}\n\t\t\t\tdraco._free(indicesPtr);\n\t\t\t\tconst attributes = [];\n\t\t\t\tfor(let i = 0; i < mesh.num_attributes(); ++i){\n\t\t\t\t\t\tattributes.push(decoder.GetAttribute(mesh, i));\n\t\t\t\t}\n\t\t\t\tattributes.sort((a, b)=>{\n\t\t\t\t\t\treturn (attributeOrder[a.attribute_type()] ?? attributeOrder.length) - (attributeOrder[b.attribute_type()] ?? attributeOrder.length);\n\t\t\t\t});\n\t\t\t\tresult.attributes = attributes.map((a)=>a.unique_id());\n\t\t\t\tlet totalVertexSize = 0;\n\t\t\t\tconst offsets = attributes.map((a)=>{\n\t\t\t\t\t\tconst offset = totalVertexSize;\n\t\t\t\t\t\ttotalVertexSize += Math.ceil(attributeSizeInBytes(a) / 4) * 4;\n\t\t\t\t\t\treturn offset;\n\t\t\t\t});\n\t\t\t\tconst hasNormals = attributes.some((a)=>a.attribute_type() === NORMAL_ATTRIBUTE);\n\t\t\t\tconst normalOffset = offsets[1];\n\t\t\t\tif (!hasNormals) {\n\t\t\t\t\t\tfor(let i = 1; i < offsets.length; ++i){\n\t\t\t\t\t\t\t\toffsets[i] += 12;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttotalVertexSize += 12;\n\t\t\t\t}\n\t\t\t\tresult.vertices = new ArrayBuffer(mesh.num_points() * totalVertexSize);\n\t\t\t\tconst dst = new Uint8Array(result.vertices);\n\t\t\t\tfor(let i = 0; i < mesh.num_attributes(); ++i){\n\t\t\t\t\t\tconst attribute = attributes[i];\n\t\t\t\t\t\tconst sizeInBytes = attributeSizeInBytes(attribute);\n\t\t\t\t\t\tconst ptrSize = mesh.num_points() * sizeInBytes;\n\t\t\t\t\t\tconst ptr = draco._malloc(ptrSize);\n\t\t\t\t\t\tdecoder.GetAttributeDataArrayForAllPoints(mesh, attribute, attribute.data_type(), ptrSize, ptr);\n\t\t\t\t\t\tconst src = new Uint8Array(draco.HEAPU8.buffer, ptr, ptrSize);\n\t\t\t\t\t\tfor(let j = 0; j < mesh.num_points(); ++j){\n\t\t\t\t\t\t\t\tfor(let c = 0; c < sizeInBytes; ++c){\n\t\t\t\t\t\t\t\t\t\tdst[j * totalVertexSize + offsets[i] + c] = src[j * sizeInBytes + c];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!hasNormals && attribute.attribute_type() === POSITION_ATTRIBUTE) {\n\t\t\t\t\t\t\t\tconst normals = generateNormals(wrap(src, attribute.data_type()), shortIndices ? new Uint16Array(result.indices) : new Uint32Array(result.indices));\n\t\t\t\t\t\t\t\tfor(let j = 0; j < mesh.num_points(); ++j){\n\t\t\t\t\t\t\t\t\t\tfor(let c = 0; c < 12; ++c){\n\t\t\t\t\t\t\t\t\t\t\t\tdst[j * totalVertexSize + normalOffset + c] = normals[j * 12 + c];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdraco._free(ptr);\n\t\t\t\t}\n\t\t\t\tdraco.destroy(mesh);\n\t\t\t\tdraco.destroy(decoder);\n\t\t\t\tdraco.destroy(buffer);\n\t\t\t\treturn result;\n\t\t};\n\t\tconst decode = (data)=>{\n\t\t\t\tconst result = decodeMesh(new Uint8Array(data.buffer));\n\t\t\t\tself.postMessage({\n\t\t\t\t\t\tjobId: data.jobId,\n\t\t\t\t\t\terror: result.error,\n\t\t\t\t\t\tindices: result.indices,\n\t\t\t\t\t\tvertices: result.vertices,\n\t\t\t\t\t\tattributes: result.attributes\n\t\t\t\t}, [\n\t\t\t\t\t\tresult.indices,\n\t\t\t\t\t\tresult.vertices\n\t\t\t\t].filter((t)=>t != null));\n\t\t};\n\t\tconst workQueue = [];\n\t\tself.onmessage = (message)=>{\n\t\t\t\tconst data = message.data;\n\t\t\t\tswitch(data.type){\n\t\t\t\t\t\tcase 'init':\n\t\t\t\t\t\t\t\tself.DracoDecoderModule({\n\t\t\t\t\t\t\t\t\t\tinstantiateWasm: (imports, successCallback)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tWebAssembly.instantiate(data.module, imports).then((result)=>successCallback(result)).catch((reason)=>console.error(`instantiate failed + ${reason}`));\n\t\t\t\t\t\t\t\t\t\t\t\treturn {};\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}).then((instance)=>{\n\t\t\t\t\t\t\t\t\t\tdraco = instance;\n\t\t\t\t\t\t\t\t\t\tworkQueue.forEach((data)=>decode(data));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'decodeMesh':\n\t\t\t\t\t\t\t\tif (draco) {\n\t\t\t\t\t\t\t\t\t\tdecode(data);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tworkQueue.push(data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t};\n}\n\nconst downloadMaxRetries = 3;\nclass JobQueue {\n\t\tconstructor(){\n\t\t\t\tthis.workers = [\n\t\t\t\t\t\t[],\n\t\t\t\t\t\t[],\n\t\t\t\t\t\t[]\n\t\t\t\t];\n\t\t\t\tthis.jobId = 0;\n\t\t\t\tthis.jobQueue = [];\n\t\t\t\tthis.jobCallbacks = new Map();\n\t\t\t\tthis.run = (worker, job)=>{\n\t\t\t\t\t\tworker.postMessage({\n\t\t\t\t\t\t\t\ttype: 'decodeMesh',\n\t\t\t\t\t\t\t\tjobId: job.jobId,\n\t\t\t\t\t\t\t\tbuffer: job.buffer\n\t\t\t\t\t\t}, [\n\t\t\t\t\t\t\t\tjob.buffer\n\t\t\t\t\t\t]);\n\t\t\t\t};\n\t\t}\n\t\tinit(workers) {\n\t\t\t\tworkers.forEach((worker)=>{\n\t\t\t\t\t\tworker.addEventListener('message', (message)=>{\n\t\t\t\t\t\t\t\tconst data = message.data;\n\t\t\t\t\t\t\t\tconst callback = this.jobCallbacks.get(data.jobId);\n\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\tcallback(data.error, {\n\t\t\t\t\t\t\t\t\t\t\t\tindices: data.indices,\n\t\t\t\t\t\t\t\t\t\t\t\tvertices: data.vertices,\n\t\t\t\t\t\t\t\t\t\t\t\tattributes: data.attributes\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.jobCallbacks.delete(data.jobId);\n\t\t\t\t\t\t\t\tif (this.jobQueue.length > 0) {\n\t\t\t\t\t\t\t\t\t\tconst job = this.jobQueue.shift();\n\t\t\t\t\t\t\t\t\t\tthis.run(worker, job);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst index2 = this.workers[2].indexOf(worker);\n\t\t\t\t\t\t\t\t\t\tif (index2 !== -1) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.workers[2].splice(index2, 1);\n\t\t\t\t\t\t\t\t\t\t\t\tthis.workers[1].push(worker);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tconst index1 = this.workers[1].indexOf(worker);\n\t\t\t\t\t\t\t\t\t\t\t\tif (index1 !== -1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.workers[1].splice(index1, 1);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.workers[0].push(worker);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tthis.workers[0] = workers;\n\t\t\t\twhile(this.jobQueue.length && (this.workers[0].length || this.workers[1].length)){\n\t\t\t\t\t\tconst job = this.jobQueue.shift();\n\t\t\t\t\t\tif (this.workers[0].length > 0) {\n\t\t\t\t\t\t\t\tconst worker = this.workers[0].shift();\n\t\t\t\t\t\t\t\tthis.workers[1].push(worker);\n\t\t\t\t\t\t\t\tthis.run(worker, job);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst worker = this.workers[1].shift();\n\t\t\t\t\t\t\t\tthis.workers[2].push(worker);\n\t\t\t\t\t\t\t\tthis.run(worker, job);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tenqueueJob(buffer, callback) {\n\t\t\t\tconst job = {\n\t\t\t\t\t\tjobId: this.jobId++,\n\t\t\t\t\t\tbuffer: buffer\n\t\t\t\t};\n\t\t\t\tthis.jobCallbacks.set(job.jobId, callback);\n\t\t\t\tif (this.workers[0].length > 0) {\n\t\t\t\t\t\tconst worker = this.workers[0].shift();\n\t\t\t\t\t\tthis.workers[1].push(worker);\n\t\t\t\t\t\tthis.run(worker, job);\n\t\t\t\t} else if (this.workers[1].length > 0) {\n\t\t\t\t\t\tconst worker = this.workers[1].shift();\n\t\t\t\t\t\tthis.workers[2].push(worker);\n\t\t\t\t\t\tthis.run(worker, job);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.jobQueue.push(job);\n\t\t\t\t}\n\t\t}\n}\nconst downloadScript = (url)=>{\n\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\tconst options = {\n\t\t\t\t\t\tcache: true,\n\t\t\t\t\t\tresponseType: 'text',\n\t\t\t\t\t\tretry: downloadMaxRetries > 0,\n\t\t\t\t\t\tmaxRetries: downloadMaxRetries\n\t\t\t\t};\n\t\t\t\thttp.get(url, options, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresolve(response);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n};\nconst compileModule = (url)=>{\n\t\tconst compileManual = ()=>{\n\t\t\t\treturn fetch(url).then((result)=>result.arrayBuffer()).then((buffer)=>WebAssembly.compile(buffer));\n\t\t};\n\t\tconst compileStreaming = ()=>{\n\t\t\t\treturn WebAssembly.compileStreaming(fetch(url)).catch((err)=>{\n\t\t\t\t\t\treturn compileManual();\n\t\t\t\t});\n\t\t};\n\t\treturn WebAssembly.compileStreaming ? compileStreaming() : compileManual();\n};\nconst defaultNumWorkers$1 = 1;\nlet jobQueue;\nconst initializeWorkers = (config)=>{\n\t\tif (jobQueue) {\n\t\t\t\treturn true;\n\t\t}\n\t\tif (!config) {\n\t\t\t\t{\n\t\t\t\t\t\tconst moduleConfig = WasmModule.getConfig('DracoDecoderModule');\n\t\t\t\t\t\tif (moduleConfig) {\n\t\t\t\t\t\t\t\tconfig = {\n\t\t\t\t\t\t\t\t\t\tjsUrl: moduleConfig.glueUrl,\n\t\t\t\t\t\t\t\t\t\twasmUrl: moduleConfig.wasmUrl,\n\t\t\t\t\t\t\t\t\t\tnumWorkers: moduleConfig.numWorkers\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconfig = {\n\t\t\t\t\t\t\t\t\t\tjsUrl: 'draco.wasm.js',\n\t\t\t\t\t\t\t\t\t\twasmUrl: 'draco.wasm.wasm',\n\t\t\t\t\t\t\t\t\t\tnumWorkers: defaultNumWorkers$1\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tif (!config.jsUrl || !config.wasmUrl) {\n\t\t\t\treturn false;\n\t\t}\n\t\tjobQueue = new JobQueue();\n\t\tPromise.all([\n\t\t\t\tdownloadScript(config.jsUrl),\n\t\t\t\tcompileModule(config.wasmUrl)\n\t\t]).then(([dracoSource, dracoModule])=>{\n\t\t\t\tconst code = [\n\t\t\t\t\t\t'/* draco */',\n\t\t\t\t\t\tdracoSource,\n\t\t\t\t\t\t'/* worker */',\n\t\t\t\t\t\t`(\\n${DracoWorker.toString()}\\n)()\\n\\n`\n\t\t\t\t].join('\\n');\n\t\t\t\tconst blob = new Blob([\n\t\t\t\t\t\tcode\n\t\t\t\t], {\n\t\t\t\t\t\ttype: \"application/javascript\"\n\t\t\t\t});\n\t\t\t\tconst workerUrl = URL.createObjectURL(blob);\n\t\t\t\tconst numWorkers = Math.max(1, Math.min(16, config.numWorkers || defaultNumWorkers$1));\n\t\t\t\tconst workers = [];\n\t\t\t\tfor(let i = 0; i < numWorkers; ++i){\n\t\t\t\t\t\tconst worker = new Worker(workerUrl);\n\t\t\t\t\t\tworker.postMessage({\n\t\t\t\t\t\t\t\ttype: 'init',\n\t\t\t\t\t\t\t\tmodule: dracoModule\n\t\t\t\t\t\t});\n\t\t\t\t\t\tworkers.push(worker);\n\t\t\t\t}\n\t\t\t\tjobQueue.init(workers);\n\t\t});\n\t\treturn true;\n};\nconst dracoDecode = (buffer, callback)=>{\n\t\tif (!initializeWorkers()) {\n\t\t\t\treturn false;\n\t\t}\n\t\tjobQueue.enqueueJob(buffer, callback);\n\t\treturn true;\n};\n\nclass GlbResources {\n\t\tdestroy() {\n\t\t\t\tif (this.renders) {\n\t\t\t\t\t\tthis.renders.forEach((render)=>{\n\t\t\t\t\t\t\t\trender.meshes = null;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n}\nconst isDataURI = (uri)=>{\n\t\treturn /^data:[^\\n\\r,\\u2028\\u2029]*,.*$/i.test(uri);\n};\nconst getDataURIMimeType = (uri)=>{\n\t\treturn uri.substring(uri.indexOf(':') + 1, uri.indexOf(';'));\n};\nconst getNumComponents = (accessorType)=>{\n\t\tswitch(accessorType){\n\t\t\t\tcase 'SCALAR':\n\t\t\t\t\t\treturn 1;\n\t\t\t\tcase 'VEC2':\n\t\t\t\t\t\treturn 2;\n\t\t\t\tcase 'VEC3':\n\t\t\t\t\t\treturn 3;\n\t\t\t\tcase 'VEC4':\n\t\t\t\t\t\treturn 4;\n\t\t\t\tcase 'MAT2':\n\t\t\t\t\t\treturn 4;\n\t\t\t\tcase 'MAT3':\n\t\t\t\t\t\treturn 9;\n\t\t\t\tcase 'MAT4':\n\t\t\t\t\t\treturn 16;\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn 3;\n\t\t}\n};\nconst getComponentType = (componentType)=>{\n\t\tswitch(componentType){\n\t\t\t\tcase 5120:\n\t\t\t\t\t\treturn TYPE_INT8;\n\t\t\t\tcase 5121:\n\t\t\t\t\t\treturn TYPE_UINT8;\n\t\t\t\tcase 5122:\n\t\t\t\t\t\treturn TYPE_INT16;\n\t\t\t\tcase 5123:\n\t\t\t\t\t\treturn TYPE_UINT16;\n\t\t\t\tcase 5124:\n\t\t\t\t\t\treturn TYPE_INT32;\n\t\t\t\tcase 5125:\n\t\t\t\t\t\treturn TYPE_UINT32;\n\t\t\t\tcase 5126:\n\t\t\t\t\t\treturn TYPE_FLOAT32;\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn 0;\n\t\t}\n};\nconst getComponentSizeInBytes = (componentType)=>{\n\t\tswitch(componentType){\n\t\t\t\tcase 5120:\n\t\t\t\t\t\treturn 1;\n\t\t\t\tcase 5121:\n\t\t\t\t\t\treturn 1;\n\t\t\t\tcase 5122:\n\t\t\t\t\t\treturn 2;\n\t\t\t\tcase 5123:\n\t\t\t\t\t\treturn 2;\n\t\t\t\tcase 5124:\n\t\t\t\t\t\treturn 4;\n\t\t\t\tcase 5125:\n\t\t\t\t\t\treturn 4;\n\t\t\t\tcase 5126:\n\t\t\t\t\t\treturn 4;\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn 0;\n\t\t}\n};\nconst getComponentDataType = (componentType)=>{\n\t\tswitch(componentType){\n\t\t\t\tcase 5120:\n\t\t\t\t\t\treturn Int8Array;\n\t\t\t\tcase 5121:\n\t\t\t\t\t\treturn Uint8Array;\n\t\t\t\tcase 5122:\n\t\t\t\t\t\treturn Int16Array;\n\t\t\t\tcase 5123:\n\t\t\t\t\t\treturn Uint16Array;\n\t\t\t\tcase 5124:\n\t\t\t\t\t\treturn Int32Array;\n\t\t\t\tcase 5125:\n\t\t\t\t\t\treturn Uint32Array;\n\t\t\t\tcase 5126:\n\t\t\t\t\t\treturn Float32Array;\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn null;\n\t\t}\n};\nconst gltfToEngineSemanticMap = {\n\t\t'POSITION': SEMANTIC_POSITION,\n\t\t'NORMAL': SEMANTIC_NORMAL,\n\t\t'TANGENT': SEMANTIC_TANGENT,\n\t\t'COLOR_0': SEMANTIC_COLOR,\n\t\t'JOINTS_0': SEMANTIC_BLENDINDICES,\n\t\t'WEIGHTS_0': SEMANTIC_BLENDWEIGHT,\n\t\t'TEXCOORD_0': SEMANTIC_TEXCOORD0,\n\t\t'TEXCOORD_1': SEMANTIC_TEXCOORD1,\n\t\t'TEXCOORD_2': SEMANTIC_TEXCOORD2,\n\t\t'TEXCOORD_3': SEMANTIC_TEXCOORD3,\n\t\t'TEXCOORD_4': SEMANTIC_TEXCOORD4,\n\t\t'TEXCOORD_5': SEMANTIC_TEXCOORD5,\n\t\t'TEXCOORD_6': SEMANTIC_TEXCOORD6,\n\t\t'TEXCOORD_7': SEMANTIC_TEXCOORD7\n};\nconst attributeOrder = {\n\t\t[SEMANTIC_POSITION]: 0,\n\t\t[SEMANTIC_NORMAL]: 1,\n\t\t[SEMANTIC_TANGENT]: 2,\n\t\t[SEMANTIC_COLOR]: 3,\n\t\t[SEMANTIC_BLENDINDICES]: 4,\n\t\t[SEMANTIC_BLENDWEIGHT]: 5,\n\t\t[SEMANTIC_TEXCOORD0]: 6,\n\t\t[SEMANTIC_TEXCOORD1]: 7,\n\t\t[SEMANTIC_TEXCOORD2]: 8,\n\t\t[SEMANTIC_TEXCOORD3]: 9,\n\t\t[SEMANTIC_TEXCOORD4]: 10,\n\t\t[SEMANTIC_TEXCOORD5]: 11,\n\t\t[SEMANTIC_TEXCOORD6]: 12,\n\t\t[SEMANTIC_TEXCOORD7]: 13\n};\nconst getDequantizeFunc = (srcType)=>{\n\t\tswitch(srcType){\n\t\t\t\tcase TYPE_INT8:\n\t\t\t\t\t\treturn (x)=>Math.max(x / 127.0, -1);\n\t\t\t\tcase TYPE_UINT8:\n\t\t\t\t\t\treturn (x)=>x / 255.0;\n\t\t\t\tcase TYPE_INT16:\n\t\t\t\t\t\treturn (x)=>Math.max(x / 32767.0, -1);\n\t\t\t\tcase TYPE_UINT16:\n\t\t\t\t\t\treturn (x)=>x / 65535.0;\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn (x)=>x;\n\t\t}\n};\nconst dequantizeArray = (dstArray, srcArray, srcType)=>{\n\t\tconst convFunc = getDequantizeFunc(srcType);\n\t\tconst len = srcArray.length;\n\t\tfor(let i = 0; i < len; ++i){\n\t\t\t\tdstArray[i] = convFunc(srcArray[i]);\n\t\t}\n\t\treturn dstArray;\n};\nconst getAccessorData = (gltfAccessor, bufferViews, flatten = false)=>{\n\t\tconst numComponents = getNumComponents(gltfAccessor.type);\n\t\tconst dataType = getComponentDataType(gltfAccessor.componentType);\n\t\tif (!dataType) {\n\t\t\t\treturn null;\n\t\t}\n\t\tlet result;\n\t\tif (gltfAccessor.sparse) {\n\t\t\t\tconst sparse = gltfAccessor.sparse;\n\t\t\t\tconst indicesAccessor = {\n\t\t\t\t\t\tcount: sparse.count,\n\t\t\t\t\t\ttype: 'SCALAR'\n\t\t\t\t};\n\t\t\t\tconst indices = getAccessorData(Object.assign(indicesAccessor, sparse.indices), bufferViews, true);\n\t\t\t\tconst valuesAccessor = {\n\t\t\t\t\t\tcount: sparse.count,\n\t\t\t\t\t\ttype: gltfAccessor.type,\n\t\t\t\t\t\tcomponentType: gltfAccessor.componentType\n\t\t\t\t};\n\t\t\t\tconst values = getAccessorData(Object.assign(valuesAccessor, sparse.values), bufferViews, true);\n\t\t\t\tif (gltfAccessor.hasOwnProperty('bufferView')) {\n\t\t\t\t\t\tconst baseAccessor = {\n\t\t\t\t\t\t\t\tbufferView: gltfAccessor.bufferView,\n\t\t\t\t\t\t\t\tbyteOffset: gltfAccessor.byteOffset,\n\t\t\t\t\t\t\t\tcomponentType: gltfAccessor.componentType,\n\t\t\t\t\t\t\t\tcount: gltfAccessor.count,\n\t\t\t\t\t\t\t\ttype: gltfAccessor.type\n\t\t\t\t\t\t};\n\t\t\t\t\t\tresult = getAccessorData(baseAccessor, bufferViews, true).slice();\n\t\t\t\t} else {\n\t\t\t\t\t\tresult = new dataType(gltfAccessor.count * numComponents);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < sparse.count; ++i){\n\t\t\t\t\t\tconst targetIndex = indices[i];\n\t\t\t\t\t\tfor(let j = 0; j < numComponents; ++j){\n\t\t\t\t\t\t\t\tresult[targetIndex * numComponents + j] = values[i * numComponents + j];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t} else {\n\t\t\t\tif (gltfAccessor.hasOwnProperty('bufferView')) {\n\t\t\t\t\t\tconst bufferView = bufferViews[gltfAccessor.bufferView];\n\t\t\t\t\t\tif (flatten && bufferView.hasOwnProperty('byteStride')) {\n\t\t\t\t\t\t\t\tconst bytesPerElement = numComponents * dataType.BYTES_PER_ELEMENT;\n\t\t\t\t\t\t\t\tconst storage = new ArrayBuffer(gltfAccessor.count * bytesPerElement);\n\t\t\t\t\t\t\t\tconst tmpArray = new Uint8Array(storage);\n\t\t\t\t\t\t\t\tlet dstOffset = 0;\n\t\t\t\t\t\t\t\tfor(let i = 0; i < gltfAccessor.count; ++i){\n\t\t\t\t\t\t\t\t\t\tlet srcOffset = (gltfAccessor.byteOffset || 0) + i * bufferView.byteStride;\n\t\t\t\t\t\t\t\t\t\tfor(let b = 0; b < bytesPerElement; ++b){\n\t\t\t\t\t\t\t\t\t\t\t\ttmpArray[dstOffset++] = bufferView[srcOffset++];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tresult = new dataType(storage);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresult = new dataType(bufferView.buffer, bufferView.byteOffset + (gltfAccessor.byteOffset || 0), gltfAccessor.count * numComponents);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tresult = new dataType(gltfAccessor.count * numComponents);\n\t\t\t\t}\n\t\t}\n\t\treturn result;\n};\nconst getAccessorDataFloat32 = (gltfAccessor, bufferViews)=>{\n\t\tconst data = getAccessorData(gltfAccessor, bufferViews, true);\n\t\tif (data instanceof Float32Array || !gltfAccessor.normalized) {\n\t\t\t\treturn data;\n\t\t}\n\t\tconst float32Data = new Float32Array(data.length);\n\t\tdequantizeArray(float32Data, data, getComponentType(gltfAccessor.componentType));\n\t\treturn float32Data;\n};\nconst getAccessorBoundingBox = (gltfAccessor)=>{\n\t\tlet min = gltfAccessor.min;\n\t\tlet max = gltfAccessor.max;\n\t\tif (!min || !max) {\n\t\t\t\treturn null;\n\t\t}\n\t\tif (gltfAccessor.normalized) {\n\t\t\t\tconst ctype = getComponentType(gltfAccessor.componentType);\n\t\t\t\tmin = dequantizeArray([], min, ctype);\n\t\t\t\tmax = dequantizeArray([], max, ctype);\n\t\t}\n\t\treturn new BoundingBox(new Vec3((max[0] + min[0]) * 0.5, (max[1] + min[1]) * 0.5, (max[2] + min[2]) * 0.5), new Vec3((max[0] - min[0]) * 0.5, (max[1] - min[1]) * 0.5, (max[2] - min[2]) * 0.5));\n};\nconst getPrimitiveType = (primitive)=>{\n\t\tif (!primitive.hasOwnProperty('mode')) {\n\t\t\t\treturn PRIMITIVE_TRIANGLES;\n\t\t}\n\t\tswitch(primitive.mode){\n\t\t\t\tcase 0:\n\t\t\t\t\t\treturn PRIMITIVE_POINTS;\n\t\t\t\tcase 1:\n\t\t\t\t\t\treturn PRIMITIVE_LINES;\n\t\t\t\tcase 2:\n\t\t\t\t\t\treturn PRIMITIVE_LINELOOP;\n\t\t\t\tcase 3:\n\t\t\t\t\t\treturn PRIMITIVE_LINESTRIP;\n\t\t\t\tcase 4:\n\t\t\t\t\t\treturn PRIMITIVE_TRIANGLES;\n\t\t\t\tcase 5:\n\t\t\t\t\t\treturn PRIMITIVE_TRISTRIP;\n\t\t\t\tcase 6:\n\t\t\t\t\t\treturn PRIMITIVE_TRIFAN;\n\t\t\t\tdefault:\n\t\t\t\t\t\treturn PRIMITIVE_TRIANGLES;\n\t\t}\n};\nconst generateIndices = (numVertices)=>{\n\t\tconst dummyIndices = new Uint16Array(numVertices);\n\t\tfor(let i = 0; i < numVertices; i++){\n\t\t\t\tdummyIndices[i] = i;\n\t\t}\n\t\treturn dummyIndices;\n};\nconst generateNormals = (sourceDesc, indices)=>{\n\t\tconst p = sourceDesc[SEMANTIC_POSITION];\n\t\tif (!p || p.components !== 3) {\n\t\t\t\treturn;\n\t\t}\n\t\tlet positions;\n\t\tif (p.size !== p.stride) {\n\t\t\t\tconst srcStride = p.stride / typedArrayTypesByteSize[p.type];\n\t\t\t\tconst src = new typedArrayTypes[p.type](p.buffer, p.offset, p.count * srcStride);\n\t\t\t\tpositions = new typedArrayTypes[p.type](p.count * 3);\n\t\t\t\tfor(let i = 0; i < p.count; ++i){\n\t\t\t\t\t\tpositions[i * 3 + 0] = src[i * srcStride + 0];\n\t\t\t\t\t\tpositions[i * 3 + 1] = src[i * srcStride + 1];\n\t\t\t\t\t\tpositions[i * 3 + 2] = src[i * srcStride + 2];\n\t\t\t\t}\n\t\t} else {\n\t\t\t\tpositions = new typedArrayTypes[p.type](p.buffer, p.offset, p.count * 3);\n\t\t}\n\t\tconst numVertices = p.count;\n\t\tif (!indices) {\n\t\t\t\tindices = generateIndices(numVertices);\n\t\t}\n\t\tconst normalsTemp = calculateNormals(positions, indices);\n\t\tconst normals = new Float32Array(normalsTemp.length);\n\t\tnormals.set(normalsTemp);\n\t\tsourceDesc[SEMANTIC_NORMAL] = {\n\t\t\t\tbuffer: normals.buffer,\n\t\t\t\tsize: 12,\n\t\t\t\toffset: 0,\n\t\t\t\tstride: 12,\n\t\t\t\tcount: numVertices,\n\t\t\t\tcomponents: 3,\n\t\t\t\ttype: TYPE_FLOAT32\n\t\t};\n};\nconst cloneTexture = (texture)=>{\n\t\tconst shallowCopyLevels = (texture)=>{\n\t\t\t\tconst result = [];\n\t\t\t\tfor(let mip = 0; mip < texture._levels.length; ++mip){\n\t\t\t\t\t\tlet level = [];\n\t\t\t\t\t\tif (texture.cubemap) {\n\t\t\t\t\t\t\t\tfor(let face = 0; face < 6; ++face){\n\t\t\t\t\t\t\t\t\t\tlevel.push(texture._levels[mip][face]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlevel = texture._levels[mip];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresult.push(level);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t};\n\t\tconst result = new Texture(texture.device, texture);\n\t\tresult._levels = shallowCopyLevels(texture);\n\t\treturn result;\n};\nconst cloneTextureAsset = (src)=>{\n\t\tconst result = new Asset(`${src.name}_clone`, src.type, src.file, src.data, src.options);\n\t\tresult.loaded = true;\n\t\tresult.resource = cloneTexture(src.resource);\n\t\tsrc.registry.add(result);\n\t\treturn result;\n};\nconst createVertexBufferInternal = (device, sourceDesc)=>{\n\t\tconst positionDesc = sourceDesc[SEMANTIC_POSITION];\n\t\tif (!positionDesc) {\n\t\t\t\treturn null;\n\t\t}\n\t\tconst numVertices = positionDesc.count;\n\t\tconst vertexDesc = [];\n\t\tfor(const semantic in sourceDesc){\n\t\t\t\tif (sourceDesc.hasOwnProperty(semantic)) {\n\t\t\t\t\t\tconst element = {\n\t\t\t\t\t\t\t\tsemantic: semantic,\n\t\t\t\t\t\t\t\tcomponents: sourceDesc[semantic].components,\n\t\t\t\t\t\t\t\ttype: sourceDesc[semantic].type,\n\t\t\t\t\t\t\t\tnormalize: !!sourceDesc[semantic].normalize\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (!VertexFormat.isElementValid(device, element)) {\n\t\t\t\t\t\t\t\telement.components++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvertexDesc.push(element);\n\t\t\t\t}\n\t\t}\n\t\tvertexDesc.sort((lhs, rhs)=>{\n\t\t\t\treturn attributeOrder[lhs.semantic] - attributeOrder[rhs.semantic];\n\t\t});\n\t\tlet i, j, k;\n\t\tlet source, target, sourceOffset;\n\t\tconst vertexFormat = new VertexFormat(device, vertexDesc);\n\t\tlet isCorrectlyInterleaved = true;\n\t\tfor(i = 0; i < vertexFormat.elements.length; ++i){\n\t\t\t\ttarget = vertexFormat.elements[i];\n\t\t\t\tsource = sourceDesc[target.name];\n\t\t\t\tsourceOffset = source.offset - positionDesc.offset;\n\t\t\t\tif (source.buffer !== positionDesc.buffer || source.stride !== target.stride || source.size !== target.size || sourceOffset !== target.offset) {\n\t\t\t\t\t\tisCorrectlyInterleaved = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\tconst vertexBuffer = new VertexBuffer(device, vertexFormat, numVertices);\n\t\tconst vertexData = vertexBuffer.lock();\n\t\tconst targetArray = new Uint32Array(vertexData);\n\t\tlet sourceArray;\n\t\tif (isCorrectlyInterleaved) {\n\t\t\t\tsourceArray = new Uint32Array(positionDesc.buffer, positionDesc.offset, numVertices * vertexBuffer.format.size / 4);\n\t\t\t\ttargetArray.set(sourceArray);\n\t\t} else {\n\t\t\t\tlet targetStride, sourceStride;\n\t\t\t\tfor(i = 0; i < vertexBuffer.format.elements.length; ++i){\n\t\t\t\t\t\ttarget = vertexBuffer.format.elements[i];\n\t\t\t\t\t\ttargetStride = target.stride / 4;\n\t\t\t\t\t\tsource = sourceDesc[target.name];\n\t\t\t\t\t\tsourceStride = source.stride / 4;\n\t\t\t\t\t\tsourceArray = new Uint32Array(source.buffer, source.offset, (source.count - 1) * sourceStride + (source.size + 3) / 4);\n\t\t\t\t\t\tlet src = 0;\n\t\t\t\t\t\tlet dst = target.offset / 4;\n\t\t\t\t\t\tconst kend = Math.floor((source.size + 3) / 4);\n\t\t\t\t\t\tfor(j = 0; j < numVertices; ++j){\n\t\t\t\t\t\t\t\tfor(k = 0; k < kend; ++k){\n\t\t\t\t\t\t\t\t\t\ttargetArray[dst + k] = sourceArray[src + k];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsrc += sourceStride;\n\t\t\t\t\t\t\t\tdst += targetStride;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tvertexBuffer.unlock();\n\t\treturn vertexBuffer;\n};\nconst createVertexBuffer = (device, attributes, indices, accessors, bufferViews, vertexBufferDict)=>{\n\t\tconst useAttributes = {};\n\t\tconst attribIds = [];\n\t\tfor(const attrib in attributes){\n\t\t\t\tif (attributes.hasOwnProperty(attrib) && gltfToEngineSemanticMap.hasOwnProperty(attrib)) {\n\t\t\t\t\t\tuseAttributes[attrib] = attributes[attrib];\n\t\t\t\t\t\tattribIds.push(`${attrib}:${attributes[attrib]}`);\n\t\t\t\t}\n\t\t}\n\t\tattribIds.sort();\n\t\tconst vbKey = attribIds.join();\n\t\tlet vb = vertexBufferDict[vbKey];\n\t\tif (!vb) {\n\t\t\t\tconst sourceDesc = {};\n\t\t\t\tfor(const attrib in useAttributes){\n\t\t\t\t\t\tconst accessor = accessors[attributes[attrib]];\n\t\t\t\t\t\tconst accessorData = getAccessorData(accessor, bufferViews);\n\t\t\t\t\t\tconst bufferView = bufferViews[accessor.bufferView];\n\t\t\t\t\t\tconst semantic = gltfToEngineSemanticMap[attrib];\n\t\t\t\t\t\tconst size = getNumComponents(accessor.type) * getComponentSizeInBytes(accessor.componentType);\n\t\t\t\t\t\tconst stride = bufferView && bufferView.hasOwnProperty('byteStride') ? bufferView.byteStride : size;\n\t\t\t\t\t\tsourceDesc[semantic] = {\n\t\t\t\t\t\t\t\tbuffer: accessorData.buffer,\n\t\t\t\t\t\t\t\tsize: size,\n\t\t\t\t\t\t\t\toffset: accessorData.byteOffset,\n\t\t\t\t\t\t\t\tstride: stride,\n\t\t\t\t\t\t\t\tcount: accessor.count,\n\t\t\t\t\t\t\t\tcomponents: getNumComponents(accessor.type),\n\t\t\t\t\t\t\t\ttype: getComponentType(accessor.componentType),\n\t\t\t\t\t\t\t\tnormalize: accessor.normalized\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (!sourceDesc.hasOwnProperty(SEMANTIC_NORMAL)) {\n\t\t\t\t\t\tgenerateNormals(sourceDesc, indices);\n\t\t\t\t}\n\t\t\t\tvb = createVertexBufferInternal(device, sourceDesc);\n\t\t\t\tvertexBufferDict[vbKey] = vb;\n\t\t}\n\t\treturn vb;\n};\nconst createSkin = (device, gltfSkin, accessors, bufferViews, nodes, glbSkins)=>{\n\t\tlet i, j, bindMatrix;\n\t\tconst joints = gltfSkin.joints;\n\t\tconst numJoints = joints.length;\n\t\tconst ibp = [];\n\t\tif (gltfSkin.hasOwnProperty('inverseBindMatrices')) {\n\t\t\t\tconst inverseBindMatrices = gltfSkin.inverseBindMatrices;\n\t\t\t\tconst ibmData = getAccessorData(accessors[inverseBindMatrices], bufferViews, true);\n\t\t\t\tconst ibmValues = [];\n\t\t\t\tfor(i = 0; i < numJoints; i++){\n\t\t\t\t\t\tfor(j = 0; j < 16; j++){\n\t\t\t\t\t\t\t\tibmValues[j] = ibmData[i * 16 + j];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbindMatrix = new Mat4();\n\t\t\t\t\t\tbindMatrix.set(ibmValues);\n\t\t\t\t\t\tibp.push(bindMatrix);\n\t\t\t\t}\n\t\t} else {\n\t\t\t\tfor(i = 0; i < numJoints; i++){\n\t\t\t\t\t\tbindMatrix = new Mat4();\n\t\t\t\t\t\tibp.push(bindMatrix);\n\t\t\t\t}\n\t\t}\n\t\tconst boneNames = [];\n\t\tfor(i = 0; i < numJoints; i++){\n\t\t\t\tboneNames[i] = nodes[joints[i]].name;\n\t\t}\n\t\tconst key = boneNames.join('#');\n\t\tlet skin = glbSkins.get(key);\n\t\tif (!skin) {\n\t\t\t\tskin = new Skin(device, ibp, boneNames);\n\t\t\t\tglbSkins.set(key, skin);\n\t\t}\n\t\treturn skin;\n};\nconst createDracoMesh = (device, primitive, accessors, bufferViews, meshVariants, meshDefaultMaterials, promises)=>{\n\t\tconst result = new Mesh(device);\n\t\tresult.aabb = getAccessorBoundingBox(accessors[primitive.attributes.POSITION]);\n\t\tconst vertexDesc = [];\n\t\tfor (const [name, index] of Object.entries(primitive.attributes)){\n\t\t\t\tconst accessor = accessors[index];\n\t\t\t\tconst semantic = gltfToEngineSemanticMap[name];\n\t\t\t\tconst componentType = getComponentType(accessor.componentType);\n\t\t\t\tvertexDesc.push({\n\t\t\t\t\t\tsemantic: semantic,\n\t\t\t\t\t\tcomponents: getNumComponents(accessor.type),\n\t\t\t\t\t\ttype: componentType,\n\t\t\t\t\t\tnormalize: accessor.normalized ?? (semantic === SEMANTIC_COLOR && (componentType === TYPE_UINT8 || componentType === TYPE_UINT16))\n\t\t\t\t});\n\t\t}\n\t\tpromises.push(new Promise((resolve, reject)=>{\n\t\t\t\tconst dracoExt = primitive.extensions.KHR_draco_mesh_compression;\n\t\t\t\tdracoDecode(bufferViews[dracoExt.bufferView].slice().buffer, (err, decompressedData)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tconsole.log(err);\n\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst order = {};\n\t\t\t\t\t\t\t\tfor (const [name, index] of Object.entries(dracoExt.attributes)){\n\t\t\t\t\t\t\t\t\t\torder[gltfToEngineSemanticMap[name]] = decompressedData.attributes.indexOf(index);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tvertexDesc.sort((a, b)=>{\n\t\t\t\t\t\t\t\t\t\treturn order[a.semantic] - order[b.semantic];\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (!primitive.attributes?.NORMAL) {\n\t\t\t\t\t\t\t\t\t\tvertexDesc.splice(1, 0, {\n\t\t\t\t\t\t\t\t\t\t\t\tsemantic: 'NORMAL',\n\t\t\t\t\t\t\t\t\t\t\t\tcomponents: 3,\n\t\t\t\t\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst vertexFormat = new VertexFormat(device, vertexDesc);\n\t\t\t\t\t\t\t\tconst numVertices = decompressedData.vertices.byteLength / vertexFormat.size;\n\t\t\t\t\t\t\t\tconst indexFormat = numVertices <= 65535 ? INDEXFORMAT_UINT16 : INDEXFORMAT_UINT32;\n\t\t\t\t\t\t\t\tconst numIndices = decompressedData.indices.byteLength / (numVertices <= 65535 ? 2 : 4);\n\t\t\t\t\t\t\t\tconst vertexBuffer = new VertexBuffer(device, vertexFormat, numVertices, {\n\t\t\t\t\t\t\t\t\t\tdata: decompressedData.vertices\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tconst indexBuffer = new IndexBuffer(device, indexFormat, numIndices, BUFFER_STATIC, decompressedData.indices);\n\t\t\t\t\t\t\t\tresult.vertexBuffer = vertexBuffer;\n\t\t\t\t\t\t\t\tresult.indexBuffer[0] = indexBuffer;\n\t\t\t\t\t\t\t\tresult.primitive[0].type = getPrimitiveType(primitive);\n\t\t\t\t\t\t\t\tresult.primitive[0].base = 0;\n\t\t\t\t\t\t\t\tresult.primitive[0].count = indexBuffer ? numIndices : numVertices;\n\t\t\t\t\t\t\t\tresult.primitive[0].indexed = !!indexBuffer;\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}));\n\t\tif (primitive?.extensions?.KHR_materials_variants) {\n\t\t\t\tconst variants = primitive.extensions.KHR_materials_variants;\n\t\t\t\tconst tempMapping = {};\n\t\t\t\tvariants.mappings.forEach((mapping)=>{\n\t\t\t\t\t\tmapping.variants.forEach((variant)=>{\n\t\t\t\t\t\t\t\ttempMapping[variant] = mapping.material;\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tmeshVariants[result.id] = tempMapping;\n\t\t}\n\t\tmeshDefaultMaterials[result.id] = primitive.material;\n\t\treturn result;\n};\nconst createMesh = (device, gltfMesh, accessors, bufferViews, vertexBufferDict, meshVariants, meshDefaultMaterials, assetOptions, promises)=>{\n\t\tconst meshes = [];\n\t\tgltfMesh.primitives.forEach((primitive)=>{\n\t\t\t\tif (primitive.extensions?.KHR_draco_mesh_compression) {\n\t\t\t\t\t\tmeshes.push(createDracoMesh(device, primitive, accessors, bufferViews, meshVariants, meshDefaultMaterials, promises));\n\t\t\t\t} else {\n\t\t\t\t\t\tlet indices = primitive.hasOwnProperty('indices') ? getAccessorData(accessors[primitive.indices], bufferViews, true) : null;\n\t\t\t\t\t\tconst vertexBuffer = createVertexBuffer(device, primitive.attributes, indices, accessors, bufferViews, vertexBufferDict);\n\t\t\t\t\t\tconst primitiveType = getPrimitiveType(primitive);\n\t\t\t\t\t\tconst mesh = new Mesh(device);\n\t\t\t\t\t\tmesh.vertexBuffer = vertexBuffer;\n\t\t\t\t\t\tmesh.primitive[0].type = primitiveType;\n\t\t\t\t\t\tmesh.primitive[0].base = 0;\n\t\t\t\t\t\tmesh.primitive[0].indexed = indices !== null;\n\t\t\t\t\t\tif (indices !== null) {\n\t\t\t\t\t\t\t\tlet indexFormat;\n\t\t\t\t\t\t\t\tif (indices instanceof Uint8Array) {\n\t\t\t\t\t\t\t\t\t\tindexFormat = INDEXFORMAT_UINT8;\n\t\t\t\t\t\t\t\t} else if (indices instanceof Uint16Array) {\n\t\t\t\t\t\t\t\t\t\tindexFormat = INDEXFORMAT_UINT16;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tindexFormat = INDEXFORMAT_UINT32;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (indexFormat === INDEXFORMAT_UINT8 && device.isWebGPU) {\n\t\t\t\t\t\t\t\t\t\tindexFormat = INDEXFORMAT_UINT16;\n\t\t\t\t\t\t\t\t\t\tindices = new Uint16Array(indices);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst indexBuffer = new IndexBuffer(device, indexFormat, indices.length, BUFFER_STATIC, indices);\n\t\t\t\t\t\t\t\tmesh.indexBuffer[0] = indexBuffer;\n\t\t\t\t\t\t\t\tmesh.primitive[0].count = indices.length;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmesh.primitive[0].count = vertexBuffer.numVertices;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (primitive.hasOwnProperty('extensions') && primitive.extensions.hasOwnProperty('KHR_materials_variants')) {\n\t\t\t\t\t\t\t\tconst variants = primitive.extensions.KHR_materials_variants;\n\t\t\t\t\t\t\t\tconst tempMapping = {};\n\t\t\t\t\t\t\t\tvariants.mappings.forEach((mapping)=>{\n\t\t\t\t\t\t\t\t\t\tmapping.variants.forEach((variant)=>{\n\t\t\t\t\t\t\t\t\t\t\t\ttempMapping[variant] = mapping.material;\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tmeshVariants[mesh.id] = tempMapping;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmeshDefaultMaterials[mesh.id] = primitive.material;\n\t\t\t\t\t\tlet accessor = accessors[primitive.attributes.POSITION];\n\t\t\t\t\t\tmesh.aabb = getAccessorBoundingBox(accessor);\n\t\t\t\t\t\tif (primitive.hasOwnProperty('targets')) {\n\t\t\t\t\t\t\t\tconst targets = [];\n\t\t\t\t\t\t\t\tprimitive.targets.forEach((target, index)=>{\n\t\t\t\t\t\t\t\t\t\tconst options = {};\n\t\t\t\t\t\t\t\t\t\tif (target.hasOwnProperty('POSITION')) {\n\t\t\t\t\t\t\t\t\t\t\t\taccessor = accessors[target.POSITION];\n\t\t\t\t\t\t\t\t\t\t\t\toptions.deltaPositions = getAccessorDataFloat32(accessor, bufferViews);\n\t\t\t\t\t\t\t\t\t\t\t\toptions.aabb = getAccessorBoundingBox(accessor);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (target.hasOwnProperty('NORMAL')) {\n\t\t\t\t\t\t\t\t\t\t\t\taccessor = accessors[target.NORMAL];\n\t\t\t\t\t\t\t\t\t\t\t\toptions.deltaNormals = getAccessorDataFloat32(accessor, bufferViews);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (gltfMesh.hasOwnProperty('extras') && gltfMesh.extras.hasOwnProperty('targetNames')) {\n\t\t\t\t\t\t\t\t\t\t\t\toptions.name = gltfMesh.extras.targetNames[index];\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\toptions.name = index.toString(10);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (gltfMesh.hasOwnProperty('weights')) {\n\t\t\t\t\t\t\t\t\t\t\t\toptions.defaultWeight = gltfMesh.weights[index];\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\toptions.preserveData = assetOptions.morphPreserveData;\n\t\t\t\t\t\t\t\t\t\ttargets.push(new MorphTarget(options));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tmesh.morph = new Morph(targets, device, {\n\t\t\t\t\t\t\t\t\t\tpreferHighPrecision: assetOptions.morphPreferHighPrecision\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmeshes.push(mesh);\n\t\t\t\t}\n\t\t});\n\t\treturn meshes;\n};\nconst extractTextureTransform = (source, material, maps)=>{\n\t\tlet map;\n\t\tconst texCoord = source.texCoord;\n\t\tif (texCoord) {\n\t\t\t\tfor(map = 0; map < maps.length; ++map){\n\t\t\t\t\t\tmaterial[`${maps[map]}MapUv`] = texCoord;\n\t\t\t\t}\n\t\t}\n\t\tconst zeros = [\n\t\t\t\t0,\n\t\t\t\t0\n\t\t];\n\t\tconst ones = [\n\t\t\t\t1,\n\t\t\t\t1\n\t\t];\n\t\tconst textureTransform = source.extensions?.KHR_texture_transform;\n\t\tif (textureTransform) {\n\t\t\t\tconst offset = textureTransform.offset || zeros;\n\t\t\t\tconst scale = textureTransform.scale || ones;\n\t\t\t\tconst rotation = textureTransform.rotation ? -textureTransform.rotation * math.RAD_TO_DEG : 0;\n\t\t\t\tconst tilingVec = new Vec2(scale[0], scale[1]);\n\t\t\t\tconst offsetVec = new Vec2(offset[0], 1.0 - scale[1] - offset[1]);\n\t\t\t\tfor(map = 0; map < maps.length; ++map){\n\t\t\t\t\t\tmaterial[`${maps[map]}MapTiling`] = tilingVec;\n\t\t\t\t\t\tmaterial[`${maps[map]}MapOffset`] = offsetVec;\n\t\t\t\t\t\tmaterial[`${maps[map]}MapRotation`] = rotation;\n\t\t\t\t}\n\t\t}\n};\nconst extensionPbrSpecGlossiness = (data, material, textures)=>{\n\t\tlet color, texture;\n\t\tif (data.hasOwnProperty('diffuseFactor')) {\n\t\t\t\tcolor = data.diffuseFactor;\n\t\t\t\tmaterial.diffuse.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));\n\t\t\t\tmaterial.opacity = color[3];\n\t\t} else {\n\t\t\t\tmaterial.diffuse.set(1, 1, 1);\n\t\t\t\tmaterial.opacity = 1;\n\t\t}\n\t\tif (data.hasOwnProperty('diffuseTexture')) {\n\t\t\t\tconst diffuseTexture = data.diffuseTexture;\n\t\t\t\ttexture = textures[diffuseTexture.index];\n\t\t\t\tmaterial.diffuseMap = texture;\n\t\t\t\tmaterial.diffuseMapChannel = 'rgb';\n\t\t\t\tmaterial.opacityMap = texture;\n\t\t\t\tmaterial.opacityMapChannel = 'a';\n\t\t\t\textractTextureTransform(diffuseTexture, material, [\n\t\t\t\t\t\t'diffuse',\n\t\t\t\t\t\t'opacity'\n\t\t\t\t]);\n\t\t}\n\t\tmaterial.useMetalness = false;\n\t\tif (data.hasOwnProperty('specularFactor')) {\n\t\t\t\tcolor = data.specularFactor;\n\t\t\t\tmaterial.specular.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));\n\t\t} else {\n\t\t\t\tmaterial.specular.set(1, 1, 1);\n\t\t}\n\t\tif (data.hasOwnProperty('glossinessFactor')) {\n\t\t\t\tmaterial.gloss = data.glossinessFactor;\n\t\t} else {\n\t\t\t\tmaterial.gloss = 1.0;\n\t\t}\n\t\tif (data.hasOwnProperty('specularGlossinessTexture')) {\n\t\t\t\tconst specularGlossinessTexture = data.specularGlossinessTexture;\n\t\t\t\tmaterial.specularMap = material.glossMap = textures[specularGlossinessTexture.index];\n\t\t\t\tmaterial.specularMapChannel = 'rgb';\n\t\t\t\tmaterial.glossMapChannel = 'a';\n\t\t\t\textractTextureTransform(specularGlossinessTexture, material, [\n\t\t\t\t\t\t'gloss',\n\t\t\t\t\t\t'metalness'\n\t\t\t\t]);\n\t\t}\n};\nconst extensionClearCoat = (data, material, textures)=>{\n\t\tif (data.hasOwnProperty('clearcoatFactor')) {\n\t\t\t\tmaterial.clearCoat = data.clearcoatFactor * 0.25;\n\t\t} else {\n\t\t\t\tmaterial.clearCoat = 0;\n\t\t}\n\t\tif (data.hasOwnProperty('clearcoatTexture')) {\n\t\t\t\tconst clearcoatTexture = data.clearcoatTexture;\n\t\t\t\tmaterial.clearCoatMap = textures[clearcoatTexture.index];\n\t\t\t\tmaterial.clearCoatMapChannel = 'r';\n\t\t\t\textractTextureTransform(clearcoatTexture, material, [\n\t\t\t\t\t\t'clearCoat'\n\t\t\t\t]);\n\t\t}\n\t\tif (data.hasOwnProperty('clearcoatRoughnessFactor')) {\n\t\t\t\tmaterial.clearCoatGloss = data.clearcoatRoughnessFactor;\n\t\t} else {\n\t\t\t\tmaterial.clearCoatGloss = 0;\n\t\t}\n\t\tif (data.hasOwnProperty('clearcoatRoughnessTexture')) {\n\t\t\t\tconst clearcoatRoughnessTexture = data.clearcoatRoughnessTexture;\n\t\t\t\tmaterial.clearCoatGlossMap = textures[clearcoatRoughnessTexture.index];\n\t\t\t\tmaterial.clearCoatGlossMapChannel = 'g';\n\t\t\t\textractTextureTransform(clearcoatRoughnessTexture, material, [\n\t\t\t\t\t\t'clearCoatGloss'\n\t\t\t\t]);\n\t\t}\n\t\tif (data.hasOwnProperty('clearcoatNormalTexture')) {\n\t\t\t\tconst clearcoatNormalTexture = data.clearcoatNormalTexture;\n\t\t\t\tmaterial.clearCoatNormalMap = textures[clearcoatNormalTexture.index];\n\t\t\t\textractTextureTransform(clearcoatNormalTexture, material, [\n\t\t\t\t\t\t'clearCoatNormal'\n\t\t\t\t]);\n\t\t\t\tif (clearcoatNormalTexture.hasOwnProperty('scale')) {\n\t\t\t\t\t\tmaterial.clearCoatBumpiness = clearcoatNormalTexture.scale;\n\t\t\t\t} else {\n\t\t\t\t\t\tmaterial.clearCoatBumpiness = 1;\n\t\t\t\t}\n\t\t}\n\t\tmaterial.clearCoatGlossInvert = true;\n};\nconst extensionUnlit = (data, material, textures)=>{\n\t\tmaterial.useLighting = false;\n\t\tmaterial.emissive.copy(material.diffuse);\n\t\tmaterial.emissiveMap = material.diffuseMap;\n\t\tmaterial.emissiveMapUv = material.diffuseMapUv;\n\t\tmaterial.emissiveMapTiling.copy(material.diffuseMapTiling);\n\t\tmaterial.emissiveMapOffset.copy(material.diffuseMapOffset);\n\t\tmaterial.emissiveMapRotation = material.diffuseMapRotation;\n\t\tmaterial.emissiveMapChannel = material.diffuseMapChannel;\n\t\tmaterial.emissiveVertexColor = material.diffuseVertexColor;\n\t\tmaterial.emissiveVertexColorChannel = material.diffuseVertexColorChannel;\n\t\tmaterial.useLighting = false;\n\t\tmaterial.useSkybox = false;\n\t\tmaterial.diffuse.set(1, 1, 1);\n\t\tmaterial.diffuseMap = null;\n\t\tmaterial.diffuseVertexColor = false;\n};\nconst extensionSpecular = (data, material, textures)=>{\n\t\tmaterial.useMetalnessSpecularColor = true;\n\t\tif (data.hasOwnProperty('specularColorTexture')) {\n\t\t\t\tmaterial.specularMap = textures[data.specularColorTexture.index];\n\t\t\t\tmaterial.specularMapChannel = 'rgb';\n\t\t\t\textractTextureTransform(data.specularColorTexture, material, [\n\t\t\t\t\t\t'specular'\n\t\t\t\t]);\n\t\t}\n\t\tif (data.hasOwnProperty('specularColorFactor')) {\n\t\t\t\tconst color = data.specularColorFactor;\n\t\t\t\tmaterial.specular.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));\n\t\t} else {\n\t\t\t\tmaterial.specular.set(1, 1, 1);\n\t\t}\n\t\tif (data.hasOwnProperty('specularFactor')) {\n\t\t\t\tmaterial.specularityFactor = data.specularFactor;\n\t\t} else {\n\t\t\t\tmaterial.specularityFactor = 1;\n\t\t}\n\t\tif (data.hasOwnProperty('specularTexture')) {\n\t\t\t\tmaterial.specularityFactorMapChannel = 'a';\n\t\t\t\tmaterial.specularityFactorMap = textures[data.specularTexture.index];\n\t\t\t\textractTextureTransform(data.specularTexture, material, [\n\t\t\t\t\t\t'specularityFactor'\n\t\t\t\t]);\n\t\t}\n};\nconst extensionIor = (data, material, textures)=>{\n\t\tif (data.hasOwnProperty('ior')) {\n\t\t\t\tmaterial.refractionIndex = 1.0 / data.ior;\n\t\t}\n};\nconst extensionDispersion = (data, material, textures)=>{\n\t\tif (data.hasOwnProperty('dispersion')) {\n\t\t\t\tmaterial.dispersion = data.dispersion;\n\t\t}\n};\nconst extensionTransmission = (data, material, textures)=>{\n\t\tmaterial.blendType = BLEND_NORMAL;\n\t\tmaterial.useDynamicRefraction = true;\n\t\tif (data.hasOwnProperty('transmissionFactor')) {\n\t\t\t\tmaterial.refraction = data.transmissionFactor;\n\t\t}\n\t\tif (data.hasOwnProperty('transmissionTexture')) {\n\t\t\t\tmaterial.refractionMapChannel = 'r';\n\t\t\t\tmaterial.refractionMap = textures[data.transmissionTexture.index];\n\t\t\t\textractTextureTransform(data.transmissionTexture, material, [\n\t\t\t\t\t\t'refraction'\n\t\t\t\t]);\n\t\t}\n};\nconst extensionSheen = (data, material, textures)=>{\n\t\tmaterial.useSheen = true;\n\t\tif (data.hasOwnProperty('sheenColorFactor')) {\n\t\t\t\tconst color = data.sheenColorFactor;\n\t\t\t\tmaterial.sheen.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));\n\t\t} else {\n\t\t\t\tmaterial.sheen.set(1, 1, 1);\n\t\t}\n\t\tif (data.hasOwnProperty('sheenColorTexture')) {\n\t\t\t\tmaterial.sheenMap = textures[data.sheenColorTexture.index];\n\t\t\t\textractTextureTransform(data.sheenColorTexture, material, [\n\t\t\t\t\t\t'sheen'\n\t\t\t\t]);\n\t\t}\n\t\tmaterial.sheenGloss = data.hasOwnProperty('sheenRoughnessFactor') ? data.sheenRoughnessFactor : 0.0;\n\t\tif (data.hasOwnProperty('sheenRoughnessTexture')) {\n\t\t\t\tmaterial.sheenGlossMap = textures[data.sheenRoughnessTexture.index];\n\t\t\t\tmaterial.sheenGlossMapChannel = 'a';\n\t\t\t\textractTextureTransform(data.sheenRoughnessTexture, material, [\n\t\t\t\t\t\t'sheenGloss'\n\t\t\t\t]);\n\t\t}\n\t\tmaterial.sheenGlossInvert = true;\n};\nconst extensionVolume = (data, material, textures)=>{\n\t\tmaterial.blendType = BLEND_NORMAL;\n\t\tmaterial.useDynamicRefraction = true;\n\t\tif (data.hasOwnProperty('thicknessFactor')) {\n\t\t\t\tmaterial.thickness = data.thicknessFactor;\n\t\t}\n\t\tif (data.hasOwnProperty('thicknessTexture')) {\n\t\t\t\tmaterial.thicknessMap = textures[data.thicknessTexture.index];\n\t\t\t\tmaterial.thicknessMapChannel = 'g';\n\t\t\t\textractTextureTransform(data.thicknessTexture, material, [\n\t\t\t\t\t\t'thickness'\n\t\t\t\t]);\n\t\t}\n\t\tif (data.hasOwnProperty('attenuationDistance')) {\n\t\t\t\tmaterial.attenuationDistance = data.attenuationDistance;\n\t\t}\n\t\tif (data.hasOwnProperty('attenuationColor')) {\n\t\t\t\tconst color = data.attenuationColor;\n\t\t\t\tmaterial.attenuation.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));\n\t\t}\n};\nconst extensionEmissiveStrength = (data, material, textures)=>{\n\t\tif (data.hasOwnProperty('emissiveStrength')) {\n\t\t\t\tmaterial.emissiveIntensity = data.emissiveStrength;\n\t\t}\n};\nconst extensionIridescence = (data, material, textures)=>{\n\t\tmaterial.useIridescence = true;\n\t\tif (data.hasOwnProperty('iridescenceFactor')) {\n\t\t\t\tmaterial.iridescence = data.iridescenceFactor;\n\t\t}\n\t\tif (data.hasOwnProperty('iridescenceTexture')) {\n\t\t\t\tmaterial.iridescenceMapChannel = 'r';\n\t\t\t\tmaterial.iridescenceMap = textures[data.iridescenceTexture.index];\n\t\t\t\textractTextureTransform(data.iridescenceTexture, material, [\n\t\t\t\t\t\t'iridescence'\n\t\t\t\t]);\n\t\t}\n\t\tif (data.hasOwnProperty('iridescenceIor')) {\n\t\t\t\tmaterial.iridescenceRefractionIndex = data.iridescenceIor;\n\t\t}\n\t\tif (data.hasOwnProperty('iridescenceThicknessMinimum')) {\n\t\t\t\tmaterial.iridescenceThicknessMin = data.iridescenceThicknessMinimum;\n\t\t}\n\t\tif (data.hasOwnProperty('iridescenceThicknessMaximum')) {\n\t\t\t\tmaterial.iridescenceThicknessMax = data.iridescenceThicknessMaximum;\n\t\t}\n\t\tif (data.hasOwnProperty('iridescenceThicknessTexture')) {\n\t\t\t\tmaterial.iridescenceThicknessMapChannel = 'g';\n\t\t\t\tmaterial.iridescenceThicknessMap = textures[data.iridescenceThicknessTexture.index];\n\t\t\t\textractTextureTransform(data.iridescenceThicknessTexture, material, [\n\t\t\t\t\t\t'iridescenceThickness'\n\t\t\t\t]);\n\t\t}\n};\nconst extensionAnisotropy = (data, material, textures)=>{\n\t\tmaterial.enableGGXSpecular = true;\n\t\tif (data.hasOwnProperty('anisotropyStrength')) {\n\t\t\t\tmaterial.anisotropyIntensity = data.anisotropyStrength;\n\t\t} else {\n\t\t\t\tmaterial.anisotropyIntensity = 0;\n\t\t}\n\t\tif (data.hasOwnProperty('anisotropyTexture')) {\n\t\t\t\tconst anisotropyTexture = data.anisotropyTexture;\n\t\t\t\tmaterial.anisotropyMap = textures[anisotropyTexture.index];\n\t\t\t\textractTextureTransform(anisotropyTexture, material, [\n\t\t\t\t\t\t'anisotropy'\n\t\t\t\t]);\n\t\t}\n\t\tif (data.hasOwnProperty('anisotropyRotation')) {\n\t\t\t\tmaterial.anisotropyRotation = data.anisotropyRotation * math.RAD_TO_DEG;\n\t\t} else {\n\t\t\t\tmaterial.anisotropyRotation = 0;\n\t\t}\n};\nconst createMaterial = (gltfMaterial, textures)=>{\n\t\tconst material = new StandardMaterial();\n\t\tif (gltfMaterial.hasOwnProperty('name')) {\n\t\t\t\tmaterial.name = gltfMaterial.name;\n\t\t}\n\t\tmaterial.occludeSpecular = SPECOCC_AO;\n\t\tmaterial.diffuseVertexColor = true;\n\t\tmaterial.specularTint = true;\n\t\tmaterial.specularVertexColor = true;\n\t\tmaterial.specular.set(1, 1, 1);\n\t\tmaterial.gloss = 1;\n\t\tmaterial.glossInvert = true;\n\t\tmaterial.useMetalness = true;\n\t\tlet color, texture;\n\t\tif (gltfMaterial.hasOwnProperty('pbrMetallicRoughness')) {\n\t\t\t\tconst pbrData = gltfMaterial.pbrMetallicRoughness;\n\t\t\t\tif (pbrData.hasOwnProperty('baseColorFactor')) {\n\t\t\t\t\t\tcolor = pbrData.baseColorFactor;\n\t\t\t\t\t\tmaterial.diffuse.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));\n\t\t\t\t\t\tmaterial.opacity = color[3];\n\t\t\t\t}\n\t\t\t\tif (pbrData.hasOwnProperty('baseColorTexture')) {\n\t\t\t\t\t\tconst baseColorTexture = pbrData.baseColorTexture;\n\t\t\t\t\t\ttexture = textures[baseColorTexture.index];\n\t\t\t\t\t\tmaterial.diffuseMap = texture;\n\t\t\t\t\t\tmaterial.diffuseMapChannel = 'rgb';\n\t\t\t\t\t\tmaterial.opacityMap = texture;\n\t\t\t\t\t\tmaterial.opacityMapChannel = 'a';\n\t\t\t\t\t\textractTextureTransform(baseColorTexture, material, [\n\t\t\t\t\t\t\t\t'diffuse',\n\t\t\t\t\t\t\t\t'opacity'\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\tif (pbrData.hasOwnProperty('metallicFactor')) {\n\t\t\t\t\t\tmaterial.metalness = pbrData.metallicFactor;\n\t\t\t\t}\n\t\t\t\tif (pbrData.hasOwnProperty('roughnessFactor')) {\n\t\t\t\t\t\tmaterial.gloss = pbrData.roughnessFactor;\n\t\t\t\t}\n\t\t\t\tif (pbrData.hasOwnProperty('metallicRoughnessTexture')) {\n\t\t\t\t\t\tconst metallicRoughnessTexture = pbrData.metallicRoughnessTexture;\n\t\t\t\t\t\tmaterial.metalnessMap = material.glossMap = textures[metallicRoughnessTexture.index];\n\t\t\t\t\t\tmaterial.metalnessMapChannel = 'b';\n\t\t\t\t\t\tmaterial.glossMapChannel = 'g';\n\t\t\t\t\t\textractTextureTransform(metallicRoughnessTexture, material, [\n\t\t\t\t\t\t\t\t'gloss',\n\t\t\t\t\t\t\t\t'metalness'\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\tif (gltfMaterial.hasOwnProperty('normalTexture')) {\n\t\t\t\tconst normalTexture = gltfMaterial.normalTexture;\n\t\t\t\tmaterial.normalMap = textures[normalTexture.index];\n\t\t\t\textractTextureTransform(normalTexture, material, [\n\t\t\t\t\t\t'normal'\n\t\t\t\t]);\n\t\t\t\tif (normalTexture.hasOwnProperty('scale')) {\n\t\t\t\t\t\tmaterial.bumpiness = normalTexture.scale;\n\t\t\t\t}\n\t\t}\n\t\tif (gltfMaterial.hasOwnProperty('occlusionTexture')) {\n\t\t\t\tconst occlusionTexture = gltfMaterial.occlusionTexture;\n\t\t\t\tmaterial.aoMap = textures[occlusionTexture.index];\n\t\t\t\tmaterial.aoMapChannel = 'r';\n\t\t\t\textractTextureTransform(occlusionTexture, material, [\n\t\t\t\t\t\t'ao'\n\t\t\t\t]);\n\t\t}\n\t\tif (gltfMaterial.hasOwnProperty('emissiveFactor')) {\n\t\t\t\tcolor = gltfMaterial.emissiveFactor;\n\t\t\t\tmaterial.emissive.set(Math.pow(color[0], 1 / 2.2), Math.pow(color[1], 1 / 2.2), Math.pow(color[2], 1 / 2.2));\n\t\t}\n\t\tif (gltfMaterial.hasOwnProperty('emissiveTexture')) {\n\t\t\t\tconst emissiveTexture = gltfMaterial.emissiveTexture;\n\t\t\t\tmaterial.emissiveMap = textures[emissiveTexture.index];\n\t\t\t\textractTextureTransform(emissiveTexture, material, [\n\t\t\t\t\t\t'emissive'\n\t\t\t\t]);\n\t\t}\n\t\tif (gltfMaterial.hasOwnProperty('alphaMode')) {\n\t\t\t\tswitch(gltfMaterial.alphaMode){\n\t\t\t\t\t\tcase 'MASK':\n\t\t\t\t\t\t\t\tmaterial.blendType = BLEND_NONE;\n\t\t\t\t\t\t\t\tif (gltfMaterial.hasOwnProperty('alphaCutoff')) {\n\t\t\t\t\t\t\t\t\t\tmaterial.alphaTest = gltfMaterial.alphaCutoff;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmaterial.alphaTest = 0.5;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'BLEND':\n\t\t\t\t\t\t\t\tmaterial.blendType = BLEND_NORMAL;\n\t\t\t\t\t\t\t\tmaterial.depthWrite = false;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\tcase 'OPAQUE':\n\t\t\t\t\t\t\t\tmaterial.blendType = BLEND_NONE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t} else {\n\t\t\t\tmaterial.blendType = BLEND_NONE;\n\t\t}\n\t\tif (gltfMaterial.hasOwnProperty('doubleSided')) {\n\t\t\t\tmaterial.twoSidedLighting = gltfMaterial.doubleSided;\n\t\t\t\tmaterial.cull = gltfMaterial.doubleSided ? CULLFACE_NONE : CULLFACE_BACK;\n\t\t} else {\n\t\t\t\tmaterial.twoSidedLighting = false;\n\t\t\t\tmaterial.cull = CULLFACE_BACK;\n\t\t}\n\t\tconst extensions = {\n\t\t\t\t'KHR_materials_clearcoat': extensionClearCoat,\n\t\t\t\t'KHR_materials_emissive_strength': extensionEmissiveStrength,\n\t\t\t\t'KHR_materials_ior': extensionIor,\n\t\t\t\t'KHR_materials_dispersion': extensionDispersion,\n\t\t\t\t'KHR_materials_iridescence': extensionIridescence,\n\t\t\t\t'KHR_materials_pbrSpecularGlossiness': extensionPbrSpecGlossiness,\n\t\t\t\t'KHR_materials_sheen': extensionSheen,\n\t\t\t\t'KHR_materials_specular': extensionSpecular,\n\t\t\t\t'KHR_materials_transmission': extensionTransmission,\n\t\t\t\t'KHR_materials_unlit': extensionUnlit,\n\t\t\t\t'KHR_materials_volume': extensionVolume,\n\t\t\t\t'KHR_materials_anisotropy': extensionAnisotropy\n\t\t};\n\t\tif (gltfMaterial.hasOwnProperty('extensions')) {\n\t\t\t\tfor(const key in gltfMaterial.extensions){\n\t\t\t\t\t\tconst extensionFunc = extensions[key];\n\t\t\t\t\t\tif (extensionFunc !== undefined) {\n\t\t\t\t\t\t\t\textensionFunc(gltfMaterial.extensions[key], material, textures);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tmaterial.update();\n\t\treturn material;\n};\nconst createAnimation = (gltfAnimation, animationIndex, gltfAccessors, bufferViews, nodes, meshes, gltfNodes)=>{\n\t\tconst createAnimData = (gltfAccessor)=>{\n\t\t\t\treturn new AnimData(getNumComponents(gltfAccessor.type), getAccessorDataFloat32(gltfAccessor, bufferViews));\n\t\t};\n\t\tconst interpMap = {\n\t\t\t\t'STEP': INTERPOLATION_STEP,\n\t\t\t\t'LINEAR': INTERPOLATION_LINEAR,\n\t\t\t\t'CUBICSPLINE': INTERPOLATION_CUBIC\n\t\t};\n\t\tconst inputMap = {};\n\t\tconst outputMap = {};\n\t\tconst curveMap = {};\n\t\tlet outputCounter = 1;\n\t\tlet i;\n\t\tfor(i = 0; i < gltfAnimation.samplers.length; ++i){\n\t\t\t\tconst sampler = gltfAnimation.samplers[i];\n\t\t\t\tif (!inputMap.hasOwnProperty(sampler.input)) {\n\t\t\t\t\t\tinputMap[sampler.input] = createAnimData(gltfAccessors[sampler.input]);\n\t\t\t\t}\n\t\t\t\tif (!outputMap.hasOwnProperty(sampler.output)) {\n\t\t\t\t\t\toutputMap[sampler.output] = createAnimData(gltfAccessors[sampler.output]);\n\t\t\t\t}\n\t\t\t\tconst interpolation = sampler.hasOwnProperty('interpolation') && interpMap.hasOwnProperty(sampler.interpolation) ? interpMap[sampler.interpolation] : INTERPOLATION_LINEAR;\n\t\t\t\tconst curve = {\n\t\t\t\t\t\tpaths: [],\n\t\t\t\t\t\tinput: sampler.input,\n\t\t\t\t\t\toutput: sampler.output,\n\t\t\t\t\t\tinterpolation: interpolation\n\t\t\t\t};\n\t\t\t\tcurveMap[i] = curve;\n\t\t}\n\t\tconst quatArrays = [];\n\t\tconst transformSchema = {\n\t\t\t\t'translation': 'localPosition',\n\t\t\t\t'rotation': 'localRotation',\n\t\t\t\t'scale': 'localScale'\n\t\t};\n\t\tconst constructNodePath = (node)=>{\n\t\t\t\tconst path = [];\n\t\t\t\twhile(node){\n\t\t\t\t\t\tpath.unshift(node.name);\n\t\t\t\t\t\tnode = node.parent;\n\t\t\t\t}\n\t\t\t\treturn path;\n\t\t};\n\t\tconst createMorphTargetCurves = (curve, gltfNode, entityPath)=>{\n\t\t\t\tconst out = outputMap[curve.output];\n\t\t\t\tif (!out) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet targetNames;\n\t\t\t\tif (meshes && meshes[gltfNode.mesh]) {\n\t\t\t\t\t\tconst mesh = meshes[gltfNode.mesh];\n\t\t\t\t\t\tif (mesh.hasOwnProperty('extras') && mesh.extras.hasOwnProperty('targetNames')) {\n\t\t\t\t\t\t\t\ttargetNames = mesh.extras.targetNames;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst outData = out.data;\n\t\t\t\tconst morphTargetCount = outData.length / inputMap[curve.input].data.length;\n\t\t\t\tconst keyframeCount = outData.length / morphTargetCount;\n\t\t\t\tconst singleBufferSize = keyframeCount * 4;\n\t\t\t\tconst buffer = new ArrayBuffer(singleBufferSize * morphTargetCount);\n\t\t\t\tfor(let j = 0; j < morphTargetCount; j++){\n\t\t\t\t\t\tconst morphTargetOutput = new Float32Array(buffer, singleBufferSize * j, keyframeCount);\n\t\t\t\t\t\tfor(let k = 0; k < keyframeCount; k++){\n\t\t\t\t\t\t\t\tmorphTargetOutput[k] = outData[k * morphTargetCount + j];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst output = new AnimData(1, morphTargetOutput);\n\t\t\t\t\t\tconst weightName = targetNames?.[j] ? `name.${targetNames[j]}` : j;\n\t\t\t\t\t\toutputMap[-outputCounter] = output;\n\t\t\t\t\t\tconst morphCurve = {\n\t\t\t\t\t\t\t\tpaths: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tentityPath: entityPath,\n\t\t\t\t\t\t\t\t\t\t\t\tcomponent: 'graph',\n\t\t\t\t\t\t\t\t\t\t\t\tpropertyPath: [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t`weight.${weightName}`\n\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tinput: curve.input,\n\t\t\t\t\t\t\t\toutput: -outputCounter,\n\t\t\t\t\t\t\t\tinterpolation: curve.interpolation\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutputCounter++;\n\t\t\t\t\t\tcurveMap[`morphCurve-${i}-${j}`] = morphCurve;\n\t\t\t\t}\n\t\t};\n\t\tfor(i = 0; i < gltfAnimation.channels.length; ++i){\n\t\t\t\tconst channel = gltfAnimation.channels[i];\n\t\t\t\tconst target = channel.target;\n\t\t\t\tconst curve = curveMap[channel.sampler];\n\t\t\t\tconst node = nodes[target.node];\n\t\t\t\tconst gltfNode = gltfNodes[target.node];\n\t\t\t\tconst entityPath = constructNodePath(node);\n\t\t\t\tif (target.path.startsWith('weights')) {\n\t\t\t\t\t\tcreateMorphTargetCurves(curve, gltfNode, entityPath);\n\t\t\t\t\t\tcurveMap[channel.sampler].morphCurve = true;\n\t\t\t\t} else {\n\t\t\t\t\t\tcurve.paths.push({\n\t\t\t\t\t\t\t\tentityPath: entityPath,\n\t\t\t\t\t\t\t\tcomponent: 'graph',\n\t\t\t\t\t\t\t\tpropertyPath: [\n\t\t\t\t\t\t\t\t\t\ttransformSchema[target.path]\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\tconst inputs = [];\n\t\tconst outputs = [];\n\t\tconst curves = [];\n\t\tfor(const inputKey in inputMap){\n\t\t\t\tinputs.push(inputMap[inputKey]);\n\t\t\t\tinputMap[inputKey] = inputs.length - 1;\n\t\t}\n\t\tfor(const outputKey in outputMap){\n\t\t\t\toutputs.push(outputMap[outputKey]);\n\t\t\t\toutputMap[outputKey] = outputs.length - 1;\n\t\t}\n\t\tfor(const curveKey in curveMap){\n\t\t\t\tconst curveData = curveMap[curveKey];\n\t\t\t\tif (curveData.morphCurve) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tcurves.push(new AnimCurve(curveData.paths, inputMap[curveData.input], outputMap[curveData.output], curveData.interpolation));\n\t\t\t\tif (curveData.paths.length > 0 && curveData.paths[0].propertyPath[0] === 'localRotation' && curveData.interpolation !== INTERPOLATION_CUBIC) {\n\t\t\t\t\t\tquatArrays.push(curves[curves.length - 1].output);\n\t\t\t\t}\n\t\t}\n\t\tquatArrays.sort();\n\t\tlet prevIndex = null;\n\t\tlet data;\n\t\tfor(i = 0; i < quatArrays.length; ++i){\n\t\t\t\tconst index = quatArrays[i];\n\t\t\t\tif (i === 0 || index !== prevIndex) {\n\t\t\t\t\t\tdata = outputs[index];\n\t\t\t\t\t\tif (data.components === 4) {\n\t\t\t\t\t\t\t\tconst d = data.data;\n\t\t\t\t\t\t\t\tconst len = d.length - 4;\n\t\t\t\t\t\t\t\tfor(let j = 0; j < len; j += 4){\n\t\t\t\t\t\t\t\t\t\tconst dp = d[j + 0] * d[j + 4] + d[j + 1] * d[j + 5] + d[j + 2] * d[j + 6] + d[j + 3] * d[j + 7];\n\t\t\t\t\t\t\t\t\t\tif (dp < 0) {\n\t\t\t\t\t\t\t\t\t\t\t\td[j + 4] *= -1;\n\t\t\t\t\t\t\t\t\t\t\t\td[j + 5] *= -1;\n\t\t\t\t\t\t\t\t\t\t\t\td[j + 6] *= -1;\n\t\t\t\t\t\t\t\t\t\t\t\td[j + 7] *= -1;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprevIndex = index;\n\t\t\t\t}\n\t\t}\n\t\tlet duration = 0;\n\t\tfor(i = 0; i < inputs.length; i++){\n\t\t\t\tdata = inputs[i]._data;\n\t\t\t\tduration = Math.max(duration, data.length === 0 ? 0 : data[data.length - 1]);\n\t\t}\n\t\treturn new AnimTrack(gltfAnimation.hasOwnProperty('name') ? gltfAnimation.name : `animation_${animationIndex}`, duration, inputs, outputs, curves);\n};\nconst tempMat = new Mat4();\nconst tempVec = new Vec3();\nconst createNode = (gltfNode, nodeIndex, nodeInstancingMap)=>{\n\t\tconst entity = new GraphNode();\n\t\tif (gltfNode.hasOwnProperty('name') && gltfNode.name.length > 0) {\n\t\t\t\tentity.name = gltfNode.name;\n\t\t} else {\n\t\t\t\tentity.name = `node_${nodeIndex}`;\n\t\t}\n\t\tif (gltfNode.hasOwnProperty('matrix')) {\n\t\t\t\ttempMat.data.set(gltfNode.matrix);\n\t\t\t\ttempMat.getTranslation(tempVec);\n\t\t\t\tentity.setLocalPosition(tempVec);\n\t\t\t\ttempMat.getEulerAngles(tempVec);\n\t\t\t\tentity.setLocalEulerAngles(tempVec);\n\t\t\t\ttempMat.getScale(tempVec);\n\t\t\t\tentity.setLocalScale(tempVec);\n\t\t}\n\t\tif (gltfNode.hasOwnProperty('rotation')) {\n\t\t\t\tconst r = gltfNode.rotation;\n\t\t\t\tentity.setLocalRotation(r[0], r[1], r[2], r[3]);\n\t\t}\n\t\tif (gltfNode.hasOwnProperty('translation')) {\n\t\t\t\tconst t = gltfNode.translation;\n\t\t\t\tentity.setLocalPosition(t[0], t[1], t[2]);\n\t\t}\n\t\tif (gltfNode.hasOwnProperty('scale')) {\n\t\t\t\tconst s = gltfNode.scale;\n\t\t\t\tentity.setLocalScale(s[0], s[1], s[2]);\n\t\t}\n\t\tif (gltfNode.hasOwnProperty('extensions') && gltfNode.extensions.EXT_mesh_gpu_instancing) {\n\t\t\t\tnodeInstancingMap.set(gltfNode, {\n\t\t\t\t\t\text: gltfNode.extensions.EXT_mesh_gpu_instancing\n\t\t\t\t});\n\t\t}\n\t\treturn entity;\n};\nconst createCamera = (gltfCamera, node)=>{\n\t\tconst projection = gltfCamera.type === 'orthographic' ? PROJECTION_ORTHOGRAPHIC : PROJECTION_PERSPECTIVE;\n\t\tconst gltfProperties = projection === PROJECTION_ORTHOGRAPHIC ? gltfCamera.orthographic : gltfCamera.perspective;\n\t\tconst componentData = {\n\t\t\t\tenabled: false,\n\t\t\t\tprojection: projection,\n\t\t\t\tnearClip: gltfProperties.znear,\n\t\t\t\taspectRatioMode: ASPECT_AUTO\n\t\t};\n\t\tif (gltfProperties.zfar) {\n\t\t\t\tcomponentData.farClip = gltfProperties.zfar;\n\t\t}\n\t\tif (projection === PROJECTION_ORTHOGRAPHIC) {\n\t\t\t\tcomponentData.orthoHeight = 0.5 * gltfProperties.ymag;\n\t\t\t\tif (gltfProperties.ymag) {\n\t\t\t\t\t\tcomponentData.aspectRatioMode = ASPECT_MANUAL;\n\t\t\t\t\t\tcomponentData.aspectRatio = gltfProperties.xmag / gltfProperties.ymag;\n\t\t\t\t}\n\t\t} else {\n\t\t\t\tcomponentData.fov = gltfProperties.yfov * math.RAD_TO_DEG;\n\t\t\t\tif (gltfProperties.aspectRatio) {\n\t\t\t\t\t\tcomponentData.aspectRatioMode = ASPECT_MANUAL;\n\t\t\t\t\t\tcomponentData.aspectRatio = gltfProperties.aspectRatio;\n\t\t\t\t}\n\t\t}\n\t\tconst cameraEntity = new Entity(gltfCamera.name);\n\t\tcameraEntity.addComponent('camera', componentData);\n\t\treturn cameraEntity;\n};\nconst createLight = (gltfLight, node)=>{\n\t\tconst lightProps = {\n\t\t\t\tenabled: false,\n\t\t\t\ttype: gltfLight.type === 'point' ? 'omni' : gltfLight.type,\n\t\t\t\tcolor: gltfLight.hasOwnProperty('color') ? new Color(gltfLight.color) : Color.WHITE,\n\t\t\t\trange: gltfLight.hasOwnProperty('range') ? gltfLight.range : 9999,\n\t\t\t\tfalloffMode: LIGHTFALLOFF_INVERSESQUARED,\n\t\t\t\tintensity: gltfLight.hasOwnProperty('intensity') ? math.clamp(gltfLight.intensity, 0, 2) : 1\n\t\t};\n\t\tif (gltfLight.hasOwnProperty('spot')) {\n\t\t\t\tlightProps.innerConeAngle = gltfLight.spot.hasOwnProperty('innerConeAngle') ? gltfLight.spot.innerConeAngle * math.RAD_TO_DEG : 0;\n\t\t\t\tlightProps.outerConeAngle = gltfLight.spot.hasOwnProperty('outerConeAngle') ? gltfLight.spot.outerConeAngle * math.RAD_TO_DEG : Math.PI / 4;\n\t\t}\n\t\tif (gltfLight.hasOwnProperty('intensity')) {\n\t\t\t\tlightProps.luminance = gltfLight.intensity * Light.getLightUnitConversion(lightTypes[lightProps.type], lightProps.outerConeAngle, lightProps.innerConeAngle);\n\t\t}\n\t\tconst lightEntity = new Entity(node.name);\n\t\tlightEntity.rotateLocal(90, 0, 0);\n\t\tlightEntity.addComponent('light', lightProps);\n\t\treturn lightEntity;\n};\nconst createSkins = (device, gltf, nodes, bufferViews)=>{\n\t\tif (!gltf.hasOwnProperty('skins') || gltf.skins.length === 0) {\n\t\t\t\treturn [];\n\t\t}\n\t\tconst glbSkins = new Map();\n\t\treturn gltf.skins.map((gltfSkin)=>{\n\t\t\t\treturn createSkin(device, gltfSkin, gltf.accessors, bufferViews, nodes, glbSkins);\n\t\t});\n};\nconst createMeshes = (device, gltf, bufferViews, options)=>{\n\t\tconst vertexBufferDict = {};\n\t\tconst meshVariants = {};\n\t\tconst meshDefaultMaterials = {};\n\t\tconst promises = [];\n\t\tconst valid = !options.skipMeshes && gltf?.meshes?.length && gltf?.accessors?.length && gltf?.bufferViews?.length;\n\t\tconst meshes = valid ? gltf.meshes.map((gltfMesh)=>{\n\t\t\t\treturn createMesh(device, gltfMesh, gltf.accessors, bufferViews, vertexBufferDict, meshVariants, meshDefaultMaterials, options, promises);\n\t\t}) : [];\n\t\treturn {\n\t\t\t\tmeshes,\n\t\t\t\tmeshVariants,\n\t\t\t\tmeshDefaultMaterials,\n\t\t\t\tpromises\n\t\t};\n};\nconst createMaterials = (gltf, textures, options)=>{\n\t\tif (!gltf.hasOwnProperty('materials') || gltf.materials.length === 0) {\n\t\t\t\treturn [];\n\t\t}\n\t\tconst preprocess = options?.material?.preprocess;\n\t\tconst process = options?.material?.process ?? createMaterial;\n\t\tconst postprocess = options?.material?.postprocess;\n\t\treturn gltf.materials.map((gltfMaterial)=>{\n\t\t\t\tif (preprocess) {\n\t\t\t\t\t\tpreprocess(gltfMaterial);\n\t\t\t\t}\n\t\t\t\tconst material = process(gltfMaterial, textures);\n\t\t\t\tif (postprocess) {\n\t\t\t\t\t\tpostprocess(gltfMaterial, material);\n\t\t\t\t}\n\t\t\t\treturn material;\n\t\t});\n};\nconst createVariants = (gltf)=>{\n\t\tif (!gltf.hasOwnProperty('extensions') || !gltf.extensions.hasOwnProperty('KHR_materials_variants')) {\n\t\t\t\treturn null;\n\t\t}\n\t\tconst data = gltf.extensions.KHR_materials_variants.variants;\n\t\tconst variants = {};\n\t\tfor(let i = 0; i < data.length; i++){\n\t\t\t\tvariants[data[i].name] = i;\n\t\t}\n\t\treturn variants;\n};\nconst createAnimations = (gltf, nodes, bufferViews, options)=>{\n\t\tif (!gltf.hasOwnProperty('animations') || gltf.animations.length === 0) {\n\t\t\t\treturn [];\n\t\t}\n\t\tconst preprocess = options?.animation?.preprocess;\n\t\tconst postprocess = options?.animation?.postprocess;\n\t\treturn gltf.animations.map((gltfAnimation, index)=>{\n\t\t\t\tif (preprocess) {\n\t\t\t\t\t\tpreprocess(gltfAnimation);\n\t\t\t\t}\n\t\t\t\tconst animation = createAnimation(gltfAnimation, index, gltf.accessors, bufferViews, nodes, gltf.meshes, gltf.nodes);\n\t\t\t\tif (postprocess) {\n\t\t\t\t\t\tpostprocess(gltfAnimation, animation);\n\t\t\t\t}\n\t\t\t\treturn animation;\n\t\t});\n};\nconst createInstancing = (device, gltf, nodeInstancingMap, bufferViews)=>{\n\t\tconst accessors = gltf.accessors;\n\t\tnodeInstancingMap.forEach((data, entity)=>{\n\t\t\t\tconst attributes = data.ext.attributes;\n\t\t\t\tlet translations;\n\t\t\t\tif (attributes.hasOwnProperty('TRANSLATION')) {\n\t\t\t\t\t\tconst accessor = accessors[attributes.TRANSLATION];\n\t\t\t\t\t\ttranslations = getAccessorDataFloat32(accessor, bufferViews);\n\t\t\t\t}\n\t\t\t\tlet rotations;\n\t\t\t\tif (attributes.hasOwnProperty('ROTATION')) {\n\t\t\t\t\t\tconst accessor = accessors[attributes.ROTATION];\n\t\t\t\t\t\trotations = getAccessorDataFloat32(accessor, bufferViews);\n\t\t\t\t}\n\t\t\t\tlet scales;\n\t\t\t\tif (attributes.hasOwnProperty('SCALE')) {\n\t\t\t\t\t\tconst accessor = accessors[attributes.SCALE];\n\t\t\t\t\t\tscales = getAccessorDataFloat32(accessor, bufferViews);\n\t\t\t\t}\n\t\t\t\tconst instanceCount = (translations ? translations.length / 3 : 0) || (rotations ? rotations.length / 4 : 0) || (scales ? scales.length / 3 : 0);\n\t\t\t\tif (instanceCount) {\n\t\t\t\t\t\tconst matrices = new Float32Array(instanceCount * 16);\n\t\t\t\t\t\tconst pos = new Vec3();\n\t\t\t\t\t\tconst rot = new Quat();\n\t\t\t\t\t\tconst scl = new Vec3(1, 1, 1);\n\t\t\t\t\t\tconst matrix = new Mat4();\n\t\t\t\t\t\tlet matrixIndex = 0;\n\t\t\t\t\t\tfor(let i = 0; i < instanceCount; i++){\n\t\t\t\t\t\t\t\tconst i3 = i * 3;\n\t\t\t\t\t\t\t\tif (translations) {\n\t\t\t\t\t\t\t\t\t\tpos.set(translations[i3], translations[i3 + 1], translations[i3 + 2]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (rotations) {\n\t\t\t\t\t\t\t\t\t\tconst i4 = i * 4;\n\t\t\t\t\t\t\t\t\t\trot.set(rotations[i4], rotations[i4 + 1], rotations[i4 + 2], rotations[i4 + 3]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (scales) {\n\t\t\t\t\t\t\t\t\t\tscl.set(scales[i3], scales[i3 + 1], scales[i3 + 2]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmatrix.setTRS(pos, rot, scl);\n\t\t\t\t\t\t\t\tfor(let m = 0; m < 16; m++){\n\t\t\t\t\t\t\t\t\t\tmatrices[matrixIndex++] = matrix.data[m];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdata.matrices = matrices;\n\t\t\t\t}\n\t\t});\n};\nconst createNodes = (gltf, options, nodeInstancingMap)=>{\n\t\tif (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) {\n\t\t\t\treturn [];\n\t\t}\n\t\tconst preprocess = options?.node?.preprocess;\n\t\tconst process = options?.node?.process ?? createNode;\n\t\tconst postprocess = options?.node?.postprocess;\n\t\tconst nodes = gltf.nodes.map((gltfNode, index)=>{\n\t\t\t\tif (preprocess) {\n\t\t\t\t\t\tpreprocess(gltfNode);\n\t\t\t\t}\n\t\t\t\tconst node = process(gltfNode, index, nodeInstancingMap);\n\t\t\t\tif (postprocess) {\n\t\t\t\t\t\tpostprocess(gltfNode, node);\n\t\t\t\t}\n\t\t\t\treturn node;\n\t\t});\n\t\tfor(let i = 0; i < gltf.nodes.length; ++i){\n\t\t\t\tconst gltfNode = gltf.nodes[i];\n\t\t\t\tif (gltfNode.hasOwnProperty('children')) {\n\t\t\t\t\t\tconst parent = nodes[i];\n\t\t\t\t\t\tconst uniqueNames = {};\n\t\t\t\t\t\tfor(let j = 0; j < gltfNode.children.length; ++j){\n\t\t\t\t\t\t\t\tconst child = nodes[gltfNode.children[j]];\n\t\t\t\t\t\t\t\tif (!child.parent) {\n\t\t\t\t\t\t\t\t\t\tif (uniqueNames.hasOwnProperty(child.name)) {\n\t\t\t\t\t\t\t\t\t\t\t\tchild.name += uniqueNames[child.name]++;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tuniqueNames[child.name] = 1;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tparent.addChild(child);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\treturn nodes;\n};\nconst createScenes = (gltf, nodes)=>{\n\t\tconst scenes = [];\n\t\tconst count = gltf.scenes.length;\n\t\tif (count === 1 && gltf.scenes[0].nodes?.length === 1) {\n\t\t\t\tconst nodeIndex = gltf.scenes[0].nodes[0];\n\t\t\t\tscenes.push(nodes[nodeIndex]);\n\t\t} else {\n\t\t\t\tfor(let i = 0; i < count; i++){\n\t\t\t\t\t\tconst scene = gltf.scenes[i];\n\t\t\t\t\t\tif (scene.nodes) {\n\t\t\t\t\t\t\t\tconst sceneRoot = new GraphNode(scene.name);\n\t\t\t\t\t\t\t\tfor(let n = 0; n < scene.nodes.length; n++){\n\t\t\t\t\t\t\t\t\t\tconst childNode = nodes[scene.nodes[n]];\n\t\t\t\t\t\t\t\t\t\tsceneRoot.addChild(childNode);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tscenes.push(sceneRoot);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\treturn scenes;\n};\nconst createCameras = (gltf, nodes, options)=>{\n\t\tlet cameras = null;\n\t\tif (gltf.hasOwnProperty('nodes') && gltf.hasOwnProperty('cameras') && gltf.cameras.length > 0) {\n\t\t\t\tconst preprocess = options?.camera?.preprocess;\n\t\t\t\tconst process = options?.camera?.process ?? createCamera;\n\t\t\t\tconst postprocess = options?.camera?.postprocess;\n\t\t\t\tgltf.nodes.forEach((gltfNode, nodeIndex)=>{\n\t\t\t\t\t\tif (gltfNode.hasOwnProperty('camera')) {\n\t\t\t\t\t\t\t\tconst gltfCamera = gltf.cameras[gltfNode.camera];\n\t\t\t\t\t\t\t\tif (gltfCamera) {\n\t\t\t\t\t\t\t\t\t\tif (preprocess) {\n\t\t\t\t\t\t\t\t\t\t\t\tpreprocess(gltfCamera);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst camera = process(gltfCamera, nodes[nodeIndex]);\n\t\t\t\t\t\t\t\t\t\tif (postprocess) {\n\t\t\t\t\t\t\t\t\t\t\t\tpostprocess(gltfCamera, camera);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (camera) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!cameras) cameras = new Map();\n\t\t\t\t\t\t\t\t\t\t\t\tcameras.set(gltfNode, camera);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\treturn cameras;\n};\nconst createLights = (gltf, nodes, options)=>{\n\t\tlet lights = null;\n\t\tif (gltf.hasOwnProperty('nodes') && gltf.hasOwnProperty('extensions') && gltf.extensions.hasOwnProperty('KHR_lights_punctual') && gltf.extensions.KHR_lights_punctual.hasOwnProperty('lights')) {\n\t\t\t\tconst gltfLights = gltf.extensions.KHR_lights_punctual.lights;\n\t\t\t\tif (gltfLights.length) {\n\t\t\t\t\t\tconst preprocess = options?.light?.preprocess;\n\t\t\t\t\t\tconst process = options?.light?.process ?? createLight;\n\t\t\t\t\t\tconst postprocess = options?.light?.postprocess;\n\t\t\t\t\t\tgltf.nodes.forEach((gltfNode, nodeIndex)=>{\n\t\t\t\t\t\t\t\tif (gltfNode.hasOwnProperty('extensions') && gltfNode.extensions.hasOwnProperty('KHR_lights_punctual') && gltfNode.extensions.KHR_lights_punctual.hasOwnProperty('light')) {\n\t\t\t\t\t\t\t\t\t\tconst lightIndex = gltfNode.extensions.KHR_lights_punctual.light;\n\t\t\t\t\t\t\t\t\t\tconst gltfLight = gltfLights[lightIndex];\n\t\t\t\t\t\t\t\t\t\tif (gltfLight) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (preprocess) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpreprocess(gltfLight);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst light = process(gltfLight, nodes[nodeIndex]);\n\t\t\t\t\t\t\t\t\t\t\t\tif (postprocess) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpostprocess(gltfLight, light);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif (light) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (!lights) lights = new Map();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlights.set(gltfNode, light);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\treturn lights;\n};\nconst linkSkins = (gltf, renders, skins)=>{\n\t\tgltf.nodes.forEach((gltfNode)=>{\n\t\t\t\tif (gltfNode.hasOwnProperty('mesh') && gltfNode.hasOwnProperty('skin')) {\n\t\t\t\t\t\tconst meshGroup = renders[gltfNode.mesh].meshes;\n\t\t\t\t\t\tmeshGroup.forEach((mesh)=>{\n\t\t\t\t\t\t\t\tmesh.skin = skins[gltfNode.skin];\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t});\n};\nconst createResources = async (device, gltf, bufferViews, textures, options)=>{\n\t\tconst preprocess = options?.global?.preprocess;\n\t\tconst postprocess = options?.global?.postprocess;\n\t\tif (preprocess) {\n\t\t\t\tpreprocess(gltf);\n\t\t}\n\t\tif (gltf.asset && gltf.asset.generator === 'PlayCanvas') ;\n\t\tconst nodeInstancingMap = new Map();\n\t\tconst nodes = createNodes(gltf, options, nodeInstancingMap);\n\t\tconst scenes = createScenes(gltf, nodes);\n\t\tconst lights = createLights(gltf, nodes, options);\n\t\tconst cameras = createCameras(gltf, nodes, options);\n\t\tconst variants = createVariants(gltf);\n\t\tconst bufferViewData = await Promise.all(bufferViews);\n\t\tconst { meshes, meshVariants, meshDefaultMaterials, promises } = createMeshes(device, gltf, bufferViewData, options);\n\t\tconst animations = createAnimations(gltf, nodes, bufferViewData, options);\n\t\tcreateInstancing(device, gltf, nodeInstancingMap, bufferViewData);\n\t\tconst textureAssets = await Promise.all(textures);\n\t\tconst textureInstances = textureAssets.map((t)=>t.resource);\n\t\tconst materials = createMaterials(gltf, textureInstances, options);\n\t\tconst skins = createSkins(device, gltf, nodes, bufferViewData);\n\t\tconst renders = [];\n\t\tfor(let i = 0; i < meshes.length; i++){\n\t\t\t\trenders[i] = new Render();\n\t\t\t\trenders[i].meshes = meshes[i];\n\t\t}\n\t\tlinkSkins(gltf, renders, skins);\n\t\tconst result = new GlbResources();\n\t\tresult.gltf = gltf;\n\t\tresult.nodes = nodes;\n\t\tresult.scenes = scenes;\n\t\tresult.animations = animations;\n\t\tresult.textures = textureAssets;\n\t\tresult.materials = materials;\n\t\tresult.variants = variants;\n\t\tresult.meshVariants = meshVariants;\n\t\tresult.meshDefaultMaterials = meshDefaultMaterials;\n\t\tresult.renders = renders;\n\t\tresult.skins = skins;\n\t\tresult.lights = lights;\n\t\tresult.cameras = cameras;\n\t\tresult.nodeInstancingMap = nodeInstancingMap;\n\t\tif (postprocess) {\n\t\t\t\tpostprocess(gltf, result);\n\t\t}\n\t\tawait Promise.all(promises);\n\t\treturn result;\n};\nconst applySampler = (texture, gltfSampler)=>{\n\t\tconst getFilter = (filter, defaultValue)=>{\n\t\t\t\tswitch(filter){\n\t\t\t\t\t\tcase 9728:\n\t\t\t\t\t\t\t\treturn FILTER_NEAREST;\n\t\t\t\t\t\tcase 9729:\n\t\t\t\t\t\t\t\treturn FILTER_LINEAR;\n\t\t\t\t\t\tcase 9984:\n\t\t\t\t\t\t\t\treturn FILTER_NEAREST_MIPMAP_NEAREST;\n\t\t\t\t\t\tcase 9985:\n\t\t\t\t\t\t\t\treturn FILTER_LINEAR_MIPMAP_NEAREST;\n\t\t\t\t\t\tcase 9986:\n\t\t\t\t\t\t\t\treturn FILTER_NEAREST_MIPMAP_LINEAR;\n\t\t\t\t\t\tcase 9987:\n\t\t\t\t\t\t\t\treturn FILTER_LINEAR_MIPMAP_LINEAR;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\treturn defaultValue;\n\t\t\t\t}\n\t\t};\n\t\tconst getWrap = (wrap, defaultValue)=>{\n\t\t\t\tswitch(wrap){\n\t\t\t\t\t\tcase 33071:\n\t\t\t\t\t\t\t\treturn ADDRESS_CLAMP_TO_EDGE;\n\t\t\t\t\t\tcase 33648:\n\t\t\t\t\t\t\t\treturn ADDRESS_MIRRORED_REPEAT;\n\t\t\t\t\t\tcase 10497:\n\t\t\t\t\t\t\t\treturn ADDRESS_REPEAT;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\treturn defaultValue;\n\t\t\t\t}\n\t\t};\n\t\tif (texture) {\n\t\t\t\tgltfSampler = gltfSampler ?? {};\n\t\t\t\ttexture.minFilter = getFilter(gltfSampler.minFilter, FILTER_LINEAR_MIPMAP_LINEAR);\n\t\t\t\ttexture.magFilter = getFilter(gltfSampler.magFilter, FILTER_LINEAR);\n\t\t\t\ttexture.addressU = getWrap(gltfSampler.wrapS, ADDRESS_REPEAT);\n\t\t\t\ttexture.addressV = getWrap(gltfSampler.wrapT, ADDRESS_REPEAT);\n\t\t}\n};\nlet gltfTextureUniqueId = 0;\nconst getTextureSource = (gltfTexture)=>gltfTexture.extensions?.KHR_texture_basisu?.source ?? gltfTexture.extensions?.EXT_texture_webp?.source ?? gltfTexture.source;\nconst createImages = (gltf, bufferViews, urlBase, registry, options)=>{\n\t\tif (!gltf.images || gltf.images.length === 0) {\n\t\t\t\treturn [];\n\t\t}\n\t\tconst preprocess = options?.image?.preprocess;\n\t\tconst processAsync = options?.image?.processAsync;\n\t\tconst postprocess = options?.image?.postprocess;\n\t\tconst mimeTypeFileExtensions = {\n\t\t\t\t'image/png': 'png',\n\t\t\t\t'image/jpeg': 'jpg',\n\t\t\t\t'image/basis': 'basis',\n\t\t\t\t'image/ktx': 'ktx',\n\t\t\t\t'image/ktx2': 'ktx2',\n\t\t\t\t'image/vnd-ms.dds': 'dds'\n\t\t};\n\t\tconst getGammaTextures = (gltf)=>{\n\t\t\t\tconst set = new Set();\n\t\t\t\tif (gltf.hasOwnProperty('materials')) {\n\t\t\t\t\t\tgltf.materials.forEach((gltfMaterial)=>{\n\t\t\t\t\t\t\t\tif (gltfMaterial.hasOwnProperty('pbrMetallicRoughness')) {\n\t\t\t\t\t\t\t\t\t\tconst pbrData = gltfMaterial.pbrMetallicRoughness;\n\t\t\t\t\t\t\t\t\t\tif (pbrData.hasOwnProperty('baseColorTexture')) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst gltfTexture = gltf.textures[pbrData.baseColorTexture.index];\n\t\t\t\t\t\t\t\t\t\t\t\tset.add(getTextureSource(gltfTexture));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (gltfMaterial.hasOwnProperty('emissiveTexture')) {\n\t\t\t\t\t\t\t\t\t\tconst gltfTexture = gltf.textures[gltfMaterial.emissiveTexture.index];\n\t\t\t\t\t\t\t\t\t\tset.add(getTextureSource(gltfTexture));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (gltfMaterial.hasOwnProperty('extensions')) {\n\t\t\t\t\t\t\t\t\t\tconst sheen = gltfMaterial.extensions.KHR_materials_sheen;\n\t\t\t\t\t\t\t\t\t\tif (sheen) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (sheen.hasOwnProperty('sheenColorTexture')) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst gltfTexture = gltf.textures[sheen.sheenColorTexture.index];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tset.add(getTextureSource(gltfTexture));\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst specularGlossiness = gltfMaterial.extensions.KHR_materials_pbrSpecularGlossiness;\n\t\t\t\t\t\t\t\t\t\tif (specularGlossiness) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (specularGlossiness.hasOwnProperty('specularGlossinessTexture')) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst gltfTexture = gltf.textures[specularGlossiness.specularGlossinessTexture.index];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tset.add(getTextureSource(gltfTexture));\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst specular = gltfMaterial.extensions.KHR_materials_specular;\n\t\t\t\t\t\t\t\t\t\tif (specular) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (specular.hasOwnProperty('specularColorTexture')) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst gltfTexture = gltf.textures[specular.specularColorTexture.index];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tset.add(getTextureSource(gltfTexture));\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn set;\n\t\t};\n\t\tconst loadTexture = (gltfImage, url, bufferView, mimeType, options, srgb)=>{\n\t\t\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\t\t\tconst continuation = (bufferViewData)=>{\n\t\t\t\t\t\t\t\tconst name = `${gltfImage.name || 'gltf-texture'}-${gltfTextureUniqueId++}`;\n\t\t\t\t\t\t\t\tconst file = {\n\t\t\t\t\t\t\t\t\t\turl: url || name\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif (bufferViewData) {\n\t\t\t\t\t\t\t\t\t\tfile.contents = bufferViewData.slice(0).buffer;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (mimeType) {\n\t\t\t\t\t\t\t\t\t\tconst extension = mimeTypeFileExtensions[mimeType];\n\t\t\t\t\t\t\t\t\t\tif (extension) {\n\t\t\t\t\t\t\t\t\t\t\t\tfile.filename = `${file.url}.${extension}`;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst data = {\n\t\t\t\t\t\t\t\t\t\tsrgb\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tconst asset = new Asset(name, 'texture', file, data, options);\n\t\t\t\t\t\t\t\tasset.on('load', (asset)=>resolve(asset));\n\t\t\t\t\t\t\t\tasset.on('error', (err)=>reject(err));\n\t\t\t\t\t\t\t\tregistry.add(asset);\n\t\t\t\t\t\t\t\tregistry.load(asset);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (bufferView) {\n\t\t\t\t\t\t\t\tbufferView.then((bufferViewData)=>continuation(bufferViewData));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcontinuation(null);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t};\n\t\tconst gammaTextures = getGammaTextures(gltf);\n\t\treturn gltf.images.map((gltfImage, i)=>{\n\t\t\t\tif (preprocess) {\n\t\t\t\t\t\tpreprocess(gltfImage);\n\t\t\t\t}\n\t\t\t\tlet promise;\n\t\t\t\tif (processAsync) {\n\t\t\t\t\t\tpromise = new Promise((resolve, reject)=>{\n\t\t\t\t\t\t\t\tprocessAsync(gltfImage, (err, textureAsset)=>{\n\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(textureAsset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tpromise = new Promise((resolve)=>{\n\t\t\t\t\t\t\t\tresolve(null);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tpromise = promise.then((textureAsset)=>{\n\t\t\t\t\t\tconst srgb = gammaTextures.has(i);\n\t\t\t\t\t\tif (textureAsset) {\n\t\t\t\t\t\t\t\treturn textureAsset;\n\t\t\t\t\t\t} else if (gltfImage.hasOwnProperty('uri')) {\n\t\t\t\t\t\t\t\tif (isDataURI(gltfImage.uri)) {\n\t\t\t\t\t\t\t\t\t\treturn loadTexture(gltfImage, gltfImage.uri, null, getDataURIMimeType(gltfImage.uri), null, srgb);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn loadTexture(gltfImage, ABSOLUTE_URL.test(gltfImage.uri) ? gltfImage.uri : path.join(urlBase, gltfImage.uri), null, null, {\n\t\t\t\t\t\t\t\t\t\tcrossOrigin: 'anonymous'\n\t\t\t\t\t\t\t\t}, srgb);\n\t\t\t\t\t\t} else if (gltfImage.hasOwnProperty('bufferView') && gltfImage.hasOwnProperty('mimeType')) {\n\t\t\t\t\t\t\t\treturn loadTexture(gltfImage, null, bufferViews[gltfImage.bufferView], gltfImage.mimeType, null, srgb);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Promise.reject(new Error(`Invalid image found in gltf (neither uri or bufferView found). index=${i}`));\n\t\t\t\t});\n\t\t\t\tif (postprocess) {\n\t\t\t\t\t\tpromise = promise.then((textureAsset)=>{\n\t\t\t\t\t\t\t\tpostprocess(gltfImage, textureAsset);\n\t\t\t\t\t\t\t\treturn textureAsset;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn promise;\n\t\t});\n};\nconst createTextures = (gltf, images, options)=>{\n\t\tif (!gltf?.images?.length || !gltf?.textures?.length) {\n\t\t\t\treturn [];\n\t\t}\n\t\tconst preprocess = options?.texture?.preprocess;\n\t\tconst processAsync = options?.texture?.processAsync;\n\t\tconst postprocess = options?.texture?.postprocess;\n\t\tconst seenImages = new Set();\n\t\treturn gltf.textures.map((gltfTexture)=>{\n\t\t\t\tif (preprocess) {\n\t\t\t\t\t\tpreprocess(gltfTexture);\n\t\t\t\t}\n\t\t\t\tlet promise;\n\t\t\t\tif (processAsync) {\n\t\t\t\t\t\tpromise = new Promise((resolve, reject)=>{\n\t\t\t\t\t\t\t\tprocessAsync(gltfTexture, gltf.images, (err, gltfImageIndex)=>{\n\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(gltfImageIndex);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tpromise = new Promise((resolve)=>{\n\t\t\t\t\t\t\t\tresolve(null);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tpromise = promise.then((gltfImageIndex)=>{\n\t\t\t\t\t\tgltfImageIndex = gltfImageIndex ?? getTextureSource(gltfTexture);\n\t\t\t\t\t\tconst cloneAsset = seenImages.has(gltfImageIndex);\n\t\t\t\t\t\tseenImages.add(gltfImageIndex);\n\t\t\t\t\t\treturn images[gltfImageIndex].then((imageAsset)=>{\n\t\t\t\t\t\t\t\tconst asset = cloneAsset ? cloneTextureAsset(imageAsset) : imageAsset;\n\t\t\t\t\t\t\t\tapplySampler(asset.resource, (gltf.samplers ?? [])[gltfTexture.sampler]);\n\t\t\t\t\t\t\t\treturn asset;\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tif (postprocess) {\n\t\t\t\t\t\tpromise = promise.then((textureAsset)=>{\n\t\t\t\t\t\t\t\tpostprocess(gltfTexture, textureAsset);\n\t\t\t\t\t\t\t\treturn textureAsset;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn promise;\n\t\t});\n};\nconst loadBuffers = (gltf, binaryChunk, urlBase, options)=>{\n\t\tif (!gltf.buffers || gltf.buffers.length === 0) {\n\t\t\t\treturn [];\n\t\t}\n\t\tconst preprocess = options?.buffer?.preprocess;\n\t\tconst processAsync = options?.buffer?.processAsync;\n\t\tconst postprocess = options?.buffer?.postprocess;\n\t\treturn gltf.buffers.map((gltfBuffer, i)=>{\n\t\t\t\tif (preprocess) {\n\t\t\t\t\t\tpreprocess(gltfBuffer);\n\t\t\t\t}\n\t\t\t\tlet promise;\n\t\t\t\tif (processAsync) {\n\t\t\t\t\t\tpromise = new Promise((resolve, reject)=>{\n\t\t\t\t\t\t\t\tprocessAsync(gltfBuffer, (err, arrayBuffer)=>{\n\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(arrayBuffer);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tpromise = new Promise((resolve)=>{\n\t\t\t\t\t\t\t\tresolve(null);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tpromise = promise.then((arrayBuffer)=>{\n\t\t\t\t\t\tif (arrayBuffer) {\n\t\t\t\t\t\t\t\treturn arrayBuffer;\n\t\t\t\t\t\t} else if (gltfBuffer.hasOwnProperty('uri')) {\n\t\t\t\t\t\t\t\tif (isDataURI(gltfBuffer.uri)) {\n\t\t\t\t\t\t\t\t\t\tconst byteString = atob(gltfBuffer.uri.split(',')[1]);\n\t\t\t\t\t\t\t\t\t\tconst binaryArray = new Uint8Array(byteString.length);\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < byteString.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tbinaryArray[j] = byteString.charCodeAt(j);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn binaryArray;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn new Promise((resolve, reject)=>{\n\t\t\t\t\t\t\t\t\t\thttp.get(ABSOLUTE_URL.test(gltfBuffer.uri) ? gltfBuffer.uri : path.join(urlBase, gltfBuffer.uri), {\n\t\t\t\t\t\t\t\t\t\t\t\tcache: true,\n\t\t\t\t\t\t\t\t\t\t\t\tresponseType: 'arraybuffer',\n\t\t\t\t\t\t\t\t\t\t\t\tretry: false\n\t\t\t\t\t\t\t\t\t\t}, (err, result)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresolve(new Uint8Array(result));\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn binaryChunk;\n\t\t\t\t});\n\t\t\t\tif (postprocess) {\n\t\t\t\t\t\tpromise = promise.then((buffer)=>{\n\t\t\t\t\t\t\t\tpostprocess(gltf.buffers[i], buffer);\n\t\t\t\t\t\t\t\treturn buffer;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn promise;\n\t\t});\n};\nconst parseGltf = (gltfChunk, callback)=>{\n\t\tconst decodeBinaryUtf8 = (array)=>{\n\t\t\t\tif (typeof TextDecoder !== 'undefined') {\n\t\t\t\t\t\treturn new TextDecoder().decode(array);\n\t\t\t\t}\n\t\t\t\tlet str = '';\n\t\t\t\tfor(let i = 0; i < array.length; i++){\n\t\t\t\t\t\tstr += String.fromCharCode(array[i]);\n\t\t\t\t}\n\t\t\t\treturn decodeURIComponent(escape(str));\n\t\t};\n\t\tconst gltf = JSON.parse(decodeBinaryUtf8(gltfChunk));\n\t\tif (gltf.asset && gltf.asset.version && parseFloat(gltf.asset.version) < 2) {\n\t\t\t\tcallback(`Invalid gltf version. Expected version 2.0 or above but found version '${gltf.asset.version}'.`);\n\t\t\t\treturn;\n\t\t}\n\t\tcallback(null, gltf);\n};\nconst parseGlb = (glbData, callback)=>{\n\t\tconst data = glbData instanceof ArrayBuffer ? new DataView(glbData) : new DataView(glbData.buffer, glbData.byteOffset, glbData.byteLength);\n\t\tconst magic = data.getUint32(0, true);\n\t\tconst version = data.getUint32(4, true);\n\t\tconst length = data.getUint32(8, true);\n\t\tif (magic !== 0x46546C67) {\n\t\t\t\tcallback(`Invalid magic number found in glb header. Expected 0x46546C67, found 0x${magic.toString(16)}`);\n\t\t\t\treturn;\n\t\t}\n\t\tif (version !== 2) {\n\t\t\t\tcallback(`Invalid version number found in glb header. Expected 2, found ${version}`);\n\t\t\t\treturn;\n\t\t}\n\t\tif (length <= 0 || length > data.byteLength) {\n\t\t\t\tcallback(`Invalid length found in glb header. Found ${length}`);\n\t\t\t\treturn;\n\t\t}\n\t\tconst chunks = [];\n\t\tlet offset = 12;\n\t\twhile(offset < length){\n\t\t\t\tconst chunkLength = data.getUint32(offset, true);\n\t\t\t\tif (offset + chunkLength + 8 > data.byteLength) {\n\t\t\t\t\t\tcallback(`Invalid chunk length found in glb. Found ${chunkLength}`);\n\t\t\t\t}\n\t\t\t\tconst chunkType = data.getUint32(offset + 4, true);\n\t\t\t\tconst chunkData = new Uint8Array(data.buffer, data.byteOffset + offset + 8, chunkLength);\n\t\t\t\tchunks.push({\n\t\t\t\t\t\tlength: chunkLength,\n\t\t\t\t\t\ttype: chunkType,\n\t\t\t\t\t\tdata: chunkData\n\t\t\t\t});\n\t\t\t\toffset += chunkLength + 8;\n\t\t}\n\t\tif (chunks.length !== 1 && chunks.length !== 2) {\n\t\t\t\tcallback('Invalid number of chunks found in glb file.');\n\t\t\t\treturn;\n\t\t}\n\t\tif (chunks[0].type !== 0x4E4F534A) {\n\t\t\t\tcallback(`Invalid chunk type found in glb file. Expected 0x4E4F534A, found 0x${chunks[0].type.toString(16)}`);\n\t\t\t\treturn;\n\t\t}\n\t\tif (chunks.length > 1 && chunks[1].type !== 0x004E4942) {\n\t\t\t\tcallback(`Invalid chunk type found in glb file. Expected 0x004E4942, found 0x${chunks[1].type.toString(16)}`);\n\t\t\t\treturn;\n\t\t}\n\t\tcallback(null, {\n\t\t\t\tgltfChunk: chunks[0].data,\n\t\t\t\tbinaryChunk: chunks.length === 2 ? chunks[1].data : null\n\t\t});\n};\nconst parseChunk = (filename, data, callback)=>{\n\t\tconst hasGlbHeader = ()=>{\n\t\t\t\tconst u8 = new Uint8Array(data);\n\t\t\t\treturn u8[0] === 103 && u8[1] === 108 && u8[2] === 84 && u8[3] === 70;\n\t\t};\n\t\tif (filename && filename.toLowerCase().endsWith('.glb') || hasGlbHeader()) {\n\t\t\t\tparseGlb(data, callback);\n\t\t} else {\n\t\t\t\tcallback(null, {\n\t\t\t\t\t\tgltfChunk: data,\n\t\t\t\t\t\tbinaryChunk: null\n\t\t\t\t});\n\t\t}\n};\nconst createBufferViews = (gltf, buffers, options)=>{\n\t\tconst result = [];\n\t\tconst preprocess = options?.bufferView?.preprocess;\n\t\tconst processAsync = options?.bufferView?.processAsync;\n\t\tconst postprocess = options?.bufferView?.postprocess;\n\t\tif (!gltf.bufferViews?.length) {\n\t\t\t\treturn result;\n\t\t}\n\t\tfor(let i = 0; i < gltf.bufferViews.length; ++i){\n\t\t\t\tconst gltfBufferView = gltf.bufferViews[i];\n\t\t\t\tif (preprocess) {\n\t\t\t\t\t\tpreprocess(gltfBufferView);\n\t\t\t\t}\n\t\t\t\tlet promise;\n\t\t\t\tif (processAsync) {\n\t\t\t\t\t\tpromise = new Promise((resolve, reject)=>{\n\t\t\t\t\t\t\t\tprocessAsync(gltfBufferView, buffers, (err, result)=>{\n\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(result);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tpromise = new Promise((resolve)=>{\n\t\t\t\t\t\t\t\tresolve(null);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tpromise = promise.then((buffer)=>{\n\t\t\t\t\t\tif (buffer) {\n\t\t\t\t\t\t\t\treturn buffer;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn buffers[gltfBufferView.buffer].then((buffer)=>{\n\t\t\t\t\t\t\t\treturn new Uint8Array(buffer.buffer, buffer.byteOffset + (gltfBufferView.byteOffset || 0), gltfBufferView.byteLength);\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tif (gltfBufferView.hasOwnProperty('byteStride')) {\n\t\t\t\t\t\tpromise = promise.then((typedArray)=>{\n\t\t\t\t\t\t\t\ttypedArray.byteStride = gltfBufferView.byteStride;\n\t\t\t\t\t\t\t\treturn typedArray;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (postprocess) {\n\t\t\t\t\t\tpromise = promise.then((typedArray)=>{\n\t\t\t\t\t\t\t\tpostprocess(gltfBufferView, typedArray);\n\t\t\t\t\t\t\t\treturn typedArray;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tresult.push(promise);\n\t\t}\n\t\treturn result;\n};\nclass GlbParser {\n\t\tstatic parse(filename, urlBase, data, device, registry, options, callback) {\n\t\t\t\tparseChunk(filename, data, (err, chunks)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparseGltf(chunks.gltfChunk, (err, gltf)=>{\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst buffers = loadBuffers(gltf, chunks.binaryChunk, urlBase, options);\n\t\t\t\t\t\t\t\tconst bufferViews = createBufferViews(gltf, buffers, options);\n\t\t\t\t\t\t\t\tconst images = createImages(gltf, bufferViews, urlBase, registry, options);\n\t\t\t\t\t\t\t\tconst textures = createTextures(gltf, images, options);\n\t\t\t\t\t\t\t\tcreateResources(device, gltf, bufferViews, textures, options).then((result)=>callback(null, result)).catch((err)=>callback(err));\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t}\n\t\tstatic createDefaultMaterial() {\n\t\t\t\treturn createMaterial({\n\t\t\t\t\t\tname: 'defaultGlbMaterial'\n\t\t\t\t}, []);\n\t\t}\n}\n\nclass AnimationHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'animation');\n\t\t\t\tthis.device = app.graphicsDevice;\n\t\t\t\tthis.assets = app.assets;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst options = {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t};\n\t\t\t\tif (url.load.startsWith('blob:') || url.load.startsWith('data:')) {\n\t\t\t\t\t\tif (path.getExtension(url.original).toLowerCase() === '.glb') {\n\t\t\t\t\t\t\t\toptions.responseType = Http.ResponseType.ARRAY_BUFFER;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toptions.responseType = Http.ResponseType.JSON;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, options, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(`Error loading animation resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (path.getExtension(url.original).toLowerCase() === '.glb') {\n\t\t\t\t\t\t\t\t\t\tGlbParser.parse('filename.glb', '', response, this.device, this.assets, asset?.options ?? {}, (err, parseResult)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst animations = parseResult.animations;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (asset?.data?.events) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < animations.length; i++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanimations[i].events = new AnimEvents(Object.values(asset.data.events));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tparseResult.destroy();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(null, animations);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcallback(null, this[`_parseAnimationV${response.animation.version}`](response));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\treturn data;\n\t\t}\n\t\t_parseAnimationV3(data) {\n\t\t\t\tconst animData = data.animation;\n\t\t\t\tconst anim = new Animation();\n\t\t\t\tanim.name = animData.name;\n\t\t\t\tanim.duration = animData.duration;\n\t\t\t\tfor(let i = 0; i < animData.nodes.length; i++){\n\t\t\t\t\t\tconst node = new AnimationNode();\n\t\t\t\t\t\tconst n = animData.nodes[i];\n\t\t\t\t\t\tnode._name = n.name;\n\t\t\t\t\t\tfor(let j = 0; j < n.keys.length; j++){\n\t\t\t\t\t\t\t\tconst k = n.keys[j];\n\t\t\t\t\t\t\t\tconst t = k.time;\n\t\t\t\t\t\t\t\tconst p = k.pos;\n\t\t\t\t\t\t\t\tconst r = k.rot;\n\t\t\t\t\t\t\t\tconst s = k.scale;\n\t\t\t\t\t\t\t\tconst pos = new Vec3(p[0], p[1], p[2]);\n\t\t\t\t\t\t\t\tconst rot = new Quat().setFromEulerAngles(r[0], r[1], r[2]);\n\t\t\t\t\t\t\t\tconst scl = new Vec3(s[0], s[1], s[2]);\n\t\t\t\t\t\t\t\tconst key = new AnimationKey(t, pos, rot, scl);\n\t\t\t\t\t\t\t\tnode._keys.push(key);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tanim.addNode(node);\n\t\t\t\t}\n\t\t\t\treturn anim;\n\t\t}\n\t\t_parseAnimationV4(data) {\n\t\t\t\tconst animData = data.animation;\n\t\t\t\tconst anim = new Animation();\n\t\t\t\tanim.name = animData.name;\n\t\t\t\tanim.duration = animData.duration;\n\t\t\t\tfor(let i = 0; i < animData.nodes.length; i++){\n\t\t\t\t\t\tconst node = new AnimationNode();\n\t\t\t\t\t\tconst n = animData.nodes[i];\n\t\t\t\t\t\tnode._name = n.name;\n\t\t\t\t\t\tconst defPos = n.defaults.p;\n\t\t\t\t\t\tconst defRot = n.defaults.r;\n\t\t\t\t\t\tconst defScl = n.defaults.s;\n\t\t\t\t\t\tfor(let j = 0; j < n.keys.length; j++){\n\t\t\t\t\t\t\t\tconst k = n.keys[j];\n\t\t\t\t\t\t\t\tconst t = k.t;\n\t\t\t\t\t\t\t\tconst p = defPos ? defPos : k.p;\n\t\t\t\t\t\t\t\tconst r = defRot ? defRot : k.r;\n\t\t\t\t\t\t\t\tconst s = defScl ? defScl : k.s;\n\t\t\t\t\t\t\t\tconst pos = new Vec3(p[0], p[1], p[2]);\n\t\t\t\t\t\t\t\tconst rot = new Quat().setFromEulerAngles(r[0], r[1], r[2]);\n\t\t\t\t\t\t\t\tconst scl = new Vec3(s[0], s[1], s[2]);\n\t\t\t\t\t\t\t\tconst key = new AnimationKey(t, pos, rot, scl);\n\t\t\t\t\t\t\t\tnode._keys.push(key);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tanim.addNode(node);\n\t\t\t\t}\n\t\t\t\treturn anim;\n\t\t}\n}\n\nclass AnimClipHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'animclip');\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst options = {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t};\n\t\t\t\tif (url.load.startsWith('blob:')) {\n\t\t\t\t\t\toptions.responseType = Http.ResponseType.JSON;\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, options, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(`Error loading animation clip resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topen(url, data) {\n\t\t\t\tconst name = data.name;\n\t\t\t\tconst duration = data.duration;\n\t\t\t\tconst inputs = data.inputs.map((input)=>{\n\t\t\t\t\t\treturn new AnimData(1, input);\n\t\t\t\t});\n\t\t\t\tconst outputs = data.outputs.map((output)=>{\n\t\t\t\t\t\treturn new AnimData(output.components, output.data);\n\t\t\t\t});\n\t\t\t\tconst curves = data.curves.map((curve)=>{\n\t\t\t\t\t\treturn new AnimCurve([\n\t\t\t\t\t\t\t\tcurve.path\n\t\t\t\t\t\t], curve.inputIndex, curve.outputIndex, curve.interpolation);\n\t\t\t\t});\n\t\t\t\treturn new AnimTrack(name, duration, inputs, outputs, curves);\n\t\t}\n}\n\nclass AnimStateGraphHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'animstategraph');\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst options = {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t};\n\t\t\t\tif (url.load.startsWith('blob:')) {\n\t\t\t\t\t\toptions.responseType = Http.ResponseType.JSON;\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, options, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(`Error loading animation state graph resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topen(url, data) {\n\t\t\t\treturn new AnimStateGraph(data);\n\t\t}\n}\n\nconst ie = function() {\n\t\tif (typeof window === 'undefined') {\n\t\t\t\treturn false;\n\t\t}\n\t\tconst ua = window.navigator.userAgent;\n\t\tconst msie = ua.indexOf('MSIE ');\n\t\tif (msie > 0) {\n\t\t\t\treturn parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);\n\t\t}\n\t\tconst trident = ua.indexOf('Trident/');\n\t\tif (trident > 0) {\n\t\t\t\tconst rv = ua.indexOf('rv:');\n\t\t\t\treturn parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);\n\t\t}\n\t\treturn false;\n}();\nconst supportedExtensions = [\n\t\t'.ogg',\n\t\t'.mp3',\n\t\t'.wav',\n\t\t'.mp4a',\n\t\t'.m4a',\n\t\t'.mp4',\n\t\t'.aac',\n\t\t'.opus'\n];\nclass AudioHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'audio');\n\t\t\t\tthis.manager = app.soundManager;\n\t\t}\n\t\t_isSupported(url) {\n\t\t\t\tconst ext = path.getExtension(url);\n\t\t\t\treturn supportedExtensions.indexOf(ext) > -1;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst success = function(resource) {\n\t\t\t\t\t\tcallback(null, new Sound(resource));\n\t\t\t\t};\n\t\t\t\tconst error = function(err) {\n\t\t\t\t\t\tlet msg = `Error loading audio url: ${url.original}`;\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tmsg += `: ${err.message || err}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconsole.warn(msg);\n\t\t\t\t\t\tcallback(msg);\n\t\t\t\t};\n\t\t\t\tif (this._createSound) {\n\t\t\t\t\t\tif (!this._isSupported(url.original)) {\n\t\t\t\t\t\t\t\terror(`Audio format for ${url.original} not supported`);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._createSound(url.load, success, error);\n\t\t\t\t} else {\n\t\t\t\t\t\terror(null);\n\t\t\t\t}\n\t\t}\n\t\t_createSound(url, success, error) {\n\t\t\t\tif (hasAudioContext()) {\n\t\t\t\t\t\tconst manager = this.manager;\n\t\t\t\t\t\tif (!manager.context) {\n\t\t\t\t\t\t\t\terror('Audio manager has no audio context');\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst options = {\n\t\t\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (url.startsWith('blob:') || url.startsWith('data:')) {\n\t\t\t\t\t\t\t\toptions.responseType = Http.ResponseType.ARRAY_BUFFER;\n\t\t\t\t\t\t}\n\t\t\t\t\t\thttp.get(url, options, (err, response)=>{\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\terror(err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmanager.context.decodeAudioData(response, success, error);\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tlet audio = null;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\taudio = new Audio();\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\terror('No support for Audio element');\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ie) {\n\t\t\t\t\t\t\t\tdocument.body.appendChild(audio);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst onReady = function() {\n\t\t\t\t\t\t\t\taudio.removeEventListener('canplaythrough', onReady);\n\t\t\t\t\t\t\t\tif (ie) {\n\t\t\t\t\t\t\t\t\t\tdocument.body.removeChild(audio);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsuccess(audio);\n\t\t\t\t\t\t};\n\t\t\t\t\t\taudio.onerror = function() {\n\t\t\t\t\t\t\t\taudio.onerror = null;\n\t\t\t\t\t\t\t\tif (ie) {\n\t\t\t\t\t\t\t\t\t\tdocument.body.removeChild(audio);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\terror();\n\t\t\t\t\t\t};\n\t\t\t\t\t\taudio.addEventListener('canplaythrough', onReady);\n\t\t\t\t\t\taudio.src = url;\n\t\t\t\t}\n\t\t}\n}\n\nclass BinaryHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'binary');\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\tresponseType: Http.ResponseType.ARRAY_BUFFER,\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading binary resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topenBinary(data) {\n\t\t\t\treturn data.buffer;\n\t\t}\n}\n\nclass GlbContainerResource {\n\t\tconstructor(data, asset, assets, defaultMaterial){\n\t\t\t\tconst createAsset = function(type, resource, index) {\n\t\t\t\t\t\tconst subAsset = GlbContainerResource.createAsset(asset.name, type, resource, index);\n\t\t\t\t\t\tassets.add(subAsset);\n\t\t\t\t\t\treturn subAsset;\n\t\t\t\t};\n\t\t\t\tconst renders = [];\n\t\t\t\tfor(let i = 0; i < data.renders.length; ++i){\n\t\t\t\t\t\trenders.push(createAsset('render', data.renders[i], i));\n\t\t\t\t}\n\t\t\t\tconst materials = [];\n\t\t\t\tfor(let i = 0; i < data.materials.length; ++i){\n\t\t\t\t\t\tmaterials.push(createAsset('material', data.materials[i], i));\n\t\t\t\t}\n\t\t\t\tconst animations = [];\n\t\t\t\tfor(let i = 0; i < data.animations.length; ++i){\n\t\t\t\t\t\tanimations.push(createAsset('animation', data.animations[i], i));\n\t\t\t\t}\n\t\t\t\tthis.data = data;\n\t\t\t\tthis._model = null;\n\t\t\t\tthis._assetName = asset.name;\n\t\t\t\tthis._assets = assets;\n\t\t\t\tthis._defaultMaterial = defaultMaterial;\n\t\t\t\tthis.renders = renders;\n\t\t\t\tthis.materials = materials;\n\t\t\t\tthis.textures = data.textures;\n\t\t\t\tthis.animations = animations;\n\t\t}\n\t\tget model() {\n\t\t\t\tif (!this._model) {\n\t\t\t\t\t\tconst model = GlbContainerResource.createModel(this.data, this._defaultMaterial);\n\t\t\t\t\t\tconst modelAsset = GlbContainerResource.createAsset(this._assetName, 'model', model, 0);\n\t\t\t\t\t\tthis._assets.add(modelAsset);\n\t\t\t\t\t\tthis._model = modelAsset;\n\t\t\t\t}\n\t\t\t\treturn this._model;\n\t\t}\n\t\tstatic createAsset(assetName, type, resource, index) {\n\t\t\t\tconst subAsset = new Asset(`${assetName}/${type}/${index}`, type, {\n\t\t\t\t\t\turl: ''\n\t\t\t\t});\n\t\t\t\tsubAsset.resource = resource;\n\t\t\t\tsubAsset.loaded = true;\n\t\t\t\treturn subAsset;\n\t\t}\n\t\tinstantiateModelEntity(options) {\n\t\t\t\tconst entity = new Entity(undefined, this._assets._loader._app);\n\t\t\t\tentity.addComponent('model', Object.assign({\n\t\t\t\t\t\ttype: 'asset',\n\t\t\t\t\t\tasset: this.model\n\t\t\t\t}, options));\n\t\t\t\treturn entity;\n\t\t}\n\t\tinstantiateRenderEntity(options) {\n\t\t\t\tconst defaultMaterial = this._defaultMaterial;\n\t\t\t\tconst skinnedMeshInstances = [];\n\t\t\t\tconst createMeshInstance = function(root, entity, mesh, materials, meshDefaultMaterials, skins, gltfNode, nodeInstancingMap) {\n\t\t\t\t\t\tconst materialIndex = meshDefaultMaterials[mesh.id];\n\t\t\t\t\t\tconst material = materialIndex === undefined ? defaultMaterial : materials[materialIndex];\n\t\t\t\t\t\tconst meshInstance = new MeshInstance(mesh, material);\n\t\t\t\t\t\tif (mesh.morph) {\n\t\t\t\t\t\t\t\tmeshInstance.morphInstance = new MorphInstance(mesh.morph);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (gltfNode.hasOwnProperty('skin')) {\n\t\t\t\t\t\t\t\tskinnedMeshInstances.push({\n\t\t\t\t\t\t\t\t\t\tmeshInstance: meshInstance,\n\t\t\t\t\t\t\t\t\t\trootBone: root,\n\t\t\t\t\t\t\t\t\t\tentity: entity\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst instData = nodeInstancingMap.get(gltfNode);\n\t\t\t\t\t\tif (instData) {\n\t\t\t\t\t\t\t\tconst matrices = instData.matrices;\n\t\t\t\t\t\t\t\tconst vbFormat = VertexFormat.getDefaultInstancingFormat(mesh.device);\n\t\t\t\t\t\t\t\tconst vb = new VertexBuffer(mesh.device, vbFormat, matrices.length / 16, {\n\t\t\t\t\t\t\t\t\t\tdata: matrices\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tmeshInstance.setInstancing(vb);\n\t\t\t\t\t\t\t\tmeshInstance.instancingData._destroyVertexBuffer = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn meshInstance;\n\t\t\t\t};\n\t\t\t\tconst cloneHierarchy = (root, node, glb)=>{\n\t\t\t\t\t\tconst entity = new Entity(undefined, this._assets._loader._app);\n\t\t\t\t\t\tnode._cloneInternal(entity);\n\t\t\t\t\t\tif (!root) root = entity;\n\t\t\t\t\t\tlet attachedMi = null;\n\t\t\t\t\t\tlet renderAsset = null;\n\t\t\t\t\t\tfor(let i = 0; i < glb.nodes.length; i++){\n\t\t\t\t\t\t\t\tconst glbNode = glb.nodes[i];\n\t\t\t\t\t\t\t\tif (glbNode === node) {\n\t\t\t\t\t\t\t\t\t\tconst gltfNode = glb.gltf.nodes[i];\n\t\t\t\t\t\t\t\t\t\tif (gltfNode.hasOwnProperty('mesh')) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst meshGroup = glb.renders[gltfNode.mesh].meshes;\n\t\t\t\t\t\t\t\t\t\t\t\trenderAsset = this.renders[gltfNode.mesh];\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let mi = 0; mi < meshGroup.length; mi++){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst mesh = meshGroup[mi];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (mesh) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst cloneMi = createMeshInstance(root, entity, mesh, glb.materials, glb.meshDefaultMaterials, glb.skins, gltfNode, glb.nodeInstancingMap);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (!attachedMi) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tattachedMi = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tattachedMi.push(cloneMi);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (glb.lights) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst lightEntity = glb.lights.get(gltfNode);\n\t\t\t\t\t\t\t\t\t\t\t\tif (lightEntity) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tentity.addChild(lightEntity.clone());\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (glb.cameras) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst cameraEntity = glb.cameras.get(gltfNode);\n\t\t\t\t\t\t\t\t\t\t\t\tif (cameraEntity) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcameraEntity.camera.system.cloneComponent(cameraEntity, entity);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (attachedMi) {\n\t\t\t\t\t\t\t\tentity.addComponent('render', Object.assign({\n\t\t\t\t\t\t\t\t\t\ttype: 'asset',\n\t\t\t\t\t\t\t\t\t\tmeshInstances: attachedMi\n\t\t\t\t\t\t\t\t}, options));\n\t\t\t\t\t\t\t\tentity.render.assignAsset(renderAsset);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst children = node.children;\n\t\t\t\t\t\tfor(let i = 0; i < children.length; i++){\n\t\t\t\t\t\t\t\tconst childClone = cloneHierarchy(root, children[i], glb);\n\t\t\t\t\t\t\t\tentity.addChild(childClone);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn entity;\n\t\t\t\t};\n\t\t\t\tconst sceneClones = [];\n\t\t\t\tfor (const scene of this.data.scenes){\n\t\t\t\t\t\tsceneClones.push(cloneHierarchy(null, scene, this.data));\n\t\t\t\t}\n\t\t\t\tskinnedMeshInstances.forEach((data)=>{\n\t\t\t\t\t\tdata.meshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(data.meshInstance.mesh.skin, data.rootBone, data.entity);\n\t\t\t\t\t\tdata.meshInstance.node.render.rootBone = data.rootBone;\n\t\t\t\t});\n\t\t\t\treturn GlbContainerResource.createSceneHierarchy(sceneClones, Entity);\n\t\t}\n\t\tgetMaterialVariants() {\n\t\t\t\treturn this.data.variants ? Object.keys(this.data.variants) : [];\n\t\t}\n\t\tapplyMaterialVariant(entity, name) {\n\t\t\t\tconst variant = name ? this.data.variants[name] : null;\n\t\t\t\tif (variant === undefined) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst renders = entity.findComponents('render');\n\t\t\t\tfor(let i = 0; i < renders.length; i++){\n\t\t\t\t\t\tconst renderComponent = renders[i];\n\t\t\t\t\t\tthis._applyMaterialVariant(variant, renderComponent.meshInstances);\n\t\t\t\t}\n\t\t}\n\t\tapplyMaterialVariantInstances(instances, name) {\n\t\t\t\tconst variant = name ? this.data.variants[name] : null;\n\t\t\t\tif (variant === undefined) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._applyMaterialVariant(variant, instances);\n\t\t}\n\t\t_applyMaterialVariant(variant, instances) {\n\t\t\t\tinstances.forEach((instance)=>{\n\t\t\t\t\t\tif (variant === null) {\n\t\t\t\t\t\t\t\tinstance.material = this._defaultMaterial;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst meshVariants = this.data.meshVariants[instance.mesh.id];\n\t\t\t\t\t\t\t\tif (meshVariants) {\n\t\t\t\t\t\t\t\t\t\tinstance.material = this.data.materials[meshVariants[variant]];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tstatic createSceneHierarchy(sceneNodes, nodeType) {\n\t\t\t\tlet root = null;\n\t\t\t\tif (sceneNodes.length === 1) {\n\t\t\t\t\t\troot = sceneNodes[0];\n\t\t\t\t} else {\n\t\t\t\t\t\troot = new nodeType('SceneGroup');\n\t\t\t\t\t\tfor (const scene of sceneNodes){\n\t\t\t\t\t\t\t\troot.addChild(scene);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn root;\n\t\t}\n\t\tstatic createModel(glb, defaultMaterial) {\n\t\t\t\tconst createMeshInstance = function(model, mesh, skins, skinInstances, materials, node, gltfNode) {\n\t\t\t\t\t\tconst materialIndex = glb.meshDefaultMaterials[mesh.id];\n\t\t\t\t\t\tconst material = materialIndex === undefined ? defaultMaterial : materials[materialIndex];\n\t\t\t\t\t\tconst meshInstance = new MeshInstance(mesh, material, node);\n\t\t\t\t\t\tif (mesh.morph) {\n\t\t\t\t\t\t\t\tconst morphInstance = new MorphInstance(mesh.morph);\n\t\t\t\t\t\t\t\tmeshInstance.morphInstance = morphInstance;\n\t\t\t\t\t\t\t\tmodel.morphInstances.push(morphInstance);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (gltfNode.hasOwnProperty('skin')) {\n\t\t\t\t\t\t\t\tconst skinIndex = gltfNode.skin;\n\t\t\t\t\t\t\t\tconst skin = skins[skinIndex];\n\t\t\t\t\t\t\t\tmesh.skin = skin;\n\t\t\t\t\t\t\t\tconst skinInstance = skinInstances[skinIndex];\n\t\t\t\t\t\t\t\tmeshInstance.skinInstance = skinInstance;\n\t\t\t\t\t\t\t\tmodel.skinInstances.push(skinInstance);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmodel.meshInstances.push(meshInstance);\n\t\t\t\t};\n\t\t\t\tconst model = new Model();\n\t\t\t\tconst skinInstances = [];\n\t\t\t\tfor (const skin of glb.skins){\n\t\t\t\t\t\tconst skinInstance = new SkinInstance(skin);\n\t\t\t\t\t\tskinInstance.bones = skin.bones;\n\t\t\t\t\t\tskinInstances.push(skinInstance);\n\t\t\t\t}\n\t\t\t\tmodel.graph = GlbContainerResource.createSceneHierarchy(glb.scenes, GraphNode);\n\t\t\t\tfor(let i = 0; i < glb.nodes.length; i++){\n\t\t\t\t\t\tconst node = glb.nodes[i];\n\t\t\t\t\t\tif (node.root === model.graph) {\n\t\t\t\t\t\t\t\tconst gltfNode = glb.gltf.nodes[i];\n\t\t\t\t\t\t\t\tif (gltfNode.hasOwnProperty('mesh')) {\n\t\t\t\t\t\t\t\t\t\tconst meshGroup = glb.renders[gltfNode.mesh].meshes;\n\t\t\t\t\t\t\t\t\t\tfor(let mi = 0; mi < meshGroup.length; mi++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst mesh = meshGroup[mi];\n\t\t\t\t\t\t\t\t\t\t\t\tif (mesh) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcreateMeshInstance(model, mesh, glb.skins, skinInstances, glb.materials, node, gltfNode);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn model;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tconst registry = this._assets;\n\t\t\t\tconst destroyAsset = function(asset) {\n\t\t\t\t\t\tregistry.remove(asset);\n\t\t\t\t\t\tasset.unload();\n\t\t\t\t};\n\t\t\t\tconst destroyAssets = function(assets) {\n\t\t\t\t\t\tassets.forEach((asset)=>{\n\t\t\t\t\t\t\t\tdestroyAsset(asset);\n\t\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tif (this.animations) {\n\t\t\t\t\t\tdestroyAssets(this.animations);\n\t\t\t\t\t\tthis.animations = null;\n\t\t\t\t}\n\t\t\t\tif (this.textures) {\n\t\t\t\t\t\tdestroyAssets(this.textures);\n\t\t\t\t\t\tthis.textures = null;\n\t\t\t\t}\n\t\t\t\tif (this.materials) {\n\t\t\t\t\t\tdestroyAssets(this.materials);\n\t\t\t\t\t\tthis.materials = null;\n\t\t\t\t}\n\t\t\t\tif (this.renders) {\n\t\t\t\t\t\tdestroyAssets(this.renders);\n\t\t\t\t\t\tthis.renders = null;\n\t\t\t\t}\n\t\t\t\tif (this._model) {\n\t\t\t\t\t\tdestroyAsset(this._model);\n\t\t\t\t\t\tthis._model = null;\n\t\t\t\t}\n\t\t\t\tthis.data = null;\n\t\t\t\tthis.assets = null;\n\t\t}\n}\n\nclass GlbContainerParser {\n\t\tconstructor(device, assets, maxRetries){\n\t\t\t\tthis._device = device;\n\t\t\t\tthis._assets = assets;\n\t\t\t\tthis._defaultMaterial = GlbParser.createDefaultMaterial();\n\t\t\t\tthis.maxRetries = maxRetries;\n\t\t}\n\t\t_getUrlWithoutParams(url) {\n\t\t\t\treturn url.indexOf('?') >= 0 ? url.split('?')[0] : url;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tAsset.fetchArrayBuffer(url.load, (err, result)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tGlbParser.parse(this._getUrlWithoutParams(url.original), path.extractPath(url.load), result, this._device, asset.registry, asset.options, (err, result)=>{\n\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tcallback(null, new GlbContainerResource(result, asset, this._assets, this._defaultMaterial));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t}, asset, this.maxRetries);\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\treturn data;\n\t\t}\n\t\tpatch(asset, assets) {}\n}\n\nclass ContainerHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'container');\n\t\t\t\tthis.glbContainerParser = new GlbContainerParser(app.graphicsDevice, app.assets, 0);\n\t\t\t\tthis.parsers = {};\n\t\t}\n\t\tset maxRetries(value) {\n\t\t\t\tthis.glbContainerParser.maxRetries = value;\n\t\t\t\tfor(const parser in this.parsers){\n\t\t\t\t\t\tif (this.parsers.hasOwnProperty(parser)) {\n\t\t\t\t\t\t\t\tthis.parsers[parser].maxRetries = value;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget maxRetries() {\n\t\t\t\treturn this.glbContainerParser.maxRetries;\n\t\t}\n\t\t_getUrlWithoutParams(url) {\n\t\t\t\treturn url.indexOf('?') >= 0 ? url.split('?')[0] : url;\n\t\t}\n\t\t_getParser(url) {\n\t\t\t\tconst ext = url ? path.getExtension(this._getUrlWithoutParams(url)).toLowerCase().replace('.', '') : null;\n\t\t\t\treturn this.parsers[ext] || this.glbContainerParser;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tthis._getParser(url.original).load(url, callback, asset);\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\treturn this._getParser(url).open(url, data, asset);\n\t\t}\n}\n\nclass CssHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'css'), this.decoder = null;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading css resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topenBinary(data) {\n\t\t\t\tthis.decoder ??= new TextDecoder('utf-8');\n\t\t\t\treturn this.decoder.decode(data);\n\t\t}\n}\n\nclass CubemapHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'cubemap');\n\t\t\t\tthis._device = app.graphicsDevice;\n\t\t\t\tthis._registry = app.assets;\n\t\t\t\tthis._loader = app.loader;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tthis.loadAssets(asset, callback);\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\treturn asset ? asset.resource : null;\n\t\t}\n\t\tpatch(asset, registry) {\n\t\t\t\tthis.loadAssets(asset, (err, result)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tregistry.fire('error', asset);\n\t\t\t\t\t\t\t\tregistry.fire(`error:${asset.id}`, err, asset);\n\t\t\t\t\t\t\t\tasset.fire('error', asset);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tgetAssetIds(cubemapAsset) {\n\t\t\t\tconst result = [];\n\t\t\t\tresult[0] = cubemapAsset.file;\n\t\t\t\tif ((cubemapAsset.loadFaces || !cubemapAsset.file) && cubemapAsset.data && cubemapAsset.data.textures) {\n\t\t\t\t\t\tfor(let i = 0; i < 6; ++i){\n\t\t\t\t\t\t\t\tresult[i + 1] = cubemapAsset.data.textures[i];\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tresult[1] = result[2] = result[3] = result[4] = result[5] = result[6] = null;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t}\n\t\tcompareAssetIds(assetIdA, assetIdB) {\n\t\t\t\tif (assetIdA && assetIdB) {\n\t\t\t\t\t\tif (parseInt(assetIdA, 10) === assetIdA || typeof assetIdA === 'string') {\n\t\t\t\t\t\t\t\treturn assetIdA === assetIdB;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn assetIdA.url === assetIdB.url;\n\t\t\t\t}\n\t\t\t\treturn assetIdA !== null === (assetIdB !== null);\n\t\t}\n\t\tupdate(cubemapAsset, assetIds, assets) {\n\t\t\t\tconst assetData = cubemapAsset.data || {};\n\t\t\t\tconst oldAssets = cubemapAsset._handlerState.assets;\n\t\t\t\tconst oldResources = cubemapAsset._resources;\n\t\t\t\tlet tex, mip, i;\n\t\t\t\tconst resources = [\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull\n\t\t\t\t];\n\t\t\t\tconst getType = function() {\n\t\t\t\t\t\tif (assetData.hasOwnProperty('type')) {\n\t\t\t\t\t\t\t\treturn assetData.type;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('rgbm')) {\n\t\t\t\t\t\t\t\treturn assetData.rgbm ? TEXTURETYPE_RGBM : TEXTURETYPE_DEFAULT;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t};\n\t\t\t\tif (!cubemapAsset.loaded || assets[0] !== oldAssets[0]) {\n\t\t\t\t\t\tif (assets[0]) {\n\t\t\t\t\t\t\t\ttex = assets[0].resource;\n\t\t\t\t\t\t\t\tif (tex.cubemap) {\n\t\t\t\t\t\t\t\t\t\tfor(i = 0; i < 6; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tresources[i + 1] = new Texture(this._device, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tname: `${cubemapAsset.name}_prelitCubemap${tex.width >> i}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcubemap: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: getType() || tex.type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\twidth: tex.width >> i,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\theight: tex.height >> i,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: tex.format,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlevels: [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttex._levels[i]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmipmaps: i === 0\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tresources[1] = tex;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tresources[1] = oldResources[1] || null;\n\t\t\t\t\t\tresources[2] = oldResources[2] || null;\n\t\t\t\t\t\tresources[3] = oldResources[3] || null;\n\t\t\t\t\t\tresources[4] = oldResources[4] || null;\n\t\t\t\t\t\tresources[5] = oldResources[5] || null;\n\t\t\t\t\t\tresources[6] = oldResources[6] || null;\n\t\t\t\t}\n\t\t\t\tconst faceAssets = assets.slice(1);\n\t\t\t\tif (!cubemapAsset.loaded || !this.cmpArrays(faceAssets, oldAssets.slice(1))) {\n\t\t\t\t\t\tif (faceAssets.indexOf(null) === -1) {\n\t\t\t\t\t\t\t\tconst faceTextures = faceAssets.map((asset)=>{\n\t\t\t\t\t\t\t\t\t\treturn asset.resource;\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tconst faceLevels = [];\n\t\t\t\t\t\t\t\tfor(mip = 0; mip < faceTextures[0]._levels.length; ++mip){\n\t\t\t\t\t\t\t\t\t\tfaceLevels.push(faceTextures.map((faceTexture)=>{\n\t\t\t\t\t\t\t\t\t\t\t\treturn faceTexture._levels[mip];\n\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst format = faceTextures[0].format;\n\t\t\t\t\t\t\t\tconst faces = new Texture(this._device, {\n\t\t\t\t\t\t\t\t\t\tname: `${cubemapAsset.name}_faces`,\n\t\t\t\t\t\t\t\t\t\tcubemap: true,\n\t\t\t\t\t\t\t\t\t\ttype: getType() || faceTextures[0].type,\n\t\t\t\t\t\t\t\t\t\twidth: faceTextures[0].width,\n\t\t\t\t\t\t\t\t\t\theight: faceTextures[0].height,\n\t\t\t\t\t\t\t\t\t\tformat: format === PIXELFORMAT_RGB8 ? PIXELFORMAT_RGBA8 : format,\n\t\t\t\t\t\t\t\t\t\tmipmaps: assetData.mipmaps ?? true,\n\t\t\t\t\t\t\t\t\t\tlevels: faceLevels,\n\t\t\t\t\t\t\t\t\t\tminFilter: assetData.hasOwnProperty('minFilter') ? assetData.minFilter : faceTextures[0].minFilter,\n\t\t\t\t\t\t\t\t\t\tmagFilter: assetData.hasOwnProperty('magFilter') ? assetData.magFilter : faceTextures[0].magFilter,\n\t\t\t\t\t\t\t\t\t\tanisotropy: assetData.hasOwnProperty('anisotropy') ? assetData.anisotropy : 1,\n\t\t\t\t\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tresources[0] = faces;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tresources[0] = oldResources[0] || null;\n\t\t\t\t}\n\t\t\t\tif (!this.cmpArrays(resources, oldResources)) {\n\t\t\t\t\t\tcubemapAsset.resources = resources;\n\t\t\t\t\t\tcubemapAsset._handlerState.assetIds = assetIds;\n\t\t\t\t\t\tcubemapAsset._handlerState.assets = assets;\n\t\t\t\t\t\tfor(i = 0; i < oldResources.length; ++i){\n\t\t\t\t\t\t\t\tif (oldResources[i] !== null && resources.indexOf(oldResources[i]) === -1) {\n\t\t\t\t\t\t\t\t\t\toldResources[i].destroy();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(i = 0; i < oldAssets.length; ++i){\n\t\t\t\t\t\tif (oldAssets[i] !== null && assets.indexOf(oldAssets[i]) === -1) {\n\t\t\t\t\t\t\t\toldAssets[i].unload();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcmpArrays(arr1, arr2) {\n\t\t\t\tif (arr1.length !== arr2.length) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < arr1.length; ++i){\n\t\t\t\t\t\tif (arr1[i] !== arr2[i]) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t\tresolveId(value) {\n\t\t\t\tconst valueInt = parseInt(value, 10);\n\t\t\t\treturn valueInt === value || valueInt.toString() === value ? valueInt : value;\n\t\t}\n\t\tloadAssets(cubemapAsset, callback) {\n\t\t\t\tif (!cubemapAsset.hasOwnProperty('_handlerState')) {\n\t\t\t\t\t\tcubemapAsset._handlerState = {\n\t\t\t\t\t\t\t\tassetIds: [\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tassets: [\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst self = this;\n\t\t\t\tconst assetIds = self.getAssetIds(cubemapAsset);\n\t\t\t\tconst assets = [\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tnull\n\t\t\t\t];\n\t\t\t\tconst loadedAssetIds = cubemapAsset._handlerState.assetIds;\n\t\t\t\tconst loadedAssets = cubemapAsset._handlerState.assets;\n\t\t\t\tconst registry = self._registry;\n\t\t\t\tlet awaiting = 7;\n\t\t\t\tconst onLoad = function(index, asset) {\n\t\t\t\t\t\tassets[index] = asset;\n\t\t\t\t\t\tawaiting--;\n\t\t\t\t\t\tif (awaiting === 0) {\n\t\t\t\t\t\t\t\tself.update(cubemapAsset, assetIds, assets);\n\t\t\t\t\t\t\t\tcallback(null, cubemapAsset.resources);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst onError = function(index, err, asset) {\n\t\t\t\t\t\tcallback(err);\n\t\t\t\t};\n\t\t\t\tconst processTexAsset = function(index, texAsset) {\n\t\t\t\t\t\tif (texAsset.loaded) {\n\t\t\t\t\t\t\t\tonLoad(index, texAsset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tregistry.once(`load:${texAsset.id}`, onLoad.bind(self, index));\n\t\t\t\t\t\t\t\tregistry.once(`error:${texAsset.id}`, onError.bind(self, index));\n\t\t\t\t\t\t\t\tif (!texAsset.loading) {\n\t\t\t\t\t\t\t\t\t\tregistry.load(texAsset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tlet texAsset;\n\t\t\t\tfor(let i = 0; i < 7; ++i){\n\t\t\t\t\t\tconst assetId = this.resolveId(assetIds[i]);\n\t\t\t\t\t\tif (!assetId) {\n\t\t\t\t\t\t\t\tonLoad(i, null);\n\t\t\t\t\t\t} else if (self.compareAssetIds(assetId, loadedAssetIds[i])) {\n\t\t\t\t\t\t\t\tprocessTexAsset(i, loadedAssets[i]);\n\t\t\t\t\t\t} else if (parseInt(assetId, 10) === assetId) {\n\t\t\t\t\t\t\t\ttexAsset = registry.get(assetId);\n\t\t\t\t\t\t\t\tif (texAsset) {\n\t\t\t\t\t\t\t\t\t\tprocessTexAsset(i, texAsset);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tsetTimeout(((index, assetId_)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tconst texAsset = registry.get(assetId_);\n\t\t\t\t\t\t\t\t\t\t\t\tif (texAsset) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tprocessTexAsset(index, texAsset);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonError(index, `failed to find dependent cubemap asset=${assetId_}`);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}).bind(null, i, assetId));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst file = typeof assetId === 'string' ? {\n\t\t\t\t\t\t\t\t\t\turl: assetId,\n\t\t\t\t\t\t\t\t\t\tfilename: assetId\n\t\t\t\t\t\t\t\t} : assetId;\n\t\t\t\t\t\t\t\tconst data = file.url.search('.dds') === -1 ? {\n\t\t\t\t\t\t\t\t\t\ttype: 'rgbp',\n\t\t\t\t\t\t\t\t\t\taddressu: 'clamp',\n\t\t\t\t\t\t\t\t\t\taddressv: 'clamp',\n\t\t\t\t\t\t\t\t\t\tmipmaps: false\n\t\t\t\t\t\t\t\t} : null;\n\t\t\t\t\t\t\t\ttexAsset = new Asset(`${cubemapAsset.name}_part_${i}`, 'texture', file, data);\n\t\t\t\t\t\t\t\tregistry.add(texAsset);\n\t\t\t\t\t\t\t\tprocessTexAsset(i, texAsset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass FolderHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'folder');\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tcallback(null, null);\n\t\t}\n}\n\nclass Font {\n\t\tconstructor(textures, data){\n\t\t\t\tthis.type = data ? data.type || FONT_MSDF : FONT_MSDF;\n\t\t\t\tthis.em = 1;\n\t\t\t\tthis.textures = textures;\n\t\t\t\tthis.intensity = 0.0;\n\t\t\t\tthis._data = null;\n\t\t\t\tthis.data = data;\n\t\t}\n\t\tset data(value) {\n\t\t\t\tthis._data = value;\n\t\t\t\tif (!value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._data.intensity !== undefined) {\n\t\t\t\t\t\tthis.intensity = this._data.intensity;\n\t\t\t\t}\n\t\t\t\tif (!this._data.info) {\n\t\t\t\t\t\tthis._data.info = {};\n\t\t\t\t}\n\t\t\t\tif (!this._data.version || this._data.version < 2) {\n\t\t\t\t\t\tthis._data.info.maps = [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twidth: this._data.info.width,\n\t\t\t\t\t\t\t\t\t\theight: this._data.info.height\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t];\n\t\t\t\t\t\tif (this._data.chars) {\n\t\t\t\t\t\t\t\tfor(const key in this._data.chars){\n\t\t\t\t\t\t\t\t\t\tthis._data.chars[key].map = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget data() {\n\t\t\t\treturn this._data;\n\t\t}\n}\n\nfunction upgradeDataSchema(data) {\n\t\tif (data.version < 3) {\n\t\t\t\tif (data.version < 2) {\n\t\t\t\t\t\tdata.info.maps = data.info.maps || [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twidth: data.info.width,\n\t\t\t\t\t\t\t\t\t\theight: data.info.height\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tdata.chars = Object.keys(data.chars || {}).reduce((newChars, key)=>{\n\t\t\t\t\t\tconst existing = data.chars[key];\n\t\t\t\t\t\tconst newKey = existing.letter !== undefined ? existing.letter : string.fromCodePoint(key);\n\t\t\t\t\t\tif (data.version < 2) {\n\t\t\t\t\t\t\t\texisting.map = existing.map || 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnewChars[newKey] = existing;\n\t\t\t\t\t\treturn newChars;\n\t\t\t\t}, {});\n\t\t\t\tdata.version = 3;\n\t\t}\n\t\treturn data;\n}\nclass FontHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'font');\n\t\t\t\tthis._loader = app.loader;\n\t\t\t\tthis.maxRetries = 0;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst self = this;\n\t\t\t\tif (path.getExtension(url.original) === '.json') {\n\t\t\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\t\t\tconst data = upgradeDataSchema(response);\n\t\t\t\t\t\t\t\t\t\tself._loadTextures(url.load.replace('.json', '.png'), data, (err, textures)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(null, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata: data,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttextures: textures\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcallback(`Error loading font resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tif (asset && asset.data) {\n\t\t\t\t\t\t\t\tasset.data = upgradeDataSchema(asset.data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._loadTextures(url.load, asset && asset.data, callback);\n\t\t\t\t}\n\t\t}\n\t\t_loadTextures(url, data, callback) {\n\t\t\t\tconst numTextures = data.info.maps.length;\n\t\t\t\tlet numLoaded = 0;\n\t\t\t\tlet error = null;\n\t\t\t\tconst textures = new Array(numTextures);\n\t\t\t\tconst loader = this._loader;\n\t\t\t\tconst loadTexture = function(index) {\n\t\t\t\t\t\tconst onLoaded = function(err, texture) {\n\t\t\t\t\t\t\t\tif (error) return;\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\terror = err;\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttexture.upload();\n\t\t\t\t\t\t\t\ttextures[index] = texture;\n\t\t\t\t\t\t\t\tnumLoaded++;\n\t\t\t\t\t\t\t\tif (numLoaded === numTextures) {\n\t\t\t\t\t\t\t\t\t\tcallback(null, textures);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (index === 0) {\n\t\t\t\t\t\t\t\tloader.load(url, 'texture', onLoaded);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tloader.load(url.replace('.png', `${index}.png`), 'texture', onLoaded);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tfor(let i = 0; i < numTextures; i++){\n\t\t\t\t\t\tloadTexture(i);\n\t\t\t\t}\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\tlet font;\n\t\t\t\tif (data.textures) {\n\t\t\t\t\t\tfont = new Font(data.textures, data.data);\n\t\t\t\t} else {\n\t\t\t\t\t\tfont = new Font(data, null);\n\t\t\t\t}\n\t\t\t\treturn font;\n\t\t}\n\t\tpatch(asset, assets) {\n\t\t\t\tconst font = asset.resource;\n\t\t\t\tif (!font.data && asset.data) {\n\t\t\t\t\t\tfont.data = asset.data;\n\t\t\t\t} else if (!asset.data && font.data) {\n\t\t\t\t\t\tasset.data = font.data;\n\t\t\t\t}\n\t\t\t\tif (asset.data) {\n\t\t\t\t\t\tasset.data = upgradeDataSchema(asset.data);\n\t\t\t\t}\n\t\t}\n}\n\nconst SH_C0 = 0.28209479177387814;\nclass SplatCompressedIterator {\n\t\tconstructor(gsplatData, p, r, s, c, sh){\n\t\t\t\tconst unpackUnorm = (value, bits)=>{\n\t\t\t\t\t\tconst t = (1 << bits) - 1;\n\t\t\t\t\t\treturn (value & t) / t;\n\t\t\t\t};\n\t\t\t\tconst unpack111011 = (result, value)=>{\n\t\t\t\t\t\tresult.x = unpackUnorm(value >>> 21, 11);\n\t\t\t\t\t\tresult.y = unpackUnorm(value >>> 11, 10);\n\t\t\t\t\t\tresult.z = unpackUnorm(value, 11);\n\t\t\t\t};\n\t\t\t\tconst unpack8888 = (result, value)=>{\n\t\t\t\t\t\tresult.x = unpackUnorm(value >>> 24, 8);\n\t\t\t\t\t\tresult.y = unpackUnorm(value >>> 16, 8);\n\t\t\t\t\t\tresult.z = unpackUnorm(value >>> 8, 8);\n\t\t\t\t\t\tresult.w = unpackUnorm(value, 8);\n\t\t\t\t};\n\t\t\t\tconst unpackRot = (result, value)=>{\n\t\t\t\t\t\tconst norm = 1.0 / (Math.sqrt(2) * 0.5);\n\t\t\t\t\t\tconst a = (unpackUnorm(value >>> 20, 10) - 0.5) * norm;\n\t\t\t\t\t\tconst b = (unpackUnorm(value >>> 10, 10) - 0.5) * norm;\n\t\t\t\t\t\tconst c = (unpackUnorm(value, 10) - 0.5) * norm;\n\t\t\t\t\t\tconst m = Math.sqrt(1.0 - (a * a + b * b + c * c));\n\t\t\t\t\t\tswitch(value >>> 30){\n\t\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\t\t\tresult.set(a, b, c, m);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\t\t\tresult.set(m, b, c, a);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\t\t\tresult.set(b, m, c, a);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 3:\n\t\t\t\t\t\t\t\t\t\tresult.set(b, c, m, a);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst lerp = (a, b, t)=>a * (1 - t) + b * t;\n\t\t\t\tconst { chunkData, chunkSize, vertexData, shData0, shData1, shData2, shBands } = gsplatData;\n\t\t\t\tconst shCoeffs = [\n\t\t\t\t\t\t3,\n\t\t\t\t\t\t8,\n\t\t\t\t\t\t15\n\t\t\t\t][shBands - 1];\n\t\t\t\tthis.read = (i)=>{\n\t\t\t\t\t\tconst ci = Math.floor(i / 256) * chunkSize;\n\t\t\t\t\t\tif (p) {\n\t\t\t\t\t\t\t\tunpack111011(p, vertexData[i * 4 + 0]);\n\t\t\t\t\t\t\t\tp.x = lerp(chunkData[ci + 0], chunkData[ci + 3], p.x);\n\t\t\t\t\t\t\t\tp.y = lerp(chunkData[ci + 1], chunkData[ci + 4], p.y);\n\t\t\t\t\t\t\t\tp.z = lerp(chunkData[ci + 2], chunkData[ci + 5], p.z);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (r) {\n\t\t\t\t\t\t\t\tunpackRot(r, vertexData[i * 4 + 1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (s) {\n\t\t\t\t\t\t\t\tunpack111011(s, vertexData[i * 4 + 2]);\n\t\t\t\t\t\t\t\ts.x = lerp(chunkData[ci + 6], chunkData[ci + 9], s.x);\n\t\t\t\t\t\t\t\ts.y = lerp(chunkData[ci + 7], chunkData[ci + 10], s.y);\n\t\t\t\t\t\t\t\ts.z = lerp(chunkData[ci + 8], chunkData[ci + 11], s.z);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (c) {\n\t\t\t\t\t\t\t\tunpack8888(c, vertexData[i * 4 + 3]);\n\t\t\t\t\t\t\t\tif (chunkSize > 12) {\n\t\t\t\t\t\t\t\t\t\tc.x = lerp(chunkData[ci + 12], chunkData[ci + 15], c.x);\n\t\t\t\t\t\t\t\t\t\tc.y = lerp(chunkData[ci + 13], chunkData[ci + 16], c.y);\n\t\t\t\t\t\t\t\t\t\tc.z = lerp(chunkData[ci + 14], chunkData[ci + 17], c.z);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sh && shBands > 0) {\n\t\t\t\t\t\t\t\tconst shData = [\n\t\t\t\t\t\t\t\t\t\tshData0,\n\t\t\t\t\t\t\t\t\t\tshData1,\n\t\t\t\t\t\t\t\t\t\tshData2\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\tfor(let j = 0; j < 3; ++j){\n\t\t\t\t\t\t\t\t\t\tfor(let k = 0; k < 15; ++k){\n\t\t\t\t\t\t\t\t\t\t\t\tsh[j * 15 + k] = k < shCoeffs ? shData[j][i * 16 + k] * (8 / 255) - 4 : 0;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t}\n}\nclass GSplatCompressedData {\n\t\tcreateIter(p, r, s, c, sh) {\n\t\t\t\treturn new SplatCompressedIterator(this, p, r, s, c, sh);\n\t\t}\n\t\tcalcAabb(result) {\n\t\t\t\tconst { chunkData, numChunks, chunkSize } = this;\n\t\t\t\tlet s = Math.exp(Math.max(chunkData[9], chunkData[10], chunkData[11]));\n\t\t\t\tlet mx = chunkData[0] - s;\n\t\t\t\tlet my = chunkData[1] - s;\n\t\t\t\tlet mz = chunkData[2] - s;\n\t\t\t\tlet Mx = chunkData[3] + s;\n\t\t\t\tlet My = chunkData[4] + s;\n\t\t\t\tlet Mz = chunkData[5] + s;\n\t\t\t\tfor(let i = 1; i < numChunks; ++i){\n\t\t\t\t\t\tconst off = i * chunkSize;\n\t\t\t\t\t\ts = Math.exp(Math.max(chunkData[off + 9], chunkData[off + 10], chunkData[off + 11]));\n\t\t\t\t\t\tmx = Math.min(mx, chunkData[off + 0] - s);\n\t\t\t\t\t\tmy = Math.min(my, chunkData[off + 1] - s);\n\t\t\t\t\t\tmz = Math.min(mz, chunkData[off + 2] - s);\n\t\t\t\t\t\tMx = Math.max(Mx, chunkData[off + 3] + s);\n\t\t\t\t\t\tMy = Math.max(My, chunkData[off + 4] + s);\n\t\t\t\t\t\tMz = Math.max(Mz, chunkData[off + 5] + s);\n\t\t\t\t}\n\t\t\t\tresult.center.set((mx + Mx) * 0.5, (my + My) * 0.5, (mz + Mz) * 0.5);\n\t\t\t\tresult.halfExtents.set((Mx - mx) * 0.5, (My - my) * 0.5, (Mz - mz) * 0.5);\n\t\t\t\treturn true;\n\t\t}\n\t\tgetCenters(result) {\n\t\t\t\tconst { vertexData, chunkData, numChunks, chunkSize } = this;\n\t\t\t\tlet mx, my, mz, Mx, My, Mz;\n\t\t\t\tfor(let c = 0; c < numChunks; ++c){\n\t\t\t\t\t\tconst off = c * chunkSize;\n\t\t\t\t\t\tmx = chunkData[off + 0];\n\t\t\t\t\t\tmy = chunkData[off + 1];\n\t\t\t\t\t\tmz = chunkData[off + 2];\n\t\t\t\t\t\tMx = chunkData[off + 3];\n\t\t\t\t\t\tMy = chunkData[off + 4];\n\t\t\t\t\t\tMz = chunkData[off + 5];\n\t\t\t\t\t\tconst end = Math.min(this.numSplats, (c + 1) * 256);\n\t\t\t\t\t\tfor(let i = c * 256; i < end; ++i){\n\t\t\t\t\t\t\t\tconst p = vertexData[i * 4];\n\t\t\t\t\t\t\t\tconst px = (p >>> 21) / 2047;\n\t\t\t\t\t\t\t\tconst py = (p >>> 11 & 0x3ff) / 1023;\n\t\t\t\t\t\t\t\tconst pz = (p & 0x7ff) / 2047;\n\t\t\t\t\t\t\t\tresult[i * 3 + 0] = (1 - px) * mx + px * Mx;\n\t\t\t\t\t\t\t\tresult[i * 3 + 1] = (1 - py) * my + py * My;\n\t\t\t\t\t\t\t\tresult[i * 3 + 2] = (1 - pz) * mz + pz * Mz;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tgetChunks(result) {\n\t\t\t\tconst { chunkData, numChunks, chunkSize } = this;\n\t\t\t\tlet mx, my, mz, Mx, My, Mz;\n\t\t\t\tfor(let c = 0; c < numChunks; ++c){\n\t\t\t\t\t\tconst off = c * chunkSize;\n\t\t\t\t\t\tmx = chunkData[off + 0];\n\t\t\t\t\t\tmy = chunkData[off + 1];\n\t\t\t\t\t\tmz = chunkData[off + 2];\n\t\t\t\t\t\tMx = chunkData[off + 3];\n\t\t\t\t\t\tMy = chunkData[off + 4];\n\t\t\t\t\t\tMz = chunkData[off + 5];\n\t\t\t\t\t\tresult[c * 6 + 0] = mx;\n\t\t\t\t\t\tresult[c * 6 + 1] = my;\n\t\t\t\t\t\tresult[c * 6 + 2] = mz;\n\t\t\t\t\t\tresult[c * 6 + 3] = Mx;\n\t\t\t\t\t\tresult[c * 6 + 4] = My;\n\t\t\t\t\t\tresult[c * 6 + 5] = Mz;\n\t\t\t\t}\n\t\t}\n\t\tcalcFocalPoint(result) {\n\t\t\t\tconst { chunkData, numChunks, chunkSize } = this;\n\t\t\t\tresult.x = 0;\n\t\t\t\tresult.y = 0;\n\t\t\t\tresult.z = 0;\n\t\t\t\tfor(let i = 0; i < numChunks; ++i){\n\t\t\t\t\t\tconst off = i * chunkSize;\n\t\t\t\t\t\tresult.x += chunkData[off + 0] + chunkData[off + 3];\n\t\t\t\t\t\tresult.y += chunkData[off + 1] + chunkData[off + 4];\n\t\t\t\t\t\tresult.z += chunkData[off + 2] + chunkData[off + 5];\n\t\t\t\t}\n\t\t\t\tresult.mulScalar(0.5 / numChunks);\n\t\t}\n\t\tget isCompressed() {\n\t\t\t\treturn true;\n\t\t}\n\t\tget numChunks() {\n\t\t\t\treturn Math.ceil(this.numSplats / 256);\n\t\t}\n\t\tget chunkSize() {\n\t\t\t\treturn this.chunkData.length / this.numChunks;\n\t\t}\n\t\tdecompress() {\n\t\t\t\tconst members = [\n\t\t\t\t\t\t'x',\n\t\t\t\t\t\t'y',\n\t\t\t\t\t\t'z',\n\t\t\t\t\t\t'f_dc_0',\n\t\t\t\t\t\t'f_dc_1',\n\t\t\t\t\t\t'f_dc_2',\n\t\t\t\t\t\t'opacity',\n\t\t\t\t\t\t'scale_0',\n\t\t\t\t\t\t'scale_1',\n\t\t\t\t\t\t'scale_2',\n\t\t\t\t\t\t'rot_0',\n\t\t\t\t\t\t'rot_1',\n\t\t\t\t\t\t'rot_2',\n\t\t\t\t\t\t'rot_3'\n\t\t\t\t];\n\t\t\t\tconst { shBands } = this;\n\t\t\t\tif (shBands > 0) {\n\t\t\t\t\t\tconst shMembers = [];\n\t\t\t\t\t\tfor(let i = 0; i < 45; ++i){\n\t\t\t\t\t\t\t\tshMembers.push(`f_rest_${i}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst location = Math.max(...[\n\t\t\t\t\t\t\t\t'f_dc_0',\n\t\t\t\t\t\t\t\t'f_dc_1',\n\t\t\t\t\t\t\t\t'f_dc_2'\n\t\t\t\t\t\t].map((name)=>members.indexOf(name)));\n\t\t\t\t\t\tmembers.splice(location + 1, 0, ...shMembers);\n\t\t\t\t}\n\t\t\t\tconst data = {};\n\t\t\t\tmembers.forEach((name)=>{\n\t\t\t\t\t\tdata[name] = new Float32Array(this.numSplats);\n\t\t\t\t});\n\t\t\t\tconst p = new Vec3();\n\t\t\t\tconst r = new Quat();\n\t\t\t\tconst s = new Vec3();\n\t\t\t\tconst c = new Vec4();\n\t\t\t\tconst sh = shBands > 0 ? new Float32Array(45) : null;\n\t\t\t\tconst iter = this.createIter(p, r, s, c, sh);\n\t\t\t\tfor(let i = 0; i < this.numSplats; ++i){\n\t\t\t\t\t\titer.read(i);\n\t\t\t\t\t\tdata.x[i] = p.x;\n\t\t\t\t\t\tdata.y[i] = p.y;\n\t\t\t\t\t\tdata.z[i] = p.z;\n\t\t\t\t\t\tdata.rot_1[i] = r.x;\n\t\t\t\t\t\tdata.rot_2[i] = r.y;\n\t\t\t\t\t\tdata.rot_3[i] = r.z;\n\t\t\t\t\t\tdata.rot_0[i] = r.w;\n\t\t\t\t\t\tdata.scale_0[i] = s.x;\n\t\t\t\t\t\tdata.scale_1[i] = s.y;\n\t\t\t\t\t\tdata.scale_2[i] = s.z;\n\t\t\t\t\t\tdata.f_dc_0[i] = (c.x - 0.5) / SH_C0;\n\t\t\t\t\t\tdata.f_dc_1[i] = (c.y - 0.5) / SH_C0;\n\t\t\t\t\t\tdata.f_dc_2[i] = (c.z - 0.5) / SH_C0;\n\t\t\t\t\t\tdata.opacity[i] = c.w <= 0 ? -40 : c.w >= 1 ? 40 : -Math.log(1 / c.w - 1);\n\t\t\t\t\t\tif (sh) {\n\t\t\t\t\t\t\t\tfor(let c = 0; c < 45; ++c){\n\t\t\t\t\t\t\t\t\t\tdata[`f_rest_${c}`][i] = sh[c];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn new GSplatData([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: 'vertex',\n\t\t\t\t\t\t\t\tcount: this.numSplats,\n\t\t\t\t\t\t\t\tproperties: members.map((name)=>{\n\t\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\t\t\tname: name,\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'float',\n\t\t\t\t\t\t\t\t\t\t\t\tbyteSize: 4,\n\t\t\t\t\t\t\t\t\t\t\t\tstorage: data[name]\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t], this.comments);\n\t\t}\n}\n\nconst strideCopy = (target, targetStride, src, srcStride, numEntries)=>{\n\t\tfor(let i = 0; i < numEntries; ++i){\n\t\t\t\tfor(let j = 0; j < srcStride; ++j){\n\t\t\t\t\t\ttarget[i * targetStride + j] = src[i * srcStride + j];\n\t\t\t\t}\n\t\t}\n};\nclass GSplatCompressedResource extends GSplatResourceBase {\n\t\tconstructor(device, gsplatData){\n\t\t\t\tsuper(device, gsplatData);\n\t\t\t\tconst { chunkData, chunkSize, numChunks, numSplats, vertexData, shBands } = gsplatData;\n\t\t\t\tthis.chunks = new Float32Array(numChunks * 6);\n\t\t\t\tgsplatData.getChunks(this.chunks);\n\t\t\t\tthis.packedTexture = this.createTexture('packedData', PIXELFORMAT_RGBA32U, this.evalTextureSize(numSplats), vertexData);\n\t\t\t\tconst chunkTextureSize = this.evalTextureSize(numChunks);\n\t\t\t\tchunkTextureSize.x *= 5;\n\t\t\t\tthis.chunkTexture = this.createTexture('chunkData', PIXELFORMAT_RGBA32F, chunkTextureSize);\n\t\t\t\tconst chunkTextureData = this.chunkTexture.lock();\n\t\t\t\tstrideCopy(chunkTextureData, 20, chunkData, chunkSize, numChunks);\n\t\t\t\tif (chunkSize === 12) {\n\t\t\t\t\t\tfor(let i = 0; i < numChunks; ++i){\n\t\t\t\t\t\t\t\tchunkTextureData[i * 20 + 15] = 1;\n\t\t\t\t\t\t\t\tchunkTextureData[i * 20 + 16] = 1;\n\t\t\t\t\t\t\t\tchunkTextureData[i * 20 + 17] = 1;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.chunkTexture.unlock();\n\t\t\t\tif (shBands > 0) {\n\t\t\t\t\t\tconst size = this.evalTextureSize(numSplats);\n\t\t\t\t\t\tthis.shTexture0 = this.createTexture('shTexture0', PIXELFORMAT_RGBA32U, size, new Uint32Array(gsplatData.shData0.buffer));\n\t\t\t\t\t\tthis.shTexture1 = this.createTexture('shTexture1', PIXELFORMAT_RGBA32U, size, new Uint32Array(gsplatData.shData1.buffer));\n\t\t\t\t\t\tthis.shTexture2 = this.createTexture('shTexture2', PIXELFORMAT_RGBA32U, size, new Uint32Array(gsplatData.shData2.buffer));\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.shTexture0 = null;\n\t\t\t\t\t\tthis.shTexture1 = null;\n\t\t\t\t\t\tthis.shTexture2 = null;\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.packedTexture?.destroy();\n\t\t\t\tthis.chunkTexture?.destroy();\n\t\t\t\tthis.shTexture0?.destroy();\n\t\t\t\tthis.shTexture1?.destroy();\n\t\t\t\tthis.shTexture2?.destroy();\n\t\t\t\tsuper.destroy();\n\t\t}\n\t\tconfigureMaterialDefines(defines) {\n\t\t\t\tdefines.set('GSPLAT_COMPRESSED_DATA', true);\n\t\t\t\tdefines.set('SH_BANDS', this.shTexture0 ? 3 : 0);\n\t\t}\n\t\tconfigureMaterial(material) {\n\t\t\t\tthis.configureMaterialDefines(material.defines);\n\t\t\t\tmaterial.setParameter('packedTexture', this.packedTexture);\n\t\t\t\tmaterial.setParameter('chunkTexture', this.chunkTexture);\n\t\t\t\tif (this.shTexture0) {\n\t\t\t\t\t\tmaterial.setParameter('shTexture0', this.shTexture0);\n\t\t\t\t\t\tmaterial.setParameter('shTexture1', this.shTexture1);\n\t\t\t\t\t\tmaterial.setParameter('shTexture2', this.shTexture2);\n\t\t\t\t}\n\t\t}\n\t\tevalTextureSize(count) {\n\t\t\t\tconst width = Math.ceil(Math.sqrt(count));\n\t\t\t\tconst height = Math.ceil(count / width);\n\t\t\t\treturn new Vec2(width, height);\n\t\t}\n}\n\nconst magicBytes = new Uint8Array([\n\t\t112,\n\t\t108,\n\t\t121,\n\t\t10\n]);\nconst endHeaderBytes = new Uint8Array([\n\t\t10,\n\t\t101,\n\t\t110,\n\t\t100,\n\t\t95,\n\t\t104,\n\t\t101,\n\t\t97,\n\t\t100,\n\t\t101,\n\t\t114,\n\t\t10\n]);\nconst dataTypeMap = new Map([\n\t\t[\n\t\t\t\t'char',\n\t\t\t\tInt8Array\n\t\t],\n\t\t[\n\t\t\t\t'uchar',\n\t\t\t\tUint8Array\n\t\t],\n\t\t[\n\t\t\t\t'short',\n\t\t\t\tInt16Array\n\t\t],\n\t\t[\n\t\t\t\t'ushort',\n\t\t\t\tUint16Array\n\t\t],\n\t\t[\n\t\t\t\t'int',\n\t\t\t\tInt32Array\n\t\t],\n\t\t[\n\t\t\t\t'uint',\n\t\t\t\tUint32Array\n\t\t],\n\t\t[\n\t\t\t\t'float',\n\t\t\t\tFloat32Array\n\t\t],\n\t\t[\n\t\t\t\t'double',\n\t\t\t\tFloat64Array\n\t\t]\n]);\nclass StreamBuf {\n\t\tconstructor(reader, progressFunc){\n\t\t\t\tthis.head = 0;\n\t\t\t\tthis.tail = 0;\n\t\t\t\tthis.reader = reader;\n\t\t\t\tthis.progressFunc = progressFunc;\n\t\t}\n\t\tasync read() {\n\t\t\t\tconst { value, done } = await this.reader.read();\n\t\t\t\tif (done) {\n\t\t\t\t\t\tthrow new Error('Stream finished before end of header');\n\t\t\t\t}\n\t\t\t\tthis.push(value);\n\t\t\t\tthis.progressFunc?.(value.byteLength);\n\t\t}\n\t\tpush(data) {\n\t\t\t\tif (!this.data) {\n\t\t\t\t\t\tthis.data = data;\n\t\t\t\t\t\tthis.view = new DataView(this.data.buffer);\n\t\t\t\t\t\tthis.tail = data.length;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst remaining = this.tail - this.head;\n\t\t\t\t\t\tconst newSize = remaining + data.length;\n\t\t\t\t\t\tif (this.data.length >= newSize) {\n\t\t\t\t\t\t\t\tif (this.head > 0) {\n\t\t\t\t\t\t\t\t\t\tthis.data.copyWithin(0, this.head, this.tail);\n\t\t\t\t\t\t\t\t\t\tthis.data.set(data, remaining);\n\t\t\t\t\t\t\t\t\t\tthis.head = 0;\n\t\t\t\t\t\t\t\t\t\tthis.tail = newSize;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.data.set(data, this.tail);\n\t\t\t\t\t\t\t\t\t\tthis.tail += data.length;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst tmp = new Uint8Array(newSize);\n\t\t\t\t\t\t\t\tif (this.head > 0 || this.tail < this.data.length) {\n\t\t\t\t\t\t\t\t\t\ttmp.set(this.data.subarray(this.head, this.tail), 0);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\ttmp.set(this.data, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttmp.set(data, remaining);\n\t\t\t\t\t\t\t\tthis.data = tmp;\n\t\t\t\t\t\t\t\tthis.view = new DataView(this.data.buffer);\n\t\t\t\t\t\t\t\tthis.head = 0;\n\t\t\t\t\t\t\t\tthis.tail = newSize;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tcompact() {\n\t\t\t\tif (this.head > 0) {\n\t\t\t\t\t\tthis.data.copyWithin(0, this.head, this.tail);\n\t\t\t\t\t\tthis.tail -= this.head;\n\t\t\t\t\t\tthis.head = 0;\n\t\t\t\t}\n\t\t}\n\t\tget remaining() {\n\t\t\t\treturn this.tail - this.head;\n\t\t}\n\t\tgetInt8() {\n\t\t\t\tconst result = this.view.getInt8(this.head);\n\t\t\t\tthis.head++;\n\t\t\t\treturn result;\n\t\t}\n\t\tgetUint8() {\n\t\t\t\tconst result = this.view.getUint8(this.head);\n\t\t\t\tthis.head++;\n\t\t\t\treturn result;\n\t\t}\n\t\tgetInt16() {\n\t\t\t\tconst result = this.view.getInt16(this.head, true);\n\t\t\t\tthis.head += 2;\n\t\t\t\treturn result;\n\t\t}\n\t\tgetUint16() {\n\t\t\t\tconst result = this.view.getUint16(this.head, true);\n\t\t\t\tthis.head += 2;\n\t\t\t\treturn result;\n\t\t}\n\t\tgetInt32() {\n\t\t\t\tconst result = this.view.getInt32(this.head, true);\n\t\t\t\tthis.head += 4;\n\t\t\t\treturn result;\n\t\t}\n\t\tgetUint32() {\n\t\t\t\tconst result = this.view.getUint32(this.head, true);\n\t\t\t\tthis.head += 4;\n\t\t\t\treturn result;\n\t\t}\n\t\tgetFloat32() {\n\t\t\t\tconst result = this.view.getFloat32(this.head, true);\n\t\t\t\tthis.head += 4;\n\t\t\t\treturn result;\n\t\t}\n\t\tgetFloat64() {\n\t\t\t\tconst result = this.view.getFloat64(this.head, true);\n\t\t\t\tthis.head += 8;\n\t\t\t\treturn result;\n\t\t}\n}\nconst parseHeader = (lines)=>{\n\t\tconst elements = [];\n\t\tconst comments = [];\n\t\tlet format;\n\t\tfor(let i = 1; i < lines.length; ++i){\n\t\t\t\tconst words = lines[i].split(' ');\n\t\t\t\tswitch(words[0]){\n\t\t\t\t\t\tcase 'comment':\n\t\t\t\t\t\t\t\tcomments.push(words.slice(1).join(' '));\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'format':\n\t\t\t\t\t\t\t\tformat = words[1];\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'element':\n\t\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\t\t\tname: words[1],\n\t\t\t\t\t\t\t\t\t\tcount: parseInt(words[2], 10),\n\t\t\t\t\t\t\t\t\t\tproperties: []\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'property':\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tif (!dataTypeMap.has(words[1])) {\n\t\t\t\t\t\t\t\t\t\t\t\tthrow new Error(`Unrecognized property data type '${words[1]}' in ply header`);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst element = elements[elements.length - 1];\n\t\t\t\t\t\t\t\t\t\telement.properties.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: words[1],\n\t\t\t\t\t\t\t\t\t\t\t\tname: words[2],\n\t\t\t\t\t\t\t\t\t\t\t\tstorage: null,\n\t\t\t\t\t\t\t\t\t\t\t\tbyteSize: dataTypeMap.get(words[1]).BYTES_PER_ELEMENT\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tthrow new Error(`Unrecognized header value '${words[0]}' in ply header`);\n\t\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\t\telements,\n\t\t\t\tformat,\n\t\t\t\tcomments\n\t\t};\n};\nconst isCompressedPly = (elements)=>{\n\t\tconst chunkProperties = [\n\t\t\t\t'min_x',\n\t\t\t\t'min_y',\n\t\t\t\t'min_z',\n\t\t\t\t'max_x',\n\t\t\t\t'max_y',\n\t\t\t\t'max_z',\n\t\t\t\t'min_scale_x',\n\t\t\t\t'min_scale_y',\n\t\t\t\t'min_scale_z',\n\t\t\t\t'max_scale_x',\n\t\t\t\t'max_scale_y',\n\t\t\t\t'max_scale_z',\n\t\t\t\t'min_r',\n\t\t\t\t'min_g',\n\t\t\t\t'min_b',\n\t\t\t\t'max_r',\n\t\t\t\t'max_g',\n\t\t\t\t'max_b'\n\t\t];\n\t\tconst vertexProperties = [\n\t\t\t\t'packed_position',\n\t\t\t\t'packed_rotation',\n\t\t\t\t'packed_scale',\n\t\t\t\t'packed_color'\n\t\t];\n\t\tconst shProperties = new Array(45).fill('').map((_, i)=>`f_rest_${i}`);\n\t\tconst hasBaseElements = ()=>{\n\t\t\t\treturn elements[0].name === 'chunk' && elements[0].properties.every((p, i)=>p.name === chunkProperties[i] && p.type === 'float') && elements[1].name === 'vertex' && elements[1].properties.every((p, i)=>p.name === vertexProperties[i] && p.type === 'uint');\n\t\t};\n\t\tconst hasSHElements = ()=>{\n\t\t\t\treturn elements[2].name === 'sh' && [\n\t\t\t\t\t\t9,\n\t\t\t\t\t\t24,\n\t\t\t\t\t\t45\n\t\t\t\t].indexOf(elements[2].properties.length) !== -1 && elements[2].properties.every((p, i)=>p.name === shProperties[i] && p.type === 'uchar');\n\t\t};\n\t\treturn elements.length === 2 && hasBaseElements() || elements.length === 3 && hasBaseElements() && hasSHElements();\n};\nconst isFloatPly = (elements)=>{\n\t\treturn elements.length === 1 && elements[0].name === 'vertex' && elements[0].properties.every((p)=>p.type === 'float');\n};\nconst readCompressedPly = async (streamBuf, elements, comments)=>{\n\t\tconst result = new GSplatCompressedData();\n\t\tresult.comments = comments;\n\t\tconst numChunks = elements[0].count;\n\t\tconst numChunkProperties = elements[0].properties.length;\n\t\tconst numVertices = elements[1].count;\n\t\tconst evalStorageSize = (count)=>{\n\t\t\t\tconst width = Math.ceil(Math.sqrt(count));\n\t\t\t\tconst height = Math.ceil(count / width);\n\t\t\t\treturn width * height;\n\t\t};\n\t\tconst storageSize = evalStorageSize(numVertices);\n\t\tresult.numSplats = numVertices;\n\t\tresult.chunkData = new Float32Array(numChunks * numChunkProperties);\n\t\tresult.vertexData = new Uint32Array(storageSize * 4);\n\t\tconst read = async (buffer, length)=>{\n\t\t\t\tconst target = new Uint8Array(buffer);\n\t\t\t\tlet cursor = 0;\n\t\t\t\twhile(cursor < length){\n\t\t\t\t\t\twhile(streamBuf.remaining === 0){\n\t\t\t\t\t\t\t\tawait streamBuf.read();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst toCopy = Math.min(length - cursor, streamBuf.remaining);\n\t\t\t\t\t\tconst src = streamBuf.data;\n\t\t\t\t\t\tfor(let i = 0; i < toCopy; ++i){\n\t\t\t\t\t\t\t\ttarget[cursor++] = src[streamBuf.head++];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t};\n\t\tawait read(result.chunkData.buffer, numChunks * numChunkProperties * 4);\n\t\tawait read(result.vertexData.buffer, numVertices * 4 * 4);\n\t\tif (elements.length === 3) {\n\t\t\t\tconst texStorageSize = storageSize * 16;\n\t\t\t\tconst shData0 = new Uint8Array(texStorageSize);\n\t\t\t\tconst shData1 = new Uint8Array(texStorageSize);\n\t\t\t\tconst shData2 = new Uint8Array(texStorageSize);\n\t\t\t\tconst chunkSize = 1024;\n\t\t\t\tconst srcCoeffs = elements[2].properties.length / 3;\n\t\t\t\tconst tmpBuf = new Uint8Array(chunkSize * srcCoeffs * 3);\n\t\t\t\tfor(let i = 0; i < result.numSplats; i += chunkSize){\n\t\t\t\t\t\tconst toRead = Math.min(chunkSize, result.numSplats - i);\n\t\t\t\t\t\tawait read(tmpBuf.buffer, toRead * srcCoeffs * 3);\n\t\t\t\t\t\tfor(let j = 0; j < toRead; ++j){\n\t\t\t\t\t\t\t\tfor(let k = 0; k < 15; ++k){\n\t\t\t\t\t\t\t\t\t\tconst tidx = (i + j) * 16 + k;\n\t\t\t\t\t\t\t\t\t\tif (k < srcCoeffs) {\n\t\t\t\t\t\t\t\t\t\t\t\tshData0[tidx] = tmpBuf[(j * 3 + 0) * srcCoeffs + k];\n\t\t\t\t\t\t\t\t\t\t\t\tshData1[tidx] = tmpBuf[(j * 3 + 1) * srcCoeffs + k];\n\t\t\t\t\t\t\t\t\t\t\t\tshData2[tidx] = tmpBuf[(j * 3 + 2) * srcCoeffs + k];\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tshData0[tidx] = 127;\n\t\t\t\t\t\t\t\t\t\t\t\tshData1[tidx] = 127;\n\t\t\t\t\t\t\t\t\t\t\t\tshData2[tidx] = 127;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresult.shData0 = shData0;\n\t\t\t\tresult.shData1 = shData1;\n\t\t\t\tresult.shData2 = shData2;\n\t\t\t\tresult.shBands = ({\n\t\t\t\t\t\t3: 1,\n\t\t\t\t\t\t8: 2,\n\t\t\t\t\t\t15: 3\n\t\t\t\t})[srcCoeffs];\n\t\t} else {\n\t\t\t\tresult.shBands = 0;\n\t\t}\n\t\treturn result;\n};\nconst readFloatPly = async (streamBuf, elements, comments)=>{\n\t\tconst element = elements[0];\n\t\tconst properties = element.properties;\n\t\tconst numProperties = properties.length;\n\t\tconst storage = properties.map((p)=>p.storage);\n\t\tconst inputSize = properties.reduce((a, p)=>a + p.byteSize, 0);\n\t\tlet vertexIdx = 0;\n\t\tlet floatData;\n\t\tconst checkFloatData = ()=>{\n\t\t\t\tconst buffer = streamBuf.data.buffer;\n\t\t\t\tif (floatData?.buffer !== buffer) {\n\t\t\t\t\t\tfloatData = new Float32Array(buffer, 0, buffer.byteLength / 4);\n\t\t\t\t}\n\t\t};\n\t\tcheckFloatData();\n\t\twhile(vertexIdx < element.count){\n\t\t\t\twhile(streamBuf.remaining < inputSize){\n\t\t\t\t\t\tawait streamBuf.read();\n\t\t\t\t\t\tcheckFloatData();\n\t\t\t\t}\n\t\t\t\tconst toRead = Math.min(element.count - vertexIdx, Math.floor(streamBuf.remaining / inputSize));\n\t\t\t\tfor(let j = 0; j < numProperties; ++j){\n\t\t\t\t\t\tconst s = storage[j];\n\t\t\t\t\t\tfor(let n = 0; n < toRead; ++n){\n\t\t\t\t\t\t\t\ts[n + vertexIdx] = floatData[n * numProperties + j];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tvertexIdx += toRead;\n\t\t\t\tstreamBuf.head += toRead * inputSize;\n\t\t}\n\t\treturn new GSplatData(elements, comments);\n};\nconst readGeneralPly = async (streamBuf, elements, comments)=>{\n\t\tfor(let i = 0; i < elements.length; ++i){\n\t\t\t\tconst element = elements[i];\n\t\t\t\tconst inputSize = element.properties.reduce((a, p)=>a + p.byteSize, 0);\n\t\t\t\tconst propertyParsingFunctions = element.properties.map((p)=>{\n\t\t\t\t\t\tif (p.storage) {\n\t\t\t\t\t\t\t\tswitch(p.type){\n\t\t\t\t\t\t\t\t\t\tcase 'char':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getInt8();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcase 'uchar':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getUint8();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcase 'short':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getInt16();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcase 'ushort':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getUint16();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcase 'int':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getInt32();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcase 'uint':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getUint32();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcase 'float':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getFloat32();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tcase 'double':\n\t\t\t\t\t\t\t\t\t\t\t\treturn (streamBuf, c)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tp.storage[c] = streamBuf.getFloat64();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\tthrow new Error(`Unsupported property data type '${p.type}' in ply header`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn (streamBuf)=>{\n\t\t\t\t\t\t\t\t\t\tstreamBuf.head += p.byteSize;\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tlet c = 0;\n\t\t\t\twhile(c < element.count){\n\t\t\t\t\t\twhile(streamBuf.remaining < inputSize){\n\t\t\t\t\t\t\t\tawait streamBuf.read();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst toRead = Math.min(element.count - c, Math.floor(streamBuf.remaining / inputSize));\n\t\t\t\t\t\tfor(let n = 0; n < toRead; ++n){\n\t\t\t\t\t\t\t\tfor(let j = 0; j < element.properties.length; ++j){\n\t\t\t\t\t\t\t\t\t\tpropertyParsingFunctions[j](streamBuf, c);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tc++;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\treturn new GSplatData(elements, comments);\n};\nconst readPly = async (reader, propertyFilter = null, progressFunc = null)=>{\n\t\tconst find = (buf, search)=>{\n\t\t\t\tconst endIndex = buf.length - search.length;\n\t\t\t\tlet i, j;\n\t\t\t\tfor(i = 0; i <= endIndex; ++i){\n\t\t\t\t\t\tfor(j = 0; j < search.length; ++j){\n\t\t\t\t\t\t\t\tif (buf[i + j] !== search[j]) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (j === search.length) {\n\t\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn -1;\n\t\t};\n\t\tconst startsWith = (a, b)=>{\n\t\t\t\tif (a.length < b.length) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < b.length; ++i){\n\t\t\t\t\t\tif (a[i] !== b[i]) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t};\n\t\tconst streamBuf = new StreamBuf(reader, progressFunc);\n\t\tlet headerLength;\n\t\twhile(true){\n\t\t\t\tawait streamBuf.read();\n\t\t\t\tif (streamBuf.tail >= magicBytes.length && !startsWith(streamBuf.data, magicBytes)) {\n\t\t\t\t\t\tthrow new Error('Invalid ply header');\n\t\t\t\t}\n\t\t\t\theaderLength = find(streamBuf.data, endHeaderBytes);\n\t\t\t\tif (headerLength !== -1) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\tconst lines = new TextDecoder('ascii').decode(streamBuf.data.subarray(0, headerLength)).split('\\n');\n\t\tconst { elements, format, comments } = parseHeader(lines);\n\t\tif (format !== 'binary_little_endian') {\n\t\t\t\tthrow new Error('Unsupported ply format');\n\t\t}\n\t\tstreamBuf.head = headerLength + endHeaderBytes.length;\n\t\tstreamBuf.compact();\n\t\tconst readData = async ()=>{\n\t\t\t\tif (isCompressedPly(elements)) {\n\t\t\t\t\t\treturn await readCompressedPly(streamBuf, elements, comments);\n\t\t\t\t}\n\t\t\t\telements.forEach((e)=>{\n\t\t\t\t\t\te.properties.forEach((p)=>{\n\t\t\t\t\t\t\t\tconst storageType = dataTypeMap.get(p.type);\n\t\t\t\t\t\t\t\tif (storageType) {\n\t\t\t\t\t\t\t\t\t\tconst storage = !propertyFilter || propertyFilter(p.name) ? new storageType(e.count) : null;\n\t\t\t\t\t\t\t\t\t\tp.storage = storage;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tif (isFloatPly(elements)) {\n\t\t\t\t\t\treturn await readFloatPly(streamBuf, elements, comments);\n\t\t\t\t}\n\t\t\t\treturn await readGeneralPly(streamBuf, elements, comments);\n\t\t};\n\t\treturn await readData();\n};\nconst defaultElementFilter = (val)=>true;\nclass PlyParser {\n\t\tconstructor(app, maxRetries){\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.maxRetries = maxRetries;\n\t\t}\n\t\tasync load(url, callback, asset) {\n\t\t\t\ttry {\n\t\t\t\t\t\tconst response = await (asset.file?.contents ?? fetch(url.load));\n\t\t\t\t\t\tif (!response || !response.body) {\n\t\t\t\t\t\t\t\tcallback('Error loading resource', null);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst totalLength = parseInt(response.headers.get('content-length') ?? '0', 10);\n\t\t\t\t\t\t\t\tlet totalReceived = 0;\n\t\t\t\t\t\t\t\tconst data = await readPly(response.body.getReader(), asset.data.elementFilter ?? defaultElementFilter, (bytes)=>{\n\t\t\t\t\t\t\t\t\t\ttotalReceived += bytes;\n\t\t\t\t\t\t\t\t\t\tif (asset) {\n\t\t\t\t\t\t\t\t\t\t\t\tasset.fire('progress', totalReceived, totalLength);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tasset.fire('load:data', data);\n\t\t\t\t\t\t\t\tif (!data.isCompressed) {\n\t\t\t\t\t\t\t\t\t\tif (asset.data.reorder ?? true) {\n\t\t\t\t\t\t\t\t\t\t\t\tdata.reorderData();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst resource = data.isCompressed && !asset.data.decompress ? new GSplatCompressedResource(this.app.graphicsDevice, data) : new GSplatResource(this.app.graphicsDevice, data.isCompressed ? data.decompress() : data);\n\t\t\t\t\t\t\t\tcallback(null, resource);\n\t\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\t\tcallback(err, null);\n\t\t\t\t}\n\t\t}\n\t\topen(url, data) {\n\t\t\t\treturn data;\n\t\t}\n}\n\nconst combineProgress = (target, assets)=>{\n\t\tconst map = new Map();\n\t\tconst fire = ()=>{\n\t\t\t\tlet loaded = 0;\n\t\t\t\tlet total = 0;\n\t\t\t\tmap.forEach((value)=>{\n\t\t\t\t\t\tloaded += value.loaded;\n\t\t\t\t\t\ttotal += value.total;\n\t\t\t\t});\n\t\t\t\ttarget.fire('progress', loaded, total);\n\t\t};\n\t\tassets.forEach((asset)=>{\n\t\t\t\tconst progress = (loaded, total)=>{\n\t\t\t\t\t\tmap.set(asset, {\n\t\t\t\t\t\t\t\tloaded,\n\t\t\t\t\t\t\t\ttotal\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfire();\n\t\t\t\t};\n\t\t\t\tconst done = ()=>{\n\t\t\t\t\t\tasset.off('progress', progress);\n\t\t\t\t\t\tasset.off('load', done);\n\t\t\t\t\t\tasset.off('error', done);\n\t\t\t\t};\n\t\t\t\tasset.on('progress', progress);\n\t\t\t\tasset.on('load', done);\n\t\t\t\tasset.on('error', done);\n\t\t});\n};\nconst upgradeMeta = (meta)=>{\n\t\tconst result = {\n\t\t\t\tversion: 1,\n\t\t\t\tcount: meta.means.shape[0],\n\t\t\t\tmeans: {\n\t\t\t\t\t\tmins: meta.means.mins,\n\t\t\t\t\t\tmaxs: meta.means.maxs,\n\t\t\t\t\t\tfiles: meta.means.files\n\t\t\t\t},\n\t\t\t\tscales: {\n\t\t\t\t\t\tmins: meta.scales.mins,\n\t\t\t\t\t\tmaxs: meta.scales.maxs,\n\t\t\t\t\t\tfiles: meta.scales.files\n\t\t\t\t},\n\t\t\t\tquats: {\n\t\t\t\t\t\tfiles: meta.quats.files\n\t\t\t\t},\n\t\t\t\tsh0: {\n\t\t\t\t\t\tmins: meta.sh0.mins,\n\t\t\t\t\t\tmaxs: meta.sh0.maxs,\n\t\t\t\t\t\tfiles: meta.sh0.files\n\t\t\t\t}\n\t\t};\n\t\tif (meta.shN) {\n\t\t\t\tresult.shN = {\n\t\t\t\t\t\tmins: meta.shN.mins,\n\t\t\t\t\t\tmaxs: meta.shN.maxs,\n\t\t\t\t\t\tfiles: meta.shN.files\n\t\t\t\t};\n\t\t}\n\t\treturn result;\n};\nclass SogsParser {\n\t\tconstructor(app, maxRetries){\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.maxRetries = maxRetries;\n\t\t}\n\t\tasync loadTextures(url, callback, asset, meta) {\n\t\t\t\tif (meta.version !== 2) {\n\t\t\t\t\t\tmeta = upgradeMeta(meta);\n\t\t\t\t}\n\t\t\t\tconst { assets } = this.app;\n\t\t\t\tconst subs = [\n\t\t\t\t\t\t'means',\n\t\t\t\t\t\t'quats',\n\t\t\t\t\t\t'scales',\n\t\t\t\t\t\t'sh0',\n\t\t\t\t\t\t'shN'\n\t\t\t\t];\n\t\t\t\tconst textures = {};\n\t\t\t\tconst promises = [];\n\t\t\t\tsubs.forEach((sub)=>{\n\t\t\t\t\t\tconst files = meta[sub]?.files ?? [];\n\t\t\t\t\t\ttextures[sub] = files.map((filename)=>{\n\t\t\t\t\t\t\t\tconst texture = new Asset(filename, 'texture', {\n\t\t\t\t\t\t\t\t\t\turl: asset.options?.mapUrl?.(filename) ?? new URL(filename, new URL(url.load, window.location.href).toString()).toString(),\n\t\t\t\t\t\t\t\t\t\tfilename\n\t\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\t\t\tmipmaps: false\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tconst promise = new Promise((resolve, reject)=>{\n\t\t\t\t\t\t\t\t\t\ttexture.on('load', ()=>resolve(null));\n\t\t\t\t\t\t\t\t\t\ttexture.on('error', (err)=>reject(err));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tassets.add(texture);\n\t\t\t\t\t\t\t\tpromises.push(promise);\n\t\t\t\t\t\t\t\treturn texture;\n\t\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tconst textureAssets = subs.map((sub)=>textures[sub]).flat();\n\t\t\t\tcombineProgress(asset, textureAssets);\n\t\t\t\ttextureAssets.forEach((t)=>assets.load(t));\n\t\t\t\tawait Promise.allSettled(promises);\n\t\t\t\tconst data = new GSplatSogsData();\n\t\t\t\tdata.meta = meta;\n\t\t\t\tdata.numSplats = meta.count;\n\t\t\t\tdata.means_l = textures.means[0].resource;\n\t\t\t\tdata.means_u = textures.means[1].resource;\n\t\t\t\tdata.quats = textures.quats[0].resource;\n\t\t\t\tdata.scales = textures.scales[0].resource;\n\t\t\t\tdata.sh0 = textures.sh0[0].resource;\n\t\t\t\tdata.sh_centroids = textures.shN?.[0]?.resource;\n\t\t\t\tdata.sh_labels = textures.shN?.[1]?.resource;\n\t\t\t\tconst decompress = asset.data?.decompress;\n\t\t\t\tif (!decompress) {\n\t\t\t\t\t\tawait data.prepareGpuData();\n\t\t\t\t}\n\t\t\t\tconst resource = decompress ? new GSplatResource(this.app.graphicsDevice, await data.decompress()) : new GSplatSogsResource(this.app.graphicsDevice, data);\n\t\t\t\tcallback(null, resource);\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (asset.data?.means) {\n\t\t\t\t\t\tthis.loadTextures(url, callback, asset, asset.data);\n\t\t\t\t} else {\n\t\t\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst options = {\n\t\t\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\t\t\tmaxRetries: this.maxRetries,\n\t\t\t\t\t\t\t\tresponseType: Http.ResponseType.JSON\n\t\t\t\t\t\t};\n\t\t\t\t\t\thttp.get(url.load, options, (err, meta)=>{\n\t\t\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\t\t\tthis.loadTextures(url, callback, asset, meta);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcallback(`Error loading gsplat meta: ${url.original} [${err}]`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n}\n\nconst parseZipArchive = (data)=>{\n\t\tconst dataView = new DataView(data);\n\t\tconst u16 = (offset)=>dataView.getUint16(offset, true);\n\t\tconst u32 = (offset)=>dataView.getUint32(offset, true);\n\t\tconst extractEocd = (offset)=>{\n\t\t\t\treturn {\n\t\t\t\t\t\tmagic: u32(offset),\n\t\t\t\t\t\tnumFiles: u16(offset + 8),\n\t\t\t\t\t\tcdSizeBytes: u32(offset + 12),\n\t\t\t\t\t\tcdOffsetBytes: u32(offset + 16)\n\t\t\t\t};\n\t\t};\n\t\tconst extractCdr = (offset)=>{\n\t\t\t\tconst filenameLength = u16(offset + 28);\n\t\t\t\tconst extraFieldLength = u16(offset + 30);\n\t\t\t\tconst fileCommentLength = u16(offset + 32);\n\t\t\t\treturn {\n\t\t\t\t\t\tmagic: u32(offset),\n\t\t\t\t\t\tcompressionMethod: u16(offset + 10),\n\t\t\t\t\t\tcompressedSizeBytes: u32(offset + 20),\n\t\t\t\t\t\tuncompressedSizeBytes: u32(offset + 24),\n\t\t\t\t\t\tlfhOffsetBytes: u32(offset + 42),\n\t\t\t\t\t\tfilename: new TextDecoder().decode(new Uint8Array(data, offset + 46, filenameLength)),\n\t\t\t\t\t\trecordSizeBytes: 46 + filenameLength + extraFieldLength + fileCommentLength\n\t\t\t\t};\n\t\t};\n\t\tconst extractLfh = (offset)=>{\n\t\t\t\tconst filenameLength = u16(offset + 26);\n\t\t\t\tconst extraLength = u16(offset + 28);\n\t\t\t\treturn {\n\t\t\t\t\t\tmagic: u32(offset),\n\t\t\t\t\t\toffsetBytes: offset + 30 + filenameLength + extraLength\n\t\t\t\t};\n\t\t};\n\t\tconst eocd = extractEocd(dataView.byteLength - 22);\n\t\tif (eocd.magic !== 0x06054b50) {\n\t\t\t\tthrow new Error('Invalid zip file: EOCDR not found');\n\t\t}\n\t\tif (eocd.cdOffsetBytes === 0xffffffff || eocd.cdSizeBytes === 0xffffffff) {\n\t\t\t\tthrow new Error('Invalid zip file: Zip64 not supported');\n\t\t}\n\t\tconst result = [];\n\t\tlet offset = eocd.cdOffsetBytes;\n\t\tfor(let i = 0; i < eocd.numFiles; i++){\n\t\t\t\tconst cdr = extractCdr(offset);\n\t\t\t\tif (cdr.magic !== 0x02014b50) {\n\t\t\t\t\t\tthrow new Error('Invalid zip file: CDR not found');\n\t\t\t\t}\n\t\t\t\tconst lfh = extractLfh(cdr.lfhOffsetBytes);\n\t\t\t\tif (lfh.magic !== 0x04034b50) {\n\t\t\t\t\t\tthrow new Error('Invalid zip file: LFH not found');\n\t\t\t\t}\n\t\t\t\tresult.push({\n\t\t\t\t\t\tfilename: cdr.filename,\n\t\t\t\t\t\tcompression: {\n\t\t\t\t\t\t\t\t0: 'none',\n\t\t\t\t\t\t\t\t8: 'deflate'\n\t\t\t\t\t\t}[cdr.compressionMethod] ?? 'unknown',\n\t\t\t\t\t\tdata: new Uint8Array(data, lfh.offsetBytes, cdr.compressedSizeBytes)\n\t\t\t\t});\n\t\t\t\toffset += cdr.recordSizeBytes;\n\t\t}\n\t\treturn result;\n};\nconst inflate = async (compressed)=>{\n\t\tconst ds = new DecompressionStream('deflate-raw');\n\t\tconst out = new Blob([\n\t\t\t\tcompressed\n\t\t]).stream().pipeThrough(ds);\n\t\tconst ab = await new Response(out).arrayBuffer();\n\t\treturn new Uint8Array(ab);\n};\nclass SogBundleParser {\n\t\tconstructor(app, maxRetries = 3){\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.maxRetries = maxRetries;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tconst handleArrayBuffer = async (arrayBuffer)=>{\n\t\t\t\t\t\tconst files = parseZipArchive(arrayBuffer);\n\t\t\t\t\t\tfor (const file of files){\n\t\t\t\t\t\t\t\tif (file.compression === 'deflate') {\n\t\t\t\t\t\t\t\t\t\tfile.data = await inflate(file.data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst metaFile = files.find((f)=>f.filename === 'meta.json');\n\t\t\t\t\t\tif (!metaFile) {\n\t\t\t\t\t\t\t\tcallback('Error: meta.json not found');\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet meta;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tmeta = JSON.parse(new TextDecoder().decode(metaFile.data));\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\tcallback(`Error parsing meta.json: ${err}`);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst filenames = [\n\t\t\t\t\t\t\t\t'means',\n\t\t\t\t\t\t\t\t'scales',\n\t\t\t\t\t\t\t\t'quats',\n\t\t\t\t\t\t\t\t'sh0',\n\t\t\t\t\t\t\t\t'shN'\n\t\t\t\t\t\t].map((key)=>meta[key]?.files ?? []).flat();\n\t\t\t\t\t\tconst textures = {};\n\t\t\t\t\t\tconst promises = [];\n\t\t\t\t\t\tfor (const filename of filenames){\n\t\t\t\t\t\t\t\tconst file = files.find((f)=>f.filename === filename);\n\t\t\t\t\t\t\t\tlet texture;\n\t\t\t\t\t\t\t\tif (file) {\n\t\t\t\t\t\t\t\t\t\ttexture = new Asset(filename, 'texture', {\n\t\t\t\t\t\t\t\t\t\t\t\turl: `${url.load}/${filename}`,\n\t\t\t\t\t\t\t\t\t\t\t\tfilename,\n\t\t\t\t\t\t\t\t\t\t\t\tcontents: file.data\n\t\t\t\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\t\t\t\t\tmipmaps: false\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst url = new URL(filename, new URL(filename, window.location.href).toString()).toString();\n\t\t\t\t\t\t\t\t\t\ttexture = new Asset(filename, 'texture', {\n\t\t\t\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\t\t\t\tfilename\n\t\t\t\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\t\t\t\t\tmipmaps: false\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst promise = new Promise((resolve, reject)=>{\n\t\t\t\t\t\t\t\t\t\ttexture.on('load', ()=>resolve(null));\n\t\t\t\t\t\t\t\t\t\ttexture.on('error', (err)=>reject(err));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tthis.app.assets.add(texture);\n\t\t\t\t\t\t\t\ttextures[filename] = texture;\n\t\t\t\t\t\t\t\tpromises.push(promise);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tObject.values(textures).forEach((t)=>this.app.assets.load(t));\n\t\t\t\t\t\tawait Promise.allSettled(promises);\n\t\t\t\t\t\tconst data = new GSplatSogsData();\n\t\t\t\t\t\tdata.meta = meta;\n\t\t\t\t\t\tdata.numSplats = meta.count;\n\t\t\t\t\t\tdata.means_l = textures[meta.means.files[0]].resource;\n\t\t\t\t\t\tdata.means_u = textures[meta.means.files[1]].resource;\n\t\t\t\t\t\tdata.quats = textures[meta.quats.files[0]].resource;\n\t\t\t\t\t\tdata.scales = textures[meta.scales.files[0]].resource;\n\t\t\t\t\t\tdata.sh0 = textures[meta.sh0.files[0]].resource;\n\t\t\t\t\t\tdata.sh_centroids = textures[meta.shN?.files[0]]?.resource;\n\t\t\t\t\t\tdata.sh_labels = textures[meta.shN?.files[1]]?.resource;\n\t\t\t\t\t\tconst decompress = asset.data?.decompress;\n\t\t\t\t\t\tif (!decompress) {\n\t\t\t\t\t\t\t\tawait data.prepareGpuData();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst resource = decompress ? new GSplatResource(this.app.graphicsDevice, await data.decompress()) : new GSplatSogsResource(this.app.graphicsDevice, data);\n\t\t\t\t\t\tcallback(null, resource);\n\t\t\t\t};\n\t\t\t\tAsset.fetchArrayBuffer(url.load, (error, arrayBuffer)=>{\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\t\tcallback(error);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\thandleArrayBuffer(arrayBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t}, asset, this.maxRetries);\n\t\t}\n}\n\nclass GSplatOctreeParser {\n\t\tconstructor(app, maxRetries){\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.maxRetries = maxRetries;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst options = {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries,\n\t\t\t\t\t\tresponseType: Http.ResponseType.JSON\n\t\t\t\t};\n\t\t\t\thttp.get(url.load, options, (err, data)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tconst resource = new GSplatOctreeResource(asset.file.url, data);\n\t\t\t\t\t\t\t\tcallback(null, resource);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading gsplat octree: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n}\n\nclass GSplatHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'gsplat');\n\t\t\t\tthis.parsers = {\n\t\t\t\t\t\tply: new PlyParser(app, 3),\n\t\t\t\t\t\tsog: new SogBundleParser(app),\n\t\t\t\t\t\tjson: new SogsParser(app, 3),\n\t\t\t\t\t\toctree: new GSplatOctreeParser(app, 3)\n\t\t\t\t};\n\t\t}\n\t\t_getUrlWithoutParams(url) {\n\t\t\t\treturn url.indexOf('?') >= 0 ? url.split('?')[0] : url;\n\t\t}\n\t\t_getParser(url) {\n\t\t\t\tconst basename = path.getBasename(this._getUrlWithoutParams(url)).toLowerCase();\n\t\t\t\tif (basename === 'lod-meta.json') {\n\t\t\t\t\t\treturn this.parsers.octree;\n\t\t\t\t}\n\t\t\t\tconst ext = path.getExtension(basename).replace('.', '');\n\t\t\t\treturn this.parsers[ext] || this.parsers.ply;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tthis._getParser(url.original).load(url, callback, asset);\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\treturn data;\n\t\t}\n}\n\nclass CompressUtils {\n\t\tstatic setCompressedPRS(entity, data, compressed) {\n\t\t\t\tconst a = compressed.singleVecs;\n\t\t\t\tlet b, i;\n\t\t\t\tconst v = data.___1;\n\t\t\t\tif (!v) {\n\t\t\t\t\t\tb = compressed.tripleVecs;\n\t\t\t\t\t\ti = data.___2;\n\t\t\t\t}\n\t\t\t\tlet n = v ? v[0] : b[i];\n\t\t\t\tentity.setLocalPosition(a[n], a[n + 1], a[n + 2]);\n\t\t\t\tn = v ? v[1] : b[i + 1];\n\t\t\t\tentity.setLocalEulerAngles(a[n], a[n + 1], a[n + 2]);\n\t\t\t\tn = v ? v[2] : b[i + 2];\n\t\t\t\tentity.setLocalScale(a[n], a[n + 1], a[n + 2]);\n\t\t}\n\t\tstatic oneCharToKey(s, data) {\n\t\t\t\tconst i = s.charCodeAt(0) - data.fieldFirstCode;\n\t\t\t\treturn data.fieldArray[i];\n\t\t}\n\t\tstatic multCharToKey(s, data) {\n\t\t\t\tlet ind = 0;\n\t\t\t\tfor(let i = 0; i < s.length; i++){\n\t\t\t\t\t\tind = ind * data.fieldCodeBase + s.charCodeAt(i) - data.fieldFirstCode;\n\t\t\t\t}\n\t\t\t\treturn data.fieldArray[ind];\n\t\t}\n}\n\nclass Decompress {\n\t\tconstructor(node, data){\n\t\t\t\tthis._node = node;\n\t\t\t\tthis._data = data;\n\t\t}\n\t\trun() {\n\t\t\t\tconst type = Object.prototype.toString.call(this._node);\n\t\t\t\tif (type === '[object Object]') {\n\t\t\t\t\t\tthis._handleMap();\n\t\t\t\t} else if (type === '[object Array]') {\n\t\t\t\t\t\tthis._handleArray();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._result = this._node;\n\t\t\t\t}\n\t\t\t\treturn this._result;\n\t\t}\n\t\t_handleMap() {\n\t\t\t\tthis._result = {};\n\t\t\t\tconst a = Object.keys(this._node);\n\t\t\t\ta.forEach(this._handleKey, this);\n\t\t}\n\t\t_handleKey(origKey) {\n\t\t\t\tlet newKey = origKey;\n\t\t\t\tconst len = origKey.length;\n\t\t\t\tif (len === 1) {\n\t\t\t\t\t\tnewKey = CompressUtils.oneCharToKey(origKey, this._data);\n\t\t\t\t} else if (len === 2) {\n\t\t\t\t\t\tnewKey = CompressUtils.multCharToKey(origKey, this._data);\n\t\t\t\t}\n\t\t\t\tthis._result[newKey] = new Decompress(this._node[origKey], this._data).run();\n\t\t}\n\t\t_handleArray() {\n\t\t\t\tthis._result = [];\n\t\t\t\tthis._node.forEach(this._handleArElt, this);\n\t\t}\n\t\t_handleArElt(elt) {\n\t\t\t\tconst v = new Decompress(elt, this._data).run();\n\t\t\t\tthis._result.push(v);\n\t\t}\n}\n\nclass SceneParser {\n\t\tconstructor(app, isTemplate){\n\t\t\t\tthis._app = app;\n\t\t\t\tthis._isTemplate = isTemplate;\n\t\t}\n\t\tparse(data) {\n\t\t\t\tconst entities = {};\n\t\t\t\tlet parent = null;\n\t\t\t\tconst compressed = data.compressedFormat;\n\t\t\t\tif (compressed && !data.entDecompressed) {\n\t\t\t\t\t\tdata.entDecompressed = true;\n\t\t\t\t\t\tdata.entities = new Decompress(data.entities, compressed).run();\n\t\t\t\t}\n\t\t\t\tfor(const id in data.entities){\n\t\t\t\t\t\tconst curData = data.entities[id];\n\t\t\t\t\t\tconst curEnt = this._createEntity(curData, compressed);\n\t\t\t\t\t\tentities[id] = curEnt;\n\t\t\t\t\t\tif (curData.parent === null) {\n\t\t\t\t\t\t\t\tparent = curEnt;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(const id in data.entities){\n\t\t\t\t\t\tconst curEnt = entities[id];\n\t\t\t\t\t\tconst children = data.entities[id].children;\n\t\t\t\t\t\tconst len = children.length;\n\t\t\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\t\t\tconst childEnt = entities[children[i]];\n\t\t\t\t\t\t\t\tif (childEnt) {\n\t\t\t\t\t\t\t\t\t\tcurEnt.addChild(childEnt);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._openComponentData(parent, data.entities);\n\t\t\t\treturn parent;\n\t\t}\n\t\t_createEntity(data, compressed) {\n\t\t\t\tconst entity = new Entity(data.name, this._app);\n\t\t\t\tentity.setGuid(data.resource_id);\n\t\t\t\tthis._setPosRotScale(entity, data, compressed);\n\t\t\t\tentity._enabled = data.enabled ?? true;\n\t\t\t\tif (this._isTemplate) {\n\t\t\t\t\t\tentity._template = true;\n\t\t\t\t} else {\n\t\t\t\t\t\tentity._enabledInHierarchy = entity._enabled;\n\t\t\t\t}\n\t\t\t\tentity.template = data.template;\n\t\t\t\tif (data.tags) {\n\t\t\t\t\t\tfor(let i = 0; i < data.tags.length; i++){\n\t\t\t\t\t\t\t\tentity.tags.add(data.tags[i]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn entity;\n\t\t}\n\t\t_setPosRotScale(entity, data, compressed) {\n\t\t\t\tif (compressed) {\n\t\t\t\t\t\tCompressUtils.setCompressedPRS(entity, data, compressed);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst p = data.position;\n\t\t\t\t\t\tconst r = data.rotation;\n\t\t\t\t\t\tconst s = data.scale;\n\t\t\t\t\t\tentity.setLocalPosition(p[0], p[1], p[2]);\n\t\t\t\t\t\tentity.setLocalEulerAngles(r[0], r[1], r[2]);\n\t\t\t\t\t\tentity.setLocalScale(s[0], s[1], s[2]);\n\t\t\t\t}\n\t\t}\n\t\t_openComponentData(entity, entities) {\n\t\t\t\tconst systemsList = this._app.systems.list;\n\t\t\t\tlet len = systemsList.length;\n\t\t\t\tconst entityData = entities[entity.getGuid()];\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tconst system = systemsList[i];\n\t\t\t\t\t\tconst componentData = entityData.components[system.id];\n\t\t\t\t\t\tif (componentData) {\n\t\t\t\t\t\t\t\tsystem.addComponent(entity, componentData);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlen = entityData.children.length;\n\t\t\t\tconst children = entity._children;\n\t\t\t\tfor(let i = 0; i < len; i++){\n\t\t\t\t\t\tif (children[i]) {\n\t\t\t\t\t\t\t\tchildren[i] = this._openComponentData(children[i], entities);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn entity;\n\t\t}\n}\n\nclass SceneUtils {\n\t\tstatic load(url, maxRetries, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\tretry: maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: maxRetries\n\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tcallback(err, response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlet errMsg = `Error while loading scene JSON ${url.original}`;\n\t\t\t\t\t\t\t\tif (err.message) {\n\t\t\t\t\t\t\t\t\t\terrMsg += `: ${err.message}`;\n\t\t\t\t\t\t\t\t\t\tif (err.stack) {\n\t\t\t\t\t\t\t\t\t\t\t\terrMsg += `\\n${err.stack}`;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\terrMsg += `: ${err}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcallback(errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n}\n\nclass HierarchyHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'hierarchy');\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tSceneUtils.load(url, this.maxRetries, callback);\n\t\t}\n\t\topen(url, data) {\n\t\t\t\tthis._app.systems.script.preloading = true;\n\t\t\t\tconst parser = new SceneParser(this._app, false);\n\t\t\t\tconst parent = parser.parse(data);\n\t\t\t\tthis._app.systems.script.preloading = false;\n\t\t\t\treturn parent;\n\t\t}\n}\n\nclass HtmlHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'html'), this.decoder = null;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading html resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topenBinary(data) {\n\t\t\t\tthis.decoder ??= new TextDecoder('utf-8');\n\t\t\t\treturn this.decoder.decode(data);\n\t\t}\n}\n\nclass JsonHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'json'), this.decoder = null;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst options = {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t};\n\t\t\t\tif (url.load.startsWith('blob:')) {\n\t\t\t\t\t\toptions.responseType = Http.ResponseType.JSON;\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, options, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading JSON resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topenBinary(data) {\n\t\t\t\tthis.decoder ??= new TextDecoder('utf-8');\n\t\t\t\treturn JSON.parse(this.decoder.decode(data));\n\t\t}\n}\n\nclass StandardMaterialValidator {\n\t\tconstructor(){\n\t\t\t\tthis.removeInvalid = true;\n\t\t\t\tthis.valid = true;\n\t\t\t\tthis.enumValidators = {\n\t\t\t\t\t\toccludeSpecular: this._createEnumValidator([\n\t\t\t\t\t\t\t\tSPECOCC_NONE,\n\t\t\t\t\t\t\t\tSPECOCC_AO,\n\t\t\t\t\t\t\t\tSPECOCC_GLOSSDEPENDENT\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tcull: this._createEnumValidator([\n\t\t\t\t\t\t\t\tCULLFACE_NONE,\n\t\t\t\t\t\t\t\tCULLFACE_BACK,\n\t\t\t\t\t\t\t\tCULLFACE_FRONT,\n\t\t\t\t\t\t\t\tCULLFACE_FRONTANDBACK\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tblendType: this._createEnumValidator([\n\t\t\t\t\t\t\t\tBLEND_SUBTRACTIVE,\n\t\t\t\t\t\t\t\tBLEND_ADDITIVE,\n\t\t\t\t\t\t\t\tBLEND_NORMAL,\n\t\t\t\t\t\t\t\tBLEND_NONE,\n\t\t\t\t\t\t\t\tBLEND_PREMULTIPLIED,\n\t\t\t\t\t\t\t\tBLEND_MULTIPLICATIVE,\n\t\t\t\t\t\t\t\tBLEND_ADDITIVEALPHA,\n\t\t\t\t\t\t\t\tBLEND_MULTIPLICATIVE2X,\n\t\t\t\t\t\t\t\tBLEND_SCREEN,\n\t\t\t\t\t\t\t\tBLEND_MIN,\n\t\t\t\t\t\t\t\tBLEND_MAX\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tdepthFunc: this._createEnumValidator([\n\t\t\t\t\t\t\t\tFUNC_NEVER,\n\t\t\t\t\t\t\t\tFUNC_LESS,\n\t\t\t\t\t\t\t\tFUNC_EQUAL,\n\t\t\t\t\t\t\t\tFUNC_LESSEQUAL,\n\t\t\t\t\t\t\t\tFUNC_GREATER,\n\t\t\t\t\t\t\t\tFUNC_NOTEQUAL,\n\t\t\t\t\t\t\t\tFUNC_GREATEREQUAL,\n\t\t\t\t\t\t\t\tFUNC_ALWAYS\n\t\t\t\t\t\t])\n\t\t\t\t};\n\t\t}\n\t\tsetInvalid(key, data) {\n\t\t\t\tthis.valid = false;\n\t\t\t\tif (this.removeInvalid) {\n\t\t\t\t\t\tdelete data[key];\n\t\t\t\t}\n\t\t}\n\t\tvalidate(data) {\n\t\t\t\tconst TYPES = standardMaterialParameterTypes;\n\t\t\t\tconst REMOVED = standardMaterialRemovedParameters;\n\t\t\t\tconst pathMapping = data.mappingFormat === 'path';\n\t\t\t\tfor(const key in data){\n\t\t\t\t\t\tconst type = TYPES[key];\n\t\t\t\t\t\tif (!type) {\n\t\t\t\t\t\t\t\tif (REMOVED[key]) {\n\t\t\t\t\t\t\t\t\t\tdelete data[key];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis.valid = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (type.startsWith('enum')) {\n\t\t\t\t\t\t\t\tconst enumType = type.split(':')[1];\n\t\t\t\t\t\t\t\tif (this.enumValidators[enumType]) {\n\t\t\t\t\t\t\t\t\t\tif (!this.enumValidators[enumType](data[key])) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'number') {\n\t\t\t\t\t\t\t\tif (typeof data[key] !== 'number') {\n\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'boolean') {\n\t\t\t\t\t\t\t\tif (typeof data[key] !== 'boolean') {\n\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'string') {\n\t\t\t\t\t\t\t\tif (typeof data[key] !== 'string') {\n\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'vec2') {\n\t\t\t\t\t\t\t\tif (!(data[key] instanceof Array && data[key].length === 2)) {\n\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'rgb') {\n\t\t\t\t\t\t\t\tif (!(data[key] instanceof Array && data[key].length === 3)) {\n\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'texture') {\n\t\t\t\t\t\t\t\tif (!pathMapping) {\n\t\t\t\t\t\t\t\t\t\tif (!(typeof data[key] === 'number' || data[key] === null)) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!(data[key] instanceof Texture)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!(typeof data[key] === 'string' || data[key] === null)) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!(data[key] instanceof Texture)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'boundingbox') {\n\t\t\t\t\t\t\t\tif (!(data[key].center && data[key].center instanceof Array && data[key].center.length === 3)) {\n\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (!(data[key].halfExtents && data[key].halfExtents instanceof Array && data[key].halfExtents.length === 3)) {\n\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'cubemap') {\n\t\t\t\t\t\t\t\tif (!(typeof data[key] === 'number' || data[key] === null || data[key] === undefined)) {\n\t\t\t\t\t\t\t\t\t\tif (!(data[key] instanceof Texture && data[key].cubemap)) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.setInvalid(key, data);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'chunks') {\n\t\t\t\t\t\t\t\tconst chunkNames = Object.keys(data[key]);\n\t\t\t\t\t\t\t\tfor(let i = 0; i < chunkNames.length; i++){\n\t\t\t\t\t\t\t\t\t\tif (typeof data[key][chunkNames[i]] !== 'string') {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.setInvalid(chunkNames[i], data[key]);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.error(`Unknown material type: ${type}`);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdata.validated = true;\n\t\t\t\treturn this.valid;\n\t\t}\n\t\t_createEnumValidator(values) {\n\t\t\t\treturn function(value) {\n\t\t\t\t\t\treturn values.indexOf(value) >= 0;\n\t\t\t\t};\n\t\t}\n}\n\nclass JsonStandardMaterialParser {\n\t\tconstructor(){\n\t\t\t\tthis._validator = null;\n\t\t}\n\t\tparse(input) {\n\t\t\t\tconst migrated = this.migrate(input);\n\t\t\t\tconst validated = this._validate(migrated);\n\t\t\t\tconst material = new StandardMaterial();\n\t\t\t\tthis.initialize(material, validated);\n\t\t\t\treturn material;\n\t\t}\n\t\tinitialize(material, data) {\n\t\t\t\tif (!data.validated) {\n\t\t\t\t\t\tdata = this._validate(data);\n\t\t\t\t}\n\t\t\t\tif (data.chunks) {\n\t\t\t\t\t\tif (data.chunks && Object.keys(data.chunks).length > 0) {\n\t\t\t\t\t\t\t\tconst dstMap = material.shaderChunks.glsl;\n\t\t\t\t\t\t\t\tObject.entries(data.chunks).forEach(([key, value])=>dstMap.set(key, value));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor(const key in data){\n\t\t\t\t\t\tconst type = standardMaterialParameterTypes[key];\n\t\t\t\t\t\tconst value = data[key];\n\t\t\t\t\t\tif (type === 'vec2') {\n\t\t\t\t\t\t\t\tmaterial[key] = new Vec2(value[0], value[1]);\n\t\t\t\t\t\t} else if (type === 'rgb') {\n\t\t\t\t\t\t\t\tmaterial[key] = new Color(value[0], value[1], value[2]);\n\t\t\t\t\t\t} else if (type === 'texture') {\n\t\t\t\t\t\t\t\tif (value instanceof Texture) {\n\t\t\t\t\t\t\t\t\t\tmaterial[key] = value;\n\t\t\t\t\t\t\t\t} else if (!(material[key] instanceof Texture && typeof value === 'number' && value > 0)) {\n\t\t\t\t\t\t\t\t\t\tmaterial[key] = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'cubemap') {\n\t\t\t\t\t\t\t\tif (value instanceof Texture) {\n\t\t\t\t\t\t\t\t\t\tmaterial[key] = value;\n\t\t\t\t\t\t\t\t} else if (!(material[key] instanceof Texture && typeof value === 'number' && value > 0)) {\n\t\t\t\t\t\t\t\t\t\tmaterial[key] = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (key === 'cubeMap' && !value) {\n\t\t\t\t\t\t\t\t\t\tmaterial.prefilteredCubemaps = null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'boundingbox') {\n\t\t\t\t\t\t\t\tconst center = new Vec3(value.center[0], value.center[1], value.center[2]);\n\t\t\t\t\t\t\t\tconst halfExtents = new Vec3(value.halfExtents[0], value.halfExtents[1], value.halfExtents[2]);\n\t\t\t\t\t\t\t\tmaterial[key] = new BoundingBox(center, halfExtents);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmaterial[key] = data[key];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmaterial.update();\n\t\t}\n\t\tmigrate(data) {\n\t\t\t\tif (data.shader) delete data.shader;\n\t\t\t\tif (data.mapping_format) {\n\t\t\t\t\t\tdata.mappingFormat = data.mapping_format;\n\t\t\t\t\t\tdelete data.mapping_format;\n\t\t\t\t}\n\t\t\t\tlet i;\n\t\t\t\tconst RENAMED_PROPERTIES = [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'bumpMapFactor',\n\t\t\t\t\t\t\t\t'bumpiness'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'aoUvSet',\n\t\t\t\t\t\t\t\t'aoMapUv'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'aoMapVertexColor',\n\t\t\t\t\t\t\t\t'aoVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'diffuseMapVertexColor',\n\t\t\t\t\t\t\t\t'diffuseVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'emissiveMapVertexColor',\n\t\t\t\t\t\t\t\t'emissiveVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'specularMapVertexColor',\n\t\t\t\t\t\t\t\t'specularVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'metalnessMapVertexColor',\n\t\t\t\t\t\t\t\t'metalnessVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'opacityMapVertexColor',\n\t\t\t\t\t\t\t\t'opacityVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'glossMapVertexColor',\n\t\t\t\t\t\t\t\t'glossVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'lightMapVertexColor',\n\t\t\t\t\t\t\t\t'lightVertexColor'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'specularMapTint',\n\t\t\t\t\t\t\t\t'specularTint'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'metalnessMapTint',\n\t\t\t\t\t\t\t\t'metalnessTint'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'clearCoatGlossiness',\n\t\t\t\t\t\t\t\t'clearCoatGloss'\n\t\t\t\t\t\t]\n\t\t\t\t];\n\t\t\t\tfor(i = 0; i < RENAMED_PROPERTIES.length; i++){\n\t\t\t\t\t\tconst _old = RENAMED_PROPERTIES[i][0];\n\t\t\t\t\t\tconst _new = RENAMED_PROPERTIES[i][1];\n\t\t\t\t\t\tif (data[_old] !== undefined) {\n\t\t\t\t\t\t\t\tif (data[_new] === undefined) {\n\t\t\t\t\t\t\t\t\t\tdata[_new] = data[_old];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdelete data[_old];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst DEPRECATED_PROPERTIES = [\n\t\t\t\t\t\t'fresnelFactor',\n\t\t\t\t\t\t'shadowSampleType'\n\t\t\t\t];\n\t\t\t\tfor(i = 0; i < DEPRECATED_PROPERTIES.length; i++){\n\t\t\t\t\t\tconst name = DEPRECATED_PROPERTIES[i];\n\t\t\t\t\t\tif (data.hasOwnProperty(name)) {\n\t\t\t\t\t\t\t\tdelete data[name];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn data;\n\t\t}\n\t\t_validate(data) {\n\t\t\t\tif (!data.validated) {\n\t\t\t\t\t\tif (!this._validator) {\n\t\t\t\t\t\t\t\tthis._validator = new StandardMaterialValidator();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._validator.validate(data);\n\t\t\t\t}\n\t\t\t\treturn data;\n\t\t}\n}\n\nconst PLACEHOLDER_MAP = {\n\t\taoMap: 'white',\n\t\taoDetailMap: 'white',\n\t\tdiffuseMap: 'gray',\n\t\tdiffuseDetailMap: 'gray',\n\t\tspecularMap: 'gray',\n\t\tspecularityFactorMap: 'white',\n\t\tmetalnessMap: 'black',\n\t\tglossMap: 'gray',\n\t\tsheenMap: 'black',\n\t\tsheenGlossMap: 'gray',\n\t\tclearCoatMap: 'black',\n\t\tclearCoatGlossMap: 'gray',\n\t\tclearCoatNormalMap: 'normal',\n\t\trefractionMap: 'white',\n\t\temissiveMap: 'gray',\n\t\tnormalMap: 'normal',\n\t\tnormalDetailMap: 'normal',\n\t\theightMap: 'gray',\n\t\topacityMap: 'gray',\n\t\tsphereMap: 'gray',\n\t\tlightMap: 'white',\n\t\tthicknessMap: 'black',\n\t\tiridescenceMap: 'black',\n\t\tiridescenceThicknessMap: 'black',\n\t\tenvAtlas: 'black',\n\t\tanisotropyMap: 'black'\n};\nclass MaterialHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'material');\n\t\t\t\tthis._assets = app.assets;\n\t\t\t\tthis._device = app.graphicsDevice;\n\t\t\t\tthis._parser = new JsonStandardMaterialParser();\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\tresponse._engine = true;\n\t\t\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\t\t\tcallback(`Error loading material: ${url.original} [${err}]`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topen(url, data) {\n\t\t\t\tconst material = this._parser.parse(data);\n\t\t\t\tif (data._engine) {\n\t\t\t\t\t\tmaterial._data = data;\n\t\t\t\t\t\tdelete data._engine;\n\t\t\t\t}\n\t\t\t\treturn material;\n\t\t}\n\t\tpatch(asset, assets) {\n\t\t\t\tif (asset.resource._data) {\n\t\t\t\t\t\tasset._data = asset.resource._data;\n\t\t\t\t\t\tdelete asset.resource._data;\n\t\t\t\t}\n\t\t\t\tasset.data.name = asset.name;\n\t\t\t\tasset.resource.name = asset.name;\n\t\t\t\tthis._bindAndAssignAssets(asset, assets);\n\t\t\t\tasset.off('unload', this._onAssetUnload, this);\n\t\t\t\tasset.on('unload', this._onAssetUnload, this);\n\t\t}\n\t\t_onAssetUnload(asset) {\n\t\t\t\tdelete asset.data.parameters;\n\t\t\t\tdelete asset.data.chunks;\n\t\t\t\tdelete asset.data.name;\n\t\t}\n\t\t_assignTexture(parameterName, materialAsset, texture) {\n\t\t\t\tmaterialAsset.resource[parameterName] = texture;\n\t\t}\n\t\t_getPlaceholderTexture(parameterName) {\n\t\t\t\tconst placeholder = PLACEHOLDER_MAP[parameterName];\n\t\t\t\treturn getBuiltInTexture(this._device, placeholder);\n\t\t}\n\t\t_assignPlaceholderTexture(parameterName, materialAsset) {\n\t\t\t\tmaterialAsset.resource[parameterName] = this._getPlaceholderTexture(parameterName);\n\t\t}\n\t\t_onTextureLoad(parameterName, materialAsset, textureAsset) {\n\t\t\t\tthis._assignTexture(parameterName, materialAsset, textureAsset.resource);\n\t\t\t\tmaterialAsset.resource.update();\n\t\t}\n\t\t_onTextureAdd(parameterName, materialAsset, textureAsset) {\n\t\t\t\tthis._assets.load(textureAsset);\n\t\t}\n\t\t_onTextureRemoveOrUnload(parameterName, materialAsset, textureAsset) {\n\t\t\t\tconst material = materialAsset.resource;\n\t\t\t\tif (material) {\n\t\t\t\t\t\tif (materialAsset.resource[parameterName] === textureAsset.resource) {\n\t\t\t\t\t\t\t\tthis._assignPlaceholderTexture(parameterName, materialAsset);\n\t\t\t\t\t\t\t\tmaterial.update();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_assignCubemap(parameterName, materialAsset, textures) {\n\t\t\t\tmaterialAsset.resource[parameterName] = textures[0];\n\t\t\t\tif (parameterName === 'cubeMap') {\n\t\t\t\t\t\tconst prefiltered = textures.slice(1);\n\t\t\t\t\t\tif (prefiltered.every((t)=>t)) {\n\t\t\t\t\t\t\t\tmaterialAsset.resource.prefilteredCubemaps = prefiltered;\n\t\t\t\t\t\t} else if (prefiltered[0]) {\n\t\t\t\t\t\t\t\tmaterialAsset.resource.envAtlas = prefiltered[0];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onCubemapLoad(parameterName, materialAsset, cubemapAsset) {\n\t\t\t\tthis._assignCubemap(parameterName, materialAsset, cubemapAsset.resources);\n\t\t\t\tthis._parser.initialize(materialAsset.resource, materialAsset.data);\n\t\t}\n\t\t_onCubemapAdd(parameterName, materialAsset, cubemapAsset) {\n\t\t\t\tthis._assets.load(cubemapAsset);\n\t\t}\n\t\t_onCubemapRemoveOrUnload(parameterName, materialAsset, cubemapAsset) {\n\t\t\t\tconst material = materialAsset.resource;\n\t\t\t\tif (materialAsset.data.prefilteredCubeMap128 === cubemapAsset.resources[1]) {\n\t\t\t\t\t\tthis._assignCubemap(parameterName, materialAsset, [\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tmaterial.update();\n\t\t\t\t}\n\t\t}\n\t\t_bindAndAssignAssets(materialAsset, assets) {\n\t\t\t\tconst data = this._parser.migrate(materialAsset.data);\n\t\t\t\tconst material = materialAsset.resource;\n\t\t\t\tconst pathMapping = data.mappingFormat === 'path';\n\t\t\t\tconst TEXTURES = standardMaterialTextureParameters;\n\t\t\t\tlet i, name, assetReference;\n\t\t\t\tfor(i = 0; i < TEXTURES.length; i++){\n\t\t\t\t\t\tname = TEXTURES[i];\n\t\t\t\t\t\tassetReference = material._assetReferences[name];\n\t\t\t\t\t\tconst dataAssetId = data[name];\n\t\t\t\t\t\tconst materialTexture = material[name];\n\t\t\t\t\t\tconst isPlaceHolderTexture = materialTexture === this._getPlaceholderTexture(name);\n\t\t\t\t\t\tconst dataValidated = data.validated;\n\t\t\t\t\t\tif (dataAssetId && (!materialTexture || !dataValidated || isPlaceHolderTexture)) {\n\t\t\t\t\t\t\t\tif (!assetReference) {\n\t\t\t\t\t\t\t\t\t\tassetReference = new AssetReference(name, materialAsset, assets, {\n\t\t\t\t\t\t\t\t\t\t\t\tload: this._onTextureLoad,\n\t\t\t\t\t\t\t\t\t\t\t\tadd: this._onTextureAdd,\n\t\t\t\t\t\t\t\t\t\t\t\tremove: this._onTextureRemoveOrUnload,\n\t\t\t\t\t\t\t\t\t\t\t\tunload: this._onTextureRemoveOrUnload\n\t\t\t\t\t\t\t\t\t\t}, this);\n\t\t\t\t\t\t\t\t\t\tmaterial._assetReferences[name] = assetReference;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (pathMapping) {\n\t\t\t\t\t\t\t\t\t\tassetReference.url = materialAsset.getAbsoluteUrl(dataAssetId);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tassetReference.id = dataAssetId;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (assetReference.asset) {\n\t\t\t\t\t\t\t\t\t\tif (assetReference.asset.resource) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._assignTexture(name, materialAsset, assetReference.asset.resource);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._assignPlaceholderTexture(name, materialAsset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tassets.load(assetReference.asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (assetReference) {\n\t\t\t\t\t\t\t\t\t\tif (pathMapping) {\n\t\t\t\t\t\t\t\t\t\t\t\tassetReference.url = null;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tassetReference.id = null;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst CUBEMAPS = standardMaterialCubemapParameters;\n\t\t\t\tfor(i = 0; i < CUBEMAPS.length; i++){\n\t\t\t\t\t\tname = CUBEMAPS[i];\n\t\t\t\t\t\tassetReference = material._assetReferences[name];\n\t\t\t\t\t\tif (data[name] && !materialAsset.data.prefilteredCubeMap128) {\n\t\t\t\t\t\t\t\tif (!assetReference) {\n\t\t\t\t\t\t\t\t\t\tassetReference = new AssetReference(name, materialAsset, assets, {\n\t\t\t\t\t\t\t\t\t\t\t\tload: this._onCubemapLoad,\n\t\t\t\t\t\t\t\t\t\t\t\tadd: this._onCubemapAdd,\n\t\t\t\t\t\t\t\t\t\t\t\tremove: this._onCubemapRemoveOrUnload,\n\t\t\t\t\t\t\t\t\t\t\t\tunload: this._onCubemapRemoveOrUnload\n\t\t\t\t\t\t\t\t\t\t}, this);\n\t\t\t\t\t\t\t\t\t\tmaterial._assetReferences[name] = assetReference;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (pathMapping) {\n\t\t\t\t\t\t\t\t\t\tassetReference.url = data[name];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tassetReference.id = data[name];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (assetReference.asset) {\n\t\t\t\t\t\t\t\t\t\tif (assetReference.asset.loaded) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._assignCubemap(name, materialAsset, assetReference.asset.resources);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tassets.load(assetReference.asset);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._parser.initialize(material, data);\n\t\t}\n}\n\nclass GlbModelParser {\n\t\tconstructor(modelHandler){\n\t\t\t\tthis._device = modelHandler.device;\n\t\t\t\tthis._defaultMaterial = modelHandler.defaultMaterial;\n\t\t\t\tthis._assets = modelHandler.assets;\n\t\t}\n\t\tparse(data, callback, asset) {\n\t\t\t\tGlbParser.parse('filename.glb', '', data, this._device, this._assets, asset?.options ?? {}, (err, result)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst model = GlbContainerResource.createModel(result, this._defaultMaterial);\n\t\t\t\t\t\t\t\tresult.destroy();\n\t\t\t\t\t\t\t\tcallback(null, model);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n}\n\nconst JSON_PRIMITIVE_TYPE = {\n\t\t'points': PRIMITIVE_POINTS,\n\t\t'lines': PRIMITIVE_LINES,\n\t\t'lineloop': PRIMITIVE_LINELOOP,\n\t\t'linestrip': PRIMITIVE_LINESTRIP,\n\t\t'triangles': PRIMITIVE_TRIANGLES,\n\t\t'trianglestrip': PRIMITIVE_TRISTRIP,\n\t\t'trianglefan': PRIMITIVE_TRIFAN\n};\nconst JSON_VERTEX_ELEMENT_TYPE = {\n\t\t'int8': TYPE_INT8,\n\t\t'uint8': TYPE_UINT8,\n\t\t'int16': TYPE_INT16,\n\t\t'uint16': TYPE_UINT16,\n\t\t'int32': TYPE_INT32,\n\t\t'uint32': TYPE_UINT32,\n\t\t'float32': TYPE_FLOAT32\n};\nclass JsonModelParser {\n\t\tconstructor(modelHandler){\n\t\t\t\tthis._device = modelHandler.device;\n\t\t\t\tthis._defaultMaterial = modelHandler.defaultMaterial;\n\t\t}\n\t\tparse(data, callback) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tif (!modelData) {\n\t\t\t\t\t\tcallback(null, null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (modelData.version <= 1) {\n\t\t\t\t\t\tcallback('JsonModelParser#parse: Trying to parse unsupported model format.');\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst nodes = this._parseNodes(data);\n\t\t\t\tconst skins = this._parseSkins(data, nodes);\n\t\t\t\tconst vertexBuffers = this._parseVertexBuffers(data);\n\t\t\t\tconst indices = this._parseIndexBuffers(data, vertexBuffers);\n\t\t\t\tconst morphs = this._parseMorphs(data, nodes, vertexBuffers);\n\t\t\t\tconst meshes = this._parseMeshes(data, skins.skins, morphs.morphs, vertexBuffers, indices.buffer, indices.data);\n\t\t\t\tconst meshInstances = this._parseMeshInstances(data, nodes, meshes, skins.skins, skins.instances, morphs.morphs, morphs.instances);\n\t\t\t\tconst model = new Model();\n\t\t\t\tmodel.graph = nodes[0];\n\t\t\t\tmodel.meshInstances = meshInstances;\n\t\t\t\tmodel.skinInstances = skins.instances;\n\t\t\t\tmodel.morphInstances = morphs.instances;\n\t\t\t\tmodel.getGraph().syncHierarchy();\n\t\t\t\tcallback(null, model);\n\t\t}\n\t\t_parseNodes(data) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tconst nodes = [];\n\t\t\t\tlet i;\n\t\t\t\tfor(i = 0; i < modelData.nodes.length; i++){\n\t\t\t\t\t\tconst nodeData = modelData.nodes[i];\n\t\t\t\t\t\tconst node = new GraphNode(nodeData.name);\n\t\t\t\t\t\tnode.setLocalPosition(nodeData.position[0], nodeData.position[1], nodeData.position[2]);\n\t\t\t\t\t\tnode.setLocalEulerAngles(nodeData.rotation[0], nodeData.rotation[1], nodeData.rotation[2]);\n\t\t\t\t\t\tnode.setLocalScale(nodeData.scale[0], nodeData.scale[1], nodeData.scale[2]);\n\t\t\t\t\t\tnode.scaleCompensation = !!nodeData.scaleCompensation;\n\t\t\t\t\t\tnodes.push(node);\n\t\t\t\t}\n\t\t\t\tfor(i = 1; i < modelData.parents.length; i++){\n\t\t\t\t\t\tnodes[modelData.parents[i]].addChild(nodes[i]);\n\t\t\t\t}\n\t\t\t\treturn nodes;\n\t\t}\n\t\t_parseSkins(data, nodes) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tconst skins = [];\n\t\t\t\tconst skinInstances = [];\n\t\t\t\tlet i, j;\n\t\t\t\tfor(i = 0; i < modelData.skins.length; i++){\n\t\t\t\t\t\tconst skinData = modelData.skins[i];\n\t\t\t\t\t\tconst inverseBindMatrices = [];\n\t\t\t\t\t\tfor(j = 0; j < skinData.inverseBindMatrices.length; j++){\n\t\t\t\t\t\t\t\tconst ibm = skinData.inverseBindMatrices[j];\n\t\t\t\t\t\t\t\tinverseBindMatrices[j] = new Mat4().set(ibm);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst skin = new Skin(this._device, inverseBindMatrices, skinData.boneNames);\n\t\t\t\t\t\tskins.push(skin);\n\t\t\t\t\t\tconst skinInstance = new SkinInstance(skin);\n\t\t\t\t\t\tconst bones = [];\n\t\t\t\t\t\tfor(j = 0; j < skin.boneNames.length; j++){\n\t\t\t\t\t\t\t\tconst boneName = skin.boneNames[j];\n\t\t\t\t\t\t\t\tconst bone = nodes[0].findByName(boneName);\n\t\t\t\t\t\t\t\tbones.push(bone);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tskinInstance.bones = bones;\n\t\t\t\t\t\tskinInstances.push(skinInstance);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tskins: skins,\n\t\t\t\t\t\tinstances: skinInstances\n\t\t\t\t};\n\t\t}\n\t\t_getMorphVertexCount(modelData, morphIndex, vertexBuffers) {\n\t\t\t\tfor(let i = 0; i < modelData.meshes.length; i++){\n\t\t\t\t\t\tconst meshData = modelData.meshes[i];\n\t\t\t\t\t\tif (meshData.morph === morphIndex) {\n\t\t\t\t\t\t\t\tconst vertexBuffer = vertexBuffers[meshData.vertices];\n\t\t\t\t\t\t\t\treturn vertexBuffer.numVertices;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t}\n\t\t_parseMorphs(data, nodes, vertexBuffers) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tconst morphs = [];\n\t\t\t\tconst morphInstances = [];\n\t\t\t\tlet i, j, vertexCount;\n\t\t\t\tlet targets, morphTarget, morphTargetArray;\n\t\t\t\tif (modelData.morphs) {\n\t\t\t\t\t\tconst sparseToFull = function(data, indices, totalCount) {\n\t\t\t\t\t\t\t\tconst full = new Float32Array(totalCount * 3);\n\t\t\t\t\t\t\t\tfor(let s = 0; s < indices.length; s++){\n\t\t\t\t\t\t\t\t\t\tconst dstIndex = indices[s] * 3;\n\t\t\t\t\t\t\t\t\t\tfull[dstIndex] = data[s * 3];\n\t\t\t\t\t\t\t\t\t\tfull[dstIndex + 1] = data[s * 3 + 1];\n\t\t\t\t\t\t\t\t\t\tfull[dstIndex + 2] = data[s * 3 + 2];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn full;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tfor(i = 0; i < modelData.morphs.length; i++){\n\t\t\t\t\t\t\t\ttargets = modelData.morphs[i].targets;\n\t\t\t\t\t\t\t\tmorphTargetArray = [];\n\t\t\t\t\t\t\t\tvertexCount = this._getMorphVertexCount(modelData, i, vertexBuffers);\n\t\t\t\t\t\t\t\tfor(j = 0; j < targets.length; j++){\n\t\t\t\t\t\t\t\t\t\tconst targetAabb = targets[j].aabb;\n\t\t\t\t\t\t\t\t\t\tconst min = targetAabb.min;\n\t\t\t\t\t\t\t\t\t\tconst max = targetAabb.max;\n\t\t\t\t\t\t\t\t\t\tconst aabb = new BoundingBox(new Vec3((max[0] + min[0]) * 0.5, (max[1] + min[1]) * 0.5, (max[2] + min[2]) * 0.5), new Vec3((max[0] - min[0]) * 0.5, (max[1] - min[1]) * 0.5, (max[2] - min[2]) * 0.5));\n\t\t\t\t\t\t\t\t\t\tconst indices = targets[j].indices;\n\t\t\t\t\t\t\t\t\t\tlet deltaPositions = targets[j].deltaPositions;\n\t\t\t\t\t\t\t\t\t\tlet deltaNormals = targets[j].deltaNormals;\n\t\t\t\t\t\t\t\t\t\tif (indices) {\n\t\t\t\t\t\t\t\t\t\t\t\tdeltaPositions = sparseToFull(deltaPositions, indices, vertexCount);\n\t\t\t\t\t\t\t\t\t\t\t\tdeltaNormals = sparseToFull(deltaNormals, indices, vertexCount);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tmorphTarget = new MorphTarget({\n\t\t\t\t\t\t\t\t\t\t\t\tdeltaPositions: deltaPositions,\n\t\t\t\t\t\t\t\t\t\t\t\tdeltaNormals: deltaNormals,\n\t\t\t\t\t\t\t\t\t\t\t\tname: targets[j].name,\n\t\t\t\t\t\t\t\t\t\t\t\taabb: aabb\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tmorphTargetArray.push(morphTarget);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst morph = new Morph(morphTargetArray, this._device);\n\t\t\t\t\t\t\t\tmorphs.push(morph);\n\t\t\t\t\t\t\t\tconst morphInstance = new MorphInstance(morph);\n\t\t\t\t\t\t\t\tmorphInstances.push(morphInstance);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tmorphs: morphs,\n\t\t\t\t\t\tinstances: morphInstances\n\t\t\t\t};\n\t\t}\n\t\t_parseVertexBuffers(data) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tconst vertexBuffers = [];\n\t\t\t\tconst attributeMap = {\n\t\t\t\t\t\tposition: SEMANTIC_POSITION,\n\t\t\t\t\t\tnormal: SEMANTIC_NORMAL,\n\t\t\t\t\t\ttangent: SEMANTIC_TANGENT,\n\t\t\t\t\t\tblendWeight: SEMANTIC_BLENDWEIGHT,\n\t\t\t\t\t\tblendIndices: SEMANTIC_BLENDINDICES,\n\t\t\t\t\t\tcolor: SEMANTIC_COLOR,\n\t\t\t\t\t\ttexCoord0: SEMANTIC_TEXCOORD0,\n\t\t\t\t\t\ttexCoord1: SEMANTIC_TEXCOORD1,\n\t\t\t\t\t\ttexCoord2: SEMANTIC_TEXCOORD2,\n\t\t\t\t\t\ttexCoord3: SEMANTIC_TEXCOORD3,\n\t\t\t\t\t\ttexCoord4: SEMANTIC_TEXCOORD4,\n\t\t\t\t\t\ttexCoord5: SEMANTIC_TEXCOORD5,\n\t\t\t\t\t\ttexCoord6: SEMANTIC_TEXCOORD6,\n\t\t\t\t\t\ttexCoord7: SEMANTIC_TEXCOORD7\n\t\t\t\t};\n\t\t\t\tfor(let i = 0; i < modelData.vertices.length; i++){\n\t\t\t\t\t\tconst vertexData = modelData.vertices[i];\n\t\t\t\t\t\tconst formatDesc = [];\n\t\t\t\t\t\tfor(const attributeName in vertexData){\n\t\t\t\t\t\t\t\tconst attribute = vertexData[attributeName];\n\t\t\t\t\t\t\t\tformatDesc.push({\n\t\t\t\t\t\t\t\t\t\tsemantic: attributeMap[attributeName],\n\t\t\t\t\t\t\t\t\t\tcomponents: attribute.components,\n\t\t\t\t\t\t\t\t\t\ttype: JSON_VERTEX_ELEMENT_TYPE[attribute.type],\n\t\t\t\t\t\t\t\t\t\tnormalize: attributeMap[attributeName] === SEMANTIC_COLOR\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst vertexFormat = new VertexFormat(this._device, formatDesc);\n\t\t\t\t\t\tconst numVertices = vertexData.position.data.length / vertexData.position.components;\n\t\t\t\t\t\tconst vertexBuffer = new VertexBuffer(this._device, vertexFormat, numVertices);\n\t\t\t\t\t\tconst iterator = new VertexIterator(vertexBuffer);\n\t\t\t\t\t\tfor(let j = 0; j < numVertices; j++){\n\t\t\t\t\t\t\t\tfor(const attributeName in vertexData){\n\t\t\t\t\t\t\t\t\t\tconst attribute = vertexData[attributeName];\n\t\t\t\t\t\t\t\t\t\tswitch(attribute.components){\n\t\t\t\t\t\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titerator.element[attributeMap[attributeName]].set(attribute.data[j]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titerator.element[attributeMap[attributeName]].set(attribute.data[j * 2], 1.0 - attribute.data[j * 2 + 1]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\tcase 3:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titerator.element[attributeMap[attributeName]].set(attribute.data[j * 3], attribute.data[j * 3 + 1], attribute.data[j * 3 + 2]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\tcase 4:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titerator.element[attributeMap[attributeName]].set(attribute.data[j * 4], attribute.data[j * 4 + 1], attribute.data[j * 4 + 2], attribute.data[j * 4 + 3]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\titerator.next();\n\t\t\t\t\t\t}\n\t\t\t\t\t\titerator.end();\n\t\t\t\t\t\tvertexBuffers.push(vertexBuffer);\n\t\t\t\t}\n\t\t\t\treturn vertexBuffers;\n\t\t}\n\t\t_parseIndexBuffers(data, vertexBuffers) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tlet indexBuffer = null;\n\t\t\t\tlet indexData = null;\n\t\t\t\tlet i;\n\t\t\t\tlet numIndices = 0;\n\t\t\t\tfor(i = 0; i < modelData.meshes.length; i++){\n\t\t\t\t\t\tconst meshData = modelData.meshes[i];\n\t\t\t\t\t\tif (meshData.indices !== undefined) {\n\t\t\t\t\t\t\t\tnumIndices += meshData.indices.length;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet maxVerts = 0;\n\t\t\t\tfor(i = 0; i < vertexBuffers.length; i++){\n\t\t\t\t\t\tmaxVerts = Math.max(maxVerts, vertexBuffers[i].numVertices);\n\t\t\t\t}\n\t\t\t\tif (numIndices > 0) {\n\t\t\t\t\t\tif (maxVerts > 0xFFFF) {\n\t\t\t\t\t\t\t\tindexBuffer = new IndexBuffer(this._device, INDEXFORMAT_UINT32, numIndices);\n\t\t\t\t\t\t\t\tindexData = new Uint32Array(indexBuffer.lock());\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tindexBuffer = new IndexBuffer(this._device, INDEXFORMAT_UINT16, numIndices);\n\t\t\t\t\t\t\t\tindexData = new Uint16Array(indexBuffer.lock());\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tbuffer: indexBuffer,\n\t\t\t\t\t\tdata: indexData\n\t\t\t\t};\n\t\t}\n\t\t_parseMeshes(data, skins, morphs, vertexBuffers, indexBuffer, indexData) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tconst meshes = [];\n\t\t\t\tlet indexBase = 0;\n\t\t\t\tfor(let i = 0; i < modelData.meshes.length; i++){\n\t\t\t\t\t\tconst meshData = modelData.meshes[i];\n\t\t\t\t\t\tconst meshAabb = meshData.aabb;\n\t\t\t\t\t\tconst min = meshAabb.min;\n\t\t\t\t\t\tconst max = meshAabb.max;\n\t\t\t\t\t\tconst aabb = new BoundingBox(new Vec3((max[0] + min[0]) * 0.5, (max[1] + min[1]) * 0.5, (max[2] + min[2]) * 0.5), new Vec3((max[0] - min[0]) * 0.5, (max[1] - min[1]) * 0.5, (max[2] - min[2]) * 0.5));\n\t\t\t\t\t\tconst indexed = meshData.indices !== undefined;\n\t\t\t\t\t\tconst mesh = new Mesh(this._device);\n\t\t\t\t\t\tmesh.vertexBuffer = vertexBuffers[meshData.vertices];\n\t\t\t\t\t\tmesh.indexBuffer[0] = indexed ? indexBuffer : null;\n\t\t\t\t\t\tmesh.primitive[0].type = JSON_PRIMITIVE_TYPE[meshData.type];\n\t\t\t\t\t\tmesh.primitive[0].base = indexed ? meshData.base + indexBase : meshData.base;\n\t\t\t\t\t\tmesh.primitive[0].count = meshData.count;\n\t\t\t\t\t\tmesh.primitive[0].indexed = indexed;\n\t\t\t\t\t\tmesh.skin = meshData.skin !== undefined ? skins[meshData.skin] : null;\n\t\t\t\t\t\tmesh.morph = meshData.morph !== undefined ? morphs[meshData.morph] : null;\n\t\t\t\t\t\tmesh.aabb = aabb;\n\t\t\t\t\t\tif (indexed) {\n\t\t\t\t\t\t\t\tindexData.set(meshData.indices, indexBase);\n\t\t\t\t\t\t\t\tindexBase += meshData.indices.length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmeshes.push(mesh);\n\t\t\t\t}\n\t\t\t\tif (indexBuffer !== null) {\n\t\t\t\t\t\tindexBuffer.unlock();\n\t\t\t\t}\n\t\t\t\treturn meshes;\n\t\t}\n\t\t_parseMeshInstances(data, nodes, meshes, skins, skinInstances, morphs, morphInstances) {\n\t\t\t\tconst modelData = data.model;\n\t\t\t\tconst meshInstances = [];\n\t\t\t\tlet i;\n\t\t\t\tfor(i = 0; i < modelData.meshInstances.length; i++){\n\t\t\t\t\t\tconst meshInstanceData = modelData.meshInstances[i];\n\t\t\t\t\t\tconst node = nodes[meshInstanceData.node];\n\t\t\t\t\t\tconst mesh = meshes[meshInstanceData.mesh];\n\t\t\t\t\t\tconst meshInstance = new MeshInstance(mesh, this._defaultMaterial, node);\n\t\t\t\t\t\tif (mesh.skin) {\n\t\t\t\t\t\t\t\tconst skinIndex = skins.indexOf(mesh.skin);\n\t\t\t\t\t\t\t\tmeshInstance.skinInstance = skinInstances[skinIndex];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (mesh.morph) {\n\t\t\t\t\t\t\t\tconst morphIndex = morphs.indexOf(mesh.morph);\n\t\t\t\t\t\t\t\tmeshInstance.morphInstance = morphInstances[morphIndex];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmeshInstances.push(meshInstance);\n\t\t\t\t}\n\t\t\t\treturn meshInstances;\n\t\t}\n}\n\nclass ModelHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'model');\n\t\t\t\tthis._parsers = [];\n\t\t\t\tthis.device = app.graphicsDevice;\n\t\t\t\tthis.assets = app.assets;\n\t\t\t\tthis.defaultMaterial = getDefaultMaterial(this.device);\n\t\t\t\tthis.addParser(new JsonModelParser(this), (url, data)=>{\n\t\t\t\t\t\treturn path.getExtension(url) === '.json';\n\t\t\t\t});\n\t\t\t\tthis.addParser(new GlbModelParser(this), (url, data)=>{\n\t\t\t\t\t\treturn path.getExtension(url) === '.glb';\n\t\t\t\t});\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst options = {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t};\n\t\t\t\tif (url.load.startsWith('blob:') || url.load.startsWith('data:')) {\n\t\t\t\t\t\tif (path.getExtension(url.original).toLowerCase() === '.glb') {\n\t\t\t\t\t\t\t\toptions.responseType = Http.ResponseType.ARRAY_BUFFER;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toptions.responseType = Http.ResponseType.JSON;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, options, (err, response)=>{\n\t\t\t\t\t\tif (!callback) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < this._parsers.length; i++){\n\t\t\t\t\t\t\t\t\t\tconst p = this._parsers[i];\n\t\t\t\t\t\t\t\t\t\tif (p.decider(url.original, response)) {\n\t\t\t\t\t\t\t\t\t\t\t\tp.parser.parse(response, (err, parseResult)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(null, parseResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}, asset);\n\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcallback('No parsers found');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading model: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topen(url, data) {\n\t\t\t\treturn data;\n\t\t}\n\t\tpatch(asset, assets) {\n\t\t\t\tif (!asset.resource) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = asset.data;\n\t\t\t\tconst self = this;\n\t\t\t\tasset.resource.meshInstances.forEach((meshInstance, i)=>{\n\t\t\t\t\t\tif (data.mapping) {\n\t\t\t\t\t\t\t\tconst handleMaterial = function(asset) {\n\t\t\t\t\t\t\t\t\t\tif (asset.resource) {\n\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance.material = asset.resource;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tasset.once('load', handleMaterial);\n\t\t\t\t\t\t\t\t\t\t\t\tassets.load(asset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tasset.once('remove', (asset)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tif (meshInstance.material === asset.resource) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance.material = self.defaultMaterial;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif (!data.mapping[i]) {\n\t\t\t\t\t\t\t\t\t\tmeshInstance.material = self.defaultMaterial;\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst id = data.mapping[i].material;\n\t\t\t\t\t\t\t\tconst url = data.mapping[i].path;\n\t\t\t\t\t\t\t\tlet material;\n\t\t\t\t\t\t\t\tif (id !== undefined) {\n\t\t\t\t\t\t\t\t\t\tif (!id) {\n\t\t\t\t\t\t\t\t\t\t\t\tmeshInstance.material = self.defaultMaterial;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tmaterial = assets.get(id);\n\t\t\t\t\t\t\t\t\t\t\t\tif (material) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thandleMaterial(material);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tassets.once(`add:${id}`, handleMaterial);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (url) {\n\t\t\t\t\t\t\t\t\t\tconst path = asset.getAbsoluteUrl(data.mapping[i].path);\n\t\t\t\t\t\t\t\t\t\tmaterial = assets.getByUrl(path);\n\t\t\t\t\t\t\t\t\t\tif (material) {\n\t\t\t\t\t\t\t\t\t\t\t\thandleMaterial(material);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tassets.once(`add:url:${path}`, handleMaterial);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\taddParser(parser, decider) {\n\t\t\t\tthis._parsers.push({\n\t\t\t\t\t\tparser: parser,\n\t\t\t\t\t\tdecider: decider\n\t\t\t\t});\n\t\t}\n}\n\nclass SceneHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'scene');\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tSceneUtils.load(url, this.maxRetries, callback);\n\t\t}\n\t\topen(url, data) {\n\t\t\t\tthis._app.systems.script.preloading = true;\n\t\t\t\tconst parser = new SceneParser(this._app, false);\n\t\t\t\tconst parent = parser.parse(data);\n\t\t\t\tconst scene = this._app.scene;\n\t\t\t\tscene.root = parent;\n\t\t\t\tthis._app.applySceneSettings(data.settings);\n\t\t\t\tthis._app.systems.script.preloading = false;\n\t\t\t\treturn scene;\n\t\t}\n}\n\nclass ScriptTypes {\n\t\tstatic{\n\t\t\t\tthis._types = [];\n\t\t}\n\t\tstatic push(Type) {\n\t\t\t\tScriptTypes._types.push(Type);\n\t\t}\n}\n\nconst reservedScriptNames = new Set([\n\t\t'system',\n\t\t'entity',\n\t\t'create',\n\t\t'destroy',\n\t\t'swap',\n\t\t'move',\n\t\t'data',\n\t\t\"scripts\",\n\t\t\"_scripts\",\n\t\t\"_scriptsIndex\",\n\t\t\"_scriptsData\",\n\t\t'enabled',\n\t\t'_oldState',\n\t\t'onEnable',\n\t\t'onDisable',\n\t\t'onPostStateChange',\n\t\t'_onSetEnabled',\n\t\t'_checkState',\n\t\t'_onBeforeRemove',\n\t\t'_onInitializeAttributes',\n\t\t'_onInitialize',\n\t\t'_onPostInitialize',\n\t\t'_onUpdate',\n\t\t'_onPostUpdate',\n\t\t'_callbacks',\n\t\t'_callbackActive',\n\t\t'has',\n\t\t'get',\n\t\t'on',\n\t\t'off',\n\t\t'fire',\n\t\t'once',\n\t\t'hasEvent'\n]);\nconst reservedAttributes = {};\nScriptAttributes.reservedNames.forEach((value, value2, set)=>{\n\t\treservedAttributes[value] = 1;\n});\nfunction registerScript(script, name, app) {\n\t\tif (typeof script !== 'function') {\n\t\t\t\tthrow new Error(`script class: '${script}' must be a constructor function (i.e. class).`);\n\t\t}\n\t\tif (!(script.prototype instanceof Script)) {\n\t\t\t\tthrow new Error(`script class: '${ScriptType.__getScriptName(script)}' does not extend pc.Script.`);\n\t\t}\n\t\tname = name || script.__name || ScriptType.__getScriptName(script);\n\t\tif (reservedScriptNames.has(name)) {\n\t\t\t\tthrow new Error(`script name: '${name}' is reserved, please change script name`);\n\t\t}\n\t\tscript.__name = name;\n\t\tconst registry = AppBase.getApplication().scripts;\n\t\tregistry.add(script);\n\t\tScriptTypes.push(script);\n}\n\nconst toLowerCamelCase = (str)=>str[0].toLowerCase() + str.substring(1);\nclass ScriptHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, \"script\");\n\t\t\t\tthis._scripts = {};\n\t\t\t\tthis._cache = {};\n\t\t}\n\t\tclearCache() {\n\t\t\t\tfor(const key in this._cache){\n\t\t\t\t\t\tconst element = this._cache[key];\n\t\t\t\t\t\tconst parent = element.parentNode;\n\t\t\t\t\t\tif (parent) {\n\t\t\t\t\t\t\t\tparent.removeChild(element);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._cache = {};\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst self = this;\n\t\t\t\tthis._app;\n\t\t\t\tconst onScriptLoad = (url.load, (err, url, extra)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tconst obj = {};\n\t\t\t\t\t\t\t\tfor(let i = 0; i < ScriptTypes._types.length; i++){\n\t\t\t\t\t\t\t\t\t\tobj[ScriptTypes._types[i].name] = ScriptTypes._types[i];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tScriptTypes._types.length = 0;\n\t\t\t\t\t\t\t\tcallback(null, obj, extra);\n\t\t\t\t\t\t\t\tconst urlWithoutEndHash = url.split('&hash=')[0];\n\t\t\t\t\t\t\t\tdelete self._loader._cache[ResourceLoader.makeKey(urlWithoutEndHash, \"script\")];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst [basePath] = url.load.split('?');\n\t\t\t\tconst isEsmScript = basePath.endsWith('.mjs');\n\t\t\t\tif (isEsmScript) {\n\t\t\t\t\t\tthis._loadModule(basePath, onScriptLoad);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._loadScript(url.load, onScriptLoad);\n\t\t\t\t}\n\t\t}\n\t\topen(url, data) {\n\t\t\t\treturn data;\n\t\t}\n\t\tpatch(asset, assets) {}\n\t\t_loadScript(url, callback) {\n\t\t\t\tconst head = document.head;\n\t\t\t\tconst element = document.createElement(\"script\");\n\t\t\t\tthis._cache[url] = element;\n\t\t\t\telement.async = false;\n\t\t\t\telement.addEventListener('error', (e)=>{\n\t\t\t\t\t\tcallback(`Script: ${e.target.src} failed to load`);\n\t\t\t\t}, false);\n\t\t\t\tlet done = false;\n\t\t\t\telement.onload = element.onreadystatechange = function() {\n\t\t\t\t\t\tif (!done && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) {\n\t\t\t\t\t\t\t\tdone = true;\n\t\t\t\t\t\t\t\tcallback(null, url, element);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\telement.src = url;\n\t\t\t\thead.appendChild(element);\n\t\t}\n\t\t_loadModule(url, callback) {\n\t\t\t\tconst isBrowserWithOrigin = platform.browser && window.location.origin !== 'null';\n\t\t\t\tconst baseUrl = isBrowserWithOrigin ? window.location.origin + window.location.pathname : import.meta.url;\n\t\t\t\tconst importUrl = new URL(url, baseUrl);\n\t\t\t\timport(/* @vite-ignore */ /* webpackIgnore: true */ importUrl.toString()).then((module)=>{\n\t\t\t\t\t\tconst filename = importUrl.pathname.split('/').pop();\n\t\t\t\t\t\tconst scriptSchema = this._app.assets.find(filename, \"script\")?.data?.scripts;\n\t\t\t\t\t\tfor(const key in module){\n\t\t\t\t\t\t\t\tconst scriptClass = module[key];\n\t\t\t\t\t\t\t\tconst extendsScriptType = scriptClass.prototype instanceof Script;\n\t\t\t\t\t\t\t\tif (extendsScriptType) {\n\t\t\t\t\t\t\t\t\t\tconst lowerCamelCaseName = toLowerCamelCase(scriptClass.name);\n\t\t\t\t\t\t\t\t\t\tif (!scriptClass.scriptName) ;\n\t\t\t\t\t\t\t\t\t\tconst scriptName = scriptClass.scriptName ?? lowerCamelCaseName;\n\t\t\t\t\t\t\t\t\t\tregisterScript(scriptClass, scriptName);\n\t\t\t\t\t\t\t\t\t\tif (scriptSchema) this._app.scripts.addSchema(scriptName, scriptSchema[scriptName]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcallback(null, url, null);\n\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\tcallback(err);\n\t\t\t\t});\n\t\t}\n}\n\nclass ShaderHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'shader'), this.decoder = null;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading shader resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topenBinary(data) {\n\t\t\t\tthis.decoder ??= new TextDecoder('utf-8');\n\t\t\t\treturn this.decoder.decode(data);\n\t\t}\n}\n\nfunction onTextureAtlasLoaded(atlasAsset) {\n\t\tconst spriteAsset = this;\n\t\tif (spriteAsset.resource) {\n\t\t\t\tspriteAsset.resource.atlas = atlasAsset.resource;\n\t\t}\n}\nfunction onTextureAtlasAdded(atlasAsset) {\n\t\tconst spriteAsset = this;\n\t\tspriteAsset.registry.load(atlasAsset);\n}\nclass SpriteHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'sprite');\n\t\t\t\tthis._assets = app.assets;\n\t\t\t\tthis._device = app.graphicsDevice;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (path.getExtension(url.original) === '.json') {\n\t\t\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\topen(url, data) {\n\t\t\t\tconst sprite = new Sprite(this._device);\n\t\t\t\tif (url) {\n\t\t\t\t\t\tsprite.__data = data;\n\t\t\t\t}\n\t\t\t\treturn sprite;\n\t\t}\n\t\tpatch(asset, assets) {\n\t\t\t\tconst sprite = asset.resource;\n\t\t\t\tif (sprite.__data) {\n\t\t\t\t\t\tasset.data.pixelsPerUnit = sprite.__data.pixelsPerUnit;\n\t\t\t\t\t\tasset.data.renderMode = sprite.__data.renderMode;\n\t\t\t\t\t\tasset.data.frameKeys = sprite.__data.frameKeys;\n\t\t\t\t\t\tif (sprite.__data.textureAtlasAsset) {\n\t\t\t\t\t\t\t\tconst atlas = assets.getByUrl(sprite.__data.textureAtlasAsset);\n\t\t\t\t\t\t\t\tif (atlas) {\n\t\t\t\t\t\t\t\t\t\tasset.data.textureAtlasAsset = atlas.id;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconsole.warn(`Could not find textureatlas with url: ${sprite.__data.textureAtlasAsset}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsprite.startUpdate();\n\t\t\t\tsprite.renderMode = asset.data.renderMode;\n\t\t\t\tsprite.pixelsPerUnit = asset.data.pixelsPerUnit;\n\t\t\t\tsprite.frameKeys = asset.data.frameKeys;\n\t\t\t\tthis._updateAtlas(asset);\n\t\t\t\tsprite.endUpdate();\n\t\t\t\tasset.off('change', this._onAssetChange, this);\n\t\t\t\tasset.on('change', this._onAssetChange, this);\n\t\t}\n\t\t_updateAtlas(asset) {\n\t\t\t\tconst sprite = asset.resource;\n\t\t\t\tif (!asset.data.textureAtlasAsset) {\n\t\t\t\t\t\tsprite.atlas = null;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._assets.off(`load:${asset.data.textureAtlasAsset}`, onTextureAtlasLoaded, asset);\n\t\t\t\tthis._assets.on(`load:${asset.data.textureAtlasAsset}`, onTextureAtlasLoaded, asset);\n\t\t\t\tconst atlasAsset = this._assets.get(asset.data.textureAtlasAsset);\n\t\t\t\tif (atlasAsset && atlasAsset.resource) {\n\t\t\t\t\t\tsprite.atlas = atlasAsset.resource;\n\t\t\t\t} else {\n\t\t\t\t\t\tif (!atlasAsset) {\n\t\t\t\t\t\t\t\tthis._assets.off(`add:${asset.data.textureAtlasAsset}`, onTextureAtlasAdded, asset);\n\t\t\t\t\t\t\t\tthis._assets.on(`add:${asset.data.textureAtlasAsset}`, onTextureAtlasAdded, asset);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._assets.load(atlasAsset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onAssetChange(asset, attribute, value, oldValue) {\n\t\t\t\tif (attribute === 'data') {\n\t\t\t\t\t\tif (value && value.textureAtlasAsset && oldValue && value.textureAtlasAsset !== oldValue.textureAtlasAsset) {\n\t\t\t\t\t\t\t\tthis._assets.off(`load:${oldValue.textureAtlasAsset}`, onTextureAtlasLoaded, asset);\n\t\t\t\t\t\t\t\tthis._assets.off(`add:${oldValue.textureAtlasAsset}`, onTextureAtlasAdded, asset);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nclass Template {\n\t\tconstructor(app, data){\n\t\t\t\tthis._templateRoot = null;\n\t\t\t\tthis._app = app;\n\t\t\t\tthis._data = data;\n\t\t}\n\t\tinstantiate() {\n\t\t\t\tif (!this._templateRoot) {\n\t\t\t\t\t\tthis._parseTemplate();\n\t\t\t\t}\n\t\t\t\treturn this._templateRoot.clone();\n\t\t}\n\t\t_parseTemplate() {\n\t\t\t\tconst parser = new SceneParser(this._app, true);\n\t\t\t\tthis._templateRoot = parser.parse(this._data);\n\t\t}\n}\n\nclass TemplateHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'template'), this.decoder = null;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst options = {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t};\n\t\t\t\thttp.get(url.load, options, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(`Error requesting template: ${url.original}`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(err, response);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topen(url, data) {\n\t\t\t\treturn new Template(this._app, data);\n\t\t}\n\t\topenBinary(data) {\n\t\t\t\tthis.decoder ??= new TextDecoder('utf-8');\n\t\t\t\treturn new Template(this._app, JSON.parse(this.decoder.decode(data)));\n\t\t}\n}\n\nclass TextHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'text'), this.decoder = null;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\tcallback(null, response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading text resource: ${url.original} [${err}]`);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\topenBinary(data) {\n\t\t\t\tthis.decoder ??= new TextDecoder('utf-8');\n\t\t\t\treturn this.decoder.decode(data);\n\t\t}\n}\n\nconst JSON_ADDRESS_MODE$1 = {\n\t\t'repeat': ADDRESS_REPEAT,\n\t\t'clamp': ADDRESS_CLAMP_TO_EDGE,\n\t\t'mirror': ADDRESS_MIRRORED_REPEAT\n};\nconst JSON_FILTER_MODE$1 = {\n\t\t'nearest': FILTER_NEAREST,\n\t\t'linear': FILTER_LINEAR,\n\t\t'nearest_mip_nearest': FILTER_NEAREST_MIPMAP_NEAREST,\n\t\t'linear_mip_nearest': FILTER_LINEAR_MIPMAP_NEAREST,\n\t\t'nearest_mip_linear': FILTER_NEAREST_MIPMAP_LINEAR,\n\t\t'linear_mip_linear': FILTER_LINEAR_MIPMAP_LINEAR\n};\nconst regexFrame = /^data\\.frames\\.(\\d+)$/;\nclass TextureAtlasHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'textureatlas');\n\t\t\t\tthis._loader = app.loader;\n\t\t}\n\t\tload(url, callback) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst self = this;\n\t\t\t\tconst handler = this._loader.getHandler('texture');\n\t\t\t\tif (path.getExtension(url.original) === '.json') {\n\t\t\t\t\t\thttp.get(url.load, {\n\t\t\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\t\t\tmaxRetries: this.maxRetries\n\t\t\t\t\t\t}, (err, response)=>{\n\t\t\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\t\t\t\tconst textureUrl = url.original.replace('.json', '.png');\n\t\t\t\t\t\t\t\t\t\tself._loader.load(textureUrl, 'texture', (err, texture)=>{\n\t\t\t\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcallback(null, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata: response,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttexture: texture\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\thandler.load(url, callback);\n\t\t\t\t}\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\tconst resource = new TextureAtlas();\n\t\t\t\tif (data.texture && data.data) {\n\t\t\t\t\t\tresource.texture = data.texture;\n\t\t\t\t\t\tresource.__data = data.data;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst handler = this._loader.getHandler('texture');\n\t\t\t\t\t\tconst texture = handler.open(url, data, asset);\n\t\t\t\t\t\tif (!texture) return null;\n\t\t\t\t\t\tresource.texture = texture;\n\t\t\t\t}\n\t\t\t\treturn resource;\n\t\t}\n\t\tpatch(asset, assets) {\n\t\t\t\tif (!asset.resource) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (asset.resource.__data) {\n\t\t\t\t\t\tif (asset.resource.__data.minfilter !== undefined) asset.data.minfilter = asset.resource.__data.minfilter;\n\t\t\t\t\t\tif (asset.resource.__data.magfilter !== undefined) asset.data.magfilter = asset.resource.__data.magfilter;\n\t\t\t\t\t\tif (asset.resource.__data.addressu !== undefined) asset.data.addressu = asset.resource.__data.addressu;\n\t\t\t\t\t\tif (asset.resource.__data.addressv !== undefined) asset.data.addressv = asset.resource.__data.addressv;\n\t\t\t\t\t\tif (asset.resource.__data.mipmaps !== undefined) asset.data.mipmaps = asset.resource.__data.mipmaps;\n\t\t\t\t\t\tif (asset.resource.__data.anisotropy !== undefined) asset.data.anisotropy = asset.resource.__data.anisotropy;\n\t\t\t\t\t\tif (asset.resource.__data.rgbm !== undefined) asset.data.rgbm = !!asset.resource.__data.rgbm;\n\t\t\t\t\t\tasset.data.frames = asset.resource.__data.frames;\n\t\t\t\t\t\tdelete asset.resource.__data;\n\t\t\t\t}\n\t\t\t\tconst texture = asset.resource.texture;\n\t\t\t\tif (texture) {\n\t\t\t\t\t\ttexture.name = asset.name;\n\t\t\t\t\t\tif (asset.data.hasOwnProperty('minfilter') && texture.minFilter !== JSON_FILTER_MODE$1[asset.data.minfilter]) {\n\t\t\t\t\t\t\t\ttexture.minFilter = JSON_FILTER_MODE$1[asset.data.minfilter];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (asset.data.hasOwnProperty('magfilter') && texture.magFilter !== JSON_FILTER_MODE$1[asset.data.magfilter]) {\n\t\t\t\t\t\t\t\ttexture.magFilter = JSON_FILTER_MODE$1[asset.data.magfilter];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (asset.data.hasOwnProperty('addressu') && texture.addressU !== JSON_ADDRESS_MODE$1[asset.data.addressu]) {\n\t\t\t\t\t\t\t\ttexture.addressU = JSON_ADDRESS_MODE$1[asset.data.addressu];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (asset.data.hasOwnProperty('addressv') && texture.addressV !== JSON_ADDRESS_MODE$1[asset.data.addressv]) {\n\t\t\t\t\t\t\t\ttexture.addressV = JSON_ADDRESS_MODE$1[asset.data.addressv];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (asset.data.hasOwnProperty('mipmaps') && texture.mipmaps !== asset.data.mipmaps) {\n\t\t\t\t\t\t\t\ttexture.mipmaps = asset.data.mipmaps;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (asset.data.hasOwnProperty('anisotropy') && texture.anisotropy !== asset.data.anisotropy) {\n\t\t\t\t\t\t\t\ttexture.anisotropy = asset.data.anisotropy;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (asset.data.hasOwnProperty('rgbm')) {\n\t\t\t\t\t\t\t\tconst type = asset.data.rgbm ? TEXTURETYPE_RGBM : TEXTURETYPE_DEFAULT;\n\t\t\t\t\t\t\t\tif (texture.type !== type) {\n\t\t\t\t\t\t\t\t\t\ttexture.type = type;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tasset.resource.texture = texture;\n\t\t\t\tconst frames = {};\n\t\t\t\tfor(const key in asset.data.frames){\n\t\t\t\t\t\tconst frame = asset.data.frames[key];\n\t\t\t\t\t\tframes[key] = {\n\t\t\t\t\t\t\t\trect: new Vec4(frame.rect),\n\t\t\t\t\t\t\t\tpivot: new Vec2(frame.pivot),\n\t\t\t\t\t\t\t\tborder: new Vec4(frame.border)\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tasset.resource.frames = frames;\n\t\t\t\tasset.off('change', this._onAssetChange, this);\n\t\t\t\tasset.on('change', this._onAssetChange, this);\n\t\t}\n\t\t_onAssetChange(asset, attribute, value) {\n\t\t\t\tlet frame;\n\t\t\t\tif (attribute === 'data' || attribute === 'data.frames') {\n\t\t\t\t\t\tconst frames = {};\n\t\t\t\t\t\tfor(const key in value.frames){\n\t\t\t\t\t\t\t\tframe = value.frames[key];\n\t\t\t\t\t\t\t\tframes[key] = {\n\t\t\t\t\t\t\t\t\t\trect: new Vec4(frame.rect),\n\t\t\t\t\t\t\t\t\t\tpivot: new Vec2(frame.pivot),\n\t\t\t\t\t\t\t\t\t\tborder: new Vec4(frame.border)\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tasset.resource.frames = frames;\n\t\t\t\t} else {\n\t\t\t\t\t\tconst match = attribute.match(regexFrame);\n\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tconst frameKey = match[1];\n\t\t\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\t\t\t\tif (!asset.resource.frames[frameKey]) {\n\t\t\t\t\t\t\t\t\t\t\t\tasset.resource.frames[frameKey] = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trect: new Vec4(value.rect),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpivot: new Vec2(value.pivot),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tborder: new Vec4(value.border)\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tframe = asset.resource.frames[frameKey];\n\t\t\t\t\t\t\t\t\t\t\t\tframe.rect.set(value.rect[0], value.rect[1], value.rect[2], value.rect[3]);\n\t\t\t\t\t\t\t\t\t\t\t\tframe.pivot.set(value.pivot[0], value.pivot[1]);\n\t\t\t\t\t\t\t\t\t\t\t\tframe.border.set(value.border[0], value.border[1], value.border[2], value.border[3]);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tasset.resource.fire('set:frame', frameKey, asset.resource.frames[frameKey]);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (asset.resource.frames[frameKey]) {\n\t\t\t\t\t\t\t\t\t\t\t\tdelete asset.resource.frames[frameKey];\n\t\t\t\t\t\t\t\t\t\t\t\tasset.resource.fire('remove:frame', frameKey);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nfunction BasisWorker() {\n\t\tconst BASIS_FORMAT = {\n\t\t\t\tcTFETC1: 0,\n\t\t\t\tcTFETC2: 1,\n\t\t\t\tcTFBC1: 2,\n\t\t\t\tcTFBC3: 3,\n\t\t\t\tcTFPVRTC1_4_RGB: 8,\n\t\t\t\tcTFPVRTC1_4_RGBA: 9,\n\t\t\t\tcTFASTC_4x4: 10,\n\t\t\t\tcTFATC_RGB: 11,\n\t\t\t\tcTFATC_RGBA_INTERPOLATED_ALPHA: 12,\n\t\t\t\tcTFRGBA32: 13,\n\t\t\t\tcTFRGB565: 14,\n\t\t\t\tcTFRGBA4444: 16\n\t\t};\n\t\tconst opaqueMapping = {\n\t\t\t\tastc: BASIS_FORMAT.cTFASTC_4x4,\n\t\t\t\tdxt: BASIS_FORMAT.cTFBC1,\n\t\t\t\tetc1: BASIS_FORMAT.cTFETC1,\n\t\t\t\tetc2: BASIS_FORMAT.cTFETC1,\n\t\t\t\tpvr: BASIS_FORMAT.cTFPVRTC1_4_RGB,\n\t\t\t\tatc: BASIS_FORMAT.cTFATC_RGB,\n\t\t\t\tnone: BASIS_FORMAT.cTFRGB565\n\t\t};\n\t\tconst alphaMapping = {\n\t\t\t\tastc: BASIS_FORMAT.cTFASTC_4x4,\n\t\t\t\tdxt: BASIS_FORMAT.cTFBC3,\n\t\t\t\tetc1: BASIS_FORMAT.cTFRGBA4444,\n\t\t\t\tetc2: BASIS_FORMAT.cTFETC2,\n\t\t\t\tpvr: BASIS_FORMAT.cTFPVRTC1_4_RGBA,\n\t\t\t\tatc: BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA,\n\t\t\t\tnone: BASIS_FORMAT.cTFRGBA4444\n\t\t};\n\t\tconst PIXEL_FORMAT = {\n\t\t\t\tETC1: 21,\n\t\t\t\tETC2_RGB: 22,\n\t\t\t\tETC2_RGBA: 23,\n\t\t\t\tDXT1: 8,\n\t\t\t\tDXT5: 10,\n\t\t\t\tPVRTC_4BPP_RGB_1: 26,\n\t\t\t\tPVRTC_4BPP_RGBA_1: 27,\n\t\t\t\tASTC_4x4: 28,\n\t\t\t\tATC_RGB: 29,\n\t\t\t\tATC_RGBA: 30,\n\t\t\t\tR8_G8_B8_A8: 7,\n\t\t\t\tR5_G6_B5: 3,\n\t\t\t\tR4_G4_B4_A4: 5\n\t\t};\n\t\tconst basisToEngineMapping = (basisFormat, deviceDetails)=>{\n\t\t\t\tswitch(basisFormat){\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFETC1:\n\t\t\t\t\t\t\t\treturn deviceDetails.formats.etc2 ? PIXEL_FORMAT.ETC2_RGB : PIXEL_FORMAT.ETC1;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFETC2:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.ETC2_RGBA;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFBC1:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.DXT1;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFBC3:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.DXT5;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFPVRTC1_4_RGB:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.PVRTC_4BPP_RGB_1;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFPVRTC1_4_RGBA:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.PVRTC_4BPP_RGBA_1;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFASTC_4x4:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.ASTC_4x4;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFATC_RGB:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.ATC_RGB;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.ATC_RGBA;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFRGBA32:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.R8_G8_B8_A8;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFRGB565:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.R5_G6_B5;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFRGBA4444:\n\t\t\t\t\t\t\t\treturn PIXEL_FORMAT.R4_G4_B4_A4;\n\t\t\t\t}\n\t\t};\n\t\tconst unswizzleGGGR = (data)=>{\n\t\t\t\tconst genB = function(R, G) {\n\t\t\t\t\t\tconst r = R * (2.0 / 255.0) - 1.0;\n\t\t\t\t\t\tconst g = G * (2.0 / 255.0) - 1.0;\n\t\t\t\t\t\tconst b = Math.sqrt(1.0 - Math.min(1.0, r * r + g * g));\n\t\t\t\t\t\treturn Math.max(0, Math.min(255, Math.floor((b + 1.0) * 0.5 * 255.0)));\n\t\t\t\t};\n\t\t\t\tfor(let offset = 0; offset < data.length; offset += 4){\n\t\t\t\t\t\tconst R = data[offset + 3];\n\t\t\t\t\t\tconst G = data[offset + 1];\n\t\t\t\t\t\tdata[offset + 0] = R;\n\t\t\t\t\t\tdata[offset + 2] = genB(R, G);\n\t\t\t\t\t\tdata[offset + 3] = 255;\n\t\t\t\t}\n\t\t\t\treturn data;\n\t\t};\n\t\tconst pack565 = (data)=>{\n\t\t\t\tconst result = new Uint16Array(data.length / 4);\n\t\t\t\tfor(let offset = 0; offset < data.length; offset += 4){\n\t\t\t\t\t\tconst R = data[offset + 0];\n\t\t\t\t\t\tconst G = data[offset + 1];\n\t\t\t\t\t\tconst B = data[offset + 2];\n\t\t\t\t\t\tresult[offset / 4] = (R & 0xf8) << 8 | (G & 0xfc) << 3 | B >> 3;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t};\n\t\tconst isPOT = (width, height)=>{\n\t\t\t\treturn (width & width - 1) === 0 && (height & height - 1) === 0;\n\t\t};\n\t\tconst performanceNow = ()=>{\n\t\t\t\treturn typeof performance !== 'undefined' ? performance.now() : 0;\n\t\t};\n\t\tlet basis;\n\t\tlet rgbPriority;\n\t\tlet rgbaPriority;\n\t\tconst chooseTargetFormat = (deviceDetails, hasAlpha, isUASTC)=>{\n\t\t\t\tif (isUASTC) {\n\t\t\t\t\t\tif (deviceDetails.formats.astc) {\n\t\t\t\t\t\t\t\treturn 'astc';\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (hasAlpha) {\n\t\t\t\t\t\t\t\tif (deviceDetails.formats.etc2) {\n\t\t\t\t\t\t\t\t\t\treturn 'etc2';\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (deviceDetails.formats.etc2) {\n\t\t\t\t\t\t\t\t\t\treturn 'etc2';\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (deviceDetails.formats.etc1) {\n\t\t\t\t\t\t\t\t\t\treturn 'etc1';\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst testInOrder = (priority)=>{\n\t\t\t\t\t\tfor(let i = 0; i < priority.length; ++i){\n\t\t\t\t\t\t\t\tconst format = priority[i];\n\t\t\t\t\t\t\t\tif (deviceDetails.formats[format]) {\n\t\t\t\t\t\t\t\t\t\treturn format;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn 'none';\n\t\t\t\t};\n\t\t\t\treturn testInOrder(hasAlpha ? rgbaPriority : rgbPriority);\n\t\t};\n\t\tconst dimensionsValid = (width, height, format)=>{\n\t\t\t\tswitch(format){\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFETC1:\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFETC2:\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFBC1:\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFBC3:\n\t\t\t\t\t\t\t\treturn (width & 0x3) === 0 && (height & 0x3) === 0;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFPVRTC1_4_RGB:\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFPVRTC1_4_RGBA:\n\t\t\t\t\t\t\t\treturn isPOT(width, height);\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFASTC_4x4:\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFATC_RGB:\n\t\t\t\t\t\tcase BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA:\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t};\n\t\tconst transcodeKTX2 = (url, data, options)=>{\n\t\t\t\tif (!basis.KTX2File) {\n\t\t\t\t\t\tthrow new Error('Basis transcoder module does not include support for KTX2.');\n\t\t\t\t}\n\t\t\t\tconst funcStart = performanceNow();\n\t\t\t\tconst basisFile = new basis.KTX2File(new Uint8Array(data));\n\t\t\t\tconst width = basisFile.getWidth();\n\t\t\t\tconst height = basisFile.getHeight();\n\t\t\t\tconst levels = basisFile.getLevels();\n\t\t\t\tconst hasAlpha = !!basisFile.getHasAlpha();\n\t\t\t\tconst isUASTC = basisFile.isUASTC && basisFile.isUASTC();\n\t\t\t\tif (!width || !height || !levels) {\n\t\t\t\t\t\tbasisFile.close();\n\t\t\t\t\t\tbasisFile.delete();\n\t\t\t\t\t\tthrow new Error(`Invalid image dimensions url=${url} width=${width} height=${height} levels=${levels}`);\n\t\t\t\t}\n\t\t\t\tconst format = chooseTargetFormat(options.deviceDetails, hasAlpha, isUASTC);\n\t\t\t\tconst unswizzle = !!options.isGGGR && format === 'pvr';\n\t\t\t\tlet basisFormat;\n\t\t\t\tif (unswizzle) {\n\t\t\t\t\t\tbasisFormat = BASIS_FORMAT.cTFRGBA32;\n\t\t\t\t} else {\n\t\t\t\t\t\tbasisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format];\n\t\t\t\t\t\tif (!dimensionsValid(width, height, basisFormat)) {\n\t\t\t\t\t\t\t\tbasisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!basisFile.startTranscoding()) {\n\t\t\t\t\t\tbasisFile.close();\n\t\t\t\t\t\tbasisFile.delete();\n\t\t\t\t\t\tthrow new Error(`Failed to start transcoding url=${url}`);\n\t\t\t\t}\n\t\t\t\tlet i;\n\t\t\t\tconst levelData = [];\n\t\t\t\tfor(let mip = 0; mip < levels; ++mip){\n\t\t\t\t\t\tconst dstSize = basisFile.getImageTranscodedSizeInBytes(mip, 0, 0, basisFormat);\n\t\t\t\t\t\tconst dst = new Uint8Array(dstSize);\n\t\t\t\t\t\tif (!basisFile.transcodeImage(dst, mip, 0, 0, basisFormat, 0, -1, -1)) {\n\t\t\t\t\t\t\t\tbasisFile.close();\n\t\t\t\t\t\t\t\tbasisFile.delete();\n\t\t\t\t\t\t\t\tthrow new Error(`Failed to transcode image url=${url}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst is16BitFormat = basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444;\n\t\t\t\t\t\tlevelData.push(is16BitFormat ? new Uint16Array(dst.buffer) : dst);\n\t\t\t\t}\n\t\t\t\tbasisFile.close();\n\t\t\t\tbasisFile.delete();\n\t\t\t\tif (unswizzle) {\n\t\t\t\t\t\tbasisFormat = BASIS_FORMAT.cTFRGB565;\n\t\t\t\t\t\tfor(i = 0; i < levelData.length; ++i){\n\t\t\t\t\t\t\t\tlevelData[i] = pack565(unswizzleGGGR(levelData[i]));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tformat: basisToEngineMapping(basisFormat, options.deviceDetails),\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tlevels: levelData,\n\t\t\t\t\t\tcubemap: false,\n\t\t\t\t\t\ttranscodeTime: performanceNow() - funcStart,\n\t\t\t\t\t\turl: url,\n\t\t\t\t\t\tunswizzledGGGR: unswizzle\n\t\t\t\t};\n\t\t};\n\t\tconst transcodeBasis = (url, data, options)=>{\n\t\t\t\tconst funcStart = performanceNow();\n\t\t\t\tconst basisFile = new basis.BasisFile(new Uint8Array(data));\n\t\t\t\tconst width = basisFile.getImageWidth(0, 0);\n\t\t\t\tconst height = basisFile.getImageHeight(0, 0);\n\t\t\t\tconst images = basisFile.getNumImages();\n\t\t\t\tconst levels = basisFile.getNumLevels(0);\n\t\t\t\tconst hasAlpha = !!basisFile.getHasAlpha();\n\t\t\t\tconst isUASTC = basisFile.isUASTC && basisFile.isUASTC();\n\t\t\t\tif (!width || !height || !images || !levels) {\n\t\t\t\t\t\tbasisFile.close();\n\t\t\t\t\t\tbasisFile.delete();\n\t\t\t\t\t\tthrow new Error(`Invalid image dimensions url=${url} width=${width} height=${height} images=${images} levels=${levels}`);\n\t\t\t\t}\n\t\t\t\tconst format = chooseTargetFormat(options.deviceDetails, hasAlpha, isUASTC);\n\t\t\t\tconst unswizzle = !!options.isGGGR && format === 'pvr';\n\t\t\t\tlet basisFormat;\n\t\t\t\tif (unswizzle) {\n\t\t\t\t\t\tbasisFormat = BASIS_FORMAT.cTFRGBA32;\n\t\t\t\t} else {\n\t\t\t\t\t\tbasisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format];\n\t\t\t\t\t\tif (!dimensionsValid(width, height, basisFormat)) {\n\t\t\t\t\t\t\t\tbasisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!basisFile.startTranscoding()) {\n\t\t\t\t\t\tbasisFile.close();\n\t\t\t\t\t\tbasisFile.delete();\n\t\t\t\t\t\tthrow new Error(`Failed to start transcoding url=${url}`);\n\t\t\t\t}\n\t\t\t\tlet i;\n\t\t\t\tconst levelData = [];\n\t\t\t\tfor(let mip = 0; mip < levels; ++mip){\n\t\t\t\t\t\tconst dstSize = basisFile.getImageTranscodedSizeInBytes(0, mip, basisFormat);\n\t\t\t\t\t\tconst dst = new Uint8Array(dstSize);\n\t\t\t\t\t\tif (!basisFile.transcodeImage(dst, 0, mip, basisFormat, 0, 0)) {\n\t\t\t\t\t\t\t\tif (mip === levels - 1 && dstSize === levelData[mip - 1].buffer.byteLength) {\n\t\t\t\t\t\t\t\t\t\tdst.set(new Uint8Array(levelData[mip - 1].buffer));\n\t\t\t\t\t\t\t\t\t\tconsole.warn(`Failed to transcode last mipmap level, using previous level instead url=${url}`);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tbasisFile.close();\n\t\t\t\t\t\t\t\t\t\tbasisFile.delete();\n\t\t\t\t\t\t\t\t\t\tthrow new Error(`Failed to transcode image url=${url}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst is16BitFormat = basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444;\n\t\t\t\t\t\tlevelData.push(is16BitFormat ? new Uint16Array(dst.buffer) : dst);\n\t\t\t\t}\n\t\t\t\tbasisFile.close();\n\t\t\t\tbasisFile.delete();\n\t\t\t\tif (unswizzle) {\n\t\t\t\t\t\tbasisFormat = BASIS_FORMAT.cTFRGB565;\n\t\t\t\t\t\tfor(i = 0; i < levelData.length; ++i){\n\t\t\t\t\t\t\t\tlevelData[i] = pack565(unswizzleGGGR(levelData[i]));\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tformat: basisToEngineMapping(basisFormat, options.deviceDetails),\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tlevels: levelData,\n\t\t\t\t\t\tcubemap: false,\n\t\t\t\t\t\ttranscodeTime: performanceNow() - funcStart,\n\t\t\t\t\t\turl: url,\n\t\t\t\t\t\tunswizzledGGGR: unswizzle\n\t\t\t\t};\n\t\t};\n\t\tconst transcode = (url, data, options)=>{\n\t\t\t\treturn options.isKTX2 ? transcodeKTX2(url, data, options) : transcodeBasis(url, data, options);\n\t\t};\n\t\tconst workerTranscode = (url, data, options)=>{\n\t\t\t\ttry {\n\t\t\t\t\t\tconst result = transcode(url, data, options);\n\t\t\t\t\t\tresult.levels = result.levels.map((v)=>v.buffer);\n\t\t\t\t\t\tself.postMessage({\n\t\t\t\t\t\t\t\turl: url,\n\t\t\t\t\t\t\t\tdata: result\n\t\t\t\t\t\t}, result.levels);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t\tself.postMessage({\n\t\t\t\t\t\t\t\turl: url,\n\t\t\t\t\t\t\t\terr: err\n\t\t\t\t\t\t}, null);\n\t\t\t\t}\n\t\t};\n\t\tconst workerInit = (config, callback)=>{\n\t\t\t\tconst instantiateWasmFunc = (imports, successCallback)=>{\n\t\t\t\t\t\tWebAssembly.instantiate(config.module, imports).then((result)=>{\n\t\t\t\t\t\t\t\tsuccessCallback(result);\n\t\t\t\t\t\t}).catch((reason)=>{\n\t\t\t\t\t\t\t\tconsole.error(`instantiate failed + ${reason}`);\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn {};\n\t\t\t\t};\n\t\t\t\tself.BASIS(config.module ? {\n\t\t\t\t\t\tinstantiateWasm: instantiateWasmFunc\n\t\t\t\t} : null).then((instance)=>{\n\t\t\t\t\t\tinstance.initializeBasis();\n\t\t\t\t\t\tbasis = instance;\n\t\t\t\t\t\trgbPriority = config.rgbPriority;\n\t\t\t\t\t\trgbaPriority = config.rgbaPriority;\n\t\t\t\t\t\tcallback(null);\n\t\t\t\t});\n\t\t};\n\t\tconst queue = [];\n\t\tself.onmessage = (message)=>{\n\t\t\t\tconst data = message.data;\n\t\t\t\tswitch(data.type){\n\t\t\t\t\t\tcase 'init':\n\t\t\t\t\t\t\t\tworkerInit(data.config, ()=>{\n\t\t\t\t\t\t\t\t\t\tfor(let i = 0; i < queue.length; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\tworkerTranscode(queue[i].url, queue[i].data, queue[i].options);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tqueue.length = 0;\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'transcode':\n\t\t\t\t\t\t\t\tif (basis) {\n\t\t\t\t\t\t\t\t\t\tworkerTranscode(data.url, data.data, data.options);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tqueue.push(data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t};\n}\n\nconst getCompressionFormats = (device)=>{\n\t\treturn {\n\t\t\t\tastc: !!device.extCompressedTextureASTC,\n\t\t\t\tatc: !!device.extCompressedTextureATC,\n\t\t\t\tdxt: !!device.extCompressedTextureS3TC,\n\t\t\t\tetc1: !!device.extCompressedTextureETC1,\n\t\t\t\tetc2: !!device.extCompressedTextureETC,\n\t\t\t\tpvr: !!device.extCompressedTexturePVRTC\n\t\t};\n};\nconst prepareWorkerModules = (config, callback)=>{\n\t\tconst getWorkerBlob = (basisCode)=>{\n\t\t\t\tconst code = [\n\t\t\t\t\t\t'/* basis */',\n\t\t\t\t\t\tbasisCode,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t`(${BasisWorker.toString()})()\\n\\n`\n\t\t\t\t].join('\\n');\n\t\t\t\treturn new Blob([\n\t\t\t\t\t\tcode\n\t\t\t\t], {\n\t\t\t\t\t\ttype: \"application/javascript\"\n\t\t\t\t});\n\t\t};\n\t\tconst wasmSupported = ()=>{\n\t\t\t\ttry {\n\t\t\t\t\t\tif (typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function') {\n\t\t\t\t\t\t\t\tconst module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));\n\t\t\t\t\t\t\t\tif (module instanceof WebAssembly.Module) {\n\t\t\t\t\t\t\t\t\t\treturn new WebAssembly.Instance(module) instanceof WebAssembly.Instance;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t\treturn false;\n\t\t};\n\t\tconst sendResponse = (basisCode, module)=>{\n\t\t\t\tcallback(null, {\n\t\t\t\t\t\tworkerUrl: URL.createObjectURL(getWorkerBlob(basisCode)),\n\t\t\t\t\t\tmodule: module,\n\t\t\t\t\t\trgbPriority: config.rgbPriority,\n\t\t\t\t\t\trgbaPriority: config.rgbaPriority\n\t\t\t\t});\n\t\t};\n\t\tconst options = {\n\t\t\t\tcache: true,\n\t\t\t\tresponseType: 'text',\n\t\t\t\tretry: config.maxRetries > 0,\n\t\t\t\tmaxRetries: config.maxRetries\n\t\t};\n\t\tif (config.glueUrl && config.wasmUrl && wasmSupported()) {\n\t\t\t\tlet basisCode = null;\n\t\t\t\tlet module = null;\n\t\t\t\thttp.get(config.glueUrl, options, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (module) {\n\t\t\t\t\t\t\t\t\t\tsendResponse(response, module);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tbasisCode = response;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst fetchPromise = fetch(config.wasmUrl);\n\t\t\t\tconst compileManual = ()=>{\n\t\t\t\t\t\tfetchPromise.then((result)=>result.arrayBuffer()).then((buffer)=>WebAssembly.compile(buffer)).then((module_)=>{\n\t\t\t\t\t\t\t\tif (basisCode) {\n\t\t\t\t\t\t\t\t\t\tsendResponse(basisCode, module_);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmodule = module_;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\t\t\tcallback(err, null);\n\t\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tif (WebAssembly.compileStreaming) {\n\t\t\t\t\t\tWebAssembly.compileStreaming(fetchPromise).then((module_)=>{\n\t\t\t\t\t\t\t\tif (basisCode) {\n\t\t\t\t\t\t\t\t\t\tsendResponse(basisCode, module_);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmodule = module_;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\t\t\tcompileManual();\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tcompileManual();\n\t\t\t\t}\n\t\t} else {\n\t\t\t\thttp.get(config.fallbackUrl, options, (err, response)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err, null);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsendResponse(response, null);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n};\nclass BasisQueue {\n\t\tconstructor(){\n\t\t\t\tthis.callbacks = {};\n\t\t\t\tthis.queue = [];\n\t\t\t\tthis.clients = [];\n\t\t}\n\t\tenqueueJob(url, data, callback, options) {\n\t\t\t\tif (this.callbacks.hasOwnProperty(url)) {\n\t\t\t\t\t\tthis.callbacks[url].push(callback);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.callbacks[url] = [\n\t\t\t\t\t\t\t\tcallback\n\t\t\t\t\t\t];\n\t\t\t\t\t\tconst job = {\n\t\t\t\t\t\t\t\turl: url,\n\t\t\t\t\t\t\t\tdata: data,\n\t\t\t\t\t\t\t\toptions: options\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (this.clients.length > 0) {\n\t\t\t\t\t\t\t\tthis.clients.shift().run(job);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.queue.push(job);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tenqueueClient(client) {\n\t\t\t\tif (this.queue.length > 0) {\n\t\t\t\t\t\tclient.run(this.queue.shift());\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.clients.push(client);\n\t\t\t\t}\n\t\t}\n\t\thandleResponse(url, err, data) {\n\t\t\t\tconst callback = this.callbacks[url];\n\t\t\t\tif (err) {\n\t\t\t\t\t\tfor(let i = 0; i < callback.length; ++i){\n\t\t\t\t\t\t\t\tcallback[i](err);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (data.format === PIXELFORMAT_RGB565 || data.format === PIXELFORMAT_RGBA4) {\n\t\t\t\t\t\t\t\tdata.levels = data.levels.map((v)=>{\n\t\t\t\t\t\t\t\t\t\treturn new Uint16Array(v);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdata.levels = data.levels.map((v)=>{\n\t\t\t\t\t\t\t\t\t\treturn new Uint8Array(v);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(let i = 0; i < callback.length; ++i){\n\t\t\t\t\t\t\t\tcallback[i](null, data);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete this.callbacks[url];\n\t\t}\n}\nclass BasisClient {\n\t\tconstructor(queue, config, eager){\n\t\t\t\tthis.queue = queue;\n\t\t\t\tthis.worker = new Worker(config.workerUrl);\n\t\t\t\tthis.worker.addEventListener('message', (message)=>{\n\t\t\t\t\t\tconst data = message.data;\n\t\t\t\t\t\tthis.queue.handleResponse(data.url, data.err, data.data);\n\t\t\t\t\t\tif (!this.eager) {\n\t\t\t\t\t\t\t\tthis.queue.enqueueClient(this);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\ttype: 'init',\n\t\t\t\t\t\tconfig: config\n\t\t\t\t});\n\t\t\t\tthis.eager = eager;\n\t\t}\n\t\trun(job) {\n\t\t\t\tconst transfer = [];\n\t\t\t\tif (job.data instanceof ArrayBuffer) {\n\t\t\t\t\t\ttransfer.push(job.data);\n\t\t\t\t}\n\t\t\t\tthis.worker.postMessage({\n\t\t\t\t\t\ttype: 'transcode',\n\t\t\t\t\t\turl: job.url,\n\t\t\t\t\t\tformat: job.format,\n\t\t\t\t\t\tdata: job.data,\n\t\t\t\t\t\toptions: job.options\n\t\t\t\t}, transfer);\n\t\t\t\tif (this.eager) {\n\t\t\t\t\t\tthis.queue.enqueueClient(this);\n\t\t\t\t}\n\t\t}\n}\nconst defaultNumWorkers = 1;\nconst defaultRgbPriority = [\n\t\t'etc2',\n\t\t'etc1',\n\t\t'astc',\n\t\t'dxt',\n\t\t'pvr',\n\t\t'atc'\n];\nconst defaultRgbaPriority = [\n\t\t'astc',\n\t\t'dxt',\n\t\t'etc2',\n\t\t'pvr',\n\t\t'atc'\n];\nconst defaultMaxRetries = 5;\nconst queue = new BasisQueue();\nlet lazyConfig = null;\nlet initializing = false;\nfunction basisInitialize(config) {\n\t\tif (initializing) {\n\t\t\t\treturn;\n\t\t}\n\t\tif (!config) {\n\t\t\t\tconfig = lazyConfig || {};\n\t\t} else if (config.lazyInit) {\n\t\t\t\tlazyConfig = config;\n\t\t\t\treturn;\n\t\t}\n\t\tif (!config.glueUrl || !config.wasmUrl || !config.fallbackUrl) {\n\t\t\t\tconst moduleConfig = WasmModule.getConfig('BASIS');\n\t\t\t\tif (moduleConfig) {\n\t\t\t\t\t\tconfig = {\n\t\t\t\t\t\t\t\tglueUrl: moduleConfig.glueUrl,\n\t\t\t\t\t\t\t\twasmUrl: moduleConfig.wasmUrl,\n\t\t\t\t\t\t\t\tfallbackUrl: moduleConfig.fallbackUrl,\n\t\t\t\t\t\t\t\tnumWorkers: moduleConfig.numWorkers\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t}\n\t\tif (config.glueUrl || config.wasmUrl || config.fallbackUrl) {\n\t\t\t\tinitializing = true;\n\t\t\t\tconst numWorkers = Math.max(1, Math.min(16, config.numWorkers || defaultNumWorkers));\n\t\t\t\tconst eagerWorkers = config.numWorkers === 1 || (config.hasOwnProperty('eagerWorkers') ? config.eagerWorkers : true);\n\t\t\t\tconfig.rgbPriority = config.rgbPriority || defaultRgbPriority;\n\t\t\t\tconfig.rgbaPriority = config.rgbaPriority || defaultRgbaPriority;\n\t\t\t\tconfig.maxRetries = config.hasOwnProperty('maxRetries') ? config.maxRetries : defaultMaxRetries;\n\t\t\t\tprepareWorkerModules(config, (err, clientConfig)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tconsole.error(`failed to initialize basis worker: ${err}`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor(let i = 0; i < numWorkers; ++i){\n\t\t\t\t\t\t\t\t\t\tqueue.enqueueClient(new BasisClient(queue, clientConfig, eagerWorkers));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n}\nlet deviceDetails = null;\nfunction basisTranscode(device, url, data, callback, options) {\n\t\tbasisInitialize();\n\t\tif (!deviceDetails) {\n\t\t\t\tdeviceDetails = {\n\t\t\t\t\t\tformats: getCompressionFormats(device)\n\t\t\t\t};\n\t\t}\n\t\tqueue.enqueueJob(url, data, callback, {\n\t\t\t\tdeviceDetails: deviceDetails,\n\t\t\t\tisGGGR: !!options?.isGGGR,\n\t\t\t\tisKTX2: !!options?.isKTX2\n\t\t});\n\t\treturn initializing;\n}\n\nclass TextureParser {\n\t\tload(url, callback, asset) {\n\t\t\t\tthrow new Error('not implemented');\n\t\t}\n\t\topen(url, data, device) {\n\t\t\t\tthrow new Error('not implemented');\n\t\t}\n}\n\nclass BasisParser extends TextureParser {\n\t\tconstructor(registry, device){\n\t\t\t\tsuper();\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.maxRetries = 0;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst transcode = (data)=>{\n\t\t\t\t\t\tconst basisModuleFound = basisTranscode(device, url.load, data, callback, {\n\t\t\t\t\t\t\t\tisGGGR: (asset?.file?.variants?.basis?.opt & 8) !== 0\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!basisModuleFound) {\n\t\t\t\t\t\t\t\tcallback(`Basis module not found. Asset [${asset.name}](${asset.getFileUrl()}) basis texture variant will not be loaded.`);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tAsset.fetchArrayBuffer(url.load, (err, result)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttranscode(result);\n\t\t\t\t\t\t}\n\t\t\t\t}, asset, this.maxRetries);\n\t\t}\n\t\topen(url, data, device, textureOptions = {}) {\n\t\t\t\tconst format = textureOptions.srgb ? pixelFormatLinearToGamma(data.format) : data.format;\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: url,\n\t\t\t\t\t\taddressU: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\taddressV: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\twidth: data.width,\n\t\t\t\t\t\theight: data.height,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: data.cubemap,\n\t\t\t\t\t\tlevels: data.levels,\n\t\t\t\t\t\t...textureOptions\n\t\t\t\t});\n\t\t\t\ttexture.upload();\n\t\t\t\treturn texture;\n\t\t}\n}\n\nclass ImgParser extends TextureParser {\n\t\tconstructor(registry, device){\n\t\t\t\tsuper();\n\t\t\t\tthis.crossOrigin = registry.prefix ? 'anonymous' : null;\n\t\t\t\tthis.maxRetries = 0;\n\t\t\t\tthis.device = device;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tconst hasContents = !!asset?.file?.contents;\n\t\t\t\tif (hasContents) {\n\t\t\t\t\t\tif (this.device.supportsImageBitmap) {\n\t\t\t\t\t\t\t\tthis._loadImageBitmapFromBlob(new Blob([\n\t\t\t\t\t\t\t\t\t\tasset.file.contents\n\t\t\t\t\t\t\t\t]), callback);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: URL.createObjectURL(new Blob([\n\t\t\t\t\t\t\t\t\t\tasset.file.contents\n\t\t\t\t\t\t\t\t])),\n\t\t\t\t\t\t\t\toriginal: url.original\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst handler = (err, result)=>{\n\t\t\t\t\t\tif (hasContents) {\n\t\t\t\t\t\t\t\tURL.revokeObjectURL(url.load);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcallback(err, result);\n\t\t\t\t};\n\t\t\t\tlet crossOrigin;\n\t\t\t\tif (asset && asset.options && asset.options.hasOwnProperty('crossOrigin')) {\n\t\t\t\t\t\tcrossOrigin = asset.options.crossOrigin;\n\t\t\t\t} else if (ABSOLUTE_URL.test(url.load)) {\n\t\t\t\t\t\tcrossOrigin = this.crossOrigin;\n\t\t\t\t}\n\t\t\t\tif (this.device.supportsImageBitmap) {\n\t\t\t\t\t\tthis._loadImageBitmap(url.load, url.original, crossOrigin, handler, asset);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._loadImage(url.load, url.original, crossOrigin, handler, asset);\n\t\t\t\t}\n\t\t}\n\t\topen(url, data, device, textureOptions = {}) {\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: url,\n\t\t\t\t\t\twidth: data.width,\n\t\t\t\t\t\theight: data.height,\n\t\t\t\t\t\tformat: textureOptions.srgb ? PIXELFORMAT_SRGBA8 : PIXELFORMAT_RGBA8,\n\t\t\t\t\t\t...textureOptions\n\t\t\t\t});\n\t\t\t\ttexture.setSource(data);\n\t\t\t\treturn texture;\n\t\t}\n\t\t_loadImage(url, originalUrl, crossOrigin, callback, asset) {\n\t\t\t\tconst image = new Image();\n\t\t\t\tif (crossOrigin) {\n\t\t\t\t\t\timage.crossOrigin = crossOrigin;\n\t\t\t\t}\n\t\t\t\tlet retries = 0;\n\t\t\t\tconst maxRetries = this.maxRetries;\n\t\t\t\tlet retryTimeout;\n\t\t\t\tconst dummySize = 1024 * 1024;\n\t\t\t\tasset?.fire('progress', 0, dummySize);\n\t\t\t\timage.onload = function() {\n\t\t\t\t\t\tasset?.fire('progress', dummySize, dummySize);\n\t\t\t\t\t\tcallback(null, image);\n\t\t\t\t};\n\t\t\t\timage.onerror = function() {\n\t\t\t\t\t\tif (retryTimeout) return;\n\t\t\t\t\t\tif (maxRetries > 0 && ++retries <= maxRetries) {\n\t\t\t\t\t\t\t\tconst retryDelay = Math.pow(2, retries) * 100;\n\t\t\t\t\t\t\t\tconsole.log(`Error loading Texture from: '${originalUrl}' - Retrying in ${retryDelay}ms...`);\n\t\t\t\t\t\t\t\tconst idx = url.indexOf('?');\n\t\t\t\t\t\t\t\tconst separator = idx >= 0 ? '&' : '?';\n\t\t\t\t\t\t\t\tretryTimeout = setTimeout(()=>{\n\t\t\t\t\t\t\t\t\t\timage.src = `${url + separator}retry=${Date.now()}`;\n\t\t\t\t\t\t\t\t\t\tretryTimeout = null;\n\t\t\t\t\t\t\t\t}, retryDelay);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback(`Error loading Texture from: '${originalUrl}'`);\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\timage.src = url;\n\t\t}\n\t\t_loadImageBitmap(url, originalUrl, crossOrigin, callback, asset) {\n\t\t\t\tconst options = {\n\t\t\t\t\t\tcache: true,\n\t\t\t\t\t\tresponseType: 'blob',\n\t\t\t\t\t\tretry: this.maxRetries > 0,\n\t\t\t\t\t\tmaxRetries: this.maxRetries,\n\t\t\t\t\t\tprogress: asset\n\t\t\t\t};\n\t\t\t\thttp.get(url, options, (err, blob)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._loadImageBitmapFromBlob(blob, callback);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\t_loadImageBitmapFromBlob(blob, callback) {\n\t\t\t\tcreateImageBitmap(blob, {\n\t\t\t\t\t\tpremultiplyAlpha: 'none',\n\t\t\t\t\t\tcolorSpaceConversion: 'none'\n\t\t\t\t}).then((imageBitmap)=>callback(null, imageBitmap)).catch((e)=>callback(e));\n\t\t}\n}\n\nconst IDENTIFIER = [\n\t\t0x58544BAB,\n\t\t0xBB313120,\n\t\t0x0A1A0A0D\n];\nconst KNOWN_FORMATS = {\n\t\t0x83F0: PIXELFORMAT_DXT1,\n\t\t0x83F2: PIXELFORMAT_DXT3,\n\t\t0x83F3: PIXELFORMAT_DXT5,\n\t\t0x8D64: PIXELFORMAT_ETC1,\n\t\t0x9274: PIXELFORMAT_ETC2_RGB,\n\t\t0x9278: PIXELFORMAT_ETC2_RGBA,\n\t\t0x8C00: PIXELFORMAT_PVRTC_4BPP_RGB_1,\n\t\t0x8C01: PIXELFORMAT_PVRTC_2BPP_RGB_1,\n\t\t0x8C02: PIXELFORMAT_PVRTC_4BPP_RGBA_1,\n\t\t0x8C03: PIXELFORMAT_PVRTC_2BPP_RGBA_1,\n\t\t0x8051: PIXELFORMAT_RGB8,\n\t\t0x8058: PIXELFORMAT_RGBA8,\n\t\t0x8C41: PIXELFORMAT_SRGB8,\n\t\t0x8C43: PIXELFORMAT_SRGBA8,\n\t\t0x8C3A: PIXELFORMAT_111110F,\n\t\t0x881B: PIXELFORMAT_RGB16F,\n\t\t0x881A: PIXELFORMAT_RGBA16F\n};\nfunction createContainer(pixelFormat, buffer, byteOffset, byteSize) {\n\t\treturn pixelFormat === PIXELFORMAT_111110F ? new Uint32Array(buffer, byteOffset, byteSize / 4) : new Uint8Array(buffer, byteOffset, byteSize);\n}\nclass KtxParser extends TextureParser {\n\t\tconstructor(registry){\n\t\t\t\tsuper();\n\t\t\t\tthis.maxRetries = 0;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tAsset.fetchArrayBuffer(url.load, callback, asset, this.maxRetries);\n\t\t}\n\t\topen(url, data, device, textureOptions = {}) {\n\t\t\t\tconst textureData = this.parse(data);\n\t\t\t\tif (!textureData) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst format = textureOptions.srgb ? pixelFormatLinearToGamma(textureData.format) : textureData.format;\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: url,\n\t\t\t\t\t\taddressU: textureData.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\taddressV: textureData.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\twidth: textureData.width,\n\t\t\t\t\t\theight: textureData.height,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: textureData.cubemap,\n\t\t\t\t\t\tlevels: textureData.levels,\n\t\t\t\t\t\t...textureOptions\n\t\t\t\t});\n\t\t\t\ttexture.upload();\n\t\t\t\treturn texture;\n\t\t}\n\t\tparse(data) {\n\t\t\t\tconst dataU32 = new Uint32Array(data);\n\t\t\t\tif (IDENTIFIER[0] !== dataU32[0] || IDENTIFIER[1] !== dataU32[1] || IDENTIFIER[2] !== dataU32[2]) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst header = {\n\t\t\t\t\t\tendianness: dataU32[3],\n\t\t\t\t\t\tglType: dataU32[4],\n\t\t\t\t\t\tglTypeSize: dataU32[5],\n\t\t\t\t\t\tglFormat: dataU32[6],\n\t\t\t\t\t\tglInternalFormat: dataU32[7],\n\t\t\t\t\t\tglBaseInternalFormat: dataU32[8],\n\t\t\t\t\t\tpixelWidth: dataU32[9],\n\t\t\t\t\t\tpixelHeight: dataU32[10],\n\t\t\t\t\t\tpixelDepth: dataU32[11],\n\t\t\t\t\t\tnumberOfArrayElements: dataU32[12],\n\t\t\t\t\t\tnumberOfFaces: dataU32[13],\n\t\t\t\t\t\tnumberOfMipmapLevels: dataU32[14],\n\t\t\t\t\t\tbytesOfKeyValueData: dataU32[15]\n\t\t\t\t};\n\t\t\t\tif (header.pixelDepth > 1) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tif (header.numberOfArrayElements !== 0) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst format = KNOWN_FORMATS[header.glInternalFormat];\n\t\t\t\tif (format === undefined) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tlet offset = 16 + header.bytesOfKeyValueData / 4;\n\t\t\t\tconst isCubemap = header.numberOfFaces > 1;\n\t\t\t\tconst levels = [];\n\t\t\t\tfor(let mipmapLevel = 0; mipmapLevel < (header.numberOfMipmapLevels || 1); mipmapLevel++){\n\t\t\t\t\t\tconst imageSizeInBytes = dataU32[offset++];\n\t\t\t\t\t\tif (isCubemap) {\n\t\t\t\t\t\t\t\tlevels.push([]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst target = isCubemap ? levels[mipmapLevel] : levels;\n\t\t\t\t\t\tfor(let face = 0; face < (isCubemap ? 6 : 1); ++face){\n\t\t\t\t\t\t\t\ttarget.push(createContainer(format, data, offset * 4, imageSizeInBytes));\n\t\t\t\t\t\t\t\toffset += imageSizeInBytes + 3 >> 2;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\twidth: header.pixelWidth,\n\t\t\t\t\t\theight: header.pixelHeight,\n\t\t\t\t\t\tlevels: levels,\n\t\t\t\t\t\tcubemap: isCubemap\n\t\t\t\t};\n\t\t}\n}\n\nconst KHRConstants = {\n\t\tKHR_DF_MODEL_UASTC: 166\n};\nclass Ktx2Parser extends TextureParser {\n\t\tconstructor(registry, device){\n\t\t\t\tsuper();\n\t\t\t\tthis.maxRetries = 0;\n\t\t\t\tthis.device = device;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tAsset.fetchArrayBuffer(url.load, (err, result)=>{\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tcallback(err, result);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.parse(result, url, callback, asset);\n\t\t\t\t\t\t}\n\t\t\t\t}, asset, this.maxRetries);\n\t\t}\n\t\topen(url, data, device, textureOptions = {}) {\n\t\t\t\tconst format = textureOptions.srgb ? pixelFormatLinearToGamma(data.format) : data.format;\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: url,\n\t\t\t\t\t\taddressU: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\taddressV: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\twidth: data.width,\n\t\t\t\t\t\theight: data.height,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: data.cubemap,\n\t\t\t\t\t\tlevels: data.levels,\n\t\t\t\t\t\t...textureOptions\n\t\t\t\t});\n\t\t\t\ttexture.upload();\n\t\t\t\treturn texture;\n\t\t}\n\t\tparse(arraybuffer, url, callback, asset) {\n\t\t\t\tconst rs = new ReadStream(arraybuffer);\n\t\t\t\tconst magic = [\n\t\t\t\t\t\trs.readU32be(),\n\t\t\t\t\t\trs.readU32be(),\n\t\t\t\t\t\trs.readU32be()\n\t\t\t\t];\n\t\t\t\tif (magic[0] !== 0xAB4B5458 || magic[1] !== 0x203230BB || magic[2] !== 0x0D0A1A0A) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst header = {\n\t\t\t\t\t\tvkFormat: rs.readU32(),\n\t\t\t\t\t\ttypeSize: rs.readU32(),\n\t\t\t\t\t\tpixelWidth: rs.readU32(),\n\t\t\t\t\t\tpixelHeight: rs.readU32(),\n\t\t\t\t\t\tpixelDepth: rs.readU32(),\n\t\t\t\t\t\tlayerCount: rs.readU32(),\n\t\t\t\t\t\tfaceCount: rs.readU32(),\n\t\t\t\t\t\tlevelCount: rs.readU32(),\n\t\t\t\t\t\tsupercompressionScheme: rs.readU32()\n\t\t\t\t};\n\t\t\t\tconst index = {\n\t\t\t\t\t\tdfdByteOffset: rs.readU32(),\n\t\t\t\t\t\tdfdByteLength: rs.readU32(),\n\t\t\t\t\t\tkvdByteOffset: rs.readU32(),\n\t\t\t\t\t\tkvdByteLength: rs.readU32(),\n\t\t\t\t\t\tsgdByteOffset: rs.readU64(),\n\t\t\t\t\t\tsgdByteLength: rs.readU64()\n\t\t\t\t};\n\t\t\t\tconst levels = [];\n\t\t\t\tfor(let i = 0; i < Math.max(1, header.levelCount); ++i){\n\t\t\t\t\t\tlevels.push({\n\t\t\t\t\t\t\t\tbyteOffset: rs.readU64(),\n\t\t\t\t\t\t\t\tbyteLength: rs.readU64(),\n\t\t\t\t\t\t\t\tuncompressedByteLength: rs.readU64()\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst dfdTotalSize = rs.readU32();\n\t\t\t\tif (dfdTotalSize !== index.kvdByteOffset - index.dfdByteOffset) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\trs.skip(8);\n\t\t\t\tconst colorModel = rs.readU8();\n\t\t\t\trs.skip(index.dfdByteLength - 9);\n\t\t\t\trs.skip(index.kvdByteLength);\n\t\t\t\tif (header.supercompressionScheme === 1 || colorModel === KHRConstants.KHR_DF_MODEL_UASTC) {\n\t\t\t\t\t\tconst basisModuleFound = basisTranscode(this.device, url.load, arraybuffer, callback, {\n\t\t\t\t\t\t\t\tisGGGR: (asset?.file?.variants?.basis?.opt & 8) !== 0,\n\t\t\t\t\t\t\t\tisKTX2: true\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!basisModuleFound) {\n\t\t\t\t\t\t\t\tcallback(`Basis module not found. Asset [${asset.name}](${asset.getFileUrl()}) basis texture variant will not be loaded.`);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tcallback('unsupported KTX2 pixel format');\n\t\t\t\t}\n\t\t}\n}\n\nclass DdsParser extends TextureParser {\n\t\tconstructor(registry){\n\t\t\t\tsuper();\n\t\t\t\tthis.maxRetries = 0;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tAsset.fetchArrayBuffer(url.load, callback, asset, this.maxRetries);\n\t\t}\n\t\topen(url, data, device, textureOptions = {}) {\n\t\t\t\tconst header = new Uint32Array(data, 0, 128 / 4);\n\t\t\t\tconst width = header[4];\n\t\t\t\tconst height = header[3];\n\t\t\t\tconst mips = Math.max(header[7], 1);\n\t\t\t\tconst isFourCc = header[20] === 4;\n\t\t\t\tconst fcc = header[21];\n\t\t\t\tconst bpp = header[22];\n\t\t\t\tconst isCubemap = header[28] === 65024;\n\t\t\t\tconst FCC_DXT1 = 827611204;\n\t\t\t\tconst FCC_DXT5 = 894720068;\n\t\t\t\tconst FCC_FP16 = 113;\n\t\t\t\tconst FCC_FP32 = 116;\n\t\t\t\tconst FCC_ETC1 = 826496069;\n\t\t\t\tconst FCC_PVRTC_2BPP_RGB_1 = 825438800;\n\t\t\t\tconst FCC_PVRTC_2BPP_RGBA_1 = 825504336;\n\t\t\t\tconst FCC_PVRTC_4BPP_RGB_1 = 825439312;\n\t\t\t\tconst FCC_PVRTC_4BPP_RGBA_1 = 825504848;\n\t\t\t\tlet compressed = false;\n\t\t\t\tlet etc1 = false;\n\t\t\t\tlet pvrtc2 = false;\n\t\t\t\tlet pvrtc4 = false;\n\t\t\t\tlet format = null;\n\t\t\t\tlet componentSize = 1;\n\t\t\t\tlet texture;\n\t\t\t\tif (isFourCc) {\n\t\t\t\t\t\tif (fcc === FCC_DXT1) {\n\t\t\t\t\t\t\t\tformat = PIXELFORMAT_DXT1;\n\t\t\t\t\t\t\t\tcompressed = true;\n\t\t\t\t\t\t} else if (fcc === FCC_DXT5) {\n\t\t\t\t\t\t\t\tformat = PIXELFORMAT_DXT5;\n\t\t\t\t\t\t\t\tcompressed = true;\n\t\t\t\t\t\t} else if (fcc === FCC_FP16) {\n\t\t\t\t\t\t\t\tformat = PIXELFORMAT_RGBA16F;\n\t\t\t\t\t\t\t\tcomponentSize = 2;\n\t\t\t\t\t\t} else if (fcc === FCC_FP32) {\n\t\t\t\t\t\t\t\tformat = PIXELFORMAT_RGBA32F;\n\t\t\t\t\t\t\t\tcomponentSize = 4;\n\t\t\t\t\t\t} else if (fcc === FCC_ETC1) {\n\t\t\t\t\t\t\t\tformat = PIXELFORMAT_ETC1;\n\t\t\t\t\t\t\t\tcompressed = true;\n\t\t\t\t\t\t\t\tetc1 = true;\n\t\t\t\t\t\t} else if (fcc === FCC_PVRTC_2BPP_RGB_1 || fcc === FCC_PVRTC_2BPP_RGBA_1) {\n\t\t\t\t\t\t\t\tformat = fcc === FCC_PVRTC_2BPP_RGB_1 ? PIXELFORMAT_PVRTC_2BPP_RGB_1 : PIXELFORMAT_PVRTC_2BPP_RGBA_1;\n\t\t\t\t\t\t\t\tcompressed = true;\n\t\t\t\t\t\t\t\tpvrtc2 = true;\n\t\t\t\t\t\t} else if (fcc === FCC_PVRTC_4BPP_RGB_1 || fcc === FCC_PVRTC_4BPP_RGBA_1) {\n\t\t\t\t\t\t\t\tformat = fcc === FCC_PVRTC_4BPP_RGB_1 ? PIXELFORMAT_PVRTC_4BPP_RGB_1 : PIXELFORMAT_PVRTC_4BPP_RGBA_1;\n\t\t\t\t\t\t\t\tcompressed = true;\n\t\t\t\t\t\t\t\tpvrtc4 = true;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (bpp === 32) {\n\t\t\t\t\t\t\t\tformat = PIXELFORMAT_RGBA8;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!format) {\n\t\t\t\t\t\ttexture = new Texture(device, {\n\t\t\t\t\t\t\t\twidth: 4,\n\t\t\t\t\t\t\t\theight: 4,\n\t\t\t\t\t\t\t\tformat: PIXELFORMAT_RGB8,\n\t\t\t\t\t\t\t\tname: 'dds-legacy-empty'\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn texture;\n\t\t\t\t}\n\t\t\t\ttexture = new Texture(device, {\n\t\t\t\t\t\tname: url,\n\t\t\t\t\t\taddressU: isCubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\taddressV: isCubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\tcubemap: isCubemap,\n\t\t\t\t\t\tmipmaps: mips > 1,\n\t\t\t\t\t\t...textureOptions\n\t\t\t\t});\n\t\t\t\tlet offset = 128;\n\t\t\t\tconst faces = isCubemap ? 6 : 1;\n\t\t\t\tlet mipSize;\n\t\t\t\tconst DXT_BLOCK_WIDTH = 4;\n\t\t\t\tconst DXT_BLOCK_HEIGHT = 4;\n\t\t\t\tconst blockSize = fcc === FCC_DXT1 ? 8 : 16;\n\t\t\t\tlet numBlocksAcross, numBlocksDown, numBlocks;\n\t\t\t\tfor(let face = 0; face < faces; face++){\n\t\t\t\t\t\tlet mipWidth = width;\n\t\t\t\t\t\tlet mipHeight = height;\n\t\t\t\t\t\tfor(let i = 0; i < mips; i++){\n\t\t\t\t\t\t\t\tif (compressed) {\n\t\t\t\t\t\t\t\t\t\tif (etc1) {\n\t\t\t\t\t\t\t\t\t\t\t\tmipSize = Math.floor((mipWidth + 3) / 4) * Math.floor((mipHeight + 3) / 4) * 8;\n\t\t\t\t\t\t\t\t\t\t} else if (pvrtc2) {\n\t\t\t\t\t\t\t\t\t\t\t\tmipSize = Math.max(mipWidth, 16) * Math.max(mipHeight, 8) / 4;\n\t\t\t\t\t\t\t\t\t\t} else if (pvrtc4) {\n\t\t\t\t\t\t\t\t\t\t\t\tmipSize = Math.max(mipWidth, 8) * Math.max(mipHeight, 8) / 2;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tnumBlocksAcross = Math.floor((mipWidth + DXT_BLOCK_WIDTH - 1) / DXT_BLOCK_WIDTH);\n\t\t\t\t\t\t\t\t\t\t\t\tnumBlocksDown = Math.floor((mipHeight + DXT_BLOCK_HEIGHT - 1) / DXT_BLOCK_HEIGHT);\n\t\t\t\t\t\t\t\t\t\t\t\tnumBlocks = numBlocksAcross * numBlocksDown;\n\t\t\t\t\t\t\t\t\t\t\t\tmipSize = numBlocks * blockSize;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tmipSize = mipWidth * mipHeight * 4;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst mipBuff = format === PIXELFORMAT_RGBA32F ? new Float32Array(data, offset, mipSize) : format === PIXELFORMAT_RGBA16F ? new Uint16Array(data, offset, mipSize) : new Uint8Array(data, offset, mipSize);\n\t\t\t\t\t\t\t\tif (!isCubemap) {\n\t\t\t\t\t\t\t\t\t\ttexture._levels[i] = mipBuff;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!texture._levels[i]) texture._levels[i] = [];\n\t\t\t\t\t\t\t\t\t\ttexture._levels[i][face] = mipBuff;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\toffset += mipSize * componentSize;\n\t\t\t\t\t\t\t\tmipWidth = Math.max(mipWidth * 0.5, 1);\n\t\t\t\t\t\t\t\tmipHeight = Math.max(mipHeight * 0.5, 1);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttexture.upload();\n\t\t\t\treturn texture;\n\t\t}\n}\n\nclass HdrParser extends TextureParser {\n\t\tconstructor(registry){\n\t\t\t\tsuper();\n\t\t\t\tthis.maxRetries = 0;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tAsset.fetchArrayBuffer(url.load, callback, asset, this.maxRetries);\n\t\t\t\tif (asset.data && !asset.data.type) {\n\t\t\t\t\t\tasset.data.type = TEXTURETYPE_RGBE;\n\t\t\t\t}\n\t\t}\n\t\topen(url, data, device, textureOptions = {}) {\n\t\t\t\tconst textureData = this.parse(data);\n\t\t\t\tif (!textureData) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst texture = new Texture(device, {\n\t\t\t\t\t\tname: url,\n\t\t\t\t\t\taddressU: ADDRESS_REPEAT,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\twidth: textureData.width,\n\t\t\t\t\t\theight: textureData.height,\n\t\t\t\t\t\tlevels: textureData.levels,\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA8,\n\t\t\t\t\t\ttype: TEXTURETYPE_RGBE,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\t...textureOptions\n\t\t\t\t});\n\t\t\t\ttexture.upload();\n\t\t\t\treturn texture;\n\t\t}\n\t\tparse(data) {\n\t\t\t\tconst readStream = new ReadStream(data);\n\t\t\t\tconst magic = readStream.readLine();\n\t\t\t\tif (!magic.startsWith('#?RADIANCE')) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst variables = {};\n\t\t\t\twhile(true){\n\t\t\t\t\t\tconst line = readStream.readLine();\n\t\t\t\t\t\tif (line.length === 0) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst parts = line.split('=');\n\t\t\t\t\t\t\t\tif (parts.length === 2) {\n\t\t\t\t\t\t\t\t\t\tvariables[parts[0]] = parts[1];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!variables.hasOwnProperty('FORMAT')) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst resolution = readStream.readLine().split(' ');\n\t\t\t\tif (resolution.length !== 4) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst height = parseInt(resolution[1], 10);\n\t\t\t\tconst width = parseInt(resolution[3], 10);\n\t\t\t\tconst pixels = this._readPixels(readStream, width, height, resolution[0] === '-Y');\n\t\t\t\tif (!pixels) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tlevels: [\n\t\t\t\t\t\t\t\tpixels\n\t\t\t\t\t\t]\n\t\t\t\t};\n\t\t}\n\t\t_readPixels(readStream, width, height, flipY) {\n\t\t\t\tif (width < 8 || width > 0x7fff) {\n\t\t\t\t\t\treturn this._readPixelsFlat(readStream, width, height);\n\t\t\t\t}\n\t\t\t\tconst rgbe = [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t];\n\t\t\t\treadStream.readArray(rgbe);\n\t\t\t\tif (rgbe[0] !== 2 || rgbe[1] !== 2 || (rgbe[2] & 0x80) !== 0) {\n\t\t\t\t\t\treadStream.skip(-4);\n\t\t\t\t\t\treturn this._readPixelsFlat(readStream, width, height);\n\t\t\t\t}\n\t\t\t\tconst buffer = new ArrayBuffer(width * height * 4);\n\t\t\t\tconst view = new Uint8Array(buffer);\n\t\t\t\tlet scanstart = flipY ? 0 : width * 4 * (height - 1);\n\t\t\t\tlet x, y, i, channel, count, value;\n\t\t\t\tfor(y = 0; y < height; ++y){\n\t\t\t\t\t\tif (y) {\n\t\t\t\t\t\t\t\treadStream.readArray(rgbe);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ((rgbe[2] << 8) + rgbe[3] !== width) {\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor(channel = 0; channel < 4; ++channel){\n\t\t\t\t\t\t\t\tx = 0;\n\t\t\t\t\t\t\t\twhile(x < width){\n\t\t\t\t\t\t\t\t\t\tcount = readStream.readU8();\n\t\t\t\t\t\t\t\t\t\tif (count > 128) {\n\t\t\t\t\t\t\t\t\t\t\t\tcount -= 128;\n\t\t\t\t\t\t\t\t\t\t\t\tif (x + count > width) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tvalue = readStream.readU8();\n\t\t\t\t\t\t\t\t\t\t\t\tfor(i = 0; i < count; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tview[scanstart + channel + 4 * x++] = value;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tif (count === 0 || x + count > width) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tfor(i = 0; i < count; ++i){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tview[scanstart + channel + 4 * x++] = readStream.readU8();\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscanstart += width * 4 * (flipY ? 1 : -1);\n\t\t\t\t}\n\t\t\t\treturn view;\n\t\t}\n\t\t_readPixelsFlat(readStream, width, height) {\n\t\t\t\treturn readStream.remainingBytes === width * height * 4 ? new Uint8Array(readStream.arraybuffer, readStream.offset) : null;\n\t\t}\n}\n\nconst JSON_ADDRESS_MODE = {\n\t\t'repeat': ADDRESS_REPEAT,\n\t\t'clamp': ADDRESS_CLAMP_TO_EDGE,\n\t\t'mirror': ADDRESS_MIRRORED_REPEAT\n};\nconst JSON_FILTER_MODE = {\n\t\t'nearest': FILTER_NEAREST,\n\t\t'linear': FILTER_LINEAR,\n\t\t'nearest_mip_nearest': FILTER_NEAREST_MIPMAP_NEAREST,\n\t\t'linear_mip_nearest': FILTER_LINEAR_MIPMAP_NEAREST,\n\t\t'nearest_mip_linear': FILTER_NEAREST_MIPMAP_LINEAR,\n\t\t'linear_mip_linear': FILTER_LINEAR_MIPMAP_LINEAR\n};\nconst JSON_TEXTURE_TYPE = {\n\t\t'default': TEXTURETYPE_DEFAULT,\n\t\t'rgbm': TEXTURETYPE_RGBM,\n\t\t'rgbe': TEXTURETYPE_RGBE,\n\t\t'rgbp': TEXTURETYPE_RGBP,\n\t\t'swizzleGGGR': TEXTURETYPE_SWIZZLEGGGR\n};\nconst _completePartialMipmapChain = function(texture) {\n\t\tconst requiredMipLevels = TextureUtils.calcMipLevelsCount(texture._width, texture._height);\n\t\tconst isHtmlElement = function(object) {\n\t\t\t\treturn object instanceof HTMLCanvasElement || object instanceof HTMLImageElement || object instanceof HTMLVideoElement;\n\t\t};\n\t\tif (!(texture._format === PIXELFORMAT_RGBA8 || texture._format === PIXELFORMAT_RGBA32F) || texture._volume || texture._compressed || texture._levels.length === 1 || texture._levels.length === requiredMipLevels || isHtmlElement(texture._cubemap ? texture._levels[0][0] : texture._levels[0])) {\n\t\t\t\treturn;\n\t\t}\n\t\tconst downsample = function(width, height, data) {\n\t\t\t\tconst sampledWidth = Math.max(1, width >> 1);\n\t\t\t\tconst sampledHeight = Math.max(1, height >> 1);\n\t\t\t\tconst sampledData = new data.constructor(sampledWidth * sampledHeight * 4);\n\t\t\t\tconst xs = Math.floor(width / sampledWidth);\n\t\t\t\tconst ys = Math.floor(height / sampledHeight);\n\t\t\t\tconst xsys = xs * ys;\n\t\t\t\tfor(let y = 0; y < sampledHeight; ++y){\n\t\t\t\t\t\tfor(let x = 0; x < sampledWidth; ++x){\n\t\t\t\t\t\t\t\tfor(let e = 0; e < 4; ++e){\n\t\t\t\t\t\t\t\t\t\tlet sum = 0;\n\t\t\t\t\t\t\t\t\t\tfor(let sy = 0; sy < ys; ++sy){\n\t\t\t\t\t\t\t\t\t\t\t\tfor(let sx = 0; sx < xs; ++sx){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsum += data[(x * xs + sx + (y * ys + sy) * width) * 4 + e];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tsampledData[(x + y * sampledWidth) * 4 + e] = sum / xsys;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn sampledData;\n\t\t};\n\t\tfor(let level = texture._levels.length; level < requiredMipLevels; ++level){\n\t\t\t\tconst width = Math.max(1, texture._width >> level - 1);\n\t\t\t\tconst height = Math.max(1, texture._height >> level - 1);\n\t\t\t\tif (texture._cubemap) {\n\t\t\t\t\t\tconst mips = [];\n\t\t\t\t\t\tfor(let face = 0; face < 6; ++face){\n\t\t\t\t\t\t\t\tmips.push(downsample(width, height, texture._levels[level - 1][face]));\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttexture._levels.push(mips);\n\t\t\t\t} else {\n\t\t\t\t\t\ttexture._levels.push(downsample(width, height, texture._levels[level - 1]));\n\t\t\t\t}\n\t\t}\n\t\ttexture._levelsUpdated = texture._cubemap ? [\n\t\t\t\t[\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue\n\t\t\t\t]\n\t\t] : [\n\t\t\t\ttrue\n\t\t];\n};\nclass TextureHandler extends ResourceHandler {\n\t\tconstructor(app){\n\t\t\t\tsuper(app, 'texture');\n\t\t\t\tconst assets = app.assets;\n\t\t\t\tconst device = app.graphicsDevice;\n\t\t\t\tthis._device = device;\n\t\t\t\tthis._assets = assets;\n\t\t\t\tthis.imgParser = new ImgParser(assets, device);\n\t\t\t\tthis.parsers = {\n\t\t\t\t\t\tdds: new DdsParser(assets),\n\t\t\t\t\t\tktx: new KtxParser(assets),\n\t\t\t\t\t\tktx2: new Ktx2Parser(assets, device),\n\t\t\t\t\t\tbasis: new BasisParser(assets, device),\n\t\t\t\t\t\thdr: new HdrParser(assets)\n\t\t\t\t};\n\t\t}\n\t\tset crossOrigin(value) {\n\t\t\t\tthis.imgParser.crossOrigin = value;\n\t\t}\n\t\tget crossOrigin() {\n\t\t\t\treturn this.imgParser.crossOrigin;\n\t\t}\n\t\tset maxRetries(value) {\n\t\t\t\tthis.imgParser.maxRetries = value;\n\t\t\t\tfor(const parser in this.parsers){\n\t\t\t\t\t\tif (this.parsers.hasOwnProperty(parser)) {\n\t\t\t\t\t\t\t\tthis.parsers[parser].maxRetries = value;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget maxRetries() {\n\t\t\t\treturn this.imgParser.maxRetries;\n\t\t}\n\t\t_getUrlWithoutParams(url) {\n\t\t\t\treturn url.indexOf('?') >= 0 ? url.split('?')[0] : url;\n\t\t}\n\t\t_getParser(url) {\n\t\t\t\tconst ext = path.getExtension(this._getUrlWithoutParams(url)).toLowerCase().replace('.', '');\n\t\t\t\treturn this.parsers[ext] || this.imgParser;\n\t\t}\n\t\t_getTextureOptions(asset) {\n\t\t\t\tconst options = {};\n\t\t\t\tif (asset) {\n\t\t\t\t\t\tif (asset.name?.length > 0) {\n\t\t\t\t\t\t\t\toptions.name = asset.name;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst assetData = asset.data;\n\t\t\t\t\t\tif (assetData.hasOwnProperty('minfilter')) {\n\t\t\t\t\t\t\t\toptions.minFilter = JSON_FILTER_MODE[assetData.minfilter];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('magfilter')) {\n\t\t\t\t\t\t\t\toptions.magFilter = JSON_FILTER_MODE[assetData.magfilter];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('addressu')) {\n\t\t\t\t\t\t\t\toptions.addressU = JSON_ADDRESS_MODE[assetData.addressu];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('addressv')) {\n\t\t\t\t\t\t\t\toptions.addressV = JSON_ADDRESS_MODE[assetData.addressv];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('mipmaps')) {\n\t\t\t\t\t\t\t\toptions.mipmaps = assetData.mipmaps;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('anisotropy')) {\n\t\t\t\t\t\t\t\toptions.anisotropy = assetData.anisotropy;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('flipY')) {\n\t\t\t\t\t\t\t\toptions.flipY = !!assetData.flipY;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (assetData.hasOwnProperty('srgb')) {\n\t\t\t\t\t\t\t\toptions.srgb = !!assetData.srgb;\n\t\t\t\t\t\t}\n\t\t\t\t\t\toptions.type = TEXTURETYPE_DEFAULT;\n\t\t\t\t\t\tif (assetData.hasOwnProperty('type')) {\n\t\t\t\t\t\t\t\toptions.type = JSON_TEXTURE_TYPE[assetData.type];\n\t\t\t\t\t\t} else if (assetData.hasOwnProperty('rgbm') && assetData.rgbm) {\n\t\t\t\t\t\t\t\toptions.type = TEXTURETYPE_RGBM;\n\t\t\t\t\t\t} else if (asset.file && (asset.file.opt & 8) !== 0) {\n\t\t\t\t\t\t\t\toptions.type = TEXTURETYPE_SWIZZLEGGGR;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn options;\n\t\t}\n\t\tload(url, callback, asset) {\n\t\t\t\tif (typeof url === 'string') {\n\t\t\t\t\t\turl = {\n\t\t\t\t\t\t\t\tload: url,\n\t\t\t\t\t\t\t\toriginal: url\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tthis._getParser(url.original).load(url, callback, asset);\n\t\t}\n\t\topen(url, data, asset) {\n\t\t\t\tif (!url) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tconst textureOptions = this._getTextureOptions(asset);\n\t\t\t\tlet texture = this._getParser(url).open(url, data, this._device, textureOptions);\n\t\t\t\tif (texture === null) {\n\t\t\t\t\t\ttexture = new Texture(this._device, {\n\t\t\t\t\t\t\t\twidth: 4,\n\t\t\t\t\t\t\t\theight: 4,\n\t\t\t\t\t\t\t\tformat: PIXELFORMAT_RGB8\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\t_completePartialMipmapChain(texture);\n\t\t\t\t\t\tif (data.unswizzledGGGR) {\n\t\t\t\t\t\t\t\tasset.file.variants.basis.opt &= -9;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn texture;\n\t\t}\n\t\tpatch(asset, assets) {\n\t\t\t\tconst texture = asset.resource;\n\t\t\t\tif (!texture) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst options = this._getTextureOptions(asset);\n\t\t\t\tfor (const key of Object.keys(options)){\n\t\t\t\t\t\ttexture[key] = options[key];\n\t\t\t\t}\n\t\t}\n}\n\nconst XRTYPE_INLINE = 'inline';\nconst XRTYPE_VR = 'immersive-vr';\nconst XRTYPE_AR = 'immersive-ar';\nconst XRSPACE_VIEWER = 'viewer';\nconst XRHAND_LEFT = 'left';\nconst XRDEPTHSENSINGUSAGE_CPU = 'cpu-optimized';\nconst XRDEPTHSENSINGUSAGE_GPU = 'gpu-optimized';\nconst XRDEPTHSENSINGFORMAT_L8A8 = 'luminance-alpha';\nconst XRDEPTHSENSINGFORMAT_R16U = 'unsigned-short';\nconst XRDEPTHSENSINGFORMAT_F32 = 'float32';\n\nclass XrDomOverlay {\n\t\tconstructor(manager){\n\t\t\t\tthis._supported = platform.browser && !!window.XRDOMOverlayState;\n\t\t\t\tthis._root = null;\n\t\t\t\tthis._manager = manager;\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget available() {\n\t\t\t\treturn this._supported && this._manager.active && this._manager._session.domOverlayState !== null;\n\t\t}\n\t\tget state() {\n\t\t\t\tif (!this._supported || !this._manager.active || !this._manager._session.domOverlayState) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._manager._session.domOverlayState.type;\n\t\t}\n\t\tset root(value) {\n\t\t\t\tif (!this._supported || this._manager.active) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._root = value;\n\t\t}\n\t\tget root() {\n\t\t\t\treturn this._root;\n\t\t}\n}\n\nconst poolVec3 = [];\nconst poolQuat = [];\nclass XrHitTestSource extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESULT = 'result';\n\t\t}\n\t\tconstructor(manager, xrHitTestSource, transient, inputSource = null){\n\t\t\t\tsuper();\n\t\t\t\tthis.manager = manager;\n\t\t\t\tthis._xrHitTestSource = xrHitTestSource;\n\t\t\t\tthis._transient = transient;\n\t\t\t\tthis._inputSource = inputSource;\n\t\t}\n\t\tremove() {\n\t\t\t\tif (!this._xrHitTestSource) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst sources = this.manager.hitTest.sources;\n\t\t\t\tconst ind = sources.indexOf(this);\n\t\t\t\tif (ind !== -1) sources.splice(ind, 1);\n\t\t\t\tthis.onStop();\n\t\t}\n\t\tonStop() {\n\t\t\t\tthis._xrHitTestSource.cancel();\n\t\t\t\tthis._xrHitTestSource = null;\n\t\t\t\tthis.fire('remove');\n\t\t\t\tthis.manager.hitTest.fire('remove', this);\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (this._transient) {\n\t\t\t\t\t\tconst transientResults = frame.getHitTestResultsForTransientInput(this._xrHitTestSource);\n\t\t\t\t\t\tfor(let i = 0; i < transientResults.length; i++){\n\t\t\t\t\t\t\t\tconst transientResult = transientResults[i];\n\t\t\t\t\t\t\t\tif (!transientResult.results.length) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet inputSource;\n\t\t\t\t\t\t\t\tif (transientResult.inputSource) {\n\t\t\t\t\t\t\t\t\t\tinputSource = this.manager.input._getByInputSource(transientResult.inputSource);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.updateHitResults(transientResult.results, inputSource);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tconst results = frame.getHitTestResults(this._xrHitTestSource);\n\t\t\t\t\t\tif (!results.length) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.updateHitResults(results);\n\t\t\t\t}\n\t\t}\n\t\tupdateHitResults(results, inputSource) {\n\t\t\t\tif (this._inputSource && this._inputSource !== inputSource) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst origin = poolVec3.pop() ?? new Vec3();\n\t\t\t\tif (inputSource) {\n\t\t\t\t\t\torigin.copy(inputSource.getOrigin());\n\t\t\t\t} else {\n\t\t\t\t\t\torigin.copy(this.manager.camera.getPosition());\n\t\t\t\t}\n\t\t\t\tlet candidateDistance = Infinity;\n\t\t\t\tlet candidateHitTestResult = null;\n\t\t\t\tconst position = poolVec3.pop() ?? new Vec3();\n\t\t\t\tconst rotation = poolQuat.pop() ?? new Quat();\n\t\t\t\tfor(let i = 0; i < results.length; i++){\n\t\t\t\t\t\tconst pose = results[i].getPose(this.manager._referenceSpace);\n\t\t\t\t\t\tconst distance = origin.distance(pose.transform.position);\n\t\t\t\t\t\tif (distance >= candidateDistance) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcandidateDistance = distance;\n\t\t\t\t\t\tcandidateHitTestResult = results[i];\n\t\t\t\t\t\tposition.copy(pose.transform.position);\n\t\t\t\t\t\trotation.copy(pose.transform.orientation);\n\t\t\t\t}\n\t\t\t\tthis.fire('result', position, rotation, inputSource || this._inputSource, candidateHitTestResult);\n\t\t\t\tthis.manager.hitTest.fire('result', this, position, rotation, inputSource || this._inputSource, candidateHitTestResult);\n\t\t\t\tpoolVec3.push(origin);\n\t\t\t\tpoolVec3.push(position);\n\t\t\t\tpoolQuat.push(rotation);\n\t\t}\n}\n\nclass XrHitTest extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_AVAILABLE = 'available';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_UNAVAILABLE = 'unavailable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_RESULT = 'result';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._supported = platform.browser && !!(window.XRSession && window.XRSession.prototype.requestHitTestSource), this._available = false, this._checkingAvailability = false, this.sources = [];\n\t\t\t\tthis.manager = manager;\n\t\t\t\tif (this._supported) {\n\t\t\t\t\t\tthis.manager.on('start', this._onSessionStart, this);\n\t\t\t\t\t\tthis.manager.on('end', this._onSessionEnd, this);\n\t\t\t\t}\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tif (this.manager.session.enabledFeatures) {\n\t\t\t\t\t\tconst available = this.manager.session.enabledFeatures.indexOf('hit-test') !== -1;\n\t\t\t\t\t\tif (!available) return;\n\t\t\t\t\t\tthis._available = available;\n\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t} else if (!this._checkingAvailability) {\n\t\t\t\t\t\tthis._checkingAvailability = true;\n\t\t\t\t\t\tthis.manager.session.requestReferenceSpace(XRSPACE_VIEWER).then((referenceSpace)=>{\n\t\t\t\t\t\t\t\tthis.manager.session.requestHitTestSource({\n\t\t\t\t\t\t\t\t\t\tspace: referenceSpace\n\t\t\t\t\t\t\t\t}).then((hitTestSource)=>{\n\t\t\t\t\t\t\t\t\t\thitTestSource.cancel();\n\t\t\t\t\t\t\t\t\t\tif (this.manager.active) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._available = true;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}).catch(()=>{});\n\t\t\t\t\t\t}).catch(()=>{});\n\t\t\t\t}\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tif (!this._available) return;\n\t\t\t\tthis._available = false;\n\t\t\t\tfor(let i = 0; i < this.sources.length; i++){\n\t\t\t\t\t\tthis.sources[i].onStop();\n\t\t\t\t}\n\t\t\t\tthis.sources = [];\n\t\t\t\tthis.fire('unavailable');\n\t\t}\n\t\tstart(options = {}) {\n\t\t\t\tif (!this._supported) {\n\t\t\t\t\t\toptions.callback?.(new Error('XR HitTest is not supported'), null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\toptions.callback?.(new Error('XR HitTest is not available'), null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!options.profile && !options.spaceType) {\n\t\t\t\t\t\toptions.spaceType = XRSPACE_VIEWER;\n\t\t\t\t}\n\t\t\t\tlet xrRay;\n\t\t\t\tconst offsetRay = options.offsetRay;\n\t\t\t\tif (offsetRay) {\n\t\t\t\t\t\tconst origin = new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z, 1.0);\n\t\t\t\t\t\tconst direction = new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z, 0.0);\n\t\t\t\t\t\txrRay = new XRRay(origin, direction);\n\t\t\t\t}\n\t\t\t\tconst callback = options.callback;\n\t\t\t\tif (options.spaceType) {\n\t\t\t\t\t\tthis.manager.session.requestReferenceSpace(options.spaceType).then((referenceSpace)=>{\n\t\t\t\t\t\t\t\tif (!this.manager.session) {\n\t\t\t\t\t\t\t\t\t\tconst err = new Error('XR Session is not started (2)');\n\t\t\t\t\t\t\t\t\t\tif (callback) callback(err);\n\t\t\t\t\t\t\t\t\t\tthis.fire('error', err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.manager.session.requestHitTestSource({\n\t\t\t\t\t\t\t\t\t\tspace: referenceSpace,\n\t\t\t\t\t\t\t\t\t\tentityTypes: options.entityTypes || undefined,\n\t\t\t\t\t\t\t\t\t\toffsetRay: xrRay\n\t\t\t\t\t\t\t\t}).then((xrHitTestSource)=>{\n\t\t\t\t\t\t\t\t\t\tthis._onHitTestSource(xrHitTestSource, false, options.inputSource, callback);\n\t\t\t\t\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\t\t\t\t\tif (callback) callback(ex);\n\t\t\t\t\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\t\t\tif (callback) callback(ex);\n\t\t\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.manager.session.requestHitTestSourceForTransientInput({\n\t\t\t\t\t\t\t\tprofile: options.profile,\n\t\t\t\t\t\t\t\tentityTypes: options.entityTypes || undefined,\n\t\t\t\t\t\t\t\toffsetRay: xrRay\n\t\t\t\t\t\t}).then((xrHitTestSource)=>{\n\t\t\t\t\t\t\t\tthis._onHitTestSource(xrHitTestSource, true, options.inputSource, callback);\n\t\t\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\t\t\tif (callback) callback(ex);\n\t\t\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\t_onHitTestSource(xrHitTestSource, transient, inputSource, callback) {\n\t\t\t\tif (!this.manager.session) {\n\t\t\t\t\t\txrHitTestSource.cancel();\n\t\t\t\t\t\tconst err = new Error('XR Session is not started (3)');\n\t\t\t\t\t\tif (callback) callback(err);\n\t\t\t\t\t\tthis.fire('error', err);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst hitTestSource = new XrHitTestSource(this.manager, xrHitTestSource, transient, inputSource ?? null);\n\t\t\t\tthis.sources.push(hitTestSource);\n\t\t\t\tif (callback) callback(null, hitTestSource);\n\t\t\t\tthis.fire('add', hitTestSource);\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this.sources.length; i++){\n\t\t\t\t\t\tthis.sources[i].update(frame);\n\t\t\t\t}\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget available() {\n\t\t\t\treturn this._available;\n\t\t}\n}\n\nclass XrTrackedImage extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_TRACKED = 'tracked';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_UNTRACKED = 'untracked';\n\t\t}\n\t\tconstructor(image, width){\n\t\t\t\tsuper(), this._bitmap = null, this._measuredWidth = 0, this._trackable = false, this._tracking = false, this._emulated = false, this._pose = null, this._position = new Vec3(), this._rotation = new Quat();\n\t\t\t\tthis._image = image;\n\t\t\t\tthis._width = width;\n\t\t}\n\t\tget image() {\n\t\t\t\treturn this._image;\n\t\t}\n\t\tset width(value) {\n\t\t\t\tthis._width = value;\n\t\t}\n\t\tget width() {\n\t\t\t\treturn this._width;\n\t\t}\n\t\tget trackable() {\n\t\t\t\treturn this._trackable;\n\t\t}\n\t\tget tracking() {\n\t\t\t\treturn this._tracking;\n\t\t}\n\t\tget emulated() {\n\t\t\t\treturn this._emulated;\n\t\t}\n\t\tprepare() {\n\t\t\t\tif (this._bitmap) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\timage: this._bitmap,\n\t\t\t\t\t\t\t\twidthInMeters: this._width\n\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn createImageBitmap(this._image).then((bitmap)=>{\n\t\t\t\t\t\tthis._bitmap = bitmap;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\timage: this._bitmap,\n\t\t\t\t\t\t\t\twidthInMeters: this._width\n\t\t\t\t\t\t};\n\t\t\t\t});\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._image = null;\n\t\t\t\tthis._pose = null;\n\t\t\t\tif (this._bitmap) {\n\t\t\t\t\t\tthis._bitmap.close();\n\t\t\t\t\t\tthis._bitmap = null;\n\t\t\t\t}\n\t\t}\n\t\tgetPosition() {\n\t\t\t\tif (this._pose) this._position.copy(this._pose.transform.position);\n\t\t\t\treturn this._position;\n\t\t}\n\t\tgetRotation() {\n\t\t\t\tif (this._pose) this._rotation.copy(this._pose.transform.orientation);\n\t\t\t\treturn this._rotation;\n\t\t}\n}\n\nclass XrImageTracking extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._supported = platform.browser && !!window.XRImageTrackingResult, this._available = false, this._images = [];\n\t\t\t\tthis._manager = manager;\n\t\t\t\tif (this._supported) {\n\t\t\t\t\t\tthis._manager.on('start', this._onSessionStart, this);\n\t\t\t\t\t\tthis._manager.on('end', this._onSessionEnd, this);\n\t\t\t\t}\n\t\t}\n\t\tadd(image, width) {\n\t\t\t\tif (!this._supported || this._manager.active) return null;\n\t\t\t\tconst trackedImage = new XrTrackedImage(image, width);\n\t\t\t\tthis._images.push(trackedImage);\n\t\t\t\treturn trackedImage;\n\t\t}\n\t\tremove(trackedImage) {\n\t\t\t\tif (this._manager.active) return;\n\t\t\t\tconst ind = this._images.indexOf(trackedImage);\n\t\t\t\tif (ind !== -1) {\n\t\t\t\t\t\ttrackedImage.destroy();\n\t\t\t\t\t\tthis._images.splice(ind, 1);\n\t\t\t\t}\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tthis._manager.session.getTrackedImageScores().then((images)=>{\n\t\t\t\t\t\tthis._available = true;\n\t\t\t\t\t\tfor(let i = 0; i < images.length; i++){\n\t\t\t\t\t\t\t\tthis._images[i]._trackable = images[i] === 'trackable';\n\t\t\t\t\t\t}\n\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\tthis._available = false;\n\t\t\t\t\t\tthis.fire('error', err);\n\t\t\t\t});\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tthis._available = false;\n\t\t\t\tfor(let i = 0; i < this._images.length; i++){\n\t\t\t\t\t\tconst image = this._images[i];\n\t\t\t\t\t\timage._pose = null;\n\t\t\t\t\t\timage._measuredWidth = 0;\n\t\t\t\t\t\tif (image._tracking) {\n\t\t\t\t\t\t\t\timage._tracking = false;\n\t\t\t\t\t\t\t\timage.fire('untracked');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tprepareImages(callback) {\n\t\t\t\tif (this._images.length) {\n\t\t\t\t\t\tPromise.all(this._images.map((trackedImage)=>{\n\t\t\t\t\t\t\t\treturn trackedImage.prepare();\n\t\t\t\t\t\t})).then((bitmaps)=>{\n\t\t\t\t\t\t\t\tcallback(null, bitmaps);\n\t\t\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\t\t\tcallback(err, null);\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tcallback(null, null);\n\t\t\t\t}\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._available) return;\n\t\t\t\tconst results = frame.getImageTrackingResults();\n\t\t\t\tconst index = {};\n\t\t\t\tfor(let i = 0; i < results.length; i++){\n\t\t\t\t\t\tindex[results[i].index] = results[i];\n\t\t\t\t\t\tconst trackedImage = this._images[results[i].index];\n\t\t\t\t\t\ttrackedImage._emulated = results[i].trackingState === 'emulated';\n\t\t\t\t\t\ttrackedImage._measuredWidth = results[i].measuredWidthInMeters;\n\t\t\t\t\t\ttrackedImage._pose = frame.getPose(results[i].imageSpace, this._manager._referenceSpace);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._images.length; i++){\n\t\t\t\t\t\tif (this._images[i]._tracking && !index[i]) {\n\t\t\t\t\t\t\t\tthis._images[i]._tracking = false;\n\t\t\t\t\t\t\t\tthis._images[i].fire('untracked');\n\t\t\t\t\t\t} else if (!this._images[i]._tracking && index[i]) {\n\t\t\t\t\t\t\t\tthis._images[i]._tracking = true;\n\t\t\t\t\t\t\t\tthis._images[i].fire('tracked');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget available() {\n\t\t\t\treturn this._available;\n\t\t}\n\t\tget images() {\n\t\t\t\treturn this._images;\n\t\t}\n}\n\nclass XrFinger {\n\t\tconstructor(index, hand){\n\t\t\t\tthis._joints = [];\n\t\t\t\tthis._tip = null;\n\t\t\t\tthis._index = index;\n\t\t\t\tthis._hand = hand;\n\t\t\t\tthis._hand._fingers.push(this);\n\t\t}\n\t\tget index() {\n\t\t\t\treturn this._index;\n\t\t}\n\t\tget hand() {\n\t\t\t\treturn this._hand;\n\t\t}\n\t\tget joints() {\n\t\t\t\treturn this._joints;\n\t\t}\n\t\tget tip() {\n\t\t\t\treturn this._tip;\n\t\t}\n}\n\nconst tipJointIds = platform.browser && window.XRHand ? [\n\t\t'thumb-tip',\n\t\t'index-finger-tip',\n\t\t'middle-finger-tip',\n\t\t'ring-finger-tip',\n\t\t'pinky-finger-tip'\n] : [];\nconst tipJointIdsIndex = {};\nfor(let i = 0; i < tipJointIds.length; i++){\n\t\ttipJointIdsIndex[tipJointIds[i]] = true;\n}\nclass XrJoint {\n\t\tconstructor(index, id, hand, finger = null){\n\t\t\t\tthis._radius = null;\n\t\t\t\tthis._localTransform = new Mat4();\n\t\t\t\tthis._worldTransform = new Mat4();\n\t\t\t\tthis._localPosition = new Vec3();\n\t\t\t\tthis._localRotation = new Quat();\n\t\t\t\tthis._position = new Vec3();\n\t\t\t\tthis._rotation = new Quat();\n\t\t\t\tthis._dirtyLocal = true;\n\t\t\t\tthis._index = index;\n\t\t\t\tthis._id = id;\n\t\t\t\tthis._hand = hand;\n\t\t\t\tthis._finger = finger;\n\t\t\t\tthis._wrist = id === 'wrist';\n\t\t\t\tthis._tip = this._finger && !!tipJointIdsIndex[id];\n\t\t}\n\t\tupdate(pose) {\n\t\t\t\tthis._dirtyLocal = true;\n\t\t\t\tthis._radius = pose.radius;\n\t\t\t\tthis._localPosition.copy(pose.transform.position);\n\t\t\t\tthis._localRotation.copy(pose.transform.orientation);\n\t\t}\n\t\t_updateTransforms() {\n\t\t\t\tif (this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtyLocal = false;\n\t\t\t\t\t\tthis._localTransform.setTRS(this._localPosition, this._localRotation, Vec3.ONE);\n\t\t\t\t}\n\t\t\t\tconst manager = this._hand._manager;\n\t\t\t\tconst parent = manager.camera.parent;\n\t\t\t\tif (parent) {\n\t\t\t\t\t\tthis._worldTransform.mul2(parent.getWorldTransform(), this._localTransform);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._worldTransform.copy(this._localTransform);\n\t\t\t\t}\n\t\t}\n\t\tgetPosition() {\n\t\t\t\tthis._updateTransforms();\n\t\t\t\tthis._worldTransform.getTranslation(this._position);\n\t\t\t\treturn this._position;\n\t\t}\n\t\tgetRotation() {\n\t\t\t\tthis._updateTransforms();\n\t\t\t\tthis._rotation.setFromMat4(this._worldTransform);\n\t\t\t\treturn this._rotation;\n\t\t}\n\t\tget id() {\n\t\t\t\treturn this._id;\n\t\t}\n\t\tget index() {\n\t\t\t\treturn this._index;\n\t\t}\n\t\tget hand() {\n\t\t\t\treturn this._hand;\n\t\t}\n\t\tget finger() {\n\t\t\t\treturn this._finger;\n\t\t}\n\t\tget wrist() {\n\t\t\t\treturn this._wrist;\n\t\t}\n\t\tget tip() {\n\t\t\t\treturn this._tip;\n\t\t}\n\t\tget radius() {\n\t\t\t\treturn this._radius || 0.005;\n\t\t}\n}\n\nlet fingerJointIds = [];\nconst vecA = new Vec3();\nconst vecB = new Vec3();\nconst vecC = new Vec3();\nif (platform.browser && window.XRHand) {\n\t\tfingerJointIds = [\n\t\t\t\t[\n\t\t\t\t\t\t'thumb-metacarpal',\n\t\t\t\t\t\t'thumb-phalanx-proximal',\n\t\t\t\t\t\t'thumb-phalanx-distal',\n\t\t\t\t\t\t'thumb-tip'\n\t\t\t\t],\n\t\t\t\t[\n\t\t\t\t\t\t'index-finger-metacarpal',\n\t\t\t\t\t\t'index-finger-phalanx-proximal',\n\t\t\t\t\t\t'index-finger-phalanx-intermediate',\n\t\t\t\t\t\t'index-finger-phalanx-distal',\n\t\t\t\t\t\t'index-finger-tip'\n\t\t\t\t],\n\t\t\t\t[\n\t\t\t\t\t\t'middle-finger-metacarpal',\n\t\t\t\t\t\t'middle-finger-phalanx-proximal',\n\t\t\t\t\t\t'middle-finger-phalanx-intermediate',\n\t\t\t\t\t\t'middle-finger-phalanx-distal',\n\t\t\t\t\t\t'middle-finger-tip'\n\t\t\t\t],\n\t\t\t\t[\n\t\t\t\t\t\t'ring-finger-metacarpal',\n\t\t\t\t\t\t'ring-finger-phalanx-proximal',\n\t\t\t\t\t\t'ring-finger-phalanx-intermediate',\n\t\t\t\t\t\t'ring-finger-phalanx-distal',\n\t\t\t\t\t\t'ring-finger-tip'\n\t\t\t\t],\n\t\t\t\t[\n\t\t\t\t\t\t'pinky-finger-metacarpal',\n\t\t\t\t\t\t'pinky-finger-phalanx-proximal',\n\t\t\t\t\t\t'pinky-finger-phalanx-intermediate',\n\t\t\t\t\t\t'pinky-finger-phalanx-distal',\n\t\t\t\t\t\t'pinky-finger-tip'\n\t\t\t\t]\n\t\t];\n}\nclass XrHand extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_TRACKING = 'tracking';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_TRACKINGLOST = 'trackinglost';\n\t\t}\n\t\tconstructor(inputSource){\n\t\t\t\tsuper(), this._tracking = false, this._fingers = [], this._joints = [], this._jointsById = {}, this._tips = [], this._wrist = null;\n\t\t\t\tconst xrHand = inputSource._xrInputSource.hand;\n\t\t\t\tthis._manager = inputSource._manager;\n\t\t\t\tthis._inputSource = inputSource;\n\t\t\t\tif (xrHand.get('wrist')) {\n\t\t\t\t\t\tconst joint = new XrJoint(0, 'wrist', this, null);\n\t\t\t\t\t\tthis._wrist = joint;\n\t\t\t\t\t\tthis._joints.push(joint);\n\t\t\t\t\t\tthis._jointsById.wrist = joint;\n\t\t\t\t}\n\t\t\t\tfor(let f = 0; f < fingerJointIds.length; f++){\n\t\t\t\t\t\tconst finger = new XrFinger(f, this);\n\t\t\t\t\t\tfor(let j = 0; j < fingerJointIds[f].length; j++){\n\t\t\t\t\t\t\t\tconst jointId = fingerJointIds[f][j];\n\t\t\t\t\t\t\t\tif (!xrHand.get(jointId)) continue;\n\t\t\t\t\t\t\t\tconst joint = new XrJoint(j, jointId, this, finger);\n\t\t\t\t\t\t\t\tthis._joints.push(joint);\n\t\t\t\t\t\t\t\tthis._jointsById[jointId] = joint;\n\t\t\t\t\t\t\t\tif (joint.tip) {\n\t\t\t\t\t\t\t\t\t\tthis._tips.push(joint);\n\t\t\t\t\t\t\t\t\t\tfinger._tip = joint;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfinger._joints.push(joint);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tconst xrInputSource = this._inputSource._xrInputSource;\n\t\t\t\tfor(let j = 0; j < this._joints.length; j++){\n\t\t\t\t\t\tconst joint = this._joints[j];\n\t\t\t\t\t\tconst jointSpace = xrInputSource.hand.get(joint._id);\n\t\t\t\t\t\tif (jointSpace) {\n\t\t\t\t\t\t\t\tlet pose;\n\t\t\t\t\t\t\t\tif (frame.session.visibilityState !== 'hidden') {\n\t\t\t\t\t\t\t\t\t\tpose = frame.getJointPose(jointSpace, this._manager._referenceSpace);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (pose) {\n\t\t\t\t\t\t\t\t\t\tjoint.update(pose);\n\t\t\t\t\t\t\t\t\t\tif (joint.wrist && !this._tracking) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._tracking = true;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.fire('tracking');\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (joint.wrist) {\n\t\t\t\t\t\t\t\t\t\tif (this._tracking) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._tracking = false;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.fire('trackinglost');\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst j1 = this._jointsById['thumb-metacarpal'];\n\t\t\t\tconst j4 = this._jointsById['thumb-tip'];\n\t\t\t\tconst j6 = this._jointsById['index-finger-phalanx-proximal'];\n\t\t\t\tconst j9 = this._jointsById['index-finger-tip'];\n\t\t\t\tconst j16 = this._jointsById['ring-finger-phalanx-proximal'];\n\t\t\t\tconst j21 = this._jointsById['pinky-finger-phalanx-proximal'];\n\t\t\t\tif (j1 && j4 && j6 && j9 && j16 && j21) {\n\t\t\t\t\t\tthis._inputSource._dirtyRay = true;\n\t\t\t\t\t\tthis._inputSource._rayLocal.origin.lerp(j4._localPosition, j9._localPosition, 0.5);\n\t\t\t\t\t\tlet jointL = j1;\n\t\t\t\t\t\tlet jointR = j21;\n\t\t\t\t\t\tif (this._inputSource.handedness === XRHAND_LEFT) {\n\t\t\t\t\t\t\t\tconst t = jointL;\n\t\t\t\t\t\t\t\tjointL = jointR;\n\t\t\t\t\t\t\t\tjointR = t;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvecA.sub2(jointL._localPosition, this._wrist._localPosition);\n\t\t\t\t\t\tvecB.sub2(jointR._localPosition, this._wrist._localPosition);\n\t\t\t\t\t\tvecC.cross(vecA, vecB).normalize();\n\t\t\t\t\t\tvecA.lerp(j6._localPosition, j16._localPosition, 0.5);\n\t\t\t\t\t\tvecA.sub(this._wrist._localPosition).normalize();\n\t\t\t\t\t\tthis._inputSource._rayLocal.direction.lerp(vecC, vecA, 0.5).normalize();\n\t\t\t\t}\n\t\t\t\tconst squeezing = this._fingerIsClosed(1) && this._fingerIsClosed(2) && this._fingerIsClosed(3) && this._fingerIsClosed(4);\n\t\t\t\tif (squeezing) {\n\t\t\t\t\t\tif (!this._inputSource._squeezing) {\n\t\t\t\t\t\t\t\tthis._inputSource._squeezing = true;\n\t\t\t\t\t\t\t\tthis._inputSource.fire('squeezestart');\n\t\t\t\t\t\t\t\tthis._manager.input.fire('squeezestart', this._inputSource);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this._inputSource._squeezing) {\n\t\t\t\t\t\t\t\tthis._inputSource._squeezing = false;\n\t\t\t\t\t\t\t\tthis._inputSource.fire('squeeze');\n\t\t\t\t\t\t\t\tthis._manager.input.fire('squeeze', this._inputSource);\n\t\t\t\t\t\t\t\tthis._inputSource.fire('squeezeend');\n\t\t\t\t\t\t\t\tthis._manager.input.fire('squeezeend', this._inputSource);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_fingerIsClosed(index) {\n\t\t\t\tconst finger = this._fingers[index];\n\t\t\t\tvecA.sub2(finger.joints[0]._localPosition, finger.joints[1]._localPosition).normalize();\n\t\t\t\tvecB.sub2(finger.joints[2]._localPosition, finger.joints[3]._localPosition).normalize();\n\t\t\t\treturn vecA.dot(vecB) < -0.8;\n\t\t}\n\t\tgetJointById(id) {\n\t\t\t\treturn this._jointsById[id] || null;\n\t\t}\n\t\tget fingers() {\n\t\t\t\treturn this._fingers;\n\t\t}\n\t\tget joints() {\n\t\t\t\treturn this._joints;\n\t\t}\n\t\tget tips() {\n\t\t\t\treturn this._tips;\n\t\t}\n\t\tget wrist() {\n\t\t\t\treturn this._wrist;\n\t\t}\n\t\tget tracking() {\n\t\t\t\treturn this._tracking;\n\t\t}\n}\n\nconst vec3A$1 = new Vec3();\nconst quat = new Quat();\nlet ids$1 = 0;\nclass XrInputSource extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECT = 'select';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTSTART = 'selectstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTEND = 'selectend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SQUEEZE = 'squeeze';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SQUEEZESTART = 'squeezestart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SQUEEZEEND = 'squeezeend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_HITTESTADD = 'hittest:add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_HITTESTREMOVE = 'hittest:remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_HITTESTRESULT = 'hittest:result';\n\t\t}\n\t\tconstructor(manager, xrInputSource){\n\t\t\t\tsuper(), this._ray = new Ray(), this._rayLocal = new Ray(), this._grip = false, this._hand = null, this._velocitiesAvailable = false, this._velocitiesTimestamp = now(), this._localTransform = null, this._worldTransform = null, this._position = new Vec3(), this._rotation = new Quat(), this._localPosition = null, this._localPositionLast = null, this._localRotation = null, this._linearVelocity = null, this._dirtyLocal = true, this._dirtyRay = false, this._selecting = false, this._squeezing = false, this._elementInput = true, this._elementEntity = null, this._hitTestSources = [];\n\t\t\t\tthis._id = ++ids$1;\n\t\t\t\tthis._manager = manager;\n\t\t\t\tthis._xrInputSource = xrInputSource;\n\t\t\t\tif (xrInputSource.hand) {\n\t\t\t\t\t\tthis._hand = new XrHand(this);\n\t\t\t\t}\n\t\t}\n\t\tget id() {\n\t\t\t\treturn this._id;\n\t\t}\n\t\tget inputSource() {\n\t\t\t\treturn this._xrInputSource;\n\t\t}\n\t\tget targetRayMode() {\n\t\t\t\treturn this._xrInputSource.targetRayMode;\n\t\t}\n\t\tget handedness() {\n\t\t\t\treturn this._xrInputSource.handedness;\n\t\t}\n\t\tget profiles() {\n\t\t\t\treturn this._xrInputSource.profiles;\n\t\t}\n\t\tget grip() {\n\t\t\t\treturn this._grip;\n\t\t}\n\t\tget hand() {\n\t\t\t\treturn this._hand;\n\t\t}\n\t\tget gamepad() {\n\t\t\t\treturn this._xrInputSource.gamepad || null;\n\t\t}\n\t\tget selecting() {\n\t\t\t\treturn this._selecting;\n\t\t}\n\t\tget squeezing() {\n\t\t\t\treturn this._squeezing;\n\t\t}\n\t\tset elementInput(value) {\n\t\t\t\tif (this._elementInput === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._elementInput = value;\n\t\t\t\tif (!this._elementInput) {\n\t\t\t\t\t\tthis._elementEntity = null;\n\t\t\t\t}\n\t\t}\n\t\tget elementInput() {\n\t\t\t\treturn this._elementInput;\n\t\t}\n\t\tget elementEntity() {\n\t\t\t\treturn this._elementEntity;\n\t\t}\n\t\tget hitTestSources() {\n\t\t\t\treturn this._hitTestSources;\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (this._hand) {\n\t\t\t\t\t\tthis._hand.update(frame);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst gripSpace = this._xrInputSource.gripSpace;\n\t\t\t\t\t\tif (gripSpace) {\n\t\t\t\t\t\t\t\tconst gripPose = frame.getPose(gripSpace, this._manager._referenceSpace);\n\t\t\t\t\t\t\t\tif (gripPose) {\n\t\t\t\t\t\t\t\t\t\tif (!this._grip) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._grip = true;\n\t\t\t\t\t\t\t\t\t\t\t\tthis._localTransform = new Mat4();\n\t\t\t\t\t\t\t\t\t\t\t\tthis._worldTransform = new Mat4();\n\t\t\t\t\t\t\t\t\t\t\t\tthis._localPositionLast = new Vec3();\n\t\t\t\t\t\t\t\t\t\t\t\tthis._localPosition = new Vec3();\n\t\t\t\t\t\t\t\t\t\t\t\tthis._localRotation = new Quat();\n\t\t\t\t\t\t\t\t\t\t\t\tthis._linearVelocity = new Vec3();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst timestamp = now();\n\t\t\t\t\t\t\t\t\t\tconst dt = (timestamp - this._velocitiesTimestamp) / 1000;\n\t\t\t\t\t\t\t\t\t\tthis._velocitiesTimestamp = timestamp;\n\t\t\t\t\t\t\t\t\t\tthis._dirtyLocal = true;\n\t\t\t\t\t\t\t\t\t\tthis._localPositionLast.copy(this._localPosition);\n\t\t\t\t\t\t\t\t\t\tthis._localPosition.copy(gripPose.transform.position);\n\t\t\t\t\t\t\t\t\t\tthis._localRotation.copy(gripPose.transform.orientation);\n\t\t\t\t\t\t\t\t\t\tthis._velocitiesAvailable = true;\n\t\t\t\t\t\t\t\t\t\tif (this._manager.input.velocitiesSupported && gripPose.linearVelocity) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._linearVelocity.copy(gripPose.linearVelocity);\n\t\t\t\t\t\t\t\t\t\t} else if (dt > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tvec3A$1.sub2(this._localPosition, this._localPositionLast).divScalar(dt);\n\t\t\t\t\t\t\t\t\t\t\t\tthis._linearVelocity.lerp(this._linearVelocity, vec3A$1, 0.15);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthis._velocitiesAvailable = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace);\n\t\t\t\t\t\tif (targetRayPose) {\n\t\t\t\t\t\t\t\tthis._dirtyRay = true;\n\t\t\t\t\t\t\t\tthis._rayLocal.origin.copy(targetRayPose.transform.position);\n\t\t\t\t\t\t\t\tthis._rayLocal.direction.set(0, 0, -1);\n\t\t\t\t\t\t\t\tquat.copy(targetRayPose.transform.orientation);\n\t\t\t\t\t\t\t\tquat.transformVector(this._rayLocal.direction, this._rayLocal.direction);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_updateTransforms() {\n\t\t\t\tif (this._dirtyLocal) {\n\t\t\t\t\t\tthis._dirtyLocal = false;\n\t\t\t\t\t\tthis._localTransform.setTRS(this._localPosition, this._localRotation, Vec3.ONE);\n\t\t\t\t}\n\t\t\t\tconst parent = this._manager.camera.parent;\n\t\t\t\tif (parent) {\n\t\t\t\t\t\tthis._worldTransform.mul2(parent.getWorldTransform(), this._localTransform);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._worldTransform.copy(this._localTransform);\n\t\t\t\t}\n\t\t}\n\t\t_updateRayTransforms() {\n\t\t\t\tconst dirty = this._dirtyRay;\n\t\t\t\tthis._dirtyRay = false;\n\t\t\t\tconst parent = this._manager.camera.parent;\n\t\t\t\tif (parent) {\n\t\t\t\t\t\tconst parentTransform = this._manager.camera.parent.getWorldTransform();\n\t\t\t\t\t\tparentTransform.getTranslation(this._position);\n\t\t\t\t\t\tthis._rotation.setFromMat4(parentTransform);\n\t\t\t\t\t\tthis._rotation.transformVector(this._rayLocal.origin, this._ray.origin);\n\t\t\t\t\t\tthis._ray.origin.add(this._position);\n\t\t\t\t\t\tthis._rotation.transformVector(this._rayLocal.direction, this._ray.direction);\n\t\t\t\t} else if (dirty) {\n\t\t\t\t\t\tthis._ray.origin.copy(this._rayLocal.origin);\n\t\t\t\t\t\tthis._ray.direction.copy(this._rayLocal.direction);\n\t\t\t\t}\n\t\t}\n\t\tgetPosition() {\n\t\t\t\tif (!this._position) return null;\n\t\t\t\tthis._updateTransforms();\n\t\t\t\tthis._worldTransform.getTranslation(this._position);\n\t\t\t\treturn this._position;\n\t\t}\n\t\tgetLocalPosition() {\n\t\t\t\treturn this._localPosition;\n\t\t}\n\t\tgetRotation() {\n\t\t\t\tif (!this._rotation) return null;\n\t\t\t\tthis._updateTransforms();\n\t\t\t\tthis._rotation.setFromMat4(this._worldTransform);\n\t\t\t\treturn this._rotation;\n\t\t}\n\t\tgetLocalRotation() {\n\t\t\t\treturn this._localRotation;\n\t\t}\n\t\tgetLinearVelocity() {\n\t\t\t\tif (!this._velocitiesAvailable) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._linearVelocity;\n\t\t}\n\t\tgetOrigin() {\n\t\t\t\tthis._updateRayTransforms();\n\t\t\t\treturn this._ray.origin;\n\t\t}\n\t\tgetDirection() {\n\t\t\t\tthis._updateRayTransforms();\n\t\t\t\treturn this._ray.direction;\n\t\t}\n\t\thitTestStart(options = {}) {\n\t\t\t\toptions.inputSource = this;\n\t\t\t\toptions.profile = this._xrInputSource.profiles[0];\n\t\t\t\tconst callback = options.callback;\n\t\t\t\toptions.callback = (err, hitTestSource)=>{\n\t\t\t\t\t\tif (hitTestSource) this.onHitTestSourceAdd(hitTestSource);\n\t\t\t\t\t\tif (callback) callback(err, hitTestSource);\n\t\t\t\t};\n\t\t\t\tthis._manager.hitTest.start(options);\n\t\t}\n\t\tonHitTestSourceAdd(hitTestSource) {\n\t\t\t\tthis._hitTestSources.push(hitTestSource);\n\t\t\t\tthis.fire('hittest:add', hitTestSource);\n\t\t\t\thitTestSource.on('result', (position, rotation, inputSource, hitTestResult)=>{\n\t\t\t\t\t\tif (inputSource !== this) return;\n\t\t\t\t\t\tthis.fire('hittest:result', hitTestSource, position, rotation, hitTestResult);\n\t\t\t\t});\n\t\t\t\thitTestSource.once('remove', ()=>{\n\t\t\t\t\t\tthis.onHitTestSourceRemove(hitTestSource);\n\t\t\t\t\t\tthis.fire('hittest:remove', hitTestSource);\n\t\t\t\t});\n\t\t}\n\t\tonHitTestSourceRemove(hitTestSource) {\n\t\t\t\tconst ind = this._hitTestSources.indexOf(hitTestSource);\n\t\t\t\tif (ind !== -1) this._hitTestSources.splice(ind, 1);\n\t\t}\n}\n\nclass XrInput extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECT = 'select';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTSTART = 'selectstart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SELECTEND = 'selectend';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SQUEEZE = 'squeeze';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SQUEEZESTART = 'squeezestart';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_SQUEEZEEND = 'squeezeend';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._inputSources = [], this.velocitiesSupported = false;\n\t\t\t\tthis.manager = manager;\n\t\t\t\tthis.velocitiesSupported = !!(platform.browser && window.XRPose?.prototype?.hasOwnProperty('linearVelocity'));\n\t\t\t\tthis._onInputSourcesChangeEvt = (evt)=>{\n\t\t\t\t\t\tthis._onInputSourcesChange(evt);\n\t\t\t\t};\n\t\t\t\tthis.manager.on('start', this._onSessionStart, this);\n\t\t\t\tthis.manager.on('end', this._onSessionEnd, this);\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tconst session = this.manager.session;\n\t\t\t\tsession.addEventListener('inputsourceschange', this._onInputSourcesChangeEvt);\n\t\t\t\tsession.addEventListener('select', (evt)=>{\n\t\t\t\t\t\tconst inputSource = this._getByInputSource(evt.inputSource);\n\t\t\t\t\t\tinputSource.update(evt.frame);\n\t\t\t\t\t\tinputSource.fire('select', evt);\n\t\t\t\t\t\tthis.fire('select', inputSource, evt);\n\t\t\t\t});\n\t\t\t\tsession.addEventListener('selectstart', (evt)=>{\n\t\t\t\t\t\tconst inputSource = this._getByInputSource(evt.inputSource);\n\t\t\t\t\t\tinputSource.update(evt.frame);\n\t\t\t\t\t\tinputSource._selecting = true;\n\t\t\t\t\t\tinputSource.fire('selectstart', evt);\n\t\t\t\t\t\tthis.fire('selectstart', inputSource, evt);\n\t\t\t\t});\n\t\t\t\tsession.addEventListener('selectend', (evt)=>{\n\t\t\t\t\t\tconst inputSource = this._getByInputSource(evt.inputSource);\n\t\t\t\t\t\tinputSource.update(evt.frame);\n\t\t\t\t\t\tinputSource._selecting = false;\n\t\t\t\t\t\tinputSource.fire('selectend', evt);\n\t\t\t\t\t\tthis.fire('selectend', inputSource, evt);\n\t\t\t\t});\n\t\t\t\tsession.addEventListener('squeeze', (evt)=>{\n\t\t\t\t\t\tconst inputSource = this._getByInputSource(evt.inputSource);\n\t\t\t\t\t\tinputSource.update(evt.frame);\n\t\t\t\t\t\tinputSource.fire('squeeze', evt);\n\t\t\t\t\t\tthis.fire('squeeze', inputSource, evt);\n\t\t\t\t});\n\t\t\t\tsession.addEventListener('squeezestart', (evt)=>{\n\t\t\t\t\t\tconst inputSource = this._getByInputSource(evt.inputSource);\n\t\t\t\t\t\tinputSource.update(evt.frame);\n\t\t\t\t\t\tinputSource._squeezing = true;\n\t\t\t\t\t\tinputSource.fire('squeezestart', evt);\n\t\t\t\t\t\tthis.fire('squeezestart', inputSource, evt);\n\t\t\t\t});\n\t\t\t\tsession.addEventListener('squeezeend', (evt)=>{\n\t\t\t\t\t\tconst inputSource = this._getByInputSource(evt.inputSource);\n\t\t\t\t\t\tinputSource.update(evt.frame);\n\t\t\t\t\t\tinputSource._squeezing = false;\n\t\t\t\t\t\tinputSource.fire('squeezeend', evt);\n\t\t\t\t\t\tthis.fire('squeezeend', inputSource, evt);\n\t\t\t\t});\n\t\t\t\tconst inputSources = session.inputSources;\n\t\t\t\tfor(let i = 0; i < inputSources.length; i++){\n\t\t\t\t\t\tthis._addInputSource(inputSources[i]);\n\t\t\t\t}\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tlet i = this._inputSources.length;\n\t\t\t\twhile(i--){\n\t\t\t\t\t\tconst inputSource = this._inputSources[i];\n\t\t\t\t\t\tthis._inputSources.splice(i, 1);\n\t\t\t\t\t\tinputSource.fire('remove');\n\t\t\t\t\t\tthis.fire('remove', inputSource);\n\t\t\t\t}\n\t\t\t\tconst session = this.manager.session;\n\t\t\t\tsession.removeEventListener('inputsourceschange', this._onInputSourcesChangeEvt);\n\t\t}\n\t\t_onInputSourcesChange(evt) {\n\t\t\t\tfor(let i = 0; i < evt.removed.length; i++){\n\t\t\t\t\t\tthis._removeInputSource(evt.removed[i]);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < evt.added.length; i++){\n\t\t\t\t\t\tthis._addInputSource(evt.added[i]);\n\t\t\t\t}\n\t\t}\n\t\t_getByInputSource(xrInputSource) {\n\t\t\t\tfor(let i = 0; i < this._inputSources.length; i++){\n\t\t\t\t\t\tif (this._inputSources[i].inputSource === xrInputSource) {\n\t\t\t\t\t\t\t\treturn this._inputSources[i];\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t}\n\t\t_addInputSource(xrInputSource) {\n\t\t\t\tif (this._getByInputSource(xrInputSource)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst inputSource = new XrInputSource(this.manager, xrInputSource);\n\t\t\t\tthis._inputSources.push(inputSource);\n\t\t\t\tthis.fire('add', inputSource);\n\t\t}\n\t\t_removeInputSource(xrInputSource) {\n\t\t\t\tfor(let i = 0; i < this._inputSources.length; i++){\n\t\t\t\t\t\tif (this._inputSources[i].inputSource !== xrInputSource) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst inputSource = this._inputSources[i];\n\t\t\t\t\t\tthis._inputSources.splice(i, 1);\n\t\t\t\t\t\tlet h = inputSource.hitTestSources.length;\n\t\t\t\t\t\twhile(h--){\n\t\t\t\t\t\t\t\tinputSource.hitTestSources[h].remove();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinputSource.fire('remove');\n\t\t\t\t\t\tthis.fire('remove', inputSource);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tfor(let i = 0; i < this._inputSources.length; i++){\n\t\t\t\t\t\tthis._inputSources[i].update(frame);\n\t\t\t\t}\n\t\t}\n\t\tget inputSources() {\n\t\t\t\treturn this._inputSources;\n\t\t}\n}\n\nconst vec3A = new Vec3();\nconst vec3B = new Vec3();\nconst mat4A = new Mat4();\nconst mat4B = new Mat4();\nclass XrLightEstimation extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_AVAILABLE = 'available';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._supported = false, this._available = false, this._lightProbeRequested = false, this._lightProbe = null, this._intensity = 0, this._rotation = new Quat(), this._color = new Color(), this._sphericalHarmonics = new Float32Array(27);\n\t\t\t\tthis._manager = manager;\n\t\t\t\tthis._manager.on('start', this._onSessionStart, this);\n\t\t\t\tthis._manager.on('end', this._onSessionEnd, this);\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tconst supported = !!this._manager.session.requestLightProbe;\n\t\t\t\tif (!supported) return;\n\t\t\t\tthis._supported = true;\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tthis._supported = false;\n\t\t\t\tthis._available = false;\n\t\t\t\tthis._lightProbeRequested = false;\n\t\t\t\tthis._lightProbe = null;\n\t\t}\n\t\tstart() {\n\t\t\t\tlet err;\n\t\t\t\tif (!this._manager.session) {\n\t\t\t\t\t\terr = new Error('XR session is not running');\n\t\t\t\t}\n\t\t\t\tif (!err && this._manager.type !== XRTYPE_AR) {\n\t\t\t\t\t\terr = new Error('XR session type is not AR');\n\t\t\t\t}\n\t\t\t\tif (!err && !this._supported) {\n\t\t\t\t\t\terr = new Error('light-estimation is not supported');\n\t\t\t\t}\n\t\t\t\tif (!err && this._lightProbe || this._lightProbeRequested) {\n\t\t\t\t\t\terr = new Error('light estimation is already requested');\n\t\t\t\t}\n\t\t\t\tif (err) {\n\t\t\t\t\t\tthis.fire('error', err);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._lightProbeRequested = true;\n\t\t\t\tthis._manager.session.requestLightProbe().then((lightProbe)=>{\n\t\t\t\t\t\tconst wasRequested = this._lightProbeRequested;\n\t\t\t\t\t\tthis._lightProbeRequested = false;\n\t\t\t\t\t\tif (this._manager.active) {\n\t\t\t\t\t\t\t\tif (wasRequested) {\n\t\t\t\t\t\t\t\t\t\tthis._lightProbe = lightProbe;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.fire('error', new Error('XR session is not active'));\n\t\t\t\t\t\t}\n\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\tthis._lightProbeRequested = false;\n\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t});\n\t\t}\n\t\tend() {\n\t\t\t\tthis._lightProbeRequested = false;\n\t\t\t\tthis._lightProbe = null;\n\t\t\t\tthis._available = false;\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._lightProbe) return;\n\t\t\t\tconst lightEstimate = frame.getLightEstimate(this._lightProbe);\n\t\t\t\tif (!lightEstimate) return;\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\tthis._available = true;\n\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t}\n\t\t\t\tconst pli = lightEstimate.primaryLightIntensity;\n\t\t\t\tthis._intensity = Math.max(1.0, Math.max(pli.x, Math.max(pli.y, pli.z)));\n\t\t\t\tvec3A.copy(pli).mulScalar(1 / this._intensity);\n\t\t\t\tthis._color.set(vec3A.x, vec3A.y, vec3A.z);\n\t\t\t\tvec3A.set(0, 0, 0);\n\t\t\t\tvec3B.copy(lightEstimate.primaryLightDirection);\n\t\t\t\tmat4A.setLookAt(vec3B, vec3A, Vec3.UP);\n\t\t\t\tmat4B.setFromAxisAngle(Vec3.RIGHT, 90);\n\t\t\t\tmat4A.mul(mat4B);\n\t\t\t\tthis._rotation.setFromMat4(mat4A);\n\t\t\t\tthis._sphericalHarmonics.set(lightEstimate.sphericalHarmonicsCoefficients);\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget available() {\n\t\t\t\treturn this._available;\n\t\t}\n\t\tget intensity() {\n\t\t\t\treturn this._available ? this._intensity : null;\n\t\t}\n\t\tget color() {\n\t\t\t\treturn this._available ? this._color : null;\n\t\t}\n\t\tget rotation() {\n\t\t\t\treturn this._available ? this._rotation : null;\n\t\t}\n\t\tget sphericalHarmonics() {\n\t\t\t\treturn this._available ? this._sphericalHarmonics : null;\n\t\t}\n}\n\nlet ids = 0;\nclass XrPlane extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_CHANGE = 'change';\n\t\t}\n\t\tconstructor(planeDetection, xrPlane){\n\t\t\t\tsuper(), this._position = new Vec3(), this._rotation = new Quat();\n\t\t\t\tthis._id = ++ids;\n\t\t\t\tthis._planeDetection = planeDetection;\n\t\t\t\tthis._xrPlane = xrPlane;\n\t\t\t\tthis._lastChangedTime = xrPlane.lastChangedTime;\n\t\t\t\tthis._orientation = xrPlane.orientation;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (!this._xrPlane) return;\n\t\t\t\tthis._xrPlane = null;\n\t\t\t\tthis.fire('remove');\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tconst manager = this._planeDetection._manager;\n\t\t\t\tconst pose = frame.getPose(this._xrPlane.planeSpace, manager._referenceSpace);\n\t\t\t\tif (pose) {\n\t\t\t\t\t\tthis._position.copy(pose.transform.position);\n\t\t\t\t\t\tthis._rotation.copy(pose.transform.orientation);\n\t\t\t\t}\n\t\t\t\tif (this._lastChangedTime !== this._xrPlane.lastChangedTime) {\n\t\t\t\t\t\tthis._lastChangedTime = this._xrPlane.lastChangedTime;\n\t\t\t\t\t\tthis.fire('change');\n\t\t\t\t}\n\t\t}\n\t\tgetPosition() {\n\t\t\t\treturn this._position;\n\t\t}\n\t\tgetRotation() {\n\t\t\t\treturn this._rotation;\n\t\t}\n\t\tget id() {\n\t\t\t\treturn this._id;\n\t\t}\n\t\tget orientation() {\n\t\t\t\treturn this._orientation;\n\t\t}\n\t\tget points() {\n\t\t\t\treturn this._xrPlane.polygon;\n\t\t}\n\t\tget label() {\n\t\t\t\treturn this._xrPlane.semanticLabel || '';\n\t\t}\n}\n\nclass XrPlaneDetection extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_AVAILABLE = 'available';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_UNAVAILABLE = 'unavailable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._supported = platform.browser && !!window.XRPlane, this._available = false, this._planesIndex = new Map(), this._planes = [];\n\t\t\t\tthis._manager = manager;\n\t\t\t\tif (this._supported) {\n\t\t\t\t\t\tthis._manager.on('start', this._onSessionStart, this);\n\t\t\t\t\t\tthis._manager.on('end', this._onSessionEnd, this);\n\t\t\t\t}\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tif (this._manager.session.enabledFeatures) {\n\t\t\t\t\t\tconst available = this._manager.session.enabledFeatures.indexOf('plane-detection') !== -1;\n\t\t\t\t\t\tif (available) {\n\t\t\t\t\t\t\t\tthis._available = true;\n\t\t\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tfor(let i = 0; i < this._planes.length; i++){\n\t\t\t\t\t\tthis._planes[i].destroy();\n\t\t\t\t\t\tthis.fire('remove', this._planes[i]);\n\t\t\t\t}\n\t\t\t\tthis._planesIndex.clear();\n\t\t\t\tthis._planes.length = 0;\n\t\t\t\tif (this._available) {\n\t\t\t\t\t\tthis._available = false;\n\t\t\t\t\t\tthis.fire('unavailable');\n\t\t\t\t}\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\tif (!this._manager.session.enabledFeatures && frame.detectedPlanes.size) {\n\t\t\t\t\t\t\t\tthis._available = true;\n\t\t\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst detectedPlanes = frame.detectedPlanes;\n\t\t\t\tfor (const [xrPlane, plane] of this._planesIndex){\n\t\t\t\t\t\tif (detectedPlanes.has(xrPlane)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._planesIndex.delete(xrPlane);\n\t\t\t\t\t\tthis._planes.splice(this._planes.indexOf(plane), 1);\n\t\t\t\t\t\tplane.destroy();\n\t\t\t\t\t\tthis.fire('remove', plane);\n\t\t\t\t}\n\t\t\t\tfor (const xrPlane of detectedPlanes){\n\t\t\t\t\t\tlet plane = this._planesIndex.get(xrPlane);\n\t\t\t\t\t\tif (!plane) {\n\t\t\t\t\t\t\t\tplane = new XrPlane(this, xrPlane);\n\t\t\t\t\t\t\t\tthis._planesIndex.set(xrPlane, plane);\n\t\t\t\t\t\t\t\tthis._planes.push(plane);\n\t\t\t\t\t\t\t\tplane.update(frame);\n\t\t\t\t\t\t\t\tthis.fire('add', plane);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tplane.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget available() {\n\t\t\t\treturn this._available;\n\t\t}\n\t\tget planes() {\n\t\t\t\treturn this._planes;\n\t\t}\n}\n\nclass XrAnchor extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_DESTROY = 'destroy';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_CHANGE = 'change';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_PERSIST = 'persist';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_FORGET = 'forget';\n\t\t}\n\t\tconstructor(anchors, xrAnchor, uuid = null){\n\t\t\t\tsuper(), this._position = new Vec3(), this._rotation = new Quat(), this._uuid = null, this._uuidRequests = null;\n\t\t\t\tthis._anchors = anchors;\n\t\t\t\tthis._xrAnchor = xrAnchor;\n\t\t\t\tthis._uuid = uuid;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (!this._xrAnchor) return;\n\t\t\t\tconst xrAnchor = this._xrAnchor;\n\t\t\t\tthis._xrAnchor.delete();\n\t\t\t\tthis._xrAnchor = null;\n\t\t\t\tthis.fire('destroy', xrAnchor, this);\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._xrAnchor) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst pose = frame.getPose(this._xrAnchor.anchorSpace, this._anchors.manager._referenceSpace);\n\t\t\t\tif (pose) {\n\t\t\t\t\t\tif (this._position.equals(pose.transform.position) && this._rotation.equals(pose.transform.orientation)) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._position.copy(pose.transform.position);\n\t\t\t\t\t\tthis._rotation.copy(pose.transform.orientation);\n\t\t\t\t\t\tthis.fire('change');\n\t\t\t\t}\n\t\t}\n\t\tgetPosition() {\n\t\t\t\treturn this._position;\n\t\t}\n\t\tgetRotation() {\n\t\t\t\treturn this._rotation;\n\t\t}\n\t\tpersist(callback) {\n\t\t\t\tif (!this._anchors.persistence) {\n\t\t\t\t\t\tcallback?.(new Error('Persistent Anchors are not supported'), null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._uuid) {\n\t\t\t\t\t\tcallback?.(null, this._uuid);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._uuidRequests) {\n\t\t\t\t\t\tif (callback) this._uuidRequests.push(callback);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._uuidRequests = [];\n\t\t\t\tthis._xrAnchor.requestPersistentHandle().then((uuid)=>{\n\t\t\t\t\t\tthis._uuid = uuid;\n\t\t\t\t\t\tthis._anchors._indexByUuid.set(this._uuid, this);\n\t\t\t\t\t\tcallback?.(null, uuid);\n\t\t\t\t\t\tfor (const uuidRequest of this._uuidRequests){\n\t\t\t\t\t\t\t\tuuidRequest(null, uuid);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._uuidRequests = null;\n\t\t\t\t\t\tthis.fire('persist', uuid);\n\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\tcallback?.(ex, null);\n\t\t\t\t\t\tfor (const uuidRequest of this._uuidRequests){\n\t\t\t\t\t\t\t\tuuidRequest(ex, null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._uuidRequests = null;\n\t\t\t\t});\n\t\t}\n\t\tforget(callback) {\n\t\t\t\tif (!this._uuid) {\n\t\t\t\t\t\tcallback?.(new Error('Anchor is not persistent'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._anchors.forget(this._uuid, (ex)=>{\n\t\t\t\t\t\tthis._uuid = null;\n\t\t\t\t\t\tcallback?.(ex);\n\t\t\t\t\t\tthis.fire('forget');\n\t\t\t\t});\n\t\t}\n\t\tget uuid() {\n\t\t\t\treturn this._uuid;\n\t\t}\n\t\tget persistent() {\n\t\t\t\treturn !!this._uuid;\n\t\t}\n}\n\nclass XrAnchors extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_AVAILABLE = 'available';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_UNAVAILABLE = 'unavailable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_DESTROY = 'destroy';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._supported = platform.browser && !!window.XRAnchor, this._available = false, this._checkingAvailability = false, this._persistence = platform.browser && !!window?.XRSession?.prototype.restorePersistentAnchor, this._creationQueue = [], this._index = new Map(), this._indexByUuid = new Map(), this._list = [], this._callbacksAnchors = new Map();\n\t\t\t\tthis.manager = manager;\n\t\t\t\tif (this._supported) {\n\t\t\t\t\t\tthis.manager.on('start', this._onSessionStart, this);\n\t\t\t\t\t\tthis.manager.on('end', this._onSessionEnd, this);\n\t\t\t\t}\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tconst available = this.manager.session.enabledFeatures?.indexOf('anchors') >= 0;\n\t\t\t\tif (!available) return;\n\t\t\t\tthis._available = available;\n\t\t\t\tthis.fire('available');\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tif (!this._available) return;\n\t\t\t\tthis._available = false;\n\t\t\t\tfor(let i = 0; i < this._creationQueue.length; i++){\n\t\t\t\t\t\tif (!this._creationQueue[i].callback) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._creationQueue[i].callback(new Error('session ended'), null);\n\t\t\t\t}\n\t\t\t\tthis._creationQueue.length = 0;\n\t\t\t\tthis._index.clear();\n\t\t\t\tthis._indexByUuid.clear();\n\t\t\t\tlet i = this._list.length;\n\t\t\t\twhile(i--){\n\t\t\t\t\t\tthis._list[i].destroy();\n\t\t\t\t}\n\t\t\t\tthis._list.length = 0;\n\t\t\t\tthis.fire('unavailable');\n\t\t}\n\t\t_createAnchor(xrAnchor, uuid = null) {\n\t\t\t\tconst anchor = new XrAnchor(this, xrAnchor, uuid);\n\t\t\t\tthis._index.set(xrAnchor, anchor);\n\t\t\t\tif (uuid) this._indexByUuid.set(uuid, anchor);\n\t\t\t\tthis._list.push(anchor);\n\t\t\t\tanchor.once('destroy', this._onAnchorDestroy, this);\n\t\t\t\treturn anchor;\n\t\t}\n\t\t_onAnchorDestroy(xrAnchor, anchor) {\n\t\t\t\tthis._index.delete(xrAnchor);\n\t\t\t\tif (anchor.uuid) this._indexByUuid.delete(anchor.uuid);\n\t\t\t\tconst ind = this._list.indexOf(anchor);\n\t\t\t\tif (ind !== -1) this._list.splice(ind, 1);\n\t\t\t\tthis.fire('destroy', anchor);\n\t\t}\n\t\tcreate(position, rotation, callback) {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\tcallback?.(new Error('Anchors API is not available'), null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (window.XRHitTestResult && position instanceof XRHitTestResult) {\n\t\t\t\t\t\tconst hitResult = position;\n\t\t\t\t\t\tcallback = rotation;\n\t\t\t\t\t\tif (!this._supported) {\n\t\t\t\t\t\t\t\tcallback?.(new Error('Anchors API is not supported'), null);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!hitResult.createAnchor) {\n\t\t\t\t\t\t\t\tcallback?.(new Error('Creating Anchor from Hit Test is not supported'), null);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\thitResult.createAnchor().then((xrAnchor)=>{\n\t\t\t\t\t\t\t\tconst anchor = this._createAnchor(xrAnchor);\n\t\t\t\t\t\t\t\tcallback?.(null, anchor);\n\t\t\t\t\t\t\t\tthis.fire('add', anchor);\n\t\t\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\t\t\tcallback?.(ex, null);\n\t\t\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._creationQueue.push({\n\t\t\t\t\t\t\t\ttransform: new XRRigidTransform(position, rotation),\n\t\t\t\t\t\t\t\tcallback: callback\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\t\trestore(uuid, callback) {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\tcallback?.(new Error('Anchors API is not available'), null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._persistence) {\n\t\t\t\t\t\tcallback?.(new Error('Anchor Persistence is not supported'), null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.manager.active) {\n\t\t\t\t\t\tcallback?.(new Error('WebXR session is not active'), null);\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.manager.session.restorePersistentAnchor(uuid).then((xrAnchor)=>{\n\t\t\t\t\t\tconst anchor = this._createAnchor(xrAnchor, uuid);\n\t\t\t\t\t\tcallback?.(null, anchor);\n\t\t\t\t\t\tthis.fire('add', anchor);\n\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\tcallback?.(ex, null);\n\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t});\n\t\t}\n\t\tforget(uuid, callback) {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\tcallback?.(new Error('Anchors API is not available'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._persistence) {\n\t\t\t\t\t\tcallback?.(new Error('Anchor Persistence is not supported'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.manager.active) {\n\t\t\t\t\t\tcallback?.(new Error('WebXR session is not active'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.manager.session.deletePersistentAnchor(uuid).then(()=>{\n\t\t\t\t\t\tcallback?.(null);\n\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\tcallback?.(ex);\n\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t});\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\tif (!this.manager.session.enabledFeatures && !this._checkingAvailability) {\n\t\t\t\t\t\t\t\tthis._checkingAvailability = true;\n\t\t\t\t\t\t\t\tframe.createAnchor(new XRRigidTransform(), this.manager._referenceSpace).then((xrAnchor)=>{\n\t\t\t\t\t\t\t\t\t\txrAnchor.delete();\n\t\t\t\t\t\t\t\t\t\tif (this.manager.active) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._available = true;\n\t\t\t\t\t\t\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}).catch(()=>{});\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._creationQueue.length) {\n\t\t\t\t\t\tfor(let i = 0; i < this._creationQueue.length; i++){\n\t\t\t\t\t\t\t\tconst request = this._creationQueue[i];\n\t\t\t\t\t\t\t\tframe.createAnchor(request.transform, this.manager._referenceSpace).then((xrAnchor)=>{\n\t\t\t\t\t\t\t\t\t\tif (request.callback) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._callbacksAnchors.set(xrAnchor, request.callback);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\t\t\t\t\tif (request.callback) {\n\t\t\t\t\t\t\t\t\t\t\t\trequest.callback(ex, null);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._creationQueue.length = 0;\n\t\t\t\t}\n\t\t\t\tfor (const [xrAnchor, anchor] of this._index){\n\t\t\t\t\t\tif (frame.trackedAnchors.has(xrAnchor)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._index.delete(xrAnchor);\n\t\t\t\t\t\tanchor.destroy();\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < this._list.length; i++){\n\t\t\t\t\t\tthis._list[i].update(frame);\n\t\t\t\t}\n\t\t\t\tfor (const xrAnchor of frame.trackedAnchors){\n\t\t\t\t\t\tif (this._index.has(xrAnchor)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst tmp = xrAnchor.anchorSpace;\n\t\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst anchor = this._createAnchor(xrAnchor);\n\t\t\t\t\t\tanchor.update(frame);\n\t\t\t\t\t\tconst callback = this._callbacksAnchors.get(xrAnchor);\n\t\t\t\t\t\tif (callback) {\n\t\t\t\t\t\t\t\tthis._callbacksAnchors.delete(xrAnchor);\n\t\t\t\t\t\t\t\tcallback(null, anchor);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.fire('add', anchor);\n\t\t\t\t}\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget available() {\n\t\t\t\treturn this._available;\n\t\t}\n\t\tget persistence() {\n\t\t\t\treturn this._persistence;\n\t\t}\n\t\tget uuids() {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tif (!this._persistence) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tif (!this.manager.active) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this.manager.session.persistentAnchors;\n\t\t}\n\t\tget list() {\n\t\t\t\treturn this._list;\n\t\t}\n}\n\nclass XrMesh extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_CHANGE = 'change';\n\t\t}\n\t\tconstructor(meshDetection, xrMesh){\n\t\t\t\tsuper(), this._lastChanged = 0, this._position = new Vec3(), this._rotation = new Quat();\n\t\t\t\tthis._meshDetection = meshDetection;\n\t\t\t\tthis._xrMesh = xrMesh;\n\t\t\t\tthis._lastChanged = this._xrMesh.lastChangedTime;\n\t\t}\n\t\tget xrMesh() {\n\t\t\t\treturn this._xrMesh;\n\t\t}\n\t\tget label() {\n\t\t\t\treturn this._xrMesh.semanticLabel || '';\n\t\t}\n\t\tget vertices() {\n\t\t\t\treturn this._xrMesh.vertices;\n\t\t}\n\t\tget indices() {\n\t\t\t\treturn this._xrMesh.indices;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tif (!this._xrMesh) return;\n\t\t\t\tthis._xrMesh = null;\n\t\t\t\tthis.fire('remove');\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tconst manager = this._meshDetection._manager;\n\t\t\t\tconst pose = frame.getPose(this._xrMesh.meshSpace, manager._referenceSpace);\n\t\t\t\tif (pose) {\n\t\t\t\t\t\tthis._position.copy(pose.transform.position);\n\t\t\t\t\t\tthis._rotation.copy(pose.transform.orientation);\n\t\t\t\t}\n\t\t\t\tif (this._lastChanged !== this._xrMesh.lastChangedTime) {\n\t\t\t\t\t\tthis._lastChanged = this._xrMesh.lastChangedTime;\n\t\t\t\t\t\tthis.fire('change');\n\t\t\t\t}\n\t\t}\n\t\tgetPosition() {\n\t\t\t\treturn this._position;\n\t\t}\n\t\tgetRotation() {\n\t\t\t\treturn this._rotation;\n\t\t}\n}\n\nclass XrMeshDetection extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_AVAILABLE = 'available';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_UNAVAILABLE = 'unavailable';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._supported = platform.browser && !!window.XRMesh, this._available = false, this._index = new Map(), this._list = [];\n\t\t\t\tthis._manager = manager;\n\t\t\t\tif (this._supported) {\n\t\t\t\t\t\tthis._manager.on('start', this._onSessionStart, this);\n\t\t\t\t\t\tthis._manager.on('end', this._onSessionEnd, this);\n\t\t\t\t}\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._available) {\n\t\t\t\t\t\tif (!this._manager.session.enabledFeatures && frame.detectedMeshes.size) {\n\t\t\t\t\t\t\t\tthis._available = true;\n\t\t\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const xrMesh of frame.detectedMeshes){\n\t\t\t\t\t\tlet mesh = this._index.get(xrMesh);\n\t\t\t\t\t\tif (!mesh) {\n\t\t\t\t\t\t\t\tmesh = new XrMesh(this, xrMesh);\n\t\t\t\t\t\t\t\tthis._index.set(xrMesh, mesh);\n\t\t\t\t\t\t\t\tthis._list.push(mesh);\n\t\t\t\t\t\t\t\tmesh.update(frame);\n\t\t\t\t\t\t\t\tthis.fire('add', mesh);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmesh.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const mesh of this._index.values()){\n\t\t\t\t\t\tif (frame.detectedMeshes.has(mesh.xrMesh)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._removeMesh(mesh);\n\t\t\t\t}\n\t\t}\n\t\t_removeMesh(mesh) {\n\t\t\t\tthis._index.delete(mesh.xrMesh);\n\t\t\t\tthis._list.splice(this._list.indexOf(mesh), 1);\n\t\t\t\tmesh.destroy();\n\t\t\t\tthis.fire('remove', mesh);\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tif (this._manager.session.enabledFeatures) {\n\t\t\t\t\t\tconst available = this._manager.session.enabledFeatures.indexOf('mesh-detection') !== -1;\n\t\t\t\t\t\tif (!available) return;\n\t\t\t\t\t\tthis._available = available;\n\t\t\t\t\t\tthis.fire('available');\n\t\t\t\t}\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tif (!this._available) return;\n\t\t\t\tthis._available = false;\n\t\t\t\tfor (const mesh of this._index.values()){\n\t\t\t\t\t\tthis._removeMesh(mesh);\n\t\t\t\t}\n\t\t\t\tthis.fire('unavailable');\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget available() {\n\t\t\t\treturn this._available;\n\t\t}\n\t\tget meshes() {\n\t\t\t\treturn this._list;\n\t\t}\n}\n\nclass XrView extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_DEPTHRESIZE = 'depth:resize';\n\t\t}\n\t\tconstructor(manager, xrView, viewsCount){\n\t\t\t\tsuper(), this._positionData = new Float32Array(3), this._viewport = new Vec4(), this._projMat = new Mat4(), this._projViewOffMat = new Mat4(), this._viewMat = new Mat4(), this._viewOffMat = new Mat4(), this._viewMat3 = new Mat3(), this._viewInvMat = new Mat4(), this._viewInvOffMat = new Mat4(), this._xrCamera = null, this._textureColor = null, this._textureDepth = null, this._depthInfo = null, this._emptyDepthBuffer = new Uint8Array(32), this._depthMatrix = new Mat4();\n\t\t\t\tthis._manager = manager;\n\t\t\t\tthis._xrView = xrView;\n\t\t\t\tconst device = this._manager.app.graphicsDevice;\n\t\t\t\tif (this._manager.views.supportedColor) {\n\t\t\t\t\t\tthis._xrCamera = this._xrView.camera;\n\t\t\t\t\t\tif (this._manager.views.availableColor && this._xrCamera) {\n\t\t\t\t\t\t\t\tthis._textureColor = new Texture(device, {\n\t\t\t\t\t\t\t\t\t\tformat: PIXELFORMAT_RGB8,\n\t\t\t\t\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t\t\t\t\tminFilter: FILTER_LINEAR,\n\t\t\t\t\t\t\t\t\t\tmagFilter: FILTER_LINEAR,\n\t\t\t\t\t\t\t\t\t\twidth: this._xrCamera.width,\n\t\t\t\t\t\t\t\t\t\theight: this._xrCamera.height,\n\t\t\t\t\t\t\t\t\t\tname: `XrView-${this._xrView.eye}-Color`\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._manager.views.supportedDepth && this._manager.views.availableDepth) {\n\t\t\t\t\t\tconst filtering = this._manager.views.depthGpuOptimized ? FILTER_NEAREST : FILTER_LINEAR;\n\t\t\t\t\t\tthis._textureDepth = new Texture(device, {\n\t\t\t\t\t\t\t\tformat: this._manager.views.depthPixelFormat,\n\t\t\t\t\t\t\t\tarrayLength: viewsCount === 1 ? 0 : viewsCount,\n\t\t\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\t\t\tminFilter: filtering,\n\t\t\t\t\t\t\t\tmagFilter: filtering,\n\t\t\t\t\t\t\t\twidth: 4,\n\t\t\t\t\t\t\t\theight: 4,\n\t\t\t\t\t\t\t\tname: `XrView-${this._xrView.eye}-Depth`\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor(let i = 0; i < this._textureDepth._levels.length; i++){\n\t\t\t\t\t\t\t\tthis._textureDepth._levels[i] = this._emptyDepthBuffer;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._textureDepth.upload();\n\t\t\t\t}\n\t\t\t\tif (this._textureColor || this._textureDepth) {\n\t\t\t\t\t\tdevice.on('devicelost', this._onDeviceLost, this);\n\t\t\t\t}\n\t\t}\n\t\tget textureColor() {\n\t\t\t\treturn this._textureColor;\n\t\t}\n\t\tget textureDepth() {\n\t\t\t\treturn this._textureDepth;\n\t\t}\n\t\tget depthUvMatrix() {\n\t\t\t\treturn this._depthMatrix;\n\t\t}\n\t\tget depthValueToMeters() {\n\t\t\t\treturn this._depthInfo?.rawValueToMeters || 0;\n\t\t}\n\t\tget eye() {\n\t\t\t\treturn this._xrView.eye;\n\t\t}\n\t\tget viewport() {\n\t\t\t\treturn this._viewport;\n\t\t}\n\t\tget projMat() {\n\t\t\t\treturn this._projMat;\n\t\t}\n\t\tget projViewOffMat() {\n\t\t\t\treturn this._projViewOffMat;\n\t\t}\n\t\tget viewOffMat() {\n\t\t\t\treturn this._viewOffMat;\n\t\t}\n\t\tget viewInvOffMat() {\n\t\t\t\treturn this._viewInvOffMat;\n\t\t}\n\t\tget viewMat3() {\n\t\t\t\treturn this._viewMat3;\n\t\t}\n\t\tget positionData() {\n\t\t\t\treturn this._positionData;\n\t\t}\n\t\tupdate(frame, xrView) {\n\t\t\t\tthis._xrView = xrView;\n\t\t\t\tif (this._manager.views.availableColor) {\n\t\t\t\t\t\tthis._xrCamera = this._xrView.camera;\n\t\t\t\t}\n\t\t\t\tconst layer = frame.session.renderState.baseLayer;\n\t\t\t\tconst viewport = layer.getViewport(this._xrView);\n\t\t\t\tthis._viewport.x = viewport.x;\n\t\t\t\tthis._viewport.y = viewport.y;\n\t\t\t\tthis._viewport.z = viewport.width;\n\t\t\t\tthis._viewport.w = viewport.height;\n\t\t\t\tthis._projMat.set(this._xrView.projectionMatrix);\n\t\t\t\tthis._viewMat.set(this._xrView.transform.inverse.matrix);\n\t\t\t\tthis._viewInvMat.set(this._xrView.transform.matrix);\n\t\t\t\tthis._updateTextureColor();\n\t\t\t\tthis._updateDepth(frame);\n\t\t}\n\t\t_updateTextureColor() {\n\t\t\t\tif (!this._manager.views.availableColor || !this._xrCamera || !this._textureColor) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst binding = this._manager.webglBinding;\n\t\t\t\tif (!binding) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst texture = binding.getCameraImage(this._xrCamera);\n\t\t\t\tif (!texture) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst device = this._manager.app.graphicsDevice;\n\t\t\t\tconst gl = device.gl;\n\t\t\t\tif (!this._frameBufferSource) {\n\t\t\t\t\t\tthis._frameBufferSource = gl.createFramebuffer();\n\t\t\t\t\t\tthis._frameBuffer = gl.createFramebuffer();\n\t\t\t\t} else {\n\t\t\t\t\t\tconst attachmentBaseConstant = gl.COLOR_ATTACHMENT0;\n\t\t\t\t\t\tconst width = this._xrCamera.width;\n\t\t\t\t\t\tconst height = this._xrCamera.height;\n\t\t\t\t\t\tdevice.setFramebuffer(this._frameBufferSource);\n\t\t\t\t\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentBaseConstant, gl.TEXTURE_2D, texture, 0);\n\t\t\t\t\t\tdevice.setFramebuffer(this._frameBuffer);\n\t\t\t\t\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentBaseConstant, gl.TEXTURE_2D, this._textureColor.impl._glTexture, 0);\n\t\t\t\t\t\tgl.bindFramebuffer(gl.READ_FRAMEBUFFER, this._frameBufferSource);\n\t\t\t\t\t\tgl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._frameBuffer);\n\t\t\t\t\t\tgl.blitFramebuffer(0, height, width, 0, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);\n\t\t\t\t}\n\t\t}\n\t\t_updateDepth(frame) {\n\t\t\t\tif (!this._manager.views.availableDepth || !this._textureDepth) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst gpu = this._manager.views.depthGpuOptimized;\n\t\t\t\tconst infoSource = gpu ? this._manager.webglBinding : frame;\n\t\t\t\tif (!infoSource) {\n\t\t\t\t\t\tthis._depthInfo = null;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst depthInfo = infoSource.getDepthInformation(this._xrView);\n\t\t\t\tif (!depthInfo) {\n\t\t\t\t\t\tthis._depthInfo = null;\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet matrixDirty = !this._depthInfo !== !depthInfo;\n\t\t\t\tthis._depthInfo = depthInfo;\n\t\t\t\tconst width = this._depthInfo?.width || 4;\n\t\t\t\tconst height = this._depthInfo?.height || 4;\n\t\t\t\tlet resized = false;\n\t\t\t\tif (this._textureDepth.width !== width || this._textureDepth.height !== height) {\n\t\t\t\t\t\tthis._textureDepth._width = width;\n\t\t\t\t\t\tthis._textureDepth._height = height;\n\t\t\t\t\t\tmatrixDirty = true;\n\t\t\t\t\t\tresized = true;\n\t\t\t\t}\n\t\t\t\tif (matrixDirty) {\n\t\t\t\t\t\tif (this._depthInfo) {\n\t\t\t\t\t\t\t\tthis._depthMatrix.data.set(this._depthInfo.normDepthBufferFromNormView.matrix);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._depthMatrix.setIdentity();\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this._depthInfo) {\n\t\t\t\t\t\tif (gpu) {\n\t\t\t\t\t\t\t\tif (this._depthInfo.texture) {\n\t\t\t\t\t\t\t\t\t\tconst gl = this._manager.app.graphicsDevice.gl;\n\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glTexture = this._depthInfo.texture;\n\t\t\t\t\t\t\t\t\t\tif (this._depthInfo.textureType === 'texture-array') {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glTarget = gl.TEXTURE_2D_ARRAY;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glTarget = gl.TEXTURE_2D;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tswitch(this._manager.views.depthPixelFormat){\n\t\t\t\t\t\t\t\t\t\t\t\tcase PIXELFORMAT_R32F:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glInternalFormat = gl.R32F;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glPixelType = gl.FLOAT;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glFormat = gl.RED;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\tcase PIXELFORMAT_DEPTH:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glInternalFormat = gl.DEPTH_COMPONENT16;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glPixelType = gl.UNSIGNED_SHORT;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glFormat = gl.DEPTH_COMPONENT;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthis._textureDepth.impl._glCreated = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis._textureDepth._levels[0] = new Uint8Array(this._depthInfo.data);\n\t\t\t\t\t\t\t\tthis._textureDepth.upload();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._textureDepth._levels[0] = this._emptyDepthBuffer;\n\t\t\t\t\t\tthis._textureDepth.upload();\n\t\t\t\t}\n\t\t\t\tif (resized) this.fire('depth:resize', width, height);\n\t\t}\n\t\tupdateTransforms(transform) {\n\t\t\t\tif (transform) {\n\t\t\t\t\t\tthis._viewInvOffMat.mul2(transform, this._viewInvMat);\n\t\t\t\t\t\tthis.viewOffMat.copy(this._viewInvOffMat).invert();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._viewInvOffMat.copy(this._viewInvMat);\n\t\t\t\t\t\tthis.viewOffMat.copy(this._viewMat);\n\t\t\t\t}\n\t\t\t\tthis._viewMat3.setFromMat4(this._viewOffMat);\n\t\t\t\tthis._projViewOffMat.mul2(this._projMat, this._viewOffMat);\n\t\t\t\tthis._positionData[0] = this._viewInvOffMat.data[12];\n\t\t\t\tthis._positionData[1] = this._viewInvOffMat.data[13];\n\t\t\t\tthis._positionData[2] = this._viewInvOffMat.data[14];\n\t\t}\n\t\t_onDeviceLost() {\n\t\t\t\tthis._frameBufferSource = null;\n\t\t\t\tthis._frameBuffer = null;\n\t\t\t\tthis._depthInfo = null;\n\t\t}\n\t\tgetDepth(u, v) {\n\t\t\t\tif (this._manager.views.depthGpuOptimized) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._depthInfo?.getDepthInMeters(u, v) ?? null;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._depthInfo = null;\n\t\t\t\tif (this._textureColor) {\n\t\t\t\t\t\tthis._textureColor.destroy();\n\t\t\t\t\t\tthis._textureColor = null;\n\t\t\t\t}\n\t\t\t\tif (this._textureDepth) {\n\t\t\t\t\t\tthis._textureDepth.destroy();\n\t\t\t\t\t\tthis._textureDepth = null;\n\t\t\t\t}\n\t\t\t\tif (this._frameBufferSource) {\n\t\t\t\t\t\tconst gl = this._manager.app.graphicsDevice.gl;\n\t\t\t\t\t\tgl.deleteFramebuffer(this._frameBufferSource);\n\t\t\t\t\t\tthis._frameBufferSource = null;\n\t\t\t\t\t\tgl.deleteFramebuffer(this._frameBuffer);\n\t\t\t\t\t\tthis._frameBuffer = null;\n\t\t\t\t}\n\t\t}\n}\n\nclass XrViews extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_ADD = 'add';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_REMOVE = 'remove';\n\t\t}\n\t\tconstructor(manager){\n\t\t\t\tsuper(), this._index = new Map(), this._indexTmp = new Map(), this._list = [], this._supportedColor = platform.browser && !!window.XRCamera && !!window.XRWebGLBinding, this._supportedDepth = platform.browser && !!window.XRDepthInformation, this._availableColor = false, this._availableDepth = false, this._depthUsage = '', this._depthFormat = '', this._depthFormats = {\n\t\t\t\t\t\t[XRDEPTHSENSINGFORMAT_L8A8]: PIXELFORMAT_LA8,\n\t\t\t\t\t\t[XRDEPTHSENSINGFORMAT_R16U]: PIXELFORMAT_DEPTH,\n\t\t\t\t\t\t[XRDEPTHSENSINGFORMAT_F32]: PIXELFORMAT_R32F\n\t\t\t\t};\n\t\t\t\tthis._manager = manager;\n\t\t\t\tthis._manager.on('start', this._onSessionStart, this);\n\t\t\t\tthis._manager.on('end', this._onSessionEnd, this);\n\t\t}\n\t\tget list() {\n\t\t\t\treturn this._list;\n\t\t}\n\t\tget supportedColor() {\n\t\t\t\treturn this._supportedColor;\n\t\t}\n\t\tget supportedDepth() {\n\t\t\t\treturn this._supportedDepth;\n\t\t}\n\t\tget availableColor() {\n\t\t\t\treturn this._availableColor;\n\t\t}\n\t\tget availableDepth() {\n\t\t\t\treturn this._availableDepth;\n\t\t}\n\t\tget depthUsage() {\n\t\t\t\treturn this._depthUsage;\n\t\t}\n\t\tget depthGpuOptimized() {\n\t\t\t\treturn this._depthUsage === XRDEPTHSENSINGUSAGE_GPU;\n\t\t}\n\t\tget depthFormat() {\n\t\t\t\treturn this._depthFormat;\n\t\t}\n\t\tget depthPixelFormat() {\n\t\t\t\treturn this._depthFormats[this._depthFormat] ?? null;\n\t\t}\n\t\tupdate(frame, xrViews) {\n\t\t\t\tfor(let i = 0; i < xrViews.length; i++){\n\t\t\t\t\t\tthis._indexTmp.set(xrViews[i].eye, xrViews[i]);\n\t\t\t\t}\n\t\t\t\tfor (const [eye, xrView] of this._indexTmp){\n\t\t\t\t\t\tlet view = this._index.get(eye);\n\t\t\t\t\t\tif (!view) {\n\t\t\t\t\t\t\t\tview = new XrView(this._manager, xrView, xrViews.length);\n\t\t\t\t\t\t\t\tthis._index.set(eye, view);\n\t\t\t\t\t\t\t\tthis._list.push(view);\n\t\t\t\t\t\t\t\tview.update(frame, xrView);\n\t\t\t\t\t\t\t\tthis.fire('add', view);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tview.update(frame, xrView);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [eye, view] of this._index){\n\t\t\t\t\t\tif (this._indexTmp.has(eye)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tview.destroy();\n\t\t\t\t\t\tthis._index.delete(eye);\n\t\t\t\t\t\tconst ind = this._list.indexOf(view);\n\t\t\t\t\t\tif (ind !== -1) this._list.splice(ind, 1);\n\t\t\t\t\t\tthis.fire('remove', view);\n\t\t\t\t}\n\t\t\t\tthis._indexTmp.clear();\n\t\t}\n\t\tget(eye) {\n\t\t\t\treturn this._index.get(eye) || null;\n\t\t}\n\t\t_onSessionStart() {\n\t\t\t\tif (this._manager.type !== XRTYPE_AR) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._manager.session.enabledFeatures) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._availableColor = this._manager.session.enabledFeatures.indexOf('camera-access') !== -1;\n\t\t\t\tthis._availableDepth = this._manager.session.enabledFeatures.indexOf('depth-sensing') !== -1;\n\t\t\t\tif (this._availableDepth) {\n\t\t\t\t\t\tconst session = this._manager.session;\n\t\t\t\t\t\tthis._depthUsage = session.depthUsage;\n\t\t\t\t\t\tthis._depthFormat = session.depthDataFormat;\n\t\t\t\t}\n\t\t}\n\t\t_onSessionEnd() {\n\t\t\t\tfor (const view of this._index.values()){\n\t\t\t\t\t\tview.destroy();\n\t\t\t\t}\n\t\t\t\tthis._index.clear();\n\t\t\t\tthis._availableColor = false;\n\t\t\t\tthis._availableDepth = false;\n\t\t\t\tthis._depthUsage = '';\n\t\t\t\tthis._depthFormat = '';\n\t\t\t\tthis._list.length = 0;\n\t\t}\n}\n\nclass XrManager extends EventHandler {\n\t\tstatic{\n\t\t\t\tthis.EVENT_AVAILABLE = 'available';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_START = 'start';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_END = 'end';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_UPDATE = 'update';\n\t\t}\n\t\tstatic{\n\t\t\t\tthis.EVENT_ERROR = 'error';\n\t\t}\n\t\tconstructor(app){\n\t\t\t\tsuper(), this._supported = platform.browser && !!navigator.xr, this._available = {}, this._type = null, this._spaceType = null, this._session = null, this._baseLayer = null, this.webglBinding = null, this._referenceSpace = null, this._camera = null, this._localPosition = new Vec3(), this._localRotation = new Quat(), this._depthNear = 0.1, this._depthFar = 1000, this._supportedFrameRates = null, this._width = 0, this._height = 0, this._framebufferScaleFactor = 1.0;\n\t\t\t\tthis.app = app;\n\t\t\t\tthis._available[XRTYPE_INLINE] = false;\n\t\t\t\tthis._available[XRTYPE_VR] = false;\n\t\t\t\tthis._available[XRTYPE_AR] = false;\n\t\t\t\tthis.views = new XrViews(this);\n\t\t\t\tthis.domOverlay = new XrDomOverlay(this);\n\t\t\t\tthis.hitTest = new XrHitTest(this);\n\t\t\t\tthis.imageTracking = new XrImageTracking(this);\n\t\t\t\tthis.planeDetection = new XrPlaneDetection(this);\n\t\t\t\tthis.meshDetection = new XrMeshDetection(this);\n\t\t\t\tthis.input = new XrInput(this);\n\t\t\t\tthis.lightEstimation = new XrLightEstimation(this);\n\t\t\t\tthis.anchors = new XrAnchors(this);\n\t\t\t\tthis.views = new XrViews(this);\n\t\t\t\tif (this._supported) {\n\t\t\t\t\t\tnavigator.xr.addEventListener('devicechange', ()=>{\n\t\t\t\t\t\t\t\tthis._deviceAvailabilityCheck();\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis._deviceAvailabilityCheck();\n\t\t\t\t\t\tthis.app.graphicsDevice.on('devicelost', this._onDeviceLost, this);\n\t\t\t\t\t\tthis.app.graphicsDevice.on('devicerestored', this._onDeviceRestored, this);\n\t\t\t\t}\n\t\t}\n\t\tdestroy() {}\n\t\tstart(camera, type, spaceType, options) {\n\t\t\t\tlet callback = options;\n\t\t\t\tif (typeof options === 'object') {\n\t\t\t\t\t\tcallback = options.callback;\n\t\t\t\t}\n\t\t\t\tif (!this._available[type]) {\n\t\t\t\t\t\tif (callback) callback(new Error('XR is not available'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._session) {\n\t\t\t\t\t\tif (callback) callback(new Error('XR session is already started'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._camera = camera;\n\t\t\t\tthis._camera.camera.xr = this;\n\t\t\t\tthis._type = type;\n\t\t\t\tthis._spaceType = spaceType;\n\t\t\t\tthis._framebufferScaleFactor = options?.framebufferScaleFactor ?? 1.0;\n\t\t\t\tthis._setClipPlanes(camera.nearClip, camera.farClip);\n\t\t\t\tconst opts = {\n\t\t\t\t\t\trequiredFeatures: [\n\t\t\t\t\t\t\t\tspaceType\n\t\t\t\t\t\t],\n\t\t\t\t\t\toptionalFeatures: []\n\t\t\t\t};\n\t\t\t\tconst device = this.app.graphicsDevice;\n\t\t\t\tif (device?.isWebGPU) {\n\t\t\t\t\t\topts.requiredFeatures.push('webgpu');\n\t\t\t\t}\n\t\t\t\tconst webgl = device?.isWebGL2;\n\t\t\t\tif (type === XRTYPE_AR) {\n\t\t\t\t\t\topts.optionalFeatures.push('light-estimation');\n\t\t\t\t\t\topts.optionalFeatures.push('hit-test');\n\t\t\t\t\t\tif (options) {\n\t\t\t\t\t\t\t\tif (options.imageTracking && this.imageTracking.supported) {\n\t\t\t\t\t\t\t\t\t\topts.optionalFeatures.push('image-tracking');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.planeDetection) {\n\t\t\t\t\t\t\t\t\t\topts.optionalFeatures.push('plane-detection');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.meshDetection) {\n\t\t\t\t\t\t\t\t\t\topts.optionalFeatures.push('mesh-detection');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.domOverlay.supported && this.domOverlay.root) {\n\t\t\t\t\t\t\t\topts.optionalFeatures.push('dom-overlay');\n\t\t\t\t\t\t\t\topts.domOverlay = {\n\t\t\t\t\t\t\t\t\t\troot: this.domOverlay.root\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options && options.anchors && this.anchors.supported) {\n\t\t\t\t\t\t\t\topts.optionalFeatures.push('anchors');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options && options.depthSensing && this.views.supportedDepth) {\n\t\t\t\t\t\t\t\topts.optionalFeatures.push('depth-sensing');\n\t\t\t\t\t\t\t\tconst usagePreference = [];\n\t\t\t\t\t\t\t\tconst dataFormatPreference = [];\n\t\t\t\t\t\t\t\tusagePreference.push(XRDEPTHSENSINGUSAGE_GPU, XRDEPTHSENSINGUSAGE_CPU);\n\t\t\t\t\t\t\t\tdataFormatPreference.push(XRDEPTHSENSINGFORMAT_F32, XRDEPTHSENSINGFORMAT_L8A8, XRDEPTHSENSINGFORMAT_R16U);\n\t\t\t\t\t\t\t\tif (options.depthSensing.usagePreference) {\n\t\t\t\t\t\t\t\t\t\tconst ind = usagePreference.indexOf(options.depthSensing.usagePreference);\n\t\t\t\t\t\t\t\t\t\tif (ind !== -1) usagePreference.splice(ind, 1);\n\t\t\t\t\t\t\t\t\t\tusagePreference.unshift(options.depthSensing.usagePreference);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.depthSensing.dataFormatPreference) {\n\t\t\t\t\t\t\t\t\t\tconst ind = dataFormatPreference.indexOf(options.depthSensing.dataFormatPreference);\n\t\t\t\t\t\t\t\t\t\tif (ind !== -1) dataFormatPreference.splice(ind, 1);\n\t\t\t\t\t\t\t\t\t\tdataFormatPreference.unshift(options.depthSensing.dataFormatPreference);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\topts.depthSensing = {\n\t\t\t\t\t\t\t\t\t\tusagePreference: usagePreference,\n\t\t\t\t\t\t\t\t\t\tdataFormatPreference: dataFormatPreference\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (webgl && options && options.cameraColor && this.views.supportedColor) {\n\t\t\t\t\t\t\t\topts.optionalFeatures.push('camera-access');\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\topts.optionalFeatures.push('hand-tracking');\n\t\t\t\tif (options && options.optionalFeatures) {\n\t\t\t\t\t\topts.optionalFeatures = opts.optionalFeatures.concat(options.optionalFeatures);\n\t\t\t\t}\n\t\t\t\tif (this.imageTracking.supported && this.imageTracking.images.length) {\n\t\t\t\t\t\tthis.imageTracking.prepareImages((err, trackedImages)=>{\n\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tif (callback) callback(err);\n\t\t\t\t\t\t\t\t\t\tthis.fire('error', err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (trackedImages !== null) {\n\t\t\t\t\t\t\t\t\t\topts.trackedImages = trackedImages;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis._onStartOptionsReady(type, spaceType, opts, callback);\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._onStartOptionsReady(type, spaceType, opts, callback);\n\t\t\t\t}\n\t\t}\n\t\t_onStartOptionsReady(type, spaceType, options, callback) {\n\t\t\t\tnavigator.xr.requestSession(type, options).then((session)=>{\n\t\t\t\t\t\tthis._onSessionStart(session, spaceType, callback);\n\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\tthis._camera.camera.xr = null;\n\t\t\t\t\t\tthis._camera = null;\n\t\t\t\t\t\tthis._type = null;\n\t\t\t\t\t\tthis._spaceType = null;\n\t\t\t\t\t\tif (callback) callback(ex);\n\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t});\n\t\t}\n\t\tend(callback) {\n\t\t\t\tif (!this._session) {\n\t\t\t\t\t\tif (callback) callback(new Error('XR Session is not initialized'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.webglBinding = null;\n\t\t\t\tif (callback) this.once('end', callback);\n\t\t\t\tthis._session.end();\n\t\t}\n\t\tisAvailable(type) {\n\t\t\t\treturn this._available[type];\n\t\t}\n\t\t_deviceAvailabilityCheck() {\n\t\t\t\tfor(const key in this._available){\n\t\t\t\t\t\tthis._sessionSupportCheck(key);\n\t\t\t\t}\n\t\t}\n\t\tinitiateRoomCapture(callback) {\n\t\t\t\tif (!this._session) {\n\t\t\t\t\t\tcallback(new Error('Session is not active'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._session.initiateRoomCapture) {\n\t\t\t\t\t\tcallback(new Error('Session does not support manual room capture'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._session.initiateRoomCapture().then(()=>{\n\t\t\t\t\t\tif (callback) callback(null);\n\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\tif (callback) callback(err);\n\t\t\t\t});\n\t\t}\n\t\tupdateTargetFrameRate(frameRate, callback) {\n\t\t\t\tif (!this._session?.updateTargetFrameRate) {\n\t\t\t\t\t\tcallback?.(new Error('unable to update frameRate'));\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._session.updateTargetFrameRate(frameRate).then(()=>{\n\t\t\t\t\t\tcallback?.();\n\t\t\t\t}).catch((err)=>{\n\t\t\t\t\t\tcallback?.(err);\n\t\t\t\t});\n\t\t}\n\t\t_sessionSupportCheck(type) {\n\t\t\t\tnavigator.xr.isSessionSupported(type).then((available)=>{\n\t\t\t\t\t\tif (this._available[type] === available) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._available[type] = available;\n\t\t\t\t\t\tthis.fire('available', type, available);\n\t\t\t\t\t\tthis.fire(`available:${type}`, available);\n\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t});\n\t\t}\n\t\t_onSessionStart(session, spaceType, callback) {\n\t\t\t\tlet failed = false;\n\t\t\t\tthis._session = session;\n\t\t\t\tconst onVisibilityChange = ()=>{\n\t\t\t\t\t\tthis.fire('visibility:change', session.visibilityState);\n\t\t\t\t};\n\t\t\t\tconst onClipPlanesChange = ()=>{\n\t\t\t\t\t\tthis._setClipPlanes(this._camera.nearClip, this._camera.farClip);\n\t\t\t\t};\n\t\t\t\tconst onEnd = ()=>{\n\t\t\t\t\t\tif (this._camera) {\n\t\t\t\t\t\t\t\tthis._camera.off('set_nearClip', onClipPlanesChange);\n\t\t\t\t\t\t\t\tthis._camera.off('set_farClip', onClipPlanesChange);\n\t\t\t\t\t\t\t\tthis._camera.camera.xr = null;\n\t\t\t\t\t\t\t\tthis._camera = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsession.removeEventListener('end', onEnd);\n\t\t\t\t\t\tsession.removeEventListener('visibilitychange', onVisibilityChange);\n\t\t\t\t\t\tif (!failed) this.fire('end');\n\t\t\t\t\t\tthis._session = null;\n\t\t\t\t\t\tthis._referenceSpace = null;\n\t\t\t\t\t\tthis._width = 0;\n\t\t\t\t\t\tthis._height = 0;\n\t\t\t\t\t\tthis._type = null;\n\t\t\t\t\t\tthis._spaceType = null;\n\t\t\t\t\t\tif (this.app.systems) {\n\t\t\t\t\t\t\t\tthis.app.tick();\n\t\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tsession.addEventListener('end', onEnd);\n\t\t\t\tsession.addEventListener('visibilitychange', onVisibilityChange);\n\t\t\t\tthis._camera.on('set_nearClip', onClipPlanesChange);\n\t\t\t\tthis._camera.on('set_farClip', onClipPlanesChange);\n\t\t\t\tthis._createBaseLayer();\n\t\t\t\tif (this.session.supportedFrameRates) {\n\t\t\t\t\t\tthis._supportedFrameRates = Array.from(this.session.supportedFrameRates);\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._supportedFrameRates = null;\n\t\t\t\t}\n\t\t\t\tthis._session.addEventListener('frameratechange', ()=>{\n\t\t\t\t\t\tthis.fire('frameratechange', this._session?.frameRate);\n\t\t\t\t});\n\t\t\t\tsession.requestReferenceSpace(spaceType).then((referenceSpace)=>{\n\t\t\t\t\t\tthis._referenceSpace = referenceSpace;\n\t\t\t\t\t\tthis.app.tick();\n\t\t\t\t\t\tif (callback) callback(null);\n\t\t\t\t\t\tthis.fire('start');\n\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\tfailed = true;\n\t\t\t\t\t\tsession.end();\n\t\t\t\t\t\tif (callback) callback(ex);\n\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t});\n\t\t}\n\t\t_setClipPlanes(near, far) {\n\t\t\t\tif (this._depthNear === near && this._depthFar === far) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._depthNear = near;\n\t\t\t\tthis._depthFar = far;\n\t\t\t\tif (!this._session) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._session.updateRenderState({\n\t\t\t\t\t\tdepthNear: this._depthNear,\n\t\t\t\t\t\tdepthFar: this._depthFar\n\t\t\t\t});\n\t\t}\n\t\t_createBaseLayer() {\n\t\t\t\tconst device = this.app.graphicsDevice;\n\t\t\t\tconst framebufferScaleFactor = device.maxPixelRatio / window.devicePixelRatio * this._framebufferScaleFactor;\n\t\t\t\tthis._baseLayer = new XRWebGLLayer(this._session, device.gl, {\n\t\t\t\t\t\talpha: true,\n\t\t\t\t\t\tdepth: true,\n\t\t\t\t\t\tstencil: true,\n\t\t\t\t\t\tframebufferScaleFactor: framebufferScaleFactor,\n\t\t\t\t\t\tantialias: false\n\t\t\t\t});\n\t\t\t\tif (device?.isWebGL2 && window.XRWebGLBinding) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis.webglBinding = new XRWebGLBinding(this._session, device.gl);\n\t\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis._session.updateRenderState({\n\t\t\t\t\t\tbaseLayer: this._baseLayer,\n\t\t\t\t\t\tdepthNear: this._depthNear,\n\t\t\t\t\t\tdepthFar: this._depthFar\n\t\t\t\t});\n\t\t}\n\t\t_onDeviceLost() {\n\t\t\t\tif (!this._session) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this.webglBinding) {\n\t\t\t\t\t\tthis.webglBinding = null;\n\t\t\t\t}\n\t\t\t\tthis._baseLayer = null;\n\t\t\t\tthis._session.updateRenderState({\n\t\t\t\t\t\tbaseLayer: this._baseLayer,\n\t\t\t\t\t\tdepthNear: this._depthNear,\n\t\t\t\t\t\tdepthFar: this._depthFar\n\t\t\t\t});\n\t\t}\n\t\t_onDeviceRestored() {\n\t\t\t\tif (!this._session) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetTimeout(()=>{\n\t\t\t\t\t\tthis.app.graphicsDevice.gl.makeXRCompatible().then(()=>{\n\t\t\t\t\t\t\t\tthis._createBaseLayer();\n\t\t\t\t\t\t}).catch((ex)=>{\n\t\t\t\t\t\t\t\tthis.fire('error', ex);\n\t\t\t\t\t\t});\n\t\t\t\t}, 0);\n\t\t}\n\t\tupdate(frame) {\n\t\t\t\tif (!this._session) return false;\n\t\t\t\tconst width = frame.session.renderState.baseLayer.framebufferWidth;\n\t\t\t\tconst height = frame.session.renderState.baseLayer.framebufferHeight;\n\t\t\t\tif (this._width !== width || this._height !== height) {\n\t\t\t\t\t\tthis._width = width;\n\t\t\t\t\t\tthis._height = height;\n\t\t\t\t\t\tthis.app.graphicsDevice.setResolution(width, height);\n\t\t\t\t}\n\t\t\t\tconst pose = frame.getViewerPose(this._referenceSpace);\n\t\t\t\tif (!pose) return false;\n\t\t\t\tconst lengthOld = this.views.list.length;\n\t\t\t\tthis.views.update(frame, pose.views);\n\t\t\t\tconst posePosition = pose.transform.position;\n\t\t\t\tconst poseOrientation = pose.transform.orientation;\n\t\t\t\tthis._localPosition.set(posePosition.x, posePosition.y, posePosition.z);\n\t\t\t\tthis._localRotation.set(poseOrientation.x, poseOrientation.y, poseOrientation.z, poseOrientation.w);\n\t\t\t\tif (lengthOld === 0 && this.views.list.length > 0) {\n\t\t\t\t\t\tconst viewProjMat = new Mat4();\n\t\t\t\t\t\tconst view = this.views.list[0];\n\t\t\t\t\t\tviewProjMat.copy(view.projMat);\n\t\t\t\t\t\tconst data = viewProjMat.data;\n\t\t\t\t\t\tconst fov = 2.0 * Math.atan(1.0 / data[5]) * 180.0 / Math.PI;\n\t\t\t\t\t\tconst aspectRatio = data[5] / data[0];\n\t\t\t\t\t\tconst farClip = data[14] / (data[10] + 1);\n\t\t\t\t\t\tconst nearClip = data[14] / (data[10] - 1);\n\t\t\t\t\t\tconst horizontalFov = false;\n\t\t\t\t\t\tconst camera = this._camera.camera;\n\t\t\t\t\t\tcamera.setXrProperties({\n\t\t\t\t\t\t\t\taspectRatio,\n\t\t\t\t\t\t\t\tfarClip,\n\t\t\t\t\t\t\t\tfov,\n\t\t\t\t\t\t\t\thorizontalFov,\n\t\t\t\t\t\t\t\tnearClip\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthis._camera.camera._node.setLocalPosition(this._localPosition);\n\t\t\t\tthis._camera.camera._node.setLocalRotation(this._localRotation);\n\t\t\t\tthis.input.update(frame);\n\t\t\t\tif (this._type === XRTYPE_AR) {\n\t\t\t\t\t\tif (this.hitTest.supported) {\n\t\t\t\t\t\t\t\tthis.hitTest.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.lightEstimation.supported) {\n\t\t\t\t\t\t\t\tthis.lightEstimation.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.imageTracking.supported) {\n\t\t\t\t\t\t\t\tthis.imageTracking.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.anchors.supported) {\n\t\t\t\t\t\t\t\tthis.anchors.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.planeDetection.supported) {\n\t\t\t\t\t\t\t\tthis.planeDetection.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.meshDetection.supported) {\n\t\t\t\t\t\t\t\tthis.meshDetection.update(frame);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.fire('update', frame);\n\t\t\t\treturn true;\n\t\t}\n\t\tget supported() {\n\t\t\t\treturn this._supported;\n\t\t}\n\t\tget active() {\n\t\t\t\treturn !!this._session;\n\t\t}\n\t\tget type() {\n\t\t\t\treturn this._type;\n\t\t}\n\t\tget spaceType() {\n\t\t\t\treturn this._spaceType;\n\t\t}\n\t\tget session() {\n\t\t\t\treturn this._session;\n\t\t}\n\t\tget frameRate() {\n\t\t\t\treturn this._session?.frameRate ?? null;\n\t\t}\n\t\tget supportedFrameRates() {\n\t\t\t\treturn this._supportedFrameRates;\n\t\t}\n\t\tget framebufferScaleFactor() {\n\t\t\t\treturn this._framebufferScaleFactor;\n\t\t}\n\t\tset fixedFoveation(value) {\n\t\t\t\tif ((this._baseLayer?.fixedFoveation ?? null) !== null) {\n\t\t\t\t\t\tif (this.app.graphicsDevice.samples > 1) ;\n\t\t\t\t\t\tthis._baseLayer.fixedFoveation = value;\n\t\t\t\t}\n\t\t}\n\t\tget fixedFoveation() {\n\t\t\t\treturn this._baseLayer?.fixedFoveation ?? null;\n\t\t}\n\t\tget camera() {\n\t\t\t\treturn this._camera ? this._camera.entity : null;\n\t\t}\n\t\tget visibilityState() {\n\t\t\t\tif (!this._session) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\treturn this._session.visibilityState;\n\t\t}\n}\n\nconst tempMeshInstances = [];\nconst lights = [\n\t\t[],\n\t\t[],\n\t\t[]\n];\nclass RenderPassPicker extends RenderPass {\n\t\tconstructor(device, renderer){\n\t\t\t\tsuper(device), this.viewBindGroups = [];\n\t\t\t\tthis.renderer = renderer;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.viewBindGroups.forEach((bg)=>{\n\t\t\t\t\t\tbg.defaultUniformBuffer.destroy();\n\t\t\t\t\t\tbg.destroy();\n\t\t\t\t});\n\t\t\t\tthis.viewBindGroups.length = 0;\n\t\t}\n\t\tupdate(camera, scene, layers, mapping) {\n\t\t\t\tthis.camera = camera;\n\t\t\t\tthis.scene = scene;\n\t\t\t\tthis.layers = layers;\n\t\t\t\tthis.mapping = mapping;\n\t\t\t\tif (scene.clusteredLightingEnabled) {\n\t\t\t\t\t\tthis.emptyWorldClusters = this.renderer.worldClustersAllocator.empty;\n\t\t\t\t}\n\t\t}\n\t\texecute() {\n\t\t\t\tconst device = this.device;\n\t\t\t\tconst { renderer, camera, scene, layers, mapping, renderTarget } = this;\n\t\t\t\tconst srcLayers = scene.layers.layerList;\n\t\t\t\tconst subLayerEnabled = scene.layers.subLayerEnabled;\n\t\t\t\tconst isTransparent = scene.layers.subLayerList;\n\t\t\t\tfor(let i = 0; i < srcLayers.length; i++){\n\t\t\t\t\t\tconst srcLayer = srcLayers[i];\n\t\t\t\t\t\tif (layers && layers.indexOf(srcLayer) < 0) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (srcLayer.enabled && subLayerEnabled[i]) {\n\t\t\t\t\t\t\t\tif (srcLayer.camerasSet.has(camera.camera)) {\n\t\t\t\t\t\t\t\t\t\tconst transparent = isTransparent[i];\n\t\t\t\t\t\t\t\t\t\tif (srcLayer._clearDepthBuffer) {\n\t\t\t\t\t\t\t\t\t\t\t\trenderer.clear(camera.camera, false, true, false);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst meshInstances = srcLayer.meshInstances;\n\t\t\t\t\t\t\t\t\t\tfor(let j = 0; j < meshInstances.length; j++){\n\t\t\t\t\t\t\t\t\t\t\t\tconst meshInstance = meshInstances[j];\n\t\t\t\t\t\t\t\t\t\t\t\tif (meshInstance.pick && meshInstance.transparent === transparent) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttempMeshInstances.push(meshInstance);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmapping.set(meshInstance.id, meshInstance);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (tempMeshInstances.length > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst clusteredLightingEnabled = scene.clusteredLightingEnabled;\n\t\t\t\t\t\t\t\t\t\t\t\tif (clusteredLightingEnabled) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst lightClusters = this.emptyWorldClusters;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlightClusters.activate();\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\trenderer.setCameraUniforms(camera.camera, renderTarget);\n\t\t\t\t\t\t\t\t\t\t\t\tif (device.supportsUniformBuffers) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trenderer.setupViewUniformBuffers(this.viewBindGroups, renderer.viewUniformFormat, renderer.viewBindGroupFormat, null);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\trenderer.renderForward(camera.camera, renderTarget, tempMeshInstances, lights, SHADER_PICK, (meshInstance)=>{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdevice.setBlendState(BlendState.NOBLEND);\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\ttempMeshInstances.length = 0;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n}\n\nconst tempSet = new Set();\nconst _rect = new Vec4();\nlet Picker$1 = class Picker {\n\t\tconstructor(app, width, height){\n\t\t\t\tthis.renderTarget = null;\n\t\t\t\tthis.mapping = new Map();\n\t\t\t\tthis.deviceValid = true;\n\t\t\t\tthis.renderer = app.renderer;\n\t\t\t\tthis.device = app.graphicsDevice;\n\t\t\t\tthis.renderPass = new RenderPassPicker(this.device, app.renderer);\n\t\t\t\tthis.width = 0;\n\t\t\t\tthis.height = 0;\n\t\t\t\tthis.resize(width, height);\n\t\t\t\tthis.device.on('destroy', ()=>{\n\t\t\t\t\t\tthis.deviceValid = false;\n\t\t\t\t});\n\t\t}\n\t\tgetSelection(x, y, width = 1, height = 1) {\n\t\t\t\tconst device = this.device;\n\t\t\t\tif (device.isWebGPU) {\n\t\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t\ty = this.renderTarget.height - (y + height);\n\t\t\t\tconst rect = this.sanitizeRect(x, y, width, height);\n\t\t\t\tdevice.setRenderTarget(this.renderTarget);\n\t\t\t\tdevice.updateBegin();\n\t\t\t\tconst pixels = new Uint8Array(4 * rect.z * rect.w);\n\t\t\t\tdevice.readPixels(rect.x, rect.y, rect.z, rect.w, pixels);\n\t\t\t\tdevice.updateEnd();\n\t\t\t\treturn this.decodePixels(pixels, this.mapping);\n\t\t}\n\t\tgetSelectionAsync(x, y, width = 1, height = 1) {\n\t\t\t\tif (this.device?.isWebGL2) {\n\t\t\t\t\t\ty = this.renderTarget.height - (y + height);\n\t\t\t\t}\n\t\t\t\tconst rect = this.sanitizeRect(x, y, width, height);\n\t\t\t\treturn this.renderTarget.colorBuffer.read(rect.x, rect.y, rect.z, rect.w, {\n\t\t\t\t\t\trenderTarget: this.renderTarget,\n\t\t\t\t\t\timmediate: true\n\t\t\t\t}).then((pixels)=>{\n\t\t\t\t\t\treturn this.decodePixels(pixels, this.mapping);\n\t\t\t\t});\n\t\t}\n\t\tsanitizeRect(x, y, width, height) {\n\t\t\t\tconst maxWidth = this.renderTarget.width;\n\t\t\t\tconst maxHeight = this.renderTarget.height;\n\t\t\t\tx = math.clamp(Math.floor(x), 0, maxWidth - 1);\n\t\t\t\ty = math.clamp(Math.floor(y), 0, maxHeight - 1);\n\t\t\t\twidth = Math.floor(Math.max(width, 1));\n\t\t\t\twidth = Math.min(width, maxWidth - x);\n\t\t\t\theight = Math.floor(Math.max(height, 1));\n\t\t\t\theight = Math.min(height, maxHeight - y);\n\t\t\t\treturn _rect.set(x, y, width, height);\n\t\t}\n\t\tdecodePixels(pixels, mapping) {\n\t\t\t\tconst selection = [];\n\t\t\t\tif (this.deviceValid) {\n\t\t\t\t\t\tconst count = pixels.length;\n\t\t\t\t\t\tfor(let i = 0; i < count; i += 4){\n\t\t\t\t\t\t\t\tconst r = pixels[i + 0];\n\t\t\t\t\t\t\t\tconst g = pixels[i + 1];\n\t\t\t\t\t\t\t\tconst b = pixels[i + 2];\n\t\t\t\t\t\t\t\tconst a = pixels[i + 3];\n\t\t\t\t\t\t\t\tconst index = (a << 24 | r << 16 | g << 8 | b) >>> 0;\n\t\t\t\t\t\t\t\tif (index !== 0xFFFFFFFF) {\n\t\t\t\t\t\t\t\t\t\ttempSet.add(mapping.get(index));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttempSet.forEach((meshInstance)=>{\n\t\t\t\t\t\t\t\tif (meshInstance) {\n\t\t\t\t\t\t\t\t\t\tselection.push(meshInstance);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\ttempSet.clear();\n\t\t\t\t}\n\t\t\t\treturn selection;\n\t\t}\n\t\tallocateRenderTarget() {\n\t\t\t\tconst colorBuffer = new Texture(this.device, {\n\t\t\t\t\t\tformat: PIXELFORMAT_RGBA8,\n\t\t\t\t\t\twidth: this.width,\n\t\t\t\t\t\theight: this.height,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\taddressV: ADDRESS_CLAMP_TO_EDGE,\n\t\t\t\t\t\tname: 'pick'\n\t\t\t\t});\n\t\t\t\tthis.renderTarget = new RenderTarget({\n\t\t\t\t\t\tcolorBuffer: colorBuffer,\n\t\t\t\t\t\tdepth: true\n\t\t\t\t});\n\t\t}\n\t\treleaseRenderTarget() {\n\t\t\t\tif (this.renderTarget) {\n\t\t\t\t\t\tthis.renderTarget.destroyTextureBuffers();\n\t\t\t\t\t\tthis.renderTarget.destroy();\n\t\t\t\t\t\tthis.renderTarget = null;\n\t\t\t\t}\n\t\t}\n\t\tprepare(camera, scene, layers) {\n\t\t\t\tif (layers instanceof Layer) {\n\t\t\t\t\t\tlayers = [\n\t\t\t\t\t\t\t\tlayers\n\t\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tif (!this.renderTarget || this.width !== this.renderTarget.width || this.height !== this.renderTarget.height) {\n\t\t\t\t\t\tthis.releaseRenderTarget();\n\t\t\t\t\t\tthis.allocateRenderTarget();\n\t\t\t\t}\n\t\t\t\tthis.mapping.clear();\n\t\t\t\tconst renderPass = this.renderPass;\n\t\t\t\trenderPass.init(this.renderTarget);\n\t\t\t\trenderPass.colorOps.clearValue = Color.WHITE;\n\t\t\t\trenderPass.colorOps.clear = true;\n\t\t\t\trenderPass.depthStencilOps.clearDepth = true;\n\t\t\t\trenderPass.update(camera, scene, layers, this.mapping);\n\t\t\t\trenderPass.render();\n\t\t}\n\t\tresize(width, height) {\n\t\t\t\tthis.width = Math.floor(width);\n\t\t\t\tthis.height = Math.floor(height);\n\t\t}\n};\n\nclass CpuTimer {\n\t\tconstructor(app){\n\t\t\t\tthis._frameIndex = 0;\n\t\t\t\tthis._frameTimings = [];\n\t\t\t\tthis._timings = [];\n\t\t\t\tthis._prevTimings = [];\n\t\t\t\tthis.unitsName = 'ms';\n\t\t\t\tthis.decimalPlaces = 1;\n\t\t\t\tthis.enabled = true;\n\t\t\t\tapp.on('frameupdate', this.begin.bind(this, 'update'));\n\t\t\t\tapp.on('framerender', this.mark.bind(this, 'render'));\n\t\t\t\tapp.on('frameend', this.mark.bind(this, 'other'));\n\t\t}\n\t\tbegin(name) {\n\t\t\t\tif (!this.enabled) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._frameIndex < this._frameTimings.length) {\n\t\t\t\t\t\tthis._frameTimings.splice(this._frameIndex);\n\t\t\t\t}\n\t\t\t\tconst tmp = this._prevTimings;\n\t\t\t\tthis._prevTimings = this._timings;\n\t\t\t\tthis._timings = this._frameTimings;\n\t\t\t\tthis._frameTimings = tmp;\n\t\t\t\tthis._frameIndex = 0;\n\t\t\t\tthis.mark(name);\n\t\t}\n\t\tmark(name) {\n\t\t\t\tif (!this.enabled) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst timestamp = now();\n\t\t\t\tif (this._frameIndex > 0) {\n\t\t\t\t\t\tconst prev = this._frameTimings[this._frameIndex - 1];\n\t\t\t\t\t\tprev[1] = timestamp - prev[1];\n\t\t\t\t} else if (this._timings.length > 0) {\n\t\t\t\t\t\tconst prev = this._timings[this._timings.length - 1];\n\t\t\t\t\t\tprev[1] = timestamp - prev[1];\n\t\t\t\t}\n\t\t\t\tif (this._frameIndex >= this._frameTimings.length) {\n\t\t\t\t\t\tthis._frameTimings.push([\n\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\ttimestamp\n\t\t\t\t\t\t]);\n\t\t\t\t} else {\n\t\t\t\t\t\tconst timing = this._frameTimings[this._frameIndex];\n\t\t\t\t\t\ttiming[0] = name;\n\t\t\t\t\t\ttiming[1] = timestamp;\n\t\t\t\t}\n\t\t\t\tthis._frameIndex++;\n\t\t}\n\t\tget timings() {\n\t\t\t\treturn this._timings.slice(0, -1).map((v)=>v[1]);\n\t\t}\n}\n\nclass GpuTimer {\n\t\tconstructor(device){\n\t\t\t\tthis.device = device;\n\t\t\t\tdevice.gpuProfiler.enabled = true;\n\t\t\t\tthis.enabled = true;\n\t\t\t\tthis.unitsName = 'ms';\n\t\t\t\tthis.decimalPlaces = 1;\n\t\t\t\tthis._timings = [];\n\t\t}\n\t\tget timings() {\n\t\t\t\tthis._timings[0] = this.device.gpuProfiler._frameTime;\n\t\t\t\treturn this._timings;\n\t\t}\n}\n\nclass StatsTimer {\n\t\tconstructor(app, statNames, decimalPlaces, unitsName, multiplier){\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.values = [];\n\t\t\t\tthis.statNames = statNames;\n\t\t\t\tif (this.statNames.length > 3) {\n\t\t\t\t\t\tthis.statNames.length = 3;\n\t\t\t\t}\n\t\t\t\tthis.unitsName = unitsName;\n\t\t\t\tthis.decimalPlaces = decimalPlaces;\n\t\t\t\tthis.multiplier = multiplier || 1;\n\t\t\t\tconst resolve = (path, obj)=>{\n\t\t\t\t\t\treturn path.split('.').reduce((prev, curr)=>{\n\t\t\t\t\t\t\t\treturn prev ? prev[curr] : null;\n\t\t\t\t\t\t}, obj || this);\n\t\t\t\t};\n\t\t\t\tapp.on('frameupdate', (ms)=>{\n\t\t\t\t\t\tfor(let i = 0; i < this.statNames.length; i++){\n\t\t\t\t\t\t\t\tthis.values[i] = resolve(this.statNames[i], this.app.stats) * this.multiplier;\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t\tget timings() {\n\t\t\t\treturn this.values;\n\t\t}\n}\n\nclass Graph {\n\t\tconstructor(name, app, watermark, textRefreshRate, timer){\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.device = app.graphicsDevice;\n\t\t\t\tthis.timer = timer;\n\t\t\t\tthis.watermark = watermark;\n\t\t\t\tthis.enabled = false;\n\t\t\t\tthis.textRefreshRate = textRefreshRate;\n\t\t\t\tthis.avgTotal = 0;\n\t\t\t\tthis.avgTimer = 0;\n\t\t\t\tthis.avgCount = 0;\n\t\t\t\tthis.timingText = '';\n\t\t\t\tthis.texture = null;\n\t\t\t\tthis.yOffset = 0;\n\t\t\t\tthis.cursor = 0;\n\t\t\t\tthis.sample = new Uint8ClampedArray(4);\n\t\t\t\tthis.sample.set([\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t255\n\t\t\t\t]);\n\t\t\t\tthis.counter = 0;\n\t\t\t\tthis.app.on('frameupdate', this.update, this);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.app.off('frameupdate', this.update, this);\n\t\t}\n\t\tloseContext() {\n\t\t\t\tif (this.timer && typeof this.timer.loseContext === 'function') {\n\t\t\t\t\t\tthis.timer.loseContext();\n\t\t\t\t}\n\t\t}\n\t\tupdate(ms) {\n\t\t\t\tconst timings = this.timer.timings;\n\t\t\t\tconst total = timings.reduce((a, v)=>a + v, 0);\n\t\t\t\tthis.avgTotal += total;\n\t\t\t\tthis.avgTimer += ms;\n\t\t\t\tthis.avgCount++;\n\t\t\t\tif (this.avgTimer > this.textRefreshRate) {\n\t\t\t\t\t\tthis.timingText = (this.avgTotal / this.avgCount).toFixed(this.timer.decimalPlaces);\n\t\t\t\t\t\tthis.avgTimer = 0;\n\t\t\t\t\t\tthis.avgTotal = 0;\n\t\t\t\t\t\tthis.avgCount = 0;\n\t\t\t\t}\n\t\t\t\tif (this.enabled) {\n\t\t\t\t\t\tlet value = 0;\n\t\t\t\t\t\tconst range = 1.5 * this.watermark;\n\t\t\t\t\t\tfor(let i = 0; i < timings.length; ++i){\n\t\t\t\t\t\t\t\tvalue += Math.floor(timings[i] / range * 255);\n\t\t\t\t\t\t\t\tthis.sample[i] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.sample[3] = this.watermark / range * 255;\n\t\t\t\t\t\tconst data = this.texture.lock();\n\t\t\t\t\t\tdata.set(this.sample, (this.cursor + this.yOffset * this.texture.width) * 4);\n\t\t\t\t\t\tthis.texture.unlock();\n\t\t\t\t\t\tthis.cursor++;\n\t\t\t\t\t\tif (this.cursor === this.texture.width) {\n\t\t\t\t\t\t\t\tthis.cursor = 0;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\trender(render2d, x, y, w, h) {\n\t\t\t\trender2d.quad(x + w, y, -w, h, this.enabled ? this.cursor : 0, this.enabled ? 0.5 + this.yOffset : this.texture.height - 1, -w, 0, this.texture, 0);\n\t\t}\n}\n\nclass WordAtlas {\n\t\tconstructor(device, words){\n\t\t\t\tconst initContext = (context)=>{\n\t\t\t\t\t\tcontext.font = '10px \"Lucida Console\", Monaco, monospace';\n\t\t\t\t\t\tcontext.textAlign = 'left';\n\t\t\t\t\t\tcontext.textBaseline = 'alphabetic';\n\t\t\t\t};\n\t\t\t\tconst isNumber = (word)=>{\n\t\t\t\t\t\treturn word === '.' || word.length === 1 && word.charCodeAt(0) >= 48 && word.charCodeAt(0) <= 57;\n\t\t\t\t};\n\t\t\t\tconst canvas = document.createElement('canvas');\n\t\t\t\tconst context = canvas.getContext('2d', {\n\t\t\t\t\t\talpha: true\n\t\t\t\t});\n\t\t\t\tinitContext(context);\n\t\t\t\tconst placements = new Map();\n\t\t\t\tconst padding = 5;\n\t\t\t\tconst width = 512;\n\t\t\t\tlet x = padding;\n\t\t\t\tlet y = padding;\n\t\t\t\twords.forEach((word)=>{\n\t\t\t\t\t\tconst measurement = context.measureText(word);\n\t\t\t\t\t\tconst l = Math.ceil(-measurement.actualBoundingBoxLeft);\n\t\t\t\t\t\tconst r = Math.ceil(measurement.actualBoundingBoxRight);\n\t\t\t\t\t\tconst a = Math.ceil(measurement.actualBoundingBoxAscent);\n\t\t\t\t\t\tconst d = Math.ceil(measurement.actualBoundingBoxDescent);\n\t\t\t\t\t\tconst w = l + r;\n\t\t\t\t\t\tconst h = a + d;\n\t\t\t\t\t\tif (x + w + padding >= width) {\n\t\t\t\t\t\t\t\tx = padding;\n\t\t\t\t\t\t\t\ty += 16;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tplacements.set(word, {\n\t\t\t\t\t\t\t\tl,\n\t\t\t\t\t\t\t\tr,\n\t\t\t\t\t\t\t\ta,\n\t\t\t\t\t\t\t\td,\n\t\t\t\t\t\t\t\tw,\n\t\t\t\t\t\t\t\th,\n\t\t\t\t\t\t\t\tx: x,\n\t\t\t\t\t\t\t\ty: y\n\t\t\t\t\t\t});\n\t\t\t\t\t\tx += w + padding;\n\t\t\t\t});\n\t\t\t\tcanvas.width = 512;\n\t\t\t\tcanvas.height = math.nextPowerOfTwo(y + 16 + padding);\n\t\t\t\tinitContext(context);\n\t\t\t\tcontext.fillStyle = 'rgb(0, 0, 0)';\n\t\t\t\tcontext.fillRect(0, 0, canvas.width, canvas.height);\n\t\t\t\tplacements.forEach((m, word)=>{\n\t\t\t\t\t\tcontext.fillStyle = isNumber(word) ? 'rgb(255, 255, 255)' : 'rgb(170, 170, 170)';\n\t\t\t\t\t\tcontext.fillText(word, m.x - m.l, m.y + m.a);\n\t\t\t\t});\n\t\t\t\tthis.placements = placements;\n\t\t\t\tconst data = context.getImageData(0, 0, canvas.width, canvas.height).data;\n\t\t\t\tfor(let i = 0; i < data.length; i += 4){\n\t\t\t\t\t\tdata[i + 3] = data[i + 0];\n\t\t\t\t\t\tdata[i + 0] = 255;\n\t\t\t\t\t\tdata[i + 1] = 255;\n\t\t\t\t\t\tdata[i + 2] = 255;\n\t\t\t\t}\n\t\t\t\tthis.texture = new Texture(device, {\n\t\t\t\t\t\tname: 'mini-stats-word-atlas',\n\t\t\t\t\t\twidth: canvas.width,\n\t\t\t\t\t\theight: canvas.height,\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\tlevels: [\n\t\t\t\t\t\t\t\tdata\n\t\t\t\t\t\t]\n\t\t\t\t});\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.texture.destroy();\n\t\t\t\tthis.texture = null;\n\t\t}\n\t\trender(render2d, word, x, y) {\n\t\t\t\tconst p = this.placements.get(word);\n\t\t\t\tif (p) {\n\t\t\t\t\t\tconst padding = 1;\n\t\t\t\t\t\trender2d.quad(x + p.l - padding, y - p.d + padding, p.w + padding * 2, p.h + padding * 2, p.x - padding, this.texture.height - p.y - p.h - padding, undefined, undefined, this.texture, 1);\n\t\t\t\t\t\treturn p.w;\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t}\n}\n\nconst vertexShaderGLSL = `\n\tattribute vec3 vertex_position;\n\tattribute vec4 vertex_texCoord0;\n\tvarying vec4 uv0;\n\tvarying float wordFlag;\n\tvoid main(void) {\n\t\tgl_Position = vec4(vertex_position.xy * 2.0 - 1.0, 0.5, 1.0);\n\t\tuv0 = vertex_texCoord0;\n\t\twordFlag = vertex_position.z;\n\t}\n`;\nconst vertexShaderWGSL = `\n\tattribute vertex_position: vec3f;\n\tattribute vertex_texCoord0: vec4f;\n\tvarying uv0: vec4f;\n\tvarying wordFlag: f32;\n\t@vertex fn vertexMain(input : VertexInput) -> VertexOutput {\n\t\tvar output : VertexOutput;\n\t\toutput.position = vec4(input.vertex_position.xy * 2.0 - 1.0, 0.5, 1.0);\n\t\toutput.uv0 = input.vertex_texCoord0;\n\t\toutput.wordFlag = input.vertex_position.z;\n\t\treturn output;\n\t}\n`;\nconst fragmentShaderGLSL = `\n\tvarying vec4 uv0;\n\tvarying float wordFlag;\n\tuniform vec4 clr;\n\tuniform sampler2D graphTex;\n\tuniform sampler2D wordsTex;\n\tvoid main (void) {\n\t\tvec4 graphSample = texture2D(graphTex, uv0.xy);\n\t\tvec4 graph;\n\t\tif (uv0.w < graphSample.r)\n\t\t\tgraph = vec4(0.7, 0.2, 0.2, 1.0);\n\t\telse if (uv0.w < graphSample.g)\n\t\t\tgraph = vec4(0.2, 0.7, 0.2, 1.0);\n\t\telse if (uv0.w < graphSample.b)\n\t\t\tgraph = vec4(0.2, 0.2, 0.7, 1.0);\n\t\telse\n\t\t\tgraph = vec4(0.0, 0.0, 0.0, 1.0 - 0.25 * sin(uv0.w * 3.14159));\n\t\tvec4 words = texture2D(wordsTex, vec2(uv0.x, 1.0 - uv0.y));\n\t\tgl_FragColor = mix(graph, words, wordFlag) * clr;\n\t}\n`;\nconst fragmentShaderWGSL = `\n\tvarying uv0: vec4f;\n\tvarying wordFlag: f32;\n\tuniform clr: vec4f;\n\tvar graphTex : texture_2d<f32>;\n\tvar graphTex_sampler : sampler;\n\tvar wordsTex : texture_2d<f32>;\n\tvar wordsTex_sampler : sampler;\n\t@fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput {\n\t\tvar uv0: vec4f = input.uv0;\n\t\tvar graphSample: vec4f = textureSample(graphTex, graphTex_sampler, uv0.xy);\n\t\tvar graph: vec4f;\n\t\tif (uv0.w < graphSample.r) {\n\t\t\tgraph = vec4f(0.7, 0.2, 0.2, 1.0);\n\t\t} else if (uv0.w < graphSample.g) {\n\t\t\tgraph = vec4f(0.2, 0.7, 0.2, 1.0);\n\t\t} else if (uv0.w < graphSample.b) {\n\t\t\tgraph = vec4f(0.2, 0.2, 0.7, 1.0);\n\t\t} else {\n\t\t\tgraph = vec4f(0.0, 0.0, 0.0, 1.0 - 0.25 * sin(uv0.w * 3.14159));\n\t\t}\n\t\tvar words: vec4f = textureSample(wordsTex, wordsTex_sampler, vec2f(uv0.x, 1.0 - uv0.y));\n\t\tvar output: FragmentOutput;\n\t\toutput.color = mix(graph, words, input.wordFlag) * uniform.clr;\n\t\treturn output;\n\t}\n`;\nclass Render2d {\n\t\tconstructor(device, maxQuads = 512){\n\t\t\t\tconst format = new VertexFormat(device, [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_POSITION,\n\t\t\t\t\t\t\t\tcomponents: 3,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsemantic: SEMANTIC_TEXCOORD0,\n\t\t\t\t\t\t\t\tcomponents: 4,\n\t\t\t\t\t\t\t\ttype: TYPE_FLOAT32\n\t\t\t\t\t\t}\n\t\t\t\t]);\n\t\t\t\tconst indices = new Uint16Array(maxQuads * 6);\n\t\t\t\tfor(let i = 0; i < maxQuads; ++i){\n\t\t\t\t\t\tindices[i * 6 + 0] = i * 4;\n\t\t\t\t\t\tindices[i * 6 + 1] = i * 4 + 1;\n\t\t\t\t\t\tindices[i * 6 + 2] = i * 4 + 2;\n\t\t\t\t\t\tindices[i * 6 + 3] = i * 4;\n\t\t\t\t\t\tindices[i * 6 + 4] = i * 4 + 2;\n\t\t\t\t\t\tindices[i * 6 + 5] = i * 4 + 3;\n\t\t\t\t}\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.buffer = new VertexBuffer(device, format, maxQuads * 4, {\n\t\t\t\t\t\tusage: BUFFER_STREAM\n\t\t\t\t});\n\t\t\t\tthis.data = new Float32Array(this.buffer.numBytes / 4);\n\t\t\t\tthis.indexBuffer = new IndexBuffer(device, INDEXFORMAT_UINT16, maxQuads * 6, BUFFER_STATIC, indices);\n\t\t\t\tthis.prim = {\n\t\t\t\t\t\ttype: PRIMITIVE_TRIANGLES,\n\t\t\t\t\t\tindexed: true,\n\t\t\t\t\t\tbase: 0,\n\t\t\t\t\t\tbaseVertex: 0,\n\t\t\t\t\t\tcount: 0\n\t\t\t\t};\n\t\t\t\tthis.quads = 0;\n\t\t\t\tthis.mesh = new Mesh(device);\n\t\t\t\tthis.mesh.vertexBuffer = this.buffer;\n\t\t\t\tthis.mesh.indexBuffer[0] = this.indexBuffer;\n\t\t\t\tthis.mesh.primitive = [\n\t\t\t\t\t\tthis.prim\n\t\t\t\t];\n\t\t\t\tconst material = new ShaderMaterial({\n\t\t\t\t\t\tuniqueName: 'MiniStats',\n\t\t\t\t\t\tvertexGLSL: vertexShaderGLSL,\n\t\t\t\t\t\tfragmentGLSL: fragmentShaderGLSL,\n\t\t\t\t\t\tvertexWGSL: vertexShaderWGSL,\n\t\t\t\t\t\tfragmentWGSL: fragmentShaderWGSL,\n\t\t\t\t\t\tattributes: {\n\t\t\t\t\t\t\t\tvertex_position: SEMANTIC_POSITION,\n\t\t\t\t\t\t\t\tvertex_texCoord0: SEMANTIC_TEXCOORD0\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis.material = material;\n\t\t\t\tmaterial.cull = CULLFACE_NONE;\n\t\t\t\tmaterial.depthState = DepthState.NODEPTH;\n\t\t\t\tmaterial.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE);\n\t\t\t\tmaterial.update();\n\t\t\t\tthis.meshInstance = new MeshInstance(this.mesh, material, new GraphNode('MiniStatsMesh'));\n\t\t\t\tthis.uniforms = {\n\t\t\t\t\t\tclr: new Float32Array(4)\n\t\t\t\t};\n\t\t\t\tthis.targetSize = {\n\t\t\t\t\t\twidth: device.width,\n\t\t\t\t\t\theight: device.height\n\t\t\t\t};\n\t\t}\n\t\tquad(x, y, w, h, u, v, uw, uh, texture, wordFlag = 0) {\n\t\t\t\tconst rw = this.targetSize.width;\n\t\t\t\tconst rh = this.targetSize.height;\n\t\t\t\tconst x0 = x / rw;\n\t\t\t\tconst y0 = y / rh;\n\t\t\t\tconst x1 = (x + w) / rw;\n\t\t\t\tconst y1 = (y + h) / rh;\n\t\t\t\tconst tw = texture.width;\n\t\t\t\tconst th = texture.height;\n\t\t\t\tconst u0 = u / tw;\n\t\t\t\tconst v0 = v / th;\n\t\t\t\tconst u1 = (u + (uw ?? w)) / tw;\n\t\t\t\tconst v1 = (v + (uh ?? h)) / th;\n\t\t\t\tthis.data.set([\n\t\t\t\t\t\tx0,\n\t\t\t\t\t\ty0,\n\t\t\t\t\t\twordFlag,\n\t\t\t\t\t\tu0,\n\t\t\t\t\t\tv0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tx1,\n\t\t\t\t\t\ty0,\n\t\t\t\t\t\twordFlag,\n\t\t\t\t\t\tu1,\n\t\t\t\t\t\tv0,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tx1,\n\t\t\t\t\t\ty1,\n\t\t\t\t\t\twordFlag,\n\t\t\t\t\t\tu1,\n\t\t\t\t\t\tv1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tx0,\n\t\t\t\t\t\ty1,\n\t\t\t\t\t\twordFlag,\n\t\t\t\t\t\tu0,\n\t\t\t\t\t\tv1,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1\n\t\t\t\t], 4 * 7 * this.quads);\n\t\t\t\tthis.quads++;\n\t\t\t\tthis.prim.count += 6;\n\t\t}\n\t\tstartFrame() {\n\t\t\t\tthis.quads = 0;\n\t\t\t\tthis.prim.count = 0;\n\t\t\t\tthis.targetSize.width = this.device.canvas.scrollWidth;\n\t\t\t\tthis.targetSize.height = this.device.canvas.scrollHeight;\n\t\t}\n\t\trender(app, layer, graphTexture, wordsTexture, clr, height) {\n\t\t\t\tthis.buffer.setData(this.data.buffer);\n\t\t\t\tthis.uniforms.clr.set(clr, 0);\n\t\t\t\tthis.material.setParameter('clr', this.uniforms.clr);\n\t\t\t\tthis.material.setParameter('graphTex', graphTexture);\n\t\t\t\tthis.material.setParameter('wordsTex', wordsTexture);\n\t\t\t\tapp.drawMeshInstance(this.meshInstance, layer);\n\t\t}\n}\n\nclass MiniStats {\n\t\tconstructor(app, options){\n\t\t\t\tconst device = app.graphicsDevice;\n\t\t\t\toptions = options || MiniStats.getDefaultOptions();\n\t\t\t\tthis.initGraphs(app, device, options);\n\t\t\t\tconst words = new Set([\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'ms',\n\t\t\t\t\t\t'0',\n\t\t\t\t\t\t'1',\n\t\t\t\t\t\t'2',\n\t\t\t\t\t\t'3',\n\t\t\t\t\t\t'4',\n\t\t\t\t\t\t'5',\n\t\t\t\t\t\t'6',\n\t\t\t\t\t\t'7',\n\t\t\t\t\t\t'8',\n\t\t\t\t\t\t'9',\n\t\t\t\t\t\t'.'\n\t\t\t\t].concat(this.graphs.map((graph)=>graph.name)).concat(options.stats ? options.stats.map((stat)=>stat.unitsName) : []).filter((item)=>!!item));\n\t\t\t\tthis.wordAtlas = new WordAtlas(device, words);\n\t\t\t\tthis.sizes = options.sizes;\n\t\t\t\tthis._activeSizeIndex = options.startSizeIndex;\n\t\t\t\tconst div = document.createElement('div');\n\t\t\t\tdiv.setAttribute('id', 'mini-stats');\n\t\t\t\tdiv.style.cssText = 'position:fixed;bottom:0;left:0;background:transparent;';\n\t\t\t\tdocument.body.appendChild(div);\n\t\t\t\tdiv.addEventListener('mouseenter', (event)=>{\n\t\t\t\t\t\tthis.opacity = 1.0;\n\t\t\t\t});\n\t\t\t\tdiv.addEventListener('mouseleave', (event)=>{\n\t\t\t\t\t\tthis.opacity = 0.7;\n\t\t\t\t});\n\t\t\t\tdiv.addEventListener('click', (event)=>{\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tif (this._enabled) {\n\t\t\t\t\t\t\t\tthis.activeSizeIndex = (this.activeSizeIndex + 1) % this.sizes.length;\n\t\t\t\t\t\t\t\tthis.resize(this.sizes[this.activeSizeIndex].width, this.sizes[this.activeSizeIndex].height, this.sizes[this.activeSizeIndex].graphs);\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tdevice.on('resizecanvas', this.updateDiv, this);\n\t\t\t\tdevice.on('losecontext', this.loseContext, this);\n\t\t\t\tapp.on('postrender', this.postRender, this);\n\t\t\t\tthis.app = app;\n\t\t\t\tthis.drawLayer = app.scene.layers.getLayerById(LAYERID_UI);\n\t\t\t\tthis.device = device;\n\t\t\t\tthis.render2d = new Render2d(device);\n\t\t\t\tthis.div = div;\n\t\t\t\tthis.width = 0;\n\t\t\t\tthis.height = 0;\n\t\t\t\tthis.gspacing = 2;\n\t\t\t\tthis.clr = [\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t0.5\n\t\t\t\t];\n\t\t\t\tthis._enabled = true;\n\t\t\t\tthis.activeSizeIndex = this._activeSizeIndex;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.device.off('resizecanvas', this.updateDiv, this);\n\t\t\t\tthis.device.off('losecontext', this.loseContext, this);\n\t\t\t\tthis.app.off('postrender', this.postRender, this);\n\t\t\t\tthis.graphs.forEach((graph)=>graph.destroy());\n\t\t\t\tthis.wordAtlas.destroy();\n\t\t\t\tthis.texture.destroy();\n\t\t}\n\t\tstatic getDefaultOptions() {\n\t\t\t\treturn {\n\t\t\t\t\t\tsizes: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twidth: 100,\n\t\t\t\t\t\t\t\t\t\theight: 16,\n\t\t\t\t\t\t\t\t\t\tspacing: 0,\n\t\t\t\t\t\t\t\t\t\tgraphs: false\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twidth: 128,\n\t\t\t\t\t\t\t\t\t\theight: 32,\n\t\t\t\t\t\t\t\t\t\tspacing: 2,\n\t\t\t\t\t\t\t\t\t\tgraphs: true\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twidth: 256,\n\t\t\t\t\t\t\t\t\t\theight: 64,\n\t\t\t\t\t\t\t\t\t\tspacing: 2,\n\t\t\t\t\t\t\t\t\t\tgraphs: true\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\tstartSizeIndex: 0,\n\t\t\t\t\t\ttextRefreshRate: 500,\n\t\t\t\t\t\tcpu: {\n\t\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\t\twatermark: 33\n\t\t\t\t\t\t},\n\t\t\t\t\t\tgpu: {\n\t\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\t\twatermark: 33\n\t\t\t\t\t\t},\n\t\t\t\t\t\tstats: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: 'Frame',\n\t\t\t\t\t\t\t\t\t\tstats: [\n\t\t\t\t\t\t\t\t\t\t\t\t'frame.ms'\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tdecimalPlaces: 1,\n\t\t\t\t\t\t\t\t\t\tunitsName: 'ms',\n\t\t\t\t\t\t\t\t\t\twatermark: 33\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: 'DrawCalls',\n\t\t\t\t\t\t\t\t\t\tstats: [\n\t\t\t\t\t\t\t\t\t\t\t\t'drawCalls.total'\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\twatermark: 1000\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t};\n\t\t}\n\t\tset activeSizeIndex(value) {\n\t\t\t\tthis._activeSizeIndex = value;\n\t\t\t\tthis.gspacing = this.sizes[value].spacing;\n\t\t\t\tthis.resize(this.sizes[value].width, this.sizes[value].height, this.sizes[value].graphs);\n\t\t}\n\t\tget activeSizeIndex() {\n\t\t\t\treturn this._activeSizeIndex;\n\t\t}\n\t\tset opacity(value) {\n\t\t\t\tthis.clr[3] = value;\n\t\t}\n\t\tget opacity() {\n\t\t\t\treturn this.clr[3];\n\t\t}\n\t\tget overallHeight() {\n\t\t\t\tconst graphs = this.graphs;\n\t\t\t\tconst spacing = this.gspacing;\n\t\t\t\treturn this.height * graphs.length + spacing * (graphs.length - 1);\n\t\t}\n\t\tset enabled(value) {\n\t\t\t\tif (value !== this._enabled) {\n\t\t\t\t\t\tthis._enabled = value;\n\t\t\t\t\t\tfor(let i = 0; i < this.graphs.length; ++i){\n\t\t\t\t\t\t\t\tthis.graphs[i].enabled = value;\n\t\t\t\t\t\t\t\tthis.graphs[i].timer.enabled = value;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\tget enabled() {\n\t\t\t\treturn this._enabled;\n\t\t}\n\t\tinitGraphs(app, device, options) {\n\t\t\t\tthis.graphs = [];\n\t\t\t\tif (options.cpu.enabled) {\n\t\t\t\t\t\tconst timer = new CpuTimer(app);\n\t\t\t\t\t\tconst graph = new Graph('CPU', app, options.cpu.watermark, options.textRefreshRate, timer);\n\t\t\t\t\t\tthis.graphs.push(graph);\n\t\t\t\t}\n\t\t\t\tif (options.gpu.enabled) {\n\t\t\t\t\t\tconst timer = new GpuTimer(device);\n\t\t\t\t\t\tconst graph = new Graph('GPU', app, options.gpu.watermark, options.textRefreshRate, timer);\n\t\t\t\t\t\tthis.graphs.push(graph);\n\t\t\t\t}\n\t\t\t\tif (options.stats) {\n\t\t\t\t\t\toptions.stats.forEach((entry)=>{\n\t\t\t\t\t\t\t\tconst timer = new StatsTimer(app, entry.stats, entry.decimalPlaces, entry.unitsName, entry.multiplier);\n\t\t\t\t\t\t\t\tconst graph = new Graph(entry.name, app, entry.watermark, options.textRefreshRate, timer);\n\t\t\t\t\t\t\t\tthis.graphs.push(graph);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst maxWidth = options.sizes.reduce((max, v)=>{\n\t\t\t\t\t\treturn v.width > max ? v.width : max;\n\t\t\t\t}, 0);\n\t\t\t\tthis.texture = new Texture(device, {\n\t\t\t\t\t\tname: 'mini-stats-graph-texture',\n\t\t\t\t\t\twidth: math.nextPowerOfTwo(maxWidth),\n\t\t\t\t\t\theight: math.nextPowerOfTwo(this.graphs.length),\n\t\t\t\t\t\tmipmaps: false,\n\t\t\t\t\t\tminFilter: FILTER_NEAREST,\n\t\t\t\t\t\tmagFilter: FILTER_NEAREST,\n\t\t\t\t\t\taddressU: ADDRESS_REPEAT,\n\t\t\t\t\t\taddressV: ADDRESS_REPEAT\n\t\t\t\t});\n\t\t\t\tthis.graphs.forEach((graph, i)=>{\n\t\t\t\t\t\tgraph.texture = this.texture;\n\t\t\t\t\t\tgraph.yOffset = i;\n\t\t\t\t});\n\t\t}\n\t\trender() {\n\t\t\t\tconst graphs = this.graphs;\n\t\t\t\tconst wordAtlas = this.wordAtlas;\n\t\t\t\tconst render2d = this.render2d;\n\t\t\t\tconst width = this.width;\n\t\t\t\tconst height = this.height;\n\t\t\t\tconst gspacing = this.gspacing;\n\t\t\t\trender2d.startFrame();\n\t\t\t\tfor(let i = 0; i < graphs.length; ++i){\n\t\t\t\t\t\tconst graph = graphs[i];\n\t\t\t\t\t\tlet y = i * (height + gspacing);\n\t\t\t\t\t\tgraph.render(render2d, 0, y, width, height);\n\t\t\t\t\t\tlet x = 1;\n\t\t\t\t\t\ty += height - 13;\n\t\t\t\t\t\tx += wordAtlas.render(render2d, graph.name, x, y) + 10;\n\t\t\t\t\t\tconst timingText = graph.timingText;\n\t\t\t\t\t\tfor(let j = 0; j < timingText.length; ++j){\n\t\t\t\t\t\t\t\tx += wordAtlas.render(render2d, timingText[j], x, y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (graph.timer.unitsName) {\n\t\t\t\t\t\t\t\tx += 3;\n\t\t\t\t\t\t\t\twordAtlas.render(render2d, graph.timer.unitsName, x, y);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trender2d.render(this.app, this.drawLayer, this.texture, this.wordAtlas.texture, this.clr, height);\n\t\t}\n\t\tresize(width, height, showGraphs) {\n\t\t\t\tconst graphs = this.graphs;\n\t\t\t\tfor(let i = 0; i < graphs.length; ++i){\n\t\t\t\t\t\tgraphs[i].enabled = showGraphs;\n\t\t\t\t}\n\t\t\t\tthis.width = width;\n\t\t\t\tthis.height = height;\n\t\t\t\tthis.updateDiv();\n\t\t}\n\t\tupdateDiv() {\n\t\t\t\tconst rect = this.device.canvas.getBoundingClientRect();\n\t\t\t\tthis.div.style.left = `${rect.left}px`;\n\t\t\t\tthis.div.style.bottom = `${window.innerHeight - rect.bottom}px`;\n\t\t\t\tthis.div.style.width = `${this.width}px`;\n\t\t\t\tthis.div.style.height = `${this.overallHeight}px`;\n\t\t}\n\t\tloseContext() {\n\t\t\t\tthis.graphs.forEach((graph)=>graph.loseContext());\n\t\t}\n\t\tpostRender() {\n\t\t\t\tif (this._enabled) {\n\t\t\t\t\t\tthis.render();\n\t\t\t\t}\n\t\t}\n}\n\nconst tmpV1$1 = new Vec3();\nconst rotation$2 = new Quat();\nclass Pose {\n\t\tconstructor(position = Vec3.ZERO, angles = Vec3.ZERO, distance = 0){\n\t\t\t\tthis.position = new Vec3();\n\t\t\t\tthis.angles = new Vec3();\n\t\t\t\tthis.distance = 0;\n\t\t\t\tthis.pitchRange = new Vec2(-Infinity, Infinity);\n\t\t\t\tthis.yawRange = new Vec2(-Infinity, Infinity);\n\t\t\t\tthis.xRange = new Vec2(-Infinity, Infinity);\n\t\t\t\tthis.yRange = new Vec2(-Infinity, Infinity);\n\t\t\t\tthis.zRange = new Vec2(-Infinity, Infinity);\n\t\t\t\tthis.set(position, angles, distance);\n\t\t}\n\t\tcopy(other) {\n\t\t\t\treturn this.set(other.position, other.angles, other.distance);\n\t\t}\n\t\tclone() {\n\t\t\t\treturn new Pose(this.position.clone(), this.angles.clone(), this.distance);\n\t\t}\n\t\tequalsApprox(other, epsilon = 1e-6) {\n\t\t\t\treturn this.position.equalsApprox(other.position, epsilon) && this.angles.equalsApprox(other.angles, epsilon) && Math.abs(this.distance - other.distance) < epsilon;\n\t\t}\n\t\tlerp(lhs, rhs, alpha1, alpha2 = alpha1, alpha3 = alpha1) {\n\t\t\t\tthis.position.lerp(lhs.position, rhs.position, alpha1);\n\t\t\t\tthis.angles.x = math.lerpAngle(lhs.angles.x, rhs.angles.x, alpha2) % 360;\n\t\t\t\tthis.angles.y = math.lerpAngle(lhs.angles.y, rhs.angles.y, alpha2) % 360;\n\t\t\t\tthis.angles.z = math.lerpAngle(lhs.angles.z, rhs.angles.z, alpha2) % 360;\n\t\t\t\tthis.distance = math.lerp(lhs.distance, rhs.distance, alpha3);\n\t\t\t\treturn this;\n\t\t}\n\t\tmove(offset) {\n\t\t\t\tthis.position.add(offset);\n\t\t\t\tthis.position.x = math.clamp(this.position.x, this.xRange.x, this.xRange.y);\n\t\t\t\tthis.position.y = math.clamp(this.position.y, this.yRange.x, this.yRange.y);\n\t\t\t\tthis.position.z = math.clamp(this.position.z, this.zRange.x, this.zRange.y);\n\t\t\t\treturn this;\n\t\t}\n\t\trotate(euler) {\n\t\t\t\tthis.angles.add(euler);\n\t\t\t\tthis.angles.x %= 360;\n\t\t\t\tthis.angles.y %= 360;\n\t\t\t\tthis.angles.z %= 360;\n\t\t\t\tthis.angles.x = math.clamp(this.angles.x, this.pitchRange.x, this.pitchRange.y);\n\t\t\t\tthis.angles.y = math.clamp(this.angles.y, this.yawRange.x, this.yawRange.y);\n\t\t\t\treturn this;\n\t\t}\n\t\tset(position, angles, distance) {\n\t\t\t\tthis.position.copy(position);\n\t\t\t\tthis.angles.copy(angles);\n\t\t\t\tthis.distance = distance;\n\t\t\t\treturn this;\n\t\t}\n\t\tlook(from, to) {\n\t\t\t\tthis.position.copy(from);\n\t\t\t\tthis.distance = from.distance(to);\n\t\t\t\tconst dir = tmpV1$1.sub2(to, from).normalize();\n\t\t\t\tconst elev = Math.atan2(-dir.y, Math.sqrt(dir.x * dir.x + dir.z * dir.z)) * math.RAD_TO_DEG;\n\t\t\t\tconst azim = Math.atan2(-dir.x, -dir.z) * math.RAD_TO_DEG;\n\t\t\t\tthis.angles.set(-elev, azim, 0);\n\t\t\t\treturn this;\n\t\t}\n\t\tgetFocus(out) {\n\t\t\t\treturn rotation$2.setFromEulerAngles(this.angles).transformVector(Vec3.FORWARD, out).mulScalar(this.distance).add(this.position);\n\t\t}\n}\n\nclass InputDelta {\n\t\tconstructor(arg){\n\t\t\t\tif (Array.isArray(arg)) {\n\t\t\t\t\t\tthis._value = arg.slice();\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._value = new Array(+arg).fill(0);\n\t\t\t\t}\n\t\t}\n\t\tadd(other) {\n\t\t\t\tfor(let i = 0; i < this._value.length; i++){\n\t\t\t\t\t\tthis._value[i] += other._value[i] || 0;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tappend(offsets) {\n\t\t\t\tfor(let i = 0; i < this._value.length; i++){\n\t\t\t\t\t\tthis._value[i] += offsets[i] || 0;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tcopy(other) {\n\t\t\t\tfor(let i = 0; i < this._value.length; i++){\n\t\t\t\t\t\tthis._value[i] = other._value[i] || 0;\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t}\n\t\tlength() {\n\t\t\t\tlet sum = 0;\n\t\t\t\tfor (const value of this._value){\n\t\t\t\t\t\tsum += value * value;\n\t\t\t\t}\n\t\t\t\treturn Math.sqrt(sum);\n\t\t}\n\t\tread() {\n\t\t\t\tconst value = this._value.slice();\n\t\t\t\tthis._value.fill(0);\n\t\t\t\treturn value;\n\t\t}\n}\nclass InputFrame {\n\t\tconstructor(data){\n\t\t\t\tthis.deltas = {};\n\t\t\t\tfor(const name in data){\n\t\t\t\t\t\tthis.deltas[name] = new InputDelta(data[name]);\n\t\t\t\t}\n\t\t}\n\t\tread() {\n\t\t\t\tconst frame = {};\n\t\t\t\tfor(const name in this.deltas){\n\t\t\t\t\t\tframe[name] = this.deltas[name].read();\n\t\t\t\t}\n\t\t\t\treturn frame;\n\t\t}\n}\nclass InputSource extends InputFrame {\n\t\ton(event, callback) {\n\t\t\t\tthis._events.on(event, callback);\n\t\t}\n\t\toff(event, callback) {\n\t\t\t\tthis._events.off(event, callback);\n\t\t}\n\t\tfire(event, ...args) {\n\t\t\t\tthis._events.fire(event, ...args);\n\t\t}\n\t\tattach(element) {\n\t\t\t\tif (this._element) {\n\t\t\t\t\t\tthis.detach();\n\t\t\t\t}\n\t\t\t\tthis._element = element;\n\t\t}\n\t\tdetach() {\n\t\t\t\tif (!this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element = null;\n\t\t\t\tthis.read();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.detach();\n\t\t\t\tthis._events.off();\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._element = null, this._events = new EventHandler();\n\t\t}\n}\nclass InputConsumer {\n\t\tupdate(frame, dt) {\n\t\t\t\tframe.read();\n\t\t}\n}\nclass InputController extends InputConsumer {\n\t\tattach(pose, smooth = true) {}\n\t\tdetach() {}\n\t\tupdate(frame, dt) {\n\t\t\t\tsuper.update(frame, dt);\n\t\t\t\treturn this._pose;\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.detach();\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._pose = new Pose();\n\t\t}\n}\n\nconst DOUBLE_TAP_THRESHOLD = 250;\nconst DOUBLE_TAP_VARIANCE = 100;\n\nconst v$1 = new Vec2();\nclass VirtualJoystick {\n\t\tconstructor({ range } = {}){\n\t\t\t\tthis._range = 70;\n\t\t\t\tthis._position = new Vec2();\n\t\t\t\tthis._value = new Vec2();\n\t\t\t\tthis._range = range ?? this._range;\n\t\t}\n\t\tget value() {\n\t\t\t\treturn this._value;\n\t\t}\n\t\tdown(x, y) {\n\t\t\t\tthis._position.set(x, y);\n\t\t\t\tthis._value.set(0, 0);\n\t\t\t\treturn [\n\t\t\t\t\t\tx,\n\t\t\t\t\t\ty,\n\t\t\t\t\t\tx,\n\t\t\t\t\t\ty\n\t\t\t\t];\n\t\t}\n\t\tmove(x, y) {\n\t\t\t\tv$1.set(x - this._position.x, y - this._position.y);\n\t\t\t\tif (v$1.length() > this._range) {\n\t\t\t\t\t\tv$1.normalize().mulScalar(this._range);\n\t\t\t\t}\n\t\t\t\tthis._value.set(math.clamp(v$1.x / this._range, -1, 1), math.clamp(v$1.y / this._range, -1, 1));\n\t\t\t\tconst { x: bx, y: by } = this._position;\n\t\t\t\treturn [\n\t\t\t\t\t\tbx,\n\t\t\t\t\t\tby,\n\t\t\t\t\t\tbx + v$1.x,\n\t\t\t\t\t\tby + v$1.y\n\t\t\t\t];\n\t\t}\n\t\tup() {\n\t\t\t\tthis._position.set(0, 0);\n\t\t\t\tthis._value.set(0, 0);\n\t\t\t\treturn [\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t-1,\n\t\t\t\t\t\t-1\n\t\t\t\t];\n\t\t}\n}\n\nconst startsWith = (str, prefix)=>str.indexOf(prefix) === 0;\nconst endsWith = (str, suffix)=>str.indexOf(suffix, str.length - suffix.length) !== -1;\nclass DualGestureSource extends InputSource {\n\t\tconstructor(layout){\n\t\t\t\tsuper({\n\t\t\t\t\t\tleftInput: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\trightInput: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdoubleTap: [\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t]\n\t\t\t\t}), this._layout = 'joystick-touch', this._pointerData = new Map(), this._lastPointer = {\n\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\ttime: 0\n\t\t\t\t};\n\t\t\t\tif (layout) {\n\t\t\t\t\t\tthis.layout = layout;\n\t\t\t\t}\n\t\t\t\tthis._leftJoystick = new VirtualJoystick();\n\t\t\t\tthis._rightJoystick = new VirtualJoystick();\n\t\t\t\tthis._onPointerDown = this._onPointerDown.bind(this);\n\t\t\t\tthis._onPointerMove = this._onPointerMove.bind(this);\n\t\t\t\tthis._onPointerUp = this._onPointerUp.bind(this);\n\t\t}\n\t\tset layout(value) {\n\t\t\t\tif (this._layout === value) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._layout = value;\n\t\t\t\tthis.read();\n\t\t\t\tthis._pointerData.clear();\n\t\t}\n\t\tget layout() {\n\t\t\t\treturn this._layout;\n\t\t}\n\t\tget leftJoystick() {\n\t\t\t\treturn this._leftJoystick;\n\t\t}\n\t\tget rightJoystick() {\n\t\t\t\treturn this._rightJoystick;\n\t\t}\n\t\t_onPointerDown(event) {\n\t\t\t\tconst { pointerType, pointerId, clientX, clientY } = event;\n\t\t\t\tif (pointerType !== 'touch') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element?.setPointerCapture(pointerId);\n\t\t\t\tconst left = clientX < window.innerWidth * 0.5;\n\t\t\t\tthis._pointerData.set(pointerId, {\n\t\t\t\t\t\tx: clientX,\n\t\t\t\t\t\ty: clientY,\n\t\t\t\t\t\tleft\n\t\t\t\t});\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst sqrDist = (this._lastPointer.x - clientX) ** 2 + (this._lastPointer.y - clientY) ** 2;\n\t\t\t\tif (sqrDist < DOUBLE_TAP_VARIANCE && now - this._lastPointer.time < DOUBLE_TAP_THRESHOLD) {\n\t\t\t\t\t\tthis.deltas.doubleTap.append([\n\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\tthis._lastPointer.x = clientX;\n\t\t\t\tthis._lastPointer.y = clientY;\n\t\t\t\tthis._lastPointer.time = now;\n\t\t\t\tif (left && startsWith(this._layout, 'joystick')) {\n\t\t\t\t\t\tthis.fire('joystick:position:left', this._leftJoystick.down(clientX, clientY));\n\t\t\t\t}\n\t\t\t\tif (!left && endsWith(this._layout, 'joystick')) {\n\t\t\t\t\t\tthis.fire('joystick:position:right', this._rightJoystick.down(clientX, clientY));\n\t\t\t\t}\n\t\t}\n\t\t_onPointerMove(event) {\n\t\t\t\tconst { pointerType, pointerId, target, clientX, clientY, movementX, movementY } = event;\n\t\t\t\tif (pointerType !== 'touch') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (target !== this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = this._pointerData.get(pointerId);\n\t\t\t\tif (!data) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { left } = data;\n\t\t\t\tdata.x = clientX;\n\t\t\t\tdata.y = clientY;\n\t\t\t\tif (left) {\n\t\t\t\t\t\tif (startsWith(this._layout, 'joystick')) {\n\t\t\t\t\t\t\t\tthis.fire('joystick:position:left', this._leftJoystick.move(clientX, clientY));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.deltas.leftInput.append([\n\t\t\t\t\t\t\t\t\t\tmovementX,\n\t\t\t\t\t\t\t\t\t\tmovementY\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (endsWith(this._layout, 'joystick')) {\n\t\t\t\t\t\t\t\tthis.fire('joystick:position:right', this._rightJoystick.move(clientX, clientY));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.deltas.rightInput.append([\n\t\t\t\t\t\t\t\t\t\tmovementX,\n\t\t\t\t\t\t\t\t\t\tmovementY\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\t_onPointerUp(event) {\n\t\t\t\tconst { pointerType, pointerId } = event;\n\t\t\t\tif (pointerType !== 'touch') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element?.releasePointerCapture(pointerId);\n\t\t\t\tconst data = this._pointerData.get(pointerId);\n\t\t\t\tif (!data) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { left } = data;\n\t\t\t\tthis._pointerData.delete(pointerId);\n\t\t\t\tif (left && startsWith(this._layout, 'joystick')) {\n\t\t\t\t\t\tthis.fire('joystick:position:left', this._leftJoystick.up());\n\t\t\t\t}\n\t\t\t\tif (!left && endsWith(this._layout, 'joystick')) {\n\t\t\t\t\t\tthis.fire('joystick:position:right', this._rightJoystick.up());\n\t\t\t\t}\n\t\t}\n\t\tattach(element) {\n\t\t\t\tsuper.attach(element);\n\t\t\t\tthis._element = element;\n\t\t\t\tthis._element.addEventListener('pointerdown', this._onPointerDown);\n\t\t\t\tthis._element.addEventListener('pointermove', this._onPointerMove);\n\t\t\t\tthis._element.addEventListener('pointerup', this._onPointerUp);\n\t\t\t\tthis._element.addEventListener('pointercancel', this._onPointerUp);\n\t\t}\n\t\tdetach() {\n\t\t\t\tif (!this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element.removeEventListener('pointerdown', this._onPointerDown);\n\t\t\t\tthis._element.removeEventListener('pointermove', this._onPointerMove);\n\t\t\t\tthis._element.removeEventListener('pointerup', this._onPointerUp);\n\t\t\t\tthis._element.removeEventListener('pointercancel', this._onPointerUp);\n\t\t\t\tthis._pointerData.clear();\n\t\t\t\tsuper.detach();\n\t\t}\n\t\tread() {\n\t\t\t\tthis.deltas.leftInput.append([\n\t\t\t\t\t\tthis._leftJoystick.value.x,\n\t\t\t\t\t\tthis._leftJoystick.value.y\n\t\t\t\t]);\n\t\t\t\tthis.deltas.rightInput.append([\n\t\t\t\t\t\tthis._rightJoystick.value.x,\n\t\t\t\t\t\tthis._rightJoystick.value.y\n\t\t\t\t]);\n\t\t\t\treturn super.read();\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis._leftJoystick.up();\n\t\t\t\tthis._rightJoystick.up();\n\t\t\t\tsuper.destroy();\n\t\t}\n}\n\nconst tmpVa = new Vec2();\nclass MultiTouchSource extends InputSource {\n\t\tconstructor(){\n\t\t\t\tsuper({\n\t\t\t\t\t\ttouch: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\tcount: [\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\tpinch: [\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t]\n\t\t\t\t}), this._pointerEvents = new Map(), this._pointerPos = new Vec2(), this._pinchDist = -1;\n\t\t\t\tthis._onPointerDown = this._onPointerDown.bind(this);\n\t\t\t\tthis._onPointerMove = this._onPointerMove.bind(this);\n\t\t\t\tthis._onPointerUp = this._onPointerUp.bind(this);\n\t\t\t\tthis._onContextMenu = this._onContextMenu.bind(this);\n\t\t}\n\t\t_onPointerDown(event) {\n\t\t\t\tconst { pointerId, pointerType } = event;\n\t\t\t\tif (pointerType !== 'touch') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element?.setPointerCapture(pointerId);\n\t\t\t\tthis._pointerEvents.set(pointerId, event);\n\t\t\t\tthis.deltas.count.append([\n\t\t\t\t\t\t1\n\t\t\t\t]);\n\t\t\t\tif (this._pointerEvents.size > 1) {\n\t\t\t\t\t\tthis._getMidPoint(this._pointerPos);\n\t\t\t\t\t\tthis._pinchDist = this._getPinchDist();\n\t\t\t\t}\n\t\t}\n\t\t_onPointerMove(event) {\n\t\t\t\tconst { pointerType, target, pointerId, movementX, movementY } = event;\n\t\t\t\tif (pointerType !== 'touch') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (target !== this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._pointerEvents.size === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._pointerEvents.set(pointerId, event);\n\t\t\t\tif (this._pointerEvents.size > 1) {\n\t\t\t\t\t\tconst mid = this._getMidPoint(tmpVa);\n\t\t\t\t\t\tthis.deltas.touch.append([\n\t\t\t\t\t\t\t\tmid.x - this._pointerPos.x,\n\t\t\t\t\t\t\t\tmid.y - this._pointerPos.y\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis._pointerPos.copy(mid);\n\t\t\t\t\t\tconst pinchDist = this._getPinchDist();\n\t\t\t\t\t\tif (this._pinchDist > 0) {\n\t\t\t\t\t\t\t\tthis.deltas.pinch.append([\n\t\t\t\t\t\t\t\t\t\tthis._pinchDist - pinchDist\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._pinchDist = pinchDist;\n\t\t\t\t} else {\n\t\t\t\t\t\tthis.deltas.touch.append([\n\t\t\t\t\t\t\t\tmovementX,\n\t\t\t\t\t\t\t\tmovementY\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t}\n\t\t_onPointerUp(event) {\n\t\t\t\tconst { pointerType, pointerId } = event;\n\t\t\t\tif (pointerType !== 'touch') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element?.releasePointerCapture(pointerId);\n\t\t\t\tthis._pointerEvents.delete(pointerId);\n\t\t\t\tthis.deltas.count.append([\n\t\t\t\t\t\t-1\n\t\t\t\t]);\n\t\t\t\tif (this._pointerEvents.size < 2) {\n\t\t\t\t\t\tthis._pinchDist = -1;\n\t\t\t\t}\n\t\t\t\tthis._pointerPos.set(0, 0);\n\t\t}\n\t\t_onContextMenu(event) {\n\t\t\t\tevent.preventDefault();\n\t\t}\n\t\t_getMidPoint(out) {\n\t\t\t\tif (this._pointerEvents.size < 2) {\n\t\t\t\t\t\treturn out.set(0, 0);\n\t\t\t\t}\n\t\t\t\tconst [a, b] = this._pointerEvents.values();\n\t\t\t\tconst dx = a.clientX - b.clientX;\n\t\t\t\tconst dy = a.clientY - b.clientY;\n\t\t\t\treturn out.set(b.clientX + dx * 0.5, b.clientY + dy * 0.5);\n\t\t}\n\t\t_getPinchDist() {\n\t\t\t\tif (this._pointerEvents.size < 2) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tconst [a, b] = this._pointerEvents.values();\n\t\t\t\tconst dx = a.clientX - b.clientX;\n\t\t\t\tconst dy = a.clientY - b.clientY;\n\t\t\t\treturn Math.sqrt(dx * dx + dy * dy);\n\t\t}\n\t\tattach(element) {\n\t\t\t\tsuper.attach(element);\n\t\t\t\tthis._element = element;\n\t\t\t\tthis._element.addEventListener('pointerdown', this._onPointerDown);\n\t\t\t\tthis._element.addEventListener('pointermove', this._onPointerMove);\n\t\t\t\tthis._element.addEventListener('pointerup', this._onPointerUp);\n\t\t\t\tthis._element.addEventListener('pointercancel', this._onPointerUp);\n\t\t\t\tthis._element.addEventListener('contextmenu', this._onContextMenu);\n\t\t}\n\t\tdetach() {\n\t\t\t\tif (!this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element.removeEventListener('pointerdown', this._onPointerDown);\n\t\t\t\tthis._element.removeEventListener('pointermove', this._onPointerMove);\n\t\t\t\tthis._element.removeEventListener('pointerup', this._onPointerUp);\n\t\t\t\tthis._element.removeEventListener('pointercancel', this._onPointerUp);\n\t\t\t\tthis._element.removeEventListener('contextmenu', this._onContextMenu);\n\t\t\t\tthis._pointerEvents.clear();\n\t\t\t\tsuper.detach();\n\t\t}\n}\n\nconst PASSIVE = {\n\t\tpassive: false\n};\nconst KEY_CODES = {\n\t\tA: 0,\n\t\tB: 1,\n\t\tC: 2,\n\t\tD: 3,\n\t\tE: 4,\n\t\tF: 5,\n\t\tG: 6,\n\t\tH: 7,\n\t\tI: 8,\n\t\tJ: 9,\n\t\tK: 10,\n\t\tL: 11,\n\t\tM: 12,\n\t\tN: 13,\n\t\tO: 14,\n\t\tP: 15,\n\t\tQ: 16,\n\t\tR: 17,\n\t\tS: 18,\n\t\tT: 19,\n\t\tU: 20,\n\t\tV: 21,\n\t\tW: 22,\n\t\tX: 23,\n\t\tY: 24,\n\t\tZ: 25,\n\t\t'0': 26,\n\t\t'1': 27,\n\t\t'2': 28,\n\t\t'3': 29,\n\t\t'4': 30,\n\t\t'5': 31,\n\t\t'6': 32,\n\t\t'7': 33,\n\t\t'8': 34,\n\t\t'9': 35,\n\t\tUP: 36,\n\t\tDOWN: 37,\n\t\tLEFT: 38,\n\t\tRIGHT: 39,\n\t\tSPACE: 40,\n\t\tSHIFT: 41,\n\t\tCTRL: 42\n};\nconst KEY_COUNT = Object.keys(KEY_CODES).length;\nconst array = Array(KEY_COUNT).fill(0);\nclass KeyboardMouseSource extends InputSource {\n\t\tstatic{\n\t\t\t\tthis.keyCode = KEY_CODES;\n\t\t}\n\t\tconstructor({ pointerLock = false } = {}){\n\t\t\t\tsuper({\n\t\t\t\t\t\tkey: Array(KEY_COUNT).fill(0),\n\t\t\t\t\t\tbutton: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\tmouse: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\twheel: [\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t]\n\t\t\t\t}), this._pointerId = -1, this._keyMap = new Map(), this._keyPrev = Array(KEY_COUNT).fill(0), this._keyNow = Array(KEY_COUNT).fill(0), this._button = Array(3).fill(0);\n\t\t\t\tthis._pointerLock = pointerLock ?? false;\n\t\t\t\tconst { keyCode } = KeyboardMouseSource;\n\t\t\t\tfor(let i = 0; i < 26; i++){\n\t\t\t\t\t\tconst code = `Key${String.fromCharCode('A'.charCodeAt(0) + i)}`;\n\t\t\t\t\t\tthis._keyMap.set(code, keyCode.A + i);\n\t\t\t\t}\n\t\t\t\tfor(let i = 0; i < 10; i++){\n\t\t\t\t\t\tconst code = `Digit${i}`;\n\t\t\t\t\t\tthis._keyMap.set(code, keyCode['0'] + i);\n\t\t\t\t}\n\t\t\t\tthis._keyMap.set('ArrowUp', keyCode.UP);\n\t\t\t\tthis._keyMap.set('ArrowDown', keyCode.DOWN);\n\t\t\t\tthis._keyMap.set('ArrowLeft', keyCode.LEFT);\n\t\t\t\tthis._keyMap.set('ArrowRight', keyCode.RIGHT);\n\t\t\t\tthis._keyMap.set('Space', keyCode.SPACE);\n\t\t\t\tthis._keyMap.set('ShiftLeft', keyCode.SHIFT);\n\t\t\t\tthis._keyMap.set('ShiftRight', keyCode.SHIFT);\n\t\t\t\tthis._keyMap.set('ControlLeft', keyCode.CTRL);\n\t\t\t\tthis._keyMap.set('ControlRight', keyCode.CTRL);\n\t\t\t\tthis._onWheel = this._onWheel.bind(this);\n\t\t\t\tthis._onPointerDown = this._onPointerDown.bind(this);\n\t\t\t\tthis._onPointerMove = this._onPointerMove.bind(this);\n\t\t\t\tthis._onPointerUp = this._onPointerUp.bind(this);\n\t\t\t\tthis._onContextMenu = this._onContextMenu.bind(this);\n\t\t\t\tthis._onKeyDown = this._onKeyDown.bind(this);\n\t\t\t\tthis._onKeyUp = this._onKeyUp.bind(this);\n\t\t}\n\t\t_onWheel(event) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.deltas.wheel.append([\n\t\t\t\t\t\tevent.deltaY\n\t\t\t\t]);\n\t\t}\n\t\t_onPointerDown(event) {\n\t\t\t\tif (event.pointerType !== 'mouse') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._pointerLock) {\n\t\t\t\t\t\tif (document.pointerLockElement !== this._element) {\n\t\t\t\t\t\t\t\tthis._element?.requestPointerLock();\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tthis._element?.setPointerCapture(event.pointerId);\n\t\t\t\t}\n\t\t\t\tthis._clearButtons();\n\t\t\t\tthis._button[event.button] = 1;\n\t\t\t\tthis.deltas.button.append(this._button);\n\t\t\t\tif (this._pointerId !== -1) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._pointerId = event.pointerId;\n\t\t}\n\t\t_onPointerMove(event) {\n\t\t\t\tif (event.pointerType !== 'mouse') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (event.target !== this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this._pointerLock) {\n\t\t\t\t\t\tif (document.pointerLockElement !== this._element) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\tif (this._pointerId !== event.pointerId) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.deltas.mouse.append([\n\t\t\t\t\t\tevent.movementX,\n\t\t\t\t\t\tevent.movementY\n\t\t\t\t]);\n\t\t}\n\t\t_onPointerUp(event) {\n\t\t\t\tif (event.pointerType !== 'mouse') {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this._pointerLock) {\n\t\t\t\t\t\tthis._element?.releasePointerCapture(event.pointerId);\n\t\t\t\t}\n\t\t\t\tthis._clearButtons();\n\t\t\t\tthis.deltas.button.append(this._button);\n\t\t\t\tif (this._pointerId !== event.pointerId) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._pointerId = -1;\n\t\t}\n\t\t_onContextMenu(event) {\n\t\t\t\tevent.preventDefault();\n\t\t}\n\t\t_onKeyDown(event) {\n\t\t\t\tif (this._pointerLock && document.pointerLockElement !== this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tthis._setKey(event.code, 1);\n\t\t}\n\t\t_onKeyUp(event) {\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tthis._setKey(event.code, 0);\n\t\t}\n\t\t_clearButtons() {\n\t\t\t\tfor(let i = 0; i < this._button.length; i++){\n\t\t\t\t\t\tif (this._button[i] === 1) {\n\t\t\t\t\t\t\t\tthis._button[i] = -1;\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._button[i] = 0;\n\t\t\t\t}\n\t\t}\n\t\t_setKey(code, value) {\n\t\t\t\tif (!this._keyMap.has(code)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._keyNow[this._keyMap.get(code) ?? 0] = value;\n\t\t}\n\t\tattach(element) {\n\t\t\t\tsuper.attach(element);\n\t\t\t\tthis._element = element;\n\t\t\t\tthis._element.addEventListener('wheel', this._onWheel, PASSIVE);\n\t\t\t\tthis._element.addEventListener('pointerdown', this._onPointerDown);\n\t\t\t\tthis._element.addEventListener('pointermove', this._onPointerMove);\n\t\t\t\tthis._element.addEventListener('pointerup', this._onPointerUp);\n\t\t\t\tthis._element.addEventListener('pointercancel', this._onPointerUp);\n\t\t\t\tthis._element.addEventListener('pointerleave', this._onPointerUp);\n\t\t\t\tthis._element.addEventListener('contextmenu', this._onContextMenu);\n\t\t\t\twindow.addEventListener('keydown', this._onKeyDown, false);\n\t\t\t\twindow.addEventListener('keyup', this._onKeyUp, false);\n\t\t}\n\t\tdetach() {\n\t\t\t\tif (!this._element) {\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._element.removeEventListener('wheel', this._onWheel, PASSIVE);\n\t\t\t\tthis._element.removeEventListener('pointerdown', this._onPointerDown);\n\t\t\t\tthis._element.removeEventListener('pointermove', this._onPointerMove);\n\t\t\t\tthis._element.removeEventListener('pointerup', this._onPointerUp);\n\t\t\t\tthis._element.removeEventListener('pointercancel', this._onPointerUp);\n\t\t\t\tthis._element.removeEventListener('pointerleave', this._onPointerUp);\n\t\t\t\tthis._element.removeEventListener('contextmenu', this._onContextMenu);\n\t\t\t\twindow.removeEventListener('keydown', this._onKeyDown, false);\n\t\t\t\twindow.removeEventListener('keyup', this._onKeyUp, false);\n\t\t\t\tthis._keyNow.fill(0);\n\t\t\t\tthis._keyPrev.fill(0);\n\t\t\t\tsuper.detach();\n\t\t}\n\t\tread() {\n\t\t\t\tfor(let i = 0; i < array.length; i++){\n\t\t\t\t\t\tarray[i] = this._keyNow[i] - this._keyPrev[i];\n\t\t\t\t\t\tthis._keyPrev[i] = this._keyNow[i];\n\t\t\t\t}\n\t\t\t\tthis.deltas.key.append(array);\n\t\t\t\treturn super.read();\n\t\t}\n}\n\nconst BUTTON_CODES = {\n\t\tA: 0,\n\t\tB: 1,\n\t\tX: 2,\n\t\tY: 3,\n\t\tLB: 4,\n\t\tRB: 5,\n\t\tLT: 6,\n\t\tRT: 7,\n\t\tSELECT: 8,\n\t\tSTART: 9,\n\t\tLEFT_STICK: 10,\n\t\tRIGHT_STICK: 11\n};\nconst BUTTON_COUNT = Object.keys(BUTTON_CODES).length;\nclass GamepadSource extends InputSource {\n\t\tstatic{\n\t\t\t\tthis.buttonCode = BUTTON_CODES;\n\t\t}\n\t\tconstructor(){\n\t\t\t\tsuper({\n\t\t\t\t\t\tbuttons: Array(BUTTON_COUNT).fill(0),\n\t\t\t\t\t\tleftStick: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t],\n\t\t\t\t\t\trightStick: [\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t]\n\t\t\t\t}), this._buttonPrev = Array(BUTTON_COUNT).fill(0);\n\t\t}\n\t\tread() {\n\t\t\t\tconst gamepads = navigator.getGamepads();\n\t\t\t\tfor(let i = 0; i < gamepads.length; i++){\n\t\t\t\t\t\tconst gp = gamepads[i];\n\t\t\t\t\t\tif (!gp) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (gp.mapping !== 'standard') {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (gp.axes.length < 4) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (gp.buttons.length < BUTTON_COUNT) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst { buttons, axes } = gp;\n\t\t\t\t\t\tfor(let j = 0; j < this._buttonPrev.length; j++){\n\t\t\t\t\t\t\t\tconst state = +buttons[j].pressed;\n\t\t\t\t\t\t\t\tthis.deltas.buttons[j] = state - this._buttonPrev[j];\n\t\t\t\t\t\t\t\tthis._buttonPrev[j] = state;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.deltas.leftStick.append([\n\t\t\t\t\t\t\t\taxes[0],\n\t\t\t\t\t\t\t\taxes[1]\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tthis.deltas.rightStick.append([\n\t\t\t\t\t\t\t\taxes[2],\n\t\t\t\t\t\t\t\taxes[3]\n\t\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t\treturn super.read();\n\t\t}\n}\n\nconst damp = (damping, dt)=>1 - Math.pow(damping, dt * 1000);\n\nconst offset$1 = new Vec3();\nconst angles$1 = new Vec3();\nconst forward = new Vec3();\nconst right = new Vec3();\nconst up = new Vec3();\nconst rotation$1 = new Quat();\nclass FlyController extends InputController {\n\t\tset pitchRange(value) {\n\t\t\t\tthis._targetPose.pitchRange.copy(value);\n\t\t\t\tthis._pose.copy(this._targetPose.rotate(Vec3.ZERO));\n\t\t}\n\t\tget pitchRange() {\n\t\t\t\treturn this._targetPose.pitchRange;\n\t\t}\n\t\tset yawRange(value) {\n\t\t\t\tthis._targetPose.yawRange.copy(value);\n\t\t\t\tthis._pose.copy(this._targetPose.rotate(Vec3.ZERO));\n\t\t}\n\t\tget yawRange() {\n\t\t\t\treturn this._targetPose.yawRange;\n\t\t}\n\t\tattach(pose, smooth = true) {\n\t\t\t\tthis._targetPose.copy(pose);\n\t\t\t\tif (!smooth) {\n\t\t\t\t\t\tthis._pose.copy(this._targetPose);\n\t\t\t\t}\n\t\t}\n\t\tdetach() {\n\t\t\t\tthis._targetPose.copy(this._pose);\n\t\t}\n\t\tupdate(frame, dt) {\n\t\t\t\tconst { move, rotate } = frame.read();\n\t\t\t\tthis._targetPose.rotate(angles$1.set(-rotate[1], -rotate[0], 0));\n\t\t\t\trotation$1.setFromEulerAngles(this._pose.angles);\n\t\t\t\trotation$1.transformVector(Vec3.FORWARD, forward);\n\t\t\t\trotation$1.transformVector(Vec3.RIGHT, right);\n\t\t\t\trotation$1.transformVector(Vec3.UP, up);\n\t\t\t\toffset$1.set(0, 0, 0);\n\t\t\t\toffset$1.add(forward.mulScalar(move[2]));\n\t\t\t\toffset$1.add(right.mulScalar(move[0]));\n\t\t\t\toffset$1.add(up.mulScalar(move[1]));\n\t\t\t\tthis._targetPose.move(offset$1);\n\t\t\t\treturn this._pose.lerp(this._pose, this._targetPose, damp(this.moveDamping, dt), damp(this.rotateDamping, dt));\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.detach();\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._targetPose = new Pose(), this.rotateDamping = 0.98, this.moveDamping = 0.98;\n\t\t}\n}\n\nconst dir = new Vec3();\nconst offset = new Vec3();\nconst angles = new Vec3();\nconst rotation = new Quat();\nclass OrbitController extends InputController {\n\t\tset pitchRange(range) {\n\t\t\t\tthis._targetRootPose.pitchRange.copy(range);\n\t\t\t\tthis._rootPose.copy(this._targetRootPose.rotate(Vec3.ZERO));\n\t\t}\n\t\tget pitchRange() {\n\t\t\t\treturn this._targetRootPose.pitchRange;\n\t\t}\n\t\tset yawRange(range) {\n\t\t\t\tthis._targetRootPose.yawRange.copy(range);\n\t\t\t\tthis._rootPose.copy(this._targetRootPose.rotate(Vec3.ZERO));\n\t\t}\n\t\tget yawRange() {\n\t\t\t\treturn this._targetRootPose.yawRange;\n\t\t}\n\t\tset zoomRange(range) {\n\t\t\t\tthis._targetChildPose.zRange.copy(range);\n\t\t\t\tthis._childPose.copy(this._targetChildPose.move(Vec3.ZERO));\n\t\t}\n\t\tget zoomRange() {\n\t\t\t\treturn this._targetRootPose.zRange;\n\t\t}\n\t\tattach(pose, smooth = true) {\n\t\t\t\tthis._targetRootPose.set(pose.getFocus(dir), pose.angles, 0);\n\t\t\t\tthis._targetChildPose.position.set(0, 0, pose.distance);\n\t\t\t\tif (!smooth) {\n\t\t\t\t\t\tthis._rootPose.copy(this._targetRootPose);\n\t\t\t\t\t\tthis._childPose.copy(this._targetChildPose);\n\t\t\t\t}\n\t\t}\n\t\tdetach() {\n\t\t\t\tthis._targetRootPose.copy(this._rootPose);\n\t\t\t\tthis._targetChildPose.copy(this._childPose);\n\t\t}\n\t\tupdate(frame, dt) {\n\t\t\t\tconst { move, rotate } = frame.read();\n\t\t\t\toffset.set(move[0], move[1], 0);\n\t\t\t\trotation.setFromEulerAngles(this._rootPose.angles).transformVector(offset, offset);\n\t\t\t\tthis._targetRootPose.move(offset);\n\t\t\t\tconst { z: dist } = this._targetChildPose.position;\n\t\t\t\tthis._targetChildPose.move(offset.set(0, 0, dist * (1 + move[2]) - dist));\n\t\t\t\tthis._targetRootPose.rotate(angles.set(-rotate[1], -rotate[0], 0));\n\t\t\t\tthis._rootPose.lerp(this._rootPose, this._targetRootPose, damp(this.moveDamping, dt), damp(this.rotateDamping, dt), 1);\n\t\t\t\tthis._childPose.lerp(this._childPose, this._targetChildPose, damp(this.zoomDamping, dt), 1, 1);\n\t\t\t\trotation.setFromEulerAngles(this._rootPose.angles).transformVector(this._childPose.position, offset).add(this._rootPose.position);\n\t\t\t\treturn this._pose.set(offset, this._rootPose.angles, this._childPose.position.z);\n\t\t}\n\t\tdestroy() {\n\t\t\t\tthis.detach();\n\t\t}\n\t\tconstructor(...args){\n\t\t\t\tsuper(...args), this._targetRootPose = new Pose(), this._rootPose = new Pose(), this._targetChildPose = new Pose(), this._childPose = new Pose(), this.rotateDamping = 0.98, this.moveDamping = 0.98, this.zoomDamping = 0.98;\n\t\t}\n}\n\n/**\n * Base class for all PlayCanvas Web Components that initialize asynchronously.\n */\nclass AsyncElement extends HTMLElement {\n    /** @ignore */\n    constructor() {\n        super();\n        this._readyPromise = new Promise((resolve) => {\n            this._readyResolve = resolve;\n        });\n    }\n    get closestApp() {\n        var _a;\n        return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-app');\n    }\n    get closestEntity() {\n        var _a;\n        return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-entity');\n    }\n    /**\n     * Called when the element is fully initialized and ready.\n     * Subclasses should call this when they're ready.\n     */\n    _onReady() {\n        this._readyResolve();\n        this.dispatchEvent(new CustomEvent('ready'));\n    }\n    /**\n     * Returns a promise that resolves with this element when it's ready.\n     * @returns A promise that resolves with this element when it's ready.\n     */\n    ready() {\n        return this._readyPromise.then(() => this);\n    }\n}\n\n/**\n * The ModuleElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`} elements.\n * The ModuleElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n */\nclass ModuleElement extends HTMLElement {\n    /** @ignore */\n    constructor() {\n        super();\n        this.loadPromise = this.loadModule();\n    }\n    async loadModule() {\n        const name = this.getAttribute('name');\n        const glueUrl = this.getAttribute('glue');\n        const wasmUrl = this.getAttribute('wasm');\n        const fallbackUrl = this.getAttribute('fallback');\n        const config = { glueUrl, wasmUrl, fallbackUrl };\n        if (name === 'Basis') {\n            basisInitialize(config);\n        }\n        else {\n            WasmModule.setConfig(name, config);\n            await new Promise((resolve) => {\n                WasmModule.getInstance(name, () => resolve());\n            });\n        }\n    }\n    getLoadPromise() {\n        return this.loadPromise;\n    }\n}\ncustomElements.define('pc-module', ModuleElement);\n\n/**\n * The AppElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.\n * The AppElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n */\nclass AppElement extends AsyncElement {\n    /**\n     * Creates a new AppElement instance.\n     *\n     * @ignore\n     */\n    constructor() {\n        super();\n        /**\n         * The canvas element.\n         */\n        this._canvas = null;\n        this._alpha = true;\n        this._backend = 'webgl2';\n        this._antialias = true;\n        this._depth = true;\n        this._stencil = true;\n        this._highResolution = true;\n        this._hierarchyReady = false;\n        this._picker = null;\n        this._hasPointerListeners = {\n            pointerenter: false,\n            pointerleave: false,\n            pointerdown: false,\n            pointerup: false,\n            pointermove: false\n        };\n        this._hoveredEntity = null;\n        this._pointerHandlers = {\n            pointermove: null,\n            pointerdown: null,\n            pointerup: null\n        };\n        /**\n         * The PlayCanvas application instance.\n         */\n        this.app = null;\n        // Bind methods to maintain 'this' context\n        this._onWindowResize = this._onWindowResize.bind(this);\n    }\n    async connectedCallback() {\n        // Get all pc-module elements that are direct children of the pc-app element\n        const moduleElements = this.querySelectorAll(':scope > pc-module');\n        // Wait for all modules to load\n        await Promise.all(Array.from(moduleElements).map(module => module.getLoadPromise()));\n        // Create and append the canvas to the element\n        this._canvas = document.createElement('canvas');\n        this.appendChild(this._canvas);\n        // Configure device types based on backend selection\n        const backendToDeviceTypes = {\n            webgpu: ['webgpu', 'webgl2'], // fallback to webgl2 if webgpu not available\n            webgl2: ['webgl2'],\n            null: ['null']\n        };\n        const deviceTypes = backendToDeviceTypes[this._backend] || [];\n        const device = await createGraphicsDevice(this._canvas, {\n            // @ts-ignore - alpha needs to be documented\n            alpha: this._alpha,\n            antialias: this._antialias,\n            depth: this._depth,\n            deviceTypes: deviceTypes,\n            stencil: this._stencil\n        });\n        device.maxPixelRatio = this._highResolution ? window.devicePixelRatio : 1;\n        const createOptions = new AppOptions();\n        createOptions.graphicsDevice = device;\n        createOptions.keyboard = new Keyboard(window);\n        createOptions.mouse = new Mouse(this._canvas);\n        createOptions.componentSystems = [\n            AnimComponentSystem,\n            AnimationComponentSystem,\n            AudioListenerComponentSystem,\n            ButtonComponentSystem,\n            CameraComponentSystem,\n            CollisionComponentSystem,\n            ElementComponentSystem,\n            GSplatComponentSystem,\n            JointComponentSystem,\n            LayoutChildComponentSystem,\n            LayoutGroupComponentSystem,\n            LightComponentSystem,\n            ModelComponentSystem,\n            ParticleSystemComponentSystem,\n            RenderComponentSystem,\n            RigidBodyComponentSystem,\n            ScreenComponentSystem,\n            ScriptComponentSystem,\n            ScrollbarComponentSystem,\n            ScrollViewComponentSystem,\n            SoundComponentSystem,\n            SpriteComponentSystem,\n            ZoneComponentSystem\n        ];\n        createOptions.resourceHandlers = [\n            AnimClipHandler,\n            AnimationHandler,\n            AnimStateGraphHandler,\n            AudioHandler,\n            BinaryHandler,\n            CssHandler,\n            ContainerHandler,\n            CubemapHandler,\n            FolderHandler,\n            FontHandler,\n            GSplatHandler,\n            HierarchyHandler,\n            HtmlHandler,\n            JsonHandler,\n            MaterialHandler,\n            ModelHandler,\n            RenderHandler,\n            ScriptHandler,\n            SceneHandler,\n            ShaderHandler,\n            SpriteHandler,\n            TemplateHandler,\n            TextHandler,\n            TextureAtlasHandler,\n            TextureHandler\n        ];\n        createOptions.soundManager = new SoundManager();\n        createOptions.lightmapper = Lightmapper;\n        createOptions.batchManager = BatchManager;\n        createOptions.xr = XrManager;\n        this.app = new AppBase(this._canvas);\n        this.app.init(createOptions);\n        this.app.setCanvasFillMode(FILLMODE_FILL_WINDOW);\n        this.app.setCanvasResolution(RESOLUTION_AUTO);\n        this._pickerCreate();\n        // Get all pc-asset elements that are direct children of the pc-app element\n        const assetElements = this.querySelectorAll(':scope > pc-asset');\n        Array.from(assetElements).forEach((assetElement) => {\n            assetElement.createAsset();\n            const asset = assetElement.asset;\n            if (asset) {\n                this.app.assets.add(asset);\n            }\n        });\n        // Get all pc-material elements that are direct children of the pc-app element\n        const materialElements = this.querySelectorAll(':scope > pc-material');\n        Array.from(materialElements).forEach((materialElement) => {\n            materialElement.createMaterial();\n        });\n        // Create all entities\n        const entityElements = this.querySelectorAll('pc-entity');\n        Array.from(entityElements).forEach((entityElement) => {\n            entityElement.createEntity(this.app);\n        });\n        // Build hierarchy\n        entityElements.forEach((entityElement) => {\n            entityElement.buildHierarchy(this.app);\n        });\n        this._hierarchyReady = true;\n        // Load assets before starting the application\n        this.app.preload(() => {\n            // Start the application\n            this.app.start();\n            // Handle window resize to keep the canvas responsive\n            window.addEventListener('resize', this._onWindowResize);\n            this._onReady();\n        });\n    }\n    disconnectedCallback() {\n        this._pickerDestroy();\n        // Clean up the application\n        if (this.app) {\n            this.app.destroy();\n            this.app = null;\n        }\n        // Remove event listeners\n        window.removeEventListener('resize', this._onWindowResize);\n        // Remove the canvas\n        if (this._canvas && this.contains(this._canvas)) {\n            this.removeChild(this._canvas);\n            this._canvas = null;\n        }\n    }\n    _onWindowResize() {\n        if (this.app) {\n            this.app.resizeCanvas();\n        }\n    }\n    _pickerCreate() {\n        const { width, height } = this.app.graphicsDevice;\n        this._picker = new Picker$1(this.app, width, height);\n        // Create bound handlers but don't attach them yet\n        this._pointerHandlers.pointermove = this._onPointerMove.bind(this);\n        this._pointerHandlers.pointerdown = this._onPointerDown.bind(this);\n        this._pointerHandlers.pointerup = this._onPointerUp.bind(this);\n        // Listen for pointer listeners being added/removed\n        ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'].forEach((type) => {\n            this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));\n            this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type));\n        });\n    }\n    _pickerDestroy() {\n        if (this._canvas) {\n            Object.entries(this._pointerHandlers).forEach(([type, handler]) => {\n                if (handler) {\n                    this._canvas.removeEventListener(type, handler);\n                }\n            });\n        }\n        this._picker = null;\n        this._pointerHandlers = {\n            pointermove: null,\n            pointerdown: null,\n            pointerup: null\n        };\n    }\n    // New helper to convert CSS coordinates to canvas (picker) coordinates\n    _getPickerCoordinates(event) {\n        // Get the canvas' bounding rectangle in CSS pixels.\n        const canvasRect = this._canvas.getBoundingClientRect();\n        // Compute scale factors based on canvas actual resolution vs. its CSS display size.\n        const scaleX = this._canvas.width / canvasRect.width;\n        const scaleY = this._canvas.height / canvasRect.height;\n        // Convert the client coordinates accordingly.\n        const x = (event.clientX - canvasRect.left) * scaleX;\n        const y = (event.clientY - canvasRect.top) * scaleY;\n        return { x, y };\n    }\n    _onPointerMove(event) {\n        if (!this._picker || !this.app)\n            return;\n        const camera = this.app.root.findComponent('camera');\n        if (!camera)\n            return;\n        // Use the helper to convert event coordinates into canvas/picker coordinates.\n        const { x, y } = this._getPickerCoordinates(event);\n        this._picker.prepare(camera, this.app.scene);\n        const selection = this._picker.getSelection(x, y);\n        // Get the currently hovered entity by walking up the hierarchy\n        let newHoverEntity = null;\n        if (selection.length > 0) {\n            let currentNode = selection[0].node;\n            while (currentNode !== null) {\n                const entityElement = this.querySelector(`pc-entity[name=\"${currentNode.name}\"]`);\n                if (entityElement) {\n                    newHoverEntity = entityElement;\n                    break;\n                }\n                currentNode = currentNode.parent;\n            }\n        }\n        // Handle enter/leave events\n        if (this._hoveredEntity !== newHoverEntity) {\n            if (this._hoveredEntity && this._hoveredEntity.hasListeners('pointerleave')) {\n                this._hoveredEntity.dispatchEvent(new PointerEvent('pointerleave', event));\n            }\n            if (newHoverEntity && newHoverEntity.hasListeners('pointerenter')) {\n                newHoverEntity.dispatchEvent(new PointerEvent('pointerenter', event));\n            }\n        }\n        // Update hover state\n        this._hoveredEntity = newHoverEntity;\n        // Handle pointermove event\n        if (newHoverEntity && newHoverEntity.hasListeners('pointermove')) {\n            newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event));\n        }\n    }\n    _onPointerDown(event) {\n        if (!this._picker || !this.app)\n            return;\n        const camera = this.app.root.findComponent('camera');\n        if (!camera)\n            return;\n        // Convert the event's pointer coordinates\n        const { x, y } = this._getPickerCoordinates(event);\n        this._picker.prepare(camera, this.app.scene);\n        const selection = this._picker.getSelection(x, y);\n        if (selection.length > 0) {\n            let currentNode = selection[0].node;\n            while (currentNode !== null) {\n                const entityElement = this.querySelector(`pc-entity[name=\"${currentNode.name}\"]`);\n                if (entityElement && entityElement.hasListeners('pointerdown')) {\n                    entityElement.dispatchEvent(new PointerEvent('pointerdown', event));\n                    break;\n                }\n                currentNode = currentNode.parent;\n            }\n        }\n    }\n    _onPointerUp(event) {\n        if (!this._picker || !this.app)\n            return;\n        const camera = this.app.root.findComponent('camera');\n        if (!camera)\n            return;\n        // Convert CSS coordinates to picker coordinates\n        const { x, y } = this._getPickerCoordinates(event);\n        this._picker.prepare(camera, this.app.scene);\n        const selection = this._picker.getSelection(x, y);\n        if (selection.length > 0) {\n            const entityElement = this.querySelector(`pc-entity[name=\"${selection[0].node.name}\"]`);\n            if (entityElement && entityElement.hasListeners('pointerup')) {\n                entityElement.dispatchEvent(new PointerEvent('pointerup', event));\n            }\n        }\n    }\n    _onPointerListenerAdded(type) {\n        if (!this._hasPointerListeners[type] && this._canvas) {\n            this._hasPointerListeners[type] = true;\n            // For enter/leave events, we need the move handler\n            const handler = (type === 'pointerenter' || type === 'pointerleave') ?\n                this._pointerHandlers.pointermove :\n                this._pointerHandlers[type];\n            if (handler) {\n                this._canvas.addEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);\n            }\n        }\n    }\n    _onPointerListenerRemoved(type) {\n        const hasListeners = Array.from(this.querySelectorAll('pc-entity'))\n            .some(entity => entity.hasListeners(type));\n        if (!hasListeners && this._canvas) {\n            this._hasPointerListeners[type] = false;\n            const handler = (type === 'pointerenter' || type === 'pointerleave') ?\n                this._pointerHandlers.pointermove :\n                this._pointerHandlers[type];\n            if (handler) {\n                this._canvas.removeEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);\n            }\n        }\n    }\n    /**\n     * Sets the alpha flag.\n     * @param value - The alpha flag.\n     */\n    set alpha(value) {\n        this._alpha = value;\n    }\n    /**\n     * Gets the alpha flag.\n     * @returns The alpha flag.\n     */\n    get alpha() {\n        return this._alpha;\n    }\n    /**\n     * Sets the antialias flag.\n     * @param value - The antialias flag.\n     */\n    set antialias(value) {\n        this._antialias = value;\n    }\n    /**\n     * Gets the antialias flag.\n     * @returns The antialias flag.\n     */\n    get antialias() {\n        return this._antialias;\n    }\n    /**\n     * Sets the graphics backend.\n     * @param value - The graphics backend ('webgpu', 'webgl2', or 'null').\n     */\n    set backend(value) {\n        this._backend = value;\n    }\n    /**\n     * Gets the graphics backend.\n     * @returns The graphics backend.\n     */\n    get backend() {\n        return this._backend;\n    }\n    /**\n     * Sets the depth flag.\n     * @param value - The depth flag.\n     */\n    set depth(value) {\n        this._depth = value;\n    }\n    /**\n     * Gets the depth flag.\n     * @returns The depth flag.\n     */\n    get depth() {\n        return this._depth;\n    }\n    /**\n     * Gets the hierarchy ready flag.\n     * @returns The hierarchy ready flag.\n     * @ignore\n     */\n    get hierarchyReady() {\n        return this._hierarchyReady;\n    }\n    /**\n     * Sets the high resolution flag. When true, the application will render at the device's\n     * physical resolution. When false, the application will render at CSS resolution.\n     * @param value - The high resolution flag.\n     */\n    set highResolution(value) {\n        this._highResolution = value;\n        if (this.app) {\n            this.app.graphicsDevice.maxPixelRatio = value ? window.devicePixelRatio : 1;\n        }\n    }\n    /**\n     * Gets the high resolution flag.\n     * @returns The high resolution flag.\n     */\n    get highResolution() {\n        return this._highResolution;\n    }\n    /**\n     * Sets the stencil flag.\n     * @param value - The stencil flag.\n     */\n    set stencil(value) {\n        this._stencil = value;\n    }\n    /**\n     * Gets the stencil flag.\n     * @returns The stencil flag.\n     */\n    get stencil() {\n        return this._stencil;\n    }\n    static get observedAttributes() {\n        return ['alpha', 'antialias', 'backend', 'depth', 'stencil', 'high-resolution'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'alpha':\n                this.alpha = newValue !== 'false';\n                break;\n            case 'antialias':\n                this.antialias = newValue !== 'false';\n                break;\n            case 'backend':\n                if (newValue === 'webgpu' || newValue === 'webgl2' || newValue === 'null') {\n                    this.backend = newValue;\n                }\n                break;\n            case 'depth':\n                this.depth = newValue !== 'false';\n                break;\n            case 'high-resolution':\n                this.highResolution = newValue !== 'false';\n                break;\n            case 'stencil':\n                this.stencil = newValue !== 'false';\n                break;\n        }\n    }\n}\ncustomElements.define('pc-app', AppElement);\n\nconst CSS_COLORS = {\n    aliceblue: '#f0f8ff',\n    antiquewhite: '#faebd7',\n    aqua: '#00ffff',\n    aquamarine: '#7fffd4',\n    azure: '#f0ffff',\n    beige: '#f5f5dc',\n    bisque: '#ffe4c4',\n    black: '#000000',\n    blanchedalmond: '#ffebcd',\n    blue: '#0000ff',\n    blueviolet: '#8a2be2',\n    brown: '#a52a2a',\n    burlywood: '#deb887',\n    cadetblue: '#5f9ea0',\n    chartreuse: '#7fff00',\n    chocolate: '#d2691e',\n    coral: '#ff7f50',\n    cornflowerblue: '#6495ed',\n    cornsilk: '#fff8dc',\n    crimson: '#dc143c',\n    cyan: '#00ffff',\n    darkblue: '#00008b',\n    darkcyan: '#008b8b',\n    darkgoldenrod: '#b8860b',\n    darkgray: '#a9a9a9',\n    darkgreen: '#006400',\n    darkgrey: '#a9a9a9',\n    darkkhaki: '#bdb76b',\n    darkmagenta: '#8b008b',\n    darkolivegreen: '#556b2f',\n    darkorange: '#ff8c00',\n    darkorchid: '#9932cc',\n    darkred: '#8b0000',\n    darksalmon: '#e9967a',\n    darkseagreen: '#8fbc8f',\n    darkslateblue: '#483d8b',\n    darkslategray: '#2f4f4f',\n    darkslategrey: '#2f4f4f',\n    darkturquoise: '#00ced1',\n    darkviolet: '#9400d3',\n    deeppink: '#ff1493',\n    deepskyblue: '#00bfff',\n    dimgray: '#696969',\n    dimgrey: '#696969',\n    dodgerblue: '#1e90ff',\n    firebrick: '#b22222',\n    floralwhite: '#fffaf0',\n    forestgreen: '#228b22',\n    fuchsia: '#ff00ff',\n    gainsboro: '#dcdcdc',\n    ghostwhite: '#f8f8ff',\n    gold: '#ffd700',\n    goldenrod: '#daa520',\n    gray: '#808080',\n    green: '#008000',\n    greenyellow: '#adff2f',\n    grey: '#808080',\n    honeydew: '#f0fff0',\n    hotpink: '#ff69b4',\n    indianred: '#cd5c5c',\n    indigo: '#4b0082',\n    ivory: '#fffff0',\n    khaki: '#f0e68c',\n    lavender: '#e6e6fa',\n    lavenderblush: '#fff0f5',\n    lawngreen: '#7cfc00',\n    lemonchiffon: '#fffacd',\n    lightblue: '#add8e6',\n    lightcoral: '#f08080',\n    lightcyan: '#e0ffff',\n    lightgoldenrodyellow: '#fafad2',\n    lightgray: '#d3d3d3',\n    lightgreen: '#90ee90',\n    lightgrey: '#d3d3d3',\n    lightpink: '#ffb6c1',\n    lightsalmon: '#ffa07a',\n    lightseagreen: '#20b2aa',\n    lightskyblue: '#87cefa',\n    lightslategray: '#778899',\n    lightslategrey: '#778899',\n    lightsteelblue: '#b0c4de',\n    lightyellow: '#ffffe0',\n    lime: '#00ff00',\n    limegreen: '#32cd32',\n    linen: '#faf0e6',\n    magenta: '#ff00ff',\n    maroon: '#800000',\n    mediumaquamarine: '#66cdaa',\n    mediumblue: '#0000cd',\n    mediumorchid: '#ba55d3',\n    mediumpurple: '#9370db',\n    mediumseagreen: '#3cb371',\n    mediumslateblue: '#7b68ee',\n    mediumspringgreen: '#00fa9a',\n    mediumturquoise: '#48d1cc',\n    mediumvioletred: '#c71585',\n    midnightblue: '#191970',\n    mintcream: '#f5fffa',\n    mistyrose: '#ffe4e1',\n    moccasin: '#ffe4b5',\n    navajowhite: '#ffdead',\n    navy: '#000080',\n    oldlace: '#fdf5e6',\n    olive: '#808000',\n    olivedrab: '#6b8e23',\n    orange: '#ffa500',\n    orangered: '#ff4500',\n    orchid: '#da70d6',\n    palegoldenrod: '#eee8aa',\n    palegreen: '#98fb98',\n    paleturquoise: '#afeeee',\n    palevioletred: '#db7093',\n    papayawhip: '#ffefd5',\n    peachpuff: '#ffdab9',\n    peru: '#cd853f',\n    pink: '#ffc0cb',\n    plum: '#dda0dd',\n    powderblue: '#b0e0e6',\n    purple: '#800080',\n    rebeccapurple: '#663399',\n    red: '#ff0000',\n    rosybrown: '#bc8f8f',\n    royalblue: '#4169e1',\n    saddlebrown: '#8b4513',\n    salmon: '#fa8072',\n    sandybrown: '#f4a460',\n    seagreen: '#2e8b57',\n    seashell: '#fff5ee',\n    sienna: '#a0522d',\n    silver: '#c0c0c0',\n    skyblue: '#87ceeb',\n    slateblue: '#6a5acd',\n    slategray: '#708090',\n    slategrey: '#708090',\n    snow: '#fffafa',\n    springgreen: '#00ff7f',\n    steelblue: '#4682b4',\n    tan: '#d2b48c',\n    teal: '#008080',\n    thistle: '#d8bfd8',\n    tomato: '#ff6347',\n    turquoise: '#40e0d0',\n    violet: '#ee82ee',\n    wheat: '#f5deb3',\n    white: '#ffffff',\n    whitesmoke: '#f5f5f5',\n    yellow: '#ffff00',\n    yellowgreen: '#9acd32'\n};\n\n/**\n * Parse a color string into a Color object. String can be in the format of '#rgb', '#rgba',\n * '#rrggbb', '#rrggbbaa', or a string of 3 or 4 comma-delimited numbers.\n *\n * @param value - The color string to parse.\n * @returns The parsed Color object.\n */\nconst parseColor = (value) => {\n    // Check if it's a CSS color name first\n    const hexColor = CSS_COLORS[value.toLowerCase()];\n    if (hexColor) {\n        return new Color().fromString(hexColor);\n    }\n    if (value.startsWith('#')) {\n        return new Color().fromString(value);\n    }\n    const components = value.split(' ').map(Number);\n    return new Color(components);\n};\n/**\n * Parse an Euler angles string into a Quat object. String can be in the format of 'x,y,z'.\n *\n * @param value - The Euler angles string to parse.\n * @returns The parsed Quat object.\n */\nconst parseQuat = (value) => {\n    const [x, y, z] = value.split(' ').map(Number);\n    const q = new Quat();\n    q.setFromEulerAngles(x, y, z);\n    return q;\n};\n/**\n * Parse a Vec2 string into a Vec2 object. String can be in the format of 'x,y'.\n *\n * @param value - The Vec2 string to parse.\n * @returns The parsed Vec2 object.\n */\nconst parseVec2 = (value) => {\n    const components = value.split(' ').map(Number);\n    return new Vec2(components);\n};\n/**\n * Parse a Vec3 string into a Vec3 object. String can be in the format of 'x,y,z'.\n *\n * @param value - The Vec3 string to parse.\n * @returns The parsed Vec3 object.\n */\nconst parseVec3 = (value) => {\n    const components = value.split(' ').map(Number);\n    return new Vec3(components);\n};\n/**\n * Parse a Vec4 string into a Vec4 object. String can be in the format of 'x,y,z,w'.\n *\n * @param value - The Vec4 string to parse.\n * @returns The parsed Vec4 object.\n */\nconst parseVec4 = (value) => {\n    const components = value.split(' ').map(Number);\n    return new Vec4(components);\n};\n\n/**\n * The EntityElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-entity/ | `<pc-entity>`} elements.\n * The EntityElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n */\nclass EntityElement extends AsyncElement {\n    constructor() {\n        super(...arguments);\n        /**\n         * Whether the entity is enabled.\n         */\n        this._enabled = true;\n        /**\n         * The name of the entity.\n         */\n        this._name = 'Untitled';\n        /**\n         * The position of the entity.\n         */\n        this._position = new Vec3();\n        /**\n         * The rotation of the entity.\n         */\n        this._rotation = new Vec3();\n        /**\n         * The scale of the entity.\n         */\n        this._scale = new Vec3(1, 1, 1);\n        /**\n         * The tags of the entity.\n         */\n        this._tags = [];\n        /**\n         * The pointer event listeners for the entity.\n         */\n        this._listeners = {};\n        /**\n         * The PlayCanvas entity instance.\n         */\n        this.entity = null;\n    }\n    createEntity(app) {\n        // Create a new entity\n        this.entity = new Entity(this.getAttribute('name') || this._name, app);\n        const enabled = this.getAttribute('enabled');\n        if (enabled) {\n            this.entity.enabled = enabled !== 'false';\n        }\n        const position = this.getAttribute('position');\n        if (position) {\n            this.entity.setLocalPosition(parseVec3(position));\n        }\n        const rotation = this.getAttribute('rotation');\n        if (rotation) {\n            this.entity.setLocalEulerAngles(parseVec3(rotation));\n        }\n        const scale = this.getAttribute('scale');\n        if (scale) {\n            this.entity.setLocalScale(parseVec3(scale));\n        }\n        const tags = this.getAttribute('tags');\n        if (tags) {\n            this.entity.tags.add(tags.split(',').map(tag => tag.trim()));\n        }\n        // Handle pointer events\n        const pointerEvents = [\n            'onpointerenter',\n            'onpointerleave',\n            'onpointerdown',\n            'onpointerup',\n            'onpointermove'\n        ];\n        pointerEvents.forEach((eventName) => {\n            const handler = this.getAttribute(eventName);\n            if (handler) {\n                const eventType = eventName.substring(2); // remove 'on' prefix\n                const eventHandler = (event) => {\n                    try {\n                        /* eslint-disable-next-line no-new-func */\n                        new Function('event', handler).call(this, event);\n                    }\n                    catch (e) {\n                        console.error('Error in event handler:', e);\n                    }\n                };\n                this.addEventListener(eventType, eventHandler);\n            }\n        });\n    }\n    buildHierarchy(app) {\n        if (!this.entity)\n            return;\n        const closestEntity = this.closestEntity;\n        if (closestEntity === null || closestEntity === void 0 ? void 0 : closestEntity.entity) {\n            closestEntity.entity.addChild(this.entity);\n        }\n        else {\n            app.root.addChild(this.entity);\n        }\n        this._onReady();\n    }\n    connectedCallback() {\n        // Wait for app to be ready\n        const closestApp = this.closestApp;\n        if (!closestApp)\n            return;\n        // If app is already running, create entity immediately\n        if (closestApp.hierarchyReady) {\n            const app = closestApp.app;\n            this.createEntity(app);\n            this.buildHierarchy(app);\n            // Handle any child entities that might exist\n            const childEntities = this.querySelectorAll('pc-entity');\n            childEntities.forEach((child) => {\n                child.createEntity(app);\n            });\n            childEntities.forEach((child) => {\n                child.buildHierarchy(app);\n            });\n        }\n    }\n    disconnectedCallback() {\n        if (this.entity) {\n            // Notify all children that their entities are about to become invalid\n            const children = this.querySelectorAll('pc-entity');\n            children.forEach((child) => {\n                child.entity = null;\n            });\n            // Destroy the entity\n            this.entity.destroy();\n            this.entity = null;\n        }\n    }\n    /**\n     * Sets the enabled state of the entity.\n     * @param value - Whether the entity is enabled.\n     */\n    set enabled(value) {\n        this._enabled = value;\n        if (this.entity) {\n            this.entity.enabled = value;\n        }\n    }\n    /**\n     * Gets the enabled state of the entity.\n     * @returns Whether the entity is enabled.\n     */\n    get enabled() {\n        return this._enabled;\n    }\n    /**\n     * Sets the name of the entity.\n     * @param value - The name of the entity.\n     */\n    set name(value) {\n        this._name = value;\n        if (this.entity) {\n            this.entity.name = value;\n        }\n    }\n    /**\n     * Gets the name of the entity.\n     * @returns The name of the entity.\n     */\n    get name() {\n        return this._name;\n    }\n    /**\n     * Sets the position of the entity.\n     * @param value - The position of the entity.\n     */\n    set position(value) {\n        this._position = value;\n        if (this.entity) {\n            this.entity.setLocalPosition(this._position);\n        }\n    }\n    /**\n     * Gets the position of the entity.\n     * @returns The position of the entity.\n     */\n    get position() {\n        return this._position;\n    }\n    /**\n     * Sets the rotation of the entity.\n     * @param value - The rotation of the entity.\n     */\n    set rotation(value) {\n        this._rotation = value;\n        if (this.entity) {\n            this.entity.setLocalEulerAngles(this._rotation);\n        }\n    }\n    /**\n     * Gets the rotation of the entity.\n     * @returns The rotation of the entity.\n     */\n    get rotation() {\n        return this._rotation;\n    }\n    /**\n     * Sets the scale of the entity.\n     * @param value - The scale of the entity.\n     */\n    set scale(value) {\n        this._scale = value;\n        if (this.entity) {\n            this.entity.setLocalScale(this._scale);\n        }\n    }\n    /**\n     * Gets the scale of the entity.\n     * @returns The scale of the entity.\n     */\n    get scale() {\n        return this._scale;\n    }\n    /**\n     * Sets the tags of the entity.\n     * @param value - The tags of the entity.\n     */\n    set tags(value) {\n        this._tags = value;\n        if (this.entity) {\n            this.entity.tags.clear();\n            this.entity.tags.add(this._tags);\n        }\n    }\n    /**\n     * Gets the tags of the entity.\n     * @returns The tags of the entity.\n     */\n    get tags() {\n        return this._tags;\n    }\n    static get observedAttributes() {\n        return [\n            'enabled',\n            'name',\n            'position',\n            'rotation',\n            'scale',\n            'tags',\n            'onpointerenter',\n            'onpointerleave',\n            'onpointerdown',\n            'onpointerup',\n            'onpointermove'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'enabled':\n                this.enabled = newValue !== 'false';\n                break;\n            case 'name':\n                this.name = newValue;\n                break;\n            case 'position':\n                this.position = parseVec3(newValue);\n                break;\n            case 'rotation':\n                this.rotation = parseVec3(newValue);\n                break;\n            case 'scale':\n                this.scale = parseVec3(newValue);\n                break;\n            case 'tags':\n                this.tags = newValue.split(',').map(tag => tag.trim());\n                break;\n            case 'onpointerenter':\n            case 'onpointerleave':\n            case 'onpointerdown':\n            case 'onpointerup':\n            case 'onpointermove':\n                if (newValue) {\n                    const eventName = name.substring(2);\n                    // Use Function.prototype.bind to avoid new Function\n                    const handler = (event) => {\n                        try {\n                            const handlerStr = this.getAttribute(eventName) || '';\n                            /* eslint-disable-next-line no-new-func */\n                            new Function('event', handlerStr).call(this, event);\n                        }\n                        catch (e) {\n                            console.error('Error in event handler:', e);\n                        }\n                    };\n                    this.addEventListener(eventName, handler);\n                }\n                break;\n        }\n    }\n    addEventListener(type, listener, options) {\n        if (!this._listeners[type]) {\n            this._listeners[type] = [];\n        }\n        this._listeners[type].push(listener);\n        super.addEventListener(type, listener, options);\n        if (type.startsWith('pointer')) {\n            this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));\n        }\n    }\n    removeEventListener(type, listener, options) {\n        if (this._listeners[type]) {\n            this._listeners[type] = this._listeners[type].filter(l => l !== listener);\n        }\n        super.removeEventListener(type, listener, options);\n        if (type.startsWith('pointer')) {\n            this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));\n        }\n    }\n    hasListeners(type) {\n        var _a;\n        return Boolean((_a = this._listeners[type]) === null || _a === void 0 ? void 0 : _a.length);\n    }\n}\ncustomElements.define('pc-entity', EntityElement);\n\n// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\r\n// Copyright (C) 2016-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\r\nvar MeshoptDecoder = (function() {\r\n\r\n\t// Built with clang version 14.0.4\r\n\t// Built from meshoptimizer 0.18\r\n\tvar wasm_base = \"b9H79Tebbbe8Fv9Gbb9Gvuuuuueu9Giuuub9Geueu9Giuuueuikqbeeedddillviebeoweuec:q;iekr;leDo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbeY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVbdE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbiL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtblK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949Wbol79IV9Rbrq:P8Yqdbk;3sezu8Jjjjjbcj;eb9Rgv8Kjjjjbc9:hodnadcefal0mbcuhoaiRbbc:Ge9hmbavaialfgrad9Radz1jjjbhwcj;abad9UhoaicefhldnadTmbaoc;WFbGgocjdaocjd6EhDcbhqinaqae9pmeaDaeaq9RaqaDfae6Egkcsfgocl4cifcd4hxdndndndnaoc9WGgmTmbcbhPcehsawcjdfhzalhHinaraH9Rax6midnaraHaxfgl9RcK6mbczhoinawcj;cbfaogifgoc9WfhOdndndndndnaHaic9WfgAco4fRbbaAci4coG4ciGPlbedibkaO9cb83ibaOcwf9cb83ibxikaOalRblalRbbgAco4gCaCciSgCE86bbaocGfalclfaCfgORbbaAcl4ciGgCaCciSgCE86bbaocVfaOaCfgORbbaAcd4ciGgCaCciSgCE86bbaoc7faOaCfgORbbaAciGgAaAciSgAE86bbaoctfaOaAfgARbbalRbegOco4gCaCciSgCE86bbaoc91faAaCfgARbbaOcl4ciGgCaCciSgCE86bbaoc4faAaCfgARbbaOcd4ciGgCaCciSgCE86bbaoc93faAaCfgARbbaOciGgOaOciSgOE86bbaoc94faAaOfgARbbalRbdgOco4gCaCciSgCE86bbaoc95faAaCfgARbbaOcl4ciGgCaCciSgCE86bbaoc96faAaCfgARbbaOcd4ciGgCaCciSgCE86bbaoc97faAaCfgARbbaOciGgOaOciSgOE86bbaoc98faAaOfgORbbalRbiglco4gAaAciSgAE86bbaoc99faOaAfgORbbalcl4ciGgAaAciSgAE86bbaoc9:faOaAfgORbbalcd4ciGgAaAciSgAE86bbaocufaOaAfgoRbbalciGglalciSglE86bbaoalfhlxdkaOalRbwalRbbgAcl4gCaCcsSgCE86bbaocGfalcwfaCfgORbbaAcsGgAaAcsSgAE86bbaocVfaOaAfgORbbalRbegAcl4gCaCcsSgCE86bbaoc7faOaCfgORbbaAcsGgAaAcsSgAE86bbaoctfaOaAfgORbbalRbdgAcl4gCaCcsSgCE86bbaoc91faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc4faOaAfgORbbalRbigAcl4gCaCcsSgCE86bbaoc93faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc94faOaAfgORbbalRblgAcl4gCaCcsSgCE86bbaoc95faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc96faOaAfgORbbalRbvgAcl4gCaCcsSgCE86bbaoc97faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc98faOaAfgORbbalRbogAcl4gCaCcsSgCE86bbaoc99faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc9:faOaAfgORbbalRbrglcl4gAaAcsSgAE86bbaocufaOaAfgoRbbalcsGglalcsSglE86bbaoalfhlxekaOal8Pbb83bbaOcwfalcwf8Pbb83bbalczfhlkdnaiam9pmbaiczfhoaral9RcL0mekkaiam6mialTmidnakTmbawaPfRbbhOcbhoazhiinaiawcj;cbfaofRbbgAce4cbaAceG9R7aOfgO86bbaiadfhiaocefgoak9hmbkkazcefhzaPcefgPad6hsalhHaPad9hmexvkkcbhlasceGmdxikalaxad2fhCdnakTmbcbhHcehsawcjdfhminaral9Rax6mialTmdalaxfhlawaHfRbbhOcbhoamhiinaiawcj;cbfaofRbbgAce4cbaAceG9R7aOfgO86bbaiadfhiaocefgoak9hmbkamcefhmaHcefgHad6hsaHad9hmbkaChlxikcbhocehsinaral9Rax6mdalTmealaxfhlaocefgoad6hsadao9hmbkaChlxdkcbhlasceGTmekc9:hoxikabaqad2fawcjdfakad2z1jjjb8Aawawcjdfakcufad2fadz1jjjb8Aakaqfhqalmbkc9:hoxekcbc99aral9Radcaadca0ESEhokavcj;ebf8Kjjjjbaok;yzeHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecjez:jjjjb8AavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhodnaeTmbcmcsaDceSEhkcbhxcbhmcbhDcbhicbhlindnaoaq9nmbc9:hoxikdndnawRbbgrc;Ve0mbavc;abfalarcl4cu7fcsGcitfgPydlhsaPydbhzdnarcsGgPak9pmbavaiarcu7fcsGcdtfydbaxaPEhraPThPdndnadcd9hmbabaDcetfgHaz87ebaHcdfas87ebaHclfar87ebxekabaDcdtfgHazBdbaHclfasBdbaHcwfarBdbkaxaPfhxavc;abfalcitfgHarBdbaHasBdlavaicdtfarBdbavc;abfalcefcsGglcitfgHazBdbaHarBdlaiaPfhialcefhlxdkdndnaPcsSmbamaPfaPc987fcefhmxekaocefhrao8SbbgPcFeGhHdndnaPcu9mmbarhoxekaocvfhoaHcFbGhHcrhPdninar8SbbgOcFbGaPtaHVhHaOcu9kmearcefhraPcrfgPc8J9hmbxdkkarcefhokaHce4cbaHceG9R7amfhmkdndnadcd9hmbabaDcetfgraz87ebarcdfas87ebarclfam87ebxekabaDcdtfgrazBdbarclfasBdbarcwfamBdbkavc;abfalcitfgramBdbarasBdlavaicdtfamBdbavc;abfalcefcsGglcitfgrazBdbaramBdlaicefhialcefhlxekdnarcpe0mbaxcefgOavaiaqarcsGfRbbgPcl49RcsGcdtfydbaPcz6gHEhravaiaP9RcsGcdtfydbaOaHfgsaPcsGgOEhPaOThOdndnadcd9hmbabaDcetfgzax87ebazcdfar87ebazclfaP87ebxekabaDcdtfgzaxBdbazclfarBdbazcwfaPBdbkavaicdtfaxBdbavc;abfalcitfgzarBdbazaxBdlavaicefgicsGcdtfarBdbavc;abfalcefcsGcitfgzaPBdbazarBdlavaiaHfcsGgicdtfaPBdbavc;abfalcdfcsGglcitfgraxBdbaraPBdlalcefhlaiaOfhiasaOfhxxekaxcbaoRbbgzEgAarc;:eSgrfhsazcsGhCazcl4hXdndnazcs0mbascefhOxekashOavaiaX9RcsGcdtfydbhskdndnaCmbaOcefhxxekaOhxavaiaz9RcsGcdtfydbhOkdndnarTmbaocefhrxekaocdfhrao8SbegHcFeGhPdnaHcu9kmbaocofhAaPcFbGhPcrhodninar8SbbgHcFbGaotaPVhPaHcu9kmearcefhraocrfgoc8J9hmbkaAhrxekarcefhrkaPce4cbaPceG9R7amfgmhAkdndnaXcsSmbarhPxekarcefhPar8SbbgocFeGhHdnaocu9kmbarcvfhsaHcFbGhHcrhodninaP8SbbgrcFbGaotaHVhHarcu9kmeaPcefhPaocrfgoc8J9hmbkashPxekaPcefhPkaHce4cbaHceG9R7amfgmhskdndnaCcsSmbaPhoxekaPcefhoaP8SbbgrcFeGhHdnarcu9kmbaPcvfhOaHcFbGhHcrhrdninao8SbbgPcFbGartaHVhHaPcu9kmeaocefhoarcrfgrc8J9hmbkaOhoxekaocefhokaHce4cbaHceG9R7amfgmhOkdndnadcd9hmbabaDcetfgraA87ebarcdfas87ebarclfaO87ebxekabaDcdtfgraABdbarclfasBdbarcwfaOBdbkavc;abfalcitfgrasBdbaraABdlavaicdtfaABdbavc;abfalcefcsGcitfgraOBdbarasBdlavaicefgicsGcdtfasBdbavc;abfalcdfcsGcitfgraABdbaraOBdlavaiazcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhialcifhlkawcefhwalcsGhlaicsGhiaDcifgDae6mbkkcbc99aoaqSEhokavc;aef8Kjjjjbaok:llevu8Jjjjjbcz9Rhvc9:hodnaecvfal0mbcuhoaiRbbc;:eGc;qe9hmbav9cb83iwaicefhraialfc98fhwdnaeTmbdnadcdSmbcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcdtfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfglBdbaoalBdbaDcefgDae9hmbxdkkcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcetfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfgl87ebaoalBdbaDcefgDae9hmbkkcbc99arawSEhokaok:Lvoeue99dud99eud99dndnadcl9hmbaeTmeindndnabcdfgd8Sbb:Yab8Sbbgi:Ygl:l:tabcefgv8Sbbgo:Ygr:l:tgwJbb;:9cawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai86bbdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad86bbdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad86bbabclfhbaecufgembxdkkaeTmbindndnabclfgd8Ueb:Yab8Uebgi:Ygl:l:tabcdfgv8Uebgo:Ygr:l:tgwJb;:FSawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai87ebdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad87ebdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad87ebabcwfhbaecufgembkkk;siliui99iue99dnaeTmbcbhiabhlindndnJ;Zl81Zalcof8UebgvciV:Y:vgoal8Ueb:YNgrJb;:FSNJbbbZJbbb:;arJbbbb9GEMgw:lJbbb9p9DTmbaw:OhDxekcjjjj94hDkalclf8Uebhqalcdf8UebhkabavcefciGaiVcetfaD87ebdndnaoak:YNgwJb;:FSNJbbbZJbbb:;awJbbbb9GEMgx:lJbbb9p9DTmbax:Ohkxekcjjjj94hkkabavcdfciGaiVcetfak87ebdndnaoaq:YNgoJb;:FSNJbbbZJbbb:;aoJbbbb9GEMgx:lJbbb9p9DTmbax:Ohqxekcjjjj94hqkabavcufciGaiVcetfaq87ebdndnJbbjZararN:tawawN:taoaoN:tgrJbbbbarJbbbb9GE:rJb;:FSNJbbbZMgr:lJbbb9p9DTmbar:Ohqxekcjjjj94hqkabavciGaiVcetfaq87ebalcwfhlaiclfhiaecufgembkkk9mbdnadcd4ae2geTmbinababydbgdcwtcw91:Yadce91cjjj;8ifcjjj98G::NUdbabclfhbaecufgembkkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;LeeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiclfaeclfydbBdbaicwfaecwfydbBdbaicxfaecxfydbBdbaiczfhiaeczfheadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk;aeedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdbaicxfalBdbaicwfalBdbaiclfalBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabkkkebcjwklz9Kbb\";\r\n\tvar wasm_simd = \"b9H79TebbbeKl9Gbb9Gvuuuuueu9Giuuub9Geueuikqbbebeedddilve9Weeeviebeoweuec:q;Aekr;leDo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbdY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVblE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtboK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbrL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949Wbwl79IV9RbDq;t9tqlbzik9:evu8Jjjjjbcz9Rhbcbheincbhdcbhiinabcwfadfaicjuaead4ceGglE86bbaialfhiadcefgdcw9hmbkaec:q:yjjbfai86bbaecitc:q1jjbfab8Piw83ibaecefgecjd9hmbkk;h8JlHud97euo978Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnadcefal0mbcuhoaiRbbc:Ge9hmbavaialfgrad9Rad;8qbbcj;abad9UhoaicefhldnadTmbaoc;WFbGgocjdaocjd6EhwcbhDinaDae9pmeawaeaD9RaDawfae6Egqcsfgoc9WGgkci2hxakcethmaocl4cifcd4hPabaDad2fhscbhzdnincehHalhOcbhAdninaraO9RaP6miavcj;cbfaAak2fhCaOaPfhlcbhidnakc;ab6mbaral9Rc;Gb6mbcbhoinaCaofhidndndndndnaOaoco4fRbbgXciGPlbedibkaipxbbbbbbbbbbbbbbbbpklbxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklbalczfhlkdndndndndnaXcd4ciGPlbedibkaipxbbbbbbbbbbbbbbbbpklzxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklzalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklzalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklzalczfhlkdndndndndnaXcl4ciGPlbedibkaipxbbbbbbbbbbbbbbbbpklaxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklaalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklaalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklaalczfhlkdndndndndnaXco4Plbedibkaipxbbbbbbbbbbbbbbbbpkl8WxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibaXc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spkl8WalclfaYpQbfaXc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibaXc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spkl8WalcwfaYpQbfaXc:q:yjjbfRbbfhlxekaialpbbbpkl8Walczfhlkaoc;abfhiaocjefak0meaihoaral9Rc;Fb0mbkkdndnaiak9pmbaici4hoinaral9RcK6mdaCaifhXdndndndndnaOaico4fRbbaocoG4ciGPlbedibkaXpxbbbbbbbbbbbbbbbbpklbxikaXalpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaXalpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaXalpbbbpklbalczfhlkaocdfhoaiczfgiak6mbkkalTmbaAci6hHalhOaAcefgohAaoclSmdxekkcbhlaHceGmdkdnakTmbavcjdfazfhiavazfpbdbhYcbhXinaiavcj;cbfaXfgopblbgLcep9TaLpxeeeeeeeeeeeeeeeegQp9op9Hp9rgLaoakfpblbg8Acep9Ta8AaQp9op9Hp9rg8ApmbzeHdOiAlCvXoQrLgEaoamfpblbg3cep9Ta3aQp9op9Hp9rg3aoaxfpblbg5cep9Ta5aQp9op9Hp9rg5pmbzeHdOiAlCvXoQrLg8EpmbezHdiOAlvCXorQLgQaQpmbedibedibedibediaYp9UgYp9AdbbaiadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfgoaYaEa8EpmwDKYqk8AExm35Ps8E8FgQaQpmbedibedibedibedip9UgYp9AdbbaoadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfgoaYaLa8ApmwKDYq8AkEx3m5P8Es8FgLa3a5pmwKDYq8AkEx3m5P8Es8Fg8ApmbezHdiOAlvCXorQLgQaQpmbedibedibedibedip9UgYp9AdbbaoadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfgoaYaLa8ApmwDKYqk8AExm35Ps8E8FgQaQpmbedibedibedibedip9UgYp9AdbbaoadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfhiaXczfgXak6mbkkazclfgzad6mbkasavcjdfaqad2;8qbbavavcjdfaqcufad2fad;8qbbaqaDfhDc9:hoalmexikkc9:hoxekcbc99aral9Radcaadca0ESEhokavcj;kbf8Kjjjjbaokwbz:bjjjbk;uzeHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecje;8kbavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhodnaeTmbcmcsaDceSEhkcbhxcbhmcbhDcbhicbhlindnaoaq9nmbc9:hoxikdndnawRbbgrc;Ve0mbavc;abfalarcl4cu7fcsGcitfgPydlhsaPydbhzdnarcsGgPak9pmbavaiarcu7fcsGcdtfydbaxaPEhraPThPdndnadcd9hmbabaDcetfgHaz87ebaHcdfas87ebaHclfar87ebxekabaDcdtfgHazBdbaHclfasBdbaHcwfarBdbkaxaPfhxavc;abfalcitfgHarBdbaHasBdlavaicdtfarBdbavc;abfalcefcsGglcitfgHazBdbaHarBdlaiaPfhialcefhlxdkdndnaPcsSmbamaPfaPc987fcefhmxekaocefhrao8SbbgPcFeGhHdndnaPcu9mmbarhoxekaocvfhoaHcFbGhHcrhPdninar8SbbgOcFbGaPtaHVhHaOcu9kmearcefhraPcrfgPc8J9hmbxdkkarcefhokaHce4cbaHceG9R7amfhmkdndnadcd9hmbabaDcetfgraz87ebarcdfas87ebarclfam87ebxekabaDcdtfgrazBdbarclfasBdbarcwfamBdbkavc;abfalcitfgramBdbarasBdlavaicdtfamBdbavc;abfalcefcsGglcitfgrazBdbaramBdlaicefhialcefhlxekdnarcpe0mbaxcefgOavaiaqarcsGfRbbgPcl49RcsGcdtfydbaPcz6gHEhravaiaP9RcsGcdtfydbaOaHfgsaPcsGgOEhPaOThOdndnadcd9hmbabaDcetfgzax87ebazcdfar87ebazclfaP87ebxekabaDcdtfgzaxBdbazclfarBdbazcwfaPBdbkavaicdtfaxBdbavc;abfalcitfgzarBdbazaxBdlavaicefgicsGcdtfarBdbavc;abfalcefcsGcitfgzaPBdbazarBdlavaiaHfcsGgicdtfaPBdbavc;abfalcdfcsGglcitfgraxBdbaraPBdlalcefhlaiaOfhiasaOfhxxekaxcbaoRbbgzEgAarc;:eSgrfhsazcsGhCazcl4hXdndnazcs0mbascefhOxekashOavaiaX9RcsGcdtfydbhskdndnaCmbaOcefhxxekaOhxavaiaz9RcsGcdtfydbhOkdndnarTmbaocefhrxekaocdfhrao8SbegHcFeGhPdnaHcu9kmbaocofhAaPcFbGhPcrhodninar8SbbgHcFbGaotaPVhPaHcu9kmearcefhraocrfgoc8J9hmbkaAhrxekarcefhrkaPce4cbaPceG9R7amfgmhAkdndnaXcsSmbarhPxekarcefhPar8SbbgocFeGhHdnaocu9kmbarcvfhsaHcFbGhHcrhodninaP8SbbgrcFbGaotaHVhHarcu9kmeaPcefhPaocrfgoc8J9hmbkashPxekaPcefhPkaHce4cbaHceG9R7amfgmhskdndnaCcsSmbaPhoxekaPcefhoaP8SbbgrcFeGhHdnarcu9kmbaPcvfhOaHcFbGhHcrhrdninao8SbbgPcFbGartaHVhHaPcu9kmeaocefhoarcrfgrc8J9hmbkaOhoxekaocefhokaHce4cbaHceG9R7amfgmhOkdndnadcd9hmbabaDcetfgraA87ebarcdfas87ebarclfaO87ebxekabaDcdtfgraABdbarclfasBdbarcwfaOBdbkavc;abfalcitfgrasBdbaraABdlavaicdtfaABdbavc;abfalcefcsGcitfgraOBdbarasBdlavaicefgicsGcdtfasBdbavc;abfalcdfcsGcitfgraABdbaraOBdlavaiazcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhialcifhlkawcefhwalcsGhlaicsGhiaDcifgDae6mbkkcbc99aoaqSEhokavc;aef8Kjjjjbaok:llevu8Jjjjjbcz9Rhvc9:hodnaecvfal0mbcuhoaiRbbc;:eGc;qe9hmbav9cb83iwaicefhraialfc98fhwdnaeTmbdnadcdSmbcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcdtfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfglBdbaoalBdbaDcefgDae9hmbxdkkcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcetfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfgl87ebaoalBdbaDcefgDae9hmbkkcbc99arawSEhokaok:EPliuo97eue978Jjjjjbca9Rhidndnadcl9hmbdnaec98GglTmbcbhvabhdinadadpbbbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDpxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpkbbadczfhdavclfgval6mbkkalae9pmeaiaeciGgvcdtgdVcbczad9R;8kbaiabalcdtfglad;8qbbdnavTmbaiaipblbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDpxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpklbkalaiad;8qbbskdnaec98GgxTmbcbhvabhdinadczfglalpbbbgopxbbbbbbFFbbbbbbFFgkp9oadpbbbgDaopmlvorxmPsCXQL358E8FpxFubbFubbFubbFubbp9op;6eaDaopmbediwDqkzHOAKY8AEgoczp:Sep;6egrp;Geaoczp:Reczp:Sep;6egwp;Gep;Kep;Legopxb;:FSb;:FSb;:FSb;:FSawaopxbbbbbbbbbbbbbbbbp:2egqawpxbbbjbbbjbbbjbbbjgmp9op9rp;Kegwawp;Meaoaop;Mearaqaramp9op9rp;Kegoaop;Mep;Kep;Kep;Jep;Negrp;Mepxbbn0bbn0bbn0bbn0gqp;Keczp:Reawarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9op9qgwaoarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogopmwDKYqk8AExm35Ps8E8Fp9qpkbbadaDakp9oawaopmbezHdiOAlvCXorQLp9qpkbbadcafhdavclfgvax6mbkkaxae9pmbaiaeciGgvcitgdfcbcaad9R;8kbaiabaxcitfglad;8qbbdnavTmbaiaipblzgopxbbbbbbFFbbbbbbFFgkp9oaipblbgDaopmlvorxmPsCXQL358E8FpxFubbFubbFubbFubbp9op;6eaDaopmbediwDqkzHOAKY8AEgoczp:Sep;6egrp;Geaoczp:Reczp:Sep;6egwp;Gep;Kep;Legopxb;:FSb;:FSb;:FSb;:FSawaopxbbbbbbbbbbbbbbbbp:2egqawpxbbbjbbbjbbbjbbbjgmp9op9rp;Kegwawp;Meaoaop;Mearaqaramp9op9rp;Kegoaop;Mep;Kep;Kep;Jep;Negrp;Mepxbbn0bbn0bbn0bbn0gqp;Keczp:Reawarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9op9qgwaoarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogopmwDKYqk8AExm35Ps8E8Fp9qpklzaiaDakp9oawaopmbezHdiOAlvCXorQLp9qpklbkalaiad;8qbbkk;4wllue97euv978Jjjjjbc8W9Rhidnaec98GglTmbcbhvabhoinaiaopbbbgraoczfgwpbbbgDpmlvorxmPsCXQL358E8Fgqczp:Segkclp:RepklbaopxbbjZbbjZbbjZbbjZpx;Zl81Z;Zl81Z;Zl81Z;Zl81Zakpxibbbibbbibbbibbbp9qp;6ep;NegkaraDpmbediwDqkzHOAKY8AEgrczp:Reczp:Sep;6ep;MegDaDp;Meakarczp:Sep;6ep;Megxaxp;Meakaqczp:Reczp:Sep;6ep;Megqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jepxb;:FSb;:FSb;:FSb;:FSgkp;Mepxbbn0bbn0bbn0bbn0grp;KepxFFbbFFbbFFbbFFbbgmp9oaxakp;Mearp;Keczp:Rep9qgxaqakp;Mearp;Keczp:ReaDakp;Mearp;Keamp9op9qgkpmbezHdiOAlvCXorQLgrp5baipblbpEb:T:j83ibaocwfarp5eaipblbpEe:T:j83ibawaxakpmwDKYqk8AExm35Ps8E8Fgkp5baipblbpEd:T:j83ibaocKfakp5eaipblbpEi:T:j83ibaocafhoavclfgval6mbkkdnalae9pmbaiaeciGgvcitgofcbcaao9R;8kbaiabalcitfgwao;8qbbdnavTmbaiaipblbgraipblzgDpmlvorxmPsCXQL358E8Fgqczp:Segkclp:RepklaaipxbbjZbbjZbbjZbbjZpx;Zl81Z;Zl81Z;Zl81Z;Zl81Zakpxibbbibbbibbbibbbp9qp;6ep;NegkaraDpmbediwDqkzHOAKY8AEgrczp:Reczp:Sep;6ep;MegDaDp;Meakarczp:Sep;6ep;Megxaxp;Meakaqczp:Reczp:Sep;6ep;Megqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jepxb;:FSb;:FSb;:FSb;:FSgkp;Mepxbbn0bbn0bbn0bbn0grp;KepxFFbbFFbbFFbbFFbbgmp9oaxakp;Mearp;Keczp:Rep9qgxaqakp;Mearp;Keczp:ReaDakp;Mearp;Keamp9op9qgkpmbezHdiOAlvCXorQLgrp5baipblapEb:T:j83ibaiarp5eaipblapEe:T:j83iwaiaxakpmwDKYqk8AExm35Ps8E8Fgkp5baipblapEd:T:j83izaiakp5eaipblapEi:T:j83iKkawaiao;8qbbkk:Pddiue978Jjjjjbc;ab9Rhidnadcd4ae2glc98GgvTmbcbhdabheinaeaepbbbgocwp:Recwp:Sep;6eaocep:SepxbbjZbbjZbbjZbbjZp:UepxbbjFbbjFbbjFbbjFp9op;Mepkbbaeczfheadclfgdav6mbkkdnaval9pmbaialciGgdcdtgeVcbc;abae9R;8kbaiabavcdtfgvae;8qbbdnadTmbaiaipblbgocwp:Recwp:Sep;6eaocep:SepxbbjZbbjZbbjZbbjZp:UepxbbjFbbjFbbjFbbjFp9op;Mepklbkavaiae;8qbbkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikkkebcjwklz9Tbb\";\r\n\r\n\tvar detector = new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,3,2,0,0,5,3,1,0,1,12,1,0,10,22,2,12,0,65,0,65,0,65,0,252,10,0,0,11,7,0,65,0,253,15,26,11]);\r\n\tvar wasmpack = new Uint8Array([32,0,65,2,1,106,34,33,3,128,11,4,13,64,6,253,10,7,15,116,127,5,8,12,40,16,19,54,20,9,27,255,113,17,42,67,24,23,146,148,18,14,22,45,70,69,56,114,101,21,25,63,75,136,108,28,118,29,73,115]);\r\n\r\n\tif (typeof WebAssembly !== 'object') {\r\n\t\treturn {\r\n\t\t\tsupported: false,\r\n\t\t};\r\n\t}\r\n\r\n\tvar wasm = WebAssembly.validate(detector) ? wasm_simd : wasm_base;\r\n\r\n\tvar instance;\r\n\r\n\tvar ready =\r\n\t\tWebAssembly.instantiate(unpack(wasm), {})\r\n\t\t.then(function(result) {\r\n\t\t\tinstance = result.instance;\r\n\t\t\tinstance.exports.__wasm_call_ctors();\r\n\t\t});\r\n\r\n\tfunction unpack(data) {\r\n\t\tvar result = new Uint8Array(data.length);\r\n\t\tfor (var i = 0; i < data.length; ++i) {\r\n\t\t\tvar ch = data.charCodeAt(i);\r\n\t\t\tresult[i] = ch > 96 ? ch - 97 : ch > 64 ? ch - 39 : ch + 4;\r\n\t\t}\r\n\t\tvar write = 0;\r\n\t\tfor (var i = 0; i < data.length; ++i) {\r\n\t\t\tresult[write++] = (result[i] < 60) ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i];\r\n\t\t}\r\n\t\treturn result.buffer.slice(0, write);\r\n\t}\r\n\r\n\tfunction decode(fun, target, count, size, source, filter) {\r\n\t\tvar sbrk = instance.exports.sbrk;\r\n\t\tvar count4 = (count + 3) & -4;\r\n\t\tvar tp = sbrk(count4 * size);\r\n\t\tvar sp = sbrk(source.length);\r\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\r\n\t\theap.set(source, sp);\r\n\t\tvar res = fun(tp, count, size, sp, source.length);\r\n\t\tif (res == 0 && filter) {\r\n\t\t\tfilter(tp, count4, size);\r\n\t\t}\r\n\t\ttarget.set(heap.subarray(tp, tp + count * size));\r\n\t\tsbrk(tp - sbrk(0));\r\n\t\tif (res != 0) {\r\n\t\t\tthrow new Error(\"Malformed buffer data: \" + res);\r\n\t\t}\r\n\t}\r\n\r\n\tvar filters = {\r\n\t\tNONE: \"\",\r\n\t\tOCTAHEDRAL: \"meshopt_decodeFilterOct\",\r\n\t\tQUATERNION: \"meshopt_decodeFilterQuat\",\r\n\t\tEXPONENTIAL: \"meshopt_decodeFilterExp\",\r\n\t};\r\n\r\n\tvar decoders = {\r\n\t\tATTRIBUTES: \"meshopt_decodeVertexBuffer\",\r\n\t\tTRIANGLES: \"meshopt_decodeIndexBuffer\",\r\n\t\tINDICES: \"meshopt_decodeIndexSequence\",\r\n\t};\r\n\r\n\tvar workers = [];\r\n\tvar requestId = 0;\r\n\r\n\tfunction createWorker(url) {\r\n\t\tvar worker = {\r\n\t\t\tobject: new Worker(url),\r\n\t\t\tpending: 0,\r\n\t\t\trequests: {}\r\n\t\t};\r\n\r\n\t\tworker.object.onmessage = function(event) {\r\n\t\t\tvar data = event.data;\r\n\r\n\t\t\tworker.pending -= data.count;\r\n\t\t\tworker.requests[data.id][data.action](data.value);\r\n\r\n\t\t\tdelete worker.requests[data.id];\r\n\t\t};\r\n\r\n\t\treturn worker;\r\n\t}\r\n\r\n\tfunction initWorkers(count) {\r\n\t\tvar source =\r\n\t\t\t\"var instance; var ready = WebAssembly.instantiate(new Uint8Array([\" + new Uint8Array(unpack(wasm)) + \"]), {})\" +\r\n\t\t\t\".then(function(result) { instance = result.instance; instance.exports.__wasm_call_ctors(); });\" +\r\n\t\t\t\"self.onmessage = workerProcess;\" +\r\n\t\t\tdecode.toString() + workerProcess.toString();\r\n\r\n\t\tvar blob = new Blob([source], {type: 'text/javascript'});\r\n\t\tvar url = URL.createObjectURL(blob);\r\n\r\n\t\tfor (var i = 0; i < count; ++i) {\r\n\t\t\tworkers[i] = createWorker(url);\r\n\t\t}\r\n\r\n\t\tURL.revokeObjectURL(url);\r\n\t}\r\n\r\n\tfunction decodeWorker(count, size, source, mode, filter) {\r\n\t\tvar worker = workers[0];\r\n\r\n\t\tfor (var i = 1; i < workers.length; ++i) {\r\n\t\t\tif (workers[i].pending < worker.pending) {\r\n\t\t\t\tworker = workers[i];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn new Promise(function (resolve, reject) {\r\n\t\t\tvar data = new Uint8Array(source);\r\n\t\t\tvar id = requestId++;\r\n\r\n\t\t\tworker.pending += count;\r\n\t\t\tworker.requests[id] = { resolve: resolve, reject: reject };\r\n\t\t\tworker.object.postMessage({ id: id, count: count, size: size, source: data, mode: mode, filter: filter }, [ data.buffer ]);\r\n\t\t});\r\n\t}\r\n\r\n\tfunction workerProcess(event) {\r\n\t\tready.then(function() {\r\n\t\t\tvar data = event.data;\r\n\t\t\ttry {\r\n\t\t\t\tvar target = new Uint8Array(data.count * data.size);\r\n\t\t\t\tdecode(instance.exports[data.mode], target, data.count, data.size, data.source, instance.exports[data.filter]);\r\n\t\t\t\tself.postMessage({ id: data.id, count: data.count, action: \"resolve\", value: target }, [ target.buffer ]);\r\n\t\t\t} catch (error) {\r\n\t\t\t\tself.postMessage({ id: data.id, count: data.count, action: \"reject\", value: error });\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\treturn {\r\n\t\tready: ready,\r\n\t\tsupported: true,\r\n\t\tuseWorkers: function(count) {\r\n\t\t\tinitWorkers(count);\r\n\t\t},\r\n\t\tdecodeVertexBuffer: function(target, count, size, source, filter) {\r\n\t\t\tdecode(instance.exports.meshopt_decodeVertexBuffer, target, count, size, source, instance.exports[filters[filter]]);\r\n\t\t},\r\n\t\tdecodeIndexBuffer: function(target, count, size, source) {\r\n\t\t\tdecode(instance.exports.meshopt_decodeIndexBuffer, target, count, size, source);\r\n\t\t},\r\n\t\tdecodeIndexSequence: function(target, count, size, source) {\r\n\t\t\tdecode(instance.exports.meshopt_decodeIndexSequence, target, count, size, source);\r\n\t\t},\r\n\t\tdecodeGltfBuffer: function(target, count, size, source, mode, filter) {\r\n\t\t\tdecode(instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);\r\n\t\t},\r\n\t\tdecodeGltfBufferAsync: function(count, size, source, mode, filter) {\r\n\t\t\tif (workers.length > 0) {\r\n\t\t\t\treturn decodeWorker(count, size, source, decoders[mode], filters[filter]);\r\n\t\t\t}\r\n\r\n\t\t\treturn ready.then(function() {\r\n\t\t\t\tvar target = new Uint8Array(count * size);\r\n\t\t\t\tdecode(instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);\r\n\t\t\t\treturn target;\r\n\t\t\t});\r\n\t\t}\r\n\t};\r\n})();\n\nconst extToType = new Map([\n    ['bin', 'binary'],\n    ['css', 'css'],\n    ['frag', 'shader'],\n    ['glb', 'container'],\n    ['glsl', 'shader'],\n    ['hdr', 'texture'],\n    ['html', 'html'],\n    ['jpg', 'texture'],\n    ['js', 'script'],\n    ['json', 'json'],\n    ['mp3', 'audio'],\n    ['mjs', 'script'],\n    ['ply', 'gsplat'],\n    ['png', 'texture'],\n    ['sog', 'gsplat'],\n    ['txt', 'text'],\n    ['vert', 'shader'],\n    ['webp', 'texture']\n]);\n// provide buffer view callback so we can handle models compressed with MeshOptimizer\n// https://github.com/zeux/meshoptimizer\nconst processBufferView = (gltfBuffer, buffers, continuation) => {\n    if (gltfBuffer.extensions && gltfBuffer.extensions.EXT_meshopt_compression) {\n        const extensionDef = gltfBuffer.extensions.EXT_meshopt_compression;\n        Promise.all([MeshoptDecoder.ready, buffers[extensionDef.buffer]]).then((promiseResult) => {\n            const buffer = promiseResult[1];\n            const byteOffset = extensionDef.byteOffset || 0;\n            const byteLength = extensionDef.byteLength || 0;\n            const count = extensionDef.count;\n            const stride = extensionDef.byteStride;\n            const result = new Uint8Array(count * stride);\n            const source = new Uint8Array(buffer.buffer, buffer.byteOffset + byteOffset, byteLength);\n            MeshoptDecoder.decodeGltfBuffer(result, count, stride, source, extensionDef.mode, extensionDef.filter);\n            continuation(null, result);\n        });\n    }\n    else {\n        continuation(null, null);\n    }\n};\n/**\n * The AssetElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-asset/ | `<pc-asset>`} elements.\n * The AssetElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n */\nclass AssetElement extends HTMLElement {\n    constructor() {\n        super(...arguments);\n        this._lazy = false;\n        /**\n         * The asset that is loaded.\n         */\n        this.asset = null;\n    }\n    disconnectedCallback() {\n        this.destroyAsset();\n    }\n    createAsset() {\n        var _a;\n        const id = this.getAttribute('id') || '';\n        const src = this.getAttribute('src') || '';\n        let type = this.getAttribute('type');\n        // If no type is specified, try to infer it from the file extension.\n        if (!type) {\n            const ext = src.split('.').pop();\n            type = (_a = extToType.get(ext || '')) !== null && _a !== void 0 ? _a : null;\n        }\n        if (!type) {\n            console.warn(`Unsupported asset type: ${src}`);\n            return;\n        }\n        if (type === 'container') {\n            this.asset = new Asset(id, type, { url: src }, undefined, {\n                // @ts-ignore TODO no definition in pc\n                bufferView: {\n                    processAsync: processBufferView.bind(this)\n                }\n            });\n        }\n        else {\n            // @ts-ignore\n            this.asset = new Asset(id, type, { url: src });\n        }\n        this.asset.preload = !this._lazy;\n    }\n    destroyAsset() {\n        if (this.asset) {\n            this.asset.unload();\n            this.asset = null;\n        }\n    }\n    /**\n     * Sets whether the asset should be loaded lazily.\n     * @param value - The lazy loading flag.\n     */\n    set lazy(value) {\n        this._lazy = value;\n        if (this.asset) {\n            this.asset.preload = !value;\n        }\n    }\n    /**\n     * Gets whether the asset should be loaded lazily.\n     * @returns The lazy loading flag.\n     */\n    get lazy() {\n        return this._lazy;\n    }\n    static get(id) {\n        const assetElement = document.querySelector(`pc-asset[id=\"${id}\"]`);\n        return assetElement === null || assetElement === void 0 ? void 0 : assetElement.asset;\n    }\n    static get observedAttributes() {\n        return ['lazy'];\n    }\n    attributeChangedCallback(name, _oldValue, _newValue) {\n        if (name === 'lazy') {\n            this.lazy = this.hasAttribute('lazy');\n        }\n    }\n}\ncustomElements.define('pc-asset', AssetElement);\n\n/**\n * Represents a component in the PlayCanvas engine.\n *\n * @category Components\n */\nclass ComponentElement extends AsyncElement {\n    /**\n     * Creates a new ComponentElement instance.\n     *\n     * @param componentName - The name of the component.\n     * @ignore\n     */\n    constructor(componentName) {\n        super();\n        this._enabled = true;\n        this._component = null;\n        this._componentName = componentName;\n    }\n    // Method to be overridden by subclasses to provide initial component data\n    getInitialComponentData() {\n        return {};\n    }\n    async addComponent() {\n        const entityElement = this.closestEntity;\n        if (entityElement) {\n            await entityElement.ready();\n            // Add the component to the entity\n            const data = this.getInitialComponentData();\n            this._component = entityElement.entity.addComponent(this._componentName, data);\n        }\n    }\n    initComponent() { }\n    async connectedCallback() {\n        var _a;\n        await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());\n        await this.addComponent();\n        this.initComponent();\n        this._onReady();\n    }\n    disconnectedCallback() {\n        // Remove the component when the element is disconnected\n        if (this.component && this.component.entity) {\n            this._component.entity.removeComponent(this._componentName);\n            this._component = null;\n        }\n    }\n    get component() {\n        return this._component;\n    }\n    /**\n     * Sets the enabled state of the component.\n     * @param value - The enabled state of the component.\n     */\n    set enabled(value) {\n        this._enabled = value;\n        if (this.component) {\n            this.component.enabled = value;\n        }\n    }\n    /**\n     * Gets the enabled state of the component.\n     * @returns The enabled state of the component.\n     */\n    get enabled() {\n        return this._enabled;\n    }\n    static get observedAttributes() {\n        return ['enabled'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'enabled':\n                this.enabled = newValue !== 'false';\n                break;\n        }\n    }\n}\n\n/**\n * The ListenerComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-listener/ | `<pc-listener>`} elements.\n * The ListenerComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass ListenerComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('audiolistener');\n    }\n    /**\n     * Gets the underlying PlayCanvas audio listener component.\n     * @returns The audio listener component.\n     */\n    get component() {\n        return super.component;\n    }\n}\ncustomElements.define('pc-listener', ListenerComponentElement);\n\nconst tonemaps = new Map([\n    ['none', TONEMAP_NONE],\n    ['linear', TONEMAP_LINEAR],\n    ['filmic', TONEMAP_FILMIC],\n    ['hejl', TONEMAP_HEJL],\n    ['aces', TONEMAP_ACES],\n    ['aces2', TONEMAP_ACES2],\n    ['neutral', TONEMAP_NEUTRAL]\n]);\n/**\n * The CameraComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-camera/ | `<pc-camera>`} elements.\n * The CameraComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass CameraComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('camera');\n        this._clearColor = new Color(0.75, 0.75, 0.75, 1);\n        this._clearColorBuffer = true;\n        this._clearDepthBuffer = true;\n        this._clearStencilBuffer = false;\n        this._cullFaces = true;\n        this._farClip = 1000;\n        this._flipFaces = false;\n        this._fov = 45;\n        this._frustumCulling = true;\n        this._gamma = 'srgb';\n        this._horizontalFov = false;\n        this._nearClip = 0.1;\n        this._orthographic = false;\n        this._orthoHeight = 10;\n        this._priority = 0;\n        this._rect = new Vec4(0, 0, 1, 1);\n        this._scissorRect = new Vec4(0, 0, 1, 1);\n        this._tonemap = 'none';\n    }\n    getInitialComponentData() {\n        return {\n            clearColor: this._clearColor,\n            clearColorBuffer: this._clearColorBuffer,\n            clearDepthBuffer: this._clearDepthBuffer,\n            clearStencilBuffer: this._clearStencilBuffer,\n            cullFaces: this._cullFaces,\n            farClip: this._farClip,\n            flipFaces: this._flipFaces,\n            fov: this._fov,\n            frustumCulling: this._frustumCulling,\n            gammaCorrection: this._gamma === 'srgb' ? GAMMA_SRGB : GAMMA_NONE,\n            horizontalFov: this._horizontalFov,\n            nearClip: this._nearClip,\n            orthographic: this._orthographic,\n            orthoHeight: this._orthoHeight,\n            priority: this._priority,\n            rect: this._rect,\n            scissorRect: this._scissorRect,\n            toneMapping: tonemaps.get(this._tonemap)\n        };\n    }\n    get xrAvailable() {\n        var _a;\n        const xrManager = (_a = this.component) === null || _a === void 0 ? void 0 : _a.system.app.xr;\n        return xrManager && xrManager.supported && xrManager.isAvailable(XRTYPE_VR);\n    }\n    /**\n     * Starts the camera in XR mode.\n     * @param type - The type of XR mode to start.\n     * @param space - The space to start the camera in.\n     */\n    startXr(type, space) {\n        if (this.component && this.xrAvailable) {\n            this.component.startXr(type, space, {\n                callback: (err) => {\n                    if (err)\n                        console.error(`WebXR Immersive VR failed to start: ${err.message}`);\n                }\n            });\n        }\n    }\n    /**\n     * Ends the camera's XR mode.\n     */\n    endXr() {\n        if (this.component) {\n            this.component.endXr();\n        }\n    }\n    /**\n     * Gets the underlying PlayCanvas camera component.\n     * @returns The camera component.\n     */\n    get component() {\n        return super.component;\n    }\n    /**\n     * Sets the clear color of the camera.\n     * @param value - The clear color.\n     */\n    set clearColor(value) {\n        this._clearColor = value;\n        if (this.component) {\n            this.component.clearColor = value;\n        }\n    }\n    /**\n     * Gets the clear color of the camera.\n     * @returns The clear color.\n     */\n    get clearColor() {\n        return this._clearColor;\n    }\n    /**\n     * Sets the clear color buffer of the camera.\n     * @param value - The clear color buffer.\n     */\n    set clearColorBuffer(value) {\n        this._clearColorBuffer = value;\n        if (this.component) {\n            this.component.clearColorBuffer = value;\n        }\n    }\n    /**\n     * Gets the clear color buffer of the camera.\n     * @returns The clear color buffer.\n     */\n    get clearColorBuffer() {\n        return this._clearColorBuffer;\n    }\n    /**\n     * Sets the clear depth buffer of the camera.\n     * @param value - The clear depth buffer.\n     */\n    set clearDepthBuffer(value) {\n        this._clearDepthBuffer = value;\n        if (this.component) {\n            this.component.clearDepthBuffer = value;\n        }\n    }\n    /**\n     * Gets the clear depth buffer of the camera.\n     * @returns The clear depth buffer.\n     */\n    get clearDepthBuffer() {\n        return this._clearDepthBuffer;\n    }\n    /**\n     * Sets the clear stencil buffer of the camera.\n     * @param value - The clear stencil buffer.\n     */\n    set clearStencilBuffer(value) {\n        this._clearStencilBuffer = value;\n        if (this.component) {\n            this.component.clearStencilBuffer = value;\n        }\n    }\n    /**\n     * Gets the clear stencil buffer of the camera.\n     * @returns The clear stencil buffer.\n     */\n    get clearStencilBuffer() {\n        return this._clearStencilBuffer;\n    }\n    /**\n     * Sets the cull faces of the camera.\n     * @param value - The cull faces.\n     */\n    set cullFaces(value) {\n        this._cullFaces = value;\n        if (this.component) {\n            this.component.cullFaces = value;\n        }\n    }\n    /**\n     * Gets the cull faces of the camera.\n     * @returns The cull faces.\n     */\n    get cullFaces() {\n        return this._cullFaces;\n    }\n    /**\n     * Sets the far clip distance of the camera.\n     * @param value - The far clip distance.\n     */\n    set farClip(value) {\n        this._farClip = value;\n        if (this.component) {\n            this.component.farClip = value;\n        }\n    }\n    /**\n     * Gets the far clip distance of the camera.\n     * @returns The far clip distance.\n     */\n    get farClip() {\n        return this._farClip;\n    }\n    /**\n     * Sets the flip faces of the camera.\n     * @param value - The flip faces.\n     */\n    set flipFaces(value) {\n        this._flipFaces = value;\n        if (this.component) {\n            this.component.flipFaces = value;\n        }\n    }\n    /**\n     * Gets the flip faces of the camera.\n     * @returns The flip faces.\n     */\n    get flipFaces() {\n        return this._flipFaces;\n    }\n    /**\n     * Sets the field of view of the camera.\n     * @param value - The field of view.\n     */\n    set fov(value) {\n        this._fov = value;\n        if (this.component) {\n            this.component.fov = value;\n        }\n    }\n    /**\n     * Gets the field of view of the camera.\n     * @returns The field of view.\n     */\n    get fov() {\n        return this._fov;\n    }\n    /**\n     * Sets the frustum culling of the camera.\n     * @param value - The frustum culling.\n     */\n    set frustumCulling(value) {\n        this._frustumCulling = value;\n        if (this.component) {\n            this.component.frustumCulling = value;\n        }\n    }\n    /**\n     * Gets the frustum culling of the camera.\n     * @returns The frustum culling.\n     */\n    get frustumCulling() {\n        return this._frustumCulling;\n    }\n    /**\n     * Sets the gamma correction of the camera.\n     * @param value - The gamma correction.\n     */\n    set gamma(value) {\n        this._gamma = value;\n        if (this.component) {\n            this.component.gammaCorrection = value === 'srgb' ? GAMMA_SRGB : GAMMA_NONE;\n        }\n    }\n    /**\n     * Gets the gamma correction of the camera.\n     * @returns The gamma correction.\n     */\n    get gamma() {\n        return this._gamma;\n    }\n    /**\n     * Sets whether the camera's field of view (fov) is horizontal or vertical. Defaults to false\n     * (meaning it is vertical be default).\n     * @param value - Whether the camera's field of view is horizontal.\n     */\n    set horizontalFov(value) {\n        this._horizontalFov = value;\n        if (this.component) {\n            this.component.horizontalFov = value;\n        }\n    }\n    /**\n     * Gets whether the camera's field of view (fov) is horizontal or vertical.\n     * @returns Whether the camera's field of view is horizontal.\n     */\n    get horizontalFov() {\n        return this._horizontalFov;\n    }\n    /**\n     * Sets the near clip distance of the camera.\n     * @param value - The near clip distance.\n     */\n    set nearClip(value) {\n        this._nearClip = value;\n        if (this.component) {\n            this.component.nearClip = value;\n        }\n    }\n    /**\n     * Gets the near clip distance of the camera.\n     * @returns The near clip distance.\n     */\n    get nearClip() {\n        return this._nearClip;\n    }\n    /**\n     * Sets the orthographic projection of the camera.\n     * @param value - The orthographic projection.\n     */\n    set orthographic(value) {\n        this._orthographic = value;\n        if (this.component) {\n            this.component.projection = value ? PROJECTION_ORTHOGRAPHIC : PROJECTION_PERSPECTIVE;\n        }\n    }\n    /**\n     * Gets the orthographic projection of the camera.\n     * @returns The orthographic projection.\n     */\n    get orthographic() {\n        return this._orthographic;\n    }\n    /**\n     * Sets the orthographic height of the camera.\n     * @param value - The orthographic height.\n     */\n    set orthoHeight(value) {\n        this._orthoHeight = value;\n        if (this.component) {\n            this.component.orthoHeight = value;\n        }\n    }\n    /**\n     * Gets the orthographic height of the camera.\n     * @returns The orthographic height.\n     */\n    get orthoHeight() {\n        return this._orthoHeight;\n    }\n    /**\n     * Sets the priority of the camera.\n     * @param value - The priority.\n     */\n    set priority(value) {\n        this._priority = value;\n        if (this.component) {\n            this.component.priority = value;\n        }\n    }\n    /**\n     * Gets the priority of the camera.\n     * @returns The priority.\n     */\n    get priority() {\n        return this._priority;\n    }\n    /**\n     * Sets the rect of the camera.\n     * @param value - The rect.\n     */\n    set rect(value) {\n        this._rect = value;\n        if (this.component) {\n            this.component.rect = value;\n        }\n    }\n    /**\n     * Gets the rect of the camera.\n     * @returns The rect.\n     */\n    get rect() {\n        return this._rect;\n    }\n    /**\n     * Sets the scissor rect of the camera.\n     * @param value - The scissor rect.\n     */\n    set scissorRect(value) {\n        this._scissorRect = value;\n        if (this.component) {\n            this.component.scissorRect = value;\n        }\n    }\n    /**\n     * Gets the scissor rect of the camera.\n     * @returns The scissor rect.\n     */\n    get scissorRect() {\n        return this._scissorRect;\n    }\n    /**\n     * Sets the tone mapping of the camera.\n     * @param value - The tone mapping.\n     */\n    set tonemap(value) {\n        var _a;\n        this._tonemap = value;\n        if (this.component) {\n            this.component.toneMapping = (_a = tonemaps.get(value)) !== null && _a !== void 0 ? _a : TONEMAP_NONE;\n        }\n    }\n    /**\n     * Gets the tone mapping of the camera.\n     * @returns The tone mapping.\n     */\n    get tonemap() {\n        return this._tonemap;\n    }\n    static get observedAttributes() {\n        return [\n            ...super.observedAttributes,\n            'clear-color',\n            'clear-color-buffer',\n            'clear-depth-buffer',\n            'clear-stencil-buffer',\n            'cull-faces',\n            'far-clip',\n            'flip-faces',\n            'fov',\n            'frustum-culling',\n            'gamma',\n            'horizontal-fov',\n            'near-clip',\n            'orthographic',\n            'ortho-height',\n            'priority',\n            'rect',\n            'scissor-rect',\n            'tonemap'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'clear-color':\n                this.clearColor = parseColor(newValue);\n                break;\n            case 'clear-color-buffer':\n                this.clearColorBuffer = newValue !== 'false';\n                break;\n            case 'clear-depth-buffer':\n                this.clearDepthBuffer = newValue !== 'false';\n                break;\n            case 'clear-stencil-buffer':\n                this.clearStencilBuffer = newValue !== 'false';\n                break;\n            case 'cull-faces':\n                this.cullFaces = newValue !== 'false';\n                break;\n            case 'far-clip':\n                this.farClip = parseFloat(newValue);\n                break;\n            case 'flip-faces':\n                this.flipFaces = newValue !== 'true';\n                break;\n            case 'fov':\n                this.fov = parseFloat(newValue);\n                break;\n            case 'frustum-culling':\n                this.frustumCulling = newValue !== 'false';\n                break;\n            case 'gamma':\n                this.gamma = newValue;\n                break;\n            case 'horizontal-fov':\n                this.horizontalFov = this.hasAttribute('horizontal-fov');\n                break;\n            case 'near-clip':\n                this.nearClip = parseFloat(newValue);\n                break;\n            case 'orthographic':\n                this.orthographic = this.hasAttribute('orthographic');\n                break;\n            case 'ortho-height':\n                this.orthoHeight = parseFloat(newValue);\n                break;\n            case 'priority':\n                this.priority = parseFloat(newValue);\n                break;\n            case 'rect':\n                this.rect = parseVec4(newValue);\n                break;\n            case 'scissor-rect':\n                this.scissorRect = parseVec4(newValue);\n                break;\n            case 'tonemap':\n                this.tonemap = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-camera', CameraComponentElement);\n\n/**\n * The CollisionComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-collision/ | `<pc-collision>`} elements.\n * The CollisionComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass CollisionComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('collision');\n        this._angularOffset = new Quat();\n        this._axis = 1;\n        this._convexHull = false;\n        this._halfExtents = new Vec3(0.5, 0.5, 0.5);\n        this._height = 2;\n        this._linearOffset = new Vec3();\n        this._radius = 0.5;\n        this._type = 'box';\n    }\n    getInitialComponentData() {\n        return {\n            axis: this._axis,\n            angularOffset: this._angularOffset,\n            convexHull: this._convexHull,\n            halfExtents: this._halfExtents,\n            height: this._height,\n            linearOffset: this._linearOffset,\n            radius: this._radius,\n            type: this._type\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas collision component.\n     * @returns The collision component.\n     */\n    get component() {\n        return super.component;\n    }\n    set angularOffset(value) {\n        this._angularOffset = value;\n        if (this.component) {\n            this.component.angularOffset = value;\n        }\n    }\n    get angularOffset() {\n        return this._angularOffset;\n    }\n    set axis(value) {\n        this._axis = value;\n        if (this.component) {\n            this.component.axis = value;\n        }\n    }\n    get axis() {\n        return this._axis;\n    }\n    set convexHull(value) {\n        this._convexHull = value;\n        if (this.component) {\n            this.component.convexHull = value;\n        }\n    }\n    get convexHull() {\n        return this._convexHull;\n    }\n    set halfExtents(value) {\n        this._halfExtents = value;\n        if (this.component) {\n            this.component.halfExtents = value;\n        }\n    }\n    get halfExtents() {\n        return this._halfExtents;\n    }\n    set height(value) {\n        this._height = value;\n        if (this.component) {\n            this.component.height = value;\n        }\n    }\n    get height() {\n        return this._height;\n    }\n    set linearOffset(value) {\n        this._linearOffset = value;\n        if (this.component) {\n            this.component.linearOffset = value;\n        }\n    }\n    get linearOffset() {\n        return this._linearOffset;\n    }\n    set radius(value) {\n        this._radius = value;\n        if (this.component) {\n            this.component.radius = value;\n        }\n    }\n    get radius() {\n        return this._radius;\n    }\n    set type(value) {\n        this._type = value;\n        if (this.component) {\n            this.component.type = value;\n        }\n    }\n    get type() {\n        return this._type;\n    }\n    static get observedAttributes() {\n        return [...super.observedAttributes, 'angular-offset', 'axis', 'convex-hull', 'half-extents', 'height', 'linear-offset', 'radius', 'type'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'angular-offset':\n                this.angularOffset = parseQuat(newValue);\n                break;\n            case 'axis':\n                this.axis = parseInt(newValue, 10);\n                break;\n            case 'convex-hull':\n                this.convexHull = this.hasAttribute('convex-hull');\n                break;\n            case 'half-extents':\n                this.halfExtents = parseVec3(newValue);\n                break;\n            case 'height':\n                this.height = parseFloat(newValue);\n                break;\n            case 'linear-offset':\n                this.linearOffset = parseVec3(newValue);\n                break;\n            case 'radius':\n                this.radius = parseFloat(newValue);\n                break;\n            case 'type':\n                this.type = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-collision', CollisionComponentElement);\n\n/**\n * The ElementComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-element/ | `<pc-element>`} elements.\n * The ElementComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass ElementComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('element');\n        this._anchor = new Vec4(0.5, 0.5, 0.5, 0.5);\n        this._asset = '';\n        this._autoWidth = true;\n        this._color = new Color(1, 1, 1, 1);\n        this._fontSize = 32;\n        this._lineHeight = 32;\n        this._pivot = new Vec2(0.5, 0.5);\n        this._text = '';\n        this._type = 'group';\n        this._width = 0;\n        this._wrapLines = false;\n    }\n    initComponent() {\n        this.component._text._material.useFog = true;\n    }\n    getInitialComponentData() {\n        return {\n            anchor: this._anchor,\n            autoWidth: this._autoWidth,\n            color: this._color,\n            fontAsset: AssetElement.get(this._asset).id,\n            fontSize: this._fontSize,\n            lineHeight: this._lineHeight,\n            pivot: this._pivot,\n            type: this._type,\n            text: this._text,\n            width: this._width,\n            wrapLines: this._wrapLines\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas element component.\n     * @returns The element component.\n     */\n    get component() {\n        return super.component;\n    }\n    /**\n     * Sets the anchor of the element component.\n     * @param value - The anchor.\n     */\n    set anchor(value) {\n        this._anchor = value;\n        if (this.component) {\n            this.component.anchor = value;\n        }\n    }\n    /**\n     * Gets the anchor of the element component.\n     * @returns The anchor.\n     */\n    get anchor() {\n        return this._anchor;\n    }\n    /**\n     * Sets the id of the `pc-asset` to use for the font.\n     * @param value - The asset ID.\n     */\n    set asset(value) {\n        this._asset = value;\n        const asset = AssetElement.get(value);\n        if (this.component && asset) {\n            this.component.fontAsset = asset.id;\n        }\n    }\n    /**\n     * Gets the id of the `pc-asset` to use for the font.\n     * @returns The asset ID.\n     */\n    get asset() {\n        return this._asset;\n    }\n    /**\n     * Sets whether the element component should automatically adjust its width.\n     * @param value - Whether to automatically adjust the width.\n     */\n    set autoWidth(value) {\n        this._autoWidth = value;\n        if (this.component) {\n            this.component.autoWidth = value;\n        }\n    }\n    /**\n     * Gets whether the element component should automatically adjust its width.\n     * @returns Whether to automatically adjust the width.\n     */\n    get autoWidth() {\n        return this._autoWidth;\n    }\n    /**\n     * Sets the color of the element component.\n     * @param value - The color.\n     */\n    set color(value) {\n        this._color = value;\n        if (this.component) {\n            this.component.color = value;\n        }\n    }\n    /**\n     * Gets the color of the element component.\n     * @returns The color.\n     */\n    get color() {\n        return this._color;\n    }\n    /**\n     * Sets the font size of the element component.\n     * @param value - The font size.\n     */\n    set fontSize(value) {\n        this._fontSize = value;\n        if (this.component) {\n            this.component.fontSize = value;\n        }\n    }\n    /**\n     * Gets the font size of the element component.\n     * @returns The font size.\n     */\n    get fontSize() {\n        return this._fontSize;\n    }\n    /**\n     * Sets the line height of the element component.\n     * @param value - The line height.\n     */\n    set lineHeight(value) {\n        this._lineHeight = value;\n        if (this.component) {\n            this.component.lineHeight = value;\n        }\n    }\n    /**\n     * Gets the line height of the element component.\n     * @returns The line height.\n     */\n    get lineHeight() {\n        return this._lineHeight;\n    }\n    /**\n     * Sets the pivot of the element component.\n     * @param value - The pivot.\n     */\n    set pivot(value) {\n        this._pivot = value;\n        if (this.component) {\n            this.component.pivot = value;\n        }\n    }\n    /**\n     * Gets the pivot of the element component.\n     * @returns The pivot.\n     */\n    get pivot() {\n        return this._pivot;\n    }\n    /**\n     * Sets the text of the element component.\n     * @param value - The text.\n     */\n    set text(value) {\n        this._text = value;\n        if (this.component) {\n            this.component.text = value;\n        }\n    }\n    /**\n     * Gets the text of the element component.\n     * @returns The text.\n     */\n    get text() {\n        return this._text;\n    }\n    /**\n     * Sets the type of the element component.\n     * @param value - The type.\n     */\n    set type(value) {\n        this._type = value;\n        if (this.component) {\n            this.component.type = value;\n        }\n    }\n    /**\n     * Gets the type of the element component.\n     * @returns The type.\n     */\n    get type() {\n        return this._type;\n    }\n    /**\n     * Sets the width of the element component.\n     * @param value - The width.\n     */\n    set width(value) {\n        this._width = value;\n        if (this.component) {\n            this.component.width = value;\n        }\n    }\n    /**\n     * Gets the width of the element component.\n     * @returns The width.\n     */\n    get width() {\n        return this._width;\n    }\n    /**\n     * Sets whether the element component should wrap lines.\n     * @param value - Whether to wrap lines.\n     */\n    set wrapLines(value) {\n        this._wrapLines = value;\n        if (this.component) {\n            this.component.wrapLines = value;\n        }\n    }\n    /**\n     * Gets whether the element component should wrap lines.\n     * @returns Whether to wrap lines.\n     */\n    get wrapLines() {\n        return this._wrapLines;\n    }\n    static get observedAttributes() {\n        return [\n            ...super.observedAttributes,\n            'anchor',\n            'asset',\n            'auto-width',\n            'color',\n            'font-size',\n            'line-height',\n            'pivot',\n            'text',\n            'type',\n            'width',\n            'wrap-lines'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'anchor':\n                this.anchor = parseVec4(newValue);\n                break;\n            case 'asset':\n                this.asset = newValue;\n                break;\n            case 'auto-width':\n                this.autoWidth = newValue !== 'false';\n                break;\n            case 'color':\n                this.color = parseColor(newValue);\n                break;\n            case 'font-size':\n                this.fontSize = Number(newValue);\n                break;\n            case 'line-height':\n                this.lineHeight = Number(newValue);\n                break;\n            case 'pivot':\n                this.pivot = parseVec2(newValue);\n                break;\n            case 'text':\n                this.text = newValue;\n                break;\n            case 'type':\n                this.type = newValue;\n                break;\n            case 'width':\n                this.width = Number(newValue);\n                break;\n            case 'wrap-lines':\n                this.wrapLines = this.hasAttribute(name);\n                break;\n        }\n    }\n}\ncustomElements.define('pc-element', ElementComponentElement);\n\nconst shadowTypes = new Map([\n    ['pcf1-16f', SHADOW_PCF1_16F],\n    ['pcf1-32f', SHADOW_PCF1_32F],\n    ['pcf3-16f', SHADOW_PCF3_16F],\n    ['pcf3-32f', SHADOW_PCF3_32F],\n    ['pcf5-16f', SHADOW_PCF5_16F],\n    ['pcf5-32f', SHADOW_PCF5_32F],\n    ['vsm-16f', SHADOW_VSM_16F],\n    ['vsm-32f', SHADOW_VSM_32F],\n    ['pcss-32f', SHADOW_PCSS_32F]\n]);\n/**\n * The LightComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-light/ | `<pc-light>`} elements.\n * The LightComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass LightComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('light');\n        this._castShadows = false;\n        this._color = new Color(1, 1, 1);\n        this._innerConeAngle = 40;\n        this._intensity = 1;\n        this._normalOffsetBias = 0.05;\n        this._outerConeAngle = 45;\n        this._range = 10;\n        this._shadowBias = 0.2;\n        this._shadowDistance = 16;\n        this._shadowIntensity = 1;\n        this._shadowResolution = 1024;\n        this._shadowType = 'pcf3-32f';\n        this._type = 'directional';\n        this._vsmBias = 0.01;\n        this._vsmBlurSize = 11;\n    }\n    getInitialComponentData() {\n        return {\n            castShadows: this._castShadows,\n            color: this._color,\n            innerConeAngle: this._innerConeAngle,\n            intensity: this._intensity,\n            normalOffsetBias: this._normalOffsetBias,\n            outerConeAngle: this._outerConeAngle,\n            range: this._range,\n            shadowBias: this._shadowBias,\n            shadowDistance: this._shadowDistance,\n            shadowIntensity: this._shadowIntensity,\n            shadowResolution: this._shadowResolution,\n            shadowType: shadowTypes.get(this._shadowType),\n            type: this._type,\n            vsmBias: this._vsmBias,\n            vsmBlurSize: this._vsmBlurSize\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas light component.\n     * @returns The light component.\n     */\n    get component() {\n        return super.component;\n    }\n    /**\n     * Sets the cast shadows flag of the light.\n     * @param value - The cast shadows flag.\n     */\n    set castShadows(value) {\n        this._castShadows = value;\n        if (this.component) {\n            this.component.castShadows = value;\n        }\n    }\n    /**\n     * Gets the cast shadows flag of the light.\n     * @returns The cast shadows flag.\n     */\n    get castShadows() {\n        return this._castShadows;\n    }\n    /**\n     * Sets the color of the light.\n     * @param value - The color.\n     */\n    set color(value) {\n        this._color = value;\n        if (this.component) {\n            this.component.color = value;\n        }\n    }\n    /**\n     * Gets the color of the light.\n     * @returns The color.\n     */\n    get color() {\n        return this._color;\n    }\n    /**\n     * Sets the inner cone angle of the light.\n     * @param value - The inner cone angle.\n     */\n    set innerConeAngle(value) {\n        this._innerConeAngle = value;\n        if (this.component) {\n            this.component.innerConeAngle = value;\n        }\n    }\n    /**\n     * Gets the inner cone angle of the light.\n     * @returns The inner cone angle.\n     */\n    get innerConeAngle() {\n        return this._innerConeAngle;\n    }\n    /**\n     * Sets the intensity of the light.\n     * @param value - The intensity.\n     */\n    set intensity(value) {\n        this._intensity = value;\n        if (this.component) {\n            this.component.intensity = value;\n        }\n    }\n    /**\n     * Gets the intensity of the light.\n     * @returns The intensity.\n     */\n    get intensity() {\n        return this._intensity;\n    }\n    /**\n     * Sets the normal offset bias of the light.\n     * @param value - The normal offset bias.\n     */\n    set normalOffsetBias(value) {\n        this._normalOffsetBias = value;\n        if (this.component) {\n            this.component.normalOffsetBias = value;\n        }\n    }\n    /**\n     * Gets the normal offset bias of the light.\n     * @returns The normal offset bias.\n     */\n    get normalOffsetBias() {\n        return this._normalOffsetBias;\n    }\n    /**\n     * Sets the outer cone angle of the light.\n     * @param value - The outer cone angle.\n     */\n    set outerConeAngle(value) {\n        this._outerConeAngle = value;\n        if (this.component) {\n            this.component.outerConeAngle = value;\n        }\n    }\n    /**\n     * Gets the outer cone angle of the light.\n     * @returns The outer cone angle.\n     */\n    get outerConeAngle() {\n        return this._outerConeAngle;\n    }\n    /**\n     * Sets the range of the light.\n     * @param value - The range.\n     */\n    set range(value) {\n        this._range = value;\n        if (this.component) {\n            this.component.range = value;\n        }\n    }\n    /**\n     * Gets the range of the light.\n     * @returns The range.\n     */\n    get range() {\n        return this._range;\n    }\n    /**\n     * Sets the shadow bias of the light.\n     * @param value - The shadow bias.\n     */\n    set shadowBias(value) {\n        this._shadowBias = value;\n        if (this.component) {\n            this.component.shadowBias = value;\n        }\n    }\n    /**\n     * Gets the shadow bias of the light.\n     * @returns The shadow bias.\n     */\n    get shadowBias() {\n        return this._shadowBias;\n    }\n    /**\n     * Sets the shadow distance of the light.\n     * @param value - The shadow distance.\n     */\n    set shadowDistance(value) {\n        this._shadowDistance = value;\n        if (this.component) {\n            this.component.shadowDistance = value;\n        }\n    }\n    /**\n     * Gets the shadow distance of the light.\n     * @returns The shadow distance.\n     */\n    get shadowDistance() {\n        return this._shadowDistance;\n    }\n    /**\n     * Sets the shadow intensity of the light.\n     * @param value - The shadow intensity.\n     */\n    set shadowIntensity(value) {\n        this._shadowIntensity = value;\n        if (this.component) {\n            this.component.shadowIntensity = value;\n        }\n    }\n    /**\n     * Gets the shadow intensity of the light.\n     * @returns The shadow intensity.\n     */\n    get shadowIntensity() {\n        return this._shadowIntensity;\n    }\n    /**\n     * Sets the shadow resolution of the light.\n     * @param value - The shadow resolution.\n     */\n    set shadowResolution(value) {\n        this._shadowResolution = value;\n        if (this.component) {\n            this.component.shadowResolution = value;\n        }\n    }\n    /**\n     * Gets the shadow resolution of the light.\n     * @returns The shadow resolution.\n     */\n    get shadowResolution() {\n        return this._shadowResolution;\n    }\n    /**\n     * Sets the shadow type of the light.\n     * @param value - The shadow type. Can be:\n     *\n     * - `pcf1-16f` - 1-tap percentage-closer filtered shadow map with 16-bit depth.\n     * - `pcf1-32f` - 1-tap percentage-closer filtered shadow map with 32-bit depth.\n     * - `pcf3-16f` - 3-tap percentage-closer filtered shadow map with 16-bit depth.\n     * - `pcf3-32f` - 3-tap percentage-closer filtered shadow map with 32-bit depth.\n     * - `pcf5-16f` - 5-tap percentage-closer filtered shadow map with 16-bit depth.\n     * - `pcf5-32f` - 5-tap percentage-closer filtered shadow map with 32-bit depth.\n     * - `vsm-16f` - Variance shadow map with 16-bit depth.\n     * - `vsm-32f` - Variance shadow map with 32-bit depth.\n     * - `pcss-32f` - Percentage-closer soft shadow with 32-bit depth.\n     */\n    set shadowType(value) {\n        var _a;\n        this._shadowType = value;\n        if (this.component) {\n            this.component.shadowType = (_a = shadowTypes.get(value)) !== null && _a !== void 0 ? _a : SHADOW_PCF3_32F;\n        }\n    }\n    /**\n     * Gets the shadow type of the light.\n     * @returns The shadow type.\n     */\n    get shadowType() {\n        return this._shadowType;\n    }\n    /**\n     * Sets the type of the light.\n     * @param value - The type.\n     */\n    set type(value) {\n        if (!['directional', 'omni', 'spot'].includes(value)) {\n            console.warn(`Invalid light type '${value}', using default type '${this._type}'.`);\n            return;\n        }\n        this._type = value;\n        if (this.component) {\n            this.component.type = value;\n        }\n    }\n    /**\n     * Gets the type of the light.\n     * @returns The type.\n     */\n    get type() {\n        return this._type;\n    }\n    /**\n     * Sets the VSM bias of the light.\n     * @param value - The VSM bias.\n     */\n    set vsmBias(value) {\n        this._vsmBias = value;\n        if (this.component) {\n            this.component.vsmBias = value;\n        }\n    }\n    /**\n     * Gets the VSM bias of the light.\n     * @returns The VSM bias.\n     */\n    get vsmBias() {\n        return this._vsmBias;\n    }\n    /**\n     * Sets the VSM blur size of the light. Minimum is 1, maximum is 25. Default is 11.\n     * @param value - The VSM blur size.\n     */\n    set vsmBlurSize(value) {\n        this._vsmBlurSize = value;\n        if (this.component) {\n            this.component.vsmBlurSize = value;\n        }\n    }\n    /**\n     * Gets the VSM blur size of the light.\n     * @returns The VSM blur size.\n     */\n    get vsmBlurSize() {\n        return this._vsmBlurSize;\n    }\n    static get observedAttributes() {\n        return [\n            ...super.observedAttributes,\n            'color',\n            'cast-shadows',\n            'intensity',\n            'inner-cone-angle',\n            'normal-offset-bias',\n            'outer-cone-angle',\n            'range',\n            'shadow-bias',\n            'shadow-distance',\n            'shadow-intensity',\n            'shadow-resolution',\n            'shadow-type',\n            'type',\n            'vsm-bias',\n            'vsm-blur-size'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'color':\n                this.color = parseColor(newValue);\n                break;\n            case 'cast-shadows':\n                this.castShadows = this.hasAttribute('cast-shadows');\n                break;\n            case 'inner-cone-angle':\n                this.innerConeAngle = Number(newValue);\n                break;\n            case 'intensity':\n                this.intensity = Number(newValue);\n                break;\n            case 'normal-offset-bias':\n                this.normalOffsetBias = Number(newValue);\n                break;\n            case 'outer-cone-angle':\n                this.outerConeAngle = Number(newValue);\n                break;\n            case 'range':\n                this.range = Number(newValue);\n                break;\n            case 'shadow-bias':\n                this.shadowBias = Number(newValue);\n                break;\n            case 'shadow-distance':\n                this.shadowDistance = Number(newValue);\n                break;\n            case 'shadow-resolution':\n                this.shadowResolution = Number(newValue);\n                break;\n            case 'shadow-intensity':\n                this.shadowIntensity = Number(newValue);\n                break;\n            case 'shadow-type':\n                this.shadowType = newValue;\n                break;\n            case 'type':\n                this.type = newValue;\n                break;\n            case 'vsm-bias':\n                this.vsmBias = Number(newValue);\n                break;\n            case 'vsm-blur-size':\n                this.vsmBlurSize = Number(newValue);\n                break;\n        }\n    }\n}\ncustomElements.define('pc-light', LightComponentElement);\n\n/**\n * The ParticleSystemComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-particles/ | `<pc-particles>`} elements.\n * The ParticleSystemComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass ParticleSystemComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('particlesystem');\n        this._asset = '';\n    }\n    getInitialComponentData() {\n        var _a;\n        const asset = AssetElement.get(this._asset);\n        if (!asset) {\n            return {};\n        }\n        if (asset.resource.colorMapAsset) {\n            const id = asset.resource.colorMapAsset;\n            const colorMapAsset = (_a = AssetElement.get(id)) === null || _a === void 0 ? void 0 : _a.id;\n            if (colorMapAsset) {\n                asset.resource.colorMapAsset = colorMapAsset;\n            }\n        }\n        return asset.resource;\n    }\n    /**\n     * Gets the underlying PlayCanvas particle system component.\n     * @returns The particle system component.\n     */\n    get component() {\n        return super.component;\n    }\n    applyConfig(resource) {\n        if (!this.component) {\n            return;\n        }\n        // Set all the config properties on the component\n        for (const key in resource) {\n            if (resource.hasOwnProperty(key)) {\n                this.component[key] = resource[key];\n            }\n        }\n    }\n    async _loadAsset() {\n        var _a;\n        const appElement = await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());\n        const app = appElement === null || appElement === void 0 ? void 0 : appElement.app;\n        const asset = AssetElement.get(this._asset);\n        if (!asset) {\n            return;\n        }\n        if (asset.loaded) {\n            this.applyConfig(asset.resource);\n        }\n        else {\n            asset.once('load', () => {\n                this.applyConfig(asset.resource);\n            });\n            app.assets.load(asset);\n        }\n    }\n    /**\n     * Sets the id of the `pc-asset` to use for the model.\n     * @param value - The asset ID.\n     */\n    set asset(value) {\n        this._asset = value;\n        if (this.isConnected) {\n            this._loadAsset();\n        }\n    }\n    /**\n     * Gets the id of the `pc-asset` to use for the model.\n     * @returns The asset ID.\n     */\n    get asset() {\n        return this._asset;\n    }\n    // Control methods\n    /**\n     * Starts playing the particle system\n     */\n    play() {\n        if (this.component) {\n            this.component.play();\n        }\n    }\n    /**\n     * Pauses the particle system\n     */\n    pause() {\n        if (this.component) {\n            this.component.pause();\n        }\n    }\n    /**\n     * Resets the particle system\n     */\n    reset() {\n        if (this.component) {\n            this.component.reset();\n        }\n    }\n    /**\n     * Stops the particle system\n     */\n    stop() {\n        if (this.component) {\n            this.component.stop();\n        }\n    }\n    static get observedAttributes() {\n        return [\n            ...super.observedAttributes,\n            'asset'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'asset':\n                this.asset = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-particles', ParticleSystemComponentElement);\n\n/**\n * The MaterialElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-material/ | `<pc-material>`} elements.\n * The MaterialElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n */\nclass MaterialElement extends HTMLElement {\n    constructor() {\n        super(...arguments);\n        this._diffuse = new Color(1, 1, 1);\n        this._diffuseMap = '';\n        this._metalnessMap = '';\n        this._normalMap = '';\n        this._roughnessMap = '';\n        this.material = null;\n    }\n    createMaterial() {\n        this.material = new StandardMaterial();\n        this.material.glossInvert = false;\n        this.material.useMetalness = false;\n        this.material.diffuse = this._diffuse;\n        this.diffuseMap = this._diffuseMap;\n        this.metalnessMap = this._metalnessMap;\n        this.normalMap = this._normalMap;\n        this.roughnessMap = this._roughnessMap;\n        this.material.update();\n    }\n    disconnectedCallback() {\n        if (this.material) {\n            this.material.destroy();\n            this.material = null;\n        }\n    }\n    setMap(map, property) {\n        if (this.material) {\n            const asset = AssetElement.get(map);\n            if (asset) {\n                if (asset.loaded) {\n                    this.material[property] = asset.resource;\n                    this.material[property].anisotropy = 4;\n                }\n                else {\n                    asset.once('load', () => {\n                        this.material[property] = asset.resource;\n                        this.material[property].anisotropy = 4;\n                        this.material.update();\n                    });\n                }\n            }\n        }\n    }\n    set diffuse(value) {\n        this._diffuse = value;\n        if (this.material) {\n            this.material.diffuse = value;\n        }\n    }\n    get diffuse() {\n        return this._diffuse;\n    }\n    set diffuseMap(value) {\n        this._diffuseMap = value;\n        this.setMap(value, 'diffuseMap');\n    }\n    get diffuseMap() {\n        return this._diffuseMap;\n    }\n    set metalnessMap(value) {\n        this._metalnessMap = value;\n        this.setMap(value, 'metalnessMap');\n    }\n    get metalnessMap() {\n        return this._metalnessMap;\n    }\n    set normalMap(value) {\n        this._normalMap = value;\n        this.setMap(value, 'normalMap');\n    }\n    get normalMap() {\n        return this._normalMap;\n    }\n    set roughnessMap(value) {\n        this._roughnessMap = value;\n        this.setMap(value, 'glossMap');\n    }\n    get roughnessMap() {\n        return this._roughnessMap;\n    }\n    static get(id) {\n        const materialElement = document.querySelector(`pc-material[id=\"${id}\"]`);\n        return materialElement === null || materialElement === void 0 ? void 0 : materialElement.material;\n    }\n    static get observedAttributes() {\n        return ['diffuse', 'diffuse-map', 'metalness-map', 'normal-map', 'roughness-map'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'diffuse':\n                this.diffuse = parseColor(newValue);\n                break;\n            case 'diffuse-map':\n                this.diffuseMap = newValue;\n                break;\n            case 'metalness-map':\n                this.metalnessMap = newValue;\n                break;\n            case 'normal-map':\n                this.normalMap = newValue;\n                break;\n            case 'roughness-map':\n                this.roughnessMap = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-material', MaterialElement);\n\n/**\n * The RenderComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-render/ | `<pc-render>`} elements.\n * The RenderComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass RenderComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('render');\n        this._castShadows = true;\n        this._material = '';\n        this._receiveShadows = true;\n        this._type = 'asset';\n    }\n    getInitialComponentData() {\n        return {\n            type: this._type,\n            castShadows: this._castShadows,\n            material: MaterialElement.get(this._material),\n            receiveShadows: this._receiveShadows\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas render component.\n     * @returns The render component.\n     */\n    get component() {\n        return super.component;\n    }\n    /**\n     * Sets the type of the render component.\n     * @param value - The type.\n     */\n    set type(value) {\n        this._type = value;\n        if (this.component) {\n            this.component.type = value;\n        }\n    }\n    /**\n     * Gets the type of the render component.\n     * @returns The type.\n     */\n    get type() {\n        return this._type;\n    }\n    /**\n     * Sets the cast shadows flag of the render component.\n     * @param value - The cast shadows flag.\n     */\n    set castShadows(value) {\n        this._castShadows = value;\n        if (this.component) {\n            this.component.castShadows = value;\n        }\n    }\n    /**\n     * Gets the cast shadows flag of the render component.\n     * @returns The cast shadows flag.\n     */\n    get castShadows() {\n        return this._castShadows;\n    }\n    /**\n     * Sets the material of the render component.\n     * @param value - The id of the material asset to use.\n     */\n    set material(value) {\n        this._material = value;\n        if (this.component) {\n            this.component.material = MaterialElement.get(value);\n        }\n    }\n    /**\n     * Gets the id of the material asset used by the render component.\n     * @returns The id of the material asset.\n     */\n    get material() {\n        return this._material;\n    }\n    /**\n     * Sets the receive shadows flag of the render component.\n     * @param value - The receive shadows flag.\n     */\n    set receiveShadows(value) {\n        this._receiveShadows = value;\n        if (this.component) {\n            this.component.receiveShadows = value;\n        }\n    }\n    /**\n     * Gets the receive shadows flag of the render component.\n     * @returns The receive shadows flag.\n     */\n    get receiveShadows() {\n        return this._receiveShadows;\n    }\n    static get observedAttributes() {\n        return [...super.observedAttributes, 'cast-shadows', 'material', 'receive-shadows', 'type'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'cast-shadows':\n                this.castShadows = newValue !== 'false';\n                break;\n            case 'material':\n                this.material = newValue;\n                break;\n            case 'receive-shadows':\n                this.receiveShadows = newValue !== 'false';\n                break;\n            case 'type':\n                this.type = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-render', RenderComponentElement);\n\n/**\n * The RigidBodyComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-rigidbody/ | `<pc-rigidbody>`} elements.\n * The RigidBodyComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass RigidBodyComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('rigidbody');\n        /**\n         * The angular damping of the rigidbody.\n         */\n        this._angularDamping = 0;\n        /**\n         * The angular factor of the rigidbody.\n         */\n        this._angularFactor = new Vec3(1, 1, 1);\n        /**\n         * The friction of the rigidbody.\n         */\n        this._friction = 0.5;\n        /**\n         * The linear damping of the rigidbody.\n         */\n        this._linearDamping = 0;\n        /**\n         * The linear factor of the rigidbody.\n         */\n        this._linearFactor = new Vec3(1, 1, 1);\n        /**\n         * The mass of the rigidbody.\n         */\n        this._mass = 1;\n        /**\n         * The restitution of the rigidbody.\n         */\n        this._restitution = 0;\n        /**\n         * The rolling friction of the rigidbody.\n         */\n        this._rollingFriction = 0;\n        /**\n         * The type of the rigidbody.\n         */\n        this._type = 'static';\n    }\n    getInitialComponentData() {\n        return {\n            angularDamping: this._angularDamping,\n            angularFactor: this._angularFactor,\n            friction: this._friction,\n            linearDamping: this._linearDamping,\n            linearFactor: this._linearFactor,\n            mass: this._mass,\n            restitution: this._restitution,\n            rollingFriction: this._rollingFriction,\n            type: this._type\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas rigidbody component.\n     * @returns The rigidbody component.\n     */\n    get component() {\n        return super.component;\n    }\n    set angularDamping(value) {\n        this._angularDamping = value;\n        if (this.component) {\n            this.component.angularDamping = value;\n        }\n    }\n    get angularDamping() {\n        return this._angularDamping;\n    }\n    set angularFactor(value) {\n        this._angularFactor = value;\n        if (this.component) {\n            this.component.angularFactor = value;\n        }\n    }\n    get angularFactor() {\n        return this._angularFactor;\n    }\n    set friction(value) {\n        this._friction = value;\n        if (this.component) {\n            this.component.friction = value;\n        }\n    }\n    get friction() {\n        return this._friction;\n    }\n    set linearDamping(value) {\n        this._linearDamping = value;\n        if (this.component) {\n            this.component.linearDamping = value;\n        }\n    }\n    get linearDamping() {\n        return this._linearDamping;\n    }\n    set linearFactor(value) {\n        this._linearFactor = value;\n        if (this.component) {\n            this.component.linearFactor = value;\n        }\n    }\n    get linearFactor() {\n        return this._linearFactor;\n    }\n    set mass(value) {\n        this._mass = value;\n        if (this.component) {\n            this.component.mass = value;\n        }\n    }\n    get mass() {\n        return this._mass;\n    }\n    set restitution(value) {\n        this._restitution = value;\n        if (this.component) {\n            this.component.restitution = value;\n        }\n    }\n    get restitution() {\n        return this._restitution;\n    }\n    set rollingFriction(value) {\n        this._rollingFriction = value;\n        if (this.component) {\n            this.component.rollingFriction = value;\n        }\n    }\n    get rollingFriction() {\n        return this._rollingFriction;\n    }\n    set type(value) {\n        this._type = value;\n        if (this.component) {\n            this.component.type = value;\n        }\n    }\n    get type() {\n        return this._type;\n    }\n    static get observedAttributes() {\n        return [...super.observedAttributes, 'angular-damping', 'angular-factor', 'friction', 'linear-damping', 'linear-factor', 'mass', 'restitution', 'rolling-friction', 'type'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'angular-damping':\n                this.angularDamping = parseFloat(newValue);\n                break;\n            case 'angular-factor':\n                this.angularFactor = parseVec3(newValue);\n                break;\n            case 'friction':\n                this.friction = parseFloat(newValue);\n                break;\n            case 'linear-damping':\n                this.linearDamping = parseFloat(newValue);\n                break;\n            case 'linear-factor':\n                this.linearFactor = parseVec3(newValue);\n                break;\n            case 'mass':\n                this.mass = parseFloat(newValue);\n                break;\n            case 'restitution':\n                this.restitution = parseFloat(newValue);\n                break;\n            case 'rolling-friction':\n                this.rollingFriction = parseFloat(newValue);\n                break;\n            case 'type':\n                this.type = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-rigidbody', RigidBodyComponentElement);\n\n/**\n * The ScreenComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-screen/ | `<pc-screen>`} elements.\n * The ScreenComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass ScreenComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('screen');\n        this._screenSpace = false;\n        this._resolution = new Vec2(640, 320);\n        this._referenceResolution = new Vec2(640, 320);\n        this._priority = 0;\n        this._blend = false;\n        this._scaleBlend = 0.5;\n    }\n    getInitialComponentData() {\n        return {\n            priority: this._priority,\n            referenceResolution: this._referenceResolution,\n            resolution: this._resolution,\n            scaleBlend: this._scaleBlend,\n            scaleMode: this._blend ? SCALEMODE_BLEND : SCALEMODE_NONE,\n            screenSpace: this._screenSpace\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas screen component.\n     * @returns The screen component.\n     */\n    get component() {\n        return super.component;\n    }\n    set priority(value) {\n        this._priority = value;\n        if (this.component) {\n            this.component.priority = this._priority;\n        }\n    }\n    get priority() {\n        return this._priority;\n    }\n    set referenceResolution(value) {\n        this._referenceResolution = value;\n        if (this.component) {\n            this.component.referenceResolution = this._referenceResolution;\n        }\n    }\n    get referenceResolution() {\n        return this._referenceResolution;\n    }\n    set resolution(value) {\n        this._resolution = value;\n        if (this.component) {\n            this.component.resolution = this._resolution;\n        }\n    }\n    get resolution() {\n        return this._resolution;\n    }\n    set scaleBlend(value) {\n        this._scaleBlend = value;\n        if (this.component) {\n            this.component.scaleBlend = this._scaleBlend;\n        }\n    }\n    get scaleBlend() {\n        return this._scaleBlend;\n    }\n    set blend(value) {\n        this._blend = value;\n        if (this.component) {\n            this.component.scaleMode = this._blend ? SCALEMODE_BLEND : SCALEMODE_NONE;\n        }\n    }\n    get blend() {\n        return this._blend;\n    }\n    set screenSpace(value) {\n        this._screenSpace = value;\n        if (this.component) {\n            this.component.screenSpace = this._screenSpace;\n        }\n    }\n    get screenSpace() {\n        return this._screenSpace;\n    }\n    static get observedAttributes() {\n        return [\n            ...super.observedAttributes,\n            'blend',\n            'screen-space',\n            'resolution',\n            'reference-resolution',\n            'priority',\n            'scale-blend'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'priority':\n                this.priority = parseInt(newValue, 10);\n                break;\n            case 'reference-resolution':\n                this.referenceResolution = parseVec2(newValue);\n                break;\n            case 'resolution':\n                this.resolution = parseVec2(newValue);\n                break;\n            case 'scale-blend':\n                this.scaleBlend = parseFloat(newValue);\n                break;\n            case 'blend':\n                this.blend = this.hasAttribute('blend');\n                break;\n            case 'screen-space':\n                this.screenSpace = this.hasAttribute('screen-space');\n                break;\n        }\n    }\n}\ncustomElements.define('pc-screen', ScreenComponentElement);\n\n/**\n * The ScriptComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-scripts/ | `<pc-scripts>`} elements.\n * The ScriptComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass ScriptComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('script');\n        // Create mutation observer to watch for child script elements\n        this.observer = new MutationObserver(this.handleMutations.bind(this));\n        this.observer.observe(this, {\n            childList: true\n        });\n        // Listen for script attribute and enable changes\n        this.addEventListener('scriptattributeschange', this.handleScriptAttributesChange.bind(this));\n        this.addEventListener('scriptenablechange', this.handleScriptEnableChange.bind(this));\n    }\n    initComponent() {\n        // Handle initial script elements\n        this.querySelectorAll(':scope > pc-script').forEach((scriptElement) => {\n            const scriptName = scriptElement.getAttribute('name');\n            const attributes = scriptElement.getAttribute('attributes');\n            if (scriptName) {\n                this.createScript(scriptName, attributes);\n            }\n        });\n    }\n    /**\n     * Recursively converts raw attribute data into proper PlayCanvas types. Supported conversions:\n     * - \"asset:assetId\" → resolves to an Asset instance\n     * - \"entity:entityId\" → resolves to an Entity instance\n     * - \"vec2:1,2\" → new Vec2(1,2)\n     * - \"vec3:1,2,3\" → new Vec3(1,2,3)\n     * - \"vec4:1,2,3,4\" → new Vec4(1,2,3,4)\n     * - \"color:1,0.5,0.5,1\" → new Color(1,0.5,0.5,1)\n     * @param item - The item to convert.\n     * @returns The converted item.\n     */\n    convertAttributes(item) {\n        if (typeof item === 'string') {\n            if (item.startsWith('asset:')) {\n                const assetId = item.slice(6);\n                const assetElement = document.querySelector(`pc-asset#${assetId}`);\n                if (assetElement) {\n                    return assetElement.asset;\n                }\n            }\n            if (item.startsWith('entity:')) {\n                const entityId = item.slice(7);\n                const entityElement = document.querySelector(`pc-entity[name=\"${entityId}\"]`);\n                if (entityElement) {\n                    return entityElement.entity;\n                }\n            }\n            if (item.startsWith('vec2:')) {\n                const parts = item.slice(5).split(',').map(Number);\n                if (parts.length === 2 && parts.every(v => !isNaN(v))) {\n                    return new Vec2(parts[0], parts[1]);\n                }\n            }\n            if (item.startsWith('vec3:')) {\n                const parts = item.slice(5).split(',').map(Number);\n                if (parts.length === 3 && parts.every(v => !isNaN(v))) {\n                    return new Vec3(parts[0], parts[1], parts[2]);\n                }\n            }\n            if (item.startsWith('vec4:')) {\n                const parts = item.slice(5).split(',').map(Number);\n                if (parts.length === 4 && parts.every(v => !isNaN(v))) {\n                    return new Vec4(parts[0], parts[1], parts[2], parts[3]);\n                }\n            }\n            if (item.startsWith('color:')) {\n                const parts = item.slice(6).split(',').map(Number);\n                if (parts.length === 4 && parts.every(v => !isNaN(v))) {\n                    return new Color(parts[0], parts[1], parts[2], parts[3]);\n                }\n            }\n            return item;\n        }\n        if (Array.isArray(item)) {\n            // If it's an array of objects, convert each element individually.\n            if (item.length > 0 && typeof item[0] === 'object') {\n                return item.map((el) => this.convertAttributes(el));\n            }\n            // Otherwise, leave the numeric array unchanged but process each element.\n            return item.map((el) => this.convertAttributes(el));\n        }\n        if (item && typeof item === 'object') {\n            const result = {};\n            for (const key in item) {\n                result[key] = this.convertAttributes(item[key]);\n            }\n            return result;\n        }\n        return item;\n    }\n    /**\n     * Preprocess the attributes object by converting its values.\n     * @param attrs - The attributes object to preprocess.\n     * @returns The preprocessed attributes object.\n     */\n    preprocessAttributes(attrs) {\n        return this.convertAttributes(attrs);\n    }\n    /**\n     * Recursively merge properties from source into target.\n     * @param target - The target object to merge into.\n     * @param source - The source object to merge from.\n     * @returns The merged object.\n     */\n    mergeDeep(target, source) {\n        for (const key in source) {\n            if (source[key] &&\n                typeof source[key] === 'object' &&\n                !Array.isArray(source[key])) {\n                if (!target[key] || typeof target[key] !== 'object') {\n                    target[key] = {};\n                }\n                this.mergeDeep(target[key], source[key]);\n            }\n            else {\n                target[key] = source[key];\n            }\n        }\n        return target;\n    }\n    /**\n     * Update script attributes by merging preprocessed values into the script.\n     * @param script - The script to update.\n     * @param attributes - The attributes to merge into the script.\n     */\n    applyAttributes(script, attributes) {\n        try {\n            const attributesObject = attributes ? JSON.parse(attributes) : {};\n            const converted = this.convertAttributes(attributesObject);\n            this.mergeDeep(script, converted);\n        }\n        catch (error) {\n            console.error(`Error parsing attributes JSON string ${attributes}:`, error);\n        }\n    }\n    handleScriptAttributesChange(event) {\n        const scriptElement = event.target;\n        const scriptName = scriptElement.getAttribute('name');\n        if (!scriptName || !this.component)\n            return;\n        const script = this.component.get(scriptName);\n        if (script) {\n            this.applyAttributes(script, event.detail.attributes);\n        }\n    }\n    handleScriptEnableChange(event) {\n        const scriptElement = event.target;\n        const scriptName = scriptElement.getAttribute('name');\n        if (!scriptName || !this.component)\n            return;\n        const script = this.component.get(scriptName);\n        if (script) {\n            script.enabled = event.detail.enabled;\n        }\n    }\n    createScript(name, attributes) {\n        if (!this.component)\n            return null;\n        let attributesObject = {};\n        if (attributes) {\n            try {\n                attributesObject = JSON.parse(attributes);\n                // Preprocess attributes: convert arrays or strings into vectors, colors, asset references, etc.\n                attributesObject = this.preprocessAttributes(attributesObject);\n            }\n            catch (error) {\n                console.error(`Error parsing attributes JSON string ${attributes}:`, error);\n            }\n        }\n        return this.component.create(name, {\n            properties: attributesObject\n        });\n    }\n    destroyScript(name) {\n        if (!this.component)\n            return;\n        this.component.destroy(name);\n    }\n    handleMutations(mutations) {\n        for (const mutation of mutations) {\n            // Handle added nodes\n            mutation.addedNodes.forEach((node) => {\n                if (node instanceof HTMLElement && node.tagName.toLowerCase() === 'pc-script') {\n                    const scriptName = node.getAttribute('name');\n                    const attributes = node.getAttribute('attributes');\n                    if (scriptName) {\n                        this.createScript(scriptName, attributes);\n                    }\n                }\n            });\n            // Handle removed nodes\n            mutation.removedNodes.forEach((node) => {\n                if (node instanceof HTMLElement && node.tagName.toLowerCase() === 'pc-script') {\n                    const scriptName = node.getAttribute('name');\n                    if (scriptName) {\n                        this.destroyScript(scriptName);\n                    }\n                }\n            });\n        }\n    }\n    disconnectedCallback() {\n        var _a;\n        this.observer.disconnect();\n        (_a = super.disconnectedCallback) === null || _a === void 0 ? void 0 : _a.call(this);\n    }\n    /**\n     * Gets the underlying PlayCanvas script component.\n     * @returns The script component.\n     */\n    get component() {\n        return super.component;\n    }\n}\ncustomElements.define('pc-scripts', ScriptComponentElement);\n\n/**\n * The ScriptElement interface provides properties and methods for manipulating\n * `<pc-script>` elements. The ScriptElement interface also inherits the properties and\n * methods of the {@link HTMLElement} interface.\n */\nclass ScriptElement extends HTMLElement {\n    constructor() {\n        super(...arguments);\n        this._attributes = '{}';\n        this._enabled = true;\n        this._name = '';\n    }\n    /**\n     * Sets the attributes of the script.\n     * @param value - The attributes of the script.\n     */\n    set scriptAttributes(value) {\n        this._attributes = value;\n        this.dispatchEvent(new CustomEvent('scriptattributeschange', {\n            detail: { attributes: value },\n            bubbles: true\n        }));\n    }\n    /**\n     * Gets the attributes of the script.\n     * @returns The attributes of the script.\n     */\n    get scriptAttributes() {\n        return this._attributes;\n    }\n    /**\n     * Sets the enabled state of the script.\n     * @param value - The enabled state of the script.\n     */\n    set enabled(value) {\n        this._enabled = value;\n        this.dispatchEvent(new CustomEvent('scriptenablechange', {\n            detail: { enabled: value },\n            bubbles: true\n        }));\n    }\n    /**\n     * Gets the enabled state of the script.\n     * @returns The enabled state of the script.\n     */\n    get enabled() {\n        return this._enabled;\n    }\n    /**\n     * Sets the name of the script to create.\n     * @param value - The name.\n     */\n    set name(value) {\n        this._name = value;\n    }\n    /**\n     * Gets the name of the script.\n     * @returns The name.\n     */\n    get name() {\n        return this._name;\n    }\n    static get observedAttributes() {\n        return ['attributes', 'enabled', 'name'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'attributes':\n                this.scriptAttributes = newValue;\n                break;\n            case 'enabled':\n                this.enabled = newValue !== 'false';\n                break;\n            case 'name':\n                this.name = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-script', ScriptElement);\n\n/**\n * The SoundComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-sounds/ | `<pc-sounds>`} elements.\n * The SoundComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass SoundComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('sound');\n        this._distanceModel = 'linear';\n        this._maxDistance = 10000;\n        this._pitch = 1;\n        this._positional = false;\n        this._refDistance = 1;\n        this._rollOffFactor = 1;\n        this._volume = 1;\n    }\n    getInitialComponentData() {\n        return {\n            distanceModel: this._distanceModel,\n            maxDistance: this._maxDistance,\n            pitch: this._pitch,\n            positional: this._positional,\n            refDistance: this._refDistance,\n            rollOffFactor: this._rollOffFactor,\n            volume: this._volume\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas sound component.\n     * @returns The sound component.\n     */\n    get component() {\n        return super.component;\n    }\n    /**\n     * Sets which algorithm to use to reduce the volume of the sound as it moves away from the listener.\n     * @param value - The distance model.\n     */\n    set distanceModel(value) {\n        this._distanceModel = value;\n        if (this.component) {\n            this.component.distanceModel = value;\n        }\n    }\n    /**\n     * Gets which algorithm to use to reduce the volume of the sound as it moves away from the listener.\n     * @returns The distance model.\n     */\n    get distanceModel() {\n        return this._distanceModel;\n    }\n    /**\n     * Sets the maximum distance from the listener at which audio falloff stops.\n     * @param value - The max distance.\n     */\n    set maxDistance(value) {\n        this._maxDistance = value;\n        if (this.component) {\n            this.component.maxDistance = value;\n        }\n    }\n    /**\n     * Gets the maximum distance from the listener at which audio falloff stops.\n     * @returns The max distance.\n     */\n    get maxDistance() {\n        return this._maxDistance;\n    }\n    /**\n     * Sets the pitch of the sound.\n     * @param value - The pitch.\n     */\n    set pitch(value) {\n        this._pitch = value;\n        if (this.component) {\n            this.component.pitch = value;\n        }\n    }\n    /**\n     * Gets the pitch of the sound.\n     * @returns The pitch.\n     */\n    get pitch() {\n        return this._pitch;\n    }\n    /**\n     * Sets the positional flag of the sound.\n     * @param value - The positional flag.\n     */\n    set positional(value) {\n        this._positional = value;\n        if (this.component) {\n            this.component.positional = value;\n        }\n    }\n    /**\n     * Gets the positional flag of the sound.\n     * @returns The positional flag.\n     */\n    get positional() {\n        return this._positional;\n    }\n    /**\n     * Sets the reference distance for reducing volume as the sound source moves further from the listener. Defaults to 1.\n     * @param value - The ref distance.\n     */\n    set refDistance(value) {\n        this._refDistance = value;\n        if (this.component) {\n            this.component.refDistance = value;\n        }\n    }\n    /**\n     * Gets the reference distance for reducing volume as the sound source moves further from the listener.\n     * @returns The ref distance.\n     */\n    get refDistance() {\n        return this._refDistance;\n    }\n    /**\n     * Sets the factor used in the falloff equation. Defaults to 1.\n     * @param value - The roll-off factor.\n     */\n    set rollOffFactor(value) {\n        this._rollOffFactor = value;\n        if (this.component) {\n            this.component.rollOffFactor = value;\n        }\n    }\n    /**\n     * Gets the factor used in the falloff equation.\n     * @returns The roll-off factor.\n     */\n    get rollOffFactor() {\n        return this._rollOffFactor;\n    }\n    /**\n     * Sets the volume of the sound.\n     * @param value - The volume.\n     */\n    set volume(value) {\n        this._volume = value;\n        if (this.component) {\n            this.component.volume = value;\n        }\n    }\n    /**\n     * Gets the volume of the sound.\n     * @returns The volume.\n     */\n    get volume() {\n        return this._volume;\n    }\n    static get observedAttributes() {\n        return [\n            ...super.observedAttributes,\n            'distance-model',\n            'max-distance',\n            'pitch',\n            'positional',\n            'ref-distance',\n            'roll-off-factor',\n            'volume'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'distance-model':\n                this.distanceModel = newValue;\n                break;\n            case 'max-distance':\n                this.maxDistance = parseFloat(newValue);\n                break;\n            case 'pitch':\n                this.pitch = parseFloat(newValue);\n                break;\n            case 'positional':\n                this.positional = this.hasAttribute('positional');\n                break;\n            case 'ref-distance':\n                this.refDistance = parseFloat(newValue);\n                break;\n            case 'roll-off-factor':\n                this.rollOffFactor = parseFloat(newValue);\n                break;\n            case 'volume':\n                this.volume = parseFloat(newValue);\n                break;\n        }\n    }\n}\ncustomElements.define('pc-sounds', SoundComponentElement);\n\n/**\n * The SoundSlotElement interface provides properties and methods for manipulating\n * `<pc-sound>` elements. The SoundSlotElement interface also inherits the properties and\n * methods of the {@link AsyncElement} interface.\n */\nclass SoundSlotElement extends AsyncElement {\n    constructor() {\n        super(...arguments);\n        this._asset = '';\n        this._autoPlay = false;\n        this._duration = null;\n        this._loop = false;\n        this._name = '';\n        this._overlap = false;\n        this._pitch = 1;\n        this._startTime = 0;\n        this._volume = 1;\n        /**\n         * The sound slot.\n         */\n        this.soundSlot = null;\n    }\n    async connectedCallback() {\n        var _a;\n        await ((_a = this.soundElement) === null || _a === void 0 ? void 0 : _a.ready());\n        const options = {\n            autoPlay: this._autoPlay,\n            loop: this._loop,\n            overlap: this._overlap,\n            pitch: this._pitch,\n            startTime: this._startTime,\n            volume: this._volume\n        };\n        if (this._duration) {\n            options.duration = this._duration;\n        }\n        this.soundSlot = this.soundElement.component.addSlot(this._name, options);\n        this.asset = this._asset;\n        if (this._autoPlay) {\n            this.soundSlot.play();\n        }\n        this._onReady();\n    }\n    disconnectedCallback() {\n        this.soundElement.component.removeSlot(this._name);\n    }\n    get soundElement() {\n        const soundElement = this.parentElement;\n        if (!(soundElement instanceof SoundComponentElement)) {\n            console.warn('pc-sound-slot must be a direct child of a pc-sound element');\n            return null;\n        }\n        return soundElement;\n    }\n    /**\n     * Sets the id of the `pc-asset` to use for the sound slot.\n     * @param value - The asset.\n     */\n    set asset(value) {\n        var _a;\n        this._asset = value;\n        if (this.soundSlot) {\n            const id = (_a = AssetElement.get(value)) === null || _a === void 0 ? void 0 : _a.id;\n            if (id) {\n                this.soundSlot.asset = id;\n            }\n        }\n    }\n    /**\n     * Gets the id of the `pc-asset` to use for the sound slot.\n     * @returns The asset.\n     */\n    get asset() {\n        return this._asset;\n    }\n    /**\n     * Sets the auto play flag of the sound slot.\n     * @param value - The auto play flag.\n     */\n    set autoPlay(value) {\n        this._autoPlay = value;\n        if (this.soundSlot) {\n            this.soundSlot.autoPlay = value;\n        }\n    }\n    /**\n     * Gets the auto play flag of the sound slot.\n     * @returns The auto play flag.\n     */\n    get autoPlay() {\n        return this._autoPlay;\n    }\n    /**\n     * Sets the duration of the sound slot.\n     * @param value - The duration.\n     */\n    set duration(value) {\n        this._duration = value;\n        if (this.soundSlot) {\n            this.soundSlot.duration = value;\n        }\n    }\n    /**\n     * Gets the duration of the sound slot.\n     * @returns The duration.\n     */\n    get duration() {\n        return this._duration;\n    }\n    /**\n     * Sets the loop flag of the sound slot.\n     * @param value - The loop flag.\n     */\n    set loop(value) {\n        this._loop = value;\n        if (this.soundSlot) {\n            this.soundSlot.loop = value;\n        }\n    }\n    /**\n     * Gets the loop flag of the sound slot.\n     * @returns The loop flag.\n     */\n    get loop() {\n        return this._loop;\n    }\n    /**\n     * Sets the name of the sound slot.\n     * @param value - The name.\n     */\n    set name(value) {\n        this._name = value;\n        if (this.soundSlot) {\n            this.soundSlot.name = value;\n        }\n    }\n    /**\n     * Gets the name of the sound slot.\n     * @returns The name.\n     */\n    get name() {\n        return this._name;\n    }\n    /**\n     * Sets the overlap flag of the sound slot.\n     * @param value - The overlap flag.\n     */\n    set overlap(value) {\n        this._overlap = value;\n        if (this.soundSlot) {\n            this.soundSlot.overlap = value;\n        }\n    }\n    /**\n     * Gets the overlap flag of the sound slot.\n     * @returns The overlap flag.\n     */\n    get overlap() {\n        return this._overlap;\n    }\n    /**\n     * Sets the pitch of the sound slot.\n     * @param value - The pitch.\n     */\n    set pitch(value) {\n        this._pitch = value;\n        if (this.soundSlot) {\n            this.soundSlot.pitch = value;\n        }\n    }\n    /**\n     * Gets the pitch of the sound slot.\n     * @returns The pitch.\n     */\n    get pitch() {\n        return this._pitch;\n    }\n    /**\n     * Sets the start time of the sound slot.\n     * @param value - The start time.\n     */\n    set startTime(value) {\n        this._startTime = value;\n        if (this.soundSlot) {\n            this.soundSlot.startTime = value;\n        }\n    }\n    /**\n     * Gets the start time of the sound slot.\n     * @returns The start time.\n     */\n    get startTime() {\n        return this._startTime;\n    }\n    /**\n     * Sets the volume of the sound slot.\n     * @param value - The volume.\n     */\n    set volume(value) {\n        this._volume = value;\n        if (this.soundSlot) {\n            this.soundSlot.volume = value;\n        }\n    }\n    /**\n     * Gets the volume of the sound slot.\n     * @returns The volume.\n     */\n    get volume() {\n        return this._volume;\n    }\n    static get observedAttributes() {\n        return ['asset', 'auto-play', 'duration', 'loop', 'name', 'overlap', 'pitch', 'start-time', 'volume'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'asset':\n                this.asset = newValue;\n                break;\n            case 'auto-play':\n                this.autoPlay = this.hasAttribute('auto-play');\n                break;\n            case 'duration':\n                this.duration = parseFloat(newValue);\n                break;\n            case 'loop':\n                this.loop = this.hasAttribute('loop');\n                break;\n            case 'name':\n                this.name = newValue;\n                break;\n            case 'overlap':\n                this.overlap = this.hasAttribute('overlap');\n                break;\n            case 'pitch':\n                this.pitch = parseFloat(newValue);\n                break;\n            case 'start-time':\n                this.startTime = parseFloat(newValue);\n                break;\n            case 'volume':\n                this.volume = parseFloat(newValue);\n                break;\n        }\n    }\n}\ncustomElements.define('pc-sound', SoundSlotElement);\n\n/**\n * The SplatComponentElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-splat/ | `<pc-splat>`} elements.\n * The SplatComponentElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n *\n * @category Components\n */\nclass SplatComponentElement extends ComponentElement {\n    /** @ignore */\n    constructor() {\n        super('gsplat');\n        this._asset = '';\n        this._castShadows = false;\n    }\n    getInitialComponentData() {\n        return {\n            asset: AssetElement.get(this._asset),\n            castShadows: this._castShadows\n        };\n    }\n    /**\n     * Gets the underlying PlayCanvas splat component.\n     * @returns The splat component.\n     */\n    get component() {\n        return super.component;\n    }\n    /**\n     * Sets id of the `pc-asset` to use for the splat.\n     * @param value - The asset ID.\n     */\n    set asset(value) {\n        this._asset = value;\n        const asset = AssetElement.get(value);\n        if (this.component && asset) {\n            this.component.asset = asset;\n        }\n    }\n    /**\n     * Gets the id of the `pc-asset` to use for the splat.\n     * @returns The asset ID.\n     */\n    get asset() {\n        return this._asset;\n    }\n    /**\n     * Sets whether the splat casts shadows.\n     * @param value - Whether the splat casts shadows.\n     */\n    set castShadows(value) {\n        this._castShadows = value;\n        if (this.component) {\n            this.component.castShadows = value;\n        }\n    }\n    /**\n     * Gets whether the splat casts shadows.\n     * @returns Whether the splat casts shadows.\n     */\n    get castShadows() {\n        return this._castShadows;\n    }\n    static get observedAttributes() {\n        return [\n            ...super.observedAttributes,\n            'asset',\n            'cast-shadows'\n        ];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        super.attributeChangedCallback(name, _oldValue, newValue);\n        switch (name) {\n            case 'asset':\n                this.asset = newValue;\n                break;\n            case 'cast-shadows':\n                this.castShadows = this.hasAttribute('cast-shadows');\n                break;\n        }\n    }\n}\ncustomElements.define('pc-splat', SplatComponentElement);\n\n/**\n * The ModelElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-model/ | `<pc-model>`} elements.\n * The ModelElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n */\nclass ModelElement extends AsyncElement {\n    constructor() {\n        super(...arguments);\n        this._asset = '';\n        this._entity = null;\n    }\n    connectedCallback() {\n        this._loadModel();\n        this._onReady();\n    }\n    disconnectedCallback() {\n        this._unloadModel();\n    }\n    _instantiate(container) {\n        this._entity = container.instantiateRenderEntity();\n        // @ts-ignore\n        if (container.animations.length > 0) {\n            this._entity.addComponent('anim');\n            // @ts-ignore\n            this._entity.anim.assignAnimation('animation', container.animations[0].resource);\n        }\n        const parentEntityElement = this.closestEntity;\n        if (parentEntityElement) {\n            parentEntityElement.ready().then(() => {\n                parentEntityElement.entity.addChild(this._entity);\n            });\n        }\n        else {\n            const appElement = this.closestApp;\n            if (appElement) {\n                appElement.ready().then(() => {\n                    appElement.app.root.addChild(this._entity);\n                });\n            }\n        }\n    }\n    async _loadModel() {\n        var _a;\n        this._unloadModel();\n        const appElement = await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());\n        const app = appElement === null || appElement === void 0 ? void 0 : appElement.app;\n        const asset = AssetElement.get(this._asset);\n        if (!asset) {\n            return;\n        }\n        if (asset.loaded) {\n            this._instantiate(asset.resource);\n        }\n        else {\n            asset.once('load', () => {\n                this._instantiate(asset.resource);\n            });\n            app.assets.load(asset);\n        }\n    }\n    _unloadModel() {\n        var _a;\n        (_a = this._entity) === null || _a === void 0 ? void 0 : _a.destroy();\n        this._entity = null;\n    }\n    /**\n     * Sets the id of the `pc-asset` to use for the model.\n     * @param value - The asset ID.\n     */\n    set asset(value) {\n        this._asset = value;\n        if (this.isConnected) {\n            this._loadModel();\n        }\n    }\n    /**\n     * Gets the id of the `pc-asset` to use for the model.\n     * @returns The asset ID.\n     */\n    get asset() {\n        return this._asset;\n    }\n    static get observedAttributes() {\n        return ['asset'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'asset':\n                this.asset = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-model', ModelElement);\n\n/**\n * The SceneElement interface provides properties and methods for manipulating\n * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-scene/ | `<pc-scene>`} elements.\n * The SceneElement interface also inherits the properties and methods of the\n * {@link HTMLElement} interface.\n */\nclass SceneElement extends AsyncElement {\n    constructor() {\n        super(...arguments);\n        /**\n         * The fog type of the scene.\n         */\n        this._fog = 'none'; // possible values: 'none', 'linear', 'exp', 'exp2'\n        /**\n         * The color of the fog.\n         */\n        this._fogColor = new Color(1, 1, 1);\n        /**\n         * The density of the fog.\n         */\n        this._fogDensity = 0;\n        /**\n         * The start distance of the fog.\n         */\n        this._fogStart = 0;\n        /**\n         * The end distance of the fog.\n         */\n        this._fogEnd = 1000;\n        /**\n         * The gravity of the scene.\n         */\n        this._gravity = new Vec3(0, -9.81, 0);\n        /**\n         * The PlayCanvas scene instance.\n         */\n        this.scene = null;\n    }\n    async connectedCallback() {\n        var _a;\n        await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());\n        this.scene = this.closestApp.app.scene;\n        this.updateSceneSettings();\n        this._onReady();\n    }\n    updateSceneSettings() {\n        if (this.scene) {\n            this.scene.fog.type = this._fog;\n            this.scene.fog.color = this._fogColor;\n            this.scene.fog.density = this._fogDensity;\n            this.scene.fog.start = this._fogStart;\n            this.scene.fog.end = this._fogEnd;\n            const appElement = this.parentElement;\n            appElement.app.systems.rigidbody.gravity.copy(this._gravity);\n        }\n    }\n    /**\n     * Sets the fog type of the scene.\n     * @param value - The fog type.\n     */\n    set fog(value) {\n        this._fog = value;\n        if (this.scene) {\n            this.scene.fog.type = value;\n        }\n    }\n    /**\n     * Gets the fog type of the scene.\n     * @returns The fog type.\n     */\n    get fog() {\n        return this._fog;\n    }\n    /**\n     * Sets the fog color of the scene.\n     * @param value - The fog color.\n     */\n    set fogColor(value) {\n        this._fogColor = value;\n        if (this.scene) {\n            this.scene.fog.color = value;\n        }\n    }\n    /**\n     * Gets the fog color of the scene.\n     * @returns The fog color.\n     */\n    get fogColor() {\n        return this._fogColor;\n    }\n    /**\n     * Sets the fog density of the scene.\n     * @param value - The fog density.\n     */\n    set fogDensity(value) {\n        this._fogDensity = value;\n        if (this.scene) {\n            this.scene.fog.density = value;\n        }\n    }\n    /**\n     * Gets the fog density of the scene.\n     * @returns The fog density.\n     */\n    get fogDensity() {\n        return this._fogDensity;\n    }\n    /**\n     * Sets the fog start distance of the scene.\n     * @param value - The fog start distance.\n     */\n    set fogStart(value) {\n        this._fogStart = value;\n        if (this.scene) {\n            this.scene.fog.start = value;\n        }\n    }\n    /**\n     * Gets the fog start distance of the scene.\n     * @returns The fog start distance.\n     */\n    get fogStart() {\n        return this._fogStart;\n    }\n    /**\n     * Sets the fog end distance of the scene.\n     * @param value - The fog end distance.\n     */\n    set fogEnd(value) {\n        this._fogEnd = value;\n        if (this.scene) {\n            this.scene.fog.end = value;\n        }\n    }\n    /**\n     * Gets the fog end distance of the scene.\n     * @returns The fog end distance.\n     */\n    get fogEnd() {\n        return this._fogEnd;\n    }\n    /**\n     * Sets the gravity of the scene.\n     * @param value - The gravity.\n     */\n    set gravity(value) {\n        this._gravity = value;\n        if (this.scene) {\n            const appElement = this.parentElement;\n            appElement.app.systems.rigidbody.gravity.copy(value);\n        }\n    }\n    /**\n     * Gets the gravity of the scene.\n     * @returns The gravity.\n     */\n    get gravity() {\n        return this._gravity;\n    }\n    static get observedAttributes() {\n        return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'fog':\n                this.fog = newValue;\n                break;\n            case 'fog-color':\n                this.fogColor = parseColor(newValue);\n                break;\n            case 'fog-density':\n                this.fogDensity = parseFloat(newValue);\n                break;\n            case 'fog-start':\n                this.fogStart = parseFloat(newValue);\n                break;\n            case 'fog-end':\n                this.fogEnd = parseFloat(newValue);\n                break;\n            case 'gravity':\n                this.gravity = parseVec3(newValue);\n                break;\n            // ... handle other attributes as well\n        }\n    }\n}\ncustomElements.define('pc-scene', SceneElement);\n\n/**\n * The SkyElement interface provides properties and methods for manipulating\n * `<pc-sky>` elements. The SkyElement interface also inherits the properties and\n * methods of the {@link HTMLElement} interface.\n */\nclass SkyElement extends AsyncElement {\n    constructor() {\n        super(...arguments);\n        this._asset = '';\n        this._center = new Vec3(0, 0.01, 0);\n        this._intensity = 1;\n        this._rotation = new Vec3();\n        this._level = 0;\n        this._lighting = false;\n        this._scale = new Vec3(100, 100, 100);\n        this._type = 'infinite';\n        this._scene = null;\n    }\n    connectedCallback() {\n        this._loadSkybox();\n        this._onReady();\n    }\n    disconnectedCallback() {\n        this._unloadSkybox();\n    }\n    _generateSkybox(asset) {\n        if (!this._scene)\n            return;\n        const source = asset.resource;\n        const skybox = EnvLighting.generateSkyboxCubemap(source);\n        skybox.anisotropy = 4;\n        this._scene.skybox = skybox;\n        if (this._lighting) {\n            const lighting = EnvLighting.generateLightingSource(source);\n            const envAtlas = EnvLighting.generateAtlas(lighting);\n            this._scene.envAtlas = envAtlas;\n        }\n        const layer = this._scene.layers.getLayerById(LAYERID_SKYBOX);\n        if (layer) {\n            layer.enabled = this._type !== 'none';\n        }\n        this._scene.sky.type = this._type;\n        this._scene.sky.node.setLocalScale(this._scale);\n        this._scene.sky.center = this._center;\n        this._scene.skyboxIntensity = this._intensity;\n        this._scene.skyboxMip = this._level;\n    }\n    async _loadSkybox() {\n        var _a;\n        const appElement = await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());\n        const app = appElement === null || appElement === void 0 ? void 0 : appElement.app;\n        if (!app) {\n            return;\n        }\n        const asset = AssetElement.get(this._asset);\n        if (!asset) {\n            return;\n        }\n        this._scene = app.scene;\n        if (asset.loaded) {\n            this._generateSkybox(asset);\n        }\n        else {\n            asset.once('load', () => {\n                this._generateSkybox(asset);\n            });\n            app.assets.load(asset);\n        }\n    }\n    _unloadSkybox() {\n        var _a, _b;\n        if (!this._scene)\n            return;\n        (_a = this._scene.skybox) === null || _a === void 0 ? void 0 : _a.destroy();\n        // @ts-ignore\n        this._scene.skybox = null;\n        (_b = this._scene.envAtlas) === null || _b === void 0 ? void 0 : _b.destroy();\n        // @ts-ignore\n        this._scene.envAtlas = null;\n        this._scene = null;\n    }\n    /**\n     * Sets the id of the `pc-asset` to use for the skybox.\n     * @param value - The asset ID.\n     */\n    set asset(value) {\n        this._asset = value;\n        if (this.isConnected) {\n            this._loadSkybox();\n        }\n    }\n    /**\n     * Gets the id of the `pc-asset` to use for the skybox.\n     * @returns The asset ID.\n     */\n    get asset() {\n        return this._asset;\n    }\n    /**\n     * Sets the center of the skybox.\n     * @param value - The center.\n     */\n    set center(value) {\n        this._center = value;\n        if (this._scene) {\n            this._scene.sky.center = this._center;\n        }\n    }\n    /**\n     * Gets the center of the skybox.\n     * @returns The center.\n     */\n    get center() {\n        return this._center;\n    }\n    /**\n     * Sets the intensity of the skybox.\n     * @param value - The intensity.\n     */\n    set intensity(value) {\n        this._intensity = value;\n        if (this._scene) {\n            this._scene.skyboxIntensity = this._intensity;\n        }\n    }\n    /**\n     * Gets the intensity of the skybox.\n     * @returns The intensity.\n     */\n    get intensity() {\n        return this._intensity;\n    }\n    /**\n     * Sets the mip level of the skybox.\n     * @param value - The mip level.\n     */\n    set level(value) {\n        this._level = value;\n        if (this._scene) {\n            this._scene.skyboxMip = this._level;\n        }\n    }\n    /**\n     * Gets the mip level of the skybox.\n     * @returns The mip level.\n     */\n    get level() {\n        return this._level;\n    }\n    /**\n     * Sets whether the skybox is used as a light source.\n     * @param value - Whether to use lighting.\n     */\n    set lighting(value) {\n        this._lighting = value;\n    }\n    /**\n     * Gets whether the skybox is used as a light source.\n     * @returns Whether to use lighting.\n     */\n    get lighting() {\n        return this._lighting;\n    }\n    /**\n     * Sets the Euler rotation of the skybox.\n     * @param value - The rotation.\n     */\n    set rotation(value) {\n        this._rotation = value;\n        if (this._scene) {\n            this._scene.skyboxRotation = new Quat().setFromEulerAngles(value);\n        }\n    }\n    /**\n     * Gets the Euler rotation of the skybox.\n     * @returns The rotation.\n     */\n    get rotation() {\n        return this._rotation;\n    }\n    /**\n     * Sets the scale of the skybox.\n     * @param value - The scale.\n     */\n    set scale(value) {\n        this._scale = value;\n        if (this._scene) {\n            this._scene.sky.node.setLocalScale(this._scale);\n        }\n    }\n    /**\n     * Gets the scale of the skybox.\n     * @returns The scale.\n     */\n    get scale() {\n        return this._scale;\n    }\n    /**\n     * Sets the type of the skybox.\n     * @param value - The type.\n     */\n    set type(value) {\n        this._type = value;\n        if (this._scene) {\n            this._scene.sky.type = this._type;\n            const layer = this._scene.layers.getLayerById(LAYERID_SKYBOX);\n            if (layer) {\n                layer.enabled = this._type !== 'none';\n            }\n        }\n    }\n    /**\n     * Gets the type of the skybox.\n     * @returns The type.\n     */\n    get type() {\n        return this._type;\n    }\n    static get observedAttributes() {\n        return ['asset', 'center', 'intensity', 'level', 'lighting', 'rotation', 'scale', 'type'];\n    }\n    attributeChangedCallback(name, _oldValue, newValue) {\n        switch (name) {\n            case 'asset':\n                this.asset = newValue;\n                break;\n            case 'center':\n                this.center = parseVec3(newValue);\n                break;\n            case 'intensity':\n                this.intensity = parseFloat(newValue);\n                break;\n            case 'level':\n                this.level = parseInt(newValue, 10);\n                break;\n            case 'lighting':\n                this.lighting = this.hasAttribute(name);\n                break;\n            case 'rotation':\n                this.rotation = parseVec3(newValue);\n                break;\n            case 'scale':\n                this.scale = parseVec3(newValue);\n                break;\n            case 'type':\n                this.type = newValue;\n                break;\n        }\n    }\n}\ncustomElements.define('pc-sky', SkyElement);\n\nclass XrControllers extends Script {\n    static scriptName = 'xrControllers';\n\n    /**\n     * The base URL for fetching the WebXR input profiles.\n     *\n     * @attribute\n     * @type {string}\n     */\n    basePath = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets/dist/profiles';\n\n    controllers = new Map();\n\n    initialize() {\n        if (!this.app.xr) {\n            console.error('XrControllers script requires XR to be enabled on the application');\n            return;\n        }\n\n        this.app.xr.input.on('add', async (inputSource) => {\n            if (!inputSource.profiles?.length) {\n                console.warn('No profiles available for input source');\n                return;\n            }\n\n            // Process all profiles concurrently\n            const profilePromises = inputSource.profiles.map(async (profileId) => {\n                const profileUrl = `${this.basePath}/${profileId}/profile.json`;\n\n                try {\n                    const response = await fetch(profileUrl);\n                    if (!response.ok) {\n                        return null;\n                    }\n\n                    const profile = await response.json();\n                    const layoutPath = profile.layouts[inputSource.handedness]?.assetPath || '';\n                    const assetPath = `${this.basePath}/${profile.profileId}/${inputSource.handedness}${layoutPath.replace(/^\\/?(left|right)/, '')}`;\n\n                    // Load the model\n                    const asset = await new Promise((resolve, reject) => {\n                        this.app.assets.loadFromUrl(assetPath, 'container', (err, asset) => {\n                            if (err) reject(err);\n                            else resolve(asset);\n                        });\n                    });\n\n                    return { profileId, asset };\n                } catch (error) {\n                    console.warn(`Failed to process profile ${profileId}`);\n                    return null;\n                }\n            });\n\n            // Wait for all profile attempts to complete\n            const results = await Promise.all(profilePromises);\n            const successfulResult = results.find(result => result !== null);\n\n            if (successfulResult) {\n                const { asset } = successfulResult;\n                const container = asset.resource;\n                const entity = container.instantiateRenderEntity();\n                this.app.root.addChild(entity);\n\n                const jointMap = new Map();\n                if (inputSource.hand) {\n                    for (const joint of inputSource.hand.joints) {\n                        const jointEntity = entity.findByName(joint.id);\n                        if (jointEntity) {\n                            jointMap.set(joint, jointEntity);\n                        }\n                    }\n                }\n\n                this.controllers.set(inputSource, { entity, jointMap });\n            } else {\n                console.warn('No compatible profiles found');\n            }\n        });\n\n        this.app.xr.input.on('remove', (inputSource) => {\n            const controller = this.controllers.get(inputSource);\n            if (controller) {\n                controller.entity.destroy();\n                this.controllers.delete(inputSource);\n            }\n        });\n    }\n\n    update(dt) {\n        if (this.app.xr?.active) {\n            for (const [inputSource, { entity, jointMap }] of this.controllers) {\n                if (inputSource.hand) {\n                    for (const [joint, jointEntity] of jointMap) {\n                        jointEntity.setPosition(joint.getPosition());\n                        jointEntity.setRotation(joint.getRotation());\n                    }\n                } else {\n                    entity.setPosition(inputSource.getPosition());\n                    entity.setRotation(inputSource.getRotation());\n                }\n            }\n        }\n    }\n}\n\n/** @import { XrInputSource } from 'playcanvas' */\n\n/**\n * Handles VR teleportation navigation by allowing users to point and teleport using either\n * hands or tracked controllers. Shows a visual ray and target indicator when the user holds\n * the select button (trigger) or makes a pinch gesture with hand tracking, and teleports to\n * the target location when released.\n *\n * This script should be attached to a parent entity of the camera entity used for the XR\n * session. Use it in conjunction with the `XrControllers` script to handle the rendering of\n * the controllers.\n */\nclass XrNavigation extends Script {\n    static scriptName = 'xrNavigation';\n\n    /** @type {Set<XrInputSource>} */\n    inputSources = new Set();\n\n    /** @type {Map<XrInputSource, boolean>} */\n    activePointers = new Map();\n\n    validColor = new Color(0, 1, 0);    // Green for valid teleport\n\n    invalidColor = new Color(1, 0, 0);   // Red for invalid teleport\n\n    /** @type {Map<XrInputSource, { handleSelectStart: Function, handleSelectEnd: Function }>} */\n    inputHandlers = new Map();\n\n    initialize() {\n        if (!this.app.xr) {\n            console.error('XrNavigation script requires XR to be enabled on the application');\n            return;\n        }\n\n        this.app.xr.input.on('add', (inputSource) => {\n            const handleSelectStart = () => {\n                this.activePointers.set(inputSource, true);\n            };\n\n            const handleSelectEnd = () => {\n                this.activePointers.set(inputSource, false);\n                this.tryTeleport(inputSource);\n            };\n\n            // Attach the handlers\n            inputSource.on('selectstart', handleSelectStart);\n            inputSource.on('selectend', handleSelectEnd);\n\n            // Store the handlers in the map\n            this.inputHandlers.set(inputSource, { handleSelectStart, handleSelectEnd });\n            this.inputSources.add(inputSource);\n        });\n\n        this.app.xr.input.on('remove', (inputSource) => {\n            const handlers = this.inputHandlers.get(inputSource);\n            if (handlers) {\n                inputSource.off('selectstart', handlers.handleSelectStart);\n                inputSource.off('selectend', handlers.handleSelectEnd);\n                this.inputHandlers.delete(inputSource);\n            }\n            this.activePointers.delete(inputSource);\n            this.inputSources.delete(inputSource);\n        });\n    }\n\n    findPlaneIntersection(origin, direction) {\n        // Find intersection with y=0 plane\n        if (Math.abs(direction.y) < 0.00001) return null;  // Ray is parallel to plane\n\n        const t = -origin.y / direction.y;\n        if (t < 0) return null;  // Intersection is behind the ray\n\n        return new Vec3(\n            origin.x + direction.x * t,\n            0,\n            origin.z + direction.z * t\n        );\n    }\n\n    tryTeleport(inputSource) {\n        const origin = inputSource.getOrigin();\n        const direction = inputSource.getDirection();\n\n        const hitPoint = this.findPlaneIntersection(origin, direction);\n        if (hitPoint) {\n            const cameraY = this.entity.getPosition().y;\n            hitPoint.y = cameraY;\n            this.entity.setPosition(hitPoint);\n        }\n    }\n\n    update() {\n        for (const inputSource of this.inputSources) {\n            // Only show ray when trigger is pressed\n            if (!this.activePointers.get(inputSource)) continue;\n\n            const start = inputSource.getOrigin();\n            const direction = inputSource.getDirection();\n\n            const hitPoint = this.findPlaneIntersection(start, direction);\n\n            if (hitPoint) {\n                // Draw line to intersection point\n                this.app.drawLine(start, hitPoint, this.validColor);\n                this.drawTeleportIndicator(hitPoint);\n            } else {\n                // Draw full length ray if no intersection\n                const end = start.clone().add(\n                    direction.clone().mulScalar(100)\n                );\n                this.app.drawLine(start, end, this.invalidColor);\n            }\n        }\n    }\n\n    drawTeleportIndicator(point) {\n        // Draw a circle at the teleport point\n        const segments = 32;\n        const radius = 0.2;\n\n        for (let i = 0; i < segments; i++) {\n            const angle1 = (i / segments) * Math.PI * 2;\n            const angle2 = ((i + 1) / segments) * Math.PI * 2;\n\n            const x1 = point.x + Math.cos(angle1) * radius;\n            const z1 = point.z + Math.sin(angle1) * radius;\n            const x2 = point.x + Math.cos(angle2) * radius;\n            const z2 = point.z + Math.sin(angle2) * radius;\n\n            this.app.drawLine(\n                new Vec3(x1, 0.01, z1),  // Slightly above ground to avoid z-fighting\n                new Vec3(x2, 0.01, z2),\n                this.validColor\n            );\n        }\n    }\n}\n\nconst migrateAnimTrack = (track) => {\n    if (!track.frameRate) {\n        const defaultFrameRate = 30;\n        track.frameRate = defaultFrameRate;\n        const times = track.keyframes.times;\n        for (let i = 0; i < times.length; i++) {\n            times[i] *= defaultFrameRate;\n        }\n    }\n    // smoothness property added in v1.4.0\n    if (!track.hasOwnProperty('smoothness')) {\n        track.smoothness = 0;\n    }\n};\nconst migrateSettings = (settings) => {\n    settings.animTracks?.forEach((track) => {\n        migrateAnimTrack(track);\n    });\n    return settings;\n};\n\n// creates an observer proxy object to wrap some target object. fires events when properties change.\nconst observe = (events, target) => {\n    const members = new Set(Object.keys(target));\n    return new Proxy(target, {\n        set(target, property, value, receiver) {\n            // prevent setting symbol properties\n            if (typeof property === 'symbol') {\n                console.error('Cannot set symbol property on target');\n                return false;\n            }\n            // not allowed to set a new value on target\n            if (!members.has(property)) {\n                console.error('Cannot set new property on target');\n                return false;\n            }\n            // set and fire event if value changed\n            if (target[property] !== value) {\n                const prev = target[property];\n                target[property] = value;\n                events.fire(`${property}:changed`, value, prev);\n            }\n            return true;\n        }\n    });\n};\n\n/**\n * Damping function to smooth out transitions.\n *\n * @param damping - Damping factor (0 < damping < 1).\n * @param dt - Delta time in seconds.\n * @returns - Damping factor adjusted for the delta time.\n */\n/**\n * Easing function for smooth transitions.\n *\n * @param x - Input value in the range [0, 1].\n * @returns - Output value in the range [0, 1].\n */\nconst easeOut = (x) => (1 - (2 ** (-10 * x))) / (1 - (2 ** -10));\n/**\n * Modulus function that handles negative values correctly.\n *\n * @param n - The number to be modulated.\n * @param m - The modulus value.\n * @returns - The result of n mod m, adjusted to be non-negative.\n */\nconst mod = (n, m) => ((n % m) + m) % m;\n\nclass CubicSpline {\n    // control times\n    times;\n    // control data: in-tangent, point, out-tangent\n    knots;\n    // dimension of the knot points\n    dim;\n    constructor(times, knots) {\n        this.times = times;\n        this.knots = knots;\n        this.dim = knots.length / times.length / 3;\n    }\n    evaluate(time, result) {\n        const { times } = this;\n        const last = times.length - 1;\n        if (time <= times[0]) {\n            this.getKnot(0, result);\n        }\n        else if (time >= times[last]) {\n            this.getKnot(last, result);\n        }\n        else {\n            let seg = 0;\n            while (time >= times[seg + 1]) {\n                seg++;\n            }\n            this.evaluateSegment(seg, (time - times[seg]) / (times[seg + 1] - times[seg]), result);\n        }\n    }\n    getKnot(index, result) {\n        const { knots, dim } = this;\n        const idx = index * 3 * dim;\n        for (let i = 0; i < dim; ++i) {\n            result[i] = knots[idx + i * 3 + 1];\n        }\n    }\n    // evaluate the spline segment at the given normalized time t\n    evaluateSegment(segment, t, result) {\n        const { knots, dim } = this;\n        const t2 = t * t;\n        const twot = t + t;\n        const omt = 1 - t;\n        const omt2 = omt * omt;\n        let idx = segment * dim * 3; // each knot has 3 values: tangent in, value, tangent out\n        for (let i = 0; i < dim; ++i) {\n            const p0 = knots[idx + 1]; // p0\n            const m0 = knots[idx + 2]; // outgoing tangent\n            const m1 = knots[idx + dim * 3]; // incoming tangent\n            const p1 = knots[idx + dim * 3 + 1]; // p1\n            idx += 3;\n            result[i] =\n                p0 * ((1 + twot) * omt2) +\n                    m0 * (t * omt2) +\n                    p1 * (t2 * (3 - twot)) +\n                    m1 * (t2 * (t - 1));\n        }\n    }\n    // calculate cubic spline knots from points\n    // times: time values for each control point\n    // points: control point values to be interpolated (n dimensional)\n    // smoothness: 0 = linear, 1 = smooth\n    static calcKnots(times, points, smoothness) {\n        const n = times.length;\n        const dim = points.length / n;\n        const knots = new Array(n * dim * 3);\n        for (let i = 0; i < n; i++) {\n            const t = times[i];\n            for (let j = 0; j < dim; j++) {\n                const idx = i * dim + j;\n                const p = points[idx];\n                let tangent;\n                if (i === 0) {\n                    tangent = (points[idx + dim] - p) / (times[i + 1] - t);\n                }\n                else if (i === n - 1) {\n                    tangent = (p - points[idx - dim]) / (t - times[i - 1]);\n                }\n                else {\n                    tangent = (points[idx + dim] - points[idx - dim]) / (times[i + 1] - times[i - 1]);\n                }\n                // convert to derivatives w.r.t normalized segment parameter\n                const inScale = i > 0 ? (times[i] - times[i - 1]) : (times[1] - times[0]);\n                const outScale = i < n - 1 ? (times[i + 1] - times[i]) : (times[i] - times[i - 1]);\n                knots[idx * 3] = tangent * inScale * smoothness;\n                knots[idx * 3 + 1] = p;\n                knots[idx * 3 + 2] = tangent * outScale * smoothness;\n            }\n        }\n        return knots;\n    }\n    static fromPoints(times, points, smoothness = 1) {\n        return new CubicSpline(times, CubicSpline.calcKnots(times, points, smoothness));\n    }\n    // create a looping spline by duplicating animation points at the end and beginning\n    static fromPointsLooping(length, times, points, smoothness = 1) {\n        if (times.length < 2) {\n            return CubicSpline.fromPoints(times, points);\n        }\n        const dim = points.length / times.length;\n        const newTimes = times.slice();\n        const newPoints = points.slice();\n        // append first two points\n        newTimes.push(length + times[0], length + times[1]);\n        newPoints.push(...points.slice(0, dim * 2));\n        // prepend last two points\n        newTimes.splice(0, 0, times[times.length - 2] - length, times[times.length - 1] - length);\n        newPoints.splice(0, 0, ...points.slice(points.length - dim * 2));\n        return CubicSpline.fromPoints(newTimes, newPoints, smoothness);\n    }\n}\n\n// track an animation cursor with support for looping and ping-pong modes\nclass AnimCursor {\n    duration = 0;\n    loopMode = 'none';\n    timer = 0;\n    cursor = 0;\n    constructor(duration, loopMode) {\n        this.reset(duration, loopMode);\n    }\n    update(deltaTime) {\n        // update animation timer\n        this.timer += deltaTime;\n        // update the track cursor\n        this.cursor += deltaTime;\n        if (this.cursor >= this.duration) {\n            switch (this.loopMode) {\n                case 'none':\n                    this.cursor = this.duration;\n                    break;\n                case 'repeat':\n                    this.cursor %= this.duration;\n                    break;\n                case 'pingpong':\n                    this.cursor %= (this.duration * 2);\n                    break;\n            }\n        }\n    }\n    reset(duration, loopMode) {\n        this.duration = duration;\n        this.loopMode = loopMode;\n        this.timer = 0;\n        this.cursor = 0;\n    }\n    set value(value) {\n        this.cursor = mod(value, this.duration);\n    }\n    get value() {\n        return this.cursor > this.duration ? this.duration - this.cursor : this.cursor;\n    }\n}\n// Manage the state of a camera animation track\nclass AnimController extends InputController {\n    spline;\n    cursor = new AnimCursor(0, 'none');\n    frameRate;\n    result = [];\n    position = new Vec3();\n    target = new Vec3();\n    constructor(spline, duration, loopMode, frameRate) {\n        super();\n        this.spline = spline;\n        this.cursor.reset(duration, loopMode);\n        this.frameRate = frameRate;\n    }\n    /**\n     * @param frame - The input frame.\n     * @param dt - The delta time.\n     * @returns - The controller pose.\n     */\n    update(frame, dt) {\n        // discard frame\n        frame.read();\n        const { cursor, result, spline, frameRate, position, target } = this;\n        // update the animation cursor\n        cursor.update(dt);\n        // evaluate the spline\n        spline.evaluate(cursor.value * frameRate, result);\n        if (result.every(isFinite)) {\n            position.set(result[0], result[1], result[2]);\n            target.set(result[3], result[4], result[5]);\n        }\n        // update pose\n        return this._pose.look(position, target);\n    }\n    // construct an animation from a settings track\n    static fromTrack(track) {\n        const { keyframes, duration, frameRate, loopMode, smoothness } = track;\n        const { times, values } = keyframes;\n        const { position, target } = values;\n        // construct the points array containing position and target\n        const points = [];\n        for (let i = 0; i < times.length; i++) {\n            points.push(position[i * 3], position[i * 3 + 1], position[i * 3 + 2]);\n            points.push(target[i * 3], target[i * 3 + 1], target[i * 3 + 2]);\n        }\n        const extra = (duration === times[times.length - 1] / frameRate) ? 1 : 0;\n        const spline = CubicSpline.fromPointsLooping((duration + extra) * frameRate, times, points, smoothness);\n        return new AnimController(spline, duration, loopMode, frameRate);\n    }\n}\n\nconst tmpV1 = new Vec3();\nconst tmpV2 = new Vec3();\n/**\n * Converts screen space mouse deltas to world space pan vector.\n *\n * @param camera - The camera component.\n * @param dx - The mouse delta x value.\n * @param dy - The mouse delta y value.\n * @param dz - The world space zoom delta value.\n * @param out - The output vector to store the pan result.\n * @returns - The pan vector in world space.\n * @private\n */\nconst screenToWorld = (camera, dx, dy, dz, out = new Vec3()) => {\n    const { system, fov, aspectRatio, horizontalFov, projection, orthoHeight } = camera;\n    const { width, height } = system.app.graphicsDevice.clientRect;\n    // normalize deltas to device coord space\n    out.set(-(dx / width) * 2, (dy / height) * 2, 0);\n    // calculate half size of the view frustum at the current distance\n    const halfSize = tmpV2.set(0, 0, 0);\n    if (projection === PROJECTION_PERSPECTIVE) {\n        const halfSlice = dz * Math.tan(0.5 * fov * math.DEG_TO_RAD);\n        if (horizontalFov) {\n            halfSize.set(halfSlice, halfSlice / aspectRatio, 0);\n        }\n        else {\n            halfSize.set(halfSlice * aspectRatio, halfSlice, 0);\n        }\n    }\n    else {\n        halfSize.set(orthoHeight * aspectRatio, orthoHeight, 0);\n    }\n    // scale by device coord space\n    out.mul(halfSize);\n    return out;\n};\nclass AppController {\n    _camera;\n    _state = {\n        axis: new Vec3(),\n        mouse: [0, 0, 0],\n        shift: 0,\n        ctrl: 0,\n        touches: 0\n    };\n    _desktopInput = new KeyboardMouseSource();\n    _orbitInput = new MultiTouchSource();\n    _flyInput = new DualGestureSource();\n    _gamepadInput = new GamepadSource();\n    frame = new InputFrame({\n        move: [0, 0, 0],\n        rotate: [0, 0, 0]\n    });\n    joystick = { base: null, stick: null };\n    // this gets overridden by the viewer based on scene size\n    moveSpeed = 1;\n    orbitSpeed = 18;\n    pinchSpeed = 0.4;\n    wheelSpeed = 0.06;\n    /**\n     * @param element - the element to attach the input to\n     * @param camera - the camera component to control\n     */\n    constructor(element, camera) {\n        this._desktopInput.attach(element);\n        this._orbitInput.attach(element);\n        this._flyInput.attach(element);\n        this._camera = camera;\n        // convert events to joystick state\n        this._flyInput.on('joystick:position:left', ([bx, by, sx, sy]) => {\n            if (bx < 0 || by < 0 || sx < 0 || sy < 0) {\n                this.joystick.base = null;\n                this.joystick.stick = null;\n                return;\n            }\n            this.joystick.base = [bx, by];\n            this.joystick.stick = [sx - bx, sy - by];\n        });\n    }\n    /**\n     * @param dt - delta time in seconds\n     * @param state - the current state of the app\n     * @param state.cameraMode - the current camera mode\n     * @param distance - the distance to the camera target\n     */\n    update(dt, state, distance) {\n        const { keyCode } = KeyboardMouseSource;\n        const { key, button, mouse, wheel } = this._desktopInput.read();\n        const { touch, pinch, count } = this._orbitInput.read();\n        const { leftInput, rightInput } = this._flyInput.read();\n        const { leftStick, rightStick } = this._gamepadInput.read();\n        // update state\n        this._state.axis.add(tmpV1.set((key[keyCode.D] - key[keyCode.A]) + (key[keyCode.RIGHT] - key[keyCode.LEFT]), (key[keyCode.E] - key[keyCode.Q]), (key[keyCode.W] - key[keyCode.S]) + (key[keyCode.UP] - key[keyCode.DOWN])));\n        this._state.touches += count[0];\n        for (let i = 0; i < button.length; i++) {\n            this._state.mouse[i] += button[i];\n        }\n        this._state.shift += key[keyCode.SHIFT];\n        this._state.ctrl += key[keyCode.CTRL];\n        if (state.cameraMode !== 'fly' && this._state.axis.length() > 0) {\n            state.cameraMode = 'fly';\n        }\n        const orbit = +(state.cameraMode === 'orbit');\n        const fly = +(state.cameraMode === 'fly');\n        const double = +(this._state.touches > 1);\n        const pan = this._state.mouse[2] || +(button[2] === -1) || double;\n        const orbitFactor = fly ? this._camera.fov / 120 : 1;\n        const { deltas } = this.frame;\n        // desktop move\n        const v = tmpV1.set(0, 0, 0);\n        const keyMove = this._state.axis.clone().normalize();\n        v.add(keyMove.mulScalar(fly * this.moveSpeed * (this._state.shift ? 4 : this._state.ctrl ? 0.25 : 1) * dt));\n        const panMove = screenToWorld(this._camera, mouse[0], mouse[1], distance);\n        v.add(panMove.mulScalar(pan));\n        const wheelMove = new Vec3(0, 0, -wheel[0]);\n        v.add(wheelMove.mulScalar(this.wheelSpeed * dt));\n        // FIXME: need to flip z axis for orbit camera\n        deltas.move.append([v.x, v.y, orbit ? -v.z : v.z]);\n        // desktop rotate\n        v.set(0, 0, 0);\n        const mouseRotate = new Vec3(mouse[0], mouse[1], 0);\n        v.add(mouseRotate.mulScalar((1 - pan) * this.orbitSpeed * orbitFactor * dt));\n        deltas.rotate.append([v.x, v.y, v.z]);\n        // mobile move\n        v.set(0, 0, 0);\n        const orbitMove = screenToWorld(this._camera, touch[0], touch[1], distance);\n        v.add(orbitMove.mulScalar(orbit * pan));\n        const flyMove = new Vec3(leftInput[0], 0, -leftInput[1]);\n        v.add(flyMove.mulScalar(fly * this.moveSpeed * dt));\n        const pinchMove = new Vec3(0, 0, pinch[0]);\n        v.add(pinchMove.mulScalar(orbit * double * this.pinchSpeed * dt));\n        deltas.move.append([v.x, v.y, v.z]);\n        // mobile rotate\n        v.set(0, 0, 0);\n        const orbitRotate = new Vec3(touch[0], touch[1], 0);\n        v.add(orbitRotate.mulScalar(orbit * (1 - pan) * this.orbitSpeed * dt));\n        const flyRotate = new Vec3(rightInput[0], rightInput[1], 0);\n        v.add(flyRotate.mulScalar(fly * this.orbitSpeed * orbitFactor * dt));\n        deltas.rotate.append([v.x, v.y, v.z]);\n        // gamepad move\n        v.set(0, 0, 0);\n        const stickMove = new Vec3(leftStick[0], 0, -leftStick[1]);\n        v.add(stickMove.mulScalar(this.moveSpeed * dt));\n        deltas.move.append([v.x, v.y, v.z]);\n        // gamepad rotate\n        v.set(0, 0, 0);\n        const stickRotate = new Vec3(rightStick[0], rightStick[1], 0);\n        v.add(stickRotate.mulScalar(this.orbitSpeed * orbitFactor * dt));\n        deltas.rotate.append([v.x, v.y, v.z]);\n    }\n}\n\nconst float32 = new Float32Array(1);\nconst uint8 = new Uint8Array(float32.buffer);\nconst two = new Vec4(2, 2, 2, 1);\nconst one = new Vec4(1, 1, 1, 0);\nclass Picker {\n    app;\n    camera;\n    picker;\n    constructor(app, camera) {\n        this.app = app;\n        this.camera = camera;\n        this.picker = null;\n    }\n    async pick(x, y) {\n        const { app, camera } = this;\n        const { graphicsDevice } = app;\n        const { canvas } = graphicsDevice;\n        const width = canvas.clientWidth;\n        const height = canvas.clientHeight;\n        y = graphicsDevice.isWebGL2 ? height - y - 1 : y;\n        // construct picker on demand\n        if (!this.picker) {\n            this.picker = new Picker$1(this.app, width, height);\n        }\n        // render scene, read depth\n        const { picker } = this;\n        picker.resize(width, height);\n        picker.prepare(camera.camera, app.scene, [app.scene.layers.getLayerByName('World')]);\n        const pixels = await picker.renderTarget.colorBuffer.read(x, y, 1, 1, {\n            renderTarget: picker.renderTarget\n        });\n        for (let i = 0; i < 4; ++i) {\n            uint8[i] = pixels[i];\n        }\n        const depth = float32[0];\n        // 255, 255, 255, 255 === NaN\n        if (!isFinite(depth)) {\n            return null;\n        }\n        // clip space\n        const pos = new Vec4(x / width, y / height, depth, 1).mul(two).sub(one);\n        if (!graphicsDevice.isWebGL2) {\n            pos.y *= -1;\n        }\n        // homogeneous view space\n        camera.camera.projectionMatrix.clone().invert().transformVec4(pos, pos);\n        // perform perspective divide\n        pos.mulScalar(1.0 / pos.w);\n        // view to world space\n        const pos3 = new Vec3(pos.x, pos.y, pos.z);\n        camera.getWorldTransform().transformPoint(pos3, pos3);\n        return pos3;\n    }\n}\n\nconst vecToAngles = (result, vec) => {\n    const radToDeg = 180 / Math.PI;\n    result.x = Math.asin(vec.y) * radToDeg;\n    result.y = Math.atan2(-vec.x, -vec.z) * radToDeg;\n    result.z = 0;\n    return result;\n};\nconst pose = new Pose();\nconst avec = new Vec3();\nconst bvec = new Vec3();\n// lerp between two poses\nconst lerpPose = (result, a, b, t) => {\n    // lerp camera position\n    result.position.lerp(a.position, b.position, t);\n    // lerp focus point and subtract from camera position\n    a.getFocus(avec);\n    b.getFocus(bvec);\n    avec.lerp(avec, bvec, t).sub(result.position);\n    // calculate distance\n    result.distance = avec.length();\n    // set angles\n    vecToAngles(result.angles, avec.mulScalar(1.0 / result.distance));\n};\n/**\n * Creates a rotation animation track\n *\n * @param initial - The initial pose of the camera.\n * @param keys - The number of keys in the animation.\n * @param duration - The duration of the animation in seconds.\n * @returns - The animation track object containing position and target keyframes.\n */\nconst createRotateTrack = (initial, keys = 12, duration = 20) => {\n    const times = new Array(keys).fill(0).map((_, i) => i / keys * duration);\n    const position = [];\n    const target = [];\n    const initialTarget = new Vec3();\n    initial.getFocus(initialTarget);\n    const mat = new Mat4();\n    const vec = new Vec3();\n    const dif = new Vec3(initial.position.x - initialTarget.x, initial.position.y - initialTarget.y, initial.position.z - initialTarget.z);\n    for (let i = 0; i < keys; ++i) {\n        mat.setFromEulerAngles(0, -i / keys * 360, 0);\n        mat.transformPoint(dif, vec);\n        position.push(initialTarget.x + vec.x);\n        position.push(initialTarget.y + vec.y);\n        position.push(initialTarget.z + vec.z);\n        target.push(initialTarget.x);\n        target.push(initialTarget.y);\n        target.push(initialTarget.z);\n    }\n    return {\n        name: 'rotate',\n        duration,\n        frameRate: 1,\n        target: 'camera',\n        loopMode: 'repeat',\n        interpolation: 'spline',\n        smoothness: 1,\n        keyframes: {\n            times,\n            values: {\n                position,\n                target\n            }\n        }\n    };\n};\nclass Viewer {\n    app;\n    entity;\n    events;\n    state;\n    settings;\n    constructor(app, entity, events, state, settings, params) {\n        const { background, camera } = settings;\n        const { graphicsDevice } = app;\n        this.app = app;\n        this.entity = entity;\n        this.events = events;\n        this.state = state;\n        this.settings = settings;\n        // disable auto render, we'll render only when camera changes\n        app.autoRender = false;\n        // apply camera animation settings\n        entity.camera.clearColor = new Color(background.color);\n        entity.camera.fov = camera.fov;\n        // handle horizontal fov on canvas resize\n        const updateHorizontalFov = () => {\n            this.entity.camera.horizontalFov = graphicsDevice.width > graphicsDevice.height;\n        };\n        graphicsDevice.on('resizecanvas', () => {\n            updateHorizontalFov();\n            app.renderNextFrame = true;\n        });\n        updateHorizontalFov();\n        // track camera changes\n        const prevProj = new Mat4();\n        const prevWorld = new Mat4();\n        app.on('framerender', () => {\n            const world = this.entity.getWorldTransform();\n            const proj = this.entity.camera.projectionMatrix;\n            const nearlyEquals = (a, b, epsilon = 1e-4) => {\n                return !a.some((v, i) => Math.abs(v - b[i]) >= epsilon);\n            };\n            if (params.ministats) {\n                app.renderNextFrame = true;\n            }\n            if (!app.autoRender && !app.renderNextFrame) {\n                if (!nearlyEquals(world.data, prevWorld.data) ||\n                    !nearlyEquals(proj.data, prevProj.data)) {\n                    app.renderNextFrame = true;\n                }\n            }\n            if (app.renderNextFrame) {\n                prevWorld.copy(world);\n                prevProj.copy(proj);\n            }\n            // suppress rendering till we're ready\n            if (!state.readyToRender) {\n                app.renderNextFrame = false;\n            }\n        });\n        events.on('hqMode:changed', (value) => {\n            graphicsDevice.maxPixelRatio = value ? window.devicePixelRatio : 1;\n            app.renderNextFrame = true;\n        });\n        graphicsDevice.maxPixelRatio = state.hqMode ? window.devicePixelRatio : 1;\n    }\n    // initialize the viewer once gsplat asset is finished loading (so we know its bound etc)\n    initialize() {\n        const { app, entity, events, state, settings } = this;\n        // get the gsplat\n        const gsplat = app.root.findComponent('gsplat');\n        // calculate scene bounding box\n        const bbox = gsplat?.instance?.meshInstance?.aabb ?? new BoundingBox();\n        // create an anim camera\n        // calculate the orbit camera frame position\n        const framePose = (() => {\n            const sceneSize = bbox.halfExtents.length();\n            const distance = sceneSize / Math.sin(entity.camera.fov / 180 * Math.PI * 0.5);\n            return new Pose().look(new Vec3(2, 1, 2).normalize().mulScalar(distance).add(bbox.center), bbox.center);\n        })();\n        // calculate the orbit camera reset position\n        const resetPose = (() => {\n            const { position, target } = settings.camera;\n            return new Pose().look(new Vec3(position ?? [2, 1, 2]), new Vec3(target ?? [0, 0, 0]));\n        })();\n        // calculate the user camera start position (the pose we'll use if there is no animation)\n        const useReset = settings.camera.position || settings.camera.target || bbox.halfExtents.length() > 100;\n        const userStart = (useReset ? resetPose : framePose).clone();\n        // if camera doesn't intersect the scene, assume it's an object we're\n        // viewing\n        const isObjectExperience = !bbox.containsPoint(userStart.position);\n        // create the cameras\n        const animCamera = ((initial, isObjectExperience) => {\n            const { animTracks, camera } = settings;\n            // extract the camera animation track from settings\n            if (animTracks?.length > 0 && camera.startAnim === 'animTrack') {\n                const track = animTracks.find((track) => track.name === camera.animTrack);\n                if (track) {\n                    return AnimController.fromTrack(track);\n                }\n            }\n            else if (isObjectExperience) {\n                // create basic rotation animation if no anim track is specified\n                return AnimController.fromTrack(createRotateTrack(initial));\n            }\n            return null;\n        })(userStart, isObjectExperience);\n        const orbitCamera = (() => {\n            const orbitCamera = new OrbitController();\n            orbitCamera.zoomRange = new Vec2(0.01, Infinity);\n            orbitCamera.pitchRange = new Vec2(-90, 90);\n            orbitCamera.rotateDamping = 0.97;\n            orbitCamera.moveDamping = 0.97;\n            orbitCamera.zoomDamping = 0.97;\n            return orbitCamera;\n        })();\n        const flyCamera = (() => {\n            const flyCamera = new FlyController();\n            flyCamera.pitchRange = new Vec2(-90, 90);\n            flyCamera.rotateDamping = 0.97;\n            flyCamera.moveDamping = 0.97;\n            return flyCamera;\n        })();\n        const getCamera = (cameraMode) => {\n            switch (cameraMode) {\n                case 'orbit': return orbitCamera;\n                case 'anim': return animCamera;\n                case 'fly': return flyCamera;\n            }\n        };\n        // set the global animation flag\n        state.hasAnimation = !!animCamera;\n        state.animationDuration = animCamera ? animCamera.cursor.duration : 0;\n        if (animCamera) {\n            state.cameraMode = 'anim';\n        }\n        // create controller\n        // set move speed based on scene size, within reason\n        const controller = new AppController(app.graphicsDevice.canvas, entity.camera);\n        // fixed move speed\n        controller.moveSpeed = 4;\n        // this pose stores the current camera position. it will be blended/smoothed\n        // toward the current active camera\n        const activePose = new Pose();\n        if (state.cameraMode === 'anim') {\n            //  first frame of the animation\n            activePose.copy(animCamera.update(controller.frame, 0));\n        }\n        else {\n            // user start position\n            activePose.copy(userStart);\n        }\n        // place all user cameras at the start position\n        orbitCamera.attach(activePose, false);\n        flyCamera.attach(activePose, false);\n        // the previous camera we're transitioning away from\n        let prevCameraMode = 'orbit';\n        // handle input events\n        events.on('inputEvent', (eventName, event) => {\n            const doReset = (pose) => {\n                switch (state.cameraMode) {\n                    case 'orbit': {\n                        orbitCamera.attach(pose, true);\n                        break;\n                    }\n                    case 'fly': {\n                        if (state.cameraMode !== 'orbit') {\n                            state.cameraMode = 'orbit';\n                        }\n                        orbitCamera.attach(pose, true);\n                        break;\n                    }\n                    case 'anim': {\n                        state.cameraMode = prevCameraMode;\n                        break;\n                    }\n                }\n            };\n            switch (eventName) {\n                case 'frame':\n                    doReset(framePose);\n                    break;\n                case 'reset':\n                    doReset(resetPose);\n                    break;\n                case 'cancel':\n                case 'interrupt':\n                    if (state.cameraMode === 'anim') {\n                        state.cameraMode = prevCameraMode;\n                    }\n                    break;\n            }\n        });\n        let currCamera = getCamera(state.cameraMode);\n        const prevPose = new Pose();\n        // transition time between cameras\n        let transitionTimer = 1;\n        // application update\n        app.on('update', (deltaTime) => {\n            // in xr mode we leave the camera alone\n            if (app.xr.active) {\n                return;\n            }\n            // update input controller\n            controller.update(deltaTime, state, activePose.distance);\n            // update touch joystick UI\n            if (state.cameraMode === 'fly') {\n                events.fire('touchJoystickUpdate', controller.joystick.base, controller.joystick.stick);\n            }\n            // use dt of 0 if animation is paused\n            const dt = state.cameraMode === 'anim' && state.animationPaused ? 0 : deltaTime;\n            // update the camera we're transitioning from\n            transitionTimer = Math.min(1, transitionTimer + deltaTime * 2.0);\n            // update camera\n            pose.copy(currCamera.update(controller.frame, dt));\n            if (transitionTimer < 1) {\n                // handle lerp away from previous camera\n                lerpPose(activePose, prevPose, pose, easeOut(transitionTimer));\n            }\n            else {\n                activePose.copy(pose);\n            }\n            // apply to camera\n            entity.setPosition(activePose.position);\n            entity.setEulerAngles(activePose.angles);\n            // update animation timeline\n            if (state.cameraMode === 'anim') {\n                state.animationTime = animCamera.cursor.value;\n            }\n        });\n        // handle camera mode switching\n        events.on('cameraMode:changed', (value, prev) => {\n            prevCameraMode = prev;\n            prevPose.copy(activePose);\n            getCamera(prev).detach();\n            currCamera = getCamera(value);\n            switch (value) {\n                case 'orbit':\n                case 'fly':\n                    currCamera.attach(activePose, false);\n                    break;\n            }\n            // reset camera transition timer\n            transitionTimer = 0;\n        });\n        events.on('setAnimationTime', (time) => {\n            if (animCamera) {\n                animCamera.cursor.value = time;\n                // switch to animation camera if we're not already there\n                if (state.cameraMode !== 'anim') {\n                    state.cameraMode = 'anim';\n                }\n            }\n        });\n        // pick orbit camera focus point on double click\n        let picker = null;\n        events.on('inputEvent', async (eventName, event) => {\n            switch (eventName) {\n                case 'dblclick': {\n                    if (!picker) {\n                        picker = new Picker(app, entity);\n                    }\n                    const result = await picker.pick(event.offsetX, event.offsetY);\n                    if (result) {\n                        if (state.cameraMode !== 'orbit') {\n                            state.cameraMode = 'orbit';\n                        }\n                        // snap distance of focus to picked point to interpolate rotation only\n                        activePose.distance = activePose.position.distance(result);\n                        orbitCamera.attach(activePose, false);\n                        orbitCamera.attach(pose.look(activePose.position, result), true);\n                    }\n                    break;\n                }\n            }\n        });\n        // initialize the camera entity to initial position and kick off the\n        // first scene sort (which usually happens during render)\n        entity.setPosition(activePose.position);\n        entity.setEulerAngles(activePose.angles);\n        gsplat?.instance?.sort(entity);\n        // handle gsplat sort updates\n        gsplat?.instance?.sorter?.on('updated', () => {\n            // request frame render when sorting changes\n            app.renderNextFrame = true;\n            if (!state.readyToRender) {\n                // we're ready to render once the first sort has completed\n                state.readyToRender = true;\n                // wait for the first valid frame to complete rendering\n                const frameHandle = app.on('frameend', () => {\n                    frameHandle.off();\n                    events.fire('firstFrame');\n                    // emit first frame event on window\n                    window.firstFrame?.();\n                });\n            }\n        });\n    }\n}\n\n// override global pick to pack depth instead of meshInstance id\nconst pickDepthGlsl = /* glsl */ `\nvec4 packFloat(float depth) {\n    uvec4 u = (uvec4(floatBitsToUint(depth)) >> uvec4(0u, 8u, 16u, 24u)) & 0xffu;\n    return vec4(u) / 255.0;\n}\nvec4 getPickOutput() {\n    return packFloat(gl_FragCoord.z);\n}\n`;\nconst pickDepthWgsl = /* wgsl */ `\n    fn packFloat(depth: f32) -> vec4f {\n        let u: vec4<u32> = (vec4<u32>(bitcast<u32>(depth)) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(0xffu);\n        return vec4f(u) / 255.0;\n    }\n\n    fn getPickOutput() -> vec4f {\n        return packFloat(pcPosition.z);\n    }\n`;\n// temporary vector\nconst v = new Vec3();\n// get experience parameters\nconst params = window.sse?.params ?? {};\n// displays a blurry poster image which resolves to sharp during loading\nconst initPoster = (events) => {\n    const element = document.getElementById('poster');\n    const blur = (progress) => `blur(${Math.floor((100 - progress) * 0.4)}px)`;\n    events.on('progress:changed', (progress) => {\n        element.style.filter = blur(progress);\n    });\n    events.on('firstFrame', () => {\n        element.style.display = 'none';\n    });\n};\n// On entering/exiting AR, we need to set the camera clear color to transparent black\nconst initXr = (app, cameraElement, state, events) => {\n    // initialize ar/vr\n    app.xr.on('available:immersive-ar', (available) => {\n        state.hasAR = available;\n    });\n    app.xr.on('available:immersive-vr', (available) => {\n        state.hasVR = available;\n    });\n    const parent = cameraElement.parentElement.entity;\n    const camera = cameraElement.entity;\n    const clearColor = new Color();\n    const parentPosition = new Vec3();\n    const parentRotation = new Quat();\n    const cameraPosition = new Vec3();\n    const cameraRotation = new Quat();\n    const angles = new Vec3();\n    parent.script.create(XrControllers);\n    parent.script.create(XrNavigation);\n    app.xr.on('start', () => {\n        app.autoRender = true;\n        // cache original camera rig positions and rotations\n        parentPosition.copy(parent.getPosition());\n        parentRotation.copy(parent.getRotation());\n        cameraPosition.copy(camera.getPosition());\n        cameraRotation.copy(camera.getRotation());\n        cameraRotation.getEulerAngles(angles);\n        // copy transform to parent to XR/VR mode starts in the right place\n        parent.setPosition(cameraPosition.x, 0, cameraPosition.z);\n        parent.setEulerAngles(0, angles.y, 0);\n        if (app.xr.type === 'immersive-ar') {\n            clearColor.copy(camera.camera.clearColor);\n            camera.camera.clearColor = new Color(0, 0, 0, 0);\n        }\n    });\n    app.xr.on('end', () => {\n        app.autoRender = false;\n        // restore camera to pre-XR state\n        parent.setPosition(parentPosition);\n        parent.setRotation(parentRotation);\n        camera.setPosition(cameraPosition);\n        camera.setRotation(cameraRotation);\n        if (app.xr.type === 'immersive-ar') {\n            camera.camera.clearColor = clearColor;\n        }\n    });\n    events.on('startAR', () => {\n        app.xr.start(app.root.findComponent('camera'), 'immersive-ar', 'local-floor');\n    });\n    events.on('startVR', () => {\n        app.xr.start(app.root.findComponent('camera'), 'immersive-vr', 'local-floor');\n    });\n    events.on('inputEvent', (event) => {\n        if (event === 'cancel' && app.xr.active) {\n            app.xr.end();\n        }\n    });\n};\nconst loadContent = (app) => {\n    const { contentUrl, contents } = window.sse;\n    const filename = new URL(contentUrl, location.href).pathname.split('/').pop();\n    const asset = new Asset(filename, 'gsplat', {\n        url: contentUrl,\n        filename,\n        contents\n    });\n    asset.on('load', () => {\n        const entity = new Entity('gsplat');\n        entity.setLocalEulerAngles(0, 0, 180);\n        entity.addComponent('gsplat', { asset });\n        app.root.addChild(entity);\n    });\n    asset.on('error', (err) => {\n        console.log(err);\n    });\n    app.assets.add(asset);\n    app.assets.load(asset);\n};\nconst waitForGsplat = (app, state) => {\n    return new Promise((resolve) => {\n        const assets = app.assets.filter(asset => asset.type === 'gsplat');\n        if (assets.length > 0) {\n            const asset = assets[0];\n            let watermark = 0;\n            asset.on('progress', (received, length) => {\n                const progress = Math.min(1, received / length) * 100;\n                if (progress > watermark) {\n                    watermark = progress;\n                    state.progress = watermark.toFixed(0);\n                }\n            });\n            if (asset.loaded) {\n                resolve(asset);\n            }\n            else {\n                asset.on('load', () => {\n                    resolve(asset);\n                });\n            }\n        }\n    });\n};\ndocument.addEventListener('DOMContentLoaded', async () => {\n    const appElement = document.querySelector('pc-app');\n    const app = (await appElement.ready()).app;\n    const { graphicsDevice } = app;\n    // enable anonymous CORS for image loading in safari\n    app.loader.getHandler('texture').imgParser.crossOrigin = 'anonymous';\n    // render skybox as plain equirect\n    const glsl = ShaderChunks.get(graphicsDevice, 'glsl');\n    glsl.set('skyboxPS', glsl.get('skyboxPS').replace('mapRoughnessUv(uv, mipLevel)', 'uv'));\n    glsl.set('pickPS', pickDepthGlsl);\n    const wgsl = ShaderChunks.get(graphicsDevice, 'wgsl');\n    wgsl.set('skyboxPS', wgsl.get('skyboxPS').replace('mapRoughnessUv(uv, uniform.mipLevel)', 'uv'));\n    wgsl.set('pickPS', pickDepthWgsl);\n    loadContent(app);\n    const cameraElement = await document.querySelector('pc-entity[name=\"camera\"]').ready();\n    const camera = cameraElement.entity;\n    const settings = migrateSettings(await window.sse?.settings);\n    const events = new EventHandler();\n    const state = observe(events, {\n        readyToRender: false, // don't render till this is set\n        hqMode: true,\n        progress: 0,\n        inputMode: 'desktop', // desktop, touch\n        cameraMode: 'orbit', // orbit, anim, fly\n        hasAnimation: false,\n        animationDuration: 0,\n        animationTime: 0,\n        animationPaused: params.noanim,\n        hasAR: app.xr.isAvailable('immersive-ar'),\n        hasVR: app.xr.isAvailable('immersive-vr'),\n        isFullscreen: false,\n        uiVisible: true\n    });\n    // Initialize the load-time poster\n    if (window.sse?.poster) {\n        initPoster(events);\n    }\n    // Initialize skybox\n    if (params.skyboxUrl) {\n        const skyAsset = new Asset('skybox', 'texture', {\n            url: params.skyboxUrl\n        }, {\n            type: 'rgbp',\n            mipmaps: false,\n            addressu: 'repeat',\n            addressv: 'clamp'\n        });\n        skyAsset.on('load', () => {\n            app.scene.envAtlas = skyAsset.resource;\n        });\n        app.assets.add(skyAsset);\n        app.assets.load(skyAsset);\n    }\n    // Construct ministats\n    if (params.ministats) {\n        // eslint-disable-next-line no-new\n        new MiniStats(app);\n    }\n    // Initialize XR support\n    initXr(app, cameraElement, state, events);\n    // Initialize viewer\n    const viewer = new Viewer(app, camera, events, state, settings, params);\n    // Wait for gsplat asset to load before initializing the viewer\n    waitForGsplat(app, state).then(() => viewer.initialize());\n    // Acquire Elements\n    const docRoot = document.documentElement;\n    const dom = [\n        'ui',\n        'controlsWrap',\n        'arMode', 'vrMode',\n        'enterFullscreen', 'exitFullscreen',\n        'info', 'infoPanel', 'desktopTab', 'touchTab', 'desktopInfoPanel', 'touchInfoPanel',\n        'timelineContainer', 'handle', 'time',\n        'buttonContainer',\n        'play', 'pause',\n        'settings', 'settingsPanel',\n        'orbitSettings', 'flySettings',\n        'fly', 'orbit', 'cameraToggleHighlight',\n        'high', 'low', 'qualityToggleHighlight',\n        'reset', 'frame',\n        'loadingText', 'loadingBar',\n        'joystickBase', 'joystick'\n    ].reduce((acc, id) => {\n        acc[id] = document.getElementById(id);\n        return acc;\n    }, {});\n    // Handle loading progress updates\n    events.on('progress:changed', (progress) => {\n        dom.loadingText.textContent = `${progress}%`;\n        if (progress < 100) {\n            dom.loadingBar.style.backgroundImage = `linear-gradient(90deg, #F60 0%, #F60 ${progress}%, white ${progress}%, white 100%)`;\n        }\n        else {\n            dom.loadingBar.style.backgroundImage = 'linear-gradient(90deg, #F60 0%, #F60 100%)';\n        }\n    });\n    // Hide loading bar once first frame is rendered\n    events.on('firstFrame', () => {\n        document.getElementById('loadingWrap').classList.add('hidden');\n    });\n    // Fullscreen support\n    const hasFullscreenAPI = docRoot.requestFullscreen && document.exitFullscreen;\n    const requestFullscreen = () => {\n        if (hasFullscreenAPI) {\n            docRoot.requestFullscreen();\n        }\n        else {\n            window.parent.postMessage('requestFullscreen', '*');\n            state.isFullscreen = true;\n        }\n    };\n    const exitFullscreen = () => {\n        if (hasFullscreenAPI) {\n            document.exitFullscreen();\n        }\n        else {\n            window.parent.postMessage('exitFullscreen', '*');\n            state.isFullscreen = false;\n        }\n    };\n    if (hasFullscreenAPI) {\n        document.addEventListener('fullscreenchange', () => {\n            state.isFullscreen = !!document.fullscreenElement;\n        });\n    }\n    dom.enterFullscreen.addEventListener('click', requestFullscreen);\n    dom.exitFullscreen.addEventListener('click', exitFullscreen);\n    // toggle fullscreen when user switches between landscape portrait\n    // orientation\n    screen?.orientation?.addEventListener('change', (event) => {\n        if (['landscape-primary', 'landscape-secondary'].includes(screen.orientation.type)) {\n            requestFullscreen();\n        }\n        else {\n            exitFullscreen();\n        }\n    });\n    // update UI when fullscreen state changes\n    events.on('isFullscreen:changed', (value) => {\n        dom.enterFullscreen.classList[value ? 'add' : 'remove']('hidden');\n        dom.exitFullscreen.classList[value ? 'remove' : 'add']('hidden');\n    });\n    // HQ mode\n    dom.high.addEventListener('click', () => {\n        state.hqMode = true;\n    });\n    dom.low.addEventListener('click', () => {\n        state.hqMode = false;\n    });\n    const updateHQ = () => {\n        dom.qualityToggleHighlight.classList[state.hqMode ? 'add' : 'remove']('right');\n    };\n    events.on('hqMode:changed', (value) => {\n        updateHQ();\n    });\n    updateHQ();\n    // AR/VR\n    const arChanged = () => dom.arMode.classList[state.hasAR ? 'remove' : 'add']('hidden');\n    const vrChanged = () => dom.vrMode.classList[state.hasVR ? 'remove' : 'add']('hidden');\n    dom.arMode.addEventListener('click', () => events.fire('startAR'));\n    dom.vrMode.addEventListener('click', () => events.fire('startVR'));\n    events.on('hasAR:changed', arChanged);\n    events.on('hasVR:changed', vrChanged);\n    arChanged();\n    vrChanged();\n    // Info panel\n    const updateInfoTab = (tab) => {\n        if (tab === 'desktop') {\n            dom.desktopTab.classList.add('active');\n            dom.touchTab.classList.remove('active');\n            dom.desktopInfoPanel.classList.remove('hidden');\n            dom.touchInfoPanel.classList.add('hidden');\n        }\n        else {\n            dom.desktopTab.classList.remove('active');\n            dom.touchTab.classList.add('active');\n            dom.desktopInfoPanel.classList.add('hidden');\n            dom.touchInfoPanel.classList.remove('hidden');\n        }\n    };\n    dom.desktopTab.addEventListener('click', () => {\n        updateInfoTab('desktop');\n    });\n    dom.touchTab.addEventListener('click', () => {\n        updateInfoTab('touch');\n    });\n    dom.info.addEventListener('pointerup', () => {\n        updateInfoTab(state.inputMode);\n        dom.infoPanel.classList.toggle('hidden');\n    });\n    dom.infoPanel.addEventListener('pointerdown', () => {\n        dom.infoPanel.classList.add('hidden');\n    });\n    events.on('inputEvent', (event) => {\n        if (event === 'cancel') {\n            // close info panel on cancel\n            dom.infoPanel.classList.add('hidden');\n            dom.settingsPanel.classList.add('hidden');\n            // close fullscreen on cancel\n            if (state.isFullscreen) {\n                exitFullscreen();\n            }\n        }\n        else if (event === 'interrupt') {\n            dom.settingsPanel.classList.add('hidden');\n        }\n    });\n    // fade ui controls after 5 seconds of inactivity\n    events.on('uiVisible:changed', (value) => {\n        dom.controlsWrap.className = value ? 'faded-in' : 'faded-out';\n    });\n    // show the ui and start a timer to hide it again\n    let uiTimeout = null;\n    const showUI = () => {\n        if (uiTimeout) {\n            clearTimeout(uiTimeout);\n        }\n        state.uiVisible = true;\n        uiTimeout = setTimeout(() => {\n            uiTimeout = null;\n            state.uiVisible = false;\n        }, 4000);\n    };\n    showUI();\n    events.on('inputEvent', showUI);\n    // Animation controls\n    events.on('hasAnimation:changed', (value, prev) => {\n        // Start and Stop animation\n        dom.play.addEventListener('click', () => {\n            state.cameraMode = 'anim';\n            state.animationPaused = false;\n        });\n        dom.pause.addEventListener('click', () => {\n            state.cameraMode = 'anim';\n            state.animationPaused = true;\n        });\n        const updatePlayPause = () => {\n            if (state.cameraMode !== 'anim' || state.animationPaused) {\n                dom.play.classList.remove('hidden');\n                dom.pause.classList.add('hidden');\n            }\n            else {\n                dom.play.classList.add('hidden');\n                dom.pause.classList.remove('hidden');\n            }\n            if (state.cameraMode === 'anim') {\n                dom.timelineContainer.classList.remove('hidden');\n            }\n            else {\n                dom.timelineContainer.classList.add('hidden');\n            }\n        };\n        // Update UI on animation changes\n        events.on('cameraMode:changed', updatePlayPause);\n        events.on('animationPaused:changed', updatePlayPause);\n        // Spacebar to play/pause\n        events.on('inputEvent', (eventName) => {\n            if (eventName === 'playPause') {\n                state.cameraMode = 'anim';\n                state.animationPaused = !state.animationPaused;\n            }\n        });\n        const updateSlider = () => {\n            dom.handle.style.left = `${state.animationTime / state.animationDuration * 100}%`;\n            dom.time.style.left = `${state.animationTime / state.animationDuration * 100}%`;\n            dom.time.innerText = `${state.animationTime.toFixed(1)}s`;\n        };\n        events.on('animationTime:changed', updateSlider);\n        events.on('animationLength:changed', updateSlider);\n        const handleScrub = (event) => {\n            const rect = dom.timelineContainer.getBoundingClientRect();\n            const t = Math.max(0, Math.min(rect.width - 1, event.clientX - rect.left)) / rect.width;\n            events.fire('setAnimationTime', state.animationDuration * t);\n        };\n        let paused = false;\n        let captured = false;\n        dom.timelineContainer.addEventListener('pointerdown', (event) => {\n            if (!captured) {\n                handleScrub(event);\n                dom.timelineContainer.setPointerCapture(event.pointerId);\n                dom.time.classList.remove('hidden');\n                paused = state.animationPaused;\n                state.animationPaused = true;\n                captured = true;\n            }\n        });\n        dom.timelineContainer.addEventListener('pointermove', (event) => {\n            if (captured) {\n                handleScrub(event);\n            }\n        });\n        dom.timelineContainer.addEventListener('pointerup', (event) => {\n            if (captured) {\n                dom.timelineContainer.releasePointerCapture(event.pointerId);\n                dom.time.classList.add('hidden');\n                state.animationPaused = paused;\n                captured = false;\n            }\n        });\n    });\n    // Camera mode UI\n    events.on('cameraMode:changed', () => {\n        if (state.cameraMode === 'fly') {\n            dom.cameraToggleHighlight.classList.add('right');\n        }\n        else {\n            dom.cameraToggleHighlight.classList.remove('right');\n        }\n        dom.orbitSettings.classList[state.cameraMode === 'orbit' ? 'remove' : 'add']('hidden');\n        dom.flySettings.classList[state.cameraMode === 'fly' ? 'remove' : 'add']('hidden');\n    });\n    dom.orbitSettings.addEventListener('click', () => {\n        dom.settingsPanel.classList.toggle('hidden');\n    });\n    dom.flySettings.addEventListener('click', () => {\n        dom.settingsPanel.classList.toggle('hidden');\n    });\n    dom.fly.addEventListener('click', () => {\n        state.cameraMode = 'fly';\n    });\n    dom.orbit.addEventListener('click', () => {\n        state.cameraMode = 'orbit';\n    });\n    dom.reset.addEventListener('click', (event) => {\n        events.fire('inputEvent', 'reset', event);\n    });\n    dom.frame.addEventListener('click', (event) => {\n        events.fire('inputEvent', 'frame', event);\n    });\n    // update UI based on touch joystick updates\n    events.on('touchJoystickUpdate', (base, stick) => {\n        if (base === null) {\n            dom.joystickBase.classList.add('hidden');\n        }\n        else {\n            v.set(stick[0], stick[1], 0).mulScalar(1 / 48);\n            if (v.length() > 1) {\n                v.normalize();\n            }\n            v.mulScalar(48);\n            dom.joystickBase.classList.remove('hidden');\n            dom.joystickBase.style.left = `${base[0]}px`;\n            dom.joystickBase.style.top = `${base[1]}px`;\n            dom.joystick.style.left = `${48 + v.x}px`;\n            dom.joystick.style.top = `${48 + v.y}px`;\n        }\n    });\n    // Hide UI\n    if (params.noui) {\n        dom.ui.classList.add('hidden');\n    }\n    // Generate input events\n    ['wheel', 'pointerdown', 'contextmenu', 'keydown'].forEach((eventName) => {\n        graphicsDevice.canvas.addEventListener(eventName, (event) => {\n            events.fire('inputEvent', 'interrupt', event);\n        });\n    });\n    graphicsDevice.canvas.addEventListener('pointermove', (event) => {\n        events.fire('inputEvent', 'interact', event);\n    });\n    // we must detect double taps manually because ios doesn't send dblclick events\n    const lastTap = { time: 0, x: 0, y: 0 };\n    graphicsDevice.canvas.addEventListener('pointerdown', (event) => {\n        const now = Date.now();\n        const delay = Math.max(0, now - lastTap.time);\n        if (delay < 300 &&\n            Math.abs(event.clientX - lastTap.x) < 8 &&\n            Math.abs(event.clientY - lastTap.y) < 8) {\n            events.fire('inputEvent', 'dblclick', event);\n            lastTap.time = 0;\n        }\n        else {\n            lastTap.time = now;\n            lastTap.x = event.clientX;\n            lastTap.y = event.clientY;\n        }\n    });\n    // update input mode based on pointer event\n    ['pointerdown', 'pointermove'].forEach((eventName) => {\n        window.addEventListener(eventName, (event) => {\n            state.inputMode = event.pointerType === 'touch' ? 'touch' : 'desktop';\n        });\n    });\n    window.addEventListener('keydown', (event) => {\n        if (event.key === 'Escape') {\n            events.fire('inputEvent', 'cancel', event);\n        }\n        else if (!event.ctrlKey && !event.altKey && !event.metaKey) {\n            switch (event.key) {\n                case 'f':\n                    events.fire('inputEvent', 'frame', event);\n                    break;\n                case 'r':\n                    events.fire('inputEvent', 'reset', event);\n                    break;\n                case ' ':\n                    events.fire('inputEvent', 'playPause', event);\n                    break;\n            }\n        }\n    });\n});\n//# sourceMappingURL=index.js.map\n";

/* eslint-disable indent */
const kSqrt03_02 = Math.sqrt(3.0 / 2.0);
const kSqrt01_03 = Math.sqrt(1.0 / 3.0);
const kSqrt02_03 = Math.sqrt(2.0 / 3.0);
const kSqrt04_03 = Math.sqrt(4.0 / 3.0);
const kSqrt01_04 = Math.sqrt(1.0 / 4.0);
const kSqrt03_04 = Math.sqrt(3.0 / 4.0);
const kSqrt01_05 = Math.sqrt(1.0 / 5.0);
const kSqrt03_05 = Math.sqrt(3.0 / 5.0);
const kSqrt06_05 = Math.sqrt(6.0 / 5.0);
const kSqrt08_05 = Math.sqrt(8.0 / 5.0);
const kSqrt09_05 = Math.sqrt(9.0 / 5.0);
const kSqrt01_06 = Math.sqrt(1.0 / 6.0);
const kSqrt05_06 = Math.sqrt(5.0 / 6.0);
const kSqrt03_08 = Math.sqrt(3.0 / 8.0);
const kSqrt05_08 = Math.sqrt(5.0 / 8.0);
const kSqrt09_08 = Math.sqrt(9.0 / 8.0);
const kSqrt05_09 = Math.sqrt(5.0 / 9.0);
const kSqrt08_09 = Math.sqrt(8.0 / 9.0);
const kSqrt01_10 = Math.sqrt(1.0 / 10.0);
const kSqrt03_10 = Math.sqrt(3.0 / 10.0);
const kSqrt01_12 = Math.sqrt(1.0 / 12.0);
const kSqrt04_15 = Math.sqrt(4.0 / 15.0);
const kSqrt01_16 = Math.sqrt(1.0 / 16.0);
const kSqrt15_16 = Math.sqrt(15.0 / 16.0);
const kSqrt01_18 = Math.sqrt(1.0 / 18.0);
const kSqrt01_60 = Math.sqrt(1.0 / 60.0);
const dp = (n, start, a, b) => {
    let sum = 0;
    for (let i = 0; i < n; i++) {
        sum += a[start + i] * b[i];
    }
    return sum;
};
const coeffsIn = new Float32Array(15);
// Rotate spherical harmonics up to band 3 based on https://github.com/andrewwillmott/sh-lib
//
// This implementation calculates the rotation factors during construction which can then
// be used to rotate multiple spherical harmonics cheaply.
class SHRotation {
    apply;
    constructor(mat) {
        const rot = mat.data;
        // band 1
        const sh1 = [
            [rot[4], -rot[7], rot[1]],
            [-rot[5], rot[8], -rot[2]],
            [rot[3], -rot[6], rot[0]]
        ];
        // band 2
        const sh2 = [[
                kSqrt01_04 * ((sh1[2][2] * sh1[0][0] + sh1[2][0] * sh1[0][2]) + (sh1[0][2] * sh1[2][0] + sh1[0][0] * sh1[2][2])),
                (sh1[2][1] * sh1[0][0] + sh1[0][1] * sh1[2][0]),
                kSqrt03_04 * (sh1[2][1] * sh1[0][1] + sh1[0][1] * sh1[2][1]),
                (sh1[2][1] * sh1[0][2] + sh1[0][1] * sh1[2][2]),
                kSqrt01_04 * ((sh1[2][2] * sh1[0][2] - sh1[2][0] * sh1[0][0]) + (sh1[0][2] * sh1[2][2] - sh1[0][0] * sh1[2][0]))
            ], [
                kSqrt01_04 * ((sh1[1][2] * sh1[0][0] + sh1[1][0] * sh1[0][2]) + (sh1[0][2] * sh1[1][0] + sh1[0][0] * sh1[1][2])),
                sh1[1][1] * sh1[0][0] + sh1[0][1] * sh1[1][0],
                kSqrt03_04 * (sh1[1][1] * sh1[0][1] + sh1[0][1] * sh1[1][1]),
                sh1[1][1] * sh1[0][2] + sh1[0][1] * sh1[1][2],
                kSqrt01_04 * ((sh1[1][2] * sh1[0][2] - sh1[1][0] * sh1[0][0]) + (sh1[0][2] * sh1[1][2] - sh1[0][0] * sh1[1][0]))
            ], [
                kSqrt01_03 * (sh1[1][2] * sh1[1][0] + sh1[1][0] * sh1[1][2]) - kSqrt01_12 * ((sh1[2][2] * sh1[2][0] + sh1[2][0] * sh1[2][2]) + (sh1[0][2] * sh1[0][0] + sh1[0][0] * sh1[0][2])),
                kSqrt04_03 * sh1[1][1] * sh1[1][0] - kSqrt01_03 * (sh1[2][1] * sh1[2][0] + sh1[0][1] * sh1[0][0]),
                sh1[1][1] * sh1[1][1] - kSqrt01_04 * (sh1[2][1] * sh1[2][1] + sh1[0][1] * sh1[0][1]),
                kSqrt04_03 * sh1[1][1] * sh1[1][2] - kSqrt01_03 * (sh1[2][1] * sh1[2][2] + sh1[0][1] * sh1[0][2]),
                kSqrt01_03 * (sh1[1][2] * sh1[1][2] - sh1[1][0] * sh1[1][0]) - kSqrt01_12 * ((sh1[2][2] * sh1[2][2] - sh1[2][0] * sh1[2][0]) + (sh1[0][2] * sh1[0][2] - sh1[0][0] * sh1[0][0]))
            ], [
                kSqrt01_04 * ((sh1[1][2] * sh1[2][0] + sh1[1][0] * sh1[2][2]) + (sh1[2][2] * sh1[1][0] + sh1[2][0] * sh1[1][2])),
                sh1[1][1] * sh1[2][0] + sh1[2][1] * sh1[1][0],
                kSqrt03_04 * (sh1[1][1] * sh1[2][1] + sh1[2][1] * sh1[1][1]),
                sh1[1][1] * sh1[2][2] + sh1[2][1] * sh1[1][2],
                kSqrt01_04 * ((sh1[1][2] * sh1[2][2] - sh1[1][0] * sh1[2][0]) + (sh1[2][2] * sh1[1][2] - sh1[2][0] * sh1[1][0]))
            ], [
                kSqrt01_04 * ((sh1[2][2] * sh1[2][0] + sh1[2][0] * sh1[2][2]) - (sh1[0][2] * sh1[0][0] + sh1[0][0] * sh1[0][2])),
                (sh1[2][1] * sh1[2][0] - sh1[0][1] * sh1[0][0]),
                kSqrt03_04 * (sh1[2][1] * sh1[2][1] - sh1[0][1] * sh1[0][1]),
                (sh1[2][1] * sh1[2][2] - sh1[0][1] * sh1[0][2]),
                kSqrt01_04 * ((sh1[2][2] * sh1[2][2] - sh1[2][0] * sh1[2][0]) - (sh1[0][2] * sh1[0][2] - sh1[0][0] * sh1[0][0]))
            ]];
        // band 3
        const sh3 = [[
                kSqrt01_04 * ((sh1[2][2] * sh2[0][0] + sh1[2][0] * sh2[0][4]) + (sh1[0][2] * sh2[4][0] + sh1[0][0] * sh2[4][4])),
                kSqrt03_02 * (sh1[2][1] * sh2[0][0] + sh1[0][1] * sh2[4][0]),
                kSqrt15_16 * (sh1[2][1] * sh2[0][1] + sh1[0][1] * sh2[4][1]),
                kSqrt05_06 * (sh1[2][1] * sh2[0][2] + sh1[0][1] * sh2[4][2]),
                kSqrt15_16 * (sh1[2][1] * sh2[0][3] + sh1[0][1] * sh2[4][3]),
                kSqrt03_02 * (sh1[2][1] * sh2[0][4] + sh1[0][1] * sh2[4][4]),
                kSqrt01_04 * ((sh1[2][2] * sh2[0][4] - sh1[2][0] * sh2[0][0]) + (sh1[0][2] * sh2[4][4] - sh1[0][0] * sh2[4][0]))
            ], [
                kSqrt01_06 * (sh1[1][2] * sh2[0][0] + sh1[1][0] * sh2[0][4]) + kSqrt01_06 * ((sh1[2][2] * sh2[1][0] + sh1[2][0] * sh2[1][4]) + (sh1[0][2] * sh2[3][0] + sh1[0][0] * sh2[3][4])),
                sh1[1][1] * sh2[0][0] + (sh1[2][1] * sh2[1][0] + sh1[0][1] * sh2[3][0]),
                kSqrt05_08 * sh1[1][1] * sh2[0][1] + kSqrt05_08 * (sh1[2][1] * sh2[1][1] + sh1[0][1] * sh2[3][1]),
                kSqrt05_09 * sh1[1][1] * sh2[0][2] + kSqrt05_09 * (sh1[2][1] * sh2[1][2] + sh1[0][1] * sh2[3][2]),
                kSqrt05_08 * sh1[1][1] * sh2[0][3] + kSqrt05_08 * (sh1[2][1] * sh2[1][3] + sh1[0][1] * sh2[3][3]),
                sh1[1][1] * sh2[0][4] + (sh1[2][1] * sh2[1][4] + sh1[0][1] * sh2[3][4]),
                kSqrt01_06 * (sh1[1][2] * sh2[0][4] - sh1[1][0] * sh2[0][0]) + kSqrt01_06 * ((sh1[2][2] * sh2[1][4] - sh1[2][0] * sh2[1][0]) + (sh1[0][2] * sh2[3][4] - sh1[0][0] * sh2[3][0]))
            ], [
                kSqrt04_15 * (sh1[1][2] * sh2[1][0] + sh1[1][0] * sh2[1][4]) + kSqrt01_05 * (sh1[0][2] * sh2[2][0] + sh1[0][0] * sh2[2][4]) - kSqrt01_60 * ((sh1[2][2] * sh2[0][0] + sh1[2][0] * sh2[0][4]) - (sh1[0][2] * sh2[4][0] + sh1[0][0] * sh2[4][4])),
                kSqrt08_05 * sh1[1][1] * sh2[1][0] + kSqrt06_05 * sh1[0][1] * sh2[2][0] - kSqrt01_10 * (sh1[2][1] * sh2[0][0] - sh1[0][1] * sh2[4][0]),
                sh1[1][1] * sh2[1][1] + kSqrt03_04 * sh1[0][1] * sh2[2][1] - kSqrt01_16 * (sh1[2][1] * sh2[0][1] - sh1[0][1] * sh2[4][1]),
                kSqrt08_09 * sh1[1][1] * sh2[1][2] + kSqrt02_03 * sh1[0][1] * sh2[2][2] - kSqrt01_18 * (sh1[2][1] * sh2[0][2] - sh1[0][1] * sh2[4][2]),
                sh1[1][1] * sh2[1][3] + kSqrt03_04 * sh1[0][1] * sh2[2][3] - kSqrt01_16 * (sh1[2][1] * sh2[0][3] - sh1[0][1] * sh2[4][3]),
                kSqrt08_05 * sh1[1][1] * sh2[1][4] + kSqrt06_05 * sh1[0][1] * sh2[2][4] - kSqrt01_10 * (sh1[2][1] * sh2[0][4] - sh1[0][1] * sh2[4][4]),
                kSqrt04_15 * (sh1[1][2] * sh2[1][4] - sh1[1][0] * sh2[1][0]) + kSqrt01_05 * (sh1[0][2] * sh2[2][4] - sh1[0][0] * sh2[2][0]) - kSqrt01_60 * ((sh1[2][2] * sh2[0][4] - sh1[2][0] * sh2[0][0]) - (sh1[0][2] * sh2[4][4] - sh1[0][0] * sh2[4][0]))
            ], [
                kSqrt03_10 * (sh1[1][2] * sh2[2][0] + sh1[1][0] * sh2[2][4]) - kSqrt01_10 * ((sh1[2][2] * sh2[3][0] + sh1[2][0] * sh2[3][4]) + (sh1[0][2] * sh2[1][0] + sh1[0][0] * sh2[1][4])),
                kSqrt09_05 * sh1[1][1] * sh2[2][0] - kSqrt03_05 * (sh1[2][1] * sh2[3][0] + sh1[0][1] * sh2[1][0]),
                kSqrt09_08 * sh1[1][1] * sh2[2][1] - kSqrt03_08 * (sh1[2][1] * sh2[3][1] + sh1[0][1] * sh2[1][1]),
                sh1[1][1] * sh2[2][2] - kSqrt01_03 * (sh1[2][1] * sh2[3][2] + sh1[0][1] * sh2[1][2]),
                kSqrt09_08 * sh1[1][1] * sh2[2][3] - kSqrt03_08 * (sh1[2][1] * sh2[3][3] + sh1[0][1] * sh2[1][3]),
                kSqrt09_05 * sh1[1][1] * sh2[2][4] - kSqrt03_05 * (sh1[2][1] * sh2[3][4] + sh1[0][1] * sh2[1][4]),
                kSqrt03_10 * (sh1[1][2] * sh2[2][4] - sh1[1][0] * sh2[2][0]) - kSqrt01_10 * ((sh1[2][2] * sh2[3][4] - sh1[2][0] * sh2[3][0]) + (sh1[0][2] * sh2[1][4] - sh1[0][0] * sh2[1][0]))
            ], [
                kSqrt04_15 * (sh1[1][2] * sh2[3][0] + sh1[1][0] * sh2[3][4]) + kSqrt01_05 * (sh1[2][2] * sh2[2][0] + sh1[2][0] * sh2[2][4]) - kSqrt01_60 * ((sh1[2][2] * sh2[4][0] + sh1[2][0] * sh2[4][4]) + (sh1[0][2] * sh2[0][0] + sh1[0][0] * sh2[0][4])),
                kSqrt08_05 * sh1[1][1] * sh2[3][0] + kSqrt06_05 * sh1[2][1] * sh2[2][0] - kSqrt01_10 * (sh1[2][1] * sh2[4][0] + sh1[0][1] * sh2[0][0]),
                sh1[1][1] * sh2[3][1] + kSqrt03_04 * sh1[2][1] * sh2[2][1] - kSqrt01_16 * (sh1[2][1] * sh2[4][1] + sh1[0][1] * sh2[0][1]),
                kSqrt08_09 * sh1[1][1] * sh2[3][2] + kSqrt02_03 * sh1[2][1] * sh2[2][2] - kSqrt01_18 * (sh1[2][1] * sh2[4][2] + sh1[0][1] * sh2[0][2]),
                sh1[1][1] * sh2[3][3] + kSqrt03_04 * sh1[2][1] * sh2[2][3] - kSqrt01_16 * (sh1[2][1] * sh2[4][3] + sh1[0][1] * sh2[0][3]),
                kSqrt08_05 * sh1[1][1] * sh2[3][4] + kSqrt06_05 * sh1[2][1] * sh2[2][4] - kSqrt01_10 * (sh1[2][1] * sh2[4][4] + sh1[0][1] * sh2[0][4]),
                kSqrt04_15 * (sh1[1][2] * sh2[3][4] - sh1[1][0] * sh2[3][0]) + kSqrt01_05 * (sh1[2][2] * sh2[2][4] - sh1[2][0] * sh2[2][0]) - kSqrt01_60 * ((sh1[2][2] * sh2[4][4] - sh1[2][0] * sh2[4][0]) + (sh1[0][2] * sh2[0][4] - sh1[0][0] * sh2[0][0]))
            ], [
                kSqrt01_06 * (sh1[1][2] * sh2[4][0] + sh1[1][0] * sh2[4][4]) + kSqrt01_06 * ((sh1[2][2] * sh2[3][0] + sh1[2][0] * sh2[3][4]) - (sh1[0][2] * sh2[1][0] + sh1[0][0] * sh2[1][4])),
                sh1[1][1] * sh2[4][0] + (sh1[2][1] * sh2[3][0] - sh1[0][1] * sh2[1][0]),
                kSqrt05_08 * sh1[1][1] * sh2[4][1] + kSqrt05_08 * (sh1[2][1] * sh2[3][1] - sh1[0][1] * sh2[1][1]),
                kSqrt05_09 * sh1[1][1] * sh2[4][2] + kSqrt05_09 * (sh1[2][1] * sh2[3][2] - sh1[0][1] * sh2[1][2]),
                kSqrt05_08 * sh1[1][1] * sh2[4][3] + kSqrt05_08 * (sh1[2][1] * sh2[3][3] - sh1[0][1] * sh2[1][3]),
                sh1[1][1] * sh2[4][4] + (sh1[2][1] * sh2[3][4] - sh1[0][1] * sh2[1][4]),
                kSqrt01_06 * (sh1[1][2] * sh2[4][4] - sh1[1][0] * sh2[4][0]) + kSqrt01_06 * ((sh1[2][2] * sh2[3][4] - sh1[2][0] * sh2[3][0]) - (sh1[0][2] * sh2[1][4] - sh1[0][0] * sh2[1][0]))
            ], [
                kSqrt01_04 * ((sh1[2][2] * sh2[4][0] + sh1[2][0] * sh2[4][4]) - (sh1[0][2] * sh2[0][0] + sh1[0][0] * sh2[0][4])),
                kSqrt03_02 * (sh1[2][1] * sh2[4][0] - sh1[0][1] * sh2[0][0]),
                kSqrt15_16 * (sh1[2][1] * sh2[4][1] - sh1[0][1] * sh2[0][1]),
                kSqrt05_06 * (sh1[2][1] * sh2[4][2] - sh1[0][1] * sh2[0][2]),
                kSqrt15_16 * (sh1[2][1] * sh2[4][3] - sh1[0][1] * sh2[0][3]),
                kSqrt03_02 * (sh1[2][1] * sh2[4][4] - sh1[0][1] * sh2[0][4]),
                kSqrt01_04 * ((sh1[2][2] * sh2[4][4] - sh1[2][0] * sh2[4][0]) - (sh1[0][2] * sh2[0][4] - sh1[0][0] * sh2[0][0]))
            ]];
        // rotate spherical harmonic coefficients, up to band 3
        this.apply = (result, src) => {
            if (!src || src === result) {
                coeffsIn.set(result);
                src = coeffsIn;
            }
            // band 1
            if (result.length < 3) {
                return;
            }
            result[0] = dp(3, 0, src, sh1[0]);
            result[1] = dp(3, 0, src, sh1[1]);
            result[2] = dp(3, 0, src, sh1[2]);
            // band 2
            if (result.length < 8) {
                return;
            }
            result[3] = dp(5, 3, src, sh2[0]);
            result[4] = dp(5, 3, src, sh2[1]);
            result[5] = dp(5, 3, src, sh2[2]);
            result[6] = dp(5, 3, src, sh2[3]);
            result[7] = dp(5, 3, src, sh2[4]);
            // band 3
            if (result.length < 15) {
                return;
            }
            result[8] = dp(7, 8, src, sh3[0]);
            result[9] = dp(7, 8, src, sh3[1]);
            result[10] = dp(7, 8, src, sh3[2]);
            result[11] = dp(7, 8, src, sh3[3]);
            result[12] = dp(7, 8, src, sh3[4]);
            result[13] = dp(7, 8, src, sh3[5]);
            result[14] = dp(7, 8, src, sh3[6]);
        };
    }
}

var State;
(function (State) {
    State[State["selected"] = 1] = "selected";
    State[State["locked"] = 2] = "locked";
    State[State["deleted"] = 4] = "deleted";
})(State || (State = {}));

var version = "2.11.7";

const generatedByString = `Generated by SuperSplat ${version}`;
// used for converting PLY opacity
const sigmoid$1 = (v) => 1 / (1 + Math.exp(-v));
// create a filter for gaussians
class GaussianFilter {
    set;
    test;
    constructor(serializeSettings) {
        let splat = null;
        let state = null;
        let opacity = null;
        this.set = (s) => {
            splat = s;
            state = splat.splatData.getProp('state');
            opacity = splat.splatData.getProp('opacity');
        };
        const onlySelected = serializeSettings.selected ?? false;
        const minOpacity = serializeSettings.minOpacity ?? 0;
        const removeInvalid = serializeSettings.removeInvalid ?? false;
        this.test = (i) => {
            // splat is deleted, always removed
            if ((state[i] & State.deleted) !== 0) {
                return false;
            }
            // optionally filter out unselected gaussians
            if (onlySelected && (state[i] !== State.selected)) {
                return false;
            }
            // optionally filter based on opacity
            if (minOpacity > 0 && sigmoid$1(opacity[i]) < minOpacity) {
                return false;
            }
            if (removeInvalid) {
                const { splatData } = splat;
                // check if any property of the gaussian is NaN/Infinity
                for (let j = 0; j < splatData.elements.length; ++j) {
                    const element = splatData.elements[j];
                    for (let k = 0; k < element.properties.length; ++k) {
                        const prop = element.properties[k];
                        const { storage } = prop;
                        if (storage && !Number.isFinite(storage[i])) {
                            return false;
                        }
                    }
                }
            }
            return true;
        };
    }
}
// count the total number of gaussians given a filter
const countGaussians = (splats, filter) => {
    return splats.reduce((accum, splat) => {
        filter.set(splat);
        for (let i = 0; i < splat.splatData.numSplats; ++i) {
            accum += filter.test(i) ? 1 : 0;
        }
        return accum;
    }, 0);
};
const getVertexProperties = (splatData) => {
    return new Set(splatData.getElement('vertex')
        .properties.filter((p) => p.storage)
        .map((p) => p.name));
};
const getCommonProps = (splats) => {
    const result = new Map(); // map of name->type
    for (let i = 0; i < splats.length; ++i) {
        const properties = splats[i].splatData.getElement('vertex').properties.filter((p) => p.storage);
        properties.forEach((p) => {
            if (result.has(p.name)) {
                result.get(p.name).add(p.type);
            }
            else {
                result.set(p.name, new Set([p.type]));
            }
        });
    }
    return [...result].filter(([_, v]) => v.size === 1).map(([name, type]) => {
        return { name, type: type.values().next().value };
    });
};
const shNames = new Array(45).fill('').map((_, i) => `f_rest_${i}`);
const shBandCoeffs = [0, 3, 8, 15];
// determine the number of sh bands present given an object with 'f_rest_*' properties
const calcSHBands = (data) => {
    return { '9': 1, '24': 2, '-1': 3 }[shNames.findIndex(v => !data.has(v))] ?? 0;
};
const DataTypeSize = (dataType) => {
    return {
        char: 1,
        uchar: 1,
        short: 2,
        ushort: 2,
        int: 4,
        uint: 4,
        float: 4,
        double: 8
    }[dataType];
};
const v$3 = new Vec3();
const q = new Quat();
// calculate splat transforms on demand and cache the result for next time
class SplatTransformCache {
    getMat;
    getRot;
    getScale;
    getSHRot;
    constructor(splat, keepWorldTransform = false) {
        const transforms = new Map();
        const indices = splat.transformTexture.getSource();
        const tmpMat = new Mat4();
        const tmpMat3 = new Mat3();
        const tmpQuat = new Quat();
        const getTransform = (index) => {
            const transformIndex = indices?.[index] ?? 0;
            let result = transforms.get(transformIndex);
            if (!result) {
                result = { transformIndex, mat: null, rot: null, scale: null, shRot: null };
                transforms.set(transformIndex, result);
            }
            return result;
        };
        this.getMat = (index) => {
            const transform = getTransform(index);
            if (!transform.mat) {
                const mat = new Mat4();
                // we must undo the transform we apply at load time to output data
                if (!keepWorldTransform) {
                    mat.setFromEulerAngles(0, 0, -180);
                    mat.mul2(mat, splat.entity.getWorldTransform());
                }
                // combine with transform palette matrix
                if (transform.transformIndex > 0) {
                    splat.transformPalette.getTransform(transform.transformIndex, tmpMat);
                    mat.mul2(mat, tmpMat);
                }
                transform.mat = mat;
            }
            return transform.mat;
        };
        this.getRot = (index) => {
            const transform = getTransform(index);
            if (!transform.rot) {
                transform.rot = new Quat().setFromMat4(this.getMat(index));
            }
            return transform.rot;
        };
        this.getScale = (index) => {
            const transform = getTransform(index);
            if (!transform.scale) {
                const scale = new Vec3();
                this.getMat(index).getScale(scale);
                transform.scale = scale;
            }
            return transform.scale;
        };
        this.getSHRot = (index) => {
            const transform = getTransform(index);
            if (!transform.shRot) {
                tmpQuat.setFromMat4(this.getMat(index));
                tmpMat3.setFromQuat(tmpQuat);
                transform.shRot = new SHRotation(tmpMat3);
            }
            return transform.shRot;
        };
    }
}
// helper class for extracting and transforming a single splat's data
// to prepare it for export
class SingleSplat {
    // final data keyed on member name
    data = {};
    // read a single gaussian's data and transform it for export
    read;
    // specify the data members required
    constructor(members, serializeSettings) {
        const data = {};
        members.forEach((name) => {
            data[name] = 0;
        });
        const hasPosition = ['x', 'y', 'z'].every(v => data.hasOwnProperty(v));
        const hasRotation = ['rot_0', 'rot_1', 'rot_2', 'rot_3'].every(v => data.hasOwnProperty(v));
        const hasScale = ['scale_0', 'scale_1', 'scale_2'].every(v => data.hasOwnProperty(v));
        const hasColor = ['f_dc_0', 'f_dc_1', 'f_dc_2'].every(v => data.hasOwnProperty(v));
        const hasOpacity = data.hasOwnProperty('opacity');
        const dstSHBands = calcSHBands(new Set(Object.keys(data)));
        const dstSHCoeffs = shBandCoeffs[dstSHBands];
        const tmpSHData = dstSHBands ? new Float32Array(dstSHCoeffs) : null;
        const cacheMap = new Map();
        let cacheEntry;
        const read = (splat, i) => {
            // get the cached data entry for this splat
            if (splat !== cacheEntry?.splat) {
                if (!cacheMap.has(splat)) {
                    const transformCache = new SplatTransformCache(splat, serializeSettings.keepWorldTransform);
                    const srcPropNames = getVertexProperties(splat.splatData);
                    const srcSHBands = calcSHBands(srcPropNames);
                    const srcSHCoeffs = shBandCoeffs[srcSHBands];
                    // cache the props objects
                    const srcProps = {};
                    members.forEach((name) => {
                        const shIndex = shNames.indexOf(name);
                        if (shIndex >= 0) {
                            const a = Math.floor(shIndex / dstSHCoeffs);
                            const b = shIndex % dstSHCoeffs;
                            srcProps[name] = (b < srcSHCoeffs) ? splat.splatData.getProp(shNames[a * srcSHCoeffs + b]) : null;
                        }
                        else {
                            srcProps[name] = splat.splatData.getProp(name);
                        }
                    });
                    const { tintClr, temperature, saturation, brightness, blackPoint, whitePoint } = splat;
                    const hasTint = (!tintClr.equals(Color.WHITE) || temperature !== 0 || saturation !== 1 || brightness !== 1 || blackPoint !== 0 || whitePoint !== 1);
                    cacheEntry = { splat, transformCache, srcProps, hasTint };
                    cacheMap.set(splat, cacheEntry);
                }
                else {
                    cacheEntry = cacheMap.get(splat);
                }
            }
            const { transformCache, srcProps, hasTint } = cacheEntry;
            // copy members
            members.forEach((name) => {
                data[name] = srcProps[name]?.[i] ?? 0;
            });
            // apply transform palette transforms
            const mat = transformCache.getMat(i);
            if (hasPosition) {
                v$3.set(data.x, data.y, data.z);
                mat.transformPoint(v$3, v$3);
                [data.x, data.y, data.z] = [v$3.x, v$3.y, v$3.z];
            }
            if (hasRotation) {
                const quat = transformCache.getRot(i);
                q.set(data.rot_1, data.rot_2, data.rot_3, data.rot_0).mul2(quat, q);
                [data.rot_1, data.rot_2, data.rot_3, data.rot_0] = [q.x, q.y, q.z, q.w];
            }
            if (hasScale) {
                const scale = transformCache.getScale(i);
                data.scale_0 = Math.log(Math.exp(data.scale_0) * scale.x);
                data.scale_1 = Math.log(Math.exp(data.scale_1) * scale.y);
                data.scale_2 = Math.log(Math.exp(data.scale_2) * scale.z);
            }
            if (dstSHBands > 0) {
                for (let c = 0; c < 3; ++c) {
                    for (let d = 0; d < dstSHCoeffs; ++d) {
                        tmpSHData[d] = data[shNames[c * dstSHCoeffs + d]];
                    }
                    transformCache.getSHRot(i).apply(tmpSHData);
                    for (let d = 0; d < dstSHCoeffs; ++d) {
                        data[shNames[c * dstSHCoeffs + d]] = tmpSHData[d];
                    }
                }
            }
            if (!serializeSettings.keepColorTint && hasColor && hasTint) {
                const { tintClr, temperature, saturation, brightness, blackPoint, whitePoint } = splat;
                const SH_C0 = 0.28209479177387814;
                const to = (value) => value * SH_C0 + 0.5;
                const from = (value) => (value - 0.5) / SH_C0;
                const applyTransform = (c, s, offset) => {
                    // offset and scale
                    c.r = offset + c.r * s.r;
                    c.g = offset + c.g * s.g;
                    c.b = offset + c.b * s.b;
                    // saturation
                    const grey = c.r * 0.299 + c.g * 0.587 + c.b * 0.114;
                    c.r = grey + (c.r - grey) * saturation;
                    c.g = grey + (c.g - grey) * saturation;
                    c.b = grey + (c.b - grey) * saturation;
                };
                const offset = -blackPoint + brightness;
                const scale = 1 / (whitePoint - blackPoint);
                const s = {
                    r: scale * tintClr.r * (1 + temperature),
                    g: scale * tintClr.g,
                    b: scale * tintClr.b * (1 - temperature)
                };
                const c = {
                    r: to(data.f_dc_0),
                    g: to(data.f_dc_1),
                    b: to(data.f_dc_2)
                };
                applyTransform(c, s, offset);
                data.f_dc_0 = from(c.r);
                data.f_dc_1 = from(c.g);
                data.f_dc_2 = from(c.b);
                if (dstSHBands > 0) {
                    for (let d = 0; d < dstSHCoeffs; ++d) {
                        c.r = data[shNames[d]];
                        c.g = data[shNames[d + dstSHCoeffs]];
                        c.b = data[shNames[d + dstSHCoeffs * 2]];
                        applyTransform(c, s, 0);
                        data[shNames[d]] = c.r;
                        data[shNames[d + dstSHCoeffs]] = c.g;
                        data[shNames[d + dstSHCoeffs * 2]] = c.b;
                    }
                }
            }
            const { transparency } = splat;
            if (!serializeSettings.keepColorTint && hasOpacity && transparency !== 1) {
                const invSig = (value) => ((value <= 0) ? -400 : ((value >= 1) ? 400 : -Math.log(1 / value - 1)));
                data.opacity = invSig(sigmoid$1(data.opacity) * transparency);
            }
        };
        this.data = data;
        this.read = read;
    }
}
const serializePly = async (splats, serializeSettings, writer, progress) => {
    const { maxSHBands, keepStateData } = serializeSettings;
    // create filter and count total gaussians
    const filter = new GaussianFilter(serializeSettings);
    const totalGaussians = countGaussians(splats, filter);
    if (totalGaussians === 0) {
        return;
    }
    // this data is filtered out, as it holds internal editor state
    const internalProps = keepStateData ? ['transform'] : ['state', 'transform'];
    const props = getCommonProps(splats)
        // filter out internal props
        .filter(p => !internalProps.includes(p.name))
        // filter out max SH bands
        .filter((p) => {
        if (!p.name.startsWith('f_rest_')) {
            return true;
        }
        const i = parseInt(p.name.slice(7), 10);
        return i < [0, 9, 24, 45][maxSHBands ?? 3];
    });
    const headerText = [
        'ply',
        'format binary_little_endian 1.0',
        // FIXME: disable for now due to other tooling not supporting any header
        // `comment ${generatedByString}`,
        `element vertex ${totalGaussians}`,
        props.map(p => `property ${p.type} ${p.name}`),
        'end_header',
        ''
    ].flat().join('\n');
    const singleSplat = new SingleSplat(props.map(p => p.name), serializeSettings);
    const gaussianSizeBytes = props.reduce((tot, p) => tot + DataTypeSize(p.type), 0);
    const buf = new Uint8Array(1024 * gaussianSizeBytes);
    const dataView = new DataView(buf.buffer);
    let offset = 0;
    const header = new TextEncoder().encode(headerText);
    // construct a progress writer over the writer
    const progressWriter = new ProgressWriter(writer, header.byteLength + totalGaussians * gaussianSizeBytes, progress);
    // write encoded header
    await progressWriter.write(header);
    for (let e = 0; e < splats.length; ++e) {
        const splat = splats[e];
        const { splatData } = splat;
        filter.set(splat);
        for (let i = 0; i < splatData.numSplats; ++i) {
            if (!filter.test(i))
                continue;
            singleSplat.read(splat, i);
            // write
            for (let j = 0; j < props.length; ++j) {
                if (props[j].type === 'uchar') {
                    dataView.setUint8(offset, singleSplat.data[props[j].name]);
                    offset += 1;
                }
                else {
                    dataView.setFloat32(offset, singleSplat.data[props[j].name], true);
                    offset += 4;
                }
            }
            // buffer is full, write it to the output stream
            if (offset === buf.byteLength) {
                await progressWriter.write(buf);
                offset = 0;
            }
        }
    }
    // write the last (most likely partially filled) buf
    if (offset > 0) {
        await progressWriter.write(new Uint8Array(buf.buffer, 0, offset));
    }
    await progressWriter.close();
};
// process and compress a chunk of 256 splats
class Chunk {
    static members = [
        'x', 'y', 'z',
        'scale_0', 'scale_1', 'scale_2',
        'f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity',
        'rot_0', 'rot_1', 'rot_2', 'rot_3'
    ];
    size;
    data = {};
    // compressed data
    position;
    rotation;
    scale;
    color;
    constructor(size = 256) {
        this.size = size;
        Chunk.members.forEach((m) => {
            this.data[m] = new Float32Array(size);
        });
        this.position = new Uint32Array(size);
        this.rotation = new Uint32Array(size);
        this.scale = new Uint32Array(size);
        this.color = new Uint32Array(size);
    }
    set(index, splat) {
        Chunk.members.forEach((name) => {
            this.data[name][index] = splat.data[name];
        });
    }
    pack() {
        const calcMinMax = (data) => {
            let min;
            let max;
            min = max = data[0];
            for (let i = 1; i < data.length; ++i) {
                const v = data[i];
                min = Math.min(min, v);
                max = Math.max(max, v);
            }
            return { min, max };
        };
        const normalize = (x, min, max) => {
            if (x <= min)
                return 0;
            if (x >= max)
                return 1;
            return (max - min < 0.00001) ? 0 : (x - min) / (max - min);
        };
        const data = this.data;
        const x = data.x;
        const y = data.y;
        const z = data.z;
        const scale_0 = data.scale_0;
        const scale_1 = data.scale_1;
        const scale_2 = data.scale_2;
        const rot_0 = data.rot_0;
        const rot_1 = data.rot_1;
        const rot_2 = data.rot_2;
        const rot_3 = data.rot_3;
        const f_dc_0 = data.f_dc_0;
        const f_dc_1 = data.f_dc_1;
        const f_dc_2 = data.f_dc_2;
        const opacity = data.opacity;
        const px = calcMinMax(x);
        const py = calcMinMax(y);
        const pz = calcMinMax(z);
        const sx = calcMinMax(scale_0);
        const sy = calcMinMax(scale_1);
        const sz = calcMinMax(scale_2);
        // clamp scale because sometimes values are at infinity
        const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
        sx.min = clamp(sx.min, -20, 20);
        sx.max = clamp(sx.max, -20, 20);
        sy.min = clamp(sy.min, -20, 20);
        sy.max = clamp(sy.max, -20, 20);
        sz.min = clamp(sz.min, -20, 20);
        sz.max = clamp(sz.max, -20, 20);
        // convert f_dc_ to colors before calculating min/max and packaging
        const SH_C0 = 0.28209479177387814;
        for (let i = 0; i < f_dc_0.length; ++i) {
            f_dc_0[i] = f_dc_0[i] * SH_C0 + 0.5;
            f_dc_1[i] = f_dc_1[i] * SH_C0 + 0.5;
            f_dc_2[i] = f_dc_2[i] * SH_C0 + 0.5;
        }
        const cr = calcMinMax(f_dc_0);
        const cg = calcMinMax(f_dc_1);
        const cb = calcMinMax(f_dc_2);
        const packUnorm = (value, bits) => {
            const t = (1 << bits) - 1;
            return Math.max(0, Math.min(t, Math.floor(value * t + 0.5)));
        };
        const pack111011 = (x, y, z) => {
            return packUnorm(x, 11) << 21 |
                packUnorm(y, 10) << 11 |
                packUnorm(z, 11);
        };
        const pack8888 = (x, y, z, w) => {
            return packUnorm(x, 8) << 24 |
                packUnorm(y, 8) << 16 |
                packUnorm(z, 8) << 8 |
                packUnorm(w, 8);
        };
        // pack quaternion into 2,10,10,10
        const packRot = (x, y, z, w) => {
            q.set(x, y, z, w).normalize();
            const a = [q.x, q.y, q.z, q.w];
            const largest = a.reduce((curr, v, i) => (Math.abs(v) > Math.abs(a[curr]) ? i : curr), 0);
            if (a[largest] < 0) {
                a[0] = -a[0];
                a[1] = -a[1];
                a[2] = -a[2];
                a[3] = -a[3];
            }
            const norm = Math.sqrt(2) * 0.5;
            let result = largest;
            for (let i = 0; i < 4; ++i) {
                if (i !== largest) {
                    result = (result << 10) | packUnorm(a[i] * norm + 0.5, 10);
                }
            }
            return result;
        };
        // pack
        for (let i = 0; i < this.size; ++i) {
            this.position[i] = pack111011(normalize(x[i], px.min, px.max), normalize(y[i], py.min, py.max), normalize(z[i], pz.min, pz.max));
            this.rotation[i] = packRot(rot_0[i], rot_1[i], rot_2[i], rot_3[i]);
            this.scale[i] = pack111011(normalize(scale_0[i], sx.min, sx.max), normalize(scale_1[i], sy.min, sy.max), normalize(scale_2[i], sz.min, sz.max));
            this.color[i] = pack8888(normalize(f_dc_0[i], cr.min, cr.max), normalize(f_dc_1[i], cg.min, cg.max), normalize(f_dc_2[i], cb.min, cb.max), 1 / (1 + Math.exp(-opacity[i])));
        }
        return { px, py, pz, sx, sy, sz, cr, cg, cb };
    }
}
// sort the compressed indices into morton order
const sortSplats = (splats, indices) => {
    // https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
    const encodeMorton3 = (x, y, z) => {
        const Part1By2 = (x) => {
            x &= 0x000003ff;
            x = (x ^ (x << 16)) & 0xff0000ff;
            x = (x ^ (x << 8)) & 0x0300f00f;
            x = (x ^ (x << 4)) & 0x030c30c3;
            x = (x ^ (x << 2)) & 0x09249249;
            return x;
        };
        return (Part1By2(z) << 2) + (Part1By2(y) << 1) + Part1By2(x);
    };
    let minx;
    let miny;
    let minz;
    let maxx;
    let maxy;
    let maxz;
    // calculate scene extents across all splats (using sort centers, because they're in world space)
    for (let i = 0; i < splats.length; ++i) {
        const splat = splats[i];
        const splatData = splat.splatData;
        const state = splatData.getProp('state');
        const { centers } = splat.entity.gsplat.instance.sorter;
        for (let i = 0; i < splatData.numSplats; ++i) {
            if ((state[i] & State.deleted) === 0) {
                const x = centers[i * 3 + 0];
                const y = centers[i * 3 + 1];
                const z = centers[i * 3 + 2];
                if (minx === undefined) {
                    minx = maxx = x;
                    miny = maxy = y;
                    minz = maxz = z;
                }
                else {
                    if (x < minx)
                        minx = x;
                    else if (x > maxx)
                        maxx = x;
                    if (y < miny)
                        miny = y;
                    else if (y > maxy)
                        maxy = y;
                    if (z < minz)
                        minz = z;
                    else if (z > maxz)
                        maxz = z;
                }
            }
        }
    }
    const xlen = maxx - minx;
    const ylen = maxy - miny;
    const zlen = maxz - minz;
    const morton = new Uint32Array(indices.length);
    let idx = 0;
    for (let i = 0; i < splats.length; ++i) {
        const splat = splats[i];
        const splatData = splat.splatData;
        const state = splatData.getProp('state');
        const { centers } = splat.entity.gsplat.instance.sorter;
        for (let i = 0; i < splatData.numSplats; ++i) {
            if ((state[i] & State.deleted) === 0) {
                const x = centers[i * 3 + 0];
                const y = centers[i * 3 + 1];
                const z = centers[i * 3 + 2];
                const ix = Math.min(1023, Math.floor(1024 * (x - minx) / xlen));
                const iy = Math.min(1023, Math.floor(1024 * (y - miny) / ylen));
                const iz = Math.min(1023, Math.floor(1024 * (z - minz) / zlen));
                morton[idx++] = encodeMorton3(ix, iy, iz);
            }
        }
    }
    // order splats by morton code
    indices.sort((a, b) => morton[a.globalIndex] - morton[b.globalIndex]);
};
const serializePlyCompressed = async (splats, options, writer, progress) => {
    const { maxSHBands } = options;
    // create filter and count total gaussians
    const filter = new GaussianFilter(options);
    // make a list of indices spanning all splats (so we can sort them together)
    const indices = [];
    for (let splatIndex = 0; splatIndex < splats.length; ++splatIndex) {
        const splatData = splats[splatIndex].splatData;
        filter.set(splats[splatIndex]);
        for (let i = 0; i < splatData.numSplats; ++i) {
            if (filter.test(i)) {
                indices.push({ splatIndex, i, globalIndex: indices.length });
            }
        }
    }
    if (indices.length === 0) {
        console.error('nothing to export');
        return;
    }
    const numSplats = indices.length;
    const numChunks = Math.ceil(numSplats / 256);
    const chunkProps = [
        'min_x', 'min_y', 'min_z',
        'max_x', 'max_y', 'max_z',
        'min_scale_x', 'min_scale_y', 'min_scale_z',
        'max_scale_x', 'max_scale_y', 'max_scale_z',
        'min_r', 'min_g', 'min_b',
        'max_r', 'max_g', 'max_b'
    ];
    const vertexProps = [
        'packed_position',
        'packed_rotation',
        'packed_scale',
        'packed_color'
    ];
    // calculate the number of output bands given the scene splat data and
    // user-chosen maxSHBands
    const outputSHBands = (() => {
        const splatBands = splats.map(s => calcSHBands(getVertexProperties(s.splatData)));
        return Math.min(maxSHBands ?? 3, Math.max(...splatBands));
    })();
    const outputSHCoeffs = shBandCoeffs[outputSHBands];
    const shHeader = outputSHBands ? [
        `element sh ${numSplats}`,
        new Array(outputSHCoeffs * 3).fill('').map((_, i) => `property uchar f_rest_${i}`)
    ].flat() : [];
    const headerText = [
        'ply',
        'format binary_little_endian 1.0',
        `comment ${generatedByString}`,
        `element chunk ${numChunks}`,
        chunkProps.map(p => `property float ${p}`),
        `element vertex ${numSplats}`,
        vertexProps.map(p => `property uint ${p}`),
        shHeader,
        'end_header\n'
    ].flat().join('\n');
    // sort splats into some kind of order (morton order rn)
    sortSplats(splats, indices);
    const singleSplat = new SingleSplat([
        'x', 'y', 'z',
        'scale_0', 'scale_1', 'scale_2',
        'f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity',
        'rot_0', 'rot_1', 'rot_2', 'rot_3'
    ], options);
    const prepareChunk = (chunk, i) => {
        const num = Math.min(numSplats, (i + 1) * 256) - i * 256;
        for (let j = 0; j < num; ++j) {
            const index = indices[i * 256 + j];
            // read splat
            singleSplat.read(splats[index.splatIndex], index.i);
            // update chunk
            chunk.set(j, singleSplat);
        }
        // pad the end of the last chunk with duplicate data
        if (num < 256) {
            for (let j = num; j < 256; ++j) {
                chunk.set(j, singleSplat);
            }
        }
    };
    const header = new TextEncoder().encode(headerText);
    const totalBytes = header.byteLength +
        numChunks * chunkProps.length * 4 +
        numSplats * vertexProps.length * 4 +
        outputSHCoeffs * 3 * numSplats;
    const progressWriter = new ProgressWriter(writer, totalBytes, progress);
    // write the header
    await progressWriter.write(header);
    const chunk = new Chunk();
    // write chunks
    const chunkData = new Float32Array(18);
    const chunkDataUint8 = new Uint8Array(chunkData.buffer);
    for (let i = 0; i < numChunks; ++i) {
        prepareChunk(chunk, i);
        const result = chunk.pack();
        chunkData[0] = result.px.min;
        chunkData[1] = result.py.min;
        chunkData[2] = result.pz.min;
        chunkData[3] = result.px.max;
        chunkData[4] = result.py.max;
        chunkData[5] = result.pz.max;
        chunkData[6] = result.sx.min;
        chunkData[7] = result.sy.min;
        chunkData[8] = result.sz.min;
        chunkData[9] = result.sx.max;
        chunkData[10] = result.sy.max;
        chunkData[11] = result.sz.max;
        chunkData[12] = result.cr.min;
        chunkData[13] = result.cg.min;
        chunkData[14] = result.cb.min;
        chunkData[15] = result.cr.max;
        chunkData[16] = result.cg.max;
        chunkData[17] = result.cb.max;
        await progressWriter.write(chunkDataUint8);
    }
    // write vertices
    const vertexData = new Uint32Array(256 * 4);
    const vertexDataUint8 = new Uint8Array(vertexData.buffer);
    for (let i = 0; i < numChunks; ++i) {
        const num = Math.min(numSplats, (i + 1) * 256) - i * 256;
        prepareChunk(chunk, i);
        chunk.pack();
        // write vertex data
        for (let j = 0; j < num; ++j) {
            vertexData[j * 4 + 0] = chunk.position[j];
            vertexData[j * 4 + 1] = chunk.rotation[j];
            vertexData[j * 4 + 2] = chunk.scale[j];
            vertexData[j * 4 + 3] = chunk.color[j];
        }
        await progressWriter.write(num === 256 ? vertexDataUint8 : new Uint8Array(vertexData.buffer, 0, num * 4 * 4));
    }
    // write sh
    const singleSplatSH = new SingleSplat(shNames.slice(0, outputSHCoeffs * 3), options);
    const shData = new Uint8Array(outputSHCoeffs * 3 * 256);
    for (let i = 0; i < numChunks; ++i) {
        const num = Math.min(numSplats, (i + 1) * 256) - i * 256;
        for (let j = 0; j < num; ++j) {
            const index = indices[i * 256 + j];
            // read splat
            singleSplatSH.read(splats[index.splatIndex], index.i);
            // quantize and write sh data
            const offset = j * outputSHCoeffs * 3;
            for (let k = 0; k < outputSHCoeffs * 3; ++k) {
                const nvalue = singleSplatSH.data[shNames[k]] / 8 + 0.5;
                shData[offset + k] = Math.max(0, Math.min(255, Math.trunc(nvalue * 256)));
            }
        }
        await progressWriter.write(num === 256 ? shData : new Uint8Array(shData.buffer, 0, num * outputSHCoeffs * 3));
    }
    await progressWriter.close();
};
const serializeSplat = async (splats, options, writer) => {
    // create filter and count total gaussians
    const filter = new GaussianFilter(options);
    const totalGaussians = countGaussians(splats, filter);
    if (totalGaussians === 0) {
        return;
    }
    // position.xyz: float32, scale.xyz: float32, color.rgba: uint8, quaternion.ijkl: uint8
    const result = new Uint8Array(totalGaussians * 32);
    const dataView = new DataView(result.buffer);
    let idx = 0;
    const props = ['x', 'y', 'z', 'opacity', 'rot_0', 'rot_1', 'rot_2', 'rot_3', 'f_dc_0', 'f_dc_1', 'f_dc_2', 'scale_0', 'scale_1', 'scale_2'];
    const singleSplat = new SingleSplat(props, options);
    const { data } = singleSplat;
    const clamp = (x) => Math.max(0, Math.min(255, x));
    const SH_C0 = 0.28209479177387814;
    for (let e = 0; e < splats.length; ++e) {
        const splat = splats[e];
        const { splatData } = splat;
        filter.set(splat);
        for (let i = 0; i < splatData.numSplats; ++i) {
            if (!filter.test(i))
                continue;
            singleSplat.read(splat, i);
            const off = idx++ * 32;
            dataView.setFloat32(off + 0, data.x, true);
            dataView.setFloat32(off + 4, data.y, true);
            dataView.setFloat32(off + 8, data.z, true);
            dataView.setFloat32(off + 12, Math.exp(data.scale_0), true);
            dataView.setFloat32(off + 16, Math.exp(data.scale_1), true);
            dataView.setFloat32(off + 20, Math.exp(data.scale_2), true);
            dataView.setUint8(off + 24, clamp((0.5 + SH_C0 * data.f_dc_0) * 255));
            dataView.setUint8(off + 25, clamp((0.5 + SH_C0 * data.f_dc_1) * 255));
            dataView.setUint8(off + 26, clamp((0.5 + SH_C0 * data.f_dc_2) * 255));
            dataView.setUint8(off + 27, clamp((1 / (1 + Math.exp(-data.opacity))) * 255));
            dataView.setUint8(off + 28, clamp(data.rot_0 * 128 + 128));
            dataView.setUint8(off + 29, clamp(data.rot_1 * 128 + 128));
            dataView.setUint8(off + 30, clamp(data.rot_2 * 128 + 128));
            dataView.setUint8(off + 31, clamp(data.rot_3 * 128 + 128));
        }
    }
    await writer.write(result);
};
const encodeBase64 = (bytes) => {
    let binary = '';
    for (let i = 0; i < bytes.length; i++) {
        const thisBytes = bytes[i];
        const thisLen = thisBytes.byteLength;
        for (let j = 0; j < thisLen; j++) {
            binary += String.fromCharCode(thisBytes[j]);
        }
    }
    return window.btoa(binary);
};
const serializeViewer = async (splats, serializeSettings, options, writer) => {
    const { experienceSettings } = options;
    // create compressed PLY data
    const plyWriter = new BufferWriter();
    await serializePlyCompressed(splats, serializeSettings, plyWriter);
    const plyBuffers = plyWriter.close();
    if (options.type === 'html') {
        const pad = (text, spaces) => {
            const whitespace = ' '.repeat(spaces);
            return text.split('\n').map(line => whitespace + line).join('\n');
        };
        const style = '<link rel="stylesheet" href="./index.css">';
        const script = '<script type="module" src="./index.js"></script>';
        const settings = 'settings: fetch(settingsUrl).then(response => response.json())';
        const content = 'fetch(contentUrl)';
        const html = index$1
            .replace(style, `<style>\n${pad(index$2, 12)}\n        </style>`)
            .replace(script, `<script type="module">\n${pad(index, 12)}\n        </script>`)
            .replace(settings, `settings: ${JSON.stringify(experienceSettings)}`)
            .replace(content, `fetch("data:application/ply;base64,${encodeBase64(plyBuffers)}")`);
        await writer.write(new TextEncoder().encode(html));
    }
    else {
        const zipWriter = new ZipWriter(writer);
        await zipWriter.file('index.html', index$1);
        await zipWriter.file('index.css', index$2);
        await zipWriter.file('index.js', index);
        await zipWriter.file('settings.json', JSON.stringify(experienceSettings, null, 4));
        await zipWriter.file('scene.compressed.ply', plyBuffers);
        await zipWriter.close();
    }
};

const isString$1 = obj => typeof obj === 'string';
const defer = () => {
  let res;
  let rej;
  const promise = new Promise((resolve, reject) => {
    res = resolve;
    rej = reject;
  });
  promise.resolve = res;
  promise.reject = rej;
  return promise;
};
const makeString = object => {
  if (object == null) return '';
  return '' + object;
};
const copy = (a, s, t) => {
  a.forEach(m => {
    if (s[m]) t[m] = s[m];
  });
};
const lastOfPathSeparatorRegExp = /###/g;
const cleanKey = key => key && key.indexOf('###') > -1 ? key.replace(lastOfPathSeparatorRegExp, '.') : key;
const canNotTraverseDeeper = object => !object || isString$1(object);
const getLastOfPath = (object, path, Empty) => {
  const stack = !isString$1(path) ? path : path.split('.');
  let stackIndex = 0;
  while (stackIndex < stack.length - 1) {
    if (canNotTraverseDeeper(object)) return {};
    const key = cleanKey(stack[stackIndex]);
    if (!object[key] && Empty) object[key] = new Empty();
    if (Object.prototype.hasOwnProperty.call(object, key)) {
      object = object[key];
    } else {
      object = {};
    }
    ++stackIndex;
  }
  if (canNotTraverseDeeper(object)) return {};
  return {
    obj: object,
    k: cleanKey(stack[stackIndex])
  };
};
const setPath = (object, path, newValue) => {
  const {
    obj,
    k
  } = getLastOfPath(object, path, Object);
  if (obj !== undefined || path.length === 1) {
    obj[k] = newValue;
    return;
  }
  let e = path[path.length - 1];
  let p = path.slice(0, path.length - 1);
  let last = getLastOfPath(object, p, Object);
  while (last.obj === undefined && p.length) {
    e = `${p[p.length - 1]}.${e}`;
    p = p.slice(0, p.length - 1);
    last = getLastOfPath(object, p, Object);
    if (last?.obj && typeof last.obj[`${last.k}.${e}`] !== 'undefined') {
      last.obj = undefined;
    }
  }
  last.obj[`${last.k}.${e}`] = newValue;
};
const pushPath = (object, path, newValue, concat) => {
  const {
    obj,
    k
  } = getLastOfPath(object, path, Object);
  obj[k] = obj[k] || [];
  obj[k].push(newValue);
};
const getPath = (object, path) => {
  const {
    obj,
    k
  } = getLastOfPath(object, path);
  if (!obj) return undefined;
  if (!Object.prototype.hasOwnProperty.call(obj, k)) return undefined;
  return obj[k];
};
const getPathWithDefaults = (data, defaultData, key) => {
  const value = getPath(data, key);
  if (value !== undefined) {
    return value;
  }
  return getPath(defaultData, key);
};
const deepExtend = (target, source, overwrite) => {
  for (const prop in source) {
    if (prop !== '__proto__' && prop !== 'constructor') {
      if (prop in target) {
        if (isString$1(target[prop]) || target[prop] instanceof String || isString$1(source[prop]) || source[prop] instanceof String) {
          if (overwrite) target[prop] = source[prop];
        } else {
          deepExtend(target[prop], source[prop], overwrite);
        }
      } else {
        target[prop] = source[prop];
      }
    }
  }
  return target;
};
const regexEscape = str => str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
var _entityMap = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#39;',
  '/': '&#x2F;'
};
const escape$1 = data => {
  if (isString$1(data)) {
    return data.replace(/[&<>"'\/]/g, s => _entityMap[s]);
  }
  return data;
};
class RegExpCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.regExpMap = new Map();
    this.regExpQueue = [];
  }
  getRegExp(pattern) {
    const regExpFromCache = this.regExpMap.get(pattern);
    if (regExpFromCache !== undefined) {
      return regExpFromCache;
    }
    const regExpNew = new RegExp(pattern);
    if (this.regExpQueue.length === this.capacity) {
      this.regExpMap.delete(this.regExpQueue.shift());
    }
    this.regExpMap.set(pattern, regExpNew);
    this.regExpQueue.push(pattern);
    return regExpNew;
  }
}
const chars = [' ', ',', '?', '!', ';'];
const looksLikeObjectPathRegExpCache = new RegExpCache(20);
const looksLikeObjectPath = (key, nsSeparator, keySeparator) => {
  nsSeparator = nsSeparator || '';
  keySeparator = keySeparator || '';
  const possibleChars = chars.filter(c => nsSeparator.indexOf(c) < 0 && keySeparator.indexOf(c) < 0);
  if (possibleChars.length === 0) return true;
  const r = looksLikeObjectPathRegExpCache.getRegExp(`(${possibleChars.map(c => c === '?' ? '\\?' : c).join('|')})`);
  let matched = !r.test(key);
  if (!matched) {
    const ki = key.indexOf(keySeparator);
    if (ki > 0 && !r.test(key.substring(0, ki))) {
      matched = true;
    }
  }
  return matched;
};
const deepFind = (obj, path, keySeparator = '.') => {
  if (!obj) return undefined;
  if (obj[path]) {
    if (!Object.prototype.hasOwnProperty.call(obj, path)) return undefined;
    return obj[path];
  }
  const tokens = path.split(keySeparator);
  let current = obj;
  for (let i = 0; i < tokens.length;) {
    if (!current || typeof current !== 'object') {
      return undefined;
    }
    let next;
    let nextPath = '';
    for (let j = i; j < tokens.length; ++j) {
      if (j !== i) {
        nextPath += keySeparator;
      }
      nextPath += tokens[j];
      next = current[nextPath];
      if (next !== undefined) {
        if (['string', 'number', 'boolean'].indexOf(typeof next) > -1 && j < tokens.length - 1) {
          continue;
        }
        i += j - i + 1;
        break;
      }
    }
    current = next;
  }
  return current;
};
const getCleanedCode = code => code?.replace('_', '-');

const consoleLogger = {
  type: 'logger',
  log(args) {
    this.output('log', args);
  },
  warn(args) {
    this.output('warn', args);
  },
  error(args) {
    this.output('error', args);
  },
  output(type, args) {
    console?.[type]?.apply?.(console, args);
  }
};
class Logger {
  constructor(concreteLogger, options = {}) {
    this.init(concreteLogger, options);
  }
  init(concreteLogger, options = {}) {
    this.prefix = options.prefix || 'i18next:';
    this.logger = concreteLogger || consoleLogger;
    this.options = options;
    this.debug = options.debug;
  }
  log(...args) {
    return this.forward(args, 'log', '', true);
  }
  warn(...args) {
    return this.forward(args, 'warn', '', true);
  }
  error(...args) {
    return this.forward(args, 'error', '');
  }
  deprecate(...args) {
    return this.forward(args, 'warn', 'WARNING DEPRECATED: ', true);
  }
  forward(args, lvl, prefix, debugOnly) {
    if (debugOnly && !this.debug) return null;
    if (isString$1(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`;
    return this.logger[lvl](args);
  }
  create(moduleName) {
    return new Logger(this.logger, {
      ...{
        prefix: `${this.prefix}:${moduleName}:`
      },
      ...this.options
    });
  }
  clone(options) {
    options = options || this.options;
    options.prefix = options.prefix || this.prefix;
    return new Logger(this.logger, options);
  }
}
var baseLogger = new Logger();

class EventEmitter {
  constructor() {
    this.observers = {};
  }
  on(events, listener) {
    events.split(' ').forEach(event => {
      if (!this.observers[event]) this.observers[event] = new Map();
      const numListeners = this.observers[event].get(listener) || 0;
      this.observers[event].set(listener, numListeners + 1);
    });
    return this;
  }
  off(event, listener) {
    if (!this.observers[event]) return;
    if (!listener) {
      delete this.observers[event];
      return;
    }
    this.observers[event].delete(listener);
  }
  emit(event, ...args) {
    if (this.observers[event]) {
      const cloned = Array.from(this.observers[event].entries());
      cloned.forEach(([observer, numTimesAdded]) => {
        for (let i = 0; i < numTimesAdded; i++) {
          observer(...args);
        }
      });
    }
    if (this.observers['*']) {
      const cloned = Array.from(this.observers['*'].entries());
      cloned.forEach(([observer, numTimesAdded]) => {
        for (let i = 0; i < numTimesAdded; i++) {
          observer.apply(observer, [event, ...args]);
        }
      });
    }
  }
}

class ResourceStore extends EventEmitter {
  constructor(data, options = {
    ns: ['translation'],
    defaultNS: 'translation'
  }) {
    super();
    this.data = data || {};
    this.options = options;
    if (this.options.keySeparator === undefined) {
      this.options.keySeparator = '.';
    }
    if (this.options.ignoreJSONStructure === undefined) {
      this.options.ignoreJSONStructure = true;
    }
  }
  addNamespaces(ns) {
    if (this.options.ns.indexOf(ns) < 0) {
      this.options.ns.push(ns);
    }
  }
  removeNamespaces(ns) {
    const index = this.options.ns.indexOf(ns);
    if (index > -1) {
      this.options.ns.splice(index, 1);
    }
  }
  getResource(lng, ns, key, options = {}) {
    const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
    const ignoreJSONStructure = options.ignoreJSONStructure !== undefined ? options.ignoreJSONStructure : this.options.ignoreJSONStructure;
    let path;
    if (lng.indexOf('.') > -1) {
      path = lng.split('.');
    } else {
      path = [lng, ns];
      if (key) {
        if (Array.isArray(key)) {
          path.push(...key);
        } else if (isString$1(key) && keySeparator) {
          path.push(...key.split(keySeparator));
        } else {
          path.push(key);
        }
      }
    }
    const result = getPath(this.data, path);
    if (!result && !ns && !key && lng.indexOf('.') > -1) {
      lng = path[0];
      ns = path[1];
      key = path.slice(2).join('.');
    }
    if (result || !ignoreJSONStructure || !isString$1(key)) return result;
    return deepFind(this.data?.[lng]?.[ns], key, keySeparator);
  }
  addResource(lng, ns, key, value, options = {
    silent: false
  }) {
    const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
    let path = [lng, ns];
    if (key) path = path.concat(keySeparator ? key.split(keySeparator) : key);
    if (lng.indexOf('.') > -1) {
      path = lng.split('.');
      value = ns;
      ns = path[1];
    }
    this.addNamespaces(ns);
    setPath(this.data, path, value);
    if (!options.silent) this.emit('added', lng, ns, key, value);
  }
  addResources(lng, ns, resources, options = {
    silent: false
  }) {
    for (const m in resources) {
      if (isString$1(resources[m]) || Array.isArray(resources[m])) this.addResource(lng, ns, m, resources[m], {
        silent: true
      });
    }
    if (!options.silent) this.emit('added', lng, ns, resources);
  }
  addResourceBundle(lng, ns, resources, deep, overwrite, options = {
    silent: false,
    skipCopy: false
  }) {
    let path = [lng, ns];
    if (lng.indexOf('.') > -1) {
      path = lng.split('.');
      deep = resources;
      resources = ns;
      ns = path[1];
    }
    this.addNamespaces(ns);
    let pack = getPath(this.data, path) || {};
    if (!options.skipCopy) resources = JSON.parse(JSON.stringify(resources));
    if (deep) {
      deepExtend(pack, resources, overwrite);
    } else {
      pack = {
        ...pack,
        ...resources
      };
    }
    setPath(this.data, path, pack);
    if (!options.silent) this.emit('added', lng, ns, resources);
  }
  removeResourceBundle(lng, ns) {
    if (this.hasResourceBundle(lng, ns)) {
      delete this.data[lng][ns];
    }
    this.removeNamespaces(ns);
    this.emit('removed', lng, ns);
  }
  hasResourceBundle(lng, ns) {
    return this.getResource(lng, ns) !== undefined;
  }
  getResourceBundle(lng, ns) {
    if (!ns) ns = this.options.defaultNS;
    return this.getResource(lng, ns);
  }
  getDataByLanguage(lng) {
    return this.data[lng];
  }
  hasLanguageSomeTranslations(lng) {
    const data = this.getDataByLanguage(lng);
    const n = data && Object.keys(data) || [];
    return !!n.find(v => data[v] && Object.keys(data[v]).length > 0);
  }
  toJSON() {
    return this.data;
  }
}

var postProcessor = {
  processors: {},
  addPostProcessor(module) {
    this.processors[module.name] = module;
  },
  handle(processors, value, key, options, translator) {
    processors.forEach(processor => {
      value = this.processors[processor]?.process(value, key, options, translator) ?? value;
    });
    return value;
  }
};

const PATH_KEY = Symbol('i18next/PATH_KEY');
function createProxy() {
  const state = [];
  const handler = Object.create(null);
  let proxy;
  handler.get = (target, key) => {
    proxy?.revoke?.();
    if (key === PATH_KEY) return state;
    state.push(key);
    proxy = Proxy.revocable(target, handler);
    return proxy.proxy;
  };
  return Proxy.revocable(Object.create(null), handler).proxy;
}
function keysFromSelector(selector, opts) {
  const {
    [PATH_KEY]: path
  } = selector(createProxy());
  return path.join(opts?.keySeparator ?? '.');
}

const checkedLoadedFor = {};
const shouldHandleAsObject = res => !isString$1(res) && typeof res !== 'boolean' && typeof res !== 'number';
class Translator extends EventEmitter {
  constructor(services, options = {}) {
    super();
    copy(['resourceStore', 'languageUtils', 'pluralResolver', 'interpolator', 'backendConnector', 'i18nFormat', 'utils'], services, this);
    this.options = options;
    if (this.options.keySeparator === undefined) {
      this.options.keySeparator = '.';
    }
    this.logger = baseLogger.create('translator');
  }
  changeLanguage(lng) {
    if (lng) this.language = lng;
  }
  exists(key, o = {
    interpolation: {}
  }) {
    const opt = {
      ...o
    };
    if (key == null) return false;
    const resolved = this.resolve(key, opt);
    return resolved?.res !== undefined;
  }
  extractFromKey(key, opt) {
    let nsSeparator = opt.nsSeparator !== undefined ? opt.nsSeparator : this.options.nsSeparator;
    if (nsSeparator === undefined) nsSeparator = ':';
    const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator;
    let namespaces = opt.ns || this.options.defaultNS || [];
    const wouldCheckForNsInKey = nsSeparator && key.indexOf(nsSeparator) > -1;
    const seemsNaturalLanguage = !this.options.userDefinedKeySeparator && !opt.keySeparator && !this.options.userDefinedNsSeparator && !opt.nsSeparator && !looksLikeObjectPath(key, nsSeparator, keySeparator);
    if (wouldCheckForNsInKey && !seemsNaturalLanguage) {
      const m = key.match(this.interpolator.nestingRegexp);
      if (m && m.length > 0) {
        return {
          key,
          namespaces: isString$1(namespaces) ? [namespaces] : namespaces
        };
      }
      const parts = key.split(nsSeparator);
      if (nsSeparator !== keySeparator || nsSeparator === keySeparator && this.options.ns.indexOf(parts[0]) > -1) namespaces = parts.shift();
      key = parts.join(keySeparator);
    }
    return {
      key,
      namespaces: isString$1(namespaces) ? [namespaces] : namespaces
    };
  }
  translate(keys, o, lastKey) {
    let opt = typeof o === 'object' ? {
      ...o
    } : o;
    if (typeof opt !== 'object' && this.options.overloadTranslationOptionHandler) {
      opt = this.options.overloadTranslationOptionHandler(arguments);
    }
    if (typeof opt === 'object') opt = {
      ...opt
    };
    if (!opt) opt = {};
    if (keys == null) return '';
    if (typeof keys === 'function') keys = keysFromSelector(keys, {
      ...this.options,
      ...opt
    });
    if (!Array.isArray(keys)) keys = [String(keys)];
    const returnDetails = opt.returnDetails !== undefined ? opt.returnDetails : this.options.returnDetails;
    const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator;
    const {
      key,
      namespaces
    } = this.extractFromKey(keys[keys.length - 1], opt);
    const namespace = namespaces[namespaces.length - 1];
    let nsSeparator = opt.nsSeparator !== undefined ? opt.nsSeparator : this.options.nsSeparator;
    if (nsSeparator === undefined) nsSeparator = ':';
    const lng = opt.lng || this.language;
    const appendNamespaceToCIMode = opt.appendNamespaceToCIMode || this.options.appendNamespaceToCIMode;
    if (lng?.toLowerCase() === 'cimode') {
      if (appendNamespaceToCIMode) {
        if (returnDetails) {
          return {
            res: `${namespace}${nsSeparator}${key}`,
            usedKey: key,
            exactUsedKey: key,
            usedLng: lng,
            usedNS: namespace,
            usedParams: this.getUsedParamsDetails(opt)
          };
        }
        return `${namespace}${nsSeparator}${key}`;
      }
      if (returnDetails) {
        return {
          res: key,
          usedKey: key,
          exactUsedKey: key,
          usedLng: lng,
          usedNS: namespace,
          usedParams: this.getUsedParamsDetails(opt)
        };
      }
      return key;
    }
    const resolved = this.resolve(keys, opt);
    let res = resolved?.res;
    const resUsedKey = resolved?.usedKey || key;
    const resExactUsedKey = resolved?.exactUsedKey || key;
    const noObject = ['[object Number]', '[object Function]', '[object RegExp]'];
    const joinArrays = opt.joinArrays !== undefined ? opt.joinArrays : this.options.joinArrays;
    const handleAsObjectInI18nFormat = !this.i18nFormat || this.i18nFormat.handleAsObject;
    const needsPluralHandling = opt.count !== undefined && !isString$1(opt.count);
    const hasDefaultValue = Translator.hasDefaultValue(opt);
    const defaultValueSuffix = needsPluralHandling ? this.pluralResolver.getSuffix(lng, opt.count, opt) : '';
    const defaultValueSuffixOrdinalFallback = opt.ordinal && needsPluralHandling ? this.pluralResolver.getSuffix(lng, opt.count, {
      ordinal: false
    }) : '';
    const needsZeroSuffixLookup = needsPluralHandling && !opt.ordinal && opt.count === 0;
    const defaultValue = needsZeroSuffixLookup && opt[`defaultValue${this.options.pluralSeparator}zero`] || opt[`defaultValue${defaultValueSuffix}`] || opt[`defaultValue${defaultValueSuffixOrdinalFallback}`] || opt.defaultValue;
    let resForObjHndl = res;
    if (handleAsObjectInI18nFormat && !res && hasDefaultValue) {
      resForObjHndl = defaultValue;
    }
    const handleAsObject = shouldHandleAsObject(resForObjHndl);
    const resType = Object.prototype.toString.apply(resForObjHndl);
    if (handleAsObjectInI18nFormat && resForObjHndl && handleAsObject && noObject.indexOf(resType) < 0 && !(isString$1(joinArrays) && Array.isArray(resForObjHndl))) {
      if (!opt.returnObjects && !this.options.returnObjects) {
        if (!this.options.returnedObjectHandler) {
          this.logger.warn('accessing an object - but returnObjects options is not enabled!');
        }
        const r = this.options.returnedObjectHandler ? this.options.returnedObjectHandler(resUsedKey, resForObjHndl, {
          ...opt,
          ns: namespaces
        }) : `key '${key} (${this.language})' returned an object instead of string.`;
        if (returnDetails) {
          resolved.res = r;
          resolved.usedParams = this.getUsedParamsDetails(opt);
          return resolved;
        }
        return r;
      }
      if (keySeparator) {
        const resTypeIsArray = Array.isArray(resForObjHndl);
        const copy = resTypeIsArray ? [] : {};
        const newKeyToUse = resTypeIsArray ? resExactUsedKey : resUsedKey;
        for (const m in resForObjHndl) {
          if (Object.prototype.hasOwnProperty.call(resForObjHndl, m)) {
            const deepKey = `${newKeyToUse}${keySeparator}${m}`;
            if (hasDefaultValue && !res) {
              copy[m] = this.translate(deepKey, {
                ...opt,
                defaultValue: shouldHandleAsObject(defaultValue) ? defaultValue[m] : undefined,
                ...{
                  joinArrays: false,
                  ns: namespaces
                }
              });
            } else {
              copy[m] = this.translate(deepKey, {
                ...opt,
                ...{
                  joinArrays: false,
                  ns: namespaces
                }
              });
            }
            if (copy[m] === deepKey) copy[m] = resForObjHndl[m];
          }
        }
        res = copy;
      }
    } else if (handleAsObjectInI18nFormat && isString$1(joinArrays) && Array.isArray(res)) {
      res = res.join(joinArrays);
      if (res) res = this.extendTranslation(res, keys, opt, lastKey);
    } else {
      let usedDefault = false;
      let usedKey = false;
      if (!this.isValidLookup(res) && hasDefaultValue) {
        usedDefault = true;
        res = defaultValue;
      }
      if (!this.isValidLookup(res)) {
        usedKey = true;
        res = key;
      }
      const missingKeyNoValueFallbackToKey = opt.missingKeyNoValueFallbackToKey || this.options.missingKeyNoValueFallbackToKey;
      const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res;
      const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing;
      if (usedKey || usedDefault || updateMissing) {
        this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? defaultValue : res);
        if (keySeparator) {
          const fk = this.resolve(key, {
            ...opt,
            keySeparator: false
          });
          if (fk && fk.res) this.logger.warn('Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.');
        }
        let lngs = [];
        const fallbackLngs = this.languageUtils.getFallbackCodes(this.options.fallbackLng, opt.lng || this.language);
        if (this.options.saveMissingTo === 'fallback' && fallbackLngs && fallbackLngs[0]) {
          for (let i = 0; i < fallbackLngs.length; i++) {
            lngs.push(fallbackLngs[i]);
          }
        } else if (this.options.saveMissingTo === 'all') {
          lngs = this.languageUtils.toResolveHierarchy(opt.lng || this.language);
        } else {
          lngs.push(opt.lng || this.language);
        }
        const send = (l, k, specificDefaultValue) => {
          const defaultForMissing = hasDefaultValue && specificDefaultValue !== res ? specificDefaultValue : resForMissing;
          if (this.options.missingKeyHandler) {
            this.options.missingKeyHandler(l, namespace, k, defaultForMissing, updateMissing, opt);
          } else if (this.backendConnector?.saveMissing) {
            this.backendConnector.saveMissing(l, namespace, k, defaultForMissing, updateMissing, opt);
          }
          this.emit('missingKey', l, namespace, k, res);
        };
        if (this.options.saveMissing) {
          if (this.options.saveMissingPlurals && needsPluralHandling) {
            lngs.forEach(language => {
              const suffixes = this.pluralResolver.getSuffixes(language, opt);
              if (needsZeroSuffixLookup && opt[`defaultValue${this.options.pluralSeparator}zero`] && suffixes.indexOf(`${this.options.pluralSeparator}zero`) < 0) {
                suffixes.push(`${this.options.pluralSeparator}zero`);
              }
              suffixes.forEach(suffix => {
                send([language], key + suffix, opt[`defaultValue${suffix}`] || defaultValue);
              });
            });
          } else {
            send(lngs, key, defaultValue);
          }
        }
      }
      res = this.extendTranslation(res, keys, opt, resolved, lastKey);
      if (usedKey && res === key && this.options.appendNamespaceToMissingKey) {
        res = `${namespace}${nsSeparator}${key}`;
      }
      if ((usedKey || usedDefault) && this.options.parseMissingKeyHandler) {
        res = this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey ? `${namespace}${nsSeparator}${key}` : key, usedDefault ? res : undefined, opt);
      }
    }
    if (returnDetails) {
      resolved.res = res;
      resolved.usedParams = this.getUsedParamsDetails(opt);
      return resolved;
    }
    return res;
  }
  extendTranslation(res, key, opt, resolved, lastKey) {
    if (this.i18nFormat?.parse) {
      res = this.i18nFormat.parse(res, {
        ...this.options.interpolation.defaultVariables,
        ...opt
      }, opt.lng || this.language || resolved.usedLng, resolved.usedNS, resolved.usedKey, {
        resolved
      });
    } else if (!opt.skipInterpolation) {
      if (opt.interpolation) this.interpolator.init({
        ...opt,
        ...{
          interpolation: {
            ...this.options.interpolation,
            ...opt.interpolation
          }
        }
      });
      const skipOnVariables = isString$1(res) && (opt?.interpolation?.skipOnVariables !== undefined ? opt.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables);
      let nestBef;
      if (skipOnVariables) {
        const nb = res.match(this.interpolator.nestingRegexp);
        nestBef = nb && nb.length;
      }
      let data = opt.replace && !isString$1(opt.replace) ? opt.replace : opt;
      if (this.options.interpolation.defaultVariables) data = {
        ...this.options.interpolation.defaultVariables,
        ...data
      };
      res = this.interpolator.interpolate(res, data, opt.lng || this.language || resolved.usedLng, opt);
      if (skipOnVariables) {
        const na = res.match(this.interpolator.nestingRegexp);
        const nestAft = na && na.length;
        if (nestBef < nestAft) opt.nest = false;
      }
      if (!opt.lng && resolved && resolved.res) opt.lng = this.language || resolved.usedLng;
      if (opt.nest !== false) res = this.interpolator.nest(res, (...args) => {
        if (lastKey?.[0] === args[0] && !opt.context) {
          this.logger.warn(`It seems you are nesting recursively key: ${args[0]} in key: ${key[0]}`);
          return null;
        }
        return this.translate(...args, key);
      }, opt);
      if (opt.interpolation) this.interpolator.reset();
    }
    const postProcess = opt.postProcess || this.options.postProcess;
    const postProcessorNames = isString$1(postProcess) ? [postProcess] : postProcess;
    if (res != null && postProcessorNames?.length && opt.applyPostProcessor !== false) {
      res = postProcessor.handle(postProcessorNames, res, key, this.options && this.options.postProcessPassResolved ? {
        i18nResolved: {
          ...resolved,
          usedParams: this.getUsedParamsDetails(opt)
        },
        ...opt
      } : opt, this);
    }
    return res;
  }
  resolve(keys, opt = {}) {
    let found;
    let usedKey;
    let exactUsedKey;
    let usedLng;
    let usedNS;
    if (isString$1(keys)) keys = [keys];
    keys.forEach(k => {
      if (this.isValidLookup(found)) return;
      const extracted = this.extractFromKey(k, opt);
      const key = extracted.key;
      usedKey = key;
      let namespaces = extracted.namespaces;
      if (this.options.fallbackNS) namespaces = namespaces.concat(this.options.fallbackNS);
      const needsPluralHandling = opt.count !== undefined && !isString$1(opt.count);
      const needsZeroSuffixLookup = needsPluralHandling && !opt.ordinal && opt.count === 0;
      const needsContextHandling = opt.context !== undefined && (isString$1(opt.context) || typeof opt.context === 'number') && opt.context !== '';
      const codes = opt.lngs ? opt.lngs : this.languageUtils.toResolveHierarchy(opt.lng || this.language, opt.fallbackLng);
      namespaces.forEach(ns => {
        if (this.isValidLookup(found)) return;
        usedNS = ns;
        if (!checkedLoadedFor[`${codes[0]}-${ns}`] && this.utils?.hasLoadedNamespace && !this.utils?.hasLoadedNamespace(usedNS)) {
          checkedLoadedFor[`${codes[0]}-${ns}`] = true;
          this.logger.warn(`key "${usedKey}" for languages "${codes.join(', ')}" won't get resolved as namespace "${usedNS}" was not yet loaded`, 'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!');
        }
        codes.forEach(code => {
          if (this.isValidLookup(found)) return;
          usedLng = code;
          const finalKeys = [key];
          if (this.i18nFormat?.addLookupKeys) {
            this.i18nFormat.addLookupKeys(finalKeys, key, code, ns, opt);
          } else {
            let pluralSuffix;
            if (needsPluralHandling) pluralSuffix = this.pluralResolver.getSuffix(code, opt.count, opt);
            const zeroSuffix = `${this.options.pluralSeparator}zero`;
            const ordinalPrefix = `${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;
            if (needsPluralHandling) {
              if (opt.ordinal && pluralSuffix.indexOf(ordinalPrefix) === 0) {
                finalKeys.push(key + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator));
              }
              finalKeys.push(key + pluralSuffix);
              if (needsZeroSuffixLookup) {
                finalKeys.push(key + zeroSuffix);
              }
            }
            if (needsContextHandling) {
              const contextKey = `${key}${this.options.contextSeparator || '_'}${opt.context}`;
              finalKeys.push(contextKey);
              if (needsPluralHandling) {
                if (opt.ordinal && pluralSuffix.indexOf(ordinalPrefix) === 0) {
                  finalKeys.push(contextKey + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator));
                }
                finalKeys.push(contextKey + pluralSuffix);
                if (needsZeroSuffixLookup) {
                  finalKeys.push(contextKey + zeroSuffix);
                }
              }
            }
          }
          let possibleKey;
          while (possibleKey = finalKeys.pop()) {
            if (!this.isValidLookup(found)) {
              exactUsedKey = possibleKey;
              found = this.getResource(code, ns, possibleKey, opt);
            }
          }
        });
      });
    });
    return {
      res: found,
      usedKey,
      exactUsedKey,
      usedLng,
      usedNS
    };
  }
  isValidLookup(res) {
    return res !== undefined && !(!this.options.returnNull && res === null) && !(!this.options.returnEmptyString && res === '');
  }
  getResource(code, ns, key, options = {}) {
    if (this.i18nFormat?.getResource) return this.i18nFormat.getResource(code, ns, key, options);
    return this.resourceStore.getResource(code, ns, key, options);
  }
  getUsedParamsDetails(options = {}) {
    const optionsKeys = ['defaultValue', 'ordinal', 'context', 'replace', 'lng', 'lngs', 'fallbackLng', 'ns', 'keySeparator', 'nsSeparator', 'returnObjects', 'returnDetails', 'joinArrays', 'postProcess', 'interpolation'];
    const useOptionsReplaceForData = options.replace && !isString$1(options.replace);
    let data = useOptionsReplaceForData ? options.replace : options;
    if (useOptionsReplaceForData && typeof options.count !== 'undefined') {
      data.count = options.count;
    }
    if (this.options.interpolation.defaultVariables) {
      data = {
        ...this.options.interpolation.defaultVariables,
        ...data
      };
    }
    if (!useOptionsReplaceForData) {
      data = {
        ...data
      };
      for (const key of optionsKeys) {
        delete data[key];
      }
    }
    return data;
  }
  static hasDefaultValue(options) {
    const prefix = 'defaultValue';
    for (const option in options) {
      if (Object.prototype.hasOwnProperty.call(options, option) && prefix === option.substring(0, prefix.length) && undefined !== options[option]) {
        return true;
      }
    }
    return false;
  }
}

class LanguageUtil {
  constructor(options) {
    this.options = options;
    this.supportedLngs = this.options.supportedLngs || false;
    this.logger = baseLogger.create('languageUtils');
  }
  getScriptPartFromCode(code) {
    code = getCleanedCode(code);
    if (!code || code.indexOf('-') < 0) return null;
    const p = code.split('-');
    if (p.length === 2) return null;
    p.pop();
    if (p[p.length - 1].toLowerCase() === 'x') return null;
    return this.formatLanguageCode(p.join('-'));
  }
  getLanguagePartFromCode(code) {
    code = getCleanedCode(code);
    if (!code || code.indexOf('-') < 0) return code;
    const p = code.split('-');
    return this.formatLanguageCode(p[0]);
  }
  formatLanguageCode(code) {
    if (isString$1(code) && code.indexOf('-') > -1) {
      let formattedCode;
      try {
        formattedCode = Intl.getCanonicalLocales(code)[0];
      } catch (e) {}
      if (formattedCode && this.options.lowerCaseLng) {
        formattedCode = formattedCode.toLowerCase();
      }
      if (formattedCode) return formattedCode;
      if (this.options.lowerCaseLng) {
        return code.toLowerCase();
      }
      return code;
    }
    return this.options.cleanCode || this.options.lowerCaseLng ? code.toLowerCase() : code;
  }
  isSupportedCode(code) {
    if (this.options.load === 'languageOnly' || this.options.nonExplicitSupportedLngs) {
      code = this.getLanguagePartFromCode(code);
    }
    return !this.supportedLngs || !this.supportedLngs.length || this.supportedLngs.indexOf(code) > -1;
  }
  getBestMatchFromCodes(codes) {
    if (!codes) return null;
    let found;
    codes.forEach(code => {
      if (found) return;
      const cleanedLng = this.formatLanguageCode(code);
      if (!this.options.supportedLngs || this.isSupportedCode(cleanedLng)) found = cleanedLng;
    });
    if (!found && this.options.supportedLngs) {
      codes.forEach(code => {
        if (found) return;
        const lngScOnly = this.getScriptPartFromCode(code);
        if (this.isSupportedCode(lngScOnly)) return found = lngScOnly;
        const lngOnly = this.getLanguagePartFromCode(code);
        if (this.isSupportedCode(lngOnly)) return found = lngOnly;
        found = this.options.supportedLngs.find(supportedLng => {
          if (supportedLng === lngOnly) return supportedLng;
          if (supportedLng.indexOf('-') < 0 && lngOnly.indexOf('-') < 0) return;
          if (supportedLng.indexOf('-') > 0 && lngOnly.indexOf('-') < 0 && supportedLng.substring(0, supportedLng.indexOf('-')) === lngOnly) return supportedLng;
          if (supportedLng.indexOf(lngOnly) === 0 && lngOnly.length > 1) return supportedLng;
        });
      });
    }
    if (!found) found = this.getFallbackCodes(this.options.fallbackLng)[0];
    return found;
  }
  getFallbackCodes(fallbacks, code) {
    if (!fallbacks) return [];
    if (typeof fallbacks === 'function') fallbacks = fallbacks(code);
    if (isString$1(fallbacks)) fallbacks = [fallbacks];
    if (Array.isArray(fallbacks)) return fallbacks;
    if (!code) return fallbacks.default || [];
    let found = fallbacks[code];
    if (!found) found = fallbacks[this.getScriptPartFromCode(code)];
    if (!found) found = fallbacks[this.formatLanguageCode(code)];
    if (!found) found = fallbacks[this.getLanguagePartFromCode(code)];
    if (!found) found = fallbacks.default;
    return found || [];
  }
  toResolveHierarchy(code, fallbackCode) {
    const fallbackCodes = this.getFallbackCodes((fallbackCode === false ? [] : fallbackCode) || this.options.fallbackLng || [], code);
    const codes = [];
    const addCode = c => {
      if (!c) return;
      if (this.isSupportedCode(c)) {
        codes.push(c);
      } else {
        this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`);
      }
    };
    if (isString$1(code) && (code.indexOf('-') > -1 || code.indexOf('_') > -1)) {
      if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code));
      if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly') addCode(this.getScriptPartFromCode(code));
      if (this.options.load !== 'currentOnly') addCode(this.getLanguagePartFromCode(code));
    } else if (isString$1(code)) {
      addCode(this.formatLanguageCode(code));
    }
    fallbackCodes.forEach(fc => {
      if (codes.indexOf(fc) < 0) addCode(this.formatLanguageCode(fc));
    });
    return codes;
  }
}

const suffixesOrder = {
  zero: 0,
  one: 1,
  two: 2,
  few: 3,
  many: 4,
  other: 5
};
const dummyRule = {
  select: count => count === 1 ? 'one' : 'other',
  resolvedOptions: () => ({
    pluralCategories: ['one', 'other']
  })
};
class PluralResolver {
  constructor(languageUtils, options = {}) {
    this.languageUtils = languageUtils;
    this.options = options;
    this.logger = baseLogger.create('pluralResolver');
    this.pluralRulesCache = {};
  }
  addRule(lng, obj) {
    this.rules[lng] = obj;
  }
  clearCache() {
    this.pluralRulesCache = {};
  }
  getRule(code, options = {}) {
    const cleanedCode = getCleanedCode(code === 'dev' ? 'en' : code);
    const type = options.ordinal ? 'ordinal' : 'cardinal';
    const cacheKey = JSON.stringify({
      cleanedCode,
      type
    });
    if (cacheKey in this.pluralRulesCache) {
      return this.pluralRulesCache[cacheKey];
    }
    let rule;
    try {
      rule = new Intl.PluralRules(cleanedCode, {
        type
      });
    } catch (err) {
      if (!Intl) {
        this.logger.error('No Intl support, please use an Intl polyfill!');
        return dummyRule;
      }
      if (!code.match(/-|_/)) return dummyRule;
      const lngPart = this.languageUtils.getLanguagePartFromCode(code);
      rule = this.getRule(lngPart, options);
    }
    this.pluralRulesCache[cacheKey] = rule;
    return rule;
  }
  needsPlural(code, options = {}) {
    let rule = this.getRule(code, options);
    if (!rule) rule = this.getRule('dev', options);
    return rule?.resolvedOptions().pluralCategories.length > 1;
  }
  getPluralFormsOfKey(code, key, options = {}) {
    return this.getSuffixes(code, options).map(suffix => `${key}${suffix}`);
  }
  getSuffixes(code, options = {}) {
    let rule = this.getRule(code, options);
    if (!rule) rule = this.getRule('dev', options);
    if (!rule) return [];
    return rule.resolvedOptions().pluralCategories.sort((pluralCategory1, pluralCategory2) => suffixesOrder[pluralCategory1] - suffixesOrder[pluralCategory2]).map(pluralCategory => `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${pluralCategory}`);
  }
  getSuffix(code, count, options = {}) {
    const rule = this.getRule(code, options);
    if (rule) {
      return `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${rule.select(count)}`;
    }
    this.logger.warn(`no plural rule found for: ${code}`);
    return this.getSuffix('dev', count, options);
  }
}

const deepFindWithDefaults = (data, defaultData, key, keySeparator = '.', ignoreJSONStructure = true) => {
  let path = getPathWithDefaults(data, defaultData, key);
  if (!path && ignoreJSONStructure && isString$1(key)) {
    path = deepFind(data, key, keySeparator);
    if (path === undefined) path = deepFind(defaultData, key, keySeparator);
  }
  return path;
};
const regexSafe = val => val.replace(/\$/g, '$$$$');
class Interpolator {
  constructor(options = {}) {
    this.logger = baseLogger.create('interpolator');
    this.options = options;
    this.format = options?.interpolation?.format || (value => value);
    this.init(options);
  }
  init(options = {}) {
    if (!options.interpolation) options.interpolation = {
      escapeValue: true
    };
    const {
      escape: escape$1$1,
      escapeValue,
      useRawValueToEscape,
      prefix,
      prefixEscaped,
      suffix,
      suffixEscaped,
      formatSeparator,
      unescapeSuffix,
      unescapePrefix,
      nestingPrefix,
      nestingPrefixEscaped,
      nestingSuffix,
      nestingSuffixEscaped,
      nestingOptionsSeparator,
      maxReplaces,
      alwaysFormat
    } = options.interpolation;
    this.escape = escape$1$1 !== undefined ? escape$1$1 : escape$1;
    this.escapeValue = escapeValue !== undefined ? escapeValue : true;
    this.useRawValueToEscape = useRawValueToEscape !== undefined ? useRawValueToEscape : false;
    this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
    this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
    this.formatSeparator = formatSeparator || ',';
    this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
    this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
    this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
    this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
    this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
    this.maxReplaces = maxReplaces || 1000;
    this.alwaysFormat = alwaysFormat !== undefined ? alwaysFormat : false;
    this.resetRegExp();
  }
  reset() {
    if (this.options) this.init(this.options);
  }
  resetRegExp() {
    const getOrResetRegExp = (existingRegExp, pattern) => {
      if (existingRegExp?.source === pattern) {
        existingRegExp.lastIndex = 0;
        return existingRegExp;
      }
      return new RegExp(pattern, 'g');
    };
    this.regexp = getOrResetRegExp(this.regexp, `${this.prefix}(.+?)${this.suffix}`);
    this.regexpUnescape = getOrResetRegExp(this.regexpUnescape, `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`);
    this.nestingRegexp = getOrResetRegExp(this.nestingRegexp, `${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`);
  }
  interpolate(str, data, lng, options) {
    let match;
    let value;
    let replaces;
    const defaultData = this.options && this.options.interpolation && this.options.interpolation.defaultVariables || {};
    const handleFormat = key => {
      if (key.indexOf(this.formatSeparator) < 0) {
        const path = deepFindWithDefaults(data, defaultData, key, this.options.keySeparator, this.options.ignoreJSONStructure);
        return this.alwaysFormat ? this.format(path, undefined, lng, {
          ...options,
          ...data,
          interpolationkey: key
        }) : path;
      }
      const p = key.split(this.formatSeparator);
      const k = p.shift().trim();
      const f = p.join(this.formatSeparator).trim();
      return this.format(deepFindWithDefaults(data, defaultData, k, this.options.keySeparator, this.options.ignoreJSONStructure), f, lng, {
        ...options,
        ...data,
        interpolationkey: k
      });
    };
    this.resetRegExp();
    const missingInterpolationHandler = options?.missingInterpolationHandler || this.options.missingInterpolationHandler;
    const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
    const todos = [{
      regex: this.regexpUnescape,
      safeValue: val => regexSafe(val)
    }, {
      regex: this.regexp,
      safeValue: val => this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)
    }];
    todos.forEach(todo => {
      replaces = 0;
      while (match = todo.regex.exec(str)) {
        const matchedVar = match[1].trim();
        value = handleFormat(matchedVar);
        if (value === undefined) {
          if (typeof missingInterpolationHandler === 'function') {
            const temp = missingInterpolationHandler(str, match, options);
            value = isString$1(temp) ? temp : '';
          } else if (options && Object.prototype.hasOwnProperty.call(options, matchedVar)) {
            value = '';
          } else if (skipOnVariables) {
            value = match[0];
            continue;
          } else {
            this.logger.warn(`missed to pass in variable ${matchedVar} for interpolating ${str}`);
            value = '';
          }
        } else if (!isString$1(value) && !this.useRawValueToEscape) {
          value = makeString(value);
        }
        const safeValue = todo.safeValue(value);
        str = str.replace(match[0], safeValue);
        if (skipOnVariables) {
          todo.regex.lastIndex += value.length;
          todo.regex.lastIndex -= match[0].length;
        } else {
          todo.regex.lastIndex = 0;
        }
        replaces++;
        if (replaces >= this.maxReplaces) {
          break;
        }
      }
    });
    return str;
  }
  nest(str, fc, options = {}) {
    let match;
    let value;
    let clonedOptions;
    const handleHasOptions = (key, inheritedOptions) => {
      const sep = this.nestingOptionsSeparator;
      if (key.indexOf(sep) < 0) return key;
      const c = key.split(new RegExp(`${sep}[ ]*{`));
      let optionsString = `{${c[1]}`;
      key = c[0];
      optionsString = this.interpolate(optionsString, clonedOptions);
      const matchedSingleQuotes = optionsString.match(/'/g);
      const matchedDoubleQuotes = optionsString.match(/"/g);
      if ((matchedSingleQuotes?.length ?? 0) % 2 === 0 && !matchedDoubleQuotes || matchedDoubleQuotes.length % 2 !== 0) {
        optionsString = optionsString.replace(/'/g, '"');
      }
      try {
        clonedOptions = JSON.parse(optionsString);
        if (inheritedOptions) clonedOptions = {
          ...inheritedOptions,
          ...clonedOptions
        };
      } catch (e) {
        this.logger.warn(`failed parsing options string in nesting for key ${key}`, e);
        return `${key}${sep}${optionsString}`;
      }
      if (clonedOptions.defaultValue && clonedOptions.defaultValue.indexOf(this.prefix) > -1) delete clonedOptions.defaultValue;
      return key;
    };
    while (match = this.nestingRegexp.exec(str)) {
      let formatters = [];
      clonedOptions = {
        ...options
      };
      clonedOptions = clonedOptions.replace && !isString$1(clonedOptions.replace) ? clonedOptions.replace : clonedOptions;
      clonedOptions.applyPostProcessor = false;
      delete clonedOptions.defaultValue;
      const keyEndIndex = /{.*}/.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
      if (keyEndIndex !== -1) {
        formatters = match[1].slice(keyEndIndex).split(this.formatSeparator).map(elem => elem.trim()).filter(Boolean);
        match[1] = match[1].slice(0, keyEndIndex);
      }
      value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);
      if (value && match[0] === str && !isString$1(value)) return value;
      if (!isString$1(value)) value = makeString(value);
      if (!value) {
        this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
        value = '';
      }
      if (formatters.length) {
        value = formatters.reduce((v, f) => this.format(v, f, options.lng, {
          ...options,
          interpolationkey: match[1].trim()
        }), value.trim());
      }
      str = str.replace(match[0], value);
      this.regexp.lastIndex = 0;
    }
    return str;
  }
}

const parseFormatStr = formatStr => {
  let formatName = formatStr.toLowerCase().trim();
  const formatOptions = {};
  if (formatStr.indexOf('(') > -1) {
    const p = formatStr.split('(');
    formatName = p[0].toLowerCase().trim();
    const optStr = p[1].substring(0, p[1].length - 1);
    if (formatName === 'currency' && optStr.indexOf(':') < 0) {
      if (!formatOptions.currency) formatOptions.currency = optStr.trim();
    } else if (formatName === 'relativetime' && optStr.indexOf(':') < 0) {
      if (!formatOptions.range) formatOptions.range = optStr.trim();
    } else {
      const opts = optStr.split(';');
      opts.forEach(opt => {
        if (opt) {
          const [key, ...rest] = opt.split(':');
          const val = rest.join(':').trim().replace(/^'+|'+$/g, '');
          const trimmedKey = key.trim();
          if (!formatOptions[trimmedKey]) formatOptions[trimmedKey] = val;
          if (val === 'false') formatOptions[trimmedKey] = false;
          if (val === 'true') formatOptions[trimmedKey] = true;
          if (!isNaN(val)) formatOptions[trimmedKey] = parseInt(val, 10);
        }
      });
    }
  }
  return {
    formatName,
    formatOptions
  };
};
const createCachedFormatter = fn => {
  const cache = {};
  return (v, l, o) => {
    let optForCache = o;
    if (o && o.interpolationkey && o.formatParams && o.formatParams[o.interpolationkey] && o[o.interpolationkey]) {
      optForCache = {
        ...optForCache,
        [o.interpolationkey]: undefined
      };
    }
    const key = l + JSON.stringify(optForCache);
    let frm = cache[key];
    if (!frm) {
      frm = fn(getCleanedCode(l), o);
      cache[key] = frm;
    }
    return frm(v);
  };
};
const createNonCachedFormatter = fn => (v, l, o) => fn(getCleanedCode(l), o)(v);
class Formatter {
  constructor(options = {}) {
    this.logger = baseLogger.create('formatter');
    this.options = options;
    this.init(options);
  }
  init(services, options = {
    interpolation: {}
  }) {
    this.formatSeparator = options.interpolation.formatSeparator || ',';
    const cf = options.cacheInBuiltFormats ? createCachedFormatter : createNonCachedFormatter;
    this.formats = {
      number: cf((lng, opt) => {
        const formatter = new Intl.NumberFormat(lng, {
          ...opt
        });
        return val => formatter.format(val);
      }),
      currency: cf((lng, opt) => {
        const formatter = new Intl.NumberFormat(lng, {
          ...opt,
          style: 'currency'
        });
        return val => formatter.format(val);
      }),
      datetime: cf((lng, opt) => {
        const formatter = new Intl.DateTimeFormat(lng, {
          ...opt
        });
        return val => formatter.format(val);
      }),
      relativetime: cf((lng, opt) => {
        const formatter = new Intl.RelativeTimeFormat(lng, {
          ...opt
        });
        return val => formatter.format(val, opt.range || 'day');
      }),
      list: cf((lng, opt) => {
        const formatter = new Intl.ListFormat(lng, {
          ...opt
        });
        return val => formatter.format(val);
      })
    };
  }
  add(name, fc) {
    this.formats[name.toLowerCase().trim()] = fc;
  }
  addCached(name, fc) {
    this.formats[name.toLowerCase().trim()] = createCachedFormatter(fc);
  }
  format(value, format, lng, options = {}) {
    const formats = format.split(this.formatSeparator);
    if (formats.length > 1 && formats[0].indexOf('(') > 1 && formats[0].indexOf(')') < 0 && formats.find(f => f.indexOf(')') > -1)) {
      const lastIndex = formats.findIndex(f => f.indexOf(')') > -1);
      formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator);
    }
    const result = formats.reduce((mem, f) => {
      const {
        formatName,
        formatOptions
      } = parseFormatStr(f);
      if (this.formats[formatName]) {
        let formatted = mem;
        try {
          const valOptions = options?.formatParams?.[options.interpolationkey] || {};
          const l = valOptions.locale || valOptions.lng || options.locale || options.lng || lng;
          formatted = this.formats[formatName](mem, l, {
            ...formatOptions,
            ...options,
            ...valOptions
          });
        } catch (error) {
          this.logger.warn(error);
        }
        return formatted;
      } else {
        this.logger.warn(`there was no format function for ${formatName}`);
      }
      return mem;
    }, value);
    return result;
  }
}

const removePending = (q, name) => {
  if (q.pending[name] !== undefined) {
    delete q.pending[name];
    q.pendingCount--;
  }
};
class Connector extends EventEmitter {
  constructor(backend, store, services, options = {}) {
    super();
    this.backend = backend;
    this.store = store;
    this.services = services;
    this.languageUtils = services.languageUtils;
    this.options = options;
    this.logger = baseLogger.create('backendConnector');
    this.waitingReads = [];
    this.maxParallelReads = options.maxParallelReads || 10;
    this.readingCalls = 0;
    this.maxRetries = options.maxRetries >= 0 ? options.maxRetries : 5;
    this.retryTimeout = options.retryTimeout >= 1 ? options.retryTimeout : 350;
    this.state = {};
    this.queue = [];
    this.backend?.init?.(services, options.backend, options);
  }
  queueLoad(languages, namespaces, options, callback) {
    const toLoad = {};
    const pending = {};
    const toLoadLanguages = {};
    const toLoadNamespaces = {};
    languages.forEach(lng => {
      let hasAllNamespaces = true;
      namespaces.forEach(ns => {
        const name = `${lng}|${ns}`;
        if (!options.reload && this.store.hasResourceBundle(lng, ns)) {
          this.state[name] = 2;
        } else if (this.state[name] < 0) ; else if (this.state[name] === 1) {
          if (pending[name] === undefined) pending[name] = true;
        } else {
          this.state[name] = 1;
          hasAllNamespaces = false;
          if (pending[name] === undefined) pending[name] = true;
          if (toLoad[name] === undefined) toLoad[name] = true;
          if (toLoadNamespaces[ns] === undefined) toLoadNamespaces[ns] = true;
        }
      });
      if (!hasAllNamespaces) toLoadLanguages[lng] = true;
    });
    if (Object.keys(toLoad).length || Object.keys(pending).length) {
      this.queue.push({
        pending,
        pendingCount: Object.keys(pending).length,
        loaded: {},
        errors: [],
        callback
      });
    }
    return {
      toLoad: Object.keys(toLoad),
      pending: Object.keys(pending),
      toLoadLanguages: Object.keys(toLoadLanguages),
      toLoadNamespaces: Object.keys(toLoadNamespaces)
    };
  }
  loaded(name, err, data) {
    const s = name.split('|');
    const lng = s[0];
    const ns = s[1];
    if (err) this.emit('failedLoading', lng, ns, err);
    if (!err && data) {
      this.store.addResourceBundle(lng, ns, data, undefined, undefined, {
        skipCopy: true
      });
    }
    this.state[name] = err ? -1 : 2;
    if (err && data) this.state[name] = 0;
    const loaded = {};
    this.queue.forEach(q => {
      pushPath(q.loaded, [lng], ns);
      removePending(q, name);
      if (err) q.errors.push(err);
      if (q.pendingCount === 0 && !q.done) {
        Object.keys(q.loaded).forEach(l => {
          if (!loaded[l]) loaded[l] = {};
          const loadedKeys = q.loaded[l];
          if (loadedKeys.length) {
            loadedKeys.forEach(n => {
              if (loaded[l][n] === undefined) loaded[l][n] = true;
            });
          }
        });
        q.done = true;
        if (q.errors.length) {
          q.callback(q.errors);
        } else {
          q.callback();
        }
      }
    });
    this.emit('loaded', loaded);
    this.queue = this.queue.filter(q => !q.done);
  }
  read(lng, ns, fcName, tried = 0, wait = this.retryTimeout, callback) {
    if (!lng.length) return callback(null, {});
    if (this.readingCalls >= this.maxParallelReads) {
      this.waitingReads.push({
        lng,
        ns,
        fcName,
        tried,
        wait,
        callback
      });
      return;
    }
    this.readingCalls++;
    const resolver = (err, data) => {
      this.readingCalls--;
      if (this.waitingReads.length > 0) {
        const next = this.waitingReads.shift();
        this.read(next.lng, next.ns, next.fcName, next.tried, next.wait, next.callback);
      }
      if (err && data && tried < this.maxRetries) {
        setTimeout(() => {
          this.read.call(this, lng, ns, fcName, tried + 1, wait * 2, callback);
        }, wait);
        return;
      }
      callback(err, data);
    };
    const fc = this.backend[fcName].bind(this.backend);
    if (fc.length === 2) {
      try {
        const r = fc(lng, ns);
        if (r && typeof r.then === 'function') {
          r.then(data => resolver(null, data)).catch(resolver);
        } else {
          resolver(null, r);
        }
      } catch (err) {
        resolver(err);
      }
      return;
    }
    return fc(lng, ns, resolver);
  }
  prepareLoading(languages, namespaces, options = {}, callback) {
    if (!this.backend) {
      this.logger.warn('No backend was added via i18next.use. Will not load resources.');
      return callback && callback();
    }
    if (isString$1(languages)) languages = this.languageUtils.toResolveHierarchy(languages);
    if (isString$1(namespaces)) namespaces = [namespaces];
    const toLoad = this.queueLoad(languages, namespaces, options, callback);
    if (!toLoad.toLoad.length) {
      if (!toLoad.pending.length) callback();
      return null;
    }
    toLoad.toLoad.forEach(name => {
      this.loadOne(name);
    });
  }
  load(languages, namespaces, callback) {
    this.prepareLoading(languages, namespaces, {}, callback);
  }
  reload(languages, namespaces, callback) {
    this.prepareLoading(languages, namespaces, {
      reload: true
    }, callback);
  }
  loadOne(name, prefix = '') {
    const s = name.split('|');
    const lng = s[0];
    const ns = s[1];
    this.read(lng, ns, 'read', undefined, undefined, (err, data) => {
      if (err) this.logger.warn(`${prefix}loading namespace ${ns} for language ${lng} failed`, err);
      if (!err && data) this.logger.log(`${prefix}loaded namespace ${ns} for language ${lng}`, data);
      this.loaded(name, err, data);
    });
  }
  saveMissing(languages, namespace, key, fallbackValue, isUpdate, options = {}, clb = () => {}) {
    if (this.services?.utils?.hasLoadedNamespace && !this.services?.utils?.hasLoadedNamespace(namespace)) {
      this.logger.warn(`did not save key "${key}" as the namespace "${namespace}" was not yet loaded`, 'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!');
      return;
    }
    if (key === undefined || key === null || key === '') return;
    if (this.backend?.create) {
      const opts = {
        ...options,
        isUpdate
      };
      const fc = this.backend.create.bind(this.backend);
      if (fc.length < 6) {
        try {
          let r;
          if (fc.length === 5) {
            r = fc(languages, namespace, key, fallbackValue, opts);
          } else {
            r = fc(languages, namespace, key, fallbackValue);
          }
          if (r && typeof r.then === 'function') {
            r.then(data => clb(null, data)).catch(clb);
          } else {
            clb(null, r);
          }
        } catch (err) {
          clb(err);
        }
      } else {
        fc(languages, namespace, key, fallbackValue, clb, opts);
      }
    }
    if (!languages || !languages[0]) return;
    this.store.addResource(languages[0], namespace, key, fallbackValue);
  }
}

const get = () => ({
  debug: false,
  initAsync: true,
  ns: ['translation'],
  defaultNS: ['translation'],
  fallbackLng: ['dev'],
  fallbackNS: false,
  supportedLngs: false,
  nonExplicitSupportedLngs: false,
  load: 'all',
  preload: false,
  simplifyPluralSuffix: true,
  keySeparator: '.',
  nsSeparator: ':',
  pluralSeparator: '_',
  contextSeparator: '_',
  partialBundledLanguages: false,
  saveMissing: false,
  updateMissing: false,
  saveMissingTo: 'fallback',
  saveMissingPlurals: true,
  missingKeyHandler: false,
  missingInterpolationHandler: false,
  postProcess: false,
  postProcessPassResolved: false,
  returnNull: false,
  returnEmptyString: true,
  returnObjects: false,
  joinArrays: false,
  returnedObjectHandler: false,
  parseMissingKeyHandler: false,
  appendNamespaceToMissingKey: false,
  appendNamespaceToCIMode: false,
  overloadTranslationOptionHandler: args => {
    let ret = {};
    if (typeof args[1] === 'object') ret = args[1];
    if (isString$1(args[1])) ret.defaultValue = args[1];
    if (isString$1(args[2])) ret.tDescription = args[2];
    if (typeof args[2] === 'object' || typeof args[3] === 'object') {
      const options = args[3] || args[2];
      Object.keys(options).forEach(key => {
        ret[key] = options[key];
      });
    }
    return ret;
  },
  interpolation: {
    escapeValue: true,
    format: value => value,
    prefix: '{{',
    suffix: '}}',
    formatSeparator: ',',
    unescapePrefix: '-',
    nestingPrefix: '$t(',
    nestingSuffix: ')',
    nestingOptionsSeparator: ',',
    maxReplaces: 1000,
    skipOnVariables: true
  },
  cacheInBuiltFormats: true
});
const transformOptions = options => {
  if (isString$1(options.ns)) options.ns = [options.ns];
  if (isString$1(options.fallbackLng)) options.fallbackLng = [options.fallbackLng];
  if (isString$1(options.fallbackNS)) options.fallbackNS = [options.fallbackNS];
  if (options.supportedLngs?.indexOf?.('cimode') < 0) {
    options.supportedLngs = options.supportedLngs.concat(['cimode']);
  }
  if (typeof options.initImmediate === 'boolean') options.initAsync = options.initImmediate;
  return options;
};

const noop = () => {};
const bindMemberFunctions = inst => {
  const mems = Object.getOwnPropertyNames(Object.getPrototypeOf(inst));
  mems.forEach(mem => {
    if (typeof inst[mem] === 'function') {
      inst[mem] = inst[mem].bind(inst);
    }
  });
};
class I18n extends EventEmitter {
  constructor(options = {}, callback) {
    super();
    this.options = transformOptions(options);
    this.services = {};
    this.logger = baseLogger;
    this.modules = {
      external: []
    };
    bindMemberFunctions(this);
    if (callback && !this.isInitialized && !options.isClone) {
      if (!this.options.initAsync) {
        this.init(options, callback);
        return this;
      }
      setTimeout(() => {
        this.init(options, callback);
      }, 0);
    }
  }
  init(options = {}, callback) {
    this.isInitializing = true;
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    if (options.defaultNS == null && options.ns) {
      if (isString$1(options.ns)) {
        options.defaultNS = options.ns;
      } else if (options.ns.indexOf('translation') < 0) {
        options.defaultNS = options.ns[0];
      }
    }
    const defOpts = get();
    this.options = {
      ...defOpts,
      ...this.options,
      ...transformOptions(options)
    };
    this.options.interpolation = {
      ...defOpts.interpolation,
      ...this.options.interpolation
    };
    if (options.keySeparator !== undefined) {
      this.options.userDefinedKeySeparator = options.keySeparator;
    }
    if (options.nsSeparator !== undefined) {
      this.options.userDefinedNsSeparator = options.nsSeparator;
    }
    const createClassOnDemand = ClassOrObject => {
      if (!ClassOrObject) return null;
      if (typeof ClassOrObject === 'function') return new ClassOrObject();
      return ClassOrObject;
    };
    if (!this.options.isClone) {
      if (this.modules.logger) {
        baseLogger.init(createClassOnDemand(this.modules.logger), this.options);
      } else {
        baseLogger.init(null, this.options);
      }
      let formatter;
      if (this.modules.formatter) {
        formatter = this.modules.formatter;
      } else {
        formatter = Formatter;
      }
      const lu = new LanguageUtil(this.options);
      this.store = new ResourceStore(this.options.resources, this.options);
      const s = this.services;
      s.logger = baseLogger;
      s.resourceStore = this.store;
      s.languageUtils = lu;
      s.pluralResolver = new PluralResolver(lu, {
        prepend: this.options.pluralSeparator,
        simplifyPluralSuffix: this.options.simplifyPluralSuffix
      });
      const usingLegacyFormatFunction = this.options.interpolation.format && this.options.interpolation.format !== defOpts.interpolation.format;
      if (usingLegacyFormatFunction) {
        this.logger.deprecate(`init: you are still using the legacy format function, please use the new approach: https://www.i18next.com/translation-function/formatting`);
      }
      if (formatter && (!this.options.interpolation.format || this.options.interpolation.format === defOpts.interpolation.format)) {
        s.formatter = createClassOnDemand(formatter);
        if (s.formatter.init) s.formatter.init(s, this.options);
        this.options.interpolation.format = s.formatter.format.bind(s.formatter);
      }
      s.interpolator = new Interpolator(this.options);
      s.utils = {
        hasLoadedNamespace: this.hasLoadedNamespace.bind(this)
      };
      s.backendConnector = new Connector(createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options);
      s.backendConnector.on('*', (event, ...args) => {
        this.emit(event, ...args);
      });
      if (this.modules.languageDetector) {
        s.languageDetector = createClassOnDemand(this.modules.languageDetector);
        if (s.languageDetector.init) s.languageDetector.init(s, this.options.detection, this.options);
      }
      if (this.modules.i18nFormat) {
        s.i18nFormat = createClassOnDemand(this.modules.i18nFormat);
        if (s.i18nFormat.init) s.i18nFormat.init(this);
      }
      this.translator = new Translator(this.services, this.options);
      this.translator.on('*', (event, ...args) => {
        this.emit(event, ...args);
      });
      this.modules.external.forEach(m => {
        if (m.init) m.init(this);
      });
    }
    this.format = this.options.interpolation.format;
    if (!callback) callback = noop;
    if (this.options.fallbackLng && !this.services.languageDetector && !this.options.lng) {
      const codes = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
      if (codes.length > 0 && codes[0] !== 'dev') this.options.lng = codes[0];
    }
    if (!this.services.languageDetector && !this.options.lng) {
      this.logger.warn('init: no languageDetector is used and no lng is defined');
    }
    const storeApi = ['getResource', 'hasResourceBundle', 'getResourceBundle', 'getDataByLanguage'];
    storeApi.forEach(fcName => {
      this[fcName] = (...args) => this.store[fcName](...args);
    });
    const storeApiChained = ['addResource', 'addResources', 'addResourceBundle', 'removeResourceBundle'];
    storeApiChained.forEach(fcName => {
      this[fcName] = (...args) => {
        this.store[fcName](...args);
        return this;
      };
    });
    const deferred = defer();
    const load = () => {
      const finish = (err, t) => {
        this.isInitializing = false;
        if (this.isInitialized && !this.initializedStoreOnce) this.logger.warn('init: i18next is already initialized. You should call init just once!');
        this.isInitialized = true;
        if (!this.options.isClone) this.logger.log('initialized', this.options);
        this.emit('initialized', this.options);
        deferred.resolve(t);
        callback(err, t);
      };
      if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this));
      this.changeLanguage(this.options.lng, finish);
    };
    if (this.options.resources || !this.options.initAsync) {
      load();
    } else {
      setTimeout(load, 0);
    }
    return deferred;
  }
  loadResources(language, callback = noop) {
    let usedCallback = callback;
    const usedLng = isString$1(language) ? language : this.language;
    if (typeof language === 'function') usedCallback = language;
    if (!this.options.resources || this.options.partialBundledLanguages) {
      if (usedLng?.toLowerCase() === 'cimode' && (!this.options.preload || this.options.preload.length === 0)) return usedCallback();
      const toLoad = [];
      const append = lng => {
        if (!lng) return;
        if (lng === 'cimode') return;
        const lngs = this.services.languageUtils.toResolveHierarchy(lng);
        lngs.forEach(l => {
          if (l === 'cimode') return;
          if (toLoad.indexOf(l) < 0) toLoad.push(l);
        });
      };
      if (!usedLng) {
        const fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
        fallbacks.forEach(l => append(l));
      } else {
        append(usedLng);
      }
      this.options.preload?.forEach?.(l => append(l));
      this.services.backendConnector.load(toLoad, this.options.ns, e => {
        if (!e && !this.resolvedLanguage && this.language) this.setResolvedLanguage(this.language);
        usedCallback(e);
      });
    } else {
      usedCallback(null);
    }
  }
  reloadResources(lngs, ns, callback) {
    const deferred = defer();
    if (typeof lngs === 'function') {
      callback = lngs;
      lngs = undefined;
    }
    if (typeof ns === 'function') {
      callback = ns;
      ns = undefined;
    }
    if (!lngs) lngs = this.languages;
    if (!ns) ns = this.options.ns;
    if (!callback) callback = noop;
    this.services.backendConnector.reload(lngs, ns, err => {
      deferred.resolve();
      callback(err);
    });
    return deferred;
  }
  use(module) {
    if (!module) throw new Error('You are passing an undefined module! Please check the object you are passing to i18next.use()');
    if (!module.type) throw new Error('You are passing a wrong module! Please check the object you are passing to i18next.use()');
    if (module.type === 'backend') {
      this.modules.backend = module;
    }
    if (module.type === 'logger' || module.log && module.warn && module.error) {
      this.modules.logger = module;
    }
    if (module.type === 'languageDetector') {
      this.modules.languageDetector = module;
    }
    if (module.type === 'i18nFormat') {
      this.modules.i18nFormat = module;
    }
    if (module.type === 'postProcessor') {
      postProcessor.addPostProcessor(module);
    }
    if (module.type === 'formatter') {
      this.modules.formatter = module;
    }
    if (module.type === '3rdParty') {
      this.modules.external.push(module);
    }
    return this;
  }
  setResolvedLanguage(l) {
    if (!l || !this.languages) return;
    if (['cimode', 'dev'].indexOf(l) > -1) return;
    for (let li = 0; li < this.languages.length; li++) {
      const lngInLngs = this.languages[li];
      if (['cimode', 'dev'].indexOf(lngInLngs) > -1) continue;
      if (this.store.hasLanguageSomeTranslations(lngInLngs)) {
        this.resolvedLanguage = lngInLngs;
        break;
      }
    }
    if (!this.resolvedLanguage && this.languages.indexOf(l) < 0 && this.store.hasLanguageSomeTranslations(l)) {
      this.resolvedLanguage = l;
      this.languages.unshift(l);
    }
  }
  changeLanguage(lng, callback) {
    this.isLanguageChangingTo = lng;
    const deferred = defer();
    this.emit('languageChanging', lng);
    const setLngProps = l => {
      this.language = l;
      this.languages = this.services.languageUtils.toResolveHierarchy(l);
      this.resolvedLanguage = undefined;
      this.setResolvedLanguage(l);
    };
    const done = (err, l) => {
      if (l) {
        if (this.isLanguageChangingTo === lng) {
          setLngProps(l);
          this.translator.changeLanguage(l);
          this.isLanguageChangingTo = undefined;
          this.emit('languageChanged', l);
          this.logger.log('languageChanged', l);
        }
      } else {
        this.isLanguageChangingTo = undefined;
      }
      deferred.resolve((...args) => this.t(...args));
      if (callback) callback(err, (...args) => this.t(...args));
    };
    const setLng = lngs => {
      if (!lng && !lngs && this.services.languageDetector) lngs = [];
      const fl = isString$1(lngs) ? lngs : lngs && lngs[0];
      const l = this.store.hasLanguageSomeTranslations(fl) ? fl : this.services.languageUtils.getBestMatchFromCodes(isString$1(lngs) ? [lngs] : lngs);
      if (l) {
        if (!this.language) {
          setLngProps(l);
        }
        if (!this.translator.language) this.translator.changeLanguage(l);
        this.services.languageDetector?.cacheUserLanguage?.(l);
      }
      this.loadResources(l, err => {
        done(err, l);
      });
    };
    if (!lng && this.services.languageDetector && !this.services.languageDetector.async) {
      setLng(this.services.languageDetector.detect());
    } else if (!lng && this.services.languageDetector && this.services.languageDetector.async) {
      if (this.services.languageDetector.detect.length === 0) {
        this.services.languageDetector.detect().then(setLng);
      } else {
        this.services.languageDetector.detect(setLng);
      }
    } else {
      setLng(lng);
    }
    return deferred;
  }
  getFixedT(lng, ns, keyPrefix) {
    const fixedT = (key, opts, ...rest) => {
      let o;
      if (typeof opts !== 'object') {
        o = this.options.overloadTranslationOptionHandler([key, opts].concat(rest));
      } else {
        o = {
          ...opts
        };
      }
      o.lng = o.lng || fixedT.lng;
      o.lngs = o.lngs || fixedT.lngs;
      o.ns = o.ns || fixedT.ns;
      if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
      const keySeparator = this.options.keySeparator || '.';
      let resultKey;
      if (o.keyPrefix && Array.isArray(key)) {
        resultKey = key.map(k => {
          if (typeof k === 'function') k = keysFromSelector(k, {
            ...this.options,
            ...opts
          });
          return `${o.keyPrefix}${keySeparator}${k}`;
        });
      } else {
        if (typeof key === 'function') key = keysFromSelector(key, {
          ...this.options,
          ...opts
        });
        resultKey = o.keyPrefix ? `${o.keyPrefix}${keySeparator}${key}` : key;
      }
      return this.t(resultKey, o);
    };
    if (isString$1(lng)) {
      fixedT.lng = lng;
    } else {
      fixedT.lngs = lng;
    }
    fixedT.ns = ns;
    fixedT.keyPrefix = keyPrefix;
    return fixedT;
  }
  t(...args) {
    return this.translator?.translate(...args);
  }
  exists(...args) {
    return this.translator?.exists(...args);
  }
  setDefaultNamespace(ns) {
    this.options.defaultNS = ns;
  }
  hasLoadedNamespace(ns, options = {}) {
    if (!this.isInitialized) {
      this.logger.warn('hasLoadedNamespace: i18next was not initialized', this.languages);
      return false;
    }
    if (!this.languages || !this.languages.length) {
      this.logger.warn('hasLoadedNamespace: i18n.languages were undefined or empty', this.languages);
      return false;
    }
    const lng = options.lng || this.resolvedLanguage || this.languages[0];
    const fallbackLng = this.options ? this.options.fallbackLng : false;
    const lastLng = this.languages[this.languages.length - 1];
    if (lng.toLowerCase() === 'cimode') return true;
    const loadNotPending = (l, n) => {
      const loadState = this.services.backendConnector.state[`${l}|${n}`];
      return loadState === -1 || loadState === 0 || loadState === 2;
    };
    if (options.precheck) {
      const preResult = options.precheck(this, loadNotPending);
      if (preResult !== undefined) return preResult;
    }
    if (this.hasResourceBundle(lng, ns)) return true;
    if (!this.services.backendConnector.backend || this.options.resources && !this.options.partialBundledLanguages) return true;
    if (loadNotPending(lng, ns) && (!fallbackLng || loadNotPending(lastLng, ns))) return true;
    return false;
  }
  loadNamespaces(ns, callback) {
    const deferred = defer();
    if (!this.options.ns) {
      if (callback) callback();
      return Promise.resolve();
    }
    if (isString$1(ns)) ns = [ns];
    ns.forEach(n => {
      if (this.options.ns.indexOf(n) < 0) this.options.ns.push(n);
    });
    this.loadResources(err => {
      deferred.resolve();
      if (callback) callback(err);
    });
    return deferred;
  }
  loadLanguages(lngs, callback) {
    const deferred = defer();
    if (isString$1(lngs)) lngs = [lngs];
    const preloaded = this.options.preload || [];
    const newLngs = lngs.filter(lng => preloaded.indexOf(lng) < 0 && this.services.languageUtils.isSupportedCode(lng));
    if (!newLngs.length) {
      if (callback) callback();
      return Promise.resolve();
    }
    this.options.preload = preloaded.concat(newLngs);
    this.loadResources(err => {
      deferred.resolve();
      if (callback) callback(err);
    });
    return deferred;
  }
  dir(lng) {
    if (!lng) lng = this.resolvedLanguage || (this.languages?.length > 0 ? this.languages[0] : this.language);
    if (!lng) return 'rtl';
    try {
      const l = new Intl.Locale(lng);
      if (l && l.getTextInfo) {
        const ti = l.getTextInfo();
        if (ti && ti.direction) return ti.direction;
      }
    } catch (e) {}
    const rtlLngs = ['ar', 'shu', 'sqr', 'ssh', 'xaa', 'yhd', 'yud', 'aao', 'abh', 'abv', 'acm', 'acq', 'acw', 'acx', 'acy', 'adf', 'ads', 'aeb', 'aec', 'afb', 'ajp', 'apc', 'apd', 'arb', 'arq', 'ars', 'ary', 'arz', 'auz', 'avl', 'ayh', 'ayl', 'ayn', 'ayp', 'bbz', 'pga', 'he', 'iw', 'ps', 'pbt', 'pbu', 'pst', 'prp', 'prd', 'ug', 'ur', 'ydd', 'yds', 'yih', 'ji', 'yi', 'hbo', 'men', 'xmn', 'fa', 'jpr', 'peo', 'pes', 'prs', 'dv', 'sam', 'ckb'];
    const languageUtils = this.services?.languageUtils || new LanguageUtil(get());
    if (lng.toLowerCase().indexOf('-latn') > 1) return 'ltr';
    return rtlLngs.indexOf(languageUtils.getLanguagePartFromCode(lng)) > -1 || lng.toLowerCase().indexOf('-arab') > 1 ? 'rtl' : 'ltr';
  }
  static createInstance(options = {}, callback) {
    return new I18n(options, callback);
  }
  cloneInstance(options = {}, callback = noop) {
    const forkResourceStore = options.forkResourceStore;
    if (forkResourceStore) delete options.forkResourceStore;
    const mergedOptions = {
      ...this.options,
      ...options,
      ...{
        isClone: true
      }
    };
    const clone = new I18n(mergedOptions);
    if (options.debug !== undefined || options.prefix !== undefined) {
      clone.logger = clone.logger.clone(options);
    }
    const membersToCopy = ['store', 'services', 'language'];
    membersToCopy.forEach(m => {
      clone[m] = this[m];
    });
    clone.services = {
      ...this.services
    };
    clone.services.utils = {
      hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
    };
    if (forkResourceStore) {
      const clonedData = Object.keys(this.store.data).reduce((prev, l) => {
        prev[l] = {
          ...this.store.data[l]
        };
        prev[l] = Object.keys(prev[l]).reduce((acc, n) => {
          acc[n] = {
            ...prev[l][n]
          };
          return acc;
        }, prev[l]);
        return prev;
      }, {});
      clone.store = new ResourceStore(clonedData, mergedOptions);
      clone.services.resourceStore = clone.store;
    }
    clone.translator = new Translator(clone.services, mergedOptions);
    clone.translator.on('*', (event, ...args) => {
      clone.emit(event, ...args);
    });
    clone.init(mergedOptions, callback);
    clone.translator.options = mergedOptions;
    clone.translator.backendConnector.services.utils = {
      hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
    };
    return clone;
  }
  toJSON() {
    return {
      options: this.options,
      store: this.store,
      language: this.language,
      languages: this.languages,
      resolvedLanguage: this.resolvedLanguage
    };
  }
}
const instance = I18n.createInstance();
instance.createInstance = I18n.createInstance;

const {
  slice,
  forEach
} = [];
function defaults(obj) {
  forEach.call(slice.call(arguments, 1), source => {
    if (source) {
      for (const prop in source) {
        if (obj[prop] === undefined) obj[prop] = source[prop];
      }
    }
  });
  return obj;
}
function hasXSS(input) {
  if (typeof input !== 'string') return false;

  // Common XSS attack patterns
  const xssPatterns = [/<\s*script.*?>/i, /<\s*\/\s*script\s*>/i, /<\s*img.*?on\w+\s*=/i, /<\s*\w+\s*on\w+\s*=.*?>/i, /javascript\s*:/i, /vbscript\s*:/i, /expression\s*\(/i, /eval\s*\(/i, /alert\s*\(/i, /document\.cookie/i, /document\.write\s*\(/i, /window\.location/i, /innerHTML/i];
  return xssPatterns.some(pattern => pattern.test(input));
}

// eslint-disable-next-line no-control-regex
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
const serializeCookie = function (name, val) {
  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
    path: '/'
  };
  const opt = options;
  const value = encodeURIComponent(val);
  let str = `${name}=${value}`;
  if (opt.maxAge > 0) {
    const maxAge = opt.maxAge - 0;
    if (Number.isNaN(maxAge)) throw new Error('maxAge should be a Number');
    str += `; Max-Age=${Math.floor(maxAge)}`;
  }
  if (opt.domain) {
    if (!fieldContentRegExp.test(opt.domain)) {
      throw new TypeError('option domain is invalid');
    }
    str += `; Domain=${opt.domain}`;
  }
  if (opt.path) {
    if (!fieldContentRegExp.test(opt.path)) {
      throw new TypeError('option path is invalid');
    }
    str += `; Path=${opt.path}`;
  }
  if (opt.expires) {
    if (typeof opt.expires.toUTCString !== 'function') {
      throw new TypeError('option expires is invalid');
    }
    str += `; Expires=${opt.expires.toUTCString()}`;
  }
  if (opt.httpOnly) str += '; HttpOnly';
  if (opt.secure) str += '; Secure';
  if (opt.sameSite) {
    const sameSite = typeof opt.sameSite === 'string' ? opt.sameSite.toLowerCase() : opt.sameSite;
    switch (sameSite) {
      case true:
        str += '; SameSite=Strict';
        break;
      case 'lax':
        str += '; SameSite=Lax';
        break;
      case 'strict':
        str += '; SameSite=Strict';
        break;
      case 'none':
        str += '; SameSite=None';
        break;
      default:
        throw new TypeError('option sameSite is invalid');
    }
  }
  if (opt.partitioned) str += '; Partitioned';
  return str;
};
const cookie = {
  create(name, value, minutes, domain) {
    let cookieOptions = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
      path: '/',
      sameSite: 'strict'
    };
    if (minutes) {
      cookieOptions.expires = new Date();
      cookieOptions.expires.setTime(cookieOptions.expires.getTime() + minutes * 60 * 1000);
    }
    if (domain) cookieOptions.domain = domain;
    document.cookie = serializeCookie(name, value, cookieOptions);
  },
  read(name) {
    const nameEQ = `${name}=`;
    const ca = document.cookie.split(';');
    for (let i = 0; i < ca.length; i++) {
      let c = ca[i];
      while (c.charAt(0) === ' ') c = c.substring(1, c.length);
      if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
  },
  remove(name, domain) {
    this.create(name, '', -1, domain);
  }
};
var cookie$1 = {
  name: 'cookie',
  // Deconstruct the options object and extract the lookupCookie property
  lookup(_ref) {
    let {
      lookupCookie
    } = _ref;
    if (lookupCookie && typeof document !== 'undefined') {
      return cookie.read(lookupCookie) || undefined;
    }
    return undefined;
  },
  // Deconstruct the options object and extract the lookupCookie, cookieMinutes, cookieDomain, and cookieOptions properties
  cacheUserLanguage(lng, _ref2) {
    let {
      lookupCookie,
      cookieMinutes,
      cookieDomain,
      cookieOptions
    } = _ref2;
    if (lookupCookie && typeof document !== 'undefined') {
      cookie.create(lookupCookie, lng, cookieMinutes, cookieDomain, cookieOptions);
    }
  }
};

var querystring = {
  name: 'querystring',
  // Deconstruct the options object and extract the lookupQuerystring property
  lookup(_ref) {
    let {
      lookupQuerystring
    } = _ref;
    let found;
    if (typeof window !== 'undefined') {
      let {
        search
      } = window.location;
      if (!window.location.search && window.location.hash?.indexOf('?') > -1) {
        search = window.location.hash.substring(window.location.hash.indexOf('?'));
      }
      const query = search.substring(1);
      const params = query.split('&');
      for (let i = 0; i < params.length; i++) {
        const pos = params[i].indexOf('=');
        if (pos > 0) {
          const key = params[i].substring(0, pos);
          if (key === lookupQuerystring) {
            found = params[i].substring(pos + 1);
          }
        }
      }
    }
    return found;
  }
};

var hash = {
  name: 'hash',
  // Deconstruct the options object and extract the lookupHash property and the lookupFromHashIndex property
  lookup(_ref) {
    let {
      lookupHash,
      lookupFromHashIndex
    } = _ref;
    let found;
    if (typeof window !== 'undefined') {
      const {
        hash
      } = window.location;
      if (hash && hash.length > 2) {
        const query = hash.substring(1);
        if (lookupHash) {
          const params = query.split('&');
          for (let i = 0; i < params.length; i++) {
            const pos = params[i].indexOf('=');
            if (pos > 0) {
              const key = params[i].substring(0, pos);
              if (key === lookupHash) {
                found = params[i].substring(pos + 1);
              }
            }
          }
        }
        if (found) return found;
        if (!found && lookupFromHashIndex > -1) {
          const language = hash.match(/\/([a-zA-Z-]*)/g);
          if (!Array.isArray(language)) return undefined;
          const index = typeof lookupFromHashIndex === 'number' ? lookupFromHashIndex : 0;
          return language[index]?.replace('/', '');
        }
      }
    }
    return found;
  }
};

let hasLocalStorageSupport = null;
const localStorageAvailable = () => {
  if (hasLocalStorageSupport !== null) return hasLocalStorageSupport;
  try {
    hasLocalStorageSupport = typeof window !== 'undefined' && window.localStorage !== null;
    if (!hasLocalStorageSupport) {
      return false;
    }
    const testKey = 'i18next.translate.boo';
    window.localStorage.setItem(testKey, 'foo');
    window.localStorage.removeItem(testKey);
  } catch (e) {
    hasLocalStorageSupport = false;
  }
  return hasLocalStorageSupport;
};
var localStorage = {
  name: 'localStorage',
  // Deconstruct the options object and extract the lookupLocalStorage property
  lookup(_ref) {
    let {
      lookupLocalStorage
    } = _ref;
    if (lookupLocalStorage && localStorageAvailable()) {
      return window.localStorage.getItem(lookupLocalStorage) || undefined; // Undefined ensures type consistency with the previous version of this function
    }
    return undefined;
  },
  // Deconstruct the options object and extract the lookupLocalStorage property
  cacheUserLanguage(lng, _ref2) {
    let {
      lookupLocalStorage
    } = _ref2;
    if (lookupLocalStorage && localStorageAvailable()) {
      window.localStorage.setItem(lookupLocalStorage, lng);
    }
  }
};

let hasSessionStorageSupport = null;
const sessionStorageAvailable = () => {
  if (hasSessionStorageSupport !== null) return hasSessionStorageSupport;
  try {
    hasSessionStorageSupport = typeof window !== 'undefined' && window.sessionStorage !== null;
    if (!hasSessionStorageSupport) {
      return false;
    }
    const testKey = 'i18next.translate.boo';
    window.sessionStorage.setItem(testKey, 'foo');
    window.sessionStorage.removeItem(testKey);
  } catch (e) {
    hasSessionStorageSupport = false;
  }
  return hasSessionStorageSupport;
};
var sessionStorage = {
  name: 'sessionStorage',
  lookup(_ref) {
    let {
      lookupSessionStorage
    } = _ref;
    if (lookupSessionStorage && sessionStorageAvailable()) {
      return window.sessionStorage.getItem(lookupSessionStorage) || undefined;
    }
    return undefined;
  },
  cacheUserLanguage(lng, _ref2) {
    let {
      lookupSessionStorage
    } = _ref2;
    if (lookupSessionStorage && sessionStorageAvailable()) {
      window.sessionStorage.setItem(lookupSessionStorage, lng);
    }
  }
};

var navigator$1 = {
  name: 'navigator',
  lookup(options) {
    const found = [];
    if (typeof navigator !== 'undefined') {
      const {
        languages,
        userLanguage,
        language
      } = navigator;
      if (languages) {
        // chrome only; not an array, so can't use .push.apply instead of iterating
        for (let i = 0; i < languages.length; i++) {
          found.push(languages[i]);
        }
      }
      if (userLanguage) {
        found.push(userLanguage);
      }
      if (language) {
        found.push(language);
      }
    }
    return found.length > 0 ? found : undefined;
  }
};

var htmlTag = {
  name: 'htmlTag',
  // Deconstruct the options object and extract the htmlTag property
  lookup(_ref) {
    let {
      htmlTag
    } = _ref;
    let found;
    const internalHtmlTag = htmlTag || (typeof document !== 'undefined' ? document.documentElement : null);
    if (internalHtmlTag && typeof internalHtmlTag.getAttribute === 'function') {
      found = internalHtmlTag.getAttribute('lang');
    }
    return found;
  }
};

var path = {
  name: 'path',
  // Deconstruct the options object and extract the lookupFromPathIndex property
  lookup(_ref) {
    let {
      lookupFromPathIndex
    } = _ref;
    if (typeof window === 'undefined') return undefined;
    const language = window.location.pathname.match(/\/([a-zA-Z-]*)/g);
    if (!Array.isArray(language)) return undefined;
    const index = typeof lookupFromPathIndex === 'number' ? lookupFromPathIndex : 0;
    return language[index]?.replace('/', '');
  }
};

var subdomain = {
  name: 'subdomain',
  lookup(_ref) {
    let {
      lookupFromSubdomainIndex
    } = _ref;
    // If given get the subdomain index else 1
    const internalLookupFromSubdomainIndex = typeof lookupFromSubdomainIndex === 'number' ? lookupFromSubdomainIndex + 1 : 1;
    // get all matches if window.location. is existing
    // first item of match is the match itself and the second is the first group match which should be the first subdomain match
    // is the hostname no public domain get the or option of localhost
    const language = typeof window !== 'undefined' && window.location?.hostname?.match(/^(\w{2,5})\.(([a-z0-9-]{1,63}\.[a-z]{2,6})|localhost)/i);

    // if there is no match (null) return undefined
    if (!language) return undefined;
    // return the given group match
    return language[internalLookupFromSubdomainIndex];
  }
};

// some environments, throws when accessing document.cookie
let canCookies = false;
try {
  canCookies = true;
  // eslint-disable-next-line no-empty
} catch (e) {}
const order = ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag'];
if (!canCookies) order.splice(1, 1);
const getDefaults = () => ({
  order,
  lookupQuerystring: 'lng',
  lookupCookie: 'i18next',
  lookupLocalStorage: 'i18nextLng',
  lookupSessionStorage: 'i18nextLng',
  // cache user language
  caches: ['localStorage'],
  excludeCacheFor: ['cimode'],
  // cookieMinutes: 10,
  // cookieDomain: 'myDomain'

  convertDetectedLanguage: l => l
});
class Browser {
  constructor(services) {
    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    this.type = 'languageDetector';
    this.detectors = {};
    this.init(services, options);
  }
  init() {
    let services = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
      languageUtils: {}
    };
    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    let i18nOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
    this.services = services;
    this.options = defaults(options, this.options || {}, getDefaults());
    if (typeof this.options.convertDetectedLanguage === 'string' && this.options.convertDetectedLanguage.indexOf('15897') > -1) {
      this.options.convertDetectedLanguage = l => l.replace('-', '_');
    }

    // backwards compatibility
    if (this.options.lookupFromUrlIndex) this.options.lookupFromPathIndex = this.options.lookupFromUrlIndex;
    this.i18nOptions = i18nOptions;
    this.addDetector(cookie$1);
    this.addDetector(querystring);
    this.addDetector(localStorage);
    this.addDetector(sessionStorage);
    this.addDetector(navigator$1);
    this.addDetector(htmlTag);
    this.addDetector(path);
    this.addDetector(subdomain);
    this.addDetector(hash);
  }
  addDetector(detector) {
    this.detectors[detector.name] = detector;
    return this;
  }
  detect() {
    let detectionOrder = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.order;
    let detected = [];
    detectionOrder.forEach(detectorName => {
      if (this.detectors[detectorName]) {
        let lookup = this.detectors[detectorName].lookup(this.options);
        if (lookup && typeof lookup === 'string') lookup = [lookup];
        if (lookup) detected = detected.concat(lookup);
      }
    });
    detected = detected.filter(d => d !== undefined && d !== null && !hasXSS(d)).map(d => this.options.convertDetectedLanguage(d));
    if (this.services && this.services.languageUtils && this.services.languageUtils.getBestMatchFromCodes) return detected; // new i18next v19.5.0
    return detected.length > 0 ? detected[0] : null; // a little backward compatibility
  }
  cacheUserLanguage(lng) {
    let caches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.options.caches;
    if (!caches) return;
    if (this.options.excludeCacheFor && this.options.excludeCacheFor.indexOf(lng) > -1) return;
    caches.forEach(cacheName => {
      if (this.detectors[cacheName]) this.detectors[cacheName].cacheUserLanguage(lng, this.options);
    });
  }
}
Browser.type = 'languageDetector';

const localizeInit = () => {
    instance
        .use(Browser)
        .init({
        detection: {
            order: ['querystring', /* 'cookie', 'localStorage', 'sessionStorage',*/ 'navigator', 'htmlTag']
        },
        supportedLngs: ['de', 'en', 'fr', 'ja', 'ko', 'zh-CN'],
        fallbackLng: 'en',
        resources: {
            de: {
                translation: {
                    // File menu
                    'file': 'Datei',
                    'file.new': 'Neu',
                    'file.open': 'Öffnen',
                    'file.import': 'Importieren...',
                    'file.load-all-data': 'PLY-Daten vollständig laden',
                    'file.save': 'Speichern',
                    'file.save-as': 'Speichern als...',
                    'file.publish': 'Veröffentlichen...',
                    'file.export': 'Exportieren',
                    'file.export.splat': 'Splat',
                    'file.export.viewer': 'Viewer App...',
                    // Render menu
                    'render': 'Rendern',
                    'render.image': 'Bild...',
                    'render.video': 'Video...',
                    'render.ok': 'Rendern',
                    'render.cancel': 'Abbrechen',
                    // Selection menu
                    'select': 'Auswahl',
                    'select.all': 'Alles',
                    'select.none': 'Aufheben',
                    'select.invert': 'Invertieren',
                    'select.lock': 'Selektion sperren',
                    'select.unlock': 'Sperre aufheben',
                    'select.delete': 'Selektion aufheben',
                    'select.reset': 'Splats zurücksetzen',
                    'select.duplicate': 'Duplizieren',
                    'select.separate': 'Separieren',
                    // Help menu
                    'help': 'Hilfe',
                    'help.shortcuts': 'Tastaturkürzel',
                    'help.user-guide': 'Handbuch',
                    'help.log-issue': 'Problem melden',
                    'help.github-repo': 'GitHub Repository',
                    'help.basics-video': 'Grundlagen Video',
                    'help.discord': 'Discord Server',
                    'help.forum': 'Forum',
                    'help.about': 'Über SuperSplat',
                    // Modes
                    'mode.centers': 'Punkt Modus',
                    'mode.rings': 'Ring Modus',
                    // Scene panel
                    'scene-manager': 'SZENEN MANAGER',
                    'transform': 'TRANSFORMATION',
                    'position': 'Position',
                    'rotation': 'Rotation',
                    'scale': 'Skalierung',
                    // Options panel
                    'options': 'ANSICHTS OPTIONEN',
                    'options.colors': 'Farben',
                    'options.bg-color': 'Hintergrundfarbe',
                    'options.selected-color': 'Selektierte Farbe',
                    'options.unselected-color': 'Nicht selektierte Farbe',
                    'options.locked-color': 'Gesperrte Farbe',
                    'options.fov': 'Sichtfeld (FoV)',
                    'options.sh-bands': 'SH Bänder',
                    'options.centers-size': 'Punktgrößen',
                    'options.outline-selection': 'Umriss Selektion',
                    'options.show-grid': 'Raster anzeigen',
                    'options.show-bound': 'Objektbox anzeigen',
                    'options.camera-fly-speed': 'Kamera Geschwindigkeit',
                    // Camera panel
                    'camera': 'KAMERA POSEN',
                    'camera.add-pose': 'Pose hinzufügen',
                    'camera.prev-pose': 'Vorherige Pose',
                    'camera.next-pose': 'Nächste Pose',
                    'camera.play-poses': 'Posen abspielen',
                    'camera.clear-poses': 'Posen leeren',
                    // Color panel
                    'colors': 'FARBE',
                    'colors.tint': 'Färbung',
                    'colors.temperature': 'Temperatur',
                    'colors.saturation': 'Sättigung',
                    'colors.brightness': 'Helligkeit',
                    'colors.blackPoint': 'Schwarzpunkt',
                    'colors.whitePoint': 'Weißpunkt',
                    'colors.transparency': 'Transparenz',
                    'colors.reset': 'Zurücksetzen',
                    // Data panel
                    'data': 'SPLAT DATEN',
                    'data.distance': 'Entfernung',
                    'data.volume': 'Volumen',
                    'data.surface-area': 'Oberfläche',
                    'data.scale-x': 'Größe X',
                    'data.scale-y': 'Größe Y',
                    'data.scale-z': 'Größe Z',
                    'data.red': 'Rot',
                    'data.green': 'Grün',
                    'data.blue': 'Blau',
                    'data.opacity': 'Deckkraft',
                    'data.hue': 'Farbe',
                    'data.saturation': 'Sättigung',
                    'data.value': 'Helligkeit',
                    'data.log-scale': 'Logarithmische Skala',
                    'data.totals': 'Summe',
                    'data.totals.splats': 'Splats',
                    'data.totals.selected': 'Selektiert',
                    'data.totals.locked': 'Gesperrt',
                    'data.totals.deleted': 'Gelöscht',
                    // Shortcuts panel
                    'shortcuts.title': 'TASTATURKÜRZEL',
                    'shortcuts.tools': 'WERKZEUGE',
                    'shortcuts.move': 'Bewegen',
                    'shortcuts.rotate': 'Drehen',
                    'shortcuts.scale': 'Skalieren',
                    'shortcuts.rect-selection': 'Rechteckselektion',
                    'shortcuts.brush-selection': 'Pinselselektion',
                    'shortcuts.picker-selection': 'Einzelselektion',
                    'shortcuts.brush-size': 'Pinsel Verkleinern/Vergrößern',
                    'shortcuts.deactivate-tool': 'Werkzeug deaktivieren',
                    'shortcuts.selection': 'SELEKTION',
                    'shortcuts.select-all': 'Alle Selektieren',
                    'shortcuts.deselect-all': 'Selektion aufheben',
                    'shortcuts.invert-selection': 'Selektion invertieren',
                    'shortcuts.add-to-selection': 'Zur Selektion hinzufügen',
                    'shortcuts.remove-from-selection': 'Von Selektion entfernen',
                    'shortcuts.delete-selected-splats': 'Selektierte Splats löschen',
                    'shortcuts.show': 'ANZEIGEN',
                    'shortcuts.hide-selected-splats': 'Selektierte Splats ausblenden',
                    'shortcuts.unhide-all-splats': 'Alle Splats einblenden',
                    'shortcuts.toggle-data-panel': 'Splat Daten Panel anzeigen',
                    'shortcuts.other': 'WEITERE',
                    'shortcuts.select-next-splat': 'Nächsten Splat selektieren',
                    'shortcuts.undo': 'Rückgängig',
                    'shortcuts.redo': 'Wiederholen',
                    'shortcuts.toggle-splat-overlay': 'Splateinblendung umschalten',
                    'shortcuts.focus-camera': 'Kamera auf selektion ausrichten',
                    'shortcuts.toggle-camera-mode': 'Kameramodus umschalten',
                    'shortcuts.toggle-grid': 'Rasteranzeige umschalten',
                    'shortcuts.toggle-gizmo-coordinate-space': 'Gizmoanzeige umschalten',
                    // Popup
                    'popup.ok': 'OK',
                    'popup.cancel': 'Abbrechen',
                    'popup.yes': 'Ja',
                    'popup.no': 'Nein',
                    'popup.error': 'FEHLER',
                    'popup.error-loading': 'FEHLER BEIM LADEN DER DATEI',
                    'popup.drop-files': 'Bitte Dateien und Ordner ablegen',
                    'popup.copy-to-clipboard': 'Link in die Zwischenablage kopieren',
                    // Right toolbar
                    'tooltip.splat-mode': 'Splat Modus ( M )',
                    'tooltip.show-hide': 'Anzeigen/Ausplenden Splats ( Leertaste )',
                    'tooltip.frame-selection': 'Rechteckselektion ( F )',
                    'tooltip.camera-reset': 'Kamera zurücksetzen',
                    'tooltip.color-panel': 'Farben',
                    'tooltip.view-options': 'Anzeige Optionen',
                    // Bottom toolbar
                    'tooltip.undo': 'Rückgängig ( Strg + Z )',
                    'tooltip.redo': 'Wiederholen ( Strg + Shift + Z )',
                    'tooltip.picker': 'Einzelselektion ( R )',
                    'tooltip.polygon': 'Polygonselektion ( P )',
                    'tooltip.brush': 'Pinselselektion ( B )',
                    'tooltip.sphere': 'Kugelselektion',
                    'tooltip.box': 'Kastenselektion',
                    'tooltip.translate': 'Verschieben ( 1 )',
                    'tooltip.rotate': 'Drehen ( 2 )',
                    'tooltip.scale': 'Skalieren ( 3 )',
                    'tooltip.local-space': 'Gizmo in local-space',
                    'tooltip.bound-center': 'Mittelpunkt verwenden',
                    // Viewer Export
                    'export.header': 'EXPORTIEREN',
                    'export.type': 'Export Typ',
                    'export.html': 'HTML',
                    'export.package': 'ZIP Paket',
                    'export.sh-bands': 'SH Bänder',
                    'export.start-position': 'Start Position',
                    'export.default': 'Standard',
                    'export.viewport': 'Aktuelle Ansicht',
                    'export.pose-camera': '1. Kamera Pose',
                    'export.fov': 'Sichtfeld (FoV)',
                    'export.background-color': 'Hintergrund',
                    'export.filename': 'Dateiname',
                    'export.animation': 'Animation',
                    'export.animation-none': 'Keine',
                    'export.animation-track': 'Track',
                    'export.ply-compress': 'PLY komprimieren',
                    'export.splats-select': 'Splats',
                    'export.splats-select.all': 'Alle Splats',
                    'export.format': 'Format',
                    'export.format-compressed-ply': 'Komprimiertes PLY',
                    'export.format-sog': 'SOG',
                    // Cursor
                    'cursor.click-to-copy': 'Klicken zum kopieren',
                    'cursor.copied': 'Kopiert!',
                    // Doc
                    'doc.reset': 'SZENE ZURÜCKSETZEN',
                    'doc.unsaved-message': 'Sie haben ungespeicherte Änderungen. Möchten Sie die Szene wirklich zurücksetzen?',
                    'doc.reset-message': 'Möchten Sie die Szene wirklich zurücksetzen?',
                    'doc.save-failed': 'FEHLGESCHLAGEN ZU SPEICHERN',
                    'doc.load-failed': 'FEHLGESCHLAGEN ZU LADEN',
                    // Publish Settings Dialog
                    'publish.header': 'VERÖFFENTLICHEN EINSTELLUNGEN',
                    'publish.ok': 'Veröffentlichen',
                    'publish.cancel': 'Abbrechen',
                    'publish.title': 'Titel',
                    'publish.description': 'Beschreibung',
                    'publish.listed': 'Aufgelistet',
                    'publish.failed': 'VERÖFFENTLICHEN FEHLGESCHLAGEN',
                    'publish.please-try-again': 'Bitte versuchen Sie es später erneut.',
                    'publish.succeeded': 'VERÖFFENTLICHEN ERFOLGREICH',
                    'publish.message': 'Verwenden Sie den Link unten, um auf Ihre Szene zuzugreifen.',
                    'publish.please-log-in': 'Das Veröffentlichen in PlayCanvas erfordert ein Benutzerkonto. Bitte melden Sie sich an und versuchen Sie es erneut.',
                    'publish.converting': 'Konvertieren...',
                    'publish.uploading': 'Hochladen...',
                    'publish.to': 'Veröffentlichen in',
                    'publish.new-scene': 'Neue Szene',
                    // Video Settings Dialog
                    'video.header': 'VIDEO EINSTELLUNGEN',
                    'video.resolution': 'Auflösung',
                    'video.frameRate': 'Bildrate',
                    'video.bitrate': 'Bitrate',
                    'video.portrait': 'Hochformat',
                    'video.transparentBg': 'Transparenter Hintergrund',
                    'video.showDebug': 'Debug-Überlagerungen anzeigen',
                    // Timeline
                    'timeline.prev-key': 'Zur vorherigen Keyframe springen',
                    'timeline.play': 'Abspielen/Stoppen',
                    'timeline.next-key': 'Zur nächsten Keyframe springen',
                    'timeline.add-key': 'Key hinzufügen',
                    'timeline.remove-key': 'Key entfernen',
                    'timeline.frame-rate': 'Bildrate',
                    'timeline.total-frames': 'Gesamtanzahl der Frames',
                    'timeline.smoothness': 'Weichheit'
                }
            },
            en: {
                translation: {
                    // File menu
                    'file': 'File',
                    'file.new': 'New',
                    'file.open': 'Open',
                    'file.import': 'Import...',
                    'file.load-all-data': 'Load all PLY data',
                    'file.save': 'Save',
                    'file.save-as': 'Save As...',
                    'file.publish': 'Publish...',
                    'file.export': 'Export',
                    'file.export.ply': 'PLY (.ply)',
                    'file.export.splat': 'Splat file (.splat)',
                    'file.export.viewer': 'Viewer App...',
                    // Render menu
                    'render': 'Render',
                    'render.image': 'Image...',
                    'render.video': 'Video...',
                    'render.ok': 'Render',
                    'render.cancel': 'Cancel',
                    'render.render-video': 'Render Video',
                    'render.rendering': 'Rendering Frames...',
                    // Selection menu
                    'select': 'Select',
                    'select.all': 'All',
                    'select.none': 'None',
                    'select.invert': 'Invert',
                    'select.lock': 'Lock Selection',
                    'select.unlock': 'Unlock All',
                    'select.delete': 'Delete Selection',
                    'select.reset': 'Reset Splat',
                    'select.duplicate': 'Duplicate',
                    'select.separate': 'Separate',
                    // Help menu
                    'help': 'Help',
                    'help.shortcuts': 'Keyboard Shortcuts',
                    'help.user-guide': 'User Guide',
                    'help.log-issue': 'Log an Issue',
                    'help.github-repo': 'GitHub Repo',
                    'help.basics-video': 'Basics Video',
                    'help.discord': 'Discord Server',
                    'help.forum': 'Forum',
                    'help.about': 'About SuperSplat',
                    // Modes
                    'mode.centers': 'Centers Mode',
                    'mode.rings': 'Rings Mode',
                    // Scene panel
                    'scene-manager': 'SCENE MANAGER',
                    'transform': 'TRANSFORM',
                    'position': 'Position',
                    'rotation': 'Rotation',
                    'scale': 'Scale',
                    // Options panel
                    'options': 'VIEW OPTIONS',
                    'options.colors': 'Colors',
                    'options.bg-color': 'Background Color',
                    'options.selected-color': 'Selected Color',
                    'options.unselected-color': 'Unselected Color',
                    'options.locked-color': 'Locked Color',
                    'options.fov': 'Field of View',
                    'options.sh-bands': 'SH Bands',
                    'options.centers-size': 'Centers Size',
                    'options.outline-selection': 'Outline Selection',
                    'options.show-grid': 'Show Grid',
                    'options.show-bound': 'Show Bound',
                    'options.camera-fly-speed': 'Fly Speed',
                    'options.tonemapping': 'Tonemapping',
                    'options.tonemapping-none': 'None',
                    'options.tonemapping-linear': 'Linear',
                    'options.tonemapping-neutral': 'Neutral',
                    'options.tonemapping-aces': 'ACES',
                    'options.tonemapping-aces2': 'ACES2',
                    'options.tonemapping-filmic': 'Filmic',
                    'options.tonemapping-hejl': 'Hejl',
                    // Camera panel
                    'camera': 'CAMERA POSES',
                    'camera.add-pose': 'Add Pose',
                    'camera.prev-pose': 'Previous Pose',
                    'camera.next-pose': 'Next Pose',
                    'camera.play-poses': 'Play Poses',
                    'camera.clear-poses': 'Clear Poses',
                    // Color panel
                    'colors': 'COLORS',
                    'colors.tint': 'Tint',
                    'colors.temperature': 'Temperature',
                    'colors.saturation': 'Saturation',
                    'colors.brightness': 'Brightness',
                    'colors.blackPoint': 'Black Point',
                    'colors.whitePoint': 'White Point',
                    'colors.transparency': 'Transparency',
                    'colors.reset': 'Reset',
                    // Data panel
                    'data': 'SPLAT DATA',
                    'data.distance': 'Distance',
                    'data.volume': 'Volume',
                    'data.surface-area': 'Surface Area',
                    'data.scale-x': 'Scale X',
                    'data.scale-y': 'Scale Y',
                    'data.scale-z': 'Scale Z',
                    'data.red': 'Red',
                    'data.green': 'Green',
                    'data.blue': 'Blue',
                    'data.opacity': 'Opacity',
                    'data.hue': 'Hue',
                    'data.saturation': 'Saturation',
                    'data.value': 'Value',
                    'data.log-scale': 'Log Scale',
                    'data.totals': 'Totals',
                    'data.totals.splats': 'Splats',
                    'data.totals.selected': 'Selected',
                    'data.totals.locked': 'Locked',
                    'data.totals.deleted': 'Deleted',
                    // Shortcuts panel
                    'shortcuts.title': 'KEYBOARD SHORTCUTS',
                    'shortcuts.tools': 'TOOLS',
                    'shortcuts.move': 'Move',
                    'shortcuts.rotate': 'Rotate',
                    'shortcuts.scale': 'Scale',
                    'shortcuts.rect-selection': 'Rect Selection',
                    'shortcuts.brush-selection': 'Brush Selection',
                    'shortcuts.picker-selection': 'Picker Selection',
                    'shortcuts.brush-size': 'Decrease/Increase brush size',
                    'shortcuts.deactivate-tool': 'Deactivate Tool',
                    'shortcuts.selection': 'SELECTION',
                    'shortcuts.select-all': 'Select All',
                    'shortcuts.deselect-all': 'Deselect All',
                    'shortcuts.invert-selection': 'Invert Selection',
                    'shortcuts.add-to-selection': 'Add to Selection',
                    'shortcuts.remove-from-selection': 'Remove from Selection',
                    'shortcuts.delete-selected-splats': 'Delete Selected Splats',
                    'shortcuts.show': 'SHOW',
                    'shortcuts.hide-selected-splats': 'Hide Selected Splats',
                    'shortcuts.unhide-all-splats': 'Unhide All Splats',
                    'shortcuts.toggle-data-panel': 'Toggle Data Panel',
                    'shortcuts.other': 'OTHER',
                    'shortcuts.select-next-splat': 'Select Next Splat',
                    'shortcuts.undo': 'Undo',
                    'shortcuts.redo': 'Redo',
                    'shortcuts.toggle-splat-overlay': 'Toggle Splat Overlay',
                    'shortcuts.focus-camera': 'Focus Camera on current selection',
                    'shortcuts.toggle-camera-mode': 'Toggle Camera Mode',
                    'shortcuts.toggle-grid': 'Toggle Grid',
                    'shortcuts.toggle-gizmo-coordinate-space': 'Toggle Gizmo Coordinate Space',
                    // Popup
                    'popup.ok': 'OK',
                    'popup.cancel': 'Cancel',
                    'popup.yes': 'Yes',
                    'popup.no': 'No',
                    'popup.error': 'ERROR',
                    'popup.error-loading': 'ERROR LOADING FILE',
                    'popup.drop-files': 'Please drop files and folders',
                    'popup.copy-to-clipboard': 'Copy link to clipboard',
                    // Right toolbar
                    'tooltip.splat-mode': 'Splat Mode ( M )',
                    'tooltip.show-hide': 'Show/Hide Splats ( Space )',
                    'tooltip.frame-selection': 'Frame Selection ( F )',
                    'tooltip.camera-reset': 'Reset Camera',
                    'tooltip.color-panel': 'Colors',
                    'tooltip.view-options': 'View Options',
                    // Bottom toolbar
                    'tooltip.undo': 'Undo ( Ctrl + Z )',
                    'tooltip.redo': 'Redo ( Ctrl + Shift + Z )',
                    'tooltip.picker': 'Picker Select ( R )',
                    'tooltip.polygon': 'Polygon Select ( P )',
                    'tooltip.brush': 'Brush Select ( B )',
                    'tooltip.sphere': 'Sphere Select',
                    'tooltip.box': 'Box Select',
                    'tooltip.translate': 'Translate ( 1 )',
                    'tooltip.rotate': 'Rotate ( 2 )',
                    'tooltip.scale': 'Scale ( 3 )',
                    'tooltip.local-space': 'Use Local Orientation',
                    'tooltip.bound-center': 'Use Bound Center',
                    // Viewer Export
                    'export.header': 'EXPORT',
                    'export.type': 'Export Type',
                    'export.html': 'HTML',
                    'export.package': 'ZIP Package',
                    'export.sh-bands': 'SH Bands',
                    'export.start-position': 'Start Position',
                    'export.default': 'Default',
                    'export.viewport': 'Current Viewport',
                    'export.pose-camera': '1st Camera Pose',
                    'export.fov': 'Field of View',
                    'export.background-color': 'Background',
                    'export.filename': 'Filename',
                    'export.animation': 'Animation',
                    'export.animation-none': 'None',
                    'export.animation-track': 'Track',
                    'export.ply-compress': 'Compress PLY',
                    'export.splats-select': 'Splats',
                    'export.splats-select.all': 'All Splats',
                    'export.format': 'File Format',
                    'export.format-compressed-ply': 'Compressed PLY',
                    'export.format-sog': 'SOG',
                    // Cursor
                    'cursor.click-to-copy': 'Click to copy',
                    'cursor.copied': 'Copied!',
                    // Doc
                    'doc.reset': 'RESET SCENE',
                    'doc.unsaved-message': 'You have unsaved changes. Are you sure you want to reset the scene?',
                    'doc.reset-message': 'Are you sure you want to reset the scene?',
                    'doc.save-failed': 'FAILED TO SAVE',
                    'doc.load-failed': 'FAILED TO LOAD',
                    // Publish Settings Dialog
                    'publish.header': 'PUBLISH SETTINGS',
                    'publish.ok': 'Publish',
                    'publish.cancel': 'Cancel',
                    'publish.title': 'Title',
                    'publish.description': 'Description',
                    'publish.listed': 'Listed',
                    'publish.failed': 'PUBLISH FAILED',
                    'publish.please-try-again': 'Please try again later.',
                    'publish.succeeded': 'PUBLISH SUCCEEDED',
                    'publish.message': 'Use the link below to access your scene.',
                    'publish.please-log-in': 'Publishing to PlayCanvas requires a user account. Please log in and try again.',
                    'publish.converting': 'Converting...',
                    'publish.uploading': 'Uploading...',
                    'publish.to': 'Publish to',
                    'publish.new-scene': 'New Scene',
                    // Image Settings Dialog
                    'image.header': 'IMAGE SETTINGS',
                    'image.preset': 'Preset',
                    'image.resolution': 'Resolution',
                    'image.transparentBg': 'Transparent Background',
                    'image.showDebug': 'Show Debug Overlays',
                    'image.resolutionCurrent': 'Current',
                    'image.resolutionCustom': 'Custom',
                    // Video Settings Dialog
                    'video.header': 'VIDEO SETTINGS',
                    'video.resolution': 'Resolution',
                    'video.frameRate': 'Frame Rate',
                    'video.bitrate': 'Bitrate',
                    'video.portrait': 'Portrait Mode',
                    'video.transparentBg': 'Transparent Background',
                    'video.showDebug': 'Show Debug Overlays',
                    // Timeline
                    'timeline.prev-key': 'Jump to previous keyframe',
                    'timeline.play': 'Play/Stop',
                    'timeline.next-key': 'Jump to next keyframe',
                    'timeline.add-key': 'Add Key',
                    'timeline.remove-key': 'Remove Key',
                    'timeline.frame-rate': 'Frame Rate',
                    'timeline.total-frames': 'Total Frames',
                    'timeline.smoothness': 'Smoothness'
                }
            },
            fr: {
                translation: {
                    // File menu
                    'file': 'Fichier',
                    'file.new': 'Créer',
                    'file.open': 'Ouvrir',
                    'file.import': 'Importer...',
                    'file.load-all-data': 'Charger toutes les données ply',
                    'file.save': 'Enregistrer',
                    'file.save-as': 'Enregistrer sous...',
                    'file.publish': 'Publier...',
                    'file.export': 'Exporter',
                    'file.export.splat': 'Fichier splat',
                    'file.export.viewer': 'Application de visualisation...',
                    // Render menu
                    'render': 'Rendu',
                    'render.image': 'Image...',
                    'render.video': 'Vidéo...',
                    'render.ok': 'Rendu',
                    'render.cancel': 'Annuler',
                    // Selection menu
                    'select': 'Sélection',
                    'select.all': 'Tout',
                    'select.none': 'Aucune',
                    'select.invert': 'Inverser',
                    'select.lock': 'Verrouiller la sélection',
                    'select.unlock': 'Tout débloquer',
                    'select.delete': 'Supprimer la sélection',
                    'select.reset': 'Réinitialiser splat',
                    'select.duplicate': 'Dupliquer',
                    'select.separate': 'Séparer',
                    // Help menu
                    'help': 'Aide',
                    'help.shortcuts': 'Raccourcis claviers',
                    'help.user-guide': 'Guide utilisateur',
                    'help.log-issue': 'Signaler un problème',
                    'help.github-repo': 'Dépôt GitHub',
                    'help.basics-video': 'Vidéo de base',
                    'help.discord': 'Serveur Discord',
                    'help.forum': 'Forum',
                    'help.about': 'À propos de SuperSplat',
                    // Modes
                    'mode.centers': 'Mode centres',
                    'mode.rings': 'Mode anneaux',
                    // Scene panel
                    'scene-manager': 'GESTIONNAIRE DE SCENE',
                    'transform': 'TRANSFORMATION',
                    'position': 'Position',
                    'rotation': 'Rotation',
                    'scale': 'Échelle',
                    // Options panel
                    'options': 'OPTIONS D\'AFFICHAGE',
                    'options.colors': 'Couleurs',
                    'options.bg-color': 'Couleur de fond',
                    'options.selected-color': 'Couleur sélectionnée',
                    'options.unselected-color': 'Couleur non sélectionnée',
                    'options.locked-color': 'Couleur verrouillée',
                    'options.fov': 'Champ de vision',
                    'options.sh-bands': 'Ordres d\'HS',
                    'options.centers-size': 'Échelle des centres',
                    'options.outline-selection': 'Contour de la sélection',
                    'options.show-grid': 'Afficher la grille',
                    'options.show-bound': 'Afficher limites',
                    'options.camera-fly-speed': 'Vitesse de vol',
                    // Camera panel
                    'camera': 'POSES DE LA CAMERA',
                    'camera.add-pose': 'Ajouter une pose',
                    'camera.prev-pose': 'Pose précédente',
                    'camera.next-pose': 'Pose suivante',
                    'camera.play-poses': 'Lire les poses',
                    'camera.clear-poses': 'Effacer les poses',
                    // Color panel
                    'colors': 'COULEURS',
                    'colors.tint': 'Teinte',
                    'colors.temperature': 'Température',
                    'colors.saturation': 'Saturation',
                    'colors.brightness': 'Luminosité',
                    'colors.blackPoint': 'Point noir',
                    'colors.whitePoint': 'Point blanc',
                    'colors.transparency': 'Transparence',
                    'colors.reset': 'Réinitialiser',
                    // Data panel
                    'data': 'DONNEES SPLAT',
                    'data.distance': 'Distance',
                    'data.volume': 'Volume',
                    'data.surface-area': 'Zone de surface',
                    'data.scale-x': 'Échelle X',
                    'data.scale-y': 'Échelle Y',
                    'data.scale-z': 'Échelle Z',
                    'data.red': 'Rouge',
                    'data.green': 'Vert',
                    'data.blue': 'Bleu',
                    'data.opacity': 'Opacité',
                    'data.hue': 'Teinte',
                    'data.saturation': 'Saturation',
                    'data.value': 'Luminosité',
                    'data.log-scale': 'Échelle logarithmique',
                    'data.totals': 'Totaux',
                    'data.totals.splats': 'Splats',
                    'data.totals.selected': 'Selectionné',
                    'data.totals.locked': 'Verrouillé',
                    'data.totals.deleted': 'Supprimé',
                    // Shortcuts panel
                    'shortcuts.title': 'RACCOURCIS CLAVIERS',
                    'shortcuts.tools': 'OUTILS',
                    'shortcuts.move': 'Déplacer',
                    'shortcuts.rotate': 'Tourner',
                    'shortcuts.scale': 'Changer l\'échelle',
                    'shortcuts.rect-selection': 'Sélection avec rectangle',
                    'shortcuts.brush-selection': 'Sélection avec pinceau',
                    'shortcuts.picker-selection': 'Sélection avec pipette',
                    'shortcuts.brush-size': 'Augmenter/Diminuer la taille du pinceau',
                    'shortcuts.deactivate-tool': 'Désactiver l\'outil',
                    'shortcuts.selection': 'SELECTION',
                    'shortcuts.select-all': 'Tout sélectionner',
                    'shortcuts.deselect-all': 'Tout desélectionner',
                    'shortcuts.invert-selection': 'Inverser la sélection',
                    'shortcuts.add-to-selection': 'Ajouter à la sélection',
                    'shortcuts.remove-from-selection': 'Retirer de la sélection',
                    'shortcuts.delete-selected-splats': 'Supprimer splats sélectionnés',
                    'shortcuts.show': 'AFFICHER',
                    'shortcuts.hide-selected-splats': 'Masquer splats sélectionnés',
                    'shortcuts.unhide-all-splats': 'Réafficher tous les splats',
                    'shortcuts.toggle-data-panel': 'Afficher/Cacher l\'onglet données',
                    'shortcuts.other': 'AUTRES',
                    'shortcuts.select-next-splat': 'Sélectionner le splat suivant',
                    'shortcuts.undo': 'Annuler',
                    'shortcuts.redo': 'Rétablir',
                    'shortcuts.toggle-splat-overlay': 'Basculer affichage splat',
                    'shortcuts.focus-camera': 'Focaliser la caméra sur la sélection actuelle',
                    'shortcuts.toggle-camera-mode': 'Basculer le mode de camera',
                    'shortcuts.toggle-grid': 'Afficher/Cacher la grille',
                    'shortcuts.toggle-gizmo-coordinate-space': 'Basculer en espace de coordonnées Gizmo',
                    // Popup
                    'popup.ok': 'OK',
                    'popup.cancel': 'Annuler',
                    'popup.yes': 'Oui',
                    'popup.no': 'Non',
                    'popup.error': 'ERREUR',
                    'popup.error-loading': 'Erreur de chargement du fichier',
                    'popup.drop-files': 'Veuillez déposer des fichiers et des dossiers',
                    'popup.copy-to-clipboard': 'Copier le lien dans le presse-papiers',
                    // Right toolbar
                    'tooltip.splat-mode': 'Mode splat ( M )',
                    'tooltip.show-hide': 'Afficher/cacher les splats ( Barre espace )',
                    'tooltip.frame-selection': 'Cadrer la sélection ( F )',
                    'tooltip.camera-reset': 'Réinitialiser la caméra',
                    'tooltip.color-panel': 'Couleurs',
                    'tooltip.view-options': 'Options d\'affichage',
                    // Bottom toolbar
                    'tooltip.undo': 'Annuler ( Ctrl + Z )',
                    'tooltip.redo': 'Rétablir ( Ctrl + Shift + Z )',
                    'tooltip.picker': 'Sélection avec pipette ( R )',
                    'tooltip.polygon': 'Sélection avec polygone ( P )',
                    'tooltip.brush': 'Sélection avec pinceau ( B )',
                    'tooltip.sphere': 'Sélection avec sphère',
                    'tooltip.box': 'Sélection de boîte',
                    'tooltip.translate': 'Translation ( 1 )',
                    'tooltip.rotate': 'Rotation ( 2 )',
                    'tooltip.scale': 'Échelle ( 3 )',
                    'tooltip.local-space': 'Espace local gizmo',
                    'tooltip.bound-center': 'Utiliser le centre de la limite',
                    // Viewer Export
                    'export.header': 'EXPORTER',
                    'export.type': 'Type d\'export',
                    'export.html': 'HTML',
                    'export.package': 'Package ZIP',
                    'export.sh-bands': 'Bandes SH',
                    'export.start-position': 'Position de départ',
                    'export.default': 'Défaut',
                    'export.viewport': 'Vue actuelle',
                    'export.pose-camera': '1ère pose de caméra',
                    'export.fov': 'Champ de vision',
                    'export.background-color': 'Arrière-plan',
                    'export.filename': 'Nom de fichier',
                    'export.animation': 'Animation',
                    'export.animation-none': 'Aucune',
                    'export.animation-track': 'Piste',
                    'export.ply-compress': 'Compresser PLY',
                    'export.splats-select': 'Splats',
                    'export.splats-select.all': 'Tous les Splats',
                    'export.format': 'Format de fichier',
                    'export.format-compressed-ply': 'PLY compressé',
                    'export.format-sog': 'SOG',
                    // Cursor
                    'cursor.click-to-copy': 'Cliquez pour copier',
                    'cursor.copied': 'Copié!',
                    // Doc
                    'doc.reset': 'REINITIALISER LA SCENE',
                    'doc.unsaved-message': 'Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir réinitialiser la scène?',
                    'doc.reset-message': 'Êtes-vous sûr de vouloir réinitialiser la scène?',
                    'doc.save-failed': 'ÉCHEC DE L\'ENREGISTREMENT',
                    'doc.load-failed': 'ÉCHEC DU CHARGEMENT',
                    // Publish Settings Dialog
                    'publish.header': 'PARAMETRES DE PUBLICATION',
                    'publish.ok': 'Publier',
                    'publish.cancel': 'Annuler',
                    'publish.title': 'Titre',
                    'publish.description': 'Description',
                    'publish.listed': 'Répertorié',
                    'publish.failed': 'ÉCHEC DE PUBLICATION',
                    'publish.please-try-again': 'Veuillez réessayer plus tard.',
                    'publish.succeeded': 'PUBLICATION RÉUSSIE',
                    'publish.message': 'Utilisez le lien ci-dessous pour accéder à votre scène.',
                    'publish.please-log-in': 'La publication sur PlayCanvas nécessite un compte utilisateur. Veuillez vous connecter et réessayer.',
                    'publish.converting': 'Conversion...',
                    'publish.uploading': 'Téléchargement...',
                    'publish.to': 'Publier sur',
                    'publish.new-scene': 'Nouvelle scène',
                    // Video Settings Dialog
                    'video.header': 'PARAMETRES VIDEO',
                    'video.resolution': 'Résolution',
                    'video.frameRate': 'Fréquence d\'image',
                    'video.bitrate': 'Débit binaire',
                    'video.portrait': 'Mode portrait',
                    'video.transparentBg': 'Fond transparent',
                    'video.showDebug': 'Afficher les superpositions de débogage',
                    // Timeline
                    'timeline.prev-key': 'Aller à la clé précédente',
                    'timeline.play': 'Jouer/Arrêter',
                    'timeline.next-key': 'Aller à la clé suivante',
                    'timeline.add-key': 'Ajouter une clé',
                    'timeline.remove-key': 'Supprimer une clé',
                    'timeline.frame-rate': 'Fréquence d\'image',
                    'timeline.total-frames': 'Nombre total de frames',
                    'timeline.smoothness': 'Douceur'
                }
            },
            ja: {
                translation: {
                    // File menu
                    'file': 'ファイル',
                    'file.new': '新規作成',
                    'file.open': '開く',
                    'file.import': 'インポート...',
                    'file.load-all-data': '全てのPLYデータを読み込む',
                    'file.save': '保存',
                    'file.save-as': '名前を付けて保存',
                    'file.publish': '公開...',
                    'file.export': 'エクスポート',
                    'file.export.splat': 'Splat (.splat)',
                    'file.export.viewer': 'Viewer App...',
                    // Render menu
                    'render': 'レンダリング',
                    'render.image': '画像...',
                    'render.video': '動画...',
                    'render.ok': 'レンダリング',
                    'render.cancel': 'キャンセル',
                    // Selection menu
                    'select': '選択',
                    'select.all': '全て',
                    'select.none': '選択を解除',
                    'select.invert': '反転',
                    'select.lock': '選択をロック',
                    'select.unlock': 'ロックを解除',
                    'select.delete': '選択を削除',
                    'select.reset': '変更を全てリセット',
                    'select.duplicate': '複製',
                    'select.separate': '分離',
                    // Help menu
                    'help': 'ヘルプ',
                    'help.shortcuts': 'キーボードショートカット',
                    'help.user-guide': 'ユーザーガイド',
                    'help.log-issue': '問題を報告',
                    'help.github-repo': 'GitHubリポジトリ',
                    'help.basics-video': '基本ビデオ',
                    'help.discord': 'Discordサーバー',
                    'help.forum': 'フォーラム',
                    'help.about': 'SuperSplatについて',
                    // Modes
                    'mode.centers': 'センターモード',
                    'mode.rings': 'リングモード',
                    // Scene panel
                    'scene-manager': 'シーンマネージャー',
                    'transform': 'トランスフォーム',
                    'position': '位置',
                    'rotation': '回転',
                    'scale': 'スケール',
                    // Options panel
                    'options': '表示オプション',
                    'options.colors': '色',
                    'options.bg-color': '背景色',
                    'options.selected-color': '選択色',
                    'options.unselected-color': '非選択色',
                    'options.locked-color': 'ロック色',
                    'options.fov': '視野 ( FOV )',
                    'options.sh-bands': '球面調和関数のバンド',
                    'options.centers-size': 'センターサイズ',
                    'options.outline-selection': '選択のアウトライン',
                    'options.show-grid': 'グリッド',
                    'options.show-bound': 'バウンディングボックス',
                    'options.camera-fly-speed': 'カメラの移動速度',
                    // Camera panel
                    'camera': 'カメラポーズ',
                    'camera.add-pose': 'ポーズを追加',
                    'camera.prev-pose': '前のポーズ',
                    'camera.next-pose': '次のポーズ',
                    'camera.play-poses': 'ポーズを再生',
                    'camera.clear-poses': 'ポーズをクリア',
                    // Color panel
                    'colors': '色',
                    'colors.tint': '色合い',
                    'colors.temperature': '温度',
                    'colors.saturation': '彩度',
                    'colors.brightness': '明るさ',
                    'colors.blackPoint': '黒点',
                    'colors.whitePoint': '白点',
                    'colors.transparency': '透明度',
                    'colors.reset': 'リセット',
                    // Data panel
                    'data': 'スプラットの統計',
                    'data.distance': '距離',
                    'data.volume': '体積',
                    'data.surface-area': '表面積',
                    'data.scale-x': 'スケール X',
                    'data.scale-y': 'スケール Y',
                    'data.scale-z': 'スケール Z',
                    'data.red': '赤',
                    'data.green': '緑',
                    'data.blue': '青',
                    'data.opacity': '不透明度',
                    'data.hue': '色相',
                    'data.saturation': '彩度',
                    'data.value': '明度',
                    'data.log-scale': '対数スケール',
                    'data.totals': '合計',
                    'data.totals.splats': 'スプラット数',
                    'data.totals.selected': '選択中',
                    'data.totals.locked': 'ロック中',
                    'data.totals.deleted': '削除',
                    // Shortcuts panel
                    'shortcuts.title': 'キーボードショートカット',
                    'shortcuts.tools': 'ツール',
                    'shortcuts.move': '移動',
                    'shortcuts.rotate': '回転',
                    'shortcuts.scale': 'スケール',
                    'shortcuts.rect-selection': '四角形選択',
                    'shortcuts.brush-selection': 'ブラシ選択',
                    'shortcuts.picker-selection': 'ピッカー選択',
                    'shortcuts.brush-size': 'ブラシサイズの増減',
                    'shortcuts.deactivate-tool': 'ツールの非アクティブ化',
                    'shortcuts.selection': '選択',
                    'shortcuts.select-all': '全て選択',
                    'shortcuts.deselect-all': '全て選択解除',
                    'shortcuts.invert-selection': '選択反転',
                    'shortcuts.add-to-selection': '選択追加',
                    'shortcuts.remove-from-selection': '選択解除',
                    'shortcuts.delete-selected-splats': '選択削除',
                    'shortcuts.show': '表示',
                    'shortcuts.hide-selected-splats': '選択非表示',
                    'shortcuts.unhide-all-splats': '全て表示',
                    'shortcuts.toggle-data-panel': 'データパネルの切り替え',
                    'shortcuts.other': 'その他',
                    'shortcuts.select-next-splat': '次のスプラットを選択',
                    'shortcuts.undo': '元に戻す',
                    'shortcuts.redo': 'やり直し',
                    'shortcuts.toggle-splat-overlay': 'スプラットオーバーレイの切り替え',
                    'shortcuts.focus-camera': 'カメラの焦点を合わせる',
                    'shortcuts.toggle-camera-mode': 'カメラモードの切り替え',
                    'shortcuts.toggle-grid': 'グリッドの切り替え',
                    'shortcuts.toggle-gizmo-coordinate-space': 'ギズモ座標空間の切り替え',
                    // Popup
                    'popup.ok': 'OK',
                    'popup.cancel': 'キャンセル',
                    'popup.yes': 'はい',
                    'popup.no': 'いいえ',
                    'popup.error': 'エラー',
                    'popup.error-loading': 'ファイルの読み込みエラー',
                    'popup.drop-files': 'ファイルやフォルダをドロップしてください',
                    'popup.copy-to-clipboard': 'リンクをクリップボードにコピー',
                    // Right toolbar
                    'tooltip.splat-mode': 'スプラットモード ( M )',
                    'tooltip.show-hide': 'スプラットの表示/非表示 ( Space )',
                    'tooltip.frame-selection': '選択をフレームイン ( F )',
                    'tooltip.camera-reset': 'カメラをリセット',
                    'tooltip.color-panel': '色',
                    'tooltip.view-options': '表示オプション',
                    // Bottom toolbar
                    'tooltip.undo': '元に戻す ( Ctrl + Z )',
                    'tooltip.redo': 'やり直し ( Ctrl + Shift + Z )',
                    'tooltip.picker': 'ピッカー選択 ( R )',
                    'tooltip.polygon': 'ポリゴン選択 ( P )',
                    'tooltip.brush': 'ブラシ選択 ( B )',
                    'tooltip.sphere': '球で選択',
                    'tooltip.box': '箱で選択',
                    'tooltip.translate': '移動 ( 1 )',
                    'tooltip.rotate': '回転 ( 2 )',
                    'tooltip.scale': 'スケール ( 3 )',
                    'tooltip.local-space': 'ローカル座標へ切り替え',
                    'tooltip.bound-center': 'バウンディングボックスの中心を使用',
                    // Viewer Export
                    'export.header': 'エクスポート',
                    'export.type': 'エクスポートタイプ',
                    'export.html': 'HTML',
                    'export.package': 'ZIPパッケージ',
                    'export.sh-bands': 'SHバンド',
                    'export.start-position': '開始位置',
                    'export.default': 'デフォルト',
                    'export.viewport': '現在のビューポート',
                    'export.pose-camera': '1番目のカメラポーズ',
                    'export.fov': '視野角',
                    'export.background-color': '背景色',
                    'export.filename': 'ファイル名',
                    'export.animation': 'アニメーション',
                    'export.animation-none': 'なし',
                    'export.animation-track': 'トラック',
                    'export.ply-compress': 'PLYを圧縮',
                    'export.splats-select': 'Splat',
                    'export.splats-select.all': 'すべてのSplat',
                    'export.format': 'ファイル形式',
                    'export.format-compressed-ply': '圧縮PLY',
                    'export.format-sog': 'SOG',
                    // Cursor
                    'cursor.click-to-copy': 'クリックしてコピー',
                    'cursor.copied': 'コピーしました！',
                    // Doc
                    'doc.reset': 'シーンをリセット',
                    'doc.unsaved-message': '保存されていない変更があります。シーンをリセットしてもよろしいですか？',
                    'doc.reset-message': 'シーンをリセットしてもよろしいですか？',
                    'doc.save-failed': '保存に失敗',
                    'doc.load-failed': '読み込みに失敗',
                    // Publish Settings Dialog
                    'publish.header': '公開設定',
                    'publish.ok': '公開',
                    'publish.cancel': 'キャンセル',
                    'publish.title': 'タイトル',
                    'publish.description': '説明',
                    'publish.listed': 'リスト',
                    'publish.failed': '公開に失敗',
                    'publish.please-try-again': '後でもう一度お試しください。',
                    'publish.succeeded': '公開に成功',
                    'publish.message': '以下のリンクを使用してシーンにアクセスしてください。',
                    'publish.please-log-in': 'PlayCanvasに公開するにはユーザーアカウントが必要です。ログインしてもう一度お試しください。',
                    'publish.converting': '変換中...',
                    'publish.uploading': 'アップロード中...',
                    'publish.to': 'に公開',
                    'publish.new-scene': '新しいシーン',
                    // Video Settings Dialog
                    'video.header': 'ビデオ設定',
                    'video.resolution': '解像度',
                    'video.frameRate': 'フレームレート',
                    'video.bitrate': 'ビットレート',
                    'video.portrait': 'ポートレートモード',
                    'video.transparentBg': '透明な背景',
                    'video.showDebug': 'デバッグオーバーレイを表示',
                    // Timeline
                    'timeline.prev-key': '前のキーフレームに移動',
                    'timeline.play': '再生/停止',
                    'timeline.next-key': '次のキーフレームに移動',
                    'timeline.add-key': 'キーフレームを追加',
                    'timeline.remove-key': 'キーフレームを削除',
                    'timeline.frame-rate': 'フレームレート',
                    'timeline.total-frames': '総フレーム数',
                    'timeline.smoothness': 'スムーズさ'
                }
            },
            ko: {
                translation: {
                    // File menu
                    'file': '파일',
                    'file.new': '새로 만들기',
                    'file.open': '열기',
                    'file.import': '가져오기...',
                    'file.load-all-data': '모든 PLY 데이터 불러오기',
                    'file.save': '저장',
                    'file.save-as': '다른 이름으로 저장...',
                    'file.publish': '게시...',
                    'file.export': '내보내기',
                    'file.export.splat': 'Splat 파일',
                    'file.export.viewer': '뷰어 앱...',
                    // Render menu
                    'render': '렌더링',
                    'render.image': '이미지...',
                    'render.video': '비디오...',
                    'render.ok': '렌더링',
                    'render.cancel': '취소',
                    // Selection menu
                    'select': '선택',
                    'select.all': '모두',
                    'select.none': '없음',
                    'select.invert': '반전',
                    'select.lock': '선택 잠금',
                    'select.unlock': '모두 잠금 해제',
                    'select.delete': '선택 삭제',
                    'select.reset': 'Splat 재설정',
                    'select.duplicate': '복제',
                    'select.separate': '분리',
                    // Help menu
                    'help': '도움말',
                    'help.shortcuts': '키보드 단축키',
                    'help.user-guide': '사용자 가이드',
                    'help.log-issue': '문제 보고',
                    'help.github-repo': 'GitHub 저장소',
                    'help.basics-video': '기본 비디오',
                    'help.discord': 'Discord 서버',
                    'help.forum': '포럼',
                    'help.about': 'SuperSplat 정보',
                    // Modes
                    'mode.centers': '센터 모드',
                    'mode.rings': '링 모드',
                    // Scene panel
                    'scene-manager': '장면 관리자',
                    'transform': '변환',
                    'position': '위치',
                    'rotation': '회전',
                    'scale': '크기',
                    // Options panel
                    'options': '보기 옵션',
                    'options.colors': '색상',
                    'options.bg-color': '배경 색상',
                    'options.selected-color': '선택된 색상',
                    'options.unselected-color': '선택되지 않은 색상',
                    'options.locked-color': '잠긴 색상',
                    'options.fov': '시야각',
                    'options.sh-bands': 'SH 밴드',
                    'options.centers-size': '센터 크기',
                    'options.outline-selection': '선택 윤곽선',
                    'options.show-grid': '그리드 표시',
                    'options.show-bound': '경계 표시',
                    'options.camera-fly-speed': '카메라 이동 속도',
                    // Camera panel
                    'camera': '카메라 포즈',
                    'camera.add-pose': '포즈 추가',
                    'camera.prev-pose': '이전 포즈',
                    'camera.next-pose': '다음 포즈',
                    'camera.play-poses': '포즈 재생',
                    'camera.clear-poses': '포즈 지우기',
                    // Color panel
                    'colors': '색상',
                    'colors.tint': '색조',
                    'colors.temperature': '온도',
                    'colors.brightness': '밝기',
                    'colors.blackPoint': '검은 점',
                    'colors.whitePoint': '흰 점',
                    'colors.transparency': '투명도',
                    'colors.reset': '리셋',
                    // Data panel
                    'data': 'SPLAT 데이터',
                    'data.distance': '거리',
                    'data.volume': '부피',
                    'data.surface-area': '표면적',
                    'data.scale-x': '크기 X',
                    'data.scale-y': '크기 Y',
                    'data.scale-z': '크기 Z',
                    'data.red': '빨강',
                    'data.green': '녹색',
                    'data.blue': '파랑',
                    'data.opacity': '불투명도',
                    'data.hue': '색조',
                    'data.saturation': '채도',
                    'data.value': '명도',
                    'data.log-scale': '로그 크기',
                    'data.totals': '합계',
                    'data.totals.splats': 'Splat',
                    'data.totals.selected': '선택',
                    'data.totals.locked': '잠금',
                    'data.totals.deleted': '삭제된',
                    // Shortcuts panel
                    'shortcuts.title': '키보드 단축키',
                    'shortcuts.tools': '도구',
                    'shortcuts.move': '이동',
                    'shortcuts.rotate': '회전',
                    'shortcuts.scale': '크기 조정',
                    'shortcuts.rect-selection': '사각형 선택',
                    'shortcuts.brush-selection': '브러시 선택',
                    'shortcuts.picker-selection': '피커 선택',
                    'shortcuts.brush-size': '브러시 크기 조정',
                    'shortcuts.deactivate-tool': '도구 비활성화',
                    'shortcuts.selection': '선택',
                    'shortcuts.select-all': '모두 선택',
                    'shortcuts.deselect-all': '모두 선택 해제',
                    'shortcuts.invert-selection': '선택 반전',
                    'shortcuts.add-to-selection': '선택 추가',
                    'shortcuts.remove-from-selection': '선택 제거',
                    'shortcuts.delete-selected-splats': '선택된 Splat 삭제',
                    'shortcuts.show': '표시',
                    'shortcuts.hide-selected-splats': '선택된 Splat 숨기기',
                    'shortcuts.unhide-all-splats': '모든 Splat 표시',
                    'shortcuts.toggle-data-panel': '데이터 패널 전환',
                    'shortcuts.other': '기타',
                    'shortcuts.select-next-splat': '다음 Splat 선택',
                    'shortcuts.undo': '실행 취소',
                    'shortcuts.redo': '다시 실행',
                    'shortcuts.toggle-splat-overlay': 'Splat 오버레이 전환',
                    'shortcuts.focus-camera': '현재 선택에 초점 맞추기',
                    'shortcuts.toggle-camera-mode': '카메라 모드 전환',
                    'shortcuts.toggle-grid': '그리드 전환',
                    'shortcuts.toggle-gizmo-coordinate-space': '기즈모 좌표 공간 전환',
                    // Popup
                    'popup.ok': '확인',
                    'popup.cancel': '취소',
                    'popup.yes': '예',
                    'popup.no': '아니요',
                    'popup.error': '오류',
                    'popup.error-loading': '파일 로드 오류',
                    'popup.drop-files': '파일 및 폴더를 드롭하세요',
                    'popup.copy-to-clipboard': '클립 보드에 링크 복사',
                    // Right toolbar
                    'tooltip.splat-mode': 'Splat 모드 ( M )',
                    'tooltip.show-hide': '스플래츠 표시/숨기기 ( Space )',
                    'tooltip.frame-selection': '프레임 선택 ( F )',
                    'tooltip.camera-reset': '카메라 재설정',
                    'tooltip.color-panel': '색상',
                    'tooltip.view-options': '보기 옵션',
                    // Bottom toolbar
                    'tooltip.undo': '실행 취소 ( Ctrl + Z )',
                    'tooltip.redo': '다시 실행 ( Ctrl + Shift + Z )',
                    'tooltip.picker': '피커 선택 ( R )',
                    'tooltip.polygon': '다각형 선택 ( P )',
                    'tooltip.brush': '브러시 선택 ( B )',
                    'tooltip.sphere': '구 선택',
                    'tooltip.box': '상자 선택',
                    'tooltip.translate': '이동 ( 1 )',
                    'tooltip.rotate': '회전 ( 2 )',
                    'tooltip.scale': '크기 조정 ( 3 )',
                    'tooltip.local-space': '로컬 공간',
                    'tooltip.bound-center': '바운드 중심 사용',
                    // Viewer Export
                    'export.header': '내보내기',
                    'export.type': '내보내기 유형',
                    'export.html': 'HTML',
                    'export.package': 'ZIP 패키지',
                    'export.sh-bands': 'SH 밴드',
                    'export.start-position': '시작 위치',
                    'export.default': '기본값',
                    'export.viewport': '현재 뷰포트',
                    'export.pose-camera': '1번째 카메라 포즈',
                    'export.fov': '시야각',
                    'export.background-color': '배경색',
                    'export.filename': '파일 이름',
                    'export.animation': '애니메이션',
                    'export.animation-none': '없음',
                    'export.animation-track': '트랙',
                    'export.ply-compress': 'PLY 압축',
                    'export.splats-select': 'Splat',
                    'export.splats-select.all': '모든 Splat',
                    'export.format': '파일 형식',
                    'export.format-compressed-ply': '압축된 PLY',
                    'export.format-sog': 'SOG',
                    // Cursor
                    'cursor.click-to-copy': '클릭하여 복사',
                    'cursor.copied': '복사됨!',
                    // Doc
                    'doc.reset': '장면 재설정',
                    'doc.unsaved-message': '저장되지 않은 변경 사항이 있습니다. 장면을 재설정하시겠습니까?',
                    'doc.reset-message': '장면을 재설정하시겠습니까?',
                    'doc.save-failed': '저장 실패',
                    'doc.load-failed': '로드 실패',
                    // Publish Settings Dialog
                    'publish.header': '게시 설정',
                    'publish.ok': '게시',
                    'publish.cancel': '취소',
                    'publish.title': '제목',
                    'publish.description': '설명',
                    'publish.listed': '목록',
                    'publish.failed': '게시 실패',
                    'publish.please-try-again': '잠시 후 다시 시도하십시오.',
                    'publish.succeeded': '게시 성공',
                    'publish.message': '아래 링크를 사용하여 장면에 액세스하십시오.',
                    'publish.please-log-in': 'PlayCanvas에 게시하려면 사용자 계정이 필요합니다. 로그인하고 다시 시도하십시오.',
                    'publish.converting': '변환 중...',
                    'publish.uploading': '업로드 중...',
                    'publish.to': '다음에 게시',
                    'publish.new-scene': '새 장면',
                    // Video Settings Dialog
                    'video.header': '비디오 설정',
                    'video.resolution': '해상도',
                    'video.frameRate': '프레임 속도',
                    'video.bitrate': '비트 전송률',
                    'video.portrait': '세로 모드',
                    'video.transparentBg': '투명 배경',
                    'video.showDebug': '디버그 오버레이 표시',
                    // Timeline
                    'timeline.prev-key': '이전 키로 이동',
                    'timeline.play': '재생/정지',
                    'timeline.next-key': '다음 키로 이동',
                    'timeline.add-key': '키 추가',
                    'timeline.remove-key': '키 제거',
                    'timeline.frame-rate': '프레임 속도',
                    'timeline.total-frames': '총 프레임 수',
                    'timeline.smoothness': '부드러움'
                }
            },
            'zh-CN': {
                translation: {
                    // File menu
                    'file': '文件',
                    'file.new': '新建',
                    'file.open': '打开',
                    'file.import': '导入...',
                    'file.load-all-data': '加载所有 PLY 数据',
                    'file.save': '保存',
                    'file.save-as': '另存为...',
                    'file.publish': '发布...',
                    'file.export': '导出',
                    'file.export.splat': 'Splat 文件',
                    'file.export.viewer': '查看器应用...',
                    // Render menu
                    'render': '渲染',
                    'render.image': '图像...',
                    'render.video': '视频...',
                    'render.ok': '渲染',
                    'render.cancel': '取消',
                    // Selection menu
                    'select': '选择',
                    'select.all': '全部',
                    'select.none': '无',
                    'select.invert': '反选',
                    'select.lock': '锁定选择',
                    'select.unlock': '解锁全部',
                    'select.delete': '删除选择',
                    'select.reset': '重置 Splat',
                    'select.duplicate': '复制',
                    'select.separate': '分离',
                    // Help menu
                    'help': '帮助',
                    'help.shortcuts': '键盘快捷键',
                    'help.user-guide': '用户指南',
                    'help.log-issue': '报告问题',
                    'help.github-repo': 'GitHub 仓库',
                    'help.basics-video': '基础视频',
                    'help.discord': 'Discord 服务器',
                    'help.forum': '论坛',
                    'help.about': '关于 SuperSplat',
                    // Modes
                    'mode.centers': '中心模式',
                    'mode.rings': '环模式',
                    // Scene panel
                    'scene-manager': '场景管理器',
                    'transform': '变换',
                    'position': '位置',
                    'rotation': '旋转',
                    'scale': '缩放',
                    // Options panel
                    'options': '视图选项',
                    'options.colors': '颜色',
                    'options.bg-color': '背景颜色',
                    'options.selected-color': '选中颜色',
                    'options.unselected-color': '未选中颜色',
                    'options.locked-color': '锁定颜色',
                    'options.fov': '视野角',
                    'options.sh-bands': 'SH 带',
                    'options.centers-size': '中心大小',
                    'options.outline-selection': '轮廓选择',
                    'options.show-grid': '显示网格',
                    'options.show-bound': '显示边界',
                    'options.camera-fly-speed': '相机飞行速度',
                    // Camera panel
                    'camera': '相机姿势',
                    'camera.add-pose': '添加姿势',
                    'camera.prev-pose': '上一个姿势',
                    'camera.next-pose': '下一个姿势',
                    'camera.play-poses': '播放姿势',
                    'camera.clear-poses': '清除姿势',
                    // Color panel
                    'colors': '颜色',
                    'colors.tint': '色调',
                    'colors.temperature': '温度',
                    'colors.saturation': '饱和度',
                    'colors.brightness': '亮度',
                    'colors.blackPoint': '黑点',
                    'colors.whitePoint': '白点',
                    'colors.transparency': '透明度',
                    'colors.reset': '重置',
                    // Data panel
                    'data': 'SPLAT 数据',
                    'data.distance': '距离',
                    'data.volume': '体积',
                    'data.surface-area': '表面积',
                    'data.scale-x': '缩放 X',
                    'data.scale-y': '缩放 Y',
                    'data.scale-z': '缩放 Z',
                    'data.red': '红',
                    'data.green': '绿',
                    'data.blue': '蓝',
                    'data.opacity': '不透明度',
                    'data.hue': '色相',
                    'data.saturation': '饱和度',
                    'data.value': '明度',
                    'data.log-scale': '对数缩放',
                    'data.totals': '总计',
                    'data.totals.splats': 'Splat',
                    'data.totals.selected': '选择',
                    'data.totals.locked': '锁定',
                    'data.totals.deleted': '删除',
                    // Shortcuts panel
                    'shortcuts.title': '键盘快捷键',
                    'shortcuts.tools': '工具',
                    'shortcuts.move': '移动',
                    'shortcuts.rotate': '旋转',
                    'shortcuts.scale': '缩放',
                    'shortcuts.rect-selection': '矩形选择',
                    'shortcuts.brush-selection': '画笔选择',
                    'shortcuts.picker-selection': '拾取选择',
                    'shortcuts.brush-size': '减小/增大画笔大小',
                    'shortcuts.deactivate-tool': '停用工具',
                    'shortcuts.selection': '选择',
                    'shortcuts.select-all': '全选',
                    'shortcuts.deselect-all': '取消全选',
                    'shortcuts.invert-selection': '反选',
                    'shortcuts.add-to-selection': '添加到选择',
                    'shortcuts.remove-from-selection': '从选择中移除',
                    'shortcuts.delete-selected-splats': '删除选择的 Splat',
                    'shortcuts.show': '显示',
                    'shortcuts.hide-selected-splats': '隐藏选择的 Splat',
                    'shortcuts.unhide-all-splats': '显示全部 Splat',
                    'shortcuts.toggle-data-panel': '切换数据面板',
                    'shortcuts.other': '其他',
                    'shortcuts.select-next-splat': '选择下一个 Splat',
                    'shortcuts.undo': '撤销',
                    'shortcuts.redo': '重做',
                    'shortcuts.toggle-splat-overlay': '切换 Splat 叠加',
                    'shortcuts.focus-camera': '聚焦当前选择',
                    'shortcuts.toggle-camera-mode': '切换相机模式',
                    'shortcuts.toggle-grid': '切换网格',
                    'shortcuts.toggle-gizmo-coordinate-space': '切换 Gizmo 坐标空间',
                    // Popup
                    'popup.ok': '确定',
                    'popup.cancel': '取消',
                    'popup.yes': '是',
                    'popup.no': '否',
                    'popup.error': '错误',
                    'popup.error-loading': '加载文件错误',
                    'popup.drop-files': '请拖放文件和文件夹',
                    'popup.copy-to-clipboard': '复制链接到剪贴板',
                    // Right toolbar
                    'tooltip.splat-mode': 'Splat 模式 ( M )',
                    'tooltip.show-hide': '显示/隐藏 Splats ( Space )',
                    'tooltip.frame-selection': '框选 ( F )',
                    'tooltip.camera-reset': '重置相机',
                    'tooltip.color-panel': '颜色',
                    'tooltip.view-options': '视图选项',
                    // Bottom toolbar
                    'tooltip.undo': '撤销 ( Ctrl + Z )',
                    'tooltip.redo': '重做 ( Ctrl + Shift + Z )',
                    'tooltip.picker': '选择器 ( R )',
                    'tooltip.polygon': '多边形选择 ( P )',
                    'tooltip.brush': '画笔 ( B )',
                    'tooltip.sphere': '球选择',
                    'tooltip.box': '盒选择',
                    'tooltip.translate': '移动 ( 1 )',
                    'tooltip.rotate': '旋转 ( 2 )',
                    'tooltip.scale': '缩放 ( 3 )',
                    'tooltip.local-space': '局部坐标系',
                    'tooltip.bound-center': '使用边界中心',
                    // Viewer Export
                    'export.header': '导出',
                    'export.type': '导出类型',
                    'export.html': 'HTML',
                    'export.package': 'ZIP 包',
                    'export.sh-bands': 'SH 带',
                    'export.start-position': '起始位置',
                    'export.default': '默认',
                    'export.viewport': '当前视口',
                    'export.pose-camera': '第一个相机姿势',
                    'export.fov': '视野角',
                    'export.background-color': '背景颜色',
                    'export.filename': '文件名',
                    'export.animation': '动画',
                    'export.animation-none': '无',
                    'export.animation-track': '轨道',
                    'export.ply-compress': '压缩PLY',
                    'export.splats-select': 'Splat',
                    'export.splats-select.all': '所有Splat',
                    'export.format': '文件格式',
                    'export.format-compressed-ply': '压缩 PLY',
                    'export.format-sog': 'SOG',
                    // Cursor
                    'cursor.click-to-copy': '点击复制',
                    'cursor.copied': '已复制!',
                    // Doc
                    'doc.reset': '重置场景',
                    'doc.unsaved-message': '有未保存的更改。您确定要重置场景吗？',
                    'doc.reset-message': '您确定要重置场景吗？',
                    'doc.save-failed': '保存失败',
                    'doc.load-failed': '加载失败',
                    // Publish Settings Dialog
                    'publish.header': '发布设置',
                    'publish.ok': '发布',
                    'publish.cancel': '取消',
                    'publish.title': '标题',
                    'publish.description': '描述',
                    'publish.listed': '列出',
                    'publish.failed': '发布失败',
                    'publish.please-try-again': '请稍后再试。',
                    'publish.succeeded': '发布成功',
                    'publish.message': '请使用以下链接访问场景。',
                    'publish.please-log-in': '要在 PlayCanvas 上发布，需要用户帐户。请登录并重试。',
                    'publish.converting': '转换中...',
                    'publish.uploading': '上传中...',
                    'publish.to': '发布到',
                    'publish.new-scene': '新场景',
                    // Video Settings Dialog
                    'video.header': '视频设置',
                    'video.resolution': '分辨率',
                    'video.frameRate': '帧率',
                    'video.bitrate': '比特率',
                    'video.portrait': '纵向模式',
                    'video.transparentBg': '透明背景',
                    'video.showDebug': '显示调试覆盖',
                    // Timeline
                    'timeline.prev-key': '上一个关键帧',
                    'timeline.play': '播放/停止',
                    'timeline.next-key': '下一个关键帧',
                    'timeline.add-key': '添加关键帧',
                    'timeline.remove-key': '删除关键帧',
                    'timeline.frame-rate': '帧率',
                    'timeline.total-frames': '总帧数',
                    'timeline.smoothness': '平滑度'
                }
            }
        },
        interpolation: {
            escapeValue: false
        }
    });
};
const localize = (key) => {
    return instance.t(key);
};

const SuperFileType = [{
        description: 'SuperSplat document',
        accept: {
            'application/x-supersplat': ['.ssproj']
        }
    }];
// helper class to show a file selector dialog.
// used when showOpenFilePicker is not available.
class FileSelector {
    show;
    constructor() {
        const fileSelector = document.createElement('input');
        fileSelector.setAttribute('id', 'document-file-selector');
        fileSelector.setAttribute('type', 'file');
        fileSelector.setAttribute('accept', '.ssproj');
        fileSelector.setAttribute('multiple', 'false');
        document.body.append(fileSelector);
        let callbackFunc = null;
        fileSelector.addEventListener('change', () => {
            callbackFunc(fileSelector.files[0]);
        });
        fileSelector.addEventListener('cancel', () => {
            callbackFunc(null);
        });
        this.show = (func) => {
            callbackFunc = func;
            fileSelector.click();
        };
    }
}
const registerDocEvents = (scene, events) => {
    // construct the file selector
    const fileSelector = window.showOpenFilePicker ? null : new FileSelector();
    // this file handle is updated as the current document is loaded and saved
    let documentFileHandle = null;
    // show the user a reset confirmation popup
    const getResetConfirmation = async () => {
        const result = await events.invoke('showPopup', {
            type: 'yesno',
            header: localize('doc.reset'),
            message: localize(events.invoke('scene.dirty') ? 'doc.unsaved-message' : 'doc.reset-message')
        });
        if (result.action !== 'yes') {
            return false;
        }
        return true;
    };
    // reset the scene
    const resetScene = () => {
        events.fire('scene.clear');
        events.fire('camera.reset');
        events.fire('doc.setName', null);
        documentFileHandle = null;
    };
    // load the document from the given file
    const loadDocument = async (file) => {
        events.fire('startSpinner');
        try {
            // reset the scene
            resetScene();
            // read the document
            /* global JSZip */
            // @ts-ignore
            const zip = new JSZip();
            await zip.loadAsync(file);
            const document = JSON.parse(await zip.file('document.json').async('text'));
            // run through each splat and load it
            for (let i = 0; i < document.splats.length; ++i) {
                const filename = `splat_${i}.ply`;
                const splatSettings = document.splats[i];
                // construct the splat asset
                const contents = await zip.file(`splat_${i}.ply`).async('blob');
                const url = URL.createObjectURL(contents);
                const splat = await scene.assetLoader.loadModel({
                    url,
                    filename
                });
                URL.revokeObjectURL(url);
                scene.add(splat);
                splat.docDeserialize(splatSettings);
            }
            // FIXME: trigger scene bound calc in a better way
            const tmp = scene.bound;
            if (tmp === null) {
                console.error('this should never fire');
            }
            events.invoke('docDeserialize.timeline', document.timeline);
            events.invoke('docDeserialize.poseSets', document.poseSets);
            events.invoke('docDeserialize.view', document.view);
            scene.camera.docDeserialize(document.camera);
        }
        catch (error) {
            await events.invoke('showPopup', {
                type: 'error',
                header: localize('doc.load-failed'),
                message: `'${error.message ?? error}'`
            });
        }
        finally {
            events.fire('stopSpinner');
        }
    };
    const saveDocument = async (options) => {
        events.fire('startSpinner');
        try {
            const splats = events.invoke('scene.allSplats');
            const document = {
                version: 0,
                camera: scene.camera.docSerialize(),
                view: events.invoke('docSerialize.view'),
                poseSets: events.invoke('docSerialize.poseSets'),
                timeline: events.invoke('docSerialize.timeline'),
                splats: splats.map(s => s.docSerialize())
            };
            const serializeSettings = {
                // even though we support saving selection state, we disable that for now
                // because including a uint8 array in the document PLY results in slow loading
                // path.
                keepStateData: false,
                keepWorldTransform: true,
                keepColorTint: true
            };
            const writer = options.stream ? new FileStreamWriter(options.stream) : new DownloadWriter(options.filename);
            const zipWriter = new ZipWriter(writer);
            await zipWriter.file('document.json', JSON.stringify(document));
            for (let i = 0; i < splats.length; ++i) {
                await zipWriter.start(`splat_${i}.ply`);
                await serializePly([splats[i]], serializeSettings, zipWriter);
            }
            await zipWriter.close();
            await writer.close();
        }
        catch (error) {
            await events.invoke('showPopup', {
                type: 'error',
                header: localize('doc.save-failed'),
                message: `'${error.message ?? error}'`
            });
        }
        finally {
            events.fire('stopSpinner');
        }
    };
    // handle user requesting a new document
    events.function('doc.new', async () => {
        if (!await getResetConfirmation()) {
            return false;
        }
        resetScene();
        return true;
    });
    // handle document file being dropped
    // NOTE: on chrome it's possible to get the FileSystemFileHandle from the DataTransferItem
    // (which would result in more seamless user experience), but this is not yet supported in
    // other browsers.
    events.function('doc.load', async (file) => {
        if (!events.invoke('scene.empty') && !await getResetConfirmation()) {
            return false;
        }
        await loadDocument(file);
        events.fire('doc.setName', file.name);
    });
    events.function('doc.open', async () => {
        if (!events.invoke('scene.empty') && !await getResetConfirmation()) {
            return false;
        }
        if (fileSelector) {
            fileSelector.show(async (file) => {
                if (file) {
                    await loadDocument(file);
                }
            });
        }
        else {
            try {
                const fileHandles = await window.showOpenFilePicker({
                    id: 'SuperSplatDocumentOpen',
                    multiple: false,
                    types: SuperFileType
                });
                if (fileHandles?.length === 1) {
                    const fileHandle = fileHandles[0];
                    // null file handle incase loadDocument fails
                    await loadDocument(await fileHandle.getFile());
                    // store file handle for subsequent saves
                    documentFileHandle = fileHandle;
                    events.fire('doc.setName', fileHandle.name);
                }
            }
            catch (error) {
                if (error.name !== 'AbortError') {
                    console.error(error);
                }
            }
        }
    });
    events.function('doc.save', async () => {
        if (documentFileHandle) {
            try {
                await saveDocument({
                    stream: await documentFileHandle.createWritable()
                });
                events.fire('doc.saved');
            }
            catch (error) {
                if (error.name !== 'AbortError' && error.name !== 'NotAllowedError') {
                    console.error(error);
                }
            }
        }
        else {
            await events.invoke('doc.saveAs');
        }
    });
    events.function('doc.saveAs', async () => {
        if (window.showSaveFilePicker) {
            try {
                const handle = await window.showSaveFilePicker({
                    id: 'SuperSplatDocumentSave',
                    types: SuperFileType,
                    suggestedName: 'scene.ssproj'
                });
                await saveDocument({ stream: await handle.createWritable() });
                documentFileHandle = handle;
                events.fire('doc.setName', handle.name);
                events.fire('doc.saved');
            }
            catch (error) {
                if (error.name !== 'AbortError') {
                    console.error(error);
                }
            }
        }
        else {
            await saveDocument({
                filename: 'scene.ssproj'
            });
            events.fire('doc.saved');
        }
    });
    // doc name
    let docName = null;
    const setDocName = (name) => {
        if (name !== docName) {
            docName = name;
            events.fire('doc.name', docName);
        }
    };
    events.function('doc.name', () => {
        return docName;
    });
    events.on('doc.setName', (name) => {
        setDocName(name);
    });
};

class EditHistory {
    history = [];
    cursor = 0;
    events;
    constructor(events) {
        this.events = events;
        events.on('edit.undo', () => {
            if (this.canUndo()) {
                this.undo();
            }
        });
        events.on('edit.redo', () => {
            if (this.canRedo()) {
                this.redo();
            }
        });
        events.on('edit.add', (editOp, suppressOp = false) => {
            this.add(editOp, suppressOp);
        });
    }
    add(editOp, suppressOp = false) {
        while (this.cursor < this.history.length) {
            this.history.pop().destroy?.();
        }
        this.history.push(editOp);
        this.redo(suppressOp);
    }
    canUndo() {
        return this.cursor > 0;
    }
    canRedo() {
        return this.cursor < this.history.length;
    }
    undo() {
        const editOp = this.history[--this.cursor];
        editOp.undo();
        this.events.fire('edit.apply', editOp);
        this.fireEvents();
    }
    redo(suppressOp = false) {
        const editOp = this.history[this.cursor++];
        if (!suppressOp) {
            editOp.do();
        }
        this.events.fire('edit.apply', editOp);
        this.fireEvents();
    }
    fireEvents() {
        this.events.fire('edit.canUndo', this.canUndo());
        this.events.fire('edit.canRedo', this.canRedo());
    }
    clear() {
        this.history.forEach((editOp) => {
            editOp.destroy?.();
        });
        this.history = [];
        this.cursor = 0;
    }
}

// build an index array based on a boolean predicate over indices
const buildIndex = (total, pred) => {
    let num = 0;
    for (let i = 0; i < total; ++i) {
        if (pred(i))
            num++;
    }
    const result = new Uint32Array(num);
    let idx = 0;
    for (let i = 0; i < total; ++i) {
        if (pred(i)) {
            result[idx++] = i;
        }
    }
    return result;
};
class StateOp {
    splat;
    indices;
    doIt;
    undoIt;
    updateFlags;
    constructor(splat, filter, doIt, undoIt, updateFlags = State.selected) {
        const splatData = splat.splatData;
        const state = splatData.getProp('state');
        const indices = buildIndex(splatData.numSplats, i => filter(state[i], i));
        this.splat = splat;
        this.indices = indices;
        this.doIt = doIt;
        this.undoIt = undoIt;
        this.updateFlags = updateFlags;
    }
    do() {
        const splatData = this.splat.splatData;
        const state = splatData.getProp('state');
        for (let i = 0; i < this.indices.length; ++i) {
            const idx = this.indices[i];
            state[idx] = this.doIt(state[idx]);
        }
        this.splat.updateState(this.updateFlags);
    }
    undo() {
        const splatData = this.splat.splatData;
        const state = splatData.getProp('state');
        for (let i = 0; i < this.indices.length; ++i) {
            const idx = this.indices[i];
            state[idx] = this.undoIt(state[idx]);
        }
        this.splat.updateState(this.updateFlags);
    }
    destroy() {
        this.splat = null;
        this.indices = null;
    }
}
class SelectAllOp extends StateOp {
    name = 'selectAll';
    constructor(splat) {
        super(splat, state => state === 0, state => state | State.selected, state => state & (~State.selected));
    }
}
class SelectNoneOp extends StateOp {
    name = 'selectNone';
    constructor(splat) {
        super(splat, state => state === State.selected, state => state & (~State.selected), state => state | State.selected);
    }
}
class SelectInvertOp extends StateOp {
    name = 'selectInvert';
    constructor(splat) {
        super(splat, state => (state & (State.locked | State.deleted)) === 0, state => state ^ State.selected, state => state ^ State.selected);
    }
}
class SelectOp extends StateOp {
    name = 'selectOp';
    constructor(splat, op, filter) {
        const filterFunc = {
            add: (state, index) => (state === 0) && filter(index),
            remove: (state, index) => (state === State.selected) && filter(index),
            set: (state, index) => (state === State.selected) !== filter(index)
        };
        const doIt = {
            add: (state) => state | State.selected,
            remove: (state) => state & (~State.selected),
            set: (state) => state ^ State.selected
        };
        const undoIt = {
            add: (state) => state & (~State.selected),
            remove: (state) => state | State.selected,
            set: (state) => state ^ State.selected
        };
        super(splat, filterFunc[op], doIt[op], undoIt[op]);
    }
}
class HideSelectionOp extends StateOp {
    name = 'hideSelection';
    constructor(splat) {
        super(splat, state => state === State.selected, state => state | State.locked, state => state & (~State.locked), State.locked);
    }
}
class UnhideAllOp extends StateOp {
    name = 'unhideAll';
    constructor(splat) {
        super(splat, state => (state & (State.locked | State.deleted)) === State.locked, state => state & (~State.locked), state => state | State.locked, State.locked);
    }
}
class DeleteSelectionOp extends StateOp {
    name = 'deleteSelection';
    constructor(splat) {
        super(splat, state => state === State.selected, state => state | State.deleted, state => state & (~State.deleted), State.deleted);
    }
}
class ResetOp extends StateOp {
    name = 'reset';
    constructor(splat) {
        super(splat, state => (state & State.deleted) !== 0, state => state & (~State.deleted), state => state | State.deleted, State.deleted);
    }
}
// op for modifying a splat transform
class EntityTransformOp {
    name = 'entityTransform';
    splat;
    oldt;
    newt;
    constructor(options) {
        this.splat = options.splat;
        this.oldt = options.oldt;
        this.newt = options.newt;
    }
    do() {
        this.splat.move(this.newt.position, this.newt.rotation, this.newt.scale);
    }
    undo() {
        this.splat.move(this.oldt.position, this.oldt.rotation, this.oldt.scale);
    }
    destroy() {
        this.splat = null;
        this.oldt = null;
        this.newt = null;
    }
}
const mat$2 = new Mat4();
// op for modifying a subset of individual splats
class SplatsTransformOp {
    name = 'splatsTransform';
    splat;
    transform;
    paletteMap;
    constructor(options) {
        this.splat = options.splat;
        this.transform = options.transform;
        this.paletteMap = options.paletteMap;
    }
    do() {
        const { splat, transform, paletteMap } = this;
        const state = splat.splatData.getProp('state');
        const indices = splat.transformTexture.lock();
        // update splat transform palette indices
        for (let i = 0; i < state.length; ++i) {
            if (state[i] === State.selected) {
                indices[i] = paletteMap.get(indices[i]);
            }
        }
        splat.transformTexture.unlock();
        splat.transformPalette.alloc(paletteMap.size);
        // update transform palette
        const { transformPalette } = splat;
        this.paletteMap.forEach((newIdx, oldIdx) => {
            transformPalette.getTransform(oldIdx, mat$2);
            mat$2.mul2(transform, mat$2);
            transformPalette.setTransform(newIdx, mat$2);
        });
        splat.makeSelectionBoundDirty();
        splat.updatePositions();
    }
    undo() {
        const { splat, paletteMap } = this;
        const state = splat.splatData.getProp('state');
        const indices = splat.transformTexture.lock();
        // invert the palette map
        const inverseMap = new Map();
        paletteMap.forEach((newIdx, oldIdx) => {
            inverseMap.set(newIdx, oldIdx);
        });
        // restore the original transform indices
        for (let i = 0; i < state.length; ++i) {
            if (state[i] === State.selected) {
                indices[i] = inverseMap.get(indices[i]);
            }
        }
        splat.transformTexture.unlock();
        splat.transformPalette.free(paletteMap.size);
        splat.makeSelectionBoundDirty();
        splat.updatePositions();
    }
    destroy() {
        this.splat = null;
        this.transform = null;
        this.paletteMap = null;
    }
}
class PlacePivotOp {
    name = 'setPivot';
    pivot;
    oldt;
    newt;
    constructor(options) {
        this.pivot = options.pivot;
        this.oldt = options.oldt;
        this.newt = options.newt;
    }
    do() {
        this.pivot.place(this.newt);
    }
    undo() {
        this.pivot.place(this.oldt);
    }
}
class SetSplatColorAdjustmentOp {
    name;
    splat;
    newState;
    oldState;
    constructor(options) {
        const { splat, oldState, newState } = options;
        this.splat = splat;
        this.oldState = oldState;
        this.newState = newState;
    }
    do() {
        const { splat } = this;
        const { tintClr, temperature, saturation, brightness, blackPoint, whitePoint, transparency } = this.newState;
        if (tintClr)
            splat.tintClr = tintClr;
        if (temperature !== null)
            splat.temperature = temperature;
        if (saturation !== null)
            splat.saturation = saturation;
        if (brightness !== null)
            splat.brightness = brightness;
        if (blackPoint !== null)
            splat.blackPoint = blackPoint;
        if (whitePoint !== null)
            splat.whitePoint = whitePoint;
        if (transparency !== null)
            splat.transparency = transparency;
    }
    undo() {
        const { splat } = this;
        const { tintClr, temperature, saturation, brightness, blackPoint, whitePoint, transparency } = this.oldState;
        if (tintClr)
            splat.tintClr = tintClr;
        if (temperature !== null)
            splat.temperature = temperature;
        if (saturation !== null)
            splat.saturation = saturation;
        if (brightness !== null)
            splat.brightness = brightness;
        if (blackPoint !== null)
            splat.blackPoint = blackPoint;
        if (whitePoint !== null)
            splat.whitePoint = whitePoint;
        if (transparency !== null)
            splat.transparency = transparency;
    }
}
class MultiOp {
    name = 'multiOp';
    ops;
    constructor(ops) {
        this.ops = ops;
    }
    do() {
        this.ops.forEach(op => op.do());
    }
    undo() {
        this.ops.forEach(op => op.undo());
    }
}
class AddSplatOp {
    name;
    scene;
    splat;
    constructor(scene, splat) {
        this.scene = scene;
        this.splat = splat;
    }
    do() {
        this.scene.add(this.splat);
    }
    undo() {
        this.scene.remove(this.splat);
    }
    destroy() {
        this.splat.destroy();
    }
}
class SplatRenameOp {
    name = 'splatRename';
    splat;
    oldName;
    newName;
    constructor(splat, newName) {
        this.splat = splat;
        this.oldName = splat.name;
        this.newName = newName;
    }
    do() {
        this.splat.name = this.newName;
    }
    undo() {
        this.splat.name = this.oldName;
    }
}

// register for editor and scene events
const registerEditorEvents = (events, editHistory, scene) => {
    const vec = new Vec3();
    const vec2 = new Vec3();
    const vec4 = new Vec4();
    const mat = new Mat4();
    // get the list of selected splats (currently limited to just a single one)
    const selectedSplats = () => {
        const selected = events.invoke('selection');
        return selected?.visible ? [selected] : [];
    };
    let lastExportCursor = 0;
    // add unsaved changes warning message.
    window.addEventListener('beforeunload', (e) => {
        if (!events.invoke('scene.dirty')) {
            // if the undo cursor matches last export, then we have no unsaved changes
            return undefined;
        }
        const msg = 'You have unsaved changes. Are you sure you want to leave?';
        e.returnValue = msg;
        return msg;
    });
    events.function('targetSize', () => {
        return scene.targetSize;
    });
    events.on('scene.clear', () => {
        scene.clear();
        editHistory.clear();
        lastExportCursor = 0;
    });
    events.function('scene.dirty', () => {
        return editHistory.cursor !== lastExportCursor;
    });
    events.on('doc.saved', () => {
        lastExportCursor = editHistory.cursor;
    });
    events.on('camera.mode', () => {
        scene.forceRender = true;
    });
    events.on('camera.overlay', () => {
        scene.forceRender = true;
    });
    events.on('camera.splatSize', () => {
        scene.forceRender = true;
    });
    events.on('view.outlineSelection', () => {
        scene.forceRender = true;
    });
    events.on('view.bands', (bands) => {
        scene.forceRender = true;
    });
    events.on('camera.bound', () => {
        scene.forceRender = true;
    });
    events.on('selection.changed', () => {
        scene.forceRender = true;
    });
    events.on('tool.coordSpace', () => {
        scene.forceRender = true;
    });
    // grid.visible
    const setGridVisible = (visible) => {
        if (visible !== scene.grid.visible) {
            scene.grid.visible = visible;
            events.fire('grid.visible', visible);
        }
    };
    events.function('grid.visible', () => {
        return scene.grid.visible;
    });
    events.on('grid.setVisible', (visible) => {
        setGridVisible(visible);
    });
    events.on('grid.toggleVisible', () => {
        setGridVisible(!scene.grid.visible);
    });
    setGridVisible(scene.config.show.grid);
    // camera.fov
    const setCameraFov = (fov) => {
        if (fov !== scene.camera.fov) {
            scene.camera.fov = fov;
            events.fire('camera.fov', scene.camera.fov);
        }
    };
    events.function('camera.fov', () => {
        return scene.camera.fov;
    });
    events.on('camera.setFov', (fov) => {
        setCameraFov(fov);
    });
    // camera.tonemapping
    events.function('camera.tonemapping', () => {
        return scene.camera.tonemapping;
    });
    events.on('camera.setTonemapping', (value) => {
        scene.camera.tonemapping = value;
    });
    // camera.bound
    let bound = scene.config.show.bound;
    const setBoundVisible = (visible) => {
        if (visible !== bound) {
            bound = visible;
            events.fire('camera.bound', bound);
        }
    };
    events.function('camera.bound', () => {
        return bound;
    });
    events.on('camera.setBound', (value) => {
        setBoundVisible(value);
    });
    events.on('camera.toggleBound', () => {
        setBoundVisible(!events.invoke('camera.bound'));
    });
    // camera.focus
    events.on('camera.focus', () => {
        const splat = selectedSplats()[0];
        if (splat) {
            const bound = splat.numSelected > 0 ? splat.selectionBound : splat.localBound;
            vec.copy(bound.center);
            const worldTransform = splat.worldTransform;
            worldTransform.transformPoint(vec, vec);
            worldTransform.getScale(vec2);
            scene.camera.focus({
                focalPoint: vec,
                radius: bound.halfExtents.length() * vec2.x,
                speed: 1
            });
        }
    });
    events.on('camera.reset', () => {
        const { initialAzim, initialElev, initialZoom } = scene.config.controls;
        const x = Math.sin(initialAzim * Math.PI / 180) * Math.cos(initialElev * Math.PI / 180);
        const y = -Math.sin(initialElev * Math.PI / 180);
        const z = Math.cos(initialAzim * Math.PI / 180) * Math.cos(initialElev * Math.PI / 180);
        const zoom = initialZoom;
        scene.camera.setPose(new Vec3(x * zoom, y * zoom, z * zoom), new Vec3(0, 0, 0));
    });
    // handle camera align events
    events.on('camera.align', (axis) => {
        switch (axis) {
            case 'px':
                scene.camera.setAzimElev(90, 0);
                break;
            case 'py':
                scene.camera.setAzimElev(0, -90);
                break;
            case 'pz':
                scene.camera.setAzimElev(0, 0);
                break;
            case 'nx':
                scene.camera.setAzimElev(270, 0);
                break;
            case 'ny':
                scene.camera.setAzimElev(0, 90);
                break;
            case 'nz':
                scene.camera.setAzimElev(180, 0);
                break;
        }
        // switch to ortho mode
        scene.camera.ortho = true;
    });
    // returns true if the selected splat has selected gaussians
    events.function('selection.splats', () => {
        const splat = events.invoke('selection');
        return splat?.numSelected > 0;
    });
    events.on('select.all', () => {
        selectedSplats().forEach((splat) => {
            events.fire('edit.add', new SelectAllOp(splat));
        });
    });
    events.on('select.none', () => {
        selectedSplats().forEach((splat) => {
            events.fire('edit.add', new SelectNoneOp(splat));
        });
    });
    events.on('select.invert', () => {
        selectedSplats().forEach((splat) => {
            events.fire('edit.add', new SelectInvertOp(splat));
        });
    });
    events.on('select.pred', (op, pred) => {
        selectedSplats().forEach((splat) => {
            events.fire('edit.add', new SelectOp(splat, op, pred));
        });
    });
    const intersectCenters = (splat, op, options) => {
        const data = scene.dataProcessor.intersect(options, splat);
        const filter = (i) => data[i] === 255;
        events.fire('edit.add', new SelectOp(splat, op, filter));
    };
    events.on('select.bySphere', (op, sphere) => {
        selectedSplats().forEach((splat) => {
            intersectCenters(splat, op, {
                sphere: { x: sphere[0], y: sphere[1], z: sphere[2], radius: sphere[3] }
            });
        });
    });
    events.on('select.byBox', (op, box) => {
        selectedSplats().forEach((splat) => {
            intersectCenters(splat, op, {
                box: { x: box[0], y: box[1], z: box[2], lenx: box[3], leny: box[4], lenz: box[5] }
            });
        });
    });
    events.on('select.rect', (op, rect) => {
        const mode = events.invoke('camera.mode');
        selectedSplats().forEach((splat) => {
            if (mode === 'centers') {
                intersectCenters(splat, op, {
                    rect: { x1: rect.start.x, y1: rect.start.y, x2: rect.end.x, y2: rect.end.y }
                });
            }
            else if (mode === 'rings') {
                const { width, height } = scene.targetSize;
                scene.camera.pickPrep(splat, op);
                const pick = scene.camera.pickRect(Math.floor(rect.start.x * width), Math.floor(rect.start.y * height), Math.floor((rect.end.x - rect.start.x) * width), Math.floor((rect.end.y - rect.start.y) * height));
                const selected = new Set(pick);
                const filter = (i) => {
                    return selected.has(i);
                };
                events.fire('edit.add', new SelectOp(splat, op, filter));
            }
        });
    });
    let maskTexture = null;
    events.on('select.byMask', (op, canvas, context) => {
        const mode = events.invoke('camera.mode');
        selectedSplats().forEach((splat) => {
            if (mode === 'centers') {
                // create mask texture
                if (!maskTexture || maskTexture.width !== canvas.width || maskTexture.height !== canvas.height) {
                    if (maskTexture) {
                        maskTexture.destroy();
                    }
                    maskTexture = new Texture(scene.graphicsDevice);
                }
                maskTexture.setSource(canvas);
                intersectCenters(splat, op, {
                    mask: maskTexture
                });
            }
            else if (mode === 'rings') {
                const mask = context.getImageData(0, 0, canvas.width, canvas.height);
                // calculate mask bound so we limit pixel operations
                let mx0 = mask.width - 1;
                let my0 = mask.height - 1;
                let mx1 = 0;
                let my1 = 0;
                for (let y = 0; y < mask.height; ++y) {
                    for (let x = 0; x < mask.width; ++x) {
                        if (mask.data[(y * mask.width + x) * 4 + 3] === 255) {
                            mx0 = Math.min(mx0, x);
                            my0 = Math.min(my0, y);
                            mx1 = Math.max(mx1, x);
                            my1 = Math.max(my1, y);
                        }
                    }
                }
                const { width, height } = scene.targetSize;
                const px0 = Math.floor(mx0 / mask.width * width);
                const py0 = Math.floor(my0 / mask.height * height);
                const px1 = Math.floor(mx1 / mask.width * width);
                const py1 = Math.floor(my1 / mask.height * height);
                const pw = px1 - px0 + 1;
                const ph = py1 - py0 + 1;
                scene.camera.pickPrep(splat, op);
                const pick = scene.camera.pickRect(px0, py0, pw, ph);
                const selected = new Set();
                for (let y = 0; y < ph; ++y) {
                    for (let x = 0; x < pw; ++x) {
                        const mx = Math.floor((px0 + x) / width * mask.width);
                        const my = Math.floor((py0 + y) / height * mask.height);
                        if (mask.data[(my * mask.width + mx) * 4] === 255) {
                            selected.add(pick[(ph - y) * pw + x]);
                        }
                    }
                }
                const filter = (i) => {
                    return selected.has(i);
                };
                events.fire('edit.add', new SelectOp(splat, op, filter));
            }
        });
    });
    events.on('select.point', (op, point) => {
        const { width, height } = scene.targetSize;
        const mode = events.invoke('camera.mode');
        selectedSplats().forEach((splat) => {
            const splatData = splat.splatData;
            if (mode === 'centers') {
                const x = splatData.getProp('x');
                const y = splatData.getProp('y');
                const z = splatData.getProp('z');
                const splatSize = events.invoke('camera.splatSize');
                const camera = scene.camera.entity.camera;
                const sx = point.x * width;
                const sy = point.y * height;
                // calculate final matrix
                mat.mul2(camera.camera._viewProjMat, splat.worldTransform);
                const filter = (i) => {
                    vec4.set(x[i], y[i], z[i], 1.0);
                    mat.transformVec4(vec4, vec4);
                    const px = (vec4.x / vec4.w * 0.5 + 0.5) * width;
                    const py = (-vec4.y / vec4.w * 0.5 + 0.5) * height;
                    return Math.abs(px - sx) < splatSize && Math.abs(py - sy) < splatSize;
                };
                events.fire('edit.add', new SelectOp(splat, op, filter));
            }
            else if (mode === 'rings') {
                scene.camera.pickPrep(splat, op);
                const pickId = scene.camera.pickRect(Math.floor(point.x * width), Math.floor(point.y * height), 1, 1)[0];
                const filter = (i) => {
                    return i === pickId;
                };
                events.fire('edit.add', new SelectOp(splat, op, filter));
            }
        });
    });
    events.on('select.hide', () => {
        selectedSplats().forEach((splat) => {
            events.fire('edit.add', new HideSelectionOp(splat));
        });
    });
    events.on('select.unhide', () => {
        selectedSplats().forEach((splat) => {
            events.fire('edit.add', new UnhideAllOp(splat));
        });
    });
    events.on('select.delete', () => {
        selectedSplats().forEach((splat) => {
            editHistory.add(new DeleteSelectionOp(splat));
        });
    });
    const performSelectionFunc = async (func) => {
        const splats = selectedSplats();
        const writer = new BufferWriter();
        await serializePly(splats, {
            maxSHBands: 3,
            selected: true
        }, writer);
        const buffers = writer.close();
        if (buffers) {
            const splat = splats[0];
            // wrap PLY in a blob and load it
            const blob = new Blob(buffers, { type: 'application/octet-stream' });
            const url = URL.createObjectURL(blob);
            const { filename } = splat;
            const copy = await scene.assetLoader.loadPly({ url, filename });
            if (func === 'separate') {
                editHistory.add(new MultiOp([
                    new DeleteSelectionOp(splat),
                    new AddSplatOp(scene, copy)
                ]));
            }
            else {
                editHistory.add(new AddSplatOp(scene, copy));
            }
            URL.revokeObjectURL(url);
        }
    };
    // duplicate the current selection
    events.on('select.duplicate', async () => {
        await performSelectionFunc('duplicate');
    });
    events.on('select.separate', async () => {
        await performSelectionFunc('separate');
    });
    events.on('scene.reset', () => {
        selectedSplats().forEach((splat) => {
            editHistory.add(new ResetOp(splat));
        });
    });
    const setAllData = (value) => {
        if (value !== scene.assetLoader.loadAllData) {
            scene.assetLoader.loadAllData = value;
            events.fire('allData', scene.assetLoader.loadAllData);
        }
    };
    events.function('allData', () => {
        return scene.assetLoader.loadAllData;
    });
    events.on('toggleAllData', (value) => {
        setAllData(!events.invoke('allData'));
    });
    // camera mode
    let activeMode = 'centers';
    const setCameraMode = (mode) => {
        if (mode !== activeMode) {
            activeMode = mode;
            events.fire('camera.mode', activeMode);
        }
    };
    events.function('camera.mode', () => {
        return activeMode;
    });
    events.on('camera.setMode', (mode) => {
        setCameraMode(mode);
    });
    events.on('camera.toggleMode', () => {
        setCameraMode(events.invoke('camera.mode') === 'centers' ? 'rings' : 'centers');
    });
    // camera overlay
    let cameraOverlay = scene.config.camera.overlay;
    const setCameraOverlay = (enabled) => {
        if (enabled !== cameraOverlay) {
            cameraOverlay = enabled;
            events.fire('camera.overlay', cameraOverlay);
        }
    };
    events.function('camera.overlay', () => {
        return cameraOverlay;
    });
    events.on('camera.setOverlay', (value) => {
        setCameraOverlay(value);
    });
    events.on('camera.toggleOverlay', () => {
        setCameraOverlay(!events.invoke('camera.overlay'));
    });
    // splat size
    let splatSize = 2;
    const setSplatSize = (value) => {
        if (value !== splatSize) {
            splatSize = value;
            events.fire('camera.splatSize', splatSize);
        }
    };
    events.function('camera.splatSize', () => {
        return splatSize;
    });
    events.on('camera.setSplatSize', (value) => {
        setSplatSize(value);
    });
    // camera fly speed
    const setFlySpeed = (value) => {
        if (value !== scene.camera.flySpeed) {
            scene.camera.flySpeed = value;
            events.fire('camera.flySpeed', value);
        }
    };
    events.function('camera.flySpeed', () => {
        return scene.camera.flySpeed;
    });
    events.on('camera.setFlySpeed', (value) => {
        setFlySpeed(value);
    });
    // outline selection
    let outlineSelection = false;
    const setOutlineSelection = (value) => {
        if (value !== outlineSelection) {
            outlineSelection = value;
            events.fire('view.outlineSelection', outlineSelection);
        }
    };
    events.function('view.outlineSelection', () => {
        return outlineSelection;
    });
    events.on('view.setOutlineSelection', (value) => {
        setOutlineSelection(value);
    });
    // view spherical harmonic bands
    let viewBands = scene.config.show.shBands;
    const setViewBands = (value) => {
        if (value !== viewBands) {
            viewBands = value;
            events.fire('view.bands', viewBands);
        }
    };
    events.function('view.bands', () => {
        return viewBands;
    });
    events.on('view.setBands', (value) => {
        setViewBands(value);
    });
    events.function('camera.getPose', () => {
        const camera = scene.camera;
        const position = camera.entity.getPosition();
        const focalPoint = camera.focalPoint;
        return {
            position: { x: position.x, y: position.y, z: position.z },
            target: { x: focalPoint.x, y: focalPoint.y, z: focalPoint.z }
        };
    });
    events.on('camera.setPose', (pose, speed = 1) => {
        scene.camera.setPose(pose.position, pose.target, speed);
    });
    // hack: fire events to initialize UI
    events.fire('camera.fov', scene.camera.fov);
    events.fire('camera.overlay', cameraOverlay);
    events.fire('view.bands', viewBands);
    // doc serialization
    events.function('docSerialize.view', () => {
        const packC = (c) => [c.r, c.g, c.b, c.a];
        return {
            bgColor: packC(events.invoke('bgClr')),
            selectedColor: packC(events.invoke('selectedClr')),
            unselectedColor: packC(events.invoke('unselectedClr')),
            lockedColor: packC(events.invoke('lockedClr')),
            shBands: events.invoke('view.bands'),
            centersSize: events.invoke('camera.splatSize'),
            outlineSelection: events.invoke('view.outlineSelection'),
            showGrid: events.invoke('grid.visible'),
            showBound: events.invoke('camera.bound'),
            flySpeed: events.invoke('camera.flySpeed')
        };
    });
    events.function('docDeserialize.view', (docView) => {
        events.fire('setBgClr', new Color(docView.bgColor));
        events.fire('setSelectedClr', new Color(docView.selectedColor));
        events.fire('setUnselectedClr', new Color(docView.unselectedColor));
        events.fire('setLockedClr', new Color(docView.lockedColor));
        events.fire('view.setBands', docView.shBands);
        events.fire('camera.setSplatSize', docView.centersSize);
        events.fire('view.setOutlineSelection', docView.outlineSelection);
        events.fire('grid.setVisible', docView.showGrid);
        events.fire('camera.setBound', docView.showBound);
        events.fire('camera.setFlySpeed', docView.flySpeed);
    });
};

class Events extends EventHandler {
    functions = new Map();
    // declare an editor function
    function(name, fn) {
        if (this.functions.has(name)) {
            throw new Error(`error: function ${name} already exists`);
        }
        this.functions.set(name, fn);
    }
    // invoke an editor function
    invoke(name, ...args) {
        const fn = this.functions.get(name);
        if (!fn) {
            console.log(`error: function not found '${name}'`);
            return;
        }
        return fn(...args);
    }
}

class DroppedFile {
    filename;
    file;
    constructor(filename, file) {
        this.filename = filename;
        this.file = file;
    }
}
const resolveDirectories = (entries) => {
    const promises = [];
    const result = [];
    entries.forEach((entry) => {
        if (entry.isFile) {
            result.push(entry);
        }
        else if (entry.isDirectory) {
            promises.push(new Promise((resolve, reject) => {
                const reader = entry.createReader();
                const p = [];
                const read = () => {
                    reader.readEntries((children) => {
                        if (children.length > 0) {
                            p.push(resolveDirectories(children));
                            read();
                        }
                        else {
                            Promise.all(p).then((children) => {
                                resolve(children.flat());
                            });
                        }
                    });
                };
                read();
            }));
        }
    });
    return Promise.all(promises).then((children) => {
        return result.concat(...children);
    });
};
const removeCommonPrefix = (urls) => {
    const split = (pathname) => {
        const parts = pathname.split(path$1.delimiter);
        const base = parts[0];
        const rest = parts.slice(1).join(path$1.delimiter);
        return [base, rest];
    };
    while (true) {
        const parts = split(urls[0].filename);
        if (parts[1].length === 0) {
            return;
        }
        for (let i = 1; i < urls.length; ++i) {
            const other = split(urls[i].filename);
            if (parts[0] !== other[0]) {
                return;
            }
        }
        for (let i = 0; i < urls.length; ++i) {
            urls[i].filename = split(urls[i].filename)[1];
        }
    }
};
// configure drag and drop
const CreateDropHandler = (target, dropHandler) => {
    const dragstart = (ev) => {
        ev.preventDefault();
        ev.stopPropagation();
        ev.dataTransfer.effectAllowed = 'all';
    };
    const dragover = (ev) => {
        ev.preventDefault();
        ev.stopPropagation();
        ev.dataTransfer.effectAllowed = 'all';
    };
    const drop = async (ev) => {
        ev.preventDefault();
        const items = Array.from(ev.dataTransfer.items)
            .map(item => item.webkitGetAsEntry())
            .filter(v => v);
        // resolve directories to files
        const entries = await resolveDirectories(items);
        const files = await Promise.all(entries.map((entry) => {
            return new Promise((resolve, reject) => {
                entry.file((entryFile) => {
                    resolve(new DroppedFile(entry.fullPath.substring(1), entryFile));
                });
            });
        }));
        if (files.length > 1) {
            // if all files share a common filename prefix, remove it
            removeCommonPrefix(files);
        }
        // finally, call the drop handler
        dropHandler(files, ev.shiftKey);
    };
    target.addEventListener('dragstart', dragstart, true);
    target.addEventListener('dragover', dragover, true);
    target.addEventListener('drop', drop, true);
};

var ElementType;
(function (ElementType) {
    ElementType["camera"] = "camera";
    ElementType["model"] = "model";
    ElementType["splat"] = "splat";
    ElementType["shadow"] = "shadow";
    ElementType["debug"] = "debug";
    ElementType["other"] = "other";
})(ElementType || (ElementType = {}));
const ElementTypeList = [
    ElementType.camera,
    ElementType.model,
    ElementType.splat,
    ElementType.shadow,
    ElementType.debug,
    ElementType.other
];
let nextUid = 1;
class Element {
    type;
    scene = null;
    uid;
    constructor(type) {
        this.type = type;
        this.uid = nextUid++;
    }
    destroy() {
        if (this.scene) {
            this.scene.remove(this);
        }
    }
    add() { }
    remove() { }
    serialize(serializer) { }
    onUpdate(deltaTime) { }
    onPostUpdate() { }
    onPreRender() { }
    onPostRender() { }
    onAdded(element) { }
    onRemoved(element) { }
    move(position, rotation, scale) { }
    get worldBound() {
        return null;
    }
}

const filePickerTypes = {
    'ply': {
        description: 'Gaussian Splat PLY File',
        accept: {
            'application/ply': ['.ply']
        }
    },
    'sog': {
        description: 'SOG Scene',
        accept: {
            'application/x-gaussian-splat': ['.json', '.sog'],
            'image/webp': ['.webp']
        }
    },
    'splat': {
        description: 'Splat File',
        accept: {
            'application/x-gaussian-splat': ['.splat']
        }
    },
    'htmlViewer': {
        description: 'Viewer HTML',
        accept: {
            'text/html': ['.html']
        }
    },
    'packageViewer': {
        description: 'Viewer ZIP',
        accept: {
            'application/zip': ['.zip']
        }
    }
};
// determine if all files share a common filename prefix followed by
// a frame number, e.g. "frame0001.ply", "frame0002.ply", etc.
const isPlySequence = (filenames) => {
    if (filenames.length < 2) {
        return false;
    }
    // eslint-disable-next-line regexp/no-super-linear-backtracking
    const regex = /(.*?)(\d+)(?:\.compressed)?\.ply$/;
    const baseMatch = filenames[0].match(regex);
    if (!baseMatch) {
        return false;
    }
    for (let i = 1; i < filenames.length; i++) {
        const thisMatch = filenames[i].match(regex);
        if (!thisMatch || thisMatch[1] !== baseMatch[1]) {
            return false;
        }
    }
    return true;
};
// sog comprises a single meta.json file and zero or more .webp files
const isSog = (filenames) => {
    const count = (extension) => filenames.reduce((sum, f) => sum + (f.endsWith(extension) ? 1 : 0), 0);
    return count('meta.json') === 1;
};
const vec$2 = new Vec3();
const loadCameraPoses = async (file, events) => {
    const response = new Response(file.contents);
    const json = await response.json();
    if (json.length > 0) {
        // calculate the average position of the camera poses
        const ave = new Vec3(0, 0, 0);
        json.forEach((pose) => {
            vec$2.set(pose.position[0], pose.position[1], pose.position[2]);
            ave.add(vec$2);
        });
        ave.mulScalar(1 / json.length);
        // sort entries by trailing number if it exists
        const sorter = (a, b) => {
            const avalue = a.id ?? a.img_name?.match(/\d*$/)?.[0];
            const bvalue = b.id ?? b.img_name?.match(/\d*$/)?.[0];
            return (avalue && bvalue) ? parseInt(avalue, 10) - parseInt(bvalue, 10) : 0;
        };
        json.sort(sorter).forEach((pose, i) => {
            if (pose.hasOwnProperty('position') && pose.hasOwnProperty('rotation')) {
                const p = new Vec3(pose.position);
                const z = new Vec3(pose.rotation[0][2], pose.rotation[1][2], pose.rotation[2][2]);
                const dot = vec$2.sub2(ave, p).dot(z);
                vec$2.copy(z).mulScalar(dot).add(p);
                events.fire('camera.addPose', {
                    name: pose.img_name ?? `${file.filename}_${i}`,
                    frame: i,
                    position: new Vec3(-p.x, -p.y, p.z),
                    target: new Vec3(-vec$2.x, -vec$2.y, vec$2.z)
                });
            }
        });
    }
};
// initialize file handler events
const initFileHandler = (scene, events, dropTarget) => {
    const showLoadError = async (message, filename) => {
        await events.invoke('showPopup', {
            type: 'error',
            header: localize('popup.error-loading'),
            message: `${message} while loading '${filename}'`
        });
    };
    // import a single file, .ply, .splat or meta.json
    const importFile = async (file, animationFrame) => {
        try {
            const model = await scene.assetLoader.loadModel({
                contents: file.contents,
                filename: file.filename,
                url: file.url,
                animationFrame
            });
            scene.add(model);
            return model;
        }
        catch (error) {
            await showLoadError(error.message ?? error, file.filename);
        }
    };
    const importCameraPoses = async (file) => {
        await loadCameraPoses(file, events);
    };
    const importSog = async (files, animationFrame) => {
        const meta = files.findIndex(f => f.filename.toLowerCase() === 'meta.json');
        const urls = files.map(file => (file.contents && URL.createObjectURL(file.contents)) ?? file.url ?? file.filename);
        const mapUrl = (name) => {
            const lowerName = name.toLowerCase();
            if (lowerName.endsWith('.webp')) {
                const idx = files.findIndex(f => f.filename.toLowerCase() === lowerName);
                if (idx >= 0) {
                    return urls[idx] ?? files[idx].url ?? files[idx].filename;
                }
            }
            return name;
        };
        const model = await scene.assetLoader.loadModel({
            filename: files[meta].filename,
            url: urls[meta],
            animationFrame,
            mapUrl: files.length > 1 ? mapUrl : null
        });
        urls.forEach(url => URL.revokeObjectURL(url));
        scene.add(model);
        return model;
    };
    // figure out what the set of files are (ply sequence, document, sog set, ply) and then import them
    const importFiles = async (files, animationFrame = false) => {
        const filenames = files.map(f => f.filename.toLocaleLowerCase());
        const result = [];
        if (isPlySequence(filenames)) {
            // handle ply sequence
            events.fire('plysequence.setFrames', files.map(f => f.contents));
            events.fire('timeline.frame', 0);
        }
        else if (isSog(filenames)) {
            // import sog files
            result.push(await importSog(files, animationFrame));
        }
        else {
            // check for unrecognized file types
            for (let i = 0; i < filenames.length; i++) {
                const filename = filenames[i];
                if (!filename.endsWith('.ssproj') && !filename.endsWith('.json') && !filename.endsWith('.ply') && !filename.endsWith('.splat') && !filename.endsWith('.sog') && !filename.endsWith('.webp')) {
                    await showLoadError('Unrecognized file type', filename);
                    return;
                }
            }
            // handle multiple files as independent imports
            for (let i = 0; i < files.length; i++) {
                if (filenames[i].endsWith('.ssproj')) {
                    await events.invoke('doc.load', files[i].contents ?? (await fetch(files[i].url)).arrayBuffer());
                }
                else if (filenames[i].endsWith('.json')) {
                    await importCameraPoses(files[i]);
                }
                else if (filenames[i].endsWith('.ply') || filenames[i].endsWith('.splat') || filenames[i].endsWith('.sog')) {
                    result.push(await importFile(files[i], animationFrame));
                }
            }
        }
        return result;
    };
    events.function('import', (files, animationFrame = false) => {
        return importFiles(files, animationFrame);
    });
    // create a file selector element as fallback when showOpenFilePicker isn't available
    let fileSelector;
    if (!window.showOpenFilePicker) {
        fileSelector = document.createElement('input');
        fileSelector.setAttribute('id', 'file-selector');
        fileSelector.setAttribute('type', 'file');
        fileSelector.setAttribute('accept', '.ply,.splat,meta.json,.json,.webp,.ssproj,.sog');
        fileSelector.setAttribute('multiple', 'true');
        fileSelector.onchange = () => {
            const files = [];
            for (let i = 0; i < fileSelector.files.length; i++) {
                const file = fileSelector.files[i];
                files.push({
                    filename: file.name,
                    contents: file
                });
            }
            importFiles(files);
            fileSelector.value = '';
        };
        document.body.append(fileSelector);
    }
    // create the file drag & drop handler
    CreateDropHandler(dropTarget, (entries, shift) => {
        importFiles(entries.map((e) => {
            return {
                filename: e.filename,
                contents: e.file
            };
        }));
    });
    // get the list of visible splats containing gaussians
    const getSplats = () => {
        return scene.getElementsByType(ElementType.splat)
            .filter(splat => splat.visible)
            .filter(splat => splat.numSplats > 0);
    };
    events.function('scene.allSplats', () => {
        return scene.getElementsByType(ElementType.splat);
    });
    events.function('scene.splats', () => {
        return getSplats();
    });
    events.function('scene.empty', () => {
        return getSplats().length === 0;
    });
    events.function('scene.import', async () => {
        if (fileSelector) {
            fileSelector.click();
        }
        else {
            try {
                const handles = await window.showOpenFilePicker({
                    id: 'SuperSplatFileImport',
                    multiple: true,
                    types: [
                        filePickerTypes.ply,
                        filePickerTypes.splat,
                        filePickerTypes.sog
                    ]
                });
                const files = [];
                for (let i = 0; i < handles.length; i++) {
                    files.push({
                        filename: handles[i].name,
                        contents: await handles[i].getFile()
                    });
                }
                importFiles(files);
            }
            catch (error) {
                if (error.name !== 'AbortError') {
                    console.error(error);
                }
            }
        }
    });
    // open a folder
    events.function('scene.openAnimation', async () => {
        try {
            const handle = await window.showDirectoryPicker({
                id: 'SuperSplatFileOpenAnimation',
                mode: 'readwrite'
            });
            if (handle) {
                const files = [];
                for await (const value of handle.values()) {
                    if (value.kind === 'file') {
                        const file = await value.getFile();
                        if (file.name.toLowerCase().endsWith('.ply')) {
                            files.push(file);
                        }
                    }
                }
                events.fire('plysequence.setFrames', files);
                events.fire('timeline.frame', 0);
            }
        }
        catch (error) {
            if (error.name !== 'AbortError') {
                console.error(error);
            }
        }
    });
    events.function('scene.export', async (exportType) => {
        const splats = getSplats();
        const hasFilePicker = !!window.showSaveFilePicker;
        // show viewer export options
        const options = await events.invoke('show.exportPopup', exportType, splats.map(s => s.name), !hasFilePicker);
        // return if user cancelled
        if (!options) {
            return;
        }
        const fileType = (exportType === 'viewer') ? (options.viewerExportSettings.type === 'zip' ? 'packageViewer' : 'htmlViewer') :
            (exportType === 'ply') ? (options.compressedPly ? 'compressedPly' : 'ply') : 'splat';
        if (hasFilePicker) {
            try {
                const fileHandle = await window.showSaveFilePicker({
                    id: 'SuperSplatFileExport',
                    types: [filePickerTypes[fileType]],
                    suggestedName: options.filename
                });
                await events.invoke('scene.write', fileType, options, await fileHandle.createWritable());
            }
            catch (error) {
                if (error.name !== 'AbortError') {
                    console.error(error);
                }
            }
        }
        else {
            await events.invoke('scene.write', fileType, options);
        }
    });
    events.function('scene.write', async (fileType, options, stream) => {
        events.fire('startSpinner');
        try {
            // setTimeout so spinner has a chance to activate
            await new Promise((resolve) => {
                setTimeout(resolve);
            });
            const { filename, splatIdx, serializeSettings, viewerExportSettings } = options;
            const writer = stream ? new FileStreamWriter(stream) : new DownloadWriter(filename);
            try {
                const splats = splatIdx === 'all' ? getSplats() : [getSplats()[splatIdx]];
                switch (fileType) {
                    case 'ply':
                        await serializePly(splats, serializeSettings, writer);
                        break;
                    case 'compressedPly':
                        serializeSettings.minOpacity = 1 / 255;
                        serializeSettings.removeInvalid = true;
                        await serializePlyCompressed(splats, serializeSettings, writer);
                        break;
                    case 'splat':
                        await serializeSplat(splats, serializeSettings, writer);
                        break;
                    case 'htmlViewer':
                    case 'packageViewer':
                        await serializeViewer(splats, serializeSettings, viewerExportSettings, writer);
                        break;
                }
            }
            finally {
                await writer.close();
            }
        }
        catch (error) {
            await events.invoke('showPopup', {
                type: 'error',
                header: localize('popup.error-loading'),
                message: `${error.message ?? error} while saving file`
            });
        }
        finally {
            events.fire('stopSpinner');
        }
    });
};

const registerPlySequenceEvents = (events) => {
    let sequenceFiles = [];
    let sequenceSplat = null;
    let sequenceFrame = -1;
    let sequenceLoading = false;
    let nextFrame = -1;
    const setFrames = (files) => {
        // eslint-disable-next-line regexp/no-super-linear-backtracking
        const regex = /(.*?)(\d+)(?:\.compressed)?\.ply$/;
        // sort frames by trailing number, if it exists
        const sorter = (a, b) => {
            const avalue = a.name?.toLowerCase().match(regex)?.[2];
            const bvalue = b.name?.toLowerCase().match(regex)?.[2];
            return (avalue && bvalue) ? parseInt(avalue, 10) - parseInt(bvalue, 10) : 0;
        };
        sequenceFiles = files.slice();
        sequenceFiles.sort(sorter);
        events.fire('timeline.frames', sequenceFiles.length);
    };
    // resolves on first render frame
    const firstRender = (splat) => {
        return new Promise((resolve) => {
            splat.entity.gsplat.instance.sorter.on('updated', (count) => {
                resolve();
            });
        });
    };
    const setFrame = async (frame) => {
        if (frame < 0 || frame >= sequenceFiles.length) {
            return;
        }
        if (sequenceLoading) {
            nextFrame = frame;
            return;
        }
        if (frame === sequenceFrame) {
            return;
        }
        // if user changed the scene, confirm
        if (events.invoke('scene.dirty')) {
            const result = await events.invoke('showPopup', {
                type: 'yesno',
                header: 'RESET SCENE',
                message: 'You have unsaved changes. Are you sure you want to reset the scene?'
            });
            if (result.action !== 'yes') {
                return;
            }
            events.fire('scene.clear');
            sequenceSplat = null;
        }
        sequenceLoading = true;
        const file = sequenceFiles[frame];
        const newSplat = await events.invoke('import', [{
                filename: file.name,
                contents: file
            }], true);
        // wait for first frame render
        await firstRender(newSplat[0]);
        // destroy the previous frame
        if (sequenceSplat) {
            sequenceSplat.destroy();
        }
        sequenceFrame = frame;
        sequenceSplat = newSplat[0];
        sequenceLoading = false;
        // initiate the next frame load
        if (nextFrame !== -1) {
            const frame = nextFrame;
            nextFrame = -1;
            setFrame(frame);
        }
    };
    events.on('plysequence.setFrames', (files) => {
        setFrames(files);
    });
    events.on('timeline.frame', async (frame) => {
        await setFrame(frame);
    });
};

const origin = location.origin;
// check whether user is logged in
const fetchUser = async () => {
    try {
        const urlResponse = await fetch(`${origin}/api/id`);
        return urlResponse.ok && await urlResponse.json();
    }
    catch (e) {
        return null;
    }
};
const fetchSceneList = async (user) => {
    const response = await fetch(`${user.apiServer}/splats?limit=128`, {
        method: 'GET',
        headers: {
            'Authorization': `Bearer ${user.token}`
        }
    });
    if (!response.ok) {
        throw new Error(`failed to get scene list (${response.statusText})`);
    }
    return (await response.json()).result;
};
class PublishWriter {
    write;
    close;
    static async create(publishSettings) {
        const { user } = publishSettings;
        const filename = 'scene.ply';
        // start upload
        const startResponse = await fetch(`${user.apiServer}/upload/start-upload`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${user.token}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ fileName: filename })
        });
        const startJson = await startResponse.json();
        const result = new PublishWriter();
        const uploadBuf = new Uint8Array(10 * 1024 * 1024); // 10MB buffer
        const parts = [];
        let partNumber = 1;
        let cursor = 0;
        const upload = async () => {
            if (cursor === 0)
                return;
            // get signed url for this part
            const urlResponse = await fetch(`${user.apiServer}/upload/signed-urls`, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${user.token}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    uploadId: startJson.uploadId,
                    key: startJson.key,
                    parts: 1,
                    partBase: partNumber
                })
            });
            if (!urlResponse.ok) {
                throw new Error(`failed to get signed url (${urlResponse.statusText})`);
            }
            const urlJson = await urlResponse.json();
            const uploadResponse = await fetch(urlJson.signedUrls[0], {
                method: 'PUT',
                body: uploadBuf.slice(0, cursor),
                headers: {
                    'Content-Type': 'application/octet-stream'
                }
            });
            if (!uploadResponse.ok) {
                throw new Error(`failed to upload data (${uploadResponse.statusText})`);
            }
            parts.push({
                PartNumber: partNumber,
                ETag: uploadResponse.headers.get('etag').replace(/^"|"$/g, '')
            });
            cursor = 0;
            partNumber++;
        };
        result.write = async (data) => {
            let readcursor = 0;
            while (data.byteLength - readcursor > 0) {
                const readSize = data.byteLength - readcursor;
                const writeSize = uploadBuf.byteLength - cursor;
                const copySize = Math.min(readSize, writeSize);
                uploadBuf.set(data.subarray(readcursor, readcursor + copySize), cursor);
                readcursor += copySize;
                cursor += copySize;
                if (cursor === uploadBuf.byteLength) {
                    await upload();
                }
            }
        };
        result.close = async () => {
            // final upload
            await upload();
            // complete the multipart upload
            const completeResult = await fetch(`${user.apiServer}/upload/complete-upload`, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${user.token}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    uploadId: startJson.uploadId,
                    key: startJson.key,
                    parts
                })
            });
            if (!completeResult.ok) {
                throw new Error(`failed to complete upload (${completeResult.statusText})`);
            }
            await completeResult.json();
            const doPublish = () => fetch(`${user.apiServer}/splats/publish`, {
                method: 'POST',
                body: JSON.stringify({
                    s3Key: startJson.key,
                    title: publishSettings.title,
                    description: publishSettings.description,
                    listed: publishSettings.listed,
                    settings: publishSettings.experienceSettings,
                    format: publishSettings.format
                }),
                headers: {
                    'Authorization': `Bearer ${user.token}`,
                    'Content-Type': 'application/json'
                }
            });
            const doRepublish = () => fetch(`${user.apiServer}/splats/${publishSettings.overwriteId}/republish`, {
                method: 'PUT',
                body: JSON.stringify({
                    s3Key: startJson.key,
                    settings: publishSettings.experienceSettings,
                    format: publishSettings.format
                }),
                headers: {
                    'Authorization': `Bearer ${user.token}`,
                    'Content-Type': 'application/json'
                }
            });
            const publishResponse = await (publishSettings.overwriteId ? doRepublish() : doPublish());
            if (!publishResponse.ok) {
                let msg;
                try {
                    const err = await publishResponse.json();
                    msg = err.error ?? msg;
                }
                catch (e) {
                    msg = 'Failed to publish';
                }
                throw new Error(msg);
            }
            return await publishResponse.json();
        };
        return result;
    }
}
const registerPublishEvents = (events) => {
    events.function('publish.userStatus', async () => {
        const user = await fetchUser();
        if (!user) {
            return null;
        }
        const scenes = await fetchSceneList(user);
        return { user, scenes };
    });
    events.function('scene.publish', async (publishSettings) => {
        try {
            events.fire('progressStart', 'Publishing...');
            events.fire('progressUpdate', {
                text: localize('publish.converting'),
                progress: 0
            });
            // delay to allow spinner to show (hopefully 10ms is enough)
            await new Promise((resolve) => {
                setTimeout(resolve, 10);
            });
            const progressFunc = (loaded, total) => {
                events.fire('progressUpdate', {
                    text: localize('publish.uploading'),
                    progress: 100 * loaded / total
                });
            };
            // create the writer chain: gzip->stream->upload
            const publishWriter = await PublishWriter.create(publishSettings);
            const gzipWriter = new GZipWriter(publishWriter);
            const splats = events.invoke('scene.splats');
            // serialize
            switch (publishSettings.format) {
                case 'compressed.ply':
                    await serializePlyCompressed(splats, publishSettings.serializeSettings, gzipWriter, progressFunc);
                    break;
                case 'sog':
                    await serializePly(splats, publishSettings.serializeSettings, gzipWriter, progressFunc);
                    break;
            }
            await gzipWriter.close();
            const response = await publishWriter.close();
            if (!response) {
                await events.invoke('showPopup', {
                    type: 'error',
                    header: localize('publish.failed'),
                    message: localize('publish.please-try-again')
                });
            }
            else {
                await events.invoke('showPopup', {
                    type: 'info',
                    header: localize('publish.succeeded'),
                    message: localize('publish.message'),
                    link: response.url
                });
            }
        }
        catch (error) {
            await events.invoke('showPopup', {
                type: 'error',
                header: localize('publish.failed'),
                message: `'${error.message ?? error}'`
            });
        }
        finally {
            events.fire('progressEnd');
        }
    });
};

var __accessCheck = (obj, member, msg) => {
  if (!member.has(obj))
    throw TypeError("Cannot " + msg);
};
var __privateGet = (obj, member, getter) => {
  __accessCheck(obj, member, "read from private field");
  return member.get(obj);
};
var __privateAdd = (obj, member, value) => {
  if (member.has(obj))
    throw TypeError("Cannot add the same private member more than once");
  member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
};
var __privateSet = (obj, member, value, setter) => {
  __accessCheck(obj, member, "write to private field");
  member.set(obj, value);
  return value;
};
var __privateWrapper = (obj, member, setter, getter) => ({
  set _(value) {
    __privateSet(obj, member, value);
  },
  get _() {
    return __privateGet(obj, member);
  }
});
var __privateMethod = (obj, member, method) => {
  __accessCheck(obj, member, "access private method");
  return method;
};

// src/misc.ts
var bytes = new Uint8Array(8);
var view = new DataView(bytes.buffer);
var u8 = (value) => {
  return [(value % 256 + 256) % 256];
};
var u16 = (value) => {
  view.setUint16(0, value, false);
  return [bytes[0], bytes[1]];
};
var i16 = (value) => {
  view.setInt16(0, value, false);
  return [bytes[0], bytes[1]];
};
var u24 = (value) => {
  view.setUint32(0, value, false);
  return [bytes[1], bytes[2], bytes[3]];
};
var u32 = (value) => {
  view.setUint32(0, value, false);
  return [bytes[0], bytes[1], bytes[2], bytes[3]];
};
var i32 = (value) => {
  view.setInt32(0, value, false);
  return [bytes[0], bytes[1], bytes[2], bytes[3]];
};
var u64 = (value) => {
  view.setUint32(0, Math.floor(value / 2 ** 32), false);
  view.setUint32(4, value, false);
  return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
};
var fixed_8_8 = (value) => {
  view.setInt16(0, 2 ** 8 * value, false);
  return [bytes[0], bytes[1]];
};
var fixed_16_16 = (value) => {
  view.setInt32(0, 2 ** 16 * value, false);
  return [bytes[0], bytes[1], bytes[2], bytes[3]];
};
var fixed_2_30 = (value) => {
  view.setInt32(0, 2 ** 30 * value, false);
  return [bytes[0], bytes[1], bytes[2], bytes[3]];
};
var ascii = (text, nullTerminated = false) => {
  let bytes2 = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i));
  if (nullTerminated)
    bytes2.push(0);
  return bytes2;
};
var last = (arr) => {
  return arr && arr[arr.length - 1];
};
var lastPresentedSample = (samples) => {
  let result = void 0;
  for (let sample of samples) {
    if (!result || sample.presentationTimestamp > result.presentationTimestamp) {
      result = sample;
    }
  }
  return result;
};
var intoTimescale = (timeInSeconds, timescale, round = true) => {
  let value = timeInSeconds * timescale;
  return round ? Math.round(value) : value;
};
var rotationMatrix = (rotationInDegrees) => {
  let theta = rotationInDegrees * (Math.PI / 180);
  let cosTheta = Math.cos(theta);
  let sinTheta = Math.sin(theta);
  return [
    cosTheta,
    sinTheta,
    0,
    -sinTheta,
    cosTheta,
    0,
    0,
    0,
    1
  ];
};
var IDENTITY_MATRIX = rotationMatrix(0);
var matrixToBytes = (matrix) => {
  return [
    fixed_16_16(matrix[0]),
    fixed_16_16(matrix[1]),
    fixed_2_30(matrix[2]),
    fixed_16_16(matrix[3]),
    fixed_16_16(matrix[4]),
    fixed_2_30(matrix[5]),
    fixed_16_16(matrix[6]),
    fixed_16_16(matrix[7]),
    fixed_2_30(matrix[8])
  ];
};
var deepClone = (x) => {
  if (!x)
    return x;
  if (typeof x !== "object")
    return x;
  if (Array.isArray(x))
    return x.map(deepClone);
  return Object.fromEntries(Object.entries(x).map(([key, value]) => [key, deepClone(value)]));
};
var isU32 = (value) => {
  return value >= 0 && value < 2 ** 32;
};

// src/box.ts
var box = (type, contents, children) => ({
  type,
  contents: contents && new Uint8Array(contents.flat(10)),
  children
});
var fullBox = (type, version, flags, contents, children) => box(
  type,
  [u8(version), u24(flags), contents ?? []],
  children
);
var ftyp = (details) => {
  let minorVersion = 512;
  if (details.fragmented)
    return box("ftyp", [
      ascii("iso5"),
      // Major brand
      u32(minorVersion),
      // Minor version
      // Compatible brands
      ascii("iso5"),
      ascii("iso6"),
      ascii("mp41")
    ]);
  return box("ftyp", [
    ascii("isom"),
    // Major brand
    u32(minorVersion),
    // Minor version
    // Compatible brands
    ascii("isom"),
    details.holdsAvc ? ascii("avc1") : [],
    ascii("mp41")
  ]);
};
var mdat = (reserveLargeSize) => ({ type: "mdat", largeSize: reserveLargeSize });
var free = (size) => ({ type: "free", size });
var moov = (tracks, creationTime, fragmented = false) => box("moov", null, [
  mvhd(creationTime, tracks),
  ...tracks.map((x) => trak(x, creationTime)),
  fragmented ? mvex(tracks) : null
]);
var mvhd = (creationTime, tracks) => {
  let duration = intoTimescale(Math.max(
    0,
    ...tracks.filter((x) => x.samples.length > 0).map((x) => {
      const lastSample = lastPresentedSample(x.samples);
      return lastSample.presentationTimestamp + lastSample.duration;
    })
  ), GLOBAL_TIMESCALE);
  let nextTrackId = Math.max(...tracks.map((x) => x.id)) + 1;
  let needsU64 = !isU32(creationTime) || !isU32(duration);
  let u32OrU64 = needsU64 ? u64 : u32;
  return fullBox("mvhd", +needsU64, 0, [
    u32OrU64(creationTime),
    // Creation time
    u32OrU64(creationTime),
    // Modification time
    u32(GLOBAL_TIMESCALE),
    // Timescale
    u32OrU64(duration),
    // Duration
    fixed_16_16(1),
    // Preferred rate
    fixed_8_8(1),
    // Preferred volume
    Array(10).fill(0),
    // Reserved
    matrixToBytes(IDENTITY_MATRIX),
    // Matrix
    Array(24).fill(0),
    // Pre-defined
    u32(nextTrackId)
    // Next track ID
  ]);
};
var trak = (track, creationTime) => box("trak", null, [
  tkhd(track, creationTime),
  mdia(track, creationTime)
]);
var tkhd = (track, creationTime) => {
  let lastSample = lastPresentedSample(track.samples);
  let durationInGlobalTimescale = intoTimescale(
    lastSample ? lastSample.presentationTimestamp + lastSample.duration : 0,
    GLOBAL_TIMESCALE
  );
  let needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale);
  let u32OrU64 = needsU64 ? u64 : u32;
  let matrix;
  if (track.info.type === "video") {
    matrix = typeof track.info.rotation === "number" ? rotationMatrix(track.info.rotation) : track.info.rotation;
  } else {
    matrix = IDENTITY_MATRIX;
  }
  return fullBox("tkhd", +needsU64, 3, [
    u32OrU64(creationTime),
    // Creation time
    u32OrU64(creationTime),
    // Modification time
    u32(track.id),
    // Track ID
    u32(0),
    // Reserved
    u32OrU64(durationInGlobalTimescale),
    // Duration
    Array(8).fill(0),
    // Reserved
    u16(0),
    // Layer
    u16(0),
    // Alternate group
    fixed_8_8(track.info.type === "audio" ? 1 : 0),
    // Volume
    u16(0),
    // Reserved
    matrixToBytes(matrix),
    // Matrix
    fixed_16_16(track.info.type === "video" ? track.info.width : 0),
    // Track width
    fixed_16_16(track.info.type === "video" ? track.info.height : 0)
    // Track height
  ]);
};
var mdia = (track, creationTime) => box("mdia", null, [
  mdhd(track, creationTime),
  hdlr(track.info.type === "video" ? "vide" : "soun"),
  minf(track)
]);
var mdhd = (track, creationTime) => {
  let lastSample = lastPresentedSample(track.samples);
  let localDuration = intoTimescale(
    lastSample ? lastSample.presentationTimestamp + lastSample.duration : 0,
    track.timescale
  );
  let needsU64 = !isU32(creationTime) || !isU32(localDuration);
  let u32OrU64 = needsU64 ? u64 : u32;
  return fullBox("mdhd", +needsU64, 0, [
    u32OrU64(creationTime),
    // Creation time
    u32OrU64(creationTime),
    // Modification time
    u32(track.timescale),
    // Timescale
    u32OrU64(localDuration),
    // Duration
    u16(21956),
    // Language ("und", undetermined)
    u16(0)
    // Quality
  ]);
};
var hdlr = (componentSubtype) => fullBox("hdlr", 0, 0, [
  ascii("mhlr"),
  // Component type
  ascii(componentSubtype),
  // Component subtype
  u32(0),
  // Component manufacturer
  u32(0),
  // Component flags
  u32(0),
  // Component flags mask
  ascii("mp4-muxer-hdlr", true)
  // Component name
]);
var minf = (track) => box("minf", null, [
  track.info.type === "video" ? vmhd() : smhd(),
  dinf(),
  stbl(track)
]);
var vmhd = () => fullBox("vmhd", 0, 1, [
  u16(0),
  // Graphics mode
  u16(0),
  // Opcolor R
  u16(0),
  // Opcolor G
  u16(0)
  // Opcolor B
]);
var smhd = () => fullBox("smhd", 0, 0, [
  u16(0),
  // Balance
  u16(0)
  // Reserved
]);
var dinf = () => box("dinf", null, [
  dref()
]);
var dref = () => fullBox("dref", 0, 0, [
  u32(1)
  // Entry count
], [
  url()
]);
var url = () => fullBox("url ", 0, 1);
var stbl = (track) => {
  const needsCtts = track.compositionTimeOffsetTable.length > 1 || track.compositionTimeOffsetTable.some((x) => x.sampleCompositionTimeOffset !== 0);
  return box("stbl", null, [
    stsd(track),
    stts(track),
    stss(track),
    stsc(track),
    stsz(track),
    stco(track),
    needsCtts ? ctts(track) : null
  ]);
};
var stsd = (track) => fullBox("stsd", 0, 0, [
  u32(1)
  // Entry count
], [
  track.info.type === "video" ? videoSampleDescription(
    VIDEO_CODEC_TO_BOX_NAME[track.info.codec],
    track
  ) : soundSampleDescription(
    AUDIO_CODEC_TO_BOX_NAME[track.info.codec],
    track
  )
]);
var videoSampleDescription = (compressionType, track) => box(compressionType, [
  Array(6).fill(0),
  // Reserved
  u16(1),
  // Data reference index
  u16(0),
  // Pre-defined
  u16(0),
  // Reserved
  Array(12).fill(0),
  // Pre-defined
  u16(track.info.width),
  // Width
  u16(track.info.height),
  // Height
  u32(4718592),
  // Horizontal resolution
  u32(4718592),
  // Vertical resolution
  u32(0),
  // Reserved
  u16(1),
  // Frame count
  Array(32).fill(0),
  // Compressor name
  u16(24),
  // Depth
  i16(65535)
  // Pre-defined
], [
  VIDEO_CODEC_TO_CONFIGURATION_BOX[track.info.codec](track),
  track.info.decoderConfig.colorSpace ? colr(track) : null
]);
var COLOR_PRIMARIES_MAP = {
  "bt709": 1,
  // ITU-R BT.709
  "bt470bg": 5,
  // ITU-R BT.470BG
  "smpte170m": 6
  // ITU-R BT.601 525 - SMPTE 170M
};
var TRANSFER_CHARACTERISTICS_MAP = {
  "bt709": 1,
  // ITU-R BT.709
  "smpte170m": 6,
  // SMPTE 170M
  "iec61966-2-1": 13
  // IEC 61966-2-1
};
var MATRIX_COEFFICIENTS_MAP = {
  "rgb": 0,
  // Identity
  "bt709": 1,
  // ITU-R BT.709
  "bt470bg": 5,
  // ITU-R BT.470BG
  "smpte170m": 6
  // SMPTE 170M
};
var colr = (track) => box("colr", [
  ascii("nclx"),
  // Colour type
  u16(COLOR_PRIMARIES_MAP[track.info.decoderConfig.colorSpace.primaries]),
  // Colour primaries
  u16(TRANSFER_CHARACTERISTICS_MAP[track.info.decoderConfig.colorSpace.transfer]),
  // Transfer characteristics
  u16(MATRIX_COEFFICIENTS_MAP[track.info.decoderConfig.colorSpace.matrix]),
  // Matrix coefficients
  u8((track.info.decoderConfig.colorSpace.fullRange ? 1 : 0) << 7)
  // Full range flag
]);
var avcC = (track) => track.info.decoderConfig && box("avcC", [
  // For AVC, description is an AVCDecoderConfigurationRecord, so nothing else to do here
  ...new Uint8Array(track.info.decoderConfig.description)
]);
var hvcC = (track) => track.info.decoderConfig && box("hvcC", [
  // For HEVC, description is a HEVCDecoderConfigurationRecord, so nothing else to do here
  ...new Uint8Array(track.info.decoderConfig.description)
]);
var vpcC = (track) => {
  if (!track.info.decoderConfig) {
    return null;
  }
  let decoderConfig = track.info.decoderConfig;
  if (!decoderConfig.colorSpace) {
    throw new Error(`'colorSpace' is required in the decoder config for VP9.`);
  }
  let parts = decoderConfig.codec.split(".");
  let profile = Number(parts[1]);
  let level = Number(parts[2]);
  let bitDepth = Number(parts[3]);
  let chromaSubsampling = 0;
  let thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange);
  let colourPrimaries = 2;
  let transferCharacteristics = 2;
  let matrixCoefficients = 2;
  return fullBox("vpcC", 1, 0, [
    u8(profile),
    // Profile
    u8(level),
    // Level
    u8(thirdByte),
    // Bit depth, chroma subsampling, full range
    u8(colourPrimaries),
    // Colour primaries
    u8(transferCharacteristics),
    // Transfer characteristics
    u8(matrixCoefficients),
    // Matrix coefficients
    u16(0)
    // Codec initialization data size
  ]);
};
var av1C = () => {
  let marker = 1;
  let version = 1;
  let firstByte = (marker << 7) + version;
  return box("av1C", [
    firstByte,
    0,
    0,
    0
  ]);
};
var soundSampleDescription = (compressionType, track) => box(compressionType, [
  Array(6).fill(0),
  // Reserved
  u16(1),
  // Data reference index
  u16(0),
  // Version
  u16(0),
  // Revision level
  u32(0),
  // Vendor
  u16(track.info.numberOfChannels),
  // Number of channels
  u16(16),
  // Sample size (bits)
  u16(0),
  // Compression ID
  u16(0),
  // Packet size
  fixed_16_16(track.info.sampleRate)
  // Sample rate
], [
  AUDIO_CODEC_TO_CONFIGURATION_BOX[track.info.codec](track)
]);
var esds = (track) => {
  let description = new Uint8Array(track.info.decoderConfig.description);
  return fullBox("esds", 0, 0, [
    // https://stackoverflow.com/a/54803118
    u32(58753152),
    // TAG(3) = Object Descriptor ([2])
    u8(32 + description.byteLength),
    // length of this OD (which includes the next 2 tags)
    u16(1),
    // ES_ID = 1
    u8(0),
    // flags etc = 0
    u32(75530368),
    // TAG(4) = ES Descriptor ([2]) embedded in above OD
    u8(18 + description.byteLength),
    // length of this ESD
    u8(64),
    // MPEG-4 Audio
    u8(21),
    // stream type(6bits)=5 audio, flags(2bits)=1
    u24(0),
    // 24bit buffer size
    u32(130071),
    // max bitrate
    u32(130071),
    // avg bitrate
    u32(92307584),
    // TAG(5) = ASC ([2],[3]) embedded in above OD
    u8(description.byteLength),
    // length
    ...description,
    u32(109084800),
    // TAG(6)
    u8(1),
    // length
    u8(2)
    // data
  ]);
};
var dOps = (track) => {
  let preskip = 3840;
  let gain = 0;
  const description = track.info.decoderConfig?.description;
  if (description) {
    if (description.byteLength < 18) {
      throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.");
    }
    const view2 = ArrayBuffer.isView(description) ? new DataView(description.buffer, description.byteOffset, description.byteLength) : new DataView(description);
    preskip = view2.getUint16(10, true);
    gain = view2.getInt16(14, true);
  }
  return box("dOps", [
    u8(0),
    // Version
    u8(track.info.numberOfChannels),
    // OutputChannelCount
    u16(preskip),
    u32(track.info.sampleRate),
    // InputSampleRate
    fixed_8_8(gain),
    // OutputGain
    u8(0)
    // ChannelMappingFamily
  ]);
};
var stts = (track) => {
  return fullBox("stts", 0, 0, [
    u32(track.timeToSampleTable.length),
    // Number of entries
    track.timeToSampleTable.map((x) => [
      // Time-to-sample table
      u32(x.sampleCount),
      // Sample count
      u32(x.sampleDelta)
      // Sample duration
    ])
  ]);
};
var stss = (track) => {
  if (track.samples.every((x) => x.type === "key"))
    return null;
  let keySamples = [...track.samples.entries()].filter(([, sample]) => sample.type === "key");
  return fullBox("stss", 0, 0, [
    u32(keySamples.length),
    // Number of entries
    keySamples.map(([index]) => u32(index + 1))
    // Sync sample table
  ]);
};
var stsc = (track) => {
  return fullBox("stsc", 0, 0, [
    u32(track.compactlyCodedChunkTable.length),
    // Number of entries
    track.compactlyCodedChunkTable.map((x) => [
      // Sample-to-chunk table
      u32(x.firstChunk),
      // First chunk
      u32(x.samplesPerChunk),
      // Samples per chunk
      u32(1)
      // Sample description index
    ])
  ]);
};
var stsz = (track) => fullBox("stsz", 0, 0, [
  u32(0),
  // Sample size (0 means non-constant size)
  u32(track.samples.length),
  // Number of entries
  track.samples.map((x) => u32(x.size))
  // Sample size table
]);
var stco = (track) => {
  if (track.finalizedChunks.length > 0 && last(track.finalizedChunks).offset >= 2 ** 32) {
    return fullBox("co64", 0, 0, [
      u32(track.finalizedChunks.length),
      // Number of entries
      track.finalizedChunks.map((x) => u64(x.offset))
      // Chunk offset table
    ]);
  }
  return fullBox("stco", 0, 0, [
    u32(track.finalizedChunks.length),
    // Number of entries
    track.finalizedChunks.map((x) => u32(x.offset))
    // Chunk offset table
  ]);
};
var ctts = (track) => {
  return fullBox("ctts", 0, 0, [
    u32(track.compositionTimeOffsetTable.length),
    // Number of entries
    track.compositionTimeOffsetTable.map((x) => [
      // Time-to-sample table
      u32(x.sampleCount),
      // Sample count
      u32(x.sampleCompositionTimeOffset)
      // Sample offset
    ])
  ]);
};
var mvex = (tracks) => {
  return box("mvex", null, tracks.map(trex));
};
var trex = (track) => {
  return fullBox("trex", 0, 0, [
    u32(track.id),
    // Track ID
    u32(1),
    // Default sample description index
    u32(0),
    // Default sample duration
    u32(0),
    // Default sample size
    u32(0)
    // Default sample flags
  ]);
};
var moof = (sequenceNumber, tracks) => {
  return box("moof", null, [
    mfhd(sequenceNumber),
    ...tracks.map(traf)
  ]);
};
var mfhd = (sequenceNumber) => {
  return fullBox("mfhd", 0, 0, [
    u32(sequenceNumber)
    // Sequence number
  ]);
};
var fragmentSampleFlags = (sample) => {
  let byte1 = 0;
  let byte2 = 0;
  let byte3 = 0;
  let byte4 = 0;
  let sampleIsDifferenceSample = sample.type === "delta";
  byte2 |= +sampleIsDifferenceSample;
  if (sampleIsDifferenceSample) {
    byte1 |= 1;
  } else {
    byte1 |= 2;
  }
  return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4;
};
var traf = (track) => {
  return box("traf", null, [
    tfhd(track),
    tfdt(track),
    trun(track)
  ]);
};
var tfhd = (track) => {
  let tfFlags = 0;
  tfFlags |= 8;
  tfFlags |= 16;
  tfFlags |= 32;
  tfFlags |= 131072;
  let referenceSample = track.currentChunk.samples[1] ?? track.currentChunk.samples[0];
  let referenceSampleInfo = {
    duration: referenceSample.timescaleUnitsToNextSample,
    size: referenceSample.size,
    flags: fragmentSampleFlags(referenceSample)
  };
  return fullBox("tfhd", 0, tfFlags, [
    u32(track.id),
    // Track ID
    u32(referenceSampleInfo.duration),
    // Default sample duration
    u32(referenceSampleInfo.size),
    // Default sample size
    u32(referenceSampleInfo.flags)
    // Default sample flags
  ]);
};
var tfdt = (track) => {
  return fullBox("tfdt", 1, 0, [
    u64(intoTimescale(track.currentChunk.startTimestamp, track.timescale))
    // Base Media Decode Time
  ]);
};
var trun = (track) => {
  let allSampleDurations = track.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample);
  let allSampleSizes = track.currentChunk.samples.map((x) => x.size);
  let allSampleFlags = track.currentChunk.samples.map(fragmentSampleFlags);
  let allSampleCompositionTimeOffsets = track.currentChunk.samples.map((x) => intoTimescale(x.presentationTimestamp - x.decodeTimestamp, track.timescale));
  let uniqueSampleDurations = new Set(allSampleDurations);
  let uniqueSampleSizes = new Set(allSampleSizes);
  let uniqueSampleFlags = new Set(allSampleFlags);
  let uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets);
  let firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1];
  let sampleDurationPresent = uniqueSampleDurations.size > 1;
  let sampleSizePresent = uniqueSampleSizes.size > 1;
  let sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1;
  let sampleCompositionTimeOffsetsPresent = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some((x) => x !== 0);
  let flags = 0;
  flags |= 1;
  flags |= 4 * +firstSampleFlagsPresent;
  flags |= 256 * +sampleDurationPresent;
  flags |= 512 * +sampleSizePresent;
  flags |= 1024 * +sampleFlagsPresent;
  flags |= 2048 * +sampleCompositionTimeOffsetsPresent;
  return fullBox("trun", 1, flags, [
    u32(track.currentChunk.samples.length),
    // Sample count
    u32(track.currentChunk.offset - track.currentChunk.moofOffset || 0),
    // Data offset
    firstSampleFlagsPresent ? u32(allSampleFlags[0]) : [],
    track.currentChunk.samples.map((_, i) => [
      sampleDurationPresent ? u32(allSampleDurations[i]) : [],
      // Sample duration
      sampleSizePresent ? u32(allSampleSizes[i]) : [],
      // Sample size
      sampleFlagsPresent ? u32(allSampleFlags[i]) : [],
      // Sample flags
      // Sample composition time offsets
      sampleCompositionTimeOffsetsPresent ? i32(allSampleCompositionTimeOffsets[i]) : []
    ])
  ]);
};
var mfra = (tracks) => {
  return box("mfra", null, [
    ...tracks.map(tfra),
    mfro()
  ]);
};
var tfra = (track, trackIndex) => {
  let version = 1;
  return fullBox("tfra", version, 0, [
    u32(track.id),
    // Track ID
    u32(63),
    // This specifies that traf number, trun number and sample number are 32-bit ints
    u32(track.finalizedChunks.length),
    // Number of entries
    track.finalizedChunks.map((chunk) => [
      u64(intoTimescale(chunk.startTimestamp, track.timescale)),
      // Time
      u64(chunk.moofOffset),
      // moof offset
      u32(trackIndex + 1),
      // traf number
      u32(1),
      // trun number
      u32(1)
      // Sample number
    ])
  ]);
};
var mfro = () => {
  return fullBox("mfro", 0, 0, [
    // This value needs to be overwritten manually from the outside, where the actual size of the enclosing mfra box
    // is known
    u32(0)
    // Size
  ]);
};
var VIDEO_CODEC_TO_BOX_NAME = {
  "avc": "avc1",
  "hevc": "hvc1",
  "vp9": "vp09",
  "av1": "av01"
};
var VIDEO_CODEC_TO_CONFIGURATION_BOX = {
  "avc": avcC,
  "hevc": hvcC,
  "vp9": vpcC,
  "av1": av1C
};
var AUDIO_CODEC_TO_BOX_NAME = {
  "aac": "mp4a",
  "opus": "Opus"
};
var AUDIO_CODEC_TO_CONFIGURATION_BOX = {
  "aac": esds,
  "opus": dOps
};
var Target = class {
};
var ArrayBufferTarget = class extends Target {
  constructor() {
    super(...arguments);
    this.buffer = null;
  }
};
var StreamTarget = class extends Target {
  constructor(options) {
    super();
    this.options = options;
    if (typeof options !== "object") {
      throw new TypeError("StreamTarget requires an options object to be passed to its constructor.");
    }
    if (options.onData) {
      if (typeof options.onData !== "function") {
        throw new TypeError("options.onData, when provided, must be a function.");
      }
      if (options.onData.length < 2) {
        throw new TypeError(
          "options.onData, when provided, must be a function that takes in at least two arguments (data and position). Ignoring the position argument, which specifies the byte offset at which the data is to be written, can lead to broken outputs."
        );
      }
    }
    if (options.chunked !== void 0 && typeof options.chunked !== "boolean") {
      throw new TypeError("options.chunked, when provided, must be a boolean.");
    }
    if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize < 1024)) {
      throw new TypeError("options.chunkSize, when provided, must be an integer and not smaller than 1024.");
    }
  }
};
var FileSystemWritableFileStreamTarget = class extends Target {
  constructor(stream, options) {
    super();
    this.stream = stream;
    this.options = options;
    if (!(stream instanceof FileSystemWritableFileStream)) {
      throw new TypeError("FileSystemWritableFileStreamTarget requires a FileSystemWritableFileStream instance.");
    }
    if (options !== void 0 && typeof options !== "object") {
      throw new TypeError("FileSystemWritableFileStreamTarget's options, when provided, must be an object.");
    }
    if (options) {
      if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) {
        throw new TypeError("options.chunkSize, when provided, must be a positive integer");
      }
    }
  }
};

// src/writer.ts
var _helper, _helperView;
var Writer = class {
  constructor() {
    this.pos = 0;
    __privateAdd(this, _helper, new Uint8Array(8));
    __privateAdd(this, _helperView, new DataView(__privateGet(this, _helper).buffer));
    /**
     * Stores the position from the start of the file to where boxes elements have been written. This is used to
     * rewrite/edit elements that were already added before, and to measure sizes of things.
     */
    this.offsets = /* @__PURE__ */ new WeakMap();
  }
  /** Sets the current position for future writes to a new one. */
  seek(newPos) {
    this.pos = newPos;
  }
  writeU32(value) {
    __privateGet(this, _helperView).setUint32(0, value, false);
    this.write(__privateGet(this, _helper).subarray(0, 4));
  }
  writeU64(value) {
    __privateGet(this, _helperView).setUint32(0, Math.floor(value / 2 ** 32), false);
    __privateGet(this, _helperView).setUint32(4, value, false);
    this.write(__privateGet(this, _helper).subarray(0, 8));
  }
  writeAscii(text) {
    for (let i = 0; i < text.length; i++) {
      __privateGet(this, _helperView).setUint8(i % 8, text.charCodeAt(i));
      if (i % 8 === 7)
        this.write(__privateGet(this, _helper));
    }
    if (text.length % 8 !== 0) {
      this.write(__privateGet(this, _helper).subarray(0, text.length % 8));
    }
  }
  writeBox(box2) {
    this.offsets.set(box2, this.pos);
    if (box2.contents && !box2.children) {
      this.writeBoxHeader(box2, box2.size ?? box2.contents.byteLength + 8);
      this.write(box2.contents);
    } else {
      let startPos = this.pos;
      this.writeBoxHeader(box2, 0);
      if (box2.contents)
        this.write(box2.contents);
      if (box2.children) {
        for (let child of box2.children)
          if (child)
            this.writeBox(child);
      }
      let endPos = this.pos;
      let size = box2.size ?? endPos - startPos;
      this.seek(startPos);
      this.writeBoxHeader(box2, size);
      this.seek(endPos);
    }
  }
  writeBoxHeader(box2, size) {
    this.writeU32(box2.largeSize ? 1 : size);
    this.writeAscii(box2.type);
    if (box2.largeSize)
      this.writeU64(size);
  }
  measureBoxHeader(box2) {
    return 8 + (box2.largeSize ? 8 : 0);
  }
  patchBox(box2) {
    let endPos = this.pos;
    this.seek(this.offsets.get(box2));
    this.writeBox(box2);
    this.seek(endPos);
  }
  measureBox(box2) {
    if (box2.contents && !box2.children) {
      let headerSize = this.measureBoxHeader(box2);
      return headerSize + box2.contents.byteLength;
    } else {
      let result = this.measureBoxHeader(box2);
      if (box2.contents)
        result += box2.contents.byteLength;
      if (box2.children) {
        for (let child of box2.children)
          if (child)
            result += this.measureBox(child);
      }
      return result;
    }
  }
};
_helper = new WeakMap();
_helperView = new WeakMap();
var _target, _buffer, _bytes, _maxPos, _ensureSize, ensureSize_fn;
var ArrayBufferTargetWriter = class extends Writer {
  constructor(target) {
    super();
    __privateAdd(this, _ensureSize);
    __privateAdd(this, _target, void 0);
    __privateAdd(this, _buffer, new ArrayBuffer(2 ** 16));
    __privateAdd(this, _bytes, new Uint8Array(__privateGet(this, _buffer)));
    __privateAdd(this, _maxPos, 0);
    __privateSet(this, _target, target);
  }
  write(data) {
    __privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos + data.byteLength);
    __privateGet(this, _bytes).set(data, this.pos);
    this.pos += data.byteLength;
    __privateSet(this, _maxPos, Math.max(__privateGet(this, _maxPos), this.pos));
  }
  finalize() {
    __privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos);
    __privateGet(this, _target).buffer = __privateGet(this, _buffer).slice(0, Math.max(__privateGet(this, _maxPos), this.pos));
  }
};
_target = new WeakMap();
_buffer = new WeakMap();
_bytes = new WeakMap();
_maxPos = new WeakMap();
_ensureSize = new WeakSet();
ensureSize_fn = function(size) {
  let newLength = __privateGet(this, _buffer).byteLength;
  while (newLength < size)
    newLength *= 2;
  if (newLength === __privateGet(this, _buffer).byteLength)
    return;
  let newBuffer = new ArrayBuffer(newLength);
  let newBytes = new Uint8Array(newBuffer);
  newBytes.set(__privateGet(this, _bytes), 0);
  __privateSet(this, _buffer, newBuffer);
  __privateSet(this, _bytes, newBytes);
};
var DEFAULT_CHUNK_SIZE = 2 ** 24;
var MAX_CHUNKS_AT_ONCE = 2;
var _target2, _sections, _chunked, _chunkSize, _chunks, _writeDataIntoChunks, writeDataIntoChunks_fn, _insertSectionIntoChunk, insertSectionIntoChunk_fn, _createChunk, createChunk_fn, _flushChunks, flushChunks_fn;
var StreamTargetWriter = class extends Writer {
  constructor(target) {
    super();
    __privateAdd(this, _writeDataIntoChunks);
    __privateAdd(this, _insertSectionIntoChunk);
    __privateAdd(this, _createChunk);
    __privateAdd(this, _flushChunks);
    __privateAdd(this, _target2, void 0);
    __privateAdd(this, _sections, []);
    __privateAdd(this, _chunked, void 0);
    __privateAdd(this, _chunkSize, void 0);
    /**
     * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out.
     * A chunk is flushed if all of its contents have been written.
     */
    __privateAdd(this, _chunks, []);
    __privateSet(this, _target2, target);
    __privateSet(this, _chunked, target.options?.chunked ?? false);
    __privateSet(this, _chunkSize, target.options?.chunkSize ?? DEFAULT_CHUNK_SIZE);
  }
  write(data) {
    __privateGet(this, _sections).push({
      data: data.slice(),
      start: this.pos
    });
    this.pos += data.byteLength;
  }
  flush() {
    if (__privateGet(this, _sections).length === 0)
      return;
    let chunks = [];
    let sorted = [...__privateGet(this, _sections)].sort((a, b) => a.start - b.start);
    chunks.push({
      start: sorted[0].start,
      size: sorted[0].data.byteLength
    });
    for (let i = 1; i < sorted.length; i++) {
      let lastChunk = chunks[chunks.length - 1];
      let section = sorted[i];
      if (section.start <= lastChunk.start + lastChunk.size) {
        lastChunk.size = Math.max(lastChunk.size, section.start + section.data.byteLength - lastChunk.start);
      } else {
        chunks.push({
          start: section.start,
          size: section.data.byteLength
        });
      }
    }
    for (let chunk of chunks) {
      chunk.data = new Uint8Array(chunk.size);
      for (let section of __privateGet(this, _sections)) {
        if (chunk.start <= section.start && section.start < chunk.start + chunk.size) {
          chunk.data.set(section.data, section.start - chunk.start);
        }
      }
      if (__privateGet(this, _chunked)) {
        __privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, chunk.data, chunk.start);
        __privateMethod(this, _flushChunks, flushChunks_fn).call(this);
      } else {
        __privateGet(this, _target2).options.onData?.(chunk.data, chunk.start);
      }
    }
    __privateGet(this, _sections).length = 0;
  }
  finalize() {
    if (__privateGet(this, _chunked)) {
      __privateMethod(this, _flushChunks, flushChunks_fn).call(this, true);
    }
  }
};
_target2 = new WeakMap();
_sections = new WeakMap();
_chunked = new WeakMap();
_chunkSize = new WeakMap();
_chunks = new WeakMap();
_writeDataIntoChunks = new WeakSet();
writeDataIntoChunks_fn = function(data, position) {
  let chunkIndex = __privateGet(this, _chunks).findIndex((x) => x.start <= position && position < x.start + __privateGet(this, _chunkSize));
  if (chunkIndex === -1)
    chunkIndex = __privateMethod(this, _createChunk, createChunk_fn).call(this, position);
  let chunk = __privateGet(this, _chunks)[chunkIndex];
  let relativePosition = position - chunk.start;
  let toWrite = data.subarray(0, Math.min(__privateGet(this, _chunkSize) - relativePosition, data.byteLength));
  chunk.data.set(toWrite, relativePosition);
  let section = {
    start: relativePosition,
    end: relativePosition + toWrite.byteLength
  };
  __privateMethod(this, _insertSectionIntoChunk, insertSectionIntoChunk_fn).call(this, chunk, section);
  if (chunk.written[0].start === 0 && chunk.written[0].end === __privateGet(this, _chunkSize)) {
    chunk.shouldFlush = true;
  }
  if (__privateGet(this, _chunks).length > MAX_CHUNKS_AT_ONCE) {
    for (let i = 0; i < __privateGet(this, _chunks).length - 1; i++) {
      __privateGet(this, _chunks)[i].shouldFlush = true;
    }
    __privateMethod(this, _flushChunks, flushChunks_fn).call(this);
  }
  if (toWrite.byteLength < data.byteLength) {
    __privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, data.subarray(toWrite.byteLength), position + toWrite.byteLength);
  }
};
_insertSectionIntoChunk = new WeakSet();
insertSectionIntoChunk_fn = function(chunk, section) {
  let low = 0;
  let high = chunk.written.length - 1;
  let index = -1;
  while (low <= high) {
    let mid = Math.floor(low + (high - low + 1) / 2);
    if (chunk.written[mid].start <= section.start) {
      low = mid + 1;
      index = mid;
    } else {
      high = mid - 1;
    }
  }
  chunk.written.splice(index + 1, 0, section);
  if (index === -1 || chunk.written[index].end < section.start)
    index++;
  while (index < chunk.written.length - 1 && chunk.written[index].end >= chunk.written[index + 1].start) {
    chunk.written[index].end = Math.max(chunk.written[index].end, chunk.written[index + 1].end);
    chunk.written.splice(index + 1, 1);
  }
};
_createChunk = new WeakSet();
createChunk_fn = function(includesPosition) {
  let start = Math.floor(includesPosition / __privateGet(this, _chunkSize)) * __privateGet(this, _chunkSize);
  let chunk = {
    start,
    data: new Uint8Array(__privateGet(this, _chunkSize)),
    written: [],
    shouldFlush: false
  };
  __privateGet(this, _chunks).push(chunk);
  __privateGet(this, _chunks).sort((a, b) => a.start - b.start);
  return __privateGet(this, _chunks).indexOf(chunk);
};
_flushChunks = new WeakSet();
flushChunks_fn = function(force = false) {
  for (let i = 0; i < __privateGet(this, _chunks).length; i++) {
    let chunk = __privateGet(this, _chunks)[i];
    if (!chunk.shouldFlush && !force)
      continue;
    for (let section of chunk.written) {
      __privateGet(this, _target2).options.onData?.(
        chunk.data.subarray(section.start, section.end),
        chunk.start + section.start
      );
    }
    __privateGet(this, _chunks).splice(i--, 1);
  }
};
var FileSystemWritableFileStreamTargetWriter = class extends StreamTargetWriter {
  constructor(target) {
    super(new StreamTarget({
      onData: (data, position) => target.stream.write({
        type: "write",
        data,
        position
      }),
      chunked: true,
      chunkSize: target.options?.chunkSize
    }));
  }
};

// src/muxer.ts
var GLOBAL_TIMESCALE = 1e3;
var SUPPORTED_VIDEO_CODECS = ["avc", "hevc", "vp9", "av1"];
var SUPPORTED_AUDIO_CODECS = ["aac", "opus"];
var TIMESTAMP_OFFSET = 2082844800;
var FIRST_TIMESTAMP_BEHAVIORS = ["strict", "offset", "cross-track-offset"];
var _options, _writer, _ftypSize, _mdat, _videoTrack, _audioTrack, _creationTime, _finalizedChunks, _nextFragmentNumber, _videoSampleQueue, _audioSampleQueue, _finalized, _validateOptions, validateOptions_fn, _writeHeader, writeHeader_fn, _computeMoovSizeUpperBound, computeMoovSizeUpperBound_fn, _prepareTracks, prepareTracks_fn, _generateMpeg4AudioSpecificConfig, generateMpeg4AudioSpecificConfig_fn, _createSampleForTrack, createSampleForTrack_fn, _addSampleToTrack, addSampleToTrack_fn, _validateTimestamp, validateTimestamp_fn, _finalizeCurrentChunk, finalizeCurrentChunk_fn, _finalizeFragment, finalizeFragment_fn, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn, _ensureNotFinalized, ensureNotFinalized_fn;
var Muxer = class {
  constructor(options) {
    __privateAdd(this, _validateOptions);
    __privateAdd(this, _writeHeader);
    __privateAdd(this, _computeMoovSizeUpperBound);
    __privateAdd(this, _prepareTracks);
    // https://wiki.multimedia.cx/index.php/MPEG-4_Audio
    __privateAdd(this, _generateMpeg4AudioSpecificConfig);
    __privateAdd(this, _createSampleForTrack);
    __privateAdd(this, _addSampleToTrack);
    __privateAdd(this, _validateTimestamp);
    __privateAdd(this, _finalizeCurrentChunk);
    __privateAdd(this, _finalizeFragment);
    __privateAdd(this, _maybeFlushStreamingTargetWriter);
    __privateAdd(this, _ensureNotFinalized);
    __privateAdd(this, _options, void 0);
    __privateAdd(this, _writer, void 0);
    __privateAdd(this, _ftypSize, void 0);
    __privateAdd(this, _mdat, void 0);
    __privateAdd(this, _videoTrack, null);
    __privateAdd(this, _audioTrack, null);
    __privateAdd(this, _creationTime, Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET);
    __privateAdd(this, _finalizedChunks, []);
    // Fields for fragmented MP4:
    __privateAdd(this, _nextFragmentNumber, 1);
    __privateAdd(this, _videoSampleQueue, []);
    __privateAdd(this, _audioSampleQueue, []);
    __privateAdd(this, _finalized, false);
    __privateMethod(this, _validateOptions, validateOptions_fn).call(this, options);
    options.video = deepClone(options.video);
    options.audio = deepClone(options.audio);
    options.fastStart = deepClone(options.fastStart);
    this.target = options.target;
    __privateSet(this, _options, {
      firstTimestampBehavior: "strict",
      ...options
    });
    if (options.target instanceof ArrayBufferTarget) {
      __privateSet(this, _writer, new ArrayBufferTargetWriter(options.target));
    } else if (options.target instanceof StreamTarget) {
      __privateSet(this, _writer, new StreamTargetWriter(options.target));
    } else if (options.target instanceof FileSystemWritableFileStreamTarget) {
      __privateSet(this, _writer, new FileSystemWritableFileStreamTargetWriter(options.target));
    } else {
      throw new Error(`Invalid target: ${options.target}`);
    }
    __privateMethod(this, _prepareTracks, prepareTracks_fn).call(this);
    __privateMethod(this, _writeHeader, writeHeader_fn).call(this);
  }
  addVideoChunk(sample, meta, timestamp, compositionTimeOffset) {
    if (!(sample instanceof EncodedVideoChunk)) {
      throw new TypeError("addVideoChunk's first argument (sample) must be of type EncodedVideoChunk.");
    }
    if (meta && typeof meta !== "object") {
      throw new TypeError("addVideoChunk's second argument (meta), when provided, must be an object.");
    }
    if (timestamp !== void 0 && (!Number.isFinite(timestamp) || timestamp < 0)) {
      throw new TypeError(
        "addVideoChunk's third argument (timestamp), when provided, must be a non-negative real number."
      );
    }
    if (compositionTimeOffset !== void 0 && !Number.isFinite(compositionTimeOffset)) {
      throw new TypeError(
        "addVideoChunk's fourth argument (compositionTimeOffset), when provided, must be a real number."
      );
    }
    let data = new Uint8Array(sample.byteLength);
    sample.copyTo(data);
    this.addVideoChunkRaw(
      data,
      sample.type,
      timestamp ?? sample.timestamp,
      sample.duration,
      meta,
      compositionTimeOffset
    );
  }
  addVideoChunkRaw(data, type, timestamp, duration, meta, compositionTimeOffset) {
    if (!(data instanceof Uint8Array)) {
      throw new TypeError("addVideoChunkRaw's first argument (data) must be an instance of Uint8Array.");
    }
    if (type !== "key" && type !== "delta") {
      throw new TypeError("addVideoChunkRaw's second argument (type) must be either 'key' or 'delta'.");
    }
    if (!Number.isFinite(timestamp) || timestamp < 0) {
      throw new TypeError("addVideoChunkRaw's third argument (timestamp) must be a non-negative real number.");
    }
    if (!Number.isFinite(duration) || duration < 0) {
      throw new TypeError("addVideoChunkRaw's fourth argument (duration) must be a non-negative real number.");
    }
    if (meta && typeof meta !== "object") {
      throw new TypeError("addVideoChunkRaw's fifth argument (meta), when provided, must be an object.");
    }
    if (compositionTimeOffset !== void 0 && !Number.isFinite(compositionTimeOffset)) {
      throw new TypeError(
        "addVideoChunkRaw's sixth argument (compositionTimeOffset), when provided, must be a real number."
      );
    }
    __privateMethod(this, _ensureNotFinalized, ensureNotFinalized_fn).call(this);
    if (!__privateGet(this, _options).video)
      throw new Error("No video track declared.");
    if (typeof __privateGet(this, _options).fastStart === "object" && __privateGet(this, _videoTrack).samples.length === __privateGet(this, _options).fastStart.expectedVideoChunks) {
      throw new Error(`Cannot add more video chunks than specified in 'fastStart' (${__privateGet(this, _options).fastStart.expectedVideoChunks}).`);
    }
    let videoSample = __privateMethod(this, _createSampleForTrack, createSampleForTrack_fn).call(this, __privateGet(this, _videoTrack), data, type, timestamp, duration, meta, compositionTimeOffset);
    if (__privateGet(this, _options).fastStart === "fragmented" && __privateGet(this, _audioTrack)) {
      while (__privateGet(this, _audioSampleQueue).length > 0 && __privateGet(this, _audioSampleQueue)[0].decodeTimestamp <= videoSample.decodeTimestamp) {
        let audioSample = __privateGet(this, _audioSampleQueue).shift();
        __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
      }
      if (videoSample.decodeTimestamp <= __privateGet(this, _audioTrack).lastDecodeTimestamp) {
        __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
      } else {
        __privateGet(this, _videoSampleQueue).push(videoSample);
      }
    } else {
      __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
    }
  }
  addAudioChunk(sample, meta, timestamp) {
    if (!(sample instanceof EncodedAudioChunk)) {
      throw new TypeError("addAudioChunk's first argument (sample) must be of type EncodedAudioChunk.");
    }
    if (meta && typeof meta !== "object") {
      throw new TypeError("addAudioChunk's second argument (meta), when provided, must be an object.");
    }
    if (timestamp !== void 0 && (!Number.isFinite(timestamp) || timestamp < 0)) {
      throw new TypeError(
        "addAudioChunk's third argument (timestamp), when provided, must be a non-negative real number."
      );
    }
    let data = new Uint8Array(sample.byteLength);
    sample.copyTo(data);
    this.addAudioChunkRaw(data, sample.type, timestamp ?? sample.timestamp, sample.duration, meta);
  }
  addAudioChunkRaw(data, type, timestamp, duration, meta) {
    if (!(data instanceof Uint8Array)) {
      throw new TypeError("addAudioChunkRaw's first argument (data) must be an instance of Uint8Array.");
    }
    if (type !== "key" && type !== "delta") {
      throw new TypeError("addAudioChunkRaw's second argument (type) must be either 'key' or 'delta'.");
    }
    if (!Number.isFinite(timestamp) || timestamp < 0) {
      throw new TypeError("addAudioChunkRaw's third argument (timestamp) must be a non-negative real number.");
    }
    if (!Number.isFinite(duration) || duration < 0) {
      throw new TypeError("addAudioChunkRaw's fourth argument (duration) must be a non-negative real number.");
    }
    if (meta && typeof meta !== "object") {
      throw new TypeError("addAudioChunkRaw's fifth argument (meta), when provided, must be an object.");
    }
    __privateMethod(this, _ensureNotFinalized, ensureNotFinalized_fn).call(this);
    if (!__privateGet(this, _options).audio)
      throw new Error("No audio track declared.");
    if (typeof __privateGet(this, _options).fastStart === "object" && __privateGet(this, _audioTrack).samples.length === __privateGet(this, _options).fastStart.expectedAudioChunks) {
      throw new Error(`Cannot add more audio chunks than specified in 'fastStart' (${__privateGet(this, _options).fastStart.expectedAudioChunks}).`);
    }
    let audioSample = __privateMethod(this, _createSampleForTrack, createSampleForTrack_fn).call(this, __privateGet(this, _audioTrack), data, type, timestamp, duration, meta);
    if (__privateGet(this, _options).fastStart === "fragmented" && __privateGet(this, _videoTrack)) {
      while (__privateGet(this, _videoSampleQueue).length > 0 && __privateGet(this, _videoSampleQueue)[0].decodeTimestamp <= audioSample.decodeTimestamp) {
        let videoSample = __privateGet(this, _videoSampleQueue).shift();
        __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
      }
      if (audioSample.decodeTimestamp <= __privateGet(this, _videoTrack).lastDecodeTimestamp) {
        __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
      } else {
        __privateGet(this, _audioSampleQueue).push(audioSample);
      }
    } else {
      __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
    }
  }
  /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */
  finalize() {
    if (__privateGet(this, _finalized)) {
      throw new Error("Cannot finalize a muxer more than once.");
    }
    if (__privateGet(this, _options).fastStart === "fragmented") {
      for (let videoSample of __privateGet(this, _videoSampleQueue))
        __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
      for (let audioSample of __privateGet(this, _audioSampleQueue))
        __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
      __privateMethod(this, _finalizeFragment, finalizeFragment_fn).call(this, false);
    } else {
      if (__privateGet(this, _videoTrack))
        __privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, __privateGet(this, _videoTrack));
      if (__privateGet(this, _audioTrack))
        __privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, __privateGet(this, _audioTrack));
    }
    let tracks = [__privateGet(this, _videoTrack), __privateGet(this, _audioTrack)].filter(Boolean);
    if (__privateGet(this, _options).fastStart === "in-memory") {
      let mdatSize;
      for (let i = 0; i < 2; i++) {
        let movieBox2 = moov(tracks, __privateGet(this, _creationTime));
        let movieBoxSize = __privateGet(this, _writer).measureBox(movieBox2);
        mdatSize = __privateGet(this, _writer).measureBox(__privateGet(this, _mdat));
        let currentChunkPos = __privateGet(this, _writer).pos + movieBoxSize + mdatSize;
        for (let chunk of __privateGet(this, _finalizedChunks)) {
          chunk.offset = currentChunkPos;
          for (let { data } of chunk.samples) {
            currentChunkPos += data.byteLength;
            mdatSize += data.byteLength;
          }
        }
        if (currentChunkPos < 2 ** 32)
          break;
        if (mdatSize >= 2 ** 32)
          __privateGet(this, _mdat).largeSize = true;
      }
      let movieBox = moov(tracks, __privateGet(this, _creationTime));
      __privateGet(this, _writer).writeBox(movieBox);
      __privateGet(this, _mdat).size = mdatSize;
      __privateGet(this, _writer).writeBox(__privateGet(this, _mdat));
      for (let chunk of __privateGet(this, _finalizedChunks)) {
        for (let sample of chunk.samples) {
          __privateGet(this, _writer).write(sample.data);
          sample.data = null;
        }
      }
    } else if (__privateGet(this, _options).fastStart === "fragmented") {
      let startPos = __privateGet(this, _writer).pos;
      let mfraBox = mfra(tracks);
      __privateGet(this, _writer).writeBox(mfraBox);
      let mfraBoxSize = __privateGet(this, _writer).pos - startPos;
      __privateGet(this, _writer).seek(__privateGet(this, _writer).pos - 4);
      __privateGet(this, _writer).writeU32(mfraBoxSize);
    } else {
      let mdatPos = __privateGet(this, _writer).offsets.get(__privateGet(this, _mdat));
      let mdatSize = __privateGet(this, _writer).pos - mdatPos;
      __privateGet(this, _mdat).size = mdatSize;
      __privateGet(this, _mdat).largeSize = mdatSize >= 2 ** 32;
      __privateGet(this, _writer).patchBox(__privateGet(this, _mdat));
      let movieBox = moov(tracks, __privateGet(this, _creationTime));
      if (typeof __privateGet(this, _options).fastStart === "object") {
        __privateGet(this, _writer).seek(__privateGet(this, _ftypSize));
        __privateGet(this, _writer).writeBox(movieBox);
        let remainingBytes = mdatPos - __privateGet(this, _writer).pos;
        __privateGet(this, _writer).writeBox(free(remainingBytes));
      } else {
        __privateGet(this, _writer).writeBox(movieBox);
      }
    }
    __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
    __privateGet(this, _writer).finalize();
    __privateSet(this, _finalized, true);
  }
};
_options = new WeakMap();
_writer = new WeakMap();
_ftypSize = new WeakMap();
_mdat = new WeakMap();
_videoTrack = new WeakMap();
_audioTrack = new WeakMap();
_creationTime = new WeakMap();
_finalizedChunks = new WeakMap();
_nextFragmentNumber = new WeakMap();
_videoSampleQueue = new WeakMap();
_audioSampleQueue = new WeakMap();
_finalized = new WeakMap();
_validateOptions = new WeakSet();
validateOptions_fn = function(options) {
  if (typeof options !== "object") {
    throw new TypeError("The muxer requires an options object to be passed to its constructor.");
  }
  if (!(options.target instanceof Target)) {
    throw new TypeError("The target must be provided and an instance of Target.");
  }
  if (options.video) {
    if (!SUPPORTED_VIDEO_CODECS.includes(options.video.codec)) {
      throw new TypeError(`Unsupported video codec: ${options.video.codec}`);
    }
    if (!Number.isInteger(options.video.width) || options.video.width <= 0) {
      throw new TypeError(`Invalid video width: ${options.video.width}. Must be a positive integer.`);
    }
    if (!Number.isInteger(options.video.height) || options.video.height <= 0) {
      throw new TypeError(`Invalid video height: ${options.video.height}. Must be a positive integer.`);
    }
    const videoRotation = options.video.rotation;
    if (typeof videoRotation === "number" && ![0, 90, 180, 270].includes(videoRotation)) {
      throw new TypeError(`Invalid video rotation: ${videoRotation}. Has to be 0, 90, 180 or 270.`);
    } else if (Array.isArray(videoRotation) && (videoRotation.length !== 9 || videoRotation.some((value) => typeof value !== "number"))) {
      throw new TypeError(`Invalid video transformation matrix: ${videoRotation.join()}`);
    }
    if (options.video.frameRate !== void 0 && (!Number.isInteger(options.video.frameRate) || options.video.frameRate <= 0)) {
      throw new TypeError(
        `Invalid video frame rate: ${options.video.frameRate}. Must be a positive integer.`
      );
    }
  }
  if (options.audio) {
    if (!SUPPORTED_AUDIO_CODECS.includes(options.audio.codec)) {
      throw new TypeError(`Unsupported audio codec: ${options.audio.codec}`);
    }
    if (!Number.isInteger(options.audio.numberOfChannels) || options.audio.numberOfChannels <= 0) {
      throw new TypeError(
        `Invalid number of audio channels: ${options.audio.numberOfChannels}. Must be a positive integer.`
      );
    }
    if (!Number.isInteger(options.audio.sampleRate) || options.audio.sampleRate <= 0) {
      throw new TypeError(
        `Invalid audio sample rate: ${options.audio.sampleRate}. Must be a positive integer.`
      );
    }
  }
  if (options.firstTimestampBehavior && !FIRST_TIMESTAMP_BEHAVIORS.includes(options.firstTimestampBehavior)) {
    throw new TypeError(`Invalid first timestamp behavior: ${options.firstTimestampBehavior}`);
  }
  if (typeof options.fastStart === "object") {
    if (options.video) {
      if (options.fastStart.expectedVideoChunks === void 0) {
        throw new TypeError(`'fastStart' is an object but is missing property 'expectedVideoChunks'.`);
      } else if (!Number.isInteger(options.fastStart.expectedVideoChunks) || options.fastStart.expectedVideoChunks < 0) {
        throw new TypeError(`'expectedVideoChunks' must be a non-negative integer.`);
      }
    }
    if (options.audio) {
      if (options.fastStart.expectedAudioChunks === void 0) {
        throw new TypeError(`'fastStart' is an object but is missing property 'expectedAudioChunks'.`);
      } else if (!Number.isInteger(options.fastStart.expectedAudioChunks) || options.fastStart.expectedAudioChunks < 0) {
        throw new TypeError(`'expectedAudioChunks' must be a non-negative integer.`);
      }
    }
  } else if (![false, "in-memory", "fragmented"].includes(options.fastStart)) {
    throw new TypeError(`'fastStart' option must be false, 'in-memory', 'fragmented' or an object.`);
  }
  if (options.minFragmentDuration !== void 0 && (!Number.isFinite(options.minFragmentDuration) || options.minFragmentDuration < 0)) {
    throw new TypeError(`'minFragmentDuration' must be a non-negative number.`);
  }
};
_writeHeader = new WeakSet();
writeHeader_fn = function() {
  __privateGet(this, _writer).writeBox(ftyp({
    holdsAvc: __privateGet(this, _options).video?.codec === "avc",
    fragmented: __privateGet(this, _options).fastStart === "fragmented"
  }));
  __privateSet(this, _ftypSize, __privateGet(this, _writer).pos);
  if (__privateGet(this, _options).fastStart === "in-memory") {
    __privateSet(this, _mdat, mdat(false));
  } else if (__privateGet(this, _options).fastStart === "fragmented") ; else {
    if (typeof __privateGet(this, _options).fastStart === "object") {
      let moovSizeUpperBound = __privateMethod(this, _computeMoovSizeUpperBound, computeMoovSizeUpperBound_fn).call(this);
      __privateGet(this, _writer).seek(__privateGet(this, _writer).pos + moovSizeUpperBound);
    }
    __privateSet(this, _mdat, mdat(true));
    __privateGet(this, _writer).writeBox(__privateGet(this, _mdat));
  }
  __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
};
_computeMoovSizeUpperBound = new WeakSet();
computeMoovSizeUpperBound_fn = function() {
  if (typeof __privateGet(this, _options).fastStart !== "object")
    return;
  let upperBound = 0;
  let sampleCounts = [
    __privateGet(this, _options).fastStart.expectedVideoChunks,
    __privateGet(this, _options).fastStart.expectedAudioChunks
  ];
  for (let n of sampleCounts) {
    if (!n)
      continue;
    upperBound += (4 + 4) * Math.ceil(2 / 3 * n);
    upperBound += 4 * n;
    upperBound += (4 + 4 + 4) * Math.ceil(2 / 3 * n);
    upperBound += 4 * n;
    upperBound += 8 * n;
  }
  upperBound += 4096;
  return upperBound;
};
_prepareTracks = new WeakSet();
prepareTracks_fn = function() {
  if (__privateGet(this, _options).video) {
    __privateSet(this, _videoTrack, {
      id: 1,
      info: {
        type: "video",
        codec: __privateGet(this, _options).video.codec,
        width: __privateGet(this, _options).video.width,
        height: __privateGet(this, _options).video.height,
        rotation: __privateGet(this, _options).video.rotation ?? 0,
        decoderConfig: null
      },
      // The fallback contains many common frame rates as factors
      timescale: __privateGet(this, _options).video.frameRate ?? 57600,
      samples: [],
      finalizedChunks: [],
      currentChunk: null,
      firstDecodeTimestamp: void 0,
      lastDecodeTimestamp: -1,
      timeToSampleTable: [],
      compositionTimeOffsetTable: [],
      lastTimescaleUnits: null,
      lastSample: null,
      compactlyCodedChunkTable: []
    });
  }
  if (__privateGet(this, _options).audio) {
    __privateSet(this, _audioTrack, {
      id: __privateGet(this, _options).video ? 2 : 1,
      info: {
        type: "audio",
        codec: __privateGet(this, _options).audio.codec,
        numberOfChannels: __privateGet(this, _options).audio.numberOfChannels,
        sampleRate: __privateGet(this, _options).audio.sampleRate,
        decoderConfig: null
      },
      timescale: __privateGet(this, _options).audio.sampleRate,
      samples: [],
      finalizedChunks: [],
      currentChunk: null,
      firstDecodeTimestamp: void 0,
      lastDecodeTimestamp: -1,
      timeToSampleTable: [],
      compositionTimeOffsetTable: [],
      lastTimescaleUnits: null,
      lastSample: null,
      compactlyCodedChunkTable: []
    });
    if (__privateGet(this, _options).audio.codec === "aac") {
      let guessedCodecPrivate = __privateMethod(this, _generateMpeg4AudioSpecificConfig, generateMpeg4AudioSpecificConfig_fn).call(
        this,
        2,
        // Object type for AAC-LC, since it's the most common
        __privateGet(this, _options).audio.sampleRate,
        __privateGet(this, _options).audio.numberOfChannels
      );
      __privateGet(this, _audioTrack).info.decoderConfig = {
        codec: __privateGet(this, _options).audio.codec,
        description: guessedCodecPrivate,
        numberOfChannels: __privateGet(this, _options).audio.numberOfChannels,
        sampleRate: __privateGet(this, _options).audio.sampleRate
      };
    }
  }
};
_generateMpeg4AudioSpecificConfig = new WeakSet();
generateMpeg4AudioSpecificConfig_fn = function(objectType, sampleRate, numberOfChannels) {
  let frequencyIndices = [96e3, 88200, 64e3, 48e3, 44100, 32e3, 24e3, 22050, 16e3, 12e3, 11025, 8e3, 7350];
  let frequencyIndex = frequencyIndices.indexOf(sampleRate);
  let channelConfig = numberOfChannels;
  let configBits = "";
  configBits += objectType.toString(2).padStart(5, "0");
  configBits += frequencyIndex.toString(2).padStart(4, "0");
  if (frequencyIndex === 15)
    configBits += sampleRate.toString(2).padStart(24, "0");
  configBits += channelConfig.toString(2).padStart(4, "0");
  let paddingLength = Math.ceil(configBits.length / 8) * 8;
  configBits = configBits.padEnd(paddingLength, "0");
  let configBytes = new Uint8Array(configBits.length / 8);
  for (let i = 0; i < configBits.length; i += 8) {
    configBytes[i / 8] = parseInt(configBits.slice(i, i + 8), 2);
  }
  return configBytes;
};
_createSampleForTrack = new WeakSet();
createSampleForTrack_fn = function(track, data, type, timestamp, duration, meta, compositionTimeOffset) {
  let presentationTimestampInSeconds = timestamp / 1e6;
  let decodeTimestampInSeconds = (timestamp - (compositionTimeOffset ?? 0)) / 1e6;
  let durationInSeconds = duration / 1e6;
  let adjusted = __privateMethod(this, _validateTimestamp, validateTimestamp_fn).call(this, presentationTimestampInSeconds, decodeTimestampInSeconds, track);
  presentationTimestampInSeconds = adjusted.presentationTimestamp;
  decodeTimestampInSeconds = adjusted.decodeTimestamp;
  if (meta?.decoderConfig) {
    if (track.info.decoderConfig === null) {
      track.info.decoderConfig = meta.decoderConfig;
    } else {
      Object.assign(track.info.decoderConfig, meta.decoderConfig);
    }
  }
  let sample = {
    presentationTimestamp: presentationTimestampInSeconds,
    decodeTimestamp: decodeTimestampInSeconds,
    duration: durationInSeconds,
    data,
    size: data.byteLength,
    type,
    // Will be refined once the next sample comes in
    timescaleUnitsToNextSample: intoTimescale(durationInSeconds, track.timescale)
  };
  return sample;
};
_addSampleToTrack = new WeakSet();
addSampleToTrack_fn = function(track, sample) {
  if (__privateGet(this, _options).fastStart !== "fragmented") {
    track.samples.push(sample);
  }
  const sampleCompositionTimeOffset = intoTimescale(sample.presentationTimestamp - sample.decodeTimestamp, track.timescale);
  if (track.lastTimescaleUnits !== null) {
    let timescaleUnits = intoTimescale(sample.decodeTimestamp, track.timescale, false);
    let delta = Math.round(timescaleUnits - track.lastTimescaleUnits);
    track.lastTimescaleUnits += delta;
    track.lastSample.timescaleUnitsToNextSample = delta;
    if (__privateGet(this, _options).fastStart !== "fragmented") {
      let lastTableEntry = last(track.timeToSampleTable);
      if (lastTableEntry.sampleCount === 1) {
        lastTableEntry.sampleDelta = delta;
        lastTableEntry.sampleCount++;
      } else if (lastTableEntry.sampleDelta === delta) {
        lastTableEntry.sampleCount++;
      } else {
        lastTableEntry.sampleCount--;
        track.timeToSampleTable.push({
          sampleCount: 2,
          sampleDelta: delta
        });
      }
      const lastCompositionTimeOffsetTableEntry = last(track.compositionTimeOffsetTable);
      if (lastCompositionTimeOffsetTableEntry.sampleCompositionTimeOffset === sampleCompositionTimeOffset) {
        lastCompositionTimeOffsetTableEntry.sampleCount++;
      } else {
        track.compositionTimeOffsetTable.push({
          sampleCount: 1,
          sampleCompositionTimeOffset
        });
      }
    }
  } else {
    track.lastTimescaleUnits = 0;
    if (__privateGet(this, _options).fastStart !== "fragmented") {
      track.timeToSampleTable.push({
        sampleCount: 1,
        sampleDelta: intoTimescale(sample.duration, track.timescale)
      });
      track.compositionTimeOffsetTable.push({
        sampleCount: 1,
        sampleCompositionTimeOffset
      });
    }
  }
  track.lastSample = sample;
  let beginNewChunk = false;
  if (!track.currentChunk) {
    beginNewChunk = true;
  } else {
    let currentChunkDuration = sample.presentationTimestamp - track.currentChunk.startTimestamp;
    if (__privateGet(this, _options).fastStart === "fragmented") {
      let mostImportantTrack = __privateGet(this, _videoTrack) ?? __privateGet(this, _audioTrack);
      const chunkDuration = __privateGet(this, _options).minFragmentDuration ?? 1;
      if (track === mostImportantTrack && sample.type === "key" && currentChunkDuration >= chunkDuration) {
        beginNewChunk = true;
        __privateMethod(this, _finalizeFragment, finalizeFragment_fn).call(this);
      }
    } else {
      beginNewChunk = currentChunkDuration >= 0.5;
    }
  }
  if (beginNewChunk) {
    if (track.currentChunk) {
      __privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, track);
    }
    track.currentChunk = {
      startTimestamp: sample.presentationTimestamp,
      samples: []
    };
  }
  track.currentChunk.samples.push(sample);
};
_validateTimestamp = new WeakSet();
validateTimestamp_fn = function(presentationTimestamp, decodeTimestamp, track) {
  const strictTimestampBehavior = __privateGet(this, _options).firstTimestampBehavior === "strict";
  const noLastDecodeTimestamp = track.lastDecodeTimestamp === -1;
  const timestampNonZero = decodeTimestamp !== 0;
  if (strictTimestampBehavior && noLastDecodeTimestamp && timestampNonZero) {
    throw new Error(
      `The first chunk for your media track must have a timestamp of 0 (received DTS=${decodeTimestamp}).Non-zero first timestamps are often caused by directly piping frames or audio data from a MediaStreamTrack into the encoder. Their timestamps are typically relative to the age of thedocument, which is probably what you want.

If you want to offset all timestamps of a track such that the first one is zero, set firstTimestampBehavior: 'offset' in the options.
`
    );
  } else if (__privateGet(this, _options).firstTimestampBehavior === "offset" || __privateGet(this, _options).firstTimestampBehavior === "cross-track-offset") {
    if (track.firstDecodeTimestamp === void 0) {
      track.firstDecodeTimestamp = decodeTimestamp;
    }
    let baseDecodeTimestamp;
    if (__privateGet(this, _options).firstTimestampBehavior === "offset") {
      baseDecodeTimestamp = track.firstDecodeTimestamp;
    } else {
      baseDecodeTimestamp = Math.min(
        __privateGet(this, _videoTrack)?.firstDecodeTimestamp ?? Infinity,
        __privateGet(this, _audioTrack)?.firstDecodeTimestamp ?? Infinity
      );
    }
    decodeTimestamp -= baseDecodeTimestamp;
    presentationTimestamp -= baseDecodeTimestamp;
  }
  if (decodeTimestamp < track.lastDecodeTimestamp) {
    throw new Error(
      `Timestamps must be monotonically increasing (DTS went from ${track.lastDecodeTimestamp * 1e6} to ${decodeTimestamp * 1e6}).`
    );
  }
  track.lastDecodeTimestamp = decodeTimestamp;
  return { presentationTimestamp, decodeTimestamp };
};
_finalizeCurrentChunk = new WeakSet();
finalizeCurrentChunk_fn = function(track) {
  if (__privateGet(this, _options).fastStart === "fragmented") {
    throw new Error("Can't finalize individual chunks if 'fastStart' is set to 'fragmented'.");
  }
  if (!track.currentChunk)
    return;
  track.finalizedChunks.push(track.currentChunk);
  __privateGet(this, _finalizedChunks).push(track.currentChunk);
  if (track.compactlyCodedChunkTable.length === 0 || last(track.compactlyCodedChunkTable).samplesPerChunk !== track.currentChunk.samples.length) {
    track.compactlyCodedChunkTable.push({
      firstChunk: track.finalizedChunks.length,
      // 1-indexed
      samplesPerChunk: track.currentChunk.samples.length
    });
  }
  if (__privateGet(this, _options).fastStart === "in-memory") {
    track.currentChunk.offset = 0;
    return;
  }
  track.currentChunk.offset = __privateGet(this, _writer).pos;
  for (let sample of track.currentChunk.samples) {
    __privateGet(this, _writer).write(sample.data);
    sample.data = null;
  }
  __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
};
_finalizeFragment = new WeakSet();
finalizeFragment_fn = function(flushStreamingWriter = true) {
  if (__privateGet(this, _options).fastStart !== "fragmented") {
    throw new Error("Can't finalize a fragment unless 'fastStart' is set to 'fragmented'.");
  }
  let tracks = [__privateGet(this, _videoTrack), __privateGet(this, _audioTrack)].filter((track) => track && track.currentChunk);
  if (tracks.length === 0)
    return;
  let fragmentNumber = __privateWrapper(this, _nextFragmentNumber)._++;
  if (fragmentNumber === 1) {
    let movieBox = moov(tracks, __privateGet(this, _creationTime), true);
    __privateGet(this, _writer).writeBox(movieBox);
  }
  let moofOffset = __privateGet(this, _writer).pos;
  let moofBox = moof(fragmentNumber, tracks);
  __privateGet(this, _writer).writeBox(moofBox);
  {
    let mdatBox = mdat(false);
    let totalTrackSampleSize = 0;
    for (let track of tracks) {
      for (let sample of track.currentChunk.samples) {
        totalTrackSampleSize += sample.size;
      }
    }
    let mdatSize = __privateGet(this, _writer).measureBox(mdatBox) + totalTrackSampleSize;
    if (mdatSize >= 2 ** 32) {
      mdatBox.largeSize = true;
      mdatSize = __privateGet(this, _writer).measureBox(mdatBox) + totalTrackSampleSize;
    }
    mdatBox.size = mdatSize;
    __privateGet(this, _writer).writeBox(mdatBox);
  }
  for (let track of tracks) {
    track.currentChunk.offset = __privateGet(this, _writer).pos;
    track.currentChunk.moofOffset = moofOffset;
    for (let sample of track.currentChunk.samples) {
      __privateGet(this, _writer).write(sample.data);
      sample.data = null;
    }
  }
  let endPos = __privateGet(this, _writer).pos;
  __privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(moofBox));
  let newMoofBox = moof(fragmentNumber, tracks);
  __privateGet(this, _writer).writeBox(newMoofBox);
  __privateGet(this, _writer).seek(endPos);
  for (let track of tracks) {
    track.finalizedChunks.push(track.currentChunk);
    __privateGet(this, _finalizedChunks).push(track.currentChunk);
    track.currentChunk = null;
  }
  if (flushStreamingWriter) {
    __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
  }
};
_maybeFlushStreamingTargetWriter = new WeakSet();
maybeFlushStreamingTargetWriter_fn = function() {
  if (__privateGet(this, _writer) instanceof StreamTargetWriter) {
    __privateGet(this, _writer).flush();
  }
};
_ensureNotFinalized = new WeakSet();
ensureNotFinalized_fn = function() {
  if (__privateGet(this, _finalized)) {
    throw new Error("Cannot add new video or audio chunks after the file has been finalized.");
  }
};

const WORKER_STR = function (urlBase) {
    const initLodepng = () => {
        return new Promise((resolve) => {
            self.importScripts(`${urlBase}/lodepng.js`);
            resolve(self.lodepng({
                locateFile: () => `${urlBase}/lodepng.wasm`
            }));
        });
    };
    const compress = (lodepng, pixels, width, height) => {
        const resultDataPtrPtr = lodepng._malloc(4);
        const resultSizePtr = lodepng._malloc(4);
        const imageData = lodepng._malloc(width * height * 4);
        // copy pixels into wasm memory
        for (let y = 0; y < height; ++y) {
            let soff = y * width;
            let doff = imageData / 4 + (height - 1 - y) * width;
            for (let x = 0; x < width; ++x) {
                lodepng.HEAPU32[doff++] = pixels[soff++];
            }
        }
        // invoke compress
        lodepng._lodepng_encode32(resultDataPtrPtr, resultSizePtr, imageData, width, height);
        // read results
        const result = lodepng.HEAPU8.slice(lodepng.HEAPU32[resultDataPtrPtr / 4], lodepng.HEAPU32[resultDataPtrPtr / 4] + lodepng.HEAPU32[resultSizePtr / 4]);
        lodepng._free(resultDataPtrPtr);
        lodepng._free(resultSizePtr);
        lodepng._free(imageData);
        return result;
    };
    initLodepng()
        .then((lodepng) => {
        self.onmessage = (message) => {
            const { data } = message;
            const { token, pixels, width, height } = data;
            // compress
            const result = compress(lodepng, pixels, width, height);
            // send result
            self.postMessage({ token, result }, undefined, [result.buffer]);
        };
        self.postMessage('ready');
    });
}.toString();
class ExternalPromise {
    resolver;
    promise;
    constructor() {
        this.promise = new Promise((resolve) => {
            this.resolver = resolve;
        });
    }
}
class PngCompressor {
    compress;
    constructor() {
        const urlBase = new URL('static/lib/lodepng', document.baseURI);
        const workerBlob = new Blob([`(${WORKER_STR})('${urlBase}')\n\n`], {
            type: 'application/javascript'
        });
        const worker = new Worker(URL.createObjectURL(workerBlob));
        const workerReady = new ExternalPromise();
        const results = new Map();
        let token = 1;
        worker.addEventListener('message', (message) => {
            if (message.data === 'ready') {
                workerReady.resolver();
            }
            else {
                const { token, result } = message.data;
                results.get(token)(result);
                results.delete(token);
            }
        });
        this.compress = async (pixels, width, height) => {
            // ensure worker is ready
            await workerReady.promise;
            // create result promise instance
            const resultReady = new ExternalPromise();
            results.set(token, resultReady.resolver);
            // compress
            worker.postMessage({ token, pixels, width, height }, [pixels.buffer]);
            token++;
            return await resultReady.promise;
        };
    }
}

const removeExtension = (filename) => {
    return filename.substring(0, filename.length - path$1.getExtension(filename).length);
};
const downloadFile = (arrayBuffer, filename) => {
    const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
    const url = window.URL.createObjectURL(blob);
    const el = document.createElement('a');
    el.download = filename;
    el.href = url;
    el.click();
    window.URL.revokeObjectURL(url);
};
const registerRenderEvents = (scene, events) => {
    let compressor;
    // wait for postrender to fire
    const postRender = () => {
        return new Promise((resolve, reject) => {
            const handle = scene.events.on('postrender', () => {
                handle.off();
                try {
                    resolve(true);
                }
                catch (error) {
                    reject(error);
                }
            });
        });
    };
    events.function('render.image', async (imageSettings) => {
        events.fire('startSpinner');
        try {
            const { width, height, transparentBg, showDebug } = imageSettings;
            const bgClr = events.invoke('bgClr');
            // start rendering to offscreen buffer only
            scene.camera.startOffscreenMode(width, height);
            scene.camera.renderOverlays = showDebug;
            if (!transparentBg) {
                scene.camera.entity.camera.clearColor.copy(bgClr);
            }
            // render the next frame
            scene.forceRender = true;
            // for render to finish
            await postRender();
            // cpu-side buffer to read pixels into
            const data = new Uint8Array(width * height * 4);
            const { renderTarget } = scene.camera.entity.camera;
            const { colorBuffer } = renderTarget;
            // read the rendered frame
            await colorBuffer.read(0, 0, width, height, { renderTarget, data });
            // the render buffer contains premultiplied alpha. so apply background color.
            if (!transparentBg) {
                // @ts-ignore
                const pixels = new Uint8ClampedArray(data.buffer);
                const { r, g, b } = bgClr;
                for (let i = 0; i < pixels.length; i += 4) {
                    const a = 255 - pixels[i + 3];
                    pixels[i + 0] += r * a;
                    pixels[i + 1] += g * a;
                    pixels[i + 2] += b * a;
                    pixels[i + 3] = 255;
                }
            }
            // construct the png compressor
            if (!compressor) {
                compressor = new PngCompressor();
            }
            const arrayBuffer = await compressor.compress(new Uint32Array(data.buffer), colorBuffer.width, colorBuffer.height);
            // construct filename
            const selected = events.invoke('selection');
            const filename = `${removeExtension(selected?.name ?? 'SuperSplat')}-image.png`;
            // download
            downloadFile(arrayBuffer, filename);
            return true;
        }
        catch (error) {
            await events.invoke('showPopup', {
                type: 'error',
                header: localize('render.failed'),
                message: `'${error.message ?? error}'`
            });
        }
        finally {
            scene.camera.endOffscreenMode();
            scene.camera.renderOverlays = true;
            scene.camera.entity.camera.clearColor.set(0, 0, 0, 0);
            events.fire('stopSpinner');
        }
    });
    events.function('render.video', async (videoSettings) => {
        events.fire('progressStart', localize('render.render-video'));
        try {
            const { startFrame, endFrame, frameRate, width, height, bitrate, transparentBg, showDebug } = videoSettings;
            const muxer = new Muxer({
                target: new ArrayBufferTarget(),
                video: {
                    codec: 'avc',
                    width,
                    height
                },
                fastStart: 'in-memory',
                firstTimestampBehavior: 'offset'
            });
            const encoder = new VideoEncoder({
                output: (chunk, meta) => {
                    muxer.addVideoChunk(chunk, meta);
                },
                error: (error) => {
                    console.log(error);
                }
            });
            encoder.configure({
                codec: height < 1080 ? 'avc1.420028' : 'avc1.640033', // H.264 profile low : high
                width,
                height,
                bitrate
            });
            // start rendering to offscreen buffer only
            scene.camera.startOffscreenMode(width, height);
            scene.camera.renderOverlays = showDebug;
            if (!transparentBg) {
                scene.camera.entity.camera.clearColor.copy(events.invoke('bgClr'));
            }
            scene.lockedRenderMode = true;
            // cpu-side buffer to read pixels into
            const data = new Uint8Array(width * height * 4);
            const line = new Uint8Array(width * 4);
            // get the list of visible splats
            const splats = scene.getElementsByType(ElementType.splat).filter(splat => splat.visible);
            // prepare the frame for rendering
            const prepareFrame = async (frameTime) => {
                events.fire('timeline.time', frameTime);
                // manually update the camera so position and rotation are correct
                scene.camera.onUpdate(0);
                // wait for sorting to complete
                await Promise.all(splats.map((splat) => {
                    // create a promise for each splat that will resolve upon sorting complete
                    return new Promise((resolve) => {
                        const { instance } = splat.entity.gsplat;
                        // listen for the sorter to complete
                        const handle = instance.sorter.on('updated', () => {
                            handle.off();
                            resolve();
                        });
                        // manually invoke sort because internally the engine sorts after render the
                        // scene call is made.
                        instance.sort(scene.camera.entity);
                        // in cases where the camera does not move between frames the sorter won't run
                        // and we need a timeout instead. this is a hack - the engine should allow us to
                        // know whether the sorter is running or not.
                        setTimeout(() => {
                            resolve();
                        }, 1000);
                    });
                }));
            };
            // capture the current video frame
            const captureFrame = async (frameTime) => {
                const { renderTarget } = scene.camera.entity.camera;
                const { colorBuffer } = renderTarget;
                // read the rendered frame
                await colorBuffer.read(0, 0, width, height, { renderTarget, data });
                // flip the buffer vertically
                for (let y = 0; y < height / 2; y++) {
                    const top = y * width * 4;
                    const bottom = (height - y - 1) * width * 4;
                    line.set(data.subarray(top, top + width * 4));
                    data.copyWithin(top, bottom, bottom + width * 4);
                    data.set(line, bottom);
                }
                // construct the video frame
                const videoFrame = new VideoFrame(data, {
                    format: 'RGBA',
                    codedWidth: width,
                    codedHeight: height,
                    timestamp: Math.floor(1e6 * frameTime),
                    duration: Math.floor(1e6 / frameRate)
                });
                encoder.encode(videoFrame);
                videoFrame.close();
            };
            const animFrameRate = events.invoke('timeline.frameRate');
            const duration = (endFrame - startFrame) / animFrameRate;
            for (let frameTime = 0; frameTime <= duration; frameTime += 1.0 / frameRate) {
                // special case the first frame
                await prepareFrame(startFrame + frameTime * animFrameRate);
                // render a frame
                scene.lockedRender = true;
                // wait for render to finish
                await postRender();
                // wait for capture
                await captureFrame(frameTime);
                events.fire('progressUpdate', {
                    text: localize('render.rendering'),
                    progress: 100 * frameTime / duration
                });
            }
            // Flush and finalize muxer
            await encoder.flush();
            muxer.finalize();
            // Download
            downloadFile(muxer.target.buffer, `${removeExtension(splats[0]?.name ?? 'SuperSplat')}-video.mp4`);
            // Free resources
            encoder.close();
            return true;
        }
        catch (error) {
            await events.invoke('showPopup', {
                type: 'error',
                header: localize('render.failed'),
                message: `'${error.message ?? error}'`
            });
        }
        finally {
            scene.camera.endOffscreenMode();
            scene.camera.renderOverlays = true;
            scene.camera.entity.camera.clearColor.set(0, 0, 0, 0);
            scene.lockedRenderMode = false;
            scene.forceRender = true; // camera likely moved, finish with normal render
            events.fire('progressEnd');
        }
    });
};

const vertexShader$9 = /* glsl*/ `
#include "gsplatCommonVS"

uniform sampler2D splatState;

uniform vec4 selectedClr;
uniform vec4 lockedClr;

uniform vec3 clrOffset;
uniform vec4 clrScale;

varying mediump vec3 texCoordIsLocked;          // store locked flat in z
varying mediump vec4 color;

#if PICK_PASS
    uniform uint pickMode;                      // 0: add, 1: remove, 2: set
#endif

mediump vec4 discardVec = vec4(0.0, 0.0, 2.0, 1.0);

uniform float saturation;

vec3 applySaturation(vec3 color) {
    vec3 grey = vec3(dot(color, vec3(0.299, 0.587, 0.114)));
    return grey + (color - grey) * saturation;
}

void main(void) {
    // read gaussian details
    SplatSource source;
    if (!initSource(source)) {
        gl_Position = discardVec;
        return;
    }

    // get per-gaussian edit state, discard if deleted
    uint vertexState = uint(texelFetch(splatState, source.uv, 0).r * 255.0 + 0.5) & 7u;

    #if OUTLINE_PASS
        if (vertexState != 1u) {
            gl_Position = discardVec;
            return;
        }
    #elif UNDERLAY_PASS
        if (vertexState != 1u) {
            gl_Position = discardVec;
            return;
        }
    #elif PICK_PASS
        if (pickMode == 0u) {
            // add: skip deleted, locked and selected splats
            if (vertexState != 0u) {
                gl_Position = discardVec;
                return;
            }
        } else if (pickMode == 1u) {
            // remove: skip deleted, locked and unselected splats
            if (vertexState != 1u) {
                gl_Position = discardVec;
                return;
            }
        } else {
            // set: skip deleted and locked splats
            if ((vertexState & 6u) != 0u) {
                gl_Position = discardVec;
                return;
            }
        }
    #else
        if ((vertexState & 4u) != 0u) {
            gl_Position = discardVec;
            return;
        }
    #endif

    // get center
    vec3 modelCenter = readCenter(source);

    SplatCenter center;
    if (!initCenter(source, modelCenter, center)) {
        gl_Position = discardVec;
        return;
    }

    SplatCorner corner;
    if (!initCorner(source, center, corner)) {
        gl_Position = discardVec;
        return;
    }

    gl_Position = center.proj + vec4(corner.offset, 0.0, 0.0);

    // store texture coord and locked state
    texCoordIsLocked = vec3(corner.uv, (vertexState & 2u) != 0u ? 1.0 : 0.0);

    #if UNDERLAY_PASS
        color = readColor(source);
        color.xyz = mix(color.xyz, selectedClr.xyz * 0.2, selectedClr.a) * selectedClr.a;
    #elif PICK_PASS
        uvec3 bits = (uvec3(source.id) >> uvec3(0u, 8u, 16u)) & uvec3(255u);
        color = vec4(vec3(bits) / 255.0, readColor(source).a);
    // handle splat color
    #elif FORWARD_PASS
        // read color
        color = readColor(source);

        // evaluate spherical harmonics
        #if SH_BANDS > 0
        // calculate the model-space view direction
            vec3 dir = normalize(center.view * mat3(center.modelView));

            // read sh coefficients
            vec3 sh[SH_COEFFS];
            float scale;
            readSHData(source, sh, scale);

            // evaluate
            color.xyz += evalSH(sh, dir) * scale;
        #endif

        // apply tint/brightness
        color = color * clrScale + vec4(clrOffset, 0.0);

        // apply saturation
        color.xyz = applySaturation(color.xyz);

        // don't allow out-of-range alpha
        color.a = clamp(color.a, 0.0, 1.0);

        // apply tonemapping
        color = vec4(prepareOutputFromGamma(max(color.xyz, 0.0)), color.w);

        // apply locked/selected colors
        if ((vertexState & 2u) != 0u) {
            // locked
            color *= lockedClr;
        } else if ((vertexState & 1u) != 0u) {
            // selected
            color.xyz = mix(color.xyz, selectedClr.xyz * 0.8, selectedClr.a);
        }
    
    #endif
}
`;
const fragmentShader$9 = /* glsl*/ `
varying mediump vec3 texCoordIsLocked;
varying mediump vec4 color;

uniform int mode;               // 0: centers, 1: rings
uniform float pickerAlpha;
uniform float ringSize;

void main(void) {
    mediump float A = dot(texCoordIsLocked.xy, texCoordIsLocked.xy);

    if (A > 1.0) {
        discard;
    }

    #if OUTLINE_PASS
        gl_FragColor = vec4(1.0, 1.0, 1.0, mode == 0 ? exp(-A * 4.0) * color.a : 1.0);
    #else
        mediump float alpha = exp(-A * 4.0) * color.a;

        #ifdef PICK_PASS
            if (alpha < pickerAlpha) {
                discard;
            }
            gl_FragColor = vec4(color.xyz, 0.0);
        #else
            if (texCoordIsLocked.z == 0.0 && ringSize > 0.0) {
                // rings mode
                if (A < 1.0 - ringSize) {
                    alpha = max(0.05, alpha);
                } else {
                    alpha = 0.6;
                }
            }

            gl_FragColor = vec4(color.xyz * alpha, alpha);
        #endif
    #endif
}
`;
const gsplatCenter = /* glsl*/ `
uniform mat4 matrix_model;
uniform mat4 matrix_view;
uniform mat4 matrix_projection;

uniform highp usampler2D splatTransform;        // per-splat index into transform palette
uniform sampler2D transformPalette;             // palette of transform matrices

mat4 applyPaletteTransform(SplatSource source, mat4 model) {
    uint transformIndex = texelFetch(splatTransform, source.uv, 0).r;
    if (transformIndex == 0u) {
        return model;
    }

    // read transform matrix
    int u = int(transformIndex % 512u) * 3;
    int v = int(transformIndex / 512u);

    mat4 t;
    t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
    t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
    t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
    t[3] = vec4(0.0, 0.0, 0.0, 1.0);

    return model * transpose(t);
}

// project the model space gaussian center to view and clip space
bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
    mat4 modelView = matrix_view * applyPaletteTransform(source, matrix_model);
    vec4 centerView = modelView * vec4(modelCenter, 1.0);

    // early out if splat is behind the camera
    if (centerView.z > 0.0) {
        return false;
    }

    vec4 centerProj = matrix_projection * centerView;

    // ensure gaussians are not clipped by camera near and far
    centerProj.z = clamp(centerProj.z, -abs(centerProj.w), abs(centerProj.w));

    center.view = centerView.xyz / centerView.w;
    center.proj = centerProj;
    center.projMat00 = matrix_projection[0][0];
    center.modelView = modelView;
    return true;
}
`;

// mapping from Mat4 to transposed 3x4 matrix
const idx = [
    0, 4, 8, 12,
    1, 5, 9, 13,
    2, 6, 10, 14
];
// texture data stores 512 matrices per row: 512 * 3 * 4 (rgba) floats
const width = 512 * 3;
// wraps a palette of transform data. transforms are stored as 3x4 (non-perspective)
// matrices
class TransformPalette {
    getTransform;
    setTransform;
    alloc;
    free;
    texture;
    constructor(device, initialSize = 4096) {
        let texture;
        let data;
        // reallocate the storage texture and copy over old data
        const realloc = (width, height) => {
            const newTexture = new Texture(device, {
                name: 'transformPalette',
                width,
                height,
                format: PIXELFORMAT_RGBA32F,
                mipmaps: false,
                addressU: ADDRESS_CLAMP_TO_EDGE,
                addressV: ADDRESS_CLAMP_TO_EDGE
            });
            const newData = newTexture.lock();
            newTexture.unlock();
            // copy over data if this is a realloc
            if (texture) {
                newData.set(data);
                texture.destroy();
            }
            texture = newTexture;
            data = newData;
        };
        this.getTransform = (index, transform) => {
            const dst = transform.data;
            for (let i = 0; i < 12; ++i) {
                dst[idx[i]] = data[index * 12 + i];
            }
        };
        this.setTransform = (index, transform) => {
            const src = transform.data;
            for (let i = 0; i < 12; ++i) {
                data[index * 12 + i] = src[idx[i]];
            }
            texture.upload();
        };
        // index of the next available matrix. index 0 is identity.
        let nextIdx = 1;
        // allocate one or more matrices from the palette, returns the index of the first matrix
        this.alloc = (num = 1) => {
            const result = nextIdx;
            while (nextIdx + num > data.length / 12) {
                realloc(width, texture.height * 2);
            }
            nextIdx += num;
            return result;
        };
        this.free = (num = 1) => {
            nextIdx -= num;
        };
        Object.defineProperty(this, 'texture', { get() {
                return texture;
            } });
        // allocate initial storage
        realloc(width, Math.ceil(initialSize / (width / 3)));
        // initialize first matrix to identity
        this.setTransform(0, Mat4.IDENTITY);
    }
}

const vec$1 = new Vec3();
const veca = new Vec3();
const vecb$1 = new Vec3();
const boundingPoints = [-1, 1].map((x) => {
    return [-1, 1].map((y) => {
        return [-1, 1].map((z) => {
            return [
                new Vec3(x, y, z), new Vec3(x * 0.75, y, z),
                new Vec3(x, y, z), new Vec3(x, y * 0.75, z),
                new Vec3(x, y, z), new Vec3(x, y, z * 0.75)
            ];
        });
    });
}).flat(3);
class Splat extends Element {
    asset;
    splatData;
    numSplats = 0;
    numDeleted = 0;
    numLocked = 0;
    numSelected = 0;
    entity;
    changedCounter = 0;
    stateTexture;
    transformTexture;
    selectionBoundStorage;
    localBoundStorage;
    worldBoundStorage;
    selectionBoundDirty = true;
    localBoundDirty = true;
    worldBoundDirty = true;
    _visible = true;
    transformPalette;
    selectionAlpha = 1;
    _name = '';
    _tintClr = new Color(1, 1, 1);
    _temperature = 0;
    _saturation = 1;
    _brightness = 0;
    _blackPoint = 0;
    _whitePoint = 1;
    _transparency = 1;
    rebuildMaterial;
    constructor(asset) {
        super(ElementType.splat);
        const splatResource = asset.resource;
        const splatData = splatResource.gsplatData;
        const { device } = splatResource;
        this._name = asset.file.filename;
        this.asset = asset;
        this.splatData = splatData;
        this.numSplats = splatData.numSplats;
        this.entity = new Entity('splatEntitiy');
        this.entity.setEulerAngles(0, 0, 180);
        this.entity.addComponent('gsplat', { asset });
        const instance = this.entity.gsplat.instance;
        // use custom render order distance calculation for splats
        instance.meshInstance.calculateSortDistance = (meshInstance, pos, dir) => {
            const bound = this.localBound;
            const mat = this.entity.getWorldTransform();
            let maxDist;
            for (let i = 0; i < 8; ++i) {
                vec$1.x = bound.center.x + bound.halfExtents.x * (i & 1 ? 1 : -1);
                vec$1.y = bound.center.y + bound.halfExtents.y * (i & 2 ? 1 : -1);
                vec$1.z = bound.center.z + bound.halfExtents.z * (i & 4 ? 1 : -1);
                mat.transformPoint(vec$1, vec$1);
                const dist = vec$1.sub(pos).dot(dir);
                if (i === 0 || dist > maxDist) {
                    maxDist = dist;
                }
            }
            return maxDist;
        };
        // added per-splat state channel
        // bit 1: selected
        // bit 2: deleted
        // bit 3: locked
        if (!this.splatData.getProp('state')) {
            this.splatData.getElement('vertex').properties.push({
                type: 'uchar',
                name: 'state',
                storage: new Uint8Array(this.splatData.numSplats),
                byteSize: 1
            });
        }
        // per-splat transform matrix
        this.splatData.getElement('vertex').properties.push({
            type: 'ushort',
            name: 'transform',
            storage: new Uint16Array(this.splatData.numSplats),
            byteSize: 2
        });
        const { width, height } = splatResource.colorTexture;
        // pack spherical harmonic data
        const createTexture = (name, format) => {
            return new Texture(device, {
                name: name,
                width: width,
                height: height,
                format: format,
                mipmaps: false,
                minFilter: FILTER_NEAREST,
                magFilter: FILTER_NEAREST,
                addressU: ADDRESS_CLAMP_TO_EDGE,
                addressV: ADDRESS_CLAMP_TO_EDGE
            });
        };
        // create the state texture
        this.stateTexture = createTexture('splatState', PIXELFORMAT_R8);
        this.transformTexture = createTexture('splatTransform', PIXELFORMAT_R16U);
        // create the transform palette
        this.transformPalette = new TransformPalette(device);
        // blend mode for splats
        new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
        this.rebuildMaterial = (bands) => {
            const { material } = instance;
            // material.blendState = blendState;
            const { glsl } = material.shaderChunks;
            glsl.set('gsplatVS', vertexShader$9);
            glsl.set('gsplatPS', fragmentShader$9);
            glsl.set('gsplatCenterVS', gsplatCenter);
            material.setDefine('SH_BANDS', `${Math.min(bands, instance.resource.shBands)}`);
            material.setParameter('splatState', this.stateTexture);
            material.setParameter('splatTransform', this.transformTexture);
            material.update();
        };
        this.selectionBoundStorage = new BoundingBox();
        this.localBoundStorage = instance.resource.aabb;
        // @ts-ignore
        this.worldBoundStorage = instance.meshInstance._aabb;
        // @ts-ignore
        instance.meshInstance._updateAabb = false;
        // when sort changes, re-render the scene
        instance.sorter.on('updated', () => {
            this.changedCounter++;
        });
    }
    destroy() {
        super.destroy();
        this.entity.destroy();
        this.asset.registry.remove(this.asset);
        this.asset.unload();
    }
    updateState(changedState = State.selected) {
        const state = this.splatData.getProp('state');
        // write state data to gpu texture
        const data = this.stateTexture.lock();
        data.set(state);
        this.stateTexture.unlock();
        let numSelected = 0;
        let numLocked = 0;
        let numDeleted = 0;
        for (let i = 0; i < state.length; ++i) {
            const s = state[i];
            if (s & State.deleted) {
                numDeleted++;
            }
            else if (s & State.locked) {
                numLocked++;
            }
            else if (s & State.selected) {
                numSelected++;
            }
        }
        this.numSplats = state.length - numDeleted;
        this.numLocked = numLocked;
        this.numSelected = numSelected;
        this.numDeleted = numDeleted;
        this.makeSelectionBoundDirty();
        // handle splats being added or removed
        if (changedState & State.deleted) {
            this.updateSorting();
        }
        this.scene.forceRender = true;
        this.scene.events.fire('splat.stateChanged', this);
    }
    updatePositions() {
        const data = this.scene.dataProcessor.calcPositions(this);
        // update the splat centers which are used for render-time sorting
        const state = this.splatData.getProp('state');
        const { sorter } = this.entity.gsplat.instance;
        const { centers } = sorter;
        for (let i = 0; i < this.splatData.numSplats; ++i) {
            if (state[i] === State.selected) {
                centers[i * 3 + 0] = data[i * 4];
                centers[i * 3 + 1] = data[i * 4 + 1];
                centers[i * 3 + 2] = data[i * 4 + 2];
            }
        }
        this.updateSorting();
        this.scene.forceRender = true;
        this.scene.events.fire('splat.positionsChanged', this);
    }
    updateSorting() {
        const state = this.splatData.getProp('state');
        this.makeLocalBoundDirty();
        let mapping;
        // create a sorter mapping to remove deleted splats
        if (this.numSplats !== state.length) {
            mapping = new Uint32Array(this.numSplats);
            let idx = 0;
            for (let i = 0; i < state.length; ++i) {
                if ((state[i] & State.deleted) === 0) {
                    mapping[idx++] = i;
                }
            }
        }
        // update sorting instance
        this.entity.gsplat.instance.sorter.setMapping(mapping);
    }
    get worldTransform() {
        return this.entity.getWorldTransform();
    }
    set name(newName) {
        if (newName !== this.name) {
            this._name = newName;
            this.scene.events.fire('splat.name', this);
        }
    }
    get name() {
        return this._name;
    }
    get filename() {
        return this.asset.file.filename;
    }
    calcSplatWorldPosition(splatId, result) {
        if (splatId >= this.splatData.numSplats) {
            return false;
        }
        // use centers data, which are updated when edits occur
        const { sorter } = this.entity.gsplat.instance;
        const { centers } = sorter;
        result.set(centers[splatId * 3 + 0], centers[splatId * 3 + 1], centers[splatId * 3 + 2]);
        this.worldTransform.transformPoint(result, result);
        return true;
    }
    add() {
        // add the entity to the scene
        this.scene.contentRoot.addChild(this.entity);
        this.scene.events.on('view.bands', this.rebuildMaterial, this);
        this.rebuildMaterial(this.scene.events.invoke('view.bands'));
        // we must update state in case the state data was loaded from ply
        this.updateState();
    }
    remove() {
        this.scene.events.off('view.bands', this.rebuildMaterial, this);
        this.scene.contentRoot.removeChild(this.entity);
        this.scene.boundDirty = true;
    }
    serialize(serializer) {
        serializer.packa(this.entity.getWorldTransform().data);
        serializer.pack(this.changedCounter);
        serializer.pack(this.visible);
        serializer.pack(this.tintClr.r, this.tintClr.g, this.tintClr.b);
        serializer.pack(this.temperature, this.saturation, this.brightness, this.blackPoint, this.whitePoint, this.transparency);
    }
    onPreRender() {
        const events = this.scene.events;
        const selected = this.scene.camera.renderOverlays && events.invoke('selection') === this;
        const cameraMode = events.invoke('camera.mode');
        const cameraOverlay = events.invoke('camera.overlay');
        // configure rings rendering
        const material = this.entity.gsplat.instance.material;
        material.setParameter('mode', cameraMode === 'rings' ? 1 : 0);
        material.setParameter('ringSize', (selected && cameraOverlay && cameraMode === 'rings') ? 0.04 : 0);
        const selectionAlpha = selected && !events.invoke('view.outlineSelection') ? this.selectionAlpha : 0;
        // configure colors
        const selectedClr = events.invoke('selectedClr');
        const unselectedClr = events.invoke('unselectedClr');
        const lockedClr = events.invoke('lockedClr');
        material.setParameter('selectedClr', [selectedClr.r, selectedClr.g, selectedClr.b, selectedClr.a * selectionAlpha]);
        material.setParameter('unselectedClr', [unselectedClr.r, unselectedClr.g, unselectedClr.b, unselectedClr.a]);
        material.setParameter('lockedClr', [lockedClr.r, lockedClr.g, lockedClr.b, lockedClr.a]);
        // combine black pointer, white point and brightness
        const offset = -this.blackPoint + this.brightness;
        const scale = 1 / (this.whitePoint - this.blackPoint);
        material.setParameter('clrOffset', [offset, offset, offset]);
        material.setParameter('clrScale', [
            scale * this.tintClr.r * (1 + this.temperature),
            scale * this.tintClr.g,
            scale * this.tintClr.b * (1 - this.temperature),
            this.transparency
        ]);
        material.setParameter('saturation', this.saturation);
        material.setParameter('transformPalette', this.transformPalette.texture);
        if (this.visible && selected) {
            // render bounding box
            if (events.invoke('camera.bound')) {
                const bound = this.localBound;
                const scale = new Mat4().setTRS(bound.center, Quat.IDENTITY, bound.halfExtents);
                scale.mul2(this.entity.getWorldTransform(), scale);
                for (let i = 0; i < boundingPoints.length / 2; i++) {
                    const a = boundingPoints[i * 2];
                    const b = boundingPoints[i * 2 + 1];
                    scale.transformPoint(a, veca);
                    scale.transformPoint(b, vecb$1);
                    this.scene.app.drawLine(veca, vecb$1, Color.WHITE, true, this.scene.debugLayer);
                }
            }
        }
        this.entity.enabled = this.visible;
        // Temp hack: override the splat viewport size because we're rendering to an offscreen
        // render target and the engine currently always takes the backbuffer size.
        // this workaround can be removed once https://github.com/playcanvas/engine/pull/7425 is
        // available
        const rt = this.scene.camera.entity.camera.renderTarget;
        this.entity.gsplat.instance.meshInstance.setParameter('viewport', [rt.width, rt.height]);
    }
    focalPoint() {
        // GSplatData has a function for calculating an weighted average of the splat positions
        // to get a focal point for the camera, but we use bound center instead
        return this.worldBound.center;
    }
    move(position, rotation, scale) {
        const entity = this.entity;
        if (position) {
            entity.setLocalPosition(position);
        }
        if (rotation) {
            entity.setLocalRotation(rotation);
        }
        if (scale) {
            entity.setLocalScale(scale);
        }
        this.makeWorldBoundDirty();
        this.scene.events.fire('splat.moved', this);
    }
    makeSelectionBoundDirty() {
        this.selectionBoundDirty = true;
        this.makeLocalBoundDirty();
    }
    makeLocalBoundDirty() {
        this.localBoundDirty = true;
        this.makeWorldBoundDirty();
    }
    makeWorldBoundDirty() {
        this.worldBoundDirty = true;
        this.scene.boundDirty = true;
    }
    // get the selection bound
    get selectionBound() {
        const selectionBound = this.selectionBoundStorage;
        if (this.selectionBoundDirty) {
            this.scene.dataProcessor.calcBound(this, selectionBound, true);
            this.selectionBoundDirty = false;
        }
        return selectionBound;
    }
    // get local space bound
    get localBound() {
        const localBound = this.localBoundStorage;
        if (this.localBoundDirty) {
            this.scene.dataProcessor.calcBound(this, localBound, false);
            this.localBoundDirty = false;
            this.entity.getWorldTransform().transformPoint(localBound.center, vec$1);
        }
        return localBound;
    }
    // get world space bound
    get worldBound() {
        const worldBound = this.worldBoundStorage;
        if (this.worldBoundDirty) {
            // calculate meshinstance aabb (transformed local bound)
            worldBound.setFromTransformedAabb(this.localBound, this.entity.getWorldTransform());
            // flag scene bound as dirty
            this.worldBoundDirty = false;
        }
        return worldBound;
    }
    set visible(value) {
        if (value !== this.visible) {
            this._visible = value;
            this.scene.events.fire('splat.visibility', this);
        }
    }
    get visible() {
        return this._visible;
    }
    set tintClr(value) {
        if (!this._tintClr.equals(value)) {
            this._tintClr.set(value.r, value.g, value.b);
            this.scene.events.fire('splat.tintClr', this);
        }
    }
    get tintClr() {
        return this._tintClr;
    }
    set temperature(value) {
        if (value !== this._temperature) {
            this._temperature = value;
            this.scene.events.fire('splat.temperature', this);
        }
    }
    get temperature() {
        return this._temperature;
    }
    set saturation(value) {
        if (value !== this._saturation) {
            this._saturation = value;
            this.scene.events.fire('splat.saturation', this);
        }
    }
    get saturation() {
        return this._saturation;
    }
    set brightness(value) {
        if (value !== this._brightness) {
            this._brightness = value;
            this.scene.events.fire('splat.brightness', this);
        }
    }
    get brightness() {
        return this._brightness;
    }
    set blackPoint(value) {
        if (value !== this._blackPoint) {
            this._blackPoint = value;
            this.scene.events.fire('splat.blackPoint', this);
        }
    }
    get blackPoint() {
        return this._blackPoint;
    }
    set whitePoint(value) {
        if (value !== this._whitePoint) {
            this._whitePoint = value;
            this.scene.events.fire('splat.whitePoint', this);
        }
    }
    get whitePoint() {
        return this._whitePoint;
    }
    set transparency(value) {
        if (value !== this._transparency) {
            this._transparency = value;
            this.scene.events.fire('splat.transparency', this);
        }
    }
    get transparency() {
        return this._transparency;
    }
    getPivot(mode, selection, result) {
        const { entity } = this;
        switch (mode) {
            case 'center':
                result.set(entity.getLocalPosition(), entity.getLocalRotation(), entity.getLocalScale());
                break;
            case 'boundCenter':
                entity.getLocalTransform().transformPoint((selection ? this.selectionBound : this.localBound).center, vec$1);
                result.set(vec$1, entity.getLocalRotation(), entity.getLocalScale());
                break;
        }
    }
    docSerialize() {
        const pack3 = (v) => [v.x, v.y, v.z];
        const pack4 = (q) => [q.x, q.y, q.z, q.w];
        const packC = (c) => [c.r, c.g, c.b, c.a];
        return {
            name: this.name,
            position: pack3(this.entity.getLocalPosition()),
            rotation: pack4(this.entity.getLocalRotation()),
            scale: pack3(this.entity.getLocalScale()),
            visible: this.visible,
            tintClr: packC(this.tintClr),
            temperature: this.temperature,
            saturation: this.saturation,
            brightness: this.brightness,
            blackPoint: this.blackPoint,
            whitePoint: this.whitePoint,
            transparency: this.transparency
        };
    }
    docDeserialize(doc) {
        const { name, position, rotation, scale, visible, tintClr, temperature, saturation, brightness, blackPoint, whitePoint, transparency } = doc;
        this.name = name;
        this.move(new Vec3(position), new Quat(rotation), new Vec3(scale));
        this.visible = visible;
        this.tintClr = new Color(tintClr[0], tintClr[1], tintClr[2], tintClr[3]);
        this.temperature = temperature ?? 0;
        this.saturation = saturation ?? 1;
        this.brightness = brightness;
        this.blackPoint = blackPoint;
        this.whitePoint = whitePoint;
        this.transparency = transparency;
    }
}

// ideally this function would stream data directly into GSplatData buffers.
// unfortunately the .splat file format has no header specifying total number
// of splats so filesize must be known in order to allocate the correct amount
// of memory.
const deserializeFromSSplat = (data) => {
    const totalSplats = data.byteLength / 32;
    const dataView = new DataView(data);
    const storage_x = new Float32Array(totalSplats);
    const storage_y = new Float32Array(totalSplats);
    const storage_z = new Float32Array(totalSplats);
    const storage_opacity = new Float32Array(totalSplats);
    const storage_rot_0 = new Float32Array(totalSplats);
    const storage_rot_1 = new Float32Array(totalSplats);
    const storage_rot_2 = new Float32Array(totalSplats);
    const storage_rot_3 = new Float32Array(totalSplats);
    const storage_f_dc_0 = new Float32Array(totalSplats);
    const storage_f_dc_1 = new Float32Array(totalSplats);
    const storage_f_dc_2 = new Float32Array(totalSplats);
    const storage_scale_0 = new Float32Array(totalSplats);
    const storage_scale_1 = new Float32Array(totalSplats);
    const storage_scale_2 = new Float32Array(totalSplats);
    const storage_state = new Uint8Array(totalSplats);
    const SH_C0 = 0.28209479177387814;
    let off;
    for (let i = 0; i < totalSplats; i++) {
        off = i * 32;
        storage_x[i] = dataView.getFloat32(off + 0, true);
        storage_y[i] = dataView.getFloat32(off + 4, true);
        storage_z[i] = dataView.getFloat32(off + 8, true);
        storage_scale_0[i] = Math.log(dataView.getFloat32(off + 12, true));
        storage_scale_1[i] = Math.log(dataView.getFloat32(off + 16, true));
        storage_scale_2[i] = Math.log(dataView.getFloat32(off + 20, true));
        storage_f_dc_0[i] = (dataView.getUint8(off + 24) / 255 - 0.5) / SH_C0;
        storage_f_dc_1[i] = (dataView.getUint8(off + 25) / 255 - 0.5) / SH_C0;
        storage_f_dc_2[i] = (dataView.getUint8(off + 26) / 255 - 0.5) / SH_C0;
        storage_opacity[i] = -Math.log(255 / dataView.getUint8(off + 27) - 1);
        storage_rot_0[i] = (dataView.getUint8(off + 28) - 128) / 128;
        storage_rot_1[i] = (dataView.getUint8(off + 29) - 128) / 128;
        storage_rot_2[i] = (dataView.getUint8(off + 30) - 128) / 128;
        storage_rot_3[i] = (dataView.getUint8(off + 31) - 128) / 128;
    }
    return new GSplatData([{
            name: 'vertex',
            count: totalSplats,
            properties: [
                { type: 'float', name: 'x', storage: storage_x, byteSize: 4 },
                { type: 'float', name: 'y', storage: storage_y, byteSize: 4 },
                { type: 'float', name: 'z', storage: storage_z, byteSize: 4 },
                { type: 'float', name: 'opacity', storage: storage_opacity, byteSize: 4 },
                { type: 'float', name: 'rot_0', storage: storage_rot_0, byteSize: 4 },
                { type: 'float', name: 'rot_1', storage: storage_rot_1, byteSize: 4 },
                { type: 'float', name: 'rot_2', storage: storage_rot_2, byteSize: 4 },
                { type: 'float', name: 'rot_3', storage: storage_rot_3, byteSize: 4 },
                { type: 'float', name: 'f_dc_0', storage: storage_f_dc_0, byteSize: 4 },
                { type: 'float', name: 'f_dc_1', storage: storage_f_dc_1, byteSize: 4 },
                { type: 'float', name: 'f_dc_2', storage: storage_f_dc_2, byteSize: 4 },
                { type: 'float', name: 'scale_0', storage: storage_scale_0, byteSize: 4 },
                { type: 'float', name: 'scale_1', storage: storage_scale_1, byteSize: 4 },
                { type: 'float', name: 'scale_2', storage: storage_scale_2, byteSize: 4 },
                { type: 'float', name: 'state', storage: storage_state, byteSize: 4 }
            ]
        }]);
};
let assetId = 0;
// handles loading gltf container assets
class AssetLoader {
    app;
    events;
    defaultAnisotropy;
    loadAllData = true;
    constructor(app, events, defaultAnisotropy) {
        this.app = app;
        this.events = events;
        this.defaultAnisotropy = defaultAnisotropy || 1;
    }
    loadPly(loadRequest) {
        if (!loadRequest.animationFrame) {
            this.events.fire('startSpinner');
        }
        let file;
        const isSog = loadRequest.filename.toLowerCase().endsWith('.sog');
        if (isSog) {
            const contents = loadRequest.contents;
            file = {
                url: contents ? URL.createObjectURL(contents) : loadRequest.url,
                filename: loadRequest.filename
            };
        }
        else {
            const contents = loadRequest.contents && (loadRequest.contents instanceof Response ? loadRequest.contents : new Response(loadRequest.contents));
            file = {
                // we must construct a unique url if contents is provided
                url: contents ? `local-asset-${assetId++}` : loadRequest.url ?? loadRequest.filename,
                filename: loadRequest.filename,
                contents
            };
        }
        const data = {
            // decompress data on load
            decompress: true,
            // disable morton re-ordering when loading animation frames
            reorder: !(loadRequest.animationFrame ?? false),
            mapUrl: loadRequest.mapUrl
        };
        const options = {
            mapUrl: loadRequest.mapUrl
        };
        return new Promise((resolve, reject) => {
            const asset = new Asset(loadRequest.filename || loadRequest.url, 'gsplat', 
            // @ts-ignore
            file, data, options);
            asset.on('load:data', (data) => {
                // support loading 2d splats by adding scale_2 property with almost 0 scale
                if (data instanceof GSplatData && data.getProp('scale_0') && data.getProp('scale_1') && !data.getProp('scale_2')) {
                    const scale2 = new Float32Array(data.numSplats).fill(Math.log(1e-6));
                    data.addProp('scale_2', scale2);
                    // place the new scale_2 property just after scale_1
                    const props = data.getElement('vertex').properties;
                    props.splice(props.findIndex((prop) => prop.name === 'scale_1') + 1, 0, props.splice(props.length - 1, 1)[0]);
                }
            });
            asset.on('load', () => {
                // check the PLY contains minimal set of we expect
                const required = [
                    'x', 'y', 'z',
                    'scale_0', 'scale_1', 'scale_2',
                    'rot_0', 'rot_1', 'rot_2', 'rot_3',
                    'f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity'
                ];
                const splatData = asset.resource.gsplatData;
                const missing = required.filter(x => !splatData.getProp(x));
                if (missing.length > 0) {
                    reject(new Error(`This file does not contain gaussian splatting data. The following properties are missing: ${missing.join(', ')}`));
                }
                else {
                    resolve(new Splat(asset));
                }
            });
            asset.on('error', (err) => {
                reject(err);
            });
            this.app.assets.add(asset);
            this.app.assets.load(asset);
        }).finally(() => {
            if (!loadRequest.animationFrame) {
                this.events.fire('stopSpinner');
            }
        });
    }
    async loadSplat(loadRequest) {
        this.events.fire('startSpinner');
        try {
            const contents = loadRequest.contents && (loadRequest.contents instanceof Response ? loadRequest.contents : new Response(loadRequest.contents));
            const response = await (contents ?? fetch(loadRequest.url || loadRequest.filename));
            if (!response || !response.ok || !response.body) {
                throw new Error('Failed to fetch splat data');
            }
            const arrayBuffer = await response.arrayBuffer();
            const gsplatData = deserializeFromSSplat(arrayBuffer);
            const asset = new Asset(loadRequest.filename || loadRequest.url, 'gsplat', {
                url: loadRequest.url,
                filename: loadRequest.filename
            });
            this.app.assets.add(asset);
            asset.resource = new GSplatResource(this.app.graphicsDevice, gsplatData);
            return new Splat(asset);
        }
        finally {
            this.events.fire('stopSpinner');
        }
    }
    loadModel(loadRequest) {
        const filename = (loadRequest.filename || loadRequest.url).toLowerCase();
        if (filename.endsWith('.splat')) {
            return this.loadSplat(loadRequest);
        }
        return this.loadPly(loadRequest);
    }
}

const fromWorldPoint = new Vec3();
const toWorldPoint = new Vec3();
const worldDiff = new Vec3();
// calculate the distance between two 2d points
const dist = (x0, y0, x1, y1) => Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2);
class PointerController {
    update;
    destroy;
    constructor(camera, target) {
        const orbit = (dx, dy) => {
            const azim = camera.azim - dx * camera.scene.config.controls.orbitSensitivity;
            const elev = camera.elevation - dy * camera.scene.config.controls.orbitSensitivity;
            camera.setAzimElev(azim, elev);
        };
        const pan = (x, y, dx, dy) => {
            // For panning to work at any zoom level, we use screen point to world projection
            // to work out how far we need to pan the pivotEntity in world space
            const c = camera.entity.camera;
            const distance = camera.distanceTween.value.distance * camera.sceneRadius / camera.fovFactor;
            c.screenToWorld(x, y, distance, fromWorldPoint);
            c.screenToWorld(x - dx, y - dy, distance, toWorldPoint);
            worldDiff.sub2(toWorldPoint, fromWorldPoint);
            worldDiff.add(camera.focalPoint);
            camera.setFocalPoint(worldDiff);
        };
        const zoom = (amount) => {
            camera.setDistance(camera.distance - (camera.distance * 0.999 + 0.001) * amount * camera.scene.config.controls.zoomSensitivity, 2);
        };
        // mouse state
        const buttons = [false, false, false];
        let x, y;
        // touch state
        let touches = [];
        let midx, midy, midlen;
        const pointerdown = (event) => {
            if (event.pointerType === 'mouse') {
                if (buttons.every(b => !b)) {
                    target.setPointerCapture(event.pointerId);
                }
                buttons[event.button] = true;
                x = event.offsetX;
                y = event.offsetY;
            }
            else if (event.pointerType === 'touch') {
                if (touches.length === 0) {
                    target.setPointerCapture(event.pointerId);
                }
                touches.push({
                    x: event.offsetX,
                    y: event.offsetY,
                    id: event.pointerId
                });
                if (touches.length === 2) {
                    midx = (touches[0].x + touches[1].x) * 0.5;
                    midy = (touches[0].y + touches[1].y) * 0.5;
                    midlen = dist(touches[0].x, touches[0].y, touches[1].x, touches[1].y);
                }
            }
        };
        const pointerup = (event) => {
            if (event.pointerType === 'mouse') {
                buttons[event.button] = false;
                if (buttons.every(b => !b)) {
                    target.releasePointerCapture(event.pointerId);
                }
            }
            else {
                touches = touches.filter(touch => touch.id !== event.pointerId);
                if (touches.length === 0) {
                    target.releasePointerCapture(event.pointerId);
                }
            }
        };
        const pointermove = (event) => {
            if (event.pointerType === 'mouse') {
                const dx = event.offsetX - x;
                const dy = event.offsetY - y;
                x = event.offsetX;
                y = event.offsetY;
                // right button can be used to orbit with ctrl key and to zoom with alt | meta key
                const mod = buttons[2] ?
                    (event.shiftKey || event.ctrlKey ? 'orbit' :
                        (event.altKey || event.metaKey ? 'zoom' : null)) :
                    null;
                if (mod === 'orbit' || (mod === null && buttons[0])) {
                    orbit(dx, dy);
                }
                else if (mod === 'zoom' || (mod === null && buttons[1])) {
                    zoom(dy * -0.02);
                }
                else if (mod === 'pan' || (mod === null && buttons[2])) {
                    pan(x, y, dx, dy);
                }
            }
            else {
                if (touches.length === 1) {
                    const touch = touches[0];
                    const dx = event.offsetX - touch.x;
                    const dy = event.offsetY - touch.y;
                    touch.x = event.offsetX;
                    touch.y = event.offsetY;
                    orbit(dx, dy);
                }
                else if (touches.length === 2) {
                    const touch = touches[touches.map(t => t.id).indexOf(event.pointerId)];
                    touch.x = event.offsetX;
                    touch.y = event.offsetY;
                    const mx = (touches[0].x + touches[1].x) * 0.5;
                    const my = (touches[0].y + touches[1].y) * 0.5;
                    const ml = dist(touches[0].x, touches[0].y, touches[1].x, touches[1].y);
                    pan(mx, my, (mx - midx), (my - midy));
                    zoom((ml - midlen) * 0.01);
                    midx = mx;
                    midy = my;
                    midlen = ml;
                }
            }
        };
        // fuzzy detection of mouse wheel events vs trackpad events
        const isMouseEvent = (deltaX, deltaY) => {
            return (Math.abs(deltaX) > 50 && deltaY === 0) ||
                (Math.abs(deltaY) > 50 && deltaX === 0) ||
                (deltaX === 0 && deltaY !== 0) && !Number.isInteger(deltaY);
        };
        const wheel = (event) => {
            const { deltaX, deltaY } = event;
            if (isMouseEvent(deltaX, deltaY)) {
                zoom(deltaY * -2e-3);
            }
            else if (event.ctrlKey || event.metaKey) {
                zoom(deltaY * -0.02);
            }
            else if (event.shiftKey) {
                pan(event.offsetX, event.offsetY, deltaX, deltaY);
            }
            else {
                orbit(deltaX, deltaY);
            }
            event.preventDefault();
        };
        // FIXME: safari sends canvas as target of dblclick event but chrome sends the target element
        const canvas = camera.scene.app.graphicsDevice.canvas;
        const dblclick = (event) => {
            if (event.target === target || event.target === canvas) {
                camera.pickFocalPoint(event.offsetX, event.offsetY);
            }
        };
        // key state
        const keys = {
            ArrowUp: 0,
            ArrowDown: 0,
            ArrowLeft: 0,
            ArrowRight: 0
        };
        const keydown = (event) => {
            if (keys.hasOwnProperty(event.key) && event.target === document.body) {
                keys[event.key] = event.shiftKey ? 10 : (event.ctrlKey || event.metaKey || event.altKey ? 0.1 : 1);
            }
        };
        const keyup = (event) => {
            if (keys.hasOwnProperty(event.key)) {
                keys[event.key] = 0;
            }
        };
        this.update = (deltaTime) => {
            const x = keys.ArrowRight - keys.ArrowLeft;
            const z = keys.ArrowDown - keys.ArrowUp;
            if (x || z) {
                const factor = deltaTime * camera.flySpeed;
                const worldTransform = camera.entity.getWorldTransform();
                const xAxis = worldTransform.getX().mulScalar(x * factor);
                const zAxis = worldTransform.getZ().mulScalar(z * factor);
                const p = camera.focalPoint.add(xAxis).add(zAxis);
                camera.setFocalPoint(p);
            }
        };
        let destroy = null;
        const wrap = (target, name, fn, options) => {
            const callback = (event) => {
                camera.scene.events.fire('camera.controller', name);
                fn(event);
            };
            target.addEventListener(name, callback, options);
            destroy = () => {
                destroy?.();
                target.removeEventListener(name, callback);
            };
        };
        wrap(target, 'pointerdown', pointerdown);
        wrap(target, 'pointerup', pointerup);
        wrap(target, 'pointermove', pointermove);
        wrap(target, 'wheel', wheel, { passive: false });
        wrap(target, 'dblclick', dblclick);
        wrap(document, 'keydown', keydown);
        wrap(document, 'keyup', keyup);
        this.destroy = destroy;
    }
}

// possible interpolation functions
const Interp = {
    sinosidal: (n) => Math.sin((n * Math.PI) / 2.0),
    quadratic: (n) => n * (2 - n),
    quartic: (n) => 1 - --n * n * n * n,
    quintic: (n) => Math.pow(n - 1, 5) + 1,
    vertebrae: (n) => -Math.pow((Math.cos(n * Math.PI) + 1) / 2, 2) + 1
};
class Ops {
    keys;
    constructor(value) {
        this.keys = Object.keys(value);
    }
    clone(obj) {
        const result = {};
        this.keys.forEach((key) => {
            result[key] = obj[key];
        });
        return result;
    }
    copy(target, source) {
        this.keys.forEach((key) => {
            target[key] = source[key];
        });
    }
    lerp(target, a, b, t) {
        this.keys.forEach((key) => {
            target[key] = a[key] + t * (b[key] - a[key]);
        });
    }
}
class TweenValue {
    ops;
    value;
    source;
    target;
    timer;
    transitionTime;
    constructor(value) {
        this.ops = new Ops(value);
        this.value = value;
        this.source = this.ops.clone(value);
        this.target = this.ops.clone(value);
        this.timer = 0;
        this.transitionTime = 0;
    }
    goto(target, transitionTime = 0.25) {
        if (transitionTime === 0) {
            this.ops.copy(this.value, target);
        }
        this.ops.copy(this.source, this.value);
        this.ops.copy(this.target, target);
        this.timer = 0;
        this.transitionTime = transitionTime;
    }
    update(deltaTime) {
        if (this.timer < this.transitionTime) {
            this.timer = Math.min(this.timer + deltaTime, this.transitionTime);
            this.ops.lerp(this.value, this.source, this.target, Interp.quintic(this.timer / this.transitionTime));
        }
        else {
            this.ops.copy(this.value, this.target);
        }
    }
}

// calculate the forward vector given azimuth and elevation
const calcForwardVec = (result, azim, elev) => {
    const ex = elev * math.DEG_TO_RAD;
    const ey = azim * math.DEG_TO_RAD;
    const s1 = Math.sin(-ex);
    const c1 = Math.cos(-ex);
    const s2 = Math.sin(-ey);
    const c2 = Math.cos(-ey);
    result.set(-c1 * s2, s1, c1 * c2);
};
// work globals
const forwardVec = new Vec3();
const cameraPosition = new Vec3();
const plane = new Plane();
const ray = new Ray();
const vec = new Vec3();
const vecb = new Vec3();
const va = new Vec3();
// modulo dealing with negative numbers
const mod = (n, m) => ((n % m) + m) % m;
class Camera extends Element {
    controller;
    entity;
    focalPointTween = new TweenValue({ x: 0, y: 0.5, z: 0 });
    azimElevTween = new TweenValue({ azim: 30, elev: -15 });
    distanceTween = new TweenValue({ distance: 1 });
    minElev = -90;
    maxElev = 90;
    sceneRadius = 1;
    flySpeed = 5;
    picker;
    workRenderTarget;
    // overridden target size
    targetSize = null;
    suppressFinalBlit = false;
    renderOverlays = true;
    updateCameraUniforms;
    constructor() {
        super(ElementType.camera);
        // create the camera entity
        this.entity = new Entity('Camera');
        this.entity.addComponent('camera');
        // NOTE: this call is needed for refraction effect to work correctly, but
        // it slows rendering and should only be made when required.
        // this.entity.camera.requestSceneColorMap(true);
    }
    // ortho
    set ortho(value) {
        if (value !== this.ortho) {
            this.entity.camera.projection = value ? PROJECTION_ORTHOGRAPHIC : PROJECTION_PERSPECTIVE;
            this.scene.events.fire('camera.ortho', value);
        }
    }
    get ortho() {
        return this.entity.camera.projection === PROJECTION_ORTHOGRAPHIC;
    }
    // fov
    set fov(value) {
        this.entity.camera.fov = value;
    }
    get fov() {
        return this.entity.camera.fov;
    }
    // tonemapping
    set tonemapping(value) {
        const mapping = {
            none: TONEMAP_NONE,
            linear: TONEMAP_LINEAR,
            neutral: TONEMAP_NEUTRAL,
            aces: TONEMAP_ACES,
            aces2: TONEMAP_ACES2,
            filmic: TONEMAP_FILMIC,
            hejl: TONEMAP_HEJL
        };
        const tvalue = mapping[value];
        if (tvalue !== undefined && tvalue !== this.entity.camera.toneMapping) {
            this.entity.camera.toneMapping = tvalue;
            this.scene.events.fire('camera.tonemapping', value);
        }
    }
    get tonemapping() {
        switch (this.entity.camera.toneMapping) {
            case TONEMAP_NONE: return 'none';
            case TONEMAP_LINEAR: return 'linear';
            case TONEMAP_NEUTRAL: return 'neutral';
            case TONEMAP_ACES: return 'aces';
            case TONEMAP_ACES2: return 'aces2';
            case TONEMAP_FILMIC: return 'filmic';
            case TONEMAP_HEJL: return 'hejl';
        }
        return 'none';
    }
    // near clip
    set near(value) {
        this.entity.camera.nearClip = value;
    }
    get near() {
        return this.entity.camera.nearClip;
    }
    // far clip
    set far(value) {
        this.entity.camera.farClip = value;
    }
    get far() {
        return this.entity.camera.farClip;
    }
    // focal point
    get focalPoint() {
        const t = this.focalPointTween.target;
        return new Vec3(t.x, t.y, t.z);
    }
    // azimuth, elevation
    get azimElev() {
        return this.azimElevTween.target;
    }
    get azim() {
        return this.azimElev.azim;
    }
    get elevation() {
        return this.azimElev.elev;
    }
    get distance() {
        return this.distanceTween.target.distance;
    }
    setFocalPoint(point, dampingFactorFactor = 1) {
        this.focalPointTween.goto(point, dampingFactorFactor * this.scene.config.controls.dampingFactor);
    }
    setAzimElev(azim, elev, dampingFactorFactor = 1) {
        // clamp
        azim = mod(azim, 360);
        elev = Math.max(this.minElev, Math.min(this.maxElev, elev));
        const t = this.azimElevTween;
        t.goto({ azim, elev }, dampingFactorFactor * this.scene.config.controls.dampingFactor);
        // handle wraparound
        if (t.source.azim - azim < -180) {
            t.source.azim += 360;
        }
        else if (t.source.azim - azim > 180) {
            t.source.azim -= 360;
        }
        // return to perspective mode on rotation
        this.ortho = false;
    }
    setDistance(distance, dampingFactorFactor = 1) {
        const controls = this.scene.config.controls;
        // clamp
        distance = Math.max(controls.minZoom, Math.min(controls.maxZoom, distance));
        const t = this.distanceTween;
        t.goto({ distance }, dampingFactorFactor * controls.dampingFactor);
    }
    setPose(position, target, dampingFactorFactor = 1) {
        vec.sub2(target, position);
        const l = vec.length();
        const azim = Math.atan2(-vec.x / l, -vec.z / l) * math.RAD_TO_DEG;
        const elev = Math.asin(vec.y / l) * math.RAD_TO_DEG;
        this.setFocalPoint(target, dampingFactorFactor);
        this.setAzimElev(azim, elev, dampingFactorFactor);
        this.setDistance(l / this.sceneRadius * this.fovFactor, dampingFactorFactor);
    }
    // convert world to screen coordinate
    worldToScreen(world, screen) {
        this.entity.camera.worldToScreen(world, screen);
    }
    add() {
        this.scene.cameraRoot.addChild(this.entity);
        this.entity.camera.layers = this.entity.camera.layers.concat([
            this.scene.shadowLayer.id,
            this.scene.debugLayer.id,
            this.scene.gizmoLayer.id
        ]);
        if (this.scene.config.camera.debugRender) {
            this.entity.camera.setShaderPass(`debug_${this.scene.config.camera.debugRender}`);
        }
        const target = document.getElementById('canvas-container');
        this.controller = new PointerController(this, target);
        // apply scene config
        const config = this.scene.config;
        const controls = config.controls;
        // configure background
        this.entity.camera.clearColor.set(0, 0, 0, 0);
        this.minElev = (controls.minPolarAngle * 180) / Math.PI - 90;
        this.maxElev = (controls.maxPolarAngle * 180) / Math.PI - 90;
        // tonemapping
        this.scene.camera.entity.camera.toneMapping = {
            linear: TONEMAP_LINEAR,
            filmic: TONEMAP_FILMIC,
            hejl: TONEMAP_HEJL,
            aces: TONEMAP_ACES,
            aces2: TONEMAP_ACES2,
            neutral: TONEMAP_NEUTRAL
        }[config.camera.toneMapping];
        // exposure
        this.scene.app.scene.exposure = config.camera.exposure;
        this.fov = config.camera.fov;
        // initial camera position and orientation
        this.setAzimElev(controls.initialAzim, controls.initialElev, 0);
        this.setDistance(controls.initialZoom, 0);
        // picker
        const { width, height } = this.scene.targetSize;
        this.picker = new Picker(this.scene.app, width, height);
        // override buffer allocation to use our render target
        this.picker.allocateRenderTarget = () => { };
        this.picker.releaseRenderTarget = () => { };
        this.scene.events.on('scene.boundChanged', this.onBoundChanged, this);
        // prepare camera-specific uniforms
        this.updateCameraUniforms = () => {
            const device = this.scene.graphicsDevice;
            const entity = this.entity;
            const camera = entity.camera;
            const set = (name, vec) => {
                device.scope.resolve(name).setValue([vec.x, vec.y, vec.z]);
            };
            // get frustum corners in world space
            const points = camera.camera.getFrustumCorners(-100);
            const worldTransform = entity.getWorldTransform();
            for (let i = 0; i < points.length; i++) {
                worldTransform.transformPoint(points[i], points[i]);
            }
            // near
            if (camera.projection === PROJECTION_PERSPECTIVE) {
                // perspective
                set('near_origin', worldTransform.getTranslation());
                set('near_x', Vec3.ZERO);
                set('near_y', Vec3.ZERO);
            }
            else {
                // orthographic
                set('near_origin', points[3]);
                set('near_x', va.sub2(points[0], points[3]));
                set('near_y', va.sub2(points[2], points[3]));
            }
            // far
            set('far_origin', points[7]);
            set('far_x', va.sub2(points[4], points[7]));
            set('far_y', va.sub2(points[6], points[7]));
        };
    }
    remove() {
        this.controller.destroy();
        this.controller = null;
        this.entity.camera.layers = this.entity.camera.layers.filter(layer => layer !== this.scene.shadowLayer.id);
        this.scene.cameraRoot.removeChild(this.entity);
        // destroy doesn't exist on picker?
        // this.picker.destroy();
        this.picker = null;
        this.scene.events.off('scene.boundChanged', this.onBoundChanged, this);
    }
    // handle the scene's bound changing. the camera must be configured to render
    // the entire extents as well as possible.
    // also update the existing camera distance to maintain the current view
    onBoundChanged(bound) {
        const prevDistance = this.distanceTween.value.distance * this.sceneRadius;
        this.sceneRadius = Math.max(1e-03, bound.halfExtents.length());
        this.setDistance(prevDistance / this.sceneRadius, 0);
    }
    serialize(serializer) {
        serializer.packa(this.entity.getWorldTransform().data);
        serializer.pack(this.fov, this.tonemapping, this.entity.camera.renderTarget?.width, this.entity.camera.renderTarget?.height);
    }
    // handle the viewer canvas resizing
    rebuildRenderTargets() {
        const device = this.scene.graphicsDevice;
        const { width, height } = this.targetSize ?? this.scene.targetSize;
        const rt = this.entity.camera.renderTarget;
        if (rt && rt.width === width && rt.height === height) {
            return;
        }
        // out with the old
        if (rt) {
            rt.destroyTextureBuffers();
            rt.destroy();
            this.workRenderTarget.destroy();
            this.workRenderTarget = null;
        }
        const createTexture = (name, width, height, format) => {
            return new Texture(device, {
                name,
                width,
                height,
                format,
                mipmaps: false,
                minFilter: FILTER_NEAREST,
                magFilter: FILTER_NEAREST,
                addressU: ADDRESS_CLAMP_TO_EDGE,
                addressV: ADDRESS_CLAMP_TO_EDGE
            });
        };
        // in with the new
        const colorBuffer = createTexture('cameraColor', width, height, PIXELFORMAT_RGBA8);
        const depthBuffer = createTexture('cameraDepth', width, height, PIXELFORMAT_DEPTH);
        const renderTarget = new RenderTarget({
            colorBuffer,
            depthBuffer,
            flipY: false,
            autoResolve: false
        });
        this.entity.camera.renderTarget = renderTarget;
        this.entity.camera.horizontalFov = width > height;
        const workColorBuffer = createTexture('workColor', width, height, PIXELFORMAT_RGBA8);
        // create pick mode render target (reuse color buffer)
        this.workRenderTarget = new RenderTarget({
            colorBuffer: workColorBuffer,
            depth: false,
            autoResolve: false
        });
        // set picker render target
        this.picker.renderTarget = this.workRenderTarget;
        this.scene.events.fire('camera.resize', { width, height });
    }
    onUpdate(deltaTime) {
        // controller update
        this.controller.update(deltaTime);
        // update underlying values
        this.focalPointTween.update(deltaTime);
        this.azimElevTween.update(deltaTime);
        this.distanceTween.update(deltaTime);
        const azimElev = this.azimElevTween.value;
        const distance = this.distanceTween.value;
        calcForwardVec(forwardVec, azimElev.azim, azimElev.elev);
        cameraPosition.copy(forwardVec);
        cameraPosition.mulScalar(distance.distance * this.sceneRadius / this.fovFactor);
        cameraPosition.add(this.focalPointTween.value);
        this.entity.setLocalPosition(cameraPosition);
        this.entity.setLocalEulerAngles(azimElev.elev, azimElev.azim, 0);
        this.fitClippingPlanes(this.entity.getLocalPosition(), this.entity.forward);
        const { camera } = this.entity;
        camera.orthoHeight = this.distanceTween.value.distance * this.sceneRadius / this.fovFactor * (this.fov / 90) * (camera.horizontalFov ? this.scene.targetSize.height / this.scene.targetSize.width : 1);
        camera.camera._updateViewProjMat();
    }
    fitClippingPlanes(cameraPosition, forwardVec) {
        const bound = this.scene.bound;
        const boundRadius = bound.halfExtents.length();
        vec.sub2(bound.center, cameraPosition);
        const dist = vec.dot(forwardVec);
        if (dist > 0) {
            this.far = dist + boundRadius;
            // if camera is placed inside the sphere bound calculate near based far
            this.near = Math.max(1e-6, dist < boundRadius ? this.far / (1024 * 16) : dist - boundRadius);
        }
        else {
            // if the scene is behind the camera
            this.far = boundRadius * 2;
            this.near = this.far / (1024 * 16);
        }
    }
    onPreRender() {
        this.rebuildRenderTargets();
        this.updateCameraUniforms();
    }
    onPostRender() {
        const device = this.scene.graphicsDevice;
        const renderTarget = this.entity.camera.renderTarget;
        // resolve msaa buffer
        if (renderTarget.samples > 1) {
            renderTarget.resolve(true, false);
        }
        // copy render target
        if (!this.suppressFinalBlit) {
            device.copyRenderTarget(renderTarget, null, true, false);
        }
    }
    focus(options) {
        const getSplatFocalPoint = () => {
            for (const element of this.scene.elements) {
                if (element.type === ElementType.splat) {
                    const focalPoint = element.focalPoint?.();
                    if (focalPoint) {
                        return focalPoint;
                    }
                }
            }
        };
        const focalPoint = options ? options.focalPoint : (getSplatFocalPoint() ?? this.scene.bound.center);
        const focalRadius = options ? options.radius : this.scene.bound.halfExtents.length();
        const fdist = focalRadius / this.sceneRadius;
        this.setDistance(isFinite(fdist) ? fdist : 1, options?.speed ?? 0);
        this.setFocalPoint(focalPoint, options?.speed ?? 0);
    }
    get fovFactor() {
        // we set the fov of the longer axis. here we get the fov of the other (smaller) axis so framing
        // doesn't cut off the scene.
        const { width, height } = this.scene.targetSize;
        const aspect = (width && height) ? this.entity.camera.horizontalFov ? height / width : width / height : 1;
        const fov = 2 * Math.atan(Math.tan(this.fov * math.DEG_TO_RAD * 0.5) * aspect);
        return Math.sin(fov * 0.5);
    }
    // intersect the scene at the given screen coordinate and focus the camera on this location
    pickFocalPoint(screenX, screenY) {
        const scene = this.scene;
        const cameraPos = this.entity.getPosition();
        const target = scene.canvas;
        const sx = screenX / target.clientWidth * scene.targetSize.width;
        const sy = screenY / target.clientHeight * scene.targetSize.height;
        const splats = scene.getElementsByType(ElementType.splat);
        let closestD = 0;
        const closestP = new Vec3();
        let closestSplat = null;
        for (let i = 0; i < splats.length; ++i) {
            const splat = splats[i];
            this.pickPrep(splat, 'set');
            const pickId = this.pick(sx, sy);
            if (pickId !== -1) {
                splat.calcSplatWorldPosition(pickId, vec);
                // create a plane at the world position facing perpendicular to the camera
                plane.setFromPointNormal(vec, this.entity.forward);
                // create the pick ray in world space
                if (this.ortho) {
                    this.entity.camera.screenToWorld(screenX, screenY, -1, vec);
                    this.entity.camera.screenToWorld(screenX, screenY, 1.0, vecb);
                    vecb.sub(vec).normalize();
                    ray.set(vec, vecb);
                }
                else {
                    this.entity.camera.screenToWorld(screenX, screenY, 1.0, vec);
                    vec.sub(cameraPos).normalize();
                    ray.set(cameraPos, vec);
                }
                // find intersection
                if (plane.intersectsRay(ray, vec)) {
                    const distance = vecb.sub2(vec, ray.origin).length();
                    if (!closestSplat || distance < closestD) {
                        closestD = distance;
                        closestP.copy(vec);
                        closestSplat = splat;
                    }
                }
            }
        }
        if (closestSplat) {
            this.setFocalPoint(closestP);
            this.setDistance(closestD / this.sceneRadius * this.fovFactor);
            scene.events.fire('camera.focalPointPicked', {
                camera: this,
                splat: closestSplat,
                position: closestP
            });
        }
    }
    // pick mode
    // render picker contents
    pickPrep(splat, op) {
        const { width, height } = this.scene.targetSize;
        const worldLayer = this.scene.app.scene.layers.getLayerByName('World');
        const device = this.scene.graphicsDevice;
        const events = this.scene.events;
        const alpha = events.invoke('camera.mode') === 'rings' ? 0.0 : 0.2;
        // hide non-selected elements
        const splats = this.scene.getElementsByType(ElementType.splat);
        splats.forEach((s) => {
            s.entity.enabled = s === splat;
        });
        device.scope.resolve('pickerAlpha').setValue(alpha);
        device.scope.resolve('pickMode').setValue(['add', 'remove', 'set'].indexOf(op));
        this.picker.resize(width, height);
        this.picker.prepare(this.entity.camera, this.scene.app.scene, [worldLayer]);
        // re-enable all splats
        splats.forEach((splat) => {
            splat.entity.enabled = true;
        });
    }
    pick(x, y) {
        return this.pickRect(x, y, 1, 1)[0];
    }
    pickRect(x, y, width, height) {
        const device = this.scene.graphicsDevice;
        const pixels = new Uint8Array(width * height * 4);
        // read pixels
        device.setRenderTarget(this.picker.renderTarget);
        device.updateBegin();
        device.readPixels(x, this.picker.renderTarget.height - y - height, width, height, pixels);
        device.updateEnd();
        const result = [];
        for (let i = 0; i < width * height; i++) {
            result.push(pixels[i * 4] |
                (pixels[i * 4 + 1] << 8) |
                (pixels[i * 4 + 2] << 16) |
                (pixels[i * 4 + 3] << 24));
        }
        return result;
    }
    docSerialize() {
        const pack3 = (v) => [v.x, v.y, v.z];
        return {
            focalPoint: pack3(this.focalPointTween.target),
            azim: this.azim,
            elev: this.elevation,
            distance: this.distance,
            fov: this.fov,
            tonemapping: this.tonemapping
        };
    }
    docDeserialize(settings) {
        this.setFocalPoint(new Vec3(settings.focalPoint), 0);
        this.setAzimElev(settings.azim, settings.elev, 0);
        this.setDistance(settings.distance, 0);
        this.fov = settings.fov;
        this.tonemapping = settings.tonemapping;
    }
    // offscreen render mode
    startOffscreenMode(width, height) {
        this.targetSize = { width, height };
        this.suppressFinalBlit = true;
    }
    endOffscreenMode() {
        this.targetSize = null;
        this.suppressFinalBlit = false;
    }
}

const vertexShader$8 = /* glsl */ `
    attribute vec2 vertex_position;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.0, 1.0);
    }
`;
const fragmentShader$8 = /* glsl */ `
    uniform highp usampler2D transformA;                // splat center x, y, z
    uniform highp usampler2D splatTransform;            // transform palette index
    uniform sampler2D transformPalette;                 // palette of transforms
    uniform sampler2D splatState;                       // per-splat state
    uniform highp ivec3 splat_params;                   // texture width, texture height, num splats
    uniform highp uint mode;                            // 0: selected, 1: visible

    // calculate min and max for a single column of splats
    void main(void) {

        vec3 boundMin = vec3(1e6);
        vec3 boundMax = vec3(-1e6);

        for (int id = 0; id < splat_params.y; id++) {
            // calculate splatUV
            ivec2 splatUV = ivec2(gl_FragCoord.x, id);

            // skip out-of-range splats
            if ((splatUV.x + splatUV.y * splat_params.x) >= splat_params.z) {
                continue;
            }

            // read splat state
            uint state = uint(texelFetch(splatState, splatUV, 0).r * 255.0);

            // skip deleted or locked splats
            if (((mode == 0u) && (state != 1u)) || ((mode == 1u) && ((state & 4u) != 0u))) {
                continue;
            }

            // read splat center
            vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);

            // apply optional per-splat transform
            uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
            if (transformIndex > 0u) {
                // read transform matrix
                int u = int(transformIndex % 512u) * 3;
                int v = int(transformIndex / 512u);

                mat3x4 t;
                t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
                t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
                t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);

                center = vec4(center, 1.0) * t;
            }

            boundMin = min(boundMin, mix(center, boundMin, isinf(center)));
            boundMax = max(boundMax, mix(center, boundMax, isinf(center)));
        }

        pcFragColor0 = vec4(boundMin, 0.0);
        pcFragColor1 = vec4(boundMax, 0.0);
    }
`;

const vertexShader$7 = /* glsl */ `
    attribute vec2 vertex_position;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.0, 1.0);
    }
`;
const fragmentShader$7 = /* glsl */ `
    uniform highp usampler2D transformA;            // splat center x, y, z
    uniform highp usampler2D splatTransform;        // transform palette index
    uniform sampler2D transformPalette;             // palette of transforms
    uniform uvec2 splat_params;                     // splat texture width, num splats

    uniform mat4 matrix_model;
    uniform mat4 matrix_viewProjection;

    uniform uvec2 output_params;                    // output width, height

    // 0: mask, 1: rect, 2: sphere
    uniform int mode;

    // mask params
    uniform sampler2D mask;                         // mask in alpha channel
    uniform vec2 mask_params;                       // mask width, height

    // rect params
    uniform vec4 rect_params;                       // rect x, y, width, height

    // sphere params
    uniform vec4 sphere_params;                     // sphere x, y, z, radius

    // box params
    uniform vec4 box_params;                     // box x, y, z
    uniform vec4 aabb_params;                    // len x, y, z

    void main(void) {
        // calculate output id
        uvec2 outputUV = uvec2(gl_FragCoord);
        uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;

        vec4 clr = vec4(0.0);

        for (uint i = 0u; i < 4u; i++) {
            uint id = outputId + i;

            if (id >= splat_params.y) {
                continue;
            }

            // calculate splatUV
            ivec2 splatUV = ivec2(
                int(id % splat_params.x),
                int(id / splat_params.x)
            );

            // read splat center
            vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);

            // apply optional per-splat transform
            uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
            if (transformIndex > 0u) {
                // read transform matrix
                int u = int(transformIndex % 512u) * 3;
                int v = int(transformIndex / 512u);

                mat3x4 t;
                t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
                t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
                t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);

                center = vec4(center, 1.0) * t;
            }

            // transform to clip space and discard if outside
            vec3 world = (matrix_model * vec4(center, 1.0)).xyz;
            vec4 clip = matrix_viewProjection * vec4(world, 1.0);
            vec3 ndc = clip.xyz / clip.w;

            // skip offscreen fragments
            if (!any(greaterThan(abs(ndc), vec3(1.0)))) {
                if (mode == 0) {
                    // select by mask
                    ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);
                    clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;
                } else if (mode == 1) {
                    // select by rect
                    clr[i] = all(greaterThan(ndc.xy * vec2(1.0, -1.0), rect_params.xy)) && all(lessThan(ndc.xy * vec2(1.0, -1.0), rect_params.zw)) ? 1.0 : 0.0;
                } else if (mode == 2) {
                    // select by sphere
                    clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;
                } else if (mode == 3) {
                    // select by box
                    vec3 relativePosition = world - box_params.xyz;
                    bool isInsideCube = true;
                    if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {
                        isInsideCube = false;
                    }
                    if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {
                        isInsideCube = false;
                    }
                    if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {
                        isInsideCube = false;
                    }
                    clr[i] = isInsideCube ? 1.0 : 0.0;
                }
            }
        }

        gl_FragColor = clr;
    }
`;

const vertexShader$6 = /* glsl */ `
    attribute vec2 vertex_position;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.0, 1.0);
    }
`;
const fragmentShader$6 = /* glsl */ `
    uniform highp usampler2D transformA;            // splat center x, y, z
    uniform highp usampler2D splatTransform;        // transform palette index
    uniform sampler2D transformPalette;             // palette of transforms
    uniform ivec2 splat_params;                     // splat texture width, num splats

    void main(void) {
        // calculate output id
        ivec2 splatUV = ivec2(gl_FragCoord);

        // skip if splat index is out of bounds
        if (splatUV.x + splatUV.y * splat_params.x >= splat_params.y) {
            discard;
        }

        // read splat center
        vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);

        // apply optional per-splat transform
        uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
        if (transformIndex > 0u) {
            // read transform matrix
            int u = int(transformIndex % 512u) * 3;
            int v = int(transformIndex / 512u);

            mat3x4 t;
            t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
            t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
            t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);

            center = vec4(center, 1.0) * t;
        }

        gl_FragColor = vec4(center, 0.0);
    }
`;

const v1 = new Vec3();
const v2 = new Vec3();
const resolve$1 = (scope, values) => {
    for (const key in values) {
        scope.resolve(key).setValue(values[key]);
    }
};
// gpu processor for splat data
class DataProcessor {
    device;
    dummyTexture;
    viewProjectionMat = new Mat4();
    splatParams = new Int32Array(3);
    getIntersectResources;
    getBoundResources;
    getPositionResources;
    constructor(device) {
        this.device = device;
        this.dummyTexture = new Texture(device, {
            width: 1,
            height: 1,
            format: PIXELFORMAT_RGBA8
        });
        const createTexture = (name, width, height, format) => {
            return new Texture(device, {
                name,
                width,
                height,
                format,
                mipmaps: false,
                addressU: ADDRESS_CLAMP_TO_EDGE,
                addressV: ADDRESS_CLAMP_TO_EDGE
            });
        };
        // intersection test
        this.getIntersectResources = (() => {
            let shader = null;
            let texture = null;
            let renderTarget = null;
            let data = null;
            return (width, numSplats) => {
                if (!shader) {
                    shader = ShaderUtils.createShader(device, {
                        uniqueName: 'intersectByMaskShader',
                        attributes: {
                            vertex_position: SEMANTIC_POSITION
                        },
                        vertexGLSL: vertexShader$7,
                        fragmentGLSL: fragmentShader$7
                    });
                }
                const resultWidth = Math.max(1, Math.floor(width / 2));
                const resultHeight = Math.ceil(numSplats / (resultWidth * 4));
                if (!texture || texture.width !== resultWidth || texture.height !== resultHeight) {
                    if (texture) {
                        texture.destroy();
                        renderTarget.destroy();
                    }
                    texture = createTexture('intersectTexture', resultWidth, resultHeight, PIXELFORMAT_RGBA8);
                    renderTarget = new RenderTarget({
                        colorBuffer: texture,
                        depth: false
                    });
                    data = new Uint8Array(resultWidth * resultHeight * 4);
                }
                return { shader, texture, renderTarget, data };
            };
        })();
        // calc bound
        this.getBoundResources = (() => {
            let shader = null;
            let minTexture = null;
            let maxTexture = null;
            let renderTarget = null;
            let minRenderTarget = null;
            let maxRenderTarget = null;
            let minData = null;
            let maxData = null;
            return (width) => {
                if (!shader) {
                    shader = ShaderUtils.createShader(device, {
                        uniqueName: 'calcBoundShader',
                        attributes: {
                            vertex_position: SEMANTIC_POSITION
                        },
                        vertexGLSL: vertexShader$8,
                        fragmentGLSL: fragmentShader$8
                    });
                }
                if (!minTexture || minTexture.width !== width) {
                    if (minTexture) {
                        minTexture.destroy();
                        maxTexture.destroy();
                        renderTarget.destroy();
                        minRenderTarget.destroy();
                        maxRenderTarget.destroy();
                    }
                    minTexture = createTexture('calcBoundMin', width, 1, PIXELFORMAT_RGBA32F);
                    maxTexture = createTexture('calcBoundMax', width, 1, PIXELFORMAT_RGBA32F);
                    renderTarget = new RenderTarget({
                        colorBuffers: [minTexture, maxTexture],
                        depth: false
                    });
                    maxRenderTarget = new RenderTarget({
                        colorBuffer: maxTexture,
                        depth: false
                    });
                    minRenderTarget = new RenderTarget({
                        colorBuffer: minTexture,
                        depth: false
                    });
                    minData = new Float32Array(width * 4);
                    maxData = new Float32Array(width * 4);
                }
                return { shader, minTexture, maxTexture, renderTarget, minRenderTarget, maxRenderTarget, minData, maxData };
            };
        })();
        // calc position
        this.getPositionResources = (() => {
            let shader = null;
            let texture = null;
            let renderTarget = null;
            let data = null;
            return (width, height, numSplats) => {
                if (!shader) {
                    shader = ShaderUtils.createShader(device, {
                        uniqueName: 'calcPositionShader',
                        attributes: {
                            vertex_position: SEMANTIC_POSITION
                        },
                        vertexGLSL: vertexShader$6,
                        fragmentGLSL: fragmentShader$6
                    });
                }
                if (!texture || texture.width !== width || texture.height !== height) {
                    if (texture) {
                        texture.destroy();
                        renderTarget.destroy();
                    }
                    texture = createTexture('positionTex', width, height, PIXELFORMAT_RGBA32F);
                    renderTarget = new RenderTarget({
                        colorBuffer: texture,
                        depth: false
                    });
                    data = new Float32Array(width * height * 4);
                }
                return { shader, texture, renderTarget, data };
            };
        })();
    }
    // calculate the intersection of a mask canvas with splat centers
    intersect(options, splat) {
        const { device } = this;
        const { scope } = device;
        const numSplats = splat.splatData.numSplats;
        const transformA = splat.entity.gsplat.instance.resource.transformATexture;
        const splatTransform = splat.transformTexture;
        const transformPalette = splat.transformPalette.texture;
        // update view projection matrix
        const camera = splat.scene.camera.entity.camera;
        this.viewProjectionMat.mul2(camera.projectionMatrix, camera.viewMatrix);
        // allocate resources
        const resources = this.getIntersectResources(transformA.width, numSplats);
        resolve$1(scope, {
            transformA,
            splatTransform,
            transformPalette,
            splat_params: [transformA.width, numSplats],
            matrix_model: splat.entity.getWorldTransform().data,
            matrix_viewProjection: this.viewProjectionMat.data,
            output_params: [resources.texture.width, resources.texture.height]
        });
        const maskOptions = options;
        if (maskOptions.mask) {
            resolve$1(scope, {
                mode: 0,
                mask: maskOptions.mask,
                mask_params: [maskOptions.mask.width, maskOptions.mask.height]
            });
        }
        else {
            resolve$1(scope, {
                mask: this.dummyTexture,
                mask_params: [0, 0]
            });
        }
        const rectOptions = options;
        if (rectOptions.rect) {
            resolve$1(scope, {
                mode: 1,
                rect_params: [
                    rectOptions.rect.x1 * 2.0 - 1.0,
                    rectOptions.rect.y1 * 2.0 - 1.0,
                    rectOptions.rect.x2 * 2.0 - 1.0,
                    rectOptions.rect.y2 * 2.0 - 1.0
                ]
            });
        }
        else {
            resolve$1(scope, {
                rect_params: [0, 0, 0, 0]
            });
        }
        const sphereOptions = options;
        if (sphereOptions.sphere) {
            resolve$1(scope, {
                mode: 2,
                sphere_params: [
                    sphereOptions.sphere.x,
                    sphereOptions.sphere.y,
                    sphereOptions.sphere.z,
                    sphereOptions.sphere.radius
                ]
            });
        }
        else {
            resolve$1(scope, {
                sphere_params: [0, 0, 0, 0]
            });
        }
        const boxOptions = options;
        if (boxOptions.box) {
            resolve$1(scope, {
                mode: 3,
                box_params: [
                    boxOptions.box.x,
                    boxOptions.box.y,
                    boxOptions.box.z,
                    0
                ],
                aabb_params: [
                    boxOptions.box.lenx * 0.5,
                    boxOptions.box.leny * 0.5,
                    boxOptions.box.lenz * 0.5,
                    0
                ]
            });
        }
        else {
            resolve$1(scope, {
                box_params: [0, 0, 0, 0],
                aabb_params: [0, 0, 0, 0]
            });
        }
        device.setBlendState(BlendState.NOBLEND);
        drawQuadWithShader(device, resources.renderTarget, resources.shader);
        const glDevice = device;
        glDevice.readPixels(0, 0, resources.texture.width, resources.texture.height, resources.data);
        return resources.data;
    }
    // use gpu to calculate either bound of the currently selected splats or the bound of
    // all visible splats
    calcBound(splat, boundingBox, onlySelected) {
        const device = splat.scene.graphicsDevice;
        const { scope } = device;
        const numSplats = splat.splatData.numSplats;
        const transformA = splat.entity.gsplat.instance.resource.transformATexture;
        const splatTransform = splat.transformTexture;
        const transformPalette = splat.transformPalette.texture;
        const splatState = splat.stateTexture;
        this.splatParams[0] = transformA.width;
        this.splatParams[1] = transformA.height;
        this.splatParams[2] = numSplats;
        // get resources
        const resources = this.getBoundResources(transformA.width);
        resolve$1(scope, {
            transformA,
            splatTransform,
            transformPalette,
            splatState,
            splat_params: this.splatParams,
            mode: onlySelected ? 0 : 1
        });
        const glDevice = device;
        device.setBlendState(BlendState.NOBLEND);
        drawQuadWithShader(device, resources.renderTarget, resources.shader);
        glDevice.gl.readPixels(0, 0, transformA.width, 1, resources.minTexture.impl._glFormat, resources.minTexture.impl._glPixelType, resources.minData);
        glDevice.setRenderTarget(resources.maxRenderTarget);
        glDevice.updateBegin();
        glDevice.gl.readPixels(0, 0, transformA.width, 1, resources.maxTexture.impl._glFormat, resources.maxTexture.impl._glPixelType, resources.maxData);
        glDevice.updateEnd();
        // resolve mins/maxs
        const { minData, maxData } = resources;
        v1.set(Infinity, Infinity, Infinity);
        v2.set(-Infinity, -Infinity, -Infinity);
        for (let i = 0; i < transformA.width; i++) {
            const a = minData[i * 4];
            const b = minData[i * 4 + 1];
            const c = minData[i * 4 + 2];
            if (isFinite(a))
                v1.x = Math.min(v1.x, a);
            if (isFinite(b))
                v1.y = Math.min(v1.y, b);
            if (isFinite(c))
                v1.z = Math.min(v1.z, c);
            const d = maxData[i * 4];
            const e = maxData[i * 4 + 1];
            const f = maxData[i * 4 + 2];
            if (isFinite(d))
                v2.x = Math.max(v2.x, d);
            if (isFinite(e))
                v2.y = Math.max(v2.y, e);
            if (isFinite(f))
                v2.z = Math.max(v2.z, f);
        }
        boundingBox.setMinMax(v1, v2);
    }
    // calculate world-space splat positions
    calcPositions(splat) {
        const { device } = this;
        const { scope } = device;
        const numSplats = splat.splatData.numSplats;
        const transformA = splat.entity.gsplat.instance.resource.transformATexture;
        const splatTransform = splat.transformTexture;
        const transformPalette = splat.transformPalette.texture;
        // allocate resources
        const resources = this.getPositionResources(transformA.width, transformA.height, numSplats);
        resolve$1(scope, {
            transformA,
            splatTransform,
            transformPalette,
            splat_params: [transformA.width, numSplats]
        });
        device.setBlendState(BlendState.NOBLEND);
        drawQuadWithShader(device, resources.renderTarget, resources.shader);
        const glDevice = device;
        glDevice.gl.readPixels(0, 0, resources.texture.width, resources.texture.height, resources.texture.impl._glFormat, resources.texture.impl._glPixelType, resources.data);
        return resources.data;
    }
}

const vertexShader$5 = /* glsl*/ `
    uniform vec3 near_origin;
    uniform vec3 near_x;
    uniform vec3 near_y;

    uniform vec3 far_origin;
    uniform vec3 far_x;
    uniform vec3 far_y;

    attribute vec2 vertex_position;

    varying vec3 worldFar;
    varying vec3 worldNear;

    void main(void) {
        gl_Position = vec4(vertex_position, 0.0, 1.0);

        vec2 p = vertex_position * 0.5 + 0.5;
        worldNear = near_origin + near_x * p.x + near_y * p.y;
        worldFar = far_origin + far_x * p.x + far_y * p.y;
    }
`;
const fragmentShader$5 = /* glsl*/ `
    uniform vec3 view_position;
    uniform mat4 matrix_viewProjection;
    uniform sampler2D blueNoiseTex32;

    uniform int plane;  // 0: x (yz), 1: y (xz), 2: z (xy)

    vec4 planes[3] = vec4[3](
        vec4(1.0, 0.0, 0.0, 0.0),
        vec4(0.0, 1.0, 0.0, 0.0),
        vec4(0.0, 0.0, 1.0, 0.0)
    );

    vec3 colors[3] = vec3[3](
        vec3(1.0, 0.2, 0.2),
        vec3(0.2, 1.0, 0.2),
        vec3(0.2, 0.2, 1.0)
    );

    int axis0[3] = int[3](1, 0, 0);
    int axis1[3] = int[3](2, 2, 1);

    varying vec3 worldNear;
    varying vec3 worldFar;

    bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
        float d = dot(dir, plane.xyz);
        if (abs(d) < 1e-06) {
            return false;
        }

        float n = -(dot(pos, plane.xyz) + plane.w) / d;
        if (n < 0.0) {
            return false;
        }

        t = n;

        return true;
    }

    // https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
    float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
        vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
        bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
        vec2 targetWidth = vec2(
            invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
            invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
        );
        vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
        vec2 lineAA = uvDeriv * 1.5;
        vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
        gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
        gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
        vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);

        grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
        grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
        grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
        grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;

        return mix(grid2.x, 1.0, grid2.y);
    }

    float calcDepth(vec3 p) {
        vec4 v = matrix_viewProjection * vec4(p, 1.0);
        return (v.z / v.w) * 0.5 + 0.5;
    }

    bool writeDepth(float alpha) {
        vec2 uv = fract(gl_FragCoord.xy / 32.0);
        float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
        return alpha > noise;
    }

    void main(void) {
        vec3 p = worldNear;
        vec3 v = normalize(worldFar - worldNear);

        // intersect ray with the world xz plane
        float t;
        if (!intersectPlane(t, p, v, planes[plane])) {
            discard;
        }

        // calculate grid intersection
        vec3 worldPos = p + v * t;
        vec2 pos = plane == 0 ? worldPos.yz : (plane == 1 ? worldPos.xz : worldPos.xy);
        vec2 ddx = dFdx(pos);
        vec2 ddy = dFdy(pos);

        float epsilon = 1.0 / 255.0;

        // calculate fade
        float fade = 1.0 - smoothstep(400.0, 1000.0, length(worldPos - view_position));
        if (fade < epsilon) {
            discard;
        }

        vec2 levelPos;
        float levelSize;
        float levelAlpha;

        // 10m grid with colored main axes
        levelPos = pos * 0.1;
        levelSize = 2.0 / 1000.0;
        levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
        if (levelAlpha > epsilon) {
            vec3 color;
            vec2 loc = abs(levelPos);
            if (loc.x < levelSize) {
                if (loc.y < levelSize) {
                    color = vec3(1.0);
                } else {
                    color = colors[axis1[plane]];
                }
            } else if (loc.y < levelSize) {
                color = colors[axis0[plane]];
            } else {
                color = vec3(0.9);
            }
            gl_FragColor = vec4(color, levelAlpha);
            gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
            return;
        }

        // 1m grid
        levelPos = pos;
        levelSize = 1.0 / 100.0;
        levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)) * fade;
        if (levelAlpha > epsilon) {
            gl_FragColor = vec4(vec3(0.7), levelAlpha);
            gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
            return;
        }

        // 0.1m grid
        levelPos = pos * 10.0;
        levelSize = 1.0 / 100.0;
        levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
        if (levelAlpha > epsilon) {
            gl_FragColor = vec4(vec3(0.7), levelAlpha);
            gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
            return;
        }

        discard;
    }
`;

const resolve = (scope, values) => {
    for (const key in values) {
        scope.resolve(key).setValue(values[key]);
    }
};
class InfiniteGrid extends Element {
    shader;
    quadRender;
    blendState = new BlendState(false);
    depthState = new DepthState(FUNC_LESSEQUAL, true);
    visible = true;
    constructor() {
        super(ElementType.debug);
    }
    add() {
        const device = this.scene.app.graphicsDevice;
        this.shader = ShaderUtils.createShader(device, {
            uniqueName: 'infinite-grid',
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexGLSL: vertexShader$5,
            fragmentGLSL: fragmentShader$5
        });
        this.quadRender = new QuadRender(this.shader);
        const blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
        const view_position = [0, 0, 0];
        const viewProjectionMatrix = new Mat4();
        let plane;
        this.scene.camera.entity.camera.on('preRenderLayer', (layer, transparent) => {
            const { scene } = this;
            if (this.visible && layer === scene.debugLayer && !transparent && scene.camera.renderOverlays) {
                const { camera } = scene;
                device.setBlendState(blendState);
                device.setCullMode(CULLFACE_NONE);
                device.setDepthState(DepthState.WRITEDEPTH);
                device.setStencilState(null, null);
                // select the correctly plane in orthographic mode
                if (camera.ortho) {
                    const cmp = (a, b) => 1.0 - Math.abs(a.dot(b)) < 1e-03;
                    const z = camera.entity.getWorldTransform().getZ();
                    plane = cmp(z, Vec3.RIGHT) ? 0 : (cmp(z, Vec3.BACK) ? 2 : 1);
                }
                else {
                    // default is xz plane
                    plane = 1;
                }
                const p = camera.entity.getPosition();
                view_position[0] = p.x;
                view_position[1] = p.y;
                view_position[2] = p.z;
                viewProjectionMatrix.mul2(camera.entity.camera.projectionMatrix, camera.entity.camera.viewMatrix);
                resolve(device.scope, {
                    plane,
                    view_position,
                    matrix_viewProjection: viewProjectionMatrix.data
                });
                this.quadRender.render();
            }
        });
    }
    remove() {
        this.shader.destroy();
        this.quadRender.destroy();
    }
    serialize(serializer) {
        serializer.pack(this.visible);
    }
}

const vertexShader$4 = /* glsl*/ `
    attribute vec2 vertex_position;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.0, 1.0);
    }
`;
const fragmentShader$4 = /* glsl*/ `
    uniform sampler2D outlineTexture;
    uniform float alphaCutoff;
    uniform vec4 clr;

    void main(void) {
        ivec2 texel = ivec2(gl_FragCoord.xy);

        // skip solid pixels
        if (texelFetch(outlineTexture, texel, 0).a > alphaCutoff) {
            discard;
        }

        for (int x = -2; x <= 2; x++) {
            for (int y = -2; y <= 2; y++) {
                if ((x != 0) && (y != 0) && (texelFetch(outlineTexture, texel + ivec2(x, y), 0).a > alphaCutoff)) {
                    gl_FragColor = clr;
                    return;
                }
            }
        }

        discard;
    }
`;

class Outline extends Element {
    entity;
    shader;
    quadRender;
    enabled = true;
    clr = new Color(1, 1, 1, 0.5);
    constructor() {
        super(ElementType.other);
        this.entity = new Entity('outlineCamera');
        this.entity.addComponent('camera');
        this.entity.camera.setShaderPass('OUTLINE');
        this.entity.camera.clearColor = new Color(0, 0, 0, 0);
    }
    add() {
        const device = this.scene.app.graphicsDevice;
        const layerId = this.scene.overlayLayer.id;
        // add selected splat to outline layer
        this.scene.events.on('selection.changed', (splat, prev) => {
            if (prev) {
                prev.entity.gsplat.layers = prev.entity.gsplat.layers.filter(id => id !== layerId);
            }
            if (splat) {
                splat.entity.gsplat.layers = splat.entity.gsplat.layers.concat([layerId]);
            }
        });
        // render overlay layer only
        this.entity.camera.layers = [layerId];
        this.scene.camera.entity.addChild(this.entity);
        this.shader = ShaderUtils.createShader(device, {
            uniqueName: 'apply-outline',
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexGLSL: vertexShader$4,
            fragmentGLSL: fragmentShader$4
        });
        this.quadRender = new QuadRender(this.shader);
        const outlineTextureId = device.scope.resolve('outlineTexture');
        const alphaCutoffId = device.scope.resolve('alphaCutoff');
        const clrId = device.scope.resolve('clr');
        const clrStorage = [1, 1, 1, 1];
        const events = this.scene.events;
        // apply the outline texture to the display before gizmos render
        this.entity.camera.on('postRenderLayer', (layer, transparent) => {
            if (!this.entity.enabled || layer !== this.scene.overlayLayer || !transparent) {
                return;
            }
            device.setBlendState(BlendState.ALPHABLEND);
            device.setCullMode(CULLFACE_NONE);
            device.setDepthState(DepthState.NODEPTH);
            device.setStencilState(null, null);
            const selectedClr = events.invoke('selectedClr');
            clrStorage[0] = selectedClr.r;
            clrStorage[1] = selectedClr.g;
            clrStorage[2] = selectedClr.b;
            clrStorage[3] = selectedClr.a;
            outlineTextureId.setValue(this.entity.camera.renderTarget.colorBuffer);
            alphaCutoffId.setValue(events.invoke('camera.mode') === 'rings' ? 0.0 : 0.4);
            clrId.setValue(clrStorage);
            const glDevice = device;
            glDevice.setRenderTarget(this.scene.camera.entity.camera.renderTarget);
            glDevice.updateBegin();
            this.quadRender.render();
            glDevice.updateEnd();
        });
    }
    remove() {
        this.scene.camera.entity.removeChild(this.entity);
    }
    onPreRender() {
        // copy camera properties
        const src = this.scene.camera.entity.camera;
        const dst = this.entity.camera;
        dst.projection = src.projection;
        dst.horizontalFov = src.horizontalFov;
        dst.fov = src.fov;
        dst.nearClip = src.nearClip;
        dst.farClip = src.farClip;
        dst.orthoHeight = src.orthoHeight;
        this.entity.enabled = this.enabled && this.scene.events.invoke('view.outlineSelection');
        this.entity.camera.renderTarget = this.scene.camera.workRenderTarget;
    }
}

class PCApp extends AppBase {
    constructor(canvas, options) {
        super(canvas);
        const appOptions = new AppOptions();
        appOptions.graphicsDevice = options.graphicsDevice;
        this.addComponentSystems(appOptions);
        this.addResourceHandles(appOptions);
        appOptions.elementInput = options.elementInput;
        appOptions.keyboard = options.keyboard;
        appOptions.mouse = options.mouse;
        appOptions.touch = options.touch;
        appOptions.gamepads = options.gamepads;
        appOptions.scriptPrefix = options.scriptPrefix;
        appOptions.assetPrefix = options.assetPrefix;
        appOptions.scriptsOrder = options.scriptsOrder;
        // appOptions.soundManager = new SoundManager(options);
        // appOptions.lightmapper = Lightmapper;
        // appOptions.batchManager = BatchManager;
        // appOptions.xr = XrManager;
        this.init(appOptions);
    }
    addComponentSystems(appOptions) {
        appOptions.componentSystems = [
            // RigidBodyComponentSystem,
            // CollisionComponentSystem,
            // JointComponentSystem,
            // AnimationComponentSystem,
            // @ts-ignore
            AnimComponentSystem,
            // ModelComponentSystem,
            // @ts-ignore
            RenderComponentSystem,
            // @ts-ignore
            CameraComponentSystem,
            // @ts-ignore
            LightComponentSystem,
            // script.legacy ? ScriptLegacyComponentSystem : ScriptComponentSystem,
            // AudioSourceComponentSystem,
            // SoundComponentSystem,
            // AudioListenerComponentSystem,
            // ParticleSystemComponentSystem,
            // ScreenComponentSystem,
            // ElementComponentSystem,
            // ButtonComponentSystem,
            // ScrollViewComponentSystem,
            // ScrollbarComponentSystem,
            // SpriteComponentSystem,
            // LayoutGroupComponentSystem,
            // LayoutChildComponentSystem,
            // ZoneComponentSystem,
            GSplatComponentSystem
        ];
    }
    addResourceHandles(appOptions) {
        appOptions.resourceHandlers = [
            // @ts-ignore
            RenderHandler,
            // AnimationHandler,
            // @ts-ignore
            AnimClipHandler,
            // @ts-ignore
            AnimStateGraphHandler,
            // ModelHandler,
            // MaterialHandler,
            // @ts-ignore
            TextureHandler,
            // TextHandler,
            // JsonHandler,
            // AudioHandler,
            // ScriptHandler,
            // SceneHandler,
            // @ts-ignore
            CubemapHandler,
            // HtmlHandler,
            // CssHandler,
            // ShaderHandler,
            // HierarchyHandler,
            // FolderHandler,
            // FontHandler,
            // BinaryHandler,
            // TextureAtlasHandler,
            // SpriteHandler,
            // TemplateHandler,
            // @ts-ignore
            ContainerHandler,
            GSplatHandler
        ];
    }
}

// this class is used by elements to store their pertinent state
// every frame. the data is then compared with the previous frame's
// values in order to determine if any changes happened.
class Serializer {
    constructor(packValue) {
        this.packValue = packValue;
    }
    packValue;
    pack(...args) {
        for (let j = 0; j < args.length; ++j) {
            this.packValue(args[j]);
        }
    }
    packa(a) {
        for (let j = 0; j < a.length; ++j) {
            this.packValue(a[j]);
        }
    }
    packVec3(v) {
        this.pack(v.x, v.y, v.z);
    }
    packColor(c) {
        this.pack(c.r, c.g, c.b, c.a);
    }
}

const common = new Set();
// this class tracks the state of scene elements and determines what
// type of objects have changed in a frame. this allows the rest of
// the application to respond to changes like re-rendering
// the scene or recalculating the scene bounding box.
class SceneState {
    states = {};
    activeValues;
    serializer = new Serializer((value) => {
        this.activeValues.push(value);
    });
    constructor() {
        for (let i = 0; i < ElementTypeList.length; ++i) {
            this.states[ElementTypeList[i]] = {
                elements: new Map(),
                valueStart: [],
                valueCount: [],
                values: []
            };
        }
    }
    reset() {
        ElementTypeList.forEach((type) => {
            const state = this.states[type];
            state.elements.clear();
            state.valueStart.length = 0;
            state.valueCount.length = 0;
            state.values.length = 0;
        });
    }
    pack(element) {
        const state = this.states[element.type];
        const start = state.values.length;
        // let element store its values
        this.activeValues = state.values;
        element.serialize(this.serializer);
        state.elements.set(element, state.elements.size);
        state.valueStart.push(start);
        state.valueCount.push(state.values.length - start);
    }
    compare(other) {
        function intersection(result, a, b) {
            result.clear();
            for (const e of a.keys()) {
                if (b.has(e)) {
                    result.add(e);
                }
            }
        }
        function diff(a, b) {
            for (const e of a.keys()) {
                if (!b.has(e)) {
                    return true;
                }
            }
        }
        function some(it, predicate) {
            for (const e of it) {
                if (predicate(e)) {
                    return true;
                }
            }
            return false;
        }
        const result = {
            added: new Array(),
            removed: new Array(),
            moved: new Array(),
            changed: new Array()
        };
        ElementTypeList.forEach((type) => {
            const prevState = other.states[type];
            const currState = this.states[type];
            // generate map of element to index
            const prev = prevState.elements;
            const curr = currState.elements;
            // make a set containing the elements present in both the previous and current frame
            intersection(common, prev, curr);
            // determine if any elements were added
            if (diff(curr, common)) {
                result.added.push(type);
            }
            // determine if any elements were removed
            if (diff(prev, common)) {
                result.removed.push(type);
            }
            // determine if any element moved order
            if (some(common.values(), (e) => {
                return prev.get(e) !== curr.get(e);
            })) {
                result.moved.push(type);
            }
            // determine if any element's state changed
            if (some(common.values(), (e) => {
                const prevIdx = prev.get(e);
                const currIdx = curr.get(e);
                const count = prevState.valueCount[prevIdx];
                if (count !== currState.valueCount[currIdx]) {
                    // number of state values changed
                    return true;
                }
                const prevStart = prevState.valueStart[prevIdx];
                const currStart = currState.valueStart[currIdx];
                const prevValues = prevState.values;
                const currValues = currState.values;
                for (let i = 0; i < count; ++i) {
                    if (prevValues[prevStart + i] !== currValues[currStart + i]) {
                        // state value changed
                        return true;
                    }
                }
                return false;
            })) {
                result.changed.push(type);
            }
        });
        return result;
    }
}

const vertexShader$3 = /* glsl */ `
    attribute uint vertex_id;

    uniform mat4 matrix_model;
    uniform mat4 matrix_viewProjection;

    uniform sampler2D splatState;
    uniform highp usampler2D splatPosition;
    uniform highp usampler2D splatTransform;        // per-splat index into transform palette
    uniform sampler2D transformPalette;             // palette of transform matrices

    uniform uvec2 texParams;

    uniform float splatSize;
    uniform vec4 selectedClr;
    uniform vec4 unselectedClr;

    varying vec4 varying_color;

    // calculate the current splat index and uv
    ivec2 calcSplatUV(uint index, uint width) {
        return ivec2(int(index % width), int(index / width));
    }

    void main(void) {
        ivec2 splatUV = calcSplatUV(vertex_id, texParams.x);
        uint splatState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);

        if ((splatState & 6u) != 0u) {
            // deleted or locked (4 or 2)
            gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
            gl_PointSize = 0.0;
        } else {
            mat4 model = matrix_model;
        
            // handle per-splat transform
            uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
            if (transformIndex > 0u) {
                // read transform matrix
                int u = int(transformIndex % 512u) * 3;
                int v = int(transformIndex / 512u);

                mat4 t;
                t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
                t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
                t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
                t[3] = vec4(0.0, 0.0, 0.0, 1.0);

                model = matrix_model * transpose(t);
            }

            varying_color = (splatState == 1u) ? selectedClr : unselectedClr;

            vec3 center = uintBitsToFloat(texelFetch(splatPosition, splatUV, 0).xyz);

            gl_Position = matrix_viewProjection * model * vec4(center, 1.0);
            gl_PointSize = splatSize;
        }
    }
`;
const fragmentShader$3 = /* glsl */ `
    varying vec4 varying_color;

    void main(void) {
        gl_FragColor = varying_color;
    }
`;

class SplatOverlay extends Element {
    meshInstance;
    splat;
    constructor() {
        super(ElementType.debug);
    }
    add() {
        const scene = this.scene;
        const device = scene.graphicsDevice;
        const material = new ShaderMaterial({
            uniqueName: 'splatOverlayMaterial',
            attributes: { vertex_id: SEMANTIC_POSITION },
            vertexGLSL: vertexShader$3,
            fragmentGLSL: fragmentShader$3
        });
        material.blendType = BLEND_NORMAL;
        const mesh = new Mesh(device);
        const meshInstance = new MeshInstance(mesh, material, null);
        const events = this.scene.events;
        const update = (splat) => {
            if (!splat) {
                meshInstance.node = null;
                return;
            }
            const splatData = splat.splatData;
            const vertexFormat = new VertexFormat(device, [{
                    semantic: SEMANTIC_POSITION,
                    components: 1,
                    type: TYPE_UINT32,
                    asInt: true
                }]);
            // TODO: make use of Splat's mapping instead of rendering all splats
            const vertexData = new Uint32Array(splatData.numSplats);
            for (let i = 0; i < splatData.numSplats; ++i) {
                vertexData[i] = i;
            }
            const vertexBuffer = new VertexBuffer(device, vertexFormat, splatData.numSplats, {
                usage: BUFFER_STATIC,
                data: vertexData.buffer
            });
            if (mesh.vertexBuffer) {
                mesh.vertexBuffer.destroy();
                mesh.vertexBuffer = null;
            }
            mesh.vertexBuffer = vertexBuffer;
            mesh.primitive[0] = {
                type: PRIMITIVE_POINTS,
                base: 0,
                baseVertex: 0,
                count: splatData.numSplats
            };
            material.setParameter('splatState', splat.stateTexture);
            material.setParameter('splatPosition', splat.entity.gsplat.instance.resource.transformATexture);
            material.setParameter('splatTransform', splat.transformTexture);
            material.setParameter('texParams', [splat.stateTexture.width, splat.stateTexture.height]);
            material.update();
            meshInstance.node = splat.entity;
            this.splat = splat;
        };
        events.on('selection.changed', (selection) => {
            update(selection);
        });
        this.meshInstance = meshInstance;
    }
    destroy() {
        this.meshInstance.material.destroy();
        this.meshInstance.destroy();
    }
    onPreRender() {
        const events = this.scene.events;
        const splatSize = events.invoke('camera.splatSize');
        if (this.meshInstance.node &&
            this.scene.camera.renderOverlays &&
            splatSize > 0 &&
            events.invoke('camera.overlay') &&
            events.invoke('camera.mode') === 'centers') {
            const selectedClr = events.invoke('selectedClr');
            const unselectedClr = events.invoke('unselectedClr');
            const { material } = this.meshInstance;
            material.setParameter('splatSize', splatSize * window.devicePixelRatio);
            material.setParameter('selectedClr', [selectedClr.r, selectedClr.g, selectedClr.b, selectedClr.a]);
            material.setParameter('unselectedClr', [unselectedClr.r, unselectedClr.g, unselectedClr.b, unselectedClr.a]);
            material.setParameter('transformPalette', this.splat.transformPalette.texture);
            this.scene.app.drawMeshInstance(this.meshInstance);
        }
    }
}

const vertexShader$2 = /* glsl*/ `
    attribute vec2 vertex_position;
    void main(void) {
        gl_Position = vec4(vertex_position, 0.0, 1.0);
    }
`;
const fragmentShader$2 = /* glsl*/ `
    uniform sampler2D blitTexture;
    void main(void) {
        ivec2 texel = ivec2(gl_FragCoord.xy);

        gl_FragColor = texelFetch(blitTexture, texel, 0);
    }
`;

class Underlay extends Element {
    entity;
    shader;
    quadRender;
    enabled = true;
    constructor() {
        super(ElementType.other);
        this.entity = new Entity('underlayCamera');
        this.entity.addComponent('camera');
        this.entity.camera.setShaderPass('UNDERLAY');
        this.entity.camera.clearColor = new Color(0, 0, 0, 0);
    }
    add() {
        const device = this.scene.app.graphicsDevice;
        this.entity.camera.layers = [this.scene.overlayLayer.id];
        this.scene.camera.entity.addChild(this.entity);
        this.shader = ShaderUtils.createShader(device, {
            uniqueName: 'apply-underlay',
            attributes: {
                vertex_position: SEMANTIC_POSITION
            },
            vertexGLSL: vertexShader$2,
            fragmentGLSL: fragmentShader$2
        });
        this.quadRender = new QuadRender(this.shader);
        const blitTextureId = device.scope.resolve('blitTexture');
        const blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE, BLENDEQUATION_ADD, BLENDMODE_ZERO, BLENDMODE_ONE);
        this.entity.camera.on('postRenderLayer', (layer, transparent) => {
            if (!this.entity.enabled || layer !== this.scene.overlayLayer || !transparent) {
                return;
            }
            device.setBlendState(blendState);
            blitTextureId.setValue(this.entity.camera.renderTarget.colorBuffer);
            const glDevice = device;
            glDevice.setRenderTarget(this.scene.camera.entity.camera.renderTarget);
            glDevice.updateBegin();
            this.quadRender.render();
            glDevice.updateEnd();
        });
    }
    remove() {
        this.scene.camera.entity.removeChild(this.entity);
    }
    onPreRender() {
        // copy camera properties
        const src = this.scene.camera.entity.camera;
        const dst = this.entity.camera;
        dst.projection = src.projection;
        dst.horizontalFov = src.horizontalFov;
        dst.fov = src.fov;
        dst.nearClip = src.nearClip;
        dst.farClip = src.farClip;
        dst.orthoHeight = src.orthoHeight;
        this.entity.enabled = this.enabled && !this.scene.events.invoke('view.outlineSelection');
        this.entity.camera.renderTarget = this.scene.camera.workRenderTarget;
    }
}

class Scene {
    events;
    config;
    canvas;
    app;
    backgroundLayer;
    shadowLayer;
    debugLayer;
    overlayLayer;
    gizmoLayer;
    sceneState = [new SceneState(), new SceneState()];
    elements = [];
    boundStorage = new BoundingBox();
    boundDirty = true;
    forceRender = false;
    lockedRenderMode = false;
    lockedRender = false;
    canvasResize = null;
    targetSize = {
        width: 0,
        height: 0
    };
    dataProcessor;
    assetLoader;
    camera;
    splatOverlay;
    grid;
    outline;
    underlay;
    contentRoot;
    cameraRoot;
    constructor(events, config, canvas, graphicsDevice) {
        this.events = events;
        this.config = config;
        this.canvas = canvas;
        // configure the playcanvas application. we render to an offscreen buffer so require
        // only the simplest of backbuffers.
        this.app = new PCApp(canvas, { graphicsDevice });
        // only render the scene when instructed
        this.app.autoRender = false;
        // @ts-ignore
        this.app._allowResize = false;
        this.app.scene.clusteredLightingEnabled = false;
        // hack: disable lightmapper first bake until we expose option for this
        // @ts-ignore
        this.app.off('prerender', this.app._firstBake, this.app);
        // @ts-ignore
        this.app.loader.getHandler('texture').imgParser.crossOrigin = 'anonymous';
        // this is required to get full res AR mode backbuffer
        this.app.graphicsDevice.maxPixelRatio = window.devicePixelRatio;
        // configure application canvas
        const observer = new ResizeObserver((entries) => {
            if (entries.length > 0) {
                const entry = entries[0];
                if (entry) {
                    if (entry.devicePixelContentBoxSize) {
                        // on non-safari browsers, we are given the pixel-perfect canvas size
                        this.canvasResize = {
                            width: entry.devicePixelContentBoxSize[0].inlineSize,
                            height: entry.devicePixelContentBoxSize[0].blockSize
                        };
                    }
                    else if (entry.contentBoxSize.length > 0) {
                        // on safari browsers we must calculate pixel size from CSS size ourselves
                        // and hope the browser performs the same calculation.
                        const pixelRatio = window.devicePixelRatio;
                        this.canvasResize = {
                            width: Math.ceil(entry.contentBoxSize[0].inlineSize * pixelRatio),
                            height: Math.ceil(entry.contentBoxSize[0].blockSize * pixelRatio)
                        };
                    }
                }
                this.forceRender = true;
            }
        });
        observer.observe(window.document.getElementById('canvas-container'));
        // configure depth layers to handle dynamic refraction
        const depthLayer = this.app.scene.layers.getLayerById(LAYERID_DEPTH);
        this.app.scene.layers.remove(depthLayer);
        this.app.scene.layers.insertOpaque(depthLayer, 2);
        // register application callbacks
        this.app.on('update', (deltaTime) => this.onUpdate(deltaTime));
        this.app.on('prerender', () => this.onPreRender());
        this.app.on('postrender', () => this.onPostRender());
        // force render on device restored
        this.app.graphicsDevice.on('devicerestored', () => {
            this.forceRender = true;
        });
        // fire pre and post render events on the camera
        this.app.scene.on(EVENT_PRERENDER_LAYER, (camera, layer, transparent) => {
            camera.fire('preRenderLayer', layer, transparent);
        });
        this.app.scene.on(EVENT_POSTRENDER_LAYER, (camera, layer, transparent) => {
            camera.fire('postRenderLayer', layer, transparent);
        });
        // background layer
        this.backgroundLayer = new Layer({
            enabled: true,
            name: 'Background Layer',
            opaqueSortMode: SORTMODE_NONE,
            transparentSortMode: SORTMODE_NONE
        });
        // shadow layer
        // this layer contains shadow caster scene mesh instances, shadow-casting
        // virtual light, shadow catching plane geometry and the main camera.
        this.shadowLayer = new Layer({
            name: 'Shadow Layer'
        });
        // debug layer
        this.debugLayer = new Layer({
            enabled: true,
            name: 'Debug Layer',
            opaqueSortMode: SORTMODE_NONE,
            transparentSortMode: SORTMODE_NONE
        });
        // overlay layer
        this.overlayLayer = new Layer({
            name: 'Overlay',
            clearDepthBuffer: false,
            opaqueSortMode: SORTMODE_NONE,
            transparentSortMode: SORTMODE_NONE
        });
        // gizmo layer
        this.gizmoLayer = new Layer({
            name: 'Gizmo',
            clearDepthBuffer: true,
            opaqueSortMode: SORTMODE_NONE,
            transparentSortMode: SORTMODE_NONE
        });
        const layers = this.app.scene.layers;
        const worldLayer = layers.getLayerByName('World');
        const idx = layers.getOpaqueIndex(worldLayer);
        layers.insert(this.backgroundLayer, idx);
        layers.insert(this.shadowLayer, idx + 1);
        layers.insert(this.debugLayer, idx + 1);
        layers.push(this.overlayLayer);
        layers.push(this.gizmoLayer);
        this.dataProcessor = new DataProcessor(this.app.graphicsDevice);
        this.assetLoader = new AssetLoader(this.app, events, this.app.graphicsDevice.maxAnisotropy);
        // create root entities
        this.contentRoot = new Entity('contentRoot');
        this.app.root.addChild(this.contentRoot);
        this.cameraRoot = new Entity('cameraRoot');
        this.app.root.addChild(this.cameraRoot);
        // create elements
        this.camera = new Camera();
        this.add(this.camera);
        this.splatOverlay = new SplatOverlay();
        this.add(this.splatOverlay);
        this.grid = new InfiniteGrid();
        this.add(this.grid);
        this.outline = new Outline();
        this.add(this.outline);
        this.underlay = new Underlay();
        this.add(this.underlay);
    }
    start() {
        // start the app
        this.app.start();
    }
    clear() {
        const splats = this.getElementsByType(ElementType.splat);
        splats.forEach((splat) => {
            this.remove(splat);
            splat.destroy();
        });
    }
    // add a scene element
    add(element) {
        if (!element.scene) {
            // add the new element
            element.scene = this;
            element.add();
            this.elements.push(element);
            // notify all elements of scene addition
            this.forEachElement(e => e !== element && e.onAdded(element));
            // notify listeners
            this.events.fire('scene.elementAdded', element);
        }
    }
    // remove an element from the scene
    remove(element) {
        if (element.scene === this) {
            // remove from list
            this.elements.splice(this.elements.indexOf(element), 1);
            // notify listeners
            this.events.fire('scene.elementRemoved', element);
            // notify all elements of scene removal
            this.forEachElement(e => e.onRemoved(element));
            element.remove();
            element.scene = null;
        }
    }
    // get the scene bound
    get bound() {
        if (this.boundDirty) {
            let valid = false;
            this.forEachElement((e) => {
                const bound = e.worldBound;
                if (bound) {
                    if (!valid) {
                        valid = true;
                        this.boundStorage.copy(bound);
                    }
                    else {
                        this.boundStorage.add(bound);
                    }
                }
            });
            this.boundDirty = false;
            this.events.fire('scene.boundChanged', this.boundStorage);
        }
        return this.boundStorage;
    }
    getElementsByType(elementType) {
        return this.elements.filter(e => e.type === elementType);
    }
    get graphicsDevice() {
        return this.app.graphicsDevice;
    }
    forEachElement(action) {
        this.elements.forEach(action);
    }
    onUpdate(deltaTime) {
        // allow elements to update
        this.forEachElement(e => e.onUpdate(deltaTime));
        // fire global update
        this.events.fire('update', deltaTime);
        // fire a 'serialize' event which listers will use to store their state. we'll use
        // this to decide if the view has changed and so requires rendering.
        const i = this.app.frame % 2;
        const state = this.sceneState[i];
        state.reset();
        this.forEachElement(e => state.pack(e));
        // diff with previous state
        const result = state.compare(this.sceneState[1 - i]);
        // generate the set of all element types that changed
        const all = new Set([...result.added, ...result.removed, ...result.moved, ...result.changed]);
        // compare with previously serialized
        if (this.lockedRenderMode) {
            this.app.renderNextFrame = this.lockedRender;
            this.lockedRender = false;
        }
        else if (!this.app.renderNextFrame) {
            this.app.renderNextFrame = this.forceRender || all.size > 0;
        }
        this.forceRender = false;
        // raise per-type update events
        ElementTypeList.forEach((type) => {
            if (all.has(type)) {
                this.events.fire(`updated:${type}`);
            }
        });
        // allow elements to postupdate
        this.forEachElement(e => e.onPostUpdate());
    }
    onPreRender() {
        if (this.canvasResize) {
            this.canvas.width = this.canvasResize.width;
            this.canvas.height = this.canvasResize.height;
            this.canvasResize = null;
        }
        // update render target size
        this.targetSize.width = Math.ceil(this.app.graphicsDevice.width / this.config.camera.pixelScale);
        this.targetSize.height = Math.ceil(this.app.graphicsDevice.height / this.config.camera.pixelScale);
        this.forEachElement(e => e.onPreRender());
        this.events.fire('prerender', this.camera.entity.getWorldTransform());
        // debug - display scene bound
        if (this.config.debug.showBound) {
            // draw element bounds
            this.forEachElement((e) => {
                if (e.type === ElementType.splat) {
                    const splat = e;
                    const local = splat.localBound;
                    this.app.drawWireAlignedBox(local.getMin(), local.getMax(), Color.RED, true, undefined, splat.entity.getWorldTransform());
                    const world = splat.worldBound;
                    this.app.drawWireAlignedBox(world.getMin(), world.getMax(), Color.GREEN);
                }
            });
            // draw scene bound
            this.app.drawWireAlignedBox(this.bound.getMin(), this.bound.getMax(), Color.BLUE);
        }
    }
    onPostRender() {
        this.forEachElement(e => e.onPostRender());
        this.events.fire('postrender');
    }
}

const DEFAULT_BG_CLR = { r: 0.4, g: 0.4, b: 0.4, a: 1 };
const DEFAULT_SELECTED_CLR = { r: 1, g: 1, b: 0, a: 1 };
const DEFAULT_UNSELECTED_CLR = { r: 0, g: 0, b: 1, a: 0.5 };
const DEFAULT_LOCKED_CLR = { r: 0, g: 0, b: 0, a: 0.05 };
// default config
const sceneConfig = {
    bgClr: DEFAULT_BG_CLR,
    selectedClr: DEFAULT_SELECTED_CLR,
    unselectedClr: DEFAULT_UNSELECTED_CLR,
    lockedClr: DEFAULT_LOCKED_CLR,
    camera: {
        pixelScale: 1,
        multisample: false,
        fov: 65,
        exposure: 1.0,
        toneMapping: 'linear',
        debugRender: '',
        overlay: true
    },
    show: {
        grid: true,
        bound: true,
        shBands: 3
    },
    controls: {
        dampingFactor: 0.2,
        minPolarAngle: 0,
        maxPolarAngle: Math.PI,
        minZoom: 1e-6,
        maxZoom: 10.0,
        initialAzim: -45,
        initialElev: -10,
        initialZoom: 1.0,
        orbitSensitivity: 0.3,
        zoomSensitivity: 0.4
    },
    debug: {
        showBound: false
    }
};
class Params {
    sources;
    constructor(sources) {
        this.sources = sources;
    }
    resolve(configs, path) {
        const get = (obj) => {
            for (const name of path) {
                if (!obj.hasOwnProperty(name)) {
                    return undefined;
                }
                obj = obj[name];
            }
            return obj;
        };
        for (const config of configs) {
            const value = get(config);
            if (value !== undefined) {
                return value;
            }
        }
        return undefined;
    }
    get(path) {
        // https://stackoverflow.com/a/67243723/2405687
        const kebabize = (s) => s.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase());
        return this.resolve(this.sources, path.split('.').map(kebabize)) ?? this.resolve(this.sources, path.split('.'));
    }
    getBool(path) {
        const value = this.get(path);
        return typeof value === 'string' ? value.toLowerCase() === 'true' : value === undefined ? undefined : !!value;
    }
    getNumber(path) {
        const value = this.get(path);
        return typeof value === 'string' ? parseFloat(value) : value;
    }
    getVec(path) {
        const value = this.get(path);
        return typeof value === 'string' ? value.split(',') : undefined;
    }
    getVec3(path) {
        const value = this.getVec(path);
        if (value) {
            const numbers = value.map(v => parseFloat(v));
            if (value.length === 1) {
                return { x: numbers[0], y: numbers[0], z: numbers[0] };
            }
            else if (value.length === 3) {
                return { x: numbers[0], y: numbers[1], z: numbers[2] };
            }
        }
        return undefined;
    }
    getColor(path) {
        const value = this.getVec(path);
        if (value) {
            const numbers = value.map(v => parseFloat(v));
            if (value.length === 1) {
                return { r: numbers[0], g: numbers[0], b: numbers[0], a: 1 };
            }
            else if (value.length === 3) {
                return { r: numbers[0], g: numbers[1], b: numbers[2], a: 1 };
            }
            else if (value.length === 4) {
                return { r: numbers[0], g: numbers[1], b: numbers[2], a: numbers[3] };
            }
        }
        return undefined;
    }
}
const getSceneConfig = (overrides) => {
    const params = new Params(overrides);
    const cmp = (a, b) => {
        return a.length === b.length && a.every((v, i) => v === b[i]);
    };
    // recurse the object and replace concrete leaf values with overrides
    const rec = (obj, path) => {
        for (const child in obj) {
            const childPath = `${path}${path.length ? '.' : ''}${child}`;
            const childValue = obj[child];
            switch (typeof childValue) {
                case 'number':
                    obj[child] = params.getNumber(childPath) ?? childValue;
                    break;
                case 'boolean':
                    obj[child] = params.getBool(childPath) ?? childValue;
                    break;
                case 'string':
                    obj[child] = params.get(childPath) ?? childValue;
                    break;
                case 'object': {
                    const keys = Object.keys(childValue).sort();
                    if (cmp(keys, ['a', 'b', 'g', 'r'])) {
                        obj[child] = params.getColor(childPath) ?? childValue;
                    }
                    else if (cmp(keys, ['x', 'y', 'z'])) {
                        obj[child] = params.getVec3(childPath) ?? childValue;
                    }
                    else {
                        rec(childValue, childPath);
                    }
                    break;
                }
                default:
                    rec(childValue, childPath);
                    break;
            }
        }
    };
    rec(sceneConfig, '');
    return sceneConfig;
};

const registerSelectionEvents = (events, scene) => {
    let selection = null;
    const setSelection = (splat) => {
        if (splat !== selection && (!splat || splat.visible)) {
            const prev = selection;
            selection = splat;
            events.fire('selection.changed', selection, prev);
        }
    };
    events.on('selection', (splat) => {
        setSelection(splat);
    });
    events.function('selection', () => {
        return selection;
    });
    events.on('selection.next', () => {
        const splats = scene.getElementsByType(ElementType.splat);
        if (splats.length > 1) {
            const idx = splats.indexOf(selection);
            setSelection(splats[(idx + 1) % splats.length]);
        }
    });
    events.on('scene.elementAdded', (element) => {
        if (element.type === ElementType.splat) {
            setSelection(element);
        }
    });
    events.on('scene.elementRemoved', (element) => {
        if (element === selection) {
            const splats = scene.getElementsByType(ElementType.splat);
            setSelection(splats.length === 1 ? null : splats.find(v => v !== element));
        }
    });
    events.on('splat.visibility', (splat) => {
        if (splat === selection && !splat.visible) {
            setSelection(null);
        }
    });
    events.on('camera.focalPointPicked', (details) => {
        setSelection(details.splat);
    });
};

class Shortcuts {
    shortcuts = [];
    constructor(events) {
        const shortcuts = this.shortcuts;
        const handleEvent = (e, down, capture) => {
            // skip keys in input fields
            if (!capture && e.target !== document.body)
                return;
            for (let i = 0; i < shortcuts.length; i++) {
                const shortcut = shortcuts[i];
                const options = shortcut.options;
                if (shortcut.keys.includes(e.key) &&
                    ((options.capture ?? false) === capture) &&
                    !!options.ctrl === !!(e.ctrlKey || e.metaKey) &&
                    !!options.shift === !!e.shiftKey) {
                    e.stopPropagation();
                    e.preventDefault();
                    // handle sticky shortcuts
                    if (options.sticky) {
                        if (down) {
                            shortcut.toggled = e.repeat;
                        }
                        if (down === shortcut.toggled) {
                            return;
                        }
                    }
                    else {
                        // ignore up events on non-sticky shortcuts
                        if (!down)
                            return;
                    }
                    if (shortcuts[i].options.event) {
                        events.fire(shortcuts[i].options.event);
                    }
                    else {
                        shortcuts[i].options.func();
                    }
                    break;
                }
            }
        };
        // register keyboard handler
        document.addEventListener('keydown', (e) => {
            handleEvent(e, true, false);
        });
        document.addEventListener('keyup', (e) => {
            handleEvent(e, false, false);
        });
        // also handle capture phase
        document.addEventListener('keydown', (e) => {
            handleEvent(e, true, true);
        }, true);
        document.addEventListener('keyup', (e) => {
            handleEvent(e, false, true);
        }, true);
    }
    register(keys, options) {
        this.shortcuts.push({ keys, options, toggled: false });
    }
}

const registerTimelineEvents = (events) => {
    let frames = 180;
    let frameRate = 30;
    let smoothness = 1;
    // frames
    const setFrames = (value) => {
        if (value !== frames) {
            frames = value;
            events.fire('timeline.frames', frames);
        }
    };
    events.function('timeline.frames', () => {
        return frames;
    });
    events.on('timeline.setFrames', (value) => {
        setFrames(value);
    });
    // frame rate
    const setFrameRate = (value) => {
        if (value !== frameRate) {
            frameRate = value;
            events.fire('timeline.frameRate', frameRate);
        }
    };
    events.function('timeline.frameRate', () => {
        return frameRate;
    });
    events.on('timeline.setFrameRate', (value) => {
        setFrameRate(value);
    });
    // smoothness
    const setSmoothness = (value) => {
        if (value !== smoothness) {
            smoothness = value;
            events.fire('timeline.smoothness', smoothness);
        }
    };
    events.function('timeline.smoothness', () => {
        return smoothness;
    });
    events.on('timeline.setSmoothness', (value) => {
        setSmoothness(value);
    });
    // current frame
    let frame = 0;
    const setFrame = (value) => {
        if (value !== frame) {
            frame = value;
            events.fire('timeline.frame', frame);
        }
    };
    events.function('timeline.frame', () => {
        return frame;
    });
    events.on('timeline.setFrame', (value) => {
        setFrame(value);
    });
    // anim controls
    let animHandle = null;
    const play = () => {
        let time = frame;
        // handle application update tick
        animHandle = events.on('update', (dt) => {
            time = (time + dt * frameRate) % frames;
            setFrame(Math.floor(time));
            events.fire('timeline.time', time);
        });
    };
    const stop = () => {
        animHandle.off();
        animHandle = null;
    };
    // playing state
    let playing = false;
    const setPlaying = (value) => {
        if (value !== playing) {
            playing = value;
            events.fire('timeline.playing', playing);
            if (playing) {
                play();
            }
            else {
                stop();
            }
        }
    };
    events.function('timeline.playing', () => {
        return playing;
    });
    events.on('timeline.setPlaying', (value) => {
        setPlaying(value);
    });
    // keys
    const keys = [];
    events.function('timeline.keys', () => {
        return keys;
    });
    events.on('timeline.addKey', (frame) => {
        keys.push(frame);
        events.fire('timeline.keyAdded', frame);
    });
    events.on('timeline.removeKey', (index) => {
        keys.splice(index, 1);
        events.fire('timeline.keyRemoved', index);
    });
    events.on('timeline.setKey', (index, frame) => {
        if (frame !== keys[index]) {
            keys[index] = frame;
            events.fire('timeline.keySet', index, frame);
        }
    });
    // doc
    events.function('docSerialize.timeline', () => {
        return {
            frames,
            frameRate,
            frame,
            smoothness
        };
    });
    events.function('docDeserialize.timeline', (data = {}) => {
        events.fire('timeline.setFrames', data.frames ?? 180);
        events.fire('timeline.setFrameRate', data.frameRate ?? 30);
        events.fire('timeline.setFrame', data.frame ?? 0);
        events.fire('timeline.setSmoothness', data.smoothness ?? 0);
    });
};

const vertexShader$1 = /* glsl */ `
    attribute vec3 vertex_position;

    uniform mat4 matrix_model;
    uniform mat4 matrix_viewProjection;

    void main() {
        gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
    }
`;
const fragmentShader$1 = /* glsl */ `
    // ray-box intersection in box space
    bool intersectBox(out float t0, out float t1, out int axis0, out int axis1, vec3 pos, vec3 dir, vec3 boxCen, vec3 boxLen)
    {
        bvec3 validDir = notEqual(dir, vec3(0.0));
        vec3 absDir = abs(dir);
        vec3 signDir = sign(dir);
        vec3 m = vec3(
            validDir.x ? 1.0 / absDir.x : 0.0,
            validDir.y ? 1.0 / absDir.y : 0.0,
            validDir.z ? 1.0 / absDir.z : 0.0
        ) * signDir;

        vec3 n = m * (pos - boxCen);
        vec3 k = abs(m) * boxLen;

        vec3 v0 = -n - k;
        vec3 v1 = -n + k;

        // replace invalid axes with -inf and +inf so the tests below ignore them
        v0 = mix(vec3(-1.0 / 0.0000001), v0, validDir);
        v1 = mix(vec3(1.0 / 0.0000001), v1, validDir);

        axis0 = (v0.x > v0.y) ? ((v0.x > v0.z) ? 0 : 2) : ((v0.y > v0.z) ? 1 : 2);
        axis1 = (v1.x < v1.y) ? ((v1.x < v1.z) ? 0 : 2) : ((v1.y < v1.z) ? 1 : 2);

        t0 = v0[axis0];
        t1 = v1[axis1];

        if (t0 > t1 || t1 < 0.0) {
            return false;
        }

        return true;
    }

    float calcDepth(in vec3 pos, in mat4 viewProjection) {
        vec4 v = viewProjection * vec4(pos, 1.0);
        return (v.z / v.w) * 0.5 + 0.5;
    }

    uniform sampler2D blueNoiseTex32;
    uniform mat4 matrix_viewProjection;
    uniform vec3 boxCen;
    uniform vec3 boxLen;

    uniform vec3 near_origin;
    uniform vec3 near_x;
    uniform vec3 near_y;

    uniform vec3 far_origin;
    uniform vec3 far_x;
    uniform vec3 far_y;

    uniform vec2 targetSize;

    bool writeDepth(float alpha) {
        ivec2 uv = ivec2(gl_FragCoord.xy);
        ivec2 size = textureSize(blueNoiseTex32, 0);
        return alpha > texelFetch(blueNoiseTex32, uv % size, 0).y;
    }

    bool strips(vec3 pos, int axis) {
        bvec3 b = lessThan(fract(pos * 2.0 + vec3(0.015)), vec3(0.03));
        b[axis] = false;
        return any(b);
    }

    void main() {
        vec2 clip = gl_FragCoord.xy / targetSize;
        vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
        vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;
        vec3 rayDir = normalize(worldFar - worldNear);

        float t0, t1;
        int axis0, axis1;
        if (!intersectBox(t0, t1, axis0, axis1, worldNear, rayDir, boxCen, boxLen)) {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 0.6);
            return;
        }

        vec3 frontPos = worldNear + rayDir * t0;
        bool front = t0 > 0.0 && strips(frontPos - boxCen, axis0);

        vec3 backPos = worldNear + rayDir * t1;
        bool back = strips(backPos - boxCen, axis1);

        if (front) {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 0.6);
            gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
        } else if (back) {
            gl_FragColor = vec4(0.0, 0.0, 0.0, 0.6);
            gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
        } else {
            discard;
        }
    }
`;

const v$2 = new Vec3();
const bound$1 = new BoundingBox();
class BoxShape extends Element {
    _lenX = 2;
    _lenY = 2;
    _lenZ = 2;
    pivot;
    material;
    constructor() {
        super(ElementType.debug);
        this.pivot = new Entity('boxPivot');
        this.pivot.addComponent('render', {
            type: 'box'
        });
    }
    add() {
        const material = new ShaderMaterial({
            uniqueName: 'boxShape',
            vertexGLSL: vertexShader$1,
            fragmentGLSL: fragmentShader$1
        });
        material.cull = CULLFACE_FRONT;
        material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
        material.update();
        this.pivot.render.meshInstances[0].material = material;
        this.pivot.render.layers = [this.scene.debugLayer.id];
        this.material = material;
        this.scene.contentRoot.addChild(this.pivot);
        this.updateBound();
    }
    remove() {
        this.scene.contentRoot.removeChild(this.pivot);
        this.scene.boundDirty = true;
    }
    destroy() {
    }
    serialize(serializer) {
        serializer.packa(this.pivot.getWorldTransform().data);
        serializer.pack(this.lenX);
        serializer.pack(this.lenY);
        serializer.pack(this.lenZ);
    }
    onPreRender() {
        this.pivot.setLocalScale(this._lenX, this._lenY, this._lenZ);
        this.pivot.getWorldTransform().getTranslation(v$2);
        this.material.setParameter('boxCen', [v$2.x, v$2.y, v$2.z]);
        this.material.setParameter('boxLen', [this._lenX * 0.5, this._lenY * 0.5, this._lenZ * 0.5]);
        const device = this.scene.graphicsDevice;
        device.scope.resolve('targetSize').setValue([device.width, device.height]);
    }
    moved() {
        this.updateBound();
    }
    updateBound() {
        bound$1.center.copy(this.pivot.getPosition());
        bound$1.halfExtents.set(this._lenX, this._lenY, this._lenZ);
        this.scene.boundDirty = true;
    }
    get worldBound() {
        return bound$1;
    }
    set lenX(lenX) {
        this._lenX = lenX;
        this.updateBound();
    }
    get lenX() {
        return this._lenX;
    }
    set lenY(lenY) {
        this._lenY = lenY;
        this.updateBound();
    }
    get lenY() {
        return this._lenY;
    }
    set lenZ(lenZ) {
        this._lenZ = lenZ;
        this.updateBound();
    }
    get lenZ() {
        return this._lenZ;
    }
}

class BoxSelection {
    activate;
    deactivate;
    active = false;
    constructor(events, scene, canvasContainer) {
        const box = new BoxShape();
        const gizmo = new TranslateGizmo(scene.camera.entity.camera, scene.gizmoLayer);
        gizmo.on('render:update', () => {
            scene.forceRender = true;
        });
        gizmo.on('transform:move', () => {
            box.moved();
        });
        // ui
        const selectToolbar = new Container({
            id: 'select-toolbar',
            hidden: true
        });
        selectToolbar.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
        });
        const setButton = new Button({ text: 'Set', class: 'select-toolbar-button' });
        const addButton = new Button({ text: 'Add', class: 'select-toolbar-button' });
        const removeButton = new Button({ text: 'Remove', class: 'select-toolbar-button' });
        const lenX = new NumericInput({
            precision: 2,
            value: box.lenX,
            placeholder: 'LenX',
            width: 80,
            min: 0.01
        });
        const lenY = new NumericInput({
            precision: 2,
            value: box.lenY,
            placeholder: 'LenY',
            width: 80,
            min: 0.01
        });
        const lenZ = new NumericInput({
            precision: 2,
            value: box.lenZ,
            placeholder: 'LenZ',
            width: 80,
            min: 0.01
        });
        selectToolbar.append(setButton);
        selectToolbar.append(addButton);
        selectToolbar.append(removeButton);
        selectToolbar.append(lenX);
        selectToolbar.append(lenY);
        selectToolbar.append(lenZ);
        canvasContainer.append(selectToolbar);
        const apply = (op) => {
            const p = box.pivot.getPosition();
            events.fire('select.byBox', op, [p.x, p.y, p.z, box.lenX, box.lenY, box.lenZ]);
        };
        setButton.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
            apply('set');
        });
        addButton.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
            apply('add');
        });
        removeButton.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
            apply('remove');
        });
        lenX.on('change', () => {
            box.lenX = lenX.value;
        });
        lenY.on('change', () => {
            box.lenY = lenY.value;
        });
        lenZ.on('change', () => {
            box.lenZ = lenZ.value;
        });
        events.on('camera.focalPointPicked', (details) => {
            if (this.active) {
                box.pivot.setPosition(details.position);
                gizmo.attach([box.pivot]);
            }
        });
        const updateGizmoSize = () => {
            const { camera, canvas } = scene;
            if (camera.ortho) {
                gizmo.size = 1125 / canvas.clientHeight;
            }
            else {
                gizmo.size = 1200 / Math.max(canvas.clientWidth, canvas.clientHeight);
            }
        };
        updateGizmoSize();
        events.on('camera.resize', updateGizmoSize);
        events.on('camera.ortho', updateGizmoSize);
        this.activate = () => {
            this.active = true;
            scene.add(box);
            gizmo.attach([box.pivot]);
            selectToolbar.hidden = false;
        };
        this.deactivate = () => {
            selectToolbar.hidden = true;
            gizmo.detach();
            scene.remove(box);
            this.active = false;
        };
    }
}

class BrushSelection {
    activate;
    deactivate;
    constructor(events, parent, mask) {
        let radius = 40;
        // create svg
        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.id = 'brush-select-svg';
        svg.classList.add('select-svg');
        // create circle element
        const circle = document.createElementNS(svg.namespaceURI, 'circle');
        circle.setAttribute('r', radius.toString());
        circle.setAttribute('fill', 'rgba(255, 102, 0, 0.2)');
        circle.setAttribute('stroke', '#f60');
        circle.setAttribute('stroke-width', '1');
        circle.setAttribute('stroke-dasharray', '5, 5');
        const { canvas, context } = mask;
        const prev = { x: 0, y: 0 };
        let dragId;
        const update = (e) => {
            const x = e.offsetX;
            const y = e.offsetY;
            circle.setAttribute('cx', x.toString());
            circle.setAttribute('cy', y.toString());
            if (dragId !== undefined) {
                context.beginPath();
                context.strokeStyle = '#f60';
                context.lineCap = 'round';
                context.lineWidth = radius * 2;
                context.moveTo(prev.x, prev.y);
                context.lineTo(x, y);
                context.stroke();
                prev.x = x;
                prev.y = y;
            }
        };
        const pointerdown = (e) => {
            if (dragId === undefined && (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) {
                e.preventDefault();
                e.stopPropagation();
                dragId = e.pointerId;
                parent.setPointerCapture(dragId);
                // initialize canvas
                if (canvas.width !== parent.clientWidth || canvas.height !== parent.clientHeight) {
                    canvas.width = parent.clientWidth;
                    canvas.height = parent.clientHeight;
                }
                // clear canvas
                context.clearRect(0, 0, canvas.width, canvas.height);
                // display it
                canvas.style.display = 'inline';
                prev.x = e.offsetX;
                prev.y = e.offsetY;
                update(e);
            }
        };
        const pointermove = (e) => {
            if (dragId !== undefined) {
                e.preventDefault();
                e.stopPropagation();
            }
            update(e);
        };
        const dragEnd = () => {
            parent.releasePointerCapture(dragId);
            dragId = undefined;
            canvas.style.display = 'none';
        };
        const pointerup = (e) => {
            if (e.pointerId === dragId) {
                e.preventDefault();
                e.stopPropagation();
                dragEnd();
                events.fire('select.byMask', e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), canvas, context);
            }
        };
        const wheel = (e) => {
            if (e.altKey || e.metaKey) {
                const { deltaX, deltaY } = e;
                events.fire((Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY) > 0 ? 'tool.brushSelection.smaller' : 'tool.brushSelection.bigger');
                e.preventDefault();
                e.stopPropagation();
            }
        };
        this.activate = () => {
            svg.style.display = 'inline';
            parent.style.display = 'block';
            parent.addEventListener('pointerdown', pointerdown);
            parent.addEventListener('pointermove', pointermove);
            parent.addEventListener('pointerup', pointerup);
            parent.addEventListener('wheel', wheel);
        };
        this.deactivate = () => {
            // cancel active operation
            if (dragId !== undefined) {
                dragEnd();
            }
            svg.style.display = 'none';
            parent.style.display = 'none';
            parent.removeEventListener('pointerdown', pointerdown);
            parent.removeEventListener('pointermove', pointermove);
            parent.removeEventListener('pointerup', pointerup);
            parent.removeEventListener('wheel', wheel);
        };
        events.on('tool.brushSelection.smaller', () => {
            radius = Math.max(1, radius / 1.05);
            circle.setAttribute('r', radius.toString());
        });
        events.on('tool.brushSelection.bigger', () => {
            radius = Math.min(500, radius * 1.05);
            circle.setAttribute('r', radius.toString());
        });
        svg.appendChild(circle);
        parent.appendChild(svg);
    }
}

class LassoSelection {
    activate;
    deactivate;
    constructor(events, parent, mask) {
        let points = [];
        let currentPoint = null;
        let lastPointTime = 0;
        // create svg
        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.id = 'lasso-select-svg';
        svg.classList.add('select-svg');
        // create polygon element
        const polygon = document.createElementNS(svg.namespaceURI, 'polygon');
        polygon.setAttribute('fill', 'none');
        polygon.setAttribute('stroke-width', '1');
        polygon.setAttribute('stroke-dasharray', '5, 5');
        polygon.setAttribute('stroke-dashoffset', '0');
        // create canvas
        const { canvas, context } = mask;
        const dist = (a, b) => {
            return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
        };
        const isClosed = () => {
            return points.length > 1 && dist(currentPoint, points[0]) < 8;
        };
        const paint = () => {
            polygon.setAttribute('points', [...points, currentPoint].reduce((prev, current) => `${prev}${current.x}, ${current.y} `, ''));
            polygon.setAttribute('stroke', isClosed() ? '#fa6' : '#f60');
        };
        let dragId;
        const update = (e) => {
            currentPoint = { x: e.offsetX, y: e.offsetY };
            const distance = points.length === 0 ? 0 : dist(currentPoint, points[points.length - 1]);
            const millis = Date.now() - lastPointTime;
            const preventCorners = distance > 20;
            const slowNarrowSpacing = millis > 500 && distance > 2;
            const fasterMediumSpacing = millis > 200 && distance > 10;
            const firstPoints = points.length === 0;
            if (dragId !== undefined && (preventCorners || slowNarrowSpacing || fasterMediumSpacing || firstPoints)) {
                points.push(currentPoint);
                lastPointTime = Date.now();
            }
            paint();
        };
        const commitSelection = (e) => {
            // initialize canvas
            if (canvas.width !== parent.clientWidth || canvas.height !== parent.clientHeight) {
                canvas.width = parent.clientWidth;
                canvas.height = parent.clientHeight;
            }
            // clear canvas
            context.clearRect(0, 0, canvas.width, canvas.height);
            context.beginPath();
            context.fillStyle = '#f60';
            context.beginPath();
            points.forEach((p, idx) => {
                if (idx === 0) {
                    context.moveTo(p.x, p.y);
                }
                else {
                    context.lineTo(p.x, p.y);
                }
            });
            context.closePath();
            context.fill();
            events.fire('select.byMask', e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), canvas, context);
        };
        const pointerdown = (e) => {
            if (dragId === undefined && (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) {
                e.preventDefault();
                e.stopPropagation();
                dragId = e.pointerId;
                parent.setPointerCapture(dragId);
                update(e);
            }
        };
        const pointermove = (e) => {
            if (dragId !== undefined) {
                e.preventDefault();
                e.stopPropagation();
            }
            update(e);
        };
        const dragEnd = () => {
            parent.releasePointerCapture(dragId);
            dragId = undefined;
        };
        const pointerup = (e) => {
            if (e.pointerId === dragId) {
                e.preventDefault();
                e.stopPropagation();
                dragEnd();
                commitSelection(e);
                points = [];
                paint();
            }
        };
        this.activate = () => {
            svg.style.display = 'inline';
            parent.style.display = 'block';
            parent.addEventListener('pointerdown', pointerdown);
            parent.addEventListener('pointermove', pointermove);
            parent.addEventListener('pointerup', pointerup);
        };
        this.deactivate = () => {
            // cancel active operation
            if (dragId !== undefined) {
                dragEnd();
            }
            svg.style.display = 'none';
            parent.style.display = 'none';
            parent.removeEventListener('pointerdown', pointerdown);
            parent.removeEventListener('pointermove', pointermove);
            parent.removeEventListener('pointerup', pointerup);
        };
        svg.appendChild(polygon);
        parent.appendChild(svg);
    }
}

class TransformTool {
    activate;
    deactivate;
    constructor(gizmo, events, scene) {
        let pivot;
        let active = false;
        let dragging = false;
        // create the transform pivot
        const pivotEntity = new Entity('gizmoPivot');
        scene.app.root.addChild(pivotEntity);
        gizmo.on('render:update', () => {
            scene.forceRender = true;
        });
        gizmo.on('transform:start', () => {
            dragging = true;
            pivot.start();
        });
        gizmo.on('transform:move', () => {
            pivot.moveTRS(pivotEntity.getLocalPosition(), pivotEntity.getLocalRotation(), pivotEntity.getLocalScale());
            scene.forceRender = true;
        });
        gizmo.on('transform:end', () => {
            pivot.end();
            dragging = false;
        });
        // reattach the gizmo to the pivot
        const reattach = () => {
            if (!active || !events.invoke('selection')) {
                if (gizmo.enabled) {
                    gizmo.detach();
                }
            }
            else if (!dragging) {
                pivot = events.invoke('pivot');
                pivotEntity.setLocalPosition(pivot.transform.position);
                pivotEntity.setLocalRotation(pivot.transform.rotation);
                pivotEntity.setLocalScale(pivot.transform.scale);
                gizmo.attach([pivotEntity]);
            }
        };
        events.on('tool.coordSpace', (coordSpace) => {
            gizmo.coordSpace = coordSpace;
        });
        // set the gizmo size to remain a constant size in screen space.
        // called in response to changes in canvas size
        const updateGizmoSize = () => {
            const { camera, canvas } = scene;
            if (camera.ortho) {
                gizmo.size = 1125 / canvas.clientHeight;
            }
            else {
                gizmo.size = 1200 / Math.max(canvas.clientWidth, canvas.clientHeight);
            }
        };
        updateGizmoSize();
        events.on('camera.resize', updateGizmoSize);
        events.on('camera.ortho', updateGizmoSize);
        this.activate = () => {
            active = true;
            reattach();
            events.on('pivot.placed', reattach);
            events.on('pivot.moved', reattach);
            events.on('selection.changed', reattach);
        };
        this.deactivate = () => {
            active = false;
            reattach();
            events.off('pivot.placed', reattach);
            events.off('pivot.moved', reattach);
            events.off('selection.changed', reattach);
        };
        // initialize coodinate space
        gizmo.coordSpace = events.invoke('tool.coordSpace');
    }
}

class MoveTool extends TransformTool {
    constructor(events, scene) {
        const gizmo = new TranslateGizmo(scene.camera.entity.camera, scene.gizmoLayer);
        super(gizmo, events, scene);
    }
}

class PolygonSelection {
    activate;
    deactivate;
    constructor(events, parent, mask) {
        let points = [];
        let currentPoint = null;
        // create svg
        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.id = 'polygon-select-svg';
        svg.classList.add('select-svg');
        // create polyline element
        const polyline = document.createElementNS(svg.namespaceURI, 'polyline');
        polyline.setAttribute('fill', 'none');
        polyline.setAttribute('stroke-width', '1');
        polyline.setAttribute('stroke-dasharray', '5, 5');
        polyline.setAttribute('stroke-dashoffset', '0');
        // create canvas
        const { canvas, context } = mask;
        const dist = (a, b) => {
            return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
        };
        const isClosed = () => {
            return points.length > 1 && dist(currentPoint, points[0]) < 8;
        };
        const paint = () => {
            polyline.setAttribute('points', [...points, currentPoint].filter(v => v).reduce((prev, current) => `${prev}${current.x}, ${current.y} `, ''));
            polyline.setAttribute('stroke', isClosed() ? '#fa6' : '#f60');
        };
        const commitSelection = (e) => {
            // initialize canvas
            if (canvas.width !== parent.clientWidth || canvas.height !== parent.clientHeight) {
                canvas.width = parent.clientWidth;
                canvas.height = parent.clientHeight;
            }
            // clear canvas
            context.clearRect(0, 0, canvas.width, canvas.height);
            context.beginPath();
            context.fillStyle = '#f60';
            context.beginPath();
            points.forEach((p, idx) => {
                if (idx === 0) {
                    context.moveTo(p.x, p.y);
                }
                else {
                    context.lineTo(p.x, p.y);
                }
            });
            context.closePath();
            context.fill();
            events.fire('select.byMask', e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), canvas, context);
            points = [];
            paint();
        };
        const pointermove = (e) => {
            currentPoint = { x: e.offsetX, y: e.offsetY };
            if (points.length > 0) {
                paint();
            }
        };
        const pointerdown = (e) => {
            if (points.length > 0 || (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) {
                e.preventDefault();
                e.stopPropagation();
            }
        };
        const pointerup = (e) => {
            if (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary) {
                e.preventDefault();
                e.stopPropagation();
                if (isClosed()) {
                    commitSelection(e);
                }
                else if (points.length === 0 || dist(points[points.length - 1], currentPoint) > 0) {
                    points.push(currentPoint);
                }
            }
        };
        const dblclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            if (points.length > 2) {
                commitSelection(e);
            }
        };
        this.activate = () => {
            svg.style.display = 'inline';
            parent.style.display = 'block';
            parent.addEventListener('pointerdown', pointerdown);
            parent.addEventListener('pointermove', pointermove);
            parent.addEventListener('pointerup', pointerup);
            parent.addEventListener('dblclick', dblclick);
        };
        this.deactivate = () => {
            // cancel active operation
            svg.style.display = 'none';
            parent.style.display = 'none';
            parent.removeEventListener('pointerdown', pointerdown);
            parent.removeEventListener('pointermove', pointermove);
            parent.removeEventListener('pointerup', pointerup);
            parent.removeEventListener('dblclick', dblclick);
            points = [];
            paint();
        };
        svg.appendChild(polyline);
        parent.appendChild(svg);
    }
}

class RectSelection {
    activate;
    deactivate;
    constructor(events, parent) {
        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.id = 'rect-select-svg';
        svg.classList.add('select-svg');
        // create rect element
        const rect = document.createElementNS(svg.namespaceURI, 'rect');
        rect.setAttribute('fill', 'none');
        rect.setAttribute('stroke', '#f60');
        rect.setAttribute('stroke-width', '1');
        rect.setAttribute('stroke-dasharray', '5, 5');
        const start = { x: 0, y: 0 };
        const end = { x: 0, y: 0 };
        let dragId;
        let dragMoved = false;
        const updateRect = () => {
            const x = Math.min(start.x, end.x);
            const y = Math.min(start.y, end.y);
            const width = Math.abs(start.x - end.x);
            const height = Math.abs(start.y - end.y);
            rect.setAttribute('x', x.toString());
            rect.setAttribute('y', y.toString());
            rect.setAttribute('width', width.toString());
            rect.setAttribute('height', height.toString());
        };
        const pointerdown = (e) => {
            if (dragId === undefined && (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) {
                e.preventDefault();
                e.stopPropagation();
                dragId = e.pointerId;
                dragMoved = false;
                parent.setPointerCapture(dragId);
                start.x = end.x = e.offsetX;
                start.y = end.y = e.offsetY;
                updateRect();
                svg.style.display = 'inline';
            }
        };
        const pointermove = (e) => {
            if (e.pointerId === dragId) {
                e.preventDefault();
                e.stopPropagation();
                dragMoved = true;
                end.x = e.offsetX;
                end.y = e.offsetY;
                updateRect();
            }
        };
        const dragEnd = () => {
            parent.releasePointerCapture(dragId);
            dragId = undefined;
            svg.style.display = 'none';
        };
        const pointerup = (e) => {
            if (e.pointerId === dragId) {
                e.preventDefault();
                e.stopPropagation();
                const w = parent.clientWidth;
                const h = parent.clientHeight;
                dragEnd();
                if (dragMoved) {
                    // rect select
                    events.fire('select.rect', e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), {
                        start: { x: Math.min(start.x, end.x) / w, y: Math.min(start.y, end.y) / h },
                        end: { x: Math.max(start.x, end.x) / w, y: Math.max(start.y, end.y) / h }
                    });
                }
                else {
                    // pick
                    events.fire('select.point', e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), { x: e.offsetX / parent.clientWidth, y: e.offsetY / parent.clientHeight });
                }
            }
        };
        this.activate = () => {
            parent.style.display = 'block';
            parent.addEventListener('pointerdown', pointerdown);
            parent.addEventListener('pointermove', pointermove);
            parent.addEventListener('pointerup', pointerup);
        };
        this.deactivate = () => {
            if (dragId !== undefined) {
                dragEnd();
            }
            parent.style.display = 'none';
            parent.removeEventListener('pointerdown', pointerdown);
            parent.removeEventListener('pointermove', pointermove);
            parent.removeEventListener('pointerup', pointerup);
        };
        parent.appendChild(svg);
        svg.appendChild(rect);
    }
    destroy() {
    }
}

class RotateTool extends TransformTool {
    constructor(events, scene) {
        const gizmo = new RotateGizmo(scene.camera.entity.camera, scene.gizmoLayer);
        super(gizmo, events, scene);
    }
}

class ScaleTool extends TransformTool {
    constructor(events, scene) {
        const gizmo = new ScaleGizmo(scene.camera.entity.camera, scene.gizmoLayer);
        // disable everything except uniform scale
        ['x', 'y', 'z', 'yz', 'xz', 'xy'].forEach((axis) => {
            gizmo.enableShape(axis, false);
        });
        // set lower bound on scale
        gizmo.lowerBoundScale.set(1e-6, 1e-6, 1e-6);
        super(gizmo, events, scene);
    }
}

const vertexShader = /* glsl */ `
    attribute vec3 vertex_position;

    uniform mat4 matrix_model;
    uniform mat4 matrix_viewProjection;

    void main() {
        gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
    }
`;
const fragmentShader = /* glsl */ `
    bool intersectSphere(out float t0, out float t1, vec3 pos, vec3 dir, vec4 sphere) {
        vec3 L = sphere.xyz - pos;
        float tca = dot(L, dir);

        float d2 = sphere.w * sphere.w - (dot(L, L) - tca * tca);
        if (d2 <= 0.0) {
            return false;
        }

        float thc = sqrt(d2);
        t0 = tca - thc;
        t1 = tca + thc;
        if (t1 <= 0.0) {
            return false;
        }

        return true;
    }

    float calcDepth(in vec3 pos, in mat4 viewProjection) {
        vec4 v = viewProjection * vec4(pos, 1.0);
        return (v.z / v.w) * 0.5 + 0.5;
    }

    vec2 calcAzimuthElev(in vec3 dir) {
        float azimuth = atan(dir.z, dir.x);
        float elev = asin(dir.y);
        return vec2(azimuth, elev) * 180.0 / 3.14159;
    }

    uniform sampler2D blueNoiseTex32;
    uniform mat4 matrix_viewProjection;
    uniform vec4 sphere;

    uniform vec3 near_origin;
    uniform vec3 near_x;
    uniform vec3 near_y;

    uniform vec3 far_origin;
    uniform vec3 far_x;
    uniform vec3 far_y;

    uniform vec2 targetSize;

    bool writeDepth(float alpha) {
        vec2 uv = fract(gl_FragCoord.xy / 32.0);
        float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
        return alpha > noise;
    }

    bool strips(vec3 lp) {
        vec2 ae = calcAzimuthElev(normalize(lp));

        float spacing = 180.0 / (2.0 * 3.14159 * sphere.w);
        float size = 0.03;
        return fract(ae.x / spacing) < size ||
               fract(ae.y / spacing) < size;
    }

    void main() {
        vec2 clip = gl_FragCoord.xy / targetSize;
        vec3 worldNear = near_origin + near_x * clip.x + near_y * clip.y;
        vec3 worldFar = far_origin + far_x * clip.x + far_y * clip.y;

        vec3 rayDir = normalize(worldFar - worldNear);

        float t0, t1;
        if (!intersectSphere(t0, t1, worldNear, rayDir, sphere)) {
            discard;
        }

        vec3 frontPos = worldNear + rayDir * t0;
        bool front = t0 > 0.0 && strips(frontPos - sphere.xyz);

        vec3 backPos = worldNear + rayDir * t1;
        bool back = strips(backPos - sphere.xyz);

        if (front) {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 0.6);
            gl_FragDepth = writeDepth(0.6) ? calcDepth(frontPos, matrix_viewProjection) : 1.0;
        } else if (back) {
            gl_FragColor = vec4(0.0, 0.0, 0.0, 0.6);
            gl_FragDepth = writeDepth(0.6) ? calcDepth(backPos, matrix_viewProjection) : 1.0;
        } else {
            discard;
        }
    }
`;

const v$1 = new Vec3();
const bound = new BoundingBox();
class SphereShape extends Element {
    _radius = 1;
    pivot;
    material;
    constructor() {
        super(ElementType.debug);
        this.pivot = new Entity('spherePivot');
        this.pivot.addComponent('render', {
            type: 'box'
        });
        const r = this._radius * 2;
        this.pivot.setLocalScale(r, r, r);
    }
    add() {
        const material = new ShaderMaterial({
            uniqueName: 'sphereShape',
            vertexGLSL: vertexShader,
            fragmentGLSL: fragmentShader
        });
        material.cull = CULLFACE_FRONT;
        material.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA);
        material.update();
        this.pivot.render.meshInstances[0].material = material;
        this.pivot.render.layers = [this.scene.debugLayer.id];
        this.material = material;
        this.scene.contentRoot.addChild(this.pivot);
        this.updateBound();
    }
    remove() {
        this.scene.contentRoot.removeChild(this.pivot);
        this.scene.boundDirty = true;
    }
    destroy() {
    }
    serialize(serializer) {
        serializer.packa(this.pivot.getWorldTransform().data);
        serializer.pack(this.radius);
    }
    onPreRender() {
        this.pivot.getWorldTransform().getTranslation(v$1);
        this.material.setParameter('sphere', [v$1.x, v$1.y, v$1.z, this.radius]);
        const device = this.scene.graphicsDevice;
        device.scope.resolve('targetSize').setValue([device.width, device.height]);
    }
    moved() {
        this.updateBound();
    }
    updateBound() {
        bound.center.copy(this.pivot.getPosition());
        bound.halfExtents.set(this.radius, this.radius, this.radius);
        this.scene.boundDirty = true;
    }
    get worldBound() {
        return bound;
    }
    set radius(radius) {
        this._radius = radius;
        const r = this._radius * 2;
        this.pivot.setLocalScale(r, r, r);
        this.updateBound();
    }
    get radius() {
        return this._radius;
    }
}

class SphereSelection {
    activate;
    deactivate;
    active = false;
    constructor(events, scene, canvasContainer) {
        const sphere = new SphereShape();
        const gizmo = new TranslateGizmo(scene.camera.entity.camera, scene.gizmoLayer);
        gizmo.on('render:update', () => {
            scene.forceRender = true;
        });
        gizmo.on('transform:move', () => {
            sphere.moved();
        });
        // ui
        const selectToolbar = new Container({
            id: 'select-toolbar',
            hidden: true
        });
        selectToolbar.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
        });
        const setButton = new Button({ text: 'Set', class: 'select-toolbar-button' });
        const addButton = new Button({ text: 'Add', class: 'select-toolbar-button' });
        const removeButton = new Button({ text: 'Remove', class: 'select-toolbar-button' });
        const radius = new NumericInput({
            precision: 2,
            value: sphere.radius,
            placeholder: 'Radius',
            width: 80,
            min: 0.01
        });
        selectToolbar.append(setButton);
        selectToolbar.append(addButton);
        selectToolbar.append(removeButton);
        selectToolbar.append(radius);
        canvasContainer.append(selectToolbar);
        const apply = (op) => {
            const p = sphere.pivot.getPosition();
            events.fire('select.bySphere', op, [p.x, p.y, p.z, sphere.radius]);
        };
        setButton.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
            apply('set');
        });
        addButton.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
            apply('add');
        });
        removeButton.dom.addEventListener('pointerdown', (e) => {
            e.stopPropagation();
            apply('remove');
        });
        radius.on('change', () => {
            sphere.radius = radius.value;
        });
        events.on('camera.focalPointPicked', (details) => {
            if (this.active) {
                sphere.pivot.setPosition(details.position);
                gizmo.attach([sphere.pivot]);
            }
        });
        const updateGizmoSize = () => {
            const { camera, canvas } = scene;
            if (camera.ortho) {
                gizmo.size = 1125 / canvas.clientHeight;
            }
            else {
                gizmo.size = 1200 / Math.max(canvas.clientWidth, canvas.clientHeight);
            }
        };
        updateGizmoSize();
        events.on('camera.resize', updateGizmoSize);
        events.on('camera.ortho', updateGizmoSize);
        this.activate = () => {
            this.active = true;
            scene.add(sphere);
            gizmo.attach([sphere.pivot]);
            selectToolbar.hidden = false;
        };
        this.deactivate = () => {
            selectToolbar.hidden = true;
            gizmo.detach();
            scene.remove(sphere);
            this.active = false;
        };
    }
}

class ToolManager {
    tools = new Map();
    events;
    active = null;
    constructor(events) {
        this.events = events;
        this.events.on('tool.deactivate', () => {
            this.activate(null);
        });
        this.events.function('tool.active', () => {
            return this.active;
        });
        let coordSpace = 'world';
        const setCoordSpace = (space) => {
            if (space !== coordSpace) {
                coordSpace = space;
                events.fire('tool.coordSpace', coordSpace);
            }
        };
        events.function('tool.coordSpace', () => {
            return coordSpace;
        });
        events.on('tool.setCoordSpace', (value) => {
            setCoordSpace(value);
        });
        events.on('tool.toggleCoordSpace', () => {
            setCoordSpace(coordSpace === 'local' ? 'world' : 'local');
        });
    }
    register(name, tool) {
        this.tools.set(name, tool);
        this.events.on(`tool.${name}`, () => {
            this.activate(name);
        });
    }
    get(toolName) {
        return (toolName && this.tools.get(toolName)) ?? null;
    }
    activate(toolName) {
        if (toolName === this.active) {
            // re-activating the currently active tool deactivates it
            if (toolName) {
                this.activate(null);
            }
        }
        else {
            // deactive old tool
            if (this.active) {
                const tool = this.tools.get(this.active);
                tool.deactivate();
                this.events.fire(`tool.${this.active}.deactivated`);
                this.events.fire('tool.deactivated', this.active);
            }
            this.active = toolName;
            // activate the new
            if (this.active) {
                const tool = this.tools.get(this.active);
                tool.activate();
            }
            this.events.fire(`tool.${toolName}.activated`);
            this.events.fire('tool.activated', toolName);
        }
    }
}

let Transform$1 = class Transform {
    position = new Vec3();
    rotation = new Quat();
    scale = new Vec3(1, 1, 1);
    constructor(position, rotation, scale) {
        this.set(position, rotation, scale);
    }
    set(position, rotation, scale) {
        if (position) {
            this.position.copy(position);
        }
        if (rotation) {
            this.rotation.copy(rotation);
        }
        if (scale) {
            this.scale.copy(scale);
        }
    }
    copy(transform) {
        this.position.copy(transform.position);
        this.rotation.copy(transform.rotation);
        this.scale.copy(transform.scale);
    }
    clone() {
        return new Transform(this.position.clone(), this.rotation.clone(), this.scale.clone());
    }
    equals(transform) {
        return this.position.equals(transform.position) &&
            this.rotation.equals(transform.rotation) &&
            this.scale.equals(transform.scale);
    }
    equalsApprox(transform, epsilon = 1e-6) {
        return this.position.equalsApprox(transform.position, epsilon) &&
            this.rotation.equalsApprox(transform.rotation, epsilon) &&
            this.scale.equalsApprox(transform.scale, epsilon);
    }
    equalsTRS(position, rotation, scale) {
        return this.position.equals(position) &&
            this.rotation.equals(rotation) &&
            this.scale.equals(scale);
    }
    equalsApproxTRS(position, rotation, scale, epsilon = 1e-6) {
        return this.position.equalsApprox(position, epsilon) &&
            this.rotation.equalsApprox(rotation, epsilon) &&
            this.scale.equalsApprox(scale, epsilon);
    }
};

const mat$1 = new Mat4();
const quat = new Quat();
const transform$1 = new Transform$1();
class EntityTransformHandler {
    events;
    splat;
    top;
    pop;
    bindMat = new Mat4();
    constructor(events) {
        this.events = events;
        events.on('pivot.started', (pivot) => {
            if (this.splat) {
                this.start();
            }
        });
        events.on('pivot.moved', (pivot) => {
            if (this.splat) {
                this.update(pivot.transform);
            }
        });
        events.on('pivot.ended', (pivot) => {
            if (this.splat) {
                this.end();
            }
        });
        events.on('pivot.origin', (mode) => {
            if (this.splat) {
                this.placePivot();
            }
        });
        events.on('camera.focalPointPicked', (details) => {
            if (this.splat && ['move', 'rotate', 'scale'].includes(this.events.invoke('tool.active'))) {
                const pivot = events.invoke('pivot');
                const newt = new Transform$1(details.position, pivot.transform.rotation, pivot.transform.scale);
                const op = new PlacePivotOp({ pivot, oldt: pivot.transform.clone(), newt });
                events.fire('edit.add', op);
            }
        });
    }
    placePivot() {
        // place initial pivot point
        const origin = this.events.invoke('pivot.origin');
        this.splat.getPivot(origin === 'center' ? 'center' : 'boundCenter', false, transform$1);
        this.events.fire('pivot.place', transform$1);
    }
    activate() {
        this.splat = this.events.invoke('selection');
        if (this.splat) {
            this.placePivot();
        }
    }
    deactivate() {
        this.splat = null;
    }
    start() {
        const pivot = this.events.invoke('pivot');
        const { transform } = pivot;
        const { entity } = this.splat;
        // calculate bind matrix
        this.bindMat.setTRS(transform.position, transform.rotation, transform.scale);
        this.bindMat.invert();
        this.bindMat.mul2(this.bindMat, entity.getLocalTransform());
        const p = entity.getLocalPosition();
        const r = entity.getLocalRotation();
        const s = entity.getLocalScale();
        // create op
        this.top = new EntityTransformOp({
            splat: this.splat,
            oldt: new Transform$1(p, r, s),
            newt: new Transform$1(p, r, s)
        });
        this.pop = new PlacePivotOp({
            pivot,
            oldt: transform.clone(),
            newt: transform.clone()
        });
    }
    update(transform) {
        mat$1.setTRS(transform.position, transform.rotation, transform.scale);
        mat$1.mul2(mat$1, this.bindMat);
        quat.setFromMat4(mat$1);
        const t = mat$1.getTranslation();
        const r = quat;
        const s = mat$1.getScale();
        this.splat.move(t, r, s);
        this.top.newt.set(t, r, s);
        this.pop.newt.copy(transform);
    }
    end() {
        // if anything changed then register the op with undo/redo system
        const { oldt, newt } = this.top;
        if (!oldt.equals(newt)) {
            this.events.fire('edit.add', new MultiOp([this.top, this.pop]));
        }
        this.top = null;
        this.pop = null;
    }
}

// stores the transform pivot location in world space
// the transform tools (translate, rotate, scale) and transform panel modify this pivot
// then the active transform handler applies the changes to the current selection.
class Pivot {
    transform = new Transform$1();
    place;
    start;
    move;
    moveTRS;
    end;
    constructor(events) {
        this.place = (transform) => {
            if (!this.transform.equals(transform)) {
                this.transform.copy(transform);
                events.fire('pivot.placed', this);
            }
        };
        this.start = () => {
            events.fire('pivot.started', this);
        };
        this.move = (transform) => {
            if (!this.transform.equals(transform)) {
                this.transform.copy(transform);
                events.fire('pivot.moved', this);
            }
        };
        this.moveTRS = (position, rotation, scale) => {
            if (!this.transform.equalsTRS(position, rotation, scale)) {
                this.transform.set(position, rotation, scale);
                events.fire('pivot.moved', this);
            }
        };
        this.end = () => {
            events.fire('pivot.ended', this);
        };
        events.on('pivot.place', this.place);
        events.on('pivot.start', this.start);
        events.on('pivot.moveTRS', this.moveTRS);
        events.on('pivot.end', this.end);
    }
}
const registerPivotEvents = (events) => {
    const pivot = new Pivot(events);
    events.function('pivot', () => {
        return pivot;
    });
    // pivot mode
    let origin = 'center';
    const setOrigin = (o) => {
        if (o !== origin) {
            origin = o;
            events.fire('pivot.origin', origin);
        }
    };
    events.function('pivot.origin', () => {
        return origin;
    });
    events.on('pivot.setOrigin', (o) => {
        setOrigin(o === 'center' ? 'center' : 'boundCenter');
    });
    events.on('pivot.toggleOrigin', () => {
        setOrigin(origin === 'center' ? 'boundCenter' : 'center');
    });
};

const mat = new Mat4();
const mat2 = new Mat4();
const transform = new Transform$1();
class SplatsTransformHandler {
    events;
    splat;
    pivotStart = new Transform$1();
    localToPivot = new Mat4();
    worldToLocal = new Mat4();
    transform = new Mat4();
    paletteMap = new Map();
    constructor(events) {
        this.events = events;
        events.on('pivot.started', (pivot) => {
            if (this.splat) {
                this.start();
            }
        });
        events.on('pivot.moved', (pivot) => {
            if (this.splat) {
                this.update(pivot.transform);
            }
        });
        events.on('pivot.ended', (pivot) => {
            if (this.splat) {
                this.end();
            }
        });
        events.on('selection.changed', (splat) => {
            if (this.splat && splat === this.splat) {
                this.placePivot();
            }
        });
        events.on('pivot.origin', (mode) => {
            if (this.splat) {
                this.placePivot();
            }
        });
        events.on('camera.focalPointPicked', (details) => {
            if (this.splat && ['move', 'rotate', 'scale'].includes(this.events.invoke('tool.active'))) {
                const pivot = events.invoke('pivot');
                const oldt = pivot.transform.clone();
                const newt = new Transform$1(details.position, pivot.transform.rotation, pivot.transform.scale);
                const op = new PlacePivotOp({ pivot, oldt, newt });
                events.fire('edit.add', op);
            }
        });
    }
    placePivot() {
        const origin = this.events.invoke('pivot.origin');
        this.splat.getPivot(origin === 'center' ? 'center' : 'boundCenter', true, transform);
        this.events.fire('pivot.place', transform);
    }
    activate() {
        this.splat = this.events.invoke('selection');
        if (this.splat) {
            this.placePivot();
        }
    }
    deactivate() {
        this.splat = null;
    }
    start() {
        const pivot = this.events.invoke('pivot');
        const { transform } = pivot;
        const { splat } = this;
        const { transformPalette } = splat;
        mat.setTRS(transform.position, transform.rotation, transform.scale);
        // calculate local -> pivot transform
        this.localToPivot.invert(mat);
        this.localToPivot.mul2(this.localToPivot, splat.entity.getLocalTransform());
        // calculate the world -> local transform
        this.worldToLocal.invert(splat.entity.getLocalTransform());
        this.pivotStart.copy(transform);
        // allocate a new transform for the current selection
        const state = splat.splatData.getProp('state');
        const indices = splat.transformTexture.lock();
        const { paletteMap } = this;
        paletteMap.clear();
        for (let i = 0; i < state.length; ++i) {
            if (state[i] === State.selected) {
                const oldIdx = indices[i];
                let newIdx;
                if (!paletteMap.has(oldIdx)) {
                    newIdx = transformPalette.alloc();
                    paletteMap.set(oldIdx, newIdx);
                }
                else {
                    newIdx = paletteMap.get(oldIdx);
                }
                indices[i] = newIdx;
            }
        }
        splat.transformTexture.unlock();
        // initialize transforms
        this.paletteMap.forEach((newIdx, oldIdx) => {
            transformPalette.getTransform(oldIdx, mat);
            transformPalette.setTransform(newIdx, mat);
        });
        splat.selectionAlpha = 0;
        splat.scene.outline.enabled = false;
        splat.scene.underlay.enabled = false;
    }
    update(transform) {
        // calculate updated new pivot -> world transform
        mat.setTRS(transform.position, transform.rotation, transform.scale);
        mat.mul2(mat, this.localToPivot); // local -> world
        mat.mul2(this.worldToLocal, mat); // world -> local
        this.transform.copy(mat);
        // update the transform palette
        const { transformPalette } = this.splat;
        this.paletteMap.forEach((newIdx, oldIdx) => {
            transformPalette.getTransform(oldIdx, mat2);
            mat2.mul2(mat, mat2);
            transformPalette.setTransform(newIdx, mat2);
        });
        this.splat.makeSelectionBoundDirty();
    }
    end() {
        const { splat, transform, paletteMap } = this;
        // TODO: consider moving this to update() function above so splats are sorted correctly
        // for render during drag (which is slower).
        splat.updatePositions();
        splat.selectionAlpha = 1;
        splat.scene.outline.enabled = true;
        splat.scene.underlay.enabled = true;
        // create op for splat transform
        const top = new SplatsTransformOp({
            splat,
            transform: transform.clone(),
            paletteMap: new Map(paletteMap)
        });
        // create op for pivot placement
        const pivot = this.events.invoke('pivot');
        const oldt = this.pivotStart.clone();
        const newt = pivot.transform.clone();
        const pop = new PlacePivotOp({ pivot, newt, oldt });
        // add the editop without applying it
        this.events.fire('edit.add', new MultiOp([top, pop]), true);
    }
}

const registerTransformHandlerEvents = (events) => {
    let transformHandler = null;
    const setTransformHandler = (handler) => {
        if (transformHandler) {
            transformHandler.deactivate();
        }
        transformHandler = handler;
        if (transformHandler) {
            transformHandler.activate();
        }
    };
    // bind transform target when selection changes
    const entityTransformHandler = new EntityTransformHandler(events);
    const splatsTransformHandler = new SplatsTransformHandler(events);
    const update = (splat) => {
        if (!splat) {
            setTransformHandler(null);
        }
        else {
            if (splat.numSelected > 0) {
                setTransformHandler(splatsTransformHandler);
            }
            else {
                setTransformHandler(entityTransformHandler);
            }
        }
    };
    events.on('selection.changed', update);
    events.on('splat.stateChanged', update);
    registerPivotEvents(events);
};

const rgb2hsv = (rgb) => {
    const r = rgb.r;
    const g = rgb.g;
    const b = rgb.b;
    const v = Math.max(r, g, b);
    const diff = v - Math.min(r, g, b);
    const diffc = (c) => {
        return (v - c) / 6 / diff + 1 / 2;
    };
    let h, s;
    if (diff === 0) {
        h = s = 0;
    }
    else {
        s = diff / v;
        const rr = diffc(r);
        const gg = diffc(g);
        const bb = diffc(b);
        if (r === v) {
            h = bb - gg;
        }
        else if (g === v) {
            h = (1 / 3) + rr - bb;
        }
        else if (b === v) {
            h = (2 / 3) + gg - rr;
        }
        if (h < 0) {
            h += 1;
        }
        else if (h > 1) {
            h -= 1;
        }
    }
    return { h, s, v };
};

class HistogramData {
    bins;
    numValues;
    minValue;
    maxValue;
    constructor(numBins) {
        this.bins = [];
        for (let i = 0; i < numBins; ++i) {
            this.bins.push({ selected: 0, unselected: 0 });
        }
    }
    calc(count, valueFunc, selectedFunc) {
        // clear bins
        const bins = this.bins;
        for (let i = 0; i < bins.length; ++i) {
            bins[i].selected = bins[i].unselected = 0;
        }
        // calculate min, max
        let min, max, i;
        for (i = 0; i < count; i++) {
            const v = valueFunc(i);
            if (v !== undefined && isFinite(v)) {
                min = max = v;
                break;
            }
        }
        // no data
        if (i === count) {
            return;
        }
        // continue min/max calc
        for (; i < count; i++) {
            const v = valueFunc(i);
            if (v !== undefined && isFinite(v)) {
                if (v < min)
                    min = v;
                else if (v > max)
                    max = v;
            }
        }
        // fill bins
        for (let i = 0; i < count; i++) {
            const v = valueFunc(i);
            if (v !== undefined && isFinite(v)) {
                const n = min === max ? 0 : (v - min) / (max - min);
                const bin = Math.min(bins.length - 1, Math.floor(n * bins.length));
                if (selectedFunc(i)) {
                    bins[bin].selected++;
                }
                else {
                    bins[bin].unselected++;
                }
            }
        }
        this.numValues = bins.reduce((t, v) => t + v.selected + v.unselected, 0);
        this.minValue = min;
        this.maxValue = max;
    }
    bucketValue(bucket) {
        return this.minValue + bucket * this.bucketSize;
    }
    get bucketSize() {
        return (this.maxValue - this.minValue) / this.bins.length;
    }
    valueToBucket(value) {
        const n = this.minValue === this.maxValue ? 0 : (value - this.minValue) / (this.maxValue - this.minValue);
        return Math.min(this.bins.length - 1, Math.floor(n * this.bins.length));
    }
}
class Histogram {
    canvas;
    context;
    histogram;
    pixelData;
    events = new Events();
    constructor(numBins, height) {
        const canvas = document.createElement('canvas');
        canvas.setAttribute('id', 'histogram-canvas');
        canvas.width = numBins;
        canvas.height = height;
        canvas.style.width = '100%';
        canvas.style.height = '100%';
        const context = canvas.getContext('2d');
        context.globalCompositeOperation = 'copy';
        context.fillStyle = 'black';
        context.fillRect(0, 0, canvas.width, canvas.height);
        this.canvas = canvas;
        this.context = context;
        this.histogram = new HistogramData(numBins);
        this.pixelData = context.createImageData(canvas.width, canvas.height);
        let dragging = false;
        let dragStart = 0;
        let dragEnd = 0;
        const offsetToBucket = (offset) => {
            const rect = this.canvas.getBoundingClientRect();
            const bins = this.histogram.bins.length;
            return Math.max(0, Math.min(bins - 1, Math.floor((offset - rect.left) / rect.width * bins)));
        };
        const bucketToOffset = (bucket) => {
            const rect = this.canvas.getBoundingClientRect();
            return bucket / this.histogram.bins.length * rect.width;
        };
        const updateHighlight = () => {
            const rect = this.canvas.getBoundingClientRect();
            const start = Math.min(dragStart, dragEnd);
            const end = Math.max(dragStart, dragEnd);
            this.events.fire('highlight', {
                x: bucketToOffset(start),
                y: 0,
                width: (end - start + 1) / this.histogram.bins.length * rect.width,
                height: rect.height
            });
        };
        this.canvas.addEventListener('pointerdown', (e) => {
            e.preventDefault();
            e.stopPropagation();
            const h = this.histogram;
            if (h.numValues) {
                this.canvas.setPointerCapture(e.pointerId);
                dragging = true;
                dragStart = dragEnd = offsetToBucket(e.clientX);
                updateHighlight();
            }
        });
        this.canvas.addEventListener('pointerup', (e) => {
            e.preventDefault();
            e.stopPropagation();
            if (dragging) {
                this.canvas.releasePointerCapture(e.pointerId);
                const op = e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set');
                this.events.fire('select', op, Math.min(dragStart, dragEnd), Math.max(dragStart, dragEnd));
                dragging = false;
            }
        });
        this.canvas.addEventListener('pointermove', (e) => {
            e.preventDefault();
            e.stopPropagation();
            const h = this.histogram;
            if (h.numValues) {
                if (dragging) {
                    dragEnd = offsetToBucket(e.clientX);
                    updateHighlight();
                }
                const rect = this.canvas.getBoundingClientRect();
                const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
                const binIndex = Math.min(h.bins.length - 1, Math.floor(x * h.bins.length));
                const bin = h.bins[binIndex];
                this.events.fire('updateOverlay', {
                    x: e.offsetX,
                    y: e.offsetY,
                    value: h.bucketValue(binIndex),
                    size: h.bucketSize,
                    selected: bin.selected,
                    unselected: bin.unselected,
                    total: h.numValues
                });
            }
        });
        this.canvas.addEventListener('pointerenter', (e) => {
            this.events.fire('showOverlay');
        });
        this.canvas.addEventListener('pointerleave', (e) => {
            this.events.fire('hideOverlay');
        });
    }
    update(options) {
        // update histogram data
        this.histogram.calc(options.count, options.valueFunc, options.selectedFunc);
        // draw histogram
        const canvas = this.canvas;
        const context = this.context;
        const pixelData = this.pixelData;
        const pixels = new Uint32Array(pixelData.data.buffer);
        const binMap = options.logScale ? (x) => Math.log(x + 1) : (x) => x;
        const bins = this.histogram.bins.map((v) => {
            return {
                selected: binMap(v.unselected + v.selected),
                unselected: binMap(v.unselected)
            };
        });
        const binMax = bins.reduce((a, v) => Math.max(a, v.selected), 0);
        let i = 0;
        for (let y = 0; y < canvas.height; y++) {
            for (let x = 0; x < bins.length; x++) {
                const bin = bins[x];
                const targetMin = binMax / canvas.height * (canvas.height - 1 - y);
                if (targetMin >= bin.selected) {
                    pixels[i++] = 0xff000000;
                }
                else {
                    const targetMax = targetMin + binMax / canvas.height;
                    if (bin.selected === bin.unselected || targetMax < bin.unselected) {
                        pixels[i++] = 0xffff7777;
                    }
                    else {
                        pixels[i++] = 0xff00ffff;
                    }
                }
            }
        }
        context.putImageData(pixelData, 0, 0);
    }
}

const SH_C0 = 0.28209479177387814;
const scaleFunc = (v) => Math.exp(v);
const colorFunc = (v) => 0.5 + v * SH_C0;
const sigmoid = (v) => 1 / (1 + Math.exp(-v));
const dataFuncs = {
    scale_0: scaleFunc,
    scale_1: scaleFunc,
    scale_2: scaleFunc,
    f_dc_0: colorFunc,
    f_dc_1: colorFunc,
    f_dc_2: colorFunc,
    opacity: sigmoid
};
// build a separator label
const sepLabel = (labelText) => {
    const container = new Container({
        class: 'control-parent',
        id: 'sep-container'
    });
    container.class.add('sep-container');
    const label = new Label({
        class: 'control-element-expand',
        text: labelText
    });
    container.append(label);
    return container;
};
// build a data label
const dataLabel = (parent, labelText) => {
    const container = new Container({
        class: 'control-parent'
    });
    const label = new Label({
        class: 'control-label',
        text: labelText
    });
    const value = new Label({
        class: 'control-element-expand'
    });
    container.append(label);
    container.append(value);
    parent.append(container);
    return value;
};
class DataPanel extends Panel {
    constructor(events, args = {}) {
        args = {
            ...args,
            headerText: localize('data'),
            id: 'data-panel',
            resizable: 'top',
            resizeMax: 1000,
            collapsed: true,
            collapsible: true,
            collapseHorizontally: false,
            flex: true,
            flexDirection: 'row'
        };
        super(args);
        // build the data controls
        const controlsContainer = new Container({
            id: 'data-controls-container'
        });
        const controls = new Container({
            id: 'data-controls'
        });
        const dataSelector = new SelectInput({
            class: 'control-element-expand',
            defaultValue: 'surface-area',
            options: []
        });
        const populateDataSelector = (splat) => {
            const localisations = {
                x: 'X',
                y: 'Y',
                z: 'Z',
                distance: localize('data.distance'),
                volume: localize('data.volume'),
                'surface-area': localize('data.surface-area'),
                scale_0: localize('data.scale-x'),
                scale_1: localize('data.scale-y'),
                scale_2: localize('data.scale-z'),
                f_dc_0: localize('data.red'),
                f_dc_1: localize('data.green'),
                f_dc_2: localize('data.blue'),
                opacity: localize('data.opacity'),
                hue: localize('data.hue'),
                saturation: localize('data.saturation'),
                value: localize('data.value')
            };
            const dataProps = splat.splatData.getElement('vertex').properties.map(p => p.name);
            const derivedProps = ['distance', 'volume', 'surface-area', 'hue', 'saturation', 'value'];
            const suppressedProps = ['state', 'transform'].concat(new Array(45).fill('').map((_, i) => `f_rest_${i}`));
            const allProps = dataProps.concat(derivedProps).filter(p => !suppressedProps.includes(p));
            const options = allProps.map((prop) => {
                return {
                    v: prop,
                    t: localisations[prop] ?? prop
                };
            });
            dataSelector.options = options;
        };
        const logScale = new Container({
            class: 'control-parent'
        });
        const logScaleLabel = new Label({
            class: 'control-label',
            text: localize('data.log-scale')
        });
        const logScaleValue = new BooleanInput({
            class: 'control-element',
            value: false
        });
        logScale.append(logScaleLabel);
        logScale.append(logScaleValue);
        controls.append(dataSelector);
        controls.append(logScale);
        controls.append(sepLabel(localize('data.totals')));
        const splatsValue = dataLabel(controls, localize('data.totals.splats'));
        const selectedValue = dataLabel(controls, localize('data.totals.selected'));
        const lockedValue = dataLabel(controls, localize('data.totals.locked'));
        const deletedValue = dataLabel(controls, localize('data.totals.deleted'));
        controlsContainer.append(controls);
        // build histogram
        const histogram = new Histogram(256, 128);
        const histogramContainer = new Container({
            id: 'histogram-container'
        });
        histogramContainer.dom.appendChild(histogram.canvas);
        this.content.append(controlsContainer);
        this.content.append(histogramContainer);
        // current splat
        let splat;
        // returns a function which will interpret the splat data for purposes of
        // viewing it in the histogram.
        // the returned values will depend on the currently selected data type:
        //   * some value functions return the raw splat data, like 'x'.
        //   * other value functions must transform the data for histogram visualization
        //     (for example 'scale_0', which must be exponentiated).
        //   * still other values are calculated/derived from multiple values of splat
        //     data like 'volume' and 'surface area'.
        const getValueFunc = () => {
            // @ts-ignore
            const dataFunc = dataFuncs[dataSelector.value];
            const data = splat.splatData.getProp(dataSelector.value);
            let func;
            if (dataFunc && data) {
                func = i => dataFunc(data[i]);
            }
            else {
                switch (dataSelector.value) {
                    case 'volume': {
                        const sx = splat.splatData.getProp('scale_0');
                        const sy = splat.splatData.getProp('scale_1');
                        const sz = splat.splatData.getProp('scale_2');
                        func = i => scaleFunc(sx[i]) * scaleFunc(sy[i]) * scaleFunc(sz[i]);
                        break;
                    }
                    case 'distance': {
                        const x = splat.splatData.getProp('x');
                        const y = splat.splatData.getProp('y');
                        const z = splat.splatData.getProp('z');
                        func = i => Math.sqrt(x[i] ** 2 + y[i] ** 2 + z[i] ** 2);
                        break;
                    }
                    case 'surface-area': {
                        const sx = splat.splatData.getProp('scale_0');
                        const sy = splat.splatData.getProp('scale_1');
                        const sz = splat.splatData.getProp('scale_2');
                        func = i => scaleFunc(sx[i]) ** 2 + scaleFunc(sy[i]) ** 2 + scaleFunc(sz[i]) ** 2;
                        break;
                    }
                    case 'hue': {
                        const r = splat.splatData.getProp('f_dc_0');
                        const g = splat.splatData.getProp('f_dc_1');
                        const b = splat.splatData.getProp('f_dc_2');
                        func = i => rgb2hsv({ r: colorFunc(r[i]), g: colorFunc(g[i]), b: colorFunc(b[i]) }).h * 360;
                        break;
                    }
                    case 'saturation': {
                        const r = splat.splatData.getProp('f_dc_0');
                        const g = splat.splatData.getProp('f_dc_1');
                        const b = splat.splatData.getProp('f_dc_2');
                        func = i => rgb2hsv({ r: colorFunc(r[i]), g: colorFunc(g[i]), b: colorFunc(b[i]) }).s;
                        break;
                    }
                    case 'value': {
                        const r = splat.splatData.getProp('f_dc_0');
                        const g = splat.splatData.getProp('f_dc_1');
                        const b = splat.splatData.getProp('f_dc_2');
                        func = i => rgb2hsv({ r: colorFunc(r[i]), g: colorFunc(g[i]), b: colorFunc(b[i]) }).v;
                        break;
                    }
                    default:
                        func = i => data[i];
                        break;
                }
            }
            return func;
        };
        const updateHistogram = () => {
            if (!splat || this.collapsed)
                return;
            const state = splat.splatData.getProp('state');
            if (state) {
                splatsValue.text = (state.length - splat.numDeleted).toString();
                selectedValue.text = splat.numSelected.toString();
                lockedValue.text = splat.numLocked.toString();
                deletedValue.text = splat.numDeleted.toString();
                // update histogram
                const func = getValueFunc();
                // update histogram
                histogram.update({
                    count: state.length,
                    valueFunc: i => ((state[i] === 0 || state[i] === State.selected) ? func(i) : undefined),
                    selectedFunc: i => state[i] === State.selected,
                    logScale: logScaleValue.value
                });
            }
        };
        this.on('expand', () => {
            updateHistogram();
        });
        events.on('splat.stateChanged', (splat_) => {
            splat = splat_;
            updateHistogram();
        });
        events.on('selection.changed', (selection) => {
            if (selection instanceof Splat) {
                splat = selection;
                populateDataSelector(splat);
                updateHistogram();
            }
        });
        events.on('dataPanel.toggle', () => {
            this.collapsed = !this.collapsed;
            updateHistogram();
        });
        dataSelector.on('change', updateHistogram);
        logScaleValue.on('change', updateHistogram);
        const popupContainer = new Container({
            id: 'data-panel-popup-container',
            hidden: true
        });
        const popupLabel = new Label({
            id: 'data-panel-popup-label',
            text: '',
            unsafe: true
        });
        popupContainer.append(popupLabel);
        this.content.append(popupContainer);
        histogram.events.on('showOverlay', () => {
            popupContainer.hidden = false;
        });
        histogram.events.on('hideOverlay', () => {
            popupContainer.hidden = true;
        });
        histogram.events.on('updateOverlay', (info) => {
            popupContainer.style.left = `${info.x + 14}px`;
            popupContainer.style.top = `${info.y}px`;
            const binValue = info.value.toFixed(2);
            const count = info.selected + info.unselected;
            const percentage = (info.total ? count / info.total * 100 : 0).toFixed(2);
            popupLabel.text = `value: ${binValue} cnt: ${count} (${percentage}%) sel: ${info.selected}`;
        });
        // highlight
        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('id', 'histogram-svg');
        // create rect element
        const rect = document.createElementNS(svg.namespaceURI, 'rect');
        rect.setAttribute('id', 'highlight-rect');
        rect.setAttribute('fill', 'rgba(255, 102, 0, 0.2)');
        rect.setAttribute('stroke', '#f60');
        rect.setAttribute('stroke-width', '1');
        rect.setAttribute('stroke-dasharray', '5, 5');
        svg.appendChild(rect);
        histogramContainer.dom.appendChild(svg);
        histogram.events.on('highlight', (info) => {
            rect.setAttribute('x', info.x.toString());
            rect.setAttribute('y', info.y.toString());
            rect.setAttribute('width', info.width.toString());
            rect.setAttribute('height', info.height.toString());
            svg.style.display = 'inline';
        });
        histogram.events.on('select', (op, start, end) => {
            svg.style.display = 'none';
            const state = splat.splatData.getProp('state');
            state.some(s => s === State.selected);
            const func = getValueFunc();
            // perform selection
            events.fire('select.pred', op, (i) => {
                if (state[i] !== 0 && state[i] !== State.selected) {
                    return false;
                }
                // select all splats that fall in the given bucket range (inclusive)
                const value = func(i);
                const bucket = histogram.histogram.valueToBucket(value);
                return bucket >= start && bucket <= end;
            });
        });
    }
}

var img$w = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M25.1369 19.0001C24.602 19.0001 24.1696 19.4201 24.1696 19.9376C24.1696 22.5222 22.0001 24.625 19.3333 24.625C16.6666 24.625 14.497 22.5222 14.497 19.9376C14.497 17.4411 16.5273 15.4124 19.0693 15.2764L18.812 15.5249C18.4338 15.8914 18.4338 16.4839 18.812 16.8505C19.0006 17.0333 19.2482 17.1251 19.4958 17.1251C19.7435 17.1251 19.9911 17.0333 20.1797 16.8505L22.1142 14.9755C22.4924 14.6089 22.4924 14.0164 22.1142 13.6499L20.1797 11.7749C19.8015 11.4084 19.1902 11.4084 18.812 11.7749C18.4338 12.1415 18.4338 12.734 18.812 13.1005L19.107 13.3865C15.4798 13.5036 12.5625 16.393 12.5625 19.9376C12.5625 23.5563 15.6007 26.5 19.3333 26.5C23.066 26.5 26.1042 23.5563 26.1042 19.9376C26.1042 19.4201 25.6718 19.0001 25.1369 19.0001' fill='currentColor'/%3e%3c/svg%3e";

var img$v = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M11 19C11 14.5817 14.5817 11 19 11V11C23.4183 11 27 14.5817 27 19V19C27 23.4183 23.4183 27 19 27V27C14.5817 27 11 23.4183 11 19V19Z' stroke='currentColor' stroke-width='1.5' stroke-dasharray='4 2' fill-opacity='0' /%3e%3cpath d='M15 19C15 16.7909 16.7909 15 19 15V15C21.2091 15 23 16.7909 23 19V19C23 21.2091 21.2091 23 19 23V23C16.7909 23 15 21.2091 15 19V19Z' fill='currentColor'/%3e%3c/svg%3e";

var img$u = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M22.0831 10.9413C23.1429 10.9357 24.0754 11.6408 24.3565 12.6603L24.6802 13.8339L23.4489 14.17L23.1252 12.9964C22.9974 12.5329 22.5736 12.2124 22.0919 12.215L20.0088 12.226L20 10.9524L22.0831 10.9413ZM16.3578 10.9751L18.4409 10.9641L18.4496 12.2377L16.3665 12.2488C15.8979 12.2513 15.4865 12.5592 15.3534 13.0072L14.892 14.5604L13.6675 14.1969L14.1289 12.6437C14.4217 11.6581 15.3267 10.9806 16.3578 10.9751ZM25.0038 15.0075L25.3275 16.1811C25.6147 17.2229 25.1486 18.3241 24.2002 18.8439L23.1295 19.4308L22.5131 18.3144L23.5838 17.7275C24.0148 17.4912 24.2268 16.9907 24.0962 16.5172L23.7725 15.3436L25.0038 15.0075ZM12.2833 18.8565L13.2061 15.7501L14.4306 16.1136L13.5078 19.22L12.2833 18.8565ZM11.3605 21.9628L11.8219 20.4096L13.0464 20.7732L12.585 22.3264C12.4506 22.7788 12.6321 23.2658 13.0304 23.5215L14.1207 24.2215L13.4306 25.2921L12.3404 24.5922C11.4641 24.0297 11.0649 22.9582 11.3605 21.9628ZM19.6953 24.1541L19.0164 25.6793C18.4343 26.9871 16.8191 27.4675 15.6112 26.692L14.5209 25.9921L15.211 24.9214L16.3012 25.6214C16.8503 25.9738 17.5845 25.7555 17.8491 25.161L18.5279 23.6358L19.6953 24.1541Z' fill='currentColor'/%3e%3cpath d='M22.6649 26.2004C22.5798 26.1153 22.5157 26.0116 22.4777 25.8975L21.4002 22.6651C21.1396 21.8834 21.8834 21.1396 22.6651 21.4002L25.8975 22.4777C26.0117 22.5157 26.1154 22.5798 26.2004 22.6649V22.6649C26.503 22.9675 26.503 23.4581 26.2004 23.7607L23.7607 26.2004C23.4581 26.503 22.9675 26.503 22.6649 26.2004V26.2004Z' fill='currentColor'/%3e%3c/svg%3e";

var img$t = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e %3cpath d='M21.6634 25.716C21.2372 26.5644 20.0524 26.5946 19.7797 25.764L17.0491 17.4463C16.7833 16.6367 17.6452 15.7799 18.4524 16.0512L26.7108 18.8274C27.5386 19.1056 27.501 20.2904 26.6511 20.7118L24.3958 21.6809L27.4216 24.7067C27.7978 25.083 27.7071 25.7837 27.219 26.2719C26.7308 26.76 26.0301 26.8507 25.6538 26.4745L22.5935 23.4142L21.6634 25.716Z' fill='currentColor'/%3e %3cpath d='M11 11H12.5V12.5H11V11Z' fill='currentColor'/%3e %3cpath d='M11 14H12.5V15.5H11V14Z' fill='currentColor'/%3e %3cpath d='M11 17H12.5V18.5H11V17Z' fill='currentColor'/%3e %3cpath d='M14 17H15.5V18.5H14V17Z' fill='currentColor'/%3e %3cpath d='M14 11H15.5V12.5H14V11Z' fill='currentColor'/%3e %3cpath d='M17 11H18.5V12.5H17V11Z' fill='currentColor'/%3e %3cpath d='M20 11H21.5V12.5H20V11Z' fill='currentColor'/%3e %3cpath d='M20 14H21.5V15.5H20V14Z' fill='currentColor'/%3e%3c/svg%3e";

var img$s = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M22.6649 26.2004C22.5798 26.1153 22.5157 26.0116 22.4777 25.8975L21.4002 22.6651C21.1396 21.8834 21.8834 21.1396 22.6651 21.4002L25.8975 22.4777C26.0117 22.5157 26.1154 22.5798 26.2004 22.6649V22.6649C26.503 22.9675 26.503 23.4581 26.2004 23.7607L23.7607 26.2004C23.4581 26.503 22.9675 26.503 22.6649 26.2004V26.2004Z' fill='currentColor'/%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='m 8.7128906%2c11.257813 2.1542974%2c4.226562 1.158203%2c-0.589844 -1.150391%2c-2.255859 0.455078%2c0.01758 0.04883%2c-1.298828 z m 3.9648434%2c0.148437 -0.04883%2c1.298828 5.197266%2c0.195313 0.04883%2c-1.298828 z m 6.496094%2c0.244141 -0.04883%2c1.298828 5.195312%2c0.195312 0.04883%2c-1.298828 z m 6.496094%2c0.24414 -0.04883%2c1.298828 0.816406%2c0.03125 -1.117188%2c1.361329 1.003907%2c0.824218 2.779297%2c-3.386718 z m -1.175781%2c3.695313 -3.298829%2c4.021484 1.00586%2c0.824219 L 25.5%2c16.416016 Z m -11.878907%2c0.46289 -1.158203%2c0.589844 2.359375%2c4.634766 1.160157%2c-0.591797 z m 2.951172%2c5.791016 -1.158203%2c0.591797 2.359375%2c4.632812 1.158203%2c-0.589843 z' fill='currentColor'/%3e%3c/svg%3e";

var img$r = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M16.3645 10.5293C17.0464 10.347 17.7625 10.25 18.5 10.25C19.2375 10.25 19.9536 10.347 20.6355 10.5293L20.2482 11.9784C19.6914 11.8296 19.1056 11.75 18.5 11.75C17.8944 11.75 17.3086 11.8296 16.7518 11.9784L16.3645 10.5293ZM11.3545 14.3738C12.0788 13.1221 13.1221 12.0788 14.3738 11.3545L15.125 12.6529C14.9598 12.7485 14.799 12.8509 14.643 12.9597C17.1237 13.9206 19.8763 13.9206 22.357 12.9597C22.201 12.8509 22.0402 12.7485 21.875 12.6529L22.6262 11.3545C23.8779 12.0788 24.9212 13.1221 25.6455 14.3738L24.3471 15.125C24.0398 14.5938 23.662 14.1083 23.2267 13.6812C20.2171 15.0134 16.7829 15.0134 13.7733 13.6812C13.338 14.1083 12.9602 14.5938 12.6529 15.125L11.3545 14.3738ZM10.25 18.5C10.25 17.7625 10.347 17.0464 10.5293 16.3645L11.9784 16.7518C11.8906 17.0803 11.8269 17.419 11.7894 17.7658C16.1247 19.3372 20.8753 19.3372 25.2106 17.7658C25.1731 17.419 25.1094 17.0803 25.0216 16.7518L26.4707 16.3645C26.653 17.0464 26.75 17.7625 26.75 18.5C26.75 19.2375 26.653 19.9536 26.4707 20.6355L25.0216 20.2482C25.1445 19.7885 25.2201 19.3091 25.2428 18.8151C20.8742 20.3208 16.1258 20.3208 11.7572 18.8151C11.7799 19.3091 11.8555 19.7885 11.9784 20.2482L10.5293 20.6355C10.347 19.9536 10.25 19.2375 10.25 18.5ZM14.3738 25.6455C13.1221 24.9212 12.0788 23.8779 11.3545 22.6262L12.6529 21.875C12.8207 22.165 13.0095 22.4415 13.2172 22.7022C16.6638 23.7308 20.3362 23.7308 23.7828 22.7022C23.9905 22.4415 24.1793 22.165 24.3471 21.875L25.6455 22.6262C24.9212 23.8779 23.8779 24.9212 22.6262 25.6455L21.875 24.3471C22.0058 24.2714 22.1338 24.1915 22.2589 24.1075C19.7769 24.5957 17.2231 24.5957 14.7411 24.1075C14.8662 24.1915 14.9942 24.2714 15.125 24.3471L14.3738 25.6455ZM18.5 26.75C17.7625 26.75 17.0464 26.653 16.3645 26.4707L16.7518 25.0216C17.3086 25.1704 17.8944 25.25 18.5 25.25C19.1056 25.25 19.6914 25.1704 20.2482 25.0216L20.6355 26.4707C19.9536 26.653 19.2375 26.75 18.5 26.75Z' fill='currentColor'/%3e%3c/svg%3e";

var img$q = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.5 14C12.5 12.8954 13.3954 12 14.5 12H24.5C25.6046 12 26.5 12.8954 26.5 14V24C26.5 25.1046 25.6046 26 24.5 26H14.5C13.3954 26 12.5 25.1046 12.5 24V14Z' stroke='currentColor' stroke-width='1.5' stroke-dasharray='4 2' fill-opacity='0' /%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M19.5 18C18.9477 18 18.5 18.4477 18.5 19C18.5 19.5523 18.9477 20 19.5 20C20.0523 20 20.5 19.5523 20.5 19C20.5 18.4477 20.0523 18 19.5 18ZM17.5 19C17.5 17.8954 18.3954 17 19.5 17C20.6046 17 21.5 17.8954 21.5 19C21.5 20.1046 20.6046 21 19.5 21C18.3954 21 17.5 20.1046 17.5 19Z' fill='currentColor'/%3e%3c/svg%3e";

var img$p = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M19.5597 13.3858L19.8547 13.0999C20.2319 12.7343 20.2319 12.1408 19.8547 11.7742C19.4765 11.4086 18.8642 11.4086 18.487 11.7742L16.5525 13.6493C16.1743 14.0159 16.1743 14.6093 16.5525 14.975L18.487 16.85C18.6756 17.0338 18.9232 17.1247 19.1708 17.1247C19.4185 17.1247 19.6651 17.0338 19.8547 16.85C20.2319 16.4844 20.2319 15.8909 19.8547 15.5243L19.5974 15.2759C22.1394 15.4128 24.1696 17.4407 24.1696 19.9373C24.1696 22.5221 21.9991 24.6249 19.3333 24.6249C16.6666 24.6249 14.497 22.5221 14.497 19.9373C14.497 19.4198 14.0637 18.9998 13.5298 18.9998C12.9949 18.9998 12.5625 19.4198 12.5625 19.9373C12.5625 23.5562 15.5997 26.5 19.3333 26.5C23.066 26.5 26.1042 23.5562 26.1042 19.9373C26.1042 16.3934 23.1869 13.503 19.5597 13.3858' fill='currentColor'/%3e%3c/svg%3e";

// import cropSvg from './svg/crop.svg';
const createSvg$9 = (svgString) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement;
};
class BottomToolbar extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            ...args,
            id: 'bottom-toolbar'
        };
        super(args);
        this.dom.addEventListener('pointerdown', (event) => {
            event.stopPropagation();
        });
        const undo = new Button({
            id: 'bottom-toolbar-undo',
            class: 'bottom-toolbar-button',
            enabled: false
        });
        const redo = new Button({
            id: 'bottom-toolbar-redo',
            class: 'bottom-toolbar-button',
            enabled: false
        });
        const picker = new Button({
            id: 'bottom-toolbar-picker',
            class: 'bottom-toolbar-tool'
        });
        const polygon = new Button({
            id: 'bottom-toolbar-polygon',
            class: 'bottom-toolbar-tool'
        });
        const brush = new Button({
            id: 'bottom-toolbar-brush',
            class: 'bottom-toolbar-tool'
        });
        const lasso = new Button({
            id: 'bottom-toolbar-lasso',
            class: 'bottom-toolbar-tool'
        });
        const sphere = new Button({
            id: 'bottom-toolbar-sphere',
            class: 'bottom-toolbar-tool'
        });
        const box = new Button({
            id: 'bottom-toolbar-box',
            class: 'bottom-toolbar-tool'
        });
        // const crop = new Button({
        //     id: 'bottom-toolbar-crop',
        //     class: ['bottom-toolbar-tool', 'disabled']
        // });
        const translate = new Button({
            id: 'bottom-toolbar-translate',
            class: 'bottom-toolbar-tool',
            icon: 'E111'
        });
        const rotate = new Button({
            id: 'bottom-toolbar-rotate',
            class: 'bottom-toolbar-tool',
            icon: 'E113'
        });
        const scale = new Button({
            id: 'bottom-toolbar-scale',
            class: 'bottom-toolbar-tool',
            icon: 'E112'
        });
        const coordSpace = new Button({
            id: 'bottom-toolbar-coord-space',
            class: 'bottom-toolbar-toggle',
            icon: 'E118'
        });
        const origin = new Button({
            id: 'bottom-toolbar-origin',
            class: ['bottom-toolbar-toggle'],
            icon: 'E189'
        });
        undo.dom.appendChild(createSvg$9(img$p));
        redo.dom.appendChild(createSvg$9(img$w));
        picker.dom.appendChild(createSvg$9(img$t));
        polygon.dom.appendChild(createSvg$9(img$s));
        brush.dom.appendChild(createSvg$9(img$v));
        sphere.dom.appendChild(createSvg$9(img$r));
        box.dom.appendChild(createSvg$9(img$q));
        lasso.dom.appendChild(createSvg$9(img$u));
        // crop.dom.appendChild(createSvg(cropSvg));
        this.append(undo);
        this.append(redo);
        this.append(new Element$1({ class: 'bottom-toolbar-separator' }));
        this.append(picker);
        this.append(lasso);
        this.append(polygon);
        this.append(brush);
        this.append(new Element$1({ class: 'bottom-toolbar-separator' }));
        this.append(sphere);
        this.append(box);
        // this.append(crop);
        this.append(new Element$1({ class: 'bottom-toolbar-separator' }));
        this.append(translate);
        this.append(rotate);
        this.append(scale);
        this.append(coordSpace);
        this.append(origin);
        undo.dom.addEventListener('click', () => events.fire('edit.undo'));
        redo.dom.addEventListener('click', () => events.fire('edit.redo'));
        polygon.dom.addEventListener('click', () => events.fire('tool.polygonSelection'));
        lasso.dom.addEventListener('click', () => events.fire('tool.lassoSelection'));
        brush.dom.addEventListener('click', () => events.fire('tool.brushSelection'));
        picker.dom.addEventListener('click', () => events.fire('tool.rectSelection'));
        sphere.dom.addEventListener('click', () => events.fire('tool.sphereSelection'));
        box.dom.addEventListener('click', () => events.fire('tool.boxSelection'));
        translate.dom.addEventListener('click', () => events.fire('tool.move'));
        rotate.dom.addEventListener('click', () => events.fire('tool.rotate'));
        scale.dom.addEventListener('click', () => events.fire('tool.scale'));
        coordSpace.dom.addEventListener('click', () => events.fire('tool.toggleCoordSpace'));
        origin.dom.addEventListener('click', () => events.fire('pivot.toggleOrigin'));
        events.on('edit.canUndo', (value) => {
            undo.enabled = value;
        });
        events.on('edit.canRedo', (value) => {
            redo.enabled = value;
        });
        events.on('tool.activated', (toolName) => {
            picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active');
            brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active');
            polygon.class[toolName === 'polygonSelection' ? 'add' : 'remove']('active');
            lasso.class[toolName === 'lassoSelection' ? 'add' : 'remove']('active');
            sphere.class[toolName === 'sphereSelection' ? 'add' : 'remove']('active');
            box.class[toolName === 'boxSelection' ? 'add' : 'remove']('active');
            translate.class[toolName === 'move' ? 'add' : 'remove']('active');
            rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active');
            scale.class[toolName === 'scale' ? 'add' : 'remove']('active');
        });
        events.on('tool.coordSpace', (space) => {
            coordSpace.dom.classList[space === 'local' ? 'add' : 'remove']('active');
        });
        events.on('pivot.origin', (o) => {
            origin.dom.classList[o === 'boundCenter' ? 'add' : 'remove']('active');
        });
        // register tooltips
        tooltips.register(undo, localize('tooltip.undo'));
        tooltips.register(redo, localize('tooltip.redo'));
        tooltips.register(picker, localize('tooltip.picker'));
        tooltips.register(brush, localize('tooltip.brush'));
        tooltips.register(polygon, localize('tooltip.polygon'));
        tooltips.register(lasso, 'Lasso Select');
        tooltips.register(sphere, localize('tooltip.sphere'));
        tooltips.register(box, localize('tooltip.box'));
        // tooltips.register(crop, 'Crop');
        tooltips.register(translate, localize('tooltip.translate'));
        tooltips.register(rotate, localize('tooltip.rotate'));
        tooltips.register(scale, localize('tooltip.scale'));
        tooltips.register(coordSpace, localize('tooltip.local-space'));
        tooltips.register(origin, localize('tooltip.bound-center'));
    }
}

// pcui slider doesn't include start and end events
class MyFancySliderInput extends SliderInput {
    _onSlideStart(pageX) {
        super._onSlideStart(pageX);
        this.emit('slide:start');
    }
    _onSlideEnd(pageX) {
        super._onSlideEnd(pageX);
        this.emit('slide:end');
    }
}
class ColorPanel extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            ...args,
            id: 'color-panel',
            class: 'panel',
            hidden: true
        };
        super(args);
        // stop pointer events bubbling
        ['pointerdown', 'pointerup', 'pointermove', 'wheel', 'dblclick'].forEach((eventName) => {
            this.dom.addEventListener(eventName, (event) => event.stopPropagation());
        });
        // header
        const header = new Container({
            class: 'panel-header'
        });
        const icon = new Label({
            class: 'panel-header-icon',
            text: '\uE146'
        });
        const label = new Label({
            class: 'panel-header-label',
            text: localize('colors')
        });
        header.append(icon);
        header.append(label);
        // tint
        const tintRow = new Container({
            class: 'color-panel-row'
        });
        const tintLabel = new Label({
            text: localize('colors.tint'),
            class: 'color-panel-row-label'
        });
        const tintPicker = new ColorPicker({
            class: 'color-panel-row-picker',
            value: [1, 1, 1]
        });
        tintRow.append(tintLabel);
        tintRow.append(tintPicker);
        // temperature
        const temperatureRow = new Container({
            class: 'color-panel-row'
        });
        const temperatureLabel = new Label({
            text: localize('colors.temperature'),
            class: 'color-panel-row-label'
        });
        const temperatureSlider = new MyFancySliderInput({
            class: 'color-panel-row-slider',
            min: -0.5,
            max: 0.5,
            step: 0.005,
            value: 0
        });
        temperatureRow.append(temperatureLabel);
        temperatureRow.append(temperatureSlider);
        // saturation
        const saturationRow = new Container({
            class: 'color-panel-row'
        });
        const saturationLabel = new Label({
            text: localize('colors.saturation'),
            class: 'color-panel-row-label'
        });
        const saturationSlider = new MyFancySliderInput({
            class: 'color-panel-row-slider',
            min: 0,
            max: 2,
            step: 0.1,
            value: 1
        });
        saturationRow.append(saturationLabel);
        saturationRow.append(saturationSlider);
        // brightness
        const brightnessRow = new Container({
            class: 'color-panel-row'
        });
        const brightnessLabel = new Label({
            text: localize('colors.brightness'),
            class: 'color-panel-row-label'
        });
        const brightnessSlider = new MyFancySliderInput({
            class: 'color-panel-row-slider',
            min: -1,
            max: 1,
            step: 0.1,
            value: 1
        });
        brightnessRow.append(brightnessLabel);
        brightnessRow.append(brightnessSlider);
        // black point
        const blackPointRow = new Container({
            class: 'color-panel-row'
        });
        const blackPointLabel = new Label({
            text: localize('colors.blackPoint'),
            class: 'color-panel-row-label'
        });
        const blackPointSlider = new MyFancySliderInput({
            class: 'color-panel-row-slider',
            min: 0,
            max: 1,
            step: 0.01,
            value: 0
        });
        blackPointRow.append(blackPointLabel);
        blackPointRow.append(blackPointSlider);
        // white point
        const whitePointRow = new Container({
            class: 'color-panel-row'
        });
        const whitePointLabel = new Label({
            text: localize('colors.whitePoint'),
            class: 'color-panel-row-label'
        });
        const whitePointSlider = new MyFancySliderInput({
            class: 'color-panel-row-slider',
            min: 0,
            max: 1,
            step: 0.01,
            value: 1
        });
        whitePointRow.append(whitePointLabel);
        whitePointRow.append(whitePointSlider);
        // transparency
        const transparencyRow = new Container({
            class: 'color-panel-row'
        });
        const transparencyLabel = new Label({
            text: localize('colors.transparency'),
            class: 'color-panel-row-label'
        });
        const transparencySlider = new MyFancySliderInput({
            class: 'color-panel-row-slider',
            min: -6,
            max: 6,
            step: 0.01,
            value: 1
        });
        transparencyRow.append(transparencyLabel);
        transparencyRow.append(transparencySlider);
        // control row
        const controlRow = new Container({
            class: 'color-panel-control-row'
        });
        const reset = new Label({
            class: 'panel-header-button',
            text: '\uE304'
        });
        controlRow.append(new Label({ class: 'panel-header-spacer' }));
        controlRow.append(reset);
        controlRow.append(new Label({ class: 'panel-header-spacer' }));
        this.append(header);
        this.append(tintRow);
        this.append(temperatureRow);
        this.append(saturationRow);
        this.append(brightnessRow);
        this.append(blackPointRow);
        this.append(whitePointRow);
        this.append(transparencyRow);
        this.append(new Label({ class: 'panel-header-spacer' }));
        this.append(controlRow);
        // handle ui updates
        let suppress = false;
        let selected = null;
        let op = null;
        const updateUIFromState = (splat) => {
            if (suppress)
                return;
            suppress = true;
            tintPicker.value = splat ? [splat.tintClr.r, splat.tintClr.g, splat.tintClr.b] : [1, 1, 1];
            temperatureSlider.value = splat ? splat.temperature : 0;
            saturationSlider.value = splat ? splat.saturation : 0;
            brightnessSlider.value = splat ? splat.brightness : 0;
            blackPointSlider.value = splat ? splat.blackPoint : 0;
            whitePointSlider.value = splat ? splat.whitePoint : 1;
            transparencySlider.value = splat ? Math.log(splat.transparency) : 0;
            suppress = false;
        };
        const start = () => {
            if (selected) {
                op = new SetSplatColorAdjustmentOp({
                    splat: selected,
                    newState: {
                        tintClr: selected.tintClr.clone(),
                        temperature: selected.temperature,
                        saturation: selected.saturation,
                        brightness: selected.brightness,
                        blackPoint: selected.blackPoint,
                        whitePoint: selected.whitePoint,
                        transparency: selected.transparency
                    },
                    oldState: {
                        tintClr: selected.tintClr.clone(),
                        temperature: selected.temperature,
                        saturation: selected.saturation,
                        brightness: selected.brightness,
                        blackPoint: selected.blackPoint,
                        whitePoint: selected.whitePoint,
                        transparency: selected.transparency
                    }
                });
            }
        };
        const end = () => {
            if (op) {
                const { newState } = op;
                newState.tintClr.set(tintPicker.value[0], tintPicker.value[1], tintPicker.value[2]);
                newState.temperature = temperatureSlider.value;
                newState.saturation = saturationSlider.value;
                newState.brightness = brightnessSlider.value;
                newState.blackPoint = blackPointSlider.value;
                newState.whitePoint = whitePointSlider.value;
                newState.transparency = Math.exp(transparencySlider.value);
                events.fire('edit.add', op);
                op = null;
            }
        };
        const updateOp = (setFunc) => {
            if (!suppress) {
                suppress = true;
                if (op) {
                    setFunc(op);
                    op.do();
                }
                else if (selected) {
                    start();
                    setFunc(op);
                    op.do();
                    end();
                }
                suppress = false;
            }
        };
        [temperatureSlider, saturationSlider, brightnessSlider, blackPointSlider, whitePointSlider, transparencySlider].forEach((slider) => {
            slider.on('slide:start', start);
            slider.on('slide:end', end);
        });
        tintPicker.on('picker:color:start', start);
        tintPicker.on('picker:color:end', end);
        tintPicker.on('change', (value) => {
            updateOp((op) => {
                op.newState.tintClr.set(value[0], value[1], value[2]);
            });
        });
        temperatureSlider.on('change', (value) => {
            updateOp((op) => {
                op.newState.temperature = value;
            });
        });
        saturationSlider.on('change', (value) => {
            updateOp((op) => {
                op.newState.saturation = value;
            });
        });
        brightnessSlider.on('change', (value) => {
            updateOp((op) => {
                op.newState.brightness = value;
            });
        });
        blackPointSlider.on('change', (value) => {
            updateOp((op) => {
                op.newState.blackPoint = value;
            });
            if (value > whitePointSlider.value) {
                whitePointSlider.value = value;
            }
        });
        whitePointSlider.on('change', (value) => {
            updateOp((op) => {
                op.newState.whitePoint = value;
            });
            if (value < blackPointSlider.value) {
                blackPointSlider.value = value;
            }
        });
        transparencySlider.on('change', (value) => {
            updateOp((op) => {
                op.newState.transparency = Math.exp(value);
            });
        });
        reset.on('click', () => {
            if (selected) {
                const op = new SetSplatColorAdjustmentOp({
                    splat: selected,
                    newState: {
                        tintClr: new Color(1, 1, 1),
                        temperature: 0,
                        saturation: 1,
                        brightness: 0,
                        blackPoint: 0,
                        whitePoint: 1,
                        transparency: 1
                    },
                    oldState: {
                        tintClr: selected.tintClr.clone(),
                        temperature: selected.temperature,
                        saturation: selected.saturation,
                        brightness: selected.brightness,
                        blackPoint: selected.blackPoint,
                        whitePoint: selected.whitePoint,
                        transparency: selected.transparency
                    }
                });
                events.fire('edit.add', op);
            }
        });
        events.on('selection.changed', (splat) => {
            selected = splat;
            updateUIFromState(splat);
        });
        events.on('splat.tintClr', updateUIFromState);
        events.on('splat.temperature', updateUIFromState);
        events.on('splat.saturation', updateUIFromState);
        events.on('splat.brightness', updateUIFromState);
        events.on('splat.blackPoint', updateUIFromState);
        events.on('splat.whitePoint', updateUIFromState);
        events.on('splat.transparency', updateUIFromState);
        tooltips.register(reset, localize('colors.reset'), 'bottom');
        // handle panel visibility
        const setVisible = (visible) => {
            if (visible === this.hidden) {
                this.hidden = !visible;
                events.fire('colorPanel.visible', visible);
            }
        };
        events.function('colorPanel.visible', () => {
            return !this.hidden;
        });
        events.on('colorPanel.setVisible', (visible) => {
            setVisible(visible);
        });
        events.on('colorPanel.toggleVisible', () => {
            setVisible(this.hidden);
        });
        events.on('viewPanel.visible', (visible) => {
            if (visible) {
                setVisible(false);
            }
        });
    }
}

var img$o = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M3 11C2.17157 11 1.5 10.3284 1.5 9.5L1.5 3.5C1.5 2.67157 2.17157 2 3 2L6 2C6.82843 2 7.5 2.67157 7.5 3.5L7.5 4.5C7.5 4.77614 7.27614 5 7 5C6.72386 5 6.5 4.77614 6.5 4.5L6.5 3.5C6.5 3.22386 6.27614 3 6 3L3 3C2.72386 3 2.5 3.22386 2.5 3.5L2.5 9.5C2.5 9.77614 2.72386 10 3 10L6 10C6.27614 10 6.5 9.77614 6.5 9.5L6.5 8.5C6.5 8.22386 6.72386 8 7 8C7.27614 8 7.5 8.22386 7.5 8.5L7.5 9.5C7.5 10.3284 6.82843 11 6 11L3 11ZM9 7L8.2 7.6C7.97909 7.76568 7.93432 8.07908 8.1 8.3C8.26569 8.52091 8.57909 8.56568 8.8 8.4L10.3733 7.22C10.8533 6.86 10.8533 6.14 10.3733 5.78L8.8 4.6C8.57909 4.43431 8.26569 4.47909 8.1 4.7C7.93432 4.92091 7.97909 5.23431 8.2 5.4L9.00001 6L5.5 6C5.22386 6 5 6.22386 5 6.5C5 6.77614 5.22386 7 5.5 7L9 7Z' fill='currentColor'/%3e%3c/svg%3e";

const createSvg$8 = (svgString, args = {}) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new Element$1({
        dom: new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement,
        ...args
    });
};
const removeKnownExtension = (filename) => {
    // remove known extensions
    const knownExtensions = ['.compressed.ply', '.ply', '.splat', '.html', '.zip'];
    for (let i = 0; i < knownExtensions.length; ++i) {
        const ext = knownExtensions[i];
        if (filename.endsWith(ext)) {
            return filename.slice(0, -ext.length);
        }
    }
    return filename;
};
class ExportPopup extends Container {
    show;
    hide;
    destroy;
    constructor(events, args = {}) {
        args = {
            id: 'export-popup',
            hidden: true,
            tabIndex: -1,
            ...args
        };
        super(args);
        // UI
        const dialog = new Container({
            id: 'dialog'
        });
        // header
        const header = new Container({
            id: 'header'
        });
        const headerText = new Label({
            id: 'header',
            text: localize('export.header')
        });
        header.append(createSvg$8(img$o, {
            id: 'icon'
        }));
        header.append(headerText);
        // content
        const content = new Container({ id: 'content' });
        // type
        const viewerTypeRow = new Container({
            class: 'row'
        });
        const viewerTypeLabel = new Label({
            class: 'label',
            text: localize('export.type')
        });
        const viewerTypeSelect = new SelectInput({
            class: 'select',
            defaultValue: 'html',
            options: [
                { v: 'html', t: localize('export.html') },
                { v: 'zip', t: localize('export.package') }
            ]
        });
        viewerTypeRow.append(viewerTypeLabel);
        viewerTypeRow.append(viewerTypeSelect);
        // viewer: camera start position
        const startRow = new Container({
            class: 'row'
        });
        const startLabel = new Label({
            class: 'label',
            text: localize('export.start-position')
        });
        const startSelect = new SelectInput({
            class: 'select',
            defaultValue: 'viewport',
            options: [
                { v: 'default', t: localize('export.default') },
                { v: 'viewport', t: localize('export.viewport') },
                { v: 'pose', t: localize('export.pose-camera') }
            ]
        });
        startRow.append(startLabel);
        startRow.append(startSelect);
        // viewer: animation
        const animationLabel = new Label({ class: 'label', text: localize('export.animation') });
        const animationSelect = new SelectInput({
            class: 'select',
            defaultValue: 'none',
            options: [
                { v: 'none', t: localize('export.animation-none') },
                { v: 'track', t: localize('export.animation-track') }
            ]
        });
        const animationRow = new Container({ class: 'row' });
        animationRow.append(animationLabel);
        animationRow.append(animationSelect);
        // viewer: clear color
        const colorRow = new Container({
            class: 'row'
        });
        const colorLabel = new Label({
            class: 'label',
            text: localize('export.background-color')
        });
        const colorPicker = new ColorPicker({
            class: 'color-picker',
            value: [1, 1, 1, 1]
        });
        colorRow.append(colorLabel);
        colorRow.append(colorPicker);
        // viewer: fov
        const fovRow = new Container({
            class: 'row'
        });
        const fovLabel = new Label({
            class: 'label',
            text: localize('export.fov')
        });
        const fovSlider = new SliderInput({
            class: 'slider',
            min: 10,
            max: 120,
            precision: 0,
            value: 60
        });
        fovRow.append(fovLabel);
        fovRow.append(fovSlider);
        // compress
        const compressRow = new Container({
            class: 'row'
        });
        const compressLabel = new Label({
            class: 'label',
            text: localize('export.ply-compress')
        });
        const compressBoolean = new BooleanInput({
            class: 'boolean',
            type: 'toggle'
        });
        compressRow.append(compressLabel);
        compressRow.append(compressBoolean);
        // splats
        const splatsRow = new Container({
            class: 'row'
        });
        const splatsLabel = new Label({
            class: 'label',
            text: localize('export.splats-select')
        });
        const splatsSelect = new SelectInput({
            class: 'select',
            defaultValue: 'ply',
            options: [
                { v: 'all', t: localize('export.splats-select.all') }
            ]
        });
        splatsRow.append(splatsLabel);
        splatsRow.append(splatsSelect);
        // spherical harmonic bands
        const bandsRow = new Container({
            class: 'row'
        });
        const bandsLabel = new Label({
            class: 'label',
            text: localize('export.sh-bands')
        });
        const bandsSlider = new SliderInput({
            class: 'slider',
            min: 0,
            max: 3,
            precision: 0,
            value: 3
        });
        bandsRow.append(bandsLabel);
        bandsRow.append(bandsSlider);
        // filename
        const filenameRow = new Container({
            class: 'row'
        });
        const filenameLabel = new Label({
            class: 'label',
            text: localize('export.filename')
        });
        const filenameEntry = new TextInput({
            class: 'text-input'
        });
        filenameRow.append(filenameLabel);
        filenameRow.append(filenameEntry);
        // content
        content.append(viewerTypeRow);
        content.append(startRow);
        content.append(animationRow);
        content.append(colorRow);
        content.append(fovRow);
        content.append(compressRow);
        content.append(splatsRow);
        content.append(bandsRow);
        content.append(filenameRow);
        // footer
        const footer = new Container({ id: 'footer' });
        const cancelButton = new Button({
            class: 'button',
            text: localize('popup.cancel')
        });
        const exportButton = new Button({
            class: 'button',
            text: localize('file.export')
        });
        footer.append(cancelButton);
        footer.append(exportButton);
        dialog.append(header);
        dialog.append(content);
        dialog.append(footer);
        this.append(dialog);
        // handlers
        let onCancel;
        let onExport;
        cancelButton.on('click', () => onCancel());
        exportButton.on('click', () => onExport());
        const keydown = (e) => {
            switch (e.key) {
                case 'Escape':
                    onCancel();
                    break;
                case 'Enter':
                    if (!e.shiftKey)
                        onExport();
                    break;
                default:
                    e.stopPropagation();
                    break;
            }
        };
        const updateExtension = (ext) => {
            filenameEntry.value = removeKnownExtension(filenameEntry.value) + ext;
        };
        compressBoolean.on('change', () => {
            updateExtension(compressBoolean.value ? '.compressed.ply' : '.ply');
        });
        viewerTypeSelect.on('change', () => {
            updateExtension(viewerTypeSelect.value === 'html' ? '.html' : '.zip');
        });
        const reset = (exportType, splatNames, hasPoses) => {
            const allRows = [
                viewerTypeRow, startRow, animationRow, colorRow, fovRow, compressRow, splatsRow, bandsRow, filenameRow
            ];
            const activeRows = {
                ply: [compressRow, splatsRow, bandsRow, filenameRow],
                splat: [splatsRow, filenameRow],
                viewer: [viewerTypeRow, startRow, animationRow, colorRow, fovRow, splatsRow, bandsRow, filenameRow]
            }[exportType];
            allRows.forEach((r) => {
                r.hidden = activeRows.indexOf(r) === -1;
            });
            // update splat list
            splatsSelect.options = [
                {
                    v: 'all',
                    t: localize('export.splats-select.all')
                },
                ...splatNames.map((s, i) => ({ v: i.toFixed(0), t: s }))
            ];
            splatsSelect.value = 'all';
            splatsSelect.enabled = splatNames.length > 1;
            bandsSlider.value = events.invoke('view.bands');
            // ply
            compressBoolean.value = false;
            // filename
            filenameEntry.value = splatNames[0];
            switch (exportType) {
                case 'ply':
                    updateExtension('.ply');
                    break;
                case 'splat':
                    updateExtension('.splat');
                    break;
                case 'viewer':
                    updateExtension(viewerTypeSelect.value === 'html' ? '.html' : '.zip');
                    break;
            }
            // viewer
            const bgClr = events.invoke('bgClr');
            startSelect.value = hasPoses ? 'pose' : 'viewport';
            startSelect.disabledOptions = hasPoses ? {} : { 'pose': startSelect.options[2].t };
            animationSelect.value = hasPoses ? 'track' : 'none';
            animationSelect.disabledOptions = hasPoses ? {} : { track: animationSelect.options[1].t };
            animationSelect.enabled = hasPoses;
            colorPicker.value = [bgClr.r, bgClr.g, bgClr.b];
            fovSlider.value = events.invoke('camera.fov');
        };
        this.show = (exportType, splatNames, showFilenameEdit) => {
            const frames = events.invoke('timeline.frames');
            const frameRate = events.invoke('timeline.frameRate');
            const smoothness = events.invoke('timeline.smoothness');
            const orderedPoses = events.invoke('camera.poses')
                .slice()
                .filter(p => p.frame >= 0 && p.frame < frames)
                .sort((a, b) => a.frame - b.frame);
            reset(exportType, splatNames, orderedPoses.length > 0);
            // filename is only shown in safari where file picker is not supported
            filenameRow.hidden = !showFilenameEdit;
            this.hidden = false;
            this.dom.addEventListener('keydown', keydown);
            this.dom.focus();
            const assemblePlyOptions = () => {
                return {
                    filename: filenameEntry.value,
                    splatIdx: splatsSelect.value === 'all' ? 'all' : splatsSelect.value,
                    serializeSettings: {
                        maxSHBands: bandsSlider.value
                    },
                    compressedPly: compressBoolean.value
                };
            };
            const assembleSplatOptions = () => {
                return {
                    filename: filenameEntry.value,
                    splatIdx: splatsSelect.value === 'all' ? 'all' : splatsSelect.value,
                    serializeSettings: {}
                };
            };
            const assembleViewerOptions = () => {
                // extract camera starting pos
                let pose;
                switch (startSelect.value) {
                    case 'pose':
                        pose = orderedPoses?.[0];
                        break;
                    case 'viewport':
                        pose = events.invoke('camera.getPose');
                        break;
                }
                const p = pose?.position;
                const t = pose?.target;
                const startAnim = (() => {
                    switch (animationSelect.value) {
                        case 'none': return 'none';
                        case 'track': return 'animTrack';
                    }
                })();
                // extract camera animation
                const animTracks = [];
                switch (startAnim) {
                    case 'none':
                        break;
                    case 'animTrack': {
                        // use camera poses
                        const times = [];
                        const position = [];
                        const target = [];
                        for (let i = 0; i < orderedPoses.length; ++i) {
                            const p = orderedPoses[i];
                            times.push(p.frame);
                            position.push(p.position.x, p.position.y, p.position.z);
                            target.push(p.target.x, p.target.y, p.target.z);
                        }
                        animTracks.push({
                            name: 'cameraAnim',
                            duration: frames / frameRate,
                            frameRate,
                            target: 'camera',
                            loopMode: 'repeat',
                            interpolation: 'spline',
                            smoothness,
                            keyframes: {
                                times,
                                values: { position, target }
                            }
                        });
                        break;
                    }
                }
                const experienceSettings = {
                    camera: {
                        fov: fovSlider.value,
                        position: p ? [p.x, p.y, p.z] : null,
                        target: t ? [t.x, t.y, t.z] : null,
                        startAnim,
                        animTrack: startAnim === 'animTrack' ? 'cameraAnim' : null
                    },
                    background: {
                        color: colorPicker.value.slice()
                    },
                    animTracks
                };
                return {
                    filename: filenameEntry.value,
                    splatIdx: splatsSelect.value === 'all' ? 'all' : splatsSelect.value,
                    serializeSettings: {
                        maxSHBands: bandsSlider.value
                    },
                    viewerExportSettings: {
                        type: viewerTypeSelect.value,
                        experienceSettings
                    }
                };
            };
            return new Promise((resolve) => {
                onCancel = () => {
                    resolve(null);
                };
                onExport = () => {
                    switch (exportType) {
                        case 'ply':
                            resolve(assemblePlyOptions());
                            break;
                        case 'splat':
                            resolve(assembleSplatOptions());
                            break;
                        case 'viewer':
                            resolve(assembleViewerOptions());
                            break;
                    }
                };
            }).finally(() => {
                this.dom.removeEventListener('keydown', keydown);
                this.hide();
            });
        };
        this.hide = () => {
            this.hidden = true;
        };
        this.destroy = () => {
            this.hide();
            super.destroy();
        };
    }
}

const createSvg$7 = (svgString, args = {}) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new Element$1({
        dom: new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement,
        ...args
    });
};
class ImageSettingsDialog extends Container {
    show;
    hide;
    destroy;
    constructor(events, args = {}) {
        args = {
            ...args,
            id: 'image-settings-dialog',
            class: 'settings-dialog',
            hidden: true,
            tabIndex: -1
        };
        super(args);
        const dialog = new Container({
            id: 'dialog'
        });
        // header
        const headerIcon = createSvg$7(img$o, { id: 'icon' });
        const headerText = new Label({ id: 'text', text: localize('image.header') });
        const header = new Container({ id: 'header' });
        header.append(headerIcon);
        header.append(headerText);
        // preset
        const presetLabel = new Label({ class: 'label', text: localize('image.preset') });
        const presetSelect = new SelectInput({
            class: 'select',
            defaultValue: 'viewport',
            options: [
                { v: 'viewport', t: localize('image.resolutionCurrent') },
                { v: 'HD', t: 'HD' },
                { v: 'QHD', t: 'QHD' },
                { v: '4K', t: '4K' },
                { v: 'custom', t: localize('image.resolutionCustom') }
            ]
        });
        const presetRow = new Container({ class: 'row' });
        presetRow.append(presetLabel);
        presetRow.append(presetSelect);
        // resolution
        const resolutionLabel = new Label({ class: 'label', text: localize('image.resolution') });
        const resolutionValue = new VectorInput({
            class: 'vector-input',
            dimensions: 2,
            min: 320,
            max: 16000,
            precision: 0,
            value: [1024, 768]
        });
        const resolutionRow = new Container({ class: 'row', enabled: false });
        resolutionRow.append(resolutionLabel);
        resolutionRow.append(resolutionValue);
        // transparent background
        const transparentBgLabel = new Label({ class: 'label', text: localize('image.transparentBg') });
        const transparentBgBoolean = new BooleanInput({ class: 'boolean', value: false });
        const transparentBgRow = new Container({ class: 'row' });
        transparentBgRow.append(transparentBgLabel);
        transparentBgRow.append(transparentBgBoolean);
        // show debug overlays
        const showDebugLabel = new Label({ class: 'label', text: localize('image.showDebug') });
        const showDebugBoolean = new BooleanInput({ class: 'boolean', value: false });
        const showDebugRow = new Container({ class: 'row' });
        showDebugRow.append(showDebugLabel);
        showDebugRow.append(showDebugBoolean);
        // content
        const content = new Container({ id: 'content' });
        content.append(presetRow);
        content.append(resolutionRow);
        content.append(transparentBgRow);
        content.append(showDebugRow);
        // footer
        const footer = new Container({ id: 'footer' });
        const cancelButton = new Button({
            class: 'button',
            text: localize('render.cancel')
        });
        const okButton = new Button({
            class: 'button',
            text: localize('render.ok')
        });
        footer.append(cancelButton);
        footer.append(okButton);
        dialog.append(header);
        dialog.append(content);
        dialog.append(footer);
        this.append(dialog);
        let targetSize;
        // Handle custom resolution activation
        const updateResolution = () => {
            const widths = {
                'viewport': targetSize.width,
                'HD': 1920,
                'QHD': 2560,
                '4K': 3840
            };
            const heights = {
                'viewport': targetSize.height,
                'HD': 1080,
                'QHD': 1440,
                '4K': 2160
            };
            resolutionValue.value = [widths[presetSelect.value], heights[presetSelect.value]];
        };
        presetSelect.on('change', () => {
            resolutionRow.enabled = presetSelect.value === 'custom';
            if (presetSelect.value !== 'custom') {
                updateResolution();
            }
        });
        // handle key bindings for enter and escape
        let onCancel;
        let onOK;
        cancelButton.on('click', () => onCancel());
        okButton.on('click', () => onOK());
        const keydown = (e) => {
            switch (e.key) {
                case 'Escape':
                    onCancel();
                    break;
                case 'Enter':
                    if (!e.shiftKey)
                        onOK();
                    break;
                default:
                    e.stopPropagation();
                    break;
            }
        };
        // reset UI and configure for current state
        const reset = () => {
            updateResolution();
        };
        // function implementations
        this.show = () => {
            targetSize = events.invoke('targetSize');
            reset();
            this.hidden = false;
            this.dom.addEventListener('keydown', keydown);
            this.dom.focus();
            return new Promise((resolve) => {
                onCancel = () => {
                    resolve(null);
                };
                onOK = () => {
                    const [width, height] = resolutionValue.value;
                    const imageSettings = {
                        width,
                        height,
                        transparentBg: transparentBgBoolean.value,
                        showDebug: showDebugBoolean.value
                    };
                    resolve(imageSettings);
                };
            }).finally(() => {
                this.dom.removeEventListener('keydown', keydown);
                this.hide();
            });
        };
        this.hide = () => {
            this.hidden = true;
        };
        this.destroy = () => {
            this.hide();
            super.destroy();
        };
    }
}

const offsetParent = (elem) => {
    const parent = elem.parentNode;
    return (parent.tagName === 'BODY' || window.getComputedStyle(parent).position !== 'static') ?
        parent :
        offsetParent(parent);
};
const arrange = (element, target, direction, padding) => {
    const rect = target.getBoundingClientRect();
    const parentRect = offsetParent(element).getBoundingClientRect();
    const style = element.style;
    switch (direction) {
        case 'left':
            break;
        case 'right':
            style.left = `${rect.right - parentRect.left + padding}px`;
            style.top = `${rect.top - parentRect.top}px`;
            break;
        case 'top':
            break;
        case 'bottom':
            style.left = `${rect.left - parentRect.left}px`;
            style.top = `${rect.bottom - parentRect.top + padding}px`;
            break;
    }
};
const isString = (value) => {
    return !value || typeof value === 'string' || value instanceof String;
};
// Detect if we're on a touch device
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
class MenuPanel extends Container {
    parentPanel = null;
    constructor(menuItems, args = {}) {
        args = {
            ...args,
            class: 'menu-panel',
            hidden: true
        };
        super(args);
        this.on('hide', () => {
            for (const menuItem of menuItems) {
                if (menuItem.subMenu) {
                    menuItem.subMenu.hidden = true;
                }
            }
        });
        this.on('show', () => {
            for (let i = 0; i < menuItems.length; i++) {
                const menuItem = menuItems[i];
                if (menuItem.isEnabled) {
                    this.dom.children.item(i).ui.enabled = menuItem.isEnabled();
                }
                if (menuItem.isVisible) {
                    this.dom.children.item(i).ui.hidden = !menuItem.isVisible();
                }
            }
        });
        for (const menuItem of menuItems) {
            const type = menuItem.subMenu ? 'menu' : menuItem.text ? 'button' : 'separator';
            const createIcon = (icon) => {
                return isString(menuItem.icon) ?
                    new Label({ class: 'menu-row-icon', text: menuItem.icon && String.fromCodePoint(parseInt(menuItem.icon, 16)) }) :
                    menuItem.icon;
            };
            let row = null;
            let activate = null;
            let deactivate = null;
            switch (type) {
                case 'button': {
                    row = new Container({ class: 'menu-row' });
                    const icon = createIcon();
                    const text = new Label({ class: 'menu-row-text', text: menuItem.text });
                    const postscript = isString(menuItem.extra) ? new Label({ class: 'menu-row-postscript', text: menuItem.extra }) : menuItem.extra;
                    row.append(icon);
                    row.append(text);
                    row.append(postscript);
                    break;
                }
                case 'menu': {
                    row = new Container({ class: 'menu-row' });
                    const icon = createIcon();
                    const text = new Label({ class: 'menu-row-text', text: menuItem.text });
                    const postscript = new Label({ class: 'menu-row-postscript', text: '\u232A' });
                    row.append(icon);
                    row.append(text);
                    row.append(postscript);
                    // set parent panel
                    menuItem.subMenu.parentPanel = this;
                    const childPanel = menuItem.subMenu;
                    if (childPanel) {
                        activate = () => {
                            if (childPanel.hidden) {
                                childPanel.position(row.dom, 'right', 2);
                                childPanel.hidden = !childPanel.hidden;
                            }
                            deactivate = () => {
                                childPanel.hidden = true;
                            };
                        };
                    }
                    break;
                }
                case 'separator':
                    this.append(new Container({ class: 'menu-row-separator' }));
                    break;
            }
            if (row) {
                let timer = -1;
                // For desktop: use hover behavior
                if (!isTouchDevice) {
                    row.dom.addEventListener('pointerenter', () => {
                        timer = window.setTimeout(() => {
                            if (deactivate) {
                                deactivate();
                            }
                            if (activate) {
                                activate();
                            }
                        }, 250);
                    });
                    row.dom.addEventListener('pointerleave', () => {
                        if (timer !== -1) {
                            clearTimeout(timer);
                            timer = -1;
                        }
                    });
                }
                row.dom.addEventListener('pointerdown', (event) => {
                    event.stopPropagation();
                });
                row.dom.addEventListener('pointerup', (event) => {
                    event.stopPropagation();
                    if (!row.disabled) {
                        // Handle submenu items differently on touch devices
                        if (menuItem.subMenu) {
                            if (isTouchDevice) {
                                // On touch devices: tap to open/close submenu
                                if (menuItem.subMenu.hidden) {
                                    // Close other submenus in this panel first
                                    if (deactivate) {
                                        deactivate();
                                    }
                                    if (activate) {
                                        activate();
                                    }
                                }
                                else {
                                    // Close the submenu if it's already open
                                    menuItem.subMenu.hidden = true;
                                }
                            }
                            // On desktop, submenus are handled by hover, so don't close the root panel
                        }
                        else if (menuItem.onSelect) {
                            // Regular menu item: execute action and close menu
                            this.rootPanel.hidden = true;
                            menuItem.onSelect();
                        }
                    }
                });
                this.append(row);
            }
        }
    }
    get rootPanel() {
        // eslint-disable-next-line  @typescript-eslint/no-this-alias
        let panel = this;
        while (panel.parentPanel) {
            panel = panel.parentPanel;
        }
        return panel;
    }
    position(parent, direction, padding = 2) {
        arrange(this.dom, parent, direction, padding);
    }
}

var img$n = "data:image/svg+xml,%3csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M3.10535 2.19302C3.27488 1.97505 3.58902 1.93578 3.80699 2.10532L7.29211 4.81596C8.06423 5.4165 8.06423 6.58348 7.29211 7.18402L3.80699 9.89467C3.58902 10.0642 3.27488 10.0249 3.10535 9.80696C2.93581 9.58899 2.97508 9.27485 3.19305 9.10532L6.67817 6.39467C6.93554 6.19449 6.93554 5.8055 6.67817 5.60532L3.19305 2.89467C2.97508 2.72513 2.93581 2.411 3.10535 2.19302Z' fill='currentColor'/%3e%3c/svg%3e";

var img$m = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3e %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M2 5.33333C2 4.22876 2.89543 3.33333 4 3.33333H12C13.1046 3.33333 14 4.22876 14 5.33333V10.6667C14 11.7712 13.1046 12.6667 12 12.6667H4C2.89543 12.6667 2 11.7712 2 10.6667V5.33333ZM4 4.66666C3.63181 4.66666 3.33333 4.96514 3.33333 5.33333V10.6667C3.33333 11.0349 3.63181 11.3333 4 11.3333H12C12.3682 11.3333 12.6667 11.0349 12.6667 10.6667V5.33333C12.6667 4.96514 12.3682 4.66666 12 4.66666H4Z' fill='currentColor'/%3e %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M6 12V4H7.33333V12H6Z' fill='currentColor'/%3e%3c/svg%3e";

var img$l = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='6 6 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.5 9H16V10H8V9L9.5 9C9.5 8.17157 10.1716 7.5 11 7.5H13C13.8284 7.5 14.5 8.17157 14.5 9ZM10.5 9L13.5 9C13.5 8.72386 13.2761 8.5 13 8.5L11 8.5C10.7239 8.5 10.5 8.72386 10.5 9ZM9.5 10.5V15.5C9.5 15.7761 9.72386 16 10 16H14C14.2761 16 14.5 15.7761 14.5 15.5V10.5H15.5V15.5C15.5 16.3284 14.8284 17 14 17H10C9.17157 17 8.5 16.3284 8.5 15.5V10.5H9.5ZM11.5 11.5C11.5 11.2239 11.2761 11 11 11C10.7239 11 10.5 11.2239 10.5 11.5V14.5C10.5 14.7761 10.7239 15 11 15C11.2761 15 11.5 14.7761 11.5 14.5V11.5ZM13 11C13.2761 11 13.5 11.2239 13.5 11.5V14.5C13.5 14.7761 13.2761 15 13 15C12.7239 15 12.5 14.7761 12.5 14.5V11.5C12.5 11.2239 12.7239 11 13 11Z' fill='currentColor'/%3e%3c/svg%3e";

var img$k = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M1.5 3.5C1.5 2.67157 2.17157 2 3 2H9C9.82843 2 10.5 2.67157 10.5 3.5V8.5C10.5 9.32843 9.82843 10 9 10H8C7.72386 10 7.5 9.77614 7.5 9.5C7.5 9.22386 7.72386 9 8 9H9C9.27614 9 9.5 8.77614 9.5 8.5V3.5C9.5 3.22386 9.27614 3 9 3H3C2.72386 3 2.5 3.22386 2.5 3.5V8.5C2.5 8.77614 2.72386 9 3 9H4C4.27614 9 4.5 9.22386 4.5 9.5C4.5 9.77614 4.27614 10 4 10H3C2.17157 10 1.5 9.32843 1.5 8.5V3.5ZM6.5 7L7.1 7.8C7.26568 8.02091 7.57908 8.06568 7.8 7.9C8.02091 7.73431 8.06568 7.42091 7.9 7.2L6.72 5.62666C6.36 5.14666 5.64 5.14666 5.28 5.62666L4.1 7.2C3.93431 7.42091 3.97908 7.73431 4.2 7.9C4.42091 8.06568 4.73431 8.02091 4.9 7.8L5.5 6.99999V10.5C5.5 10.7761 5.72386 11 6 11C6.27614 11 6.5 10.7761 6.5 10.5V7Z' fill='currentColor'/%3e%3c/svg%3e";

var img$j = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M9 2C9 1.72386 8.77614 1.5 8.5 1.5C8.22386 1.5 8 1.72386 8 2V3H7C6.72386 3 6.5 3.22386 6.5 3.5C6.5 3.77614 6.72386 4 7 4H8V5C8 5.27614 8.22386 5.5 8.5 5.5C8.77614 5.5 9 5.27614 9 5V4H10C10.2761 4 10.5 3.77614 10.5 3.5C10.5 3.22386 10.2761 3 10 3H9V2ZM3.5 3C3.22386 3 3 3.22386 3 3.5V8.5C3 8.77614 3.22386 9 3.5 9H8.5C8.77614 9 9 8.77614 9 8.5V6.5C9 6.22386 9.22386 6 9.5 6C9.77614 6 10 6.22386 10 6.5V8.5C10 9.32843 9.32843 10 8.5 10H3.5C2.67157 10 2 9.32843 2 8.5V3.5C2 2.67157 2.67157 2 3.5 2H5.5C5.77614 2 6 2.22386 6 2.5C6 2.77614 5.77614 3 5.5 3H3.5Z' fill='currentColor'/%3e%3c/svg%3e";

var img$i = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M6 3V5C6 5.55228 6.44772 6 7 6H9M9.5 6.41421V8.5C9.5 9.05228 9.05228 9.5 8.5 9.5H3.5C2.94772 9.5 2.5 9.05228 2.5 8.5V3.5C2.5 2.94772 2.94772 2.5 3.5 2.5H5.58579C5.851 2.5 6.10536 2.60536 6.29289 2.79289L9.20711 5.70711C9.39464 5.89464 9.5 6.149 9.5 6.41421Z' stroke='currentColor'/%3e%3c/svg%3e";

var img$h = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 12 12' fill='none'%3e %3cg transform='translate(0%2c 12) scale(1%2c -1)'%3e %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M2.5 9C2.5 8.72386 2.72386 8.5 3 8.5L9 8.5C9.27614 8.5 9.5 8.72386 9.5 9C9.5 9.27614 9.27614 9.5 9 9.5L3 9.5C2.72386 9.5 2.5 9.27614 2.5 9ZM5.50001 6L4.90001 5.2C4.73432 4.97909 4.42092 4.93432 4.20001 5.1C3.9791 5.26569 3.93432 5.57909 4.10001 5.8L5.28001 7.37334C5.64001 7.85334 6.36001 7.85334 6.72001 7.37334L7.90001 5.8C8.06569 5.57909 8.02092 5.26569 7.80001 5.1C7.57909 4.93432 7.26569 4.97909 7.10001 5.2L6.50001 6.00001L6.50001 2.5C6.50001 2.22386 6.27615 2 6.00001 2C5.72387 2 5.50001 2.22386 5.50001 2.5L5.50001 6Z' fill='currentColor'/%3e %3c/g%3e%3c/svg%3e";

var img$g = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M4.66421 2C4.26639 2 3.88486 2.15803 3.60355 2.43934L2.43934 3.60355C2.15804 3.88486 2 4.26639 2 4.66421V8.5C2 9.32843 2.67157 10 3.5 10H8.5C9.32843 10 10 9.32843 10 8.5V3.5C10 2.67157 9.32843 2 8.5 2H7.5H5.5H4.66421ZM5 3H4.66421C4.53161 3 4.40443 3.05268 4.31066 3.14645L3.14645 4.31066C3.05268 4.40443 3 4.53161 3 4.66421V8.5C3 8.77614 3.22386 9 3.5 9H8.5C8.77614 9 9 8.77614 9 8.5V3.5C9 3.22386 8.77614 3 8.5 3H8V4.5C8 4.77614 7.77614 5 7.5 5H5.5C5.22386 5 5 4.77614 5 4.5V3Z' fill='currentColor'/%3e%3c/svg%3e";

var img$f = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M2.5 3C2.5 2.72386 2.72386 2.5 3 2.5H4C4.27614 2.5 4.5 2.27614 4.5 2C4.5 1.72386 4.27614 1.5 4 1.5H3C2.17157 1.5 1.5 2.17157 1.5 3V4C1.5 4.27614 1.72386 4.5 2 4.5C2.27614 4.5 2.5 4.27614 2.5 4V3ZM8 1.5C7.72386 1.5 7.5 1.72386 7.5 2C7.5 2.27614 7.72386 2.5 8 2.5H9C9.27614 2.5 9.5 2.72386 9.5 3V4C9.5 4.27614 9.72386 4.5 10 4.5C10.2761 4.5 10.5 4.27614 10.5 4V3C10.5 2.17157 9.82843 1.5 9 1.5H8ZM2.5 8C2.5 7.72386 2.27614 7.5 2 7.5C1.72386 7.5 1.5 7.72386 1.5 8V9C1.5 9.82843 2.17157 10.5 3 10.5H4C4.27614 10.5 4.5 10.2761 4.5 10C4.5 9.72386 4.27614 9.5 4 9.5H3C2.72386 9.5 2.5 9.27614 2.5 9V8ZM10.5 8C10.5 7.72386 10.2761 7.5 10 7.5C9.72386 7.5 9.5 7.72386 9.5 8V9C9.5 9.27614 9.27614 9.5 9 9.5H8C7.72386 9.5 7.5 9.72386 7.5 10C7.5 10.2761 7.72386 10.5 8 10.5H9C9.82843 10.5 10.5 9.82843 10.5 9V8ZM4.5 3.5C3.94772 3.5 3.5 3.94772 3.5 4.5V7.5C3.5 8.05228 3.94772 8.5 4.5 8.5H7.5C8.05228 8.5 8.5 8.05228 8.5 7.5V4.5C8.5 3.94772 8.05228 3.5 7.5 3.5H4.5Z' fill='currentColor'/%3e%3c/svg%3e";

var img$e = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M4.49999 5H5.49999V6.5H6.99999V7.5H5.49999V9H4.49999V7.5H2.99999V6.5H4.49999V5Z' fill='currentColor'/%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M9 9H9.49999C10.3284 9 11 8.32843 11 7.5V2.5C11 1.67157 10.3284 1 9.49999 1H4.49999C3.67157 1 2.99999 1.67157 2.99999 2.5V3H2.5C1.67157 3 1 3.67157 1 4.5V9.5C1 10.3284 1.67157 11 2.5 11H7.5C8.32843 11 9 10.3284 9 9.5V9ZM4.49999 2C4.22385 2 3.99999 2.22386 3.99999 2.5V3H7.5C8.32843 3 9 3.67157 9 4.5V8H9.49999C9.77614 8 9.99999 7.77614 9.99999 7.5V2.5C9.99999 2.22386 9.77614 2 9.49999 2H4.49999ZM2 4.5C2 4.22386 2.22386 4 2.5 4H7.5C7.77614 4 8 4.22386 8 4.5V9.5C8 9.77614 7.77614 10 7.5 10H2.5C2.22386 10 2 9.77614 2 9.5V4.5Z' fill='currentColor'/%3e%3c/svg%3e";

var img$d = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M2.5 3C2.5 2.72386 2.72386 2.5 3 2.5H4C4.27614 2.5 4.5 2.27614 4.5 2C4.5 1.72386 4.27614 1.5 4 1.5H3C2.17157 1.5 1.5 2.17157 1.5 3V4C1.5 4.27614 1.72386 4.5 2 4.5C2.27614 4.5 2.5 4.27614 2.5 4V3ZM8 1.5C7.72386 1.5 7.5 1.72386 7.5 2C7.5 2.27614 7.72386 2.5 8 2.5H9C9.27614 2.5 9.5 2.72386 9.5 3V4C9.5 4.27614 9.72386 4.5 10 4.5C10.2761 4.5 10.5 4.27614 10.5 4V3C10.5 2.17157 9.82843 1.5 9 1.5H8ZM2.5 8C2.5 7.72386 2.27614 7.5 2 7.5C1.72386 7.5 1.5 7.72386 1.5 8V9C1.5 9.82843 2.17157 10.5 3 10.5H4C4.27614 10.5 4.5 10.2761 4.5 10C4.5 9.72386 4.27614 9.5 4 9.5H3C2.72386 9.5 2.5 9.27614 2.5 9V8ZM10.5 8C10.5 7.72386 10.2761 7.5 10 7.5C9.72386 7.5 9.5 7.72386 9.5 8V9C9.5 9.27614 9.27614 9.5 9 9.5H8C7.72386 9.5 7.5 9.72386 7.5 10C7.5 10.2761 7.72386 10.5 8 10.5H9C9.82843 10.5 10.5 9.82843 10.5 9V8ZM4.5 3.5C3.94772 3.5 3.5 3.94772 3.5 4.5V7.5C3.5 8.05228 3.94772 8.5 4.5 8.5H7.5C8.05228 8.5 8.5 8.05228 8.5 7.5V4.5C8.5 3.94772 8.05228 3.5 7.5 3.5H4.5ZM4.5 4.5H7.5V7.5H4.5V4.5Z' fill='currentColor'/%3e%3c/svg%3e";

var img$c = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3crect x='3' y='6' width='6' height='4' rx='1' stroke='currentColor'/%3e%3cpath d='M8 6V4C8 2.89543 7.10457 2 6 2V2C4.89543 2 4 2.89543 4 4V6' stroke='currentColor'/%3e%3cpath d='M7 8C7 8.55228 6.55228 9 6 9V9C5.44772 9 5 8.55228 5 8V8C5 7.44772 5.44772 7 6 7V7C6.55228 7 7 7.44772 7 8V8Z' fill='currentColor'/%3e%3c/svg%3e";

var img$b = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M3 2.5C2.72386 2.5 2.5 2.72386 2.5 3V4C2.5 4.27614 2.27614 4.5 2 4.5C1.72386 4.5 1.5 4.27614 1.5 4V3C1.5 2.17157 2.17157 1.5 3 1.5H4C4.27614 1.5 4.5 1.72386 4.5 2C4.5 2.27614 4.27614 2.5 4 2.5H3ZM7.5 2C7.5 1.72386 7.72386 1.5 8 1.5H9C9.82843 1.5 10.5 2.17157 10.5 3V4C10.5 4.27614 10.2761 4.5 10 4.5C9.72386 4.5 9.5 4.27614 9.5 4V3C9.5 2.72386 9.27614 2.5 9 2.5H8C7.72386 2.5 7.5 2.27614 7.5 2ZM2 7.5C2.27614 7.5 2.5 7.72386 2.5 8V9C2.5 9.27614 2.72386 9.5 3 9.5H4C4.27614 9.5 4.5 9.72386 4.5 10C4.5 10.2761 4.27614 10.5 4 10.5H3C2.17157 10.5 1.5 9.82843 1.5 9V8C1.5 7.72386 1.72386 7.5 2 7.5ZM10 7.5C10.2761 7.5 10.5 7.72386 10.5 8V9C10.5 9.82843 9.82843 10.5 9 10.5H8C7.72386 10.5 7.5 10.2761 7.5 10C7.5 9.72386 7.72386 9.5 8 9.5H9C9.27614 9.5 9.5 9.27614 9.5 9V8C9.5 7.72386 9.72386 7.5 10 7.5Z' fill='currentColor'/%3e%3c/svg%3e";

var img$a = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='-1 -1 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M2.66421 0C2.26639 0 1.88486 0.158035 1.60355 0.43934L0.43934 1.60355C0.158035 1.88486 0 2.26639 0 2.66421V6.5C0 7.32843 0.671573 8 1.5 8H6.5C7.32843 8 8 7.32843 8 6.5V1.5C8 0.671573 7.32843 0 6.5 0H5.5H3.5H2.66421ZM3 1H2.66421C2.53161 1 2.40443 1.05268 2.31066 1.14645L1.14645 2.31066C1.05268 2.40443 1 2.53161 1 2.66421V6.5C1 6.77614 1.22386 7 1.5 7H6.5C6.77614 7 7 6.77614 7 6.5V1.5C7 1.22386 6.77614 1 6.5 1H6V2.5C6 2.77614 5.77614 3 5.5 3H3.5C3.22386 3 3 2.77614 3 2.5V1Z' fill='currentColor'/%3e%3c/svg%3e";

var img$9 = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3crect x='3' y='6' width='6' height='4' rx='1' stroke='currentColor'/%3e%3cpath d='M8 6V4C8 2.89543 7.10457 2 6 2V2C4.89543 2 4 2.89543 4 4V4' stroke='currentColor'/%3e%3cpath d='M7 8C7 8.55228 6.55228 9 6 9V9C5.44772 9 5 8.55228 5 8V8C5 7.44772 5.44772 7 6 7V7C6.55228 7 7 7.44772 7 8V8Z' fill='currentColor'/%3e%3c/svg%3e";

var img$8 = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cdefs%3e%3clinearGradient id='linear-gradient' x1='189.41' y1='245.63' x2='328.24' y2='680.04' gradientTransform='translate(0 -198)' gradientUnits='userSpaceOnUse'%3e%3cstop offset='0' stop-color='%23f26722'/%3e%3cstop offset='1' stop-color='%23fbae53'/%3e%3c/linearGradient%3e%3c/defs%3e%3crect fill='url(%23linear-gradient)' width='512' height='512'/%3e%3cpath fill='white' d='M129.83%2c217c9.75%2c0%2c17.64-7.9%2c17.64-17.64s-7.9-17.64-17.64-17.64-17.64%2c7.9-17.64%2c17.64%2c7.9%2c17.64%2c17.64%2c17.64Z'/%3e%3cpath fill='white' d='M388.74%2c253.94c-12.46%2c0-22.57%2c10.11-22.57%2c22.57s10.11%2c22.57%2c22.57%2c22.57%2c22.57-10.11%2c22.57-22.57-10.11-22.57-22.57-22.57h0Z'/%3e%3cpath fill='white' d='M345.26%2c161.1h.02c.37-.05.65-.22.97-.33%2c16.78-2.32%2c29.75-16.57%2c29.75-33.99%2c0-19.04-15.43-34.46-34.46-34.46s-34.46%2c15.43-34.46%2c34.46c0%2c4.29.88%2c8.35%2c2.32%2c12.14.04.12-.01.25.04.38%2c10.55%2c29.18-22.33%2c23.62-39.07%2c20.74-.49-.09-.88-.07-1.32-.11-4.27-.56-8.59-.97-13.03-.97-53.93%2c0-97.64%2c43.71-97.64%2c97.64%2c0%2c9.84%2c1.48%2c19.32%2c4.2%2c28.28.04.1%2c0%2c.17.05.27%2c8.97%2c30.15-14.83%2c25.49-25.52%2c25.69-1.04-.13-2.09-.32-3.18-.32-13.51%2c0-24.45%2c10.94-24.45%2c24.45s10.94%2c24.45%2c24.45%2c24.45c11.69%2c0%2c21.44-8.22%2c23.85-19.19h.02c6.31-26.57%2c25.9-18.02%2c31.15-12.67.2.2.4.26.59.42%2c17.44%2c16.25%2c40.75%2c26.26%2c66.47%2c26.26.67%2c0%2c1.34-.09%2c2.01-.1.66.01%2c1.31.04%2c2.04.04%2c9.7-.04%2c17.82%2c10.91%2c4.94%2c29.68h.13c-4.42%2c6.01-7.1%2c13.36-7.1%2c21.41%2c0%2c20.09%2c16.29%2c36.39%2c36.39%2c36.39s36.39-16.29%2c36.39-36.39c0-15.4-9.61-28.49-23.12-33.81-.32-.15-.61-.29-.98-.45-12.28-4.94-25.43-17.69-6.89-27.1l-.04-.1c31.93-16.06%2c53.88-49.02%2c53.88-87.2%2c0-21.15-6.8-40.68-18.23-56.66-.13-.23-.23-.47-.44-.74-23.15-31.67-2.37-36.07%2c10.3-38.09h-.01ZM230.79%2c284.71c0%2c7.95-6.44%2c14.38-14.38%2c14.38s-14.38-6.44-14.38-14.38v-22.95c0-7.95%2c6.44-14.38%2c14.38-14.38s14.38%2c6.44%2c14.38%2c14.38v22.95ZM309.78%2c284.71c0%2c7.95-6.44%2c14.38-14.38%2c14.38s-14.38-6.44-14.38-14.38v-22.95c0-7.95%2c6.44-14.38%2c14.38-14.38s14.38%2c6.44%2c14.38%2c14.38v22.95Z'/%3e%3c/svg%3e";

const createSvg$6 = (svgString) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new Element$1({
        dom: new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement
    });
};
class Menu extends Container {
    constructor(events, args = {}) {
        args = {
            ...args,
            id: 'menu'
        };
        super(args);
        const menubar = new Container({
            id: 'menu-bar'
        });
        menubar.dom.addEventListener('pointerdown', (event) => {
            event.stopPropagation();
        });
        const iconDom = document.createElement('img');
        iconDom.src = img$8;
        iconDom.setAttribute('id', 'app-icon');
        const aDom = document.createElement('a');
        aDom.href = new URL(window.location.href).origin;
        aDom.target = '_blank';
        aDom.appendChild(iconDom);
        const icon = new Element$1({
            dom: aDom
        });
        const scene = new Label({
            text: localize('file'),
            class: 'menu-option'
        });
        const render = new Label({
            text: localize('render'),
            class: 'menu-option'
        });
        const selection = new Label({
            text: localize('select'),
            class: 'menu-option'
        });
        const help = new Label({
            text: localize('help'),
            class: 'menu-option'
        });
        const toggleCollapsed = () => {
            document.body.classList.toggle('collapsed');
        };
        // collapse menu on mobile
        if (document.body.clientWidth < 600) {
            toggleCollapsed();
        }
        const collapse = createSvg$6(img$m);
        collapse.dom.classList.add('menu-icon');
        collapse.dom.setAttribute('id', 'menu-collapse');
        collapse.dom.addEventListener('click', toggleCollapsed);
        const arrow = createSvg$6(img$n);
        arrow.dom.classList.add('menu-icon');
        arrow.dom.setAttribute('id', 'menu-arrow');
        arrow.dom.addEventListener('click', toggleCollapsed);
        const buttonsContainer = new Container({
            id: 'menu-bar-options'
        });
        buttonsContainer.append(scene);
        buttonsContainer.append(selection);
        buttonsContainer.append(render);
        buttonsContainer.append(help);
        buttonsContainer.append(collapse);
        buttonsContainer.append(arrow);
        menubar.append(icon);
        menubar.append(buttonsContainer);
        const exportMenuPanel = new MenuPanel([{
                text: localize('file.export.ply'),
                icon: createSvg$6(img$o),
                isEnabled: () => !events.invoke('scene.empty'),
                onSelect: () => events.invoke('scene.export', 'ply')
            }, {
                text: localize('file.export.splat'),
                icon: createSvg$6(img$o),
                isEnabled: () => !events.invoke('scene.empty'),
                onSelect: () => events.invoke('scene.export', 'splat')
            }, {
            // separator
            }, {
                text: localize('file.export.viewer'),
                icon: createSvg$6(img$o),
                isEnabled: () => !events.invoke('scene.empty'),
                onSelect: () => events.invoke('scene.export', 'viewer')
            }]);
        const fileMenuPanel = new MenuPanel([{
                text: localize('file.new'),
                icon: createSvg$6(img$j),
                isEnabled: () => !events.invoke('scene.empty'),
                onSelect: () => events.invoke('doc.new')
            }, {
                text: localize('file.open'),
                icon: createSvg$6(img$i),
                onSelect: async () => {
                    await events.invoke('doc.open');
                }
            }, {
            // separator
            }, {
                text: localize('file.save'),
                icon: createSvg$6(img$g),
                isEnabled: () => events.invoke('doc.name'),
                onSelect: async () => await events.invoke('doc.save')
            }, {
                text: localize('file.save-as'),
                icon: createSvg$6(img$g),
                isEnabled: () => !events.invoke('scene.empty'),
                onSelect: async () => await events.invoke('doc.saveAs')
            }, {
            // separator
            }, {
                text: localize('file.import'),
                icon: createSvg$6(img$k),
                onSelect: async () => {
                    await events.invoke('scene.import');
                }
            }, {
                text: localize('file.export'),
                icon: createSvg$6(img$o),
                subMenu: exportMenuPanel
            }, {
                text: localize('file.publish'),
                icon: createSvg$6(img$h),
                isEnabled: () => !events.invoke('scene.empty'),
                onSelect: async () => await events.invoke('show.publishSettingsDialog')
            }]);
        const selectionMenuPanel = new MenuPanel([{
                text: localize('select.all'),
                icon: createSvg$6(img$f),
                extra: 'Ctrl + A',
                onSelect: () => events.fire('select.all')
            }, {
                text: localize('select.none'),
                icon: createSvg$6(img$b),
                extra: 'Shift + A',
                onSelect: () => events.fire('select.none')
            }, {
                text: localize('select.invert'),
                icon: createSvg$6(img$d),
                extra: 'Ctrl + I',
                onSelect: () => events.fire('select.invert')
            }, {
            // separator
            }, {
                text: localize('select.lock'),
                icon: createSvg$6(img$c),
                extra: 'H',
                isEnabled: () => events.invoke('selection.splats'),
                onSelect: () => events.fire('select.hide')
            }, {
                text: localize('select.unlock'),
                icon: createSvg$6(img$9),
                extra: 'U',
                onSelect: () => events.fire('select.unhide')
            }, {
                text: localize('select.delete'),
                icon: createSvg$6(img$l),
                extra: 'Delete',
                isEnabled: () => events.invoke('selection.splats'),
                onSelect: () => events.fire('select.delete')
            }, {
                text: localize('select.reset'),
                onSelect: () => events.fire('scene.reset')
            }, {
            // separator
            }, {
                text: localize('select.duplicate'),
                icon: createSvg$6(img$e),
                isEnabled: () => events.invoke('selection.splats'),
                onSelect: () => events.fire('select.duplicate')
            }, {
                text: localize('select.separate'),
                icon: createSvg$6(img$a),
                isEnabled: () => events.invoke('selection.splats'),
                onSelect: () => events.fire('select.separate')
            }]);
        const renderMenuPanel = new MenuPanel([{
                text: localize('render.image'),
                icon: createSvg$6(img$o),
                onSelect: async () => await events.invoke('show.imageSettingsDialog')
            }, {
                text: localize('render.video'),
                icon: createSvg$6(img$o),
                onSelect: async () => await events.invoke('show.videoSettingsDialog')
            }]);
        const helpMenuPanel = new MenuPanel([{
                text: localize('help.shortcuts'),
                icon: 'E136',
                onSelect: () => events.fire('show.shortcuts')
            }, {
                text: localize('help.user-guide'),
                icon: 'E232',
                onSelect: () => window.open('https://developer.playcanvas.com/user-manual/gaussian-splatting/editing/supersplat/', '_blank').focus()
            }, {
                text: localize('help.log-issue'),
                icon: 'E336',
                onSelect: () => window.open('https://github.com/playcanvas/supersplat/issues', '_blank').focus()
            }, {
                text: localize('help.github-repo'),
                icon: 'E259',
                onSelect: () => window.open('https://github.com/playcanvas/supersplat', '_blank').focus()
            }, {
            // separator
            }, {
                text: localize('help.basics-video'),
                icon: 'E261',
                onSelect: () => window.open('https://youtu.be/MwzaEM2I55I', '_blank').focus()
            }, {
            // separator
            }, {
                text: localize('help.discord'),
                icon: 'E233',
                onSelect: () => window.open('https://discord.gg/T3pnhRTTAY', '_blank').focus()
            }, {
                text: localize('help.forum'),
                icon: 'E432',
                onSelect: () => window.open('https://forum.playcanvas.com', '_blank').focus()
            }, {
            // separator
            }, {
                text: localize('help.about'),
                icon: 'E138',
                onSelect: () => events.invoke('show.about')
            }]);
        this.append(menubar);
        this.append(fileMenuPanel);
        this.append(exportMenuPanel);
        this.append(selectionMenuPanel);
        this.append(renderMenuPanel);
        this.append(helpMenuPanel);
        const options = [{
                dom: scene.dom,
                menuPanel: fileMenuPanel
            }, {
                dom: selection.dom,
                menuPanel: selectionMenuPanel
            }, {
                dom: render.dom,
                menuPanel: renderMenuPanel
            }, {
                dom: help.dom,
                menuPanel: helpMenuPanel
            }];
        options.forEach((option) => {
            const activate = () => {
                option.menuPanel.position(option.dom, 'bottom', 2);
                options.forEach((opt) => {
                    opt.menuPanel.hidden = opt !== option;
                });
            };
            option.dom.addEventListener('pointerdown', (event) => {
                if (!option.menuPanel.hidden) {
                    option.menuPanel.hidden = true;
                }
                else {
                    activate();
                }
            });
            option.dom.addEventListener('pointerenter', (event) => {
                if (!options.every(opt => opt.menuPanel.hidden)) {
                    activate();
                }
            });
        });
        const checkEvent = (event) => {
            if (!this.dom.contains(event.target)) {
                options.forEach((opt) => {
                    opt.menuPanel.hidden = true;
                });
            }
        };
        window.addEventListener('pointerdown', checkEvent, true);
        window.addEventListener('pointerup', checkEvent, true);
    }
}

var img$7 = "data:image/svg+xml,%3csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e %3cpath d='M2.70711 6.70711C2.31658 6.31658 2.31658 5.68342 2.70711 5.29289L5.29301 2.70699C5.68353 2.31647 6.3167 2.31647 6.70722 2.70699L9.29312 5.29289C9.68365 5.68342 9.68365 6.31658 9.29312 6.70711L6.70722 9.29301C6.3167 9.68353 5.68353 9.68353 5.29301 9.29301L2.70711 6.70711Z' fill='currentColor'/%3e%3c/svg%3e";

var img$6 = "data:image/svg+xml,%3csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e %3ccircle cx='6' cy='6' r='5' stroke='currentColor'/%3e %3cpath d='M3.70711 6.70711C3.31658 6.31658 3.31658 5.68342 3.70711 5.29289L5.29289 3.70711C5.68342 3.31658 6.31658 3.31658 6.70711 3.70711L8.29289 5.29289C8.68342 5.68342 8.68342 6.31658 8.29289 6.70711L6.70711 8.29289C6.31658 8.68342 5.68342 8.68342 5.29289 8.29289L3.70711 6.70711Z' fill='currentColor'/%3e%3c/svg%3e";

const createSvg$5 = (svgString) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement;
};
class ModeToggle extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            id: 'mode-toggle',
            class: 'centers-mode',
            ...args
        };
        super(args);
        const centersIcon = new Element$1({
            id: 'centers-icon',
            dom: createSvg$5(img$7)
        });
        const ringsIcon = new Element$1({
            id: 'rings-icon',
            dom: createSvg$5(img$6)
        });
        const centersText = new Label({
            id: 'centers-text',
            text: localize('mode.centers')
        });
        const ringsText = new Label({
            id: 'rings-text',
            text: localize('mode.rings')
        });
        this.append(centersIcon);
        this.append(ringsIcon);
        this.append(centersText);
        this.append(ringsText);
        this.dom.addEventListener('pointerdown', (event) => {
            event.stopPropagation();
            events.fire('camera.toggleMode');
            events.fire('camera.setOverlay', true);
        });
        events.on('camera.mode', (mode) => {
            this.class[mode === 'centers' ? 'add' : 'remove']('centers-mode');
            this.class[mode === 'rings' ? 'add' : 'remove']('rings-mode');
        });
        tooltips.register(this, localize('tooltip.splat-mode'));
    }
}

var img$5 = "data:image/png;base64, ";

class Popup extends Container {
    show;
    hide;
    destroy;
    constructor(tooltips, args = {}) {
        args = {
            id: 'popup',
            hidden: true,
            tabIndex: -1,
            ...args
        };
        super(args);
        const dialog = new Container({
            id: 'popup-dialog'
        });
        const header = new Label({
            id: 'popup-header'
        });
        const text = new Label({
            id: 'popup-text'
        });
        const linkText = new Label({
            id: 'popup-link-text'
        });
        const linkCopy = new Button({
            id: 'popup-link-copy',
            icon: 'E351'
        });
        const linkRow = new Container({
            id: 'popup-link-row'
        });
        linkRow.append(linkText);
        linkRow.append(linkCopy);
        const okButton = new Button({
            class: 'popup-button',
            text: localize('popup.ok')
        });
        const cancelButton = new Button({
            class: 'popup-button',
            text: localize('popup.cancel')
        });
        const yesButton = new Button({
            class: 'popup-button',
            text: localize('popup.yes')
        });
        const noButton = new Button({
            class: 'popup-button',
            text: localize('popup.no')
        });
        const buttons = new Container({
            id: 'popup-buttons'
        });
        buttons.append(okButton);
        buttons.append(cancelButton);
        buttons.append(yesButton);
        buttons.append(noButton);
        dialog.append(header);
        dialog.append(text);
        dialog.append(linkRow);
        dialog.append(buttons);
        this.append(dialog);
        let okFn;
        let cancelFn;
        let yesFn;
        let noFn;
        let containerFn;
        let copyFn;
        okButton.on('click', () => {
            okFn();
        });
        cancelButton.on('click', () => {
            cancelFn();
        });
        yesButton.on('click', () => {
            yesFn();
        });
        noButton.on('click', () => {
            noFn();
        });
        this.on('click', () => {
            containerFn();
        });
        dialog.on('click', (event) => {
            event.stopPropagation();
        });
        linkCopy.on('click', () => {
            copyFn();
        });
        this.show = (options) => {
            header.text = options.header;
            text.text = options.message;
            const { type, link } = options;
            ['error', 'info', 'yesno', 'okcancel'].forEach((t) => {
                text.class[t === type ? 'add' : 'remove'](t);
            });
            // configure based on message type
            okButton.hidden = type === 'yesno';
            cancelButton.hidden = type !== 'okcancel';
            yesButton.hidden = type !== 'yesno';
            noButton.hidden = type !== 'yesno';
            this.hidden = false;
            linkRow.hidden = link === undefined;
            if (link !== undefined) {
                linkText.dom.innerHTML = `<a href='${link}' target='_blank'>${link}</a>`;
                linkCopy.icon = 'E352';
            }
            // take keyboard focus so shortcuts stop working
            this.dom.focus();
            return new Promise((resolve) => {
                okFn = () => {
                    this.hide();
                    resolve({
                        action: 'ok'
                    });
                };
                cancelFn = () => {
                    this.hide();
                    resolve({ action: 'cancel' });
                };
                yesFn = () => {
                    this.hide();
                    resolve({ action: 'yes' });
                };
                noFn = () => {
                    this.hide();
                    resolve({ action: 'no' });
                };
                containerFn = () => {
                    if (type === 'info' && link === undefined) {
                        cancelFn();
                    }
                };
                copyFn = () => {
                    navigator.clipboard.writeText(link);
                    linkCopy.icon = 'E348';
                };
            });
        };
        this.hide = () => {
            this.hidden = true;
        };
        this.destroy = () => {
            this.hide();
            super.destroy();
        };
        tooltips.register(linkCopy, localize('popup.copy-to-clipboard'));
    }
}

class Progress extends Container {
    setHeader;
    setText;
    setProgress;
    constructor(args = {}) {
        args = {
            ...args,
            id: 'progress-container',
            hidden: true
        };
        super(args);
        this.dom.tabIndex = 0;
        const header = new Label({
            id: 'header'
        });
        const text = new Element$1({
            dom: 'div',
            id: 'text'
        });
        const bar = new Element$1({
            dom: 'div',
            id: 'bar',
            class: 'pulsate'
        });
        const content = new Container({
            id: 'content'
        });
        content.append(text);
        content.append(bar);
        const dialog = new Container({
            id: 'dialog'
        });
        dialog.append(header);
        dialog.append(content);
        this.append(dialog);
        this.dom.addEventListener('keydown', (event) => {
            if (this.hidden)
                return;
            event.stopPropagation();
            event.preventDefault();
        });
        this.setHeader = (headerMsg) => {
            header.text = headerMsg;
        };
        this.setText = (textMsg) => {
            text.dom.textContent = textMsg;
        };
        this.setProgress = (progress) => {
            bar.dom.style.backgroundImage = `linear-gradient(90deg, #F60 0%, #F60 ${progress}%, #00000000 ${progress}%, #00000000 100%)`;
        };
    }
}

const createSvg$4 = (svgString, args = {}) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new Element$1({
        dom: new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement,
        ...args
    });
};
class PublishSettingsDialog extends Container {
    show;
    hide;
    destroy;
    constructor(events, args = {}) {
        args = {
            ...args,
            id: 'publish-settings-dialog',
            class: 'settings-dialog',
            hidden: true,
            tabIndex: -1
        };
        super(args);
        const dialog = new Container({
            id: 'dialog'
        });
        // header
        const headerIcon = createSvg$4(img$o, { id: 'icon' });
        const headerText = new Label({ id: 'text', text: localize('publish.header') });
        const header = new Container({ id: 'header' });
        header.append(headerIcon);
        header.append(headerText);
        // overwrite
        const overwriteLabel = new Label({ class: 'label', text: localize('publish.to') });
        const overwriteSelect = new SelectInput({
            class: 'select'
        });
        const overwriteRow = new Container({ class: 'row' });
        overwriteRow.append(overwriteLabel);
        overwriteRow.append(overwriteSelect);
        // title
        const titleLabel = new Label({ class: 'label', text: localize('publish.title') });
        const titleInput = new TextInput({ class: 'text-input' });
        const titleRow = new Container({ class: 'row' });
        titleRow.append(titleLabel);
        titleRow.append(titleInput);
        // description
        const descLabel = new Label({ class: 'label', text: localize('publish.description') });
        const descInput = new TextAreaInput({ class: 'text-area' });
        const descRow = new Container({ class: 'row' });
        descRow.append(descLabel);
        descRow.append(descInput);
        // listed
        const listLabel = new Label({ class: 'label', text: localize('publish.listed') });
        const listBoolean = new BooleanInput({ class: 'boolean', value: true });
        const listRow = new Container({ class: 'row' });
        listRow.append(listLabel);
        listRow.append(listBoolean);
        // start position
        const startLabel = new Label({ class: 'label', text: localize('export.start-position') });
        const startSelect = new SelectInput({
            class: 'select',
            defaultValue: 'viewport',
            options: [
                { v: 'default', t: localize('export.default') },
                { v: 'viewport', t: localize('export.viewport') },
                { v: 'pose', t: localize('export.pose-camera') }
            ]
        });
        const startRow = new Container({ class: 'row' });
        startRow.append(startLabel);
        startRow.append(startSelect);
        // animation
        const animationLabel = new Label({ class: 'label', text: localize('export.animation') });
        const animationSelect = new SelectInput({
            class: 'select',
            defaultValue: 'none',
            options: [
                { v: 'none', t: localize('export.animation-none') },
                { v: 'track', t: localize('export.animation-track') }
            ]
        });
        const animationRow = new Container({ class: 'row' });
        animationRow.append(animationLabel);
        animationRow.append(animationSelect);
        // clear color
        const colorLabel = new Label({ class: 'label', text: localize('export.background-color') });
        const colorPicker = new ColorPicker({
            class: 'color-picker',
            value: [1, 1, 1, 1]
        });
        const colorRow = new Container({ class: 'row' });
        colorRow.append(colorLabel);
        colorRow.append(colorPicker);
        // fov
        const fovLabel = new Label({ class: 'label', text: localize('export.fov') });
        const fovSlider = new SliderInput({
            class: 'slider',
            min: 10,
            max: 120,
            precision: 0,
            value: 60
        });
        const fovRow = new Container({ class: 'row' });
        fovRow.append(fovLabel);
        fovRow.append(fovSlider);
        // bands
        const bandsLabel = new Label({ class: 'label', text: localize('export.sh-bands') });
        const bandsSlider = new SliderInput({
            class: 'slider',
            min: 0,
            max: 3,
            precision: 0,
            value: 3
        });
        const bandsRow = new Container({ class: 'row' });
        bandsRow.append(bandsLabel);
        bandsRow.append(bandsSlider);
        // format
        const formatLabel = new Label({ class: 'label', text: localize('export.format') });
        const formatSelect = new SelectInput({
            class: 'select',
            defaultValue: 'sog',
            options: [
                { v: 'sog', t: localize('export.format-sog') },
                { v: 'compressed.ply', t: localize('export.format-compressed-ply') }
            ]
        });
        const formatRow = new Container({ class: 'row' });
        formatRow.append(formatLabel);
        formatRow.append(formatSelect);
        // content
        const content = new Container({ id: 'content' });
        content.append(overwriteRow);
        content.append(titleRow);
        content.append(descRow);
        content.append(listRow);
        content.append(startRow);
        content.append(animationRow);
        content.append(colorRow);
        content.append(fovRow);
        content.append(bandsRow);
        content.append(formatRow);
        // footer
        const footer = new Container({ id: 'footer' });
        const cancelButton = new Button({
            class: 'button',
            text: localize('publish.cancel')
        });
        const okButton = new Button({
            class: 'button',
            text: localize('publish.ok')
        });
        footer.append(cancelButton);
        footer.append(okButton);
        dialog.append(header);
        dialog.append(content);
        dialog.append(footer);
        this.append(dialog);
        // handle key bindings for enter and escape
        let onCancel;
        let onOK;
        cancelButton.on('click', () => onCancel());
        okButton.on('click', () => onOK());
        const keydown = (e) => {
            switch (e.key) {
                case 'Escape':
                    onCancel();
                    break;
                case 'Enter':
                    if (!e.shiftKey)
                        onOK();
                    break;
                default:
                    e.stopPropagation();
                    break;
            }
        };
        overwriteSelect.on('change', () => {
            const isNew = overwriteSelect.value === '0';
            titleInput.disabled = !isNew;
            descInput.disabled = !isNew;
            listBoolean.disabled = !isNew;
        });
        // reset UI and configure for current state
        const reset = (hasPoses, overwriteList) => {
            const splats = events.invoke('scene.splats');
            const filename = splats[0].filename;
            const dot = splats[0].filename.lastIndexOf('.');
            const bgClr = events.invoke('bgClr');
            overwriteSelect.options = [{
                    v: '0', t: localize('publish.new-scene')
                }].concat(overwriteList.map((s, i) => ({ v: (i + 1).toString(), t: s })));
            overwriteSelect.value = '0';
            titleInput.value = filename.slice(0, dot > 0 ? dot : undefined);
            descInput.value = '';
            listBoolean.value = true;
            startSelect.value = hasPoses ? 'pose' : 'viewport';
            startSelect.disabledOptions = hasPoses ? {} : { pose: startSelect.options[2].t };
            animationSelect.value = hasPoses ? 'track' : 'none';
            animationSelect.disabledOptions = hasPoses ? {} : { track: animationSelect.options[1].t };
            colorPicker.value = [bgClr.r, bgClr.g, bgClr.b];
            fovSlider.value = events.invoke('camera.fov');
            bandsSlider.value = events.invoke('view.bands');
            formatSelect.value = 'sog';
        };
        // function implementations
        this.show = (userStatus) => {
            const frames = events.invoke('timeline.frames');
            const frameRate = events.invoke('timeline.frameRate');
            const smoothness = events.invoke('timeline.smoothness');
            // get poses
            const orderedPoses = events.invoke('camera.poses')
                .slice()
                .filter(p => p.frame >= 0 && p.frame < frames)
                .sort((a, b) => a.frame - b.frame);
            // overwrite options
            const overwriteList = userStatus.scenes.map((s) => {
                return `${s.hash} - ${s.title}`;
            });
            // reset UI
            reset(orderedPoses.length > 0, overwriteList);
            this.hidden = false;
            this.dom.addEventListener('keydown', keydown);
            this.dom.focus();
            return new Promise((resolve) => {
                onCancel = () => {
                    resolve(null);
                };
                onOK = () => {
                    // extract camera starting position
                    let pose;
                    switch (startSelect.value) {
                        case 'pose':
                            pose = orderedPoses?.[0];
                            break;
                        case 'viewport':
                            pose = events.invoke('camera.getPose');
                            break;
                    }
                    const p = pose?.position;
                    const t = pose?.target;
                    const startAnim = (() => {
                        switch (animationSelect.value) {
                            case 'none': return 'none';
                            case 'track': return 'animTrack';
                        }
                    })();
                    // extract camera animation
                    const animTracks = [];
                    switch (startAnim) {
                        case 'none':
                            break;
                        case 'animTrack': {
                            // use camera poses
                            const times = [];
                            const position = [];
                            const target = [];
                            for (let i = 0; i < orderedPoses.length; ++i) {
                                const p = orderedPoses[i];
                                times.push(p.frame);
                                position.push(p.position.x, p.position.y, p.position.z);
                                target.push(p.target.x, p.target.y, p.target.z);
                            }
                            animTracks.push({
                                name: 'cameraAnim',
                                duration: frames / frameRate,
                                frameRate,
                                target: 'camera',
                                loopMode: 'repeat',
                                interpolation: 'spline',
                                smoothness,
                                keyframes: {
                                    times,
                                    values: { position, target }
                                }
                            });
                            break;
                        }
                    }
                    // build experience details
                    const experienceSettings = {
                        camera: {
                            fov: fovSlider.value,
                            position: p ? [p.x, p.y, p.z] : null,
                            target: t ? [t.x, t.y, t.z] : null,
                            startAnim,
                            animTrack: startAnim === 'animTrack' ? 'cameraAnim' : null
                        },
                        background: {
                            color: colorPicker.value.slice()
                        },
                        animTracks
                    };
                    const serializeSettings = {
                        maxSHBands: bandsSlider.value,
                        minOpacity: 1 / 255, // remove completely semitransparent splats
                        removeInvalid: true // remove gaussians with any NaN data
                    };
                    resolve({
                        user: userStatus.user,
                        title: titleInput.value,
                        description: descInput.value,
                        listed: listBoolean.value,
                        serializeSettings,
                        experienceSettings,
                        format: formatSelect.value,
                        overwriteId: overwriteSelect.value !== '0' ? userStatus.scenes[parseInt(overwriteSelect.value, 10) - 1].id : undefined
                    });
                };
            }).finally(() => {
                this.dom.removeEventListener('keydown', keydown);
                this.hide();
            });
        };
        this.hide = () => {
            this.hidden = true;
        };
        this.destroy = () => {
            this.hide();
            super.destroy();
        };
    }
}

var img$4 = "data:image/svg+xml,%3csvg width='38' height='38' viewBox='0 0 38 38' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12 14C12 12.8954 12.8954 12 14 12H24C25.1046 12 26 12.8954 26 14V24C26 25.1046 25.1046 26 24 26H14C12.8954 26 12 25.1046 12 24V14Z' stroke='currentColor' stroke-width='1.5' stroke-dasharray='4 3' fill-opacity='0'/%3e%3cpath d='M15 16C15 15.4477 15.4477 15 16 15H22C22.5523 15 23 15.4477 23 16V22C23 22.5523 22.5523 23 22 23H16C15.4477 23 15 22.5523 15 22V16Z' fill='currentColor'/%3e%3c/svg%3e";

var img$3 = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38' fill='none'%3e%3cg transform='scale(2) translate(3.5%2c 3.25)'%3e %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M6 1.5C6.27614 1.5 6.5 1.72386 6.5 2V6.22288L10.265 8.576C10.4992 8.72236 10.5704 9.03083 10.424 9.265C10.2776 9.49917 9.96917 9.57035 9.735 9.424L6 7.08962L2.265 9.424C2.03083 9.57035 1.72235 9.49917 1.576 9.265C1.42964 9.03083 1.50083 8.72236 1.735 8.576L5.5 6.22288V2C5.5 1.72386 5.72386 1.5 6 1.5Z' fill='currentColor'/%3e%3c/g%3e%3c/svg%3e";

var img$2 = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3e %3cg transform='scale(0.5) translate(12 12)'%3e %3crect fill-rule='nonzero' x='0' y='0' width='24' height='24'%3e%3c/rect%3e %3cline x1='4' y1='7' x2='12' y2='7' stroke='currentColor' stroke-width='2' stroke-linecap='round'%3e%3c/line%3e %3cline x1='4' y1='17' x2='6' y2='17' stroke='currentColor' stroke-width='2' stroke-linecap='round'%3e%3c/line%3e %3cline x1='18' y1='7' x2='20' y2='7' stroke='currentColor' stroke-width='2' stroke-linecap='round'%3e%3c/line%3e %3cline x1='13' y1='17' x2='20' y2='17' stroke='currentColor' stroke-width='2' stroke-linecap='round'%3e%3c/line%3e %3ccircle stroke='currentColor' stroke-width='2' stroke-linecap='round' cx='15' cy='7' r='3'%3e%3c/circle%3e %3ccircle stroke='currentColor' stroke-width='2' stroke-linecap='round' cx='9' cy='17' r='3'%3e%3c/circle%3e %3c/g%3e%3c/svg%3e";

const createSvg$3 = (svgString) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement;
};
class RightToolbar extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            ...args,
            id: 'right-toolbar'
        };
        super(args);
        this.dom.addEventListener('pointerdown', (event) => {
            event.stopPropagation();
        });
        const ringsModeToggle = new Button({
            id: 'right-toolbar-mode-toggle',
            class: 'right-toolbar-toggle'
        });
        const showHideSplats = new Button({
            id: 'right-toolbar-show-hide',
            class: ['right-toolbar-toggle', 'active']
        });
        const cameraFrameSelection = new Button({
            id: 'right-toolbar-frame-selection',
            class: 'right-toolbar-button'
        });
        const cameraReset = new Button({
            id: 'right-toolbar-camera-origin',
            class: 'right-toolbar-button'
        });
        const colorPanel = new Button({
            id: 'right-toolbar-color-panel',
            class: 'right-toolbar-toggle'
        });
        const options = new Button({
            id: 'right-toolbar-options',
            class: 'right-toolbar-toggle',
            icon: 'E283'
        });
        const centersDom = createSvg$3(img$7);
        const ringsDom = createSvg$3(img$6);
        ringsDom.style.display = 'none';
        ringsModeToggle.dom.appendChild(centersDom);
        ringsModeToggle.dom.appendChild(ringsDom);
        showHideSplats.dom.appendChild(createSvg$3(img$q));
        cameraFrameSelection.dom.appendChild(createSvg$3(img$4));
        cameraReset.dom.appendChild(createSvg$3(img$3));
        colorPanel.dom.appendChild(createSvg$3(img$2));
        this.append(ringsModeToggle);
        this.append(showHideSplats);
        this.append(new Element$1({ class: 'right-toolbar-separator' }));
        this.append(cameraFrameSelection);
        this.append(cameraReset);
        this.append(colorPanel);
        this.append(new Element$1({ class: 'right-toolbar-separator' }));
        this.append(options);
        tooltips.register(ringsModeToggle, localize('tooltip.splat-mode'), 'left');
        tooltips.register(showHideSplats, localize('tooltip.show-hide'), 'left');
        tooltips.register(cameraFrameSelection, localize('tooltip.frame-selection'), 'left');
        tooltips.register(cameraReset, localize('tooltip.camera-reset'), 'left');
        tooltips.register(colorPanel, localize('tooltip.color-panel'), 'left');
        tooltips.register(options, localize('tooltip.view-options'), 'left');
        // add event handlers
        ringsModeToggle.on('click', () => {
            events.fire('camera.toggleMode');
            events.fire('camera.setOverlay', true);
        });
        showHideSplats.on('click', () => events.fire('camera.toggleOverlay'));
        cameraFrameSelection.on('click', () => events.fire('camera.focus'));
        cameraReset.on('click', () => events.fire('camera.reset'));
        colorPanel.on('click', () => events.fire('colorPanel.toggleVisible'));
        options.on('click', () => events.fire('viewPanel.toggleVisible'));
        events.on('camera.mode', (mode) => {
            ringsModeToggle.class[mode === 'rings' ? 'add' : 'remove']('active');
            centersDom.style.display = mode === 'rings' ? 'none' : 'block';
            ringsDom.style.display = mode === 'rings' ? 'block' : 'none';
        });
        events.on('camera.overlay', (value) => {
            showHideSplats.class[value ? 'add' : 'remove']('active');
        });
        events.on('colorPanel.visible', (visible) => {
            colorPanel.class[visible ? 'add' : 'remove']('active');
        });
        events.on('viewPanel.visible', (visible) => {
            options.class[visible ? 'add' : 'remove']('active');
        });
    }
}

var img$1 = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='4 4 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.33313 10.6271C7.1272 10.4432 6.81112 10.461 6.62714 10.6669C6.44316 10.8728 6.4615 11.1894 6.66743 11.3734L6.66819 11.374L6.67032 11.3759L6.67705 11.3818L6.70021 11.4019C6.71991 11.4188 6.74808 11.4426 6.7843 11.4724C6.85671 11.5318 6.96148 11.6151 7.09523 11.7142C7.1782 11.7757 7.27253 11.8434 7.37741 11.9155L6.64645 12.6465C6.45118 12.8417 6.45118 13.1583 6.64645 13.3536C6.84171 13.5488 7.15829 13.5488 7.35355 13.3536L8.25285 12.4543C8.73378 12.7179 9.31351 12.9846 9.96266 13.1827L9.6318 15.1678C9.58641 15.4402 9.77042 15.6978 10.0428 15.7432C10.3152 15.7886 10.5728 15.6046 10.6182 15.3322L10.9382 13.4119C11.2804 13.468 11.6354 13.5 12 13.5C12.3646 13.5 12.7196 13.468 13.0618 13.4119L13.3818 15.3322C13.4272 15.6046 13.6848 15.7886 13.9572 15.7432C14.2296 15.6978 14.4136 15.4402 14.3682 15.1678L14.0373 13.1827C14.6865 12.9846 15.2662 12.7179 15.7472 12.4543L16.6464 13.3536C16.8417 13.5488 17.1583 13.5488 17.3536 13.3536C17.5488 13.1583 17.5488 12.8417 17.3536 12.6465L16.6226 11.9155C16.7275 11.8434 16.8218 11.7757 16.9048 11.7142C17.0385 11.6151 17.1433 11.5318 17.2157 11.4724C17.2519 11.4426 17.2801 11.4188 17.2998 11.4019L17.323 11.3818L17.3297 11.3759L17.3318 11.374L17.3331 11.3729C17.3333 11.3728 17.3331 11.3729 17 11L17.3331 11.3729C17.5391 11.1889 17.5568 10.8728 17.3729 10.6669C17.1889 10.461 16.8728 10.4433 16.6669 10.6271L16.664 10.6296L16.6486 10.643C16.6341 10.6554 16.6115 10.6746 16.5811 10.6995C16.5203 10.7494 16.4286 10.8224 16.3094 10.9108C16.0705 11.0878 15.7231 11.3251 15.2937 11.5624C14.4289 12.0403 13.2643 12.5 12 12.5C10.7357 12.5 9.57108 12.0403 8.7063 11.5624C8.27693 11.3251 7.92953 11.0878 7.69063 10.9108C7.57136 10.8224 7.47967 10.7494 7.41888 10.6995C7.38849 10.6746 7.36587 10.6554 7.35143 10.643L7.33598 10.6296L7.33313 10.6271ZM7.00028 10.9997C7.33313 10.6271 7.33323 10.6272 7.33313 10.6271L7.00028 10.9997ZM7 11L6.66743 11.3734C6.6673 11.3732 6.66688 11.3729 7 11Z' fill='currentColor'/%3e%3c/svg%3e";

var img = "data:image/svg+xml,%3csvg width='16' height='16' viewBox='4 4 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11ZM10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12Z' fill='currentColor'/%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11.9999 9.5C9.64477 9.5 8.00887 10.9874 7.28843 11.8087C7.18771 11.9235 7.18771 12.0765 7.28843 12.1913C8.00887 13.0126 9.64477 14.5 11.9999 14.5C14.3551 14.5 15.991 13.0126 16.7114 12.1913C16.8121 12.0765 16.8121 11.9235 16.7114 11.8087C15.991 10.9874 14.3551 9.5 11.9999 9.5ZM6.53667 11.1492C7.32348 10.2523 9.21141 8.5 11.9999 8.5C14.7884 8.5 16.6764 10.2523 17.4632 11.1492C17.8949 11.6414 17.8949 12.3586 17.4632 12.8508C16.6764 13.7477 14.7884 15.5 11.9999 15.5C9.21141 15.5 7.32348 13.7477 6.53667 12.8508C6.10496 12.3586 6.10496 11.6414 6.53667 11.1492Z' fill='currentColor'/%3e%3c/svg%3e";

const createSvg$2 = (svgString) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement;
};
class SplatItem extends Container {
    getName;
    setName;
    getSelected;
    setSelected;
    getVisible;
    setVisible;
    destroy;
    constructor(name, edit, args = {}) {
        args = {
            ...args,
            class: ['splat-item', 'visible']
        };
        super(args);
        const text = new Label({
            class: 'splat-item-text',
            text: name
        });
        const visible = new Element$1({
            dom: createSvg$2(img),
            class: 'splat-item-visible'
        });
        const invisible = new Element$1({
            dom: createSvg$2(img$1),
            class: 'splat-item-visible',
            hidden: true
        });
        const remove = new Element$1({
            dom: createSvg$2(img$l),
            class: 'splat-item-delete'
        });
        this.append(text);
        this.append(visible);
        this.append(invisible);
        this.append(remove);
        this.getName = () => {
            return text.value;
        };
        this.setName = (value) => {
            text.value = value;
        };
        this.getSelected = () => {
            return this.class.contains('selected');
        };
        this.setSelected = (value) => {
            if (value !== this.selected) {
                if (value) {
                    this.class.add('selected');
                    this.emit('select', this);
                }
                else {
                    this.class.remove('selected');
                    this.emit('unselect', this);
                }
            }
        };
        this.getVisible = () => {
            return this.class.contains('visible');
        };
        this.setVisible = (value) => {
            if (value !== this.visible) {
                visible.hidden = !value;
                invisible.hidden = value;
                if (value) {
                    this.class.add('visible');
                    this.emit('visible', this);
                }
                else {
                    this.class.remove('visible');
                    this.emit('invisible', this);
                }
            }
        };
        const toggleVisible = (event) => {
            event.stopPropagation();
            this.visible = !this.visible;
        };
        const handleRemove = (event) => {
            event.stopPropagation();
            this.emit('removeClicked', this);
        };
        // rename on double click
        text.dom.addEventListener('dblclick', (event) => {
            event.stopPropagation();
            const onblur = () => {
                this.remove(edit);
                this.emit('rename', edit.value);
                edit.input.removeEventListener('blur', onblur);
                text.hidden = false;
            };
            text.hidden = true;
            this.appendAfter(edit, text);
            edit.value = text.value;
            edit.input.addEventListener('blur', onblur);
            edit.focus();
        });
        // handle clicks
        visible.dom.addEventListener('click', toggleVisible);
        invisible.dom.addEventListener('click', toggleVisible);
        remove.dom.addEventListener('click', handleRemove);
        this.destroy = () => {
            visible.dom.removeEventListener('click', toggleVisible);
            invisible.dom.removeEventListener('click', toggleVisible);
            remove.dom.removeEventListener('click', handleRemove);
        };
    }
    set name(value) {
        this.setName(value);
    }
    get name() {
        return this.getName();
    }
    set selected(value) {
        this.setSelected(value);
    }
    get selected() {
        return this.getSelected();
    }
    set visible(value) {
        this.setVisible(value);
    }
    get visible() {
        return this.getVisible();
    }
}
class SplatList extends Container {
    constructor(events, args = {}) {
        args = {
            ...args,
            class: 'splat-list'
        };
        super(args);
        const items = new Map();
        // edit input used during renames
        const edit = new TextInput({
            id: 'splat-edit'
        });
        events.on('scene.elementAdded', (element) => {
            if (element.type === ElementType.splat) {
                const splat = element;
                const item = new SplatItem(splat.name, edit);
                this.append(item);
                items.set(splat, item);
                item.on('visible', () => {
                    splat.visible = true;
                    // also select it if there is no other selection
                    if (!events.invoke('selection')) {
                        events.fire('selection', splat);
                    }
                });
                item.on('invisible', () => {
                    splat.visible = false;
                });
                item.on('rename', (value) => {
                    events.fire('edit.add', new SplatRenameOp(splat, value));
                });
            }
        });
        events.on('scene.elementRemoved', (element) => {
            if (element.type === ElementType.splat) {
                const splat = element;
                const item = items.get(splat);
                if (item) {
                    this.remove(item);
                    items.delete(splat);
                }
            }
        });
        events.on('selection.changed', (selection) => {
            items.forEach((value, key) => {
                value.selected = key === selection;
            });
        });
        events.on('splat.name', (splat) => {
            const item = items.get(splat);
            if (item) {
                item.name = splat.name;
            }
        });
        events.on('splat.visibility', (splat) => {
            const item = items.get(splat);
            if (item) {
                item.visible = splat.visible;
            }
        });
        this.on('click', (item) => {
            for (const [key, value] of items) {
                if (item === value) {
                    events.fire('selection', key);
                    break;
                }
            }
        });
        this.on('removeClicked', async (item) => {
            let splat;
            for (const [key, value] of items) {
                if (item === value) {
                    splat = key;
                    break;
                }
            }
            if (!splat) {
                return;
            }
            const result = await events.invoke('showPopup', {
                type: 'yesno',
                header: 'Remove Splat',
                message: `Are you sure you want to remove '${splat.name}' from the scene? This operation can not be undone.`
            });
            if (result?.action === 'yes') {
                splat.destroy();
            }
        });
    }
    _onAppendChild(element) {
        super._onAppendChild(element);
        if (element instanceof SplatItem) {
            element.on('click', () => {
                this.emit('click', element);
            });
            element.on('removeClicked', () => {
                this.emit('removeClicked', element);
            });
        }
    }
    _onRemoveChild(element) {
        if (element instanceof SplatItem) {
            element.unbind('click');
            element.unbind('removeClicked');
        }
        super._onRemoveChild(element);
    }
}

const v = new Vec3();
class Transform extends Container {
    constructor(events, args = {}) {
        args = {
            ...args,
            id: 'transform'
        };
        super(args);
        const axis = new Container({
            class: 'transform-row'
        });
        const axisLabel = new Label({
            class: 'transform-label',
            text: ''
        });
        const xLabel = new Label({
            class: ['transform-expand', 'transform-label', 'transform-axis-label'],
            text: 'x'
        });
        const yLabel = new Label({
            class: ['transform-expand', 'transform-label', 'transform-axis-label'],
            text: 'y'
        });
        const zLabel = new Label({
            class: ['transform-expand', 'transform-label', 'transform-axis-label'],
            text: 'z'
        });
        axis.append(axisLabel);
        axis.append(xLabel);
        axis.append(yLabel);
        axis.append(zLabel);
        // position
        const position = new Container({
            class: 'transform-row'
        });
        const positionLabel = new Label({
            class: 'transform-label',
            text: localize('position')
        });
        const positionVector = new VectorInput({
            class: 'transform-expand',
            precision: 3,
            dimensions: 3,
            value: [0, 0, 0],
            enabled: false
        });
        position.append(positionLabel);
        position.append(positionVector);
        // rotation
        const rotation = new Container({
            class: 'transform-row'
        });
        const rotationLabel = new Label({
            class: 'transform-label',
            text: localize('rotation')
        });
        const rotationVector = new VectorInput({
            class: 'transform-expand',
            precision: 2,
            dimensions: 3,
            value: [0, 0, 0],
            enabled: false
        });
        rotation.append(rotationLabel);
        rotation.append(rotationVector);
        // scale
        const scale = new Container({
            class: 'transform-row'
        });
        const scaleLabel = new Label({
            class: 'transform-label',
            text: localize('scale')
        });
        const scaleInput = new NumericInput({
            class: 'transform-expand',
            precision: 3,
            value: 1,
            min: 0.001,
            max: 10000,
            enabled: false
        });
        scale.append(scaleLabel);
        scale.append(scaleInput);
        this.append(axis);
        this.append(position);
        this.append(rotation);
        this.append(scale);
        const toArray = (v) => {
            return [v.x, v.y, v.z];
        };
        let uiUpdating = false;
        let mouseUpdating = false;
        // update UI with pivot
        const updateUI = (pivot) => {
            uiUpdating = true;
            const transform = pivot.transform;
            transform.rotation.getEulerAngles(v);
            positionVector.value = toArray(transform.position);
            rotationVector.value = toArray(v);
            scaleInput.value = transform.scale.x;
            uiUpdating = false;
        };
        // update pivot with UI
        const updatePivot = (pivot) => {
            const p = positionVector.value;
            const r = rotationVector.value;
            const q = new Quat().setFromEulerAngles(r[0], r[1], r[2]);
            const s = scaleInput.value;
            if (q.w < 0) {
                q.mulScalar(-1);
            }
            pivot.moveTRS(new Vec3(p[0], p[1], p[2]), q, new Vec3(s, s, s));
        };
        // handle a change in the UI state
        const change = () => {
            if (!uiUpdating) {
                const pivot = events.invoke('pivot');
                if (mouseUpdating) {
                    updatePivot(pivot);
                }
                else {
                    pivot.start();
                    updatePivot(pivot);
                    pivot.end();
                }
            }
        };
        const mousedown = () => {
            mouseUpdating = true;
            const pivot = events.invoke('pivot');
            pivot.start();
        };
        const mouseup = () => {
            const pivot = events.invoke('pivot');
            updatePivot(pivot);
            mouseUpdating = false;
            pivot.end();
        };
        [positionVector.inputs, rotationVector.inputs, scaleInput].flat().forEach((input) => {
            input.on('change', change);
            input.on('slider:mousedown', mousedown);
            input.on('slider:mouseup', mouseup);
        });
        // toggle ui availability based on selection
        events.on('selection.changed', (selection) => {
            positionVector.enabled = rotationVector.enabled = scaleInput.enabled = !!selection;
        });
        events.on('pivot.placed', (pivot) => {
            updateUI(pivot);
        });
        events.on('pivot.moved', (pivot) => {
            if (!mouseUpdating) {
                updateUI(pivot);
            }
        });
        events.on('pivot.ended', (pivot) => {
            updateUI(pivot);
        });
    }
}

const createSvg$1 = (svgString) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement;
};
class ScenePanel extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            ...args,
            id: 'scene-panel',
            class: 'panel'
        };
        super(args);
        // stop pointer events bubbling
        ['pointerdown', 'pointerup', 'pointermove', 'wheel', 'dblclick'].forEach((eventName) => {
            this.dom.addEventListener(eventName, (event) => event.stopPropagation());
        });
        const sceneHeader = new Container({
            class: 'panel-header'
        });
        const sceneIcon = new Label({
            text: '\uE344',
            class: 'panel-header-icon'
        });
        const sceneLabel = new Label({
            text: localize('scene-manager'),
            class: 'panel-header-label'
        });
        const sceneImport = new Container({
            class: 'panel-header-button'
        });
        sceneImport.dom.appendChild(createSvg$1(img$k));
        const sceneNew = new Container({
            class: 'panel-header-button'
        });
        sceneNew.dom.appendChild(createSvg$1(img$j));
        sceneHeader.append(sceneIcon);
        sceneHeader.append(sceneLabel);
        sceneHeader.append(sceneImport);
        sceneHeader.append(sceneNew);
        sceneImport.on('click', async () => {
            await events.invoke('scene.import');
        });
        sceneNew.on('click', () => {
            events.invoke('doc.new');
        });
        tooltips.register(sceneImport, 'Import Scene', 'top');
        tooltips.register(sceneNew, 'New Scene', 'top');
        const splatList = new SplatList(events);
        const splatListContainer = new Container({
            class: 'splat-list-container'
        });
        splatListContainer.append(splatList);
        const transformHeader = new Container({
            class: 'panel-header'
        });
        const transformIcon = new Label({
            text: '\uE111',
            class: 'panel-header-icon'
        });
        const transformLabel = new Label({
            text: localize('transform'),
            class: 'panel-header-label'
        });
        transformHeader.append(transformIcon);
        transformHeader.append(transformLabel);
        this.append(sceneHeader);
        this.append(splatListContainer);
        this.append(transformHeader);
        this.append(new Transform(events));
        this.append(new Element$1({
            class: 'panel-header',
            height: 20
        }));
    }
}

const shortcutList = [
    { header: 'tools' },
    { key: '1', action: 'move' },
    { key: '2', action: 'rotate' },
    { key: '3', action: 'scale' },
    { key: 'R', action: 'rect-selection' },
    { key: 'B', action: 'brush-selection' },
    { key: 'P', action: 'picker-selection' },
    { key: '[ ]', action: 'brush-size' },
    { key: 'Esc', action: 'deactivate-tool' },
    { header: 'selection' },
    { key: 'Ctrl + A', action: 'select-all' },
    { key: 'Shift + A', action: 'deselect-all' },
    { key: 'Ctrl + I', action: 'invert-selection' },
    { key: 'Shift', action: 'add-to-selection' },
    { key: 'Ctrl', action: 'remove-from-selection' },
    { key: 'Delete', action: 'delete-selected-splats' },
    { header: 'show' },
    { key: 'H', action: 'hide-selected-splats' },
    { key: 'U', action: 'unhide-all-splats' },
    { key: 'D', action: 'toggle-data-panel' },
    { header: 'other' },
    { key: 'Tab', action: 'select-next-splat' },
    { key: 'Ctrl + Z', action: 'undo' },
    { key: 'Ctrl + Shift + Z', action: 'redo' },
    { key: 'Space', action: 'toggle-splat-overlay' },
    { key: 'F', action: 'focus-camera' },
    { key: 'M', action: 'toggle-camera-mode' },
    { key: 'G', action: 'toggle-grid' },
    { key: 'C', action: 'toggle-gizmo-coordinate-space' }
];
class ShortcutsPopup extends Overlay {
    constructor(args = {}) {
        args = {
            ...args,
            id: 'shortcuts-popup',
            clickable: true,
            hidden: true
        };
        super(args);
        const shortcutsContainer = new Container({
            id: 'shortcuts-container'
        });
        shortcutList.forEach((shortcut) => {
            if (shortcut.header) {
                const label = new Label({
                    class: 'shortcut-header-label',
                    text: localize(`shortcuts.${shortcut.header}`)
                });
                const entry = new Container({
                    class: 'shortcut-header'
                });
                entry.append(label);
                shortcutsContainer.append(entry);
            }
            else {
                const key = new Label({
                    class: 'shortcut-key',
                    text: shortcut.key
                });
                const action = new Label({
                    class: 'shortcut-action',
                    text: localize(`shortcuts.${shortcut.action}`)
                });
                const entry = new Container({
                    class: 'shortcut-entry'
                });
                entry.append(key);
                entry.append(action);
                shortcutsContainer.append(entry);
            }
        });
        const shortcutsPanel = new Panel({
            id: 'shortcuts-panel',
            headerText: localize('shortcuts.title')
        });
        shortcutsPanel.append(shortcutsContainer);
        this.append(shortcutsPanel);
    }
}

class Spinner extends Container {
    constructor(args = {}) {
        args = {
            ...args,
            id: 'spinner-container',
            hidden: true
        };
        super(args);
        this.dom.tabIndex = 0;
        const spinner = new Element$1({
            dom: 'div',
            class: 'spinner'
        });
        this.append(spinner);
        this.dom.addEventListener('keydown', (event) => {
            if (this.hidden)
                return;
            event.stopPropagation();
            event.preventDefault();
        });
    }
}

class Ticks extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            ...args,
            id: 'ticks'
        };
        super(args);
        const workArea = new Container({
            id: 'ticks-area'
        });
        this.append(workArea);
        let addKey;
        let removeKey;
        let frameFromOffset;
        let moveCursor;
        // rebuild the timeline
        const rebuild = () => {
            // clear existing labels
            workArea.dom.innerHTML = '';
            const numFrames = events.invoke('timeline.frames');
            const currentFrame = events.invoke('timeline.frame');
            const padding = 20;
            const width = this.dom.getBoundingClientRect().width - padding * 2;
            const labelStep = Math.max(1, Math.floor(numFrames / Math.max(1, Math.floor(width / 50))));
            const numLabels = Math.max(1, Math.ceil(numFrames / labelStep));
            const offsetFromFrame = (frame) => {
                return padding + Math.floor(frame / (numFrames - 1) * width);
            };
            frameFromOffset = (offset) => {
                return Math.max(0, Math.min(numFrames - 1, Math.floor((offset - padding) / width * (numFrames - 1))));
            };
            // timeline labels
            for (let i = 0; i < numLabels; i++) {
                const thisFrame = Math.floor(i * labelStep);
                const label = document.createElement('div');
                label.classList.add('time-label');
                label.style.left = `${offsetFromFrame(thisFrame)}px`;
                label.textContent = thisFrame.toString();
                workArea.dom.appendChild(label);
            }
            // keys
            const keys = [];
            const createKey = (value) => {
                const label = document.createElement('div');
                label.classList.add('time-label', 'key');
                label.style.left = `${offsetFromFrame(value)}px`;
                let dragging = false;
                let toFrame = -1;
                label.addEventListener('pointerdown', (event) => {
                    if (!dragging && event.isPrimary) {
                        dragging = true;
                        label.classList.add('dragging');
                        label.setPointerCapture(event.pointerId);
                        event.stopPropagation();
                    }
                });
                label.addEventListener('pointermove', (event) => {
                    if (dragging) {
                        toFrame = frameFromOffset(parseInt(label.style.left, 10) + event.offsetX);
                        label.style.left = `${offsetFromFrame(toFrame)}px`;
                    }
                });
                label.addEventListener('pointerup', (event) => {
                    if (dragging && event.isPrimary) {
                        const fromIndex = keys.indexOf(label);
                        const fromFrame = events.invoke('timeline.keys')[fromIndex];
                        if (fromFrame !== toFrame) {
                            events.fire('timeline.move', fromFrame, toFrame);
                            events.fire('timeline.frame', events.invoke('timeline.frame'));
                        }
                        label.releasePointerCapture(event.pointerId);
                        label.classList.remove('dragging');
                        dragging = false;
                    }
                });
                workArea.dom.appendChild(label);
                keys.push(label);
            };
            events.invoke('timeline.keys').forEach(createKey);
            addKey = (value) => {
                createKey(value);
            };
            removeKey = (index) => {
                workArea.dom.removeChild(keys[index]);
                keys.splice(index, 1);
            };
            // cursor
            const cursor = document.createElement('div');
            cursor.classList.add('time-label', 'cursor');
            cursor.style.left = `${offsetFromFrame(currentFrame)}px`;
            cursor.textContent = currentFrame.toString();
            workArea.dom.appendChild(cursor);
            moveCursor = (frame) => {
                cursor.style.left = `${offsetFromFrame(frame)}px`;
                cursor.textContent = frame.toString();
            };
        };
        // handle scrubbing
        let scrubbing = false;
        workArea.dom.addEventListener('pointerdown', (event) => {
            if (!scrubbing && event.isPrimary) {
                scrubbing = true;
                workArea.dom.setPointerCapture(event.pointerId);
                events.fire('timeline.setFrame', frameFromOffset(event.offsetX));
            }
        });
        workArea.dom.addEventListener('pointermove', (event) => {
            if (scrubbing) {
                events.fire('timeline.setFrame', frameFromOffset(event.offsetX));
            }
        });
        workArea.dom.addEventListener('pointerup', (event) => {
            if (scrubbing && event.isPrimary) {
                workArea.dom.releasePointerCapture(event.pointerId);
                scrubbing = false;
            }
        });
        // rebuild the timeline on dom resize
        new ResizeObserver(() => rebuild()).observe(workArea.dom);
        // rebuild when timeline frames change
        events.on('timeline.frames', () => {
            rebuild();
        });
        events.on('timeline.frame', (frame) => {
            moveCursor(frame);
        });
        events.on('timeline.keyAdded', (value) => {
            addKey(value);
        });
        events.on('timeline.keyRemoved', (index) => {
            removeKey(index);
        });
    }
}
class TimelinePanel extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            ...args,
            id: 'timeline-panel'
        };
        super(args);
        // play controls
        const prev = new Button({
            class: 'button',
            text: '\uE162'
        });
        const play = new Button({
            class: 'button',
            text: '\uE131'
        });
        const next = new Button({
            class: 'button',
            text: '\uE164'
        });
        // key controls
        const addKey = new Button({
            class: 'button',
            text: '\uE120'
        });
        const removeKey = new Button({
            class: 'button',
            text: '\uE121',
            enabled: false
        });
        const buttonControls = new Container({
            id: 'button-controls'
        });
        buttonControls.append(prev);
        buttonControls.append(play);
        buttonControls.append(next);
        buttonControls.append(addKey);
        buttonControls.append(removeKey);
        // settings
        const speed = new SelectInput({
            id: 'speed',
            defaultValue: 30,
            options: [
                { v: 1, t: '1 fps' },
                { v: 6, t: '6 fps' },
                { v: 12, t: '12 fps' },
                { v: 24, t: '24 fps' },
                { v: 30, t: '30 fps' },
                { v: 60, t: '60 fps' }
            ]
        });
        speed.on('change', (value) => {
            events.fire('timeline.setFrameRate', parseInt(value, 10));
        });
        events.on('timeline.frameRate', (frameRate) => {
            speed.value = frameRate.toString();
        });
        const frames = new NumericInput({
            id: 'totalFrames',
            value: 180,
            min: 1,
            max: 10000,
            precision: 0
        });
        frames.on('change', (value) => {
            events.fire('timeline.setFrames', value);
        });
        events.on('timeline.frames', (framesIn) => {
            frames.value = framesIn;
        });
        // smoothness
        const smoothness = new NumericInput({
            id: 'smoothness',
            min: 0,
            max: 1,
            step: 0.05,
            value: 1
        });
        smoothness.on('change', (value) => {
            events.fire('timeline.setSmoothness', value);
        });
        events.on('timeline.smoothness', (smoothnessIn) => {
            smoothness.value = smoothnessIn;
        });
        const settingsControls = new Container({
            id: 'settings-controls'
        });
        settingsControls.append(speed);
        settingsControls.append(frames);
        settingsControls.append(smoothness);
        // append control groups
        const controlsWrap = new Container({
            id: 'controls-wrap'
        });
        const spacerL = new Container({
            class: 'spacer'
        });
        const spacerR = new Container({
            class: 'spacer'
        });
        spacerR.append(settingsControls);
        controlsWrap.append(spacerL);
        controlsWrap.append(buttonControls);
        controlsWrap.append(spacerR);
        const ticks = new Ticks(events, tooltips);
        this.append(controlsWrap);
        this.append(ticks);
        // ui handlers
        const skip = (dir) => {
            const orderedKeys = events.invoke('timeline.keys').map((frame, index) => {
                return { frame, index };
            }).sort((a, b) => a.frame - b.frame);
            if (orderedKeys.length > 0) {
                const frame = events.invoke('timeline.frame');
                const nextKey = orderedKeys.findIndex(k => (dir === 'back' ? k.frame >= frame : k.frame > frame));
                const l = orderedKeys.length;
                if (nextKey === -1) {
                    events.fire('timeline.setFrame', orderedKeys[dir === 'back' ? l - 1 : 0].frame);
                }
                else {
                    events.fire('timeline.setFrame', orderedKeys[dir === 'back' ? (nextKey + l - 1) % l : nextKey].frame);
                }
            }
            else {
                // if there are no keys, just to start of timeline or end
                if (dir === 'back') {
                    events.fire('timeline.setFrame', 0);
                }
                else {
                    events.fire('timeline.setFrame', events.invoke('timeline.frames') - 1);
                }
            }
        };
        prev.on('click', () => {
            skip('back');
        });
        play.on('click', () => {
            if (events.invoke('timeline.playing')) {
                events.fire('timeline.setPlaying', false);
                play.text = '\uE131';
            }
            else {
                events.fire('timeline.setPlaying', true);
                play.text = '\uE135';
            }
        });
        next.on('click', () => {
            skip('forward');
        });
        addKey.on('click', () => {
            events.fire('timeline.add', events.invoke('timeline.frame'));
        });
        removeKey.on('click', () => {
            const index = events.invoke('timeline.keys').indexOf(events.invoke('timeline.frame'));
            if (index !== -1) {
                events.fire('timeline.remove', index);
                events.fire('timeline.frame', events.invoke('timeline.frame'));
            }
        });
        const canDelete = (frame) => events.invoke('timeline.keys').includes(frame);
        events.on('timeline.frame', (frame) => {
            removeKey.enabled = canDelete(frame);
        });
        events.on('timeline.keyRemoved', (index) => {
            removeKey.enabled = canDelete(events.invoke('timeline.frame'));
        });
        events.on('timeline.keyAdded', (frame) => {
            removeKey.enabled = canDelete(frame);
        });
        // cancel animation playback if user interacts with camera
        events.on('camera.controller', (type) => {
            if (events.invoke('timeline.playing')) ;
        });
        // tooltips
        tooltips.register(prev, localize('timeline.prev-key'), 'top');
        tooltips.register(play, localize('timeline.play'), 'top');
        tooltips.register(next, localize('timeline.next-key'), 'top');
        tooltips.register(addKey, localize('timeline.add-key'), 'top');
        tooltips.register(removeKey, localize('timeline.remove-key'), 'top');
        tooltips.register(speed, localize('timeline.frame-rate'), 'top');
        tooltips.register(frames, localize('timeline.total-frames'), 'top');
        tooltips.register(smoothness, localize('timeline.smoothness'), 'top');
    }
}

class Tooltips extends Container {
    register;
    unregister;
    destroy;
    constructor(args = {}) {
        args = {
            ...args,
            class: 'tooltips',
            hidden: true
        };
        super(args);
        const text = new Label({
            class: 'tooltips-content'
        });
        this.append(text);
        const targets = new Map();
        const style = this.dom.style;
        let timer = 0;
        this.register = (target, textString, direction = 'bottom') => {
            const activate = () => {
                const rect = target.dom.getBoundingClientRect();
                const midx = Math.floor((rect.left + rect.right) * 0.5);
                const midy = Math.floor((rect.top + rect.bottom) * 0.5);
                switch (direction) {
                    case 'left':
                        style.left = `${rect.left}px`;
                        style.top = `${midy}px`;
                        style.transform = 'translate(calc(-100% - 10px), -50%)';
                        break;
                    case 'right':
                        style.left = `${rect.right}px`;
                        style.top = `${midy}px`;
                        style.transform = 'translate(10px, -50%)';
                        break;
                    case 'top':
                        style.left = `${midx}px`;
                        style.top = `${rect.top}px`;
                        style.transform = 'translate(-50%, calc(-100% - 10px))';
                        break;
                    case 'bottom':
                        style.left = `${midx}px`;
                        style.top = `${rect.bottom}px`;
                        style.transform = 'translate(-50%, 10px)';
                        break;
                }
                text.text = textString;
                style.display = 'inline';
            };
            const startTimer = (fn) => {
                timer = window.setTimeout(() => {
                    fn();
                    timer = -1;
                }, 250);
            };
            const cancelTimer = () => {
                if (timer >= 0) {
                    clearTimeout(timer);
                    timer = -1;
                }
            };
            const enter = () => {
                cancelTimer();
                if (style.display === 'inline') {
                    activate();
                }
                else {
                    startTimer(() => activate());
                }
            };
            const leave = () => {
                cancelTimer();
                if (style.display === 'inline') {
                    startTimer(() => {
                        style.display = 'none';
                    });
                }
            };
            target.dom.addEventListener('pointerenter', enter);
            target.dom.addEventListener('pointerleave', leave);
            target.on('destroy', () => {
                this.unregister(target);
            });
            targets.set(target, { enter, leave });
        };
        this.unregister = (target) => {
            const value = targets.get(target);
            if (value) {
                target.dom.removeEventListener('pointerenter', value.enter);
                target.dom.removeEventListener('pointerleave', value.leave);
                targets.delete(target);
            }
        };
        this.destroy = () => {
            for (const target of targets.keys()) {
                this.unregister(target);
            }
        };
    }
}

const createSvg = (svgString, args = {}) => {
    const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length));
    return new Element$1({
        dom: new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement,
        ...args
    });
};
class VideoSettingsDialog extends Container {
    show;
    hide;
    destroy;
    constructor(events, args = {}) {
        args = {
            ...args,
            id: 'video-settings-dialog',
            class: 'settings-dialog',
            hidden: true,
            tabIndex: -1
        };
        super(args);
        const dialog = new Container({
            id: 'dialog'
        });
        // header
        const headerIcon = createSvg(img$o, { id: 'icon' });
        const headerText = new Label({ id: 'text', text: localize('video.header') });
        const header = new Container({ id: 'header' });
        header.append(headerIcon);
        header.append(headerText);
        // resolution
        const resolutionLabel = new Label({ class: 'label', text: localize('video.resolution') });
        const resolutionSelect = new SelectInput({
            class: 'select',
            defaultValue: '1080',
            options: [
                { v: '540', t: '960x540' },
                { v: '720', t: '1280x720' },
                { v: '1080', t: '1920x1080' },
                { v: '1440', t: '2560x1440' },
                { v: '4k', t: '3840x2160' }
            ]
        });
        const resolutionRow = new Container({ class: 'row' });
        resolutionRow.append(resolutionLabel);
        resolutionRow.append(resolutionSelect);
        // framerate
        const frameRateLabel = new Label({ class: 'label', text: localize('video.frameRate') });
        const frameRateSelect = new SelectInput({
            class: 'select',
            defaultValue: '30',
            options: [
                { v: '12', t: '12 fps' },
                { v: '24', t: '24 fps' },
                { v: '30', t: '30 fps' },
                { v: '60', t: '60 fps' },
                { v: '120', t: '120 fps' }
            ]
        });
        const frameRateRow = new Container({ class: 'row' });
        frameRateRow.append(frameRateLabel);
        frameRateRow.append(frameRateSelect);
        // bitrate
        const bitrateLabel = new Label({ class: 'label', text: localize('video.bitrate') });
        const bitrateSelect = new SelectInput({
            class: 'select',
            defaultValue: 'high',
            options: [
                { v: 'low', t: 'Low' },
                { v: 'medium', t: 'Medium' },
                { v: 'high', t: 'High' },
                { v: 'ultra', t: 'Ultra' }
            ]
        });
        const bitrateRow = new Container({ class: 'row' });
        bitrateRow.append(bitrateLabel);
        bitrateRow.append(bitrateSelect);
        // portrait mode
        const portraitLabel = new Label({ class: 'label', text: localize('video.portrait') });
        const portraitBoolean = new BooleanInput({ class: 'boolean', value: false });
        const portraitRow = new Container({ class: 'row' });
        portraitRow.append(portraitLabel);
        portraitRow.append(portraitBoolean);
        // transparent background
        const transparentBgLabel = new Label({ class: 'label', text: localize('video.transparentBg') });
        const transparentBgBoolean = new BooleanInput({ class: 'boolean', value: false });
        const transparentBgRow = new Container({ class: 'row' });
        transparentBgRow.append(transparentBgLabel);
        transparentBgRow.append(transparentBgBoolean);
        // hide transparent background till we add support for webm
        // video container
        transparentBgRow.hidden = true;
        // show debug overlays
        const showDebugLabel = new Label({ class: 'label', text: localize('video.showDebug') });
        const showDebugBoolean = new BooleanInput({ class: 'boolean', value: false });
        const showDebugRow = new Container({ class: 'row' });
        showDebugRow.append(showDebugLabel);
        showDebugRow.append(showDebugBoolean);
        // content
        const content = new Container({ id: 'content' });
        content.append(resolutionRow);
        content.append(frameRateRow);
        content.append(bitrateRow);
        content.append(portraitRow);
        content.append(transparentBgRow);
        content.append(showDebugRow);
        // footer
        const footer = new Container({ id: 'footer' });
        const cancelButton = new Button({
            class: 'button',
            text: localize('render.cancel')
        });
        const okButton = new Button({
            class: 'button',
            text: localize('render.ok')
        });
        footer.append(cancelButton);
        footer.append(okButton);
        dialog.append(header);
        dialog.append(content);
        dialog.append(footer);
        this.append(dialog);
        // handle key bindings for enter and escape
        let onCancel;
        let onOK;
        cancelButton.on('click', () => onCancel());
        okButton.on('click', () => onOK());
        const keydown = (e) => {
            switch (e.key) {
                case 'Escape':
                    onCancel();
                    break;
                case 'Enter':
                    if (!e.shiftKey)
                        onOK();
                    break;
                default:
                    e.stopPropagation();
                    break;
            }
        };
        // function implementations
        this.show = () => {
            this.hidden = false;
            this.dom.addEventListener('keydown', keydown);
            this.dom.focus();
            return new Promise((resolve) => {
                onCancel = () => {
                    resolve(null);
                };
                onOK = () => {
                    const widths = {
                        '540': 960,
                        '720': 1280,
                        '1080': 1920,
                        '1440': 2560,
                        '4k': 3840
                    };
                    const heights = {
                        '540': 540,
                        '720': 720,
                        '1080': 1080,
                        '1440': 1440,
                        '4k': 2160
                    };
                    const frameRates = {
                        '12': 12,
                        '24': 24,
                        '30': 30,
                        '60': 60,
                        '120': 120
                    };
                    // bits per pixel per frame for different quality settings
                    const bppfs = {
                        'low': 0.001,
                        'medium': 0.01,
                        'high': 0.1,
                        'ultra': 1
                    };
                    // scale down higher resolutions
                    const bbpfFactors = {
                        '540': 1,
                        '720': 1 / 2,
                        '1080': 1 / 3,
                        '1440': 1 / 4,
                        '4k': 1 / 5
                    };
                    const portrait = portraitBoolean.value;
                    const width = (portrait ? heights : widths)[resolutionSelect.value];
                    const height = (portrait ? widths : heights)[resolutionSelect.value];
                    const frameRate = frameRates[frameRateSelect.value];
                    const bppf = bppfs[bitrateSelect.value] * bbpfFactors[resolutionSelect.value];
                    // bitrate (bps) = 100m * (width × height × frame rate × bppf) / 1m
                    const bitrate = Math.floor(10 * width * height * frameRate * bppf);
                    const videoSettings = {
                        startFrame: 0,
                        endFrame: events.invoke('timeline.frames') - 1,
                        frameRate,
                        width,
                        height,
                        bitrate,
                        transparentBg: transparentBgBoolean.value,
                        showDebug: showDebugBoolean.value
                    };
                    resolve(videoSettings);
                };
            }).finally(() => {
                this.dom.removeEventListener('keydown', keydown);
                this.hide();
            });
        };
        this.hide = () => {
            this.hidden = true;
        };
        this.destroy = () => {
            this.hide();
            super.destroy();
        };
    }
}

const vecx = new Vec3();
const vecy = new Vec3();
const vecz = new Vec3();
const mat4 = new Mat4();
class ViewCube extends Container {
    update;
    constructor(events, args = {}) {
        args = {
            ...args,
            id: 'view-cube-container'
        };
        super(args);
        // construct svg elements
        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.id = 'view-cube-svg';
        const group = document.createElementNS(svg.namespaceURI, 'g');
        svg.appendChild(group);
        const circle = (color, fill, text) => {
            const result = document.createElementNS(svg.namespaceURI, 'g');
            const circle = document.createElementNS(svg.namespaceURI, 'circle');
            circle.setAttribute('fill', fill ? color : '#555');
            circle.setAttribute('stroke', color);
            circle.setAttribute('stroke-width', '2');
            circle.setAttribute('r', '10');
            circle.setAttribute('cx', '0');
            circle.setAttribute('cy', '0');
            circle.setAttribute('pointer-events', 'all');
            result.appendChild(circle);
            if (text) {
                const t = document.createElementNS(svg.namespaceURI, 'text');
                t.setAttribute('font-size', '10');
                t.setAttribute('font-family', 'Arial');
                t.setAttribute('font-weight', 'bold');
                t.setAttribute('text-anchor', 'middle');
                t.setAttribute('alignment-baseline', 'central');
                t.textContent = text;
                result.appendChild(t);
            }
            result.setAttribute('cursor', 'pointer');
            group.appendChild(result);
            return result;
        };
        const line = (color) => {
            const result = document.createElementNS(svg.namespaceURI, 'line');
            result.setAttribute('stroke', color);
            result.setAttribute('stroke-width', '2');
            group.appendChild(result);
            return result;
        };
        const r = '#f44';
        const g = '#4f4';
        const b = '#77f';
        const shapes = {
            nx: circle(r, false),
            ny: circle(g, false),
            nz: circle(b, false),
            xaxis: line(r),
            yaxis: line(g),
            zaxis: line(b),
            px: circle(r, true, 'X'),
            py: circle(g, true, 'Y'),
            pz: circle(b, true, 'Z')
        };
        shapes.px.children[0].addEventListener('pointerdown', (e) => {
            events.fire('camera.align', 'px');
            e.stopPropagation();
        });
        shapes.py.children[0].addEventListener('pointerdown', (e) => {
            events.fire('camera.align', 'py');
            e.stopPropagation();
        });
        shapes.pz.children[0].addEventListener('pointerdown', (e) => {
            events.fire('camera.align', 'pz');
            e.stopPropagation();
        });
        shapes.nx.children[0].addEventListener('pointerdown', (e) => {
            events.fire('camera.align', 'nx');
            e.stopPropagation();
        });
        shapes.ny.children[0].addEventListener('pointerdown', (e) => {
            events.fire('camera.align', 'ny');
            e.stopPropagation();
        });
        shapes.nz.children[0].addEventListener('pointerdown', (e) => {
            events.fire('camera.align', 'nz');
            e.stopPropagation();
        });
        this.dom.appendChild(svg);
        let cw = 0;
        let ch = 0;
        this.update = (cameraMatrix) => {
            const w = this.dom.clientWidth;
            const h = this.dom.clientHeight;
            if (w && h) {
                if (w !== cw || h !== ch) {
                    // resize elements
                    svg.setAttribute('width', w.toString());
                    svg.setAttribute('height', h.toString());
                    group.setAttribute('transform', `translate(${w * 0.5}, ${h * 0.5})`);
                    cw = w;
                    ch = h;
                }
                mat4.invert(cameraMatrix);
                mat4.getX(vecx);
                mat4.getY(vecy);
                mat4.getZ(vecz);
                const transform = (group, x, y) => {
                    group.setAttribute('transform', `translate(${x * 40}, ${y * 40})`);
                };
                const x2y2 = (line, x, y) => {
                    line.setAttribute('x2', (x * 40).toString());
                    line.setAttribute('y2', (y * 40).toString());
                };
                transform(shapes.px, vecx.x, -vecx.y);
                transform(shapes.nx, -vecx.x, vecx.y);
                transform(shapes.py, vecy.x, -vecy.y);
                transform(shapes.ny, -vecy.x, vecy.y);
                transform(shapes.pz, vecz.x, -vecz.y);
                transform(shapes.nz, -vecz.x, vecz.y);
                x2y2(shapes.xaxis, vecx.x, -vecx.y);
                x2y2(shapes.yaxis, vecy.x, -vecy.y);
                x2y2(shapes.zaxis, vecz.x, -vecz.y);
                // reorder dom for the mighty svg painter's algorithm
                const order = [
                    { n: ['xaxis', 'px'], value: vecx.z },
                    { n: ['yaxis', 'py'], value: vecy.z },
                    { n: ['zaxis', 'pz'], value: vecz.z },
                    { n: ['nx'], value: -vecx.z },
                    { n: ['ny'], value: -vecy.z },
                    { n: ['nz'], value: -vecz.z }
                ].sort((a, b) => a.value - b.value);
                const fragment = document.createDocumentFragment();
                order.forEach((o) => {
                    o.n.forEach((n) => {
                        // @ts-ignore
                        fragment.appendChild(shapes[n]);
                    });
                });
                group.appendChild(fragment);
            }
        };
    }
}

class ViewPanel extends Container {
    constructor(events, tooltips, args = {}) {
        args = {
            ...args,
            id: 'view-panel',
            class: 'panel',
            hidden: true
        };
        super(args);
        // stop pointer events bubbling
        ['pointerdown', 'pointerup', 'pointermove', 'wheel', 'dblclick'].forEach((eventName) => {
            this.dom.addEventListener(eventName, (event) => event.stopPropagation());
        });
        // header
        const header = new Container({
            class: 'panel-header'
        });
        const icon = new Label({
            text: '\uE403',
            class: 'panel-header-icon'
        });
        const label = new Label({
            text: localize('options'),
            class: 'panel-header-label'
        });
        header.append(icon);
        header.append(label);
        // colors
        const clrRow = new Container({
            class: 'view-panel-row'
        });
        const clrLabel = new Label({
            text: localize('options.colors'),
            class: 'view-panel-row-label'
        });
        const clrPickers = new Container({
            class: 'view-panel-row-pickers'
        });
        const bgClrPicker = new ColorPicker({
            class: 'view-panel-row-picker',
            channels: 3
        });
        const selectedClrPicker = new ColorPicker({
            class: 'view-panel-row-picker',
            channels: 4
        });
        const unselectedClrPicker = new ColorPicker({
            class: 'view-panel-row-picker',
            channels: 4
        });
        const lockedClrPicker = new ColorPicker({
            class: 'view-panel-row-picker',
            channels: 4
        });
        const toArray = (clr) => {
            return [clr.r, clr.g, clr.b, clr.a];
        };
        events.on('bgClr', (clr) => {
            bgClrPicker.value = toArray(clr);
        });
        events.on('selectedClr', (clr) => {
            selectedClrPicker.value = toArray(clr);
        });
        events.on('unselectedClr', (clr) => {
            unselectedClrPicker.value = toArray(clr);
        });
        events.on('lockedClr', (clr) => {
            lockedClrPicker.value = toArray(clr);
        });
        clrPickers.append(bgClrPicker);
        clrPickers.append(selectedClrPicker);
        clrPickers.append(unselectedClrPicker);
        clrPickers.append(lockedClrPicker);
        clrRow.append(clrLabel);
        clrRow.append(clrPickers);
        // tonemapping
        const tonemappingRow = new Container({
            class: 'view-panel-row'
        });
        const tonemappingLabel = new Label({
            text: localize('options.tonemapping'),
            class: 'view-panel-row-label'
        });
        const tonemappingSelection = new SelectInput({
            class: 'view-panel-row-select',
            defaultValue: 'none',
            options: [
                { v: 'none', t: localize('options.tonemapping-none') },
                { v: 'linear', t: localize('options.tonemapping-linear') },
                { v: 'neutral', t: localize('options.tonemapping-neutral') },
                { v: 'aces', t: localize('options.tonemapping-aces') },
                { v: 'aces2', t: localize('options.tonemapping-aces2') },
                { v: 'filmic', t: localize('options.tonemapping-filmic') },
                { v: 'hejl', t: localize('options.tonemapping-hejl') }
            ]
        });
        tonemappingRow.append(tonemappingLabel);
        tonemappingRow.append(tonemappingSelection);
        // camera fov
        const fovRow = new Container({
            class: 'view-panel-row'
        });
        const fovLabel = new Label({
            text: localize('options.fov'),
            class: 'view-panel-row-label'
        });
        const fovSlider = new SliderInput({
            class: 'view-panel-row-slider',
            min: 10,
            max: 120,
            precision: 1,
            value: 60
        });
        fovRow.append(fovLabel);
        fovRow.append(fovSlider);
        // sh bands
        const shBandsRow = new Container({
            class: 'view-panel-row'
        });
        const shBandsLabel = new Label({
            text: localize('options.sh-bands'),
            class: 'view-panel-row-label'
        });
        const shBandsSlider = new SliderInput({
            class: 'view-panel-row-slider',
            min: 0,
            max: 3,
            precision: 0,
            value: 3
        });
        shBandsRow.append(shBandsLabel);
        shBandsRow.append(shBandsSlider);
        // centers size
        const centersSizeRow = new Container({
            class: 'view-panel-row'
        });
        const centersSizeLabel = new Label({
            text: localize('options.centers-size'),
            class: 'view-panel-row-label'
        });
        const centersSizeSlider = new SliderInput({
            class: 'view-panel-row-slider',
            min: 0,
            max: 10,
            precision: 1,
            value: 2
        });
        centersSizeRow.append(centersSizeLabel);
        centersSizeRow.append(centersSizeSlider);
        // camera fly speed
        const cameraFlySpeedRow = new Container({
            class: 'view-panel-row'
        });
        const cameraFlySpeedLabel = new Label({
            text: localize('options.camera-fly-speed'),
            class: 'view-panel-row-label'
        });
        const cameraFlySpeedSlider = new SliderInput({
            class: 'view-panel-row-slider',
            min: 0.1,
            max: 30,
            precision: 1,
            value: 5
        });
        cameraFlySpeedRow.append(cameraFlySpeedLabel);
        cameraFlySpeedRow.append(cameraFlySpeedSlider);
        // outline selection
        const outlineSelectionRow = new Container({
            class: 'view-panel-row'
        });
        const outlineSelectionLabel = new Label({
            text: localize('options.outline-selection'),
            class: 'view-panel-row-label'
        });
        const outlineSelectionToggle = new BooleanInput({
            type: 'toggle',
            class: 'view-panel-row-toggle',
            value: false
        });
        outlineSelectionRow.append(outlineSelectionLabel);
        outlineSelectionRow.append(outlineSelectionToggle);
        // show grid
        const showGridRow = new Container({
            class: 'view-panel-row'
        });
        const showGridLabel = new Label({
            text: localize('options.show-grid'),
            class: 'view-panel-row-label'
        });
        const showGridToggle = new BooleanInput({
            type: 'toggle',
            class: 'view-panel-row-toggle',
            value: true
        });
        showGridRow.append(showGridLabel);
        showGridRow.append(showGridToggle);
        // show bound
        const showBoundRow = new Container({
            class: 'view-panel-row'
        });
        const showBoundLabel = new Label({
            text: localize('options.show-bound'),
            class: 'view-panel-row-label'
        });
        const showBoundToggle = new BooleanInput({
            type: 'toggle',
            class: 'view-panel-row-toggle',
            value: true
        });
        showBoundRow.append(showBoundLabel);
        showBoundRow.append(showBoundToggle);
        this.append(header);
        this.append(clrRow);
        this.append(tonemappingRow);
        this.append(fovRow);
        this.append(shBandsRow);
        this.append(centersSizeRow);
        this.append(cameraFlySpeedRow);
        this.append(outlineSelectionRow);
        this.append(showGridRow);
        this.append(showBoundRow);
        // handle panel visibility
        const setVisible = (visible) => {
            if (visible === this.hidden) {
                this.hidden = !visible;
                events.fire('viewPanel.visible', visible);
            }
        };
        events.function('viewPanel.visible', () => {
            return !this.hidden;
        });
        events.on('viewPanel.setVisible', (visible) => {
            setVisible(visible);
        });
        events.on('viewPanel.toggleVisible', () => {
            setVisible(this.hidden);
        });
        events.on('colorPanel.visible', (visible) => {
            if (visible) {
                setVisible(false);
            }
        });
        // sh bands
        events.on('view.bands', (bands) => {
            shBandsSlider.value = bands;
        });
        shBandsSlider.on('change', (value) => {
            events.fire('view.setBands', value);
        });
        // splat size
        events.on('camera.splatSize', (value) => {
            centersSizeSlider.value = value;
        });
        centersSizeSlider.on('change', (value) => {
            events.fire('camera.setSplatSize', value);
            events.fire('camera.setOverlay', true);
            events.fire('camera.setMode', 'centers');
        });
        // camera speed
        events.on('camera.flySpeed', (value) => {
            cameraFlySpeedSlider.value = value;
        });
        cameraFlySpeedSlider.on('change', (value) => {
            events.fire('camera.setFlySpeed', value);
        });
        // outline selection
        events.on('view.outlineSelection', (value) => {
            outlineSelectionToggle.value = value;
        });
        outlineSelectionToggle.on('change', (value) => {
            events.fire('view.setOutlineSelection', value);
        });
        // show grid
        events.on('grid.visible', (visible) => {
            showGridToggle.value = visible;
        });
        showGridToggle.on('change', () => {
            events.fire('grid.setVisible', showGridToggle.value);
        });
        // show bound
        events.on('camera.bound', (visible) => {
            showBoundToggle.value = visible;
        });
        showBoundToggle.on('change', () => {
            events.fire('camera.setBound', showBoundToggle.value);
        });
        // background color
        bgClrPicker.on('change', (value) => {
            events.fire('setBgClr', new Color(value[0], value[1], value[2]));
        });
        selectedClrPicker.on('change', (value) => {
            events.fire('setSelectedClr', new Color(value[0], value[1], value[2], value[3]));
        });
        unselectedClrPicker.on('change', (value) => {
            events.fire('setUnselectedClr', new Color(value[0], value[1], value[2], value[3]));
        });
        lockedClrPicker.on('change', (value) => {
            events.fire('setLockedClr', new Color(value[0], value[1], value[2], value[3]));
        });
        // camera fov
        events.on('camera.fov', (fov) => {
            fovSlider.value = fov;
        });
        fovSlider.on('change', (value) => {
            events.fire('camera.setFov', value);
        });
        // tonemapping
        events.on('camera.tonemapping', (tonemapping) => {
            tonemappingSelection.value = tonemapping;
        });
        tonemappingSelection.on('change', (value) => {
            events.fire('camera.setTonemapping', value);
        });
        // tooltips
        tooltips.register(bgClrPicker, localize('options.bg-color'), 'left');
        tooltips.register(selectedClrPicker, localize('options.selected-color'), 'top');
        tooltips.register(unselectedClrPicker, localize('options.unselected-color'), 'top');
        tooltips.register(lockedClrPicker, localize('options.locked-color'), 'top');
    }
}

class EditorUI {
    appContainer;
    topContainer;
    canvasContainer;
    toolsContainer;
    canvas;
    popup;
    constructor(events) {
        localizeInit();
        // favicon
        const link = document.createElement('link');
        link.rel = 'icon';
        link.href = img$5;
        document.head.appendChild(link);
        // app
        const appContainer = new Container({
            id: 'app-container'
        });
        // editor
        const editorContainer = new Container({
            id: 'editor-container'
        });
        // tooltips container
        const tooltipsContainer = new Container({
            id: 'tooltips-container'
        });
        // top container
        const topContainer = new Container({
            id: 'top-container'
        });
        // canvas
        const canvas = document.createElement('canvas');
        canvas.id = 'canvas';
        // app label
        const appLabel = new Label({
            id: 'app-label',
            text: `SUPERSPLAT v${version}`
        });
        // cursor label
        const cursorLabel = new Label({
            id: 'cursor-label'
        });
        let fullprecision = '';
        events.on('camera.focalPointPicked', (details) => {
            cursorLabel.text = `${details.position.x.toFixed(2)}, ${details.position.y.toFixed(2)}, ${details.position.z.toFixed(2)}`;
            fullprecision = `${details.position.x}, ${details.position.y}, ${details.position.z}`;
        });
        ['pointerdown', 'pointerup', 'pointermove', 'wheel', 'dblclick'].forEach((eventName) => {
            cursorLabel.dom.addEventListener(eventName, (event) => event.stopPropagation());
        });
        cursorLabel.dom.addEventListener('pointerdown', () => {
            navigator.clipboard.writeText(fullprecision);
            const orig = cursorLabel.text;
            cursorLabel.text = localize('cursor.copied');
            setTimeout(() => {
                cursorLabel.text = orig;
            }, 1000);
        });
        // canvas container
        const canvasContainer = new Container({
            id: 'canvas-container'
        });
        // tools container
        const toolsContainer = new Container({
            id: 'tools-container'
        });
        // tooltips
        const tooltips = new Tooltips();
        tooltipsContainer.append(tooltips);
        // bottom toolbar
        const scenePanel = new ScenePanel(events, tooltips);
        const viewPanel = new ViewPanel(events, tooltips);
        const colorPanel = new ColorPanel(events, tooltips);
        const bottomToolbar = new BottomToolbar(events, tooltips);
        const rightToolbar = new RightToolbar(events, tooltips);
        const modeToggle = new ModeToggle(events, tooltips);
        const menu = new Menu(events);
        canvasContainer.dom.appendChild(canvas);
        canvasContainer.append(appLabel);
        canvasContainer.append(cursorLabel);
        canvasContainer.append(toolsContainer);
        canvasContainer.append(scenePanel);
        canvasContainer.append(viewPanel);
        canvasContainer.append(colorPanel);
        canvasContainer.append(bottomToolbar);
        canvasContainer.append(rightToolbar);
        canvasContainer.append(modeToggle);
        canvasContainer.append(menu);
        // view axes container
        const viewCube = new ViewCube(events);
        canvasContainer.append(viewCube);
        events.on('prerender', (cameraMatrix) => {
            viewCube.update(cameraMatrix);
        });
        // main container
        const mainContainer = new Container({
            id: 'main-container'
        });
        const timelinePanel = new TimelinePanel(events, tooltips);
        const dataPanel = new DataPanel(events);
        mainContainer.append(canvasContainer);
        mainContainer.append(timelinePanel);
        mainContainer.append(dataPanel);
        editorContainer.append(mainContainer);
        tooltips.register(cursorLabel, localize('cursor.click-to-copy'), 'top');
        // message popup
        const popup = new Popup(tooltips);
        // shortcuts popup
        const shortcutsPopup = new ShortcutsPopup();
        // export popup
        const exportPopup = new ExportPopup(events);
        // publish settings
        const publishSettingsDialog = new PublishSettingsDialog(events);
        // image settings
        const imageSettingsDialog = new ImageSettingsDialog(events);
        // video settings
        const videoSettingsDialog = new VideoSettingsDialog(events);
        topContainer.append(popup);
        topContainer.append(exportPopup);
        topContainer.append(publishSettingsDialog);
        topContainer.append(imageSettingsDialog);
        topContainer.append(videoSettingsDialog);
        appContainer.append(editorContainer);
        appContainer.append(topContainer);
        appContainer.append(tooltipsContainer);
        appContainer.append(shortcutsPopup);
        this.appContainer = appContainer;
        this.topContainer = topContainer;
        this.canvasContainer = canvasContainer;
        this.toolsContainer = toolsContainer;
        this.canvas = canvas;
        this.popup = popup;
        document.body.appendChild(appContainer.dom);
        document.body.setAttribute('tabIndex', '-1');
        events.on('show.shortcuts', () => {
            shortcutsPopup.hidden = false;
        });
        events.function('show.exportPopup', (exportType, splatNames, showFilenameEdit) => {
            return exportPopup.show(exportType, splatNames, showFilenameEdit);
        });
        events.function('show.publishSettingsDialog', async () => {
            // show popup if user isn't logged in
            const userStatus = await events.invoke('publish.userStatus');
            if (!userStatus) {
                await events.invoke('showPopup', {
                    type: 'error',
                    header: localize('popup.error'),
                    message: localize('publish.please-log-in')
                });
                return false;
            }
            // get user publish settings
            const publishSettings = await publishSettingsDialog.show(userStatus);
            // do publish
            if (publishSettings) {
                await events.invoke('scene.publish', publishSettings);
            }
        });
        events.function('show.imageSettingsDialog', async () => {
            const imageSettings = await imageSettingsDialog.show();
            if (imageSettings) {
                await events.invoke('render.image', imageSettings);
            }
        });
        events.function('show.videoSettingsDialog', async () => {
            const videoSettings = await videoSettingsDialog.show();
            if (videoSettings) {
                await events.invoke('render.video', videoSettings);
            }
        });
        events.function('show.about', () => {
            return this.popup.show({
                type: 'info',
                header: 'About',
                message: `SUPERSPLAT v${version}`
            });
        });
        events.function('showPopup', (options) => {
            return this.popup.show(options);
        });
        // spinner
        const spinner = new Spinner();
        topContainer.append(spinner);
        events.on('startSpinner', () => {
            spinner.hidden = false;
        });
        events.on('stopSpinner', () => {
            spinner.hidden = true;
        });
        // progress
        const progress = new Progress();
        topContainer.append(progress);
        events.on('progressStart', (header) => {
            progress.hidden = false;
            progress.setHeader(header);
        });
        events.on('progressUpdate', (options) => {
            progress.setText(options.text);
            progress.setProgress(options.progress);
        });
        events.on('progressEnd', () => {
            progress.hidden = true;
        });
        // initialize canvas to correct size before creating graphics device etc
        const pixelRatio = window.devicePixelRatio;
        canvas.width = Math.ceil(canvasContainer.dom.offsetWidth * pixelRatio);
        canvas.height = Math.ceil(canvasContainer.dom.offsetHeight * pixelRatio);
        ['contextmenu', 'gesturestart', 'gesturechange', 'gestureend'].forEach((event) => {
            document.addEventListener(event, (e) => {
                e.preventDefault();
            }, true);
        });
        // whenever the canvas container is clicked, set keyboard focus on the body
        canvasContainer.dom.addEventListener('pointerdown', (event) => {
            // set focus on the body if user is busy pressing on the canvas or a child of the tools
            // element
            if (event.target === canvas || toolsContainer.dom.contains(event.target)) {
                document.body.focus();
            }
        }, true);
    }
}

const getURLArgs = () => {
    // extract settings from command line in non-prod builds only
    const config = {};
    const apply = (key, value) => {
        let obj = config;
        key.split('.').forEach((k, i, a) => {
            if (i === a.length - 1) {
                obj[k] = value;
            }
            else {
                if (!obj.hasOwnProperty(k)) {
                    obj[k] = {};
                }
                obj = obj[k];
            }
        });
    };
    const params = new URLSearchParams(window.location.search.slice(1));
    params.forEach((value, key) => {
        apply(key, value);
    });
    return config;
};
const initShortcuts = (events) => {
    const shortcuts = new Shortcuts(events);
    shortcuts.register(['Delete', 'Backspace'], { event: 'select.delete' });
    shortcuts.register(['Escape'], { event: 'tool.deactivate' });
    shortcuts.register(['Tab'], { event: 'selection.next' });
    shortcuts.register(['1'], { event: 'tool.move', sticky: true });
    shortcuts.register(['2'], { event: 'tool.rotate', sticky: true });
    shortcuts.register(['3'], { event: 'tool.scale', sticky: true });
    shortcuts.register(['G', 'g'], { event: 'grid.toggleVisible' });
    shortcuts.register(['C', 'c'], { event: 'tool.toggleCoordSpace' });
    shortcuts.register(['F', 'f'], { event: 'camera.focus' });
    shortcuts.register(['R', 'r'], { event: 'tool.rectSelection', sticky: true });
    shortcuts.register(['P', 'p'], { event: 'tool.polygonSelection', sticky: true });
    shortcuts.register(['L', 'l'], { event: 'tool.lassoSelection', sticky: true });
    shortcuts.register(['B', 'b'], { event: 'tool.brushSelection', sticky: true });
    shortcuts.register(['A', 'a'], { event: 'select.all', ctrl: true });
    shortcuts.register(['A', 'a'], { event: 'select.none', shift: true });
    shortcuts.register(['I', 'i'], { event: 'select.invert', ctrl: true });
    shortcuts.register(['H', 'h'], { event: 'select.hide' });
    shortcuts.register(['U', 'u'], { event: 'select.unhide' });
    shortcuts.register(['['], { event: 'tool.brushSelection.smaller' });
    shortcuts.register([']'], { event: 'tool.brushSelection.bigger' });
    shortcuts.register(['Z', 'z'], { event: 'edit.undo', ctrl: true, capture: true });
    shortcuts.register(['Z', 'z'], { event: 'edit.redo', ctrl: true, shift: true, capture: true });
    shortcuts.register(['M', 'm'], { event: 'camera.toggleMode' });
    shortcuts.register(['D', 'd'], { event: 'dataPanel.toggle' });
    shortcuts.register([' '], { event: 'camera.toggleOverlay' });
    return shortcuts;
};
const main = async () => {
    // root events object
    const events = new Events();
    // url
    const url = new URL(window.location.href);
    // edit history
    const editHistory = new EditHistory(events);
    // editor ui
    const editorUI = new EditorUI(events);
    // create the graphics device
    const graphicsDevice = await createGraphicsDevice(editorUI.canvas, {
        deviceTypes: ['webgl2'],
        antialias: false,
        depth: false,
        stencil: false,
        xrCompatible: false,
        powerPreference: 'high-performance'
    });
    const overrides = [
        getURLArgs()
    ];
    // resolve scene config
    const sceneConfig = getSceneConfig(overrides);
    // construct the manager
    const scene = new Scene(events, sceneConfig, editorUI.canvas, graphicsDevice);
    // colors
    const bgClr = new Color();
    const selectedClr = new Color();
    const unselectedClr = new Color();
    const lockedClr = new Color();
    const setClr = (target, value, event) => {
        if (!target.equals(value)) {
            target.copy(value);
            events.fire(event, target);
        }
    };
    const setBgClr = (clr) => {
        setClr(bgClr, clr, 'bgClr');
    };
    const setSelectedClr = (clr) => {
        setClr(selectedClr, clr, 'selectedClr');
    };
    const setUnselectedClr = (clr) => {
        setClr(unselectedClr, clr, 'unselectedClr');
    };
    const setLockedClr = (clr) => {
        setClr(lockedClr, clr, 'lockedClr');
    };
    events.on('setBgClr', (clr) => {
        setBgClr(clr);
    });
    events.on('setSelectedClr', (clr) => {
        setSelectedClr(clr);
    });
    events.on('setUnselectedClr', (clr) => {
        setUnselectedClr(clr);
    });
    events.on('setLockedClr', (clr) => {
        setLockedClr(clr);
    });
    events.function('bgClr', () => {
        return bgClr;
    });
    events.function('selectedClr', () => {
        return selectedClr;
    });
    events.function('unselectedClr', () => {
        return unselectedClr;
    });
    events.function('lockedClr', () => {
        return lockedClr;
    });
    events.on('bgClr', (clr) => {
        const cnv = (v) => `${Math.max(0, Math.min(255, (v * 255))).toFixed(0)}`;
        document.body.style.backgroundColor = `rgba(${cnv(clr.r)},${cnv(clr.g)},${cnv(clr.b)},1)`;
    });
    events.on('selectedClr', (clr) => {
        scene.forceRender = true;
    });
    events.on('unselectedClr', (clr) => {
        scene.forceRender = true;
    });
    events.on('lockedClr', (clr) => {
        scene.forceRender = true;
    });
    // initialize colors from application config
    const toColor = (value) => {
        return new Color(value.r, value.g, value.b, value.a);
    };
    setBgClr(toColor(sceneConfig.bgClr));
    setSelectedClr(toColor(sceneConfig.selectedClr));
    setUnselectedClr(toColor(sceneConfig.unselectedClr));
    setLockedClr(toColor(sceneConfig.lockedClr));
    // create the mask selection canvas
    const maskCanvas = document.createElement('canvas');
    const maskContext = maskCanvas.getContext('2d');
    maskCanvas.setAttribute('id', 'mask-canvas');
    maskContext.globalCompositeOperation = 'copy';
    const mask = {
        canvas: maskCanvas,
        context: maskContext
    };
    // tool manager
    const toolManager = new ToolManager(events);
    toolManager.register('rectSelection', new RectSelection(events, editorUI.toolsContainer.dom));
    toolManager.register('brushSelection', new BrushSelection(events, editorUI.toolsContainer.dom, mask));
    toolManager.register('polygonSelection', new PolygonSelection(events, editorUI.toolsContainer.dom, mask));
    toolManager.register('lassoSelection', new LassoSelection(events, editorUI.toolsContainer.dom, mask));
    toolManager.register('sphereSelection', new SphereSelection(events, scene, editorUI.canvasContainer));
    toolManager.register('boxSelection', new BoxSelection(events, scene, editorUI.canvasContainer));
    toolManager.register('move', new MoveTool(events, scene));
    toolManager.register('rotate', new RotateTool(events, scene));
    toolManager.register('scale', new ScaleTool(events, scene));
    editorUI.toolsContainer.dom.appendChild(maskCanvas);
    window.scene = scene;
    registerEditorEvents(events, editHistory, scene);
    registerSelectionEvents(events, scene);
    registerTimelineEvents(events);
    registerCameraPosesEvents(events);
    registerTransformHandlerEvents(events);
    registerPlySequenceEvents(events);
    registerPublishEvents(events);
    registerDocEvents(scene, events);
    registerRenderEvents(scene, events);
    initShortcuts(events);
    initFileHandler(scene, events, editorUI.appContainer.dom);
    // load async models
    scene.start();
    // handle load params
    const loadList = url.searchParams.getAll('load');
    for (const value of loadList) {
        const decoded = decodeURIComponent(value);
        await events.invoke('import', [{
                filename: decoded.split('/').pop(),
                url: decoded
            }]);
    }
    // handle OS-based file association in PWA mode
    if ('launchQueue' in window) {
        window.launchQueue.setConsumer(async (launchParams) => {
            for (const file of launchParams.files) {
                await events.invoke('import', [{
                        filename: file.name,
                        contents: await file.getFile()
                    }]);
            }
        });
    }
};

// print out versions of dependent packages
// NOTE: add dummy style reference to prevent tree shaking
console.log(`SuperSplat v${version} | PCUI v${version$2} (${revision$1}) | Engine v${version$1} (${revision})`);
main();
//# sourceMappingURL=index.js.map
