UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

12,571 lines 544 kB
/* eslint-disable @typescript-eslint/naming-convention */
/**
 * Base error. Due to limitations of typedoc-check and missing documentation
 * in lib.es5.d.ts, cannot extend Error directly for RuntimeError.
 * @ignore
 */
class BaseError extends Error {
}
// See https://stackoverflow.com/questions/12915412/how-do-i-extend-a-host-object-e-g-error-in-typescript
// and https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
// Polyfill for Object.setPrototypeOf if necessary.
BaseError._setPrototypeOf = Object.setPrototypeOf ||
    ((o, proto) => {
        o.__proto__ = proto;
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return o;
    });
/**
 * Used for flow control when an operation is aborted, such as with AbortController.
 */
class AbortError extends BaseError {
    constructor(message = "Operation aborted") {
        super(message);
        this.name = "AbortError";
        BaseError._setPrototypeOf(this, AbortError.prototype);
    }
}

/**
 * Wrapper class for promise with external resolve and reject.
 */
class Deferred {
    /**
     * The resolve method of the promise associated with this deferred object.
     */
    get resolve() {
        return this._resolve;
    }
    /**
     * The reject method of the promise associated with this deferred object.
     */
    get reject() {
        return this._reject;
    }
    /**
     * Constructor for this deferred object.
     */
    constructor() {
        this.promise = new Promise((resolve, reject) => {
            this._resolve = resolve;
            this._reject = reject;
        });
    }
}

/**
 * Provides a simple way of creating the rough equivalent of an async critical section.
 *
 * @example
 * ```typescript
 * const myLock = new AsyncLock();
 *
 * private async MyFuncAsync(): Promise<void> {
 *   await myLock.lockAsync(async () => {
 *     await operation1Async();
 *     await operation2Async();
 *   });
 * }
 * ```
 */
class AsyncLock {
    constructor() {
        this._currentOperation = Promise.resolve();
    }
    /**
     * Executes the provided function when the lock is acquired (e.g. when the previous operation finishes).
     * @param func The function to execute.
     * @param signal An optional signal that can be used to abort the operation.
     * @returns A promise that resolves when the func finishes executing.
     */
    // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
    lockAsync(func, signal) {
        signal?.throwIfAborted();
        const wrappedFunc = signal
            ? // eslint-disable-next-line @typescript-eslint/promise-function-async
                () => {
                    signal.throwIfAborted();
                    return func();
                }
            : func;
        // eslint-disable-next-line github/no-then
        const newOperation = this._currentOperation.then(wrappedFunc);
        // NOTE: It would be simpler to just hold a Promise<unknown>, but this class should not prevent an object held by the returned promise from being garbage collected.
        this._currentOperation = new Promise((resolve) => {
            // eslint-disable-next-line github/no-then
            newOperation.then(() => resolve(), resolve);
        });
        return newOperation;
    }
    /**
     * Executes the provided function when all the specified locks are acquired.
     * @param func The function to execute.
     * @param locks The locks to acquire.
     * @param signal An optional signal that can be used to abort the operation.
     * @returns A promise that resolves when the func finishes executing.
     */
    static async LockAsync(func, locks, signal) {
        signal?.throwIfAborted();
        if (locks.length === 0) {
            return await func();
        }
        const deferred = new Deferred();
        let acquiredLocks = 0;
        for (const lock of locks) {
            lock.lockAsync(async () => {
                acquiredLocks++;
                if (acquiredLocks === locks.length) {
                    deferred.resolve(await func());
                }
                return await deferred.promise;
                // eslint-disable-next-line github/no-then
            }, signal).catch((e) => deferred.reject(e));
        }
        return await deferred.promise;
    }
}

/* eslint-disable no-console */
/**
 * Logger used throughout the application to allow configuration of
 * the log level required for the messages.
 */
class Logger {
    static _CheckLimit(message, limit) {
        let entry = Logger._LogLimitOutputs[message];
        if (!entry) {
            entry = { limit, current: 1 };
            Logger._LogLimitOutputs[message] = entry;
        }
        else {
            entry.current++;
        }
        return entry.current <= entry.limit;
    }
    static _GenerateLimitMessage(message, level = 1) {
        const entry = Logger._LogLimitOutputs[message];
        if (!entry || !Logger.MessageLimitReached) {
            return;
        }
        const type = this._Levels[level];
        if (entry.current === entry.limit) {
            Logger[type.name](Logger.MessageLimitReached.replace(/%LIMIT%/g, "" + entry.limit).replace(/%TYPE%/g, type.name ?? ""));
        }
    }
    static _AddLogEntry(entry) {
        Logger._LogCache = entry + Logger._LogCache;
        if (Logger.OnNewCacheEntry) {
            Logger.OnNewCacheEntry(entry);
        }
    }
    static _FormatMessage(message) {
        const padStr = (i) => (i < 10 ? "0" + i : "" + i);
        const date = new Date();
        return "[" + padStr(date.getHours()) + ":" + padStr(date.getMinutes()) + ":" + padStr(date.getSeconds()) + "]: " + message;
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    static _LogDisabled(message, limit) {
        // nothing to do
    }
    static _LogEnabled(level = 1, message, limit) {
        // take first message if array
        const msg = Array.isArray(message) ? message[0] : message;
        if (limit !== undefined && !Logger._CheckLimit(msg, limit)) {
            return;
        }
        const formattedMessage = Logger._FormatMessage(msg);
        const type = this._Levels[level];
        const optionals = Array.isArray(message) ? message.slice(1) : [];
        type.logFunc && type.logFunc("BJS - " + formattedMessage, ...optionals);
        const entry = `<div style='color:${type.color}'>${formattedMessage}</div><br>`;
        Logger._AddLogEntry(entry);
        Logger._GenerateLimitMessage(msg, level);
    }
    /**
     * Gets current log cache (list of logs)
     */
    static get LogCache() {
        return Logger._LogCache;
    }
    /**
     * Clears the log cache
     */
    static ClearLogCache() {
        Logger._LogCache = "";
        Logger._LogLimitOutputs = {};
        Logger.errorsCount = 0;
    }
    /**
     * Sets the current log level. This property is a bit field, allowing you to combine different levels (MessageLogLevel / WarningLogLevel / ErrorLogLevel).
     * Use NoneLogLevel to disable logging and AllLogLevel for a quick way to enable all levels.
     */
    static set LogLevels(level) {
        Logger.Log = Logger._LogDisabled;
        Logger.Warn = Logger._LogDisabled;
        Logger.Error = Logger._LogDisabled;
        const levels = [Logger.MessageLogLevel, Logger.WarningLogLevel, Logger.ErrorLogLevel];
        for (const l of levels) {
            if ((level & l) === l) {
                const type = this._Levels[l];
                Logger[type.name] = Logger._LogEnabled.bind(Logger, l);
            }
        }
    }
}
/**
 * No log
 */
Logger.NoneLogLevel = 0;
/**
 * Only message logs
 */
Logger.MessageLogLevel = 1;
/**
 * Only warning logs
 */
Logger.WarningLogLevel = 2;
/**
 * Only error logs
 */
Logger.ErrorLogLevel = 4;
/**
 * All logs
 */
Logger.AllLogLevel = 7;
/**
 * Message to display when a message has been logged too many times
 */
Logger.MessageLimitReached = "Too many %TYPE%s (%LIMIT%), no more %TYPE%s will be reported for this message.";
Logger._LogCache = "";
Logger._LogLimitOutputs = {};
// levels according to the (binary) numbering.
Logger._Levels = [
    {},
    { color: "white", logFunc: console.log, name: "Log" },
    { color: "orange", logFunc: console.warn, name: "Warn" },
    {},
    { color: "red", logFunc: console.error, name: "Error" },
];
/**
 * Gets a value indicating the number of loading errors
 * @ignorenaming
 */
// eslint-disable-next-line @typescript-eslint/naming-convention
Logger.errorsCount = 0;
/**
 * Log a message to the console
 */
Logger.Log = Logger._LogEnabled.bind(Logger, Logger.MessageLogLevel);
/**
 * Write a warning message to the console
 */
Logger.Warn = Logger._LogEnabled.bind(Logger, Logger.WarningLogLevel);
/**
 * Write an error message to the console
 */
Logger.Error = Logger._LogEnabled.bind(Logger, Logger.ErrorLogLevel);

/** This file must only contain pure code and pure imports */
const IsWeakRefSupported = typeof WeakRef !== "undefined";
/**
 * A class serves as a medium between the observable and its observers
 */
class EventState {
    /**
     * Create a new EventState
     * @param mask defines the mask associated with this state
     * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
     * @param target defines the original target of the state
     * @param currentTarget defines the current target of the state
     */
    constructor(mask, skipNextObservers = false, target, currentTarget) {
        this.initialize(mask, skipNextObservers, target, currentTarget);
    }
    /**
     * Initialize the current event state
     * @param mask defines the mask associated with this state
     * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
     * @param target defines the original target of the state
     * @param currentTarget defines the current target of the state
     * @returns the current event state
     */
    initialize(mask, skipNextObservers = false, target, currentTarget) {
        this.mask = mask;
        this.skipNextObservers = skipNextObservers;
        this.target = target;
        this.currentTarget = currentTarget;
        return this;
    }
}
/**
 * Represent an observer registered to a given Observable object.
 */
class Observer {
    /**
     * Creates a new observer
     * @param callback defines the callback to call when the observer is notified
     * @param mask defines the mask of the observer (used to filter notifications)
     * @param scope defines the current scope used to restore the JS context
     */
    constructor(
    /**
     * Defines the callback to call when the observer is notified
     */
    callback, 
    /**
     * Defines the mask of the observer (used to filter notifications)
     */
    mask, 
    /**
     * [null] Defines the current scope used to restore the JS context
     */
    scope = null) {
        this.callback = callback;
        this.mask = mask;
        this.scope = scope;
        /** @internal */
        this._willBeUnregistered = false;
        /**
         * Gets or sets a property defining that the observer as to be unregistered after the next notification
         */
        this.unregisterOnNextCall = false;
        /**
         * this function can be used to remove the observer from the observable.
         * It will be set by the observable that the observer belongs to.
         * @internal
         */
        this._remove = null;
    }
    /**
     * Remove the observer from its observable
     * This can be used instead of using the observable's remove function.
     * @param defer if true, the removal will be deferred to avoid callback skipping (default: false)
     */
    remove(defer = false) {
        if (this._remove) {
            this._remove(defer);
        }
    }
}
/**
 * The Observable class is a simple implementation of the Observable pattern.
 *
 * There's one slight particularity though: a given Observable can notify its observer using a particular mask value, only the Observers registered with this mask value will be notified.
 * This enable a more fine grained execution without having to rely on multiple different Observable objects.
 * For instance you may have a given Observable that have four different types of notifications: Move (mask = 0x01), Stop (mask = 0x02), Turn Right (mask = 0X04), Turn Left (mask = 0X08).
 * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
 */
class Observable {
    /**
     * Create an observable from a Promise.
     * @param promise a promise to observe for fulfillment.
     * @param onErrorObservable an observable to notify if a promise was rejected.
     * @returns the new Observable
     */
    static FromPromise(promise, onErrorObservable) {
        const observable = new Observable();
        promise
            // eslint-disable-next-line github/no-then
            .then((ret) => {
            observable.notifyObservers(ret);
        })
            // eslint-disable-next-line github/no-then
            .catch((err) => {
            if (onErrorObservable) {
                onErrorObservable.notifyObservers(err);
            }
            else {
                throw err;
            }
        });
        return observable;
    }
    /**
     * Gets the list of observers
     * Note that observers that were recently deleted may still be present in the list because they are only really deleted on the next javascript tick!
     */
    get observers() {
        return this._observers;
    }
    /**
     * Creates a new observable
     * @param onObserverAdded defines a callback to call when a new observer is added
     * @param notifyIfTriggered If set to true the observable will notify when an observer was added if the observable was already triggered.
     */
    constructor(onObserverAdded, 
    /**
     * [false] If set to true the observable will notify when an observer was added if the observable was already triggered.
     * This is helpful to single-state observables like the scene onReady or the dispose observable.
     */
    notifyIfTriggered = false) {
        this.notifyIfTriggered = notifyIfTriggered;
        this._observers = new Array();
        this._numObserversMarkedAsDeleted = 0;
        this._hasNotified = false;
        this._eventState = new EventState(0);
        if (onObserverAdded) {
            this._onObserverAdded = onObserverAdded;
        }
    }
    add(callback, mask = -1, insertFirst = false, scope = null, unregisterOnFirstCall = false) {
        if (!callback) {
            return null;
        }
        const observer = new Observer(callback, mask, scope);
        observer.unregisterOnNextCall = unregisterOnFirstCall;
        if (insertFirst) {
            this._observers.unshift(observer);
        }
        else {
            this._observers.push(observer);
        }
        if (this._onObserverAdded) {
            this._onObserverAdded(observer);
        }
        // If the observable was already triggered and the observable is set to notify if triggered, notify the new observer
        if (this._hasNotified && this.notifyIfTriggered) {
            if (this._lastNotifiedValue !== undefined) {
                this.notifyObserver(observer, this._lastNotifiedValue);
            }
        }
        // attach the remove function to the observer
        const observableWeakRef = IsWeakRefSupported ? new WeakRef(this) : { deref: () => this };
        observer._remove = (defer = false) => {
            const observable = observableWeakRef.deref();
            if (observable) {
                defer ? observable.remove(observer) : observable._remove(observer);
            }
        };
        return observer;
    }
    addOnce(callback) {
        return this.add(callback, undefined, undefined, undefined, true);
    }
    /**
     * Remove an Observer from the Observable object
     * @param observer the instance of the Observer to remove
     * @returns false if it doesn't belong to this Observable
     */
    remove(observer) {
        if (!observer) {
            return false;
        }
        observer._remove = null;
        const index = this._observers.indexOf(observer);
        if (index !== -1) {
            this._deferUnregister(observer);
            return true;
        }
        return false;
    }
    /**
     * Remove a callback from the Observable object
     * @param callback the callback to remove
     * @param scope optional scope. If used only the callbacks with this scope will be removed
     * @returns false if it doesn't belong to this Observable
     */
    removeCallback(callback, scope) {
        for (let index = 0; index < this._observers.length; index++) {
            const observer = this._observers[index];
            if (observer._willBeUnregistered) {
                continue;
            }
            if (observer.callback === callback && (!scope || scope === observer.scope)) {
                this._deferUnregister(observer);
                return true;
            }
        }
        return false;
    }
    /**
     * @internal
     */
    _deferUnregister(observer) {
        if (observer._willBeUnregistered) {
            return;
        }
        this._numObserversMarkedAsDeleted++;
        observer.unregisterOnNextCall = false;
        observer._willBeUnregistered = true;
        setTimeout(() => {
            this._remove(observer);
        }, 0);
    }
    // This should only be called when not iterating over _observers to avoid callback skipping.
    // Removes an observer from the _observer Array.
    _remove(observer, updateCounter = true) {
        if (!observer) {
            return false;
        }
        const index = this._observers.indexOf(observer);
        if (index !== -1) {
            if (updateCounter) {
                this._numObserversMarkedAsDeleted--;
            }
            this._observers.splice(index, 1);
            return true;
        }
        return false;
    }
    /**
     * Moves the observable to the top of the observer list making it get called first when notified
     * @param observer the observer to move
     */
    makeObserverTopPriority(observer) {
        this._remove(observer, false);
        this._observers.unshift(observer);
    }
    /**
     * Moves the observable to the bottom of the observer list making it get called last when notified
     * @param observer the observer to move
     */
    makeObserverBottomPriority(observer) {
        this._remove(observer, false);
        this._observers.push(observer);
    }
    /**
     * Notify all Observers by calling their respective callback with the given data
     * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
     * @param eventData defines the data to send to all observers
     * @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
     * @param target defines the original target of the state
     * @param currentTarget defines the current target of the state
     * @param userInfo defines any user info to send to observers
     * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
     */
    notifyObservers(eventData, mask = -1, target, currentTarget, userInfo) {
        // this prevents potential memory leaks - if an object is disposed but the observable doesn't get cleared.
        if (this.notifyIfTriggered) {
            this._hasNotified = true;
            this._lastNotifiedValue = eventData;
        }
        if (!this._observers.length) {
            return true;
        }
        const state = this._eventState;
        state.mask = mask;
        state.target = target;
        state.currentTarget = currentTarget;
        state.skipNextObservers = false;
        state.lastReturnValue = eventData;
        state.userInfo = userInfo;
        for (const obs of this._observers) {
            if (obs._willBeUnregistered) {
                continue;
            }
            if (obs.mask & mask) {
                if (obs.unregisterOnNextCall) {
                    this._deferUnregister(obs);
                }
                if (obs.scope) {
                    state.lastReturnValue = obs.callback.apply(obs.scope, [eventData, state]);
                }
                else {
                    state.lastReturnValue = obs.callback(eventData, state);
                }
            }
            if (state.skipNextObservers) {
                return false;
            }
        }
        return true;
    }
    /**
     * Notify a specific observer
     * @param observer defines the observer to notify
     * @param eventData defines the data to be sent to each callback
     * @param mask is used to filter observers defaults to -1
     */
    notifyObserver(observer, eventData, mask = -1) {
        // this prevents potential memory leaks - if an object is disposed but the observable doesn't get cleared.
        if (this.notifyIfTriggered) {
            this._hasNotified = true;
            this._lastNotifiedValue = eventData;
        }
        if (observer._willBeUnregistered) {
            return;
        }
        const state = this._eventState;
        state.mask = mask;
        state.skipNextObservers = false;
        if (observer.unregisterOnNextCall) {
            this._deferUnregister(observer);
        }
        observer.callback(eventData, state);
    }
    /**
     * Gets a boolean indicating if the observable has at least one observer
     * @returns true is the Observable has at least one Observer registered
     */
    hasObservers() {
        return this._observers.length - this._numObserversMarkedAsDeleted > 0;
    }
    /**
     * Clear the list of observers
     */
    clear() {
        while (this._observers.length) {
            const o = this._observers.pop();
            if (o) {
                o._remove = null;
            }
        }
        this._onObserverAdded = null;
        this._numObserversMarkedAsDeleted = 0;
        this.cleanLastNotifiedState();
    }
    /**
     * Clean the last notified state - both the internal last value and the has-notified flag
     */
    cleanLastNotifiedState() {
        this._hasNotified = false;
        this._lastNotifiedValue = undefined;
    }
    /**
     * Clone the current observable
     * @returns a new observable
     */
    clone() {
        const result = new Observable();
        result._observers = this._observers.slice(0);
        return result;
    }
    /**
     * Does this observable handles observer registered with a given mask
     * @param mask defines the mask to be tested
     * @returns whether or not one observer registered with the given mask is handled
     **/
    hasSpecificMask(mask = -1) {
        for (const obs of this._observers) {
            if (obs.mask & mask || obs.mask === mask) {
                return true;
            }
        }
        return false;
    }
}

/**
 * Throws if any of the supplied abort signals is aborted.
 * @param abortSignals The signals to check.
 */
function throwIfAborted(...abortSignals) {
    for (const signal of abortSignals) {
        signal?.throwIfAborted();
    }
}
/**
 * Fire-and-forget wrapper for a promise that logs non-abort errors.
 * @param promise The promise to observe.
 */
function observePromise(promise) {
    // eslint-disable-next-line @typescript-eslint/no-floating-promises
    (async () => {
        try {
            await promise;
        }
        catch (error) {
            if (!(error instanceof AbortError)) {
                Logger.Error(error);
            }
        }
    })();
}
const shadowQualityOptions = ["none", "normal", "high"];
const toneMappingOptions = ["none", "standard", "aces", "neutral"];
/**
 * Checks if the given value is a valid tone mapping option.
 * @param value The value to check.
 * @returns True if the value is a valid tone mapping option, otherwise false.
 */
function IsToneMapping(value) {
    return toneMappingOptions.includes(value);
}
/**
 * Checks if the given value is a valid shadow quality option.
 * @param value The value to check.
 * @returns True if the value is a valid shadow quality option, otherwise false.
 */
function IsShadowQuality(value) {
    return shadowQualityOptions.includes(value);
}
/**
 * Provides the result of a hot spot query.
 */
class ViewerHotSpotResult {
    constructor() {
        /**
         * 2D canvas position in pixels.
         */
        this.screenPosition = [NaN, NaN];
        /**
         * 3D world coordinates.
         */
        this.worldPosition = [NaN, NaN, NaN];
        /**
         * Visibility range is [-1..1]. A value of 0 means camera eye is on the plane.
         */
        this.visibility = NaN;
    }
}
/**
 * The default values for {@link ViewerBaseOptions}. Used as fallbacks by both the full Viewer
 * and the Lite Viewer; each re-exports this as its own `DefaultViewerOptions`.
 */
const DefaultViewerBaseOptions = {
    clearColor: [0, 0, 0, 0],
    autoSuspendRendering: true,
    environmentConfig: {
        intensity: 1,
        blur: 0.3,
        rotation: 0,
    },
    environmentLighting: "auto",
    environmentSkybox: "none",
    cameraAutoOrbit: {
        enabled: false,
        delay: 2000,
        speed: 0.05,
    },
    animationAutoPlay: false,
    animationSpeed: 1,
    shadowConfig: {
        quality: "none",
    },
    postProcessing: {
        toneMapping: "neutral",
        contrast: 1,
        exposure: 1,
        ssao: "auto",
    },
    useRightHandedSystem: false,
    useOpenPBR: false,
};
/**
 * @internal
 * Orbit angle (radians) applied to the arc-rotate camera on every automatic reframe (model load
 * and animation switch). Shared by the full Viewer and ViewerLite so both frame to the same default
 * viewpoint. Exported from `viewerBase` for internal sharing only — intentionally not re-exported
 * from the package index, so it is not part of the public API.
 */
const FramingCameraAlpha = Math.PI / 2;
/**
 * @internal
 * Elevation angle (radians) applied to the arc-rotate camera on every automatic reframe. Shared by
 * the full Viewer and ViewerLite. Exported from `viewerBase` for internal sharing only —
 * intentionally not re-exported from the package index, so it is not part of the public API.
 */
const FramingCameraBeta = Math.PI / 2.4;
/**
 * Common base for the full Babylon.js {@link Viewer} and the lite Viewer.
 *
 * Encapsulates the pieces that are identical between both engine backends:
 * - The 18 public observables exposed by the viewer surface area
 * - In-flight load operation tracking (used to compute aggregate `loadingProgress`)
 * - The `_throwIfDisposedOrAborted` helper used at the start of every async operation
 * - The disposed flag and observable teardown in `dispose()`
 *
 * Subclasses are responsible for everything engine-specific (scene/engine creation,
 * model + environment loading orchestration, camera, post-processing, shadows, etc.)
 * and for declaring `implements IViewer` themselves so the public API contract is
 * verified at the leaf class level.
 */
class ViewerBase {
    constructor() {
        // ── Observables ──
        // Concrete `Observable<T>` is exposed publicly (it satisfies `IReadonlyObservable<T>`
        // for the `IViewer` interface contract). Subclasses notify directly via `this.onXxx.notifyObservers()`.
        /**
         * Fired when the environment has changed.
         */
        this.onEnvironmentChanged = new Observable();
        /**
         * Fired when the environment configuration has changed.
         */
        this.onEnvironmentConfigurationChanged = new Observable();
        /**
         * Fired when an error occurs while loading the environment.
         */
        this.onEnvironmentError = new Observable();
        /**
         * Fired when the shadows configuration changes.
         */
        this.onShadowsConfigurationChanged = new Observable();
        /**
         * Fired when the post processing state changes.
         */
        this.onPostProcessingChanged = new Observable();
        /**
         * Fired when a model is loaded into the viewer (or unloaded from the viewer).
         * @remarks
         * The event argument is the source that was loaded, or null if no model is loaded.
         */
        this.onModelChanged = new Observable();
        /**
         * Fired when an error occurs while loading a model.
         */
        this.onModelError = new Observable();
        /**
         * Fired when progress changes on loading activity.
         */
        this.onLoadingProgressChanged = new Observable();
        /**
         * Fired when the camera auto orbit state changes.
         */
        this.onCameraAutoOrbitChanged = new Observable();
        /**
         * Fired when the selected animation changes.
         */
        this.onSelectedAnimationChanged = new Observable();
        /**
         * Fired when the animation speed changes.
         */
        this.onAnimationSpeedChanged = new Observable();
        /**
         * Fired when the selected animation is playing or paused.
         */
        this.onIsAnimationPlayingChanged = new Observable();
        /**
         * Fired when the current point on the selected animation timeline changes.
         */
        this.onAnimationProgressChanged = new Observable();
        /**
         * Fired when the selected material variant changes.
         */
        this.onSelectedMaterialVariantChanged = new Observable();
        /**
         * Fired when the hot spots object changes to a complete new object instance.
         */
        this.onHotSpotsChanged = new Observable();
        /**
         * Fired when the cameras as hot spots property changes.
         */
        this.onCamerasAsHotSpotsChanged = new Observable();
        /**
         * Fired after each frame is rendered.
         */
        this.onAfterRenderObservable = new Observable();
        /**
         * Fired when the clear color changes.
         */
        this.onClearColorChanged = new Observable();
        /**
         * @internal Tracks in-flight load operations (model + environment + shadows) so that
         * `loadingProgress` can return either an aggregate progress number, `true` (indeterminate),
         * or `false` (no operations in flight).
         */
        this._loadOperations = new Set();
        /** @internal True after `dispose()` has been called. */
        this._isDisposed = false;
        // ──────────────────────────────────────────────────────────────────────────
        // Environment loading orchestration
        // ──────────────────────────────────────────────────────────────────────────
        /** Lock guarding lighting-side environment loads. */
        this._loadEnvironmentLightingLock = new AsyncLock();
        /** Abort controller for the currently in-flight lighting-side load (null when none). */
        this._loadEnvironmentLightingAbortController = null;
        /** Lock guarding skybox-side environment loads. */
        this._loadEnvironmentSkyboxLock = new AsyncLock();
        /** Abort controller for the currently in-flight skybox-side load (null when none). */
        this._loadEnvironmentSkyboxAbortController = null;
        // ── Environment configuration (intensity / blur / rotation) ──
        /** @internal Current environment intensity. Initialized from options in subclass constructors. */
        this._environmentIntensity = DefaultViewerBaseOptions.environmentConfig.intensity;
        /** @internal Current environment skybox blur. Initialized from options in subclass constructors. */
        this._environmentBlur = DefaultViewerBaseOptions.environmentConfig.blur;
        /** @internal Current environment rotation in radians. Initialized from options in subclass constructors. */
        this._environmentRotation = DefaultViewerBaseOptions.environmentConfig.rotation;
        // ── Camera auto-orbit ──
        /** @internal Initialized from options in subclass constructors. */
        this._autoOrbitEnabled = DefaultViewerBaseOptions.cameraAutoOrbit.enabled;
        /** @internal Initialized from options in subclass constructors. */
        this._autoOrbitSpeed = DefaultViewerBaseOptions.cameraAutoOrbit.speed;
        /** @internal Initialized from options in subclass constructors. */
        this._autoOrbitDelay = DefaultViewerBaseOptions.cameraAutoOrbit.delay;
        // ── Clear color ──
        /**
         * @internal The current scene clear color, stored as a stable mutable record so consumers
         * holding a reference returned by the `clearColor` getter see updates from the setter and from
         * `reset("environment")`. The setter mutates this object in-place rather than replacing it.
         */
        this._clearColor = { r: 0, g: 0, b: 0, a: 0 };
        // ── Hot spots ──
        /** @internal Pure state — no engine state. Subclasses initialize via the public `hotSpots` setter in their constructor body. */
        this._hotSpots = {};
        // ──────────────────────────────────────────────────────────────────────────
        // Model loading orchestration
        // ──────────────────────────────────────────────────────────────────────────
        /** Lock guarding model loads (and resets). */
        this._loadModelLock = new AsyncLock();
        /** Abort controller for the currently in-flight model load (null when none). */
        this._loadModelAbortController = null;
        // ──────────────────────────────────────────────────────────────────────────
        // Shadow update orchestration
        // ──────────────────────────────────────────────────────────────────────────
        /** Lock guarding shadow updates. */
        this._updateShadowsLock = new AsyncLock();
        /** Abort controller for the currently in-flight shadow update (null when none). */
        this._shadowsAbortController = null;
        /**
         * @internal The currently committed shadow quality. Subclasses initialize this from their
         * options in their constructor and read it in their `_loadModelImpl` etc. The base class
         * commits a new value here only after `_updateShadowsImpl` succeeds, so failed/aborted
         * shadow updates don't leave this field out of sync with engine state.
         */
        this._shadowQuality = DefaultViewerBaseOptions.shadowConfig.quality;
    }
    /**
     * The current loading progress. False when no load is in flight, true when at least one
     * load is in flight with indeterminate progress, or a number between 0 and 1 representing
     * the average of all in-flight loads' progress.
     */
    get loadingProgress() {
        if (this._loadOperations.size > 0) {
            let totalProgress = 0;
            for (const operation of this._loadOperations) {
                if (operation.progress == null) {
                    return true;
                }
                totalProgress += operation.progress;
            }
            return totalProgress / this._loadOperations.size;
        }
        return false;
    }
    /**
     * Begin tracking a new load operation. Subclasses call this at the start of an async load
     * and dispose the returned handle when it completes (or fails). The handle exposes a
     * `progress` setter that, when assigned, fires `onLoadingProgressChanged`.
     * @returns A handle that can be disposed when the operation completes; the `progress` setter
     *   updates the aggregate `loadingProgress` as the operation runs.
     */
    _beginLoadOperation() {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const viewer = this;
        let progress = null;
        const loadOperation = {
            get progress() {
                return progress;
            },
            set progress(value) {
                progress = value;
                viewer.onLoadingProgressChanged.notifyObservers();
            },
            dispose: () => {
                viewer._loadOperations.delete(loadOperation);
                viewer.onLoadingProgressChanged.notifyObservers();
            },
        };
        this._loadOperations.add(loadOperation);
        this.onLoadingProgressChanged.notifyObservers();
        return loadOperation;
    }
    /**
     * @internal Throws if the viewer has been disposed or any of the supplied abort signals
     * are aborted. Used at the start of every async operation to bail out early.
     * @param abortSignals Optional abort signals to check.
     */
    _throwIfDisposedOrAborted(...abortSignals) {
        if (this._isDisposed) {
            throw new Error("Viewer is disposed.");
        }
        throwIfAborted(...abortSignals);
    }
    /**
     * @internal The abort signal of the currently in-flight lighting-side load, or `undefined` if
     * none. Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async
     * work (shadows, post-processing, etc.) when the user starts a new lighting load.
     */
    get _loadEnvironmentLightingAbortSignal() {
        return this._loadEnvironmentLightingAbortController?.signal;
    }
    /**
     * @internal The abort signal of the currently in-flight skybox-side load, or `undefined` if
     * none. Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async
     * work (shadows, post-processing, etc.) when the user starts a new skybox load.
     */
    get _loadEnvironmentSkyboxAbortSignal() {
        return this._loadEnvironmentSkyboxAbortController?.signal;
    }
    /**
     * Loads an environment from the specified URL. The lighting and skybox sides have
     * independent locks and abort controllers so concurrent requests for one side don't
     * cancel an in-flight load for the other.
     * @param url The URL of the environment to load.
     * @param options Selects which sides to update (defaults to both) and forwards engine-specific extras.
     * @param abortSignal Optional signal that can be used to abort the load externally.
     * @returns A promise that resolves when the environment has finished loading.
     */
    async loadEnvironment(url, options, abortSignal) {
        return await this._updateEnvironment(url, options, abortSignal);
    }
    /**
     * Removes the loaded environment. By default removes both lighting and skybox; pass `options`
     * to remove only one side. Subclasses (notably the full Viewer) may override to add backend-specific
     * fallback behavior such as substituting a default environment for lighting when the scene contains
     * PBR materials.
     * @param options Selects which sides to remove (defaults to both).
     * @param abortSignal Optional signal that can be used to abort the operation externally.
     * @returns A promise that resolves when the environment has finished resetting.
     */
    async resetEnvironment(options, abortSignal) {
        return await this._updateEnvironment(undefined, options, abortSignal);
    }
    /**
     * @internal Internal helper exposing the dual-lock orchestration with a nullable URL.
     * Used by the public `loadEnvironment` (with a string URL) and by subclass `resetEnvironment`
     * implementations (which pass `undefined` to clear or `"auto"` to load defaults).
     *
     * Subclasses should NOT override this — override the abstract `_loadEnvironmentImpl` instead.
     */
    async _updateEnvironment(url, options, abortSignal) {
        this._throwIfDisposedOrAborted(abortSignal);
        // Default semantics: omitting `options` updates both; passing `options` updates only
        // the sides whose flag is truthy. `{ lighting: true }` alone updates only lighting,
        // `{ skybox: true }` alone updates only skybox.
        const updateLighting = options ? !!options.lighting : true;
        const updateSkybox = options ? !!options.skybox : true;
        if (!updateLighting && !updateSkybox) {
            return;
        }
        // Resolved options object — `lighting` and `skybox` are guaranteed booleans here, while
        // engine-specific extras (e.g. `extension`) are forwarded as-is. Passed to the impl so
        // it can use both the resolved flags and the original extras without duplicating the
        // default-resolution logic.
        const resolvedOptions = { ...options, lighting: updateLighting, skybox: updateSkybox };
        const locks = [];
        const internalAbortControllers = [];
        if (updateLighting) {
            this._loadEnvironmentLightingAbortController?.abort(new AbortError("New environment lighting is being loaded before previous environment lighting finished loading."));
            const lightingAbortController = (this._loadEnvironmentLightingAbortController = new AbortController());
            locks.push(this._loadEnvironmentLightingLock);
            internalAbortControllers.push(lightingAbortController);
        }
        if (updateSkybox) {
            this._loadEnvironmentSkyboxAbortController?.abort(new AbortError("New environment skybox is being loaded before previous environment skybox finished loading."));
            const skyboxAbortController = (this._loadEnvironmentSkyboxAbortController = new AbortController());
            locks.push(this._loadEnvironmentSkyboxLock);
            internalAbortControllers.push(skyboxAbortController);
        }
        // Composite abort fires only when ALL relevant internal aborts fire — a skybox-only
        // re-request shouldn't cancel an in-progress lighting load when both were requested
        // together.
        const compositeAbortController = new AbortController();
        const checkAllAborted = () => {
            if (internalAbortControllers.every((c) => c.signal.aborted)) {
                compositeAbortController.abort(new AbortError(internalAbortControllers.map((controller) => controller.signal.reason).join(" | ")));
            }
        };
        for (const controller of internalAbortControllers) {
            controller.signal.addEventListener("abort", checkAllAborted);
        }
        try {
            await AsyncLock.LockAsync(async () => {
                throwIfAborted(abortSignal, compositeAbortController.signal);
                await this._loadEnvironmentImpl(url?.trim() ?? url, resolvedOptions, abortSignal, compositeAbortController.signal);
            }, locks);
        }
        finally {
            for (const controller of internalAbortControllers) {
                controller.signal.removeEventListener("abort", checkAllAborted);
            }
        }
    }
    get environmentConfig() {
        return {
            intensity: this._environmentIntensity,
            blur: this._environmentBlur,
            rotation: this._environmentRotation,
        };
    }
    set environmentConfig(value) {
        if (value.blur !== undefined && value.blur !== this._environmentBlur) {
            this._environmentBlur = value.blur;
            this._applyEnvironmentBlur();
        }
        if (value.intensity !== undefined && value.intensity !== this._environmentIntensity) {
            this._environmentIntensity = value.intensity;
            this._applyEnvironmentIntensity();
        }
        if (value.rotation !== undefined && value.rotation !== this._environmentRotation) {
            this._environmentRotation = value.rotation;
            this._applyEnvironmentRotation();
        }
        this.onEnvironmentConfigurationChanged.notifyObservers();
    }
    get cameraAutoOrbit() {
        return {
            enabled: this._autoOrbitEnabled,
            speed: this._autoOrbitSpeed,
            delay: this._autoOrbitDelay,
        };
    }
    set cameraAutoOrbit(value) {
        let changed = false;
        if (value.enabled !== undefined && value.enabled !== this._autoOrbitEnabled) {
            this._autoOrbitEnabled = value.enabled;
            this._applyCameraAutoOrbitEnabled();
            changed = true;
        }
        if (value.speed !== undefined && value.speed !== this._autoOrbitSpeed) {
            this._autoOrbitSpeed = value.speed;
            this._applyCameraAutoOrbitSpeed();
            changed = true;
        }
        if (value.delay !== undefined && value.delay !== this._autoOrbitDelay) {
            this._autoOrbitDelay = value.delay;
            this._applyCameraAutoOrbitDelay();
            changed = true;
        }
        if (changed) {
            this.onCameraAutoOrbitChanged.notifyObservers();
        }
    }
    /**
     * The viewer clear color (e.g. background).
     */
    get clearColor() {
        return this._clearColor;
    }
    set clearColor(value) {
        this._clearColor.r = value.r;
        this._clearColor.g = value.g;
        this._clearColor.b = value.b;
        this._clearColor.a = value.a;
        this._applyClearColor();
        this.onClearColorChanged.notifyObservers();
    }
    /**
     * The set of defined hotspots.
     */
    get hotSpots() {
        return this._hotSpots;
    }
    set hotSpots(value) {
        this._hotSpots = value;
        this.onHotSpotsChanged.notifyObservers();
    }
    /**
     * @internal The abort signal of the currently in-flight model load, or `undefined` if none.
     * Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async work
     * (shadows, environment fallback, etc.) when the user starts a new model load.
     */
    get _loadModelAbortSignal() {
        return this._loadModelAbortController?.signal;
    }
    /**
     * Loads a 3D model from the specified source.
     * @param source The source of the model to load.
     * @param options Engine-specific options for loading the model.
     * @param abortSignal Optional signal that can be used to abort the load externally.
     * @returns A promise that resolves when the model has finished loading.
     */
    async loadModel(source, options, abortSignal) {
        return await this._updateModel(source, options, abortSignal);
    }
    /**
     * Unloads the current 3D model if one is loaded.
     * @param abortSignal Optional signal that can be used to abort the reset.
     * @returns A promise that resolves when the current model has been unloaded.
     */
    async resetModel(abortSignal) {
        return await this._updateModel(undefined, undefined, abortSignal);
    }
    /**
     * @internal Internal helper exposing the model load orchestration with `source: undefined` meaning
     * "unload the current model". Subclasses should NOT override this — override `_loadModelImpl` instead.
     */
    async _updateModel(source, options, abortSignal) {
        this._throwIfDisposedOrAborted(abortSignal);
        this._loadModelAbortController?.abort(new AbortError("New model is being loaded before previous model finished loading."));
        const internalAbortController = (this._loadModelAbortController = new AbortController());
        await this._loadModelLock.lockAsync(async () => {
            throwIfAborted(abortSignal, internalAbortController.signal);
            await this._loadModelImpl(source, options, abortSignal, internalAbortController.signal);
        });
        // Post-lock follow-up (e.g. operations that need other locks). Skip if a newer model load
        // has already aborted us — the new load owns the post-load work for its own state.
        if (!internalAbortController.signal.aborted) {
            await this._afterLoadModel(source, options, abortSignal, internalAbortController.signal);
        }
    }
    /**
     * @internal Optional post-lock hook invoked AFTER the model load lock is released, allowing
     * subclasses to do follow-up work that needs other locks (e.g. environment fallback). The base
     * skips this hook if the load was superseded by a newer one before we got here.
     *
     * Implementations that await additional work should re-check `internalAbortSignal.aborted`
     * after each await to avoid acting on stale state (a newer load may have started during the
     * await window).
     *
     * Default: no-op.
     */
    async _afterLoadModel(
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    source, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    options, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    abortSignal, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    internalAbortSignal) {
        // Default: no-op.
    }
    /**
     * @internal The abort signal of the currently in-flight shadow update, or `undefined` if none.
     * Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async work
     * when the user starts a new shadow update.
     */
    get _shadowsAbortSignal() {
        return this._shadowsAbortController?.signal;
    }
    /**
     * Gets the current shadow configuration.
     */
    get shadowConfig() {
        return { quality: this._shadowQuality };
    }
    /**
     * Updates the shadow configuration. Skips work if the requested value matches the currently
     * committed one. Subclasses can override this to validate the requested quality (e.g. throw on
     * unsupported combinations) before delegating to `super.updateShadows(value, abortSignal)`.
     * @param value The new shadow configuration.
     * @param abortSignal Optional signal that can be used to abort the update externally.
     * @returns A promise that resolves when the shadow update completes.
     */
    async updateShadows(value, abortSignal) {
        if (value.quality === undefined || value.quality === this._shadowQuality) {
            return;
        }
        // Commit `_shadowQuality` BEFORE running the impl: the engine-specific shadow setup (and
        // other handlers that may fire while shadows are being reconfigured, e.g. environment
        // rotation) read `this._shadowQuality` as the target value, so it needs to be visible
        // during the impl. On failure/abort we roll back below so the public `shadowConfig` stays
        // in sync with the actually-applied engine state and a retry with the same quality isn't
        // short-circuited by the early-return above.
        const previousQuality = this._shadowQuality;
        this._shadowQuality = value.quality;
        try {
            await this._updateShadows(this._shadowQuality, abortSignal);
        }
        catch (error) {
            // Only roll back if a newer `updateShadows` hasn't already committed its own value on
            // top of ours; otherwise we'd clobber the newer caller's intent.
            if (this._shadowQuality === value.quality) {
                this._shadowQuality = previousQuality;
            }
            throw error;
        }
        this.onShadowsConfigurationChanged.notifyObservers();
    }
    /**
     * Runs the engine-specific shadow update at the given quality under the shared lock, with
     * abort-prev semantics. Subclasses should call this (rather than `_updateShadowsImpl` directly)
     * when they need to re-run the shadow setup (e.g. after a model change or environment change).
     * The public `updateShadows` also routes through this helper.
     * @param quality The shadow quality to apply. Defaults to the currently committed quality
     *   (`this._shadowQuality`), which is the right choice for re-running shadow setup without
     *   changing the committed quality. The public `updateShadows` passes a resolved new quality.
     * @param abortSignal Optional external abort signal.
     * @returns A promise that resolves when the shadow update completes.
     */
    async _updateShadows(quality = this._shadowQuality, abortSignal) {
        this._throwIfDisposedOrAborted(abortSignal);
        this._shadowsAbortController?.abort(new AbortError("Shadows quality is being changed before previous shadows finished initializing."));
        const internalAbortController = (this._shadowsAbortController = new AbortController());
        await this._updateShadowsLock.lockAsync(async () => {
            throwIfAborted(abortSignal, internalAbortController.signal);
            await this._updateShadowsImpl(quality, abortSignal, internalAbortController.signal);
        });
    }
    /**
     * Disposes the viewer and releases shared resources (observables, disposed flag).
     * Subclasses MUST override this method to dispose their own engine-specific state
     * (engine, scene, abort controllers, models, etc.) and call `super.dispose()` last
     * so observable consumers see the engine-specific notifications before observables clear.
     *
     * Subclasses should also early-return if `_isDisposed` is already true.
     */
    dispose() {
        this._loadEnvironmentLightingAbortController?.abort(new AbortError("The viewer is being disposed."));
        this._loadEnvironmentLightingAbortController = null;
        this._loadEnvironmentSkyboxAbortController?.abort(new AbortError("The viewer is being disposed."));
        this._loadEnvironmentSkyboxAbortController = null;
        this._loadModelAbortController?.abort(new AbortError("The viewer is being disposed."));
        this._loadModelAbortController = null;
        this._shadowsAbortController?.abort(new AbortError("The viewer is being disposed."));
        this._shadowsAbortController = null;
        this.onEnvironmentChanged.clear();
        this.onEnvironmentConfigurationChanged.clear();
        this.onEnvironmentError.clear();
        this.onShadowsConfigurationChanged.clear();
        this.onPostProcessingChanged.clear();
        this.onModelChanged.clear();
        this.onModelError.clear();
        this.onLoadingProgressChanged.clear();
        this.onCameraAutoOrbitChanged.clear();
        this.onSelectedAnimationChanged.clear();
        this.onAnimationSpeedChanged.clear();
        this.onIsAnimationPlayingChanged.clear();
        this.onAnimationProgressChanged.clear();
        this.onSelectedMaterialVariantChanged.clear();
        this.onHotSpotsChanged.clear();
        this.onCamerasAsHotSpotsChanged.clear();
        this.onAfterRenderObservable.clear();
        this.onClearColorChanged.clear();
        this._isDisposed = true;
    }
    // ── Reset orchestration ──
    /**
     * Resets the viewer to its initial state based on the options passed in to the constructor.
     * @param flags The flags that specify which parts of the viewer to reset. If no flags are provided, all parts will be reset.
     * - "source": Reset the loaded model.
     * - "environment": Reset environment related state.
     * - "shadow": Reset shadow related state.
     * - "animation": Reset animation related state.
     * - "camera": Reset camera related state.
     * - "post-processing": Reset post-processing related state.
     * - "material-variant": Reset material variant related state.
     */
    reset(...flags) {
        this._reset(true, ...flags);
    }
    /**
     * @internal
     * Orchestrates the reset operation in canonical flag order. The {@link interpolate} parameter is
     * forwarded to per-flag hooks (currently only `_resetCamera`) so internal callers can reset
     * without camera animation.
     */
    _reset(interpolate, ...flags) {
        const all = flags.length === 0;
        if (all || flags.includes("source")) {
            this._resetModel();
        }
        if (all || flags.includes("environment")) {
            this._resetEnvironment();
        }
        if (all || flags.includes("shadow")) {
            this._resetShadows();
        }
        if (all || flags.includes("animation")) {
            this._resetAnimation();
        }
        if (all || flags.includes("camera")) {
            this._resetCamera(interpolate);
        }
        if (all || flags.includes("post-processing")) {
            this._resetPostProcessing();
        }
        if (all || flags.includes("material-variant")) {
            this._resetMaterialVariant();
        }
    }
    /**
     * @internal Resets the loaded model to the source specified at construction (or no model if no source was specified).
     */
    _resetModel() {
        observePromise(this._updateModel(this._options?.source));
    }
    /**
     * @internal Resets the shadow configuration to the value specified at construction.
     */
    _resetShadows() {
        observePromise(this.updateShadows({ quality: this._options?.shadowConfig?.quality ?? DefaultViewerBaseOptions.shadowConfig.quality }));
    }
    /**
     * @internal Resets the selected material variant to the value specified at construction (or null if not specified).
     */
    _resetMaterialVariant() {
        this.selectedMaterialVariant = this._options?.selectedMaterialVariant ?? null;
    }
}

/******************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */


function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
}
function __runInitializers(thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
}
function __setFunctionName(f, name, prefix) {
    if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
    return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
}
function __classPrivateFieldGet(receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
}

function __classPrivateFieldSet(receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
}

typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
    var e = new Error(message);
    return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const t$3=t=>(e,o)=>{ void 0!==o?o.addInitializer(()=>{customElements.define(t,e);}):customElements.define(t,e);};

/**
 * @license
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const t$2=globalThis,e$5=t$2.ShadowRoot&&(void 0===t$2.ShadyCSS||t$2.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$3=Symbol(),o$6=new WeakMap;let n$5 = class n{constructor(t,e,o){if(this._$cssResult$=true,o!==s$3)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$5&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$6.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$6.set(s,t));}return t}toString(){return this.cssText}};const r$6=t=>new n$5("string"==typeof t?t:t+"",void 0,s$3),i$4=(t,...e)=>{const o=1===t.length?t[0]:e.reduce((e,s,o)=>e+(t=>{if(true===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[o+1],t[0]);return new n$5(o,t,s$3)},S$1=(s,o)=>{if(e$5)s.adoptedStyleSheets=o.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of o){const o=document.createElement("style"),n=t$2.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$3=e$5?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$6(e)})(t):t;

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */const{is:i$3,defineProperty:e$4,getOwnPropertyDescriptor:h$2,getOwnPropertyNames:r$5,getOwnPropertySymbols:o$5,getPrototypeOf:n$4}=Object,a$1=globalThis,c$2=a$1.trustedTypes,l$1=c$2?c$2.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$2=(t,s)=>!i$3(t,s),b$1={attribute:true,type:String,converter:u$1,reflect:false,useDefault:false,hasChanged:f$2};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;let y$1 = class y extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=b$1){if(s.state&&(s.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=true),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e$4(this.prototype,t,h);}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h$2(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i);},configurable:true,enumerable:true}}static getPropertyOptions(t){return this.elementProperties.get(t)??b$1}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$4(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...r$5(t),...o$5(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$3(s));}else void 0!==s&&i.push(c$3(s));return i}static _$Eu(t,s){const i=s.attribute;return  false===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this));}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$EO?.delete(t);}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(true),this._$EO?.forEach(t=>t.hostConnected?.());}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.());}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&true===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null;}}requestUpdate(t,s,i,e=false,h){if(void 0!==t){const r=this.constructor;if(false===e&&(h=this[t]),i??=r.getPropertyOptions(t),!((i.hasChanged??f$2)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(r._$Eu(t,i))))return;this.C(t,s,i);} false===this.isUpdatePending&&(this._$ES=this._$EP());}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),true!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),true===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];true!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e);}}let t=false;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(s)):this._$EM();}catch(s){throw t=false,this._$EM(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(t)),this.updated(t);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return  true}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM();}updated(t){}firstUpdated(t){}};y$1.elementStyles=[],y$1.shadowRootOptions={mode:"open"},y$1[d$1("elementProperties")]=new Map,y$1[d$1("finalized")]=new Map,p$1?.({ReactiveElement:y$1}),(a$1.reactiveElementVersions??=[]).push("2.1.2");

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */const o$4={attribute:true,type:String,converter:u$1,reflect:false,hasChanged:f$2},r$4=(t=o$4,e,r)=>{const{kind:n,metadata:i}=r;let s=globalThis.litPropertyMetadata.get(i);if(void 0===s&&globalThis.litPropertyMetadata.set(i,s=new Map),"setter"===n&&((t=Object.create(t)).wrapped=true),s.set(r.name,t),"accessor"===n){const{name:o}=r;return {set(r){const n=e.get.call(this);e.set.call(this,r),this.requestUpdate(o,n,t,true,r);},init(e){return void 0!==e&&this.C(o,void 0,t,e),e}}}if("setter"===n){const{name:o}=r;return function(r){const n=this[o];e.call(this,r),this.requestUpdate(o,n,t,true,r);}}throw Error("Unsupported decorator location: "+n)};function n$3(t){return (e,o)=>"object"==typeof o?r$4(t,e,o):((t,e,o)=>{const r=e.hasOwnProperty(o);return e.constructor.createProperty(o,t),r?Object.getOwnPropertyDescriptor(e,o):void 0})(t,e,o)}

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */function r$3(r){return n$3({...r,state:true,attribute:false})}

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const e$3=(e,t,c)=>(c.configurable=true,c.enumerable=true,Reflect.decorate&&"object"!=typeof t&&Object.defineProperty(e,t,c),c);

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */function e$2(e,r){return (n,s,i)=>{const o=t=>t.renderRoot?.querySelector(e)??null;return e$3(n,s,{get(){return o(this)}})}}

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const t$1=globalThis,i$2=t=>t,s$2=t$1.trustedTypes,e$1=s$2?s$2.createPolicy("lit-html",{createHTML:t=>t}):void 0,h$1="$lit$",o$3=`lit$${Math.random().toFixed(9).slice(2)}$`,n$2="?"+o$3,r$2=`<${n$2}>`,l=document,c$1=()=>l.createComment(""),a=t=>null===t||"object"!=typeof t&&"function"!=typeof t,u=Array.isArray,d=t=>u(t)||"function"==typeof t?.[Symbol.iterator],f$1="[ \t\n\f\r]",v=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${f$1}(?:([^\\s"'>=/]+)(${f$1}*=${f$1}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,x=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),b=x(1),E=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),C=new WeakMap,P=l.createTreeWalker(l,129);function V(t,i){if(!u(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==e$1?e$1.createHTML(i):i}const N=(t,i)=>{const s=t.length-1,e=[];let n,l=2===i?"<svg>":3===i?"<math>":"",c=v;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,f=0;for(;f<s.length&&(c.lastIndex=f,u=c.exec(s),null!==u);)f=c.lastIndex,c===v?"!--"===u[1]?c=_:void 0!==u[1]?c=m:void 0!==u[2]?(y.test(u[2])&&(n=RegExp("</"+u[2],"g")),c=p):void 0!==u[3]&&(c=p):c===p?">"===u[0]?(c=n??v,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?p:'"'===u[3]?$:g):c===$||c===g?c=p:c===_||c===m?c=v:(c=p,n=void 0);const x=c===p&&t[i+1].startsWith("/>")?" ":"";l+=c===v?s+r$2:d>=0?(e.push(a),s.slice(0,d)+h$1+s.slice(d)+o$3+x):s+o$3+(-2===d?i:x);}return [V(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),e]};class S{constructor({strings:t,_$litType$:i},e){let r;this.parts=[];let l=0,a=0;const u=t.length-1,d=this.parts,[f,v]=N(t,i);if(this.el=S.createElement(f,e),P.currentNode=this.el.content,2===i||3===i){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=P.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(h$1)){const i=v[a++],s=r.getAttribute(t).split(o$3),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:l,name:e[2],strings:s,ctor:"."===e[1]?I:"?"===e[1]?L:"@"===e[1]?z:H}),r.removeAttribute(t);}else t.startsWith(o$3)&&(d.push({type:6,index:l}),r.removeAttribute(t));if(y.test(r.tagName)){const t=r.textContent.split(o$3),i=t.length-1;if(i>0){r.textContent=s$2?s$2.emptyScript:"";for(let s=0;s<i;s++)r.append(t[s],c$1()),P.nextNode(),d.push({type:2,index:++l});r.append(t[i],c$1());}}}else if(8===r.nodeType)if(r.data===n$2)d.push({type:2,index:l});else {let t=-1;for(;-1!==(t=r.data.indexOf(o$3,t+1));)d.push({type:7,index:l}),t+=o$3.length-1;}l++;}}static createElement(t,i){const s=l.createElement("template");return s.innerHTML=t,s}}function M(t,i,s=t,e){if(i===E)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=a(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=M(t,h._$AS(t,i.values),h,e)),i}class R{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??l).importNode(i,true);P.currentNode=e;let h=P.nextNode(),o=0,n=0,r=s[0];for(;void 0!==r;){if(o===r.index){let i;2===r.type?i=new k(h,h.nextSibling,this,t):1===r.type?i=new r.ctor(h,r.name,r.strings,this,t):6===r.type&&(i=new Z(h,this,t)),this._$AV.push(i),r=s[++n];}o!==r?.index&&(h=P.nextNode(),o++);}return P.currentNode=l,e}p(t){let i=0;for(const s of this._$AV) void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class k{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=A,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=M(this,t,i),a(t)?t===A||null==t||""===t?(this._$AH!==A&&this._$AR(),this._$AH=A):t!==this._$AH&&t!==E&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):d(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==A&&a(this._$AH)?this._$AA.nextSibling.data=t:this.T(l.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=S.createElement(V(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new R(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=C.get(t.strings);return void 0===i&&C.set(t.strings,i=new S(t)),i}k(t){u(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new k(this.O(c$1()),this.O(c$1()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,s){for(this._$AP?.(false,true,s);t!==this._$AB;){const s=i$2(t).nextSibling;i$2(t).remove(),t=s;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class H{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=A,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=M(this,t,i,0),o=!a(t)||t!==this._$AH&&t!==E,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=M(this,e[s+n],i,n),r===E&&(r=this._$AH[n]),o||=!a(r)||r!==this._$AH[n],r===A?t=A:t!==A&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===A?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class I extends H{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===A?void 0:t;}}class L extends H{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==A);}}class z extends H{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=M(this,t,i,0)??A)===E)return;const s=this._$AH,e=t===A&&s!==A||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==A&&(s===A||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class Z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){M(this,t);}}const B=t$1.litHtmlPolyfillSupport;B?.(S,k),(t$1.litHtmlVersions??=[]).push("3.3.2");const D=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new k(i.insertBefore(c$1(),t),t,void 0,s??{});}return h._$AI(t),h};

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */const s$1=globalThis;let i$1 = class i extends y$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=D(r,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return E}};i$1._$litElement$=true,i$1["finalized"]=true,s$1.litElementHydrateSupport?.({LitElement:i$1});const o$2=s$1.litElementPolyfillSupport;o$2?.({LitElement:i$1});(s$1.litElementVersions??=[]).push("4.2.2");

/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */const r$1=o=>void 0===o.strings;

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const t={CHILD:2},e=t=>(...e)=>({_$litDirective$:t,values:e});class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i;}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */const s=(i,t)=>{const e=i._$AN;if(void 0===e)return  false;for(const i of e)i._$AO?.(t,false),s(i,t);return  true},o$1=i=>{let t,e;do{if(void 0===(t=i._$AM))break;e=t._$AN,e.delete(i),i=t;}while(0===e?.size)},r=i=>{for(let t;t=i._$AM;i=t){let e=t._$AN;if(void 0===e)t._$AN=e=new Set;else if(e.has(i))break;e.add(i),c(t);}};function h(i){ void 0!==this._$AN?(o$1(this),this._$AM=i,r(this)):this._$AM=i;}function n$1(i,t=false,e=0){const r=this._$AH,h=this._$AN;if(void 0!==h&&0!==h.size)if(t)if(Array.isArray(r))for(let i=e;i<r.length;i++)s(r[i],false),o$1(r[i]);else null!=r&&(s(r,false),o$1(r));else s(this,i);}const c=i=>{i.type==t.CHILD&&(i._$AP??=n$1,i._$AQ??=h);};class f extends i{constructor(){super(...arguments),this._$AN=void 0;}_$AT(i,t,e){super._$AT(i,t,e),r(this),this.isConnected=i._$AU;}_$AO(i,t=true){i!==this.isConnected&&(this.isConnected=i,i?this.reconnected?.():this.disconnected?.()),t&&(s(this,i),o$1(this));}setValue(t){if(r$1(this._$Ct))this._$Ct._$AI(t,this);else {const i=[...this._$Ct._$AH];i[this._$Ci]=t,this._$Ct._$AI(i,this,0);}}disconnected(){}reconnected(){}}

const o=new WeakMap,n=e(class extends f{render(i){return A}update(i,[s]){const e=s!==this.G;return e&&void 0!==this.G&&this.rt(void 0),(e||this.lt!==this.ct)&&(this.G=s,this.ht=i.options?.host,this.rt(this.ct=i.element)),A}rt(t){if(this.isConnected||(t=void 0),"function"==typeof this.G){const i=this.ht??globalThis;let s=o.get(i);void 0===s&&(s=new WeakMap,o.set(i,s)),void 0!==s.get(this.G)&&this.G.call(this.ht,void 0),s.set(this.G,t),void 0!==t&&this.G.call(this.ht,t);}else this.G.value=t;}get lt(){return "function"==typeof this.G?o.get(this.ht??globalThis)?.get(this.G):this.G?.value}disconnected(){this.lt===this.ct&&this.rt(void 0);}reconnected(){this.rt(this.ct);}});

// Icon SVG is pulled from https://iconcloud.design
const playFilledIcon = "M5 5.27368C5 3.56682 6.82609 2.48151 8.32538 3.2973L20.687 10.0235C22.2531 10.8756 22.2531 13.124 20.687 13.9762L8.32538 20.7024C6.82609 21.5181 5 20.4328 5 18.726V5.27368Z";
const pauseFilledIcon = "M5.74609 3C4.7796 3 3.99609 3.7835 3.99609 4.75V19.25C3.99609 20.2165 4.7796 21 5.74609 21H9.24609C10.2126 21 10.9961 20.2165 10.9961 19.25V4.75C10.9961 3.7835 10.2126 3 9.24609 3H5.74609ZM14.7461 3C13.7796 3 12.9961 3.7835 12.9961 4.75V19.25C12.9961 20.2165 13.7796 21 14.7461 21H18.2461C19.2126 21 19.9961 20.2165 19.9961 19.25V4.75C19.9961 3.7835 19.2126 3 18.2461 3H14.7461Z";
const arrowResetFilledIcon = "M7.20711 2.54289C7.59763 2.93342 7.59763 3.56658 7.20711 3.95711L5.41421 5.75H13.25C17.6683 5.75 21.25 9.33172 21.25 13.75C21.25 18.1683 17.6683 21.75 13.25 21.75C8.83172 21.75 5.25 18.1683 5.25 13.75C5.25 13.1977 5.69772 12.75 6.25 12.75C6.80228 12.75 7.25 13.1977 7.25 13.75C7.25 17.0637 9.93629 19.75 13.25 19.75C16.5637 19.75 19.25 17.0637 19.25 13.75C19.25 10.4363 16.5637 7.75 13.25 7.75H5.41421L7.20711 9.54289C7.59763 9.93342 7.59763 10.5666 7.20711 10.9571C6.81658 11.3476 6.18342 11.3476 5.79289 10.9571L2.29289 7.45711C1.90237 7.06658 1.90237 6.43342 2.29289 6.04289L5.79289 2.54289C6.18342 2.15237 6.81658 2.15237 7.20711 2.54289Z";
const targetFilledIcon = "M12 14C13.1046 14 14 13.1046 14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14ZM6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18C8.68629 18 6 15.3137 6 12ZM12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8ZM2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4Z";
const arrowClockwiseFilledIcon = "M5 12C5 8.13401 8.13401 5 12 5C13.32 5 14.5542 5.36484 15.608 6H15C14.4477 6 14 6.44772 14 7C14 7.55228 14.4477 8 15 8H18C18.5523 8 19 7.55228 19 7C19 6 19 5 19 4C19 3.44772 18.5523 3 18 3C17.4477 3 17 3.44772 17 4V4.51575C15.5702 3.5588 13.85 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12C21 11.6199 20.9764 11.2448 20.9304 10.8763C20.8621 10.3282 20.3624 9.93935 19.8144 10.0077C19.2663 10.076 18.8775 10.5757 18.9458 11.1237C18.9815 11.4104 19 11.7028 19 12C19 15.866 15.866 19 12 19C8.13401 19 5 15.866 5 12Z";
const allowedAnimationSpeeds = [0.5, 1, 1.5, 2];
// Converts any standard html color string to an IColor4Like object.
function parseColor(color) {
    if (!color) {
        return null;
    }
    const canvas = document.createElement("canvas");
    canvas.width = canvas.height = 1;
    const context = canvas.getContext("2d");
    if (!context) {
        throw new Error("Unable to get 2d context for parseColor");
    }
    context.clearRect(0, 0, 1, 1);
    context.fillStyle = color;
    context.fillRect(0, 0, 1, 1);
    const data = context.getImageData(0, 0, 1, 1).data;
    return { r: data[0] / 255, g: data[1] / 255, b: data[2] / 255, a: data[3] / 255 };
}
function colorToHex(color) {
    const toHex = (v) => Math.round(v * 255)
        .toString(16)
        .padStart(2, "0");
    return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}${toHex(color.a)}`;
}
function coerceNumericAttribute(value) {
    return value == null ? null : Number(value);
}
function coerceCameraOrbitOrTarget(value) {
    if (!value) {
        return null;
    }
    const array = value.trim().split(/\s+/);
    if (array.length !== 3) {
        throw new Error(`Camera orbit and target should be defined as three space separated numbers, but was specified as "${value}".`);
    }
    return array.map((value) => Number(value));
}
function coerceToneMapping(value) {
    if (!value || !IsToneMapping(value)) {
        return null;
    }
    return value;
}
function coerceShadowQuality(value) {
    if (!value || !IsShadowQuality(value)) {
        return null;
    }
    return value;
}
function coerceResetMode(value) {
    if (!value || value === "auto") {
        return "auto";
    }
    if (value === "reframe") {
        return "reframe";
    }
    return value.trim().split(/\s+/);
}
/**
 * Abstract base class for viewer custom elements.
 * Contains all shared UI logic and depends only on IViewer.
 */
let ViewerElementBase = (() => {
    var _a, _ViewerElementBase__isFaultedBacking_accessor_storage, _ViewerElementBase_renderWhenIdle_accessor_storage, _ViewerElementBase_source_accessor_storage, _ViewerElementBase_extension_accessor_storage, _ViewerElementBase_useOpenPBR_accessor_storage, _ViewerElementBase_environmentLighting_accessor_storage, _ViewerElementBase_environmentSkybox_accessor_storage, _ViewerElementBase_environmentIntensity_accessor_storage, _ViewerElementBase_environmentRotation_accessor_storage, _ViewerElementBase_shadowQuality_accessor_storage, _ViewerElementBase__loadingProgress_accessor_storage, _ViewerElementBase_skyboxBlur_accessor_storage, _ViewerElementBase_toneMapping_accessor_storage, _ViewerElementBase_contrast_accessor_storage, _ViewerElementBase_exposure_accessor_storage, _ViewerElementBase_ssao_accessor_storage, _ViewerElementBase_clearColor_accessor_storage, _ViewerElementBase_cameraAutoOrbit_accessor_storage, _ViewerElementBase_cameraAutoOrbitSpeed_accessor_storage, _ViewerElementBase_cameraAutoOrbitDelay_accessor_storage, _ViewerElementBase_hotSpots_accessor_storage, _ViewerElementBase_animationAutoPlay_accessor_storage, _ViewerElementBase_selectedAnimation_accessor_storage, _ViewerElementBase_animationSpeed_accessor_storage, _ViewerElementBase_animationProgress_accessor_storage, _ViewerElementBase__animations_accessor_storage, _ViewerElementBase__isAnimationPlaying_accessor_storage, _ViewerElementBase__showAnimationSlider_accessor_storage, _ViewerElementBase_selectedMaterialVariant_accessor_storage, _ViewerElementBase_camerasAsHotSpots_accessor_storage, _ViewerElementBase_resetMode_accessor_storage, _ViewerElementBase__canvasContainer_accessor_storage, _ViewerElementBase__hotSpotSelect_accessor_storage;
    let _classSuper = i$1;
    let _instanceExtraInitializers = [];
    let __isFaultedBacking_decorators;
    let __isFaultedBacking_initializers = [];
    let __isFaultedBacking_extraInitializers = [];
    let _renderWhenIdle_decorators;
    let _renderWhenIdle_initializers = [];
    let _renderWhenIdle_extraInitializers = [];
    let _source_decorators;
    let _source_initializers = [];
    let _source_extraInitializers = [];
    let _extension_decorators;
    let _extension_initializers = [];
    let _extension_extraInitializers = [];
    let _useOpenPBR_decorators;
    let _useOpenPBR_initializers = [];
    let _useOpenPBR_extraInitializers = [];
    let _set_environment_decorators;
    let _environmentLighting_decorators;
    let _environmentLighting_initializers = [];
    let _environmentLighting_extraInitializers = [];
    let _environmentSkybox_decorators;
    let _environmentSkybox_initializers = [];
    let _environmentSkybox_extraInitializers = [];
    let _environmentIntensity_decorators;
    let _environmentIntensity_initializers = [];
    let _environmentIntensity_extraInitializers = [];
    let _environmentRotation_decorators;
    let _environmentRotation_initializers = [];
    let _environmentRotation_extraInitializers = [];
    let _shadowQuality_decorators;
    let _shadowQuality_initializers = [];
    let _shadowQuality_extraInitializers = [];
    let __loadingProgress_decorators;
    let __loadingProgress_initializers = [];
    let __loadingProgress_extraInitializers = [];
    let _skyboxBlur_decorators;
    let _skyboxBlur_initializers = [];
    let _skyboxBlur_extraInitializers = [];
    let _toneMapping_decorators;
    let _toneMapping_initializers = [];
    let _toneMapping_extraInitializers = [];
    let _contrast_decorators;
    let _contrast_initializers = [];
    let _contrast_extraInitializers = [];
    let _exposure_decorators;
    let _exposure_initializers = [];
    let _exposure_extraInitializers = [];
    let _ssao_decorators;
    let _ssao_initializers = [];
    let _ssao_extraInitializers = [];
    let _clearColor_decorators;
    let _clearColor_initializers = [];
    let _clearColor_extraInitializers = [];
    let _cameraAutoOrbit_decorators;
    let _cameraAutoOrbit_initializers = [];
    let _cameraAutoOrbit_extraInitializers = [];
    let _cameraAutoOrbitSpeed_decorators;
    let _cameraAutoOrbitSpeed_initializers = [];
    let _cameraAutoOrbitSpeed_extraInitializers = [];
    let _cameraAutoOrbitDelay_decorators;
    let _cameraAutoOrbitDelay_initializers = [];
    let _cameraAutoOrbitDelay_extraInitializers = [];
    let _hotSpots_decorators;
    let _hotSpots_initializers = [];
    let _hotSpots_extraInitializers = [];
    let _animationAutoPlay_decorators;
    let _animationAutoPlay_initializers = [];
    let _animationAutoPlay_extraInitializers = [];
    let _selectedAnimation_decorators;
    let _selectedAnimation_initializers = [];
    let _selectedAnimation_extraInitializers = [];
    let _animationSpeed_decorators;
    let _animationSpeed_initializers = [];
    let _animationSpeed_extraInitializers = [];
    let _animationProgress_decorators;
    let _animationProgress_initializers = [];
    let _animationProgress_extraInitializers = [];
    let __animations_decorators;
    let __animations_initializers = [];
    let __animations_extraInitializers = [];
    let __isAnimationPlaying_decorators;
    let __isAnimationPlaying_initializers = [];
    let __isAnimationPlaying_extraInitializers = [];
    let __showAnimationSlider_decorators;
    let __showAnimationSlider_initializers = [];
    let __showAnimationSlider_extraInitializers = [];
    let _selectedMaterialVariant_decorators;
    let _selectedMaterialVariant_initializers = [];
    let _selectedMaterialVariant_extraInitializers = [];
    let _camerasAsHotSpots_decorators;
    let _camerasAsHotSpots_initializers = [];
    let _camerasAsHotSpots_extraInitializers = [];
    let _resetMode_decorators;
    let _resetMode_initializers = [];
    let _resetMode_extraInitializers = [];
    let __canvasContainer_decorators;
    let __canvasContainer_initializers = [];
    let __canvasContainer_extraInitializers = [];
    let __hotSpotSelect_decorators;
    let __hotSpotSelect_initializers = [];
    let __hotSpotSelect_extraInitializers = [];
    return _a = class ViewerElementBase extends _classSuper {
            /**
             * Creates an instance of a ViewerElementBase subclass.
             * @param _options The options to use when creating the Viewer.
             */
            constructor(_options = {}) {
                super();
                this._options = (__runInitializers(this, _instanceExtraInitializers), _options);
                this._viewerLock = new AsyncLock();
                this._animationSliderResizeObserver = null;
                // Bindings for properties that are synchronized both ways between the lower level Viewer and the ViewerElementBase.
                this._propertyBindings = [
                    this._createPropertyBinding("clearColor", (viewer) => viewer.onClearColorChanged, (viewer) => (viewer.clearColor = this.clearColor ?? { r: 0, g: 0, b: 0, a: 0 }), (viewer) => (this.clearColor = viewer.clearColor)),
                    this._createPropertyBinding("skyboxBlur", (viewer) => viewer.onEnvironmentConfigurationChanged, (viewer) => (viewer.environmentConfig = { blur: this.skyboxBlur ?? viewer.environmentConfig.blur }), (viewer) => (this.skyboxBlur = viewer.environmentConfig.blur)),
                    this._createPropertyBinding("environmentIntensity", (viewer) => viewer.onEnvironmentConfigurationChanged, (viewer) => (viewer.environmentConfig = { intensity: this.environmentIntensity ?? viewer.environmentConfig.intensity }), (viewer) => (this.environmentIntensity = viewer.environmentConfig.intensity)),
                    this._createPropertyBinding("environmentRotation", (viewer) => viewer.onEnvironmentConfigurationChanged, (viewer) => (viewer.environmentConfig = { rotation: this.environmentRotation ?? viewer.environmentConfig.rotation }), (viewer) => (this.environmentRotation = viewer.environmentConfig.rotation)),
                    this._createPropertyBinding("toneMapping", (viewer) => viewer.onPostProcessingChanged, (viewer) => {
                        if (this.toneMapping) {
                            viewer.postProcessing = { toneMapping: this.toneMapping };
                        }
                    }, (viewer) => (this.toneMapping = viewer.postProcessing?.toneMapping)),
                    this._createPropertyBinding("contrast", (viewer) => viewer.onPostProcessingChanged, (viewer) => (viewer.postProcessing = { contrast: this.contrast ?? undefined }), (viewer) => (this.contrast = viewer.postProcessing.contrast)),
                    this._createPropertyBinding("exposure", (viewer) => viewer.onPostProcessingChanged, (viewer) => (viewer.postProcessing = { exposure: this.exposure ?? undefined }), (viewer) => (this.exposure = viewer.postProcessing.exposure)),
                    this._createPropertyBinding("ssao", (viewer) => viewer.onPostProcessingChanged, (viewer) => (viewer.postProcessing = { ssao: this.ssao ?? undefined }), (viewer) => (this.ssao = viewer.postProcessing.ssao)),
                    this._createPropertyBinding("cameraAutoOrbit", (viewer) => viewer.onCameraAutoOrbitChanged, (viewer) => (viewer.cameraAutoOrbit = { enabled: this.cameraAutoOrbit }), (viewer) => (this.cameraAutoOrbit = viewer.cameraAutoOrbit.enabled)),
                    this._createPropertyBinding("cameraAutoOrbitSpeed", (viewer) => viewer.onCameraAutoOrbitChanged, (viewer) => (viewer.cameraAutoOrbit = { speed: this.cameraAutoOrbitSpeed ?? undefined }), (viewer) => (this.cameraAutoOrbitSpeed = viewer.cameraAutoOrbit.speed)),
                    this._createPropertyBinding("cameraAutoOrbitDelay", (viewer) => viewer.onCameraAutoOrbitChanged, (viewer) => (viewer.cameraAutoOrbit = { delay: this.cameraAutoOrbitDelay ?? undefined }), (viewer) => (this.cameraAutoOrbitDelay = viewer.cameraAutoOrbit.delay)),
                    this._createPropertyBinding("animationSpeed", (viewer) => viewer.onAnimationSpeedChanged, (viewer) => (viewer.animationSpeed = this.animationSpeed), (viewer) => {
                        let speed = viewer.animationSpeed;
                        speed = allowedAnimationSpeeds.reduce((prev, curr) => (Math.abs(curr - speed) < Math.abs(prev - speed) ? curr : prev));
                        this.animationSpeed = speed;
                        this._dispatchCustomEvent("animationspeedchange", (type) => new Event(type));
                    }),
                    this._createPropertyBinding("selectedAnimation", (viewer) => viewer.onSelectedAnimationChanged, (viewer) => (viewer.selectedAnimation = this.selectedAnimation ?? viewer.selectedAnimation), (viewer) => (this.selectedAnimation = viewer.selectedAnimation)),
                    this._createPropertyBinding("selectedMaterialVariant", (viewer) => viewer.onSelectedMaterialVariantChanged, (viewer) => (viewer.selectedMaterialVariant = this.selectedMaterialVariant ?? viewer.selectedMaterialVariant ?? ""), (viewer) => (this.selectedMaterialVariant = viewer.selectedMaterialVariant)),
                    this._createPropertyBinding("hotSpots", (viewer) => viewer.onHotSpotsChanged, (viewer) => (viewer.hotSpots = this.hotSpots ?? viewer.hotSpots), (viewer) => (this.hotSpots = viewer.hotSpots)),
                    this._createPropertyBinding("camerasAsHotSpots", (viewer) => viewer.onCamerasAsHotSpotsChanged, (viewer) => (viewer.camerasAsHotSpots = this.camerasAsHotSpots ?? viewer.camerasAsHotSpots), (viewer) => (this.camerasAsHotSpots = viewer.camerasAsHotSpots)),
                ];
                _ViewerElementBase__isFaultedBacking_accessor_storage.set(this, __runInitializers(this, __isFaultedBacking_initializers, false));
                _ViewerElementBase_renderWhenIdle_accessor_storage.set(this, (__runInitializers(this, __isFaultedBacking_extraInitializers), __runInitializers(this, _renderWhenIdle_initializers, this._options.autoSuspendRendering === false)));
                _ViewerElementBase_source_accessor_storage.set(this, (__runInitializers(this, _renderWhenIdle_extraInitializers), __runInitializers(this, _source_initializers, this._options.source ?? null)));
                _ViewerElementBase_extension_accessor_storage.set(this, (__runInitializers(this, _source_extraInitializers), __runInitializers(this, _extension_initializers, null)));
                _ViewerElementBase_useOpenPBR_accessor_storage.set(this, (__runInitializers(this, _extension_extraInitializers), __runInitializers(this, _useOpenPBR_initializers, this._options.useOpenPBR ?? false)));
                _ViewerElementBase_environmentLighting_accessor_storage.set(this, (__runInitializers(this, _useOpenPBR_extraInitializers), __runInitializers(this, _environmentLighting_initializers, this._options.environmentLighting ?? null)));
                _ViewerElementBase_environmentSkybox_accessor_storage.set(this, (__runInitializers(this, _environmentLighting_extraInitializers), __runInitializers(this, _environmentSkybox_initializers, this._options.environmentSkybox ?? null)));
                _ViewerElementBase_environmentIntensity_accessor_storage.set(this, (__runInitializers(this, _environmentSkybox_extraInitializers), __runInitializers(this, _environmentIntensity_initializers, this._options.environmentConfig?.intensity ?? null)));
                _ViewerElementBase_environmentRotation_accessor_storage.set(this, (__runInitializers(this, _environmentIntensity_extraInitializers), __runInitializers(this, _environmentRotation_initializers, this._options.environmentConfig?.rotation ?? null)));
                _ViewerElementBase_shadowQuality_accessor_storage.set(this, (__runInitializers(this, _environmentRotation_extraInitializers), __runInitializers(this, _shadowQuality_initializers, null)));
                _ViewerElementBase__loadingProgress_accessor_storage.set(this, (__runInitializers(this, _shadowQuality_extraInitializers), __runInitializers(this, __loadingProgress_initializers, false)));
                _ViewerElementBase_skyboxBlur_accessor_storage.set(this, (__runInitializers(this, __loadingProgress_extraInitializers), __runInitializers(this, _skyboxBlur_initializers, this._options.environmentConfig?.blur ?? null)));
                _ViewerElementBase_toneMapping_accessor_storage.set(this, (__runInitializers(this, _skyboxBlur_extraInitializers), __runInitializers(this, _toneMapping_initializers, this._options.postProcessing?.toneMapping ?? null)));
                _ViewerElementBase_contrast_accessor_storage.set(this, (__runInitializers(this, _toneMapping_extraInitializers), __runInitializers(this, _contrast_initializers, this._options.postProcessing?.contrast ?? null)));
                _ViewerElementBase_exposure_accessor_storage.set(this, (__runInitializers(this, _contrast_extraInitializers), __runInitializers(this, _exposure_initializers, this._options.postProcessing?.exposure ?? null)));
                _ViewerElementBase_ssao_accessor_storage.set(this, (__runInitializers(this, _exposure_extraInitializers), __runInitializers(this, _ssao_initializers, this._options.postProcessing?.ssao ?? null)));
                _ViewerElementBase_clearColor_accessor_storage.set(this, (__runInitializers(this, _ssao_extraInitializers), __runInitializers(this, _clearColor_initializers, this._options.clearColor
                    ? { r: this._options.clearColor[0], g: this._options.clearColor[1], b: this._options.clearColor[2], a: this._options.clearColor[3] ?? 1 }
                    : null)));
                _ViewerElementBase_cameraAutoOrbit_accessor_storage.set(this, (__runInitializers(this, _clearColor_extraInitializers), __runInitializers(this, _cameraAutoOrbit_initializers, this._options.cameraAutoOrbit?.enabled ?? false)));
                _ViewerElementBase_cameraAutoOrbitSpeed_accessor_storage.set(this, (__runInitializers(this, _cameraAutoOrbit_extraInitializers), __runInitializers(this, _cameraAutoOrbitSpeed_initializers, this._options.cameraAutoOrbit?.speed ?? null)));
                _ViewerElementBase_cameraAutoOrbitDelay_accessor_storage.set(this, (__runInitializers(this, _cameraAutoOrbitSpeed_extraInitializers), __runInitializers(this, _cameraAutoOrbitDelay_initializers, this._options.cameraAutoOrbit?.delay ?? null)));
                _ViewerElementBase_hotSpots_accessor_storage.set(this, (__runInitializers(this, _cameraAutoOrbitDelay_extraInitializers), __runInitializers(this, _hotSpots_initializers, this._options.hotSpots ?? {})));
                _ViewerElementBase_animationAutoPlay_accessor_storage.set(this, (__runInitializers(this, _hotSpots_extraInitializers), __runInitializers(this, _animationAutoPlay_initializers, !!this._options.animationAutoPlay)));
                _ViewerElementBase_selectedAnimation_accessor_storage.set(this, (__runInitializers(this, _animationAutoPlay_extraInitializers), __runInitializers(this, _selectedAnimation_initializers, this._options.selectedAnimation ?? null)));
                _ViewerElementBase_animationSpeed_accessor_storage.set(this, (__runInitializers(this, _selectedAnimation_extraInitializers), __runInitializers(this, _animationSpeed_initializers, this._options.animationSpeed ?? 1)));
                _ViewerElementBase_animationProgress_accessor_storage.set(this, (__runInitializers(this, _animationSpeed_extraInitializers), __runInitializers(this, _animationProgress_initializers, 0)));
                _ViewerElementBase__animations_accessor_storage.set(this, (__runInitializers(this, _animationProgress_extraInitializers), __runInitializers(this, __animations_initializers, [])));
                _ViewerElementBase__isAnimationPlaying_accessor_storage.set(this, (__runInitializers(this, __animations_extraInitializers), __runInitializers(this, __isAnimationPlaying_initializers, false)));
                _ViewerElementBase__showAnimationSlider_accessor_storage.set(this, (__runInitializers(this, __isAnimationPlaying_extraInitializers), __runInitializers(this, __showAnimationSlider_initializers, true)));
                _ViewerElementBase_selectedMaterialVariant_accessor_storage.set(this, (__runInitializers(this, __showAnimationSlider_extraInitializers), __runInitializers(this, _selectedMaterialVariant_initializers, this._options.selectedMaterialVariant ?? null)));
                _ViewerElementBase_camerasAsHotSpots_accessor_storage.set(this, (__runInitializers(this, _selectedMaterialVariant_extraInitializers), __runInitializers(this, _camerasAsHotSpots_initializers, false)));
                _ViewerElementBase_resetMode_accessor_storage.set(this, (__runInitializers(this, _camerasAsHotSpots_extraInitializers), __runInitializers(this, _resetMode_initializers, "auto")));
                _ViewerElementBase__canvasContainer_accessor_storage.set(this, (__runInitializers(this, _resetMode_extraInitializers), __runInitializers(this, __canvasContainer_initializers, void 0)));
                _ViewerElementBase__hotSpotSelect_accessor_storage.set(this, (__runInitializers(this, __canvasContainer_extraInitializers), __runInitializers(this, __hotSpotSelect_initializers, void 0)));
                __runInitializers(this, __hotSpotSelect_extraInitializers);
                this._options = _options;
            }
            /** @internal */
            // eslint-disable-next-line @typescript-eslint/naming-convention
            static get observedAttributes() {
                // These attributes don't have corresponding properties, so they are managed directly.
                return [...super.observedAttributes, "camera-orbit", "camera-target"];
            }
            /**
             * Get hotspot world and screen values from a named hotspot
             * @param name slot of the hot spot
             * @param result resulting world and screen positions
             * @returns world position, world normal and screen space coordinates
             */
            queryHotSpot(name, result) {
                if (this._viewer) {
                    return this._viewer.queryHotSpot(name, result);
                }
                return false;
            }
            /**
             * Updates the camera to focus on a named hotspot.
             * @param name The name of the hotspot to focus on.
             * @returns true if the hotspot was found and the camera was updated, false otherwise.
             */
            focusHotSpot(name) {
                if (this._viewer) {
                    return this._viewer.focusHotSpot(name);
                }
                return false;
            }
            get _isFaultedBacking() { return __classPrivateFieldGet(this, _ViewerElementBase__isFaultedBacking_accessor_storage, "f"); }
            set _isFaultedBacking(value) { __classPrivateFieldSet(this, _ViewerElementBase__isFaultedBacking_accessor_storage, value, "f"); }
            get _isFaulted() {
                return this._isFaultedBacking;
            }
            /**
             * When true, the scene will be rendered even if no scene state has changed.
             */
            get renderWhenIdle() { return __classPrivateFieldGet(this, _ViewerElementBase_renderWhenIdle_accessor_storage, "f"); }
            set renderWhenIdle(value) { __classPrivateFieldSet(this, _ViewerElementBase_renderWhenIdle_accessor_storage, value, "f"); }
            /**
             * The model URL.
             */
            get source() { return __classPrivateFieldGet(this, _ViewerElementBase_source_accessor_storage, "f"); }
            set source(value) { __classPrivateFieldSet(this, _ViewerElementBase_source_accessor_storage, value, "f"); }
            /**
             * Forces the model to be loaded with the specified extension.
             * @remarks
             * If this property is not set, the extension will be inferred from the model URL when possible.
             */
            get extension() { return __classPrivateFieldGet(this, _ViewerElementBase_extension_accessor_storage, "f"); }
            set extension(value) { __classPrivateFieldSet(this, _ViewerElementBase_extension_accessor_storage, value, "f"); }
            /**
             * If true, load glTF files using the OpenPBR material instead of the default PBR material.
             * @experimental
             */
            get useOpenPBR() { return __classPrivateFieldGet(this, _ViewerElementBase_useOpenPBR_accessor_storage, "f"); }
            set useOpenPBR(value) { __classPrivateFieldSet(this, _ViewerElementBase_useOpenPBR_accessor_storage, value, "f"); }
            /**
             * The texture URLs used for lighting and skybox. Setting this property will set both environmentLighting and environmentSkybox.
             */
            get environment() {
                return { lighting: this.environmentLighting, skybox: this.environmentSkybox };
            }
            set environment(url) {
                this.environmentLighting = url || null;
                this.environmentSkybox = url || null;
            }
            /**
             * The texture URL for lighting.
             */
            get environmentLighting() { return __classPrivateFieldGet(this, _ViewerElementBase_environmentLighting_accessor_storage, "f"); }
            set environmentLighting(value) { __classPrivateFieldSet(this, _ViewerElementBase_environmentLighting_accessor_storage, value, "f"); }
            /**
             * The texture URL for the skybox.
             */
            get environmentSkybox() { return __classPrivateFieldGet(this, _ViewerElementBase_environmentSkybox_accessor_storage, "f"); }
            set environmentSkybox(value) { __classPrivateFieldSet(this, _ViewerElementBase_environmentSkybox_accessor_storage, value, "f"); }
            /**
             * A value between 0 and 2 that specifies the intensity of the environment lighting.
             */
            get environmentIntensity() { return __classPrivateFieldGet(this, _ViewerElementBase_environmentIntensity_accessor_storage, "f"); }
            set environmentIntensity(value) { __classPrivateFieldSet(this, _ViewerElementBase_environmentIntensity_accessor_storage, value, "f"); }
            /**
             * A value in radians that specifies the rotation of the environment.
             */
            get environmentRotation() { return __classPrivateFieldGet(this, _ViewerElementBase_environmentRotation_accessor_storage, "f"); }
            set environmentRotation(value) { __classPrivateFieldSet(this, _ViewerElementBase_environmentRotation_accessor_storage, value, "f"); }
            /**
             * The type of shadows to use.
             */
            get shadowQuality() { return __classPrivateFieldGet(this, _ViewerElementBase_shadowQuality_accessor_storage, "f"); }
            set shadowQuality(value) { __classPrivateFieldSet(this, _ViewerElementBase_shadowQuality_accessor_storage, value, "f"); }
            get _loadingProgress() { return __classPrivateFieldGet(this, _ViewerElementBase__loadingProgress_accessor_storage, "f"); }
            set _loadingProgress(value) { __classPrivateFieldSet(this, _ViewerElementBase__loadingProgress_accessor_storage, value, "f"); }
            /**
             * Gets information about loading activity.
             * @remarks
             * false indicates no loading activity.
             * true indicates loading activity with no progress information.
             * A number between 0 and 1 indicates loading activity with progress information.
             */
            get loadingProgress() {
                return this._loadingProgress;
            }
            /**
             * A value between 0 and 1 that specifies how much to blur the skybox.
             */
            get skyboxBlur() { return __classPrivateFieldGet(this, _ViewerElementBase_skyboxBlur_accessor_storage, "f"); }
            set skyboxBlur(value) { __classPrivateFieldSet(this, _ViewerElementBase_skyboxBlur_accessor_storage, value, "f"); }
            /**
             * The tone mapping to use for rendering the scene.
             */
            get toneMapping() { return __classPrivateFieldGet(this, _ViewerElementBase_toneMapping_accessor_storage, "f"); }
            set toneMapping(value) { __classPrivateFieldSet(this, _ViewerElementBase_toneMapping_accessor_storage, value, "f"); }
            /**
             * The contrast applied to the scene.
             */
            get contrast() { return __classPrivateFieldGet(this, _ViewerElementBase_contrast_accessor_storage, "f"); }
            set contrast(value) { __classPrivateFieldSet(this, _ViewerElementBase_contrast_accessor_storage, value, "f"); }
            /**
             * The exposure applied to the scene.
             */
            get exposure() { return __classPrivateFieldGet(this, _ViewerElementBase_exposure_accessor_storage, "f"); }
            set exposure(value) { __classPrivateFieldSet(this, _ViewerElementBase_exposure_accessor_storage, value, "f"); }
            /**
             * Enables or disables screen space ambient occlusion (SSAO).
             */
            get ssao() { return __classPrivateFieldGet(this, _ViewerElementBase_ssao_accessor_storage, "f"); }
            set ssao(value) { __classPrivateFieldSet(this, _ViewerElementBase_ssao_accessor_storage, value, "f"); }
            /**
             * The clear color (e.g. background color) for the viewer.
             */
            get clearColor() { return __classPrivateFieldGet(this, _ViewerElementBase_clearColor_accessor_storage, "f"); }
            set clearColor(value) { __classPrivateFieldSet(this, _ViewerElementBase_clearColor_accessor_storage, value, "f"); }
            /**
             * Enables or disables camera auto-orbit.
             */
            get cameraAutoOrbit() { return __classPrivateFieldGet(this, _ViewerElementBase_cameraAutoOrbit_accessor_storage, "f"); }
            set cameraAutoOrbit(value) { __classPrivateFieldSet(this, _ViewerElementBase_cameraAutoOrbit_accessor_storage, value, "f"); }
            /**
             * The speed at which the camera auto-orbits around the target.
             */
            get cameraAutoOrbitSpeed() { return __classPrivateFieldGet(this, _ViewerElementBase_cameraAutoOrbitSpeed_accessor_storage, "f"); }
            set cameraAutoOrbitSpeed(value) { __classPrivateFieldSet(this, _ViewerElementBase_cameraAutoOrbitSpeed_accessor_storage, value, "f"); }
            /**
             * The delay in milliseconds before the camera starts auto-orbiting.
             */
            get cameraAutoOrbitDelay() { return __classPrivateFieldGet(this, _ViewerElementBase_cameraAutoOrbitDelay_accessor_storage, "f"); }
            set cameraAutoOrbitDelay(value) { __classPrivateFieldSet(this, _ViewerElementBase_cameraAutoOrbitDelay_accessor_storage, value, "f"); }
            /**
             * The set of defined hot spots.
             */
            get hotSpots() { return __classPrivateFieldGet(this, _ViewerElementBase_hotSpots_accessor_storage, "f"); }
            set hotSpots(value) { __classPrivateFieldSet(this, _ViewerElementBase_hotSpots_accessor_storage, value, "f"); }
            /**
             * True if the viewer has any hotspots.
             */
            get _hasHotSpots() {
                return Object.keys(this.hotSpots).length > 0;
            }
            /**
             * True if the default animation should play automatically when a model is loaded.
             */
            get animationAutoPlay() { return __classPrivateFieldGet(this, _ViewerElementBase_animationAutoPlay_accessor_storage, "f"); }
            set animationAutoPlay(value) { __classPrivateFieldSet(this, _ViewerElementBase_animationAutoPlay_accessor_storage, value, "f"); }
            /**
             * The list of animation names for the currently loaded model.
             */
            get animations() {
                return this._animations;
            }
            /**
             * True if the loaded model has any animations.
             */
            get _hasAnimations() {
                return this._animations.length > 0;
            }
            /**
             * The currently selected animation index.
             */
            get selectedAnimation() { return __classPrivateFieldGet(this, _ViewerElementBase_selectedAnimation_accessor_storage, "f"); }
            set selectedAnimation(value) { __classPrivateFieldSet(this, _ViewerElementBase_selectedAnimation_accessor_storage, value, "f"); }
            /**
             * True if an animation is currently playing.
             */
            get isAnimationPlaying() {
                return this._isAnimationPlaying;
            }
            /**
             * The speed scale at which animations are played.
             */
            get animationSpeed() { return __classPrivateFieldGet(this, _ViewerElementBase_animationSpeed_accessor_storage, "f"); }
            set animationSpeed(value) { __classPrivateFieldSet(this, _ViewerElementBase_animationSpeed_accessor_storage, value, "f"); }
            /**
             * The current point on the selected animation timeline, normalized between 0 and 1.
             */
            get animationProgress() { return __classPrivateFieldGet(this, _ViewerElementBase_animationProgress_accessor_storage, "f"); }
            set animationProgress(value) { __classPrivateFieldSet(this, _ViewerElementBase_animationProgress_accessor_storage, value, "f"); }
            get _animations() { return __classPrivateFieldGet(this, _ViewerElementBase__animations_accessor_storage, "f"); }
            set _animations(value) { __classPrivateFieldSet(this, _ViewerElementBase__animations_accessor_storage, value, "f"); }
            get _isAnimationPlaying() { return __classPrivateFieldGet(this, _ViewerElementBase__isAnimationPlaying_accessor_storage, "f"); }
            set _isAnimationPlaying(value) { __classPrivateFieldSet(this, _ViewerElementBase__isAnimationPlaying_accessor_storage, value, "f"); }
            get _showAnimationSlider() { return __classPrivateFieldGet(this, _ViewerElementBase__showAnimationSlider_accessor_storage, "f"); }
            set _showAnimationSlider(value) { __classPrivateFieldSet(this, _ViewerElementBase__showAnimationSlider_accessor_storage, value, "f"); }
            /**
             * The list of material variants for the currently loaded model.
             */
            get materialVariants() {
                return this._viewer?.materialVariants ?? [];
            }
            /**
             * The currently selected material variant.
             */
            get selectedMaterialVariant() { return __classPrivateFieldGet(this, _ViewerElementBase_selectedMaterialVariant_accessor_storage, "f"); }
            set selectedMaterialVariant(value) { __classPrivateFieldSet(this, _ViewerElementBase_selectedMaterialVariant_accessor_storage, value, "f"); }
            /**
             * True if scene cameras should be used as hotspots.
             */
            get camerasAsHotSpots() { return __classPrivateFieldGet(this, _ViewerElementBase_camerasAsHotSpots_accessor_storage, "f"); }
            set camerasAsHotSpots(value) { __classPrivateFieldSet(this, _ViewerElementBase_camerasAsHotSpots_accessor_storage, value, "f"); }
            /**
             * Determines the behavior of the reset function, and the associated default reset button.
             * @remarks
             * - "auto" - Resets the camera to the initial pose if it makes sense given other viewer state, such as the selected animation.
             * - "reframe" - Reframes the camera based on the current viewer state (ignores the initial pose).
             * - [ResetFlag] - A space separated list of reset flags that reset various aspects of the viewer state.
             */
            get resetMode() { return __classPrivateFieldGet(this, _ViewerElementBase_resetMode_accessor_storage, "f"); }
            set resetMode(value) { __classPrivateFieldSet(this, _ViewerElementBase_resetMode_accessor_storage, value, "f"); }
            get _canvasContainer() { return __classPrivateFieldGet(this, _ViewerElementBase__canvasContainer_accessor_storage, "f"); }
            set _canvasContainer(value) { __classPrivateFieldSet(this, _ViewerElementBase__canvasContainer_accessor_storage, value, "f"); }
            get _hotSpotSelect() { return __classPrivateFieldGet(this, _ViewerElementBase__hotSpotSelect_accessor_storage, "f"); }
            set _hotSpotSelect(value) { __classPrivateFieldSet(this, _ViewerElementBase__hotSpotSelect_accessor_storage, value, "f"); }
            /**
             * Toggles the play/pause animation state if there is a selected animation.
             */
            toggleAnimation() {
                this._viewer?.toggleAnimation();
            }
            /**
             * Resets the Viewer state based on the @see resetMode property.
             */
            reset() {
                this._reset(this.resetMode);
            }
            _reset(mode) {
                switch (mode) {
                    case "auto":
                        this._viewer?.resetCamera(undefined);
                        break;
                    case "reframe":
                        this._viewer?.resetCamera(true);
                        break;
                    default:
                        this._viewer?.reset(...mode);
                        break;
                }
            }
            /**
             * Resets the camera to its initial pose.
             */
            resetCamera() {
                this._reset("reframe");
            }
            /**
             * Reloads the viewer. This is typically only needed when the viewer is in a faulted state (e.g. due to the context being lost).
             */
            reload() {
                this._tearDownViewer();
                this._setupViewer();
            }
            /** @internal */
            connectedCallback() {
                super.connectedCallback();
                this._setupViewer();
            }
            /** @internal */
            disconnectedCallback() {
                super.disconnectedCallback();
                this._tearDownViewer();
            }
            /** @internal */
            // eslint-disable-next-line @typescript-eslint/naming-convention
            attributeChangedCallback(name, oldValue, newValue) {
                super.attributeChangedCallback(name, oldValue, newValue);
                if (this.hasUpdated) {
                    if (name == "camera-orbit") {
                        const value = coerceCameraOrbitOrTarget(newValue);
                        if (value) {
                            this._viewer?.updateCamera({ alpha: value[0], beta: value[1], radius: value[2] });
                        }
                        else {
                            this._viewer?.resetCamera(false);
                        }
                    }
                    else if (name == "camera-target") {
                        const value = coerceCameraOrbitOrTarget(newValue);
                        if (value) {
                            this._viewer?.updateCamera({ targetX: value[0], targetY: value[1], targetZ: value[2] });
                        }
                        else {
                            this._viewer?.resetCamera(false);
                        }
                    }
                }
            }
            /** @internal */
            // eslint-disable-next-line @typescript-eslint/naming-convention
            update(changedProperties) {
                super.update(changedProperties);
                if (this._hotSpotSelect) {
                    this._hotSpotSelect.value = "";
                }
                if (this._needsReload(changedProperties)) {
                    this._tearDownViewer();
                    this._setupViewer();
                }
                else {
                    this._propertyBindings.filter((binding) => changedProperties.has(binding.property)).forEach((binding) => binding.updateViewer());
                    if (changedProperties.has("source") || changedProperties.has("useOpenPBR")) {
                        this._updateModel();
                    }
                    if (changedProperties.has("environmentLighting") || changedProperties.has("environmentSkybox")) {
                        this._updateEnv({
                            lighting: changedProperties.has("environmentLighting"),
                            skybox: changedProperties.has("environmentSkybox"),
                        });
                    }
                    if (changedProperties.has("shadowQuality")) {
                        this._updateShadows(this.shadowQuality);
                    }
                }
            }
            /**
             * Determines whether a full viewer reload is required for the given property changes.
             * Subclasses can override to add additional reload triggers.
             * @param changedProperties The properties that have changed.
             * @returns True if the viewer needs to be reloaded.
             */
            _needsReload(changedProperties) {
                return changedProperties.get("renderWhenIdle") != null;
            }
            /** @internal */
            // eslint-disable-next-line @typescript-eslint/naming-convention
            render() {
                return b `
            <div class="full-size">
                <div id="canvasContainer" class="full-size"></div>
                ${this._renderOverlay()}
            </div>
        `;
            }
            /**
             * Renders the progress bar.
             * @returns The template result for the progress bar.
             */
            _renderProgressBar() {
                const showProgressBar = this.loadingProgress !== false;
                // If loadingProgress is true, then the progress bar is indeterminate so the value doesn't matter.
                const progressValue = typeof this.loadingProgress === "boolean" ? 0 : this.loadingProgress * 100;
                const isIndeterminate = this.loadingProgress === true;
                return b `
            <div part="progress-bar" class="bar loading-progress-outer ${showProgressBar ? "" : "loading-progress-outer-inactive"}" aria-label="Loading Progress">
                <div
                    class="loading-progress-inner ${isIndeterminate ? "loading-progress-inner-indeterminate" : ""}"
                    style="${isIndeterminate ? "" : `width: ${progressValue}%`}"
                ></div>
            </div>
        `;
            }
            /**
             * Renders the toolbar.
             * @returns The template result for the toolbar.
             */
            _renderToolbar() {
                let toolbarControls = [];
                if (this._viewer?.isModelLoaded) {
                    // If the model has animations, add animation controls.
                    if (this._hasAnimations) {
                        toolbarControls.push(b `
                    <div class="animation-timeline">
                        <button aria-label="${this.isAnimationPlaying ? "Pause" : "Play"}" @click="${this.toggleAnimation}">
                            ${!this.isAnimationPlaying
                            ? b `
                                          <svg viewBox="0 0 24 24">
                                              <path d="${playFilledIcon}" fill="currentColor"></path>
                                          </svg>
                                      `
                            : b `
                                          <svg viewBox="0 0 24 24">
                                              <path d="${pauseFilledIcon}" fill="currentColor"></path>
                                          </svg>
                                      `}
                        </button>
                        <input
                            ${n(this._onAnimationSliderChanged)}
                            aria-label="Animation Progress"
                            class="animation-timeline-input"
                            style="${this._showAnimationSlider ? "" : "visibility: hidden"}"
                            type="range"
                            min="0"
                            max="1"
                            step="0.0001"
                            .value="${this.animationProgress}"
                            @input="${this._onAnimationTimelineChanged}"
                            @pointerdown="${this._onAnimationTimelinePointerDown}"
                        />
                    </div>
                    <select aria-label="Select Animation Speed" @change="${this._onAnimationSpeedChanged}">
                        ${allowedAnimationSpeeds.map((speed) => b `<option value="${speed}" .selected="${this.animationSpeed === speed}">${speed}x</option> `)}
                    </select>
                    ${this.animations.length > 1
                            ? b `<select aria-label="Select Animation" @change="${this._onSelectedAnimationChanged}">
                                  ${this.animations.map((name, index) => b `<option value="${index}" .selected="${this.selectedAnimation === index}">${name}</option>`)}
                              </select>`
                            : ""}
                `);
                    }
                    // If the model has material variants, add material variant controls.
                    if (this.materialVariants.length > 1) {
                        toolbarControls.push(b `
                    <select aria-label="Select Material Variant" @change="${this._onMaterialVariantChanged}">
                        ${this.materialVariants.map((name) => b `<option value="${name}" .selected="${this.selectedMaterialVariant === name}">${name}</option>`)}
                    </select>
                `);
                    }
                    // Always include a button to reset the camera pose.
                    toolbarControls.push(b `
                <button aria-label="Reset Camera Pose" @click="${this.reset}">
                    <svg viewBox="0 0 24 24">
                        <path d="${arrowResetFilledIcon}" fill="currentColor"></path>
                    </svg>
                </button>
            `);
                    // If hotspots have been defined, add hotspot controls.
                    if (this._hasHotSpots) {
                        toolbarControls.push(b `
                    <div class="select-container">
                        <select id="hotSpotSelect" aria-label="Select HotSpot" @change="${this._onHotSpotsChanged}">
                            <!-- When the select is forced to be less wide than the options, padding on the right is lost. Pad with white space. -->
                            ${Object.keys(this.hotSpots).map((name) => b `<option value="${name}">${name}&nbsp;&nbsp;</option>`)}
                        </select>
                        <!-- This button is not actually interactive, we want input to pass through to the select below. -->
                        <button style="pointer-events: none">
                            <svg viewBox="0 0 24 24">
                                <path d="${targetFilledIcon}" fill="currentColor"></path>
                            </svg>
                        </button>
                    </div>
                `);
                    }
                    // Add a vertical divider between each toolbar control.
                    const controlCount = toolbarControls.length;
                    const separator = b `<div class="divider"></div>`;
                    toolbarControls = toolbarControls.reduce((toolbarControls, toolbarControl, index) => {
                        if (index < controlCount - 1) {
                            return [...toolbarControls, toolbarControl, separator];
                        }
                        else {
                            return [...toolbarControls, toolbarControl];
                        }
                    }, new Array());
                }
                if (toolbarControls.length > 0) {
                    return b ` <div part="tool-bar" class="bar ${this._hasAnimations ? "" : "bar-min"} tool-bar">${toolbarControls}</div>`;
                }
                else {
                    return b ``;
                }
            }
            /**
             * Renders the reload button.
             * @returns The template result for the reload button.
             */
            _renderReloadButton() {
                return b `${this._isFaulted
                    ? b `
                      <button aria-label="Reload" part="reload-button" class="reload-button" @click="${this.reload}">
                          <svg viewBox="0 0 24 24">
                              <path d="${arrowClockwiseFilledIcon}" fill="currentColor"></path>
                          </svg>
                      </button>
                  `
                    : ""}`;
            }
            /**
             * Renders UI elements that overlay the viewer.
             * Override this method to provide additional rendering for the component.
             * @returns TemplateResult The rendered template result.
             */
            _renderOverlay() {
                // NOTE: The unnamed 'slot' element holds all child elements of the <babylon-viewer> that do not specify a 'slot' attribute.
                return b `
            <slot class="full-size children-slot"></slot>
            <slot name="progress-bar">${this._renderProgressBar()}</slot>
            <slot name="tool-bar">${this._renderToolbar()}</slot>
            <slot name="reload-button">${this._renderReloadButton()}</slot>
        `;
            }
            /**
             * Dispatches a custom event.
             * @param type The type of the event.
             * @param event A function that creates the event.
             */
            _dispatchCustomEvent(type, event) {
                this.dispatchEvent(event(type));
            }
            /**
             * Handles changes to the selected animation.
             * @param event The change event.
             */
            _onSelectedAnimationChanged(event) {
                const selectElement = event.target;
                this.selectedAnimation = Number(selectElement.value);
            }
            /**
             * Handles changes to the animation speed.
             * @param event The change event.
             */
            _onAnimationSpeedChanged(event) {
                const selectElement = event.target;
                this.animationSpeed = Number(selectElement.value);
            }
            /**
             * Handles changes to the animation timeline.
             * @param event The change event.
             */
            _onAnimationTimelineChanged(event) {
                if (this._viewer) {
                    const input = event.target;
                    const value = Number(input.value);
                    if (value !== this.animationProgress) {
                        this._viewer.animationProgress = value;
                    }
                }
            }
            /**
             * Handles pointer down events on the animation timeline.
             * @param event The pointer down event.
             */
            _onAnimationTimelinePointerDown(event) {
                if (this._viewer?.isAnimationPlaying) {
                    this._viewer.pauseAnimation();
                    const input = event.target;
                    input.addEventListener("pointerup", () => this._viewer?.playAnimation(), { once: true });
                }
            }
            /**
             * Handles changes to the selected material variant.
             * @param event The change event.
             */
            _onMaterialVariantChanged(event) {
                const selectElement = event.target;
                this.selectedMaterialVariant = selectElement.value;
            }
            /**
             * Handles changes to the hot spot list.
             * @param event The change event.
             */
            _onHotSpotsChanged(event) {
                const selectElement = event.target;
                const hotSpotName = selectElement.value;
                // We don't actually want a selected value, this is just a one time trigger.
                selectElement.value = "";
                this.focusHotSpot(hotSpotName);
            }
            _onAnimationSliderChanged(element) {
                this._animationSliderResizeObserver?.disconnect();
                if (element) {
                    this._animationSliderResizeObserver = new ResizeObserver(() => {
                        this._showAnimationSlider = element.clientWidth >= 80;
                    });
                    this._animationSliderResizeObserver.observe(element);
                }
            }
            // Helper function to simplify keeping Viewer properties in sync with ViewerElementBase properties.
            _createPropertyBinding(property, getObservable, updateViewer, updateElement) {
                const tryUpdateViewer = (viewer) => {
                    try {
                        updateViewer(viewer);
                    }
                    catch (error) {
                        Logger.Error(error);
                    }
                };
                return {
                    property,
                    onInitialized: (viewer) => {
                        getObservable(viewer).add(() => {
                            updateElement(viewer);
                        });
                        tryUpdateViewer(viewer);
                    },
                    updateViewer: () => {
                        if (this._viewer) {
                            tryUpdateViewer(this._viewer);
                        }
                    },
                };
            }
            async _setupViewer() {
                await this._viewerLock.lockAsync(async () => {
                    // The first time the element is connected, the canvas container may not be available yet.
                    // Wait for the first update if needed.
                    if (!this._canvasContainer) {
                        await this.updateComplete;
                    }
                    if (this._canvasContainer && !this._viewer) {
                        const canvas = document.createElement("canvas");
                        canvas.className = "full-size canvas";
                        canvas.setAttribute("touch-action", "none");
                        this._canvasContainer.appendChild(canvas);
                        // Proxy intercepts option reads so that current HTML attribute values
                        // take precedence over the initial options passed to the constructor.
                        // eslint-disable-next-line @typescript-eslint/no-this-alias
                        const viewerElement = this;
                        const options = new Proxy(this._options, {
                            get(target, prop) {
                                switch (prop) {
                                    case "autoSuspendRendering":
                                        return !(viewerElement.hasAttribute("render-when-idle") || target.autoSuspendRendering === false);
                                    case "source":
                                        return viewerElement.getAttribute("source") ?? target.source;
                                    case "pluginExtension":
                                        return viewerElement.extension ?? target.pluginExtension;
                                    case "useOpenPBR":
                                        return viewerElement.hasAttribute("use-open-pbr") ? true : (viewerElement.useOpenPBR ?? target.useOpenPBR);
                                    case "environmentLighting":
                                        return viewerElement.getAttribute("environment-lighting") ?? viewerElement.getAttribute("environment") ?? target.environmentLighting;
                                    case "environmentSkybox":
                                        return viewerElement.getAttribute("environment-skybox") ?? viewerElement.getAttribute("environment") ?? target.environmentSkybox;
                                    case "environmentConfig":
                                        return {
                                            intensity: coerceNumericAttribute(viewerElement.getAttribute("environment-intensity")) ?? target.environmentConfig?.intensity,
                                            blur: coerceNumericAttribute(viewerElement.getAttribute("skybox-blur")) ?? target.environmentConfig?.blur,
                                            rotation: coerceNumericAttribute(viewerElement.getAttribute("environment-rotation")) ?? target.environmentConfig?.rotation,
                                        };
                                    case "shadowConfig":
                                        return {
                                            quality: coerceShadowQuality(viewerElement.getAttribute("shadow-quality")) ?? target.shadowConfig?.quality,
                                        };
                                    case "cameraOrbit":
                                        return coerceCameraOrbitOrTarget(viewerElement.getAttribute("camera-orbit")) ?? target.cameraOrbit;
                                    case "cameraTarget":
                                        return coerceCameraOrbitOrTarget(viewerElement.getAttribute("camera-target")) ?? target.cameraTarget;
                                    case "cameraAutoOrbit":
                                        return {
                                            enabled: viewerElement.hasAttribute("camera-auto-orbit") || target.cameraAutoOrbit?.enabled,
                                            speed: coerceNumericAttribute(viewerElement.getAttribute("camera-auto-orbit-speed")) ?? target.cameraAutoOrbit?.speed,
                                            delay: coerceNumericAttribute(viewerElement.getAttribute("camera-auto-orbit-delay")) ?? target.cameraAutoOrbit?.delay,
                                        };
                                    case "animationAutoPlay":
                                        return viewerElement.hasAttribute("animation-auto-play") || target.animationAutoPlay;
                                    case "animationSpeed":
                                        return coerceNumericAttribute(viewerElement.getAttribute("animation-speed")) ?? target.animationSpeed;
                                    case "selectedAnimation":
                                        return coerceNumericAttribute(viewerElement.getAttribute("selected-animation")) ?? target.selectedAnimation;
                                    case "postProcessing":
                                        return {
                                            toneMapping: coerceToneMapping(viewerElement.getAttribute("tone-mapping")) ?? target.postProcessing?.toneMapping,
                                            contrast: coerceNumericAttribute(viewerElement.getAttribute("contrast")) ?? target.postProcessing?.contrast,
                                            exposure: coerceNumericAttribute(viewerElement.getAttribute("exposure")) ?? target.postProcessing?.exposure,
                                            ssao: viewerElement.hasAttribute("ssao") || target.postProcessing?.ssao,
                                        };
                                    case "selectedMaterialVariant":
                                        return viewerElement.getAttribute("material-variant") ?? target.selectedMaterialVariant;
                                    case "onFaulted":
                                        return (error) => {
                                            viewerElement._isFaultedBacking = true;
                                            target.onFaulted?.(error);
                                            viewerElement._tearDownViewer();
                                        };
                                    default:
                                        return target[prop];
                                }
                            },
                        });
                        const viewer = await this._createViewer(canvas, options);
                        this._viewer = viewer;
                        viewer.onEnvironmentChanged.add(() => {
                            this._dispatchCustomEvent("environmentchange", (type) => new Event(type));
                        });
                        viewer.onEnvironmentConfigurationChanged.add(() => {
                            this._dispatchCustomEvent("environmentconfigurationchange", (type) => new Event(type));
                        });
                        viewer.onEnvironmentError.add((error) => {
                            this._dispatchCustomEvent("environmenterror", (type) => new ErrorEvent(type, { error }));
                        });
                        viewer.onShadowsConfigurationChanged.add(() => {
                            this._dispatchCustomEvent("shadowsconfigurationchange", (type) => new Event(type));
                        });
                        viewer.onModelChanged.add((source) => {
                            this._animations = [...viewer.animations];
                            this._dispatchCustomEvent("modelchange", (type) => new CustomEvent(type, { detail: source }));
                        });
                        viewer.onModelError.add((error) => {
                            this._animations = [...viewer.animations];
                            this._dispatchCustomEvent("modelerror", (type) => new ErrorEvent(type, { error }));
                        });
                        viewer.onLoadingProgressChanged.add(() => {
                            this._loadingProgress = viewer.loadingProgress;
                            this._dispatchCustomEvent("loadingprogresschange", (type) => new Event(type));
                        });
                        viewer.onSelectedAnimationChanged.add(() => {
                            this._dispatchCustomEvent("selectedanimationchange", (type) => new Event(type));
                        });
                        viewer.onIsAnimationPlayingChanged.add(() => {
                            this._isAnimationPlaying = viewer.isAnimationPlaying ?? false;
                            this._dispatchCustomEvent("animationplayingchange", (type) => new Event(type));
                        });
                        viewer.onAnimationProgressChanged.add(() => {
                            this.animationProgress = viewer.animationProgress ?? 0;
                            this._dispatchCustomEvent("animationprogresschange", (type) => new Event(type));
                        });
                        viewer.onSelectedMaterialVariantChanged.add(() => {
                            this._dispatchCustomEvent("selectedmaterialvariantchange", (type) => new Event(type));
                        });
                        viewer.onAfterRenderObservable.add(() => {
                            this._dispatchCustomEvent("viewerrender", (type) => new Event(type));
                        });
                        this._propertyBindings.forEach((binding) => binding.onInitialized(viewer));
                        this._dispatchCustomEvent("viewerready", (type) => new Event(type));
                    }
                    this._isFaultedBacking = false;
                });
            }
            async _tearDownViewer() {
                await this._viewerLock.lockAsync(async () => {
                    if (this._viewer) {
                        this._viewer.dispose();
                        this._viewer = undefined;
                    }
                    this._onViewerTornDown();
                    this._loadingProgress = false;
                    // We want to replace the canvas for two reasons:
                    // 1. When the viewer element is reconnected to the DOM, we don't want to briefly see the last frame of the previous model.
                    // 2. If we are changing engines (e.g. WebGL to WebGPU), we need to create a new canvas for the new engine.
                    if (this._canvasContainer && this._canvasContainer.firstElementChild) {
                        this._canvasContainer.removeChild(this._canvasContainer.firstElementChild);
                    }
                });
            }
            /**
             * Called during teardown after the viewer has been disposed.
             * Subclasses can override to clean up additional state.
             */
            _onViewerTornDown() {
                // Intentionally empty — subclasses override as needed.
            }
            async _updateModel() {
                if (this._viewer) {
                    try {
                        if (this.source) {
                            await this._viewer.loadModel(this.source, {
                                pluginExtension: this.extension ?? undefined,
                                useOpenPBR: this.useOpenPBR,
                            });
                        }
                        else {
                            await this._viewer.resetModel();
                        }
                    }
                    catch (error) {
                        // If loadModel was aborted (e.g. because a new model load was requested before this one finished), we can just ignore the error.
                        if (!(error instanceof AbortError)) {
                            Logger.Error(error);
                        }
                    }
                }
            }
            async _updateEnv(options) {
                if (this._viewer) {
                    try {
                        const updates = [];
                        if (options.lighting && options.skybox && this.environmentLighting === this.environmentSkybox) {
                            updates.push([this.environmentLighting, { lighting: true, skybox: true }]);
                        }
                        else {
                            if (options.lighting) {
                                updates.push([this.environmentLighting, { lighting: true }]);
                            }
                            if (options.skybox) {
                                updates.push([this.environmentSkybox, { skybox: true }]);
                            }
                        }
                        const promises = updates.map(async ([url, options]) => {
                            if (url) {
                                await this._viewer?.loadEnvironment(url, options);
                            }
                            else {
                                await this._viewer?.resetEnvironment(options);
                            }
                        });
                        await Promise.all(promises);
                    }
                    catch (error) {
                        // If loadEnvironment was aborted (e.g. because a new environment load was requested before this one finished), we can just ignore the error.
                        if (!(error instanceof AbortError)) {
                            Logger.Error(error);
                        }
                    }
                }
            }
            async _updateShadows(quality) {
                if (!quality) {
                    return;
                }
                try {
                    const options = { quality };
                    await this._viewer?.updateShadows(options);
                }
                catch (error) {
                    // If loadEnvironment was aborted (e.g. because a new environment load was requested before this one finished), we can just ignore the error.
                    if (!(error instanceof AbortError)) {
                        Logger.Error(error);
                    }
                }
            }
        },
        _ViewerElementBase__isFaultedBacking_accessor_storage = new WeakMap(),
        _ViewerElementBase_renderWhenIdle_accessor_storage = new WeakMap(),
        _ViewerElementBase_source_accessor_storage = new WeakMap(),
        _ViewerElementBase_extension_accessor_storage = new WeakMap(),
        _ViewerElementBase_useOpenPBR_accessor_storage = new WeakMap(),
        _ViewerElementBase_environmentLighting_accessor_storage = new WeakMap(),
        _ViewerElementBase_environmentSkybox_accessor_storage = new WeakMap(),
        _ViewerElementBase_environmentIntensity_accessor_storage = new WeakMap(),
        _ViewerElementBase_environmentRotation_accessor_storage = new WeakMap(),
        _ViewerElementBase_shadowQuality_accessor_storage = new WeakMap(),
        _ViewerElementBase__loadingProgress_accessor_storage = new WeakMap(),
        _ViewerElementBase_skyboxBlur_accessor_storage = new WeakMap(),
        _ViewerElementBase_toneMapping_accessor_storage = new WeakMap(),
        _ViewerElementBase_contrast_accessor_storage = new WeakMap(),
        _ViewerElementBase_exposure_accessor_storage = new WeakMap(),
        _ViewerElementBase_ssao_accessor_storage = new WeakMap(),
        _ViewerElementBase_clearColor_accessor_storage = new WeakMap(),
        _ViewerElementBase_cameraAutoOrbit_accessor_storage = new WeakMap(),
        _ViewerElementBase_cameraAutoOrbitSpeed_accessor_storage = new WeakMap(),
        _ViewerElementBase_cameraAutoOrbitDelay_accessor_storage = new WeakMap(),
        _ViewerElementBase_hotSpots_accessor_storage = new WeakMap(),
        _ViewerElementBase_animationAutoPlay_accessor_storage = new WeakMap(),
        _ViewerElementBase_selectedAnimation_accessor_storage = new WeakMap(),
        _ViewerElementBase_animationSpeed_accessor_storage = new WeakMap(),
        _ViewerElementBase_animationProgress_accessor_storage = new WeakMap(),
        _ViewerElementBase__animations_accessor_storage = new WeakMap(),
        _ViewerElementBase__isAnimationPlaying_accessor_storage = new WeakMap(),
        _ViewerElementBase__showAnimationSlider_accessor_storage = new WeakMap(),
        _ViewerElementBase_selectedMaterialVariant_accessor_storage = new WeakMap(),
        _ViewerElementBase_camerasAsHotSpots_accessor_storage = new WeakMap(),
        _ViewerElementBase_resetMode_accessor_storage = new WeakMap(),
        _ViewerElementBase__canvasContainer_accessor_storage = new WeakMap(),
        _ViewerElementBase__hotSpotSelect_accessor_storage = new WeakMap(),
        (() => {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __isFaultedBacking_decorators = [r$3()];
            _renderWhenIdle_decorators = [n$3({ attribute: "render-when-idle", type: Boolean })];
            _source_decorators = [n$3()];
            _extension_decorators = [n$3()];
            _useOpenPBR_decorators = [n$3({ attribute: "use-open-pbr", type: Boolean })];
            _set_environment_decorators = [n$3({
                    hasChanged: (newValue, oldValue) => {
                        const environmentUrl = newValue || null;
                        return environmentUrl !== oldValue.lighting || environmentUrl !== oldValue.skybox;
                    },
                })];
            _environmentLighting_decorators = [n$3({ attribute: "environment-lighting" })];
            _environmentSkybox_decorators = [n$3({ attribute: "environment-skybox" })];
            _environmentIntensity_decorators = [n$3({ type: Number, attribute: "environment-intensity" })];
            _environmentRotation_decorators = [n$3({
                    type: Number,
                    attribute: "environment-rotation",
                })];
            _shadowQuality_decorators = [n$3({
                    attribute: "shadow-quality",
                })];
            __loadingProgress_decorators = [r$3()];
            _skyboxBlur_decorators = [n$3({ attribute: "skybox-blur" })];
            _toneMapping_decorators = [n$3({
                    attribute: "tone-mapping",
                    converter: (value) => {
                        if (!value || !IsToneMapping(value)) {
                            return "neutral";
                        }
                        return value;
                    },
                })];
            _contrast_decorators = [n$3()];
            _exposure_decorators = [n$3()];
            _ssao_decorators = [n$3({ type: String })];
            _clearColor_decorators = [n$3({
                    attribute: "clear-color",
                    converter: {
                        fromAttribute: parseColor,
                        toAttribute: (color) => (color ? colorToHex(color) : null),
                    },
                })];
            _cameraAutoOrbit_decorators = [n$3({
                    attribute: "camera-auto-orbit",
                    type: Boolean,
                })];
            _cameraAutoOrbitSpeed_decorators = [n$3({
                    attribute: "camera-auto-orbit-speed",
                    type: Number,
                })];
            _cameraAutoOrbitDelay_decorators = [n$3({
                    attribute: "camera-auto-orbit-delay",
                    type: Number,
                })];
            _hotSpots_decorators = [n$3({
                    attribute: "hotspots",
                    converter: (value) => {
                        if (!value) {
                            return {};
                        }
                        return JSON.parse(value);
                    },
                })];
            _animationAutoPlay_decorators = [n$3({ attribute: "animation-auto-play", type: Boolean })];
            _selectedAnimation_decorators = [n$3({ attribute: "selected-animation", type: Number })];
            _animationSpeed_decorators = [n$3({ attribute: "animation-speed" })];
            _animationProgress_decorators = [n$3({ attribute: false })];
            __animations_decorators = [r$3()];
            __isAnimationPlaying_decorators = [r$3()];
            __showAnimationSlider_decorators = [r$3()];
            _selectedMaterialVariant_decorators = [n$3({ attribute: "material-variant" })];
            _camerasAsHotSpots_decorators = [n$3({ attribute: "cameras-as-hotspots", type: Boolean })];
            _resetMode_decorators = [n$3({ attribute: "reset-mode", converter: coerceResetMode })];
            __canvasContainer_decorators = [e$2("#canvasContainer")];
            __hotSpotSelect_decorators = [e$2("#hotSpotSelect")];
            __esDecorate(_a, null, __isFaultedBacking_decorators, { kind: "accessor", name: "_isFaultedBacking", static: false, private: false, access: { has: obj => "_isFaultedBacking" in obj, get: obj => obj._isFaultedBacking, set: (obj, value) => { obj._isFaultedBacking = value; } }, metadata: _metadata }, __isFaultedBacking_initializers, __isFaultedBacking_extraInitializers);
            __esDecorate(_a, null, _renderWhenIdle_decorators, { kind: "accessor", name: "renderWhenIdle", static: false, private: false, access: { has: obj => "renderWhenIdle" in obj, get: obj => obj.renderWhenIdle, set: (obj, value) => { obj.renderWhenIdle = value; } }, metadata: _metadata }, _renderWhenIdle_initializers, _renderWhenIdle_extraInitializers);
            __esDecorate(_a, null, _source_decorators, { kind: "accessor", name: "source", static: false, private: false, access: { has: obj => "source" in obj, get: obj => obj.source, set: (obj, value) => { obj.source = value; } }, metadata: _metadata }, _source_initializers, _source_extraInitializers);
            __esDecorate(_a, null, _extension_decorators, { kind: "accessor", name: "extension", static: false, private: false, access: { has: obj => "extension" in obj, get: obj => obj.extension, set: (obj, value) => { obj.extension = value; } }, metadata: _metadata }, _extension_initializers, _extension_extraInitializers);
            __esDecorate(_a, null, _useOpenPBR_decorators, { kind: "accessor", name: "useOpenPBR", static: false, private: false, access: { has: obj => "useOpenPBR" in obj, get: obj => obj.useOpenPBR, set: (obj, value) => { obj.useOpenPBR = value; } }, metadata: _metadata }, _useOpenPBR_initializers, _useOpenPBR_extraInitializers);
            __esDecorate(_a, null, _set_environment_decorators, { kind: "setter", name: "environment", static: false, private: false, access: { has: obj => "environment" in obj, set: (obj, value) => { obj.environment = value; } }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(_a, null, _environmentLighting_decorators, { kind: "accessor", name: "environmentLighting", static: false, private: false, access: { has: obj => "environmentLighting" in obj, get: obj => obj.environmentLighting, set: (obj, value) => { obj.environmentLighting = value; } }, metadata: _metadata }, _environmentLighting_initializers, _environmentLighting_extraInitializers);
            __esDecorate(_a, null, _environmentSkybox_decorators, { kind: "accessor", name: "environmentSkybox", static: false, private: false, access: { has: obj => "environmentSkybox" in obj, get: obj => obj.environmentSkybox, set: (obj, value) => { obj.environmentSkybox = value; } }, metadata: _metadata }, _environmentSkybox_initializers, _environmentSkybox_extraInitializers);
            __esDecorate(_a, null, _environmentIntensity_decorators, { kind: "accessor", name: "environmentIntensity", static: false, private: false, access: { has: obj => "environmentIntensity" in obj, get: obj => obj.environmentIntensity, set: (obj, value) => { obj.environmentIntensity = value; } }, metadata: _metadata }, _environmentIntensity_initializers, _environmentIntensity_extraInitializers);
            __esDecorate(_a, null, _environmentRotation_decorators, { kind: "accessor", name: "environmentRotation", static: false, private: false, access: { has: obj => "environmentRotation" in obj, get: obj => obj.environmentRotation, set: (obj, value) => { obj.environmentRotation = value; } }, metadata: _metadata }, _environmentRotation_initializers, _environmentRotation_extraInitializers);
            __esDecorate(_a, null, _shadowQuality_decorators, { kind: "accessor", name: "shadowQuality", static: false, private: false, access: { has: obj => "shadowQuality" in obj, get: obj => obj.shadowQuality, set: (obj, value) => { obj.shadowQuality = value; } }, metadata: _metadata }, _shadowQuality_initializers, _shadowQuality_extraInitializers);
            __esDecorate(_a, null, __loadingProgress_decorators, { kind: "accessor", name: "_loadingProgress", static: false, private: false, access: { has: obj => "_loadingProgress" in obj, get: obj => obj._loadingProgress, set: (obj, value) => { obj._loadingProgress = value; } }, metadata: _metadata }, __loadingProgress_initializers, __loadingProgress_extraInitializers);
            __esDecorate(_a, null, _skyboxBlur_decorators, { kind: "accessor", name: "skyboxBlur", static: false, private: false, access: { has: obj => "skyboxBlur" in obj, get: obj => obj.skyboxBlur, set: (obj, value) => { obj.skyboxBlur = value; } }, metadata: _metadata }, _skyboxBlur_initializers, _skyboxBlur_extraInitializers);
            __esDecorate(_a, null, _toneMapping_decorators, { kind: "accessor", name: "toneMapping", static: false, private: false, access: { has: obj => "toneMapping" in obj, get: obj => obj.toneMapping, set: (obj, value) => { obj.toneMapping = value; } }, metadata: _metadata }, _toneMapping_initializers, _toneMapping_extraInitializers);
            __esDecorate(_a, null, _contrast_decorators, { kind: "accessor", name: "contrast", static: false, private: false, access: { has: obj => "contrast" in obj, get: obj => obj.contrast, set: (obj, value) => { obj.contrast = value; } }, metadata: _metadata }, _contrast_initializers, _contrast_extraInitializers);
            __esDecorate(_a, null, _exposure_decorators, { kind: "accessor", name: "exposure", static: false, private: false, access: { has: obj => "exposure" in obj, get: obj => obj.exposure, set: (obj, value) => { obj.exposure = value; } }, metadata: _metadata }, _exposure_initializers, _exposure_extraInitializers);
            __esDecorate(_a, null, _ssao_decorators, { kind: "accessor", name: "ssao", static: false, private: false, access: { has: obj => "ssao" in obj, get: obj => obj.ssao, set: (obj, value) => { obj.ssao = value; } }, metadata: _metadata }, _ssao_initializers, _ssao_extraInitializers);
            __esDecorate(_a, null, _clearColor_decorators, { kind: "accessor", name: "clearColor", static: false, private: false, access: { has: obj => "clearColor" in obj, get: obj => obj.clearColor, set: (obj, value) => { obj.clearColor = value; } }, metadata: _metadata }, _clearColor_initializers, _clearColor_extraInitializers);
            __esDecorate(_a, null, _cameraAutoOrbit_decorators, { kind: "accessor", name: "cameraAutoOrbit", static: false, private: false, access: { has: obj => "cameraAutoOrbit" in obj, get: obj => obj.cameraAutoOrbit, set: (obj, value) => { obj.cameraAutoOrbit = value; } }, metadata: _metadata }, _cameraAutoOrbit_initializers, _cameraAutoOrbit_extraInitializers);
            __esDecorate(_a, null, _cameraAutoOrbitSpeed_decorators, { kind: "accessor", name: "cameraAutoOrbitSpeed", static: false, private: false, access: { has: obj => "cameraAutoOrbitSpeed" in obj, get: obj => obj.cameraAutoOrbitSpeed, set: (obj, value) => { obj.cameraAutoOrbitSpeed = value; } }, metadata: _metadata }, _cameraAutoOrbitSpeed_initializers, _cameraAutoOrbitSpeed_extraInitializers);
            __esDecorate(_a, null, _cameraAutoOrbitDelay_decorators, { kind: "accessor", name: "cameraAutoOrbitDelay", static: false, private: false, access: { has: obj => "cameraAutoOrbitDelay" in obj, get: obj => obj.cameraAutoOrbitDelay, set: (obj, value) => { obj.cameraAutoOrbitDelay = value; } }, metadata: _metadata }, _cameraAutoOrbitDelay_initializers, _cameraAutoOrbitDelay_extraInitializers);
            __esDecorate(_a, null, _hotSpots_decorators, { kind: "accessor", name: "hotSpots", static: false, private: false, access: { has: obj => "hotSpots" in obj, get: obj => obj.hotSpots, set: (obj, value) => { obj.hotSpots = value; } }, metadata: _metadata }, _hotSpots_initializers, _hotSpots_extraInitializers);
            __esDecorate(_a, null, _animationAutoPlay_decorators, { kind: "accessor", name: "animationAutoPlay", static: false, private: false, access: { has: obj => "animationAutoPlay" in obj, get: obj => obj.animationAutoPlay, set: (obj, value) => { obj.animationAutoPlay = value; } }, metadata: _metadata }, _animationAutoPlay_initializers, _animationAutoPlay_extraInitializers);
            __esDecorate(_a, null, _selectedAnimation_decorators, { kind: "accessor", name: "selectedAnimation", static: false, private: false, access: { has: obj => "selectedAnimation" in obj, get: obj => obj.selectedAnimation, set: (obj, value) => { obj.selectedAnimation = value; } }, metadata: _metadata }, _selectedAnimation_initializers, _selectedAnimation_extraInitializers);
            __esDecorate(_a, null, _animationSpeed_decorators, { kind: "accessor", name: "animationSpeed", static: false, private: false, access: { has: obj => "animationSpeed" in obj, get: obj => obj.animationSpeed, set: (obj, value) => { obj.animationSpeed = value; } }, metadata: _metadata }, _animationSpeed_initializers, _animationSpeed_extraInitializers);
            __esDecorate(_a, null, _animationProgress_decorators, { kind: "accessor", name: "animationProgress", static: false, private: false, access: { has: obj => "animationProgress" in obj, get: obj => obj.animationProgress, set: (obj, value) => { obj.animationProgress = value; } }, metadata: _metadata }, _animationProgress_initializers, _animationProgress_extraInitializers);
            __esDecorate(_a, null, __animations_decorators, { kind: "accessor", name: "_animations", static: false, private: false, access: { has: obj => "_animations" in obj, get: obj => obj._animations, set: (obj, value) => { obj._animations = value; } }, metadata: _metadata }, __animations_initializers, __animations_extraInitializers);
            __esDecorate(_a, null, __isAnimationPlaying_decorators, { kind: "accessor", name: "_isAnimationPlaying", static: false, private: false, access: { has: obj => "_isAnimationPlaying" in obj, get: obj => obj._isAnimationPlaying, set: (obj, value) => { obj._isAnimationPlaying = value; } }, metadata: _metadata }, __isAnimationPlaying_initializers, __isAnimationPlaying_extraInitializers);
            __esDecorate(_a, null, __showAnimationSlider_decorators, { kind: "accessor", name: "_showAnimationSlider", static: false, private: false, access: { has: obj => "_showAnimationSlider" in obj, get: obj => obj._showAnimationSlider, set: (obj, value) => { obj._showAnimationSlider = value; } }, metadata: _metadata }, __showAnimationSlider_initializers, __showAnimationSlider_extraInitializers);
            __esDecorate(_a, null, _selectedMaterialVariant_decorators, { kind: "accessor", name: "selectedMaterialVariant", static: false, private: false, access: { has: obj => "selectedMaterialVariant" in obj, get: obj => obj.selectedMaterialVariant, set: (obj, value) => { obj.selectedMaterialVariant = value; } }, metadata: _metadata }, _selectedMaterialVariant_initializers, _selectedMaterialVariant_extraInitializers);
            __esDecorate(_a, null, _camerasAsHotSpots_decorators, { kind: "accessor", name: "camerasAsHotSpots", static: false, private: false, access: { has: obj => "camerasAsHotSpots" in obj, get: obj => obj.camerasAsHotSpots, set: (obj, value) => { obj.camerasAsHotSpots = value; } }, metadata: _metadata }, _camerasAsHotSpots_initializers, _camerasAsHotSpots_extraInitializers);
            __esDecorate(_a, null, _resetMode_decorators, { kind: "accessor", name: "resetMode", static: false, private: false, access: { has: obj => "resetMode" in obj, get: obj => obj.resetMode, set: (obj, value) => { obj.resetMode = value; } }, metadata: _metadata }, _resetMode_initializers, _resetMode_extraInitializers);
            __esDecorate(_a, null, __canvasContainer_decorators, { kind: "accessor", name: "_canvasContainer", static: false, private: false, access: { has: obj => "_canvasContainer" in obj, get: obj => obj._canvasContainer, set: (obj, value) => { obj._canvasContainer = value; } }, metadata: _metadata }, __canvasContainer_initializers, __canvasContainer_extraInitializers);
            __esDecorate(_a, null, __hotSpotSelect_decorators, { kind: "accessor", name: "_hotSpotSelect", static: false, private: false, access: { has: obj => "_hotSpotSelect" in obj, get: obj => obj._hotSpotSelect, set: (obj, value) => { obj._hotSpotSelect = value; } }, metadata: _metadata }, __hotSpotSelect_initializers, __hotSpotSelect_extraInitializers);
            if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        })(),
        /** @internal */
        // eslint-disable-next-line @typescript-eslint/naming-convention
        _a.styles = i$4 `
        :host {
            --ui-foreground-color: white;
            --ui-background-hue: 233;
            --ui-background-saturation: 8%;
            --ui-background-lightness: 39%;
            --ui-background-opacity: 0.75;
            --ui-background-color: hsla(var(--ui-background-hue), var(--ui-background-saturation), var(--ui-background-lightness), var(--ui-background-opacity));
            --ui-background-color-hover: hsla(
                var(--ui-background-hue),
                var(--ui-background-saturation),
                calc(var(--ui-background-lightness) - 10%),
                calc(var(--ui-background-opacity) - 0.1)
            );
            all: inherit;
            overflow: hidden;
        }

        .full-size {
            display: block;
            position: relative;
            width: 100%;
            height: 100%;
        }

        .canvas {
            outline: none;
        }

        .children-slot {
            position: absolute;
            top: 0;
            background: transparent;
            pointer-events: none;
        }

        .reload-button {
            position: absolute;
            top: 50%;
            left: 50%;
            width: 25%;
            transform: translate(-50%, -50%);
            color: var(--ui-foreground-color);
            background-color: var(--ui-background-color);
            border: 1px solid transparent;
            border-radius: 24px;
            padding: 0;
            cursor: pointer;
            outline: none;
        }

        .reload-button:hover {
            background-color: var(--ui-background-color-hover);
        }

        .bar {
            position: absolute;
            width: calc(100% - 24px);
            min-width: 370px;
            max-width: 1280px;
            left: 50%;
            transform: translateX(-50%);
            background-color: var(--ui-background-color);
        }

        .bar-min {
            width: unset;
            min-width: unset;
            max-width: unset;
        }

        .loading-progress-outer {
            height: 4px;
            border-radius: 4px;
            border: 1px solid var(--ui-background-color);
            outline: none;
            top: 12px;
            pointer-events: none;
            transition: opacity 0.5s ease;
        }

        .loading-progress-outer-inactive {
            opacity: 0;
            /* Set the background color to the foreground color while in the inactive state so that the color seen is correct while fading out the opacity. */
            background-color: var(--ui-foreground-color);
        }

        .loading-progress-inner {
            width: 0;
            height: 100%;
            border-radius: inherit;
            background-color: var(--ui-foreground-color);
            transition: width 0.3s linear;
        }

        /* The right side of the inner progress bar starts aligned with the left side of the outer progress bar (container).
           So, if the width is 30%, then the left side of the inner progress bar moves a total of 130% of the width of the container.
           This is why the first keyframe is at 23% ((100/130)*30).
         */
        @keyframes indeterminate {
            0% {
                left: 0%;
                width: 0%;
            }
            23% {
                left: 0%;
                width: 30%;
            }
            77% {
                left: 70%;
                width: 30%;
            }
            100% {
                left: 100%;
                width: 0%;
            }
        }

        .loading-progress-inner-indeterminate {
            position: absolute;
            animation: indeterminate 1.5s infinite;
            animation-timing-function: linear;
        }

        .tool-bar {
            display: flex;
            flex-direction: row;
            align-items: center;
            border-radius: 12px;
            border-color: var(--ui-foreground-color);
            height: 48px;
            bottom: 12px;
            color: var(--ui-foreground-color);
            -webkit-tap-highlight-color: transparent;
        }

        .tool-bar * {
            height: 100%;
            min-width: 48px;
        }

        .tool-bar .divider {
            min-width: 1px;
            margin: 0px 6px;
            height: 66%;
            background-color: var(--ui-foreground-color);
        }

        .tool-bar select {
            background: none;
            min-width: 52px;
            max-width: 128px;
            border: 1px solid transparent;
            border-radius: inherit;
            color: inherit;
            font-size: 14px;
            padding: 0px 12px;
            cursor: pointer;
            outline: none;
            appearance: none; /* Remove default styling */
            -webkit-appearance: none; /* Remove default styling for Safari */
        }

        .tool-bar .select-container {
            position: relative;
            display: flex;
            border-radius: inherit;
            border-width: 0;
            padding: 0;
        }

        .tool-bar .select-container select {
            position: absolute;
            min-width: 0;
            width: 100%;
        }

        .tool-bar .select-container button {
            position: absolute;
            border-width: 0;
        }

        .tool-bar select:hover,
        .tool-bar select:focus {
            background-color: var(--ui-background-color-hover);
        }

        .tool-bar select option {
            background-color: var(--ui-background-color);
            color: var(--ui-foreground-color);
        }

        .tool-bar select:focus-visible {
            border-color: inherit;
        }

        .tool-bar button {
            background: none;
            border: 1px solid transparent;
            border-radius: inherit;
            color: inherit;
            padding: 0;
            cursor: pointer;
            outline: none;
        }

        .tool-bar button:hover {
            background-color: var(--ui-background-color-hover);
        }

        .tool-bar button:focus-visible {
            border-color: inherit;
        }

        .tool-bar button svg {
            width: 32px;
            height: 32px;
        }

        .animation-timeline {
            display: flex;
            flex: 1;
            position: relative;
            overflow: hidden;
            cursor: pointer;
            align-items: center;
            border-radius: inherit;
            border-color: inherit;
        }

        .animation-timeline-input {
            -webkit-appearance: none;
            cursor: pointer;
            width: 100%;
            height: 100%;
            outline: none;
            border: 1px solid transparent;
            border-radius: inherit;
            padding: 0 12px;
            background-color: transparent;
        }

        .animation-timeline-input:focus-visible {
            border-color: inherit;
        }

        /*Chrome -webkit */

        .animation-timeline-input::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            border: 2px solid;
            color: var(--ui-foreground-color);
            border-radius: 50%;
            background: hsla(var(--ui-background-hue), var(--ui-background-saturation), var(--ui-background-lightness), 1);
            margin-top: -10px;
        }

        .animation-timeline-input::-webkit-slider-runnable-track {
            height: 2px;
            -webkit-appearance: none;
            background-color: var(--ui-foreground-color);
        }

        /** FireFox -moz */

        .animation-timeline-input::-moz-range-progress {
            height: 2px;
            background-color: var(--ui-foreground-color);
        }

        .animation-timeline-input::-moz-range-thumb {
            width: 16px;
            height: 16px;
            border: 2px solid var(--ui-foreground-color);
            border-radius: 50%;
            background: hsla(var(--ui-background-hue), var(--ui-background-saturation), var(--ui-background-lightness), 1);
        }

        .animation-timeline-input::-moz-range-track {
            height: 2px;
            background: var(--ui-foreground-color);
        }
    `,
        _a;
})();

function ThrowLiteError(code, ...args) {
  const error = new Error(`#${code}`);
  error.lite = args;
  throw error;
}

const F32 = Float32Array;
const F64 = Float64Array;
const U32 = Uint32Array;
const I32 = Int32Array;
const U16 = Uint16Array;
const I16 = Int16Array;
const U8 = Uint8Array;
const I8 = Int8Array;
const U8C = Uint8ClampedArray;
const DV = DataView;

function _defaultAllocate() {
  return new F32(16);
}
let _allocate;
function allocateMat4() {
  return (_allocate ?? _defaultAllocate)();
}
function _setHpmAllocator(allocate) {
  _allocate = allocate;
}

const TU = globalThis.GPUTextureUsage;
const BU = globalThis.GPUBufferUsage;
const SS = globalThis.GPUShaderStage;
const CW = globalThis.GPUColorWrite;

const REVERSE_DEPTH_COMPARE = "greater-equal";
function targetSignatureKey(desc) {
  return `${desc._colorFormat ?? "-"}|${desc._depthStencilFormat ?? "-"}|${desc._depthCompare ?? ""}|${desc._sampleCount}`;
}
function createRenderTarget(descriptor) {
  return {
    _descriptor: descriptor,
    _colorTexture: null,
    _colorView: null,
    _depthTexture: null,
    _depthView: null,
    _width: 0,
    _height: 0
  };
}
function buildRenderTarget(rt, engine) {
  if (rt._eager) {
    return;
  }
  disposeRenderTarget(rt);
  const desc = rt._descriptor;
  const { width, height } = resolveSize(desc);
  rt._width = width;
  rt._height = height;
  const device = engine._device;
  const allocColor = !!desc.format;
  if (allocColor) {
    rt._colorTexture = device.createTexture({
      label: desc.lbl,
      size: { width, height },
      format: desc.format,
      sampleCount: desc.samples,
      usage: TU.RENDER_ATTACHMENT | TU.TEXTURE_BINDING | TU.COPY_SRC
    });
    rt._colorView = rt._colorTexture.createView();
  }
  if (desc.dFormat) {
    rt._depthTexture = device.createTexture({
      label: desc.lbl,
      size: { width, height },
      format: desc.dFormat,
      sampleCount: desc.samples,
      usage: TU.RENDER_ATTACHMENT | TU.TEXTURE_BINDING
    });
    rt._depthView = rt._depthTexture.createView();
  }
}
function disposeRenderTarget(rt) {
  if (!rt || rt._eager) {
    return;
  }
  if (rt._colorTexture) {
    rt._colorTexture.destroy();
    rt._colorTexture = null;
    rt._colorView = null;
  }
  if (rt._depthTexture) {
    if (rt._ownsDepthTexture !== false) {
      rt._depthTexture.destroy();
    }
    rt._depthTexture = null;
    rt._depthView = null;
  }
  rt._width = 0;
  rt._height = 0;
}
function resolveSize(desc) {
  const size = desc.size;
  if ("canvas" in size) {
    const canvas = size.canvas;
    return { width: canvas.width, height: canvas.height };
  }
  return size;
}

const VERSION = /* @__PURE__ */ (() => "1.24.0" )();
const _ENGINE_TAG = `Babylon Lite v${VERSION}`;

function isDomCanvas(canvas) {
  return "clientWidth" in canvas;
}
function toSrgbFormat(format) {
  return format.endsWith("-srgb") ? format : `${format}-srgb`;
}
let _nextSurfaceId = 1;
function _buildSurface(engine, canvas, options) {
  const context = canvas.getContext("webgpu");
  if (!context) {
    ThrowLiteError(48);
  }
  if (isDomCanvas(canvas)) {
    canvas.setAttribute("data-engine", _ENGINE_TAG);
  }
  const configureFormat = options?.format ?? navigator.gpu.getPreferredCanvasFormat();
  const renderFormat = options?.srgb ? toSrgbFormat(configureFormat) : configureFormat;
  const alphaMode = options?.alphaMode ?? "opaque";
  context.configure({ device: engine._device, format: configureFormat, alphaMode, viewFormats: [renderFormat] });
  const msaaSamples = options?.msaaSamples === 1 ? 1 : 4;
  const scRT = createRenderTarget({ lbl: "swapchain", format: renderFormat, samples: 1, size: { width: 0, height: 0 } });
  scRT._eager = true;
  return {
    engine,
    canvas,
    format: renderFormat,
    msaaSamples,
    scRT,
    maxDevicePixelRatio: options?.maxDevicePixelRatio ?? Infinity,
    _uniqueId: _nextSurfaceId++,
    _context: context,
    _configureFormat: configureFormat,
    _alphaMode: alphaMode,
    _renderingContexts: []
  };
}
function _refreshScRT(surface) {
  const tex = surface._context.getCurrentTexture();
  const swap = surface.scRT;
  swap._colorTexture = tex;
  swap._colorView = tex.createView({ format: surface.format });
  swap._width = tex.width;
  swap._height = tex.height;
}
function resizeSurface(surface) {
  const canvas = surface.canvas;
  if (!isDomCanvas(canvas)) {
    return;
  }
  const clientWidth = surface._w ?? canvas.clientWidth;
  const clientHeight = surface._h ?? canvas.clientHeight;
  if (!(clientWidth > 0 && clientHeight > 0)) {
    return;
  }
  const scale = Math.min(globalThis.devicePixelRatio || 1, surface.maxDevicePixelRatio);
  const w = clientWidth * scale | 0;
  const h = clientHeight * scale | 0;
  setSurfaceSize(surface, w, h);
}
function setSurfaceSize(surface, widthPx, heightPx) {
  const canvas = surface.canvas;
  const w = widthPx | 0;
  const h = heightPx | 0;
  if (!(w > 0 && h > 0)) {
    return;
  }
  if (w === canvas.width && h === canvas.height) {
    return;
  }
  canvas.width = w;
  canvas.height = h;
  surface.scRT._width = w;
  surface.scRT._height = h;
  for (const c of surface._renderingContexts) {
    c._resize?.();
  }
}

function runBatch(batch) {
  for (const retire of batch.splice(0)) {
    try {
      retire();
    } catch {
    }
  }
}
function retireGpuResources(engine, retirement) {
  (engine._retirements ??= []).push(retirement);
}
function flushGpuResourceRetirements(engine) {
  const batch = engine._retirements;
  if (!batch) {
    return;
  }
  engine._retirements = null;
  const inFlight = engine._retiring ??= [];
  inFlight.push(batch);
  queueMicrotask(() => {
    void engine._device.queue.onSubmittedWorkDone().then(() => {
      const index = inFlight.indexOf(batch);
      if (index >= 0) {
        inFlight.splice(index, 1);
      }
      runBatch(batch);
    }).catch(() => void 0);
  });
}
function disposeGpuResourceRetirements(engine) {
  const batch = engine._retirements;
  const inFlight = engine._retiring;
  engine._retirements = null;
  engine._retiring = null;
  if (batch) {
    runBatch(batch);
  }
  inFlight?.forEach(runBatch);
}

let _vis = 0;
function bumpVisibilityEpoch() {
  _vis = _vis + 1 | 0;
}
function isRenderingContextRegistered(surface, context) {
  return surface._renderingContexts.indexOf(context) !== -1;
}
function registerRenderingContext(surface, context) {
  if (surface._renderingContexts.indexOf(context) !== -1) {
    return false;
  }
  surface._renderingContexts.push(context);
  return true;
}
function unregisterRenderingContext(surface, context) {
  const list = surface._renderingContexts;
  const i = list.indexOf(context);
  if (i === -1) {
    return false;
  }
  list.splice(i, 1);
  return true;
}
async function createEngine(canvas, options) {
  const adapter = await navigator.gpu.requestAdapter({ powerPreference: "high-performance" });
  if (!adapter) {
    ThrowLiteError(46);
  }
  const features = [];
  for (const f of [
    "float32-filterable",
    "texture-compression-astc",
    "texture-compression-bc",
    "texture-compression-etc2",
    "timestamp-query",
    "primitive-index"
  ]) {
    if (adapter.features.has(f)) {
      features.push(f);
    }
  }
  const device = await adapter.requestDevice({ requiredFeatures: features, requiredLimits: options?.requiredLimits });
  console.log(`${_ENGINE_TAG} - WebGPU engine`);
  const useHpm = !!options?.useHighPrecisionMatrix;
  const useFO = !!options?.useFloatingOrigin;
  if (useFO && !useHpm) {
    ThrowLiteError(47);
  }
  if (useHpm) {
    const { allocateF64Mat4 } = await import('./_mat4-storage-f64-DHd1XfOQ.esm.js');
    _setHpmAllocator(allocateF64Mat4);
  }
  let _wrapRenderableForFO;
  let _makePackMeshWorld;
  let _lightFoVersion;
  let _applyLightFoOffset;
  if (useFO) {
    const [{ wrapRenderableForFO, lightFoVersion, applyLightFoOffset }, { makePackMeshWorld }] = await Promise.all([
      import('./floating-origin-BDU9WTLV.esm.js'),
      import('./pack-mat4-with-offset-BPdKUZol.esm.js')
    ]);
    _wrapRenderableForFO = wrapRenderableForFO;
    _makePackMeshWorld = makePackMeshWorld;
    _lightFoVersion = lightFoVersion;
    _applyLightFoOffset = applyLightFoOffset;
  }
  const engine = { _device: device };
  const surfaces = [engine];
  Object.assign(
    engine,
    {
      engine,
      // self-reference: the engine IS its primary surface
      surfaces,
      // public readonly view of `_surfaces` (same underlying array)
      _surfaces: surfaces,
      _device: device,
      _options: options,
      drawCallCount: 0,
      gpuFrameTimeMs: 0,
      useHighPrecisionMatrix: useHpm,
      useFloatingOrigin: useFO,
      _animFrameId: 0,
      _renderFn: null,
      _currentEncoder: void 0,
      _currentDelta: 0,
      _cbs: [],
      _wrapRenderableForFO,
      _makePackMeshWorld,
      _lightFoVersion,
      _applyLightFoOffset
    },
    _buildSurface(engine, canvas, options)
  );
  resizeSurface(engine);
  _refreshScRT(engine);
  return engine;
}
function resizeEngine(engine) {
  for (const surface of engine.surfaces) {
    resizeSurface(surface);
  }
}
function getRenderTargetSize(surface) {
  const c = surface.canvas;
  return { width: c.width, height: c.height };
}
function startEngine(engine) {
  return new Promise((resolve) => {
    let firstRafFrame = true;
    let lastTime = 0;
    engine._renderFn = (now) => {
      const delta = firstRafFrame ? 0 : lastTime > 0 ? now - lastTime : 16.667;
      lastTime = now;
      resizeEngine(engine);
      renderFrame(engine, delta);
      if (firstRafFrame) {
        firstRafFrame = false;
        resolve();
      }
      if (engine._renderFn) {
        engine._animFrameId = requestAnimationFrame(engine._renderFn);
      }
    };
    engine._animFrameId = requestAnimationFrame(engine._renderFn);
  });
}
function stopEngine(engine) {
  if (engine._animFrameId) {
    cancelAnimationFrame(engine._animFrameId);
  }
  engine._animFrameId = 0;
  engine._renderFn = null;
  flushGpuResourceRetirements(engine);
}
function disposeEngine(engine) {
  disposeGpuResourceRetirements(engine);
  stopEngine(engine);
  const surfaces = engine._surfaces;
  for (const s of surfaces) {
    s._renderingContexts.length = 0;
    s._ro?.disconnect();
    s._context.unconfigure();
  }
  surfaces.length = 0;
  engine._disposeStorageBuffers?.();
  engine._device.destroy();
}
function renderFrame(engine, delta) {
  const surfaces = engine.surfaces;
  let total = 0;
  for (let i = 0; i < surfaces.length; i++) {
    total += surfaces[i]._renderingContexts.length;
  }
  if (total === 0) {
    flushGpuResourceRetirements(engine);
    return;
  }
  const encoder = engine._device.createCommandEncoder({ label: "frame" });
  engine._currentEncoder = encoder;
  engine._currentDelta = delta;
  engine._gpuTimerBegin?.(encoder);
  let drawCalls = 0;
  for (let i = 0; i < surfaces.length; i++) {
    const surface = surfaces[i];
    surface._capturePreFrame?.(surface);
    _refreshScRT(surface);
    const ctxs = surface._renderingContexts;
    for (let j = 0; j < ctxs.length; j++) {
      const s = ctxs[j];
      s._update();
      drawCalls += s._drawCallsPre;
      drawCalls += s._record();
    }
  }
  const finalEncoder = engine._currentEncoder;
  for (let i = 0; i < surfaces.length; i++) {
    const surface = surfaces[i];
    surface._captureService?.(surface, finalEncoder);
  }
  engine._gpuTimerEnd?.(finalEncoder);
  engine._cbs[0] = finalEncoder.finish();
  engine._device.queue.submit(engine._cbs);
  flushGpuResourceRetirements(engine);
  engine.drawCallCount = drawCalls;
  engine._gpuTimerResolve?.();
}

function getState(engine) {
  return engine._deviceLostRecovery ??= {
    _forceNextLoss: false,
    _requiredFeatures: [],
    _armedDevice: null,
    _registrations: [],
    _samplerDescriptors: /* @__PURE__ */ new WeakMap(),
    _captureRefs: 0,
    _meshCaptureRefs: 0
  };
}
function _enableDeviceLostRecovery(engine, registration) {
  const state = getState(engine);
  const registrations = state._registrations;
  if (registrations.length === 0) {
    state._requiredFeatures = Array.from(engine._device.features);
  }
  if (!registrations.some((current) => current._kind === registration._kind)) {
    registration._enable?.(engine);
  }
  registrations.push(registration);
  arm(engine, state);
  let disabled = false;
  return {
    disable() {
      if (disabled) {
        return;
      }
      disabled = true;
      const index = registrations.indexOf(registration);
      if (index >= 0) {
        registrations.splice(index, 1);
      }
      if (!registrations.some((current) => current._kind === registration._kind)) {
        registration._disable?.(engine);
      }
    }
  };
}
function arm(engine, state) {
  const device = engine._device;
  if (state._armedDevice === device) {
    return;
  }
  state._armedDevice = device;
  void device.lost.then((info) => {
    if (state._registrations.length === 0 || state._armedDevice !== device) {
      return;
    }
    if (info.reason === "destroyed" && !state._forceNextLoss) {
      return;
    }
    state._forceNextLoss = false;
    const registrations = [...state._registrations];
    for (const registration of registrations) {
      registration._onLost?.(info);
    }
    void import('./device-lost-recovery-run-DrSWIIkF.esm.js').then(({ runDeviceLostRecovery }) => runDeviceLostRecovery(engine, state, registrations)).then(
      () => {
        arm(engine, state);
        for (const registration of registrations) {
          registration._onRecovered?.();
        }
      },
      (error) => {
        for (const registration of registrations) {
          registration._onRecoveryFailed?.(error);
        }
      }
    );
  });
}

function attachRecoveryCapture(engine) {
  const state = engine._deviceLostRecovery;
  engine._dlr = {
    u(tex, url, opts) {
      tex._recoverySource = { kind: "url", url, opts: { ...opts } };
    },
    s(tex, r, g, b, a) {
      tex._recoverySource = { kind: "solid", rgba: [r, g, b, a] };
    },
    b(tex, bitmap, srgb, mipMaps, fallback) {
      tex._recoverySource = {
        kind: "bitmap",
        bitmap,
        srgb,
        mipMaps,
        fallback
      };
    },
    p(tex, data, options) {
      tex._recoverySource = {
        kind: "pixels",
        data: data.slice(0, tex.width * tex.height * 4),
        width: tex.width,
        height: tex.height,
        options: { ...options }
      };
    },
    r(tex, width, height, format, samplerDesc) {
      tex._recoverySource = { kind: "render", width, height, format, samplerDesc };
    },
    w(tex, data, x, y, width, height, dataOffset = 0, bytesPerRow = width * 4) {
      const source = tex._recoverySource;
      if (source?.kind !== "pixels") {
        return;
      }
      const rowBytes = width * 4;
      for (let row = 0; row < height; row++) {
        const srcStart = dataOffset + row * bytesPerRow;
        const dstStart = ((y + row) * source.width + x) * 4;
        source.data.set(data.subarray(srcStart, srcStart + rowBytes), dstStart);
      }
    },
    m(mesh, uv2s, tangents, colors, gpuIndices, indexFormat) {
      if (!engine._deviceLostRecovery?._meshCaptureRefs) {
        return;
      }
      mesh._cpuUv2s = uv2s ?? null;
      mesh._cpuTangents = tangents ?? null;
      mesh._cpuColors = colors ?? null;
      mesh._cpuGpuIndices = gpuIndices;
      mesh._cpuIndexFormat = indexFormat;
    },
    e(scene, url, brdfUrl) {
      if (state._meshCaptureRefs) {
        scene._envRecoverySource = { kind: "env", url, brdfUrl };
      }
    },
    h(scene, url, faceSize) {
      if (state._meshCaptureRefs) {
        scene._envRecoverySource = { kind: "hdr", url, faceSize };
      }
    }
  };
}
function _retainDeviceLostRecoveryCapture(engine, includeMeshes = false) {
  const state = engine._deviceLostRecovery;
  if (!state) {
    ThrowLiteError(42);
  }
  state._captureRefs++;
  if (includeMeshes) {
    state._meshCaptureRefs++;
  }
  if (state._captureRefs === 1) {
    attachRecoveryCapture(engine);
  }
}
function _releaseDeviceLostRecoveryCapture(engine, includeMeshes = false) {
  const state = engine._deviceLostRecovery;
  if (!state || state._captureRefs === 0) {
    return;
  }
  state._captureRefs--;
  if (includeMeshes && state._meshCaptureRefs > 0) {
    state._meshCaptureRefs--;
  }
  if (state._captureRefs === 0) {
    engine._dlr = void 0;
  }
}

function enableDeviceLostSceneRecovery(engine, options = {}) {
  return _enableDeviceLostRecovery(engine, {
    _kind: "scene",
    _recoverOrder: 100,
    _enable(currentEngine) {
      _retainDeviceLostRecoveryCapture(currentEngine, true);
    },
    _disable(currentEngine) {
      _releaseDeviceLostRecoveryCapture(currentEngine, true);
    },
    async _recover(currentEngine) {
      const { rebuildRegisteredScenes } = await import('./recovery-rebuild-OAOymy7J.esm.js');
      await rebuildRegisteredScenes(currentEngine);
    },
    _onLost: options.onLost,
    _onRecovered: options.onRecovered,
    _onRecoveryFailed: options.onRecoveryFailed
  });
}

function retain(resource) {
  resource._refCount = (resource._refCount ?? 1) + 1;
}
function release(resource) {
  const count = resource._refCount;
  if (count === void 0 || count <= 1) {
    return true;
  }
  resource._refCount = count - 1;
  return false;
}

function buildRuntimeThinMesh(scene, mesh, pending, material = mesh.material) {
  return import('./scene-runtime-mesh-build-DIVoL4LH.esm.js').then((module) => module.A(scene, material, mesh, pending)).catch((error) => console.error(error));
}
function setThinInstances(mesh, matrices, count) {
  mesh._runtimeThinBuild = buildRuntimeThinMesh;
  if (!mesh.thinInstances) {
    mesh.thinInstances = {
      matrices,
      count,
      _capacity: count,
      _version: 1,
      _gpuBuffer: null,
      _gpuBufferStorage: false,
      _gpuVersion: 0,
      _dirtyMin: 0,
      _dirtyMax: count,
      _colorVersion: 0,
      _colorDirtyMin: 0,
      _colorDirtyMax: 0,
      _colorGpuBuffer: null,
      _colorGpuBufferStorage: false,
      _colorGpuVersion: 0,
      _gpuCullingEnabled: false
    };
  } else {
    mesh.thinInstances.matrices = matrices;
    mesh.thinInstances.count = count;
    mesh.thinInstances._capacity = count;
    mesh.thinInstances._version++;
    mesh.thinInstances._dirtyMin = 0;
    mesh.thinInstances._dirtyMax = count;
  }
}
function _detachThinInstanceLodMesh(mesh) {
  const ti = mesh.thinInstances;
  if (!ti) {
    return;
  }
  const source = ti._lodSource;
  const sourceTi = source?.thinInstances;
  if (source && sourceTi?._lodPartner === mesh) {
    sourceTi._lodPartner = null;
    sourceTi._lodDistance = void 0;
    sourceTi._lodBand = void 0;
    sourceTi._lodBuckets = null;
    source._clone = void 0;
  }
  if (ti._lodSource) {
    releaseLodConsumer(ti);
  }
  const partner = ti._lodPartner;
  const partnerTi = partner?.thinInstances;
  ti._lodPartner = null;
  ti._lodDistance = void 0;
  ti._lodBand = void 0;
  ti._lodBuckets = null;
  mesh._clone = void 0;
  if (partner && partnerTi?._lodSource === mesh) {
    partner._clone = void 0;
    releaseLodConsumer(partnerTi);
  }
}
function releaseLodConsumer(lodTi) {
  lodTi._lodSource = null;
  lodTi._lodBuckets = null;
  if (lodTi._lodAutoCull) {
    lodTi._lodAutoCull = false;
    lodTi._gpuCullingEnabled = false;
    lodTi._gpuVersion = -1;
    lodTi._colorGpuVersion = -1;
  }
}

function disposeMeshGpu(mesh) {
  if (mesh._disposed) {
    return;
  }
  mesh._disposed = true;
  const g = mesh._gpu;
  if (release(g)) {
    g.positionBuffer.destroy();
    g.normalBuffer.destroy();
    g.uvBuffer.destroy();
    g.indexBuffer.destroy();
    g.tangentBuffer?.destroy();
    g.uv2Buffer?.destroy();
    g.colorBuffer?.destroy();
  }
  const ti = mesh.thinInstances;
  if (ti && release(ti)) {
    _detachThinInstanceLodMesh(mesh);
    ti._gpuBuffer?.destroy();
    ti._colorGpuBuffer?.destroy();
    ti._drawArgsBuffer?.destroy();
  }
  const sk = mesh.skeleton;
  if (sk && release(sk)) {
    sk.boneTexture.destroy();
    if (release(sk._skinBuffers)) {
      sk.jointsBuffer.destroy();
      sk.weightsBuffer.destroy();
      sk.joints1Buffer?.destroy();
      sk.weights1Buffer?.destroy();
    }
  }
  const vat = mesh.vat;
  if (vat && release(vat)) {
    vat.settingsBuffer.destroy();
    vat.instanceTexture?.destroy();
    if (release(vat._textureResource)) {
      vat._textureResource.texture.destroy();
    }
    if (release(vat._skinBuffers)) {
      vat.jointsBuffer.destroy();
      vat.weightsBuffer.destroy();
      vat.joints1Buffer?.destroy();
      vat.weights1Buffer?.destroy();
    }
  }
  const mt = mesh.morphTargets;
  if (mt && release(mt)) {
    mt.deltasBuffer.destroy();
    mt.weightsBuffer.destroy();
  }
}

let _meshScenes = null;
function enqueueMaterialSwap(scene, mesh) {
  if (scene._materialSwapQueue.includes(mesh)) {
    return;
  }
  scene._materialSwapQueue.push(mesh);
}
function installMaterialSetter(mesh) {
  let _mat = mesh.material;
  Object.defineProperty(mesh, "material", {
    get() {
      return _mat;
    },
    set(v) {
      if (v !== _mat) {
        _mat = v;
        const scenes = _meshScenes?.get(mesh);
        if (scenes) {
          for (const scene of scenes) {
            enqueueMaterialSwap(scene, mesh);
          }
        }
      }
    },
    configurable: true,
    enumerable: true
  });
}
function registerMeshScene(scene, mesh) {
  if (mesh._disposed) {
    ThrowLiteError(371, mesh.name);
  }
  const map = _meshScenes ??= /* @__PURE__ */ new WeakMap();
  let scenes = map.get(mesh);
  if (!scenes) {
    map.set(mesh, scenes = /* @__PURE__ */ new Set());
    installMaterialSetter(mesh);
  }
  scenes.add(scene);
}
function unregisterMeshScene(scene, mesh) {
  const scenes = _meshScenes?.get(mesh);
  if (!scenes) {
    return true;
  }
  scenes.delete(scene);
  return scenes.size === 0;
}

function processMaterialSwaps(scene) {
  const q = scene._materialSwapQueue;
  if (!q[0] || scene._runtimeBuilds?.w) {
    return;
  }
  let changed;
  let pending;
  let firstBuilds;
  const renderables = scene._renderables;
  for (const mesh of q) {
    const mat = mesh.material;
    if (!mat) {
      continue;
    }
    const runtimeBuild = mesh._runtimeThinBuild;
    if (runtimeBuild) {
      pending = runtimeBuild(scene, mesh, pending);
      continue;
    }
    const group = scene._groups.get(mat._buildGroup);
    const rebuild = group?.r;
    if (!rebuild) {
      (firstBuilds ??= []).push(mesh);
      continue;
    }
    if (group._w?.(mesh)) {
      (firstBuilds ??= []).push([mesh, mat]);
      continue;
    }
    const old = scene._meshDisposables.get(mesh);
    if (old) {
      scene._meshDisposables.delete(mesh);
      retireGpuResources(scene.surface.engine, () => old.forEach((fn) => fn()));
    }
    const o = group.o;
    let dead;
    for (let i = renderables.length; i--; ) {
      if (renderables[i].mesh === mesh) {
        dead = renderables.splice(i, 1)[0];
      }
    }
    mat._csmGen = -~mat._csmGen;
    const built = rebuild(scene, mesh);
    if (o) {
      const oi = o.indexOf(dead);
      oi < 0 ? o.push(built) : o[oi] = built;
    }
    changed = renderables.push(built);
  }
  if (changed) {
    renderables.sort((a, b) => a.order - b.order);
    scene._renderableVersion++;
    scene._materialEpoch++;
  }
  q.length = 0;
  if (!firstBuilds) {
    return pending;
  }
  const builds = firstBuilds;
  return import('./scene-runtime-mesh-build-DIVoL4LH.esm.js').then(
    ({ C }) => C(scene, builds, pending),
    (error) => {
      const hooks = scene._runtimeBuilds;
      if (hooks) {
        hooks._x(error);
      } else {
        console.error(error);
      }
    }
  );
}

let _tickAnimationImpl = null;
function _setTickAnimationImpl(impl) {
  _tickAnimationImpl = impl;
}
function tickAnimation(group, deltaMs, engine) {
  _tickAnimationImpl?.(group, deltaMs, engine);
}

function createFrameGraph(_engine) {
  const fg = {
    _tasks: [],
    _currentProcessedTask: null,
    build() {
      for (let i = 0; i < fg._tasks.length; i++) {
        recordTask(fg, fg._tasks[i]);
      }
      for (let i = 0; i < fg._tasks.length; i++) {
        const passes = fg._tasks[i]._passes;
        for (let j = 0; j < passes.length; j++) {
          passes[j]._initialize();
        }
      }
    },
    execute() {
      let drawCalls = 0;
      for (const task of fg._tasks) {
        if (task.execute) {
          drawCalls += task.execute();
        } else {
          for (const pass of task._passes) {
            drawCalls += pass._execute();
          }
        }
      }
      return drawCalls;
    },
    dispose() {
      for (const task of fg._tasks) {
        task.dispose();
      }
      fg._tasks.length = 0;
      fg._currentProcessedTask = null;
    }
  };
  return fg;
}
function recordTask(fg, task) {
  task._passes.length = 0;
  fg._currentProcessedTask = task;
  try {
    task.record();
  } finally {
    fg._currentProcessedTask = null;
  }
}
function _appendTask(fg, task) {
  fg._tasks.push(task);
}

function mat4MultiplyInto(dst, d, a, i, b, j) {
  const a0 = a[i], a1 = a[i + 1], a2 = a[i + 2], a3 = a[i + 3];
  const a4 = a[i + 4], a5 = a[i + 5], a6 = a[i + 6], a7 = a[i + 7];
  const a8 = a[i + 8], a9 = a[i + 9], a10 = a[i + 10], a11 = a[i + 11];
  const a12 = a[i + 12], a13 = a[i + 13], a14 = a[i + 14], a15 = a[i + 15];
  let b0 = b[j], b1 = b[j + 1], b2 = b[j + 2], b3 = b[j + 3];
  dst[d] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3;
  dst[d + 1] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3;
  dst[d + 2] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3;
  dst[d + 3] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3;
  b0 = b[j + 4];
  b1 = b[j + 5];
  b2 = b[j + 6];
  b3 = b[j + 7];
  dst[d + 4] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3;
  dst[d + 5] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3;
  dst[d + 6] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3;
  dst[d + 7] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3;
  b0 = b[j + 8];
  b1 = b[j + 9];
  b2 = b[j + 10];
  b3 = b[j + 11];
  dst[d + 8] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3;
  dst[d + 9] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3;
  dst[d + 10] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3;
  dst[d + 11] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3;
  b0 = b[j + 12];
  b1 = b[j + 13];
  b2 = b[j + 14];
  b3 = b[j + 15];
  dst[d + 12] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3;
  dst[d + 13] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3;
  dst[d + 14] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3;
  dst[d + 15] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3;
}

function mat4PerspectiveLHToRef(out, fov, aspect, near, far) {
  const tan = 1 / Math.tan(fov * 0.5);
  const range = far - near;
  out[0] = tan / aspect;
  out[5] = tan;
  out[10] = -near / range;
  out[11] = 1;
  out[14] = far * near / range;
}

function getViewMatrix(camera) {
  const ver = camera.worldMatrixVersion;
  if (camera._viewVer === ver) {
    return camera._viewCache;
  }
  const v = camera._viewCache;
  const w = camera.worldMatrix;
  const useFO = camera._useFloatingOrigin;
  const cx = useFO ? 0 : w[12];
  const cy = useFO ? 0 : w[13];
  const cz = useFO ? 0 : w[14];
  v[0] = w[0];
  v[1] = w[4];
  v[2] = w[8];
  v[3] = 0;
  v[4] = w[1];
  v[5] = w[5];
  v[6] = w[9];
  v[7] = 0;
  v[8] = w[2];
  v[9] = w[6];
  v[10] = w[10];
  v[11] = 0;
  v[12] = -(w[0] * cx + w[1] * cy + w[2] * cz);
  v[13] = -(w[4] * cx + w[5] * cy + w[6] * cz);
  v[14] = -(w[8] * cx + w[9] * cy + w[10] * cz);
  v[15] = 1;
  camera._viewVer = ver;
  return v;
}
function _cameraChangeKey(camera) {
  if (camera._projFov !== camera.fov || camera._projNear !== camera.nearPlane || camera._projFar !== camera.farPlane) {
    camera._projFov = camera.fov;
    camera._projNear = camera.nearPlane;
    camera._projFar = camera.farPlane;
    camera._projRev = (camera._projRev ?? 0) + 1;
  }
  return camera.worldMatrixVersion + (camera._projRev ?? 0);
}
function getProjectionMatrix(camera, aspectRatio) {
  const ver = _cameraChangeKey(camera);
  if (camera._projVer === ver && camera._projAspect === aspectRatio) {
    return camera._projCache;
  }
  const p = camera._projCache;
  {
    mat4PerspectiveLHToRef(p, camera.fov, aspectRatio, camera.nearPlane, camera.farPlane);
  }
  camera._projVer = ver;
  camera._projAspect = aspectRatio;
  return p;
}
function getViewProjectionMatrix(camera, aspectRatio) {
  const ver = _cameraChangeKey(camera);
  if (camera._vpVer === ver && camera._vpAspect === aspectRatio) {
    return camera._vpCache;
  }
  const vp = camera._vpCache;
  mat4MultiplyInto(vp, 0, getProjectionMatrix(camera, aspectRatio), 0, getViewMatrix(camera), 0);
  camera._vpVer = ver;
  camera._vpAspect = aspectRatio;
  return vp;
}
function getCameraPosition(camera) {
  const w = camera.worldMatrix;
  return { x: w[12], y: w[13], z: w[14] };
}
function getEffectiveAspectRatio(camera, targetWidth, targetHeight) {
  const v = camera?.viewport;
  return targetWidth / targetHeight * (v ? v.width / v.height : 1);
}

let _cachedSceneBGL = null;
let _cachedDevice$1 = null;
function getSceneBindGroupLayout(engine) {
  const device = engine._device;
  if (_cachedSceneBGL && _cachedDevice$1 === device) {
    return _cachedSceneBGL;
  }
  _cachedDevice$1 = device;
  _cachedSceneBGL = device.createBindGroupLayout({
    label: "scene",
    entries: [
      { binding: 0, visibility: SS.VERTEX | SS.FRAGMENT, buffer: { type: "uniform" } },
      { binding: 1, visibility: SS.FRAGMENT, buffer: { type: "uniform" } }
    ]
  });
  return _cachedSceneBGL;
}
function clearSceneBGLCache() {
  _cachedSceneBGL = null;
  _cachedDevice$1 = null;
}
function createDefaultPipelineDescriptor(opts) {
  const target = opts._blend ? { format: opts._format, blend: opts._blend } : { format: opts._format };
  return {
    label: opts._label,
    layout: opts._engine._device.createPipelineLayout({ bindGroupLayouts: opts._bgls }),
    vertex: { module: opts._vertModule, entryPoint: "main", buffers: opts._vertexBuffers },
    fragment: { module: opts._fragModule, entryPoint: "main", targets: [target] },
    depthStencil: {
      format: opts._depthStencilFormat ?? "depth24plus-stencil8",
      depthCompare: opts._depthCompare ?? REVERSE_DEPTH_COMPARE,
      depthWriteEnabled: opts._depthWriteEnabled ?? true
    },
    multisample: { count: opts._msaaSamples },
    primitive: { topology: "triangle-list", cullMode: opts._cullMode ?? "back", frontFace: "ccw" }
  };
}

function packMat4IntoF32(view, mat, offsetFloats = 0, srcOffsetFloats = 0) {
  const src = mat;
  if (srcOffsetFloats === 0 && src.length === 16) {
    view.set(src, offsetFloats);
    return;
  }
  const s = srcOffsetFloats;
  const o = offsetFloats;
  view[o + 0] = src[s + 0];
  view[o + 1] = src[s + 1];
  view[o + 2] = src[s + 2];
  view[o + 3] = src[s + 3];
  view[o + 4] = src[s + 4];
  view[o + 5] = src[s + 5];
  view[o + 6] = src[s + 6];
  view[o + 7] = src[s + 7];
  view[o + 8] = src[s + 8];
  view[o + 9] = src[s + 9];
  view[o + 10] = src[s + 10];
  view[o + 11] = src[s + 11];
  view[o + 12] = src[s + 12];
  view[o + 13] = src[s + 13];
  view[o + 14] = src[s + 14];
  view[o + 15] = src[s + 15];
}

function _packSceneUniforms(data, eng, scene, camera, aspect) {
  data.fill(0);
  const viewProj = getViewProjectionMatrix(camera, aspect);
  const viewMat = getViewMatrix(camera);
  const wm = camera.worldMatrix;
  packMat4IntoF32(data, viewProj, 0);
  packMat4IntoF32(data, viewMat, 16);
  if (eng.useFloatingOrigin) {
    data[32] = 0;
    data[33] = 0;
    data[34] = 0;
  } else {
    data[32] = wm[12];
    data[33] = wm[13];
    data[34] = wm[14];
  }
  data[87] = eng.canvas.width;
  const envTextures = scene._envTextures;
  const img = scene.imageProcessing;
  data[76] = img.exposure;
  data[77] = img.contrast;
  data[78] = envTextures?.lodGenerationScale ?? 0.8;
  data[79] = +img.toneMappingEnabled;
  data[37] = eng.canvas.height;
}

function align(n, to) {
  return n + to - 1 & ~(to - 1);
}
function createUniformBuffer(engine, data, label) {
  const device = engine._device;
  const buf = device.createBuffer({
    label,
    size: align(data.byteLength, 16),
    usage: BU.UNIFORM | BU.COPY_DST
  });
  device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
  return buf;
}
function createEmptyUniformBuffer(engine, byteLength, label) {
  return engine._device.createBuffer({
    label,
    size: align(byteLength, 16),
    usage: BU.UNIFORM | BU.COPY_DST
  });
}
function createMappedBuffer(engine, data, usage, label) {
  const size = align(Math.max(data.byteLength, 4), 4);
  const buf = engine._device.createBuffer({
    label,
    size,
    usage: usage | BU.COPY_DST,
    mappedAtCreation: true
  });
  new U8(buf.getMappedRange()).set(new U8(data.buffer, data.byteOffset, data.byteLength));
  buf.unmap();
  return buf;
}

const SCENE_UBO_BYTES = 368;

let MAX_LIGHTS = 16;
function setMaxLights(n) {
  if (!Number.isFinite(n) || n < 1) {
    throw new Error(`setMaxLights: expected positive integer, got ${n}`);
  }
  MAX_LIGHTS = n | 0;
}
const LIGHT_ENTRY_FLOATS = 16;

const _countU32 = new U32(1);
const _countF32 = new F32(_countU32.buffer);
const MSH_LIGHT_INDEX_WORD_OFFSET = 20;
function meshLightIndexVec4Count() {
  return Math.ceil(MAX_LIGHTS / 4);
}
function getLightsUboSize() {
  return 16 + MAX_LIGHTS * LIGHT_ENTRY_FLOATS * 4;
}
function computeLightsVersion(lights) {
  let v = 0;
  for (const light of lights) {
    v += light._lightVersion ?? 0;
  }
  return v;
}
function fillLightsData(data, lights) {
  data.fill(0);
  let count = 0;
  const headerFloats = 4;
  for (const light of lights) {
    if (count >= MAX_LIGHTS) {
      break;
    }
    if (!light._writeLightUbo) {
      continue;
    }
    light._writeLightUbo(data, headerFloats + count * LIGHT_ENTRY_FLOATS);
    count++;
  }
  _countU32[0] = count;
  data[0] = _countF32[0];
}
function ensureSceneLightState(engine, scene) {
  let state = scene._lightGpuState;
  const byteSize = getLightsUboSize();
  if (state && state._byteSize === byteSize) {
    return state;
  }
  const registerDisposer = !state;
  state?._buffer.destroy();
  const scratch = new F32(byteSize / 4);
  fillLightsData(scratch, scene.lights);
  engine._applyLightFoOffset?.(scratch, scene);
  state = {
    _buffer: createUniformBuffer(engine, scratch),
    _scratch: scratch,
    _version: computeLightsVersion(scene.lights) + (engine._lightFoVersion?.(scene) ?? 0),
    _listVersion: scene._lightListVersion ?? 0,
    _lightCount: scene.lights.length,
    _byteSize: byteSize
  };
  scene._lightGpuState = state;
  if (registerDisposer) {
    scene._disposables.push(() => {
      scene._lightGpuState?._buffer.destroy();
      scene._lightGpuState = void 0;
    });
  }
  return state;
}
function refreshSceneLightsUBO(engine, scene) {
  const state = ensureSceneLightState(engine, scene);
  const version = computeLightsVersion(scene.lights) + (engine._lightFoVersion?.(scene) ?? 0);
  const listVersion = scene._lightListVersion ?? 0;
  if (version !== state._version || listVersion !== state._listVersion || scene.lights.length !== state._lightCount) {
    state._version = version;
    state._listVersion = listVersion;
    state._lightCount = scene.lights.length;
    fillLightsData(state._scratch, scene.lights);
    engine._applyLightFoOffset?.(state._scratch, scene);
    engine._device.queue.writeBuffer(state._buffer, 0, state._scratch);
  }
  return state._buffer;
}
function appendMeshLightUboFields(fields) {
  fields.push({ _name: "lc", _type: "u32" });
  fields.push({ _name: "li", _type: `array<vec4<u32>, ${meshLightIndexVec4Count()}>` });
}
function meshLightIndexWGSL(meshVar, functionName = "mli") {
  return `fn ${functionName}(i: u32) -> u32 { return ${meshVar}.li[i / 4u][i % 4u]; }`;
}
function affectsMesh(light, mesh) {
  const meshId = mesh.id;
  const included = light.includedOnlyMeshIds;
  if (included?.size) {
    return !!meshId && included.has(meshId);
  }
  return !meshId || !light.excludedMeshIds?.has(meshId);
}
function writeMeshLightSelection(mesh, lights, data) {
  const u32 = data ? new U32(data.buffer, data.byteOffset, data.byteLength / 4) : null;
  let count = 0;
  let single = -1;
  let pi = 0;
  for (const light of lights) {
    if (pi >= MAX_LIGHTS) {
      break;
    }
    if (!light._writeLightUbo) {
      continue;
    }
    if (affectsMesh(light, mesh)) {
      single = pi;
      if (u32) {
        u32[MSH_LIGHT_INDEX_WORD_OFFSET + count] = pi;
      }
      count++;
    }
    pi++;
  }
  if (u32) {
    u32[16] = count;
    for (let i = count; i < MAX_LIGHTS; i++) {
      u32[MSH_LIGHT_INDEX_WORD_OFFSET + i] = 0;
    }
  }
  return count === 1 ? single + 1 : -count;
}

function createRenderTask(config, engine, scene) {
  const sc = scene;
  config.clr ??= true;
  const desc = config.rt._descriptor;
  const targetSignature = {
    _colorFormat: desc.format,
    _depthStencilFormat: config.depth?._descriptor.dFormat ?? desc.dFormat,
    _depthCompare: desc._depthCompare,
    _sampleCount: desc.samples ?? 1
  };
  const sceneBGL = getSceneBindGroupLayout(engine);
  const sceneUBO = createEmptyUniformBuffer(engine, SCENE_UBO_BYTES);
  const lightsUBO = ensureSceneLightState(engine, sc)._buffer;
  const sceneBG = engine._device.createBindGroup({
    layout: sceneBGL,
    entries: [
      { binding: 0, resource: { buffer: sceneUBO } },
      { binding: 1, resource: { buffer: lightsUBO } }
    ]
  });
  const colorAttachment = { loadOp: "clear", storeOp: "store" };
  const updateContext = { targetWidth: 0, targetHeight: 0 };
  const autoMirror = config.autoMirror !== false;
  const ownsRt = !config.sharedRt;
  const task = {
    name: config.name,
    _config: config,
    engine,
    scene: sc,
    _passes: [],
    _renderables: [],
    _opaqueBindings: [],
    _directBindings: [],
    _transparentBindings: [],
    _ob: [],
    _lastVersion: -1,
    _lastVis: 0,
    _recorded: false,
    _renderPassDescriptor: { colorAttachments: [colorAttachment] },
    _colorAttachment: colorAttachment,
    _sceneUBO: sceneUBO,
    _sceneBG: sceneBG,
    _lightsUBO: lightsUBO,
    _suData: new F32(SCENE_UBO_BYTES / 4),
    _sceneUboCacheKey: [],
    _targetSignature: targetSignature,
    _updateBatches: [],
    _pendingMeshes: [],
    addMesh(mesh, opts) {
      const material = opts?.material ?? mesh.material;
      if (!material) {
        return;
      }
      task._pendingMeshes.push({ mesh, material });
      if (task._recorded) {
        resolvePendingMeshes(task, sc);
        task._af = false;
        buildBindings(task, engine, targetSignature);
      }
    },
    record() {
      if (task._af) {
        task._renderables.length = 0;
      }
      resolvePendingMeshes(task, sc);
      task._af = autoMirror && !task._renderables.length;
      if (task._af) {
        task._renderables.push(...sc._renderables);
      }
      const rt = config.rt;
      if (ownsRt) {
        buildRenderTarget(rt, engine);
        if (config.rst && (rt._descriptor.samples ?? 1) > 1) {
          buildRenderTarget(config.rst, engine);
        }
      }
      if (config.depth && !config.depth._eager) {
        buildRenderTarget(config.depth, engine);
      }
      updateContext.targetWidth = rt._width;
      updateContext.targetHeight = rt._height;
      refreshTaskSceneBindGroup(task, engine);
      buildBindings(task, engine, targetSignature);
      buildRenderPassDescriptor(task, rt);
      task._recorded = true;
    },
    execute() {
      return executePass(task, engine, targetSignature, updateContext);
    },
    dispose() {
      task._passes.length = task._opaqueBindings.length = task._directBindings.length = 0;
      task._transparentBindings.length = task._renderables.length = task._ob.length = 0;
      if (ownsRt) {
        disposeRenderTarget(config.rt);
        disposeRenderTarget(config.rst);
      }
      disposeRenderTarget(config.depth);
      task._sceneUBO.destroy();
      for (const batch of task._updateBatches) {
        batch.destroy();
      }
      task._updateBatches.length = 0;
    }
  };
  return task;
}
function removeMeshFromTask(task, mesh) {
  if (!task._renderables) {
    return;
  }
  let removed = false;
  for (let i = task._pendingMeshes.length - 1; i >= 0; i--) {
    if (task._pendingMeshes[i].mesh === mesh) {
      task._pendingMeshes.splice(i, 1);
      removed = true;
    }
  }
  for (let i = task._renderables.length - 1; i >= 0; i--) {
    if (task._renderables[i].mesh === mesh) {
      task._renderables.splice(i, 1);
      removed = true;
    }
  }
  for (const arr of [task._opaqueBindings, task._directBindings, task._transparentBindings]) {
    for (let i = arr.length - 1; i >= 0; i--) {
      if (arr[i].renderable.mesh === mesh) {
        arr.splice(i, 1);
        removed = true;
      }
    }
  }
  if (removed) {
    task._ob.length = 0;
    task._lastVersion = -1;
  }
}
function resolvePendingMeshes(task, sc) {
  if (!task._pendingMeshes.length) {
    return;
  }
  for (const { mesh, material } of task._pendingMeshes) {
    const builder = material._buildGroup;
    const group = sc._groups.get(builder);
    const rebuild = group ? group.r : builder._rebuildSingle;
    if (!rebuild) {
      throw Error();
    }
    const renderable = rebuild(sc, mesh, material);
    if (!task._renderables.includes(renderable)) {
      task._renderables.push(renderable);
    }
  }
  task._pendingMeshes.length = 0;
}
function sortTransparentBindings(task, camera) {
  const arr = task._transparentBindings;
  if (arr.length <= 1 || !camera) {
    return;
  }
  const v = getViewMatrix(camera);
  for (const b of arr) {
    const wc = b.renderable._worldCenter;
    b._sortDistance = wc ? wc[0] * v[2] + wc[1] * v[6] + wc[2] * v[10] + v[14] : 0;
  }
  arr.sort((a, b) => b._sortDistance - a._sortDistance || a.renderable.order - b.renderable.order);
}
function buildBindings(task, eng, targetSignature) {
  const opaque = task._opaqueBindings;
  const direct = task._directBindings;
  const transparent = task._transparentBindings;
  opaque.length = direct.length = transparent.length = 0;
  for (const r of task._renderables) {
    const binding = r.bind(eng, targetSignature);
    for (const batch of binding._updateBatches ?? []) {
      if (!task._updateBatches.includes(batch)) {
        task._updateBatches.push(batch);
      }
    }
    if (r.isTransparent || r._transmissive) {
      transparent.push(binding);
    } else if (r._direct) {
      direct.push(binding);
    } else {
      opaque.push(binding);
    }
  }
  opaque.sort((a, b) => a.renderable.order - b.renderable.order);
  direct.sort((a, b) => a.renderable.order - b.renderable.order);
  task._ob.length = 0;
  task._lastVersion = task.scene._renderableVersion;
}
function buildRenderPassDescriptor(task, rt) {
  const config = task._config;
  const att = task._colorAttachment;
  att.view = rt._colorView;
  att.resolveTarget = config.rst?._colorView ?? void 0;
  task._renderPassDescriptor.colorAttachments = rt._colorView ? [att] : [];
  const depthSrc = config.depth ?? rt;
  const depthView = depthSrc._depthView;
  let depthAttachment;
  if (depthView) {
    const dd = depthSrc._descriptor;
    const loadOp = (config.depth ? depthSrc._eager : config.depthClear === false) ? "load" : "clear";
    depthAttachment = {
      view: depthView,
      depthClearValue: dd._depthClearValue ?? 0,
      depthLoadOp: loadOp,
      depthStoreOp: "store"
    };
    if (dd.dFormat?.includes("stencil")) {
      depthAttachment.stencilClearValue = 0;
      depthAttachment.stencilLoadOp = loadOp;
      depthAttachment.stencilStoreOp = "store";
    }
  }
  task._renderPassDescriptor.depthStencilAttachment = depthAttachment;
}
function prepareRenderTaskPass(task, eng, targetSignature, context) {
  const sc = task.scene;
  if (task._af && task._lastVersion !== sc._renderableVersion) {
    task._renderables.length = 0;
    task._renderables.push(...sc._renderables);
    buildBindings(task, eng, targetSignature);
  }
  refreshTaskSceneBindGroup(task, eng);
  const camera = task._config.cam ?? sc.camera;
  if (targetSignature._colorFormat) {
    if (!task._config._skipClusteredLights) {
      sc._clusteredLightUpdater?.(camera, context.targetWidth, context.targetHeight);
    }
    refreshSceneLightsUBO(eng, sc);
  }
  _writePassSceneUBO(task, eng, sc, camera);
  context._camera = camera;
  for (const batch of task._updateBatches) {
    batch.reset();
  }
  updateBindings(task._opaqueBindings, context);
  updateBindings(task._directBindings, context);
  updateBindings(task._transparentBindings, context);
  for (const batch of task._updateBatches) {
    batch.flush(eng);
  }
  sortTransparentBindings(task, camera);
}
function executePass(task, eng, targetSignature, context) {
  if (task.enabled === false) {
    return 0;
  }
  const sc = task.scene;
  const sampleCount = targetSignature._sampleCount;
  prepareRenderTaskPass(task, eng, targetSignature, context);
  const att = task._colorAttachment;
  const cfg = task._config;
  if (cfg.rt._colorView) {
    if (cfg.rt === eng.scRT) {
      att.view = cfg.rt._colorView;
    }
    att.resolveTarget = cfg.rst?._colorView ?? void 0;
    att.clearValue = cfg.clrColor ?? sc.clearColor;
    att.loadOp = cfg.clr ? "clear" : "load";
  }
  if (task._executeWithTransmission) {
    return task._executeWithTransmission(sampleCount);
  }
  const pass = eng._currentEncoder.beginRenderPass(task._renderPassDescriptor);
  const draws = executePassBody(task, pass);
  pass.end();
  return draws;
}
function executePassBody(task, pass) {
  const eng = task.engine;
  const cfg = task._config;
  const rt = cfg.rt;
  const scene = task.scene;
  const opaqueBindings = task._opaqueBindings;
  const opaqueBundles = task._ob;
  const sceneBG = task._sceneBG;
  const camera = cfg.cam ?? scene.camera;
  const v = camera?.viewport;
  if (v) {
    const rw = rt._width;
    const rh = rt._height;
    const x = Math.floor(v.x * rw);
    const y = Math.floor((1 - v.y - v.height) * rh);
    const w = Math.ceil((v.x + v.width) * rw) - x;
    const h = Math.ceil((1 - v.y) * rh) - y;
    pass.setViewport(x, y, w, h, 0, 1);
    pass.setScissorRect(x, y, w, h);
  }
  pass.setBindGroup(0, sceneBG);
  if (task._lastVersion !== scene._renderableVersion || task._lastVis !== _vis || !opaqueBundles.length) {
    const desc = rt._descriptor;
    const be = eng._device.createRenderBundleEncoder({
      colorFormats: desc.format ? [desc.format] : [],
      // Use the task's target signature, not the RT descriptor: a depth
      // override (config.depth) supplies the depth format externally, so
      // the cached opaque pipelines are built with it while the colour RT
      // carries no depthStencilFormat of its own. The bundle encoder's
      // attachment state must match those pipelines exactly.
      depthStencilFormat: task._targetSignature._depthStencilFormat,
      sampleCount: desc.samples ?? 1
    });
    be.setBindGroup(0, sceneBG);
    drawList(be, opaqueBindings, eng);
    opaqueBundles[0] = be.finish();
    task._lastVersion = scene._renderableVersion;
    task._lastVis = _vis;
  }
  let draws = opaqueBindings.length;
  pass.executeBundles(opaqueBundles);
  pass.setBindGroup(0, sceneBG);
  draws += drawList(pass, task._directBindings, eng);
  draws += drawList(pass, task._transparentBindings, eng);
  return draws;
}
function refreshTaskSceneBindGroup(task, eng) {
  const lightsUBO = ensureSceneLightState(eng, task.scene)._buffer;
  if (lightsUBO === task._lightsUBO) {
    return;
  }
  task._lightsUBO = lightsUBO;
  task._sceneBG = eng._device.createBindGroup({
    layout: getSceneBindGroupLayout(eng),
    entries: [
      { binding: 0, resource: { buffer: task._sceneUBO } },
      { binding: 1, resource: { buffer: lightsUBO } }
    ]
  });
  task._ob.length = 0;
  task._lastVersion = -1;
}
function _writePassSceneUBO(task, eng, scene, camera) {
  if (!camera) {
    return;
  }
  const v = camera.viewport;
  const rt = task._config.rt;
  const aspect = (task._config.cs ? eng.canvas.width / eng.canvas.height : rt._width / rt._height) * (v ? v.width / v.height : 1);
  const fog = scene.fog;
  const img = scene.imageProcessing;
  const wv = _cameraChangeKey(camera);
  const envTextures = scene._envTextures;
  const s = task._sceneUboCacheKey;
  if (s[0] === camera && s[1] === fog && s[2] === wv && s[3] === aspect && s[4] === img.exposure && s[5] === img.contrast && s[6] === envTextures) {
    return;
  }
  s[0] = camera;
  s[1] = fog;
  s[2] = wv;
  s[3] = aspect;
  s[4] = img.exposure;
  s[5] = img.contrast;
  s[6] = envTextures;
  const data = task._suData;
  _packSceneUniforms(data, eng, scene, camera, aspect);
  const contribs = scene._sceneUboContributors;
  if (contribs) {
    for (const c of contribs) {
      c(data, scene);
    }
  }
  eng._device.queue.writeBuffer(task._sceneUBO, 0, data);
}
function updateBindings(list, context) {
  for (const b of list) {
    b.update?.(context);
  }
}
function drawList(enc, list, engine) {
  let lp = null;
  let draws = 0;
  for (const b of list) {
    const mesh = b.renderable.mesh;
    if (mesh && mesh.visible === false) {
      continue;
    }
    if (b.pipeline !== lp) {
      enc.setPipeline(b.pipeline);
      lp = b.pipeline;
    }
    draws += b.draw(enc, engine);
  }
  return draws;
}

let _lateCleanup = null;
function createSceneContext(surface, options) {
  const eng = surface.engine;
  const ctxLocal = {
    _kind: "scene",
    surface,
    clearColor: { r: 0.2, g: 0.2, b: 0.3, a: 1 },
    camera: null,
    lights: [],
    meshes: [],
    animationGroups: [],
    fog: null,
    clipPlane: null,
    shadowGenerators: [],
    imageProcessing: { exposure: 1, contrast: 1, toneMappingEnabled: false },
    _renderables: [],
    _prePasses: [],
    _pickSources: [],
    _uniformUpdaters: [],
    fixedDeltaMs: 0,
    _beforeRender: [],
    _deferredBuilders: [],
    _groups: /* @__PURE__ */ new Map(),
    _disposables: [],
    _meshDisposables: /* @__PURE__ */ new Map(),
    _meshAuxDisposables: /* @__PURE__ */ new Map(),
    _materialSwapQueue: [],
    _renderableVersion: 0,
    _materialEpoch: 0,
    _built: false,
    _drawCallsPre: 0,
    _update() {
      if (eng.useFloatingOrigin && ctx.camera && !ctx.camera._useFloatingOrigin) {
        ctx.camera._useFloatingOrigin = true;
        ctx.camera._viewVer = -1;
        ctx.camera._vpVer = -1;
      }
      const d = ctx.fixedDeltaMs > 0 ? ctx.fixedDeltaMs : eng._currentDelta;
      const encoder = eng._currentEncoder;
      let draws = 0;
      for (const cb of ctx._beforeRender) {
        cb(d);
      }
      if (ctx._materialSwapQueue.length) {
        void processMaterialSwaps(ctx);
      }
      for (const pp of ctx._prePasses) {
        draws += pp.execute(encoder, eng);
      }
      for (const u of ctx._uniformUpdaters) {
        u.update(eng);
      }
      ctx._drawCallsPre = draws;
    },
    _record() {
      return ctx._frameGraph.execute();
    },
    _resize() {
      ctx._frameGraph.build();
    }
  };
  const ctx = ctxLocal;
  const fg = createFrameGraph();
  ctx._frameGraph = fg;
  {
    const msaa = surface.msaaSamples > 1;
    const rt = msaa ? createRenderTarget({ lbl: "scene-color", format: surface.format, dFormat: "depth24plus-stencil8", samples: surface.msaaSamples, size: surface }) : surface.scRT;
    const depth = msaa ? void 0 : createRenderTarget({ lbl: "scene-depth", dFormat: "depth24plus-stencil8", samples: 1, size: surface });
    _appendTask(fg, createRenderTask({ name: "scene", rt, rst: msaa ? surface.scRT : void 0, depth }, eng, ctx));
  }
  ctx._disposables.push(() => fg.dispose());
  return ctx;
}
function onBeforeRender(scene, cb) {
  scene._beforeRender.unshift(cb);
}
function addToScene(scene, entity) {
  const ctx = scene;
  if ("entities" in entity) {
    const result = entity;
    for (const e of result.entities) {
      addToScene(scene, e);
    }
    if (result.clearColor) {
      ctx.clearColor = result.clearColor;
    }
    if (result.camera && !ctx.camera) {
      ctx.camera = result.camera;
    }
    if (result.animationGroups?.length) {
      const engine = ctx.surface.engine;
      const groups = result.animationGroups;
      ctx.animationGroups.push(...groups);
      const hook = (deltaMs) => {
        for (const g of groups) {
          tickAnimation(g, deltaMs, engine);
        }
      };
      result._beforeRenderHook = hook;
      ctx._beforeRender.push(hook);
    }
    result._sceneSetup?.(ctx, result);
    return;
  }
  if ("_gpu" in entity && "material" in entity) {
    const mesh = entity;
    registerMeshScene(ctx, mesh);
    ctx.meshes.push(mesh);
    const build = mesh.material ? mesh.material._buildGroup : void 0;
    if (build) {
      let group = ctx._groups.get(build);
      if (!group) {
        group = [];
        ctx._groups.set(build, group);
        if (!ctx._built) {
          ctx._deferredBuilders.push(async () => {
            const result = await build(ctx, group);
            ctx._renderables.push(...result.renderables);
            group.o = result.renderables;
            if (result.updater) {
              ctx._uniformUpdaters.push(result.updater);
            }
            group.r = result.rebuildSingle;
          });
        }
      }
      group.push(mesh);
      if (ctx._built || group.r) {
        enqueueMaterialSwap(ctx, mesh);
      }
    }
  } else if ("lightType" in entity) {
    ctx.lights.push(entity);
  }
  const kids = entity.children;
  if (kids?.length) {
    for (const child of kids) {
      child.parent = entity;
      addToScene(scene, child);
    }
  }
}
function disposeScene(scene) {
  const ctx = scene;
  if (ctx._z) {
    return;
  }
  ctx._z = true;
  const lateCleanup = _lateCleanup ??= /* @__PURE__ */ new WeakMap();
  lateCleanup.set(ctx, () => 1);
  unregisterRenderingContext(ctx.surface, ctx);
  const cleanup = () => {
    lateCleanup.set(ctx, () => {
      for (const fns of ctx._meshDisposables.values()) {
        fns.forEach((dispose) => dispose());
      }
      for (const fns of ctx._meshAuxDisposables.values()) {
        fns.forEach((dispose) => dispose());
      }
      ctx._meshDisposables.clear();
      ctx._meshAuxDisposables.clear();
      ctx._disposables.splice(0).forEach((dispose) => dispose());
      ctx._renderables.length = ctx._uniformUpdaters.length = 0;
      return 1;
    });
    for (const fn of ctx._disposables) {
      fn();
    }
    for (const fns of ctx._meshDisposables.values()) {
      for (const fn of fns) {
        fn();
      }
    }
    ctx._meshDisposables.clear();
    for (const fns of ctx._meshAuxDisposables.values()) {
      for (const fn of fns) {
        fn();
      }
    }
    ctx._meshAuxDisposables.clear();
    for (const mesh of ctx.meshes) {
      if (unregisterMeshScene(ctx, mesh)) {
        disposeMeshGpu(mesh);
      }
    }
    ctx._groups.clear();
    ctx.meshes.length = 0;
    ctx._renderables.length = 0;
    ctx._prePasses.length = 0;
    ctx._pickSources.length = 0;
    ctx._uniformUpdaters.length = 0;
    ctx._beforeRender.length = 0;
    ctx._deferredBuilders.length = 0;
    ctx._disposables.length = 0;
    ctx._materialSwapQueue.length = 0;
    ctx.lights.length = 0;
    ctx.animationGroups.length = 0;
    ctx.shadowGenerators.length = 0;
    ctx.camera = null;
  };
  cleanup();
}
async function buildScene(scene) {
  const ctx = scene;
  if (!ctx._built) {
    ctx._materialSwapQueue.length = 0;
  }
  while (ctx._deferredBuilders.length) {
    const builders = ctx._deferredBuilders.splice(0);
    await Promise.all(builders.map((b) => b()));
  }
  await processMaterialSwaps(ctx);
  _lateCleanup?.get(ctx)?.() || (ctx._runtimeBuilds?._e(), ctx._renderableVersion++, ctx._built = true);
  await ctx._rebuildHook?.(ctx);
}
async function registerScene(scene) {
  const ctx = scene;
  const surface = ctx.surface;
  if (isRenderingContextRegistered(surface, ctx)) {
    return;
  }
  await buildScene(scene);
  ctx._renderables.sort(byOrder$1);
  await Promise.all(ctx._frameGraph._tasks.map((task) => task._preload?.()));
  ctx._frameGraph.build();
  if (surface._renderingContexts[0]) {
    const overlay = await import('./swapchain-overlay-BCHeIw7u.esm.js');
    overlay.configureSwapchainOverlayScene(surface, ctx);
  }
  _lateCleanup?.get(ctx)?.() || registerRenderingContext(surface, ctx);
}
async function registerSceneWithShadowSupport(scene) {
  const ctx = scene;
  const surface = ctx.surface;
  if (isRenderingContextRegistered(surface, ctx)) {
    return;
  }
  await buildScene(scene);
  ctx._renderables.sort(byOrder$1);
  await ensureShadowTask(surface.engine, ctx);
  await Promise.all(ctx._frameGraph._tasks.map((task) => task._preload?.()));
  ctx._frameGraph.build();
  if (surface._renderingContexts[0]) {
    const overlay = await import('./swapchain-overlay-BCHeIw7u.esm.js');
    overlay.configureSwapchainOverlayScene(surface, ctx);
  }
  _lateCleanup?.get(ctx)?.() || registerRenderingContext(surface, ctx);
}
const byOrder$1 = (a, b) => a.order - b.order;
async function ensureShadowTask(engine, scene) {
  if (scene._frameGraph._tasks.some((task) => task.name === "shadow")) {
    return;
  }
  const { createShadowTask } = await import('./shadow-task-DsRCgXNg.esm.js');
  scene._frameGraph._tasks.unshift(createShadowTask(engine, scene));
}
function unregisterScene(scene) {
  unregisterRenderingContext(scene.surface, scene);
}

function mat4LookAtWorldLHToRef(out, eye, target, up) {
  out[3] = 0;
  out[7] = 0;
  out[11] = 0;
  out[12] = eye.x;
  out[13] = eye.y;
  out[14] = eye.z;
  out[15] = 1;
  let zx = target.x - eye.x;
  let zy = target.y - eye.y;
  let zz = target.z - eye.z;
  const zLen = Math.sqrt(zx * zx + zy * zy + zz * zz);
  let xx = 0;
  let xy = 0;
  let xz = 0;
  let xLen = 0;
  if (zLen >= 1e-10) {
    const invZ = 1 / zLen;
    zx *= invZ;
    zy *= invZ;
    zz *= invZ;
    xx = up.y * zz - up.z * zy;
    xy = up.z * zx - up.x * zz;
    xz = up.x * zy - up.y * zx;
    xLen = Math.sqrt(xx * xx + xy * xy + xz * xz);
  }
  if (xLen < 1e-10) {
    out[0] = 1;
    out[1] = 0;
    out[2] = 0;
    out[4] = 0;
    out[5] = 1;
    out[6] = 0;
    out[8] = 0;
    out[9] = 0;
    out[10] = 1;
    return;
  }
  const invX = 1 / xLen;
  xx *= invX;
  xy *= invX;
  xz *= invX;
  out[0] = xx;
  out[1] = xy;
  out[2] = xz;
  out[4] = zy * xz - zz * xy;
  out[5] = zz * xx - zx * xz;
  out[6] = zx * xy - zy * xx;
  out[8] = zx;
  out[9] = zy;
  out[10] = zz;
}

const Vec3Up = { x: 0, y: 1, z: 0 };

function mat4ComposeInto(dst, off, tx, ty, tz, qx, qy, qz, qw, sx, sy, sz) {
  const xx = qx * qx, yy = qy * qy, zz = qz * qz;
  const xy = qx * qy, xz = qx * qz, yz = qy * qz;
  const wx = qw * qx, wy = qw * qy, wz = qw * qz;
  dst[off] = (1 - 2 * (yy + zz)) * sx;
  dst[off + 1] = 2 * (xy + wz) * sx;
  dst[off + 2] = 2 * (xz - wy) * sx;
  dst[off + 3] = 0;
  dst[off + 4] = 2 * (xy - wz) * sy;
  dst[off + 5] = (1 - 2 * (xx + zz)) * sy;
  dst[off + 6] = 2 * (yz + wx) * sy;
  dst[off + 7] = 0;
  dst[off + 8] = 2 * (xz + wy) * sz;
  dst[off + 9] = 2 * (yz - wx) * sz;
  dst[off + 10] = (1 - 2 * (xx + yy)) * sz;
  dst[off + 11] = 0;
  dst[off + 12] = tx;
  dst[off + 13] = ty;
  dst[off + 14] = tz;
  dst[off + 15] = 1;
}

function mat4Compose(tx, ty, tz, qx, qy, qz, qw, sx, sy, sz) {
  const out = allocateMat4();
  mat4ComposeInto(out, 0, tx, ty, tz, qx, qy, qz, qw, sx, sy, sz);
  return out;
}

function mat4Identity() {
  const m = allocateMat4();
  m[0] = 1;
  m[5] = 1;
  m[10] = 1;
  m[15] = 1;
  return m;
}

const WM_STATE = Symbol("wmState");
function attachWorldMatrixState(host, state) {
  host[WM_STATE] = state;
}
function peekWorldMatrixState(p) {
  if (p === null) {
    return null;
  }
  const s = p[WM_STATE];
  return s ?? null;
}
function composeTrsLocalMatrix(position, rotation, scaling) {
  const isIdentity = position.x === 0 && position.y === 0 && position.z === 0 && rotation.x === 0 && rotation.y === 0 && rotation.z === 0 && rotation.w === 1 && scaling.x === 1 && scaling.y === 1 && scaling.z === 1;
  return isIdentity ? mat4Identity() : mat4Compose(position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, scaling.x, scaling.y, scaling.z);
}
function createWorldMatrixState(getLocalMatrix) {
  let _worldVersion = 0;
  let _lastSeenParentVersion = -1;
  let _cachedWorld = null;
  const _ownedWorld = allocateMat4();
  let _parent = null;
  let _parentState = null;
  const _children = [];
  function invalidate() {
    _cachedWorld = null;
    _worldVersion++;
    for (const child of _children) {
      child._invalidate();
    }
  }
  function pollForeignParent() {
    const pv = _parent.worldMatrixVersion;
    if (pv !== _lastSeenParentVersion) {
      _lastSeenParentVersion = pv;
      invalidate();
    }
  }
  const state = {
    get parent() {
      return _parent;
    },
    set parent(p) {
      if (p === _parent) {
        return;
      }
      if (_parentState !== null) {
        _parentState._removeChild(state);
      }
      _parent = p;
      _parentState = peekWorldMatrixState(p);
      if (_parentState !== null) {
        _parentState._addChild(state);
      }
      _lastSeenParentVersion = -1;
      invalidate();
    },
    markLocalDirty() {
      invalidate();
    },
    getWorldMatrix() {
      if (_parentState === null && _parent !== null) {
        pollForeignParent();
      }
      if (_cachedWorld !== null) {
        return _cachedWorld;
      }
      const local = getLocalMatrix();
      if (_parent !== null) {
        const pw = _parent.worldMatrix;
        mat4MultiplyInto(_ownedWorld, 0, pw, 0, local, 0);
        _cachedWorld = _ownedWorld;
      } else {
        _cachedWorld = local;
      }
      return _cachedWorld;
    },
    getWorldMatrixVersion() {
      if (_parentState === null && _parent !== null) {
        pollForeignParent();
      }
      return _worldVersion;
    },
    _invalidate() {
      invalidate();
    },
    _addChild(child) {
      _children.push(child);
    },
    _removeChild(child) {
      const i = _children.indexOf(child);
      if (i >= 0) {
        _children.splice(i, 1);
      }
    }
  };
  return state;
}

class ObservableVec3 {
  _x;
  _y;
  _z;
  _onDirty;
  constructor(x, y, z, onDirty) {
    this._x = x;
    this._y = y;
    this._z = z;
    this._onDirty = onDirty;
  }
  get x() {
    return this._x;
  }
  set x(v) {
    if (this._x !== v) {
      this._x = v;
      this._onDirty();
    }
  }
  get y() {
    return this._y;
  }
  set y(v) {
    if (this._y !== v) {
      this._y = v;
      this._onDirty();
    }
  }
  get z() {
    return this._z;
  }
  set z(v) {
    if (this._z !== v) {
      this._z = v;
      this._onDirty();
    }
  }
  /** Bulk set — one dirty notification instead of three. */
  set(x, y, z) {
    this._x = x;
    this._y = y;
    this._z = z;
    this._onDirty();
  }
  /** Copy values from another vector. */
  copyFrom(v) {
    this.set(v.x, v.y, v.z);
  }
  /** Copy into a Float32Array at offset. */
  toArray(out, offset = 0) {
    out[offset] = this._x;
    out[offset + 1] = this._y;
    out[offset + 2] = this._z;
  }
}

function createArcRotateCamera(alpha, beta, radius, target) {
  function localEyePosition() {
    const cosA = Math.cos(cam.alpha), sinA = Math.sin(cam.alpha);
    const cosB = Math.cos(cam.beta);
    let sinB = Math.sin(cam.beta);
    if (sinB === 0) {
      sinB = 1e-4;
    }
    return {
      x: cam.target.x + cam.radius * cosA * sinB,
      y: cam.target.y + cam.radius * cosB,
      z: cam.target.z + cam.radius * sinA * sinB
    };
  }
  const _localMat = allocateMat4();
  function cameraLocalWorldMatrix() {
    mat4LookAtWorldLHToRef(_localMat, localEyePosition(), cam.target, Vec3Up);
    return _localMat;
  }
  const wm = createWorldMatrixState(cameraLocalWorldMatrix);
  const onDirty = () => wm.markLocalDirty();
  const scalars = { alpha, beta, radius };
  const cam = {
    alpha: 0,
    // placeholder — overridden by defineProperty below
    beta: 0,
    radius: 0,
    target: new ObservableVec3(target.x, target.y, target.z, onDirty),
    fov: 0.8,
    nearPlane: 0.1,
    farPlane: 1e3,
    children: [],
    inertia: 0.9,
    panningInertia: 0.9,
    angularSensibility: 1e3,
    panningSensibility: 50,
    wheelPrecision: 3,
    inertialAlphaOffset: 0,
    inertialBetaOffset: 0,
    inertialRadiusOffset: 0,
    inertialPanningX: 0,
    inertialPanningY: 0,
    // Matrix caches use the process-global allocator — F32 by default,
    // F64 after an HPM engine is created. Same backing as the camera world
    // matrix above, so the camera's storage precision is uniform.
    _viewCache: allocateMat4(),
    _projCache: allocateMat4(),
    _vpCache: allocateMat4(),
    get parent() {
      return wm.parent;
    },
    set parent(v) {
      wm.parent = v;
    },
    get worldMatrix() {
      return wm.getWorldMatrix();
    },
    get worldMatrixVersion() {
      return wm.getWorldMatrixVersion();
    }
  };
  for (const key of ["alpha", "beta", "radius"]) {
    Object.defineProperty(cam, key, {
      get: () => scalars[key],
      set: (v) => {
        if (scalars[key] !== v) {
          scalars[key] = v;
          onDirty();
          cam._clampToLimits?.();
        }
      },
      configurable: true,
      enumerable: true
    });
  }
  attachWorldMatrixState(cam, wm);
  return cam;
}

function emptyWorldAabb() {
  return { minX: Infinity, minY: Infinity, minZ: Infinity, maxX: -Infinity, maxY: -Infinity, maxZ: -Infinity };
}
function addRange(acc, axis, center, radius) {
  const min = center - radius;
  const max = center + radius;
  const minKey = axis === 0 ? "minX" : axis === 1 ? "minY" : "minZ";
  const maxKey = axis === 0 ? "maxX" : axis === 1 ? "maxY" : "maxZ";
  if (min < acc[minKey]) {
    acc[minKey] = min;
  }
  if (max > acc[maxKey]) {
    acc[maxKey] = max;
  }
}
function expandWorldAabbForMesh(acc, mesh) {
  const bmin = mesh.boundMin;
  const bmax = mesh.boundMax;
  if (!bmin || !bmax) {
    return;
  }
  if (mesh._expandWorldBounds !== void 0) {
    mesh._expandWorldBounds(acc, mesh);
    return;
  }
  const world = mesh.worldMatrix;
  const center = [(bmin[0] + bmax[0]) * 0.5, (bmin[1] + bmax[1]) * 0.5, (bmin[2] + bmax[2]) * 0.5];
  const extent = [(bmax[0] - bmin[0]) * 0.5, (bmax[1] - bmin[1]) * 0.5, (bmax[2] - bmin[2]) * 0.5];
  for (let row = 0; row < 3; row++) {
    let transformedCenter = world[12 + row];
    let transformedRadius = 0;
    for (let column = 0; column < 3; column++) {
      const coefficient = world[column * 4 + row];
      transformedCenter += coefficient * center[column];
      transformedRadius += Math.abs(coefficient) * extent[column];
    }
    addRange(acc, row, transformedCenter, transformedRadius);
  }
}

function removeFromScene(scene, entity) {
  if ("entities" in entity) {
    const container = entity;
    for (const e of container.entities) {
      removeFromScene(scene, e);
    }
    if (container.camera && scene.camera === container.camera) {
      scene.camera = null;
    }
    const groups = container.animationGroups;
    if (groups?.length) {
      for (const g of groups) {
        spliceOut(scene.animationGroups, g);
      }
    }
    const hook = container._beforeRenderHook;
    if (hook) {
      spliceOut(scene._beforeRender, hook);
      container._beforeRenderHook = void 0;
    }
    const cleanups = container._sceneCleanups;
    if (cleanups) {
      const cleanup = cleanups.get(scene);
      if (cleanup) {
        spliceOut(scene._disposables, cleanup);
        cleanups.delete(scene);
        if (!scene._z) {
          cleanup();
        }
      }
    }
    return;
  }
  if ("_gpu" in entity && "material" in entity) {
    removeMeshFromScene(scene, entity);
    removeChildren(scene, entity);
    return;
  }
  if ("lightType" in entity) {
    spliceOut(scene.lights, entity);
    const sg = entity.shadowGenerator;
    if (sg) {
      disposeShadowGenerator(scene, sg);
    } else {
      markTopologyDirty(scene);
    }
  } else if ("fov" in entity && "nearPlane" in entity) {
    if (scene.camera === entity) {
      scene.camera = null;
    }
  } else if ("_shadowType" in entity && "_light" in entity) {
    disposeShadowGenerator(scene, entity);
  }
  detachParent(entity);
  removeChildren(scene, entity);
}
function retireMeshTeardown(scene, teardown) {
  for (const dispose of teardown) {
    const packet = dispose.p;
    if (packet) {
      packet._disposed = true;
      const owner = packet._owner;
      if (owner) {
        const index = owner.indexOf(packet);
        if (index >= 0) {
          owner.splice(index, 1);
        }
        packet._owner = void 0;
      }
    }
  }
  retireSceneGpu(scene, () => {
    for (const fn of teardown) {
      fn();
    }
  });
}
function retireSceneGpu(scene, teardown) {
  if (scene._z) {
    teardown();
    return;
  }
  retireGpuResources(scene.surface.engine, teardown);
}
function disposeShadowGenerator(scene, sg) {
  spliceOut(scene.shadowGenerators, sg);
  const light = sg._light;
  if (light && light.shadowGenerator === sg) {
    light.shadowGenerator = void 0;
  }
  markTopologyDirty(scene);
  const state = sg._shadowTaskState;
  if (state) {
    sg._shadowTaskState = void 0;
    queueTopologyRetirement(scene, () => state._task.dispose());
  }
}
function queueTopologyRetirement(scene, retirement) {
  if (!scene._pendingTopologyRetirements) {
    scene._pendingTopologyRetirements = [];
    scene._disposables.push(() => drainOnDispose(scene));
  }
  scene._pendingTopologyRetirements.push(retirement);
}
function drainOnDispose(scene) {
  const pending = scene._pendingTopologyRetirements?.splice(0);
  if (!pending?.length) {
    return;
  }
  const run = () => {
    for (const dispose of pending) {
      try {
        dispose();
      } catch {
      }
    }
  };
  const device = scene.surface.engine._device;
  if (device) {
    void device.queue.onSubmittedWorkDone().then(run, run);
  } else {
    run();
  }
}
function markTopologyDirty(scene) {
  scene._lightListVersion = (scene._lightListVersion ?? 0) + 1;
  if (scene._built) {
    scene._rebuildHook = rebuildOnNextBuild;
  }
}
async function rebuildOnNextBuild(scene) {
  const { rebuildSceneRenderables } = await Promise.resolve().then(function () { return sceneRebuild; });
  await rebuildSceneRenderables(scene);
}
function spliceOut(arr, item) {
  const i = arr.indexOf(item);
  if (i >= 0) {
    arr.splice(i, 1);
  }
}
function detachParent(node) {
  if (node && typeof node === "object" && "parent" in node) {
    node.parent = null;
  }
}
function removeChildren(scene, node) {
  const kids = node.children;
  if (kids?.length) {
    for (const child of [...kids]) {
      removeFromScene(scene, child);
    }
  }
}
function removeMeshFromScene(scene, mesh) {
  for (const task of scene._frameGraph._tasks) {
    task._removeMesh?.(mesh);
  }
  const fns = scene._meshDisposables.get(mesh);
  let didMutate = false;
  const teardown = [];
  if (fns) {
    didMutate = true;
    teardown.push(...fns);
    scene._meshDisposables.delete(mesh);
  }
  const auxFns = scene._meshAuxDisposables.get(mesh);
  if (auxFns) {
    didMutate = true;
    teardown.push(...auxFns);
    scene._meshAuxDisposables.delete(mesh);
  }
  const mi2 = scene.meshes.indexOf(mesh);
  if (mi2 >= 0) {
    scene.meshes.splice(mi2, 1);
    didMutate = true;
  }
  const i = scene._renderables.findIndex((r) => r.mesh === mesh);
  if (i >= 0) {
    scene._renderables.splice(i, 1);
    didMutate = true;
  }
  if (didMutate) {
    scene._renderableVersion++;
  }
  for (const group of scene._groups.values()) {
    const gi = group.indexOf(mesh);
    if (gi >= 0) {
      group.splice(gi, 1);
    }
  }
  const qi = scene._materialSwapQueue.indexOf(mesh);
  if (qi >= 0) {
    scene._materialSwapQueue.splice(qi, 1);
  }
  scene._runtimeBuilds?.remove(mesh);
  mesh.parent = null;
  for (const task of scene._frameGraph._tasks) {
    if ("_renderables" in task) {
      removeMeshFromTask(task, mesh);
    }
  }
  if (unregisterMeshScene(scene, mesh)) {
    teardown.push(() => disposeMeshGpu(mesh));
  }
  if (teardown.length) {
    retireMeshTeardown(scene, teardown);
  }
}

function writeEnvUbo(data, scene) {
  data[36] = scene._environmentRotation ?? 0;
  const sh = scene._envTextures?.sphericalHarmonics;
  if (sh) {
    data.set(sh, 40);
  }
}
function _registerSceneUboContributor(scene, contributor) {
  const list = scene._sceneUboContributors ??= [];
  if (!list.includes(contributor)) {
    list.push(contributor);
  }
}
function _invalidateSceneUboCaches(scene) {
  for (const task of scene._frameGraph._tasks) {
    if (task._sceneUboCacheKey) {
      task._sceneUboCacheKey.length = 0;
    }
  }
}
function registerEnvSceneUniforms(scene) {
  _registerSceneUboContributor(scene, writeEnvUbo);
}

const BASE_SAMPLE = /textureSampleLevel\(\s*([^,]+?)\s*,\s*([^,]+?)\s*,\s*([^,]+?)\s*,\s*0(?:\.0?)?\s*\)\.rgb/;
const _apply = (fragment, kind) => {
  const match = fragment.match(BASE_SAMPLE);
  const cubemap = match?.[1];
  const sampler = match?.[2];
  const direction = match?.[3];
  if (!cubemap || !sampler || !direction) {
    throw new Error("Environment blur: skybox cubemap sample not found.");
  }
  const lodTail = kind === "dds" ? "0.8" : "scene.vImageInfos.z+scene._envPad2";
  const lod = `clamp(scene._envPad1*log2(f32(textureDimensions(${cubemap}).x))*${lodTail},0.0,f32(textureNumLevels(${cubemap})-1))`;
  return fragment.replace(BASE_SAMPLE, `textureSampleLevel(${cubemap},${sampler},${direction},0.0+(${lod})).rgb`);
};

const environmentBlurSkyboxPatch = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
    __proto__: null,
    _apply
}, Symbol.toStringTag, { value: 'Module' }));

const sceneUniformsWgsl = "struct a{viewProjection:mat4x4<f32>,view:mat4x4<f32>,vEyePosition:vec4<f32>,envRotationY:f32,_envPad0:f32,_envPad1:f32,_envPad2:f32,vSphericalL00:vec4<f32>,vSphericalL1_1:vec4<f32>,vSphericalL10:vec4<f32>,vSphericalL11:vec4<f32>,vSphericalL2_2:vec4<f32>,vSphericalL2_1:vec4<f32>,vSphericalL20:vec4<f32>,vSphericalL21:vec4<f32>,vSphericalL22:vec4<f32>,vImageInfos:vec4<f32>,vFogInfos:vec4<f32>,vFogColor:vec4<f32>,clipPlane:vec4<f32>}@group(0) @binding(0) var<uniform> scene:a;";

const SCENE_UBO_WGSL = sceneUniformsWgsl;

function _registerEnvironmentSkyboxShaderPatch(scene, order, loadPatch) {
  const loaders = scene._environmentSkyboxShaderPatchLoaders ??= [];
  loaders[order] = loadPatch;
  scene._environmentSkyboxShaderComposer ??= async (fragment, kind) => {
    fragment = kind === "hdr" ? SCENE_UBO_WGSL + fragment : fragment;
    for (const load of loaders.slice()) {
      if (load) {
        fragment = (await load())._apply(fragment, kind);
      }
    }
    return fragment;
  };
}

const loadBlurSkyboxPatch = () => environmentBlurSkyboxPatch;
function writeEnvironmentBlurUbo(data, scene) {
  data[38] = scene._environmentBlur ?? 0;
  data[39] = scene._envTextures?.lodGenerationOffset ?? 0;
}
function setEnvironmentBlur(scene, blur) {
  scene._environmentBlur = blur;
  _registerEnvironmentSkyboxShaderPatch(scene, 1, loadBlurSkyboxPatch);
  _registerSceneUboContributor(scene, writeEnvironmentBlurUbo);
  _invalidateSceneUboCaches(scene);
}

const loadRotationSkyboxPatch = () => import('./environment-rotation-fragment-YlVv5-fm.esm.js');
function setEnvironmentRotation(scene, rotation) {
  registerEnvSceneUniforms(scene);
  scene._environmentRotation = rotation;
  _registerEnvironmentSkyboxShaderPatch(scene, 0, loadRotationSkyboxPatch);
  _invalidateSceneUboCaches(scene);
}

let _texRefs = null;
function texRefs() {
  if (!_texRefs) {
    _texRefs = /* @__PURE__ */ new WeakMap();
  }
  return _texRefs;
}
function acquireTexture(tex) {
  const m = texRefs();
  m.set(tex.texture, (m.get(tex.texture) ?? 0) + 1);
}
function releaseTexture(tex) {
  const m = texRefs();
  const c = (m.get(tex.texture) ?? 1) - 1;
  if (c <= 0) {
    tex.texture.destroy();
    m.delete(tex.texture);
    return true;
  }
  m.set(tex.texture, c);
  return false;
}
function acquireGPUTexture(tex) {
  const m = texRefs();
  m.set(tex, (m.get(tex) ?? 0) + 1);
}
function releaseGPUTexture(tex) {
  const m = texRefs();
  const c = (m.get(tex) ?? 1) - 1;
  if (c <= 0) {
    tex.destroy();
    m.delete(tex);
    return true;
  }
  m.set(tex, c);
  return false;
}
let _samplerCache = null;
function samplerKey(desc) {
  return `${desc.minFilter ?? "nearest"}:${desc.magFilter ?? "nearest"}:${desc.mipmapFilter ?? "nearest"}:${desc.addressModeU ?? "clamp-to-edge"}:${desc.addressModeV ?? "clamp-to-edge"}:${desc.addressModeW ?? "clamp-to-edge"}:${desc.maxAnisotropy ?? 1}`;
}
function getOrCreateSampler(engine, desc = {}) {
  const device = engine._device;
  if (!_samplerCache) {
    _samplerCache = /* @__PURE__ */ new WeakMap();
  }
  let dc = _samplerCache.get(device);
  if (!dc) {
    dc = /* @__PURE__ */ new Map();
    _samplerCache.set(device, dc);
  }
  const key = samplerKey(desc);
  let s = dc.get(key);
  if (!s) {
    s = device.createSampler(desc);
    dc.set(key, s);
  }
  return s;
}
function clearSamplerCache(engine) {
  const device = engine._device;
  _samplerCache?.delete(device);
}

const _bilinearDesc = { magFilter: "linear", minFilter: "linear" };
const _trilinearDesc = { magFilter: "linear", minFilter: "linear", mipmapFilter: "linear" };
function getBilinearSampler(engine) {
  return getOrCreateSampler(engine, _bilinearDesc);
}
function getTrilinearSampler(engine) {
  return getOrCreateSampler(engine, _trilinearDesc);
}

function mipLevelCount(width, height) {
  return Math.floor(Math.log2(Math.max(width, height))) + 1;
}
function biasedMipLevelCount(width, height, lodBias) {
  const maxDim = Math.max(width, height);
  return Math.max(1, Math.floor(Math.log2(maxDim) - lodBias) + 1);
}

function mat4Invert(input) {
  const m = input;
  const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
  const a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
  const a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
  const a30 = m[12], a31 = m[13], a32 = m[14], a33 = m[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;
  let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
  if (Math.abs(det) < 1e-10) {
    return null;
  }
  det = 1 / det;
  const out = allocateMat4();
  out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
  out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
  out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
  out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
  out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
  out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
  out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
  out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
  out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
  out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
  out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
  out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
  out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
  out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
  out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
  out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
  return out;
}

function attachControl(camera, canvas, scene, options) {
  const ROTATION_EPSILON = 1e-3;
  const RADIUS_EPSILON = 1e-3;
  const PANNING_EPSILON = 1e-4;
  let isDragging = false;
  let isPanning = false;
  let lastX = 0;
  let lastY = 0;
  const activeTouches = /* @__PURE__ */ new Map();
  let pinchStartDist = 0;
  let pinchStartRadius = 0;
  function onPointerDown(e) {
    canvas.setPointerCapture(e.pointerId);
    lastX = e.clientX;
    lastY = e.clientY;
    if (e.button === 0) {
      isDragging = true;
      isPanning = false;
    } else if (e.button === 2) {
      isDragging = false;
      isPanning = true;
    }
  }
  function onPointerMove(e) {
    const dx = e.clientX - lastX;
    const dy = e.clientY - lastY;
    lastX = e.clientX;
    lastY = e.clientY;
    if (activeTouches.size >= 2) {
      return;
    }
    if (!isDragging && !isPanning) {
      return;
    }
    if (isDragging) {
      const angularSensibility = camera.angularSensibility;
      camera.inertialAlphaOffset -= dx / angularSensibility;
      camera.inertialBetaOffset -= dy / angularSensibility;
    }
    if (isPanning) {
      const panningSensibility = camera.panningSensibility;
      camera.inertialPanningX += -dx / panningSensibility;
      camera.inertialPanningY += dy / panningSensibility;
    }
  }
  function onPointerUp(e) {
    canvas.releasePointerCapture(e.pointerId);
    isDragging = false;
    isPanning = false;
  }
  function onWheel(e) {
    e.preventDefault();
    camera.inertialRadiusOffset -= e.deltaY * camera.radius / (camera.wheelPrecision * 1e3);
  }
  function onContextMenu(e) {
    e.preventDefault();
  }
  function onTouchStart(e) {
    for (let i = 0; i < e.changedTouches.length; i++) {
      const touch = e.changedTouches[i];
      activeTouches.set(touch.identifier, { x: touch.clientX, y: touch.clientY });
    }
    if (activeTouches.size >= 2) {
      isDragging = false;
      isPanning = false;
      const iter = activeTouches.values();
      const p0 = iter.next().value;
      const p1 = iter.next().value;
      pinchStartDist = Math.hypot(p1.x - p0.x, p1.y - p0.y);
      pinchStartRadius = camera.radius;
      e.preventDefault();
    }
  }
  function onTouchMove(e) {
    for (let i = 0; i < e.changedTouches.length; i++) {
      const touch = e.changedTouches[i];
      activeTouches.set(touch.identifier, { x: touch.clientX, y: touch.clientY });
    }
    if (activeTouches.size >= 2) {
      e.preventDefault();
      const iter = activeTouches.values();
      const p0 = iter.next().value;
      const p1 = iter.next().value;
      const dist = Math.hypot(p1.x - p0.x, p1.y - p0.y);
      if (pinchStartDist > 0 && dist > 0) {
        camera.radius = pinchStartRadius * (pinchStartDist / dist);
        camera.radius = Math.max(0.01, camera.radius);
      }
    }
  }
  function onTouchEnd(e) {
    for (let i = 0; i < e.changedTouches.length; i++) {
      activeTouches.delete(e.changedTouches[i].identifier);
    }
    if (activeTouches.size === 1) {
      const p = activeTouches.values().next().value;
      lastX = p.x;
      lastY = p.y;
    }
    if (activeTouches.size < 2) {
      pinchStartDist = 0;
    }
  }
  function onGesture(e) {
    e.preventDefault();
  }
  function applyInertia() {
    if (camera.inertialAlphaOffset !== 0 || camera.inertialBetaOffset !== 0) {
      camera.alpha += camera.inertialAlphaOffset;
      camera.beta += camera.inertialBetaOffset;
      const eps = 0.01;
      camera.beta = Math.max(eps, Math.min(Math.PI - eps, camera.beta));
      camera.inertialAlphaOffset *= camera.inertia;
      camera.inertialBetaOffset *= camera.inertia;
      if (Math.abs(camera.inertialAlphaOffset) < ROTATION_EPSILON) {
        camera.inertialAlphaOffset = 0;
      }
      if (Math.abs(camera.inertialBetaOffset) < ROTATION_EPSILON) {
        camera.inertialBetaOffset = 0;
      }
    }
    if (camera.inertialRadiusOffset !== 0) {
      camera.radius -= camera.inertialRadiusOffset;
      camera.radius = Math.max(0.01, camera.radius);
      camera.inertialRadiusOffset *= camera.inertia;
      if (Math.abs(camera.inertialRadiusOffset) < RADIUS_EPSILON) {
        camera.inertialRadiusOffset = 0;
      }
    }
    if (camera.inertialPanningX !== 0 || camera.inertialPanningY !== 0) {
      const cosA = Math.cos(camera.alpha);
      const sinA = Math.sin(camera.alpha);
      const rightX = -sinA;
      const rightZ = cosA;
      const panScale = camera.radius * 1e-3;
      camera.target.x += rightX * camera.inertialPanningX * panScale;
      camera.target.y += camera.inertialPanningY * panScale;
      camera.target.z += rightZ * camera.inertialPanningX * panScale;
      camera.inertialPanningX *= camera.panningInertia;
      camera.inertialPanningY *= camera.panningInertia;
      if (Math.abs(camera.inertialPanningX) < PANNING_EPSILON) {
        camera.inertialPanningX = 0;
      }
      if (Math.abs(camera.inertialPanningY) < PANNING_EPSILON) {
        camera.inertialPanningY = 0;
      }
    }
  }
  if (scene) {
    scene._beforeRender.push(applyInertia);
  }
  const listeners = [
    ["pointerdown", onPointerDown],
    ["pointermove", onPointerMove],
    ["pointerup", onPointerUp],
    ["wheel", onWheel, { passive: false }],
    ["contextmenu", onContextMenu],
    ["touchstart", onTouchStart, { passive: false }],
    ["touchmove", onTouchMove, { passive: false }],
    ["touchend", onTouchEnd],
    ["gesturestart", onGesture, { passive: false }],
    ["gesturechange", onGesture, { passive: false }],
    ["gestureend", onGesture, { passive: false }]
  ];
  for (const [ev, h, opts] of listeners) {
    canvas.addEventListener(ev, h, opts);
  }
  return () => {
    if (scene) {
      const idx = scene._beforeRender.indexOf(applyInertia);
      if (idx >= 0) {
        scene._beforeRender.splice(idx, 1);
      }
    }
    for (const [ev, h] of listeners) {
      canvas.removeEventListener(ev, h);
    }
  };
}

function toError(reason) {
  return reason instanceof Error ? reason : new Error(String(reason));
}
const RetiredDriver = () => {
};
function runFrameInterpolation(scene, step, signal) {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) {
      reject(toError(signal.reason));
      return;
    }
    let settled = false;
    let onAbort;
    const finish = () => {
      if (settled) {
        return;
      }
      settled = true;
      const list = scene._beforeRender;
      const index = list.indexOf(driver);
      if (index >= 0) {
        list[index] = RetiredDriver;
        while (list.length > 0 && list[list.length - 1] === RetiredDriver) {
          list.pop();
        }
      }
      if (onAbort && signal) {
        signal.removeEventListener("abort", onAbort);
      }
    };
    const driver = (deltaMs) => {
      if (settled) {
        return;
      }
      const deltaSeconds = (deltaMs > 0 ? deltaMs : 1e3 / 60) / 1e3;
      let shouldContinue;
      try {
        shouldContinue = step(deltaSeconds);
      } catch (error) {
        finish();
        reject(toError(error));
        return;
      }
      if (!shouldContinue) {
        finish();
        resolve();
      }
    };
    if (signal) {
      onAbort = () => {
        finish();
        reject(toError(signal.reason));
      };
      signal.addEventListener("abort", onAbort);
    }
    scene._beforeRender.push(driver);
  });
}

function expDampFactor(deltaSeconds, factor) {
  if (deltaSeconds <= 0) {
    return 0;
  }
  return 1 - Math.pow(2, -deltaSeconds / factor);
}
function dampScalar(current, goal, t) {
  return current + (goal - current) * t;
}
function lerpAngleShortest(current, goal, t) {
  const twoPi = Math.PI * 2;
  let delta = (goal - current) % twoPi;
  if (delta > Math.PI) {
    delta -= twoPi;
  } else if (delta < -Math.PI) {
    delta += twoPi;
  }
  return current + delta * t;
}

function lerpVec3ToRef(a, b, t, out) {
  out.x = a.x + (b.x - a.x) * t;
  out.y = a.y + (b.y - a.y) * t;
  out.z = a.z + (b.z - a.z) * t;
  return out;
}

const DefaultInterpolationFactor = 0.1;
const TerminationEpsilon = 1e-3;
function clampToLimit(value, lower, upper) {
  if (lower !== void 0 && value < lower) {
    return lower;
  }
  if (upper !== void 0 && value > upper) {
    return upper;
  }
  return value;
}
function interpolateArcRotateCamera(camera, scene, goal, signal, options) {
  const factor = DefaultInterpolationFactor;
  const hasAlpha = goal.alpha !== void 0 && !isNaN(goal.alpha);
  const hasBeta = goal.beta !== void 0 && !isNaN(goal.beta);
  const hasRadius = goal.radius !== void 0 && !isNaN(goal.radius);
  const hasTarget = goal.target !== void 0;
  let goalAlpha = 0;
  let goalBeta = 0;
  let goalRadius = 0;
  const goalTarget = { x: 0, y: 0, z: 0 };
  let lastAlpha = 0;
  let lastBeta = 0;
  let lastRadius = 0;
  const lastTarget = { x: 0, y: 0, z: 0 };
  let first = true;
  const step = (deltaSeconds) => {
    if (first) {
      first = false;
      camera.inertialAlphaOffset = 0;
      camera.inertialBetaOffset = 0;
      camera.inertialRadiusOffset = 0;
      camera.inertialPanningX = 0;
      camera.inertialPanningY = 0;
      goalAlpha = hasAlpha ? goal.alpha : camera.alpha;
      goalBeta = hasBeta ? goal.beta : camera.beta;
      goalRadius = hasRadius ? goal.radius : camera.radius;
      goalTarget.x = hasTarget ? goal.target.x : camera.target.x;
      goalTarget.y = hasTarget ? goal.target.y : camera.target.y;
      goalTarget.z = hasTarget ? goal.target.z : camera.target.z;
    } else if (camera.alpha !== lastAlpha || camera.beta !== lastBeta || camera.radius !== lastRadius || camera.target.x !== lastTarget.x || camera.target.y !== lastTarget.y || camera.target.z !== lastTarget.z) {
      throw new Error("ArcRotate camera interpolation was interrupted.");
    }
    const clampedAlpha = clampToLimit(goalAlpha, camera.lowerAlphaLimit, camera.upperAlphaLimit);
    const clampedBeta = clampToLimit(goalBeta, camera.lowerBetaLimit, camera.upperBetaLimit);
    const clampedRadius = clampToLimit(goalRadius, camera.lowerRadiusLimit, camera.upperRadiusLimit);
    const t = expDampFactor(deltaSeconds, factor);
    camera.alpha = lerpAngleShortest(camera.alpha, clampedAlpha, t);
    camera.beta = lerpAngleShortest(camera.beta, clampedBeta, t);
    camera.radius = dampScalar(camera.radius, clampedRadius, t);
    lerpVec3ToRef(camera.target, goalTarget, t, camera.target);
    const radiusScale = Math.abs(clampedRadius) > 1e-6 ? Math.abs(clampedRadius) : 1;
    const alphaRemaining = Math.abs(lerpAngleShortest(camera.alpha, clampedAlpha, 1) - camera.alpha);
    const betaRemaining = Math.abs(lerpAngleShortest(camera.beta, clampedBeta, 1) - camera.beta);
    const radiusRemaining = Math.abs(clampedRadius - camera.radius) / radiusScale;
    const dx = goalTarget.x - camera.target.x;
    const dy = goalTarget.y - camera.target.y;
    const dz = goalTarget.z - camera.target.z;
    const targetRemaining = Math.hypot(dx, dy, dz) / radiusScale;
    if (alphaRemaining < TerminationEpsilon && betaRemaining < TerminationEpsilon && radiusRemaining < TerminationEpsilon && targetRemaining < TerminationEpsilon) {
      camera.alpha = clampedAlpha;
      camera.beta = clampedBeta;
      camera.radius = clampedRadius;
      camera.target.x = goalTarget.x;
      camera.target.y = goalTarget.y;
      camera.target.z = goalTarget.z;
      return false;
    }
    lastAlpha = camera.alpha;
    lastBeta = camera.beta;
    lastRadius = camera.radius;
    lastTarget.x = camera.target.x;
    lastTarget.y = camera.target.y;
    lastTarget.z = camera.target.z;
    return true;
  };
  return runFrameInterpolation(scene, step, signal);
}

function createPickingRay(x, y, vpMatrix, width, height) {
  const invVP = mat4Invert(vpMatrix);
  if (!invVP) {
    return null;
  }
  const ndcX = 2 * x / width - 1;
  const ndcY = 1 - 2 * y / height;
  const near = unprojectPoint(invVP, ndcX, ndcY, 1);
  const far = unprojectPoint(invVP, ndcX, ndcY, 0);
  const dx = far[0] - near[0];
  const dy = far[1] - near[1];
  const dz = far[2] - near[2];
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
  if (len < 1e-10) {
    return null;
  }
  const invLen = 1 / len;
  return {
    origin: near,
    direction: [dx * invLen, dy * invLen, dz * invLen],
    length: len
  };
}
function unprojectPoint(invVP, ndcX, ndcY, depth) {
  const x = invVP[0] * ndcX + invVP[4] * ndcY + invVP[8] * depth + invVP[12];
  const y = invVP[1] * ndcX + invVP[5] * ndcY + invVP[9] * depth + invVP[13];
  const z = invVP[2] * ndcX + invVP[6] * ndcY + invVP[10] * depth + invVP[14];
  const w = invVP[3] * ndcX + invVP[7] * ndcY + invVP[11] * depth + invVP[15];
  const invW = 1 / w;
  return [x * invW, y * invW, z * invW];
}

function createLightBase(getLocalMatrix) {
  const wm = createWorldMatrixState(getLocalMatrix);
  const lvs = {
    _lightVersion: 0,
    b() {
      lvs._lightVersion++;
    }
  };
  const onDirty = () => {
    wm.markLocalDirty();
    lvs._lightVersion++;
  };
  return { wm, onDirty, lvs };
}
function applyWorldMatrixAccessors(target, wm, lvs) {
  Object.defineProperties(target, {
    parent: {
      get() {
        return wm.parent;
      },
      set(v) {
        wm.parent = v;
      },
      enumerable: true,
      configurable: true
    },
    worldMatrix: {
      get() {
        return wm.getWorldMatrix();
      },
      enumerable: true,
      configurable: true
    },
    worldMatrixVersion: {
      get() {
        return wm.getWorldMatrixVersion();
      },
      enumerable: true,
      configurable: true
    }
  });
  if (lvs) {
    Object.defineProperty(target, "_lightVersion", {
      get() {
        return lvs._lightVersion;
      },
      enumerable: false,
      configurable: true
    });
    target._bumpLightVersion = lvs.b;
  }
  attachWorldMatrixState(target, wm);
  return target;
}

function localMatrixFromDirection(dx, dy, dz, px = 0, py = 0, pz = 0, out) {
  const flen = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
  const fx = dx / flen, fy = dy / flen, fz = dz / flen;
  let rx = -fz, rz = fx;
  const ry = 0;
  const rlen = Math.sqrt(rx * rx + ry * ry + rz * rz) || 1;
  rx /= rlen;
  rz /= rlen;
  const ux = fy * rz - fz * ry, uy = fz * rx - fx * rz, uz = fx * ry - fy * rx;
  const out4 = out ?? new F32(16);
  const m = out4;
  m[0] = rx;
  m[1] = ry;
  m[2] = rz;
  m[3] = 0;
  m[4] = ux;
  m[5] = uy;
  m[6] = uz;
  m[7] = 0;
  m[8] = fx;
  m[9] = fy;
  m[10] = fz;
  m[11] = 0;
  m[12] = px;
  m[13] = py;
  m[14] = pz;
  m[15] = 1;
  return out4;
}

function createDirectionalLight(direction, intensity = 1) {
  const _localMatrix = allocateMat4();
  const { wm, onDirty, lvs } = createLightBase(() => {
    return localMatrixFromDirection(light.direction.x, light.direction.y, light.direction.z, light.position.x, light.position.y, light.position.z, _localMatrix);
  });
  const light = applyWorldMatrixAccessors(
    {
      lightType: "directional",
      children: [],
      direction: new ObservableVec3(direction[0], direction[1], direction[2], onDirty),
      position: new ObservableVec3(0, 0, 0, onDirty),
      diffuse: [1, 1, 1],
      specular: [1, 1, 1],
      intensity,
      _writeLightUbo: (data, offset) => {
        const o = offset;
        const w = light.worldMatrix;
        data[o] = w[8];
        data[o + 1] = w[9];
        data[o + 2] = w[10];
        data[o + 3] = 1;
        data[o + 4] = light.diffuse[0] * light.intensity;
        data[o + 5] = light.diffuse[1] * light.intensity;
        data[o + 6] = light.diffuse[2] * light.intensity;
        data[o + 7] = Number.MAX_VALUE;
        data[o + 8] = light.specular[0] * light.intensity;
        data[o + 9] = light.specular[1] * light.intensity;
        data[o + 10] = light.specular[2] * light.intensity;
      }
    },
    wm,
    lvs
  );
  return light;
}

var directionalLight = /*#__PURE__*/Object.freeze({
    __proto__: null,
    createDirectionalLight: createDirectionalLight
});

const PBR_HAS_NORMAL_MAP = 1 << 0;
const PBR_HAS_EMISSIVE = 1 << 1;
const PBR_HAS_ENV = 1 << 2;
const PBR_HAS_ALPHA_TEST = 1 << 3;
const PBR_HAS_TONEMAP = 1 << 4;
const PBR_HAS_FOG = 1 << 5;
const PBR_HAS_ALPHA_BLEND = 1 << 6;
const PBR_HAS_SPEC_GLOSS = 1 << 7;
const PBR_HAS_DOUBLE_SIDED = 1 << 8;
const PBR_HAS_METALLIC_REFLECTANCE_MAP = 1 << 10;
const PBR_HAS_REFLECTANCE_MAP = 1 << 11;
const PBR_HAS_USE_ALPHA_ONLY_MR = 1 << 12;
const PBR_HAS_OCCLUSION = 1 << 15;
const PBR_HAS_SPECULAR_AA = 1 << 17;
const PBR_HAS_CLEARCOAT = 1 << 20;
const PBR_HAS_EMISSIVE_COLOR = 1 << 21;
const PBR_HAS_SHEEN = 1 << 22;
const PBR_HAS_SHEEN_TEXTURE = 1 << 23;
const PBR_HAS_GAMMA_ALBEDO = 1 << 25;
const PBR_HAS_ANISOTROPY = 1 << 26;
const PBR_HAS_SUBSURFACE = 1 << 27;
const PBR_HAS_THICKNESS_MAP = 1 << 28;
const PBR_HAS_SKYBOX = 1 << 29;
const PBR_HAS_SHEEN_ALBEDO_SCALING = 1 << 30;
const PBR2_HAS_REFRACTION = 1 << 4;
const PBR2_HAS_UV_TRANSFORM = 1 << 9;
const PBR2_HAS_REFLECTANCE_FACTORS = 1 << 10;
const PBR2_HAS_UV2 = 1 << 11;
const PBR2_HAS_BASE_COLOR_FACTOR = 1 << 12;
const PBR2_NO_COLOR_OUTPUT = 1 << 15;
const PBR2_ESM_SHADOW_OUTPUT = 1 << 16;

let _pbrExts = null;
let _pbrExtsSorted = null;
function _registerPbrExt(ext) {
  (_pbrExts ??= /* @__PURE__ */ new Map()).set(ext.id, ext);
  _pbrExtsSorted = null;
}
function _getPbrExts() {
  return _pbrExts ??= /* @__PURE__ */ new Map();
}
function _getPbrExtsSorted() {
  if (!_pbrExtsSorted) {
    const map = _pbrExts;
    _pbrExtsSorted = map ? Array.from(map.values()).sort((a, b) => a.id.localeCompare(b.id)) : [];
  }
  return _pbrExtsSorted;
}
let _pbrSceneHooks = null;
function _registerPbrSceneHook(hook) {
  (_pbrSceneHooks ??= /* @__PURE__ */ new Set()).add(hook);
}
function _getPbrSceneHooks() {
  return _pbrSceneHooks ?? [];
}

class ObservableQuat {
  _x;
  _y;
  _z;
  _w;
  _onDirty;
  /** Bumped on every value change. Lets derived caches (e.g. the Euler proxy) detect
   *  external quaternion writes and re-sync only when needed. */
  _version = 0;
  constructor(x, y, z, w, onDirty) {
    this._x = x;
    this._y = y;
    this._z = z;
    this._w = w;
    this._onDirty = onDirty;
  }
  /** Monotonic change counter — incremented whenever any component changes. */
  get version() {
    return this._version;
  }
  get x() {
    return this._x;
  }
  set x(v) {
    if (this._x !== v) {
      this._x = v;
      this._version++;
      this._onDirty();
    }
  }
  get y() {
    return this._y;
  }
  set y(v) {
    if (this._y !== v) {
      this._y = v;
      this._version++;
      this._onDirty();
    }
  }
  get z() {
    return this._z;
  }
  set z(v) {
    if (this._z !== v) {
      this._z = v;
      this._version++;
      this._onDirty();
    }
  }
  get w() {
    return this._w;
  }
  set w(v) {
    if (this._w !== v) {
      this._w = v;
      this._version++;
      this._onDirty();
    }
  }
  /** Bulk set — one dirty notification instead of four. */
  set(x, y, z, w) {
    this._x = x;
    this._y = y;
    this._z = z;
    this._w = w;
    this._version++;
    this._onDirty();
  }
  /** Copy values from another quaternion. */
  copyFrom(q) {
    this.set(q.x, q.y, q.z, q.w);
  }
  /** Copy into a Float32Array at offset. */
  toArray(out, offset = 0) {
    out[offset] = this._x;
    out[offset + 1] = this._y;
    out[offset + 2] = this._z;
    out[offset + 3] = this._w;
  }
}

function eulerToQuat(rx, ry, rz) {
  const cx = Math.cos(rx * 0.5), sx_ = Math.sin(rx * 0.5);
  const cy = Math.cos(ry * 0.5), sy_ = Math.sin(ry * 0.5);
  const cz = Math.cos(rz * 0.5), sz_ = Math.sin(rz * 0.5);
  return [sx_ * cy * cz + cx * sy_ * sz_, cx * sy_ * cz - sx_ * cy * sz_, cx * cy * sz_ + sx_ * sy_ * cz, cx * cy * cz - sx_ * sy_ * sz_];
}
function quatToEulerXYZ(qx, qy, qz, qw) {
  const sinY = 2 * (qx * qz + qw * qy);
  const ry = Math.asin(Math.max(-1, Math.min(1, sinY)));
  const rx = Math.atan2(-(2 * (qy * qz - qw * qx)), 1 - 2 * (qx * qx + qy * qy));
  const rz = Math.atan2(-(2 * (qx * qy - qw * qz)), 1 - 2 * (qy * qy + qz * qz));
  return [rx, ry, rz];
}

function createEulerProxy(rq) {
  let ex = 0;
  let ey = 0;
  let ez = 0;
  let syncedVersion = -1;
  const sync = () => {
    if (rq.version !== syncedVersion) {
      const e = quatToEulerXYZ(rq.x, rq.y, rq.z, rq.w);
      ex = e[0];
      ey = e[1];
      ez = e[2];
      syncedVersion = rq.version;
    }
  };
  const apply = (x, y, z) => {
    ex = x;
    ey = y;
    ez = z;
    const [a, b, c, d] = eulerToQuat(x, y, z);
    rq.set(a, b, c, d);
    syncedVersion = rq.version;
  };
  return {
    get x() {
      sync();
      return ex;
    },
    set x(v) {
      sync();
      apply(v, ey, ez);
    },
    get y() {
      sync();
      return ey;
    },
    set y(v) {
      sync();
      apply(ex, v, ez);
    },
    get z() {
      sync();
      return ez;
    },
    set z(v) {
      sync();
      apply(ex, ey, v);
    },
    set: apply
  };
}
function createSceneNode(name, px = 0, py = 0, pz = 0, qx = 0, qy = 0, qz = 0, qw = 1, sx = 1, sy = 1, sz = 1) {
  return createSceneNodeCore(name, null, px, py, pz, qx, qy, qz, qw, sx, sy, sz);
}
function createSceneNodeFromMatrix(name, matrix) {
  return createSceneNodeCore(name, matrix);
}
function createSceneNodeCore(name, matrix, px = 0, py = 0, pz = 0, qx = 0, qy = 0, qz = 0, qw = 1, sx = 1, sy = 1, sz = 1) {
  const wm = createWorldMatrixState(() => {
    return node._localMatrix ?? composeTrsLocalMatrix(node.position, node.rotationQuaternion, node.scaling);
  });
  const onWmDirty = () => {
    if (!node._localMatrix) {
      wm.markLocalDirty();
    }
  };
  const position = new ObservableVec3(px, py, pz, onWmDirty);
  const rq = new ObservableQuat(qx, qy, qz, qw, onWmDirty);
  const rotation = createEulerProxy(rq);
  const scaling = new ObservableVec3(sx, sy, sz, onWmDirty);
  const node = {
    name,
    children: [],
    position,
    rotationQuaternion: rq,
    rotation,
    scaling,
    get parent() {
      return wm.parent;
    },
    set parent(v) {
      wm.parent = v;
    },
    get worldMatrix() {
      return wm.getWorldMatrix();
    },
    get worldMatrixVersion() {
      return wm.getWorldMatrixVersion();
    }
  };
  if (matrix) {
    node._localMatrix = matrix;
  }
  attachWorldMatrixState(node, wm);
  return node;
}

function initMeshTransform(partialMesh, px = 0, py = 0, pz = 0, rx = 0, ry = 0, rz = 0, sx = 1, sy = 1, sz = 1) {
  const wm = createWorldMatrixState(() => composeTrsLocalMatrix(mesh.position, mesh.rotationQuaternion, mesh.scaling));
  const onWmDirty = () => wm.markLocalDirty();
  const [iqx, iqy, iqz, iqw] = eulerToQuat(rx, ry, rz);
  const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onWmDirty);
  const rotationQuaternion = rq;
  const rotation = createEulerProxy(rq);
  const position = new ObservableVec3(px, py, pz, onWmDirty);
  const scaling = new ObservableVec3(sx, sy, sz, onWmDirty);
  const mesh = { ...partialMesh, position, rotationQuaternion, rotation, scaling };
  if (!mesh.children) {
    mesh.children = [];
  }
  Object.defineProperty(mesh, "parent", {
    get() {
      return wm.parent;
    },
    set(v) {
      wm.parent = v;
    },
    configurable: true,
    enumerable: true
  });
  Object.defineProperty(mesh, "worldMatrix", {
    get() {
      return wm.getWorldMatrix();
    },
    configurable: true,
    enumerable: false
  });
  Object.defineProperty(mesh, "worldMatrixVersion", {
    get() {
      return wm.getWorldMatrixVersion();
    },
    configurable: true,
    enumerable: false
  });
  attachWorldMatrixState(mesh, wm);
  return mesh;
}
function uploadMeshToGPU(engine, positions, normals, indices, uvs, uvs2, tangents, colors) {
  const device = engine._device;
  const positionBuffer = createMappedBuffer(engine, positions, BU.VERTEX);
  const normalBuffer = createMappedBuffer(engine, normals, BU.VERTEX);
  const indexBuffer = createMappedBuffer(engine, indices, BU.INDEX);
  let uvBuffer;
  if (uvs && uvs.length > 0) {
    uvBuffer = createMappedBuffer(engine, uvs, BU.VERTEX);
  } else {
    uvBuffer = device.createBuffer({
      size: positions.length / 3 * 8,
      usage: BU.VERTEX,
      mappedAtCreation: true
    });
    uvBuffer.unmap();
  }
  let uv2Buffer = null;
  const tangentBuffer = null;
  const colorBuffer = null;
  return {
    positionBuffer,
    normalBuffer,
    uvBuffer,
    uv2Buffer,
    tangentBuffer,
    colorBuffer,
    hasUv: !!uvs && uvs.length > 0,
    hasUv2: false,
    hasTangent: false,
    hasColor: false,
    indexBuffer,
    indexCount: indices.length,
    indexFormat: "uint32"
  };
}

function computeAabb(positions, world) {
  let minX = Infinity, minY = Infinity, minZ = Infinity;
  let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
  {
    for (let i = 0; i < positions.length; i += 3) {
      const x = positions[i];
      const y = positions[i + 1];
      const z = positions[i + 2];
      if (x < minX) {
        minX = x;
      }
      if (x > maxX) {
        maxX = x;
      }
      if (y < minY) {
        minY = y;
      }
      if (y > maxY) {
        maxY = y;
      }
      if (z < minZ) {
        minZ = z;
      }
      if (z > maxZ) {
        maxZ = z;
      }
    }
  }
  return [
    [minX, minY, minZ],
    [maxX, maxY, maxZ]
  ];
}

function createDiscData(options = {}) {
  const radius = options.radius ?? 0.5;
  const tessellation = options.tessellation ?? 64;
  const arc = options.arc && (options.arc <= 0 || options.arc > 1) ? 1 : options.arc ?? 1;
  const positions = [];
  const uvs = [];
  const indices = [];
  positions.push(0, 0, 0);
  uvs.push(0.5, 0.5);
  const theta = Math.PI * 2 * arc;
  const step = arc === 1 ? theta / tessellation : theta / (tessellation - 1);
  let a = 0;
  for (let t = 0; t < tessellation; t++) {
    const x = Math.cos(a);
    const y = Math.sin(a);
    positions.push(radius * x, radius * y, 0);
    uvs.push((x + 1) / 2, (1 - y) / 2);
    a += step;
  }
  if (arc === 1) {
    positions.push(positions[3], positions[4], positions[5]);
    uvs.push(uvs[2], uvs[3]);
  }
  const vertexNb = positions.length / 3;
  for (let i = 1; i < vertexNb - 1; i++) {
    indices.push(i + 1, 0, i);
  }
  const normals = new F32(vertexNb * 3);
  for (let i = 0; i < vertexNb; i++) {
    normals[i * 3 + 2] = -1;
  }
  return {
    positions: new F32(positions),
    normals,
    uvs: new F32(uvs),
    indices: new U32(indices)
  };
}

function createMeshFromData(engine, name, positions, normals, indices, uvs, uvs2, tangents, colors) {
  const [min, max] = computeAabb(positions);
  const mesh = initMeshTransform({
    name,
    material: null,
    receiveShadows: false,
    boundMin: isFinite(min[0]) ? min : void 0,
    boundMax: isFinite(max[0]) ? max : void 0,
    _gpu: uploadMeshToGPU(engine, positions, normals, indices, uvs)
  });
  mesh._cpuPositions = positions;
  mesh._cpuNormals = normals;
  mesh._cpuUvs = uvs;
  mesh._cpuIndices = indices;
  engine._dlr?.m(mesh, null, null, null, indices, "uint32");
  return mesh;
}
function createDisc(engine, options) {
  const data = createDiscData(options);
  return createMeshFromData(engine, "disc", data.positions, data.normals, data.indices, data.uvs);
}

function createSolidTexture2D(engine, r, g, b, a = 1) {
  const device = engine._device;
  const texture = device.createTexture({
    size: { width: 1, height: 1 },
    format: "rgba8unorm",
    usage: TU.TEXTURE_BINDING | TU.COPY_DST
  });
  const data = new U8([Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), Math.round(a * 255)]);
  device.queue.writeTexture({ texture }, data, { bytesPerRow: 4, rowsPerImage: 1 }, { width: 1, height: 1 });
  const sampler = getBilinearSampler(engine);
  const tex = { texture, view: texture.createView(), sampler, width: 1, height: 1 };
  engine._dlr?.s(tex, r, g, b, a);
  return tex;
}

const MSH_HAS_TANGENTS = 1 << 0;
const MSH_HAS_SKELETON = 1 << 1;
const MSH_HAS_SKELETON_8 = 1 << 2;
const MSH_HAS_MORPH_TARGETS = 1 << 3;
const MSH_HAS_THIN_INSTANCES = 1 << 4;
const MSH_HAS_INSTANCE_COLOR = 1 << 5;
const MSH_HAS_VERTEX_COLOR = 1 << 6;
const MSH_HAS_UV2 = 1 << 7;
const MSH_RECEIVE_SHADOWS = 1 << 8;
const MSH_VAT = 1 << 9;
const MSH_FLAT_NORMAL = 1 << 10;
function _computeMeshFeatures(mesh, receiveShadows = false) {
  const gpu = mesh._gpu;
  let features = 0;
  if (gpu.tangentBuffer) {
    features |= MSH_HAS_TANGENTS;
  }
  if (mesh.vat) {
    features |= MSH_VAT;
    if (mesh.vat.joints1Buffer) {
      features |= MSH_HAS_SKELETON_8;
    }
  } else if (mesh.skeleton) {
    features |= MSH_HAS_SKELETON;
    if (mesh.skeleton.joints1Buffer) {
      features |= MSH_HAS_SKELETON_8;
    }
  }
  if (mesh.morphTargets) {
    features |= MSH_HAS_MORPH_TARGETS;
  }
  if (mesh.thinInstances) {
    features |= MSH_HAS_THIN_INSTANCES;
    if (mesh.thinInstances.colors) {
      features |= MSH_HAS_INSTANCE_COLOR;
    }
  }
  if (gpu.colorBuffer) {
    features |= MSH_HAS_VERTEX_COLOR;
  }
  if (gpu.uv2Buffer) {
    features |= MSH_HAS_UV2;
  }
  if (mesh._flatNormal) {
    features |= MSH_FLAT_NORMAL;
  }
  if (receiveShadows) {
    features |= MSH_RECEIVE_SHADOWS;
  }
  return features;
}

let _pbrFallbackResolver = null;
function _installPbrFallbackResolver(resolve) {
  _pbrFallbackResolver = resolve;
}
const _bindingsCache = /* @__PURE__ */ new Map();
let _cachedDevice = null;
function ensureDevice(engine) {
  if (_cachedDevice !== engine._device) {
    _bindingsCache.clear();
    _cachedDevice = engine._device;
  }
}
function clearPbrPipelineCache() {
  _bindingsCache.clear();
  _cachedDevice = null;
}
function getOrCreatePbrBindings(engine, features, features2, meshFeatures, sceneFeatures, composed, shaderKey = "", stencil = null) {
  ensureDevice(engine);
  const key = `${features}:${features2}:${meshFeatures}:${sceneFeatures}:${shaderKey}${""}`;
  const cached = _bindingsCache.get(key);
  if (cached) {
    return cached;
  }
  const device = engine._device;
  const meshBGL = device.createBindGroupLayout(composed._meshBGLDescriptor);
  let shadowBGL = null;
  if (composed._shadowBGLDescriptor) {
    shadowBGL = device.createBindGroupLayout(composed._shadowBGLDescriptor);
  }
  const bindings = {
    _features: features,
    _features2: features2,
    _meshFeatures: meshFeatures,
    _meshBGL: meshBGL,
    _shadowBGL: shadowBGL,
    _composed: composed,
    _pipelines: /* @__PURE__ */ new Map()
  };
  _bindingsCache.set(key, bindings);
  return bindings;
}
function getOrCreatePbrPipeline(engine, sig, bindings, material) {
  ensureDevice(engine);
  sig._sampleCount > 1 && false;
  const key = `${targetSignatureKey(sig)}${""}`;
  const cached = bindings._pipelines.get(key);
  if (cached) {
    return cached;
  }
  const device = engine._device;
  const { _features: features, _features2: features2, _composed: composed } = bindings;
  const esmShadowOutput = (features2 & PBR2_ESM_SHADOW_OUTPUT) !== 0;
  const hasAlpha = !esmShadowOutput && (features & PBR_HAS_ALPHA_BLEND) !== 0;
  const hasDoubleSided = (features & PBR_HAS_DOUBLE_SIDED) !== 0;
  const sceneBGL = getSceneBindGroupLayout(engine);
  const bgls = bindings._shadowBGL ? [sceneBGL, bindings._meshBGL, bindings._shadowBGL] : [sceneBGL, bindings._meshBGL];
  const vertModule = device.createShaderModule({ code: composed._vertexWGSL });
  const noColorOutput = (features2 & PBR2_NO_COLOR_OUTPUT) !== 0;
  const fragModule = !sig._colorFormat && !noColorOutput ? null : device.createShaderModule({ code: composed._fragmentWGSL });
  const fragTarget = noColorOutput ? null : { format: sig._colorFormat, writeMask: CW.ALL };
  if (hasAlpha && fragTarget) {
    fragTarget.blend = {
      color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
      alpha: { srcFactor: "one", dstFactor: "one", operation: "add" }
    };
  }
  const pipeline = device.createRenderPipeline({
    layout: device.createPipelineLayout({ bindGroupLayouts: bgls }),
    vertex: { module: vertModule, entryPoint: "main", buffers: composed._vertexBufferLayouts },
    ...fragModule ? { fragment: { module: fragModule, entryPoint: "main", targets: fragTarget ? [fragTarget] : [] } } : {},
    ...sig._depthStencilFormat ? {
      depthStencil: {
        format: sig._depthStencilFormat,
        depthCompare: sig._depthCompare ?? REVERSE_DEPTH_COMPARE,
        depthWriteEnabled: noColorOutput || esmShadowOutput || !hasAlpha,
        // Pre-baked stencil sub-fields, applied only on a stencil-capable target — the same
        // material in the depth32float shadow/depth pass keeps plain depth state (no stencil → no
        // format mismatch). Gated on `_stencilResolver` (the opt-in hook) so the entire branch —
        // including the `bindings._stencil` reads — folds out of stencil-free bundles.
        ...{}
      }
    } : {},
    multisample: { count: sig._sampleCount },
    // `topology` and `frontFace` are omitted deliberately: WebGPU already defaults them to
    // "triangle-list" and "ccw", so naming them costs every PBR scene ~42 bytes to restate the
    // spec. Anything unusual — a non-triangle topology, a strip's index format, or a mirrored
    // mesh's reversed winding — arrives pre-built in `_prim` and overrides through the spread.
    // Resolving those cases here instead costs the topology names, the winding branch and a call
    // in scenes that never draw such a mesh, which is what pushed a dozen scenes past their
    // bundle ceilings. See ComposedShader._prim.
    primitive: { cullMode: hasDoubleSided ? "none" : "back", ...composed._prim }
  });
  bindings._pipelines.set(key, pipeline);
  return pipeline;
}
function createPbrMeshBindGroup(engine, bindings, composed, meshUBO, materialUBO, material, env, meshCtx, refractionTexture) {
  const device = engine._device;
  const features = bindings._features;
  const features2 = bindings._features2;
  const meshFeatures = bindings._meshFeatures;
  const hasNormal = (features & PBR_HAS_NORMAL_MAP) !== 0 && (meshFeatures & MSH_HAS_TANGENTS) !== 0;
  const hasCotangentNormal = (features & PBR_HAS_NORMAL_MAP) !== 0 && (meshFeatures & MSH_HAS_TANGENTS) === 0;
  const hasAnyNormal = hasNormal || hasCotangentNormal;
  const hasEmissive = (features & PBR_HAS_EMISSIVE) !== 0;
  const hasSpecGloss = (features & PBR_HAS_SPEC_GLOSS) !== 0;
  const esmShadowOutput = (features2 & PBR2_ESM_SHADOW_OUTPUT) !== 0;
  const entries = [];
  let b = 0;
  const addTex = (t) => {
    entries.push({ binding: b++, resource: t.view });
    entries.push({ binding: b++, resource: t.sampler });
  };
  const ctx = {
    _engine: engine,
    _features: features,
    _features2: features2,
    _meshFeatures: meshFeatures,
    _material: material,
    _mesh: meshCtx ?? void 0,
    _env: env,
    _refractionTexture: refractionTexture
  };
  const sortedExts = _getPbrExtsSorted();
  const fragIds = composed._fragmentKey ? composed._fragmentKey.split("|").filter((s) => s.length > 0) : [];
  entries.push({ binding: b++, resource: { buffer: meshUBO } });
  entries.push({ binding: b++, resource: { buffer: materialUBO } });
  for (const ext of sortedExts) {
    if (ext.phase === "vertex" && ext.bind) {
      b = ext.bind(ctx, entries, b);
    }
  }
  addTex(material.baseColorTexture ?? _pbrFallbackResolver?.(engine));
  if (hasAnyNormal) {
    addTex(material.normalTexture);
  }
  addTex(material.ormTexture ?? _pbrFallbackResolver?.(engine));
  if ((features2 & PBR2_HAS_UV2) !== 0 && (meshFeatures & MSH_HAS_UV2) !== 0 && material.occlusionTexture && material.occlusionTexCoord === 1) {
    addTex(material.occlusionTexture);
  }
  if (hasEmissive) {
    addTex(material.emissiveTexture);
  }
  if (hasSpecGloss) {
    addTex(material.specGlossTexture);
  }
  if (esmShadowOutput) {
    entries.push({
      binding: b++,
      resource: { buffer: material._esmShadowParamsUBO }
    });
  }
  const seenExts = [];
  for (const fid of fragIds) {
    const ext = sortedExts.find((e) => e.id === fid || fid.startsWith(e.id + "-"));
    if (!ext || ext.phase === "vertex" || !ext.bind || seenExts.includes(ext)) {
      continue;
    }
    seenExts.push(ext);
    b = ext.bind(ctx, entries, b);
  }
  return device.createBindGroup({ layout: bindings._meshBGL, entries });
}

let _pbrGroupBuilder = null;
function getPbrGroupBuilder() {
  if (_pbrGroupBuilder) {
    return _pbrGroupBuilder;
  }
  const builder = async (scene, meshes) => {
    const envTex = scene._envTextures;
    const renderableMod = await import('./pbr-renderable-kO5FIKKF.esm.js');
    const result = await renderableMod.buildPbrRenderables(scene, meshes, envTex);
    builder._rebuildSingle = result.rebuildSingle;
    return result;
  };
  builder._materialFamily = "pbr";
  return _pbrGroupBuilder = builder;
}
function _computePbrMaterialFeatures(mat) {
  let features = (mat.emissiveTexture ? PBR_HAS_EMISSIVE : 0) | (mat.normalTexture ? PBR_HAS_NORMAL_MAP : 0) | (mat.alphaBlend === true || (mat._alphaCutOff ?? 0) <= 0 && mat.alpha < 1 ? PBR_HAS_ALPHA_BLEND : 0) | (mat.specGlossTexture ? PBR_HAS_SPEC_GLOSS : 0) | (mat.doubleSided ? PBR_HAS_DOUBLE_SIDED : 0);
  if ((mat.occlusionStrength ?? 1) > 0) {
    features |= PBR_HAS_OCCLUSION;
  }
  if (mat.enableSpecularAA) {
    features |= PBR_HAS_SPECULAR_AA;
  }
  let features2 = 0;
  for (const ext of _getPbrExts().values()) {
    if (ext.detect) {
      const d = ext.detect(mat);
      features |= d.f;
      features2 |= d.f2;
    }
  }
  if (mat._uv2Mask) {
    features2 |= PBR2_HAS_UV2;
  }
  if (mat.baseColorFactor) {
    features2 |= PBR2_HAS_BASE_COLOR_FACTOR;
  }
  return { features, features2 };
}
function createPbrMaterial(props) {
  _installPbrFallbackResolver((engine) => engine._pbrFallbackTex ??= createSolidTexture2D(engine, 1, 1, 1));
  return {
    ...props,
    _buildGroup: getPbrGroupBuilder(),
    _uboVersion: 0
  };
}
function collectPbrBoundTextures(mat) {
  const t = [];
  for (const tex of [mat.baseColorTexture, mat.normalTexture, mat.ormTexture, mat.occlusionTexture, mat.emissiveTexture, mat.specGlossTexture]) {
    if (tex) {
      t.push(tex);
    }
  }
  for (const ext of _getPbrExts().values()) {
    ext.textures?.(mat, t);
  }
  return t;
}

const PBR2_HAS_SHADOW_ONLY = 1 << 30;
function createShadowOnlyFragment() {
  const unrolled = [];
  for (let i = 0; i < MAX_LIGHTS; i++) {
    unrolled.push(`so_shadowMin = min(so_shadowMin, shadowFactors[${i}]);`);
  }
  const bc = `
{
var so_shadowMin = 1.0;
${unrolled.join("\n")}
color = material.shadowOnlyColor;
alpha = saturate((1.0 - so_shadowMin) * material.shadowOnlyFalloff) * material.shadowOnlyOpacity;
}
`;
  return {
    _id: "shadow-only",
    _uboFields: [
      { _name: "shadowOnlyColor", _type: "vec3<f32>" },
      { _name: "shadowOnlyOpacity", _type: "f32" },
      { _name: "shadowOnlyFalloff", _type: "f32" }
    ],
    _fragmentSlots: {
      BC: bc,
      // Overwrite finalAlpha after the alpha block's luminanceOverAlpha fold so
      // environment/direct specular can't make the shadow catcher opaque. `alpha`
      // holds the shadow term set in BC.
      FA: `finalAlpha = alpha * material.materialAlpha;`
    }
  };
}
function writeShadowOnlyUBO(data, material, offsets) {
  if (!material._shadowOnly) {
    return;
  }
  if (offsets.has("shadowOnlyColor")) {
    const off = offsets.get("shadowOnlyColor") / 4;
    const tint = material._shadowOnlyColor ?? [0, 0, 0];
    data[off] = tint[0];
    data[off + 1] = tint[1];
    data[off + 2] = tint[2];
  }
  if (offsets.has("shadowOnlyOpacity")) {
    data[offsets.get("shadowOnlyOpacity") / 4] = material._shadowOnlyOpacity ?? 1;
  }
  if (offsets.has("shadowOnlyFalloff")) {
    data[offsets.get("shadowOnlyFalloff") / 4] = material._shadowOnlyFalloff ?? 1;
  }
}
const pbrExt = {
  id: "shadow-only",
  phase: "fragment",
  detect(mat) {
    return mat._shadowOnly ? { f: PBR_HAS_ALPHA_BLEND, f2: PBR2_HAS_SHADOW_ONLY } : { f: 0, f2: 0 };
  },
  frag(ctx) {
    if (!(ctx._features2 & PBR2_HAS_SHADOW_ONLY)) {
      return null;
    }
    return createShadowOnlyFragment();
  },
  writeUbo: writeShadowOnlyUBO
};

function setShadowOnly(mat, options) {
  mat._shadowOnly = true;
  if (options?.color) {
    mat._shadowOnlyColor = options.color;
  }
  if (options?.opacity !== void 0) {
    mat._shadowOnlyOpacity = options.opacity;
  }
  if (options?.falloff !== void 0) {
    mat._shadowOnlyFalloff = options.falloff;
  }
  _registerPbrExt(pbrExt);
}

let shadowTaskInputs = null;
let shadowTaskInputPreloader = null;
function getShadowTaskInputs() {
  shadowTaskInputs ??= /* @__PURE__ */ new WeakMap();
  return shadowTaskInputs;
}
function setShadowTaskCasterMeshes(shadowGenerator, casterMeshes) {
  getShadowTaskInputs().set(shadowGenerator, casterMeshes);
  if (!shadowTaskInputPreloader) {
    return;
  }
  shadowGenerator._preloadPending = casterMeshes;
  void shadowTaskInputPreloader(shadowGenerator, casterMeshes).then(
    () => {
      if (shadowGenerator._preloadPending === casterMeshes) {
        shadowGenerator._preloadPending = void 0;
      }
    },
    // Leave the set parked — hence the generator skipped: a failed import means the factory is still
    // missing, and rendering anyway would throw inside the frame with a far less actionable stack.
    (error) => console.error(error)
  );
}
function _getShadowTaskCasterMeshes(shadowGenerator) {
  return shadowTaskInputs?.get(shadowGenerator);
}
function _setShadowTaskInputPreloader(preloader) {
  shadowTaskInputPreloader = preloader;
}

const byOrder = (a, b) => a.order - b.order;
const MAX_RECONCILE_PASSES = 8;
const rearmRebuild = (scene) => rebuildSceneRenderables(scene);
async function rebuildScenePbrPipelines(scene, force = false) {
  await rebuildSceneGroups(scene, "pbr", force);
}
async function rebuildSceneRenderables(scene) {
  await rebuildSceneGroups(scene, void 0, false);
}
async function rebuildSceneGroups(scene, family, force) {
  const ctx = scene;
  if (!ctx._built && !force) {
    return;
  }
  const generation = ctx._lightListVersion ?? 0;
  const engine = ctx.surface.engine;
  const retireOld = (disposers) => {
    if (ctx._z) {
      disposers.forEach((dispose) => dispose());
    } else {
      retireGpuResources(engine, () => disposers.forEach((dispose) => dispose()));
    }
  };
  let changed = false;
  let aborted = false;
  let completed = false;
  let rearmed = false;
  try {
    for (let pass = 1; ; pass++) {
      if (family !== void 0) {
        await rebuildEachGroup();
        break;
      }
      reconcileGroups(ctx);
      await rebuildEachGroup();
      if (!membershipDrifted(ctx)) {
        break;
      }
      if (pass >= MAX_RECONCILE_PASSES) {
        ctx._rebuildHook = rearmRebuild;
        rearmed = true;
        aborted = true;
        break;
      }
    }
    completed = true;
  } finally {
    if (changed) {
      ctx._renderables.sort(byOrder);
      ctx._renderableVersion++;
      ctx._materialEpoch++;
      if (ctx._built) {
        ctx._frameGraph.build();
      }
    }
    if (family === void 0 && completed && !aborted && (ctx._lightListVersion ?? 0) === generation) {
      const pending = ctx._pendingTopologyRetirements?.splice(0);
      if (pending?.length) {
        retireOld(pending);
      }
      if (!rearmed) {
        ctx._rebuildHook = void 0;
      }
    }
  }
  async function rebuildEachGroup() {
    for (const [builder, meshes] of ctx._groups) {
      if (family !== void 0 && builder._materialFamily !== family) {
        continue;
      }
      if (meshes.length === 0) {
        if (meshes.o || meshes.r) {
          dropGroupOutput(builder, meshes);
          changed = true;
        }
        continue;
      }
      const runtime = ctx._runtimeBuilds;
      await runtime?.wait(meshes);
      let unstable = false;
      const rebuild = async () => {
        const groupMeshes = [...meshes].filter((mesh) => ctx.meshes.includes(mesh) && mesh.material?._buildGroup === builder);
        if (groupMeshes.length === 0) {
          dropGroupOutput(builder, meshes);
          return;
        }
        const hadBuiltGroup = !!meshes.r;
        const cleanupStart = ctx._disposables.length;
        const oldByMesh = /* @__PURE__ */ new Map();
        const materials = new Map(groupMeshes.map((mesh) => [mesh, mesh.material]));
        for (const mesh of groupMeshes) {
          const disposers = ctx._meshDisposables.get(mesh);
          if (disposers) {
            oldByMesh.set(mesh, disposers);
            ctx._meshDisposables.delete(mesh);
          }
        }
        let result;
        let transmission;
        ctx._p = (value) => {
          transmission = value;
          return true;
        };
        try {
          result = await builder(ctx, groupMeshes);
        } catch (error) {
          transmission?.[1]();
          for (const mesh of groupMeshes) {
            const previous = oldByMesh.get(mesh);
            const built = ctx._meshDisposables.get(mesh);
            if (built && built !== previous) {
              for (const dispose of built) {
                dispose();
              }
            }
            const live = ctx.meshes.includes(mesh) && mesh.material === materials.get(mesh) && mesh.material?._buildGroup === builder && meshes.includes(mesh);
            if (previous && live) {
              ctx._meshDisposables.set(mesh, previous);
            } else {
              ctx._meshDisposables.delete(mesh);
              if (previous) {
                retireOld(previous);
              }
            }
          }
          throw error;
        } finally {
          delete ctx._p;
        }
        if (ctx._z || runtime?._d()) {
          transmission?.[1]();
          for (const mesh of groupMeshes) {
            const built = ctx._meshDisposables.get(mesh);
            const previous = oldByMesh.get(mesh);
            if (built && built !== previous) {
              for (const dispose of built) {
                dispose();
              }
            }
            ctx._meshDisposables.delete(mesh);
          }
          for (const dispose of ctx._disposables.splice(0)) {
            dispose();
          }
          const oldDisposers2 = [...oldByMesh.values()].flat();
          retireOld(oldDisposers2);
          return;
        }
        transmission?.[0]();
        builder._rebuildSingle = result.rebuildSingle;
        meshes.r = ctx._runtimeBuilds?.base(builder, result.rebuildSingle) ?? result.rebuildSingle;
        if (builder._materialFamily === "pbr" && result._G) {
          meshes._w = null;
        }
        if (hadBuiltGroup) {
          dedupeGroupCleanup(ctx, cleanupStart);
        }
        const liveMeshes = new Set(
          groupMeshes.filter(
            (mesh) => ctx.meshes.includes(mesh) && mesh.material === materials.get(mesh) && mesh.material?._buildGroup === builder && meshes.includes(mesh)
          )
        );
        if (liveMeshes.size !== groupMeshes.length && result.renderables.some((renderable) => !renderable.mesh)) {
          for (const mesh of groupMeshes) {
            const built = ctx._meshDisposables.get(mesh);
            const previous = oldByMesh.get(mesh);
            if (built && built !== previous) {
              for (const dispose of built) {
                dispose();
              }
            }
            if (previous && ctx.meshes.includes(mesh)) {
              ctx._meshDisposables.set(mesh, previous);
            } else {
              ctx._meshDisposables.delete(mesh);
              if (previous) {
                retireOld(previous);
              }
            }
          }
          unstable = true;
          return;
        }
        const rebuiltMaterials = /* @__PURE__ */ new Set();
        for (const mesh of groupMeshes) {
          if (!liveMeshes.has(mesh)) {
            const previous = oldByMesh.get(mesh);
            const built = ctx._meshDisposables.get(mesh);
            if (built && built !== previous) {
              for (const dispose of built) {
                dispose();
              }
            }
            if (previous && ctx.meshes.includes(mesh)) {
              ctx._meshDisposables.set(mesh, previous);
              oldByMesh.delete(mesh);
            } else {
              ctx._meshDisposables.delete(mesh);
            }
            continue;
          }
          rebuiltMaterials.add(mesh.material);
          ctx._runtimeBuilds?.reset(mesh);
        }
        for (const material of rebuiltMaterials) {
          material._csmGen = (material._csmGen ?? 0) + 1;
        }
        const stale = meshes.o;
        const staleSet = stale?.length ? new Set(stale) : null;
        for (let i = ctx._renderables.length - 1; i >= 0; i--) {
          const existing = ctx._renderables[i];
          if (liveMeshes.has(existing.mesh) || staleSet?.has(existing)) {
            ctx._renderables.splice(i, 1);
          }
        }
        const kept = result.renderables.filter((renderable) => !renderable.mesh || liveMeshes.has(renderable.mesh));
        ctx._renderables.push(...kept);
        meshes.o = kept;
        const oldDisposers = [...oldByMesh.values()].flat();
        retireOld(oldDisposers);
      };
      const { X } = await import('./scene-runtime-mesh-build-DIVoL4LH.esm.js');
      const MAX_ATTEMPTS = 3;
      for (let attempt = 1; ; attempt++) {
        unstable = false;
        await X(ctx, builder, rebuild);
        if (ctx._z || runtime?._d()) {
          aborted = true;
          return;
        }
        if (!unstable) {
          break;
        }
        if (attempt >= MAX_ATTEMPTS) {
          dropGroupOutput(builder, meshes);
          ctx._rebuildHook = rearmRebuild;
          rearmed = true;
          aborted = true;
          break;
        }
      }
      if (unstable) {
        changed = true;
        continue;
      }
      changed = true;
    }
  }
  function membershipDrifted(scene2) {
    for (const mesh of scene2.meshes) {
      const build = mesh.material?._buildGroup;
      if (build && !scene2._groups.get(build)?.includes(mesh)) {
        return true;
      }
    }
    return false;
  }
  function reconcileGroups(scene2) {
    const wanted = /* @__PURE__ */ new Map();
    for (const mesh of scene2.meshes) {
      const build = mesh.material?._buildGroup;
      if (!build) {
        continue;
      }
      const list = wanted.get(build);
      if (list) {
        list.push(mesh);
      } else {
        wanted.set(build, [mesh]);
      }
    }
    for (const [build, group] of scene2._groups) {
      const next = wanted.get(build) ?? [];
      if (group.length !== next.length || next.some((mesh, i) => group[i] !== mesh)) {
        group.length = 0;
        group.push(...next);
      }
      wanted.delete(build);
    }
    for (const [build, next] of wanted) {
      const group = next;
      scene2._groups.set(build, group);
    }
  }
  function dropGroupOutput(builder, meshes) {
    const owned = meshes.o;
    if (owned?.length) {
      const ownedSet = new Set(owned);
      for (let i = ctx._renderables.length - 1; i >= 0; i--) {
        if (ownedSet.has(ctx._renderables[i])) {
          ctx._renderables.splice(i, 1);
        }
      }
    }
    meshes.o = void 0;
    meshes.r = void 0;
    ctx._runtimeBuilds?.dropBase(builder);
    ctx._rebuildHook = rearmRebuild;
    rearmed = true;
  }
  function dedupeGroupCleanup(scene2, start) {
    const existing = new Set(scene2._disposables.slice(0, start));
    for (let i = scene2._disposables.length - 1; i >= start; i--) {
      if (existing.has(scene2._disposables[i])) {
        scene2._disposables.splice(i, 1);
      }
    }
  }
}

var sceneRebuild = /*#__PURE__*/Object.freeze({
    __proto__: null,
    rebuildScenePbrPipelines: rebuildScenePbrPipelines,
    rebuildSceneRenderables: rebuildSceneRenderables
});

const StandardToneMapping = {
  id: "standard",
  helpersWGSL: "",
  callWGSL: `color*=scene.vImageInfos.x;
color=1.0-exp2(-1.590579*color);`
};

async function setSceneImageProcessing(scene, update) {
  const ip = scene.imageProcessing;
  const prevEnabled = ip.toneMappingEnabled;
  const prevToneMappingId = ip.toneMapping?.id ?? StandardToneMapping.id;
  Object.assign(ip, update);
  const enabledChanged = ip.toneMappingEnabled !== prevEnabled;
  const nextToneMappingId = ip.toneMapping?.id ?? StandardToneMapping.id;
  const toneMappingChanged = ip.toneMappingEnabled && nextToneMappingId !== prevToneMappingId;
  if (enabledChanged || toneMappingChanged) {
    await rebuildScenePbrPipelines(scene);
  }
}

const ACES_HELPERS_WGSL = `
const ACESInputMat = mat3x3<f32>(vec3<f32>(0.59719,0.07600,0.02840),vec3<f32>(0.35458,0.90834,0.13383),vec3<f32>(0.04823,0.01566,0.83777));
const ACESOutputMat = mat3x3<f32>(vec3<f32>(1.60475,-0.10208,-0.00327),vec3<f32>(-0.53108,1.10813,-0.07276),vec3<f32>(-0.07367,-0.00605,1.07602));
fn RRTAndODTFit(v: vec3<f32>) -> vec3<f32> { let a = v*(v+0.0245786)-0.000090537; let b = v*(0.983729*v+0.4329510)+0.238081; return a/b; }
fn ACESFitted(color: vec3<f32>) -> vec3<f32> { var c = ACESInputMat*color; c = RRTAndODTFit(c); c = ACESOutputMat*c; return saturate(c); }
`;
const ACES_TONEMAP_CALL_WGSL = `color *= scene.vImageInfos.x;
color = ACESFitted(color);`;
const AcesToneMapping = {
  id: "aces",
  helpersWGSL: ACES_HELPERS_WGSL,
  callWGSL: ACES_TONEMAP_CALL_WGSL
};

const NEUTRAL_HELPERS_WGSL = `
const PBRNeutralStartCompression: f32 = 0.8 - 0.04;
const PBRNeutralDesaturation: f32 = 0.15;
fn PBRNeutralToneMapping(color: vec3<f32>) -> vec3<f32> {
    let x = min(color.r, min(color.g, color.b));
    let offset = select(0.04, x - 6.25 * x * x, x < 0.08);
    var result = color - offset;
    let peak = max(result.r, max(result.g, result.b));
    if (peak < PBRNeutralStartCompression) { return result; }
    let d = 1.0 - PBRNeutralStartCompression;
    let newPeak = 1.0 - d * d / (peak + d - PBRNeutralStartCompression);
    result *= newPeak / peak;
    let g = 1.0 - 1.0 / (PBRNeutralDesaturation * (peak - newPeak) + 1.0);
    return mix(result, newPeak * vec3<f32>(1.0, 1.0, 1.0), g);
}
`;
const NEUTRAL_TONEMAP_CALL_WGSL = `color *= scene.vImageInfos.x;
color = PBRNeutralToneMapping(color);`;
const NeutralToneMapping = {
  id: "neutral",
  helpersWGSL: NEUTRAL_HELPERS_WGSL,
  callWGSL: NEUTRAL_TONEMAP_CALL_WGSL
};

function mat4Determinant3(m) {
  return m[0] * (m[5] * m[10] - m[6] * m[9]) + m[1] * (m[6] * m[8] - m[4] * m[10]) + m[2] * (m[4] * m[9] - m[5] * m[8]);
}

function createTransformNode(name, px = 0, py = 0, pz = 0, qx = 0, qy = 0, qz = 0, qw = 1, sx = 1, sy = 1, sz = 1) {
  return createSceneNode(name, px, py, pz, qx, qy, qz, qw, sx, sy, sz);
}

let _tmpLocal = null;
let _tmpAnim = null;
function getLoaderTmpLocal() {
  return _tmpLocal ??= allocateMat4();
}
function getLoaderTmpAnim() {
  return _tmpAnim ??= allocateMat4();
}

const FLOAT = 5126;
const UNSIGNED_SHORT = 5123;
const UNSIGNED_INT = 5125;
const UNSIGNED_BYTE = 5121;
const TYPE_SIZES = {
  SCALAR: 1,
  VEC2: 2,
  VEC3: 3,
  VEC4: 4,
  MAT2: 4,
  MAT3: 9,
  MAT4: 16
};
function resolveAccessor(json, binChunk, accessorIdx) {
  const accessor = json.accessors[accessorIdx];
  const componentCount = TYPE_SIZES[accessor.type] ?? 1;
  const count = accessor.count;
  const len = count * componentCount;
  let Ctor;
  switch (accessor.componentType) {
    case FLOAT:
      Ctor = F32;
      break;
    case UNSIGNED_SHORT:
      Ctor = U16;
      break;
    case UNSIGNED_INT:
      Ctor = U32;
      break;
    case UNSIGNED_BYTE:
      Ctor = U8;
      break;
    case 5122:
      Ctor = I16;
      break;
    case 5120:
      Ctor = I8;
      break;
    default:
      ThrowLiteError(108, accessor.componentType);
  }
  const data = accessor.bufferView === void 0 ? new Ctor(len) : new Ctor(binChunk.buffer, binChunk.byteOffset + (json.bufferViews[accessor.bufferView].byteOffset ?? 0) + (accessor.byteOffset ?? 0), len);
  return { _data: data, _count: count, _componentCount: componentCount };
}
function getTextureImageIndex(tex) {
  return tex.extensions?.EXT_texture_webp?.source ?? tex.source;
}
function anyPrimitive(json, pred) {
  for (const m of json.meshes ?? []) {
    for (const p of m.primitives ?? []) {
      if (pred(p)) {
        return true;
      }
    }
  }
  return false;
}
function needsOrmComposite(json) {
  const mats = json.materials ?? [];
  const textures = json.textures ?? [];
  for (const m of mats) {
    const mr = m.pbrMetallicRoughness?.metallicRoughnessTexture;
    const occ = m.occlusionTexture;
    if (mr && occ && textures[mr.index] && textures[occ.index] && getTextureImageIndex(textures[mr.index]) !== getTextureImageIndex(textures[occ.index])) {
      return true;
    }
  }
  return false;
}
async function resolveImage(json, binChunk, imageIdx, baseUrl) {
  const image = json.images[imageIdx];
  if (image.bufferView !== void 0) {
    const bv = json.bufferViews[image.bufferView];
    const offset = binChunk.byteOffset + (bv.byteOffset ?? 0);
    const slice = binChunk.buffer.slice(offset, offset + bv.byteLength);
    const blob = new Blob([slice], { type: image.mimeType ?? "image/png" });
    return createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" });
  }
  if (image.uri) {
    return (await import('./gltf-json-asset-DuIFR7lm.esm.js')).resolveExternalImage(image.uri, baseUrl);
  }
  ThrowLiteError(109);
}
const RH_TO_LH_ROOT = new F32([-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
function buildParentMap(json) {
  const parentMap = /* @__PURE__ */ new Map();
  const nodes = json.nodes ?? [];
  for (let i = 0; i < nodes.length; i++) {
    const children = nodes[i].children;
    if (children) {
      for (const childIdx of children) {
        parentMap.set(childIdx, i);
      }
    }
  }
  return parentMap;
}
function findParent(parentMap, childIdx) {
  return parentMap.get(childIdx) ?? -1;
}
function computeNodeWorldMatrix(json, nodeIdx, parentMap, cache) {
  const cached = cache.get(nodeIdx);
  if (cached) {
    return cached;
  }
  const node = json.nodes[nodeIdx];
  const parentIdx = findParent(parentMap, nodeIdx);
  const parentWorld = parentIdx !== -1 ? computeNodeWorldMatrix(json, parentIdx, parentMap, cache) : RH_TO_LH_ROOT;
  let localBuf;
  if (node.matrix) {
    localBuf = new F32(node.matrix);
  } else {
    const t = node.translation ?? [0, 0, 0];
    const r = node.rotation ?? [0, 0, 0, 1];
    const s = node.scale ?? [1, 1, 1];
    const local = getLoaderTmpLocal();
    mat4ComposeInto(local, 0, t[0], t[1], t[2], r[0], r[1], r[2], r[3], s[0], s[1], s[2]);
    localBuf = local;
  }
  const world = new F32(16);
  mat4MultiplyInto(world, 0, parentWorld, 0, localBuf, 0);
  cache.set(nodeIdx, world);
  return world;
}

async function assembleMaterial(json, binChunk, materialIdx, baseUrl, imageCache) {
  const rawMat = json.materials?.[materialIdx];
  const mat = rawMat ?? {};
  const pbr = mat.pbrMetallicRoughness ?? {};
  const fetchImg = makeImageFetcher(json, binChunk, baseUrl, imageCache);
  const [baseColorImg, mrImg, normalImg, occlusionImg, emissiveImg] = await Promise.all([
    fetchImg(pbr.baseColorTexture),
    fetchImg(pbr.metallicRoughnessTexture),
    fetchImg(mat.normalTexture),
    fetchImg(mat.occlusionTexture),
    fetchImg(mat.emissiveTexture)
  ]);
  return {
    _baseColorFactor: pbr.baseColorFactor ?? [1, 1, 1, 1],
    _metallicFactor: pbr.metallicFactor ?? 1,
    _roughnessFactor: pbr.roughnessFactor ?? 1,
    _emissiveFactor: mat.emissiveFactor ?? [0, 0, 0],
    _baseColorImage: baseColorImg,
    _metallicRoughnessImage: mrImg,
    _normalImage: normalImg,
    _normalScale: typeof mat.normalTexture?.scale === "number" ? mat.normalTexture.scale : 1,
    _occlusionTexCoord: typeof mat.occlusionTexture?.texCoord === "number" ? mat.occlusionTexture.texCoord : 0,
    _occlusionImage: occlusionImg,
    _emissiveImage: emissiveImg,
    _doubleSided: !!mat.doubleSided,
    _alphaMode: mat.alphaMode ?? "OPAQUE",
    _alphaCutoff: mat.alphaCutoff ?? 0.5,
    _rawMatDef: rawMat
  };
}
function makeImageFetcher(json, binChunk, baseUrl, imageCache) {
  return (texInfo) => {
    if (!texInfo) {
      return Promise.resolve(null);
    }
    const imgIdx = getTextureImageIndex(json.textures[texInfo.index]);
    return imageCache[imgIdx] ??= resolveImage(json, binChunk, imgIdx, baseUrl);
  };
}

function linearToSrgbByte(v) {
  const c = Math.max(0, Math.min(1, v));
  return Math.round((c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055) * 255);
}

const identityTexWrap = (tex) => tex;
function uploadTex(engine, bitmap, srgb, sampler, generateMipmaps, fallback) {
  const device = engine._device;
  const w = bitmap?.width ?? 1;
  const h = bitmap?.height ?? 1;
  const fmt = srgb ? "rgba8unorm-srgb" : "rgba8unorm";
  const mips = bitmap ? mipLevelCount(w, h) : 1;
  const tex = device.createTexture({
    size: { width: w, height: h },
    format: fmt,
    usage: TU.TEXTURE_BINDING | TU.COPY_DST | TU.COPY_SRC | TU.RENDER_ATTACHMENT,
    mipLevelCount: mips
  });
  if (bitmap) {
    device.queue.copyExternalImageToTexture({ source: bitmap }, { texture: tex, premultipliedAlpha: false }, { width: w, height: h });
    generateMipmaps(engine, tex);
  } else {
    device.queue.writeTexture({ texture: tex }, fallback ?? new U8([255, 255, 255, 255]), { bytesPerRow: 4 }, { width: 1, height: 1 });
  }
  const result = {
    texture: tex,
    view: tex.createView(),
    sampler,
    width: w,
    height: h
  };
  engine._dlr?.b(result, bitmap, srgb, !!bitmap, fallback);
  return result;
}
function uploadBaseColorFactorTexture(engine, factor, sampler, generateMipmaps) {
  return uploadTex(
    engine,
    null,
    true,
    sampler,
    generateMipmaps,
    new U8([linearToSrgbByte(factor[0]), linearToSrgbByte(factor[1]), linearToSrgbByte(factor[2]), Math.round(Math.max(0, Math.min(1, factor[3])) * 255)])
  );
}
function uploadOrmFactorTexture(engine, roughness, metallic, sampler, generateMipmaps) {
  const clamp = (value) => Math.round(Math.max(0, Math.min(1, value)) * 255);
  return uploadTex(engine, null, false, sampler, generateMipmaps, new U8([255, clamp(roughness), clamp(metallic), 255]));
}
function needsGltfEmissive(mat, emissiveTexture) {
  const ef = mat._emissiveFactor;
  return !(ef[0] === 0 && ef[1] === 0 && ef[2] === 0 || !!emissiveTexture && ef[0] === 1 && ef[1] === 1 && ef[2] === 1);
}
async function applyGltfOptInPbrFeatures(props, mat) {
  if (!props._emissiveColor && needsGltfEmissive(mat, props.emissiveTexture)) {
    const { setPbrEmissive } = await import('./set-emissive-BaULKXk1.esm.js');
    const ef = mat._emissiveFactor;
    setPbrEmissive(props, [ef[0], ef[1], ef[2]]);
  }
  if (mat._alphaMode === "MASK") {
    const { setPbrAlphaCutoff } = await import('./set-alpha-cutoff-CHkekqDI.esm.js');
    setPbrAlphaCutoff(props, mat._alphaCutoff);
  }
}
function assemblePbrProps(mat, baseColorTexture, ormTexture, normalTexture, emissiveTexture, extLayers) {
  const props = {
    baseColorTexture,
    normalTexture,
    ormTexture,
    emissiveTexture,
    ...mat._baseColorImage && !isDefaultBaseColorFactor(mat._baseColorFactor) ? { baseColorFactor: mat._baseColorFactor } : void 0,
    doubleSided: mat._doubleSided,
    occlusionStrength: mat._occlusionImage ? 1 : 0,
    ...mat._normalScale !== 1 ? { normalTextureScale: mat._normalScale } : void 0,
    ...mat._metallicRoughnessImage ? { metallicFactor: mat._metallicFactor, roughnessFactor: mat._roughnessFactor } : void 0,
    enableSpecularAA: true,
    ...mat._alphaMode === "BLEND" ? { alphaBlend: true, alpha: mat._baseColorFactor[3] } : void 0,
    ...mat._alphaMode === "MASK" ? { alpha: mat._baseColorFactor[3] } : void 0,
    ...mat._rawMatDef?.name ? { name: mat._rawMatDef.name } : void 0,
    ...extLayers,
    _buildGroup: getPbrGroupBuilder(),
    _uboVersion: 0
  };
  return props;
}
function isDefaultBaseColorFactor(f) {
  return f[0] === 1 && f[1] === 1 && f[2] === 1 && f[3] === 1;
}
function buildDefaultPbrTextures(engine, mat, sampler, generateMipmaps, getCachedTex) {
  const baseColorTexture = mat._baseColorImage ? getCachedTex(mat._baseColorImage, true) : uploadBaseColorFactorTexture(engine, mat._baseColorFactor, sampler, generateMipmaps);
  const normalTexture = mat._normalImage ? getCachedTex(mat._normalImage, false) : void 0;
  const emissiveTexture = mat._emissiveImage ? getCachedTex(mat._emissiveImage, true) : void 0;
  const single = mat._metallicRoughnessImage ?? mat._occlusionImage;
  let ormTexture;
  if (single && (!mat._metallicRoughnessImage || !mat._occlusionImage || mat._metallicRoughnessImage === mat._occlusionImage)) {
    ormTexture = getCachedTex(single, false);
  } else if (!single) {
    ormTexture = uploadOrmFactorTexture(engine, mat._roughnessFactor, mat._metallicFactor, sampler, generateMipmaps);
  } else {
    ormTexture = getCachedTex(mat._metallicRoughnessImage, false);
  }
  return { baseColorTexture, ormTexture, normalTexture, emissiveTexture };
}

let _interleavePromise;
function loadInterleave() {
  return _interleavePromise ??= import('./gltf-interleave-DsM6I3ck.esm.js');
}
let _gltfFeatureRegistryPromise;
function importGltfFeatureRegistry() {
  return _gltfFeatureRegistryPromise ??= import('./gltf-feature-registry-Bi0l6k0Y.esm.js');
}
let _colorNormalizePromise;
function importColorNormalize() {
  return _colorNormalizePromise ??= import('./gltf-color-normalize-DMyW2DtT.esm.js');
}
function buildTightGltfMesh(engine, meshData, material, name, source) {
  const [boundMin, boundMax] = computeAabb(meshData._positions);
  const indices = meshData._indices;
  const uint32 = indices instanceof U32;
  const gpu = source ? source._gpu : {
    positionBuffer: createMappedBuffer(engine, meshData._positions, BU.VERTEX),
    normalBuffer: createMappedBuffer(engine, meshData._normals, BU.VERTEX),
    tangentBuffer: meshData._tangents ? createMappedBuffer(engine, meshData._tangents, BU.VERTEX) : null,
    uvBuffer: createMappedBuffer(engine, meshData._uvs, BU.VERTEX),
    uv2Buffer: meshData._uv2s ? createMappedBuffer(engine, meshData._uv2s, BU.VERTEX) : null,
    colorBuffer: meshData._colors ? createMappedBuffer(engine, meshData._colors, BU.VERTEX) : null,
    indexBuffer: createMappedBuffer(engine, indices, BU.INDEX),
    indexCount: meshData._indexCount,
    indexFormat: uint32 ? "uint32" : "uint16"
  };
  const mesh = initMeshTransform({
    name,
    material,
    receiveShadows: false,
    boundMin,
    boundMax,
    _gpu: gpu,
    _flatNormal: meshData._flatNormal
  });
  mesh._cpuPositions = meshData._positions;
  mesh._cpuNormals = meshData._normals;
  mesh._cpuUvs = meshData._uvs;
  mesh._cpuIndices = source ? source._cpuIndices : uint32 ? indices : new U32(indices);
  engine._dlr?.m(mesh, meshData._uv2s, meshData._tangents, meshData._colors, indices, gpu.indexFormat);
  return mesh;
}
async function loadGltf(engine, source) {
  const { json, binChunk, baseUrl } = await fetchGltfAsset(source);
  const parentMap = buildParentMap(json);
  const worldMatrixCache = /* @__PURE__ */ new Map();
  const featureRegistry = assetUsesGltfFeatures(json) ? await importGltfFeatureRegistry() : void 0;
  const features = featureRegistry ? await featureRegistry.loadGltfFeatures(json) : [];
  let activeBin = binChunk;
  for (const f of features) {
    if (f.preParse) {
      const replacement = await f.preParse(json, activeBin);
      if (replacement) {
        activeBin = replacement;
      }
    }
  }
  const matExts = features.filter((f) => f.applyMaterial);
  const texWraps = features.filter((f) => f.wrapTexture).map((f) => f.wrapTexture);
  const wrapTex = !texWraps.length ? identityTexWrap : (tex, ti) => texWraps.reduce((acc, w) => w(acc, ti), tex);
  const decodedPrimitives = /* @__PURE__ */ new Map();
  for (const frag of await Promise.all(features.flatMap((f) => f.preMesh ? [f.preMesh(json, activeBin, baseUrl)] : []))) {
    for (const [k, v] of frag) {
      decodedPrimitives.set(k, v);
    }
  }
  const meshDatas = await extractAllMeshes(json, activeBin, baseUrl, parentMap, worldMatrixCache, decodedPrimitives);
  const ctx = {
    _engine: engine,
    _json: json,
    _binChunk: activeBin,
    _baseUrl: baseUrl,
    _parentMap: parentMap,
    _worldMatrixCache: worldMatrixCache,
    _matExts: matExts,
    _runMatExts: featureRegistry?.runGltfMaterialFeatures,
    _wrapTex: wrapTex
  };
  const meshes = await uploadMeshes(meshDatas, features, ctx);
  const { root, nodeMap } = buildNodeHierarchy(json, meshes, meshDatas);
  ctx._nodeMap = nodeMap;
  const assetFragments = await Promise.all(features.flatMap((f) => f.applyAsset ? [f.applyAsset(meshes, root, ctx)] : []));
  const container = { entities: [root] };
  for (const frag of assetFragments) {
    if (frag.entities?.length) {
      container.entities.push(...frag.entities);
    }
    const { entities: _ignored, _sceneSetup, ...rest } = frag;
    Object.assign(container, rest);
    if (_sceneSetup) {
      const prev = container._sceneSetup;
      container._sceneSetup = (scene, target) => {
        prev?.(scene, target);
        _sceneSetup(scene, target);
      };
    }
  }
  return container;
}
async function fetchGltfAsset(source) {
  const isUrl = typeof source === "string";
  let baseUrl = "";
  if (isUrl) {
    try {
      baseUrl = new URL(".", new URL(source, globalThis.location?.href)) + "";
    } catch {
    }
  }
  const buffer = isUrl ? await (await fetch(source)).arrayBuffer() : source instanceof Blob ? await source.arrayBuffer() : source;
  if (buffer.byteLength >= 4 && new DV(buffer).getUint32(0, true) === 1179937895) {
    const glb = await import('./gltf-glb-parser-vjcPWLyX.esm.js');
    return { ...glb.parseGlbContainer(buffer), baseUrl };
  }
  const jsonAsset = await import('./gltf-json-asset-DuIFR7lm.esm.js');
  return jsonAsset.parseGltfJsonAsset(buffer, baseUrl);
}
function assetUsesGltfFeatures(json) {
  return json.extensionsUsed?.length || json.animations?.length || // "extras" (per-item metadata) or "sparse" (sparse accessor) anywhere in the asset means a
  // feature module is needed. One stringify covers both — same cheap substring gate as extras.
  /extras|sparse/.test(JSON.stringify(json)) || json.skins?.length && anyPrimitive(json, (p) => p.attributes?.JOINTS_0 !== void 0) || anyPrimitive(json, (p) => !!p.targets?.length) || // A node with a negative-determinant local transform (odd negative scale, or a `matrix`
  // with negative 3x3 determinant) may need the negative-winding feature. This mirrors the
  // registry's `hasNegDetNode` predicate so a positive-determinant `matrix` node — extremely
  // common, e.g. TextureSettingsTest — does NOT needlessly pull the feature registry.
  json.nodes?.some((n) => n.scale ? n.scale[0] * n.scale[1] * n.scale[2] < 0 : n.matrix ? mat4Determinant3(n.matrix) < 0 : false) || // Non-triangle primitive topology (POINTS/LINES/LINE_STRIP/TRIANGLE_STRIP).
  anyPrimitive(json, (p) => p.mode !== void 0 && p.mode !== 4) || needsOrmComposite(json);
}
function buildNodeHierarchy(json, meshes, meshDatas) {
  const nodeToMeshes = [];
  for (let i = 0; i < meshDatas.length; i++) {
    const ni = meshDatas[i]._nodeIndex;
    (nodeToMeshes[ni] ??= []).push(meshes[i]);
  }
  const nodeMap = new Array(json.nodes?.length ?? 0);
  function buildNode(nodeIdx) {
    const node = json.nodes[nodeIdx];
    const name = node.name ?? `node_${nodeIdx}`;
    let tn;
    if (node.matrix) {
      tn = createSceneNodeFromMatrix(name, node.matrix);
    } else {
      const t = node.translation ?? [0, 0, 0];
      const r = node.rotation ?? [0, 0, 0, 1];
      const s = node.scale ?? [1, 1, 1];
      tn = createTransformNode(name, t[0], t[1], t[2], r[0], r[1], r[2], r[3], s[0], s[1], s[2]);
    }
    nodeMap[nodeIdx] = tn;
    if (node.children) {
      for (const childIdx of node.children) {
        tn.children.push(buildNode(childIdx));
      }
    }
    const nodeMeshes = nodeToMeshes[nodeIdx] ?? [];
    tn.children.push(...nodeMeshes);
    return tn;
  }
  const sceneRoots = json.scenes?.[json.scene ?? 0]?.nodes ?? [];
  const rootChildren = sceneRoots.map((ni) => buildNode(ni));
  const root = createTransformNode("__root__", 0, 0, 0, 0, 0, 0, 1, -1, 1, 1);
  root.children.push(...rootChildren);
  return { root, nodeMap };
}
async function extractAllMeshes(json, binChunk, baseUrl, parentMap, worldMatrixCache, decodedPrimitives) {
  const imageCache = [];
  const matCache = [];
  const getMat = (matIdx) => {
    const key = (matIdx ?? -1) + 1;
    return matCache[key] ??= assembleMaterial(json, binChunk, key - 1, baseUrl, imageCache);
  };
  const partials = [];
  const matPromises = [];
  const _accs = json.accessors;
  const _bvs = json.bufferViews;
  const _strided = (p) => {
    for (const k in p.attributes) {
      const a = _accs[p.attributes[k]];
      const s = _bvs?.[a?.bufferView]?.byteStride;
      if (s !== void 0 && s !== (TYPE_SIZES[a.type] ?? 1) * (a.componentType === 5126 || a.componentType === 5125 ? 4 : a.componentType === 5123 || a.componentType === 5122 ? 2 : 1)) {
        return true;
      }
    }
    return false;
  };
  for (let nodeIdx = 0; nodeIdx < json.nodes.length; nodeIdx++) {
    const node = json.nodes[nodeIdx];
    if (node.mesh === void 0) {
      continue;
    }
    const meshIndex = node.mesh;
    const mesh = json.meshes[meshIndex];
    const worldMatrix = computeNodeWorldMatrix(json, nodeIdx, parentMap, worldMatrixCache);
    for (let primitiveIndex = 0; primitiveIndex < mesh.primitives.length; primitiveIndex++) {
      const primitive = mesh.primitives[primitiveIndex];
      const attrs = primitive.attributes;
      const decoded = decodedPrimitives.get(primitive);
      if (!decoded && _strided(primitive)) {
        const ip = await (await loadInterleave()).buildInterleavedPartial(json, binChunk, primitive, worldMatrix, nodeIdx);
        if (ip) {
          matPromises.push(getMat(primitive.material));
          partials.push(ip);
          continue;
        }
      }
      const resolveAttr = (name) => {
        if (decoded && decoded._attributes.has(name)) {
          const data = decoded._attributes.get(name);
          const componentCount = data.length / decoded._vertexCount;
          return { _data: data, _count: decoded._vertexCount, _componentCount: componentCount };
        }
        const idx = attrs[name];
        return idx !== void 0 ? resolveAccessor(json, binChunk, idx) : null;
      };
      const posData = resolveAttr("POSITION");
      const normData = resolveAttr("NORMAL");
      const uvData = resolveAttr("TEXCOORD_0");
      const uv2Data = resolveAttr("TEXCOORD_1");
      const tanData = resolveAttr("TANGENT");
      const colorData = resolveAttr("COLOR_0");
      const idxData = decoded ? decoded._indexCount > 0 ? { _data: decoded._indices, _count: decoded._indexCount} : null : primitive.indices !== void 0 ? resolveAccessor(json, binChunk, primitive.indices) : null;
      const normalsHelper = !idxData || !normData ? await import('./gltf-normals-DEUAzhrX.esm.js') : null;
      const colors = colorData ? (await importColorNormalize()).normalizeColorToVec4(colorData._data, colorData._count, colorData._componentCount) : null;
      const uvs = uvData ? uvData._data instanceof F32 ? uvData._data : (await importColorNormalize()).normalizeUvToVec2(uvData._data, uvData._count) : new F32(posData._count * 2);
      const uv2s = uv2Data ? uv2Data._data instanceof F32 ? uv2Data._data : (await importColorNormalize()).normalizeUvToVec2(uv2Data._data, uv2Data._count) : null;
      const indices = idxData ? idxData._data instanceof U32 ? new U32(idxData._data) : idxData._data instanceof U8 ? Uint16Array.from(idxData._data) : new U16(idxData._data.buffer, idxData._data.byteOffset, idxData._count) : normalsHelper.createSequentialIndices(posData._count);
      matPromises.push(getMat(primitive.material));
      const normals = normData ? normData._data : normalsHelper.computeSmoothNormals(posData._data, indices, posData._count);
      partials.push({
        _positions: posData._data,
        _normals: normals,
        _tangents: tanData ? tanData._data : null,
        _uvs: uvs,
        _uv2s: uv2s,
        _colors: colors,
        _flatNormal: !normData,
        _indices: indices,
        _vertexCount: posData._count,
        _indexCount: indices.length,
        _worldMatrix: worldMatrix,
        _nodeIndex: nodeIdx,
        _primitive: primitive,
        _decoded: decoded
      });
    }
  }
  const materials = await Promise.all(matPromises);
  return partials.map((p, i) => ({ ...p, _material: materials[i] }));
}
let _generateMipmaps = null;
async function ensureMipmapModule() {
  if (!_generateMipmaps) {
    _generateMipmaps = (await import('./generate-mipmaps-BpLaf4fV.esm.js')).generateMipmaps;
  }
}
async function uploadMeshes(meshDatas, features, ctx) {
  const { _engine: engine, _json: json, _binChunk: binChunk, _baseUrl: baseUrl, _matExts: matExts, _wrapTex: wrapTex } = ctx;
  const sampler = getOrCreateSampler(engine, {
    magFilter: "linear",
    minFilter: "linear",
    mipmapFilter: "linear",
    addressModeU: "repeat",
    addressModeV: "repeat",
    maxAnisotropy: 4
  });
  let samplerFor;
  let buildSampledPbrTextures;
  if (json.samplers?.some((s) => s.wrapS > 10497 || s.wrapT > 10497 || s.magFilter === 9728 || s.minFilter != null && s.minFilter !== 9729 && s.minFilter !== 9987)) {
    const mod = await import('./gltf-sampler-desc-BuA447gC.esm.js');
    samplerFor = mod.makeSamplerFor(engine, json, sampler);
    buildSampledPbrTextures = mod.buildSampledPbrTextures;
  }
  await ensureMipmapModule();
  const meshFeatures = features.filter((f) => f.applyMesh);
  const texCache = /* @__PURE__ */ new Map();
  const getCachedTexture = (bitmap, srgb) => {
    let textures = texCache.get(bitmap);
    if (!textures) {
      texCache.set(bitmap, textures = []);
    }
    const key = +srgb;
    let tex = textures[key];
    if (!tex) {
      tex = uploadTex(engine, bitmap, srgb, sampler, _generateMipmaps);
      textures[key] = tex;
    }
    return tex;
  };
  const extImageCache = matExts.length ? [] : null;
  const extFetchImg = extImageCache ? makeImageFetcher(json, binChunk, baseUrl, extImageCache) : null;
  const extCtx = {
    _engine: engine,
    async _texture(texInfo, sRGB) {
      if (!texInfo || !extFetchImg) {
        return void 0;
      }
      const img = await extFetchImg(texInfo);
      return img ? wrapTex(getCachedTexture(img, sRGB), texInfo) : void 0;
    },
    _uploadImage(bitmap, sRGB) {
      return uploadTex(engine, bitmap, sRGB, sampler, _generateMipmaps);
    }
  };
  let _needsPbrExt = wrapTex !== identityTexWrap;
  if (!_needsPbrExt) {
    const mats = json.materials;
    if (mats && JSON.stringify(mats).includes('"texCoord":1')) {
      _needsPbrExt = true;
    }
  }
  let _pbrExtPromise = null;
  const _ensurePbrExt = () => _pbrExtPromise ??= import('./gltf-pbr-builder-ext-BKOr-vYk.esm.js');
  const builtMaterialCache = /* @__PURE__ */ new Map();
  const buildPbrFromGltfMat = (mat) => {
    let cached = builtMaterialCache.get(mat);
    if (!cached) {
      cached = (async () => {
        const extLayers = matExts.length ? await ctx._runMatExts(mat, matExts, extCtx) : void 0;
        let props;
        if (_needsPbrExt) {
          const extMod = await _ensurePbrExt();
          const tex = extMod.buildDefaultPbrTexturesExt(engine, mat, sampler, _generateMipmaps, getCachedTexture, wrapTex, samplerFor);
          props = extMod.assemblePbrPropsExt(mat, tex, extLayers);
          await extMod.applyGltfUvTransform(props, tex);
        } else {
          const tex = buildSampledPbrTextures ? buildSampledPbrTextures(engine, mat, sampler, _generateMipmaps, samplerFor, getCachedTexture) : buildDefaultPbrTextures(engine, mat, sampler, _generateMipmaps, getCachedTexture);
          props = assemblePbrProps(mat, tex.baseColorTexture, tex.ormTexture, tex.normalTexture, tex.emissiveTexture, extLayers);
        }
        await applyGltfOptInPbrFeatures(props, mat);
        return props;
      })();
      builtMaterialCache.set(mat, cached);
    }
    return cached;
  };
  if (new Set(meshDatas.map((m) => m._primitive)).size < meshDatas.length) {
    return import('./gltf-share-DR_p1XRc.esm.js').then((module) => module.share(meshDatas, buildPbrFromGltfMat, buildTightGltfMesh, meshFeatures, ctx));
  }
  return Promise.all(
    meshDatas.map(async (m, i) => {
      const material = await buildPbrFromGltfMat(m._material);
      const meshName = json.meshes[json.nodes[m._nodeIndex].mesh].name || `gltf_mesh_${i}`;
      const mesh = m._vb ? (await loadInterleave()).buildInterleavedMesh(engine, m, i, material, meshName) : buildTightGltfMesh(engine, m, material, meshName);
      mesh._authoredSign = -1;
      await Promise.all(meshFeatures.map((f) => f.applyMesh(m, mesh, ctx)));
      return mesh;
    })
  );
}

function getContainerMeshes(container) {
  const meshes = [];
  const seen = /* @__PURE__ */ new Set();
  const visit = (node) => {
    if (seen.has(node)) {
      return;
    }
    seen.add(node);
    if (node._gpu) {
      meshes.push(node);
    }
    const children = node.children;
    if (children) {
      for (const child of children) {
        visit(child);
      }
    }
  };
  for (const entity of container.entities) {
    if ("lightType" in entity) {
      continue;
    }
    visit(entity);
  }
  return meshes;
}

function getVariantNames(container) {
  return container.materialVariants?.names ?? [];
}
function selectVariant(container, variantName) {
  const data = container.materialVariants;
  if (!data) {
    return;
  }
  for (const entry of data.originals) {
    entry.mesh.material = entry.material;
  }
  const entries = data.variants[variantName];
  if (entries) {
    for (const entry of entries) {
      entry.mesh.material = entry.material;
    }
  }
}
function resetVariant(container) {
  const data = container.materialVariants;
  if (!data) {
    return;
  }
  for (const entry of data.originals) {
    entry.mesh.material = entry.material;
  }
}

const ENV_MAGIC = new U8([134, 22, 135, 150, 246, 214, 150, 54]);
function parseEnvFile(buffer) {
  const bytes = new U8(buffer);
  for (let i = 0; i < 8; i++) {
    if (bytes[i] !== ENV_MAGIC[i]) {
      throw new Error("Invalid .env file: bad magic");
    }
  }
  let pos = 8;
  while (pos < bytes.length && bytes[pos] !== 0) {
    pos++;
  }
  const jsonStr = new TextDecoder().decode(bytes.subarray(8, pos));
  pos++;
  const binaryStart = pos;
  const manifest = JSON.parse(jsonStr);
  const width = manifest.width;
  const mipCount = mipLevelCount(width, width);
  const irr = manifest.irradiance;
  const irradianceSH = new F32(27);
  const shKeys = ["x", "y", "z", "xx", "yy", "zz", "yz", "zx", "xy"];
  for (let i = 0; i < 9; i++) {
    const coeff = irr[shKeys[i]];
    irradianceSH[i * 3] = coeff[0];
    irradianceSH[i * 3 + 1] = coeff[1];
    irradianceSH[i * 3 + 2] = coeff[2];
  }
  const mipmaps = manifest.specular.mipmaps;
  const imageType = manifest.imageType || "image/png";
  const faceBlobs = [];
  for (const entry of mipmaps) {
    const start = binaryStart + entry.position;
    const slice = buffer.slice(start, start + entry.length);
    faceBlobs.push(new Blob([slice], { type: imageType }));
  }
  return { faceBlobs, irradianceSH, width, mipCount };
}

function computeSceneSize(scene, userSkyboxSize) {
  const acc = emptyWorldAabb();
  for (const m of scene.meshes) {
    expandWorldAabbForMesh(acc, m);
  }
  const minX = acc.minX, minY = acc.minY, minZ = acc.minZ;
  const maxX = acc.maxX, maxY = acc.maxY, maxZ = acc.maxZ;
  if (!isFinite(minX)) {
    return { groundSize: 15, skyboxSize: userSkyboxSize, rootPosition: [0, 0, 0] };
  }
  const dx = maxX - minX, dy = maxY - minY, dz = maxZ - minZ;
  const sceneDiagonalLength = Math.sqrt(dx * dx + dy * dy + dz * dz);
  let groundSize = 15;
  let skyboxSize = userSkyboxSize;
  const cam = scene.camera;
  if (cam && "upperRadiusLimit" in cam && cam.upperRadiusLimit) {
    groundSize = cam.upperRadiusLimit * 2;
    skyboxSize = groundSize;
  }
  if (sceneDiagonalLength > groundSize) {
    groundSize = sceneDiagonalLength * 2;
    skyboxSize = groundSize;
  }
  groundSize *= 1.1;
  skyboxSize *= 1.5;
  const rootPosition = [minX + dx * 0.5, minY - 1e-5, minZ + dz * 0.5];
  return { groundSize, skyboxSize, rootPosition };
}

var sceneSize = /*#__PURE__*/Object.freeze({
    __proto__: null,
    computeSceneSize: computeSceneSize
});

async function loadBrdfImage(url) {
  const response = await fetch(url);
  if (response.ok) {
    try {
      return await createImageBitmap(await response.blob(), { premultiplyAlpha: "none", colorSpaceConversion: "none" });
    } catch {
    }
  }
  throw new Error(`BRDF LUT '${url}' is not an image (${response.status} ${response.headers.get("content-type") ?? ""}).`);
}
function assembleEnvironmentTextures(specularCube, brdfLut, irradianceSH, lodGenerationScale, engine, sphericalHarmonics) {
  return {
    specularCube,
    specularCubeView: specularCube.createView({ dimension: "cube" }),
    brdfLut,
    brdfLutView: brdfLut.createView(),
    cubeSampler: getTrilinearSampler(engine),
    brdfSampler: getBilinearSampler(engine),
    irradianceSH,
    sphericalHarmonics: sphericalHarmonics ?? polynomialToPreScaledHarmonics(irradianceSH),
    lodGenerationScale
  };
}

async function loadEnvironment(scene, url, options) {
  const engine = scene.surface.engine;
  const envPromise = fetch(url).then((r) => r.arrayBuffer());
  const brdfPromise = loadBrdfImage(options.brdfUrl);
  const envBuffer = await envPromise;
  const { faceBlobs, irradianceSH, width, mipCount } = parseEnvFile(envBuffer);
  const faceImages = await Promise.all(faceBlobs.map((blob) => createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" })));
  const rgbd = await import('./rgbd-decode-BjFbAbyB.esm.js');
  const specularCube = rgbd.uploadCubemapRGBD(engine, faceImages, width, mipCount);
  for (const img of faceImages) {
    img.close();
  }
  const brdfImage = await brdfPromise;
  const brdfLut = rgbd.decodeBrdfPng(engine, brdfImage);
  brdfImage.close();
  const textures = assembleEnvironmentTextures(specularCube, brdfLut, irradianceSH, 0.8, engine);
  scene._envTextures = textures;
  registerEnvSceneUniforms(scene);
  acquireGPUTexture(specularCube);
  acquireGPUTexture(brdfLut);
  scene._disposables.push(() => {
    releaseGPUTexture(specularCube);
    releaseGPUTexture(brdfLut);
  });
  scene.imageProcessing.toneMappingEnabled = true;
  scene.imageProcessing.exposure = 0.8;
  scene.imageProcessing.contrast = 1.2;
  const groundUrl = options?.groundTextureUrl;
  groundUrl ? fetch(groundUrl).then((r) => r.blob()).then((b) => createImageBitmap(b, { premultiplyAlpha: "none" })) : void 0;
  const skyboxUrl = options?.skyboxUrl;
  const skyboxIsDds = skyboxUrl != null && skyboxUrl.toLowerCase().endsWith(".dds");
  const skyboxIsEnv = skyboxUrl != null && (skyboxUrl === url || skyboxUrl.toLowerCase().endsWith(".env"));
  const bgOptions = {
    skipSkybox: skyboxIsDds || skyboxIsEnv || options?.skipSkybox};
  engine._dlr?.e(scene, url, options.brdfUrl);
  scene._deferredBuilders.push(async () => {
    const primaryColor = scene.environmentPrimaryColor ?? [0.08697355964132344, 0.08697355964132344, 0.2122208331110881];
    const { groundSize, skyboxSize: autoSkyboxSize, rootPosition } = computeSceneSize(scene, options?.skyboxSize);
    const skyHalfSize = autoSkyboxSize / 2;
    if (!bgOptions.skipSkybox) {
      const skybox = await import('./background-solid-skybox-DdiIEHbP.esm.js');
      scene._renderables.push(skybox.buildSolidSkyboxRenderable(scene, textures, skyHalfSize, rootPosition, primaryColor));
    }
    if (skyboxIsDds) {
      const skybox = await import('./background-dds-skybox-ULlWZfKe.esm.js');
      scene._renderables.push(await skybox.buildDdsSkyboxRenderable(scene, skyHalfSize, rootPosition, primaryColor, skyboxUrl));
    }
    if (skyboxIsEnv) {
      const skybox = await import('./background-hdr-skybox-FpJ-ApA1.esm.js');
      scene._renderables.push(await skybox.buildHdrSkyboxRenderable(scene, textures, skyHalfSize, rootPosition, primaryColor));
    }
  });
  return textures;
}
function polynomialToPreScaledHarmonics(poly) {
  const C00xy = 0.3333338747897695;
  const C00z = 0.33333298856284405;
  const C1 = 1.4999984284682104;
  const C2 = 3.999982863580422;
  const C20zz = 1.3333326611423701;
  const C20xy = 0.6666653397393608;
  const C22 = 1.999991431790211;
  const out = new F32(36);
  for (let i = 0; i < 3; i++) {
    const x = poly[i];
    const y = poly[3 + i];
    const z = poly[6 + i];
    const xx = poly[9 + i];
    const yy = poly[12 + i];
    const zz = poly[15 + i];
    const yz = poly[18 + i];
    const zx = poly[21 + i];
    const xy = poly[24 + i];
    out[i] = (xx + yy) * C00xy + zz * C00z;
    out[4 + i] = y * C1;
    out[8 + i] = z * C1;
    out[12 + i] = x * C1;
    out[16 + i] = xy * C2;
    out[20 + i] = yz * C2;
    out[24 + i] = zz * C20zz - (xx + yy) * C20xy;
    out[28 + i] = zx * C2;
    out[32 + i] = (xx - yy) * C22;
  }
  return out;
}

function shToPolynomial(sh) {
  const invPI = 1 / Math.PI;
  const poly = new F32(27);
  for (let ch = 0; ch < 3; ch++) {
    const o = ch * 9;
    const L00 = sh[o], L1_1 = sh[o + 1], L10 = sh[o + 2], L11 = sh[o + 3];
    const L2_2 = sh[o + 4], L2_1 = sh[o + 5], L20 = sh[o + 6], L21 = sh[o + 7], L22 = sh[o + 8];
    poly[0 * 3 + ch] = L11 * 1.02333 * invPI;
    poly[1 * 3 + ch] = L1_1 * 1.02333 * invPI;
    poly[2 * 3 + ch] = L10 * 1.02333 * invPI;
    poly[3 * 3 + ch] = (L00 * 0.886227 - L20 * 0.247708 + L22 * 0.429043) * invPI;
    poly[4 * 3 + ch] = (L00 * 0.886227 - L20 * 0.247708 - L22 * 0.429043) * invPI;
    poly[5 * 3 + ch] = (L00 * 0.886227 + L20 * 0.495417) * invPI;
    poly[6 * 3 + ch] = L2_1 * 0.858086 * invPI;
    poly[7 * 3 + ch] = L21 * 0.858086 * invPI;
    poly[8 * 3 + ch] = L2_2 * 0.858086 * invPI;
  }
  return poly;
}

function parseRGBE(buffer) {
  const bytes = new U8(buffer);
  let pos = 0;
  function readLine() {
    let line = "";
    while (pos < bytes.length) {
      const ch = bytes[pos++];
      if (ch === 10) {
        break;
      }
      if (ch !== 13) {
        line += String.fromCharCode(ch);
      }
    }
    return line;
  }
  const sig = readLine();
  if (!sig.startsWith("#?")) {
    ThrowLiteError(112);
  }
  let format = "";
  while (pos < bytes.length) {
    const line = readLine();
    if (line === "") {
      break;
    }
    if (line.startsWith("FORMAT=")) {
      format = line.slice(7);
    }
  }
  if (format && format !== "32-bit_rle_rgbe") {
    ThrowLiteError(113, format);
  }
  const resLine = readLine();
  const resMatch = resLine.match(/-Y\s+(\d+)\s+\+X\s+(\d+)/);
  if (!resMatch) {
    ThrowLiteError(114, resLine);
  }
  const height = parseInt(resMatch[1], 10);
  const width = parseInt(resMatch[2], 10);
  const data = new F32(width * height * 3);
  const scanlineBuf = new U8(width * 4);
  for (let y = 0; y < height; y++) {
    pos = decodeScanline(bytes, pos, width, data, y * width * 3, scanlineBuf);
  }
  return { width, height, data };
}
function decodeScanline(bytes, pos, width, out, outOffset, scanline) {
  if (width >= 8 && width <= 32767 && bytes[pos] === 2 && bytes[pos + 1] === 2 && bytes[pos + 2] === (width >> 8 & 255) && bytes[pos + 3] === (width & 255)) {
    pos += 4;
    for (let ch = 0; ch < 4; ch++) {
      let ptr = ch;
      let count = 0;
      while (count < width) {
        const a = bytes[pos++];
        if (a > 128) {
          const runLen = a - 128;
          const val = bytes[pos++];
          for (let i = 0; i < runLen; i++) {
            scanline[ptr] = val;
            ptr += 4;
          }
          count += runLen;
        } else {
          for (let i = 0; i < a; i++) {
            scanline[ptr] = bytes[pos++];
            ptr += 4;
          }
          count += a;
        }
      }
    }
    for (let x = 0; x < width; x++) {
      rgbeToFloat(scanline[x * 4], scanline[x * 4 + 1], scanline[x * 4 + 2], scanline[x * 4 + 3], out, outOffset + x * 3);
    }
  } else {
    for (let x = 0; x < width; x++) {
      rgbeToFloat(bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3], out, outOffset + x * 3);
      pos += 4;
    }
  }
  return pos;
}
function rgbeToFloat(r, g, b, e, out, off) {
  if (e === 0) {
    out[off] = out[off + 1] = out[off + 2] = 0;
  } else {
    const scale = Math.pow(2, e - 136);
    out[off] = r * scale;
    out[off + 1] = g * scale;
    out[off + 2] = b * scale;
  }
}
function computeSHFromEquirect(data, width, height) {
  const Y00 = 0.282094791773878;
  const Y1 = 0.48860251190292;
  const Y2_2c = 1.092548430592079;
  const Y20c = 0.31539156525252;
  const Y22c = 0.54627421529604;
  const sh = new F64(27);
  let totalWeight = 0;
  for (let py = 0; py < height; py++) {
    const phi = (py + 0.5) / height * Math.PI;
    const sinPhi = Math.sin(phi);
    const cosPhi = Math.cos(phi);
    const dOmega = sinPhi * (Math.PI / height) * (2 * Math.PI / width);
    for (let px = 0; px < width; px++) {
      const theta = (2 * (px + 0.5) / width - 1) * Math.PI;
      const x = sinPhi * Math.sin(theta);
      const y = cosPhi;
      const z = sinPhi * Math.cos(theta);
      const idx = (py * width + px) * 3;
      let cr = data[idx], cg = data[idx + 1], cb = data[idx + 2];
      const maxCh = Math.max(cr, cg, cb);
      if (maxCh > 4096) {
        const s = 4096 / maxCh;
        cr *= s;
        cg *= s;
        cb *= s;
      }
      const w = dOmega;
      totalWeight += w;
      const b0 = Y00;
      const b1 = Y1 * y, b2 = Y1 * z, b3 = Y1 * x;
      const b4 = Y2_2c * x * y, b5 = Y2_2c * y * z;
      const b6 = Y20c * (3 * z * z - 1);
      const b7 = Y2_2c * x * z, b8 = Y22c * (x * x - y * y);
      const basis = [b0, b1, b2, b3, b4, b5, b6, b7, b8];
      for (let i = 0; i < 9; i++) {
        const bw = basis[i] * w;
        sh[i] = sh[i] + cr * bw;
        sh[9 + i] = sh[9 + i] + cg * bw;
        sh[18 + i] = sh[18 + i] + cb * bw;
      }
    }
  }
  const correction = 4 * Math.PI / totalWeight;
  for (let i = 0; i < 27; i++) {
    sh[i] = sh[i] * correction;
  }
  const irradScale = [1, 2 / 3, 2 / 3, 2 / 3, 0.25, 0.25, 0.25, 0.25, 0.25];
  for (let ch = 0; ch < 3; ch++) {
    for (let i = 0; i < 9; i++) {
      sh[ch * 9 + i] = sh[ch * 9 + i] * irradScale[i];
    }
  }
  return shToPolynomial(sh);
}

var hdrParser = /*#__PURE__*/Object.freeze({
    __proto__: null,
    computeSHFromEquirect: computeSHFromEquirect,
    parseRGBE: parseRGBE
});

const brdfLutWGSL = "@group(0) @binding(0) var z:texture_storage_2d<rgba16float,write>;fn x(G:u32)->f32{var a=G;a=(a<<16u)|(a>>16u);a=((a&0x55555555u)<<1u)|((a&0xAAAAAAAAu)>>1u);a=((a&0x33333333u)<<2u)|((a&0xCCCCCCCCu)>>2u);a=((a&0x0F0F0F0Fu)<<4u)|((a&0xF0F0F0F0u)>>4u);a=((a&0x00FF00FFu)<<8u)|((a&0xFF00FF00u)>>8u);return f32(a)*2.3283064365386963e-10;}fn w(B:f32,u:f32,J:f32)->vec3f{let t=2.0*3.14159265359*B;let k=sqrt((1.0-u)/(1.0+(J-1.0)*u));let q=sqrt(1.0-k*k);return vec3f(cos(t)*q,sin(t)*q,k);}fn y(b:f32,n:f32)->vec2f{let o=vec3f(sqrt(1.0-b*b),0.0,b);let p=n*n;let e=p*p;var r=0.0;var l=0.0;let g=1024u;for(var f=0u;f<g;f++){let F=f32(f)/f32(g);let E=x(f);let j=w(F,E,e);let i=max(dot(o,j),0.0);let A=2.0*i*j-o;let d=max(A.z,0.0);let s=max(j.z,0.0);if (d>0.0&&s>0.0){let C=d*sqrt(b*b*(1.0-e)+e);let D=b*sqrt(d*d*(1.0-e)+e);let v=(0.5/max(C+D,1e-6))*d*(4.0*i/s);let m=pow(1.0-i,5.0);r+=(1.0-m)*v;l+=m*v;}}return vec2f(r/f32(g),l/f32(g));}@compute @workgroup_size(8,8) fn main(@builtin(global_invocation_id) c:vec3u){if (c.x>=256u||c.y>=256u){return;}let H=max((f32(c.x)+0.5)/256.0,0.001);let I=max((f32(c.y)+0.5)/256.0,0.04);let h=y(H,I);textureStore(z,vec2u(c.x,c.y),vec4f(h.y,h.x+h.y,0.0,1.0));}";

const equirectToCubeWGSL = "struct t{faceSize:u32,equirectWidth:u32,equirectHeight:u32,_pad:u32}@group(0) @binding(0) var p:texture_2d<f32>;@group(0) @binding(1) var k:texture_storage_2d_array<rgba16float,write>;@group(0) @binding(2) var<uniform> params:t;const i=3.14159265359;const d=array<vec3<f32>,24>(vec3(1.0,-1.0,1.0),vec3(-1.0,-1.0,1.0),vec3(1.0,1.0,1.0),vec3(-1.0,1.0,1.0),vec3(-1.0,-1.0,-1.0),vec3(1.0,-1.0,-1.0),vec3(-1.0,1.0,-1.0),vec3(1.0,1.0,-1.0),vec3(-1.0,-1.0,-1.0),vec3(-1.0,-1.0,1.0),vec3(1.0,-1.0,-1.0),vec3(1.0,-1.0,1.0),vec3(1.0,1.0,-1.0),vec3(1.0,1.0,1.0),vec3(-1.0,1.0,-1.0),vec3(-1.0,1.0,1.0),vec3(1.0,-1.0,-1.0),vec3(1.0,-1.0,1.0),vec3(1.0,1.0,-1.0),vec3(1.0,1.0,1.0),vec3(-1.0,-1.0,1.0),vec3(-1.0,-1.0,-1.0),vec3(-1.0,1.0,1.0),vec3(-1.0,1.0,-1.0));@compute @workgroup_size(8,8,1) fn main(@builtin(global_invocation_id) a:vec3u){let h=a.z;let c=params.faceSize;if (a.x>=c||a.y>=c||h>=6u){return;}let f=f32(a.x)/f32(c);let e=f32(a.y)/f32(c);let b=h*4u;let g=normalize(d[b]*(1.0-f)*(1.0-e)+d[b+1u]*f*(1.0-e)+d[b+2u]*(1.0-f)*e+d[b+3u]*f*e);let l=atan2(g.z,g.x);let m=acos(clamp(g.y,-1.0,1.0));let n=l/i*0.5+0.5;let o=m/i;let j=clamp(i32(round(n*f32(params.equirectWidth))),0,i32(params.equirectWidth)-1);let q=clamp(i32(round(o*f32(params.equirectHeight))),0,i32(params.equirectHeight)-1);let r=i32(params.equirectHeight)-q-1;let s=textureLoad(p,vec2<i32>(j,r),0);textureStore(k,vec2<i32>(a.xy),i32(h),vec4<f32>(s.rgb,1.0));}";

const prefilterCubeWGSL = "struct ha{faceSize:u32,mipLevel:u32,totalMips:u32,srcSize:u32}@group(0) @binding(0) var x:texture_cube<f32>;@group(0) @binding(1) var F:sampler;@group(0) @binding(2) var z:texture_storage_2d_array<rgba16float,write>;@group(0) @binding(3) var<uniform> params:ha;const k=3.14159265359;const n=1024u;const g=array<vec3<f32>,24>(vec3(1.0,-1.0,1.0),vec3(-1.0,-1.0,1.0),vec3(1.0,1.0,1.0),vec3(-1.0,1.0,1.0),vec3(-1.0,-1.0,-1.0),vec3(1.0,-1.0,-1.0),vec3(-1.0,1.0,-1.0),vec3(1.0,1.0,-1.0),vec3(-1.0,-1.0,-1.0),vec3(-1.0,-1.0,1.0),vec3(1.0,-1.0,-1.0),vec3(1.0,-1.0,1.0),vec3(1.0,1.0,-1.0),vec3(1.0,1.0,1.0),vec3(-1.0,1.0,-1.0),vec3(-1.0,1.0,1.0),vec3(1.0,-1.0,-1.0),vec3(1.0,-1.0,1.0),vec3(1.0,1.0,-1.0),vec3(1.0,1.0,1.0),vec3(-1.0,-1.0,1.0),vec3(-1.0,-1.0,-1.0),vec3(-1.0,1.0,1.0),vec3(-1.0,1.0,-1.0));fn I(ca:u32,j:f32,d:f32)->vec3<f32>{let i=ca*4u;return normalize(g[i]*(1.0-j)*(1.0-d)+g[i+1u]*j*(1.0-d)+g[i+2u]*(1.0-j)*d+g[i+3u]*j*d);}fn J(ba:u32)->f32{var a=ba;a=(a<<16u)|(a>>16u);a=((a&0x55555555u)<<1u)|((a&0xAAAAAAAAu)>>1u);a=((a&0x33333333u)<<2u)|((a&0xCCCCCCCCu)>>2u);a=((a&0x0F0F0F0Fu)<<4u)|((a&0xF0F0F0F0u)>>4u);a=((a&0x00FF00FFu)<<8u)|((a&0xFF00FF00u)>>8u);return f32(a)*2.3283064365386963e-10;}fn K(aa:f32,C:f32,s:f32)->vec3<f32>{let Z=s*s;let u=2.0*k*aa;let o=sqrt((1.0-C)/(1.0+(Z-1.0)*C));let w=sqrt(1.0-o*o);return vec3<f32>(cos(u)*w,sin(u)*w,o);}fn L(y:f32,t:f32)->f32{let A=y*y*(t-1.0)+1.0;return t/(k*A*A);}fn H(Y:u32,M:f32,S:f32)->vec3<f32>{return I(Y,M,S);}@compute @workgroup_size(8,8,1) fn main(@builtin(global_invocation_id) c:vec3u){let f=c.z;let h=params.faceSize>>params.mipLevel;if (c.x>=h||c.y>=h||f>=6u){return;}let R=f32(c.x)/f32(h);let Q=f32(c.y)/f32(h);let b=normalize(H(f,R,Q));let q=pow(2.0,f32(params.mipLevel)/0.8)/f32(params.srcSize);if (params.mipLevel==0u){let O=textureSampleLevel(x,F,b,0.0);textureStore(z,vec2<i32>(c.xy),i32(f),vec4<f32>(O.rgb,1.0));return;}var X=select(vec3<f32>(1.0,0.0,0.0),vec3<f32>(0.0,0.0,1.0),abs(b.z)<0.999);let v=normalize(cross(X,b));let P=cross(b,v);var p=vec3<f32>(0.0);var m=0.0;let E=f32(params.srcSize);let T=4.0*k/(6.0*E*E);let U=f32(params.totalMips)-1.0;for(var e=0u;e<n;e++){let W=f32(e)/f32(n);let N=J(e);let r=K(W,N,q);let B=v*r.x+P*r.y+b*r.z;let D=max(dot(b,B),0.0);let G=2.0*D*B-b;let l=dot(b,G);if (l>0.0){let da=q*q;let ea=L(D,da)/4.0;let fa=1.0/(f32(n)*max(ea,0.0001));let ga=clamp(0.5*log2(fa/T)+1.0,0.0,U);let V=textureSampleLevel(x,F,G,ga);p+=V.rgb*l;m+=l;}}if (m>0.0){p/=m;}textureStore(z,vec2<i32>(c.xy),i32(f),vec4<f32>(p,1.0));}";

function equirectToCubemapGPU(engine, hdr, faceSize) {
  const device = engine._device;
  const equirectTex = device.createTexture({
    size: [hdr.width, hdr.height],
    format: "rgba32float",
    usage: TU.TEXTURE_BINDING | TU.COPY_DST
  });
  {
    const rgba = new F32(hdr.width * hdr.height * 4);
    for (let i = 0; i < hdr.width * hdr.height; i++) {
      rgba[i * 4] = hdr.data[i * 3];
      rgba[i * 4 + 1] = hdr.data[i * 3 + 1];
      rgba[i * 4 + 2] = hdr.data[i * 3 + 2];
      rgba[i * 4 + 3] = 1;
    }
    device.queue.writeTexture({ texture: equirectTex }, rgba.buffer, { bytesPerRow: hdr.width * 16 }, { width: hdr.width, height: hdr.height });
  }
  const cubeTex = device.createTexture({
    size: [faceSize, faceSize, 6],
    format: "rgba16float",
    usage: TU.TEXTURE_BINDING | TU.STORAGE_BINDING | TU.COPY_SRC,
    dimension: "2d"
  });
  const module = device.createShaderModule({ code: equirectToCubeWGSL });
  const pipeline = device.createComputePipeline({
    layout: "auto",
    compute: { module, entryPoint: "main" }
  });
  const paramBuf = createUniformBuffer(engine, new U32([faceSize, hdr.width, hdr.height, 0]));
  const bg = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [
      { binding: 0, resource: equirectTex.createView() },
      { binding: 1, resource: cubeTex.createView({ dimension: "2d-array", arrayLayerCount: 6 }) },
      { binding: 2, resource: { buffer: paramBuf } }
    ]
  });
  const enc = device.createCommandEncoder();
  const pass = enc.beginComputePass();
  pass.setPipeline(pipeline);
  pass.setBindGroup(0, bg);
  pass.dispatchWorkgroups(Math.ceil(faceSize / 8), Math.ceil(faceSize / 8), 6);
  pass.end();
  device.queue.submit([enc.finish()]);
  equirectTex.destroy();
  paramBuf.destroy();
  return cubeTex;
}
function prefilterCubemapGPU(engine, srcCube, faceSize, mipCount) {
  const device = engine._device;
  const dstCube = device.createTexture({
    size: { width: faceSize, height: faceSize, depthOrArrayLayers: 6 },
    mipLevelCount: mipCount,
    format: "rgba16float",
    usage: TU.TEXTURE_BINDING | TU.STORAGE_BINDING | TU.COPY_DST
  });
  const srcCubeView = srcCube.createView({ dimension: "cube" });
  const srcSampler = getBilinearSampler(engine);
  const pipeline = device.createComputePipeline({
    layout: "auto",
    compute: { module: device.createShaderModule({ code: prefilterCubeWGSL }), entryPoint: "main" }
  });
  const paramsBuffer = createEmptyUniformBuffer(engine, 16);
  {
    const copyEnc = device.createCommandEncoder();
    copyEnc.copyTextureToTexture({ texture: srcCube }, { texture: dstCube, mipLevel: 0 }, { width: faceSize, height: faceSize, depthOrArrayLayers: 6 });
    device.queue.submit([copyEnc.finish()]);
  }
  for (let mip = 1; mip < mipCount; mip++) {
    const mipSize = faceSize >> mip;
    if (mipSize < 1) {
      break;
    }
    device.queue.writeBuffer(paramsBuffer, 0, new U32([faceSize, mip, mipCount, faceSize]));
    const dstView = dstCube.createView({
      dimension: "2d-array",
      baseMipLevel: mip,
      mipLevelCount: 1,
      baseArrayLayer: 0,
      arrayLayerCount: 6
    });
    const bindGroup = device.createBindGroup({
      layout: pipeline.getBindGroupLayout(0),
      entries: [
        { binding: 0, resource: srcCubeView },
        { binding: 1, resource: srcSampler },
        { binding: 2, resource: dstView },
        { binding: 3, resource: { buffer: paramsBuffer } }
      ]
    });
    const encoder = device.createCommandEncoder();
    const pass = encoder.beginComputePass();
    pass.setPipeline(pipeline);
    pass.setBindGroup(0, bindGroup);
    pass.dispatchWorkgroups(Math.ceil(mipSize / 8), Math.ceil(mipSize / 8), 6);
    pass.end();
    device.queue.submit([encoder.finish()]);
  }
  srcCube.destroy();
  paramsBuffer.destroy();
  return dstCube;
}
let _brdfPipeline = null;
let _brdfPipelineDevice = null;
function generateBrdfLut(engine) {
  const device = engine._device;
  if (!_brdfPipeline || _brdfPipelineDevice !== device) {
    _brdfPipeline = device.createComputePipeline({
      layout: "auto",
      compute: { module: device.createShaderModule({ code: brdfLutWGSL }), entryPoint: "main" }
    });
    _brdfPipelineDevice = device;
  }
  const size = 256;
  const texture = device.createTexture({
    size: { width: size, height: size },
    format: "rgba16float",
    usage: TU.TEXTURE_BINDING | TU.STORAGE_BINDING
  });
  const bindGroup = device.createBindGroup({
    layout: _brdfPipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: texture.createView() }]
  });
  const encoder = device.createCommandEncoder();
  const pass = encoder.beginComputePass();
  pass.setPipeline(_brdfPipeline);
  pass.setBindGroup(0, bindGroup);
  pass.dispatchWorkgroups(Math.ceil(size / 8), Math.ceil(size / 8));
  pass.end();
  device.queue.submit([encoder.finish()]);
  return texture;
}

var hdrIblPipeline = /*#__PURE__*/Object.freeze({
    __proto__: null,
    equirectToCubemapGPU: equirectToCubemapGPU,
    generateBrdfLut: generateBrdfLut,
    prefilterCubemapGPU: prefilterCubemapGPU
});

async function loadHdrEnvironment(scene, url, options) {
  const engine = scene.surface.engine;
  const faceSize = options?.faceSize ?? 256;
  const buffer = await fetch(url).then((r) => r.arrayBuffer());
  const hdr = parseRGBE(buffer);
  const irradianceSH = computeSHFromEquirect(hdr.data, hdr.width, hdr.height);
  const srcCube = equirectToCubemapGPU(engine, hdr, faceSize);
  const mipCount = mipLevelCount(faceSize, faceSize);
  const specularCube = prefilterCubemapGPU(engine, srcCube, faceSize, mipCount);
  const brdfLut = generateBrdfLut(engine);
  const textures = assembleEnvironmentTextures(specularCube, brdfLut, irradianceSH, 1, engine);
  scene._envTextures = textures;
  registerEnvSceneUniforms(scene);
  acquireGPUTexture(specularCube);
  acquireGPUTexture(brdfLut);
  scene._disposables.push(() => {
    releaseGPUTexture(specularCube);
    releaseGPUTexture(brdfLut);
  });
  scene.imageProcessing.toneMappingEnabled = false;
  scene.imageProcessing.exposure = 0.8;
  scene.imageProcessing.contrast = 1.2;
  engine._dlr?.h(scene, url, faceSize);
  scene._deferredBuilders.push(async () => {
    if (textures.specularCubeView) {
      let autoSkyboxSize = options?.skyboxSize;
      let rootPosition = options?.skyboxPosition;
      if (autoSkyboxSize === void 0 || rootPosition === void 0) {
        const { computeSceneSize } = await Promise.resolve().then(function () { return sceneSize; });
        const size = computeSceneSize(scene, autoSkyboxSize);
        autoSkyboxSize = size.skyboxSize;
        rootPosition = size.rootPosition;
      }
      const primaryColor = scene.environmentPrimaryColor ?? [0.08697355964132344, 0.08697355964132344, 0.2122208331110881];
      const { buildHdrSkyboxRenderable } = await import('./background-hdr-skybox-FpJ-ApA1.esm.js');
      scene._renderables.push(await buildHdrSkyboxRenderable(scene, textures, autoSkyboxSize / 2, rootPosition, primaryColor));
    }
  });
  return textures;
}

function writeShadowUboFields(out, sg) {
  packMat4IntoF32(out, sg._lightMatrix, 0);
  out[16] = sg._depthValues[0];
  out[17] = sg._depthValues[1];
  out[18] = 0;
  out[19] = 0;
  out[20] = sg._shadowsInfo[0];
  out[21] = sg._shadowsInfo[1];
  out[22] = sg._shadowsInfo[2];
  out[23] = sg._shadowsInfo[3];
}
function buildLightViewMatrix(dirX, dirY, dirZ, px, py, pz) {
  const len = Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ) || 1;
  const fx = dirX / len;
  const fy = dirY / len;
  const fz = dirZ / len;
  let upX = 0, upY = 1, upZ = 0;
  if (Math.abs(fy) > 0.99) {
    upX = 0;
    upY = 0;
    upZ = 1;
  }
  let rx = upY * fz - upZ * fy;
  let ry = upZ * fx - upX * fz;
  let rz = upX * fy - upY * fx;
  const rLen = Math.sqrt(rx * rx + ry * ry + rz * rz) || 1;
  rx /= rLen;
  ry /= rLen;
  rz /= rLen;
  const ux = fy * rz - fz * ry;
  const uy = fz * rx - fx * rz;
  const uz = fx * ry - fy * rx;
  return new F32([rx, ux, fx, 0, ry, uy, fy, 0, rz, uz, fz, 0, -(rx * px + ry * py + rz * pz), -(ux * px + uy * py + uz * pz), -(fx * px + fy * py + fz * pz), 1]);
}
function multiply4x4(a, b) {
  const out = new F32(16);
  for (let row = 0; row < 4; row++) {
    for (let col = 0; col < 4; col++) {
      let sum = 0;
      for (let k = 0; k < 4; k++) {
        sum += a[row + k * 4] * b[k + col * 4];
      }
      out[row + col * 4] = sum;
    }
  }
  return out;
}
function computeDirectionalLightMatrix(light, casterMeshes, orthoMinZ, orthoMaxZ, offX = 0, offY = 0, offZ = 0) {
  const view = buildLightViewMatrix(light.direction.x, light.direction.y, light.direction.z, light.position.x - offX, light.position.y - offY, light.position.z - offZ);
  let minX = Infinity;
  let maxX = -Infinity;
  let minY = Infinity;
  let maxY = -Infinity;
  for (const mesh of casterMeshes) {
    const world = mesh.worldMatrix;
    const boundMin = mesh.boundMin ?? [-0.5, -0.5, -0.5];
    const boundMax = mesh.boundMax ?? [0.5, 0.5, 0.5];
    for (let corner = 0; corner < 8; corner++) {
      const localX = corner & 1 ? boundMax[0] : boundMin[0];
      const localY = corner & 2 ? boundMax[1] : boundMin[1];
      const localZ = corner & 4 ? boundMax[2] : boundMin[2];
      const worldX = world[0] * localX + world[4] * localY + world[8] * localZ + world[12] - offX;
      const worldY = world[1] * localX + world[5] * localY + world[9] * localZ + world[13] - offY;
      const worldZ = world[2] * localX + world[6] * localY + world[10] * localZ + world[14] - offZ;
      const viewX = view[0] * worldX + view[4] * worldY + view[8] * worldZ + view[12];
      const viewY = view[1] * worldX + view[5] * worldY + view[9] * worldZ + view[13];
      minX = Math.min(minX, viewX);
      maxX = Math.max(maxX, viewX);
      minY = Math.min(minY, viewY);
      maxY = Math.max(maxY, viewY);
    }
  }
  if (!Number.isFinite(minX)) {
    minX = -1;
    maxX = 1;
    minY = -1;
    maxY = 1;
  }
  const padX = (maxX - minX) * 0.1;
  const padY = (maxY - minY) * 0.1;
  minX -= padX;
  maxX += padX;
  minY -= padY;
  maxY += padY;
  const projection = new F32(16);
  projection[0] = 2 / (maxX - minX);
  projection[5] = 2 / (maxY - minY);
  projection[10] = 1 / (orthoMaxZ - orthoMinZ);
  projection[12] = -(maxX + minX) / (maxX - minX);
  projection[13] = -(maxY + minY) / (maxY - minY);
  projection[14] = -orthoMinZ / (orthoMaxZ - orthoMinZ);
  projection[15] = 1;
  return { _view: view, _viewProj: multiply4x4(projection, view), _near: orthoMinZ, _far: orthoMaxZ };
}
function createShadowParamsUBO(engine, bias, depthScale) {
  const data = new F32(8);
  data[0] = bias;
  data[2] = depthScale;
  data[4] = 0;
  data[5] = 1;
  return createUniformBuffer(engine, data);
}
function createShadowRenderTarget(sg, colorTexture = null, depthTexture = sg._depthTexture) {
  const mapSize = sg._config._mapSize;
  return {
    _descriptor: {
      size: { width: mapSize, height: mapSize },
      format: colorTexture ? "rgba16float" : void 0,
      dFormat: "depth32float",
      _depthClearValue: 1,
      _depthCompare: "less-equal",
      samples: 1
    },
    _colorTexture: colorTexture,
    _colorView: colorTexture?.createView() ?? null,
    _depthTexture: depthTexture,
    _depthView: depthTexture.createView(),
    _width: mapSize,
    _height: mapSize,
    _eager: true,
    // Borrowed: the depth map is the generator's shared shadow map (persists for the generator's
    // lifetime); per-task render-target disposal must NOT destroy it (it's reused after rebuilds).
    _ownsDepthTexture: false
  };
}
function createSharedShadowUBO(engine, _lightMatrix, _depthValues, _shadowsInfo) {
  const data = new F32(24);
  writeShadowUboFields(data, { _lightMatrix, _depthValues, _shadowsInfo });
  const ubo = createUniformBuffer(engine, data);
  return { ubo, data };
}
function casterVersionSum(casterMeshes) {
  let sum = 0;
  for (const mesh of casterMeshes) {
    sum += mesh.worldMatrixVersion + ~~mesh.thinInstances?._version;
  }
  return sum;
}
function createShadowCamera(sg) {
  return {
    fov: 1,
    nearPlane: 1,
    farPlane: 1,
    children: [],
    _viewCache: allocateMat4(),
    _projCache: allocateMat4(),
    _vpCache: allocateMat4(),
    get worldMatrix() {
      return sg._light.worldMatrix;
    },
    get worldMatrixVersion() {
      const state = this._shadowCameraVersion;
      return state ?? sg._light.worldMatrixVersion;
    }
  };
}
function updateShadowCameraBase(camera, cameraVersion, near, far, view, viewProj) {
  camera.nearPlane = near;
  camera.farPlane = far;
  camera._shadowCameraVersion = cameraVersion;
  camera._viewCache = view;
  camera._viewVer = cameraVersion;
  camera._vpCache = viewProj;
  camera._vpVer = _cameraChangeKey(camera);
  camera._vpAspect = 1;
}

const blurVertSrc = "struct c{@builtin(position) clipPos:vec4<f32>,@location(0) sampleCenter:vec2<f32>}@vertex fn main(@builtin(vertex_index) d:u32)->c{var e=array<vec2<f32>,3>(vec2<f32>(-1.0,-1.0),vec2<f32>(3.0,-1.0),vec2<f32>(-1.0,3.0));var a:c;let b=e[d];a.clipPos=vec4<f32>(b,0.0,1.0);a.sampleCenter=b*vec2<f32>(0.5,-0.5)+0.5;return a;}";

let esmShadowTaskResources = null;
let createStandardEsmShadowMaterialView;
let createPbrEsmShadowMaterialView;
let createNodeEsmShadowMaterialView;
function getEsmShadowTaskResourceMap() {
  esmShadowTaskResources ??= /* @__PURE__ */ new WeakMap();
  return esmShadowTaskResources;
}
function setEsmShadowTaskResources(sg, resources) {
  getEsmShadowTaskResourceMap().set(sg, resources);
}
function getEsmShadowTaskResources(sg) {
  return esmShadowTaskResources?.get(sg) ?? null;
}
async function preloadEsmShadowTaskState(casterMeshes) {
  const loads = [];
  let needsStandard = false;
  let needsPbr = false;
  let needsNode = false;
  for (const mesh of casterMeshes) {
    const family = mesh.material?._buildGroup._materialFamily;
    needsStandard ||= family === "standard";
    needsPbr ||= family === "pbr";
    needsNode ||= family === "node";
  }
  if (needsStandard && !createStandardEsmShadowMaterialView) {
    loads.push(
      import('./esm-shadow-view-DZj4wj16.esm.js').then((module) => {
        createStandardEsmShadowMaterialView = module.createStandardEsmShadowMaterialView;
      })
    );
  }
  if (needsPbr && !createPbrEsmShadowMaterialView) {
    loads.push(
      import('./esm-shadow-view-CmGKY4DZ.esm.js').then((module) => {
        createPbrEsmShadowMaterialView = module.createPbrEsmShadowMaterialView;
      })
    );
  }
  if (needsNode && !createNodeEsmShadowMaterialView) {
    loads.push(
      import('./esm-shadow-view-V9GW64Bg.esm.js').then((module) => {
        createNodeEsmShadowMaterialView = module.createNodeEsmShadowMaterialView;
      })
    );
  }
  await Promise.all(loads);
}
function nearestBestKernel(idealKernel) {
  const v = Math.round(Math.max(idealKernel, 1));
  for (const k of [v, v - 1, v + 1, v - 2, v + 2]) {
    if (k % 2 !== 0 && Math.floor(k / 2) % 2 === 0 && k > 0) {
      return Math.max(k, 3);
    }
  }
  return Math.max(v, 3);
}
function gaussianWeight(x) {
  const sigma = 1 / 3;
  return Math.exp(-(x * x / (2 * sigma * sigma))) / (Math.sqrt(2 * Math.PI) * sigma);
}
function createKernelBlurSamples(idealKernel) {
  const n = nearestBestKernel(idealKernel);
  const centerIndex = (n - 1) / 2;
  const offsets = [];
  const weights = [];
  let totalWeight = 0;
  for (let i = 0; i < n; i++) {
    const u = i / (n - 1);
    const weight = gaussianWeight(u * 2 - 1);
    offsets[i] = i - centerIndex;
    weights[i] = weight;
    totalWeight += weight;
  }
  for (let i = 0; i < weights.length; i++) {
    weights[i] = weights[i] / totalWeight;
  }
  const linearOffsets = [];
  const linearWeights = [];
  for (let i = 0; i <= centerIndex; i += 2) {
    const j = Math.min(i + 1, Math.floor(centerIndex));
    if (i === j) {
      linearOffsets.push(offsets[i]);
      linearWeights.push(weights[i]);
      continue;
    }
    const sharedCell = j === centerIndex;
    const weightLinear = weights[i] + weights[j] * (sharedCell ? 0.5 : 1);
    const offsetLinear = offsets[i] + 1 / (1 + weights[i] / weights[j]);
    if (offsetLinear === 0) {
      linearOffsets.push(offsets[i], offsets[i + 1]);
      linearWeights.push(weights[i], weights[i + 1]);
    } else {
      linearOffsets.push(offsetLinear, -offsetLinear);
      linearWeights.push(weightLinear, weightLinear);
    }
  }
  return { offsets: linearOffsets, weights: linearWeights };
}
function wgslFloat(value) {
  const n = Object.is(value, -0) ? 0 : value;
  let s = n.toPrecision(10);
  if (!/[.eE]/.test(s)) {
    s += ".0";
  }
  return s;
}
function createShadowBlurFragmentWGSL(blurKernel) {
  const { offsets, weights } = createKernelBlurSamples(blurKernel);
  const count = offsets.length;
  return `struct BlurParams{delta:vec2<f32>,_pad:vec2<f32>,};@group(0) @binding(0) var<uniform> params:BlurParams;@group(0) @binding(1) var srcTex:texture_2d<f32>;@group(0) @binding(2) var srcSampler:sampler;const OFFSETS=array<f32,${count}>(${offsets.map(wgslFloat).join(",")});const WEIGHTS=array<f32,${count}>(${weights.map(wgslFloat).join(",")});@fragment fn main(@location(0) sampleCenter:vec2<f32>)->@location(0) vec4<f32>{var blend=vec4<f32>(0.0);for(var i=0u;i<${count}u;i=i+1u){blend+=textureSample(srcTex,srcSampler,sampleCenter+params.delta*OFFSETS[i])*WEIGHTS[i];}return blend;}`;
}
function ensureEsmShadowTaskState(engine, scene, sg, casterMeshes, existingState) {
  const existing = existingState;
  if (existing) {
    if (existing._casterMeshes === casterMeshes) {
      return existing;
    }
    existing._task.dispose();
  }
  const resources = getEsmShadowTaskResources(sg);
  if (!resources) {
    throw new Error("ShadowTask: missing ESM metadata.");
  }
  const materialViews = /* @__PURE__ */ new Map();
  const camera = createShadowCamera(sg);
  const taskState = {
    _task: createRenderTask(
      {
        name: "esm",
        rt: createShadowRenderTarget(sg, resources._esmTexture, resources._depthBuffer),
        clr: true,
        clrColor: { r: 0, g: 0, b: 0, a: 0 },
        cam: camera,
        _skipClusteredLights: true
      },
      engine,
      scene
    ),
    _camera: camera,
    _cameraVersion: 0,
    _lastCasterVersion: -1,
    _lastLightVersion: -1,
    _lastFoVersion: -1,
    _casterMeshes: casterMeshes,
    _scene: scene
  };
  for (const mesh of casterMeshes) {
    const material = mesh.material;
    if (material) {
      taskState._task.addMesh(mesh, { material: getEsmShadowView(material, materialViews, sg._shadowParamsUBO) });
    }
  }
  return taskState;
}
function renderEsmShadowMap(engine, sg, state) {
  const resources = getEsmShadowTaskResources(sg);
  if (!resources) {
    return 0;
  }
  const casterMeshes = state._casterMeshes;
  const casterVersion = casterVersionSum(casterMeshes);
  const lightVersion = sg._light.worldMatrixVersion;
  const foCam = engine.useFloatingOrigin ? state._scene.camera : null;
  const foVersion = foCam ? foCam.worldMatrixVersion : 0;
  const offX = foCam ? foCam.worldMatrix[12] : 0;
  const offY = foCam ? foCam.worldMatrix[13] : 0;
  const offZ = foCam ? foCam.worldMatrix[14] : 0;
  if (!sg._config._forceRefreshEveryFrame && casterVersion === state._lastCasterVersion && lightVersion === state._lastLightVersion && foVersion === state._lastFoVersion) {
    return 0;
  }
  const matrix = computeDirectionalLightMatrix(sg._light, casterMeshes, sg._config._orthoMinZ, sg._config._orthoMaxZ, offX, offY, offZ);
  if (shadowMatrixChanged(sg._lightMatrix, matrix._viewProj)) {
    packMat4IntoF32(sg._lightMatrix, matrix._viewProj, 0);
    sg._version++;
    writeShadowUboFields(resources._shadowUboData, sg);
    engine._device.queue.writeBuffer(sg._shadowUBO, 0, resources._shadowUboData);
  }
  updateShadowCamera(state, matrix);
  state._lastCasterVersion = casterVersion;
  state._lastLightVersion = lightVersion;
  state._lastFoVersion = foVersion;
  let draws = state._task.execute?.() ?? 0;
  const encoder = engine._currentEncoder;
  const bh = encoder.beginRenderPass({
    colorAttachments: [
      {
        view: resources._blurTexH.createView(),
        loadOp: "clear",
        storeOp: "store",
        clearValue: { r: 0, g: 0, b: 0, a: 0 }
      }
    ]
  });
  bh.setPipeline(resources._blurPipeline);
  bh.setBindGroup(0, resources._blurHBG);
  bh.draw(3);
  bh.end();
  const bv = encoder.beginRenderPass({
    colorAttachments: [
      {
        view: sg._depthTexture.createView(),
        loadOp: "clear",
        storeOp: "store",
        clearValue: { r: 0, g: 0, b: 0, a: 0 }
      }
    ]
  });
  bv.setPipeline(resources._blurPipeline);
  bv.setBindGroup(0, resources._blurVBG);
  bv.draw(3);
  bv.end();
  draws += 2;
  return draws;
}
function updateShadowCamera(state, matrix) {
  state._cameraVersion++;
  updateShadowCameraBase(state._camera, state._cameraVersion, matrix._near, matrix._far, matrix._view, matrix._viewProj);
}
function getEsmShadowView(material, cache, shadowParamsUBO) {
  const cached = cache.get(material);
  if (cached) {
    return cached;
  }
  const family = material._buildGroup._materialFamily;
  let view;
  if (family === "standard") {
    view = createStandardEsmShadowMaterialView(material, shadowParamsUBO);
  } else if (family === "pbr") {
    view = createPbrEsmShadowMaterialView(material, shadowParamsUBO);
  } else if (family === "node") {
    view = createNodeEsmShadowMaterialView(material, shadowParamsUBO);
  }
  cache.set(material, view);
  return view;
}
function shadowMatrixChanged(a, b) {
  for (let i = 0; i < 16; i++) {
    if (a[i] !== b[i]) {
      return true;
    }
  }
  return false;
}
function createEsmDirectionalShadowGenerator(engine, _light, cfg = {}) {
  const device = engine._device;
  const mapSize = cfg.mapSize ?? 1024;
  const depthScale = cfg.depthScale ?? 50;
  const bias = cfg.bias ?? 5e-5;
  const blurKernel = cfg.blurKernel ?? 1;
  const blurScale = cfg.blurScale ?? 2;
  const darkness = cfg.darkness ?? 0;
  const frustumEdgeFalloff = cfg.frustumEdgeFalloff ?? 0;
  const orthoMinZ = cfg.orthoMinZ ?? 1;
  const orthoMaxZ = cfg.orthoMaxZ ?? 1e4;
  const forceRefreshEveryFrame = cfg.forceRefreshEveryFrame ?? false;
  const blurSize = mapSize / blurScale;
  const _config = {
    _mapSize: mapSize,
    _bias: bias,
    _orthoMinZ: orthoMinZ,
    _orthoMaxZ: orthoMaxZ,
    _forceRefreshEveryFrame: forceRefreshEveryFrame
  };
  const _shadowParamsUBO = createShadowParamsUBO(engine, bias, depthScale);
  const esmTexture = device.createTexture({
    size: { width: mapSize, height: mapSize },
    format: "rgba16float",
    usage: TU.RENDER_ATTACHMENT | TU.TEXTURE_BINDING
  });
  const depthBuf = device.createTexture({
    size: { width: mapSize, height: mapSize },
    format: "depth32float",
    usage: TU.RENDER_ATTACHMENT
  });
  const blurTexH = device.createTexture({
    size: { width: blurSize, height: blurSize },
    format: "rgba16float",
    usage: TU.RENDER_ATTACHMENT | TU.TEXTURE_BINDING
  });
  const blurTexV = device.createTexture({
    size: { width: blurSize, height: blurSize },
    format: "rgba16float",
    usage: TU.RENDER_ATTACHMENT | TU.TEXTURE_BINDING
  });
  const blurVert = device.createShaderModule({ code: blurVertSrc });
  const blurFrag = device.createShaderModule({ code: createShadowBlurFragmentWGSL(blurKernel) });
  const blurBGL = device.createBindGroupLayout({
    entries: [
      { binding: 0, visibility: SS.VERTEX | SS.FRAGMENT, buffer: { type: "uniform" } },
      { binding: 1, visibility: SS.FRAGMENT, texture: { sampleType: "float" } },
      { binding: 2, visibility: SS.FRAGMENT, sampler: { type: "filtering" } }
    ]
  });
  const blurPipeline = device.createRenderPipeline({
    layout: device.createPipelineLayout({ bindGroupLayouts: [blurBGL] }),
    vertex: { module: blurVert, entryPoint: "main" },
    fragment: { module: blurFrag, entryPoint: "main", targets: [{ format: "rgba16float" }] },
    primitive: { topology: "triangle-list", cullMode: "none" }
  });
  const blurSampler = getBilinearSampler(engine);
  const blurHData = new F32([1 / blurSize, 0, 0, 0]);
  const blurHUBO = createUniformBuffer(engine, blurHData);
  const blurHBG = device.createBindGroup({
    layout: blurBGL,
    entries: [
      { binding: 0, resource: { buffer: blurHUBO } },
      { binding: 1, resource: esmTexture.createView() },
      { binding: 2, resource: blurSampler }
    ]
  });
  const blurVData = new F32([0, 1 / blurSize, 0, 0]);
  const blurVUBO = createUniformBuffer(engine, blurVData);
  const blurVBG = device.createBindGroup({
    layout: blurBGL,
    entries: [
      { binding: 0, resource: { buffer: blurVUBO } },
      { binding: 1, resource: blurTexH.createView() },
      { binding: 2, resource: blurSampler }
    ]
  });
  const _lightMatrix = new F32(16);
  const _shadowsInfo = new F32([darkness, 0, depthScale, frustumEdgeFalloff]);
  const _depthValues = new F32([0, 1]);
  const { ubo: _shadowUBO, data: shadowUboData } = createSharedShadowUBO(engine, _lightMatrix, _depthValues, _shadowsInfo);
  const _depthTexture = blurTexV;
  const _depthSampler = blurSampler;
  const sg = {
    _shadowType: "esm",
    _light,
    _depthTexture,
    _depthSampler,
    _lightMatrix,
    _shadowsInfo,
    _depthValues,
    _shadowParamsUBO,
    _shadowUBO,
    _config,
    _version: 0
  };
  sg._preloadShadowTask = preloadEsmShadowTaskState;
  sg._ensureShadowTaskState = (engine2, scene, casterMeshes) => {
    const state = ensureEsmShadowTaskState(engine2, scene, sg, casterMeshes, sg._shadowTaskState ?? null);
    sg._shadowTaskState = state;
    return state;
  };
  sg._renderShadowMap = (engine2, state) => {
    return renderEsmShadowMap(engine2, sg, state);
  };
  setEsmShadowTaskResources(sg, {
    _esmTexture: esmTexture,
    _depthBuffer: depthBuf,
    _blurTexH: blurTexH,
    _blurPipeline: blurPipeline,
    _blurHBG: blurHBG,
    _blurVBG: blurVBG,
    _shadowUboData: shadowUboData,
    _blurKernel: blurKernel,
    _blurScale: blurScale
  });
  return sg;
}

var esmDirectionalShadowGenerator = /*#__PURE__*/Object.freeze({
    __proto__: null,
    createEsmDirectionalShadowGenerator: createEsmDirectionalShadowGenerator,
    getEsmShadowTaskResources: getEsmShadowTaskResources,
    setEsmShadowTaskResources: setEsmShadowTaskResources
});

const INTERP_LINEAR = 0;
const INTERP_STEP = 1;
const INTERP_CUBICSPLINE = 2;
const PATH_TRANSLATION = 0;
const PATH_ROTATION = 1;
const PATH_SCALE = 2;
const PATH_WEIGHTS = 3;
const PATH_POINTER = 4;

function findKeyframe(input, t) {
  let lo = 0;
  let hi = input.length - 1;
  if (t <= input[0]) {
    return 0;
  }
  if (t >= input[hi]) {
    return hi > 0 ? hi - 1 : 0;
  }
  while (lo < hi - 1) {
    const mid = lo + hi >> 1;
    if (input[mid] <= t) {
      lo = mid;
    } else {
      hi = mid;
    }
  }
  return lo;
}
const _quat = new F32([0, 0, 0, 1]);
function normalizeQuat4(buf, o) {
  const x = buf[o];
  const y = buf[o + 1];
  const z = buf[o + 2];
  const w = buf[o + 3];
  const lenSq = x * x + y * y + z * z + w * w;
  if (lenSq > 0) {
    const inv = 1 / Math.sqrt(lenSq);
    buf[o] = x * inv;
    buf[o + 1] = y * inv;
    buf[o + 2] = z * inv;
    buf[o + 3] = w * inv;
  }
}
function quatSlerp(out, ax, ay, az, aw, bx, by, bz, bw, t) {
  let dot = ax * bx + ay * by + az * bz + aw * bw;
  if (dot < 0) {
    bx = -bx;
    by = -by;
    bz = -bz;
    bw = -bw;
    dot = -dot;
  }
  if (dot > 0.9995) {
    out[0] = ax + t * (bx - ax);
    out[1] = ay + t * (by - ay);
    out[2] = az + t * (bz - az);
    out[3] = aw + t * (bw - aw);
    normalizeQuat4(out, 0);
    return;
  }
  const theta = Math.acos(dot);
  const sinTheta = Math.sin(theta);
  const wa = Math.sin((1 - t) * theta) / sinTheta;
  const wb = Math.sin(t * theta) / sinTheta;
  out[0] = wa * ax + wb * bx;
  out[1] = wa * ay + wb * by;
  out[2] = wa * az + wb * bz;
  out[3] = wa * aw + wb * bw;
}
function evaluateSampler(sampler, t, stride, isQuat, dst, dstOffset) {
  const { input, output, interpolation } = sampler;
  const keyCount = input.length;
  if (keyCount === 0) {
    return;
  }
  if (keyCount === 1 || t <= input[0]) {
    const srcOff = interpolation === INTERP_CUBICSPLINE ? stride : 0;
    for (let c = 0; c < stride; c++) {
      dst[dstOffset + c] = output[srcOff + c];
    }
    return;
  }
  const idx = findKeyframe(input, t);
  const t0 = input[idx];
  const t1 = input[idx + 1];
  if (interpolation === INTERP_STEP) {
    const srcOff = (t >= t1 ? idx + 1 : idx) * stride;
    for (let c = 0; c < stride; c++) {
      dst[dstOffset + c] = output[srcOff + c];
    }
    return;
  }
  const dt = t1 - t0;
  const f = t >= t1 ? 1 : dt > 0 ? (t - t0) / dt : 0;
  if (interpolation === INTERP_CUBICSPLINE) {
    const f2 = f * f;
    const f3 = f2 * f;
    const h00 = 2 * f3 - 3 * f2 + 1;
    const h10 = f3 - 2 * f2 + f;
    const h01 = -2 * f3 + 3 * f2;
    const h11 = f3 - f2;
    const k0 = idx * stride * 3;
    const k1 = (idx + 1) * stride * 3;
    for (let c = 0; c < stride; c++) {
      const p0 = output[k0 + stride + c];
      const m0 = output[k0 + 2 * stride + c] * dt;
      const p1 = output[k1 + stride + c];
      const m1 = output[k1 + c] * dt;
      dst[dstOffset + c] = h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1;
    }
    if (isQuat) {
      normalizeQuat4(dst, dstOffset);
    }
    return;
  }
  const s0 = idx * stride;
  const s1 = (idx + 1) * stride;
  if (isQuat) {
    quatSlerp(_quat, output[s0], output[s0 + 1], output[s0 + 2], output[s0 + 3], output[s1], output[s1 + 1], output[s1 + 2], output[s1 + 3], f);
    dst[dstOffset] = _quat[0];
    dst[dstOffset + 1] = _quat[1];
    dst[dstOffset + 2] = _quat[2];
    dst[dstOffset + 3] = _quat[3];
  } else {
    for (let c = 0; c < stride; c++) {
      dst[dstOffset + c] = output[s0 + c] + f * (output[s1 + c] - output[s0 + c]);
    }
  }
}

const _boneTmp = new F32(16);
const RH_TO_LH = new F32([-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
const TRS_STRIDE = 12;
const T_OFF = 0;
const R_OFF = 3;
const S_OFF = 7;
function computeTopoOrder(nodes) {
  const n = nodes.length;
  const order = new I32(n);
  const visited = new U8(n);
  let cursor = 0;
  function visit(idx) {
    if (visited[idx]) {
      return;
    }
    visited[idx] = 1;
    const p = nodes[idx].parentIdx;
    if (p >= 0) {
      visit(p);
    }
    order[cursor++] = idx;
  }
  for (let i = 0; i < n; i++) {
    visit(i);
  }
  return order;
}
function createAnimationController(clip, nodes, skeletons, morphBindings, nodeTargets, excludedNodeIndices, boneOverrides, nodeNames) {
  const requiresEngine = skeletons.length > 0 || morphBindings.length > 0;
  const numNodes = nodes.length;
  const nodeTrsBindings = [];
  if (nodeTargets) {
    const maskByNode = /* @__PURE__ */ new Map();
    for (let ci = 0; ci < clip.channels.length; ci++) {
      const ch = clip.channels[ci];
      const bit = ch.path === PATH_TRANSLATION ? 1 : ch.path === PATH_ROTATION ? 2 : ch.path === PATH_SCALE ? 4 : 0;
      if (bit === 0) {
        continue;
      }
      const ni = ch.nodeIdx;
      if (ni < 0 || excludedNodeIndices?.has(ni) || !nodeTargets[ni]) {
        continue;
      }
      maskByNode.set(ni, (maskByNode.get(ni) ?? 0) | bit);
    }
    for (const [ni, mask] of maskByNode) {
      nodeTrsBindings.push({ target: nodeTargets[ni], off: ni * TRS_STRIDE, mask });
    }
  }
  const currentTRS = new F32(numNodes * TRS_STRIDE);
  const localMat = new F32(numNodes * 16);
  const worldMat = new F32(numNodes * 16);
  const topoOrder = computeTopoOrder(nodes);
  const boneScratch = skeletons.map((s) => s.boneMatrices);
  const morphBindingsByNode = [];
  for (let morphIndex = 0; morphIndex < morphBindings.length; morphIndex++) {
    const mb = morphBindings[morphIndex];
    let arr = morphBindingsByNode[mb.nodeIdx];
    if (!arr) {
      arr = [];
      morphBindingsByNode[mb.nodeIdx] = arr;
    }
    arr.push(mb);
  }
  const pointerScratch = new F32(16);
  let morphUploadF32 = pointerScratch;
  let cachedEngine;
  let uploadGpu = true;
  const _setMask = (mask) => {
    if (!mask || mask.disabled || !nodeNames || true) {
      return;
    }
  };
  const ctrl = {
    time: 0,
    playing: true,
    speedRatio: 1,
    loop: true,
    _setMask,
    _debugWorldMat: worldMat,
    _tickCpu(deltaMs, engine) {
      const previous = uploadGpu;
      uploadGpu = false;
      try {
        ctrl.tick(deltaMs, engine);
      } finally {
        uploadGpu = previous;
      }
    },
    tick: clip.duration <= 0 ? noopAnimationTick : (deltaMs, engine) => {
      if (engine) {
        cachedEngine = engine;
      }
      const activeEngine = engine ?? cachedEngine;
      if (requiresEngine && uploadGpu && !activeEngine) {
        ThrowLiteError(378);
      }
      const device = requiresEngine && uploadGpu ? activeEngine._device : null;
      if (ctrl.playing) {
        ctrl.time += deltaMs / 1e3 * ctrl.speedRatio;
      }
      if (ctrl.loop) {
        ctrl.time %= clip.duration;
        if (ctrl.time < 0) {
          ctrl.time += clip.duration;
        }
      } else {
        ctrl.time = Math.min(Math.max(ctrl.time, 0), clip.duration);
      }
      const t = ctrl.time;
      for (let i = 0; i < numNodes; i++) {
        const n = nodes[i];
        const off = i * TRS_STRIDE;
        currentTRS[off + T_OFF] = n.tx;
        currentTRS[off + T_OFF + 1] = n.ty;
        currentTRS[off + T_OFF + 2] = n.tz;
        currentTRS[off + R_OFF] = n.rx;
        currentTRS[off + R_OFF + 1] = n.ry;
        currentTRS[off + R_OFF + 2] = n.rz;
        currentTRS[off + R_OFF + 3] = n.rw;
        currentTRS[off + S_OFF] = n.sx;
        currentTRS[off + S_OFF + 1] = n.sy;
        currentTRS[off + S_OFF + 2] = n.sz;
      }
      if (boneOverrides !== void 0 && boneOverrides.size > 0) ;
      for (let channelIndex = 0; channelIndex < clip.channels.length; channelIndex++) {
        const ch = clip.channels[channelIndex];
        const sampler = clip.samplers[ch.samplerIdx];
        const base = ch.nodeIdx * TRS_STRIDE;
        switch (ch.path) {
          case PATH_TRANSLATION:
            evaluateSampler(sampler, t, 3, false, currentTRS, base + T_OFF);
            break;
          case PATH_ROTATION:
            evaluateSampler(sampler, t, 4, true, currentTRS, base + R_OFF);
            break;
          case PATH_SCALE:
            evaluateSampler(sampler, t, 3, false, currentTRS, base + S_OFF);
            break;
          case PATH_WEIGHTS: {
            const bindings = morphBindingsByNode[ch.nodeIdx];
            if (bindings) {
              const tc = bindings[0].targetCount;
              if (tc > morphUploadF32.length) {
                morphUploadF32 = new F32(tc);
              }
              morphUploadF32.fill(0);
              evaluateSampler(sampler, t, tc, false, morphUploadF32, 0);
              for (let bindingIndex = 0; bindingIndex < bindings.length; bindingIndex++) {
                const mb = bindings[bindingIndex];
                mb.weights.set(morphUploadF32.subarray(0, tc));
                if (uploadGpu) {
                  device.queue.writeBuffer(mb.runtimeMorphTargets?.weightsBuffer ?? mb.weightsBuffer, 16, morphUploadF32.buffer, 0, tc * 4);
                }
              }
            }
            break;
          }
          case PATH_POINTER: {
            if (ch.pointerArity && ch.pointerWriter) {
              evaluateSampler(sampler, t, ch.pointerArity, ch.pointerQuaternion === true, pointerScratch, 0);
              ch.pointerWriter(pointerScratch, 0);
            }
            break;
          }
        }
      }
      if (boneOverrides !== void 0 && boneOverrides.size > 0) ;
      for (let bi = 0; bi < nodeTrsBindings.length; bi++) {
        const b = nodeTrsBindings[bi];
        const o = b.off;
        if (b.mask & 1) {
          b.target.position.set(currentTRS[o + T_OFF], currentTRS[o + T_OFF + 1], currentTRS[o + T_OFF + 2]);
        }
        if (b.mask & 2) {
          b.target.rotationQuaternion.set(currentTRS[o + R_OFF], currentTRS[o + R_OFF + 1], currentTRS[o + R_OFF + 2], currentTRS[o + R_OFF + 3]);
        }
        if (b.mask & 4) {
          b.target.scaling.set(currentTRS[o + S_OFF], currentTRS[o + S_OFF + 1], currentTRS[o + S_OFF + 2]);
        }
      }
      for (let idx = 0; idx < numNodes; idx++) {
        const nodeIdx = topoOrder[idx];
        const node = nodes[nodeIdx];
        const off = nodeIdx * TRS_STRIDE;
        if (node._matrix) {
          localMat.set(node._matrix, nodeIdx * 16);
        } else {
          mat4ComposeInto(
            localMat,
            nodeIdx * 16,
            currentTRS[off + T_OFF],
            currentTRS[off + T_OFF + 1],
            currentTRS[off + T_OFF + 2],
            currentTRS[off + R_OFF],
            currentTRS[off + R_OFF + 1],
            currentTRS[off + R_OFF + 2],
            currentTRS[off + R_OFF + 3],
            currentTRS[off + S_OFF],
            currentTRS[off + S_OFF + 1],
            currentTRS[off + S_OFF + 2]
          );
        }
        const parentIdx = node.parentIdx;
        if (parentIdx >= 0) {
          mat4MultiplyInto(worldMat, nodeIdx * 16, worldMat, parentIdx * 16, localMat, nodeIdx * 16);
        } else {
          mat4MultiplyInto(worldMat, nodeIdx * 16, RH_TO_LH, 0, localMat, nodeIdx * 16);
        }
      }
      for (let si = 0; si < skeletons.length; si++) {
        const skel = skeletons[si];
        const boneData = boneScratch[si];
        for (let bi = 0; bi < skel.boneCount; bi++) {
          const jointIdx = skel.jointNodes[bi];
          const ibmOff = bi * 16;
          mat4MultiplyInto(_boneTmp, 0, skel.invMeshWorld, 0, worldMat, jointIdx * 16);
          mat4MultiplyInto(boneData, bi * 16, _boneTmp, 0, skel.inverseBindMatrices, ibmOff);
        }
        if (uploadGpu) {
          const texWidth = skel.boneCount * 4;
          device.queue.writeTexture(
            { texture: skel.runtimeSkeleton?.boneTexture ?? skel.boneTexture },
            boneData.buffer,
            { bytesPerRow: texWidth * 16 },
            { width: texWidth, height: 1 }
          );
        }
      }
    }
  };
  return ctrl;
}
function noopAnimationTick() {
}

const DEFAULT_FRAME_RATE$1 = 60;
function playAnimation(group) {
  group.isPlaying = true;
  group._stopped = false;
}
function pauseAnimation(group) {
  group.isPlaying = false;
}
function stopAnimation(group) {
  group.isPlaying = false;
  group.currentTime = 0;
  group._stopped = true;
}
function syncControllerFromGroup(group, ctrl) {
  ctrl.time = group.currentTime;
  ctrl.playing = group.isPlaying;
  ctrl.speedRatio = group.speedRatio;
  ctrl.loop = group.loopAnimation;
  ctrl._setMask?.(group.mask ?? null);
}
function tickAnimationCore(group, deltaMs, engine) {
  if (!group._stopped && group._ctrl) {
    syncControllerFromGroup(group, group._ctrl);
    group._ctrl.tick(deltaMs, engine);
    group.currentTime = group._ctrl.time;
  }
}
function tickAnimationImpl(group, deltaMs, engine) {
  if (group._animationManager) {
    return;
  }
  tickAnimationCore(group, deltaMs, engine);
}
function _installTickAnimation() {
  _setTickAnimationImpl(tickAnimationImpl);
}
function goToFrame(group, frame, engine) {
  const ctrl = group._ctrl;
  group.currentTime = frame / (group.frameRate || DEFAULT_FRAME_RATE$1);
  group.isPlaying = false;
  if (ctrl) {
    syncControllerFromGroup(group, ctrl);
    if (engine || !group._stopped || !group._gltfMixer) {
      ctrl.tick(0, engine);
      group.currentTime = ctrl.time;
    }
  }
}
function createAnimationGroups(animData) {
  const { clips, nodes, skeletons, morphBindings, nodeTargets, excludedNodeIndices, nodeNames, boneOverrides } = animData;
  const hasPointer = clips.some((c) => c.channels.some((ch) => ch.path === PATH_POINTER));
  const hasNodeWriteback = clips.some(
    (c) => c.channels.some(
      (ch) => (ch.path === PATH_TRANSLATION || ch.path === PATH_ROTATION || ch.path === PATH_SCALE) && ch.nodeIdx >= 0 && !excludedNodeIndices.has(ch.nodeIdx) && !!nodeTargets[ch.nodeIdx]
    )
  );
  if (clips.length === 0 || skeletons.length === 0 && morphBindings.length === 0 && !hasPointer && !hasNodeWriteback) {
    return [];
  }
  _installTickAnimation();
  return clips.map((clip, clipIndex) => {
    const ctrl = createAnimationController(clip, nodes, skeletons, morphBindings, nodeTargets, excludedNodeIndices, boneOverrides, nodeNames);
    const started = clipIndex === 0;
    const group = {
      name: clip.name || `animation_${clipIndex}`,
      duration: clip.duration,
      frameRate: clip.frameRate || DEFAULT_FRAME_RATE$1,
      isPlaying: started,
      currentTime: 0,
      targetedAnimations: clip.channels.map((ch) => {
        const nodeIndex = ch.nodeIdx >= 0 ? ch.nodeIdx : void 0;
        return {
          target: nodeIndex !== void 0 ? nodeTargets[nodeIndex] : void 0,
          targetName: nodeIndex !== void 0 ? nodeNames[nodeIndex] : void 0,
          nodeIndex,
          path: pathName(ch.path)
        };
      }),
      speedRatio: 1,
      loopAnimation: true,
      weight: 1,
      _ctrl: ctrl,
      _stopped: !started
    };
    group._gltfMixer = [clip, nodes, skeletons];
    return group;
  });
}
function pathName(path) {
  return path === PATH_TRANSLATION ? "translation" : path === PATH_ROTATION ? "rotation" : path === PATH_SCALE ? "scale" : path === PATH_POINTER ? "pointer" : "weights";
}

var animationGroup = /*#__PURE__*/Object.freeze({
    __proto__: null,
    _installTickAnimation: _installTickAnimation,
    createAnimationGroups: createAnimationGroups,
    goToFrame: goToFrame,
    pauseAnimation: pauseAnimation,
    playAnimation: playAnimation,
    stopAnimation: stopAnimation,
    tickAnimationCore: tickAnimationCore
});

const FULL_VIEWPORT = { x: 0, y: 0, width: 1, height: 1 };
function clamp01(value) {
  return Math.max(0, Math.min(1, value));
}
function resolveCameraViewport(camera, targetWidth, targetHeight) {
  const v = camera?.viewport ?? FULL_VIEWPORT;
  const x0 = clamp01(v.x);
  const y0 = clamp01(1 - v.y - v.height);
  const x1 = clamp01(v.x + v.width);
  const y1 = clamp01(1 - v.y);
  const x = Math.floor(x0 * targetWidth);
  const y = Math.floor(y0 * targetHeight);
  const width = Math.max(0, Math.ceil(x1 * targetWidth) - x);
  const height = Math.max(0, Math.ceil(y1 * targetHeight) - y);
  return { x, y, width, height };
}

const DEFAULT_FRAME_RATE = 60;
function extentCorners(min, max) {
  const c = new Float32Array(24);
  for (let i = 0; i < 8; i++) {
    c[i * 3] = i & 1 ? max[0] : min[0];
    c[i * 3 + 1] = i & 2 ? max[1] : min[1];
    c[i * 3 + 2] = i & 4 ? max[2] : min[2];
  }
  return c;
}
function computeMorphedRange(mesh, vertexCount) {
  const positions = mesh._cpuPositions;
  const componentCount = vertexCount * 3;
  const minP = new Float32Array(positions.subarray(0, componentCount));
  const maxP = new Float32Array(minP);
  const morph = mesh.morphTargets;
  if (morph) {
    for (const target of morph.targets) {
      const deltas = target.positions;
      const count = Math.min(deltas.length, componentCount);
      for (let i = 0; i < count; i++) {
        const p = positions[i] + deltas[i];
        if (p < minP[i]) {
          minP[i] = p;
        }
        if (p > maxP[i]) {
          maxP[i] = p;
        }
      }
    }
  }
  return { minP, maxP };
}
function buildContribution(mesh) {
  const positions = mesh._cpuPositions;
  if (!positions || positions.length === 0) {
    if (mesh.boundMin && mesh.boundMax) {
      return { bones: null, corners: extentCorners(mesh.boundMin, mesh.boundMax) };
    }
    return { bones: null, corners: null };
  }
  const vertexCount = positions.length / 3 | 0;
  const { minP, maxP } = computeMorphedRange(mesh, vertexCount);
  const skeleton = mesh.skeleton;
  if (skeleton && skeleton.weights) {
    const boneCount = skeleton.boneCount;
    const boneMin = new Float32Array(boneCount * 3).fill(Number.POSITIVE_INFINITY);
    const boneMax = new Float32Array(boneCount * 3).fill(Number.NEGATIVE_INFINITY);
    const boneUsed = new Uint8Array(boneCount);
    const accumulate = (joints, weights, vertex) => {
      const base = vertex * 4;
      for (let k = 0; k < 4; k++) {
        if (weights[base + k] > 0) {
          const bone = joints[base + k];
          if (bone < boneCount) {
            const bo = bone * 3;
            const vo = vertex * 3;
            if (minP[vo] < boneMin[bo]) {
              boneMin[bo] = minP[vo];
            }
            if (minP[vo + 1] < boneMin[bo + 1]) {
              boneMin[bo + 1] = minP[vo + 1];
            }
            if (minP[vo + 2] < boneMin[bo + 2]) {
              boneMin[bo + 2] = minP[vo + 2];
            }
            if (maxP[vo] > boneMax[bo]) {
              boneMax[bo] = maxP[vo];
            }
            if (maxP[vo + 1] > boneMax[bo + 1]) {
              boneMax[bo + 1] = maxP[vo + 1];
            }
            if (maxP[vo + 2] > boneMax[bo + 2]) {
              boneMax[bo + 2] = maxP[vo + 2];
            }
            boneUsed[bone] = 1;
          }
        }
      }
    };
    const joints0 = skeleton.joints;
    const weights0 = skeleton.weights;
    const joints1 = skeleton.joints1;
    const weights1 = skeleton.weights1;
    for (let v = 0; v < vertexCount; v++) {
      accumulate(joints0, weights0, v);
      if (joints1 && weights1) {
        accumulate(joints1, weights1, v);
      }
    }
    const bones = [];
    for (let b = 0; b < boneCount; b++) {
      if (boneUsed[b]) {
        const o = b * 3;
        bones.push({
          boneIndex: b,
          corners: extentCorners([boneMin[o], boneMin[o + 1], boneMin[o + 2]], [boneMax[o], boneMax[o + 1], boneMax[o + 2]])
        });
      }
    }
    return { bones, corners: null };
  }
  let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, minZ = Number.POSITIVE_INFINITY;
  let maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY, maxZ = Number.NEGATIVE_INFINITY;
  for (let v = 0; v < vertexCount; v++) {
    const o = v * 3;
    if (minP[o] < minX) {
      minX = minP[o];
    }
    if (minP[o + 1] < minY) {
      minY = minP[o + 1];
    }
    if (minP[o + 2] < minZ) {
      minZ = minP[o + 2];
    }
    if (maxP[o] > maxX) {
      maxX = maxP[o];
    }
    if (maxP[o + 1] > maxY) {
      maxY = maxP[o + 1];
    }
    if (maxP[o + 2] > maxZ) {
      maxZ = maxP[o + 2];
    }
  }
  return { bones: null, corners: extentCorners([minX, minY, minZ], [maxX, maxY, maxZ]) };
}
function accumulateCorners(corners, matrix, extent) {
  const m0 = matrix[0], m1 = matrix[1], m2 = matrix[2], m4 = matrix[4], m5 = matrix[5], m6 = matrix[6], m8 = matrix[8], m9 = matrix[9], m10 = matrix[10], m12 = matrix[12], m13 = matrix[13], m14 = matrix[14];
  const min = extent.minimum;
  const max = extent.maximum;
  for (let i = 0; i < 8; i++) {
    const lx = corners[i * 3];
    const ly = corners[i * 3 + 1];
    const lz = corners[i * 3 + 2];
    const x = m0 * lx + m4 * ly + m8 * lz + m12;
    const y = m1 * lx + m5 * ly + m9 * lz + m13;
    const z = m2 * lx + m6 * ly + m10 * lz + m14;
    if (x < min[0]) {
      min[0] = x;
    }
    if (y < min[1]) {
      min[1] = y;
    }
    if (z < min[2]) {
      min[2] = z;
    }
    if (x > max[0]) {
      max[0] = x;
    }
    if (y > max[1]) {
      max[1] = y;
    }
    if (z > max[2]) {
      max[2] = z;
    }
  }
}
function computeMaxExtents(meshes, animationGroup = null, engine = null, animationStep = 1 / 6) {
  const contributions = meshes.map(buildContribution);
  const extents = meshes.map(() => ({
    minimum: [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
    maximum: [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY]
  }));
  const scratchMatrix = new Float32Array(16);
  const updateExtents = () => {
    for (let i = 0; i < meshes.length; i++) {
      const contribution = contributions[i];
      const worldMatrix = meshes[i].worldMatrix;
      if (contribution.bones) {
        const boneMatrices = meshes[i].skeleton.boneMatrices;
        for (const bone of contribution.bones) {
          mat4MultiplyInto(scratchMatrix, 0, worldMatrix, 0, boneMatrices, bone.boneIndex * 16);
          accumulateCorners(bone.corners, scratchMatrix, extents[i]);
        }
      } else if (contribution.corners) {
        accumulateCorners(contribution.corners, worldMatrix, extents[i]);
      }
    }
  };
  if (animationGroup && animationGroup.duration > 0) {
    const frameRate = animationGroup.frameRate || DEFAULT_FRAME_RATE;
    const savedTime = animationGroup.currentTime;
    const savedPlaying = animationGroup.isPlaying;
    const savedStopped = animationGroup._stopped;
    const step = Math.max(animationStep, 1e-3);
    const engineArg = engine ?? void 0;
    animationGroup._stopped = false;
    for (let time = 0; time <= animationGroup.duration; time += step) {
      goToFrame(animationGroup, time * frameRate, engineArg);
      updateExtents();
    }
    goToFrame(animationGroup, savedTime * frameRate, engineArg);
    animationGroup._stopped = savedStopped;
    animationGroup.isPlaying = savedPlaying;
  } else {
    updateExtents();
  }
  return extents;
}

function createEmptyPickingInfo() {
  return {
    hit: false,
    distance: 0,
    pickedPoint: null,
    pickedNormal: null,
    pickedNormalWorld: null,
    pickedFaceNormal: null,
    pickedFaceNormalWorld: null,
    pickedMesh: null,
    faceId: -1,
    bu: 0,
    bv: 0,
    subMeshId: 0,
    thinInstanceIndex: -1,
    ray: null,
    _normalsInvalid: false
  };
}

function createSingleUniformBGL(engine, label, visibility) {
  return engine._device.createBindGroupLayout({
    label,
    entries: [{ binding: 0, visibility, buffer: { type: "uniform" } }]
  });
}

let _device = null;
let _layout = null;
function getPickingSceneBGL(engine) {
  if (_device !== engine._device) {
    _device = engine._device;
    _layout = null;
  }
  return _layout ??= createSingleUniformBGL(engine, "picking-scene-bgl", SS.VERTEX | SS.FRAGMENT);
}

const _pickVP = new F32(20);
const PICK_MESH_UBO_BYTES = 80;
const _uboScratch = new ArrayBuffer(PICK_MESH_UBO_BYTES);
const _uboF32 = new F32(_uboScratch);
const _uboU32 = new U32(_uboScratch);
const _uboView = new U8(_uboScratch);
function createGpuPicker(scene) {
  return {
    _detailedPicking: false,
    _device: null,
    _scene: scene,
    _rt: null,
    _sceneUbo: null,
    _sceneBG: null,
    _contributors: null,
    _pending: null
  };
}
function ensurePickerDevice(engine, picker) {
  if (picker._device === engine._device) {
    return;
  }
  if (picker._device) {
    disposePicker(picker);
  }
  picker._device = engine._device;
}
function ensureTargets(engine, picker) {
  const device = engine._device;
  if (picker._rt) {
    return picker._rt;
  }
  const colorTex = device.createTexture({ label: "pick-color", size: [1, 1], format: "rgba8unorm", usage: TU.RENDER_ATTACHMENT | TU.COPY_SRC });
  const depthColorTex = device.createTexture({
    label: "pick-depth-color",
    size: [1, 1],
    format: "r32float",
    usage: TU.RENDER_ATTACHMENT | TU.COPY_SRC
  });
  const depthTex = device.createTexture({ label: "pick-depth", size: [1, 1], format: "depth24plus", usage: TU.RENDER_ATTACHMENT });
  picker._rt = {
    colorTex,
    colorView: colorTex.createView(),
    depthColorTex,
    depthColorView: depthColorTex.createView(),
    detail: null,
    depthTex,
    depthView: depthTex.createView(),
    colorStaging: device.createBuffer({ label: "pick-color-staging", size: 256, usage: BU.COPY_DST | BU.MAP_READ }),
    depthStaging: device.createBuffer({ label: "pick-depth-staging", size: 256, usage: BU.COPY_DST | BU.MAP_READ })
  };
  return picker._rt;
}
function ensureSceneUbo(engine, picker) {
  const device = engine._device;
  if (!picker._sceneUbo) {
    picker._sceneUbo = createEmptyUniformBuffer(engine, 80, "pick-scene-ubo");
    const sceneBGL = getPickingSceneBGL(engine);
    picker._sceneBG = device.createBindGroup({ label: "pick-scene-bg", layout: sceneBGL, entries: [{ binding: 0, resource: { buffer: picker._sceneUbo } }] });
  }
  return picker._sceneUbo;
}
function computePickVP(out, vp, sampleX, sampleY, w, h) {
  const ndcX = 2 * sampleX / w - 1;
  const ndcY = 1 - 2 * sampleY / h;
  for (let c = 0; c < 4; c++) {
    const base = c * 4;
    const w3 = vp[base + 3];
    out[base] = w * (vp[base] - ndcX * w3);
    out[base + 1] = h * (vp[base + 1] - ndcY * w3);
    out[base + 2] = vp[base + 2];
    out[base + 3] = w3;
  }
}
function createPickDiscardBindGroup(engine, layout, discard, mesh, tempBuffers) {
  const storage = discard.storage;
  if (!storage || storage.length === 0) {
    return null;
  }
  const entries = [];
  for (let i = 0; i < storage.length; i++) {
    const data = storage[i].data(mesh);
    if (!data) {
      return null;
    }
    const buffer = createMappedBuffer(engine, data, BU.STORAGE, "pick-discard-storage");
    tempBuffers.push(buffer);
    entries.push({ binding: i, resource: { buffer } });
  }
  const device = engine._device;
  return device.createBindGroup({
    label: `pick-discard-${discard.key}-bg`,
    layout,
    entries
  });
}
async function pickAsyncImpl(picker, x, y, options) {
  const scene = picker._scene;
  const pickFilter = options?.filter ?? null;
  const pickDiscard = options?.discard ?? null;
  const ignored = options?.ignore;
  const debugLabel = options?.debugLabel;
  const engine = scene.surface.engine;
  if (!scene.camera) {
    return createEmptyPickingInfo();
  }
  ensurePickerDevice(engine, picker);
  const device = engine._device;
  const detailed = picker._detailedPicking;
  const preparedContributors = [];
  if (!pickFilter && scene._pickSources.length > 0) {
    const sources = scene._pickSources.slice();
    for (const source of sources) {
      let contributor = picker._contributors?.get(source);
      if (!contributor) {
        const pipeline = await source.load();
        if (!scene._pickSources.includes(source)) {
          continue;
        }
        contributor = pipeline.createPickContributor(source.entity);
        (picker._contributors ??= /* @__PURE__ */ new Map()).set(source, contributor);
      }
      preparedContributors.push({ source, contributor });
    }
  }
  let needsDeformation = false;
  let needsAdvancedPipeline = !!pickDiscard?.worldAdjustWgsl || !!pickDiscard?.vertexData || !!pickDiscard?.storage?.some((storage) => storage.vertex);
  let candidates;
  if (ignored) {
    const prepared = (await import('./picking-ignore-Cp33g1qr.esm.js')).prepareIgnoredCandidates(scene.meshes, ignored, pickFilter, needsAdvancedPipeline);
    candidates = prepared.candidates;
    needsDeformation = prepared.deformed;
    needsAdvancedPipeline = prepared.advanced;
  } else {
    candidates = [];
    for (const mesh of scene.meshes) {
      if (mesh.pickable !== false && (!pickFilter || pickFilter(mesh))) {
        candidates.push({ mesh, ignore: null });
        needsDeformation ||= !!(mesh.morphTargets || mesh.skeleton);
        needsAdvancedPipeline ||= !!mesh.vat || !!mesh.thinInstances || !!mesh._gpu._vbLayout?._p;
      }
    }
  }
  const deformProjection = needsDeformation && !needsAdvancedPipeline ? await import('./deform-picking-projection-G3fO67vb.esm.js') : null;
  const deformedVertex$1 = needsDeformation && detailed ? await Promise.resolve().then(function () { return deformedVertex; }) : null;
  const detailedPicking = detailed ? await import('./detailed-picking-DMw8TQN4.esm.js') : null;
  const debug = debugLabel ? await import('./picking-debug-CEIxZYHh.esm.js') : null;
  const advancedDraw = needsAdvancedPipeline ? await (await import('./picking-advanced-draw-BWjaIOHU.esm.js')).prepareAdvancedDraw(engine, candidates) : null;
  const pipelineApi = advancedDraw ? null : detailed ? await import('./picking-detailed-pipeline-B6UGKf2r.esm.js') : await import('./picking-pipeline-Dm5JdCLw.esm.js');
  if (engine._device !== device) {
    return pickAsyncImpl(picker, x, y, options);
  }
  const canvas = scene.surface.canvas;
  const camera = scene.camera;
  if (!camera) {
    return createEmptyPickingInfo();
  }
  const backingWidth = canvas.width;
  const backingHeight = canvas.height;
  const clientWidth = ("clientWidth" in canvas ? canvas.clientWidth : 0) || backingWidth;
  const clientHeight = ("clientHeight" in canvas ? canvas.clientHeight : 0) || backingHeight;
  const scaleX = backingWidth / clientWidth;
  const scaleY = backingHeight / clientHeight;
  const pickX = x * scaleX;
  const pickY = y * scaleY;
  const viewport = resolveCameraViewport(camera, backingWidth, backingHeight);
  const w = viewport.width;
  const h = viewport.height;
  if (w === 0 || h === 0) {
    return createEmptyPickingInfo();
  }
  if (pickX < viewport.x || pickY < viewport.y || pickX >= viewport.x + viewport.width || pickY >= viewport.y + viewport.height) {
    return createEmptyPickingInfo();
  }
  const px = Math.max(0, Math.min(Math.floor(pickX - viewport.x), w - 1));
  const py = Math.max(0, Math.min(Math.floor(pickY - viewport.y), h - 1));
  const sampleX = pickX - viewport.x;
  const sampleY = pickY - viewport.y;
  const pixelCenterX = px + 0.5;
  const pixelCenterY = py + 0.5;
  const aspect = w / h;
  const vp = getViewProjectionMatrix(camera, aspect);
  const pickRay = detailed || debugLabel ? createPickingRay(sampleX, sampleY, vp, w, h) : null;
  const debugInput = debug ? [x, y, pickX, pickY, px, py, backingWidth, backingHeight, clientWidth, clientHeight, viewport.x, viewport.y, viewport.width, viewport.height] : null;
  computePickVP(_pickVP, vp, sampleX, sampleY, w, h);
  _pickVP[16] = viewport.x + pixelCenterX;
  _pickVP[17] = viewport.y + pixelCenterY;
  const rt = ensureTargets(engine, picker);
  const detailTarget = detailed ? detailedPicking.ensureDetailTarget(engine, rt) : null;
  const sceneUbo = ensureSceneUbo(engine, picker);
  device.queue.writeBuffer(sceneUbo, 0, _pickVP);
  let nextId = 1;
  const encoder = device.createCommandEncoder({ label: "pick" });
  const colorAttachments = [
    { view: rt.colorView, clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: "clear", storeOp: "store" },
    { view: rt.depthColorView, clearValue: { r: 1, g: 0, b: 0, a: 0 }, loadOp: "clear", storeOp: "store" }
  ];
  if (detailTarget) {
    colorAttachments.push({ view: detailTarget.view, clearValue: { r: 4294967295, g: 0, b: 0, a: 0 }, loadOp: "clear", storeOp: "store" });
  }
  const pass = encoder.beginRenderPass({
    colorAttachments,
    depthStencilAttachment: { view: rt.depthView, depthClearValue: 0, depthLoadOp: "clear", depthStoreOp: "discard" }
  });
  const tempBuffers = [];
  const detailedPositions = detailed ? /* @__PURE__ */ new Map() : null;
  const detailedNormals = detailed ? /* @__PURE__ */ new Map() : null;
  const detailedPoses = deformedVertex$1?.captureDeformPoses(candidates) ?? null;
  let meshRanges;
  if (advancedDraw) {
    const result = advancedDraw.draw(pass, picker._sceneBG, nextId, pickDiscard, detailed, detailedPicking, tempBuffers, detailedPositions, detailedNormals);
    nextId = result.nextId;
    meshRanges = result.ranges;
  } else {
    meshRanges = [];
    for (const { mesh } of candidates) {
      const gpu = mesh._gpu;
      const projection = deformProjection?.getDeformPickingProjection(engine, mesh) ?? null;
      const defaults = pipelineApi.getPickingPipelineSet(engine, null, projection);
      const discarded = pickDiscard ? pipelineApi.getPickingPipelineSet(engine, pickDiscard, projection) : null;
      if (detailedPositions && mesh._cpuPositions) {
        detailedPositions.set(mesh, mesh._cpuPositions);
      }
      if (detailedNormals && mesh._cpuNormals) {
        detailedNormals.set(mesh, mesh._cpuNormals);
      }
      const discardBG = pickDiscard && discarded?.discardBGL ? createPickDiscardBindGroup(engine, discarded.discardBGL, pickDiscard, mesh, tempBuffers) : null;
      const set = discarded && (!discarded.discardBGL || discardBG) ? discarded : defaults;
      _uboF32.set(mesh.worldMatrix, 0);
      _uboU32[16] = nextId;
      const ubo = createUniformBuffer(engine, _uboView, "pick-mesh-ubo");
      tempBuffers.push(ubo);
      pass.setPipeline(set.regularPipeline);
      pass.setBindGroup(0, picker._sceneBG);
      pass.setBindGroup(
        1,
        device.createBindGroup({
          layout: set.regularPipeline.getBindGroupLayout(1),
          entries: [{ binding: 0, resource: { buffer: ubo } }]
        })
      );
      if (discardBG) {
        pass.setBindGroup(2, discardBG);
      }
      pass.setVertexBuffer(0, gpu.positionBuffer);
      if (projection) {
        deformProjection.bindDeformPickingProjection(engine, pass, set.regularPipeline, mesh, 1, !!discardBG);
      }
      pass.setIndexBuffer(gpu.indexBuffer, gpu.indexFormat);
      pass.drawIndexed(gpu.indexCount);
      meshRanges.push({
        base: nextId++,
        count: 1,
        mesh,
        thin: false,
        world: detailedPicking ? detailedPicking.copyDetailedWorldMatrix(mesh.worldMatrix) : null,
        thinVersion: 0,
        worldAdjusted: false
      });
    }
  }
  const contribRanges = [];
  if (preparedContributors.length > 0) {
    const pickCtx = { picker, pass, engine, scene, camera, sceneBG: picker._sceneBG, px: sampleX, py: sampleY, w, h, detailed };
    for (const { source, contributor } of preparedContributors) {
      if (!scene._pickSources.includes(source)) {
        continue;
      }
      const base = nextId;
      nextId = contributor.draw(pickCtx, base);
      if (nextId > base) {
        contribRanges.push({ base, count: nextId - base, contributor });
      }
    }
  }
  pass.end();
  encoder.copyTextureToBuffer({ texture: rt.colorTex }, { buffer: rt.colorStaging, bytesPerRow: 256 }, { width: 1, height: 1 });
  encoder.copyTextureToBuffer({ texture: rt.depthColorTex }, { buffer: rt.depthStaging, bytesPerRow: 256 }, { width: 1, height: 1 });
  if (detailTarget) {
    detailedPicking.copyDetailTarget(encoder, detailTarget);
  }
  device.queue.submit([encoder.finish()]);
  let pickId;
  let depth;
  let primitiveIndex = -1;
  let localPoint = null;
  try {
    const maps = [rt.colorStaging.mapAsync(GPUMapMode.READ), rt.depthStaging.mapAsync(GPUMapMode.READ)];
    const detailRead = detailTarget ? detailedPicking.readDetailTarget(detailTarget) : null;
    if (detailRead) {
      maps.push(detailRead.then(() => void 0));
    }
    await Promise.all(maps);
    const colorData = new U8(rt.colorStaging.getMappedRange());
    pickId = colorData[0] << 16 | colorData[1] << 8 | colorData[2];
    depth = new F32(rt.depthStaging.getMappedRange())[0];
    if (detailRead) {
      ({ primitiveIndex, localPoint } = await detailRead);
    }
    rt.colorStaging.unmap();
    rt.depthStaging.unmap();
  } finally {
    for (let i = 0; i < tempBuffers.length; i++) {
      tempBuffers[i].destroy();
    }
  }
  if (pickId === 0) {
    if (debug) {
      debug.tracePick(debugLabel, debugInput, pickRay, pickId, depth, false);
    }
    return createEmptyPickingInfo();
  }
  let hitMesh = null;
  let hitRange = null;
  let hitThinIdx = -1;
  for (let i = 0; i < meshRanges.length; i++) {
    const range = meshRanges[i];
    if (pickId >= range.base && pickId < range.base + range.count) {
      hitMesh = range.mesh;
      hitRange = range;
      hitThinIdx = range.thin ? pickId - range.base : -1;
      break;
    }
  }
  let hitContributor = null;
  let contribLocalId = -1;
  if (!hitMesh) {
    for (let ri = 0; ri < contribRanges.length; ri++) {
      const r = contribRanges[ri];
      if (pickId >= r.base && pickId < r.base + r.count) {
        hitContributor = r.contributor;
        contribLocalId = pickId - r.base;
        break;
      }
    }
  }
  if (!hitMesh && !hitContributor) {
    if (debug) {
      debug.tracePick(debugLabel, debugInput, pickRay, pickId, depth, false, true);
    }
    return createEmptyPickingInfo();
  }
  const info = createEmptyPickingInfo();
  info.hit = true;
  info.pickedMesh = hitMesh;
  info.thinInstanceIndex = hitThinIdx;
  info.ray = detailed ? pickRay : null;
  const invVP = mat4Invert(vp);
  if (invVP) {
    const ndcX = 2 * sampleX / w - 1;
    const ndcY = 1 - 2 * sampleY / h;
    const wx = invVP[0] * ndcX + invVP[4] * ndcY + invVP[8] * depth + invVP[12];
    const wy = invVP[1] * ndcX + invVP[5] * ndcY + invVP[9] * depth + invVP[13];
    const wz = invVP[2] * ndcX + invVP[6] * ndcY + invVP[10] * depth + invVP[14];
    const ww = invVP[3] * ndcX + invVP[7] * ndcY + invVP[11] * depth + invVP[15];
    const invW = 1 / ww;
    info.pickedPoint = [wx * invW, wy * invW, wz * invW];
    const origin = detailed && pickRay ? { x: pickRay.origin[0], y: pickRay.origin[1], z: pickRay.origin[2] } : getCameraPosition(camera);
    const dx = info.pickedPoint[0] - origin.x;
    const dy = info.pickedPoint[1] - origin.y;
    const dz = info.pickedPoint[2] - origin.z;
    info.distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
  }
  if (hitContributor) {
    hitContributor.resolve(info, contribLocalId);
  } else if (hitMesh && hitRange?.world && localPoint && primitiveIndex >= 0) {
    const thinStateStable = !hitRange.thin || hitMesh.thinInstances?._version === hitRange.thinVersion;
    if (thinStateStable) {
      const positions = detailedPositions?.get(hitMesh);
      const world = detailedPicking.detailedWorldMatrix(hitRange.world, hitMesh, hitThinIdx);
      const deformTriangle = deformedVertex$1?.deformerFor(detailedPoses, hitMesh) ?? null;
      detailedPicking.populateDetailedMeshInfo(
        info,
        hitMesh,
        primitiveIndex,
        localPoint,
        positions,
        detailedNormals?.get(hitMesh),
        world,
        !hitRange.worldAdjusted,
        deformTriangle
      );
    }
  }
  if (debug) {
    debug.tracePick(debugLabel, debugInput, info.ray ?? pickRay, pickId, depth, true, false, hitMesh ? hitMesh.name ?? "(unnamed)" : "(contributor)", hitThinIdx, info);
  }
  return info;
}
function pickAsync(picker, x, y, options) {
  const prior = picker._pending ?? Promise.resolve();
  const run = prior.then(
    () => pickAsyncImpl(picker, x, y, options),
    () => pickAsyncImpl(picker, x, y, options)
    // a prior pick's rejection must not wedge the queue for this caller
  );
  picker._pending = run.then(
    () => void 0,
    () => void 0
  );
  return run;
}
function disposePicker(picker) {
  if (picker._rt) {
    for (const resource of [
      picker._rt.colorTex,
      picker._rt.depthColorTex,
      picker._rt.detail?.texture,
      picker._rt.depthTex,
      picker._rt.colorStaging,
      picker._rt.depthStaging,
      picker._rt.detail?.staging
    ]) {
      resource?.destroy();
    }
    picker._rt = null;
  }
  picker._sceneUbo?.destroy();
  picker._sceneUbo = null;
  picker._sceneBG = null;
  if (picker._contributors) {
    for (const contributor of picker._contributors.values()) {
      contributor.dispose?.();
    }
    picker._contributors = null;
  }
  picker._device = null;
}

function addMorphDelta(morph, out, outOffset, componentOffset, weightsOverride) {
  let x = out[outOffset];
  let y = out[outOffset + 1];
  let z = out[outOffset + 2];
  const activeWeights = weightsOverride ?? morph.weights;
  const targetCount = Math.min(morph.count, morph.targets.length);
  for (let t = 0; t < targetCount; t++) {
    const weight = activeWeights[t] ?? 0;
    if (weight === 0) {
      continue;
    }
    const positions = morph.targets[t].positions;
    x += positions[componentOffset] * weight;
    y += positions[componentOffset + 1] * weight;
    z += positions[componentOffset + 2] * weight;
  }
  out[outOffset] = x;
  out[outOffset + 1] = y;
  out[outOffset + 2] = z;
}
const _boneTransformScratch = [0, 0, 0];
function skinVertexToRef(boneMatrices, joints, weights, joints1, weights1, vertexIndex, x, y, z, wCoord, out, outOffset) {
  let rx = 0;
  let ry = 0;
  let rz = 0;
  const base = vertexIndex * 4;
  for (let i = 0; i < 4; i++) {
    const weight = weights[base + i] ?? 0;
    if (weight !== 0) {
      transformByBoneToRef(boneMatrices, joints[base + i] ?? 0, x, y, z, wCoord, _boneTransformScratch);
      rx += _boneTransformScratch[0] * weight;
      ry += _boneTransformScratch[1] * weight;
      rz += _boneTransformScratch[2] * weight;
    }
  }
  if (joints1 && weights1) {
    for (let i = 0; i < 4; i++) {
      const weight = weights1[base + i] ?? 0;
      if (weight !== 0) {
        transformByBoneToRef(boneMatrices, joints1[base + i] ?? 0, x, y, z, wCoord, _boneTransformScratch);
        rx += _boneTransformScratch[0] * weight;
        ry += _boneTransformScratch[1] * weight;
        rz += _boneTransformScratch[2] * weight;
      }
    }
  }
  out[outOffset] = rx;
  out[outOffset + 1] = ry;
  out[outOffset + 2] = rz;
}
function transformByBoneToRef(boneMatrices, joint, x, y, z, wCoord, out) {
  const o = joint * 16;
  out[0] = boneMatrices[o] * x + boneMatrices[o + 4] * y + boneMatrices[o + 8] * z + boneMatrices[o + 12] * wCoord;
  out[1] = boneMatrices[o + 1] * x + boneMatrices[o + 5] * y + boneMatrices[o + 9] * z + boneMatrices[o + 13] * wCoord;
  out[2] = boneMatrices[o + 2] * x + boneMatrices[o + 6] * y + boneMatrices[o + 10] * z + boneMatrices[o + 14] * wCoord;
}

function captureDeformPose(mesh) {
  const skeleton = mesh.skeleton;
  const morph = mesh.morphTargets;
  if (!skeleton && !morph) {
    return null;
  }
  return {
    boneMatrices: skeleton ? new Float32Array(skeleton.boneMatrices) : null,
    morphWeights: morph ? new Float32Array(morph.weights) : null
  };
}
function captureDeformPoses(candidates) {
  const poses = /* @__PURE__ */ new Map();
  for (const { mesh } of candidates) {
    const pose = captureDeformPose(mesh);
    if (pose) {
      poses.set(mesh, pose);
    }
  }
  return poses;
}
function deformerFor(poses, mesh) {
  const pose = poses?.get(mesh) ?? null;
  return pose ? (m, i0, i1, i2, out) => deformTriangleToRef(m, i0, i1, i2, out, pose) : null;
}
let _deformScratch;
function computeDeformedPositionToRef(mesh, vertexIndex, out) {
  return deformVertexToRef(mesh, vertexIndex, out, null);
}
function deformVertexToRef(mesh, vertexIndex, out, pose) {
  const base = mesh._cpuPositions;
  if (!base) {
    return false;
  }
  const componentOffset = vertexIndex * 3;
  if (componentOffset < 0 || componentOffset + 2 >= base.length) {
    return false;
  }
  const scratch = _deformScratch ??= new Float32Array(3);
  scratch[0] = base[componentOffset];
  scratch[1] = base[componentOffset + 1];
  scratch[2] = base[componentOffset + 2];
  const morph = mesh.morphTargets;
  if (morph) {
    addMorphDelta(morph, scratch, 0, componentOffset, pose?.morphWeights);
  }
  const skeleton = mesh.skeleton;
  if (skeleton) {
    skinVertexToRef(
      pose?.boneMatrices ?? skeleton.boneMatrices,
      skeleton.joints,
      skeleton.weights,
      skeleton.joints1,
      skeleton.weights1,
      vertexIndex,
      scratch[0],
      scratch[1],
      scratch[2],
      1,
      scratch,
      0
    );
  }
  out.x = scratch[0];
  out.y = scratch[1];
  out.z = scratch[2];
  return true;
}
let _triangleVertex;
function writeDeformedVertex(mesh, vertexIndex, out, offset, pose) {
  const vertex = _triangleVertex ??= { x: 0, y: 0, z: 0 };
  if (!deformVertexToRef(mesh, vertexIndex, vertex, pose)) {
    return false;
  }
  out[offset] = vertex.x;
  out[offset + 1] = vertex.y;
  out[offset + 2] = vertex.z;
  return true;
}
function deformTriangleToRef(mesh, i0, i1, i2, out, pose = null) {
  return writeDeformedVertex(mesh, i0, out, 0, pose) && writeDeformedVertex(mesh, i1, out, 3, pose) && writeDeformedVertex(mesh, i2, out, 6, pose);
}

var deformedVertex = /*#__PURE__*/Object.freeze({
    __proto__: null,
    captureDeformPose: captureDeformPose,
    captureDeformPoses: captureDeformPoses,
    computeDeformedPositionToRef: computeDeformedPositionToRef,
    deformTriangleToRef: deformTriangleToRef,
    deformerFor: deformerFor
});

/**
 * Suspends rendering while a canvas is scrolled out of the viewport.
 * @remarks
 * Shared by the full and Lite Viewer canvas factories. The two flavors implement suspension very differently
 * (full disposes a render loop controller synchronously, Lite stops the engine's rAF loop under an async
 * lock), but the observer wiring and the pairing of suspend/resume against intersection changes are identical,
 * so only the `suspendRendering` callback differs.
 * @param canvas The canvas whose visibility should drive rendering.
 * @param suspendRendering Called when the canvas leaves the viewport; the returned disposable is disposed when it comes back.
 * @returns A disposable that stops observing the canvas. Must be disposed along with the Viewer.
 */
function SuspendRenderingWhenOffscreen(canvas, suspendRendering) {
    let offscreenRenderingSuspension = null;
    const intersectionObserver = new IntersectionObserver((entries) => {
        if (entries.length > 0) {
            if (entries[entries.length - 1].isIntersecting) {
                offscreenRenderingSuspension?.dispose();
                offscreenRenderingSuspension = null;
            }
            else {
                // `??=` rather than `=`: repeated non-intersecting records must not acquire a second
                // suspension, which would leak a reference and leave rendering suspended forever.
                offscreenRenderingSuspension ??= suspendRendering();
            }
        }
    });
    intersectionObserver.observe(canvas);
    return { dispose: () => intersectionObserver.disconnect() };
}

// ── Defaults ──
/**
 * The default options for the Lite Viewer.
 */
const DefaultViewerOptions = DefaultViewerBaseOptions;
const DefaultCameraAlpha = -Math.PI / 2;
const DefaultCameraBeta = Math.PI / 2.5;
const DefaultCameraRadius = 3;
const DefaultShadowLightDirection = [0.12, -1, 0.05];
// ── Helpers ──
// Reusable scratch vectors for hotspot resolution to keep per-frame querying zero-allocation.
const _tmpHotSpotVectors = {
    a: { x: 0, y: 0, z: 0 },
    b: { x: 0, y: 0, z: 0 },
    c: { x: 0, y: 0, z: 0 },
    worldPos: { x: 0, y: 0, z: 0 },
    worldNormal: { x: 0, y: 0, z: 0 },
};
function getExtension(url, explicitExt) {
    if (explicitExt) {
        return explicitExt.startsWith(".") ? explicitExt : `.${explicitExt}`;
    }
    const dotIdx = url.lastIndexOf(".");
    return dotIdx >= 0 ? url.substring(dotIdx).toLowerCase() : "";
}
/**
 * Map the Viewer's tone-mapping mode to a Babylon Lite {@link LiteToneMapping} value (or `undefined`
 * when tone mapping should be disabled).
 * @param mode - The Viewer tone-mapping mode.
 * @returns The corresponding Lite tone mapping, or `undefined` to disable tone mapping.
 */
function toneMappingToLiteToneMapping(mode) {
    switch (mode) {
        case "standard":
            return StandardToneMapping;
        case "aces":
            return AcesToneMapping;
        case "neutral":
            return NeutralToneMapping;
        case "none":
            return undefined;
    }
}
// ── Viewer ──
/**
 * A lightweight implementation of the {@link IViewer} interface built on the Babylon Lite API.
 *
 * @remarks
 * Babylon Lite is a WebGPU-only engine that provides a subset of the full Babylon.js feature set.
 * Features that are not available in Lite (SSAO, "high" shadow quality, hot spots, File/ArrayBufferView model sources)
 * will log warnings and fall back gracefully.
 *
 * The `autoSuspendRendering` option is silently ignored: Lite has no scene-mutation tracking, so it cannot
 * detect when a scene is idle. Rendering is still suspended while the canvas is offscreen (see
 * {@link CreateViewerForCanvas}), which is the case that dominates cost on pages with multiple viewers.
 */
class Viewer extends ViewerBase {
    /**
     * Creates a new Viewer instance.
     * @param _engine The Babylon Lite engine context.
     * @param _options Optional viewer configuration.
     */
    constructor(_engine, _options) {
        super();
        this._engine = _engine;
        this._options = _options;
        this._detachControl = null;
        /** True while the engine's requestAnimationFrame loop is running. False while suspended or disposed. */
        this._renderLoopRunning = false;
        /**
         * True once the scene has been registered with the engine (deferred builders run, renderables bucketed).
         * Distinct from {@link _renderLoopRunning}: suspending rendering stops the rAF loop but leaves the scene
         * registered, so code that needs to know "has the scene been built" must consult this instead.
         */
        this._sceneRegistered = false;
        /** Number of live suspension handles returned by {@link _suspendRendering}. Rendering runs only at zero. */
        this._suspendRenderCount = 0;
        /**
         * Serializes every engine start/stop and scene (un)registration, so a suspend/resume triggered by the
         * offscreen observer can never interleave with {@link _beginRendering}'s stop, unregister, register,
         * start sequence (which is itself re-entered from model loads, environment loads, and construction).
         */
        this._renderLoopLock = new AsyncLock();
        /**
         * Resolves the pending `startEngine` await when the render loop is stopped before its first frame.
         * Lite's `startEngine` promise only settles from inside the rAF callback, so stopping the loop first
         * (by suspension or disposal) would otherwise leave the await pending forever.
         */
        this._renderLoopStopped = null;
        // Auto-orbit
        this._autoOrbitIdleTime = 0;
        this._lastPointerTime = 0;
        // Environment
        /** The currently-loaded lighting URL ("auto" resolves to the embedded default). null = no lighting loaded. */
        this._currentLightingUrl = null;
        /** The currently-loaded skybox URL ("auto" resolves to the embedded default). null = no skybox loaded. */
        this._currentSkyboxUrl = null;
        // Post processing
        this._toneMapping = this._options?.postProcessing?.toneMapping ?? DefaultViewerOptions.postProcessing.toneMapping;
        this._contrast = this._options?.postProcessing?.contrast ?? DefaultViewerOptions.postProcessing.contrast;
        this._exposure = this._options?.postProcessing?.exposure ?? DefaultViewerOptions.postProcessing.exposure;
        this._ssaoOption = this._options?.postProcessing?.ssao ?? DefaultViewerOptions.postProcessing.ssao;
        /** Serializes the async PBR-pipeline rebuilds triggered by image-processing updates
         *  (`setSceneImageProcessing`) and environment relights (`rebuildScenePbrPipelines`), so overlapping
         *  changes can't run concurrent rebuilds (which race on the scene's renderable list and leak). */
        this._pbrRebuildLock = new AsyncLock();
        // Shadows
        this._shadowGenerator = null;
        // The directional light and ground disc created by `_setupShadows`, tracked so `_unloadCurrentModel`
        // can tear them down instead of accumulating a new light + ground on every model (re)load.
        this._shadowLight = null;
        this._shadowGround = null;
        // Model
        this._container = null;
        /** GPU picker for double-click focus, created lazily on first double-click. Disposed with the viewer. */
        this._picker = null;
        /** The source that was passed to the most recent {@link loadModel} call, for notifications. */
        this._modelSource = null;
        /**
         * True once the first model load has built its Lite material group (via `registerScene`). Because
         * `_scene` is created once and never recreated, and glTF models all share Lite's singleton PBR group
         * builder, later model loads reuse that already-built group: their meshes are enqueued into the
         * per-frame material-swap queue and the running render loop materializes them, so those loads must
         * NOT re-register the scene (re-registration clears the swap queue and would drop the model). See the
         * (re-)registration decision in {@link _loadModelImpl}.
         */
        this._modelMaterialGroupBuilt = false;
        /** Cached animation-aware model bounds for the current model. Reset on unload. See {@link _computeModelBounds}. */
        this._cachedModelBounds = null;
        // Animation
        this._selectedAnimation = -1;
        this._animationSpeed = this._options?.animationSpeed ?? DefaultViewerOptions.animationSpeed;
        this._wasPlaying = false;
        this._lastProgress = -1;
        // Material variants
        this._selectedMaterialVariant = null;
        // Hot spots
        this._camerasAsHotSpots = false;
        /**
         * Aborts the in-flight camera interpolation (from {@link focusHotSpot}) when a new one starts or
         * the viewer is disposed. Null when no interpolation is running.
         */
        this._cameraInterpolationAbort = null;
        this._defaultTarget = { x: 0, y: 0, z: 0 };
        // ── Private Helpers ──
        this._onPointerActivity = () => {
            this._lastPointerTime = performance.now();
        };
        /**
         * Handles a canvas double-click: GPU-picks the model at the cursor and, on a hit, focuses the camera
         * on the picked point; on a miss (background), reframes the camera. Mirrors the full Viewer's
         * `POINTERDOUBLETAP` handler.
         * @param event The double-click mouse event; its offset coordinates locate the pick on the canvas.
         */
        this._onCanvasDoubleClick = (event) => {
            void this._handleDoubleClick(event.offsetX, event.offsetY);
        };
        this._deviceLostRecovery = enableDeviceLostSceneRecovery(_engine, {
            onRecoveryFailed: (error) => {
                const recoveryError = error instanceof Error ? error : new Error(`Babylon Lite device recovery failed: ${String(error)}`, { cause: error });
                if (this._options?.onFaulted) {
                    this._options.onFaulted(recoveryError);
                }
                else {
                    // Prefer the stack: an unhandled recovery failure is only diagnosable from where it was
                    // thrown, and the message alone gives no indication of which rebuild step failed.
                    Logger.Error(recoveryError.stack ?? recoveryError.message);
                }
            },
        });
        this._shadowQuality = this._options?.shadowConfig?.quality ?? DefaultViewerOptions.shadowConfig.quality;
        this._environmentIntensity = this._options?.environmentConfig?.intensity ?? DefaultViewerOptions.environmentConfig.intensity;
        this._environmentBlur = this._options?.environmentConfig?.blur ?? DefaultViewerOptions.environmentConfig.blur;
        this._environmentRotation = this._options?.environmentConfig?.rotation ?? DefaultViewerOptions.environmentConfig.rotation;
        this._autoOrbitEnabled = this._options?.cameraAutoOrbit?.enabled ?? DefaultViewerOptions.cameraAutoOrbit.enabled;
        this._autoOrbitSpeed = this._options?.cameraAutoOrbit?.speed ?? DefaultViewerOptions.cameraAutoOrbit.speed;
        this._autoOrbitDelay = this._options?.cameraAutoOrbit?.delay ?? DefaultViewerOptions.cameraAutoOrbit.delay;
        if (this._options?.hotSpots) {
            this.hotSpots = this._options.hotSpots;
        }
        // Create scene internally (matching how full Viewer owns its scene)
        this._scene = createSceneContext(_engine);
        // Camera — NaN means "auto" (will be recomputed when model loads)
        const orbitAlpha = _options?.cameraOrbit?.[0];
        const orbitBeta = _options?.cameraOrbit?.[1];
        const orbitRadius = _options?.cameraOrbit?.[2];
        const alpha = orbitAlpha != null && !isNaN(orbitAlpha) ? orbitAlpha : DefaultCameraAlpha;
        const beta = orbitBeta != null && !isNaN(orbitBeta) ? orbitBeta : DefaultCameraBeta;
        const radius = orbitRadius != null && !isNaN(orbitRadius) ? orbitRadius : DefaultCameraRadius;
        this._defaultAlpha = alpha;
        this._defaultBeta = beta;
        this._defaultRadius = radius;
        if (_options?.cameraTarget) {
            const tx = _options.cameraTarget[0];
            const ty = _options.cameraTarget[1];
            const tz = _options.cameraTarget[2];
            this._defaultTarget = {
                x: tx != null && !isNaN(tx) ? tx : 0,
                y: ty != null && !isNaN(ty) ? ty : 0,
                z: tz != null && !isNaN(tz) ? tz : 0,
            };
        }
        this._camera = createArcRotateCamera(alpha, beta, radius, this._defaultTarget);
        this._scene.camera = this._camera;
        this._detachControl = attachControl(this._camera, this._engine.canvas, this._scene);
        // Track pointer activity for auto-orbit idle detection. Use the instance method (not a local
        // closure) so `dispose()` can remove the exact same listener references. Seed the last-activity
        // time to now so auto-orbit waits the configured delay before starting rather than treating the
        // freshly-constructed viewer (with `_lastPointerTime` at 0) as already idle.
        this._lastPointerTime = performance.now();
        this._engine.canvas.addEventListener("pointerdown", this._onPointerActivity);
        this._engine.canvas.addEventListener("pointermove", this._onPointerActivity);
        this._engine.canvas.addEventListener("wheel", this._onPointerActivity);
        // Double-click to focus: on the model, focus the picked point; on the background, reframe.
        // Mirrors the full Viewer's POINTERDOUBLETAP handler (viewer.ts).
        this._engine.canvas.addEventListener("dblclick", this._onCanvasDoubleClick);
        // Clear color — initialize base field from options, then push to engine.
        this._clearColor.r = _options?.clearColor?.[0] ?? 0;
        this._clearColor.g = _options?.clearColor?.[1] ?? 0;
        this._clearColor.b = _options?.clearColor?.[2] ?? 0;
        this._clearColor.a = _options?.clearColor?.[3] ?? 0;
        this._applyClearColor();
        // Auto-orbit, environment config, animation speed, and hot spots are initialized
        // inline at the field declarations.
        // Post processing — apply the initial Viewer state (loaded from options at field init
        // time above) to `scene.imageProcessing`, which still holds Lite's defaults until we
        // push our values. We bypass the public setter because the fields are already at the
        // target values, so the setter would dedup and skip the scene apply.
        this._applyImageProcessingToScene();
        // Shadow config — route through updateShadows so unsupported "high" is normalized to "normal"
        if (_options?.shadowConfig?.quality) {
            observePromise(this.updateShadows({ quality: _options.shadowConfig.quality }));
        }
        // Per-frame callback
        onBeforeRender(this._scene, (deltaMs) => {
            if (this._isDisposed) {
                return;
            }
            this._updateAutoOrbit(deltaMs);
            this._pollAnimationState();
            this.onAfterRenderObservable.notifyObservers();
        });
        // Keep the scene unbuilt until an initial shadow-casting model is added. Registering the
        // empty scene first leaves later material groups without their boot-time builders, so the
        // shadow task cannot materialize its caster renderables.
        if (!_options?.source || this._shadowQuality === "none") {
            observePromise(this._beginRendering());
        }
        // Initial loads — each will restart the render loop to pick up new renderables
        const initialLightingUrl = _options?.environmentLighting ?? DefaultViewerOptions.environmentLighting;
        const initialSkyboxUrl = _options?.environmentSkybox ?? DefaultViewerOptions.environmentSkybox;
        // Initial environment + model loads. The model is loaded AFTER the environment so its PBR pipeline is
        // built with the environment (IBL) present in one pass — Lite bakes the environment into the PBR
        // shaders at build time. This is an optimization, not a correctness requirement: an environment added
        // after a model is already built triggers a pixel-identical pipeline rebuild (see
        // `_rebuildModelPbrForEnvironment`). Ordering the initial loads this way just avoids that extra rebuild
        // (and the brief unlit frame before it) on the common path.
        observePromise((async () => {
            const envLoads = [];
            // If lighting and skybox URLs are the same, do one combined load. Otherwise do them separately.
            if (initialLightingUrl === initialSkyboxUrl) {
                if (initialLightingUrl !== "none") {
                    envLoads.push(this.loadEnvironment(initialLightingUrl));
                }
            }
            else {
                if (initialLightingUrl !== "none") {
                    envLoads.push(this.loadEnvironment(initialLightingUrl, { lighting: true, skybox: false }));
                }
                if (initialSkyboxUrl !== "none") {
                    envLoads.push(this.loadEnvironment(initialSkyboxUrl, { lighting: false, skybox: true }));
                }
            }
            // Don't block (or fail) the model load if an environment load rejects (e.g. a bad URL) — the
            // model should still appear. `allSettled` waits for the env textures to be in place first.
            await Promise.allSettled(envLoads);
            if (_options?.source) {
                await this.loadModel(_options.source);
            }
        })());
    }
    // ── Clear Color ──
    /** @internal */
    _applyClearColor() {
        const cc = this._scene.clearColor;
        cc.r = this._clearColor.r;
        cc.g = this._clearColor.g;
        cc.b = this._clearColor.b;
        cc.a = this._clearColor.a;
    }
    // ── Camera ──
    /** @internal Lite stores auto-orbit state on the base class fields and consults them in its idle loop. No engine push needed. */
    _applyCameraAutoOrbitEnabled() { }
    /** @internal Lite stores auto-orbit state on the base class fields. */
    _applyCameraAutoOrbitSpeed() { }
    /** @internal Lite stores auto-orbit state on the base class fields. */
    _applyCameraAutoOrbitDelay() { }
    resetCamera(reframe) {
        // The public reset always animates the transition, matching the full Viewer's `resetCamera`.
        this._resetCameraCore(reframe, true);
    }
    /**
     * Shared implementation of camera reset. Resolves the reframe default (matching the full Viewer:
     * reframe to model bounds when the selected animation differs from the default, otherwise return to
     * the explicit default pose) and moves the camera there, optionally animating the transition.
     * @param reframe Whether to reframe to model bounds; when undefined, decided from animation state.
     * @param interpolate Whether to animate the camera to the reset pose.
     */
    _resetCameraCore(reframe, interpolate) {
        if (reframe === undefined) {
            // Match the full Viewer: when the selected animation differs from the default, the explicit
            // default pose likely won't frame the model (it may even be out of view), so reframe to the
            // model bounds instead. See viewer.ts `resetCamera`.
            reframe = this._selectedAnimation !== (this._options?.selectedAnimation ?? 0);
        }
        if (reframe) {
            this._frameCameraToModel(interpolate);
            return;
        }
        // Non-reframe reset restores the "default" framing: the model bounds pose with any explicit
        // cameraOrbit/cameraTarget option overrides applied. Mirrors the full Viewer's
        // `_resetCamera` -> `_reframeCameraFromBounds`, so with no such options it returns to exactly the
        // load-time framing (fixing "reset moves the camera"). Before any model is loaded there are no
        // bounds, so fall back to the fixed default pose the camera was created with.
        if (this._frameCameraToModel(interpolate, true)) {
            return;
        }
        this._moveCameraTo({
            alpha: this._defaultAlpha,
            beta: this._defaultBeta,
            radius: this._defaultRadius,
            target: this._defaultTarget,
        }, interpolate);
    }
    updateCamera(pose) {
        // Animate to the requested pose, matching the full Viewer's `updateCamera`
        // (which routes through `interpolateTo`). Omitted fields keep the current value.
        const target = pose.targetX !== undefined || pose.targetY !== undefined || pose.targetZ !== undefined
            ? {
                x: pose.targetX ?? this._camera.target.x,
                y: pose.targetY ?? this._camera.target.y,
                z: pose.targetZ ?? this._camera.target.z,
            }
            : undefined;
        this._moveCameraTo({ alpha: pose.alpha, beta: pose.beta, radius: pose.radius, target }, true);
    }
    /**
     * Moves the camera to a goal pose, either by animating (via {@link interpolateArcRotateCamera}) or by
     * snapping directly. Either way, any in-flight interpolation is first canceled so it can't fight the
     * new pose. Omitted or NaN goal fields keep the camera's current value for that channel.
     * @param goal The destination camera pose.
     * @param interpolate Whether to animate the transition.
     */
    _moveCameraTo(goal, interpolate) {
        if (interpolate) {
            this._interpolateCameraTo(goal);
            return;
        }
        // Snap: cancel any running interpolation, then write the pose directly.
        this._cameraInterpolationAbort?.abort(new AbortError("Camera interpolation superseded."));
        this._cameraInterpolationAbort = null;
        if (goal.alpha !== undefined && !isNaN(goal.alpha)) {
            this._camera.alpha = goal.alpha;
        }
        if (goal.beta !== undefined && !isNaN(goal.beta)) {
            this._camera.beta = goal.beta;
        }
        if (goal.radius !== undefined && !isNaN(goal.radius)) {
            this._camera.radius = goal.radius;
        }
        if (goal.target) {
            // Mutate the existing target ObservableVec3 in place — replacing it with a plain object
            // would silently lose Lite's ObservableVec3 dirty-tracking, which is what notifies the
            // camera that its world matrix needs to recompute when target changes.
            this._camera.target.x = goal.target.x;
            this._camera.target.y = goal.target.y;
            this._camera.target.z = goal.target.z;
        }
    }
    /**
     * Frames the camera to the loaded model's bounds, matching the full Viewer's framing math. Near/far
     * planes and zoom limits are applied immediately; the orbit pose is moved (snapped or animated) via
     * {@link _moveCameraTo}.
     *
     * When `applyDefaultPoseOverrides` is true, the bounds-derived orbit pose is overridden per-channel by
     * any explicit `cameraOrbit`/`cameraTarget` options — mirroring the full Viewer's
     * `_resetCamera` -> `_reframeCameraFromBounds`. With no such options this equals the pure bounds
     * framing used on model load, so a reset returns to exactly the load-time framing.
     * @param interpolate Whether to animate the camera to the framing pose.
     * @param applyDefaultPoseOverrides Whether to override the bounds pose with explicit camera options.
     * @returns True if the model had bounds and the camera was framed; false if there is no model to frame.
     */
    _frameCameraToModel(interpolate, applyDefaultPoseOverrides = false) {
        const bounds = this._computeModelBounds();
        if (!bounds) {
            return false;
        }
        // Mirror the full Viewer's framing math (viewer.ts `_getCameraConfig` / `_reframeCameraFromBounds`)
        // so Lite frames identically. The framing radius is the bounding-box diagonal * 1.1. `bounds.radius`
        // is the bounding-sphere radius (half the diagonal), so the diagonal is `bounds.radius * 2`.
        let radius = bounds.radius * 2 * 1.1;
        if (!isFinite(radius) || radius <= 0) {
            radius = 1;
        }
        // Near/far planes and zoom limits scaled to the framing radius, matching the full Viewer
        // (near = radius * 0.001, far = radius * 1000, radius limits = radius * 0.001 .. radius * 5).
        // These are always bounds-derived (never overridden) and applied immediately (not interpolated),
        // matching the full Viewer's `_reframeCameraFromBounds`.
        this._camera.nearPlane = radius * 0.001;
        this._camera.farPlane = radius * 1000;
        this._camera.lowerRadiusLimit = radius * 0.001;
        this._camera.upperRadiusLimit = radius * 5;
        // Default to the bounds framing pose, matching the full Viewer (its reframe always applies a fixed
        // alpha/beta, independent of any cameraOrbit option). This keeps a consistent viewpoint across load
        // and animation switches.
        let alpha = FramingCameraAlpha;
        let beta = FramingCameraBeta;
        let radiusGoal = radius;
        const target = { x: bounds.center[0], y: bounds.center[1], z: bounds.center[2] };
        // For a "default pose" reset, override each channel with the explicit camera option when present,
        // falling back to the bounds framing otherwise — matching the full Viewer's `_reframeCameraFromBounds`.
        if (applyDefaultPoseOverrides) {
            const orbit = this._options?.cameraOrbit;
            if (orbit) {
                if (orbit[0] != null && !isNaN(orbit[0])) {
                    alpha = orbit[0];
                }
                if (orbit[1] != null && !isNaN(orbit[1])) {
                    beta = orbit[1];
                }
                if (orbit[2] != null && !isNaN(orbit[2])) {
                    radiusGoal = orbit[2];
                }
            }
            const cameraTarget = this._options?.cameraTarget;
            if (cameraTarget) {
                if (cameraTarget[0] != null && !isNaN(cameraTarget[0])) {
                    target.x = cameraTarget[0];
                }
                if (cameraTarget[1] != null && !isNaN(cameraTarget[1])) {
                    target.y = cameraTarget[1];
                }
                if (cameraTarget[2] != null && !isNaN(cameraTarget[2])) {
                    target.z = cameraTarget[2];
                }
            }
        }
        this._moveCameraTo({ alpha, beta, radius: radiusGoal, target }, interpolate);
        return true;
    }
    /**
     * Compute the aggregate world-space bounding box of the loaded model, accounting for
     * animation.
     *
     * Delegates to Lite's {@link computeMaxExtents}, which steps through the currently-selected
     * animation group and unions every sampled pose. This captures the full swept volume of
     * node (TRS), skeletal, and morph-target animation — so skinned models like the
     * acrobaticPlane glTF frame correctly instead of reporting their (much smaller) bind-pose
     * AABB. Meshes are gathered with {@link getContainerMeshes} so Viewer-added meshes (e.g. the
     * shadow-receiver disc) are excluded.
     *
     * The result is cached for the lifetime of the loaded model (reset in
     * `_unloadCurrentModel`) so the two consumers — `_frameCameraToModel` (camera target +
     * radius + near/far planes) and `_setupShadows` (light positioning, ground placement,
     * frustum sizing) — share a single animation sweep rather than stepping it twice.
     *
     * @returns aggregate `min`, `max`, `center`, and bounding-sphere `radius`
     *   (= half the diagonal), or `null` if the model has no bounds info.
     */
    _computeModelBounds() {
        if (!this._container) {
            return null;
        }
        if (this._cachedModelBounds) {
            return this._cachedModelBounds;
        }
        const meshes = getContainerMeshes(this._container);
        if (meshes.length === 0) {
            return null;
        }
        // Sample the selected animation so the bounds cover the model's full motion.
        const animationGroup = this._getActiveAnimationGroup();
        const extents = computeMaxExtents(meshes, animationGroup, this._engine);
        let minX = Infinity, minY = Infinity, minZ = Infinity;
        let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
        for (const extent of extents) {
            // Skip meshes that contributed no geometry (inverted extent).
            if (extent.minimum[0] > extent.maximum[0]) {
                continue;
            }
            if (extent.minimum[0] < minX) {
                minX = extent.minimum[0];
            }
            if (extent.minimum[1] < minY) {
                minY = extent.minimum[1];
            }
            if (extent.minimum[2] < minZ) {
                minZ = extent.minimum[2];
            }
            if (extent.maximum[0] > maxX) {
                maxX = extent.maximum[0];
            }
            if (extent.maximum[1] > maxY) {
                maxY = extent.maximum[1];
            }
            if (extent.maximum[2] > maxZ) {
                maxZ = extent.maximum[2];
            }
        }
        if (minX > maxX) {
            return null;
        }
        const dx = maxX - minX;
        const dy = maxY - minY;
        const dz = maxZ - minZ;
        const radius = Math.sqrt(dx * dx + dy * dy + dz * dz) / 2;
        this._cachedModelBounds = {
            min: [minX, minY, minZ],
            max: [maxX, maxY, maxZ],
            center: [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2],
            radius,
        };
        return this._cachedModelBounds;
    }
    // ── Environment ──
    /** @internal Lite has no engine state for intensity. */
    _applyEnvironmentIntensity() { }
    /** @internal */
    _applyEnvironmentBlur() {
        if (this._currentSkyboxUrl !== null) {
            setEnvironmentBlur(this._scene, this._environmentBlur);
        }
    }
    /** @internal */
    _applyEnvironmentRotation() {
        setEnvironmentRotation(this._scene, this._environmentRotation);
        this._rotateShadowLightWithEnvironment();
    }
    /** @internal */
    async _loadEnvironmentImpl(url, options, abortSignal, compositeAbortSignal) {
        const updateLighting = options.lighting;
        const updateSkybox = options.skybox;
        // Resolve the target URLs for lighting and skybox after applying the update.
        const targetLightingUrl = updateLighting ? (url ?? null) : this._currentLightingUrl;
        const targetSkyboxUrl = updateSkybox ? (url ?? null) : this._currentSkyboxUrl;
        // No-op: nothing actually changes.
        if (targetLightingUrl === this._currentLightingUrl && targetSkyboxUrl === this._currentSkyboxUrl) {
            return;
        }
        try {
            // Babylon Lite's current public API has no way to remove or replace existing env state:
            // - `liteLoadEnvironment` and `loadHdrEnvironment` always set `scene._envTextures` and push
            //   skybox/ground builders, but they don't remove anything that's already there.
            // - There's no PBR-scene-compatible standalone skybox loader.
            // We allow ADDING new state where there was none, but throw on any REPLACEMENT.
            if (this._currentLightingUrl !== null && targetLightingUrl !== this._currentLightingUrl) {
                const action = targetLightingUrl === null ? "remove" : "replace";
                throw new Error(`Babylon Lite cannot ${action} the loaded environment lighting ("${this._currentLightingUrl}"${targetLightingUrl === null ? "" : ` → "${targetLightingUrl}"`}). ` +
                    `Recreate the Viewer to change the environment.`);
            }
            if (this._currentSkyboxUrl !== null && targetSkyboxUrl !== this._currentSkyboxUrl) {
                const action = targetSkyboxUrl === null ? "remove" : "replace";
                throw new Error(`Babylon Lite cannot ${action} the loaded environment skybox ("${this._currentSkyboxUrl}"${targetSkyboxUrl === null ? "" : ` → "${targetSkyboxUrl}"`}). ` +
                    `Recreate the Viewer to change the environment.`);
            }
            // Skybox-only addition with no lighting yet: Lite couples the skybox to the IBL cubemap — a
            // single environment texture drives BOTH the background and the reflections/lighting. (This
            // matches the full viewer's `environment-skybox`, where the model also reflects the skybox.)
            // So a skybox requested with no lighting loaded is satisfied by loading the requested skybox
            // URL AS the environment: it provides the skybox and, unavoidably in Lite, the IBL too.
            const skyboxOnlyBecomesEnv = !updateLighting && updateSkybox && this._currentLightingUrl === null;
            const effectiveLightingUrl = skyboxOnlyBecomesEnv ? (targetSkyboxUrl ?? "auto") : (targetLightingUrl ?? "auto");
            const resolvedLightingUrl = effectiveLightingUrl === "auto" ? (await import('./defaultEnvironment-5jBs1zfd.esm.js')).default : effectiveLightingUrl;
            const ext = getExtension(resolvedLightingUrl, options.extension);
            setEnvironmentRotation(this._scene, this._environmentRotation);
            if (targetSkyboxUrl !== null) {
                setEnvironmentBlur(this._scene, this._environmentBlur);
            }
            // Lite's `loadHdrEnvironment` cannot suppress its skybox build. So:
            // - `lighting: true, skybox: false` with .hdr → would build an unwanted skybox. Throw.
            // - `lighting: true, skybox: true` (or skybox-only with same URL) → fine.
            if (ext === ".hdr" && !updateSkybox) {
                throw new Error("Babylon Lite cannot load only the lighting from a .hdr URL — `loadHdrEnvironment` always builds a skybox. " +
                    "Update lighting and skybox together, or use a .env URL.");
            }
            if (ext === ".hdr") {
                await loadHdrEnvironment(this._scene, resolvedLightingUrl, {
                    useCubemapSkybox: true,
                    skipGround: true,
                    skyboxSize: 20,
                });
                this._currentLightingUrl = effectiveLightingUrl;
                this._currentSkyboxUrl = effectiveLightingUrl;
            }
            else {
                const resolvedSkyboxUrl = targetSkyboxUrl === "auto" ? (await import('./defaultEnvironment-5jBs1zfd.esm.js')).default : (targetSkyboxUrl ?? undefined);
                // Note: when skybox-only is requested but lighting already exists, we still call
                // liteLoadEnvironment with the existing lighting URL — this re-fetches and re-uploads
                // the cubemap (wasteful) but correctly builds the requested skybox. Per the contract
                // ("honoring by re-loading is OK"), this is acceptable.
                await loadEnvironment(this._scene, resolvedLightingUrl, {
                    brdfUrl: (await import('./defaultBRDF-VHOoJ9Eb.esm.js')).default,
                    skyboxUrl: resolvedSkyboxUrl,
                    skipSkybox: !updateSkybox,
                    skipGround: true,
                    skyboxSize: 20,
                });
                this._currentLightingUrl = effectiveLightingUrl;
                if (updateSkybox) {
                    this._currentSkyboxUrl = targetSkyboxUrl;
                }
            }
            // Lite's env loader unconditionally overwrites `scene.imageProcessing` (toneMappingEnabled,
            // exposure, contrast) with its own defaults — clobbering whatever the Viewer set during
            // construction or via subsequent `postProcessing` setter calls. Re-push our committed
            // values so the user-facing post-processing state survives env loads.
            this._applyImageProcessingToScene();
            // Re-register the scene so that the env loader's deferred builders are processed
            // and the new skybox/ground renderables land in the draw buckets. Lite only fills
            // `_opaqueRenderables` etc. at registerScene() time. Gated on `_sceneRegistered` (not the
            // rAF-loop state) so an environment loaded while rendering is suspended still re-registers;
            // otherwise the skybox would never appear, even after rendering resumes.
            if (this._sceneRegistered) {
                await this._beginRendering();
            }
            // Relight an already-built model. Lite bakes the environment (IBL) textures into the PBR
            // shaders when the material group is BUILT; `loadEnvironment` only updates `scene._envTextures`
            // + the skybox and does NOT rebuild existing PBR groups (and the re-registration above only runs
            // deferred builders, not already-built ones). So when the environment finishes loading AFTER the
            // model's pipeline was built — the common concurrent-load race where the model wins, or an
            // environment explicitly added to an already-displayed model — the model keeps its previous
            // (often absent) IBL and renders unlit/black while the skybox looks correct. Force a PBR rebuild
            // so the model picks up the new lighting. No-op when no model is loaded.
            if (this._sceneRegistered && this._container) {
                await this._rebuildModelPbrForEnvironment();
            }
            throwIfAborted(abortSignal, compositeAbortSignal);
            this.onEnvironmentChanged.notifyObservers();
        }
        catch (e) {
            if (!(e instanceof AbortError)) {
                this.onEnvironmentError.notifyObservers(e);
            }
            throw e;
        }
    }
    // ── Post Processing ──
    get postProcessing() {
        return {
            toneMapping: this._toneMapping,
            contrast: this._contrast,
            exposure: this._exposure,
            ssao: this._ssaoOption,
        };
    }
    set postProcessing(value) {
        let changed = false;
        if (value.toneMapping !== undefined && value.toneMapping !== this._toneMapping) {
            this._toneMapping = value.toneMapping;
            changed = true;
        }
        if (value.exposure !== undefined && value.exposure !== this._exposure) {
            this._exposure = value.exposure;
            changed = true;
        }
        if (value.contrast !== undefined && value.contrast !== this._contrast) {
            this._contrast = value.contrast;
            changed = true;
        }
        if (value.ssao !== undefined && value.ssao !== this._ssaoOption) {
            this._ssaoOption = value.ssao;
            if (value.ssao !== "disabled") {
                Logger.Warn("Viewer: SSAO is not supported by Babylon Lite.");
            }
            changed = true;
        }
        if (changed) {
            // Apply the change to the running scene. Exposure/contrast are read live from the scene UBO
            // each frame, but tone mapping (enabled state + algorithm) is baked into the PBR shaders at
            // registration time, so a change there requires a pipeline rebuild. `setSceneImageProcessing`
            // does both: it updates the config and rebuilds the affected PBR pipelines only when the tone
            // mapping actually changed (and no-ops the rebuild before the scene is first registered).
            this._applyImageProcessingDynamic();
            this.onPostProcessingChanged.notifyObservers();
        }
    }
    /**
     * Apply the current committed post-processing state to the running scene via
     * `setSceneImageProcessing`, serialized through {@link _pbrRebuildLock} so overlapping calls
     * never run concurrent PBR-pipeline rebuilds. Each queued apply re-reads the latest committed state
     * when it runs, so a burst of rapid changes collapses to the final state (intermediate updates that
     * no longer differ are no-ops).
     */
    _applyImageProcessingDynamic() {
        observePromise(this._pbrRebuildLock.lockAsync(async () => await setSceneImageProcessing(this._scene, this._liteImageProcessingUpdate())));
    }
    /**
     * Force a rebuild of the loaded model's PBR pipelines so they pick up the scene's current environment
     * (IBL) textures. Needed because Lite bakes the environment into the PBR shaders at build time and
     * `loadEnvironment` doesn't rebuild existing PBR groups, so a model built before its environment loads
     * renders unlit/black. Used when an environment is added or changed AFTER a model is already displayed.
     *
     * `rebuildScenePbrPipelines` re-runs the PBR group builder against the scene's current `_envTextures`,
     * producing pipelines pixel-identical to a model built with the environment present from the start.
     *
     * Serialized through {@link _pbrRebuildLock} with the other image-processing updates (which also
     * rebuild PBR pipelines) so the two can't race.
     * @returns A promise that resolves once the model's PBR pipelines have been rebuilt.
     */
    async _rebuildModelPbrForEnvironment() {
        await this._pbrRebuildLock.lockAsync(async () => {
            await rebuildScenePbrPipelines(this._scene);
        });
    }
    /**
     * Build the Babylon Lite {@link ImageProcessingUpdate} that mirrors the Viewer's committed
     * post-processing state (`_toneMapping`, `_exposure`, `_contrast`). SSAO has no
     * `scene.imageProcessing` slot in Lite — it's tracked in `_ssaoOption` but doesn't render anything
     * yet (Lite has no SSAO support).
     * @returns The Lite image-processing update mirroring the Viewer's committed state.
     */
    _liteImageProcessingUpdate() {
        const toneMapping = toneMappingToLiteToneMapping(this._toneMapping);
        return {
            toneMappingEnabled: toneMapping !== undefined,
            toneMapping,
            exposure: this._exposure,
            contrast: this._contrast,
        };
    }
    /**
     * Push the Viewer's committed post-processing state directly into `scene.imageProcessing`. Used on
     * paths where the scene is not yet registered or is about to be (re-)registered — construction, and
     * after env loads (Lite's env loader overwrites `scene.imageProcessing` with its own defaults, so we
     * re-push our values before re-registration bakes them into the shaders). The dynamic path (the
     * `postProcessing` setter) instead uses `setSceneImageProcessing` for a targeted pipeline rebuild.
     */
    _applyImageProcessingToScene() {
        Object.assign(this._scene.imageProcessing, this._liteImageProcessingUpdate());
    }
    // ── Shadows ──
    /**
     * Updates the shadow configuration.
     * @param value The new shadow configuration.
     * @param abortSignal Optional signal that can be used to abort the update externally.
     * @returns A promise that resolves when the shadow update completes.
     */
    async updateShadows(value, abortSignal) {
        if (value.quality === "high") {
            throw new Error("Babylon Lite does not support 'high' shadow quality. Use 'normal' or 'none'.");
        }
        return await super.updateShadows(value, abortSignal);
    }
    /**
     * @internal
     * Lite cannot cleanly add or remove shadow infrastructure (light, ground disc, shadow generator)
     * after the scene has been registered. Adding meshes post-register requires re-running deferred
     * GPU builders, which corrupts the existing model's pipeline state. Reloading the model breaks
     * for similar reasons (the previous scene state isn't fully torn down).
     *
     * For now, shadow quality is effectively fixed at the value provided in the initial constructor
     * options: `_setupShadows` runs once during `_loadModelImpl` (before `addToScene` and the first
     * `registerScene`), so initial setup works correctly. Subsequent calls to `updateShadows` change
     * the committed `_shadowQuality` field but do not re-run shadow setup. Callers that need to
     * change shadow quality should recreate the viewer.
     */
    async _updateShadowsImpl(
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    quality, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    abortSignal, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    internalAbortSignal) {
        Logger.Warn("Babylon Lite cannot toggle shadow quality after the model is loaded. " +
            "Set `shadowConfig.quality` in the viewer constructor options instead, or recreate the viewer to change shadow rendering.");
    }
    async _setupShadows(abortSignal, internalAbortSignal) {
        if (!this._container || this._shadowQuality === "none") {
            return;
        }
        // Caster meshes: snapshot the scene's flat mesh list BEFORE we add the ground disc, so the
        // disc isn't itself treated as a shadow caster. (`container.entities` for glTF only
        // contains the root TransformNode, so filtering entities directly always returns empty.)
        const casterMeshes = [...this._scene.meshes];
        if (casterMeshes.length === 0) {
            return;
        }
        // Keep deformation-aware shadow bounds behind one lazy entry so static casters don't pay
        // for the morph-target or skeletal bounds providers.
        const hasMorphTargets = casterMeshes.some((mesh) => !!mesh.morphTargets);
        const hasSkeletons = casterMeshes.some((mesh) => !!mesh.skeleton);
        const deformableShadows = hasMorphTargets || hasSkeletons ? await import('./viewerShadows-IwWOcws0.esm.js') : undefined;
        throwIfAborted(abortSignal, internalAbortSignal);
        // Bounds for ground placement, ground sizing, and shadow light positioning. Falls back
        // to a unit cube if no mesh has bounds info — shadows still set up sensibly.
        const bounds = this._computeModelBounds() ?? {
            min: [-1, -1, -1],
            center: [0, 0, 0],
            radius: 1,
        };
        const minY = bounds.min[1];
        const [cx, cy, cz] = bounds.center;
        const radius = bounds.radius;
        // Directional light. Position it well outside the model along the negated light direction
        // so Lite's directional shadow camera (placed at light.position looking in light.direction)
        // sees the model in front of it. With the default (0, 0, 0) position, the model — which
        // typically sits with its base at or above origin — would be behind the shadow camera and
        // get clipped, leaving the shadow map empty. (Mirrors the full Viewer's positionFactor
        // logic: light.position = -direction * radius * 3.)
        //
        // The direction is kept nearly vertical (a small horizontal component relative to the -Y
        // drop) so the cast shadow sits centered directly beneath the model rather than being
        // pushed off to one side as an oblique streak. The full Viewer derives its light direction
        // from the IBL's dominant direction, which for the typical diffuse studio environment is
        // close to overhead (e.g. ~(-0.12, -0.99, -0.01)); Lite has no IBL dominant-direction
        // analysis, so this near-overhead fixed direction approximates that soft, grounded look.
        const lightDir = [...DefaultShadowLightDirection];
        const dirLen = Math.sqrt(lightDir[0] ** 2 + lightDir[1] ** 2 + lightDir[2] ** 2);
        const positionFactor = radius * 3;
        const light = createDirectionalLight(lightDir, 1);
        light.position.set(cx - (lightDir[0] / dirLen) * positionFactor, cy - (lightDir[1] / dirLen) * positionFactor, cz - (lightDir[2] / dirLen) * positionFactor);
        addToScene(this._scene, light);
        this._shadowLight = light;
        this._rotateShadowLightWithEnvironment();
        // Shadow ground disc. `setShadowOnly` enables Lite's shadow-only shader path, which mirrors
        // BJS's `BackgroundMaterial.shadowOnly`: the surface is invisible everywhere except where
        // shadow falls on it, where it appears in the configured color (black here). The
        // ground needs `receiveShadows = true` so Lite compiles it on the multi-light path where the
        // per-light `shadowFactors` the shadow-only path reads are actually written. `createPbrMaterial`
        // installs its own 1×1 fallback base/ORM textures, so none need to be supplied here.
        //
        // Disc radius is intentionally enormous (~1000× the model radius) so the disc is
        // effectively an infinite ground plane. Animated models can move/translate well outside
        // a tightly-fit ground without the cast shadow clipping at the disc edge. The disc itself
        // is alpha-zero outside the shadow region, so the visible scene is unchanged.
        const groundRadius = radius * 1000;
        const ground = createDisc(this._engine, { radius: groundRadius, tessellation: 64 });
        ground.rotation.x = Math.PI / 2;
        ground.position.y = minY;
        ground.receiveShadows = true;
        // Shadow-only is a tree-shakeable, opt-in PBR feature: importing and calling its enabler is
        // what pulls in and registers the shader fragment. Apps that never import the enabler pay
        // zero bundle cost. The feature also forces the alpha-blend path, so no separate
        // `alphaBlend: true` is needed for the disc to composite over the scene.
        const groundMaterial = createPbrMaterial({});
        setShadowOnly(groundMaterial, {
            color: [0, 0, 0],
            // Keep the shadow light and translucent so it reads as a soft, subtle grounding shadow
            // similar to the full Viewer (whose directional shadow darkness is only ~0.2–0.8),
            // rather than a heavy pitch-black blob. Paired with the large blurKernel below, this
            // gives a gentle penumbra instead of a hard silhouette.
            opacity: 0.3,
            // Use the natural ESM falloff (falloff = 1) so the penumbra fades smoothly. Values > 1
            // collapse the gradient into a hard aliased edge.
            falloff: 1,
        });
        ground.material = groundMaterial;
        addToScene(this._scene, ground);
        this._shadowGround = ground;
        // Shadow generator. Lite drives shadow rendering from `light.shadowGenerator`, not from a
        // separate scene-level list — so attaching to the light is the load-bearing step here.
        // Casters are registered separately via `setShadowTaskCasterMeshes`: pass only the model
        // meshes, since including the ground disc would cause the disc to occlude itself in the
        // shadow map.
        //
        // ESM (exponential shadow map) FP16 precision and scale invariance both pivot on the
        // caster's NDC-depth fraction. The shader stores `exp(-depthScale * NDC_depth)` per
        // texel; with depthScale=50 (Lite default), values for NDC > ~0.2 underflow to zero in
        // FP16, collapsing the soft penumbra into a binary silhouette → hard aliased shadow
        // edge.
        //
        // Counter-intuitively, the fix is to make orthoMaxZ much LARGER than the model + light
        // distance, not smaller: a wide depth range puts the entire caster at small NDC depth
        // where ESM exp values stay near 1 and FP16 has plenty of precision. The blur kernel
        // (fixed in texels) then produces the visible penumbra by smearing those near-1 values
        // toward zero across the silhouette boundary.
        //
        // Setting orthoMaxZ = positionFactor * 100 makes the caster's NDC-depth fraction (~0.7%)
        // invariant to model scale, so the shadow looks identical for a 16 cm airplane and a
        // 5 m UFO.
        const orthoMinZ = 0;
        const orthoMaxZ = positionFactor * 100;
        this._shadowGenerator = createEsmDirectionalShadowGenerator(this._engine, light, {
            orthoMinZ,
            orthoMaxZ,
            // Wide kernel blur on the ESM shadow map. The default (1) leaves a crisp, hard-edged
            // shadow; a large kernel spreads the penumbra into a soft gradient that matches the full
            // Viewer's soft shadow look (which ramps blurKernel up to 64 for diffuse environments).
            blurKernel: 48,
        });
        if (hasMorphTargets) {
            deformableShadows?.enableMorphTargetShadows(this._shadowGenerator);
        }
        if (hasSkeletons) {
            deformableShadows?.enableSkeletonShadows(this._shadowGenerator);
        }
        setShadowTaskCasterMeshes(this._shadowGenerator, casterMeshes);
        light.shadowGenerator = this._shadowGenerator ?? undefined;
    }
    _rotateShadowLightWithEnvironment() {
        const light = this._shadowLight;
        const bounds = this._computeModelBounds();
        if (!light || !bounds) {
            return;
        }
        const angle = -this._environmentRotation;
        const cosine = Math.cos(angle);
        const sine = Math.sin(angle);
        const directionX = DefaultShadowLightDirection[0] * cosine + DefaultShadowLightDirection[2] * sine;
        const directionY = DefaultShadowLightDirection[1];
        const directionZ = -0.12 * sine + DefaultShadowLightDirection[2] * cosine;
        const directionLength = Math.sqrt(directionX ** 2 + directionY ** 2 + directionZ ** 2);
        const positionFactor = bounds.radius * 3;
        const [centerX, centerY, centerZ] = bounds.center;
        light.direction.set(directionX, directionY, directionZ);
        light.position.set(centerX - (directionX / directionLength) * positionFactor, centerY - (directionY / directionLength) * positionFactor, centerZ - (directionZ / directionLength) * positionFactor);
    }
    // ── Model Loading ──
    /** @internal */
    async _loadModelImpl(source, options, abortSignal, internalAbortSignal) {
        // Source `undefined` flows through from `resetModel`. Treat as "unload, no new load".
        if (source === undefined) {
            const hadModel = this._modelSource !== null;
            this._unloadCurrentModel();
            if (hadModel) {
                this.onModelChanged.notifyObservers(null);
            }
            return;
        }
        const loadOperation = this._beginLoadOperation();
        try {
            if (typeof source !== "string") {
                Logger.Warn("Viewer: Only string URLs are supported for model loading. File and ArrayBufferView sources are not supported.");
                throw new Error("Unsupported model source type");
            }
            // Unload previous model
            this._unloadCurrentModel();
            // Load new model
            const url = source;
            if (options?.pluginExtension) {
                // Append extension hint if provided
                const ext = options.pluginExtension.startsWith(".") ? options.pluginExtension : `.${options.pluginExtension}`;
                if (!url.toLowerCase().endsWith(ext.toLowerCase())) {
                    // The Lite loader determines format from URL extension;
                    // for now we just trust the URL.
                }
            }
            const container = await loadGltf(this._engine, url);
            throwIfAborted(abortSignal, internalAbortSignal);
            this._container = container;
            this._modelSource = source;
            // Set up animation state BEFORE `addToScene` registers the tick callback. This way the
            // very first tick of the new render loop sees only the selected group as eligible to
            // tick — preventing any "wrong animation" flash on the first rendered frame.
            // (Lite's `createAnimationGroups` sets every clip to auto-play; we need exactly one to
            // be active at all times.)
            this._setupAnimations();
            // Add to scene and rebuild renderables
            addToScene(this._scene, container);
            // Frame the camera to the model BEFORE the first rendered frame, so the model never
            // appears briefly at the previous (default) camera position. Snap (no interpolation) here,
            // matching the full Viewer, which loads with `interpolateCamera: false`. Apply the explicit
            // cameraOrbit/cameraTarget option overrides on top of the bounds framing, mirroring the full
            // Viewer's post-load `_reset(false, "camera")` (viewer.ts `_loadModelImpl`) — without this,
            // an explicit `camera-orbit` is ignored on initial load.
            this._frameCameraToModel(false, true);
            // Setup shadows BEFORE the first rendered frame so the shadow ground's deferred GPU
            // builder is processed by the upcoming `registerScene` (Lite only runs deferred builders
            // during `registerScene`; meshes added afterwards stay invisible until re-registration).
            if (this._shadowQuality !== "none") {
                await this._setupShadows(abortSignal, internalAbortSignal);
            }
            // Materialize the newly-added model. There are two paths, and using the wrong one drops the
            // model:
            //
            // - First build of a material group (`addToScene` queued a deferred builder): the renderable
            //   is built only by `registerScene` -> `buildScene`, and that first build also populates the
            //   group's `_rebuildSingle` closure. This happens on the initial model load (the constructor
            //   registers an empty scene, so the model's PBR group is new) and whenever a load introduces a
            //   brand-new material family. Re-register to run the deferred builder.
            //
            // - Re-using an already-built group (`addToScene` only enqueued the meshes into the per-frame
            //   material-swap queue): this is the model-swap case — the previous model was removed, but Lite
            //   retains the (now-empty) group key, so the new meshes reuse it and get a swap-queue entry
            //   instead of a deferred builder. The running render loop drains that queue every frame
            //   (`processMaterialSwaps`), builds each renderable via the group's `_rebuildSingle`, and bumps
            //   `_renderableVersion` so the frame graph re-buckets them. Re-registering here would be wrong:
            //   `buildScene` clears the material-swap queue before the loop can drain it, dropping the model.
            //
            // So only (re-)register when the scene isn't registered yet or the model's material group
            // has not been built before; otherwise let the running loop drain the swap queue.
            //
            // `this._scene` is created once in the constructor and never recreated (it is disposed only in
            // `dispose()`), and the Viewer only loads glTF, whose meshes all share Lite's singleton PBR group
            // builder. So the first successful model load builds that group (populating its per-mesh rebuild
            // closure), and every later load — swap, reload, reset, or clear-then-load — reuses it via the
            // swap queue. `_modelMaterialGroupBuilt` tracks that one-time transition.
            //
            // Shadows are excluded from the swap-queue path: `_setupShadows` adds a fresh light + ground on
            // every load (`_unloadCurrentModel` tears down the previous ones), and the shadow frame-graph task
            // is wired at registration time. For a shadow-enabled viewer we therefore keep the (documented,
            // construction-time) re-registration behavior rather than draining the swap queue, so the shadow
            // task is rebuilt against the new light/ground instead of going stale. Shadow quality is fixed at
            // construction (see `_updateShadowsImpl`), so this only affects the rarely-used shadow + model-swap
            // combination.
            //
            // The condition tests `_sceneRegistered` rather than the rAF-loop state so that a model loaded
            // while rendering is suspended still registers its renderables; the swap queue it may instead
            // enqueue into is drained by the first frame after rendering resumes.
            if (!this._sceneRegistered || this._shadowQuality !== "none" || !this._modelMaterialGroupBuilt) {
                await this._beginRendering();
            }
            this._modelMaterialGroupBuilt = true;
            // Apply clear color from model if present
            if (container.clearColor) {
                this._scene.clearColor = container.clearColor;
                this.onClearColorChanged.notifyObservers();
            }
            // Apply material variant from options
            if (this._options?.selectedMaterialVariant) {
                this.selectedMaterialVariant = this._options.selectedMaterialVariant;
            }
            this.onModelChanged.notifyObservers(source);
        }
        catch (e) {
            if (!(e instanceof AbortError)) {
                this.onModelError.notifyObservers(e);
            }
            throw e;
        }
        finally {
            loadOperation.dispose();
        }
    }
    _unloadCurrentModel() {
        // Reset animation state
        this._selectedAnimation = -1;
        this._wasPlaying = false;
        this._lastProgress = -1;
        if (this._container) {
            // Reset material variant
            if (this._selectedMaterialVariant !== null) {
                resetVariant(this._container);
                this._selectedMaterialVariant = null;
            }
            // Remove the model's renderables from the scene. `addToScene` adds the container's meshes but
            // nothing removes them on unload, so without this the previous model stays rendered after a
            // `clear model` (source removed) or `change model source`. `removeFromScene` fully tears down
            // each mesh (renderables, frame-graph task bindings, GPU buffers) and bumps the renderable
            // version, so the removal is reflected by the live render loop even when the scene is not
            // re-registered (the model-cleared path does not re-register).
            //
            // TODO: Simplify once https://github.com/BabylonJS/Babylon-Lite/pull/337 is merged and picked
            // up in the junctioned Lite build. That PR makes `removeFromScene` accept the same union as
            // `addToScene` (including `AssetContainer`) and undoes the add field-by-field. The entire block
            // below (stop animation groups, splice them out of `scene.animationGroups`, and remove each
            // container mesh) collapses to a single symmetric call:
            //     removeFromScene(this._scene, this._container);
            // That PR also plugs a leak this manual teardown cannot reach: `addToScene` pushes an anonymous
            // `_beforeRender` animation-tick closure with no removal handle. Stopping the groups here keeps
            // it harmless (it ticks stopped clips), but the closure stays in `scene._beforeRender` across
            // loads. The PR stores it as `AssetContainer._beforeRenderHook` so removal can splice it out.
            const groups = this._container.animationGroups;
            if (groups) {
                // Stop the container's clips first so the animation tick callback `addToScene` registered
                // for them does not keep advancing a model whose meshes are being removed.
                for (const group of groups) {
                    stopAnimation(group);
                }
                for (const group of groups) {
                    const index = this._scene.animationGroups.indexOf(group);
                    if (index >= 0) {
                        this._scene.animationGroups.splice(index, 1);
                    }
                }
            }
            for (const mesh of getContainerMeshes(this._container)) {
                removeFromScene(this._scene, mesh);
            }
        }
        // Tear down the shadow infrastructure created by `_setupShadows` so a subsequent (re)load doesn't
        // accumulate duplicate lights/ground discs. The ground disc is a mesh we can fully remove + free;
        // the directional light is detached by removing it from the scene's light list. (Lite has no public
        // shadow-generator dispose API yet — dropping our reference plus removing the light detaches it from
        // the render path, and the shadow frame-graph task is rebuilt on the next `registerScene`.)
        if (this._shadowGround) {
            removeFromScene(this._scene, this._shadowGround);
            disposeMeshGpu(this._shadowGround);
            this._shadowGround = null;
        }
        if (this._shadowLight) {
            const lightIndex = this._scene.lights.indexOf(this._shadowLight);
            if (lightIndex >= 0) {
                this._scene.lights.splice(lightIndex, 1);
            }
            this._shadowLight = null;
        }
        this._shadowGenerator = null;
        this._container = null;
        this._modelSource = null;
        this._cachedModelBounds = null;
    }
    // ── Animation ──
    get animations() {
        const groups = this._container?.animationGroups;
        if (!groups || groups.length === 0) {
            return [];
        }
        return groups.map((g) => g.name);
    }
    get selectedAnimation() {
        return this._selectedAnimation;
    }
    set selectedAnimation(index) {
        const groups = this._container?.animationGroups;
        if (!groups || groups.length === 0) {
            this._selectedAnimation = -1;
            this.onSelectedAnimationChanged.notifyObservers();
            return;
        }
        const newIndex = index >= 0 && index < groups.length ? index : -1;
        if (newIndex === this._selectedAnimation) {
            return;
        }
        // Capture whether the previously-active animation was playing so we can preserve play state
        // across selection (matches full Viewer's behavior).
        const previousActive = this._getActiveAnimationGroup();
        const wasPlaying = previousActive?.isPlaying ?? false;
        this._selectedAnimation = newIndex;
        this._isolateSelectedAnimation();
        // The framing bounds are animation-specific (each clip sweeps a different volume), so drop
        // the cached bounds and reframe the camera to the newly-selected animation. Without this the
        // camera stays framed for the previously-selected clip and the model can animate out of view
        // — e.g. the acrobaticPlane/UFO "flight" clip travels far outside the "hover" bounds. Matches
        // the full Viewer, whose `selectedAnimation` setter calls `_reframeCamera` (which interpolates).
        this._cachedModelBounds = null;
        this._frameCameraToModel(true);
        this.onSelectedAnimationChanged.notifyObservers();
        if (wasPlaying) {
            this.playAnimation();
        }
    }
    get animationSpeed() {
        return this._animationSpeed;
    }
    set animationSpeed(value) {
        this._animationSpeed = value;
        const group = this._getActiveAnimationGroup();
        if (group) {
            group.speedRatio = value;
        }
        this.onAnimationSpeedChanged.notifyObservers();
    }
    get isAnimationPlaying() {
        const group = this._getActiveAnimationGroup();
        return group ? group.isPlaying : false;
    }
    get animationProgress() {
        const group = this._getActiveAnimationGroup();
        if (!group || group.duration <= 0) {
            return 0;
        }
        // currentTime is in seconds; duration is also in seconds
        return Math.min(group.currentTime / group.duration, 1);
    }
    set animationProgress(value) {
        const group = this._getActiveAnimationGroup();
        if (!group || group.duration <= 0) {
            return;
        }
        // goToFrame expects a frame number at 60 fps
        const targetSeconds = value * group.duration;
        const frameAt60fps = targetSeconds * 60;
        goToFrame(group, frameAt60fps);
        this.onAnimationProgressChanged.notifyObservers();
    }
    toggleAnimation() {
        if (this.isAnimationPlaying) {
            observePromise(this.pauseAnimation());
        }
        else {
            this.playAnimation();
        }
    }
    playAnimation() {
        const group = this._getActiveAnimationGroup();
        if (group) {
            group.speedRatio = this._animationSpeed;
            group.loopAnimation = true;
            playAnimation(group);
            this.onIsAnimationPlayingChanged.notifyObservers();
        }
    }
    async pauseAnimation() {
        const group = this._getActiveAnimationGroup();
        if (group) {
            pauseAnimation(group);
            this.onIsAnimationPlayingChanged.notifyObservers();
        }
    }
    _getActiveAnimationGroup() {
        const groups = this._container?.animationGroups;
        if (!groups || this._selectedAnimation < 0 || this._selectedAnimation >= groups.length) {
            return null;
        }
        return groups[this._selectedAnimation];
    }
    /**
     * Enforces the "only the selected animation may be playing" invariant by stopping every
     * non-selected animation group (`stopAnimation` blocks subsequent ticks) and pausing the
     * selected one (so its tick still runs and applies the current-time pose).
     *
     * Lite's `tickAnimation` writes bone TRS every frame regardless of `playing`, so a merely
     * paused non-selected group would still pollute the mesh transforms with its current frame's
     * pose. Only `stopAnimation` blocks tick entirely.
     *
     * The selected group's tick must be allowed to run (so switching between animations updates
     * the pose). Lite's `pauseAnimation` doesn't un-stop a previously-stopped group, so we run
     * `playAnimation` then `pauseAnimation` to clear the stopped flag while ending up paused —
     * the tick fires next frame and applies the time-0 pose.
     */
    _isolateSelectedAnimation() {
        const groups = this._container?.animationGroups;
        if (!groups) {
            return;
        }
        for (let i = 0; i < groups.length; i++) {
            const group = groups[i];
            if (i === this._selectedAnimation) {
                playAnimation(group);
                pauseAnimation(group);
            }
            else {
                stopAnimation(group);
            }
        }
    }
    _setupAnimations() {
        const groups = this._container?.animationGroups;
        if (!groups || groups.length === 0) {
            this._selectedAnimation = -1;
            return;
        }
        // Select the first animation by default, or the one specified in options
        const defaultIndex = this._options?.selectedAnimation ?? 0;
        this._selectedAnimation = defaultIndex >= 0 && defaultIndex < groups.length ? defaultIndex : 0;
        // Stop every non-selected group; pause the selected one. See `_isolateSelectedAnimation`.
        this._isolateSelectedAnimation();
        this.onSelectedAnimationChanged.notifyObservers();
        // Auto-play if configured
        if (this._options?.animationAutoPlay) {
            this.playAnimation();
        }
    }
    _pollAnimationState() {
        const group = this._getActiveAnimationGroup();
        if (!group) {
            return;
        }
        const isPlaying = group.isPlaying;
        if (isPlaying !== this._wasPlaying) {
            this._wasPlaying = isPlaying;
            this.onIsAnimationPlayingChanged.notifyObservers();
        }
        if (isPlaying) {
            const progress = group.duration > 0 ? group.currentTime / group.duration : 0;
            if (progress !== this._lastProgress) {
                this._lastProgress = progress;
                this.onAnimationProgressChanged.notifyObservers();
            }
        }
    }
    // ── Material Variants ──
    get materialVariants() {
        if (!this._container) {
            return [];
        }
        return getVariantNames(this._container);
    }
    get selectedMaterialVariant() {
        return this._selectedMaterialVariant;
    }
    set selectedMaterialVariant(value) {
        if (value === this._selectedMaterialVariant) {
            return;
        }
        this._selectedMaterialVariant = value;
        if (this._container) {
            if (value === null) {
                resetVariant(this._container);
            }
            else {
                selectVariant(this._container, value);
            }
        }
        this.onSelectedMaterialVariantChanged.notifyObservers();
    }
    // ── Hot Spots ──
    get camerasAsHotSpots() {
        return this._camerasAsHotSpots;
    }
    set camerasAsHotSpots(value) {
        if (value === this._camerasAsHotSpots) {
            return;
        }
        this._camerasAsHotSpots = value;
        this.onCamerasAsHotSpotsChanged.notifyObservers();
    }
    queryHotSpot(name, result) {
        return this._queryHotSpot(name, result) != null;
    }
    focusHotSpot(name) {
        const result = new ViewerHotSpotResult();
        const hotSpot = this._queryHotSpot(name, result);
        if (!hotSpot) {
            return false;
        }
        observePromise(this.pauseAnimation());
        // Smoothly move the camera to the hotspot's associated orbit pose (if any), always retargeting
        // to the hotspot's world position — mirroring the full Viewer's `focusHotSpot`, which calls
        // `ArcRotateCamera.interpolateTo`. Omitted/NaN orbit components keep the camera's current value.
        const orbit = hotSpot.cameraOrbit;
        const alpha = orbit?.[0] == null ? undefined : Number(orbit[0]);
        const beta = orbit?.[1] == null ? undefined : Number(orbit[1]);
        const radius = orbit?.[2] == null ? undefined : Number(orbit[2]);
        this._interpolateCameraTo({
            alpha,
            beta,
            radius,
            target: { x: result.worldPosition[0], y: result.worldPosition[1], z: result.worldPosition[2] },
        });
        return true;
    }
    /**
     * Starts a camera interpolation toward the given goal pose, canceling any interpolation already in
     * flight. Lite's arc-rotate camera has no built-in interpolation, so this drives
     * {@link interpolateArcRotateCamera} from the scene render loop. The returned promise is intentionally
     * swallowed: it rejects when the transition is superseded, aborted, or interrupted by user input,
     * none of which are error conditions here.
     * @param goal The destination camera pose; omitted fields keep the current value.
     */
    _interpolateCameraTo(goal) {
        this._cameraInterpolationAbort?.abort(new AbortError("Camera interpolation superseded."));
        const abortController = new AbortController();
        this._cameraInterpolationAbort = abortController;
        void (async () => {
            try {
                await interpolateArcRotateCamera(this._camera, this._scene, goal, abortController.signal);
            }
            catch {
                // Superseded / aborted / interrupted by user input — all expected, not error conditions.
            }
            finally {
                if (this._cameraInterpolationAbort === abortController) {
                    this._cameraInterpolationAbort = null;
                }
            }
        })();
    }
    /**
     * Resolves a named hotspot to its world position, screen position, and visibility, writing the
     * result into `result`. Returns the hotspot definition on success (so callers like
     * {@link focusHotSpot} can read its `cameraOrbit`), or `null` if the hotspot is unknown or cannot
     * be resolved (e.g. an out-of-range surface vertex).
     *
     * Surface hotspots track skeletal + morph animation: the three referenced vertices are deformed
     * for the current frame via {@link computeDeformedPositionToRef} (mesh-local), barycentric-
     * blended, then transformed to world space by the mesh world matrix — mirroring Babylon.js core's
     * `GetHotSpotToRef`. World hotspots use their fixed position/normal.
     * @param name The name of the hotspot to resolve.
     * @param result The result object to write the world position, screen position, and visibility into.
     * @returns The hotspot definition on success, or `null` if it cannot be resolved.
     */
    _queryHotSpot(name, result) {
        const hotSpot = this.hotSpots[name];
        if (!hotSpot) {
            return null;
        }
        const worldPos = _tmpHotSpotVectors.worldPos;
        const worldNormal = _tmpHotSpotVectors.worldNormal;
        if (hotSpot.type === "surface") {
            // Hotspot `meshIndex` values are authored against the full Viewer, whose glTF loader
            // inserts a synthetic `__root__` mesh at `assetContainer.meshes[0]` — so index 1 is the
            // first real mesh. Lite's `getContainerMeshes` returns only renderable meshes (no root),
            // so shift by one to keep shared hotspot configs consistent across both viewers.
            const meshes = this._container ? getContainerMeshes(this._container) : [];
            const mesh = meshes[hotSpot.meshIndex - 1];
            if (!mesh) {
                return null;
            }
            if (!this._getSurfaceHotSpotToRef(mesh, hotSpot.pointIndex, hotSpot.barycentric, worldPos, worldNormal)) {
                return null;
            }
        }
        else {
            worldPos.x = hotSpot.position[0];
            worldPos.y = hotSpot.position[1];
            worldPos.z = hotSpot.position[2];
            worldNormal.x = hotSpot.normal[0];
            worldNormal.y = hotSpot.normal[1];
            worldNormal.z = hotSpot.normal[2];
        }
        // Project the world position to screen space. Aspect matches the rendered drawing buffer;
        // the NDC→pixel mapping uses the canvas CSS size so it aligns with the DOM annotation overlay.
        const canvas = this._engine.canvas;
        const bufferWidth = canvas.width || 1;
        const bufferHeight = canvas.height || 1;
        const cssWidth = canvas.clientWidth || bufferWidth;
        const cssHeight = canvas.clientHeight || bufferHeight;
        const aspect = getEffectiveAspectRatio(this._camera, bufferWidth, bufferHeight);
        const vp = getViewProjectionMatrix(this._camera, aspect);
        // clip = VP * [worldPos, 1] (column-major).
        const cx = vp[0] * worldPos.x + vp[4] * worldPos.y + vp[8] * worldPos.z + vp[12];
        const cy = vp[1] * worldPos.x + vp[5] * worldPos.y + vp[9] * worldPos.z + vp[13];
        const cw = vp[3] * worldPos.x + vp[7] * worldPos.y + vp[11] * worldPos.z + vp[15];
        if (cw <= 0) {
            // Behind the camera — report as invalid (matches an off-screen/back projection).
            result.screenPosition[0] = NaN;
            result.screenPosition[1] = NaN;
        }
        else {
            const ndcX = cx / cw;
            const ndcY = cy / cw;
            // Inverse of Lite's createPickingRay screen→NDC mapping (Y flipped for WebGPU).
            result.screenPosition[0] = ((ndcX + 1) / 2) * cssWidth;
            result.screenPosition[1] = ((1 - ndcY) / 2) * cssHeight;
        }
        result.worldPosition[0] = worldPos.x;
        result.worldPosition[1] = worldPos.y;
        result.worldPosition[2] = worldPos.z;
        // Visibility: dot(eyeToSurface, worldNormal). > 0 front-facing, <= 0 back-facing.
        const eye = getCameraPosition(this._camera);
        let ex = eye.x - worldPos.x;
        let ey = eye.y - worldPos.y;
        let ez = eye.z - worldPos.z;
        const len = Math.hypot(ex, ey, ez) || 1;
        ex /= len;
        ey /= len;
        ez /= len;
        result.visibility = ex * worldNormal.x + ey * worldNormal.y + ez * worldNormal.z;
        return hotSpot;
    }
    /**
     * Computes the world-space position and normal of a surface hotspot on `mesh` from three vertex
     * indices and barycentric weights, applying the mesh's current animation pose. Mirrors core's
     * `GetHotSpotToRef`: deform each vertex to mesh-local space, blend by barycentric, then transform
     * the single blended point (and the local triangle normal) to world space.
     * @param mesh The mesh the hotspot is anchored to.
     * @param pointIndex The three vertex indices defining the hotspot's triangle.
     * @param barycentric The barycentric weights blending the three vertices.
     * @param outPos Receives the world-space hotspot position.
     * @param outNormal Receives the world-space hotspot normal.
     * @returns `true` if the position and normal were computed, or `false` if a vertex is out of range.
     */
    _getSurfaceHotSpotToRef(mesh, pointIndex, barycentric, outPos, outNormal) {
        const a = _tmpHotSpotVectors.a;
        const b = _tmpHotSpotVectors.b;
        const c = _tmpHotSpotVectors.c;
        if (!computeDeformedPositionToRef(mesh, pointIndex[0], a) ||
            !computeDeformedPositionToRef(mesh, pointIndex[1], b) ||
            !computeDeformedPositionToRef(mesh, pointIndex[2], c)) {
            return false;
        }
        // Barycentric blend in mesh-local space.
        const lx = a.x * barycentric[0] + b.x * barycentric[1] + c.x * barycentric[2];
        const ly = a.y * barycentric[0] + b.y * barycentric[1] + c.y * barycentric[2];
        const lz = a.z * barycentric[0] + b.z * barycentric[1] + c.z * barycentric[2];
        // Local triangle normal = (b - a) x (c - a).
        const abx = b.x - a.x;
        const aby = b.y - a.y;
        const abz = b.z - a.z;
        const acx = c.x - a.x;
        const acy = c.y - a.y;
        const acz = c.z - a.z;
        const nx = aby * acz - abz * acy;
        const ny = abz * acx - abx * acz;
        const nz = abx * acy - aby * acx;
        const m = mesh.worldMatrix;
        // Position → world (column-major, with translation).
        outPos.x = m[0] * lx + m[4] * ly + m[8] * lz + m[12];
        outPos.y = m[1] * lx + m[5] * ly + m[9] * lz + m[13];
        outPos.z = m[2] * lx + m[6] * ly + m[10] * lz + m[14];
        // Normal → world (rotation/scale only, no translation), then normalize.
        const wnx = m[0] * nx + m[4] * ny + m[8] * nz;
        const wny = m[1] * nx + m[5] * ny + m[9] * nz;
        const wnz = m[2] * nx + m[6] * ny + m[10] * nz;
        const nlen = Math.hypot(wnx, wny, wnz) || 1;
        outNormal.x = wnx / nlen;
        outNormal.y = wny / nlen;
        outNormal.z = wnz / nlen;
        return true;
    }
    // ── State ──
    get isModelLoaded() {
        return this._container !== null;
    }
    /** @internal */
    _resetEnvironment() {
        // Reset scalar env config to defaults (matches full Viewer's reset hook). The setter fires
        // `onEnvironmentConfigurationChanged` and is independent of the URL state.
        this.environmentConfig = {
            intensity: this._options?.environmentConfig?.intensity ?? DefaultViewerOptions.environmentConfig.intensity,
            blur: this._options?.environmentConfig?.blur ?? DefaultViewerOptions.environmentConfig.blur,
            rotation: this._options?.environmentConfig?.rotation ?? DefaultViewerOptions.environmentConfig.rotation,
        };
        observePromise(this.resetEnvironment());
        const initialLightingUrl = this._options?.environmentLighting ?? DefaultViewerOptions.environmentLighting;
        const initialSkyboxUrl = this._options?.environmentSkybox ?? DefaultViewerOptions.environmentSkybox;
        if (initialLightingUrl === initialSkyboxUrl) {
            if (initialLightingUrl !== "none") {
                observePromise(this.loadEnvironment(initialLightingUrl));
            }
        }
        else {
            if (initialLightingUrl !== "none") {
                observePromise(this.loadEnvironment(initialLightingUrl, { lighting: true, skybox: false }));
            }
            if (initialSkyboxUrl !== "none") {
                observePromise(this.loadEnvironment(initialSkyboxUrl, { lighting: false, skybox: true }));
            }
        }
    }
    /** @internal */
    _resetAnimation() {
        const groups = this._container?.animationGroups;
        if (groups && groups.length > 0) {
            this._selectedAnimation = this._options?.selectedAnimation ?? 0;
            this._animationSpeed = this._options?.animationSpeed ?? 1;
            this._isolateSelectedAnimation();
            this.onSelectedAnimationChanged.notifyObservers();
            this.onAnimationSpeedChanged.notifyObservers();
            if (this._options?.animationAutoPlay) {
                this.playAnimation();
            }
        }
    }
    /**
     * @internal
     * Resets the camera to its default/framing pose, animating the transition when `interpolate` is true
     * (e.g. a user-initiated reset) and snapping when false (e.g. an initial reset before the first frame).
     */
    _resetCamera(interpolate) {
        this._resetCameraCore(undefined, interpolate);
        this.cameraAutoOrbit = this._options?.cameraAutoOrbit
            ? {
                enabled: this._options.cameraAutoOrbit.enabled ?? DefaultViewerOptions.cameraAutoOrbit.enabled,
                speed: this._options.cameraAutoOrbit.speed ?? DefaultViewerOptions.cameraAutoOrbit.speed,
                delay: this._options.cameraAutoOrbit.delay ?? DefaultViewerOptions.cameraAutoOrbit.delay,
            }
            : { ...DefaultViewerOptions.cameraAutoOrbit };
    }
    /** @internal */
    _resetPostProcessing() {
        // Route through the public setter so we get free dedup + observable-notify-on-change
        // semantics, matching what the user-facing API does.
        this.postProcessing = {
            toneMapping: this._options?.postProcessing?.toneMapping ?? DefaultViewerOptions.postProcessing.toneMapping,
            contrast: this._options?.postProcessing?.contrast ?? DefaultViewerOptions.postProcessing.contrast,
            exposure: this._options?.postProcessing?.exposure ?? DefaultViewerOptions.postProcessing.exposure,
            ssao: this._options?.postProcessing?.ssao ?? DefaultViewerOptions.postProcessing.ssao,
        };
    }
    /**
     * Registers the scene with the engine and starts the render loop.
     * Safe to call multiple times — stops and re-registers if already running.
     * @remarks
     * Serialized against suspend/resume via {@link _renderLoopLock} so a scroll-driven suspension can never
     * land in the middle of the stop, unregister, register, start sequence below.
     */
    async _beginRendering() {
        await this._renderLoopLock.lockAsync(async () => {
            if (this._isDisposed) {
                return;
            }
            this._stopRenderLoop();
            if (this._sceneRegistered) {
                unregisterScene(this._scene);
                this._sceneRegistered = false;
            }
            // Install the scene-owned frame-graph shadow task only when shadows are enabled, so the
            // shadow-task bundle is tree-shaken out for viewers that never render shadows.
            if (this._shadowQuality === "none") {
                await registerScene(this._scene);
            }
            else {
                await registerSceneWithShadowSupport(this._scene);
            }
            if (this._isDisposed) {
                return;
            }
            this._sceneRegistered = true;
            await this._startRenderLoop();
        });
    }
    /**
     * Starts the engine's render loop, unless rendering is suspended (or the viewer is disposed), and waits
     * for the first frame.
     * @remarks
     * Lite's `startEngine` promise resolves from inside the rAF callback, so it never settles if the loop is
     * stopped before that first frame. {@link _renderLoopStopped} races against it so a suspension or a
     * disposal arriving in that window can't leave this await (and any model load awaiting it) pending forever.
     */
    async _startRenderLoop() {
        if (this._renderLoopRunning || this._isDisposed || this._suspendRenderCount > 0) {
            return;
        }
        this._renderLoopRunning = true;
        const firstFrame = startEngine(this._engine);
        const stopped = new Promise((resolve) => (this._renderLoopStopped = resolve));
        await Promise.race([firstFrame, stopped]);
        this._renderLoopStopped = null;
    }
    /** Stops the engine's render loop (if running) and releases anyone awaiting its first frame. */
    _stopRenderLoop() {
        if (this._renderLoopRunning) {
            stopEngine(this._engine);
            this._renderLoopRunning = false;
        }
        this._renderLoopStopped?.();
        this._renderLoopStopped = null;
    }
    /**
     * Suspends rendering until the returned disposable is disposed.
     * @remarks
     * Reference counted, mirroring the full Viewer: rendering only resumes once every suspension handle has
     * been disposed. The scene stays registered while suspended, so resuming does not rebuild anything.
     * @returns A disposable that resumes rendering (when no other suspensions are outstanding).
     * @internal
     */
    _suspendRendering() {
        if (this._suspendRenderCount++ === 0) {
            observePromise(this._renderLoopLock.lockAsync(() => this._stopRenderLoop()));
        }
        let disposed = false;
        return {
            dispose: () => {
                if (!disposed) {
                    disposed = true;
                    if (--this._suspendRenderCount === 0) {
                        // Not awaited: resuming must not block the caller (typically an IntersectionObserver
                        // callback) on the first rendered frame.
                        observePromise(this._renderLoopLock.lockAsync(async () => await this._startRenderLoop()));
                    }
                }
            },
        };
    }
    dispose() {
        if (this._isDisposed) {
            return;
        }
        // Disable device-lost recovery before any teardown below: everything that follows frees GPU
        // resources (picker, model, scene, engine), and a loss arriving mid-teardown would otherwise
        // kick off a rebuild against resources that are in the process of being destroyed.
        this._deviceLostRecovery.disable();
        // Detach camera controls
        this._detachControl?.();
        this._detachControl = null;
        // Cancel any in-flight camera interpolation so its render-loop callback stops touching the scene.
        this._cameraInterpolationAbort?.abort(new AbortError("Viewer disposed."));
        this._cameraInterpolationAbort = null;
        // Clean up pointer listeners
        this._engine.canvas.removeEventListener("pointerdown", this._onPointerActivity);
        this._engine.canvas.removeEventListener("pointermove", this._onPointerActivity);
        this._engine.canvas.removeEventListener("wheel", this._onPointerActivity);
        this._engine.canvas.removeEventListener("dblclick", this._onCanvasDoubleClick);
        // Dispose the GPU picker (if a double-click ever created it).
        if (this._picker) {
            disposePicker(this._picker);
            this._picker = null;
        }
        // Unload model
        this._unloadCurrentModel();
        // Stop and dispose engine/scene
        this._stopRenderLoop();
        unregisterScene(this._scene);
        this._sceneRegistered = false;
        disposeScene(this._scene);
        disposeEngine(this._engine);
        // Base disposes observables and sets _isDisposed = true
        super.dispose();
    }
    /**
     * Picks the model at the given canvas coordinates and either focuses the picked point (hit) or
     * reframes the camera (miss). Only the loaded model's meshes are pickable, so Viewer-added meshes
     * (e.g. the shadow-receiver disc) never swallow a pick or count as a "model" hit.
     * @param x The canvas-relative CSS x coordinate of the double-click.
     * @param y The canvas-relative CSS y coordinate of the double-click.
     */
    async _handleDoubleClick(x, y) {
        // With no loaded model there is nothing to pick; treat as a background double-click (reframe).
        if (this._container) {
            const pickables = new Set(getContainerMeshes(this._container));
            this._picker ??= createGpuPicker(this._scene);
            const pick = await pickAsync(this._picker, x, y, { filter: (mesh) => pickables.has(mesh) });
            if (this._isDisposed) {
                return;
            }
            if (pick.hit && pick.pickedPoint) {
                this._focusCameraOnPoint(pick.pickedPoint);
                return;
            }
        }
        // Background double-click: reframe the camera to the model bounds (animated).
        this.resetCamera(true);
    }
    /**
     * Focuses the camera on a world-space point, mirroring the full Viewer's double-tap-on-model behavior.
     * The target and radius are first snapped so the point lies on the current view axis at its picked
     * depth — this preserves the camera position and avoids a dolly along the view axis — then the target
     * is interpolated to the actual point (orbit angles and radius held).
     * @param point The world-space point to focus on.
     */
    _focusCameraOnPoint(point) {
        const position = getCameraPosition(this._camera);
        const target = this._camera.target;
        // Forward (view) direction of the ArcRotate camera: from the camera position toward the target.
        let fx = target.x - position.x;
        let fy = target.y - position.y;
        let fz = target.z - position.z;
        const length = Math.hypot(fx, fy, fz) || 1;
        fx /= length;
        fy /= length;
        fz /= length;
        // Distance to the picked point measured along the view axis.
        const distance = (point[0] - position.x) * fx + (point[1] - position.y) * fy + (point[2] - position.z) * fz;
        // Snap the target onto the view axis at the picked depth and set the radius to match. Because
        // position = target - forward * radius, this leaves the camera position unchanged; only the
        // subsequent target interpolation moves the camera. Mutate the ObservableVec3 in place to keep
        // Lite's dirty-tracking. This must precede the interpolation so it starts from this pose.
        target.x = position.x + fx * distance;
        target.y = position.y + fy * distance;
        target.z = position.z + fz * distance;
        this._camera.radius = distance;
        // Interpolate the target to the actual picked point (orbit angles and radius held).
        this._interpolateCameraTo({ target: { x: point[0], y: point[1], z: point[2] } });
    }
    _updateAutoOrbit(deltaMs) {
        if (!this._autoOrbitEnabled || this._cameraInterpolationAbort) {
            this._autoOrbitIdleTime = 0;
            return;
        }
        const now = performance.now();
        const idleMs = now - this._lastPointerTime;
        if (idleMs < this._autoOrbitDelay) {
            this._autoOrbitIdleTime = 0;
            return;
        }
        this._autoOrbitIdleTime += deltaMs;
        // Rotate alpha based on speed (radians per second)
        const rotationAmount = (this._autoOrbitSpeed * deltaMs) / 1000;
        this._camera.alpha += rotationAmount;
    }
}
/**
 * Creates a new {@link Viewer} instance for the given canvas element.
 * @param canvas The HTML canvas element to render into.
 * @param options Optional viewer configuration.
 * @returns A promise that resolves to the initialized Viewer.
 */
async function CreateViewerForCanvas(canvas, options) {
    const engine = await createEngine(canvas, { alphaMode: "premultiplied" });
    const viewer = new Viewer(engine, options);
    // If the canvas is not visible, suspend rendering. Matches the full Viewer, where this is unconditional
    // (the `autoSuspendRendering` option gates idle suspension, not offscreen suspension).
    const offscreenSuspensionObserver = SuspendRenderingWhenOffscreen(canvas, () => viewer._suspendRendering());
    // Override the Viewer's dispose method to add in additional cleanup. Matches the full Viewer's factory:
    // only the observer needs disconnecting — any live suspension handle is dropped along with the Viewer.
    const disposeViewer = viewer.dispose.bind(viewer);
    viewer.dispose = () => {
        offscreenSuspensionObserver.dispose();
        disposeViewer();
    };
    return viewer;
}

/**
 * Viewer custom element backed by the Babylon Lite engine (WebGPU-only).
 * Provides the same `<babylon-viewer>` tag as the full viewer — the two are mutually exclusive.
 */
class ViewerElement extends ViewerElementBase {
    constructor(options = {}) {
        super(options);
    }
    /**
     * Gets the underlying Viewer instance (when the viewer is in a loaded state).
     */
    get viewer() {
        return this._viewer;
    }
    async _createViewer(canvas, options) {
        return await CreateViewerForCanvas(canvas, options);
    }
}
/**
 * Displays a 3D model using the Babylon Lite Viewer (WebGPU-only).
 * @remarks
 * This element registers as `<babylon-viewer>` and is mutually exclusive with the full Babylon.js viewer element.
 * Import `@babylonjs/viewer/lite` instead of `@babylonjs/viewer` to use the Lite viewer.
 */
let HTML3DElement = (() => {
    let _classDecorators = [t$3("babylon-viewer")];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = ViewerElement;
    _classThis = class extends _classSuper {
        /**
         * Creates a new HTML3DElement backed by the Lite viewer.
         * @param options The options to use for the viewer.
         */
        constructor(options) {
            super(options);
        }
    };
    __setFunctionName(_classThis, "HTML3DElement");
    (() => {
        const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
        __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
        _classThis = _classDescriptor.value;
        if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        __runInitializers(_classThis, _classExtraInitializers);
    })();
    return _classThis;
})();
/**
 * Creates a custom HTML element that creates an HTML3DElement with the specified name and configuration.
 * @param elementName The name of the custom element.
 * @param options The options to use for the viewer.
 */
function ConfigureCustomViewerElement(elementName, options) {
    customElements.define(elementName, 
    // eslint-disable-next-line jsdoc/require-jsdoc
    class extends HTML3DElement {
        constructor() {
            super(options);
        }
    });
}

/**
 * Displays child elements at the screen space location of a hotspot in a babylon-viewer.
 * @remarks
 * The babylon-viewer-annotation element must be a child of a babylon-viewer element.
 */
let HTML3DAnnotationElement = (() => {
    var _HTML3DAnnotationElement_hotSpot_accessor_storage;
    let _classDecorators = [t$3("babylon-viewer-annotation")];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = i$1;
    let _hotSpot_decorators;
    let _hotSpot_initializers = [];
    let _hotSpot_extraInitializers = [];
    _classThis = class extends _classSuper {
        /**
         * The name of the hotspot to track.
         */
        get hotSpot() { return __classPrivateFieldGet(this, _HTML3DAnnotationElement_hotSpot_accessor_storage, "f"); }
        set hotSpot(value) { __classPrivateFieldSet(this, _HTML3DAnnotationElement_hotSpot_accessor_storage, value, "f"); }
        /** @internal */
        connectedCallback() {
            super.connectedCallback();
            this._internals.states?.add("invalid");
            this._connectingAbortController?.abort();
            this._connectingAbortController = new AbortController();
            const abortSignal = this._connectingAbortController.signal;
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            (async () => {
                // Custom element registration can happen at any time via a call to customElements.define, which means it is possible
                // the parent custom element hasn't been defined yet. This especially can happen if the order of imports and exports
                // results in the parent element being defined after the HTML3DAnnotationElement within the final JS bundle.
                if (this.parentElement?.matches(":not(:defined)")) {
                    await customElements.whenDefined(this.parentElement?.tagName.toLowerCase());
                    // If the element has since been disconnected or reconnected, abort this connection process.
                    if (abortSignal.aborted) {
                        return;
                    }
                }
                if (!(this.parentElement instanceof ViewerElementBase)) {
                    // eslint-disable-next-line no-console
                    console.warn("The babylon-viewer-annotation element must be a child of a babylon-viewer element.");
                    return;
                }
                this._mutationObserver.observe(this, { childList: true, characterData: true });
                this._sanitizeInnerHTML();
                const viewerElement = this.parentElement;
                const hotSpotResult = new ViewerHotSpotResult();
                const updateAnnotation = (this._updateAnnotation = () => {
                    if (this.hotSpot) {
                        if (viewerElement.queryHotSpot(this.hotSpot, hotSpotResult)) {
                            const [screenX, screenY] = hotSpotResult.screenPosition;
                            this.style.transform = `translate(${screenX}px, ${screenY}px)`;
                            this._internals.states?.delete("invalid");
                            if (hotSpotResult.visibility <= 0) {
                                this._internals.states?.add("back-facing");
                            }
                            else {
                                this._internals.states?.delete("back-facing");
                            }
                        }
                        else {
                            this._internals.states?.add("invalid");
                        }
                    }
                });
                this._updateAnnotation();
                viewerElement.addEventListener("viewerrender", updateAnnotation);
                this._viewerAttachment = {
                    dispose() {
                        viewerElement.removeEventListener("viewerrender", updateAnnotation);
                    },
                };
            })();
        }
        /** @internal */
        disconnectedCallback() {
            super.disconnectedCallback();
            this._connectingAbortController?.abort();
            this._connectingAbortController = null;
            this._viewerAttachment?.dispose();
            this._viewerAttachment = null;
            this._internals.states?.add("invalid");
            this._updateAnnotation = null;
        }
        /** @internal */
        // eslint-disable-next-line @typescript-eslint/naming-convention
        render() {
            return b ` <slot><div aria-label="${this.hotSpot} annotation" part="annotation" class="annotation">${this.hotSpot}</div></slot> `;
        }
        /** @internal */
        // eslint-disable-next-line @typescript-eslint/naming-convention
        update(changedProperties) {
            super.update(changedProperties);
            if (changedProperties.has("hotSpot")) {
                this._updateAnnotation?.();
            }
        }
        _sanitizeInnerHTML() {
            if (this.innerHTML.trim().length === 0) {
                this.innerHTML = "";
            }
        }
        constructor() {
            super(...arguments);
            this._internals = this.attachInternals();
            this._mutationObserver = new MutationObserver((mutations) => {
                if (mutations.some((mutation) => mutation.type === "childList")) {
                    this._sanitizeInnerHTML();
                }
            });
            this._viewerAttachment = null;
            this._connectingAbortController = null;
            this._updateAnnotation = null;
            _HTML3DAnnotationElement_hotSpot_accessor_storage.set(this, __runInitializers(this, _hotSpot_initializers, ""));
            __runInitializers(this, _hotSpot_extraInitializers);
        }
    };
    _HTML3DAnnotationElement_hotSpot_accessor_storage = new WeakMap();
    __setFunctionName(_classThis, "HTML3DAnnotationElement");
    (() => {
        const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
        _hotSpot_decorators = [n$3({ attribute: "hotspot" })];
        __esDecorate(_classThis, null, _hotSpot_decorators, { kind: "accessor", name: "hotSpot", static: false, private: false, access: { has: obj => "hotSpot" in obj, get: obj => obj.hotSpot, set: (obj, value) => { obj.hotSpot = value; } }, metadata: _metadata }, _hotSpot_initializers, _hotSpot_extraInitializers);
        __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
        _classThis = _classDescriptor.value;
        if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
    })();
    /** @internal */
    // eslint-disable-next-line @typescript-eslint/naming-convention
    _classThis.styles = i$4 `
        :host {
            --annotation-foreground-color: black;
            --annotation-background-color: white;
            display: inline-block;
            position: absolute;
            transition: opacity 0.25s;
        }

        :host([hidden]) {
            display: none;
        }

        :host(:state(back-facing)) {
            opacity: 0.2;
        }

        :host(:state(invalid)) {
            display: none;
        }

        .annotation {
            transform: translate(-50%, -135%);
            font-size: 14px;
            padding: 0px 6px;
            border-radius: 6px;
            color: var(--annotation-foreground-color);
            background-color: var(--annotation-background-color);
        }

        .annotation::after {
            content: "";
            position: absolute;
            left: 50%;
            height: 60%;
            aspect-ratio: 1;
            transform: translate(-50%, 110%) rotate(-45deg);
            background-color: inherit;
            clip-path: polygon(0 0, 100% 100%, 0 100%, 0 0);
        }
    `;
    (() => {
        __runInitializers(_classThis, _classExtraInitializers);
    })();
    return _classThis;
})();

export { PBR_HAS_TONEMAP as $, PBR_HAS_SKYBOX as A, BU as B, PBR_HAS_SPECULAR_AA as C, MSH_HAS_TANGENTS as D, PBR_HAS_ENV as E, F32 as F, MSH_RECEIVE_SHADOWS as G, PBR_HAS_ANISOTROPY as H, I32 as I, MSH_HAS_THIN_INSTANCES as J, MSH_HAS_VERTEX_COLOR as K, LIGHT_ENTRY_FLOATS as L, MAX_LIGHTS as M, MSH_HAS_UV2 as N, PBR_HAS_SPEC_GLOSS as O, PBR_HAS_EMISSIVE as P, PBR_HAS_FOG as Q, MSH_HAS_INSTANCE_COLOR as R, SCENE_UBO_BYTES as S, ThrowLiteError as T, U16 as U, PBR2_ESM_SHADOW_OUTPUT as V, PBR2_NO_COLOR_OUTPUT as W, PBR_HAS_OCCLUSION as X, PBR2_HAS_BASE_COLOR_FACTOR as Y, PBR_HAS_ALPHA_BLEND as Z, _getShadowTaskCasterMeshes as _, F64 as a, computeNodeWorldMatrix as a$, PBR_HAS_DOUBLE_SIDED as a0, MSH_FLAT_NORMAL as a1, MSH_HAS_MORPH_TARGETS as a2, PBR_HAS_EMISSIVE_COLOR as a3, PBR_HAS_METALLIC_REFLECTANCE_MAP as a4, PBR_HAS_REFLECTANCE_MAP as a5, PBR2_HAS_REFLECTANCE_FACTORS as a6, writeMeshLightSelection as a7, _registerPbrExt as a8, _getPbrSceneHooks as a9, uploadTex as aA, allocateMat4 as aB, mat4MultiplyInto as aC, getPickingSceneBGL as aD, stopEngine as aE, disposeGpuResourceRetirements as aF, _refreshScRT as aG, resizeEngine as aH, startEngine as aI, PBR_HAS_ALPHA_TEST as aJ, assembleEnvironmentTextures as aK, acquireGPUTexture as aL, releaseGPUTexture as aM, loadBrdfImage as aN, parseEnvFile as aO, MSH_HAS_SKELETON as aP, MSH_HAS_SKELETON_8 as aQ, bumpVisibilityEpoch as aR, getViewProjectionMatrix as aS, PBR_HAS_CLEARCOAT as aT, PBR_HAS_SHEEN_TEXTURE as aU, PBR_HAS_SHEEN as aV, PBR_HAS_SHEEN_ALBEDO_SCALING as aW, PBR_HAS_THICKNESS_MAP as aX, PBR_HAS_SUBSURFACE as aY, U8C as aZ, setMaxLights as a_, StandardToneMapping as aa, clearPbrPipelineCache as ab, clearSamplerCache as ac, _computePbrMaterialFeatures as ad, _computeMeshFeatures as ae, getOrCreatePbrBindings as af, packMat4IntoF32 as ag, PBR2_HAS_REFRACTION as ah, createPbrMeshBindGroup as ai, collectPbrBoundTextures as aj, acquireTexture as ak, releaseTexture as al, getOrCreatePbrPipeline as am, TYPE_SIZES as an, initMeshTransform as ao, U32 as ap, resolveAccessor as aq, DV as ar, needsOrmComposite as as, anyPrimitive as at, mat4Determinant3 as au, uploadBaseColorFactorTexture as av, uploadOrmFactorTexture as aw, retain as ax, getBilinearSampler as ay, getPbrGroupBuilder as az, clearSceneBGLCache as b, createWorldMatrixState as b0, composeTrsLocalMatrix as b1, ObservableQuat as b2, createEulerProxy as b3, ObservableVec3 as b4, attachWorldMatrixState as b5, eulerToQuat as b6, CW as b7, getRenderTargetSize as b8, getViewMatrix as b9, getCameraPosition as bA, brdfLutWGSL as bB, identityTexWrap as bC, assembleMaterial as bD, applyGltfOptInPbrFeatures as bE, makeImageFetcher as bF, ConfigureCustomViewerElement as bG, CreateViewerForCanvas as bH, DefaultViewerOptions as bI, HTML3DAnnotationElement as bJ, HTML3DElement as bK, Viewer as bL, ViewerElement as bM, ViewerHotSpotResult as bN, directionalLight as bO, sceneRebuild as bP, hdrParser as bQ, hdrIblPipeline as bR, esmDirectionalShadowGenerator as bS, animationGroup as bT, getProjectionMatrix as ba, I8 as bb, I16 as bc, mat4Invert as bd, mat4Identity as be, getLoaderTmpAnim as bf, INTERP_CUBICSPLINE as bg, INTERP_STEP as bh, INTERP_LINEAR as bi, PATH_WEIGHTS as bj, PATH_SCALE as bk, PATH_ROTATION as bl, PATH_TRANSLATION as bm, findParent as bn, PATH_POINTER as bo, setThinInstances as bp, mat4ComposeInto as bq, _registerPbrSceneHook as br, createRenderTarget as bs, drawList as bt, _vis as bu, biasedMipLevelCount as bv, PBR_HAS_USE_ALPHA_ONLY_MR as bw, createLightBase as bx, applyWorldMatrixAccessors as by, localMatrixFromDirection as bz, computeAabb as c, createEmptyUniformBuffer as d, ensureSceneLightState as e, createMappedBuffer as f, getSceneBindGroupLayout as g, _setShadowTaskInputPreloader as h, isRenderingContextRegistered as i, createUniformBuffer as j, SCENE_UBO_WGSL as k, createDefaultPipelineDescriptor as l, createSingleUniformBGL as m, SS as n, TU as o, U8 as p, getOrCreateSampler as q, retireGpuResources as r, appendMeshLightUboFields as s, targetSignatureKey as t, meshLightIndexWGSL as u, _getPbrExts as v, PBR_HAS_NORMAL_MAP as w, PBR2_HAS_UV2 as x, PBR2_HAS_UV_TRANSFORM as y, PBR_HAS_GAMMA_ALBEDO as z };
//# sourceMappingURL=index-DMbDahsc.esm.js.map