// 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 }; });