/** * vtkWeb JavaScript Library. * * This module allow the Web client to create viewport to vtkWeb views. * Those viewport are interactive windows that are used to render 2D/3D content * and response to user mouse interactions. * * This module registers itself as: 'vtkweb-viewport' * * @class vtkWeb.Viewport */ (function (GLOBAL, $) { // ---------------------------------------------------------------------- // Viewport constants // ---------------------------------------------------------------------- var DEFAULT_RENDERERS_CONTAINER_HTML = "
", DEFAULT_RENDERERS_CONTAINER_CSS = { "position": "absolute", "top": "0px", "left": "0px", "right": "0px", "bottom": "0px", "z-index" : "0", "overflow": "hidden" }, DEFAULT_MOUSE_LISTENER_HTML = "", DEFAULT_MOUSE_LISTENER_CSS = { "position": "absolute", "top": "0px", "left": "0px", "right": "0px", "bottom": "0px", "z-index" : "3" }, DEFAULT_STATISTIC_HTML = "", DEFAULT_STATISTIC_CSS = { "position": "absolute", "top": "0px", "left": "0px", "right": "0px", "bottom": "0px", "z-index" : "2", "display" : "none" }, DEFAULT_OVERLAY_HTML = "", DEFAULT_OVERLAY_CSS = { "position": "absolute", "top": "0px", "left": "0px", "right": "0px", "bottom": "0px", "z-index" : "1" }, module = {}, DEFAULT_VIEWPORT_OPTIONS = { session: null, view: -1, enableInteractions: true, renderer: 'image' }; // ---------------------------------------------------------------------- // Mouse interaction helper methods for viewport // ---------------------------------------------------------------------- function preventDefault(event) { event.preventDefault(); } // ---------------------------------------------------------------------- function attachMouseListener(mouseListenerContainer, renderersContainer, overlayContainer, viewport) { var current_button = null, area = [0,0,0,0], vp = viewport, overlayCtx2D = overlayContainer[0].getContext('2d'); // Draw rectangle in overlay function clearOverlay() { if(overlayCtx2D !== null) { overlayCtx2D.canvas.width = mouseListenerContainer.width(); overlayCtx2D.canvas.height = mouseListenerContainer.height(); overlayCtx2D.clearRect(0,0,overlayCtx2D.canvas.width,overlayCtx2D.canvas.height); } } function redrawSelection() { clearOverlay(); if(overlayCtx2D !== null) { overlayCtx2D.strokeStyle="#FFFFFF"; var x1 = Math.min(area[0],area[2]); var y1 = Math.min(area[1],area[3]); var x2 = Math.max(area[0],area[2]); var y2 = Math.max(area[1],area[3]); var width = Math.abs(x2 - x1); var height = Math.abs(y2 - y1); overlayCtx2D.rect(x1, overlayContainer.height()-y2, width, height); overlayCtx2D.stroke(); } } function extractCoordinates(event, start, reorder) { var elem_position = $(event.delegateTarget).offset(), height = $(event.delegateTarget).height(), x = (event.pageX - elem_position.left), y = (event.pageY - elem_position.top), offset = start ? 0 : 2; area[0 + offset] = x; area[1 + offset] = height - y; if(reorder) { // Re-order area var newArea = [ Math.min(area[0], area[2]), Math.min(area[1], area[3]), Math.max(area[0], area[2]), Math.max(area[1], area[3]) ]; area[0] = newArea[0]; area[1] = newArea[1]; area[2] = newArea[2]; area[3] = newArea[3]; } } // Internal method used to pre-process the interaction to standardise it // for a vtkWeb usage. function mouseInteraction(event) { if(event.hasOwnProperty("type")) { if(event.type === 'mouseup') { current_button = null; if (vp.getSelectionMode() === true) { clearOverlay(); } renderersContainer.trigger($.extend(event, { type: 'mouse', action: 'up', current_button: current_button })); extractCoordinates(event, false, true); renderersContainer.trigger({ type: 'endInteraction', area: area }); } else if(event.type === 'mousedown') { current_button = event.which; // Override button if modifier is used // Middle: Alt - Right: Shift if(event.shiftKey) { current_button = 3; event.shiftKey = false; } else if(event.altKey) { current_button = 2; event.altKey = false; } extractCoordinates(event, true, false); renderersContainer.trigger('startInteraction'); renderersContainer.trigger($.extend(event, { type: 'mouse', action: 'down', current_button: current_button })); } else if(event.type === 'mousemove' && current_button != null) { if (vp.getSelectionMode() === true) { extractCoordinates(event, false, false); redrawSelection(); } renderersContainer.trigger($.extend(event, { type: 'mouse', action: 'move', current_button: current_button })); } } } // Bind listener to UI container mouseListenerContainer.bind("contextmenu mouseover click", preventDefault); mouseListenerContainer.bind('mousedown mouseup mousemove', mouseInteraction); mouseListenerContainer.dblclick(function(event){ renderersContainer.trigger($.extend(event, { type: 'mouse', action: 'dblclick', current_button: event.which })); }); mouseListenerContainer.bind("DOMMouseScroll mousewheel",function(event){ var scrollValue = (event.originalEvent.wheelDeltaY || -event.originalEvent.detail); renderersContainer.trigger($.extend(event, { type: 'mouse', action: 'scroll', current_button: current_button, scroll: scrollValue })); }); } // ---------------------------------------------------------------------- function attachTouchListener(mouseListenerContainer, renderersContainer, viewport) { var current_button = null, posX, posY, defaultDragButton = 1, isZooming = false, isDragging = false, mouseAction = 'up', target; function mobileTouchInteraction(evt) { evt.gesture.preventDefault(); switch(evt.type) { case 'drag': if(isZooming) { return; } current_button = defaultDragButton; if(mouseAction === 'up') { mouseAction = "down"; target = evt.gesture.target; isDragging = true; } else { mouseAction = "move"; } posX = evt.gesture.touches[0].pageX; posY = evt.gesture.touches[0].pageY; break; case 'hold': if(defaultDragButton === 1) { defaultDragButton = 2; mouseListenerContainer.html("Pan mode").css('color','#FFFFFF'); } else { defaultDragButton = 1; mouseListenerContainer.html("Rotation mode").css('color','#FFFFFF'); } break; case 'release': mouseListenerContainer.html(''); current_button = 0; mouseAction = "up"; isZooming = false; isDragging = false; break; case 'doubletap': viewport.resetCamera(); return; case 'pinch': if(isDragging) { return; } current_button = 3; if(mouseAction === 'up') { mouseAction = 'down'; posX = 0; posY = mouseListenerContainer.height(); target = evt.gesture.target; isZooming = true; } else { mouseAction = 'move'; posY = mouseListenerContainer.height() * (1+(evt.gesture.scale-1)/2); } break; } //mouseListenerContainer.html(mouseAction + ' (' + posX + ', ' + posY + ') b:' + current_button + ' z: ' + isZooming ).css('color','#FFFFFF'); // Trigger event renderersContainer.trigger({ type: 'mouse', action: mouseAction, current_button: current_button, charCode: '', altKey: false, ctrlKey: false, shiftKey: false, metaKey: false, delegateTarget: target, pageX: posX, pageY: posY }); } // Bind listener to UI container mouseListenerContainer.hammer({ prevent_default : true, prevent_mouseevents : true, transform : true, transform_always_block : true, transform_min_scale : 0.03, transform_min_rotation : 2, drag : true, drag_max_touches : 1, drag_min_distance : 10, swipe : false, hold : true // To switch from rotation to pan }).on("doubletap pinch drag release hold", mobileTouchInteraction); } // ---------------------------------------------------------------------- // Viewport statistic manager // ---------------------------------------------------------------------- function createStatisticManager() { var statistics = {}, formatters = {}; // Fill stat formatters for(var factoryKey in vtkWeb.ViewportFactory) { var factory = vtkWeb.ViewportFactory[factoryKey]; if(factory.hasOwnProperty('stats')) { for(var key in factory.stats) { formatters[key] = factory.stats[key]; } } } function handleEvent(event) { var id = event.stat_id, value = event.stat_value, statObject = null; if(!statistics.hasOwnProperty(id) && formatters.hasOwnProperty(id)) { if(formatters[id].type === 'time') { statObject = statistics[id] = createTimeValueRecord(); } else if (formatters[id].type === 'value') { statObject = statistics[id] = createValueRecord(); } } else { statObject = statistics[id]; } if(statObject != null) { statObject.record(value); } } // ------------------------------------------------------------------ function toHTML() { var buffer = createBuffer(), hasContent = false, key, formater, stat, min, max; // Extract stat data buffer.append("| Timed Quantity | Current | Min | Max | Average |
| "); buffer.append(formater.label); buffer.append(" | "); buffer.append(formater.convert(stat.value)); buffer.append(" | "); buffer.append((min < max) ? min : max); buffer.append(" | "); buffer.append((min > max) ? min : max); buffer.append(" | "); buffer.append(formater.convert(stat.getAverageValue())); buffer.append(" |