/**
* vtkWeb JavaScript Library.
*
* This module extend the vtkWeb viewport to add support for WebGL rendering.
*
* @class vtkWeb.viewports.webgl
*
* Viewport Factory description:
* - Key: webgl
* - Stats:
* - webgl-fps
* - webgl-nb-objects
* - webgl-fetch-scene
* - webgl-fetch-object
*/
(function (GLOBAL, $) {
var module = {},
RENDERER_CSS = {
"position": "absolute",
"top" : "0px",
"left" : "0px",
"right" : "0px",
"bottom" : "0px",
"z-index" : "0"
},
RENDERER_CSS_2D = {
"z-index" : "1"
},
RENDERER_CSS_3D = {
"z-index" : "0"
},
DEFAULT_OPTIONS = {
keepServerInSynch: false
},
FACTORY_KEY = 'webgl',
FACTORY = {
'builder': createGeometryDeliveryRenderer,
'options': DEFAULT_OPTIONS,
'stats': {
'webgl-fps': {
label: 'Framerate',
type: 'time',
convert: function(value) {
if(value === 0) {
return 0;
}
return (1000 / value).toFixed(2);
}
},
'webgl-nb-objects': {
label: 'Number of 3D objects',
type: 'value',
convert: NoOp
},
'webgl-fetch-scene': {
label: 'Fetch scene (ms)',
type: 'time',
convert: NoOp
},
'webgl-fetch-object': {
label: 'Fetch object (ms)',
type: 'time',
convert: NoOp
}
}
},
DEFAULT_SHADERS = {},
mvMatrixStack = [],
PROGRESS_BAR_TEMPLATE =
'
' +
'
Download Progress' +
'
' +
'
';
// ----------------------------------------------------------------------
function NoOp(a) {
return a;
}
// ----------------------------------------------------------------------
// Initialize the Shaders
// ----------------------------------------------------------------------
DEFAULT_SHADERS["shader-fs"] = {
type: "x-shader/x-fragment",
code: "\
#ifdef GL_ES\n\
precision highp float;\n\
#endif\n\
uniform bool uIsLine;\
varying vec4 vColor;\
varying vec4 vTransformedNormal;\
varying vec4 vPosition;\
void main(void) {\
float directionalLightWeighting1 = max(dot(normalize(vTransformedNormal.xyz), vec3(0.0, 0.0, 1.0)), 0.0); \
float directionalLightWeighting2 = max(dot(normalize(vTransformedNormal.xyz), vec3(0.0, 0.0, -1.0)), 0.0);\
vec3 lightWeighting = max(vec3(1.0, 1.0, 1.0) * directionalLightWeighting1, vec3(1.0, 1.0, 1.0) * directionalLightWeighting2);\
if (uIsLine == false){\
gl_FragColor = vec4(vColor.rgb * lightWeighting, vColor.a);\
} else {\
gl_FragColor = vColor*vec4(1.0, 1.0, 1.0, 1.0);\
}\
}"
};
// ----------------------------------------------------------------------
DEFAULT_SHADERS["shader-vs"] = {
type: "x-shader/x-vertex",
code: "\
attribute vec3 aVertexPosition;\
attribute vec4 aVertexColor;\
attribute vec3 aVertexNormal;\
uniform mat4 uMVMatrix;\
uniform mat4 uPMatrix;\
uniform mat4 uNMatrix;\
varying vec4 vColor;\
varying vec4 vPosition;\
varying vec4 vTransformedNormal;\
void main(void) {\
vPosition = uMVMatrix * vec4(aVertexPosition, 1.0);\
gl_Position = uPMatrix * vPosition;\
vTransformedNormal = uNMatrix * vec4(aVertexNormal, 1.0);\
vColor = aVertexColor;\
}"
};
// ----------------------------------------------------------------------
DEFAULT_SHADERS["shader-fs-Point"] = {
type: "x-shader/x-fragment",
code: "\
#ifdef GL_ES\n\
precision highp float;\n\
#endif\n\
varying vec4 vColor;\
void main(void) {\
gl_FragColor = vColor;\
}"
};
// ----------------------------------------------------------------------
DEFAULT_SHADERS["shader-vs-Point"] = {
type: "x-shader/x-vertex",
code: "\
attribute vec3 aVertexPosition;\
attribute vec4 aVertexColor;\
uniform mat4 uMVMatrix;\
uniform mat4 uPMatrix;\
uniform mat4 uNMatrix;\
uniform float uPointSize;\
varying vec4 vColor;\
void main(void) {\
vec4 pos = uMVMatrix * vec4(aVertexPosition, 1.0);\
gl_Position = uPMatrix * pos;\
vColor = aVertexColor*vec4(1.0, 1.0, 1.0, 1.0);\
gl_PointSize = uPointSize;\
}"
};
// ----------------------------------------------------------------------
function getShader(gl, id) {
try {
var jsonShader = DEFAULT_SHADERS[id], shader = null;
// Allocate shader
if(jsonShader.type === "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if(jsonShader.type === "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
// Set code and compile
gl.shaderSource(shader, jsonShader.code);
gl.compileShader(shader);
// Check compilation
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
} catch(error) {
console.log(error);
}
}
// ----------------------------------------------------------------------
function initializeShader(gl, shaderProgram, pointShaderProgram) {
try {
var vertexShader = getShader(gl, 'shader-vs'),
fragmentShader = getShader(gl, 'shader-fs'),
pointFragShader = getShader(gl, 'shader-fs-Point'),
pointVertShader = getShader(gl, 'shader-vs-Point');
// Initialize program
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.attachShader(pointShaderProgram, pointVertShader);
gl.attachShader(pointShaderProgram, pointFragShader);
gl.linkProgram(pointShaderProgram);
if (!gl.getProgramParameter(pointShaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise the point shaders");
}
gl.useProgram(pointShaderProgram);
pointShaderProgram.vertexPositionAttribute = gl.getAttribLocation(pointShaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(pointShaderProgram.vertexPositionAttribute);
pointShaderProgram.vertexColorAttribute = gl.getAttribLocation(pointShaderProgram, "aVertexColor");
gl.enableVertexAttribArray(pointShaderProgram.vertexColorAttribute);
pointShaderProgram.pMatrixUniform = gl.getUniformLocation(pointShaderProgram, "uPMatrix");
pointShaderProgram.mvMatrixUniform = gl.getUniformLocation(pointShaderProgram, "uMVMatrix");
pointShaderProgram.nMatrixUniform = gl.getUniformLocation(pointShaderProgram, "uNMatrix");
pointShaderProgram.uPointSize = gl.getUniformLocation(pointShaderProgram, "uPointSize");
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
shaderProgram.vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal");
gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
shaderProgram.nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix");
shaderProgram.uIsLine = gl.getUniformLocation(shaderProgram, "uIsLine");
} catch(error) {
console.log(error);
}
}
// ----------------------------------------------------------------------
// GL rendering metods
// ----------------------------------------------------------------------
function setMatrixUniforms(gl, shaderProgram, projMatrix, mvMatrix) {
var mvMatrixInv = mat4.create(), normal = mat4.create();
mat4.invert(mvMatrixInv, mvMatrix);
mat4.transpose(normal, mvMatrixInv);
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, projMatrix);
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
if(shaderProgram.nMatrixUniform != null) gl.uniformMatrix4fv(shaderProgram.nMatrixUniform, false, normal);
}
// ----------------------------------------------------------------------
function renderMesh(renderingContext, camera) {
try {
var obj = this,
mvMatrix = mat4.clone(camera.getCameraMatrices()[1]),
projMatrix = mat4.clone(camera.getCameraMatrices()[0]),
objMatrix = mat4.transpose(mat4.create(), obj.matrix),
gl = renderingContext.gl,
shaderProgram = renderingContext.shaderProgram;
gl.useProgram(shaderProgram);
gl.uniform1i(shaderProgram.uIsLine, false);
mvMatrix = mat4.multiply(mvMatrix, mvMatrix, objMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.vbuff);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, obj.vbuff.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.nbuff);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, obj.nbuff.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.cbuff);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, obj.cbuff.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibuff);
setMatrixUniforms(gl, shaderProgram, projMatrix, mvMatrix);
gl.drawElements(gl.TRIANGLES, obj.numberOfIndex, gl.UNSIGNED_SHORT, 0);
} catch(error) {
console.log(error);
}
}
// ----------------------------------------------------------------------
function renderLine(renderingContext, camera) {
try {
var obj = this,
mvMatrix = mat4.clone(camera.getCameraMatrices()[1]),
projMatrix = mat4.clone(camera.getCameraMatrices()[0]),
objMatrix = mat4.transpose(mat4.create(), obj.matrix),
gl = renderingContext.gl,
shaderProgram = renderingContext.shaderProgram;
gl.useProgram(shaderProgram);
gl.enable(gl.POLYGON_OFFSET_FILL); //Avoid zfighting
gl.polygonOffset(-1.0, -1.0);
gl.uniform1i(shaderProgram.uIsLine, true);
mvMatrix = mat4.multiply(mvMatrix, mvMatrix, objMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.lbuff);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, obj.lbuff.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.nbuff);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, obj.nbuff.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.cbuff);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, obj.cbuff.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibuff);
setMatrixUniforms(gl, shaderProgram, projMatrix, mvMatrix);
gl.drawElements(gl.LINES, obj.numberOfIndex, gl.UNSIGNED_SHORT, 0);
gl.disable(gl.POLYGON_OFFSET_FILL);
} catch(error) {
console.log(error);
}
}
// ----------------------------------------------------------------------
function renderPoints(renderingContext, camera) {
try {
var obj = this,
mvMatrix = mat4.clone(camera.getCameraMatrices()[1]),
projMatrix = mat4.clone(camera.getCameraMatrices()[0]),
objMatrix = mat4.transpose(mat4.create(), obj.matrix),
gl = renderingContext.gl,
pointShaderProgram = renderingContext.pointShaderProgram;
gl.useProgram(pointShaderProgram);
gl.enable(gl.POLYGON_OFFSET_FILL); //Avoid zfighting
gl.polygonOffset(-1.0, -1.0);
gl.uniform1f(pointShaderProgram.uPointSize, 2.0);
mvMatrix = mat4.multiply(mvMatrix, mvMatrix, objMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.pbuff);
gl.vertexAttribPointer(pointShaderProgram.vertexPositionAttribute, obj.pbuff.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.cbuff);
gl.vertexAttribPointer(pointShaderProgram.vertexColorAttribute, obj.cbuff.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms(gl, pointShaderProgram, projMatrix, mvMatrix);
gl.drawArrays(gl.POINTS, 0, obj.numberOfPoints);
gl.disable(gl.POLYGON_OFFSET_FILL);
} catch(error) {
console.log(error);
}
}
// ----------------------------------------------------------------------
function renderColorMap(renderingContext, camera) {
try {
var obj = this, ctx = renderingContext.ctx2d, range, txt, color, c, v,
size, pos, dx, dy, realSize, textSizeX, textSizeY, grad,
width = renderingContext.container.width(),
height = renderingContext.container.height();
range = [obj.colors[0][0], obj.colors[obj.colors.length-1][0]];
size = [obj.size[0]*width, obj.size[1]*height];
pos = [obj.position[0]*width, (1-obj.position[1])*height];
pos[1] = pos[1]-size[1];
dx = size[0]/size[1];
dy = size[1]/size[0];
realSize = size;
textSizeX = Math.round(height/35);
textSizeY = Math.round(height/23);
if (obj.orientation == 1){
size[0] = size[0]*dy/25;
size[1] = size[1]-(2*textSizeY);
} else {
size[0] = size[0];
size[1] = size[1]*dx/25;
}
// Draw Gradient
if(obj.orientation == 1){
pos[1] += 2*textSizeY;
grad = ctx.createLinearGradient(pos[0], pos[1], pos[0], pos[1]+size[1]);
} else {
pos[1] += 2*textSizeY;
grad = ctx.createLinearGradient(pos[0], pos[1], pos[0]+size[0], pos[1]);
}
if ((range[1]-range[0]) == 0){
color = 'rgba(' + obj.colors[0][1] + ',' + obj.colors[0][2] + ',' + obj.colors[0][3] + ',1)';
grad.addColorStop(0, color);
grad.addColorStop(1, color);
} else {
for(c=0; c= 0; layer--) {
localRenderer = sceneJSON.Renderers[layer];
localWidth = localRenderer.size[0] - localRenderer.origin[0];
localHeight = localRenderer.size[1] - localRenderer.origin[1];
localCamera = localRenderer.camera;
// Convert % to pixel based
localWidth *= width;
localHeight *= height;
localX = localRenderer.origin[0] * width;
localY = localRenderer.origin[1] * height;
localX = (localX < 0) ? 0 : localX;
localY = (localY < 0) ? 0 : localY;
// Update renderer camera aspect ratio
localCamera.setViewSize(localWidth, localHeight); // FIXME maybe use the local width/height
// Setup viewport
gl.viewport(localX, localY, localWidth, localHeight);
// Render non-transparent objects for the current layer
nbObjects += objectHandler.renderSolid(layer, renderingContext, localCamera);
// Now render transparent objects
gl.enable(gl.BLEND); //Enable transparency
gl.enable(gl.POLYGON_OFFSET_FILL); //Avoid zfighting
gl.polygonOffset(-1.0, -1.0);
nbObjects += objectHandler.renderTransparent(layer, renderingContext, localCamera);
gl.disable(gl.POLYGON_OFFSET_FILL);
gl.disable(gl.BLEND);
}
if (saveScreenOnRender === true) {
screenImage = renderingContext.gl.canvas.toDataURL();
}
// Update frame rate
container.trigger({
type: 'stats',
stat_id: 'webgl-fps',
stat_value: 1
});
container.trigger({
type: 'stats',
stat_id: 'webgl-nb-objects',
stat_value: nbObjects
});
} catch(error) {
console.log(error);
}
container.trigger('done');
}
// ------------------------------------------------------------------
function pushCameraState() {
if(cameraLayerZero != null) {
var fp_ = cameraLayerZero.getFocalPoint(),
up_ = cameraLayerZero.getViewUp(),
pos_ = cameraLayerZero.getPosition(),
fp = [fp_[0], fp_[1], fp_[2]],
up = [up_[0], up_[1], up_[2]],
pos = [pos_[0], pos_[1], pos_[2]];
session.call("viewport.camera.update", [Number(options.view), fp, up, pos]);
}
}
// ------------------------------------------------------------------
function updateScene() {
try{
if(sceneJSON === null || typeof(sceneJSON) === "undefined") {
return;
}
// Local variables
var bgColor1 = [0,0,0], bgColor2 = [0,0,0], renderer;
// Create camera for each renderer + handle Background (Layer 0)
otherCamera = [];
for(var idx = 0; idx < sceneJSON.Renderers.length; idx++) {
renderer = sceneJSON.Renderers[idx];
renderer.camera = createCamera();
renderer.camera.setCenterOfRotation(sceneJSON.Center);
renderer.camera.setCameraParameters( renderer.LookAt[0],
[renderer.LookAt[7], renderer.LookAt[8], renderer.LookAt[9]],
[renderer.LookAt[1], renderer.LookAt[2], renderer.LookAt[3]],
[renderer.LookAt[4], renderer.LookAt[5], renderer.LookAt[6]]);
// Custom handling of layer 0
if(renderer.layer === 0) {
cameraLayerZero = renderer.camera;
bgColor1 = bgColor2 = renderer.Background1;
if(typeof(renderer.Background2) != "undefined") {
bgColor2 = renderer.Background2;
}
} else {
otherCamera.push(renderer.camera);
}
}
background = buildBackground(gl, bgColor1, bgColor2);
// Update the list of object to render
objectHandler.updateDisplayList(sceneJSON);
// Fetch the object that we are missing
objectHandler.fetchMissingObjects(fetchObject);
// Draw scene
drawScene(false);
} catch(error) {
console.log(error);
}
}
// ------------------------------------------------------------------
function handleDownloadProgressEvent(event) {
var status = event.status;
function updateProgressBar(element, percentProgress) {
var progressString = percentProgress + '%';
element.attr('aria-valuenow', percentProgress)
.css('width', progressString);
//.html(progressString);
}
if (status === 'create') {
var initialMessage = "Downloading metadata for all timesteps",
html = PROGRESS_BAR_TEMPLATE.replace(/MESSAGE/, initialMessage),
progressElt = $(html);
container.append(progressElt);
} else if (status === 'update') {
var progressType = event.progressType,
pbElt = $('.progress-meter');
if (progressType === 'retrieved-metadata') {
$('.progress-message-span').text('Metadata retrieved, downloading objects');
pbElt.removeClass('progress-bar-striped active');
updateProgressBar(pbElt, 0);
} else {
var numPartsThisSha = event.numParts;
m_numberOfPartsDownloaded += numPartsThisSha;
var numberRemaining = m_numberOfPartsToDownload - m_numberOfPartsDownloaded;
var percent = ((m_numberOfPartsDownloaded / m_numberOfPartsToDownload) * 100).toFixed(0);
if (numberRemaining <= 0) {
$('.download-progressbar-container').remove();
} else {
$('.progress-message-span').text('Downloading objects');
updateProgressBar(pbElt, percent);
}
}
}
}
// ------------------------------------------------------------------
function downloadAllTimesteps() {
container.trigger({
type: 'downloadProgress',
status: 'create'
});
session.call('viewport.webgl.metadata.alltimesteps', []).then(function(result){
if (result.hasOwnProperty('success') && result.success === true) {
var metaDataList = result.metaDataList;
container.trigger({
type: 'downloadProgress',
status: 'update',
progressType: 'retrieved-metadata'
});
// For progress events, I want to first know how many items to retrieve
m_numberOfPartsToDownload = 0;
for (var sha in metaDataList) {
if (metaDataList.hasOwnProperty(sha)) {
m_numberOfPartsToDownload += metaDataList[sha].numParts;
}
}
m_numberOfPartsDownloaded = 0;
setTimeout(function() {
// Now go through and download the heavy data for anythin we don't already have
for (var sha in metaDataList) {
if (metaDataList.hasOwnProperty(sha)) {
var numParts = metaDataList[sha].numParts,
objId = metaDataList[sha].id,
alreadyCached = true;
// Before I go and fetch all the parts for this object, make sure
// I don't already have them cached
for (var i = 0; i < numParts; i+=1) {
var obj = {
'id': objId,
'md5': sha,
'part': i + 1
};
if (!objectHandler.isObjectRegistered(obj)) {
alreadyCached = false;
break;
}
}
if (alreadyCached === false) {
fetchCachedObject(sha);
} else {
container.trigger({
type: 'downloadProgress',
status: 'update',
numParts: numParts
});
}
}
}
}, 500);
}
}, function(metaDataError) {
console.log("Error retrieving metadata for all timesteps");
console.log(metaDataError);
});
}
// ------------------------------------------------------------------
function fetchCachedObject(sha) {
var viewId = Number(options.view);
session.call('viewport.webgl.cached.data', [sha]).then(function(result) {
if (result.success === false) {
console.log("Fetching cached data for " + sha + " failed, reason:");
consolelog(result.reason);
return;
}
var dataObject = result.data;
if (dataObject.hasOwnProperty('partsList')) {
for (var dIdx = 0; dIdx < dataObject.partsList.length; dIdx += 1) {
// Create a complete scene part object and cache it
var newObject = {
md5: dataObject.md5,
part: dIdx + 1,
vid: viewId,
id: dataObject.id,
data: atob(dataObject.partsList[dIdx]),
hasTransparency: dataObject.transparency,
layer: dataObject.layer,
render: function(){}
};
// Process object
initializeObject(gl, newObject);
// Register it for rendering
objectHandler.registerObject(newObject);
}
container.trigger({
type: 'downloadProgress',
status: 'update',
numParts: dataObject.partsList.length
});
}
}, function(err) {
console.log('viewport.webgl.cached.data rpc method failed');
console.log(err);
});
}
// ------------------------------------------------------------------
// Add renderer into the DOM
container.append(renderer);
// ------------------------------------------------------------------
// Add viewport listener
container.bind('invalidateScene', function() {
if(renderer.hasClass('active')){
fetchScene();
}
}).bind('render', function(){
if(renderer.hasClass('active')){
drawScene(false);
}
}).bind('resetViewId', function(e){
options.view = -1;
}).bind('downloadAllTimesteps', function(event){
if(renderer.hasClass('active')){
downloadAllTimesteps();
}
}).bind('downloadProgress', function(event){
if(renderer.hasClass('active')){
handleDownloadProgressEvent(event);
}
}).bind('clearCache', function(event){
if(renderer.hasClass('active')){
objectHandler.clearCache();
container.trigger('invalidateScene');
}
}).bind('captureRenderedImage', function(e){
if (renderer.hasClass('active')) {
drawScene(true);
$(container).parent().trigger({
type: 'captured-screenshot-ready',
imageData: screenImage
});
}
}).bind('mouse', function(event){
if(renderer.hasClass('active')){
event.preventDefault();
if(event.action === 'down') {
mouseHandling.button = event.current_button;
mouseHandling.lastX = event.pageX;
mouseHandling.lastY = event.pageY;
} else if (event.action === 'up') {
mouseHandling.button = null;
} else if (event.action === 'move' && mouseHandling.button != null && cameraLayerZero != null) {
var newMouseX= event.pageX, newMouseY = event.pageY,
mouseDX = mouseHandling.lastX - newMouseX,
mouseDY = mouseHandling.lastY - newMouseY,
lastMouseX = mouseHandling.lastX,
lastMouseY = mouseHandling.lastY,
panD, zTrans,
focalPoint, focusWorldPt, focusDisplayPt,
displayPt1, displayPt2,
worldPt1, worldPt2,
width = renderer.width(),
height = renderer.height();
mouseHandling.lastX = newMouseX;
mouseHandling.lastY = newMouseY;
if (mouseHandling.button === 1) {
cameraLayerZero.rotate(mouseDX, mouseDY);
for(var i in otherCamera) {
otherCamera[i].rotate(mouseDX, mouseDY);
}
} else if (mouseHandling.button === 2) {
panD = cameraLayerZero.calculatePanDeltas(
width, height, newMouseX, newMouseY,
lastMouseX, lastMouseY);
cameraLayerZero.pan(-panD[0], -panD[1], -panD[2] );
} else if (mouseHandling.button === 3) {
zTrans = (newMouseY - lastMouseY) / height;
// Calculate zoom scale here
if (zTrans > 0) {
cameraLayerZero.zoom(1 - Math.abs(zTrans));
} else {
cameraLayerZero.zoom(1 + Math.abs(zTrans));
}
}
drawScene(false);
pushCameraState();
}
}
}).bind('active', function(){
if(renderer.hasClass('active')){
// Setup GL context
gl.viewportWidth = renderer.width();
gl.viewportHeight = renderer.height();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
initializeShader(gl, shaderProgram, pointShaderProgram);
// Ready to render data
fetchScene();
drawScene(false);
}
});
}
// ----------------------------------------------------------------------
// Camera object
// ----------------------------------------------------------------------
function createCamera() {
var viewAngle = 30.0,
centerOfRotation = vec3.set(vec3.create(), 0.0,0.0,-1.0),
aspect = 1.0,
left = -1.0,
right = 1.0,
bottom = -1.0,
top = 1.0,
near = 0.01,
far = 10000.0,
position = vec4.set(vec4.create(), 0.0, 0.0, 0.0, 1.0),
focalPoint = vec4.set(vec4.create(), 0.0, 0.0, -1.0, 1.0),
viewUp = vec4.set(vec4.create(), 0.0, 1.0, 0.0, 0.0),
rightDir = vec4.set(vec4.create(), 1.0, 0.0, 0.0, 0.0),
projectionMatrix = mat4.create(),
modelViewMatrix = mat4.create(),
perspective = true,
width = 100,
height = 100,
modified = true;
directionOfProjection = vec4.fromValues(0.0, 0.0, -1.0, 0.0),
// Initialize to identity (just to be safe)
mat4.identity(modelViewMatrix);
mat4.identity(projectionMatrix);
function computeOrthogonalAxes() {
computeDirectionOfProjection();
vec3.cross(rightDir, directionOfProjection, viewUp);
vec3.normalize(rightDir, rightDir);
modified = true;
};
function computeDirectionOfProjection() {
vec3.subtract(directionOfProjection, focalPoint, position);
vec3.normalize(directionOfProjection, directionOfProjection);
modified = true;
};
function worldToDisplay(worldPt, width, height) {
var viewProjectionMatrix = mat4.create();
mat4.multiply(viewProjectionMatrix, projectionMatrix, modelViewMatrix),
result = vec4.create();
// Transform world to clipping coordinates
var clipPt = vec4.create();
vec4.transformMat4(clipPt, worldPt, viewProjectionMatrix);
if (clipPt[3] !== 0.0) {
clipPt[0] = clipPt[0] / clipPt[3];
clipPt[1] = clipPt[1] / clipPt[3];
clipPt[2] = clipPt[2] / clipPt[3];
clipPt[3] = 1.0;
}
var winX = Math.round((((clipPt[0]) + 1) / 2.0) * width);
// / We calculate -point3D.getY() because the screen Y axis is
// / oriented top->down
var winY = Math.round(((1 - clipPt[1]) / 2.0) * height);
var winZ = clipPt[2];
var winW = clipPt[3];
vec4.set(result, winX, winY, winZ, winW);
return result;
};
function displayToWorld(displayPt, width, height) {
var x = (2.0 * displayPt[0] / width) - 1;
var y = -(2.0 * displayPt[1] / height) + 1;
var z = displayPt[2];
var viewProjectionInverse = mat4.create();
mat4.multiply(viewProjectionInverse, projectionMatrix, modelViewMatrix);
mat4.invert(viewProjectionInverse, viewProjectionInverse);
var worldPt = vec4.create();
vec4.set(worldPt, x, y, z, 1);
vec4.transformMat4(worldPt, worldPt, viewProjectionInverse);
if (worldPt[3] !== 0.0) {
worldPt[0] = worldPt[0] / worldPt[3];
worldPt[1] = worldPt[1] / worldPt[3];
worldPt[2] = worldPt[2] / worldPt[3];
worldPt[3] = 1.0;
}
return worldPt;
};
return {
calculatePanDeltas: function(
width, height, newMouseX, newMouseY, lastMouseX, lastMouseY) {
var dx,dy,dz,
focusDisplayPt, displayPt1, displayPt2,
worldPt1, worldPt2,
focusWorldPt = vec4.fromValues(
focalPoint[0], focalPoint[1], focalPoint[2], 1);
focusDisplayPt =
worldToDisplay(
focusWorldPt, width, height);
displayPt1 = vec4.fromValues(
newMouseX, newMouseY, focusDisplayPt[2], 1.0);
displayPt2 = vec4.fromValues(
lastMouseX, lastMouseY, focusDisplayPt[2], 1.0);
worldPt1 = displayToWorld(
displayPt1, width, height);
worldPt2 = displayToWorld(
displayPt2, width, height);
dx = worldPt1[0] - worldPt2[0];
dy = worldPt1[1] - worldPt2[1];
dz = worldPt1[2] - worldPt2[2];
return [dx,dy,dz];
},
getFocalPoint: function() {
return focalPoint;
},
getPosition: function() {
return position;
},
getViewUp: function() {
return viewUp;
},
setCenterOfRotation: function(center) {
//console.log('[CAMERA] centerOfRotation ' + center);
vec3.set(centerOfRotation, center[0], center[1], center[2]);
},
setCameraParameters : function(angle, pos, focal, up) {
//console.log("[CAMERA] angle: " + angle + " position: " + pos + " focal: " + focal + " up: " + up );
viewAngle = angle * Math.PI / 180;
vec4.set(position, pos[0], pos[1], pos[2], 1.0);
vec4.set(focalPoint, focal[0], focal[1], focal[2], 1.0);
vec4.set(viewUp, up[0], up[1], up[2], 0.0);
modified = true;
},
setViewSize : function(w, h) {
//console.log('[CAMERA] width: ' + w + ' height: ' + h);
aspect = w/h;
width = w;
height = h;
modified = true;
},
enableOrtho : function() {
perspective = false;
modified = true;
},
enablePerspective : function() {
perspective = true;
modified = true;
},
zoom : function(d) {
if (d === 0) {
return;
}
d = d * vec3.distance(focalPoint, position);
position[0] = focalPoint[0] - d * directionOfProjection[0];
position[1] = focalPoint[1] - d * directionOfProjection[1];
position[2] = focalPoint[2] - d * directionOfProjection[2];
modified = true;
this.getCameraMatrices();
},
pan : function(dx, dy, dz) {
position[0] += dx;
position[1] += dy;
position[2] += dz;
focalPoint[0] += dx;
focalPoint[1] += dy;
focalPoint[2] += dz;
computeOrthogonalAxes();
modified = true;
},
rotate : function(dx, dy) {
dx = 0.5 * dx * (Math.PI / 180.0);
dy = 0.5 * dy * (Math.PI / 180.0);
var mat = mat4.create(),
inverseCenterOfRotation = new vec3.create();
mat4.identity(mat);
inverseCenterOfRotation[0] = -centerOfRotation[0];
inverseCenterOfRotation[1] = -centerOfRotation[1];
inverseCenterOfRotation[2] = -centerOfRotation[2];
mat4.translate(mat, mat, centerOfRotation);
mat4.rotate(mat, mat, dx, viewUp);
mat4.rotate(mat, mat, dy, rightDir);
mat4.translate(mat, mat, inverseCenterOfRotation);
vec3.transformMat4(position, position, mat);
vec3.transformMat4(focalPoint, focalPoint, mat);
// Update viewup vector
vec4.transformMat4(viewUp, viewUp, mat);
vec4.normalize(viewUp, viewUp);
computeOrthogonalAxes();
modified = true;
this.getCameraMatrices();
},
getCameraMatrices : function() {
if (modified) {
// Compute project matrix
if (perspective) {
mat4.perspective(projectionMatrix, viewAngle, aspect, near, far);
} else {
mat4.ortho(projectionMatrix, left, right, bottom, top, near, far);
}
// Compute modelview matrix
computeOrthogonalAxes();
mat4.lookAt(modelViewMatrix, position, focalPoint, viewUp);
modified = false;
};
return [projectionMatrix, modelViewMatrix];
}
};
}
// ----------------------------------------------------------------------
// Init vtkWeb module if needed
// ----------------------------------------------------------------------
if (GLOBAL.hasOwnProperty("vtkWeb")) {
module = GLOBAL.vtkWeb || {};
} else {
GLOBAL.vtkWeb = module;
}
// ----------------------------------------------------------------------
// Extend the viewport factory - ONLY IF WEBGL IS SUPPORTED
// ----------------------------------------------------------------------
try {
if (GLOBAL.WebGLRenderingContext && typeof(vec3) != "undefined" && typeof(mat4) != "undefined") {
var canvas = GLOBAL.document.createElement('canvas'),
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if(gl) {
// WebGL is supported
if(!module.hasOwnProperty('ViewportFactory')) {
module['ViewportFactory'] = {};
}
module.ViewportFactory[FACTORY_KEY] = FACTORY;
}
}
} catch(exception) {
// nothing to do
}
// ----------------------------------------------------------------------
// Local module registration
// ----------------------------------------------------------------------
try {
// Tests for presence of jQuery and glMatrix, then registers this module
if ($ !== undefined && module.ViewportFactory[FACTORY_KEY] !== undefined) {
module.registerModule('vtkweb-viewport-webgl');
}
} catch(err) {
console.error('jQuery or glMatrix is missing or browser does not support WebGL: ' + err.message);
}
}(window, jQuery));