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

/**
 * Orbit camera controller
 * @public
 */ class OrbitCameraController extends BaseCameraController {
    /** @internal */ options;
    /** @internal */ lastMouseX;
    /** @internal */ lastMouseY;
    /** @internal */ rotateX;
    /** @internal */ rotateY;
    /** @internal */ eyePos;
    /** @internal */ upVector;
    /** @internal */ xVector;
    /** @internal */ direction;
    /** @internal */ quat;
    /** @internal */ currentOp;
    /** @internal */ panVelocityX;
    /** @internal */ panVelocityY;
    /**
   * Creates an instance of OrbitCameraController
   * @param options - The creation options
   */ constructor(options){
        super();
        this.options = Object.assign({
            center: Vector3.zero(),
            damping: 1,
            rotateSpeed: 1,
            panSpeed: 1,
            zoomSpeed: 1,
            controls: {
                rotate: {
                    button: 0,
                    shiftKey: false,
                    ctrlKey: false,
                    altKey: false,
                    metaKey: false
                },
                pan: {
                    button: 0,
                    shiftKey: true,
                    ctrlKey: false,
                    altKey: false,
                    metaKey: false
                },
                zoom: {
                    button: 2,
                    shiftKey: false,
                    ctrlKey: true,
                    altKey: false,
                    metaKey: false
                },
                zoomWheel: true
            }
        }, options || {});
        this.eyePos = new Vector3();
        this.upVector = new Vector3();
        this.xVector = new Vector3();
        this.direction = new Vector3();
        this.quat = new Quaternion();
        this.reset();
    }
    /** Rotation center */ get center() {
        return this.options.center;
    }
    set center(val) {
        const center = this.options.center;
        const dx = val.x - center.x;
        const dy = val.y - center.y;
        const dz = val.z - center.z;
        center.x += dx;
        center.y += dy;
        center.z += dz;
        this.eyePos.x += dx;
        this.eyePos.y += dy;
        this.eyePos.z += dz;
    }
    /**
   * {@inheritDoc BaseCameraController.reset}
   * @override
   */ reset() {
        this.lastMouseX = 0;
        this.lastMouseY = 0;
        this.rotateX = 0;
        this.rotateY = 0;
        this.upVector = Vector3.axisPY();
        this.currentOp = 0;
        this.panVelocityX = 0;
        this.panVelocityY = 0;
        this._loadCameraParams();
    }
    /**
   * {@inheritDoc BaseCameraController._onMouseDown}
   * @override
   */ _onMouseDown(evt) {
        if (this.matchesControl(evt, this.options.controls.rotate)) {
            this.lastMouseX = evt.offsetX;
            this.lastMouseY = evt.offsetY;
            this.rotateX = 0;
            this.rotateY = 0;
            this.panVelocityX = 0;
            this.panVelocityY = 0;
            this.currentOp = 1;
            return true;
        } else if (this.matchesControl(evt, this.options.controls.pan)) {
            this.lastMouseX = evt.offsetX;
            this.lastMouseY = evt.offsetY;
            this.rotateX = 0;
            this.rotateY = 0;
            this.panVelocityX = 0;
            this.panVelocityY = 0;
            this.currentOp = 2;
            return true;
        } else if (this.matchesControl(evt, this.options.controls.zoom)) {
            this.lastMouseX = evt.offsetX;
            this.lastMouseY = evt.offsetY;
            this.currentOp = 3;
            this.rotateX = 0;
            this.rotateY = 0;
            this.panVelocityX = 0;
            this.panVelocityY = 0;
            return true;
        }
        return false;
    }
    /**
   * {@inheritDoc BaseCameraController._onMouseUp}
   * @override
   */ _onMouseUp(evt) {
        const control = this.currentOp === 1 ? this.options.controls.rotate : this.currentOp === 2 ? this.options.controls.pan : this.currentOp === 3 ? this.options.controls.zoom : null;
        if (control && evt.button === control.button) {
            this.currentOp = 0;
            return true;
        }
        return false;
    }
    /**
   * {@inheritDoc BaseCameraController._onMouseWheel}
   * @override
   */ _onMouseWheel(evt) {
        this.zoom(evt.deltaY);
        return true;
    }
    /**
   * {@inheritDoc BaseCameraController._onMouseMove}
   * @override
   */ _onMouseMove(evt) {
        if (this.currentOp !== 0) {
            const dx = evt.offsetX - this.lastMouseX;
            const dy = evt.offsetY - this.lastMouseY;
            this.lastMouseX = evt.offsetX;
            this.lastMouseY = evt.offsetY;
            if (this.currentOp === 1) {
                this.rotateX -= dy * this.options.rotateSpeed * 0.005;
                this.rotateY -= dx * this.options.rotateSpeed * 0.005;
            } else if (this.currentOp === 2) {
                this.panVelocityX = -dx * this.options.panSpeed;
                this.panVelocityY = dy * this.options.panSpeed;
            } else if (this.currentOp === 3) {
                this.zoom(dy);
            }
            return true;
        }
        return false;
    }
    zoom(dy) {
        const distance = Vector3.distance(this.eyePos, this.options.center);
        let t = dy > 0 ? this.options.zoomSpeed : dy < 0 ? -this.options.zoomSpeed : 0;
        t = Math.exp(t * 0.1);
        if (t > 1 || distance > 0.01) {
            this.eyePos.combineBy(this.options.center, t, 1 - t);
        }
    }
    matchesControl(evt, control) {
        return evt.button === control.button && evt.shiftKey === control.shiftKey && evt.ctrlKey === control.ctrlKey && evt.altKey === control.altKey && evt.metaKey === control.metaKey;
    }
    /** @internal */ _loadCameraParams() {
        const camera = this._getCamera();
        if (camera) {
            this.lookAt(camera.position, this.options.center, this.upVector);
        }
    }
    lookAt(from, to, up) {
        const camera = this._getCamera();
        if (camera) {
            this.eyePos.set(from);
            this.options.center.set(to);
            this.upVector.set(up);
            camera.lookAt(this.eyePos, this.options.center, this.upVector);
            Vector3.sub(this.eyePos, this.options.center, this.direction);
            this.direction.inplaceNormalize();
            const mat = camera.localMatrix;
            this.xVector.setXYZ(mat[0], mat[1], mat[2]);
        }
    }
    /**
   * Set options
   * @param opt - options
   */ setOptions(opt) {
        Object.assign(this.options, opt ?? {});
        this.reset();
    }
    /**
   * {@inheritDoc BaseCameraController._onUpdate}
   * @override
   */ _onUpdate() {
        const camera = this._getCamera();
        const center = this.options.center;
        if (Math.abs(this.panVelocityX) > 0.0001 || Math.abs(this.panVelocityY) > 0.0001) {
            const right = this.xVector;
            const up = this.upVector;
            center.combineBy(right, 1, this.panVelocityX);
            center.combineBy(up, 1, this.panVelocityY);
            this.eyePos.combineBy(right, 1, this.panVelocityX);
            this.eyePos.combineBy(up, 1, this.panVelocityY);
            this.panVelocityX *= 1 - this.options.damping;
            this.panVelocityY *= 1 - this.options.damping;
            if (Math.abs(this.panVelocityX) < 0.0001) {
                this.panVelocityX = 0;
            }
            if (Math.abs(this.panVelocityY) < 0.0001) {
                this.panVelocityY = 0;
            }
        }
        if (Math.abs(this.rotateX) > 0.0001 || Math.abs(this.rotateY) > 0.0001) {
            Quaternion.fromAxisAngle(this.xVector, this.rotateX, this.quat);
            this.quat.transform(this.eyePos.subBy(center), this.eyePos);
            Quaternion.fromEulerAngle(0, this.rotateY, 0, 'ZYX', this.quat);
            this.quat.transform(this.eyePos, this.eyePos);
            this.quat.transform(this.xVector, this.xVector).inplaceNormalize();
            Vector3.normalize(this.eyePos, this.direction).inplaceNormalize();
            Vector3.cross(this.direction, this.xVector, this.upVector).inplaceNormalize();
            this.eyePos.addBy(center);
            this.rotateX *= 1 - this.options.damping;
            this.rotateY *= 1 - this.options.damping;
            if (Math.abs(this.rotateX) < 0.0001) {
                this.rotateX = 0;
            }
            if (Math.abs(this.rotateY) < 0.0001) {
                this.rotateY = 0;
            }
        }
        camera.lookAt(this.eyePos, center, this.upVector);
    }
}

export { OrbitCameraController };
//# sourceMappingURL=orbit.js.map
