@itwin/core-frontend
Version:
iTwin.js frontend components
329 lines • 13.7 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Tiles
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DynamicIModelTile = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const IModelApp_1 = require("../../IModelApp");
const internal_1 = require("../../tile/internal");
/** The root tile for the branch of an [[IModelTileTree]] containing graphics for elements that have been modified during the current
* Not intended for direct consumption - exported for use by [[IModelTileTree]].
* [[GraphicalEditingScope]].
*/
class DynamicIModelTile extends internal_1.Tile {
constructor(params, tree) {
super(params, tree);
}
static create(root, elements) {
return new RootTile(root, elements);
}
}
exports.DynamicIModelTile = DynamicIModelTile;
/** The child tiles of the root tile, representing inserted or modified elements and sorted by element Id. */
class ElementTiles extends core_bentley_1.SortedArray {
constructor() {
super((lhs, rhs) => (0, core_bentley_1.compareStrings)(lhs.contentId, rhs.contentId));
}
get array() {
return this._array;
}
}
/** The root tile. Each of its children represent a newly-inserted or modified element. */
class RootTile extends DynamicIModelTile {
_hiddenElements;
transformToTree;
_elements;
get _imodelRoot() { return this.parent; }
get _elementChildren() {
(0, core_bentley_1.assert)(undefined !== this.children);
(0, core_bentley_1.assert)(this.children === this._elements.array);
return this._elements.array;
}
constructor(parent, elements) {
const params = {
parent,
isLeaf: false,
contentId: "dynamic",
range: core_geometry_1.Range3d.createNull(),
maximumSize: parent.tileScreenSize,
};
super(params, parent.tree);
this._hiddenElements = new core_bentley_1.Id64.Uint32Set();
const inverseTransform = parent.tree.iModelTransform.inverse();
(0, core_bentley_1.assert)(undefined !== inverseTransform);
this.transformToTree = inverseTransform;
this._elements = new ElementTiles();
this.loadChildren(); // initially empty.
(0, core_bentley_1.assert)(undefined !== this.children);
this.setIsReady();
this.handleGeometryChanges(elements);
}
get hiddenElements() {
return this._hiddenElements.toId64Array();
}
get dynamicElements() {
return this._elements.array.map((tile) => tile.contentId);
}
get appearanceProvider() {
return this;
}
getFeatureAppearance(source, elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) {
if (this._hiddenElements.has(elemLo, elemHi))
return undefined;
return source.getAppearance(elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId);
}
handleGeometryChanges(changes) {
(0, core_bentley_1.assert)(undefined !== this.children);
for (const change of changes) {
if (change.type !== core_bentley_1.DbOpcode.Insert)
this._hiddenElements.addId(change.id);
let tile = this._elements.findEquivalent((t) => (0, core_bentley_1.compareStrings)(t.contentId, change.id));
if (change.type === core_bentley_1.DbOpcode.Delete) {
if (tile) {
tile[Symbol.dispose]();
this._elements.remove(tile);
}
}
else {
const range = change.range.isNull ? change.range.clone() : this.transformToTree.multiplyRange(change.range);
if (tile)
tile.update(range);
else
this._elements.insert(tile = new ElementTile(this, change.id, range));
}
}
// Recompute range.
this.range.setNull();
for (const element of this._elements)
this.range.extendRange(element.range);
this._imodelRoot.updateDynamicRange(this);
}
_loadChildren(resolve, _reject) {
// This is invoked from constructor. We will add a child per element later - for now just mark the children as having been loaded.
resolve(this._elements.array);
}
get channel() {
throw new Error("Root dynamic tile has no content");
}
async requestContent() {
(0, core_bentley_1.assert)(false, "Root dynamic tile has no content");
return undefined;
}
async readContent(_data, _system, _isCanceled) {
throw new Error("Root dynamic tile has no content");
}
selectTiles(selected, args) {
for (const child of this._elementChildren)
child.selectTiles(selected, args);
}
pruneChildren(olderThan) {
// Never discard ElementTiles - do discard not-recently-used graphics.
for (const child of this._elementChildren)
child.pruneChildren(olderThan);
}
}
/** Represents a single element that has been inserted or had its geometric properties modified during the current [[GraphicalEditingScope]].
* It has no graphics of its own; it has any number of child tiles, each of which have graphics of a different level of detail.
* Its contentId is the element's Id.
*/
class ElementTile extends internal_1.Tile {
constructor(parent, elementId, range) {
super({
parent,
isLeaf: false,
contentId: elementId,
range,
maximumSize: parent.maximumSize,
}, parent.tree);
this.loadChildren();
this.setIsReady();
}
_loadChildren(resolve, _reject) {
// Invoked from constructor. We'll add child tiles later as needed.
resolve([]);
}
get channel() {
throw new Error("ElementTile has no content");
}
async requestContent(_isCanceled) {
(0, core_bentley_1.assert)(false, "ElementTile has no content");
return undefined;
}
async readContent(_data, _system, _isCanceled) {
throw new Error("ElementTile has no content");
}
pruneChildren(olderThan) {
const children = this.children;
(0, core_bentley_1.assert)(undefined !== children);
const partitionIndex = (0, core_bentley_1.partitionArray)(children, (child) => !child.usageMarker.isExpired(olderThan));
// Remove expired children.
if (partitionIndex < children.length) {
const expired = children.splice(partitionIndex);
for (const child of expired)
child[Symbol.dispose]();
}
// Restore ordering.
children.sort((x, y) => y.toleranceLog10 - x.toleranceLog10);
}
selectTiles(selected, args) {
(0, core_bentley_1.assert)(undefined !== this.children);
if (this.isRegionCulled(args))
return;
args.markUsed(this);
// ###TODO: Test content range culled.
// Compute the ideal chord tolerance.
(0, core_bentley_1.assert)(this.maximumSize > 0);
const pixelSize = args.getPixelSizeInMetersAtClosestPoint(this);
(0, core_bentley_1.assert)(pixelSize > 0);
// Round down to the nearest power of ten.
const toleranceLog10 = Math.floor(Math.log10(pixelSize));
// Find (or create) a child tile of desired tolerance. Also find a child tile that can be substituted for the desired tile if that tile's content is not yet loaded.
// NB: Children are sorted in descending order by log10(tolerance)
const children = this.children;
let closestMatch;
let exactMatch;
for (let i = 0; i < children.length; i++) {
const child = children[i];
const tol = child.toleranceLog10;
if (tol > toleranceLog10) {
(0, core_bentley_1.assert)(undefined === exactMatch);
if (child.hasGraphics)
closestMatch = child;
}
else if (tol === toleranceLog10) {
exactMatch = child;
}
else if (tol < toleranceLog10) {
if (!exactMatch)
children.splice(i++, 0, exactMatch = new GraphicsTile(this, toleranceLog10));
if (child.hasGraphics && (!closestMatch || closestMatch.toleranceLog10 > toleranceLog10))
closestMatch = child;
}
}
if (!exactMatch) {
(0, core_bentley_1.assert)(children.length === 0 || children[children.length - 1].toleranceLog10 > toleranceLog10);
children.push(exactMatch = new GraphicsTile(this, toleranceLog10));
}
if (!exactMatch.isReady) {
args.insertMissing(exactMatch);
if (closestMatch) {
selected.push(closestMatch);
args.markUsed(closestMatch);
}
}
else if (exactMatch.hasGraphics) {
selected.push(exactMatch);
args.markUsed(exactMatch);
}
}
update(range) {
range.clone(this.range);
const center = this.range.low.interpolate(0.5, this.range.high);
const radius = 0.5 * this.range.low.distance(this.range.high);
this.boundingSphere.init(center, radius);
// Discard out-dated graphics.
(0, core_bentley_1.assert)(undefined !== this.children);
for (const child of this.children)
child[Symbol.dispose]();
this.children.length = 0;
}
}
function* makeIdSequence() {
let current = 0;
while (true) {
if (current >= Number.MAX_SAFE_INTEGER)
current = 0;
yield ++current;
}
}
const requestIdSequence = makeIdSequence();
/** Supplies graphics of a specific LOD for a single element. */
class GraphicsTile extends internal_1.Tile {
toleranceLog10;
tolerance;
constructor(parent, toleranceLog10) {
(0, core_bentley_1.assert)(Math.round(toleranceLog10) === toleranceLog10);
super({
parent,
isLeaf: true,
contentId: `${parent.contentId}_${toleranceLog10}`,
range: parent.range,
maximumSize: parent.maximumSize,
}, parent.tree);
this.toleranceLog10 = toleranceLog10;
this.tolerance = 10 ** toleranceLog10;
}
computeLoadPriority() {
// We want the element's graphics to be updated as soon as possible
return 0;
}
_loadChildren(resolve, _reject) {
resolve(undefined);
}
get channel() {
return IModelApp_1.IModelApp.tileAdmin.channels.elementGraphicsRpc;
}
async requestContent(_isCanceled) {
// ###TODO tree flags (enforce display priority)
// ###TODO classifiers, animation
(0, core_bentley_1.assert)(undefined !== this.parent);
const requestId = requestIdSequence.next();
(0, core_bentley_1.assert)(!requestId.done);
(0, core_bentley_1.assert)(this.tree instanceof internal_1.IModelTileTree);
const idProvider = this.tree.contentIdProvider;
const props = {
id: requestId.value.toString(16),
elementId: this.parent.contentId,
toleranceLog10: this.toleranceLog10,
formatVersion: idProvider.majorFormatVersion,
location: this.tree.iModelTransform.toJSON(),
contentFlags: idProvider.contentFlags,
omitEdges: !this.tree.edgeOptions,
edgeType: this.tree.edgeOptions && "non-indexed" !== this.tree.edgeOptions.type ? 2 : 1,
smoothPolyfaceEdges: this.tree.edgeOptions && this.tree.edgeOptions.smooth,
clipToProjectExtents: this.tree.is3d,
sectionCut: this.tree.stringifiedSectionClip,
useAbsolutePositions: true,
};
return IModelApp_1.IModelApp.tileAdmin.requestElementGraphics(this.tree.iModel, props);
}
async readContent(data, system, isCanceled) {
if (undefined === isCanceled)
isCanceled = () => !this.isLoading;
(0, core_bentley_1.assert)(data instanceof Uint8Array);
const stream = core_bentley_1.ByteStream.fromUint8Array(data);
const position = stream.curPos;
const format = stream.readUint32();
stream.curPos = position;
// ###TODO: IModelGraphics format wraps IModel format.
(0, core_bentley_1.assert)(core_common_1.TileFormat.IModel === format);
const tree = this.tree;
(0, core_bentley_1.assert)(tree instanceof internal_1.IModelTileTree);
const { iModel, modelId, is3d, containsTransformNodes } = tree;
const reader = internal_1.ImdlReader.create({
stream, iModel, modelId, is3d, system, isCanceled, containsTransformNodes,
type: tree.batchType,
loadEdges: false !== tree.edgeOptions,
options: { tileId: this.contentId },
timeline: tree.timeline,
});
let content = { isLeaf: true };
if (reader) {
try {
content = await reader.read();
}
catch {
//
}
}
return content;
}
}
//# sourceMappingURL=DynamicIModelTile.js.map