/
VisibleFeature.ts
161 lines (145 loc) · 6.85 KB
/
VisibleFeature.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Rendering
*/
import { assert, Id64String } from "@itwin/core-bentley";
import { GeometryClass } from "@itwin/core-common";
import { ViewRect } from "../common/ViewRect";
import { Viewport } from "../Viewport";
import { IModelConnection } from "../IModelConnection";
import { Pixel } from "./Pixel";
/** Represents a [Feature]($common) determined to be visible within a [[Viewport]].
* @see [[Viewport.queryVisibleFeatures]].
* @public
*/
export interface VisibleFeature {
/** The Id of the [Element]($backend) associated with the feature. May be invalid or transient. */
readonly elementId: Id64String;
/** The Id of the [SubCategory]($backend) associated with the feature. May be invalid or transient. */
readonly subCategoryId: Id64String;
/** The class of geometry associated with the feature. */
readonly geometryClass: GeometryClass;
/** The Id of the [GeometricModel]($backend) associated with the feature. May be invalid or transient. */
readonly modelId: Id64String;
/** The iModel associated with the feature. In some cases this may differ from the [[Viewport]]'s iModel. */
iModel: IModelConnection;
}
/** Options specifying how to query for visible [Feature]($common)s by reading pixels rendered by a [[Viewport]].
* This method of determining visibility considers a feature "visible" if it lit up at least one pixel.
* @note A pixel that is behind another, transparent pixel is not considered visible.
* @see [[QueryVisibleFeaturesOptions]].
* @public
*/
export interface QueryScreenFeaturesOptions {
/** Union discriminator for [[QueryVisibleFeaturesOptions]]. */
source: "screen";
/** If true, non-locatable features are considered visible. */
includeNonLocatable?: boolean;
/** If specified, a sub-region of the [[Viewport]] to which to constrain the query. */
rect?: ViewRect;
}
/** Options specifying how to query for visible [Feature]($common)s by inspecting the [[Tile]]s selected for display by a [[Viewport]].
* This method of determining visibility considers a feature "visible" if it is included in at least one tile selected for display and is
* not otherwise rendered invisible by the view's [[CategorySelectorState]], [SubCategoryAppearance]($common) overrides, [FeatureOverrides]($common), or
* other means.
* @note If a clip volume is applied to the view, features contained in tiles that *intersect* the clip volume are considered visible regardless of whether
* their geometry would actually be entirely clipped out by the clip volume.
* @see [[QueryVisibleFeaturesOptions]].
* @public
*/
export interface QueryTileFeaturesOptions {
/** Union discriminator for [[QueryVisibleFeaturesOptions]]. */
source: "tiles";
/** If true, non-locatable features are considered visible. */
includeNonLocatable?: boolean;
}
/** Options specifying how to query for visible [Feature]($common)s.
* @see [[Viewport.queryVisibleFeatures]].
* @public
*/
export type QueryVisibleFeaturesOptions = QueryScreenFeaturesOptions | QueryTileFeaturesOptions;
/** A function supplied to [[Viewport.queryVisibleFeatures]] to process the results. The iterable supplied to the callback consists of all of the
* [Feature]($common)s determined to be visible. The same feature may recur multiple times.
* @note The iterable supplied to the callback is usable only within the callback. Once the callback exits, the iterable becomes empty.
* @public
*/
export type QueryVisibleFeaturesCallback = (features: Iterable<VisibleFeature>) => void;
/** Ensures that the iterable supplied to QueryVisibleFeaturesCallback becomes invalidated once the callback exits.
* The iterable relies on RenderTarget state that changes from one frame to another.
*/
class ExpiringIterable implements Iterable<VisibleFeature> {
private _features: Iterable<VisibleFeature>;
private _disposed = false;
public constructor(features: Iterable<VisibleFeature>) {
this._features = features;
}
public dispose(): void {
this._disposed = true;
this._features = [];
}
public [Symbol.iterator](): Iterator<VisibleFeature> {
assert(!this._disposed, "The iterable supplied to QueryVisibleFeaturesCallback is valid only for the duration of the callback.");
return this._features[Symbol.iterator]();
}
}
function invokeCallback(features: Iterable<VisibleFeature>, callback: QueryVisibleFeaturesCallback): void {
const iterable = new ExpiringIterable(features);
try {
callback(iterable);
} finally {
iterable.dispose();
}
}
/** Features read from pixels rendered by a viewport. */
class ScreenFeatures implements Iterable<VisibleFeature> {
private readonly _pixels: Pixel.Buffer;
private readonly _rect: ViewRect;
private readonly _iModel: IModelConnection;
public constructor(pixels: Pixel.Buffer, rect: ViewRect, viewport: Viewport) {
this._pixels = pixels;
this._rect = rect.clone();
this._rect.right = viewport.cssPixelsToDevicePixels(this._rect.right);
this._rect.bottom = viewport.cssPixelsToDevicePixels(this._rect.bottom);
this._iModel = viewport.iModel;
}
public [Symbol.iterator](): Iterator<VisibleFeature> {
function* iterator(pixels: Pixel.Buffer, rect: ViewRect, iModel: IModelConnection) {
for (let x = rect.left; x < rect.right; x++) {
for (let y = rect.top; y < rect.bottom; y++) {
const pixel = pixels.getPixel(x, y);
if (pixel.feature && pixel.modelId) {
yield {
elementId: pixel.feature.elementId,
subCategoryId: pixel.feature.subCategoryId,
geometryClass: pixel.feature.geometryClass,
modelId: pixel.modelId,
iModel: pixel.iModel ?? iModel,
};
}
}
}
}
return iterator(this._pixels, this._rect, this._iModel);
}
}
/** Implementation of [[Viewport.queryVisibleFeatures]].
* @internal
*/
export function queryVisibleFeatures(viewport: Viewport, options: QueryVisibleFeaturesOptions, callback: QueryVisibleFeaturesCallback): void {
assert("screen" === options.source || "tiles" === options.source);
switch (options.source) {
case "screen":
const rect = options.rect ?? viewport.viewRect;
viewport.readPixels(rect, Pixel.Selector.Feature, (pixels) => invokeCallback(pixels ? new ScreenFeatures(pixels, rect, viewport) : [], callback), true !== options.includeNonLocatable);
break;
case "tiles":
viewport.target.queryVisibleTileFeatures(options, viewport.iModel, (features) => invokeCallback(features, callback));
break;
default:
invokeCallback([], callback);
break;
}
}