UNPKG

@itwin/core-frontend

Version:
342 lines • 17.8 kB
/*--------------------------------------------------------------------------------------------- * 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, dispose } from "@itwin/core-bentley"; import { CartographicRange, FillFlags, RenderMode, TextureTransparency, ThematicGradientTransparencyMode } from "@itwin/core-common"; import { SurfaceType } from "../../../common/internal/render/SurfaceParams"; import { AttributeMap } from "./AttributeMap"; import { GL } from "./GL"; import { BufferHandle, BufferParameters, BuffersContainer } from "./AttributeBuffers"; import { System } from "./System"; import { MeshGeometry } from "./MeshGeometry"; import { LayerTextureParams, ProjectedTexture } from "./MapLayerParams"; import { MapCartoRectangle, PlanarProjection, PlanarTilePatch } from "../../../tile/internal"; import { Vector3d } from "@itwin/core-geometry"; /** @internal */ export function wantMaterials(vf) { return vf.materials && RenderMode.SmoothShade === vf.renderMode; } function wantLighting(vf) { return RenderMode.SmoothShade === vf.renderMode && vf.lighting; } /** @internal */ export class SurfaceGeometry extends MeshGeometry { _buffers; _indices; hasTextures; textureParams; get lutBuffers() { return this._buffers; } static create(mesh, params) { const indices = params.surface.indices; const indexBuffer = 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 CartographicRange(tileEcefRange, transformECEF); const boundingBox = cartographicRange.getLongitudeLatitudeBoundingBox(); const mapCartoRectangle = MapCartoRectangle.fromRadians(boundingBox.low.x, boundingBox.low.y, boundingBox.high.x, boundingBox.high.y); const corners = tile.range.corners(); const normal = 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 PlanarTilePatch(corners, normal, chordHeight); const surfaceProjection = new 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 ProjectedTexture(layerClassifier, meshParams, meshParams.tileRectangle); }); let surfaceGeometry; if (undefined !== indexBuffer) { const indexCount = indices.length; const hasLayerTextures = layerTextures.length > 0; const layerTextureParams = hasLayerTextures ? LayerTextureParams.create(layerTextures) : undefined; surfaceGeometry = new SurfaceGeometry(indexBuffer, indexCount, mesh, layerTextureParams); } return surfaceGeometry; } get isDisposed() { return this._buffers.isDisposed && this._indices.isDisposed; } [Symbol.dispose]() { dispose(this._buffers); dispose(this._indices); } collectStatistics(stats) { stats.addSurface(this._indices.bytesUsed); } get isLit() { return SurfaceType.Lit === this.surfaceType || SurfaceType.TexturedLit === this.surfaceType; } get isTexturedType() { return SurfaceType.Textured === this.surfaceType || 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 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 FillFlags.Blanking !== (this.fillFlags & FillFlags.Blanking); } get asSurface() { return this; } get asEdge() { return undefined; } get asSilhouette() { return undefined; } _draw(numInstances, instanceBuffersContainer) { const system = 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.POLYGON_OFFSET_FILL); system.context.polygonOffset(1.0, 1.0); } const bufs = instanceBuffersContainer !== undefined ? instanceBuffersContainer : this._buffers; bufs.bind(); system.drawArrays(GL.PrimitiveType.Triangles, 0, this._numIndices, numInstances); bufs.unbind(); if (offset) system.context.disable(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 (FillFlags.Behind === (this.fillFlags & 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 (FillFlags.Background === (this.fillFlags & 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 (RenderMode.Wireframe === vf.renderMode && !this.mesh.isTextureAlwaysDisplayed) { const fillFlags = this.fillFlags; const showFill = FillFlags.Always === (fillFlags & FillFlags.Always) || (vf.fill && FillFlags.ByView === (fillFlags & FillFlags.ByView)); if (!showFill) return "none"; } // If transparency disabled by render mode or view flag, always draw opaque. if (!vf.transparency || RenderMode.SolidFill === vf.renderMode || 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) { assert(undefined !== target.analysisStyle?.thematic); switch (target.analysisStyle.thematic.thematicSettings.textureTransparency) { case TextureTransparency.Translucent: return "translucent"; case TextureTransparency.Opaque: return opaquePass; case 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 === ThematicGradientTransparencyMode.MultiplySurfaceAndGradient) { switch (thematic.gradientSettings.textureTransparency) { case TextureTransparency.Opaque: // This surface's alpha gets multiplied by 1 - gradient colors are all opaque. return hasAlpha ? "translucent" : opaquePass; case TextureTransparency.Translucent: // This surface's alpha gets multiplied by < 1 - gradient colors are all translucent. return "translucent"; case 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 TextureTransparency.Translucent: hasAlpha = true; break; case 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 (FillFlags.None !== (fillFlags & 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 (RenderMode.Wireframe === vf.renderMode) { // Fill displayed even in wireframe? return FillFlags.None !== (fillFlags & 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 && (RenderMode.HiddenLine === mode || RenderMode.SolidFill === mode)) { flags[4 /* SurfaceBitIndex.TransparencyThreshold */] = 1; if (RenderMode.HiddenLine === mode && FillFlags.Always !== (this.fillFlags & 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 = BuffersContainer.create(); const attrPos = AttributeMap.findAttribute("a_pos", 0 /* TechniqueId.Surface */, false); assert(undefined !== attrPos); this._buffers.addBuffer(indices, [BufferParameters.create(attrPos.location, 3, 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 RenderMode.SmoothShade: return flags.textures; case RenderMode.Wireframe: return FillFlags.Always === (fill & FillFlags.Always) || (flags.fill && FillFlags.ByView === (fill & FillFlags.ByView)); default: return FillFlags.Always === (fill & FillFlags.Always); } } wantNormalMaps(target, normalMapExists) { if (!normalMapExists || !target.displayNormalMaps) return false; const flags = target.currentViewFlags; switch (flags.renderMode) { case RenderMode.SmoothShade: return flags.textures; default: return false; } } } //# sourceMappingURL=SurfaceGeometry.js.map