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