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 { Object3D, Vector3 } from "three";
import { Gizmos } from "../engine/engine_gizmos.js";
import { Mathf } from "../engine/engine_math.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { getTempVector } from "../engine/engine_three_utils.js";
import { getParam } from "../engine/engine_utils.js";
import { Behaviour } from "./Component.js";
import { USDZExporter } from "./export/usdz/USDZExporter.js";
import { Renderer } from "./Renderer.js";
const debugSeeThrough = getParam("debugseethrough");
let i = 0;
/**
 * Makes the object fade out when it is obscuring the reference point from the camera. This component can be put on any object in the scene. It will affect all Renderer components on the same object and child objects.
 *
 * Useful for e.g. making walls transparent when the camera is outside or hiding object's that would otherwise block the view.
 *
 * Requires a Renderer component on the same object or a child object.
 *
 * - Example https://see-through-walls-z23hmxbz1kjfjn.needle.run/
 */
export class SeeThrough extends Behaviour {
    /**
     * Assign a reference point - if this point will be obscured from the camera by this object then this object will fade out.
     * If no reference point is assigned the scene's root object will be used as reference point.
     */
    referencePoint = null;
    /**
     * Fade Duration in seconds
     * @default 0.05
    */
    fadeDuration = .05;
    /**
     * Minimum alpha value when fading out (0-1)
     * @default 0
     */
    minAlpha = 0;
    /**
     * When useAlphaHash is enabled the object will fade out using alpha hashing, this means the object can stay opaque. If disabled the object will set to be transparent when fading out.
     * @default true
     */
    useAlphaHash = true;
    /**
     * Set this to force updating the reference point position and direction
     */
    set needsUpdate(val) {
        this._needsUpdate = val;
    }
    get needsUpdate() {
        return this._needsUpdate;
    }
    /**
     * Override the alpha value, -1 means no override
     * @default -1
     */
    overrideAlpha = -1;
    /**
     *
     */
    autoUpdate = true;
    _referencePointVector = new Vector3();
    _referencePointDir = new Vector3();
    _distance = 0;
    _renderer = null;
    _needsUpdate = true;
    _id = i++;
    /** * @internal */
    onEnable() {
        this._needsUpdate = true;
        this._renderer = null;
        SeeThroughUsdzExporterPlugin.components.push(this);
    }
    /**  @internal */
    onDisable() {
        this._renderer?.forEach(r => {
            const original = this.rendererMaterialsOriginal.get(r);
            for (let i = 0; i < r.sharedMaterials.length; i++) {
                const mat = r.sharedMaterials[i];
                if (!mat)
                    continue;
                if (original && original[i]) {
                    r.sharedMaterials[i] = original[i];
                }
            }
            this.rendererMaterials.delete(r);
            this.rendererMaterialsOriginal.delete(r);
        });
        const index = SeeThroughUsdzExporterPlugin.components.indexOf(this);
        if (index !== -1)
            SeeThroughUsdzExporterPlugin.components.splice(index, 1);
    }
    /**
     * @internal
     */
    update() {
        if (this._needsUpdate) {
            this._needsUpdate = false;
            this._renderer = this.gameObject.getComponentsInChildren(Renderer);
            // NOTE: instead of using the object's anchor (gameObject.worldPosition) we could also get the object's bounding box center:
            // getBoundingBox(this.gameObject); // < import { getBoundingBox } from "@needle-tools/engine";
            this.updateDirection();
        }
        else if (this.autoUpdate && (this.context.time.frame + this._id) % 20 === 0) {
            this.updateDirection();
        }
        if (!this.autoUpdate)
            return;
        if (!this.referencePoint)
            return;
        const dot = this._referencePointDir.dot(this.context.mainCamera.worldForward);
        const shouldHide = dot > .2;
        if (debugSeeThrough && this.referencePoint) {
            const wp = this.gameObject.worldPosition;
            Gizmos.DrawArrow(getTempVector(wp), wp.sub(this._referencePointDir), shouldHide ? 0xFF0000 : 0x00FF00);
            Gizmos.DrawWireSphere(this.referencePoint.worldPosition, .05, 0x0000FF);
        }
        if (shouldHide) {
            this.updateAlpha(this.minAlpha, this.fadeDuration);
        }
        else {
            this.updateAlpha(1, this.fadeDuration);
        }
    }
    rendererMaterials = new WeakMap();
    rendererMaterialsOriginal = new WeakMap();
    updateDirection() {
        this.referencePoint ??= this.context.scene;
        this._referencePointVector.copy(this.gameObject.worldPosition.sub(this.referencePoint.worldPosition));
        this._distance = this._referencePointVector.length();
        this._referencePointDir.copy(this._referencePointVector)
            .multiply(getTempVector(1, .5, 1)) // Reduce vertical influence
            .normalize();
    }
    /**
     * Update the alpha of the object's materials towards the target alpha over the given duration.
     * @param targetAlpha Target alpha value (0-1)
     * @param duration Duration in seconds to reach the target alpha. 0 means immediate. Default is the component's fadeDuration.
     */
    updateAlpha(targetAlpha, duration = this.fadeDuration) {
        if (this.overrideAlpha !== undefined && this.overrideAlpha !== -1) {
            targetAlpha = this.overrideAlpha;
        }
        this._renderer?.forEach(renderer => {
            if (targetAlpha < .9) {
                renderer.gameObject.raycastAllowed = false;
            }
            else {
                renderer.gameObject.raycastAllowed = true;
            }
            if (!this.rendererMaterials.has(renderer)) {
                const originalMaterials = new Array();
                const clonedMaterials = new Array();
                // We clone the materials once and store them, so we can modify the opacity without affecting other objects using the same material. This could potentially be optimized further to re-use materials between renderers if multiple renderers use the same material.
                for (let i = 0; i < renderer.sharedMaterials.length; i++) {
                    const mat = renderer.sharedMaterials[i];
                    if (!mat)
                        continue;
                    originalMaterials.push(mat);
                    const matClone = mat.clone();
                    // @ts-ignore
                    matClone.userData = mat.userData || {};
                    matClone.userData.seeThrough = {
                        initial: {
                            opacity: matClone.opacity,
                            transparent: matClone.transparent,
                            alphaHash: matClone.alphaHash
                        }
                    };
                    clonedMaterials.push(matClone);
                    renderer.sharedMaterials[i] = matClone;
                }
                this.rendererMaterials.set(renderer, clonedMaterials);
                this.rendererMaterialsOriginal.set(renderer, originalMaterials);
            }
            const materials = renderer.hasLightmap ? renderer.sharedMaterials : this.rendererMaterials.get(renderer);
            if (!materials)
                return;
            for (const mat of materials) {
                if (!mat)
                    continue;
                let newAlpha = Mathf.lerp(mat.opacity, targetAlpha, duration <= 0 ? 1 : this.context.time.deltaTime / duration);
                ;
                if (newAlpha >= 0.99)
                    newAlpha = 1;
                else if (newAlpha <= 0.01)
                    newAlpha = 0;
                const wasTransparent = mat.transparent;
                const wasAlphaHash = mat.alphaHash;
                const previousOpacity = mat.opacity;
                mat.alphaHash = this.useAlphaHash;
                if (mat.userData && "seeThrough" in mat.userData) {
                    const initial = mat.userData.seeThrough.initial;
                    mat.opacity = initial.opacity * newAlpha;
                    mat.transparent = mat.opacity >= 1 ? initial.transparent : !this.useAlphaHash;
                }
                else {
                    mat.transparent = mat.opacity >= 1 ? false : !this.useAlphaHash;
                }
                if (wasTransparent !== mat.transparent
                    || wasAlphaHash !== mat.alphaHash
                    || mat.opacity !== previousOpacity // MeshPhysicsMaterial needs that and maybe other materials too...
                ) {
                    mat.needsUpdate = true;
                }
            }
        });
    }
}
__decorate([
    serializable(Object3D)
], SeeThrough.prototype, "referencePoint", void 0);
__decorate([
    serializable()
], SeeThrough.prototype, "fadeDuration", void 0);
__decorate([
    serializable()
], SeeThrough.prototype, "minAlpha", void 0);
__decorate([
    serializable()
], SeeThrough.prototype, "useAlphaHash", void 0);
__decorate([
    serializable()
], SeeThrough.prototype, "overrideAlpha", void 0);
__decorate([
    serializable()
], SeeThrough.prototype, "autoUpdate", void 0);
;
class SeeThroughUsdzExporterPlugin {
    static components = [];
    get extensionName() {
        return "SeeThrough";
    }
}
const seeThroughUsdzExporterPlugin = new SeeThroughUsdzExporterPlugin();
USDZExporter.beforeExport.addEventListener(args => {
    if (SeeThroughUsdzExporterPlugin.components.length === 0)
        return;
    if (args.exporter.extensions.includes(seeThroughUsdzExporterPlugin) === false) {
        args.exporter.extensions.push(seeThroughUsdzExporterPlugin);
    }
});
//# sourceMappingURL=SeeThrough.js.map