UNPKG

@itwin/core-frontend

Version:
350 lines • 17.5 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, Id64 } from "@itwin/core-bentley"; import { PackedFeature } from "@itwin/core-common"; import { LineCode } from "./LineCode"; import { GL } from "./GL"; import { TextureUnit } from "./RenderFlags"; import { sync } from "./Sync"; import { System } from "./System"; import { Texture2DDataUpdater, TextureHandle } from "./Texture"; import { DisplayParams } from "../../../common/internal/render/DisplayParams"; import { computeDimensions } from "../../../common/internal/render/VertexTable"; export function isFeatureHilited(feature, hilites, isModelHilited) { if (hilites.isEmpty) return false; if ("union" === hilites.modelSubCategoryMode) return isModelHilited || hilites.elements.hasPair(feature.elementId) || hilites.subcategories.hasPair(feature.subCategoryId); return hilites.elements.hasPair(feature.elementId) || (isModelHilited && hilites.subcategories.hasPair(feature.subCategoryId)); } const scratchPackedFeature = PackedFeature.createWithIndex(); /** @internal */ export class FeatureOverrides { target; _options; _lut; _mostRecentSymbologyOverrides; _lastFlashId = Id64.invalid; _hiliteSyncObserver = {}; _pickExclusionsSyncObserver = {}; _anyOverridden = true; _allHidden = true; _anyTranslucent = true; _anyViewIndependentTranslucent = true; _anyOpaque = true; _anyHilited = true; _lutParams = new Float32Array(2); _uniformSymbologyFlags = 0 /* EmphasisFlags.None */; _cleanup; get anyOverridden() { return this._anyOverridden; } get allHidden() { return this._allHidden; } get anyTranslucent() { return this._anyTranslucent; } get anyViewIndependentTranslucent() { return this._anyViewIndependentTranslucent; } get anyOpaque() { return this._anyOpaque; } get anyHilited() { return this._anyHilited; } /** For tests. */ get lutData() { return this._lut?.dataBytes; } get byteLength() { return undefined !== this._lut ? this._lut.bytesUsed : 0; } get isUniform() { return 3 === this._lutParams[0] && 1 === this._lutParams[1]; } updateUniformSymbologyFlags() { this._uniformSymbologyFlags = 0 /* EmphasisFlags.None */; if (!this.isUniform || !this._lut) return; let flags = this._lut.dataBytes[0]; if (0 !== (flags & 16 /* OvrFlags.Flashed */)) this._uniformSymbologyFlags |= 4 /* EmphasisFlags.Flashed */; if (0 !== (flags & 32 /* OvrFlags.NonLocatable */)) this._uniformSymbologyFlags |= 8 /* EmphasisFlags.NonLocatable */; if (!this._anyHilited) return; flags = this._lut.dataBytes[1] << 8; if (0 !== (flags & 256 /* OvrFlags.Hilited */)) this._uniformSymbologyFlags |= 1 /* EmphasisFlags.Hilite */; if (0 !== (flags & 512 /* OvrFlags.Emphasized */)) this._uniformSymbologyFlags |= 2 /* EmphasisFlags.Emphasized */; } getUniformOverrides() { assert(this.isUniform); assert(undefined !== this._lut); assert(undefined !== this._lut.dataBytes); return this._lut.dataBytes; } _initialize(provider, map, ovrs, pickExcludes, hilite, flashed) { const nFeatures = map.numFeatures; const dims = computeDimensions(nFeatures, 3, 0, System.instance.maxTextureSize); const width = dims.width; const height = dims.height; assert(width * height >= nFeatures); this._lutParams[0] = width; this._lutParams[1] = height; const data = new Uint8Array(width * height * 4); const creator = new Texture2DDataUpdater(data); this.buildLookupTable(provider, creator, map, ovrs, pickExcludes, flashed, hilite); return TextureHandle.createForData(width, height, data, true, GL.Texture.WrapMode.ClampToEdge); } _update(provider, map, lut, pickExcludes, flashed, hilites, ovrs) { const updater = new Texture2DDataUpdater(lut.dataBytes); if (undefined === ovrs) { this.updateFlashedAndHilited(updater, map, pickExcludes, flashed, hilites); } else { assert(undefined !== hilites); this.buildLookupTable(provider, updater, map, ovrs, pickExcludes, flashed, hilites); } lut.update(updater); } setTransparency(transparency, viewDependentTransparency, data, transparencyByteIndex, curFlags) { // transparency in range [0, 1]...convert to byte and invert so 0=transparent... let alpha = 1.0 - transparency; alpha = Math.floor(0xff * alpha + 0.5); if ((0xff - alpha) < DisplayParams.minTransparency) alpha = 0xff; data.setByteAtIndex(transparencyByteIndex, alpha); if (0xff === alpha) { this._anyOpaque = true; } else { this._anyTranslucent = true; if (!viewDependentTransparency) { curFlags |= 1024 /* OvrFlags.ViewIndependentTransparency */; this._anyViewIndependentTranslucent = true; } } return curFlags; } buildLookupTable(provider, data, map, ovr, pickExclude, flashedIdParts, hilites) { const allowHilite = true !== this._options.noHilite; const allowFlash = true !== this._options.noFlash; const allowEmphasis = true !== this._options.noEmphasis; let isModelHilited = false; const prevModelId = { lower: -1, upper: -1 }; this._anyOpaque = this._anyTranslucent = this._anyViewIndependentTranslucent = this._anyHilited = false; let nHidden = 0; let nOverridden = 0; // NB: We currently use 3 RGBA values per feature as follows: // [0] // RG = override flags (see OvrFlags enum) // B = line code // A = line weight (if we need an extra byte in future, could combine code+weight into a single byte). // [1] // RGB = rgb // A = alpha // [2] // RGB = line rgb // A = line alpha for (const feature of map.iterable(scratchPackedFeature)) { const i = feature.index; const dataIndex = i * 4 * 3; if (prevModelId.lower !== feature.modelId.lower || prevModelId.upper !== feature.modelId.upper) { prevModelId.lower = feature.modelId.lower; prevModelId.upper = feature.modelId.upper; isModelHilited = allowHilite && hilites.models.hasPair(feature.modelId); } const app = provider.getFeatureAppearance(ovr, feature.elementId.lower, feature.elementId.upper, feature.subCategoryId.lower, feature.subCategoryId.upper, feature.geometryClass, feature.modelId.lower, feature.modelId.upper, map.type, feature.animationNodeId); // NB: If the appearance is fully transparent, then: // - For normal ("primary") models, getAppearance() returns undefined. // - For classifier models, getAppearance() returns the appearance, and classification shader will discard fully-transparent classified pixels. // (The latter is how we clip the classified model using the classifiers). if (undefined === app) { // The feature is not visible. We don't care about any of the other overrides, because we're not going to render it. data.setOvrFlagsAtIndex(dataIndex, 4096 /* OvrFlags.Visibility */); nHidden++; nOverridden++; continue; } let flags = app.nonLocatable ? 32 /* OvrFlags.NonLocatable */ : 0 /* OvrFlags.None */; if (allowHilite && isFeatureHilited(feature, hilites, isModelHilited)) { flags |= 256 /* OvrFlags.Hilited */; this._anyHilited = true; } if (allowEmphasis && app.emphasized) { flags |= 512 /* OvrFlags.Emphasized */; this._anyHilited = true; } if (app.overridesRgb && app.rgb) { flags |= 2 /* OvrFlags.Rgb */; const rgb = app.rgb; data.setByteAtIndex(dataIndex + 4, rgb.r); data.setByteAtIndex(dataIndex + 5, rgb.g); data.setByteAtIndex(dataIndex + 6, rgb.b); } if (undefined !== app.transparency) { flags |= 4 /* OvrFlags.Alpha */; flags = this.setTransparency(app.transparency, app.viewDependentTransparency, data, dataIndex + 7, flags); } const lineRgb = app.getLineRgb(); if (lineRgb) { flags |= 1 /* OvrFlags.LineRgb */; data.setByteAtIndex(dataIndex + 8, lineRgb.r); data.setByteAtIndex(dataIndex + 9, lineRgb.g); data.setByteAtIndex(dataIndex + 10, lineRgb.b); } const lineTransp = app.getLineTransparency(); if (undefined !== lineTransp) { flags |= 8 /* OvrFlags.LineAlpha */; flags = this.setTransparency(lineTransp, app.viewDependentTransparency, data, dataIndex + 11, flags); } if (app.overridesWeight && app.weight) { flags |= 128 /* OvrFlags.Weight */; let weight = app.weight; weight = Math.min(31, weight); weight = Math.max(1, weight); data.setByteAtIndex(dataIndex + 3, weight); } if (app.overridesLinePixels && app.linePixels) { flags |= 64 /* OvrFlags.LineCode */; const lineCode = LineCode.valueFromLinePixels(app.linePixels); data.setByteAtIndex(dataIndex + 2, lineCode); } if (app.ignoresMaterial) flags |= 8192 /* OvrFlags.IgnoreMaterial */; if (allowFlash && undefined !== flashedIdParts && feature.elementId.lower === flashedIdParts.lower && feature.elementId.upper === flashedIdParts.upper) flags |= 16 /* OvrFlags.Flashed */; if (pickExclude?.hasPair(feature.elementId)) { flags |= 2048 /* OvrFlags.InvisibleDuringPick */; ++nHidden; } data.setOvrFlagsAtIndex(dataIndex, flags); if (0 /* OvrFlags.None */ !== flags) nOverridden++; } this._allHidden = (nHidden === map.numFeatures); this._anyOverridden = (nOverridden > 0); this.updateUniformSymbologyFlags(); } // NB: If hilites is undefined, it means that the hilited set has not changed. updateFlashedAndHilited(data, map, pickExcludes, flashed, hilites) { if (!hilites || true === this._options.noHilite) { this.updateFlashed(data, map, pickExcludes, flashed); return; } const allowFlash = true !== this._options.noFlash; const intersect = "intersection" === hilites.modelSubCategoryMode; this._anyOverridden = this._anyHilited = false; for (const feature of map.iterable(scratchPackedFeature)) { const dataIndex = feature.index * 4 * 3; const oldFlags = data.getOvrFlagsAtIndex(dataIndex); if (0 /* OvrFlags.None */ !== (oldFlags & 4096 /* OvrFlags.Visibility */)) { // If it's invisible, none of the other flags matter. We can't flash it and don't want to hilite it. this._anyOverridden = true; continue; } const isModelHilited = hilites.models.hasPair(feature.modelId); let isHilited = isModelHilited && !intersect; if (!isHilited) isHilited = hilites.elements.hasPair(feature.elementId); if (!isHilited) if (isModelHilited || !intersect) isHilited = hilites.subcategories.hasPair(feature.subCategoryId); let isFlashed = false; if (flashed && allowFlash) isFlashed = feature.elementId.lower === flashed.lower && feature.elementId.upper === flashed.upper; let newFlags = isFlashed ? (oldFlags | 16 /* OvrFlags.Flashed */) : (oldFlags & ~16 /* OvrFlags.Flashed */); newFlags = isHilited ? (newFlags | 256 /* OvrFlags.Hilited */) : (newFlags & ~256 /* OvrFlags.Hilited */); if (pickExcludes) { newFlags = pickExcludes.hasPair(feature.elementId) ? (newFlags | 2048 /* OvrFlags.InvisibleDuringPick */) : (newFlags & ~2048 /* OvrFlags.InvisibleDuringPick */); } data.setOvrFlagsAtIndex(dataIndex, newFlags); if (0 /* OvrFlags.None */ !== newFlags) { this._anyOverridden = true; this._anyHilited = this._anyHilited || isHilited || 0 /* OvrFlags.None */ !== (newFlags & 512 /* OvrFlags.Emphasized */); } } this.updateUniformSymbologyFlags(); } updateFlashed(data, map, pickExcludes, flashed) { if (true === this._options.noFlash && !pickExcludes) return; this._anyOverridden = false; const elemId = { lower: 0, upper: 0 }; for (let i = 0; i < map.numFeatures; i++) { const dataIndex = i * 4 * 3; const oldFlags = data.getOvrFlagsAtIndex(dataIndex); if (0 /* OvrFlags.None */ !== (oldFlags & 4096 /* OvrFlags.Visibility */)) { // If it's invisible, none of the other flags matter and we can't flash it. this._anyOverridden = true; continue; } let isFlashed = false; let thisElemId; if (flashed && !this._options.noFlash) { thisElemId = map.getElementIdPair(i, elemId); isFlashed = thisElemId.lower === flashed.lower && thisElemId.upper === flashed.upper; } let newFlags = isFlashed ? (oldFlags | 16 /* OvrFlags.Flashed */) : (oldFlags & ~16 /* OvrFlags.Flashed */); if (pickExcludes) { if (!thisElemId) { thisElemId = map.getElementIdPair(i, elemId); } newFlags = pickExcludes.hasPair(thisElemId) ? (newFlags | 2048 /* OvrFlags.InvisibleDuringPick */) : (newFlags & ~2048 /* OvrFlags.InvisibleDuringPick */); } data.setOvrFlagsAtIndex(dataIndex, newFlags); if (0 /* OvrFlags.None */ !== newFlags) this._anyOverridden = true; } this.updateUniformSymbologyFlags(); } constructor(target, options, cleanup) { this.target = target; this._options = options; this._cleanup = cleanup; } static createFromTarget(target, options, cleanup) { return new FeatureOverrides(target, options, cleanup); } get isDisposed() { return undefined === this._lut; } [Symbol.dispose]() { this._lut = dispose(this._lut); if (this._cleanup) { this._cleanup(); this._cleanup = undefined; } } initFromMap(map, provider) { const nFeatures = map.numFeatures; assert(0 < nFeatures); this._lut = dispose(this._lut); const ovrs = this.target.currentFeatureSymbologyOverrides; this._mostRecentSymbologyOverrides = ovrs; const hilite = this.target.hilites; this._lut = this._initialize(provider, map, ovrs, this.target.pickExclusions, hilite, this.target.flashed); this._lastFlashId = Id64.invalid; this._hiliteSyncObserver = {}; this._pickExclusionsSyncObserver = {}; } update(features, provider) { let ovrs = this.target.currentFeatureSymbologyOverrides; const ovrsUpdated = ovrs !== this._mostRecentSymbologyOverrides; if (ovrsUpdated) this._mostRecentSymbologyOverrides = ovrs; else ovrs = undefined; const flashedId = this.target.flashedId; const hilite = this.target.hilites; const hiliteUpdated = !sync(this.target.hiliteSyncTarget, this._hiliteSyncObserver); const pickExcludesUpdated = !sync(this.target.pickExclusionsSyncTarget, this._pickExclusionsSyncObserver); if (ovrsUpdated || hiliteUpdated || flashedId !== this._lastFlashId || pickExcludesUpdated) { // _lut can be undefined if context was lost, (gl.createTexture returns null) if (this._lut) { this._update(provider, features, this._lut, undefined !== ovrs || pickExcludesUpdated ? this.target.pickExclusions : undefined, this.target.flashed, undefined !== ovrs || hiliteUpdated ? hilite : undefined, ovrs); } this._lastFlashId = flashedId; } } bindLUTParams(uniform) { uniform.setUniform2fv(this._lutParams); } bindLUT(uniform) { if (this._lut) this._lut.bindSampler(uniform, TextureUnit.FeatureSymbology); } bindUniformSymbologyFlags(uniform) { uniform.setUniform1f(this._uniformSymbologyFlags); } } //# sourceMappingURL=FeatureOverrides.js.map