text: /// <reference types="./WebXRManager.d.ts" />
  /// <reference lib="dom" />
  import { ArrayCamera } from "../../cameras/ArrayCamera.js";
  import { EventDispatcher } from "../../core/EventDispatcher.js";
  import { PerspectiveCamera } from "../../cameras/PerspectiveCamera.js";
  import { Vector3 } from "../../math/Vector3.js";
  import { Vector4 } from "../../math/Vector4.js";
  import { WebGLAnimation } from "../webgl/WebGLAnimation.js";
  import { WebXRController } from "./WebXRController.js";
  
  function WebXRManager(renderer, gl) {
    const scope = this;
  
    let session = null;
  
    let framebufferScaleFactor = 1.0;
  
    let referenceSpace = null;
    let referenceSpaceType = "local-floor";
  
    let pose = null;
  
    const controllers = [];
    const inputSourcesMap = new Map();
  
    //
  
    const cameraL = new PerspectiveCamera();
    cameraL.layers.enable(1);
    cameraL.viewport = new Vector4();
  
    const cameraR = new PerspectiveCamera();
    cameraR.layers.enable(2);
    cameraR.viewport = new Vector4();
  
    const cameras = [cameraL, cameraR];
  
    const cameraVR = new ArrayCamera();
    cameraVR.layers.enable(1);
    cameraVR.layers.enable(2);
  
    let _currentDepthNear = null;
    let _currentDepthFar = null;
  
    //
  
    this.enabled = false;
  
    this.isPresenting = false;
  
    this.getController = function (index) {
      let controller = controllers[index];
  
      if (controller === undefined) {
        controller = new WebXRController();
        controllers[index] = controller;
      }
  
      return controller.getTargetRaySpace();
    };
  
    this.getControllerGrip = function (index) {
      let controller = controllers[index];
  
      if (controller === undefined) {
        controller = new WebXRController();
        controllers[index] = controller;
      }
  
      return controller.getGripSpace();
    };
  
    this.getHand = function (index) {
      let controller = controllers[index];
  
      if (controller === undefined) {
        controller = new WebXRController();
        controllers[index] = controller;
      }
  
      return controller.getHandSpace();
    };
  
    //
  
    function onSessionEvent(event) {
      const controller = inputSourcesMap.get(event.inputSource);
  
      if (controller) {
        controller.dispatchEvent({ type: event.type, data: event.inputSource });
      }
    }
  
    function onSessionEnd() {
      inputSourcesMap.forEach(function (controller, inputSource) {
        controller.disconnect(inputSource);
      });
  
      inputSourcesMap.clear();
  
      //
  
      renderer.setFramebuffer(null);
      renderer.setRenderTarget(renderer.getRenderTarget()); // Hack #15830
      animation.stop();
  
      scope.isPresenting = false;
  
      scope.dispatchEvent({ type: "sessionend" });
    }
  
    function onRequestReferenceSpace(value) {
      referenceSpace = value;
  
      animation.setContext(session);
      animation.start();
  
      scope.isPresenting = true;
  
      scope.dispatchEvent({ type: "sessionstart" });
    }
  
    this.setFramebufferScaleFactor = function (value) {
      framebufferScaleFactor = value;
  
      if (scope.isPresenting === true) {
        console.warn(
          "THREE.WebXRManager: Cannot change framebuffer scale while presenting.",
        );
      }
    };
  
    this.setReferenceSpaceType = function (value) {
      referenceSpaceType = value;
  
      if (scope.isPresenting === true) {
        console.warn(
          "THREE.WebXRManager: Cannot change reference space type while presenting.",
        );
      }
    };
  
    this.getReferenceSpace = function () {
      return referenceSpace;
    };
  
    this.getSession = function () {
      return session;
    };
  
    this.setSession = function (value) {
      session = value;
  
      if (session !== null) {
        session.addEventListener("select", onSessionEvent);
        session.addEventListener("selectstart", onSessionEvent);
        session.addEventListener("selectend", onSessionEvent);
        session.addEventListener("squeeze", onSessionEvent);
        session.addEventListener("squeezestart", onSessionEvent);
        session.addEventListener("squeezeend", onSessionEvent);
        session.addEventListener("end", onSessionEnd);
  
        const attributes = gl.getContextAttributes();
  
        if (attributes.xrCompatible !== true) {
          gl.makeXRCompatible();
        }
  
        const layerInit = {
          antialias: attributes.antialias,
          alpha: attributes.alpha,
          depth: attributes.depth,
          stencil: attributes.stencil,
          framebufferScaleFactor: framebufferScaleFactor,
        };
  
        // eslint-disable-next-line no-undef
        const baseLayer = new XRWebGLLayer(session, gl, layerInit);
  
        session.updateRenderState({ baseLayer: baseLayer });
  
        session.requestReferenceSpace(referenceSpaceType).then(
          onRequestReferenceSpace,
        );
  
        //
  
        session.addEventListener("inputsourceschange", updateInputSources);
      }
    };
  
    function updateInputSources(event) {
      const inputSources = session.inputSources;
  
      // Assign inputSources to available controllers
  
      for (let i = 0; i < controllers.length; i++) {
        inputSourcesMap.set(inputSources[i], controllers[i]);
      }
  
      // Notify disconnected
  
      for (let i = 0; i < event.removed.length; i++) {
        const inputSource = event.removed[i];
        const controller = inputSourcesMap.get(inputSource);
  
        if (controller) {
          controller.dispatchEvent({ type: "disconnected", data: inputSource });
          inputSourcesMap.delete(inputSource);
        }
      }
  
      // Notify connected
  
      for (let i = 0; i < event.added.length; i++) {
        const inputSource = event.added[i];
        const controller = inputSourcesMap.get(inputSource);
  
        if (controller) {
          controller.dispatchEvent({ type: "connected", data: inputSource });
        }
      }
    }
  
    //
  
    const cameraLPos = new Vector3();
    const cameraRPos = new Vector3();
  
    /**
  	 * Assumes 2 cameras that are parallel and share an X-axis, and that
  	 * the cameras' projection and world matrices have already been set.
  	 * And that near and far planes are identical for both cameras.
  	 * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
  	 */
    function setProjectionFromUnion(camera, cameraL, cameraR) {
      cameraLPos.setFromMatrixPosition(cameraL.matrixWorld);
      cameraRPos.setFromMatrixPosition(cameraR.matrixWorld);
  
      const ipd = cameraLPos.distanceTo(cameraRPos);
  
      const projL = cameraL.projectionMatrix.elements;
      const projR = cameraR.projectionMatrix.elements;
  
      // VR systems will have identical far and near planes, and
      // most likely identical top and bottom frustum extents.
      // Use the left camera for these values.
      const near = projL[14] / (projL[10] - 1);
      const far = projL[14] / (projL[10] + 1);
      const topFov = (projL[9] + 1) / projL[5];
      const bottomFov = (projL[9] - 1) / projL[5];
  
      const leftFov = (projL[8] - 1) / projL[0];
      const rightFov = (projR[8] + 1) / projR[0];
      const left = near * leftFov;
      const right = near * rightFov;
  
      // Calculate the new camera's position offset from the
      // left camera. xOffset should be roughly half `ipd`.
      const zOffset = ipd / (-leftFov + rightFov);
      const xOffset = zOffset * -leftFov;
  
      // TODO: Better way to apply this offset?
      cameraL.matrixWorld.decompose(
        camera.position,
        camera.quaternion,
        camera.scale,
      );
      camera.translateX(xOffset);
      camera.translateZ(zOffset);
      camera.matrixWorld.compose(
        camera.position,
        camera.quaternion,
        camera.scale,
      );
      camera.matrixWorldInverse.getInverse(camera.matrixWorld);
  
      // Find the union of the frustum values of the cameras and scale
      // the values so that the near plane's position does not change in world space,
      // although must now be relative to the new union camera.
      const near2 = near + zOffset;
      const far2 = far + zOffset;
      const left2 = left - xOffset;
      const right2 = right + (ipd - xOffset);
      const top2 = topFov * far / far2 * near2;
      const bottom2 = bottomFov * far / far2 * near2;
  
      camera.projectionMatrix.makePerspective(
        left2,
        right2,
        top2,
        bottom2,
        near2,
        far2,
      );
    }
  
    function updateCamera(camera, parent) {
      if (parent === null) {
        camera.matrixWorld.copy(camera.matrix);
      } else {
        camera.matrixWorld.multiplyMatrices(parent.matrixWorld, camera.matrix);
      }
  
      camera.matrixWorldInverse.getInverse(camera.matrixWorld);
    }
  
    this.getCamera = function (camera) {
      cameraVR.near = cameraR.near = cameraL.near = camera.near;
      cameraVR.far = cameraR.far = cameraL.far = camera.far;
  
      if (
        _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far
      ) {
        // Note that the new renderState won't apply until the next frame. See #18320
  
        session.updateRenderState({
          depthNear: cameraVR.near,
          depthFar: cameraVR.far,
        });
  
        _currentDepthNear = cameraVR.near;
        _currentDepthFar = cameraVR.far;
      }
  
      const parent = camera.parent;
      const cameras = cameraVR.cameras;
  
      updateCamera(cameraVR, parent);
  
      for (let i = 0; i < cameras.length; i++) {
        updateCamera(cameras[i], parent);
      }
  
      // update camera and its children
  
      camera.matrixWorld.copy(cameraVR.matrixWorld);
  
      const children = camera.children;
  
      for (let i = 0, l = children.length; i < l; i++) {
        children[i].updateMatrixWorld(true);
      }
  
      // update projection matrix for proper view frustum culling
  
      if (cameras.length === 2) {
        setProjectionFromUnion(cameraVR, cameraL, cameraR);
      } else {
        // assume single camera setup (AR)
  
        cameraVR.projectionMatrix.copy(cameraL.projectionMatrix);
      }
  
      return cameraVR;
    };
  
    // Animation Loop
  
    let onAnimationFrameCallback = null;
  
    function onAnimationFrame(time, frame) {
      pose = frame.getViewerPose(referenceSpace);
  
      if (pose !== null) {
        const views = pose.views;
        const baseLayer = session.renderState.baseLayer;
  
        renderer.setFramebuffer(baseLayer.framebuffer);
  
        let cameraVRNeedsUpdate = false;
  
        // check if it's necessary to rebuild cameraVR's camera list
  
        if (views.length !== cameraVR.cameras.length) {
          cameraVR.cameras.length = 0;
          cameraVRNeedsUpdate = true;
        }
  
        for (let i = 0; i < views.length; i++) {
          const view = views[i];
          const viewport = baseLayer.getViewport(view);
  
          const camera = cameras[i];
          camera.matrix.fromArray(view.transform.matrix);
          camera.projectionMatrix.fromArray(view.projectionMatrix);
          camera.viewport.set(
            viewport.x,
            viewport.y,
            viewport.width,
            viewport.height,
          );
  
          if (i === 0) {
            cameraVR.matrix.copy(camera.matrix);
          }
  
          if (cameraVRNeedsUpdate === true) {
            cameraVR.cameras.push(camera);
          }
        }
      }
  
      //
  
      const inputSources = session.inputSources;
  
      for (let i = 0; i < controllers.length; i++) {
        const controller = controllers[i];
        const inputSource = inputSources[i];
  
        controller.update(inputSource, frame, referenceSpace);
      }
  
      if (onAnimationFrameCallback) onAnimationFrameCallback(time, frame);
    }
  
    const animation = new WebGLAnimation();
    animation.setAnimationLoop(onAnimationFrame);
  
    this.setAnimationLoop = function (callback) {
      onAnimationFrameCallback = callback;
    };
  
    this.dispose = function () {};
  }
  
  Object.assign(WebXRManager.prototype, EventDispatcher.prototype);
  
  export { WebXRManager };
  