import { halton23, Matrix4x4, Vector4, DRef, Vector2, Vector3, Ray, Frustum } from '@zephyr3d/base';
import { SceneNode } from '../scene/scene_node.js';
import { Compositor } from '../posteffect/compositor.js';
import { TAA } from '../posteffect/taa.js';
import { SSR } from '../posteffect/ssr.js';
import { Tonemap } from '../posteffect/tonemap.js';
import { FXAA } from '../posteffect/fxaa.js';
import { Bloom } from '../posteffect/bloom.js';
import { SAO } from '../posteffect/sao.js';
import { MotionBlur } from '../posteffect/motionblur.js';
import { ColorAdjust } from '../posteffect/coloradjust.js';
import { getDevice } from '../app/api.js';
import { ScreenAdapter } from '../app/screen.js';
import { ABufferOIT } from '../render/abuffer_oit.js';
import { WeightedBlendedOIT } from '../render/weightedblended_oit.js';

/**
 * A renderable camera node that manages view/projection math, frusta,
 * input control, picking, and a post-processing chain via a compositor.
 *
 * Key features:
 * - Maintains projection, view, VP, and inverse VP matrices and lazily recomputes them when invalidated.
 * - Provides world- and view-space frusta for culling and clipping.
 * - Supports perspective and orthographic projections.
 * - Integrates with post effects (Tonemap, FXAA, TAA, Bloom, SSR, SSAO, Motion Blur) through an internal `Compositor`.
 * - Handles temporal jitter and history state when TAA or motion blur are enabled.
 * - Emits picking rays from screen coordinates and supports async GPU picking.
 * - Optional controller integration for user input handling.
 *
 * Performance notes:
 * - Matrices/frusta are computed on demand and cached until invalidation.
 * - Temporal jitter and history are set up only when required by enabled features and device support.
 *
 * @public
 */ class Camera extends SceneNode {
    /** @internal Halton 2-3 sequence used for TAA jittering. */ static _halton23 = halton23(16);
    /** @internal Per-camera history resource manager. */ static _historyResourceManager = new WeakMap();
    /** @internal Screen adapter for this camera */ _screenAdapter;
    /** @internal Whether the camera is adapted */ _adapted;
    /** @internal Adapted viewport */ _adaptedViewport;
    /** @internal Adapted relative viewport */ _adaptedRelativeViewport;
    /** @internal RenderTarget version */ _adaptedVersion;
    /** @internal Projection matrix. */ _projMatrix;
    /** @internal Inverse projection matrix. */ _invProjMatrix;
    /** @internal View matrix (world -\> camera). */ _viewMatrix;
    /** @internal View-projection matrix. */ _viewProjMatrix;
    /** @internal Inverse view-projection matrix. */ _invViewProjMatrix;
    /** @internal Framebuffer clear color, disabled when null. Default is null */ _clearColor;
    /** @internal Framebuffer depth clear value, disabled when null. Default is 1 */ _clearDepth;
    /** @internal Framebuffer stencil clear value, disabled when null. Default is 0 */ _clearStencil;
    /** @internal Optional clip plane in camera space. */ _clipPlane;
    /** @internal Camera controller (input). */ _controller;
    /** @internal World-space frustum (from VP). */ _frustum;
    /** @internal View-space frustum (from P). */ _frustumV;
    /** @internal Dirty flag indicating derived matrices/frusta need recompute. */ _dirty;
    /** @internal Viewport [x, y, w, h]; null uses full framebuffer. */ _viewport;
    /** @internal Scissor rectangle [x, y, w, h]; null uses viewport. */ _scissor;
    /** @internal Clip plane mask for custom clipping schemes. */ _clipMask;
    /** @internal Order-Independent Transparency reference. */ _oit;
    /** @internal OIT algorithm selection mode. */ _oitMode;
    /** @internal ABuffer OIT layer budget. */ _oitABufferLayers;
    /** @internal Whether to perform a depth pre-pass. */ _depthPrePass;
    /** @internal Whether command buffers may be reused for optimization. */ _commandBufferReuse;
    /** @internal Hi-Z acceleration enable (primarily for SSR). */ _HiZ;
    /** @internal If true, a float point backbuffer will be used. The default value is true */ _HDR;
    /** @internal Tonemap enable flag (via post effect). */ _toneMap;
    /** @internal Tonemap post effect reference. */ _postEffectTonemap;
    /** @internal Tonemap exposure. */ _tonemapExposure;
    /** @internal Motion blur enable flag (via post effect). */ _motionBlur;
    /** @internal Motion blur post effect reference. */ _postEffectMotionBlur;
    /** @internal Motion blur strength. */ _motionBlurStrength;
    /** @internal Bloom enable flag (via post effect). */ _bloom;
    /** @internal Bloom post effect reference. */ _postEffectBloom;
    /** @internal Bloom downsample level cap. */ _bloomMaxDownsampleLevels;
    /** @internal Bloom downsample resolution limit. */ _bloomDownsampleLimit;
    /** @internal Bloom threshold. */ _bloomThreshold;
    /** @internal Bloom threshold knee (soft thresholding). */ _bloomThresholdKnee;
    /** @internal Bloom intensity. */ _bloomIntensity;
    /** @internal Color adjustment enable flag (via post effect). */ _colorAdjustEnabled;
    /** @internal Color adjustment post effect reference. */ _postEffectColorAdjust;
    /** @internal Color adjustment saturation. */ _colorAdjustSaturation;
    /** @internal Color adjustment contrast. */ _colorAdjustContrast;
    /** @internal Color adjustment hue (degrees). */ _colorAdjustHue;
    /** @internal Color adjustment sharpen amount. */ _colorAdjustSharpen;
    /** @internal FXAA enable flag (via post effect). */ _FXAA;
    /** @internal FXAA post effect reference. */ _postEffectFXAA;
    /** @internal TAA enable flag (via post effect). */ _TAA;
    /** @internal TAA post effect reference. */ _postEffectTAA;
    /** @internal TAA debug mode (implementation-defined). */ _TAADebug;
    /** @internal SSR enable flag (via post effect). */ _SSR;
    /** @internal SSR post effect reference. */ _postEffectSSR;
    /** @internal SSR parameter vector: (maxDistance, iterations, thickness, reserved). */ _ssrParams;
    /** @internal SSR roughness cutoff; above this SSR is suppressed. */ _ssrMaxRoughness;
    /** @internal SSR roughness factor scaling. */ _ssrRoughnessFactor;
    /** @internal SSR stride for ray marching. */ _ssrStride;
    /** @internal Whether SSR thickness is computed automatically. */ _ssrCalcThickness;
    /** @internal SSR blur scale. */ _ssrBlurriness;
    /** @internal SSR blur depth cutoff. */ _ssrBlurDepthCutoff;
    /** @internal SSR blur kernel size. */ _ssrBlurKernelSize;
    /** @internal SSR Gaussian blur standard deviation. */ _ssrBlurStdDev;
    /** @internal SSAO enable flag (via post effect). */ _SSAO;
    /** @internal SSAO post effect reference. */ _postEffectSSAO;
    /** @internal SSAO scale (sampling radius multiplier). */ _SSAOScale;
    /** @internal SSAO bias (self-shadowing reduction). */ _SSAOBias;
    /** @internal SSAO sample radius. */ _SSAORadius;
    /** @internal SSAO intensity. */ _SSAOIntensity;
    /** @internal SSAO blur depth cutoff. */ _SSAOBlurDepthCutoff;
    /** @internal Pending GPU-pick promise (one-shot). */ _pickResultPromise;
    /** @internal Resolver for the pending pick promise. */ _pickResultResolve;
    /** @internal Last pick X position (viewport-relative). */ _pickPosX;
    /** @internal Last pick Y position (viewport-relative). */ _pickPosY;
    /** @internal Last resolved pick result (optional cache). */ _pickResult;
    /** @internal Current jitter value in clip space (x, y). */ _jitterValue;
    /** @internal Previous frame’s jitter value. */ _prevJitterValue;
    /** @internal Current jittered VP matrix. */ _jitteredVPMatrix;
    /** @internal Inverse of the current jittered VP matrix. */ _jitteredInvVPMatrix;
    /** @internal Previous frame’s non-jittered VP matrix. */ _prevVPMatrix;
    /** @internal Previous frame’s camera world position. */ _prevPosition;
    /** @internal Previous frame’s jittered VP matrix. */ _prevJitteredVPMatrix;
    /** @internal Post-processing compositor attached to this camera. */ _compositor;
    /** @internal Pointer interaction rectangle in css pixels (relative to canvas) */ _interactionRect;
    /** @internal captured by which mouse button (-1 if not captured) */ _capturedButton;
    /**
   * Creates a new camera node.
   *
   * Initializes projection/view matrices, temporal fields, controller linkage, and
   * builds the default post-processing pipeline on the internal compositor.
   *
   * @param scene - The scene that owns this camera.
   * @param projectionMatrix - Optional projection matrix to initialize with.
   */ constructor(scene, projectionMatrix){
        super(scene);
        this._projMatrix = projectionMatrix || Matrix4x4.identity();
        this._invProjMatrix = Matrix4x4.invert(this._projMatrix);
        this._viewMatrix = Matrix4x4.identity();
        this._viewProjMatrix = Matrix4x4.identity();
        this._invViewProjMatrix = Matrix4x4.identity();
        this._clipPlane = null;
        this._clearColor = new Vector4(0, 0, 0, 1);
        this._clearDepth = 1;
        this._clearStencil = 0;
        this._dirty = true;
        this._controller = null;
        this._viewport = null;
        this._adaptedViewport = null;
        this._adaptedRelativeViewport = null;
        this._adaptedVersion = 0;
        this._scissor = null;
        this._clipMask = 0;
        this._frustum = null;
        this._frustumV = null;
        this._oit = new DRef();
        this._oitMode = 'none';
        this._oitABufferLayers = 20;
        this._depthPrePass = false;
        this._screenAdapter = new ScreenAdapter();
        this._adapted = false;
        this._HiZ = false;
        this._HDR = true;
        this._toneMap = true;
        this._postEffectTonemap = new DRef();
        this._tonemapExposure = 1;
        this._motionBlur = false;
        this._postEffectMotionBlur = new DRef();
        this._motionBlurStrength = 1;
        this._bloom = false;
        this._postEffectBloom = new DRef();
        this._bloomMaxDownsampleLevels = 4;
        this._bloomDownsampleLimit = 32;
        this._bloomThreshold = 0.8;
        this._bloomThresholdKnee = 0;
        this._bloomIntensity = 1;
        this._colorAdjustEnabled = false;
        this._postEffectColorAdjust = new DRef();
        this._colorAdjustSaturation = 1;
        this._colorAdjustContrast = 1;
        this._colorAdjustHue = 0;
        this._colorAdjustSharpen = 0;
        this._FXAA = false;
        this._postEffectFXAA = new DRef();
        this._TAA = false;
        this._postEffectTAA = new DRef();
        this._TAADebug = 0;
        this._SSR = false;
        this._postEffectSSR = new DRef();
        this._ssrParams = new Vector4(100, 120, 0.5, 0);
        this._ssrMaxRoughness = 0.8;
        this._ssrRoughnessFactor = 1;
        this._ssrStride = 2;
        this._ssrCalcThickness = false;
        this._ssrBlurriness = 0.01;
        this._ssrBlurDepthCutoff = 2;
        this._ssrBlurKernelSize = 10;
        this._ssrBlurStdDev = 10;
        this._SSAO = false;
        this._postEffectSSAO = new DRef();
        this._SSAOScale = 10;
        this._SSAOBias = 1;
        this._SSAOIntensity = 0.025;
        this._SSAORadius = 100;
        this._SSAOBlurDepthCutoff = 2;
        this._pickResult = null;
        this._commandBufferReuse = true;
        this._jitteredVPMatrix = new Matrix4x4();
        this._jitteredInvVPMatrix = new Matrix4x4();
        this._jitterValue = new Vector2(0, 0);
        this._prevVPMatrix = null;
        this._prevPosition = null;
        this._prevJitteredVPMatrix = null;
        this._prevJitterValue = null;
        this._pickResultPromise = null;
        this._pickResultResolve = null;
        this._pickPosX = 0;
        this._pickPosY = 0;
        this._compositor = new Compositor();
        this._capturedButton = -1;
        this._interactionRect = null;
        this.updatePostProcessing();
        if (scene && !scene.mainCamera) {
            scene.mainCamera = this;
        }
    }
    /**
   * The compositor that owns and runs the camera's post-processing chain.
   */ get compositor() {
        return this._compositor;
    }
    /**
   * Pointer interaction rectangle in css pixels (relative to canvas)
   */ get interactionRect() {
        return this._interactionRect;
    }
    set interactionRect(rect) {
        this._interactionRect = rect;
    }
    /**
   * Framebuffer clear color, or `null` to disable.
   */ get clearColor() {
        return this._clearColor;
    }
    set clearColor(v) {
        this._clearColor = v?.clone() ?? null;
    }
    /**
   * Framebuffer stencil clear value, disabled when null. Default is 0.
   */ get clearDepth() {
        return this._clearDepth;
    }
    set clearDepth(v) {
        this._clearDepth = v;
    }
    /**
   * Framebuffer stencil clear value, disabled when null. Default is 0.
   */ get clearStencil() {
        return this._clearStencil;
    }
    set clearStencil(v) {
        this._clearStencil = v;
    }
    /**
   * Whether Hi-Z acceleration is enabled.
   *
   * Often improves SSR performance with little quality impact when supported.
   */ get HiZ() {
        return this._HiZ;
    }
    set HiZ(val) {
        this._HiZ = !!val;
    }
    /**
   * Whether HDR backbuffer is enabled.
   *
   * Tonemap should be disabled when not using HDR backbuffer.
   */ get HDR() {
        return this._HDR;
    }
    set HDR(val) {
        this._HDR = !!val;
    }
    /**
   * Whether tonemapping is enabled via the post effect.
   */ get toneMap() {
        return this._postEffectTonemap.get().enabled;
    }
    set toneMap(val) {
        this._postEffectTonemap.get().enabled = !!val;
    }
    /**
   * Whether motion blur is enabled via the post effect.
   */ get motionBlur() {
        return this._postEffectMotionBlur.get().enabled;
    }
    set motionBlur(val) {
        this._postEffectMotionBlur.get().enabled = !!val;
    }
    /** Motion blur strength */ get motionBlurStrength() {
        return this._motionBlurStrength;
    }
    set motionBlurStrength(val) {
        this._motionBlurStrength = val;
        if (this._postEffectMotionBlur.get()) {
            this._postEffectMotionBlur.get().strength = this._motionBlurStrength;
        }
    }
    /**
   * Gets whether Bloom is enabled.
   */ get bloom() {
        return this._postEffectBloom.get().enabled;
    }
    set bloom(val) {
        this._postEffectBloom.get().enabled = !!val;
    }
    /**
   * Maximum bloom downsample levels
   */ get bloomMaxDownsampleLevels() {
        return this._bloomMaxDownsampleLevels;
    }
    set bloomMaxDownsampleLevels(val) {
        this._bloomMaxDownsampleLevels = val;
        if (this._postEffectBloom.get()) {
            this._postEffectBloom.get().maxDownsampleLevel = val;
        }
    }
    /**
   * Bloom downsample limit
   */ get bloomDownsampleLimit() {
        return this._bloomDownsampleLimit;
    }
    set bloomDownsampleLimit(val) {
        this._bloomDownsampleLimit = val;
        if (this._postEffectBloom.get()) {
            this._postEffectBloom.get().downsampleLimit = val;
        }
    }
    /**
   * Bloom threshold
   */ get bloomThreshold() {
        return this._bloomThreshold;
    }
    set bloomThreshold(val) {
        this._bloomThreshold = val;
        if (this._postEffectBloom.get()) {
            this._postEffectBloom.get().threshold = val;
        }
    }
    /**
   * Bloom threshold knee
   */ get bloomThresholdKnee() {
        return this._bloomThresholdKnee;
    }
    set bloomThresholdKnee(val) {
        this._bloomThresholdKnee = val;
        if (this._postEffectBloom.get()) {
            this._postEffectBloom.get().thresholdKnee = val;
        }
    }
    /**
   * Bloom intensity
   */ get bloomIntensity() {
        return this._bloomIntensity;
    }
    set bloomIntensity(val) {
        this._bloomIntensity = val;
        if (this._postEffectBloom.get()) {
            this._postEffectBloom.get().intensity = val;
        }
    }
    /** Whether color adjustment is enabled. */ get colorAdjust() {
        return this._postEffectColorAdjust.get().enabled;
    }
    set colorAdjust(val) {
        this._postEffectColorAdjust.get().enabled = !!val;
    }
    /** Color adjustment saturation, 1 means unchanged. */ get colorAdjustSaturation() {
        return this._colorAdjustSaturation;
    }
    set colorAdjustSaturation(val) {
        this._colorAdjustSaturation = val;
        if (this._postEffectColorAdjust.get()) {
            this._postEffectColorAdjust.get().saturation = val;
        }
    }
    /** Color adjustment contrast, 1 means unchanged. */ get colorAdjustContrast() {
        return this._colorAdjustContrast;
    }
    set colorAdjustContrast(val) {
        this._colorAdjustContrast = val;
        if (this._postEffectColorAdjust.get()) {
            this._postEffectColorAdjust.get().contrast = val;
        }
    }
    /** Color adjustment hue in degrees. */ get colorAdjustHue() {
        return this._colorAdjustHue;
    }
    set colorAdjustHue(val) {
        this._colorAdjustHue = val;
        if (this._postEffectColorAdjust.get()) {
            this._postEffectColorAdjust.get().hue = val;
        }
    }
    /** Color adjustment sharpen amount, 0 means disabled. */ get colorAdjustSharpen() {
        return this._colorAdjustSharpen;
    }
    set colorAdjustSharpen(val) {
        this._colorAdjustSharpen = val;
        if (this._postEffectColorAdjust.get()) {
            this._postEffectColorAdjust.get().sharpen = val;
        }
    }
    /**
   * Gets whether FXAA is enabled.
   */ get FXAA() {
        return this._postEffectFXAA.get().enabled;
    }
    set FXAA(val) {
        this._postEffectFXAA.get().enabled = !!val;
    }
    /**
   * Tonemap exposure
   */ get toneMapExposure() {
        return this._tonemapExposure;
    }
    set toneMapExposure(val) {
        this._tonemapExposure = val;
        if (this._postEffectTonemap.get()) {
            this._postEffectTonemap.get().exposure = val;
        }
    }
    /**
   * Gets whether TAA is enabled.
   */ get TAA() {
        return this._postEffectTAA.get().enabled;
    }
    set TAA(val) {
        this._postEffectTAA.get().enabled = !!val;
    }
    /**
   * Gets the debug flag for TAA
   */ get TAADebug() {
        return this._TAADebug;
    }
    set TAADebug(val) {
        this._TAADebug = val;
    }
    /**
   * Gets whether Screen Space Reflections (SSR) is enabled.
   */ get SSR() {
        return this._postEffectSSR.get().enabled;
    }
    set SSR(val) {
        this._postEffectSSR.get().enabled = !!val;
    }
    /**
   * Gets the maximum roughness value for screen space reflections.
   * Controls the cutoff point where surfaces are considered too rough for SSR.
   */ get ssrMaxRoughness() {
        return this._ssrMaxRoughness;
    }
    set ssrMaxRoughness(val) {
        this._ssrMaxRoughness = val;
    }
    /**
   * Gets the roughness factor for SSR calculations.
   * Affects how surface roughness influences reflection clarity.
   */ get ssrRoughnessFactor() {
        return this._ssrRoughnessFactor;
    }
    set ssrRoughnessFactor(val) {
        this._ssrRoughnessFactor = val;
    }
    /**
   * Gets the stride value for SSR ray marching.
   * Controls the step size during ray marching. Larger values improve performance but may miss details.
   */ get ssrStride() {
        return this._ssrStride;
    }
    set ssrStride(val) {
        this._ssrStride = val;
    }
    /**
   * Gets the maximum distance for SSR ray marching.
   * Defines how far rays will travel when searching for reflection intersections.
   */ get ssrMaxDistance() {
        return this._ssrParams.x;
    }
    set ssrMaxDistance(val) {
        this._ssrParams.x = val;
    }
    /**
   * Gets the number of iterations for SSR ray marching.
   * Higher values provide more accurate reflections but impact performance.
   */ get ssrIterations() {
        return this._ssrParams.y;
    }
    set ssrIterations(val) {
        this._ssrParams.y = val;
    }
    /**
   * Gets the thickness value for SSR calculations.
   * Determines the thickness threshold for surfaces when calculating reflections.
   */ get ssrThickness() {
        return this._ssrParams.z;
    }
    set ssrThickness(val) {
        this._ssrParams.z = val;
    }
    /**
   * Gets whether SSR should calculate thickness automatically.
   * When enabled, the system will dynamically compute surface thickness for reflections.
   */ get ssrCalcThickness() {
        return this._ssrCalcThickness;
    }
    set ssrCalcThickness(val) {
        this._ssrCalcThickness = !!val;
    }
    /**
   * Gets the blur scale factor for SSR.
   * Controls the overall intensity of the blur effect applied to reflections.
   */ get ssrBlurScale() {
        return this._ssrBlurriness;
    }
    set ssrBlurScale(val) {
        this._ssrBlurriness = val;
    }
    /**
   * Gets the depth cutoff value for SSR blur.
   * Determines at what depth difference the blur effect should be reduced or eliminated.
   */ get ssrBlurDepthCutoff() {
        return this._ssrBlurDepthCutoff;
    }
    set ssrBlurDepthCutoff(val) {
        this._ssrBlurDepthCutoff = val;
    }
    /**
   * Gets the kernel size for the SSR blur effect.
   * Defines the size of the blur kernel. Larger values create softer, more spread-out blur.
   */ get ssrBlurKernelSize() {
        return this._ssrBlurKernelSize;
    }
    set ssrBlurKernelSize(val) {
        this._ssrBlurKernelSize = val;
    }
    /**
   * Gets the standard deviation for the SSR Gaussian blur.
   * Controls the distribution of the blur effect. Higher values create more pronounced blur.
   */ get ssrBlurStdDev() {
        return this._ssrBlurStdDev;
    }
    set ssrBlurStdDev(val) {
        this._ssrBlurStdDev = val;
    }
    /** @internal */ get ssrParams() {
        return this._ssrParams;
    }
    /**
   * Gets whether SSAO is enabled.
   */ get SSAO() {
        return this._postEffectSSAO.get().enabled;
    }
    set SSAO(val) {
        this._postEffectSSAO.get().enabled = !!val;
    }
    /** SSAO scale */ get SSAOScale() {
        return this._SSAOScale;
    }
    set SSAOScale(val) {
        this._SSAOScale = val;
        if (this._postEffectSSAO.get()) {
            this._postEffectSSAO.get().scale = val;
        }
    }
    /** SSAO bias */ get SSAOBias() {
        return this._SSAOBias;
    }
    set SSAOBias(val) {
        this._SSAOBias = val;
        if (this._postEffectSSAO.get()) {
            this._postEffectSSAO.get().bias = val;
        }
    }
    /** SSAO radius */ get SSAORadius() {
        return this._SSAORadius;
    }
    set SSAORadius(val) {
        this._SSAORadius = val;
        if (this._postEffectSSAO.get()) {
            this._postEffectSSAO.get().radius = val;
        }
    }
    /** SSAO intensity */ get SSAOIntensity() {
        return this._SSAOIntensity;
    }
    set SSAOIntensity(val) {
        this._SSAOIntensity = val;
        if (this._postEffectSSAO.get()) {
            this._postEffectSSAO.get().intensity = val;
        }
    }
    /** SSAO depth cutoff */ get SSAOBlurDepthCutoff() {
        return this._SSAOBlurDepthCutoff;
    }
    set SSAOBlurDepthCutoff(val) {
        this._SSAOBlurDepthCutoff = val;
        if (this._postEffectSSAO.get()) {
            this._postEffectSSAO.get().blurDepthCutoff = val;
        }
    }
    /** Whether to perform a depth pass */ get depthPrePass() {
        return this._depthPrePass;
    }
    set depthPrePass(val) {
        this._depthPrePass = !!val;
    }
    /** Whether to allow command buffer reuse optimization */ get commandBufferReuse() {
        return this._commandBufferReuse;
    }
    set commandBufferReuse(val) {
        this._commandBufferReuse = !!val;
    }
    /** Whether this camera is adapted to screen settings */ get adapted() {
        return this._adapted;
    }
    set adapted(val) {
        if (val !== this._adapted) {
            this._adapted = !!val;
            this._adaptedViewport = null;
            this._adaptedRelativeViewport = null;
            this._invalidate(true);
        }
    }
    /** OIT */ get oit() {
        return this._oit.get();
    }
    set oit(val) {
        this._oit.set(val);
        const inferredMode = this.inferOITMode(val);
        if (inferredMode) {
            this._oitMode = inferredMode;
        }
    }
    /** OIT mode */ get oitMode() {
        return this._oitMode;
    }
    set oitMode(val) {
        const mode = val ?? 'none';
        if (mode !== this._oitMode) {
            this._oitMode = mode;
            this._oit.set(this.createOITForMode(mode));
        }
    }
    /** ABuffer OIT layer budget. */ get oitABufferLayers() {
        return this._oitABufferLayers;
    }
    set oitABufferLayers(val) {
        const layers = Math.max(1, Math.floor(val || 0));
        if (layers !== this._oitABufferLayers) {
            this._oitABufferLayers = layers;
            if (this._oitMode === 'abuffer') {
                this._oit.set(this.createOITForMode('abuffer'));
            }
        }
    }
    /** Clip plane mask */ get clipMask() {
        return this._clipMask;
    }
    set clipMask(val) {
        this._clipMask = val;
    }
    /** Viewport used for rendering, if null, use full framebuffer size */ get viewport() {
        if (this._adapted) {
            if (!this._adaptedViewport) {
                this._adaptedViewport = this.calcAdaptedViewport(this._adaptedViewport);
            }
            return this._adaptedViewport;
        }
        return this._viewport;
    }
    set viewport(rect) {
        this._viewport = rect?.slice() ?? null;
    }
    /** Scissor rectangle used for rendering, if null, use viewport value */ get scissor() {
        return this._adapted ? this.viewport : this._scissor;
    }
    set scissor(rect) {
        this._scissor = rect?.slice() ?? null;
    }
    get relativeViewport() {
        if (this._adapted) {
            if (!this._adaptedRelativeViewport) {
                this._adaptedRelativeViewport = this.calcRelativeAdaptedViewport(this._adaptedRelativeViewport);
            }
            return this._adaptedRelativeViewport;
        }
        return this._viewport;
    }
    /**
   * Screen configuration used for adapting the camera viewport
   */ get screenConfig() {
        return this._screenAdapter.config;
    }
    set screenConfig(config) {
        this._screenAdapter.config = config;
    }
    /**
   * Screen viewport used for adapting the camera viewport
   */ get screenViewport() {
        return this._screenAdapter.viewport;
    }
    set screenViewport(viewport) {
        this._screenAdapter.viewport = viewport;
    }
    /**
   * Handle input events
   * @param ev - input event object
   * @param type - event type, default to ev.type
   * @returns Boolean value indicates whether the event was handled.
   */ handleEvent(ev, type) {
        let handled = false;
        if (this._controller) {
            if (this._capturedButton < 0 && (ev instanceof PointerEvent || ev instanceof WheelEvent) && !this.posInViewport(ev.offsetX, ev.offsetY)) {
                return false;
            }
            type = type ?? ev.type;
            if (type === 'pointerdown') {
                if (this._capturedButton < 0) {
                    this._capturedButton = ev.button;
                }
                handled = this._controller.onMouseDown(ev);
            } else if (type === 'pointerup') {
                handled = this._controller.onMouseUp(ev);
                if (this._capturedButton === ev.button) {
                    this._capturedButton = -1;
                }
            } else if (type === 'pointermove') {
                handled = this._controller.onMouseMove(ev);
            } else if (type === 'wheel') {
                handled = this._controller.onMouseWheel(ev);
            } else if (type === 'keydown') {
                handled = this._controller.onKeyDown(ev);
            } else if (type === 'keyup') {
                handled = this._controller.onKeyUp(ev);
            }
            if (handled && ev.preventDefault) {
                ev.preventDefault();
            }
        }
        return handled;
    }
    /**
   * Constructs a ray based on the given screen coordinates.
   *
   * @param x - The x-component of the screen coordinates, relative to the top-left corner of the viewport.
   * @param y - The y-component of the screen coordinates, relative to the top-left corner of the viewport.
   * @param viewportWidth - Optional width of the viewport for normalizing screen coordinates. If not provided, it will be derived from the camera's viewport or the device's viewport.
   * @param viewportHeight - Optional height of the viewport for normalizing screen coordinates. If not provided, it will be derived from the camera's viewport or the device's viewport.
   * @returns The ray originating from the camera position and passing through the given screen coordinates.
   */ constructRay(x, y, viewportWidth, viewportHeight) {
        const width = viewportWidth ?? (this.viewport ? this.viewport[2] : getDevice().getViewport().width);
        const height = viewportHeight ?? (this.viewport ? this.viewport[3] : getDevice().getViewport().height);
        const ndcX = 2 * x / width - 1;
        const ndcY = 1 - 2 * y / height;
        const nearClip = new Vector4(ndcX, ndcY, -1, 1);
        const farClip = new Vector4(ndcX, ndcY, 1, 1);
        const nearWorld = this.invViewProjectionMatrix.transform(nearClip);
        const farWorld = this.invViewProjectionMatrix.transform(farClip);
        if (this.isPerspective()) {
            nearWorld.scaleBy(1 / nearWorld.w);
            farWorld.scaleBy(1 / farWorld.w);
        }
        const vEye = this.isPerspective() ? this.getWorldPosition() : nearWorld.xyz();
        const vDir = Vector3.sub(farWorld.xyz(), vEye).inplaceNormalize();
        return new Ray(vEye, vDir);
    }
    /**
   * Place the camera by specifying the camera position and the target point
   * @param eye - The camera position
   * @param target - The target point to look at
   * @param up - The up vector
   * @returns self
   */ lookAt(eye, target, up) {
        return this.setLocalTransform(Matrix4x4.lookAt(eye, target, up));
    }
    /**
   * Place the camera to look at a given cube face at a given camera position
   * @param face - The cube face to look at
   * @param position - The camera position
   * @returns self
   */ lookAtCubeFace(face, position) {
        return this.setLocalTransform(Matrix4x4.lookAtCubeFace(face, position ?? this.position));
    }
    /**
   * Setup a perspective projection matrix for the camera
   * @param fovY - The vertical field of view in radians.
   * @param aspect - The aspect ratio
   * @param zNear - The near clip plane
   * @param zFar - The far clip plane
   * @returns self
   */ setPerspective(fovY, aspect, zNear, zFar) {
        this._projMatrix.perspective(fovY, aspect, zNear, zFar);
        Matrix4x4.invert(this._projMatrix, this._invProjMatrix);
        this._invalidate(true);
        return this;
    }
    /**
   * Setup a orthogonal projection matrix for the camera
   * @param left - Left bound of the frustum
   * @param right - Right bound of the frustum
   * @param bottom - Bottom bound of the frustum
   * @param top - Top bound of the frustum
   * @param near - Near bound of the frustum.
   * @param far - Far bound of the frustum.
   * @returns self
   */ setOrtho(left, right, bottom, top, near, far) {
        this._projMatrix.ortho(left, right, bottom, top, near, far);
        Matrix4x4.invert(this._projMatrix, this._invProjMatrix);
        this._invalidate(true);
        return this;
    }
    /**
   * Setup a projection matrix for the camera
   * @param matrix - The projection matrix
   */ setProjectionMatrix(matrix) {
        if (matrix && matrix !== this._projMatrix) {
            this._projMatrix = matrix;
            Matrix4x4.invert(this._projMatrix, this._invProjMatrix);
            this._invalidate(true);
        }
    }
    /**
   * Gets the projection matrix of the camera
   * @returns The projection matrix
   */ getProjectionMatrix() {
        if (this.dirtyCheck()) {
            this._compute();
        }
        return this._projMatrix;
    }
    /**
   * Gets the inverse projection matrix of the camera
   * @returns The projection matrix
   */ getInvProjectionMatrix() {
        if (this.dirtyCheck()) {
            this._compute();
        }
        return this._invProjMatrix;
    }
    getRotationMatrix() {
        const rotationMatrix = new Matrix4x4();
        this.worldMatrix.decompose(null, rotationMatrix, null);
        const xAxis = rotationMatrix.getRow(0).xyz().scaleBy(-1);
        const yAxis = rotationMatrix.getRow(1).xyz();
        const zAxis = rotationMatrix.getRow(2).xyz().scaleBy(-1);
        rotationMatrix.setRow(0, new Vector4(xAxis.x, xAxis.y, xAxis.z, 0));
        rotationMatrix.setRow(1, new Vector4(yAxis.x, yAxis.y, yAxis.z, 0));
        rotationMatrix.setRow(2, new Vector4(zAxis.x, zAxis.y, zAxis.z, 0));
        return rotationMatrix;
    }
    /**
   * View matrix of the camera
   *
   * @remarks
   * Camera's view matrix will transform a point from the world space to the camera space
   */ get viewMatrix() {
        if (this.dirtyCheck()) {
            this._compute();
        }
        return this._viewMatrix;
    }
    get viewProjectionMatrix() {
        if (this.dirtyCheck()) {
            this._compute();
        }
        return this._viewProjMatrix;
    }
    /**
   * The inverse-view-projection matrix of the camera
   *
   * @remarks
   * The inverse-view-projection matrix transforms a point from the clip space to the camera space
   */ get invViewProjectionMatrix() {
        if (this.dirtyCheck()) {
            this._compute();
        }
        return this._invViewProjMatrix;
    }
    /** Gets the frustum of the camera */ get frustum() {
        if (this.dirtyCheck()) {
            this._compute();
        }
        return this._frustum;
    }
    get frustumViewSpace() {
        if (this.dirtyCheck()) {
            this._compute();
        }
        if (!this._frustumV) {
            this._frustumV = new Frustum(this._projMatrix);
        }
        return this._frustumV;
    }
    /** The camera controller  */ get controller() {
        return this._controller;
    }
    set controller(controller) {
        this.setController(controller);
    }
    /** {@inheritDoc SceneNode.isCamera} */ isCamera() {
        return true;
    }
    /** Gets the near clip plane of the camera */ getNearPlane() {
        return this.getProjectionMatrix().getNearPlane();
    }
    /** Gets the far clip plane of the camera */ getFarPlane() {
        return this.getProjectionMatrix().getFarPlane();
    }
    /** Gets the vertical field of view of the camera */ getFOV() {
        return this.getProjectionMatrix().getFov();
    }
    /** Gets the tangent of half of the vertical field of view */ getTanHalfFovy() {
        return this.getProjectionMatrix().getTanHalfFov();
    }
    /** Gets the aspect ratio */ getAspect() {
        return this.getProjectionMatrix().getAspect();
    }
    /** Returns true if the camera is perspective */ isPerspective() {
        return this.getProjectionMatrix().isPerspective();
    }
    /** Returns true if the camera is orthographic */ isOrtho() {
        return this.getProjectionMatrix().isOrtho();
    }
    /**
   * Gets the camera history resource manager for temporal effects
   * @returns History resource manager
   */ getHistoryResourceManager() {
        let manager = Camera._historyResourceManager.get(this);
        if (!manager) {
            // Import dynamically to avoid circular dependency
            // The actual import happens in forward_plus_builder.ts
            return null;
        }
        return manager;
    }
    /**
   * Sets the camera history resource manager for temporal effects
   * @internal
   */ setHistoryResourceManager(manager) {
        Camera._historyResourceManager.set(this, manager);
    }
    /**
   * Clears the camera history data which is used in temporal reprojection
   */ clearHistoryData() {
        // Clear history resource manager
        const manager = Camera._historyResourceManager.get(this);
        if (manager) {
            manager.dispose();
            Camera._historyResourceManager.delete(this);
        }
        this._prevVPMatrix = null;
        this._prevPosition = null;
        this._prevJitteredVPMatrix = null;
        this._prevJitterValue = null;
    }
    /** @internal */ updatePostProcessing() {
        this._compositor.clear();
        if (!this._postEffectSSR.get()) {
            const ssr = new SSR();
            ssr.enabled = false;
            this._postEffectSSR.set(ssr);
            this._compositor.appendPostEffect(ssr);
        }
        if (!this._postEffectSSAO.get()) {
            const ssao = new SAO();
            ssao.enabled = false;
            ssao.scale = this._SSAOScale;
            ssao.bias = this._SSAOBias;
            ssao.radius = this._SSAORadius;
            ssao.intensity = this._SSAOIntensity;
            ssao.blurDepthCutoff = this._SSAOBlurDepthCutoff;
            this._postEffectSSAO.set(ssao);
            this._compositor.appendPostEffect(ssao);
        }
        if (!this._postEffectTAA.get()) {
            const taa = new TAA();
            taa.enabled = false;
            this._postEffectTAA.set(taa);
            this._compositor.appendPostEffect(taa);
        }
        if (!this._postEffectMotionBlur.get()) {
            const motionBlur = new MotionBlur();
            motionBlur.enabled = false;
            motionBlur.strength = this._motionBlurStrength;
            this._postEffectMotionBlur.set(motionBlur);
            this._compositor.appendPostEffect(motionBlur);
        }
        if (!this._postEffectTonemap.get()) {
            const tonemap = new Tonemap();
            tonemap.enabled = true;
            tonemap.exposure = this._tonemapExposure;
            this._postEffectTonemap.set(tonemap);
            this._compositor.appendPostEffect(tonemap);
        }
        if (!this._postEffectColorAdjust.get()) {
            const colorAdjust = new ColorAdjust();
            colorAdjust.enabled = false;
            colorAdjust.saturation = this._colorAdjustSaturation;
            colorAdjust.contrast = this._colorAdjustContrast;
            colorAdjust.hue = this._colorAdjustHue;
            colorAdjust.sharpen = this._colorAdjustSharpen;
            this._postEffectColorAdjust.set(colorAdjust);
            this._compositor.appendPostEffect(colorAdjust);
        }
        if (!this._postEffectFXAA.get()) {
            const fxaa = new FXAA();
            fxaa.enabled = false;
            this._postEffectFXAA.set(fxaa);
            this._compositor.appendPostEffect(fxaa);
        }
        if (!this._postEffectBloom.get()) {
            const bloom = new Bloom();
            bloom.enabled = false;
            bloom.maxDownsampleLevel = this._bloomMaxDownsampleLevels;
            bloom.downsampleLimit = this._bloomDownsampleLimit;
            bloom.threshold = this._bloomThreshold;
            bloom.thresholdKnee = this._bloomThresholdKnee;
            bloom.intensity = this._bloomIntensity;
            this._postEffectBloom.set(bloom);
            this._compositor.appendPostEffect(bloom);
        }
    }
    /**
   * Renders a scene
   * @param scene - The scene to be rendered
   * @param compositor - Compositor instance that will be used to apply postprocess effects
   */ render(scene) {
        const device = getDevice();
        //this.updatePostProcessing(device);
        const useMotionVector = (this.TAA || this.motionBlur) && device.type !== 'webgl';
        const useTAA = useMotionVector && this.TAA;
        scene.dispatchEvent('startrender', scene, this, this._compositor);
        if (useMotionVector) {
            const width = device.getDrawingBufferWidth();
            const height = device.getDrawingBufferHeight();
            if (useTAA) {
                const halton = Camera._halton23[device.frameInfo.frameCounter % Camera._halton23.length];
                this._jitterValue.setXY(halton[0] * 2 / width, halton[1] * 2 / height);
            } else {
                this._jitterValue.setXY(0, 0);
            }
            this._jitteredVPMatrix.set(this.getProjectionMatrix());
            this._jitteredVPMatrix[8] += this._jitterValue.x;
            this._jitteredVPMatrix[9] += this._jitterValue.y;
            this._jitteredVPMatrix.multiplyRight(this.viewMatrix);
            Matrix4x4.invert(this._jitteredVPMatrix, this._jitteredInvVPMatrix);
            if (!this._prevJitteredVPMatrix) {
                this._prevJitteredVPMatrix = new Matrix4x4();
                this._prevJitteredVPMatrix.set(this._jitteredVPMatrix);
                this._prevJitterValue = new Vector2(this._jitterValue);
            }
            if (!this._prevVPMatrix) {
                this._prevVPMatrix = new Matrix4x4();
                this._prevVPMatrix.set(this.viewProjectionMatrix);
                this._prevPosition = this.getWorldPosition();
            }
        } else {
            this._jitterValue.setXY(0, 0);
            this._prevVPMatrix = null;
            this._prevPosition = null;
            this._prevJitteredVPMatrix = null;
            this._prevJitterValue = null;
            this._jitteredInvVPMatrix.set(this.invViewProjectionMatrix);
        }
        device.pushDeviceStates();
        device.reverseVertexWindingOrder(false);
        scene.getRenderer().renderScene(scene, this);
        device.popDeviceStates();
        if (useMotionVector) {
            this._prevJitteredVPMatrix.set(this._jitteredVPMatrix);
            this._prevJitterValue.set(this._jitterValue);
            this._prevVPMatrix.set(this.viewProjectionMatrix);
            this._prevPosition = this.getWorldPosition();
        }
        scene.dispatchEvent('endrender', scene, this, this._compositor);
    }
    async pickAsync(posX, posY) {
        this._pickPosX = posX;
        this._pickPosY = posY;
        if (!this._pickResultPromise) {
            this._pickResultPromise = new Promise((resolve)=>{
                this._pickResultResolve = (result)=>{
                    resolve(result);
                    this._pickResultPromise = null;
                    this._pickResultResolve = null;
                };
            });
        }
        return this._pickResultPromise;
    }
    /** @internal */ getPickResultResolveFunc() {
        return this._pickResultResolve;
    }
    /** @internal */ getPickPosX() {
        return this._pickPosX;
    }
    /** @internal */ getPickPosY() {
        return this._pickPosY;
    }
    /**
   * Updates the controller state
   */ updateController() {
        this._controller?.update();
    }
    /**
   * Reset the controller
   */ resetController() {
        this._controller?.reset();
    }
    /** @internal */ get jitteredVPMatrix() {
        return this._jitteredVPMatrix;
    }
    /** @internal */ get jitteredInvVPMatrix() {
        return this._jitteredInvVPMatrix;
    }
    /** @internal */ get jitterValue() {
        return this._jitterValue;
    }
    /** @internal */ get prevJitteredVPMatrix() {
        return this._prevJitteredVPMatrix;
    }
    /** @internal */ get prevJitterValue() {
        return this._prevJitterValue;
    }
    /** @internal */ get prevVPMatrix() {
        return this._prevVPMatrix;
    }
    /** @internal */ get prevPosition() {
        return this._prevPosition;
    }
    /** @internal */ setController(controller) {
        if (this._controller !== controller) {
            if (controller && controller._getCamera() && controller._getCamera() !== this) {
                throw new Error('Camera.setController failed: one camera controller object cannot be assigned to multiple camera');
            }
            this._controller?._setCamera(null);
            this._controller = controller;
            this._controller?._setCamera(this);
        }
        return this;
    }
    /** @internal */ _invalidate(projectMatrixChanged) {
        this._dirty = true;
        if (projectMatrixChanged) {
            this._frustumV = null;
        }
    }
    /** @internal */ _compute() {
        this._computeProj();
        Matrix4x4.invertAffine(this.worldMatrix, this._viewMatrix);
        Matrix4x4.multiply(this._projMatrix, this._viewMatrix, this._viewProjMatrix);
        Matrix4x4.invert(this._viewProjMatrix, this._invViewProjMatrix);
        if (!this._frustum) {
            this._frustum = new Frustum(this._viewProjMatrix);
        } else {
            this._frustum.initWithMatrix(this._viewProjMatrix);
        }
    }
    /** @internal */ _computeProj() {}
    /** @internal */ _onTransformChanged(invalidateLocal) {
        super._onTransformChanged(invalidateLocal);
        this._invalidate(false);
    }
    /** {@inheritdoc SceneNode.onDispose} */ onDispose() {
        super.onDispose();
        this.setController(null);
        this.clearHistoryData();
        this._postEffectBloom.dispose();
        this._postEffectFXAA.dispose();
        this._postEffectMotionBlur.dispose();
        this._postEffectSSAO.dispose();
        this._postEffectSSR.dispose();
        this._postEffectTAA.dispose();
        this._postEffectTonemap.dispose();
        this._postEffectColorAdjust.dispose();
        this._oit.dispose();
    }
    /** @internal */ createOITForMode(mode) {
        if (mode === 'abuffer') {
            return new ABufferOIT(this._oitABufferLayers);
        }
        if (mode === 'weighted') {
            return new WeightedBlendedOIT();
        }
        return null;
    }
    /** @internal */ inferOITMode(oit) {
        if (!oit) {
            return 'none';
        }
        const type = oit.getType();
        if (type === ABufferOIT.type) {
            return 'abuffer';
        }
        if (type === WeightedBlendedOIT.type) {
            return 'weighted';
        }
        return null;
    }
    /** @internal */ posInViewport(x, y) {
        let rect = this._interactionRect;
        if (!rect && this.viewport) {
            const cvs = getDevice().canvas;
            const vp = this.viewport;
            const vp_x = vp[0];
            const vp_y = cvs.clientHeight - vp[1] - vp[3];
            const vp_w = vp[2];
            const vp_h = vp[3];
            rect = [
                vp_x,
                vp_y,
                vp_w,
                vp_h
            ];
        }
        if (!rect) {
            return true;
        }
        x -= rect[0];
        y -= rect[1];
        return x >= 0 && x < rect[2] && y >= 0 && y < rect[3];
    }
    /** @internal */ dirtyCheck() {
        if (this._adapted) {
            const version = this._screenAdapter.version;
            if (this._adaptedVersion !== version) {
                this._dirty = true;
                this._adaptedViewport = null;
                this._adaptedRelativeViewport = null;
                this._adaptedVersion = version;
            }
        }
        if (this._dirty) {
            this._dirty = false;
            return true;
        }
        return false;
    }
    /** @internal */ calcAdaptedViewport(outViewport) {
        outViewport = outViewport ?? [];
        const transform = this._screenAdapter.transform;
        outViewport[0] = transform.croppedViewport.x;
        outViewport[1] = transform.croppedViewport.y;
        outViewport[2] = transform.croppedViewport.width;
        outViewport[3] = transform.croppedViewport.height;
        return outViewport;
    }
    /** @internal */ calcRelativeAdaptedViewport(outViewport) {
        outViewport = outViewport ?? [];
        const transform = this._screenAdapter.transform;
        outViewport[0] = transform.croppedViewport.x - transform.viewportX;
        outViewport[1] = transform.croppedViewport.y - transform.viewportY;
        outViewport[2] = transform.croppedViewport.width;
        outViewport[3] = transform.croppedViewport.height;
        return outViewport;
    }
    /** @internal */ calcAdaptedOrthographicProjection(nearClip, farClip, outMatrix) {
        const matrix = outMatrix ?? new Matrix4x4();
        const transform = this._screenAdapter.transform;
        const scaleX = this._screenAdapter.config.designWidth / transform.viewportWidth;
        const scaleY = this._screenAdapter.config.designHeight / transform.viewportHeight;
        const left = -transform.croppedViewport.width * scaleX * 0.5;
        const right = -left;
        const bottom = transform.croppedViewport.height * scaleY * 0.5;
        const top = -bottom;
        return matrix.ortho(left, right, bottom, top, nearClip, farClip);
    }
    /** @internal */ calcAdaptedPerspectiveProjection(fov, nearClip, farClip, outMatrix) {
        const matrix = outMatrix ?? new Matrix4x4();
        const transform = this._screenAdapter.transform;
        const aspect = transform.viewportHeight !== 0 ? transform.viewportWidth / transform.viewportHeight : 1;
        const h = nearClip * Math.tan(fov * 0.5);
        const w = h * aspect;
        let left = -w + 2 * w * (transform.croppedViewport.x - transform.viewportX) / transform.viewportWidth;
        let right = w - 2 * w * (transform.viewportX + transform.viewportWidth - transform.croppedViewport.x - transform.croppedViewport.width) / transform.viewportWidth;
        let bottom = -h + 2 * w * (transform.croppedViewport.y - transform.viewportY) / transform.viewportHeight;
        let top = h - 2 * h * (transform.viewportY + transform.viewportHeight - transform.croppedViewport.y - transform.croppedViewport.height) / transform.viewportHeight;
        return matrix.frustum(left, right, bottom, top, nearClip, farClip);
    }
}

export { Camera };
//# sourceMappingURL=camera.js.map
