@kitware/vtk.js
Version:
Visualization Toolkit for the Web
391 lines (317 loc) • 15.2 kB
JavaScript
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 };