@itwin/core-frontend
Version:
iTwin.js frontend components
325 lines (293 loc) • 12.2 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 { AttributeMap } from "../AttributeMap";
import { TextureUnit } from "../RenderFlags";
import { ProgramBuilder, } from "../ShaderBuilder";
import { System } from "../System";
import { addColor } from "./Color";
import { addEdgeContrast } from "./Edge";
import { addFrustum, addShaderFlags } from "./Common";
import { unquantize2d } from "./Decode";
import { addHiliter } from "./FeatureSymbology";
import { addWhiteOnWhiteReversal } from "./Fragment";
import { addLineCode as addLineCodeUniform, addLineWeight, addModelViewMatrix, addProjectionMatrix, addSamplePosition, } from "./Vertex";
import { addModelToWindowCoordinates, addViewport } from "./Viewport";
const checkForDiscard = "return discardByLineCode;";
const applyLineCode = `
if (v_texc.x >= 0.0) { // v_texc = (-1,-1) for solid lines - don't bother with any of this
vec4 texColor = TEXTURE(u_lineCodeTexture, v_texc);
discardByLineCode = (0.0 == texColor.r);
}
if (v_lnInfo.w > 0.5) { // line needs pixel trimming
// calculate pixel distance from pixel center to expected line center, opposite dir from major
vec2 dxy = gl_FragCoord.xy - v_lnInfo.xy;
if (v_lnInfo.w < 1.5) // not x-major
dxy = dxy.yx;
float dist = v_lnInfo.z * dxy.x - dxy.y;
float distA = abs(dist);
if (distA > 0.5 || (distA == 0.5 && dist < 0.0))
discardByLineCode = true; // borrow this flag to force discard
}
return baseColor;
`;
const computeTextureCoord = `
vec2 computeLineCodeTextureCoords(vec2 windowDir, vec4 projPos, float adjust) {
vec2 texc;
float lineCode = computeLineCode();
if (0.0 == lineCode) {
// Solid line - tell frag shader not to bother.
texc = vec2(-1.0, -1.0);
} else {
const float imagesPerPixel = 1.0/32.0;
const float textureCoordinateBase = 8192.0; // Temp workardound for clipping problem in perspective views (negative values don't seem to interpolate correctly).
if (abs(windowDir.x) > abs(windowDir.y))
texc.x = textureCoordinateBase + imagesPerPixel * (projPos.x + adjust * windowDir.x);
else
texc.x = textureCoordinateBase + imagesPerPixel * (projPos.y + adjust * windowDir.y);
const float numLineCodes = 16.0; // NB: Actually only 10, but texture is 16px tall because it needs to be a power of 2.
const float rowsPerCode = 1.0;
const float numRows = numLineCodes*rowsPerCode;
const float centerY = 0.5/numRows;
const float stepY = rowsPerCode/numRows;
texc.y = stepY * lineCode + centerY;
}
return texc;
}
`;
/** @internal */
export const adjustWidth = `
void adjustWidth(inout float width, vec2 d2, vec2 org) {
if (u_aaSamples > 1) {
if (width < 5.0)
width += (5.0 - width) * 0.125;
return;
}
// calculate slope based width adjustment for non-AA lines, widths 1 to 4
vec2 d2A = abs(d2);
const float s_myFltEpsilon = 0.0001; // limit test resolution to 4 digits in case 24 bit (s16e7) is used in hardware
if (d2A.y > s_myFltEpsilon && width < 4.5) {
float len = length(d2A);
float tan = d2A.x / d2A.y;
if (width < 1.5) { // width 1
if (tan <= 1.0)
width = d2A.y;
else
width = d2A.x;
// width 1 requires additional adjustment plus trimming in frag shader using v_lnInfo
width *= 1.01;
v_lnInfo.xy = org;
v_lnInfo.w = 1.0; // set flag to do trimming
// set slope in v_lnInfo.z
if (d2A.x - d2A.y > s_myFltEpsilon) {
v_lnInfo.z = d2.y / d2.x;
v_lnInfo.w += 2.0; // add in x-major flag
} else
v_lnInfo.z = d2.x / d2.y;
} else if (width < 2.5) { // width 2
if (tan <= 0.5)
width = 2.0 * d2A.y;
else
width = (d2A.y + 2.0 * d2A.x);
} else if (width < 3.5) { // width 3
if (tan <= 1.0)
width = (3.0 * d2A.y + d2A.x);
else
width = (d2A.y + 3.0 * d2A.x);
} else { // if (width < 4.5) // width 4
if (tan <= 0.5)
width = (4.0 * d2A.y + d2A.x);
else if (tan <= 2.0)
width = (3.0 * d2A.y + 3.0 * d2A.x);
else
width = (d2A.y + 4.0 * d2A.x);
}
width /= len;
}
}
`;
/** @internal */
export function addAdjustWidth(vert) {
vert.addUniform("u_aaSamples", 1 /* VariableType.Int */, (prog) => {
prog.addGraphicUniform("u_aaSamples", (attr, params) => {
const numSamples = System.instance.frameBufferStack.currentFbMultisampled ? params.target.compositor.antialiasSamples : 1;
attr.setUniform1i(numSamples);
});
});
vert.addFunction(adjustWidth);
}
/** @internal */
export function addLineCodeTexture(frag) {
frag.addUniform("u_lineCodeTexture", 8 /* VariableType.Sampler2D */, (prog) => {
prog.addProgramUniform("u_lineCodeTexture", (uniform) => {
const lct = System.instance.lineCodeTexture;
assert(undefined !== lct);
if (undefined !== lct)
lct.bindSampler(uniform, TextureUnit.LineCode);
});
});
}
/** @internal */
export function addLineCode(prog, args) {
const vert = prog.vert;
const frag = prog.frag;
addLineCodeUniform(vert);
const funcCall = `computeLineCodeTextureCoords(${args})`;
prog.addFunctionComputedVaryingWithArgs("v_texc", 3 /* VariableType.Vec2 */, funcCall, computeTextureCoord);
addFrustum(prog);
addLineCodeTexture(prog.frag);
frag.set(3 /* FragmentShaderComponent.FinalizeBaseColor */, applyLineCode);
frag.set(4 /* FragmentShaderComponent.CheckForDiscard */, checkForDiscard);
frag.addGlobal("discardByLineCode", 0 /* VariableType.Boolean */, "false");
}
function polylineAddLineCode(prog) {
addLineCode(prog, lineCodeArgs);
addModelViewMatrix(prog.vert);
}
function addCommon(prog) {
const vert = prog.vert;
addModelToWindowCoordinates(vert); // adds u_mvp, u_viewportTransformation
addProjectionMatrix(vert);
addModelViewMatrix(vert);
addViewport(vert);
vert.addGlobal("g_windowPos", 5 /* VariableType.Vec4 */);
vert.addGlobal("g_prevPos", 5 /* VariableType.Vec4 */);
vert.addGlobal("g_nextPos", 5 /* VariableType.Vec4 */);
vert.addGlobal("g_windowDir", 3 /* VariableType.Vec2 */);
vert.addInitializer(decodeAdjacentPositions);
vert.addFunction(unquantize2d);
addLineWeight(vert);
vert.addGlobal("miterAdjust", 2 /* VariableType.Float */, "0.0");
prog.addVarying("v_eyeSpace", 4 /* VariableType.Vec3 */);
vert.set(10 /* VertexShaderComponent.ComputePosition */, computePosition);
prog.addVarying("v_lnInfo", 5 /* VariableType.Vec4 */);
addAdjustWidth(vert);
addSamplePosition(vert);
vert.addFunction(decodePosition);
}
const decodePosition = `
vec4 decodePosition(vec3 baseIndex) {
float index = decodeUInt24(baseIndex);
return samplePosition(index);
}
`;
const decodeAdjacentPositions = `
g_prevPos = decodePosition(a_prevIndex);
g_nextPos = decodePosition(a_nextIndex);
`;
const computePosition = `
const float kNone = 0.0,
kSquare = 1.0*3.0,
kMiter = 2.0*3.0,
kMiterInsideOnly = 3.0*3.0,
kJointBase = 4.0*3.0,
kNegatePerp = 8.0*3.0,
kNegateAlong = 16.0*3.0,
kNoneAdjWt = 32.0*3.0;
v_lnInfo = vec4(0.0, 0.0, 0.0, 0.0); // init and set flag to false
vec4 next = g_nextPos;
vec4 pos;
g_windowPos = modelToWindowCoordinates(rawPos, next, pos, v_eyeSpace);
if (g_windowPos.w == 0.0)
return g_windowPos;
float param = a_param;
float weight = computeLineWeight();
float scale = 1.0, directionScale = 1.0;
if (param >= kNoneAdjWt)
param -= kNoneAdjWt;
if (param >= kNegateAlong) {
directionScale = -directionScale;
param -= kNegateAlong;
}
if (param >= kNegatePerp) {
scale = -1.0;
param -= kNegatePerp;
}
vec4 otherPos;
vec3 otherMvPos;
vec4 projNext = modelToWindowCoordinates(next, rawPos, otherPos, otherMvPos);
g_windowDir = projNext.xy - g_windowPos.xy;
if (param < kJointBase) {
vec2 dir = (directionScale > 0.0) ? g_windowDir : -g_windowDir;
vec2 pos = (directionScale > 0.0) ? g_windowPos.xy : projNext.xy;
adjustWidth(weight, dir, pos);
}
if (kNone != param) {
vec2 delta = vec2(0.0);
vec4 prev = g_prevPos;
vec4 projPrev = modelToWindowCoordinates(prev, rawPos, otherPos, otherMvPos);
vec2 prevDir = g_windowPos.xy - projPrev.xy;
float thisLength = sqrt(g_windowDir.x * g_windowDir.x + g_windowDir.y * g_windowDir.y);
const float s_minNormalizeLength = 1.0E-5; // avoid normalizing zero length vectors.
float dist = weight / 2.0;
if (thisLength > s_minNormalizeLength) {
g_windowDir /= thisLength;
float prevLength = sqrt(prevDir.x * prevDir.x + prevDir.y * prevDir.y);
if (prevLength > s_minNormalizeLength) {
prevDir /= prevLength;
const float s_minParallelDot= -.9999, s_maxParallelDot = .9999;
float prevNextDot = dot(prevDir, g_windowDir);
if (prevNextDot < s_minParallelDot || prevNextDot > s_maxParallelDot) // No miter if parallel or antiparallel.
param = kSquare;
} else
param = kSquare;
} else {
g_windowDir = -normalize(prevDir);
param = kSquare;
}
vec2 perp = scale * vec2(-g_windowDir.y, g_windowDir.x);
if (param == kSquare) {
delta = perp;
} else {
vec2 bisector = normalize(prevDir - g_windowDir);
float dotP = dot (bisector, perp);
if (dotP != 0.0) { // Should never occur - but avoid divide by zero.
const float maxMiter = 3.0;
float miterDistance = 1.0/dotP;
if (param == kMiter) { // Straight miter.
delta = (abs(miterDistance) > maxMiter) ? perp : bisector * miterDistance;
} else if (param == kMiterInsideOnly) { // Miter at inside, square at outside (to make room for joint).
delta = (dotP > 0.0 || abs(miterDistance) > maxMiter) ? perp : bisector * miterDistance;
} else {
const float jointTriangleCount = 3.0;
float ratio = (param - kJointBase) / jointTriangleCount; // 3 triangles per half-joint as defined in Graphics.cpp
delta = normalize((1.0 - ratio) * bisector + (dotP < 0.0 ? -ratio : ratio) * perp); // Miter/Straight combination.
}
}
}
miterAdjust = dot(g_windowDir, delta) * dist; // Not actually used for hilite shader but meh.
pos.x += dist * delta.x * 2.0 * pos.w / u_viewport.x;
pos.y += dist * delta.y * 2.0 * pos.w / u_viewport.y;
}
return pos;
`;
const lineCodeArgs = "g_windowDir, g_windowPos, miterAdjust";
/** @internal */
export function createPolylineBuilder(isInstanced, positionType) {
const instanced = 1 /* IsInstanced.Yes */ === isInstanced;
const attrMap = AttributeMap.findAttributeMap(1 /* TechniqueId.Polyline */, instanced);
const builder = new ProgramBuilder(attrMap, { positionType, instanced });
addShaderFlags(builder);
addCommon(builder);
polylineAddLineCode(builder);
addColor(builder);
addEdgeContrast(builder.vert);
addWhiteOnWhiteReversal(builder.frag);
return builder;
}
/** @internal */
export function createPolylineHiliter(isInstanced, positionType) {
const instanced = 1 /* IsInstanced.Yes */ === isInstanced;
const attrMap = AttributeMap.findAttributeMap(1 /* TechniqueId.Polyline */, instanced);
const builder = new ProgramBuilder(attrMap, { positionType, instanced });
addCommon(builder);
addFrustum(builder);
addHiliter(builder, true);
return builder;
}
//# sourceMappingURL=Polyline.js.map