View Decorations
A View Decoration shows application-generated graphics in a ScreenViewport in addition to the persistent (i.e. scene) geometry displayed by the Viewport itself. In contrast to the graphics from the persistent geometry (e.g. the Models), View Decorations must be re-evaluated every time a frame is rendered. In this sense, they decorate the frame with graphics that are only valid for a single frame.
View Decorators
The process of creating View Decorations starts by adding an object that implements the Decorator interface to the ViewManager
via the ViewManager.addDecorator method. The most important part of the Decorate
interface is the Decorator.decorate method, which is called every time iTwin.js renders a frame for any ScreenViewport. The argument to the decorate
method is a DecorateContext that supplies information about the ScreenViewport being rendered, as well as methods to create and save decoration graphics. The DecorateContext.viewport member holds the target viewport. If you wish to decorate only a single viewport, you must test this member against your intended viewport.
The job of the decorate
method is to supply the graphics (the Decorations) for a single frame of a single ScreenViewport.
A Decorator
remains active until you call ViewManager.dropDecorator (Note: ViewManager.addDecorator returns a method that calls this for you if you wish to use it.)
A InteractiveTool can also show decorations and does not need to call the ViewManager.addDecorator method to add itself. InteractiveTool.decorate is called for the active tool to add its decorations, InteractiveTool.decorate is not called when the tool is paused by another tool such as a ViewTool. To show decorations while paused, a tool can implement InteractiveTool.decorateSuspended.
To learn how to optimize when your decorations are invalidated by using cached decorations, see the section on cached decorations.
Categories of View Decorations
Sometimes decorations are meant to intersperse with the scene geometry, and sometimes they are meant to display atop of it. For this reason, there are 3 broad categories of View Decorations:
- View Graphic Decorations - are drawn using iTwin.js render primitives into the WebGL context.
- Canvas Decoration - are drawn onto the 2d canvas using CanvasRenderingContext2D. Canvas decorations are always on top of View Graphic Decorations
- HTML Decorations - are HTMLElements that are added to the DOM. HTML decorations are always on top of Canvas Decorations.
Note that a single Decorator can create multiple Decorations, from any or all of the categories above.
View Graphic Decorations
View Graphic Decorations are drawn using the iTwin.js rendering system through WebGL. There are 5 types of View Graphic Decorations, defined by the GraphicType enum.
- GraphicType.ViewBackground - displayed behind all scene geometry
- GraphicType.Scene - interspersed with scene geometry, rendered using view's render mode and lighting
- GraphicType.WorldDecoration - interspersed with scene geometry, rendered with smooth shading and default lighting
- GraphicType.WorldOverlay - displayed atop scene geometry
- GraphicType.ViewOverlay - displayed atop scene geometry, drawn in view coordinates.
Note that
GraphicType.ViewOverlay
performs the same function as Canvas Decorators and are generally less flexible and less efficient. Prefer Canvas Decorations instead.
You typically create View Graphic Decorations by calling DecorateContext.createGraphicBuilder on the context supplied to decorate
, supplying the appropriate GraphicType
.
You then add one or more graphics to the GraphicBuilder using its methods. Finally, you add the completed graphics to the frame by calling DecorateContext.addDecorationFromBuilder. Another option is to use readGltfGraphics to produce the graphics from a glTF asset and supply them to DecorateContext.addDecoration.
GraphicBuilder decorations
The following example illustrates creating a view graphic decoration to show the IModel.projectExtents in spatial views using a GraphicBuilder:
glTF decorations
The following example illustrates creating a view graphic decoration from a glTF asset using readGltfGraphics:
Pickable View Graphic Decorations
View Graphic Decorations are drawn into or atop the scene. To make your View Graphic Decorations pickable (i.e. allow the user to click on them to perform an action, or to give feedback when the cursor hovers over them), you must:
- Obtain a
TransientId
by calling IModelConnection.transientIds.next - Supply that TransientId as the 3rd argument to DecorateContext.createGraphicBuilder
- Implement Decorator.testDecorationHit to return
true
when the supplied Id matches your decoration's Id. - Implement Decorator.getDecorationToolTip and/or Decorator.onDecorationButtonEvent to supply a tooltip and perform an action when your decoration is clicked.
The following example illustrates creating a pickable view graphic decoration in order to supply a tooltip message when under the cursor:
If you have many decorations to draw with different pickable Ids, it can be more efficient to produce them using a single GraphicBuilder than producing one graphic per pickable Id. This can be achieved by calling GraphicBuilder.activatePickableId or to specify the pickable Id to associate with subsequently-added geometry.
Canvas Decorations
A CanvasDecoration is drawn atop the scene using CanvasRenderingContext2D. To add a CanvasDecoration, call DecorateContext.addCanvasDecoration from your Decorator.decorate method.
CanvasDecorators must implement CanvasDecoration.drawDecoration to supply visible graphics, by calling methods on CanvasRenderingContext2D.
CanvasDecorators may optionally include the member CanvasDecoration.position, that becomes the 0,0 point for your CanvasRenderingContext2D calls.
The following example illustrates creating a canvas decoration to show a plus symbol at the center of the view:
Markers are a type of Canvas Decoration
Pickable Canvas Decorations
To make your CanvasDecorations pickable, implement CanvasDecoration.pick and return true
if the supplied point lies within your decoration's region.
If you return true from your CanvasDecoration.pick
method, you can implement:
- CanvasDecoration.onMouseEnter - the mouse has entered your decoration
- CanvasDecoration.onMouseLeave - the mouse has left your decoration
- CanvasDecoration.onMouseMove - the mouse has moved inside your decoration
- CanvasDecoration.onMouseButton - a mouse button went up or down inside your decoration
- CanvasDecoration.onWheel - the wheel was rolled over your decoration
- CanvasDecoration.decorationCursor - the cursor to be displayed while the pointer is in your decoration
HTML Decorations
HTML Decorations are simply HTMLElements that you add to the DOM on top of your views. In your Decorator.decorate method, use DecorateContext.addHtmlDecoration to add HTML Decorations.
HTML Decorators are appended to an HTMLDivElement called "overlay-decorators" that is created by ScreenViewport.create. All children of that Div are removed every frame, so you must re-add your HTML Decorator each time your Decorator.decorate method is called.
The "overlay-decorators" Div is stacked on top of the canvas, but behind the "overlay-tooltip" Div (where tooltips are displayed.)
Decoration Precedence
The order of precedence for Decorations is:
- GraphicType.ViewBackground decorations are drawn behind the scene
- GraphicType.Scene and GraphicType.WorldDecoration decorations are drawn in the scene
- GraphicType.WorldOverlay and GraphicType.ViewOverlay decorations are drawn on top of the scene
- Canvas Decorations are drawn on top of all View Graphic decorations
- HTML Decorations are drawn on top of all Canvas decorations
- The ToolTip is on top of all HTML decorations
Within a decoration type, the last decoration drawn is on top of earlier decorations.
Cached Decorations
As described in the section about view decorators, a decorator object's decorate
method is invoked to create new Decorations whenever a viewport's decorations are invalidated. Decorations are invalidated quite frequently - for example, every time the view frustum or scene changes, and even on every mouse motion. Most decorators' decorations only actually need to change when the scene changes. Having to regenerate them every time the mouse moves is quite wasteful and - for all but the most trivial decorations - can negatively impact framerate. Here is an example of a decorator that draws some complicated shape in a specified color:
We can avoid unnecessarily recreating decorations by defining the useCachedDecorations
property on a decorator object. If this is true
, then whenever the viewport's decorations are invalidated, the viewport will first check to see if it already has cached decorations for this decorator. If so, it will simply reuse them; if not, it will invoke decorate
and cache the result. When the scene changes, our cached decorations will automatically be discarded. Here is the decorator from above, updated to use cached decorations:
For a decorator defining the useCachedDecorations
property as true, the functions ViewManager.invalidateCachedDecorationsAllViews and ScreenViewport.invalidateCachedDecorations give the decorator much tighter control over when its decorations are regenerated. This can potentially result in significantly improved performance.
Last Updated: 20 December, 2022