@acransac/vtk.js
Version:
Visualization Toolkit for the Web
1,423 lines (1,322 loc) • 67.8 kB
JavaScript
import { mat3, mat4, vec3 } from 'gl-matrix';
import macro from 'vtk.js/Sources/macro';
import vtkHelper from 'vtk.js/Sources/Rendering/OpenGL/Helper';
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkOpenGLTexture from 'vtk.js/Sources/Rendering/OpenGL/Texture';
import vtkProperty from 'vtk.js/Sources/Rendering/Core/Property';
import vtkShaderProgram from 'vtk.js/Sources/Rendering/OpenGL/ShaderProgram';
import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode';
import vtkPolyDataVS from 'vtk.js/Sources/Rendering/OpenGL/glsl/vtkPolyDataVS.glsl';
import vtkPolyDataFS from 'vtk.js/Sources/Rendering/OpenGL/glsl/vtkPolyDataFS.glsl';
import vtkReplacementShaderMapper from 'vtk.js/Sources/Rendering/OpenGL/ReplacementShaderMapper';
/* eslint-disable no-lonely-if */
const primTypes = {
Start: 0,
Points: 0,
Lines: 1,
Tris: 2,
TriStrips: 3,
TrisEdges: 4,
TriStripsEdges: 5,
End: 6,
};
const { Representation, Shading } = vtkProperty;
const { ScalarMode } = vtkMapper;
const { Filter, Wrap } = vtkOpenGLTexture;
const { vtkErrorMacro } = macro;
const StartEvent = { type: 'StartEvent' };
const EndEvent = { type: 'EndEvent' };
// ----------------------------------------------------------------------------
// vtkOpenGLPolyDataMapper methods
// ----------------------------------------------------------------------------
function vtkOpenGLPolyDataMapper(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkOpenGLPolyDataMapper');
publicAPI.buildPass = (prepass) => {
if (prepass) {
model.openGLActor = publicAPI.getFirstAncestorOfType('vtkOpenGLActor');
model.openGLRenderer = model.openGLActor.getFirstAncestorOfType(
'vtkOpenGLRenderer'
);
model.openGLRenderWindow = model.openGLRenderer.getParent();
model.openGLCamera = model.openGLRenderer.getViewNodeFor(
model.openGLRenderer.getRenderable().getActiveCamera()
);
}
};
// Renders myself
publicAPI.translucentPass = (prepass) => {
if (prepass) {
publicAPI.render();
}
};
publicAPI.opaqueZBufferPass = (prepass) => {
if (prepass) {
model.haveSeenDepthRequest = true;
model.renderDepth = true;
publicAPI.render();
model.renderDepth = false;
}
};
publicAPI.opaquePass = (prepass) => {
if (prepass) {
publicAPI.render();
}
};
publicAPI.render = () => {
const ctx = model.openGLRenderWindow.getContext();
if (model.context !== ctx) {
model.context = ctx;
for (let i = primTypes.Start; i < primTypes.End; i++) {
model.primitives[i].setOpenGLRenderWindow(model.openGLRenderWindow);
}
}
const actor = model.openGLActor.getRenderable();
const ren = model.openGLRenderer.getRenderable();
publicAPI.renderPiece(ren, actor);
};
publicAPI.buildShaders = (shaders, ren, actor) => {
publicAPI.getShaderTemplate(shaders, ren, actor);
// user specified pre replacements
const openGLSpec = model.renderable.getViewSpecificProperties().OpenGL;
let shaderReplacements = null;
if (openGLSpec) {
shaderReplacements = openGLSpec.ShaderReplacements;
}
if (shaderReplacements) {
for (let i = 0; i < shaderReplacements.length; i++) {
const currReplacement = shaderReplacements[i];
if (currReplacement.replaceFirst) {
const shaderType = currReplacement.shaderType;
const ssrc = shaders[shaderType];
const substituteRes = vtkShaderProgram.substitute(
ssrc,
currReplacement.originalValue,
currReplacement.replacementValue,
currReplacement.replaceAll
);
shaders[shaderType] = substituteRes.result;
}
}
}
publicAPI.replaceShaderValues(shaders, ren, actor);
// user specified post replacements
if (shaderReplacements) {
for (let i = 0; i < shaderReplacements.length; i++) {
const currReplacement = shaderReplacements[i];
if (!currReplacement.replaceFirst) {
const shaderType = currReplacement.shaderType;
const ssrc = shaders[shaderType];
const substituteRes = vtkShaderProgram.substitute(
ssrc,
currReplacement.originalValue,
currReplacement.replacementValue,
currReplacement.replaceAll
);
shaders[shaderType] = substituteRes.result;
}
}
}
};
publicAPI.getShaderTemplate = (shaders, ren, actor) => {
const openGLSpecProp = model.renderable.getViewSpecificProperties().OpenGL;
let vertexShaderCode = vtkPolyDataVS;
if (openGLSpecProp) {
const vertexSpecProp = openGLSpecProp.VertexShaderCode;
if (vertexSpecProp !== undefined && vertexSpecProp !== '') {
vertexShaderCode = vertexSpecProp;
}
}
shaders.Vertex = vertexShaderCode;
let fragmentShaderCode = vtkPolyDataFS;
if (openGLSpecProp) {
const fragmentSpecProp = openGLSpecProp.FragmentShaderCode;
if (fragmentSpecProp !== undefined && fragmentSpecProp !== '') {
fragmentShaderCode = fragmentSpecProp;
}
}
shaders.Fragment = fragmentShaderCode;
let geometryShaderCode = '';
if (openGLSpecProp) {
const geometrySpecProp = openGLSpecProp.GeometryShaderCode;
if (geometrySpecProp !== undefined) {
geometryShaderCode = geometrySpecProp;
}
}
shaders.Geometry = geometryShaderCode;
};
publicAPI.replaceShaderColor = (shaders, ren, actor) => {
let VSSource = shaders.Vertex;
let GSSource = shaders.Geometry;
let FSSource = shaders.Fragment;
const lastLightComplexity = model.lastBoundBO.getReferenceByName(
'lastLightComplexity'
);
// create the material/color property declarations, and VS implementation
// these are always defined
let colorDec = [
'uniform float ambient;',
'uniform float diffuse;',
'uniform float specular;',
'uniform float opacityUniform; // the fragment opacity',
'uniform vec3 ambientColorUniform;',
'uniform vec3 diffuseColorUniform;',
];
// add more for specular
if (lastLightComplexity) {
colorDec = colorDec.concat([
'uniform vec3 specularColorUniform;',
'uniform float specularPowerUniform;',
]);
}
// now handle the more complex fragment shader implementation
// the following are always defined variables. We start
// by assigning a default value from the uniform
let colorImpl = [
'vec3 ambientColor;',
' vec3 diffuseColor;',
' float opacity;',
];
if (lastLightComplexity) {
colorImpl = colorImpl.concat([
' vec3 specularColor;',
' float specularPower;',
]);
}
colorImpl = colorImpl.concat([
' ambientColor = ambientColorUniform;',
' diffuseColor = diffuseColorUniform;',
' opacity = opacityUniform;',
]);
if (lastLightComplexity) {
colorImpl = colorImpl.concat([
' specularColor = specularColorUniform;',
' specularPower = specularPowerUniform;',
]);
}
// add scalar vertex coloring
if (
model.lastBoundBO.getCABO().getColorComponents() !== 0 &&
!model.drawingEdges
) {
colorDec = colorDec.concat(['varying vec4 vertexColorVSOutput;']);
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Dec', [
'attribute vec4 scalarColor;',
'varying vec4 vertexColorVSOutput;',
]).result;
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Impl', [
'vertexColorVSOutput = scalarColor;',
]).result;
GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Dec', [
'in vec4 vertexColorVSOutput[];',
'out vec4 vertexColorGSOutput;',
]).result;
GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Impl', [
'vertexColorGSOutput = vertexColorVSOutput[i];',
]).result;
}
if (
model.lastBoundBO.getCABO().getColorComponents() !== 0 &&
!model.drawingEdges
) {
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Color::Impl',
colorImpl.concat([
' diffuseColor = vertexColorVSOutput.rgb;',
' ambientColor = vertexColorVSOutput.rgb;',
' opacity = opacity*vertexColorVSOutput.a;',
])
).result;
} else {
if (
model.renderable.getInterpolateScalarsBeforeMapping() &&
model.renderable.getColorCoordinates() &&
!model.drawingEdges
) {
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Color::Impl',
colorImpl.concat([
' vec4 texColor = texture2D(texture1, tcoordVCVSOutput.st);',
' diffuseColor = texColor.rgb;',
' ambientColor = texColor.rgb;',
' opacity = opacity*texColor.a;',
])
).result;
} else {
if (actor.getBackfaceProperty() && !model.drawingEdges) {
colorDec = colorDec.concat([
'uniform float opacityUniformBF; // the fragment opacity',
'uniform float ambientIntensityBF; // the material ambient',
'uniform float diffuseIntensityBF; // the material diffuse',
'uniform vec3 ambientColorUniformBF; // ambient material color',
'uniform vec3 diffuseColorUniformBF; // diffuse material color',
]);
if (lastLightComplexity) {
colorDec = colorDec.concat([
'uniform float specularIntensityBF; // the material specular intensity',
'uniform vec3 specularColorUniformBF; // intensity weighted color',
'uniform float specularPowerUniformBF;',
]);
colorImpl = colorImpl.concat([
'if (gl_FrontFacing == false) {',
' ambientColor = ambientIntensityBF * ambientColorUniformBF;',
' diffuseColor = diffuseIntensityBF * diffuseColorUniformBF;',
' specularColor = specularIntensityBF * specularColorUniformBF;',
' specularPower = specularPowerUniformBF;',
' opacity = opacityUniformBF; }',
]);
} else {
colorImpl = colorImpl.concat([
'if (gl_FrontFacing == false) {',
' ambientColor = ambientIntensityBF * ambientColorUniformBF;',
' diffuseColor = diffuseIntensityBF * diffuseColorUniformBF;',
' opacity = opacityUniformBF; }',
]);
}
}
if (model.haveCellScalars && !model.drawingEdges) {
colorDec = colorDec.concat(['uniform samplerBuffer texture1;']);
}
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Color::Impl',
colorImpl
).result;
}
}
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Color::Dec',
colorDec
).result;
shaders.Vertex = VSSource;
shaders.Geometry = GSSource;
shaders.Fragment = FSSource;
};
publicAPI.replaceShaderLight = (shaders, ren, actor) => {
let FSSource = shaders.Fragment;
// check for shadow maps
const shadowFactor = '';
const lastLightComplexity = model.lastBoundBO.getReferenceByName(
'lastLightComplexity'
);
const lastLightCount = model.lastBoundBO.getReferenceByName(
'lastLightCount'
);
let sstring = [];
switch (lastLightComplexity) {
case 0: // no lighting or RENDER_VALUES
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Light::Impl',
[
' gl_FragData[0] = vec4(ambientColor * ambient + diffuseColor * diffuse, opacity);',
' //VTK::Light::Impl',
],
false
).result;
break;
case 1: // headlight
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Light::Impl',
[
' float df = max(0.0, normalVCVSOutput.z);',
' float sf = pow(df, specularPower);',
' vec3 diffuseL = df * diffuseColor;',
' vec3 specularL = sf * specularColor;',
' gl_FragData[0] = vec4(ambientColor * ambient + diffuseL * diffuse + specularL * specular, opacity);',
' //VTK::Light::Impl',
],
false
).result;
break;
case 2: // light kit
for (let lc = 0; lc < lastLightCount; ++lc) {
sstring = sstring.concat([
`uniform vec3 lightColor${lc};`,
`uniform vec3 lightDirectionVC${lc}; // normalized`,
`uniform vec3 lightHalfAngleVC${lc}; // normalized`,
]);
}
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Light::Dec',
sstring
).result;
sstring = [
'vec3 diffuseL = vec3(0,0,0);',
' vec3 specularL = vec3(0,0,0);',
' float df;',
];
for (let lc = 0; lc < lastLightCount; ++lc) {
sstring = sstring.concat([
` df = max(0.0, dot(normalVCVSOutput, -lightDirectionVC${lc}));`,
` diffuseL += ((df${shadowFactor}) * lightColor${lc});`,
` if (dot(normalVCVSOutput, lightDirectionVC${lc}) < 0.0)`,
' {',
` float sf = pow( max(0.0, dot(lightHalfAngleVC${lc},normalVCVSOutput)), specularPower);`,
` specularL += ((sf${shadowFactor}) * lightColor${lc});`,
' }',
]);
}
sstring = sstring.concat([
' diffuseL = diffuseL * diffuseColor;',
' specularL = specularL * specularColor;',
' gl_FragData[0] = vec4(ambientColor * ambient + diffuseL * diffuse + specularL * specular, opacity);',
' //VTK::Light::Impl',
]);
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Light::Impl',
sstring,
false
).result;
break;
case 3: // positional
for (let lc = 0; lc < lastLightCount; ++lc) {
sstring = sstring.concat([
`uniform vec3 lightColor${lc};`,
`uniform vec3 lightDirectionVC${lc}; // normalized`,
`uniform vec3 lightHalfAngleVC${lc}; // normalized`,
`uniform vec3 lightPositionVC${lc};`,
`uniform vec3 lightAttenuation${lc};`,
`uniform float lightConeAngle${lc};`,
`uniform float lightExponent${lc};`,
`uniform int lightPositional${lc};`,
]);
}
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Light::Dec',
sstring
).result;
sstring = [
'vec3 diffuseL = vec3(0,0,0);',
' vec3 specularL = vec3(0,0,0);',
' vec3 vertLightDirectionVC;',
' float attenuation;',
' float df;',
];
for (let lc = 0; lc < lastLightCount; ++lc) {
sstring = sstring.concat([
' attenuation = 1.0;',
` if (lightPositional${lc} == 0)`,
' {',
` vertLightDirectionVC = lightDirectionVC${lc};`,
' }',
' else',
' {',
` vertLightDirectionVC = vertexVC.xyz - lightPositionVC${lc};`,
' float distanceVC = length(vertLightDirectionVC);',
' vertLightDirectionVC = normalize(vertLightDirectionVC);',
' attenuation = 1.0 /',
` (lightAttenuation${lc}.x`,
` + lightAttenuation${lc}.y * distanceVC`,
` + lightAttenuation${lc}.z * distanceVC * distanceVC);`,
' // per OpenGL standard cone angle is 90 or less for a spot light',
` if (lightConeAngle${lc} <= 90.0)`,
' {',
` float coneDot = dot(vertLightDirectionVC, lightDirectionVC${lc});`,
' // if inside the cone',
` if (coneDot >= cos(radians(lightConeAngle${lc})))`,
' {',
` attenuation = attenuation * pow(coneDot, lightExponent${lc});`,
' }',
' else',
' {',
' attenuation = 0.0;',
' }',
' }',
' }',
' df = max(0.0, attenuation*dot(normalVCVSOutput, -vertLightDirectionVC));',
` diffuseL += ((df${shadowFactor}) * lightColor${lc});`,
' if (dot(normalVCVSOutput, vertLightDirectionVC) < 0.0)',
' {',
` float sf = attenuation*pow( max(0.0, dot(lightHalfAngleVC${lc},normalVCVSOutput)), specularPower);`,
` specularL += ((sf${shadowFactor}) * lightColor${lc});`,
' }',
]);
}
sstring = sstring.concat([
' diffuseL = diffuseL * diffuseColor;',
' specularL = specularL * specularColor;',
' gl_FragData[0] = vec4(ambientColor * ambient + diffuseL * diffuse + specularL * specular, opacity);',
' //VTK::Light::Impl',
]);
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Light::Impl',
sstring,
false
).result;
break;
default:
vtkErrorMacro('bad light complexity');
}
shaders.Fragment = FSSource;
};
publicAPI.replaceShaderNormal = (shaders, ren, actor) => {
const lastLightComplexity = model.lastBoundBO.getReferenceByName(
'lastLightComplexity'
);
if (lastLightComplexity > 0) {
let VSSource = shaders.Vertex;
let GSSource = shaders.Geometry;
let FSSource = shaders.Fragment;
if (model.lastBoundBO.getCABO().getNormalOffset()) {
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Normal::Dec', [
'attribute vec3 normalMC;',
'uniform mat3 normalMatrix;',
'varying vec3 normalVCVSOutput;',
]).result;
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::Normal::Impl',
['normalVCVSOutput = normalMatrix * normalMC;']
).result;
GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Normal::Dec', [
'in vec3 normalVCVSOutput[];',
'out vec3 normalVCGSOutput;',
]).result;
GSSource = vtkShaderProgram.substitute(
GSSource,
'//VTK::Normal::Impl',
['normalVCGSOutput = normalVCVSOutput[i];']
).result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Normal::Dec', [
'varying vec3 normalVCVSOutput;',
]).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Normal::Impl',
[
'vec3 normalVCVSOutput = normalize(normalVCVSOutput);',
// if (!gl_FrontFacing) does not work in intel hd4000 mac
// if (int(gl_FrontFacing) == 0) does not work on mesa
' if (gl_FrontFacing == false) { normalVCVSOutput = -normalVCVSOutput; }',
]
).result;
} else {
if (model.haveCellNormals) {
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Normal::Dec',
['uniform mat3 normalMatrix;', 'uniform samplerBuffer textureN;']
).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Normal::Impl',
[
'vec3 normalVCVSOutput = normalize(normalMatrix *',
' texelFetchBuffer(textureN, gl_PrimitiveID + PrimitiveIDOffset).xyz);',
' if (gl_FrontFacing == false) { normalVCVSOutput = -normalVCVSOutput; }',
]
).result;
} else {
if (
publicAPI.getOpenGLMode(
actor.getProperty().getRepresentation(),
model.lastBoundBO.getPrimitiveType()
) === model.context.LINES
) {
// generate a normal for lines, it will be perpendicular to the line
// and maximally aligned with the camera view direction
// no clue if this is the best way to do this.
// the code below has been optimized a bit so what follows is
// an explanation of the basic approach. Compute the gradient of the line
// with respect to x and y, the the larger of the two
// cross that with the camera view direction. That gives a vector
// orthogonal to the camera view and the line. Note that the line and the camera
// view are probably not orthogonal. Which is why when we cross result that with
// the line gradient again we get a reasonable normal. It will be othogonal to
// the line (which is a plane but maximally aligned with the camera view.
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::UniformFlow::Impl',
[
' vec3 fdx = vec3(dFdx(vertexVC.x),dFdx(vertexVC.y),dFdx(vertexVC.z));',
' vec3 fdy = vec3(dFdy(vertexVC.x),dFdy(vertexVC.y),dFdy(vertexVC.z));',
' //VTK::UniformFlow::Impl',
] // For further replacements
).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Normal::Impl',
[
'vec3 normalVCVSOutput;',
' fdx = normalize(fdx);',
' fdy = normalize(fdy);',
' if (abs(fdx.x) > 0.0)',
' { normalVCVSOutput = normalize(cross(vec3(fdx.y, -fdx.x, 0.0), fdx)); }',
' else { normalVCVSOutput = normalize(cross(vec3(fdy.y, -fdy.x, 0.0), fdy));}',
]
).result;
} else {
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Normal::Dec',
['uniform int cameraParallel;']
).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::UniformFlow::Impl',
[
// ' vec3 fdx = vec3(dFdx(vertexVC.x),dFdx(vertexVC.y),dFdx(vertexVC.z));',
// ' vec3 fdy = vec3(dFdy(vertexVC.x),dFdy(vertexVC.y),dFdy(vertexVC.z));',
' vec3 fdx = dFdx(vertexVC.xyz);',
' vec3 fdy = dFdy(vertexVC.xyz);',
' //VTK::UniformFlow::Impl',
] // For further replacements
).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Normal::Impl',
[
' fdx = normalize(fdx);',
' fdy = normalize(fdy);',
' vec3 normalVCVSOutput = normalize(cross(fdx,fdy));',
// the code below is faster, but does not work on some devices
// 'vec3 normalVC = normalize(cross(dFdx(vertexVC.xyz), dFdy(vertexVC.xyz)));',
' if (cameraParallel == 1 && normalVCVSOutput.z < 0.0) { normalVCVSOutput = -1.0*normalVCVSOutput; }',
' if (cameraParallel == 0 && dot(normalVCVSOutput,vertexVC.xyz) > 0.0) { normalVCVSOutput = -1.0*normalVCVSOutput; }',
]
).result;
}
}
}
shaders.Vertex = VSSource;
shaders.Geometry = GSSource;
shaders.Fragment = FSSource;
}
};
publicAPI.replaceShaderPositionVC = (shaders, ren, actor) => {
let VSSource = shaders.Vertex;
let GSSource = shaders.Geometry;
let FSSource = shaders.Fragment;
// for points make sure to add in the point size
if (
actor.getProperty().getRepresentation() === Representation.POINTS ||
model.lastBoundBO.getPrimitiveType() === primTypes.Points
) {
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::PositionVC::Impl',
[
'//VTK::PositionVC::Impl',
` gl_PointSize = ${actor.getProperty().getPointSize()}.0;`,
],
false
).result;
}
// do we need the vertex in the shader in View Coordinates
const lastLightComplexity = model.lastBoundBO.getReferenceByName(
'lastLightComplexity'
);
if (lastLightComplexity > 0) {
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::PositionVC::Dec',
['varying vec4 vertexVCVSOutput;']
).result;
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::PositionVC::Impl',
[
'vertexVCVSOutput = MCVCMatrix * vertexMC;',
' gl_Position = MCPCMatrix * vertexMC;',
]
).result;
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', [
'uniform mat4 MCPCMatrix;',
'uniform mat4 MCVCMatrix;',
]).result;
GSSource = vtkShaderProgram.substitute(
GSSource,
'//VTK::PositionVC::Dec',
['in vec4 vertexVCVSOutput[];', 'out vec4 vertexVCGSOutput;']
).result;
GSSource = vtkShaderProgram.substitute(
GSSource,
'//VTK::PositionVC::Impl',
['vertexVCGSOutput = vertexVCVSOutput[i];']
).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::PositionVC::Dec',
['varying vec4 vertexVCVSOutput;']
).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::PositionVC::Impl',
['vec4 vertexVC = vertexVCVSOutput;']
).result;
} else {
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', [
'uniform mat4 MCPCMatrix;',
]).result;
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::PositionVC::Impl',
[' gl_Position = MCPCMatrix * vertexMC;']
).result;
}
shaders.Vertex = VSSource;
shaders.Geometry = GSSource;
shaders.Fragment = FSSource;
};
publicAPI.replaceShaderTCoord = (shaders, ren, actor) => {
if (model.lastBoundBO.getCABO().getTCoordOffset()) {
let VSSource = shaders.Vertex;
let GSSource = shaders.Geometry;
let FSSource = shaders.Fragment;
if (model.drawingEdges) {
return;
}
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::TCoord::Impl',
'tcoordVCVSOutput = tcoordMC;'
).result;
// we only handle the first texture by default
// additional textures are activated and we set the uniform
// for the texture unit they are assigned to, but you have to
// add in the shader code to do something with them
const tus = model.openGLActor.getActiveTextures();
let tNumComp = 2;
let tcdim = 2;
if (tus && tus.length > 0) {
tNumComp = tus[0].getComponents();
if (tus[0].getTarget() === model.context.TEXTURE_CUBE_MAP) {
tcdim = 3;
}
}
if (model.renderable.getColorTextureMap()) {
tNumComp = model.renderable
.getColorTextureMap()
.getPointData()
.getScalars()
.getNumberOfComponents();
tcdim = 2;
}
if (tcdim === 2) {
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::TCoord::Dec',
'attribute vec2 tcoordMC; varying vec2 tcoordVCVSOutput;'
).result;
GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::TCoord::Dec', [
'in vec2 tcoordVCVSOutput[];',
'out vec2 tcoordVCGSOutput;',
]).result;
GSSource = vtkShaderProgram.substitute(
GSSource,
'//VTK::TCoord::Impl',
'tcoordVCGSOutput = tcoordVCVSOutput[i];'
).result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Dec', [
'varying vec2 tcoordVCVSOutput;',
'uniform sampler2D texture1;',
]).result;
if (tus && tus.length >= 1) {
switch (tNumComp) {
case 1:
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::TCoord::Impl',
[
'vec4 tcolor = texture2D(texture1, tcoordVCVSOutput);',
'gl_FragData[0] = clamp(gl_FragData[0],0.0,1.0)*',
' vec4(tcolor.r,tcolor.r,tcolor.r,1.0);',
]
).result;
break;
case 2:
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::TCoord::Impl',
[
'vec4 tcolor = texture2D(texture1, tcoordVCVSOutput);',
'gl_FragData[0] = clamp(gl_FragData[0],0.0,1.0)*',
' vec4(tcolor.r,tcolor.r,tcolor.r,tcolor.g);',
]
).result;
break;
default:
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::TCoord::Impl',
'gl_FragData[0] = clamp(gl_FragData[0],0.0,1.0)*texture2D(texture1, tcoordVCVSOutput.st);'
).result;
}
}
} else {
VSSource = vtkShaderProgram.substitute(
VSSource,
'//VTK::TCoord::Dec',
'attribute vec3 tcoordMC; varying vec3 tcoordVCVSOutput;'
).result;
GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::TCoord::Dec', [
'in vec3 tcoordVCVSOutput[];',
'out vec3 tcoordVCGSOutput;',
]).result;
GSSource = vtkShaderProgram.substitute(
GSSource,
'//VTK::TCoord::Impl',
'tcoordVCGSOutput = tcoordVCVSOutput[i];'
).result;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Dec', [
'varying vec3 tcoordVCVSOutput;',
'uniform samplerCube texture1;',
]).result;
switch (tNumComp) {
case 1:
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::TCoord::Impl',
[
'vec4 tcolor = textureCube(texture1, tcoordVCVSOutput);',
'gl_FragData[0] = clamp(gl_FragData[0],0.0,1.0)*',
' vec4(tcolor.r,tcolor.r,tcolor.r,1.0);',
]
).result;
break;
case 2:
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::TCoord::Impl',
[
'vec4 tcolor = textureCube(texture1, tcoordVCVSOutput);',
'gl_FragData[0] = clamp(gl_FragData[0],0.0,1.0)*',
' vec4(tcolor.r,tcolor.r,tcolor.r,tcolor.g);',
]
).result;
break;
default:
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::TCoord::Impl',
'gl_FragData[0] = clamp(gl_FragData[0],0.0,1.0)*textureCube(texture1, tcoordVCVSOutput);'
).result;
}
}
shaders.Vertex = VSSource;
shaders.Geometry = GSSource;
shaders.Fragment = FSSource;
}
};
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) {
macro.vtkErrorMacro('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.getCoincidentParameters = (ren, actor) => {
// 1. ResolveCoincidentTopology is On and non zero for this primitive
// type
let cp = null;
const prop = actor.getProperty();
if (
model.renderable.getResolveCoincidentTopology() ||
(prop.getEdgeVisibility() &&
prop.getRepresentation() === Representation.SURFACE)
) {
const primType = model.lastBoundBO.getPrimitiveType();
if (
primType === primTypes.Points ||
prop.getRepresentation() === Representation.POINTS
) {
cp = model.renderable.getCoincidentTopologyPointOffsetParameter();
} else if (
primType === primTypes.Lines ||
prop.getRepresentation() === Representation.WIREFRAME
) {
cp = model.renderable.getCoincidentTopologyLineOffsetParameters();
} else if (
primType === primTypes.Tris ||
primType === primTypes.TriStrips
) {
cp = model.renderable.getCoincidentTopologyPolygonOffsetParameters();
}
if (
primType === primTypes.TrisEdges ||
primType === primTypes.TriStripsEdges
) {
cp = model.renderable.getCoincidentTopologyPolygonOffsetParameters();
cp.factor /= 2.0;
cp.offset /= 2.0;
}
}
// hardware picking always offset due to saved zbuffer
// This gets you above the saved surface depth buffer.
// vtkHardwareSelector* selector = ren->GetSelector();
// if (selector &&
// selector->GetFieldAssociation() == vtkDataObject::FIELD_ASSOCIATION_POINTS)
// {
// offset -= 2.0;
// return;
// }
return cp;
};
publicAPI.replaceShaderPicking = (shaders, ren, actor) => {
let FSSource = shaders.Fragment;
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Dec', [
'uniform vec3 mapperIndex;',
'uniform int picking;',
]).result;
FSSource = vtkShaderProgram.substitute(
FSSource,
'//VTK::Picking::Impl',
' gl_FragData[0] = picking != 0 ? vec4(mapperIndex,1.0) : gl_FragData[0];'
).result;
shaders.Fragment = FSSource;
};
publicAPI.replaceShaderValues = (shaders, ren, actor) => {
publicAPI.replaceShaderColor(shaders, ren, actor);
publicAPI.replaceShaderNormal(shaders, ren, actor);
publicAPI.replaceShaderLight(shaders, ren, actor);
publicAPI.replaceShaderTCoord(shaders, ren, actor);
publicAPI.replaceShaderPicking(shaders, ren, actor);
publicAPI.replaceShaderClip(shaders, ren, actor);
publicAPI.replaceShaderCoincidentOffset(shaders, ren, actor);
publicAPI.replaceShaderPositionVC(shaders, ren, actor);
if (model.haveSeenDepthRequest) {
let FSSource = shaders.Fragment;
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.Fragment = FSSource;
}
};
publicAPI.getNeedToRebuildShaders = (cellBO, ren, actor) => {
let lightComplexity = 0;
let numberOfLights = 0;
const primType = cellBO.getPrimitiveType();
const poly = model.currentInput;
// different algo from C++ as of 5/2019
let needLighting = false;
const pointNormals = poly.getPointData().getNormals();
const cellNormals = poly.getCellData().getNormals();
const flat = actor.getProperty().getInterpolation() === Shading.FLAT;
const representation = actor.getProperty().getRepresentation();
const mode = publicAPI.getOpenGLMode(representation, primType);
// 1) all surfaces need lighting
if (mode === model.context.TRIANGLES) {
needLighting = true;
// 2) all cell normals without point normals need lighting
} else if (cellNormals && !pointNormals) {
needLighting = true;
// 3) Phong + pointNormals need lighting
} else if (!flat && pointNormals) {
needLighting = true;
// 4) Phong Lines need lighting
} else if (!flat && mode === model.context.LINES) {
needLighting = true;
}
// 5) everything else is unlit
// do we need lighting?
if (actor.getProperty().getLighting() && needLighting) {
// consider the lighting complexity to determine which case applies
// simple headlight, Light Kit, the whole feature set of VTK
lightComplexity = 0;
const lights = ren.getLightsByReference();
for (let index = 0; index < lights.length; ++index) {
const light = lights[index];
const status = light.getSwitch();
if (status > 0) {
numberOfLights++;
if (lightComplexity === 0) {
lightComplexity = 1;
}
}
if (
lightComplexity === 1 &&
(numberOfLights > 1 ||
light.getIntensity() !== 1.0 ||
!light.lightTypeIsHeadLight())
) {
lightComplexity = 2;
}
if (lightComplexity < 3 && light.getPositional()) {
lightComplexity = 3;
}
}
}
let needRebuild = false;
const lastLightComplexity = model.lastBoundBO.getReferenceByName(
'lastLightComplexity'
);
const lastLightCount = model.lastBoundBO.getReferenceByName(
'lastLightCount'
);
if (
lastLightComplexity !== lightComplexity ||
lastLightCount !== numberOfLights
) {
model.lastBoundBO.set({ lastLightComplexity: lightComplexity }, true);
model.lastBoundBO.set({ lastLightCount: numberOfLights }, true);
needRebuild = true;
}
// has something changed that would require us to recreate the shader?
// candidates are
// property modified (representation interpolation and lighting)
// input modified
// light complexity changed
if (
model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest ||
cellBO.getProgram() === 0 ||
cellBO.getShaderSourceTime().getMTime() < publicAPI.getMTime() ||
cellBO.getShaderSourceTime().getMTime() < actor.getMTime() ||
cellBO.getShaderSourceTime().getMTime() < model.renderable.getMTime() ||
cellBO.getShaderSourceTime().getMTime() < model.currentInput.getMTime() ||
needRebuild
) {
model.lastHaveSeenDepthRequest = model.haveSeenDepthRequest;
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.setPropertyShaderParameters(cellBO, ren, actor);
publicAPI.setCameraShaderParameters(cellBO, ren, actor);
publicAPI.setLightingShaderParameters(cellBO, ren, actor);
const listCallbacks = model.renderable.getViewSpecificProperties()
.ShadersCallbacks;
if (listCallbacks) {
listCallbacks.forEach((object) => {
object.callback(object.userData, cellBO, ren, actor);
});
}
};
publicAPI.setMapperShaderParameters = (cellBO, ren, actor) => {
// Now to update the VAO too, if necessary.
if (cellBO.getProgram().isUniformUsed('PrimitiveIDOffset')) {
cellBO
.getProgram()
.setUniformi('PrimitiveIDOffset', model.primitiveIDOffset);
}
if (
cellBO.getCABO().getElementCount() &&
(model.VBOBuildTime.getMTime() >
cellBO.getAttributeUpdateTime().getMTime() ||
cellBO.getShaderSourceTime().getMTime() >
cellBO.getAttributeUpdateTime().getMTime())
) {
const lastLightComplexity = model.lastBoundBO.getReferenceByName(
'lastLightComplexity'
);
if (cellBO.getProgram().isAttributeUsed('vertexMC')) {
if (
!cellBO
.getVAO()
.addAttributeArray(
cellBO.getProgram(),
cellBO.getCABO(),
'vertexMC',
cellBO.getCABO().getVertexOffset(),
cellBO.getCABO().getStride(),
model.context.FLOAT,
3,
false
)
) {
vtkErrorMacro('Error setting vertexMC in shader VAO.');
}
}
if (
cellBO.getProgram().isAttributeUsed('normalMC') &&
cellBO.getCABO().getNormalOffset() &&
lastLightComplexity > 0
) {
if (
!cellBO
.getVAO()
.addAttributeArray(
cellBO.getProgram(),
cellBO.getCABO(),
'normalMC',
cellBO.getCABO().getNormalOffset(),
cellBO.getCABO().getStride(),
model.context.FLOAT,
3,
false
)
) {
vtkErrorMacro('Error setting normalMC in shader VAO.');
}
} else {
cellBO.getVAO().removeAttributeArray('normalMC');
}
model.renderable.getCustomShaderAttributes().forEach((attrName, idx) => {
if (cellBO.getProgram().isAttributeUsed(`${attrName}MC`)) {
if (
!cellBO
.getVAO()
.addAttributeArray(
cellBO.getProgram(),
cellBO.getCABO(),
`${attrName}MC`,
cellBO.getCABO().getCustomData()[idx].offset,
cellBO.getCABO().getStride(),
model.context.FLOAT,
cellBO.getCABO().getCustomData()[idx].components,
false
)
) {
vtkErrorMacro(`Error setting ${attrName}MC 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(),
false
)
) {
vtkErrorMacro('Error setting tcoordMC in shader VAO.');
}
} else {
cellBO.getVAO().removeAttributeArray('tcoordMC');
}
if (
cellBO.getProgram().isAttributeUsed('scalarColor') &&
cellBO.getCABO().getColorComponents()
) {
if (
!cellBO
.getVAO()
.addAttributeArray(
cellBO.getProgram(),
cellBO.getCABO().getColorBO(),
'scalarColor',
cellBO.getCABO().getColorOffset(),
cellBO.getCABO().getColorBOStride(),
model.context.UNSIGNED_BYTE,
4,
true
)
) {
vtkErrorMacro('Error setting scalarColor in shader VAO.');
}
} else {
cellBO.getVAO().removeAttributeArray('scalarColor');
}
cellBO.getAttributeUpdateTime().modified();
}
if (model.renderable.getNumberOfClippingPlanes()) {
// add all the clipping planes
let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
if (numClipPlanes > 6) {
macro.vtkErrorMacro('OpenGL has a limit of 6 clipping planes');
numClipPlanes = 6;
}
const planeEquations = [];
for (let i = 0; i < numClipPlanes; i++) {
const planeEquation = [];
model.renderable.getClippingPlaneInDataCoords(
actor.getMatrix(),
i,
planeEquation
);
for (let j = 0; j < 4; j++) {
planeEquations.push(planeEquation[j]);
}
}
cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
cellBO.getProgram().setUniform4fv('clipPlanes', 6, planeEquations);
}
if (
model.internalColorTexture &&
cellBO.getProgram().isUniformUsed('texture1')
) {
cellBO
.getProgram()
.setUniformi('texture1', model.internalColorTexture.getTextureUnit());
}
const tus = model.openGLActor.getActiveTextures();
if (tus) {
for (let index = 0; index < tus.length; ++index) {
const tex = tus[index];
const texUnit = tex.getTextureUnit();
const tname = `texture${texUnit + 1}`;
if (cellBO.getProgram().isUniformUsed(tname)) {
cellBO.getProgram().setUniformi(tname, texUnit);
}
}
}
// handle depth requests
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 selector = model.openGLRenderer.getSelector();
cellBO
.getProgram()
.setUniform3fArray(
'mapperIndex',
selector ? selector.getPropColorValue() : [0.0, 0.0, 0.0]
);
cellBO
.getProgram()
.setUniformi('picking', selector ? selector.getCurrentPass() + 1 : 0);
};
publicAPI.setLightingShaderParameters = (cellBO, ren, actor) => {
// for unlit and headlight there are no lighting parameters
const lastLightComplexity = model.lastBoundBO.getReferenceByName(
'lastLightComplexity'
);
if (lastLightComplexity < 2) {
return;
}
const program = cellBO.getProgram();
// bind some light settings
let numberOfLights = 0;
const lights = ren.getLightsByReference();
for (let index = 0; index < lights.length; ++index) {
const light = lights[index];
const status = light.getSwitch();
if (status > 0.0) {
const dColor = light.getColorByReference();
const intensity = light.getIntensity();
model.lightColor[0] = dColor[0] * intensity;
model.lightColor[1] = dColor[1] * intensity;
model.lightColor[2] = dColor[2] * intensity;
// get required info from light
const ld = light.getDirection();
const transform = ren.getActiveCamera().getViewMatrix();
const newLightDirection = [...ld];
if (light.lightTypeIsSceneLight()) {
newLightDirection[0] =
transform[0] * ld[0] + transform[1] * ld[1] + transform[2] * ld[2];
newLightDirection[1] =
transform[4] * ld[0] + transform[5] * ld[1] + transform[6] * ld[2];
newLightDirection[2] =
transform[8] * ld[0] + transform[9] * ld[1] + transform[10] * ld[2];
vtkMath.normalize(newLightDirection);
}
model.lightDirection[0] = newLightDirection[0];
model.lightDirection[1] = newLightDirection[1];
model.lightDirection[2] = newLightDirection[2];
model.lightHalfAngle[0] = -model.lightDirection[0];
model.lightHalfAngle[1] = -model.lightDirection[1];
model.lightHalfAngle[2] = -model.lightDirection[2] + 1.0;
vtkMath.normalize(model.lightDirection);
program.setUniform3fArray(
`lightColor${numberOfLights}`,
model.lightColor
);
program.setUniform3fArray(
`lightDirectionVC${numberOfLights}`,
model.lightDirection
);
program.setUniform3fArray(
`lightHalfAngleVC${numberOfLights}`,
model.lightHalfAngle
);
numberOfLights++;
}
}
// we are done unless we have positional lights
if (lastLightComplexity < 3) {
return;
}
// for lightkit case there are some parameters to set
const cam = ren.getActiveCamera();
const viewTF = cam.get