UNPKG

3d-tiles-renderer

Version:

https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification

297 lines (200 loc) 6.26 kB
import { Mesh, MeshBasicMaterial, PlaneGeometry } from 'three'; export const TILE_X = Symbol( 'TILE_X' ); export const TILE_Y = Symbol( 'TILE_Y' ); export const TILE_LEVEL = Symbol( 'TILE_LEVEL' ); // Base class for supporting tiled images with a consistent size / resolution per tile export class ImageFormatPlugin { get tiling() { return this.imageSource.tiling; } constructor( options = {} ) { const { pixelSize = 0.01, center = false, useRecommendedSettings = true, imageSource = null, } = options; this.priority = - 10; this.tiles = null; // tiling scheme this.imageSource = imageSource; // options this.pixelSize = pixelSize; this.center = center; this.useRecommendedSettings = useRecommendedSettings; } // Plugin functions init( tiles ) { if ( this.useRecommendedSettings ) { tiles.errorTarget = 1; // TODO: apply skip traversal settings here once supported, as well, for faster loading } this.tiles = tiles; this.imageSource.fetchOptions = tiles.fetchOptions; this.imageSource.fetchData = ( url, options ) => { tiles.invokeAllPlugins( plugin => url = plugin.preprocessURL ? plugin.preprocessURL( url, null ) : url ); return tiles.invokeOnePlugin( plugin => plugin !== this && plugin.fetchData && plugin.fetchData( url, options ) ); }; } async loadRootTileSet() { const { tiles, imageSource } = this; let url = tiles.rootURL; tiles.invokeAllPlugins( plugin => url = plugin.preprocessURL ? plugin.preprocessURL( url, null ) : url ); await imageSource.init( url ); return this.getTileset( url ); } async parseToMesh( buffer, tile, extension, uri, abortSignal ) { if ( abortSignal.aborted ) { return null; } // Construct texture const tx = tile[ TILE_X ]; const ty = tile[ TILE_Y ]; const level = tile[ TILE_LEVEL ]; const texture = await this.imageSource.processBufferToTexture( buffer ); // clean up the texture if it's not going to be used. if ( abortSignal.aborted ) { texture.dispose(); texture.image.close(); return null; } this.imageSource.setData( tx, ty, level, texture ); // Construct mesh let sx = 1, sy = 1; let x = 0, y = 0, z = 0; const boundingBox = tile.boundingVolume.box; if ( boundingBox ) { [ x, y, z ] = boundingBox; sx = boundingBox[ 3 ]; sy = boundingBox[ 7 ]; } // adjust the geometry transform itself rather than the mesh because it reduces the artifact errors // when using batched mesh rendering. const mesh = new Mesh( new PlaneGeometry( 2 * sx, 2 * sy ), new MeshBasicMaterial( { map: texture, transparent: true } ) ); mesh.position.set( x, y, z ); return mesh; } preprocessNode( tile ) { // generate children const { tiling } = this; const maxLevel = tiling.maxLevel; const level = tile[ TILE_LEVEL ]; if ( level < maxLevel && tile.parent !== null ) { this.expandChildren( tile ); } } disposeTile( tile ) { const tx = tile[ TILE_X ]; const ty = tile[ TILE_Y ]; const level = tile[ TILE_LEVEL ]; const { imageSource } = this; if ( imageSource.has( tx, ty, level ) ) { // only dispose of the image data if it hasn't been aborted imageSource.release( tx, ty, level ); } } // Local functions getTileset( baseUrl ) { const { tiling, tiles } = this; const minLevel = tiling.minLevel; const { tileCountX, tileCountY } = tiling.getLevel( minLevel ); // generate all children for the root const children = []; for ( let x = 0; x < tileCountX; x ++ ) { for ( let y = 0; y < tileCountY; y ++ ) { const child = this.createChild( x, y, minLevel ); if ( child !== null ) { children.push( child ); } } } // generate tile set const tileset = { asset: { version: '1.1' }, geometricError: 1e5, root: { refine: 'REPLACE', geometricError: 1e5, boundingVolume: this.createBoundingVolume( 0, 0, - 1 ), children, [ TILE_LEVEL ]: - 1, [ TILE_X ]: 0, [ TILE_Y ]: 0, } }; tiles.preprocessTileSet( tileset, baseUrl ); return tileset; } getUrl( x, y, level ) { return this.imageSource.getUrl( x, y, level ); } createBoundingVolume( x, y, level ) { const { center, pixelSize, tiling } = this; const { pixelWidth, pixelHeight } = tiling.getLevel( tiling.maxLevel ); // calculate the world space bounds position from the range const [ minX, minY, maxX, maxY ] = level === - 1 ? tiling.getFullBounds( true ) : tiling.getTileBounds( x, y, level, true ); let extentsX = ( maxX - minX ) / 2; let extentsY = ( maxY - minY ) / 2; let centerX = minX + extentsX; let centerY = minY + extentsY; if ( center ) { centerX -= 0.5; centerY -= 0.5; } // scale the fields centerX *= pixelWidth * pixelSize; extentsX *= pixelWidth * pixelSize; centerY *= pixelHeight * pixelSize; extentsY *= pixelHeight * pixelSize; // return bounding box return { box: [ // center centerX, centerY, 0, // x, y, z half vectors extentsX, 0.0, 0.0, 0.0, extentsY, 0.0, 0.0, 0.0, 0.0, ], }; } createChild( x, y, level ) { const { pixelSize, tiling } = this; if ( ! tiling.getTileExists( x, y, level ) ) { return null; } // the scale ration of the image at this level const { pixelWidth, pixelHeight } = tiling.getLevel( tiling.maxLevel ); const { pixelWidth: levelWidth, pixelHeight: levelHeight } = tiling.getLevel( level ); const geometricError = pixelSize * ( Math.max( pixelWidth / levelWidth, pixelHeight / levelHeight ) - 1 ); // Generate the node return { refine: 'REPLACE', geometricError: geometricError, boundingVolume: this.createBoundingVolume( x, y, level ), content: { uri: this.getUrl( x, y, level ), }, children: [], // save the tile params so we can expand later [ TILE_X ]: x, [ TILE_Y ]: y, [ TILE_LEVEL ]: level, }; } expandChildren( tile ) { const level = tile[ TILE_LEVEL ]; const x = tile[ TILE_X ]; const y = tile[ TILE_Y ]; for ( let cx = 0; cx < 2; cx ++ ) { for ( let cy = 0; cy < 2; cy ++ ) { const child = this.createChild( 2 * x + cx, 2 * y + cy, level + 1 ); if ( child ) { tile.children.push( child ); } } } } }