Wed, 31 Dec 2014 06:09:35 +0100
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 | }; |