/*!
 * Copyright (c) 2026 Vertex Software LLC. All rights reserved.
 */
import { BoundingSphere, Line3, Matrix4, Plane, Vector3, } from "@vertexvis/geometry";
import { UUID } from "@vertexvis/utils";
import { decodePng } from "../../workers/png-decoder-pool";
import { constrainViewVector, updateLookAtRelativeToBoundingBoxCenter, } from "../rendering/vectors";
import * as ClippingPlanes from "./clippingPlanes";
import { DepthBuffer } from "./depthBuffer";
import { FeatureMap } from "./featureMap";
import * as FrameCamera from "./frameCamera";
export class Frame {
    constructor(correlationIds, temporalRefinementCorrelationId, sequenceNumber, dimensions, image, scene, depthBufferBytes, featureMapBytes, id = UUID.create()) {
        this.correlationIds = correlationIds;
        this.temporalRefinementCorrelationId = temporalRefinementCorrelationId;
        this.sequenceNumber = sequenceNumber;
        this.dimensions = dimensions;
        this.image = image;
        this.scene = scene;
        this.depthBufferBytes = depthBufferBytes;
        this.featureMapBytes = featureMapBytes;
        this.id = id;
    }
    getId() {
        return this.id;
    }
    async depthBuffer() {
        if (this.cachedDepthBuffer == null) {
            this.cachedDepthBuffer =
                this.depthBufferBytes != null
                    ? this.decodeDepthBuffer(new Uint8Array(this.depthBufferBytes))
                    : Promise.resolve(undefined);
        }
        return this.cachedDepthBuffer;
    }
    async decodeDepthBuffer(bytes) {
        const png = await decodePng(bytes);
        return DepthBuffer.fromPng(png, this.scene.camera, this.image.imageAttr);
    }
    async featureMap() {
        if (this.cachedFeatureMap == null) {
            this.cachedFeatureMap =
                this.featureMapBytes != null
                    ? this.decodeFeatureMap(new Uint8Array(this.featureMapBytes))
                    : Promise.resolve(undefined);
        }
        return this.cachedFeatureMap;
    }
    async decodeFeatureMap(bytes) {
        const png = await decodePng(bytes);
        return FeatureMap.fromPng(png, this.image.imageAttr);
    }
    copy({ correlationIds, temporalRefinementCorrelationId, sequenceNumber, dimensions, image, scene, depthBufferBytes, featureMapBytes, }) {
        return new Frame(correlationIds !== null && correlationIds !== void 0 ? correlationIds : this.correlationIds, temporalRefinementCorrelationId !== null && temporalRefinementCorrelationId !== void 0 ? temporalRefinementCorrelationId : this.temporalRefinementCorrelationId, sequenceNumber !== null && sequenceNumber !== void 0 ? sequenceNumber : this.sequenceNumber, dimensions !== null && dimensions !== void 0 ? dimensions : this.dimensions, image !== null && image !== void 0 ? image : this.image, scene !== null && scene !== void 0 ? scene : this.scene, depthBufferBytes !== null && depthBufferBytes !== void 0 ? depthBufferBytes : this.depthBufferBytes, featureMapBytes !== null && featureMapBytes !== void 0 ? featureMapBytes : this.featureMapBytes, this.id);
    }
}
export class FrameImage {
    constructor(imageAttr, imageBytes) {
        this.imageAttr = imageAttr;
        this.imageBytes = imageBytes;
    }
}
export class FrameScene {
    constructor(camera, boundingBox, crossSection, worldOrientation, hasChanged, sceneViewSummary, modelViewId) {
        this.camera = camera;
        this.boundingBox = boundingBox;
        this.crossSection = crossSection;
        this.worldOrientation = worldOrientation;
        this.hasChanged = hasChanged;
        this.sceneViewSummary = sceneViewSummary;
        this.modelViewId = modelViewId;
    }
    copy({ camera, boundingBox, crossSection, worldOrientation, hasChanged, sceneViewSummary, modelViewId, }) {
        return new FrameScene(camera !== null && camera !== void 0 ? camera : this.camera, boundingBox !== null && boundingBox !== void 0 ? boundingBox : this.boundingBox, crossSection !== null && crossSection !== void 0 ? crossSection : this.crossSection, worldOrientation !== null && worldOrientation !== void 0 ? worldOrientation : this.worldOrientation, hasChanged !== null && hasChanged !== void 0 ? hasChanged : this.hasChanged, sceneViewSummary !== null && sceneViewSummary !== void 0 ? sceneViewSummary : this.sceneViewSummary, modelViewId !== null && modelViewId !== void 0 ? modelViewId : this.modelViewId);
    }
}
export class FrameCameraBase {
    constructor(position, lookAt, up, near, far, aspectRatio) {
        this.position = position;
        this.lookAt = lookAt;
        this.up = up;
        this.near = near;
        this.far = far;
        this.aspectRatio = aspectRatio;
    }
    get direction() {
        return Vector3.normalize(this.viewVector);
    }
    get viewVector() {
        return Vector3.subtract(this.lookAt, this.position);
    }
    get worldMatrix() {
        return this.computeCameraMatrices().worldMatrix;
    }
    get viewMatrix() {
        return this.computeCameraMatrices().viewMatrix;
    }
    get projectionMatrix() {
        return this.computeCameraMatrices().projectionMatrix;
    }
    get projectionMatrixInverse() {
        return this.computeCameraMatrices().projectionMatrixInverse;
    }
    get projectionViewMatrix() {
        return this.computeCameraMatrices().projectionViewMatrix;
    }
    get frustumProjectionViewMatrix() {
        return this.computeCameraMatrices().projectionViewMatrix;
    }
    static fromBoundingBox(camera, boundingBox, aspectRatio) {
        var _a;
        const { near, far } = ClippingPlanes.fromBoundingBoxAndLookAtCamera(boundingBox, camera);
        if (FrameCamera.isOrthographicFrameCamera(camera)) {
            const boundingSphere = BoundingSphere.create(boundingBox);
            const viewVector = constrainViewVector(camera.viewVector, boundingSphere);
            const lookAt = updateLookAtRelativeToBoundingBoxCenter(camera.lookAt, viewVector, boundingSphere.center);
            return new FrameOrthographicCamera(viewVector, lookAt, camera.up, near, far, aspectRatio, camera.fovHeight, camera.rotationPoint);
        }
        else {
            return new FramePerspectiveCamera(camera.position, camera.lookAt, camera.up, near, far, aspectRatio, (_a = camera.fovY) !== null && _a !== void 0 ? _a : 45);
        }
    }
    /**
     * Returns whether this `FrameCameraBase` is an orthographic camera.
     */
    isOrthographic() {
        return false;
    }
    /**
     * Returns whether this `FrameCameraBase` is a perspective camera.
     */
    isPerspective() {
        return false;
    }
    /**
     * Checks if the given point, in world space, is behind the near plane
     * of the camera.
     *
     * @param world A point in world space.
     * @returns `true` if the point is behind the camera.
     */
    isPointBehindNear(world) {
        const { position, direction, near } = this;
        const dist = Vector3.dot(Vector3.subtract(world, position), direction);
        return dist < near;
    }
    /**
     * Returns a point on the near plane that intersects with `line`. If `line`
     * does not intersect, then `undefined` is returned.
     *
     * @param line The line to intersect.
     * @returns A point in world space, or `undefined` if the line does not
     * intersect with the near plane.
     */
    intersectLineWithNear(line) {
        const { position, direction, near } = this;
        const vs = Vector3.subtract(line.start, position);
        const ve = Vector3.subtract(line.end, position);
        const vl = Line3.create({ start: vs, end: ve });
        const nearP = Plane.create({ normal: direction, constant: -near });
        const pt = Plane.intersectLine(nearP, vl);
        return pt != null ? Vector3.add(pt, position) : undefined;
    }
    computeCameraMatrices() {
        if (this.cameraMatrices == null) {
            return {
                viewMatrix: Matrix4.makeIdentity(),
                worldMatrix: Matrix4.makeIdentity(),
                projectionMatrix: Matrix4.makeIdentity(),
                projectionMatrixInverse: Matrix4.makeIdentity(),
                projectionViewMatrix: Matrix4.makeIdentity(),
            };
        }
        return this.cameraMatrices;
    }
    updateCameraMatrices(cameraMatrices) {
        this.cameraMatrices = cameraMatrices;
        return this.cameraMatrices;
    }
}
export class FramePerspectiveCamera extends FrameCameraBase {
    constructor(position, lookAt, up, near, far, aspectRatio, fovY) {
        super(position, lookAt, up, near, far, aspectRatio);
        this.position = position;
        this.lookAt = lookAt;
        this.up = up;
        this.near = near;
        this.far = far;
        this.aspectRatio = aspectRatio;
        this.fovY = fovY;
    }
    /**
     * Converts this `FramePerspectiveCamera` to a `FrameOrthographicCamera` using
     * the provided `boundingBox` to compute the viewing frustum.
     *
     * @param boundingBox The visible bounding box.
     */
    toOrthographic(boundingBox) {
        return FrameCameraBase.fromBoundingBox(FrameCamera.toOrthographic(this, boundingBox), boundingBox, this.aspectRatio);
    }
    isPerspective() {
        return true;
    }
    computeCameraMatrices() {
        if (this.cameraMatrices == null) {
            const viewMatrix = Matrix4.makeLookAtView(this.position, this.lookAt, this.up);
            const worldMatrix = Matrix4.invert(viewMatrix);
            const projectionMatrix = Matrix4.makePerspective(this.near, this.far, this.fovY, this.aspectRatio);
            const projectionMatrixInverse = Matrix4.invert(projectionMatrix);
            const projectionViewMatrix = Matrix4.multiply(projectionMatrix, viewMatrix);
            return super.updateCameraMatrices({
                viewMatrix,
                worldMatrix,
                projectionMatrix,
                projectionMatrixInverse,
                projectionViewMatrix,
            });
        }
        return this.cameraMatrices;
    }
}
export class FrameOrthographicCamera extends FrameCameraBase {
    constructor(viewVector, lookAt, up, near, far, aspectRatio, fovHeight, rotationPoint) {
        super(Vector3.add(lookAt, Vector3.negate(viewVector)), lookAt, up, near, far, aspectRatio);
        this.lookAt = lookAt;
        this.up = up;
        this.near = near;
        this.far = far;
        this.aspectRatio = aspectRatio;
        this.fovHeight = fovHeight;
        this.rotationPoint = rotationPoint;
        this.top = fovHeight * 0.5;
        this.bottom = -this.top;
        this.right = this.top * aspectRatio;
        this.left = -this.right;
    }
    /**
     * Converts this `FrameOrthographicCamera` to a `FramePerspectiveCamera` using
     * the provided `boundingBox` to compute the near and far clipping planes.
     *
     * @param boundingBox The visible bounding box.
     */
    toPerspective(boundingBox) {
        return FrameCameraBase.fromBoundingBox(FrameCamera.toPerspective(this), boundingBox, this.aspectRatio);
    }
    isOrthographic() {
        return true;
    }
    get frustumProjectionViewMatrix() {
        const frustumProjectionMatrix = Matrix4.makeFrustum(this.left, this.right, this.top, this.bottom, this.near, this.far);
        return Matrix4.multiply(frustumProjectionMatrix, this.computeCameraMatrices().viewMatrix);
    }
    computeCameraMatrices() {
        if (this.cameraMatrices == null) {
            const viewMatrix = Matrix4.makeLookAtView(this.position, this.lookAt, this.up);
            const worldMatrix = Matrix4.invert(viewMatrix);
            const projectionMatrix = Matrix4.makeOrthographic(this.left, this.right, this.bottom, this.top, this.near, this.far);
            const projectionMatrixInverse = Matrix4.invert(projectionMatrix);
            const projectionViewMatrix = Matrix4.multiply(projectionMatrix, viewMatrix);
            return super.updateCameraMatrices({
                viewMatrix,
                worldMatrix,
                projectionMatrix,
                projectionMatrixInverse,
                projectionViewMatrix,
            });
        }
        return this.cameraMatrices;
    }
}
//# sourceMappingURL=frame.js.map
