@itwin/core-frontend
Version:
iTwin.js frontend components
386 lines (367 loc) • 18.8 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module WebGL
*/
import { assert } from "@itwin/core-bentley";
import { Matrix4d } from "@itwin/core-geometry";
import { SpatialClassifierInsideDisplay } from "@itwin/core-common";
import { Matrix4 } from "../Matrix";
import { PlanarClassifierContent } from "../PlanarClassifier";
import { TextureUnit } from "../RenderFlags";
import { Texture2DHandle } from "../Texture";
import { addShaderFlags, addUInt32s } from "./Common";
import { addClassifierFlash } from "./FeatureSymbology";
import { addWindowToTexCoords } from "./Fragment";
import { addInstancedRtcMatrix } from "./Vertex";
export const volClassOpaqueColor = `
vec4 volClassColor(vec4 baseColor, float depth) {
if (depth <= TEXTURE(s_pClassSampler, windowCoordsToTexCoords(gl_FragCoord.xy)).r)
discard;
return vec4(baseColor.rgb, 1.0);
}
`;
const volClassTranslucentColor = `
vec4 volClassColor(vec4 baseColor, float depth) {
return vec4(baseColor.rgb, depth); // This will never be called, so we use depth here to avoid a compile error
}
`;
const applyPlanarClassificationPrelude = `
const float dimScale = .7;
vec2 classPos = v_pClassPos.xy / v_pClassPosW;
bool isOutside = classPos.x < 0.0 || classPos.x > 1.0 || classPos.y < 0.0 || classPos.y > 1.0;
if (u_pClassColorParams.x > kClassifierDisplay_Element) { // texture/terrain drape.
if (u_pClassColorParams.x > kTextureDrape) {
return volClassColor(baseColor, depth);
}
if (isOutside)
discard;
vec3 rgb = TEXTURE(s_pClassSampler, classPos.xy).rgb;
return vec4(rgb, baseColor.a);
}
float imageCount = u_pClassColorParams.z;
// If imageCount is less than zero - the mask sense is inverted - inside rather than outside. (masks only)
bool doInvert = false;
if (imageCount < 0.0) {
imageCount = - imageCount;
doInvert = true;
}
vec4 colorTexel = vec4(0);
vec4 maskTexel = vec4(0);
bool doMask = imageCount != kTextureContentClassifierOnly;
bool doClassify = imageCount != kTextureContentMaskOnly;
if (!isOutside) {
if (imageCount == kTextureContentClassifierOnly) {
colorTexel = TEXTURE(s_pClassSampler, vec2(classPos.x, classPos.y / imageCount));
} else if (imageCount == kTextureContentMaskOnly) {
maskTexel = TEXTURE(s_pClassSampler, vec2(classPos.x, classPos.y));
} else if (imageCount == kTextureContentClassifierAndMask) {
colorTexel = TEXTURE(s_pClassSampler, vec2(classPos.x, classPos.y / imageCount));
maskTexel = TEXTURE(s_pClassSampler, vec2(classPos.x, (2.0 + classPos.y) / imageCount));
}
if (colorTexel.b >= 0.5) {
if (u_shaderFlags[kShaderBit_IgnoreNonLocatable]) {
discard;
return vec4(0.0);
}
colorTexel.b = (colorTexel.b * 255.0 - 128.0) / 127.0;
} else {
colorTexel.b *= 255.0 / 127.0;
}
}
if (doMask) {
bool masked = !isOutside && (maskTexel.r + maskTexel.g + maskTexel.b + maskTexel.a) > 0.0;
if (doInvert)
masked = !masked;
if (masked) {
float maskTransparency = u_pClassColorParams.w < 0.0 ? (1.0 - maskTexel.a) : u_pClassColorParams.w;
if (maskTransparency <= 0.0) {
discard;
return vec4(0);
}
baseColor.a = baseColor.a * maskTransparency;
}
if (!doClassify)
return baseColor;
}
bool isClassified = !isOutside && (colorTexel.r + colorTexel.g + colorTexel.b + colorTexel.a > 0.0);
float param = isClassified ? u_pClassColorParams.x : u_pClassColorParams.y;
if (kClassifierDisplay_Off == param) {
discard;
return vec4(0);
}
`;
// Currently we discard if classifier is pure black (acts as clipping mask).
// These could be more efficiently handled with masks.
const applyPlanarClassificationColor = applyPlanarClassificationPrelude + // eslint-disable-line prefer-template
`
float colorMix = u_pClassPointCloud ? .65 : .35;
vec4 classColor;
if (kClassifierDisplay_On == param)
classColor = baseColor;
else if (!isClassified || kClassifierDisplay_Dimmed == param)
classColor = vec4(baseColor.rgb * dimScale, baseColor.a);
else if (kClassifierDisplay_Hilite == param)
classColor = vec4(mix(baseColor.rgb, u_hilite_settings[0], u_hilite_settings[2][0]), baseColor.a);
else {
if (colorTexel.b > colorTexel.a) {
discard;
return vec4(0.0);
}
// NB: colorTexel contains pre-multiplied alpha. We know it is greater than zero from above.
float alpha = colorTexel.a * baseColor.a;
vec3 rgb = colorTexel.rgb / colorTexel.a;
rgb = mix(baseColor.rgb, rgb, colorMix);
classColor = vec4(rgb, alpha);
}
if (kClassifierDisplay_Element != param && isClassified) {
if (colorTexel.r > colorTexel.a && kClassifierDisplay_Hilite != param)
classColor = vec4(mix(baseColor.rgb, u_hilite_settings[0], u_hilite_settings[2][0]), 1.0);
if (colorTexel.g > colorTexel.a)
classColor = applyClassifierFlash(classColor);
}
return classColor;
`;
const applyPlanarClassificationColorForThematic = applyPlanarClassificationPrelude + // eslint-disable-line prefer-template
`
vec4 classColor = baseColor;
if (kClassifierDisplay_Element == param) {
if (colorTexel.b > colorTexel.a) {
discard;
return vec4(0.0);
}
// We stashed the element alpha in blue channel. Make sure to handle pre-multiplied alpha.
baseColor.rgb = baseColor.rgb / baseColor.a;
classColor = vec4(baseColor.rgb, colorTexel.b);
classColor.rgb *= classColor.a;
colorTexel.a = 0.5; // make conditions below potentially pass
}
if (isClassified) {
if (colorTexel.r > colorTexel.a && kClassifierDisplay_Hilite != param)
classColor = vec4(mix(baseColor.rgb, u_hilite_settings[0], u_hilite_settings[2][0]), 1.0);
if (colorTexel.g > colorTexel.a)
classColor = applyClassifierFlash(classColor);
}
return classColor;
`;
const overrideFeatureId = `
if (u_pClassColorParams.x != kClassifierDisplay_Element) return currentId;
vec2 classPos = v_pClassPos / v_pClassPosW;
vec4 featureTexel = TEXTURE(s_pClassSampler, vec2(classPos.x, (1.0 + classPos.y) / u_pClassColorParams.z));
return (featureTexel == vec4(0)) ? currentId : addUInt32s(u_batchBase, featureTexel * 255.0) / 255.0;
`;
const computeClassifiedHiliteColor = `
vec2 classPos = v_pClassPos / v_pClassPosW;
return TEXTURE(s_pClassHiliteSampler, classPos);
`;
const computeClassifiedSurfaceHiliteColor = `
if (isSurfaceBitSet(kSurfaceBit_HasTexture) && TEXTURE(s_texture, v_texCoord).a <= 0.15)
return vec4(0.0);
${computeClassifiedHiliteColor}`;
const computeClassifierPos = "vec4 classProj = u_pClassProj * rawPosition; v_pClassPos = classProj.xy;";
const computeInstancedClassifierPos = "vec4 classProj = u_pClassProj * g_instancedRtcMatrix * rawPosition; v_pClassPos = classProj.xy;";
const computeClassifierPosW = "v_pClassPosW = classProj.w;";
const scratchBytes = new Uint8Array(4);
const scratchBatchBaseId = new Uint32Array(scratchBytes.buffer);
const scratchBatchBaseComponents = [0, 0, 0, 0];
const scratchColorParams = new Float32Array(4); // Unclassified scale, classified base scale, classified classifier scale, content/image count... MaskOnly = 1, ClassifierOnly = 2, ClassifierAndMask = 3
const scratchModel = Matrix4d.createIdentity();
const scratchModelProjection = Matrix4d.createIdentity();
const scratchMatrix = new Matrix4();
function addPlanarClassifierCommon(builder) {
const vert = builder.vert;
vert.addUniform("u_pClassProj", 7 /* VariableType.Mat4 */, (prog) => {
prog.addGraphicUniform("u_pClassProj", (uniform, params) => {
const source = params.target.currentPlanarClassifierOrDrape;
assert(undefined !== source || undefined !== params.target.activeVolumeClassifierTexture);
if (undefined !== params.target.currentPlanarClassifierOrDrape) {
source.projectionMatrix.multiplyMatrixMatrix(Matrix4d.createTransform(params.target.currentTransform, scratchModel), scratchModelProjection);
scratchMatrix.initFromMatrix4d(scratchModelProjection);
}
else
scratchMatrix.initIdentity(); // needs to be identity for volume classifiers
uniform.setMatrix4(scratchMatrix);
});
});
if (vert.usesInstancedGeometry)
addInstancedRtcMatrix(vert);
builder.addInlineComputedVarying("v_pClassPos", 3 /* VariableType.Vec2 */, vert.usesInstancedGeometry ? computeInstancedClassifierPos : computeClassifierPos);
builder.addInlineComputedVarying("v_pClassPosW", 2 /* VariableType.Float */, computeClassifierPosW);
addPlanarClassifierConstants(builder.frag);
}
function addPlanarClassifierConstants(builder) {
builder.addDefine("kClassifierDisplay_Off", SpatialClassifierInsideDisplay.Off.toFixed(1));
builder.addDefine("kClassifierDisplay_On", SpatialClassifierInsideDisplay.On.toFixed(1));
builder.addDefine("kClassifierDisplay_Dimmed", SpatialClassifierInsideDisplay.Dimmed.toFixed(1));
builder.addDefine("kClassifierDisplay_Hilite", SpatialClassifierInsideDisplay.Hilite.toFixed(1));
builder.addDefine("kClassifierDisplay_Element", SpatialClassifierInsideDisplay.ElementColor.toFixed(1));
const td = SpatialClassifierInsideDisplay.ElementColor + 1;
builder.addDefine("kTextureDrape", td.toFixed(1));
builder.addDefine("kTextureContentClassifierOnly", PlanarClassifierContent.ClassifierOnly.toFixed(1));
builder.addDefine("kTextureContentMaskOnly", PlanarClassifierContent.MaskOnly.toFixed(1));
builder.addDefine("kTextureContentClassifierAndMask", PlanarClassifierContent.ClassifierAndMask.toFixed(1));
}
/** @internal */
export function addColorPlanarClassifier(builder, translucent, isThematic) {
addPlanarClassifierCommon(builder);
const frag = builder.frag;
frag.addUniform("s_pClassSampler", 8 /* VariableType.Sampler2D */, (prog) => {
prog.addGraphicUniform("s_pClassSampler", (uniform, params) => {
const source = params.target.currentPlanarClassifierOrDrape;
const volClass = params.target.activeVolumeClassifierTexture;
assert(undefined !== source || undefined !== volClass);
if (source) {
assert(undefined !== source.texture);
source.texture.texture.bindSampler(uniform, TextureUnit.PlanarClassification);
}
else
Texture2DHandle.bindSampler(uniform, volClass, TextureUnit.PlanarClassification);
});
});
frag.addUniform("u_pClassColorParams", 5 /* VariableType.Vec4 */, (prog) => {
prog.addGraphicUniform("u_pClassColorParams", (uniform, params) => {
const source = params.target.currentPlanarClassifierOrDrape;
const volClass = params.target.activeVolumeClassifierTexture;
assert(undefined !== source || undefined !== volClass);
if (undefined !== source) {
source.getParams(scratchColorParams);
}
else {
scratchColorParams[0] = 6.0; // Volume classifier, by element color.
scratchColorParams[1] = 0.5; // used for alpha value
scratchColorParams[2] = 0.0; // Not used for volume.
scratchColorParams[3] = 0.0; // Not used for volume.
}
uniform.setUniform4fv(scratchColorParams);
});
});
if (isThematic === 0 /* IsThematic.No */) {
frag.addUniform("u_pClassPointCloud", 0 /* VariableType.Boolean */, (prog) => {
prog.addGraphicUniform("u_pClassPointCloud", (uniform, params) => {
const classifier = params.target.currentPlanarClassifier;
const isPointCloud = undefined !== classifier && classifier.isClassifyingPointCloud;
uniform.setUniform1i(isPointCloud ? 1 : 0);
});
});
}
addClassifierFlash(frag);
if (translucent)
// We will never call the shaders for volume classifiers with translucency,
// so use a different version of the function which does not use glFragCoord to reduce the varyings count
frag.addFunction(volClassTranslucentColor);
else {
addWindowToTexCoords(frag);
frag.addFunction(volClassOpaqueColor);
}
addShaderFlags(builder);
frag.set(13 /* FragmentShaderComponent.ApplyPlanarClassifier */, (isThematic === 0 /* IsThematic.No */) ? applyPlanarClassificationColor : applyPlanarClassificationColorForThematic);
}
/** @internal */
export function addFeaturePlanarClassifier(builder) {
const frag = builder.frag;
frag.addUniform("u_batchBase", 5 /* VariableType.Vec4 */, (prog) => {
prog.addGraphicUniform("u_batchBase", (uniform, params) => {
const classifier = params.target.currentPlanarClassifier;
if (classifier !== undefined) {
scratchBatchBaseId[0] = classifier.baseBatchId;
scratchBatchBaseComponents[0] = scratchBytes[0];
scratchBatchBaseComponents[1] = scratchBytes[1];
scratchBatchBaseComponents[2] = scratchBytes[2];
scratchBatchBaseComponents[3] = scratchBytes[3];
}
uniform.setUniform4fv(scratchBatchBaseComponents);
});
});
frag.set(19 /* FragmentShaderComponent.OverrideFeatureId */, overrideFeatureId);
frag.addFunction(addUInt32s);
}
/** @internal */
export function addHilitePlanarClassifier(builder, supportTextures = true) {
addPlanarClassifierCommon(builder);
const frag = builder.frag;
frag.addUniform("s_pClassHiliteSampler", 8 /* VariableType.Sampler2D */, (prog) => {
prog.addGraphicUniform("s_pClassHiliteSampler", (uniform, params) => {
const classifier = params.target.currentPlanarClassifier;
assert(undefined !== classifier && undefined !== classifier.hiliteTexture);
classifier.hiliteTexture.texture.bindSampler(uniform, TextureUnit.PlanarClassificationHilite);
});
});
frag.set(1 /* FragmentShaderComponent.ComputeBaseColor */, supportTextures ? computeClassifiedSurfaceHiliteColor : computeClassifiedHiliteColor);
}
// NonLocatable flag is put in upper bit of blue component when drawing the classification texture.
const encodeNonLocatableWithFeatures = `
vec4 encodeNonLocatable(vec4 clr) {
float encoded_b = (floor(clr.b * 127.0) + float(extractNthBit(floor(v_feature_emphasis + 0.5), kEmphBit_NonLocatable)) * 128.0) / 255.0;
return vec4(clr.r, clr.g, encoded_b, clr.a);
}
`;
const encodeNonLocatable = `
vec4 encodeNonLocatable(vec4 clr) {
float encoded_b = floor(clr.b * 127.0) / 255.0;
return vec4(clr.r, clr.g, encoded_b, clr.a);
}
`;
const overrideClassifierColorPrelude = `
if (0.0 == u_planarClassifierInsideMode)
return currentColor;
if (0.0 == currentColor.a)
return encodeNonLocatable(vec4(0.0, 0.0, 1.0, 0.5));
`;
const overrideClassifierEmphasis = `
if (kClassifierDisplay_Element != u_planarClassifierInsideMode) {
float emph = floor(v_feature_emphasis + 0.5);
if (0.0 != emph)
return encodeNonLocatable(vec4(extractNthBit(emph, kEmphBit_Hilite), extractNthBit(emph, kEmphBit_Flash), 0.0, 0.5));
}
`;
const overrideClassifierColorPostlude = `
return encodeNonLocatable(currentColor);
`;
const overrideClassifierWithFeatures = overrideClassifierColorPrelude + overrideClassifierEmphasis + overrideClassifierColorPostlude;
const overrideClassifierForClip = overrideClassifierColorPrelude + overrideClassifierColorPostlude;
const overrideClassifierColorPreludeForThematic = `
if (0.0 == u_planarClassifierInsideMode)
return currentColor;
if (0.0 == currentColor.a)
return encodeNonLocatable(vec4(0.0, 0.0, 1.0, 0.5));
bool isElem = kClassifierDisplay_Element == u_planarClassifierInsideMode;
`;
const overrideClassifierEmphasisForThematic = `
float emph = floor(v_feature_emphasis + 0.5);
if (0.0 != emph)
return encodeNonLocatable(vec4(extractNthBit(emph, kEmphBit_Hilite), extractNthBit(emph, kEmphBit_Flash), isElem ? currentColor.a : 0.0, isElem ? 1.0 : 0.5));
else if (kClassifierDisplay_Element == u_planarClassifierInsideMode)
return encodeNonLocatable(vec4(0.0, 0.0, currentColor.a, 1.0));
`;
// Thematic classifiers use alpha of 1 to blend; we just want thematic colors to largely win out except when selecting and flashing classifiers.
const overrideClassifierColorPostludeClipForThematic = `
return encodeNonLocatable(isElem ? vec4(0.0, 0.0, 1.0, 1.0) : currentColor);
`;
const overrideClassifierWithFeaturesForThematic = overrideClassifierColorPreludeForThematic + overrideClassifierEmphasisForThematic + overrideClassifierColorPostlude;
const overrideClassifierForClipForThematic = overrideClassifierColorPreludeForThematic + overrideClassifierColorPostludeClipForThematic;
/** The classified geometry needs some information about the classifier geometry. The classified fragment shader outputs special values that do not represent valid RGB+A combinations when using
* pre-multiplied alpha. The alpha channel will be 0.5, and the red, green, and/or blue channels will be 1.0:
* - Red: hilited.
* - Green: flashed.
* - Blue: fully-transparent. Indicates clipping mask (discard the classified pixel).
* @internal
*/
export function addOverrideClassifierColor(builder, isThematic) {
addPlanarClassifierConstants(builder.frag);
builder.frag.addUniform("u_planarClassifierInsideMode", 2 /* VariableType.Float */, (prog) => {
prog.addGraphicUniform("u_planarClassifierInsideMode", (uniform, params) => {
const classifier = params.target.currentlyDrawingClassifier;
const override = undefined !== classifier ? classifier.insideDisplay : 0;
uniform.setUniform1f(override);
});
});
const haveOverrides = undefined !== builder.frag.find("v_feature_emphasis");
builder.frag.addFunction(haveOverrides ? encodeNonLocatableWithFeatures : encodeNonLocatable);
if (isThematic === 0 /* IsThematic.No */)
builder.frag.set(21 /* FragmentShaderComponent.OverrideColor */, haveOverrides ? overrideClassifierWithFeatures : overrideClassifierForClip);
else
builder.frag.set(21 /* FragmentShaderComponent.OverrideColor */, haveOverrides ? overrideClassifierWithFeaturesForThematic : overrideClassifierForClipForThematic);
}
//# sourceMappingURL=PlanarClassification.js.map