Home Reference Source

src/components/CameraDirector2D.js

import Camera2D from './Camera2D';
import Component from '../systems/EntitySystem/Component';

/**
 * Control multiple 2D cameras with one director.
 *
 * @example
 * const component = new CameraDirector2D();
 * component.deserialize({ zoomOut: 1024, zoomMode: 'keep-aspect', cameras: [ './cameraA', './cameraB' ] });
 */
export default class CameraDirector2D extends Component {

  /**
   * Componeny factory.
   *
   * @return {CameraDirector2D} Component instance.
   */
  static factory() {
    return new CameraDirector2D();
  }

  /** @type {*} */
  static get propsTypes() {
    return {
      ...Component.propsTypes,
      zoom: 'number',
      zoomOut: 'number',
      near: 'number',
      far: 'number',
      zoomMode: 'enum(pixel-perfect, keep-aspect)',
      captureEntity: 'string_null',
      cameras: 'array(string)'
    };
  }

  /** @type {*} */
  static get ZoomMode() {
    return Camera2D.ZoomMode;
  }

  /** @type {number} */
  get zoom() {
    return this._zoom;
  }

  /** @type {number} */
  set zoom(value) {
    if (typeof value !== 'number') {
      throw new Error('`value` is not type of Number');
    }

    this._zoom = value;
  }

  /** @type {number} */
  get zoomOut() {
    const { _zoom } = this;
    return _zoom !== 0 ? 1 / _zoom : 1;
  }

  /** @type {number} */
  set zoomOut(value) {
    if (typeof value !== 'number') {
      throw new Error('`value` is not type of Number');
    }

    this._zoom = value !== 0 ? 1 / value : 1;
  }

  /** @type {number} */
  get near() {
    return this._near;
  }

  /** @type {number} */
  set near(value) {
    if (typeof value !== 'number') {
      throw new Error('`value` is not type of Number');
    }

    this._near = value;
  }

  /** @type {number} */
  get far() {
    return this._far;
  }

  /** @type {number} */
  set far(value) {
    if (typeof value !== 'number') {
      throw new Error('`value` is not type of Number');
    }

    this._far = value;
  }

  /** @type {string} */
  get zoomMode() {
    return this._zoomMode;
  }

  /** @type {string} */
  set zoomMode(value) {
    if (typeof value !== 'string') {
      throw new Error('`value` is not type of String');
    }

    this._zoomMode = value;
  }

  /** @type {string|null} */
  get captureEntity() {
    return this._captureEntity;
  }

  /** @type {number|null} */
  set captureEntity(value) {
    if (typeof value !== 'string') {
      throw new Error('`value` is not type of String');
    }

    this._captureEntity = value;
  }

  /** @type {Array.<string>} */
  get cameras() {
    return [ ...this._cameras ];
  }

  /** @type {Array.<string>} */
  set cameras(value) {
    if (!value) {
      this._cameras = null;
      return;
    }

    if (!(value instanceof Array)) {
      throw new Error('`value` is not type of Array!');
    }
    for (let i = 0, c = value.length; i < c; ++i) {
      if (typeof value[i] !== 'string') {
        throw new Error(`\`value\` item #${i} is not type of String!`);
      }
    }

    this._cameras = [ ...value ];
  }

  /**
   * Constructor.
   */
  constructor() {
    super();

    this._zoom = 1;
    this._near = -1;
    this._far = 1;
    this._zoomMode = Camera2D.ZoomMode.PIXEL_PERFECT;
    this._captureEntity = null;
    this._cameras = null;
  }

  /**
   * @override
   */
  onAction(name, ...args) {
    if (name === 'view') {
      return this.onView(...args);
    }
  }

  /**
   * @override
   */
  onPropertySerialize(name, value) {
    if (this.zoomOut > this.zoom ? name === 'zoom' : name === 'zoomOut') {
      return;
    } else if (name === 'cameras') {
      return !value ? [] : [ ...value ];
    } else {
      return super.onPropertySerialize(name, value);
    }
  }

  /**
   * Called when camera need to view rendered scene.
   *
   * @param {WebGLRenderingContext}	gl - WebGL context.
   * @param {RenderSystem}	renderer - Calling renderer instance.
   * @param {number}	deltaTime - Delta time.
   *
   * @return {boolean} True if ignore viewing entity children, false otherwise.
   */
  onView(gl, renderer, deltaTime) {
    const {
      entity,
      _zoom,
      _near,
      _far,
      _zoomMode,
      _captureEntity,
      _cameras
    } = this;

    if (!entity || !_cameras || _cameras.length === 0) {
      return true;
    }

    for (const item of _cameras) {
      const cameraEntity = entity.findEntity(item);
      if (!cameraEntity) {
        console.warn(`Cannot find camera entity: ${item}`);
        continue;
      }

      const camera = cameraEntity.getComponent('Camera2D');
      if (!!camera) {
        camera.zoom = _zoom;
        camera.near = _near;
        camera.far = _far;
        camera.zoomMode = _zoomMode;
        camera.captureEntity = _captureEntity;
        cameraEntity.performAction('view', ...args);
      } else {
        console.warn(`Camera entity does not have Camera2D component: ${item}`);
      }
    }

    return true;
  }

}