<commit_before>/** Usage:
*
* Add Infowindow model:
*
* var infowindowModel = new cdb.geo.ui.InfowindowModel({
*   template_name: 'templates/map/infowindow',
*   latlng: [72, -45],
*   offset: [100, 10]
* });
*
* var infowindow = new cdb.geo.ui.Infowindow({
*   model: infowindowModel,
*   mapView: mapView
* });
*
* Show the infowindow:
* infowindow.showInfowindow();
*
*/

cdb.geo.ui.InfowindowModel = Backbone.Model.extend({

  defaults: {
    template_name: 'geo/infowindow',
    latlng: [0, 0],
    offset: [28, 0], // offset of the tip calculated from the bottom left corner
    autoPan: true,
    content: "",
    visibility: false,
    fields: null // contains the fields displayed in the infowindow
  },

  clearFields: function() {
    this.set({fields: []});
  },

  _cloneFields: function() {
    return _(this.get('fields')).map(function(v) {
      return _.clone(v);
    });
  },

  _setFields: function(f) {
    f.sort(function(a, b) { return a.position -  b.position; });
    this.set({'fields': f});
  },

  addField: function(fieldName, at) {
    if(!this.containsField(fieldName)) {
      var fields = this._cloneFields() || [];
      at = at === undefined ? fields.length: at;
      fields.push({name: fieldName, title: true, position: at});
      //sort fields
      this._setFields(fields);
    }
    return this;
  },

  getFieldProperty: function(fieldName, k) {
    if(this.containsField(fieldName)) {
      var fields = this.get('fields') || [];
      var idx = _.indexOf(_(fields).pluck('name'), fieldName);
      return fields[idx][k];
    }
    return null;
  },

  setFieldProperty: function(fieldName, k, v) {
    if(this.containsField(fieldName)) {
      var fields = this._cloneFields() || [];
      var idx = _.indexOf(_(fields).pluck('name'), fieldName);
      fields[idx][k] = v;
      this._setFields(fields);
    }
    return this;
  },

  getFieldPos: function(fieldName) {
    var p = this.getFieldProperty(fieldName, 'position');
    if(p == undefined) {
      return Number.MAX_VALUE;
    }
    return p;
  },

  containsField: function(fieldName) {
    var fields = this.get('fields') || [];
    return _.contains(_(fields).pluck('name'), fieldName);
  },

  removeField: function(fieldName) {
    if(this.containsField(fieldName)) {
      var fields = this._cloneFields() || [];
      var idx = _.indexOf(_(fields).pluck('name'), fieldName);
      if(idx >= 0) {
        fields.splice(idx, 1);
      }
      this._setFields(fields);
    }
    return this;
  }

});

cdb.geo.ui.Infowindow = cdb.core.View.extend({
  className: "infowindow",

  events: {
    // Close bindings
    "click .close":       "_closeInfowindow",
    "touchstart .close":  "_closeInfowindow",
    // Rest infowindow bindings
    "dragstart":          "_checkOrigin",
    "mousedown":          "_checkOrigin",
    "touchstart":         "_checkOrigin",
    "mousewheel":         "_stopPropagation",
    "DOMMouseScroll":     "_stopPropagation",
    "dbclick":            "_stopPropagation",
    "click":              "_stopPropagation"
  },

  initialize: function(){

    _.bindAll(this, "render", "setLatLng", "changeTemplate", "_updatePosition", "_update", "toggle", "show", "hide");

    this.mapView = this.options.mapView;

    this.template = this.options.template ? this.options.template : cdb.templates.getTemplate(this.model.get("template_name"));

    this.add_related_model(this.model);

    this.model.bind('change:content',       this.render, this);
    this.model.bind('change:template_name', this.changeTemplate, this);
    this.model.bind('change:latlng',        this._update, this);
    this.model.bind('change:visibility',    this.toggle, this);
    this.model.bind('change:template',      this._compileTemplate, this);

    this.mapView.map.bind('change',         this._updatePosition, this);
    //this.map.on('viewreset', this._updatePosition, this);
    this.mapView.bind('drag',               this._updatePosition, this);

    var that = this;
    this.mapView.bind('zoomstart', function(){
      that.hide(true);
    });
    this.mapView.bind('zoomend', function() {
      that.show(true);
    });

    // Set min height to show the scroll
    this.minHeightToScroll = 180;

    this.render();
    this.$el.hide();

  },

  changeTemplate: function(template_name) {
    this.template = cdb.templates.getTemplate(this.model.get("template_name"));
    this.render();
  },

  _compileTemplate: function() {
    this.template = new cdb.core.Template({
       template: this.model.get('template'),
       type: this.model.get('template_type') || 'mustache'
    }).asFunction()
    this.render();
  },

  _checkOrigin: function(ev) {
    // If the mouse down come from jspVerticalBar
    // dont stop the propagation, but if the event
    // is a touchstart, stop the propagation
    var come_from_scroll = (($(ev.target).closest(".jspVerticalBar").length > 0) && (ev.type != "touchstart"));

    if (!come_from_scroll) {
      ev.stopPropagation();
    }
  },

  render: function() {
    if(this.template) {

      // If there is content, destroy the jscrollpane first, then remove the content.
      var $jscrollpane = this.$el.find(".cartodb-popup-content");
      if ($jscrollpane.length > 0 && $jscrollpane.data() != null) {
        $jscrollpane.data().jsp && $jscrollpane.data().jsp.destroy();
      }

      this.$el.html($(this.template(_.clone(this.model.attributes))));

      // Hello jscrollpane hacks!
      // It needs some time to initialize, if not it doesn't render properly the fields
      // Check the height of the content + the header if exists
      var that = this;
      setTimeout(function() {
        var actual_height = that.$el.find(".cartodb-popup-content").outerHeight() + that.$el.find(".cartodb-popup-header").outerHeight();
        if (that.minHeightToScroll <= actual_height)
          that.$el.find(".cartodb-popup-content").jScrollPane({
            maintainPosition:       false,
            verticalDragMinHeight:  20
          });
      },1)
    }
    return this;
  },

  toggle: function() {
    this.model.get("visibility") ? this.show() : this.hide();
  },

  _stopPropagation: function(ev) {
    ev.stopPropagation();
  },

  _closeInfowindow: function(ev) {
    if (ev) {
      ev.preventDefault()
      ev.stopPropagation();
    }

    this.model.set("visibility",false);
  },

  /**
  * Set the correct position for the popup
  * @params {latlng} A new Leaflet LatLng object
  */
  setLatLng: function (latlng) {
    this.model.set("latlng", latlng);
    return this;
  },

  showInfowindow: function() {
    this.model.set("visibility", true);
  },

  show: function (no_pan) {
    var that = this;

    if (this.model.get("visibility")) {
      that.$el.css({ left: -5000 });
      that._update(no_pan);
    }
  },

  isHidden: function () {
    return !this.model.get("visibility");
  },

  hide: function (force) {
    if (force || !this.model.get("visibility")) this._animateOut();
  },

  _update: function (no_pan) {

    if(!this.isHidden()) {
      var delay = 0;

      if (!no_pan) {
        var delay = this._adjustPan();
      }

      this._updatePosition();
      this._animateIn(delay);
    }
  },

  _animateIn: function(delay) {
    if (!$.browser.msie || ($.browser.msie && $.browser.version.search("9.") != -1)) {
      this.$el.css({
        'marginBottom':'-10px',
        'display':'block',
        opacity:0
      });

      this.$el
        .delay(delay)
        .animate({
          opacity: 1,
          marginBottom: 0
        },300);
    } else {
      this.$el.show();
    }
  },

  _animateOut: function() {

    if (!$.browser.msie || ($.browser.msie && $.browser.version.search("9.") != -1)) {
      var that = this;
      this.$el.animate({
        marginBottom: "-10px",
        opacity:      "0",
        display:      "block"
      }, 180, function() {
        that.$el.css({display: "none"});
      });
    } else {
      this.$el.hide();
    }

  },

  /**
  * Update the position (private)
  */
  _updatePosition: function () {
    var
    offset          = this.model.get("offset")
    pos             = this.mapView.latLonToPixel(this.model.get("latlng")),
    x               = this.$el.position().left,
    y               = this.$el.position().top,
    containerHeight = this.$el.outerHeight(true),
    containerWidth  = this.$el.width(),
    left            = pos.x - offset[0],
    size            = this.mapView.getSize(),
    bottom          = -1*(pos.y - offset[1] - size.y);

    this.$el.css({ bottom: bottom, left: left });
  },

  _adjustPan: function (callback) {

    var offset = this.model.get("offset");

    if (!this.model.get("autoPan")) { return; }

    var
      x               = this.$el.position().left,
      y               = this.$el.position().top,
      containerHeight = this.$el.outerHeight(true) + 15, // Adding some more space
      containerWidth  = this.$el.width(),
      pos             = this.mapView.latLonToPixel(this.model.get("latlng")),
      adjustOffset    = {x: 0, y: 0};
      size            = this.mapView.getSize()
      wait_callback   = 0;

    if (pos.x - offset[0] < 0) {
      adjustOffset.x = pos.x - offset[0] - 10;
    }

    if (pos.x - offset[0] + containerWidth > size.x) {
      adjustOffset.x = pos.x + containerWidth - size.x - offset[0] + 10;
    }

    if (pos.y - containerHeight < 0) {
      adjustOffset.y = pos.y - containerHeight - 10;
    }

    if (pos.y - containerHeight > size.y) {
      adjustOffset.y = pos.y + containerHeight - size.y;
    }

    if (adjustOffset.x || adjustOffset.y) {
      this.mapView.panBy(adjustOffset);
      wait_callback = 300;
    }

    return wait_callback;
  }

});
<commit_msg>new ways of managing infowindow fields rendering<commit_after>/** Usage:
*
* Add Infowindow model:
*
* var infowindowModel = new cdb.geo.ui.InfowindowModel({
*   template_name: 'templates/map/infowindow',
*   latlng: [72, -45],
*   offset: [100, 10]
* });
*
* var infowindow = new cdb.geo.ui.Infowindow({
*   model: infowindowModel,
*   mapView: mapView
* });
*
* Show the infowindow:
* infowindow.showInfowindow();
*
*/

cdb.geo.ui.InfowindowModel = Backbone.Model.extend({

  defaults: {
    template_name: 'geo/infowindow',
    latlng: [0, 0],
    offset: [28, 0], // offset of the tip calculated from the bottom left corner
    autoPan: true,
    content: "",
    visibility: false,
    fields: null // contains the fields displayed in the infowindow
  },

  clearFields: function() {
    this.set({fields: []});
  },

  _cloneFields: function() {
    return _(this.get('fields')).map(function(v) {
      return _.clone(v);
    });
  },

  _setFields: function(f) {
    f.sort(function(a, b) { return a.position -  b.position; });
    this.set({'fields': f});
  },

  addField: function(fieldName, at) {
    if(!this.containsField(fieldName)) {
      var fields = this._cloneFields() || [];
      at = at === undefined ? fields.length: at;
      fields.push({name: fieldName, title: true, position: at});
      //sort fields
      this._setFields(fields);
    }
    return this;
  },

  // addField: function(fieldName, at) {
  //   if(!this.containsField(fieldName)) {
  //     //var fields = this._cloneFields() || [];

  //     var fields = this.get('fields')
  //       , sort = at === undefined;

  //     at = at === undefined ? fields.length: at;
  //     fields.push({name: fieldName, title: true, position: at});
  //     // if (sort)
  //     //   fields.sort(function(a, b) { return a.position -  b.position; });
  //     this.trigger('changeFields')
  //     //this.set({'fields': f});

  //     //sort fields
  //     //this._setFields(fields);
  //   }
  //   return this;
  // },

  getFieldProperty: function(fieldName, k) {
    if(this.containsField(fieldName)) {
      var fields = this.get('fields') || [];
      var idx = _.indexOf(_(fields).pluck('name'), fieldName);
      return fields[idx][k];
    }
    return null;
  },

  setFieldProperty: function(fieldName, k, v) {
    if(this.containsField(fieldName)) {
      var fields = this._cloneFields() || [];
      var idx = _.indexOf(_(fields).pluck('name'), fieldName);
      fields[idx][k] = v;
      this._setFields(fields);
    }
    return this;
  },

  getFieldPos: function(fieldName) {
    var p = this.getFieldProperty(fieldName, 'position');
    if(p == undefined) {
      return Number.MAX_VALUE;
    }
    return p;
  },

  containsField: function(fieldName) {
    var fields = this.get('fields') || [];
    return _.contains(_(fields).pluck('name'), fieldName);
  },

  removeField: function(fieldName) {
    if(this.containsField(fieldName)) {
      var fields = this._cloneFields() || [];
      var idx = _.indexOf(_(fields).pluck('name'), fieldName);
      if(idx >= 0) {
        fields.splice(idx, 1);
      }
      this._setFields(fields);
    }
    return this;
  }

});

cdb.geo.ui.Infowindow = cdb.core.View.extend({
  className: "infowindow",

  events: {
    // Close bindings
    "click .close":       "_closeInfowindow",
    "touchstart .close":  "_closeInfowindow",
    // Rest infowindow bindings
    "dragstart":          "_checkOrigin",
    "mousedown":          "_checkOrigin",
    "touchstart":         "_checkOrigin",
    "mousewheel":         "_stopPropagation",
    "DOMMouseScroll":     "_stopPropagation",
    "dbclick":            "_stopPropagation",
    "click":              "_stopPropagation"
  },

  initialize: function(){

    _.bindAll(this, "render", "setLatLng", "changeTemplate", "_updatePosition", "_update", "toggle", "show", "hide");

    this.mapView = this.options.mapView;

    this.template = this.options.template ? this.options.template : cdb.templates.getTemplate(this.model.get("template_name"));

    this.add_related_model(this.model);

    this.model.bind('change:content',       this.render, this);
    this.model.bind('change:template_name', this.changeTemplate, this);
    this.model.bind('change:latlng',        this._update, this);
    this.model.bind('change:visibility',    this.toggle, this);
    this.model.bind('change:template',      this._compileTemplate, this);

    this.mapView.map.bind('change',         this._updatePosition, this);
    //this.map.on('viewreset', this._updatePosition, this);
    this.mapView.bind('drag',               this._updatePosition, this);

    var that = this;
    this.mapView.bind('zoomstart', function(){
      that.hide(true);
    });
    this.mapView.bind('zoomend', function() {
      that.show(true);
    });

    // Set min height to show the scroll
    this.minHeightToScroll = 180;

    this.render();
    this.$el.hide();

  },

  changeTemplate: function(template_name) {
    this.template = cdb.templates.getTemplate(this.model.get("template_name"));
    this.render();
  },

  _compileTemplate: function() {
    this.template = new cdb.core.Template({
       template: this.model.get('template'),
       type: this.model.get('template_type') || 'mustache'
    }).asFunction()
    this.render();
  },

  _checkOrigin: function(ev) {
    // If the mouse down come from jspVerticalBar
    // dont stop the propagation, but if the event
    // is a touchstart, stop the propagation
    var come_from_scroll = (($(ev.target).closest(".jspVerticalBar").length > 0) && (ev.type != "touchstart"));

    if (!come_from_scroll) {
      ev.stopPropagation();
    }
  },

  render: function() {
    if(this.template) {

      // If there is content, destroy the jscrollpane first, then remove the content.
      var $jscrollpane = this.$el.find(".cartodb-popup-content");
      if ($jscrollpane.length > 0 && $jscrollpane.data() != null) {
        $jscrollpane.data().jsp && $jscrollpane.data().jsp.destroy();
      }

      this.$el.html($(this.template(_.clone(this.model.attributes))));

      // Hello jscrollpane hacks!
      // It needs some time to initialize, if not it doesn't render properly the fields
      // Check the height of the content + the header if exists
      var that = this;
      setTimeout(function() {
        var actual_height = that.$el.find(".cartodb-popup-content").outerHeight() + that.$el.find(".cartodb-popup-header").outerHeight();
        if (that.minHeightToScroll <= actual_height)
          that.$el.find(".cartodb-popup-content").jScrollPane({
            maintainPosition:       false,
            verticalDragMinHeight:  20
          });
      },1)
    }
    return this;
  },

  toggle: function() {
    this.model.get("visibility") ? this.show() : this.hide();
  },

  _stopPropagation: function(ev) {
    ev.stopPropagation();
  },

  _closeInfowindow: function(ev) {
    if (ev) {
      ev.preventDefault()
      ev.stopPropagation();
    }

    this.model.set("visibility",false);
  },

  /**
  * Set the correct position for the popup
  * @params {latlng} A new Leaflet LatLng object
  */
  setLatLng: function (latlng) {
    this.model.set("latlng", latlng);
    return this;
  },

  showInfowindow: function() {
    this.model.set("visibility", true);
  },

  show: function (no_pan) {
    var that = this;

    if (this.model.get("visibility")) {
      that.$el.css({ left: -5000 });
      that._update(no_pan);
    }
  },

  isHidden: function () {
    return !this.model.get("visibility");
  },

  hide: function (force) {
    if (force || !this.model.get("visibility")) this._animateOut();
  },

  _update: function (no_pan) {

    if(!this.isHidden()) {
      var delay = 0;

      if (!no_pan) {
        var delay = this._adjustPan();
      }

      this._updatePosition();
      this._animateIn(delay);
    }
  },

  _animateIn: function(delay) {
    if (!$.browser.msie || ($.browser.msie && $.browser.version.search("9.") != -1)) {
      this.$el.css({
        'marginBottom':'-10px',
        'display':'block',
        opacity:0
      });

      this.$el
        .delay(delay)
        .animate({
          opacity: 1,
          marginBottom: 0
        },300);
    } else {
      this.$el.show();
    }
  },

  _animateOut: function() {

    if (!$.browser.msie || ($.browser.msie && $.browser.version.search("9.") != -1)) {
      var that = this;
      this.$el.animate({
        marginBottom: "-10px",
        opacity:      "0",
        display:      "block"
      }, 180, function() {
        that.$el.css({display: "none"});
      });
    } else {
      this.$el.hide();
    }

  },

  /**
  * Update the position (private)
  */
  _updatePosition: function () {
    var
    offset          = this.model.get("offset")
    pos             = this.mapView.latLonToPixel(this.model.get("latlng")),
    x               = this.$el.position().left,
    y               = this.$el.position().top,
    containerHeight = this.$el.outerHeight(true),
    containerWidth  = this.$el.width(),
    left            = pos.x - offset[0],
    size            = this.mapView.getSize(),
    bottom          = -1*(pos.y - offset[1] - size.y);

    this.$el.css({ bottom: bottom, left: left });
  },

  _adjustPan: function (callback) {

    var offset = this.model.get("offset");

    if (!this.model.get("autoPan")) { return; }

    var
      x               = this.$el.position().left,
      y               = this.$el.position().top,
      containerHeight = this.$el.outerHeight(true) + 15, // Adding some more space
      containerWidth  = this.$el.width(),
      pos             = this.mapView.latLonToPixel(this.model.get("latlng")),
      adjustOffset    = {x: 0, y: 0};
      size            = this.mapView.getSize()
      wait_callback   = 0;

    if (pos.x - offset[0] < 0) {
      adjustOffset.x = pos.x - offset[0] - 10;
    }

    if (pos.x - offset[0] + containerWidth > size.x) {
      adjustOffset.x = pos.x + containerWidth - size.x - offset[0] + 10;
    }

    if (pos.y - containerHeight < 0) {
      adjustOffset.y = pos.y - containerHeight - 10;
    }

    if (pos.y - containerHeight > size.y) {
      adjustOffset.y = pos.y + containerHeight - size.y;
    }

    if (adjustOffset.x || adjustOffset.y) {
      this.mapView.panBy(adjustOffset);
      wait_callback = 300;
    }

    return wait_callback;
  }

});
<|endoftext|>


<commit_before>
import {connect} from 'react-redux';
import {call} from 'redux-saga/effects';

import {makeSafeProxy, directCompose, reverseCompose} from './utils';

export default Bundle;

function undeclaredDependencyError (target, property) {
  throw new Error(`use of undeclared dependency ${property}`);
}

function Bundle (linker, builder) {
  this.builder = builder;
  this.locals = makeSafeProxy({}, undeclaredDependencyError);
  this._ = {
    linker: linker,
    bundles: [],
    earlyReducers: [],
    actionReducers: [],
    lateReducers: [],
    views: [],
    defers: [],
    sagas: [],
    sealed: false
  };
}

Bundle.prototype.include = function (builder) {
  this._assertNotSealed();
  const bundle = new Bundle(this._.linker, builder);
  this._.bundles.push(bundle);
  builder(bundle, bundle.locals);
  return bundle.locals;
};

Bundle.prototype.use = function (...names) {
  this._assertNotSealed();
  this._.linker.declareUse(this.locals, names);
};

Bundle.prototype.pack = function (...names) {
  this._assertNotSealed();
  const target = makeSafeProxy({}, undeclaredDependencyError);
  this._.linker.declareUse(target, names);
  return target;
};

Bundle.prototype.defineValue = function (name, value) {
  this._assertNotSealed();
  this._.linker.publish('value', name, value);
  this.use(name);
};

Bundle.prototype.defineSelector = function (name, value) {
  this._assertNotSealed();
  this._.linker.publish('selector', name, value);
  this.use(name);
};

Bundle.prototype.defineAction = function (name, actionType) {
  this._assertNotSealed();
  this._.linker.declareActionType(actionType, name);
  this._.linker.publish('action', name, actionType);
  this.use(name);
};

Bundle.prototype.defineView = function (name, selector, view) {
  this._assertNotSealed();
  if (view === undefined) {
    view = selector;
    selector = undefined;
  }
  this.use(name);
  this._.views.push({name, view, selector});
};

Bundle.prototype.addReducer = function (name, reducer) {
  this._assertNotSealed();
  if (reducer === undefined) {
    this._.lateReducers.push(name); // name is the reducer function
  } else {
    this.use(name);
    this._.actionReducers.push({name, reducer});
  }
};

Bundle.prototype.addEarlyReducer = function (reducer) {
  this._assertNotSealed();
  this._.earlyReducers.push(reducer);
};

Bundle.prototype.addLateReducer = function (reducer) {
  this._assertNotSealed();
  this._.lateReducers.push(reducer);
};

Bundle.prototype.addSaga = function (saga) {
  this._assertNotSealed();
  this._.sagas.push(saga);
};

Bundle.prototype.addEnhancer = function (enhancer) {
  this._assertNotSealed();
  this._.linker.addEnhancer(enhancer);
};

Bundle.prototype.defer = function (callback) {
  this._assertNotSealed();
  this._.defers.push(callback);
};

Bundle.prototype.lookup = function (name) {
  return this._.linker.lookup(name);
};

/* TODO: hide this Bundle methods */

Bundle.prototype._assertNotSealed = function () {
  if (this._.sealed) {
    throw new Error('Dynamically calling epic-linker directives is not supported.');
  }
};

Bundle.prototype._linkViews = function () {
  var i;
  // Define and connect views.
  for (i = 0; i < this._.views.length; i += 1) {
    let {name, selector, view} = this._.views[i];
    if (selector !== undefined) {
      if (typeof selector === 'string') {
        selector = this.locals[selector];
      }
      if (typeof selector !== 'function') {
        throw new Error(`invalid selector for view`, name);
      }
      view = connect(selector)(view);
    }
    view.displayName = `View(${name})`;
    this._.linker.publish('view', name, view);
  }
  // Define and connect views in included bundles.
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._linkViews();
  }
};

Bundle.prototype._runDefers = function (...args) {
  var i;
  // The bundle's defers run first,
  for (i = 0; i < this._.defers.length; i += 1) {
    this._.defers[i].call(null, ...args);
  }
  // followed by the defers in included bundles.
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._runDefers(...args);
  }
};

Bundle.prototype._earlyReducer = function () {
  const reducers = this._.earlyReducers.concat(
    this._.bundles.map(bundle => bundle._earlyReducer()));
  // [x1,…,xn].reduce(f, a) = f(f(f(a,x1), …), xn)
  // Use directCompose so that early-reducers added first apply first.
  return reducers.reduce(reverseCompose, null);
};

Bundle.prototype._buildActionMap = function (actionMap) {
  var i;
  for (i = 0; i < this._.actionReducers.length; i += 1) {
    const {name, reducer} = this._.actionReducers[i];
    const actionType = this.locals[name];
    const prevReducer = actionMap.get(actionType);
    actionMap.set(actionType, reverseCompose(prevReducer, reducer));
  }
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._buildActionMap(actionMap);
  }
};

Bundle.prototype._lateReducer = function () {
  const reducers = this._.lateReducers.concat(
    this._.bundles.map(bundle => bundle._lateReducer()));
  // [x1,…,xn].reduce(f, a) = f(f(f(a,x1), …), xn)
  // Use directCompose so that late-reducers added first apply last.
  return reducers.reduce(directCompose, null);
};

Bundle.prototype._saga = function () {
  var i;
  const effects = [];
  for (i = 0; i < this._.sagas.length; i += 1) {
    effects.push(call(this._.sagas[i]));
  }
  for (i = 0; i < this._.bundles.length; i += 1) {
    effects.push(call(this._.bundles[i]._saga()));
  }
  return function* () {
    yield effects;
  };
};

Bundle.prototype._seal = function () {
  var i;
  this._.sealed = true;
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._seal();
  }
};
<commit_msg>use 'all' effect<commit_after>
import {connect} from 'react-redux';
import {all, call} from 'redux-saga/effects';

import {makeSafeProxy, directCompose, reverseCompose} from './utils';

export default Bundle;

function undeclaredDependencyError (target, property) {
  throw new Error(`use of undeclared dependency ${property}`);
}

function Bundle (linker, builder) {
  this.builder = builder;
  this.locals = makeSafeProxy({}, undeclaredDependencyError);
  this._ = {
    linker: linker,
    bundles: [],
    earlyReducers: [],
    actionReducers: [],
    lateReducers: [],
    views: [],
    defers: [],
    sagas: [],
    sealed: false
  };
}

Bundle.prototype.include = function (builder) {
  this._assertNotSealed();
  const bundle = new Bundle(this._.linker, builder);
  this._.bundles.push(bundle);
  builder(bundle, bundle.locals);
  return bundle.locals;
};

Bundle.prototype.use = function (...names) {
  this._assertNotSealed();
  this._.linker.declareUse(this.locals, names);
};

Bundle.prototype.pack = function (...names) {
  this._assertNotSealed();
  const target = makeSafeProxy({}, undeclaredDependencyError);
  this._.linker.declareUse(target, names);
  return target;
};

Bundle.prototype.defineValue = function (name, value) {
  this._assertNotSealed();
  this._.linker.publish('value', name, value);
  this.use(name);
};

Bundle.prototype.defineSelector = function (name, value) {
  this._assertNotSealed();
  this._.linker.publish('selector', name, value);
  this.use(name);
};

Bundle.prototype.defineAction = function (name, actionType) {
  this._assertNotSealed();
  this._.linker.declareActionType(actionType, name);
  this._.linker.publish('action', name, actionType);
  this.use(name);
};

Bundle.prototype.defineView = function (name, selector, view) {
  this._assertNotSealed();
  if (view === undefined) {
    view = selector;
    selector = undefined;
  }
  this.use(name);
  this._.views.push({name, view, selector});
};

Bundle.prototype.addReducer = function (name, reducer) {
  this._assertNotSealed();
  if (reducer === undefined) {
    this._.lateReducers.push(name); // name is the reducer function
  } else {
    this.use(name);
    this._.actionReducers.push({name, reducer});
  }
};

Bundle.prototype.addEarlyReducer = function (reducer) {
  this._assertNotSealed();
  this._.earlyReducers.push(reducer);
};

Bundle.prototype.addLateReducer = function (reducer) {
  this._assertNotSealed();
  this._.lateReducers.push(reducer);
};

Bundle.prototype.addSaga = function (saga) {
  this._assertNotSealed();
  this._.sagas.push(saga);
};

Bundle.prototype.addEnhancer = function (enhancer) {
  this._assertNotSealed();
  this._.linker.addEnhancer(enhancer);
};

Bundle.prototype.defer = function (callback) {
  this._assertNotSealed();
  this._.defers.push(callback);
};

Bundle.prototype.lookup = function (name) {
  return this._.linker.lookup(name);
};

/* TODO: hide this Bundle methods */

Bundle.prototype._assertNotSealed = function () {
  if (this._.sealed) {
    throw new Error('Dynamically calling epic-linker directives is not supported.');
  }
};

Bundle.prototype._linkViews = function () {
  var i;
  // Define and connect views.
  for (i = 0; i < this._.views.length; i += 1) {
    let {name, selector, view} = this._.views[i];
    if (selector !== undefined) {
      if (typeof selector === 'string') {
        selector = this.locals[selector];
      }
      if (typeof selector !== 'function') {
        throw new Error(`invalid selector for view`, name);
      }
      view = connect(selector)(view);
    }
    view.displayName = `View(${name})`;
    this._.linker.publish('view', name, view);
  }
  // Define and connect views in included bundles.
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._linkViews();
  }
};

Bundle.prototype._runDefers = function (...args) {
  var i;
  // The bundle's defers run first,
  for (i = 0; i < this._.defers.length; i += 1) {
    this._.defers[i].call(null, ...args);
  }
  // followed by the defers in included bundles.
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._runDefers(...args);
  }
};

Bundle.prototype._earlyReducer = function () {
  const reducers = this._.earlyReducers.concat(
    this._.bundles.map(bundle => bundle._earlyReducer()));
  // [x1,…,xn].reduce(f, a) = f(f(f(a,x1), …), xn)
  // Use directCompose so that early-reducers added first apply first.
  return reducers.reduce(reverseCompose, null);
};

Bundle.prototype._buildActionMap = function (actionMap) {
  var i;
  for (i = 0; i < this._.actionReducers.length; i += 1) {
    const {name, reducer} = this._.actionReducers[i];
    const actionType = this.locals[name];
    const prevReducer = actionMap.get(actionType);
    actionMap.set(actionType, reverseCompose(prevReducer, reducer));
  }
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._buildActionMap(actionMap);
  }
};

Bundle.prototype._lateReducer = function () {
  const reducers = this._.lateReducers.concat(
    this._.bundles.map(bundle => bundle._lateReducer()));
  // [x1,…,xn].reduce(f, a) = f(f(f(a,x1), …), xn)
  // Use directCompose so that late-reducers added first apply last.
  return reducers.reduce(directCompose, null);
};

Bundle.prototype._saga = function () {
  var i;
  const effects = [];
  for (i = 0; i < this._.sagas.length; i += 1) {
    effects.push(call(this._.sagas[i]));
  }
  for (i = 0; i < this._.bundles.length; i += 1) {
    effects.push(call(this._.bundles[i]._saga()));
  }
  return function* () {
    yield all(effects);
  };
};

Bundle.prototype._seal = function () {
  var i;
  this._.sealed = true;
  for (i = 0; i < this._.bundles.length; i += 1) {
    this._.bundles[i]._seal();
  }
};
<|endoftext|>


<commit_before>//////////////////////////////////////////////////////////////////////////////
/**
 * @module ogs.vgl
 */

/*jslint devel: true, forin: true, newcap: true, plusplus: true*/
/*jslint white: true, continue:true, indent: 2*/

/*global vglModule, ogs, vec3, vec4, mat4, inherit, $*/
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class camera
 *
 * @class
 * @returns {vglModule.camera}
 */
//////////////////////////////////////////////////////////////////////////////
vglModule.camera = function() {
  'use strict';

  if (!(this instanceof vglModule.camera)) {
    return new vglModule.camera();
  }
  vglModule.groupNode.call(this);

  /** @private */
  var m_viewAngle = (Math.PI * 30) / 180.0,
      m_position = vec4.fromValues(0.0, 0.0, 0.0, 1.0),
      m_focalPoint = vec4.fromValues(0.0, 0.0, -5.0, 1.0),
      m_centerOfRotation = vec3.fromValues(0.0, 0.0, 0.0),
      m_viewUp = vec4.fromValues(0.0, 1.0, 0.0, 0.0),
      m_rightDir = vec4.fromValues(1.0, 0.0, 0.0, 0.0),
      m_near = 0.01,
      m_far = 10000.0,
      m_viewAspect = 1.0,
      m_directionOfProjection = vec4.fromValues(0.0, 0.0, -1.0, 0.0),
      m_viewPlaneNormal = vec4.fromValues(0.0, 0.0, 1.0, 0.0),
      m_viewMatrix = mat4.create(),
      m_projectionMatrix = mat4.create(),
      m_computeModelViewMatrixTime = ogs.vgl.timestamp(),
      m_computeProjectMatrixTime = ogs.vgl.timestamp(),
      m_left = -1.0,
      m_right = 1.0,
      m_top = +1.0,
      m_bottom = -1.0,
      m_enableParallelProjection = false;

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get view angle of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewAngle = function() {
    return m_viewAngle;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set view angle of the camera in degrees, which is converted to radians.
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setViewAngleDegrees = function(a) {
      m_viewAngle = (Math.PI * a) / 180.0;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get position of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.position = function() {
    return m_position;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set position of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setPosition = function(x, y, z) {
    m_position = vec4.fromValues(x, y, z, 1.0);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get focal point of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.focalPoint = function() {
    return m_focalPoint;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set focal point of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setFocalPoint = function(x, y, z) {
    m_focalPoint = vec4.fromValues(x, y, z, 1.0);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get view-up direction of camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewUpDirection = function() {
    return m_viewUp;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set view-up direction of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setViewUpDirection = function(x, y, z) {
    m_viewUp = vec4.fromValues(x, y, z, 0);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get center of rotation for camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.centerOfRotation = function() {
    return m_centerOfRotation;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set center of rotation for camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setCenterOfRotation = function(centerOfRotation) {
    m_centerOfRotation = centerOfRotation;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get clipping range of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.getClippingRange = function() {
    return [m_near, m_far];
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set clipping range of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setClippingRange = function(near, far) {
    m_near = near;
    m_far = far;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get view aspect
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewAspect = function() {
    return m_viewAspect;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set view aspect
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setViewAspect = function(aspect) {
    m_viewAspect = aspect;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return if parallel projection is enabled
   */
  ////////////////////////////////////////////////////////////////////////////
  this.isEnabledParallelProjection = function() {
    return m_enableParallelProjection;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Enable / disable parallel projection
   */
  ////////////////////////////////////////////////////////////////////////////
  this.enableParallelProjection = function(flag) {
    if (flag !== m_enableParallelProjection) {
      m_enableParallelProjection = flag;
      this.modified();
      return true;
    }

    return false;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set parallel projection parameters
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setParallelProjection = function(left, right, top, bottom) {
    m_left = left;
    m_right = right;
    m_top = top;
    m_bottom = bottom;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return direction of projection
   */
  ////////////////////////////////////////////////////////////////////////////
  this.directionOfProjection = function() {
    this.computeDirectionOfProjection();
    return m_directionOfProjection;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return view plane normal direction
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewPlaneNormal = function() {
    this.computeViewPlaneNormal();
    return m_viewPlaneNormal;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return view-matrix for the camera This method does not compute the
   * view-matrix for the camera. It is assumed that a call to computeViewMatrix
   * has been made earlier.
   *
   * @returns {mat4}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewMatrix = function() {
    return this.computeViewMatrix();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return camera projection matrix This method does not compute the
   * projection-matrix for the camera. It is assumed that a call to
   * computeProjectionMatrix has been made earlier.
   *
   * @returns {mat4}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.projectionMatrix = function() {
    return this.computeProjectionMatrix();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute direction of projection
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeDirectionOfProjection = function() {
    vec3.subtract(m_directionOfProjection, m_focalPoint, m_position);
    vec3.normalize(m_directionOfProjection, m_directionOfProjection);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute view plane normal
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeViewPlaneNormal = function() {
    m_viewPlaneNormal[0] = -m_directionOfProjection[0];
    m_viewPlaneNormal[1] = -m_directionOfProjection[1];
    m_viewPlaneNormal[2] = -m_directionOfProjection[2];
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Move camera closer or further away from the scene
   */
  ////////////////////////////////////////////////////////////////////////////
  this.zoom = function(d) {
    if (d === 0) {
      return;
    }

    d = d * vec3.distance(m_focalPoint, m_position);
    m_position[0] = m_focalPoint[0] - d * m_directionOfProjection[0];
    m_position[1] = m_focalPoint[1] - d * m_directionOfProjection[1];
    m_position[2] = m_focalPoint[2] - d * m_directionOfProjection[2];

    this.modified();
    // TODO: If the distance between focal point and the camera position
    // goes really low then we run into issues
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Move camera sideways
   */
  ////////////////////////////////////////////////////////////////////////////
  this.pan = function(dx, dy, dz) {
    m_position[0] += dx;
    m_position[1] += dy;
    m_position[2] += dz;

    m_focalPoint[0] += dx;
    m_focalPoint[1] += dy;
    m_focalPoint[2] += dz;

    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute camera coordinate axes
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeOrthogonalAxes = function() {
    this.computeDirectionOfProjection();
    vec3.cross(m_rightDir, m_directionOfProjection, m_viewUp);
    vec3.normalize(m_rightDir, m_rightDir);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Rotate camera around center of rotation
   * @param dx Rotation around vertical axis in degrees
   * @param dy Rotation around horizontal axis in degrees
   */
  ////////////////////////////////////////////////////////////////////////////
  this.rotate = function(dx, dy) {

    // Convert degrees into radians
    dx = 0.5 * dx * (Math.PI / 180.0);
    dy = 0.5 * dy * (Math.PI / 180.0);

    var mat = mat4.create(),
        inverseCenterOfRotation = new vec3.create();

    mat4.identity(mat);

    inverseCenterOfRotation[0] = -m_centerOfRotation[0];
    inverseCenterOfRotation[1] = -m_centerOfRotation[1];
    inverseCenterOfRotation[2] = -m_centerOfRotation[2];

    mat4.translate(mat, mat, m_centerOfRotation);
    mat4.rotate(mat, mat, dx, m_viewUp);
    mat4.rotate(mat, mat, dy, m_rightDir);
    mat4.translate(mat, mat, inverseCenterOfRotation);

    vec4.transformMat4(m_position, m_position, mat);
    vec4.transformMat4(m_focalPoint, m_focalPoint, mat);

    // Update viewup vector
    vec4.transformMat4(m_viewUp, m_viewUp, mat);
    vec4.normalize(m_viewUp, m_viewUp);

    // Update direction of projection
    this.computeOrthogonalAxes();

    // Mark modified
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute camera view matrix
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeViewMatrix = function() {
    if (m_computeModelViewMatrixTime.getMTime() < this.getMTime()) {
      mat4.lookAt(m_viewMatrix, m_position, m_focalPoint, m_viewUp);
      m_computeModelViewMatrixTime.modified();
    }

    return m_viewMatrix;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute camera projection matrix
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeProjectionMatrix = function() {
    if (m_computeProjectMatrixTime.getMTime() < this.getMTime()) {
      if (!m_enableParallelProjection) {
        mat4.perspective(m_projectionMatrix, m_viewAngle, m_viewAspect, m_near, m_far);
      } else {
        console.log('paralle projection');
        mat4.ortho(m_projectionMatrix,
          m_left, m_right, m_bottom, m_top, m_near, m_far);
      }

      m_computeProjectMatrixTime.modified();
    }

    return m_projectionMatrix;
  };

  this.computeDirectionOfProjection();

  return this;
};

inherit(vglModule.camera, vglModule.groupNode);
<commit_msg>Revert "Add API function to set viewAngle."<commit_after>//////////////////////////////////////////////////////////////////////////////
/**
 * @module ogs.vgl
 */

/*jslint devel: true, forin: true, newcap: true, plusplus: true*/
/*jslint white: true, continue:true, indent: 2*/

/*global vglModule, ogs, vec3, vec4, mat4, inherit, $*/
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class camera
 *
 * @class
 * @returns {vglModule.camera}
 */
//////////////////////////////////////////////////////////////////////////////
vglModule.camera = function() {
  'use strict';

  if (!(this instanceof vglModule.camera)) {
    return new vglModule.camera();
  }
  vglModule.groupNode.call(this);

  /** @private */
  var m_viewAngle = (Math.PI * 30) / 180.0,
      m_position = vec4.fromValues(0.0, 0.0, 0.0, 1.0),
      m_focalPoint = vec4.fromValues(0.0, 0.0, -5.0, 1.0),
      m_centerOfRotation = vec3.fromValues(0.0, 0.0, 0.0),
      m_viewUp = vec4.fromValues(0.0, 1.0, 0.0, 0.0),
      m_rightDir = vec4.fromValues(1.0, 0.0, 0.0, 0.0),
      m_near = 0.01,
      m_far = 10000.0,
      m_viewAspect = 1.0,
      m_directionOfProjection = vec4.fromValues(0.0, 0.0, -1.0, 0.0),
      m_viewPlaneNormal = vec4.fromValues(0.0, 0.0, 1.0, 0.0),
      m_viewMatrix = mat4.create(),
      m_projectionMatrix = mat4.create(),
      m_computeModelViewMatrixTime = ogs.vgl.timestamp(),
      m_computeProjectMatrixTime = ogs.vgl.timestamp(),
      m_left = -1.0,
      m_right = 1.0,
      m_top = +1.0,
      m_bottom = -1.0,
      m_enableParallelProjection = false;

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get view angle of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewAngle = function() {
    return m_viewAngle;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get position of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.position = function() {
    return m_position;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set position of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setPosition = function(x, y, z) {
    m_position = vec4.fromValues(x, y, z, 1.0);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get focal point of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.focalPoint = function() {
    return m_focalPoint;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set focal point of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setFocalPoint = function(x, y, z) {
    m_focalPoint = vec4.fromValues(x, y, z, 1.0);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get view-up direction of camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewUpDirection = function() {
    return m_viewUp;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set view-up direction of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setViewUpDirection = function(x, y, z) {
    m_viewUp = vec4.fromValues(x, y, z, 0);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get center of rotation for camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.centerOfRotation = function() {
    return m_centerOfRotation;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set center of rotation for camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setCenterOfRotation = function(centerOfRotation) {
    m_centerOfRotation = centerOfRotation;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get clipping range of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.getClippingRange = function() {
    return [m_near, m_far];
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set clipping range of the camera
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setClippingRange = function(near, far) {
    m_near = near;
    m_far = far;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get view aspect
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewAspect = function() {
    return m_viewAspect;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set view aspect
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setViewAspect = function(aspect) {
    m_viewAspect = aspect;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return if parallel projection is enabled
   */
  ////////////////////////////////////////////////////////////////////////////
  this.isEnabledParallelProjection = function() {
    return m_enableParallelProjection;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Enable / disable parallel projection
   */
  ////////////////////////////////////////////////////////////////////////////
  this.enableParallelProjection = function(flag) {
    if (flag !== m_enableParallelProjection) {
      m_enableParallelProjection = flag;
      this.modified();
      return true;
    }

    return false;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set parallel projection parameters
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setParallelProjection = function(left, right, top, bottom) {
    m_left = left;
    m_right = right;
    m_top = top;
    m_bottom = bottom;
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return direction of projection
   */
  ////////////////////////////////////////////////////////////////////////////
  this.directionOfProjection = function() {
    this.computeDirectionOfProjection();
    return m_directionOfProjection;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return view plane normal direction
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewPlaneNormal = function() {
    this.computeViewPlaneNormal();
    return m_viewPlaneNormal;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return view-matrix for the camera This method does not compute the
   * view-matrix for the camera. It is assumed that a call to computeViewMatrix
   * has been made earlier.
   *
   * @returns {mat4}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.viewMatrix = function() {
    return this.computeViewMatrix();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return camera projection matrix This method does not compute the
   * projection-matrix for the camera. It is assumed that a call to
   * computeProjectionMatrix has been made earlier.
   *
   * @returns {mat4}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.projectionMatrix = function() {
    return this.computeProjectionMatrix();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute direction of projection
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeDirectionOfProjection = function() {
    vec3.subtract(m_directionOfProjection, m_focalPoint, m_position);
    vec3.normalize(m_directionOfProjection, m_directionOfProjection);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute view plane normal
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeViewPlaneNormal = function() {
    m_viewPlaneNormal[0] = -m_directionOfProjection[0];
    m_viewPlaneNormal[1] = -m_directionOfProjection[1];
    m_viewPlaneNormal[2] = -m_directionOfProjection[2];
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Move camera closer or further away from the scene
   */
  ////////////////////////////////////////////////////////////////////////////
  this.zoom = function(d) {
    if (d === 0) {
      return;
    }

    d = d * vec3.distance(m_focalPoint, m_position);
    m_position[0] = m_focalPoint[0] - d * m_directionOfProjection[0];
    m_position[1] = m_focalPoint[1] - d * m_directionOfProjection[1];
    m_position[2] = m_focalPoint[2] - d * m_directionOfProjection[2];

    this.modified();
    // TODO: If the distance between focal point and the camera position
    // goes really low then we run into issues
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Move camera sideways
   */
  ////////////////////////////////////////////////////////////////////////////
  this.pan = function(dx, dy, dz) {
    m_position[0] += dx;
    m_position[1] += dy;
    m_position[2] += dz;

    m_focalPoint[0] += dx;
    m_focalPoint[1] += dy;
    m_focalPoint[2] += dz;

    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute camera coordinate axes
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeOrthogonalAxes = function() {
    this.computeDirectionOfProjection();
    vec3.cross(m_rightDir, m_directionOfProjection, m_viewUp);
    vec3.normalize(m_rightDir, m_rightDir);
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Rotate camera around center of rotation
   * @param dx Rotation around vertical axis in degrees
   * @param dy Rotation around horizontal axis in degrees
   */
  ////////////////////////////////////////////////////////////////////////////
  this.rotate = function(dx, dy) {

    // Convert degrees into radians
    dx = 0.5 * dx * (Math.PI / 180.0);
    dy = 0.5 * dy * (Math.PI / 180.0);

    var mat = mat4.create(),
        inverseCenterOfRotation = new vec3.create();

    mat4.identity(mat);

    inverseCenterOfRotation[0] = -m_centerOfRotation[0];
    inverseCenterOfRotation[1] = -m_centerOfRotation[1];
    inverseCenterOfRotation[2] = -m_centerOfRotation[2];

    mat4.translate(mat, mat, m_centerOfRotation);
    mat4.rotate(mat, mat, dx, m_viewUp);
    mat4.rotate(mat, mat, dy, m_rightDir);
    mat4.translate(mat, mat, inverseCenterOfRotation);

    vec4.transformMat4(m_position, m_position, mat);
    vec4.transformMat4(m_focalPoint, m_focalPoint, mat);

    // Update viewup vector
    vec4.transformMat4(m_viewUp, m_viewUp, mat);
    vec4.normalize(m_viewUp, m_viewUp);

    // Update direction of projection
    this.computeOrthogonalAxes();

    // Mark modified
    this.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute camera view matrix
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeViewMatrix = function() {
    if (m_computeModelViewMatrixTime.getMTime() < this.getMTime()) {
      mat4.lookAt(m_viewMatrix, m_position, m_focalPoint, m_viewUp);
      m_computeModelViewMatrixTime.modified();
    }

    return m_viewMatrix;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Compute camera projection matrix
   */
  ////////////////////////////////////////////////////////////////////////////
  this.computeProjectionMatrix = function() {
    if (m_computeProjectMatrixTime.getMTime() < this.getMTime()) {
      if (!m_enableParallelProjection) {
        mat4.perspective(m_projectionMatrix, m_viewAngle, m_viewAspect, m_near, m_far);
      } else {
        console.log('paralle projection');
        mat4.ortho(m_projectionMatrix,
          m_left, m_right, m_bottom, m_top, m_near, m_far);
      }

      m_computeProjectMatrixTime.modified();
    }

    return m_projectionMatrix;
  };

  this.computeDirectionOfProjection();

  return this;
};

inherit(vglModule.camera, vglModule.groupNode);
<|endoftext|>


<commit_before>import { mat4_create, mat4_lookAt } from './mat4';
import { object3d_create } from './object3d';
import { quat_setFromRotationMatrix } from './quat';
import { vec3_clone, vec3_Y } from './vec3';

var DEG_TO_RAD = Math.PI / 180;

export function camera_create(fov, aspect, near, far) {
  return camera_updateProjectionMatrix(Object.assign(
    object3d_create(),
    {
      fov: fov || 60,
      near: near || 0.1,
      far: far || 2000,
      aspect: aspect || 1,
      up: vec3_clone(vec3_Y),
      matrixWorldInverse: mat4_create(),
      projectionMatrix: mat4_create(),
    }
  ));
}

export var camera_lookAt = (function() {
  var m1 = mat4_create();

  return function(camera, vector) {
    mat4_lookAt(m1, vector, camera.position, camera.up);
    quat_setFromRotationMatrix(camera.quaternion, m1);
  };
}());

export function camera_updateProjectionMatrix(camera) {
  var near = camera.near;
  var far = camera.far;

  var top = near * Math.tan(camera.fov * 0.5 * DEG_TO_RAD);
  var bottom = -top;
  var left = bottom * camera.aspect;
  var right = top * camera.aspect;

  var x = 2 * near / (right - left);
  var y = 2 * near / (top - bottom);

  var a = (right + left) / (right - left);
  var b = (top + bottom) / (top - bottom);
  var c = -(far + near) / (far - near);
  var d = -2 * far * near / (far - near);

  camera.projectionMatrix.set([
    x, 0, 0, 0,
    0, y, 0, 0,
    a, b, c, -1,
    0, 0, d, 0,
  ]);

  return camera;
}
<commit_msg>Fix arg order in Camera#updateProjectionMatrix()<commit_after>import { mat4_create, mat4_lookAt } from './mat4';
import { object3d_create } from './object3d';
import { quat_setFromRotationMatrix } from './quat';
import { vec3_clone, vec3_Y } from './vec3';

var DEG_TO_RAD = Math.PI / 180;

export function camera_create(fov, aspect, near, far) {
  return camera_updateProjectionMatrix(Object.assign(
    object3d_create(),
    {
      fov: fov || 60,
      near: near || 0.1,
      far: far || 2000,
      aspect: aspect || 1,
      up: vec3_clone(vec3_Y),
      matrixWorldInverse: mat4_create(),
      projectionMatrix: mat4_create(),
    }
  ));
}

export var camera_lookAt = (function() {
  var m1 = mat4_create();

  return function(camera, vector) {
    mat4_lookAt(m1, camera.position, vector, camera.up);
    quat_setFromRotationMatrix(camera.quaternion, m1);
  };
}());

export function camera_updateProjectionMatrix(camera) {
  var near = camera.near;
  var far = camera.far;

  var top = near * Math.tan(camera.fov * 0.5 * DEG_TO_RAD);
  var bottom = -top;
  var left = bottom * camera.aspect;
  var right = top * camera.aspect;

  var x = 2 * near / (right - left);
  var y = 2 * near / (top - bottom);

  var a = (right + left) / (right - left);
  var b = (top + bottom) / (top - bottom);
  var c = -(far + near) / (far - near);
  var d = -2 * far * near / (far - near);

  camera.projectionMatrix.set([
    x, 0, 0, 0,
    0, y, 0, 0,
    a, b, c, -1,
    0, 0, d, 0,
  ]);

  return camera;
}
<|endoftext|>


<commit_before>"use strict";

var CityTour = CityTour || {};

CityTour.AnimationManager = function(terrain, roadNetwork, cameraPole, camera) {
  var animationManager = {};
  var animators = [];

  var pathFinder = new CityTour.DijktrasPathFinder(roadNetwork);
  var horizontalAnimationController  = new CityTour.HorizontalAnimationController(cameraPole, pathFinder);

  var init = function() {
    var START_X = 0;
    var START_Y = 40;
    var SWOOP_DISTANCE_IN_BLOCKS = 20;

    var furthestOutIntersection = CityTour.Config.HALF_BLOCK_ROWS;
    while (!roadNetwork.hasIntersection(0, furthestOutIntersection)) {
      furthestOutIntersection -= 1;
    }

    var startZ = furthestOutIntersection + SWOOP_DISTANCE_IN_BLOCKS;
    var distanceToCityEdge = SWOOP_DISTANCE_IN_BLOCKS * CityTour.Config.BLOCK_AND_STREET_DEPTH;

    cameraPole.position.x = START_X;
    cameraPole.position.y = START_Y;
    cameraPole.position.z = startZ * CityTour.Config.BLOCK_AND_STREET_DEPTH;

    var framesUntilCityEdge = Math.abs(distanceToCityEdge / horizontalAnimationController.deltaZ());
    var terrainHeightAtTouchdown = terrain.heightAtCoordinates(0.0, furthestOutIntersection) + 0.5;
    var swoopDescentDelta = (START_Y - terrainHeightAtTouchdown) / framesUntilCityEdge;

    var vertical = new CityTour.verticalAnimation(cameraPole, camera, terrainHeightAtTouchdown + 0.5, swoopDescentDelta);
    var forward = new CityTour.forwardAnimation(cameraPole, horizontalAnimationController.targetSceneX(), horizontalAnimationController.deltaX(), horizontalAnimationController.targetSceneZ(), horizontalAnimationController.deltaZ());
    var debugBirdseye = new CityTour.debugBirdsEyeAnimation(cameraPole, camera);
    animators = [vertical, forward];
    //animators = [debugBirdseye];
  };

  animationManager.animate = function(frameCount) {
    if (animators.length === 0) {
      init();
    }

    for (var i = 0; i < frameCount; i++) {
      var newAnimators = [];

      animators.forEach(function (animator) {
        animator.animate();

        if (animator.finished === true) {
          if (animator instanceof CityTour.forwardAnimation) {
            horizontalAnimationController.nextTarget();
            newAnimators.push(new CityTour.rotationAnimation(cameraPole, horizontalAnimationController.targetAngle(), horizontalAnimationController.deltaAngle()));
          }
          else if (animator instanceof CityTour.rotationAnimation) {
            newAnimators.push(new CityTour.forwardAnimation(cameraPole, horizontalAnimationController.targetSceneX(), horizontalAnimationController.deltaX(), horizontalAnimationController.targetSceneZ(), horizontalAnimationController.deltaZ()));
          }
        }
        else {
          newAnimators.push(animator);
        }
      });
      animators = newAnimators;
    }

    var mapX = CityTour.Coordinates.sceneXToMapX(cameraPole.position.x);
    var mapZ = CityTour.Coordinates.sceneZToMapZ(cameraPole.position.z);

    var y = terrain.heightAtCoordinates(mapX, mapZ);
    cameraPole.position.y = Math.max(cameraPole.position.y, y + 0.5);
  };

  return animationManager;
};

CityTour.HorizontalAnimationController = function(cameraPole, pathFinder) {
  var FORWARD_MOTION_DELTA = 0.2;
  var ROTATION_DELTA = 0.03;
  var HALF_PI = Math.PI / 2.0;
  var THREE_PI_OVER_TWO = (3.0 * Math.PI) / 2.0;
  var TWO_PI = Math.PI * 2.0;

  var targetMapX = 0.0;
  var targetSceneX = 0.0;
  var targetMapZ = 0.0;
  var targetSceneZ = 0.0;
  var deltaX = 0.0;
  var deltaZ = -FORWARD_MOTION_DELTA;
  var targetAngle = 0.0;
  var deltaAngle;

  var determineNextTargetPoint = function() {
    var oldTargetMapX = targetMapX;
    var oldTargetMapZ = targetMapZ;

    pathFinder.nextTarget();
    targetMapX = pathFinder.targetMapX();
    targetMapZ = pathFinder.targetMapZ();
    targetSceneX = CityTour.Coordinates.mapXToSceneX(targetMapX);
    targetSceneZ = CityTour.Coordinates.mapZToSceneZ(targetMapZ);

    deltaX = 0.0;
    deltaZ = 0.0;
    if (oldTargetMapX > targetMapX) {
      deltaX = -FORWARD_MOTION_DELTA;
    }
    else if (oldTargetMapX < targetMapX) {
      deltaX = FORWARD_MOTION_DELTA;
    }
    else if (oldTargetMapZ > targetMapZ) {
      deltaZ = -FORWARD_MOTION_DELTA;
    }
    else if (oldTargetMapZ < targetMapZ) {
      deltaZ = FORWARD_MOTION_DELTA;
    }

    determineRotationAngle2(oldTargetMapX, oldTargetMapZ, targetMapX, targetMapZ);
  };

  var determineRotationAngle = function() {
    var oldTargetAngle = targetAngle;

    if (deltaX < 0) {
      targetAngle = HALF_PI;
    }
    else if (deltaX > 0) {
      targetAngle = THREE_PI_OVER_TWO;
    }
    else if (deltaZ > 0) {
      targetAngle = Math.PI;
    }
    else if (deltaZ < 0) {
      targetAngle = 0;
    }

    // Prevent an extra long turn (i.e. 270deg instead of 90deg)
    if (oldTargetAngle === 0.0 && targetAngle === THREE_PI_OVER_TWO) {
      oldTargetAngle = TWO_PI;
      cameraPole.rotation.y = TWO_PI;
    }
    else if (oldTargetAngle === THREE_PI_OVER_TWO && targetAngle === 0.0) {
      oldTargetAngle = -HALF_PI;
      cameraPole.rotation.y = -HALF_PI;
    }

    deltaAngle = ROTATION_DELTA;
    deltaAngle *= (targetAngle > oldTargetAngle) ? 1 : -1;
  };

  var determineRotationAngle2 = function(oldTargetMapX, oldTargetMapZ, targetMapX, targetMapZ) {
    var oldTargetAngle = targetAngle;

    var x = targetMapX - oldTargetMapX;
    var z = -(targetMapZ - oldTargetMapZ);
    var angle = Math.atan2(z, x);
    if (angle < HALF_PI) {
      angle += TWO_PI;
    }
    var rightHandedAngle = angle - HALF_PI;

    targetAngle = rightHandedAngle;

    // Prevent an extra long turn (i.e. 270deg instead of 90deg)
    if (oldTargetAngle === 0.0 && targetAngle === THREE_PI_OVER_TWO) {
      oldTargetAngle = TWO_PI;
      cameraPole.rotation.y = TWO_PI;
    }
    else if (oldTargetAngle === THREE_PI_OVER_TWO && targetAngle === 0.0) {
      oldTargetAngle = -HALF_PI;
      cameraPole.rotation.y = -HALF_PI;
    }

    deltaAngle = ROTATION_DELTA;
    deltaAngle *= (targetAngle > oldTargetAngle) ? 1 : -1;
  };

  var horizontalAnimationController = {};

  horizontalAnimationController.targetSceneX = function() { return targetSceneX; };
  horizontalAnimationController.targetMapX = function() { return targetMapX; };
  horizontalAnimationController.targetSceneZ = function() { return targetSceneZ; };
  horizontalAnimationController.targetMapZ = function() { return targetMapZ; };
  horizontalAnimationController.deltaX  = function() { return deltaX; };
  horizontalAnimationController.deltaZ  = function() { return deltaZ; };
  horizontalAnimationController.targetAngle = function() { return targetAngle; };
  horizontalAnimationController.deltaAngle = function() { return deltaAngle; };

  horizontalAnimationController.nextTarget = function() {
    determineNextTargetPoint();
    //determineRotationAngle();
  };

  return horizontalAnimationController;
};

CityTour.verticalAnimation = function(cameraPole, camera, targetY, yDelta) {
  this.cameraPole = cameraPole;
  this.camera = camera;
  this.targetY = targetY;
  this.yDelta = yDelta;
  this.targetAngle = 0.0;
  this.angleDelta = 0.0155140377955;
  this.framesInCurrentMode = 0;
  this.framesUntilTarget = 1500;
  this.mode = 'initial_swoop';
  this.finished = false;
}

CityTour.verticalAnimation.prototype.animate = function() {
  var step = function(current, target, delta) {
    if (current === target) {
      return target;
    }
    else {
      if (current > target) {
        if ((current - target) < delta) {
          return target;
        }
        else {
          return current - delta;
        }
      }
      else if (current < target) {
        if ((target - current) < delta) {
          return target;
        }
        else {
          return current + delta;
        }
      }
    }
  };

  this.framesInCurrentMode += 1;

  this.cameraPole.position.y = step(this.cameraPole.position.y, this.targetY, this.yDelta);
  this.camera.rotation.x = step(this.camera.rotation.x, this.targetAngle, this.angleDelta);

  if (this.framesInCurrentMode >= this.framesUntilTarget) {
    if (this.mode === 'driving') {
      this.mode = 'birdseye';
      this.targetY = 150;
      this.yDelta = 2;
      this.targetAngle = -(Math.PI / 3);
    }
    else if (this.mode === 'hovering' || this.mode === 'initial_swoop') {
      this.mode = 'driving';
      this.targetY = -100000;
      this.yDelta = 0.05;
      this.targetAngle = 0.0;
    }
    else if (this.mode === 'birdseye') {
      this.mode = 'hovering';
      this.targetY = 15;
      this.yDelta = 2;
      this.targetAngle = 0.0;
    }

    this.framesInCurrentMode = 0;
  }
};

CityTour.forwardAnimation = function(cameraPole, targetX, deltaX, targetZ, deltaZ) {
  this.cameraPole = cameraPole;
  this.targetX = targetX;
  this.deltaX = deltaX;
  this.targetZ = targetZ;
  this.deltaZ = deltaZ;
  this.finished = false;
}

CityTour.forwardAnimation.prototype.animate = function() {
  this.cameraPole.position.x += this.deltaX;
  this.cameraPole.position.z += this.deltaZ;

  if ((this.deltaX < 0 && this.cameraPole.position.x < this.targetX) || (this.deltaX > 0 && this.cameraPole.position.x > this.targetX) ||
      (this.deltaZ < 0 && this.cameraPole.position.z < this.targetZ) || (this.deltaZ > 0 && this.cameraPole.position.z > this.targetZ)) {
    this.cameraPole.position.x = this.targetX;
    this.cameraPole.position.z = this.targetZ;

    this.finished = true;
  }
};

CityTour.rotationAnimation = function(cameraPole, targetAngle, deltaAngle) {
  this.cameraPole = cameraPole;
  this.targetAngle = targetAngle;
  this.deltaAngle = deltaAngle;
  this.finished = false;
}

CityTour.rotationAnimation.prototype.animate = function() {
  this.cameraPole.rotation.y += this.deltaAngle;

  if ((this.deltaAngle < 0 && this.cameraPole.rotation.y <= this.targetAngle) || (this.deltaAngle > 0 && this.cameraPole.rotation.y >= this.targetAngle)) {
    if (this.targetAngle >= Math.PI * 2 || this.targetAngle <= Math.PI * -2) {
      this.targetAngle = 0;
    }

    this.cameraPole.rotation.y = this.targetAngle;
    this.finished = true;
  }
};

CityTour.debugBirdsEyeAnimation = function(camera) {
  this.camera = camera;
  this.finished = false;
};

CityTour.debugBirdsEyeAnimation.prototype.animate = function() {
  this.camera.position.x = 0;
  this.camera.position.y = 900;
  this.camera.position.z = 0;
  this.camera.rotation.x = -(Math.PI / 2);
};
<commit_msg>Changing code style to match rest of code base<commit_after>"use strict";

var CityTour = CityTour || {};

CityTour.AnimationManager = function(terrain, roadNetwork, cameraPole, camera) {
  var animationManager = {};
  var animators = [];

  var pathFinder = new CityTour.DijktrasPathFinder(roadNetwork);
  var horizontalAnimationController  = new CityTour.HorizontalAnimationController(cameraPole, pathFinder);

  var init = function() {
    var START_X = 0;
    var START_Y = 40;
    var SWOOP_DISTANCE_IN_BLOCKS = 20;

    var furthestOutIntersection = CityTour.Config.HALF_BLOCK_ROWS;
    while (!roadNetwork.hasIntersection(0, furthestOutIntersection)) {
      furthestOutIntersection -= 1;
    }

    var startZ = furthestOutIntersection + SWOOP_DISTANCE_IN_BLOCKS;
    var distanceToCityEdge = SWOOP_DISTANCE_IN_BLOCKS * CityTour.Config.BLOCK_AND_STREET_DEPTH;

    cameraPole.position.x = START_X;
    cameraPole.position.y = START_Y;
    cameraPole.position.z = startZ * CityTour.Config.BLOCK_AND_STREET_DEPTH;

    var framesUntilCityEdge = Math.abs(distanceToCityEdge / horizontalAnimationController.deltaZ());
    var terrainHeightAtTouchdown = terrain.heightAtCoordinates(0.0, furthestOutIntersection) + 0.5;
    var swoopDescentDelta = (START_Y - terrainHeightAtTouchdown) / framesUntilCityEdge;

    var vertical = new CityTour.VerticalAnimation(cameraPole, camera, terrainHeightAtTouchdown + 0.5, swoopDescentDelta);
    var forward = new CityTour.ForwardAnimation(cameraPole, horizontalAnimationController.targetSceneX(), horizontalAnimationController.deltaX(), horizontalAnimationController.targetSceneZ(), horizontalAnimationController.deltaZ());
    var debugBirdseye = new CityTour.DebugBirdsEyeAnimation(cameraPole, camera);
    animators = [vertical, forward];
    //animators = [debugBirdseye];
  };

  animationManager.animate = function(frameCount) {
    if (animators.length === 0) {
      init();
    }

    for (var i = 0; i < frameCount; i++) {
      var newAnimators = [];

      animators.forEach(function (animator) {
        animator.animate();

        if (animator.finished() === true) {
          if (animator instanceof CityTour.ForwardAnimation) {
            horizontalAnimationController.nextTarget();
            newAnimators.push(new CityTour.RotationAnimation(cameraPole, horizontalAnimationController.targetAngle(), horizontalAnimationController.deltaAngle()));
          }
          else if (animator instanceof CityTour.RotationAnimation) {
            newAnimators.push(new CityTour.ForwardAnimation(cameraPole, horizontalAnimationController.targetSceneX(), horizontalAnimationController.deltaX(), horizontalAnimationController.targetSceneZ(), horizontalAnimationController.deltaZ()));
          }
        }
        else {
          newAnimators.push(animator);
        }
      });
      animators = newAnimators;
    }

    var mapX = CityTour.Coordinates.sceneXToMapX(cameraPole.position.x);
    var mapZ = CityTour.Coordinates.sceneZToMapZ(cameraPole.position.z);

    var y = terrain.heightAtCoordinates(mapX, mapZ);
    cameraPole.position.y = Math.max(cameraPole.position.y, y + 0.5);
  };

  return animationManager;
};

CityTour.HorizontalAnimationController = function(cameraPole, pathFinder) {
  var FORWARD_MOTION_DELTA = 0.2;
  var ROTATION_DELTA = 0.03;
  var HALF_PI = Math.PI / 2.0;
  var THREE_PI_OVER_TWO = (3.0 * Math.PI) / 2.0;
  var TWO_PI = Math.PI * 2.0;

  var targetMapX = 0.0;
  var targetSceneX = 0.0;
  var targetMapZ = 0.0;
  var targetSceneZ = 0.0;
  var deltaX = 0.0;
  var deltaZ = -FORWARD_MOTION_DELTA;
  var targetAngle = 0.0;
  var deltaAngle;

  var determineNextTargetPoint = function() {
    var oldTargetMapX = targetMapX;
    var oldTargetMapZ = targetMapZ;

    pathFinder.nextTarget();
    targetMapX = pathFinder.targetMapX();
    targetMapZ = pathFinder.targetMapZ();
    targetSceneX = CityTour.Coordinates.mapXToSceneX(targetMapX);
    targetSceneZ = CityTour.Coordinates.mapZToSceneZ(targetMapZ);

    deltaX = 0.0;
    deltaZ = 0.0;
    if (oldTargetMapX > targetMapX) {
      deltaX = -FORWARD_MOTION_DELTA;
    }
    else if (oldTargetMapX < targetMapX) {
      deltaX = FORWARD_MOTION_DELTA;
    }
    else if (oldTargetMapZ > targetMapZ) {
      deltaZ = -FORWARD_MOTION_DELTA;
    }
    else if (oldTargetMapZ < targetMapZ) {
      deltaZ = FORWARD_MOTION_DELTA;
    }

    determineRotationAngle2(oldTargetMapX, oldTargetMapZ, targetMapX, targetMapZ);
  };

  var determineRotationAngle = function() {
    var oldTargetAngle = targetAngle;

    if (deltaX < 0) {
      targetAngle = HALF_PI;
    }
    else if (deltaX > 0) {
      targetAngle = THREE_PI_OVER_TWO;
    }
    else if (deltaZ > 0) {
      targetAngle = Math.PI;
    }
    else if (deltaZ < 0) {
      targetAngle = 0;
    }

    // Prevent an extra long turn (i.e. 270deg instead of 90deg)
    if (oldTargetAngle === 0.0 && targetAngle === THREE_PI_OVER_TWO) {
      oldTargetAngle = TWO_PI;
      cameraPole.rotation.y = TWO_PI;
    }
    else if (oldTargetAngle === THREE_PI_OVER_TWO && targetAngle === 0.0) {
      oldTargetAngle = -HALF_PI;
      cameraPole.rotation.y = -HALF_PI;
    }

    deltaAngle = ROTATION_DELTA;
    deltaAngle *= (targetAngle > oldTargetAngle) ? 1 : -1;
  };

  var determineRotationAngle2 = function(oldTargetMapX, oldTargetMapZ, targetMapX, targetMapZ) {
    var oldTargetAngle = targetAngle;

    var x = targetMapX - oldTargetMapX;
    var z = -(targetMapZ - oldTargetMapZ);
    var angle = Math.atan2(z, x);
    if (angle < HALF_PI) {
      angle += TWO_PI;
    }
    var rightHandedAngle = angle - HALF_PI;

    targetAngle = rightHandedAngle;

    // Prevent an extra long turn (i.e. 270deg instead of 90deg)
    if (oldTargetAngle === 0.0 && targetAngle === THREE_PI_OVER_TWO) {
      oldTargetAngle = TWO_PI;
      cameraPole.rotation.y = TWO_PI;
    }
    else if (oldTargetAngle === THREE_PI_OVER_TWO && targetAngle === 0.0) {
      oldTargetAngle = -HALF_PI;
      cameraPole.rotation.y = -HALF_PI;
    }

    deltaAngle = ROTATION_DELTA;
    deltaAngle *= (targetAngle > oldTargetAngle) ? 1 : -1;
  };

  var horizontalAnimationController = {};

  horizontalAnimationController.targetSceneX = function() { return targetSceneX; };
  horizontalAnimationController.targetMapX = function() { return targetMapX; };
  horizontalAnimationController.targetSceneZ = function() { return targetSceneZ; };
  horizontalAnimationController.targetMapZ = function() { return targetMapZ; };
  horizontalAnimationController.deltaX  = function() { return deltaX; };
  horizontalAnimationController.deltaZ  = function() { return deltaZ; };
  horizontalAnimationController.targetAngle = function() { return targetAngle; };
  horizontalAnimationController.deltaAngle = function() { return deltaAngle; };

  horizontalAnimationController.nextTarget = function() {
    determineNextTargetPoint();
    //determineRotationAngle();
  };

  return horizontalAnimationController;
};

CityTour.VerticalAnimation = function(cameraPole, camera, targetY, yDelta) {
  var targetAngle = 0.0;
  var angleDelta = 0.0155140377955;
  var framesInCurrentMode = 0;
  var framesUntilTarget = 1500;
  var mode = 'initial_swoop';
  var finished = false;

  var step = function(current, target, delta) {
    if (current === target) {
      return target;
    }
    else {
      if (current > target) {
        if ((current - target) < delta) {
          return target;
        }
        else {
          return current - delta;
        }
      }
      else if (current < target) {
        if ((target - current) < delta) {
          return target;
        }
        else {
          return current + delta;
        }
      }
    }
  };

  var animate = function() {
    framesInCurrentMode += 1;

    cameraPole.position.y = step(cameraPole.position.y, targetY, yDelta);
    camera.rotation.x = step(camera.rotation.x, targetAngle, angleDelta);

    if (framesInCurrentMode >= framesUntilTarget) {
      if (mode === 'driving') {
        mode = 'birdseye';
        targetY = 150;
        yDelta = 2;
        targetAngle = -(Math.PI / 3);
      }
      else if (mode === 'hovering' || mode === 'initial_swoop') {
        mode = 'driving';
        targetY = -100000;
        yDelta = 0.05;
        targetAngle = 0.0;
      }
      else if (mode === 'birdseye') {
        mode = 'hovering';
        targetY = 15;
        yDelta = 2;
        targetAngle = 0.0;
      }

      framesInCurrentMode = 0;
    }
  };

  var verticalAnimation = {};
  verticalAnimation.animate = animate;

  return verticalAnimation;
};

CityTour.ForwardAnimation = function(cameraPole, targetX, deltaX, targetZ, deltaZ) {
  var finished = false;

  var animate = function() {
    cameraPole.position.x += deltaX;
    cameraPole.position.z += deltaZ;

    if ((deltaX < 0 && cameraPole.position.x < targetX) || (deltaX > 0 && cameraPole.position.x > targetX) ||
        (deltaZ < 0 && cameraPole.position.z < targetZ) || (deltaZ > 0 && cameraPole.position.z > targetZ)) {
      cameraPole.position.x = targetX;
      cameraPole.position.z = targetZ;

      finished = true;
    }
  };

  var forwardAnimation = {};
  forwardAnimation.animate = animate;
  forwardAnimation.finished = function() { return finished; };

  return forwardAnimation;
};

CityTour.RotationAnimation = function(cameraPole, targetAngle, deltaAngle) {
  finished = false;

  var animate = function() {
    cameraPole.rotation.y += deltaAngle;

    if ((deltaAngle < 0 && cameraPole.rotation.y <= targetAngle) || (deltaAngle > 0 && cameraPole.rotation.y >= targetAngle)) {
      if (targetAngle >= Math.PI * 2 || targetAngle <= Math.PI * -2) {
        targetAngle = 0;
      }

      cameraPole.rotation.y = targetAngle;
      finished = true;
    }
  };

  var rotationAnimation = {};
  rotationAnimation.animate = animate;
  rotationAnimation.finished = function() { return finished; };

  return rotationAnimation;
};

CityTour.DebugBirdsEyeAnimation = function(camera) {
  var finished = false;

  var debugBirdsEyeAnimation = {};

  debugBirdsEyeAnimation.animate = function() {
    camera.position.x = 0;
    camera.position.y = 900;
    camera.position.z = 0;
    camera.rotation.x = -(Math.PI / 2);
  };

  return debugBirdsEyeAnimation;
};
<|endoftext|>