123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- import { TilesRenderer } from '../base/TilesRenderer.js';
- import { ThreeB3DMLoader } from './ThreeB3DMLoader.js';
- import { TilesGroup } from './TilesGroup.js';
- import {
- Matrix4,
- Box3,
- Sphere,
- Vector3,
- Group,
- Vector2,
- Math as MathUtils,
- Box3Helper,
- Quaternion,
- Frustum,
- Ray,
- Mesh
- } from 'three';
- const DEG2RAD = MathUtils.DEG2RAD;
- const tempMat = new Matrix4();
- const tempQuaternion = new Quaternion();
- const tempVector = new Vector3();
- const resVector = new Vector2();
- const vecX = new Vector3();
- const vecY = new Vector3();
- const vecZ = new Vector3();
- const ray = new Ray();
- const _sphere = new Sphere();
- function emptyRaycast () {};
- class ThreeTilesRenderer extends TilesRenderer {
- get displayBounds() {
- return this._displayBounds;
- }
- set displayBounds( val ) {
- if ( val !== this.displayBounds ) {
- this._displayBounds = val;
- this.traverse( t => {
- const scene = t.cached.scene;
- const boxHelper = t.cached.boxHelper;
- if ( scene ) {
- if ( val ) {
- scene.add( boxHelper );
- boxHelper.updateMatrixWorld( true );
- } else {
- scene.remove( boxHelper );
- }
- }
- } );
- }
- }
- constructor( url, cameras, renderer ) {
- super( url );
- this.group = new TilesGroup( this );
- this.cameras = Array.isArray( cameras ) ? cameras : [ cameras ];
- this.frustums = [];
- this.renderer = renderer;
- this.activeSet = new Set();
- this.visibleSet = new Set();
- }
- /* Public API */
- getBounds( box ) {
- if ( ! this.root ) {
- return false;
- }
- const cached = this.root.cached;
- const boundingBox = cached.box;
- const obbMat = cached.boxTransform;
- const transformMat = cached.transform;
- box.copy( boundingBox );
- tempMat.multiplyMatrices( transformMat, obbMat );
- box.applyMatrix4( tempMat );
- return true;
- }
- raycast( raycaster, intersects ) {
- const activeSet = this.activeSet;
- const group = this.group;
- this.traverse( tile => {
- const cached = tile.cached;
- const groupMatrixWorld = group.matrixWorld;
- const transformMat = cached.transform;
- tempMat.copy( groupMatrixWorld );
- tempMat.multiply( transformMat );
- const sphere = cached.sphere;
- if ( sphere ) {
- _sphere.copy( sphere );
- _sphere.applyMatrix4( tempMat );
- if ( ! raycaster.ray.intersectsSphere( _sphere ) ) {
- return true;
- }
- }
- const boundingBox = cached.box;
- const obbMat = cached.boxTransform;
- if ( boundingBox ) {
- tempMat.multiply( obbMat );
- tempMat.getInverse( tempMat );
- ray.copy( raycaster.ray ).applyMatrix4( tempMat );
- if ( ! ray.intersectsBox( boundingBox ) ) {
- return true;
- }
- }
- // TODO: check region
- const scene = cached.scene;
- if ( activeSet.has( scene ) ) {
- raycaster.intersectObject( scene, true, intersects );
- scene.traverse( c => {
- if ( c !== cached.boxHelper ) {
- Object.getPrototypeOf( c ).raycast.call( c, raycaster, intersects );
- }
- } );
- return true;
- }
- } );
- }
- /* Overriden */
- update() {
- const group = this.group;
- const renderer = this.renderer;
- const cameras = this.cameras;
- const frustums = this.frustums;
- // automatically scale the array of frustums to match the cameras
- while ( frustums.length > cameras.length ) {
- frustums.pop();
- }
- while ( frustums.length < cameras.length ) {
- frustums.push( new Frustum() );
- }
- // store the camera frustums in the 3d tiles root frame
- for ( let i = 0, l = frustums.length; i < l; i ++ ) {
- const camera = cameras[ i ];
- const frustum = frustums[ i ];
- tempMat.copy( group.matrixWorld );
- tempMat.premultiply( camera.matrixWorldInverse );
- tempMat.premultiply( camera.projectionMatrix );
- frustum.setFromMatrix( tempMat );
- }
- // store the resolution of the render
- renderer.getSize( resVector );
- resVector.multiplyScalar( renderer.getPixelRatio() );
- super.update();
- }
- preprocessNode( tile, parentTile, tileSetDir ) {
- super.preprocessNode( tile, parentTile, tileSetDir );
- const transform = new Matrix4();
- if ( tile.transform ) {
- const transformArr = tile.transform;
- for ( let i = 0; i < 16; i ++ ) {
- transform.elements[ i ] = transformArr[ i ];
- }
- } else {
- transform.identity();
- }
- if ( parentTile ) {
- transform.multiply( parentTile.cached.transform );
- }
- let box = null;
- let boxTransform = null;
- if ( 'box' in tile.boundingVolume ) {
- const data = tile.boundingVolume.box;
- box = new Box3();
- boxTransform = new Matrix4();
- // get the extents of the bounds in each axis
- vecX.set( data[ 3 ], data[ 4 ], data[ 5 ] );
- vecY.set( data[ 6 ], data[ 7 ], data[ 8 ] );
- vecZ.set( data[ 9 ], data[ 10 ], data[ 11 ] );
- const scaleX = vecX.length();
- const scaleY = vecY.length();
- const scaleZ = vecZ.length();
- vecX.normalize();
- vecY.normalize();
- vecZ.normalize();
- // create the oriented frame that the box exists in
- boxTransform.set(
- vecX.x, vecY.x, vecZ.x, data[ 0 ],
- vecX.y, vecY.y, vecZ.y, data[ 1 ],
- vecX.z, vecY.z, vecZ.z, data[ 2 ],
- 0, 0, 0, 1
- );
- // scale the box by the extents
- box.min.set( - scaleX, - scaleY, - scaleZ );
- box.max.set( scaleX, scaleY, scaleZ );
- }
- let sphere = null;
- if ( 'sphere' in tile.boundingVolume ) {
- const data = tile.boundingVolume.sphere;
- sphere = new Sphere();
- sphere.center.set( data[ 0 ], data[ 1 ], data[ 2 ] );
- sphere.radius = data[ 3 ];
- } else if ( 'box' in tile.boundingVolume ) {
- sphere = new Sphere();
- box.getBoundingSphere( sphere );
- boxTransform.decompose( sphere.center, tempQuaternion, tempVector );
- }
- let region = null;
- if ( 'region' in tile.boundingVolume ) {
- console.warn( 'ThreeTilesRenderer: region bounding volume not supported.' );
- }
- tile.cached = {
- loadIndex: 0,
- transform,
- active: false,
- box,
- boxTransform,
- sphere,
- region,
- boxHelper: null,
- scene: null,
- };
- }
- parseTile( buffer, tile ) {
- tile._loadIndex = tile._loadIndex || 0;
- tile._loadIndex ++;
- // TODO: 90 degree rotation must be applied to GLTF file to resolve "up"
- const loadIndex = tile._loadIndex;
- return new ThreeB3DMLoader().parse( buffer ).then( res => {
- if ( tile._loadIndex !== loadIndex ) {
- return;
- }
- const cached = tile.cached;
- const cachedBox = cached.box;
- const cachedBoxMat = cached.boxTransform;
- // add a helper group to represent the obb rotation matrix
- const boxHelper = new Box3Helper( cachedBox );
- const boxHelperGroup = new Group();
- cachedBoxMat.decompose( boxHelperGroup.position, boxHelperGroup.quaternion, boxHelperGroup.scale );
- boxHelperGroup.add( boxHelper );
- boxHelperGroup.updateMatrixWorld( true );
- const scene = res.scene;
- cached.transform.decompose( scene.position, scene.quaternion, scene.scale );
- scene.traverse( c => c.frustumCulled = false );
- cached.boxHelper = boxHelperGroup;
- cached.scene = res.scene;
- if ( this.displayBounds ) {
- cached.scene.add( cached.boxHelper );
- }
- // We handle raycasting in a custom way so remove it from here
- boxHelper.raycast = emptyRaycast;
- scene.traverse( c => {
- c.raycast = emptyRaycast;
- } );
- } );
- }
- disposeTile( tile ) {
- const cached = tile.cached;
- if ( cached.scene ) {
- const scene = cached.scene;
- if ( scene.parent ) {
- scene.parent.remove( scene );
- }
- scene.traverse( c => {
- if ( c.geometry ) {
- c.geometry.dispose();
- }
- if ( c.material ) {
- c.material.dispose();
- // TODO: dispose textures
- }
- } );
- cached.scene = null;
- cached.boxHelper = null;
- }
- tile._loadIndex ++;
- }
- setTileVisible( tile, visible ) {
- const scene = tile.cached.scene;
- const visibleSet = this.visibleSet;
- const group = this.group;
- if ( visible ) {
- if ( scene && ! scene.parent ) {
- group.add( scene );
- visibleSet.add( scene );
- scene.updateMatrixWorld( true );
- }
- } else {
- group.remove( scene );
- visibleSet.delete( scene );
- }
- }
- setTileActive( tile, active ) {
- const cached = tile.cached;
- const activeSet = this.activeSet;
- if ( active !== cached.active ) {
- cached.active = active;
- if ( active ) {
- activeSet.add( cached.scene );
- } else {
- activeSet.delete( cached.scene );
- }
- }
- }
- calculateError( tile ) {
- const cached = tile.cached;
- const cameras = this.cameras;
- // TODO: Use the content bounding volume here?
- const boundingVolume = tile.boundingVolume;
- const transformMat = cached.transform;
- if ( 'box' in boundingVolume ) {
- const group = this.group;
- const boundingBox = cached.box;
- const obbMat = cached.boxTransform;
- // TODO: these can likely be cached? Or the world transform mat can be used
- // transformMat can be rolled into oobMat
- tempMat.copy( transformMat );
- tempMat.multiply( obbMat );
- tempMat.getInverse( tempMat );
- let minError = Infinity;
- for ( let i = 0, l = cameras.length; i < l; i ++ ) {
- // transform camera position into local frame of the tile bounding box
- const cam = cameras[ i ];
- tempVector.copy( cam.position );
- tempVector.applyMatrix4( tempMat );
- let error;
- if ( cam.isOrthographic ) {
- const w = cam.right - cam.left;
- const h = cam.top - cam.bottom;
- const pixelSize = Math.Max( h, w ) / Math.Max( resVector.width, resVector.height );
- error = tile.geometricError / pixelSize;
- } else {
- const distance = boundingBox.distanceToPoint( tempVector );
- // assume the scales on all axes are uniform.
- let scale;
- // account for tile scale.
- tempVector.setFromMatrixScale( tempMat );
- scale = tempVector.x;
- // account for parent group scale. Divide because this matrix has not been inverted like the previous one.
- tempVector.setFromMatrixScale( group.matrixWorld );
- scale /= tempVector.x;
- if ( Math.abs( Math.max( scale.x - scale.y, scale.x - scale.z ) ) > 1e-6 ) {
- console.warn( 'ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when claculating screen space error.' );
- }
- const scaledDistance = distance * scale;
- const sseDenominator = 2 * Math.tan( 0.5 * cam.fov * DEG2RAD );
- error = ( tile.geometricError * resVector.height ) / ( scaledDistance * sseDenominator );
- }
- minError = Math.min( minError, error );
- }
- return minError;
- } else if ( 'sphere' in boundingVolume ) {
- // const sphere = cached.sphere;
- console.warn( 'ThreeTilesRenderer : Sphere bounds not supported.' );
- } else if ( 'region' in boundingVolume ) {
- // unsupported
- console.warn( 'ThreeTilesRenderer : Region bounds not supported.' );
- }
- return Infinity;
- }
- tileInView( tile ) {
- // TODO: we should use the more precise bounding volumes here if possible
- // cache the root-space planes
- const sphere = tile.cached.sphere;
- if ( sphere ) {
- _sphere.copy( sphere );
- _sphere.applyMatrix4( tile.cached.transform );
- const frustums = this.frustums;
- for ( let i = 0, l = frustums.length; i < l; i ++ ) {
- const frustum = frustums[ i ];
- if ( frustum.intersectsSphere( _sphere ) ) {
- return true;
- }
- }
- return false;
- }
- return true;
- }
- }
- export { ThreeTilesRenderer };
|