mirror of
https://github.com/OpenFOAM/ThirdParty-6.git
synced 2025-12-08 06:57:43 +00:00
893 lines
32 KiB
JavaScript
893 lines
32 KiB
JavaScript
/**
|
|
* 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 = "<div class='renderers'></div>",
|
|
DEFAULT_RENDERERS_CONTAINER_CSS = {
|
|
"position": "absolute",
|
|
"top": "0px",
|
|
"left": "0px",
|
|
"right": "0px",
|
|
"bottom": "0px",
|
|
"z-index" : "0",
|
|
"overflow": "hidden"
|
|
},
|
|
|
|
DEFAULT_MOUSE_LISTENER_HTML = "<div class='mouse-listener'></div>",
|
|
DEFAULT_MOUSE_LISTENER_CSS = {
|
|
"position": "absolute",
|
|
"top": "0px",
|
|
"left": "0px",
|
|
"right": "0px",
|
|
"bottom": "0px",
|
|
"z-index" : "3"
|
|
},
|
|
|
|
DEFAULT_STATISTIC_HTML = "<div class='statistics'></div>",
|
|
DEFAULT_STATISTIC_CSS = {
|
|
"position": "absolute",
|
|
"top": "0px",
|
|
"left": "0px",
|
|
"right": "0px",
|
|
"bottom": "0px",
|
|
"z-index" : "2",
|
|
"display" : "none"
|
|
},
|
|
|
|
DEFAULT_OVERLAY_HTML = "<canvas class='overlay'></canvas>",
|
|
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("<table class='viewport-stat'>");
|
|
buffer.append("<tr class='stat-header'><td class='label'>Timed Quantity</td><td class='value'>Current</td><td class='min'>Min</td><td class='max'>Max</td><td class='avg'>Average</td></tr>");
|
|
for(key in statistics) {
|
|
if(formatters.hasOwnProperty(key) && statistics[key].valid) {
|
|
formater = formatters[key];
|
|
stat = statistics[key];
|
|
hasContent = true;
|
|
|
|
// The functiion may swap the order
|
|
min = formater.convert(stat.min);
|
|
max = formater.convert(stat.max);
|
|
|
|
buffer.append("<tr><td class='label'>");
|
|
buffer.append(formater.label);
|
|
buffer.append("</td><td class='value'>");
|
|
buffer.append(formater.convert(stat.value));
|
|
buffer.append("</td><td class='min'>");
|
|
buffer.append((min < max) ? min : max);
|
|
buffer.append("</td><td class='max'>");
|
|
buffer.append((min > max) ? min : max);
|
|
buffer.append("</td><td class='avg'>");
|
|
buffer.append(formater.convert(stat.getAverageValue()));
|
|
buffer.append("</td></tr>");
|
|
}
|
|
}
|
|
buffer.append("</table>");
|
|
|
|
return hasContent ? buffer.toString() : "";
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
return {
|
|
eventHandler: handleEvent,
|
|
toHTML: toHTML,
|
|
reset: function() {
|
|
statistics = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
function createBuffer() {
|
|
var idx = -1, buffer = [];
|
|
return {
|
|
clear: function(){
|
|
idx = -1;
|
|
buffer = [];
|
|
},
|
|
append: function(str) {
|
|
buffer[++idx] = str;
|
|
return this;
|
|
},
|
|
toString: function() {
|
|
return buffer.join('');
|
|
}
|
|
};
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
function createTimeValueRecord() {
|
|
var lastTime, sum, count;
|
|
|
|
// Default values
|
|
lastTime = 0;
|
|
sum = 0;
|
|
count = 0;
|
|
|
|
return {
|
|
value: 0.0,
|
|
valid: false,
|
|
min: +1000000000.0,
|
|
max: -1000000000.0,
|
|
|
|
record: function(v) {
|
|
if(v === 0) {
|
|
this.start();
|
|
} else if (v === 1) {
|
|
this.stop();
|
|
}
|
|
},
|
|
|
|
start: function() {
|
|
lastTime = new Date().getTime();
|
|
},
|
|
|
|
stop: function() {
|
|
if(lastTime != 0) {
|
|
this.valid = true;
|
|
var time = new Date().getTime();
|
|
this.value = time - lastTime;
|
|
this.min = (this.min < this.value) ? this.min : this.value;
|
|
this.max = (this.max > this.value) ? this.max : this.value;
|
|
//
|
|
sum += this.value;
|
|
count++;
|
|
}
|
|
},
|
|
|
|
reset: function() {
|
|
count = 0;
|
|
sum = 0;
|
|
lastTime = 0;
|
|
this.value = 0;
|
|
this.min = +1000000000.0;
|
|
this.max = -1000000000.0;
|
|
this.valid = false;
|
|
},
|
|
|
|
getAverageValue: function() {
|
|
if(count == 0) {
|
|
return 0;
|
|
}
|
|
return Math.floor(sum / count);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
function createValueRecord() {
|
|
var sum = 0, count = 0;
|
|
|
|
return {
|
|
value: 0.0,
|
|
valid: false,
|
|
min: +1000000000.0,
|
|
max: -1000000000.0,
|
|
|
|
record: function(v) {
|
|
this.valid = true;
|
|
this.value = v;
|
|
this.min = (this.min < this.value) ? this.min : this.value;
|
|
this.max = (this.max > this.value) ? this.max : this.value;
|
|
//
|
|
sum += this.value;
|
|
count++;
|
|
},
|
|
|
|
reset: function() {
|
|
count = 0;
|
|
sum = 0;
|
|
this.value = 0;
|
|
this.min = +1000000000.0;
|
|
this.max = -1000000000.0;
|
|
this.valid = false;
|
|
},
|
|
|
|
getAverageValue: function() {
|
|
if(count === 0) {
|
|
return 0;
|
|
}
|
|
return Math.floor(sum / count);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Viewport container definition
|
|
// ----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Create a new viewport for a vtkWeb View.
|
|
* The options are explained below.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {Object} options
|
|
* Configure the viewport to create the way we want.
|
|
*
|
|
* options = {
|
|
* session: sessionObject, // Object used to communicate with the remote server.
|
|
* view: -1, // -1 for active view or use the proper viewId
|
|
* enableInteractions: true, // True if mouse interaction should be forwarded to the server
|
|
* renderer: 'image' // Type of renderer to be used. Can only be 'image' 'webgl' or 'vgl'.
|
|
* // --- image renderer options
|
|
* interactiveQuality: 30, // StillRender quality when interacting
|
|
* stillQuality: 100, // StillRender quality when not interacting
|
|
* // --- webgl/vgl renderer options
|
|
* keepServerInSynch: false. // Push camera information to server if true
|
|
* }
|
|
*
|
|
* @return {vtkWeb.Viewport}
|
|
*/
|
|
function createViewport(options) {
|
|
// Make sure we have a valid autobahn session
|
|
if (options.session === null) {
|
|
throw "'session' must be provided within the option.";
|
|
}
|
|
|
|
function onReadyInternal() {
|
|
if(userReadyCallback.fn) {
|
|
// Providing just a short delay before triggering the "image-not-state"
|
|
// event provides a break in the action so that the browser can draw the
|
|
// image we just copied to the src attribute.
|
|
setTimeout(userReadyCallback.fn, 0);
|
|
if(userReadyCallback.once) {
|
|
userReadyCallback.fn = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create viewport
|
|
var config = $.extend({}, DEFAULT_VIEWPORT_OPTIONS, options),
|
|
session = options.session,
|
|
rendererContainer = $(DEFAULT_RENDERERS_CONTAINER_HTML).css(DEFAULT_RENDERERS_CONTAINER_CSS),
|
|
mouseListener = $(DEFAULT_MOUSE_LISTENER_HTML).css(DEFAULT_MOUSE_LISTENER_CSS),
|
|
statContainer = $(DEFAULT_STATISTIC_HTML).css(DEFAULT_STATISTIC_CSS),
|
|
overlayContainer = $(DEFAULT_OVERLAY_HTML).css(DEFAULT_OVERLAY_CSS),
|
|
onDoneQueue = [],
|
|
statisticManager = createStatisticManager(),
|
|
inSelectionMode = false,
|
|
userReadyCallback = { fn: null, once: false},
|
|
viewport = {
|
|
/**
|
|
* Update the active renderer to be something else.
|
|
* This allow the user to switch from Image Delivery to Geometry delivery
|
|
* or even any other available renderer type available.
|
|
*
|
|
* The available renderers are indexed inside the following object vtkWeb.ViewportFactory.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {String} rendererName
|
|
* Key used to ID the renderer type.
|
|
*/
|
|
setActiveRenderer: function(rendererName) {
|
|
$('.' + rendererName, rendererContainer).addClass('active').show().siblings().removeClass('active').hide();
|
|
rendererContainer.trigger('active');
|
|
statContainer[0].innerHTML = '';
|
|
statisticManager.reset();
|
|
},
|
|
|
|
/**
|
|
* Method that should be called each time something in the scene as changed
|
|
* and we want to update the viewport to reflect the latest state of the scene.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {Function} ondone Function to call after rendering is complete.
|
|
*/
|
|
invalidateScene: function(onDone) {
|
|
onDoneQueue.push(onDone);
|
|
rendererContainer.trigger('invalidateScene');
|
|
},
|
|
|
|
/**
|
|
* Method that should be called when nothing has changed in the scene
|
|
* but for some reason the viewport has been dirty.
|
|
* (i.e. Toggeling the statistic information within the viewport)
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {Function} ondone Function to call after rendering is complete.
|
|
*/
|
|
render: function(onDone, args) {
|
|
onDoneQueue.push(onDone);
|
|
rendererContainer.trigger({type: 'render', options: args});
|
|
},
|
|
|
|
/**
|
|
* Reset the camera of the scene to make it fit in the screen as well
|
|
* as invalidating the scene automatically.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {Function} ondone Function to call after rendering is complete.
|
|
*/
|
|
resetCamera: function(onDone) {
|
|
onDoneQueue.push(onDone);
|
|
return session.call("viewport.camera.reset", [Number(config.view)]).then(function () {
|
|
rendererContainer.trigger('invalidateScene');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Update Orientation Axes Visibility for the given view
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {Boolean} show
|
|
* Show: true / Hide: false
|
|
* @param {Function} ondone Function to call after rendering is complete.
|
|
*/
|
|
updateOrientationAxesVisibility: function (show, onDone) {
|
|
return session.call("viewport.axes.orientation.visibility.update", [Number(config.view), show]).then(function () {
|
|
onDoneQueue.push(onDone);
|
|
rendererContainer.trigger('invalidateScene');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Update the Center Axes Visibility for the given view
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {Boolean} show
|
|
* Show: true / Hide: false
|
|
* @param {Function} ondone Function to call after rendering is complete.
|
|
*/
|
|
updateCenterAxesVisibility: function (show, onDone) {
|
|
return session.call("viewport.axes.center.visibility.update", [Number(config.view), show]).then(function () {
|
|
onDoneQueue.push(onDone);
|
|
rendererContainer.trigger('invalidateScene');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Reset view id.
|
|
* This allow to invalidate the viewport and use the new active view
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
*/
|
|
resetViewId: function () {
|
|
rendererContainer.trigger('resetViewId');
|
|
},
|
|
|
|
/**
|
|
* Ask the concrete renderer to generate an image and trigger its own
|
|
* event when the image is ready.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
*/
|
|
captureScreenImage: function () {
|
|
rendererContainer.trigger('captureRenderedImage');
|
|
},
|
|
|
|
/**
|
|
*
|
|
*/
|
|
downloadTimestepData: function(shaList) {
|
|
rendererContainer.trigger('downloadAllTimesteps');
|
|
},
|
|
|
|
/*
|
|
*
|
|
*/
|
|
clearGeometryCache: function() {
|
|
rendererContainer.trigger('clearCache');
|
|
},
|
|
|
|
/*
|
|
* Provide a function that will be called when
|
|
*/
|
|
onReady: function(readyCallback, once) {
|
|
userReadyCallback.fn = readyCallback;
|
|
userReadyCallback.once = once;
|
|
},
|
|
|
|
/**
|
|
* Attach viewport to a DOM element
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {String} selector
|
|
* The will be used internally to get the jQuery associated element
|
|
*
|
|
* <div class="renderer"></div>
|
|
* viewport.bind(".renderer");
|
|
*
|
|
* <div id="renderer"></div>
|
|
* viewport.bind("#renderer");
|
|
*
|
|
* <html>
|
|
* <body>
|
|
* <!-- renderer -->
|
|
* <div></div>
|
|
* </body>
|
|
* </html>
|
|
* viewport.bind("body > div");
|
|
*/
|
|
bind: function (selector) {
|
|
var container = $(selector);
|
|
if (container.attr("__vtkWeb_viewport__") !== "true") {
|
|
container.attr("__vtkWeb_viewport__", "true");
|
|
container.append(rendererContainer).append(mouseListener).append(statContainer).append(overlayContainer);
|
|
rendererContainer.trigger('invalidateScene');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove viewport from DOM element
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
*/
|
|
unbind: function () {
|
|
var parentElement = rendererContainer.parent();
|
|
if (parentElement) {
|
|
parentElement.attr("__vtkWeb_viewport__", "false");
|
|
rendererContainer.remove();
|
|
mouseListener.remove();
|
|
statContainer.remove();
|
|
overlayContainer.remove();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update statistic visibility
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @param {Boolean} visible
|
|
*/
|
|
showStatistics: function(isVisible) {
|
|
if(isVisible) {
|
|
statContainer.show();
|
|
} else {
|
|
statContainer.hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear current statistic values
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
*/
|
|
resetStatistics: function() {
|
|
statisticManager.reset();
|
|
statContainer.empty();
|
|
},
|
|
|
|
/**
|
|
* Event triggered before a mouse down.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @event startInteraction
|
|
*/
|
|
/**
|
|
* Event triggered after a mouse up.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @event endInteraction
|
|
* @param area
|
|
* Provide the area in pixel between the startInteraction and endInteraction.
|
|
* This can be used for selection or zoom to box... [minX, minY, maxX, maxY]
|
|
*/
|
|
|
|
/**
|
|
* Toggle selection mode.
|
|
*
|
|
* When selection mode is active, the next interaction will
|
|
* create a 2D rectangle that will be used to define an area to select.
|
|
* This won't perform any selection but will provide another interaction
|
|
* mode on the client side which will then trigger an event with
|
|
* the given area. It is the responsibility of the user to properly
|
|
* handle that event and eventually trigger the appropriate server
|
|
* code to perform a selection or a zoom-to-box type of action.
|
|
*
|
|
* @member vtkWeb.Viewport
|
|
* @return {Boolean} inSelectionMode state
|
|
*/
|
|
toggleSelectionMode: function() {
|
|
inSelectionMode = !inSelectionMode;
|
|
return inSelectionMode;
|
|
},
|
|
|
|
/**
|
|
* Return whether or not viewport is in selection mode.
|
|
*
|
|
* @return {Boolean} true if viewport is in selection mode, false otherwise.
|
|
*/
|
|
getSelectionMode: function() {
|
|
return inSelectionMode;
|
|
}
|
|
};
|
|
|
|
// Attach config object to renderer parent
|
|
rendererContainer.data('config', config);
|
|
|
|
// Attach onDone listener
|
|
rendererContainer.bind('done', function(){
|
|
while(onDoneQueue.length > 0) {
|
|
var callback = onDoneQueue.pop();
|
|
try {
|
|
if(callback) {
|
|
callback();
|
|
}
|
|
} catch(error) {
|
|
console.log("On Done callback error:");
|
|
console.log(error);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Attach the on ready internal
|
|
rendererContainer.bind('renderer-ready', onReadyInternal);
|
|
|
|
// Create any renderer type that is available
|
|
for(var key in vtkWeb.ViewportFactory) {
|
|
try {
|
|
vtkWeb.ViewportFactory[key].builder(rendererContainer);
|
|
} catch(error) {
|
|
console.log("Error while trying to load renderer: " + key);
|
|
console.log(error);
|
|
}
|
|
}
|
|
|
|
// Set default renderer
|
|
viewport.setActiveRenderer(config.renderer);
|
|
|
|
// Attach mouse listener if requested
|
|
if (config.enableInteractions) {
|
|
attachMouseListener(mouseListener, rendererContainer, overlayContainer, viewport);
|
|
try {
|
|
attachTouchListener(mouseListener, rendererContainer, viewport);
|
|
} catch(error) {
|
|
console.log('Hammer is not properly initialized');
|
|
console.log(error);
|
|
}
|
|
}
|
|
|
|
// Attach stat listener
|
|
rendererContainer.bind('stats', function(event){
|
|
statisticManager.eventHandler(event);
|
|
statContainer[0].innerHTML = statisticManager.toHTML();
|
|
});
|
|
|
|
return viewport;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Init vtkWeb module if needed
|
|
// ----------------------------------------------------------------------
|
|
if (GLOBAL.hasOwnProperty("vtkWeb")) {
|
|
module = GLOBAL.vtkWeb || {};
|
|
} else {
|
|
GLOBAL.vtkWeb = module;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Export internal methods to the vtkWeb module
|
|
// ----------------------------------------------------------------------
|
|
module.createViewport = function (option) {
|
|
return createViewport(option);
|
|
};
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Local module registration
|
|
// ----------------------------------------------------------------------
|
|
try {
|
|
// Tests for presence of jQuery, then registers this module
|
|
if ($ !== undefined) {
|
|
module.registerModule('vtkweb-viewport');
|
|
} else {
|
|
console.error('Module failed to register, jQuery is missing');
|
|
}
|
|
} catch(err) {
|
|
console.error('Caught exception while registering module: ' + err.message);
|
|
}
|
|
|
|
}(window, jQuery));
|