import { Vector3, Quaternion, Matrix3x3, Matrix4x4 } from '@zephyr3d/base';
import { BaseCameraController } from './base.js';

/**
 * FPS camera controller
 * @public
 */ class FPSCameraController extends BaseCameraController {
    /** @internal */ options;
    /** @internal */ mouseDown;
    /** @internal */ lastMouseX;
    /** @internal */ lastMouseY;
    /** @internal */ keyUp;
    /** @internal */ keyDown;
    /** @internal */ keyLeft;
    /** @internal */ keyRight;
    /** @internal */ keyForward;
    /** @internal */ keyBackward;
    /**
   * Creates an instance of FPSCameraController
   * @param options - The creation options
   */ constructor(options){
        super();
        this.options = Object.assign({
            controlKeys: {
                up: 'KeyQ',
                down: 'KeyE',
                forward: 'KeyW',
                backward: 'KeyS',
                left: 'KeyA',
                right: 'KeyD'
            },
            moveSpeed: 0.2,
            rotateSpeed: 0.01
        }, options || {});
        this.reset();
    }
    /**
   * {@inheritDoc BaseCameraController.reset}
   * @override
   */ reset() {
        this.mouseDown = false;
        this.lastMouseX = 0;
        this.lastMouseY = 0;
        this.keyUp = false;
        this.keyDown = false;
        this.keyLeft = false;
        this.keyRight = false;
        this.keyForward = false;
        this.keyBackward = false;
    }
    /**
   * {@inheritDoc BaseCameraController._onMouseDown}
   * @override
   */ _onMouseDown(evt) {
        if (evt.button === 0) {
            this.mouseDown = true;
            this.lastMouseX = evt.offsetX;
            this.lastMouseY = evt.offsetY;
            return true;
        }
        return false;
    }
    /**
   * {@inheritDoc BaseCameraController._onMouseUp}
   * @override
   */ _onMouseUp(evt) {
        if (evt.button === 0 && this.mouseDown) {
            this.mouseDown = false;
            return true;
        }
        return false;
    }
    /**
   * {@inheritDoc BaseCameraController._onMouseMove}
   * @override
   */ _onMouseMove(evt) {
        const camera = this._getCamera();
        if (this.mouseDown) {
            const dx = evt.offsetX - this.lastMouseX;
            const dy = evt.offsetY - this.lastMouseY;
            this.lastMouseX = evt.offsetX;
            this.lastMouseY = evt.offsetY;
            const zAxis = camera.worldMatrix.getRow(2).xyz();
            const alpha = Math.atan2(zAxis.z, zAxis.x) + dx * this.options.rotateSpeed;
            const beta = Math.min(Math.PI / 2.1, Math.max(-Math.PI / 2.1, Math.asin(zAxis.y) + dy * this.options.rotateSpeed));
            const newY = Math.sin(beta);
            const r = Math.sqrt(Math.max(0, 1 - newY * newY));
            const newZ = Math.sin(alpha) * r;
            const newX = Math.cos(alpha) * r;
            zAxis.setXYZ(newX, newY, newZ).inplaceNormalize();
            const XAxis = Vector3.cross(Vector3.axisPY(), zAxis).inplaceNormalize();
            const YAxis = Vector3.cross(zAxis, XAxis).inplaceNormalize();
            const rotation = Quaternion.fromRotationMatrix(new Matrix3x3([
                XAxis.x,
                XAxis.y,
                XAxis.z,
                YAxis.x,
                YAxis.y,
                YAxis.z,
                zAxis.x,
                zAxis.y,
                zAxis.z
            ]));
            if (!camera.parent) {
                camera.rotation.set(rotation);
            } else {
                const pos = new Vector3();
                const scale = new Vector3();
                camera.worldMatrix.decompose(scale, null, pos);
                const newWorldMatrix = Matrix4x4.scaling(scale).rotateLeft(rotation).translateLeft(pos);
                const newLocalMatrix = camera.parent ? newWorldMatrix.multiplyLeftAffine(Matrix4x4.invertAffine(camera.parent.worldMatrix)) : newWorldMatrix;
                newLocalMatrix.decompose(scale, rotation, pos);
                camera.position.set(pos);
                camera.scale.set(scale);
                camera.rotation.set(rotation);
            }
            return true;
        }
        return false;
    }
    /**
   * {@inheritDoc BaseCameraController._onKeyDown}
   * @override
   */ _onKeyDown(evt) {
        switch(evt.code){
            case this.options.controlKeys.up:
                this.keyUp = true;
                break;
            case this.options.controlKeys.down:
                this.keyDown = true;
                break;
            case this.options.controlKeys.left:
                this.keyLeft = true;
                break;
            case this.options.controlKeys.right:
                this.keyRight = true;
                break;
            case this.options.controlKeys.forward:
                this.keyForward = true;
                break;
            case this.options.controlKeys.backward:
                this.keyBackward = true;
                break;
            default:
                return false;
        }
        return true;
    }
    /**
   * {@inheritDoc BaseCameraController._onKeyUp}
   * @override
   */ _onKeyUp(evt) {
        switch(evt.code){
            case this.options.controlKeys.up:
                this.keyUp = false;
                break;
            case this.options.controlKeys.down:
                this.keyDown = false;
                break;
            case this.options.controlKeys.left:
                this.keyLeft = false;
                break;
            case this.options.controlKeys.right:
                this.keyRight = false;
                break;
            case this.options.controlKeys.forward:
                this.keyForward = false;
                break;
            case this.options.controlKeys.backward:
                this.keyBackward = false;
                break;
            default:
                return false;
        }
        return true;
    }
    /**
   * Set options
   * @param opt - options
   */ setOptions(opt) {
        Object.assign(this.options, opt ?? {});
        this.reset();
    }
    /**
   * {@inheritDoc BaseCameraController._onUpdate}
   * @override
   */ _onUpdate() {
        const camera = this._getCamera();
        const x = camera.worldMatrix.getRow(0).xyz();
        x.y = 0;
        x.inplaceNormalize();
        const z = camera.worldMatrix.getRow(2).xyz().inplaceNormalize();
        const move = new Vector3(0, 0, 0);
        let changed = false;
        if (this.keyForward) {
            changed = true;
            move.subBy(Vector3.scale(z, this.options.moveSpeed));
        }
        if (this.keyBackward) {
            changed = true;
            move.addBy(Vector3.scale(z, this.options.moveSpeed));
        }
        if (this.keyUp) {
            changed = true;
            move.y += this.options.moveSpeed;
        }
        if (this.keyDown) {
            changed = true;
            move.y -= this.options.moveSpeed;
        }
        if (this.keyLeft) {
            changed = true;
            move.subBy(Vector3.scale(x, this.options.moveSpeed));
        }
        if (this.keyRight) {
            changed = true;
            move.addBy(Vector3.scale(x, this.options.moveSpeed));
        }
        if (changed) {
            if (camera.parent) {
                const pos = new Vector3();
                const scale = new Vector3();
                const rotation = new Quaternion();
                camera.worldMatrix.decompose(scale, rotation, pos);
                pos.addBy(move);
                const newWorldMatrix = Matrix4x4.scaling(scale).rotateLeft(rotation).translateLeft(pos);
                const newLocalMatrix = newWorldMatrix.multiplyLeftAffine(Matrix4x4.invertAffine(camera.parent.worldMatrix));
                newLocalMatrix.decompose(scale, rotation, pos);
                camera.position.set(pos);
                camera.scale.set(scale);
                camera.rotation.set(rotation);
            } else {
                camera.moveBy(move);
            }
        }
    }
}

export { FPSCameraController };
//# sourceMappingURL=fps.js.map
