@kitware/vtk.js
Version:
Visualization Toolkit for the Web
1,030 lines (994 loc) • 55.3 kB
JavaScript
import { mat4, mat3 } from 'gl-matrix';
import Constants from '../Core/ImageMapper/Constants.js';
import { n as newInstance$1, e as setGet, o as obj, r as vtkErrorMacro$1, h as chain, c as macro } from '../../macros2.js';
import vtkDataArray from '../../Common/Core/DataArray.js';
import { VtkDataTypes } from '../../Common/Core/DataArray/Constants.js';
import vtkHelper from './Helper.js';
import vtkOpenGLTexture from './Texture.js';
import vtkShaderProgram from './ShaderProgram.js';
import vtkViewNode from '../SceneGraph/ViewNode.js';
import { Representation } from '../Core/Property/Constants.js';
import { Filter, Wrap } from './Texture/Constants.js';
import { InterpolationType } from '../Core/ImageProperty/Constants.js';
import { getTransferFunctionsHash } from './RenderWindow/resourceSharingHelper.js';
import { v as vtkPolyDataVS } from './glsl/vtkPolyDataVS.glsl.js';
import { v as vtkPolyDataFS } from './glsl/vtkPolyDataFS.glsl.js';
import vtkReplacementShaderMapper from './ReplacementShaderMapper.js';
import '../Core/Mapper/CoincidentTopologyHelper.js';
import { registerOverride } from './ViewNodeFactory.js';
import { Resolve } from '../Core/Mapper/Static.js';
const {
vtkErrorMacro
} = macro;
const {
SlicingMode
} = Constants;
// ----------------------------------------------------------------------------
// helper methods
// ----------------------------------------------------------------------------
function splitStringOnEnter(inputString) {
// Split the input string into an array of lines based on "Enter" (newline) characters
// Remove any leading or trailing whitespace from each line and filter out empty lines
const lines = inputString.split('\n');
const trimmedLines = [];
for (let i = 0; i < lines.length; ++i) {
const trimmedLine = lines[i].trim();
if (trimmedLine.length > 0) {
trimmedLines.push(trimmedLine);
}
}
return trimmedLines;
}
// ----------------------------------------------------------------------------
// vtkOpenGLImageMapper methods
// ----------------------------------------------------------------------------
function vtkOpenGLImageMapper(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkOpenGLImageMapper');
function unregisterGraphicsResources(renderWindow) {
// The openGLTexture is not shared
model.openGLTexture.releaseGraphicsResources(renderWindow);
// All these other resources are shared
[model._colorTransferFunc, model._pwFunc, model._labelOutlineThicknessArray, model._labelOutlineOpacity].forEach(coreObject => renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI));
}
publicAPI.buildPass = prepass => {
if (prepass) {
model.currentRenderPass = null;
model.openGLImageSlice = publicAPI.getFirstAncestorOfType('vtkOpenGLImageSlice');
model._openGLRenderer = publicAPI.getFirstAncestorOfType('vtkOpenGLRenderer');
const oldOglRenderWindow = model._openGLRenderWindow;
model._openGLRenderWindow = model._openGLRenderer.getLastAncestorOfType('vtkOpenGLRenderWindow');
if (oldOglRenderWindow && !oldOglRenderWindow.isDeleted() && oldOglRenderWindow !== model._openGLRenderWindow) {
// Unregister the mapper when the render window changes
unregisterGraphicsResources(oldOglRenderWindow);
}
model.context = model._openGLRenderWindow.getContext();
model.tris.setOpenGLRenderWindow(model._openGLRenderWindow);
const ren = model._openGLRenderer.getRenderable();
model.openGLCamera = model._openGLRenderer.getViewNodeFor(ren.getActiveCamera(), model.openGLCamera);
// is slice set by the camera
if (model.renderable.isA('vtkImageMapper') && model.renderable.getSliceAtFocalPoint()) {
model.renderable.setSliceFromCamera(ren.getActiveCamera());
}
}
};
publicAPI.translucentPass = (prepass, renderPass) => {
if (prepass) {
model.currentRenderPass = renderPass;
publicAPI.render();
}
};
publicAPI.zBufferPass = prepass => {
if (prepass) {
model.haveSeenDepthRequest = true;
model.renderDepth = true;
publicAPI.render();
model.renderDepth = false;
}
};
publicAPI.opaqueZBufferPass = prepass => publicAPI.zBufferPass(prepass);
publicAPI.opaquePass = prepass => {
if (prepass) {
publicAPI.render();
}
};
publicAPI.getCoincidentParameters = (ren, actor) => {
if (
// backwards compat with code that (errorneously) set this to boolean
// eslint-disable-next-line eqeqeq
model.renderable.getResolveCoincidentTopology() == Resolve.PolygonOffset) {
return model.renderable.getCoincidentTopologyPolygonOffsetParameters();
}
return null;
};
// Renders myself
publicAPI.render = () => {
const actor = model.openGLImageSlice.getRenderable();
const ren = model._openGLRenderer.getRenderable();
publicAPI.renderPiece(ren, actor);
};
publicAPI.getShaderTemplate = (shaders, ren, actor) => {
shaders.Vertex = vtkPolyDataVS;
shaders.Fragment = vtkPolyDataFS;
shaders.Geometry = '';
};
publicAPI.replaceShaderValues = (shaders, ren, actor) => {
let VSSource = shaders.Vertex;
let FSSource = shaders.Fragment;
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', ['uniform mat4 MCPCMatrix;']).result;
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::PositionVC::Impl', [' gl_Position = MCPCMatrix * vertexMC;']).result;
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::TCoord::Impl', 'tcoordVCVSOutput = tcoordMC;').result;
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::TCoord::Dec', 'attribute vec2 tcoordMC; varying vec2 tcoordVCVSOutput;').result;
const tNumComp = model.openGLTexture.getComponents();
const iComps = actor.getProperty().getIndependentComponents();
let tcoordDec = ['varying vec2 tcoordVCVSOutput;',
// color shift and scale
'uniform float cshift0;', 'uniform float cscale0;',
// pwf shift and scale
'uniform float pwfshift0;', 'uniform float pwfscale0;', 'uniform sampler2D texture1;', 'uniform sampler2D colorTexture1;', 'uniform sampler2D pwfTexture1;', 'uniform float opacity;'];
if (actor.getProperty().getUseLabelOutline()) {
tcoordDec = tcoordDec.concat([
// outline thickness
'uniform sampler2D labelOutlineTexture1;',
// outline opacity
'uniform sampler2D labelOutlineOpacityTexture1;']);
}
if (iComps) {
for (let comp = 1; comp < tNumComp; comp++) {
tcoordDec = tcoordDec.concat([
// color shift and scale
`uniform float cshift${comp};`, `uniform float cscale${comp};`,
// weighting shift and scale
`uniform float pwfshift${comp};`, `uniform float pwfscale${comp};`]);
}
// the heights defined below are the locations
// for the up to four components of the tfuns
// the tfuns have a height of 2XnumComps pixels so the
// values are computed to hit the middle of the two rows
// for that component
switch (tNumComp) {
case 1:
tcoordDec = tcoordDec.concat(['uniform float mix0;', '#define height0 0.5']);
break;
case 2:
tcoordDec = tcoordDec.concat(['uniform float mix0;', 'uniform float mix1;', '#define height0 0.25', '#define height1 0.75']);
break;
case 3:
tcoordDec = tcoordDec.concat(['uniform float mix0;', 'uniform float mix1;', 'uniform float mix2;', '#define height0 0.17', '#define height1 0.5', '#define height2 0.83']);
break;
case 4:
tcoordDec = tcoordDec.concat(['uniform float mix0;', 'uniform float mix1;', 'uniform float mix2;', 'uniform float mix3;', '#define height0 0.125', '#define height1 0.375', '#define height2 0.625', '#define height3 0.875']);
break;
default:
vtkErrorMacro('Unsupported number of independent coordinates.');
}
}
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Dec', tcoordDec).result;
// check for the outline thickness and opacity
const vtkImageLabelOutline = actor.getProperty().getUseLabelOutline();
if (vtkImageLabelOutline === true) {
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelOutline::Dec', ['uniform float vpWidth;', 'uniform float vpHeight;', 'uniform float vpOffsetX;', 'uniform float vpOffsetY;', 'uniform mat4 PCWCMatrix;', 'uniform mat4 vWCtoIDX;', 'uniform ivec3 imageDimensions;', 'uniform int sliceAxis;']).result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ImageLabelOutlineOn', '#define vtkImageLabelOutlineOn').result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelOutlineHelperFunction', ['#ifdef vtkImageLabelOutlineOn', 'vec3 fragCoordToIndexSpace(vec4 fragCoord) {', ' vec4 pcPos = vec4(', ' (fragCoord.x / vpWidth - vpOffsetX - 0.5) * 2.0,', ' (fragCoord.y / vpHeight - vpOffsetY - 0.5) * 2.0,', ' (fragCoord.z - 0.5) * 2.0,', ' 1.0);', '', ' vec4 worldCoord = PCWCMatrix * pcPos;', ' vec4 vertex = (worldCoord/worldCoord.w);', '', ' vec3 index = (vWCtoIDX * vertex).xyz;', '', ' // half voxel fix for labelmapOutline', ' return (index + vec3(0.5)) / vec3(imageDimensions);', '}', 'vec2 getSliceCoords(vec3 coord, int axis) {', ' if (axis == 0) return coord.yz;', ' if (axis == 1) return coord.xz;', ' if (axis == 2) return coord.xy;', '}', '#endif']).result;
}
if (iComps) {
const rgba = ['r', 'g', 'b', 'a'];
let tcoordImpl = ['vec4 tvalue = texture2D(texture1, tcoordVCVSOutput);'];
for (let comp = 0; comp < tNumComp; comp++) {
tcoordImpl = tcoordImpl.concat([`vec3 tcolor${comp} = mix${comp} * texture2D(colorTexture1, vec2(tvalue.${rgba[comp]} * cscale${comp} + cshift${comp}, height${comp})).rgb;`, `float compWeight${comp} = mix${comp} * texture2D(pwfTexture1, vec2(tvalue.${rgba[comp]} * pwfscale${comp} + pwfshift${comp}, height${comp})).r;`]);
}
switch (tNumComp) {
case 1:
tcoordImpl = tcoordImpl.concat(['gl_FragData[0] = vec4(tcolor0.rgb, opacity);']);
break;
case 2:
tcoordImpl = tcoordImpl.concat(['float weightSum = compWeight0 + compWeight1;', 'gl_FragData[0] = vec4(vec3((tcolor0.rgb * (compWeight0 / weightSum)) + (tcolor1.rgb * (compWeight1 / weightSum))), opacity);']);
break;
case 3:
tcoordImpl = tcoordImpl.concat(['float weightSum = compWeight0 + compWeight1 + compWeight2;', 'gl_FragData[0] = vec4(vec3((tcolor0.rgb * (compWeight0 / weightSum)) + (tcolor1.rgb * (compWeight1 / weightSum)) + (tcolor2.rgb * (compWeight2 / weightSum))), opacity);']);
break;
case 4:
tcoordImpl = tcoordImpl.concat(['float weightSum = compWeight0 + compWeight1 + compWeight2 + compWeight3;', 'gl_FragData[0] = vec4(vec3((tcolor0.rgb * (compWeight0 / weightSum)) + (tcolor1.rgb * (compWeight1 / weightSum)) + (tcolor2.rgb * (compWeight2 / weightSum)) + (tcolor3.rgb * (compWeight3 / weightSum))), opacity);']);
break;
default:
vtkErrorMacro('Unsupported number of independent coordinates.');
}
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Impl', tcoordImpl).result;
} else {
// dependent components
switch (tNumComp) {
case 1:
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Impl', [...splitStringOnEnter(`
#ifdef vtkImageLabelOutlineOn
vec3 centerPosIS = fragCoordToIndexSpace(gl_FragCoord);
float centerValue = texture2D(texture1, getSliceCoords(centerPosIS, sliceAxis)).r;
bool pixelOnBorder = false;
vec3 tColor = texture2D(colorTexture1, vec2(centerValue * cscale0 + cshift0, 0.5)).rgb;
float scalarOpacity = texture2D(pwfTexture1, vec2(centerValue * pwfscale0 + pwfshift0, 0.5)).r;
float opacityToUse = scalarOpacity * opacity;
int segmentIndex = int(centerValue * 255.0);
float textureCoordinate = float(segmentIndex - 1) / 1024.0;
float textureValue = texture2D(labelOutlineTexture1, vec2(textureCoordinate, 0.5)).r;
float outlineOpacity = texture2D(labelOutlineOpacityTexture1, vec2(textureCoordinate, 0.5)).r;
int actualThickness = int(textureValue * 255.0);
if (segmentIndex == 0){
gl_FragData[0] = vec4(0.0, 0.0, 0.0, 0.0);
return;
}
for (int i = -actualThickness; i <= actualThickness; i++) {
for (int j = -actualThickness; j <= actualThickness; j++) {
if (i == 0 || j == 0) {
continue;
}
vec4 neighborPixelCoord = vec4(gl_FragCoord.x + float(i),
gl_FragCoord.y + float(j),
gl_FragCoord.z, gl_FragCoord.w);
vec3 neighborPosIS = fragCoordToIndexSpace(neighborPixelCoord);
float value = texture2D(texture1, getSliceCoords(neighborPosIS, sliceAxis)).r;
if (value != centerValue) {
pixelOnBorder = true;
break;
}
}
if (pixelOnBorder == true) {
break;
}
}
if (pixelOnBorder == true) {
gl_FragData[0] = vec4(tColor, outlineOpacity);
}
else {
gl_FragData[0] = vec4(tColor, opacityToUse);
}
#else
float intensity = texture2D(texture1, tcoordVCVSOutput).r;
vec3 tcolor = texture2D(colorTexture1, vec2(intensity * cscale0 + cshift0, 0.5)).rgb;
float scalarOpacity = texture2D(pwfTexture1, vec2(intensity * pwfscale0 + pwfshift0, 0.5)).r;
gl_FragData[0] = vec4(tcolor, scalarOpacity * opacity);
#endif
`)]).result;
break;
case 2:
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Impl', ['vec4 tcolor = texture2D(texture1, tcoordVCVSOutput);', 'float intensity = tcolor.r*cscale0 + cshift0;', 'gl_FragData[0] = vec4(texture2D(colorTexture1, vec2(intensity, 0.5)).rgb, pwfscale0*tcolor.g + pwfshift0);']).result;
break;
case 3:
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Impl', ['vec4 tcolor = cscale0*texture2D(texture1, tcoordVCVSOutput.st) + cshift0;', 'gl_FragData[0] = vec4(texture2D(colorTexture1, vec2(tcolor.r,0.5)).r,', ' texture2D(colorTexture1, vec2(tcolor.g,0.5)).r,', ' texture2D(colorTexture1, vec2(tcolor.b,0.5)).r, opacity);']).result;
break;
default:
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Impl', ['vec4 tcolor = cscale0*texture2D(texture1, tcoordVCVSOutput.st) + cshift0;', 'gl_FragData[0] = vec4(texture2D(colorTexture1, vec2(tcolor.r,0.5)).r,', ' texture2D(colorTexture1, vec2(tcolor.g,0.5)).r,', ' texture2D(colorTexture1, vec2(tcolor.b,0.5)).r, tcolor.a);']).result;
}
}
if (model.haveSeenDepthRequest) {
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Dec', 'uniform int depthRequest;').result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Impl', ['if (depthRequest == 1) {', 'float iz = floor(gl_FragCoord.z*65535.0 + 0.1);', 'float rf = floor(iz/256.0)/255.0;', 'float gf = mod(iz,256.0)/255.0;', 'gl_FragData[0] = vec4(rf, gf, 0.0, 1.0); }']).result;
}
shaders.Vertex = VSSource;
shaders.Fragment = FSSource;
publicAPI.replaceShaderClip(shaders, ren, actor);
publicAPI.replaceShaderCoincidentOffset(shaders, ren, actor);
};
publicAPI.replaceShaderClip = (shaders, ren, actor) => {
let VSSource = shaders.Vertex;
let FSSource = shaders.Fragment;
if (model.renderable.getNumberOfClippingPlanes()) {
let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
if (numClipPlanes > 6) {
vtkErrorMacro$1('OpenGL has a limit of 6 clipping planes');
numClipPlanes = 6;
}
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Dec', ['uniform int numClipPlanes;', 'uniform vec4 clipPlanes[6];', 'varying float clipDistancesVSOutput[6];']).result;
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Impl', ['for (int planeNum = 0; planeNum < 6; planeNum++)', ' {', ' if (planeNum >= numClipPlanes)', ' {', ' break;', ' }', ' clipDistancesVSOutput[planeNum] = dot(clipPlanes[planeNum], vertexMC);', ' }']).result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Dec', ['uniform int numClipPlanes;', 'varying float clipDistancesVSOutput[6];']).result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Impl', ['for (int planeNum = 0; planeNum < 6; planeNum++)', ' {', ' if (planeNum >= numClipPlanes)', ' {', ' break;', ' }', ' if (clipDistancesVSOutput[planeNum] < 0.0) discard;', ' }']).result;
}
shaders.Vertex = VSSource;
shaders.Fragment = FSSource;
};
publicAPI.getNeedToRebuildShaders = (cellBO, ren, actor) => {
// has something changed that would require us to recreate the shader?
// candidates are
// property modified (representation interpolation and lighting)
// input modified
// light complexity changed
// render pass shader replacement changed
const tNumComp = model.openGLTexture.getComponents();
const iComp = actor.getProperty().getIndependentComponents();
// has the render pass shader replacement changed? Two options
let needRebuild = false;
if (!model.currentRenderPass && model.lastRenderPassShaderReplacement || model.currentRenderPass && model.currentRenderPass.getShaderReplacement() !== model.lastRenderPassShaderReplacement) {
needRebuild = true;
}
if (needRebuild || model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest || cellBO.getProgram()?.getHandle() === 0 || cellBO.getShaderSourceTime().getMTime() < model.renderable.getMTime() || cellBO.getShaderSourceTime().getMTime() < model.currentInput.getMTime() || cellBO.getShaderSourceTime().getMTime() < actor.getProperty().getMTime() || model.lastTextureComponents !== tNumComp || model.lastIndependentComponents !== iComp) {
model.lastHaveSeenDepthRequest = model.haveSeenDepthRequest;
model.lastTextureComponents = tNumComp;
model.lastIndependentComponents = iComp;
return true;
}
return false;
};
publicAPI.updateShaders = (cellBO, ren, actor) => {
model.lastBoundBO = cellBO;
// has something changed that would require us to recreate the shader?
if (publicAPI.getNeedToRebuildShaders(cellBO, ren, actor)) {
const shaders = {
Vertex: null,
Fragment: null,
Geometry: null
};
publicAPI.buildShaders(shaders, ren, actor);
// compile and bind the program if needed
const newShader = model._openGLRenderWindow.getShaderCache().readyShaderProgramArray(shaders.Vertex, shaders.Fragment, shaders.Geometry);
// if the shader changed reinitialize the VAO
if (newShader !== cellBO.getProgram()) {
cellBO.setProgram(newShader);
// reset the VAO as the shader has changed
cellBO.getVAO().releaseGraphicsResources();
}
cellBO.getShaderSourceTime().modified();
} else {
model._openGLRenderWindow.getShaderCache().readyShaderProgram(cellBO.getProgram());
}
cellBO.getVAO().bind();
publicAPI.setMapperShaderParameters(cellBO, ren, actor);
publicAPI.setCameraShaderParameters(cellBO, ren, actor);
publicAPI.setPropertyShaderParameters(cellBO, ren, actor);
};
publicAPI.setMapperShaderParameters = (cellBO, ren, actor) => {
// Now to update the VAO too, if necessary.
if (cellBO.getCABO().getElementCount() && (model.VBOBuildTime > cellBO.getAttributeUpdateTime().getMTime() || cellBO.getShaderSourceTime().getMTime() > cellBO.getAttributeUpdateTime().getMTime())) {
if (cellBO.getProgram().isAttributeUsed('vertexMC')) {
if (!cellBO.getVAO().addAttributeArray(cellBO.getProgram(), cellBO.getCABO(), 'vertexMC', cellBO.getCABO().getVertexOffset(), cellBO.getCABO().getStride(), model.context.FLOAT, 3, model.context.FALSE)) {
vtkErrorMacro('Error setting vertexMC in shader VAO.');
}
}
if (cellBO.getProgram().isAttributeUsed('tcoordMC') && cellBO.getCABO().getTCoordOffset()) {
if (!cellBO.getVAO().addAttributeArray(cellBO.getProgram(), cellBO.getCABO(), 'tcoordMC', cellBO.getCABO().getTCoordOffset(), cellBO.getCABO().getStride(), model.context.FLOAT, cellBO.getCABO().getTCoordComponents(), model.context.FALSE)) {
vtkErrorMacro('Error setting tcoordMC in shader VAO.');
}
}
cellBO.getAttributeUpdateTime().modified();
}
const texUnit = model.openGLTexture.getTextureUnit();
cellBO.getProgram().setUniformi('texture1', texUnit);
const numComp = model.openGLTexture.getComponents();
const iComps = actor.getProperty().getIndependentComponents();
if (iComps) {
for (let i = 0; i < numComp; i++) {
cellBO.getProgram().setUniformf(`mix${i}`, actor.getProperty().getComponentWeight(i));
}
}
const oglShiftScale = model.openGLTexture.getShiftAndScale();
// three levels of shift scale combined into one
// for performance in the fragment shader
for (let i = 0; i < numComp; i++) {
let cw = actor.getProperty().getColorWindow();
let cl = actor.getProperty().getColorLevel();
const target = iComps ? i : 0;
const cfun = actor.getProperty().getRGBTransferFunction(target);
if (cfun && actor.getProperty().getUseLookupTableScalarRange()) {
const cRange = cfun.getRange();
cw = cRange[1] - cRange[0];
cl = 0.5 * (cRange[1] + cRange[0]);
}
const scale = oglShiftScale.scale / cw;
const shift = (oglShiftScale.shift - cl) / cw + 0.5;
cellBO.getProgram().setUniformf(`cshift${i}`, shift);
cellBO.getProgram().setUniformf(`cscale${i}`, scale);
}
// pwf shift/scale
for (let i = 0; i < numComp; i++) {
let pwfScale = 1.0;
let pwfShift = 0.0;
const target = iComps ? i : 0;
const pwfun = actor.getProperty().getPiecewiseFunction(target);
if (pwfun) {
const pwfRange = pwfun.getRange();
const length = pwfRange[1] - pwfRange[0];
const mid = 0.5 * (pwfRange[0] + pwfRange[1]);
pwfScale = oglShiftScale.scale / length;
pwfShift = (oglShiftScale.shift - mid) / length + 0.5;
}
cellBO.getProgram().setUniformf(`pwfshift${i}`, pwfShift);
cellBO.getProgram().setUniformf(`pwfscale${i}`, pwfScale);
}
if (model.haveSeenDepthRequest) {
cellBO.getProgram().setUniformi('depthRequest', model.renderDepth ? 1 : 0);
}
// handle coincident
if (cellBO.getProgram().isUniformUsed('coffset')) {
const cp = publicAPI.getCoincidentParameters(ren, actor);
cellBO.getProgram().setUniformf('coffset', cp.offset);
// cfactor isn't always used when coffset is.
if (cellBO.getProgram().isUniformUsed('cfactor')) {
cellBO.getProgram().setUniformf('cfactor', cp.factor);
}
}
const texColorUnit = model.colorTexture.getTextureUnit();
cellBO.getProgram().setUniformi('colorTexture1', texColorUnit);
const texOpacityUnit = model.pwfTexture.getTextureUnit();
cellBO.getProgram().setUniformi('pwfTexture1', texOpacityUnit);
if (actor.getProperty().getUseLabelOutline()) {
const outlineThicknessUnit = model.labelOutlineThicknessTexture.getTextureUnit();
cellBO.getProgram().setUniformi('labelOutlineTexture1', outlineThicknessUnit);
const texOutlineOpacityUnit = model.labelOutlineOpacityTexture.getTextureUnit();
cellBO.getProgram().setUniformi('labelOutlineOpacityTexture1', texOutlineOpacityUnit);
}
if (model.renderable.getNumberOfClippingPlanes()) {
// add all the clipping planes
let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
if (numClipPlanes > 6) {
vtkErrorMacro$1('OpenGL has a limit of 6 clipping planes');
numClipPlanes = 6;
}
const shiftScaleEnabled = cellBO.getCABO().getCoordShiftAndScaleEnabled();
const inverseShiftScaleMatrix = shiftScaleEnabled ? cellBO.getCABO().getInverseShiftAndScaleMatrix() : null;
const mat = inverseShiftScaleMatrix ? mat4.copy(model.imagematinv, actor.getMatrix()) : actor.getMatrix();
if (inverseShiftScaleMatrix) {
mat4.transpose(mat, mat);
mat4.multiply(mat, mat, inverseShiftScaleMatrix);
mat4.transpose(mat, mat);
}
// transform crop plane normal with transpose(inverse(worldToIndex))
mat4.transpose(model.imagemat, model.currentInput.getIndexToWorld());
mat4.multiply(model.imagematinv, mat, model.imagemat);
const planeEquations = [];
for (let i = 0; i < numClipPlanes; i++) {
const planeEquation = [];
model.renderable.getClippingPlaneInDataCoords(model.imagematinv, i, planeEquation);
for (let j = 0; j < 4; j++) {
planeEquations.push(planeEquation[j]);
}
}
cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
cellBO.getProgram().setUniform4fv('clipPlanes', planeEquations);
}
};
publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => {
const program = cellBO.getProgram();
const actMats = model.openGLImageSlice.getKeyMatrices();
const image = model.currentInput;
const i2wmat4 = image.getIndexToWorld();
mat4.multiply(model.imagemat, actMats.mcwc, i2wmat4);
const keyMats = model.openGLCamera.getKeyMatrices(ren);
mat4.multiply(model.imagemat, keyMats.wcpc, model.imagemat);
if (cellBO.getCABO().getCoordShiftAndScaleEnabled()) {
const inverseShiftScaleMat = cellBO.getCABO().getInverseShiftAndScaleMatrix();
mat4.multiply(model.imagemat, model.imagemat, inverseShiftScaleMat);
}
program.setUniformMatrix('MCPCMatrix', model.imagemat);
const vtkImageLabelOutline = actor.getProperty().getUseLabelOutline();
if (vtkImageLabelOutline === true) {
const worldToIndex = image.getWorldToIndex();
const imageDimensions = image.getDimensions();
let sliceAxis = model.renderable.getClosestIJKAxis().ijkMode;
// SlicingMode.NONE equates to SlicingMode.K
if (sliceAxis === SlicingMode.NONE) {
sliceAxis = SlicingMode.K;
}
program.setUniform3i('imageDimensions', imageDimensions[0], imageDimensions[1], imageDimensions[2]);
program.setUniformi('sliceAxis', sliceAxis);
program.setUniformMatrix('vWCtoIDX', worldToIndex);
const labelOutlineKeyMats = model.openGLCamera.getKeyMatrices(ren);
// Get the projection coordinate to world coordinate transformation matrix.
mat4.invert(model.projectionToWorld, labelOutlineKeyMats.wcpc);
model.openGLCamera.getKeyMatrices(ren);
program.setUniformMatrix('PCWCMatrix', model.projectionToWorld);
const size = publicAPI.getRenderTargetSize();
program.setUniformf('vpWidth', size[0]);
program.setUniformf('vpHeight', size[1]);
const offset = publicAPI.getRenderTargetOffset();
program.setUniformf('vpOffsetX', offset[0] / size[0]);
program.setUniformf('vpOffsetY', offset[1] / size[1]);
}
};
publicAPI.setPropertyShaderParameters = (cellBO, ren, actor) => {
const program = cellBO.getProgram();
const ppty = actor.getProperty();
const opacity = ppty.getOpacity();
program.setUniformf('opacity', opacity);
};
publicAPI.renderPieceStart = (ren, actor) => {
// make sure the BOs are up to date
publicAPI.updateBufferObjects(ren, actor);
// Bind the OpenGL, this is shared between the different primitive/cell types.
model.lastBoundBO = null;
};
publicAPI.renderPieceDraw = (ren, actor) => {
const gl = model.context;
// activate the texture
model.openGLTexture.activate();
model.colorTexture.activate();
if (actor.getProperty().getUseLabelOutline()) {
model.labelOutlineThicknessTexture.activate();
model.labelOutlineOpacityTexture.activate();
}
model.pwfTexture.activate();
// draw polygons
if (model.tris.getCABO().getElementCount()) {
// First we do the triangles, update the shader, set uniforms, etc.
publicAPI.updateShaders(model.tris, ren, actor);
gl.drawArrays(gl.TRIANGLES, 0, model.tris.getCABO().getElementCount());
model.tris.getVAO().release();
}
model.openGLTexture.deactivate();
model.colorTexture.deactivate();
if (actor.getProperty().getUseLabelOutline()) {
model.labelOutlineThicknessTexture.deactivate();
model.labelOutlineOpacityTexture.deactivate();
}
model.pwfTexture.deactivate();
};
publicAPI.renderPieceFinish = (ren, actor) => {};
publicAPI.renderPiece = (ren, actor) => {
// Make sure that we have been properly initialized.
// if (ren.getRenderWindow().checkAbortStatus()) {
// return;
// }
publicAPI.invokeEvent({
type: 'StartEvent'
});
model.renderable.update();
model.currentInput = model.renderable.getCurrentImage();
publicAPI.invokeEvent({
type: 'EndEvent'
});
if (!model.currentInput) {
vtkErrorMacro('No input!');
return;
}
publicAPI.renderPieceStart(ren, actor);
publicAPI.renderPieceDraw(ren, actor);
publicAPI.renderPieceFinish(ren, actor);
};
publicAPI.updateBufferObjects = (ren, actor) => {
// Rebuild buffers if needed
if (publicAPI.getNeedToRebuildBufferObjects(ren, actor)) {
publicAPI.buildBufferObjects(ren, actor);
}
};
publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => model.VBOBuildTime.getMTime() < publicAPI.getMTime() || model.VBOBuildTime.getMTime() < actor.getMTime() || model.VBOBuildTime.getMTime() < model.renderable.getMTime() || model.VBOBuildTime.getMTime() < actor.getProperty().getMTime() || model.VBOBuildTime.getMTime() < model.currentInput.getMTime() || !model.openGLTexture?.getHandle() || !model.colorTexture?.getHandle() || actor.getProperty().getUseLabelOutline() && (!model.labelOutlineThicknessTexture?.getHandle() || !model.labelOutlineOpacityTexture?.getHandle()) || !model.pwfTexture?.getHandle();
publicAPI.buildBufferObjects = (ren, actor) => {
const image = model.currentInput;
if (!image) {
return;
}
const imgScalars = image.getPointData() && image.getPointData().getScalars();
if (!imgScalars) {
return;
}
const dataType = imgScalars.getDataType();
const numComp = imgScalars.getNumberOfComponents();
const actorProperty = actor.getProperty();
const iType = actorProperty.getInterpolationType();
const iComps = actorProperty.getIndependentComponents();
const numIComps = iComps ? numComp : 1;
const textureHeight = iComps ? 2 * numIComps : 1;
const colorTransferFunctions = [];
for (let component = 0; component < numIComps; ++component) {
colorTransferFunctions.push(actorProperty.getRGBTransferFunction(component));
}
const cfunToString = getTransferFunctionsHash(colorTransferFunctions, iComps, numIComps);
const firstColorTransferFunc = actorProperty.getRGBTransferFunction();
const cTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstColorTransferFunc);
const reBuildC = !cTex?.oglObject?.getHandle() || cTex?.hash !== cfunToString;
if (reBuildC) {
model.colorTexture = vtkOpenGLTexture.newInstance({
resizable: true
});
model.colorTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
let cWidth = model.renderable.getColorTextureWidth();
if (cWidth <= 0) {
cWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
}
const cSize = cWidth * textureHeight * 3;
const cTable = new Uint8ClampedArray(cSize);
// set interpolation on the texture based on property setting
if (iType === InterpolationType.NEAREST) {
model.colorTexture.setMinificationFilter(Filter.NEAREST);
model.colorTexture.setMagnificationFilter(Filter.NEAREST);
} else {
model.colorTexture.setMinificationFilter(Filter.LINEAR);
model.colorTexture.setMagnificationFilter(Filter.LINEAR);
}
if (firstColorTransferFunc) {
const tmpTable = new Float32Array(cWidth * 3);
for (let c = 0; c < numIComps; c++) {
const cfun = actorProperty.getRGBTransferFunction(c);
const cRange = cfun.getRange();
cfun.getTable(cRange[0], cRange[1], cWidth, tmpTable, 1);
if (iComps) {
for (let i = 0; i < cWidth * 3; i++) {
cTable[c * cWidth * 6 + i] = 255.0 * tmpTable[i];
cTable[c * cWidth * 6 + i + cWidth * 3] = 255.0 * tmpTable[i];
}
} else {
for (let i = 0; i < cWidth * 3; i++) {
cTable[c * cWidth * 6 + i] = 255.0 * tmpTable[i];
}
}
}
model.colorTexture.resetFormatAndType();
model.colorTexture.create2DFromRaw({
width: cWidth,
height: textureHeight,
numComps: 3,
dataType: VtkDataTypes.UNSIGNED_CHAR,
data: cTable
});
} else {
for (let i = 0; i < cWidth * 3; ++i) {
cTable[i] = 255.0 * i / ((cWidth - 1) * 3);
cTable[i + 1] = 255.0 * i / ((cWidth - 1) * 3);
cTable[i + 2] = 255.0 * i / ((cWidth - 1) * 3);
}
model.colorTexture.create2DFromRaw({
width: cWidth,
height: 1,
numComps: 3,
dataType: VtkDataTypes.UNSIGNED_CHAR,
data: cTable
});
}
if (firstColorTransferFunc) {
model._openGLRenderWindow.setGraphicsResourceForObject(firstColorTransferFunc, model.colorTexture, cfunToString);
if (firstColorTransferFunc !== model._colorTransferFunc) {
model._openGLRenderWindow.registerGraphicsResourceUser(firstColorTransferFunc, publicAPI);
model._openGLRenderWindow.unregisterGraphicsResourceUser(model._colorTransferFunc, publicAPI);
}
model._colorTransferFunc = firstColorTransferFunc;
}
} else {
model.colorTexture = cTex.oglObject;
}
// Build piecewise function buffer. This buffer is used either
// for component weighting or opacity, depending on whether we're
// rendering components independently or not.
const opacityFunctions = [];
for (let component = 0; component < numIComps; ++component) {
opacityFunctions.push(actorProperty.getPiecewiseFunction(component));
}
const pwfunToString = getTransferFunctionsHash(opacityFunctions, iComps, numIComps);
const firstPwFunc = actorProperty.getPiecewiseFunction();
const pwfTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstPwFunc);
// rebuild opacity tfun?
const reBuildPwf = !pwfTex?.oglObject?.getHandle() || pwfTex?.hash !== pwfunToString;
if (reBuildPwf) {
let pwfWidth = model.renderable.getOpacityTextureWidth();
if (pwfWidth <= 0) {
pwfWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
}
const pwfSize = pwfWidth * textureHeight;
const pwfTable = new Uint8ClampedArray(pwfSize);
model.pwfTexture = vtkOpenGLTexture.newInstance({
resizable: true
});
model.pwfTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
// set interpolation on the texture based on property setting
if (iType === InterpolationType.NEAREST) {
model.pwfTexture.setMinificationFilter(Filter.NEAREST);
model.pwfTexture.setMagnificationFilter(Filter.NEAREST);
} else {
model.pwfTexture.setMinificationFilter(Filter.LINEAR);
model.pwfTexture.setMagnificationFilter(Filter.LINEAR);
}
if (firstPwFunc) {
const pwfFloatTable = new Float32Array(pwfSize);
const tmpTable = new Float32Array(pwfWidth);
for (let c = 0; c < numIComps; ++c) {
const pwfun = actorProperty.getPiecewiseFunction(c);
if (pwfun === null) {
// Piecewise constant max if no function supplied for this component
pwfFloatTable.fill(1.0);
} else {
const pwfRange = pwfun.getRange();
pwfun.getTable(pwfRange[0], pwfRange[1], pwfWidth, tmpTable, 1);
// adjust for sample distance etc
if (iComps) {
for (let i = 0; i < pwfWidth; i++) {
pwfFloatTable[c * pwfWidth * 2 + i] = tmpTable[i];
pwfFloatTable[c * pwfWidth * 2 + i + pwfWidth] = tmpTable[i];
}
} else {
for (let i = 0; i < pwfWidth; i++) {
pwfFloatTable[c * pwfWidth * 2 + i] = tmpTable[i];
}
}
}
}
model.pwfTexture.resetFormatAndType();
model.pwfTexture.create2DFromRaw({
width: pwfWidth,
height: textureHeight,
numComps: 1,
dataType: VtkDataTypes.FLOAT,
data: pwfFloatTable
});
} else {
// default is opaque
pwfTable.fill(255.0);
model.pwfTexture.create2DFromRaw({
width: pwfWidth,
height: 1,
numComps: 1,
dataType: VtkDataTypes.UNSIGNED_CHAR,
data: pwfTable
});
}
if (firstPwFunc) {
model._openGLRenderWindow.setGraphicsResourceForObject(firstPwFunc, model.pwfTexture, pwfunToString);
if (firstPwFunc !== model._pwFunc) {
model._openGLRenderWindow.registerGraphicsResourceUser(firstPwFunc, publicAPI);
model._openGLRenderWindow.unregisterGraphicsResourceUser(model._pwFunc, publicAPI);
}
model._pwFunc = firstPwFunc;
}
} else {
model.pwfTexture = pwfTex.oglObject;
}
if (actor.getProperty().getUseLabelOutline()) {
// Build outline thickness + opacity buffers
publicAPI.updatelabelOutlineThicknessTexture(actor);
publicAPI.updateLabelOutlineOpacityTexture(actor);
}
// Find what IJK axis and what direction to slice along
const {
ijkMode
} = model.renderable.getClosestIJKAxis();
// Find the IJK slice
let slice = model.renderable.getSlice();
if (ijkMode !== model.renderable.getSlicingMode()) {
// If not IJK slicing, get the IJK slice from the XYZ position/slice
slice = model.renderable.getSliceAtPosition(slice);
}
// Use sub-Slice number/offset if mapper being used is vtkImageArrayMapper,
// since this mapper uses a collection of vtkImageData (and not just a single vtkImageData).
const nSlice = model.renderable.isA('vtkImageArrayMapper') ? model.renderable.getSubSlice() // get subSlice of the current (possibly multi-frame) image
: Math.round(slice);
// Find sliceOffset
const ext = image.getExtent();
let sliceOffset;
if (ijkMode === SlicingMode.I) {
sliceOffset = nSlice - ext[0];
}
if (ijkMode === SlicingMode.J) {
sliceOffset = nSlice - ext[2];
}
if (ijkMode === SlicingMode.K || ijkMode === SlicingMode.NONE) {
sliceOffset = nSlice - ext[4];
}
// rebuild the VBO if the data has changed
const toString = `${slice}A${image.getMTime()}A${imgScalars.getMTime()}B${publicAPI.getMTime()}C${model.renderable.getSlicingMode()}D${actor.getProperty().getInterpolationType()}`;
if (model.VBOBuildString !== toString) {
// Build the VBOs
const dims = image.getDimensions();
if (!model.openGLTexture) {
model.openGLTexture = vtkOpenGLTexture.newInstance({
resizable: true
});
}
model.openGLTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
// Use norm16 for scalar texture if the extension is available
model.openGLTexture.setOglNorm16Ext(model.context.getExtension('EXT_texture_norm16'));
if (iType === InterpolationType.NEAREST) {
if (new Set([1, 3, 4]).has(numComp) && dataType === VtkDataTypes.UNSIGNED_CHAR && !iComps) {
model.openGLTexture.setGenerateMipmap(true);
model.openGLTexture.setMinificationFilter(Filter.NEAREST);
} else {
model.openGLTexture.setMinificationFilter(Filter.NEAREST);
}
model.openGLTexture.setMagnificationFilter(Filter.NEAREST);
} else {
if (numComp === 4 && dataType === VtkDataTypes.UNSIGNED_CHAR && !iComps) {
model.openGLTexture.setGenerateMipmap(true);
model.openGLTexture.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
} else {
model.openGLTexture.setMinificationFilter(Filter.LINEAR);
}
model.openGLTexture.setMagnificationFilter(Filter.LINEAR);
}
model.openGLTexture.setWrapS(Wrap.CLAMP_TO_EDGE);
model.openGLTexture.setWrapT(Wrap.CLAMP_TO_EDGE);
const sliceSize = dims[0] * dims[1] * numComp;
const ptsArray = new Float32Array(12);
const tcoordArray = new Float32Array(8);
for (let i = 0; i < 4; i++) {
tcoordArray[i * 2] = i % 2 ? 1.0 : 0.0;
tcoordArray[i * 2 + 1] = i > 1 ? 1.0 : 0.0;
}
// Determine depth position of the slicing plane in the scene.
// Slicing modes X, Y, and Z use a continuous axis position, whereas
// slicing modes I, J, and K should use discrete positions.
const sliceDepth = [SlicingMode.X, SlicingMode.Y, SlicingMode.Z].includes(model.renderable.getSlicingMode()) ? slice : nSlice;
const spatialExt = image.getSpatialExtent();
const basicScalars = imgScalars.getData();
let scalars = null;
// Get right scalars according to slicing mode
if (ijkMode === SlicingMode.I) {
scalars = new basicScalars.constructor(dims[2] * dims[1] * numComp);
let id = 0;
for (let k = 0; k < dims[2]; k++) {
for (let j = 0; j < dims[1]; j++) {
let bsIdx = (sliceOffset + j * dims[0] + k * dims[0] * dims[1]) * numComp;
id = (k * dims[1] + j) * numComp;
const end = bsIdx + numComp;
while (bsIdx < end) {
scalars[id++] = basicScalars[bsIdx++];
}
}
}
dims[0] = dims[1];
dims[1] = dims[2];
ptsArray[0] = sliceDepth;
ptsArray[1] = spatialExt[2];
ptsArray[2] = spatialExt[4];
ptsArray[3] = sliceDepth;
ptsArray[4] = spatialExt[3];
ptsArray[5] = spatialExt[4];
ptsArray[6] = sliceDepth;
ptsArray[7] = spatialExt[2];
ptsArray[8] = spatialExt[5];
ptsArray[9] = sliceDepth;
ptsArray[10] = spatialExt[3];
ptsArray[11] = spatialExt[5];
} else if (ijkMode === SlicingMode.J) {
scalars = new basicScalars.constructor(dims[2] * dims[0] * numComp);
let id = 0;
for (let k = 0; k < dims[2]; k++) {
for (let i = 0; i < dims[0]; i++) {
let bsIdx = (i + sliceOffset * dims[0] + k * dims[0] * dims[1]) * numComp;
id = (k * dims[0] + i) * numComp;
const end = bsIdx + numComp;
while (bsIdx < end) {
scalars[id++] = basicScalars[bsIdx++];
}
}
}
dims[1] = dims[2];
ptsArray[0] = spatialExt[0];
ptsArray[1] = sliceDepth;
ptsArray[2] = spatialExt[4];
ptsArray[3] = spatialExt[1];
ptsArray[4] = sliceDepth;
ptsArray[5] = spatialExt[4];
ptsArray[6] = spatialExt[0];
ptsArray[7] = sliceDepth;
ptsArray[8] = spatialExt[5];
ptsArray[9] = spatialExt[1];
ptsArray[10] = sliceDepth;
ptsArray[11] = spatialExt[5];
} else if (ijkMode === SlicingMode.K || ijkMode === SlicingMode.NONE) {
scalars = basicScalars.subarray(sliceOffset * sliceSize, (sliceOffset + 1) * sliceSize);
ptsArray[0] = spatialExt[0];
ptsArray[1] = spatialExt[2];
ptsArray[2] = sliceDepth;
ptsArray[3] = spatialExt[1];
ptsArray[4] = spatialExt[2];
ptsArray[5] = sliceDepth;
ptsArray[6] = spatialExt[0];
ptsArray[7] = spatialExt[3];
ptsArray[8] = sliceDepth;
ptsArray[9] = spatialExt[1];
ptsArray[10] = spatialExt[3];
ptsArray[11] = sliceDepth;
} else {
vtkErrorMacro('Reformat slicing not yet supported.');
}
/**
*
* Fetch the ranges of the source volume, `imgScalars`, and use them when
* creating the texture. Whilst the pre-calculated ranges may not be
* strictly correct for the slice, it is guaranteed to be within the
* source volume's range.
*
* There is a significant performance improvement by pre-setting the range
* of the scalars array particularly when scrolling through the source
* volume as there is no need to calculate the range of the slice scalar.
*
* @type{ import("../../../interfaces").vtkRange[] }
*/
const ranges = imgScalars.getRanges();
// Don't share this resource as `scalars` is created in this function
// so it is impossible to share
model.openGLTexture.resetFormatAndType();
model.openGLTexture.create2DFilterableFromRaw({
width: dims[0],
height: dims[1],
numComps: numComp,
dataType: imgScalars.getDataType(),
data: scalars,
preferSizeOverAccuracy: !!model.renderable.getPreferSizeOverAccuracy?.(),
ranges
});
model.openGLTexture.activate();
model.openGLTexture.sendParameters();
model.openGLTexture.deactivate();
const points = vtkDataArray.newInstance({
numberOfComponents: 3,
values: ptsArray
});
points.setName('points');
const tcoords = vtkDataArray.newInstance({
numberOfComponents: 2,
values: tcoordArray
});
tcoords.setName('tcoords');
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 cells = vtkDataArray.newInstance({
numberOfComponents: 1,
values: cellArray
});
model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, {
points,
tcoords,
cellOffset: 0
});
model.VBOBuildTime.modified();
model.VBOBuildString = toString;
}
};
publicAPI.updateLabelOutlineOpacityTexture = image => {
let labelOutlineOpacity = image.getProperty().getLabelOutlineOpacity();
// when the labelOutlineOpacity is a number, we use _cachedLabelOutlineOpacityObj
// as a stable object reference for `[labelOutlineOpacity]`.
if (typeof labelOutlineOpacity === 'number') {
if (model._cachedLabelOutlineOpacityObj?.[0] === labelOutlineOpacity) {
labelOutlineOpacity = model._cachedLabelOutlineOpacityObj;
} else {
labelOutlineOpacity = [labelOutlineOpacity];
}
model._cachedLabelOutlineOpacityObj = labelOutlineOpacity;
}
const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutlineOpacity);
const toString = `${labelOutlineOpacity.join('-')}`;
const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== toString;
if (reBuildL) {
let lWidth = model.renderable.getLabelOutlineTextureWidth();
if (lWidth <= 0) {
lWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
}
const lHeight = 1;
const lSize = lWidth * lHeight;
const lTable = new Float32Array(lSize);
for (let i = 0; i < lWidth; ++i) {
// Retrieve the opacity value for the current segment index.
// If the value is undefined, use the first element's value as a default, otherwise use the value (even if 0)
lTable[i] = labelOutlineOpacity[i] ?? labelOutlineOpacity[0];
}
model.labelOutlineOpacityTexture = vtkOpenGLTexture.newInstance({
resizable: false
});
model.labelOutlineOpacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
model.labelOutlineOpacityTexture.resetFormatAndType();
model.labelOutlineOpacityTexture.setMinificationFilter(Filter.NEAREST);
model.labelOutlineOpacityTexture.setMagnificationFilter(Filter.NEAREST);
// Create a 2D texture (acting as 1D) from the raw data
model.labelOutlineOpacityTexture.create2DFromRaw({
width: lWidth,
height: lHeight,
numComps: 1,
dataType: VtkDataTypes.FLOAT,
data: lTable
});
if (labelOutlineOpacity) {
model._openGLRenderWindow.setGraphicsResourceForObject(labelOutlineOpacity, model.labelOutlineOpacityTexture, toString);
if (labelOutlineOpacity !== model._labelOutlineOpacity) {
model._openGLRenderWindow.registerGraphicsResourceUser(labelOutlineOpacity, publicAPI);
model._openGLRenderWindow.unregisterGraphicsResourceUser(model._labelOutlineOpacity, publicAPI);
}
model._labelOutlineOpacity = labelOutlineOpacity;
}
} else {
model.labelOutlineOpacityTexture = lTex.oglObject;
}
};
publicAPI.updatelabelOutlineThicknessTexture = image => {
const labelOutlineThicknessArray = image.getProperty().getLabelOutlineThicknessByReference();
const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutl