/** * 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' + '
' + ' MESSAGE' + '
' + '
' + '
' + '
' + '
' + '
'; // ---------------------------------------------------------------------- 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));