import macro from 'vtk.js/Sources/macros'; import vtkOpenGLFramebuffer from 'vtk.js/Sources/Rendering/OpenGL/Framebuffer'; import vtkRenderPass from 'vtk.js/Sources/Rendering/SceneGraph/RenderPass'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkHelper from 'vtk.js/Sources/Rendering/OpenGL/Helper'; import vtkVertexArrayObject from 'vtk.js/Sources/Rendering/OpenGL/VertexArrayObject';
import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants';
const { vtkErrorMacro } = macro;
function vtkConvolution2DPass(publicAPI, model) { model.classHierarchy.push('vtkConvolution2DPass');
publicAPI.computeKernelWeight = function computeKernelWeight(kernel) { const weight = kernel.reduce((prev, curr) => prev + curr); return weight <= 0 ? 1 : weight; };
publicAPI.traverse = (viewNode, parent = null) => { if (model.deleted) { return; }
if (model.kernelDimension % 2 !== 1) { vtkErrorMacro( 'Invalid kernel dimension! Kernel dimension must be odd (e.g. 3, 5, 7, ...).' ); return; }
if (model.kernel === null) { model.kernel = new Float32Array(model.kernelDimension); model.kernel[Math.floor(model.kernelDimension / 2)] = 1; }
const kernelLength = model.kernelDimension * model.kernelDimension; if (model.kernel.length !== kernelLength) { vtkErrorMacro( `The given kernel is invalid. 2D convolution kernels have to be 1D arrays with ${kernelLength} components representing the ${model.kernelDimension}x${model.kernelDimension} kernel in row-major form.` ); return; }
if (model.framebuffer === null) { model.framebuffer = vtkOpenGLFramebuffer.newInstance(); }
const size = viewNode.getSize(); const gl = viewNode.getContext();
if (gl === null) { model.delegates.forEach((val) => { val.traverse(viewNode, publicAPI); }); return; }
if (model.VBOBuildTime.getMTime() < publicAPI.getMTime()) { model.tris.setOpenGLRenderWindow(viewNode); publicAPI.buildVertexBuffer(); }
model.framebuffer.setOpenGLRenderWindow(viewNode); model.framebuffer.saveCurrentBindingsAndBuffers();
const fbSize = model.framebuffer.getSize();
if (fbSize === null || fbSize[0] !== size[0] || fbSize[1] !== size[1]) { model.framebuffer.create(size[0], size[1]); model.framebuffer.populateFramebuffer(); }
model.framebuffer.bind();
model.delegates.forEach((val) => { val.traverse(viewNode, publicAPI); });
model.framebuffer.restorePreviousBindingsAndBuffers();
if ( model.convolutionShader !== null && model.oldKernelDimension !== model.kernelDimension ) { model.convolutionShader = null; model.oldKernelDimension = model.kernelDimension; }
if (model.convolutionShader === null) { model.convolutionShader = viewNode .getShaderCache() .readyShaderProgramArray( [ '//VTK::System::Dec', 'attribute vec4 vertexDC;', 'attribute vec2 tcoordTC;', 'varying vec2 tcoord;', 'void main() { tcoord = tcoordTC; gl_Position = vertexDC; }', ].join('\n'), publicAPI.getFragmentShaderCode(model.kernelDimension), '' ); const program = model.convolutionShader;
model.copyVAO = vtkVertexArrayObject.newInstance(); model.copyVAO.setOpenGLRenderWindow(viewNode);
model.tris.getCABO().bind(); if ( !model.copyVAO.addAttributeArray( program, model.tris.getCABO(), 'vertexDC', model.tris.getCABO().getVertexOffset(), model.tris.getCABO().getStride(), gl.FLOAT, 3, gl.FALSE ) ) { vtkErrorMacro('Error setting vertexDC in copy shader VAO.'); } if ( !model.copyVAO.addAttributeArray( program, model.tris.getCABO(), 'tcoordTC', model.tris.getCABO().getTCoordOffset(), model.tris.getCABO().getStride(), gl.FLOAT, 2, gl.FALSE ) ) { vtkErrorMacro('Error setting vertexDC in copy shader VAO.'); } } else { viewNode.getShaderCache().readyShaderProgram(model.convolutionShader); }
gl.viewport(0, 0, size[0], size[1]); gl.scissor(0, 0, size[0], size[1]);
const tex = model.framebuffer.getColorTexture(); tex.activate(); model.convolutionShader.setUniformi('u_image', tex.getTextureUnit()); model.convolutionShader.setUniform2f( 'u_textureSize', tex.getWidth(), tex.getHeight() ); model.convolutionShader.setUniformfv('u_kernel', model.kernel); model.convolutionShader.setUniformf( 'u_kernelWeight', publicAPI.computeKernelWeight(model.kernel) );
gl.drawArrays(gl.TRIANGLES, 0, model.tris.getCABO().getElementCount()); tex.deactivate(); };
publicAPI.getFragmentShaderCode = (kernelDimension) => { const kernelLength = kernelDimension * kernelDimension; let shaderCode = [ '//VTK::System::Dec', '//VTK::Output::Dec', 'uniform sampler2D u_image;', 'uniform vec2 u_textureSize;', `uniform float u_kernel[${kernelLength}];`, 'uniform float u_kernelWeight;', 'varying vec2 tcoord;', 'void main(){', ' vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;', ' vec4 colorSum =\n', ].join('\n');
const halfDim = Math.floor(kernelDimension / 2);
let i = 0; for (let y = -halfDim; y <= halfDim; ++y) { for (let x = -halfDim; x <= halfDim; ++x) { shaderCode += ` texture2D(u_image, tcoord + onePixel * vec2(${x}, ${y})) * u_kernel[${i}]`; ++i;
if (i !== kernelLength) { shaderCode += ' +\n'; } } }
shaderCode += [ ';', ' gl_FragData[0] = vec4((colorSum / u_kernelWeight).rgb, texture2D(u_image, tcoord).a);', '}', ].join('\n');
return shaderCode; };
publicAPI.buildVertexBuffer = () => { const ptsArray = new Float32Array([ -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, ]);
const tcoordArray = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
const cellArray = new Uint16Array([4, 0, 1, 3, 2]);
const points = vtkDataArray.newInstance({ numberOfComponents: 3, values: ptsArray, }); points.setName('points'); const tcoords = vtkDataArray.newInstance({ numberOfComponents: 2, values: tcoordArray, }); tcoords.setName('tcoords'); const cells = vtkDataArray.newInstance({ numberOfComponents: 1, values: cellArray, }); model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, { points, tcoords, cellOffset: 0, });
model.VBOBuildTime.modified(); }; }
const DEFAULT_VALUES = { framebuffer: null, convolutionShader: null, tris: null, kernel: [0, 0, 0, 0, 1, 0, 0, 0, 0], oldKernelDimension: 3, kernelDimension: 3, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
vtkRenderPass.extend(publicAPI, model, initialValues);
model.VBOBuildTime = {}; macro.obj(model.VBOBuildTime, { mtime: 0 });
model.tris = vtkHelper.newInstance();
macro.setGet(publicAPI, model, ['kernel', 'kernelDimension']);
macro.get(publicAPI, model, ['framebuffer']);
vtkConvolution2DPass(publicAPI, model); }
export const newInstance = macro.newInstance(extend, 'vtkConvolution2DPass');
export default { newInstance, extend };
|