import { expect } from "chai" ; import { ByteStream , Id64 , Id64String } from "@itwin/core-bentley" ; import { BatchType , CurrentImdlVersion , EdgeType , ImdlFlags , ImdlHeader , IModelRpcProps , IModelTileRpcInterface , IModelTileTreeId , iModelTileTreeIdToString , ModelProps , RelatedElementProps , RenderMode , TileContentSource , TileFormat , TileReadStatus , ViewFlags , } from "@itwin/core-common" ; import { GeometricModelState , ImdlReader , IModelApp , IModelConnection , IModelTileTree , iModelTileTreeParamsFromJSON , MockRender , RenderGraphic , SnapshotConnection , TileAdmin , TileRequest , TileTreeLoadStatus , ViewState , } from "@itwin/core-frontend" ; import { SurfaceType } from "@itwin/core-frontend/lib/cjs/render-primitives" ; import { Batch , GraphicsArray , MeshGraphic , PolylineGeometry , Primitive , RenderOrder } from "@itwin/core-frontend/lib/cjs/webgl" ; import { TestUtility } from "../../TestUtility" ; import { TileTestCase , TileTestData } from "./data/TileIO.data" ; import { TILE_DATA_1_1 } from "./data/TileIO.data.1.1" ; import { TILE_DATA_1_2 } from "./data/TileIO.data.1.2" ; import { TILE_DATA_1_3 } from "./data/TileIO.data.1.3" ; import { TILE_DATA_1_4 } from "./data/TileIO.data.1.4" ; import { TILE_DATA_2_0 } from "./data/TileIO.data.2.0" ; import { changeHeaderLength , changeMajorVersion , changeMinorVersion } from "./data/TileIO.data.fake" ; const testCases = [ TILE_DATA_1_1 , TILE_DATA_1_2 , TILE_DATA_1_3 , TILE_DATA_1_4 , TILE_DATA_2_0 , ] ; const currentTestCase = testCases [ testCases . length - 1 ] ; const numBaseTestCases = testCases . length ; for ( let i = 0 ; i < numBaseTestCases ; i ++ ) { const testCase = testCases [ i ] ; testCases . push ( changeMinorVersion ( testCase , 5000 + i ) ) ; testCases . push ( changeMajorVersion ( testCase , 6000 + i ) ) ; testCases . push ( changeHeaderLength ( testCase , 7000 + i , 8 ) ) ; } export class FakeGMState extends GeometricModelState { public get is3d ( ) : boolean { return true ; } public override get is2d ( ) : boolean { return ! this . is3d ; } public constructor ( props , iModel ) { super ( props , iModel ) ; } } export class FakeModelProps implements ModelProps { public modeledElement : RelatedElementProps ; public classFullName : string = "fake" ; public constructor ( props ) { this . modeledElement = props ; } } export class FakeREProps implements RelatedElementProps { public id : Id64String ; public constructor ( ) { this . id = Id64 . invalid ; } } export function fakeViewState ( iModel , options ? ) { const scheduleState = options ?. animationId ? { getModelAnimationId : ( ) => options . animationId } : undefined ; return { iModel , is3d : ( ) => true !== options ?. is2d , viewFlags : new ViewFlags ( { renderMode : options ?. renderMode ?? RenderMode . SmoothShade , visibleEdges : options ?. visibleEdges ?? false } ) , displayStyle : { scheduleState } } as ViewState ; } function delta ( a , b ) { return Math . abs ( a - b ) ; } type ProcessGraphic = ( graphic : RenderGraphic ) => void ; function processHeader ( data , test , numElements ) { const stream = ByteStream . fromUint8Array ( test . bytes ) ; stream . reset ( ) ; const header = new ImdlHeader ( stream ) ; expect ( header . isValid ) . to . be . true ; expect ( header . format ) . to . equal ( TileFormat . IModel ) ; expect ( header . versionMajor ) . to . equal ( data . versionMajor ) ; expect ( header . versionMinor ) . to . equal ( data . versionMinor ) ; expect ( header . headerLength ) . to . equal ( data . headerLength ) ; expect ( header . tileLength ) . to . equal ( test . bytes . byteLength ) ; expect ( header . flags ) . to . equal ( test . flags ) ; expect ( header . numElementsIncluded ) . to . equal ( numElements ) ; expect ( header . numElementsExcluded ) . to . equal ( 0 ) ; expect ( header . isReadableVersion ) . to . equal ( ! data . unreadable ) ; } function createReader ( imodel , data , test ) { const model = new FakeGMState ( new FakeModelProps ( new FakeREProps ( ) ) , imodel ) ; const stream = ByteStream . fromUint8Array ( test . bytes ) ; const reader = ImdlReader . create ( { stream , iModel : imodel , modelId : model . id , is3d : model . is3d , system : IModelApp . renderSystem } ) ; expect ( undefined === reader ) . to . equal ( ! ! data . unreadable ) ; return reader ; } async function processRectangle ( data , imodel , processGraphic ) { processHeader ( data , data . rectangle , 1 ) ; const reader = createReader ( imodel , data , data . rectangle ) ; if ( undefined !== reader ) { const result = await reader . read ( ) ; expect ( result . readStatus ) . to . equal ( TileReadStatus . Success ) ; expect ( result . isLeaf ) . to . be . true ; expect ( result . contentRange ) . not . to . be . undefined ; const low = result . contentRange ! . low ; expect ( delta ( low . x , - 2.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( low . y , - 5.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( low . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; const high = result . contentRange ! . high ; expect ( delta ( high . x , 2.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( high . y , 5.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( high . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( result . graphic ) . not . to . be . undefined ; processGraphic ( result . graphic ! ) ; } } async function processEachRectangle ( imodel , processGraphic ) { for ( const data of testCases ) await processRectangle ( data , imodel , processGraphic ) ; } async function processTriangles ( data , imodel , processGraphic ) { processHeader ( data , data . triangles , 6 ) ; const reader = createReader ( imodel , data , data . triangles ) ; if ( undefined !== reader ) { const result = await reader . read ( ) ; expect ( result . readStatus ) . to . equal ( TileReadStatus . Success ) ; expect ( result . isLeaf ) . to . be . true ; expect ( result . contentRange ) . not . to . be . undefined ; const low = result . contentRange ! . low ; expect ( delta ( low . x , - 7.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( low . y , - 10.0 ) ) . to . be . lessThan ( 0.00051 ) ; expect ( delta ( low . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; const high = result . contentRange ! . high ; expect ( delta ( high . x , 7.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( high . y , 10.0 ) ) . to . be . lessThan ( 0.00051 ) ; expect ( delta ( high . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( result . graphic ) . not . to . be . undefined ; processGraphic ( result . graphic ! ) ; } } async function processEachTriangles ( imodel , processGraphic ) { for ( const data of testCases ) await processTriangles ( data , imodel , processGraphic ) ; } async function processLineString ( data , imodel , processGraphic ) { processHeader ( data , data . lineString , 1 ) ; const reader = createReader ( imodel , data , data . lineString ) ; if ( undefined !== reader ) { const result = await reader . read ( ) ; expect ( result . readStatus ) . to . equal ( TileReadStatus . Success ) ; expect ( result . isLeaf ) . to . be . true ; expect ( result . contentRange ) . not . to . be . undefined ; const low = result . contentRange ! . low ; expect ( delta ( low . x , - 7.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( low . y , - 10.0 ) ) . to . be . lessThan ( 0.00051 ) ; expect ( delta ( low . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; const high = result . contentRange ! . high ; expect ( delta ( high . x , 7.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( high . y , 10.0 ) ) . to . be . lessThan ( 0.00051 ) ; expect ( delta ( high . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( result . graphic ) . not . to . be . undefined ; processGraphic ( result . graphic ! ) ; } } async function processEachLineString ( imodel , processGraphic ) { for ( const data of testCases ) await processLineString ( data , imodel , processGraphic ) ; } async function processLineStrings ( data , imodel , processGraphic ) { processHeader ( data , data . lineStrings , 3 ) ; const reader = createReader ( imodel , data , data . lineStrings ) ; if ( undefined !== reader ) { const result = await reader . read ( ) ; expect ( result . readStatus ) . to . equal ( TileReadStatus . Success ) ; expect ( result . isLeaf ) . to . be . true ; expect ( result . contentRange ) . not . to . be . undefined ; const low = result . contentRange ! . low ; expect ( delta ( low . x , - 7.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( low . y , - 30.0 ) ) . to . be . lessThan ( 0.0016 ) ; expect ( delta ( low . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; const high = result . contentRange ! . high ; expect ( delta ( high . x , 7.5 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( high . y , 30.0 ) ) . to . be . lessThan ( 0.0016 ) ; expect ( delta ( high . z , 0.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( result . graphic ) . not . to . be . undefined ; processGraphic ( result . graphic ! ) ; } } async function processEachLineStrings ( imodel , processGraphic ) { for ( const data of testCases ) await processLineStrings ( data , imodel , processGraphic ) ; } async function processCylinder ( data , imodel , processGraphic ) { processHeader ( data , data . cylinder , 1 ) ; const reader = createReader ( imodel , data , data . cylinder ) ; if ( undefined !== reader ) { const result = await reader . read ( ) ; expect ( result . readStatus ) . to . equal ( TileReadStatus . Success ) ; expect ( result . isLeaf ) . to . be . false ; expect ( result . contentRange ) . not . to . be . undefined ; const low = result . contentRange ! . low ; expect ( delta ( low . x , - 2.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( low . y , - 2.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( low . z , - 3.0 ) ) . to . be . lessThan ( 0.0005 ) ; const high = result . contentRange ! . high ; expect ( delta ( high . x , 2.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( high . y , 2.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( delta ( high . z , 3.0 ) ) . to . be . lessThan ( 0.0005 ) ; expect ( result . graphic ) . not . to . be . undefined ; processGraphic ( result . graphic ! ) ; } } async function processEachCylinder ( imodel , processGraphic ) { for ( const data of testCases ) await processCylinder ( data , imodel , processGraphic ) ; } describe ( "TileIO (WebGL)" , ( ) => { let imodel ; before ( async ( ) => { await TestUtility . startFrontend ( ) ; imodel = await SnapshotConnection . openFile ( "test.bim" ) ; } ) ; after ( async ( ) => { if ( imodel ) await imodel . close ( ) ; await TestUtility . shutdownFrontend ( ) ; } ) ; it ( "should read an iModel tile containing a single rectangle" , async ( ) => { if ( IModelApp . initialized ) { await processEachRectangle ( imodel , ( graphic ) => { expect ( graphic ) . to . be . instanceOf ( Batch ) ; const batch = graphic as Batch ; expect ( batch . featureTable . isUniform ) . to . be . true ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . to . be . instanceOf ( MeshGraphic ) ; const mg = batch . graphic as MeshGraphic ; expect ( mg . surfaceType ) . to . equal ( SurfaceType . Lit ) ; expect ( mg . meshData ) . not . to . be . undefined ; expect ( mg . meshData . edgeLineCode ) . to . equal ( 0 ) ; expect ( mg . meshData . edgeWidth ) . to . equal ( 1 ) ; expect ( mg . meshData . isPlanar ) . to . be . true ; expect ( mg . meshData . lut . numRgbaPerVertex ) . to . equal ( 4 ) ; expect ( mg . meshData . lut . numVertices ) . to . equal ( 4 ) ; expect ( mg . meshData . lut . colorInfo . isUniform ) . to . be . true ; expect ( mg . meshData . lut . colorInfo . isNonUniform ) . to . be . false ; expect ( mg . meshData . lut . colorInfo . hasTranslucency ) . to . be . false ; } ) ; } } ) ; it ( "should read an iModel tile containing multiple meshes and non-uniform feature/color tables" , async ( ) => { if ( IModelApp . initialized ) { await processEachTriangles ( imodel , ( graphic ) => { expect ( graphic ) . to . be . instanceOf ( Batch ) ; const batch = graphic as Batch ; expect ( batch . featureTable . isUniform ) . to . be . false ; expect ( batch . featureTable . numFeatures ) . to . equal ( 6 ) ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . to . be . instanceOf ( GraphicsArray ) ; const list = batch . graphic as GraphicsArray ; expect ( list . graphics . length ) . to . equal ( 2 ) ; expect ( list . graphics [ 0 ] ) . to . be . instanceOf ( MeshGraphic ) ; let mg = list . graphics [ 0 ] as MeshGraphic ; expect ( mg . surfaceType ) . to . be . equal ( SurfaceType . Lit ) ; expect ( mg . meshData ) . not . to . be . undefined ; expect ( mg . meshData . edgeLineCode ) . to . equal ( 0 ) ; expect ( mg . meshData . edgeWidth ) . to . equal ( 1 ) ; expect ( mg . meshData . isPlanar ) . to . be . true ; expect ( mg . meshData . lut . numRgbaPerVertex ) . to . equal ( 4 ) ; expect ( mg . meshData . lut . numVertices ) . to . equal ( 9 ) ; expect ( mg . meshData . lut . colorInfo . isUniform ) . to . be . false ; expect ( mg . meshData . lut . colorInfo . isNonUniform ) . to . be . true ; expect ( mg . meshData . lut . colorInfo . hasTranslucency ) . to . be . false ; expect ( list . graphics [ 1 ] ) . to . be . instanceOf ( MeshGraphic ) ; mg = list . graphics [ 1 ] as MeshGraphic ; expect ( mg . surfaceType ) . to . be . equal ( SurfaceType . Lit ) ; expect ( mg . meshData ) . not . to . be . undefined ; expect ( mg . meshData . edgeLineCode ) . to . equal ( 0 ) ; expect ( mg . meshData . edgeWidth ) . to . equal ( 1 ) ; expect ( mg . meshData . isPlanar ) . to . be . true ; expect ( mg . meshData . lut . numRgbaPerVertex ) . to . equal ( 4 ) ; expect ( mg . meshData . lut . numVertices ) . to . equal ( 9 ) ; expect ( mg . meshData . lut . colorInfo . isUniform ) . to . be . false ; expect ( mg . meshData . lut . colorInfo . isNonUniform ) . to . be . true ; expect ( mg . meshData . lut . colorInfo . hasTranslucency ) . to . be . true ; } ) ; } } ) ; it ( "should read an iModel tile containing single open yellow line string" , async ( ) => { if ( IModelApp . initialized ) { await processEachLineString ( imodel , ( graphic ) => { expect ( graphic ) . to . be . instanceOf ( Batch ) ; const batch = graphic as Batch ; expect ( batch . featureTable . isUniform ) . to . be . true ; expect ( batch . featureTable . numFeatures ) . to . equal ( 1 ) ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . to . be . instanceOf ( Primitive ) ; const plinePrim = batch . graphic as Primitive ; expect ( plinePrim . hasFeatures ) . to . be . true ; expect ( plinePrim . isEdge ) . to . be . false ; expect ( plinePrim . isLit ) . to . be . false ; expect ( plinePrim . renderOrder ) . to . equal ( RenderOrder . Linear ) ; expect ( plinePrim . cachedGeometry ) . to . not . be . undefined ; const plGeom = plinePrim . cachedGeometry as PolylineGeometry ; expect ( plGeom . numIndices ) . to . equal ( 114 ) ; expect ( plGeom . lut . numVertices ) . to . equal ( 6 ) ; expect ( plGeom . lineCode ) . to . equal ( 0 ) ; expect ( plGeom . lineWeight ) . to . equal ( 9 ) ; expect ( plGeom . isPlanar ) . to . be . false ; } ) ; } } ) ; it ( "should read an iModel tile containing multiple line strings" , async ( ) => { if ( IModelApp . initialized ) { await processEachLineStrings ( imodel , ( graphic ) => { expect ( graphic ) . to . be . instanceOf ( Batch ) ; const batch = graphic as Batch ; expect ( batch . featureTable . isUniform ) . to . be . false ; expect ( batch . featureTable . numFeatures ) . to . equal ( 3 ) ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . to . be . instanceOf ( GraphicsArray ) ; const list = batch . graphic as GraphicsArray ; expect ( list . graphics . length ) . to . equal ( 2 ) ; expect ( list . graphics [ 0 ] ) . to . be . instanceOf ( Primitive ) ; let plinePrim = list . graphics [ 0 ] as Primitive ; expect ( plinePrim . hasFeatures ) . to . be . true ; expect ( plinePrim . isEdge ) . to . be . false ; expect ( plinePrim . isLit ) . to . be . false ; expect ( plinePrim . renderOrder ) . to . equal ( RenderOrder . Linear ) ; expect ( plinePrim . cachedGeometry ) . to . not . be . undefined ; let plGeom = plinePrim . cachedGeometry as PolylineGeometry ; expect ( plGeom . numIndices ) . to . equal ( 114 ) ; expect ( plGeom . lut . numVertices ) . to . equal ( 6 ) ; expect ( plGeom . lineCode ) . to . equal ( 0 ) ; expect ( plGeom . lineWeight ) . to . equal ( 9 ) ; expect ( plGeom . isPlanar ) . to . be . false ; expect ( list . graphics [ 1 ] ) . to . be . instanceOf ( Primitive ) ; plinePrim = list . graphics [ 1 ] as Primitive ; expect ( plinePrim . hasFeatures ) . to . be . true ; expect ( plinePrim . isEdge ) . to . be . false ; expect ( plinePrim . isLit ) . to . be . false ; expect ( plinePrim . renderOrder ) . to . equal ( RenderOrder . Linear ) ; expect ( plinePrim . cachedGeometry ) . to . not . be . undefined ; plGeom = plinePrim . cachedGeometry as PolylineGeometry ; expect ( plGeom . numIndices ) . to . equal ( 228 ) ; expect ( plGeom . lut . numVertices ) . to . equal ( 12 ) ; expect ( plGeom . lineCode ) . to . equal ( 2 ) ; expect ( plGeom . lineWeight ) . to . equal ( 9 ) ; expect ( plGeom . isPlanar ) . to . be . false ; } ) ; } } ) ; it ( "should read an iModel tile containing edges and silhouettes" , async ( ) => { if ( IModelApp . initialized ) { await processEachCylinder ( imodel , ( graphic ) => { expect ( graphic ) . to . be . instanceOf ( Batch ) ; const batch = graphic as Batch ; expect ( batch . featureTable . isUniform ) . to . be . true ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . to . be . instanceOf ( MeshGraphic ) ; const mg = batch . graphic as MeshGraphic ; expect ( mg . surfaceType ) . to . equal ( SurfaceType . Lit ) ; expect ( mg . meshData ) . not . to . be . undefined ; expect ( mg . meshData . edgeLineCode ) . to . equal ( 0 ) ; expect ( mg . meshData . edgeWidth ) . to . equal ( 1 ) ; expect ( mg . meshData . isPlanar ) . to . be . false ; expect ( mg . meshData . lut . numRgbaPerVertex ) . to . equal ( 4 ) ; expect ( mg . meshData . lut . numVertices ) . to . equal ( 146 ) ; expect ( mg . meshData . lut . colorInfo . isUniform ) . to . be . true ; expect ( mg . meshData . lut . colorInfo . isNonUniform ) . to . be . false ; expect ( mg . meshData . lut . colorInfo . hasTranslucency ) . to . be . false ; } ) ; } } ) ; } ) ; describe ( "TileIO (mock render)" , ( ) => { let imodel ; before ( async ( ) => { await TestUtility . startFrontend ( undefined , true ) ; imodel = await SnapshotConnection . openFile ( "test.bim" ) ; } ) ; after ( async ( ) => { if ( imodel ) await imodel . close ( ) ; await TestUtility . shutdownFrontend ( ) ; } ) ; it ( "should support canceling operation" , async ( ) => { if ( IModelApp . initialized ) { const model = new FakeGMState ( new FakeModelProps ( new FakeREProps ( ) ) , imodel ) ; const stream = ByteStream . fromUint8Array ( currentTestCase . rectangle . bytes ) ; const reader = ImdlReader . create ( { stream , iModel : model . iModel , modelId : model . id , is3d : model . is3d , system : IModelApp . renderSystem , type : BatchType . Primary , loadEdges : true , isCanceled : ( _ ) => true } ) ; expect ( reader ) . not . to . be . undefined ; const result = await reader ! . read ( ) ; expect ( result . readStatus ) . to . equal ( TileReadStatus . Canceled ) ; } } ) ; it ( "should obtain tiles from backend" , async ( ) => { const modelProps = await imodel . models . getProps ( "0x22" ) ; expect ( modelProps . length ) . to . equal ( 1 ) ; const tree = await IModelApp . tileAdmin . requestTileTreeProps ( imodel , modelProps [ 0 ] . id ! . toString ( ) ) ; expect ( tree . id ) . to . equal ( modelProps [ 0 ] . id ) ; expect ( tree . maxTilesToSkip ) . to . equal ( 1 ) ; expect ( tree . rootTile ) . not . to . be . undefined ; const rootTile = tree . rootTile ; expect ( rootTile . contentId ) . to . equal ( "0/0/0/0/1" ) ; expect ( rootTile . isLeaf ) . to . be . false ; } ) ; it ( "should read an iModel tile containing a single rectangle" , async ( ) => { await processEachRectangle ( imodel , ( graphic ) => { expect ( graphic ) . instanceof ( MockRender . Batch ) ; const batch = graphic as MockRender . Batch ; expect ( batch . featureTable . isUniform ) . to . be . true ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . instanceof ( MockRender . Graphic ) ; } ) ; } ) ; it ( "should read an iModel tile containing multiple meshes and non-uniform feature/color tables" , async ( ) => { await processEachTriangles ( imodel , ( graphic ) => { expect ( graphic ) . instanceof ( MockRender . Batch ) ; const batch = graphic as MockRender . Batch ; expect ( batch . featureTable . isUniform ) . to . be . false ; expect ( batch . featureTable . numFeatures ) . to . equal ( 6 ) ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . instanceof ( MockRender . List ) ; const list = batch . graphic as MockRender . List ; expect ( list . graphics . length ) . to . equal ( 2 ) ; } ) ; } ) ; it ( "should read an iModel tile containing single open yellow line string" , async ( ) => { await processEachLineString ( imodel , ( graphic ) => { expect ( graphic ) . instanceof ( MockRender . Batch ) ; const batch = graphic as MockRender . Batch ; expect ( batch . featureTable . isUniform ) . to . be . true ; expect ( batch . featureTable . numFeatures ) . to . equal ( 1 ) ; expect ( batch . graphic ) . not . to . be . undefined ; } ) ; } ) ; it ( "should read an iModel tile containing multiple line strings" , async ( ) => { await processEachLineStrings ( imodel , ( graphic ) => { expect ( graphic ) . instanceof ( MockRender . Batch ) ; const batch = graphic as MockRender . Batch ; expect ( batch . featureTable . isUniform ) . to . be . false ; expect ( batch . featureTable . numFeatures ) . to . equal ( 3 ) ; expect ( batch . graphic ) . not . to . be . undefined ; expect ( batch . graphic ) . to . be . instanceOf ( MockRender . List ) ; const list = batch . graphic as MockRender . List ; expect ( list . graphics . length ) . to . equal ( 2 ) ; } ) ; } ) ; it ( "should read an iModel tile containing edges and silhouettes" , async ( ) => { await processEachCylinder ( imodel , ( graphic ) => { expect ( graphic ) . instanceof ( MockRender . Batch ) ; const batch = graphic as MockRender . Batch ; expect ( batch . featureTable . isUniform ) . to . be . true ; expect ( batch . graphic ) . not . to . be . undefined ; } ) ; } ) ; } ) ; async function waitUntil ( condition ) { if ( condition ( ) ) return ; await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 100 ) ) ; return waitUntil ( condition ) ; } async function getGeometricModel ( imodel , modelId ) { await imodel . models . load ( modelId ) ! ; const baseModel = imodel . models . getLoaded ( modelId ) ! ; expect ( baseModel ) . not . to . be . undefined ; const model = baseModel . asGeometricModel ! ; expect ( model ) . not . to . be . undefined ; return model ; } async function getTileTree ( imodel , modelId , edgesRequired = true , animationId ? ) { const model = await getGeometricModel ( imodel , modelId ) ; return getPrimaryTileTree ( model , edgesRequired , animationId ) ; } async function getPrimaryTileTree ( model , edgesRequired = true , animationId ? ) { const view = fakeViewState ( model . iModel , { animationId , visibleEdges : edgesRequired } ) ; const ref = model . createTileTreeReference ( view ) ; const owner = ref . treeOwner ; owner . load ( ) ; await waitUntil ( ( ) => { return TileTreeLoadStatus . Loaded === owner . loadStatus ; } ) ; const tree = owner . tileTree ; expect ( tree ) . not . to . be . undefined ; return tree ! as IModelTileTree ; } describe ( "mirukuru TileTree" , ( ) => { let imodel ; class TestTarget extends MockRender . OnScreenTarget { public override setRenderToScreen ( toScreen ) { return toScreen ? document . createElement ( "canvas" ) : undefined ; } } class TestSystem extends MockRender . System { public override createTarget ( canvas ) { return new TestTarget ( this , canvas ) ; } } before ( async ( ) => { MockRender . App . systemFactory = ( ) => new TestSystem ( ) ; await MockRender . App . startup ( ) ; imodel = await SnapshotConnection . openFile ( "mirukuru.ibim" ) ; } ) ; afterEach ( ( ) => { if ( imodel ) { imodel . tiles . reset ( ) ; IModelApp . tileAdmin . resetStatistics ( ) ; } } ) ; after ( async ( ) => { if ( imodel ) await imodel . close ( ) ; await MockRender . App . shutdown ( ) ; } ) ; it ( "should obtain a single leaf tile" , async ( ) => { const modelProps = await imodel . models . getProps ( "0x1c" ) ; expect ( modelProps . length ) . to . equal ( 1 ) ; const treeProps = await IModelApp . tileAdmin . requestTileTreeProps ( imodel , modelProps [ 0 ] . id ! ) ; expect ( treeProps . id ) . to . equal ( modelProps [ 0 ] . id ) ; expect ( treeProps . rootTile ) . not . to . be . undefined ; const rootTile = treeProps . rootTile ; expect ( rootTile . isLeaf ) . not . to . be . true ; const options = { is3d : true , batchType : BatchType . Primary , edgesRequired : true , allowInstancing : true } ; const params = iModelTileTreeParamsFromJSON ( treeProps , imodel , "0x1c" , options ) ; const tree = new IModelTileTree ( params , { edges : EdgeType . NonIndexed , type : BatchType . Primary } ) ; const response = await tree . staticBranch . requestContent ( ) ; expect ( response ) . not . to . be . undefined ; expect ( response ) . instanceof ( Uint8Array ) ; const isCanceled = ( ) => false ; const gfx = await tree . staticBranch . readContent ( response as Uint8Array , IModelApp . renderSystem , isCanceled ) ; expect ( gfx ) . not . to . be . undefined ; expect ( gfx . graphic ) . not . to . be . undefined ; expect ( gfx . isLeaf ) . to . be . true ; expect ( gfx . contentRange ) . not . to . be . undefined ; expect ( gfx . contentRange ! . isNull ) . to . be . false ; const projExt = imodel . projectExtents ; expect ( projExt . maxLength ( ) ) . to . equal ( gfx . contentRange ! . maxLength ( ) ) ; } ) ; it ( "should load model's tile tree asynchronously" , async ( ) => { const tree = getTileTree ( imodel , "0x1c" ) ! ; expect ( tree ) . not . to . be . undefined ; } ) ; it ( "should have expected metadata for root tile" , async ( ) => { const test = async ( tree , expectedVersion , expectedRootContentId ) => { expect ( tree ) . not . to . be . undefined ; expect ( tree . staticBranch . contentId ) . to . equal ( expectedRootContentId ) ; const response = await tree . staticBranch . requestContent ( ) ; expect ( response ) . instanceof ( Uint8Array ) ; const stream = ByteStream . fromUint8Array ( response as Uint8Array ) ; const header = new ImdlHeader ( stream ) ; expect ( header . isValid ) . to . be . true ; expect ( header . format ) . to . equal ( TileFormat . IModel ) ; expect ( header . version ) . to . equal ( expectedVersion ) ; expect ( header . versionMajor ) . to . equal ( expectedVersion >> 0x10 ) ; expect ( header . versionMinor ) . to . equal ( expectedVersion & 0xffff ) ; expect ( header . flags ) . to . equal ( ImdlFlags . None ) ; expect ( header . numElementsIncluded ) . to . equal ( 1 ) ; expect ( header . numElementsExcluded ) . to . equal ( 0 ) ; const projExt = imodel . projectExtents ; expect ( projExt . xLength ( ) ) . to . equal ( header . contentRange . xLength ( ) ) ; expect ( projExt . yLength ( ) ) . to . equal ( header . contentRange . yLength ( ) ) ; expect ( header . contentRange . zLength ( ) ) . to . equal ( 0 ) ; } ; const modelTree = await getTileTree ( imodel , "0x1c" ) ; await test ( modelTree , CurrentImdlVersion . Combined , "-b-0-0-0-0-1" ) ; const v3Props = await IModelApp . tileAdmin . requestTileTreeProps ( imodel , "0x1c" ) ; expect ( v3Props ) . not . to . be . undefined ; const options = { is3d : true , batchType : BatchType . Primary , edgesRequired : false , allowInstancing : false } ; const params = iModelTileTreeParamsFromJSON ( v3Props , imodel , "0x1c" , options ) ; const v3Tree = new IModelTileTree ( params , { edges : EdgeType . None , type : BatchType . Primary } ) ; await test ( v3Tree , 0x00030000 , "_3_0_0_0_0_0_1" ) ; } ) ; it ( "should use a different tile tree when view flags change" , async ( ) => { const modelId = "0x1c" ; await imodel . models . load ( modelId ) ; const model = imodel . models . getLoaded ( modelId ) as GeometricModelState ; const viewState = fakeViewState ( imodel ) ; const treeRef = model . createTileTreeReference ( viewState ) ; const noEdges = treeRef . treeOwner ; viewState . viewFlags = viewState . viewFlags . with ( "visibleEdges" , true ) ; const edges = treeRef . treeOwner ; expect ( edges ) . not . to . equal ( noEdges ) ; const edges2 = treeRef . treeOwner ; expect ( edges2 ) . to . equal ( edges ) ; viewState . viewFlags = viewState . viewFlags . with ( "visibleEdges" , false ) ; const noEdges2 = treeRef . treeOwner ; expect ( noEdges2 ) . to . equal ( noEdges ) ; } ) ; } ) ; describe . skip ( "TileAdmin" , ( ) => { let theIModel ; const cleanup = async ( ) => { if ( theIModel ) { await theIModel . close ( ) ; theIModel = undefined ; } if ( IModelApp . initialized ) await TestUtility . shutdownFrontend ( ) ; } ; after ( async ( ) => { await cleanup ( ) ; } ) ; class TileAdminApp extends MockRender . App { public static async start ( props ) { await cleanup ( ) ; await super . startup ( { tileAdmin : props } ) ; theIModel = await SnapshotConnection . openFile ( "mirukuru.ibim" ) ; return theIModel ; } public static async restart ( props ) { await this . stop ( ) ; return this . start ( props ) ; } public static async stop ( ) { if ( undefined !== theIModel ) { await theIModel . close ( ) ; theIModel = undefined ; } await TestUtility . shutdownFrontend ( ) ; } } it ( "should omit or load edges based on configuration and view flags" , async ( ) => { class App extends TileAdminApp { private static async testPrimaryTree ( imodel , expectedTreeIdStr , animationId ? ) { const requestWithoutEdges = true ; let expectedTreeIdStrNoEdges = expectedTreeIdStr ; if ( requestWithoutEdges ) { const lastIndex = expectedTreeIdStr . lastIndexOf ( "0x" ) ; expectedTreeIdStrNoEdges = `${ expectedTreeIdStr . substring ( 0 , lastIndex ) }E:0_${ expectedTreeIdStr . substring ( lastIndex ) }` ; } const treeId = { type : BatchType . Primary , edges : EdgeType . None , animationId } ; let actualTreeIdStr = iModelTileTreeIdToString ( "0x1c" , treeId , IModelApp . tileAdmin ) ; expect ( actualTreeIdStr ) . to . equal ( expectedTreeIdStrNoEdges ) ; const treePropsNoEdges = await IModelApp . tileAdmin . requestTileTreeProps ( imodel , actualTreeIdStr ) ; expect ( treePropsNoEdges . id ) . to . equal ( actualTreeIdStr ) ; const treeNoEdges = await getTileTree ( imodel , "0x1c" , false , animationId ) ; expect ( treeNoEdges . id ) . to . equal ( actualTreeIdStr ) ; const treeNoEdges2 = await getTileTree ( imodel , "0x1c" , false , animationId ) ; expect ( treeNoEdges2 ) . to . equal ( treeNoEdges ) ; expect ( await this . rootTileHasEdges ( treeNoEdges , imodel ) ) . to . equal ( ! requestWithoutEdges ) ; treeId . edges = EdgeType . NonIndexed ; actualTreeIdStr = iModelTileTreeIdToString ( "0x1c" , treeId , IModelApp . tileAdmin ) ; expect ( actualTreeIdStr ) . to . equal ( expectedTreeIdStr ) ; const treeProps = await IModelApp . tileAdmin . requestTileTreeProps ( imodel , actualTreeIdStr ) ; expect ( treeProps . id ) . to . equal ( actualTreeIdStr ) ; const tree = await getTileTree ( imodel , "0x1c" , true , animationId ) ; expect ( tree . id ) . to . equal ( actualTreeIdStr ) ; expect ( tree ) . not . to . equal ( treeNoEdges ) ; const tree2 = await getTileTree ( imodel , "0x1c" , true , animationId ) ; expect ( tree2 ) . to . equal ( tree ) ; expect ( await this . rootTileHasEdges ( tree , imodel ) ) . to . be . true ; const treeNoEdges3 = await getTileTree ( imodel , "0x1c" , false , animationId ) ; expect ( treeNoEdges3 ) . not . to . equal ( tree ) ; } private static async rootTileHasEdges ( tree , imodel ) { const response = await tree . staticBranch . requestContent ( ) as Uint8Array ; expect ( response ) . not . to . be . undefined ; expect ( response ) . instanceof ( Uint8Array ) ; const stream = ByteStream . fromUint8Array ( response ) ; const reader = ImdlReader . create ( { stream , iModel : imodel , modelId : "0x1c" , is3d : true , system : IModelApp . renderSystem } ) ; expect ( reader ) . not . to . be . undefined ; const meshes = ( reader as any ) . _meshes ; expect ( meshes ) . not . to . be . undefined ; for ( const key of Object . keys ( meshes ) ) { const mesh = meshes [ key ] ; for ( const primitive of mesh . primitives ) if ( undefined !== primitive . edges ) return true ; } return false ; } public static async test ( imodel ) { const version = CurrentImdlVersion . Major . toString ( 16 ) ; await this . testPrimaryTree ( imodel , `${ version }_1-0x1c` ) ; } } const myImodel = await App . start ( { } ) ; await App . test ( myImodel ) ; await App . stop ( ) ; } ) ; it ( "should honor maximum major tile format version" , async ( ) => { class App extends TileAdminApp { public static async testMajorVersion ( maximumMajorTileFormatVersion , expectedMajorVersion ) { const imodel = await App . start ( { maximumMajorTileFormatVersion } ) ; let treeId = "0x1c" ; if ( undefined === maximumMajorTileFormatVersion || maximumMajorTileFormatVersion >= 4 ) { const v = undefined !== maximumMajorTileFormatVersion ? maximumMajorTileFormatVersion : CurrentImdlVersion . Major ; treeId = `${ v . toString ( 16 ) }_1-0x1c` ; } const tree = await IModelApp . tileAdmin . requestTileTreeProps ( imodel , treeId ) ; expect ( tree ) . not . to . be . undefined ; expect ( tree . id ) . to . equal ( treeId ) ; expect ( tree . formatVersion ) . not . to . be . undefined ; const majorVersion = ( tree . formatVersion ! ) >>> 0x10 ; expect ( majorVersion ) . to . equal ( expectedMajorVersion ) ; expect ( tree . rootTile . contentId ) . to . equal ( "0/0/0/0/1" ) ; await App . stop ( ) ; } } await App . testMajorVersion ( 3 , 3 ) ; await App . testMajorVersion ( 1 , 3 ) ; await App . testMajorVersion ( 4 , 4 ) ; await App . testMajorVersion ( undefined , CurrentImdlVersion . Major ) ; } ) ; it ( "should form expected content request" , async ( ) => { class App extends TileAdminApp { public static async test ( useProjectExtents ) { const imodel = await App . start ( { useProjectExtents } ) ; const flags = useProjectExtents ? "1" : "0" ; const treeId = `8_${ flags }-0x1c` ; const treeProps = await IModelApp . tileAdmin . requestTileTreeProps ( imodel , treeId ) ; const qualifier = treeProps . contentIdQualifier ; expect ( qualifier !== undefined ) . to . equal ( useProjectExtents ) ; if ( undefined !== qualifier ) expect ( qualifier . length > 0 ) . to . be . true ; const options = { is3d : true , batchType : BatchType . Primary , edgesRequired : true , allowInstancing : true } ; const params = iModelTileTreeParamsFromJSON ( treeProps , imodel , "0x1c" , options ) ; const tree = new IModelTileTree ( params , { edges : EdgeType . NonIndexed , type : BatchType . Primary } ) ; const intfc = IModelTileRpcInterface . getClient ( ) ; const generateTileContent = intfc . generateTileContent ; intfc . generateTileContent = async ( _token , tileTreeId , _contentId , guid ) => { expect ( tileTreeId ) . to . equal ( treeId ) ; expect ( guid ) . not . to . be . undefined ; if ( ! useProjectExtents ) expect ( guid ) . to . equal ( "first" ) ; else expect ( guid ) . to . equal ( `first_${ qualifier ! }` ) ; return TileContentSource . Backend ; } ; await tree . staticBranch . requestContent ( ) ; intfc . generateTileContent = generateTileContent ; await App . stop ( ) ; } } await App . test ( false ) ; await App . test ( true ) ; } ) ; } ) ;