Skip to content
Snippets Groups Projects
aframe-frustum-lock-component.js 7.09 KiB
Newer Older
  • Learn to ignore specific revisions
  • 42loop's avatar
    42loop committed
    'use strict';
    
    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    
    var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
    
    exports.default = aframeFrustumLockComponent;
    
    var _lodash = require('lodash.throttle');
    
    var _lodash2 = _interopRequireDefault(_lodash);
    
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    
    var COMPONENT_NAME = 'frustum-lock';
    
    /**
     * @param aframe {Object} The Aframe instance to register with
     * @param componentName {String} The component name to use. Default:
     * 'frustum-lock'
     */
    function aframeFrustumLockComponent(aframe) {
      var componentName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : COMPONENT_NAME;
    
    
      // From https://github.com/ngokevin/aframe-animation-component/blob/master/index.js#L176
      function getPropertyType(el, property) {
        var _property$split = property.split('.');
    
        var _property$split2 = _slicedToArray(_property$split, 2);
    
        var compName = _property$split2[0];
        var propName = _property$split2[1];
    
        var component = el.components[compName] || aframe.components[compName];
    
        // Raw attributes
        if (!component) {
          return null;
        }
    
        if (propName) {
          return component.schema[propName].type;
        }
    
        return component.schema.type;
      }
    
      function isValidTypeOrThrow(element, propertyName, type) {
        var widthPropType = getPropertyType(element, propertyName);
    
        // null is a valid value (for raw attributes)
        if (widthPropType && widthPropType !== type) {
          throw new Error('unable to update property ' + propertyName + '; not a ' + type);
        }
      }
    
      function calculateFrustrumSize(cameraEl, depth) {
        var threeCamera = cameraEl.getObject3D('camera');
        var height = 2 * Math.tan(threeCamera.fov * aframe.THREE.Math.DEG2RAD / 2) * depth;
        var width = height * threeCamera.aspect;
        return { width: width, height: height };
      }
    
      /**
       * Frustum Lock component for A-Frame.
       */
      aframe.registerComponent(componentName, {
        schema: {
    
          /**
           * @param {string} [widthProperty=width] - Once frustum width is
           * calculated, this property on the element will be given the value.
           */
          widthProperty: { default: 'width' },
    
          /**
           * @param {string} [heightProperty=width] - Once frustum height is
           * calculated, this property on the element will be given the value.
           */
          heightProperty: { default: 'height' },
    
          /**
           * @param {number} [depth=10] - Distance along the z-index to position the
           * entity once frustum size calculated.
           */
          depth: { default: 10 },
    
          /**
           * @param {number} [throttleTimeout=100] - Frustum calculations are
           * performed on resize and enter/exit vr. This throttles the calculations
           * to every throttleTimeout milliseconds.
           */
          throttleTimeout: { default: 100 }
        },
    
        /**
         * Set if component needs multiple instancing.
         */
        multiple: false,
    
        /**
         * Called once when component is attached. Generally for initial setup.
         */
        init: function init() {
          var _this = this;
    
          this._attachedEventListener = false;
          this._waitingForCameraInit = false;
    
          this._doFrustumCalcs = function (camera) {
            var _calculateFrustrumSiz = calculateFrustrumSize(camera, _this.data.depth);
    
            var width = _calculateFrustrumSiz.width;
            var height = _calculateFrustrumSiz.height;
    
    
            aframe.utils.entity.setComponentProperty(_this.el, _this.data.widthProperty, width);
            aframe.utils.entity.setComponentProperty(_this.el, _this.data.heightProperty, height);
            _this.el.setAttribute('position', '0 0 -' + _this.data.depth);
          };
    
          this._activeCameraListener = function (event) {
            _this._waitingForCameraInit = false;
            _this.el.sceneEl.removeEventListener('camera-set-active', _this._activeCameraListener);
            _this._doFrustumCalcs(event.detail.cameraEl);
          };
    
          this._attachEventListeners = function () {
            if (_this._attachedEventListener) {
              return;
            }
            _this._attachedEventListener = (0, _lodash2.default)(_this._resizeEventListener, _this.data.throttleTimeout);
            window.addEventListener('resize', _this._attachedEventListener);
            _this.el.sceneEl.addEventListener('enter-vr', _this._attachedEventListener);
            _this.el.sceneEl.addEventListener('exit-vr', _this._attachedEventListener);
          };
    
          this._removeEventListeners = function () {
            if (!_this._attachedEventListener) {
              return;
            }
            window.removeEventListener('resize', _this._attachedEventListener);
            _this.el.sceneEl.removeEventListener('enter-vr', _this._attachedEventListener);
            _this.el.sceneEl.removeEventListener('exit-vr', _this._attachedEventListener);
            _this._attachedEventListener.cancel();
            _this._attachedEventListener = undefined;
          };
    
          this._resizeEventListener = function () {
            if (_this._waitingForCameraInit) {
              return;
            }
    
            var cameraEl = _this.el.sceneEl.systems.camera.activeCameraEl;
    
            // no active camera available. Let the update() function handle doing the
            // calculations in the future.
            if (!cameraEl) {
              return;
            }
    
            _this._doFrustumCalcs(cameraEl);
          };
        },
    
    
        /**
         * Called when component is attached and when component data changes.
         * Generally modifies the entity based on the data.
         */
        update: function update(oldData) {
    
          if (oldData.widthProperty !== this.data.widthProperty) {
            isValidTypeOrThrow(this.el, this.data.widthProperty, 'number');
          }
    
          if (oldData.heightProperty !== this.data.heightProperty) {
            isValidTypeOrThrow(this.el, this.data.heightProperty, 'number');
          }
    
          // Check the waiting flag so we don't accidentally do the calculations
          // over and over again
          if (!aframe.utils.deepEqual(oldData, this.data) && !this._waitingForCameraInit) {
    
            if (this.el.sceneEl.systems.camera.activeCameraEl) {
    
              // Active camera available, go straight to the calculations
              this._doFrustumCalcs(this.el.sceneEl.systems.camera.activeCameraEl);
            } else {
    
              // no active camera, so let's wait
              this._waitingForCameraInit = true;
              this.el.sceneEl.addEventListener('camera-set-active', this._activeCameraListener);
            }
          }
        },
        play: function play() {
          this._attachEventListeners();
        },
        stop: function stop() {
          this._removeEventListeners();
        }
      });
    }