@itwin/core-frontend
Version:
iTwin.js frontend components
347 lines • 18.8 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.SurfaceGeometry = void 0;
exports.wantMaterials = wantMaterials;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const SurfaceParams_1 = require("../../../common/internal/render/SurfaceParams");
const AttributeMap_1 = require("./AttributeMap");
const GL_1 = require("./GL");
const AttributeBuffers_1 = require("./AttributeBuffers");
const System_1 = require("./System");
const MeshGeometry_1 = require("./MeshGeometry");
const MapLayerParams_1 = require("./MapLayerParams");
const internal_1 = require("../../../tile/internal");
const core_geometry_1 = require("@itwin/core-geometry");
/** @internal */
function wantMaterials(vf) {
return vf.materials && core_common_1.RenderMode.SmoothShade === vf.renderMode;
}
function wantLighting(vf) {
return core_common_1.RenderMode.SmoothShade === vf.renderMode && vf.lighting;
}
/** @internal */
class SurfaceGeometry extends MeshGeometry_1.MeshGeometry {
_buffers;
_indices;
hasTextures;
textureParams;
get lutBuffers() { return this._buffers; }
static create(mesh, params) {
const indices = params.surface.indices;
const indexBuffer = AttributeBuffers_1.BufferHandle.createArrayBuffer(indices.data);
const tile = params.tileData;
const layerClassifiers = tile?.layerClassifiers;
if (!layerClassifiers?.size || !tile || undefined === layerClassifiers) {
return undefined !== indexBuffer ? new SurfaceGeometry(indexBuffer, indices.length, mesh, undefined) : undefined;
}
const transformECEF = tile.ecefTransform;
const tileEcefRange = transformECEF.multiplyRange(tile.range);
const cartographicRange = new core_common_1.CartographicRange(tileEcefRange, transformECEF);
const boundingBox = cartographicRange.getLongitudeLatitudeBoundingBox();
const mapCartoRectangle = internal_1.MapCartoRectangle.fromRadians(boundingBox.low.x, boundingBox.low.y, boundingBox.high.x, boundingBox.high.y);
const corners = tile.range.corners();
const normal = core_geometry_1.Vector3d.createCrossProductToPoints(corners[0], corners[1], corners[2])?.normalize();
if (!normal) {
return undefined !== indexBuffer ? new SurfaceGeometry(indexBuffer, indices.length, mesh, undefined) : undefined;
}
const chordHeight = corners[0].distance(corners[3]) / 2;
const surfacePlanarTilePatch = new internal_1.PlanarTilePatch(corners, normal, chordHeight);
const surfaceProjection = new internal_1.PlanarProjection(surfacePlanarTilePatch);
const meshParams = {
projection: surfaceProjection,
tileRectangle: mapCartoRectangle,
tileId: undefined,
baseColor: undefined,
baseTransparent: false,
layerClassifiers
};
const layerTextures = [];
let sequentialIndex = 0;
layerClassifiers?.forEach((layerClassifier) => {
layerTextures[sequentialIndex++] = new MapLayerParams_1.ProjectedTexture(layerClassifier, meshParams, meshParams.tileRectangle);
});
let surfaceGeometry;
if (undefined !== indexBuffer) {
const indexCount = indices.length;
const hasLayerTextures = layerTextures.length > 0;
const layerTextureParams = hasLayerTextures ? MapLayerParams_1.LayerTextureParams.create(layerTextures) : undefined;
surfaceGeometry = new SurfaceGeometry(indexBuffer, indexCount, mesh, layerTextureParams);
}
return surfaceGeometry;
}
get isDisposed() {
return this._buffers.isDisposed
&& this._indices.isDisposed;
}
[Symbol.dispose]() {
(0, core_bentley_1.dispose)(this._buffers);
(0, core_bentley_1.dispose)(this._indices);
}
collectStatistics(stats) {
stats.addSurface(this._indices.bytesUsed);
}
get isLit() { return SurfaceParams_1.SurfaceType.Lit === this.surfaceType || SurfaceParams_1.SurfaceType.TexturedLit === this.surfaceType; }
get isTexturedType() { return SurfaceParams_1.SurfaceType.Textured === this.surfaceType || SurfaceParams_1.SurfaceType.TexturedLit === this.surfaceType; }
get hasTexture() { return this.isTexturedType && undefined !== this.texture; }
get hasNormalMap() { return this.isLit && this.isTexturedType && undefined !== this.normalMap; }
get isGlyph() { return this.mesh.isGlyph; }
get alwaysRenderTranslucent() { return this.isGlyph; }
get isTileSection() { return undefined !== this.texture && this.texture.isTileSection; }
get isClassifier() { return SurfaceParams_1.SurfaceType.VolumeClassifier === this.surfaceType; }
get supportsThematicDisplay() {
return !this.isGlyph;
}
get allowColorOverride() {
// Text background color should not be overridden by feature symbology overrides - otherwise it becomes unreadable...
// We don't actually know if we have text.
// We do know that text background color uses blanking fill. So do ImageGraphics, so they're also going to forbid overriding their color.
return core_common_1.FillFlags.Blanking !== (this.fillFlags & core_common_1.FillFlags.Blanking);
}
get asSurface() { return this; }
get asEdge() { return undefined; }
get asSilhouette() { return undefined; }
_draw(numInstances, instanceBuffersContainer) {
const system = System_1.System.instance;
// If we can't write depth in the fragment shader, use polygonOffset to force blanking regions to draw behind.
const offset = 2 /* RenderOrder.BlankingRegion */ === this.renderOrder && !system.supportsLogZBuffer;
if (offset) {
system.context.enable(GL_1.GL.POLYGON_OFFSET_FILL);
system.context.polygonOffset(1.0, 1.0);
}
const bufs = instanceBuffersContainer !== undefined ? instanceBuffersContainer : this._buffers;
bufs.bind();
system.drawArrays(GL_1.GL.PrimitiveType.Triangles, 0, this._numIndices, numInstances);
bufs.unbind();
if (offset)
system.context.disable(GL_1.GL.POLYGON_OFFSET_FILL);
}
wantMixMonochromeColor(target) {
// Text relies on white-on-white reversal.
return !this.isGlyph && (this.isLitSurface || this.wantTextures(target, this.hasTexture));
}
get techniqueId() { return 0 /* TechniqueId.Surface */; }
get isLitSurface() { return this.isLit; }
get hasBakedLighting() { return this.mesh.hasBakedLighting; }
get renderOrder() {
if (core_common_1.FillFlags.Behind === (this.fillFlags & core_common_1.FillFlags.Behind))
return 2 /* RenderOrder.BlankingRegion */;
let order = this.isLit ? 4 /* RenderOrder.LitSurface */ : 3 /* RenderOrder.UnlitSurface */;
if (this.isPlanar)
order = order | 8 /* RenderOrder.PlanarBit */;
return order;
}
getColor(target) {
if (core_common_1.FillFlags.Background === (this.fillFlags & core_common_1.FillFlags.Background))
return target.uniforms.style.backgroundColorInfo;
else
return this.colorInfo;
}
getPass(target) {
// Classifiers have a dedicated pass
if (this.isClassifier)
return "classification";
let opaquePass = this.isPlanar ? "opaque-planar" : "opaque";
// When reading pixels, glyphs are always opaque. Otherwise always transparent (for anti-aliasing).
if (this.isGlyph)
return target.isReadPixelsInProgress ? opaquePass : "translucent";
const vf = target.currentViewFlags;
// When rendering thematic isolines, we need translucency because they have anti-aliasing.
const thematic = target.wantThematicDisplay && this.supportsThematicDisplay ? target.uniforms.thematic.thematicDisplay : undefined;
if (thematic && target.uniforms.thematic.wantIsoLines)
return "translucent";
// In wireframe, unless fill is explicitly enabled for planar region, surface does not draw
if (core_common_1.RenderMode.Wireframe === vf.renderMode && !this.mesh.isTextureAlwaysDisplayed) {
const fillFlags = this.fillFlags;
const showFill = core_common_1.FillFlags.Always === (fillFlags & core_common_1.FillFlags.Always) || (vf.fill && core_common_1.FillFlags.ByView === (fillFlags & core_common_1.FillFlags.ByView));
if (!showFill)
return "none";
}
// If transparency disabled by render mode or view flag, always draw opaque.
if (!vf.transparency || core_common_1.RenderMode.SolidFill === vf.renderMode || core_common_1.RenderMode.HiddenLine === vf.renderMode)
return opaquePass;
// A gradient texture applied by analysis style always fully determines the transparency of the surface.
if (this.hasScalarAnimation && undefined !== target.analysisTexture) {
(0, core_bentley_1.assert)(undefined !== target.analysisStyle?.thematic);
switch (target.analysisStyle.thematic.thematicSettings.textureTransparency) {
case core_common_1.TextureTransparency.Translucent:
return "translucent";
case core_common_1.TextureTransparency.Opaque:
return opaquePass;
case core_common_1.TextureTransparency.Mixed:
return `${opaquePass}-translucent`;
}
}
// We have 3 sources of alpha: the material, the texture, and the color.
// Base alpha comes from the material if it overrides it; otherwise from the color.
// The texture's alpha is multiplied by the base alpha.
// So we must draw in the translucent pass if the texture has transparency OR the base alpha is less than 1.
let hasAlpha = false;
const mat = wantMaterials(vf) ? this.mesh.materialInfo : undefined;
if (undefined !== mat && mat.overridesAlpha)
hasAlpha = mat.hasTranslucency;
else
hasAlpha = this.getColor(target).hasTranslucency;
// Thematic gradient can optionally multiply gradient alpha with surface alpha.
if (thematic && thematic.gradientSettings.transparencyMode === core_common_1.ThematicGradientTransparencyMode.MultiplySurfaceAndGradient) {
switch (thematic.gradientSettings.textureTransparency) {
case core_common_1.TextureTransparency.Opaque:
// This surface's alpha gets multiplied by 1 - gradient colors are all opaque.
return hasAlpha ? "translucent" : opaquePass;
case core_common_1.TextureTransparency.Translucent:
// This surface's alpha gets multiplied by < 1 - gradient colors are all translucent.
return "translucent";
case core_common_1.TextureTransparency.Mixed:
// The gradient contains a mix of translucent and opaque colors.
return hasAlpha ? "translucent" : `${opaquePass}-translucent`;
}
}
if (!hasAlpha) {
const tex = this.wantTextures(target, true) ? this.texture : undefined;
switch (tex?.transparency) {
case core_common_1.TextureTransparency.Translucent:
hasAlpha = true;
break;
case core_common_1.TextureTransparency.Mixed:
opaquePass = `${opaquePass}-translucent`;
break;
}
}
return hasAlpha ? "translucent" : opaquePass;
}
_wantWoWReversal(target) {
if (this.isGlyph) {
// Raster text is always subject to white-on-white reversal.
return true;
}
const fillFlags = this.fillFlags;
if (core_common_1.FillFlags.None !== (fillFlags & core_common_1.FillFlags.Background)) {
return false; // fill color explicitly from background
}
if (this.wantTextures(target, this.hasTexture)) {
// Don't invert white pixels of textures.
return false;
}
const vf = target.currentViewFlags;
if (core_common_1.RenderMode.Wireframe === vf.renderMode) {
// Fill displayed even in wireframe?
return core_common_1.FillFlags.None !== (fillFlags & core_common_1.FillFlags.Always);
}
if (vf.visibleEdges) {
return false; // never invert surfaces when edges are displayed
}
if (this.isLit && wantLighting(vf)) {
return false; // the lit color won't be pure white anyway.
}
return true;
}
get materialInfo() { return this.mesh.materialInfo; }
useTexture(params) {
return this.wantTextures(params.target, this.hasTexture);
}
useNormalMap(params) {
return this.wantNormalMaps(params.target, this.hasNormalMap);
}
computeSurfaceFlags(params, flags) {
const target = params.target;
const vf = target.currentViewFlags;
const useMaterial = wantMaterials(vf);
flags[3 /* SurfaceBitIndex.IgnoreMaterial */] = useMaterial ? 0 : 1;
flags[9 /* SurfaceBitIndex.HasMaterialAtlas */] = useMaterial && this.hasMaterialAtlas ? 1 : 0;
flags[1 /* SurfaceBitIndex.ApplyLighting */] = 0;
flags[6 /* SurfaceBitIndex.HasColorAndNormal */] = 0;
if (this.isLit) {
flags[2 /* SurfaceBitIndex.HasNormals */] = 1;
if (wantLighting(vf))
flags[1 /* SurfaceBitIndex.ApplyLighting */] = 1;
// Textured meshes store normal in place of color index.
// Untextured lit meshes store normal where textured meshes would store UV coords.
// Tell shader where to find normal.
if (!this.isTexturedType) {
flags[6 /* SurfaceBitIndex.HasColorAndNormal */] = 1;
}
}
else {
flags[2 /* SurfaceBitIndex.HasNormals */] = 0;
}
flags[0 /* SurfaceBitIndex.HasTexture */] = this.useTexture(params) ? 1 : 0;
flags[8 /* SurfaceBitIndex.HasNormalMap */] = this.useNormalMap(params) ? 1 : 0;
flags[10 /* SurfaceBitIndex.UseConstantLodTextureMapping */] = this.mesh.textureUsesConstantLod ? 1 : 0;
flags[11 /* SurfaceBitIndex.UseConstantLodNormalMapMapping */] = this.mesh.normalMapUsesConstantLod ? 1 : 0;
// The transparency threshold controls how transparent a surface must be to allow light to pass through; more opaque surfaces cast shadows.
flags[4 /* SurfaceBitIndex.TransparencyThreshold */] = params.target.isDrawingShadowMap ? 1 : 0;
flags[5 /* SurfaceBitIndex.BackgroundFill */] = 0;
switch (params.renderPass) {
// NB: We need this for opaque pass due to SolidFill (must compute transparency, discard below threshold, render opaque at or above threshold)
case 2 /* RenderPass.OpaqueLinear */:
case 3 /* RenderPass.OpaquePlanar */:
case 5 /* RenderPass.OpaqueGeneral */:
case 8 /* RenderPass.Translucent */:
case 12 /* RenderPass.WorldOverlay */:
case 1 /* RenderPass.OpaqueLayers */:
case 7 /* RenderPass.TranslucentLayers */:
case 11 /* RenderPass.OverlayLayers */: {
const mode = vf.renderMode;
if (!this.isGlyph && (core_common_1.RenderMode.HiddenLine === mode || core_common_1.RenderMode.SolidFill === mode)) {
flags[4 /* SurfaceBitIndex.TransparencyThreshold */] = 1;
if (core_common_1.RenderMode.HiddenLine === mode && core_common_1.FillFlags.Always !== (this.fillFlags & core_common_1.FillFlags.Always)) {
// fill flags test for text - doesn't render with bg fill in hidden line mode.
flags[5 /* SurfaceBitIndex.BackgroundFill */] = 1;
}
break;
}
}
}
}
constructor(indices, numIndices, mesh, textureParams) {
super(mesh, numIndices);
this.textureParams = textureParams;
this._buffers = AttributeBuffers_1.BuffersContainer.create();
const attrPos = AttributeMap_1.AttributeMap.findAttribute("a_pos", 0 /* TechniqueId.Surface */, false);
(0, core_bentley_1.assert)(undefined !== attrPos);
this._buffers.addBuffer(indices, [AttributeBuffers_1.BufferParameters.create(attrPos.location, 3, GL_1.GL.DataType.UnsignedByte, false, 0, 0, false)]);
this._indices = indices;
this.hasTextures = undefined !== this.textureParams && this.textureParams.params.some((x) => undefined !== x.texture);
}
wantTextures(target, surfaceTextureExists) {
if (this.hasScalarAnimation && undefined !== target.analysisTexture)
return true;
if (!surfaceTextureExists)
return false;
if (this.mesh.isTextureAlwaysDisplayed)
return true;
if (this.supportsThematicDisplay && target.wantThematicDisplay)
return false;
const fill = this.fillFlags;
const flags = target.currentViewFlags;
// ###TODO need to distinguish between gradient fill and actual textures...
switch (flags.renderMode) {
case core_common_1.RenderMode.SmoothShade:
return flags.textures;
case core_common_1.RenderMode.Wireframe:
return core_common_1.FillFlags.Always === (fill & core_common_1.FillFlags.Always) || (flags.fill && core_common_1.FillFlags.ByView === (fill & core_common_1.FillFlags.ByView));
default:
return core_common_1.FillFlags.Always === (fill & core_common_1.FillFlags.Always);
}
}
wantNormalMaps(target, normalMapExists) {
if (!normalMapExists || !target.displayNormalMaps)
return false;
const flags = target.currentViewFlags;
switch (flags.renderMode) {
case core_common_1.RenderMode.SmoothShade:
return flags.textures;
default:
return false;
}
}
}
exports.SurfaceGeometry = SurfaceGeometry;
//# sourceMappingURL=SurfaceGeometry.js.map