var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { LOD as ThreeLOD, Object3D } from "three";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { getParam } from "../engine/engine_utils.js";
import { Behaviour } from "./Component.js";
import { Renderer } from "./Renderer.js";
const debug = getParam("debuglods");
const noLods = getParam("nolods");
/**
 * Defines how LOD levels transition between each other
 */
var LODFadeMode;
(function (LODFadeMode) {
    /** Instant switch between LOD levels */
    LODFadeMode[LODFadeMode["None"] = 0] = "None";
    /** Smooth cross-fade transition between levels */
    LODFadeMode[LODFadeMode["CrossFade"] = 1] = "CrossFade";
    /** SpeedTree-style blending for vegetation */
    LODFadeMode[LODFadeMode["SpeedTree"] = 2] = "SpeedTree";
})(LODFadeMode || (LODFadeMode = {}));
/**
 * Defines a single LOD level with its transition distance and associated renderers.
 * Used by {@link LODGroup} to configure level of detail switching.
 */
export class LODModel {
    /** Screen height ratio (0-1) at which this LOD becomes active */
    screenRelativeTransitionHeight;
    /** Distance from camera at which this LOD becomes active */
    distance;
    /** Renderers to show at this LOD level */
    renderers;
}
__decorate([
    serializable()
], LODModel.prototype, "screenRelativeTransitionHeight", void 0);
__decorate([
    serializable()
], LODModel.prototype, "distance", void 0);
__decorate([
    serializable(Renderer)
], LODModel.prototype, "renderers", void 0);
class LOD {
    model;
    get renderers() { return this.model.renderers; }
    constructor(model) {
        this.model = model;
    }
}
/**
 * LODGroup manages multiple levels of detail for optimized rendering.
 * Objects switch between different detail levels based on distance from camera.
 *
 * LOD levels are defined in {@link LODModel} objects, each specifying:
 * - The distance at which that level becomes active
 * - The {@link Renderer} components to show at that level
 *
 * This is useful for performance optimization - showing high-detail models up close
 * and lower-detail versions at distance where the difference isn't visible.
 *
 * **Progressive Loading:**
 * For automatic texture/mesh LOD streaming, see the `@needle-tools/gltf-progressive` package
 * which provides progressive loading capabilities independent of this component.
 *
 * **Debug options:**
 * - `?debuglods` - Log LOD switching information
 * - `?nolods` - Disable LOD system entirely
 *
 * @summary Level of Detail Group for optimizing rendering
 * @category Rendering
 * @group Components
 * @see {@link LODModel} for configuring individual LOD levels
 * @see {@link Renderer} for the renderers controlled by LOD
 * @see {@link LODsManager} for programmatic control of progressive LODs
 * @link https://npmjs.com/package/@needle-tools/gltf-progressive
 */
export class LODGroup extends Behaviour {
    /** Array of LOD level configurations */
    lodModels = [];
    _lods = [];
    _settings = [];
    // https://threejs.org/docs/#api/en/objects/LOD
    _lodsHandler;
    start() {
        if (debug)
            console.log("LODGROUP", this.name, this.lodModels, this);
        if (noLods)
            return;
        if (this._lodsHandler)
            return;
        if (!this.gameObject)
            return;
        if (this.lodModels && Array.isArray(this.lodModels)) {
            const renderers = [];
            for (const model of this.lodModels) {
                const lod = new LOD(model);
                this._lods.push(lod);
                for (const rend of lod.renderers) {
                    if (!renderers.includes(rend))
                        renderers.push(rend);
                }
            }
            this._lodsHandler = new Array();
            for (let i = 0; i < renderers.length; i++) {
                const handler = new ThreeLOD();
                this._lodsHandler.push(handler);
                this.gameObject.add(handler);
            }
            const empty = new Object3D();
            empty.name = "Cull " + this.name;
            for (let i = 0; i < renderers.length; i++) {
                const rend = renderers[i];
                const handler = this._lodsHandler[i];
                const obj = rend.gameObject;
                if (debug)
                    console.log(i, obj.name);
                for (const lod of this._lods) {
                    const dist = lod.model.distance;
                    // get object to be lodded, it can be empty
                    let object = null;
                    if (lod.renderers.includes(rend)) {
                        object = obj;
                    }
                    else {
                        object = empty;
                    }
                    if (object.type === "Group") {
                        console.warn(`LODGroup ${this.name}: Group or MultiMaterial object's are not supported as LOD object: ${object.name}`);
                        continue;
                    }
                    if (debug)
                        console.log("LEVEL", object.name, dist);
                    handler.autoUpdate = false;
                    this.onAddLodLevel(handler, object, lod.model.distance);
                }
            }
        }
    }
    onAfterRender() {
        if (!this.gameObject)
            return;
        if (!this._lodsHandler)
            return;
        const cam = this.context.mainCamera;
        if (!cam)
            return;
        for (const h of this._lodsHandler) {
            h.update(cam);
            const levelIndex = h.getCurrentLevel();
            const level = h.levels[levelIndex];
            h.layers.mask = level.object.layers.mask;
        }
    }
    onAddLodLevel(lod, obj, dist) {
        if (obj === this.gameObject) {
            console.warn("LODGroup component must be on parent object and not mesh directly at the moment", obj.name, obj);
            return;
        }
        lod.addLevel(obj, dist * this._distanceFactor, .01);
        const setting = { lod: lod, levelIndex: lod.levels.length - 1, distance: dist };
        this._settings.push(setting);
    }
    _distanceFactor = 1;
    /**
     * Adjusts all LOD transition distances by a multiplier.
     * Values > 1 push LOD transitions further away (higher quality at distance).
     * Values < 1 bring transitions closer (better performance).
     * @param factor Multiplier to apply to all LOD distances
     */
    distanceFactor(factor) {
        if (factor === this._distanceFactor)
            return;
        this._distanceFactor = factor;
        for (const setting of this._settings) {
            const level = setting.lod.levels[setting.levelIndex];
            level.distance = setting.distance * factor;
        }
    }
}
__decorate([
    serializable(LODModel)
], LODGroup.prototype, "lodModels", void 0);
//# sourceMappingURL=LODGroup.js.map