browser/devtools/tilt/tilt-visualizer.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 "use strict";
     8 const {Cu, Ci, ChromeWorker} = require("chrome");
    10 let TiltGL = require("devtools/tilt/tilt-gl");
    11 let TiltUtils = require("devtools/tilt/tilt-utils");
    12 let TiltVisualizerStyle = require("devtools/tilt/tilt-visualizer-style");
    13 let {EPSILON, TiltMath, vec3, mat4, quat4} = require("devtools/tilt/tilt-math");
    14 let {TargetFactory} = require("devtools/framework/target");
    16 Cu.import("resource://gre/modules/Services.jsm");
    17 Cu.import("resource:///modules/devtools/gDevTools.jsm");
    19 const ELEMENT_MIN_SIZE = 4;
    20 const INVISIBLE_ELEMENTS = {
    21   "head": true,
    22   "base": true,
    23   "basefont": true,
    24   "isindex": true,
    25   "link": true,
    26   "meta": true,
    27   "option": true,
    28   "script": true,
    29   "style": true,
    30   "title": true
    31 };
    33 // a node is represented in the visualization mesh as a rectangular stack
    34 // of 5 quads composed of 12 vertices; we draw these as triangles using an
    35 // index buffer of 12 unsigned int elements, obviously one for each vertex;
    36 // if a webpage has enough nodes to overflow the index buffer elements size,
    37 // weird things may happen; thus, when necessary, we'll split into groups
    38 const MAX_GROUP_NODES = Math.pow(2, Uint16Array.BYTES_PER_ELEMENT * 8) / 12 - 1;
    40 const WIREFRAME_COLOR = [0, 0, 0, 0.25];
    41 const INTRO_TRANSITION_DURATION = 1000;
    42 const OUTRO_TRANSITION_DURATION = 800;
    43 const INITIAL_Z_TRANSLATION = 400;
    44 const MOVE_INTO_VIEW_ACCURACY = 50;
    46 const MOUSE_CLICK_THRESHOLD = 10;
    47 const MOUSE_INTRO_DELAY = 200;
    48 const ARCBALL_SENSITIVITY = 0.5;
    49 const ARCBALL_ROTATION_STEP = 0.15;
    50 const ARCBALL_TRANSLATION_STEP = 35;
    51 const ARCBALL_ZOOM_STEP = 0.1;
    52 const ARCBALL_ZOOM_MIN = -3000;
    53 const ARCBALL_ZOOM_MAX = 500;
    54 const ARCBALL_RESET_SPHERICAL_FACTOR = 0.1;
    55 const ARCBALL_RESET_LINEAR_FACTOR = 0.01;
    57 const TILT_CRAFTER = "resource:///modules/devtools/tilt/TiltWorkerCrafter.js";
    58 const TILT_PICKER = "resource:///modules/devtools/tilt/TiltWorkerPicker.js";
    61 /**
    62  * Initializes the visualization presenter and controller.
    63  *
    64  * @param {Object} aProperties
    65  *                 an object containing the following properties:
    66  *        {Window} chromeWindow: a reference to the top level window
    67  *        {Window} contentWindow: the content window holding the visualized doc
    68  *       {Element} parentNode: the parent node to hold the visualization
    69  *        {Object} notifications: necessary notifications for Tilt
    70  *      {Function} onError: optional, function called if initialization failed
    71  *      {Function} onLoad: optional, function called if initialization worked
    72  */
    73 function TiltVisualizer(aProperties)
    74 {
    75   // make sure the properties parameter is a valid object
    76   aProperties = aProperties || {};
    78   /**
    79    * Save a reference to the top-level window.
    80    */
    81   this.chromeWindow = aProperties.chromeWindow;
    82   this.tab = aProperties.tab;
    84   /**
    85    * The canvas element used for rendering the visualization.
    86    */
    87   this.canvas = TiltUtils.DOM.initCanvas(aProperties.parentNode, {
    88     focusable: true,
    89     append: true
    90   });
    92   /**
    93    * Visualization logic and drawing loop.
    94    */
    95   this.presenter = new TiltVisualizer.Presenter(this.canvas,
    96     aProperties.chromeWindow,
    97     aProperties.contentWindow,
    98     aProperties.notifications,
    99     aProperties.onError || null,
   100     aProperties.onLoad || null);
   102   /**
   103    * Visualization mouse and keyboard controller.
   104    */
   105   this.controller = new TiltVisualizer.Controller(this.canvas, this.presenter);
   106 }
   108 exports.TiltVisualizer = TiltVisualizer;
   110 TiltVisualizer.prototype = {
   112   /**
   113    * Initializes the visualizer.
   114    */
   115   init: function TV_init()
   116   {
   117     this.presenter.init();
   118     this.bindToInspector(this.tab);
   119   },
   121   /**
   122    * Checks if this object was initialized properly.
   123    *
   124    * @return {Boolean} true if the object was initialized properly
   125    */
   126   isInitialized: function TV_isInitialized()
   127   {
   128     return this.presenter && this.presenter.isInitialized() &&
   129            this.controller && this.controller.isInitialized();
   130   },
   132   /**
   133    * Removes the overlay canvas used for rendering the visualization.
   134    */
   135   removeOverlay: function TV_removeOverlay()
   136   {
   137     if (this.canvas && this.canvas.parentNode) {
   138       this.canvas.parentNode.removeChild(this.canvas);
   139     }
   140   },
   142   /**
   143    * Explicitly cleans up this visualizer and sets everything to null.
   144    */
   145   cleanup: function TV_cleanup()
   146   {
   147     this.unbindInspector();
   149     if (this.controller) {
   150       TiltUtils.destroyObject(this.controller);
   151     }
   152     if (this.presenter) {
   153       TiltUtils.destroyObject(this.presenter);
   154     }
   156     let chromeWindow = this.chromeWindow;
   158     TiltUtils.destroyObject(this);
   159     TiltUtils.clearCache();
   160     TiltUtils.gc(chromeWindow);
   161   },
   163   /**
   164    * Listen to the inspector activity.
   165    */
   166   bindToInspector: function TV_bindToInspector(aTab)
   167   {
   168     this._browserTab = aTab;
   170     this.onNewNodeFromInspector = this.onNewNodeFromInspector.bind(this);
   171     this.onNewNodeFromTilt = this.onNewNodeFromTilt.bind(this);
   172     this.onInspectorReady = this.onInspectorReady.bind(this);
   173     this.onToolboxDestroyed = this.onToolboxDestroyed.bind(this);
   175     gDevTools.on("inspector-ready", this.onInspectorReady);
   176     gDevTools.on("toolbox-destroyed", this.onToolboxDestroyed);
   178     Services.obs.addObserver(this.onNewNodeFromTilt,
   179                              this.presenter.NOTIFICATIONS.HIGHLIGHTING,
   180                              false);
   181     Services.obs.addObserver(this.onNewNodeFromTilt,
   182                              this.presenter.NOTIFICATIONS.UNHIGHLIGHTING,
   183                              false);
   185     let target = TargetFactory.forTab(aTab);
   186     let toolbox = gDevTools.getToolbox(target);
   187     if (toolbox) {
   188       let panel = toolbox.getPanel("inspector");
   189       if (panel) {
   190         this.inspector = panel;
   191         this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
   192         this.onNewNodeFromInspector();
   193       }
   194     }
   195   },
   197   /**
   198    * Unregister inspector event listeners.
   199    */
   200   unbindInspector: function TV_unbindInspector()
   201   {
   202     this._browserTab = null;
   204     if (this.inspector) {
   205       if (this.inspector.selection) {
   206         this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
   207       }
   208       this.inspector = null;
   209     }
   211     gDevTools.off("inspector-ready", this.onInspectorReady);
   212     gDevTools.off("toolbox-destroyed", this.onToolboxDestroyed);
   214     Services.obs.removeObserver(this.onNewNodeFromTilt,
   215                                 this.presenter.NOTIFICATIONS.HIGHLIGHTING);
   216     Services.obs.removeObserver(this.onNewNodeFromTilt,
   217                                 this.presenter.NOTIFICATIONS.UNHIGHLIGHTING);
   218   },
   220   /**
   221    * When a new inspector is started.
   222    */
   223   onInspectorReady: function TV_onInspectorReady(event, toolbox, panel)
   224   {
   225     if (toolbox.target.tab === this._browserTab) {
   226       this.inspector = panel;
   227       this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
   228       this.onNewNodeFromTilt();
   229     }
   230   },
   232   /**
   233    * When the toolbox, therefor the inspector, is closed.
   234    */
   235   onToolboxDestroyed: function TV_onToolboxDestroyed(event, tab)
   236   {
   237     if (tab === this._browserTab &&
   238         this.inspector) {
   239       if (this.inspector.selection) {
   240         this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
   241       }
   242       this.inspector = null;
   243     }
   244   },
   246   /**
   247    * When a new node is selected in the inspector.
   248    */
   249   onNewNodeFromInspector: function TV_onNewNodeFromInspector()
   250   {
   251     if (this.inspector &&
   252         this.inspector.selection.reason != "tilt") {
   253       let selection = this.inspector.selection;
   254       let canHighlightNode = selection.isNode() &&
   255                               selection.isConnected() &&
   256                               selection.isElementNode();
   257       if (canHighlightNode) {
   258         this.presenter.highlightNode(selection.node);
   259       } else {
   260         this.presenter.highlightNodeFor(-1);
   261       }
   262     }
   263   },
   265   /**
   266    * When a new node is selected in Tilt.
   267    */
   268   onNewNodeFromTilt: function TV_onNewNodeFromTilt()
   269   {
   270     if (!this.inspector) {
   271       return;
   272     }
   273     let nodeIndex = this.presenter._currentSelection;
   274     if (nodeIndex < 0) {
   275       this.inspector.selection.setNodeFront(null, "tilt");
   276     }
   277     let node = this.presenter._traverseData.nodes[nodeIndex];
   278     node = this.inspector.walker.frontForRawNode(node);
   279     this.inspector.selection.setNodeFront(node, "tilt");
   280   },
   281 };
   283 /**
   284  * This object manages the visualization logic and drawing loop.
   285  *
   286  * @param {HTMLCanvasElement} aCanvas
   287  *                            the canvas element used for rendering
   288  * @param {Window} aChromeWindow
   289  *                 a reference to the top-level window
   290  * @param {Window} aContentWindow
   291  *                 the content window holding the document to be visualized
   292  * @param {Object} aNotifications
   293  *                 necessary notifications for Tilt
   294  * @param {Function} onError
   295  *                   function called if initialization failed
   296  * @param {Function} onLoad
   297  *                   function called if initialization worked
   298  */
   299 TiltVisualizer.Presenter = function TV_Presenter(
   300   aCanvas, aChromeWindow, aContentWindow, aNotifications, onError, onLoad)
   301 {
   302   /**
   303    * A canvas overlay used for drawing the visualization.
   304    */
   305   this.canvas = aCanvas;
   307   /**
   308    * Save a reference to the top-level window, to access Tilt.
   309    */
   310   this.chromeWindow = aChromeWindow;
   312   /**
   313    * The content window generating the visualization
   314    */
   315   this.contentWindow = aContentWindow;
   317   /**
   318    * Shortcut for accessing notifications strings.
   319    */
   320   this.NOTIFICATIONS = aNotifications;
   322   /**
   323    * Use the default node callback function
   324    */
   325   this.nodeCallback = null;
   327   /**
   328    * Create the renderer, containing useful functions for easy drawing.
   329    */
   330   this._renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
   332   /**
   333    * A custom shader used for drawing the visualization mesh.
   334    */
   335   this._visualizationProgram = null;
   337   /**
   338    * The combined mesh representing the document visualization.
   339    */
   340   this._texture = null;
   341   this._meshData = null;
   342   this._meshStacks = null;
   343   this._meshWireframe = null;
   344   this._traverseData = null;
   346   /**
   347    * A highlight quad drawn over a stacked dom node.
   348    */
   349   this._highlight = {
   350     disabled: true,
   351     v0: vec3.create(),
   352     v1: vec3.create(),
   353     v2: vec3.create(),
   354     v3: vec3.create()
   355   };
   357   /**
   358    * Scene transformations, exposing offset, translation and rotation.
   359    * Modified by events in the controller through delegate functions.
   360    */
   361   this.transforms = {
   362     zoom: 1,
   363     offset: vec3.create(),      // mesh offset, aligned to the viewport center
   364     translation: vec3.create(), // scene translation, on the [x, y, z] axis
   365     rotation: quat4.create()    // scene rotation, expressed as a quaternion
   366   };
   368   /**
   369    * Variables holding information about the initial and current node selected.
   370    */
   371   this._currentSelection = -1; // the selected node index
   372   this._initialMeshConfiguration = false; // true if the 3D mesh was configured
   374   /**
   375    * Variable specifying if the scene should be redrawn.
   376    * This should happen usually when the visualization is translated/rotated.
   377    */
   378   this._redraw = true;
   380   /**
   381    * Total time passed since the rendering started.
   382    * If the rendering is paused, this property won't get updated.
   383    */
   384   this._time = 0;
   386   /**
   387    * Frame delta time (the ammount of time passed for each frame).
   388    * This is used to smoothly interpolate animation transfroms.
   389    */
   390   this._delta = 0;
   391   this._prevFrameTime = 0;
   392   this._currFrameTime = 0;
   393 };
   395 TiltVisualizer.Presenter.prototype = {
   397   /**
   398    * Initializes the presenter and starts the animation loop
   399    */
   400   init: function TVP_init()
   401   {
   402     this._setup();
   403     this._loop();
   404   },
   406   /**
   407    * The initialization logic.
   408    */
   409   _setup: function TVP__setup()
   410   {
   411     let renderer = this._renderer;
   413     // if the renderer was destroyed, don't continue setup
   414     if (!renderer || !renderer.context) {
   415       return;
   416     }
   418     // create the visualization shaders and program to draw the stacks mesh
   419     this._visualizationProgram = new renderer.Program({
   420       vs: TiltVisualizer.MeshShader.vs,
   421       fs: TiltVisualizer.MeshShader.fs,
   422       attributes: ["vertexPosition", "vertexTexCoord", "vertexColor"],
   423       uniforms: ["mvMatrix", "projMatrix", "sampler"]
   424     });
   426     // get the document zoom to properly scale the visualization
   427     this.transforms.zoom = this._getPageZoom();
   429     // bind the owner object to the necessary functions
   430     TiltUtils.bindObjectFunc(this, "^_on");
   431     TiltUtils.bindObjectFunc(this, "_loop");
   433     this._setupTexture();
   434     this._setupMeshData();
   435     this._setupEventListeners();
   436     this.canvas.focus();
   437   },
   439   /**
   440    * Get page zoom factor.
   441    * @return {Number}
   442    */
   443   _getPageZoom: function TVP__getPageZoom() {
   444     return this.contentWindow
   445       .QueryInterface(Ci.nsIInterfaceRequestor)
   446       .getInterface(Ci.nsIDOMWindowUtils)
   447       .fullZoom;
   448   },
   450   /**
   451    * The animation logic.
   452    */
   453   _loop: function TVP__loop()
   454   {
   455     let renderer = this._renderer;
   457     // if the renderer was destroyed, don't continue rendering
   458     if (!renderer || !renderer.context) {
   459       return;
   460     }
   462     // prepare for the next frame of the animation loop
   463     this.chromeWindow.mozRequestAnimationFrame(this._loop);
   465     // only redraw if we really have to
   466     if (this._redraw) {
   467       this._redraw = false;
   468       this._drawVisualization();
   469     }
   471     // update the current presenter transfroms from the controller
   472     if ("function" === typeof this._controllerUpdate) {
   473       this._controllerUpdate(this._time, this._delta);
   474     }
   476     this._handleFrameDelta();
   477     this._handleKeyframeNotifications();
   478   },
   480   /**
   481    * Calculates the current frame delta time.
   482    */
   483   _handleFrameDelta: function TVP__handleFrameDelta()
   484   {
   485     this._prevFrameTime = this._currFrameTime;
   486     this._currFrameTime = this.chromeWindow.mozAnimationStartTime;
   487     this._delta = this._currFrameTime - this._prevFrameTime;
   488   },
   490   /**
   491    * Draws the visualization mesh and highlight quad.
   492    */
   493   _drawVisualization: function TVP__drawVisualization()
   494   {
   495     let renderer = this._renderer;
   496     let transforms = this.transforms;
   497     let w = renderer.width;
   498     let h = renderer.height;
   499     let ih = renderer.initialHeight;
   501     // if the mesh wasn't created yet, don't continue rendering
   502     if (!this._meshStacks || !this._meshWireframe) {
   503       return;
   504     }
   506     // clear the context to an opaque black background
   507     renderer.clear();
   508     renderer.perspective();
   510     // apply a transition transformation using an ortho and perspective matrix
   511     let ortho = mat4.ortho(0, w, h, 0, -1000, 1000);
   513     if (!this._isExecutingDestruction) {
   514       let f = this._time / INTRO_TRANSITION_DURATION;
   515       renderer.lerp(renderer.projMatrix, ortho, f, 8);
   516     } else {
   517       let f = this._time / OUTRO_TRANSITION_DURATION;
   518       renderer.lerp(renderer.projMatrix, ortho, 1 - f, 8);
   519     }
   521     // apply the preliminary transformations to the model view
   522     renderer.translate(w * 0.5, ih * 0.5, -INITIAL_Z_TRANSLATION);
   524     // calculate the camera matrix using the rotation and translation
   525     renderer.translate(transforms.translation[0], 0,
   526                        transforms.translation[2]);
   528     renderer.transform(quat4.toMat4(transforms.rotation));
   530     // offset the visualization mesh to center
   531     renderer.translate(transforms.offset[0],
   532                        transforms.offset[1] + transforms.translation[1], 0);
   534     renderer.scale(transforms.zoom, transforms.zoom);
   536     // draw the visualization mesh
   537     renderer.strokeWeight(2);
   538     renderer.depthTest(true);
   539     this._drawMeshStacks();
   540     this._drawMeshWireframe();
   541     this._drawHighlight();
   543     // make sure the initial transition is drawn until finished
   544     if (this._time < INTRO_TRANSITION_DURATION ||
   545         this._time < OUTRO_TRANSITION_DURATION) {
   546       this._redraw = true;
   547     }
   548     this._time += this._delta;
   549   },
   551   /**
   552    * Draws the meshStacks object.
   553    */
   554   _drawMeshStacks: function TVP__drawMeshStacks()
   555   {
   556     let renderer = this._renderer;
   557     let mesh = this._meshStacks;
   559     let visualizationProgram = this._visualizationProgram;
   560     let texture = this._texture;
   561     let mvMatrix = renderer.mvMatrix;
   562     let projMatrix = renderer.projMatrix;
   564     // use the necessary shader
   565     visualizationProgram.use();
   567     for (let i = 0, len = mesh.length; i < len; i++) {
   568       let group = mesh[i];
   570       // bind the attributes and uniforms as necessary
   571       visualizationProgram.bindVertexBuffer("vertexPosition", group.vertices);
   572       visualizationProgram.bindVertexBuffer("vertexTexCoord", group.texCoord);
   573       visualizationProgram.bindVertexBuffer("vertexColor", group.color);
   575       visualizationProgram.bindUniformMatrix("mvMatrix", mvMatrix);
   576       visualizationProgram.bindUniformMatrix("projMatrix", projMatrix);
   577       visualizationProgram.bindTexture("sampler", texture);
   579       // draw the vertices as TRIANGLES indexed elements
   580       renderer.drawIndexedVertices(renderer.context.TRIANGLES, group.indices);
   581     }
   583     // save the current model view and projection matrices
   584     mesh.mvMatrix = mat4.create(mvMatrix);
   585     mesh.projMatrix = mat4.create(projMatrix);
   586   },
   588   /**
   589    * Draws the meshWireframe object.
   590    */
   591   _drawMeshWireframe: function TVP__drawMeshWireframe()
   592   {
   593     let renderer = this._renderer;
   594     let mesh = this._meshWireframe;
   596     for (let i = 0, len = mesh.length; i < len; i++) {
   597       let group = mesh[i];
   599       // use the necessary shader
   600       renderer.useColorShader(group.vertices, WIREFRAME_COLOR);
   602       // draw the vertices as LINES indexed elements
   603       renderer.drawIndexedVertices(renderer.context.LINES, group.indices);
   604     }
   605   },
   607   /**
   608    * Draws a highlighted quad around a currently selected node.
   609    */
   610   _drawHighlight: function TVP__drawHighlight()
   611   {
   612     // check if there's anything to highlight (i.e any node is selected)
   613     if (!this._highlight.disabled) {
   615       // set the corresponding state to draw the highlight quad
   616       let renderer = this._renderer;
   617       let highlight = this._highlight;
   619       renderer.depthTest(false);
   620       renderer.fill(highlight.fill, 0.5);
   621       renderer.stroke(highlight.stroke);
   622       renderer.strokeWeight(highlight.strokeWeight);
   623       renderer.quad(highlight.v0, highlight.v1, highlight.v2, highlight.v3);
   624     }
   625   },
   627   /**
   628    * Creates or refreshes the texture applied to the visualization mesh.
   629    */
   630   _setupTexture: function TVP__setupTexture()
   631   {
   632     let renderer = this._renderer;
   634     // destroy any previously created texture
   635     TiltUtils.destroyObject(this._texture); this._texture = null;
   637     // if the renderer was destroyed, don't continue setup
   638     if (!renderer || !renderer.context) {
   639       return;
   640     }
   642     // get the maximum texture size
   643     this._maxTextureSize =
   644       renderer.context.getParameter(renderer.context.MAX_TEXTURE_SIZE);
   646     // use a simple shim to get the image representation of the document
   647     // this will be removed once the MOZ_window_region_texture bug #653656
   648     // is finished; currently just converting the document image to a texture
   649     // applied to the mesh
   650     this._texture = new renderer.Texture({
   651       source: TiltGL.TextureUtils.createContentImage(this.contentWindow,
   652                                                      this._maxTextureSize),
   653       format: "RGB"
   654     });
   656     if ("function" === typeof this._onSetupTexture) {
   657       this._onSetupTexture();
   658       this._onSetupTexture = null;
   659     }
   660   },
   662   /**
   663    * Create the combined mesh representing the document visualization by
   664    * traversing the document & adding a stack for each node that is drawable.
   665    *
   666    * @param {Object} aMeshData
   667    *                 object containing the necessary mesh verts, texcoord etc.
   668    */
   669   _setupMesh: function TVP__setupMesh(aMeshData)
   670   {
   671     let renderer = this._renderer;
   673     // destroy any previously created mesh
   674     TiltUtils.destroyObject(this._meshStacks); this._meshStacks = [];
   675     TiltUtils.destroyObject(this._meshWireframe); this._meshWireframe = [];
   677     // if the renderer was destroyed, don't continue setup
   678     if (!renderer || !renderer.context) {
   679       return;
   680     }
   682     // save the mesh data for future use
   683     this._meshData = aMeshData;
   685     // create a sub-mesh for each group in the mesh data
   686     for (let i = 0, len = aMeshData.groups.length; i < len; i++) {
   687       let group = aMeshData.groups[i];
   689       // create the visualization mesh using the vertices, texture coordinates
   690       // and indices computed when traversing the document object model
   691       this._meshStacks.push({
   692         vertices: new renderer.VertexBuffer(group.vertices, 3),
   693         texCoord: new renderer.VertexBuffer(group.texCoord, 2),
   694         color: new renderer.VertexBuffer(group.color, 3),
   695         indices: new renderer.IndexBuffer(group.stacksIndices)
   696       });
   698       // additionally, create a wireframe representation to make the
   699       // visualization a bit more pretty
   700       this._meshWireframe.push({
   701         vertices: this._meshStacks[i].vertices,
   702         indices: new renderer.IndexBuffer(group.wireframeIndices)
   703       });
   704     }
   706     // configure the required mesh transformations and background only once
   707     if (!this._initialMeshConfiguration) {
   708       this._initialMeshConfiguration = true;
   710       // set the necessary mesh offsets
   711       this.transforms.offset[0] = -renderer.width * 0.5;
   712       this.transforms.offset[1] = -renderer.height * 0.5;
   714       // make sure the canvas is opaque now that the initialization is finished
   715       this.canvas.style.background = TiltVisualizerStyle.canvas.background;
   717       this._drawVisualization();
   718       this._redraw = true;
   719     }
   721     if ("function" === typeof this._onSetupMesh) {
   722       this._onSetupMesh();
   723       this._onSetupMesh = null;
   724     }
   725   },
   727   /**
   728    * Computes the mesh vertices, texture coordinates etc. by groups of nodes.
   729    */
   730   _setupMeshData: function TVP__setupMeshData()
   731   {
   732     let renderer = this._renderer;
   734     // if the renderer was destroyed, don't continue setup
   735     if (!renderer || !renderer.context) {
   736       return;
   737     }
   739     // traverse the document and get the depths, coordinates and local names
   740     this._traverseData = TiltUtils.DOM.traverse(this.contentWindow, {
   741       nodeCallback: this.nodeCallback,
   742       invisibleElements: INVISIBLE_ELEMENTS,
   743       minSize: ELEMENT_MIN_SIZE,
   744       maxX: this._texture.width,
   745       maxY: this._texture.height
   746     });
   748     let worker = new ChromeWorker(TILT_CRAFTER);
   750     worker.addEventListener("message", function TVP_onMessage(event) {
   751       this._setupMesh(event.data);
   752     }.bind(this), false);
   754     // calculate necessary information regarding vertices, texture coordinates
   755     // etc. in a separate thread, as this process may take a while
   756     worker.postMessage({
   757       maxGroupNodes: MAX_GROUP_NODES,
   758       style: TiltVisualizerStyle.nodes,
   759       texWidth: this._texture.width,
   760       texHeight: this._texture.height,
   761       nodesInfo: this._traverseData.info
   762     });
   763   },
   765   /**
   766    * Sets up event listeners necessary for the presenter.
   767    */
   768   _setupEventListeners: function TVP__setupEventListeners()
   769   {
   770     this.contentWindow.addEventListener("resize", this._onResize, false);
   771   },
   773   /**
   774    * Called when the content window of the current browser is resized.
   775    */
   776   _onResize: function TVP_onResize(e)
   777   {
   778     let zoom = this._getPageZoom();
   779     let width = e.target.innerWidth * zoom;
   780     let height = e.target.innerHeight * zoom;
   782     // handle aspect ratio changes to update the projection matrix
   783     this._renderer.width = width;
   784     this._renderer.height = height;
   786     this._redraw = true;
   787   },
   789   /**
   790    * Highlights a specific node.
   791    *
   792    * @param {Element} aNode
   793    *                  the html node to be highlighted
   794    * @param {String} aFlags
   795    *                 flags specifying highlighting options
   796    */
   797   highlightNode: function TVP_highlightNode(aNode, aFlags)
   798   {
   799     this.highlightNodeFor(this._traverseData.nodes.indexOf(aNode), aFlags);
   800   },
   802   /**
   803    * Picks a stacked dom node at the x and y screen coordinates and highlights
   804    * the selected node in the mesh.
   805    *
   806    * @param {Number} x
   807    *                 the current horizontal coordinate of the mouse
   808    * @param {Number} y
   809    *                 the current vertical coordinate of the mouse
   810    * @param {Object} aProperties
   811    *                 an object containing the following properties:
   812    *      {Function} onpick: function to be called after picking succeeded
   813    *      {Function} onfail: function to be called after picking failed
   814    */
   815   highlightNodeAt: function TVP_highlightNodeAt(x, y, aProperties)
   816   {
   817     // make sure the properties parameter is a valid object
   818     aProperties = aProperties || {};
   820     // try to pick a mesh node using the current x, y coordinates
   821     this.pickNode(x, y, {
   823       /**
   824        * Mesh picking failed (nothing was found for the picked point).
   825        */
   826       onfail: function TVP_onHighlightFail()
   827       {
   828         this.highlightNodeFor(-1);
   830         if ("function" === typeof aProperties.onfail) {
   831           aProperties.onfail();
   832         }
   833       }.bind(this),
   835       /**
   836        * Mesh picking succeeded.
   837        *
   838        * @param {Object} aIntersection
   839        *                 object containing the intersection details
   840        */
   841       onpick: function TVP_onHighlightPick(aIntersection)
   842       {
   843         this.highlightNodeFor(aIntersection.index);
   845         if ("function" === typeof aProperties.onpick) {
   846           aProperties.onpick();
   847         }
   848       }.bind(this)
   849     });
   850   },
   852   /**
   853    * Sets the corresponding highlight coordinates and color based on the
   854    * information supplied.
   855    *
   856    * @param {Number} aNodeIndex
   857    *                 the index of the node in the this._traverseData array
   858    * @param {String} aFlags
   859    *                 flags specifying highlighting options
   860    */
   861   highlightNodeFor: function TVP_highlightNodeFor(aNodeIndex, aFlags)
   862   {
   863     this._redraw = true;
   865     // if the node was already selected, don't do anything
   866     if (this._currentSelection === aNodeIndex) {
   867       return;
   868     }
   870     // if an invalid or nonexisted node is specified, disable the highlight
   871     if (aNodeIndex < 0) {
   872       this._currentSelection = -1;
   873       this._highlight.disabled = true;
   875       Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.UNHIGHLIGHTING, null);
   876       return;
   877     }
   879     let highlight = this._highlight;
   880     let info = this._traverseData.info[aNodeIndex];
   881     let style = TiltVisualizerStyle.nodes;
   883     highlight.disabled = false;
   884     highlight.fill = style[info.name] || style.highlight.defaultFill;
   885     highlight.stroke = style.highlight.defaultStroke;
   886     highlight.strokeWeight = style.highlight.defaultStrokeWeight;
   888     let x = info.coord.left;
   889     let y = info.coord.top;
   890     let w = info.coord.width;
   891     let h = info.coord.height;
   892     let z = info.coord.depth + info.coord.thickness;
   894     vec3.set([x,     y,     z], highlight.v0);
   895     vec3.set([x + w, y,     z], highlight.v1);
   896     vec3.set([x + w, y + h, z], highlight.v2);
   897     vec3.set([x,     y + h, z], highlight.v3);
   899     this._currentSelection = aNodeIndex;
   901     // if something is highlighted, make sure it's inside the current viewport;
   902     // the point which should be moved into view is considered the center [x, y]
   903     // position along the top edge of the currently selected node
   905     if (aFlags && aFlags.indexOf("moveIntoView") !== -1)
   906     {
   907       this.controller.arcball.moveIntoView(vec3.lerp(
   908         vec3.scale(this._highlight.v0, this.transforms.zoom, []),
   909         vec3.scale(this._highlight.v1, this.transforms.zoom, []), 0.5));
   910     }
   912     Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.HIGHLIGHTING, null);
   913   },
   915   /**
   916    * Deletes a node from the visualization mesh.
   917    *
   918    * @param {Number} aNodeIndex
   919    *                 the index of the node in the this._traverseData array;
   920    *                 if not specified, it will default to the current selection
   921    */
   922   deleteNode: function TVP_deleteNode(aNodeIndex)
   923   {
   924     // we probably don't want to delete the html or body node.. just sayin'
   925     if ((aNodeIndex = aNodeIndex || this._currentSelection) < 1) {
   926       return;
   927     }
   929     let renderer = this._renderer;
   931     let groupIndex = parseInt(aNodeIndex / MAX_GROUP_NODES);
   932     let nodeIndex = parseInt((aNodeIndex + (groupIndex ? 1 : 0)) % MAX_GROUP_NODES);
   933     let group = this._meshStacks[groupIndex];
   934     let vertices = group.vertices.components;
   936     for (let i = 0, k = 36 * nodeIndex; i < 36; i++) {
   937       vertices[i + k] = 0;
   938     }
   940     group.vertices = new renderer.VertexBuffer(vertices, 3);
   941     this._highlight.disabled = true;
   942     this._redraw = true;
   944     Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.NODE_REMOVED, null);
   945   },
   947   /**
   948    * Picks a stacked dom node at the x and y screen coordinates and issues
   949    * a callback function with the found intersection.
   950    *
   951    * @param {Number} x
   952    *                 the current horizontal coordinate of the mouse
   953    * @param {Number} y
   954    *                 the current vertical coordinate of the mouse
   955    * @param {Object} aProperties
   956    *                 an object containing the following properties:
   957    *      {Function} onpick: function to be called at intersection
   958    *      {Function} onfail: function to be called if no intersections
   959    */
   960   pickNode: function TVP_pickNode(x, y, aProperties)
   961   {
   962     // make sure the properties parameter is a valid object
   963     aProperties = aProperties || {};
   965     // if the mesh wasn't created yet, don't continue picking
   966     if (!this._meshStacks || !this._meshWireframe) {
   967       return;
   968     }
   970     let worker = new ChromeWorker(TILT_PICKER);
   972     worker.addEventListener("message", function TVP_onMessage(event) {
   973       if (event.data) {
   974         if ("function" === typeof aProperties.onpick) {
   975           aProperties.onpick(event.data);
   976         }
   977       } else {
   978         if ("function" === typeof aProperties.onfail) {
   979           aProperties.onfail();
   980         }
   981       }
   982     }, false);
   984     let zoom = this._getPageZoom();
   985     let width = this._renderer.width * zoom;
   986     let height = this._renderer.height * zoom;
   987     x *= zoom;
   988     y *= zoom;
   990     // create a ray following the mouse direction from the near clipping plane
   991     // to the far clipping plane, to check for intersections with the mesh,
   992     // and do all the heavy lifting in a separate thread
   993     worker.postMessage({
   994       vertices: this._meshData.allVertices,
   996       // create the ray destined for 3D picking
   997       ray: vec3.createRay([x, y, 0], [x, y, 1], [0, 0, width, height],
   998         this._meshStacks.mvMatrix,
   999         this._meshStacks.projMatrix)
  1000     });
  1001   },
  1003   /**
  1004    * Delegate translation method, used by the controller.
  1006    * @param {Array} aTranslation
  1007    *                the new translation on the [x, y, z] axis
  1008    */
  1009   setTranslation: function TVP_setTranslation(aTranslation)
  1011     let x = aTranslation[0];
  1012     let y = aTranslation[1];
  1013     let z = aTranslation[2];
  1014     let transforms = this.transforms;
  1016     // only update the translation if it's not already set
  1017     if (transforms.translation[0] !== x ||
  1018         transforms.translation[1] !== y ||
  1019         transforms.translation[2] !== z) {
  1021       vec3.set(aTranslation, transforms.translation);
  1022       this._redraw = true;
  1024   },
  1026   /**
  1027    * Delegate rotation method, used by the controller.
  1029    * @param {Array} aQuaternion
  1030    *                the rotation quaternion, as [x, y, z, w]
  1031    */
  1032   setRotation: function TVP_setRotation(aQuaternion)
  1034     let x = aQuaternion[0];
  1035     let y = aQuaternion[1];
  1036     let z = aQuaternion[2];
  1037     let w = aQuaternion[3];
  1038     let transforms = this.transforms;
  1040     // only update the rotation if it's not already set
  1041     if (transforms.rotation[0] !== x ||
  1042         transforms.rotation[1] !== y ||
  1043         transforms.rotation[2] !== z ||
  1044         transforms.rotation[3] !== w) {
  1046       quat4.set(aQuaternion, transforms.rotation);
  1047       this._redraw = true;
  1049   },
  1051   /**
  1052    * Handles notifications at specific frame counts.
  1053    */
  1054   _handleKeyframeNotifications: function TV__handleKeyframeNotifications()
  1056     if (!TiltVisualizer.Prefs.introTransition && !this._isExecutingDestruction) {
  1057       this._time = INTRO_TRANSITION_DURATION;
  1059     if (!TiltVisualizer.Prefs.outroTransition && this._isExecutingDestruction) {
  1060       this._time = OUTRO_TRANSITION_DURATION;
  1063     if (this._time >= INTRO_TRANSITION_DURATION &&
  1064        !this._isInitializationFinished &&
  1065        !this._isExecutingDestruction) {
  1067       this._isInitializationFinished = true;
  1068       Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.INITIALIZED, null);
  1070       if ("function" === typeof this._onInitializationFinished) {
  1071         this._onInitializationFinished();
  1075     if (this._time >= OUTRO_TRANSITION_DURATION &&
  1076        !this._isDestructionFinished &&
  1077         this._isExecutingDestruction) {
  1079       this._isDestructionFinished = true;
  1080       Services.obs.notifyObservers(this.contentWindow, this.NOTIFICATIONS.BEFORE_DESTROYED, null);
  1082       if ("function" === typeof this._onDestructionFinished) {
  1083         this._onDestructionFinished();
  1086   },
  1088   /**
  1089    * Starts executing the destruction sequence and issues a callback function
  1090    * when finished.
  1092    * @param {Function} aCallback
  1093    *                   the destruction finished callback
  1094    */
  1095   executeDestruction: function TV_executeDestruction(aCallback)
  1097     if (!this._isExecutingDestruction) {
  1098       this._isExecutingDestruction = true;
  1099       this._onDestructionFinished = aCallback;
  1101       // if we execute the destruction after the initialization finishes,
  1102       // proceed normally; otherwise, skip everything and immediately issue
  1103       // the callback
  1105       if (this._time > OUTRO_TRANSITION_DURATION) {
  1106         this._time = 0;
  1107         this._redraw = true;
  1108       } else {
  1109         aCallback();
  1112   },
  1114   /**
  1115    * Checks if this object was initialized properly.
  1117    * @return {Boolean} true if the object was initialized properly
  1118    */
  1119   isInitialized: function TVP_isInitialized()
  1121     return this._renderer && this._renderer.context;
  1122   },
  1124   /**
  1125    * Function called when this object is destroyed.
  1126    */
  1127   _finalize: function TVP__finalize()
  1129     TiltUtils.destroyObject(this._visualizationProgram);
  1130     TiltUtils.destroyObject(this._texture);
  1132     if (this._meshStacks) {
  1133       this._meshStacks.forEach(function(group) {
  1134         TiltUtils.destroyObject(group.vertices);
  1135         TiltUtils.destroyObject(group.texCoord);
  1136         TiltUtils.destroyObject(group.color);
  1137         TiltUtils.destroyObject(group.indices);
  1138       });
  1140     if (this._meshWireframe) {
  1141       this._meshWireframe.forEach(function(group) {
  1142         TiltUtils.destroyObject(group.indices);
  1143       });
  1146     TiltUtils.destroyObject(this._renderer);
  1148     // Closing the tab would result in contentWindow being a dead object,
  1149     // so operations like removing event listeners won't work anymore.
  1150     if (this.contentWindow == this.chromeWindow.content) {
  1151       this.contentWindow.removeEventListener("resize", this._onResize, false);
  1154 };
  1156 /**
  1157  * A mouse and keyboard controller implementation.
  1159  * @param {HTMLCanvasElement} aCanvas
  1160  *                            the visualization canvas element
  1161  * @param {TiltVisualizer.Presenter} aPresenter
  1162  *                                   the presenter instance to control
  1163  */
  1164 TiltVisualizer.Controller = function TV_Controller(aCanvas, aPresenter)
  1166   /**
  1167    * A canvas overlay on which mouse and keyboard event listeners are attached.
  1168    */
  1169   this.canvas = aCanvas;
  1171   /**
  1172    * Save a reference to the presenter to modify its model-view transforms.
  1173    */
  1174   this.presenter = aPresenter;
  1175   this.presenter.controller = this;
  1177   /**
  1178    * The initial controller dimensions and offset, in pixels.
  1179    */
  1180   this._zoom = aPresenter.transforms.zoom;
  1181   this._left = (aPresenter.contentWindow.pageXOffset || 0) * this._zoom;
  1182   this._top = (aPresenter.contentWindow.pageYOffset || 0) * this._zoom;
  1183   this._width = aCanvas.width;
  1184   this._height = aCanvas.height;
  1186   /**
  1187    * Arcball used to control the visualization using the mouse.
  1188    */
  1189   this.arcball = new TiltVisualizer.Arcball(
  1190     this.presenter.chromeWindow, this._width, this._height, 0,
  1192       this._width + this._left < aPresenter._maxTextureSize ? -this._left : 0,
  1193       this._height + this._top < aPresenter._maxTextureSize ? -this._top : 0
  1194     ]);
  1196   /**
  1197    * Object containing the rotation quaternion and the translation amount.
  1198    */
  1199   this._coordinates = null;
  1201   // bind the owner object to the necessary functions
  1202   TiltUtils.bindObjectFunc(this, "_update");
  1203   TiltUtils.bindObjectFunc(this, "^_on");
  1205   // add the necessary event listeners
  1206   this.addEventListeners();
  1208   // attach this controller's update function to the presenter ondraw event
  1209   this.presenter._controllerUpdate = this._update;
  1210 };
  1212 TiltVisualizer.Controller.prototype = {
  1214   /**
  1215    * Adds events listeners required by this controller.
  1216    */
  1217   addEventListeners: function TVC_addEventListeners()
  1219     let canvas = this.canvas;
  1220     let presenter = this.presenter;
  1222     // bind commonly used mouse and keyboard events with the controller
  1223     canvas.addEventListener("mousedown", this._onMouseDown, false);
  1224     canvas.addEventListener("mouseup", this._onMouseUp, false);
  1225     canvas.addEventListener("mousemove", this._onMouseMove, false);
  1226     canvas.addEventListener("mouseover", this._onMouseOver, false);
  1227     canvas.addEventListener("mouseout", this._onMouseOut, false);
  1228     canvas.addEventListener("MozMousePixelScroll", this._onMozScroll, false);
  1229     canvas.addEventListener("keydown", this._onKeyDown, false);
  1230     canvas.addEventListener("keyup", this._onKeyUp, false);
  1231     canvas.addEventListener("blur", this._onBlur, false);
  1233     // handle resize events to change the arcball dimensions
  1234     presenter.contentWindow.addEventListener("resize", this._onResize, false);
  1235   },
  1237   /**
  1238    * Removes all added events listeners required by this controller.
  1239    */
  1240   removeEventListeners: function TVC_removeEventListeners()
  1242     let canvas = this.canvas;
  1243     let presenter = this.presenter;
  1245     canvas.removeEventListener("mousedown", this._onMouseDown, false);
  1246     canvas.removeEventListener("mouseup", this._onMouseUp, false);
  1247     canvas.removeEventListener("mousemove", this._onMouseMove, false);
  1248     canvas.removeEventListener("mouseover", this._onMouseOver, false);
  1249     canvas.removeEventListener("mouseout", this._onMouseOut, false);
  1250     canvas.removeEventListener("MozMousePixelScroll", this._onMozScroll, false);
  1251     canvas.removeEventListener("keydown", this._onKeyDown, false);
  1252     canvas.removeEventListener("keyup", this._onKeyUp, false);
  1253     canvas.removeEventListener("blur", this._onBlur, false);
  1255     // Closing the tab would result in contentWindow being a dead object,
  1256     // so operations like removing event listeners won't work anymore.
  1257     if (presenter.contentWindow == presenter.chromeWindow.content) {
  1258       presenter.contentWindow.removeEventListener("resize", this._onResize, false);
  1260   },
  1262   /**
  1263    * Function called each frame, updating the visualization camera transforms.
  1265    * @param {Number} aTime
  1266    *                 total time passed since rendering started
  1267    * @param {Number} aDelta
  1268    *                 the current animation frame delta
  1269    */
  1270   _update: function TVC__update(aTime, aDelta)
  1272     this._time = aTime;
  1273     this._coordinates = this.arcball.update(aDelta);
  1275     this.presenter.setRotation(this._coordinates.rotation);
  1276     this.presenter.setTranslation(this._coordinates.translation);
  1277   },
  1279   /**
  1280    * Called once after every time a mouse button is pressed.
  1281    */
  1282   _onMouseDown: function TVC__onMouseDown(e)
  1284     e.target.focus();
  1285     e.preventDefault();
  1286     e.stopPropagation();
  1288     if (this._time < MOUSE_INTRO_DELAY) {
  1289       return;
  1292     // calculate x and y coordinates using using the client and target offset
  1293     let button = e.which;
  1294     this._downX = e.clientX - e.target.offsetLeft;
  1295     this._downY = e.clientY - e.target.offsetTop;
  1297     this.arcball.mouseDown(this._downX, this._downY, button);
  1298   },
  1300   /**
  1301    * Called every time a mouse button is released.
  1302    */
  1303   _onMouseUp: function TVC__onMouseUp(e)
  1305     e.preventDefault();
  1306     e.stopPropagation();
  1308     if (this._time < MOUSE_INTRO_DELAY) {
  1309       return;
  1312     // calculate x and y coordinates using using the client and target offset
  1313     let button = e.which;
  1314     let upX = e.clientX - e.target.offsetLeft;
  1315     let upY = e.clientY - e.target.offsetTop;
  1317     // a click in Tilt is issued only when the mouse pointer stays in
  1318     // relatively the same position
  1319     if (Math.abs(this._downX - upX) < MOUSE_CLICK_THRESHOLD &&
  1320         Math.abs(this._downY - upY) < MOUSE_CLICK_THRESHOLD) {
  1322       this.presenter.highlightNodeAt(upX, upY);
  1325     this.arcball.mouseUp(upX, upY, button);
  1326   },
  1328   /**
  1329    * Called every time the mouse moves.
  1330    */
  1331   _onMouseMove: function TVC__onMouseMove(e)
  1333     e.preventDefault();
  1334     e.stopPropagation();
  1336     if (this._time < MOUSE_INTRO_DELAY) {
  1337       return;
  1340     // calculate x and y coordinates using using the client and target offset
  1341     let moveX = e.clientX - e.target.offsetLeft;
  1342     let moveY = e.clientY - e.target.offsetTop;
  1344     this.arcball.mouseMove(moveX, moveY);
  1345   },
  1347   /**
  1348    * Called when the mouse leaves the visualization bounds.
  1349    */
  1350   _onMouseOver: function TVC__onMouseOver(e)
  1352     e.preventDefault();
  1353     e.stopPropagation();
  1355     this.arcball.mouseOver();
  1356   },
  1358   /**
  1359    * Called when the mouse leaves the visualization bounds.
  1360    */
  1361   _onMouseOut: function TVC__onMouseOut(e)
  1363     e.preventDefault();
  1364     e.stopPropagation();
  1366     this.arcball.mouseOut();
  1367   },
  1369   /**
  1370    * Called when the mouse wheel is used.
  1371    */
  1372   _onMozScroll: function TVC__onMozScroll(e)
  1374     e.preventDefault();
  1375     e.stopPropagation();
  1377     this.arcball.zoom(e.detail);
  1378   },
  1380   /**
  1381    * Called when a key is pressed.
  1382    */
  1383   _onKeyDown: function TVC__onKeyDown(e)
  1385     let code = e.keyCode || e.which;
  1387     if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
  1388       e.preventDefault();
  1389       e.stopPropagation();
  1390       this.arcball.keyDown(code);
  1391     } else {
  1392       this.arcball.cancelKeyEvents();
  1395     if (e.keyCode === e.DOM_VK_ESCAPE) {
  1396       let {TiltManager} = require("devtools/tilt/tilt");
  1397       let tilt =
  1398         TiltManager.getTiltForBrowser(this.presenter.chromeWindow);
  1399       e.preventDefault();
  1400       e.stopPropagation();
  1401       tilt.destroy(tilt.currentWindowId, true);
  1403   },
  1405   /**
  1406    * Called when a key is released.
  1407    */
  1408   _onKeyUp: function TVC__onKeyUp(e)
  1410     let code = e.keyCode || e.which;
  1412     if (code === e.DOM_VK_X) {
  1413       this.presenter.deleteNode();
  1415     if (code === e.DOM_VK_F) {
  1416       let highlight = this.presenter._highlight;
  1417       let zoom = this.presenter.transforms.zoom;
  1419       this.arcball.moveIntoView(vec3.lerp(
  1420         vec3.scale(highlight.v0, zoom, []),
  1421         vec3.scale(highlight.v1, zoom, []), 0.5));
  1423     if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
  1424       e.preventDefault();
  1425       e.stopPropagation();
  1426       this.arcball.keyUp(code);
  1428   },
  1430   /**
  1431    * Called when the canvas looses focus.
  1432    */
  1433   _onBlur: function TVC__onBlur(e) {
  1434     this.arcball.cancelKeyEvents();
  1435   },
  1437   /**
  1438    * Called when the content window of the current browser is resized.
  1439    */
  1440   _onResize: function TVC__onResize(e)
  1442     let zoom = this.presenter._getPageZoom();
  1443     let width = e.target.innerWidth * zoom;
  1444     let height = e.target.innerHeight * zoom;
  1446     this.arcball.resize(width, height);
  1447   },
  1449   /**
  1450    * Checks if this object was initialized properly.
  1452    * @return {Boolean} true if the object was initialized properly
  1453    */
  1454   isInitialized: function TVC_isInitialized()
  1456     return this.arcball ? true : false;
  1457   },
  1459   /**
  1460    * Function called when this object is destroyed.
  1461    */
  1462   _finalize: function TVC__finalize()
  1464     TiltUtils.destroyObject(this.arcball);
  1465     TiltUtils.destroyObject(this._coordinates);
  1467     this.removeEventListeners();
  1468     this.presenter.controller = null;
  1469     this.presenter._controllerUpdate = null;
  1471 };
  1473 /**
  1474  * This is a general purpose 3D rotation controller described by Ken Shoemake
  1475  * in the Graphics Interface ’92 Proceedings. It features good behavior
  1476  * easy implementation, cheap execution.
  1478  * @param {Window} aChromeWindow
  1479  *                 a reference to the top-level window
  1480  * @param {Number} aWidth
  1481  *                 the width of canvas
  1482  * @param {Number} aHeight
  1483  *                 the height of canvas
  1484  * @param {Number} aRadius
  1485  *                 optional, the radius of the arcball
  1486  * @param {Array} aInitialTrans
  1487  *                optional, initial vector translation
  1488  * @param {Array} aInitialRot
  1489  *                optional, initial quaternion rotation
  1490  */
  1491 TiltVisualizer.Arcball = function TV_Arcball(
  1492   aChromeWindow, aWidth, aHeight, aRadius, aInitialTrans, aInitialRot)
  1494   /**
  1495    * Save a reference to the top-level window to set/remove intervals.
  1496    */
  1497   this.chromeWindow = aChromeWindow;
  1499   /**
  1500    * Values retaining the current horizontal and vertical mouse coordinates.
  1501    */
  1502   this._mousePress = vec3.create();
  1503   this._mouseRelease = vec3.create();
  1504   this._mouseMove = vec3.create();
  1505   this._mouseLerp = vec3.create();
  1506   this._mouseButton = -1;
  1508   /**
  1509    * Object retaining the current pressed key codes.
  1510    */
  1511   this._keyCode = {};
  1513   /**
  1514    * The vectors representing the mouse coordinates mapped on the arcball
  1515    * and their perpendicular converted from (x, y) to (x, y, z) at specific
  1516    * events like mousePressed and mouseDragged.
  1517    */
  1518   this._startVec = vec3.create();
  1519   this._endVec = vec3.create();
  1520   this._pVec = vec3.create();
  1522   /**
  1523    * The corresponding rotation quaternions.
  1524    */
  1525   this._lastRot = quat4.create();
  1526   this._deltaRot = quat4.create();
  1527   this._currentRot = quat4.create(aInitialRot);
  1529   /**
  1530    * The current camera translation coordinates.
  1531    */
  1532   this._lastTrans = vec3.create();
  1533   this._deltaTrans = vec3.create();
  1534   this._currentTrans = vec3.create(aInitialTrans);
  1535   this._zoomAmount = 0;
  1537   /**
  1538    * Additional rotation and translation vectors.
  1539    */
  1540   this._additionalRot = vec3.create();
  1541   this._additionalTrans = vec3.create();
  1542   this._deltaAdditionalRot = quat4.create();
  1543   this._deltaAdditionalTrans = vec3.create();
  1545   // load the keys controlling the arcball
  1546   this._loadKeys();
  1548   // set the current dimensions of the arcball
  1549   this.resize(aWidth, aHeight, aRadius);
  1550 };
  1552 TiltVisualizer.Arcball.prototype = {
  1554   /**
  1555    * Call this function whenever you need the updated rotation quaternion
  1556    * and the zoom amount. These values will be returned as "rotation" and
  1557    * "translation" properties inside an object.
  1559    * @param {Number} aDelta
  1560    *                 the current animation frame delta
  1562    * @return {Object} the rotation quaternion and the translation amount
  1563    */
  1564   update: function TVA_update(aDelta)
  1566     let mousePress = this._mousePress;
  1567     let mouseRelease = this._mouseRelease;
  1568     let mouseMove = this._mouseMove;
  1569     let mouseLerp = this._mouseLerp;
  1570     let mouseButton = this._mouseButton;
  1572     // smoothly update the mouse coordinates
  1573     mouseLerp[0] += (mouseMove[0] - mouseLerp[0]) * ARCBALL_SENSITIVITY;
  1574     mouseLerp[1] += (mouseMove[1] - mouseLerp[1]) * ARCBALL_SENSITIVITY;
  1576     // cache the interpolated mouse coordinates
  1577     let x = mouseLerp[0];
  1578     let y = mouseLerp[1];
  1580     // the smoothed arcball rotation may not be finished when the mouse is
  1581     // pressed again, so cancel the rotation if other events occur or the
  1582     // animation finishes
  1583     if (mouseButton === 3 || x === mouseRelease[0] && y === mouseRelease[1]) {
  1584       this._rotating = false;
  1587     let startVec = this._startVec;
  1588     let endVec = this._endVec;
  1589     let pVec = this._pVec;
  1591     let lastRot = this._lastRot;
  1592     let deltaRot = this._deltaRot;
  1593     let currentRot = this._currentRot;
  1595     // left mouse button handles rotation
  1596     if (mouseButton === 1 || this._rotating) {
  1597       // the rotation doesn't stop immediately after the left mouse button is
  1598       // released, so add a flag to smoothly continue it until it ends
  1599       this._rotating = true;
  1601       // find the sphere coordinates of the mouse positions
  1602       this._pointToSphere(x, y, this.width, this.height, this.radius, endVec);
  1604       // compute the vector perpendicular to the start & end vectors
  1605       vec3.cross(startVec, endVec, pVec);
  1607       // if the begin and end vectors don't coincide
  1608       if (vec3.length(pVec) > 0) {
  1609         deltaRot[0] = pVec[0];
  1610         deltaRot[1] = pVec[1];
  1611         deltaRot[2] = pVec[2];
  1613         // in the quaternion values, w is cosine (theta / 2),
  1614         // where theta is the rotation angle
  1615         deltaRot[3] = -vec3.dot(startVec, endVec);
  1616       } else {
  1617         // return an identity rotation quaternion
  1618         deltaRot[0] = 0;
  1619         deltaRot[1] = 0;
  1620         deltaRot[2] = 0;
  1621         deltaRot[3] = 1;
  1624       // calculate the current rotation based on the mouse click events
  1625       quat4.multiply(lastRot, deltaRot, currentRot);
  1626     } else {
  1627       // save the current quaternion to stack rotations
  1628       quat4.set(currentRot, lastRot);
  1631     let lastTrans = this._lastTrans;
  1632     let deltaTrans = this._deltaTrans;
  1633     let currentTrans = this._currentTrans;
  1635     // right mouse button handles panning
  1636     if (mouseButton === 3) {
  1637       // calculate a delta translation between the new and old mouse position
  1638       // and save it to the current translation
  1639       deltaTrans[0] = mouseMove[0] - mousePress[0];
  1640       deltaTrans[1] = mouseMove[1] - mousePress[1];
  1642       currentTrans[0] = lastTrans[0] + deltaTrans[0];
  1643       currentTrans[1] = lastTrans[1] + deltaTrans[1];
  1644     } else {
  1645       // save the current panning to stack translations
  1646       lastTrans[0] = currentTrans[0];
  1647       lastTrans[1] = currentTrans[1];
  1650     let zoomAmount = this._zoomAmount;
  1651     let keyCode = this._keyCode;
  1653     // mouse wheel handles zooming
  1654     deltaTrans[2] = (zoomAmount - currentTrans[2]) * ARCBALL_ZOOM_STEP;
  1655     currentTrans[2] += deltaTrans[2];
  1657     let additionalRot = this._additionalRot;
  1658     let additionalTrans = this._additionalTrans;
  1659     let deltaAdditionalRot = this._deltaAdditionalRot;
  1660     let deltaAdditionalTrans = this._deltaAdditionalTrans;
  1662     let rotateKeys = this.rotateKeys;
  1663     let panKeys = this.panKeys;
  1664     let zoomKeys = this.zoomKeys;
  1665     let resetKey = this.resetKey;
  1667     // handle additional rotation and translation by the keyboard
  1668     if (keyCode[rotateKeys.left]) {
  1669       additionalRot[0] -= ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1671     if (keyCode[rotateKeys.right]) {
  1672       additionalRot[0] += ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1674     if (keyCode[rotateKeys.up]) {
  1675       additionalRot[1] += ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1677     if (keyCode[rotateKeys.down]) {
  1678       additionalRot[1] -= ARCBALL_SENSITIVITY * ARCBALL_ROTATION_STEP;
  1680     if (keyCode[panKeys.left]) {
  1681       additionalTrans[0] -= ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1683     if (keyCode[panKeys.right]) {
  1684       additionalTrans[0] += ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1686     if (keyCode[panKeys.up]) {
  1687       additionalTrans[1] -= ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1689     if (keyCode[panKeys.down]) {
  1690       additionalTrans[1] += ARCBALL_SENSITIVITY * ARCBALL_TRANSLATION_STEP;
  1692     if (keyCode[zoomKeys["in"][0]] ||
  1693         keyCode[zoomKeys["in"][1]] ||
  1694         keyCode[zoomKeys["in"][2]]) {
  1695       this.zoom(-ARCBALL_TRANSLATION_STEP);
  1697     if (keyCode[zoomKeys["out"][0]] ||
  1698         keyCode[zoomKeys["out"][1]]) {
  1699       this.zoom(ARCBALL_TRANSLATION_STEP);
  1701     if (keyCode[zoomKeys["unzoom"]]) {
  1702       this._zoomAmount = 0;
  1704     if (keyCode[resetKey]) {
  1705       this.reset();
  1708     // update the delta key rotations and translations
  1709     deltaAdditionalRot[0] +=
  1710       (additionalRot[0] - deltaAdditionalRot[0]) * ARCBALL_SENSITIVITY;
  1711     deltaAdditionalRot[1] +=
  1712       (additionalRot[1] - deltaAdditionalRot[1]) * ARCBALL_SENSITIVITY;
  1713     deltaAdditionalRot[2] +=
  1714       (additionalRot[2] - deltaAdditionalRot[2]) * ARCBALL_SENSITIVITY;
  1716     deltaAdditionalTrans[0] +=
  1717       (additionalTrans[0] - deltaAdditionalTrans[0]) * ARCBALL_SENSITIVITY;
  1718     deltaAdditionalTrans[1] +=
  1719       (additionalTrans[1] - deltaAdditionalTrans[1]) * ARCBALL_SENSITIVITY;
  1721     // create an additional rotation based on the key events
  1722     quat4.fromEuler(
  1723       deltaAdditionalRot[0],
  1724       deltaAdditionalRot[1],
  1725       deltaAdditionalRot[2], deltaRot);
  1727     // create an additional translation based on the key events
  1728     vec3.set([deltaAdditionalTrans[0], deltaAdditionalTrans[1], 0], deltaTrans);
  1730     // handle the reset animation steps if necessary
  1731     if (this._resetInProgress) {
  1732       this._nextResetStep(aDelta || 1);
  1735     // return the current rotation and translation
  1736     return {
  1737       rotation: quat4.multiply(deltaRot, currentRot),
  1738       translation: vec3.add(deltaTrans, currentTrans)
  1739     };
  1740   },
  1742   /**
  1743    * Function handling the mouseDown event.
  1744    * Call this when the mouse was pressed.
  1746    * @param {Number} x
  1747    *                 the current horizontal coordinate of the mouse
  1748    * @param {Number} y
  1749    *                 the current vertical coordinate of the mouse
  1750    * @param {Number} aButton
  1751    *                 which mouse button was pressed
  1752    */
  1753   mouseDown: function TVA_mouseDown(x, y, aButton)
  1755     // save the mouse down state and prepare for rotations or translations
  1756     this._mousePress[0] = x;
  1757     this._mousePress[1] = y;
  1758     this._mouseButton = aButton;
  1759     this._cancelReset();
  1760     this._save();
  1762     // find the sphere coordinates of the mouse positions
  1763     this._pointToSphere(
  1764       x, y, this.width, this.height, this.radius, this._startVec);
  1766     quat4.set(this._currentRot, this._lastRot);
  1767   },
  1769   /**
  1770    * Function handling the mouseUp event.
  1771    * Call this when a mouse button was released.
  1773    * @param {Number} x
  1774    *                 the current horizontal coordinate of the mouse
  1775    * @param {Number} y
  1776    *                 the current vertical coordinate of the mouse
  1777    */
  1778   mouseUp: function TVA_mouseUp(x, y)
  1780     // save the mouse up state and prepare for rotations or translations
  1781     this._mouseRelease[0] = x;
  1782     this._mouseRelease[1] = y;
  1783     this._mouseButton = -1;
  1784   },
  1786   /**
  1787    * Function handling the mouseMove event.
  1788    * Call this when the mouse was moved.
  1790    * @param {Number} x
  1791    *                 the current horizontal coordinate of the mouse
  1792    * @param {Number} y
  1793    *                 the current vertical coordinate of the mouse
  1794    */
  1795   mouseMove: function TVA_mouseMove(x, y)
  1797     // save the mouse move state and prepare for rotations or translations
  1798     // only if the mouse is pressed
  1799     if (this._mouseButton !== -1) {
  1800       this._mouseMove[0] = x;
  1801       this._mouseMove[1] = y;
  1803   },
  1805   /**
  1806    * Function handling the mouseOver event.
  1807    * Call this when the mouse enters the context bounds.
  1808    */
  1809   mouseOver: function TVA_mouseOver()
  1811     // if the mouse just entered the parent bounds, stop the animation
  1812     this._mouseButton = -1;
  1813   },
  1815   /**
  1816    * Function handling the mouseOut event.
  1817    * Call this when the mouse leaves the context bounds.
  1818    */
  1819   mouseOut: function TVA_mouseOut()
  1821     // if the mouse leaves the parent bounds, stop the animation
  1822     this._mouseButton = -1;
  1823   },
  1825   /**
  1826    * Function handling the arcball zoom amount.
  1827    * Call this, for example, when the mouse wheel was scrolled or zoom keys
  1828    * were pressed.
  1830    * @param {Number} aZoom
  1831    *                 the zoom direction and speed
  1832    */
  1833   zoom: function TVA_zoom(aZoom)
  1835     this._cancelReset();
  1836     this._zoomAmount = TiltMath.clamp(this._zoomAmount - aZoom,
  1837       ARCBALL_ZOOM_MIN, ARCBALL_ZOOM_MAX);
  1838   },
  1840   /**
  1841    * Function handling the keyDown event.
  1842    * Call this when a key was pressed.
  1844    * @param {Number} aCode
  1845    *                 the code corresponding to the key pressed
  1846    */
  1847   keyDown: function TVA_keyDown(aCode)
  1849     this._cancelReset();
  1850     this._keyCode[aCode] = true;
  1851   },
  1853   /**
  1854    * Function handling the keyUp event.
  1855    * Call this when a key was released.
  1857    * @param {Number} aCode
  1858    *                 the code corresponding to the key released
  1859    */
  1860   keyUp: function TVA_keyUp(aCode)
  1862     this._keyCode[aCode] = false;
  1863   },
  1865   /**
  1866    * Maps the 2d coordinates of the mouse location to a 3d point on a sphere.
  1868    * @param {Number} x
  1869    *                 the current horizontal coordinate of the mouse
  1870    * @param {Number} y
  1871    *                 the current vertical coordinate of the mouse
  1872    * @param {Number} aWidth
  1873    *                 the width of canvas
  1874    * @param {Number} aHeight
  1875    *                 the height of canvas
  1876    * @param {Number} aRadius
  1877    *                 optional, the radius of the arcball
  1878    * @param {Array} aSphereVec
  1879    *                a 3d vector to store the sphere coordinates
  1880    */
  1881   _pointToSphere: function TVA__pointToSphere(
  1882     x, y, aWidth, aHeight, aRadius, aSphereVec)
  1884     // adjust point coords and scale down to range of [-1..1]
  1885     x = (x - aWidth * 0.5) / aRadius;
  1886     y = (y - aHeight * 0.5) / aRadius;
  1888     // compute the square length of the vector to the point from the center
  1889     let normal = 0;
  1890     let sqlength = x * x + y * y;
  1892     // if the point is mapped outside of the sphere
  1893     if (sqlength > 1) {
  1894       // calculate the normalization factor
  1895       normal = 1 / Math.sqrt(sqlength);
  1897       // set the normalized vector (a point on the sphere)
  1898       aSphereVec[0] = x * normal;
  1899       aSphereVec[1] = y * normal;
  1900       aSphereVec[2] = 0;
  1901     } else {
  1902       // set the vector to a point mapped inside the sphere
  1903       aSphereVec[0] = x;
  1904       aSphereVec[1] = y;
  1905       aSphereVec[2] = Math.sqrt(1 - sqlength);
  1907   },
  1909   /**
  1910    * Cancels all pending transformations caused by key events.
  1911    */
  1912   cancelKeyEvents: function TVA_cancelKeyEvents()
  1914     this._keyCode = {};
  1915   },
  1917   /**
  1918    * Cancels all pending transformations caused by mouse events.
  1919    */
  1920   cancelMouseEvents: function TVA_cancelMouseEvents()
  1922     this._rotating = false;
  1923     this._mouseButton = -1;
  1924   },
  1926   /**
  1927    * Incremental translation method.
  1929    * @param {Array} aTranslation
  1930    *                the translation ammount on the [x, y] axis
  1931    */
  1932   translate: function TVP_translate(aTranslation)
  1934     this._additionalTrans[0] += aTranslation[0];
  1935     this._additionalTrans[1] += aTranslation[1];
  1936   },
  1938   /**
  1939    * Incremental rotation method.
  1941    * @param {Array} aRotation
  1942    *                the rotation ammount along the [x, y, z] axis
  1943    */
  1944   rotate: function TVP_rotate(aRotation)
  1946     // explicitly rotate along y, x, z values because they're eulerian angles
  1947     this._additionalRot[0] += TiltMath.radians(aRotation[1]);
  1948     this._additionalRot[1] += TiltMath.radians(aRotation[0]);
  1949     this._additionalRot[2] += TiltMath.radians(aRotation[2]);
  1950   },
  1952   /**
  1953    * Moves a target point into view only if it's outside the currently visible
  1954    * area bounds (in which case it also resets any additional transforms).
  1956    * @param {Arary} aPoint
  1957    *                the [x, y] point which should be brought into view
  1958    */
  1959   moveIntoView: function TVA_moveIntoView(aPoint) {
  1960     let visiblePointX = -(this._currentTrans[0] + this._additionalTrans[0]);
  1961     let visiblePointY = -(this._currentTrans[1] + this._additionalTrans[1]);
  1963     if (aPoint[1] - visiblePointY - MOVE_INTO_VIEW_ACCURACY > this.height ||
  1964         aPoint[1] - visiblePointY + MOVE_INTO_VIEW_ACCURACY < 0 ||
  1965         aPoint[0] - visiblePointX > this.width ||
  1966         aPoint[0] - visiblePointX < 0) {
  1967       this.reset([0, -aPoint[1]]);
  1969   },
  1971   /**
  1972    * Resize this implementation to use different bounds.
  1973    * This function is automatically called when the arcball is created.
  1975    * @param {Number} newWidth
  1976    *                 the new width of canvas
  1977    * @param {Number} newHeight
  1978    *                 the new  height of canvas
  1979    * @param {Number} newRadius
  1980    *                 optional, the new radius of the arcball
  1981    */
  1982   resize: function TVA_resize(newWidth, newHeight, newRadius)
  1984     if (!newWidth || !newHeight) {
  1985       return;
  1988     // set the new width, height and radius dimensions
  1989     this.width = newWidth;
  1990     this.height = newHeight;
  1991     this.radius = newRadius ? newRadius : Math.min(newWidth, newHeight);
  1992     this._save();
  1993   },
  1995   /**
  1996    * Starts an animation resetting the arcball transformations to identity.
  1998    * @param {Array} aFinalTranslation
  1999    *                optional, final vector translation
  2000    * @param {Array} aFinalRotation
  2001    *                optional, final quaternion rotation
  2002    */
  2003   reset: function TVA_reset(aFinalTranslation, aFinalRotation)
  2005     if ("function" === typeof this._onResetStart) {
  2006       this._onResetStart();
  2007       this._onResetStart = null;
  2010     this.cancelMouseEvents();
  2011     this.cancelKeyEvents();
  2012     this._cancelReset();
  2014     this._save();
  2015     this._resetFinalTranslation = vec3.create(aFinalTranslation);
  2016     this._resetFinalRotation = quat4.create(aFinalRotation);
  2017     this._resetInProgress = true;
  2018   },
  2020   /**
  2021    * Cancels the current arcball reset animation if there is one.
  2022    */
  2023   _cancelReset: function TVA__cancelReset()
  2025     if (this._resetInProgress) {
  2026       this._resetInProgress = false;
  2027       this._save();
  2029       if ("function" === typeof this._onResetFinish) {
  2030         this._onResetFinish();
  2031         this._onResetFinish = null;
  2032         this._onResetStep = null;
  2035   },
  2037   /**
  2038    * Executes the next step in the arcball reset animation.
  2040    * @param {Number} aDelta
  2041    *                 the current animation frame delta
  2042    */
  2043   _nextResetStep: function TVA__nextResetStep(aDelta)
  2045     // a very large animation frame delta (in case of seriously low framerate)
  2046     // would cause all the interpolations to become highly unstable
  2047     aDelta = TiltMath.clamp(aDelta, 1, 100);
  2049     let fNearZero = EPSILON * EPSILON;
  2050     let fInterpLin = ARCBALL_RESET_LINEAR_FACTOR * aDelta;
  2051     let fInterpSph = ARCBALL_RESET_SPHERICAL_FACTOR;
  2052     let fTran = this._resetFinalTranslation;
  2053     let fRot = this._resetFinalRotation;
  2055     let t = vec3.create(fTran);
  2056     let r = quat4.multiply(quat4.inverse(quat4.create(this._currentRot)), fRot);
  2058     // reset the rotation quaternion and translation vector
  2059     vec3.lerp(this._currentTrans, t, fInterpLin);
  2060     quat4.slerp(this._currentRot, r, fInterpSph);
  2062     // also reset any additional transforms by the keyboard or mouse
  2063     vec3.scale(this._additionalTrans, fInterpLin);
  2064     vec3.scale(this._additionalRot, fInterpLin);
  2065     this._zoomAmount *= fInterpLin;
  2067     // clear the loop if the all values are very close to zero
  2068     if (vec3.length(vec3.subtract(this._lastRot, fRot, [])) < fNearZero &&
  2069         vec3.length(vec3.subtract(this._deltaRot, fRot, [])) < fNearZero &&
  2070         vec3.length(vec3.subtract(this._currentRot, fRot, [])) < fNearZero &&
  2071         vec3.length(vec3.subtract(this._lastTrans, fTran, [])) < fNearZero &&
  2072         vec3.length(vec3.subtract(this._deltaTrans, fTran, [])) < fNearZero &&
  2073         vec3.length(vec3.subtract(this._currentTrans, fTran, [])) < fNearZero &&
  2074         vec3.length(this._additionalRot) < fNearZero &&
  2075         vec3.length(this._additionalTrans) < fNearZero) {
  2077       this._cancelReset();
  2080     if ("function" === typeof this._onResetStep) {
  2081       this._onResetStep();
  2083   },
  2085   /**
  2086    * Loads the keys to control this arcball.
  2087    */
  2088   _loadKeys: function TVA__loadKeys()
  2090     this.rotateKeys = {
  2091       "up": Ci.nsIDOMKeyEvent["DOM_VK_W"],
  2092       "down": Ci.nsIDOMKeyEvent["DOM_VK_S"],
  2093       "left": Ci.nsIDOMKeyEvent["DOM_VK_A"],
  2094       "right": Ci.nsIDOMKeyEvent["DOM_VK_D"],
  2095     };
  2096     this.panKeys = {
  2097       "up": Ci.nsIDOMKeyEvent["DOM_VK_UP"],
  2098       "down": Ci.nsIDOMKeyEvent["DOM_VK_DOWN"],
  2099       "left": Ci.nsIDOMKeyEvent["DOM_VK_LEFT"],
  2100       "right": Ci.nsIDOMKeyEvent["DOM_VK_RIGHT"],
  2101     };
  2102     this.zoomKeys = {
  2103       "in": [
  2104         Ci.nsIDOMKeyEvent["DOM_VK_I"],
  2105         Ci.nsIDOMKeyEvent["DOM_VK_ADD"],
  2106         Ci.nsIDOMKeyEvent["DOM_VK_EQUALS"],
  2107       ],
  2108       "out": [
  2109         Ci.nsIDOMKeyEvent["DOM_VK_O"],
  2110         Ci.nsIDOMKeyEvent["DOM_VK_SUBTRACT"],
  2111       ],
  2112       "unzoom": Ci.nsIDOMKeyEvent["DOM_VK_0"]
  2113     };
  2114     this.resetKey = Ci.nsIDOMKeyEvent["DOM_VK_R"];
  2115   },
  2117   /**
  2118    * Saves the current arcball state, typically after resize or mouse events.
  2119    */
  2120   _save: function TVA__save()
  2122     if (this._mousePress) {
  2123       let x = this._mousePress[0];
  2124       let y = this._mousePress[1];
  2126       this._mouseMove[0] = x;
  2127       this._mouseMove[1] = y;
  2128       this._mouseRelease[0] = x;
  2129       this._mouseRelease[1] = y;
  2130       this._mouseLerp[0] = x;
  2131       this._mouseLerp[1] = y;
  2133   },
  2135   /**
  2136    * Function called when this object is destroyed.
  2137    */
  2138   _finalize: function TVA__finalize()
  2140     this._cancelReset();
  2142 };
  2144 /**
  2145  * Tilt configuration preferences.
  2146  */
  2147 TiltVisualizer.Prefs = {
  2149   /**
  2150    * Specifies if Tilt is enabled or not.
  2151    */
  2152   get enabled()
  2154     return this._enabled;
  2155   },
  2157   set enabled(value)
  2159     TiltUtils.Preferences.set("enabled", "boolean", value);
  2160     this._enabled = value;
  2161   },
  2163   get introTransition()
  2165     return this._introTransition;
  2166   },
  2168   set introTransition(value)
  2170     TiltUtils.Preferences.set("intro_transition", "boolean", value);
  2171     this._introTransition = value;
  2172   },
  2174   get outroTransition()
  2176     return this._outroTransition;
  2177   },
  2179   set outroTransition(value)
  2181     TiltUtils.Preferences.set("outro_transition", "boolean", value);
  2182     this._outroTransition = value;
  2183   },
  2185   /**
  2186    * Loads the preferences.
  2187    */
  2188   load: function TVC_load()
  2190     let prefs = TiltVisualizer.Prefs;
  2191     let get = TiltUtils.Preferences.get;
  2193     prefs._enabled = get("enabled", "boolean");
  2194     prefs._introTransition = get("intro_transition", "boolean");
  2195     prefs._outroTransition = get("outro_transition", "boolean");
  2197 };
  2199 /**
  2200  * A custom visualization shader.
  2202  * @param {Attribute} vertexPosition: the vertex position
  2203  * @param {Attribute} vertexTexCoord: texture coordinates used by the sampler
  2204  * @param {Attribute} vertexColor: specific [r, g, b] color for each vertex
  2205  * @param {Uniform} mvMatrix: the model view matrix
  2206  * @param {Uniform} projMatrix: the projection matrix
  2207  * @param {Uniform} sampler: the texture sampler to fetch the pixels from
  2208  */
  2209 TiltVisualizer.MeshShader = {
  2211   /**
  2212    * Vertex shader.
  2213    */
  2214   vs: [
  2215     "attribute vec3 vertexPosition;",
  2216     "attribute vec2 vertexTexCoord;",
  2217     "attribute vec3 vertexColor;",
  2219     "uniform mat4 mvMatrix;",
  2220     "uniform mat4 projMatrix;",
  2222     "varying vec2 texCoord;",
  2223     "varying vec3 color;",
  2225     "void main() {",
  2226     "  gl_Position = projMatrix * mvMatrix * vec4(vertexPosition, 1.0);",
  2227     "  texCoord = vertexTexCoord;",
  2228     "  color = vertexColor;",
  2229     "}"
  2230   ].join("\n"),
  2232   /**
  2233    * Fragment shader.
  2234    */
  2235   fs: [
  2236     "#ifdef GL_ES",
  2237     "precision lowp float;",
  2238     "#endif",
  2240     "uniform sampler2D sampler;",
  2242     "varying vec2 texCoord;",
  2243     "varying vec3 color;",
  2245     "void main() {",
  2246     "  if (texCoord.x < 0.0) {",
  2247     "    gl_FragColor = vec4(color, 1.0);",
  2248     "  } else {",
  2249     "    gl_FragColor = vec4(texture2D(sampler, texCoord).rgb, 1.0);",
  2250     "  }",
  2251     "}"
  2252   ].join("\n")
  2253 };

mercurial