{"size": 9287, "ext": "js", "max_stars_repo_path": "geppetto.js/geppetto-ui/src/3d-canvas/threeDEngine/CameraManager.js", "max_stars_repo_name": "MetaCell/geppetto-meta", "max_stars_repo_stars_event_min_datetime": "2022-02-24T18:15:03.000Z", "max_stars_repo_stars_event_max_datetime": "2022-02-24T18:15:03.000Z", "max_issues_repo_path": "geppetto.js/geppetto-ui/src/3d-canvas/threeDEngine/CameraManager.js", "max_issues_repo_name": "MetaCell/geppetto-meta", "max_issues_repo_issues_event_min_datetime": "2021-03-17T09:40:17.000Z", "max_issues_repo_issues_event_max_datetime": "2022-03-30T11:09:44.000Z", "max_forks_repo_path": "geppetto.js/geppetto-ui/src/3d-canvas/threeDEngine/CameraManager.js", "max_forks_repo_name": "MetaCell/geppetto-meta", "max_forks_repo_forks_event_min_datetime": null, "max_forks_repo_forks_event_max_datetime": null}
{"max_stars_count": 1, "max_issues_count": 237, "max_forks_count": null, "avg_line_length": 25.5840220386, "max_line_length": 84, "alphanum_fraction": 0.5965327878}
import * as THREE from 'three';

export default class CameraManager {
  constructor (engine, cameraOptions) {
    this.engine = engine;
    this.sceneCenter = new THREE.Vector3();
    this.camera = new THREE.PerspectiveCamera(
      cameraOptions.angle,
      cameraOptions.aspect,
      cameraOptions.near,
      cameraOptions.far
    );
    this.engine.scene.add(this.camera);
    this.camera.up = new THREE.Vector3(0, 1, 0);
    this.camera.direction = new THREE.Vector3(0, 0, 1);
    this.camera.lookAt(this.sceneCenter);
    this.baseZoom = cameraOptions.baseZoom;
    this.firstLoad = false;
  }

  update (cameraOptions) {
    const {
      position,
      rotation,
      autoRotate,
      movieFilter,
      zoomTo,
      reset,
      rotateSpeed
    } = cameraOptions;

    if (reset || (!this.firstLoad && position === undefined)) {
      this.resetCamera(position);
      if (!this.firstLoad) {
        this.firstLoad = true;
      }
    } else {
      if (position && !this.firstLoad) {
        this.setCameraPosition(position.x, position.y, position.z);
      }
      if (rotation && !this.firstLoad) {
        this.setCameraRotation(
          rotation.rx,
          rotation.ry,
          rotation.rz,
          rotation.radius
        );
      }
      if (!this.firstLoad) {
        this.firstLoad = true;
      }
      if (autoRotate) {
        this.autoRotate(movieFilter);
      }
      if (zoomTo && Array.isArray(zoomTo)) {
        const instances = zoomTo.map(element => Instances.getInstance(element));
        if (instances.length > 0) {
          this.zoomTo(instances);
        }
      }
      if (rotateSpeed){
        this.engine.controls.rotateSpeed = rotateSpeed
      }
    }
  }

  /**
   *
   * @param instances
   */
  zoomTo (instances) {
    this.engine.controls.reset();
    this.zoomToParameters(this.zoomIterator(instances, {}));
  }

  /**
   *
   * @param instances
   * @param zoomParameters
   * @returns {*}
   */
  zoomIterator (instances, zoomParameters) {
    const that = this;
    for (let i = 0; i < instances.length; i++) {
      const instancePath = instances[i].getInstancePath();
      const mesh = this.engine.meshFactory.meshes[instancePath];
      if (mesh) {
        mesh.traverse(function (object) {
          if (Object.prototype.hasOwnProperty.call(object, 'geometry')) {
            that.addMeshToZoomParameters(object, zoomParameters);
          }
        });
      } else {
        zoomParameters = this.zoomIterator(
          instances[i].getChildren(),
          zoomParameters
        );
      }
    }
    return zoomParameters;
  }

  /**
   *
   * @param mesh
   * @param zoomParameters
   * @returns {*}
   */
  addMeshToZoomParameters (mesh, zoomParameters) {
    mesh.geometry.computeBoundingBox();
    const bb = mesh.geometry.boundingBox;
    bb.translate(mesh.localToWorld(new THREE.Vector3()));

    // If min and max vectors are null, first values become default min and max
    if (
      zoomParameters.aabbMin == undefined
      && zoomParameters.aabbMax == undefined
    ) {
      zoomParameters.aabbMin = bb.min;
      zoomParameters.aabbMax = bb.max;
    } else {
      // Compare other meshes, particles BB's to find min and max
      zoomParameters.aabbMin.x = Math.min(zoomParameters.aabbMin.x, bb.min.x);
      zoomParameters.aabbMin.y = Math.min(zoomParameters.aabbMin.y, bb.min.y);
      zoomParameters.aabbMin.z = Math.min(zoomParameters.aabbMin.z, bb.min.z);
      zoomParameters.aabbMax.x = Math.max(zoomParameters.aabbMax.x, bb.max.x);
      zoomParameters.aabbMax.y = Math.max(zoomParameters.aabbMax.y, bb.max.y);
      zoomParameters.aabbMax.z = Math.max(zoomParameters.aabbMax.z, bb.max.z);
    }

    return zoomParameters;
  }

  /**
   *
   * @param zoomParameters
   */
  zoomToParameters (zoomParameters) {
    // Compute world AABB center
    this.sceneCenter.x
      = (zoomParameters.aabbMax.x + zoomParameters.aabbMin.x) * 0.5;
    this.sceneCenter.y
      = (zoomParameters.aabbMax.y + zoomParameters.aabbMin.y) * 0.5;
    this.sceneCenter.z
      = (zoomParameters.aabbMax.z + zoomParameters.aabbMin.z) * 0.5;

    this.updateCamera(zoomParameters.aabbMax, zoomParameters.aabbMin);
  }

  resetCamera (position) {
    if (position) {
      this.setCameraPosition(position.x, position.y, position.z);
      return;
    }

    this.engine.controls.reset();

    let aabbMin = null;
    let aabbMax = null;
    let maxSize = null;
    this.engine.scene.traverse(function (child) {
      if (
        Object.prototype.hasOwnProperty.call(child, 'geometry')
        && child.visible === true
      ) {
        child.geometry.computeBoundingBox();

        let bb = child.geometry.boundingBox;
        let size = bb.getSize(new THREE.Vector3()).length();
        bb.translate(child.localToWorld(new THREE.Vector3()));

        /*
         * If min and max vectors are null, first values become
         * default min and max
         */
        if (aabbMin == null && aabbMax == null && maxSize == null) {
          aabbMin = bb.min;
          aabbMax = bb.max;
          maxSize = size
        } else {
          // Compare other meshes, particles BB's to find min and max
          aabbMin.x = Math.min(aabbMin.x, bb.min.x);
          aabbMin.y = Math.min(aabbMin.y, bb.min.y);
          aabbMin.z = Math.min(aabbMin.z, bb.min.z);
          aabbMax.x = Math.max(aabbMax.x, bb.max.x);
          aabbMax.y = Math.max(aabbMax.y, bb.max.y);
          aabbMax.z = Math.max(aabbMax.z, bb.max.z);
          maxSize = Math.max(maxSize, size);
        }
      }
    });

    if (aabbMin != null && aabbMax != null) {
      // Compute world AABB center
      this.sceneCenter.x = (aabbMax.x + aabbMin.x) * 0.5;
      this.sceneCenter.y = (aabbMax.y + aabbMin.y) * 0.5;
      this.sceneCenter.z = (aabbMax.z + aabbMin.z) * 0.5;
      this.camera.near = maxSize / 100;
      this.camera.far = maxSize * 100;
      this.updateCamera(aabbMax, aabbMin);
    }
  }

  /**
   * Update camera with new position and place to lookat
   * @param aabbMax
   * @param aabbMin
   */
  updateCamera (aabbMax, aabbMin) {
    // Compute world AABB "radius"
    let diag = new THREE.Vector3();
    diag = diag.subVectors(aabbMax, aabbMin);
    const radius = diag.length() * 0.5;

    this.pointCameraTo(this.sceneCenter);

    // Compute offset needed to move the camera back that much needed to center AABB
    const offset
      = radius
      / Math.sin((Math.PI / 180.0) * this.camera.fov * 0.5)
      / this.baseZoom;

    const dir = this.camera.direction.clone();
    dir.multiplyScalar(offset);

    // Store camera position

    this.camera.position.addVectors(dir, this.engine.controls.target);
    this.camera.updateProjectionMatrix();
  }

  /**
   *  Refocus camera to the center of the new object
   * @param node
   */
  pointCameraTo (node) {
    let COG;
    if (node instanceof THREE.Vector3) {
      COG = node;
    } else {
      COG = this.shapeCenterOfGravity(node);
    }
    const v = new THREE.Vector3();
    v.subVectors(COG, this.engine.controls.target);
    this.camera.position.addVectors(this.camera.position, v);

    // retrieve camera orientation

    this.camera.lookAt(COG);
    this.engine.controls.target.set(COG.x, COG.y, COG.z);
  }

  /**
   *
   * @param obj
   * @returns {*}
   */
  shapeCenterOfGravity (obj) {
    return this.boundingBox(obj).center();
  }

  /**
   *
   * @param obj
   * @returns {*}
   */
  boundingBox (obj) {
    if (obj instanceof THREE.Mesh) {
      const geometry = obj.geometry;
      geometry.computeBoundingBox();
      return geometry.boundingBox;
    }

    if (obj instanceof THREE.Object3D) {
      const bb = new THREE.Box3();
      for (let i = 0; i < obj.children.length; i++) {
        bb.union(this.boundingBox(obj.children[i]));
      }
      return bb;
    }
  }

  /**
   * Returns the camera
   * @returns camera
   */
  getCamera () {
    return this.camera;
  }

  /**
   * @param x
   * @param y
   */
  incrementCameraPan (x, y) {
    this.engine.controls.incrementPanEnd(x, y);
  }

  /**
   * @param x
   * @param y
   * @param z
   */
  incrementCameraRotate (x, y, z) {
    this.engine.controls.incrementRotationEnd(x, y, z);
  }

  /**
   * @param z
   */
  incrementCameraZoom (z) {
    this.engine.controls.incrementZoomEnd(z);
  }

  /**
   * @param x
   * @param y
   * @param z
   */
  setCameraPosition (x, y, z) {
    this.engine.controls.setPosition(x, y, z);
  }
  /**
   * @param rx
   * @param ry
   * @param rz
   * @param radius
   */
  setCameraRotation (rx, ry, rz, radius) {
    this.engine.controls.setRotation(rx, ry, rz, radius);
  }

  /**
   * Rotate the camera around the selection
   * @movieFilter
   */
  autoRotate (movieFilter) {
    const that = this;
    if (this.rotate == null) {
      if (movieFilter === undefined || movieFilter === true) {
        this.movieMode(true);
      }
      this.rotate = setInterval(function () {
        that.incrementCameraRotate(0.05, 0);
      }, 75);
    } else {
      if (movieFilter === undefined || movieFilter === true) {
        this.movieMode(false);
      }
      clearInterval(this.rotate);
      this.rotate = null;
    }
  }

  /**
   *
   * @param shaders
   */
  movieMode (shaders) {
    this.engine.configureRenderer(shaders);
  }
}
