UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

391 lines (317 loc) 15.2 kB
import { mat4, vec4 } from 'gl-matrix'; import Constants from '../Core/ImageMapper/Constants.js'; import { newInstance as newInstance$1, obj } from '../../macros.js'; import vtkWebGPUShaderCache from './ShaderCache.js'; import vtkWebGPUFullScreenQuad from './FullScreenQuad.js'; import vtkWebGPUUniformBuffer from './UniformBuffer.js'; import vtkWebGPUSampler from './Sampler.js'; import { InterpolationType } from '../Core/ImageProperty/Constants.js'; import { registerOverride } from './ViewNodeFactory.js'; var SlicingMode = Constants.SlicingMode; var imgFragTemplate = "\n//VTK::Renderer::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::Image::Dec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::IOStructs::Dec\n\n@fragment\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output: fragmentOutput;\n\n //VTK::Image::Sample\n\n // var computedColor: vec4<f32> = vec4<f32>(1.0,0.7, 0.5, 1.0);\n\n//VTK::RenderEncoder::Impl\n\n return output;\n}\n"; // ---------------------------------------------------------------------------- // helper methods // ---------------------------------------------------------------------------- function computeFnToString(property, fn, numberOfComponents) { var pwfun = fn.apply(property); if (pwfun) { var iComps = property.getIndependentComponents(); return "".concat(property.getMTime(), "-").concat(iComps, "-").concat(numberOfComponents); } return '0'; } // ---------------------------------------------------------------------------- // vtkWebGPUImageMapper methods // ---------------------------------------------------------------------------- var tmpMat4 = new Float64Array(16); var tmp2Mat4 = new Float64Array(16); var tmp3Mat4 = new Float64Array(16); var ptsArray1 = new Float64Array(4); var ptsArray2 = new Float64Array(4); function vtkWebGPUImageMapper(publicAPI, model) { // Set our className model.classHierarchy.push('vtkWebGPUImageMapper'); publicAPI.buildPass = function (prepass) { if (prepass) { model.WebGPUImageSlice = publicAPI.getFirstAncestorOfType('vtkWebGPUImageSlice'); model.WebGPURenderer = model.WebGPUImageSlice.getFirstAncestorOfType('vtkWebGPURenderer'); model.WebGPURenderWindow = model.WebGPURenderer.getParent(); model.device = model.WebGPURenderWindow.getDevice(); var ren = model.WebGPURenderer.getRenderable(); // is slice set by the camera if (model.renderable.getSliceAtFocalPoint()) { model.renderable.setSliceFromCamera(ren.getActiveCamera()); } } }; // Renders myself publicAPI.translucentPass = function (prepass) { if (prepass) { publicAPI.render(); } }; publicAPI.opaquePass = function (prepass) { if (prepass) { publicAPI.render(); } }; publicAPI.render = function () { model.renderable.update(); model.currentInput = model.renderable.getInputData(); publicAPI.prepareToDraw(model.WebGPURenderer.getRenderEncoder()); model.renderEncoder.registerDrawCallback(model.pipeline, publicAPI.draw); }; publicAPI.computePipelineHash = function () { var ext = model.currentInput.getExtent(); if (ext[0] === ext[1] || ext[2] === ext[3] || ext[4] === ext[5]) { model.dimensions = 2; model.pipelineHash = 'img2'; } else { model.dimensions = 3; model.pipelineHash = 'img3'; } }; publicAPI.updateUBO = function () { var utime = model.UBO.getSendTime(); var actor = model.WebGPUImageSlice.getRenderable(); var volMapr = actor.getMapper(); if (publicAPI.getMTime() > utime || model.renderable.getMTime() > utime || actor.getProperty().getMTime() > utime) { // compute the SCTCMatrix var image = volMapr.getInputData(); var center = model.WebGPURenderer.getStabilizedCenterByReference(); mat4.identity(tmpMat4); mat4.translate(tmpMat4, tmpMat4, center); // tmpMat4 is now SC->World var mcwcmat = actor.getMatrix(); mat4.transpose(tmp2Mat4, mcwcmat); mat4.invert(tmp2Mat4, tmp2Mat4); // tmp2Mat4 is now world to model mat4.multiply(tmpMat4, tmp2Mat4, tmpMat4); // tmp4Mat is now SC->Model // the method on the data is world to index but the volume is in // model coordinates so really in this context it is model to index var modelToIndex = image.getWorldToIndex(); mat4.multiply(tmpMat4, modelToIndex, tmpMat4); // tmpMat4 is now SC -> Index, save this as we need it later mat4.invert(tmp3Mat4, tmpMat4); // need translation and scale mat4.fromTranslation(tmp2Mat4, [0.5, 0.5, 0.5]); mat4.multiply(tmpMat4, tmp2Mat4, tmpMat4); var dims = image.getDimensions(); mat4.identity(tmp2Mat4); mat4.scale(tmp2Mat4, tmp2Mat4, [1.0 / dims[0], 1.0 / dims[1], 1.0 / dims[2]]); mat4.multiply(tmpMat4, tmp2Mat4, tmpMat4); // tmpMat4 is now SC -> Tcoord model.UBO.setArray('SCTCMatrix', tmpMat4); // need to compute the plane here in world coordinates // then pass that down in the UBO var ext = model.currentInput.getExtent(); // Find what IJK axis and what direction to slice along var _model$renderable$get = model.renderable.getClosestIJKAxis(), ijkMode = _model$renderable$get.ijkMode; // Find the IJK slice var nSlice = model.renderable.getSlice(); if (ijkMode !== model.renderable.getSlicingMode()) { // If not IJK slicing, get the IJK slice from the XYZ position/slice nSlice = model.renderable.getSliceAtPosition(nSlice); } var axis0 = 2; var axis1 = 0; var axis2 = 1; if (ijkMode === SlicingMode.I) { axis0 = 0; axis1 = 1; axis2 = 2; } else if (ijkMode === SlicingMode.J) { axis0 = 1; axis1 = 2; axis2 = 0; } ptsArray1[axis0] = nSlice; ptsArray1[axis1] = ext[axis1 * 2] - 0.5; ptsArray1[axis2] = ext[axis2 * 2] - 0.5; ptsArray1[3] = 1.0; vec4.transformMat4(ptsArray1, ptsArray1, tmp3Mat4); model.UBO.setArray('Origin', ptsArray1); ptsArray2[axis0] = nSlice; ptsArray2[axis1] = ext[axis1 * 2 + 1] + 0.5; ptsArray2[axis2] = ext[axis2 * 2] - 0.5; ptsArray2[3] = 1.0; vec4.transformMat4(ptsArray2, ptsArray2, tmp3Mat4); vec4.subtract(ptsArray2, ptsArray2, ptsArray1); ptsArray2[3] = 1.0; model.UBO.setArray('Axis1', ptsArray2); ptsArray2[axis0] = nSlice; ptsArray2[axis1] = ext[axis1 * 2] - 0.5; ptsArray2[axis2] = ext[axis2 * 2 + 1] + 0.5; ptsArray2[3] = 1.0; vec4.transformMat4(ptsArray2, ptsArray2, tmp3Mat4); vec4.subtract(ptsArray2, ptsArray2, ptsArray1); ptsArray2[3] = 1.0; model.UBO.setArray('Axis2', ptsArray2); // three levels of shift scale combined into one // for performance in the fragment shader var cScale = [1, 1, 1, 1]; var cShift = [0, 0, 0, 0]; var tView = model.textureViews[0]; var tScale = tView.getTexture().getScale(); var numComp = tView.getTexture().getNumberOfComponents(); var iComps = false; // todo handle independent? for (var i = 0; i < numComp; i++) { var cw = actor.getProperty().getColorWindow(); var cl = actor.getProperty().getColorLevel(); var target = iComps ? i : 0; var cfun = actor.getProperty().getRGBTransferFunction(target); if (cfun) { var cRange = cfun.getRange(); cw = cRange[1] - cRange[0]; cl = 0.5 * (cRange[1] + cRange[0]); } cScale[i] = tScale / cw; cShift[i] = -cl / cw + 0.5; } model.UBO.setArray('cScale', cScale); model.UBO.setArray('cShift', cShift); model.UBO.sendIfNeeded(model.device); } }; publicAPI.updateLUTImage = function () { var actorProperty = model.WebGPUImageSlice.getRenderable().getProperty(); var tView = publicAPI.getTextureViews()[0]; tView.getTexture().getNumberOfComponents(); var numIComps = 1; var cfunToString = computeFnToString(actorProperty, actorProperty.getRGBTransferFunction, numIComps); if (model.colorTextureString !== cfunToString) { model.numRows = numIComps; var colorArray = new Uint8Array(model.numRows * 2 * model.rowLength * 4); var cfun = actorProperty.getRGBTransferFunction(); if (cfun) { var tmpTable = new Float32Array(model.rowLength * 3); for (var c = 0; c < numIComps; c++) { cfun = actorProperty.getRGBTransferFunction(c); var cRange = cfun.getRange(); cfun.getTable(cRange[0], cRange[1], model.rowLength, tmpTable, 1); { for (var _i = 0; _i < model.rowLength; _i++) { var _idx = c * model.rowLength * 8 + _i * 4; colorArray[_idx] = 255.0 * tmpTable[_i * 3]; colorArray[_idx + 1] = 255.0 * tmpTable[_i * 3 + 1]; colorArray[_idx + 2] = 255.0 * tmpTable[_i * 3 + 2]; colorArray[_idx + 3] = 255.0; for (var _j = 0; _j < 4; _j++) { colorArray[_idx + model.rowLength * 4 + _j] = colorArray[_idx + _j]; } } } } } else { for (var _i2 = 0; _i2 < model.rowLength; ++_i2) { var grey = 255.0 * _i2 / (model.rowLength - 1); colorArray[_i2 * 4] = grey; colorArray[_i2 * 4 + 1] = grey; colorArray[_i2 * 4 + 2] = grey; colorArray[_i2 * 4 + 3] = 255.0; for (var _j2 = 0; _j2 < 4; _j2++) { colorArray[_i2 * 4 + model.rowLength * 4 + _j2] = colorArray[_i2 * 4 + _j2]; } } } { var treq = { nativeArray: colorArray, width: model.rowLength, height: model.numRows * 2, depth: 1, format: 'rgba8unorm' }; var newTex = model.device.getTextureManager().getTexture(treq); var tview = newTex.createView('tfunTexture'); model.textureViews[1] = tview; } model.colorTextureString = cfunToString; } }; var superClassUpdateBuffers = publicAPI.updateBuffers; publicAPI.updateBuffers = function () { superClassUpdateBuffers(); var newTex = model.device.getTextureManager().getTextureForImageData(model.currentInput); var tViews = model.textureViews; if (!tViews[0] || tViews[0].getTexture() !== newTex) { var tview = newTex.createView('imgTexture'); tViews[0] = tview; } publicAPI.updateLUTImage(); publicAPI.updateUBO(); // set interpolation on the texture based on property setting var actorProperty = model.WebGPUImageSlice.getRenderable().getProperty(); var iType = actorProperty.getInterpolationType() === InterpolationType.NEAREST ? 'nearest' : 'linear'; if (!model.clampSampler || iType !== model.clampSampler.getOptions().minFilter) { model.clampSampler = vtkWebGPUSampler.newInstance({ label: 'clampSampler' }); model.clampSampler.create(model.device, { minFilter: iType, magFilter: iType }); model.additionalBindables = [model.clampSampler]; } }; var sr = publicAPI.getShaderReplacements(); publicAPI.replaceShaderPosition = function (hash, pipeline, vertexInput) { var vDesc = pipeline.getShaderDescription('vertex'); vDesc.addBuiltinOutput('vec4<f32>', '@builtin(position) Position'); var code = vDesc.getCode(); var lines = ['var pos: vec4<f32> = mapperUBO.Origin +', ' (vertexBC.x * 0.5 + 0.5) * mapperUBO.Axis1 + (vertexBC.y * 0.5 + 0.5) * mapperUBO.Axis2;', 'pos.w = 1.0;']; if (model.dimensions === 2) { lines.push('var tcoord : vec2<f32> = (mapperUBO.SCTCMatrix * pos).xy;'); } else { lines.push('var tcoord : vec3<f32> = (mapperUBO.SCTCMatrix * pos).xyz;'); } lines.push('output.tcoordVS = tcoord;', 'output.Position = rendererUBO.SCPCMatrix * pos;'); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', lines).result; vDesc.setCode(code); }; sr.set('replaceShaderPosition', publicAPI.replaceShaderPosition); publicAPI.replaceShaderTCoord = function (hash, pipeline, vertexInput) { var vDesc = pipeline.getShaderDescription('vertex'); if (model.dimensions === 2) { vDesc.addOutput('vec2<f32>', 'tcoordVS'); } else { vDesc.addOutput('vec3<f32>', 'tcoordVS'); } }; sr.set('replaceShaderTCoord', publicAPI.replaceShaderTCoord); publicAPI.replaceShaderImage = function (hash, pipeline, vertexInput) { var fDesc = pipeline.getShaderDescription('fragment'); var code = fDesc.getCode(); if (model.dimensions === 3) { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Image::Sample', [" var computedColor: vec4<f32> =", " textureSampleLevel(imgTexture, clampSampler, input.tcoordVS, 0.0);", "//VTK::Image::Sample"]).result; } else { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Image::Sample', [" var computedColor: vec4<f32> =", " textureSampleLevel(imgTexture, clampSampler, input.tcoordVS, 0.0);", "//VTK::Image::Sample"]).result; } code = vtkWebGPUShaderCache.substitute(code, '//VTK::Image::Sample', [" var coord: vec2<f32> =", " vec2<f32>(computedColor.r * mapperUBO.cScale.r + mapperUBO.cShift.r, 0.5);", " computedColor = textureSampleLevel(tfunTexture, clampSampler, coord, 0.0);"]).result; fDesc.setCode(code); }; sr.set('replaceShaderImage', publicAPI.replaceShaderImage); } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- var DEFAULT_VALUES = { rowLength: 1024 }; // ---------------------------------------------------------------------------- function extend(publicAPI, model) { var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Inheritance vtkWebGPUFullScreenQuad.extend(publicAPI, model, initialValues); publicAPI.setFragmentShaderTemplate(imgFragTemplate); model.UBO = vtkWebGPUUniformBuffer.newInstance({ label: 'mapperUBO' }); model.UBO.addEntry('SCTCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('Origin', 'vec4<f32>'); model.UBO.addEntry('Axis2', 'vec4<f32>'); model.UBO.addEntry('Axis1', 'vec4<f32>'); model.UBO.addEntry('cScale', 'vec4<f32>'); model.UBO.addEntry('cShift', 'vec4<f32>'); model.lutBuildTime = {}; obj(model.lutBuildTime, { mtime: 0 }); model.imagemat = mat4.identity(new Float64Array(16)); model.imagematinv = mat4.identity(new Float64Array(16)); model.VBOBuildTime = {}; obj(model.VBOBuildTime); // Object methods vtkWebGPUImageMapper(publicAPI, model); } // ---------------------------------------------------------------------------- var newInstance = newInstance$1(extend, 'vtkWebGPUImageMapper'); // ---------------------------------------------------------------------------- var index = { newInstance: newInstance, extend: extend }; // Register ourself to WebGPU backend if imported registerOverride('vtkImageMapper', newInstance); export { index as default, extend, newInstance };