Javascript  |  153行  |  4.83 KB

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.exportTo('ui', function() {

  function lerp(a, b, interp) {
    return (a * (1 - interp)) +
        (b * interp);
  }

  /**
   * @constructor
   */
  function Camera(targetElement) {
    this.targetElement_ = targetElement;

    this.onMouseDown_ = this.onMouseDown_.bind(this);
    this.onMouseMove_ = this.onMouseMove_.bind(this);
    this.onMouseUp_ = this.onMouseUp_.bind(this);

    this.cameraStart_ = {x: 0, y: 0};
    this.rotations_ = {x: 0, y: 0};
    this.rotationStart_ = {x: 0, y: 0};
    this.matrixParameters_ = {
      thicknessRatio: 0.012, // Ratio of thickness to world size.
      strengthRatioX: 0.7, // Ratio of mousemove X pixels to degrees rotated.
      strengthRatioY: 0.25 // Ratio of mousemove Y pixels to degrees rotated.
    };

    this.targetElement_.addEventListener('mousedown', this.onMouseDown_);
    this.targetElement_.addEventListener('layersChange',
        this.scheduleRepaint.bind(this));
  }

  Camera.prototype = {

    scheduleRepaint: function() {
      if (this.repaintPending_)
        return;
      this.repaintPending_ = true;
      base.requestAnimationFrameInThisFrameIfPossible(
          this.repaint_, this);
    },

    /** Call only inside of a requestAnimationFrame. */
    repaint: function() {
      this.repaintPending_ = true;
      this.repaint_();
    },

    repaint_: function() {
      if (!this.repaintPending_)
        return;

      this.repaintPending_ = false;
      var layers = this.targetElement_.layers;

      if (!layers)
        return;

      var numLayers = layers.length;

      var vpThickness;
      if (this.targetElement_.viewport) {
        vpThickness = this.matrixParameters_.thicknessRatio *
            Math.min(this.targetElement_.viewport.worldRect.width,
                     this.targetElement_.viewport.worldRect.height);
      } else {
        vpThickness = 0;
      }
      vpThickness = Math.max(vpThickness, 15);

      // When viewing the stack head-on, we want no foreshortening effects. As
      // we move off axis, let the thickness grow as well as the amount of
      // perspective foreshortening.
      var maxRotation = Math.max(Math.abs(this.rotations_.x),
                                 Math.abs(this.rotations_.y));
      var clampLimit = 30;
      var clampedMaxRotation = Math.min(maxRotation, clampLimit);
      var percentToClampLimit = clampedMaxRotation / clampLimit;
      var persp = Math.pow(Math.E,
                           lerp(Math.log(5000), Math.log(500),
                                percentToClampLimit));
      this.targetElement_.webkitPerspective = persp;
      var effectiveThickness = vpThickness * percentToClampLimit;

      // Set depth of each layer such that they center around 0.
      var deepestLayerZ = -effectiveThickness * 0.5;
      var depthIncreasePerLayer = effectiveThickness /
          Math.max(1, numLayers - 1);
      for (var i = 0; i < numLayers; i++) {
        var layer = layers[i];
        var newDepth = deepestLayerZ + i * depthIncreasePerLayer;
        layer.style.webkitTransform = 'translateZ(' + newDepth + 'px)';
      }

      // Set rotation matrix to whatever is stored.
      var transformString = '';
      transformString += 'rotateX(' + this.rotations_.x + 'deg)';
      transformString += ' rotateY(' + this.rotations_.y + 'deg)';
      var container = this.targetElement_.contentContainer;
      container.style.webkitTransform = transformString;
    },

    updateCameraStart_: function(x, y) {
      this.cameraStart_.x = x;
      this.cameraStart_.y = y;
      this.rotationStart_.x = this.rotations_.x;
      this.rotationStart_.y = this.rotations_.y;
    },

    updateCamera_: function(x, y) {
      var delta = {
        x: this.cameraStart_.x - x,
        y: this.cameraStart_.y - y
      };
      // update new rotation matrix (note the parameter swap)
      // "strength" is ration between mouse dist and rotation amount.
      this.rotations_.x = this.rotationStart_.x + delta.y *
          this.matrixParameters_.strengthRatioY;
      this.rotations_.y = this.rotationStart_.y + -delta.x *
          this.matrixParameters_.strengthRatioX;
      this.scheduleRepaint();
    },

    onMouseDown_: function(e) {
      this.updateCameraStart_(e.x, e.y);
      document.addEventListener('mousemove', this.onMouseMove_);
      document.addEventListener('mouseup', this.onMouseUp_);
      e.preventDefault();
      return true;
    },

    onMouseMove_: function(e) {
      this.updateCamera_(e.x, e.y);
    },

    onMouseUp_: function(e) {
      document.removeEventListener('mousemove', this.onMouseMove_);
      document.removeEventListener('mouseup', this.onMouseUp_);
      this.updateCamera_(e.x, e.y);
    },

  };

  return {
    Camera: Camera
  };
});