@itwin/core-frontend
Version:
iTwin.js frontend components
308 lines (287 loc) • 14.6 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getComputeThematicIndex = getComputeThematicIndex;
exports.addThematicDisplay = addThematicDisplay;
const core_common_1 = require("@itwin/core-common");
const System_1 = require("../System");
const RenderPass_1 = require("./RenderPass");
const Vertex_1 = require("./Vertex");
const RenderFlags_1 = require("../RenderFlags");
const Common_1 = require("./Common");
const getSensorFloat = `
vec4 getSensor(int index) {
float x = 0.5;
float y = (float(index) + 0.5) / float(u_numSensors);
return TEXTURE(s_sensorSampler, vec2(x, y));
}
`;
// Access a gradient texture at the specified index.
// A stepped gradient texture is arranged with single unique color pixels for each step. The dimension of a stepped gradient texture is stepCount.
// A smooth gradient texture is arranged with blended color pixels across the entire span of the texture. The dimension of a smooth gradient texture is the system's maximum texture size.
const getColor = `
vec4 getColor(float ndx) {
if (ndx < 0.0 || ndx > 1.0)
return u_marginColor;
return TEXTURE(s_texture, vec2(0.0, ndx));
}
`;
// Access a stepped gradient texture at the specified index taking into account isolines.
// The texture format is exactly as described above for stepped mode. We just access the gradient differently,
// specifically to ensure that the texels sampled result in lines of overall singular colors - no stepping into the
// neighboring bands.
const getIsoLineColor = `
vec4 getIsoLineColor(float ndx, float stepCount) {
if (ndx < 0.01 || ndx > 0.99)
return u_marginColor;
ndx += 0.5 / stepCount; // center on step pixels
return TEXTURE(s_texture, vec2(0.0, ndx));
}
`;
const fwidth = `\nfloat _universal_fwidth(float coord) { return fwidth(coord); }\n`;
const slopeAndHillShadeShader = ` else if (kThematicDisplayMode_Slope == u_thematicDisplayMode) {
float d = dot(g_normal, u_thematicAxis);
if (d < 0.0)
d = -d;
// The range of d is now 0 to 1 (90 degrees to 0 degrees).
// However, the range from 0 to 1 is not linear. Therefore, we use acos() to find the actual angle in radians.
d = acos(d);
// range of d is currently 1.5708 to 0 radians.
if (d < u_thematicRange.x || d > u_thematicRange.y)
d = -1.0; // use marginColor if outside the requested range
else { // convert d from radians to 0 to 1 using requested range
d -= u_thematicRange.x;
d /= (u_thematicRange.y - u_thematicRange.x);
}
ndx = d;
} else if (kThematicDisplayMode_HillShade == u_thematicDisplayMode) {
float d = dot(g_normal, u_thematicSunDirection);
ndx = max(0.0, d);
}`;
// Access the appropriate gradient texel for a particular index based on display mode and gradient mode.
const applyThematicColorPrelude = `
float ndx = v_thematicIndex;
if (kThematicDisplayMode_InverseDistanceWeightedSensors == u_thematicDisplayMode) {
float sensorSum = 0.0;
float contributionSum = 0.0;
vec3 sensorPos;
float sensorValue;
float sensorWeight;
ndx = -1.0; // default index = marginColor
float distanceCutoff = u_thematicSettings.y;
for (int i = 0; i < 8192; i++) { // ###TODO: set maximum number of sensors during an incremental form of shader construction
if (i >= u_numSensors)
break;
vec4 sensor = getSensor(i);
float dist = distance(v_eyeSpace, sensor.xyz);
bool skipThisSensor = (distanceCutoff > 0.0 && dist > distanceCutoff);
if (!skipThisSensor) {
float contribution = 1.0 / pow(dist, 2.0);
sensorSum += sensor.w * contribution;
contributionSum += contribution;
}
}
if (contributionSum > 0.0) // avoid division by zero
ndx = sensorSum / contributionSum;
}`;
const applyThematicColorPostlude = `
float gradientMode = u_thematicSettings.x;
float stepCount = u_thematicSettings.z;
vec4 rgba = (kThematicGradientMode_IsoLines == gradientMode) ? getIsoLineColor(ndx, stepCount) : getColor(ndx);
rgba.a = baseColor.a * (u_thematicSettings.w > 0.0 ? rgba.a : 1.0);
rgba = mix(rgba, baseColor, u_thematicColorMix);
if (kThematicGradientMode_IsoLines == gradientMode) {
float coord = v_thematicIndex * stepCount;
float line = abs(fract(coord - 0.5) - 0.5) / _universal_fwidth(coord);
rgba.a = 1.0 - min(line, 1.0);
if (u_discardBetweenIsolines && 0.0 == rgba.a)
discard;
} else if (kThematicGradientMode_SteppedWithDelimiter == gradientMode) {
float coord = v_thematicIndex * stepCount;
float line = abs(fract(coord - 0.5) - 0.5) / _universal_fwidth(coord);
float value = min(line, 1.0);
rgba.rgb *= value;
}
return rgba;
`;
// fwidth does not function for point clouds, so we work around the limitation with a less-than-ideal rendering of isolines and delimiters
// using a tolerance not based on neighboring fragments.
const delimiterToleranceForPointClouds = `0.025`; // / (stepCount * 40.0)`;
const applyThematicColorPostludeForPointClouds = `
float gradientMode = u_thematicSettings.x;
float stepCount = u_thematicSettings.z;
vec4 rgba = (kThematicGradientMode_IsoLines == gradientMode) ? getIsoLineColor(ndx, stepCount) : getColor(ndx);
rgba.a = baseColor.a * (u_thematicSettings.w > 0.0 ? rgba.a : 1.0);
rgba = mix(rgba, baseColor, u_thematicColorMix);
if (kThematicGradientMode_IsoLines == gradientMode) {
float coord = v_thematicIndex * stepCount;
float line = abs(fract(coord - 0.5) - 0.5);
if (line > ${delimiterToleranceForPointClouds})
discard;
} else if (kThematicGradientMode_SteppedWithDelimiter == gradientMode) {
float coord = v_thematicIndex * stepCount;
float line = abs(fract(coord - 0.5) - 0.5);
float value = min(line, 1.0);
if (line < ${delimiterToleranceForPointClouds} && value < 1.0)
rgba.rgb *= 0.0;
}
return rgba;
`;
function _getShader(isPointCloud) {
return isPointCloud ?
applyThematicColorPrelude + applyThematicColorPostludeForPointClouds : // do not include slope and hillshade for point clouds
applyThematicColorPrelude + slopeAndHillShadeShader + applyThematicColorPostlude; // include all modes for everything else
}
// Compute the value for the varying to be interpolated to the fragment shader in order to access the color in the thematic gradient texture
// We will project a vector onto another vector using this equation: proju = (v . u) / (v . v) * v
function getComputeThematicIndex(instanced, skipSlopeAndHillShade, decodeNormal) {
const modelPos = instanced ? "(g_instancedRtcMatrix * rawPosition)" : "rawPosition";
const heightMode = `
if (kThematicDisplayMode_Height == u_thematicDisplayMode) {
vec3 u = (u_modelToWorld * ${modelPos}).xyz;
vec3 v = u_thematicAxis;
vec3 proju = (dot(v, u) / dot(v, v)) * v;
vec3 a = v * u_thematicRange.s;
vec3 b = v * u_thematicRange.t;
vec3 c = proju;
v_thematicIndex = findFractionalPositionOnLine(a, b, c);
}`;
const hillShadeMode = ` else if (kThematicDisplayMode_HillShade == u_thematicDisplayMode) {
v_thematicIndex = computeSurfaceNormal().z;
}`;
const hillShadeMode2 = ` else if (kThematicDisplayMode_HillShade == u_thematicDisplayMode) {
v_thematicIndex = g_hillshadeIndex;
}`;
return skipSlopeAndHillShade ? heightMode : heightMode + (decodeNormal ? hillShadeMode : hillShadeMode2);
}
// Determine the fractional position of c on line segment ab. Assumes the three points are aligned on the same axis.
const findFractionalPositionOnLine = `
float abDist = distance(a, b);
return dot(b - a, c - a) / (abDist * abDist);
`;
function addThematicDisplayModeConstants(builder) {
builder.addDefine("kThematicDisplayMode_Height", core_common_1.ThematicDisplayMode.Height.toFixed(1));
builder.addDefine("kThematicDisplayMode_InverseDistanceWeightedSensors", core_common_1.ThematicDisplayMode.InverseDistanceWeightedSensors.toFixed(1));
builder.addDefine("kThematicDisplayMode_Slope", core_common_1.ThematicDisplayMode.Slope.toFixed(1));
builder.addDefine("kThematicDisplayMode_HillShade", core_common_1.ThematicDisplayMode.HillShade.toFixed(1));
}
function addThematicGradientModeConstants(builder) {
builder.addDefine("kThematicGradientMode_Smooth", core_common_1.ThematicGradientMode.Smooth.toFixed(1));
builder.addDefine("kThematicGradientMode_Stepped", core_common_1.ThematicGradientMode.Stepped.toFixed(1));
builder.addDefine("kThematicGradientMode_SteppedWithDelimiter", core_common_1.ThematicGradientMode.SteppedWithDelimiter.toFixed(1));
builder.addDefine("kThematicGradientMode_IsoLines", core_common_1.ThematicGradientMode.IsoLines.toFixed(1));
}
/** @internal */
function addThematicDisplay(builder, isForPointClouds = false, isForTerrainMesh = false) {
const frag = builder.frag;
const vert = builder.vert;
(0, RenderPass_1.addRenderPass)(builder.frag);
if (!isForPointClouds && !isForTerrainMesh)
(0, Vertex_1.addProjectionMatrix)(vert);
(0, Common_1.addEyeSpace)(builder);
if (vert.usesInstancedGeometry)
(0, Vertex_1.addInstancedRtcMatrix)(vert);
vert.addFunction("float findFractionalPositionOnLine(vec3 a, vec3 b, vec3 c)", findFractionalPositionOnLine);
vert.addUniform("u_modelToWorld", 7 /* VariableType.Mat4 */, (prog) => {
prog.addGraphicUniform("u_modelToWorld", (uniform, params) => {
params.target.uniforms.branch.bindModelToWorldTransform(uniform, params.geometry, false);
});
});
builder.addUniform("u_thematicRange", 3 /* VariableType.Vec2 */, (prog) => {
prog.addGraphicUniform("u_thematicRange", (uniform, params) => {
params.target.uniforms.thematic.bindRange(uniform);
});
});
builder.addUniform("u_thematicAxis", 4 /* VariableType.Vec3 */, (prog) => {
prog.addGraphicUniform("u_thematicAxis", (uniform, params) => {
params.target.uniforms.thematic.bindAxis(uniform);
});
});
if (!isForPointClouds) {
builder.addUniform("u_thematicSunDirection", 4 /* VariableType.Vec3 */, (prog) => {
prog.addGraphicUniform("u_thematicSunDirection", (uniform, params) => {
params.target.uniforms.thematic.bindSunDirection(uniform);
});
});
}
addThematicGradientModeConstants(builder.frag);
addThematicDisplayModeConstants(builder.frag);
addThematicDisplayModeConstants(builder.vert);
builder.addUniform("u_thematicDisplayMode", 2 /* VariableType.Float */, (prog) => {
prog.addGraphicUniform("u_thematicDisplayMode", (uniform, params) => {
params.target.uniforms.thematic.bindDisplayMode(uniform);
});
});
frag.addUniform("u_marginColor", 5 /* VariableType.Vec4 */, (prog) => {
prog.addGraphicUniform("u_marginColor", (uniform, params) => {
params.target.uniforms.thematic.bindMarginColor(uniform);
});
});
// gradientMode, distanceCutoff, stepCount
builder.addUniform("u_thematicSettings", 5 /* VariableType.Vec4 */, (prog) => {
prog.addGraphicUniform("u_thematicSettings", (uniform, params) => {
params.target.uniforms.thematic.bindFragSettings(uniform);
});
});
if (isForPointClouds || isForTerrainMesh) {
builder.frag.addUniform("u_thematicColorMix", 2 /* VariableType.Float */, (prog) => {
prog.addGraphicUniform("u_thematicColorMix", (uniform, params) => {
uniform.setUniform1f(params.target.uniforms.thematic.thematicDisplay?.gradientSettings.colorMix || 0.0);
});
});
}
else {
builder.frag.addUniform("u_thematicColorMix", 2 /* VariableType.Float */, (prog) => {
prog.addGraphicUniform("u_thematicColorMix", (uniform, _params) => {
uniform.setUniform1f(0.0);
});
});
}
frag.addUniform("u_numSensors", 1 /* VariableType.Int */, (prog) => {
prog.addGraphicUniform("u_numSensors", (uniform, params) => {
if (params.target.wantThematicSensors) {
if (params.target.uniforms.thematic.wantGlobalSensorTexture)
params.target.uniforms.thematic.bindNumSensors(uniform);
else // we are batching separate sensor textures per-tile; use the number of sensors from the batch
params.target.uniforms.batch.bindNumThematicSensors(uniform);
}
else {
uniform.setUniform1i(0);
}
});
});
frag.addUniform("s_sensorSampler", 8 /* VariableType.Sampler2D */, (prog) => {
prog.addGraphicUniform("s_sensorSampler", (uniform, params) => {
if (params.target.wantThematicSensors) {
if (params.target.uniforms.thematic.wantGlobalSensorTexture) {
params.target.uniforms.thematic.bindSensors(uniform);
}
else { // we are batching separate sensor textures per-tile; bind the batch's sensor texture
params.target.uniforms.batch.bindThematicSensors(uniform);
}
}
else {
System_1.System.instance.ensureSamplerBound(uniform, RenderFlags_1.TextureUnit.ThematicSensors);
}
});
});
if (!isForPointClouds) { // allows us to know when to discard between isolines to make them pickable
builder.frag.addUniform("u_discardBetweenIsolines", 0 /* VariableType.Boolean */, (prog) => {
prog.addProgramUniform("u_discardBetweenIsolines", (uniform, params) => {
uniform.setUniform1i(params.target.isReadPixelsInProgress ? 1 : 0);
});
});
}
frag.addFunction(fwidth);
frag.addFunction(getSensorFloat);
frag.addFunction(getColor);
frag.addFunction(getIsoLineColor);
frag.set(7 /* FragmentShaderComponent.ApplyThematicDisplay */, _getShader(isForPointClouds));
}
//# sourceMappingURL=Thematic.js.map