UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

539 lines (528 loc) 20.3 kB
import { m as macro } from '../../../macros2.js'; import vtkOpenGLTexture from '../Texture.js'; import vtkDataArray from '../../../Common/Core/DataArray.js'; import vtkHelper from '../Helper.js'; import vtkProperty from '../../Core/Property.js'; import vtkVertexArrayObject from '../VertexArrayObject.js'; import vtkOpenGLFramebuffer from '../Framebuffer.js'; import vtkLineIntegralConvolution2D from './LineIntegralConvolution2D.js'; import { v as vtkLineIntegralConvolution2D_quadVS } from './glsl/vtkLineIntegralConvolution2D_quadVS.glsl.js'; import { v as vtkLineIntegralConvolution2D_SC } from './glsl/vtkLineIntegralConvolution2D_SC.glsl.js'; import { v as vtkSurfaceLICInterface_DCpy } from './glsl/vtkSurfaceLICInterface_DCpy.glsl.js'; import { v as vtkSurfaceLICInterface_CE } from './glsl/vtkSurfaceLICInterface_CE.glsl.js'; import seedrandom from 'seedrandom'; import { NoiseType, ContrastEnhanceMode } from '../../Core/SurfaceLICInterface/Constants.js'; import vtkSurfaceLICInterface from '../../Core/SurfaceLICInterface.js'; const { Representation } = vtkProperty; // ---------------------------------------------------------------------------- // vtkLICInterface methods // ---------------------------------------------------------------------------- function getQuadPoly(openGLRenderWindow) { const quad = vtkHelper.newInstance(); quad.setOpenGLRenderWindow(openGLRenderWindow); // build the CABO const ptsArray = new Float32Array(12); for (let i = 0; i < 4; i++) { ptsArray[i * 3] = i % 2 * 2 - 1.0; ptsArray[i * 3 + 1] = i > 1 ? 1.0 : -1.0; ptsArray[i * 3 + 2] = 0.0; } const tCoord = new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]); const cellArray = new Uint16Array(8); cellArray[0] = 3; cellArray[1] = 0; cellArray[2] = 1; cellArray[3] = 3; cellArray[4] = 3; cellArray[5] = 0; cellArray[6] = 3; cellArray[7] = 2; const points = vtkDataArray.newInstance({ numberOfComponents: 3, values: ptsArray }); points.setName('points'); const cells = vtkDataArray.newInstance({ numberOfComponents: 1, values: cellArray }); const tArray = vtkDataArray.newInstance({ numberOfComponents: 2, values: tCoord }); quad.getCABO().createVBO(cells, 'polys', Representation.SURFACE, { points, cellOffset: 0, tcoords: tArray }); return quad; } function vtkOpenGLSurfaceLICInterface(publicAPI, model) { model.classHierarchy.push('vtkOpenGLSurfaceLICInterface'); publicAPI.renderQuad = (bounds, program) => { const poly = model.licQuad; const gl = model.context; let VAO = model.licQuadVAO; if (!VAO) { VAO = vtkVertexArrayObject.newInstance(); VAO.setOpenGLRenderWindow(model._openGLRenderWindow); model.licQuadVAO = VAO; } if (model.previousProgramHash !== program.getMd5Hash()) { VAO.shaderProgramChanged(); poly.getCABO().bind(); VAO.addAttributeArray(program, poly.getCABO(), 'vertexDC', poly.getCABO().getVertexOffset(), poly.getCABO().getStride(), model.context.FLOAT, 3, model.context.FALSE); VAO.addAttributeArray(program, poly.getCABO(), 'tcoordDC', poly.getCABO().getTCoordOffset(), poly.getCABO().getStride(), model.context.FLOAT, 2, model.context.FALSE); model.previousProgramHash = program.getMd5Hash(); } gl.drawArrays(gl.TRIANGLES, 0, poly.getCABO().getElementCount()); VAO.release(); }; function generateGaussianNoise(length, numberOfNoiseLevels, noiseImpulseProbability, noiseImpulseBackgroundValue, min, max) { const N = 2048; const impulseProb = Math.max(0.0, Math.min(1.0, noiseImpulseProbability)); const noise = Float32Array.from({ length: length * length }, () => { let val = 0; if (impulseProb === 1.0 || Math.random() > 1.0 - impulseProb) { for (let i = 0; i < N; ++i) { val += Math.random(); } } return val; }); // Normalize let maxVal = 0.0; let minVal = N + 1; noise.forEach(val => { // Don't count 0s for minVal if impulseProb < 1.0 if (impulseProb === 1.0) { minVal = val < minVal ? val : minVal; } else { minVal = val < minVal && val > 0.0 ? val : minVal; } maxVal = val > maxVal ? val : maxVal; }); let diff = maxVal - minVal; if (diff === 0.0) { minVal = 0.0; if (maxVal === 0.0) { diff = 1.0; } else { diff = maxVal; } } const maxLevel = numberOfNoiseLevels - 1; const delta = maxLevel !== 0 ? 1.0 / maxLevel : 0.0; const noiseRange = max - min; return noise.map(val => { const normalized = val < minVal ? val : (val - minVal) / diff; const l = Math.floor(normalized * numberOfNoiseLevels); const quantized = l > maxLevel ? maxLevel : l; if (val >= minVal) { if (numberOfNoiseLevels === 1) { return max; } return min + quantized * delta * noiseRange; } return noiseImpulseBackgroundValue; }); } function generateUniformNoise(_ref, numberOfNoiseLevels, min, max) { let [width, height] = _ref; const diff = max - min; return Float32Array.from({ length: width * height }, () => { let r = Math.random(); r = Math.floor(r * numberOfNoiseLevels) / numberOfNoiseLevels; r = r * diff + min; if (r > 1.0) { return 1.0; } if (r < 0.0) { return 0.0; } return r; }); } publicAPI.generateNoiseTexture = length => { if (!model.noiseTexture || model.licInterface.getRebuildNoiseTexture()) { model.licInterface.setRebuildNoiseTexture(false); if (model.noiseTexture) { model.noiseTexture.releaseGraphicsResources(); } // Reseed RNG seedrandom(model.noiseGeneratorSeed, { global: true }); let base = []; const { noiseTextureType, noiseGrainSize, numberOfNoiseLevels, noiseImpulseProbability, noiseImpulseBackgroundValue, minNoiseValue, maxNoiseValue } = model.licInterface.get('noiseTextureType', 'noiseGrainSize', 'numberOfNoiseLevels', 'noiseImpulseProbability', 'noiseImpulseBackgroundValue', 'minNoiseValue', 'maxNoiseValue'); switch (noiseTextureType) { case NoiseType.GAUSSIAN: base = generateGaussianNoise(Math.floor(length / noiseGrainSize), numberOfNoiseLevels, noiseImpulseProbability, noiseImpulseBackgroundValue, minNoiseValue, maxNoiseValue); break; case NoiseType.UNIFORM: default: base = generateUniformNoise([Math.ceil(length / noiseGrainSize), Math.ceil(length / noiseGrainSize)], numberOfNoiseLevels, minNoiseValue, maxNoiseValue); } const invGrainSize = 1.0 / noiseGrainSize; const values = Float32Array.from({ length: length * length * 4 }, (val, index) => { const baseIndex = index / 4; if (index % 4 === 0) { const x = Math.floor(baseIndex % length * invGrainSize); const y = Math.floor(baseIndex / length * invGrainSize); return base[y * (length / noiseGrainSize) + x]; } if (index % 4 === 1 || index % 4 === 3) { return 1.0; } return 0.0; }); const texture = vtkOpenGLTexture.newInstance({ wrapS: vtkOpenGLTexture.Wrap.REPEAT, wrapT: vtkOpenGLTexture.Wrap.REPEAT, minificationFilter: vtkOpenGLTexture.Filter.NEAREST, magnificationFilter: vtkOpenGLTexture.Filter.NEAREST, generateMipMap: false, openGLDataType: model.context.FLOAT, baseLevel: 0, maxLevel: 0, autoParameters: false }); texture.setOpenGLRenderWindow(model._openGLRenderWindow); texture.create2DFromRaw(length, length, 4, 'Float32Array', values); texture.activate(); texture.sendParameters(); texture.deactivate(); model.noiseTexture = texture; } }; publicAPI.buildAShader = fSource => model._openGLRenderWindow.getShaderCache().readyShaderProgramArray(vtkLineIntegralConvolution2D_quadVS, fSource, ''); publicAPI.allocateTextures = () => { const nearest = vtkOpenGLTexture.Filter.NEAREST; const linear = vtkOpenGLTexture.Filter.LINEAR; const rw = model._openGLRenderWindow; if (!model.geometryImage) { model.geometryImage = publicAPI.allocateTexture(rw, nearest); } if (!model.vectorImage) { model.vectorImage = publicAPI.allocateTexture(rw, linear); } if (!model.maskVectorImage) { model.maskVectorImage = publicAPI.allocateTexture(rw, linear); } if (!model.LICImage) { model.LICImage = publicAPI.allocateTexture(rw, nearest); } if (!model.RGBColorImage) { model.RGBColorImage = publicAPI.allocateTexture(rw, nearest); } if (!model.HSLColorImage) { model.HSLColorImage = publicAPI.allocateTexture(rw, nearest); } if (!model.depthTexture) { model.depthTexture = publicAPI.allocateDepthTexture(rw); } }; publicAPI.allocateTexture = (openGLRenderWindow, filter) => { const gl = model.context; const texture = vtkOpenGLTexture.newInstance({ wrapS: vtkOpenGLTexture.Wrap.CLAMP_TO_EDGE, wrapT: vtkOpenGLTexture.Wrap.CLAMP_TO_EDGE, minificationFilter: filter, magnificationFilter: filter, generateMipmap: false, openGLDataType: gl.FLOAT, baseLevel: 0, maxLevel: 0, autoParameters: false }); texture.setOpenGLRenderWindow(openGLRenderWindow); texture.setInternalFormat(gl.RGBA32F); texture.create2DFromRaw(...model.size, 4, 'Float32Array', null); texture.activate(); texture.sendParameters(); texture.deactivate(); return texture; }; publicAPI.allocateDepthTexture = openGLRenderWindow => { const gl = model.context; const texture = vtkOpenGLTexture.newInstance({ generateMipmap: false, openGLDataType: gl.FLOAT, autoParameters: false }); texture.setOpenGLRenderWindow(openGLRenderWindow); texture.createDepthFromRaw(...model.size, 'Float32Array', null); texture.activate(); texture.sendParameters(); texture.deactivate(); return texture; }; publicAPI.createFBO = () => { if (!model.framebuffer) { model.licHelper = null; // All buffers need rebuilding const fb = vtkOpenGLFramebuffer.newInstance(); fb.setOpenGLRenderWindow(model._openGLRenderWindow); fb.saveCurrentBindingsAndBuffers(); fb.create(...model.size); fb.populateFramebuffer(); model.framebuffer = fb; fb.restorePreviousBindingsAndBuffers(); } }; publicAPI.completedGeometry = () => { const gl = model.context; const fb = model.framebuffer; fb.removeColorBuffer(0); fb.removeColorBuffer(1); fb.removeColorBuffer(2); fb.removeDepthBuffer(); gl.drawBuffers([gl.NONE]); fb.restorePreviousBindingsAndBuffers(); }; publicAPI.buildAllShaders = () => { if (model.shadersNeedBuilding) { model.licColorPass = publicAPI.buildAShader(vtkLineIntegralConvolution2D_SC); model.licCopyPass = publicAPI.buildAShader(vtkSurfaceLICInterface_DCpy); model.enhanceContrastPass = publicAPI.buildAShader(vtkSurfaceLICInterface_CE); model.shadersNeedBuilding = false; } }; publicAPI.initializeResources = () => { publicAPI.createFBO(); publicAPI.generateNoiseTexture(model.licInterface.getNoiseTextureSize()); publicAPI.allocateTextures(); publicAPI.buildAllShaders(); if (!model.licQuad) { model.licQuad = getQuadPoly(model._openGLRenderWindow); } if (!model.licHelper) { model.licHelper = vtkLineIntegralConvolution2D.newInstance(); } }; publicAPI.prepareForGeometry = () => { const fb = model.framebuffer; fb.saveCurrentBindingsAndBuffers(); fb.bind(); model.geometryImage.activate(); model.vectorImage.activate(); model.maskVectorImage.activate(); // Don't use location 1 as it can be used by order independant translucent pass // Translucent pass uses location 1 because of this line: // gl_FragData[1].r = weight; fb.removeColorBuffer(0); fb.removeColorBuffer(2); fb.removeColorBuffer(3); fb.setColorBuffer(model.geometryImage, 0); fb.setColorBuffer(model.vectorImage, 2); fb.setColorBuffer(model.maskVectorImage, 3); fb.setDepthBuffer(model.depthTexture); const gl = model.context; gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3]); gl.viewport(0, 0, ...model.size); gl.scissor(0, 0, ...model.size); gl.disable(gl.BLEND); gl.disable(gl.DEPTH_TEST); gl.disable(gl.SCISSOR_TEST); gl.clearColor(0.0, 0.0, 0.0, 0.0); // eslint-disable-next-line no-bitwise gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT); }; // Copies the LIC image to the renderWindow. Will try to upscale the image to match the rw's size. publicAPI.copyToScreen = windowSize => { model.RGBColorImage.activate(); model.depthTexture.activate(); if (!model.licCopyPass) { publicAPI.initializeResources(); } const copyPass = model.licCopyPass; model._openGLRenderWindow.getShaderCache().readyShaderProgram(copyPass); const gl = model.context; gl.viewport(0, 0, ...windowSize); gl.scissor(0, 0, ...windowSize); gl.disable(gl.BLEND); gl.enable(gl.DEPTH_TEST); gl.disable(gl.SCISSOR_TEST); copyPass.setUniformi('texDepth', model.depthTexture.getTextureUnit()); copyPass.setUniformi('texRGBColors', model.RGBColorImage.getTextureUnit()); publicAPI.renderQuad(windowSize, copyPass); model.RGBColorImage.deactivate(); model.depthTexture.deactivate(); }; publicAPI.combineColorsAndLIC = () => { const gl = model.context; const fb = model.framebuffer; fb.saveCurrentBindingsAndBuffers(); fb.bind(); fb.create(...model.size); fb.removeColorBuffer(0); fb.removeColorBuffer(1); fb.setColorBuffer(model.RGBColorImage, 0); fb.setColorBuffer(model.HSLColorImage, 1); gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]); gl.disable(gl.DEPTH_TEST); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.clear(gl.COLOR_BUFFER_BIT); model.vectorImage.activate(); model.geometryImage.activate(); model.LICImage.activate(); if (!model.licColorPass) { publicAPI.initializeResources(); } const colorPass = model.licColorPass; model._openGLRenderWindow.getShaderCache().readyShaderProgram(colorPass); colorPass.setUniformi('texVectors', model.vectorImage.getTextureUnit()); colorPass.setUniformi('texGeomColors', model.geometryImage.getTextureUnit()); const { colorMode, LICIntensity, mapModeBias, maskIntensity, maskColor, enhanceContrast, lowColorContrastEnhancementFactor, highColorContrastEnhancementFactor } = model.licInterface.get('colorMode', 'LICIntensity', 'mapModeBias', 'maskIntensity', 'maskColor', 'enhanceContrast', 'lowColorContrastEnhancementFactor', 'highColorContrastEnhancementFactor'); colorPass.setUniformi('texLIC', model.LICImage.getTextureUnit()); colorPass.setUniformi('uScalarColorMode', colorMode); colorPass.setUniformf('uLICIntensity', LICIntensity); colorPass.setUniformf('uMapBias', mapModeBias); colorPass.setUniformf('uMaskIntensity', maskIntensity); colorPass.setUniform3f('uMaskColor', ...maskColor); publicAPI.renderQuad(model.size, colorPass); model.vectorImage.deactivate(); model.geometryImage.deactivate(); model.LICImage.deactivate(); fb.removeColorBuffer(0); fb.removeColorBuffer(1); gl.drawBuffers([gl.NONE]); if (enhanceContrast === ContrastEnhanceMode.COLOR || enhanceContrast === ContrastEnhanceMode.BOTH) { // min and max luminance values. Most of the time close to 0 and 1 let min = 0.0; let max = 1.0; let lDiff = max - min; min += lDiff * lowColorContrastEnhancementFactor; max -= lDiff * highColorContrastEnhancementFactor; lDiff = max - min; fb.setColorBuffer(model.RGBColorImage); gl.drawBuffers([gl.COLOR_ATTACHMENT0]); model.geometryImage.activate(); model.HSLColorImage.activate(); model.LICImage.activate(); if (!model.enhanceContrastPass) { publicAPI.initializeResources(); } const { enhanceContrastPass } = model; model._openGLRenderWindow.getShaderCache().readyShaderProgram(enhanceContrastPass); enhanceContrastPass.setUniformi('texGeomColors', model.geometryImage.getTextureUnit()); enhanceContrastPass.setUniformi('texHSLColors', model.HSLColorImage.getTextureUnit()); enhanceContrastPass.setUniformi('texLIC', model.LICImage.getTextureUnit()); enhanceContrastPass.setUniformf('uLMin', min); enhanceContrastPass.setUniformf('uLMaxMinDiff', lDiff); publicAPI.renderQuad(model.size, enhanceContrastPass); model.geometryImage.deactivate(); model.HSLColorImage.deactivate(); model.LICImage.deactivate(); fb.removeColorBuffer(0); gl.drawBuffers([gl.NONE]); } fb.restorePreviousBindingsAndBuffers(); }; publicAPI.applyLIC = () => { const options = model.licInterface.get('stepSize', 'numberOfSteps', 'enhancedLIC', 'enhanceContrast', 'lowLICContrastEnhancementFactor', 'highLICContrastEnhancementFactor', 'antiAlias', 'normalizeVectors', 'maskThreshold', 'transformVectors'); const resultTexture = model.licHelper.executeLIC(model.size, model.vectorImage, model.maskVectorImage, model.noiseTexture, model._openGLRenderWindow, options); if (!resultTexture) { console.error('Failed to compute image LIC'); model.LICImage = null; return; } model.LICImage = resultTexture; }; publicAPI.setSize = size => { // If size changed, reallocate fb and textures if (Array.isArray(size) && size.length === 2) { if (!model.size || model.size[0] !== size[0] || model.size[1] !== size[1]) { model.size = size; publicAPI.releaseGraphicsResources(); } } }; publicAPI.releaseGraphicsResources = () => { if (model.geometryImage) { model.geometryImage.releaseGraphicsResources(); model.geometryImage = null; } if (model.vectorImage) { model.vectorImage.releaseGraphicsResources(); model.vectorImage = null; } if (model.maskVectorImage) { model.maskVectorImage.releaseGraphicsResources(); model.maskVectorImage = null; } if (model.LICImage) { model.LICImage.releaseGraphicsResources(); model.LICImage = null; } if (model.RGBColorImage) { model.RGBColorImage.releaseGraphicsResources(); model.RGBColorImage = null; } if (model.HSLColorImage) { model.HSLColorImage.releaseGraphicsResources(); model.HSLColorImage = null; } if (model.depthTexture) { model.depthTexture.releaseGraphicsResources(); model.depthTexture = null; } if (model.framebuffer) { model.framebuffer.releaseGraphicsResources(); model.framebuffer = null; } }; } const DEFAULT_VALUES = { context: null, // _openGLRenderWindow: null, shadersNeedBuilding: true, reallocateTextures: true, size: null, licInterface: null }; function extend(publicAPI, model) { let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Inherit vtkSurfaceLICInterface.extend(publicAPI, model, initialValues); macro.obj(publicAPI, model); macro.setGet(publicAPI, model, ['context', '_openGLRenderWindow', 'reallocateTextures', 'licInterface', 'size']); macro.moveToProtected(publicAPI, model, ['openGLRenderWindow']); // Object methods vtkOpenGLSurfaceLICInterface(publicAPI, model); } // ---------------------------------------------------------------------------- const newInstance = macro.newInstance(extend, 'vtkSurfaceLICInterface'); // ---------------------------------------------------------------------------- var vtkOpenGLSurfaceLICInterface$1 = { newInstance, extend }; export { vtkOpenGLSurfaceLICInterface$1 as default, extend, newInstance };