-
Notifications
You must be signed in to change notification settings - Fork 208
/
ViewState.ts
2423 lines (2039 loc) · 106 KB
/
ViewState.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Views
*/
import { assert, BeEvent, dispose, Id64, Id64Arg, Id64String, JsonUtils } from "@itwin/core-bentley";
import {
Angle, AxisOrder, ClipVector, Constant, Geometry, LongitudeLatitudeNumber, LowAndHighXY, LowAndHighXYZ, Map4d, Matrix3d,
Plane3dByOriginAndUnitNormal, Point2d, Point3d, Range2d, Range3d, Ray3d, Transform, Vector2d, Vector3d, XAndY,
XYAndZ, XYZ, YawPitchRollAngles,
} from "@itwin/core-geometry";
import {
AnalysisStyle, AxisAlignedBox3d, Camera, Cartographic, ColorDef, FeatureAppearance, Frustum, GlobeMode, GridOrientationType,
HydrateViewStateRequestProps, HydrateViewStateResponseProps, IModelReadRpcInterface,
ModelClipGroups, Npc, RenderSchedule, SubCategoryOverride,
ViewDefinition2dProps, ViewDefinition3dProps, ViewDefinitionProps, ViewDetails, ViewDetails3d, ViewFlags, ViewStateProps,
} from "@itwin/core-common";
import { AuxCoordSystem2dState, AuxCoordSystem3dState, AuxCoordSystemState } from "./AuxCoordSys";
import { CategorySelectorState } from "./CategorySelectorState";
import { DisplayStyle2dState, DisplayStyle3dState, DisplayStyleState } from "./DisplayStyleState";
import { DrawingViewState } from "./DrawingViewState";
import { ElementState } from "./EntityState";
import { Frustum2d } from "./Frustum2d";
import { IModelApp } from "./IModelApp";
import { IModelConnection } from "./IModelConnection";
import { GeometricModel2dState, GeometricModelState } from "./ModelState";
import { NotifyMessageDetails, OutputMessagePriority } from "./NotificationManager";
import { RenderClipVolume } from "./render/RenderClipVolume";
import { RenderMemory } from "./render/RenderMemory";
import { SheetViewState } from "./SheetViewState";
import { SpatialViewState } from "./SpatialViewState";
import { StandardView, StandardViewId } from "./StandardView";
import { DisclosedTileTreeSet, TileTreeReference } from "./tile/internal";
import { MarginOptions, OnViewExtentsError } from "./ViewAnimation";
import { DecorateContext, SceneContext } from "./ViewContext";
import { areaToEyeHeight, areaToEyeHeightFromGcs, GlobalLocation } from "./ViewGlobalLocation";
import { ViewingSpace } from "./ViewingSpace";
import { Viewport } from "./Viewport";
import { ViewPose, ViewPose2d, ViewPose3d } from "./ViewPose";
import { ViewStatus } from "./ViewStatus";
import { EnvironmentDecorations } from "./EnvironmentDecorations";
/** Describes the largest and smallest values allowed for the extents of a [[ViewState]].
* Attempts to exceed these limits in any dimension will fail, preserving the previous extents.
* @public
* @extensions
*/
export interface ExtentLimits {
/** The smallest allowed extent in any dimension. */
min: number;
/** The largest allowed extent in any dimension. */
max: number;
}
/** A [Transform]($core-geometry) supplied by a [[ModelDisplayTransformProvider]] to be applied to a model when displaying it in a [[Viewport]].
* @beta
*/
export interface ModelDisplayTransform {
/** The transform to be applied to the model. */
transform: Transform;
/** If `true`, [[transform]] is pre-multiplied with (i.e., appled before) the model's base transform.
* Otherwise, the display transform will instead be post-multiplied with (i.e., applied after) the base transform.
*/
premultiply?: boolean;
}
/** Interface adopted by an object that wants to apply a [[ModelDisplayTransform]] to one or more models within a [[Viewport]].
* @see [[ViewState.modelDisplayTransformProvider]] to get or set the transform provider for a view.
* @see [[ViewState.computeDisplayTransform]] to compute a full display transform for a model or an element within it, which may include a transform supplied by a ModelDisplayTransformProvider.
* @beta
*/
export interface ModelDisplayTransformProvider {
/** Return the transform to be applied to the specified model, if any. */
getModelDisplayTransform(modelId: Id64String): ModelDisplayTransform | undefined;
}
/** Arguments supplied to [[ViewState.computeDisplayTransform]].
* @beta
*/
export interface ComputeDisplayTransformArgs {
/** The Id of the model for which to compute the display transform. */
modelId: Id64String;
/** The Id of a specific element belonging to the model identified by [[modelId]] for which to compute the display transform. */
elementId?: Id64String;
/** The point in time, expressed in [Unix seconds](https://en.wikipedia.org/wiki/Unix_time), at which to evaluate the display transform.
* Defaults to the [DisplayStyleSettings.timePoint]($common) specified by the view's display style.
*/
timePoint?: number;
/** The element Id of the [ViewAttachment]($backend) through which the element or model is drawn.
* @beta
*/
viewAttachmentId?: Id64String;
/** If supplied, [[ViewState.computeDisplayTransform]] will modify and return this Transform to hold the result instead of allocating a new Transform.
* @note If [[ViewState.computeDisplayTransform]] returns `undefined`, this Transform will be unmodified.
*/
output?: Transform;
}
/** Arguments to [[ViewState3d.lookAt]] for either a perspective or orthographic view
* @public
*/
export interface LookAtArgs {
/** The new location of the camera/eye. */
readonly eyePoint: XYAndZ;
/** A vector that orients the camera's "up" (view y). This vector must not be parallel to the view direction. */
readonly upVector: Vector3d;
/** The new size (width and height) of the view rectangle on the focus plane centered on the targetPoint. If undefined, the existing size is unchanged. */
readonly newExtents?: XAndY;
/** The distance from the eyePoint to the front plane. If undefined, the existing front distance is used. */
readonly frontDistance?: number;
/** The distance from the eyePoint to the back plane. If undefined, the existing back distance is used. */
readonly backDistance?: number;
/** Used for providing onExtentsError. */
readonly opts?: OnViewExtentsError;
}
/** Arguments to [[ViewState3d.lookAt]] to set up a perspective view
* @public
*/
export interface LookAtPerspectiveArgs extends LookAtArgs {
/** The new location to which the camera should point. This becomes the center of the view on the focus plane. */
readonly targetPoint: XYAndZ;
readonly viewDirection?: never;
readonly lensAngle?: never;
}
/** Arguments to [[ViewState3d.lookAt]] to set up an orthographic view
* @public
*/
export interface LookAtOrthoArgs extends LookAtArgs {
/** The direction in which the view should look. */
readonly viewDirection: XYAndZ;
readonly targetPoint?: never;
readonly lensAngle?: never;
}
/** Arguments to [[ViewState3d.lookAt]] to set up an perspective view using a (field-of-view) lens angle.
* @public
*/
export interface LookAtUsingLensAngle extends LookAtArgs {
/** The new location to which the camera should point. This becomes the center of the view on the focus plane. */
readonly targetPoint: XYAndZ;
/** The angle that defines the field-of-view for the camera. Must be between .0001 and pi. */
readonly lensAngle: Angle;
readonly viewDirection?: never;
}
/** Decorates the viewport with the view's grid. Graphics are cached as long as scene remains valid. */
class GridDecorator {
public constructor(private readonly _view: ViewState) { }
public readonly useCachedDecorations = true;
public decorate(context: DecorateContext): void {
const vp = context.viewport;
if (!vp.isGridOn)
return;
const orientation = this._view.getGridOrientation();
if (GridOrientationType.AuxCoord < orientation) {
return; // NEEDSWORK...
}
if (GridOrientationType.AuxCoord === orientation) {
this._view.auxiliaryCoordinateSystem.drawGrid(context);
return;
}
const isoGrid = false;
const gridsPerRef = this._view.getGridsPerRef();
const spacing = Point2d.createFrom(this._view.getGridSpacing());
const origin = Point3d.create();
const matrix = Matrix3d.createIdentity();
const fixedRepsAuto = Point2d.create();
this._view.getGridSettings(vp, origin, matrix, orientation);
context.drawStandardGrid(origin, matrix, spacing, gridsPerRef, isoGrid, orientation !== GridOrientationType.View ? fixedRepsAuto : undefined);
}
}
const scratchCorners = Range3d.createNull().corners();
const scratchRay = Ray3d.createZero();
const unitRange2d = Range2d.createXYXY(0, 0, 1, 1);
const scratchRange2d = Range2d.createNull();
const scratchRange2dIntersect = Range2d.createNull();
/** Arguments to [[ViewState.attachToViewport]].
* @note The [[Viewport]] has a dependency upon and control over the [[ViewState]]. Do not use `attachToViewport` to introduce a dependency in
* the opposite direction.
* @public
*/
export type AttachToViewportArgs = Viewport;
/** The front-end state of a [[ViewDefinition]] element.
* A ViewState is typically associated with a [[Viewport]] to display the contents of the view on the screen. A ViewState being displayed by a Viewport is considered to be
* "attached" to that viewport; a "detached" viewport is not being displayed by any viewport. Because the Viewport modifies the state of its attached ViewState, a ViewState
* can only be attached to one Viewport at a time. Technically, two Viewports can display two different ViewStates that both use the same [[DisplayStyleState]], but this is
* discouraged - changes made to the style by one Viewport will affect the contents of the other Viewport.
* * @see [Views]($docs/learning/frontend/Views.md)
* @public
* @extensions
*/
export abstract class ViewState extends ElementState {
public static override get className() { return "ViewDefinition"; }
private _auxCoordSystem?: AuxCoordSystemState;
private _extentLimits?: ExtentLimits;
private _modelDisplayTransformProvider?: ModelDisplayTransformProvider;
public description?: string;
public isPrivate?: boolean;
private readonly _gridDecorator: GridDecorator;
private _categorySelector: CategorySelectorState;
private _displayStyle: DisplayStyleState;
private readonly _unregisterCategorySelectorListeners: VoidFunction[] = [];
/** An event raised when the set of categories viewed by this view changes, *only* if the view is attached to a [[Viewport]]. */
public readonly onViewedCategoriesChanged = new BeEvent<() => void>();
/** An event raised just before assignment to the [[displayStyle]] property, *only* if the view is attached to a [[Viewport]].
* @see [[DisplayStyleSettings]] for events raised when properties of the display style change.
*/
public readonly onDisplayStyleChanged = new BeEvent<(newStyle: DisplayStyleState) => void>();
/** Event raised just before assignment to the [[modelDisplayTransformProvider]] property, *only* if the view is attached to a [[Viewport]].
* @beta
*/
public readonly onModelDisplayTransformProviderChanged = new BeEvent<(newProvider: ModelDisplayTransformProvider | undefined) => void>();
/** Selects the categories that are display by this ViewState. */
public get categorySelector(): CategorySelectorState {
return this._categorySelector;
}
public set categorySelector(selector: CategorySelectorState) {
if (selector === this._categorySelector)
return;
const isAttached = this.isAttachedToViewport;
this.unregisterCategorySelectorListeners();
this._categorySelector = selector;
if (isAttached) {
this.registerCategorySelectorListeners();
this.onViewedCategoriesChanged.raiseEvent();
}
}
/** The style that controls how the contents of the view are displayed. */
public get displayStyle(): DisplayStyleState {
return this._displayStyle;
}
public set displayStyle(style: DisplayStyleState) {
if (style === this.displayStyle)
return;
if (this.isAttachedToViewport)
this.onDisplayStyleChanged.raiseEvent(style);
this._displayStyle = style;
}
/** @internal */
protected constructor(props: ViewDefinitionProps, iModel: IModelConnection, categoryOrClone: CategorySelectorState, displayStyle: DisplayStyleState) {
super(props, iModel);
this.description = props.description;
this.isPrivate = props.isPrivate;
this._displayStyle = displayStyle;
this._categorySelector = categoryOrClone;
this._gridDecorator = new GridDecorator(this);
if (!(categoryOrClone instanceof ViewState)) // is this from the clone method?
return; // not from clone
// from clone, 3rd argument is source ViewState
const source = categoryOrClone as ViewState;
this._categorySelector = source.categorySelector.clone();
this._displayStyle = source.displayStyle.clone();
this._extentLimits = source._extentLimits;
this._auxCoordSystem = source._auxCoordSystem;
this._modelDisplayTransformProvider = source._modelDisplayTransformProvider;
}
/** Create a new ViewState object from a set of properties. Generally this is called internally by [[IModelConnection.Views.load]] after the properties
* have been read from an iModel. But, it can also be used to create a ViewState in memory, from scratch or from properties stored elsewhere.
*/
public static createFromProps(_props: ViewStateProps, _iModel: IModelConnection): ViewState | undefined { return undefined; }
/** Serialize this ViewState as a set of properties that can be used to recreate it via [[ViewState.createFromProps]]. */
public toProps(): ViewStateProps {
return {
viewDefinitionProps: this.toJSON(),
categorySelectorProps: this.categorySelector.toJSON(),
displayStyleProps: this.displayStyle.toJSON(),
};
}
/** Flags controlling various aspects of this view's [[DisplayStyleState]].
* @see [DisplayStyleSettings.viewFlags]($common)
*/
public get viewFlags(): ViewFlags {
return this.displayStyle.viewFlags;
}
public set viewFlags(flags: ViewFlags) {
this.displayStyle.viewFlags = flags;
}
/** See [DisplayStyleSettings.analysisStyle]($common). */
public get analysisStyle(): AnalysisStyle | undefined {
return this.displayStyle.settings.analysisStyle;
}
/** The [RenderSchedule.Script]($common) that animates the contents of the view, if any.
* @see [[DisplayStyleState.scheduleScript]].
*/
public get scheduleScript(): RenderSchedule.Script | undefined {
return this.displayStyle.scheduleScript;
}
/** @internal */
public get scheduleScriptReference(): RenderSchedule.ScriptReference | undefined { // eslint-disable-line deprecation/deprecation
return this.displayStyle.scheduleScriptReference; // eslint-disable-line deprecation/deprecation
}
/** Get the globe projection mode.
* @internal
*/
public get globeMode(): GlobeMode { return this.displayStyle.globeMode; }
/** Determine whether this ViewState exactly matches another. */
public override equals(other: this): boolean { return super.equals(other) && this.categorySelector.equals(other.categorySelector) && this.displayStyle.equals(other.displayStyle); }
/** Convert to JSON representation. */
public override toJSON(): ViewDefinitionProps {
const json = super.toJSON() as ViewDefinitionProps;
json.categorySelectorId = this.categorySelector.id;
json.displayStyleId = this.displayStyle.id;
json.isPrivate = this.isPrivate;
json.description = this.description;
return json;
}
/**
* Populates the hydrateRequest object stored on the ViewState with:
* not loaded categoryIds based off of the ViewStates categorySelector.
* Auxiliary coordinate system id if valid.
*/
protected preload(hydrateRequest: HydrateViewStateRequestProps): void {
const acsId = this.getAuxiliaryCoordinateSystemId();
if (Id64.isValid(acsId))
hydrateRequest.acsId = acsId;
}
/** Asynchronously load any required data for this ViewState from the backend.
* FINAL, No subclass should override load. If additional load behavior is needed, see preload and postload.
* @note callers should await the Promise returned by this method before using this ViewState.
* @see [Views]($docs/learning/frontend/Views.md)
*/
public async load(): Promise<void> {
// If the iModel associated with the viewState is a blankConnection,
// then no data can be retrieved from the backend.
if (this.iModel.isBlank)
return;
const hydrateRequest: HydrateViewStateRequestProps = {};
this.preload(hydrateRequest);
const promises: Promise<void>[] = [
IModelReadRpcInterface.getClientForRouting(this.iModel.routingContext.token).hydrateViewState(this.iModel.getRpcProps(), hydrateRequest).
then(async (hydrateResponse) => this.postload(hydrateResponse)),
this.displayStyle.load(),
];
const subcategories = this.iModel.subcategories.load(this.categorySelector.categories);
if (undefined !== subcategories)
promises.push(subcategories.promise.then((_) => { }));
await Promise.all(promises);
}
protected async postload(hydrateResponse: HydrateViewStateResponseProps): Promise<void> {
if (hydrateResponse.acsElementProps)
this._auxCoordSystem = AuxCoordSystemState.fromProps(hydrateResponse.acsElementProps, this.iModel);
}
/** Returns true if all [[TileTree]]s required by this view have been loaded.
* Note that the map tile trees associated to the viewport rather than the view, to check the
* map tiles as well call [[Viewport.areAreAllTileTreesLoaded]].
*/
public get areAllTileTreesLoaded(): boolean {
let allLoaded = true;
this.forEachTileTreeRef((ref) => {
allLoaded = allLoaded && ref.isLoadingComplete;
});
return allLoaded;
}
/** Get the name of the [[ViewDefinition]] from which this ViewState originated. */
public get name(): string {
return this.code.value;
}
/** Get this view's background color. */
public get backgroundColor(): ColorDef {
return this.displayStyle.backgroundColor;
}
/** Query the symbology overrides applied to geometry belonging to a specific subcategory when rendered using this ViewState.
* @param id The Id of the subcategory.
* @return The symbology overrides applied to all geometry belonging to the specified subcategory, or undefined if no such overrides exist.
*/
public getSubCategoryOverride(id: Id64String): SubCategoryOverride | undefined {
return this.displayStyle.getSubCategoryOverride(id);
}
/** Query the symbology overrides applied to a model when rendered using this ViewState.
* @param id The Id of the model.
* @return The symbology overrides applied to the model, or undefined if no such overrides exist.
*/
public getModelAppearanceOverride(id: Id64String): FeatureAppearance | undefined {
return this.displayStyle.settings.getModelAppearanceOverride(id);
}
/** @internal */
public isSubCategoryVisible(id: Id64String): boolean {
const app = this.iModel.subcategories.getSubCategoryAppearance(id);
if (undefined === app)
return false;
const ovr = this.getSubCategoryOverride(id);
if (undefined === ovr || undefined === ovr.invisible)
return !app.invisible;
return !ovr.invisible;
}
/** Provides access to optional detail settings for this view. */
public abstract get details(): ViewDetails;
/** Returns true if this ViewState is-a [[ViewState3d]] */
public abstract is3d(): this is ViewState3d;
/** Returns true if this ViewState is-a [[ViewState2d]] */
public is2d(): this is ViewState2d { return !this.is3d(); }
/** Returns true if this ViewState is-a [[SpatialViewState]] */
public abstract isSpatialView(): this is SpatialViewState;
/** Returns true if this ViewState is-a [[DrawingViewState]] */
public abstract isDrawingView(): this is DrawingViewState;
/** Returns true if this ViewState is-a [[SheetViewState]] */
public isSheetView(): this is SheetViewState { return false; }
/** Returns true if [[ViewTool]]s are allowed to operate in three dimensions on this view. */
public abstract allow3dManipulations(): boolean;
/** @internal */
public abstract createAuxCoordSystem(acsName: string): AuxCoordSystemState;
/** Get the extents of this view in [[CoordSystem.World]] coordinates. */
public abstract getViewedExtents(): AxisAlignedBox3d;
/** Compute a range in [[CoordSystem.World]] coordinates that tightly encloses the contents of this view.
* @see [[FitViewTool]].
*/
public abstract computeFitRange(): Range3d;
/** Returns true if this view displays the contents of a [[Model]] specified by Id. */
public abstract viewsModel(modelId: Id64String): boolean;
/** Get the origin of this view in [[CoordSystem.World]] coordinates. */
public abstract getOrigin(): Point3d;
/** Get the extents of this view in [[CoordSystem.World]] coordinates. */
public abstract getExtents(): Vector3d;
/** Get the 3x3 ortho-normal Matrix3d for this view. */
public abstract getRotation(): Matrix3d;
/** Set the origin of this view in [[CoordSystem.World]] coordinates. */
public abstract setOrigin(viewOrg: XYAndZ): void;
/** Set the extents of this view in [[CoordSystem.World]] coordinates. */
public abstract setExtents(viewDelta: Vector3d): void;
/** set the center of this view to a new position. */
public setCenter(center: Point3d) {
const diff = center.minus(this.getCenter());
this.setOrigin(this.getOrigin().plus(diff));
}
/** Change the rotation of the view.
* @note viewRot must be ortho-normal. For 2d views, only the rotation angle about the z axis is used.
*/
public abstract setRotation(viewRot: Matrix3d): void;
/** Execute a function on each viewed model */
public abstract forEachModel(func: (model: GeometricModelState) => void): void;
/** Execute a function against the [[TileTreeReference]]s associated with each viewed model.
* @note Each model may have more than one tile tree reference - for instance, if the view has a schedule script containing animation transforms.
* @internal
*/
public abstract forEachModelTreeRef(func: (treeRef: TileTreeReference) => void): void;
/** Execute a function against each [[TileTreeReference]] associated with this view.
* @note This may include tile trees not associated with any [[GeometricModelState]] - e.g., context reality data.
*/
public forEachTileTreeRef(func: (treeRef: TileTreeReference) => void): void {
this.forEachModelTreeRef(func);
this.displayStyle.forEachTileTreeRef(func);
}
/** Disclose *all* TileTrees currently in use by this view. This set may include trees not reported by [[forEachTileTreeRef]] - e.g., those used by view attachments, map-draped terrain, etc.
* @internal
*/
public discloseTileTrees(trees: DisclosedTileTreeSet): void {
this.forEachTileTreeRef((ref) => trees.disclose(ref));
}
/** Discloses graphics memory consumed by viewed tile trees and other consumers like view attachments.
* @internal
*/
public collectStatistics(stats: RenderMemory.Statistics): void {
const trees = new DisclosedTileTreeSet();
this.discloseTileTrees(trees);
for (const tree of trees)
tree.collectStatistics(stats);
this.collectNonTileTreeStatistics(stats);
}
/** Discloses graphics memory consumed by any consumers *other* than viewed tile trees, like view attachments.
* @internal
*/
public collectNonTileTreeStatistics(_stats: RenderMemory.Statistics): void {
//
}
/** Capture a copy of this view's viewed volume.
* @see [[applyPose]] to apply the pose to this or another view.
* @public
* @extensions
*/
public abstract savePose(): ViewPose;
/** Apply a pose to this view to change the viewed volume.
* @see [[savePose]] to capture the view's pose.
* @public
* @extensions
*/
public abstract applyPose(props: ViewPose): this;
/** @internal */
public createScene(context: SceneContext): void {
this.forEachTileTreeRef((ref: TileTreeReference) => ref.addToScene(context));
}
/** Add view-specific decorations. The base implementation draws the grid. Subclasses must invoke super.decorate()
* @internal
*/
public decorate(context: DecorateContext): void {
this.drawGrid(context);
}
/** @internal */
public static getStandardViewMatrix(id: StandardViewId): Matrix3d {
return StandardView.getStandardRotation(id);
}
/** Orient this view to one of the [[StandardView]] rotations. */
public setStandardRotation(id: StandardViewId) { this.setRotation(ViewState.getStandardViewMatrix(id)); }
/** Orient this view to one of the [[StandardView]] rotations, if the the view is not viewing the project then the
* standard rotation is relative to the global position rather than the project.
*/
public setStandardGlobalRotation(id: StandardViewId) {
const worldToView = ViewState.getStandardViewMatrix(id);
const globeToWorld = this.getGlobeRotation();
if (globeToWorld)
return this.setRotation(worldToView.multiplyMatrixMatrix(globeToWorld));
else
this.setRotation(worldToView);
}
/** Get the target point of the view. If there is no camera, center is returned. */
public getTargetPoint(result?: Point3d): Point3d { return this.getCenter(result); }
/** Get the point at the geometric center of the view. */
public getCenter(result?: Point3d): Point3d {
const delta = this.getRotation().multiplyTransposeVector(this.getExtents());
return this.getOrigin().plusScaled(delta, 0.5, result);
}
/** @internal */
public drawGrid(context: DecorateContext): void {
context.addFromDecorator(this._gridDecorator);
}
/** @internal */
public computeWorldToNpc(viewRot?: Matrix3d, inOrigin?: Point3d, delta?: Vector3d, enforceFrontToBackRatio = true): { map: Map4d | undefined, frustFraction: number } {
if (viewRot === undefined)
viewRot = this.getRotation();
const xVector = viewRot.rowX();
const yVector = viewRot.rowY();
const zVector = viewRot.rowZ();
if (delta === undefined)
delta = this.getExtents();
if (inOrigin === undefined)
inOrigin = this.getOrigin();
let frustFraction = 1.0;
let xExtent: Vector3d;
let yExtent: Vector3d;
let zExtent: Vector3d;
let origin: Point3d;
// Compute root vectors along edges of view frustum.
if (this.is3d() && this.isCameraOn) {
const camera = this.camera;
const eyeToOrigin = Vector3d.createStartEnd(camera.eye, inOrigin); // vector from origin on backplane to eye
viewRot.multiplyVectorInPlace(eyeToOrigin); // align with view coordinates.
const focusDistance = camera.focusDist;
let zDelta = delta.z;
let zBack = eyeToOrigin.z; // Distance from eye to backplane.
let zFront = zBack + zDelta; // Distance from eye to frontplane.
const nearScale = IModelApp.renderSystem.supportsLogZBuffer ? ViewingSpace.nearScaleLog24 : ViewingSpace.nearScaleNonLog24;
if (enforceFrontToBackRatio && zFront / zBack < nearScale) {
// In this case we are running up against the zBuffer resolution limitation (currently 24 bits).
// Set back clipping plane at 10 kilometer which gives us a front clipping plane about 3 meters.
// Decreasing the maximumBackClip (MicroStation uses 1 kilometer) will reduce the minimum front
// clip, but also reduce the back clip (so far geometry may not be visible).
const maximumBackClip = 10 * Constant.oneKilometer;
if (-zBack > maximumBackClip) {
zBack = -maximumBackClip;
eyeToOrigin.z = zBack;
}
zFront = zBack * nearScale;
zDelta = zFront - eyeToOrigin.z;
}
// z out back of eye ===> origin z coordinates are negative. (Back plane more negative than front plane)
const backFraction = -zBack / focusDistance; // Perspective fraction at back clip plane.
const frontFraction = -zFront / focusDistance; // Perspective fraction at front clip plane.
frustFraction = frontFraction / backFraction;
// delta.x,delta.y are view rectangle sizes at focus distance. Scale to back plane:
xExtent = xVector.scale(delta.x * backFraction); // xExtent at back == delta.x * backFraction.
yExtent = yVector.scale(delta.y * backFraction); // yExtent at back == delta.y * backFraction.
// Calculate the zExtent in the View coordinate system.
zExtent = new Vector3d(eyeToOrigin.x * (frontFraction - backFraction), eyeToOrigin.y * (frontFraction - backFraction), zDelta);
viewRot.multiplyTransposeVectorInPlace(zExtent); // rotate back to root coordinates.
origin = new Point3d(
eyeToOrigin.x * backFraction, // Calculate origin in eye coordinates
eyeToOrigin.y * backFraction,
eyeToOrigin.z);
viewRot.multiplyTransposeVectorInPlace(origin); // Rotate back to root coordinates
origin.plus(camera.eye, origin); // Add the eye point.
} else {
origin = inOrigin;
xExtent = xVector.scale(delta.x);
yExtent = yVector.scale(delta.y);
zExtent = zVector.scale(delta.z ? delta.z : 1.0);
}
// calculate the root-to-npc mapping (using expanded frustum)
return { map: Map4d.createVectorFrustum(origin, xExtent, yExtent, zExtent, frustFraction), frustFraction };
}
/** Calculate the world coordinate Frustum from the parameters of this ViewState.
* @param result Optional Frustum to hold result. If undefined a new Frustum is created.
* @returns The 8-point Frustum with the corners of this ViewState, or undefined if the parameters are invalid.
*/
public calculateFrustum(result?: Frustum): Frustum | undefined {
const val = this.computeWorldToNpc();
if (undefined === val.map)
return undefined;
const box = result ? result.initNpc() : new Frustum();
val.map.transform1.multiplyPoint3dArrayQuietNormalize(box.points);
return box;
}
public calculateFocusCorners() {
const map = this.computeWorldToNpc().map!;
const focusNpcZ = Geometry.clamp(map.transform0.multiplyPoint3dQuietNormalize(this.getTargetPoint()).z, 0, 1.0);
const pts = [new Point3d(0.0, 0.0, focusNpcZ), new Point3d(1.0, 0.0, focusNpcZ), new Point3d(0.0, 1.0, focusNpcZ), new Point3d(1.0, 1.0, focusNpcZ)];
map.transform1.multiplyPoint3dArrayQuietNormalize(pts);
return pts;
}
/** Initialize the origin, extents, and rotation from an existing Frustum
* This function is commonly used in the implementation of [[ViewTool]]s as follows:
* 1. Obtain the ViewState's initial frustum.
* 2. Modify the frustum based on user input.
* 3. Update the ViewState to match the modified frustum.
* @param frustum the input Frustum.
* @param opts for providing onExtentsError
* @return Success if the frustum was successfully updated, or an appropriate error code.
*/
public setupFromFrustum(inFrustum: Frustum, opts?: OnViewExtentsError): ViewStatus {
const frustum = inFrustum.clone(); // make sure we don't modify input frustum
frustum.fixPointOrder();
const frustPts = frustum.points;
const viewOrg = frustPts[Npc.LeftBottomRear];
// frustumX, frustumY, frustumZ are vectors along edges of the frustum. They are NOT unit vectors.
// X and Y should be perpendicular, and Z should be right handed.
const frustumX = Vector3d.createFrom(frustPts[Npc.RightBottomRear].minus(viewOrg));
const frustumY = Vector3d.createFrom(frustPts[Npc.LeftTopRear].minus(viewOrg));
const frustumZ = Vector3d.createFrom(frustPts[Npc.LeftBottomFront].minus(viewOrg));
const frustMatrix = Matrix3d.createRigidFromColumns(frustumX, frustumY, AxisOrder.XYZ);
if (!frustMatrix)
return ViewStatus.InvalidWindow;
// if we're close to one of the standard views, adjust to it to remove any "fuzz"
StandardView.adjustToStandardRotation(frustMatrix);
const xDir = frustMatrix.getColumn(0);
const yDir = frustMatrix.getColumn(1);
const zDir = frustMatrix.getColumn(2);
// set up view Rotation matrix as rows of frustum matrix.
const viewRot = frustMatrix.inverse();
if (!viewRot)
return ViewStatus.InvalidWindow;
// Left handed frustum?
const zSize = zDir.dotProduct(frustumZ);
if (zSize < 0.0)
return ViewStatus.InvalidWindow;
const viewDiagRoot = new Vector3d();
viewDiagRoot.plus2Scaled(xDir, xDir.dotProduct(frustumX), yDir, yDir.dotProduct(frustumY), viewDiagRoot); // vectors on the back plane
viewDiagRoot.plusScaled(zDir, zSize, viewDiagRoot); // add in z vector perpendicular to x,y
// use center of frustum and view diagonal for origin. Original frustum may not have been orthogonal
frustum.getCenter().plusScaled(viewDiagRoot, -0.5, viewOrg);
// delta is in view coordinates
const viewDelta = viewRot.multiplyVector(viewDiagRoot);
const status = this.adjustViewDelta(viewDelta, viewOrg, viewRot, undefined, opts);
if (ViewStatus.Success !== status)
return status;
this.setOrigin(viewOrg);
this.setExtents(viewDelta);
this.setRotation(viewRot);
this._updateMaxGlobalScopeFactor();
return ViewStatus.Success;
}
/** Get or set the largest and smallest values allowed for the extents for this ViewState
* The default limits vary based on the type of view:
* - Spatial and drawing view extents cannot exceed the diameter of the earth.
* - Sheet view extents cannot exceed ten times the paper size of the sheet.
* Explicitly setting the extent limits overrides the default limits.
* @see [[resetExtentLimits]] to restore the default limits.
*/
public get extentLimits(): ExtentLimits { return undefined !== this._extentLimits ? this._extentLimits : this.defaultExtentLimits; }
public set extentLimits(limits: ExtentLimits) { this._extentLimits = limits; }
/** Resets the largest and smallest values allowed for the extents of this ViewState to their default values.
* @see [[extentLimits]].
*/
public resetExtentLimits(): void { this._extentLimits = undefined; }
/** Returns the default extent limits for this ViewState. These limits are used if the [[extentLimits]] have not been explicitly overridden.
*/
public abstract get defaultExtentLimits(): ExtentLimits;
public setDisplayStyle(style: DisplayStyleState) { this.displayStyle = style; }
/** Adjust the y dimension of this ViewState so that its aspect ratio matches the supplied value.
* @internal
*/
public fixAspectRatio(windowAspect: number): void {
const origExtents = this.getExtents();
const extents = origExtents.clone();
extents.y = extents.x / (windowAspect * this.getAspectRatioSkew());
if (extents.isAlmostEqual(origExtents))
return;
// adjust origin by half of the distance we modified extents to keep centered
const origin = this.getOrigin().clone();
origin.addScaledInPlace(this.getRotation().multiplyTransposeVector(extents.vectorTo(origExtents, origExtents)), .5);
this.setOrigin(origin);
this.setExtents(extents);
}
/** @internal */
public outputStatusMessage(status: ViewStatus): ViewStatus {
IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, IModelApp.localization.getLocalizedString(`iModelJs:Viewing.${ViewStatus[status]}`)));
return status;
}
/** @internal */
public adjustViewDelta(delta: Vector3d, origin: XYZ, rot: Matrix3d, aspect?: number, opts?: OnViewExtentsError): ViewStatus {
const origDelta = delta.clone();
let status = ViewStatus.Success;
const limit = this.extentLimits;
const limitDelta = (val: number) => {
if (val < limit.min) {
val = limit.min;
status = ViewStatus.MinWindow;
} else if (val > limit.max) {
val = limit.max;
status = ViewStatus.MaxWindow;
}
return val;
};
delta.x = limitDelta(delta.x);
delta.y = limitDelta(delta.y);
if (aspect) { // skip if either undefined or 0
aspect *= this.getAspectRatioSkew();
if (delta.x > (aspect * delta.y))
delta.y = delta.x / aspect;
else
delta.x = delta.y * aspect;
}
if (!delta.isAlmostEqual(origDelta))
origin.addScaledInPlace(rot.multiplyTransposeVector(delta.vectorTo(origDelta, origDelta)), .5);
return (status !== ViewStatus.Success && opts?.onExtentsError) ? opts.onExtentsError(status) : status;
}
/** Adjust the aspect ratio of this ViewState so it matches the supplied value. The adjustment is accomplished by increasing one dimension
* and leaving the other unchanged, depending on the ratio of this ViewState's current aspect ratio to the supplied one. This means the result
* always shows everything in the current volume, plus potentially more.
* @note The *automatic* adjustment that happens when ViewStates are used in Viewports **always** adjusts the Y axis (making
* it potentially smaller). That's so that process can be reversible if the view's aspect ratio changes repeatedly (as happens when panels slide in/out, etc.)
*/
public adjustAspectRatio(aspect: number) {
const extents = this.getExtents();
const origin = this.getOrigin();
this.adjustViewDelta(extents, origin, this.getRotation(), aspect);
this.setExtents(extents);
this.setOrigin(origin);
}
/** Set the CategorySelector for this view. */
public setCategorySelector(categories: CategorySelectorState) { this.categorySelector = categories; }
/** get the auxiliary coordinate system state object for this ViewState. */
public get auxiliaryCoordinateSystem(): AuxCoordSystemState {
if (!this._auxCoordSystem)
this._auxCoordSystem = this.createAuxCoordSystem("");
return this._auxCoordSystem;
}
/** Get the Id of the auxiliary coordinate system for this ViewState */
public getAuxiliaryCoordinateSystemId(): Id64String {
return this.details.auxiliaryCoordinateSystemId;
}
/** Set or clear the AuxiliaryCoordinateSystem for this view.
* @param acs the new AuxiliaryCoordinateSystem for this view. If undefined, no AuxiliaryCoordinateSystem will be used.
*/
public setAuxiliaryCoordinateSystem(acs?: AuxCoordSystemState) {
this._auxCoordSystem = acs;
this.details.auxiliaryCoordinateSystemId = undefined !== acs ? acs.id : Id64.invalid;
}
/** Determine whether the specified Category is displayed in this view */
public viewsCategory(id: Id64String): boolean {
return this.categorySelector.isCategoryViewed(id);
}
/** Get the aspect ratio (width/height) of this view */
public getAspectRatio(): number {
const extents = this.getExtents();
return extents.x / extents.y;
}
/** Get the aspect ratio skew (x/y, usually 1.0) that is used to exaggerate the y axis of the view. */
public getAspectRatioSkew(): number {
return this.details.aspectRatioSkew;
}
/** Set the aspect ratio skew (x/y) for this view. To remove aspect ratio skew, pass 1.0 for val. */
public setAspectRatioSkew(val: number) {
this.details.aspectRatioSkew = val;
}
/** Get the unit vector that points in the view X (left-to-right) direction.
* @param result optional Vector3d to be used for output. If undefined, a new object is created.
*/
public getXVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(0, result); }
/** Get the unit vector that points in the view Y (bottom-to-top) direction.
* @param result optional Vector3d to be used for output. If undefined, a new object is created.
*/
public getYVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(1, result); }
/** Get the unit vector that points in the view Z (front-to-back) direction.
* @param result optional Vector3d to be used for output. If undefined, a new object is created.
*/
public getZVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(2, result); }
/** Set or clear the clipping volume for this view.
* @param clip the new clipping volume. If undefined, clipping is removed from view.
* @note The ViewState takes ownership of the supplied ClipVector - it should not be modified after passing it to this function.
*/
public setViewClip(clip?: ClipVector) {
this.details.clipVector = clip;
}
/** Get the clipping volume for this view, if defined
* @note Do *not* modify the returned ClipVector. If you wish to change the ClipVector, clone the returned ClipVector, modify it as desired, and pass the clone to [[setViewClip]].
*/
public getViewClip(): ClipVector | undefined {
return this.details.clipVector;
}
/** Set the grid settings for this view */
public setGridSettings(orientation: GridOrientationType, spacing: Point2d, gridsPerRef: number): void {
switch (orientation) {
case GridOrientationType.WorldYZ:
case GridOrientationType.WorldXZ:
if (!this.is3d())
return;
break;
}
this.details.gridOrientation = orientation;
this.details.gridsPerRef = gridsPerRef;
this.details.gridSpacing = spacing;
}
/** Populate the given origin and rotation with information from the grid settings from the grid orientation. */
public getGridSettings(vp: Viewport, origin: Point3d, rMatrix: Matrix3d, orientation: GridOrientationType) {
// start with global origin (for spatial views) and identity matrix
rMatrix.setIdentity();
origin.setFrom(vp.view.isSpatialView() ? vp.view.iModel.globalOrigin : Point3d.create());
switch (orientation) {
case GridOrientationType.View: {
const centerWorld = Point3d.create(0.5, 0.5, 0.5);
vp.npcToWorld(centerWorld, centerWorld);
rMatrix.setFrom(vp.rotation);
rMatrix.multiplyXYZtoXYZ(origin, origin);
origin.z = centerWorld.z;
rMatrix.multiplyTransposeVectorInPlace(origin);
break;
}
case GridOrientationType.WorldXY:
break;
case GridOrientationType.WorldYZ: {
const rowX = rMatrix.getRow(0);
const rowY = rMatrix.getRow(1);
const rowZ = rMatrix.getRow(2);
rMatrix.setRow(0, rowY);
rMatrix.setRow(1, rowZ);
rMatrix.setRow(2, rowX);
break;
}
case GridOrientationType.WorldXZ: {
const rowX = rMatrix.getRow(0);
const rowY = rMatrix.getRow(1);
const rowZ = rMatrix.getRow(2);
rMatrix.setRow(0, rowX);
rMatrix.setRow(1, rowZ);
rMatrix.setRow(2, rowY);
break;
}
}
}
/** Get the grid settings for this view */
public getGridOrientation(): GridOrientationType {
return this.details.gridOrientation;
}
public getGridsPerRef(): number {
return this.details.gridsPerRef;
}
public getGridSpacing(): XAndY {
return this.details.gridSpacing;
}
/** Change the volume that this view displays, keeping its current rotation.
* @param volume The new volume, in world-coordinates, for the view. The resulting view will show all of worldVolume, by fitting a
* view-axis-aligned bounding box around it. For views that are not aligned with the world coordinate system, this will sometimes
* result in a much larger volume than worldVolume.
* @param aspect The X/Y aspect ratio of the view into which the result will be displayed. If the aspect ratio of the volume does not
* match aspect, the shorter axis is lengthened and the volume is centered. If aspect is undefined, no adjustment is made.
* @param options for providing MarginPercent and onExtentsError
* @note for 2d views, only the X and Y values of volume are used.
*/