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.

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

mercurial