import * as macro from 'vtk.js/Sources/macros'; import DeepEqual from 'fast-deep-equal'; import { vec3, mat3, mat4 } from 'gl-matrix';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants'; import vtkHelper from 'vtk.js/Sources/Rendering/OpenGL/Helper'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import vtkOpenGLFramebuffer from 'vtk.js/Sources/Rendering/OpenGL/Framebuffer'; import vtkOpenGLTexture from 'vtk.js/Sources/Rendering/OpenGL/Texture'; import vtkReplacementShaderMapper from 'vtk.js/Sources/Rendering/OpenGL/ReplacementShaderMapper'; import vtkShaderProgram from 'vtk.js/Sources/Rendering/OpenGL/ShaderProgram'; import vtkVertexArrayObject from 'vtk.js/Sources/Rendering/OpenGL/VertexArrayObject'; import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode'; import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants'; import { Wrap, Filter, } from 'vtk.js/Sources/Rendering/OpenGL/Texture/Constants'; import { InterpolationType, OpacityMode, ColorMixPreset, } from 'vtk.js/Sources/Rendering/Core/VolumeProperty/Constants'; import { BlendMode } from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants';
import { getTransferFunctionHash, getImageDataHash, } from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow/resourceSharingHelper';
import vtkVolumeVS from 'vtk.js/Sources/Rendering/OpenGL/glsl/vtkVolumeVS.glsl'; import vtkVolumeFS from 'vtk.js/Sources/Rendering/OpenGL/glsl/vtkVolumeFS.glsl';
import { registerOverride } from 'vtk.js/Sources/Rendering/OpenGL/ViewNodeFactory';
const { vtkWarningMacro, vtkErrorMacro } = macro;
function getColorCodeFromPreset(colorMixPreset) { switch (colorMixPreset) { case ColorMixPreset.CUSTOM: return '//VTK::CustomColorMix'; case ColorMixPreset.ADDITIVE: return ` // compute normals mat4 normalMat = computeMat4Normal(posIS, tValue, tstep); #if (vtkLightComplexity > 0) && defined(vtkComputeNormalFromOpacity) vec3 scalarInterp0[2]; vec4 normalLight0 = computeNormalForDensity(posIS, tstep, scalarInterp0, 0); scalarInterp0[0] = scalarInterp0[0] * oscale0 + oshift0; scalarInterp0[1] = scalarInterp0[1] * oscale0 + oshift0; normalLight0 = computeDensityNormal(scalarInterp0, height0, 1.0);
vec3 scalarInterp1[2]; vec4 normalLight1 = computeNormalForDensity(posIS, tstep, scalarInterp1, 1); scalarInterp1[0] = scalarInterp1[0] * oscale1 + oshift1; scalarInterp1[1] = scalarInterp1[1] * oscale1 + oshift1; normalLight1 = computeDensityNormal(scalarInterp1, height1, 1.0); #else vec4 normalLight0 = normalMat[0]; vec4 normalLight1 = normalMat[1]; #endif
// compute opacities float opacity0 = pwfValue0; float opacity1 = pwfValue1; #ifdef vtkGradientOpacityOn float gof0 = computeGradientOpacityFactor(normalMat[0].a, goscale0, goshift0, gomin0, gomax0); opacity0 *= gof0; float gof1 = computeGradientOpacityFactor(normalMat[1].a, goscale1, goshift1, gomin1, gomax1); opacity1 *= gof1; #endif float opacitySum = opacity0 + opacity1; if (opacitySum <= 0.0) { return vec4(0.0); }
// mix the colors and opacities tColor0 = applyAllLightning(tColor0, opacity0, posIS, normalLight0); tColor1 = applyAllLightning(tColor1, opacity1, posIS, normalLight1); vec3 mixedColor = (opacity0 * tColor0 + opacity1 * tColor1) / opacitySum; return vec4(mixedColor, min(1.0, opacitySum)); `; case ColorMixPreset.COLORIZE: return ` // compute normals mat4 normalMat = computeMat4Normal(posIS, tValue, tstep); #if (vtkLightComplexity > 0) && defined(vtkComputeNormalFromOpacity) vec3 scalarInterp0[2]; vec4 normalLight0 = computeNormalForDensity(posIS, tstep, scalarInterp0, 0); scalarInterp0[0] = scalarInterp0[0] * oscale0 + oshift0; scalarInterp0[1] = scalarInterp0[1] * oscale0 + oshift0; normalLight0 = computeDensityNormal(scalarInterp0, height0, 1.0); #else vec4 normalLight0 = normalMat[0]; #endif
// compute opacities float opacity0 = pwfValue0; #ifdef vtkGradientOpacityOn float gof0 = computeGradientOpacityFactor(normalMat[0].a, goscale0, goshift0, gomin0, gomax0); opacity0 *= gof0; #endif
// mix the colors and opacities vec3 color = tColor0 * mix(vec3(1.0), tColor1, pwfValue1); color = applyAllLightning(color, opacity0, posIS, normalLight0); return vec4(color, opacity0); `; default: return null; } }
function vtkOpenGLVolumeMapper(publicAPI, model) { model.classHierarchy.push('vtkOpenGLVolumeMapper');
function unregisterGraphicsResources(renderWindow) { [ model._scalars, model._scalarOpacityFunc, model._colorTransferFunc, model._labelOutlineThicknessArray, ].forEach((coreObject) => renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI) ); }
publicAPI.buildPass = () => { model.zBufferTexture = null; };
publicAPI.zBufferPass = (prepass, renderPass) => { if (prepass) { const zbt = renderPass.getZBufferTexture(); if (zbt !== model.zBufferTexture) { model.zBufferTexture = zbt; } } };
publicAPI.opaqueZBufferPass = (prepass, renderPass) => publicAPI.zBufferPass(prepass, renderPass);
publicAPI.volumePass = (prepass, renderPass) => { if (prepass) { const oldOglRenderWindow = model._openGLRenderWindow; model._openGLRenderWindow = publicAPI.getLastAncestorOfType( 'vtkOpenGLRenderWindow' ); if ( oldOglRenderWindow && !oldOglRenderWindow.isDeleted() && oldOglRenderWindow !== model._openGLRenderWindow ) { unregisterGraphicsResources(oldOglRenderWindow); } model.context = model._openGLRenderWindow.getContext(); model.tris.setOpenGLRenderWindow(model._openGLRenderWindow); model.jitterTexture.setOpenGLRenderWindow(model._openGLRenderWindow); model.framebuffer.setOpenGLRenderWindow(model._openGLRenderWindow);
model.openGLVolume = publicAPI.getFirstAncestorOfType('vtkOpenGLVolume'); const actor = model.openGLVolume.getRenderable(); model._openGLRenderer = publicAPI.getFirstAncestorOfType('vtkOpenGLRenderer'); const ren = model._openGLRenderer.getRenderable(); model.openGLCamera = model._openGLRenderer.getViewNodeFor( ren.getActiveCamera() ); publicAPI.renderPiece(ren, actor); } };
publicAPI.getShaderTemplate = (shaders, ren, actor) => { shaders.Vertex = vtkVolumeVS; shaders.Fragment = vtkVolumeFS; shaders.Geometry = ''; };
publicAPI.useIndependentComponents = (actorProperty) => { const iComps = actorProperty.getIndependentComponents(); const image = model.currentInput; const numComp = image ?.getPointData() ?.getScalars() ?.getNumberOfComponents(); const colorMixPreset = actorProperty.getColorMixPreset(); return (iComps && numComp >= 2) || !!colorMixPreset; };
publicAPI.replaceShaderValues = (shaders, ren, actor) => { const actorProps = actor.getProperty(); let FSSource = shaders.Fragment;
const iType = actorProps.getInterpolationType(); if (iType === InterpolationType.LINEAR) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::TrilinearOn', '#define vtkTrilinearOn' ).result; }
const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor); if (vtkImageLabelOutline === true) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::ImageLabelOutlineOn', '#define vtkImageLabelOutlineOn' ).result; }
const LabelEdgeProjection = model.renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND;
if (LabelEdgeProjection) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::LabelEdgeProjectionOn', '#define vtkLabelEdgeProjectionOn' ).result; }
const numComp = model.scalarTexture.getComponents(); FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::NumComponents', `#define vtkNumComponents ${numComp}` ).result;
const useIndependentComps = publicAPI.useIndependentComponents(actorProps); if (useIndependentComps) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::IndependentComponentsOn', '#define UseIndependentComponents' ).result; }
const proportionalComponents = []; const forceNearestComponents = []; for (let nc = 0; nc < numComp; nc++) { if (actorProps.getOpacityMode(nc) === OpacityMode.PROPORTIONAL) { proportionalComponents.push(`#define vtkComponent${nc}Proportional`); } if (actorProps.getForceNearestInterpolation(nc)) { forceNearestComponents.push(`#define vtkComponent${nc}ForceNearest`); } }
FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::vtkProportionalComponents', proportionalComponents.join('\n') ).result;
FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::vtkForceNearestComponents', forceNearestComponents.join('\n') ).result;
const colorMixPreset = actorProps.getColorMixPreset(); const colorMixCode = getColorCodeFromPreset(colorMixPreset); if (colorMixCode) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::CustomComponentsColorMixOn', '#define vtkCustomComponentsColorMix' ).result; FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::CustomComponentsColorMix::Impl', colorMixCode ).result; }
const ext = model.currentInput.getSpatialExtent(); const spc = model.currentInput.getSpacing(); const vsize = new Float64Array(3); vec3.set( vsize, (ext[1] - ext[0]) * spc[0], (ext[3] - ext[2]) * spc[1], (ext[5] - ext[4]) * spc[2] );
const maxSamples = vec3.length(vsize) / publicAPI.getCurrentSampleDistance(ren);
FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::MaximumSamplesValue', `${Math.ceil(maxSamples)}` ).result;
FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::LightComplexity', `#define vtkLightComplexity ${model.lightComplexity}` ).result;
if (model.lightComplexity > 0) { if (model.renderable.getVolumetricScatteringBlending() > 0.0) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::VolumeShadowOn', `#define VolumeShadowOn` ).result; } if (model.renderable.getVolumetricScatteringBlending() < 1.0) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::SurfaceShadowOn', `#define SurfaceShadowOn` ).result; } if ( model.renderable.getLocalAmbientOcclusion() && actorProps.getAmbient() > 0.0 ) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::localAmbientOcclusionOn', `#define localAmbientOcclusionOn` ).result; } }
const numIComps = useIndependentComps ? numComp : 1; model.gopacity = false; for (let nc = 0; !model.gopacity && nc < numIComps; ++nc) { model.gopacity ||= actorProps.getUseGradientOpacity(nc); } if (model.gopacity) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::GradientOpacityOn', '#define vtkGradientOpacityOn' ).result; }
if (model.renderable.getComputeNormalFromOpacity()) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::vtkComputeNormalFromOpacity', `#define vtkComputeNormalFromOpacity` ).result; }
if (model.zBufferTexture !== null) { FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Dec', [ 'uniform sampler2D zBufferTexture;', 'uniform float vpZWidth;', 'uniform float vpZHeight;', ]).result; FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Impl', [ 'vec4 depthVec = texture2D(zBufferTexture, vec2(gl_FragCoord.x / vpZWidth, gl_FragCoord.y/vpZHeight));', 'float zdepth = (depthVec.r*256.0 + depthVec.g)/257.0;', 'zdepth = zdepth * 2.0 - 1.0;', 'if (cameraParallel == 0) {', 'zdepth = -2.0 * camFar * camNear / (zdepth*(camFar-camNear)-(camFar+camNear)) - camNear;}', 'else {', 'zdepth = (zdepth + 1.0) * 0.5 * (camFar - camNear);}\n', 'zdepth = -zdepth/rayDir.z;', 'dists.y = min(zdepth,dists.y);', ]).result; }
FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::BlendMode', `${model.renderable.getBlendMode()}` ).result;
shaders.Fragment = FSSource;
publicAPI.replaceShaderLight(shaders, ren, actor); publicAPI.replaceShaderClippingPlane(shaders, ren, actor); };
publicAPI.replaceShaderLight = (shaders, ren, actor) => { if (model.lightComplexity === 0) { return; } let FSSource = shaders.Fragment;
let lightNum = 0; ren.getLights().forEach((light) => { if (light.getSwitch()) { lightNum += 1; } }); FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::Light::Dec', [ `uniform int lightNum;`, `uniform bool twoSidedLighting;`, `uniform vec3 lightColor[${lightNum}];`, `uniform vec3 lightDirectionVC[${lightNum}]; // normalized`, `uniform vec3 lightHalfAngleVC[${lightNum}];`, '//VTK::Light::Dec', ], false ).result; if (model.lightComplexity === 3) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::Light::Dec', [ `uniform vec3 lightPositionVC[${lightNum}];`, `uniform vec3 lightAttenuation[${lightNum}];`, `uniform float lightConeAngle[${lightNum}];`, `uniform float lightExponent[${lightNum}];`, `uniform int lightPositional[${lightNum}];`, ], false ).result; }
if (model.renderable.getVolumetricScatteringBlending() > 0.0) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::VolumeShadow::Dec', [ `uniform float volumetricScatteringBlending;`, `uniform float giReach;`, `uniform float volumeShadowSamplingDistFactor;`, `uniform float anisotropy;`, `uniform float anisotropy2;`, ], false ).result; } if ( model.renderable.getLocalAmbientOcclusion() && actor.getProperty().getAmbient() > 0.0 ) { FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::LAO::Dec', [ `uniform int kernelRadius;`, `uniform vec2 kernelSample[${model.renderable.getLAOKernelRadius()}];`, `uniform int kernelSize;`, ], false ).result; } shaders.Fragment = FSSource; };
publicAPI.replaceShaderClippingPlane = (shaders, ren, actor) => { let FSSource = shaders.Fragment;
if (model.renderable.getClippingPlanes().length > 0) { const clipPlaneSize = model.renderable.getClippingPlanes().length; FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::ClipPlane::Dec', [ `uniform vec3 vClipPlaneNormals[6];`, `uniform float vClipPlaneDistances[6];`, `uniform vec3 vClipPlaneOrigins[6];`, `uniform int clip_numPlanes;`, '//VTK::ClipPlane::Dec', '#define vtkClippingPlanesOn', ], false ).result;
FSSource = vtkShaderProgram.substitute( FSSource, '//VTK::ClipPlane::Impl', [ `for(int i = 0; i < ${clipPlaneSize}; i++) {`, ' float rayDirRatio = dot(rayDir, vClipPlaneNormals[i]);', ' float equationResult = dot(vertexVCVSOutput, vClipPlaneNormals[i]) + vClipPlaneDistances[i];', ' if (rayDirRatio == 0.0)', ' {', ' if (equationResult < 0.0) dists.x = dists.y;', ' continue;', ' }', ' float result = -1.0 * equationResult / rayDirRatio;', ' if (rayDirRatio < 0.0) dists.y = min(dists.y, result);', ' else dists.x = max(dists.x, result);', '}', '//VTK::ClipPlane::Impl', ], false ).result; }
shaders.Fragment = FSSource; };
const recomputeLightComplexity = (actor, lights) => { let lightComplexity = 0; if ( actor.getProperty().getShade() && model.renderable.getBlendMode() === BlendMode.COMPOSITE_BLEND ) { lightComplexity = 0; model.numberOfLights = 0;
lights.forEach((light) => { const status = light.getSwitch(); if (status > 0) { model.numberOfLights++; if (lightComplexity === 0) { lightComplexity = 1; } }
if ( lightComplexity === 1 && (model.numberOfLights > 1 || light.getIntensity() !== 1.0 || !light.lightTypeIsHeadLight()) ) { lightComplexity = 2; } if (lightComplexity < 3 && light.getPositional()) { lightComplexity = 3; } }); } if (lightComplexity !== model.lightComplexity) { model.lightComplexity = lightComplexity; publicAPI.modified(); } };
publicAPI.getNeedToRebuildShaders = (cellBO, ren, actor) => { const actorProps = actor.getProperty();
recomputeLightComplexity(actor, ren.getLights());
const numComp = model.scalarTexture.getComponents(); const opacityModes = []; const forceNearestInterps = []; for (let nc = 0; nc < numComp; nc++) { opacityModes.push(actorProps.getOpacityMode(nc)); forceNearestInterps.push(actorProps.getForceNearestInterpolation(nc)); }
const ext = model.currentInput.getSpatialExtent(); const spc = model.currentInput.getSpacing(); const vsize = new Float64Array(3); vec3.set( vsize, (ext[1] - ext[0]) * spc[0], (ext[3] - ext[2]) * spc[1], (ext[5] - ext[4]) * spc[2] );
const maxSamples = vec3.length(vsize) / publicAPI.getCurrentSampleDistance(ren);
const hasZBufferTexture = !!model.zBufferTexture;
const state = { iComps: actorProps.getIndependentComponents(), colorMixPreset: actorProps.getColorMixPreset(), interpolationType: actorProps.getInterpolationType(), useLabelOutline: publicAPI.isLabelmapOutlineRequired(actor), numComp, maxSamples, useGradientOpacity: actorProps.getUseGradientOpacity(0), blendMode: model.renderable.getBlendMode(), hasZBufferTexture, opacityModes, forceNearestInterps, };
if ( cellBO.getProgram()?.getHandle() === 0 || cellBO.getShaderSourceTime().getMTime() < publicAPI.getMTime() || cellBO.getShaderSourceTime().getMTime() < model.renderable.getMTime() || !model.previousState || !DeepEqual(model.previousState, state) ) { model.previousState = state; return true; } return false; };
publicAPI.updateShaders = (cellBO, ren, actor) => { if (publicAPI.getNeedToRebuildShaders(cellBO, ren, actor)) { const shaders = { Vertex: null, Fragment: null, Geometry: null };
publicAPI.buildShaders(shaders, ren, actor);
const newShader = model._openGLRenderWindow .getShaderCache() .readyShaderProgramArray( shaders.Vertex, shaders.Fragment, shaders.Geometry );
if (newShader !== cellBO.getProgram()) { cellBO.setProgram(newShader); 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.getClippingPlaneShaderParameters(cellBO, ren, actor); };
publicAPI.setMapperShaderParameters = (cellBO, ren, actor) => { const program = cellBO.getProgram();
if ( cellBO.getCABO().getElementCount() && (model.VBOBuildTime.getMTime() > cellBO.getAttributeUpdateTime().getMTime() || cellBO.getShaderSourceTime().getMTime() > cellBO.getAttributeUpdateTime().getMTime()) ) { if (program.isAttributeUsed('vertexDC')) { if ( !cellBO .getVAO() .addAttributeArray( program, cellBO.getCABO(), 'vertexDC', cellBO.getCABO().getVertexOffset(), cellBO.getCABO().getStride(), model.context.FLOAT, 3, model.context.FALSE ) ) { vtkErrorMacro('Error setting vertexDC in shader VAO.'); } } cellBO.getAttributeUpdateTime().modified(); }
program.setUniformi('texture1', model.scalarTexture.getTextureUnit()); program.setUniformf( 'sampleDistance', publicAPI.getCurrentSampleDistance(ren) );
const volInfo = model.scalarTexture.getVolumeInfo(); const ipScalarRange = model.renderable.getIpScalarRange();
if (volInfo?.dataComputedScale?.length) { const minVals = []; const maxVals = []; for (let i = 0; i < 4; i++) { minVals[i] = ipScalarRange[0] * volInfo.dataComputedScale[i] + volInfo.dataComputedOffset[i]; maxVals[i] = ipScalarRange[1] * volInfo.dataComputedScale[i] + volInfo.dataComputedOffset[i]; minVals[i] = (minVals[i] - volInfo.offset[i]) / volInfo.scale[i]; maxVals[i] = (maxVals[i] - volInfo.offset[i]) / volInfo.scale[i]; } program.setUniform4f( 'ipScalarRangeMin', minVals[0], minVals[1], minVals[2], minVals[3] ); program.setUniform4f( 'ipScalarRangeMax', maxVals[0], maxVals[1], maxVals[2], maxVals[3] ); }
if (model.zBufferTexture !== null) { program.setUniformi( 'zBufferTexture', model.zBufferTexture.getTextureUnit() ); const size = model._useSmallViewport ? [model._smallViewportWidth, model._smallViewportHeight] : model._openGLRenderWindow.getFramebufferSize(); program.setUniformf('vpZWidth', size[0]); program.setUniformf('vpZHeight', size[1]); } };
publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => { const keyMats = model.openGLCamera.getKeyMatrices(ren); const actMats = model.openGLVolume.getKeyMatrices();
mat4.multiply(model.modelToView, keyMats.wcvc, actMats.mcwc);
const program = cellBO.getProgram();
const cam = model.openGLCamera.getRenderable(); const crange = cam.getClippingRange(); program.setUniformf('camThick', crange[1] - crange[0]); program.setUniformf('camNear', crange[0]); program.setUniformf('camFar', crange[1]);
const bounds = model.currentInput.getBounds(); const dims = model.currentInput.getDimensions();
const pos = new Float64Array(3); const dir = new Float64Array(3); let dcxmin = 1.0; let dcxmax = -1.0; let dcymin = 1.0; let dcymax = -1.0;
for (let i = 0; i < 8; ++i) { vec3.set( pos, bounds[i % 2], bounds[2 + (Math.floor(i / 2) % 2)], bounds[4 + Math.floor(i / 4)] ); vec3.transformMat4(pos, pos, model.modelToView); if (!cam.getParallelProjection()) { vec3.normalize(dir, pos);
const t = -crange[0] / pos[2]; vec3.scale(pos, dir, t); } vec3.transformMat4(pos, pos, keyMats.vcpc);
dcxmin = Math.min(pos[0], dcxmin); dcxmax = Math.max(pos[0], dcxmax); dcymin = Math.min(pos[1], dcymin); dcymax = Math.max(pos[1], dcymax); }
program.setUniformf('dcxmin', dcxmin); program.setUniformf('dcxmax', dcxmax); program.setUniformf('dcymin', dcymin); program.setUniformf('dcymax', dcymax);
if (program.isUniformUsed('cameraParallel')) { program.setUniformi('cameraParallel', cam.getParallelProjection()); }
const ext = model.currentInput.getSpatialExtent(); const spc = model.currentInput.getSpacing(); const vsize = new Float64Array(3); vec3.set( vsize, (ext[1] - ext[0]) * spc[0], (ext[3] - ext[2]) * spc[1], (ext[5] - ext[4]) * spc[2] ); program.setUniform3f('vSpacing', spc[0], spc[1], spc[2]);
vec3.set(pos, ext[0], ext[2], ext[4]); model.currentInput.indexToWorldVec3(pos, pos);
vec3.transformMat4(pos, pos, model.modelToView); program.setUniform3f('vOriginVC', pos[0], pos[1], pos[2]);
const i2wmat4 = model.currentInput.getIndexToWorld(); mat4.multiply(model.idxToView, model.modelToView, i2wmat4);
mat3.multiply( model.idxNormalMatrix, keyMats.normalMatrix, actMats.normalMatrix ); mat3.multiply( model.idxNormalMatrix, model.idxNormalMatrix, model.currentInput.getDirectionByReference() );
const maxSamples = vec3.length(vsize) / publicAPI.getCurrentSampleDistance(ren); if (maxSamples > model.renderable.getMaximumSamplesPerRay()) { vtkWarningMacro(`The number of steps required ${Math.ceil( maxSamples )} is larger than the specified maximum number of steps ${model.renderable.getMaximumSamplesPerRay()}. Please either change the volumeMapper sampleDistance or its maximum number of samples.`); }
const vctoijk = new Float64Array(3);
vec3.set(vctoijk, 1.0, 1.0, 1.0); vec3.divide(vctoijk, vctoijk, vsize); program.setUniform3f('vVCToIJK', vctoijk[0], vctoijk[1], vctoijk[2]); program.setUniform3i('volumeDimensions', dims[0], dims[1], dims[2]); program.setUniform3f('volumeSpacings', spc[0], spc[1], spc[2]);
if (!model._openGLRenderWindow.getWebgl2()) { const volInfo = model.scalarTexture.getVolumeInfo(); program.setUniformf('texWidth', model.scalarTexture.getWidth()); program.setUniformf('texHeight', model.scalarTexture.getHeight()); program.setUniformi('xreps', volInfo.xreps); program.setUniformi('xstride', volInfo.xstride); program.setUniformi('ystride', volInfo.ystride); }
const normal = new Float64Array(3); const pos2 = new Float64Array(3); for (let i = 0; i < 6; ++i) { switch (i) { case 1: vec3.set(normal, -1.0, 0.0, 0.0); vec3.set(pos2, ext[0], ext[2], ext[4]); break; case 2: vec3.set(normal, 0.0, 1.0, 0.0); vec3.set(pos2, ext[1], ext[3], ext[5]); break; case 3: vec3.set(normal, 0.0, -1.0, 0.0); vec3.set(pos2, ext[0], ext[2], ext[4]); break; case 4: vec3.set(normal, 0.0, 0.0, 1.0); vec3.set(pos2, ext[1], ext[3], ext[5]); break; case 5: vec3.set(normal, 0.0, 0.0, -1.0); vec3.set(pos2, ext[0], ext[2], ext[4]); break; case 0: default: vec3.set(normal, 1.0, 0.0, 0.0); vec3.set(pos2, ext[1], ext[3], ext[5]); break; } vec3.transformMat3(normal, normal, model.idxNormalMatrix); vec3.transformMat4(pos2, pos2, model.idxToView); const dist = -1.0 * vec3.dot(pos2, normal);
program.setUniform3f(`vPlaneNormal${i}`, normal[0], normal[1], normal[2]); program.setUniformf(`vPlaneDistance${i}`, dist); }
if (publicAPI.isLabelmapOutlineRequired(actor)) { const image = model.currentInput; const worldToIndex = image.getWorldToIndex();
program.setUniformMatrix('vWCtoIDX', worldToIndex);
const camera = ren.getActiveCamera(); const [cRange0, cRange1] = camera.getClippingRange(); const distance = camera.getDistance();
camera.setClippingRange(distance, distance + 0.1); const labelOutlineKeyMats = model.openGLCamera.getKeyMatrices(ren);
mat4.invert(model.projectionToWorld, labelOutlineKeyMats.wcpc);
camera.setClippingRange(cRange0, cRange1);
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]); }
mat4.invert(model.projectionToView, keyMats.vcpc); program.setUniformMatrix('PCVCMatrix', model.projectionToView);
if (model.lightComplexity === 0) { return; } let lightNum = 0; const lightColor = []; const lightDir = []; const halfAngle = []; ren.getLights().forEach((light) => { const status = light.getSwitch(); if (status > 0) { const dColor = light.getColor(); const intensity = light.getIntensity(); lightColor[0 + lightNum * 3] = dColor[0] * intensity; lightColor[1 + lightNum * 3] = dColor[1] * intensity; lightColor[2 + lightNum * 3] = dColor[2] * intensity; const ldir = light.getDirection(); vec3.set(normal, ldir[0], ldir[1], ldir[2]); vec3.transformMat3(normal, normal, keyMats.normalMatrix); vec3.normalize(normal, normal); lightDir[0 + lightNum * 3] = normal[0]; lightDir[1 + lightNum * 3] = normal[1]; lightDir[2 + lightNum * 3] = normal[2]; halfAngle[0 + lightNum * 3] = -0.5 * normal[0]; halfAngle[1 + lightNum * 3] = -0.5 * normal[1]; halfAngle[2 + lightNum * 3] = -0.5 * (normal[2] - 1.0); lightNum++; } }); program.setUniformi('twoSidedLighting', ren.getTwoSidedLighting()); program.setUniformi('lightNum', lightNum); program.setUniform3fv('lightColor', lightColor); program.setUniform3fv('lightDirectionVC', lightDir); program.setUniform3fv('lightHalfAngleVC', halfAngle);
if (model.lightComplexity === 3) { lightNum = 0; const lightPositionVC = []; const lightAttenuation = []; const lightConeAngle = []; const lightExponent = []; const lightPositional = []; ren.getLights().forEach((light) => { const status = light.getSwitch(); if (status > 0) { const attenuation = light.getAttenuationValues(); lightAttenuation[0 + lightNum * 3] = attenuation[0]; lightAttenuation[1 + lightNum * 3] = attenuation[1]; lightAttenuation[2 + lightNum * 3] = attenuation[2]; lightExponent[lightNum] = light.getExponent(); lightConeAngle[lightNum] = light.getConeAngle(); lightPositional[lightNum] = light.getPositional(); const lp = light.getTransformedPosition(); vec3.transformMat4(lp, lp, model.modelToView); lightPositionVC[0 + lightNum * 3] = lp[0]; lightPositionVC[1 + lightNum * 3] = lp[1]; lightPositionVC[2 + lightNum * 3] = lp[2]; lightNum += 1; } }); program.setUniform3fv('lightPositionVC', lightPositionVC); program.setUniform3fv('lightAttenuation', lightAttenuation); program.setUniformfv('lightConeAngle', lightConeAngle); program.setUniformfv('lightExponent', lightExponent); program.setUniformiv('lightPositional', lightPositional); } if (model.renderable.getVolumetricScatteringBlending() > 0.0) { program.setUniformf( 'giReach', model.renderable.getGlobalIlluminationReach() ); program.setUniformf( 'volumetricScatteringBlending', model.renderable.getVolumetricScatteringBlending() ); program.setUniformf( 'volumeShadowSamplingDistFactor', model.renderable.getVolumeShadowSamplingDistFactor() ); program.setUniformf('anisotropy', model.renderable.getAnisotropy()); program.setUniformf( 'anisotropy2', model.renderable.getAnisotropy() ** 2.0 ); } if ( model.renderable.getLocalAmbientOcclusion() && actor.getProperty().getAmbient() > 0.0 ) { const ks = model.renderable.getLAOKernelSize(); program.setUniformi('kernelSize', ks); const kernelSample = []; for (let i = 0; i < ks; i++) { kernelSample[i * 2] = Math.random() * 0.5; kernelSample[i * 2 + 1] = Math.random() * 0.5; } program.setUniform2fv('kernelSample', kernelSample); program.setUniformi( 'kernelRadius', model.renderable.getLAOKernelRadius() ); } };
publicAPI.setPropertyShaderParameters = (cellBO, ren, actor) => { const program = cellBO.getProgram();
program.setUniformi('ctexture', model.colorTexture.getTextureUnit()); program.setUniformi('otexture', model.opacityTexture.getTextureUnit()); program.setUniformi('jtexture', model.jitterTexture.getTextureUnit()); program.setUniformi( 'ttexture', model.labelOutlineThicknessTexture.getTextureUnit() );
const volInfo = model.scalarTexture.getVolumeInfo(); const vprop = actor.getProperty();
const numComp = model.scalarTexture.getComponents(); const useIndependentComps = publicAPI.useIndependentComponents(vprop); if (useIndependentComps) { for (let i = 0; i < numComp; i++) { program.setUniformf( `mix${i}`, actor.getProperty().getComponentWeight(i) ); } }
for (let i = 0; i < numComp; i++) { const target = useIndependentComps ? i : 0; const sscale = volInfo.scale[i]; const ofun = vprop.getScalarOpacity(target); const oRange = ofun.getRange(); const oscale = sscale / (oRange[1] - oRange[0]); const oshift = (volInfo.offset[i] - oRange[0]) / (oRange[1] - oRange[0]); program.setUniformf(`oshift${i}`, oshift); program.setUniformf(`oscale${i}`, oscale);
const cfun = vprop.getRGBTransferFunction(target); const cRange = cfun.getRange(); const cshift = (volInfo.offset[i] - cRange[0]) / (cRange[1] - cRange[0]); const cScale = sscale / (cRange[1] - cRange[0]); program.setUniformf(`cshift${i}`, cshift); program.setUniformf(`cscale${i}`, cScale); }
if (model.gopacity) { if (useIndependentComps) { for (let nc = 0; nc < numComp; ++nc) { const sscale = volInfo.scale[nc]; const useGO = vprop.getUseGradientOpacity(nc); if (useGO) { const gomin = vprop.getGradientOpacityMinimumOpacity(nc); const gomax = vprop.getGradientOpacityMaximumOpacity(nc); program.setUniformf(`gomin${nc}`, gomin); program.setUniformf(`gomax${nc}`, gomax); const goRange = [ vprop.getGradientOpacityMinimumValue(nc), vprop.getGradientOpacityMaximumValue(nc), ]; program.setUniformf( `goscale${nc}`, (sscale * (gomax - gomin)) / (goRange[1] - goRange[0]) ); program.setUniformf( `goshift${nc}`, (-goRange[0] * (gomax - gomin)) / (goRange[1] - goRange[0]) + gomin ); } else { program.setUniformf(`gomin${nc}`, 1.0); program.setUniformf(`gomax${nc}`, 1.0); program.setUniformf(`goscale${nc}`, 0.0); program.setUniformf(`goshift${nc}`, 1.0); } } } else { const sscale = volInfo.scale[numComp - 1]; const gomin = vprop.getGradientOpacityMinimumOpacity(0); const gomax = vprop.getGradientOpacityMaximumOpacity(0); program.setUniformf('gomin0', gomin); program.setUniformf('gomax0', gomax); const goRange = [ vprop.getGradientOpacityMinimumValue(0), vprop.getGradientOpacityMaximumValue(0), ]; program.setUniformf( 'goscale0', (sscale * (gomax - gomin)) / (goRange[1] - goRange[0]) ); program.setUniformf( 'goshift0', (-goRange[0] * (gomax - gomin)) / (goRange[1] - goRange[0]) + gomin ); } }
const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor); if (vtkImageLabelOutline === true) { const labelOutlineOpacity = actor.getProperty().getLabelOutlineOpacity(); program.setUniformf('outlineOpacity', labelOutlineOpacity); }
if (model.lightComplexity > 0) { program.setUniformf('vAmbient', vprop.getAmbient()); program.setUniformf('vDiffuse', vprop.getDiffuse()); program.setUniformf('vSpecular', vprop.getSpecular()); program.setUniformf('vSpecularPower', vprop.getSpecularPower()); } };
publicAPI.getClippingPlaneShaderParameters = (cellBO, ren, actor) => { if (model.renderable.getClippingPlanes().length > 0) { const keyMats = model.openGLCamera.getKeyMatrices(ren);
const clipPlaneNormals = []; const clipPlaneDistances = []; const clipPlaneOrigins = [];
const clipPlanes = model.renderable.getClippingPlanes(); const clipPlaneSize = clipPlanes.length; for (let i = 0; i < clipPlaneSize; ++i) { const clipPlaneNormal = clipPlanes[i].getNormal(); const clipPlanePos = clipPlanes[i].getOrigin();
vec3.transformMat3( clipPlaneNormal, clipPlaneNormal, keyMats.normalMatrix );
vec3.transformMat4(clipPlanePos, clipPlanePos, keyMats.wcvc);
const clipPlaneDist = -1.0 * vec3.dot(clipPlanePos, clipPlaneNormal);
clipPlaneNormals.push(clipPlaneNormal[0]); clipPlaneNormals.push(clipPlaneNormal[1]); clipPlaneNormals.push(clipPlaneNormal[2]); clipPlaneDistances.push(clipPlaneDist); clipPlaneOrigins.push(clipPlanePos[0]); clipPlaneOrigins.push(clipPlanePos[1]); clipPlaneOrigins.push(clipPlanePos[2]); } const program = cellBO.getProgram(); program.setUniform3fv(`vClipPlaneNormals`, clipPlaneNormals); program.setUniformfv(`vClipPlaneDistances`, clipPlaneDistances); program.setUniform3fv(`vClipPlaneOrigins`, clipPlaneOrigins); program.setUniformi(`clip_numPlanes`, clipPlaneSize); } };
publicAPI.delete = macro.chain( () => { if (model._animationRateSubscription) { model._animationRateSubscription.unsubscribe(); model._animationRateSubscription = null; } }, () => { if (model._openGLRenderWindow) { unregisterGraphicsResources(model._openGLRenderWindow); } }, publicAPI.delete );
publicAPI.getRenderTargetSize = () => { if (model._useSmallViewport) { return [model._smallViewportWidth, model._smallViewportHeight]; }
const { usize, vsize } = model._openGLRenderer.getTiledSizeAndOrigin();
return [usize, vsize]; };
publicAPI.getRenderTargetOffset = () => { const { lowerLeftU, lowerLeftV } = model._openGLRenderer.getTiledSizeAndOrigin();
return [lowerLeftU, lowerLeftV]; };
publicAPI.getCurrentSampleDistance = (ren) => { const rwi = ren.getVTKWindow().getInteractor(); const baseSampleDistance = model.renderable.getSampleDistance(); if (rwi.isAnimating()) { const factor = model.renderable.getInteractionSampleDistanceFactor(); return baseSampleDistance * factor; } return baseSampleDistance; };
publicAPI.renderPieceStart = (ren, actor) => { const rwi = ren.getVTKWindow().getInteractor();
if (!model._lastScale) { model._lastScale = model.renderable.getInitialInteractionScale(); } model._useSmallViewport = false; if (rwi.isAnimating() && model._lastScale > 1.5) { model._useSmallViewport = true; }
if (!model._animationRateSubscription) { model._animationRateSubscription = rwi.onAnimationFrameRateUpdate(() => { if (model.renderable.getAutoAdjustSampleDistances()) { const frate = rwi.getRecentAnimationFrameRate(); const adjustment = rwi.getDesiredUpdateRate() / frate;
if (adjustment > 1.15 || adjustment < 0.85) { model._lastScale *= adjustment; } if (model._lastScale > 400) { model._lastScale = 400; } if (model._lastScale < 1.5) { model._lastScale = 1.5; } } else { model._lastScale = model.renderable.getImageSampleDistance() * model.renderable.getImageSampleDistance(); } }); }
if (model._useSmallViewport) { const size = model._openGLRenderWindow.getFramebufferSize(); const scaleFactor = 1 / Math.sqrt(model._lastScale); model._smallViewportWidth = Math.ceil(scaleFactor * size[0]); model._smallViewportHeight = Math.ceil(scaleFactor * size[1]);
if (model._smallViewportHeight > size[1]) { model._smallViewportHeight = size[1]; } if (model._smallViewportWidth > size[0]) { model._smallViewportWidth = size[0]; } model.framebuffer.saveCurrentBindingsAndBuffers();
if (model.framebuffer.getGLFramebuffer() === null) { model.framebuffer.create(size[0], size[1]); model.framebuffer.populateFramebuffer(); } else { const fbSize = model.framebuffer.getSize(); if (!fbSize || fbSize[0] !== size[0] || fbSize[1] !== size[1]) { model.framebuffer.create(size[0], size[1]); model.framebuffer.populateFramebuffer(); } } model.framebuffer.bind(); const gl = model.context; gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.colorMask(true, true, true, true); gl.clear(gl.COLOR_BUFFER_BIT); gl.viewport(0, 0, model._smallViewportWidth, model._smallViewportHeight); model.fvp = [ model._smallViewportWidth / size[0], model._smallViewportHeight / size[1], ]; } model.context.disable(model.context.DEPTH_TEST);
publicAPI.updateBufferObjects(ren, actor);
const iType = actor.getProperty().getInterpolationType(); if (iType === InterpolationType.NEAREST) { model.scalarTexture.setMinificationFilter(Filter.NEAREST); model.scalarTexture.setMagnificationFilter(Filter.NEAREST); } else { model.scalarTexture.setMinificationFilter(Filter.LINEAR); model.scalarTexture.setMagnificationFilter(Filter.LINEAR); }
if (model.zBufferTexture !== null) { model.zBufferTexture.activate(); } };
publicAPI.renderPieceDraw = (ren, actor) => { const gl = model.context;
model.scalarTexture.activate(); model.opacityTexture.activate(); model.labelOutlineThicknessTexture.activate(); model.colorTexture.activate(); model.jitterTexture.activate();
publicAPI.updateShaders(model.tris, ren, actor);
gl.drawArrays(gl.TRIANGLES, 0, model.tris.getCABO().getElementCount()); model.tris.getVAO().release();
model.scalarTexture.deactivate(); model.colorTexture.deactivate(); model.opacityTexture.deactivate(); model.labelOutlineThicknessTexture.deactivate(); model.jitterTexture.deactivate(); };
publicAPI.renderPieceFinish = (ren, actor) => { if (model.zBufferTexture !== null) { model.zBufferTexture.deactivate(); }
if (model._useSmallViewport) { model.framebuffer.restorePreviousBindingsAndBuffers();
if (model.copyShader === null) { model.copyShader = model._openGLRenderWindow .getShaderCache() .readyShaderProgramArray( [ '//VTK::System::Dec', 'attribute vec4 vertexDC;', 'uniform vec2 tfactor;', 'varying vec2 tcoord;', 'void main() { tcoord = vec2(vertexDC.x*0.5 + 0.5, vertexDC.y*0.5 + 0.5) * tfactor; gl_Position = vertexDC; }', ].join('\n'), [ '//VTK::System::Dec', '//VTK::Output::Dec', 'uniform sampler2D texture1;', 'varying vec2 tcoord;', 'void main() { gl_FragData[0] = texture2D(texture1,tcoord); }', ].join('\n'), '' ); const program = model.copyShader;
model.copyVAO = vtkVertexArrayObject.newInstance(); model.copyVAO.setOpenGLRenderWindow(model._openGLRenderWindow);
model.tris.getCABO().bind(); if ( !model.copyVAO.addAttributeArray( program, model.tris.getCABO(), 'vertexDC', model.tris.getCABO().getVertexOffset(), model.tris.getCABO().getStride(), model.context.FLOAT, 3, model.context.FALSE ) ) { vtkErrorMacro('Error setting vertexDC in copy shader VAO.'); } } else { model._openGLRenderWindow .getShaderCache() .readyShaderProgram(model.copyShader); }
const size = model._openGLRenderWindow.getFramebufferSize(); model.context.viewport(0, 0, size[0], size[1]);
const tex = model.framebuffer.getColorTexture(); tex.activate(); model.copyShader.setUniformi('texture', tex.getTextureUnit()); model.copyShader.setUniform2f('tfactor', model.fvp[0], model.fvp[1]);
const gl = model.context; gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
model.context.drawArrays( model.context.TRIANGLES, 0, model.tris.getCABO().getElementCount() ); tex.deactivate();
gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); } };
publicAPI.renderPiece = (ren, actor) => { publicAPI.invokeEvent({ type: 'StartEvent' }); model.renderable.update(); model.currentInput = model.renderable.getInputData(); publicAPI.invokeEvent({ type: 'EndEvent' });
if (!model.currentInput) { vtkErrorMacro('No input!'); return; }
publicAPI.renderPieceStart(ren, actor); publicAPI.renderPieceDraw(ren, actor); publicAPI.renderPieceFinish(ren, actor); };
publicAPI.computeBounds = (ren, actor) => { if (!publicAPI.getInput()) { vtkMath.uninitializeBounds(model.Bounds); return; } model.bounds = publicAPI.getInput().getBounds(); };
publicAPI.updateBufferObjects = (ren, actor) => { if (publicAPI.getNeedToRebuildBufferObjects(ren, actor)) { publicAPI.buildBufferObjects(ren, actor); } };
publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => { if ( 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.scalarTexture?.getHandle() || !model.colorTexture?.getHandle() || !model.labelOutlineThicknessTexture?.getHandle() ) { return true; } return false; };
publicAPI.buildBufferObjects = (ren, actor) => { const image = model.currentInput; if (!image) { return; }
const scalars = image.getPointData() && image.getPointData().getScalars(); if (!scalars) { return; }
const vprop = actor.getProperty();
if (!model.jitterTexture.getHandle()) { const oTable = new Uint8Array(32 * 32); for (let i = 0; i < 32 * 32; ++i) { oTable[i] = 255.0 * Math.random(); } model.jitterTexture.setMinificationFilter(Filter.LINEAR); model.jitterTexture.setMagnificationFilter(Filter.LINEAR); model.jitterTexture.create2DFromRaw( 32, 32, 1, VtkDataTypes.UNSIGNED_CHAR, oTable ); }
const numComp = scalars.getNumberOfComponents(); const useIndependentComps = publicAPI.useIndependentComponents(vprop); const numIComps = useIndependentComps ? numComp : 1;
const scalarOpacityFunc = vprop.getScalarOpacity(); const opTex = model._openGLRenderWindow.getGraphicsResourceForObject(scalarOpacityFunc); let toString = getTransferFunctionHash( scalarOpacityFunc, useIndependentComps, numIComps ); const reBuildOp = !opTex?.oglObject || opTex.hash !== toString; if (reBuildOp) { model.opacityTexture = vtkOpenGLTexture.newInstance(); model.opacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow); const oWidth = 1024; const oSize = oWidth * 2 * numIComps; const ofTable = new Float32Array(oSize); const tmpTable = new Float32Array(oWidth);
for (let c = 0; c < numIComps; ++c) { const ofun = vprop.getScalarOpacity(c); const opacityFactor = publicAPI.getCurrentSampleDistance(ren) / vprop.getScalarOpacityUnitDistance(c);
const oRange = ofun.getRange(); ofun.getTable(oRange[0], oRange[1], oWidth, tmpTable, 1); for (let i = 0; i < oWidth; ++i) { ofTable[c * oWidth * 2 + i] = 1.0 - (1.0 - tmpTable[i]) ** opacityFactor; ofTable[c * oWidth * 2 + i + oWidth] = ofTable[c * oWidth * 2 + i]; } }
model.opacityTexture.resetFormatAndType(); model.opacityTexture.setMinificationFilter(Filter.LINEAR); model.opacityTexture.setMagnificationFilter(Filter.LINEAR);
if ( model._openGLRenderWindow.getWebgl2() || (model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear')) ) { model.opacityTexture.create2DFromRaw( oWidth, 2 * numIComps, 1, VtkDataTypes.FLOAT, ofTable ); } else { const oTable = new Uint8ClampedArray(oSize); for (let i = 0; i < oSize; ++i) { oTable[i] = 255.0 * ofTable[i]; } model.opacityTexture.create2DFromRaw( oWidth, 2 * numIComps, 1, VtkDataTypes.UNSIGNED_CHAR, oTable ); } if (scalarOpacityFunc) { model._openGLRenderWindow.setGraphicsResourceForObject( scalarOpacityFunc, model.opacityTexture, toString ); if (scalarOpacityFunc !== model._scalarOpacityFunc) { model._openGLRenderWindow.registerGraphicsResourceUser( scalarOpacityFunc, publicAPI ); model._openGLRenderWindow.unregisterGraphicsResourceUser( model._scalarOpacityFunc, publicAPI ); } model._scalarOpacityFunc = scalarOpacityFunc; } } else { model.opacityTexture = opTex.oglObject; }
const colorTransferFunc = vprop.getRGBTransferFunction(); toString = getTransferFunctionHash( colorTransferFunc, useIndependentComps, numIComps ); const cTex = model._openGLRenderWindow.getGraphicsResourceForObject(colorTransferFunc); const reBuildC = !cTex?.oglObject?.getHandle() || cTex?.hash !== toString; if (reBuildC) { model.colorTexture = vtkOpenGLTexture.newInstance(); model.colorTexture.setOpenGLRenderWindow(model._openGLRenderWindow); const cWidth = 1024; const cSize = cWidth * 2 * numIComps * 3; const cTable = new Uint8ClampedArray(cSize); const tmpTable = new Float32Array(cWidth * 3);
for (let c = 0; c < numIComps; ++c) { const cfun = vprop.getRGBTransferFunction(c); const cRange = cfun.getRange(); cfun.getTable(cRange[0], cRange[1], cWidth, tmpTable, 1); 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]; } }
model.colorTexture.resetFormatAndType(); model.colorTexture.setMinificationFilter(Filter.LINEAR); model.colorTexture.setMagnificationFilter(Filter.LINEAR);
model.colorTexture.create2DFromRaw( cWidth, 2 * numIComps, 3, VtkDataTypes.UNSIGNED_CHAR, cTable ); if (colorTransferFunc) { model._openGLRenderWindow.setGraphicsResourceForObject( colorTransferFunc, model.colorTexture, toString ); if (colorTransferFunc !== model._colorTransferFunc) { model._openGLRenderWindow.registerGraphicsResourceUser( colorTransferFunc, publicAPI ); model._openGLRenderWindow.unregisterGraphicsResourceUser( model._colorTransferFunc, publicAPI ); } model._colorTransferFunc = colorTransferFunc; } } else { model.colorTexture = cTex.oglObject; }
publicAPI.updateLabelOutlineThicknessTexture(actor);
const tex = model._openGLRenderWindow.getGraphicsResourceForObject(scalars); toString = getImageDataHash(image, scalars); const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== toString; if (reBuildTex) { model.scalarTexture = vtkOpenGLTexture.newInstance(); model.scalarTexture.setOpenGLRenderWindow(model._openGLRenderWindow); const dims = image.getDimensions(); model.scalarTexture.setOglNorm16Ext( model.context.getExtension('EXT_texture_norm16') ); model.scalarTexture.resetFormatAndType(); model.scalarTexture.create3DFilterableFromDataArray( dims[0], dims[1], dims[2], scalars, model.renderable.getPreferSizeOverAccuracy() ); if (scalars) { model._openGLRenderWindow.setGraphicsResourceForObject( scalars, model.scalarTexture, toString ); if (scalars !== model._scalars) { model._openGLRenderWindow.registerGraphicsResourceUser( scalars, publicAPI ); model._openGLRenderWindow.unregisterGraphicsResourceUser( model._scalars, publicAPI ); } model._scalars = scalars; } } else { model.scalarTexture = tex.oglObject; }
if (!model.tris.getCABO().getElementCount()) { 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] = -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, }); model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, { points, cellOffset: 0, }); }
model.VBOBuildTime.modified(); };
publicAPI.updateLabelOutlineThicknessTexture = (volume) => { const labelOutlineThicknessArray = volume .getProperty() .getLabelOutlineThickness();
const lTex = model._openGLRenderWindow.getGraphicsResourceForObject( labelOutlineThicknessArray );
const toString = `${labelOutlineThicknessArray.join('-')}`;
const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== toString;
if (reBuildL) { model.labelOutlineThicknessTexture = vtkOpenGLTexture.newInstance(); model.labelOutlineThicknessTexture.setOpenGLRenderWindow( model._openGLRenderWindow ); const lWidth = 1024; const lHeight = 1; const lSize = lWidth * lHeight; const lTable = new Uint8Array(lSize);
for (let i = 0; i < lWidth; ++i) { const thickness = typeof labelOutlineThicknessArray[i] !== 'undefined' ? labelOutlineThicknessArray[i] : labelOutlineThicknessArray[0];
lTable[i] = thickness; }
model.labelOutlineThicknessTexture.resetFormatAndType(); model.labelOutlineThicknessTexture.setMinificationFilter(Filter.NEAREST); model.labelOutlineThicknessTexture.setMagnificationFilter(Filter.NEAREST);
model.labelOutlineThicknessTexture.create2DFromRaw( lWidth, lHeight, 1, VtkDataTypes.UNSIGNED_CHAR, lTable );
if (labelOutlineThicknessArray) { model._openGLRenderWindow.setGraphicsResourceForObject( labelOutlineThicknessArray, model.labelOutlineThicknessTexture, toString ); if (labelOutlineThicknessArray !== model._labelOutlineThicknessArray) { model._openGLRenderWindow.registerGraphicsResourceUser( labelOutlineThicknessArray, publicAPI ); model._openGLRenderWindow.unregisterGraphicsResourceUser( model._labelOutlineThicknessArray, publicAPI ); } model._labelOutlineThicknessArray = labelOutlineThicknessArray; } } else { model.labelOutlineThicknessTexture = lTex.oglObject; } };
publicAPI.isLabelmapOutlineRequired = (actor) => { const prop = actor.getProperty(); const renderable = model.renderable;
return ( prop.getUseLabelOutline() || renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND ); }; }
const DEFAULT_VALUES = { context: null, VBOBuildTime: null, scalarTexture: null, opacityTexture: null, opacityTextureString: null, colorTexture: null, colorTextureString: null, jitterTexture: null, labelOutlineThicknessTexture: null, labelOutlineThicknessTextureString: null, tris: null, framebuffer: null, copyShader: null, copyVAO: null, lastXYF: 1.0, targetXYF: 1.0, zBufferTexture: null, lastZBufferTexture: null, lightComplexity: 0, fullViewportTime: 1.0, idxToView: null, idxNormalMatrix: null, modelToView: null, projectionToView: null, avgWindowArea: 0.0, avgFrameTime: 0.0, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
vtkViewNode.extend(publicAPI, model, initialValues);
vtkReplacementShaderMapper.implementBuildShadersWithReplacements( publicAPI, model, initialValues );
model.VBOBuildTime = {}; macro.obj(model.VBOBuildTime, { mtime: 0 });
model.tris = vtkHelper.newInstance(); model.jitterTexture = vtkOpenGLTexture.newInstance(); model.jitterTexture.setWrapS(Wrap.REPEAT); model.jitterTexture.setWrapT(Wrap.REPEAT); model.framebuffer = vtkOpenGLFramebuffer.newInstance();
model.idxToView = mat4.identity(new Float64Array(16)); model.idxNormalMatrix = mat3.identity(new Float64Array(9)); model.modelToView = mat4.identity(new Float64Array(16)); model.projectionToView = mat4.identity(new Float64Array(16)); model.projectionToWorld = mat4.identity(new Float64Array(16));
macro.setGet(publicAPI, model, ['context']);
vtkOpenGLVolumeMapper(publicAPI, model); }
export const newInstance = macro.newInstance(extend, 'vtkOpenGLVolumeMapper');
export default { newInstance, extend };
registerOverride('vtkVolumeMapper', newInstance);
|