browser/devtools/tilt/tilt-visualizer.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/tilt/tilt-visualizer.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2253 @@
     1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +"use strict";
    1.10 +
    1.11 +const {Cu, Ci, ChromeWorker} = require("chrome");
    1.12 +
    1.13 +let TiltGL = require("devtools/tilt/tilt-gl");
    1.14 +let TiltUtils = require("devtools/tilt/tilt-utils");
    1.15 +let TiltVisualizerStyle = require("devtools/tilt/tilt-visualizer-style");
    1.16 +let {EPSILON, TiltMath, vec3, mat4, quat4} = require("devtools/tilt/tilt-math");
    1.17 +let {TargetFactory} = require("devtools/framework/target");
    1.18 +
    1.19 +Cu.import("resource://gre/modules/Services.jsm");
    1.20 +Cu.import("resource:///modules/devtools/gDevTools.jsm");
    1.21 +
    1.22 +const ELEMENT_MIN_SIZE = 4;
    1.23 +const INVISIBLE_ELEMENTS = {
    1.24 +  "head": true,
    1.25 +  "base": true,
    1.26 +  "basefont": true,
    1.27 +  "isindex": true,
    1.28 +  "link": true,
    1.29 +  "meta": true,
    1.30 +  "option": true,
    1.31 +  "script": true,
    1.32 +  "style": true,
    1.33 +  "title": true
    1.34 +};
    1.35 +
    1.36 +// a node is represented in the visualization mesh as a rectangular stack
    1.37 +// of 5 quads composed of 12 vertices; we draw these as triangles using an
    1.38 +// index buffer of 12 unsigned int elements, obviously one for each vertex;
    1.39 +// if a webpage has enough nodes to overflow the index buffer elements size,
    1.40 +// weird things may happen; thus, when necessary, we'll split into groups
    1.41 +const MAX_GROUP_NODES = Math.pow(2, Uint16Array.BYTES_PER_ELEMENT * 8) / 12 - 1;
    1.42 +
    1.43 +const WIREFRAME_COLOR = [0, 0, 0, 0.25];
    1.44 +const INTRO_TRANSITION_DURATION = 1000;
    1.45 +const OUTRO_TRANSITION_DURATION = 800;
    1.46 +const INITIAL_Z_TRANSLATION = 400;
    1.47 +const MOVE_INTO_VIEW_ACCURACY = 50;
    1.48 +
    1.49 +const MOUSE_CLICK_THRESHOLD = 10;
    1.50 +const MOUSE_INTRO_DELAY = 200;
    1.51 +const ARCBALL_SENSITIVITY = 0.5;
    1.52 +const ARCBALL_ROTATION_STEP = 0.15;
    1.53 +const ARCBALL_TRANSLATION_STEP = 35;
    1.54 +const ARCBALL_ZOOM_STEP = 0.1;
    1.55 +const ARCBALL_ZOOM_MIN = -3000;
    1.56 +const ARCBALL_ZOOM_MAX = 500;
    1.57 +const ARCBALL_RESET_SPHERICAL_FACTOR = 0.1;
    1.58 +const ARCBALL_RESET_LINEAR_FACTOR = 0.01;
    1.59 +
    1.60 +const TILT_CRAFTER = "resource:///modules/devtools/tilt/TiltWorkerCrafter.js";
    1.61 +const TILT_PICKER = "resource:///modules/devtools/tilt/TiltWorkerPicker.js";
    1.62 +
    1.63 +
    1.64 +/**
    1.65 + * Initializes the visualization presenter and controller.
    1.66 + *
    1.67 + * @param {Object} aProperties
    1.68 + *                 an object containing the following properties:
    1.69 + *        {Window} chromeWindow: a reference to the top level window
    1.70 + *        {Window} contentWindow: the content window holding the visualized doc
    1.71 + *       {Element} parentNode: the parent node to hold the visualization
    1.72 + *        {Object} notifications: necessary notifications for Tilt
    1.73 + *      {Function} onError: optional, function called if initialization failed
    1.74 + *      {Function} onLoad: optional, function called if initialization worked
    1.75 + */
    1.76 +function TiltVisualizer(aProperties)
    1.77 +{
    1.78 +  // make sure the properties parameter is a valid object
    1.79 +  aProperties = aProperties || {};
    1.80 +
    1.81 +  /**
    1.82 +   * Save a reference to the top-level window.
    1.83 +   */
    1.84 +  this.chromeWindow = aProperties.chromeWindow;
    1.85 +  this.tab = aProperties.tab;
    1.86 +
    1.87 +  /**
    1.88 +   * The canvas element used for rendering the visualization.
    1.89 +   */
    1.90 +  this.canvas = TiltUtils.DOM.initCanvas(aProperties.parentNode, {
    1.91 +    focusable: true,
    1.92 +    append: true
    1.93 +  });
    1.94 +
    1.95 +  /**
    1.96 +   * Visualization logic and drawing loop.
    1.97 +   */
    1.98 +  this.presenter = new TiltVisualizer.Presenter(this.canvas,
    1.99 +    aProperties.chromeWindow,
   1.100 +    aProperties.contentWindow,
   1.101 +    aProperties.notifications,
   1.102 +    aProperties.onError || null,
   1.103 +    aProperties.onLoad || null);
   1.104 +
   1.105 +  /**
   1.106 +   * Visualization mouse and keyboard controller.
   1.107 +   */
   1.108 +  this.controller = new TiltVisualizer.Controller(this.canvas, this.presenter);
   1.109 +}
   1.110 +
   1.111 +exports.TiltVisualizer = TiltVisualizer;
   1.112 +
   1.113 +TiltVisualizer.prototype = {
   1.114 +
   1.115 +  /**
   1.116 +   * Initializes the visualizer.
   1.117 +   */
   1.118 +  init: function TV_init()
   1.119 +  {
   1.120 +    this.presenter.init();
   1.121 +    this.bindToInspector(this.tab);
   1.122 +  },
   1.123 +
   1.124 +  /**
   1.125 +   * Checks if this object was initialized properly.
   1.126 +   *
   1.127 +   * @return {Boolean} true if the object was initialized properly
   1.128 +   */
   1.129 +  isInitialized: function TV_isInitialized()
   1.130 +  {
   1.131 +    return this.presenter && this.presenter.isInitialized() &&
   1.132 +           this.controller && this.controller.isInitialized();
   1.133 +  },
   1.134 +
   1.135 +  /**
   1.136 +   * Removes the overlay canvas used for rendering the visualization.
   1.137 +   */
   1.138 +  removeOverlay: function TV_removeOverlay()
   1.139 +  {
   1.140 +    if (this.canvas && this.canvas.parentNode) {
   1.141 +      this.canvas.parentNode.removeChild(this.canvas);
   1.142 +    }
   1.143 +  },
   1.144 +
   1.145 +  /**
   1.146 +   * Explicitly cleans up this visualizer and sets everything to null.
   1.147 +   */
   1.148 +  cleanup: function TV_cleanup()
   1.149 +  {
   1.150 +    this.unbindInspector();
   1.151 +
   1.152 +    if (this.controller) {
   1.153 +      TiltUtils.destroyObject(this.controller);
   1.154 +    }
   1.155 +    if (this.presenter) {
   1.156 +      TiltUtils.destroyObject(this.presenter);
   1.157 +    }
   1.158 +
   1.159 +    let chromeWindow = this.chromeWindow;
   1.160 +
   1.161 +    TiltUtils.destroyObject(this);
   1.162 +    TiltUtils.clearCache();
   1.163 +    TiltUtils.gc(chromeWindow);
   1.164 +  },
   1.165 +
   1.166 +  /**
   1.167 +   * Listen to the inspector activity.
   1.168 +   */
   1.169 +  bindToInspector: function TV_bindToInspector(aTab)
   1.170 +  {
   1.171 +    this._browserTab = aTab;
   1.172 +
   1.173 +    this.onNewNodeFromInspector = this.onNewNodeFromInspector.bind(this);
   1.174 +    this.onNewNodeFromTilt = this.onNewNodeFromTilt.bind(this);
   1.175 +    this.onInspectorReady = this.onInspectorReady.bind(this);
   1.176 +    this.onToolboxDestroyed = this.onToolboxDestroyed.bind(this);
   1.177 +
   1.178 +    gDevTools.on("inspector-ready", this.onInspectorReady);
   1.179 +    gDevTools.on("toolbox-destroyed", this.onToolboxDestroyed);
   1.180 +
   1.181 +    Services.obs.addObserver(this.onNewNodeFromTilt,
   1.182 +                             this.presenter.NOTIFICATIONS.HIGHLIGHTING,
   1.183 +                             false);
   1.184 +    Services.obs.addObserver(this.onNewNodeFromTilt,
   1.185 +                             this.presenter.NOTIFICATIONS.UNHIGHLIGHTING,
   1.186 +                             false);
   1.187 +
   1.188 +    let target = TargetFactory.forTab(aTab);
   1.189 +    let toolbox = gDevTools.getToolbox(target);
   1.190 +    if (toolbox) {
   1.191 +      let panel = toolbox.getPanel("inspector");
   1.192 +      if (panel) {
   1.193 +        this.inspector = panel;
   1.194 +        this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
   1.195 +        this.onNewNodeFromInspector();
   1.196 +      }
   1.197 +    }
   1.198 +  },
   1.199 +
   1.200 +  /**
   1.201 +   * Unregister inspector event listeners.
   1.202 +   */
   1.203 +  unbindInspector: function TV_unbindInspector()
   1.204 +  {
   1.205 +    this._browserTab = null;
   1.206 +
   1.207 +    if (this.inspector) {
   1.208 +      if (this.inspector.selection) {
   1.209 +        this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
   1.210 +      }
   1.211 +      this.inspector = null;
   1.212 +    }
   1.213 +
   1.214 +    gDevTools.off("inspector-ready", this.onInspectorReady);
   1.215 +    gDevTools.off("toolbox-destroyed", this.onToolboxDestroyed);
   1.216 +
   1.217 +    Services.obs.removeObserver(this.onNewNodeFromTilt,
   1.218 +                                this.presenter.NOTIFICATIONS.HIGHLIGHTING);
   1.219 +    Services.obs.removeObserver(this.onNewNodeFromTilt,
   1.220 +                                this.presenter.NOTIFICATIONS.UNHIGHLIGHTING);
   1.221 +  },
   1.222 +
   1.223 +  /**
   1.224 +   * When a new inspector is started.
   1.225 +   */
   1.226 +  onInspectorReady: function TV_onInspectorReady(event, toolbox, panel)
   1.227 +  {
   1.228 +    if (toolbox.target.tab === this._browserTab) {
   1.229 +      this.inspector = panel;
   1.230 +      this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
   1.231 +      this.onNewNodeFromTilt();
   1.232 +    }
   1.233 +  },
   1.234 +
   1.235 +  /**
   1.236 +   * When the toolbox, therefor the inspector, is closed.
   1.237 +   */
   1.238 +  onToolboxDestroyed: function TV_onToolboxDestroyed(event, tab)
   1.239 +  {
   1.240 +    if (tab === this._browserTab &&
   1.241 +        this.inspector) {
   1.242 +      if (this.inspector.selection) {
   1.243 +        this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
   1.244 +      }
   1.245 +      this.inspector = null;
   1.246 +    }
   1.247 +  },
   1.248 +
   1.249 +  /**
   1.250 +   * When a new node is selected in the inspector.
   1.251 +   */
   1.252 +  onNewNodeFromInspector: function TV_onNewNodeFromInspector()
   1.253 +  {
   1.254 +    if (this.inspector &&
   1.255 +        this.inspector.selection.reason != "tilt") {
   1.256 +      let selection = this.inspector.selection;
   1.257 +      let canHighlightNode = selection.isNode() &&
   1.258 +                              selection.isConnected() &&
   1.259 +                              selection.isElementNode();
   1.260 +      if (canHighlightNode) {
   1.261 +        this.presenter.highlightNode(selection.node);
   1.262 +      } else {
   1.263 +        this.presenter.highlightNodeFor(-1);
   1.264 +      }
   1.265 +    }
   1.266 +  },
   1.267 +
   1.268 +  /**
   1.269 +   * When a new node is selected in Tilt.
   1.270 +   */
   1.271 +  onNewNodeFromTilt: function TV_onNewNodeFromTilt()
   1.272 +  {
   1.273 +    if (!this.inspector) {
   1.274 +      return;
   1.275 +    }
   1.276 +    let nodeIndex = this.presenter._currentSelection;
   1.277 +    if (nodeIndex < 0) {
   1.278 +      this.inspector.selection.setNodeFront(null, "tilt");
   1.279 +    }
   1.280 +    let node = this.presenter._traverseData.nodes[nodeIndex];
   1.281 +    node = this.inspector.walker.frontForRawNode(node);
   1.282 +    this.inspector.selection.setNodeFront(node, "tilt");
   1.283 +  },
   1.284 +};
   1.285 +
   1.286 +/**
   1.287 + * This object manages the visualization logic and drawing loop.
   1.288 + *
   1.289 + * @param {HTMLCanvasElement} aCanvas
   1.290 + *                            the canvas element used for rendering
   1.291 + * @param {Window} aChromeWindow
   1.292 + *                 a reference to the top-level window
   1.293 + * @param {Window} aContentWindow
   1.294 + *                 the content window holding the document to be visualized
   1.295 + * @param {Object} aNotifications
   1.296 + *                 necessary notifications for Tilt
   1.297 + * @param {Function} onError
   1.298 + *                   function called if initialization failed
   1.299 + * @param {Function} onLoad
   1.300 + *                   function called if initialization worked
   1.301 + */
   1.302 +TiltVisualizer.Presenter = function TV_Presenter(
   1.303 +  aCanvas, aChromeWindow, aContentWindow, aNotifications, onError, onLoad)
   1.304 +{
   1.305 +  /**
   1.306 +   * A canvas overlay used for drawing the visualization.
   1.307 +   */
   1.308 +  this.canvas = aCanvas;
   1.309 +
   1.310 +  /**
   1.311 +   * Save a reference to the top-level window, to access Tilt.
   1.312 +   */
   1.313 +  this.chromeWindow = aChromeWindow;
   1.314 +
   1.315 +  /**
   1.316 +   * The content window generating the visualization
   1.317 +   */
   1.318 +  this.contentWindow = aContentWindow;
   1.319 +
   1.320 +  /**
   1.321 +   * Shortcut for accessing notifications strings.
   1.322 +   */
   1.323 +  this.NOTIFICATIONS = aNotifications;
   1.324 +
   1.325 +  /**
   1.326 +   * Use the default node callback function
   1.327 +   */
   1.328 +  this.nodeCallback = null;
   1.329 +
   1.330 +  /**
   1.331 +   * Create the renderer, containing useful functions for easy drawing.
   1.332 +   */
   1.333 +  this._renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
   1.334 +
   1.335 +  /**
   1.336 +   * A custom shader used for drawing the visualization mesh.
   1.337 +   */
   1.338 +  this._visualizationProgram = null;
   1.339 +
   1.340 +  /**
   1.341 +   * The combined mesh representing the document visualization.
   1.342 +   */
   1.343 +  this._texture = null;
   1.344 +  this._meshData = null;
   1.345 +  this._meshStacks = null;
   1.346 +  this._meshWireframe = null;
   1.347 +  this._traverseData = null;
   1.348 +
   1.349 +  /**
   1.350 +   * A highlight quad drawn over a stacked dom node.
   1.351 +   */
   1.352 +  this._highlight = {
   1.353 +    disabled: true,
   1.354 +    v0: vec3.create(),
   1.355 +    v1: vec3.create(),
   1.356 +    v2: vec3.create(),
   1.357 +    v3: vec3.create()
   1.358 +  };
   1.359 +
   1.360 +  /**
   1.361 +   * Scene transformations, exposing offset, translation and rotation.
   1.362 +   * Modified by events in the controller through delegate functions.
   1.363 +   */
   1.364 +  this.transforms = {
   1.365 +    zoom: 1,
   1.366 +    offset: vec3.create(),      // mesh offset, aligned to the viewport center
   1.367 +    translation: vec3.create(), // scene translation, on the [x, y, z] axis
   1.368 +    rotation: quat4.create()    // scene rotation, expressed as a quaternion
   1.369 +  };
   1.370 +
   1.371 +  /**
   1.372 +   * Variables holding information about the initial and current node selected.
   1.373 +   */
   1.374 +  this._currentSelection = -1; // the selected node index
   1.375 +  this._initialMeshConfiguration = false; // true if the 3D mesh was configured
   1.376 +
   1.377 +  /**
   1.378 +   * Variable specifying if the scene should be redrawn.
   1.379 +   * This should happen usually when the visualization is translated/rotated.
   1.380 +   */
   1.381 +  this._redraw = true;
   1.382 +
   1.383 +  /**
   1.384 +   * Total time passed since the rendering started.
   1.385 +   * If the rendering is paused, this property won't get updated.
   1.386 +   */
   1.387 +  this._time = 0;
   1.388 +
   1.389 +  /**
   1.390 +   * Frame delta time (the ammount of time passed for each frame).
   1.391 +   * This is used to smoothly interpolate animation transfroms.
   1.392 +   */
   1.393 +  this._delta = 0;
   1.394 +  this._prevFrameTime = 0;
   1.395 +  this._currFrameTime = 0;
   1.396 +};
   1.397 +
   1.398 +TiltVisualizer.Presenter.prototype = {
   1.399 +
   1.400 +  /**
   1.401 +   * Initializes the presenter and starts the animation loop
   1.402 +   */
   1.403 +  init: function TVP_init()
   1.404 +  {
   1.405 +    this._setup();
   1.406 +    this._loop();
   1.407 +  },
   1.408 +
   1.409 +  /**
   1.410 +   * The initialization logic.
   1.411 +   */
   1.412 +  _setup: function TVP__setup()
   1.413 +  {
   1.414 +    let renderer = this._renderer;
   1.415 +
   1.416 +    // if the renderer was destroyed, don't continue setup
   1.417 +    if (!renderer || !renderer.context) {
   1.418 +      return;
   1.419 +    }
   1.420 +
   1.421 +    // create the visualization shaders and program to draw the stacks mesh
   1.422 +    this._visualizationProgram = new renderer.Program({
   1.423 +      vs: TiltVisualizer.MeshShader.vs,
   1.424 +      fs: TiltVisualizer.MeshShader.fs,
   1.425 +      attributes: ["vertexPosition", "vertexTexCoord", "vertexColor"],
   1.426 +      uniforms: ["mvMatrix", "projMatrix", "sampler"]
   1.427 +    });
   1.428 +
   1.429 +    // get the document zoom to properly scale the visualization
   1.430 +    this.transforms.zoom = this._getPageZoom();
   1.431 +
   1.432 +    // bind the owner object to the necessary functions
   1.433 +    TiltUtils.bindObjectFunc(this, "^_on");
   1.434 +    TiltUtils.bindObjectFunc(this, "_loop");
   1.435 +
   1.436 +    this._setupTexture();
   1.437 +    this._setupMeshData();
   1.438 +    this._setupEventListeners();
   1.439 +    this.canvas.focus();
   1.440 +  },
   1.441 +
   1.442 +  /**
   1.443 +   * Get page zoom factor.
   1.444 +   * @return {Number}
   1.445 +   */
   1.446 +  _getPageZoom: function TVP__getPageZoom() {
   1.447 +    return this.contentWindow
   1.448 +      .QueryInterface(Ci.nsIInterfaceRequestor)
   1.449 +      .getInterface(Ci.nsIDOMWindowUtils)
   1.450 +      .fullZoom;
   1.451 +  },
   1.452 +
   1.453 +  /**
   1.454 +   * The animation logic.
   1.455 +   */
   1.456 +  _loop: function TVP__loop()
   1.457 +  {
   1.458 +    let renderer = this._renderer;
   1.459 +
   1.460 +    // if the renderer was destroyed, don't continue rendering
   1.461 +    if (!renderer || !renderer.context) {
   1.462 +      return;
   1.463 +    }
   1.464 +
   1.465 +    // prepare for the next frame of the animation loop
   1.466 +    this.chromeWindow.mozRequestAnimationFrame(this._loop);
   1.467 +
   1.468 +    // only redraw if we really have to
   1.469 +    if (this._redraw) {
   1.470 +      this._redraw = false;
   1.471 +      this._drawVisualization();
   1.472 +    }
   1.473 +
   1.474 +    // update the current presenter transfroms from the controller
   1.475 +    if ("function" === typeof this._controllerUpdate) {
   1.476 +      this._controllerUpdate(this._time, this._delta);
   1.477 +    }
   1.478 +
   1.479 +    this._handleFrameDelta();
   1.480 +    this._handleKeyframeNotifications();
   1.481 +  },
   1.482 +
   1.483 +  /**
   1.484 +   * Calculates the current frame delta time.
   1.485 +   */
   1.486 +  _handleFrameDelta: function TVP__handleFrameDelta()
   1.487 +  {
   1.488 +    this._prevFrameTime = this._currFrameTime;
   1.489 +    this._currFrameTime = this.chromeWindow.mozAnimationStartTime;
   1.490 +    this._delta = this._currFrameTime - this._prevFrameTime;
   1.491 +  },
   1.492 +
   1.493 +  /**
   1.494 +   * Draws the visualization mesh and highlight quad.
   1.495 +   */
   1.496 +  _drawVisualization: function TVP__drawVisualization()
   1.497 +  {
   1.498 +    let renderer = this._renderer;
   1.499 +    let transforms = this.transforms;
   1.500 +    let w = renderer.width;
   1.501 +    let h = renderer.height;
   1.502 +    let ih = renderer.initialHeight;
   1.503 +
   1.504 +    // if the mesh wasn't created yet, don't continue rendering
   1.505 +    if (!this._meshStacks || !this._meshWireframe) {
   1.506 +      return;
   1.507 +    }
   1.508 +
   1.509 +    // clear the context to an opaque black background
   1.510 +    renderer.clear();
   1.511 +    renderer.perspective();
   1.512 +
   1.513 +    // apply a transition transformation using an ortho and perspective matrix
   1.514 +    let ortho = mat4.ortho(0, w, h, 0, -1000, 1000);
   1.515 +
   1.516 +    if (!this._isExecutingDestruction) {
   1.517 +      let f = this._time / INTRO_TRANSITION_DURATION;
   1.518 +      renderer.lerp(renderer.projMatrix, ortho, f, 8);
   1.519 +    } else {
   1.520 +      let f = this._time / OUTRO_TRANSITION_DURATION;
   1.521 +      renderer.lerp(renderer.projMatrix, ortho, 1 - f, 8);
   1.522 +    }
   1.523 +
   1.524 +    // apply the preliminary transformations to the model view
   1.525 +    renderer.translate(w * 0.5, ih * 0.5, -INITIAL_Z_TRANSLATION);
   1.526 +
   1.527 +    // calculate the camera matrix using the rotation and translation
   1.528 +    renderer.translate(transforms.translation[0], 0,
   1.529 +                       transforms.translation[2]);
   1.530 +
   1.531 +    renderer.transform(quat4.toMat4(transforms.rotation));
   1.532 +
   1.533 +    // offset the visualization mesh to center
   1.534 +    renderer.translate(transforms.offset[0],
   1.535 +                       transforms.offset[1] + transforms.translation[1], 0);
   1.536 +
   1.537 +    renderer.scale(transforms.zoom, transforms.zoom);
   1.538 +
   1.539 +    // draw the visualization mesh
   1.540 +    renderer.strokeWeight(2);
   1.541 +    renderer.depthTest(true);
   1.542 +    this._drawMeshStacks();
   1.543 +    this._drawMeshWireframe();
   1.544 +    this._drawHighlight();
   1.545 +
   1.546 +    // make sure the initial transition is drawn until finished
   1.547 +    if (this._time < INTRO_TRANSITION_DURATION ||
   1.548 +        this._time < OUTRO_TRANSITION_DURATION) {
   1.549 +      this._redraw = true;
   1.550 +    }
   1.551 +    this._time += this._delta;
   1.552 +  },
   1.553 +
   1.554 +  /**
   1.555 +   * Draws the meshStacks object.
   1.556 +   */
   1.557 +  _drawMeshStacks: function TVP__drawMeshStacks()
   1.558 +  {
   1.559 +    let renderer = this._renderer;
   1.560 +    let mesh = this._meshStacks;
   1.561 +
   1.562 +    let visualizationProgram = this._visualizationProgram;
   1.563 +    let texture = this._texture;
   1.564 +    let mvMatrix = renderer.mvMatrix;
   1.565 +    let projMatrix = renderer.projMatrix;
   1.566 +
   1.567 +    // use the necessary shader
   1.568 +    visualizationProgram.use();
   1.569 +
   1.570 +    for (let i = 0, len = mesh.length; i < len; i++) {
   1.571 +      let group = mesh[i];
   1.572 +
   1.573 +      // bind the attributes and uniforms as necessary
   1.574 +      visualizationProgram.bindVertexBuffer("vertexPosition", group.vertices);
   1.575 +      visualizationProgram.bindVertexBuffer("vertexTexCoord", group.texCoord);
   1.576 +      visualizationProgram.bindVertexBuffer("vertexColor", group.color);
   1.577 +
   1.578 +      visualizationProgram.bindUniformMatrix("mvMatrix", mvMatrix);
   1.579 +      visualizationProgram.bindUniformMatrix("projMatrix", projMatrix);
   1.580 +      visualizationProgram.bindTexture("sampler", texture);
   1.581 +
   1.582 +      // draw the vertices as TRIANGLES indexed elements
   1.583 +      renderer.drawIndexedVertices(renderer.context.TRIANGLES, group.indices);
   1.584 +    }
   1.585 +
   1.586 +    // save the current model view and projection matrices
   1.587 +    mesh.mvMatrix = mat4.create(mvMatrix);
   1.588 +    mesh.projMatrix = mat4.create(projMatrix);
   1.589 +  },
   1.590 +
   1.591 +  /**
   1.592 +   * Draws the meshWireframe object.
   1.593 +   */
   1.594 +  _drawMeshWireframe: function TVP__drawMeshWireframe()
   1.595 +  {
   1.596 +    let renderer = this._renderer;
   1.597 +    let mesh = this._meshWireframe;
   1.598 +
   1.599 +    for (let i = 0, len = mesh.length; i < len; i++) {
   1.600 +      let group = mesh[i];
   1.601 +
   1.602 +      // use the necessary shader
   1.603 +      renderer.useColorShader(group.vertices, WIREFRAME_COLOR);
   1.604 +
   1.605 +      // draw the vertices as LINES indexed elements
   1.606 +      renderer.drawIndexedVertices(renderer.context.LINES, group.indices);
   1.607 +    }
   1.608 +  },
   1.609 +
   1.610 +  /**
   1.611 +   * Draws a highlighted quad around a currently selected node.
   1.612 +   */
   1.613 +  _drawHighlight: function TVP__drawHighlight()
   1.614 +  {
   1.615 +    // check if there's anything to highlight (i.e any node is selected)
   1.616 +    if (!this._highlight.disabled) {
   1.617 +
   1.618 +      // set the corresponding state to draw the highlight quad
   1.619 +      let renderer = this._renderer;
   1.620 +      let highlight = this._highlight;
   1.621 +
   1.622 +      renderer.depthTest(false);
   1.623 +      renderer.fill(highlight.fill, 0.5);
   1.624 +      renderer.stroke(highlight.stroke);
   1.625 +      renderer.strokeWeight(highlight.strokeWeight);
   1.626 +      renderer.quad(highlight.v0, highlight.v1, highlight.v2, highlight.v3);
   1.627 +    }
   1.628 +  },
   1.629 +
   1.630 +  /**
   1.631 +   * Creates or refreshes the texture applied to the visualization mesh.
   1.632 +   */
   1.633 +  _setupTexture: function TVP__setupTexture()
   1.634 +  {
   1.635 +    let renderer = this._renderer;
   1.636 +
   1.637 +    // destroy any previously created texture
   1.638 +    TiltUtils.destroyObject(this._texture); this._texture = null;
   1.639 +
   1.640 +    // if the renderer was destroyed, don't continue setup
   1.641 +    if (!renderer || !renderer.context) {
   1.642 +      return;
   1.643 +    }
   1.644 +
   1.645 +    // get the maximum texture size
   1.646 +    this._maxTextureSize =
   1.647 +      renderer.context.getParameter(renderer.context.MAX_TEXTURE_SIZE);
   1.648 +
   1.649 +    // use a simple shim to get the image representation of the document
   1.650 +    // this will be removed once the MOZ_window_region_texture bug #653656
   1.651 +    // is finished; currently just converting the document image to a texture
   1.652 +    // applied to the mesh
   1.653 +    this._texture = new renderer.Texture({
   1.654 +      source: TiltGL.TextureUtils.createContentImage(this.contentWindow,
   1.655 +                                                     this._maxTextureSize),
   1.656 +      format: "RGB"
   1.657 +    });
   1.658 +
   1.659 +    if ("function" === typeof this._onSetupTexture) {
   1.660 +      this._onSetupTexture();
   1.661 +      this._onSetupTexture = null;
   1.662 +    }
   1.663 +  },
   1.664 +
   1.665 +  /**
   1.666 +   * Create the combined mesh representing the document visualization by
   1.667 +   * traversing the document & adding a stack for each node that is drawable.
   1.668 +   *
   1.669 +   * @param {Object} aMeshData
   1.670 +   *                 object containing the necessary mesh verts, texcoord etc.
   1.671 +   */
   1.672 +  _setupMesh: function TVP__setupMesh(aMeshData)
   1.673 +  {
   1.674 +    let renderer = this._renderer;
   1.675 +
   1.676 +    // destroy any previously created mesh
   1.677 +    TiltUtils.destroyObject(this._meshStacks); this._meshStacks = [];
   1.678 +    TiltUtils.destroyObject(this._meshWireframe); this._meshWireframe = [];
   1.679 +
   1.680 +    // if the renderer was destroyed, don't continue setup
   1.681 +    if (!renderer || !renderer.context) {
   1.682 +      return;
   1.683 +    }
   1.684 +
   1.685 +    // save the mesh data for future use
   1.686 +    this._meshData = aMeshData;
   1.687 +
   1.688 +    // create a sub-mesh for each group in the mesh data
   1.689 +    for (let i = 0, len = aMeshData.groups.length; i < len; i++) {
   1.690 +      let group = aMeshData.groups[i];
   1.691 +
   1.692 +      // create the visualization mesh using the vertices, texture coordinates
   1.693 +      // and indices computed when traversing the document object model
   1.694 +      this._meshStacks.push({
   1.695 +        vertices: new renderer.VertexBuffer(group.vertices, 3),
   1.696 +        texCoord: new renderer.VertexBuffer(group.texCoord, 2),
   1.697 +        color: new renderer.VertexBuffer(group.color, 3),
   1.698 +        indices: new renderer.IndexBuffer(group.stacksIndices)
   1.699 +      });
   1.700 +
   1.701 +      // additionally, create a wireframe representation to make the
   1.702 +      // visualization a bit more pretty
   1.703 +      this._meshWireframe.push({
   1.704 +        vertices: this._meshStacks[i].vertices,
   1.705 +        indices: new renderer.IndexBuffer(group.wireframeIndices)
   1.706 +      });
   1.707 +    }
   1.708 +
   1.709 +    // configure the required mesh transformations and background only once
   1.710 +    if (!this._initialMeshConfiguration) {
   1.711 +      this._initialMeshConfiguration = true;
   1.712 +
   1.713 +      // set the necessary mesh offsets
   1.714 +      this.transforms.offset[0] = -renderer.width * 0.5;
   1.715 +      this.transforms.offset[1] = -renderer.height * 0.5;
   1.716 +
   1.717 +      // make sure the canvas is opaque now that the initialization is finished
   1.718 +      this.canvas.style.background = TiltVisualizerStyle.canvas.background;
   1.719 +
   1.720 +      this._drawVisualization();
   1.721 +      this._redraw = true;
   1.722 +    }
   1.723 +
   1.724 +    if ("function" === typeof this._onSetupMesh) {
   1.725 +      this._onSetupMesh();
   1.726 +      this._onSetupMesh = null;
   1.727 +    }
   1.728 +  },
   1.729 +
   1.730 +  /**
   1.731 +   * Computes the mesh vertices, texture coordinates etc. by groups of nodes.
   1.732 +   */
   1.733 +  _setupMeshData: function TVP__setupMeshData()
   1.734 +  {
   1.735 +    let renderer = this._renderer;
   1.736 +
   1.737 +    // if the renderer was destroyed, don't continue setup
   1.738 +    if (!renderer || !renderer.context) {
   1.739 +      return;
   1.740 +    }
   1.741 +
   1.742 +    // traverse the document and get the depths, coordinates and local names
   1.743 +    this._traverseData = TiltUtils.DOM.traverse(this.contentWindow, {
   1.744 +      nodeCallback: this.nodeCallback,
   1.745 +      invisibleElements: INVISIBLE_ELEMENTS,
   1.746 +      minSize: ELEMENT_MIN_SIZE,
   1.747 +      maxX: this._texture.width,
   1.748 +      maxY: this._texture.height
   1.749 +    });
   1.750 +
   1.751 +    let worker = new ChromeWorker(TILT_CRAFTER);
   1.752 +
   1.753 +    worker.addEventListener("message", function TVP_onMessage(event) {
   1.754 +      this._setupMesh(event.data);
   1.755 +    }.bind(this), false);
   1.756 +
   1.757 +    // calculate necessary information regarding vertices, texture coordinates
   1.758 +    // etc. in a separate thread, as this process may take a while
   1.759 +    worker.postMessage({
   1.760 +      maxGroupNodes: MAX_GROUP_NODES,
   1.761 +      style: TiltVisualizerStyle.nodes,
   1.762 +      texWidth: this._texture.width,
   1.763 +      texHeight: this._texture.height,
   1.764 +      nodesInfo: this._traverseData.info
   1.765 +    });
   1.766 +  },
   1.767 +
   1.768 +  /**
   1.769 +   * Sets up event listeners necessary for the presenter.
   1.770 +   */
   1.771 +  _setupEventListeners: function TVP__setupEventListeners()
   1.772 +  {
   1.773 +    this.contentWindow.addEventListener("resize", this._onResize, false);
   1.774 +  },
   1.775 +
   1.776 +  /**
   1.777 +   * Called when the content window of the current browser is resized.
   1.778 +   */
   1.779 +  _onResize: function TVP_onResize(e)
   1.780 +  {
   1.781 +    let zoom = this._getPageZoom();
   1.782 +    let width = e.target.innerWidth * zoom;
   1.783 +    let height = e.target.innerHeight * zoom;
   1.784 +
   1.785 +    // handle aspect ratio changes to update the projection matrix
   1.786 +    this._renderer.width = width;
   1.787 +    this._renderer.height = height;
   1.788 +
   1.789 +    this._redraw = true;
   1.790 +  },
   1.791 +
   1.792 +  /**
   1.793 +   * Highlights a specific node.
   1.794 +   *
   1.795 +   * @param {Element} aNode
   1.796 +   *                  the html node to be highlighted
   1.797 +   * @param {String} aFlags
   1.798 +   *                 flags specifying highlighting options
   1.799 +   */
   1.800 +  highlightNode: function TVP_highlightNode(aNode, aFlags)
   1.801 +  {
   1.802 +    this.highlightNodeFor(this._traverseData.nodes.indexOf(aNode), aFlags);
   1.803 +  },
   1.804 +
   1.805 +  /**
   1.806 +   * Picks a stacked dom node at the x and y screen coordinates and highlights
   1.807 +   * the selected node in the mesh.
   1.808 +   *
   1.809 +   * @param {Number} x
   1.810 +   *                 the current horizontal coordinate of the mouse
   1.811 +   * @param {Number} y
   1.812 +   *                 the current vertical coordinate of the mouse
   1.813 +   * @param {Object} aProperties
   1.814 +   *                 an object containing the following properties:
   1.815 +   *      {Function} onpick: function to be called after picking succeeded
   1.816 +   *      {Function} onfail: function to be called after picking failed
   1.817 +   */
   1.818 +  highlightNodeAt: function TVP_highlightNodeAt(x, y, aProperties)
   1.819 +  {
   1.820 +    // make sure the properties parameter is a valid object
   1.821 +    aProperties = aProperties || {};
   1.822 +
   1.823 +    // try to pick a mesh node using the current x, y coordinates
   1.824 +    this.pickNode(x, y, {
   1.825 +
   1.826 +      /**
   1.827 +       * Mesh picking failed (nothing was found for the picked point).
   1.828 +       */
   1.829 +      onfail: function TVP_onHighlightFail()
   1.830 +      {
   1.831 +        this.highlightNodeFor(-1);
   1.832 +
   1.833 +        if ("function" === typeof aProperties.onfail) {
   1.834 +          aProperties.onfail();
   1.835 +        }
   1.836 +      }.bind(this),
   1.837 +
   1.838 +      /**
   1.839 +       * Mesh picking succeeded.
   1.840 +       *
   1.841 +       * @param {Object} aIntersection
   1.842 +       *                 object containing the intersection details
   1.843 +       */
   1.844 +      onpick: function TVP_onHighlightPick(aIntersection)
   1.845 +      {
   1.846 +        this.highlightNodeFor(aIntersection.index);
   1.847 +
   1.848 +        if ("function" === typeof aProperties.onpick) {
   1.849 +          aProperties.onpick();
   1.850 +        }
   1.851 +      }.bind(this)
   1.852 +    });
   1.853 +  },
   1.854 +
   1.855 +  /**
   1.856 +   * Sets the corresponding highlight coordinates and color based on the
   1.857 +   * information supplied.
   1.858 +   *
   1.859 +   * @param {Number} aNodeIndex
   1.860 +   *                 the index of the node in the this._traverseData array
   1.861 +   * @param {String} aFlags
   1.862 +   *                 flags specifying highlighting options
   1.863 +   */
   1.864 +  highlightNodeFor: function TVP_highlightNodeFor(aNodeIndex, aFlags)
   1.865 +  {
   1.866 +    this._redraw = true;
   1.867 +
   1.868 +    // if the node was already selected, don't do anything
   1.869 +    if (this._currentSelection === aNodeIndex) {
   1.870 +      return;
   1.871 +    }
   1.872 +
   1.873 +    // if an invalid or nonexisted node is specified, disable the highlight
   1.874 +    if (aNodeIndex < 0) {
   1.875 +      this._currentSelection = -1;
   1.876 +      this._highlight.disabled = true;
   1.877 +
   1.878 +      Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.UNHIGHLIGHTING, null);
   1.879 +      return;
   1.880 +    }
   1.881 +
   1.882 +    let highlight = this._highlight;
   1.883 +    let info = this._traverseData.info[aNodeIndex];
   1.884 +    let style = TiltVisualizerStyle.nodes;
   1.885 +
   1.886 +    highlight.disabled = false;
   1.887 +    highlight.fill = style[info.name] || style.highlight.defaultFill;
   1.888 +    highlight.stroke = style.highlight.defaultStroke;
   1.889 +    highlight.strokeWeight = style.highlight.defaultStrokeWeight;
   1.890 +
   1.891 +    let x = info.coord.left;
   1.892 +    let y = info.coord.top;
   1.893 +    let w = info.coord.width;
   1.894 +    let h = info.coord.height;
   1.895 +    let z = info.coord.depth + info.coord.thickness;
   1.896 +
   1.897 +    vec3.set([x,     y,     z], highlight.v0);
   1.898 +    vec3.set([x + w, y,     z], highlight.v1);
   1.899 +    vec3.set([x + w, y + h, z], highlight.v2);
   1.900 +    vec3.set([x,     y + h, z], highlight.v3);
   1.901 +
   1.902 +    this._currentSelection = aNodeIndex;
   1.903 +
   1.904 +    // if something is highlighted, make sure it's inside the current viewport;
   1.905 +    // the point which should be moved into view is considered the center [x, y]
   1.906 +    // position along the top edge of the currently selected node
   1.907 +
   1.908 +    if (aFlags && aFlags.indexOf("moveIntoView") !== -1)
   1.909 +    {
   1.910 +      this.controller.arcball.moveIntoView(vec3.lerp(
   1.911 +        vec3.scale(this._highlight.v0, this.transforms.zoom, []),
   1.912 +        vec3.scale(this._highlight.v1, this.transforms.zoom, []), 0.5));
   1.913 +    }
   1.914 +
   1.915 +    Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.HIGHLIGHTING, null);
   1.916 +  },
   1.917 +
   1.918 +  /**
   1.919 +   * Deletes a node from the visualization mesh.
   1.920 +   *
   1.921 +   * @param {Number} aNodeIndex
   1.922 +   *                 the index of the node in the this._traverseData array;
   1.923 +   *                 if not specified, it will default to the current selection
   1.924 +   */
   1.925 +  deleteNode: function TVP_deleteNode(aNodeIndex)
   1.926 +  {
   1.927 +    // we probably don't want to delete the html or body node.. just sayin'
   1.928 +    if ((aNodeIndex = aNodeIndex || this._currentSelection) < 1) {
   1.929 +      return;
   1.930 +    }
   1.931 +
   1.932 +    let renderer = this._renderer;
   1.933 +
   1.934 +    let groupIndex = parseInt(aNodeIndex / MAX_GROUP_NODES);
   1.935 +    let nodeIndex = parseInt((aNodeIndex + (groupIndex ? 1 : 0)) % MAX_GROUP_NODES);
   1.936 +    let group = this._meshStacks[groupIndex];
   1.937 +    let vertices = group.vertices.components;
   1.938 +
   1.939 +    for (let i = 0, k = 36 * nodeIndex; i < 36; i++) {
   1.940 +      vertices[i + k] = 0;
   1.941 +    }
   1.942 +
   1.943 +    group.vertices = new renderer.VertexBuffer(vertices, 3);
   1.944 +    this._highlight.disabled = true;
   1.945 +    this._redraw = true;
   1.946 +
   1.947 +    Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.NODE_REMOVED, null);
   1.948 +  },
   1.949 +
   1.950 +  /**
   1.951 +   * Picks a stacked dom node at the x and y screen coordinates and issues
   1.952 +   * a callback function with the found intersection.
   1.953 +   *
   1.954 +   * @param {Number} x
   1.955 +   *                 the current horizontal coordinate of the mouse
   1.956 +   * @param {Number} y
   1.957 +   *                 the current vertical coordinate of the mouse
   1.958 +   * @param {Object} aProperties
   1.959 +   *                 an object containing the following properties:
   1.960 +   *      {Function} onpick: function to be called at intersection
   1.961 +   *      {Function} onfail: function to be called if no intersections
   1.962 +   */
   1.963 +  pickNode: function TVP_pickNode(x, y, aProperties)
   1.964 +  {
   1.965 +    // make sure the properties parameter is a valid object
   1.966 +    aProperties = aProperties || {};
   1.967 +
   1.968 +    // if the mesh wasn't created yet, don't continue picking
   1.969 +    if (!this._meshStacks || !this._meshWireframe) {
   1.970 +      return;
   1.971 +    }
   1.972 +
   1.973 +    let worker = new ChromeWorker(TILT_PICKER);
   1.974 +
   1.975 +    worker.addEventListener("message", function TVP_onMessage(event) {
   1.976 +      if (event.data) {
   1.977 +        if ("function" === typeof aProperties.onpick) {
   1.978 +          aProperties.onpick(event.data);
   1.979 +        }
   1.980 +      } else {
   1.981 +        if ("function" === typeof aProperties.onfail) {
   1.982 +          aProperties.onfail();
   1.983 +        }
   1.984 +      }
   1.985 +    }, false);
   1.986 +
   1.987 +    let zoom = this._getPageZoom();
   1.988 +    let width = this._renderer.width * zoom;
   1.989 +    let height = this._renderer.height * zoom;
   1.990 +    x *= zoom;
   1.991 +    y *= zoom;
   1.992 +
   1.993 +    // create a ray following the mouse direction from the near clipping plane
   1.994 +    // to the far clipping plane, to check for intersections with the mesh,
   1.995 +    // and do all the heavy lifting in a separate thread
   1.996 +    worker.postMessage({
   1.997 +      vertices: this._meshData.allVertices,
   1.998 +
   1.999 +      // create the ray destined for 3D picking
  1.1000 +      ray: vec3.createRay([x, y, 0], [x, y, 1], [0, 0, width, height],
  1.1001 +        this._meshStacks.mvMatrix,
  1.1002 +        this._meshStacks.projMatrix)
  1.1003 +    });
  1.1004 +  },
  1.1005 +
  1.1006 +  /**
  1.1007 +   * Delegate translation method, used by the controller.
  1.1008 +   *
  1.1009 +   * @param {Array} aTranslation
  1.1010 +   *                the new translation on the [x, y, z] axis
  1.1011 +   */
  1.1012 +  setTranslation: function TVP_setTranslation(aTranslation)
  1.1013 +  {
  1.1014 +    let x = aTranslation[0];
  1.1015 +    let y = aTranslation[1];
  1.1016 +    let z = aTranslation[2];
  1.1017 +    let transforms = this.transforms;
  1.1018 +
  1.1019 +    // only update the translation if it's not already set
  1.1020 +    if (transforms.translation[0] !== x ||
  1.1021 +        transforms.translation[1] !== y ||
  1.1022 +        transforms.translation[2] !== z) {
  1.1023 +
  1.1024 +      vec3.set(aTranslation, transforms.translation);
  1.1025 +      this._redraw = true;
  1.1026 +    }
  1.1027 +  },
  1.1028 +
  1.1029 +  /**
  1.1030 +   * Delegate rotation method, used by the controller.
  1.1031 +   *
  1.1032 +   * @param {Array} aQuaternion
  1.1033 +   *                the rotation quaternion, as [x, y, z, w]
  1.1034 +   */
  1.1035 +  setRotation: function TVP_setRotation(aQuaternion)
  1.1036 +  {
  1.1037 +    let x = aQuaternion[0];
  1.1038 +    let y = aQuaternion[1];
  1.1039 +    let z = aQuaternion[2];
  1.1040 +    let w = aQuaternion[3];
  1.1041 +    let transforms = this.transforms;
  1.1042 +
  1.1043 +    // only update the rotation if it's not already set
  1.1044 +    if (transforms.rotation[0] !== x ||
  1.1045 +        transforms.rotation[1] !== y ||
  1.1046 +        transforms.rotation[2] !== z ||
  1.1047 +        transforms.rotation[3] !== w) {
  1.1048 +
  1.1049 +      quat4.set(aQuaternion, transforms.rotation);
  1.1050 +      this._redraw = true;
  1.1051 +    }
  1.1052 +  },
  1.1053 +
  1.1054 +  /**
  1.1055 +   * Handles notifications at specific frame counts.
  1.1056 +   */
  1.1057 +  _handleKeyframeNotifications: function TV__handleKeyframeNotifications()
  1.1058 +  {
  1.1059 +    if (!TiltVisualizer.Prefs.introTransition && !this._isExecutingDestruction) {
  1.1060 +      this._time = INTRO_TRANSITION_DURATION;
  1.1061 +    }
  1.1062 +    if (!TiltVisualizer.Prefs.outroTransition && this._isExecutingDestruction) {
  1.1063 +      this._time = OUTRO_TRANSITION_DURATION;
  1.1064 +    }
  1.1065 +
  1.1066 +    if (this._time >= INTRO_TRANSITION_DURATION &&
  1.1067 +       !this._isInitializationFinished &&
  1.1068 +       !this._isExecutingDestruction) {
  1.1069 +
  1.1070 +      this._isInitializationFinished = true;
  1.1071 +      Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.INITIALIZED, null);
  1.1072 +
  1.1073 +      if ("function" === typeof this._onInitializationFinished) {
  1.1074 +        this._onInitializationFinished();
  1.1075 +      }
  1.1076 +    }
  1.1077 +
  1.1078 +    if (this._time >= OUTRO_TRANSITION_DURATION &&
  1.1079 +       !this._isDestructionFinished &&
  1.1080 +        this._isExecutingDestruction) {
  1.1081 +
  1.1082 +      this._isDestructionFinished = true;
  1.1083 +      Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.BEFORE_DESTROYED, null);
  1.1084 +
  1.1085 +      if ("function" === typeof this._onDestructionFinished) {
  1.1086 +        this._onDestructionFinished();
  1.1087 +      }
  1.1088 +    }
  1.1089 +  },
  1.1090 +
  1.1091 +  /**
  1.1092 +   * Starts executing the destruction sequence and issues a callback function
  1.1093 +   * when finished.
  1.1094 +   *
  1.1095 +   * @param {Function} aCallback
  1.1096 +   *                   the destruction finished callback
  1.1097 +   */
  1.1098 +  executeDestruction: function TV_executeDestruction(aCallback)
  1.1099 +  {
  1.1100 +    if (!this._isExecutingDestruction) {
  1.1101 +      this._isExecutingDestruction = true;
  1.1102 +      this._onDestructionFinished = aCallback;
  1.1103 +
  1.1104 +      // if we execute the destruction after the initialization finishes,
  1.1105 +      // proceed normally; otherwise, skip everything and immediately issue
  1.1106 +      // the callback
  1.1107 +
  1.1108 +      if (this._time > OUTRO_TRANSITION_DURATION) {
  1.1109 +        this._time = 0;
  1.1110 +        this._redraw = true;
  1.1111 +      } else {
  1.1112 +        aCallback();
  1.1113 +      }
  1.1114 +    }
  1.1115 +  },
  1.1116 +
  1.1117 +  /**
  1.1118 +   * Checks if this object was initialized properly.
  1.1119 +   *
  1.1120 +   * @return {Boolean} true if the object was initialized properly
  1.1121 +   */
  1.1122 +  isInitialized: function TVP_isInitialized()
  1.1123 +  {
  1.1124 +    return this._renderer && this._renderer.context;
  1.1125 +  },
  1.1126 +
  1.1127 +  /**
  1.1128 +   * Function called when this object is destroyed.
  1.1129 +   */
  1.1130 +  _finalize: function TVP__finalize()
  1.1131 +  {
  1.1132 +    TiltUtils.destroyObject(this._visualizationProgram);
  1.1133 +    TiltUtils.destroyObject(this._texture);
  1.1134 +
  1.1135 +    if (this._meshStacks) {
  1.1136 +      this._meshStacks.forEach(function(group) {
  1.1137 +        TiltUtils.destroyObject(group.vertices);
  1.1138 +        TiltUtils.destroyObject(group.texCoord);
  1.1139 +        TiltUtils.destroyObject(group.color);
  1.1140 +        TiltUtils.destroyObject(group.indices);
  1.1141 +      });
  1.1142 +    }
  1.1143 +    if (this._meshWireframe) {
  1.1144 +      this._meshWireframe.forEach(function(group) {
  1.1145 +        TiltUtils.destroyObject(group.indices);
  1.1146 +      });
  1.1147 +    }
  1.1148 +
  1.1149 +    TiltUtils.destroyObject(this._renderer);
  1.1150 +
  1.1151 +    // Closing the tab would result in contentWindow being a dead object,
  1.1152 +    // so operations like removing event listeners won't work anymore.
  1.1153 +    if (this.contentWindow == this.chromeWindow.content) {
  1.1154 +      this.contentWindow.removeEventListener("resize", this._onResize, false);
  1.1155 +    }
  1.1156 +  }
  1.1157 +};
  1.1158 +
  1.1159 +/**
  1.1160 + * A mouse and keyboard controller implementation.
  1.1161 + *
  1.1162 + * @param {HTMLCanvasElement} aCanvas
  1.1163 + *                            the visualization canvas element
  1.1164 + * @param {TiltVisualizer.Presenter} aPresenter
  1.1165 + *                                   the presenter instance to control
  1.1166 + */
  1.1167 +TiltVisualizer.Controller = function TV_Controller(aCanvas, aPresenter)
  1.1168 +{
  1.1169 +  /**
  1.1170 +   * A canvas overlay on which mouse and keyboard event listeners are attached.
  1.1171 +   */
  1.1172 +  this.canvas = aCanvas;
  1.1173 +
  1.1174 +  /**
  1.1175 +   * Save a reference to the presenter to modify its model-view transforms.
  1.1176 +   */
  1.1177 +  this.presenter = aPresenter;
  1.1178 +  this.presenter.controller = this;
  1.1179 +
  1.1180 +  /**
  1.1181 +   * The initial controller dimensions and offset, in pixels.
  1.1182 +   */
  1.1183 +  this._zoom = aPresenter.transforms.zoom;
  1.1184 +  this._left = (aPresenter.contentWindow.pageXOffset || 0) * this._zoom;
  1.1185 +  this._top = (aPresenter.contentWindow.pageYOffset || 0) * this._zoom;
  1.1186 +  this._width = aCanvas.width;
  1.1187 +  this._height = aCanvas.height;
  1.1188 +
  1.1189 +  /**
  1.1190 +   * Arcball used to control the visualization using the mouse.
  1.1191 +   */
  1.1192 +  this.arcball = new TiltVisualizer.Arcball(
  1.1193 +    this.presenter.chromeWindow, this._width, this._height, 0,
  1.1194 +    [
  1.1195 +      this._width + this._left < aPresenter._maxTextureSize ? -this._left : 0,
  1.1196 +      this._height + this._top < aPresenter._maxTextureSize ? -this._top : 0
  1.1197 +    ]);
  1.1198 +
  1.1199 +  /**
  1.1200 +   * Object containing the rotation quaternion and the translation amount.
  1.1201 +   */
  1.1202 +  this._coordinates = null;
  1.1203 +
  1.1204 +  // bind the owner object to the necessary functions
  1.1205 +  TiltUtils.bindObjectFunc(this, "_update");
  1.1206 +  TiltUtils.bindObjectFunc(this, "^_on");
  1.1207 +
  1.1208 +  // add the necessary event listeners
  1.1209 +  this.addEventListeners();
  1.1210 +
  1.1211 +  // attach this controller's update function to the presenter ondraw event
  1.1212 +  this.presenter._controllerUpdate = this._update;
  1.1213 +};
  1.1214 +
  1.1215 +TiltVisualizer.Controller.prototype = {
  1.1216 +
  1.1217 +  /**
  1.1218 +   * Adds events listeners required by this controller.
  1.1219 +   */
  1.1220 +  addEventListeners: function TVC_addEventListeners()
  1.1221 +  {
  1.1222 +    let canvas = this.canvas;
  1.1223 +    let presenter = this.presenter;
  1.1224 +
  1.1225 +    // bind commonly used mouse and keyboard events with the controller
  1.1226 +    canvas.addEventListener("mousedown", this._onMouseDown, false);
  1.1227 +    canvas.addEventListener("mouseup", this._onMouseUp, false);
  1.1228 +    canvas.addEventListener("mousemove", this._onMouseMove, false);
  1.1229 +    canvas.addEventListener("mouseover", this._onMouseOver, false);
  1.1230 +    canvas.addEventListener("mouseout", this._onMouseOut, false);
  1.1231 +    canvas.addEventListener("MozMousePixelScroll", this._onMozScroll, false);
  1.1232 +    canvas.addEventListener("keydown", this._onKeyDown, false);
  1.1233 +    canvas.addEventListener("keyup", this._onKeyUp, false);
  1.1234 +    canvas.addEventListener("blur", this._onBlur, false);
  1.1235 +
  1.1236 +    // handle resize events to change the arcball dimensions
  1.1237 +    presenter.contentWindow.addEventListener("resize", this._onResize, false);
  1.1238 +  },
  1.1239 +
  1.1240 +  /**
  1.1241 +   * Removes all added events listeners required by this controller.
  1.1242 +   */
  1.1243 +  removeEventListeners: function TVC_removeEventListeners()
  1.1244 +  {
  1.1245 +    let canvas = this.canvas;
  1.1246 +    let presenter = this.presenter;
  1.1247 +
  1.1248 +    canvas.removeEventListener("mousedown", this._onMouseDown, false);
  1.1249 +    canvas.removeEventListener("mouseup", this._onMouseUp, false);
  1.1250 +    canvas.removeEventListener("mousemove", this._onMouseMove, false);
  1.1251 +    canvas.removeEventListener("mouseover", this._onMouseOver, false);
  1.1252 +    canvas.removeEventListener("mouseout", this._onMouseOut, false);
  1.1253 +    canvas.removeEventListener("MozMousePixelScroll", this._onMozScroll, false);
  1.1254 +    canvas.removeEventListener("keydown", this._onKeyDown, false);
  1.1255 +    canvas.removeEventListener("keyup", this._onKeyUp, false);
  1.1256 +    canvas.removeEventListener("blur", this._onBlur, false);
  1.1257 +
  1.1258 +    // Closing the tab would result in contentWindow being a dead object,
  1.1259 +    // so operations like removing event listeners won't work anymore.
  1.1260 +    if (presenter.contentWindow == presenter.chromeWindow.content) {
  1.1261 +      presenter.contentWindow.removeEventListener("resize", this._onResize, false);
  1.1262 +    }
  1.1263 +  },
  1.1264 +
  1.1265 +  /**
  1.1266 +   * Function called each frame, updating the visualization camera transforms.
  1.1267 +   *
  1.1268 +   * @param {Number} aTime
  1.1269 +   *                 total time passed since rendering started
  1.1270 +   * @param {Number} aDelta
  1.1271 +   *                 the current animation frame delta
  1.1272 +   */
  1.1273 +  _update: function TVC__update(aTime, aDelta)
  1.1274 +  {
  1.1275 +    this._time = aTime;
  1.1276 +    this._coordinates = this.arcball.update(aDelta);
  1.1277 +
  1.1278 +    this.presenter.setRotation(this._coordinates.rotation);
  1.1279 +    this.presenter.setTranslation(this._coordinates.translation);
  1.1280 +  },
  1.1281 +
  1.1282 +  /**
  1.1283 +   * Called once after every time a mouse button is pressed.
  1.1284 +   */
  1.1285 +  _onMouseDown: function TVC__onMouseDown(e)
  1.1286 +  {
  1.1287 +    e.target.focus();
  1.1288 +    e.preventDefault();
  1.1289 +    e.stopPropagation();
  1.1290 +
  1.1291 +    if (this._time < MOUSE_INTRO_DELAY) {
  1.1292 +      return;
  1.1293 +    }
  1.1294 +
  1.1295 +    // calculate x and y coordinates using using the client and target offset
  1.1296 +    let button = e.which;
  1.1297 +    this._downX = e.clientX - e.target.offsetLeft;
  1.1298 +    this._downY = e.clientY - e.target.offsetTop;
  1.1299 +
  1.1300 +    this.arcball.mouseDown(this._downX, this._downY, button);
  1.1301 +  },
  1.1302 +
  1.1303 +  /**
  1.1304 +   * Called every time a mouse button is released.
  1.1305 +   */
  1.1306 +  _onMouseUp: function TVC__onMouseUp(e)
  1.1307 +  {
  1.1308 +    e.preventDefault();
  1.1309 +    e.stopPropagation();
  1.1310 +
  1.1311 +    if (this._time < MOUSE_INTRO_DELAY) {
  1.1312 +      return;
  1.1313 +    }
  1.1314 +
  1.1315 +    // calculate x and y coordinates using using the client and target offset
  1.1316 +    let button = e.which;
  1.1317 +    let upX = e.clientX - e.target.offsetLeft;
  1.1318 +    let upY = e.clientY - e.target.offsetTop;
  1.1319 +
  1.1320 +    // a click in Tilt is issued only when the mouse pointer stays in
  1.1321 +    // relatively the same position
  1.1322 +    if (Math.abs(this._downX - upX) < MOUSE_CLICK_THRESHOLD &&
  1.1323 +        Math.abs(this._downY - upY) < MOUSE_CLICK_THRESHOLD) {
  1.1324 +
  1.1325 +      this.presenter.highlightNodeAt(upX, upY);
  1.1326 +    }
  1.1327 +
  1.1328 +    this.arcball.mouseUp(upX, upY, button);
  1.1329 +  },
  1.1330 +
  1.1331 +  /**
  1.1332 +   * Called every time the mouse moves.
  1.1333 +   */
  1.1334 +  _onMouseMove: function TVC__onMouseMove(e)
  1.1335 +  {
  1.1336 +    e.preventDefault();
  1.1337 +    e.stopPropagation();
  1.1338 +
  1.1339 +    if (this._time < MOUSE_INTRO_DELAY) {
  1.1340 +      return;
  1.1341 +    }
  1.1342 +
  1.1343 +    // calculate x and y coordinates using using the client and target offset
  1.1344 +    let moveX = e.clientX - e.target.offsetLeft;
  1.1345 +    let moveY = e.clientY - e.target.offsetTop;
  1.1346 +
  1.1347 +    this.arcball.mouseMove(moveX, moveY);
  1.1348 +  },
  1.1349 +
  1.1350 +  /**
  1.1351 +   * Called when the mouse leaves the visualization bounds.
  1.1352 +   */
  1.1353 +  _onMouseOver: function TVC__onMouseOver(e)
  1.1354 +  {
  1.1355 +    e.preventDefault();
  1.1356 +    e.stopPropagation();
  1.1357 +
  1.1358 +    this.arcball.mouseOver();
  1.1359 +  },
  1.1360 +
  1.1361 +  /**
  1.1362 +   * Called when the mouse leaves the visualization bounds.
  1.1363 +   */
  1.1364 +  _onMouseOut: function TVC__onMouseOut(e)
  1.1365 +  {
  1.1366 +    e.preventDefault();
  1.1367 +    e.stopPropagation();
  1.1368 +
  1.1369 +    this.arcball.mouseOut();
  1.1370 +  },
  1.1371 +
  1.1372 +  /**
  1.1373 +   * Called when the mouse wheel is used.
  1.1374 +   */
  1.1375 +  _onMozScroll: function TVC__onMozScroll(e)
  1.1376 +  {
  1.1377 +    e.preventDefault();
  1.1378 +    e.stopPropagation();
  1.1379 +
  1.1380 +    this.arcball.zoom(e.detail);
  1.1381 +  },
  1.1382 +
  1.1383 +  /**
  1.1384 +   * Called when a key is pressed.
  1.1385 +   */
  1.1386 +  _onKeyDown: function TVC__onKeyDown(e)
  1.1387 +  {
  1.1388 +    let code = e.keyCode || e.which;
  1.1389 +
  1.1390 +    if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
  1.1391 +      e.preventDefault();
  1.1392 +      e.stopPropagation();
  1.1393 +      this.arcball.keyDown(code);
  1.1394 +    } else {
  1.1395 +      this.arcball.cancelKeyEvents();
  1.1396 +    }
  1.1397 +
  1.1398 +    if (e.keyCode === e.DOM_VK_ESCAPE) {
  1.1399 +      let {TiltManager} = require("devtools/tilt/tilt");
  1.1400 +      let tilt =
  1.1401 +        TiltManager.getTiltForBrowser(this.presenter.chromeWindow);
  1.1402 +      e.preventDefault();
  1.1403 +      e.stopPropagation();
  1.1404 +      tilt.destroy(tilt.currentWindowId, true);
  1.1405 +    }
  1.1406 +  },
  1.1407 +
  1.1408 +  /**
  1.1409 +   * Called when a key is released.
  1.1410 +   */
  1.1411 +  _onKeyUp: function TVC__onKeyUp(e)
  1.1412 +  {
  1.1413 +    let code = e.keyCode || e.which;
  1.1414 +
  1.1415 +    if (code === e.DOM_VK_X) {
  1.1416 +      this.presenter.deleteNode();
  1.1417 +    }
  1.1418 +    if (code === e.DOM_VK_F) {
  1.1419 +      let highlight = this.presenter._highlight;
  1.1420 +      let zoom = this.presenter.transforms.zoom;
  1.1421 +
  1.1422 +      this.arcball.moveIntoView(vec3.lerp(
  1.1423 +        vec3.scale(highlight.v0, zoom, []),
  1.1424 +        vec3.scale(highlight.v1, zoom, []), 0.5));
  1.1425 +    }
  1.1426 +    if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
  1.1427 +      e.preventDefault();
  1.1428 +      e.stopPropagation();
  1.1429 +      this.arcball.keyUp(code);
  1.1430 +    }
  1.1431 +  },
  1.1432 +
  1.1433 +  /**
  1.1434 +   * Called when the canvas looses focus.
  1.1435 +   */
  1.1436 +  _onBlur: function TVC__onBlur(e) {
  1.1437 +    this.arcball.cancelKeyEvents();
  1.1438 +  },
  1.1439 +
  1.1440 +  /**
  1.1441 +   * Called when the content window of the current browser is resized.
  1.1442 +   */
  1.1443 +  _onResize: function TVC__onResize(e)
  1.1444 +  {
  1.1445 +    let zoom = this.presenter._getPageZoom();
  1.1446 +    let width = e.target.innerWidth * zoom;
  1.1447 +    let height = e.target.innerHeight * zoom;
  1.1448 +
  1.1449 +    this.arcball.resize(width, height);
  1.1450 +  },
  1.1451 +
  1.1452 +  /**
  1.1453 +   * Checks if this object was initialized properly.
  1.1454 +   *
  1.1455 +   * @return {Boolean} true if the object was initialized properly
  1.1456 +   */
  1.1457 +  isInitialized: function TVC_isInitialized()
  1.1458 +  {
  1.1459 +    return this.arcball ? true : false;
  1.1460 +  },
  1.1461 +
  1.1462 +  /**
  1.1463 +   * Function called when this object is destroyed.
  1.1464 +   */
  1.1465 +  _finalize: function TVC__finalize()
  1.1466 +  {
  1.1467 +    TiltUtils.destroyObject(this.arcball);
  1.1468 +    TiltUtils.destroyObject(this._coordinates);
  1.1469 +
  1.1470 +    this.removeEventListeners();
  1.1471 +    this.presenter.controller = null;
  1.1472 +    this.presenter._controllerUpdate = null;
  1.1473 +  }
  1.1474 +};
  1.1475 +
  1.1476 +/**
  1.1477 + * This is a general purpose 3D rotation controller described by Ken Shoemake
  1.1478 + * in the Graphics Interface ’92 Proceedings. It features good behavior
  1.1479 + * easy implementation, cheap execution.
  1.1480 + *
  1.1481 + * @param {Window} aChromeWindow
  1.1482 + *                 a reference to the top-level window
  1.1483 + * @param {Number} aWidth
  1.1484 + *                 the width of canvas
  1.1485 + * @param {Number} aHeight
  1.1486 + *                 the height of canvas
  1.1487 + * @param {Number} aRadius
  1.1488 + *                 optional, the radius of the arcball
  1.1489 + * @param {Array} aInitialTrans
  1.1490 + *                optional, initial vector translation
  1.1491 + * @param {Array} aInitialRot
  1.1492 + *                optional, initial quaternion rotation
  1.1493 + */
  1.1494 +TiltVisualizer.Arcball = function TV_Arcball(
  1.1495 +  aChromeWindow, aWidth, aHeight, aRadius, aInitialTrans, aInitialRot)
  1.1496 +{
  1.1497 +  /**
  1.1498 +   * Save a reference to the top-level window to set/remove intervals.
  1.1499 +   */
  1.1500 +  this.chromeWindow = aChromeWindow;
  1.1501 +
  1.1502 +  /**
  1.1503 +   * Values retaining the current horizontal and vertical mouse coordinates.
  1.1504 +   */
  1.1505 +  this._mousePress = vec3.create();
  1.1506 +  this._mouseRelease = vec3.create();
  1.1507 +  this._mouseMove = vec3.create();
  1.1508 +  this._mouseLerp = vec3.create();
  1.1509 +  this._mouseButton = -1;
  1.1510 +
  1.1511 +  /**
  1.1512 +   * Object retaining the current pressed key codes.
  1.1513 +   */
  1.1514 +  this._keyCode = {};
  1.1515 +
  1.1516 +  /**
  1.1517 +   * The vectors representing the mouse coordinates mapped on the arcball
  1.1518 +   * and their perpendicular converted from (x, y) to (x, y, z) at specific
  1.1519 +   * events like mousePressed and mouseDragged.
  1.1520 +   */
  1.1521 +  this._startVec = vec3.create();
  1.1522 +  this._endVec = vec3.create();
  1.1523 +  this._pVec = vec3.create();
  1.1524 +
  1.1525 +  /**
  1.1526 +   * The corresponding rotation quaternions.
  1.1527 +   */
  1.1528 +  this._lastRot = quat4.create();
  1.1529 +  this._deltaRot = quat4.create();
  1.1530 +  this._currentRot = quat4.create(aInitialRot);
  1.1531 +
  1.1532 +  /**
  1.1533 +   * The current camera translation coordinates.
  1.1534 +   */
  1.1535 +  this._lastTrans = vec3.create();
  1.1536 +  this._deltaTrans = vec3.create();
  1.1537 +  this._currentTrans = vec3.create(aInitialTrans);
  1.1538 +  this._zoomAmount = 0;
  1.1539 +
  1.1540 +  /**
  1.1541 +   * Additional rotation and translation vectors.
  1.1542 +   */
  1.1543 +  this._additionalRot = vec3.create();
  1.1544 +  this._additionalTrans = vec3.create();
  1.1545 +  this._deltaAdditionalRot = quat4.create();
  1.1546 +  this._deltaAdditionalTrans = vec3.create();
  1.1547 +
  1.1548 +  // load the keys controlling the arcball
  1.1549 +  this._loadKeys();
  1.1550 +
  1.1551 +  // set the current dimensions of the arcball
  1.1552 +  this.resize(aWidth, aHeight, aRadius);
  1.1553 +};
  1.1554 +
  1.1555 +TiltVisualizer.Arcball.prototype = {
  1.1556 +
  1.1557 +  /**
  1.1558 +   * Call this function whenever you need the updated rotation quaternion
  1.1559 +   * and the zoom amount. These values will be returned as "rotation" and
  1.1560 +   * "translation" properties inside an object.
  1.1561 +   *
  1.1562 +   * @param {Number} aDelta
  1.1563 +   *                 the current animation frame delta
  1.1564 +   *
  1.1565 +   * @return {Object} the rotation quaternion and the translation amount
  1.1566 +   */
  1.1567 +  update: function TVA_update(aDelta)
  1.1568 +  {
  1.1569 +    let mousePress = this._mousePress;
  1.1570 +    let mouseRelease = this._mouseRelease;
  1.1571 +    let mouseMove = this._mouseMove;
  1.1572 +    let mouseLerp = this._mouseLerp;
  1.1573 +    let mouseButton = this._mouseButton;
  1.1574 +
  1.1575 +    // smoothly update the mouse coordinates
  1.1576 +    mouseLerp[0] += (mouseMove[0] - mouseLerp[0]) * ARCBALL_SENSITIVITY;
  1.1577 +    mouseLerp[1] += (mouseMove[1] - mouseLerp[1]) * ARCBALL_SENSITIVITY;
  1.1578 +
  1.1579 +    // cache the interpolated mouse coordinates
  1.1580 +    let x = mouseLerp[0];
  1.1581 +    let y = mouseLerp[1];
  1.1582 +
  1.1583 +    // the smoothed arcball rotation may not be finished when the mouse is
  1.1584 +    // pressed again, so cancel the rotation if other events occur or the
  1.1585 +    // animation finishes
  1.1586 +    if (mouseButton === 3 || x === mouseRelease[0] && y === mouseRelease[1]) {
  1.1587 +      this._rotating = false;
  1.1588 +    }
  1.1589 +
  1.1590 +    let startVec = this._startVec;
  1.1591 +    let endVec = this._endVec;
  1.1592 +    let pVec = this._pVec;
  1.1593 +
  1.1594 +    let lastRot = this._lastRot;
  1.1595 +    let deltaRot = this._deltaRot;
  1.1596 +    let currentRot = this._currentRot;
  1.1597 +
  1.1598 +    // left mouse button handles rotation
  1.1599 +    if (mouseButton === 1 || this._rotating) {
  1.1600 +      // the rotation doesn't stop immediately after the left mouse button is
  1.1601 +      // released, so add a flag to smoothly continue it until it ends
  1.1602 +      this._rotating = true;
  1.1603 +
  1.1604 +      // find the sphere coordinates of the mouse positions
  1.1605 +      this._pointToSphere(x, y, this.width, this.height, this.radius, endVec);
  1.1606 +
  1.1607 +      // compute the vector perpendicular to the start & end vectors
  1.1608 +      vec3.cross(startVec, endVec, pVec);
  1.1609 +
  1.1610 +      // if the begin and end vectors don't coincide
  1.1611 +      if (vec3.length(pVec) > 0) {
  1.1612 +        deltaRot[0] = pVec[0];
  1.1613 +        deltaRot[1] = pVec[1];
  1.1614 +        deltaRot[2] = pVec[2];
  1.1615 +
  1.1616 +        // in the quaternion values, w is cosine (theta / 2),
  1.1617 +        // where theta is the rotation angle
  1.1618 +        deltaRot[3] = -vec3.dot(startVec, endVec);
  1.1619 +      } else {
  1.1620 +        // return an identity rotation quaternion
  1.1621 +        deltaRot[0] = 0;
  1.1622 +        deltaRot[1] = 0;
  1.1623 +        deltaRot[2] = 0;
  1.1624 +        deltaRot[3] = 1;
  1.1625 +      }
  1.1626 +
  1.1627 +      // calculate the current rotation based on the mouse click events
  1.1628 +      quat4.multiply(lastRot, deltaRot, currentRot);
  1.1629 +    } else {
  1.1630 +      // save the current quaternion to stack rotations
  1.1631 +      quat4.set(currentRot, lastRot);
  1.1632 +    }
  1.1633 +
  1.1634 +    let lastTrans = this._lastTrans;
  1.1635 +    let deltaTrans = this._deltaTrans;
  1.1636 +    let currentTrans = this._currentTrans;
  1.1637 +
  1.1638 +    // right mouse button handles panning
  1.1639 +    if (mouseButton === 3) {
  1.1640 +      // calculate a delta translation between the new and old mouse position
  1.1641 +      // and save it to the current translation
  1.1642 +      deltaTrans[0] = mouseMove[0] - mousePress[0];
  1.1643 +      deltaTrans[1] = mouseMove[1] - mousePress[1];
  1.1644 +
  1.1645 +      currentTrans[0] = lastTrans[0] + deltaTrans[0];
  1.1646 +      currentTrans[1] = lastTrans[1] + deltaTrans[1];
  1.1647 +    } else {
  1.1648 +      // save the current panning to stack translations
  1.1649 +      lastTrans[0] = currentTrans[0];
  1.1650 +      lastTrans[1] = currentTrans[1];
  1.1651 +    }
  1.1652 +
  1.1653 +    let zoomAmount = this._zoomAmount;
  1.1654 +    let keyCode = this._keyCode;
  1.1655 +
  1.1656 +    // mouse wheel handles zooming
  1.1657 +    deltaTrans[2] = (zoomAmount - currentTrans[2]) * ARCBALL_ZOOM_STEP;
  1.1658 +    currentTrans[2] += deltaTrans[2];
  1.1659 +
  1.1660 +    let additionalRot = this._additionalRot;
  1.1661 +    let additionalTrans = this._additionalTrans;
  1.1662 +    let deltaAdditionalRot = this._deltaAdditionalRot;
  1.1663 +    let deltaAdditionalTrans = this._deltaAdditionalTrans;
  1.1664 +
  1.1665 +    let rotateKeys = this.rotateKeys;
  1.1666 +    let panKeys = this.panKeys;
  1.1667 +    let zoomKeys = this.zoomKeys;
  1.1668 +    let resetKey = this.resetKey;
  1.1669 +
  1.1670 +    // handle additional rotation and translation by the keyboard
  1.1671 +    if (keyCode[rotateKeys.left]) {
  1.1672 +      additionalRot[0] -= ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1.1673 +    }
  1.1674 +    if (keyCode[rotateKeys.right]) {
  1.1675 +      additionalRot[0] += ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1.1676 +    }
  1.1677 +    if (keyCode[rotateKeys.up]) {
  1.1678 +      additionalRot[1] += ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1.1679 +    }
  1.1680 +    if (keyCode[rotateKeys.down]) {
  1.1681 +      additionalRot[1] -= ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1.1682 +    }
  1.1683 +    if (keyCode[panKeys.left]) {
  1.1684 +      additionalTrans[0] -= ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1.1685 +    }
  1.1686 +    if (keyCode[panKeys.right]) {
  1.1687 +      additionalTrans[0] += ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1.1688 +    }
  1.1689 +    if (keyCode[panKeys.up]) {
  1.1690 +      additionalTrans[1] -= ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1.1691 +    }
  1.1692 +    if (keyCode[panKeys.down]) {
  1.1693 +      additionalTrans[1] += ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1.1694 +    }
  1.1695 +    if (keyCode[zoomKeys["in"][0]] ||
  1.1696 +        keyCode[zoomKeys["in"][1]] ||
  1.1697 +        keyCode[zoomKeys["in"][2]]) {
  1.1698 +      this.zoom(-ARCBALL_TRANSLATION_STEP);
  1.1699 +    }
  1.1700 +    if (keyCode[zoomKeys["out"][0]] ||
  1.1701 +        keyCode[zoomKeys["out"][1]]) {
  1.1702 +      this.zoom(ARCBALL_TRANSLATION_STEP);
  1.1703 +    }
  1.1704 +    if (keyCode[zoomKeys["unzoom"]]) {
  1.1705 +      this._zoomAmount = 0;
  1.1706 +    }
  1.1707 +    if (keyCode[resetKey]) {
  1.1708 +      this.reset();
  1.1709 +    }
  1.1710 +
  1.1711 +    // update the delta key rotations and translations
  1.1712 +    deltaAdditionalRot[0] +=
  1.1713 +      (additionalRot[0] - deltaAdditionalRot[0]) * ARCBALL_SENSITIVITY;
  1.1714 +    deltaAdditionalRot[1] +=
  1.1715 +      (additionalRot[1] - deltaAdditionalRot[1]) * ARCBALL_SENSITIVITY;
  1.1716 +    deltaAdditionalRot[2] +=
  1.1717 +      (additionalRot[2] - deltaAdditionalRot[2]) * ARCBALL_SENSITIVITY;
  1.1718 +
  1.1719 +    deltaAdditionalTrans[0] +=
  1.1720 +      (additionalTrans[0] - deltaAdditionalTrans[0]) * ARCBALL_SENSITIVITY;
  1.1721 +    deltaAdditionalTrans[1] +=
  1.1722 +      (additionalTrans[1] - deltaAdditionalTrans[1]) * ARCBALL_SENSITIVITY;
  1.1723 +
  1.1724 +    // create an additional rotation based on the key events
  1.1725 +    quat4.fromEuler(
  1.1726 +      deltaAdditionalRot[0],
  1.1727 +      deltaAdditionalRot[1],
  1.1728 +      deltaAdditionalRot[2], deltaRot);
  1.1729 +
  1.1730 +    // create an additional translation based on the key events
  1.1731 +    vec3.set([deltaAdditionalTrans[0], deltaAdditionalTrans[1], 0], deltaTrans);
  1.1732 +
  1.1733 +    // handle the reset animation steps if necessary
  1.1734 +    if (this._resetInProgress) {
  1.1735 +      this._nextResetStep(aDelta || 1);
  1.1736 +    }
  1.1737 +
  1.1738 +    // return the current rotation and translation
  1.1739 +    return {
  1.1740 +      rotation: quat4.multiply(deltaRot, currentRot),
  1.1741 +      translation: vec3.add(deltaTrans, currentTrans)
  1.1742 +    };
  1.1743 +  },
  1.1744 +
  1.1745 +  /**
  1.1746 +   * Function handling the mouseDown event.
  1.1747 +   * Call this when the mouse was pressed.
  1.1748 +   *
  1.1749 +   * @param {Number} x
  1.1750 +   *                 the current horizontal coordinate of the mouse
  1.1751 +   * @param {Number} y
  1.1752 +   *                 the current vertical coordinate of the mouse
  1.1753 +   * @param {Number} aButton
  1.1754 +   *                 which mouse button was pressed
  1.1755 +   */
  1.1756 +  mouseDown: function TVA_mouseDown(x, y, aButton)
  1.1757 +  {
  1.1758 +    // save the mouse down state and prepare for rotations or translations
  1.1759 +    this._mousePress[0] = x;
  1.1760 +    this._mousePress[1] = y;
  1.1761 +    this._mouseButton = aButton;
  1.1762 +    this._cancelReset();
  1.1763 +    this._save();
  1.1764 +
  1.1765 +    // find the sphere coordinates of the mouse positions
  1.1766 +    this._pointToSphere(
  1.1767 +      x, y, this.width, this.height, this.radius, this._startVec);
  1.1768 +
  1.1769 +    quat4.set(this._currentRot, this._lastRot);
  1.1770 +  },
  1.1771 +
  1.1772 +  /**
  1.1773 +   * Function handling the mouseUp event.
  1.1774 +   * Call this when a mouse button was released.
  1.1775 +   *
  1.1776 +   * @param {Number} x
  1.1777 +   *                 the current horizontal coordinate of the mouse
  1.1778 +   * @param {Number} y
  1.1779 +   *                 the current vertical coordinate of the mouse
  1.1780 +   */
  1.1781 +  mouseUp: function TVA_mouseUp(x, y)
  1.1782 +  {
  1.1783 +    // save the mouse up state and prepare for rotations or translations
  1.1784 +    this._mouseRelease[0] = x;
  1.1785 +    this._mouseRelease[1] = y;
  1.1786 +    this._mouseButton = -1;
  1.1787 +  },
  1.1788 +
  1.1789 +  /**
  1.1790 +   * Function handling the mouseMove event.
  1.1791 +   * Call this when the mouse was moved.
  1.1792 +   *
  1.1793 +   * @param {Number} x
  1.1794 +   *                 the current horizontal coordinate of the mouse
  1.1795 +   * @param {Number} y
  1.1796 +   *                 the current vertical coordinate of the mouse
  1.1797 +   */
  1.1798 +  mouseMove: function TVA_mouseMove(x, y)
  1.1799 +  {
  1.1800 +    // save the mouse move state and prepare for rotations or translations
  1.1801 +    // only if the mouse is pressed
  1.1802 +    if (this._mouseButton !== -1) {
  1.1803 +      this._mouseMove[0] = x;
  1.1804 +      this._mouseMove[1] = y;
  1.1805 +    }
  1.1806 +  },
  1.1807 +
  1.1808 +  /**
  1.1809 +   * Function handling the mouseOver event.
  1.1810 +   * Call this when the mouse enters the context bounds.
  1.1811 +   */
  1.1812 +  mouseOver: function TVA_mouseOver()
  1.1813 +  {
  1.1814 +    // if the mouse just entered the parent bounds, stop the animation
  1.1815 +    this._mouseButton = -1;
  1.1816 +  },
  1.1817 +
  1.1818 +  /**
  1.1819 +   * Function handling the mouseOut event.
  1.1820 +   * Call this when the mouse leaves the context bounds.
  1.1821 +   */
  1.1822 +  mouseOut: function TVA_mouseOut()
  1.1823 +  {
  1.1824 +    // if the mouse leaves the parent bounds, stop the animation
  1.1825 +    this._mouseButton = -1;
  1.1826 +  },
  1.1827 +
  1.1828 +  /**
  1.1829 +   * Function handling the arcball zoom amount.
  1.1830 +   * Call this, for example, when the mouse wheel was scrolled or zoom keys
  1.1831 +   * were pressed.
  1.1832 +   *
  1.1833 +   * @param {Number} aZoom
  1.1834 +   *                 the zoom direction and speed
  1.1835 +   */
  1.1836 +  zoom: function TVA_zoom(aZoom)
  1.1837 +  {
  1.1838 +    this._cancelReset();
  1.1839 +    this._zoomAmount = TiltMath.clamp(this._zoomAmount - aZoom,
  1.1840 +      ARCBALL_ZOOM_MIN, ARCBALL_ZOOM_MAX);
  1.1841 +  },
  1.1842 +
  1.1843 +  /**
  1.1844 +   * Function handling the keyDown event.
  1.1845 +   * Call this when a key was pressed.
  1.1846 +   *
  1.1847 +   * @param {Number} aCode
  1.1848 +   *                 the code corresponding to the key pressed
  1.1849 +   */
  1.1850 +  keyDown: function TVA_keyDown(aCode)
  1.1851 +  {
  1.1852 +    this._cancelReset();
  1.1853 +    this._keyCode[aCode] = true;
  1.1854 +  },
  1.1855 +
  1.1856 +  /**
  1.1857 +   * Function handling the keyUp event.
  1.1858 +   * Call this when a key was released.
  1.1859 +   *
  1.1860 +   * @param {Number} aCode
  1.1861 +   *                 the code corresponding to the key released
  1.1862 +   */
  1.1863 +  keyUp: function TVA_keyUp(aCode)
  1.1864 +  {
  1.1865 +    this._keyCode[aCode] = false;
  1.1866 +  },
  1.1867 +
  1.1868 +  /**
  1.1869 +   * Maps the 2d coordinates of the mouse location to a 3d point on a sphere.
  1.1870 +   *
  1.1871 +   * @param {Number} x
  1.1872 +   *                 the current horizontal coordinate of the mouse
  1.1873 +   * @param {Number} y
  1.1874 +   *                 the current vertical coordinate of the mouse
  1.1875 +   * @param {Number} aWidth
  1.1876 +   *                 the width of canvas
  1.1877 +   * @param {Number} aHeight
  1.1878 +   *                 the height of canvas
  1.1879 +   * @param {Number} aRadius
  1.1880 +   *                 optional, the radius of the arcball
  1.1881 +   * @param {Array} aSphereVec
  1.1882 +   *                a 3d vector to store the sphere coordinates
  1.1883 +   */
  1.1884 +  _pointToSphere: function TVA__pointToSphere(
  1.1885 +    x, y, aWidth, aHeight, aRadius, aSphereVec)
  1.1886 +  {
  1.1887 +    // adjust point coords and scale down to range of [-1..1]
  1.1888 +    x = (x - aWidth * 0.5) / aRadius;
  1.1889 +    y = (y - aHeight * 0.5) / aRadius;
  1.1890 +
  1.1891 +    // compute the square length of the vector to the point from the center
  1.1892 +    let normal = 0;
  1.1893 +    let sqlength = x * x + y * y;
  1.1894 +
  1.1895 +    // if the point is mapped outside of the sphere
  1.1896 +    if (sqlength > 1) {
  1.1897 +      // calculate the normalization factor
  1.1898 +      normal = 1 / Math.sqrt(sqlength);
  1.1899 +
  1.1900 +      // set the normalized vector (a point on the sphere)
  1.1901 +      aSphereVec[0] = x * normal;
  1.1902 +      aSphereVec[1] = y * normal;
  1.1903 +      aSphereVec[2] = 0;
  1.1904 +    } else {
  1.1905 +      // set the vector to a point mapped inside the sphere
  1.1906 +      aSphereVec[0] = x;
  1.1907 +      aSphereVec[1] = y;
  1.1908 +      aSphereVec[2] = Math.sqrt(1 - sqlength);
  1.1909 +    }
  1.1910 +  },
  1.1911 +
  1.1912 +  /**
  1.1913 +   * Cancels all pending transformations caused by key events.
  1.1914 +   */
  1.1915 +  cancelKeyEvents: function TVA_cancelKeyEvents()
  1.1916 +  {
  1.1917 +    this._keyCode = {};
  1.1918 +  },
  1.1919 +
  1.1920 +  /**
  1.1921 +   * Cancels all pending transformations caused by mouse events.
  1.1922 +   */
  1.1923 +  cancelMouseEvents: function TVA_cancelMouseEvents()
  1.1924 +  {
  1.1925 +    this._rotating = false;
  1.1926 +    this._mouseButton = -1;
  1.1927 +  },
  1.1928 +
  1.1929 +  /**
  1.1930 +   * Incremental translation method.
  1.1931 +   *
  1.1932 +   * @param {Array} aTranslation
  1.1933 +   *                the translation ammount on the [x, y] axis
  1.1934 +   */
  1.1935 +  translate: function TVP_translate(aTranslation)
  1.1936 +  {
  1.1937 +    this._additionalTrans[0] += aTranslation[0];
  1.1938 +    this._additionalTrans[1] += aTranslation[1];
  1.1939 +  },
  1.1940 +
  1.1941 +  /**
  1.1942 +   * Incremental rotation method.
  1.1943 +   *
  1.1944 +   * @param {Array} aRotation
  1.1945 +   *                the rotation ammount along the [x, y, z] axis
  1.1946 +   */
  1.1947 +  rotate: function TVP_rotate(aRotation)
  1.1948 +  {
  1.1949 +    // explicitly rotate along y, x, z values because they're eulerian angles
  1.1950 +    this._additionalRot[0] += TiltMath.radians(aRotation[1]);
  1.1951 +    this._additionalRot[1] += TiltMath.radians(aRotation[0]);
  1.1952 +    this._additionalRot[2] += TiltMath.radians(aRotation[2]);
  1.1953 +  },
  1.1954 +
  1.1955 +  /**
  1.1956 +   * Moves a target point into view only if it's outside the currently visible
  1.1957 +   * area bounds (in which case it also resets any additional transforms).
  1.1958 +   *
  1.1959 +   * @param {Arary} aPoint
  1.1960 +   *                the [x, y] point which should be brought into view
  1.1961 +   */
  1.1962 +  moveIntoView: function TVA_moveIntoView(aPoint) {
  1.1963 +    let visiblePointX = -(this._currentTrans[0] + this._additionalTrans[0]);
  1.1964 +    let visiblePointY = -(this._currentTrans[1] + this._additionalTrans[1]);
  1.1965 +
  1.1966 +    if (aPoint[1] - visiblePointY - MOVE_INTO_VIEW_ACCURACY > this.height ||
  1.1967 +        aPoint[1] - visiblePointY + MOVE_INTO_VIEW_ACCURACY < 0 ||
  1.1968 +        aPoint[0] - visiblePointX > this.width ||
  1.1969 +        aPoint[0] - visiblePointX < 0) {
  1.1970 +      this.reset([0, -aPoint[1]]);
  1.1971 +    }
  1.1972 +  },
  1.1973 +
  1.1974 +  /**
  1.1975 +   * Resize this implementation to use different bounds.
  1.1976 +   * This function is automatically called when the arcball is created.
  1.1977 +   *
  1.1978 +   * @param {Number} newWidth
  1.1979 +   *                 the new width of canvas
  1.1980 +   * @param {Number} newHeight
  1.1981 +   *                 the new  height of canvas
  1.1982 +   * @param {Number} newRadius
  1.1983 +   *                 optional, the new radius of the arcball
  1.1984 +   */
  1.1985 +  resize: function TVA_resize(newWidth, newHeight, newRadius)
  1.1986 +  {
  1.1987 +    if (!newWidth || !newHeight) {
  1.1988 +      return;
  1.1989 +    }
  1.1990 +
  1.1991 +    // set the new width, height and radius dimensions
  1.1992 +    this.width = newWidth;
  1.1993 +    this.height = newHeight;
  1.1994 +    this.radius = newRadius ? newRadius : Math.min(newWidth, newHeight);
  1.1995 +    this._save();
  1.1996 +  },
  1.1997 +
  1.1998 +  /**
  1.1999 +   * Starts an animation resetting the arcball transformations to identity.
  1.2000 +   *
  1.2001 +   * @param {Array} aFinalTranslation
  1.2002 +   *                optional, final vector translation
  1.2003 +   * @param {Array} aFinalRotation
  1.2004 +   *                optional, final quaternion rotation
  1.2005 +   */
  1.2006 +  reset: function TVA_reset(aFinalTranslation, aFinalRotation)
  1.2007 +  {
  1.2008 +    if ("function" === typeof this._onResetStart) {
  1.2009 +      this._onResetStart();
  1.2010 +      this._onResetStart = null;
  1.2011 +    }
  1.2012 +
  1.2013 +    this.cancelMouseEvents();
  1.2014 +    this.cancelKeyEvents();
  1.2015 +    this._cancelReset();
  1.2016 +
  1.2017 +    this._save();
  1.2018 +    this._resetFinalTranslation = vec3.create(aFinalTranslation);
  1.2019 +    this._resetFinalRotation = quat4.create(aFinalRotation);
  1.2020 +    this._resetInProgress = true;
  1.2021 +  },
  1.2022 +
  1.2023 +  /**
  1.2024 +   * Cancels the current arcball reset animation if there is one.
  1.2025 +   */
  1.2026 +  _cancelReset: function TVA__cancelReset()
  1.2027 +  {
  1.2028 +    if (this._resetInProgress) {
  1.2029 +      this._resetInProgress = false;
  1.2030 +      this._save();
  1.2031 +
  1.2032 +      if ("function" === typeof this._onResetFinish) {
  1.2033 +        this._onResetFinish();
  1.2034 +        this._onResetFinish = null;
  1.2035 +        this._onResetStep = null;
  1.2036 +      }
  1.2037 +    }
  1.2038 +  },
  1.2039 +
  1.2040 +  /**
  1.2041 +   * Executes the next step in the arcball reset animation.
  1.2042 +   *
  1.2043 +   * @param {Number} aDelta
  1.2044 +   *                 the current animation frame delta
  1.2045 +   */
  1.2046 +  _nextResetStep: function TVA__nextResetStep(aDelta)
  1.2047 +  {
  1.2048 +    // a very large animation frame delta (in case of seriously low framerate)
  1.2049 +    // would cause all the interpolations to become highly unstable
  1.2050 +    aDelta = TiltMath.clamp(aDelta, 1, 100);
  1.2051 +
  1.2052 +    let fNearZero = EPSILON * EPSILON;
  1.2053 +    let fInterpLin = ARCBALL_RESET_LINEAR_FACTOR * aDelta;
  1.2054 +    let fInterpSph = ARCBALL_RESET_SPHERICAL_FACTOR;
  1.2055 +    let fTran = this._resetFinalTranslation;
  1.2056 +    let fRot = this._resetFinalRotation;
  1.2057 +
  1.2058 +    let t = vec3.create(fTran);
  1.2059 +    let r = quat4.multiply(quat4.inverse(quat4.create(this._currentRot)), fRot);
  1.2060 +
  1.2061 +    // reset the rotation quaternion and translation vector
  1.2062 +    vec3.lerp(this._currentTrans, t, fInterpLin);
  1.2063 +    quat4.slerp(this._currentRot, r, fInterpSph);
  1.2064 +
  1.2065 +    // also reset any additional transforms by the keyboard or mouse
  1.2066 +    vec3.scale(this._additionalTrans, fInterpLin);
  1.2067 +    vec3.scale(this._additionalRot, fInterpLin);
  1.2068 +    this._zoomAmount *= fInterpLin;
  1.2069 +
  1.2070 +    // clear the loop if the all values are very close to zero
  1.2071 +    if (vec3.length(vec3.subtract(this._lastRot, fRot, [])) < fNearZero &&
  1.2072 +        vec3.length(vec3.subtract(this._deltaRot, fRot, [])) < fNearZero &&
  1.2073 +        vec3.length(vec3.subtract(this._currentRot, fRot, [])) < fNearZero &&
  1.2074 +        vec3.length(vec3.subtract(this._lastTrans, fTran, [])) < fNearZero &&
  1.2075 +        vec3.length(vec3.subtract(this._deltaTrans, fTran, [])) < fNearZero &&
  1.2076 +        vec3.length(vec3.subtract(this._currentTrans, fTran, [])) < fNearZero &&
  1.2077 +        vec3.length(this._additionalRot) < fNearZero &&
  1.2078 +        vec3.length(this._additionalTrans) < fNearZero) {
  1.2079 +
  1.2080 +      this._cancelReset();
  1.2081 +    }
  1.2082 +
  1.2083 +    if ("function" === typeof this._onResetStep) {
  1.2084 +      this._onResetStep();
  1.2085 +    }
  1.2086 +  },
  1.2087 +
  1.2088 +  /**
  1.2089 +   * Loads the keys to control this arcball.
  1.2090 +   */
  1.2091 +  _loadKeys: function TVA__loadKeys()
  1.2092 +  {
  1.2093 +    this.rotateKeys = {
  1.2094 +      "up": Ci.nsIDOMKeyEvent["DOM_VK_W"],
  1.2095 +      "down": Ci.nsIDOMKeyEvent["DOM_VK_S"],
  1.2096 +      "left": Ci.nsIDOMKeyEvent["DOM_VK_A"],
  1.2097 +      "right": Ci.nsIDOMKeyEvent["DOM_VK_D"],
  1.2098 +    };
  1.2099 +    this.panKeys = {
  1.2100 +      "up": Ci.nsIDOMKeyEvent["DOM_VK_UP"],
  1.2101 +      "down": Ci.nsIDOMKeyEvent["DOM_VK_DOWN"],
  1.2102 +      "left": Ci.nsIDOMKeyEvent["DOM_VK_LEFT"],
  1.2103 +      "right": Ci.nsIDOMKeyEvent["DOM_VK_RIGHT"],
  1.2104 +    };
  1.2105 +    this.zoomKeys = {
  1.2106 +      "in": [
  1.2107 +        Ci.nsIDOMKeyEvent["DOM_VK_I"],
  1.2108 +        Ci.nsIDOMKeyEvent["DOM_VK_ADD"],
  1.2109 +        Ci.nsIDOMKeyEvent["DOM_VK_EQUALS"],
  1.2110 +      ],
  1.2111 +      "out": [
  1.2112 +        Ci.nsIDOMKeyEvent["DOM_VK_O"],
  1.2113 +        Ci.nsIDOMKeyEvent["DOM_VK_SUBTRACT"],
  1.2114 +      ],
  1.2115 +      "unzoom": Ci.nsIDOMKeyEvent["DOM_VK_0"]
  1.2116 +    };
  1.2117 +    this.resetKey = Ci.nsIDOMKeyEvent["DOM_VK_R"];
  1.2118 +  },
  1.2119 +
  1.2120 +  /**
  1.2121 +   * Saves the current arcball state, typically after resize or mouse events.
  1.2122 +   */
  1.2123 +  _save: function TVA__save()
  1.2124 +  {
  1.2125 +    if (this._mousePress) {
  1.2126 +      let x = this._mousePress[0];
  1.2127 +      let y = this._mousePress[1];
  1.2128 +
  1.2129 +      this._mouseMove[0] = x;
  1.2130 +      this._mouseMove[1] = y;
  1.2131 +      this._mouseRelease[0] = x;
  1.2132 +      this._mouseRelease[1] = y;
  1.2133 +      this._mouseLerp[0] = x;
  1.2134 +      this._mouseLerp[1] = y;
  1.2135 +    }
  1.2136 +  },
  1.2137 +
  1.2138 +  /**
  1.2139 +   * Function called when this object is destroyed.
  1.2140 +   */
  1.2141 +  _finalize: function TVA__finalize()
  1.2142 +  {
  1.2143 +    this._cancelReset();
  1.2144 +  }
  1.2145 +};
  1.2146 +
  1.2147 +/**
  1.2148 + * Tilt configuration preferences.
  1.2149 + */
  1.2150 +TiltVisualizer.Prefs = {
  1.2151 +
  1.2152 +  /**
  1.2153 +   * Specifies if Tilt is enabled or not.
  1.2154 +   */
  1.2155 +  get enabled()
  1.2156 +  {
  1.2157 +    return this._enabled;
  1.2158 +  },
  1.2159 +
  1.2160 +  set enabled(value)
  1.2161 +  {
  1.2162 +    TiltUtils.Preferences.set("enabled", "boolean", value);
  1.2163 +    this._enabled = value;
  1.2164 +  },
  1.2165 +
  1.2166 +  get introTransition()
  1.2167 +  {
  1.2168 +    return this._introTransition;
  1.2169 +  },
  1.2170 +
  1.2171 +  set introTransition(value)
  1.2172 +  {
  1.2173 +    TiltUtils.Preferences.set("intro_transition", "boolean", value);
  1.2174 +    this._introTransition = value;
  1.2175 +  },
  1.2176 +
  1.2177 +  get outroTransition()
  1.2178 +  {
  1.2179 +    return this._outroTransition;
  1.2180 +  },
  1.2181 +
  1.2182 +  set outroTransition(value)
  1.2183 +  {
  1.2184 +    TiltUtils.Preferences.set("outro_transition", "boolean", value);
  1.2185 +    this._outroTransition = value;
  1.2186 +  },
  1.2187 +
  1.2188 +  /**
  1.2189 +   * Loads the preferences.
  1.2190 +   */
  1.2191 +  load: function TVC_load()
  1.2192 +  {
  1.2193 +    let prefs = TiltVisualizer.Prefs;
  1.2194 +    let get = TiltUtils.Preferences.get;
  1.2195 +
  1.2196 +    prefs._enabled = get("enabled", "boolean");
  1.2197 +    prefs._introTransition = get("intro_transition", "boolean");
  1.2198 +    prefs._outroTransition = get("outro_transition", "boolean");
  1.2199 +  }
  1.2200 +};
  1.2201 +
  1.2202 +/**
  1.2203 + * A custom visualization shader.
  1.2204 + *
  1.2205 + * @param {Attribute} vertexPosition: the vertex position
  1.2206 + * @param {Attribute} vertexTexCoord: texture coordinates used by the sampler
  1.2207 + * @param {Attribute} vertexColor: specific [r, g, b] color for each vertex
  1.2208 + * @param {Uniform} mvMatrix: the model view matrix
  1.2209 + * @param {Uniform} projMatrix: the projection matrix
  1.2210 + * @param {Uniform} sampler: the texture sampler to fetch the pixels from
  1.2211 + */
  1.2212 +TiltVisualizer.MeshShader = {
  1.2213 +
  1.2214 +  /**
  1.2215 +   * Vertex shader.
  1.2216 +   */
  1.2217 +  vs: [
  1.2218 +    "attribute vec3 vertexPosition;",
  1.2219 +    "attribute vec2 vertexTexCoord;",
  1.2220 +    "attribute vec3 vertexColor;",
  1.2221 +
  1.2222 +    "uniform mat4 mvMatrix;",
  1.2223 +    "uniform mat4 projMatrix;",
  1.2224 +
  1.2225 +    "varying vec2 texCoord;",
  1.2226 +    "varying vec3 color;",
  1.2227 +
  1.2228 +    "void main() {",
  1.2229 +    "  gl_Position = projMatrix * mvMatrix * vec4(vertexPosition, 1.0);",
  1.2230 +    "  texCoord = vertexTexCoord;",
  1.2231 +    "  color = vertexColor;",
  1.2232 +    "}"
  1.2233 +  ].join("\n"),
  1.2234 +
  1.2235 +  /**
  1.2236 +   * Fragment shader.
  1.2237 +   */
  1.2238 +  fs: [
  1.2239 +    "#ifdef GL_ES",
  1.2240 +    "precision lowp float;",
  1.2241 +    "#endif",
  1.2242 +
  1.2243 +    "uniform sampler2D sampler;",
  1.2244 +
  1.2245 +    "varying vec2 texCoord;",
  1.2246 +    "varying vec3 color;",
  1.2247 +
  1.2248 +    "void main() {",
  1.2249 +    "  if (texCoord.x < 0.0) {",
  1.2250 +    "    gl_FragColor = vec4(color, 1.0);",
  1.2251 +    "  } else {",
  1.2252 +    "    gl_FragColor = vec4(texture2D(sampler, texCoord).rgb, 1.0);",
  1.2253 +    "  }",
  1.2254 +    "}"
  1.2255 +  ].join("\n")
  1.2256 +};

mercurial